diff -u --recursive --new-file v1.3.71/linux/CREDITS linux/CREDITS
--- v1.3.71/linux/CREDITS	Tue Mar  5 10:10:43 1996
+++ linux/CREDITS	Fri Mar  8 08:11:46 1996
@@ -180,6 +180,13 @@
 S: Fremont, California 94539
 S: USA
 
+N: Gordon Chaffee
+E: chaffee@plateau.cs.berkeley.edu
+D: vfat filesystem
+S: 3674 Oakwood Terrace #201
+S: Fremont, CA 94536
+S: USA
+
 N: Raymond Chen
 E: raymondc@microsoft.com
 D: Author of Configure script
@@ -235,6 +242,15 @@
 S: 2312 SB  Leiden
 S: The Netherlands
 
+N: David Davies
+E: davies@wanton.lkg.dec.com
+S: Digital Equipment Corporation
+S: 550 King Street
+S: Littleton, MA 01460
+S: U.S.A.
+D: Network driver author - depca, ewrk3 and de4x5
+D: Wrote shared interrupt support
+
 N: Wayne Davison
 E: davison@borland.com
 D: Second extended file system co-designer
@@ -378,10 +394,19 @@
 E: philipg@onsett.com
 D: Kernel / timekeeping stuff
 
+N: Miquel van Smoorenburg
+E: miquels@cistron.nl
+D: Kernel and net hacker. Sysvinit, minicom. doing Debian stuff.
+S: Cistron Internet Services
+S: PO-Box 297
+S: 2400 AG, Alphen aan den Rijn
+S: The Netherlands
+
 N: Danny ter Haar
 E: dth@cistron.nl
 D: /proc/procinfo, reboot on panic , kernel pre-patch tester ;)
-S: PObox 297
+S: Cistron Internet Services
+S: PO-Box 297
 S: 2400 AG, Alphen aan den Rijn
 S: The Netherlands
 
diff -u --recursive --new-file v1.3.71/linux/Documentation/Configure.help linux/Documentation/Configure.help
--- v1.3.71/linux/Documentation/Configure.help	Tue Mar  5 10:10:43 1996
+++ linux/Documentation/Configure.help	Fri Mar  8 10:03:32 1996
@@ -1039,6 +1039,13 @@
   understand if you enable this; it will enlarge your kernel by about
   12KB. If in doubt, say Y.
 
+Automatic Disk Geometery Translation 
+CONFIG_SCSI_AUTO_BIOSP
+  When this is set to Y, Linux will examine the parition table to
+  determine the mapping used under the other operating systems (e.g.
+  DOS), and set these parameters to the determined values, or if the
+  disk has no valid partition table, to an optimal value.
+
 AdvanSys SCSI support
 CONFIG_SCSI_ADVANSYS
   This is a driver for all SCSI host adaptors manufactured by
@@ -1534,6 +1541,21 @@
   Multiple-Ethernet-mini-HOWTO, available from
   sunsite.unc.edu:/pub/Linux/docs/HOWTO/mini.
 
+SMC 9194 Support
+CONFIG_SMC9194
+  This has support for the SMC9xxx based Ethernet cards.  Choose this option
+  if you have a DELL laptop with the docking station, or another SMC9192/9194
+  based chipset.  Say Y if you want it compiled into the kernel, and read  
+  the Ethernet-HOWTO, available via ftp (user: anonymous) in
+  sunsite.unc.edu:/pub/Linux/docs/HOWTO.  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.
+
 AMD LANCE and PCnet (AT1500 and NE2100) support
 CONFIG_LANCE
   If you have a network (ethernet) card of this type, say Y and read
@@ -2626,22 +2648,19 @@
   program via anonymous ftp which is able to configure this driver
   during runtime. If you want this, say Y.
 
-QIC-117 tape support
+Ftape (QIC-80/Travan) support
 CONFIG_FTAPE
-  This option is obsolete as of Linux v1.3.34. If you would like to
-  use a tape drive that uses the floppy disk controller, like QIC-40,
-  QIC-80, QIC-117, QIC-3010 (examples: Colorado Jumbo or Conner
-  Tape-Stor), you want to read the Ftape-HOWTO, available via ftp
-  (user: anonymous) from sunsite.unc.edu:/pub/Linux/docs/HOWTO. Then
-  get the ftape distribution v2.04 or higher from
-  sunsite.unc.edu:/pub/Linux/kernel/tapes/. Tape drives that attach to
-  the parallel port, like the Colorado Tracker, are not yet supported
-  by Linux.
-
-number of ftape buffers
-NR_FTAPE_BUFFERS 3
-  This option is obsolete since Linux v1.3.34. Upgrade your ftape
-  distribution to v2.04.
+  If you have a tape drive that is connected to your floppy
+  controller, say Y here. Some tape drives (like the Iomega Ditto
+  3200) come with a high speed controller of its own.  These drives
+  (and their companion controller) is also supported. If you have a
+  special controller (such as the CMS FC-10, FC-20, Iomega Mach-II, or
+  Ditto Dash), you must configure it by editing the file
+  drivers/char/ftape/Makefile.  This driver is also available as a
+  runtime loadable 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.
 
 Zilog serial support
 CONFIG_SUN_ZS
diff -u --recursive --new-file v1.3.71/linux/Documentation/filesystems/vfat.txt linux/Documentation/filesystems/vfat.txt
--- v1.3.71/linux/Documentation/filesystems/vfat.txt	Thu Jan  1 02:00:00 1970
+++ linux/Documentation/filesystems/vfat.txt	Tue Mar  5 13:03:26 1996
@@ -0,0 +1,455 @@
+USING VFAT
+----------------------------------------------------------------------
+To use the vfat filesystem, use the filesystem type 'vfat'.  i.e.
+  mount -t vfat /dev/fd0 /mnt
+
+No special partition formatter is required.  mkdosfs will work fine
+if you want to format from within Linux.
+
+VFAT MOUNT OPTIONS
+----------------------------------------------------------------------
+uni_xlate     -- Translate unhandled Unicode characters to special
+		 escaped sequences.  This would let you backup and
+		 restore filenames that are created with any Unicode
+		 characters.  Until Linux supports Unicode for real,
+		 this gives you an alternative.  Without this option,
+		 a '?' is used when no translation is possible.  The
+		 escape character is ':' because it is otherwise
+		 illegal on the vfat filesystem.  The escape sequence
+		 that gets used, where u is the unicode character, is:
+			':', (u & 0x3f), ((u>>6) & 0x3f), (u>>12),
+posix         -- Allow names of same letters, different case such as
+                 'LongFileName' and 'longfilename' to coexist.  This has some
+                 problems currently because 8.3 conflicts are not handled
+                 correctly for Posix filesystem compliance.
+nonumtail     -- When creating 8.3 aliases, normally the alias will
+                 end in '~1' or tilde followed by some number.  If this
+                 option is set, then if the filename is 
+                 "longfilename.txt" and "longfile.txt" does not
+                 currently exist in the directory, 'longfile.txt' will
+                 be the short alias instead of 'longfi~1.txt'. 
+                  
+quiet         -- Stops printing certain warning messages.
+
+
+TODO
+----------------------------------------------------------------------
+* Need to get rid of the raw scanning stuff.  Instead, always use
+  a get next directory entry approach.  The only thing left that uses
+  raw scanning is the directory renaming code.
+
+* Need to add dcache_lookup code msdos filesystem.  This means the
+  directories need to be versioned like the vfat filesystem.
+
+* Add support for different codepages.  Right now, we only support
+  the a single English codepage.
+
+* Fix the Posix filesystem support to work in 8.3 space.  This involves
+  renaming aliases if a conflict occurs between a new filename and
+  an old alias.  This is quite a mess.
+
+
+POSSIBLE PROBLEMS
+----------------------------------------------------------------------
+* vfat_valid_longname does not properly checked reserved names.
+* When a volume name is the same as a directory name in the root
+  directory of the filesystem, the directory name sometimes shows
+  up empty an empty file.
+
+BUG REPORTS
+----------------------------------------------------------------------
+If you have trouble with the VFAT filesystem, mail bug reports to
+chaffee@bugs-bunny.cs.berkeley.edu.  Please specify the filename
+and the operation that gave you trouble.
+
+
+NOTES ON THE STRUCTURE OF THE VFAT FILESYTEM
+----------------------------------------------------------------------
+(This documentation was provided by Galen C. Hunt <gchunt@cs.rochester.edu>
+ and lightly annotated by Gordon Chaffee).
+
+This document presents a very rough, technical overview of my
+knowledge of the extended FAT file system used in Windows NT 3.5 and
+Windows 95.  I don't guarantee that any of the following is correct,
+but it appears to be so.
+
+The extended FAT file system is almost identical to the FAT
+file system used in DOS versions up to and including 6.223410239847
+:-).  The significant change has been the addition of long file names.
+Theses names support up to 255 characters including spaces and lower
+case characters as opposed to the traditional 8.3 short names.
+
+Here is the description of the traditional FAT entry in the current
+Windows 95 filesystem:
+
+        struct directory { // Short 8.3 names 
+                unsigned char name[8];          // file name 
+                unsigned char ext[3];           // file extension 
+                unsigned char attr;             // attribute byte 
+		unsigned char lcase;		// Case for base and extension
+		unsigned char ctime_ms;		// Creation time, milliseconds
+		unsigned char ctime[2];		// Creation time
+		unsigned char cdate[2];		// Creation date
+		unsigned char adate[2];		// Last access date
+		unsigned char reserved[2];	// reserved values (ignored) 
+                unsigned char time[2];          // time stamp 
+                unsigned char date[2];          // date stamp 
+                unsigned char start[2];         // starting cluster number 
+                unsigned char size[4];          // size of the file 
+        };
+
+The lcase field specifies if the base and/or the extension of an 8.3
+name should be capitalized.  This field does not seem to be used by
+Windows 95 but it is used by Windows NT.  The case of filenames is not
+completely compatible from Windows NT to Windows 95.  It is not completely
+compatible in the reverse direction, however.  Filenames that fit in
+the 8.3 namespace and are written on Windows NT to be lowercase will
+show up as uppercase on Windows 95.
+
+Note that the "start" and "size" values are actually little
+endian integer values.  The descriptions of the fields in this
+structure are public knowledge and can be found elsewhere.
+
+With the extended FAT system, Microsoft has inserted extra
+directory entries for any files with extended names.  (Any name which
+legally fits within the old 8.3 encoding scheme does not have extra
+entries.)  I call these extra entries slots.  Basically, a slot is a
+specially formatted directory entry which holds up to 13 characters of
+a files extended name.  Think of slots as additional labeling for the
+directory entry of the file to which they correspond.  Microsoft
+prefers to refer to the 8.3 entry for a file as its alias and the
+extended slot directory entries as the file name. 
+
+The C structure for a slot directory entry follows:
+
+        struct slot { // Up to 13 characters of a long name 
+                unsigned char id;               // sequence number for slot 
+                unsigned char name0_4[10];      // first 5 characters in name 
+                unsigned char attr;             // attribute byte
+                unsigned char reserved;         // always 0 
+                unsigned char alias_checksum;   // checksum for 8.3 alias 
+                unsigned char name5_10[12];     // 6 more characters in name
+                unsigned char start[2];         // starting cluster number
+                unsigned char name11_12[4];     // last 2 characters in name
+        };
+
+If the layout of the slots looks a little odd, it's only
+because of Microsoft's efforts to maintain compatibility with old
+software.  The slots must be disguised to prevent old software from
+panicing.  To this end, a number of measures are taken:
+
+        1) The attribute byte for a slot directory entry is always set
+           to 0x0f.  This corresponds to an old directory entry with
+           attributes of "hidden", "system", "read-only", and "volume
+           label".  Most old software will ignore any directory
+           entries with the "volume label" bit set.  Real volume label
+           entries don't have the other three bits set.
+
+        2) The starting cluster is always set to 0, an impossible
+           value for a DOS file.
+
+Because the extended FAT system is backward compatible, it is
+possible for old software to modify directory entries.  Measures must
+be taken to insure the validity of slots.  An extended FAT system can
+verify that a slot does in fact belong to an 8.3 directory entry by
+the following:
+
+        1) Positioning.  Slots for a file always immediately proceed
+           their corresponding 8.3 directory entry.  In addition, each
+           slot has an id which marks its order in the extended file
+           name.  Here is a very abbreviated view of an 8.3 directory
+           entry and its corresponding long name slots for the file
+           "My Big File.Extension which is long":
+
+                <proceeding files...>
+                <slot #3, id = 0x43, characters = "h is long">
+                <slot #2, id = 0x02, characters = "xtension whic">
+                <slot #1, id = 0x01, characters = "My Big File.E">
+                <directory entry, name = "MYBIGFIL.EXT">
+
+           Note that the slots are stored from last to first.  Slots
+           are numbered from 1 to N.  The Nth slot is or'ed with 0x40
+           to mark it as the last one.
+
+        2) Checksum.  Each slot has an "alias_checksum" value.  The
+           checksum is calculated from the 8.3 name using the
+           following algorithm:
+
+                for (sum = i = 0; i < 11; i++) {
+                        sum = (((sum&1)<<7)|((sum&0xfe)>>1)) + name[i]
+                }
+
+	3) If there is in the final slot, a Unicode NULL (0x0000) is stored
+           after the final character.  After that, all unused characters in
+           the final slot are set to Unicode 0xFFFF.
+
+Finally, note that the extended name is stored in Unicode.  Each Unicode
+character takes two bytes.
+
+
+NOTES ON UNICODE TRANSLATION IN VFAT FILESYTEM
+----------------------------------------------------------------------
+(Information provided by Steve Searle <steve@mgu.bath.ac.uk>)
+
+Char used as   Char(s) used   Char(s) used in     Entries which have
+filename       in shortname   longname slot       been corrected
+0x80 (128)     0x80           0xC7                   
+0x81 (129)     0x9A           0xFC
+0x82 (130)     0x90           0xE9                     E
+0x83 (131)     0xB6           0xE2                     E
+0x84 (132)     0x8E           0xE4                     E
+0x85 (133)     0xB7           0xE0                     E
+0x86 (134)     0x8F           0xE5                     E
+0x87 (135)     0x80           0xE7                     E
+0x88 (136)     0xD2           0xEA                     E
+0x89 (137)     0xD3           0xEB                     E
+0x8A (138)     0xD4           0xE8                     E
+0x8B (139)     0xD8           0xEF                     E
+0x8C (140)     0xD7           0xEE                     E
+0x8D (141)     0xDE           0xEC                     E
+0x8E (142)     0x8E           0xC4                     E
+0x8F (143)     0x8F           0xC5                     E
+0x90 (144)     0x90           0xC9                     E
+0x91 (145)     0x92           0xE6                     E
+0x92 (146)     0x92           0xC6                     E
+0x93 (147)     0xE2           0xF4                     E
+0x94 (148)     0x99           0xF6
+0x95 (149)     0xE3           0xF2
+0x96 (150)     0xEA           0xFB
+0x97 (151)     0xEB           0xF9
+0x98 (152)     "_~1"          0xFF
+0x99 (153)     0x99           0xD6
+0x9A (154)     0x9A           0xDC
+0x9B (155)     0x9D           0xF8
+0x9C (156)     0x9C           0xA3
+0x9D (157)     0x9D           0xD8
+0x9E (158)     0x9E           0xD7
+0x9F (159)     0x9F           0x92
+0xA0 (160)     0xB5           0xE1
+0xA1 (161)     0xD6           0xE0
+0xA2 (162)     0xE0           0xF3
+0xA3 (163)     0xE9           0xFA
+0xA4 (164)     0xA5           0xF1
+0xA5 (165)     0xA5           0xD1
+0xA6 (166)     0xA6           0xAA
+0xA7 (167)     0xA7           0xBA
+0xA8 (168)     0xA8           0xBF
+0xA9 (169)     0xA9           0xAE
+0xAA (170)     0xAA           0xAC
+0xAB (171)     0xAB           0xBD
+0xAC (172)     0xAC           0xBC
+0xAD (173)     0xAD           0xA1
+0xAE (174)     0xAE           0xAB
+0xAF (175)     0xAF           0xBB
+0xB0 (176)     0xB0           0x91 0x25
+0xB1 (177)     0xB1           0x92 0x25
+0xB2 (178)     0xB2           0x93 0x25
+0xB3 (179)     0xB3           0x02 0x25
+0xB4 (180)     0xB4           0x24 0x25
+0xB5 (181)     0xB5           0xC1
+0xB6 (182)     0xB6           0xC2
+0xB7 (183)     0xB7           0xC0
+0xB8 (184)     0xB8           0xA9
+0xB9 (185)     0xB9           0x63 0x25
+0xBA (186)     0xBA           0x51 0x25
+0xBB (187)     0xBB           0x57 0x25
+0xBC (188)     0xBC           0x5D 0x25
+0xBD (189)     0xBD           0xA2
+0xBE (190)     0xBE           0xA5
+0xBF (191)     0xBF           0x10 0x25
+0xC0 (192)     0xC0           0x14 0x25
+0xC1 (193)     0xC1           0x34 0x25
+0xC2 (194)     0xC2           0x2C 0x25
+0xC3 (195)     0xC3           0x1C 0x25
+0xC4 (196)     0xC4           0x00 0x25
+0xC5 (197)     0xC5           0x3C 0x25
+0xC6 (198)     0xC7           0xE3                     E
+0xC7 (199)     0xC7           0xC3
+0xC8 (200)     0xC8           0x5A 0x25                E
+0xC9 (201)     0xC9           0x54 0x25                E
+0xCA (202)     0xCA           0x69 0x25                E
+0xCB (203)     0xCB           0x66 0x25                E
+0xCC (204)     0xCC           0x60 0x25                E
+0xCD (205)     0xCD           0x50 0x25                E
+0xCE (206)     0xCE           0x6C 0x25                E
+0xCF (207)     0xCF           0xA4                     E
+0xD0 (208)     0xD1           0xF0
+0xD1 (209)     0xD1           0xD0
+0xD2 (210)     0xD2           0xCA
+0xD3 (211)     0xD3           0xCB
+0xD4 (212)     0xD4           0xC8
+0xD5 (213)     0x49           0x31 0x01
+0xD6 (214)     0xD6           0xCD
+0xD7 (215)     0xD7           0xCE
+0xD8 (216)     0xD8           0xCF
+0xD9 (217)     0xD9           0x18 0x25
+0xDA (218)     0xDA           0x0C 0x25
+0xDB (219)     0xDB           0x88 0x25
+0xDC (220)     0xDC           0x84 0x25
+0xDD (221)     0xDD           0xA6
+0xDE (222)     0xDE           0xCC
+0xDF (223)     0xDF           0x80 0x25
+0xE0 (224)     0xE0           0xD3
+0xE1 (225)     0xE1           0xDF
+0xE2 (226)     0xE2           0xD4
+0xE3 (227)     0xE3           0xD2
+0xE4 (228)     0x05           0xF5
+0xE5 (229)     0x05           0xD5
+0xE6 (230)     0xE6           0xB5
+0xE7 (231)     0xE8           0xFE
+0xE8 (232)     0xE8           0xDE
+0xE9 (233)     0xE9           0xDA
+0xEA (234)     0xEA           0xDB
+0xEB (235)     0xEB           0xD9
+0xEC (236)     0xED           0xFD
+0xED (237)     0xED           0xDD
+0xEE (238)     0xEE           0xAF
+0xEF (239)     0xEF           0xB4
+0xF0 (240)     0xF0           0xAD
+0xF1 (241)     0xF1           0xB1
+0xF2 (242)     0xF2           0x17 0x20
+0xF3 (243)     0xF3           0xBE
+0xF4 (244)     0xF4           0xB6
+0xF5 (245)     0xF5           0xA7
+0xF6 (246)     0xF6           0xF7
+0xF7 (247)     0xF7           0xB8
+0xF8 (248)     0xF8           0xB0
+0xF9 (249)     0xF9           0xA8
+0xFA (250)     0xFA           0xB7
+0xFB (251)     0xFB           0xB9
+0xFC (252)     0xFC           0xB3
+0xFD (253)     0xFD           0xB2
+0xFE (254)     0xFE           0xA0 0x25
+0xFF (255)     0xFF           0xA0
+
+
+Page 0
+0x80 (128)     0x00
+0x81 (129)     0x00
+0x82 (130)     0x00
+0x83 (131)     0x00
+0x84 (132)     0x00
+0x85 (133)     0x00
+0x86 (134)     0x00
+0x87 (135)     0x00
+0x88 (136)     0x00
+0x89 (137)     0x00
+0x8A (138)     0x00
+0x8B (139)     0x00
+0x8C (140)     0x00
+0x8D (141)     0x00
+0x8E (142)     0x00
+0x8F (143)     0x00
+0x90 (144)     0x00
+0x91 (145)     0x00
+0x92 (146)     0x00
+0x93 (147)     0x00
+0x94 (148)     0x00
+0x95 (149)     0x00
+0x96 (150)     0x00
+0x97 (151)     0x00
+0x98 (152)     0x00
+0x99 (153)     0x00
+0x9A (154)     0x00
+0x9B (155)     0x00
+0x9C (156)     0x00
+0x9D (157)     0x00
+0x9E (158)     0x00
+0x9F (159)     0x92
+0xA0 (160)     0xFF
+0xA1 (161)     0xAD
+0xA2 (162)     0xBD
+0xA3 (163)     0x9C
+0xA4 (164)     0xCF
+0xA5 (165)     0xBE
+0xA6 (166)     0xDD
+0xA7 (167)     0xF5
+0xA8 (168)     0xF9
+0xA9 (169)     0xB8
+0xAA (170)     0x00
+0xAB (171)     0xAE
+0xAC (172)     0xAA
+0xAD (173)     0xF0
+0xAE (174)     0x00
+0xAF (175)     0xEE
+0xB0 (176)     0xF8
+0xB1 (177)     0xF1
+0xB2 (178)     0xFD
+0xB3 (179)     0xFC
+0xB4 (180)     0xEF
+0xB5 (181)     0xE6
+0xB6 (182)     0xF4
+0xB7 (183)     0xFA
+0xB8 (184)     0xF7
+0xB9 (185)     0xFB
+0xBA (186)     0x00
+0xBB (187)     0xAF
+0xBC (188)     0xAC
+0xBD (189)     0xAB
+0xBE (190)     0xF3
+0xBF (191)     0x00
+0xC0 (192)     0xB7
+0xC1 (193)     0xB5
+0xC2 (194)     0xB6
+0xC3 (195)     0xC7
+0xC4 (196)     0x8E
+0xC5 (197)     0x8F
+0xC6 (198)     0x92
+0xC7 (199)     0x80
+0xC8 (200)     0xD4
+0xC9 (201)     0x90
+0xCA (202)     0xD2
+0xCB (203)     0xD3
+0xCC (204)     0xDE
+0xCD (205)     0xD6
+0xCE (206)     0xD7
+0xCF (207)     0xD8
+0xD0 (208)     0x00
+0xD1 (209)     0xA5
+0xD2 (210)     0xE3
+0xD3 (211)     0xE0
+0xD4 (212)     0xE2
+0xD5 (213)     0xE5
+0xD6 (214)     0x99
+0xD7 (215)     0x9E
+0xD8 (216)     0x9D
+0xD9 (217)     0xEB
+0xDA (218)     0xE9
+0xDB (219)     0xEA
+0xDC (220)     0x9A
+0xDD (221)     0xED
+0xDE (222)     0xE8
+0xDF (223)     0xE1
+0xE0 (224)     0x85, 0xA1
+0xE1 (225)     0xA0
+0xE2 (226)     0x83
+0xE3 (227)     0xC6
+0xE4 (228)     0x84
+0xE5 (229)     0x86
+0xE6 (230)     0x91
+0xE7 (231)     0x87
+0xE8 (232)     0x8A
+0xE9 (233)     0x82
+0xEA (234)     0x88
+0xEB (235)     0x89
+0xEC (236)     0x8D
+0xED (237)     0x00
+0xEE (238)     0x8C
+0xEF (239)     0x8B
+0xF0 (240)     0xD0
+0xF1 (241)     0xA4
+0xF2 (242)     0x95
+0xF3 (243)     0xA2
+0xF4 (244)     0x93
+0xF5 (245)     0xE4
+0xF6 (246)     0x94
+0xF7 (247)     0xF6
+0xF8 (248)     0x9B
+0xF9 (249)     0x97
+0xFA (250)     0xA3
+0xFB (251)     0x96
+0xFC (252)     0x81
+0xFD (253)     0xEC
+0xFE (254)     0xE7
+0xFF (255)     0x98
+
diff -u --recursive --new-file v1.3.71/linux/Documentation/networking/ppp.txt linux/Documentation/networking/ppp.txt
--- v1.3.71/linux/Documentation/networking/ppp.txt	Tue Oct 10 18:46:30 1995
+++ linux/Documentation/networking/ppp.txt	Tue Mar  5 10:01:26 1996
@@ -1,4 +1,4 @@
-The PPP support for this kernel requires the 2.2.0a version of the
+The PPP support for this kernel requires the 2.2.0 version of the
 pppd daemon. You will find the current version of the daemon on
 sunsite.unc.edu in the /pub/Linux/system/Network/serial directory.
 
diff -u --recursive --new-file v1.3.71/linux/MAINTAINERS linux/MAINTAINERS
--- v1.3.71/linux/MAINTAINERS	Sat Mar  2 10:43:20 1996
+++ linux/MAINTAINERS	Thu Mar  7 09:09:46 1996
@@ -59,13 +59,18 @@
 	Obsolete:	Ex code. Something tagged obsolete generally means
 			its been replaced by a better system and you should
 			be using that.
-
 3C501 NETWORK DRIVER
 P:	Alan Cox
 M:	net-patches@lxorguk.ukuu.org.uk
 L:	linux-net@vger.rutgers.edu
 S:	Maintained
 
+APM DRIVER
+P:      Rik Faith
+M:      faith@cs.unc.edu
+L:      linux-laptop@vger.rutgers.edu
+S:      Maintained
+
 APPLETALK NETWORK LAYER
 P:	Alan Cox & University Of Michigan
 M:	net-patches@lxorguk.ukuu.org.uk, Cc: netatalk@umich.edu
@@ -78,6 +83,18 @@
 L:	linux-hams@vger.rutges.edu
 S:	Maintained
 
+FUTURE DOMAIN TMC-16x0 SCSI DRIVER (16-bit)
+P:      Rik Faith
+M:      faith@cs.unc.edu
+L:      linux-scsi@vger.rutgers.edu
+S:      Odd fixes (e.g., new signatures)
+
+FTAPE/QIC-117:
+P:	Kai Harrekilde-Petersen
+M:	khp@pip.dknet.dk [from 960401: khp@dolphinics.no]
+L:	linux-tape@vger.rutgers.edu
+S:	Maintained
+
 IPX NETWORK LAYER
 P:	Alan Cox [for the moment]
 M:	net-patches@lxorguk.ukuu.org.uk
@@ -90,6 +107,12 @@
 L:	isdn4linux@hub-wue.franken.de
 S:	Maintained
 
+MODULE SUPPORT [GENERAL], KERNELD
+P:	Bjorn Ekwall
+M:	bj0rn@blox.se
+L:	linux-kernel@vger.rutgers.edu
+S:	Maintained
+
 NETROM NETWORK LAYER
 P:	Jon Naylor
 M:	jsn@cs.nott.ac.uk
@@ -101,6 +124,12 @@
 M:	net-patches@lxorguk.ukuu.org.uk
 L:	linux-net@vger.rutgers.edu
 S:	Odd Fixes <-> Maintained subject to workloads
+
+PPP PROTOCOL DRIVERS AND COMPRESSORS
+P:	Al Longyear
+M:	longyear@netcom.com, Cc: longyear@sii.com
+L:	linux-ppp@vger.rutgers.edu
+S:	Maintained
 
 SMP:
 P:	Alan Cox
diff -u --recursive --new-file v1.3.71/linux/Makefile linux/Makefile
--- v1.3.71/linux/Makefile	Tue Mar  5 10:10:45 1996
+++ linux/Makefile	Tue Mar  5 12:44:00 1996
@@ -1,6 +1,6 @@
 VERSION = 1
 PATCHLEVEL = 3
-SUBLEVEL = 71
+SUBLEVEL = 72
 
 ARCH = i386
 
diff -u --recursive --new-file v1.3.71/linux/arch/i386/defconfig linux/arch/i386/defconfig
--- v1.3.71/linux/arch/i386/defconfig	Wed Feb 28 11:49:59 1996
+++ linux/arch/i386/defconfig	Wed Mar  6 15:07:18 1996
@@ -145,6 +145,7 @@
 # CONFIG_MS_BUSMOUSE is not set
 # CONFIG_ATIXL_BUSMOUSE is not set
 # CONFIG_QIC02_TAPE is not set
+# CONFIG_FTAPE is not set
 # CONFIG_APM is not set
 # CONFIG_WATCHDOG is not set
 
diff -u --recursive --new-file v1.3.71/linux/arch/i386/kernel/irq.c linux/arch/i386/kernel/irq.c
--- v1.3.71/linux/arch/i386/kernel/irq.c	Sat Mar  2 10:43:21 1996
+++ linux/arch/i386/kernel/irq.c	Wed Mar  6 13:59:34 1996
@@ -33,7 +33,6 @@
 #include <asm/smp.h>
 
 #define CR0_NE 32
-#define TIMER_IRQ 0                     /* Keep this in sync with time.c */
 
 static unsigned char cache_21 = 0xff;
 static unsigned char cache_A1 = 0xff;
@@ -96,8 +95,16 @@
  * atomic. The specific handler is chosen depending on the SA_INTERRUPT
  * flag when installing a handler. Finally, one "bad interrupt" handler, that
  * is used when no handler is present.
+ *
+ * The timer interrupt is handled specially to insure that the jiffies
+ * variable is updated at all times.  Specifically, the timer interrupt is
+ * just like the complete handlers except that it is invoked with interrupts
+ * disabled and should never re-enable them.  If other interrupts were
+ * allowed to be processed while the timer interrupt is active, then the
+ * other interrupts would have to avoid using the jiffies variable for delay
+ * and interval timing operations to avoid hanging the system.
  */
-BUILD_IRQ(FIRST,0,0x01)
+BUILD_TIMER_IRQ(FIRST,0,0x01)
 BUILD_IRQ(FIRST,1,0x02)
 BUILD_IRQ(FIRST,2,0x04)
 BUILD_IRQ(FIRST,3,0x08)
diff -u --recursive --new-file v1.3.71/linux/arch/i386/kernel/ldt.c linux/arch/i386/kernel/ldt.c
--- v1.3.71/linux/arch/i386/kernel/ldt.c	Sun Feb  5 23:39:17 1995
+++ linux/arch/i386/kernel/ldt.c	Thu Mar  7 16:24:45 1996
@@ -34,11 +34,35 @@
 	return size;
 }
 
+static inline int limits_ok(struct modify_ldt_ldt_s *ldt_info)
+{
+	unsigned long base, limit;
+	/* linear address of first and last accessible byte */
+	unsigned long first, last;
+
+	base = ldt_info->base_addr;
+	limit = ldt_info->limit;
+	if (ldt_info->limit_in_pages)
+		limit = limit * PAGE_SIZE + PAGE_SIZE - 1;
+
+	first = base;
+	last = limit + base;
+
+	/* segment grows down? */
+	if (ldt_info->contents == 1) {
+		/* data segment grows down */
+		first = base+limit+1;
+		last = base+65535;
+		if (ldt_info->seg_32bit)
+			last = base-1;
+	}
+	return (last >= first && last < TASK_SIZE);
+}
+
 static int write_ldt(void * ptr, unsigned long bytecount)
 {
 	struct modify_ldt_ldt_s ldt_info;
 	unsigned long *lp;
-	unsigned long base, limit;
 	int error, i;
 
 	if (bytecount != sizeof(ldt_info))
@@ -52,13 +76,7 @@
 	if (ldt_info.contents == 3 || ldt_info.entry_number >= LDT_ENTRIES)
 		return -EINVAL;
 
-	limit = ldt_info.limit;
-	base = ldt_info.base_addr;
-	if (ldt_info.limit_in_pages)
-		limit *= PAGE_SIZE;
-
-	limit += base;
-	if (limit < base || limit >= 0xC0000000)
+	if (!limits_ok(&ldt_info))
 		return -EINVAL;
 
 	if (!current->ldt) {
diff -u --recursive --new-file v1.3.71/linux/arch/i386/kernel/time.c linux/arch/i386/kernel/time.c
--- v1.3.71/linux/arch/i386/kernel/time.c	Sat Mar  2 10:43:21 1996
+++ linux/arch/i386/kernel/time.c	Wed Mar  6 13:59:34 1996
@@ -20,12 +20,11 @@
 
 #include <asm/segment.h>
 #include <asm/io.h>
+#include <asm/irq.h>
 
 #include <linux/mc146818rtc.h>
 #include <linux/timex.h>
 #include <linux/config.h>
-
-#define TIMER_IRQ 0
 
 /* Cycle counter value at the previous timer interrupt.. */
 static unsigned long long last_timer_cc = 0;
diff -u --recursive --new-file v1.3.71/linux/drivers/block/ide.c linux/drivers/block/ide.c
--- v1.3.71/linux/drivers/block/ide.c	Sat Mar  2 10:43:22 1996
+++ linux/drivers/block/ide.c	Fri Mar  8 09:43:25 1996
@@ -524,7 +524,7 @@
  */
 static void init_gendisk (ide_hwif_t *hwif)
 {
-	struct gendisk *gd;
+	struct gendisk *gd, **gdp;
 	unsigned int unit, units, minors;
 	int *bs;
 
@@ -556,8 +556,9 @@
 	gd->init	= ide_geninit;		/* initialization function */
 	gd->real_devices= hwif;			/* ptr to internal data */
 
-	gd->next = gendisk_head;		/* link new major into list */
-	hwif->gd = gendisk_head = gd;
+	for (gdp = &gendisk_head; *gdp; gdp = &((*gdp)->next)) ;
+	gd->next = NULL;			/* link to tail of list */
+	hwif->gd = *gdp = gd;
 }
 
 static void do_reset1 (ide_drive_t *, int);		/* needed below */
@@ -2168,6 +2169,15 @@
 			drive->removeable = 1;
 	}
 
+	/* SunDisk drives: treat as non-removeable, force one unit */
+	if (id->model[0] == 'S' && id->model[1] == 'u') {
+		drive->removeable = 0;
+		if (drive->select.all & (1<<4)) {
+		    drive->present = 0;
+		    return;
+		}
+	}
+	
 	drive->media = ide_disk;
 	/* Extract geometry if we did not already have one for the drive */
 	if (!drive->present) {
@@ -3004,6 +3014,63 @@
 #endif
 }
 
+static int hwif_init (int h)
+{
+	ide_hwif_t *hwif = &ide_hwifs[h];
+	void (*rfn)(void);
+	
+	if (hwif->noprobe)
+		return 0;
+	else {
+		if (hwif->io_base == HD_DATA)
+			probe_cmos_for_drives (hwif);
+		probe_hwif (hwif);
+		if (!hwif->present)
+			return 0;
+	}
+	if (!hwif->irq) {
+		if (!(hwif->irq = default_irqs[h])) {
+			printk("%s: DISABLED, NO IRQ\n", hwif->name);
+			return (hwif->present = 0);
+		}
+	}
+#ifdef CONFIG_BLK_DEV_HD
+	if (hwif->irq == HD_IRQ && hwif->io_base != HD_DATA) {
+		printk("%s: CANNOT SHARE IRQ WITH OLD HARDDISK DRIVER (hd.c)\n", hwif->name);
+		return (hwif->present = 0);
+	}
+#endif /* CONFIG_BLK_DEV_HD */
+	
+	hwif->present = 0; /* we set it back to 1 if all is ok below */
+	switch (hwif->major) {
+	case IDE0_MAJOR: rfn = &do_ide0_request; break;
+#if MAX_HWIFS > 1
+	case IDE1_MAJOR: rfn = &do_ide1_request; break;
+#endif
+#if MAX_HWIFS > 2
+	case IDE2_MAJOR: rfn = &do_ide2_request; break;
+#endif
+#if MAX_HWIFS > 3
+	case IDE3_MAJOR: rfn = &do_ide3_request; break;
+#endif
+	default:
+		printk("%s: request_fn NOT DEFINED\n", hwif->name);
+		return (hwif->present = 0);
+	}
+	if (register_blkdev (hwif->major, hwif->name, &ide_fops)) {
+		printk("%s: UNABLE TO GET MAJOR NUMBER %d\n", hwif->name, hwif->major);
+	} else if (init_irq (hwif)) {
+		printk("%s: UNABLE TO GET IRQ %d\n", hwif->name, hwif->irq);
+		(void) unregister_blkdev (hwif->major, hwif->name);
+	} else {
+		init_gendisk(hwif);
+		blk_dev[hwif->major].request_fn = rfn;
+		read_ahead[hwif->major] = 8;	/* (4kB) */
+		hwif->present = 1;	/* success */
+	}
+	return hwif->present;
+}
+
 /*
  * This is gets invoked once during initialization, to set *everything* up
  */
@@ -3020,70 +3087,71 @@
 	/*
 	 * Probe for drives in the usual way.. CMOS/BIOS, then poke at ports
 	 */
-	for (h = 0; h < MAX_HWIFS; ++h) {
-		ide_hwif_t *hwif = &ide_hwifs[h];
-		if (!hwif->noprobe) {
-			if (hwif->io_base == HD_DATA)
-				probe_cmos_for_drives (hwif);
-			probe_hwif (hwif);
-		}
-		if (hwif->present) {
-			if (!hwif->irq) {
-				if (!(hwif->irq = default_irqs[h])) {
-					printk("%s: DISABLED, NO IRQ\n", hwif->name);
-					hwif->present = 0;
-					continue;
-				}
-			}
-#ifdef CONFIG_BLK_DEV_HD
-			if (hwif->irq == HD_IRQ && hwif->io_base != HD_DATA) {
-				printk("%s: CANNOT SHARE IRQ WITH OLD HARDDISK DRIVER (hd.c)\n", hwif->name);
-				hwif->present = 0;
-			}
-#endif /* CONFIG_BLK_DEV_HD */
-		}
-	}
-
-	/*
-	 * Now we try to set up irqs and major devices for what was found
-	 */
-	for (h = MAX_HWIFS-1; h >= 0; --h) {
-		void (*rfn)(void);
-		ide_hwif_t *hwif = &ide_hwifs[h];
-		if (!hwif->present)
-			continue;
-		hwif->present = 0; /* we set it back to 1 if all is ok below */
-		switch (hwif->major) {
-			case IDE0_MAJOR: rfn = &do_ide0_request; break;
-#if MAX_HWIFS > 1
-			case IDE1_MAJOR: rfn = &do_ide1_request; break;
-#endif
-#if MAX_HWIFS > 2
-			case IDE2_MAJOR: rfn = &do_ide2_request; break;
-#endif
-#if MAX_HWIFS > 3
-			case IDE3_MAJOR: rfn = &do_ide3_request; break;
-#endif
-			default:
-				printk("%s: request_fn NOT DEFINED\n", hwif->name);
-				continue;
-		}
-		if (register_blkdev (hwif->major, hwif->name, &ide_fops)) {
-			printk("%s: UNABLE TO GET MAJOR NUMBER %d\n", hwif->name, hwif->major);
-		} else if (init_irq (hwif)) {
-			printk("%s: UNABLE TO GET IRQ %d\n", hwif->name, hwif->irq);
-			(void) unregister_blkdev (hwif->major, hwif->name);
-		} else {
-			init_gendisk(hwif);
-			blk_dev[hwif->major].request_fn = rfn;
-			read_ahead[hwif->major] = 8;	/* (4kB) */
-			hwif->present = 1;	/* success */
-		}
-	}
+	for (h = 0; h < MAX_HWIFS; ++h)
+		hwif_init(h);
 
 #ifdef CONFIG_BLK_DEV_IDETAPE
 	idetape_register_chrdev();	/* Register character device interface to the ide tape */
 #endif /* CONFIG_BLK_DEV_IDETAPE */
 	
 	return 0;
+}
+
+int ide_register(int io_base, int ctl_port, int irq)
+{
+	int h, i;
+	ide_hwif_t *hwif;
+	for (h = 0; h < MAX_HWIFS; ++h) {
+		hwif = &ide_hwifs[h];
+		if (hwif->present == 0) break;
+	}
+	hwif->io_base = io_base;
+	hwif->ctl_port = ctl_port;
+	hwif->irq = irq;
+	hwif->noprobe = 0;
+	if (hwif_init(h) != 0) {
+		hwif->gd->real_devices = hwif->drives[0].name;
+		for (i = 0; i < hwif->gd->nr_real; i++)
+			revalidate_disk(MKDEV(hwif->major, i<<PARTN_BITS));
+		return h;
+	}
+	else
+		return -1;
+}
+
+void ide_unregister(int h)
+{
+	struct gendisk *prev_gd, *gd;
+	ide_hwif_t *hwif;
+
+	if ((h < 0) || (h >= MAX_HWIFS))
+		return;
+	hwif = &ide_hwifs[h];
+	if (hwif->present) {
+		hwif->present = 0;
+		/* This assumes that there is only one group member */
+		free_irq(hwif->irq, NULL);
+		kfree(hwif->hwgroup);
+		irq_to_hwgroup[hwif->irq] = NULL;
+		unregister_blkdev(hwif->major, hwif->name);
+		kfree(blksize_size[hwif->major]);
+		blk_dev[hwif->major].request_fn = NULL;
+		blksize_size[hwif->major] = NULL;
+		gd = gendisk_head; prev_gd = NULL;
+		while (gd && (gd != hwif->gd)) {
+			prev_gd = gd;
+			gd = gd->next;
+		}
+		if (gd != hwif->gd)
+			printk("gd not in disk chain!\n");
+		else {
+			if (prev_gd != NULL)
+				prev_gd->next = gd->next;
+			else
+				gendisk_head = gd->next;
+			kfree(gd->sizes);
+			kfree(gd->part);
+			kfree(gd);
+		}
+	}
 }
diff -u --recursive --new-file v1.3.71/linux/drivers/char/Config.in linux/drivers/char/Config.in
--- v1.3.71/linux/drivers/char/Config.in	Wed Feb 28 11:50:00 1996
+++ linux/drivers/char/Config.in	Wed Mar  6 15:07:18 1996
@@ -31,6 +31,12 @@
     comment 'ftp://titus.cfw.com/pub/Linux/util/'
 fi
 fi
+
+tristate 'Ftape (QIC-80/Travan) support' CONFIG_FTAPE
+if [ "$CONFIG_FTAPE" != "n" ]; then
+  comment 'Set IObase/IRQ/DMA for ftape in ./drivers/char/ftape/Makefile'
+fi
+
 bool 'Advanced Power Management BIOS support' CONFIG_APM
 if [ "$CONFIG_APM" = "y" ]; then
   bool '   Ignore USER SUSPEND' CONFIG_APM_IGNORE_USER_SUSPEND
diff -u --recursive --new-file v1.3.71/linux/drivers/char/Makefile linux/drivers/char/Makefile
--- v1.3.71/linux/drivers/char/Makefile	Wed Feb 28 11:50:01 1996
+++ linux/drivers/char/Makefile	Wed Mar  6 15:07:18 1996
@@ -9,6 +9,10 @@
 # parent makes..
 #
 
+SUB_DIRS     := 
+MOD_SUB_DIRS := $(SUB_DIRS)
+ALL_SUB_DIRS := $(SUB_DIRS) ftape
+
 #
 # This file contains the font map for the default (hardware) font
 #
@@ -114,6 +118,15 @@
 
 ifdef CONFIG_QIC02_TAPE
 L_OBJS += tpqic02.o 
+endif
+
+ifeq ($(CONFIG_FTAPE),y)
+SUB_DIRS += ftape
+L_OBJS += ftape/ftape.o
+else
+  ifeq ($(CONFIG_FTAPE),m)
+  MOD_SUB_DIRS += ftape
+  endif
 endif
 
 ifdef CONFIG_APM
diff -u --recursive --new-file v1.3.71/linux/drivers/char/apm_bios.c linux/drivers/char/apm_bios.c
--- v1.3.71/linux/drivers/char/apm_bios.c	Tue Mar  5 10:10:54 1996
+++ linux/drivers/char/apm_bios.c	Thu Mar  7 09:13:21 1996
@@ -19,11 +19,15 @@
  *    Documentation
  * January 1996, Rik Faith (faith@cs.unc.edu):
  *    Make /proc/apm easy to format (bump driver version)
+ * March 1996, Rik Faith (faith@cs.unc.edu):
+ *    Prohibit APM BIOS calls unless apm_enabled.
+ *    (Thanks to Ulrich Windl <Ulrich.Windl@rz.uni-regensburg.de>)
  *
  * History:
  *    0.6b: first version in official kernel, Linux 1.3.46
  *    0.7: changed /proc/apm format, Linux 1.3.58
  *    0.8: fixed gcc 2.7.[12] compilation problems, Linux 1.3.59
+ *    0.9: only call bios if bios is present, Linux 1.3.72
  *
  * Reference:
  *
@@ -307,7 +311,7 @@
 
 static struct timer_list	apm_timer;
 
-static char			driver_version[] = "0.8";/* no spaces */
+static char			driver_version[] = "0.9";/* no spaces */
 
 #ifdef APM_DEBUG
 static char *	apm_event_name[] = {
@@ -403,6 +407,7 @@
 }
 
 #ifdef CONFIG_APM_DISPLAY_BLANK
+/* Called by apm_display_blank and apm_display_unblank when apm_enabled. */
 static int apm_set_display_power_state(u_short state)
 {
 	u_short	error;
@@ -415,6 +420,7 @@
 #endif
 
 #ifdef CONFIG_APM_DO_ENABLE
+/* Called by apm_setup if apm_enabled will be true. */
 static int apm_enable_power_management(void)
 {
 	u_short	error;
@@ -460,12 +466,13 @@
 		printk("apm_bios: %s: unknown error code %#2.2x\n", str, err);
 }
 
+/* Called from console driver -- must make sure apm_enabled. */
 int apm_display_blank(void)
 {
 #ifdef CONFIG_APM_DISPLAY_BLANK
 	int	error;
 
-	if (apm_bios_info.version == 0)
+	if (!apm_enabled || apm_bios_info.version == 0)
 		return 0;
 	error = apm_set_display_power_state(APM_STATE_STANDBY);
 	if (error == APM_SUCCESS)
@@ -475,12 +482,13 @@
 	return 0;
 }
 
+/* Called from console driver -- must make sure apm_enabled. */
 int apm_display_unblank(void)
 {
 #ifdef CONFIG_APM_DISPLAY_BLANK
 	int error;
 
-	if (apm_bios_info.version == 0)
+	if (!apm_enabled || apm_bios_info.version == 0)
 		return 0;
 	error = apm_set_display_power_state(APM_STATE_READY);
 	if (error == APM_SUCCESS)
@@ -715,6 +723,7 @@
 	add_timer(&apm_timer);
 }
 
+/* Called from sys_idle, must make sure apm_enabled. */
 int apm_do_idle(void)
 {
 #ifdef CONFIG_APM_CPU_IDLE
@@ -734,16 +743,17 @@
 #endif
 }
 
+/* Called from sys_idle, must make sure apm_enabled. */
 void apm_do_busy(void)
 {
 #ifdef CONFIG_APM_CPU_IDLE
 	unsigned short	error;
 
+	if (!apm_enabled)
+		return;
+	
 #ifndef ALWAYS_CALL_BUSY
 	if (!clock_slowed)
-		return;
-#else
-	if (!apm_enabled)
 		return;
 #endif
 
diff -u --recursive --new-file v1.3.71/linux/drivers/char/cyclades.c linux/drivers/char/cyclades.c
--- v1.3.71/linux/drivers/char/cyclades.c	Tue Mar  5 10:10:55 1996
+++ linux/drivers/char/cyclades.c	Fri Mar  8 10:04:52 1996
@@ -1,5 +1,5 @@
 static char rcsid[] =
-"$Revision: 1.36.3.4 $$Date: 1995/11/13 20:45:10 $";
+"$Revision: 1.36.3.5 $$Date: 1996/03/07 15:20:17 $";
 /*
  *  linux/drivers/char/cyclades.c
  *
@@ -24,6 +24,12 @@
  *   int cy_open(struct tty_struct *tty, struct file *filp);
  *
  * $Log: cyclades.c,v $
+ * Revision 1.36.3.5  1996/03/07 15:20:17  bentson
+ * Some global changes to interrupt handling spilled into
+ * this driver--mostly unused arguments in system function
+ * calls.  Also added change by Marcio Saito which should
+ * reduce lost interrupts at startup by fast processors.
+ *
  * Revision 1.36.3.4  1995/11/13  20:45:10  bentson
  * Changes by Corey Minyard <minyard@wf-rch.cirr.com> distributed
  * in 1.3.41 kernel to remove a possible race condition, extend
@@ -627,8 +633,8 @@
 	    intr_base_addr[CySRER<<index] &= ~CyTxMpty;
 	    intr_base_addr[CyTIR<<index] = (save_xir & 0x3f);
 	    intr_base_addr[CyCAR<<index] = (save_car);
-	    *(intr_base_addr + (Cy_ClrIntr<<index)) = 0; /* Cy_ClrIntr is 0x1800 */
 	}
+	*(intr_base_addr + (Cy_ClrIntr<<index)) = 0; /* Cy_ClrIntr is 0x1800 */
     return;
 } /* cy_probe */
 
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/Makefile linux/drivers/char/ftape/Makefile
--- v1.3.71/linux/drivers/char/ftape/Makefile	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/Makefile	Wed Mar  6 15:07:18 1996
@@ -0,0 +1,65 @@
+#
+# Makefile for the ftape device driver.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definitions are now inherited from the
+# parent makes..
+#
+
+#  Valid ftape options are:
+#       NO_TRACE        - if defined, only information and errors show up.
+#       NO_TRACE_AT_ALL - if defined, no trace output shows up.
+#       GCC_2_4_5_BUG   - must be set if using gcc-2.4.5 to prevent
+#                         bad assembler-code for the dma handling.
+#	NR_BUFFERS	- Number of ftape DMA buffers (keep it at 3!)
+#       VERIFY_HEADERS  - if set the headers segments are verified after
+#                         being written.
+#       PROBE_FC10      - if defined will look for a FC-10 card at specified
+#                         settings (FDC_BASE,FDC_IRQ,FDC_DMA) before using
+#                         the standard fd controller.
+#       FDC_BASE        - sets base address (only!) if using non-standard fdc
+#       FDC_IRQ         - sets interrupt if FDC_BASE is defined
+#       FDC_DMA         - sets dma channel if FDC_BASE is defined
+#       MACH2           - Support for Mountain MACH-2 contoller at either 1E0
+#                         or 3E0, don't forget the FDC_OPT's !
+#	CLK_48MHZ	- Set to 1. If you have a i82078-1 FDC and it does not
+#			  work, try setting it to 0. (only used for i82078-1's)
+#	FDC_82078SL	- If you have a 82078SL, define this.
+
+FTAPE_OPT       = -DVERIFY_HEADERS -DNR_BUFFERS=3 -DCLK_48MHZ=1 \
+	-DFDC_82078SL -DNO_TRACE
+
+#  If you're using a non-standard floppy disk controller for the
+#  tape drive, enable one (only!) of the following lines and set
+#  the FDC_BASE, FDC_IRQ and FDC_DMA parameters to the actual values.
+#
+#  Note1: A FC-10/FC-20 controller must use either of DMA 1, 2, or 3.
+#         DMA 5 and 7 does NOT work!.
+#
+#  Note2: IRQ 2 and IRQ 9 can be considered the same. When using IRQ 2
+#         on a controller you must specify IRQ 9 here!
+#
+# For a Mountain MACH-2 controller, try 
+#FDC_OPT         = -DMACH2 -DFDC_BASE=0x1E0 -DFDC_IRQ=6 -DFDC_DMA=2
+#
+# For Colorado CMS FC-10 or FC-20 controllers:
+#FDC_OPT         = -DPROBE_FC10 -DFDC_BASE=0x180 -DFDC_IRQ=9 -DFDC_DMA=3
+#
+# Secondary floppy disk controller:
+#FDC_OPT         = -DFDC_BASE=0x370 -DFDC_IRQ=9 -DFDC_DMA=3
+#
+# This enables some (most?) 2Mbps controllers:
+#FDC_OPT         = -DFDC_BASE=0x3E0 -DFDC_IRQ=6 -DFDC_DMA=2
+
+EXTRA_CFLAGS := $(FTAPE_OPT) $(FDC_OPT) -D__NO_VERSION__
+
+O_TARGET := ftape.o
+O_OBJS   = kernel-interface.o tracing.o fdc-io.o fdc-isr.o \
+	   ftape-bsm.o ftape-ctl.o ftape-eof.o ftape-read.o ftape-rw.o \
+	   ftape-write.o ftape-io.o calibr.o ecc.o fc-10.o
+M_OBJS   = $(O_TARGET)
+
+include $(TOPDIR)/Rules.make
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/README.PCI linux/drivers/char/ftape/README.PCI
--- v1.3.71/linux/drivers/char/ftape/README.PCI	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/README.PCI	Wed Mar  6 15:07:18 1996
@@ -0,0 +1,79 @@
+Some notes for ftape users with PCI motherboards:
+=================================================
+
+The problem:
+------------
+
+There have been some problem reports from people using PCI-bus based
+systems getting overrun errors.
+I wasn't able to reproduce these until I ran ftape on a Intel Plato
+(Premiere PCI II) motherboard with bios version 1.00.08AX1.
+It turned out that if GAT (Guaranteed Access Timing) is enabled (?)
+ftape gets a lot of overrun errors.
+The problem disappears when disabling GAT in the bios.
+Note that Intel removed this setting (permanently disabled) from the
+1.00.10AX1 bios !
+
+It looks like that if GAT is enabled there are often large periods
+(greater than 120 us !??) on the ISA bus that the DMA controller cannot
+service the floppy disk controller.
+I cannot imagine this being acceptable in a decent PCI implementation.
+Maybe this is a `feature' of the chipset. I can only speculate why
+Intel choose to remove the option from the latest Bios...
+
+The lesson of this all is that there may be other motherboard
+implementations having the same of similar problems.
+If you experience a lot of overrun errors during a backup to tape,
+see if there is some setting in the Bios that may influence the
+bus timing.
+
+I judge this a hardware problem and not a limitation of ftape ;-)
+My DOS backup software seems to be suffering from the same problems
+and even refuses to run at 1 Mbps !
+Ftape will reduce the datarate from 1 Mbps to 500 Kbps if the number
+of overrun errors on a track exceeds a threshold.
+
+
+Possible solutions:
+-------------------
+
+Some of the problems were solved by upgrading the (flash) bios.
+Other suggest that it has to do with the FDC being on the PCI
+bus, but that is not the case with the Intel Premiere II boards.
+[If upgrading the bios doesn't solve the problem you could try
+a floppy disk controller on the isa-bus].
+
+Here is a list of systems and recommended BIOS settings:
+
+
+        Intel Premiere PCI (Revenge):
+
+Bios version 1.00.09.AF2 is reported to work.
+
+
+
+        Intel Premiere PCI II (Plato):
+
+Bios version 1.00.10.AX1 and version 11 beta are ok.
+If using version 1.00.08.AX1, GAT must be disabled !
+
+
+
+        ASUS PCI/I-SP3G:
+
+Preferred settings:     ISA-GAT-mode : disabled
+                        DMA-linebuffer-mode : standard
+                        ISA-masterbuffer-mode : standard
+
+
+        DELL Dimension XPS P90
+
+Bios version A2 is reported to be broken, while bios version A5 works.
+You can get a flash bios upgrade from http://www.dell.com
+
+
+To see if you're having the GAT problem, try making a backup
+under DOS. If it's very slow and often repositions you're
+probably having this problem.
+
+                        --//--
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/RELEASE-NOTES linux/drivers/char/ftape/RELEASE-NOTES
--- v1.3.71/linux/drivers/char/ftape/RELEASE-NOTES	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/RELEASE-NOTES	Wed Mar  6 15:07:18 1996
@@ -0,0 +1,552 @@
+===== Release notes for ftape-2.07, 04/03/96 =====
+
+
+- ftape updated to compile against 1.3.70.
+- Iomega 700 and Wangtek 3200 recognised.
+
+
+===== Release notes for ftape-2.06b, 13/02/96 =====
+
+Another simple bugfix version.
+
+- Jumbo 700 recognised.
+- Typo in vendors.h fixed.
+
+
+===== Release notes for ftape-2.06a, 10/02/96 =====
+
+This release is a simple bugfix version.
+
+- Linux/SMP: ftape *should* work, if you remember to add the symbol __SMP__
+  to the Makefile (you know what I'm talking about, if you're playing with
+  Linux/SMP :).
+- FC-10/20: Only accepts IRQs 3-7, or 9.  If IRQ 9, properly tell the card
+  to use IRQ 2.  Thanks to Greg Crider (gcrider@iclnet.org) for finding and
+  locating this bug and testing the patch.
+- Insight drive recognised correctly again.
+- Motor-on wakeup version of the Iomega 250 drive added
+
+
+===== Release notes for ftape-2.06, 28/01/96 =====
+
+Special thanks go to Neal Friedman and Steven Sorbom for their
+help in producing and testing this release.
+
+I have continued to clean up the code, with an eye towards inclusion
+of ftape in Linus' official kernel (In fact, as I type this, I am
+running on a kernel with ftape support statically linked).  I have
+test-compiled ftape against my 1.2.13 tree without problems.
+Hopefully, everything should be OK for the v1.2.x people.
+
+WARNING! Alan Cox has mailed me that ftape does *NOT* work with
+Linux/SMP.  If you try to run ftape under Linux/SMP, it will cause a
+kernel deadlock (which is worse than a panic).
+
+- QIC-3020/TR-3: 1Mbps support works.  Neal is capable of reading and
+  writing data to a tape.  ftape will automatically detect the type of
+  tape (e.g. TR-3 vs QIC-80) and move the fdc in and out of
+  "perpendicular mode" as neccesarry.
+- 2Mbps support is disabled by default, since it is not fully
+  debugged.  If you are adventurous, remove -DFDC_82078SL in the
+  Makefile and see what happens :-)
+- fdc detection: silly bugs removed (Only 2Mbps fdcs were affected)
+  and added detection of the National Semiconductors PC8744 fdc chip
+  (used in the PC873xx "super-IO" chips).
+- Removed warning about incompatible types when compiling with Linux
+  1.2.x.
+- README.PCI updated with info about the DELL Dimension XPS P90.
+- Connor TST3200R added to detected drives.
+- `swapout' utility added to distribution.  It will dirty 5Meg of
+  memory, trying to swap out other programs.  Just say `make swapout'
+  to build it.  ftape will do this automatically Real Soon Now (ie:
+  when I have found out which kernel memory alloc function to call).
+
+
+===== Release notes for ftape-2.05, 08/01/96 =====
+
+- For v1.2.x Kernels, you must apply the patch linux-1.2/ksyms.patch to
+  the kernel and rebuild it (it adds the __get_dma_pages symbol to
+  ksyms.c).
+- Included new asm-i386/io.h file from v1.3.x kernel series, to enable
+  gcc v.2.7.[12] to compile v1.2.x kernels (linux-1.2/io.h).
+- Module versions: If you wish to compile ftape as a versioned module,
+  you must first compile your kernel with CONFIG_MODVERSIONS=y.
+  Otherwise, you will get complaints that <linux/modversions.h> does not
+  exist (if that happens, a `touch modversions.h' will help you out).
+- CLK_48MHZ: new define in the Makefile (default: non-zero).  If you have
+  a tape controller card that uses the i82078(-1) chip, but cannot get
+  it to work with ftape, try set it to 0 (and please report this).
+- QIC-3010/3020: Complete support is still missing, but will hopefully
+  come soon.  Steven Sorbom has kindly provided me with hints about
+  this.  Writing of QIC-3020 tapes definitely does NOT work (do not try
+  it! - the drive will not be in "perpendicular mode" and this will ruin
+  the formatting info on the tape).
+- ftape_num_buffers is out of fashion: use NR_BUFFERS instead (and
+  recompile if you want to change it :-).
+
+
+===== Release notes for ftape-2.04, 01/01/96 =====
+
+This version by Kai Harrekilde-Petersen <khp@pip.dknet.dk>
+
+- ALERT! Support for Kernels earlier then v1.1.85 is about to go away.
+  I intend to clean up some of the code (getting rid of an annoyingly
+  large numbers of #ifdef mostly), which means that support for
+  pre-1.1.85 kernels must go as well.
+- NR_FTAPE_BUFFERS is gone; You can instead select the number of dma
+  buffers by saying `insmod ftape.o ftape_num_buffer=<n>' instead.
+- Configure script gone.  ftape will now automagically determine your
+  kernel version by /usr/include/linux/version.h instead.
+- CONFIG_MODVERSIONS now work.  All combinations of versioned /
+  unversioned kernel and ftape module works (at least with my 1.3.52
+  kernel).
+- If you have problems with inserting ftape into an old (1.2.x)
+  kernel (e.g. insmod says "1.2.8 does not match 1.2.8), recompile
+  your modules utilities with your new compiler.
+- Reveal TB1400 drive added to vendors.h
+- Support for the i82078-1 (2Mbps) chip is coming along.  The
+  biggest problem is that I don't have such a card, which makes
+  testing / debugging somewhat problematic.  The second biggest
+  problem is that I do not have the QIC-3010/3020 standards either.
+  Status right now is that the chip is detected, and it should be
+  possible to put it into 2Mbps mode.  However, I do not know what
+  "extras" are needed to complete the support.  Although putting the
+  i82078 into 1Mbps mode ought to work out of the box, it doesn't
+  (right now, ftape complains about id am errors).
+
+
+===== Release notes for ftape-2.04beta5, 29/12/95 =====
+
+Bas offline linux-tape
+----------------------
+For reasons only known to the majordomo mail list processor, Bas was
+kicked off the linux-tape list sometime during the summer.  Being
+overworked at his for-pay job, he didn't notice it much.  Instead I
+(Kai, khp@pip.dknet.dk) has worked on ftape to produce the 2.04(beta)
+version.
+
+zftape
+------
+Note that there exists a much improved version of ftape, written by
+Claus-Justus Heine <claus@willi.math.rwth-aachen.de> which is named
+zftape, which conforms to the QIC-80 specs on how to mark backups, and
+is capable of doing automatic compression.  However, zftape makes
+substantial changes to ftape, and I (Kai) have therefore declined to
+integrate zftape into ftape.  Hopefully, this will happen soon.
+
+CONFIG_QIC117 removed from the kernel
+-------------------------------------
+The biggest change of all is that ftape now will allocate its dma
+buffers when it is inserted.  The means that the CONFIG_QIC117 option
+has disappeared from the Linux kernel as of v1.3.34.  If you have an
+earlier kernel, simply answer 'no' to the question will do the trick
+(if you get complains about __get_free_pages() missing, contact the
+linux-tape mailing list).
+
+Note that ftape-2.04beta will work equally well on kernels with and
+without `ftape support'.  The only catch is, that you will waste
+around 96-128Kb of precious DMA'able memory on a box that has ftape
+support compiled in.
+
+Now for the real changes:
+
+- FC-20 can now use DMA channels 1, 2, and 3. Thanks to Daniel
+  Cohen, catman@wpi.edu.
+- ftape no longer requires a (gigantic) 96Kb buffer to be statically
+  allocated by the kernel.
+- Added new Iomega drive (8882) to vendors.h
+- -fno-strength-reduce added to Makefile, since GCC is broken.
+- i82078-1 (2Mbps) FDC support started.
+
+
+===== Release notes for ftape-2.03b, 27/05/95 =====
+
+- Prevented verify_area to return error if called with zero length.
+- Fixed a bug in flush_buffers that caused too much padding to be
+  written when a final segment had bad sectors.
+- Increased maximum fast-seek overshoot value from 5 to 10 segments.
+- Breaking loop after 5 retries when positioning fails.
+- Fixed wrong calculation of tape length for QIC-3010 and QIC-3020
+  tapes (densities were swapped).
+- Fixed wrong calculation of overshoot on seek_forward: Wrong sign
+  of error.
+- Suppress (false) error message due to new tape loaded.
+- Added two new CMS drives (11c3 and 11c5) to vendors.h.
+
+
+===== Release notes for ftape-2.03a, 09/05/95 =====
+
+- Fixed display of old error (even if already cleared) in ftape_open.
+- Improved tape length detection, ioctls would fail for 425 ft tapes.
+  Until the tape length is calculated with data from the header
+  segment, we'll use worst-case values.
+- Clear eof_mark after rewinding ioctls.
+- Fixed wrong version message (2.03 had 2.02g id).
+- Fixed bug that caused the fdc to be reset very frequently.
+  This shouldn't affect normal operation but the timing of the
+  report routines has changed again and that may cause problems.
+  We'll just have to find out....
+- Implemented correct write precompensation setting for QIC-3010/3020.
+- Cleaned up fdc_interrupt_wait routine. Hope it still works :-)
+- Finally removed (already disabled) special eof mark handling for
+  gnu tar.
+- Changed order of get_dma_residue and disable_dma in fdc-isr.c
+  because the current order would fail on at least one system.
+  We're back to the original order again, hope (and expect) this
+  doesn't break any other system.
+
+
+===== Release notes for ftape-2.03, 07/05/95 =====
+
+(Changes refer to the first ftape-2.02 release)
+
+Support for wide and extended length tapes
+------------------------------------------
+The Conner TSM 420 and 850 drives are reported to be working.
+I haven't received any reports about other brands; the TSM 420
+and 850 seem to be the most widely used wide drives.
+Extended length tapes (425 ft) with normal QIC-80 drives
+are operating too (At least I've had no reports stating otherwise).
+_Not_ yet completely supported (although they may work) are
+QIC-3020 drives and 2 Mbps floppy disk controllers won't work at
+the highest speed.
+If someone is kind enough to send me one of these, I'll include
+support for it too ;-)
+
+Easier configuration
+--------------------
+Problems due to wrong settings in the Makefile are prevented
+by using a configuration script that sets the necessary (kernel
+version dependent) compile time options.
+This kernel version is now determined from the sources found
+at /usr/src/linux, or if not found, the old way using
+/proc/version.
+Versioned modules will be used automatically when supported
+by- and configured in- the kernel.
+Note that the current modules code (1.1.87) is still broken
+and _needs_ the fix included in the insmod directory.
+Please don't send me any more Oops reports caused by insmod :-(
+
+Reduced module size
+-------------------
+The standard module size is much reduced and some compile time
+options can even reduce it further. (I don't recommend this
+for normal use but it can be handy for rescue diskettes)
+
+Option:           Approx. module size:
+
+<standard>             150 Kb
+NO_TRACE               125 Kb
+NO_TRACE_AT_ALL         67 Kb
+
+
+Much improved driver interruption
+---------------------------------
+Most possible loops have been broken and signal detection
+has been improved.
+In most cases the driver can be aborted by ^C (SIGINT) and
+SIGKILL (kill -9) will generate be a sure kill.
+(Note that aborting a tape operation may damage the last
+data written to tape)
+
+Improved error recovery
+-----------------------
+Ftape now returns an error (ENODATA) to the application if
+a segment proves to be unrecoverable and then skips the
+bad segment.
+This causes most applications to continue to work (tar
+and afio) loosing only a small amount (up to 29 Kb) of data.
+Retried read operations will now be done slighly off-track
+to improve the chance of success. Serious head off-track
+errors will be detected.
+
+FC-10 and FC-20 controllers
+---------------------------
+Ftape now supports both the old CMS FC-10 and the newer FC-20
+controllers.
+Because the operation of these cards is still undocumented,
+thus far they will only work with the default settings (See
+Makefile). Any feed-back on how to use them with other settings
+will be welcome !
+Compilation will fail if one changes the settings to illegal
+values.
+
+Kernels and compilers
+---------------------
+Ftape is currently being developped using the 2.5.8 compiler.
+The older 2.4.5 probably works too (Set option in Makefile!).
+I have no experience with any later compilers nor Elf support.
+Any information on this is welcome.
+The latest kernel I have tested ftape with is 1.2.6.
+
+Compression
+-----------
+An impressive collection of changes for ftape including
+on-the-fly compression is still lying on my desk.
+If 2.03 proves to be reliable I might start integrating these
+but as usual, I'm short in time :-(
+
+Formatting
+----------
+There is still no way to format tapes under Linux. As far as
+I know all attempts to write such a program have died now.
+Since formatted tapes are rather common now, I think all we
+need is a utility that writes a worst case pattern and verifies
+that with the drive put in verify mode, reducing margins.
+Any takers ?
+
+Furthermore
+-----------
+Cleaned up messages.
+Prepared to support multiple tape drives on one fdc.
+Thanks to all the people who sent bug reports and helped me
+improve the driver. Without trying to be complete I'll mention
+Gary Anderson (without his accurate reports and unreliable
+hardware there wouldn't be a 2.03), Stefan Kneifel (FC-20),
+Robert Broughton (FC-20, you were almost there ;-), Bjorn
+Ekwall (for the versioned modules and buggy insmod ;-), Peter
+Fox, Christopher Oliver, Ralph Whittaker and not the least
+Linus Torvalds (for Linux and keeping me busy because of
+changes to the kernel ;-)
+Thanks to anyone I forgot, for the bug reports, the ftape
+bashing and the mental support...
+
+
+That's it for now. Have Fun,
+
+Bas.
+
+
+===== Release notes for ftape-2.02g, 06/05/95 =====
+
+- Added extra test to break read-id loop with signal.
+- Changed rewind code to handle negative overshoot for drives
+  that take very long to start or stop.
+- Let use of get/set i/o-regions depend on kernel version.
+- Changed code to use a more general test for conditional
+  compilations depending on kernel version.
+- Improved micro-step functionality to go off-track only
+  while reading (id & data).
+- Added failure on tape-not-referenced bit in ftape_command.
+- Added FOREVER option to read-wait routine.
+- Changed read-id to use shorter timeout causing smaller
+  rewinds on timeout.
+- Made kernel-interface functions static.
+
+
+===== Release notes for ftape-2.02f, 03/05/95 =====
+
+- Added support for dual tape drives on my system, extended Configure
+  script to detect host 'dodo'.
+- Log media defect in history if ecc failed and no data was returned.
+- Fixed Configure script that was failing for kernel versions with
+  double digit version or revision numbers.
+
+
+===== Release notes for ftape-2.02e, 01/05/95 =====
+
+- Fixed reposition loop at logical eot (failing read_id).
+- Fixed 34 segment offset when rewinding.
+- Added fast seek capability for more than 255 segments.
+- Fixed wrong busy result from ftape_command causing reverse
+  seek to fail.
+- Added breakout from infinite rewind loop (if something fails).
+
+
+===== Release notes for ftape-2.02d, 30/04/95 =====
+
+- Improved abortion on signals: Interrupt will make a gracefull
+  exit, Kill will be less nice and should be used if everything
+  else fails.
+- Included check for tape-head off track.
+- Implemented exit from tape-start loop.
+- Added kernel io-port registration.
+- Implemented skip of failing segment (ENODATA) on ecc failure.
+  This allows afio and tar to continue when the tape is damaged.
+- Made distinction between drive names with different codes.
+
+
+===== Release notes for ftape-2.02c, 22/04/95 =====
+
+- Fixed too tight command queueing after tape stop/pause command
+  issued from within interrupt service routine (Showed as timeout
+  on Acknowledge errors during retries on some systems)
+- Tried to fix timeouts when using 425 ft tape because the extended
+  length doesn't seem to be detected by the hardware.
+  We now use the format code from the header segment so adjust the
+  timing after reading the header segment.
+- Fixed some messages stating 'unexpected something...' being not
+  unexpected anymore.
+- Started preparations for merge of dynamic buffer allocation and
+  compression code.
+- Changed some debug messages to include relevant segment information
+  at level 4.
+- Included early bail-out when drive offline, preventing a lot of
+  false messages.
+- Moved ftape_parameter_xxx() offsets into function instead of in calls.
+- Removed 'weird, drive busy but no data' error when caused by
+  an error during a read-id.
+- Improved 'timeout on acknowledge' diagnostics.
+- Moved MODULE option into Configure.
+- Reduced code size when no tracing at all was set (Claus Heine).
+- No longer log error code 0 (no error) as an error.
+
+
+===== Release notes for ftape-2.02b, 09/04/95 =====
+
+- Relaxed timing for status operation and displaying
+  abnormal results. Hopefully this shows what's going
+  wrong with the Conner TSM850R drives.
+- Created script for configuration, using version number
+  of kernel source if available, otherwise /proc/version.
+- Fixed conditionals in kernel-interface.c.
+- Removed unavoidable TRACE output.
+
+
+===== Release notes for ftape-2.02a, 01/04/95 =====
+
+- Implemented `new-style' (versioned) modules support for new
+  kernels.
+- Reduced size of module by moving static data to bss.
+- Now using version number of kernel source instead of running
+  kernel for kernel versions >= 1.1.82
+- Added feedback on drive speeds to vendor information.
+- Included fixed insmod sources to distribution (Let's hope
+  the modules distribution get fixed soon :-/).
+
+Note that I haven't yet implemented any of the code extension I
+received. I hope to find some time to do this soon.
+
+
+===== Release notes for ftape-2.02, 15/01/95 =====
+
+
+- Fixed failing repositioning when overshoot was incremented.
+- Fixed rate selection: Because of a deficiency in the QIC-117
+  specification one cannot distinguish between a not implemented
+  and a failing command. Therefor we now try to find out if the
+  drive does support this command before usage.
+- Fixed error retry using wrong offset in fdc-isr.
+- Improved retry code to retry only once on a single no-data
+  error in a segment.
+- Validate sector number extracted from eof mark because an
+  invalid file mark (due to ???) could cause kernel panic.
+- Split ftape-io.c into ftape-io.c and ftape-ctl.c files.
+- Corrected too high media error count after writing to
+  a bad tape.
+- Added #include <asm/segment.h> again because old kernel versions
+  need it.
+- Fixed fdc not being disabled when open failed because no tape
+  drive was found.
+- Fixed problem with soft error in sector 32 (shift operator with
+  shiftcount 32 is not defined).
+
+
+===== Release notes for ftape-2.01, 08/01/95 =====
+
+
+- Removed TESTING setting from distributed Makefile.
+- Fixed `mt asf' failure: Rewind was deferred to close which
+  overruled the fsf ioctl.
+- Prevented non-interruptible commands being interrupted.
+- Added missing timeout.pause setting.
+- Maximum tape speed read from drive type information table.
+  If the information is not in the table (0) the drive will
+  determine the speed itself and put a message in the logfile.
+  This information should then be added to the table in the
+  vendors.h file (and reported to me).
+- Added call to ftape_init_drive after soft reset for those
+  (antique) drives that don't do an implicit seek_load_point
+  after a reset or power up.
+- Don't try to set data rate if reset failed.
+- Prevent update of seek variables when starting from the
+  beginning or the end of the tape.
+- Fixed wrong adjustment of overshoot in seek_forward().
+- Added sync to Makefile (again).
+- Added code to diagnose timer problems (calibr.c).
+- Replaced time differences by timediff calls.
+- Removed reference to do_floppy from object for recent kernels.
+- Fixed wrong display of 'failing dma controller' message.
+- Removed various no longer used #include statements.
+- Added max. tape speed value to vendor-struct.
+- Changed ftape-command to check pre-conditions and wait
+  if needed.
+- Further updated qic117.h to rev G.
+- Combined command name table and restrictions table to one.
+  Extended this table with some new fields.
+- Increased timeout on Ack timer value and included code to
+  report out of spec behaviour.
+- Increased rewind timeout margin to calculated + 20%.
+- Improved data rate selection so it won't fail on some
+  older (pre standard) drives.
+- Changed initialisation code so drive will be rewound if the
+  driver is reloaded and the tape is not at bot.
+- Moved some of the flush operations from close to the ioctls.
+- Added exit code value to failing verify area message.
+- Loop until tape halted in smart-stop.
+- Fast seek handled specially if located at bot or eot.
+- Being more conservative on overshoot value.
+
+
+===== Release notes for ftape-2.00, 31/12/94 =====
+
+  The Install-guide is completely rewritten and now also includes
+some information on how to use the driver. If you're either new
+to ftape or new to Unix tape devices make sure to read it !
+
+  If you own a pci system and experience problems with the
+ftape driver make sure to read the README.PCI file. It contains
+some hints on how to fix your hardware.
+
+  For anybody who hasn't noticed: The version number of the
+driver has been incremented (The latest released version has
+been version 1.14d).
+  This has been done for two major reasons:
+
+  o  A new (better) error recovery scheme is implemented.
+  o  Support for new drive types has been added.
+
+  All these improvements/changes will probably include a couple
+of new (and old?) bugs. If you encounter any problems that you think
+I'm not yet aware of, feel free to send a report to <bas@vimec.nl>.
+  I recommend keeping a version of ftape-1.14d available, just
+in case ;-)
+
+  This version should work with all kernel versions from 1.0.9 up
+to 1.1.72 (and probably earlier and later versions too).
+
+
+Major new features:
+
+- Better handling of tapes with defects: When a sector repeatedly
+  (SOFT_RETRIES in ftape.h) cannot be written to or read from it is
+  marked as an hard error and gets skipped.
+  The error correction code can handle up to three of these hard
+  errors provided there are no other errors in that segment (32 Kb).
+  
+- Allows writing to tapes with defects (although the risc of loosing
+  data increases !)
+  Look for the media-defects entry printed with the statistics when
+  the tape is closed. A non-zero value here shows a bad tape.
+  [the actual count is wrong (too high), this is a known bug].
+
+- Use of backup header segment if first one is failing.
+
+- Support for extended length tapes with QIC-80: both 425 and 1100 ft.
+  0.25 inch tapes are now recognized and handled.
+
+- Support for new QIC-80 drives with 8 mm `wide' tapes (e.g. Conner
+  TSM 420).
+
+- Support for new QIC-3010 and QIC-3020 drives (experimental) with
+  both 0.25 inch and 8 mm tapes.
+
+Some minor features were added, a couple of small bugs were fixed and
+probably some new ones introduced ;-).
+
+[lseek() didn't make it into this version]
+
+Have fun,
+
+Bas.
+----
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/calibr.c linux/drivers/char/ftape/calibr.c
--- v1.3.71/linux/drivers/char/ftape/calibr.c	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/calibr.c	Wed Mar  6 15:07:18 1996
@@ -0,0 +1,184 @@
+/* Yo, Emacs! we're -*- Linux-C -*-
+ *
+ *      Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ *      GP calibration routine for processor speed dependent
+ *      functions.
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/ftape.h>
+#include <asm/system.h>
+#include <asm/io.h>
+
+#include "tracing.h"
+#include "calibr.h"
+#include "fdc-io.h"
+
+#undef DEBUG
+
+unsigned timestamp(void)
+{
+	unsigned count;
+	unsigned long flags;
+
+	save_flags(flags);
+	cli();
+	outb_p(0x00, 0x43);	/* latch the count ASAP */
+	count = inb_p(0x40);	/* read the latched count */
+	count |= inb(0x40) << 8;
+	restore_flags(flags);
+	return (LATCH - count);	/* normal: downcounter */
+}
+
+int timediff(int t0, int t1)
+{
+	/*  Calculate difference in usec for timestamp results t0 & t1.
+	 *  Note that the maximum timespan allowed is 1/HZ or we'll lose ticks!
+	 */
+	if (t1 < t0) {
+		t1 += LATCH;
+	}
+	return (1000 * (t1 - t0)) / ((CLOCK_TICK_RATE + 500) / 1000);
+}
+
+/*      To get an indication of the I/O performance,
+ *      measure the duration of the inb() function.
+ */
+void time_inb(void)
+{
+	TRACE_FUN(8, "time_inb");
+	int i;
+	int t0, t1;
+	unsigned long flags;
+	int status;
+
+	save_flags(flags);
+	cli();
+	t0 = timestamp();
+	for (i = 0; i < 1000; ++i) {
+		status = inb(fdc.msr);
+	}
+	t1 = timestamp();
+	restore_flags(flags);
+	if (t1 - t0 <= 0) {
+		t1 += LATCH;
+	}
+	TRACEx1(4, "inb() duration: %d nsec", timediff(t0, t1));
+	TRACE_EXIT;
+}
+
+/*  Haven't studied on why, but there sometimes is a problem
+ *  with the tick timer readout. The two bytes get swapped.
+ *  This hack solves that problem by doing one extra input.
+ */
+void fix_clock(void)
+{
+	TRACE_FUN(8, "fix_clock");
+	int t;
+	int i;
+
+	for (i = 0; i < 1000; ++i) {
+		t = timestamp();
+		if (t < 0) {
+			inb_p(0x40);	/* get in sync again */
+			TRACE(2, "clock counter fixed");
+			break;
+		}
+	}
+	TRACE_EXIT;
+}
+
+/*
+ *      Input:  function taking int count as parameter.
+ *              pointers to calculated calibration variables.
+ */
+int calibrate(char *name, void (*fun) (int), int *calibr_count, int *calibr_time)
+{
+	TRACE_FUN(5, "calibrate");
+	static int first_time = 1;
+	int i;
+	int old_tc = 0;
+	int old_count = 1;
+	int old_time = 1;
+
+	if (first_time) {	/* get idea of I/O performance */
+		fix_clock();
+		time_inb();
+		first_time = 0;
+	}
+	/*    value of timeout must be set so that on very slow systems
+	 *    it will give a time less than one jiffy, and on
+	 *    very fast systems it'll give reasonable precision.
+	 */
+
+	*calibr_count = 10;
+	for (i = 0; i < 15; ++i) {
+		int t0, t1;
+		unsigned long flags;
+		int once;
+		int multiple;
+		int tc;
+
+		*calibr_time = *calibr_count;	/* set TC to 1 */
+		fun(0);		/* dummy, get code into cache */
+		save_flags(flags);
+		cli();
+		t0 = timestamp();
+		fun(0);		/* overhead + one test */
+		t1 = timestamp();
+		if (t1 < t0) {
+			t1 += LATCH;
+		}
+		once = t1 - t0;
+		t0 = timestamp();
+		fun(*calibr_count);	/* overhead + multiple tests */
+		t1 = timestamp();
+		if (t1 < t0) {
+			t1 += LATCH;
+		}
+		multiple = t1 - t0;
+		restore_flags(flags);
+		*calibr_time = (10000 * (multiple - once)) / (CLOCK_TICK_RATE / 100);
+		--*calibr_count;	/* because delta corresponds to this count */
+		tc = (1000 * *calibr_time) / *calibr_count;
+		TRACEx4(8, "once:%4d us,%5d times:%6d us, TC:%5d ns",
+			(10000 * once) / (CLOCK_TICK_RATE / 100),
+			*calibr_count,
+			(10000 * multiple) / (CLOCK_TICK_RATE / 100),
+			tc);
+		/*
+		 * increase the count until the resulting time nears 2/HZ,
+		 * then the tc will drop sharply because we lose LATCH counts.
+		 */
+		if (tc <= old_tc / 2) {
+			*calibr_time = old_time;
+			*calibr_count = old_count;
+			break;
+		}
+		old_tc = tc;
+		old_count = *calibr_count;
+		old_time = *calibr_time;
+		*calibr_count *= 2;
+	}
+	TRACEx3(4, "TC for `%s()' = %d nsec (at %d counts)",
+	     name, (1000 * *calibr_time) / *calibr_count, *calibr_count);
+	TRACE_EXIT;
+	return 0;
+}
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/calibr.h linux/drivers/char/ftape/calibr.h
--- v1.3.71/linux/drivers/char/ftape/calibr.h	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/calibr.h	Wed Mar  6 15:07:18 1996
@@ -0,0 +1,39 @@
+#ifndef _CALIBRATE_H
+#define _CALIBRATE_H
+
+/*
+ *      Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ $Source: /home/bas/distr/ftape-2.03b/RCS/calibr.h,v $
+ $Author: bas $
+ *
+ $Revision: 1.20 $
+ $Date: 1995/04/22 07:30:15 $
+ $State: Beta $
+ *
+ *      This file contains a gp calibration routine for
+ *      hardware dependent timeout functions.
+ */
+
+#include <linux/timex.h>
+
+extern int calibrate(char *name, void (*fun) (int), int *calibr_count, int *calibr_time);
+extern unsigned timestamp(void);
+extern int timediff(int t0, int t1);
+
+#endif
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/ecc.c linux/drivers/char/ftape/ecc.c
--- v1.3.71/linux/drivers/char/ftape/ecc.c	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/ecc.c	Wed Mar  6 15:07:18 1996
@@ -0,0 +1,894 @@
+/* Yo, Emacs! we're -*- Linux-C -*-
+ *
+ *      Copyright (c) 1993 Ning and David Mosberger.
+ *
+ * This is based on code originally written by Bas Laarhoven (bas@vimec.nl)
+ * and David L. Brown, Jr., and incorporates improvements suggested by
+ * Kai Harrekilde-Petersen.
+ *
+ * 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.
+ *
+ * $Source: /home/bas/distr/ftape-2.03b/RCS/ecc.c,v $
+ * $Author: bas $
+ *
+ * $Revision: 1.32 $
+ * $Date: 1995/04/22 07:30:15 $
+ * $State: Beta $
+ *
+ *      This file contains the Reed-Solomon error correction code 
+ *      for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include <linux/module.h>
+#include <linux/ftape.h>
+#include <stdio.h>
+#include <sys/errno.h>
+
+#include "tracing.h"
+#include "ecc.h"
+
+/*
+ * Machines that are big-endian should define macro BIG_ENDIAN.
+ * Unfortunately, there doesn't appear to be a standard include
+ * file that works for all OSs.
+ */
+
+#if defined(__sparc__) || defined(__hppa)
+#define BIG_ENDIAN
+#endif				/* __sparc__ || __hppa */
+
+#if defined(__mips__)
+#error Find a smart way to determine the Endianness of the MIPS CPU
+#endif
+
+#ifdef TEST
+
+#undef TRACE()
+#undef TRACE_()
+#undef TRACE()
+#undef TRACEi()
+#undef TRACElx()
+#undef TRACE_FUN()
+#undef TRACE_EXIT
+#define printk  printf
+#define TRACE_FUN( level, name) char __fun[] = name
+#define TRACE_EXIT
+#define TRACE_(l,m) { if (ftape_ecc_tracing >= (l) && (l) <= TOP_LEVEL) { \
+    printk( "[%03d] " __FILE__ " (%s) - ", (int)ftape_trace_id++, __fun); \
+    m;  } }
+#define TRACE(l,m) TRACE_(l,printk(m".\n"))
+#define TRACEi(l,m,i) TRACE_(l,printk(m" %d.\n",i))
+#define TRACElx(l,m,i) TRACE_(l,printk(m" 0x%08lx.\n",i))
+
+int ftape_ecc_tracing = 1;
+unsigned char ftape_trace_id = 0;
+
+#endif				/* TEST */
+
+/*
+ * Notice: to minimize the potential for confusion, we use r to
+ *         denote the independent variable of the polynomials
+ *         in the Galois Field GF(2^8).  We reserve x for polynomials
+ *         that that have coefficients in GF(2^8).
+ *         
+ * The Galois Field in which coefficient arithmetic is performed are
+ * the polynomials over Z_2 (i.e., 0 and 1) modulo the irreducible
+ * polynomial f(r), where f(r)=r^8 + r^7 + r^2 + r + 1.  A polynomial
+ * is represented as a byte with the MSB as the coefficient of r^7 and
+ * the LSB as the coefficient of r^0.  For example, the binary
+ * representation of f(x) is 0x187 (of course, this doesn't fit into 8
+ * bits).  In this field, the polynomial r is a primitive element.
+ * That is, r^i with i in 0,...,255 enumerates all elements in the
+ * field.
+ *
+ * The generator polynomial for the QIC-80 ECC is
+ *
+ *      g(x) = x^3 + r^105*x^2 + r^105*x + 1
+ *
+ * which can be factored into:
+ *
+ *      g(x) = (x-r^-1)(x-r^0)(x-r^1)
+ *
+ * the byte representation of the coefficients are:
+ *
+ *      r^105 = 0xc0
+ *      r^-1  = 0xc3
+ *      r^0   = 0x01
+ *      r^1   = 0x02
+ *
+ * Notice that r^-1 = r^254 as exponent arithmetic is performed
+ * modulo 2^8-1 = 255.
+ *
+ * For more information on Galois Fields and Reed-Solomon codes,
+ * refer to any good book.  I found _An Introduction to Error
+ * Correcting Codes with Applications_ by S. A. Vanstone and
+ * P. C. van Oorschot to be a good introduction into the former.
+ * _CODING THEORY: The Essentials_ I found very useful for its
+ * concise description of Reed-Solomon encoding/decoding.
+ *
+ */
+
+typedef unsigned char Matrix[3][3];
+
+/*
+ * gfpow[] is defined such that gfpow[i] returns r^i if
+ * i is in the range [0..255].
+ */
+static const unsigned char gfpow[] =
+{
+	0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
+	0x87, 0x89, 0x95, 0xad, 0xdd, 0x3d, 0x7a, 0xf4,
+	0x6f, 0xde, 0x3b, 0x76, 0xec, 0x5f, 0xbe, 0xfb,
+	0x71, 0xe2, 0x43, 0x86, 0x8b, 0x91, 0xa5, 0xcd,
+	0x1d, 0x3a, 0x74, 0xe8, 0x57, 0xae, 0xdb, 0x31,
+	0x62, 0xc4, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, 0x67,
+	0xce, 0x1b, 0x36, 0x6c, 0xd8, 0x37, 0x6e, 0xdc,
+	0x3f, 0x7e, 0xfc, 0x7f, 0xfe, 0x7b, 0xf6, 0x6b,
+	0xd6, 0x2b, 0x56, 0xac, 0xdf, 0x39, 0x72, 0xe4,
+	0x4f, 0x9e, 0xbb, 0xf1, 0x65, 0xca, 0x13, 0x26,
+	0x4c, 0x98, 0xb7, 0xe9, 0x55, 0xaa, 0xd3, 0x21,
+	0x42, 0x84, 0x8f, 0x99, 0xb5, 0xed, 0x5d, 0xba,
+	0xf3, 0x61, 0xc2, 0x03, 0x06, 0x0c, 0x18, 0x30,
+	0x60, 0xc0, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0,
+	0x47, 0x8e, 0x9b, 0xb1, 0xe5, 0x4d, 0x9a, 0xb3,
+	0xe1, 0x45, 0x8a, 0x93, 0xa1, 0xc5, 0x0d, 0x1a,
+	0x34, 0x68, 0xd0, 0x27, 0x4e, 0x9c, 0xbf, 0xf9,
+	0x75, 0xea, 0x53, 0xa6, 0xcb, 0x11, 0x22, 0x44,
+	0x88, 0x97, 0xa9, 0xd5, 0x2d, 0x5a, 0xb4, 0xef,
+	0x59, 0xb2, 0xe3, 0x41, 0x82, 0x83, 0x81, 0x85,
+	0x8d, 0x9d, 0xbd, 0xfd, 0x7d, 0xfa, 0x73, 0xe6,
+	0x4b, 0x96, 0xab, 0xd1, 0x25, 0x4a, 0x94, 0xaf,
+	0xd9, 0x35, 0x6a, 0xd4, 0x2f, 0x5e, 0xbc, 0xff,
+	0x79, 0xf2, 0x63, 0xc6, 0x0b, 0x16, 0x2c, 0x58,
+	0xb0, 0xe7, 0x49, 0x92, 0xa3, 0xc1, 0x05, 0x0a,
+	0x14, 0x28, 0x50, 0xa0, 0xc7, 0x09, 0x12, 0x24,
+	0x48, 0x90, 0xa7, 0xc9, 0x15, 0x2a, 0x54, 0xa8,
+	0xd7, 0x29, 0x52, 0xa4, 0xcf, 0x19, 0x32, 0x64,
+	0xc8, 0x17, 0x2e, 0x5c, 0xb8, 0xf7, 0x69, 0xd2,
+	0x23, 0x46, 0x8c, 0x9f, 0xb9, 0xf5, 0x6d, 0xda,
+	0x33, 0x66, 0xcc, 0x1f, 0x3e, 0x7c, 0xf8, 0x77,
+	0xee, 0x5b, 0xb6, 0xeb, 0x51, 0xa2, 0xc3, 0x01
+};
+
+/*
+ * This is a log table.  That is, gflog[r^i] returns i (modulo f(r)).
+ * gflog[0] is undefined and the first element is therefore not valid.
+ */
+static const unsigned char gflog[256] =
+{
+	0xff, 0x00, 0x01, 0x63, 0x02, 0xc6, 0x64, 0x6a,
+	0x03, 0xcd, 0xc7, 0xbc, 0x65, 0x7e, 0x6b, 0x2a,
+	0x04, 0x8d, 0xce, 0x4e, 0xc8, 0xd4, 0xbd, 0xe1,
+	0x66, 0xdd, 0x7f, 0x31, 0x6c, 0x20, 0x2b, 0xf3,
+	0x05, 0x57, 0x8e, 0xe8, 0xcf, 0xac, 0x4f, 0x83,
+	0xc9, 0xd9, 0xd5, 0x41, 0xbe, 0x94, 0xe2, 0xb4,
+	0x67, 0x27, 0xde, 0xf0, 0x80, 0xb1, 0x32, 0x35,
+	0x6d, 0x45, 0x21, 0x12, 0x2c, 0x0d, 0xf4, 0x38,
+	0x06, 0x9b, 0x58, 0x1a, 0x8f, 0x79, 0xe9, 0x70,
+	0xd0, 0xc2, 0xad, 0xa8, 0x50, 0x75, 0x84, 0x48,
+	0xca, 0xfc, 0xda, 0x8a, 0xd6, 0x54, 0x42, 0x24,
+	0xbf, 0x98, 0x95, 0xf9, 0xe3, 0x5e, 0xb5, 0x15,
+	0x68, 0x61, 0x28, 0xba, 0xdf, 0x4c, 0xf1, 0x2f,
+	0x81, 0xe6, 0xb2, 0x3f, 0x33, 0xee, 0x36, 0x10,
+	0x6e, 0x18, 0x46, 0xa6, 0x22, 0x88, 0x13, 0xf7,
+	0x2d, 0xb8, 0x0e, 0x3d, 0xf5, 0xa4, 0x39, 0x3b,
+	0x07, 0x9e, 0x9c, 0x9d, 0x59, 0x9f, 0x1b, 0x08,
+	0x90, 0x09, 0x7a, 0x1c, 0xea, 0xa0, 0x71, 0x5a,
+	0xd1, 0x1d, 0xc3, 0x7b, 0xae, 0x0a, 0xa9, 0x91,
+	0x51, 0x5b, 0x76, 0x72, 0x85, 0xa1, 0x49, 0xeb,
+	0xcb, 0x7c, 0xfd, 0xc4, 0xdb, 0x1e, 0x8b, 0xd2,
+	0xd7, 0x92, 0x55, 0xaa, 0x43, 0x0b, 0x25, 0xaf,
+	0xc0, 0x73, 0x99, 0x77, 0x96, 0x5c, 0xfa, 0x52,
+	0xe4, 0xec, 0x5f, 0x4a, 0xb6, 0xa2, 0x16, 0x86,
+	0x69, 0xc5, 0x62, 0xfe, 0x29, 0x7d, 0xbb, 0xcc,
+	0xe0, 0xd3, 0x4d, 0x8c, 0xf2, 0x1f, 0x30, 0xdc,
+	0x82, 0xab, 0xe7, 0x56, 0xb3, 0x93, 0x40, 0xd8,
+	0x34, 0xb0, 0xef, 0x26, 0x37, 0x0c, 0x11, 0x44,
+	0x6f, 0x78, 0x19, 0x9a, 0x47, 0x74, 0xa7, 0xc1,
+	0x23, 0x53, 0x89, 0xfb, 0x14, 0x5d, 0xf8, 0x97,
+	0x2e, 0x4b, 0xb9, 0x60, 0x0f, 0xed, 0x3e, 0xe5,
+	0xf6, 0x87, 0xa5, 0x17, 0x3a, 0xa3, 0x3c, 0xb7
+};
+
+/*
+ * This is a multiplication table for the factor
+ * 0xc0 (i.e., r^105 (modulo f(r)).
+ * gfmul_c0[f] returns r^105 * f(r) (modulo f(r)).
+ */
+static const unsigned char gfmul_c0[256] =
+{
+	0x00, 0xc0, 0x07, 0xc7, 0x0e, 0xce, 0x09, 0xc9,
+	0x1c, 0xdc, 0x1b, 0xdb, 0x12, 0xd2, 0x15, 0xd5,
+	0x38, 0xf8, 0x3f, 0xff, 0x36, 0xf6, 0x31, 0xf1,
+	0x24, 0xe4, 0x23, 0xe3, 0x2a, 0xea, 0x2d, 0xed,
+	0x70, 0xb0, 0x77, 0xb7, 0x7e, 0xbe, 0x79, 0xb9,
+	0x6c, 0xac, 0x6b, 0xab, 0x62, 0xa2, 0x65, 0xa5,
+	0x48, 0x88, 0x4f, 0x8f, 0x46, 0x86, 0x41, 0x81,
+	0x54, 0x94, 0x53, 0x93, 0x5a, 0x9a, 0x5d, 0x9d,
+	0xe0, 0x20, 0xe7, 0x27, 0xee, 0x2e, 0xe9, 0x29,
+	0xfc, 0x3c, 0xfb, 0x3b, 0xf2, 0x32, 0xf5, 0x35,
+	0xd8, 0x18, 0xdf, 0x1f, 0xd6, 0x16, 0xd1, 0x11,
+	0xc4, 0x04, 0xc3, 0x03, 0xca, 0x0a, 0xcd, 0x0d,
+	0x90, 0x50, 0x97, 0x57, 0x9e, 0x5e, 0x99, 0x59,
+	0x8c, 0x4c, 0x8b, 0x4b, 0x82, 0x42, 0x85, 0x45,
+	0xa8, 0x68, 0xaf, 0x6f, 0xa6, 0x66, 0xa1, 0x61,
+	0xb4, 0x74, 0xb3, 0x73, 0xba, 0x7a, 0xbd, 0x7d,
+	0x47, 0x87, 0x40, 0x80, 0x49, 0x89, 0x4e, 0x8e,
+	0x5b, 0x9b, 0x5c, 0x9c, 0x55, 0x95, 0x52, 0x92,
+	0x7f, 0xbf, 0x78, 0xb8, 0x71, 0xb1, 0x76, 0xb6,
+	0x63, 0xa3, 0x64, 0xa4, 0x6d, 0xad, 0x6a, 0xaa,
+	0x37, 0xf7, 0x30, 0xf0, 0x39, 0xf9, 0x3e, 0xfe,
+	0x2b, 0xeb, 0x2c, 0xec, 0x25, 0xe5, 0x22, 0xe2,
+	0x0f, 0xcf, 0x08, 0xc8, 0x01, 0xc1, 0x06, 0xc6,
+	0x13, 0xd3, 0x14, 0xd4, 0x1d, 0xdd, 0x1a, 0xda,
+	0xa7, 0x67, 0xa0, 0x60, 0xa9, 0x69, 0xae, 0x6e,
+	0xbb, 0x7b, 0xbc, 0x7c, 0xb5, 0x75, 0xb2, 0x72,
+	0x9f, 0x5f, 0x98, 0x58, 0x91, 0x51, 0x96, 0x56,
+	0x83, 0x43, 0x84, 0x44, 0x8d, 0x4d, 0x8a, 0x4a,
+	0xd7, 0x17, 0xd0, 0x10, 0xd9, 0x19, 0xde, 0x1e,
+	0xcb, 0x0b, 0xcc, 0x0c, 0xc5, 0x05, 0xc2, 0x02,
+	0xef, 0x2f, 0xe8, 0x28, 0xe1, 0x21, 0xe6, 0x26,
+	0xf3, 0x33, 0xf4, 0x34, 0xfd, 0x3d, 0xfa, 0x3a
+};
+
+
+/*
+ * Returns V modulo 255 provided V is in the range -255,-254,...,509.
+ */
+static inline unsigned char mod255(int v)
+{
+	if (v > 0) {
+		if (v < 255) {
+			return v;
+		} else {
+			return v - 255;
+		}
+	} else {
+		return v + 255;
+	}
+}
+
+
+/*
+ * Add two numbers in the field.  Addition in this field is
+ * equivalent to a bit-wise exclusive OR operation---subtraction
+ * is therefore identical to addition.
+ */
+static inline unsigned char gfadd(unsigned char a, unsigned char b)
+{
+	return a ^ b;
+}
+
+
+/*
+ * Add two vectors of numbers in the field.  Each byte in A and B get
+ * added individually.
+ */
+static inline unsigned long gfadd_long(unsigned long a, unsigned long b)
+{
+	return a ^ b;
+}
+
+
+/*
+ * Multiply two numbers in the field:
+ */
+static inline unsigned char gfmul(unsigned char a, unsigned char b)
+{
+	if (a && b) {
+		return gfpow[mod255(gflog[a] + gflog[b])];
+	} else {
+		return 0;
+	}
+}
+
+
+/*
+ * Just like gfmul, except we have already looked up the log
+ * of the second number.
+ */
+static inline unsigned char gfmul_exp(unsigned char a, int b)
+{
+	if (a) {
+		return gfpow[mod255(gflog[a] + b)];
+	} else {
+		return 0;
+	}
+}
+
+
+/*
+ * Just like gfmul_exp, except that A is a vector of numbers.  That is,
+ * each byte in A gets multiplied by gfpow[mod255(B)].
+ */
+static inline unsigned long gfmul_exp_long(unsigned long a, int b)
+{
+	TRACE_FUN(8, "gfmul_exp_long");
+	unsigned char t;
+
+	if (sizeof(long) == 4) {
+		TRACE_EXIT;
+		return
+		    ((t = a >> 24 & 0xff) ? (((unsigned long) gfpow[mod255(gflog[t] + b)]) << 24) : 0) |
+		    ((t = a >> 16 & 0xff) ? (((unsigned long) gfpow[mod255(gflog[t] + b)]) << 16) : 0) |
+		    ((t = a >> 8 & 0xff) ? (((unsigned long) gfpow[mod255(gflog[t] + b)]) << 8) : 0) |
+		    ((t = a >> 0 & 0xff) ? (((unsigned long) gfpow[mod255(gflog[t] + b)]) << 0) : 0);
+#if !defined(linux)
+	} else if (sizeof(long) == 8) {
+		TRACE_EXIT;
+		return
+		    ((t = a >> 56 & 0xff) ? (((unsigned long) gfpow[mod255(gflog[t] + b)]) << 56) : 0) |
+		    ((t = a >> 48 & 0xff) ? (((unsigned long) gfpow[mod255(gflog[t] + b)]) << 48) : 0) |
+		    ((t = a >> 40 & 0xff) ? (((unsigned long) gfpow[mod255(gflog[t] + b)]) << 40) : 0) |
+		    ((t = a >> 32 & 0xff) ? (((unsigned long) gfpow[mod255(gflog[t] + b)]) << 32) : 0) |
+		    ((t = a >> 24 & 0xff) ? (((unsigned long) gfpow[mod255(gflog[t] + b)]) << 24) : 0) |
+		    ((t = a >> 16 & 0xff) ? (((unsigned long) gfpow[mod255(gflog[t] + b)]) << 16) : 0) |
+		    ((t = a >> 8 & 0xff) ? (((unsigned long) gfpow[mod255(gflog[t] + b)]) << 8) : 0) |
+		    ((t = a >> 0 & 0xff) ? (((unsigned long) gfpow[mod255(gflog[t] + b)]) << 0) : 0);
+#endif
+	} else {
+		TRACEx1(1, "Error: size of long is %d bytes", (int) sizeof(long));
+	}
+	TRACE_EXIT;
+	return -1;
+}
+
+
+/*
+ * Divide two numbers in the field.  Returns a/b (modulo f(x)).
+ */
+static inline unsigned char gfdiv(unsigned char a, unsigned char b)
+{
+	TRACE_FUN(8, "gfdiv");
+	if (!b) {
+		TRACE(-1, "Error: division by zero");
+		return 0xff;
+	} else if (a == 0) {
+		return 0;
+	} else {
+		return gfpow[mod255(gflog[a] - gflog[b])];
+	}
+	TRACE_EXIT;
+}
+
+
+/*
+ * The following functions return the inverse of the matrix of the
+ * linear system that needs to be solved to determine the error
+ * magnitudes.  The first deals with matrices of rank 3, while the
+ * second deals with matrices of rank 2.  The error indices are passed
+ * in arguments L0,..,L2 (0=first sector, 31=last sector).  The
+ * error indices must be sorted in ascending order, i.e., L0<L1<L2.
+ *
+ * The linear system that needs to be solved for the error
+ * magnitudes is A * b = s, where s is the known vector of
+ * syndromes, b is the vector of error magnitudes and A in
+ * the ORDER=3 case:
+ *
+ *    A_3 = {{1/r^L[0], 1/r^L[1], 1/r^L[2]},
+ *          {        1,        1,        1},
+ *          {   r^L[0],   r^L[1],   r^L[2]}}
+ */
+static inline int gfinv3(unsigned char l0, unsigned char l1, unsigned char l2, Matrix Ainv)
+{
+	TRACE_FUN(8, "gfinv3");
+	unsigned char det;
+	unsigned char t20, t10, t21, t12, t01, t02;
+	int log_det;
+
+	/* compute some intermediate results: */
+	t20 = gfpow[l2 - l0];	/* t20 = r^l2/r^l0 */
+	t10 = gfpow[l1 - l0];	/* t10 = r^l1/r^l0 */
+	t21 = gfpow[l2 - l1];	/* t21 = r^l2/r^l1 */
+	t12 = gfpow[l1 - l2 + 255];	/* t12 = r^l1/r^l2 */
+	t01 = gfpow[l0 - l1 + 255];	/* t01 = r^l0/r^l1 */
+	t02 = gfpow[l0 - l2 + 255];	/* t02 = r^l0/r^l2 */
+	/*
+	 * Calculate the determinant of matrix A_3^-1 (sometimes called
+	 * the Vandermonde determinant):
+	 */
+	det = gfadd(t20, gfadd(t10, gfadd(t21, gfadd(t12, gfadd(t01, t02)))));
+	if (!det) {
+		TRACE(1, "Inversion failed (3 CRC errors, >0 CRC failures)");
+		TRACE_EXIT;
+		return 0;
+	}
+	log_det = 255 - gflog[det];
+
+	/*
+	 * Now, calculate all of the coefficients:
+	 */
+	Ainv[0][0] = gfmul_exp(gfadd(gfpow[l1], gfpow[l2]), log_det);
+	Ainv[0][1] = gfmul_exp(gfadd(t21, t12), log_det);
+	Ainv[0][2] = gfmul_exp(gfadd(gfpow[255 - l1], gfpow[255 - l2]), log_det);
+
+	Ainv[1][0] = gfmul_exp(gfadd(gfpow[l0], gfpow[l2]), log_det);
+	Ainv[1][1] = gfmul_exp(gfadd(t20, t02), log_det);
+	Ainv[1][2] = gfmul_exp(gfadd(gfpow[255 - l0], gfpow[255 - l2]), log_det);
+
+	Ainv[2][0] = gfmul_exp(gfadd(gfpow[l0], gfpow[l1]), log_det);
+	Ainv[2][1] = gfmul_exp(gfadd(t10, t01), log_det);
+	Ainv[2][2] = gfmul_exp(gfadd(gfpow[255 - l0], gfpow[255 - l1]), log_det);
+
+	TRACE_EXIT;
+	return 1;
+}
+
+
+static inline int gfinv2(unsigned char l0, unsigned char l1, Matrix Ainv)
+{
+	TRACE_FUN(8, "gfinv2");
+	unsigned char det;
+	unsigned char t1, t2;
+	int log_det;
+
+	t1 = gfpow[255 - l0];
+	t2 = gfpow[255 - l1];
+	det = gfadd(t1, t2);
+	if (!det) {
+		TRACE(1, "Inversion failed (2 CRC errors, >0 CRC failures)");
+		TRACE_EXIT;
+		return 0;
+	}
+	log_det = 255 - gflog[det];
+
+	/*
+	 * Now, calculate all of the coefficients:
+	 */
+	Ainv[0][0] = Ainv[1][0] = gfpow[log_det];
+
+	Ainv[0][1] = gfmul_exp(t2, log_det);
+	Ainv[1][1] = gfmul_exp(t1, log_det);
+
+	TRACE_EXIT;
+	return 1;
+}
+
+
+/*
+ * Multiply matrix A by vector S and return result in vector B.
+ * M is assumed to be of order NxN, S and B of order Nx1.
+ */
+static inline void gfmat_mul(int n, Matrix A, unsigned char *s, unsigned char *b)
+{
+	int i, j;
+	unsigned char dot_prod;
+
+	for (i = 0; i < n; ++i) {
+		dot_prod = 0;
+		for (j = 0; j < n; ++j) {
+			dot_prod = gfadd(dot_prod, gfmul(A[i][j], s[j]));
+		}
+		b[i] = dot_prod;
+	}
+}
+
+
+
+/*
+ * The Reed Solomon ECC codes are computed over the N-th byte of each
+ * block, where N=SECTOR_SIZE.  There are up to 29 blocks of data, and
+ * 3 blocks of ECC.  The blocks are stored contiguously in memory.
+ * A segment, consequently, is assumed to have at least 4 blocks:
+ * one or more data blocks plus three ECC blocks.
+ *
+ * Notice: In QIC-80 speak, a CRC error is a sector with an incorrect
+ *         CRC.  A CRC failure is a sector with incorrect data, but
+ *         a valid CRC.  In the error control literature, the former
+ *         is usually called "erasure", the latter "error."
+ */
+/*
+ * Compute the parity bytes for C columns of data, where C is the
+ * number of bytes that fit into a long integer.  We use a linear
+ * feed-back register to do this.  The parity bytes P[0], P[STRIDE],
+ * P[2*STRIDE] are computed such that:
+ *
+ *              x^k * p(x) + m(x) = 0 (modulo g(x))
+ *
+ * where k = NBLOCKS,
+ *       p(x) = P[0] + P[STRIDE]*x + P[2*STRIDE]*x^2, and
+ *       m(x) = sum_{i=0}^k m_i*x^i.
+ *       m_i  = DATA[i*SECTOR_SIZE]
+ */
+static inline void set_parity(unsigned long *data, int nblocks, unsigned long *p, int stride)
+{
+	TRACE_FUN(8, "set_parity");
+	unsigned long p0, p1, p2, t1, t2, *end;
+
+	end = data + nblocks * (SECTOR_SIZE / sizeof(long));
+	p0 = p1 = p2 = 0;
+	while (data < end) {
+		/*
+		 * The new parity bytes p0_i, p1_i, p2_i are computed from the old
+		 * values p0_{i-1}, p1_{i-1}, p2_{i-1} recursively as:
+		 *
+		 *        p0_i = p1_{i-1} + r^105 * (m_{i-1} - p0_{i-1})
+		 *        p1_i = p2_{i-1} + r^105 * (m_{i-1} - p0_{i-1})
+		 *        p2_i =                    (m_{i-1} - p0_{i-1})
+		 *
+		 * With the initial condition: p0_0 = p1_0 = p2_0 = 0.
+		 */
+		t1 = gfadd_long(*data, p0);
+		/*
+		 * Multiply each byte in t1 by 0xc0:
+		 */
+		if (sizeof(long) == 4) {
+			t2 = ((unsigned long) gfmul_c0[t1 >> 24 & 0xff]) << 24 |
+			    ((unsigned long) gfmul_c0[t1 >> 16 & 0xff]) << 16 |
+			    ((unsigned long) gfmul_c0[t1 >> 8 & 0xff]) << 8 |
+			    ((unsigned long) gfmul_c0[t1 >> 0 & 0xff]) << 0;
+#if !defined(linux)
+		} else if (sizeof(long) == 8) {
+			t2 = ((unsigned long) gfmul_c0[t1 >> 56 & 0xff]) << 56 |
+			    ((unsigned long) gfmul_c0[t1 >> 48 & 0xff]) << 48 |
+			    ((unsigned long) gfmul_c0[t1 >> 40 & 0xff]) << 40 |
+			    ((unsigned long) gfmul_c0[t1 >> 32 & 0xff]) << 32 |
+			    ((unsigned long) gfmul_c0[t1 >> 24 & 0xff]) << 24 |
+			    ((unsigned long) gfmul_c0[t1 >> 16 & 0xff]) << 16 |
+			    ((unsigned long) gfmul_c0[t1 >> 8 & 0xff]) << 8 |
+			    ((unsigned long) gfmul_c0[t1 >> 0 & 0xff]) << 0;
+#endif
+		} else {
+			TRACEx1(1, "Error: long is of size %d", (int) sizeof(long));
+		}
+		p0 = gfadd_long(t2, p1);
+		p1 = gfadd_long(t2, p2);
+		p2 = t1;
+		data += SECTOR_SIZE / sizeof(long);
+	}
+	*p = p0;
+	p += stride;
+	*p = p1;
+	p += stride;
+	*p = p2;
+	TRACE_EXIT;
+}
+
+
+/*
+ * Compute the 3 syndrome values.  DATA should point to the first byte
+ * of the column for which the syndromes are desired.  The syndromes
+ * are computed over the first NBLOCKS of rows.  The three bytes will be
+ * placed in S[0], S[1], and S[2].
+ *
+ * S[i] is the value of the "message" polynomial m(x) evaluated at the
+ * i-th root of the generator polynomial g(x).
+ *
+ * As g(x)=(x-r^-1)(x-1)(x-r^1) we evaluate the message polynomial at
+ * x=r^-1 to get S[0], at x=r^0=1 to get S[1], and at x=r to get S[2].
+ * This could be done directly and efficiently via the Horner scheme.
+ * However, it would require multiplication tables for the factors
+ * r^-1 (0xc3) and r (0x02).  The following scheme does not require
+ * any multiplication tables beyond what's needed for set_parity()
+ * anyway and is slightly faster if there are no errors and slightly
+ * slower if there are errors.  The latter is hopefully the infrequent
+ * case.
+ *
+ * To understand the alternative algorithm, notice that
+ * set_parity(m, k, p) computes parity bytes such that:
+ *
+ *      x^k * p(x) = m(x) (modulo g(x)).
+ *
+ * That is, to evaluate m(r^m), where r^m is a root of g(x), we can
+ * simply evaluate (r^m)^k*p(r^m).  Also, notice that p is 0 if and
+ * only if s is zero.  That is, if all parity bytes are 0, we know
+ * there is no error in the data and consequently there is no need to
+ * compute s(x) at all!  In all other cases, we compute s(x) from p(x)
+ * by evaluating (r^m)^k*p(r^m) for m=-1, m=0, and m=1.  The p(x)
+ * polynomial is evaluated via the Horner scheme.
+ */
+static int compute_syndromes(unsigned long *data, int nblocks, unsigned long *s)
+{
+	unsigned long p[3];
+
+	set_parity(data, nblocks, p, 1);
+	if (p[0] | p[1] | p[2]) {
+		/*
+		 * Some of the checked columns do not have a zero syndrome.  For
+		 * simplicity, we compute the syndromes for all columns that we
+		 * have computed the remainders for.
+		 */
+		s[0] = gfmul_exp_long(gfadd_long(p[0], gfmul_exp_long(gfadd_long(p[1],
+			      gfmul_exp_long(p[2], -1)), -1)), -nblocks);
+		s[1] = gfadd_long(gfadd_long(p[2], p[1]), p[0]);
+		s[2] = gfmul_exp_long(gfadd_long(p[0], gfmul_exp_long(gfadd_long(p[1],
+				 gfmul_exp_long(p[2], 1)), 1)), nblocks);
+		return 0;
+	} else {
+		return 1;
+	}
+}
+
+
+/*
+ * Correct the block in the column pointed to by DATA.  There are NBAD
+ * CRC errors and their indices are in BAD_LOC[0], up to
+ * BAD_LOC[NBAD-1].  If NBAD>1, Ainv holds the inverse of the matrix
+ * of the linear system that needs to be solved to determine the error
+ * magnitudes.  S[0], S[1], and S[2] are the syndrome values.  If row
+ * j gets corrected, then bit j will be set in CORRECTION_MAP.
+ */
+static inline int correct_block(unsigned char *data, int nblocks,
+				int nbad, int *bad_loc, Matrix Ainv,
+				unsigned char *s,
+				BAD_SECTOR * correction_map)
+{
+	TRACE_FUN(8, "correct_block");
+	int ncorrected = 0;
+	int i;
+	unsigned char t1, t2;
+	unsigned char c0, c1, c2;	/* check bytes */
+	unsigned char error_mag[3], log_error_mag;
+	unsigned char *dp, l, e;
+
+	switch (nbad) {
+	case 0:
+		/* might have a CRC failure: */
+		if (s[0] == 0) {
+			/* more than one error */
+			TRACE(1, "ECC failed (0 CRC errors, >1 CRC failures)");
+			TRACE_EXIT;
+			return -1;
+		}		/* if */
+		t1 = gfdiv(s[1], s[0]);
+		if ((bad_loc[nbad++] = gflog[t1]) >= nblocks) {
+			TRACE(1, "ECC failed (0 CRC errors, >1 CRC failures): ");
+			TRACEi(1, "attempt to correct data at ", bad_loc[0]);
+			TRACE_EXIT;
+			return -1;
+		}
+		error_mag[0] = s[1];
+		break;
+	case 1:
+		t1 = gfadd(gfmul_exp(s[1], bad_loc[0]), s[2]);
+		t2 = gfadd(gfmul_exp(s[0], bad_loc[0]), s[1]);
+		if (t1 == 0 && t2 == 0) {
+			/* one erasure, no error: */
+			Ainv[0][0] = gfpow[bad_loc[0]];
+		} else if (t1 == 0 || t2 == 0) {
+			/* one erasure and more than one error: */
+			TRACE(1, "ECC failed (1 erasure, >1 error)");
+			TRACE_EXIT;
+			return -1;
+		} else {
+			/* one erasure, one error: */
+			if ((bad_loc[nbad++] = gflog[gfdiv(t1, t2)]) >= nblocks) {
+				TRACE(1, "ECC failed (1 CRC errors, >1 CRC failures): ");
+				TRACEi(1, "attempt to correct data at ", bad_loc[1]);
+				TRACE_EXIT;
+				return -1;
+			}	/* if */
+			if (!gfinv2(bad_loc[0], bad_loc[1], Ainv)) {
+				/* inversion failed---must have more than one error */
+				TRACE_EXIT;
+				return -1;
+			}
+		}
+		/*
+		 *  FALL THROUGH TO ERROR MAGNITUDE COMPUTATION:
+		 */
+	case 2:
+	case 3:
+		/* compute error magnitudes: */
+		gfmat_mul(nbad, Ainv, s, error_mag);
+		break;
+
+	default:
+		TRACE(1, "Internal Error: number of CRC errors > 3");
+		TRACE_EXIT;
+		return -1;
+	}
+
+	/*
+	 * Perform correction by adding ERROR_MAG[i] to the byte at offset
+	 * BAD_LOC[i].  Also add the value of the computed error polynomial
+	 * to the syndrome values.  If the correction was successful, the
+	 * resulting check bytes should be zero (i.e., the corrected data
+	 * is a valid code word).
+	 */
+	c0 = s[0];
+	c1 = s[1];
+	c2 = s[2];
+	for (i = 0; i < nbad; ++i) {
+		e = error_mag[i];
+		if (e) {
+			/* correct the byte at offset L by magnitude E: */
+			l = bad_loc[i];
+			dp = &data[l * SECTOR_SIZE];
+			*dp = gfadd(*dp, e);
+			*correction_map |= 1 << l;
+			++ncorrected;
+
+			log_error_mag = gflog[e];
+			c0 = gfadd(c0, gfpow[mod255(log_error_mag - l)]);
+			c1 = gfadd(c1, e);
+			c2 = gfadd(c2, gfpow[mod255(log_error_mag + l)]);
+		}
+	}
+	if (c0 || c1 || c2) {
+		TRACE(1, "ECC self-check failed, too many errors");
+		TRACE_EXIT;
+		return -1;
+	}
+	TRACE_EXIT;
+	return ncorrected;
+}
+
+
+#if defined(ECC_SANITY_CHECK) || defined(ECC_PARANOID)
+
+/*
+ * Perform a sanity check on the computed parity bytes:
+ */
+static int sanity_check(unsigned long *data, int nblocks)
+{
+	TRACE_FUN(8, "sanity_check");
+	unsigned long s[3];
+
+	if (!compute_syndromes(data, nblocks, s)) {
+		TRACE(-1, "Internal Error: syndrome self-check failed");
+		TRACE_EXIT;
+		return 0;
+	}
+	TRACE_EXIT;
+	return 1;
+}
+
+#endif				/* defined(ECC_SANITY_CHECK) || defined(ECC_PARANOID) */
+
+
+
+/*
+ * Compute the parity for an entire segment of data.
+ */
+int ecc_set_segment_parity(struct memory_segment *mseg)
+{
+	int i;
+	unsigned char *parity_bytes;
+
+	parity_bytes = &mseg->data[(mseg->blocks - 3) * SECTOR_SIZE];
+	for (i = 0; i < SECTOR_SIZE; i += sizeof(long)) {
+		set_parity((unsigned long *) &mseg->data[i], mseg->blocks - 3,
+			   (unsigned long *) &parity_bytes[i],
+			   SECTOR_SIZE / sizeof(long));
+#ifdef ECC_PARANOID
+		if (!sanity_check((unsigned long *) &mseg->data[i], mseg->blocks)) {
+			return -1;
+		}
+#endif				/* ECC_PARANOID */
+	}
+	return 0;
+}
+
+
+/*
+ * Checks and corrects (if possible) the segment MSEG.  Returns one of
+ * ECC_OK, ECC_CORRECTED, and ECC_FAILED.
+ */
+int ecc_correct_data(struct memory_segment *mseg)
+{
+	TRACE_FUN(5, "ecc_correct_data");
+	int col, i, result;
+	int ncorrected = 0;
+	int nerasures = 0;	/* # of erasures (CRC errors) */
+	int erasure_loc[3];	/* erasure locations */
+	unsigned long ss[3];
+	unsigned char s[3];
+	Matrix Ainv;
+
+	mseg->corrected = 0;
+
+	/* find first column that has non-zero syndromes: */
+	for (col = 0; col < SECTOR_SIZE; col += sizeof(long)) {
+		if (!compute_syndromes((unsigned long *) &mseg->data[col],
+				       mseg->blocks, ss)) {
+			/* something is wrong---have to fix things */
+			break;
+		}
+	}
+	if (col >= SECTOR_SIZE) {
+		/* all syndromes are ok, therefore nothing to correct */
+		TRACE_EXIT;
+		return ECC_OK;
+	}
+	/* count the number of CRC errors if there were any: */
+	if (mseg->read_bad) {
+		for (i = 0; i < mseg->blocks; i++) {
+			if (BAD_CHECK(mseg->read_bad, i)) {
+				if (nerasures >= 3) {
+					/* this is too much for ECC */
+					TRACE(1, "ECC failed (>3 CRC errors)");
+					TRACE_EXIT;
+					return ECC_FAILED;
+				}	/* if */
+				erasure_loc[nerasures++] = i;
+			}
+		}
+	}
+	/*
+	   * If there are at least 2 CRC errors, determine inverse of matrix
+	   * of linear system to be solved:
+	 */
+	switch (nerasures) {
+	case 2:
+		if (!gfinv2(erasure_loc[0], erasure_loc[1], Ainv)) {
+			TRACE_EXIT;
+			return ECC_FAILED;
+		}
+		break;
+	case 3:
+		if (!gfinv3(erasure_loc[0], erasure_loc[1], erasure_loc[2], Ainv)) {
+			TRACE_EXIT;
+			return ECC_FAILED;
+		}
+		break;
+	default:
+		/* this is not an error condition... */
+		break;
+	}
+
+	do {
+		for (i = 0; i < sizeof(long); ++i) {
+			s[0] = ss[0];
+			s[1] = ss[1];
+			s[2] = ss[2];
+			if (s[0] | s[1] | s[2]) {
+#ifdef BIG_ENDIAN
+				result = correct_block(&mseg->data[col + sizeof(long) - 1 - i],
+						       mseg->blocks,
+					 nerasures, erasure_loc, Ainv, s,
+						       &mseg->corrected);
+#else
+				result = correct_block(&mseg->data[col + i], mseg->blocks,
+					 nerasures, erasure_loc, Ainv, s,
+						       &mseg->corrected);
+#endif
+				if (result < 0) {
+					TRACE_EXIT;
+					return ECC_FAILED;
+				}
+				ncorrected += result;
+			}
+			ss[0] >>= 8;
+			ss[1] >>= 8;
+			ss[2] >>= 8;
+		}
+
+#ifdef ECC_SANITY_CHECK
+		if (!sanity_check((unsigned long *) &mseg->data[col], mseg->blocks)) {
+			TRACE_EXIT;
+			return ECC_FAILED;
+		}
+#endif				/* ECC_SANITY_CHECK */
+
+		/* find next column with non-zero syndromes: */
+		while ((col += sizeof(long)) < SECTOR_SIZE) {
+			if (!compute_syndromes((unsigned long *) &mseg->data[col],
+					       mseg->blocks, ss)) {
+				/* something is wrong---have to fix things */
+				break;
+			}
+		}
+	} while (col < SECTOR_SIZE);
+	if (ncorrected && nerasures == 0) {
+		TRACE(2, "block contained error not caught by CRC");
+	}
+	TRACEi((ncorrected > 0) ? 4 : 8, "number of corrections:", ncorrected);
+	TRACE_EXIT;
+	return ncorrected ? ECC_CORRECTED : ECC_OK;
+}
+
+/*** end of ecc.c ***/
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/ecc.h linux/drivers/char/ftape/ecc.h
--- v1.3.71/linux/drivers/char/ftape/ecc.h	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/ecc.h	Wed Mar  6 15:07:19 1996
@@ -0,0 +1,85 @@
+/*
+ *      Copyright (C) 1993 Ning and David Mosberger.
+ *      Original:
+ *      Copyright (C) 1993 Bas Laarhoven.
+ *      Copyright (C) 1992 David L. Brown, Jr.
+ *
+ * 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.
+ *
+ *
+ * $Source: /home/bas/distr/ftape-2.03b/RCS/ecc.h,v $
+ * $Author: bas $
+ *
+ * $Revision: 1.20 $
+ * $Date: 1995/01/08 14:16:21 $
+ * $State: Beta $
+ *
+ *      This file contains the definitions for the
+ *      Reed-Solomon error correction code 
+ *      for the QIC-40/80 tape streamer device driver.
+ */
+#ifndef _ecc_h_
+#define _ecc_h_
+
+typedef unsigned long BAD_SECTOR;
+#define BAD_CLEAR(entry) ((entry)=0)
+#define BAD_SET(entry,sector) ((entry)|=(1<<(sector)))
+#define BAD_CHECK(entry,sector) ((entry)&(1<<(sector)))
+
+/*
+ * Return values for ecc_correct_data:
+ */
+enum {
+	ECC_OK,			/* Data was correct. */
+	ECC_CORRECTED,		/* Correctable error in data. */
+	ECC_FAILED,		/* Could not correct data. */
+};
+
+/*
+ * Representation of an in memory segment.  MARKED_BAD lists the
+ * sectors that were marked bad during formatting.  If the N-th sector
+ * in a segment is marked bad, bit 1<<N will be set in MARKED_BAD.
+ * The sectors should be read in from the disk and packed, as if the
+ * bad sectors were not there, and the segment just contained fewer
+ * sectors.  READ_SECTORS is a bitmap of errors encountered while
+ * reading the data.  These offsets are relative to the packed data.
+ * BLOCKS is a count of the sectors not marked bad.  This is just to
+ * prevent having to count the zero bits in MARKED_BAD each time this
+ * is needed.  DATA is the actual sector packed data from (or to) the
+ * tape.
+ */
+struct memory_segment {
+	BAD_SECTOR marked_bad;
+	BAD_SECTOR read_bad;
+	int blocks;
+	unsigned char *data;
+	BAD_SECTOR corrected;
+};
+
+/*
+ * ecc.c defined global variables:
+ */
+#ifdef TEST
+extern int ftape_ecc_tracing;
+#endif
+
+/*
+ * ecc.c defined global functions:
+ */
+extern int ecc_correct_data(struct memory_segment *data);
+extern int ecc_set_segment_parity(struct memory_segment *data);
+
+#endif	/* _ecc_h_ */
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/fc-10.c linux/drivers/char/ftape/fc-10.c
--- v1.3.71/linux/drivers/char/ftape/fc-10.c	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/fc-10.c	Wed Mar  6 15:07:19 1996
@@ -0,0 +1,161 @@
+/* Yo, Emacs! we're -*- Linux-C -*-
+ *
+ 
+   Copyright (C) 1993,1994 Jon Tombs.
+
+   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.
+
+   The entire guts of this program was written by dosemu, modified to
+   record reads and writes to the ports in the 0x180-0x188 address space,
+   while running the CMS program TAPE.EXE V2.0.5 supplied with the drive.
+
+   Modified to use an array of addresses and generally cleaned up (made
+   much shorter) 4 June 94, dosemu isn't that good at writing short code it
+   would seem :-). Made independant of 0x180, but I doubt it will work
+   at any other address.
+
+   Modified for distribution with ftape source. 21 June 94, SJL.
+
+   Modifications on 20 October 95, by Daniel Cohen (catman@wpi.edu):
+   Modified to support different DMA, IRQ, and IO Ports.  Borland's
+   Turbo Debugger in virtual 8086 mode (TD386.EXE with hardware breakpoints
+   provided by the TDH386.SYS Device Driver) was used on the CMS program
+   TAPE V4.0.5.  I set breakpoints on I/O to ports 0x180-0x187.  Note that
+   CMS's program will not successfully configure the tape drive if you set
+   breakpoints on IO Reads, but you can set them on IO Writes without problems.
+   Known problems:
+   - You can not use DMA Channels 5 or 7.
+
+   Modification on 29 January 96, by Daniel Cohen (catman@wpi.edu):
+   Modified to only accept IRQs 3 - 7, or 9.  Since we can only send a 3 bit
+   number representing the IRQ to the card, special handling is required when
+   IRQ 9 is selected.  IRQ 2 and 9 are the same, and we should request IRQ 9
+   from the kernel while telling the card to use IRQ 2.  Thanks to Greg
+   Crider (gcrider@iclnet.org) for finding and locating this bug, as well as
+   testing the patch.
+
+ *
+ *      This file contains code for the CMS FC-10/FC-20 card.
+ */
+
+#include <linux/module.h>
+#include <linux/ftape.h>
+#include <asm/io.h>
+
+#include "tracing.h"
+#include "fdc-io.h"
+#include "fc-10.h"
+
+#ifdef PROBE_FC10
+
+/*  This code will only work if the FC-10 (or FC-20) is set to
+ *  use DMA channels 1, 2, or 3.  DMA channels 5 and 7 seem to be 
+ *  initialized by the same command as channels 1 and 3, respectively.
+ */
+#if (FDC_DMA > 3)
+#error : The FC-10/20 must be set to use DMA channels 1, 2, or 3!
+#endif
+
+/*  Only allow the FC-10/20 to use IRQ 3-7, or 9.  Note that CMS's program
+ *  only accepts IRQ's 2-7, but in linux, IRQ 2 is the same as IRQ 9.
+ */
+#if (FDC_IRQ < 3 || FDC_IRQ == 8 || FDC_IRQ > 9)
+#error : The FC-10/20 must be set to use IRQ levels 3 - 7, or 9!
+#error :              Note IRQ 9 is the same as IRQ 2
+#endif
+
+unsigned short inbs_magic[] = {
+	0x3, 0x3, 0x0, 0x4, 0x7, 0x2, 0x5, 0x3, 0x1, 0x4,
+	0x3, 0x5, 0x2, 0x0, 0x3, 0x7, 0x4, 0x2,
+	0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7
+};
+
+unsigned short fc10_ports[] = {
+	0x180, 0x210, 0x2A0, 0x300, 0x330, 0x340, 0x370
+};
+
+int fc10_enable(void)
+{
+	int i;
+	byte cardConfig = 0x00;
+	byte x;
+
+	/*  Clear state machine ???
+	 */
+	for (i = 0; i < NR_ITEMS(inbs_magic); i++) {
+		inb(FDC_BASE + inbs_magic[i]);
+	}
+	outb(0x0, FDC_BASE);
+
+	x = inb(FDC_BASE);
+	if (x == 0x13 || x == 0x93) {
+		for (i = 1; i < 8; i++) {
+			if (inb(FDC_BASE + i) != x) {
+				return 0;
+			}
+		}
+	} else {
+		return 0;
+	}
+
+	outb(0x8, FDC_BASE);
+
+	for (i = 0; i < 8; i++) {
+		if (inb(FDC_BASE + i) != 0x0) {
+			return 0;
+		}
+	}
+	outb(0x10, FDC_BASE);
+
+	for (i = 0; i < 8; i++) {
+		if (inb(FDC_BASE + i) != 0xff) {
+			return 0;
+		}
+	}
+
+	/*  Okay, we found a FC-10 card ! ???
+	 */
+	outb(0x0, fdc.ccr);
+
+	/*  Clear state machine again ???
+	 */
+	for (i = 0; i < NR_ITEMS(inbs_magic); i++) {
+		inb(FDC_BASE + inbs_magic[i]);
+	}
+	/* Send io port */
+	for (i = 0; i < NR_ITEMS(fc10_ports); i++)
+		if (FDC_BASE == fc10_ports[i])
+			cardConfig = i + 1;
+	if (cardConfig == 0)
+		return 0;	/* Invalid I/O Port */
+	/* and IRQ - If using IRQ 9, tell the FC card it is actually IRQ 2 */
+	if (FDC_IRQ != 9)
+		cardConfig |= FDC_IRQ << 3;
+	else
+		cardConfig |= 2 << 3;
+
+	/* and finally DMA Channel */
+	cardConfig |= FDC_DMA << 6;
+	outb(cardConfig, FDC_BASE);	/* DMA [2 bits]/IRQ [3 bits]/BASE [3 bits] */
+
+	/*  Enable FC-10 ???
+	 */
+	outb(0, fdc.ccr);
+	outb(0, FDC_BASE + 0x6);
+	outb(8, fdc.dor);
+	outb(8, fdc.dor);
+	outb(1, FDC_BASE + 0x6);
+
+	/*  Initialize fdc, select drive B:
+	 */
+	outb(0x08, fdc.dor);	/* assert reset, dma & irq enabled */
+	outb(0x0c, fdc.dor);	/* release reset */
+	outb(0x2d, fdc.dor);	/* select drive 1 */
+
+	return (x == 0x93) ? 2 : 1;
+}
+
+#endif /* CMS_FC10_CONTROLLER */
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/fc-10.h linux/drivers/char/ftape/fc-10.h
--- v1.3.71/linux/drivers/char/ftape/fc-10.h	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/fc-10.h	Wed Mar  6 15:07:19 1996
@@ -0,0 +1,42 @@
+#ifndef _FC_10_H
+#define _FC_10_H
+
+/*
+ * Copyright (C) 1994 Bas Laarhoven.
+
+ 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.
+
+ *
+ $Source: /home/bas/distr/ftape-2.03b/RCS/fc-10.h,v $
+ $Author: bas $
+ *
+ $Revision: 1.3 $
+ $Date: 1995/01/08 14:16:21 $
+ $State: Beta $
+ *
+ *      This file contains definitions for the FC-10 code
+ *      of the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+/*
+ *      fc-10.c defined global vars.
+ */
+
+/*
+ *      fc-10.c defined global functions.
+ */
+extern int fc10_enable(void);
+
+#endif
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/fdc-io.c linux/drivers/char/ftape/fdc-io.c
--- v1.3.71/linux/drivers/char/ftape/fdc-io.c	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/fdc-io.c	Wed Mar  6 15:07:19 1996
@@ -0,0 +1,1300 @@
+/* Yo, Emacs! we're -*- Linux-C -*-
+ *
+ *      Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ *      This file contains the low-level floppy disk interface code
+ *      for the QIC-40/80 tape streamer device driver.
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/ioport.h>
+#include <linux/ftape.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/irq.h>
+
+#include "tracing.h"
+#include "fdc-io.h"
+#include "fdc-isr.h"
+#include "ftape-io.h"
+#include "ftape-rw.h"
+#include "calibr.h"
+#include "fc-10.h"
+#include "qic117.h"
+
+
+/*      Global vars.
+ */
+int ftape_unit = -1;
+int ftape_motor = 0;
+int current_cylinder = -1;
+fdc_mode_enum fdc_mode = fdc_idle;
+fdc_config_info fdc = {0};
+
+/*      Local vars.
+ */
+static int fdc_calibr_count;
+static int fdc_calibr_time;
+static int fdc_confused = 0;
+static int fdc_status;
+volatile byte fdc_head;		/* FDC head */
+volatile byte fdc_cyl;		/* FDC track */
+volatile byte fdc_sect;		/* FDC sector */
+static int fdc_data_rate = 0;	/* default rate = 500 Kbps */
+static int fdc_seek_rate = 14;	/* default rate = 2 msec @ 500 Kbps */
+static void (*do_ftape) (void);
+static int fdc_fifo_state;	/* original fifo setting - fifo enabled */
+static int fdc_fifo_thr;	/* original fifo setting - thresshold */
+static int fdc_lock_state;	/* original lock setting - locked */
+static int fdc_fifo_locked = 0;	/* has fifo && lock set ? */
+static byte fdc_precomp = 0;	/* sets fdc to default precomp. value */
+static byte fdc_drv_spec[4];	/* drive specification bytes for i82078 */
+static int perpend_mode;	/* true if fdc is in perpendicular mode */
+
+void fdc_catch_stray_interrupts(unsigned count)
+{
+	unsigned long flags;
+
+	save_flags(flags);
+	cli();
+	if (count == 0) {
+		expected_stray_interrupts = 0;
+	} else {
+		expected_stray_interrupts += count;
+	}
+	restore_flags(flags);
+}
+
+/*  Wait during a timeout period for a given FDC status.
+ *  If usecs == 0 then just test status, else wait at least for usecs.
+ *  Returns -ETIME on timeout. Function must be calibrated first !
+ */
+int fdc_wait(int usecs, byte mask, byte state)
+{
+	int count_1 = (fdc_calibr_count * usecs - 1) / fdc_calibr_time;
+
+	do {
+		fdc_status = inb_p(fdc.msr);
+		if ((fdc_status & mask) == state) {
+			return 0;
+		}
+	} while (count_1-- >= 0);
+	return -ETIME;
+}
+
+int fdc_ready_wait(int usecs)
+{
+	return fdc_wait(usecs, FDC_DATA_READY, FDC_DATA_READY);
+}
+
+static void fdc_usec_wait(int usecs)
+{
+	fdc_wait(usecs, 0, 1);	/* will always timeout ! */
+}
+
+int fdc_ready_out_wait(int usecs)
+{
+	fdc_usec_wait(RQM_DELAY);	/* wait for valid RQM status */
+	return fdc_wait(usecs, FDC_DATA_OUT_READY, FDC_DATA_OUT_READY);
+}
+
+int fdc_ready_in_wait(int usecs)
+{
+	fdc_usec_wait(RQM_DELAY);	/* wait for valid RQM status */
+	return fdc_wait(usecs, FDC_DATA_OUT_READY, FDC_DATA_IN_READY);
+}
+
+int fdc_wait_calibrate(void)
+{
+	return calibrate("fdc_wait",
+		     fdc_usec_wait, &fdc_calibr_count, &fdc_calibr_time);
+}
+
+/*  Wait for a (short) while for the FDC to become ready
+ *  and transfer the next command byte.
+ *  Return -ETIME on timeout on getting ready (depends on hardware!).
+ */
+int fdc_write(byte data)
+{
+	fdc_usec_wait(RQM_DELAY);	/* wait for valid RQM status */
+	if (fdc_wait(150, FDC_DATA_READY_MASK, FDC_DATA_IN_READY) < 0) {
+		return -ETIME;
+	} else {
+		outb(data, fdc.fifo);
+		return 0;
+	}
+}
+
+/*  Wait for a (short) while for the FDC to become ready
+ *  and transfer the next result byte.
+ *  Return -ETIME if timeout on getting ready (depends on hardware!).
+ */
+int fdc_read(byte * data)
+{
+	fdc_usec_wait(RQM_DELAY);	/* wait for valid RQM status */
+	if (fdc_wait(150, FDC_DATA_READY_MASK, FDC_DATA_OUT_READY) < 0) {
+		return -ETIME;
+	} else {
+		*data = inb(fdc.fifo);
+		return 0;
+	}
+}
+
+/*  Output a cmd_len long command string to the FDC.
+ *  The FDC should be ready to receive a new command or
+ *  an error (EBUSY) will occur.
+ */
+int fdc_command(byte * cmd_data, int cmd_len)
+{
+	TRACE_FUN(8, "fdc_command");
+	int result = 0;
+	unsigned long flags;
+	int count = cmd_len;
+
+	fdc_usec_wait(RQM_DELAY);	/* wait for valid RQM status */
+	save_flags(flags);
+	cli();
+	fdc_status = inb(fdc.msr);
+	if ((fdc_status & FDC_DATA_READY_MASK) == FDC_DATA_IN_READY) {
+		int retry = 0;
+		fdc_mode = *cmd_data;	/* used by isr */
+		interrupt_seen = 0;
+		while (count) {
+			result = fdc_write(*cmd_data);
+			if (result < 0) {
+				TRACEx3(6, "fdc_mode = %02x, status = %02x at index %d",
+					(int) fdc_mode, (int) fdc_status, cmd_len - count);
+				if (++retry <= 3) {
+					TRACE(2, "fdc_write timeout, retry");
+				} else {
+					TRACE(1, "fdc_write timeout, fatal");
+					fdc_confused = 1;
+					/* recover ??? */
+					break;
+				}
+			} else {
+				--count;
+				++cmd_data;
+			}
+		}
+	} else {
+		TRACE(1, "fdc not ready");
+		result = -EBUSY;
+	}
+	restore_flags(flags);
+	TRACE_EXIT;
+	return result;
+}
+
+/*  Input a res_len long result string from the FDC.
+ *  The FDC should be ready to send the result or an error
+ *  (EBUSY) will occur.
+ */
+int fdc_result(byte * res_data, int res_len)
+{
+	TRACE_FUN(8, "fdc_result");
+	int result = 0;
+	unsigned long flags;
+	int count = res_len;
+
+	save_flags(flags);
+	cli();
+	fdc_status = inb(fdc.msr);
+	if ((fdc_status & FDC_DATA_READY_MASK) == FDC_DATA_OUT_READY) {
+		int retry = 0;
+		while (count) {
+			if (!(fdc_status & FDC_BUSY)) {
+				TRACE(1, "premature end of result phase");
+			}
+			result = fdc_read(res_data);
+			if (result < 0) {
+				TRACEx3(6, "fdc_mode = %02x, status = %02x at index %d",
+					(int) fdc_mode, (int) fdc_status, res_len - count);
+				if (++retry <= 3) {
+					TRACE(2, "fdc_read timeout, retry");
+				} else {
+					TRACE(1, "fdc_read timeout, fatal");
+					fdc_confused = 1;
+					/* recover ??? */
+					break;
+				}
+			} else {
+				--count;
+				++res_data;
+			}
+		}
+	} else {
+		TRACE(1, "fdc not ready");
+		result = -EBUSY;
+	}
+	restore_flags(flags);
+	fdc_usec_wait(RQM_DELAY);	/* allow FDC to negate BSY */
+	TRACE_EXIT;
+	return result;
+}
+
+/*      Handle command and result phases for
+ *      commands without data phase.
+ */
+int fdc_issue_command(byte * out_data, int out_count,
+		      byte * in_data, int in_count)
+{
+	TRACE_FUN(8, "fdc_issue_command");
+	int result;
+	int t0, t1;
+
+	if (out_count > 0) {
+		result = fdc_command(out_data, out_count);
+		if (result < 0) {
+			TRACE(1, "fdc_command failed");
+			TRACE_EXIT;
+			return result;
+		}
+	}
+	/* will take 24 - 30 usec for fdc_sense_drive_status and
+	 * fdc_sense_interrupt_status commands.
+	 *    35 fails sometimes (5/9/93 SJL)
+	 * On a loaded system it incidentally takes longer than
+	 * this for the fdc to get ready ! ?????? WHY ??????
+	 * So until we know what's going on use a very long timeout.
+	 */
+	t0 = timestamp();
+	result = fdc_ready_out_wait(500 /* usec */ );
+	t1 = timestamp();
+	if (result < 0) {
+		TRACEi(1, "fdc_ready_out_wait failed after:", timediff(t0, t1));
+		TRACE_EXIT;
+		return result;
+	}
+	if (in_count > 0) {
+		result = fdc_result(in_data, in_count);
+		if (result < 0) {
+			TRACE(1, "result phase aborted");
+			TRACE_EXIT;
+			return result;
+		}
+	}
+	TRACE_EXIT;
+	return 0;
+}
+
+/*      Wait for FDC interrupt with timeout.
+ *      Signals are blocked so the wait will not be aborted.
+ *      Note: interrupts must be enabled ! (23/05/93 SJL)
+ */
+int fdc_interrupt_wait(int time)
+{
+	TRACE_FUN(8, "fdc_interrupt_wait");
+	struct wait_queue wait =
+	{current, NULL};
+	int result = -ETIME;
+	int need_cleanup = 0;
+	int current_blocked = current->blocked;
+	static int resetting = 0;
+
+	if (wait_intr) {
+		TRACE(1, "error: nested call");
+		return -EIO;	/* return error... */
+	}
+	if (interrupt_seen == 0) {
+		/* timeout time will be between 0 and MSPT milliseconds too long !
+		 */
+		current->timeout = jiffies + 1 + (time + MSPT - 1) / MSPT;
+		current->state = TASK_INTERRUPTIBLE;
+		current->blocked = _BLOCK_ALL;
+		add_wait_queue(&wait_intr, &wait);
+		do {
+			schedule();	/* sets TASK_RUNNING on timeout */
+		} while (!interrupt_seen && current->state != TASK_RUNNING);
+		current->blocked = current_blocked;	/* restore */
+		remove_wait_queue(&wait_intr, &wait);
+		if (interrupt_seen) {
+			current->timeout = 0;	/* interrupt hasn't cleared this */
+			result = 0;
+		} else {
+#if 1
+/*** remove me when sure this doesn't happen ***/
+			if (current->timeout > 0) {
+				TRACE(-1, "*** BUG: unexpected schedule exit ***");
+				if (current->signal & ~current->blocked) {
+					TRACE(4, "caused by signal ?");
+				}
+			}
+#endif
+			if (current->signal & ~current->blocked) {
+				result = -EINTR;
+			} else {
+				result = -ETIME;
+			}
+			need_cleanup = 1;	/* missing interrupt, reset fdc. */
+		}
+	} else {
+		result = 0;
+	}
+	/*  In first instance, next statement seems unnecessary since
+	 *  it will be cleared in fdc_command. However, a small part of
+	 *  the software seems to rely on this being cleared here
+	 *  (ftape_close might fail) so stick to it until things get fixed !
+	 */
+	interrupt_seen = 0;	/* clear for next call */
+
+	if (need_cleanup & !resetting) {
+		resetting = 1;	/* break infinite recursion if reset fails */
+		TRACE(8, "cleanup reset");
+		fdc_reset();
+		resetting = 0;
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+/*      Start/stop drive motor. Enable DMA mode.
+ */
+void fdc_motor(int motor)
+{
+	TRACE_FUN(8, "fdc_motor");
+	int unit = FTAPE_UNIT;
+	int data = unit | FDC_RESET_NOT | FDC_DMA_MODE;
+
+	ftape_motor = motor;
+	if (ftape_motor) {
+		data |= FDC_MOTOR_0 << unit;
+		TRACEx1(4, "turning motor %d on", unit);
+	} else {
+		TRACEx1(4, "turning motor %d off", unit);
+	}
+#ifdef MACH2
+	outb_p(data, fdc.dor2);
+#else
+	outb_p(data, fdc.dor);
+#endif
+	ftape_sleep(10 * MILLISECOND);
+	TRACE_EXIT;
+}
+
+static void fdc_update_dsr(void)
+{
+	TRACE_FUN(8, "fdc_update_dsr");
+
+	TRACEx2(5, "rate = %d, precomp = %d", fdc_data_rate, fdc_precomp);
+	if (fdc.type >= i82077) {
+		outb_p((fdc_data_rate & 0x03) | fdc_precomp, fdc.dsr);
+	} else {
+		outb_p(fdc_data_rate, fdc.ccr);
+	}
+	TRACE_EXIT;
+}
+
+void fdc_set_write_precomp(int precomp)
+{
+	/*  write precompensation can be set in multiples of 41.67 nsec.
+	 *  round the parameter to the nearest multiple and convert it
+	 *  into a fdc setting. Note that 0 means default to the fdc,
+	 *  7 is used instead of that.
+	 */
+	fdc_precomp = ((precomp + 21) / 42) << 2;
+	if (fdc_precomp == 0) {
+		fdc_precomp = 7 << 2;
+	}
+	fdc_update_dsr();
+}
+
+/* Read back the Drive Specification regs on a i82078, so that we
+ * are able to restore them later
+ */
+void fdc_save_drive_specs(void)
+{
+	byte cmd1[] =
+	{FDC_DRIVE_SPEC, 0x80};
+	byte cmd2[] =
+	{FDC_DRIVE_SPEC, 0x00, 0x00, 0x00, 0x00, 0xc0};
+	int result;
+
+	TRACE_FUN(8, "fdc_save_drive_specs");
+	if (fdc.type >= i82078_1) {
+		result = fdc_issue_command(cmd1, NR_ITEMS(cmd1), fdc_drv_spec, 4);
+		if (result >= 0) {
+			cmd2[1] = (fdc_drv_spec[0] & 0x03) | 0x04;
+			cmd2[2] = (fdc_drv_spec[1] & 0x03) | 0x24;
+			cmd2[3] = (fdc_drv_spec[2] & 0x03) | 0x44;
+			cmd2[4] = (fdc_drv_spec[3] & 0x03) | 0x64;
+			fdc_command(cmd2, NR_ITEMS(cmd2));
+			if (result < 0) {
+				TRACE(1, "Setting of drive specs failed");
+				return;
+			}
+		} else {
+			TRACE(2, "Save of drive specs failed");
+		}
+	}
+	TRACE_EXIT;
+}
+
+/* Restore the previously saved Drive Specification values */
+void fdc_restore_drive_specs(void)
+{
+	byte cmd[] =
+	{FDC_DRIVE_SPEC, 0x00, 0x00, 0x00, 0x00, 0xc0};
+	int result;
+
+	TRACE_FUN(8, "fdc_restore_drive_specs");
+	if (fdc.type > i82078_1) {
+		cmd[1] = (fdc_drv_spec[0] & 0x1f) | 0x00;
+		cmd[2] = (fdc_drv_spec[1] & 0x1f) | 0x20;
+		cmd[3] = (fdc_drv_spec[2] & 0x1f) | 0x40;
+		cmd[4] = (fdc_drv_spec[3] & 0x1f) | 0x60;
+		result = fdc_command(cmd, NR_ITEMS(cmd));
+		if (result < 0) {
+			TRACE(2, "Restoration of drive specs failed");
+		}
+	}
+	TRACE_EXIT;
+}
+
+/* Select clock for fdc, must correspond with tape drive setting !
+ * This also influences the fdc timing so we must adjust some values.
+ */
+void fdc_set_data_rate(int rate)
+{
+	/* Select clock for fdc, must correspond with tape drive setting !
+	 * This also influences the fdc timing so we must adjust some values.
+	 */
+	fdc_data_rate = rate;
+	fdc_update_dsr();
+	fdc_set_seek_rate(fdc_seek_rate);	/* re-adjust for changed clock */
+}
+
+/*      Reset the floppy disk controller. Leave the ftape_unit selected.
+ */
+void fdc_reset(void)
+{
+	TRACE_FUN(8, "fdc_reset");
+	int unit = FTAPE_UNIT;
+	byte fdc_ctl = unit | FDC_DMA_MODE;
+	int st0;
+	int i;
+	int result;
+	int dummy;
+
+	if (ftape_motor) {
+		fdc_ctl |= FDC_MOTOR_0 << unit;
+	}
+#ifdef MACH2
+	outb_p(fdc_ctl & 0x0f, fdc.dor);
+	outb_p(fdc_ctl, fdc.dor2);
+#else
+	outb_p(fdc_ctl, fdc.dor);	/* assert reset, keep unit selected */
+#endif
+	fdc_usec_wait(10 /* usec */ );	/* delay >= 14 fdc clocks */
+	fdc_ctl |= FDC_RESET_NOT;
+	fdc_mode = fdc_idle;
+#ifdef MACH2
+	outb_p(fdc_ctl & 0x0f, fdc.dor);
+	outb_p(fdc_ctl, fdc.dor2);
+#else
+	outb_p(fdc_ctl, fdc.dor);	/* release reset */
+#endif
+	result = fdc_interrupt_wait(1 * SECOND);
+	if (result < 0) {
+		TRACE(1, "missing interrupt after reset");
+	}
+	fdc_set_data_rate(fdc_data_rate);	/* keep original setting */
+	fdc_usec_wait(1000 /* usec */ );	/* don't know why, but needed */
+	for (i = 0; i < 4; ++i) {	/* clear disk-change status */
+		fdc_sense_interrupt_status(&st0, &dummy);
+		if (i == unit) {
+			current_cylinder = dummy;
+		}
+	}
+	fdc_set_seek_rate(2);
+	TRACE_EXIT;
+}
+
+/* When we're done, put the fdc into reset mode so that the regular
+   floppy disk driver will figure out that something is wrong and
+   initialize the controller the way it wants. */
+void fdc_disable(void)
+{
+	TRACE_FUN(8, "fdc_disable");
+	int result;
+	byte cmd1[] = {FDC_CONFIGURE, 0x00, 0x00, 0x00};
+	byte cmd2[] = {FDC_LOCK};
+	byte cmd3[] = {FDC_UNLOCK};
+	byte stat[1];
+
+	if (CLK_48MHZ && fdc.type >= i82078)
+		cmd1[0] |= FDC_CLK48_BIT;
+	if (fdc_fifo_locked) {
+		result = fdc_issue_command(cmd3, 1, stat, 1);
+		if (result < 0 || stat[0] != 0x00) {
+			TRACE(-1, "couldn't unlock fifo, configuration remains changed");
+		} else {
+			cmd1[2] = ((fdc_fifo_state) ? 0 : 0x20) + (fdc_fifo_thr - 1);
+			result = fdc_command(cmd1, NR_ITEMS(cmd1));
+			if (result < 0) {
+				TRACE(-1, "couldn't reconfigure fifo to old state");
+			} else if (fdc_lock_state) {
+				result = fdc_issue_command(cmd2, 1, stat, 1);
+				if (result < 0) {
+					TRACE(-1, "couldn't lock old state again");
+				}
+			}
+			TRACEx3(5, "fifo restored: %sabled, thr. %d, %slocked",
+				fdc_fifo_state ? "en" : "dis",
+			   fdc_fifo_thr, (fdc_lock_state) ? "" : "not ");
+		}
+		fdc_fifo_locked = 0;
+	}
+#ifdef MACH2
+	outb_p(FTAPE_UNIT & 0x0f, fdc.dor);
+	outb_p(FTAPE_UNIT, fdc.dor2);
+	udelay(10);
+	outb_p(FDC_RESET_NOT & 0x0f, fdc.dor);
+	outb_p(FDC_RESET_NOT, fdc.dor2);
+#else
+	outb_p(FTAPE_UNIT, fdc.dor);
+	udelay(10);
+	outb_p(FDC_RESET_NOT, fdc.dor);
+#endif
+	TRACE_EXIT;
+}
+
+/*      Specify FDC seek-rate
+ */
+int fdc_set_seek_rate(int seek_rate)
+{
+	byte in[3];
+	const int hut = 1;	/* minimize head unload time */
+	const int hlt = 1;	/* minimize head load time */
+	const int rates[] = {250, 2000, 500, 1000};
+
+	in[0] = FDC_SPECIFY;
+	in[1] = (((16 - (rates[fdc_data_rate & 0x03] * seek_rate) / 500) << 4) |
+		 hut);
+	in[2] = (hlt << 1) | 0;
+	fdc_seek_rate = seek_rate;
+
+	return fdc_command(in, 3);
+}
+
+/*      Sense drive status: get unit's drive status (ST3)
+ */
+int fdc_sense_drive_status(int *st3)
+{
+	TRACE_FUN(8, "fdc_sense_drive_status");
+	int result;
+	byte out[2];
+	byte in[1];
+
+	out[0] = FDC_SENSED;
+	out[1] = FTAPE_UNIT;
+	result = fdc_issue_command(out, 2, in, 1);
+	if (result < 0) {
+		TRACE(1, "issue_command failed");
+	} else {
+		*st3 = in[0];
+		result = 0;
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+/*      Sense Interrupt Status command:
+ *      should be issued at the end of each seek.
+ *      get ST0 and current cylinder.
+ */
+int fdc_sense_interrupt_status(int *st0, int *current_cylinder)
+{
+	TRACE_FUN(8, "fdc_sense_interrupt_status");
+	int result;
+	byte out[1];
+	byte in[2];
+
+	out[0] = FDC_SENSEI;
+	result = fdc_issue_command(out, 1, in, 2);
+	if (result) {
+		TRACE(1, "issue_command failed");
+	} else {
+		*st0 = in[0];
+		*current_cylinder = in[1];
+		result = 0;
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+/*      step to track
+ */
+int fdc_seek(int track)
+{
+	TRACE_FUN(8, "fdc_seek");
+	int result;
+	byte out[3];
+	int st0, pcn;
+
+	out[0] = FDC_SEEK;
+	out[1] = FTAPE_UNIT;
+	out[2] = track;
+	seek_completed = 0;
+	result = fdc_command(out, 3);
+	if (result != 0) {
+		TRACEi(1, "failed, status =", result);
+		TRACEx1(4, "destination was: %d, resetting FDC...", track);
+		/*  We really need this command to work !
+		 */
+		fdc_reset();
+		TRACE_EXIT;
+		return result;
+	}
+	/*    Handle interrupts until seek_completed or timeout.
+	 */
+	for (;;) {
+		result = fdc_interrupt_wait(2 * SECOND);
+		if (result < 0) {
+			TRACEi(2, "fdc_interrupt_wait timeout, status =", result);
+			TRACE_EXIT;
+			return result;
+		} else if (seek_completed) {
+			result = fdc_sense_interrupt_status(&st0, &pcn);
+			if (result != 0) {
+				TRACEi(1, "fdc_sense_interrupt_status failed, status =", result);
+				TRACE_EXIT;
+				return result;
+			}
+			if ((st0 & ST0_SEEK_END) == 0) {
+				TRACE(1, "no seek-end after seek completion !??");
+				TRACE_EXIT;
+				return -EIO;
+			}
+			break;
+		}
+	}
+	/*    Verify whether we issued the right tape command.
+	 */
+	/* Verify that we seek to the proper track. */
+	if (pcn != track) {
+		TRACE(1, "bad seek..");
+		TRACE_EXIT;
+		return -EIO;
+	}
+	current_cylinder = pcn;
+	TRACE_EXIT;
+	return 0;
+}
+
+/*      Recalibrate and wait until home.
+ */
+int fdc_recalibrate(void)
+{
+	TRACE_FUN(8, "fdc_recalibrate");
+	int result;
+	byte out[2];
+	int st0;
+	int pcn;
+	int retry;
+
+	result = fdc_set_seek_rate(6);
+	if (result) {
+		TRACEi(1, "fdc_set_seek_rate failed, status =", result);
+		TRACE_EXIT;
+		return result;
+	}
+	out[0] = FDC_RECAL;
+	out[1] = FTAPE_UNIT;
+	seek_completed = 0;
+	result = fdc_command(out, 2);
+	if (result) {
+		TRACEi(1, "fdc_command failed, status =", result);
+		TRACE_EXIT;
+		return result;
+	}
+	/*    Handle interrupts until seek_completed or timeout.
+	 */
+	for (retry = 0;; ++retry) {
+		result = fdc_interrupt_wait(2 * SECOND);
+		if (result < 0) {
+			TRACE(1, "fdc_interrupt_wait failed");
+			TRACE_EXIT;
+			return result;
+		} else if (result == 0 && seek_completed) {
+			result = fdc_sense_interrupt_status(&st0, &pcn);
+			if (result != 0) {
+				TRACEi(1, "fdc_sense_interrupt_status failed, status =", result);
+				TRACE_EXIT;
+				return result;
+			}
+			if ((st0 & ST0_SEEK_END) == 0) {
+				if (retry < 1) {
+					continue;	/* some drives/fdc's give an extra interrupt */
+				} else {
+					TRACE(1, "no seek-end after seek completion !??");
+					TRACE_EXIT;
+					return -EIO;
+				}
+			}
+			break;
+		}
+	}
+	current_cylinder = pcn;
+	if (pcn != 0) {
+		TRACEi(1, "failed: resulting track =", pcn);
+	}
+	result = fdc_set_seek_rate(2);
+	if (result != 0) {
+		TRACEi(1, "fdc_set_seek_rate failed, status =", result);
+		TRACE_EXIT;
+		return result;
+	}
+	TRACE_EXIT;
+	return 0;
+}
+
+/*      Setup Floppy Disk Controller and DMA to read or write the next cluster
+ *      of good sectors from or to the current segment.
+ */
+int setup_fdc_and_dma(buffer_struct * buff, unsigned char operation)
+{
+	TRACE_FUN(8, "setup_fdc_and_dma");
+	unsigned long flags;
+	byte perpend[] = {FDC_PERPEND, 0x00};
+	unsigned char out[9];
+	int result;
+	int dma_mode;
+
+	if (operation == FDC_READ || operation == FDC_READ_DELETED) {
+		dma_mode = DMA_MODE_READ;
+		if (qic_std == QIC_TAPE_QIC3020) {
+			if (fdc.type < i82077AA) {
+				/* fdc does not support perpendicular mode. complain */
+				TRACE(0, "Your FDC does not support QIC-3020.");
+				return -EIO;
+			}
+			/* enable perpendicular mode */
+			perpend[1] = 0x83 + (0x04 << FTAPE_UNIT);
+			result = fdc_command(perpend, 2);
+			if (result < 0) {
+				TRACE(1, "Perpendicular mode entry failed!");
+			} else {
+				TRACE(4, "Perpendicular mode entered");
+				perpend_mode = 1;
+			}
+		} else if (perpend_mode) {
+			/* Turn off perpendicular mode */
+			perpend[1] = 0x80;
+			result = fdc_command(perpend, 2);
+			if (result < 0) {
+				TRACE(1, "Perpendicular mode exit failed!");
+			} else {
+				TRACE(4, "Perpendicular mode exited");
+				perpend_mode = 0;
+			}
+		}
+		TRACEx2(5, "xfer %d sectors to 0x%p", buff->sector_count, buff->ptr);
+	} else if (operation == FDC_WRITE || operation == FDC_WRITE_DELETED) {
+		dma_mode = DMA_MODE_WRITE;
+		/* When writing QIC-3020 tapes, turn on perpendicular mode.
+		 */
+		if (qic_std == QIC_TAPE_QIC3020) {
+			if (fdc.type < i82077AA) {
+				/* fdc does not support perpendicular mode: complain */
+				TRACE(0, "Your FDC does not support QIC-3020.");
+				return -EIO;
+			}
+			perpend[1] = 0x83 + (0x4 << FTAPE_UNIT);
+			result = fdc_command(perpend, 2);
+			if (result < 0) {
+				TRACE(1, "Perpendicular mode entry failed!");
+			} else {
+				TRACE(4, "Perpendicular mode entered");
+				perpend_mode = 1;
+			}
+		} else if (perpend_mode) {
+			perpend[1] = 0x80;
+			result = fdc_command(perpend, 2);
+			if (result < 0) {
+				TRACE(1, "Perpendicular mode exit failed!");
+			} else {
+				TRACE(4, "Perpendicular mode exited");
+				perpend_mode = 0;
+			}
+		}
+		TRACEx2(5, "xfer %d sectors from 0x%p", buff->sector_count, buff->ptr);
+	} else {
+		TRACE(-1, "bug: illegal operation parameter");
+		TRACE_EXIT;
+		return -EIO;
+	}
+	/* Program the DMA controller.
+	 */
+	save_flags(flags);
+	cli();			/* could be called from ISR ! */
+	disable_dma(fdc.dma);
+	clear_dma_ff(fdc.dma);
+	set_dma_mode(fdc.dma, dma_mode);
+	set_dma_addr(fdc.dma, (unsigned) buff->ptr);
+	set_dma_count(fdc.dma, SECTOR_SIZE * buff->sector_count);
+#ifdef GCC_2_4_5_BUG
+	/*  This seemingly stupid construction confuses the gcc-2.4.5
+	 *  code generater enough to create correct code.
+	 */
+	if (1) {
+		int i;
+
+		for (i = 0; i < 1; ++i) {
+			udelay(1);
+		}
+	}
+#endif
+	enable_dma(fdc.dma);
+	/* Issue FDC command to start reading/writing.
+	 */
+	out[0] = operation;
+	out[1] = FTAPE_UNIT;
+	out[2] = buff->cyl;
+	out[3] = buff->head;
+	out[4] = buff->sect + buff->sector_offset;
+	out[5] = 3;		/* Sector size of 1K. */
+	out[6] = out[4] + buff->sector_count - 1;	/* last sector */
+	out[7] = 109;		/* Gap length. */
+	out[8] = 0xff;		/* No limit to transfer size. */
+	restore_flags(flags);
+	TRACEx4(6, "C: 0x%02x, H: 0x%02x, R: 0x%02x, cnt: 0x%02x",
+		out[2], out[3], out[4], out[6] - out[4] + 1);
+	result = fdc_command(out, 9);
+	if (result != 0) {
+		fdc_mode = fdc_idle;
+		TRACE(1, "fdc_command failed");
+	}
+	fdc_setup_error = result;
+	TRACE_EXIT;
+	return result;
+}
+
+int fdc_fifo_enable(void)
+{
+	TRACE_FUN(8, "fdc_fifo_enable");
+	int result = 0;
+	byte cmd0[] = {FDC_DUMPREGS};
+	byte cmd1[] = {FDC_CONFIGURE, 0, 0x07, 0}; /* enable fifo, thr = 8 */
+	byte cmd2[] = {FDC_LOCK};
+	byte cmd3[] = {FDC_UNLOCK};
+	byte stat;
+	byte reg[10];
+	int i;
+
+	if (CLK_48MHZ && fdc.type >= i82078)
+		cmd1[0] |= FDC_CLK48_BIT;
+	if (!fdc_fifo_locked) {
+		/*  Dump fdc internal registers for examination
+		 */
+		result = fdc_command(cmd0, NR_ITEMS(cmd0));
+		if (result < 0) {
+			TRACE(2, "FDC dumpreg command failed, fifo unchanged");
+			result = -EIO;
+		} else {
+			/*  Now read fdc internal registers from fifo
+			 */
+			for (i = 0; i < NR_ITEMS(reg); ++i) {
+				fdc_read(&reg[i]);
+				TRACEx2(6, "Register %d = 0x%02x", i, reg[i]);
+			}
+			fdc_fifo_state = (reg[8] & 0x20) == 0;
+			fdc_lock_state = reg[7] & 0x80;
+			fdc_fifo_thr = 1 + (reg[8] & 0x0f);
+			TRACEx3(5, "original fifo state: %sabled, thresshold %d, %slocked",
+				(fdc_fifo_state) ? "en" : "dis",
+			   fdc_fifo_thr, (fdc_lock_state) ? "" : "not ");
+			/*  If fdc is already locked, unlock it first !
+			 */
+			if (fdc_lock_state) {
+				fdc_ready_wait(100);
+				result = fdc_command(cmd3, NR_ITEMS(cmd3));
+				if (result < 0) {
+					TRACE(-1, "FDC unlock command failed, configuration unchanged");
+					result = -EIO;
+				}
+			}
+			/*  Enable fifo and set thresshold at xx bytes to allow a
+			 *  reasonably large latency and reduce number of dma bursts.
+			 */
+			fdc_ready_wait(100);
+			result = fdc_command(cmd1, NR_ITEMS(cmd1));
+			if (result < 0) {
+				TRACE(-1, "FDC configure command failed, fifo unchanged");
+				result = -EIO;
+			} else {
+				/*  Now lock configuration so reset will not change it
+				 */
+				result = fdc_issue_command(cmd2, NR_ITEMS(cmd2), &stat, 1);
+				if (result < 0 || stat != 0x10) {
+					TRACEx1(-1, "FDC lock command failed, stat = 0x%02x", stat);
+					result = -EIO;
+				} else {
+					fdc_fifo_locked = 1;
+					result = 0;
+				}
+			}
+		}
+	} else {
+		TRACE(2, "Fifo not enabled because locked");
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+/*   Determine fd controller type 
+ */
+static byte fdc_save_state[2] = {0, 0};
+
+int fdc_probe(void)
+{
+	TRACE_FUN(8, "fdc_probe");
+	byte cmd[1];
+	byte stat[16];		/* must be able to hold dumpregs & save results */
+	int result;
+
+	/*  Try to find out what kind of fd controller we have to deal with
+	 *  Scheme borrowed from floppy driver:
+	 *  first try if FDC_DUMPREGS command works
+	 *  (this indicates that we have a 82072 or better)
+	 *  then try the FDC_VERSION command (82072 doesn't support this)
+	 *  then try the FDC_UNLOCK command (some older 82077's don't support this)
+	 *  then try the FDC_PARTID command (82078's support this)
+	 */
+	cmd[0] = FDC_DUMPREGS;
+	result = fdc_issue_command(cmd, 1, stat, 1);
+	if (result == 0) {
+		if (stat[0] == 0x80) {
+			/* invalid command: must be pre 82072
+			 */
+			TRACE(2, "Type 8272A/765A compatible FDC found");
+			result = i8272;
+		} else {
+			fdc_result(&stat[1], 9);
+			fdc_save_state[0] = stat[7];
+			fdc_save_state[1] = stat[8];
+			cmd[0] = FDC_VERSION;
+			result = fdc_issue_command(cmd, 1, stat, 1);
+			if (result < 0 || stat[0] == 0x80) {
+				TRACE(2, "Type 82072 FDC found");
+				result = i8272;
+			} else if (*stat == 0x90) {
+				cmd[0] = FDC_UNLOCK;
+				result = fdc_issue_command(cmd, 1, stat, 1);
+				if (result < 0 || stat[0] != 0x00) {
+					TRACE(2, "Type pre-1991 82077 FDC found, treating it like a 82072");
+					result = i8272;
+				} else {
+					int i;
+
+					if (fdc_save_state[0] & 0x80) { /* was locked */
+						cmd[0] = FDC_LOCK; /* restore lock */
+						result = fdc_issue_command(cmd, 1, stat, 1);
+						TRACE(2, "FDC is already locked");
+					}
+					/* Test for a i82078 FDC */
+					cmd[0] = FDC_PARTID;
+					result = fdc_issue_command(cmd, 1, stat, 1);
+					if (result < 0 || stat[0] == 0x80) {
+						/* invalid command: not a i82078xx type FDC */
+						result = no_fdc;
+						for (i = 0; i < 4; ++i) {
+							outb_p(i, fdc.tdr);
+							if ((inb_p(fdc.tdr) & 0x03) != i) {
+								result = i82077;
+								break;
+							}
+						}
+						if (result == no_fdc) {
+							result = i82077AA;
+							TRACE(2, "Type 82077AA FDC found");
+						} else {
+							TRACE(2, "Type 82077 FDC found");
+						}
+					} else {
+						/* FDC_PARTID cmd succeeded */
+						switch (stat[0] >> 5) {
+						case 0x0:
+							/* i82078SL or i82078-1.  The SL part cannot run at 2Mbps (the
+							 * SL and -1 dies are identical; they are speed graded after
+							 * production, according to Intel).  Some SL's can be detected
+							 * by doing a SAVE cmd and look at bit 7 of the first byte (the
+							 * SEL3V# bit).  If it is 0, the part runs off 3Volts, and hence
+							 * it is a SL.
+							 */
+							cmd[0] = FDC_SAVE;
+							result = fdc_issue_command(cmd, 1, stat, 16);
+							if (result < 0) {
+								TRACE(1, "FDC_SAVE failed. Dunno why");
+								/* guess we better claim the fdc to be a i82078 */
+								result = i82078;
+								TRACE(2, "Type i82078 FDC (i suppose) found");
+							} else {
+								if ((stat[0] & FDC_SEL3V_BIT)) {
+									/* fdc running off 5Volts; Pray that it's a i82078-1
+									 */
+									TRACE(2, "Type i82078-1 or 5Volt i82078SL FDC found");
+									TRACE(2, "Treating it as an i82078-1 (2Mbps) FDC");
+									result = i82078_1;
+								} else {
+									TRACE(2, "Type 3Volt i82078SL FDC (1Mbps) found");
+									result = i82078;
+								}
+							}
+							break;
+						case 0x1:
+						case 0x2: /* S82078B (?!) */
+							/* 44pin i82078 found */
+							result = i82078;
+							TRACE(2, "Type i82078 FDC found");
+							break;
+						case 0x3: /* NSC PC8744 core; used in several super-IO chips */
+							result = i82077AA;
+							TRACE(2, "Type 82077AA compatible FDC found");
+							break;
+						default:
+							TRACE(2, "A previously undetected FDC found");
+							TRACEi(2, "Treating it as a 82077AA. Please report partid=",
+							       stat[0]);
+							result = i82077AA;
+						} /* switch(stat[ 0] >> 5) */
+					} /* if (result < 0 || stat[ 0] == 0x80) */
+				}
+			} else {
+				TRACE(2, "Unknown FDC found");
+				result = i8272;
+			}
+		}
+	} else {
+		TRACE(-1, "No FDC found");
+		result = no_fdc;
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+void fdc_config_regs(unsigned fdc_base, unsigned fdc_irq, unsigned fdc_dma)
+{
+	fdc.irq = fdc_irq;
+	fdc.dma = fdc_dma;
+	fdc.sra = fdc_base;
+	fdc.srb = fdc_base + 1;
+	fdc.dor = fdc_base + 2;
+	fdc.tdr = fdc_base + 3;
+	fdc.msr = fdc.dsr = fdc_base + 4;
+	fdc.fifo = fdc_base + 5;
+#if defined MACH2 || defined PROBE_FC10
+	fdc.dor2 = fdc_base + 6;
+#endif
+	fdc.dir = fdc.ccr = fdc_base + 7;
+}
+
+/*  If probing for a FC-10/20 controller the fdc base address, interrupt
+ *  and dma channel must be specified.
+ *  If using an alternate fdc controller, base address, interrupt and
+ *  dma channel must be specified.
+ */
+#if defined PROBE_FC10 && !defined FDC_BASE
+#error No FDC base address (FDC_BASE) specified in Makefile!
+#endif
+#if defined FDC_BASE && !defined FDC_IRQ
+#error No interrupt (FDC_IRQ) specified in Makefile!
+#endif
+#if defined FDC_BASE && !defined FDC_DMA
+#error No dma channel (FDC_DMA) specified in Makefile!
+#endif
+
+void fdc_config(void)
+{
+	TRACE_FUN(8, "fdc_config");
+	static int already_done = 0;
+
+	if (!already_done) {
+#ifdef PROBE_FC10
+		int fc_type;
+
+		fdc_config_regs(FDC_BASE, FDC_IRQ, FDC_DMA);
+		fc_type = fc10_enable();
+		if (fc_type != 0) {
+			TRACEx1(2, "FC-%c0 controller found", '0' + fc_type);
+			fdc.type = fc10;
+			fdc.hook = &do_ftape;
+		} else {
+			TRACE(2, "FC-10/20 controller not found");
+			fdc.type = no_fdc;
+			fdc.dor2 = 0;	/* not used with std fdc */
+			fdc_config_regs(0x3f0, 6, 2);	/* back to std fdc again */
+			fdc.hook = &do_ftape;
+		}
+#else
+#ifdef FDC_BASE
+		TRACE(2, "Using fdc controller at alternate address");
+		fdc_config_regs(FDC_BASE, FDC_IRQ, FDC_DMA);
+		fdc.hook = &do_ftape;
+#else
+		TRACE(2, "Using the standard fdc controller");
+		fdc_config_regs(0x3f0, 6, 2);	/* std fdc */
+		fdc.hook = &do_ftape;
+#endif /* !FDC_BASE */
+#endif /* !PROBE_FC10 */
+	}
+	*(fdc.hook) = fdc_isr;	/* hook our handler in */
+	already_done = 1;
+	TRACE_EXIT;
+}
+
+static void ftape_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	TRACE_FUN(8, "ftape_interrupt");
+	void (*handler) (void) = *fdc.hook;
+
+	*fdc.hook = NULL;
+	if (handler) {
+		handler();
+	} else {
+		TRACE(-1, "Unexpected ftape interrupt");
+	}
+	TRACE_EXIT;
+}
+
+int fdc_grab_irq_and_dma(void)
+{
+	TRACE_FUN(8, "fdc_grab_irq_and_dma");
+	int result = 0;
+	static char ftape_id[] = "ftape";
+
+	if (fdc.hook == &do_ftape) {
+		/*  Get fast interrupt handler.
+		 */
+		result = request_irq(fdc.irq, ftape_interrupt, SA_INTERRUPT,
+				     "ftape", ftape_id);
+		if (result) {
+			TRACEx1(-1, "Unable to grab IRQ%d for ftape driver", fdc.irq);
+			result = -EIO;
+		} else {
+			result = request_dma(fdc.dma, ftape_id);
+			if (result) {
+				TRACEx1(-1, "Unable to grab DMA%d for ftape driver", fdc.dma);
+				free_irq(fdc.irq, NULL);
+				result = -EIO;
+			} else {
+				enable_irq(fdc.irq);
+			}
+		}
+	}
+#ifdef FDC_DMA
+	if (result == 0 && FDC_DMA == 2) {
+		/*  Using same dma channel as standard fdc, need to disable the
+		 *  dma-gate on the std fdc. This couldn't be done in the floppy
+		 *  driver as some laptops are using the dma-gate to enter a
+		 *  low power or even suspended state :-(
+		 */
+		outb_p(FDC_RESET_NOT, 0x3f2);
+		TRACE(2, "DMA-gate on standard fdc disabled");
+	}
+#endif
+	TRACE_EXIT;
+	return result;
+}
+
+int fdc_release_irq_and_dma(void)
+{
+	TRACE_FUN(8, "fdc_grab_irq_and_dma");
+	int result = 0;
+
+	if (fdc.hook == &do_ftape) {
+		disable_dma(fdc.dma);	/* just in case... */
+		free_dma(fdc.dma);
+		disable_irq(fdc.irq);
+		free_irq(fdc.irq, NULL);
+	}
+#ifdef FDC_DMA
+	if (result == 0 && FDC_DMA == 2) {
+		/*  Using same dma channel as standard fdc, need to disable the
+		 *  dma-gate on the std fdc. This couldn't be done in the floppy
+		 *  driver as some laptops are using the dma-gate to enter a
+		 *  low power or even suspended state :-(
+		 */
+		outb_p(FDC_RESET_NOT | FDC_DMA_MODE, 0x3f2);
+		TRACE(2, "DMA-gate on standard fdc enabled again");
+	}
+#endif
+	TRACE_EXIT;
+	return result;
+}
+
+int fdc_uninit(void)
+{
+	TRACE_FUN(8, "fdc_uninit");
+	int result = 0;
+
+	if (fdc.sra != 0) {
+		if (fdc.dor2 == 0) {
+			release_region(fdc.sra, 6);
+			release_region(fdc.sra + 7, 1);
+		} else {
+			release_region(fdc.sra, 8);
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int fdc_init(void)
+{
+	TRACE_FUN(8, "fdc_init");
+	int result = 0;
+
+	fdc_config();
+	if (fdc_grab_irq_and_dma() < 0) {
+		result = -EBUSY;
+	} else {
+		ftape_motor = 0;
+		fdc_catch_stray_interrupts(1);	/* one always comes */
+		TRACE(5, "resetting fdc");
+		fdc_reset();	/* init fdc & clear track counters */
+		if (fdc.type == no_fdc) {	/* default, means no FC-10 or 20 found */
+			fdc.type = fdc_probe();
+		}
+		if (fdc.type != no_fdc) {
+			if (fdc.type >= i82077) {
+				if (fdc_fifo_enable() < 0) {
+					TRACE(2, "couldn't enable fdc fifo !");
+				} else {
+					TRACE(5, "fdc fifo enabled and locked");
+				}
+			}
+		} else {
+			fdc_release_irq_and_dma();
+			result = -EIO;
+		}
+	}
+	if (result >= 0) {
+		if (fdc.dor2 == 0) {
+			request_region(fdc.sra, 6, "fdc (ftape)");
+			request_region(fdc.sra + 7, 1, "fdc (ftape)");
+		} else {
+			request_region(fdc.sra, 8, "fdc (ftape)");
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/fdc-io.h linux/drivers/char/ftape/fdc-io.h
--- v1.3.71/linux/drivers/char/ftape/fdc-io.h	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/fdc-io.h	Wed Mar  6 15:07:19 1996
@@ -0,0 +1,182 @@
+#ifndef _FDC_IO_H
+#define _FDC_IO_H
+
+/*
+ * Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ $Source: /home/bas/distr/ftape-2.03b/RCS/fdc-io.h,v $
+ $Author: bas $
+ *
+ $Revision: 1.38 $
+ $Date: 1995/05/10 16:09:36 $
+ $State: Beta $
+ *
+ *      This file contains the low level functions
+ *      that communicate with the floppy disk controller,
+ *      for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include <linux/fdreg.h>
+
+#define FDC_SK_BIT      (0x20)
+#define FDC_MT_BIT      (0x80)
+
+#define FDC_READ        (FD_READ & ~(FDC_SK_BIT | FDC_MT_BIT))
+#define FDC_WRITE       (FD_WRITE & ~FDC_MT_BIT)
+#define FDC_READ_DELETED (0x4c)
+#define FDC_WRITE_DELETED (0x49)
+#define FDC_READID      (0x4a)
+#define FDC_SENSED      (0x04)
+#define FDC_SENSEI      (FD_SENSEI)
+#define FDC_RECAL       (FD_RECALIBRATE)
+#define FDC_SEEK        (FD_SEEK)
+#define FDC_SPECIFY     (FD_SPECIFY)
+#define FDC_RECALIBR    (FD_RECALIBRATE)
+#define FDC_VERSION     (FD_VERSION)
+#define FDC_PERPEND     (FD_PERPENDICULAR)
+#define FDC_DUMPREGS    (FD_DUMPREGS)
+#define FDC_LOCK        (FD_LOCK)
+#define FDC_UNLOCK      (FD_UNLOCK)
+#define FDC_CONFIGURE   (FD_CONFIGURE)
+#define FDC_DRIVE_SPEC  (0x8e)	/* i82078 has this (any others?) */
+#define FDC_PARTID      (0x18)	/* i82078 has this */
+#define FDC_SAVE        (0x2e)	/* i82078 has this (any others?) */
+#define FDC_RESTORE     (0x4e)	/* i82078 has this (any others?) */
+
+#define FDC_STATUS_MASK (STATUS_BUSY | STATUS_DMA | STATUS_DIR | STATUS_READY)
+#define FDC_DATA_READY  (STATUS_READY)
+#define FDC_DATA_OUTPUT (STATUS_DIR)
+#define FDC_DATA_READY_MASK (STATUS_READY | STATUS_DIR)
+#define FDC_DATA_OUT_READY  (STATUS_READY | STATUS_DIR)
+#define FDC_DATA_IN_READY   (STATUS_READY)
+#define FDC_BUSY        (STATUS_BUSY)
+#define FDC_CLK48_BIT   (0x80)
+#define FDC_SEL3V_BIT   (0x40)
+
+#define ST0_INT_MASK    (ST0_INTR)
+#define FDC_INT_NORMAL  (ST0_INTR & 0x00)
+#define FDC_INT_ABNORMAL (ST0_INTR & 0x40)
+#define FDC_INT_INVALID (ST0_INTR & 0x80)
+#define FDC_INT_READYCH (ST0_INTR & 0xC0)
+#define ST0_SEEK_END    (ST0_SE)
+#define ST3_TRACK_0     (ST3_TZ)
+
+#define FDC_RESET_NOT   (0x04)
+#define FDC_DMA_MODE    (0x08)
+#define FDC_MOTOR_0     (0x10)
+#define FDC_MOTOR_1     (0x20)
+
+typedef struct {
+	void (**hook) (void);	/* our wedge into the isr */
+	enum {
+		no_fdc, i8272, i82077, i82077AA, fc10,
+		i82078, i82078_1
+	} type;			/* FDC type */
+	unsigned char irq;	/* FDC irq nr */
+	unsigned char dma;	/* FDC dma channel nr */
+	unsigned short sra;	/* Status register A (PS/2 only) */
+	unsigned short srb;	/* Status register B (PS/2 only) */
+	unsigned short dor;	/* Digital output register */
+	unsigned short tdr;	/* Tape Drive Register (82077SL-1 &
+				   82078 only) */
+	unsigned short msr;	/* Main Status Register */
+	unsigned short dsr;	/* Datarate Select Register (8207x only) */
+	unsigned short fifo;	/* Data register / Fifo on 8207x */
+	unsigned short dir;	/* Digital Input Register */
+	unsigned short ccr;	/* Configuration Control Register */
+	unsigned short dor2;	/* Alternate dor on MACH-2 controller,
+				   also used with FC-10, meaning unknown */
+} fdc_config_info;
+
+typedef enum {
+	fdc_data_rate_250 = 2,
+	fdc_data_rate_500 = 0,
+	fdc_data_rate_1000 = 3,
+	fdc_data_rate_2000 = 1,	/* i82078-1: remember to use Data Rate Table #2 */
+} fdc_data_rate_type;
+
+typedef enum {
+	waiting = 0,
+	reading,
+	writing,
+	done,
+	error,
+} buffer_state_enum;
+
+typedef volatile enum {
+	fdc_idle = 0,
+	fdc_reading_data = FDC_READ,
+	fdc_seeking = FDC_SEEK,
+	fdc_writing_data = FDC_WRITE,
+	fdc_reading_id = FDC_READID,
+	fdc_recalibrating = FDC_RECAL,
+} fdc_mode_enum;
+
+/*
+ *      fdc-io.c defined public variables
+ */
+extern fdc_mode_enum fdc_mode;
+extern volatile enum runner_status_enum runner_status;
+extern int old_vfo;
+extern volatile int head;
+extern volatile int tail;
+extern int fdc_setup_error;	/* outdated ??? */
+extern struct wait_queue *wait_intr;
+extern volatile unsigned int next_segment;	/* next segment for read ahead */
+extern int ftape_unit;		/* fdc unit specified at ftape_open() */
+extern int ftape_motor;		/* fdc motor line state */
+extern int current_cylinder;	/* track nr the FDC thinks we're on */
+extern volatile byte fdc_head;	/* FDC head */
+extern volatile byte fdc_cyl;	/* FDC track */
+extern volatile byte fdc_sect;	/* FDC sector */
+extern fdc_config_info fdc;	/* FDC hardware configuration */
+
+/*
+ *      fdc-io.c defined public functions
+ */
+extern void fdc_catch_stray_interrupts(unsigned count);
+extern int fdc_ready_wait(int timeout);
+extern int fdc_write(byte data);
+extern int fdc_read(byte * data);
+extern int fdc_command(byte * cmd_data, int cmd_len);
+extern int fdc_result(byte * res_data, int res_len);
+extern int fdc_issue_command(byte * out_data, int out_count, \
+			     byte * in_data, int in_count);
+extern void fdc_isr(void);
+extern int fdc_interrupt_wait(int time);
+extern void fdt_sleep(unsigned int time);
+extern int fdc_specify(int head_unload_time, int seek_rate,
+		       int head_load_time, int non_dma);
+extern int fdc_set_seek_rate(int seek_rate);
+extern int fdc_seek(int track);
+extern int fdc_sense_drive_status(int *st3);
+extern void fdc_motor(int motor);
+extern void fdc_reset(void);
+extern int fdc_recalibrate(void);
+extern void fdc_disable(void);
+extern int fdc_wait_calibrate(void);
+extern int fdc_sense_interrupt_status(int *st0, int *current_cylinder);
+extern void fdc_save_drive_specs(void);
+extern void fdc_restore_drive_specs(void);
+extern void fdc_set_data_rate(int rate);
+extern int fdc_release_irq_and_dma(void);
+extern int fdc_init(void);
+extern int fdc_uninit(void);
+extern void fdc_set_write_precomp(int precomp);
+
+#endif
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/fdc-isr.c linux/drivers/char/ftape/fdc-isr.c
--- v1.3.71/linux/drivers/char/ftape/fdc-isr.c	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/fdc-isr.c	Wed Mar  6 15:07:19 1996
@@ -0,0 +1,814 @@
+/*
+ *      Copyright (C) 1994-1995 Bas Laarhoven.
+
+ 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.
+
+ $Source: /home/bas/distr/ftape-2.03b/RCS/fdc-isr.c,v $
+ $Author: bas $
+ *
+ $Revision: 1.36 $
+ $Date: 1995/05/27 08:54:21 $
+ $State: Beta $
+ *
+ *      This file contains the interrupt service routine and associated
+ *      code for the QIC-40/80 tape streamer device driver.
+ */
+
+#include <linux/module.h>
+#include <linux/ftape.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+
+#define volatile		/* */
+
+#include "tracing.h"
+#include "fdc-isr.h"
+#include "qic117.h"
+#include "fdc-io.h"
+#include "ftape-ctl.h"
+#include "ftape-rw.h"
+#include "ftape-io.h"
+#include "calibr.h"
+#include "ftape-bsm.h"
+
+/*      Global vars.
+ */
+volatile int expected_stray_interrupts = 0;
+volatile int seek_completed = 0;
+volatile int interrupt_seen = 0;
+volatile int expect_stray_interrupt = 0;
+int random_rw = 0;
+
+/*      Local vars.
+ */
+typedef enum {
+	no_error = 0, id_am_error = 0x01, id_crc_error = 0x02,
+	data_am_error = 0x04, data_crc_error = 0x08,
+	no_data_error = 0x10, overrun_error = 0x20,
+} error_cause;
+static int hide_interrupt;
+static int stop_read_ahead = 0;
+
+
+static void print_error_cause(int cause)
+{
+	TRACE_FUN(8, "print_error_cause");
+
+	switch (cause) {
+	case no_data_error:
+		TRACE(4, "no data error");
+		break;
+	case id_am_error:
+		TRACE(4, "id am error");
+		break;
+	case id_crc_error:
+		TRACE(4, "id crc error");
+		break;
+	case data_am_error:
+		TRACE(4, "data am error");
+		break;
+	case data_crc_error:
+		TRACE(4, "data crc error");
+		break;
+	case overrun_error:
+		TRACE(4, "overrun error");
+		break;
+	default:
+	}
+	TRACE_EXIT;
+}
+
+static char *
+get_fdc_mode_text(fdc_mode_enum fdc_mode)
+{
+	switch (fdc_mode) {
+	case fdc_idle:
+		return "fdc_idle";
+	case fdc_reading_data:
+		return "fdc_reading_data";
+	case fdc_seeking:
+		return "fdc_seeking";
+	case fdc_writing_data:
+		return "fdc_writing_data";
+	case fdc_reading_id:
+		return "fdc_reading_id";
+	case fdc_recalibrating:
+		return "fdc_recalibrating";
+	default:
+		return "unknown";
+	}
+}
+
+static void
+decode_irq_cause(fdc_mode_enum fdc_mode, byte st[],
+		 char **fdc_mode_txt, error_cause * cause)
+{
+	TRACE_FUN(8, "decode_irq_cause");
+
+	/*  Valid st[], decode cause of interrupt.
+	 */
+	*fdc_mode_txt = get_fdc_mode_text(fdc_mode);
+	switch (st[0] & ST0_INT_MASK) {
+	case FDC_INT_NORMAL:
+		TRACEx1(fdc_mode == fdc_reading_id ? 6 : 5,
+			"normal completion: %s", *fdc_mode_txt);
+		*cause = no_error;
+		break;
+	case FDC_INT_ABNORMAL:
+		TRACEx1(5, "abnormal completion %s", *fdc_mode_txt);
+		TRACEx3(6, "ST0: 0x%02x, ST1: 0x%02x, ST2: 0x%02x",
+			st[0], st[1], st[2]);
+		TRACEx4(6, "C: 0x%02x, H: 0x%02x, R: 0x%02x, N: 0x%02x",
+			st[3], st[4], st[5], st[6]);
+		if (st[1] & 0x01) {
+			if (st[2] & 0x01) {
+				*cause = data_am_error;
+			} else {
+				*cause = id_am_error;
+			}
+		} else if (st[1] & 0x20) {
+			if (st[2] & 0x20) {
+				*cause = data_crc_error;
+			} else {
+				*cause = id_crc_error;
+			}
+		} else if (st[1] & 0x04) {
+			*cause = no_data_error;
+		} else if (st[1] & 0x10) {
+			*cause = overrun_error;
+		}
+		print_error_cause(*cause);
+		break;
+	case FDC_INT_INVALID:
+		TRACEx1(5, "invalid completion %s", *fdc_mode_txt);
+		*cause = no_error;
+		break;
+	case FDC_INT_READYCH:
+		TRACEx1(5, "ready change %s", *fdc_mode_txt);
+		*cause = no_error;
+		break;
+	default:
+	}
+	TRACE_EXIT;
+}
+
+static void update_history(error_cause cause)
+{
+	switch (cause) {
+	case id_am_error:
+		history.id_am_errors++;
+		break;
+	case id_crc_error:
+		history.id_crc_errors++;
+		break;
+	case data_am_error:
+		history.data_am_errors++;
+		break;
+	case data_crc_error:
+		history.data_crc_errors++;
+		break;
+	case overrun_error:
+		history.overrun_errors++;
+		break;
+	case no_data_error:
+		history.no_data_errors++;
+		break;
+	default:
+	}
+}
+
+static void skip_bad_sector(buffer_struct * buff)
+{
+	TRACE_FUN(8, "skip_bad_sector");
+
+	/* Mark sector as soft error and skip it
+	 */
+	if (buff->remaining > 0) {
+		++buff->sector_offset;
+		++buff->data_offset;
+		--buff->remaining;
+		buff->ptr += SECTOR_SIZE;
+		buff->bad_sector_map >>= 1;
+	} else {
+		++buff->sector_offset;	/* hack for error maps */
+		TRACE(1, "skipping last sector in segment");
+	}
+	TRACE_EXIT;
+}
+
+static void update_error_maps(buffer_struct * buff, unsigned error_offset)
+{
+	TRACE_FUN(8, "update_error_maps");
+	int hard = 0;
+
+	/* error_offset is a sector offset !
+	 */
+	if (buff->retry < SOFT_RETRIES) {
+		buff->soft_error_map |= (1 << error_offset);
+	} else {
+		buff->hard_error_map |= (1 << error_offset);
+		buff->soft_error_map &= ~buff->hard_error_map;
+		buff->retry = -1;	/* will be set to 0 in setup_segment */
+		hard = 1;
+	}
+	TRACEx2(4, "sector %d : %s error", SECTOR(error_offset),
+		hard ? "hard" : "soft");
+	TRACEx2(5, "hard map: 0x%08lx, soft map: 0x%08lx",
+		buff->hard_error_map, buff->soft_error_map);
+	TRACE_EXIT;
+}
+
+/*
+ *  Error cause:   Amount xferred:  Action:
+ *
+ *  id_am_error         0           mark bad and skip
+ *  id_crc_error        0           mark bad and skip
+ *  data_am_error       0           mark bad and skip
+ *  data_crc_error    % 1024        mark bad and skip
+ *  no_data_error       0           retry on write
+ *                                  mark bad and skip on read
+ *  overrun_error  [ 0..all-1 ]     mark bad and skip
+ *  no_error           all          continue
+ */
+static void determine_progress(buffer_struct * buff, error_cause cause, int mode)
+{
+	TRACE_FUN(8, "determine_progress");
+	unsigned nr_not_xferred;
+	unsigned nr_xferred;
+	unsigned dma_residue;
+
+	/*  Using less preferred order of disable_dma and get_dma_residue
+	 *  because this seems to fail on at least one system if reversed!
+	 */
+	dma_residue = get_dma_residue(fdc.dma);
+	disable_dma(fdc.dma);
+	nr_xferred = buff->sector_count * SECTOR_SIZE - dma_residue;
+	if (cause == no_error && dma_residue == 0) {
+		nr_not_xferred = 0;
+	} else {
+		if (cause == no_error) {
+			TRACEx1(4, "unexpected DMA residue: 0x%04x", dma_residue);
+		} else {
+			TRACEx1(6, "DMA residue = 0x%04x", dma_residue);
+		}
+		nr_not_xferred = ((dma_residue + (SECTOR_SIZE - 1)) / SECTOR_SIZE);
+		buff->sector_count -= nr_not_xferred;	/* adjust to actual value */
+	}
+	/*  Update var's influenced by the DMA operation.
+	 */
+	if (buff->sector_count > 0) {
+		buff->sector_offset += buff->sector_count;
+		buff->data_offset += buff->sector_count;
+		buff->ptr += buff->sector_count * SECTOR_SIZE;
+		buff->remaining -= buff->sector_count;
+		buff->bad_sector_map >>= buff->sector_count;
+	}
+	if (cause == no_error) {
+		TRACEx1(5, "%d Sector(s) transferred", buff->sector_count);
+	} else if (cause == no_data_error) {
+		TRACEx1(5, "Sector %d not found", SECTOR(buff->sector_offset));
+	} else if (nr_xferred > 0 || cause == id_crc_error ||
+		   cause == id_am_error || cause == data_am_error) {
+		TRACEx1(5, "Error in sector %d", SECTOR(buff->sector_offset));
+	} else if (cause == overrun_error) {
+		/* got an overrun error on the first byte, must be a hardware problem
+		 */
+		TRACE(-1, "Unexpected error: failing DMA controller ?");
+	} else {
+		TRACEx1(4, "Unexpected error at sector %d", SECTOR(buff->sector_offset));
+	}
+	/*  Sector_offset points to the problem area, except if we got
+	 *  a data_crc_error. In that case it points one past the failing
+	 *  sector.
+	 *  Now adjust sector_offset so it allways points one past he
+	 *  failing sector. I.e. skip the bad sector.
+	 */
+	if (cause != no_error) {
+		if (cause != data_crc_error) {
+			skip_bad_sector(buff);
+		}
+		update_error_maps(buff, buff->sector_offset - 1);
+	}
+	TRACE_EXIT;
+}
+
+static int calc_steps(int cmd)
+{
+	if (current_cylinder > cmd) {
+		return current_cylinder - cmd;
+	} else {
+		return current_cylinder + cmd;
+	}
+}
+
+static void pause_tape(unsigned segment, int retry, int fdc_mode)
+{
+	TRACE_FUN(8, "pause_tape");
+	int result;
+	/*  The 3rd initializer needs to be explicit or else gcc will
+	 *  generate a reference to memset :-(
+	 */
+	byte out[3] =
+	{FDC_SEEK, FTAPE_UNIT, 0};
+
+	/*  We'll use a raw seek command to get the tape to rewind
+	 *  and stop for a retry.
+	 */
+	++history.rewinds;
+	if (qic117_cmds[current_command].non_intr) {
+		TRACE(2, "motion command may be issued too soon");
+	}
+	if (retry && (fdc_mode == fdc_reading_data || fdc_mode == fdc_reading_id)) {
+		current_command = QIC_MICRO_STEP_PAUSE;
+		might_be_off_track = 1;
+	} else {
+		current_command = QIC_PAUSE;
+	}
+	out[2] = calc_steps(current_command);
+	result = fdc_command(out, 3);	/* issue QIC_117 command */
+	if (result < 0) {
+		TRACEx1(4, "qic-pause failed, status = %d", result);
+	} else {
+		location.known = 0;
+		runner_status = idle;
+		hide_interrupt = 1;
+		tape_running = 0;
+	}
+	TRACE_EXIT;
+}
+
+static void stop_tape(unsigned segment)
+{
+	TRACE_FUN(8, "stop_tape");
+	int result;
+	byte out[3] =
+	{FDC_SEEK, FTAPE_UNIT, calc_steps(QIC_STOP_TAPE)};
+
+	if (qic117_cmds[current_command].non_intr) {
+		TRACE(2, "motion command may be issued too soon");
+	}
+	current_command = QIC_STOP_TAPE;
+	/*  We'll use a raw seek command to get the tape to stop
+	 */
+	result = fdc_command(out, 3);	/* issue QIC_117 command */
+	if (result < 0) {
+		TRACEx1(4, "qic-stop failed, status = %d", result);
+	} else {
+		runner_status = idle;
+		hide_interrupt = 1;
+		tape_running = 0;
+	}
+	TRACE_EXIT;
+}
+
+static void continue_xfer(buffer_struct ** p_buff, error_cause cause,
+			  int fdc_mode, unsigned skip)
+{
+	TRACE_FUN(8, "continue_xfer");
+	buffer_struct *buff = *p_buff;
+	int write = (fdc_mode == fdc_writing_data);
+	byte fdc_op = (write) ? FDC_WRITE : FDC_READ;
+
+	if (skip > 0) {
+		/* This part can be removed if it never happens
+		 */
+		if (runner_status != running ||
+		    (buff->status != (write ? writing : reading))) {
+			TRACEx2(1, "unexpected runner/buffer state %d/%d",
+				runner_status, buff->status);
+			buff->status = error;
+			*p_buff = next_buffer(&head);	/* finish this buffer */
+			runner_status = aborting;
+			fdc_mode = fdc_idle;
+		}
+	}
+	if (buff->remaining > 0 && calc_next_cluster(&buffer[head]) > 0) {
+		/* still sectors left in current segment, continue with this segment
+		 */
+		if (setup_fdc_and_dma(&buffer[head], fdc_op) < 0) {
+			/* failed, abort operation
+			 */
+			buff->bytes = buff->ptr - buff->address;
+			buff->status = error;
+			buff = *p_buff = next_buffer(&head);	/* finish this buffer */
+			runner_status = aborting;
+			fdc_mode = fdc_idle;
+		}
+	} else {
+		/* current segment completed
+		 */
+		unsigned last_segment = buff->segment_id;
+		int eot = ((last_segment + 1) % segments_per_track) == 0;
+		int next = buff->next_segment;	/* 0 means stop ! */
+
+		buff->bytes = buff->ptr - buff->address;
+		buff->status = done;
+		buff = *p_buff = next_buffer(&head);
+		if (eot) {
+			/* finished last segment on current track, can't continue
+			 */
+			runner_status = logical_eot;
+			fdc_mode = fdc_idle;
+		} else if (next > 0) {
+			/* continue with next segment
+			 */
+			if (buff->status == waiting) {
+				if (write && next != buff->segment_id) {
+					TRACE(5, "segments out of order, aborting write");
+					runner_status = do_abort;
+					fdc_mode = fdc_idle;
+				} else {
+					setup_new_segment(&buffer[head], next, 0);
+					if (stop_read_ahead) {
+						buff->next_segment = 0;
+						stop_read_ahead = 0;
+					}
+					if (calc_next_cluster(&buffer[head]) == 0 ||
+					    setup_fdc_and_dma(&buffer[head], fdc_op) != 0) {
+						TRACEx1(1, "couldn't start %s-ahead", (write) ? "write" : "read");
+						runner_status = do_abort;
+						fdc_mode = fdc_idle;
+					} else {
+						buff->status = (write) ? writing : reading;	/* keep on going */
+					}
+				}
+			} else {
+				TRACEx1(5, "all input buffers %s, pausing tape",
+					(write) ? "empty" : "full");
+				pause_tape(last_segment, 0, fdc_mode);
+				runner_status = idle;	/* not quite true until next irq */
+			}
+		} else {
+			/* don't continue with next segment
+			 */
+			TRACEx1(5, "no %s allowed, stopping tape",
+				(write) ? "write next" : "read ahead");
+			if (random_rw) {
+				stop_tape(last_segment);
+			} else {
+				pause_tape(last_segment, 0, fdc_mode);
+			}
+			runner_status = idle;	/* not quite true until next irq */
+		}
+	}
+	TRACE_EXIT;
+	return;
+}
+
+static void
+retry_sector(buffer_struct ** p_buff, error_cause cause, int fdc_mode,
+	     unsigned skip)
+{
+	TRACE_FUN(8, "retry_sector");
+	buffer_struct *buff = *p_buff;
+
+	TRACEx1(4, "%s error, will retry",
+		(fdc_mode == fdc_writing_data) ? "write" : "read");
+	pause_tape(buff->segment_id, 1, fdc_mode);
+	runner_status = aborting;
+	buff->status = error;
+	buff->skip = skip;
+	TRACE_EXIT;
+}
+
+static unsigned
+find_resume_point(buffer_struct * buff)
+{
+	TRACE_FUN(8, "find_resume_point");
+	int i = 0;
+	unsigned long mask;
+	unsigned long map;
+
+	/*  This function is to be called after all variables have been
+	 *  updated to point past the failing sector.
+	 *  If there are any soft errors before the failing sector,
+	 *  find the first soft error and return the sector offset.
+	 *  Otherwise find the last hard error.
+	 *  Note: there should allways be at least one hard or soft error !
+	 */
+	if (buff->sector_offset < 1 || buff->sector_offset > 32) {
+		TRACEx1(1, "bug: sector_offset = %d", buff->sector_offset);
+	} else {
+		if (buff->sector_offset >= 32) {	/* C-limitation on shift ! */
+			mask = 0xffffffff;
+		} else {
+			mask = (1 << buff->sector_offset) - 1;
+		}
+		map = buff->soft_error_map & mask;
+		if (map) {
+			while ((map & (1 << i)) == 0) {
+				++i;
+			}
+			TRACEx1(4, "at sector %d", SECTOR(i));
+		} else {
+			map = buff->hard_error_map & mask;
+			i = buff->sector_offset - 1;
+			if (map) {
+				while ((map & (1 << i)) == 0) {
+					--i;
+				}
+				TRACEx1(4, "after sector %d", SECTOR(i));
+				++i;	/* first sector after last hard error */
+			} else {
+				TRACE(1, "bug: no soft or hard errors");
+			}
+		}
+	}
+	TRACE_EXIT;
+	return i;
+}
+
+/*      FDC interrupt service routine.
+ */
+void
+fdc_isr(void)
+{
+	TRACE_FUN(8, "fdc_isr");
+	int result;
+	int status;
+	error_cause cause = no_error;
+	byte in[7];
+	static int isr_active = 0;
+	int t0;
+	buffer_struct *buff = &buffer[head];
+	int skip;
+
+	t0 = timestamp();
+	if (isr_active) {
+		TRACE(-1, "nested interrupt, not good !");
+		*fdc.hook = fdc_isr;	/* hook our handler into the fdc code again */
+		TRACE_EXIT;
+		return;
+	}
+	++isr_active;
+	sti();			/* enables interrupts again */
+	status = inb_p(fdc.msr);
+	if (status & FDC_BUSY) {	/*  Entering Result Phase */
+		hide_interrupt = 0;
+		result = fdc_result(in, 7);	/* better get it fast ! */
+		if (result < 0) {
+			/*  Entered unknown state...
+			 */
+			TRACE(1, "probably fatal error during FDC Result Phase");
+			TRACE(1, "drive may hang until (power) reset :-(");
+			/*  what to do next ????
+			 */
+		} else {
+			int i;
+			char *fdc_mode_txt;
+
+			decode_irq_cause(fdc_mode, in, &fdc_mode_txt, &cause);
+			for (i = 0; i < NR_BUFFERS; ++i) {
+				TRACEx3(8, "buffer[%d] status: %d, segment_id: %d",
+					i, buffer[i].status, buffer[i].segment_id);
+			}
+			switch (fdc_mode) {
+
+			case fdc_reading_data:{
+
+					if (cause == no_error) {
+						TRACEi(5, "reading segment", buff->segment_id);
+					} else {
+						TRACEi(4, "error reading segment", buff->segment_id);
+					}
+					if (runner_status == aborting || runner_status == do_abort) {
+						TRACEx1(4, "aborting %s", fdc_mode_txt);
+						break;
+					}
+					if (buff->retry > 0) {
+						TRACEx1(5, "this is retry nr %d", buff->retry);
+					}
+					if (buff->bad_sector_map == FAKE_SEGMENT) {
+						/* This condition occurs when reading a `fake' sector that's
+						 * not accessible. Doesn't really matter as we would have
+						 * ignored it anyway !
+						 * Chance is that we're past the next segment now, so the
+						 * next operation may fail and result in a retry.
+						 */
+						TRACE(4, "skipping empty segment (read)");
+						buff->remaining = 0;	/* skip failing sector */
+						continue_xfer(&buff, no_error, fdc_mode, 1);	/* fake success */
+					} else {
+						switch (cause) {
+						case no_error:{
+								determine_progress(buff, cause, fdc_reading_data);
+								if (in[2] & 0x40) {
+									/*  Handle deleted data in header segments.
+									 *  Skip segment and force read-ahead.
+									 */
+									TRACEx1(2, "deleted data in sector %d",
+										SECTOR(buff->sector_offset - 1));
+									buff->deleted = 1;
+									buff->remaining = 0;	/* abort transfer */
+									buff->soft_error_map |= (-1L << buff->sector_offset);
+									if (buff->segment_id == 0) {
+										stop_read_ahead = 1;	/* stop on next segment */
+									}
+									buff->next_segment = buff->segment_id + 1;	/* force read-ahead */
+									skip = (SECTORS_PER_SEGMENT - buff->sector_offset);
+								} else {
+									skip = 0;
+								}
+								continue_xfer(&buff, cause, fdc_mode, skip);
+								break;
+							}
+						case no_data_error:
+							/*  Tape started too far ahead of or behind the right sector.
+							 *  This may also happen in the middle of a segment !
+							 *  Handle no-data as soft error. If next sector fails too,
+							 *  a retry (with needed reposition) will follow.
+							 */
+						case id_am_error:
+						case id_crc_error:
+						case data_am_error:
+						case data_crc_error:
+						case overrun_error:{
+								int first_error = (buff->soft_error_map == 0 &&
+										   buff->hard_error_map == 0);
+
+								update_history(cause);
+								determine_progress(buff, cause, fdc_reading_data);
+								if (first_error) {
+									skip = buff->sector_offset;
+								} else {
+									skip = find_resume_point(buff);
+								}
+								/*  Try to resume with next sector on single errors (let ecc
+								 *  correct it), but retry on no_data (we'll be past the
+								 *  target when we get here so we cannot retry) or on multiple
+								 *  errors (reduce chance on ecc failure).
+								 */
+								if (first_error && cause != no_data_error) {
+									continue_xfer(&buff, cause, fdc_mode, skip);
+								} else {
+									retry_sector(&buff, cause, fdc_mode, skip);
+								}
+								break;
+							}
+						default:{
+								/*  Don't know why this could happen but find out.
+								 */
+								TRACE(1, "unexpected error");
+								determine_progress(buff, cause, fdc_reading_data);
+								retry_sector(&buff, cause, fdc_mode, 0);
+								break;
+							}
+						}
+					}
+					break;
+				}
+
+			case fdc_reading_id:{
+
+					if (cause == no_error) {
+						fdc_cyl = in[3];
+						fdc_head = in[4];
+						fdc_sect = in[5];
+						TRACEx3(6, "id read: C: 0x%02x, H: 0x%02x, R: 0x%02x",
+							fdc_cyl, fdc_head, fdc_sect);
+					} else {	/* no valid information, use invalid sector */
+						fdc_cyl =
+						    fdc_head =
+						    fdc_sect = 0;
+						TRACE(5, "Didn't find valid sector Id");
+					}
+					fdc_mode = fdc_idle;
+					break;
+				}
+
+			case fdc_writing_data:{
+
+					if (cause == no_error) {
+						TRACEi(5, "writing segment", buff->segment_id);
+					} else {
+						TRACEi(4, "error writing segment", buff->segment_id);
+					}
+					if (runner_status == aborting || runner_status == do_abort) {
+						TRACEx1(5, "aborting %s", fdc_mode_txt);
+						break;
+					}
+					if (buff->retry > 0) {
+						TRACEx1(5, "this is retry nr %d", buff->retry);
+					}
+					if (buff->bad_sector_map == FAKE_SEGMENT) {
+						/* This condition occurs when trying to write to a `fake'
+						 * sector that's not accessible. Doesn't really matter as
+						 * it isn't used anyway ! Might be located at wrong segment,
+						 * then we'll fail on the next segment.
+						 */
+						TRACE(4, "skipping empty segment (write)");
+						buff->remaining = 0;	/* skip failing sector */
+						continue_xfer(&buff, no_error, fdc_mode, 1);	/* fake success */
+					} else {
+						switch (cause) {
+						case no_error:{
+								determine_progress(buff, cause, fdc_writing_data);
+								continue_xfer(&buff, cause, fdc_mode, 0);
+								break;
+							}
+						case no_data_error:
+						case id_am_error:
+						case id_crc_error:
+						case data_am_error:
+						case overrun_error:{
+								update_history(cause);
+								determine_progress(buff, cause, fdc_writing_data);
+								skip = find_resume_point(buff);
+								retry_sector(&buff, cause, fdc_mode, skip);
+								break;
+							}
+						default:{
+								if (in[1] & 0x02) {
+									TRACE(1, "media not writable");
+								} else {
+									TRACE(-1, "unforseen write error");
+								}
+								fdc_mode = fdc_idle;
+								break;
+							}
+						}
+					}
+					break;
+				}
+			default:
+
+				TRACEx1(1, "Warning: unexpected irq during: %s",
+					fdc_mode_txt);
+				fdc_mode = fdc_idle;
+				break;
+			}
+		}
+		if (runner_status == do_abort) {
+			/*      cease operation, remember tape position
+			 */
+			TRACE(5, "runner aborting");
+			runner_status = aborting;
+			++expected_stray_interrupts;
+		}
+	} else { /* !FDC_BUSY  */
+		/*  clear interrupt, cause should be gotten by issueing
+		 *  a Sense Interrupt Status command.
+		 */
+		if (fdc_mode == fdc_recalibrating || fdc_mode == fdc_seeking) {
+			if (hide_interrupt) {
+				int st0;
+				int pcn;
+
+				result = fdc_sense_interrupt_status(&st0, &pcn);
+				current_cylinder = pcn;
+				TRACE(5, "handled hidden interrupt");
+			}
+			seek_completed = 1;
+			fdc_mode = fdc_idle;
+		} else if (!wait_intr) {
+			if (expected_stray_interrupts == 0) {
+				TRACE(2, "unexpected stray interrupt");
+			} else {
+				TRACE(5, "expected stray interrupt");
+				--expected_stray_interrupts;
+			}
+		} else {
+			if (fdc_mode == fdc_reading_data || fdc_mode == fdc_writing_data ||
+			    fdc_mode == fdc_reading_id) {
+				byte status = inb_p(fdc.msr);
+				if (status & FDC_BUSY) {
+					TRACE(-1, "***** FDC failure, busy too late");
+				} else {
+					TRACE(-1, "***** FDC failure, no busy");
+				}
+			} else {
+				TRACE(6, "awaited stray interrupt");
+			}
+		}
+		hide_interrupt = 0;
+	}
+	/*    Handle sleep code.
+	 */
+	if (!hide_interrupt) {
+		++interrupt_seen;
+		if (wait_intr) {
+			wake_up_interruptible(&wait_intr);
+		}
+	} else {
+		TRACEx1(5, "hiding interrupt while %s", wait_intr ? "waiting" : "active");
+	}
+	t0 = timediff(t0, timestamp());
+	if (t0 >= 1000) {	/* only tell us about long calls */
+		TRACEx1(7, "isr() duration: %5d usec", t0);
+	}
+	*fdc.hook = fdc_isr;	/* hook our handler into the fdc code again */
+	TRACE_EXIT;
+	--isr_active;
+}
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/fdc-isr.h linux/drivers/char/ftape/fdc-isr.h
--- v1.3.71/linux/drivers/char/ftape/fdc-isr.h	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/fdc-isr.h	Wed Mar  6 15:07:19 1996
@@ -0,0 +1,56 @@
+#ifndef _FDC_ISR_H
+#define _FDC_ISR_H
+
+/*
+ * Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ $Source: /home/bas/distr/ftape-2.03b/RCS/fdc-isr.h,v $
+ $Author: bas $
+ *
+ $Revision: 1.8 $
+ $Date: 1995/04/22 07:30:15 $
+ $State: Beta $
+ *
+ *      This file contains the low level functions
+ *      that communicate with the floppy disk controller,
+ *      for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+/*
+ *      fdc-isr.c defined public variables
+ */
+extern volatile int expected_stray_interrupts;	/* masks stray interrupts */
+extern volatile int seek_completed;	/* flag set by isr */
+extern volatile int interrupt_seen;	/* flag set by isr */
+extern volatile int expect_stray_interrupt;
+
+/*
+ *      fdc-io.c defined public functions
+ */
+extern void fdc_isr(void);
+
+/*
+ *      A kernel hook that steals one interrupt from the floppy
+ *      driver (Should be fixed when the new fdc driver gets ready)
+ *      See the linux kernel source files:
+ *          drivers/block/floppy.c & drivers/block/blk.h
+ *      for the details.
+ */
+extern void (*do_floppy) (void);
+
+#endif
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/ftape-bsm.c linux/drivers/char/ftape/ftape-bsm.c
--- v1.3.71/linux/drivers/char/ftape/ftape-bsm.c	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/ftape-bsm.c	Wed Mar  6 15:07:19 1996
@@ -0,0 +1,429 @@
+/*
+ *      Copyright (C) 1994-1995 Bas Laarhoven.
+
+ 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.
+
+ $Source: /home/bas/distr/ftape-2.03b/RCS/ftape-bsm.c,v $
+ $Author: bas $
+ *
+ $Revision: 1.7 $
+ $Date: 1995/04/30 13:15:14 $
+ $State: Beta $
+ *
+ *      This file contains the bad-sector map handling code for
+ *      the QIC-117 floppy tape driver for Linux.
+ *      QIC-40, QIC-80, QIC-3010 and QIC-3020 maps are implemented.
+ */
+
+#include <linux/module.h>
+#include <linux/ftape.h>
+#include <linux/string.h>
+
+#include "tracing.h"
+#include "ftape-bsm.h"
+#include "ftape-ctl.h"
+#include "ftape-rw.h"
+
+
+/*      Global vars.
+ */
+int bad_sector_map_changed = 0;
+
+/*      Local vars.
+ */
+static byte bad_sector_map[BAD_SECTOR_MAP_SIZE];
+typedef enum {
+	forward, backward
+} mode_type;
+
+#if 0
+/*  fix_tape converts a normal QIC-80 tape into a 'wide' tape.
+ *  For testing purposes only !
+ */
+void fix_tape(byte * buffer)
+{
+	static byte list[BAD_SECTOR_MAP_SIZE];
+	unsigned long *src_ptr = (unsigned long *) list;
+	byte *dst_ptr = bad_sector_map;
+	unsigned long map;
+	unsigned sector = 1;
+	int i;
+
+	memcpy(list, bad_sector_map, sizeof(list));
+	memset(bad_sector_map, 0, sizeof(bad_sector_map));
+	while ((byte *) src_ptr - list < sizeof(list)) {
+		map = *src_ptr++;
+		if (map == EMPTY_SEGMENT) {
+			*(unsigned long *) dst_ptr = 0x800000 + sector;
+			dst_ptr += 3;
+			sector += SECTORS_PER_SEGMENT;
+		} else {
+			for (i = 0; i < SECTORS_PER_SEGMENT; ++i) {
+				if (map & 1) {
+					*(unsigned long *) dst_ptr = sector;
+					dst_ptr += 3;
+				}
+				map >>= 1;
+				++sector;
+			}
+		}
+	}
+	bad_sector_map_changed = 1;
+	*(buffer + 4) = 4;	/* put new format code */
+	format_code = 4;
+}
+
+#endif
+
+byte *
+ find_end_of_bsm_list(byte * ptr, byte * limit)
+{
+	while (ptr + 2 < limit) {
+		if (ptr[0] || ptr[1] || ptr[2]) {
+			ptr += 3;
+		} else {
+			return ptr;
+		}
+	}
+	return NULL;
+}
+
+void store_bad_sector_map(byte * buffer)
+{
+	TRACE_FUN(8, "store_bad_sector_map");
+	size_t count;
+	size_t offset;
+
+	/*  Store the bad sector map in buffer.
+	 */
+	if (format_code == 4) {
+		offset = 256;
+		count = sizeof(bad_sector_map);
+	} else {
+		offset = 2 * SECTOR_SIZE;	/* skip failed sector log */
+		count = sizeof(bad_sector_map) - (offset - 256);
+	}
+	memcpy(buffer + offset, bad_sector_map, count);
+	TRACE_EXIT;
+}
+
+void put_sector(byte ** ptr, unsigned long sector)
+{
+	*(*ptr)++ = sector & 0xff;
+	sector >>= 8;
+	*(*ptr)++ = sector & 0xff;
+	sector >>= 8;
+	*(*ptr)++ = sector & 0xff;
+}
+
+unsigned long get_sector(byte ** ptr, mode_type mode)
+{
+	unsigned long sector;
+
+	if (mode == forward) {
+		sector = *(*ptr)++;
+		sector += *(*ptr)++ << 8;
+		sector += *(*ptr)++ << 16;
+	} else {
+		sector = *--(*ptr) << 16;
+		sector += *--(*ptr) << 8;
+		sector += *--(*ptr);
+	}
+	return sector;
+}
+
+void extract_bad_sector_map(byte * buffer)
+{
+	TRACE_FUN(8, "extract_bad_sector_map");
+
+	/*  Fill the bad sector map with the contents of buffer.
+	 */
+	if (format_code == 4) {
+		/* QIC-3010/3020 and wide QIC-80 tapes no longer have a failed
+		 * sector log but use this area to extend the bad sector map.
+		 */
+		memcpy(bad_sector_map, buffer + 256, sizeof(bad_sector_map));
+	} else {
+		/* non-wide QIC-80 tapes have a failed sector log area that
+		 * mustn't be included in the bad sector map.
+		 */
+		memcpy(bad_sector_map, buffer + 256 + FAILED_SECTOR_LOG_SIZE,
+		       sizeof(bad_sector_map) - FAILED_SECTOR_LOG_SIZE);
+	}
+#if 0
+	/* for testing of bad sector handling at end of tape
+	 */
+	((unsigned long *) bad_sector_map)[segments_per_track * tracks_per_tape - 3] = 0x000003e0;
+	((unsigned long *) bad_sector_map)[segments_per_track * tracks_per_tape - 2] = 0xff3fffff;
+	((unsigned long *) bad_sector_map)[segments_per_track * tracks_per_tape - 1] = 0xffffe000;
+#endif
+#if 0
+	/*  Enable to test bad sector handling
+	 */
+	((unsigned long *) bad_sector_map)[30] = 0xfffffffe;
+	((unsigned long *) bad_sector_map)[32] = 0x7fffffff;
+	((unsigned long *) bad_sector_map)[34] = 0xfffeffff;
+	((unsigned long *) bad_sector_map)[36] = 0x55555555;
+	((unsigned long *) bad_sector_map)[38] = 0xffffffff;
+	((unsigned long *) bad_sector_map)[50] = 0xffff0000;
+	((unsigned long *) bad_sector_map)[51] = 0xffffffff;
+	((unsigned long *) bad_sector_map)[52] = 0xffffffff;
+	((unsigned long *) bad_sector_map)[53] = 0x0000ffff;
+#endif
+#if 0
+	/*  Enable when testing multiple volume tar dumps.
+	 */
+	for (i = first_data_segment; i <= ftape_last_segment.id - 7; ++i) {
+		((unsigned long *) bad_sector_map)[i] = EMPTY_SEGMENT;
+	}
+#endif
+#if 0
+	/*  Enable when testing bit positions in *_error_map
+	 */
+	for (i = first_data_segment; i <= ftape_last_segment.id; ++i) {
+		((unsigned long *) bad_sector_map)[i] |= 0x00ff00ff;
+	}
+#endif
+	if (tracing > 2) {
+		unsigned int map;
+		int good_sectors = 0;
+		int bad_sectors;
+		unsigned int total_bad = 0;
+		int i;
+
+		if (format_code == 4 || format_code == 3) {
+			byte *ptr = bad_sector_map;
+			unsigned sector;
+
+			do {
+				sector = get_sector(&ptr, forward);
+				if (sector != 0) {
+					if (format_code == 4 && sector & 0x800000) {
+						total_bad += SECTORS_PER_SEGMENT - 3;
+						TRACEx1(6, "bad segment at sector: %6d", sector & 0x7fffff);
+					} else {
+						++total_bad;
+						TRACEx1(6, "bad sector: %6d", sector);
+					}
+				}
+			} while (sector != 0);
+			/*  Display end-of-file marks
+			 */
+			do {
+				sector = *((unsigned short *) ptr)++;
+				if (sector) {
+					TRACEx2(4, "eof mark: %4d/%2d", sector,
+					    *((unsigned short *) ptr)++);
+				}
+			} while (sector);
+		} else {
+			for (i = first_data_segment;
+			 i < segments_per_track * tracks_per_tape; ++i) {
+				map = ((unsigned long *) bad_sector_map)[i];
+				bad_sectors = count_ones(map);
+				if (bad_sectors > 0) {
+					TRACEx2(6, "bsm for segment %4d: 0x%08x", i, map);
+					if (bad_sectors > SECTORS_PER_SEGMENT - 3) {
+						bad_sectors = SECTORS_PER_SEGMENT - 3;
+					}
+					total_bad += bad_sectors;
+				}
+			}
+		}
+		good_sectors = ((segments_per_track * tracks_per_tape - first_data_segment)
+				* (SECTORS_PER_SEGMENT - 3)) - total_bad;
+		TRACEx1(3, "%d Kb usable on this tape",
+			good_sectors - ftape_last_segment.free);
+		if (total_bad == 0) {
+			TRACE(1, "WARNING: this tape has no bad blocks registered !");
+		} else {
+			TRACEx1(2, "%d bad sectors", total_bad);
+		}
+	}
+	TRACE_EXIT;
+}
+
+unsigned long cvt2map(int sector)
+{
+	return 1 << (((sector & 0x7fffff) - 1) % SECTORS_PER_SEGMENT);
+}
+
+int cvt2segment(int sector)
+{
+	return ((sector & 0x7fffff) - 1) / SECTORS_PER_SEGMENT;
+}
+
+int forward_seek_entry(int segment_id, byte ** ptr, unsigned long *map)
+{
+	byte *tmp_ptr;
+	unsigned long sector;
+	int segment;
+	int count;
+
+	do {
+		sector = get_sector(ptr, forward);
+		segment = cvt2segment(sector);
+	} while (sector != 0 && segment < segment_id);
+	tmp_ptr = *ptr - 3;	/* point to first sector >= segment_id */
+	/*  Get all sectors in segment_id
+	 */
+	if (format_code == 4 && (sector & 0x800000) && segment == segment_id) {
+		*map = EMPTY_SEGMENT;
+		count = 32;
+	} else {
+		*map = 0;
+		count = 0;
+		while (sector != 0 && segment == segment_id) {
+			*map |= cvt2map(sector);
+			sector = get_sector(ptr, forward);
+			segment = cvt2segment(sector);
+			++count;
+		}
+	}
+	*ptr = tmp_ptr;
+	return count;
+}
+
+int backwards_seek_entry(int segment_id, byte ** ptr, unsigned long *map)
+{
+	unsigned long sector;
+	int segment;
+	int count;
+
+	*map = 0;
+	if (*ptr > bad_sector_map) {
+		do {
+			sector = get_sector(ptr, backward);
+			segment = cvt2segment(sector);
+		} while (*ptr > bad_sector_map && segment > segment_id);
+		count = 0;
+		if (segment > segment_id) {
+			/*  at start of list, no entry found */
+		} else if (segment < segment_id) {
+			/*  before smaller entry, adjust for overshoot */
+			*ptr += 3;
+		} else {
+			/*  get all sectors in segment_id */
+			if (format_code == 4 && (sector & 0x800000)) {
+				*map = EMPTY_SEGMENT;
+				count = 32;
+			} else {
+				do {
+					*map |= cvt2map(sector);
+					++count;
+					if (*ptr <= bad_sector_map) {
+						break;
+					}
+					sector = get_sector(ptr, backward);
+					segment = cvt2segment(sector);
+				} while (segment == segment_id);
+				if (segment < segment_id) {
+					*ptr += 3;
+				}
+			}
+		}
+	} else {
+		count = 0;
+	}
+	return count;
+}
+
+void put_bad_sector_entry(int segment_id, unsigned long new_map)
+{
+	byte *ptr = bad_sector_map;
+	int count;
+	int new_count;
+	unsigned long map;
+
+	if (format_code == 3 || format_code == 4) {
+		count = forward_seek_entry(segment_id, &ptr, &map);
+		new_count = count_ones(new_map);
+		/*  If format code == 4 put empty segment instead of 32 bad sectors.
+		 */
+		if (new_count == 32 && format_code == 4) {
+			new_count = 1;
+		}
+		if (count != new_count) {
+			/*  insert (or delete if < 0) new_count - count entries.
+			 *  Move trailing part of list including terminating 0.
+			 */
+			byte *hi_ptr = ptr;
+
+			do {
+			} while (get_sector(&hi_ptr, forward) != 0);
+			memmove(ptr + new_count, ptr + count, hi_ptr - (ptr + count));
+		}
+		if (new_count == 1 && new_map == EMPTY_SEGMENT) {
+			put_sector(&ptr, 0x800001 + segment_id * SECTORS_PER_SEGMENT);
+		} else {
+			int i = 0;
+
+			while (new_map) {
+				if (new_map & 1) {
+					put_sector(&ptr, 1 + segment_id * SECTORS_PER_SEGMENT + i);
+				}
+				++i;
+				new_map >>= 1;
+			}
+		}
+	} else {
+		((unsigned long *) bad_sector_map)[segment_id] = new_map;
+	}
+	bad_sector_map_changed = 1;
+}
+
+unsigned long get_bad_sector_entry(int segment_id)
+{
+	TRACE_FUN(8, "get_bad_sector_entry");
+	static unsigned long map = 0;
+
+	if (used_header_segment == -1) {
+		/*  When reading header segment we'll need a blank map.
+		 */
+		map = 0;
+	} else if (format_code == 3 || format_code == 4) {
+		/*  Invariants:
+		 *    map - mask value returned on last call.
+		 *    ptr - points to first sector greater or equal to
+		 *          first sector in last_referenced segment.
+		 *    last_referenced - segment id used in the last call,
+		 *                      sector and map belong to this id.
+		 *  This code is designed for sequential access and retries.
+		 *  For true random access it may have to be redesigned.
+		 */
+		static int last_reference = -1;
+		static byte *ptr = bad_sector_map;
+
+		if (segment_id > last_reference) {
+			/*  Skip all sectors before segment_id
+			 */
+			forward_seek_entry(segment_id, &ptr, &map);
+		} else if (segment_id < last_reference) {
+			/*  Skip backwards until begin of buffer or first sector in segment_id
+			 */
+			backwards_seek_entry(segment_id, &ptr, &map);
+		}		/* segment_id == last_reference : keep map */
+		last_reference = segment_id;
+	} else {
+		map = ((unsigned long *) bad_sector_map)[segment_id];
+	}
+	TRACE_EXIT;
+	return map;
+}
+
+void ftape_init_bsm(void)
+{
+	memset(bad_sector_map, 0, sizeof(bad_sector_map));
+}
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/ftape-bsm.h linux/drivers/char/ftape/ftape-bsm.h
--- v1.3.71/linux/drivers/char/ftape/ftape-bsm.h	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/ftape-bsm.h	Wed Mar  6 15:07:19 1996
@@ -0,0 +1,62 @@
+#ifndef _FTAPE_BSM_H
+#define _FTAPE_BSM_H
+
+/*
+ * Copyright (C) 1994-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ $Source: /home/bas/distr/ftape-2.03b/RCS/ftape-bsm.h,v $
+ $Author: bas $
+ *
+ $Revision: 1.5 $
+ $Date: 1995/04/22 07:30:15 $
+ $State: Beta $
+ *
+ *      This file contains definitions for the bad sector map handling
+ *      routines for the QIC-117 floppy-tape driver for Linux.
+ */
+
+#define EMPTY_SEGMENT           (0xffffffff)
+#define FAKE_SEGMENT            (0xfffffffe)
+
+/*  failed sector log size (only used if format code != 4).
+ */
+#define FAILED_SECTOR_LOG_SIZE  (2 * SECTOR_SIZE - 256)
+
+/*  maximum (format code 4) bad sector map size (bytes).
+ */
+#define BAD_SECTOR_MAP_SIZE     (29 * SECTOR_SIZE - 256)
+
+/*
+ *      ftape-io.c defined global vars.
+ */
+extern bad_sector_map_changed;
+
+/*
+ *      ftape-io.c defined global functions.
+ */
+extern void update_bad_sector_map(byte * buffer);
+extern void store_bad_sector_map(byte * buffer);
+extern void extract_bad_sector_map(byte * buffer);
+extern unsigned long get_bad_sector_entry(int segment_id);
+extern void put_bad_sector_entry(int segment_id, unsigned long mask);
+extern void add_segment_to_bad_sector_map(unsigned segment);
+extern void clear_bad_sector_map(int count);
+extern byte *find_end_of_bsm_list(byte * ptr, byte * limit);
+extern void ftape_init_bsm(void);
+
+#endif
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/ftape-ctl.c linux/drivers/char/ftape/ftape-ctl.c
--- v1.3.71/linux/drivers/char/ftape/ftape-ctl.c	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/ftape-ctl.c	Wed Mar  6 15:07:19 1996
@@ -0,0 +1,884 @@
+/*
+ *      Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ *      This file contains the non-read/write ftape functions
+ *      for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/ftape.h>
+#include <asm/segment.h>
+
+#include "tracing.h"
+#include "ftape-eof.h"
+#include "ftape-io.h"
+#include "ftape-ctl.h"
+#include "ftape-write.h"
+#include "ftape-read.h"
+#include "ftape-rw.h"
+#include "qic117.h"
+#include "ftape-bsm.h"
+
+
+/*      Global vars.
+ */
+int segments_per_track = 102;
+int segments_per_head = 1020;
+int segments_per_cylinder = 4;
+int tracks_per_tape = 20;
+int ftape_failure = 1;
+int ftape_seg_pos = 0;
+int first_data_segment = -1;
+int ftape_state = idle;		/* use buffer_state_enum */
+history_record history;
+int write_protected;
+int ftape_offline = 0;
+int no_tape = 1;
+int formatted = 0;
+int ftape_data_rate = 0;
+int going_offline = 0;
+int read_only = 0;
+
+/*      Local vars.
+ */
+static int ftape_last_error = 0;
+static const vendor_struct vendors[] = QIC117_VENDORS;
+static const wakeup_method methods[] = WAKEUP_METHODS;
+static int init_drive_needed = 1;
+
+
+static int ftape_not_operational(int status)
+{
+	/* return true if status indicates tape can not be used.
+	 */
+	return ((status ^ QIC_STATUS_CARTRIDGE_PRESENT) &
+		(QIC_STATUS_ERROR |
+		 QIC_STATUS_CARTRIDGE_PRESENT |
+		 QIC_STATUS_NEW_CARTRIDGE));
+}
+
+int ftape_seek_to_eot(void)
+{
+	TRACE_FUN(8, "ftape_seek_to_eot");
+	int result;
+	int status;
+
+	result = ftape_ready_wait(timeout.pause, &status);
+	while ((status & QIC_STATUS_AT_EOT) == 0) {
+		if (result < 0) {
+			TRACE(1, "failed");
+			TRACE_EXIT;
+			return result;
+		}
+		if (ftape_not_operational(status)) {
+			TRACE_EXIT;
+			return -EIO;
+		}
+		result = ftape_command_wait(QIC_PHYSICAL_FORWARD,
+					    timeout.rewind, &status);
+	}
+	TRACE_EXIT;
+	return 0;
+}
+
+int ftape_seek_to_bot(void)
+{
+	TRACE_FUN(8, "ftape_seek_to_bot");
+	int result;
+	int status;
+
+	result = ftape_ready_wait(timeout.pause, &status);
+	while ((status & QIC_STATUS_AT_BOT) == 0) {
+		if (result < 0) {
+			TRACE(1, "failed");
+			TRACE_EXIT;
+			return result;
+		}
+		if (ftape_not_operational(status)) {
+			TRACE_EXIT;
+			return -EIO;
+		}
+		result = ftape_command_wait(QIC_PHYSICAL_REVERSE,
+					    timeout.rewind, &status);
+	}
+	TRACE_EXIT;
+	return 0;
+}
+
+void ftape_reset_position(void)
+{
+	ftape_seg_pos = first_data_segment;
+	reset_eof_list();
+}
+
+int ftape_new_cartridge(void)
+{
+	location.track = -1;	/* force seek on first access */
+	first_data_segment = -1;	/* unknown */
+	ftape_zap_read_buffers();
+	ftape_zap_write_buffers();
+	ftape_reset_position();
+	return 0;
+}
+
+int ftape_abort_operation(void)
+{
+	TRACE_FUN(5, "ftape_abort_operation");
+	int result = 0;
+	int i;
+	int status;
+
+	if (runner_status == running) {
+		TRACE(5, "aborting runner, waiting");
+		runner_status = do_abort;
+		/* set timeout so that the tape will run to logical EOT
+		 * if we missed the last sector and there are no queue pulses.
+		 */
+		result = ftape_dumb_stop();
+		if (result == 0) {
+			runner_status = idle;
+		}
+	}
+	if (runner_status != idle) {
+		if (runner_status == do_abort) {
+			TRACE(5, "forcing runner abort");
+		}
+		TRACE(5, "stopping tape");
+		result = ftape_command_wait(QIC_STOP_TAPE, timeout.stop, &status);
+		location.known = 0;
+		runner_status = idle;
+	}
+	for (i = 0; i < NR_BUFFERS; ++i) {
+		buffer[i].status = waiting;
+	}
+	head = tail = 0;
+	TRACE_EXIT;
+	return result;
+}
+
+int lookup_vendor_id(int vendor_id)
+{
+	int i = 0;
+
+	while (vendors[i].vendor_id != vendor_id) {
+		if (++i >= NR_ITEMS(vendors)) {
+			return -1;
+		}
+	}
+	return i;
+}
+
+void ftape_detach_drive(void)
+{
+	TRACE_FUN(8, "ftape_detach_drive");
+
+	TRACE(5, "disabling tape drive and fdc");
+	ftape_put_drive_to_sleep(drive_type);
+	fdc_catch_stray_interrupts(1);	/* one always comes */
+	fdc_disable();
+	fdc_release_irq_and_dma();
+	TRACE_EXIT;
+}
+
+static void clear_history(void)
+{
+	history.used = 0;
+	history.id_am_errors =
+	    history.id_crc_errors =
+	    history.data_am_errors =
+	    history.data_crc_errors =
+	    history.overrun_errors =
+	    history.no_data_errors =
+	    history.retries =
+	    history.crc_errors =
+	    history.crc_failures =
+	    history.ecc_failures =
+	    history.corrected =
+	    history.defects =
+	    history.rewinds = 0;
+}
+
+int ftape_activate_drive(vendor_struct * drive_type)
+{
+	TRACE_FUN(5, "ftape_activate_drive");
+	int result = 0;
+
+	/* If we already know the drive type, wake it up.
+	 * Else try to find out what kind of drive is attached.
+	 */
+	if (drive_type->wake_up != unknown_wake_up) {
+		TRACE(5, "enabling tape drive and fdc");
+		result = ftape_wakeup_drive(drive_type->wake_up);
+		if (result < 0) {
+			TRACE(1, "known wakeup method failed");
+		}
+	} else {
+		int old_tracing = tracing;
+		wake_up_types method;
+
+		/*  Try to awaken the drive using all known methods.
+		 *  Lower tracing for a while.
+		 */
+		if (tracing <= 4) {
+			tracing = 0;
+		}
+		for (method = no_wake_up; method < NR_ITEMS(methods); ++method) {
+			drive_type->wake_up = method;
+#if 0
+			/*  Test setup for dual drive configuration in dodo.
+			 *  /dev/rft2 uses mountain wakeup only -> Archive QIC-80
+			 *  /dev/rft3 uses colorado wakeup only -> Jumbo QIC-40
+			 *  Other systems will use the normal scheme.
+			 */
+			if ((FTAPE_UNIT < 2) ||
+			(FTAPE_UNIT == 2 && method == wake_up_mountain) ||
+			(FTAPE_UNIT == 3 && method == wake_up_colorado)) {
+				result = ftape_wakeup_drive(drive_type->wake_up);
+			} else {
+				result = -EIO;
+			}
+#else
+			result = ftape_wakeup_drive(drive_type->wake_up);
+#endif
+			if (result >= 0) {
+				int tracing = old_tracing;	/* fool TRACE */
+				TRACEx1(2, "drive wakeup method: %s",
+				      methods[drive_type->wake_up].name);
+				break;
+			}
+		}
+		tracing = old_tracing;
+		if (method >= NR_ITEMS(methods)) {
+			/* no response at all, cannot open this drive */
+			drive_type->wake_up = unknown_wake_up;
+			TRACE(1, "no tape drive found !");
+			tracing = old_tracing;
+			result = -ENODEV;
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_get_drive_status(int *new_tape, int *no_tape, int *wp_tape)
+{
+	TRACE_FUN(5, "ftape_get_drive_status");
+	int result;
+	int status;
+
+	*no_tape =
+	    *wp_tape = 0;
+	/*    Tape drive is activated now.
+	 *    First clear error status if present.
+	 */
+	do {
+		result = ftape_ready_wait(timeout.reset, &status);
+		if (result < 0) {
+			if (result == -ETIME) {
+				TRACE(1, "ftape_ready_wait timeout");
+			} else if (result == -EINTR) {
+				TRACE(1, "ftape_ready_wait aborted");
+			} else {
+				TRACE(1, "ftape_ready_wait failed");
+			}
+			result = -EIO;
+			break;
+		}
+		/*  Clear error condition (drive is ready !)
+		 */
+		if (status & QIC_STATUS_ERROR) {
+			int error;
+			int command;
+
+			TRACE(1, "error status set");
+			result = ftape_report_error(&error, &command, 1);
+			if (result < 0) {
+				TRACEi(1, "report_error_code failed:", result);
+				ftape_reset_drive();	/* hope it's working next time */
+				init_drive_needed = 1;
+				result = -EIO;
+				break;
+			} else if (error != 0) {
+				TRACEi(4, "error code   :", error);
+				TRACEi(4, "error command:", command);
+			}
+		}
+		if (status & QIC_STATUS_NEW_CARTRIDGE) {
+			int error;
+			int command;
+			int old_tracing = tracing;
+
+			/*  Undocumented feature: Must clear (not present!) error 
+			 *  here or we'll fail later.
+			 */
+			tracing = 0;
+			ftape_report_error(&error, &command, 1);
+			tracing = old_tracing;
+			TRACE(3, "status: new cartridge");
+			*new_tape = 1;
+		}
+	} while (status & QIC_STATUS_ERROR);
+
+	*no_tape = !(status & QIC_STATUS_CARTRIDGE_PRESENT);
+	*wp_tape = (status & QIC_STATUS_WRITE_PROTECT);
+	if (*no_tape) {
+		TRACE(1, "no cartridge present");
+	} else {
+		if (*wp_tape) {
+			TRACE(2, "Write protected cartridge");
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+void ftape_log_vendor_id(void)
+{
+	TRACE_FUN(5, "ftape_log_vendor_id");
+	int vendor_index;
+
+	ftape_report_vendor_id(&drive_type.vendor_id);
+	vendor_index = lookup_vendor_id(drive_type.vendor_id);
+	if (drive_type.vendor_id == UNKNOWN_VENDOR &&
+	    drive_type.wake_up == wake_up_colorado) {
+		vendor_index = 0;
+		drive_type.vendor_id = 0;	/* hack to get rid of all this mail */
+	}
+	if (vendor_index < 0) {
+		/* Unknown vendor id, first time opening device.
+		 * The drive_type remains set to type found at wakeup time, this
+		 * will probably keep the driver operating for this new vendor.
+		 */
+		TRACE(-1, "============ unknown vendor id ===========");
+		TRACE(-1, "A new, yet unsupported tape drive is found");
+		TRACE(-1, "Please report the following values:");
+		TRACEx1(-1, "   Vendor id     : 0x%04x", drive_type.vendor_id);
+		TRACEx1(-1, "   Wakeup method : %s", methods[drive_type.wake_up].name);
+		TRACE(-1, "And a description of your tape drive to:");
+		TRACE(-1, "Kai Harrekilde-Petersen <khp@pip.dknet.dk>");
+		TRACE(-1, "==========================================");
+		drive_type.speed = 500;		/* deci-ips: very safe value */
+	} else {
+		drive_type.name = vendors[vendor_index].name;
+		drive_type.speed = vendors[vendor_index].speed;
+		TRACEx1(3, "tape drive type: %s", drive_type.name);
+		/* scan all methods for this vendor_id in table */
+		while (drive_type.wake_up != vendors[vendor_index].wake_up) {
+			if (vendor_index < NR_ITEMS(vendors) - 1 &&
+			    vendors[vendor_index + 1].vendor_id == drive_type.vendor_id) {
+				++vendor_index;
+			} else {
+				break;
+			}
+		}
+		if (drive_type.wake_up != vendors[vendor_index].wake_up) {
+			TRACE(-1, "==========================================");
+			TRACE(-1, "wakeup type mismatch:");
+			TRACEx2(-1, "found: %s, expected: %s",
+				methods[drive_type.wake_up].name,
+			    methods[vendors[vendor_index].wake_up].name);
+			TRACE(-1, "please report this to <khp@pip.dknet.dk>");
+			TRACE(-1, "==========================================");
+		}
+	}
+	TRACE_EXIT;
+}
+
+void ftape_calc_timeouts(void)
+{
+	TRACE_FUN(8, "ftape_calc_timeouts");
+	int speed;		/* deci-ips ! */
+	int length;
+
+	/*                           tape transport speed
+	 *  data rate:        QIC-40   QIC-80   QIC-3010 QIC-3020
+	 *
+	 *    250 Kbps        25 ips     n/a      n/a      n/a
+	 *    500 Kbps        50 ips   34 ips   22.6 ips   n/a
+	 *      1 Mbps          n/a    68 ips   45.2 ips 22.6 ips
+	 *      2 Mbps          n/a      n/a      n/a    45.2 ips
+	 *
+	 *  fast tape transport speed is at least 68 ips.
+	 */
+	switch (qic_std) {
+	case QIC_TAPE_QIC40:
+		speed = (ftape_data_rate == 3) ? 250 : 500;
+		break;
+	case QIC_TAPE_QIC80:
+		speed = (ftape_data_rate == 2) ? 340 : 680;
+		break;
+	case QIC_TAPE_QIC3010:
+		speed = (ftape_data_rate == 2) ? 226 : 452;
+		break;
+	case QIC_TAPE_QIC3020:
+		speed = (ftape_data_rate == 1) ? 226 : 452;
+		break;
+	default:
+		TRACE(-1, "Unknown qic_std (bug) ?");
+		speed = 500;
+		break;
+	}
+	if (tape_len <= 0) {
+		/*  Handle unknown length tapes as 1100 ft ones (worst case)
+		 */
+		TRACE(1, "Unknown tape length, using worst case timing values!");
+		length = 1100;
+	} else {
+		length = tape_len;
+	}
+	if (drive_type.speed == 0) {
+		unsigned long t0;
+		int dt;
+
+		ftape_seek_to_bot();
+		t0 = jiffies;
+		ftape_seek_to_eot();
+		ftape_seek_to_bot();
+		dt = (int) ((jiffies - t0) * MSPT);
+		drive_type.speed = (2 * 12 * length * 1000) / dt;
+		TRACE(-1, "==========================================");
+		TRACEx1(-1, "drive : %s", drive_type.name);
+		TRACEx2(-1, "delta time = %d, length = %d", dt, length);
+		TRACEx1(-1, "has max tape speed of %d ips", drive_type.speed);
+		TRACE(-1, "please report this to <khp@pip.dknet.dk>");
+		TRACE(-1, "==========================================");
+	}
+	/*  time to go from bot to eot at normal speed (data rate):
+	 *  time = (1+delta) * length (ft) * 12 (inch/ft) / speed (ips)
+	 *  delta = 10 % for seek speed, 20 % for rewind speed.
+	 */
+	timeout.seek = (length * 132 * SECOND) / speed;
+	timeout.rewind = (length * 144 * SECOND) / (10 * drive_type.speed);
+	timeout.reset = 20 * SECOND + timeout.rewind;
+	TRACEx2(4, "speed = %d, length = %d", speed, length);
+	TRACEx1(4, "seek timeout: %d sec", (timeout.seek + 500) / 1000);
+	TRACEx1(4, "rewind timeout: %d sec", (timeout.rewind + 500) / 1000);
+	TRACE_EXIT;
+}
+
+int ftape_init_drive(int *formatted)
+{
+	TRACE_FUN(5, "ftape_init_drive");
+	int result = 0;
+	int status;
+
+	result = ftape_report_raw_drive_status(&status);
+	if (result >= 0 && (status & QIC_STATUS_CARTRIDGE_PRESENT)) {
+		if (!(status & QIC_STATUS_AT_BOT)) {
+			/*  Antique drives will get here after a soft reset,
+			 *  modern ones only if the driver is loaded when the
+			 *  tape wasn't rewound properly.
+			 */
+			ftape_seek_to_bot();
+		}
+		if (!(status & QIC_STATUS_REFERENCED)) {
+			TRACE(5, "starting seek_load_point");
+			result = ftape_command_wait(QIC_SEEK_LOAD_POINT,
+						 timeout.reset, &status);
+			if (result < 0) {
+				TRACE(1, "seek_load_point failed (command)");
+			}
+		}
+	}
+	if (result >= 0) {
+		int rate;
+
+		*formatted = (status & QIC_STATUS_REFERENCED);
+		if (!*formatted) {
+			TRACE(1, "Warning: tape is not formatted !");
+		}
+		/*  Select highest rate supported by both fdc and drive.
+		 *  Start with highest rate supported by the fdc.
+		 */
+		if (fdc.type >= i82078_1)
+			rate = 0;
+		else if (fdc.type >= i82077)
+			rate = 1;
+		else
+			rate = 2;
+		do {
+			result = ftape_set_data_rate(rate);
+			if (result >= 0) {
+				ftape_calc_timeouts();
+				break;
+			}
+			++rate;
+		} while (rate < 4);
+		if (result < 0) {
+			result = -EIO;
+		}
+	}
+	if (result >= 0) {
+		/* Tape should be at bot if new cartridge ! */
+		ftape_new_cartridge();
+	}
+	init_drive_needed = 0;
+	TRACE_EXIT;
+	return result;
+}
+
+/*      OPEN routine called by kernel-interface code
+ */
+int _ftape_open(void)
+{
+	TRACE_FUN(8, "_ftape_open");
+	int result;
+	static int new_tape = 1;
+
+	result = fdc_init();
+	if (result >= 0) {
+		result = ftape_activate_drive(&drive_type);
+		if (result < 0) {
+			fdc_disable();
+			fdc_release_irq_and_dma();
+
+		} else {
+			result = ftape_get_drive_status(&new_tape, &no_tape, &write_protected);
+			if (result < 0) {
+				ftape_detach_drive();
+			} else {
+				if (drive_type.vendor_id == UNKNOWN_VENDOR) {
+					ftape_log_vendor_id();
+				}
+				if (no_tape) {
+					ftape_offline = 1;
+				} else if (new_tape) {
+					ftape_offline = 0;
+					init_drive_needed = 1;
+					read_only = 0;	/* enable writes again */
+				}
+				if (!ftape_offline && init_drive_needed) {
+					result = ftape_init_drive(&formatted);
+					if (result >= 0) {
+						new_tape = 0;
+					} else {
+						ftape_detach_drive();
+					}
+				}
+				if (result >= 0) {
+					clear_history();
+				}
+			}
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+/*      RELEASE routine called by kernel-interface code
+ */
+int _ftape_close(void)
+{
+	TRACE_FUN(8, "_ftape_close");
+	int result = 0;
+	int last_segment = 0;
+
+	if (!ftape_offline) {
+		result = ftape_flush_buffers();
+		last_segment = ftape_seg_pos - 1;
+		if (!(ftape_unit & FTAPE_NO_REWIND)) {
+			if (result >= 0) {
+				result = ftape_update_header_segments(NULL, 1);
+				if (result < 0) {
+					TRACE(1, "error: update of header segments failed");
+				}
+			} else {
+				TRACE(1, "error: unable to update header segments");
+			}
+		}
+		ftape_abort_operation();
+		if (!(ftape_unit & FTAPE_NO_REWIND)) {
+			if (!no_tape) {
+				TRACE(5, "rewinding tape");
+				result = ftape_seek_to_bot();
+			}
+			ftape_reset_position();
+			ftape_zap_read_buffers();
+			ftape_zap_write_buffers();
+		}
+	}
+	ftape_detach_drive();
+	fdc_uninit();
+	if (history.used) {
+		TRACE(3, "== Non-fatal errors this run: ==");
+		TRACE(3, "fdc isr statistics:");
+		TRACEi(3, " id_am_errors     :", history.id_am_errors);
+		TRACEi(3, " id_crc_errors    :", history.id_crc_errors);
+		TRACEi(3, " data_am_errors   :", history.data_am_errors);
+		TRACEi(3, " data_crc_errors  :", history.data_crc_errors);
+		TRACEi(3, " overrun_errors   :", history.overrun_errors);
+		TRACEi(3, " no_data_errors   :", history.no_data_errors);
+		TRACEi(3, " retries          :", history.retries);
+		if (history.used & 1) {
+			TRACE(3, "ecc statistics:");
+			TRACEi(3, " crc_errors       :", history.crc_errors);
+			TRACEi(3, " crc_failures     :", history.crc_failures);
+			TRACEi(3, " ecc_failures     :", history.ecc_failures);
+			TRACEi(3, " sectors corrected:", history.corrected);
+		}
+		TRACEx2(3, "media defects     : %d%s", history.defects,
+			history.defects ? " !!!" : "");
+		TRACEi(3, "repositions       :", history.rewinds);
+		TRACEi(3, "last segment      :", last_segment);
+	}
+	if (going_offline) {
+		going_offline = 0;
+		ftape_offline = 1;
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+/*      IOCTL routine called by kernel-interface code
+ */
+int _ftape_ioctl(unsigned int command, void *arg)
+{
+	TRACE_FUN(8, "ftape_ioctl");
+	int result = EINVAL;
+	union {
+		struct mtop mtop;
+		struct mtget mtget;
+	} krnl_arg;
+	int arg_size = (command & IOCSIZE_MASK) >> IOCSIZE_SHIFT;
+
+	/* This check will only catch arguments that are too large !
+	 */
+	if ((command & IOC_INOUT) && arg_size > sizeof(krnl_arg)) {
+		TRACEi(1, "bad argument size:", arg_size);
+		TRACE_EXIT;
+		return -EINVAL;
+	}
+	if (command & IOC_IN) {
+		int error = verify_area(VERIFY_READ, arg, arg_size);
+		if (error) {
+			TRACE_EXIT;
+			return error;
+		}
+		memcpy_fromfs(&krnl_arg.mtop, arg, arg_size);
+	}
+	TRACEx1(5, "called with ioctl command: 0x%08x", command);
+	switch (command) {
+		/* cpio compatibility
+		 * mtrasx and mtreset are mt extension by Hennus Bergman
+		 * mtseek and mttell are mt extension by eddy olk
+		 */
+	case MTIOCTOP:
+		TRACEx1(5, "calling MTIOCTOP command: 0x%08x", krnl_arg.mtop.mt_op);
+		switch (krnl_arg.mtop.mt_op) {
+		case MTNOP:
+			/* gnu mt calls MTNOP before MTIOCGET to set status */
+			result = 0;
+			break;
+		case MTRESET:
+			result = ftape_reset_drive();
+			init_drive_needed = 1;
+			if (result < 0 || ftape_offline) {
+				break;
+			}
+			result = ftape_seek_to_bot();
+			ftape_reset_position();
+			break;
+		case MTREW:
+		case MTOFFL:
+			if (ftape_offline) {
+				result = -EIO;
+				break;
+			}
+			ftape_flush_buffers();
+			ftape_update_header_segments(NULL, 1);
+			result = ftape_seek_to_bot();
+			ftape_reset_position();
+			if (krnl_arg.mtop.mt_op == MTOFFL) {
+				going_offline = 1;
+				TRACE(4, "Putting tape drive offline");
+			}
+			result = 0;
+			break;
+		case MTRETEN:
+			if (ftape_offline) {
+				result = -EIO;
+				break;
+			}
+			result = ftape_seek_to_eot();
+			if (result >= 0) {
+				result = ftape_seek_to_bot();
+			}
+			ftape_reset_position();
+			break;
+		case MTERASE:
+			if (ftape_offline) {
+				result = -EIO;
+				break;
+			}
+			result = ftape_erase();
+			break;
+		case MTEOM:
+			if (ftape_offline) {
+				result = -EIO;
+				break;
+			}
+			result = ftape_seek_eom();
+			break;
+		case MTFSFM:
+			if (ftape_offline) {
+				result = -EIO;
+				break;
+			}
+			eof_mark = 1;	/* position ready to extend */
+		case MTFSF:
+			if (ftape_offline) {
+				result = -EIO;
+				break;
+			}
+			result = ftape_seek_eof(krnl_arg.mtop.mt_count);
+			break;
+		case MTBSFM:
+			if (ftape_offline) {
+				result = -EIO;
+				break;
+			}
+			eof_mark = 1;	/* position ready to extend */
+		case MTBSF:
+			if (ftape_offline) {
+				result = -EIO;
+				break;
+			}
+			result = ftape_seek_eof(-krnl_arg.mtop.mt_count);
+			break;
+		case MTFSR:
+			if (ftape_offline) {
+				result = -EIO;
+				break;
+			}
+			tracing = krnl_arg.mtop.mt_count;
+			TRACEx1(2, "tracing set to %d", tracing);
+			result = 0;
+			break;
+		case MTBSR:
+			if (ftape_offline) {
+				result = -EIO;
+				break;
+			}
+#if 0
+			result = ftape_fix();
+#else
+			result = 0;
+#endif
+			break;
+		case MTWEOF:
+			if (ftape_offline) {
+				result = -EIO;
+				break;
+			}
+			result = ftape_weof(krnl_arg.mtop.mt_count, ftape_seg_pos, 1);
+			if (result >= 0) {
+				ftape_seg_pos += krnl_arg.mtop.mt_count - 1;
+			}
+			break;
+			/* MTRASx and MTRESET are mt extension by Hennus Bergman
+			 */
+		case MTRAS1:
+		case MTRAS2:
+		case MTRAS3:
+		case MTSEEK:
+		case MTTELL:
+		default:
+			TRACEi(1, "MTIOCTOP sub-command not implemented:", krnl_arg.mtop.mt_op);
+			result = -EIO;
+			break;
+		}
+		break;
+	case MTIOCGET:
+		krnl_arg.mtget.mt_type = drive_type.vendor_id + 0x800000;
+		krnl_arg.mtget.mt_resid = 0;	/* not implemented */
+		krnl_arg.mtget.mt_dsreg = 0;	/* status register */
+		krnl_arg.mtget.mt_gstat =	/* device independent status */
+		    ((ftape_offline) ? 0 : GMT_ONLINE(-1L)) |
+		    ((write_protected) ? GMT_WR_PROT(-1L) : 0) |
+		    ((no_tape) ? GMT_DR_OPEN(-1L) : 0);
+		krnl_arg.mtget.mt_erreg = ftape_last_error;	/* error register */
+		result = ftape_file_no(&krnl_arg.mtget.mt_fileno,
+				       &krnl_arg.mtget.mt_blkno);
+		break;
+	case MTIOCPOS:
+		TRACE(5, "Mag tape ioctl command: MTIOCPOS");
+		TRACE(1, "MTIOCPOS command not implemented");
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+	if (command & IOC_OUT) {
+		int error = verify_area(VERIFY_WRITE, arg, arg_size);
+		if (error) {
+			TRACE_EXIT;
+			return error;
+		}
+		memcpy_tofs(arg, &krnl_arg, arg_size);
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+void ftape_init_driver(void)
+{
+	drive_type.vendor_id = UNKNOWN_VENDOR;
+	drive_type.speed = 0;
+	drive_type.wake_up = unknown_wake_up;
+	drive_type.name = "Unkown";
+
+	timeout.seek = 650 * SECOND;
+	timeout.reset = 670 * SECOND;
+	timeout.rewind = 650 * SECOND;
+	timeout.head_seek = 15 * SECOND;
+	timeout.stop = 5 * SECOND;
+	timeout.pause = 16 * SECOND;
+
+	qic_std = -1;
+	tape_len = -1;
+	current_command = 0;
+	current_cylinder = -1;
+
+	segments_per_track = 102;
+	segments_per_head = 1020;
+	segments_per_cylinder = 4;
+	tracks_per_tape = 20;
+	ftape_failure = 1;
+	ftape_seg_pos = 0;
+	first_data_segment = -1;
+	ftape_state = idle;
+	no_tape = 1;
+	formatted = 0;
+	ftape_data_rate = 0;
+	going_offline = 0;
+	read_only = 0;
+
+	init_drive_needed = 1;
+	header_segment_1 = -1;
+	header_segment_2 = -1;
+	used_header_segment = -1;
+	location.track = -1;
+	location.known = 0;
+	tape_running = 0;
+	might_be_off_track = 1;
+
+	ftape_new_cartridge();	/* init some tape related variables */
+	ftape_init_bsm();
+}
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/ftape-ctl.h linux/drivers/char/ftape/ftape-ctl.h
--- v1.3.71/linux/drivers/char/ftape/ftape-ctl.h	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/ftape-ctl.h	Wed Mar  6 15:07:19 1996
@@ -0,0 +1,94 @@
+#ifndef _FTAPE_CTL_H
+#define _FTAPE_CTL_H
+
+/*
+ * Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ $Source: /home/bas/distr/ftape-2.03b/RCS/ftape-ctl.h,v $
+ $Author: bas $
+ *
+ $Revision: 1.4 $
+ $Date: 1995/05/03 18:04:03 $
+ $State: Beta $
+ *
+ *      This file contains the non-standard IOCTL related definitions
+ *      for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include <linux/ioctl.h>
+#include <linux/mtio.h>
+
+#include "vendors.h"
+
+
+typedef struct {
+	int used;		/* any reading or writing done */
+	/* isr statistics */
+	unsigned int id_am_errors;	/* id address mark not found */
+	unsigned int id_crc_errors;	/* crc error in id address mark */
+	unsigned int data_am_errors;	/* data address mark not found */
+	unsigned int data_crc_errors;	/* crc error in data field */
+	unsigned int overrun_errors;	/* fdc access timing problem */
+	unsigned int no_data_errors;	/* sector not found */
+	unsigned int retries;	/* number of tape retries */
+	/* ecc statistics */
+	unsigned int crc_errors;	/* crc error in data */
+	unsigned int crc_failures;	/* bad data without crc error */
+	unsigned int ecc_failures;	/* failed to correct */
+	unsigned int corrected;	/* total sectors corrected */
+	/* general statistics */
+	unsigned int rewinds;	/* number of tape rewinds */
+	unsigned int defects;	/* bad sectors due to media defects */
+} history_record;
+
+/*
+ *      ftape-ctl.c defined global vars.
+ */
+extern int ftape_failure;
+extern int write_protected;
+extern ftape_offline;
+extern int formatted;
+extern int no_tape;
+extern history_record history;
+extern int ftape_data_rate;
+extern int going_offline;
+extern vendor_struct drive_type;
+extern int segments_per_track;
+extern int segments_per_head;
+extern int segments_per_cylinder;
+extern int tracks_per_tape;
+extern int ftape_seg_pos;
+extern int first_data_segment;
+extern int ftape_state;
+extern int read_only;
+
+/*
+ *      ftape-ctl.c defined global functions.
+ */
+extern int _ftape_open(void);
+extern int _ftape_close(void);
+extern int _ftape_ioctl(unsigned int command, void *arg);
+extern int ftape_seek_to_bot(void);
+extern int ftape_seek_to_eot(void);
+extern int ftape_new_cartridge(void);
+extern int ftape_abort_operation(void);
+extern void ftape_reset_position(void);
+extern void ftape_calc_timeouts(void);
+extern void ftape_init_driver(void);
+
+#endif
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/ftape-eof.c linux/drivers/char/ftape/ftape-eof.c
--- v1.3.71/linux/drivers/char/ftape/ftape-eof.c	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/ftape-eof.c	Wed Mar  6 15:07:19 1996
@@ -0,0 +1,555 @@
+
+/*
+ *      Copyright (C) 1994-1995 Bas Laarhoven.
+
+ 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.
+
+ $Source: /home/bas/distr/ftape-2.03b/RCS/ftape-eof.c,v $
+ $Author: bas $
+ *
+ $Revision: 1.21 $
+ $Date: 1995/05/27 08:54:21 $
+ $State: Beta $
+ *
+ *      This file contains the eof mark handling code
+ *      for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include <linux/module.h>
+#include <linux/ftape.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+
+#include "tracing.h"
+#include "ftape-eof.h"
+#include "ftape-write.h"
+#include "ftape-read.h"
+#include "ftape-rw.h"
+#include "ftape-ctl.h"
+#include "ftape-bsm.h"
+
+/*      Global vars.
+ */
+int failed_sector_log_changed = 0;
+int eof_mark = 0;
+
+/*      Local vars.
+ */
+static struct failed_sector_entry {
+	unsigned short segment;
+	unsigned short sector;
+} *eof_mark_ptr;
+
+typedef union {
+	struct failed_sector_entry mark;
+	unsigned long entry;
+} eof_mark_union;
+
+/*  a copy of the failed sector log from the header segment.
+ */
+static eof_mark_union eof_map[(2048 - 256) / 4];
+
+/*  index into eof_map table pointing to last found eof mark.
+ */
+static int eof_index;
+
+/*  number of eof marks (entries in bad sector log) on tape.
+ */
+static int nr_of_eof_marks = -1;
+
+static char linux_tape_label[] = "Linux raw format V";
+enum {
+	min_fmt_version = 1, max_fmt_version = 2
+};
+static unsigned ftape_fmt_version = 0;
+
+
+/*  Ftape (mis)uses the bad sector log to record end-of-file marks.
+ *  Initially (when the tape is erased) all entries in the bad sector
+ *  log are added to the tape's bad sector map. The bad sector log
+ *  then is cleared.
+ *
+ *  The bad sector log normally contains entries of the form:
+ *  even 16-bit word: segment number of bad sector
+ *   odd 16-bit word: encoded date
+ *  There can be a total of 448 entries (1792 bytes).
+ *
+ *  My guess is that no program is using this bad sector log (the
+ *  format seems useless as there is no indication of the bad sector
+ *  itself, only the segment)
+ *  However, if any program does use the bad sector log, the format
+ *  used by ftape will let the program think there are some bad sectors
+ *  and no harm is done.
+ *  
+ *  The eof mark entries that ftape stores in the bad sector log:
+ *  even 16-bit word: segment number of eof mark
+ *   odd 16-bit word: sector number of eof mark [1..32]
+ *  
+ *  The eof_map as maintained is a sorted list of eof mark entries.
+ *
+ *
+ *  The tape name field in the header segments is used to store a
+ *  linux tape identification string and a version number.
+ *  This way the tape can be recognized as a Linux raw format
+ *  tape when using tools under other OS's.
+ *
+ *  'Wide' QIC tapes (format code 4) don't have a failed sector list
+ *  anymore. That space is used for the (longer) bad sector map that
+ *  now is a variable length list too.
+ *  We now store our end-of-file marker list after the bad-sector-map
+ *  on tape. The list is delimited by a (long) 0 entry.
+ */
+
+int ftape_validate_label(char *label)
+{
+	TRACE_FUN(8, "ftape_validate_label");
+	int result = 0;
+
+	TRACEx1(4, "tape  label = `%s'", label);
+	ftape_fmt_version = 0;
+	if (memcmp(label, linux_tape_label, strlen(linux_tape_label)) == 0) {
+		int pos = strlen(linux_tape_label);
+		while (label[pos] >= '0' && label[pos] <= '9') {
+			ftape_fmt_version *= 10;
+			ftape_fmt_version = label[pos++] - '0';
+		}
+		result = (ftape_fmt_version >= min_fmt_version &&
+			  ftape_fmt_version <= max_fmt_version);
+	}
+	TRACEx1(4, "format version = %d", ftape_fmt_version);
+	TRACE_EXIT;
+	return result;
+}
+
+static byte *
+ find_end_of_eof_list(byte * ptr, byte * limit)
+{
+	while (ptr + 3 < limit) {
+		if (*(unsigned long *) ptr) {
+			++(unsigned long *) ptr;
+		} else {
+			return ptr;
+		}
+	}
+	return NULL;
+}
+
+void reset_eof_list(void)
+{
+	TRACE_FUN(8, "reset_eof_list");
+
+	eof_mark_ptr = &eof_map[0].mark;
+	eof_index = 0;
+	eof_mark = 0;
+	TRACE_EXIT;
+}
+
+/*  Test if `segment' has an eof mark set (optimized for sequential access).
+ *  return 0 if not eof mark or sector number (> 0) if eof mark set.
+ */
+int check_for_eof(unsigned segment)
+{
+	TRACE_FUN(8, "check_for_eof");
+	static unsigned last_reference = INT_MAX;
+	int result;
+
+	if (segment < last_reference) {
+		reset_eof_list();
+	}
+	last_reference = segment;
+	while (eof_index < nr_of_eof_marks && segment > eof_mark_ptr->segment) {
+		++eof_mark_ptr;
+		++eof_index;
+	}
+	if (eof_index < nr_of_eof_marks && segment == eof_mark_ptr->segment) {
+		TRACEx3(5, "hit mark %d/%d at index %d",
+			eof_map[eof_index].mark.segment, eof_map[eof_index].mark.sector,
+			eof_index);
+		if (eof_mark_ptr->sector >= SECTORS_PER_SEGMENT) {
+			TRACEx2(-1, "Bad file mark detected: %d/%d",
+			    eof_mark_ptr->segment, eof_mark_ptr->sector);
+			result = 0;	/* return bogus (but valid) value */
+		} else {
+			result = eof_mark_ptr->sector;
+		}
+	} else {
+		result = 0;
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+void clear_eof_mark_if_set(unsigned segment, unsigned byte_count)
+{
+	TRACE_FUN(5, "clear_eof_mark_if_set");
+	if (ftape_fmt_version != 0 &&
+	    check_for_eof(segment) > 0 &&
+	    byte_count >= eof_mark_ptr->sector * SECTOR_SIZE) {
+		TRACEx3(5, "clearing mark %d/%d at index %d",
+		 eof_mark_ptr->segment, eof_mark_ptr->sector, eof_index);
+		memmove(&eof_map[eof_index], &eof_map[eof_index + 1],
+			(nr_of_eof_marks - eof_index) * sizeof(*eof_map));
+		--nr_of_eof_marks;
+		failed_sector_log_changed = 1;
+	}
+	TRACE_EXIT;
+}
+
+void put_file_mark_in_map(unsigned segment, unsigned sector)
+{
+	TRACE_FUN(8, "put_file_mark_in_map");
+	eof_mark_union new;
+	int index;
+	eof_mark_union *ptr;
+
+	if (ftape_fmt_version != 0) {
+		new.mark.segment = segment;
+		new.mark.sector = sector;
+		for (index = 0, ptr = &eof_map[0];
+		  index < nr_of_eof_marks && ptr->mark.segment < segment;
+		     ++index, ++ptr) {
+		}
+		if (index < nr_of_eof_marks) {
+			if (ptr->mark.segment == segment) {
+				/* overwrite */
+				if (ptr->mark.sector == sector) {
+					TRACEx2(5, "mark %d/%d already exists",
+						new.mark.segment, new.mark.sector);
+				} else {
+					TRACEx5(5, "overwriting %d/%d at index %d with %d/%d",
+						ptr->mark.segment, ptr->mark.sector, index,
+						new.mark.segment, new.mark.sector);
+					ptr->entry = new.entry;
+					failed_sector_log_changed = 1;
+				}
+			} else {
+				/* insert */
+				TRACEx5(5, "inserting %d/%d at index %d before %d/%d",
+				new.mark.segment, new.mark.sector, index,
+				    ptr->mark.segment, ptr->mark.sector);
+				memmove(ptr + 1, ptr, (nr_of_eof_marks - index) * sizeof(*eof_map));
+				ptr->entry = new.entry;
+				++nr_of_eof_marks;
+				failed_sector_log_changed = 1;
+			}
+		} else {
+			/* append */
+			TRACEx3(5, "appending %d/%d at index %d",
+				new.mark.segment, new.mark.sector, index);
+			ptr->entry = new.entry;
+			++nr_of_eof_marks;
+			failed_sector_log_changed = 1;
+		}
+	}
+	TRACE_EXIT;
+}
+
+/*  Write count file marks to tape starting at first non-bad
+ *  sector following the given segment and sector.
+ *  sector = base 1 !
+ */
+int ftape_weof(unsigned count, unsigned segment, unsigned sector)
+{
+	TRACE_FUN(5, "ftape_weof");
+	int result = 0;
+	unsigned long mask = get_bad_sector_entry(segment);
+	unsigned sector_nr = 0;
+
+	if (ftape_fmt_version != 0) {
+		if (sector < 1 || sector > 29 ||
+		    segment + count >= ftape_last_segment.id) {
+			TRACEx3(5, "parameter out of range: %d, %d, %d", count, segment, sector);
+			result = -EIO;
+		} else {
+			while (count-- > 0) {
+				do {	/* count logical sectors */
+					do {	/* skip until good sector */
+						while (mask & 1) {	/* skip bad sectors */
+							++sector_nr;
+							mask >>= 1;
+						}
+						if (sector_nr >= 29) {
+							if (++segment >= ftape_last_segment.id) {
+								TRACEx1(5, "segment out of range: %d", segment);
+								result = -EIO;
+								break;
+							}
+							mask = get_bad_sector_entry(segment);
+							sector_nr = 0;
+						}
+					} while (mask & 1);
+					++sector_nr;	/* point to good sector */
+					mask >>= 1;
+				} while (--sector);
+				if (result >= 0) {
+					TRACEx2(5, "writing filemark %d/%d", segment, sector_nr);
+					put_file_mark_in_map(segment, sector_nr);
+					++segment;	/* next segment */
+					sector_nr = 0;
+					sector = 1;	/* first sector */
+				}
+			}
+		}
+	} else {
+		result = -EPERM;
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_erase(void)
+{
+	TRACE_FUN(5, "ftape_erase");
+	int result = 0;
+	int i;
+	unsigned long now = 0;
+	byte *buffer = deblock_buffer;
+
+	if (write_protected) {
+		result = -EROFS;
+	} else {
+		result = read_header_segment(buffer);
+		if (result >= 0) {
+			/*  Copy entries from bad-sector-log into bad-sector-map
+			 */
+			TRACEx1(5, "old label: `%s'", (char *) (buffer + 30));
+			if (!ftape_validate_label((char *) &buffer[30])) {
+				TRACE(5, "invalid label, overwriting with new");
+				memset(buffer + 30, 0, 44);
+				memcpy(buffer + 30, linux_tape_label, strlen(linux_tape_label));
+				buffer[30 + strlen(linux_tape_label)] = '2';
+				TRACEx1(5, "new label: `%s'", (char *) (buffer + 30));
+				PUT4(buffer, 74, now);
+				if (format_code != 4) {
+					for (i = 0; i < nr_of_eof_marks; ++i) {
+						unsigned failing_segment = eof_map[i].mark.segment;
+
+						if (!valid_segment_no(failing_segment)) {
+							TRACEi(4, "bad entry in failed sector log:", failing_segment);
+						} else {
+							put_bad_sector_entry(failing_segment, EMPTY_SEGMENT);
+							TRACEx2(4, "moved entry %d from failed sector log (%d)",
+								i, failing_segment);
+						}
+					}
+				}
+			}
+			/*  Clear failed sector log: remove all tape marks
+			 */
+			failed_sector_log_changed = 1;
+			memset(eof_map, 0, sizeof(eof_map));
+			nr_of_eof_marks = 0;
+			ftape_fmt_version = max_fmt_version;
+#if 0
+			fix_tape(buffer);	/* see ftape-bsm.c ! */
+#endif
+			result = ftape_update_header_segments(buffer, 1);
+			prevent_flush();	/* prevent flush_buffers writing file marks */
+			reset_eof_list();
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+void extract_file_marks(byte * address)
+{
+	TRACE_FUN(8, "extract_file_marks");
+	int i;
+
+	if (format_code == 4) {
+		byte *end;
+		byte *start = find_end_of_bsm_list(address + 256,
+					     address + 29 * SECTOR_SIZE);
+
+		memset(eof_map, 0, sizeof(eof_map));
+		nr_of_eof_marks = 0;
+		if (start) {
+			start += 3;	/* skip end of list mark */
+			end = find_end_of_eof_list(start, address + 29 * SECTOR_SIZE);
+			if (end && end - start <= sizeof(eof_map)) {
+				nr_of_eof_marks = (end - start) / sizeof(unsigned long);
+				memcpy(eof_map, start, end - start);
+			} else {
+				TRACE(1, "File Mark List is too long or damaged !");
+			}
+		} else {
+			TRACE(1, "Bad Sector List is too long or damaged !");
+		}
+	} else {
+		memcpy(eof_map, address + 256, sizeof(eof_map));
+		nr_of_eof_marks = GET2(address, 144);
+	}
+	TRACEi(4, "number of file marks:", nr_of_eof_marks);
+	if (ftape_fmt_version == 1) {
+		TRACE(-1, "swapping version 1 fields");
+		/*  version 1 format uses swapped sector and segment fields, correct that !
+		 */
+		for (i = 0; i < nr_of_eof_marks; ++i) {
+			unsigned short tmp = eof_map[i].mark.segment;
+			eof_map[i].mark.segment = eof_map[i].mark.sector;
+			eof_map[i].mark.sector = tmp;
+		}
+	}
+	for (i = 0; i < nr_of_eof_marks; ++i) {
+		TRACEx2(4, "eof mark: %5d/%2d",
+			eof_map[i].mark.segment, eof_map[i].mark.sector);
+	}
+	reset_eof_list();
+	TRACE_EXIT;
+}
+
+int update_failed_sector_log(byte * buffer)
+{
+	TRACE_FUN(8, "update_failed_sector_log");
+
+	if (ftape_fmt_version != 0 && failed_sector_log_changed) {
+		if (ftape_fmt_version == 1) {
+			TRACE(-1, "upgrading version 1 format to version 2");
+			/*  version 1 will be upgraded to version 2 when written.
+			 */
+			buffer[30 + strlen(linux_tape_label)] = '2';
+			ftape_fmt_version = 2;
+			TRACEx1(-1, "new tape label = \"%s\"", &buffer[30]);
+		}
+		if (format_code == 4) {
+			byte *dest = find_end_of_bsm_list(buffer + 256,
+					  buffer + 29 * SECTOR_SIZE) + 3;
+
+			if (dest) {
+				TRACEx2(4, "eof_map at byte offset %6d, size %d",
+					dest - buffer - 256, nr_of_eof_marks * sizeof(unsigned long));
+				memcpy(dest, eof_map, nr_of_eof_marks * sizeof(unsigned long));
+				PUT4(dest, nr_of_eof_marks * sizeof(unsigned long), 0);
+			}
+		} else {
+			memcpy(buffer + 256, eof_map, sizeof(eof_map));
+			PUT2(buffer, 144, nr_of_eof_marks);
+		}
+		failed_sector_log_changed = 0;
+		return 1;
+	}
+	TRACE_EXIT;
+	return 0;
+}
+
+int ftape_seek_eom(void)
+{
+	TRACE_FUN(5, "ftape_seek_eom");
+	int result = 0;
+	unsigned eom;
+
+	if (first_data_segment == -1) {
+		result = read_header_segment(deblock_buffer);
+	}
+	if (result >= 0 && ftape_fmt_version != 0) {
+		eom = first_data_segment;
+		eof_index = 0;
+		eof_mark_ptr = &eof_map[0].mark;
+		/*  If fresh tape, count should be zero but we don't
+		 *  want to worry about the case it's one.
+		 */
+		for (eof_index = 1, eof_mark_ptr = &eof_map[1].mark;
+		     eof_index < nr_of_eof_marks; ++eof_index, ++eof_mark_ptr) {
+			/*  The eom is recorded as two eof marks in succeeding segments
+			 *  where the second one is always at segment number 1.
+			 */
+			if (eof_mark_ptr->sector == 1) {
+				if (eof_mark_ptr->segment == (eof_mark_ptr - 1)->segment + 1) {
+					eom = eof_mark_ptr->segment;
+					break;
+				}
+			}
+		}
+		ftape_seg_pos = eom;
+		TRACEx1(5, "eom found at segment %d", eom);
+	} else {
+		TRACE(5, "Couldn't get eof mark table");
+		result = -EIO;
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_seek_eof(unsigned count)
+{
+	TRACE_FUN(5, "ftape_seek_eof");
+	int result = 0;
+	enum {
+		not = 0, begin, end
+	} bad_seek = not;
+
+	if (first_data_segment == -1) {
+		result = read_header_segment(deblock_buffer);
+	}
+	TRACEx1(5, "tape positioned at segment %d", ftape_seg_pos);
+	if (ftape_fmt_version == 0) {
+		result = -1;
+	}
+	if (result >= 0 && count != 0) {
+		for (eof_index = 0; eof_index <= nr_of_eof_marks; ++eof_index) {
+			if (eof_index == nr_of_eof_marks ||	/* start seeking after last mark */
+			    ftape_seg_pos <= eof_map[eof_index].mark.segment) {
+				eof_index += count;
+				if (eof_index < 1) {	/* begin of tape */
+					ftape_seg_pos = first_data_segment;
+					if (eof_index < 0) {	/* `before' begin of tape */
+						eof_index = 0;
+						bad_seek = begin;
+					}
+				} else if (eof_index >= nr_of_eof_marks) {	/* `after' end of tape */
+					ftape_seg_pos = segments_per_track * tracks_per_tape;
+					if (eof_index > nr_of_eof_marks) {
+						eof_index = nr_of_eof_marks;
+						bad_seek = end;
+					}
+				} else {	/* after requested file mark */
+					ftape_seg_pos = eof_map[eof_index - 1].mark.segment + 1;
+				}
+				eof_mark_ptr = &eof_map[eof_index].mark;
+				break;
+			}
+		}
+	}
+	if (result < 0) {
+		TRACE(5, "Couldn't get eof mark table");
+		result = -EIO;
+	} else if (bad_seek != not) {
+		TRACEx1(1, "seek reached %s of tape",
+			(bad_seek == begin) ? "begin" : "end");
+		result = -EIO;
+	} else {
+		TRACEx1(5, "tape repositioned to segment %d", ftape_seg_pos);
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_file_no(daddr_t * f_no, daddr_t * b_no)
+{
+	TRACE_FUN(5, "ftape_file_no");
+	int result = 0;
+	int i;
+
+	*f_no = eof_index;
+	*b_no = ftape_seg_pos;
+	TRACEi(4, "number of file marks:", nr_of_eof_marks);
+	for (i = 0; i < nr_of_eof_marks; ++i) {
+		TRACEx2(4, "eof mark: %5d/%2d",
+			eof_map[i].mark.segment, eof_map[i].mark.sector);
+	}
+	TRACE_EXIT;
+	return result;
+}
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/ftape-eof.h linux/drivers/char/ftape/ftape-eof.h
--- v1.3.71/linux/drivers/char/ftape/ftape-eof.h	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/ftape-eof.h	Wed Mar  6 15:07:19 1996
@@ -0,0 +1,55 @@
+
+
+#ifndef _FTAPE_EOF_H
+#define _FTAPE_EOF_H
+
+/*
+ * Copyright (C) 1994-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ $Source: /home/bas/distr/ftape-2.03b/RCS/ftape-eof.h,v $
+ $Author: bas $
+ *
+ $Revision: 1.12 $
+ $Date: 1995/04/22 07:30:15 $
+ $State: Beta $
+ *
+ *      Definitions and declarations for the end of file markers
+ *      for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+/*      ftape-eof.c defined global vars.
+ */
+extern int failed_sector_log_changed;
+extern int eof_mark;
+
+/*      ftape-eof.c defined global functions.
+ */
+extern void clear_eof_mark_if_set(unsigned segment, unsigned byte_count);
+extern void reset_eof_list(void);
+extern int check_for_eof(unsigned segment);
+extern int ftape_weof(unsigned count, unsigned segment, unsigned sector);
+extern int ftape_erase(void);
+extern void put_file_mark_in_map(unsigned segment, unsigned sector);
+extern void extract_file_marks(byte * address);
+extern int update_failed_sector_log(byte * buffer);
+extern int ftape_seek_eom(void);
+extern int ftape_seek_eof(unsigned count);
+extern int ftape_file_no(daddr_t * file, daddr_t * block);
+extern int ftape_validate_label(char *label);
+
+#endif
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/ftape-io.c linux/drivers/char/ftape/ftape-io.c
--- v1.3.71/linux/drivers/char/ftape/ftape-io.c	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/ftape-io.c	Wed Mar  6 15:07:19 1996
@@ -0,0 +1,1051 @@
+/*
+ *      Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ $Source: /home/bas/distr/ftape-2.03b/RCS/ftape-io.c,v $
+ $Author: bas $
+ *
+ $Revision: 1.58 $
+ $Date: 1995/05/27 08:54:21 $
+ $State: Beta $
+ *
+ *      This file contains the general control functions
+ *      for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/ftape.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <linux/ioctl.h>
+#include <linux/mtio.h>
+
+#include "tracing.h"
+#include "fdc-io.h"
+#include "qic117.h"
+#include "ftape-io.h"
+#include "ftape-ctl.h"
+#include "ftape-rw.h"
+#include "ftape-write.h"
+#include "ftape-read.h"
+#include "ftape-eof.h"
+#include "kernel-interface.h"
+#include "calibr.h"
+
+/*      Global vars.
+ */
+/* NOTE: sectors start numbering at 1, all others at 0 ! */
+timeout_table timeout;
+vendor_struct drive_type;
+int qic_std;
+int tape_len;
+volatile int current_command;
+const struct qic117_command_table qic117_cmds[] = QIC117_COMMANDS;
+int might_be_off_track;
+
+/*      Local vars.
+ */
+static int command_parameter = 0;
+/*      command-restrictions is a table according
+ *      to the QIC-117 specs specifying the state
+ *      the drive status should be in at command execution.
+ */
+static const ftape_error ftape_errors[] = QIC117_ERRORS;
+static int ftape_udelay_count;
+static int ftape_udelay_time;
+static const struct {
+	char *text;
+	int fdc_code;
+	byte drive_code;
+	int precomp;
+} rates[4] = {
+
+#if defined(FDC_82078SL)
+	{
+		"2 M", -1 /* unsupported */ , QIC_CONFIG_RATE_2000, 0
+	},
+#else
+	{
+		"2 M", fdc_data_rate_2000, QIC_CONFIG_RATE_2000, 0
+	},
+#endif
+	{
+		"1 M", fdc_data_rate_1000, QIC_CONFIG_RATE_1000, 42
+	},
+	{
+		"500 K", fdc_data_rate_500, QIC_CONFIG_RATE_500, 125
+	},
+	{
+		"250 K", fdc_data_rate_250, QIC_CONFIG_RATE_250, 250
+	},
+};
+typedef enum {
+	prehistoric, pre_qic117c, post_qic117b, post_qic117d
+} qic_model;
+
+
+void udelay(int usecs)
+{
+	volatile int count = (1 + (usecs * ftape_udelay_count - 1) /
+			      ftape_udelay_time);
+	volatile int i;
+
+	while (count-- > 0) {
+		for (i = 0; i < 20; ++i);
+	}
+}
+
+int udelay_calibrate(void)
+{
+	return calibrate("udelay", udelay, &ftape_udelay_count, &ftape_udelay_time);
+}
+
+/*      Delay (msec) routine.
+ */
+void ftape_sleep(unsigned int time)
+{
+	TRACE_FUN(8, "ftape_sleep");
+	unsigned long flags;
+	int ticks = 1 + (time + MSPT - 1) / MSPT;
+
+	/*    error in range [0..1] MSPT
+	 */
+	if (time < MSPT) {
+		/*  Time too small for scheduler, do a busy wait ! */
+		udelay(1000 * time);
+	} else {
+		TRACEx2(8, "%d msec, %d ticks", time, ticks);
+		current->timeout = jiffies + ticks;
+		current->state = TASK_INTERRUPTIBLE;
+		save_flags(flags);
+		sti();
+		do {
+			while (current->state != TASK_RUNNING) {
+				schedule();
+			}
+			if (current->signal & ~current->blocked) {
+				TRACE(1, "awoken by non-blocked signal :-(");
+				break;	/* exit on signal */
+			}
+		} while (current->timeout > 0);
+		restore_flags(flags);
+	}
+	TRACE_EXIT;
+}
+
+/* forward */ int ftape_report_raw_drive_status(int *status);
+
+/*      Issue a tape command:
+ *      Generate command # of step pulses.
+ */
+int ftape_command(int command)
+{
+	TRACE_FUN(8, "ftape_command");
+	int result = 0;
+	int track;
+	int old_tracing = tracing;
+	static int level = 0;
+	int status = -1;
+
+	if (++level > 5) {
+		/*  This is a bug we'll want to know about.
+		 */
+		TRACEx1(1, "bug - recursion for command: %d", command);
+		result = -EIO;
+	} else if (command_parameter) {
+		/*  Don't check restrictions for parameters.
+		 */
+		TRACEx1(5, "called with parameter = %d", command - 2);
+	} else if (command <= 0 || command > NR_ITEMS(qic117_cmds)) {
+		/*  This is a bug we'll want to know about too.
+		 */
+		TRACEx1(-1, "bug - bad command: %d", command);
+		result = -EIO;
+	} else {
+		/*  disable logging and restriction check for some commands,
+		 *  check all other commands that have a prescribed starting status.
+		 */
+		if (command == QIC_REPORT_DRIVE_STATUS) {
+			TRACE(8, "report drive status called");
+			tracing = 0;
+		} else if (command == QIC_REPORT_NEXT_BIT) {
+			tracing = 0;
+		} else {
+			TRACEx1(5, "%s", qic117_cmds[command].name);
+			/*  A new motion command during an uninterruptible (motion)
+			 *  command requires a ready status before the new command
+			 *  can be issued. Otherwise a new motion command needs to
+			 *  be checked against required status.
+			 */
+			if (qic117_cmds[command].cmd_type == motion &&
+			    qic117_cmds[current_command].non_intr) {
+				ftape_report_raw_drive_status(&status);
+				if ((status & QIC_STATUS_READY) == 0) {
+					TRACEx2(4, "motion cmd (%d) during non-intr cmd (%d)",
+						command, current_command);
+					TRACE(4, "waiting until drive gets ready");
+					ftape_ready_wait(timeout.seek, &status);
+				}
+			}
+			if (qic117_cmds[command].mask != 0) {
+				byte difference;
+
+				/*  Some commands do require a certain status:
+				 */
+				if (status == -1) {	/* not yet set */
+					ftape_report_raw_drive_status(&status);
+				}
+				difference = ((status ^ qic117_cmds[command].state) &
+					      qic117_cmds[command].mask);
+				/*  Wait until the drive gets ready. This may last forever
+				 *  if the drive never gets ready...
+				 */
+				while ((difference & QIC_STATUS_READY) != 0) {
+					TRACEx1(4, "command %d issued while not ready", command);
+					TRACE(4, "waiting until drive gets ready");
+					ftape_ready_wait(timeout.seek, &status);
+					difference = ((status ^ qic117_cmds[command].state) &
+					      qic117_cmds[command].mask);
+					/*  Bail out on signal !
+					 */
+					if (current->signal & _DONT_BLOCK) {
+						result = -EINTR;
+						break;
+					}
+				}
+				while (result == 0 && (difference & QIC_STATUS_ERROR) != 0) {
+					int err;
+					int cmd;
+
+					TRACEx1(4, "command %d issued while error pending", command);
+					TRACE(4, "clearing error status");
+					ftape_report_error(&err, &cmd, 1);
+					ftape_report_raw_drive_status(&status);
+					difference = ((status ^ qic117_cmds[command].state) &
+					      qic117_cmds[command].mask);
+					/*  Bail out on fatal signal !
+					 */
+					if (current->signal & _DONT_BLOCK) {
+						result = -EINTR;
+						break;
+					}
+				}
+				if (result == 0 && difference) {
+					/*  Any remaining difference can't be solved here.
+					 */
+					if (difference & (QIC_STATUS_CARTRIDGE_PRESENT |
+					       QIC_STATUS_NEW_CARTRIDGE |
+						QIC_STATUS_REFERENCED)) {
+						TRACE(1, "Fatal: tape removed or reinserted !");
+						ftape_failure = 1;
+					} else {
+						TRACEx2(1, "wrong state: 0x%02x should be: 0x%02x",
+							status & qic117_cmds[command].mask,
+							qic117_cmds[command].state);
+					}
+					result = -EIO;
+				}
+				if (~status & QIC_STATUS_READY & qic117_cmds[command].mask) {
+					TRACE(1, "Bad: still busy!");
+					result = -EBUSY;
+				}
+			}
+		}
+	}
+	tracing = old_tracing;
+	/*  Now all conditions are met or result is < 0.
+	 */
+	if (result >= 0) {
+		/*  Always wait for a command_timeout period to separate
+		 *  individuals commands and/or parameters.
+		 */
+		ftape_sleep(3 * MILLISECOND);
+		/*  Keep cylinder nr within range, step towards home if possible.
+		 */
+		if (current_cylinder >= command) {
+			track = current_cylinder - command;
+		} else {
+			track = current_cylinder + command;
+		}
+		result = fdc_seek(track);
+		/*  position is no longer valid after any of these commands
+		 *  have completed.
+		 */
+		if (qic117_cmds[command].cmd_type == motion &&
+		    command != QIC_LOGICAL_FORWARD && command != QIC_STOP_TAPE) {
+			location.known = 0;
+		}
+		command_parameter = 0;	/* always turned off for next command */
+		current_command = command;
+	}
+	--level;
+	TRACE_EXIT;
+	return result;
+}
+
+/*      Send a tape command parameter:
+ *      Generates command # of step pulses.
+ *      Skips tape-status call !
+ */
+int ftape_parameter(int command)
+{
+	command_parameter = 1;
+	return ftape_command(command + 2);
+}
+
+/*      Wait for the drive to get ready.
+ *      timeout time in milli-seconds
+ *      Returned status is valid if result != -EIO
+ */
+int ftape_ready_wait(int timeout, int *status)
+{
+	TRACE_FUN(8, "ftape_ready_wait");
+	int result;
+	unsigned long t0;
+	const int poll_delay = 100 * MILLISECOND;
+
+	for (;;) {
+		t0 = jiffies;
+		result = ftape_report_raw_drive_status(status);
+		if (result < 0) {
+			TRACE(1, "ftape_report_raw_drive_status failed");
+			result = -EIO;
+			break;
+		}
+		if (*status & QIC_STATUS_READY) {
+			result = 0;
+			break;
+		}
+		if (timeout >= 0) {
+			/* this will fail when jiffies wraps around about
+			 * once every year :-)
+			 */
+			timeout -= ((jiffies - t0) * SECOND) / HZ;
+			if (timeout <= 0) {
+				TRACE(1, "timeout");
+				result = -ETIME;
+				break;
+			}
+			ftape_sleep(poll_delay);
+			timeout -= poll_delay;
+		} else {
+			ftape_sleep(poll_delay);
+		}
+		if (current->signal & _NEVER_BLOCK) {
+			TRACE(1, "interrupted by fatal signal");
+			result = -EINTR;
+			break;	/* exit on signal */
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+/*      Issue command and wait up to timeout seconds for drive ready
+ */
+int ftape_command_wait(int command, int timeout, int *status)
+{
+	TRACE_FUN(8, "ftape_command_wait");
+	int result;
+
+	/* Drive should be ready, issue command
+	 */
+	result = ftape_command(command);
+	if (result >= 0) {
+		result = ftape_ready_wait(timeout, status);
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_parameter_wait(int command, int timeout, int *status)
+{
+	TRACE_FUN(8, "ftape_parameter_wait");
+	int result;
+
+	/* Drive should be ready, issue command
+	 */
+	result = ftape_parameter(command);
+	if (result >= 0) {
+		result = ftape_ready_wait(timeout, status);
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+/*--------------------------------------------------------------------------
+ *      Report operations
+ */
+
+/* Query the drive about its status.  The command is sent and
+   result_length bits of status are returned (2 extra bits are read
+   for start and stop). */
+
+static int ftape_report_operation(int *status, int command, int result_length)
+{
+	TRACE_FUN(8, "ftape_report_operation");
+	int i, st3;
+	int result;
+	unsigned int t0, t1, dt;
+
+	result = ftape_command(command);
+	if (result < 0) {
+		TRACE(1, "ftape_command failed");
+		TRACE_EXIT;
+		return result;
+	}
+	t0 = timestamp();
+	dt = 0;
+	i = 0;
+	do {
+		++i;
+		ftape_sleep(3 * MILLISECOND);	/* see remark below */
+		result = fdc_sense_drive_status(&st3);
+		if (result < 0) {
+			TRACE(1, "fdc_sense_drive_status failed");
+			TRACE_EXIT;
+			return result;
+		}
+		/*  Calculate time difference every iteration because timer may
+		 *  wrap around (but only one !) and timediff will account for this.
+		 *  Note that the sleep above must be < 1/HZ or we'll lose ticks !
+		 */
+		t1 = timestamp();
+		dt += timediff(t0, t1);
+		t0 = t1;
+		/*  Ack should be asserted within Ttimout + Tack = 6 msec.
+		 *  Looks like some drives fail to do this so extend this
+		 *  period to 300 msec.
+		 */
+	} while (!(st3 & ST3_TRACK_0) && dt < 300000);
+	if (st3 & ST3_TRACK_0) {
+		/*  dt may be larger than expected because of other tasks
+		 *  scheduled while we were sleeping.
+		 */
+		if (i > 1 && dt > 6000) {
+			TRACEx2(1, "Acknowledge after %u msec. (%i iter)", dt / 1000, i);
+		}
+	} else {
+		TRACEx2(1, "No acknowledge after %u msec. (%i iter)", dt / 1000, i);
+		TRACE(1, "timeout on Acknowledge");
+		TRACE_EXIT;
+		return -EIO;
+	}
+	*status = 0;
+	for (i = 0; i < result_length + 1; i++) {
+		result = ftape_command(QIC_REPORT_NEXT_BIT);
+		if (result < 0) {
+			TRACE(1, "report next bit failed");
+			TRACE_EXIT;
+			return result;
+		}
+#if 1
+		/*  fdc_seek does interrupt wait, so why should we ?
+		 *  (it will only fail causing fdc to be reset...)
+		 *  It's only purpose may be the delay, we'll have to find out!
+		 */
+#else
+		fdc_interrupt_wait(25 * MILLISECOND);	/* fails only if hw fails */
+#endif
+		result = fdc_sense_drive_status(&st3);
+		if (result < 0) {
+			TRACE(1, "fdc_sense_drive_status (2) failed");
+			TRACE_EXIT;
+			return result;
+		}
+		if (i < result_length) {
+			*status |= ((st3 & ST3_TRACK_0) ? 1 : 0) << i;
+		} else {
+			if ((st3 & ST3_TRACK_0) == 0) {
+				TRACE(1, "missing status stop bit");
+				TRACE_EXIT;
+				return -EIO;
+			}
+		}
+	}
+	/* this command will put track zero and index back into normal state */
+	result = ftape_command(QIC_REPORT_NEXT_BIT);
+	TRACE_EXIT;
+	return 0;
+}
+
+/* Report the current drive status. */
+
+int ftape_report_raw_drive_status(int *status)
+{
+	TRACE_FUN(8, "ftape_report_raw_drive_status");
+	int result;
+	int count = 0;
+
+	do {
+		result = ftape_report_operation(status, QIC_REPORT_DRIVE_STATUS, 8);
+	} while (result < 0 && ++count <= 3);
+	if (result < 0) {
+		TRACE(1, "report_operation failed");
+		result = -EIO;
+	} else if (*status & QIC_STATUS_READY) {
+		current_command = 0;	/* completed */
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_report_drive_status(int *status)
+{
+	TRACE_FUN(8, "ftape_report_drive_status");
+	int result;
+
+	result = ftape_report_raw_drive_status(status);
+	if (result < 0) {
+		TRACE(1, "ftape_report_raw_drive_status failed");
+		TRACE_EXIT;
+		return result;
+	}
+	if (*status & QIC_STATUS_NEW_CARTRIDGE ||
+	    !(*status & QIC_STATUS_CARTRIDGE_PRESENT)) {
+		ftape_failure = 1;	/* will inhibit further operations */
+		TRACE_EXIT;
+		return -EIO;
+	}
+	if (*status & QIC_STATUS_READY && *status & QIC_STATUS_ERROR) {
+		/*  Let caller handle all errors */
+		TRACE(2, "warning: error status set!");
+		result = 1;
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_report_error(int *error, int *command, int report)
+{
+	TRACE_FUN(8, "ftape_report_error");
+	int code;
+	int result;
+
+	result = ftape_report_operation(&code, QIC_REPORT_ERROR_CODE, 16);
+	if (result < 0) {
+		result = -EIO;
+	} else {
+		*error = code & 0xff;
+		*command = (code >> 8) & 0xff;
+		if (report) {
+			if (*error != 0) {
+				TRACEi(3, "errorcode:", *error);
+			} else {
+				TRACE(3, "No error");
+			}
+		}
+		if (report && *error != 0 && tracing > 3) {
+			if (*error >= 0 && *error < NR_ITEMS(ftape_errors)) {
+				TRACEx1(-1, "%sFatal ERROR:",
+					(ftape_errors[*error].fatal ? "" : "Non-"));
+				TRACEx1(-1, "%s ...", ftape_errors[*error].message);
+			} else {
+				TRACE(-1, "Unknown ERROR !");
+			}
+			if (*command >= 0 && *command < NR_ITEMS(qic117_cmds) &&
+			    qic117_cmds[*command].name != NULL) {
+				TRACEx1(-1, "... caused by command \'%s\'",
+					qic117_cmds[*command].name);
+			} else {
+				TRACEi(-1, "... caused by unknown command", *command);
+			}
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_in_error_state(int status)
+{
+	TRACE_FUN(8, "ftape_in_error_state");
+	int result = 0;
+
+	if ((status & QIC_STATUS_READY) && (status & QIC_STATUS_ERROR)) {
+		TRACE(2, "warning: error status set!");
+		result = 1;
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+static int ftape_report_configuration(qic_model * model, int *rate,
+				      int *qic_std, int *tape_len)
+{
+	int result;
+	int config;
+	int status;
+
+	TRACE_FUN(8, "ftape_report_configuration");
+	result = ftape_report_operation(&config, QIC_REPORT_DRIVE_CONFIGURATION, 8);
+	if (result < 0) {
+		*model = prehistoric;
+		*rate = QIC_CONFIG_RATE_500;
+		*qic_std = QIC_TAPE_QIC40;
+		*tape_len = 205;
+		result = 0;
+	} else {
+		*rate = (config & QIC_CONFIG_RATE_MASK) >> QIC_CONFIG_RATE_SHIFT;
+		result = ftape_report_operation(&status, QIC_REPORT_TAPE_STATUS, 8);
+		if (result < 0) {
+			/* pre- QIC117 rev C spec. drive, QIC_CONFIG_80 bit is valid.
+			 */
+			*qic_std = (config & QIC_CONFIG_80) ? QIC_TAPE_QIC80 : QIC_TAPE_QIC40;
+			*tape_len = (config & QIC_CONFIG_LONG) ? 307 : 205;
+			*model = pre_qic117c;
+			result = 0;
+		} else {
+			*model = post_qic117b;
+			TRACEx1(8, "report tape status result = %02x", status);
+			/* post- QIC117 rev C spec. drive, QIC_CONFIG_80 bit is invalid.
+			 */
+			switch (status & QIC_TAPE_STD_MASK) {
+			case QIC_TAPE_QIC40:
+			case QIC_TAPE_QIC80:
+			case QIC_TAPE_QIC3020:
+			case QIC_TAPE_QIC3010:
+				*qic_std = status & QIC_TAPE_STD_MASK;
+				break;
+			default:
+				*qic_std = -1;
+				break;
+			}
+			switch (status & QIC_TAPE_LEN_MASK) {
+			case QIC_TAPE_205FT:
+				/* Unfortunately the new QIC-117 rev G standard shows
+				 * no way to discriminate between 205 and 425 ft tapes.
+				 * The obvious way seems not to be used: the QIC_CONFIG_LONG
+				 * bit isn't used for this (on all drives ?).
+				 */
+				if (config & QIC_CONFIG_LONG) {
+					*tape_len = 425;	/* will this ever execute ??? */
+				} else {
+					*tape_len = 0;	/* length unknown: 205 or 425 ft. */
+				}
+				break;
+			case QIC_TAPE_307FT:
+				*tape_len = 307;
+				break;
+			case QIC_TAPE_400FT:
+				/*
+				 * Trouble! Iomega Ditto 800 and Conner TST800R drives reports
+				 * 400ft for 750ft tapes. Yuck, yuck, yuck.  Since the value
+				 * is only used to compute a timeout value, the largest of the
+				 * two is used.
+				 */
+				*tape_len = 750;	/* either 400 or 750 ft. */
+				break;
+			case QIC_TAPE_1100FT:
+				*tape_len = 1100;
+				break;
+			case QIC_TAPE_FLEX:
+				*tape_len = 0;
+				break;
+			default:
+				*tape_len = -1;
+				break;
+			}
+			if (*qic_std == -1 || *tape_len == -1) {
+				TRACE(2, "post qic-117b spec drive with unknown tape");
+				result = -EIO;
+			} else {
+				result = 0;
+			}
+		}
+	}
+	TRACE_EXIT;
+	return (result < 0) ? -EIO : 0;
+}
+
+int ftape_report_rom_version(int *version)
+{
+	int result;
+
+	result = ftape_report_operation(version, QIC_REPORT_ROM_VERSION, 8);
+	return (result < 0) ? -EIO : 0;
+}
+
+int ftape_report_signature(int *signature)
+{
+	int result;
+
+	result = ftape_command(28);
+	result = ftape_report_operation(signature, 9, 8);
+	result = ftape_command(30);
+	return (result < 0) ? -EIO : 0;
+}
+
+void ftape_report_vendor_id(unsigned int *id)
+{
+	TRACE_FUN(8, "ftape_report_vendor_id");
+	int result;
+
+	/*
+	 *    We'll try to get a vendor id from the drive.
+	 *    First according to the QIC-117 spec, a 16-bit id is requested.
+	 *    If that fails we'll try an 8-bit version, otherwise we'll try
+	 *    an undocumented query.
+	 */
+	result = ftape_report_operation((int *) id, QIC_REPORT_VENDOR_ID, 16);
+	if (result < 0) {
+		result = ftape_report_operation((int *) id, QIC_REPORT_VENDOR_ID, 8);
+		if (result < 0) {
+			/*  The following is an undocumented call found in the CMS code.
+			 */
+			result = ftape_report_operation((int *) id, 24, 8);
+			if (result < 0) {
+				*id = UNKNOWN_VENDOR;
+			} else {
+				TRACEx1(4, "got old 8 bit id: %04x", *id);
+				*id |= 0x20000;
+			}
+		} else {
+			TRACEx1(4, "got 8 bit id: %04x", *id);
+			*id |= 0x10000;
+		}
+	} else {
+		TRACEx1(4, "got 16 bit id: %04x", *id);
+	}
+	if (*id == 0x0047) {
+		int version;
+		int sign;
+
+		result = ftape_report_rom_version(&version);
+		if (result < 0) {
+			TRACE(-1, "report rom version failed");
+			TRACE_EXIT;
+			return;
+		}
+		TRACEx1(4, "CMS rom version: %d", version);
+		ftape_command(QIC_ENTER_DIAGNOSTIC_1);
+		ftape_command(QIC_ENTER_DIAGNOSTIC_1);
+		result = ftape_report_operation(&sign, 9, 8);
+		if (result < 0) {
+			int error, command;
+
+			ftape_report_error(&error, &command, 1);
+			ftape_command(QIC_ENTER_PRIMARY_MODE);
+			TRACE_EXIT;
+			return;	/* faalt hier ! */
+		} else {
+			TRACEx1(4, "CMS signature: %02x", sign);
+		}
+		if (sign == 0xa5) {
+			result = ftape_report_operation(&sign, 37, 8);
+			if (result < 0) {
+				if (version >= 63) {
+					*id = 0x8880;
+					TRACE(4, "This is an Iomega drive !");
+				} else {
+					*id = 0x0047;
+					TRACE(4, "This is a real CMS drive !");
+				}
+			} else {
+				*id = 0x0047;
+				TRACEx1(4, "CMS status: %d", sign);
+			}
+		} else {
+			*id = UNKNOWN_VENDOR;
+		}
+		ftape_command(QIC_ENTER_PRIMARY_MODE);
+	}
+	TRACE_EXIT;
+}
+
+void ftape_set_rate_test(int *supported)
+{
+	TRACE_FUN(8, "ftape_set_rate_test");
+	int error;
+	int command;
+	int i;
+	int result;
+	int status;
+
+	/*  Check if the drive does support the select rate command by testing
+	 *  all different settings.
+	 *  If any one is accepted we assume the command is supported, else not.
+	 */
+	*supported = 0;
+	for (i = 0; i < NR_ITEMS(rates); ++i) {
+		result = ftape_command(QIC_SELECT_RATE);
+		if (result >= 0) {
+			result = ftape_parameter_wait(rates[i].drive_code,
+						    1 * SECOND, &status);
+			if (result >= 0) {
+				if (status & QIC_STATUS_ERROR) {
+					result = ftape_report_error(&error, &command, 0);
+				} else {
+					*supported = 1;		/* did accept a request */
+				}
+			}
+		}
+	}
+	TRACEx1(4, "Select Rate command is%s supported",
+		*supported ? "" : " not");
+	TRACE_EXIT;
+}
+
+int ftape_set_data_rate(int new_rate)
+{
+	TRACE_FUN(8, "ftape_set_data_rate");
+	int status;
+	int result;
+	int data_rate;
+	qic_model model;
+	int supported;
+	static int first_time = 1;
+
+	if (first_time) {
+		ftape_set_rate_test(&supported);
+		first_time = 0;
+	}
+	if (rates[new_rate].fdc_code == -1) {
+		TRACEx1(4, "%sb/s data rate not supported by the fdc",
+			rates[new_rate].text);
+		result = -EINVAL;
+	} else {
+		int error = 0;
+		int command;
+
+		result = ftape_command(QIC_SELECT_RATE);
+		if (result >= 0) {
+			result = ftape_parameter_wait(rates[new_rate].drive_code,
+						    1 * SECOND, &status);
+			result = ftape_report_raw_drive_status(&status);
+			if (result >= 0 && (status & QIC_STATUS_ERROR)) {
+				result = ftape_report_error(&error, &command, 0);
+				if (result >= 0 && supported &&
+				    error == 31 && command == QIC_SELECT_RATE) {
+					result = -EINVAL;
+				}
+			}
+		}
+		if (result >= 0) {
+			result = ftape_report_configuration(&model, &data_rate,
+						    &qic_std, &tape_len);
+			if (result >= 0 && data_rate != rates[new_rate].drive_code) {
+				result = -EINVAL;
+			}
+		}
+		if (result < 0) {
+			TRACEx1(4, "could not set %sb/s data rate", rates[new_rate].text);
+		} else {
+			TRACEx2(2, "%s drive @ %sb/s",
+				(model == prehistoric) ? "prehistoric" :
+				((model == pre_qic117c) ? "pre QIC-117C" :
+				 ((model == post_qic117b) ? "post QIC-117B" : "post QIC-117D")),
+				rates[new_rate].text);
+			if (tape_len == 0) {
+				TRACEx1(2, "unknown length QIC-%s tape",
+				     (qic_std == QIC_TAPE_QIC40) ? "40" :
+				    ((qic_std == QIC_TAPE_QIC80) ? "80" :
+				     ((qic_std == QIC_TAPE_QIC3010) ? "3010" : "3020")));
+			} else {
+				TRACEx2(2, "%d ft. QIC-%s tape",
+					tape_len,
+				     (qic_std == QIC_TAPE_QIC40) ? "40" :
+				    ((qic_std == QIC_TAPE_QIC80) ? "80" :
+				     ((qic_std == QIC_TAPE_QIC3010) ? "3010" : "3020")));
+			}
+			/*
+			 *  Set data rate and write precompensation as specified:
+			 *
+			 *            |  QIC-40/80  | QIC-3010/3020
+			 *   rate     |   precomp   |    precomp
+			 *  ----------+-------------+--------------
+			 *  250 Kbps. |   250 ns.   |     0 ns.
+			 *  500 Kbps. |   125 ns.   |     0 ns.
+			 *    1 Mbps. |    42 ns.   |     0 ns.
+			 *    2 Mbps  |      N/A    |     0 ns.
+			 */
+			if (qic_std == QIC_TAPE_QIC40 || qic_std == QIC_TAPE_QIC80) {
+				fdc_set_write_precomp(rates[new_rate].precomp);
+			} else {
+				fdc_set_write_precomp(0);
+			}
+			fdc_set_data_rate(rates[new_rate].fdc_code);
+			ftape_data_rate = new_rate;	/* store rate set */
+		}
+	}
+	if (result < 0 && result != -EINVAL) {
+		result = -EIO;
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+/*      Seek the head to the specified track.
+ */
+int ftape_seek_head_to_track(int track)
+{
+	TRACE_FUN(8, "ftape_seek_head_to_track");
+	int status;
+	int result;
+
+	location.track = -1;	/* remains set in case of error */
+	if (track < 0 || track >= tracks_per_tape) {
+		TRACE(-1, "track out of bounds");
+		result = -EINVAL;
+	} else {
+		TRACEx1(5, "seeking track %d", track);
+		result = ftape_command(QIC_SEEK_HEAD_TO_TRACK);
+		if (result < 0) {
+			TRACE(1, "ftape_command failed");
+		} else {
+			result = ftape_parameter_wait(track, timeout.head_seek, &status);
+			if (result < 0) {
+				TRACE(1, "ftape_parameter_wait failed");
+			} else {
+				location.track = track;
+				might_be_off_track = 0;
+			}
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_wakeup_drive(wake_up_types method)
+{
+	TRACE_FUN(8, "ftape_wakeup_drive");
+	int result;
+	int status;
+	int motor_on = 0;
+
+	switch (method) {
+	case wake_up_colorado:
+		result = ftape_command(QIC_PHANTOM_SELECT);
+		if (result == 0) {
+			result = ftape_parameter( /* unit */ 0);
+		}
+		break;
+	case wake_up_mountain:
+		result = ftape_command(QIC_SOFT_SELECT);
+		if (result == 0) {
+			ftape_sleep(MILLISECOND);	/* NEEDED */
+			result = ftape_parameter(18);
+		}
+		break;
+	case wake_up_insight:
+		ftape_sleep(100 * MILLISECOND);
+		motor_on = 1;
+		fdc_motor(motor_on);	/* enable is done by motor-on */
+	case no_wake_up:
+		result = 0;
+		break;
+	default:
+		result = -ENODEV;	/* unknown wakeup method */
+	}
+	/*  If wakeup succeeded we should't get and error here..
+	 */
+	if (result == 0) {
+		result = ftape_report_raw_drive_status(&status);
+		if (result < 0 && motor_on) {
+			fdc_motor(0);	/* motor off if failed */
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_put_drive_to_sleep(vendor_struct drive_type)
+{
+	TRACE_FUN(8, "ftape_put_drive_to_sleep");
+	int result;
+
+	switch (drive_type.wake_up) {
+	case wake_up_colorado:
+		result = ftape_command(QIC_PHANTOM_DESELECT);
+		break;
+	case wake_up_mountain:
+		result = ftape_command(QIC_SOFT_DESELECT);
+		break;
+	case wake_up_insight:
+		fdc_motor(0);	/* enable is done by motor-on */
+	case no_wake_up:	/* no wakeup / no sleep ! */
+		result = 0;
+		break;
+	default:
+		result = -ENODEV;	/* unknown wakeup method */
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_reset_drive(void)
+{
+	TRACE_FUN(8, "ftape_reset_drive");
+	int result = 0;
+	int status;
+	int err_code;
+	int err_command;
+	int i;
+
+	/*    We want to re-establish contact with our drive.
+	 *    Fire a number of reset commands (single step pulses)
+	 *    and pray for success.
+	 */
+	for (i = 0; i < 2; ++i) {
+		TRACE(5, "Resetting fdc");
+		fdc_reset();
+		ftape_sleep(10 * MILLISECOND);
+		TRACE(5, "Reset command to drive");
+		result = ftape_command(QIC_RESET);
+		if (result == 0) {
+			ftape_sleep(1 * SECOND);	/* drive not accessible during 1 second */
+			TRACE(5, "Re-selecting drive");
+			/*  Strange, the QIC-117 specs don't mention this but the
+			 *  drive gets deselected after a soft reset !
+			 *  So we need to enable it again.
+			 */
+			result = ftape_wakeup_drive(drive_type.wake_up);
+			if (result < 0) {
+				TRACE(1, "Wakeup failed !");
+			}
+			TRACE(5, "Waiting until drive gets ready");
+			result = ftape_ready_wait(timeout.reset, &status);
+			if (result == 0 && status & QIC_STATUS_ERROR) {
+				result = ftape_report_error(&err_code, &err_command, 1);
+				if (result == 0 && err_code == 27) {
+					/* Okay, drive saw reset command and responded as it should
+					 */
+					break;
+				} else {
+					result = -EIO;
+				}
+			} else {
+				result = -EIO;
+			}
+		}
+		if (current->signal & _DONT_BLOCK) {
+			TRACE(1, "aborted by non-blockable signal");
+			result = -EINTR;
+			break;	/* exit on signal */
+		}
+	}
+	if (result != 0) {
+		TRACE(1, "General failure to reset tape drive");
+	} else {
+		/*  Restore correct settings
+		 */
+		ftape_set_data_rate(ftape_data_rate);	/* keep original rate */
+	}
+	TRACE_EXIT;
+	return result;
+}
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/ftape-io.h linux/drivers/char/ftape/ftape-io.h
--- v1.3.71/linux/drivers/char/ftape/ftape-io.h	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/ftape-io.h	Wed Mar  6 15:07:20 1996
@@ -0,0 +1,77 @@
+#ifndef _FTAPE_IO_H
+#define _FTAPE_IO_H
+
+/*
+ * Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ $Source: /home/bas/distr/ftape-2.03b/RCS/ftape-io.h,v $
+ $Author: bas $
+ *
+ $Revision: 1.36 $
+ $Date: 1995/05/06 16:11:53 $
+ $State: Beta $
+ *
+ *      This file contains definitions for the glue part
+ *      of the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include "vendors.h"
+
+typedef struct {
+	unsigned seek;
+	unsigned reset;
+	unsigned rewind;
+	unsigned head_seek;
+	unsigned stop;
+	unsigned pause;
+} timeout_table;
+
+/*
+ *      ftape-io.c defined global vars.
+ */
+extern timeout_table timeout;
+extern int qic_std;
+extern int tape_len;
+extern volatile int current_command;
+extern const struct qic117_command_table qic117_cmds[];
+extern int might_be_off_track;
+
+/*
+ *      ftape-io.c defined global functions.
+ */
+extern void udelay(int usecs);
+extern int udelay_calibrate(void);
+extern void ftape_sleep(unsigned int time);
+extern void ftape_report_vendor_id(unsigned int *id);
+extern int ftape_command(int command);
+extern int ftape_command_wait(int command, int timeout, int *status);
+extern int ftape_report_drive_status(int *status);
+extern int ftape_report_raw_drive_status(int *status);
+extern int ftape_report_status(int *status);
+extern int ftape_interrupt_wait(int time);
+extern int ftape_ready_wait(int timeout, int *status);
+extern int ftape_seek_head_to_track(int track);
+extern int ftape_parameter(int command);
+extern int ftape_in_error_state(int status);
+extern int ftape_set_data_rate(int rate);
+extern int ftape_report_error(int *error, int *command, int report);
+extern int ftape_reset_drive(void);
+extern int ftape_put_drive_to_sleep(vendor_struct drive_type);
+extern int ftape_wakeup_drive(wake_up_types method);
+
+#endif
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/ftape-read.c linux/drivers/char/ftape/ftape-read.c
--- v1.3.71/linux/drivers/char/ftape/ftape-read.c	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/ftape-read.c	Wed Mar  6 15:07:20 1996
@@ -0,0 +1,678 @@
+/*
+ *      Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ $Source: /home/bas/distr/ftape-2.03b/RCS/ftape-read.c,v $
+ $Author: bas $
+ *
+ $Revision: 1.30 $
+ $Date: 1995/05/27 08:54:21 $
+ $State: Beta $
+ *
+ *      This file contains the reading code
+ *      for the QIC-117 floppy-tape driver for Linux.
+ */
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/ftape.h>
+#include <asm/segment.h>
+
+#include "tracing.h"
+#include "ftape-read.h"
+#include "qic117.h"
+#include "ftape-io.h"
+#include "ftape-ctl.h"
+#include "ftape-rw.h"
+#include "ftape-write.h"
+#include "ftape-eof.h"
+#include "ecc.h"
+#include "ftape-bsm.h"
+
+/*      Global vars.
+ */
+
+/*      Local vars.
+ */
+int buf_pos_rd = 0;
+int buf_len_rd = 0;
+
+void ftape_zap_read_buffers(void)
+{
+	int i;
+
+	for (i = 0; i < NR_BUFFERS; ++i) {
+		/*
+		 * changed to "fit" with dynamic allocation of tape_buffer. --khp
+		 */
+		buffer[i].address = tape_buffer[i];
+		buffer[i].status = waiting;
+		buffer[i].bytes = 0;
+		buffer[i].skip = 0;
+		buffer[i].retry = 0;
+	}
+	buf_len_rd = 0;
+	buf_pos_rd = 0;
+	eof_mark = 0;
+	ftape_state = idle;
+}
+
+static unsigned long convert_sector_map(buffer_struct * buff)
+{
+	TRACE_FUN(8, "convert_sector_map");
+	int i = 0;
+	unsigned long bad_map = get_bad_sector_entry(buff->segment_id);
+	unsigned long src_map = buff->soft_error_map | buff->hard_error_map;
+	unsigned long dst_map = 0;
+
+	if (bad_map || src_map) {
+		TRACEx1(5, "bad_map = 0x%08lx", bad_map);
+		TRACEx1(5, "src_map = 0x%08lx", src_map);
+	}
+	while (bad_map) {
+		while ((bad_map & 1) == 0) {
+			if (src_map & 1) {
+				dst_map |= (1 << i);
+			}
+			src_map >>= 1;
+			bad_map >>= 1;
+			++i;
+		}
+		/* (bad_map & 1) == 1 */
+		src_map >>= 1;
+		bad_map >>= 1;
+	}
+	if (src_map) {
+		dst_map |= (src_map << i);
+	}
+	if (dst_map) {
+		TRACEx1(5, "dst_map = 0x%08lx", dst_map);
+	}
+	TRACE_EXIT;
+	return dst_map;
+}
+
+int correct_and_copy(unsigned int tail, byte * destination)
+{
+	TRACE_FUN(8, "correct_and_copy");
+	struct memory_segment mseg;
+	int result;
+	BAD_SECTOR read_bad;
+
+	mseg.read_bad = convert_sector_map(&buffer[tail]);
+	mseg.marked_bad = 0;	/* not used... */
+	mseg.blocks = buffer[tail].bytes / SECTOR_SIZE;
+	mseg.data = buffer[tail].address;
+	/*    If there are no data sectors we can skip this segment.
+	 */
+	if (mseg.blocks <= 3) {
+		TRACE(4, "empty segment");
+		TRACE_EXIT;
+		return 0;
+	}
+	read_bad = mseg.read_bad;
+	history.crc_errors += count_ones(read_bad);
+	result = ecc_correct_data(&mseg);
+	if (read_bad != 0 || mseg.corrected != 0) {
+		TRACElx(4, "crc error map:", read_bad);
+		TRACElx(4, "corrected map:", mseg.corrected);
+		history.corrected += count_ones(mseg.corrected);
+	}
+	if (result == ECC_CORRECTED || result == ECC_OK) {
+		if (result == ECC_CORRECTED) {
+			TRACEi(3, "ecc corrected segment:", buffer[tail].segment_id);
+		}
+		memcpy(destination, mseg.data, (mseg.blocks - 3) * SECTOR_SIZE);
+		if ((read_bad ^ mseg.corrected) & mseg.corrected) {
+			/* sectors corrected without crc errors set */
+			history.crc_failures++;
+		}
+		TRACE_EXIT;
+		return (mseg.blocks - 3) * SECTOR_SIZE;
+	} else {
+		TRACEi(1, "ecc failure on segment", buffer[tail].segment_id);
+		history.ecc_failures++;
+		TRACE_EXIT;
+		return -EAGAIN;	/* should retry */
+	}
+	TRACE_EXIT;
+	return 0;
+}
+
+/*      Read given segment into buffer at address.
+ */
+int read_segment(unsigned segment_id, byte * address, int *eof_mark,
+		 int read_ahead)
+{
+	TRACE_FUN(5, "read_segment");
+	int read_done = 0;
+	int result = 0;
+	int bytes_read = 0;
+	int retry = 0;
+
+	TRACEi(5, "segment_id =", segment_id);
+	if (ftape_state != reading) {
+		if (ftape_state == writing) {
+			ftape_flush_buffers();	/* flush write buffer */
+			TRACE(5, "calling ftape_abort_operation");
+			result = ftape_abort_operation();
+			if (result < 0) {
+				TRACE(1, "ftape_abort_operation failed");
+				TRACE_EXIT;
+				return -EIO;
+			}
+		} else {
+			/* clear remaining read buffers */
+			ftape_zap_read_buffers();
+		}
+		ftape_state = reading;
+	}
+	if (segment_id >= segments_per_track * tracks_per_tape) {
+		TRACE(5, "reading past end of tape");
+		TRACE_EXIT;
+		return -ENOSPC;
+	}
+	for (;;) {
+		/*    Search all full buffers for the first matching the wanted segment.
+		 *    Clear other buffers on the fly.
+		 */
+		while (!read_done && buffer[tail].status == done) {
+			if (buffer[tail].segment_id == segment_id) {
+				unsigned eof_sector;
+				unsigned sector_count = 0;
+				unsigned long bsm = get_bad_sector_entry(segment_id);
+				int i;
+
+				/*        If out buffer is already full, return its contents.
+				 */
+				if (buffer[tail].deleted) {
+					TRACEi(5, "found segment in cache :", segment_id);
+					TRACE_EXIT;
+					/*  Return a value that read_header_segment understands.
+					 *  As this should only occur when searching for the header
+					 *  segments it shouldn't be misinterpreted elsewhere.
+					 */
+					return 0;
+				}
+				TRACEi(5, "found segment in cache :", segment_id);
+				eof_sector = check_for_eof(segment_id);
+				if (eof_sector > 0) {
+					TRACEi(5, "end of file mark in sector:", eof_sector);
+					for (i = 1; i < eof_sector; ++i) {
+						if ((bsm & 1) == 0) {
+							++sector_count;
+						}
+						bsm >>= 1;
+					}
+					*eof_mark = 1;
+				}
+				if (eof_sector != 1) {	/* not found or gt 1 */
+					result = correct_and_copy(tail, address);
+					TRACEi(5, "segment contains (bytes) :", result);
+					if (result < 0) {
+						if (result != -EAGAIN) {
+							TRACE_EXIT;
+							return result;
+						}
+						/* keep read_done == 0, will trigger ftape_abort_operation
+						 * because reading wrong segment.
+						 */
+						TRACE(1, "ecc failed, retry");
+						++retry;
+					} else {
+						read_done = 1;
+					}
+				} else {
+					read_done = 1;
+				}
+				if (eof_sector > 0) {
+					bytes_read = sector_count * SECTOR_SIZE;
+					TRACEi(5, "partial read count:", bytes_read);
+				} else {
+					bytes_read = result;
+				}
+			} else {
+				TRACEi(5, "zapping segment in cache :", buffer[tail].segment_id);
+			}
+			buffer[tail].status = waiting;
+			next_buffer(&tail);
+		}
+		if (!read_done && buffer[tail].status == reading) {
+			if (buffer[tail].segment_id == segment_id) {
+				int result = wait_segment(reading);
+				if (result < 0) {
+					if (result == -EINTR) {
+						TRACE_EXIT;
+						return result;
+					}
+					TRACE(1, "wait_segment failed while reading");
+					ftape_abort_operation();
+				}
+			} else {
+				/*        We're reading the wrong segment, stop runner.
+				 */
+				ftape_abort_operation();
+			}
+		}
+		/*    if just passed the last segment on a track, wait for BOT or EOT mark.
+		 */
+		if (runner_status == logical_eot) {
+			int status;
+			result = ftape_ready_wait(timeout.seek, &status);
+			if (result < 0) {
+				TRACE(1, "ftape_ready_wait waiting for eot/bot failed");
+			}
+			if ((status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT)) == 0) {
+				TRACE(1, "eot/bot not reached");
+			}
+			runner_status = end_of_tape;
+		}
+		/*    should runner stop ?
+		 */
+		if (runner_status == aborting || runner_status == buffer_overrun ||
+		    runner_status == end_of_tape) {
+			if (runner_status != end_of_tape &&
+			 !(runner_status == aborting && !tape_running)) {
+				ftape_dumb_stop();
+			}
+			if (runner_status == aborting) {
+				if (buffer[head].status == reading || buffer[head].status == error) {
+					if (buffer[head].status == error) {
+						history.defects += count_ones(buffer[head].hard_error_map);
+					}
+					buffer[head].status = waiting;
+				}
+			}
+			runner_status = idle;	/* aborted ? */
+		}
+		/*    If segment to read is empty, do not start runner for it,
+		 *    but wait for next read call.
+		 */
+		if (get_bad_sector_entry(segment_id) == EMPTY_SEGMENT) {
+			bytes_read = 0;		/* flag empty segment */
+			read_done = 1;
+		}
+		/*  Allow escape from this loop on signal !
+		 */
+		if (current->signal & _DONT_BLOCK) {
+			TRACE(2, "interrupted by non-blockable signal");
+			TRACE_EXIT;
+			return -EINTR;
+		}
+		/*    If we got a segment: quit, or else retry up to limit.
+		 */
+		if (read_done) {
+			break;
+		}
+		if (retry > RETRIES_ON_ECC_ERROR) {
+			history.defects++;
+			TRACE(1, "too many retries on ecc failure");
+			TRACE_EXIT;
+			return -ENODATA;
+		}
+		/*    Now at least one buffer is empty !
+		 *    Restart runner & tape if needed.
+		 */
+		TRACEx3(8, "head: %d, tail: %d, runner_status: %d",
+			head, tail, runner_status);
+		TRACEx2(8, "buffer[].status, [head]: %d, [tail]: %d",
+			buffer[head].status, buffer[tail].status);
+		if (buffer[tail].status == waiting) {
+			setup_new_segment(&buffer[head], segment_id, -1);
+			if (!read_ahead) {
+				buffer[head].next_segment = 0;	/* disable read-ahead */
+			}
+			calc_next_cluster(&buffer[head]);
+			if (runner_status == idle) {
+				result = ftape_start_tape(segment_id,
+					     buffer[head].sector_offset);
+				if (result < 0) {
+					TRACEx1(1, "Error: segment %d unreachable", segment_id);
+					TRACE_EXIT;
+					return result;
+				}
+				runner_status = running;
+			}
+			buffer[head].status = reading;
+			setup_fdc_and_dma(&buffer[head], FDC_READ);
+		}
+	}
+	if (read_done) {
+		TRACE_EXIT;
+		return bytes_read;
+	} else {
+		TRACE(1, "too many retries");
+		TRACE_EXIT;
+		return -EIO;
+	}
+}
+
+int read_header_segment(byte * address)
+{
+	TRACE_FUN(5, "read_header_segment");
+	int i;
+	int result;
+	int header_segment = -1;
+	unsigned int max_floppy_side;
+	unsigned int max_floppy_track;
+	unsigned int max_floppy_sector;
+	int first_failed = 0;
+	int status;
+	int new_tape_len;
+
+	result = ftape_report_drive_status(&status);
+	if (result < 0) {
+		TRACE(1, "error: error_status or report failure");
+		TRACE_EXIT;
+		return -EIO;
+	}
+	TRACE(5, "reading...");
+	ftape_last_segment.id = 68;	/* will allow us to read the header ! */
+	/*  We're looking for the first header segment.
+	 *  A header segment cannot contain bad sectors, therefor at the
+	 *  tape start, segments with bad sectors are (according to QIC-40/80)
+	 *  written with deleted data marks and must be skipped.
+	 */
+	used_header_segment = -1;
+	result = 0;
+	for (header_segment = 0;
+	     header_segment < ftape_last_segment.id && result == 0;
+	     ++header_segment) {
+		/*  Set no read-ahead, the isr will force read-ahead whenever
+		 *  it encounters deleted data !
+		 */
+		result = read_segment(header_segment, address, &status, 0);
+		if (result < 0 && !first_failed) {
+			TRACE(1, "header segment damaged, trying backup");
+			first_failed = 1;
+			result = 0;	/* force read of next (backup) segment */
+		}
+	}
+	if (result < 0 || header_segment >= ftape_last_segment.id) {
+		TRACE(1, "no readable header segment found");
+		TRACE_EXIT;
+		return -EIO;
+	}
+	result = ftape_abort_operation();
+	if (result < 0) {
+		TRACE(1, "ftape_abort_operation failed");
+		TRACE_EXIT;
+		return -EIO;
+	}
+	if (GET4(address, 0) != 0xaa55aa55) {
+		TRACE(1, "wrong signature in header segment");
+		TRACE_EXIT;
+		return -EIO;
+	}
+	header_segment_1 = GET2(address, 6);
+	header_segment_2 = GET2(address, 8);
+	TRACEx2(2, "header segments are %d and %d",
+		header_segment_1, header_segment_2);
+	used_header_segment = (first_failed) ? header_segment_2 : header_segment_1;
+
+	/*    Verify tape parameters...
+	 *    QIC-40/80 spec:                 tape_parameters:
+	 *
+	 *    segments-per-track              segments_per_track
+	 *    tracks-per-cartridge            tracks_per_tape
+	 *    max-floppy-side                 (segments_per_track *
+	 *                                    tracks_per_tape - 1) /
+	 *                                    segments_per_head
+	 *    max-floppy-track                segments_per_head /
+	 *                                    segments_per_cylinder - 1
+	 *    max-floppy-sector               segments_per_cylinder *
+	 *                                    SECTORS_PER_SEGMENT
+	 */
+	format_code = (format_type) * (address + 4);
+	segments_per_track = GET2(address, 24);
+	tracks_per_tape = *(address + 26);
+	max_floppy_side = *(address + 27);
+	max_floppy_track = *(address + 28);
+	max_floppy_sector = *(address + 29);
+	TRACEx6(4, "(fmt/spt/tpc/fhm/ftm/fsm) = %d/%d/%d/%d/%d/%d",
+		format_code, segments_per_track, tracks_per_tape,
+		max_floppy_side, max_floppy_track, max_floppy_sector);
+	new_tape_len = tape_len;
+	switch (format_code) {
+	case fmt_425ft:
+		new_tape_len = 425;
+		break;
+	case fmt_normal:
+		if (tape_len == 0) {	/* otherwise 307 ft */
+			new_tape_len = 205;
+		}
+		break;
+	case fmt_1100ft:
+		new_tape_len = 1100;
+		break;
+	case fmt_wide:{
+			int segments_per_1000_inch = 1;		/* non-zero default for switch */
+			switch (qic_std) {
+			case QIC_TAPE_QIC40:
+				segments_per_1000_inch = 332;
+				break;
+			case QIC_TAPE_QIC80:
+				segments_per_1000_inch = 488;
+				break;
+			case QIC_TAPE_QIC3010:
+				segments_per_1000_inch = 730;
+				break;
+			case QIC_TAPE_QIC3020:
+				segments_per_1000_inch = 1430;
+				break;
+			}
+			new_tape_len = (1000 * segments_per_track +
+					(segments_per_1000_inch - 1)) / segments_per_1000_inch;
+			break;
+		}
+	default:
+		TRACE(1, "unknown tape format, please report !");
+		TRACE_EXIT;
+		return -EIO;
+	}
+	if (new_tape_len != tape_len) {
+		tape_len = new_tape_len;
+		TRACEx1(1, "calculated tape length is %d ft", tape_len);
+		ftape_calc_timeouts();
+	}
+	if (segments_per_track == 0 && tracks_per_tape == 0 &&
+	    max_floppy_side == 0 && max_floppy_track == 0 &&
+	    max_floppy_sector == 0) {
+		/*  QIC-40 Rev E and earlier has no values in the header.
+		 */
+		segments_per_track = 68;
+		tracks_per_tape = 20;
+		max_floppy_side = 1;
+		max_floppy_track = 169;
+		max_floppy_sector = 128;
+	}
+	/*  This test will compensate for the wrong parameter on tapes
+	 *  formatted by Conner software.
+	 */
+	if (segments_per_track == 150 &&
+	    tracks_per_tape == 28 &&
+	    max_floppy_side == 7 &&
+	    max_floppy_track == 149 &&
+	    max_floppy_sector == 128) {
+		TRACE(-1, "the famous CONNER bug: max_floppy_side off by one !");
+		max_floppy_side = 6;
+	}
+	/*  This test will compensate for the wrong parameter on tapes
+	 *  formatted by Colorado Windows software.
+	 */
+	if (segments_per_track == 150 &&
+	    tracks_per_tape == 28 &&
+	    max_floppy_side == 6 &&
+	    max_floppy_track == 150 &&
+	    max_floppy_sector == 128) {
+		TRACE(-1, "the famous Colorado bug: max_floppy_track off by one !");
+		max_floppy_track = 149;
+	}
+	segments_per_head = ((max_floppy_sector / SECTORS_PER_SEGMENT) *
+			     (max_floppy_track + 1));
+	/*
+	 *    Verify drive_configuration with tape parameters
+	 */
+	if (segments_per_head == 0 || segments_per_cylinder == 0 ||
+	  ((segments_per_track * tracks_per_tape - 1) / segments_per_head
+	   != max_floppy_side) ||
+	    (segments_per_head / segments_per_cylinder - 1 != max_floppy_track) ||
+	(segments_per_cylinder * SECTORS_PER_SEGMENT != max_floppy_sector)
+#ifdef TESTING
+	    || (format_code == 4 && (max_floppy_track != 254 || max_floppy_sector != 128))
+#endif
+	    ) {
+		TRACE(1, "Tape parameters inconsistency, please report");
+		TRACE_EXIT;
+		return -EIO;
+	}
+	first_data_segment = GET2(address, 10);		/* first data segment */
+	TRACEi(4, "first data segment:", first_data_segment);
+	extract_bad_sector_map(address);
+	/*  Find the highest segment id that allows still one full
+	 *  deblock_buffer to be written to tape.
+	 */
+	ftape_last_segment.size = 0;
+	for (i = segments_per_track * tracks_per_tape - 1; i >= 0; --i) {
+		int space = SECTORS_PER_SEGMENT - 3 - count_ones(get_bad_sector_entry(i));
+		if (space > 0) {
+			ftape_last_segment.size += space;	/* sectors free */
+			ftape_last_segment.free = (ftape_last_segment.size -
+				   sizeof(deblock_buffer) / SECTOR_SIZE);
+			if (ftape_last_segment.free >= 0) {
+				ftape_last_segment.id = i;
+				TRACEx2(4, "`last' segment is %d, %d Kb",
+					ftape_last_segment.id, ftape_last_segment.size);
+				break;
+			}
+		}
+	}
+	/* Copy the failed sector log into our local buffer.
+	 */
+	if (!ftape_validate_label(&deblock_buffer[30])) {
+		TRACE(-1, "This tape has no `Linux raw format' label,\n"
+		      "***** Use `mt' to erase this tape if you want to use file marks !");
+	} else {
+		extract_file_marks(address);
+	}
+	ftape_reset_position();
+	TRACE_EXIT;
+	return 0;
+}
+
+int _ftape_read(char *buff, int req_len)
+{
+	TRACE_FUN(5, "_ftape_read");
+	int result = 0;
+	int cnt;
+	int to_do = req_len;
+	static int remaining;
+	int bytes_read = 0;
+
+	if (ftape_offline || !formatted || no_tape) {
+		TRACEx3(-1, "offline = %d, formatted = %d, no_tape = %d",
+			ftape_offline, formatted, no_tape);
+		result = -EIO;
+	} else {
+		history.used |= 1;
+		if (first_data_segment == -1) {
+			result = read_header_segment(deblock_buffer);
+		}
+	}
+	if (result < 0) {
+		TRACE_EXIT;
+		return result;
+	}
+	/*  As GNU tar doesn't accept partial read counts when the multiple
+	 *  volume flag is set, we make sure to return the requested amount
+	 *  of data. Except, of course, at the end of the tape or file mark.
+	 */
+	while (to_do > 0) {	/* don't return with a partial count ! */
+		/*  If we're reading the `last' segment(s) on tape, make sure we don't
+		 *  get more than 29 Kb from it (As it only contains this much).
+		 *  This works only for sequential access, so random access should
+		 *  stay away from this `last' segment.
+		 *  Note: ftape_seg_pos points to the next segment what will be
+		 *        read, so it's one too hight here!
+		 */
+		if (!eof_mark && ftape_seg_pos - 1 >= ftape_last_segment.id) {
+			TRACEi(5, "remaining of last segment:", remaining);
+			if (to_do > remaining) {
+				to_do = remaining;	/* fake a smaller request */
+				TRACE(5, "clipped request to remaining");
+			}
+		}
+		while (!eof_mark && buf_len_rd == 0) {
+			/*  When starting to read the `last' segment, set remaining
+			 */
+			if (ftape_seg_pos == ftape_last_segment.id) {
+				remaining = sizeof(deblock_buffer);
+				TRACEi(5, "remaining set to:", remaining);
+			}
+			result = read_segment(ftape_seg_pos, deblock_buffer, &eof_mark, 1);
+			if (result < 0) {
+				if (result == -ENODATA) {
+					/*  Unable to recover tape data, return error and skip bad spot.
+					 */
+					++ftape_seg_pos;
+				}
+				TRACEx1(4, "read_segment result: %d", result);
+				TRACE_EXIT;
+				return result;
+			}
+			/*  Allow escape from this loop on signal !
+			 */
+			if (current->signal & _DONT_BLOCK) {
+				TRACE(2, "interrupted by non-blockable signal");
+				TRACE_EXIT;
+				return -EINTR;
+			}
+			buf_pos_rd = 0;
+			buf_len_rd = result;
+			++ftape_seg_pos;
+		}
+		/*  Take as much as we can use
+		 */
+		cnt = (buf_len_rd < to_do) ? buf_len_rd : to_do;
+		TRACEi(7, "nr bytes just read:", cnt);
+		if (cnt > 0) {
+			result = verify_area(VERIFY_WRITE, buff, cnt);
+			if (result) {
+				TRACEx1(1, "verify_area failed, exitcode = %d", result);
+				TRACE_EXIT;
+				return -EIO;
+			}
+			memcpy_tofs(buff, deblock_buffer + buf_pos_rd, cnt);
+			buff += cnt;
+			to_do -= cnt;	/* what's left from req_len */
+			remaining -= cnt;	/* what remains on this tape */
+			bytes_read += cnt;	/* what we got so far */
+			buf_pos_rd += cnt;	/* index in buffer */
+			buf_len_rd -= cnt;	/* remaining bytes in buffer */
+		}
+		if (eof_mark && buf_len_rd == 0) {	/* nothing left */
+			TRACE(5, "partial count because of eof mark");
+			if (bytes_read == 0) {
+				eof_mark = 0;	/* no need for mark next read */
+			}
+			break;
+		}
+	}
+	TRACE_EXIT;
+	return bytes_read;
+}
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/ftape-read.h linux/drivers/char/ftape/ftape-read.h
--- v1.3.71/linux/drivers/char/ftape/ftape-read.h	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/ftape-read.h	Wed Mar  6 15:07:20 1996
@@ -0,0 +1,45 @@
+#ifndef _FTAPE_READ_H
+#define _FTAPE_READ_H
+
+/*
+ * Copyright (C) 1994-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ $Source: /home/bas/distr/ftape-2.03b/RCS/ftape-read.h,v $
+ $Author: bas $
+ *
+ $Revision: 1.13 $
+ $Date: 1995/05/10 16:09:36 $
+ $State: Beta $
+ *
+ *      This file contains the definitions for the read functions
+ *      for the QIC-117 floppy-tape driver for Linux.
+ *
+ */
+
+/*      ftape-read.c defined global vars.
+ */
+
+/*      ftape-read.c defined global functions.
+ */
+extern int _ftape_read(char *buff, int req_len);
+extern int read_header_segment(byte * address);
+extern int read_segment(unsigned segment, byte * address, int *eof_mark,
+			int read_ahead);
+extern void ftape_zap_read_buffers(void);
+
+#endif				/* _FTAPE_READ_H */
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/ftape-rw.c linux/drivers/char/ftape/ftape-rw.c
--- v1.3.71/linux/drivers/char/ftape/ftape-rw.c	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/ftape-rw.c	Wed Mar  6 15:07:20 1996
@@ -0,0 +1,954 @@
+/*
+ *      Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ $Source: /home/bas/distr/ftape-2.03b/RCS/ftape-rw.c,v $
+ $Author: bas $
+ *
+ $Revision: 1.54 $
+ $Date: 1995/05/27 08:55:27 $
+ $State: Beta $
+ *
+ *      This file contains some common code for the segment read and segment
+ *      write routines for the QIC-117 floppy-tape driver for Linux.
+ */
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/ftape.h>
+
+#include "tracing.h"
+#include "ftape-rw.h"
+#include "fdc-io.h"
+#include "kernel-interface.h"
+#include "qic117.h"
+#include "ftape-io.h"
+#include "ftape-ctl.h"
+#include "ftape-read.h"
+#include "ftape-eof.h"
+#include "ecc.h"
+#include "ftape-bsm.h"
+
+/*      Global vars.
+ */
+volatile enum runner_status_enum runner_status = idle;
+byte deblock_buffer[(SECTORS_PER_SEGMENT - 3) * SECTOR_SIZE];
+byte scratch_buffer[(SECTORS_PER_SEGMENT - 3) * SECTOR_SIZE];
+buffer_struct buffer[NR_BUFFERS];
+struct wait_queue *wait_intr = NULL;
+volatile int head;
+volatile int tail;		/* not volatile but need same type as head */
+int fdc_setup_error;
+ftape_last_segment_struct ftape_last_segment;
+int header_segment_1 = -1;
+int header_segment_2 = -1;
+int used_header_segment = -1;
+location_record location =
+{-1, 0};
+volatile int tape_running = 0;
+format_type format_code;
+
+/*      Local vars.
+ */
+static int overrun_count_offset = 0;
+static int inhibit_correction = 0;
+
+
+/*      Increment cyclic buffer nr.
+ */
+buffer_struct *
+ next_buffer(volatile int *x)
+{
+	if (++*x >= NR_BUFFERS) {
+		*x = 0;
+	}
+	return &buffer[*x];
+}
+
+int valid_segment_no(unsigned segment)
+{
+	return (segment >= first_data_segment && segment <= ftape_last_segment.id);
+}
+
+/*      Count nr of 1's in pattern.
+ */
+int count_ones(unsigned long mask)
+{
+	int bits;
+
+	for (bits = 0; mask != 0; mask >>= 1) {
+		if (mask & 1) {
+			++bits;
+		}
+	}
+	return bits;
+}
+
+/*      Calculate Floppy Disk Controller and DMA parameters for a segment.
+ *      head:   selects buffer struct in array.
+ *      offset: number of physical sectors to skip (including bad ones).
+ *      count:  number of physical sectors to handle (including bad ones).
+ */
+static int setup_segment(buffer_struct * buff, unsigned int segment_id,
+	unsigned int sector_offset, unsigned int sector_count, int retry)
+{
+	TRACE_FUN(8, "setup_segment");
+	unsigned long offset_mask;
+	unsigned long mask;
+
+	buff->segment_id = segment_id;
+	buff->sector_offset = sector_offset;
+	buff->remaining = sector_count;
+	buff->head = segment_id / segments_per_head;
+	buff->cyl = (segment_id % segments_per_head) / segments_per_cylinder;
+	buff->sect = (segment_id % segments_per_cylinder) * SECTORS_PER_SEGMENT + 1;
+	buff->deleted = 0;
+	offset_mask = (1 << buff->sector_offset) - 1;
+	mask = get_bad_sector_entry(segment_id) & offset_mask;
+	while (mask) {
+		if (mask & 1) {
+			offset_mask >>= 1;	/* don't count bad sector */
+		}
+		mask >>= 1;
+	}
+	buff->data_offset = count_ones(offset_mask);	/* good sectors to skip */
+	buff->ptr = buff->address + buff->data_offset * SECTOR_SIZE;
+	TRACEx1(5, "data offset = %d sectors", buff->data_offset);
+	if (retry) {
+		buff->soft_error_map &= offset_mask;	/* keep skipped part */
+	} else {
+		buff->hard_error_map = buff->soft_error_map = 0;
+	}
+	buff->bad_sector_map = get_bad_sector_entry(buff->segment_id);
+	if (buff->bad_sector_map != 0) {
+		TRACEx2(4, "segment: %d, bad sector map: %08lx",
+			buff->segment_id, buff->bad_sector_map);
+	} else {
+		TRACEx1(5, "segment: %d", buff->segment_id);
+	}
+	if (buff->sector_offset > 0) {
+		buff->bad_sector_map >>= buff->sector_offset;
+	}
+	if (buff->sector_offset != 0 || buff->remaining != SECTORS_PER_SEGMENT) {
+		TRACEx2(5, "sector offset = %d, count = %d",
+			buff->sector_offset, buff->remaining);
+	}
+	/*
+	 *    Segments with 3 or less sectors are not written with
+	 *    valid data because there is no space left for the ecc.
+	 *    The data written is whatever happens to be in the buffer.
+	 *    Reading such a segment will return a zero byte-count.
+	 *    To allow us to read/write segments with all bad sectors
+	 *    we fake one readable sector in the segment. This prevents
+	 *    having to handle these segments in a very special way.
+	 *    It is not important if the reading of this bad sector
+	 *    fails or not (the data is ignored). It is only read to
+	 *    keep the driver running.
+	 *    The QIC-40/80 spec. has no information on how to handle
+	 *    this case, so this is my interpretation.
+	 */
+	if (buff->bad_sector_map == EMPTY_SEGMENT) {
+		TRACE(5, "empty segment, fake first sector good");
+		buff->bad_sector_map = FAKE_SEGMENT;
+	}
+	fdc_setup_error = 0;
+	buff->next_segment = segment_id + 1;
+	TRACE_EXIT;
+	return 0;
+}
+
+/*      Calculate Floppy Disk Controller and DMA parameters for a new segment.
+ */
+int setup_new_segment(buffer_struct * buff, unsigned int segment_id, int skip)
+{
+	TRACE_FUN(5, "setup_new_segment");
+	int result = 0;
+	static int old_segment_id = -1;
+	static int old_ftape_state = idle;
+	int retry = 0;
+	unsigned offset = 0;
+	int count = SECTORS_PER_SEGMENT;
+
+	TRACEx3(5, "%s segment %d (old = %d)",
+		(ftape_state == reading) ? "reading" : "writing",
+		segment_id, old_segment_id);
+	if (ftape_state != old_ftape_state) {	/* when verifying */
+		old_segment_id = -1;
+		old_ftape_state = ftape_state;
+	}
+	if (segment_id == old_segment_id) {
+		++buff->retry;
+		++history.retries;
+		TRACEx1(5, "setting up for retry nr %d", buff->retry);
+		retry = 1;
+		if (skip && buff->skip > 0) {	/* allow skip on retry */
+			offset = buff->skip;
+			count -= offset;
+			TRACEx1(5, "skipping %d sectors", offset);
+		}
+	} else {
+		buff->retry = 0;
+		buff->skip = 0;
+		old_segment_id = segment_id;
+	}
+	result = setup_segment(buff, segment_id, offset, count, retry);
+	TRACE_EXIT;
+	return result;
+}
+
+/*      Determine size of next cluster of good sectors.
+ */
+int calc_next_cluster(buffer_struct * buff)
+{
+	/* Skip bad sectors.
+	 */
+	while (buff->remaining > 0 && (buff->bad_sector_map & 1) != 0) {
+		buff->bad_sector_map >>= 1;
+		++buff->sector_offset;
+		--buff->remaining;
+	}
+	/* Find next cluster of good sectors
+	 */
+	if (buff->bad_sector_map == 0) {	/* speed up */
+		buff->sector_count = buff->remaining;
+	} else {
+		unsigned long map = buff->bad_sector_map;
+
+		buff->sector_count = 0;
+		while (buff->sector_count < buff->remaining && (map & 1) == 0) {
+			++buff->sector_count;
+			map >>= 1;
+		}
+	}
+	return buff->sector_count;
+}
+
+int check_bot_eot(int status)
+{
+	TRACE_FUN(5, "check_bot_eot");
+
+	if (status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT)) {
+		location.bot = ((location.track & 1) == 0 ?
+				(status & QIC_STATUS_AT_BOT) :
+				(status & QIC_STATUS_AT_EOT));
+		location.eot = !location.bot;
+		location.segment = (location.track +
+			(location.bot ? 0 : 1)) * segments_per_track - 1;
+		location.sector = -1;
+		location.known = 1;
+		TRACEx1(5, "tape at logical %s", location.bot ? "bot" : "eot");
+		TRACEx1(5, "segment = %d", location.segment);
+	} else {
+		location.known = 0;
+	}
+	TRACE_EXIT;
+	return location.known;
+}
+
+/*      Read Id of first sector passing tape head.
+ */
+int ftape_read_id(void)
+{
+	TRACE_FUN(8, "ftape_read_id");
+	int result;
+	int status;
+	byte out[2];
+
+	/* Assume tape is running on entry, be able to handle
+	 * situation where it stopped or is stopping.
+	 */
+	location.known = 0;	/* default is location not known */
+	out[0] = FDC_READID;
+	out[1] = FTAPE_UNIT;
+	result = fdc_command(out, 2);
+	if (result < 0) {
+		TRACE(1, "fdc_command failed");
+	} else {
+		result = fdc_interrupt_wait(20 * SECOND);
+		if (result == 0) {
+			if (fdc_sect == 0) {
+				result = ftape_report_drive_status(&status);
+				if (result == 0) {
+					if (status & QIC_STATUS_READY) {
+						tape_running = 0;
+						TRACE(5, "tape has stopped");
+						check_bot_eot(status);
+						if (!location.known) {
+							result = -EIO;
+						}
+					} else {
+						/*  If read-id failed because of a hard or soft
+						 *  error, return an error. Higher level must retry!
+						 */
+						result = -EIO;
+					}
+				}
+			} else {
+				location.known = 1;
+				location.segment = (segments_per_head * fdc_head
+					+ segments_per_cylinder * fdc_cyl
+				 + (fdc_sect - 1) / SECTORS_PER_SEGMENT);
+				location.sector = (fdc_sect - 1) % SECTORS_PER_SEGMENT;
+				location.eot =
+				    location.bot = 0;
+			}
+		} else if (result == -ETIME) {
+			/*  Didn't find id on tape, must be near end: Wait until stopped.
+			 */
+			result = ftape_ready_wait(FOREVER, &status);
+			if (result >= 0) {
+				tape_running = 0;
+				TRACE(5, "tape has stopped");
+				check_bot_eot(status);
+				if (!location.known) {
+					result = -EIO;
+				}
+			}
+		} else {
+			/* Interrupted or otherwise failing fdc_interrupt_wait()
+			 */
+			TRACE(1, "fdc_interrupt_wait failed :(");
+			result = -EIO;
+		}
+	}
+	if (!location.known) {
+		TRACE(5, "no id found");
+	} else {
+		if (location.sector == 0) {
+			TRACEx2(5, "passing segment %d/%d", location.segment, location.sector);
+		} else {
+			TRACEx2(6, "passing segment %d/%d", location.segment, location.sector);
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+static int logical_forward(void)
+{
+	tape_running = 1;
+	return ftape_command(QIC_LOGICAL_FORWARD);
+}
+
+static int stop_tape(int *pstatus)
+{
+	TRACE_FUN(5, "stop_tape");
+	int retry = 0;
+	int result;
+
+	do {
+		result = ftape_command_wait(QIC_STOP_TAPE, timeout.stop, pstatus);
+		if (result == 0) {
+			if ((*pstatus & QIC_STATUS_READY) == 0) {
+				result = -EIO;
+			} else {
+				tape_running = 0;
+			}
+		}
+	} while (result < 0 && ++retry <= 3);
+	if (result < 0) {
+		TRACE(1, "failed ! (fatal)");
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_dumb_stop(void)
+{
+	TRACE_FUN(5, "ftape_dumb_stop");
+	int result;
+	int status;
+
+	/* Abort current fdc operation if it's busy (probably read
+	 * or write operation pending) with a reset.
+	 */
+	result = fdc_ready_wait(100 /* usec */ );
+	if (result < 0) {
+		TRACE(1, "aborting fdc operation");
+		fdc_reset();
+	}
+	/*  Reading id's after the last segment on a track may fail
+	 *  but eventually the drive will become ready (logical eot).
+	 */
+	result = ftape_report_drive_status(&status);
+	location.known = 0;
+	do {
+		if (result == 0 && status & QIC_STATUS_READY) {
+			/* Tape is not running any more.
+			 */
+			TRACE(5, "tape already halted");
+			check_bot_eot(status);
+			tape_running = 0;
+		} else if (tape_running) {
+			/* Tape is (was) still moving.
+			 */
+#ifdef TESTING
+			ftape_read_id();
+#endif
+			result = stop_tape(&status);
+		} else {
+			/* Tape not yet ready but stopped.
+			 */
+			result = ftape_ready_wait(timeout.pause, &status);
+		}
+	} while (tape_running);
+#ifndef TESTING
+	location.known = 0;
+#endif
+	TRACE_EXIT;
+	return result;
+}
+
+/*      Wait until runner has finished tail buffer.
+ */
+int wait_segment(buffer_state_enum state)
+{
+	TRACE_FUN(5, "wait_segment");
+	int result = 0;
+
+	while (buffer[tail].status == state) {
+		/*  First buffer still being worked on, wait up to timeout.
+		 */
+		result = fdc_interrupt_wait(50 * SECOND);
+		if (result < 0) {
+			if (result != -EINTR) {
+				TRACE(1, "fdc_interrupt_wait failed");
+				result = -ETIME;
+			}
+			break;
+		}
+		if (fdc_setup_error) {
+			TRACE(1, "setup error");
+			/* recover... */
+			result = -EIO;
+			break;
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+/* forward */ static int seek_forward(int segment_id);
+
+int fast_seek(int count, int reverse)
+{
+	TRACE_FUN(5, "fast_seek");
+	int result = 0;
+	int status;
+
+	if (count > 0) {
+		/*  If positioned at begin or end of tape, fast seeking needs
+		 *  special treatment.
+		 *  Starting from logical bot needs a (slow) seek to the first
+		 *  segment before the high speed seek. Most drives do this
+		 *  automatically but some older don't, so we treat them
+		 *  all the same.
+		 *  Starting from logical eot is even more difficult because
+		 *  we cannot (slow) reverse seek to the last segment.
+		 *  TO BE IMPLEMENTED.
+		 */
+		inhibit_correction = 0;
+		if (location.known &&
+		    ((location.bot && !reverse) ||
+		     (location.eot && reverse))) {
+			if (!reverse) {
+				/*  (slow) skip to first segment on a track
+				 */
+				seek_forward(location.track * segments_per_track);
+				--count;
+			} else {
+				/*  When seeking backwards from end-of-tape the number
+				 *  of erased gaps found seems to be higher than expected.
+				 *  Therefor the drive must skip some more segments than
+				 *  calculated, but we don't know how many.
+				 *  Thus we will prevent the re-calculation of offset
+				 *  and overshoot when seeking backwards.
+				 */
+				inhibit_correction = 1;
+				count += 3;	/* best guess */
+			}
+		}
+	} else {
+		TRACEx1(5, "warning: zero or negative count: %d", count);
+	}
+	if (count > 0) {
+		int i;
+		int nibbles = count > 255 ? 3 : 2;
+
+		if (count > 4095) {
+			TRACE(4, "skipping clipped at 4095 segment");
+			count = 4095;
+		}
+		/* Issue this tape command first. */
+		if (!reverse) {
+			TRACEx1(4, "skipping %d segment(s)", count);
+			result = ftape_command(nibbles == 3 ?
+			   QIC_SKIP_EXTENDED_FORWARD : QIC_SKIP_FORWARD);
+		} else {
+			TRACEx1(4, "backing up %d segment(s)", count);
+			result = ftape_command(nibbles == 3 ?
+			   QIC_SKIP_EXTENDED_REVERSE : QIC_SKIP_REVERSE);
+		}
+		if (result < 0) {
+			TRACE(4, "Skip command failed");
+		} else {
+			--count;	/* 0 means one gap etc. */
+			for (i = 0; i < nibbles; ++i) {
+				if (result >= 0) {
+					result = ftape_parameter(count & 15);
+					count /= 16;
+				}
+			}
+			result = ftape_ready_wait(timeout.rewind, &status);
+			if (result >= 0) {
+				tape_running = 0;
+			}
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+static int validate(int id)
+{
+	/*  Check to see if position found is off-track as reported once.
+	 *  Because all tracks in one direction lie next to each other,
+	 *  if off-track the error will be approximately 2 * segments_per_track.
+	 */
+	if (location.track == -1) {
+		return 1;	/* unforseen situation, don't generate error */
+	} else {
+		/*  Use margin of segments_per_track on both sides because ftape
+		 *  needs some margin and the error we're looking for is much larger !
+		 */
+		int lo = (location.track - 1) * segments_per_track;
+		int hi = (location.track + 2) * segments_per_track;
+
+		return (id >= lo && id < hi);
+	}
+}
+
+static int seek_forward(int segment_id)
+{
+	TRACE_FUN(5, "seek_forward");
+	int failures = 0;
+	int result = 0;
+	int count;
+	static int margin = 1;	/* fixed: stop this before target */
+	static int overshoot = 1;
+	static int min_count = 8;
+	int expected = -1;
+	int target = segment_id - margin;
+	int fast_seeking;
+
+	if (!location.known) {
+		TRACE(1, "fatal: cannot seek from unknown location");
+		result = -EIO;
+	} else if (!validate(segment_id)) {
+		TRACE(1, "fatal: head off track (bad hardware?)");
+		ftape_sleep(1 * SECOND);
+		ftape_failure = 1;
+		result = -EIO;
+	} else {
+		int prev_segment = location.segment;
+
+		TRACEx4(4, "from %d/%d to %d/0 - %d", location.segment,
+			location.sector, segment_id, margin);
+		count = target - location.segment - overshoot;
+		fast_seeking = (count > min_count + (location.bot ? 1 : 0));
+		if (fast_seeking) {
+			TRACEx1(5, "fast skipping %d segments", count);
+			expected = segment_id - margin;
+			fast_seek(count, 0);
+		}
+		if (!tape_running) {
+			logical_forward();
+		}
+		while (location.segment < segment_id) {
+			/*  This requires at least one sector in a (bad) segment to
+			 *  have a valid and readable sector id !
+			 *  It looks like this is not guaranteed, so we must try
+			 *  to find a way to skip an EMPTY_SEGMENT. !!! FIXME !!!
+			 */
+			if (ftape_read_id() < 0 || !location.known) {
+				location.known = 0;
+				if (!tape_running || ++failures > SECTORS_PER_SEGMENT ||
+				    (current->signal & _DONT_BLOCK)) {
+					TRACE(1, "read_id failed completely");
+					result = -EIO;
+					break;
+				} else {
+					TRACEx1(5, "read_id failed, retry (%d)", failures);
+				}
+			} else if (fast_seeking) {
+				TRACEx4(4, "ended at %d/%d (%d,%d)", location.segment,
+					location.sector, overshoot, inhibit_correction);
+				if (!inhibit_correction &&
+				    (location.segment < expected ||
+				 location.segment > expected + margin)) {
+					int error = location.segment - expected;
+					TRACEx2(4, "adjusting overshoot from %d to %d",
+					   overshoot, overshoot + error);
+					overshoot += error;
+					/*  All overshoots have the same direction, so it should
+					 *  never become negative, but who knows.
+					 */
+					if (overshoot < -5 || overshoot > 10) {
+						if (overshoot < 0) {
+							overshoot = -5;		/* keep sane value */
+						} else {
+							overshoot = 10;		/* keep sane value */
+						}
+						TRACEx1(4, "clipped overshoot to %d", overshoot);
+					}
+				}
+				fast_seeking = 0;
+			}
+			if (location.known) {
+				if (location.segment > prev_segment + 1) {
+					TRACEx1(4, "missed segment %d while skipping", prev_segment + 1);
+				}
+				prev_segment = location.segment;
+			}
+		}
+		if (location.segment > segment_id) {
+			TRACEx2(4, "failed: skip ended at segment %d/%d",
+				location.segment, location.sector);
+			result = -EIO;
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+static int skip_reverse(int segment_id, int *pstatus)
+{
+	TRACE_FUN(5, "skip_reverse");
+	int result = 0;
+	int failures = 0;
+	static int overshoot = 1;
+	static int min_rewind = 2;	/* 1 + overshoot */
+	static const int margin = 1;	/* stop this before target */
+	int expected = 0;
+	int count;
+	int short_seek;
+	int target = segment_id - margin;
+
+	if (location.known && !validate(segment_id)) {
+		TRACE(1, "fatal: head off track (bad hardware?)");
+		ftape_sleep(1 * SECOND);
+		ftape_failure = 1;
+		result = -EIO;
+	} else
+		do {
+			if (!location.known) {
+				TRACE(-1, "warning: location not known");
+			}
+			TRACEx4(4, "from %d/%d to %d/0 - %d",
+				location.segment, location.sector, segment_id, margin);
+			/*  min_rewind == 1 + overshoot_when_doing_minimum_rewind
+			 *  overshoot  == overshoot_when_doing_larger_rewind
+			 *  Initially min_rewind == 1 + overshoot, optimization
+			 *  of both values will be done separately.
+			 *  overshoot and min_rewind can be negative as both are
+			 *  sums of three components:
+			 *  any_overshoot == rewind_overshoot - stop_overshoot - start_overshoot
+			 */
+			if (location.segment - target - (min_rewind - 1) < 1) {
+				short_seek = 1;
+			} else {
+				count = location.segment - target - overshoot;
+				short_seek = (count < 1);
+			}
+			if (short_seek) {
+				count = 1;	/* do shortest rewind */
+				expected = location.segment - min_rewind;
+				if (expected / segments_per_track != location.track) {
+					expected = location.track * segments_per_track;
+				}
+			} else {
+				expected = target;
+			}
+			fast_seek(count, 1);
+			logical_forward();
+			result = ftape_read_id();
+			if (result == 0 && location.known) {
+				TRACEx5(4, "ended at %d/%d (%d,%d,%d)", location.segment,
+					location.sector, min_rewind, overshoot, inhibit_correction);
+				if (!inhibit_correction &&
+				    (location.segment < expected ||
+				 location.segment > expected + margin)) {
+					int error = expected - location.segment;
+					if (short_seek) {
+						TRACEx2(4, "adjusting min_rewind from %d to %d",
+							min_rewind, min_rewind + error);
+						min_rewind += error;
+						if (min_rewind < -5) {	/* is this right ? FIXME ! */
+							min_rewind = -5;	/* keep sane value */
+							TRACEx1(4, "clipped min_rewind to %d", min_rewind);
+						}
+					} else {
+						TRACEx2(4, "adjusting overshoot from %d to %d",
+							overshoot, overshoot + error);
+						overshoot += error;
+						if (overshoot < -5 || overshoot > 10) {
+							if (overshoot < 0) {
+								overshoot = -5;		/* keep sane value */
+							} else {
+								overshoot = 10;		/* keep sane value */
+							}
+							TRACEx1(4, "clipped overshoot to %d", overshoot);
+						}
+					}
+				}
+			} else {
+				if ((!tape_running && !location.known) ||
+				    ++failures > SECTORS_PER_SEGMENT) {
+					TRACE(1, "read_id failed completely");
+					result = -EIO;
+					break;
+				} else {
+					TRACEx1(5, "ftape_read_id failed, retry (%d)", failures);
+				}
+				result = ftape_report_drive_status(pstatus);
+				if (result < 0) {
+					TRACEi(1, "ftape_report_drive_status failed with code", result);
+					break;
+				}
+			}
+		} while (location.segment > segment_id &&
+			 (current->signal & _DONT_BLOCK) == 0);
+	if (location.known) {
+		TRACEx2(4, "current location: %d/%d", location.segment, location.sector);
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+static int determine_position(void)
+{
+	TRACE_FUN(5, "determine_position");
+	int retry = 0;
+	int fatal = 0;
+	int status;
+	int result;
+
+	if (!tape_running) {
+		/*  This should only happen if tape is stopped by isr.
+		 */
+		TRACE(5, "waiting for tape stop");
+		result = ftape_ready_wait(timeout.pause, &status);
+		if (result < 0) {
+			TRACE(5, "drive still running (fatal)");
+			tape_running = 1;	/* ? */
+		}
+	} else {
+		ftape_report_drive_status(&status);
+	}
+	if (status & QIC_STATUS_READY) {
+		/*  Drive must be ready to check error state !
+		 */
+		TRACE(5, "drive is ready");
+		if (status & QIC_STATUS_ERROR) {
+			int error;
+			int command;
+
+			/*  Report and clear error state, try to continue.
+			 */
+			TRACE(5, "error status set");
+			ftape_report_error(&error, &command, 1);
+			ftape_ready_wait(timeout.reset, &status);
+			tape_running = 0;	/* ? */
+		}
+		if (check_bot_eot(status)) {
+			if (location.bot) {
+				if ((status & QIC_STATUS_READY) == 0) {
+					/* tape moving away from bot/eot, let's see if we
+					 * can catch up with the first segment on this track.
+					 */
+				} else {
+					TRACE(5, "start tape from logical bot");
+					logical_forward();	/* start moving */
+				}
+			} else {
+				if ((status & QIC_STATUS_READY) == 0) {
+					TRACE(4, "waiting for logical end of track");
+					result = ftape_ready_wait(timeout.reset, &status);
+					/* error handling needed ? */
+				} else {
+					TRACE(4, "tape at logical end of track");
+				}
+			}
+		} else {
+			TRACE(5, "start tape");
+			logical_forward();	/* start moving */
+			location.known = 0;	/* not cleared by logical forward ! */
+		}
+	}
+	if (!location.known) {
+		/* tape should be moving now, start reading id's
+		 */
+		TRACE(5, "location unknown");
+		do {
+			result = ftape_read_id();
+			if (result < 0) {
+				/*  read-id somehow failed, tape may have reached end
+				 *  or some other error happened.
+				 */
+				TRACE(5, "read-id failed");
+				ftape_report_drive_status(&status);
+				if (status & QIC_STATUS_READY) {
+					tape_running = 0;
+					TRACEx1(4, "tape stopped for unknown reason ! status = 0x%02x",
+						status);
+					if (status & QIC_STATUS_ERROR) {
+						fatal = 1;
+					} else {
+						if (check_bot_eot(status)) {
+							result = 0;
+						} else {
+							fatal = 1;	/* oops, tape stopped but not at end ! */
+						}
+					}
+				}
+				result = -EIO;
+			}
+		} while (result < 0 && !fatal && ++retry < SECTORS_PER_SEGMENT);
+	} else {
+		result = 0;
+	}
+	TRACEx1(5, "tape is positioned at segment %d", location.segment);
+	TRACE_EXIT;
+	return result;
+}
+
+/*      Get the tape running and position it just before the
+ *      requested segment.
+ *      Seek tape-track and reposition as needed.
+ */
+int ftape_start_tape(int segment_id, int sector_offset)
+{
+	TRACE_FUN(5, "ftape_start_tape");
+	int track = segment_id / segments_per_track;
+	int result = -EIO;
+	int status;
+	static int last_segment = -1;
+	static int bad_bus_timing = 0;
+	/* number of segments passing the head between starting the tape
+	 * and being able to access the first sector.
+	 */
+	static int start_offset = 1;
+	int retry = 0;
+
+	/* If sector_offset > 0, seek into wanted segment instead of
+	 * into previous.
+	 * This allows error recovery if a part of the segment is bad
+	 * (erased) causing the tape drive to generate an index pulse
+	 * thus causing a no-data error before the requested sector
+	 * is reached.
+	 */
+	tape_running = 0;
+	TRACEx3(4, "target segment: %d/%d%s", segment_id, sector_offset,
+		buffer[head].retry > 0 ? " retry" : "");
+	if (buffer[head].retry > 0) {	/* this is a retry */
+		if (!bad_bus_timing && ftape_data_rate == 1 &&
+		    history.overrun_errors - overrun_count_offset >= 8) {
+			ftape_set_data_rate(ftape_data_rate + 1);
+			bad_bus_timing = 1;
+			TRACE(2, "reduced datarate because of excessive overrun errors");
+		}
+	}
+	last_segment = segment_id;
+	if (location.track != track || (might_be_off_track &&
+					buffer[head].retry == 0)) {
+		/* current track unknown or not equal to destination
+		 */
+		ftape_ready_wait(timeout.seek, &status);
+		ftape_seek_head_to_track(track);
+		overrun_count_offset = history.overrun_errors;
+	}
+	do {
+		if (!location.known) {
+			determine_position();
+		}
+		/*  Check if we are able to catch the requested segment in time.
+		 */
+		if (location.known && location.segment >= segment_id -
+		    ((tape_running || location.bot) ? 0 : start_offset)) {
+			/*  Too far ahead (in or past target segment).
+			 */
+			if (tape_running) {
+				result = stop_tape(&status);
+				if (result < 0) {
+					TRACEi(1, "stop tape failed with code", result);
+					break;
+				}
+				TRACE(5, "tape stopped");
+				tape_running = 0;
+			}
+			TRACE(5, "repositioning");
+			++history.rewinds;
+			if (segment_id % segments_per_track < start_offset) {
+				/*  If seeking to first segments on track better do a complete
+				 *  rewind to logical begin of track to get a more steady tape
+				 *  motion.
+				 */
+				result = ftape_command_wait((location.track & 1) ?
+						   QIC_PHYSICAL_FORWARD :
+						    QIC_PHYSICAL_REVERSE,
+						timeout.rewind, &status);
+				check_bot_eot(status);	/* update location */
+			} else {
+				result = skip_reverse(segment_id - start_offset, &status);
+			}
+		}
+		if (!location.known) {
+			TRACE(-1, "panic: location not known");
+			result = -EIO;
+			if ((current->signal & _DONT_BLOCK) || ftape_failure) {
+				break;
+			} else {
+				continue;
+			}
+		}
+		TRACEx2(4, "current segment: %d/%d", location.segment, location.sector);
+		/*  We're on the right track somewhere before the wanted segment.
+		 *  Start tape movement if needed and skip to just before or inside
+		 *  the requested segment. Keep tape running.
+		 */
+		result = 0;
+		if (location.segment < segment_id -
+		    ((tape_running || location.bot) ? 0 : start_offset)) {
+			if (sector_offset > 0) {
+				result = seek_forward(segment_id);
+			} else {
+				result = seek_forward(segment_id - 1);
+			}
+		}
+		if (result == 0 &&
+		    location.segment != segment_id - (sector_offset > 0 ? 0 : 1)) {
+			result = -EIO;
+		}
+	} while (result < 0 && !ftape_failure &&
+		 (current->signal & _DONT_BLOCK) == 0 &&
+		 ++retry <= 5);
+	if (result < 0) {
+		TRACE(1, "failed to reposition");
+	}
+	TRACE_EXIT;
+	return result;
+}
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/ftape-rw.h linux/drivers/char/ftape/ftape-rw.h
--- v1.3.71/linux/drivers/char/ftape/ftape-rw.h	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/ftape-rw.h	Wed Mar  6 15:07:20 1996
@@ -0,0 +1,173 @@
+#ifndef _FTAPE_RW_H
+#define _FTAPE_RW_H
+
+/*
+ * Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ $Source: /home/bas/distr/ftape-2.03b/RCS/ftape-rw.h,v $
+ $Author: bas $
+ *
+ $Revision: 1.33 $
+ $Date: 1995/04/22 07:30:15 $
+ $State: Beta $
+ *
+ *      This file contains the definitions for the read and write
+ *      functions for the QIC-117 floppy-tape driver for Linux.
+ *
+ */
+
+#include "fdc-io.h"
+#include "kernel-interface.h"
+
+#define GET2( address, offset) *(short*)(address + offset)
+#define GET4( address, offset) *(long*)(address + offset)
+#define PUT2( address, offset, value) *(short*)(address + offset) = value
+#define PUT4( address, offset, value) *(long*)(address + offset) = value
+
+enum runner_status_enum {
+	idle = 0,
+	running,
+	do_abort,
+	aborting,
+	logical_eot,
+	end_of_tape,
+	buffer_overrun,
+	buffer_underrun,
+};
+
+typedef struct {
+	byte *address;
+	volatile buffer_state_enum status;
+	volatile byte *ptr;
+	volatile unsigned bytes;
+	volatile unsigned segment_id;
+
+	/* bitmap for remainder of segment not yet handled.
+	 * one bit set for each bad sector that must be skipped.
+	 */
+	volatile unsigned long bad_sector_map;
+
+	/* bitmap with bad data blocks in data buffer.
+	 * the errors in this map may be retried.
+	 */
+	volatile unsigned long soft_error_map;
+
+	/* bitmap with bad data blocks in data buffer
+	 * the errors in this map may not be retried.
+	 */
+	volatile unsigned long hard_error_map;
+
+	/* retry counter for soft errors.
+	 */
+	volatile int retry;
+
+	/* sectors to skip on retry ???
+	 */
+	volatile unsigned int skip;
+
+	/* nr of data blocks in data buffer
+	 */
+	volatile unsigned data_offset;
+
+	/* offset in segment for first sector to be handled.
+	 */
+	volatile unsigned sector_offset;
+
+	/* size of cluster of good sectors to be handled.
+	 */
+	volatile unsigned sector_count;
+
+	/* size of remaining part of segment to be handled.
+	 */
+	volatile unsigned remaining;
+
+	/* points to next segment (contigious) to be handled,
+	 * or is zero if no read-ahead is allowed.
+	 */
+	volatile unsigned next_segment;
+
+	/* flag being set if deleted data was read.
+	 */
+	volatile int deleted;
+
+	volatile byte head;
+	volatile byte cyl;
+	volatile byte sect;
+} buffer_struct;
+
+typedef struct {
+	int active;
+	int error;
+	int offset;
+} ftape_fast_start_struct;
+
+typedef struct {
+	int id;
+	int size;
+	int free;
+} ftape_last_segment_struct;
+
+typedef struct {
+	int track;		/* tape head position */
+	volatile int known;	/* validates bot, segment, sector */
+	volatile int bot;	/* logical begin of track */
+	volatile int eot;	/* logical end of track */
+	volatile int segment;	/* current segment */
+	volatile int sector;	/* sector offset within current segment */
+} location_record;
+
+typedef enum {
+	fmt_normal = 2, fmt_1100ft = 3, fmt_wide = 4, fmt_425ft = 5
+} format_type;
+
+/*      ftape-rw.c defined global vars.
+ */
+extern int tracing;
+extern byte trace_id;
+extern buffer_struct buffer[];
+extern location_record location;
+extern volatile ftape_fast_start_struct ftape_fast_start;
+extern byte deblock_buffer[(SECTORS_PER_SEGMENT - 3) * SECTOR_SIZE];
+extern byte scratch_buffer[(SECTORS_PER_SEGMENT - 3) * SECTOR_SIZE];
+extern ftape_last_segment_struct ftape_last_segment;
+extern int header_segment_1;
+extern int header_segment_2;
+extern int used_header_segment;
+extern unsigned int fast_seek_segment_time;
+extern volatile int tape_running;
+extern format_type format_code;
+
+/*      ftape-rw.c defined global functions.
+ */
+extern int count_ones(unsigned long mask);
+extern int valid_segment_no(unsigned segment);
+extern int setup_new_segment(buffer_struct * buff, unsigned int segment_id,
+			     int offset);
+extern int calc_next_cluster(buffer_struct * buff);
+extern buffer_struct *next_buffer(volatile int *x);
+extern int ftape_read_id(void);
+extern void ftape_tape_parameters(byte drive_configuration);
+extern int wait_segment(buffer_state_enum state);
+extern int ftape_dumb_stop(void);
+extern int ftape_start_tape(int segment_id, int offset);
+
+/*      fdc-io.c defined global functions.
+ */
+extern int setup_fdc_and_dma(buffer_struct * buff, byte operation);
+
+#endif				/* _FTAPE_RW_H */
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/ftape-write.c linux/drivers/char/ftape/ftape-write.c
--- v1.3.71/linux/drivers/char/ftape/ftape-write.c	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/ftape-write.c	Wed Mar  6 15:07:20 1996
@@ -0,0 +1,724 @@
+
+
+
+/*
+ *      Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ $Source: /home/bas/distr/ftape-2.03b/RCS/ftape-write.c,v $
+ $Author: bas $
+ *
+ $Revision: 1.26 $
+ $Date: 1995/05/27 08:55:27 $
+ $State: Beta $
+ *
+ *      This file contains the writing code
+ *      for the QIC-117 floppy-tape driver for Linux.
+ */
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/ftape.h>
+#include <asm/segment.h>
+
+#include "tracing.h"
+#include "ftape-write.h"
+#include "ftape-read.h"
+#include "qic117.h"
+#include "ftape-io.h"
+#include "ftape-ctl.h"
+#include "ftape-rw.h"
+#include "ftape-eof.h"
+#include "ecc.h"
+#include "ftape-bsm.h"
+
+
+/*      Global vars.
+ */
+
+/*      Local vars.
+ */
+static int buf_pos_wr = 0;
+static int last_write_failed = 0;
+static int need_flush = 0;
+
+#define WRITE_MULTI  0
+#define WRITE_SINGLE 1
+
+void ftape_zap_write_buffers(void)
+{
+	int i;
+
+	for (i = 0; i < NR_BUFFERS; ++i) {
+		buffer[i].status = done;
+	}
+	need_flush = 0;
+}
+
+int copy_and_gen_ecc(char *destination, byte * source,
+		     unsigned int bad_sector_map)
+{
+	TRACE_FUN(8, "copy_and_gen_ecc");
+	int result;
+	struct memory_segment mseg;
+	int bads = count_ones(bad_sector_map);
+
+	if (bads > 0) {
+		TRACEi(4, "bad sectors in map:", bads);
+	}
+	if (bads + 3 >= SECTORS_PER_SEGMENT) {
+		TRACE(4, "empty segment");
+		mseg.blocks = 0;	/* skip entire segment */
+		result = 0;	/* nothing written */
+	} else {
+		mseg.blocks = SECTORS_PER_SEGMENT - bads;
+		mseg.data = destination;
+		memcpy(mseg.data, source, (mseg.blocks - 3) * SECTOR_SIZE);
+		result = ecc_set_segment_parity(&mseg);
+		if (result < 0) {
+			TRACE(1, "ecc_set_segment_parity failed");
+		} else {
+			result = (mseg.blocks - 3) * SECTOR_SIZE;
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+void prevent_flush(void)
+{
+	need_flush = 0;
+	ftape_state = idle;
+}
+
+int start_writing(int mode)
+{
+	TRACE_FUN(5, "start_writing");
+	int result = 0;
+	buffer_struct *buff = &buffer[head];
+	int segment_id = buff->segment_id;
+
+	if (ftape_state == writing && buff->status == waiting) {
+		setup_new_segment(buff, segment_id, 1);
+		if (mode == WRITE_SINGLE) {
+			buffer[head].next_segment = 0;	/* stop tape instead of pause */
+		}
+		calc_next_cluster(buff);	/* prepare */
+		buff->status = writing;
+		if (runner_status == idle) {
+			TRACEi(5, "starting runner for segment", segment_id);
+			result = ftape_start_tape(segment_id, buff->sector_offset);
+			if (result >= 0) {
+				runner_status = running;
+			}
+		}
+		if (result >= 0) {
+			result = setup_fdc_and_dma(buff, FDC_WRITE);	/* go */
+		}
+		ftape_state = writing;
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int loop_until_writes_done(void)
+{
+	TRACE_FUN(5, "loop_until_writes_done");
+	int i;
+	int result = 0;
+
+	/*
+	 *  Wait until all data is actually written to tape.
+	 */
+	while (ftape_state == writing && buffer[head].status != done) {
+		TRACEx2(7, "tail: %d, head: %d", tail, head);
+		for (i = 0; i < NR_BUFFERS; ++i) {
+			TRACEx3(8, "buffer[ %d] segment_id: %d, status: %d",
+			      i, buffer[i].segment_id, buffer[i].status);
+		}
+		result = fdc_interrupt_wait(5 * SECOND);
+		if (result < 0) {
+			TRACE(1, "fdc_interrupt_wait failed");
+			last_write_failed = 1;
+			break;
+		}
+		if (buffer[head].status == error) {
+			/* Allow escape from loop when signaled !
+			 */
+			if (current->signal & _DONT_BLOCK) {
+				TRACE(2, "interrupted by signal");
+				TRACE_EXIT;
+				result = -EINTR;	/* is this the right return value ? */
+				break;
+			}
+			if (buffer[head].hard_error_map != 0) {
+				/*  Implement hard write error recovery here
+				 */
+			}
+			buffer[head].status = waiting;	/* retry this one */
+			if (runner_status == aborting) {
+				ftape_dumb_stop();
+				runner_status = idle;
+			}
+			if (runner_status != idle) {
+				TRACE(1, "unexpected state: runner_status != idle");
+				result = -EIO;
+				break;
+			}
+			start_writing(WRITE_MULTI);
+		}
+		TRACE(5, "looping until writes done");
+		result = 0;	/* normal exit status */
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+/*      Write given segment from buffer at address onto tape.
+ */
+int write_segment(unsigned segment_id, byte * address, int flushing)
+{
+	TRACE_FUN(5, "write_segment");
+	int result = 0;
+	int bytes_written = 0;
+
+	TRACEi(5, "segment_id =", segment_id);
+	if (ftape_state != writing) {
+		if (ftape_state == reading) {
+			TRACE(5, "calling ftape_abort_operation");
+			result = ftape_abort_operation();
+			if (result < 0) {
+				TRACE(1, "ftape_abort_operation failed");
+			}
+		}
+		ftape_zap_read_buffers();
+		ftape_zap_write_buffers();
+		ftape_state = writing;
+	}
+	/*    if all buffers full we'll have to wait...
+	 */
+	wait_segment(writing);
+	if (buffer[tail].status == error) {
+		/*  setup for a retry
+		 */
+		buffer[tail].status = waiting;
+		bytes_written = -EAGAIN;	/* force retry */
+		if (buffer[tail].hard_error_map != 0) {
+			TRACEx1(1, "warning: %d hard error(s) in written segment",
+				count_ones(buffer[tail].hard_error_map));
+			TRACEx1(4, "hard_error_map = 0x%08lx", buffer[tail].hard_error_map);
+			/*  Implement hard write error recovery here
+			 */
+		}
+	} else if (buffer[tail].status == done) {
+		history.defects += count_ones(buffer[tail].hard_error_map);
+	} else {
+		TRACE(1, "wait for empty segment failed");
+		result = -EIO;
+	}
+	/*    If just passed last segment on tape: wait for BOT or EOT mark.
+	 */
+	if (result >= 0 && runner_status == logical_eot) {
+		int status;
+
+		result = ftape_ready_wait(timeout.seek, &status);
+		if (result < 0 || (status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT)) == 0) {
+			TRACE(1, "eot/bot not reached");
+		} else {
+			runner_status = end_of_tape;
+		}
+	}
+	/*    should runner stop ?
+	 */
+	if (result >= 0 &&
+	(runner_status == aborting || runner_status == buffer_underrun ||
+	 runner_status == end_of_tape)) {
+		if (runner_status != end_of_tape) {
+			result = ftape_dumb_stop();
+		}
+		if (result >= 0) {
+			if (runner_status == aborting) {
+				if (buffer[head].status == writing) {
+					buffer[head].status = done;	/* ????? */
+				}
+			}
+			runner_status = idle;	/* aborted ? */
+		}
+	}
+	/*  Don't start tape if runner idle and segment empty.
+	 */
+	if (result >= 0 && !(runner_status == idle &&
+		    get_bad_sector_entry(segment_id) == EMPTY_SEGMENT)) {
+		if (buffer[tail].status == done) {
+			/*    now at least one buffer is empty, fill it with our data.
+			 *    skip bad sectors and generate ecc.
+			 *    copy_and_gen_ecc return nr of bytes written,
+			 *    range 0..29 Kb inclusive !
+			 */
+			result = copy_and_gen_ecc(buffer[tail].address, address,
+				       get_bad_sector_entry(segment_id));
+			if (result >= 0) {
+				bytes_written = result;
+				buffer[tail].segment_id = segment_id;
+				buffer[tail].status = waiting;
+				next_buffer(&tail);
+			}
+		}
+		/*    Start tape only if all buffers full or flush mode.
+		 *    This will give higher probability of streaming.
+		 */
+		if (result >= 0 && runner_status != running &&
+		    ((head == tail && buffer[tail].status == waiting) || flushing)) {
+			result = start_writing(WRITE_MULTI);
+		}
+	}
+	TRACE_EXIT;
+	return (result < 0) ? result : bytes_written;
+}
+
+/*  Write as much as fits from buffer to the given segment on tape
+ *  and handle retries.
+ *  Return the number of bytes written (>= 0), or:
+ *      -EIO          write failed
+ *      -EINTR        interrupted by signal
+ *      -ENOSPC       device full
+ */
+int _write_segment(unsigned int segment_id, byte * buffer, int flush)
+{
+	TRACE_FUN(5, "_write_segment");
+	int retry = 0;
+	int result;
+
+	history.used |= 2;
+	for (;;) {
+		if (segment_id > ftape_last_segment.id && !flush) {
+			result = -ENOSPC;	/* tape full */
+			break;
+		}
+		result = write_segment(segment_id, buffer, flush);
+		if (result < 0) {
+			if (result == -EAGAIN) {
+				if (++retry > 100) {
+					TRACE(1, "write failed, >100 retries in segment");
+					result = -EIO;	/* give up */
+					break;
+				} else {
+					TRACEx1(2, "write error, retry %d", retry);
+				}
+			} else {
+				TRACEi(1, "write_segment failed, error:", -result);
+				break;
+			}
+		} else {	/* success */
+			if (result == 0) {	/* empty segment */
+				TRACE(4, "empty segment, nothing written");
+			}
+			break;
+		}
+		/* Allow escape from loop when signaled !
+		 */
+		if (current->signal & _DONT_BLOCK) {
+			TRACE(2, "interrupted by signal");
+			TRACE_EXIT;
+			result = -EINTR;	/* is this the right return value ? */
+			break;
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int update_header_segment(unsigned segment, byte * buffer)
+{
+	TRACE_FUN(5, "update_header_segment");
+	int result = 0;
+	int status;
+
+	if (buffer == NULL) {
+		TRACE(5, "no input buffer specified");
+		buffer = deblock_buffer;
+		result = read_segment(used_header_segment, buffer, &status, 0);
+		if (bad_sector_map_changed) {
+			store_bad_sector_map(buffer);
+		}
+		if (failed_sector_log_changed) {
+			update_failed_sector_log(buffer);
+		}
+	}
+	if (result >= 0 && GET4(buffer, 0) != 0xaa55aa55) {
+		TRACE(1, "wrong header signature found, aborting");
+		result = -EIO;
+	}
+	if (result >= 0) {
+		result = _write_segment(segment, buffer, 0);
+		if (result >= 0 && runner_status == idle) {
+			/*  Force flush for single segment instead of relying on
+			 *  flush in read_segment for multiple segments.
+			 */
+			result = start_writing(WRITE_SINGLE);
+			if (result >= 0 && ftape_state == writing) {
+				result = loop_until_writes_done();
+				prevent_flush();
+			}
+		}
+#ifdef VERIFY_HEADERS
+		if (result >= 0) {	/* read back and verify */
+			result = read_segment(segment, scratch_buffer, &status, 0);
+			/*  Should retry if soft error during read !
+			 *  TO BE IMPLEMENTED
+			 */
+			if (result >= 0) {
+				if (memcmp(buffer, scratch_buffer, sizeof(buffer)) == 0) {
+					result = 0;	/* verified */
+					TRACE(5, "verified");
+				} else {
+					result = -EIO;	/* verify failed */
+					TRACE(5, "verify failed");
+				}
+			}
+		}
+#endif
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_write_header_segments(byte * buffer)
+{
+	TRACE_FUN(5, "ftape_write_header_segments");
+	int result = 0;
+	int retry = 0;
+	int header_1_ok = 0;
+	int header_2_ok = 0;
+
+	do {
+		if (!header_1_ok) {
+			result = update_header_segment(header_segment_1, buffer);
+			if (result < 0) {
+				continue;
+			}
+			header_1_ok = 1;
+		}
+		if (!header_2_ok) {
+			result = update_header_segment(header_segment_2, buffer);
+			if (result < 0) {
+				continue;
+			}
+			header_2_ok = 1;
+		}
+	} while (result < 0 && retry++ < 3);
+	if (result < 0) {
+		if (!header_1_ok) {
+			TRACE(1, "update of first header segment failed");
+		}
+		if (!header_2_ok) {
+			TRACE(1, "update of second header segment failed");
+		}
+		result = -EIO;
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_update_header_segments(byte * buffer, int update)
+{
+	TRACE_FUN(5, "ftape_update_header_segments");
+	int result = 0;
+	int dummy;
+	int header_changed = 1;
+
+	if (ftape_state == writing) {
+		result = loop_until_writes_done();
+	}
+	if (read_only) {
+		result = 0;	/* exit and fake success */
+		TRACE(4, "Tape set read-only: no update");
+	} else if (result >= 0) {
+		result = ftape_abort_operation();
+		if (result >= 0) {
+			if (buffer == NULL) {
+				if (bad_sector_map_changed || failed_sector_log_changed) {
+					ftape_seek_to_bot();	/* prevents extra rewind */
+					buffer = deblock_buffer;
+					result = read_segment(used_header_segment, buffer, &dummy, 0);
+					if (result < 0) {
+						TRACE_EXIT;
+						return result;
+					}
+				}
+				header_changed = 0;
+			}
+			if (update) {
+				if (bad_sector_map_changed) {
+					store_bad_sector_map(buffer);
+					header_changed = 1;
+				}
+				if (failed_sector_log_changed) {
+					update_failed_sector_log(buffer);
+					header_changed = 1;
+				}
+			}
+			if (header_changed) {
+				ftape_seek_to_bot();	/* prevents extra rewind */
+				result = ftape_write_header_segments(buffer);
+			}
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_flush_buffers(void)
+{
+	TRACE_FUN(5, "ftape_flush_buffers");
+	int result;
+	int pad_count;
+	int data_remaining;
+	static int active = 0;
+
+	if (active) {
+		TRACE(5, "nested call, abort");
+		TRACE_EXIT;
+		return 0;
+	}
+	active = 1;
+	TRACEi(5, "entered, ftape_state =", ftape_state);
+	if (ftape_state != writing && !need_flush) {
+		active = 0;
+		TRACE(5, "no need for flush");
+		TRACE_EXIT;
+		return 0;
+	}
+	data_remaining = buf_pos_wr;
+	buf_pos_wr = 0;		/* prevent further writes if this fails */
+	TRACE(5, "flushing write buffers");
+	if (last_write_failed) {
+		ftape_zap_write_buffers();
+		active = 0;
+		TRACE_EXIT;
+		return write_protected ? -EROFS : -EIO;
+	}
+	/*
+	 *    If there is any data not written to tape yet, append zero's
+	 *    up to the end of the sector. Then write the segment(s) to tape.
+	 */
+	if (data_remaining > 0) {
+		int written;
+
+		do {
+			TRACEi(4, "remaining in buffer:", data_remaining);
+			pad_count = sizeof(deblock_buffer) - data_remaining;
+			TRACEi(7, "flush, padding count:", pad_count);
+			memset(deblock_buffer + data_remaining, 0, pad_count);	/* pad buffer */
+			result = _write_segment(ftape_seg_pos, deblock_buffer, 1);
+			if (result < 0) {
+				if (result != -ENOSPC) {
+					last_write_failed = 1;
+				}
+				active = 0;
+				TRACE_EXIT;
+				return result;
+			}
+			written = result;
+			clear_eof_mark_if_set(ftape_seg_pos, written);
+			TRACEi(7, "flush, moved out buffer:", written);
+			if (written > 0) {
+				data_remaining -= written;
+				if (data_remaining > 0) {
+					/*  Need another segment for remaining data, move the remainder
+					 *  to the beginning of the buffer
+					 */
+					memmove(deblock_buffer, deblock_buffer + written, data_remaining);
+				}
+			}
+			++ftape_seg_pos;
+		} while (data_remaining > 0);
+		/*  Data written to last segment == data_remaining + written
+		 *  value is in range [1..29K].
+		 */
+		TRACEx2(4, "last write: %d, netto pad-count: %d",
+			data_remaining + written, -data_remaining);
+		if (-1024 < data_remaining && data_remaining <= 0) {
+			/*  Last sector of segment was used for data, so put eof mark
+			 *  in next segment and position at second file mark.
+			 */
+			if (ftape_weof(2, ftape_seg_pos, 1) >= 0) {
+				++ftape_seg_pos;	/* position between file marks */
+			}
+		} else {
+			/*  Put eof mark in previous segment after data and position
+			 *  at second file mark.
+			 */
+			ftape_weof(2, ftape_seg_pos - 1, 1 +
+				   ((SECTOR_SIZE - 1 + result + data_remaining) / SECTOR_SIZE));
+		}
+	} else {
+		TRACE(7, "deblock_buffer empty");
+		if (ftape_weof(2, ftape_seg_pos, 1) >= 0) {
+			++ftape_seg_pos;	/* position between file marks */
+		}
+		start_writing(WRITE_MULTI);
+	}
+	TRACE(7, "waiting");
+	result = loop_until_writes_done();
+	if (result < 0) {
+		TRACE(1, "flush buffers failed");
+	}
+	ftape_state = idle;
+	last_write_failed = 0;
+	need_flush = 0;
+	active = 0;
+	TRACE_EXIT;
+	return result;
+}
+
+int _ftape_write(const char *buff, int req_len)
+{
+	TRACE_FUN(5, "_ftape_write");
+	int result = 0;
+	int cnt;
+	int written = 0;
+
+	if (write_protected) {
+		TRACE(1, "error: cartridge write protected");
+		last_write_failed = 1;
+		result = -EROFS;
+	} else if (ftape_offline || !formatted || no_tape) {
+		result = -EIO;
+	} else if (first_data_segment == -1) {
+		/*
+		 *    If we haven't read the header segment yet, do it now.
+		 *    This will verify the configuration, get the eof markers
+		 *    and the bad sector table.
+		 *    We'll use the deblock buffer for scratch.
+		 */
+		result = read_header_segment(deblock_buffer);
+		if (result >= 0 && ftape_seg_pos > ftape_last_segment.id) {
+			result = -ENOSPC;	/* full is full */
+		}
+	}
+	if (result < 0) {
+		TRACE_EXIT;
+		return result;
+	}
+	/*
+	 *    This part writes data blocks to tape until the
+	 *    requested amount is written.
+	 *    The data will go in a buffer until it's enough
+	 *    for a segment without bad sectors. Then we'll write
+	 *    that segment to tape.
+	 *    The bytes written will be removed from the buffer
+	 *    and the process is repeated until there is less
+	 *    than one segment to write left in the buffer.
+	 */
+	while (req_len > 0) {
+		int space_left = sizeof(deblock_buffer) - buf_pos_wr;
+
+		TRACEi(7, "remaining req_len:", req_len);
+		TRACEi(7, "          buf_pos:", buf_pos_wr);
+		cnt = (req_len < space_left) ? req_len : space_left;
+		if (cnt > 0) {
+			result = verify_area(VERIFY_READ, buff, cnt);
+			if (result) {
+				TRACE(1, "verify_area failed");
+				last_write_failed = 1;
+				TRACE_EXIT;
+				return result;
+			}
+			memcpy_fromfs(deblock_buffer + buf_pos_wr, buff, cnt);
+			buff += cnt;
+			req_len -= cnt;
+			buf_pos_wr += cnt;
+		}
+		TRACEi(7, "moved into blocking buffer:", cnt);
+		while (buf_pos_wr >= sizeof(deblock_buffer)) {
+			/*  If this is the last buffer to be written, let flush handle it.
+			 */
+			if (ftape_seg_pos >= ftape_last_segment.id) {
+				TRACEi(7, "remaining in blocking buffer:", buf_pos_wr);
+				TRACEi(7, "just written bytes:", written + cnt);
+				TRACE_EXIT;
+				return written + cnt;
+			}
+			/* Got one full buffer, write it to disk
+			 */
+			result = _write_segment(ftape_seg_pos, deblock_buffer, 0);
+			TRACEi(5, "_write_segment result =", result);
+			if (result < 0) {
+				if (result == -EAGAIN) {
+					TRACE(5, "retry...");
+					continue;	/* failed, retry same segment */
+				}
+				last_write_failed = 1;
+				TRACE_EXIT;
+				return result;
+			} else {
+				clear_eof_mark_if_set(ftape_seg_pos, result);
+			}
+			if (result > 0 && result < buf_pos_wr) {
+				/* Partial write: move remainder in lower part of buffer
+				 */
+				memmove(deblock_buffer, deblock_buffer + result, buf_pos_wr - result);
+			}
+			TRACEi(7, "moved out of blocking buffer:", result);
+			buf_pos_wr -= result;	/* remainder */
+			++ftape_seg_pos;
+			/* Allow us to escape from this loop with a signal !
+			 */
+			if (current->signal & _DONT_BLOCK) {
+				TRACE(2, "interrupted by signal");
+				last_write_failed = 1;
+				TRACE_EXIT;
+				return -EINTR;	/* is this the right return value ? */
+			}
+		}
+		written += cnt;
+	}
+	TRACEi(7, "remaining in blocking buffer:", buf_pos_wr);
+	TRACEi(7, "just written bytes:", written);
+	last_write_failed = 0;
+	if (!need_flush && written > 0) {
+		need_flush = 1;
+	}
+	TRACE_EXIT;
+	return written;		/* bytes written */
+}
+
+int ftape_fix(void)
+{
+	TRACE_FUN(5, "ftape_fix");
+	int result = 0;
+	int dummy;
+	int status;
+
+	if (write_protected) {
+		result = -EROFS;
+	} else {
+		/*  This will copy header segment 2 to header segment 1
+		 *  Spares us a tape format operation if header 2 is still good.
+		 */
+		header_segment_1 = 0;
+		header_segment_2 = 1;
+		first_data_segment = 2;
+		result = read_segment(header_segment_2, scratch_buffer, &dummy, 0);
+		result = ftape_ready_wait(timeout.pause, &status);
+		result = ftape_write_header_segments(scratch_buffer);
+	}
+	TRACE_EXIT;
+	return result;
+}
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/ftape-write.h linux/drivers/char/ftape/ftape-write.h
--- v1.3.71/linux/drivers/char/ftape/ftape-write.h	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/ftape-write.h	Wed Mar  6 15:07:20 1996
@@ -0,0 +1,48 @@
+#ifndef _FTAPE_WRITE_H
+#define _FTAPE_WRITE_H
+
+/*
+ * Copyright (C) 1994-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ $Source: /home/bas/distr/ftape-2.03b/RCS/ftape-write.h,v $
+ $Author: bas $
+ *
+ $Revision: 1.13 $
+ $Date: 1995/04/22 07:30:15 $
+ $State: Beta $
+ *
+ *      This file contains the definitions for the write functions
+ *      for the QIC-117 floppy-tape driver for Linux.
+ *
+ */
+
+/*      ftape-write.c defined global vars.
+ */
+
+/*      ftape-write.c defined global functions.
+ */
+extern int _ftape_write(const char *buff, int req_len);
+extern int ftape_flush_buffers(void);
+extern int ftape_write_header_segments(byte * buffer);
+extern int ftape_update_header_segments(byte * buffer, int update);
+extern int write_segment(unsigned segment, byte * address, int flushing);
+extern int ftape_fix(void);
+extern void prevent_flush(void);
+extern void ftape_zap_write_buffers(void);
+
+#endif				/* _FTAPE_WRITE_H */
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/kernel-interface.c linux/drivers/char/ftape/kernel-interface.c
--- v1.3.71/linux/drivers/char/ftape/kernel-interface.c	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/kernel-interface.c	Wed Mar  6 15:07:20 1996
@@ -0,0 +1,384 @@
+/*
+ *      Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ *      This file contains the code that interfaces the kernel
+ *      for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <asm/segment.h>
+#include <linux/kernel.h>
+#include <linux/signal.h>
+#include <linux/major.h>
+#include <linux/malloc.h>
+#include <linux/ftape.h>
+#include <asm/dma.h>
+
+#include "tracing.h"
+#include "kernel-interface.h"
+#include "ftape-read.h"
+#include "ftape-write.h"
+#include "ftape-io.h"
+#include "ftape-ctl.h"
+#include "ftape-rw.h"
+#include "fdc-io.h"
+
+
+/*      Global vars.
+ */
+
+/* Allocating a 96Kb DMAable buffer in one chunk wont work due to
+ * memory fragmentation.  To avoid this, it is broken up into
+ * NR_BUFFERS chunks of 32Kbyte. --khp
+ */
+
+byte *tape_buffer[NR_BUFFERS] =
+{NULL};
+
+#ifdef MODULE
+char kernel_version[] = UTS_RELEASE;
+#endif
+
+/*      Local vars.
+ */
+static int busy_flag = 0;
+static int old_sigmask;
+
+static int ftape_open(struct inode *ino, struct file *filep);
+static void ftape_close(struct inode *ino, struct file *filep);
+static int ftape_ioctl(struct inode *ino, struct file *filep,
+		       unsigned int command, unsigned long arg);
+static int ftape_read(struct inode *ino, struct file *fp, char *buff,
+		      int req_len);
+static int ftape_write(struct inode *ino, struct file *fp, const char *buff,
+		       int req_len);
+static int ftape_lseek(struct inode *ino, struct file *filep,
+		       off_t offset, int origin);
+#if 0
+static int ftape_select(void);
+static int ftape_mmap(int dev, unsigned off, int prot);
+#else
+#define ftape_select NULL
+#define ftape_mmap NULL
+#endif
+
+static struct file_operations ftape_cdev =
+{
+	ftape_lseek,		/* lseek */
+	ftape_read,		/* read */
+	ftape_write,		/* write */
+	NULL,			/* readdir */
+	ftape_select,		/* select */
+	ftape_ioctl,		/* ioctl */
+	ftape_mmap,		/* mmap */
+	ftape_open,		/* open */
+	ftape_close,		/* release */
+	NULL,			/* fsync */
+};
+
+/*
+ * DMA'able memory allocation stuff.
+ */
+
+static inline
+int __get_order(int size)
+{
+	int order;
+
+	for (order = 0; order < NR_MEM_LISTS; ++order)
+		if (size <= (PAGE_SIZE << order))
+			return order;
+	return -1;
+}
+
+static inline
+void *dmaalloc(int order)
+{
+	return (void *) __get_dma_pages(GFP_KERNEL, order);
+}
+
+static inline
+void dmafree(void *addr, int order)
+{
+	free_pages((unsigned long) addr, order);
+}
+
+/*
+ * Called by modules package when installing the driver
+ * or by kernel during the initialization phase
+ */
+
+#ifdef MODULE
+#define ftape_init init_module
+#endif
+
+int ftape_init(void)
+{
+	int n;
+	int order;
+	TRACE_FUN(5, "init_module");
+#ifdef MODULE
+	printk(KERN_INFO "ftape-2.07 960304\n"
+	       KERN_INFO " (c) 1993-1995 Bas Laarhoven (bas@vimec.nl)\n"
+	       KERN_INFO " (c) 1995-1996 Kai Harrekilde-Petersen (khp@pip.dknet.dk)\n"
+	KERN_INFO " QIC-117 driver for QIC-40/80/3010/3020 tape drives\n"
+	       KERN_INFO " Compiled for kernel version %s"
+#ifdef MODVERSIONS
+	       " with versioned symbols"
+#endif
+	       "\n", kernel_version);
+#else				/* !MODULE */
+	/* print a short no-nonsense boot message */
+	printk("ftape-2.07 960304 for Linux 1.3.70\n");
+#endif				/* MODULE */
+	TRACE(3, "installing QIC-117 ftape driver...");
+	if (register_chrdev(QIC117_TAPE_MAJOR, "ftape", &ftape_cdev)) {
+		TRACE(1, "register_chrdev failed");
+		TRACE_EXIT;
+		return -EIO;
+	}
+	TRACEx1(3, "init_module @ 0x%p", init_module);
+	/*
+	 * Allocate the DMA buffers. They are deallocated at cleanup() time.
+	 */
+	order = __get_order(BUFF_SIZE);
+	if (order < 0) {
+		TRACE(1, "__get_order failed (no memory?)");
+		if (unregister_chrdev(QIC117_TAPE_MAJOR, "ftape") != 0) {
+			TRACE(3, "unregister_chrdev failed");
+		}
+		return -ENOMEM;
+	}
+	for (n = 0; n < NR_BUFFERS; n++) {
+		tape_buffer[n] = (byte *) dmaalloc(order);
+		if (!tape_buffer[n]) {
+			TRACE(1, "dmaalloc() failed");
+			for (n = 0; n < NR_BUFFERS; n++) {
+				if (tape_buffer[n]) {
+					dmafree(tape_buffer[n], order);
+					tape_buffer[n] = NULL;
+				}
+			}
+			current->blocked = old_sigmask;		/* restore mask */
+			if (unregister_chrdev(QIC117_TAPE_MAJOR, "ftape") != 0) {
+				TRACE(3, "unregister_chrdev failed");
+			}
+			TRACE_EXIT;
+			return -ENOMEM;
+		} else {
+			TRACEx2(3, "dma-buffer #%d @ %p", n, tape_buffer[n]);
+		}
+	}
+	busy_flag = 0;
+	ftape_unit = -1;
+	ftape_failure = 1;	/* inhibit any operation but open */
+	udelay_calibrate();	/* must be before fdc_wait_calibrate ! */
+	fdc_wait_calibrate();
+	TRACE_EXIT;
+#ifdef MODULE
+	register_symtab(0);	/* remove global ftape symbols */
+#endif
+	return 0;
+}
+
+
+#ifdef MODULE
+/*      Called by modules package when removing the driver
+ */
+void cleanup_module(void)
+{
+	int n;
+	int order;
+	TRACE_FUN(5, "cleanup_module");
+
+	if (unregister_chrdev(QIC117_TAPE_MAJOR, "ftape") != 0) {
+		TRACE(3, "failed");
+	} else {
+		TRACE(3, "successful");
+	}
+	order = __get_order(BUFF_SIZE);
+	if (order < 0) {
+		TRACE(1, "__get_order failed (but why?!)");
+	} else {
+		for (n = 0; n < NR_BUFFERS; n++) {
+			if (tape_buffer[n]) {
+				dmafree(tape_buffer[n], order);
+				tape_buffer[n] = NULL;
+				TRACEx1(3, "removed dma-buffer #%d", n);
+			} else {
+				TRACEx1(1, "dma-buffer #%d == NULL (bug?)", n);
+			}
+		}
+	}
+	TRACE_EXIT;
+}
+#endif				/* MODULE */
+
+/*      Open ftape device
+ */
+static int ftape_open(struct inode *ino, struct file *filep)
+{
+	TRACE_FUN(4, "ftape_open");
+	int result;
+	MOD_INC_USE_COUNT;	/* lock module in memory */
+
+	TRACEi(5, "called for minor", MINOR(ino->i_rdev));
+	if (busy_flag) {
+		TRACE(1, "failed: already busy");
+		MOD_DEC_USE_COUNT;	/* unlock module in memory */
+		TRACE_EXIT;
+		return -EBUSY;
+	}
+	if ((MINOR(ino->i_rdev) & ~FTAPE_NO_REWIND) > 3) {
+		TRACE(1, "failed: illegal unit nr");
+		MOD_DEC_USE_COUNT;	/* unlock module in memory */
+		TRACE_EXIT;
+		return -ENXIO;
+	}
+	if (ftape_unit == -1 || FTAPE_UNIT != (MINOR(ino->i_rdev) & 3)) {
+		/*  Other selection than last time
+		 */
+		ftape_init_driver();
+	}
+	ftape_unit = MINOR(ino->i_rdev);
+	ftape_failure = 0;	/* allow tape operations */
+	old_sigmask = current->blocked;
+	current->blocked = _BLOCK_ALL;
+	fdc_save_drive_specs();	/* save Drive Specification regs on i82078-1's */
+	result = _ftape_open();
+	if (result < 0) {
+		TRACE(1, "_ftape_open failed");
+		current->blocked = old_sigmask;		/* restore mask */
+		MOD_DEC_USE_COUNT;	/* unlock module in memory */
+		TRACE_EXIT;
+		return result;
+	} else {
+		busy_flag = 1;
+		/*  Mask signals that will disturb proper operation of the
+		 *  program that is calling.
+		 */
+		current->blocked = old_sigmask | _DO_BLOCK;
+		TRACE_EXIT;
+		return 0;
+	}
+}
+
+/*      Close ftape device
+ */
+static void ftape_close(struct inode *ino, struct file *filep)
+{
+	TRACE_FUN(4, "ftape_close");
+	int result;
+
+	if (!busy_flag || MINOR(ino->i_rdev) != ftape_unit) {
+		TRACE(1, "failed: not busy or wrong unit");
+		TRACE_EXIT;
+		return;		/* keep busy_flag !(?) */
+	}
+	current->blocked = _BLOCK_ALL;
+	result = _ftape_close();
+	if (result < 0) {
+		TRACE(1, "_ftape_close failed");
+	}
+	fdc_restore_drive_specs();	/* restore original values */
+	ftape_failure = 1;	/* inhibit any operation but open */
+	busy_flag = 0;
+	current->blocked = old_sigmask;		/* restore before open state */
+	TRACE_EXIT;
+	MOD_DEC_USE_COUNT;	/* unlock module in memory */
+}
+
+/*      Ioctl for ftape device
+ */
+static int ftape_ioctl(struct inode *ino, struct file *filep,
+		       unsigned int command, unsigned long arg)
+{
+	TRACE_FUN(4, "ftape_ioctl");
+	int result = -EIO;
+	int old_sigmask;
+
+	if (!busy_flag || MINOR(ino->i_rdev) != ftape_unit || ftape_failure) {
+		TRACE(1, "failed: not busy, failure or wrong unit");
+		TRACE_EXIT;
+		return -EIO;
+	}
+	old_sigmask = current->blocked;		/* save mask */
+	current->blocked = _BLOCK_ALL;
+	/* This will work as long as sizeof( void*) == sizeof( long)
+	 */
+	result = _ftape_ioctl(command, (void *) arg);
+	current->blocked = old_sigmask;		/* restore mask */
+	TRACE_EXIT;
+	return result;
+}
+
+/*      Read from tape device
+ */
+static int ftape_read(struct inode *ino, struct file *fp, char *buff, int req_len)
+{
+	TRACE_FUN(5, "ftape_read");
+	int result = -EIO;
+	int old_sigmask;
+
+	TRACEi(5, "called with count:", req_len);
+	if (!busy_flag || MINOR(ino->i_rdev) != ftape_unit || ftape_failure) {
+		TRACE(1, "failed: not busy, failure or wrong unit");
+		TRACE_EXIT;
+		return -EIO;
+	}
+	old_sigmask = current->blocked;		/* save mask */
+	current->blocked = _BLOCK_ALL;
+	result = _ftape_read(buff, req_len);
+	TRACEi(7, "return with count:", result);
+	current->blocked = old_sigmask;		/* restore mask */
+	TRACE_EXIT;
+	return result;
+}
+
+/*      Write to tape device
+ */
+static int ftape_write(struct inode *ino, struct file *fp, const char *buff, int req_len)
+{
+	TRACE_FUN(8, "ftape_write");
+	int result = -EIO;
+	int old_sigmask;
+
+	TRACEi(5, "called with count:", req_len);
+	if (!busy_flag || MINOR(ino->i_rdev) != ftape_unit || ftape_failure) {
+		TRACE(1, "failed: not busy, failure or wrong unit");
+		TRACE_EXIT;
+		return -EIO;
+	}
+	old_sigmask = current->blocked;		/* save mask */
+	current->blocked = _BLOCK_ALL;
+	result = _ftape_write(buff, req_len);
+	TRACEi(7, "return with count:", result);
+	current->blocked = old_sigmask;		/* restore mask */
+	TRACE_EXIT;
+	return result;
+}
+
+/*      Seek tape device - not implemented !
+ */
+static int ftape_lseek(struct inode *ino, struct file *filep, off_t offset, int origin)
+{
+	return -ESPIPE;
+}
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/kernel-interface.h linux/drivers/char/ftape/kernel-interface.h
--- v1.3.71/linux/drivers/char/ftape/kernel-interface.h	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/kernel-interface.h	Wed Mar  6 15:07:20 1996
@@ -0,0 +1,65 @@
+#ifndef _KERNEL_INTERFACE_H
+#define _KERNEL_INTERFACE_H
+
+/*
+ * Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ $Source: /home/bas/distr/ftape-2.03b/RCS/kernel-interface.h,v $
+ $Author: bas $
+ *
+ $Revision: 1.24 $
+ $Date: 1995/04/30 13:15:14 $
+ $State: Beta $
+ *
+ * ----Description----
+ *
+ */
+
+#include <linux/linkage.h>
+#include <linux/signal.h>
+
+#define _S(nr) (1<<((nr)-1))
+#define _NEVER_BLOCK    (_S(SIGKILL)|_S(SIGSTOP))
+#define _DONT_BLOCK     (_NEVER_BLOCK|_S(SIGINT))
+#define _DO_BLOCK       (_S(SIGPIPE))
+#define _BLOCK_ALL      (0xffffffffL)
+
+
+#ifndef QIC117_TAPE_MAJOR
+#define QIC117_TAPE_MAJOR 27
+#endif
+
+#define FTAPE_NO_REWIND 4	/* mask for minor nr */
+
+/*      kernel-interface.c defined global variables.
+ */
+extern byte *tape_buffer[];
+extern char kernel_version[];
+
+/*      kernel-interface.c defined global functions.
+ */
+asmlinkage extern int init_module(void);
+asmlinkage extern void cleanup_module(void);
+
+/*      kernel global functions not (yet) standard accessible
+ *      (linked at load time by modules package).
+ */
+asmlinkage extern sys_sgetmask(void);
+asmlinkage extern sys_ssetmask(int);
+
+#endif
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/qic117.h linux/drivers/char/ftape/qic117.h
--- v1.3.71/linux/drivers/char/ftape/qic117.h	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/qic117.h	Wed Mar  6 15:07:21 1996
@@ -0,0 +1,280 @@
+#ifndef _QIC117_H
+#define _QIC117_H
+
+/*
+ *      Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ $Source: /home/bas/distr/ftape-2.03b/RCS/qic117.h,v $
+ $Author: bas $
+ *
+ $Revision: 1.27 $
+ $Date: 1995/05/01 19:02:20 $
+ $State: Beta $
+ *
+ *      This file contains QIC-117 spec. related definitions
+ *      for the QIC-40/80 floppy-tape driver for Linux.
+ *
+ *      These data were taken from the Quarter-Inch Cartridge
+ *      Drive Standards, Inc. document titled:
+ *      `Common Command Set Interface Specification for Flexible
+ *       Disk Controller Based Minicartridge Tape Drives'
+ *       document QIC-117 Revision C, 3 Dec 92.
+ *      For more information, contact:
+ *       Quarter-Inch Cartridge Drive Standards, Inc.
+ *       311 East Carrillo Street
+ *       Santa Barbara, California 93101
+ *       Telephone (805) 963-3853
+ *       Fax       (805) 962-1541
+ *
+ *      Current QIC standard revisions (of interest) are:
+ *       QIC-80-MC,   Rev. M, 15 Jun 95.
+ *       QIC-117,     Rev. I, 15 Mar 95.
+ *       QIC-122,     Rev. B,  6 Mar 91.
+ *       QIC-130,     Rev. C,  2 Sep 92.
+ *       QIC-3010-MC, Rev. F, 14 Jun 95.
+ *       QIC-3020-MC, Rev. G, 31 Aug 95.
+ *       QIC-CRF3,    Rev. B, 15 Jun 95.
+ *
+ */
+
+/*
+ *      QIC-117 common command set rev. I.
+ *      These commands are sent to the tape unit
+ *      as number of pulses over the step line.
+ */
+
+#define QIC_RESET			1
+#define QIC_REPORT_NEXT_BIT		2
+#define QIC_PAUSE			3
+#define QIC_MICRO_STEP_PAUSE		4
+#define QIC_ALTERNATE_TIMEOUT		5
+#define QIC_REPORT_DRIVE_STATUS		6
+#define QIC_REPORT_ERROR_CODE		7
+#define QIC_REPORT_DRIVE_CONFIGURATION	8
+#define QIC_REPORT_ROM_VERSION		9
+#define QIC_LOGICAL_FORWARD		10
+#define QIC_PHYSICAL_REVERSE		11
+#define QIC_PHYSICAL_FORWARD		12
+#define QIC_SEEK_HEAD_TO_TRACK		13
+#define QIC_SEEK_LOAD_POINT		14
+#define QIC_ENTER_FORMAT_MODE		15
+#define QIC_WRITE_REFERENCE_BURST	16
+#define QIC_ENTER_VERIFY_MODE		17
+#define QIC_STOP_TAPE			18
+/* commands 19-20: reserved */
+#define QIC_MICRO_STEP_HEAD_UP		21
+#define QIC_MICRO_STEP_HEAD_DOWN	22
+#define QIC_SOFT_SELECT			23
+#define QIC_SOFT_DESELECT		24
+#define QIC_SKIP_REVERSE		25
+#define QIC_SKIP_FORWARD		26
+#define QIC_SELECT_RATE			27
+/* command 27, in ccs2: Select Rate or Format */
+#define QIC_ENTER_DIAGNOSTIC_1		28
+#define QIC_ENTER_DIAGNOSTIC_2		29
+#define QIC_ENTER_PRIMARY_MODE		30
+/* command 31: vendor unique */
+#define QIC_REPORT_VENDOR_ID		32
+#define QIC_REPORT_TAPE_STATUS		33
+#define QIC_SKIP_EXTENDED_REVERSE	34
+#define QIC_SKIP_EXTENDED_FORWARD	35
+#define QIC_CALIBRATE_TAPE_LENGTH	36
+#define QIC_REPORT_FORMAT_SEGMENTS	37
+#define QIC_SET_FORMAT_SEGMENTS		38
+/* commands 39-45: reserved */
+#define QIC_PHANTOM_SELECT		46
+#define QIC_PHANTOM_DESELECT		47
+
+typedef enum {
+	discretional = 0, required, ccs1, ccs2
+} qic_compatibility;
+
+typedef enum {
+	unused, mode, motion, report
+} command_types;
+
+struct qic117_command_table {
+	char *name;
+	byte mask;
+	byte state;
+	byte cmd_type;
+	byte non_intr;
+	byte level;
+};
+
+#define QIC117_COMMANDS {\
+/* command                           mask  state cmd_type           */\
+/* |    name                         |     |     |       non_intr   */\
+/* |    |                            |     |     |       |  level   */\
+/* 0*/ {NULL,                        0x00, 0x00, mode,   0, discretional},\
+/* 1*/ {"soft reset",                0x00, 0x00, motion, 1, required},\
+/* 2*/ {"report next bit",           0x00, 0x00, report, 0, required},\
+/* 3*/ {"pause",                     0x36, 0x24, motion, 1, required},\
+/* 4*/ {"micro step pause",          0x36, 0x24, motion, 1, required},\
+/* 5*/ {"alternate command timeout", 0x00, 0x00, mode,   0, required},\
+/* 6*/ {"report drive status",       0x00, 0x00, report, 0, required},\
+/* 7*/ {"report error code",         0x01, 0x01, report, 0, required},\
+/* 8*/ {"report drive configuration",0x00, 0x00, report, 0, required},\
+/* 9*/ {"report rom version",        0x00, 0x00, report, 0, required},\
+/*10*/ {"logical forward",           0x37, 0x25, motion, 0, required},\
+/*11*/ {"physical reverse",          0x17, 0x05, motion, 0, required},\
+/*12*/ {"physical forward",          0x17, 0x05, motion, 0, required},\
+/*13*/ {"seek head to track",        0x37, 0x25, motion, 0, required},\
+/*14*/ {"seek load point",           0x17, 0x05, motion, 1, required},\
+/*15*/ {"enter format mode",         0x1f, 0x05, mode,   0, required},\
+/*16*/ {"write reference burst",     0x1f, 0x05, motion, 1, required},\
+/*17*/ {"enter verify mode",         0x37, 0x25, mode,   0, required},\
+/*18*/ {"stop tape",                 0x00, 0x00, motion, 1, required},\
+/*19*/ {"reserved (19)",             0x00, 0x00, unused, 0, discretional},\
+/*20*/ {"reserved (20)",             0x00, 0x00, unused, 0, discretional},\
+/*21*/ {"micro step head up",        0x02, 0x00, motion, 0, required},\
+/*22*/ {"micro step head down",      0x02, 0x00, motion, 0, required},\
+/*23*/ {"soft select",               0x00, 0x00, mode,   0, discretional},\
+/*24*/ {"soft deselect",             0x00, 0x00, mode,   0, discretional},\
+/*25*/ {"skip segments reverse",     0x36, 0x24, motion, 1, required},\
+/*26*/ {"skip segments forward",     0x36, 0x24, motion, 1, required},\
+/*27*/ {"select rate [or format]",   0x03, 0x01, mode,   0, required /* [ccs2] */},\
+/*28*/ {"enter diag mode 1",         0x00, 0x00, mode,   0, discretional},\
+/*29*/ {"enter diag mode 2",         0x00, 0x00, mode,   0, discretional},\
+/*30*/ {"enter primary mode",        0x00, 0x00, mode,   0, required},\
+/*31*/ {"vendor unique (31)",        0x00, 0x00, unused, 0, discretional},\
+/*32*/ {"report vendor id",          0x00, 0x00, report, 0, required},\
+/*33*/ {"report tape status",        0x04, 0x04, report, 0, ccs1},\
+/*34*/ {"skip extended reverse",     0x36, 0x24, motion, 1, ccs1},\
+/*35*/ {"skip extended forward",     0x36, 0x24, motion, 1, ccs1},\
+/*36*/ {"calibrate tape length",     0x17, 0x05, motion, 1, ccs2},\
+/*37*/ {"report format segments",    0x17, 0x05, report, 0, ccs2},\
+/*38*/ {"set format segments",       0x17, 0x05, mode,   0, ccs2},\
+/*39*/ {"reserved (39)",             0x00, 0x00, unused, 0, discretional},\
+/*40*/ {"vendor unique (40)",        0x00, 0x00, unused, 0, discretional},\
+/*41*/ {"vendor unique (41)",        0x00, 0x00, unused, 0, discretional},\
+/*42*/ {"vendor unique (42)",        0x00, 0x00, unused, 0, discretional},\
+/*43*/ {"vendor unique (43)",        0x00, 0x00, unused, 0, discretional},\
+/*44*/ {"vendor unique (44)",        0x00, 0x00, unused, 0, discretional},\
+/*45*/ {"vendor unique (45)",        0x00, 0x00, unused, 0, discretional},\
+/*46*/ {"phantom select",            0x00, 0x00, mode,   0, discretional},\
+/*47*/ {"phantom deselect",          0x00, 0x00, mode,   0, discretional},\
+}
+
+/*
+ *      Status bits returned by QIC_REPORT_DRIVE_STATUS
+ */
+
+#define QIC_STATUS_READY	0x01	/* Drive is ready or idle. */
+#define QIC_STATUS_ERROR	0x02	/* Error detected, must read
+					   error code to clear this */
+#define QIC_STATUS_CARTRIDGE_PRESENT 0x04	/* Tape is present */
+#define QIC_STATUS_WRITE_PROTECT 0x08	/* Tape is write protected */
+#define QIC_STATUS_NEW_CARTRIDGE 0x10	/* New cartridge inserted, must
+					   read error status to clear. */
+#define QIC_STATUS_REFERENCED	0x20	/* Cartridge appears to have been
+					   formatted. */
+#define QIC_STATUS_AT_BOT	0x40	/* Cartridge is at physical
+					   beginning of tape. */
+#define QIC_STATUS_AT_EOT	0x80	/* Cartridge is at physical end
+					   of tape. */
+/*
+ *      Status bits returned by QIC_REPORT_DRIVE_CONFIGURATION
+ */
+
+#define QIC_CONFIG_RATE_MASK	0x18
+#define QIC_CONFIG_RATE_SHIFT	3
+#define QIC_CONFIG_RATE_250	0
+#define QIC_CONFIG_RATE_500	2
+#define QIC_CONFIG_RATE_1000	3
+#define QIC_CONFIG_RATE_2000	1
+
+#define QIC_CONFIG_LONG		0x40	/* Extra Length Tape Detected */
+#define QIC_CONFIG_80		0x80	/* QIC-80 detected. */
+
+/*
+ *      Status bits returned by QIC_REPORT_TAPE_STATUS
+ */
+
+#define QIC_TAPE_STD_MASK       0x0f
+#define QIC_TAPE_QIC40  	0x01
+#define QIC_TAPE_QIC80  	0x02
+#define QIC_TAPE_QIC3020  	0x03
+#define QIC_TAPE_QIC3010  	0x04
+
+#define QIC_TAPE_LEN_MASK       0x70
+#define QIC_TAPE_205FT          0x10
+#define QIC_TAPE_307FT          0x20
+#define QIC_TAPE_400FT          0x30
+#define QIC_TAPE_1100FT         0x40
+#define QIC_TAPE_FLEX		0x60
+
+#define QIC_TAPE_WIDE		0x80
+
+/*
+ *      Errors: List of error codes, and their severity.
+ */
+
+typedef struct {
+	char *message;		/* Text describing the error. */
+	int fatal;		/* Non-zero if the error is fatal. */
+} ftape_error;
+
+#define QIC117_ERRORS {\
+  /* 0*/ { "No error", 0, },\
+  /* 1*/ { "Command Received while Drive Not Ready", 0, },\
+  /* 2*/ { "Cartridge Not Present or Removed", 1, },\
+  /* 3*/ { "Motor Speed Error (not within 1%)", 1, },\
+  /* 4*/ { "Motor Speed Fault (jammed, or gross speed error", 1, },\
+  /* 5*/ { "Cartridge Write Protected", 1, },\
+  /* 6*/ { "Undefined or Reserved Command Code", 1, },\
+  /* 7*/ { "Illegal Track Address Specified for Seek", 1, },\
+  /* 8*/ { "Illegal Command in Report Subcontext", 0, },\
+  /* 9*/ { "Illegal Entry into a Diagnostic Mode", 1, },\
+  /*10*/ { "Broken Tape Detected (based on hole sensor)", 1, },\
+  /*11*/ { "Warning--Read Gain Setting Error", 1, },\
+  /*12*/ { "Command Received While Error Status Pending (obs)", 1, },\
+  /*13*/ { "Command Received While New Cartridge Pending", 1, },\
+  /*14*/ { "Command Illegal or Undefined in Primary Mode", 1, },\
+  /*15*/ { "Command Illegal or Undefined in Format Mode", 1, },\
+  /*16*/ { "Command Illegal or Undefined in Verify Mode", 1, },\
+  /*17*/ { "Logical Forward Not a Logical BOT or no Format Segments in Format Mode", 1, },\
+  /*18*/ { "Logical EOT Before All Segments generated", 1, },\
+  /*19*/ { "Command Illegal When Cartridge Not Referenced", 1, },\
+  /*20*/ { "Self-Diagnostic Failed (cannot be cleared)", 1, },\
+  /*21*/ { "Warning EEPROM Not Initialized, Defaults Set", 1, },\
+  /*22*/ { "EEPROM Corrupted or Hardware Failure", 1, },\
+  /*23*/ { "Motion Time-out Error", 1, },\
+  /*24*/ { "Data Segment Too Long -- Logical Forward or Pause", 1, },\
+  /*25*/ { "Transmit Overrun (obs)", 1, },\
+  /*26*/ { "Power On Reset Occurred", 0, },\
+  /*27*/ { "Software Reset Occurred", 0, },\
+  /*28*/ { "Diagnostic Mode 1 Error", 1, },\
+  /*29*/ { "Diagnostic Mode 2 Error", 1, },\
+  /*30*/ { "Command Received During Non-Interruptable Process", 1, },\
+  /*31*/ { "Rate or Format Selection Error", 1, },\
+  /*32*/ { "Illegal Command While in High Speed Mode", 1, },\
+  /*33*/ { "Illegal Seek Segment Value", 1, },\
+  /*34*/ { "Invalid Media", 1, },\
+  /*35*/ { "Head Positioning Failure", 1, },\
+  /*36*/ { "Write Reference Burst Failure", 1, },\
+  /*37*/ { "Prom Code Missing", 1, },\
+  /*38*/ { "Invalid Format", 1, },\
+  /*39*/ { "EOT/BOT System Failure", 1, },\
+  /*40*/ { "Prom A Checksum Error", 1, },\
+  /*41*/ { "Drive Wakeup Reset Occurred", 1, },\
+  /*42*/ { "Prom B Checksum Error", 1, },\
+  /*43*/ { "Illegal Entry into Format Mode", 1, },\
+}
+
+#endif				/* _QIC117_H */
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/tracing.c linux/drivers/char/ftape/tracing.c
--- v1.3.71/linux/drivers/char/ftape/tracing.c	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/tracing.c	Wed Mar  6 15:07:21 1996
@@ -0,0 +1,104 @@
+
+/*
+ *      Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ *      This file contains the reading code
+ *      for the QIC-117 floppy-tape driver for Linux.
+ */
+
+#include <linux/module.h>
+#include <linux/ftape.h>
+
+#include "tracing.h"
+
+/*      Global vars.
+ */
+/*      tracing
+ *      set it to:     to get:
+ *       0              bugs
+ *       1              + errors
+ *       2              + warnings
+ *       3              + information
+ *       4              + more information
+ *       5              + program flow
+ *       6              + fdc/dma info
+ *       7              + data flow
+ *       8              + everything else
+ */
+int tracing = 3;		/* Default level: report only errors */
+
+#ifndef NO_TRACE_AT_ALL
+
+byte trace_id = 0;
+int function_nest_level = 0;
+
+/*      Local vars.
+ */
+static char spacing[] = "*                              ";
+
+int trace_call(int level, char *file, char *name)
+{
+	char *indent;
+
+	if (tracing >= level && level <= TOP_LEVEL) {
+		/*    Since printk seems not to work with "%*s" format
+		 *    we'll use this work-around.
+		 */
+		if (function_nest_level < sizeof(spacing)) {
+			indent = spacing + sizeof(spacing) - 1 - function_nest_level;
+		} else {
+			indent = spacing;
+		}
+		printk(KERN_INFO "[%03d]%s+%s (%s)\n", (int) trace_id++, indent, file, name);
+	}
+	return function_nest_level++;
+}
+
+void trace_exit(int level, char *file, char *name)
+{
+	char *indent;
+
+	if (tracing >= level && level <= TOP_LEVEL) {
+		/*    Since printk seems not to work with "%*s" format
+		 *    we'll use this work-around.
+		 */
+		if (function_nest_level < sizeof(spacing)) {
+			indent = spacing + sizeof(spacing) - 1 - function_nest_level;
+		} else {
+			indent = spacing;
+		}
+		printk(KERN_INFO "[%03d]%s-%s (%s)\n", (int) trace_id++, indent, file, name);
+	}
+}
+
+void trace_log(char *file, char *name)
+{
+	char *indent;
+
+	/*    Since printk seems not to work with "%*s" format
+	 *    we'll use this work-around.
+	 */
+	if (function_nest_level < sizeof(spacing)) {
+		indent = spacing + sizeof(spacing) - 1 - function_nest_level;
+	} else {
+		indent = spacing;
+	}
+	printk(KERN_INFO "[%03d]%s%s (%s) - ", (int) trace_id++, indent, file, name);
+}
+
+#endif				/* NO_TRACE_AT_ALL */
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/tracing.h linux/drivers/char/ftape/tracing.h
--- v1.3.71/linux/drivers/char/ftape/tracing.h	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/tracing.h	Wed Mar  6 15:07:21 1996
@@ -0,0 +1,99 @@
+#ifndef _TRACING_H
+#define _TRACING_H
+
+/*
+ * Copyright (C) 1994-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ $Source: /home/bas/distr/ftape-2.03b/RCS/tracing.h,v $
+ $Author: bas $
+ *
+ $Revision: 1.10 $
+ $Date: 1995/04/22 07:30:15 $
+ $State: Beta $
+ *
+ *      This file contains definitions that eases the debugging
+ *      of the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include <linux/kernel.h>
+
+#ifdef NO_TRACE_AT_ALL
+static inline void trace_dummy(void)
+{
+}
+
+#define TRACE_FUN( level, name) int _trace_dummy
+#define TRACE_EXIT              _trace_dummy= 0
+#define TRACE_(l,m)             trace_dummy()
+#define TRACE(l,m)              trace_dummy()
+#define TRACEi(l,m,i)           trace_dummy()
+#define TRACElx(l,m,i)          trace_dummy()
+#define TRACEx1(l,m,a)          trace_dummy()
+#define TRACEx2(l,m,a,b)        trace_dummy()
+#define TRACEx3(l,m,a,b,c)      trace_dummy()
+#define TRACEx4(l,m,a,b,c,d)    trace_dummy()
+#define TRACEx5(l,m,a,b,c,d,e)  trace_dummy()
+#define TRACEx6(l,m,a,b,c,d,e,f)  trace_dummy()
+#else
+#ifdef NO_TRACE
+#define TOP_LEVEL 2
+#else
+#define TOP_LEVEL 10
+#endif
+
+#define TRACE_FUN( level, name) \
+  char _trace_fun[] = name; \
+  int _function_nest_level = trace_call( level, __FILE__, _trace_fun); \
+  int _tracing = level
+
+#define TRACE_EXIT \
+  function_nest_level = _function_nest_level; \
+  trace_exit( _tracing, __FILE__, _trace_fun)
+
+#define TRACE_(l,m) \
+{ \
+  if (tracing >= (l) && (l) <= TOP_LEVEL) { \
+    trace_log( __FILE__, _trace_fun); \
+    m; \
+  } \
+}
+#define TRACE(l,m) TRACE_(l,printk(m".\n"))
+#define TRACEi(l,m,i) TRACE_(l,printk(m" %d.\n",i))
+#define TRACElx(l,m,i) TRACE_(l,printk(m" 0x%08lx.\n",i))
+#define TRACEx1(l,m,a) TRACE_(l,printk(m".\n",a))
+#define TRACEx2(l,m,a,b) TRACE_(l,printk(m".\n",a,b))
+#define TRACEx3(l,m,a,b,c) TRACE_(l,printk(m".\n",a,b,c))
+#define TRACEx4(l,m,a,b,c,d) TRACE_(l,printk(m".\n",a,b,c,d))
+#define TRACEx5(l,m,a,b,c,d,e) TRACE_(l,printk(m".\n",a,b,c,d,e))
+#define TRACEx6(l,m,a,b,c,d,e,f) TRACE_(l,printk(m".\n",a,b,c,d,e,f))
+
+/*      Global variables declared in tracing.c
+ */
+extern unsigned char trace_id;
+extern int tracing;		/* sets default level */
+extern int function_nest_level;
+
+/*      Global functions declared in tracing.c
+ */
+extern int trace_call(int level, char *file, char *name);
+extern void trace_exit(int level, char *file, char *name);
+extern void trace_log(char *file, char *name);
+
+#endif				/* NO_TRACE_AT_ALL */
+
+#endif
diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/vendors.h linux/drivers/char/ftape/vendors.h
--- v1.3.71/linux/drivers/char/ftape/vendors.h	Thu Jan  1 02:00:00 1970
+++ linux/drivers/char/ftape/vendors.h	Wed Mar  6 15:07:21 1996
@@ -0,0 +1,127 @@
+#ifndef _VENDORS_H
+#define _VENDORS_H
+
+/*
+ *      Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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.
+
+ *
+ $Source: /home/bas/distr/ftape-2.03b/RCS/vendors.h,v $
+ $Author: bas $
+ *
+ $Revision: 1.34 $
+ $Date: 1995/05/27 08:54:21 $
+ $State: Beta $
+ *
+ *      This file contains the supported drive types
+ *      with their QIC-117 spec. vendor code and
+ *      drive dependent configuration information.
+ */
+
+typedef enum {
+	unknown_wake_up = 0,
+	no_wake_up,
+	wake_up_colorado,
+	wake_up_mountain,
+	wake_up_insight,
+} wake_up_types;
+
+typedef struct {
+	wake_up_types wake_up;	/* see wake_up_types */
+	char *name;		/* Text describing the drive */
+} wakeup_method;
+
+/*  Note: order of entries in WAKEUP_METHODS must be so that a variable
+ *        of type wake_up_types can be used as an index in the array.
+ */
+#define WAKEUP_METHODS { \
+  { unknown_wake_up,    "Unknown" }, \
+  { no_wake_up,         "None" }, \
+  { wake_up_colorado,   "Colorado" }, \
+  { wake_up_mountain,   "Mountain" }, \
+  { wake_up_insight,    "Motor-on" }, \
+}
+
+typedef struct {
+	unsigned int vendor_id;	/* vendor id from drive */
+	int speed;		/* maximum tape transport speed (ips) */
+	wake_up_types wake_up;	/* see wake_up_types */
+	char *name;		/* Text describing the drive */
+} vendor_struct;
+
+#define UNKNOWN_VENDOR (-1)
+
+#define QIC117_VENDORS { \
+/* see _vendor_struct */ \
+  { 0x00000,  82, wake_up_colorado,  "Colorado DJ-10 (old)" }, \
+  { 0x00047,  90, wake_up_colorado,  "Colorado DJ-10/DJ-20" }, \
+  { 0x011c2,  84, wake_up_colorado,  "Colorado 700" }, \
+  { 0x011c3,  90, wake_up_colorado,  "Colorado 1400" }, \
+  { 0x011c4,  84, wake_up_colorado,  "Colorado DJ-10/DJ-20 (new)" }, \
+  { 0x011c5,  84, wake_up_colorado,  "HP Colorado T1000" }, \
+  { 0x00005,  45, wake_up_mountain,  "Archive 5580i" }, \
+  { 0x10005,  50, wake_up_insight,   "Insight 80Mb, Irwin 80SX" }, \
+  { 0x00140,  74, wake_up_mountain,  "Archive S.Hornet [Identity/Escom]" }, \
+  { 0x00146,  72, wake_up_mountain,  "Archive 31250Q [Escom]" }, \
+  { 0x0014a, 100, wake_up_mountain,  "Archive XL9250i [Conner/Escom]" }, \
+  { 0x0014c,  98, wake_up_mountain,  "Conner C250MQT" }, \
+  { 0x0014e,  80, wake_up_mountain,  "Conner C250MQ" }, \
+  { 0x00150,  80, wake_up_mountain,  "Conner TSM420R/TST800R" }, \
+  { 0x00152,  80, wake_up_mountain,  "Conner TSM850R" }, \
+  { 0x00156,  80, wake_up_mountain,  "Conner TSM850R/1700R/TST3200R" }, \
+  { 0x00180,   0, wake_up_mountain,  "Summit SE 150" }, \
+  { 0x00181,  85, wake_up_mountain,  "Summit SE 250, Mountain FS8000" }, \
+  { 0x001c1,  82, no_wake_up,        "Wangtek 3040F" }, \
+  { 0x001c8,  64, no_wake_up,        "Wangtek 3080F" }, \
+  { 0x001c8,  64, wake_up_colorado,  "Wangtek 3080F" }, \
+  { 0x001ca,  67, no_wake_up,        "Wangtek 3080F (new)" }, \
+  { 0x001cc,  77, wake_up_colorado,  "Wangtek 3200" }, \
+  { 0x001cd,  75, wake_up_colorado,  "Reveal TB1400" }, \
+  { 0x00380,   0, wake_up_colorado,  "Exabyte EXB-1500" }, \
+  { 0x08880,  64, no_wake_up,        "Iomega 250" }, \
+  { 0x08880,  64, wake_up_colorado,  "Iomega 250, Ditto 800" }, \
+  { 0x08880,  64, wake_up_insight,   "Iomega 250" }, \
+  { 0x08881,   0, wake_up_colorado,  "Iomega 700" }, \
+  { 0x08882,  80, wake_up_colorado,  "Iomega 3200" }, \
+  { 0x00021,  70, no_wake_up,        "AIWA CT-803" }, \
+  { 0x00021,   0, wake_up_mountain,  "COREtape QIC80" }, \
+}
+
+#define QIC117_MAKE_CODES { \
+  { 0, "Unassigned" }, \
+  { 1, "Alloy Computer Products" }, \
+  { 2, "3M" }, \
+  { 3, "Tandberg Data" }, \
+  { 4, "Colorado" }, \
+  { 5, "Archive/Conner" }, \
+  { 6, "Mountain/Summit Memory Systems" }, \
+  { 7, "Wangtek/Rexon/Tecmar" }, \
+  { 8, "Sony" }, \
+  { 9, "Cipher Data Products" }, \
+  { 10, "Irwin Magnetic Systems" }, \
+  { 11, "Braemar" }, \
+  { 12, "Verbatim" }, \
+  { 13, "Core International" }, \
+  { 14, "Exabyte" }, \
+  { 15, "Teac" }, \
+  { 16, "Gigatek" }, \
+  { 17, "ComByte" }, \
+  { 18, "PERTEC Memories" }, \
+  { 71, "Colorado" }, \
+  { 546, "Iomega Inc" }, \
+}
+
+#endif
diff -u --recursive --new-file v1.3.71/linux/drivers/char/mem.c linux/drivers/char/mem.c
--- v1.3.71/linux/drivers/char/mem.c	Wed Feb 28 11:50:01 1996
+++ linux/drivers/char/mem.c	Thu Mar  7 15:59:02 1996
@@ -13,6 +13,7 @@
 #include <linux/tty.h>
 #include <linux/mouse.h>
 #include <linux/tpqic02.h>
+#include <linux/ftape.h>
 #include <linux/malloc.h>
 #include <linux/mman.h>
 #include <linux/mm.h>
@@ -386,7 +387,7 @@
 #ifdef CONFIG_PRINTER
 	lp_init();
 #endif
-#if defined (CONFIG_BUSMOUSE) || defined (CONFIG_82C710_MOUSE) || \
+#if defined (CONFIG_BUSMOUSE) || \
     defined (CONFIG_PSMOUSE) || defined (CONFIG_MS_BUSMOUSE) || \
     defined (CONFIG_ATIXL_BUSMOUSE) || defined(CONFIG_SOFT_WATCHDOG)
 	mouse_init();
@@ -399,6 +400,9 @@
 #endif
 #if CONFIG_ISDN
 	isdn_init();
+#endif
+#ifdef CONFIG_FTAPE
+	ftape_init();
 #endif
 	return 0;
 }
diff -u --recursive --new-file v1.3.71/linux/drivers/char/serial.c linux/drivers/char/serial.c
--- v1.3.71/linux/drivers/char/serial.c	Tue Mar  5 10:10:55 1996
+++ linux/drivers/char/serial.c	Fri Mar  8 09:43:24 1996
@@ -76,6 +76,8 @@
 #define RS_STROBE_TIME (10*HZ)
 #define RS_ISR_PASS_LIMIT 256
 
+#define IRQ_T(info) ((info->flags & ASYNC_SHARE_IRQ) ? SA_SHIRQ : SA_INTERRUPT)
+
 #define _INLINE_ inline
   
 /*
@@ -962,7 +964,8 @@
 		} else 
 			handler = rs_interrupt_single;
 
-		retval = request_irq(info->irq, handler, SA_INTERRUPT, "serial", NULL);
+		retval = request_irq(info->irq, handler, IRQ_T(info),
+				     "serial", NULL);
 		if (retval) {
 			restore_flags(flags);
 			if (suser()) {
@@ -1090,7 +1093,8 @@
 			  !IRQ_ports[info->irq]->next_port)) {
 		if (IRQ_ports[info->irq]) {
 			free_irq(info->irq, NULL);
-			retval = request_irq(info->irq, rs_interrupt_single, SA_INTERRUPT, "serial", NULL);
+			retval = request_irq(info->irq, rs_interrupt_single,
+					     IRQ_T(info), "serial", NULL);
 			
 			if (retval)
 				printk("serial shutdown: request_irq: error %d"
@@ -1852,7 +1856,7 @@
 		else
 			handler = rs_interrupt;
 
-		retval = request_irq(info->irq, handler, SA_INTERRUPT,
+		retval = request_irq(info->irq, handler, IRQ_T(info),
 				     "serial", NULL);
 		if (retval) {
 			printk("Couldn't reallocate serial interrupt "
@@ -2823,6 +2827,7 @@
 	}
 	info->irq = req->irq;
 	info->port = req->port;
+	info->flags = req->flags;
 	autoconfig(info);
 	if (info->type == PORT_UNKNOWN) {
 		restore_flags(flags);
diff -u --recursive --new-file v1.3.71/linux/drivers/net/CONFIG linux/drivers/net/CONFIG
--- v1.3.71/linux/drivers/net/CONFIG	Wed Oct  4 14:14:30 1995
+++ linux/drivers/net/CONFIG	Wed Mar  6 14:53:46 1996
@@ -40,9 +40,9 @@
 #       EWRK3_DEBUG	Set the desired debug level
 #
 #  DE4x5		The DIGITAL series of PCI/EISA Ethernet Cards,
-#			DE425, DE434, DE435, DE500
+#			DE425, DE434, DE435, DE450, DE500
 #     DE4X5_DEBUG	Set the desired debug level
-#     IS_NOT_DEC        May allow driver to work with Zynx & SMC cards - 
+#     DEC_ONLY          Allows driver to work with DIGITAL cards only - 
 #			see linux/drivers/net/README.de4x5
 #     DE4X5_AUTOSENSE   (Default) auto media/mode selection
 #                       If you want at least one board to not autosense then
@@ -59,8 +59,6 @@
 #                       For manual 100Mb/s mode selection  (DC21140) = _100Mb
 #                       The DC21040 will default to TP if TP_NW is specified
 #                       The DC21041 will default to BNC if BNC_AUI is specified
-#                       The DC21140 needs it's speed to be manually set to
-#                       10Mb/s or 100Mb/s (AUTO defaults to 10Mb/s)
 #
 
 # The following options exist, but cannot be set in this file.
@@ -81,8 +79,8 @@
 NE_OPTS		=
 HP_OPTS		=
 PLIP_OPTS	=
-DEPCA_OPTS	= -DDEPCA_DEBUG=1
-EWRK3_OPTS	= -DEWRK3_DEBUG=1
-DE4X5_OPTS	= -DDE4X5_DEBUG=1 -DDE4X5_AUTOSENSE=AUTO
+DEPCA_OPTS	= 
+EWRK3_OPTS	= 
+DE4X5_OPTS	= -DDE4X5_AUTOSENSE=AUTO
 ELP_OPTS	= -DELP_DEBUG=1 -DELP_NEED_HARD_RESET=0
 
diff -u --recursive --new-file v1.3.71/linux/drivers/net/Config.in linux/drivers/net/Config.in
--- v1.3.71/linux/drivers/net/Config.in	Tue Mar  5 10:10:57 1996
+++ linux/drivers/net/Config.in	Fri Mar  8 10:03:32 1996
@@ -19,6 +19,7 @@
 if [ "$CONFIG_NET_VENDOR_SMC" = "y" ]; then
   tristate 'WD80*3 support' CONFIG_WD80x3
   tristate 'SMC Ultra support' CONFIG_ULTRA
+  tristate 'SMC 9194 support' CONFIG_SMC9194
 fi
 bool 'AMD LANCE and PCnet (AT1500 and NE2100) support' CONFIG_LANCE
 bool '3COM cards' CONFIG_NET_VENDOR_3COM
diff -u --recursive --new-file v1.3.71/linux/drivers/net/Makefile linux/drivers/net/Makefile
--- v1.3.71/linux/drivers/net/Makefile	Wed Feb 28 11:50:05 1996
+++ linux/drivers/net/Makefile	Fri Mar  8 10:03:32 1996
@@ -63,6 +63,15 @@
   endif
 endif
 
+ifeq ($(CONFIG_SMC9194),y)
+L_OBJS += smc9194.o
+else
+  ifeq ($(CONFIG_SMC9194),m)
+  M_OBJS += smc9194.o
+  endif
+endif
+
+
 ifeq ($(CONFIG_WD80x3),y)
 L_OBJS += wd.o
 CONFIG_8390_BUILTIN = y
diff -u --recursive --new-file v1.3.71/linux/drivers/net/README.ewrk3 linux/drivers/net/README.ewrk3
--- v1.3.71/linux/drivers/net/README.ewrk3	Wed Nov  2 11:38:34 1994
+++ linux/drivers/net/README.ewrk3	Wed Mar  6 15:04:20 1996
@@ -4,9 +4,10 @@
 'NICSETUP.EXE' tools provided on  the DOS drivers  disk (type 'make' in that
 subdirectory to make the tools).
 
-The supported cards  are  DE203, DE204 and  DE205.  All other cards are  NOT
-supported - refer  to the depca files for   running the LANCE based  network
-cards from Digital.
+The supported  cards are DE203,  DE204 and DE205.  All   other cards are NOT
+supported - refer to 'depca.c' for running the LANCE based network cards and
+'de4x5.c'  for the  DIGITAL   Semiconductor PCI  chip  based  adapters  from
+Digital.
 
 The ability to load  this driver as a  loadable module has been included and
 used extensively  during the driver  development (to save those  long reboot
@@ -15,13 +16,14 @@
     0) have a copy of the loadable modules code installed on your system.
     1) copy ewrk3.c from the  /linux/drivers/net directory to your favourite
     temporary directory.
-    2) edit the  source code near  line 1750 to reflect  the I/O address and
+    2) edit the  source code near  line 1898 to reflect  the I/O address and
     IRQ you're using.
     3) compile  ewrk3.c, but include -DMODULE in  the command line to ensure
     that the correct bits are compiled (see end of source code).
     4) if you are wanting to add a new  card, goto 5. Otherwise, recompile a
     kernel with the ewrk3 configuration turned off and reboot.
     5) insmod ewrk3.o
+          [Alan Cox: Changed this so you can insmod ewrk3.o irq=x io=y]
     6) run the net startup bits for your new eth?? interface manually 
     (usually /etc/rc.inet[12] at boot time). 
     7) enjoy!
diff -u --recursive --new-file v1.3.71/linux/drivers/net/README.smc9 linux/drivers/net/README.smc9
--- v1.3.71/linux/drivers/net/README.smc9	Thu Jan  1 02:00:00 1970
+++ linux/drivers/net/README.smc9	Fri Mar  8 10:03:32 1996
@@ -0,0 +1,42 @@
+
+SMC 9xxxx Driver 
+Revision 0.12 
+3/5/96
+Copyright 1996  Erik Stahlman 
+Released under terms of the GNU public license. 
+
+This file contains the instructions and caveats for my SMC9xxx driver.  You
+should not be using the driver without reading this file.  
+
+Things to note about installation:
+
+  1. The driver should work on all kernels from 1.2.13 until 1.3.71.
+	(A kernel patch is supplied for 1.3.71 )
+
+  2. If you include this into the kernel, you might need to change some
+	options, such as for forcing IRQ.   
+
+ 
+  3.  To compile as a module, run 'make' .   
+	Make will give you the appropriate options for various kernel support.
+ 
+  4.  Loading the driver as a module :
+
+	use:   insmod smc9194.o 
+	optional parameters:
+		io=xxxx    : your base address
+		irq=xx	   : your irq 
+		ifport=x   :	0 for whatever is default
+				1 for twisted pair
+				2 for AUI  ( or BNC on some cards )
+
+How to obtain the latest version? 
+	
+FTP:  
+	ftp://fenris.campus.vt.edu/smc9/smc9-12.tar.gz
+	ftp://sfbox.vt.edu/filebox/F/fenris/smc9/smc9-12.tar.gz 
+   
+
+Contacting me:
+    erik@mail.vt.edu
+ 
diff -u --recursive --new-file v1.3.71/linux/drivers/net/Space.c linux/drivers/net/Space.c
--- v1.3.71/linux/drivers/net/Space.c	Wed Feb 28 11:50:05 1996
+++ linux/drivers/net/Space.c	Fri Mar  8 10:03:32 1996
@@ -69,6 +69,7 @@
 extern int SK_init(struct device *);
 extern int seeq8005_probe(struct device *);
 extern int tc59x_probe(struct device *);
+extern int smc9_init( struct device * );
 
 /* Detachable devices ("pocket adaptors") */
 extern int atp_init(struct device *);
@@ -95,6 +96,9 @@
 #endif	
 #if defined(CONFIG_ULTRA)
 	&& ultra_probe(dev)
+#endif
+#if defined(CONFIG_SMC9194)
+	&& smc9_init(dev)
 #endif
 #if defined(CONFIG_WD80x3) || defined(WD80x3)
 	&& wd_probe(dev)
diff -u --recursive --new-file v1.3.71/linux/drivers/net/de4x5.c linux/drivers/net/de4x5.c
--- v1.3.71/linux/drivers/net/de4x5.c	Sat Mar  2 10:43:31 1996
+++ linux/drivers/net/de4x5.c	Wed Mar  6 14:53:46 1996
@@ -8,9 +8,10 @@
     This driver is written for the Digital Equipment Corporation series
     of EtherWORKS ethernet cards:
 
-	DE425 TP/COAX EISA
+        DE425 TP/COAX EISA
 	DE434 TP PCI
 	DE435 TP/COAX/AUI PCI
+	DE450 TP/COAX/AUI PCI
 	DE500 10/100 PCI Fasternet
 
     The driver has been tested on a relatively busy network using the DE425,
@@ -28,7 +29,7 @@
     measurement. Their error is +/-20k on a quiet (private) network and also
     depend on what load the CPU has.
 
-    The author may    be  reached as davies@wanton.lkg.dec.com  or   Digital
+    The author may    be reached  at  davies@wanton.lkg.dec.com  or  Digital
     Equipment Corporation, 550 King Street, Littleton MA 01460.
 
     =========================================================================
@@ -41,51 +42,53 @@
     multiple depca, EtherWORKS 3 cards and de4x5 cards and  have not found a
     problem yet (provided you have at least depca.c v0.38) ...
 
-    PCI support  has been added  to allow the  driver to work with the DE434
-    and  DE435 cards. The I/O  accesses  are a  bit of a   kludge due to the
-    differences  in the  EISA and PCI    CSR address offsets  from the  base
+    PCI support has been added  to allow the driver  to work with the DE434,
+    DE435, DE450 and DE500 cards. The I/O accesses are a bit of a kludge due
+    to the differences in the EISA and PCI CSR address offsets from the base
     address.
 
-    The ability to load  this driver as a loadable  module has been included
-    and  used extensively during the  driver development (to save those long
-    reboot sequences).  Loadable module support under  PCI has been achieved
-    by letting any I/O address less than 0x1000 be assigned as:
-
-                       0xghh
-
-    where g is the bus number (usually 0 until the BIOS's get fixed)
-         hh is the device number (max is 32 per bus).
+    The ability to load  this driver as a  loadable module has been included
+    and used  extensively during the  driver development (to save those long
+    reboot sequences).  Loadable module support  under PCI and EISA has been
+    achieved by letting the driver autoprobe as if it were compiled into the
+    kernel, except that there is no autoprobing of the IRQ lines. This is of
+    no great  consequence except do make sure  you're not sharing interrupts
+    with  anything that cannot  accomodate  interrupt sharing!  The existing
+    register_netdevice() code will only allow one device to be registered at
+    a time. 
+
+    ************************************************************************
+    For now, please only use the 'io=??' assignment (see  2. below, ?? != 0)
+    when loading a module.
+    ************************************************************************
 
     Essentially, the I/O address and IRQ information  are ignored and filled
     in later by  the PCI BIOS   during the PCI  probe.  Note  that the board
     should be in the system at boot time so that its I/O address and IRQ are
-    allocated by the PCI BIOS automatically. The special case of device 0 on
-    bus 0  is  not allowed  as  the probe  will think   you're autoprobing a
-    module.
+    allocated by the PCI BIOS automatically. 
 
     To utilise this ability, you have to do 8 things:
 
     0) have a copy of the loadable modules code installed on your system.
     1) copy de4x5.c from the  /linux/drivers/net directory to your favourite
     temporary directory.
-    2) edit the  source code near  line 2762 to reflect  the I/O address and
-    IRQ you're using, or assign these when loading by:
+    2) edit the source code near line 3779 to reflect the I/O address you're
+    using (only  if you want to manually  load the module),  or assign these
+    when loading by:
 
-                   insmod de4x5.o irq=x io=y
+                   insmod de4x5.o io=0xghh         where g = bus number
+		                                        hh = device number   
 
     3) compile  de4x5.c, but include -DMODULE in  the command line to ensure
     that the correct bits are compiled (see end of source code).
     4) if you are wanting to add a new  card, goto 5. Otherwise, recompile a
     kernel with the de4x5 configuration turned off and reboot.
-    5) insmod de4x5.o
-    6) run the net startup bits for your new eth?? interface manually 
+    5) insmod de4x5.o [io=0xghh]
+    6) run the net startup bits for your new eth?? interface(s) manually 
     (usually /etc/rc.inet[12] at boot time). 
     7) enjoy!
 
-    Note that autoprobing is not allowed in loadable modules - the system is
-    already up and running and you're messing with interrupts.
-
-    To unload a module, turn off the associated interface 
+    To unload a module, turn off the associated interface(s) 
     'ifconfig eth?? down' then 'rmmod de4x5'.
 
     Automedia detection is included so that in  principal you can disconnect
@@ -93,13 +96,30 @@
     pause whilst the   driver figures out   where its media went).  My tests
     using ping showed that it appears to work....
 
-    A compile time  switch to allow  Znyx  recognition has been  added. This
-    "feature" is in no way supported nor tested  in this driver and the user
-    may use it at his/her sole discretion.  I have had 2 conflicting reports
-    that  my driver  will or   won't  work with   Znyx. Try Donald  Becker's
-    'tulip.c' if this driver doesn't work for  you. I will not be supporting
-    Znyx cards since I have no information on them  and can't test them in a
-    system.
+    By  default,  the driver will  now   autodetect any  DECchip based card.
+    Should you have a need to restrict the driver to DIGITAL only cards, you
+    can compile with a  DEC_ONLY define, or if  loading as a module, use the
+    'dec_only=1'  parameter. However, this  "feature" is in no way supported
+    nor  tested in this  driver  and the user  may use  it  at his/her  sole
+    discretion.   I have had  2 conflicting reports that  my  driver will or
+    won't work  with Znyx.   Try  Donald Becker's 'tulip.c' if   this driver
+    doesn't work for you. I will not be supporting Znyx  and SMC cards since
+    I have  no information on  them and  can't test  them in  a system (this
+    applies most particularly to the DC21140 based cards).
+
+    I've changed the timing routines to  use the kernel timer and scheduling
+    functions  so that the  hangs  and other assorted  problems that occured
+    while autosensing the  media  should be gone.  A  bonus  for the DC21040
+    auto  media sense algorithm is  that it can now  use one that is more in
+    line with the  rest (the DC21040  chip doesn't  have a hardware  timer).
+    The downside is the 1 'jiffies' (10ms) resolution.
+
+    IEEE 802.3u MII interface code has  been added in anticipation that some
+    products may use it in the future.
+
+    The SMC9332 card  has a non-compliant SROM  which needs fixing -  I have
+    patched this  driver to detect it  because the SROM format used complies
+    to a previous DEC-STD format.
 
     TO DO:
     ------
@@ -126,22 +146,41 @@
 			  Change media autodetection to allow manual setting.
 			  Completed DE500 (DC21140) support.
       0.241   18-Apr-95   Interim release without DE500 Autosense Algorithm.
-      0.242   10-May-95   Minor changes
-      0.30    12-Jun-95   Timer fix for DC21140
+      0.242   10-May-95   Minor changes.
+      0.30    12-Jun-95   Timer fix for DC21140.
                           Portability changes.
 			  Add ALPHA changes from <jestabro@ant.tay1.dec.com>.
 			  Add DE500 semi automatic autosense.
 			  Add Link Fail interrupt TP failure detection.
 			  Add timer based link change detection.
 			  Plugged a memory leak in de4x5_queue_pkt().
-      0.31    13-Jun-95   Fixed PCI stuff for 1.3.1
-      0.32    26-Jun-95   Added verify_area() calls in de4x5_ioctl() from
-                          suggestion by <heiko@colossus.escape.de>
+      0.31    13-Jun-95   Fixed PCI stuff for 1.3.1.
+      0.32    26-Jun-95   Added verify_area() calls in de4x5_ioctl() from a
+                          suggestion by <heiko@colossus.escape.de>.
+      0.33     8-Aug-95   Add shared interrupt support (not released yet).
+      0.331   21-Aug-95   Fix de4x5_open() with fast CPUs.
+                          Fix de4x5_interrupt().
+                          Fix dc21140_autoconf() mess.
+			  No shared interrupt support.
+      0.332   11-Sep-95   Added MII management interface routines.
+      0.40     5-Mar-96   Fix setup frame timeout <maartenb@hpkuipc.cern.ch>.
+                          Add kernel timer code (h/w is too flaky).
+			  Add MII based PHY autosense.
+			  Add new multicasting code.
+			  Add new autosense algorithms for media/mode 
+			  selection using kernel scheduling/timing.
+			  Re-formatted.
+			  Made changes suggested by <jeff@router.patch.net>:
+			    Change driver to detect all DECchip based cards
+			    with DEC_ONLY restriction a special case.
+			    Changed driver to autprobe as a module. No irq
+			    checking is done now - assume BIOS is good!
+			  Added SMC9332 detection <manabe@Roy.dsl.tutics.ac.jp>
 
     =========================================================================
 */
 
-static const char *version = "de4x5.c:v0.32 6/26/95 davies@wanton.lkg.dec.com\n";
+static const char *version = "de4x5.c:v0.40 96/3/5 davies@wanton.lkg.dec.com\n";
 
 #include <linux/module.h>
 
@@ -171,6 +210,57 @@
 
 #include "de4x5.h"
 
+#define c_char const char
+
+/*
+** MII Information
+*/
+struct phy_table {
+    int reset;              /* Hard reset required? */
+    int id;                 /* IEEE OUI */
+    int ta;                 /* One cycle TA time - 802.3u is confusing here */
+    struct {                /* Non autonegotiation (parallel) speed det. */
+	int reg;
+	int mask;
+	int value;
+    } spd;
+};
+
+struct mii_phy {
+    int reset;              /* Hard reset required? */
+    int id;                 /* IEEE OUI */
+    int ta;                 /* One cycle TA time */
+    struct {                /* Non autonegotiation (parallel) speed det. */
+	int reg;
+	int mask;
+	int value;
+    } spd;
+    int addr;               /* MII address for the PHY */
+};
+
+#define DE4X5_MAX_PHY 8     /* Allow upto 8 attached PHY devices per board */
+
+/*
+** Define the know universe of PHY devices that can be
+** recognised by this driver
+*/
+static struct phy_table phy_info[] = {
+    {0, NATIONAL_TX, 1, {0x19, 0x40, 0x00}},   /* National TX */
+    {1, BROADCOM_T4, 1, {0x10, 0x02, 0x02}},   /* Broadcom T4 */
+    {0, SEEQ_T4    , 1, {0x12, 0x10, 0x10}},   /* SEEQ T4 */
+    {0, CYPRESS_T4 , 1, {0x05, 0x20, 0x20}}    /* Cypress T4 */
+};
+
+/*
+** Define special SROM detection cases
+*/
+static c_char enet_det[][ETH_ALEN] = {
+    {0x00, 0x00, 0x0c, 0x00, 0x00, 0x00}
+};
+
+#define SMC 1
+
+
 #ifdef DE4X5_DEBUG
 static int de4x5_debug = DE4X5_DEBUG;
 #else
@@ -182,6 +272,7 @@
 #else
 static int de4x5_autosense = AUTO;  /* Do auto media/mode sensing */
 #endif
+#define DE4X5_AUTOSENSE_MS 250      /* msec autosense tick (DE500) */
 
 #ifdef DE4X5_FULL_DUPLEX            /* Should be done on a per adapter basis */
 static s32 de4x5_full_duplex = 1;
@@ -209,27 +300,27 @@
 #define QUEUE_PKT_TIMEOUT (3*HZ)        /* 3 second timeout */
 
 
-#define CRC_POLYNOMIAL_BE 0x04c11db7UL   /* Ethernet CRC, big endian */
-#define CRC_POLYNOMIAL_LE 0xedb88320UL   /* Ethernet CRC, little endian */
+#define CRC_POLYNOMIAL_BE 0x04c11db7UL  /* Ethernet CRC, big endian */
+#define CRC_POLYNOMIAL_LE 0xedb88320UL  /* Ethernet CRC, little endian */
 
 /*
 ** EISA bus defines
 */
-#define DE4X5_EISA_IO_PORTS   0x0c00     /* I/O port base address, slot 0 */
-#define DE4X5_EISA_TOTAL_SIZE 0xfff      /* I/O address extent */
+#define DE4X5_EISA_IO_PORTS   0x0c00    /* I/O port base address, slot 0 */
+#define DE4X5_EISA_TOTAL_SIZE 0x100     /* I/O address extent */
 
 #define MAX_EISA_SLOTS 16
 #define EISA_SLOT_INC 0x1000
 
-#define DE4X5_SIGNATURE {"DE425",""}
+#define DE4X5_SIGNATURE {"DE425","DE434","DE435","DE450","DE500"}
 #define DE4X5_NAME_LENGTH 8
 
 /*
 ** PCI Bus defines
 */
-#define PCI_MAX_BUS_NUM 8
-#define DE4X5_PCI_TOTAL_SIZE 0x80        /* I/O address extent */
-#define DE4X5_CLASS_CODE     0x00020000  /* Network controller, Ethernet */
+#define PCI_MAX_BUS_NUM      8
+#define DE4X5_PCI_TOTAL_SIZE 0x80       /* I/O address extent */
+#define DE4X5_CLASS_CODE     0x00020000 /* Network controller, Ethernet */
 
 /*
 ** Memory Alignment. Each descriptor is 4 longwords long. To force a
@@ -237,23 +328,23 @@
 ** DESC_ALIGN. ALIGN aligns the start address of the private memory area
 ** and hence the RX descriptor ring's first entry. 
 */
-#define ALIGN4      ((u_long)4 - 1)    /* 1 longword align */
-#define ALIGN8      ((u_long)8 - 1)    /* 2 longword align */
-#define ALIGN16     ((u_long)16 - 1)   /* 4 longword align */
-#define ALIGN32     ((u_long)32 - 1)   /* 8 longword align */
-#define ALIGN64     ((u_long)64 - 1)   /* 16 longword align */
-#define ALIGN128    ((u_long)128 - 1)  /* 32 longword align */
+#define ALIGN4      ((u_long)4 - 1)     /* 1 longword align */
+#define ALIGN8      ((u_long)8 - 1)     /* 2 longword align */
+#define ALIGN16     ((u_long)16 - 1)    /* 4 longword align */
+#define ALIGN32     ((u_long)32 - 1)    /* 8 longword align */
+#define ALIGN64     ((u_long)64 - 1)    /* 16 longword align */
+#define ALIGN128    ((u_long)128 - 1)   /* 32 longword align */
 
-#define ALIGN         ALIGN32          /* Keep the DC21040 happy... */
+#define ALIGN         ALIGN32           /* Keep the DC21040 happy... */
 #define CACHE_ALIGN   CAL_16LONG
-#define DESC_SKIP_LEN DSL_0            /* Must agree with DESC_ALIGN */
-/*#define DESC_ALIGN    u32 dummy[4]; / * Must agree with DESC_SKIP_LEN */
+#define DESC_SKIP_LEN DSL_0             /* Must agree with DESC_ALIGN */
+/*#define DESC_ALIGN    u32 dummy[4];  / * Must agree with DESC_SKIP_LEN */
 #define DESC_ALIGN
 
-#ifndef IS_NOT_DEC                     /* See README.de4x5 for using this */
-static int is_not_dec = 0;
+#ifndef DEC_ONLY                        /* See README.de4x5 for using this */
+static int dec_only = 0;
 #else
-static int is_not_dec = 1;
+static int dec_only = 1;
 #endif
 
 /*
@@ -261,24 +352,24 @@
 */
 #define ENABLE_IRQs { \
     imr |= lp->irq_en;\
-    outl(imr, DE4X5_IMR);                   /* Enable the IRQs */\
+    outl(imr, DE4X5_IMR);               /* Enable the IRQs */\
 }
 
 #define DISABLE_IRQs {\
     imr = inl(DE4X5_IMR);\
     imr &= ~lp->irq_en;\
-    outl(imr, DE4X5_IMR);                   /* Disable the IRQs */\
+    outl(imr, DE4X5_IMR);               /* Disable the IRQs */\
 }
 
 #define UNMASK_IRQs {\
     imr |= lp->irq_mask;\
-    outl(imr, DE4X5_IMR);                   /* Unmask the IRQs */\
+    outl(imr, DE4X5_IMR);               /* Unmask the IRQs */\
 }
 
 #define MASK_IRQs {\
     imr = inl(DE4X5_IMR);\
     imr &= ~lp->irq_mask;\
-    outl(imr, DE4X5_IMR);                   /* Mask the IRQs */\
+    outl(imr, DE4X5_IMR);               /* Mask the IRQs */\
 }
 
 /*
@@ -287,19 +378,19 @@
 #define START_DE4X5 {\
     omr = inl(DE4X5_OMR);\
     omr |= OMR_ST | OMR_SR;\
-    outl(omr, DE4X5_OMR);                   /* Enable the TX and/or RX */\
+    outl(omr, DE4X5_OMR);               /* Enable the TX and/or RX */\
 }
 
 #define STOP_DE4X5 {\
     omr = inl(DE4X5_OMR);\
     omr &= ~(OMR_ST|OMR_SR);\
-    outl(omr, DE4X5_OMR);                   /* Disable the TX and/or RX */ \
+    outl(omr, DE4X5_OMR);               /* Disable the TX and/or RX */ \
 }
 
 /*
 ** DE4X5 SIA RESET
 */
-#define RESET_SIA outl(0, DE4X5_SICR);      /* Reset SIA connectivity regs */
+#define RESET_SIA outl(0, DE4X5_SICR);  /* Reset SIA connectivity regs */
 
 /*
 ** DE500 AUTOSENSE TIMER INTERVAL (MILLISECS)
@@ -310,12 +401,16 @@
 ** SROM Structure
 */
 struct de4x5_srom {
-  char reserved[18];
-  char version;
-  char num_adapters;
-  char ieee_addr[6];
-  char info[100];
-  short chksum;
+    char sub_vendor_id[2];
+    char sub_system_id[2];
+    char reserved[12];
+    char id_block_crc;
+    char reserved2;
+    char version;
+    char num_adapters;
+    char ieee_addr[6];
+    char info[100];
+    short chksum;
 };
 
 /*
@@ -326,11 +421,11 @@
 ** is possible. 1536 showed better 'ttcp' performance. Take your pick. 32 TX
 ** descriptors are needed for machines with an ALPHA CPU.
 */
-#define NUM_RX_DESC 8                        /* Number of RX descriptors */
-#define NUM_TX_DESC 32                       /* Number of TX descriptors */
-#define BUFF_ALLOC_RETRIES 10                /* In case of memory shortage */
-#define RX_BUFF_SZ 1536                      /* Power of 2 for kmalloc and */
-                                             /* Multiple of 4 for DC21040 */
+#define NUM_RX_DESC 8                   /* Number of RX descriptors   */
+#define NUM_TX_DESC 32                  /* Number of TX descriptors   */
+#define RX_BUFF_SZ  1536                /* Power of 2 for kmalloc and */
+                                        /* Multiple of 4 for DC21040  */
+                                        /* Allows 512 byte alignment  */
 struct de4x5_desc {
     volatile s32 status;
     u32 des1;
@@ -343,42 +438,81 @@
 ** The DE4X5 private structure
 */
 #define DE4X5_PKT_STAT_SZ 16
-#define DE4X5_PKT_BIN_SZ  128                /* Should be >=100 unless you
-                                                increase DE4X5_PKT_STAT_SZ */
+#define DE4X5_PKT_BIN_SZ  128            /* Should be >=100 unless you
+                                            increase DE4X5_PKT_STAT_SZ */
 
 struct de4x5_private {
-    char adapter_name[80];                   /* Adapter name */
-    struct de4x5_desc rx_ring[NUM_RX_DESC];  /* RX descriptor ring */
-    struct de4x5_desc tx_ring[NUM_TX_DESC];  /* TX descriptor ring */
-    struct sk_buff *skb[NUM_TX_DESC];        /* TX skb for freeing when sent */
-    int rx_new, rx_old;                      /* RX descriptor ring pointers */
-    int tx_new, tx_old;                      /* TX descriptor ring pointers */
-    char setup_frame[SETUP_FRAME_LEN];       /* Holds MCA and PA info. */
-    struct enet_statistics stats;            /* Public stats */
+    char adapter_name[80];                  /* Adapter name                 */
+    struct de4x5_desc rx_ring[NUM_RX_DESC]; /* RX descriptor ring           */
+    struct de4x5_desc tx_ring[NUM_TX_DESC]; /* TX descriptor ring           */
+    struct sk_buff *skb[NUM_TX_DESC];       /* TX skb for freeing when sent */
+    int rx_new, rx_old;                     /* RX descriptor ring pointers  */
+    int tx_new, tx_old;                     /* TX descriptor ring pointers  */
+    char setup_frame[SETUP_FRAME_LEN];      /* Holds MCA and PA info.       */
+    char frame[64];                         /* Min sized packet for loopback*/
+    struct enet_statistics stats;           /* Public stats                 */
     struct {
-	u_int bins[DE4X5_PKT_STAT_SZ]; /* Private stats counters */
+	u_int bins[DE4X5_PKT_STAT_SZ];      /* Private stats counters       */
 	u_int unicast;
 	u_int multicast;
 	u_int broadcast;
 	u_int excessive_collisions;
 	u_int tx_underruns;
 	u_int excessive_underruns;
+	u_int rx_runt_frames;
+	u_int rx_collision;
+	u_int rx_dribble;
+	u_int rx_overflow;
     } pktStats;
     char rxRingSize;
     char txRingSize;
-    int  bus;                                /* EISA or PCI */
-    int  bus_num;                            /* PCI Bus number */
-    int  chipset;                            /* DC21040, DC21041 or DC21140 */
-    s32  irq_mask;                           /* Interrupt Mask (Enable) bits */
-    s32  irq_en;                             /* Summary interrupt bits */
-    int  media;                              /* Media (eg TP), mode (eg 100B)*/
-    int  linkProb;                           /* Possible Link Problem */
-    int  autosense;                          /* Allow/disallow autosensing */
-    int  tx_enable;                          /* Enable descriptor polling */
-    int  lostMedia;                          /* Possibly lost media */
-    int  setup_f;                            /* Setup frame filtering type */
+    int  bus;                               /* EISA or PCI                  */
+    int  bus_num;                           /* PCI Bus number               */
+    int  state;                             /* Adapter OPENED or CLOSED     */
+    int  chipset;                           /* DC21040, DC21041 or DC21140  */
+    s32  irq_mask;                          /* Interrupt Mask (Enable) bits */
+    s32  irq_en;                            /* Summary interrupt bits       */
+    int  media;                             /* Media (eg TP), mode (eg 100B)*/
+    int  c_media;                           /* Remember the last media conn */
+    int  linkOK;                            /* Link is OK                   */
+    int  autosense;                         /* Allow/disallow autosensing   */
+    int  tx_enable;                         /* Enable descriptor polling    */
+    int  lostMedia;                         /* Possibly lost media          */
+    int  setup_f;                           /* Setup frame filtering type   */
+    int  local_state;                       /* State within a 'media' state */
+    struct mii_phy phy[DE4X5_MAX_PHY];      /* List of attached PHY devices */
+    int  active;                            /* Index to active PHY device   */
+    int  mii_cnt;                           /* Number of attached PHY's     */
+    int  timeout;                           /* Scheduling counter           */
+    struct timer_list timer;                /* Timer info for kernel        */
+    int tmp;                                /* Temporary global per card    */
+    struct {
+	void *priv;                         /* Original kmalloc'd mem addr  */
+	void *buf;                          /* Original kmalloc'd mem addr  */
+	s32 csr0;                           /* Saved Bus Mode Register      */
+	s32 csr6;                           /* Saved Operating Mode Reg.    */
+	s32 csr7;                           /* Saved IRQ Mask Register      */
+	s32 csr13;                          /* Saved SIA Connectivity Reg.  */
+	s32 csr14;                          /* Saved SIA TX/RX Register     */
+	s32 csr15;                          /* Saved SIA General Register   */
+	int save_cnt;                       /* Flag if state already saved  */
+	struct sk_buff *skb;                /* Save the (re-ordered) skb's  */
+    } cache;
 };
 
+/*
+** Kludge to get around the fact that the CSR addresses have different
+** offsets in the PCI and EISA boards. Also note that the ethernet address
+** PROM is accessed differently.
+*/
+static struct bus_type {
+    int bus;
+    int bus_num;
+    int device;
+    int chipset;
+    struct de4x5_srom srom;
+    int autosense;
+} bus;
 
 /*
 ** The transmit ring full condition is described by the tx_old and tx_new
@@ -388,8 +522,8 @@
 **    tx_old+txRingSize = tx_new+1  Full ring  (wrapped condition)
 */
 #define TX_BUFFS_AVAIL ((lp->tx_old<=lp->tx_new)?\
-			 lp->tx_old+lp->txRingSize-lp->tx_new-1:\
-                         lp->tx_old               -lp->tx_new-1)
+			lp->tx_old+lp->txRingSize-lp->tx_new-1:\
+			lp->tx_old               -lp->tx_new-1)
 
 /*
 ** Public Functions
@@ -407,26 +541,45 @@
 */
 static int     de4x5_hw_init(struct device *dev, u_long iobase);
 static int     de4x5_init(struct device *dev);
+static int     de4x5_sw_reset(struct device *dev);
 static int     de4x5_rx(struct device *dev);
 static int     de4x5_tx(struct device *dev);
 static int     de4x5_ast(struct device *dev);
 
 static int     autoconf_media(struct device *dev);
 static void    create_packet(struct device *dev, char *frame, int len);
-static void    dce_us_delay(u32 usec);
-static void    dce_ms_delay(u32 msec);
+static void    de4x5_us_delay(u32 usec);
+static void    de4x5_ms_delay(u32 msec);
 static void    load_packet(struct device *dev, char *buf, u32 flags, struct sk_buff *skb);
-static void    dc21040_autoconf(struct device *dev);
-static void    dc21041_autoconf(struct device *dev);
-static void    dc21140_autoconf(struct device *dev);
+static int     dc21040_autoconf(struct device *dev);
+static int     dc21041_autoconf(struct device *dev);
+static int     dc21140m_autoconf(struct device *dev);
+static int     de4x5_suspect_state(struct device *dev, int timeout, int prev_state, int (*fn)(struct device *, int), int (*asfn)(struct device *));
+static int     dc21040_state(struct device *dev, int csr13, int csr14, int csr15, int timeout, int next_state, int suspect_state, int (*fn)(struct device *, int));
 static int     test_media(struct device *dev, s32 irqs, s32 irq_mask, s32 csr13, s32 csr14, s32 csr15, s32 msec);
 /*static int     test_sym_link(struct device *dev, u32 msec);*/
-static int     ping_media(struct device *dev);
+static int     test_mii_reg(struct device *dev, int reg, int mask, int pol, long msec);
+static int     is_spd_100(struct device *dev);
+static int     is_100_up(struct device *dev);
+static int     is_10_up(struct device *dev);
+static int     is_anc_capable(struct device *dev);
+static int     ping_media(struct device *dev, int msec);
+static void    de4x5_save_skbs(struct device *dev);
+static void    de4x5_restore_skbs(struct device *dev);
+static void    de4x5_cache_state(struct device *dev, int flag);
+static void    de4x5_put_cache(struct device *dev, struct sk_buff *skb);
+static void    de4x5_putb_cache(struct device *dev, struct sk_buff *skb);
+static struct  sk_buff *de4x5_get_cache(struct device *dev);
+static void    de4x5_setup_intr(struct device *dev);
+static void    de4x5_init_connection(struct device *dev);
+static int     de4x5_reset_phy(struct device *dev);
 static void    reset_init_sia(struct device *dev, s32 sicr, s32 strr, s32 sigr);
 static int     test_ans(struct device *dev, s32 irqs, s32 irq_mask, s32 msec);
-static void    load_ms_timer(struct device *dev, u32 msec);
+static int     test_tp(struct device *dev, s32 msec);
 static int     EISA_signature(char *name, s32 eisa_id);
-static int     DevicePresent(u_long iobase);
+static int     PCI_signature(char *name, struct bus_type *lp);
+static void    DevicePresent(u_long iobase);
+static int     de4x5_bad_srom(struct bus_type *lp);
 static short   srom_rd(u_long address, u_char offset);
 static void    srom_latch(u_int command, u_long address);
 static void    srom_command(u_int command, u_long address);
@@ -435,6 +588,17 @@
 /*static void    srom_busy(u_int command, u_long address);*/
 static void    sendto_srom(u_int command, u_long addr);
 static int     getfrom_srom(u_long addr);
+static int     mii_rd(u_char phyreg, u_char phyaddr, u_long ioaddr);
+static void    mii_wr(int data, u_char phyreg, u_char phyaddr, u_long ioaddr);
+static int     mii_rdata(u_long ioaddr);
+static void    mii_wdata(int data, int len, u_long ioaddr);
+static void    mii_ta(u_long rw, u_long ioaddr);
+static int     mii_swap(int data, int len);
+static void    mii_address(u_char addr, u_long ioaddr);
+static void    sendto_mii(u32 command, int data, u_long ioaddr);
+static int     getfrom_mii(u32 command, u_long ioaddr);
+static int     mii_get_oui(u_char phyaddr, u_long ioaddr);
+static int     mii_get_phy(struct device *dev);
 static void    SetMulticastFilter(struct device *dev);
 static int     get_hw_addr(struct device *dev);
 
@@ -444,14 +608,20 @@
 static char    *build_setup_frame(struct device *dev, int mode);
 static void    disable_ast(struct device *dev);
 static void    enable_ast(struct device *dev, u32 time_out);
-static void    kick_tx(struct device *dev);
+static long    de4x5_switch_to_srl(struct device *dev);
+static long    de4x5_switch_to_mii(struct device *dev);
+static void    timeout(struct device *dev, void (*fn)(u_long data), u_long data, u_long msec);
+static void    de4x5_dbg_open(struct device *dev);
+static void    de4x5_dbg_mii(struct device *dev, int k);
+static void    de4x5_dbg_media(struct device *dev);
+static void    de4x5_dbg_srom(struct de4x5_srom *p);
+static int     de4x5_strncmp(char *a, char *b, int n);
 
 #ifdef MODULE
 int  init_module(void);
 void cleanup_module(void);
-static int autoprobed = 1, loading_module = 1;
+static int autoprobed = 0, loading_module = 1;
 # else
-static unsigned char de4x5_irq[] = {5,9,10,11};
 static int autoprobed = 0, loading_module = 0;
 #endif /* MODULE */
 
@@ -459,256 +629,224 @@
 static int num_de4x5s = 0, num_eth = 0;
 
 /*
-** Kludge to get around the fact that the CSR addresses have different
-** offsets in the PCI and EISA boards. Also note that the ethernet address
-** PROM is accessed differently.
-*/
-static struct bus_type {
-    int bus;
-    int bus_num;
-    int device;
-    int chipset;
-    struct de4x5_srom srom;
-    int autosense;
-} bus;
-
-/*
 ** Miscellaneous defines...
 */
 #define RESET_DE4X5 {\
     int i;\
     i=inl(DE4X5_BMR);\
-    dce_ms_delay(1);\
+    de4x5_ms_delay(1);\
     outl(i | BMR_SWR, DE4X5_BMR);\
-    dce_ms_delay(1);\
+    de4x5_ms_delay(1);\
     outl(i, DE4X5_BMR);\
-    dce_ms_delay(1);\
-    for (i=0;i<5;i++) {inl(DE4X5_BMR); dce_ms_delay(1);}\
-    dce_ms_delay(1);\
+    de4x5_ms_delay(1);\
+    for (i=0;i<5;i++) {inl(DE4X5_BMR); de4x5_ms_delay(1);}\
+    de4x5_ms_delay(1);\
 }
 
 
-
+/*
+** Autoprobing in modules is allowed here. See the top of the file for
+** more info. Until I fix (un)register_netdevice() we won't be able to use it
+** though.
+*/
 int de4x5_probe(struct device *dev)
 {
-  int tmp = num_de4x5s, status = -ENODEV;
-  u_long iobase = dev->base_addr;
-
-  if ((iobase == 0) && loading_module){
-    printk("Autoprobing is not supported when loading a module based driver.\n");
-    status = -EIO;
-  } else {
+    int tmp = num_de4x5s, status = -ENODEV;
+    u_long iobase = dev->base_addr;
+    
     eisa_probe(dev, iobase);
     pci_probe(dev, iobase);
-
+    
     if ((tmp == num_de4x5s) && (iobase != 0) && loading_module) {
-      printk("%s: de4x5_probe() cannot find device at 0x%04lx.\n", dev->name, 
-	                                                               iobase);
+	printk("%s: de4x5_probe() cannot find device at 0x%04lx.\n", dev->name, 
+	       iobase);
     }
-
+    
     /*
     ** Walk the device list to check that at least one device
     ** initialised OK
     */
     for (; (dev->priv == NULL) && (dev->next != NULL); dev = dev->next);
-
+    
     if (dev->priv) status = 0;
     if (iobase == 0) autoprobed = 1;
-  }
-
-  return status;
+    
+    return status;
 }
 
 static int
 de4x5_hw_init(struct device *dev, u_long iobase)
 {
-  struct bus_type *lp = &bus;
-  int tmpbus, tmpchs, i, j, status=0;
-  char *tmp;
-
-  /* Ensure we're not sleeping */
-  if (lp->chipset == DC21041) {
-    outl(0, PCI_CFDA);
-    dce_ms_delay(10);
-  }
-
-  RESET_DE4X5;
-
-  if ((inl(DE4X5_STS) & (STS_TS | STS_RS)) == 0) {
+    struct bus_type *lp = &bus;
+    int tmpbus, tmpchs, status=0;
+    int i, media = *((char *)&(lp->srom) + *((char *)&(lp->srom) + 19) * 3);
+    char *tmp;
+    
+    /* Ensure we're not sleeping */
+    if (lp->chipset == DC21041) {
+	outl(0, PCI_CFDA);
+	de4x5_ms_delay(10);
+    }
+    
+    RESET_DE4X5;
+    
+    if ((inl(DE4X5_STS) & (STS_TS | STS_RS)) != 0) {
+	return -ENXIO;                       /* Hardware could not reset */
+    }
+    
     /* 
     ** Now find out what kind of DC21040/DC21041/DC21140 board we have.
     */
     if (lp->bus == PCI) {
-      if (!is_not_dec) {
-	if ((lp->chipset == DC21040) || (lp->chipset == DC21041)) {
-	  strcpy(name, "DE435");
-	} else if (lp->chipset == DC21140) {
-	  strcpy(name, "DE500");                /* Must read the SROM here! */
-	}
-      } else {
-	strcpy(name, "UNKNOWN");
-      }
+	PCI_signature(name, lp);
     } else {
-      EISA_signature(name, EISA_ID0);
+	EISA_signature(name, EISA_ID0);
     }
-
-    if (*name != '\0') {                         /* found a board signature */
-      dev->base_addr = iobase;
-      if (lp->bus == EISA) {
+    
+    if (*name == '\0') {                     /* Not found a board signature */
+	return -ENXIO;
+    }
+    
+    dev->base_addr = iobase;
+    if (lp->bus == EISA) {
 	printk("%s: %s at %04lx (EISA slot %ld)", 
-	                        dev->name, name, iobase, ((iobase>>12)&0x0f));
-      } else {                                   /* PCI port address */
+	       dev->name, name, iobase, ((iobase>>12)&0x0f));
+    } else {                                 /* PCI port address */
 	printk("%s: %s at %04lx (PCI bus %d, device %d)", dev->name, name,
-	                                      iobase, lp->bus_num, lp->device);
-      }
-	
-      printk(", h/w address ");
-      status = get_hw_addr(dev);
-      for (i = 0; i < ETH_ALEN - 1; i++) {       /* get the ethernet addr. */
+	       iobase, lp->bus_num, lp->device);
+    }
+    
+    printk(", h/w address ");
+    status = get_hw_addr(dev);
+    for (i = 0; i < ETH_ALEN - 1; i++) {     /* get the ethernet addr. */
 	printk("%2.2x:", dev->dev_addr[i]);
-      }
-      printk("%2.2x,\n", dev->dev_addr[i]);
-      
-      tmpbus = lp->bus;
-      tmpchs = lp->chipset;
-
-      if (status == 0) {
+    }
+    printk("%2.2x,\n", dev->dev_addr[i]);
+    
+    tmpbus = lp->bus;
+    tmpchs = lp->chipset;
+    
+    if (status != 0) {
+	printk("      which has an Ethernet PROM CRC error.\n");
+	return -ENXIO;
+    } else {
 	struct de4x5_private *lp;
-
+	
 	/* 
 	** Reserve a section of kernel memory for the adapter
 	** private area and the TX/RX descriptor rings.
 	*/
 	dev->priv = (void *) kmalloc(sizeof(struct de4x5_private) + ALIGN, 
-				                                   GFP_KERNEL);
-	if (dev->priv == NULL)
-		return -ENOMEM;
+				     GFP_KERNEL);
+	if (dev->priv == NULL) {
+	    return -ENOMEM;
+	}
+	
 	/*
 	** Align to a longword boundary
 	*/
+	tmp = dev->priv;
 	dev->priv = (void *)(((u_long)dev->priv + ALIGN) & ~ALIGN);
 	lp = (struct de4x5_private *)dev->priv;
 	memset(dev->priv, 0, sizeof(struct de4x5_private));
 	lp->bus = tmpbus;
 	lp->chipset = tmpchs;
+	lp->cache.priv = tmp;
 
 	/*
-	** Choose autosensing
+	** Check for an MII interface
+	*/
+	if (media & MEDIA_MII) {                   /* MII interface? */
+	    if (!mii_get_phy(dev)) {
+		printk("%s: MII search failed, no device found when one was expected\n", dev->name);
+		return -ENXIO;
+	    }
+	} else {
+	    mii_get_phy(dev);                      /* Search the MII anyway! */
+	}
+	
+	/*
+	** Choose correct autosensing in case someone messed up
 	*/
 	if (de4x5_autosense & AUTO) {
-	  lp->autosense = AUTO;
+	    lp->autosense = AUTO;
 	} else {
-	  if (lp->chipset != DC21140) {
-	    if ((lp->chipset == DC21040) && (de4x5_autosense & TP_NW)) {
-	      de4x5_autosense = TP;
-	    }
-	    if ((lp->chipset == DC21041) && (de4x5_autosense & BNC_AUI)) {
-	      de4x5_autosense = BNC;
-	    }
-	    lp->autosense = de4x5_autosense & 0x001f;
-	  } else {
-	    lp->autosense = de4x5_autosense & 0x00c0;
-	  }
+	    if (lp->chipset != DC21140) {
+		if ((lp->chipset == DC21040) && (de4x5_autosense & TP_NW)) {
+		    de4x5_autosense = TP;
+		}
+		if ((lp->chipset == DC21041) && (de4x5_autosense & BNC_AUI)) {
+		    de4x5_autosense = BNC;
+		}
+		lp->autosense = de4x5_autosense & 0x001f;
+	    } else {
+		lp->autosense = de4x5_autosense & 0x00c0;
+	    }
 	}
-
+	
 	sprintf(lp->adapter_name,"%s (%s)", name, dev->name);
-	request_region(iobase, (lp->bus == PCI ? DE4X5_PCI_TOTAL_SIZE :
-			                         DE4X5_EISA_TOTAL_SIZE), 
-		                                 lp->adapter_name);
-
+	
 	/*
 	** Allocate contiguous receive buffers, long word aligned. 
-	** This could be a possible memory leak if the private area
-	** is ever hosed.
 	*/
-	for (tmp=NULL, j=0; (j<BUFF_ALLOC_RETRIES) && (tmp==NULL); j++) {
-	  if ((tmp = (void *)kmalloc(RX_BUFF_SZ * NUM_RX_DESC + ALIGN, 
-	       	  		                        GFP_KERNEL)) != NULL) {
+	if ((tmp = (void *)kmalloc(RX_BUFF_SZ * NUM_RX_DESC + ALIGN, 
+				   GFP_KERNEL)) != NULL) {
+	    lp->cache.buf = tmp;
 	    tmp = (char *)(((u_long) tmp + ALIGN) & ~ALIGN);
 	    for (i=0; i<NUM_RX_DESC; i++) {
-	      lp->rx_ring[i].status = 0;
-	      lp->rx_ring[i].des1 = RX_BUFF_SZ;
-	      lp->rx_ring[i].buf = virt_to_bus(tmp + i * RX_BUFF_SZ);
-	      lp->rx_ring[i].next = (u32)NULL;
+		lp->rx_ring[i].status = 0;
+		lp->rx_ring[i].des1 = RX_BUFF_SZ;
+		lp->rx_ring[i].buf = virt_to_bus(tmp + i * RX_BUFF_SZ);
+		lp->rx_ring[i].next = (u32)NULL;
 	    }
 	    barrier();
-	  }
-	}
-
-	if (tmp != NULL) {
-	  lp->rxRingSize = NUM_RX_DESC;
-	  lp->txRingSize = NUM_TX_DESC;
-	  
-	  /* Write the end of list marker to the descriptor lists */
-	  lp->rx_ring[lp->rxRingSize - 1].des1 |= RD_RER;
-	  lp->tx_ring[lp->txRingSize - 1].des1 |= TD_TER;
-
-	  /* Tell the adapter where the TX/RX rings are located. */
-	  outl(virt_to_bus(lp->rx_ring), DE4X5_RRBA);
-	  outl(virt_to_bus(lp->tx_ring), DE4X5_TRBA);
-
-	  /* Initialise the IRQ mask and Enable/Disable */
-	  lp->irq_mask = IMR_RIM | IMR_TIM | IMR_TUM ;
-	  lp->irq_en   = IMR_NIM | IMR_AIM;
-
-	  lp->tx_enable = TRUE;
-
-	  if (dev->irq < 2) {
-#ifndef MODULE
-	    unsigned char irqnum;
-	    s32 omr;
-	    autoirq_setup(0);
 	    
-	    omr = inl(DE4X5_OMR);
-	    outl(IMR_AIM|IMR_RUM, DE4X5_IMR); /* Unmask RUM interrupt */
-	    outl(OMR_SR | omr, DE4X5_OMR);    /* Start RX w/no descriptors */
+	    request_region(iobase, (lp->bus == PCI ? DE4X5_PCI_TOTAL_SIZE :
+				    DE4X5_EISA_TOTAL_SIZE), 
+			   lp->adapter_name);
+	    
+	    lp->rxRingSize = NUM_RX_DESC;
+	    lp->txRingSize = NUM_TX_DESC;
+	    
+	    /* Write the end of list marker to the descriptor lists */
+	    lp->rx_ring[lp->rxRingSize - 1].des1 |= RD_RER;
+	    lp->tx_ring[lp->txRingSize - 1].des1 |= TD_TER;
+	    
+	    /* Tell the adapter where the TX/RX rings are located. */
+	    outl(virt_to_bus(lp->rx_ring), DE4X5_RRBA);
+	    outl(virt_to_bus(lp->tx_ring), DE4X5_TRBA);
+	    
+	    /* Initialise the IRQ mask and Enable/Disable */
+	    lp->irq_mask = IMR_RIM | IMR_TIM | IMR_TUM ;
+	    lp->irq_en   = IMR_NIM | IMR_AIM;
 
-	    irqnum = autoirq_report(1);
-	    if (!irqnum) {
-	      printk("      and failed to detect IRQ line.\n");
-	      status = -ENXIO;
-	    } else {
-	      for (dev->irq=0,i=0; (i<sizeof(de4x5_irq)) && (!dev->irq); i++) {
-		if (irqnum == de4x5_irq[i]) {
-		  dev->irq = irqnum;
-		  printk("      and uses IRQ%d.\n", dev->irq);
-		}
-	      }
-		  
-	      if (!dev->irq) {
-		printk("      but incorrect IRQ line detected.\n");
-		status = -ENXIO;
-	      }
-	    }
-		
-	    outl(0, DE4X5_IMR);               /* Re-mask RUM interrupt */
+	    /* Create a loopback packet frame for later media probing */
+	    create_packet(dev, lp->frame, sizeof(lp->frame));
 
-#endif /* MODULE */
-	  } else {
-	    printk("      and requires IRQ%d (not probed).\n", dev->irq);
-	  }
-	} else {
-	  printk("%s: Kernel could not allocate RX buffer memory.\n", 
-		                                                    dev->name);
-	  status = -ENXIO;
-	}
-	if (status) release_region(iobase, (lp->bus == PCI ? 
-					             DE4X5_PCI_TOTAL_SIZE :
-			                             DE4X5_EISA_TOTAL_SIZE));
-      } else {
-	printk("      which has an Ethernet PROM CRC error.\n");
-	status = -ENXIO;
-      }
-    } else {
-      status = -ENXIO;
+	    /* Initialise the adapter state */
+	    lp->state = CLOSED;
+
+	    printk("      and requires IRQ%d (provided by %s).\n", dev->irq,
+		   ((lp->bus == PCI) ? "PCI BIOS" : "EISA CNFG"));
+	} else {
+	    printk("%s: Kernel could not allocate RX buffer memory.\n", dev->name);
+	}
+	if (status) {
+	    release_region(iobase, (lp->bus == PCI ? 
+				    DE4X5_PCI_TOTAL_SIZE :
+				    DE4X5_EISA_TOTAL_SIZE));
+	    if (lp->rx_ring[0].buf) {
+		kfree(bus_to_virt(lp->rx_ring[0].buf));
+	    }
+	    kfree(dev->priv);
+	    dev->priv = NULL;
+	    
+	    return -ENXIO;
+	}
     }
-  } else {
-    status = -ENXIO;
-  }
-  
-  if (!status) {
+    
     if (de4x5_debug > 0) {
-      printk(version);
+	printk(version);
     }
     
     /* The DE4X5-specific entries in the device structure. */
@@ -723,131 +861,70 @@
     
     /* Fill in the generic field of the device structure. */
     ether_setup(dev);
-
+    
     /* Let the adapter sleep to save power */
     if (lp->chipset == DC21041) {
-      outl(0, DE4X5_SICR);
-      outl(CFDA_PSM, PCI_CFDA);
-    }
-  } else {                            /* Incorrectly initialised hardware */
-    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-    if (lp) {
-      kfree_s(bus_to_virt(lp->rx_ring[0].buf),
-	                                    RX_BUFF_SZ * NUM_RX_DESC + ALIGN);
-    }
-    if (dev->priv) {
-      kfree_s(dev->priv, sizeof(struct de4x5_private) + ALIGN);
-      dev->priv = NULL;
+	outl(0, DE4X5_SICR);
+	outl(CFDA_PSM, PCI_CFDA);
     }
-  }
-
-  return status;
+    
+    return status;
 }
 
 
 static int
 de4x5_open(struct device *dev)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-  int i, status = 0;
-  s32 imr, omr, sts;
-
-  /*
-  ** Wake up the adapter
-  */
-  if (lp->chipset == DC21041) {
-    outl(0, PCI_CFDA);
-    dce_ms_delay(10);
-  }
-
-  if (request_irq(dev->irq, (void *)de4x5_interrupt, SA_SHIRQ, lp->adapter_name, dev)) {
-    printk("de4x5_open(): Requested IRQ%d is busy\n",dev->irq);
-    status = -EAGAIN;
-  } else {
-
-    irq2dev_map[dev->irq] = dev;
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    int status = 0;
+    s32 omr;
+    
+    /*
+    ** Wake up the adapter
+    */
+    if (lp->chipset == DC21041) {
+	outl(0, PCI_CFDA);
+	de4x5_ms_delay(10);
+    }
+    
+    lp->state = OPEN;
+    
     /* 
     ** Re-initialize the DE4X5... 
     */
     status = de4x5_init(dev);
-
-    if (de4x5_debug > 1){
-      printk("%s: de4x5 open with irq %d\n",dev->name,dev->irq);
-      printk("\tphysical address: ");
-      for (i=0;i<6;i++){
-	printk("%2.2x:",(short)dev->dev_addr[i]);
-      }
-      printk("\n");
-      printk("Descriptor head addresses:\n");
-      printk("\t0x%8.8lx  0x%8.8lx\n",(u_long)lp->rx_ring,(u_long)lp->tx_ring);
-      printk("Descriptor addresses:\nRX: ");
-      for (i=0;i<lp->rxRingSize-1;i++){
-	if (i < 3) {
-	  printk("0x%8.8lx  ",(u_long)&lp->rx_ring[i].status);
-	}
-      }
-      printk("...0x%8.8lx\n",(u_long)&lp->rx_ring[i].status);
-      printk("TX: ");
-      for (i=0;i<lp->txRingSize-1;i++){
-	if (i < 3) {
-	  printk("0x%8.8lx  ", (u_long)&lp->tx_ring[i].status);
-	}
-      }
-      printk("...0x%8.8lx\n", (u_long)&lp->tx_ring[i].status);
-      printk("Descriptor buffers:\nRX: ");
-      for (i=0;i<lp->rxRingSize-1;i++){
-	if (i < 3) {
-	  printk("0x%8.8x  ",lp->rx_ring[i].buf);
-	}
-      }
-      printk("...0x%8.8x\n",lp->rx_ring[i].buf);
-      printk("TX: ");
-      for (i=0;i<lp->txRingSize-1;i++){
-	if (i < 3) {
-	  printk("0x%8.8x  ", lp->tx_ring[i].buf);
-	}
-      }
-      printk("...0x%8.8x\n", lp->tx_ring[i].buf);
-      printk("Ring size: \nRX: %d\nTX: %d\n", 
-	     (short)lp->rxRingSize, 
-	     (short)lp->txRingSize); 
-      printk("\tstatus:  %d\n", status);
-    }
-
-    if (!status) {
-      dev->tbusy = 0;                         
-      dev->start = 1;
-      dev->interrupt = UNMASK_INTERRUPTS;
-      dev->trans_start = jiffies;
-
-      START_DE4X5;
-
-      /* Unmask and enable DE4X5 board interrupts */
-      imr = 0;
-      UNMASK_IRQs;
-
-      /* Reset any pending (stale) interrupts */
-      sts = inl(DE4X5_STS);
-      outl(sts, DE4X5_STS);
-
-      ENABLE_IRQs;
+    
+    de4x5_dbg_open(dev);
+    
+    if (request_irq(dev->irq, (void *)de4x5_interrupt, 0, lp->adapter_name, dev)) {
+	printk("de4x5_open(): Requested IRQ%d is busy\n",dev->irq);
+	status = -EAGAIN;
+    } else {
+	dev->tbusy = 0;                         
+	dev->start = 1;
+	dev->interrupt = UNMASK_INTERRUPTS;
+	dev->trans_start = jiffies;
+	
+	START_DE4X5;
+	
+	de4x5_setup_intr(dev);
     }
+    
     if (de4x5_debug > 1) {
-      printk("\tsts:  0x%08x\n", inl(DE4X5_STS));
-      printk("\tbmr:  0x%08x\n", inl(DE4X5_BMR));
-      printk("\timr:  0x%08x\n", inl(DE4X5_IMR));
-      printk("\tomr:  0x%08x\n", inl(DE4X5_OMR));
-      printk("\tsisr: 0x%08x\n", inl(DE4X5_SISR));
-      printk("\tsicr: 0x%08x\n", inl(DE4X5_SICR));
-      printk("\tstrr: 0x%08x\n", inl(DE4X5_STRR));
-      printk("\tsigr: 0x%08x\n", inl(DE4X5_SIGR));
+	printk("\tsts:  0x%08x\n", inl(DE4X5_STS));
+	printk("\tbmr:  0x%08x\n", inl(DE4X5_BMR));
+	printk("\timr:  0x%08x\n", inl(DE4X5_IMR));
+	printk("\tomr:  0x%08x\n", inl(DE4X5_OMR));
+	printk("\tsisr: 0x%08x\n", inl(DE4X5_SISR));
+	printk("\tsicr: 0x%08x\n", inl(DE4X5_SICR));
+	printk("\tstrr: 0x%08x\n", inl(DE4X5_STRR));
+	printk("\tsigr: 0x%08x\n", inl(DE4X5_SIGR));
     }
-  }
-
-  MOD_INC_USE_COUNT;
-
-  return status;
+    
+    MOD_INC_USE_COUNT;
+    
+    return status;
 }
 
 /*
@@ -861,74 +938,91 @@
 static int
 de4x5_init(struct device *dev)
 {  
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-  int i, j, status = 0;
-  s32 bmr, omr;
-
-  /* Lock out other processes whilst setting up the hardware */
-  set_bit(0, (void *)&dev->tbusy);
-
-  RESET_DE4X5;
-
-  bmr = inl(DE4X5_BMR);
-  bmr |= PBL_8 | DESC_SKIP_LEN | CACHE_ALIGN;
-  outl(bmr, DE4X5_BMR);
-
-  if (lp->chipset != DC21140) {
-    omr = TR_96;
-    lp->setup_f = HASH_PERF;
-  } else {
-    omr = OMR_SDP | OMR_SF;
-    lp->setup_f = PERFECT;
-  }
-  outl(virt_to_bus(lp->rx_ring), DE4X5_RRBA);
-  outl(virt_to_bus(lp->tx_ring), DE4X5_TRBA);
-
-  lp->rx_new = lp->rx_old = 0;
-  lp->tx_new = lp->tx_old = 0;
-
-  for (i = 0; i < lp->rxRingSize; i++) {
-    lp->rx_ring[i].status = R_OWN;
-  }
-
-  for (i = 0; i < lp->txRingSize; i++) {
-    lp->tx_ring[i].status = 0;
-  }
-
-  barrier();
-
-  /* Build the setup frame depending on filtering mode */
-  SetMulticastFilter(dev);
-
-  if (lp->chipset != DC21140) {
-    load_packet(dev, lp->setup_frame, HASH_F|TD_SET|SETUP_FRAME_LEN, NULL);
-  } else {
-    load_packet(dev, lp->setup_frame, PERFECT_F|TD_SET|SETUP_FRAME_LEN, NULL);
-  }
-  outl(omr|OMR_ST, DE4X5_OMR);
-
-  /* Poll for completion of setup frame (interrupts are disabled for now) */
-  for (j=0, i=jiffies;(i<=jiffies+HZ/100) && (j==0);) {
-    if (lp->tx_ring[lp->tx_new].status >= 0) j=1;
-  }
-  outl(omr, DE4X5_OMR);                        /* Stop everything! */
-
-  if (j == 0) {
-    printk("%s: Setup frame timed out, status %08x\n", dev->name, 
-	                                                       inl(DE4X5_STS));
-    status = -EIO;
-  }
-
-  lp->tx_new = (++lp->tx_new) % lp->txRingSize;
-  lp->tx_old = lp->tx_new;
-
-  /* Autoconfigure the connected port */
-  if (autoconf_media(dev) == 0) {
-    status = -EIO;
-  }
+    /* Lock out other processes whilst setting up the hardware */
+    set_bit(0, (void *)&dev->tbusy);
+    
+    de4x5_sw_reset(dev);
+    
+    /* Autoconfigure the connected port */
+    autoconf_media(dev);
+    
+    return 0;
+}
 
-  return 0;
+static int de4x5_sw_reset(struct device *dev)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    int i, j, status = 0;
+    s32 bmr, omr;
+    
+    /* Select the MII or SRL port now and RESET the MAC */
+    if (lp->phy[lp->active].id == 0) {
+	de4x5_switch_to_srl(dev);
+    } else {
+	de4x5_switch_to_mii(dev);
+    }
+    
+    /* 
+    ** Set the programmable burst length to 8 longwords for all the DC21140
+    ** Fasternet chips and 4 longwords for all others: DMA errors result
+    ** without these values. Cache align 16 long.
+    */
+    bmr = (lp->chipset==DC21140 ? PBL_8 : PBL_4) | DESC_SKIP_LEN | CACHE_ALIGN;
+    outl(bmr, DE4X5_BMR);
+    
+    omr = inl(DE4X5_OMR) & ~OMR_PR;             /* Turn of promiscuous mode */
+    if (lp->chipset != DC21140) {
+	omr |= TR_96;
+	lp->setup_f = HASH_PERF;
+    } else {
+	omr |= OMR_SDP | OMR_SB | (!lp->phy[lp->active].id ? OMR_SF : 0);
+	lp->setup_f = PERFECT;
+    }
+    outl(virt_to_bus(lp->rx_ring), DE4X5_RRBA);
+    outl(virt_to_bus(lp->tx_ring), DE4X5_TRBA);
+    
+    lp->rx_new = lp->rx_old = 0;
+    lp->tx_new = lp->tx_old = 0;
+    
+    for (i = 0; i < lp->rxRingSize; i++) {
+	lp->rx_ring[i].status = R_OWN;
+    }
+    
+    for (i = 0; i < lp->txRingSize; i++) {
+	lp->tx_ring[i].status = 0;
+    }
+    
+    barrier();
+    
+    /* Build the setup frame depending on filtering mode */
+    SetMulticastFilter(dev);
+    
+    if (lp->chipset != DC21140) {
+	load_packet(dev, lp->setup_frame, HASH_F|TD_SET|SETUP_FRAME_LEN, NULL);
+    } else {
+	load_packet(dev, lp->setup_frame, PERFECT_F|TD_SET|SETUP_FRAME_LEN, NULL);
+    }
+    outl(omr|OMR_ST, DE4X5_OMR);
+    
+    /* Poll for setup frame completion (adapter interrupts are disabled now) */
+    sti();                                       /* Ensure timer interrupts */
+    for (j=0, i=0;(i<500) && (j==0);i++) {       /* Upto 500ms delay */
+	udelay(1000);
+	if (lp->tx_ring[lp->tx_new].status >= 0) j=1;
+    }
+    outl(omr, DE4X5_OMR);                        /* Stop everything! */
+    
+    if (j == 0) {
+	printk("%s: Setup frame timed out, status %08x\n", dev->name, 
+	       inl(DE4X5_STS));
+	status = -EIO;
+    }
+    
+    lp->tx_new = (++lp->tx_new) % lp->txRingSize;
+    lp->tx_old = lp->tx_new;
+    
+    return status;
 }
 
 /* 
@@ -937,120 +1031,71 @@
 static int
 de4x5_queue_pkt(struct sk_buff *skb, struct device *dev)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-  int i, status = 0;
-  s32 imr, omr, sts;
-
-  /*
-  ** Clean out the TX ring asynchronously to interrupts - sometimes the
-  ** interrupts are lost by delayed descriptor status updates relative to
-  ** the irq assertion, especially with a busy PCI bus.
-  */
-  if (set_bit(0, (void*)&dev->tbusy) == 0) {
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    int status = 0;
+    
+    if (skb == NULL) {
+	dev_tint(dev);
+	return 0;
+    }
+
+    if (lp->tx_enable == NO) {                   /* Cannot send for now */
+	de4x5_put_cache(dev, skb);               /* Queue the buffer locally */
+	return 0;                                
+    }
+    
+    /*
+    ** Clean out the TX ring asynchronously to interrupts - sometimes the
+    ** interrupts are lost by delayed descriptor status updates relative to
+    ** the irq assertion, especially with a busy PCI bus.
+    */
+    set_bit(0, (void*)&dev->tbusy);
     cli();
     de4x5_tx(dev);
-    dev->tbusy = 0;
     sti();
-  }
-  
-  /* 
-  ** Transmitter timeout, possibly serious problems.
-  ** The 'lostMedia' threshold accounts for transient errors that
-  ** were noticed when switching media.
-  */
-  if (dev->tbusy || (lp->lostMedia > LOST_MEDIA_THRESHOLD)) {
-    u_long tickssofar = jiffies - dev->trans_start;
-    if ((tickssofar < QUEUE_PKT_TIMEOUT) &&
-	(lp->lostMedia <= LOST_MEDIA_THRESHOLD)) {
-      status = -1;
-    } else {
-      if (de4x5_debug >= 1) {
-	printk("%s: transmit timed out, status %08x, tbusy:%ld, lostMedia:%d tickssofar:%ld, resetting.\n",dev->name, inl(DE4X5_STS), dev->tbusy, lp->lostMedia, tickssofar);
-      }
-
-      /* Stop and reset the TX and RX... */
-      STOP_DE4X5;
-
-      /* Re-queue any skb's. */
-      for (i=lp->tx_old; i!=lp->tx_new; i=(++i)%lp->txRingSize) {
-	if (lp->skb[i] != NULL) {
-	  if (lp->skb[i]->len != FAKE_FRAME_LEN) {
-	    if (lp->tx_ring[i].status == T_OWN) {
-	      dev_queue_xmit(lp->skb[i], dev, SOPRI_NORMAL);
-	    } else {                               /* already sent */
-	      dev_kfree_skb(lp->skb[i], FREE_WRITE);
-	    }
-	  } else {
-	    dev_kfree_skb(lp->skb[i], FREE_WRITE);
-	  }
-	  lp->skb[i] = NULL;
-	}
-      }
-      if (skb->len != FAKE_FRAME_LEN) {
-	dev_queue_xmit(skb, dev, SOPRI_NORMAL);
-      } else {
-	dev_kfree_skb(skb, FREE_WRITE);
-      }
-
-      /* Initialise the hardware */
-      status = de4x5_init(dev);
-
-      /* Unmask DE4X5 board interrupts */
-      if (!status) {
-	/* Start here to clean stale interrupts later */
-	dev->interrupt = UNMASK_INTERRUPTS;
-	dev->start = 1;
-	dev->tbusy = 0;                         
-	dev->trans_start = jiffies;
-      
-	START_DE4X5;
-
-	/* Unmask DE4X5 board interrupts */
-	imr = 0;
-	UNMASK_IRQs;
-
-	/* Clear any pending (stale) interrupts */
-	sts = inl(DE4X5_STS);
-	outl(sts, DE4X5_STS);
-
-	ENABLE_IRQs;
-      } else {
-	printk("%s: hardware initialisation failure, status %08x.\n",
-	                                            dev->name, inl(DE4X5_STS));
-      }
-    }
-  } else if (skb == NULL) {
-    dev_tint(dev);
-  } else if (skb->len == FAKE_FRAME_LEN) {     /* Don't TX a fake frame! */
-    dev_kfree_skb(skb, FREE_WRITE);
-  } else if (skb->len > 0) {
-    /* Enforce 1 process per h/w access */
-    if (set_bit(0, (void*)&dev->tbusy) != 0) { 
-      printk("%s: Transmitter access conflict.\n", dev->name);
-      status = -1;                             /* Re-queue packet */
-    } else {
-      cli();
-      if (TX_BUFFS_AVAIL) {                    /* Fill in a Tx ring entry */
-	load_packet(dev, skb->data, TD_IC | TD_LS | TD_FS | skb->len, skb);
-	if (lp->tx_enable) {
-	  outl(POLL_DEMAND, DE4X5_TPD);        /* Start the TX */
+    
+    /* Transmit descriptor ring full or stale skb */
+    if (dev->tbusy || lp->skb[lp->tx_new]) {
+	if (dev->interrupt) {
+	    de4x5_putb_cache(dev, skb);          /* Requeue the buffer */
+	} else {
+	    de4x5_put_cache(dev, skb);
 	}
-
-	lp->tx_new = (++lp->tx_new) % lp->txRingSize; /* Ensure a wrap */
-	dev->trans_start = jiffies;
-
-	if (TX_BUFFS_AVAIL) {
-	  dev->tbusy = 0;                      /* Another pkt may be queued */
+	if (de4x5_debug > 1) {
+	    printk("%s: transmit busy, lost media or stale skb found:\n  STS:%08x\n  tbusy:%ld\n  lostMedia:%d\n  IMR:%08x\n  OMR:%08x\n Stale skb: %s\n",dev->name, inl(DE4X5_STS), dev->tbusy, lp->lostMedia, inl(DE4X5_IMR), inl(DE4X5_OMR), (lp->skb[lp->tx_new] ? "YES" : "NO"));
+	}
+    } else if (skb->len > 0) {
+	/* If we already have stuff queued locally, use that first */
+	if (lp->cache.skb && !dev->interrupt) {
+	    de4x5_put_cache(dev, skb);
+	    skb = de4x5_get_cache(dev);
+	}
+
+	while (skb && !dev->tbusy && !lp->skb[lp->tx_new]) {
+	    set_bit(0, (void*)&dev->tbusy);
+	    cli();
+	    if (TX_BUFFS_AVAIL) {           /* Fill in a Tx ring entry */
+		load_packet(dev, skb->data, 
+			    TD_IC | TD_LS | TD_FS | skb->len, skb);
+		outl(POLL_DEMAND, DE4X5_TPD);/* Start the TX */
+		
+		lp->tx_new = (++lp->tx_new) % lp->txRingSize;
+		dev->trans_start = jiffies;
+		    
+		if (TX_BUFFS_AVAIL) {
+		    dev->tbusy = 0;         /* Another pkt may be queued */
+		}
+		skb = de4x5_get_cache(dev);
+	    }
+	    sti();
+	}
+	if (skb && (dev->tbusy || lp->skb[lp->tx_new])) {
+	    de4x5_putb_cache(dev, skb);
 	}
-      } else {                                 /* Ring full - re-queue */
-	status = -1;
-      }
-      sti();
     }
-  }
-
-  return status;
+    
+    return status;
 }
 
 /*
@@ -1071,144 +1116,150 @@
     struct de4x5_private *lp;
     s32 imr, omr, sts;
     u_long iobase;
-
+    
     if (dev == NULL) {
 	printk ("de4x5_interrupt(): irq %d for unknown device.\n", irq);
-    } else {
-      lp = (struct de4x5_private *)dev->priv;
-      iobase = dev->base_addr;
-
-      if (dev->interrupt)
-	printk("%s: Re-entering the interrupt handler.\n", dev->name);
-
-      DISABLE_IRQs;                      /* Ensure non re-entrancy */
-      dev->interrupt = MASK_INTERRUPTS;
-
-      while ((sts = inl(DE4X5_STS)) & lp->irq_mask) { /* Read IRQ status */
+	return;
+    }
+    lp = (struct de4x5_private *)dev->priv;
+    iobase = dev->base_addr;
+	
+    if (dev->interrupt)
+      printk("%s: Re-entering the interrupt handler.\n", dev->name);
+	
+    DISABLE_IRQs;                        /* Ensure non re-entrancy */
+    dev->interrupt = MASK_INTERRUPTS;
+	
+    for (;;) {
+	sts = inl(DE4X5_STS);            /* Read IRQ status */
 	outl(sts, DE4X5_STS);            /* Reset the board interrupts */
-
-	if (sts & (STS_RI | STS_RU))	 /* Rx interrupt (packet[s] arrived) */
+	    
+	if (!(sts & lp->irq_mask)) break;/* All done */
+	    
+	if (sts & (STS_RI | STS_RU))     /* Rx interrupt (packet[s] arrived) */
 	  de4x5_rx(dev);
-
+	    
 	if (sts & (STS_TI | STS_TU))     /* Tx interrupt (packet sent) */
 	  de4x5_tx(dev); 
-
-	if (sts & STS_TM)                /* Autosense tick */
-	  de4x5_ast(dev);
-
+	    
 	if (sts & STS_LNF) {             /* TP Link has failed */
-	  lp->lostMedia = LOST_MEDIA_THRESHOLD + 1;
-	  lp->irq_mask &= ~IMR_LFM;
-	  kick_tx(dev);
+	    lp->lostMedia = LOST_MEDIA_THRESHOLD + 1;
+	    lp->irq_mask &= ~IMR_LFM;
 	}
-
+	    
 	if (sts & STS_SE) {              /* Bus Error */
-	  STOP_DE4X5;
-	  printk("%s: Fatal bus error occured, sts=%#8x, device stopped.\n",
-	                                                      dev->name, sts);
+	    STOP_DE4X5;
+	    printk("%s: Fatal bus error occured, sts=%#8x, device stopped.\n",
+		   dev->name, sts);
+	    return;
 	}
-      }
-
-      if (TX_BUFFS_AVAIL && dev->tbusy) {/* Any resources available? */
-	dev->tbusy = 0;                  /* Clear TX busy flag */
-	mark_bh(NET_BH);
-      }
+    }
 
-      dev->interrupt = UNMASK_INTERRUPTS;
-      ENABLE_IRQs;
+    /* Load the TX ring with any locally stored packets */
+    while (lp->cache.skb && !dev->tbusy && lp->tx_enable) {
+	de4x5_queue_pkt(de4x5_get_cache(dev), dev);
     }
 
+    dev->interrupt = UNMASK_INTERRUPTS;
+    ENABLE_IRQs;
+    
     return;
 }
 
 static int
 de4x5_rx(struct device *dev)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  int i, entry;
-  s32 status;
-  char *buf;
-
-  for (entry = lp->rx_new; lp->rx_ring[entry].status >= 0;entry = lp->rx_new) {
-    status = lp->rx_ring[entry].status;
-
-    if (status & RD_FS) {                   /* Remember the start of frame */
-      lp->rx_old = entry;
-    }
-
-    if (status & RD_LS) {                   /* Valid frame status */
-      if (status & RD_ES) {	            /* There was an error. */
-	lp->stats.rx_errors++;              /* Update the error stats. */
-	if (status & (RD_RF | RD_TL)) lp->stats.rx_frame_errors++;
-	if (status & RD_CE)           lp->stats.rx_crc_errors++;
-	if (status & RD_OF)           lp->stats.rx_fifo_errors++;
-      } else {                              /* A valid frame received */
-	struct sk_buff *skb;
-	short pkt_len = (short)(lp->rx_ring[entry].status >> 16) - 4;
-
-	if ((skb = dev_alloc_skb(pkt_len+2)) != NULL) {
-	  skb->dev = dev;
-	
-	  skb_reserve(skb,2);		/* Align */
-	  if (entry < lp->rx_old) {         /* Wrapped buffer */
-	    short len = (lp->rxRingSize - lp->rx_old) * RX_BUFF_SZ;
-	    memcpy(skb_put(skb,len), bus_to_virt(lp->rx_ring[lp->rx_old].buf), len);
-	    memcpy(skb_put(skb,pkt_len-len), bus_to_virt(lp->rx_ring[0].buf), pkt_len - len);
-	  } else {                          /* Linear buffer */
-	    memcpy(skb_put(skb,pkt_len), bus_to_virt(lp->rx_ring[lp->rx_old].buf), pkt_len);
-	  }
-
-	  /* Push up the protocol stack */
-	  skb->protocol=eth_type_trans(skb,dev);
-	  netif_rx(skb);
-
-	  /* Update stats */
-	  lp->stats.rx_packets++;
-	  for (i=1; i<DE4X5_PKT_STAT_SZ-1; i++) {
-	    if (pkt_len < (i*DE4X5_PKT_BIN_SZ)) {
-	      lp->pktStats.bins[i]++;
-	      i = DE4X5_PKT_STAT_SZ;
-	    }
-	  }
-	  buf = skb->data;                  /* Look at the dest addr */
-	  if (buf[0] & 0x01) {              /* Multicast/Broadcast */
-	    if ((*(s32 *)&buf[0] == -1) && (*(s16 *)&buf[4] == -1)) {
-	      lp->pktStats.broadcast++;
-	    } else {
-	      lp->pktStats.multicast++;
-	    }
-	  } else if ((*(s32 *)&buf[0] == *(s32 *)&dev->dev_addr[0]) &&
-		     (*(s16 *)&buf[4] == *(s16 *)&dev->dev_addr[4])) {
-	    lp->pktStats.unicast++;
-	  }
-	  
-	  lp->pktStats.bins[0]++;           /* Duplicates stats.rx_packets */
-	  if (lp->pktStats.bins[0] == 0) {  /* Reset counters */
-	    memset((char *)&lp->pktStats, 0, sizeof(lp->pktStats));
-	  }
-	} else {
-	  printk("%s: Insufficient memory; nuking packet.\n", dev->name);
-	  lp->stats.rx_dropped++;	      /* Really, deferred. */
-	  break;
-	}
-      }
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    int i, entry;
+    s32 status;
+    char *buf;
+    
+    for (entry=lp->rx_new; lp->rx_ring[entry].status>=0;entry=lp->rx_new) {
+	status = lp->rx_ring[entry].status;
+	
+	if (status & RD_FS) {                 /* Remember the start of frame */
+	    lp->rx_old = entry;
+	}
+	
+	if (status & RD_LS) {                 /* Valid frame status */
+	    lp->linkOK++;
+	    if (status & RD_ES) {	      /* There was an error. */
+		lp->stats.rx_errors++;        /* Update the error stats. */
+		if (status & (RD_RF | RD_TL)) lp->stats.rx_frame_errors++;
+		if (status & RD_CE)           lp->stats.rx_crc_errors++;
+		if (status & RD_OF)           lp->stats.rx_fifo_errors++;
+		if (status & RD_TL)           lp->stats.rx_length_errors++;
+		if (status & RD_RF)           lp->pktStats.rx_runt_frames++;
+		if (status & RD_CS)           lp->pktStats.rx_collision++;
+		if (status & RD_DB)           lp->pktStats.rx_dribble++;
+		if (status & RD_OF)           lp->pktStats.rx_overflow++;
+	    } else {                          /* A valid frame received */
+		struct sk_buff *skb;
+		short pkt_len = (short)(lp->rx_ring[entry].status >> 16) - 4;
+		
+		if ((skb = dev_alloc_skb(pkt_len+2)) == NULL) {
+		    printk("%s: Insufficient memory; nuking packet.\n", 
+			                                            dev->name);
+		    lp->stats.rx_dropped++;   /* Really, deferred. */
+		    break;
+		}
 
-      /* Change buffer ownership for this last frame, back to the adapter */
-      for (; lp->rx_old!=entry; lp->rx_old=(++lp->rx_old)%lp->rxRingSize) {
-	lp->rx_ring[lp->rx_old].status = R_OWN;
-	barrier();
-      }
-      lp->rx_ring[entry].status = R_OWN;
-      barrier();
+		skb->dev = dev;
+		skb_reserve(skb,2);	      /* Align */
+		if (entry < lp->rx_old) {     /* Wrapped buffer */
+		    short len = (lp->rxRingSize - lp->rx_old) * RX_BUFF_SZ;
+		    memcpy(skb_put(skb,len), bus_to_virt(lp->rx_ring[lp->rx_old].buf), len);
+		    memcpy(skb_put(skb,pkt_len-len), bus_to_virt(lp->rx_ring[0].buf), pkt_len - len);
+		} else {                      /* Linear buffer */
+		    memcpy(skb_put(skb,pkt_len), bus_to_virt(lp->rx_ring[lp->rx_old].buf), pkt_len);
+		}
+		    
+		/* Push up the protocol stack */
+		skb->protocol=eth_type_trans(skb,dev);
+		netif_rx(skb);
+		    
+		/* Update stats */
+		lp->stats.rx_packets++;
+		for (i=1; i<DE4X5_PKT_STAT_SZ-1; i++) {
+		    if (pkt_len < (i*DE4X5_PKT_BIN_SZ)) {
+			lp->pktStats.bins[i]++;
+			i = DE4X5_PKT_STAT_SZ;
+		    }
+		}
+		buf = skb->data;              /* Look at the dest addr */
+		if (buf[0] & 0x01) {          /* Multicast/Broadcast */
+		    if ((*(s32 *)&buf[0] == -1) && (*(s16 *)&buf[4] == -1)) {
+			lp->pktStats.broadcast++;
+		    } else {
+			lp->pktStats.multicast++;
+		    }
+		} else if ((*(s32 *)&buf[0] == *(s32 *)&dev->dev_addr[0]) &&
+			   (*(s16 *)&buf[4] == *(s16 *)&dev->dev_addr[4])) {
+		    lp->pktStats.unicast++;
+		}
+		
+		lp->pktStats.bins[0]++;       /* Duplicates stats.rx_packets */
+		if (lp->pktStats.bins[0] == 0) { /* Reset counters */
+		    memset((char *)&lp->pktStats, 0, sizeof(lp->pktStats));
+		}
+	    }
+	    
+	    /* Change buffer ownership for this frame, back to the adapter */
+	    for (;lp->rx_old!=entry;lp->rx_old=(++lp->rx_old)%lp->rxRingSize) {
+		lp->rx_ring[lp->rx_old].status = R_OWN;
+		barrier();
+	    }
+	    lp->rx_ring[entry].status = R_OWN;
+	    barrier();
+	}
+	
+	/*
+	** Update entry information
+	*/
+	lp->rx_new = (++lp->rx_new) % lp->rxRingSize;
     }
-
-    /*
-    ** Update entry information
-    */
-    lp->rx_new = (++lp->rx_new) % lp->rxRingSize;
-  }
-
-  return 0;
+    
+    return 0;
 }
 
 /*
@@ -1217,200 +1268,172 @@
 static int
 de4x5_tx(struct device *dev)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-  int entry;
-  s32 status;
-
-  for (entry = lp->tx_old; entry != lp->tx_new; entry = lp->tx_old) {
-    status = lp->tx_ring[entry].status;
-    if (status < 0) {                            /* Buffer not sent yet */
-      break;
-    } else if (status & TD_ES) {                 /* An error happened */
-      lp->stats.tx_errors++; 
-      if (status & TD_NC)  lp->stats.tx_carrier_errors++;
-      if (status & TD_LC)  lp->stats.tx_window_errors++;
-      if (status & TD_UF)  lp->stats.tx_fifo_errors++;
-      if (status & TD_LC)  lp->stats.collisions++;
-      if (status & TD_EC)  lp->pktStats.excessive_collisions++;
-      if (status & TD_DE)  lp->stats.tx_aborted_errors++;
-
-      if ((status != 0x7fffffff) &&              /* Not setup frame */
-	  (status & (TD_LO | TD_NC | TD_EC | TD_LF))) {
-	lp->lostMedia++;
-	if (lp->lostMedia > LOST_MEDIA_THRESHOLD) { /* Trip autosense */
-	  kick_tx(dev);
-	}
-      } else {
-	outl(POLL_DEMAND, DE4X5_TPD);            /* Restart a stalled TX */
-      }
-    } else {                                     /* Packet sent */
-      lp->stats.tx_packets++;
-      lp->lostMedia = 0;                         /* Remove transient problem */
-    }
-    /* Free the buffer if it's not a setup frame. */
-    if (lp->skb[entry] != NULL) {
-      dev_kfree_skb(lp->skb[entry], FREE_WRITE);
-      lp->skb[entry] = NULL;
-    }
-
-    /* Update all the pointers */
-    lp->tx_old = (++lp->tx_old) % lp->txRingSize;
-  }
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    int entry;
+    s32 status;
+    
+    for (entry = lp->tx_old; entry != lp->tx_new; entry = lp->tx_old) {
+	status = lp->tx_ring[entry].status;
+	if (status < 0) {                     /* Buffer not sent yet */
+	    break;
+	} else if (status & TD_ES) {          /* An error happened */
+	    lp->stats.tx_errors++; 
+	    if (status & TD_NC)  lp->stats.tx_carrier_errors++;
+	    if (status & TD_LC)  lp->stats.tx_window_errors++;
+	    if (status & TD_UF)  lp->stats.tx_fifo_errors++;
+	    if (status & TD_LC)  lp->stats.collisions++;
+	    if (status & TD_EC)  lp->pktStats.excessive_collisions++;
+	    if (status & TD_DE)  lp->stats.tx_aborted_errors++;
+	    
+	    if ((status != 0x7fffffff) &&     /* Not setup frame */
+		(status & (TD_LO | TD_NC | TD_EC | TD_LF))) {
+		lp->lostMedia++;
+	    } else {
+		outl(POLL_DEMAND, DE4X5_TPD); /* Restart a stalled TX */
+	    }
+	} else {                              /* Packet sent */
+	    lp->stats.tx_packets++;
+	    lp->lostMedia = 0;                /* Remove transient problem */
+	}
+	/* Free the buffer if it's not a setup frame. */
+	if (lp->skb[entry] != NULL) {
+	    dev_kfree_skb(lp->skb[entry], FREE_WRITE);
+	    lp->skb[entry] = NULL;
+	}
+	
+	/* Update all the pointers */
+	lp->tx_old = (++lp->tx_old) % lp->txRingSize;
+    }
 
-  return 0;
+    if (TX_BUFFS_AVAIL && dev->tbusy) {  /* Any resources available? */
+	dev->tbusy = 0;                  /* Clear TX busy flag */
+	if (dev->interrupt) mark_bh(NET_BH);
+    }
+	
+    return 0;
 }
 
 static int
 de4x5_ast(struct device *dev)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-  s32 gep;
-
-  disable_ast(dev);
-
-  if (lp->chipset == DC21140) {
-    gep = inl(DE4X5_GEP);
-    if (((lp->media == _100Mb) &&  (gep & GEP_SLNK)) ||
-	((lp->media == _10Mb)  &&  (gep & GEP_LNP))  ||
-	((lp->media == _10Mb)  && !(gep & GEP_SLNK)) ||
-	 (lp->media == NC)) {
-      if (lp->linkProb || ((lp->media == NC) && (!(gep & GEP_LNP)))) {
-	lp->lostMedia = LOST_MEDIA_THRESHOLD + 1;
-	lp->linkProb = 0;
-	kick_tx(dev);
-      } else {
-	switch(lp->media) {
-	case NC:
-	  lp->linkProb = 0;
-	  enable_ast(dev, DE4X5_AUTOSENSE_MS);
-	  break;
-
-	case _10Mb:
-	  lp->linkProb = 1;                    /* Flag a potential problem */
-	  enable_ast(dev, 1500);
-	  break;
-
-	case _100Mb:
-	  lp->linkProb = 1;                    /* Flag a potential problem */
-	  enable_ast(dev, 4000);
-	  break;
-	}
-      }
-    } else {
-      lp->linkProb = 0;                        /* Link OK */
-      enable_ast(dev, DE4X5_AUTOSENSE_MS);
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    int next_tick = DE4X5_AUTOSENSE_MS;
+    
+    disable_ast(dev);
+    
+    if (lp->chipset == DC21140) {
+	next_tick = dc21140m_autoconf(dev);
+    } else if (lp->chipset == DC21041) {
+	next_tick = dc21041_autoconf(dev);
+    } else if (lp->chipset == DC21040) {
+	next_tick = dc21040_autoconf(dev);
     }
-  }
-
-  return 0;
+    lp->linkOK = 0;
+    enable_ast(dev, next_tick);
+    
+    return 0;
 }
 
 static int
 de4x5_close(struct device *dev)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-  s32 imr, omr;
-
-  dev->start = 0;
-  dev->tbusy = 1;
-
-  if (de4x5_debug > 1) {
-    printk("%s: Shutting down ethercard, status was %8.8x.\n",
-	   dev->name, inl(DE4X5_STS));
-  }
-
-  /* 
-  ** We stop the DE4X5 here... mask interrupts and stop TX & RX
-  */
-  DISABLE_IRQs;
-
-  STOP_DE4X5;
-
-  /*
-  ** Free the associated irq
-  */
-  free_irq(dev->irq, dev);
-  irq2dev_map[dev->irq] = 0;
-
-  MOD_DEC_USE_COUNT;
-
-  /* Put the adapter to sleep to save power */
-  if (lp->chipset == DC21041) {
-    outl(0, DE4X5_SICR);
-    outl(CFDA_PSM, PCI_CFDA);
-  }
-
-  return 0;
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    s32 imr, omr;
+    
+    disable_ast(dev);
+    dev->start = 0;
+    dev->tbusy = 1;
+    
+    if (de4x5_debug > 1) {
+	printk("%s: Shutting down ethercard, status was %8.8x.\n",
+	       dev->name, inl(DE4X5_STS));
+    }
+    
+    /* 
+    ** We stop the DE4X5 here... mask interrupts and stop TX & RX
+    */
+    DISABLE_IRQs;
+    
+    STOP_DE4X5;
+    
+    /*
+    ** Free the associated irq
+    */
+    free_irq(dev->irq, dev);
+    lp->state = CLOSED;
+    
+    MOD_DEC_USE_COUNT;
+    
+    /* Put the adapter to sleep to save power */
+    if (lp->chipset == DC21041) {
+	outl(0, DE4X5_SICR);
+	outl(CFDA_PSM, PCI_CFDA);
+    }
+    
+    return 0;
 }
 
 static struct enet_statistics *
 de4x5_get_stats(struct device *dev)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-
-  lp->stats.rx_missed_errors = (int) (inl(DE4X5_MFC) & (MFC_OVFL | MFC_CNTR));
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
     
-  return &lp->stats;
+    lp->stats.rx_missed_errors = (int)(inl(DE4X5_MFC) & (MFC_OVFL | MFC_CNTR));
+    
+    return &lp->stats;
 }
 
 static void load_packet(struct device *dev, char *buf, u32 flags, struct sk_buff *skb)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-
-  lp->tx_ring[lp->tx_new].buf = virt_to_bus(buf);
-  lp->tx_ring[lp->tx_new].des1 &= TD_TER;
-  lp->tx_ring[lp->tx_new].des1 |= flags;
-  lp->skb[lp->tx_new] = skb;
-  barrier();
-  lp->tx_ring[lp->tx_new].status = T_OWN;
-  barrier();
-
-  return;
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    
+    lp->tx_ring[lp->tx_new].buf = virt_to_bus(buf);
+    lp->tx_ring[lp->tx_new].des1 &= TD_TER;
+    lp->tx_ring[lp->tx_new].des1 |= flags;
+    lp->skb[lp->tx_new] = skb;
+    barrier();
+    lp->tx_ring[lp->tx_new].status = T_OWN;
+    barrier();
+    
+    return;
 }
+
 /*
 ** Set or clear the multicast filter for this adaptor.
-** num_addrs == -1	Promiscuous mode, receive all packets - now supported.
-**                      Can also use the ioctls.
-** num_addrs == 0	Normal mode, clear multicast list
-** num_addrs > 0	Multicast mode, receive normal and MC packets, and do
-** 			best-effort filtering.
-** num_addrs == HASH_TABLE_LEN
-**	                Set all multicast bits (pass all multicasts).
 */
 static void
 set_multicast_list(struct device *dev)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-
-  /* First, double check that the adapter is open */
-  if (irq2dev_map[dev->irq] != NULL) {
-    if (dev->flags & IFF_PROMISC) {         /* set promiscuous mode */
-      u32 omr;
-      omr = inl(DE4X5_OMR);
-      omr |= OMR_PR;
-      outl(omr, DE4X5_OMR);
-    } else { 
-      SetMulticastFilter(dev);
-      if (lp->setup_f == HASH_PERF) {
-	load_packet(dev, lp->setup_frame, TD_IC | HASH_F | TD_SET | 
-		                                        SETUP_FRAME_LEN, NULL);
-      } else {
-	load_packet(dev, lp->setup_frame, TD_IC | PERFECT_F | TD_SET | 
-		                                        SETUP_FRAME_LEN, NULL);
-      }
-      
-      lp->tx_new = (++lp->tx_new) % lp->txRingSize;
-      outl(POLL_DEMAND, DE4X5_TPD);                /* Start the TX */
-      dev->trans_start = jiffies;
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    
+    /* First, double check that the adapter is open */
+    if (lp->state == OPEN) {
+	if (dev->flags & IFF_PROMISC) {         /* set promiscuous mode */
+	    u32 omr;
+	    omr = inl(DE4X5_OMR);
+	    omr |= OMR_PR;
+	    outl(omr, DE4X5_OMR);
+	} else { 
+	    SetMulticastFilter(dev);
+	    if (lp->setup_f == HASH_PERF) {
+		load_packet(dev, lp->setup_frame, TD_IC | HASH_F | TD_SET | 
+			    SETUP_FRAME_LEN, NULL);
+	    } else {
+		load_packet(dev, lp->setup_frame, TD_IC | PERFECT_F | TD_SET | 
+			    SETUP_FRAME_LEN, NULL);
+	    }
+	    
+	    lp->tx_new = (++lp->tx_new) % lp->txRingSize;
+	    outl(POLL_DEMAND, DE4X5_TPD);       /* Start the TX */
+	    dev->trans_start = jiffies;
+	}
     }
-  }
-
-  return;
+    
+    return;
 }
 
 /*
@@ -1420,59 +1443,58 @@
 */
 static void SetMulticastFilter(struct device *dev)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  struct dev_mc_list *dmi=dev->mc_list;
-  u_long iobase = dev->base_addr;
-  int i, j, bit, byte;
-  u16 hashcode;
-  u32 omr, crc, poly = CRC_POLYNOMIAL_LE;
-  char *pa;
-  unsigned char *addrs;
-
-  omr = inl(DE4X5_OMR);
-  omr &= ~OMR_PR;
-  pa = build_setup_frame(dev, ALL);          /* Build the basic frame */
-
-  if ((dev->flags & IFF_ALLMULTI) || (dev->mc_count > 14)) {
-    omr |= OMR_PM;                           /* Pass all multicasts */
-  } else if (lp->setup_f == HASH_PERF) {
-                                             /* Now update the MCA table */
-    for (i=0;i<dev->mc_count;i++) {          /* for each address in the list */
-      addrs=dmi->dmi_addr;
-      dmi=dmi->next;
-      if ((*addrs & 0x01) == 1) {            /* multicast address? */ 
-	crc = 0xffffffff;                    /* init CRC for each address */
-	for (byte=0;byte<ETH_ALEN;byte++) {  /* for each address byte */
-	                                     /* process each address bit */ 
-	  for (bit = *addrs++,j=0;j<8;j++, bit>>=1) {
-	    crc = (crc >> 1) ^ (((crc ^ bit) & 0x01) ? poly : 0);
-	  }
-	}
-	hashcode = crc & HASH_BITS;          /* hashcode is 9 LSb of CRC */
-	
-	byte = hashcode >> 3;                /* bit[3-8] -> byte in filter */
-	bit = 1 << (hashcode & 0x07);        /* bit[0-2] -> bit in byte */
-	
-	byte <<= 1;                          /* calc offset into setup frame */
-	if (byte & 0x02) {
-	  byte -= 1;
-	}
-	lp->setup_frame[byte] |= bit;
-      }
-    }
-  } else {                                   /* Perfect filtering */
-    for (j=0; j<dev->mc_count; j++) {
-      addrs=dmi->dmi_addr;
-      dmi=dmi->next;
-      for (i=0; i<ETH_ALEN; i++) { 
-	*(pa + (i&1)) = *addrs++;
-	if (i & 0x01) pa += 4;
-      }
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    struct dev_mc_list *dmi=dev->mc_list;
+    u_long iobase = dev->base_addr;
+    int i, j, bit, byte;
+    u16 hashcode;
+    u32 omr, crc, poly = CRC_POLYNOMIAL_LE;
+    char *pa;
+    unsigned char *addrs;
+    
+    omr = inl(DE4X5_OMR);
+    omr &= ~(OMR_PR | OMR_PM);
+    pa = build_setup_frame(dev, ALL);        /* Build the basic frame */
+    
+    if ((dev->flags & IFF_ALLMULTI) || (dev->mc_count > 14)) {
+	omr |= OMR_PM;                       /* Pass all multicasts */
+    } else if (lp->setup_f == HASH_PERF) {   /* Hash Filtering */
+	for (i=0;i<dev->mc_count;i++) {      /* for each address in the list */
+	    addrs=dmi->dmi_addr;
+	    dmi=dmi->next;
+	    if ((*addrs & 0x01) == 1) {      /* multicast address? */ 
+		crc = 0xffffffff;            /* init CRC for each address */
+		for (byte=0;byte<ETH_ALEN;byte++) {/* for each address byte */
+		                             /* process each address bit */ 
+		    for (bit = *addrs++,j=0;j<8;j++, bit>>=1) {
+			crc = (crc >> 1) ^ (((crc ^ bit) & 0x01) ? poly : 0);
+		    }
+		}
+		hashcode = crc & HASH_BITS;  /* hashcode is 9 LSb of CRC */
+		
+		byte = hashcode >> 3;        /* bit[3-8] -> byte in filter */
+		bit = 1 << (hashcode & 0x07);/* bit[0-2] -> bit in byte */
+		
+		byte <<= 1;                  /* calc offset into setup frame */
+		if (byte & 0x02) {
+		    byte -= 1;
+		}
+		lp->setup_frame[byte] |= bit;
+	    }
+	}
+    } else {                                 /* Perfect filtering */
+	for (j=0; j<dev->mc_count; j++) {
+	    addrs=dmi->dmi_addr;
+	    dmi=dmi->next;
+	    for (i=0; i<ETH_ALEN; i++) { 
+		*(pa + (i&1)) = *addrs++;
+		if (i & 0x01) pa += 4;
+	    }
+	}
     }
-  }
-  outl(omr, DE4X5_OMR);
-
-  return;
+    outl(omr, DE4X5_OMR);
+    
+    return;
 }
 
 /*
@@ -1481,57 +1503,54 @@
 */
 static void eisa_probe(struct device *dev, u_long ioaddr)
 {
-  int i, maxSlots, status;
-  u_short vendor, device;
-  s32 cfid;
-  u_long iobase;
-  struct bus_type *lp = &bus;
-  char name[DE4X5_STRLEN];
-
-  if (!ioaddr && autoprobed) return ;            /* Been here before ! */
-  if ((ioaddr < 0x1000) && (ioaddr > 0)) return; /* PCI MODULE special */
-
-  lp->bus = EISA;
-
-  if (ioaddr == 0) {                     /* Autoprobing */
-    iobase = EISA_SLOT_INC;              /* Get the first slot address */
-    i = 1;
-    maxSlots = MAX_EISA_SLOTS;
-  } else {                               /* Probe a specific location */
-    iobase = ioaddr;
-    i = (ioaddr >> 12);
-    maxSlots = i + 1;
-  }
-
-  for (status = -ENODEV; (i<maxSlots) && (dev!=NULL); i++, iobase+=EISA_SLOT_INC) {
-    if (EISA_signature(name, EISA_ID)) {
-      cfid = inl(PCI_CFID);
-      device = (u_short)(cfid >> 16);
-      vendor = (u_short) cfid;
-
-      lp->bus = EISA;
-      lp->chipset = device;
-      if (DevicePresent(EISA_APROM) == 0) { 
-	/* Write the PCI Configuration Registers */
-	outl(PCI_COMMAND_IO | PCI_COMMAND_MASTER, PCI_CFCS);
-	outl(0x00004000, PCI_CFLT);
-	outl(iobase, PCI_CBIO);
-
-	if (check_region(iobase, DE4X5_EISA_TOTAL_SIZE) == 0) {
-	  if ((dev = alloc_device(dev, iobase)) != NULL) {
-	    if ((status = de4x5_hw_init(dev, iobase)) == 0) {
-	      num_de4x5s++;
-	    }
-	    num_eth++;
-	  }
-	} else if (autoprobed) {
-	  printk("%s: region already allocated at 0x%04lx.\n", dev->name, iobase);
+    int i, maxSlots, status;
+    u_short vendor, device;
+    s32 cfid;
+    u_long iobase;
+    struct bus_type *lp = &bus;
+    char name[DE4X5_STRLEN];
+    
+    if (!ioaddr && autoprobed) return;     /* Been here before ! */
+    
+    lp->bus = EISA;
+    
+    if (ioaddr == 0) {                     /* Autoprobing */
+	iobase = EISA_SLOT_INC;            /* Get the first slot address */
+	i = 1;
+	maxSlots = MAX_EISA_SLOTS;
+    } else {                               /* Probe a specific location */
+	iobase = ioaddr;
+	i = (ioaddr >> 12);
+	maxSlots = i + 1;
+    }
+    
+    for (status = -ENODEV; (i<maxSlots) && (dev!=NULL); i++, iobase+=EISA_SLOT_INC) {
+	if (EISA_signature(name, EISA_ID)) {
+	    cfid = inl(PCI_CFID);
+	    device = (u_short)(cfid >> 16);
+	    vendor = (u_short) cfid;
+	    
+	    lp->chipset = device;
+	    DevicePresent(EISA_APROM);
+	    /* Write the PCI Configuration Registers */
+	    outl(PCI_COMMAND_IO | PCI_COMMAND_MASTER, PCI_CFCS);
+	    outl(0x00004000, PCI_CFLT);
+	    outl(iobase, PCI_CBIO);
+	    
+	    if (check_region(iobase, DE4X5_EISA_TOTAL_SIZE) == 0) {
+		if ((dev = alloc_device(dev, iobase)) != NULL) {
+		    if ((status = de4x5_hw_init(dev, iobase)) == 0) {
+			num_de4x5s++;
+		    }
+		    num_eth++;
+		}
+	    } else if (autoprobed) {
+		printk("%s: region already allocated at 0x%04lx.\n", dev->name,iobase);
+	    }
 	}
-      }
     }
-  }
-
-  return;
+    
+    return;
 }
 
 /*
@@ -1551,79 +1570,80 @@
 
 static void pci_probe(struct device *dev, u_long ioaddr)
 {
-  u_char irq;
-  u_char pb, pbus, dev_num, dnum, dev_fn;
-  u_short vendor, device, index, status;
-  u_int class = DE4X5_CLASS_CODE;
-  u_int iobase;
-  struct bus_type *lp = &bus;
-
-  if (!ioaddr && autoprobed) return ;        /* Been here before ! */
-
-  if (pcibios_present()) {
+    u_char irq;
+    u_char pb, pbus, dev_num, dnum, dev_fn;
+    u_short vendor, device, index, status;
+    u_int class = DE4X5_CLASS_CODE;
+    u_int iobase;
+    struct bus_type *lp = &bus;
+    
+    if (!ioaddr && autoprobed) return;       /* Been here before ! */
+    
+    if (!pcibios_present()) return;          /* No PCI bus in this machine! */
+    
     lp->bus = PCI;
-
+    
     if (ioaddr < 0x1000) {
-      pbus = (u_short)(ioaddr >> 8);
-      dnum = (u_short)(ioaddr & 0xff);
+	pbus = (u_short)(ioaddr >> 8);
+	dnum = (u_short)(ioaddr & 0xff);
     } else {
-      pbus = 0;
-      dnum = 0;
+	pbus = 0;
+	dnum = 0;
     }
     
     for (index=0; 
 	 (pcibios_find_class(class, index, &pb, &dev_fn)!= PCIBIOS_DEVICE_NOT_FOUND);
 	 index++) {
-      dev_num = PCI_SLOT(dev_fn);
-
-      if ((!pbus && !dnum) || ((pbus == pb) && (dnum == dev_num))) {
-	pcibios_read_config_word(pb, PCI_DEVICE, PCI_VENDOR_ID, &vendor);
-	pcibios_read_config_word(pb, PCI_DEVICE, PCI_DEVICE_ID, &device);
-	if (is_DC21040 || is_DC21041 || is_DC21140) {
-	  /* Set the device number information */
-	  lp->device = dev_num;
-	  lp->bus_num = pb;
-
-	  /* Set the chipset information */
-	  lp->chipset = device;
-
-	  /* Get the board I/O address */
-	  pcibios_read_config_dword(pb, PCI_DEVICE, PCI_BASE_ADDRESS_0, &iobase);
-	  iobase &= CBIO_MASK;
-
-	  /* Fetch the IRQ to be used */
-	  pcibios_read_config_byte(pb, PCI_DEVICE, PCI_INTERRUPT_LINE, &irq);
-
-	  /* Check if I/O accesses and Bus Mastering are enabled */
-	  pcibios_read_config_word(pb, PCI_DEVICE, PCI_COMMAND, &status);
-	  if (status & PCI_COMMAND_IO) {
+	dev_num = PCI_SLOT(dev_fn);
+	
+	if ((!pbus && !dnum) || ((pbus == pb) && (dnum == dev_num))) {
+	    pcibios_read_config_word(pb, PCI_DEVICE, PCI_VENDOR_ID, &vendor);
+	    pcibios_read_config_word(pb, PCI_DEVICE, PCI_DEVICE_ID, &device);
+	    if (!(is_DC21040 || is_DC21041 || is_DC21140)) continue;
+	    
+	    /* Set the device number information */
+	    lp->device = dev_num;
+	    lp->bus_num = pb;
+	    
+	    /* Set the chipset information */
+	    lp->chipset = device;
+	    
+	    /* Get the board I/O address */
+	    pcibios_read_config_dword(pb, PCI_DEVICE, PCI_BASE_ADDRESS_0, &iobase);
+	    iobase &= CBIO_MASK;
+	    
+	    /* Fetch the IRQ to be used */
+	    pcibios_read_config_byte(pb, PCI_DEVICE, PCI_INTERRUPT_LINE, &irq);
+	    if ((irq == 0) || (irq == (u_char) 0xff)) continue;
+	    
+	    /* Check if I/O accesses and Bus Mastering are enabled */
+	    pcibios_read_config_word(pb, PCI_DEVICE, PCI_COMMAND, &status);
+	    if (!(status & PCI_COMMAND_IO)) continue;
+	    
 	    if (!(status & PCI_COMMAND_MASTER)) {
-	      status |= PCI_COMMAND_MASTER;
-	      pcibios_write_config_word(pb, PCI_DEVICE, PCI_COMMAND, status);
-	      pcibios_read_config_word(pb, PCI_DEVICE, PCI_COMMAND, &status);
-	    }
-	    if (status & PCI_COMMAND_MASTER) {
-	      if ((DevicePresent(DE4X5_APROM) == 0) || is_not_dec) {
-		if (check_region(iobase, DE4X5_PCI_TOTAL_SIZE) == 0) {
-		  if ((dev = alloc_device(dev, iobase)) != NULL) {
+		status |= PCI_COMMAND_MASTER;
+		pcibios_write_config_word(pb, PCI_DEVICE, PCI_COMMAND, status);
+		pcibios_read_config_word(pb, PCI_DEVICE, PCI_COMMAND, &status);
+	    }
+	    if (!(status & PCI_COMMAND_MASTER)) continue;
+	    
+	    DevicePresent(DE4X5_APROM);
+	    if (check_region(iobase, DE4X5_PCI_TOTAL_SIZE) == 0) {
+		if ((dev = alloc_device(dev, iobase)) != NULL) {
 		    dev->irq = irq;
 		    if ((status = de4x5_hw_init(dev, iobase)) == 0) {
-		      num_de4x5s++;
+			num_de4x5s++;
 		    }
 		    num_eth++;
-		  }
-		} else if (autoprobed) {
-		  printk("%s: region already allocated at 0x%04x.\n", dev->name, (u_short)iobase);
 		}
-	      }
+	    } else if (autoprobed) {
+		printk("%s: region already allocated at 0x%04x.\n", dev->name, 
+		       (u_short)iobase);
 	    }
-	  }
 	}
-      }
     }
-  }
-
-  return;
+    
+    return;
 }
 
 /*
@@ -1632,536 +1652,1103 @@
 */
 static struct device *alloc_device(struct device *dev, u_long iobase)
 {
-  int addAutoProbe = 0;
-  struct device *tmp = NULL, *ret;
-  int (*init)(struct device *) = NULL;
-
-  /*
-  ** Check the device structures for an end of list or unused device
-  */
-  if (!loading_module) {
+    int addAutoProbe = 0;
+    struct device *tmp = NULL, *ret;
+    int (*init)(struct device *) = NULL;
+    
+    if (loading_module) return dev;
+    
+    /*
+    ** Check the device structures for an end of list or unused device
+    */
     while (dev->next != NULL) {
-      if ((dev->base_addr == DE4X5_NDA) || (dev->base_addr == 0)) break;
-      dev = dev->next;                     /* walk through eth device list */
-      num_eth++;                           /* increment eth device number */
+	if ((dev->base_addr == DE4X5_NDA) || (dev->base_addr == 0)) break;
+	dev = dev->next;                   /* walk through eth device list */
+	num_eth++;                         /* increment eth device number */
     }
-
+    
     /*
     ** If an autoprobe is requested for another device, we must re-insert
     ** the request later in the list. Remember the current position first.
     */
     if ((dev->base_addr == 0) && (num_de4x5s > 0)) {
-      addAutoProbe++;
-      tmp = dev->next;                     /* point to the next device */
-      init = dev->init;                    /* remember the probe function */
+	addAutoProbe++;
+	tmp = dev->next;                   /* point to the next device */
+	init = dev->init;                  /* remember the probe function */
     }
-
+    
     /*
     ** If at end of list and can't use current entry, malloc one up. 
     ** If memory could not be allocated, print an error message.
     */
     if ((dev->next == NULL) &&  
-	!((dev->base_addr == DE4X5_NDA) || (dev->base_addr == 0))){
-      dev->next = (struct device *)kmalloc(sizeof(struct device) + 8,
-					   GFP_KERNEL);
-
-      dev = dev->next;                     /* point to the new device */
-      if (dev == NULL) {
-	printk("eth%d: Device not initialised, insufficient memory\n",
-	       num_eth);
-      } else {
-	/*
-	** If the memory was allocated, point to the new memory area
-	** and initialize it (name, I/O address, next device (NULL) and
-	** initialisation probe routine).
-	*/
-	dev->name = (char *)(dev + sizeof(struct device));
-	if (num_eth > 9999) {
-	  sprintf(dev->name,"eth????");    /* New device name */
-	} else {
-	  sprintf(dev->name,"eth%d", num_eth);/* New device name */
-	}
-	dev->base_addr = iobase;           /* assign the io address */
-	dev->next = NULL;                  /* mark the end of list */
-	dev->init = &de4x5_probe;          /* initialisation routine */
-	num_de4x5s++;
-      }
+	!((dev->base_addr == DE4X5_NDA) || (dev->base_addr == 0))) {
+	dev->next = (struct device *)kmalloc(sizeof(struct device)+8, GFP_KERNEL);
+	dev = dev->next;                   /* point to the new device */
+	if (dev == NULL) {
+	    printk("eth%d: Device not initialised, insufficient memory\n", num_eth);
+	} else {
+	    /*
+	    ** If the memory was allocated, point to the new memory area
+	    ** and initialize it (name, I/O address, next device (NULL) and
+	    ** initialisation probe routine).
+	    */
+	    dev->name = (char *)(dev + sizeof(struct device));
+	    if (num_eth > 9999) {
+		sprintf(dev->name,"eth????");/* New device name */
+	    } else {
+		sprintf(dev->name,"eth%d", num_eth);/* New device name */
+	    }
+	    dev->base_addr = iobase;       /* assign the io address */
+	    dev->next = NULL;              /* mark the end of list */
+	    dev->init = &de4x5_probe;      /* initialisation routine */
+	    num_de4x5s++;
+	}
     }
     ret = dev;                             /* return current struct, or NULL */
-  
+     
     /*
     ** Now figure out what to do with the autoprobe that has to be inserted.
     ** Firstly, search the (possibly altered) list for an empty space.
     */
     if (ret != NULL) {
-      if (addAutoProbe) {
-	for (; (tmp->next!=NULL) && (tmp->base_addr!=DE4X5_NDA); tmp=tmp->next);
-
-	/*
-	** If no more device structures and can't use the current one, malloc
-	** one up. If memory could not be allocated, print an error message.
-	*/
-	if ((tmp->next == NULL) && !(tmp->base_addr == DE4X5_NDA)) {
-	  tmp->next = (struct device *)kmalloc(sizeof(struct device) + 8,
-					       GFP_KERNEL);
-	  tmp = tmp->next;                     /* point to the new device */
-	  if (tmp == NULL) {
-	    printk("%s: Insufficient memory to extend the device list.\n", 
-		   dev->name);
-	  } else {
+	if (addAutoProbe) {
+	    for (; (tmp->next!=NULL) && (tmp->base_addr!=DE4X5_NDA); tmp=tmp->next);
 	    /*
-	    ** If the memory was allocated, point to the new memory area
-	    ** and initialize it (name, I/O address, next device (NULL) and
-	    ** initialisation probe routine).
+	    ** If no more device structures and can't use the current one, 
+	    ** malloc one up. If memory could not be allocated, print an error
+	    **message.
 	    */
-	    tmp->name = (char *)(tmp + sizeof(struct device));
-	    if (num_eth > 9999) {
-	      sprintf(tmp->name,"eth????");       /* New device name */
-	    } else {
-	      sprintf(tmp->name,"eth%d", num_eth);/* New device name */
+	    if ((tmp->next == NULL) && !(tmp->base_addr == DE4X5_NDA)) {
+		tmp->next = (struct device *)kmalloc(sizeof(struct device) + 8,
+						     GFP_KERNEL);
+		tmp = tmp->next;           /* point to the new device */
+		if (tmp == NULL) {
+		    printk("%s: Insufficient memory to extend the device list.\n", 
+			   dev->name);
+		} else {
+		    /*
+		    ** If the memory was allocated, point to the new memory
+		    ** area and initialize it (name, I/O address, next device
+		    ** (NULL) and initialisation probe routine).
+		    */
+		    tmp->name = (char *)(tmp + sizeof(struct device));
+		    if (num_eth > 9999) {
+			sprintf(tmp->name,"eth????");
+		    } else {               /* New device name */
+			sprintf(tmp->name,"eth%d", num_eth);
+		    }
+		    tmp->base_addr = 0;    /* re-insert the io address */
+		    tmp->next = NULL;      /* mark the end of list */
+		    tmp->init = init;      /* initialisation routine */
+		}
+	    } else {                       /* structure already exists */
+		tmp->base_addr = 0;        /* re-insert the io address */
 	    }
-	    tmp->base_addr = 0;                /* re-insert the io address */
-	    tmp->next = NULL;                  /* mark the end of list */
-	    tmp->init = init;                  /* initialisation routine */
-	  }
-	} else {                               /* structure already exists */
-	  tmp->base_addr = 0;                  /* re-insert the io address */
-	}
-      }
-    }
-  } else {
-    ret = dev;
-  }
-
-  return ret;
+	}
+    }
+    
+    return ret;
 }
 
 /*
 ** Auto configure the media here rather than setting the port at compile
-** time. This routine is called by de4x5_init() when a loss of media is
+** time. This routine is called by de4x5_init() and when a loss of media is
 ** detected (excessive collisions, loss of carrier, no carrier or link fail
-** [TP]) to check whether the user has been sneaky and changed the port on us.
+** [TP] or no recent receive activity) to check whether the user has been 
+** sneaky and changed the port on us.
 */
 static int autoconf_media(struct device *dev)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-
-  lp->tx_enable = YES;
-  if (de4x5_debug > 0 ) {
-    if (lp->chipset != DC21140) {
-      printk("%s: Searching for media... ",dev->name);
-    } else {
-      printk("%s: Searching for mode... ",dev->name);
-    }
-  }
-
-  if (lp->chipset == DC21040) {
-    lp->media = (lp->autosense == AUTO ? TP : lp->autosense);
-    dc21040_autoconf(dev);
-  } else if (lp->chipset == DC21041) {
-    lp->media = (lp->autosense == AUTO ? TP_NW : lp->autosense);
-    dc21041_autoconf(dev);
-  } else if (lp->chipset == DC21140) {
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    int next_tick = DE4X5_AUTOSENSE_MS;;
+    
+    lp->linkOK = 0;
+    lp->c_media = AUTO;                     /* Bogus last media */
     disable_ast(dev);
-    lp->media = (lp->autosense == AUTO ? _10Mb : lp->autosense);
-    dc21140_autoconf(dev);
-  }
-
-  if (de4x5_debug > 0 ) {
-    if (lp->chipset != DC21140) {
-      printk("media is %s\n", (lp->media == NC  ? "unconnected!" :
-			      (lp->media == TP  ? "TP." :
-			      (lp->media == ANS ? "TP/Nway." :
-			      (lp->media == BNC ? "BNC." : 
-			      (lp->media == AUI ? "AUI." : 
-				                  "BNC/AUI."
-			      ))))));
-    } else {
-      printk("mode is %s\n",(lp->media == NC      ? "link down.":
-			    (lp->media == _100Mb  ? "100Mb/s." :
-			    (lp->media == _10Mb   ? "10Mb/s." :
-				                    "\?\?\?"
-			    ))));
-    }
-  }
-
-  if (lp->media) {
-    lp->lostMedia = 0;
     inl(DE4X5_MFC);                         /* Zero the lost frames counter */
-    if ((lp->media == TP) || (lp->media == ANS)) {
-      lp->irq_mask |= IMR_LFM;
+    lp->media = INIT;
+    if (lp->chipset == DC21040) {
+	next_tick = dc21040_autoconf(dev);
+    } else if (lp->chipset == DC21041) {
+	next_tick = dc21041_autoconf(dev);
+    } else if (lp->chipset == DC21140) {
+	next_tick = dc21140m_autoconf(dev);
     }
-  }
-  dce_ms_delay(10);
-
-  return (lp->media);
+    if (lp->autosense == AUTO) enable_ast(dev, next_tick);
+    
+    return (lp->media);
 }
 
-static void dc21040_autoconf(struct device *dev)
-{
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-  int i, linkBad;
-  s32 sisr = 0, t_3s    = 3000;
-
-  switch (lp->media) {
-  case TP:
-    reset_init_sia(dev, 0x8f01, 0xffff, 0x0000);
-    for (linkBad=1,i=0;(i<t_3s) && linkBad && !(sisr & SISR_NCR);i++) {
-      if (((sisr = inl(DE4X5_SISR)) & SISR_LKF) == 0) linkBad = 0;
-      dce_ms_delay(1);
-    }
-    if (linkBad && (lp->autosense == AUTO)) {
-      lp->media = BNC_AUI;
-      dc21040_autoconf(dev);
-    }
-    break;
-
-  case BNC:
-  case AUI:
-  case BNC_AUI:
-    reset_init_sia(dev, 0x8f09, 0x0705, 0x0006);
-    dce_ms_delay(500);
-    linkBad = ping_media(dev);
-    if (linkBad && (lp->autosense == AUTO)) {
-      lp->media = EXT_SIA;
-      dc21040_autoconf(dev);
-    }
-    break;
-
-  case EXT_SIA:
-    reset_init_sia(dev, 0x3041, 0x0000, 0x0006);
-    dce_ms_delay(500);
-    linkBad = ping_media(dev);
-    if (linkBad && (lp->autosense == AUTO)) {
-      lp->media = NC;
-      dc21040_autoconf(dev);
-    }
-    break;
-
-  case NC:
+/*
+** Autoconfigure the media when using the DC21040. AUI cannot be distinguished
+** from BNC as the port has a jumper to set thick or thin wire. When set for
+** BNC, the BNC port will indicate activity if it's not terminated correctly.
+** The only way to test for that is to place a loopback packet onto the
+** network and watch for errors. Since we're messing with the interrupt mask
+** register, disable the board interrupts and do not allow any more packets to
+** be queued to the hardware. Re-enable everything only when the media is
+** found.
+** I may have to "age out" locally queued packets so that the higher layer
+** timeouts don't effectively duplicate packets on the network.
+*/
+static int dc21040_autoconf(struct device *dev)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    int next_tick = DE4X5_AUTOSENSE_MS;
+    s32 imr;
+    
+    switch (lp->media) {
+      case INIT:
+	DISABLE_IRQs;
+	lp->tx_enable = NO;
+	lp->timeout = -1;
+	de4x5_save_skbs(dev);
+	if ((lp->autosense == AUTO) || (lp->autosense == TP)) {
+	    lp->media = TP;
+	} else if ((lp->autosense == BNC) || (lp->autosense == AUI) || (lp->autosense == BNC_AUI)) {
+	    lp->media = BNC_AUI;
+	} else if (lp->autosense == EXT_SIA) {
+	    lp->media = EXT_SIA;
+	} else {
+	    lp->media = NC;
+	}
+	lp->local_state = 0;
+	next_tick = dc21040_autoconf(dev);
+	break;
+	
+      case TP:
+	dc21040_state(dev, 0x8f01, 0xffff, 0x0000, 3000, BNC_AUI, 
+		                                         TP_SUSPECT, test_tp);
+	break;
+	
+      case TP_SUSPECT:
+	de4x5_suspect_state(dev, 1000, TP, test_tp, dc21040_autoconf);
+	break;
+	
+      case BNC:
+      case AUI:
+      case BNC_AUI:
+	dc21040_state(dev, 0x8f09, 0x0705, 0x0006, 3000, EXT_SIA, 
+		                                  BNC_AUI_SUSPECT, ping_media);
+	break;
+	
+      case BNC_AUI_SUSPECT:
+	de4x5_suspect_state(dev, 1000, BNC_AUI, ping_media, dc21040_autoconf);
+	break;
+	
+      case EXT_SIA:
+	dc21040_state(dev, 0x3041, 0x0000, 0x0006, 3000, 
+		                              NC, EXT_SIA_SUSPECT, ping_media);
+	break;
+	
+      case EXT_SIA_SUSPECT:
+	de4x5_suspect_state(dev, 1000, EXT_SIA, ping_media, dc21040_autoconf);
+	break;
+	
+      case NC:
 #ifndef __alpha__
-    reset_init_sia(dev, 0x8f01, 0xffff, 0x0000);
-    break;
+	reset_init_sia(dev, 0x8f01, 0xffff, 0x0000);
 #else
-    /* JAE: for Alpha, default to BNC/AUI, *not* TP */
-    reset_init_sia(dev, 0x8f09, 0x0705, 0x0006);
+	/* JAE: for Alpha, default to BNC/AUI, *not* TP */
+	reset_init_sia(dev, 0x8f09, 0x0705, 0x0006);
 #endif  /* i386 */
-  }
-
-  return;
+	de4x5_dbg_media(dev);
+	lp->media = INIT;
+	lp->tx_enable = NO;
+	break;
+    }
+    
+    return next_tick;
 }
 
-/*
-** Autoconfigure the media when using the DC21041. AUI needs to be tested
-** before BNC, because the BNC port will indicate activity if it's not
-** terminated correctly. The only way to test for that is to place a loopback
-** packet onto the network and watch for errors.
-*/
-static void dc21041_autoconf(struct device *dev)
+static int
+dc21040_state(struct device *dev, int csr13, int csr14, int csr15, int timeout,
+	      int next_state, int suspect_state, 
+	      int (*fn)(struct device *, int))
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-  s32 sts, irqs, irq_mask, omr;
-
-  switch (lp->media) {
-  case TP_NW:
-    omr = inl(DE4X5_OMR);        /* Set up full duplex for the autonegotiate */
-    outl(omr | OMR_FD, DE4X5_OMR);
-    irqs = STS_LNF | STS_LNP;
-    irq_mask = IMR_LFM | IMR_LPM;
-    sts = test_media(dev, irqs, irq_mask, 0xef01, 0xffff, 0x0008, 2400);
-    if (sts & STS_LNP) {
-      lp->media = ANS;
-    } else {
-      lp->media = AUI;
-    }
-    dc21041_autoconf(dev);
-    break;
-
-  case ANS:
-    irqs = STS_LNP;
-    irq_mask = IMR_LPM;
-    sts = test_ans(dev, irqs, irq_mask, 3000);
-    if (!(sts & STS_LNP) && (lp->autosense == AUTO)) {
-      lp->media = TP;
-      dc21041_autoconf(dev);
-    }
-    break;
-
-  case TP:
-    omr = inl(DE4X5_OMR);                      /* Set up half duplex for TP */
-    outl(omr & ~OMR_FD, DE4X5_OMR);
-    irqs = STS_LNF | STS_LNP;
-    irq_mask = IMR_LFM | IMR_LPM;
-    sts = test_media(dev, irqs, irq_mask, 0xef01, 0xff3f, 0x0008, 2400);
-    if (!(sts & STS_LNP) && (lp->autosense == AUTO)) {
-      if (inl(DE4X5_SISR) & SISR_NRA) {    /* Non selected port activity */
-	lp->media = AUI;
-      } else {
-	lp->media = BNC;
-      }
-      dc21041_autoconf(dev);
-    }
-    break;
-
-  case AUI:
-    omr = inl(DE4X5_OMR);                      /* Set up half duplex for AUI */
-    outl(omr & ~OMR_FD, DE4X5_OMR);
-    irqs = 0;
-    irq_mask = 0;
-    sts = test_media(dev, irqs, irq_mask, 0xef09, 0xf7fd, 0x000e, 1000);
-    if (!(inl(DE4X5_SISR) & SISR_SRA) && (lp->autosense == AUTO)) {
-      lp->media = BNC;
-      dc21041_autoconf(dev);
-    }
-    break;
-
-  case BNC:
-    omr = inl(DE4X5_OMR);                      /* Set up half duplex for BNC */
-    outl(omr & ~OMR_FD, DE4X5_OMR);
-    irqs = 0;
-    irq_mask = 0;
-    sts = test_media(dev, irqs, irq_mask, 0xef09, 0xf7fd, 0x0006, 1000);
-    if (!(inl(DE4X5_SISR) & SISR_SRA) && (lp->autosense == AUTO)) {
-      lp->media = NC;
-    } else {                                   /* Ensure media connected */
-      if (ping_media(dev)) lp->media = NC;
-    }
-    break;
-
-  case NC:
-    omr = inl(DE4X5_OMR);        /* Set up full duplex for the autonegotiate */
-    outl(omr | OMR_FD, DE4X5_OMR);
-    reset_init_sia(dev, 0xef01, 0xffff, 0x0008);/* Initialise the SIA */
-    break;
-  }
-
-  return;
-}
-
-/*
-** Reduced feature version (temporary I hope)
-*/
-static void dc21140_autoconf(struct device *dev)
-{
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-  s32 omr;
-
-  switch(lp->media) {
-  case _100Mb:      /* Set 100Mb/s, MII Port with PCS Function and Scrambler */
-    omr = (inl(DE4X5_OMR) & ~(OMR_PS | OMR_HBD | OMR_TTM | OMR_PCS | OMR_SCR));
-    omr |= (de4x5_full_duplex ? OMR_FD : 0);   /* Set up Full Duplex */
-    outl(omr | OMR_PS | OMR_HBD | OMR_PCS | OMR_SCR, DE4X5_OMR);
-    outl(GEP_FDXD | GEP_MODE, DE4X5_GEP);
-    break;
-
-  case _10Mb:       /* Set conventional 10Mb/s ENDEC interface */
-    omr = (inl(DE4X5_OMR) & ~(OMR_PS | OMR_HBD | OMR_TTM | OMR_PCS | OMR_SCR));
-    omr |= (de4x5_full_duplex ? OMR_FD : 0);   /* Set up Full Duplex */
-    outl(omr | OMR_TTM, DE4X5_OMR);
-    outl(GEP_FDXD, DE4X5_GEP);
-    break;
-  }
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    int next_tick = DE4X5_AUTOSENSE_MS;
+    int linkBad;
 
-  return;
+    switch (lp->local_state) {
+      case 0:
+	reset_init_sia(dev, csr13, csr14, csr15);
+	lp->local_state++;
+	next_tick = 500;
+	break;
+	    
+      case 1:
+	if (!lp->tx_enable) {
+	    linkBad = fn(dev, timeout);
+	    if (linkBad < 0) {
+		next_tick = linkBad & ~TIMER_CB;
+	    } else {
+		if (linkBad && (lp->autosense == AUTO)) {
+		    lp->local_state = 0;
+		    lp->media = next_state;
+		} else {
+		    de4x5_init_connection(dev);
+		}
+	    }
+	} else if (!lp->linkOK && (lp->autosense == AUTO)) {
+	    lp->media = suspect_state;
+	    next_tick = 3000;
+	}
+	break;
+    }
+    
+    return next_tick;
 }
 
 static int
-test_media(struct device *dev, s32 irqs, s32 irq_mask, s32 csr13, s32 csr14, s32 csr15, s32 msec)
+de4x5_suspect_state(struct device *dev, int timeout, int prev_state,
+		      int (*fn)(struct device *, int),
+		      int (*asfn)(struct device *))
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-  s32 sts, time, csr12;
-
-  reset_init_sia(dev, csr13, csr14, csr15);
-
-  /* Set link_fail_inhibit_timer */
-  load_ms_timer(dev, msec);
-
-  /* clear all pending interrupts */
-  sts = inl(DE4X5_STS);
-  outl(sts, DE4X5_STS);
-
-  /* clear csr12 NRA and SRA bits */
-  csr12 = inl(DE4X5_SISR);
-  outl(csr12, DE4X5_SISR);
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    int next_tick = DE4X5_AUTOSENSE_MS;
+    int linkBad;
 
-  /* Poll for timeout - timer interrupt doesn't work correctly */
-  do {
-    time = inl(DE4X5_GPT) & GPT_VAL;
-    sts = inl(DE4X5_STS);
-  } while ((time != 0) && !(sts & irqs));
+    switch (lp->local_state) {
+      case 1:
+	if (lp->linkOK && !LOST_MEDIA) {
+	    lp->media = prev_state;
+	} else {
+	    lp->local_state++;
+	    next_tick = asfn(dev);
+	}
+	break;
 
-  sts = inl(DE4X5_STS);
+      case 2:
+	linkBad = fn(dev, timeout);
+	if (linkBad < 0) {
+	    next_tick = linkBad & ~TIMER_CB;
+	} else if (!linkBad) {
+	    lp->local_state--;
+	    lp->media = prev_state;
+	} else {
+	    lp->media = INIT;
+	}
+    }
 
-  return sts;
+    return next_tick;
 }
-/*
-static int test_sym_link(struct device *dev, u32 msec)
-{
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-  u32 gep, time;
-
-  / * Set link_fail_inhibit_timer * /
-  load_ms_timer(dev, msec);
 
-  / * Poll for timeout or SYM_LINK=0 * /
-  do {
-    time = inl(DE4X5_GPT) & GPT_VAL;
-    gep = inl(DE4X5_GEP) & (GEP_SLNK | GEP_LNP);
-  } while ((time > 0) && (gep & GEP_SLNK));
-
-  return gep;
-}
-*/
 /*
-** Send a packet onto the media and watch for send errors that indicate the
-** media is bad or unconnected.
+** Autoconfigure the media when using the DC21041. AUI needs to be tested
+** before BNC, because the BNC port will indicate activity if it's not
+** terminated correctly. The only way to test for that is to place a loopback
+** packet onto the network and watch for errors. Since we're messing with
+** the interrupt mask register, disable the board interrupts and do not allow
+** any more packets to be queued to the hardware. Re-enable everything only
+** when the media is found.
 */
-static int ping_media(struct device *dev)
+static int dc21041_autoconf(struct device *dev)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-  int i, entry, linkBad;
-  s32 omr, t_3s = 4000;
-  char frame[64];
-
-  create_packet(dev, frame, sizeof(frame));
-
-  entry = lp->tx_new;                        /* Remember the ring position */
-  load_packet(dev, frame, TD_LS | TD_FS | sizeof(frame),NULL);
-
-  omr = inl(DE4X5_OMR);
-  outl(omr|OMR_ST, DE4X5_OMR);
-
-  lp->tx_new = (++lp->tx_new) % lp->txRingSize;
-  lp->tx_old = lp->tx_new;
-
-  /* Poll for completion of frame (interrupts are disabled for now)... */
-  for (linkBad=1,i=0;(i<t_3s) && linkBad;i++) {
-    if ((inl(DE4X5_SISR) & SISR_NCR) == 1) break;
-    if (lp->tx_ring[entry].status >= 0) linkBad=0;
-    dce_ms_delay(1);
-  }
-  outl(omr, DE4X5_OMR); 
-
-  return ((linkBad || (lp->tx_ring[entry].status & TD_ES)) ? 1 : 0);
-}
-
-/*
-** Check the Auto Negotiation State. Return OK when a link pass interrupt
-** is received and the auto-negotiation status is NWAY OK.
-*/
-static int test_ans(struct device *dev, s32 irqs, s32 irq_mask, s32 msec)
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    s32 sts, irqs, irq_mask, imr, omr;
+    int next_tick = DE4X5_AUTOSENSE_MS;
+    
+    switch (lp->media) {
+      case INIT:
+	DISABLE_IRQs;
+	lp->tx_enable = NO;
+	lp->timeout = -1;
+	de4x5_save_skbs(dev);          /* Save non transmitted skb's */
+	if ((lp->autosense == AUTO) || (lp->autosense == TP_NW)) {
+	    lp->media = TP;            /* On chip auto negotiation is broken */
+	} else if (lp->autosense == TP) {
+	    lp->media = TP;
+	} else if (lp->autosense == BNC) {
+	    lp->media = BNC;
+	} else if (lp->autosense == AUI) {
+	    lp->media = AUI;
+	} else {
+	    lp->media = NC;
+	}
+	lp->local_state = 0;
+	next_tick = dc21041_autoconf(dev);
+	break;
+	
+      case TP_NW:
+	if (lp->timeout < 0) {
+	    omr = inl(DE4X5_OMR);/* Set up full duplex for the autonegotiate */
+	    outl(omr | OMR_FD, DE4X5_OMR);
+	}
+	irqs = STS_LNF | STS_LNP;
+	irq_mask = IMR_LFM | IMR_LPM;
+	sts = test_media(dev, irqs, irq_mask, 0xef01, 0xffff, 0x0008, 2400);
+	if (sts < 0) {
+	    next_tick = sts & ~TIMER_CB;
+	} else {
+	    if (sts & STS_LNP) {
+		lp->media = ANS;
+	    } else {
+		lp->media = AUI;
+	    }
+	    next_tick = dc21041_autoconf(dev);
+	}
+	break;
+	
+      case ANS:
+	if (!lp->tx_enable) {
+	    irqs = STS_LNP;
+	    irq_mask = IMR_LPM;
+	    sts = test_ans(dev, irqs, irq_mask, 3000);
+	    if (sts < 0) {
+		next_tick = sts & ~TIMER_CB;
+	    } else {
+		if (!(sts & STS_LNP) && (lp->autosense == AUTO)) {
+		    lp->media = TP;
+		    next_tick = dc21041_autoconf(dev);
+		} else {
+		    de4x5_init_connection(dev);
+		}
+	    }
+	} else if (!lp->linkOK && (lp->autosense == AUTO)) {
+	    lp->media = ANS_SUSPECT;
+	    next_tick = 3000;
+	}
+	break;
+	
+      case ANS_SUSPECT:
+	de4x5_suspect_state(dev, 1000, ANS, test_tp, dc21041_autoconf);
+	break;
+	
+      case TP:
+	if (!lp->tx_enable) {
+	    if (lp->timeout < 0) {
+		omr = inl(DE4X5_OMR);          /* Set up half duplex for TP */
+		outl(omr & ~OMR_FD, DE4X5_OMR);
+	    }
+	    irqs = STS_LNF | STS_LNP;
+	    irq_mask = IMR_LFM | IMR_LPM;
+	    sts = test_media(dev,irqs, irq_mask, 0xef01, 0xff3f, 0x0008, 2400);
+	    if (sts < 0) {
+		next_tick = sts & ~TIMER_CB;
+	    } else {
+		if (!(sts & STS_LNP) && (lp->autosense == AUTO)) {
+		    if (inl(DE4X5_SISR) & SISR_NRA) {
+			lp->media = AUI;       /* Non selected port activity */
+		    } else {
+			lp->media = BNC;
+		    }
+		    next_tick = dc21041_autoconf(dev);
+		} else {
+		    de4x5_init_connection(dev);
+		}
+	    }
+	} else if (!lp->linkOK && (lp->autosense == AUTO)) {
+	    lp->media = TP_SUSPECT;
+	    next_tick = 3000;
+	}
+	break;
+	
+      case TP_SUSPECT:
+	de4x5_suspect_state(dev, 1000, TP, test_tp, dc21041_autoconf);
+	break;
+	
+      case AUI:
+	if (!lp->tx_enable) {
+	    if (lp->timeout < 0) {
+		omr = inl(DE4X5_OMR);          /* Set up half duplex for AUI */
+		outl(omr & ~OMR_FD, DE4X5_OMR);
+	    }
+	    irqs = 0;
+	    irq_mask = 0;
+	    sts = test_media(dev,irqs, irq_mask, 0xef09, 0xf7fd, 0x000e, 1000);
+	    if (sts < 0) {
+		next_tick = sts & ~TIMER_CB;
+	    } else {
+		if (!(inl(DE4X5_SISR) & SISR_SRA) && (lp->autosense == AUTO)) {
+		    lp->media = BNC;
+		    next_tick = dc21041_autoconf(dev);
+		} else {
+		    de4x5_init_connection(dev);
+		}
+	    }
+	} else if (!lp->linkOK && (lp->autosense == AUTO)) {
+	    lp->media = AUI_SUSPECT;
+	    next_tick = 3000;
+	}
+	break;
+	
+      case AUI_SUSPECT:
+	de4x5_suspect_state(dev, 1000, AUI, ping_media, dc21041_autoconf);
+	break;
+	
+      case BNC:
+	switch (lp->local_state) {
+	  case 0:
+	    if (lp->timeout < 0) {
+		omr = inl(DE4X5_OMR);          /* Set up half duplex for BNC */
+		outl(omr & ~OMR_FD, DE4X5_OMR);
+	    }
+	    irqs = 0;
+	    irq_mask = 0;
+	    sts = test_media(dev,irqs, irq_mask, 0xef09, 0xf7fd, 0x0006, 1000);
+	    if (sts < 0) {
+		next_tick = sts & ~TIMER_CB;
+	    } else {
+		if (!(inl(DE4X5_SISR) & SISR_SRA) && (lp->autosense == AUTO)) {
+		    lp->media = NC;
+		} else {
+		    lp->local_state++;         /* Ensure media connected */
+		    next_tick = dc21041_autoconf(dev);
+		}
+	    }
+	    break;
+	    
+	  case 1:
+	    if (!lp->tx_enable) {
+		if ((sts = ping_media(dev, 3000)) < 0) {
+		    next_tick = sts & ~TIMER_CB;
+		} else {
+		    if (sts) {
+			lp->local_state = 0;
+			lp->media = NC;
+		    } else {
+			de4x5_init_connection(dev);
+		    }
+		}
+	    } else if (!lp->linkOK && (lp->autosense == AUTO)) {
+		lp->media = BNC_SUSPECT;
+		next_tick = 3000;
+	    }
+	    break;
+	}
+	break;
+	
+      case BNC_SUSPECT:
+	de4x5_suspect_state(dev, 1000, BNC, ping_media, dc21041_autoconf);
+	break;
+	
+      case NC:
+	omr = inl(DE4X5_OMR);    /* Set up full duplex for the autonegotiate */
+	outl(omr | OMR_FD, DE4X5_OMR);
+	reset_init_sia(dev, 0xef01, 0xffff, 0x0008);/* Initialise the SIA */
+	de4x5_dbg_media(dev);
+	lp->media = INIT;
+	lp->tx_enable = NO;
+	break;
+    }
+    
+    return next_tick;
+}
+
+static int dc21140m_autoconf(struct device *dev)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    int ana, anlpa, cap, cr, sr, iobase = dev->base_addr;
+    int next_tick = DE4X5_AUTOSENSE_MS;
+    u_long imr, omr;
+    
+    switch(lp->media) {
+      case INIT: 
+	DISABLE_IRQs;
+	lp->tx_enable = FALSE;
+	lp->timeout = -1;
+	if ((next_tick = de4x5_reset_phy(dev)) < 0) {
+	    next_tick &= ~TIMER_CB;
+	} else {
+	    de4x5_save_skbs(dev);          /* Save non transmitted skb's */
+	    SET_10Mb;
+	    if (lp->autosense == _100Mb) {
+		lp->media = _100Mb;
+	    } else if (lp->autosense == _10Mb) {
+		lp->media = _10Mb;
+	    } else if ((lp->autosense == AUTO) && (sr=is_anc_capable(dev))) {
+		ana = (((sr >> 6) & MII_ANA_TAF) | MII_ANA_CSMA);
+		ana &= (de4x5_full_duplex ? ~0 : ~MII_ANA_FDAM);
+		mii_wr(ana, MII_ANA, lp->phy[lp->active].addr, DE4X5_MII);
+		lp->media = ANS;
+	    } else if (lp->autosense == AUTO) {
+		lp->media = SPD_DET;
+	    } else if (is_spd_100(dev) && is_100_up(dev)) {
+		lp->media = _100Mb;
+	    } else {
+		lp->media = NC;
+	    }
+	    lp->local_state = 0;
+	    next_tick = dc21140m_autoconf(dev);
+	}
+	break;
+	
+      case ANS:
+	switch (lp->local_state) {
+	  case 0:
+	    if (lp->timeout < 0) {
+		mii_wr(MII_CR_ASSE | MII_CR_RAN, MII_CR, lp->phy[lp->active].addr, DE4X5_MII);
+	    }
+	    cr = test_mii_reg(dev, MII_CR, MII_CR_RAN, FALSE, 500);
+	    if (cr < 0) {
+		next_tick = cr & ~TIMER_CB;
+	    } else {
+		if (cr) {
+		    lp->local_state = 0;
+		    lp->media = SPD_DET;
+		} else {
+		    lp->local_state++;
+		}
+		next_tick = dc21140m_autoconf(dev);
+	    }
+	    break;
+	    
+	  case 1:
+	    if ((sr=test_mii_reg(dev, MII_SR, MII_SR_ASSC, TRUE, 3000)) < 0) {
+		next_tick = sr & ~TIMER_CB;
+	    } else {
+		lp->media = SPD_DET;
+		lp->local_state = 0;
+		if (sr) {                         /* Success! */
+		    anlpa = mii_rd(MII_ANLPA, lp->phy[lp->active].addr, DE4X5_MII);
+		    ana = mii_rd(MII_ANA, lp->phy[lp->active].addr, DE4X5_MII);
+		    if ((anlpa & MII_ANLPA_ACK) && !(anlpa & MII_ANLPA_RF) &&
+			(cap = anlpa & MII_ANLPA_TAF & ana)) {
+			if (cap & MII_ANA_100M) {
+			    de4x5_full_duplex = ((ana & anlpa & MII_ANA_FDAM & MII_ANA_100M) ? TRUE : FALSE);
+			    lp->media = _100Mb;
+			} else if (cap & MII_ANA_10M) {
+			    de4x5_full_duplex = ((ana & anlpa & MII_ANA_FDAM & MII_ANA_10M) ? TRUE : FALSE);
+			    lp->media = _10Mb;
+			}
+		    }
+		}                       /* Auto Negotiation failed to finish */
+		next_tick = dc21140m_autoconf(dev);
+	    }                           /* Auto Negotiation failed to start */
+	    break;
+	}
+	break;
+	
+      case SPD_DET:                              /* Choose 10Mb/s or 100Mb/s */
+	if (!lp->phy[lp->active].id) {
+	    outl(GEP_FDXD | GEP_MODE, DE4X5_GEP);
+	}
+	if (is_spd_100(dev) && is_100_up(dev)) {
+	    lp->media = _100Mb;
+	} else if (!is_spd_100(dev) && is_10_up(dev)) {
+	    lp->media = _10Mb;
+	} else {
+	    lp->media = NC;
+	}
+	next_tick = dc21140m_autoconf(dev);
+	break;
+	
+      case _100Mb:                               /* Set 100Mb/s */
+	next_tick = 3000;
+	if (!lp->tx_enable) {
+	    SET_100Mb;
+	    de4x5_init_connection(dev);
+	} else {
+	    if (!lp->linkOK && (lp->autosense == AUTO)) {
+		if (!(is_spd_100(dev) && is_100_up(dev))) {
+		    lp->media = INIT;
+		    next_tick = DE4X5_AUTOSENSE_MS;
+		}
+	    }
+	}
+	break;
+	
+      case _10Mb:                                /* Set 10Mb/s */
+	next_tick = 3000;
+	if (!lp->tx_enable) {
+	    SET_10Mb;
+	    de4x5_init_connection(dev);
+	} else {
+	    if (!lp->linkOK && (lp->autosense == AUTO)) {
+		if (!(!is_spd_100(dev) && is_10_up(dev))) {
+		    lp->media = INIT;
+		    next_tick = DE4X5_AUTOSENSE_MS;
+		}
+	    }
+	}
+	break;
+	
+      case NC:
+	SET_10Mb;
+	de4x5_dbg_media(dev);
+	lp->media = INIT;
+	lp->tx_enable = FALSE;
+	break;
+    }
+    
+    return next_tick;
+}
+
+static void de4x5_init_connection(struct device *dev)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-  s32 sts, ans;
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
 
-  outl(irq_mask, DE4X5_IMR);
+    de4x5_dbg_media(dev);
+    de4x5_restore_skbs(dev);
+    cli();
+    de4x5_rx(dev);
+    de4x5_setup_intr(dev);
+    lp->lostMedia = 0;
+    lp->tx_enable = YES;
+    sti();
+    outl(POLL_DEMAND, DE4X5_TPD);
 
-  /* Set timeout limit */
-  load_ms_timer(dev, msec);
+    return;
+}
 
-  /* clear all pending interrupts */
-  sts = inl(DE4X5_STS);
-  outl(sts, DE4X5_STS);
+static int de4x5_reset_phy(struct device *dev)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    int next_tick = 0;
 
-  /* Poll for interrupts */
-  do {
-    ans = inl(DE4X5_SISR) & SISR_ANS;
-    sts = inl(DE4X5_STS);
-  } while (!(sts & irqs) && (ans ^ ANS_NWOK) != 0);
+    if (lp->phy[lp->active].id) {
+	if (lp->timeout < 0) {
+	    outl(GEP_HRST, DE4X5_GEP);           /* Hard RESET the PHY dev. */
+	    udelay(1000);                        /* Assert for 1ms */
+	    outl(0x00, DE4X5_GEP);
+	    udelay(2000);                        /* Wait for 2ms */
+	    mii_wr(MII_CR_RST, MII_CR, lp->phy[lp->active].addr, DE4X5_MII);
+	}
+	next_tick = test_mii_reg(dev, MII_CR, MII_CR_RST, FALSE, 500);
+    }
+    
+    return next_tick;
+}
+
+static int
+test_media(struct device *dev, s32 irqs, s32 irq_mask, s32 csr13, s32 csr14, s32 csr15, s32 msec)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    s32 sts, csr12;
+    
+    if (lp->timeout < 0) {
+	lp->timeout = msec/100;
+	reset_init_sia(dev, csr13, csr14, csr15);
+
+	/* set up the interrupt mask */
+	outl(irq_mask, DE4X5_IMR);
+
+	/* clear all pending interrupts */
+	sts = inl(DE4X5_STS);
+	outl(sts, DE4X5_STS);
+	
+	/* clear csr12 NRA and SRA bits */
+	if (lp->chipset == DC21041) {
+	    csr12 = inl(DE4X5_SISR);
+	    outl(csr12, DE4X5_SISR);
+	}
+    }
+    
+    sts = inl(DE4X5_STS) & ~TIMER_CB;
+    
+    if (!(sts & irqs) && --lp->timeout) {
+	sts = 100 | TIMER_CB;
+    } else {
+	lp->timeout = -1;
+    }
+    
+    return sts;
+}
 
-  return ((sts & STS_LNP) && ((ans ^ ANS_NWOK) == 0) ? STS_LNP : 0);
+static int test_tp(struct device *dev, s32 msec)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    int sisr;
+    
+    if (lp->timeout < 0) {
+	lp->timeout = msec/100;
+    }
+    
+    sisr = (inl(DE4X5_SISR) & ~TIMER_CB) & (SISR_LKF | SISR_NCR);
+
+    if (sisr && --lp->timeout) {
+	sisr = 100 | TIMER_CB;
+    } else {
+	lp->timeout = -1;
+    }
+    
+    return sisr;
 }
 
 /*
 **
+**
 */
-static void reset_init_sia(struct device *dev, s32 sicr, s32 strr, s32 sigr)
+static int test_mii_reg(struct device *dev, int reg, int mask, int pol, long msec)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    int test, iobase = dev->base_addr;
+    
+    if (lp->timeout < 0) {
+	lp->timeout = msec/100;
+    }
+    
+    if (pol) pol = ~0;
+    reg = mii_rd(reg, lp->phy[lp->active].addr, DE4X5_MII) & mask;
+    test = (reg ^ pol) & mask;
+    
+    if (test && --lp->timeout) {
+	reg = 100 | TIMER_CB;
+    } else {
+	lp->timeout = -1;
+    }
+    
+    return reg;
+}
+
+static int is_spd_100(struct device *dev)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    int spd;
+    
+    if (lp->phy[lp->active].id) {
+	spd = mii_rd(lp->phy[lp->active].spd.reg, lp->phy[lp->active].addr, DE4X5_MII);
+	spd = ~(spd ^ lp->phy[lp->active].spd.value);
+	spd &= lp->phy[lp->active].spd.mask;
+    } else {
+	spd = ((~inl(DE4X5_GEP)) & GEP_SLNK);
+    }
+    
+    return spd;
+}
+
+static int is_100_up(struct device *dev)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    
+    if (lp->phy[lp->active].id) {
+	/* Double read for sticky bits & temporary drops */
+	mii_rd(MII_SR, lp->phy[lp->active].addr, DE4X5_MII);
+	return (mii_rd(MII_SR, lp->phy[lp->active].addr, DE4X5_MII) & MII_SR_LKS);
+    } else {
+	return ((~inl(DE4X5_GEP)) & GEP_SLNK);
+    }
+}
+
+static int is_10_up(struct device *dev)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    
+    if (lp->phy[lp->active].id) {
+	/* Double read for sticky bits & temporary drops */
+	mii_rd(MII_SR, lp->phy[lp->active].addr, DE4X5_MII);
+	return (mii_rd(MII_SR, lp->phy[lp->active].addr, DE4X5_MII) & MII_SR_LKS);
+    } else {
+	return ((~inl(DE4X5_GEP)) & GEP_LNP);
+    }
+}
+
+static int is_anc_capable(struct device *dev)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    
+    if (lp->phy[lp->active].id) {
+	return (mii_rd(MII_SR, lp->phy[lp->active].addr, DE4X5_MII) & MII_SR_ANC);
+    } else {
+	return 0;
+    }
+}
 
-  RESET_SIA;
-  outl(sigr, DE4X5_SIGR);
-  outl(strr, DE4X5_STRR);
-  outl(sicr, DE4X5_SICR);
+/*
+** Send a packet onto the media and watch for send errors that indicate the
+** media is bad or unconnected.
+*/
+static int ping_media(struct device *dev, int msec)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    int sisr;
+    
+    if (lp->timeout < 0) {
+	lp->timeout = msec/100;
+	
+	lp->tmp = lp->tx_new;                /* Remember the ring position */
+	load_packet(dev, lp->frame, TD_LS | TD_FS | sizeof(lp->frame), NULL);
+	lp->tx_new = (++lp->tx_new) % lp->txRingSize;
+	outl(POLL_DEMAND, DE4X5_TPD);
+    }
+    
+    sisr = inl(DE4X5_SISR);
 
-  return;
+    if ((!(sisr & SISR_NCR)) && (lp->tx_ring[lp->tmp].status < 0) && (--lp->timeout)) {
+	sisr = 100 | TIMER_CB;
+    } else {
+	if ((!(sisr & SISR_NCR)) && 
+	    !(lp->tx_ring[lp->tmp].status & (T_OWN | TD_ES)) && lp->timeout) {
+	    sisr = 0;
+	} else {
+	    sisr = 1;
+	}
+	lp->timeout = -1;
+    }
+    
+    return sisr;
 }
 
 /*
-** Load the timer on the DC21041 and 21140. Max time is 13.42 secs.
+** When a user pulls a connection, the DECchip can end up in a
+** 'running - waiting for end of transmission' state. This means that we
+** have to perform a chip soft reset to ensure that we can synchronize
+** the hardware and software and make any media probes using a loopback
+** packet meaningful.
 */
-static void load_ms_timer(struct device *dev, u32 msec)
+static void de4x5_save_skbs(struct device *dev)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-  s32 i = 2048, j;
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    int i;
+    s32 omr;
+
+    if (!lp->cache.save_cnt) {
+	STOP_DE4X5;
+	de4x5_tx(dev);                          /* Flush any sent skb's */
+	for (i=lp->tx_new; i!=lp->tx_old; i--) {
+	    if (lp->skb[i]) {
+		de4x5_putb_cache(dev, lp->skb[i]);
+		lp->skb[i] = NULL;
+	    }
+	    if (i==0) i=lp->txRingSize;
+	}
+        if (lp->skb[i]) {
+	    de4x5_putb_cache(dev, lp->skb[i]);
+	    lp->skb[i] = NULL;
+	}
 
-  if (lp->chipset == DC21140) {
-    j = inl(DE4X5_OMR);
-    if ((j & OMR_TTM) && (j & OMR_PS)) {          /* 10Mb/s MII */
-      i = 8192;
-    } else if ((~j & OMR_TTM) && (j & OMR_PS)) {  /* 100Mb/s MII */
-      i = 819;
+	de4x5_cache_state(dev, DE4X5_SAVE_STATE);
+	de4x5_sw_reset(dev);
+	de4x5_cache_state(dev, DE4X5_RESTORE_STATE);
+	dev->tbusy = 0;
+	lp->cache.save_cnt++;
+	START_DE4X5;
     }
-  }
 
-  outl((s32)(msec * 10000)/i, DE4X5_GPT);
+    return;
+}
+
+static void de4x5_restore_skbs(struct device *dev)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    struct sk_buff *skb;
+    int i;
+    s32 omr;
+
+    if (lp->cache.save_cnt) {
+	STOP_DE4X5;
+	de4x5_cache_state(dev, DE4X5_SAVE_STATE);
+	de4x5_sw_reset(dev);
+	de4x5_cache_state(dev, DE4X5_RESTORE_STATE);
+	dev->tbusy = 1;
+
+	for (i=0; TX_BUFFS_AVAIL && lp->cache.skb; i++) {
+	    skb = de4x5_get_cache(dev);
+	    load_packet(dev, skb->data, TD_IC | TD_LS | TD_FS | skb->len, skb);
+	    lp->tx_new = (++lp->tx_new) % lp->txRingSize;
+	}
+	if (TX_BUFFS_AVAIL) {
+	    dev->tbusy = 0;
+	}
+	lp->cache.save_cnt--;
+	START_DE4X5;
+    }
+        
+    return;
+}
+
+static void de4x5_cache_state(struct device *dev, int flag)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    s32 gep;
 
-  return;
+    switch(flag) {
+      case DE4X5_SAVE_STATE:
+	lp->cache.csr0 = inl(DE4X5_BMR);
+	lp->cache.csr6 = (inl(DE4X5_OMR) & ~(OMR_ST | OMR_SR));
+	lp->cache.csr7 = inl(DE4X5_IMR);
+	if (lp->chipset != DC21140) {
+	    lp->cache.csr13 = inl(DE4X5_SICR);
+	    lp->cache.csr14 = inl(DE4X5_STRR);
+	    lp->cache.csr15 = inl(DE4X5_SIGR);
+	}
+	break;
+
+      case DE4X5_RESTORE_STATE:
+	outl(lp->cache.csr0, DE4X5_BMR);
+	outl(lp->cache.csr6, DE4X5_OMR);
+	outl(lp->cache.csr7, DE4X5_IMR);
+	if (lp->chipset == DC21140) {
+	    outl(GEP_INIT, DE4X5_GEP);
+	    gep = (lp->media == _100Mb ? GEP_MODE : 0);
+	    if (!lp->phy[lp->active].id && !de4x5_full_duplex) {
+		gep |= GEP_FDXD;
+	    }
+	    outl(gep, DE4X5_GEP);
+	} else {
+	    reset_init_sia(dev, lp->cache.csr13, lp->cache.csr14, 
+			                                      lp->cache.csr15);
+	}
+	break;
+    }
+
+    return;
+}
+
+static void de4x5_put_cache(struct device *dev, struct sk_buff *skb)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    struct sk_buff *p;
+
+    if (lp->cache.skb) {
+	for (p=lp->cache.skb; p->next; p=p->next);
+	p->next = skb;
+    } else {
+	lp->cache.skb = skb;
+    }
+    skb->next = NULL;
+
+    return;
+}
+
+static void de4x5_putb_cache(struct device *dev, struct sk_buff *skb)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    struct sk_buff *p = lp->cache.skb;
+
+    lp->cache.skb = skb;
+    skb->next = p;
+
+    return;
+}
+
+static struct sk_buff *de4x5_get_cache(struct device *dev)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    struct sk_buff *p = lp->cache.skb;
+
+    if (p) {
+	lp->cache.skb = p->next;
+	p->next = NULL;
+    }
+
+    return p;
 }
 
 /*
-** Create an Ethernet packet with an invalid CRC
+** Check the Auto Negotiation State. Return OK when a link pass interrupt
+** is received and the auto-negotiation status is NWAY OK.
 */
-static void create_packet(struct device *dev, char *frame, int len)
+static int test_ans(struct device *dev, s32 irqs, s32 irq_mask, s32 msec)
 {
-  int i;
-  char *buf = frame;
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    s32 sts, ans;
+    
+    if (lp->timeout < 0) {
+	lp->timeout = msec/100;
+	outl(irq_mask, DE4X5_IMR);
+	
+	/* clear all pending interrupts */
+	sts = inl(DE4X5_STS);
+	outl(sts, DE4X5_STS);
+    }
+    
+    ans = inl(DE4X5_SISR) & SISR_ANS;
+    sts = inl(DE4X5_STS) & ~TIMER_CB;
+    
+    if (!(sts & irqs) && (ans ^ ANS_NWOK) && --lp->timeout) {
+	sts = 100 | TIMER_CB;
+    } else {
+	lp->timeout = -1;
+    }
+    
+    return sts;
+}
 
-  for (i=0; i<ETH_ALEN; i++) {             /* Use this source address */
-    *buf++ = dev->dev_addr[i];
-  }
-  for (i=0; i<ETH_ALEN; i++) {             /* Use this destination address */
-    *buf++ = dev->dev_addr[i];
-  }
+static void de4x5_setup_intr(struct device *dev)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    s32 imr, sts;
+    
+    if (inl(DE4X5_OMR) & OMR_SR) {   /* Only unmask if TX/RX is enabled */
+	imr = 0;
+	UNMASK_IRQs;
+	sts = inl(DE4X5_STS);        /* Reset any pending (stale) interrupts */
+	outl(sts, DE4X5_STS);
+	ENABLE_IRQs;
+    }
+    
+    return;
+}
 
-  *buf++ = 0;                              /* Packet length (2 bytes) */
-  *buf++ = 1;
-  
-  return;
+/*
+**
+*/
+static void reset_init_sia(struct device *dev, s32 sicr, s32 strr, s32 sigr)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    u_long iobase = dev->base_addr;
+    
+    RESET_SIA;
+    outl(sigr, DE4X5_SIGR);
+    outl(strr, DE4X5_STRR);
+    outl(sicr, DE4X5_SICR);
+
+    return;
 }
 
 /*
-** Known delay in microseconds
+** Create a loopback ethernet packet with an invalid CRC
 */
-static void dce_us_delay(u32 usec)
+static void create_packet(struct device *dev, char *frame, int len)
 {
-  udelay(usec);
+    int i;
+    char *buf = frame;
+    
+    for (i=0; i<ETH_ALEN; i++) {             /* Use this source address */
+	*buf++ = dev->dev_addr[i];
+    }
+    for (i=0; i<ETH_ALEN; i++) {             /* Use this destination address */
+	*buf++ = dev->dev_addr[i];
+    }
+    
+    *buf++ = 0;                              /* Packet length (2 bytes) */
+    *buf++ = 1;
+    
+    return;
+}
 
-  return;
+/*
+** Known delay in microseconds
+*/
+static void de4x5_us_delay(u32 usec)
+{
+    udelay(usec);
+    
+    return;
 }
 
 /*
 ** Known delay in milliseconds, in millisecond steps.
 */
-static void dce_ms_delay(u32 msec)
+static void de4x5_ms_delay(u32 msec)
 {
-  u_int i;
-  
-  for (i=0; i<msec; i++) {
-    dce_us_delay(1000);
-  }
-
-  return;
+    u_int i;
+    
+    for (i=0; i<msec; i++) {
+	de4x5_us_delay(1000);
+    }
+    
+    return;
 }
 
 
@@ -2170,148 +2757,173 @@
 */
 static int EISA_signature(char *name, s32 eisa_id)
 {
-  u_int i;
-  const char *signatures[] = DE4X5_SIGNATURE;
-  char ManCode[DE4X5_STRLEN];
-  union {
-    s32 ID;
-    char Id[4];
-  } Eisa;
-  int status = 0;
-
-  *name = '\0';
-  Eisa.ID = inl(eisa_id);
-
-  ManCode[0]=(((Eisa.Id[0]>>2)&0x1f)+0x40);
-  ManCode[1]=(((Eisa.Id[1]&0xe0)>>5)+((Eisa.Id[0]&0x03)<<3)+0x40);
-  ManCode[2]=(((Eisa.Id[2]>>4)&0x0f)+0x30);
-  ManCode[3]=((Eisa.Id[2]&0x0f)+0x30);
-  ManCode[4]=(((Eisa.Id[3]>>4)&0x0f)+0x30);
-  ManCode[5]='\0';
-
-  for (i=0;(*signatures[i] != '\0') && (*name == '\0');i++) {
-    if (strstr(ManCode, signatures[i]) != NULL) {
-      strcpy(name,ManCode);
-      status = 1;
+    c_char *signatures[] = DE4X5_SIGNATURE;
+    char ManCode[DE4X5_STRLEN];
+    union {
+	s32 ID;
+	char Id[4];
+    } Eisa;
+    int i, status = 0, siglen = sizeof(signatures)/sizeof(c_char *);
+    
+    *name = '\0';
+    Eisa.ID = inl(eisa_id);
+    
+    ManCode[0]=(((Eisa.Id[0]>>2)&0x1f)+0x40);
+    ManCode[1]=(((Eisa.Id[1]&0xe0)>>5)+((Eisa.Id[0]&0x03)<<3)+0x40);
+    ManCode[2]=(((Eisa.Id[2]>>4)&0x0f)+0x30);
+    ManCode[3]=((Eisa.Id[2]&0x0f)+0x30);
+    ManCode[4]=(((Eisa.Id[3]>>4)&0x0f)+0x30);
+    ManCode[5]='\0';
+    
+    for (i=0;i<siglen;i++) {
+	if (strstr(ManCode, signatures[i]) != NULL) {
+	    strcpy(name,ManCode);
+	    status = 1;
+	    break;
+	}
     }
-  }
-
-  return status;                           /* return the device name string */
+    
+    return status;                         /* return the device name string */
 }
 
 /*
-** Look for a special sequence in the Ethernet station address PROM that
-** is common across all DIGITAL network adapter products.
-** 
-** Search the Ethernet address ROM for the signature. Since the ROM address
-** counter can start at an arbitrary point, the search must include the entire
-** probe sequence length plus the (length_of_the_signature - 1).
-** Stop the search IMMEDIATELY after the signature is found so that the
-** PROM address counter is correctly positioned at the start of the
-** ethernet address for later read out.
+** Look for a particular board name in the PCI configuration space
 */
-
-static int DevicePresent(u_long aprom_addr)
+static int PCI_signature(char *name, struct bus_type *lp)
 {
-  union {
-    struct {
-      u32 a;
-      u32 b;
-    } llsig;
-    char Sig[sizeof(u32) << 1];
-  } dev;
-  char data;
-  int i, j, tmp, status = 0;
-  short sigLength;
-  struct bus_type *lp = &bus;
-
-  dev.llsig.a = ETH_PROM_SIG;
-  dev.llsig.b = ETH_PROM_SIG;
-  sigLength = sizeof(u32) << 1;
-
-  if (lp->chipset == DC21040) {
-    for (i=0,j=0;(j<sigLength) && (i<PROBE_LENGTH+sigLength-1);i++) {
-      if (lp->bus == PCI) {
-	while ((tmp = inl(aprom_addr)) < 0);
-	data = (char)tmp;
-      } else {
-	data = inb(aprom_addr);
-      }
-      if (dev.Sig[j] == data) {   /* track signature */
-	j++;
-      } else {                    /* lost signature; begin search again */
-	if (data == dev.Sig[0]) {
-	  j=1;
-	} else {
-	  j=0;
-	}
-      }
-    }
-
-    if (j!=sigLength) {
-      status = -ENODEV;           /* search failed */
-    }
-
-  } else {                        /* use new srom */
-    short *p = (short *)&lp->srom;
-    for (i=0; i<(sizeof(struct de4x5_srom)>>1); i++) {
-      *p++ = srom_rd(aprom_addr, i);
+    c_char *de4x5_signatures[] = DE4X5_SIGNATURE;
+    int i, status = 0, siglen = sizeof(de4x5_signatures)/sizeof(c_char *);
+    
+    if (lp->chipset == DC21040) {
+	strcpy(name, "DE434/5");
+    } else {
+	int i = *((char *)&lp->srom + 19) * 3;
+	if (lp->chipset == DC21041) {
+	    strncpy(name, (char *)&lp->srom + 26 + i, 8);
+	} else if (lp->chipset == DC21140) {
+	    strncpy(name, (char *)&lp->srom + 26 + i, 8);
+	}
+    }
+    name[8] = '\0';
+    for (i=0; i<siglen; i++) {
+	if (strstr(name,de4x5_signatures[i])!=NULL) break;
+    }
+    if (i == siglen) {
+	if (dec_only) {
+	    *name = '\0';
+	} else {
+	    strcpy(name, "UNKNOWN");
+	}
     }
-  }
+    
+    return status;
+}
 
-  return status;
+/*
+** Set up the Ethernet PROM counter to the start of the Ethernet address on
+** the DC21040, else  read the SROM for the other chips.
+*/
+static void DevicePresent(u_long aprom_addr)
+{
+    int i;
+    struct bus_type *lp = &bus;
+    
+    if (lp->chipset == DC21040) {
+	outl(0, aprom_addr);           /* Reset Ethernet Address ROM Pointer */
+    } else {                           /* Read new srom */
+	short *p = (short *)&lp->srom;
+	for (i=0; i<(sizeof(struct de4x5_srom)>>1); i++) {
+	    *p++ = srom_rd(aprom_addr, i);
+	}
+	de4x5_dbg_srom((struct de4x5_srom *)&lp->srom);
+    }
+    
+    return;
 }
 
 static int get_hw_addr(struct device *dev)
 {
-  u_long iobase = dev->base_addr;
-  int i, k, tmp, status = 0;
-  u_short j,chksum;
-  struct bus_type *lp = &bus;
-
-  for (i=0,k=0,j=0;j<3;j++) {
-    k <<= 1 ;
-    if (k > 0xffff) k-=0xffff;
-
+    u_long iobase = dev->base_addr;
+    int broken, i, k, tmp, status = 0;
+    u_short j,chksum;
+    struct bus_type *lp = &bus;
+
+    broken = de4x5_bad_srom(lp);
+    for (i=0,k=0,j=0;j<3;j++) {
+	k <<= 1;
+	if (k > 0xffff) k-=0xffff;
+	
+	if (lp->bus == PCI) {
+	    if (lp->chipset == DC21040) {
+		while ((tmp = inl(DE4X5_APROM)) < 0);
+		k += (u_char) tmp;
+		dev->dev_addr[i++] = (u_char) tmp;
+		while ((tmp = inl(DE4X5_APROM)) < 0);
+		k += (u_short) (tmp << 8);
+		dev->dev_addr[i++] = (u_char) tmp;
+	    } else if (!broken) {
+		dev->dev_addr[i] = (u_char) lp->srom.ieee_addr[i]; i++;
+		dev->dev_addr[i] = (u_char) lp->srom.ieee_addr[i]; i++;
+	    } else if (broken == SMC) {           /* Assume SMC9332 for now */
+		dev->dev_addr[i] = *((u_char *)&lp->srom + i); i++;
+		dev->dev_addr[i] = *((u_char *)&lp->srom + i); i++;
+	    }
+	} else {
+	    k += (u_char) (tmp = inb(EISA_APROM));
+	    dev->dev_addr[i++] = (u_char) tmp;
+	    k += (u_short) ((tmp = inb(EISA_APROM)) << 8);
+	    dev->dev_addr[i++] = (u_char) tmp;
+	}
+	
+	if (k > 0xffff) k-=0xffff;
+    }
+    if (k == 0xffff) k=0;
+    
     if (lp->bus == PCI) {
-      if (lp->chipset == DC21040) {
-	while ((tmp = inl(DE4X5_APROM)) < 0);
-	k += (u_char) tmp;
-	dev->dev_addr[i++] = (u_char) tmp;
-	while ((tmp = inl(DE4X5_APROM)) < 0);
-	k += (u_short) (tmp << 8);
-	dev->dev_addr[i++] = (u_char) tmp;
-      } else {
-	dev->dev_addr[i] = (u_char) lp->srom.ieee_addr[i]; i++;
-	dev->dev_addr[i] = (u_char) lp->srom.ieee_addr[i]; i++;
-      }
-    } else {
-      k += (u_char) (tmp = inb(EISA_APROM));
-      dev->dev_addr[i++] = (u_char) tmp;
-      k += (u_short) ((tmp = inb(EISA_APROM)) << 8);
-      dev->dev_addr[i++] = (u_char) tmp;
-    }
-
-    if (k > 0xffff) k-=0xffff;
-  }
-  if (k == 0xffff) k=0;
+	if (lp->chipset == DC21040) {
+	    while ((tmp = inl(DE4X5_APROM)) < 0);
+	    chksum = (u_char) tmp;
+	    while ((tmp = inl(DE4X5_APROM)) < 0);
+	    chksum |= (u_short) (tmp << 8);
+	    if (k != chksum) status = -1;
+	}
+    } else {
+	chksum = (u_char) inb(EISA_APROM);
+	chksum |= (u_short) (inb(EISA_APROM) << 8);
+	if (k != chksum) status = -1;
+    }
+    
+    return status;
+}
 
-  if (lp->bus == PCI) {
-    if (lp->chipset == DC21040) {
-      while ((tmp = inl(DE4X5_APROM)) < 0);
-      chksum = (u_char) tmp;
-      while ((tmp = inl(DE4X5_APROM)) < 0);
-      chksum |= (u_short) (tmp << 8);
-      if (k != chksum) status = -1;
-    }
-  } else {
-    chksum = (u_char) inb(EISA_APROM);
-    chksum |= (u_short) (inb(EISA_APROM) << 8);
-    if (k != chksum) status = -1;
-  }
+/*
+** Test for enet addresses in the first 32 bytes. The built-in strncmp
+** didn't seem to work here...?
+*/
+static int de4x5_bad_srom(struct bus_type *lp)
+{
+    int i, status = 0;
 
+    for (i=0; i<sizeof(enet_det)/ETH_ALEN; i++) {
+	if (!de4x5_strncmp((char *)&lp->srom, (char *)&enet_det[i], 3) &&
+	    !de4x5_strncmp((char *)&lp->srom+0x10, (char *)&enet_det[i], 3)) {
+	    status = SMC;
+	    break;
+	}
+    }
 
-  return status;
+    return status;
+}
+
+static int de4x5_strncmp(char *a, char *b, int n)
+{
+    int ret=0;
+
+    for (;n && !ret;n--) {
+	ret = *a++ - *b++;
+    }
+
+    return ret;
 }
 
 /*
@@ -2319,466 +2931,928 @@
 */
 static short srom_rd(u_long addr, u_char offset)
 {
-  sendto_srom(SROM_RD | SROM_SR, addr);
+    sendto_srom(SROM_RD | SROM_SR, addr);
+    
+    srom_latch(SROM_RD | SROM_SR | DT_CS, addr);
+    srom_command(SROM_RD | SROM_SR | DT_IN | DT_CS, addr);
+    srom_address(SROM_RD | SROM_SR | DT_CS, addr, offset);
+    
+    return srom_data(SROM_RD | SROM_SR | DT_CS, addr);
+}
 
-  srom_latch(SROM_RD | SROM_SR | DT_CS, addr);
-  srom_command(SROM_RD | SROM_SR | DT_IN | DT_CS, addr);
-  srom_address(SROM_RD | SROM_SR | DT_CS, addr, offset);
+static void srom_latch(u_int command, u_long addr)
+{
+    sendto_srom(command, addr);
+    sendto_srom(command | DT_CLK, addr);
+    sendto_srom(command, addr);
+    
+    return;
+}
+
+static void srom_command(u_int command, u_long addr)
+{
+    srom_latch(command, addr);
+    srom_latch(command, addr);
+    srom_latch((command & 0x0000ff00) | DT_CS, addr);
+    
+    return;
+}
 
-  return srom_data(SROM_RD | SROM_SR | DT_CS, addr);
+static void srom_address(u_int command, u_long addr, u_char offset)
+{
+    int i;
+    char a;
+    
+    a = (char)(offset << 2);
+    for (i=0; i<6; i++, a <<= 1) {
+	srom_latch(command | ((a < 0) ? DT_IN : 0), addr);
+    }
+    de4x5_us_delay(1);
+    
+    i = (getfrom_srom(addr) >> 3) & 0x01;
+    if (i != 0) {
+	printk("Bad SROM address phase.....\n");
+    }
+    
+    return;
+}
+
+static short srom_data(u_int command, u_long addr)
+{
+    int i;
+    short word = 0;
+    s32 tmp;
+    
+    for (i=0; i<16; i++) {
+	sendto_srom(command  | DT_CLK, addr);
+	tmp = getfrom_srom(addr);
+	sendto_srom(command, addr);
+	
+	word = (word << 1) | ((tmp >> 3) & 0x01);
+    }
+    
+    sendto_srom(command & 0x0000ff00, addr);
+    
+    return word;
+}
+
+/*
+static void srom_busy(u_int command, u_long addr)
+{
+   sendto_srom((command & 0x0000ff00) | DT_CS, addr);
+   
+   while (!((getfrom_srom(addr) >> 3) & 0x01)) {
+       de4x5_ms_delay(1);
+   }
+   
+   sendto_srom(command & 0x0000ff00, addr);
+   
+   return;
+}
+*/
+
+static void sendto_srom(u_int command, u_long addr)
+{
+    outl(command, addr);
+    udelay(1);
+    
+    return;
+}
+
+static int getfrom_srom(u_long addr)
+{
+    s32 tmp;
+    
+    tmp = inl(addr);
+    udelay(1);
+    
+    return tmp;
+}
+
+/*
+** MII Read/Write
+*/
+
+static int mii_rd(u_char phyreg, u_char phyaddr, u_long ioaddr)
+{
+    mii_wdata(MII_PREAMBLE,  2, ioaddr);   /* Start of 34 bit preamble...    */
+    mii_wdata(MII_PREAMBLE, 32, ioaddr);   /* ...continued                   */
+    mii_wdata(MII_STRD, 4, ioaddr);        /* SFD and Read operation         */
+    mii_address(phyaddr, ioaddr);          /* PHY address to be accessed     */
+    mii_address(phyreg, ioaddr);           /* PHY Register to read           */
+    mii_ta(MII_STRD, ioaddr);              /* Turn around time - 2 MDC       */
+    
+    return mii_rdata(ioaddr);              /* Read data                      */
+}
+
+static void mii_wr(int data, u_char phyreg, u_char phyaddr, u_long ioaddr)
+{
+    mii_wdata(MII_PREAMBLE,  2, ioaddr);   /* Start of 34 bit preamble...    */
+    mii_wdata(MII_PREAMBLE, 32, ioaddr);   /* ...continued                   */
+    mii_wdata(MII_STWR, 4, ioaddr);        /* SFD and Write operation        */
+    mii_address(phyaddr, ioaddr);          /* PHY address to be accessed     */
+    mii_address(phyreg, ioaddr);           /* PHY Register to write          */
+    mii_ta(MII_STWR, ioaddr);              /* Turn around time - 2 MDC       */
+    data = mii_swap(data, 16);             /* Swap data bit ordering         */
+    mii_wdata(data, 16, ioaddr);           /* Write data                     */
+    
+    return;
+}
+
+static int mii_rdata(u_long ioaddr)
+{
+    int i;
+    s32 tmp = 0;
+    
+    for (i=0; i<16; i++) {
+	tmp <<= 1;
+	tmp |= getfrom_mii(MII_MRD | MII_RD, ioaddr);
+    }
+    
+    return tmp;
+}
+
+static void mii_wdata(int data, int len, u_long ioaddr)
+{
+    int i;
+    
+    for (i=0; i<len; i++) {
+	sendto_mii(MII_MWR | MII_WR, data, ioaddr);
+	data >>= 1;
+    }
+    
+    return;
+}
+
+static void mii_address(u_char addr, u_long ioaddr)
+{
+    int i;
+    
+    addr = mii_swap(addr, 5);
+    for (i=0; i<5; i++) {
+	sendto_mii(MII_MWR | MII_WR, addr, ioaddr);
+	addr >>= 1;
+    }
+    
+    return;
 }
 
-static void srom_latch(u_int command, u_long addr)
+static void mii_ta(u_long rw, u_long ioaddr)
 {
-  sendto_srom(command, addr);
-  sendto_srom(command | DT_CLK, addr);
-  sendto_srom(command, addr);
-
-  return;
+    if (rw == MII_STWR) {
+	sendto_mii(MII_MWR | MII_WR, 1, ioaddr);  
+	getfrom_mii(MII_MRD | MII_RD, ioaddr);
+    } else {
+	getfrom_mii(MII_MRD | MII_RD, ioaddr);        /* Tri-state MDIO */
+    }
+    
+    return;
 }
 
-static void srom_command(u_int command, u_long addr)
+static int mii_swap(int data, int len)
 {
-  srom_latch(command, addr);
-  srom_latch(command, addr);
-  srom_latch((command & 0x0000ff00) | DT_CS, addr);
-
-  return;
+    int i, tmp = 0;
+    
+    for (i=0; i<len; i++) {
+	tmp <<= 1;
+	tmp |= (data & 1);
+	data >>= 1;
+    }
+    
+    return tmp;
 }
 
-static void srom_address(u_int command, u_long addr, u_char offset)
+static void sendto_mii(u32 command, int data, u_long ioaddr)
 {
-  int i;
-  char a;
-
-  a = (char)(offset << 2);
-  for (i=0; i<6; i++, a <<= 1) {
-    srom_latch(command | ((a < 0) ? DT_IN : 0), addr);
-  }
-  dce_us_delay(1);
-
-  i = (getfrom_srom(addr) >> 3) & 0x01;
-  if (i != 0) {
-    printk("Bad SROM address phase.....\n");
-/*    printk(".");*/
-  }
-
-  return;
+    u32 j;
+    
+    j = (data & 1) << 17;
+    outl(command | j, ioaddr);
+    udelay(1);
+    outl(command | MII_MDC | j, ioaddr);
+    udelay(1);
+    
+    return;
 }
 
-static short srom_data(u_int command, u_long addr)
+static int getfrom_mii(u32 command, u_long ioaddr)
 {
-  int i;
-  short word = 0;
-  s32 tmp;
-
-  for (i=0; i<16; i++) {
-    sendto_srom(command  | DT_CLK, addr);
-    tmp = getfrom_srom(addr);
-    sendto_srom(command, addr);
-
-    word = (word << 1) | ((tmp >> 3) & 0x01);
-  }
-
-  sendto_srom(command & 0x0000ff00, addr);
-
-  return word;
+    outl(command, ioaddr);
+    udelay(1);
+    outl(command | MII_MDC, ioaddr);
+    udelay(1);
+    
+    return ((inl(ioaddr) >> 19) & 1);
 }
 
 /*
-static void srom_busy(u_int command, u_long addr)
+** Here's 3 ways to calculate the OUI from the ID registers. One's a brain
+** dead approach, 2 aren't (clue: mine isn't!).
+*/
+static int mii_get_oui(u_char phyaddr, u_long ioaddr)
 {
-  sendto_srom((command & 0x0000ff00) | DT_CS, addr);
+/*
+    union {
+	u_short reg;
+	u_char breg[2];
+    } a;
+    int i, r2, r3, ret=0;*/
+    int r2, r3;
 
-  while (!((getfrom_srom(addr) >> 3) & 0x01)) {
-    dce_ms_delay(1);
-  }
+    /* Read r2 and r3 */
+    r2 = mii_rd(MII_ID0, phyaddr, ioaddr);
+    r3 = mii_rd(MII_ID1, phyaddr, ioaddr);
+                                                /* SEEQ and Cypress way * /
+    / * Shuffle r2 and r3 * /
+    a.reg=0;
+    r3 = ((r3>>10)|(r2<<6))&0x0ff;
+    r2 = ((r2>>2)&0x3fff);
 
-  sendto_srom(command & 0x0000ff00, addr);
+    / * Bit reverse r3 * /
+    for (i=0;i<8;i++) {
+	ret<<=1;
+	ret |= (r3&1);
+	r3>>=1;
+    }
 
-  return;
-}
-*/
+    / * Bit reverse r2 * /
+    for (i=0;i<16;i++) {
+	a.reg<<=1;
+	a.reg |= (r2&1);
+	r2>>=1;
+    }
 
-static void sendto_srom(u_int command, u_long addr)
-{
-  outl(command, addr);
-  dce_us_delay(1);
+    / * Swap r2 bytes * /
+    i=a.breg[0];
+    a.breg[0]=a.breg[1];
+    a.breg[1]=i;
 
-  return;
+    return ((a.reg<<8)|ret); */                 /* SEEQ and Cypress way */
+/*    return ((r2<<6)|(u_int)(r3>>10)); */      /* NATIONAL and BROADCOM way */
+    return r2;                                  /* (I did it) My way */
 }
 
-static int getfrom_srom(u_long addr)
+static int mii_get_phy(struct device *dev)
 {
-  s32 tmp;
-
-  tmp = inl(addr);
-  dce_us_delay(1);
-
-  return tmp;
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    int iobase = dev->base_addr;
+    int i, j, k, limit=sizeof(phy_info)/sizeof(struct phy_table);
+    int id;
+    
+    /* Issue a hard PHY reset - Broadcom is screwed up otherwise */
+    outl(GEP_HRST, DE4X5_GEP);
+    udelay(1000);                                  /* Assert for 1ms */
+    outl(0x00, DE4X5_GEP);
+    udelay(2000);                                  /* Wait for 2ms */
+    
+    /* Search the MII address space for possible PHY devices */
+    lp->active = 0;
+    for (lp->mii_cnt=0, i=1; i<DE4X5_MAX_MII; i++) {
+	id = mii_get_oui(i, DE4X5_MII); 
+	if ((id == 0) || (id == -1)) continue;     /* Valid ID? */
+	for (j=0; j<limit; j++) {                  /* Search PHY table */
+	    if (id != phy_info[j].id) continue;    /* ID match? */
+	    for (k=0; lp->phy[k].id && (k < DE4X5_MAX_PHY); k++);
+	    if (k < DE4X5_MAX_PHY) {
+		memcpy((char *)&lp->phy[k],
+		       (char *)&phy_info[j], sizeof(struct phy_table));
+		lp->phy[k].addr = i;
+		lp->mii_cnt++;
+	    } else {
+		i = DE4X5_MAX_MII;                 /* Stop the search */
+		j = limit;
+	    }
+	}
+    }
+    if (lp->phy[lp->active].id) {                  /* Reset the PHY devices */
+	for (k=0; lp->phy[k].id && (k < DE4X5_MAX_PHY); k++) { /*For each PHY*/
+	    mii_wr(MII_CR_RST, MII_CR, lp->phy[k].addr, DE4X5_MII);
+	    while (mii_rd(MII_CR, lp->phy[k].addr, DE4X5_MII) & MII_CR_RST);
+	    
+	    de4x5_dbg_mii(dev, k);
+	}
+    }
+    
+    return lp->mii_cnt;
 }
 
 static char *build_setup_frame(struct device *dev, int mode)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  int i;
-  char *pa = lp->setup_frame;
-
-  /* Initialise the setup frame */
-  if (mode == ALL) {
-    memset(lp->setup_frame, 0, SETUP_FRAME_LEN);
-  }
-
-  if (lp->setup_f == HASH_PERF) {
-    for (pa=lp->setup_frame+IMPERF_PA_OFFSET, i=0; i<ETH_ALEN; i++) {
-      *(pa + i) = dev->dev_addr[i];                 /* Host address */
-      if (i & 0x01) pa += 2;
-    }
-    *(lp->setup_frame + (HASH_TABLE_LEN >> 3) - 3) = 0x80; /* B'cast address */
-  } else {
-    for (i=0; i<ETH_ALEN; i++) { /* Host address */
-      *(pa + (i&1)) = dev->dev_addr[i];
-      if (i & 0x01) pa += 4;
-    }
-    for (i=0; i<ETH_ALEN; i++) { /* Broadcast address */
-      *(pa + (i&1)) = (char) 0xff;
-      if (i & 0x01) pa += 4;
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    int i;
+    char *pa = lp->setup_frame;
+    
+    /* Initialise the setup frame */
+    if (mode == ALL) {
+	memset(lp->setup_frame, 0, SETUP_FRAME_LEN);
     }
-  }
-
-  return pa;                     /* Points to the next entry */
+    
+    if (lp->setup_f == HASH_PERF) {
+	for (pa=lp->setup_frame+IMPERF_PA_OFFSET, i=0; i<ETH_ALEN; i++) {
+	    *(pa + i) = dev->dev_addr[i];                 /* Host address */
+	    if (i & 0x01) pa += 2;
+	}
+	*(lp->setup_frame + (HASH_TABLE_LEN >> 3) - 3) = 0x80;
+    } else {
+	for (i=0; i<ETH_ALEN; i++) { /* Host address */
+	    *(pa + (i&1)) = dev->dev_addr[i];
+	    if (i & 0x01) pa += 4;
+	}
+	for (i=0; i<ETH_ALEN; i++) { /* Broadcast address */
+	    *(pa + (i&1)) = (char) 0xff;
+	    if (i & 0x01) pa += 4;
+	}
+    }
+    
+    return pa;                     /* Points to the next entry */
 }
 
 static void enable_ast(struct device *dev, u32 time_out)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-
-  lp->irq_mask |= IMR_TMM;
-  outl(lp->irq_mask, DE4X5_IMR);
-  load_ms_timer(dev, time_out);
-
-  return;
+    timeout(dev, (void *)&de4x5_ast, (u_long)dev, time_out);
+    
+    return;
 }
 
 static void disable_ast(struct device *dev)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  u_long iobase = dev->base_addr;
-
-  lp->irq_mask &= ~IMR_TMM;
-  outl(lp->irq_mask, DE4X5_IMR);
-  load_ms_timer(dev, 0);
-
-  return;
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    
+    del_timer(&lp->timer);
+    
+    return;
 }
 
-static void kick_tx(struct device *dev)
+static long de4x5_switch_to_mii(struct device *dev)
 {
-  struct sk_buff *skb;
-
-  if ((skb = alloc_skb(0, GFP_ATOMIC)) != NULL) {
-    skb->len= FAKE_FRAME_LEN;
-    skb->arp=1;
-    skb->dev=dev;
-    dev_queue_xmit(skb, dev, SOPRI_NORMAL);
-  }
-
-  return;
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    int iobase = dev->base_addr;
+    long omr;
+    
+    /* Assert the OMR_PS bit in CSR6 */
+    omr = (inl(DE4X5_OMR) & ~(OMR_PS | OMR_HBD | OMR_TTM | OMR_PCS | OMR_SCR));
+    omr |= (OMR_PS | OMR_HBD);
+    outl(omr, DE4X5_OMR);
+    
+    /* Soft Reset */
+    RESET_DE4X5;
+    
+    /* Restore the GEP */
+    if (lp->chipset == DC21140) {
+	outl(GEP_INIT, DE4X5_GEP);
+	outl(0, DE4X5_GEP);
+    }
+    
+    /* Restore CSR6 */
+    outl(omr, DE4X5_OMR);
+    
+    return omr;
 }
 
-/*
-** Perform IOCTL call functions here. Some are privileged operations and the
-** effective uid is checked in those cases.
-*/
-static int de4x5_ioctl(struct device *dev, struct ifreq *rq, int cmd)
+static long de4x5_switch_to_srl(struct device *dev)
 {
-  struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
-  struct de4x5_ioctl *ioc = (struct de4x5_ioctl *) &rq->ifr_data;
-  u_long iobase = dev->base_addr;
-  int i, j, status = 0;
-  s32 omr;
-  union {
-    u8  addr[(HASH_TABLE_LEN * ETH_ALEN)];
-    u16 sval[(HASH_TABLE_LEN * ETH_ALEN) >> 1];
-    u32 lval[(HASH_TABLE_LEN * ETH_ALEN) >> 2];
-  } tmp;
-
-  switch(ioc->cmd) {
-  case DE4X5_GET_HWADDR:             /* Get the hardware address */
-    ioc->len = ETH_ALEN;
-    status = verify_area(VERIFY_WRITE, (void *)ioc->data, ioc->len);
-    if (status)
-      break;
-    for (i=0; i<ETH_ALEN; i++) {
-      tmp.addr[i] = dev->dev_addr[i];
-    }
-    memcpy_tofs(ioc->data, tmp.addr, ioc->len);
-    
-    break;
-  case DE4X5_SET_HWADDR:             /* Set the hardware address */
-    status = verify_area(VERIFY_READ, (void *)ioc->data, ETH_ALEN);
-    if (status)
-      break;
-    status = -EPERM;
-    if (!suser())
-      break;
-    status = 0;
-    memcpy_fromfs(tmp.addr, ioc->data, ETH_ALEN);
-    for (i=0; i<ETH_ALEN; i++) {
-      dev->dev_addr[i] = tmp.addr[i];
-    }
-    build_setup_frame(dev, PHYS_ADDR_ONLY);
-    /* Set up the descriptor and give ownership to the card */
-    while (set_bit(0, (void *)&dev->tbusy) != 0);/* Wait for lock to free*/
-    if (lp->setup_f == HASH_PERF) {
-      load_packet(dev, lp->setup_frame, TD_IC | HASH_F | TD_SET | 
-    	                                        SETUP_FRAME_LEN, NULL);
-    } else {
-      load_packet(dev, lp->setup_frame, TD_IC | PERFECT_F | TD_SET | 
-    	                                        SETUP_FRAME_LEN, NULL);
-    }
-    lp->tx_new = (++lp->tx_new) % lp->txRingSize;
-    outl(POLL_DEMAND, DE4X5_TPD);                /* Start the TX */
-    dev->tbusy = 0;                              /* Unlock the TX ring */
-
-    break;
-  case DE4X5_SET_PROM:               /* Set Promiscuous Mode */
-    if (suser()) {
-      omr = inl(DE4X5_OMR);
-      omr |= OMR_PR;
-      outl(omr, DE4X5_OMR);
-    } else {
-      status = -EPERM;
-    }
-
-    break;
-  case DE4X5_CLR_PROM:               /* Clear Promiscuous Mode */
-    if (suser()) {
-      omr = inl(DE4X5_OMR);
-      omr &= ~OMR_PR;
-      outb(omr, DE4X5_OMR);
-    } else {
-      status = -EPERM;
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    int iobase = dev->base_addr;
+    long omr;
+    
+    /* Deassert the OMR_PS bit in CSR6 */
+    omr = (inl(DE4X5_OMR) & ~(OMR_PS | OMR_HBD | OMR_TTM | OMR_PCS | OMR_SCR));
+    outl(omr | OMR_TTM, DE4X5_OMR);
+    outl(omr, DE4X5_OMR);
+    
+    /* Soft Reset */
+    RESET_DE4X5;
+    
+    /* Restore the GEP */
+    if (lp->chipset == DC21140) {
+	outl(GEP_INIT, DE4X5_GEP);
+	outl(0, DE4X5_GEP);
     }
+    
+    /* Restore CSR6 */
+    outl(omr, DE4X5_OMR);
+    
+    return omr;
+}
 
-    break;
-  case DE4X5_SAY_BOO:                /* Say "Boo!" to the kernel log file */
-    printk("%s: Boo!\n", dev->name);
-
-    break;
-  case DE4X5_GET_MCA:                /* Get the multicast address table */
-    ioc->len = (HASH_TABLE_LEN >> 3);
-    status = verify_area(VERIFY_WRITE, ioc->data, ioc->len);
-    if (status)
-      break;
-    memcpy_tofs(ioc->data, lp->setup_frame, ioc->len); 
+static void timeout(struct device *dev, void (*fn)(u_long data), u_long data, u_long msec)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    int dt;
+    
+    /* First, cancel any pending timer events */
+    del_timer(&lp->timer);
+    
+    /* Convert msec to ticks */
+    dt = (msec * HZ) / 1000;
+    if (dt==0) dt=1;
+    
+    /* Set up timer */
+    lp->timer.expires = jiffies + dt;
+    lp->timer.function = fn;
+    lp->timer.data = data;
+    add_timer(&lp->timer);
+    
+    return;
+}
 
-    break;
-  case DE4X5_SET_MCA:                /* Set a multicast address */
-    if (suser()) {
-      if (ioc->len != HASH_TABLE_LEN) {         /* MCA changes */
-	if (!(status = verify_area(VERIFY_READ, (void *)ioc->data, ETH_ALEN * ioc->len))) {
-	  memcpy_fromfs(tmp.addr, ioc->data, ETH_ALEN * ioc->len);
-	  set_multicast_list(dev);
+static void de4x5_dbg_open(struct device *dev)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    int i;
+    
+    if (de4x5_debug > 1) {
+	printk("%s: de4x5 opening with irq %d\n",dev->name,dev->irq);
+	printk("\tphysical address: ");
+	for (i=0;i<6;i++) {
+	    printk("%2.2x:",(short)dev->dev_addr[i]);
+	}
+	printk("\n");
+	printk("Descriptor head addresses:\n");
+	printk("\t0x%8.8lx  0x%8.8lx\n",(u_long)lp->rx_ring,(u_long)lp->tx_ring);
+	printk("Descriptor addresses:\nRX: ");
+	for (i=0;i<lp->rxRingSize-1;i++){
+	    if (i < 3) {
+		printk("0x%8.8lx  ",(u_long)&lp->rx_ring[i].status);
+	    }
 	}
-      } else {
-	set_multicast_list(dev);
-      }
-    } else {
-      status = -EPERM;
+	printk("...0x%8.8lx\n",(u_long)&lp->rx_ring[i].status);
+	printk("TX: ");
+	for (i=0;i<lp->txRingSize-1;i++){
+	    if (i < 3) {
+		printk("0x%8.8lx  ", (u_long)&lp->tx_ring[i].status);
+	    }
+	}
+	printk("...0x%8.8lx\n", (u_long)&lp->tx_ring[i].status);
+	printk("Descriptor buffers:\nRX: ");
+	for (i=0;i<lp->rxRingSize-1;i++){
+	    if (i < 3) {
+		printk("0x%8.8x  ",lp->rx_ring[i].buf);
+	    }
+	}
+	printk("...0x%8.8x\n",lp->rx_ring[i].buf);
+	printk("TX: ");
+	for (i=0;i<lp->txRingSize-1;i++){
+	    if (i < 3) {
+		printk("0x%8.8x  ", lp->tx_ring[i].buf);
+	    }
+	}
+	printk("...0x%8.8x\n", lp->tx_ring[i].buf);
+	printk("Ring size: \nRX: %d\nTX: %d\n", 
+	       (short)lp->rxRingSize, 
+	       (short)lp->txRingSize); 
     }
+    
+    return;
+}
 
-    break;
-  case DE4X5_CLR_MCA:                /* Clear all multicast addresses */
-    if (suser()) {
-      set_multicast_list(dev);
-    } else {
-      status = -EPERM;
+static void de4x5_dbg_mii(struct device *dev, int k)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    int iobase = dev->base_addr;
+    
+    if (de4x5_debug > 2) {
+	printk("\nMII CR:  %x\n",mii_rd(MII_CR,lp->phy[k].addr,DE4X5_MII));
+	printk("MII SR:  %x\n",mii_rd(MII_SR,lp->phy[k].addr,DE4X5_MII));
+	printk("MII ID0: %x\n",mii_rd(MII_ID0,lp->phy[k].addr,DE4X5_MII));
+	printk("MII ID1: %x\n",mii_rd(MII_ID1,lp->phy[k].addr,DE4X5_MII));
+	if (lp->phy[k].id != BROADCOM_T4) {
+	    printk("MII ANA: %x\n",mii_rd(0x04,lp->phy[k].addr,DE4X5_MII));
+	    printk("MII ANC: %x\n",mii_rd(0x05,lp->phy[k].addr,DE4X5_MII));
+	}
+	printk("MII 16:  %x\n",mii_rd(0x10,lp->phy[k].addr,DE4X5_MII));
+	if (lp->phy[k].id != BROADCOM_T4) {
+	    printk("MII 17:  %x\n",mii_rd(0x11,lp->phy[k].addr,DE4X5_MII));
+	    printk("MII 18:  %x\n",mii_rd(0x12,lp->phy[k].addr,DE4X5_MII));
+	} else {
+	    printk("MII 20:  %x\n",mii_rd(0x14,lp->phy[k].addr,DE4X5_MII));
+	}
     }
+    
+    return;
+}
 
-    break;
-  case DE4X5_MCA_EN:                 /* Enable pass all multicast addressing */
-    if (suser()) {
-      omr = inl(DE4X5_OMR);
-      omr |= OMR_PM;
-      outl(omr, DE4X5_OMR);
-    } else {
-      status = -EPERM;
+static void de4x5_dbg_media(struct device *dev)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    
+    if (lp->media != lp->c_media) {
+	if (de4x5_debug > 0) {
+	    if (lp->chipset != DC21140) {
+		printk("%s: media is %s\n", dev->name,
+		       (lp->media == NC  ? "unconnected!" :
+			(lp->media == TP  ? "TP." :
+			 (lp->media == ANS ? "TP/Nway." :
+			  (lp->media == BNC ? "BNC." : 
+			   (lp->media == BNC_AUI ? "BNC/AUI." : 
+			    (lp->media == EXT_SIA ? "EXT SIA." : 
+			     "???."
+			     )))))));
+	    } else {
+		printk("%s: mode is %s\n", dev->name,
+		    (lp->media == NC ? "link down or incompatible connection.":
+		     (lp->media == _100Mb  ? "100Mb/s." :
+		      (lp->media == _10Mb   ? "10Mb/s." :
+		       "\?\?\?"
+		       ))));
+	    }
+	}
+	lp->c_media = lp->media;
     }
+    
+    return;
+}
 
-    break;
-  case DE4X5_GET_STATS:              /* Get the driver statistics */
-    ioc->len = sizeof(lp->pktStats);
-    status = verify_area(VERIFY_WRITE, (void *)ioc->data, ioc->len);
-    if (status)
-      break;
-
-    cli();
-    memcpy_tofs(ioc->data, &lp->pktStats, ioc->len); 
-    sti();
+static void de4x5_dbg_srom(struct de4x5_srom *p)
+{
+    int i;
 
-    break;
-  case DE4X5_CLR_STATS:              /* Zero out the driver statistics */
-    if (suser()) {
-      cli();
-      memset(&lp->pktStats, 0, sizeof(lp->pktStats));
-      sti();
-    } else {
-      status = -EPERM;
-    }
-
-    break;
-  case DE4X5_GET_OMR:                /* Get the OMR Register contents */
-    tmp.addr[0] = inl(DE4X5_OMR);
-    if (!(status = verify_area(VERIFY_WRITE, (void *)ioc->data, 1))) {
-      memcpy_tofs(ioc->data, tmp.addr, 1);
-    }
-
-    break;
-  case DE4X5_SET_OMR:                /* Set the OMR Register contents */
-    if (suser()) {
-      if (!(status = verify_area(VERIFY_READ, (void *)ioc->data, 1))) {
-	memcpy_fromfs(tmp.addr, ioc->data, 1);
-	outl(tmp.addr[0], DE4X5_OMR);
-      }
-    } else {
-      status = -EPERM;
-    }
-
-    break;
-  case DE4X5_GET_REG:                /* Get the DE4X5 Registers */
-    j = 0;
-    tmp.lval[0] = inl(DE4X5_STS); j+=4;
-    tmp.lval[1] = inl(DE4X5_BMR); j+=4;
-    tmp.lval[2] = inl(DE4X5_IMR); j+=4;
-    tmp.lval[3] = inl(DE4X5_OMR); j+=4;
-    tmp.lval[4] = inl(DE4X5_SISR); j+=4;
-    tmp.lval[5] = inl(DE4X5_SICR); j+=4;
-    tmp.lval[6] = inl(DE4X5_STRR); j+=4;
-    tmp.lval[7] = inl(DE4X5_SIGR); j+=4;
-    ioc->len = j;
-    if (!(status = verify_area(VERIFY_WRITE, (void *)ioc->data, ioc->len))) {
-      memcpy_tofs(ioc->data, tmp.addr, ioc->len);
+    if (de4x5_debug > 1) {
+	printk("Sub-system Vendor ID: %04x\n", (u_short)*(p->sub_vendor_id));
+	printk("Sub-system ID:        %04x\n", (u_short)*(p->sub_system_id));
+	printk("ID Block CRC:         %02x\n", (u_char)(p->id_block_crc));
+
+	printk("Hardware Address:     ");
+	for (i=0;i<ETH_ALEN-1;i++) {
+	    printk("%02x:", (u_char)*(p->ieee_addr+i));
+	}
+	printk("%02x\n", (u_char)*(p->ieee_addr+i));
+	printk("CRC checksum:         %04x\n", (u_short)(p->chksum));
+	for (i=0; i<64; i++) {
+	    printk("%3d %04x\n", i<<1, (u_short)*((u_short *)p+i));
+	}
     }
-    break;
-
-#define DE4X5_DUMP              0x0f /* Dump the DE4X5 Status */
 
-  case DE4X5_DUMP:
-    j = 0;
-    tmp.addr[j++] = dev->irq;
-    for (i=0; i<ETH_ALEN; i++) {
-      tmp.addr[j++] = dev->dev_addr[i];
-    }
-    tmp.addr[j++] = lp->rxRingSize;
-    tmp.lval[j>>2] = (long)lp->rx_ring; j+=4;
-    tmp.lval[j>>2] = (long)lp->tx_ring; j+=4;
+    return;
+}
 
-    for (i=0;i<lp->rxRingSize-1;i++){
-      if (i < 3) {
+/*
+** Perform IOCTL call functions here. Some are privileged operations and the
+** effective uid is checked in those cases.
+*/
+static int de4x5_ioctl(struct device *dev, struct ifreq *rq, int cmd)
+{
+    struct de4x5_private *lp = (struct de4x5_private *)dev->priv;
+    struct de4x5_ioctl *ioc = (struct de4x5_ioctl *) &rq->ifr_data;
+    u_long iobase = dev->base_addr;
+    int i, j, status = 0;
+    s32 omr;
+    union {
+	u8  addr[(HASH_TABLE_LEN * ETH_ALEN)];
+	u16 sval[(HASH_TABLE_LEN * ETH_ALEN) >> 1];
+	u32 lval[(HASH_TABLE_LEN * ETH_ALEN) >> 2];
+    } tmp;
+    
+    switch(ioc->cmd) {
+      case DE4X5_GET_HWADDR:           /* Get the hardware address */
+	ioc->len = ETH_ALEN;
+	status = verify_area(VERIFY_WRITE, (void *)ioc->data, ioc->len);
+	if (status)
+	  break;
+	for (i=0; i<ETH_ALEN; i++) {
+	    tmp.addr[i] = dev->dev_addr[i];
+	}
+	memcpy_tofs(ioc->data, tmp.addr, ioc->len);
+	
+	break;
+      case DE4X5_SET_HWADDR:           /* Set the hardware address */
+	status = verify_area(VERIFY_READ, (void *)ioc->data, ETH_ALEN);
+	if (status)
+	  break;
+	status = -EPERM;
+	if (!suser())
+	  break;
+	status = 0;
+	memcpy_fromfs(tmp.addr, ioc->data, ETH_ALEN);
+	for (i=0; i<ETH_ALEN; i++) {
+	    dev->dev_addr[i] = tmp.addr[i];
+	}
+	build_setup_frame(dev, PHYS_ADDR_ONLY);
+	/* Set up the descriptor and give ownership to the card */
+	while (set_bit(0, (void *)&dev->tbusy) != 0);/* Wait for lock to free*/
+	if (lp->setup_f == HASH_PERF) {
+	    load_packet(dev, lp->setup_frame, TD_IC | HASH_F | TD_SET | 
+			SETUP_FRAME_LEN, NULL);
+	} else {
+	    load_packet(dev, lp->setup_frame, TD_IC | PERFECT_F | TD_SET | 
+			SETUP_FRAME_LEN, NULL);
+	}
+	lp->tx_new = (++lp->tx_new) % lp->txRingSize;
+	outl(POLL_DEMAND, DE4X5_TPD);                /* Start the TX */
+	dev->tbusy = 0;                              /* Unlock the TX ring */
+	
+	break;
+      case DE4X5_SET_PROM:             /* Set Promiscuous Mode */
+	if (suser()) {
+	    omr = inl(DE4X5_OMR);
+	    omr |= OMR_PR;
+	    outl(omr, DE4X5_OMR);
+	} else {
+	    status = -EPERM;
+	}
+	
+	break;
+      case DE4X5_CLR_PROM:             /* Clear Promiscuous Mode */
+	if (suser()) {
+	    omr = inl(DE4X5_OMR);
+	    omr &= ~OMR_PR;
+	    outb(omr, DE4X5_OMR);
+	} else {
+	    status = -EPERM;
+	}
+	
+	break;
+      case DE4X5_SAY_BOO:              /* Say "Boo!" to the kernel log file */
+	printk("%s: Boo!\n", dev->name);
+	
+	break;
+      case DE4X5_GET_MCA:              /* Get the multicast address table */
+	ioc->len = (HASH_TABLE_LEN >> 3);
+	status = verify_area(VERIFY_WRITE, ioc->data, ioc->len);
+	if (!status) {
+	    memcpy_tofs(ioc->data, lp->setup_frame, ioc->len); 
+	}
+	
+	break;
+      case DE4X5_SET_MCA:              /* Set a multicast address */
+	if (suser()) {
+	    /******* FIX ME! ********/
+	    if (ioc->len != HASH_TABLE_LEN) {         /* MCA changes */
+		if (!(status = verify_area(VERIFY_READ, (void *)ioc->data, ETH_ALEN * ioc->len))) {
+		    memcpy_fromfs(tmp.addr, ioc->data, ETH_ALEN * ioc->len);
+		    set_multicast_list(dev);
+		}
+	    } else {
+		set_multicast_list(dev);
+	    }
+	} else {
+	    status = -EPERM;
+	}
+	
+	break;
+      case DE4X5_CLR_MCA:              /* Clear all multicast addresses */
+	if (suser()) {
+	    /******* FIX ME! ********/
+	    set_multicast_list(dev);
+	} else {
+	    status = -EPERM;
+	}
+	
+	break;
+      case DE4X5_MCA_EN:               /* Enable pass all multicast addressing */
+	if (suser()) {
+	    omr = inl(DE4X5_OMR);
+	    omr |= OMR_PM;
+	    outl(omr, DE4X5_OMR);
+	} else {
+	    status = -EPERM;
+	}
+	
+	break;
+      case DE4X5_GET_STATS:            /* Get the driver statistics */
+	ioc->len = sizeof(lp->pktStats);
+	status = verify_area(VERIFY_WRITE, (void *)ioc->data, ioc->len);
+	if (status)
+	  break;
+	
+	cli();
+	memcpy_tofs(ioc->data, &lp->pktStats, ioc->len); 
+	sti();
+	
+	break;
+      case DE4X5_CLR_STATS:            /* Zero out the driver statistics */
+	if (suser()) {
+	    cli();
+	    memset(&lp->pktStats, 0, sizeof(lp->pktStats));
+	    sti();
+	} else {
+	    status = -EPERM;
+	}
+	
+	break;
+      case DE4X5_GET_OMR:              /* Get the OMR Register contents */
+	tmp.addr[0] = inl(DE4X5_OMR);
+	if (!(status = verify_area(VERIFY_WRITE, (void *)ioc->data, 1))) {
+	    memcpy_tofs(ioc->data, tmp.addr, 1);
+	}
+	
+	break;
+      case DE4X5_SET_OMR:              /* Set the OMR Register contents */
+	if (suser()) {
+	    if (!(status = verify_area(VERIFY_READ, (void *)ioc->data, 1))) {
+		memcpy_fromfs(tmp.addr, ioc->data, 1);
+		outl(tmp.addr[0], DE4X5_OMR);
+	    }
+	} else {
+	    status = -EPERM;
+	}
+	
+	break;
+      case DE4X5_GET_REG:              /* Get the DE4X5 Registers */
+	j = 0;
+	tmp.lval[0] = inl(DE4X5_STS); j+=4;
+	tmp.lval[1] = inl(DE4X5_BMR); j+=4;
+	tmp.lval[2] = inl(DE4X5_IMR); j+=4;
+	tmp.lval[3] = inl(DE4X5_OMR); j+=4;
+	tmp.lval[4] = inl(DE4X5_SISR); j+=4;
+	tmp.lval[5] = inl(DE4X5_SICR); j+=4;
+	tmp.lval[6] = inl(DE4X5_STRR); j+=4;
+	tmp.lval[7] = inl(DE4X5_SIGR); j+=4;
+	ioc->len = j;
+	if (!(status = verify_area(VERIFY_WRITE, (void *)ioc->data, ioc->len))) {
+	    memcpy_tofs(ioc->data, tmp.addr, ioc->len);
+	}
+	break;
+	
+#define DE4X5_DUMP              0x0f /* Dump the DE4X5 Status */
+	
+      case DE4X5_DUMP:
+	j = 0;
+	tmp.addr[j++] = dev->irq;
+	for (i=0; i<ETH_ALEN; i++) {
+	    tmp.addr[j++] = dev->dev_addr[i];
+	}
+	tmp.addr[j++] = lp->rxRingSize;
+	tmp.lval[j>>2] = (long)lp->rx_ring; j+=4;
+	tmp.lval[j>>2] = (long)lp->tx_ring; j+=4;
+	
+	for (i=0;i<lp->rxRingSize-1;i++){
+	    if (i < 3) {
+		tmp.lval[j>>2] = (long)&lp->rx_ring[i].status; j+=4;
+	    }
+	}
 	tmp.lval[j>>2] = (long)&lp->rx_ring[i].status; j+=4;
-      }
-    }
-    tmp.lval[j>>2] = (long)&lp->rx_ring[i].status; j+=4;
-    for (i=0;i<lp->txRingSize-1;i++){
-      if (i < 3) {
+	for (i=0;i<lp->txRingSize-1;i++){
+	    if (i < 3) {
+		tmp.lval[j>>2] = (long)&lp->tx_ring[i].status; j+=4;
+	    }
+	}
 	tmp.lval[j>>2] = (long)&lp->tx_ring[i].status; j+=4;
-      }
-    }
-    tmp.lval[j>>2] = (long)&lp->tx_ring[i].status; j+=4;
-      
-    for (i=0;i<lp->rxRingSize-1;i++){
-      if (i < 3) {
+	
+	for (i=0;i<lp->rxRingSize-1;i++){
+	    if (i < 3) {
+		tmp.lval[j>>2] = (s32)lp->rx_ring[i].buf; j+=4;
+	    }
+	}
 	tmp.lval[j>>2] = (s32)lp->rx_ring[i].buf; j+=4;
-      }
-    }
-    tmp.lval[j>>2] = (s32)lp->rx_ring[i].buf; j+=4;
-    for (i=0;i<lp->txRingSize-1;i++){
-      if (i < 3) {
+	for (i=0;i<lp->txRingSize-1;i++){
+	    if (i < 3) {
+		tmp.lval[j>>2] = (s32)lp->tx_ring[i].buf; j+=4;
+	    }
+	}
 	tmp.lval[j>>2] = (s32)lp->tx_ring[i].buf; j+=4;
-      }
+	
+	for (i=0;i<lp->rxRingSize;i++){
+	    tmp.lval[j>>2] = lp->rx_ring[i].status; j+=4;
+	}
+	for (i=0;i<lp->txRingSize;i++){
+	    tmp.lval[j>>2] = lp->tx_ring[i].status; j+=4;
+	}
+	
+	tmp.lval[j>>2] = inl(DE4X5_BMR);  j+=4;
+	tmp.lval[j>>2] = inl(DE4X5_TPD);  j+=4;
+	tmp.lval[j>>2] = inl(DE4X5_RPD);  j+=4;
+	tmp.lval[j>>2] = inl(DE4X5_RRBA); j+=4;
+	tmp.lval[j>>2] = inl(DE4X5_TRBA); j+=4;
+	tmp.lval[j>>2] = inl(DE4X5_STS);  j+=4;
+	tmp.lval[j>>2] = inl(DE4X5_OMR);  j+=4;
+	tmp.lval[j>>2] = inl(DE4X5_IMR);  j+=4;
+	tmp.lval[j>>2] = lp->chipset; j+=4; 
+	if (lp->chipset == DC21140) {
+	    tmp.lval[j>>2] = inl(DE4X5_GEP);  j+=4;
+	} else {
+	    tmp.lval[j>>2] = inl(DE4X5_SISR); j+=4;
+	    tmp.lval[j>>2] = inl(DE4X5_SICR); j+=4;
+	    tmp.lval[j>>2] = inl(DE4X5_STRR); j+=4;
+	    tmp.lval[j>>2] = inl(DE4X5_SIGR); j+=4; 
+	}
+	tmp.lval[j>>2] = lp->phy[lp->active].id; j+=4; 
+	if (lp->phy[lp->active].id) {
+	    tmp.lval[j>>2] = lp->active; j+=4; 
+	    tmp.lval[j>>2]=mii_rd(MII_CR,lp->phy[lp->active].addr,DE4X5_MII); j+=4;
+	    tmp.lval[j>>2]=mii_rd(MII_SR,lp->phy[lp->active].addr,DE4X5_MII); j+=4;
+	    tmp.lval[j>>2]=mii_rd(MII_ID0,lp->phy[lp->active].addr,DE4X5_MII); j+=4;
+	    tmp.lval[j>>2]=mii_rd(MII_ID1,lp->phy[lp->active].addr,DE4X5_MII); j+=4;
+	    if (lp->phy[lp->active].id != BROADCOM_T4) {
+		tmp.lval[j>>2]=mii_rd(MII_ANA,lp->phy[lp->active].addr,DE4X5_MII); j+=4;
+		tmp.lval[j>>2]=mii_rd(MII_ANLPA,lp->phy[lp->active].addr,DE4X5_MII); j+=4;
+	    }
+	    tmp.lval[j>>2]=mii_rd(0x10,lp->phy[lp->active].addr,DE4X5_MII); j+=4;
+	    if (lp->phy[lp->active].id != BROADCOM_T4) {
+		tmp.lval[j>>2]=mii_rd(0x11,lp->phy[lp->active].addr,DE4X5_MII); j+=4;
+		tmp.lval[j>>2]=mii_rd(0x12,lp->phy[lp->active].addr,DE4X5_MII); j+=4;
+	    } else {
+		tmp.lval[j>>2]=mii_rd(0x14,lp->phy[lp->active].addr,DE4X5_MII); j+=4;
+	    }
+	}
+	
+	tmp.addr[j++] = lp->txRingSize;
+	tmp.addr[j++] = dev->tbusy;
+	
+	ioc->len = j;
+	if (!(status = verify_area(VERIFY_WRITE, (void *)ioc->data, ioc->len))) {
+	    memcpy_tofs(ioc->data, tmp.addr, ioc->len);
+	}
+	
+	break;
+      default:
+	status = -EOPNOTSUPP;
     }
-    tmp.lval[j>>2] = (s32)lp->tx_ring[i].buf; j+=4;
-      
-    for (i=0;i<lp->rxRingSize;i++){
-      tmp.lval[j>>2] = lp->rx_ring[i].status; j+=4;
-    }
-    for (i=0;i<lp->txRingSize;i++){
-      tmp.lval[j>>2] = lp->tx_ring[i].status; j+=4;
-    }
-
-    tmp.lval[j>>2] = inl(DE4X5_STS); j+=4;
-    tmp.lval[j>>2] = inl(DE4X5_BMR); j+=4;
-    tmp.lval[j>>2] = inl(DE4X5_IMR); j+=4;
-    tmp.lval[j>>2] = inl(DE4X5_OMR); j+=4;
-    tmp.lval[j>>2] = inl(DE4X5_SISR); j+=4;
-    tmp.lval[j>>2] = inl(DE4X5_SICR); j+=4;
-    tmp.lval[j>>2] = inl(DE4X5_STRR); j+=4;
-    tmp.lval[j>>2] = inl(DE4X5_SIGR); j+=4; 
-    
-    tmp.addr[j++] = lp->txRingSize;
-    tmp.addr[j++] = dev->tbusy;
-      
-    ioc->len = j;
-    if (!(status = verify_area(VERIFY_WRITE, (void *)ioc->data, ioc->len))) {
-      memcpy_tofs(ioc->data, tmp.addr, ioc->len);
-    }
-
-    break;
-  default:
-    status = -EOPNOTSUPP;
-  }
-
-  return status;
+    
+    return status;
 }
 
 #ifdef MODULE
+/*
+** Note now that module autoprobing is allowed under EISA and PCI. The
+** IRQ lines will not be auto-detected; instead I'll rely on the BIOSes
+** to "do the right thing".
+**
+** NB: Current register_netdevice() code does not permit assigning io=0 as
+** this driver will autoprobe all instances of acceptable DECchips. The
+** cleanup_module() code needs work still....(just to unload modules owned
+** by this driver).
+*/
 static char devicename[9] = { 0, };
 static struct device thisDE4X5 = {
-  devicename, /* device name is inserted by linux/drivers/net/net_init.c */
-  0, 0, 0, 0,
-  0x2000, 10, /* I/O address, IRQ */
-  0, 0, 0, NULL, de4x5_probe };
-	
-static int io=0x000b;	/* EDIT THESE LINES FOR YOUR CONFIGURATION */
-static int irq=10;	/* or use the insmod io= irq= options 		*/
+    devicename,   /* device name inserted by /linux/drivers/net/net_init.c  */
+    0, 0, 0, 0,
+    0, 0,         /* I/O address, IRQ                                       */
+    0, 0, 0, NULL, de4x5_probe };
+
+static int io=0x0b; /* EDIT THESE LINES FOR YOUR CONFIGURATION              */
 
 int
 init_module(void)
 {
-  thisDE4X5.base_addr=io;
-  thisDE4X5.irq=irq;
-  if (register_netdev(&thisDE4X5) != 0)
-    return -EIO;
-  return 0;
+    struct device *p  = (struct device *)&thisDE4X5;
+    
+    thisDE4X5.base_addr = io;                   /* Now autoprobe the module */
+    thisDE4X5.irq = 0;
+    
+    for (; p!=NULL; p=p->next) {
+	if (register_netdev(p) != 0)
+	  return -EIO;
+    }
+    io=0;
+    return 0;
 }
 
 void
 cleanup_module(void)
 {
-  struct de4x5_private *lp = (struct de4x5_private *) thisDE4X5.priv;
+    struct de4x5_private *lp = (struct de4x5_private *) thisDE4X5.priv;
+    struct device *p  = (struct device *)&thisDE4X5;
+    int keep_loaded = 0;
+    
+    for (; p!=NULL; p=p->next) {
+	keep_loaded += (p->flags & IFF_UP);     /* Is an interface up?       */
+    }
+    
+    if (keep_loaded) {
+	printk("de4x5: Cannot unload modules - %d interface%s%s still active.\n",
+	       keep_loaded, (keep_loaded>1 ? "s ": " "),
+	       (keep_loaded>1 ? "are": "is"));
+	return;
+    }
+    
+    for (p=thisDE4X5.next; p!=NULL; p=p->next) {
+	if (p->priv) {                          /* Private area allocated?   */
+	    struct de4x5_private *lp = (struct de4x5_private *)p->priv;
+	    if (lp->cache.buf) {                /* MAC buffers allocated?    */
+		kfree(lp->cache.buf);           /* Free the MAC buffers      */
+	    }
+	    release_region(p->base_addr, (lp->bus == PCI ? 
+					  DE4X5_PCI_TOTAL_SIZE :
+					  DE4X5_EISA_TOTAL_SIZE));
+	    kfree(lp->cache.priv);              /* Free the private area     */
+	}
+	unregister_netdev(p);
+	kfree(p);                               /* Free the device structure */
+    }
+
+    if (thisDE4X5.priv) {
+	if (lp->cache.buf) {                    /* Are MAC buffers allocated */
+            kfree(lp->cache.buf);
+	}
+	release_region(thisDE4X5.base_addr,
+		      (lp->bus == PCI ? 
+		       DE4X5_PCI_TOTAL_SIZE :
+		       DE4X5_EISA_TOTAL_SIZE));
+	kfree(lp->cache.priv);
+	thisDE4X5.priv = NULL;
+    }
+    unregister_netdev(&thisDE4X5);
 
-  if (lp) {
-    kfree_s(bus_to_virt(lp->rx_ring[0].buf), RX_BUFF_SZ * NUM_RX_DESC + ALIGN);
-  }
-  kfree_s(thisDE4X5.priv, sizeof(struct de4x5_private) + ALIGN);
-  thisDE4X5.priv = NULL;
-
-  release_region(thisDE4X5.base_addr, (lp->bus == PCI ? 
-					             DE4X5_PCI_TOTAL_SIZE :
-			                             DE4X5_EISA_TOTAL_SIZE));
-  unregister_netdev(&thisDE4X5);
+    return;
 }
 #endif /* MODULE */
 
 
 /*
  * Local variables:
- *  kernel-compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O2 -m486 -c de4x5.c"
+ *  compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O2 -m486 -c de4x5.c"
  *
- *  module-compile-command: "gcc -D__KERNEL__ -DMODULE -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O2 -m486 -c de4x5.c"
+ *  compile-command: "gcc -D__KERNEL__ -DMODULE -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O2 -m486 -c de4x5.c"
  * End:
  */
-
-
diff -u --recursive --new-file v1.3.71/linux/drivers/net/de4x5.h linux/drivers/net/de4x5.h
--- v1.3.71/linux/drivers/net/de4x5.h	Wed Nov  8 07:11:32 1995
+++ linux/drivers/net/de4x5.h	Wed Mar  6 14:53:46 1996
@@ -25,6 +25,7 @@
 #define DE4X5_APROM  iobase+(0x048 << lp->bus)  /* Ethernet Address PROM */
 #define DE4X5_BROM   iobase+(0x048 << lp->bus)  /* Boot ROM Register */
 #define DE4X5_SROM   iobase+(0x048 << lp->bus)  /* Serial ROM Register */
+#define DE4X5_MII    iobase+(0x048 << lp->bus)  /* MII Interface Register */
 #define DE4X5_DDR    iobase+(0x050 << lp->bus)  /* Data Diagnostic Register */
 #define DE4X5_FDR    iobase+(0x058 << lp->bus)  /* Full Duplex Register */
 #define DE4X5_GPT    iobase+(0x058 << lp->bus)  /* General Purpose Timer Reg.*/
@@ -359,8 +360,18 @@
 #define BROM_DT   0x000000ff       /* Data Byte */
 
 /*
-** DC21041 Serial/Ethernet Address ROM (DE4X5_SROM)
+** DC21041 Serial/Ethernet Address ROM (DE4X5_SROM, DE4X5_MII)
 */
+#define MII_MDI   0x00080000       /* MII Management Data In */
+#define MII_MDO   0x00060000       /* MII Management Mode/Data Out */
+#define MII_MRD   0x00040000       /* MII Management Define Read Mode */
+#define MII_MWR   0x00000000       /* MII Management Define Write Mode */
+#define MII_MDT   0x00020000       /* MII Management Data Out */
+#define MII_MDC   0x00010000       /* MII Management Clock */
+#define MII_RD    0x00004000       /* Read from MII */
+#define MII_WR    0x00002000       /* Write to MII */
+#define MII_SEL   0x00000800       /* Select MII when RESET */
+
 #define SROM_MODE 0x00008000       /* MODE_1: 0,  MODE_0: 1  (read only) */
 #define SROM_RD   0x00004000       /* Read from Boot ROM */
 #define SROM_WR   0x00002000       /* Write to Boot ROM */
@@ -374,6 +385,89 @@
 #define DT_CLK    0x00000002       /* Serial ROM Clock */
 #define DT_CS     0x00000001       /* Serial ROM Chip Select */
 
+#define MII_PREAMBLE 0xffffffff    /* MII Management Preamble */
+#define MII_TEST     0xaaaaaaaa    /* MII Test Signal */
+#define MII_STRD     0x06          /* Start of Frame+Op Code: use low nibble */
+#define MII_STWR     0x0a          /* Start of Frame+Op Code: use low nibble */
+
+#define MII_CR       0x00          /* MII Management Control Register */
+#define MII_SR       0x01          /* MII Management Status Register */
+#define MII_ID0      0x02          /* PHY Identifier Register 0 */
+#define MII_ID1      0x03          /* PHY Identifier Register 1 */
+#define MII_ANA      0x04          /* Auto Negotiation Advertisement */
+#define MII_ANLPA    0x05          /* Auto Negotiation Link Partner Ability */
+#define MII_ANE      0x06          /* Auto Negotiation Expansion */
+#define MII_ANP      0x07          /* Auto Negotiation Next Page TX */
+
+#define DE4X5_MAX_MII 32           /* Maximum address of MII PHY devices */
+
+/*
+** MII Management Control Register
+*/
+#define MII_CR_RST  0x8000         /* RESET the PHY chip */
+#define MII_CR_LPBK 0x4000         /* Loopback enable */
+#define MII_CR_SPD  0x2000         /* 0: 10Mb/s; 1: 100Mb/s */
+#define MII_CR_10   0x0000         /* Set 10Mb/s */
+#define MII_CR_100  0x2000         /* Set 100Mb/s */
+#define MII_CR_ASSE 0x1000         /* Auto Speed Select Enable */
+#define MII_CR_PD   0x0800         /* Power Down */
+#define MII_CR_ISOL 0x0400         /* Isolate Mode */
+#define MII_CR_RAN  0x0200         /* Restart Auto Negotiation */
+#define MII_CR_FDM  0x0100         /* Full Duplex Mode */
+#define MII_CR_CTE  0x0080         /* Collision Test Enable */
+
+/*
+** MII Management Status Register
+*/
+#define MII_SR_T4C  0x8000         /* 100BASE-T4 capable */
+#define MII_SR_TXFD 0x4000         /* 100BASE-TX Full Duplex capable */
+#define MII_SR_TXHD 0x2000         /* 100BASE-TX Half Duplex capable */
+#define MII_SR_TFD  0x1000         /* 10BASE-T Full Duplex capable */
+#define MII_SR_THD  0x0800         /* 10BASE-T Half Duplex capable */
+#define MII_SR_ASSC 0x0020         /* Auto Speed Selection Complete*/
+#define MII_SR_RFD  0x0010         /* Remote Fault Detected */
+#define MII_SR_ANC  0x0008         /* Auto Negotiation capable */
+#define MII_SR_LKS  0x0004         /* Link Status */
+#define MII_SR_JABD 0x0002         /* Jabber Detect */
+#define MII_SR_XC   0x0001         /* Extended Capabilities */
+
+/*
+** MII Management Auto Negotiation Advertisement Register
+*/
+#define MII_ANA_TAF  0x03e0        /* Technology Ability Field */
+#define MII_ANA_T4AM 0x0400        /* T4 Technology Ability Mask */
+#define MII_ANA_TXAM 0x0180        /* TX Technology Ability Mask */
+#define MII_ANA_FDAM 0x0140        /* Full Duplex Technology Ability Mask */
+#define MII_ANA_HDAM 0x02a0        /* Half Duplex Technology Ability Mask */
+#define MII_ANA_100M 0x0380        /* 100Mb Technology Ability Mask */
+#define MII_ANA_10M  0x0060        /* 10Mb Technology Ability Mask */
+#define MII_ANA_CSMA 0x0001        /* CSMA-CD Capable */
+
+/*
+** MII Management Auto Negotiation Remote End Register
+*/
+#define MII_ANLPA_NP   0x8000      /* Next Page (Enable) */
+#define MII_ANLPA_ACK  0x4000      /* Remote Acknowledge */
+#define MII_ANLPA_RF   0x2000      /* Remote Fault */
+#define MII_ANLPA_TAF  0x03e0      /* Technology Ability Field */
+#define MII_ANLPA_T4AM 0x0400      /* T4 Technology Ability Mask */
+#define MII_ANLPA_TXAM 0x0180      /* TX Technology Ability Mask */
+#define MII_ANLPA_FDAM 0x0140      /* Full Duplex Technology Ability Mask */
+#define MII_ANLPA_HDAM 0x02a0      /* Half Duplex Technology Ability Mask */
+#define MII_ANLPA_100M 0x0380      /* 100Mb Technology Ability Mask */
+#define MII_ANLPA_10M  0x0060      /* 10Mb Technology Ability Mask */
+#define MII_ANLPA_CSMA 0x0001      /* CSMA-CD Capable */
+
+/*
+** SROM Media Definitions (ABG SROM Section)
+*/
+#define MEDIA_NWAY     0x0080      /* Nway (Auto Negotiation) on PHY */
+#define MEDIA_MII      0x0040      /* MII Present on the adapter */
+#define MEDIA_FIBRE    0x0008      /* Fibre Media present */
+#define MEDIA_AUI      0x0004      /* AUI Media present */
+#define MEDIA_TP       0x0002      /* TP Media present */
+#define MEDIA_BNC      0x0001      /* BNC Media present */
+
 /*
 ** DC21040 Full Duplex Register (DE4X5_FDR)
 */
@@ -392,11 +486,12 @@
 #define GEP_LNP  0x00000080        /* Link Pass               (input) */
 #define GEP_SLNK 0x00000040        /* SYM LINK                (input) */
 #define GEP_SDET 0x00000020        /* Signal Detect           (input) */
+#define GEP_HRST 0x00000010        /* Hard RESET (to PHY)     (output) */
 #define GEP_FDXD 0x00000008        /* Full Duplex Disable     (output) */
 #define GEP_PHYL 0x00000004        /* PHY Loopback            (output) */
 #define GEP_FLED 0x00000002        /* Force Activity LED on   (output) */
 #define GEP_MODE 0x00000001        /* 0: 10Mb/s,  1: 100Mb/s           */
-#define GEP_INIT 0x0000010f        /* Setup inputs (0) and outputs (1) */
+#define GEP_INIT 0x0000011f        /* Setup inputs (0) and outputs (1) */
 
 
 /*
@@ -556,21 +651,32 @@
 /*
 ** Media / mode state machine definitions
 */
-#define NC         0x0000          /* No Connection */
-#define TP         0x0001          /* 10Base-T */
-#define TP_NW      0x0002          /* 10Base-T with Nway */
-#define BNC        0x0004          /* Thinwire */
-#define AUI        0x0008          /* Thickwire */
-#define BNC_AUI    0x0010          /* BNC/AUI on DC21040 indistinguishable */
-#define ANS        0x0020          /* Intermediate AutoNegotiation State */
-#define EXT_SIA    0x0400	    /* external SIA (as on DEC MULTIA) */
-
-#define _10Mb      0x0040          /* 10Mb/s Ethernet */
-#define _100Mb     0x0080          /* 100Mb/s Ethernet */
-#define SYM_WAIT   0x0100          /* Wait for SYM_LINK */
-#define INIT       0x0200          /* Initial state */
+#define NC              0x0000     /* No Connection */
+#define TP              0x0001     /* 10Base-T */
+#define TP_NW           0x0002     /* 10Base-T with Nway */
+#define BNC             0x0004     /* Thinwire */
+#define AUI             0x0008     /* Thickwire */
+#define BNC_AUI         0x0010     /* BNC/AUI on DC21040 indistinguishable */
+#define ANS             0x0020     /* Intermediate AutoNegotiation State */
+#define ANS_1           0x0021     /* Intermediate AutoNegotiation State */
+
+#define _10Mb           0x0040     /* 10Mb/s Ethernet */
+#define _100Mb          0x0080     /* 100Mb/s Ethernet */
+#define SPD_DET         0x0100     /* Parallel speed detection */
+#define INIT            0x0200     /* Initial state */
+#define EXT_SIA         0x0400     /* External SIA for motherboard chip */
+#define ANS_SUSPECT     0x0802     /* Suspect the ANS (TP) port is down */
+#define TP_SUSPECT      0x0803     /* Suspect the TP port is down */
+#define BNC_AUI_SUSPECT 0x0804     /* Suspect the BNC or AUI port is down */
+#define EXT_SIA_SUSPECT 0x0805     /* Suspect the EXT SIA port is down */
+#define BNC_SUSPECT     0x0806     /* Suspect the BNC port is down */
+#define AUI_SUSPECT     0x0807     /* Suspect the AUI port is down */
+#define _10Mb_SUSPECT   0x0808     /* Suspect 10Mb/s is down */
+#define _100Mb_SUSPECT  0x0809     /* Suspect 100Mb/s is down */
+#define LINK_RESET      0x080a     /* Reset the PHY and re-init auto sense */
 
-#define AUTO       0x4000          /* Auto sense the media or speed */
+#define AUTO            0x4000     /* Auto sense the media or speed */
+#define TIMER_CB        0x80000000 /* Timer callback detection */
 
 /*
 ** Miscellaneous
@@ -587,12 +693,19 @@
 #define POLL_DEMAND          1
 
 #define LOST_MEDIA_THRESHOLD 3
+#define LOST_MEDIA           (lp->lostMedia > LOST_MEDIA_THRESHOLD)
 
 #define MASK_INTERRUPTS      1
 #define UNMASK_INTERRUPTS    0
 
 #define DE4X5_STRLEN         8
 
+#define DE4X5_INIT           0     /* Initialisation time */
+#define DE4X5_RUN            1     /* Run time */
+
+#define DE4X5_SAVE_STATE     0
+#define DE4X5_RESTORE_STATE  1
+
 /*
 ** Address Filtering Modes
 */
@@ -609,9 +722,56 @@
 */
 #define NO                   0
 #define FALSE                0
+#define CLOSED               0
 
-#define YES                  !0
-#define TRUE                 !0
+#define YES                  ~0
+#define TRUE                 ~0
+#define OPEN                 ~0
+
+/*
+** IEEE OUIs for various PHY vendor/chip combos - Reg 2 values only. Since
+** the vendors seem split 50-50 on how to calculate the OUI register values
+** anyway, just reading Reg2 seems reasonable for now [see de4x5_get_oui()].
+*/
+#define NATIONAL_TX 0x2000
+#define BROADCOM_T4 0x03e0
+#define SEEQ_T4     0x0016
+#define CYPRESS_T4  0x0014
+
+/*
+** Speed Selection stuff
+*/
+#define SET_10Mb {\
+  if (lp->phy[lp->active].id) {\
+    mii_wr(MII_CR_10|MII_CR_ASSE,MII_CR,lp->phy[lp->active].addr,DE4X5_MII);\
+    omr = inl(DE4X5_OMR) & ~(OMR_TTM | OMR_PCS | OMR_SCR);\
+    omr |= (de4x5_full_duplex ? OMR_FD : 0) | OMR_TTM;\
+    outl(omr, DE4X5_OMR);\
+    outl(0, DE4X5_GEP);\
+  } else {\
+    omr = (inl(DE4X5_OMR) & ~(OMR_PS | OMR_HBD | OMR_TTM | OMR_PCS | OMR_SCR));\
+    omr |= (de4x5_full_duplex ? OMR_FD : 0);\
+    outl(omr | OMR_TTM, DE4X5_OMR);\
+    outl((de4x5_full_duplex ? 0 : GEP_FDXD), DE4X5_GEP);\
+  }\
+}
+
+#define SET_100Mb {\
+  if (lp->phy[lp->active].id) {\
+    mii_wr(MII_CR_100|MII_CR_ASSE, MII_CR, lp->phy[lp->active].addr, DE4X5_MII);\
+    omr = inl(DE4X5_OMR) & ~(OMR_TTM | OMR_PCS | OMR_SCR);\
+    sr = mii_rd(MII_SR, lp->phy[lp->active].addr, DE4X5_MII);\
+    if (!(sr & MII_ANA_T4AM) && de4x5_full_duplex) omr |= OMR_FD;\
+    outl(omr, DE4X5_OMR);\
+    outl(((!(sr & MII_ANA_T4AM) && de4x5_full_duplex) ? 0:GEP_FDXD)|GEP_MODE,\
+	                                                          DE4X5_GEP);\
+  } else {\
+    omr = (inl(DE4X5_OMR) & ~(OMR_PS | OMR_HBD | OMR_TTM | OMR_PCS | OMR_SCR));\
+    omr |= (de4x5_full_duplex ? OMR_FD : 0);\
+    outl(omr | OMR_PS | OMR_HBD | OMR_PCS | OMR_SCR, DE4X5_OMR);\
+    outl((de4x5_full_duplex ? 0 : GEP_FDXD) | GEP_MODE, DE4X5_GEP);\
+  }\
+}
 
 /*
 ** Include the IOCTL stuff
diff -u --recursive --new-file v1.3.71/linux/drivers/net/depca.c linux/drivers/net/depca.c
--- v1.3.71/linux/drivers/net/depca.c	Sat Mar  2 10:43:31 1996
+++ linux/drivers/net/depca.c	Wed Mar  6 15:04:20 1996
@@ -413,7 +413,7 @@
 void          cleanup_module(void);
 static int    autoprobed = 1, loading_module = 1;
 # else
-static u_char de1xx_irq[] = {2,3,4,5,7,0};
+static u_char de1xx_irq[] = {2,3,4,5,7,9,0};
 static u_char de2xx_irq[] = {5,9,10,11,15,0};
 static u_char de422_irq[] = {5,9,10,11,0};
 static u_char *depca_irq;
@@ -1648,19 +1648,19 @@
     printk("\nDescriptor buffers:\nRX: ");
     for (i=0;i<lp->rxRingMask;i++){
       if (i < 3) {
-	printk("0x%8.8x  ", (u32) readl(&lp->rx_ring[i].base));
+	printk("0x%8.8x  ", readl(&lp->rx_ring[i].base));
       }
     }
-    printk("...0x%8.8x\n", (u32) readl(&lp->rx_ring[i].base));
+    printk("...0x%8.8x\n", readl(&lp->rx_ring[i].base));
     printk("TX: ");
     for (i=0;i<lp->txRingMask;i++){
       if (i < 3) {
-	printk("0x%8.8x  ", (u32) readl(&lp->tx_ring[i].base));
+	printk("0x%8.8x  ", readl(&lp->tx_ring[i].base));
       }
     }
-    printk("...0x%8.8x\n", (u32) readl(&lp->tx_ring[i].base));
+    printk("...0x%8.8x\n", readl(&lp->tx_ring[i].base));
     printk("Initialisation block at 0x%8.8lx\n",lp->sh_mem);
-    printk("\tmode: 0x%4.4x\n", (u16) readw(&p->mode));
+    printk("\tmode: 0x%4.4x\n",readw(&p->mode));
     printk("\tphysical address: ");
     for (i=0;i<ETH_ALEN-1;i++){
       printk("%2.2x:",(u_char)readb(&p->phys_addr[i]));
@@ -1671,8 +1671,8 @@
       printk("%2.2x:",(u_char)readb(&p->mcast_table[i]));
     }
     printk("%2.2x\n",(u_char)readb(&p->mcast_table[i]));
-    printk("\trx_ring at: 0x%8.8x\n", (u32) readl(&p->rx_ring));
-    printk("\ttx_ring at: 0x%8.8x\n", (u32) readl(&p->tx_ring));
+    printk("\trx_ring at: 0x%8.8x\n",readl(&p->rx_ring));
+    printk("\ttx_ring at: 0x%8.8x\n",readl(&p->tx_ring));
     printk("dma_buffs: 0x%8.8lx\n",lp->dma_buffs);
     printk("Ring size:\nRX: %d  Log2(rxRingMask): 0x%8.8x\n", 
 	   (int)lp->rxRingMask + 1, 
@@ -1880,7 +1880,6 @@
 void
 cleanup_module(void)
 {
-  release_region(thisDepca.base_addr, DEPCA_TOTAL_SIZE);
   if (thisDepca.priv) {
     kfree(thisDepca.priv);
     thisDepca.priv = NULL;
@@ -1888,6 +1887,7 @@
   thisDepca.irq=0;
 
   unregister_netdev(&thisDepca);
+  release_region(thisDepca.base_addr, DEPCA_TOTAL_SIZE);
 }
 #endif /* MODULE */
 
diff -u --recursive --new-file v1.3.71/linux/drivers/net/ewrk3.c linux/drivers/net/ewrk3.c
--- v1.3.71/linux/drivers/net/ewrk3.c	Sat Mar  2 10:43:32 1996
+++ linux/drivers/net/ewrk3.c	Wed Mar  6 15:04:20 1996
@@ -70,7 +70,7 @@
     0) have a copy of the loadable modules code installed on your system.
     1) copy ewrk3.c from the  /linux/drivers/net directory to your favourite
     temporary directory.
-    2) edit the  source code near  line 1880 to reflect  the I/O address and
+    2) edit the  source code near  line 1898 to reflect  the I/O address and
     IRQ you're using.
     3) compile  ewrk3.c, but include -DMODULE in  the command line to ensure
     that the correct bits are compiled (see end of source code).
@@ -130,7 +130,8 @@
                           Added verify_area() calls in depca_ioctl() from
                           suggestion by <heiko@colossus.escape.de>.
 			  Add new multicasting code.
-      0.41    20-Jan-96   Fix IRQ set up problem reported by <???>.
+      0.41    20-Jan-96   Fix IRQ set up problem reported by 
+                          <kenneth@bbs.sas.ntu.ac.sg>.
 
     =========================================================================
 */
@@ -393,7 +394,7 @@
   icr &= 0x70;
   outb(icr, EWRK3_ICR);                           /* Disable all the IRQs */
 
-  if (nicsr == CSR_TXD|CSR_RXD) {
+  if (nicsr == (CSR_TXD|CSR_RXD)) {
 
     /* Check that the EEPROM is alive and well and not living on Pluto... */
     for (chksum=0, i=0; i<EEPROM_MAX; i+=2) {
@@ -1166,7 +1167,7 @@
 }
 
 /*
-** Set or clear the multicast filter for this adaptor.
+** Set or clear the multicast filter for this adapter.
 */
 static void
 set_multicast_list(struct device *dev)
diff -u --recursive --new-file v1.3.71/linux/drivers/net/ibmtr.c linux/drivers/net/ibmtr.c
--- v1.3.71/linux/drivers/net/ibmtr.c	Sat Mar  2 10:43:32 1996
+++ linux/drivers/net/ibmtr.c	Fri Mar  8 09:43:23 1996
@@ -34,14 +34,32 @@
         - removed redundant information display
         - some code reworking
 
+   Chagnes by Steve Kipisz (bungy@ibm.net or kipisz@vnet.ibm.com)
+                           (January 18 1996):
+        - swapped WWOR and WWCR in ibmtr.h
+        - moved some init code from tok_probe into trdev_init.  The
+          PCMCIA code can call trdev_init to complete initializing
+          the driver.
+	- added -DPCMCIA to support PCMCIA
+	- detecting PCMCIA Card Removal in interrupt handler.  if
+	  ISRP is FF, then a PCMCIA card has been removed
+
    Warnings !!!!!!!!!!!!!!
       This driver is only partially sanitized for support of multiple
       adapters.  It will almost definately fail if more than one
       active adapter is identified.
 */
 	
+#ifdef PCMCIA
+#define MODULE
+#endif
+
 #include <linux/module.h>
 
+#ifdef PCMCIA
+#undef MODULE
+#endif
+
 #define NO_AUTODETECT 1
 #undef NO_AUTODETECT
 #undef ENABLE_PAGING
@@ -86,6 +104,7 @@
 #include <linux/string.h>
 #include <linux/skbuff.h>
 #include <linux/interrupt.h>
+#include <linux/delay.h>
 #include <linux/netdevice.h>
 #include <linux/trdevice.h>
 #include <stddef.h>
@@ -133,8 +152,9 @@
 int tok_probe(struct device *dev);
 unsigned char get_sram_size(struct tok_info *adapt_info);
 
-static void tok_init_card(unsigned long dev_addr);
-static void tok_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+static int  tok_init_card(struct device *dev);
+int trdev_init(struct device *dev);
+void tok_interrupt(int irq, void *dev_id, struct pt_regs *regs);
 
 static void initial_tok_int(struct device *dev);
 
@@ -506,14 +526,8 @@
 
   dev->base_addr=PIOaddr; /* set the value for device */
 
-  dev->open=tok_open;
-  dev->stop=tok_close;
-  dev->hard_start_xmit=tok_send_packet;
-  dev->get_stats = NULL;
-  dev->get_stats = tok_get_stats;
-  dev->set_multicast_list = NULL;
-  tr_setup(dev);
-  tok_init_card((unsigned long)dev);
+  trdev_init(dev);
+  tok_init_card(dev);
 
   return 0;  /* Return 0 to indicate we have found a Token Ring card. */
 }
@@ -541,12 +555,32 @@
               (adapt_info->mmio+ ACA_OFFSET + ACA_RW + RRR_ODD))>>2)+4);
 }
 
+
+int trdev_init(struct device *dev)
+{
+  struct tok_info *ti=(struct tok_info *)dev->priv;
+
+  ti->open_status=CLOSED;
+
+  dev->init=tok_init_card;
+  dev->open=tok_open;
+  dev->stop=tok_close;
+  dev->hard_start_xmit=tok_send_packet;
+  dev->get_stats = NULL;
+  dev->get_stats = tok_get_stats;
+  dev->set_multicast_list = NULL;
+  tr_setup(dev);
+
+  return 0;
+}
+
+
 static int tok_open(struct device *dev) {
 
    struct tok_info *ti=(struct tok_info *)dev->priv;
 
    if(ti->open_status==CLOSED) {
-      tok_init_card((unsigned long)dev);
+      tok_init_card(dev);
    }
 
 	if(ti->open_status==IN_PROGRESS) {
@@ -590,7 +624,7 @@
 	return 0;
 }
 
-static void tok_interrupt (int irq, void *dev_id, struct pt_regs *regs)
+void tok_interrupt (int irq, void *dev_id, struct pt_regs *regs)
 {
 
 	unsigned char status;
@@ -621,9 +655,27 @@
 	
 	status=*(unsigned char *)(ti->mmio + ACA_OFFSET + ACA_RW + ISRP_ODD);
 
+#ifdef PCMCIA
+	/* Check if the PCMCIA card was pulled. */
+	if (status == 0xFF)
+	{
+	   DPRINTK("PCMCIA card removed.\n");
+	   dev->interrupt = 0;
+	   return;
+	}
+
+	/* Check ISRP EVEN too. */
+	if ( *(unsigned char *)(ti->mmio + ACA_OFFSET + ACA_RW + ISRP_EVEN) == 0xFF)
+	{
+	   DPRINTK("PCMCIA card removed.\n");
+	   dev->interrupt = 0;
+	   return;
+	}
+#endif
+
 	if(status & ADAP_CHK_INT) {
 		int i;
-		unsigned char *check_reason=ti->mmio + ntohs(*(unsigned short *)(ti->mmio + ACA_OFFSET + ACA_RW +WWCR_EVEN));
+                unsigned char *check_reason=ti->sram + ntohs(*(unsigned short *)(ti->mmio + ACA_OFFSET + ACA_RW +WWCR_EVEN));
 
 		DPRINTK("adapter check interrupt\n");
 
@@ -941,12 +993,10 @@
 
 }
 
-static void tok_init_card(unsigned long dev_addr) {
+static int tok_init_card(struct device *dev) {
 
    struct tok_info *ti;
 	short PIOaddr;
-	int i;
-	struct  device *dev=(struct device *)dev_addr;
         PIOaddr = dev->base_addr;
         ti=(struct tok_info *) dev->priv;
 
@@ -968,7 +1018,7 @@
 	DPRINTK("resetting card\n");
 #endif
 	outb(0,PIOaddr+ADAPTRESET);
-	for(i=jiffies+5;jiffies<=i;); /* wait 50ms */
+        udelay(50000);
 	outb(0,PIOaddr+ADAPTRESETREL);
 #ifndef TR_NEWFORMAT
 	DPRINTK("card reset\n");
@@ -977,7 +1027,7 @@
         ti->open_status=IN_PROGRESS;
 
 	*(unsigned char *)(ti->mmio + ACA_OFFSET + ACA_SET + ISRP_EVEN)=INT_ENABLE;
-
+        return 0;
 }
 
 static void open_sap(unsigned char type,struct device *dev) {
diff -u --recursive --new-file v1.3.71/linux/drivers/net/ibmtr.h linux/drivers/net/ibmtr.h
--- v1.3.71/linux/drivers/net/ibmtr.h	Sun Feb 11 15:32:45 1996
+++ linux/drivers/net/ibmtr.h	Fri Mar  8 09:43:24 1996
@@ -60,10 +60,10 @@
 /* 00 for 8k, 01 for 16k, 10 for 32k, 11 for 64k  */
 #define WRBR_EVEN       0x02    /* Write region base registers - even and odd */
 #define WRBR_ODD        0x03
-#define WWCR_EVEN       0x04    /* Write window close registers - even and odd */
-#define WWCR_ODD        0x05
-#define WWOR_EVEN       0x06    /* Write window open registers - even and odd */
-#define WWOR_ODD        0x07
+#define WWOR_EVEN       0x04    /* Write window open registers - even and odd */
+#define WWOR_ODD        0x05
+#define WWCR_EVEN       0x06    /* Write window close registers - even and odd */
+#define WWCR_ODD        0x07
 
 /* Interrupt status registers - PC system  - even and odd */
 #define ISRP_EVEN       0x08
diff -u --recursive --new-file v1.3.71/linux/drivers/net/ppp.c linux/drivers/net/ppp.c
--- v1.3.71/linux/drivers/net/ppp.c	Tue Mar  5 10:10:59 1996
+++ linux/drivers/net/ppp.c	Tue Mar  5 10:05:49 1996
@@ -6,10 +6,11 @@
  *  Dynamic PPP devices by Jim Freeman <jfree@caldera.com>.
  *  ppp_tty_receive ``noisy-raise-bug'' fixed by Ove Ewerlid <ewerlid@syscon.uu.se>
  *
- *  ==FILEVERSION 7==
+ *  ==FILEVERSION 960303==
  *
  *  NOTE TO MAINTAINERS:
- *     If you modify this file at all, increment the number above.
+ *     If you modify this file at all, please set the number above to the
+ *     date of the modification as YYMMDD (year month day).
  *     ppp.c is shipped with a PPP distribution as well as with the kernel;
  *     if everyone increases the FILEVERSION number above, then scripts
  *     can do the right thing when deciding whether to install a new ppp.c
@@ -39,21 +40,12 @@
    CHECK_CHARACTERS   - Enable the checking on all received characters for
 			8 data bits, no parity. This adds a small amount of
 			processing for each received character.
-			
-   NEW_SKBUFF	      - Use NET3.020 sk_buff's
 */
 
-/* #define NEW_SKBUFF		1 */
 #define OPTIMIZE_FLAG_TIME	((HZ * 3)/2)
 
 #define CHECK_CHARACTERS	1
 #define PPP_COMPRESS		1
-#define USE_SKB_PROTOCOL 1  /* Set by the installation program! */
-
-#ifdef  NEW_SKBUFF
-#undef  USE_SKB_PROTOCOL
-#define USE_SKB_PROTOCOL 2
-#endif
 
 #ifndef PPP_MAX_DEV
 #define PPP_MAX_DEV	256
@@ -68,7 +60,6 @@
  */
 
 #include <linux/module.h>
-
 #include <endian.h>
 #include <linux/kernel.h>
 #include <linux/sched.h>
@@ -94,26 +85,25 @@
 #include <linux/inet.h>
 #include <linux/ioctl.h>
 
-#ifdef NEW_SKBUFF
-#include <linux/netprotocol.h>
-#else
 typedef struct sk_buff	     sk_buff;
-#define skb_data(skb)	     ((unsigned char *) (skb)->data)
-#endif
+#define skb_data(skb)	     ((__u8 *) (skb)->data)
 
 #include <linux/ip.h>
 #include <linux/tcp.h>
 #include <linux/if_arp.h>
 #include "slhc.h"
+
+#define fcstab	ppp_crc16_table		/* Name of the table in the kernel */
 #include <linux/ppp_defs.h>
+
 #include <linux/socket.h>
 #include <linux/if_ppp.h>
 #include <linux/if_pppvar.h>
 
-#undef   PACKETPTR
-#define  PACKETPTR 1
+#undef	 PACKETPTR
+#define	 PACKETPTR 1
 #include <linux/ppp-comp.h>
-#undef   PACKETPTR
+#undef	 PACKETPTR
 
 #define bsd_decompress	(*ppp->sc_rcomp->decompress)
 #define bsd_compress	(*ppp->sc_xcomp->compress)
@@ -123,11 +113,11 @@
 #endif
 
 #ifndef PPP_LQR
-#define PPP_LQR 0xc025  /* Link Quality Reporting Protocol */
+#define PPP_LQR 0xc025	/* Link Quality Reporting Protocol */
 #endif
 
-int ppp_register_compressor (struct compressor *cp);
-void ppp_unregister_compressor (struct compressor *cp);
+static int ppp_register_compressor (struct compressor *cp);
+static void ppp_unregister_compressor (struct compressor *cp);
 
 /*
  * Local functions
@@ -138,26 +128,27 @@
 static void ppp_kick_tty (struct ppp *, struct ppp_buffer *bfr);
 static int ppp_doframe (struct ppp *);
 static struct ppp *ppp_alloc (void);
-static void ppp_print_buffer (const u_char *, const u_char *, int);
+static struct ppp *ppp_find (int pid_value);
+static void ppp_print_buffer (const __u8 *, const __u8 *, int);
 extern inline void ppp_stuff_char (struct ppp *ppp,
 				   register struct ppp_buffer *buf,
-				   register u_char chr);
+				   register __u8 chr);
 extern inline int lock_buffer (register struct ppp_buffer *buf);
 
-static int rcv_proto_ip         (struct ppp *, u_short, u_char *, int);
-static int rcv_proto_ipx        (struct ppp *, u_short, u_char *, int);
-static int rcv_proto_vjc_comp   (struct ppp *, u_short, u_char *, int);
-static int rcv_proto_vjc_uncomp (struct ppp *, u_short, u_char *, int);
-static int rcv_proto_unknown    (struct ppp *, u_short, u_char *, int);
-static int rcv_proto_lqr        (struct ppp *, u_short, u_char *, int);
-static void ppp_doframe_lower   (struct ppp *, u_char *, int);
-static int ppp_doframe          (struct ppp *);
+static int rcv_proto_ip		(struct ppp *, __u16, __u8 *, int);
+static int rcv_proto_ipx	(struct ppp *, __u16, __u8 *, int);
+static int rcv_proto_vjc_comp	(struct ppp *, __u16, __u8 *, int);
+static int rcv_proto_vjc_uncomp (struct ppp *, __u16, __u8 *, int);
+static int rcv_proto_unknown	(struct ppp *, __u16, __u8 *, int);
+static int rcv_proto_lqr	(struct ppp *, __u16, __u8 *, int);
+static void ppp_doframe_lower	(struct ppp *, __u8 *, int);
+static int ppp_doframe		(struct ppp *);
 
 extern int  ppp_bsd_compressor_init(void);
-static void ppp_proto_ccp (struct ppp *ppp, u_char *dp, int len, int rcvd);
-static int  rcv_proto_ccp (struct ppp *, u_short, u_char *, int);
+static void ppp_proto_ccp (struct ppp *ppp, __u8 *dp, int len, int rcvd);
+static int  rcv_proto_ccp (struct ppp *, __u16, __u8 *, int);
 
-#define ins_char(pbuf,c) (buf_base(pbuf) [(pbuf)->count++] = (u_char)(c))
+#define ins_char(pbuf,c) (buf_base(pbuf) [(pbuf)->count++] = (__u8)(c))
 
 #ifndef OPTIMIZE_FLAG_TIME
 #define OPTIMIZE_FLAG_TIME	0
@@ -189,57 +180,26 @@
 static int ppp_dev_close (struct device *);
 static int ppp_dev_xmit (sk_buff *, struct device *);
 static struct enet_statistics *ppp_dev_stats (struct device *);
-
-#if USE_SKB_PROTOCOL == 0  /* The 1.2.x kernel is here */
-#define dev_alloc_skb(count)        alloc_skb(count, GFP_ATOMIC)
-#define skb_put(skb,count)          skb_data(skb)
-#define get_long_user(addr)	    get_user_long((void *) addr)
-#define get_int_user(addr)	    ((int) get_user_long((void *) addr))
-#define put_byte_user(val,addr)	    put_fs_byte(val,((u_char *) (addr)))
-#define put_long_user(val,addr)	    put_fs_long((val),((void *) (addr)))
-
-static unsigned short ppp_dev_type (sk_buff *, struct device *);
-static int ppp_dev_header (unsigned char *buff, struct device *dev,
-			   unsigned short type, void *daddr, void *saddr,
-			   unsigned len, struct sk_buff *skb);
-
-#else /* The 1.3.x kernel is here */
-#define get_long_user(addr)	    get_user(((int *) addr))
-#define get_int_user(addr)	    ((int) get_user(((int *) addr)))
-#define put_byte_user(val,addr)	    put_user((val),((u_char *) (addr)))
-#define put_long_user(val,addr)	    put_user((val),((int *) (addr)))
-
-static int ppp_dev_header (sk_buff *, struct device *, unsigned short,
-			   void *, void *, unsigned);
-#endif
-
-#ifdef NEW_SKBUFF
-static int ppp_dev_input (struct protocol *self, struct protocol *lower,
-			  sk_buff *skb, void *saddr, void *daddr);
-static int ppp_dev_output (struct protocol *self, sk_buff *skb, int type,
-			   int subid, void *saddr, void *daddr, void *opt);
-static int ppp_dev_getkey(int protocol, int subid, unsigned char *key);
-#else
-static int ppp_dev_rebuild (void *, struct device *, unsigned long,
-			    sk_buff *);
-#endif
-
+static int ppp_dev_header (sk_buff *, struct device *, __u16,
+			   void *, void *, unsigned int);
+static int ppp_dev_rebuild (void *eth, struct device *dev,
+                            unsigned long raddr, struct sk_buff *skb);
 /*
  * TTY callbacks
  */
 
-static int ppp_tty_read (struct tty_struct *, struct file *, u_char *,
+static int ppp_tty_read (struct tty_struct *, struct file *, __u8 *,
 			 unsigned int);
-static int ppp_tty_write (struct tty_struct *, struct file *, const u_char *,
+static int ppp_tty_write (struct tty_struct *, struct file *, const __u8 *,
 			  unsigned int);
 static int ppp_tty_ioctl (struct tty_struct *, struct file *, unsigned int,
-			  unsigned long);
+                          unsigned long);
 static int ppp_tty_select (struct tty_struct *tty, struct inode *inode,
 		      struct file *filp, int sel_type, select_table * wait);
 static int ppp_tty_open (struct tty_struct *);
 static void ppp_tty_close (struct tty_struct *);
 static int ppp_tty_room (struct tty_struct *tty);
-static void ppp_tty_receive (struct tty_struct *tty, const u_char * cp,
+static void ppp_tty_receive (struct tty_struct *tty, const __u8 * cp,
 			     char *fp, int count);
 static void ppp_tty_wakeup (struct tty_struct *tty);
 
@@ -247,7 +207,7 @@
 #define CHECK_PPP_VOID()  if (!ppp->inuse) { printk (ppp_warning, __LINE__); return;}
 
 #define in_xmap(ppp,c)	(ppp->xmit_async_map[(c) >> 5] & (1 << ((c) & 0x1f)))
-#define in_rmap(ppp,c)	((((unsigned int) (u_char) (c)) < 0x20) && \
+#define in_rmap(ppp,c)	((((unsigned int) (__u8) (c)) < 0x20) && \
 			ppp->recv_async_map & (1 << (c)))
 
 #define bset(p,b)	((p)[(b) >> 5] |= (1 << ((b) & 0x1f)))
@@ -258,47 +218,43 @@
 #define ppp2dev(ppp)	((struct device *) ppp->dev)
 
 struct ppp_hdr {
-	unsigned char address;
-	unsigned char control;
-	unsigned char protocol[2];
+	__u8 address;
+	__u8 control;
+	__u8 protocol[2];
 };
 
 #define PPP_HARD_HDR_LEN	(sizeof (struct ppp_hdr))
 
-typedef struct  ppp_ctrl {
+typedef struct	ppp_ctrl {
 	struct ppp_ctrl *next;		/* Next structure in the list	*/
-	char            name [8];	/* Name of the device		*/
-	struct ppp      ppp;		/* PPP control table		*/
-	struct device   dev;		/* Device information table	*/
+	char		name [8];	/* Name of the device		*/
+	struct ppp	ppp;		/* PPP control table		*/
+	struct device	dev;		/* Device information table	*/
 } ppp_ctrl_t;
 
 static ppp_ctrl_t *ppp_list = NULL;
 
 #define ctl2ppp(ctl) (struct ppp *)    &ctl->ppp
 #define ctl2dev(ctl) (struct device *) &ctl->dev
-#undef  PPP_NRUNIT
+#undef	PPP_NRUNIT
 
 /* Buffer types */
-#define BUFFER_TYPE_DEV_RD	0  /* ppp read buffer       */
-#define BUFFER_TYPE_TTY_WR	1  /* tty write buffer      */
-#define BUFFER_TYPE_DEV_WR	2  /* ppp write buffer      */
-#define BUFFER_TYPE_TTY_RD	3  /* tty read buffer       */
+#define BUFFER_TYPE_DEV_RD	0  /* ppp read buffer	    */
+#define BUFFER_TYPE_TTY_WR	1  /* tty write buffer	    */
+#define BUFFER_TYPE_DEV_WR	2  /* ppp write buffer	    */
+#define BUFFER_TYPE_TTY_RD	3  /* tty read buffer	    */
 #define BUFFER_TYPE_VJ		4  /* vj compression buffer */
 
 /* Define this string only once for all macro envocations */
 static char ppp_warning[] = KERN_WARNING "PPP: ALERT! not INUSE! %d\n";
 
-static char szVersion[]         = PPP_VERSION;
+static char szVersion[]		= PPP_VERSION;
  
-#ifdef NEW_SKBUFF
-static struct protocol proto_ppp;
-#endif
-
 /*
  * Information for the protocol decoder
  */
 
-typedef int (*pfn_proto)  (struct ppp *, u_short, u_char *, int);
+typedef int (*pfn_proto)  (struct ppp *, __u16, __u8 *, int);
 
 typedef struct ppp_proto_struct {
 	int		proto;
@@ -307,24 +263,16 @@
 
 static
 ppp_proto_type proto_list[] = {
-	{ PPP_IP,         rcv_proto_ip         },
-	{ PPP_IPX,        rcv_proto_ipx        },
-	{ PPP_VJC_COMP,   rcv_proto_vjc_comp   },
+	{ PPP_IP,	  rcv_proto_ip	       },
+	{ PPP_IPX,	  rcv_proto_ipx	       },
+	{ PPP_VJC_COMP,	  rcv_proto_vjc_comp   },
 	{ PPP_VJC_UNCOMP, rcv_proto_vjc_uncomp },
-	{ PPP_LQR,        rcv_proto_lqr        },
-	{ PPP_CCP,        rcv_proto_ccp        },
-	{ 0,              rcv_proto_unknown    }  /* !!! MUST BE LAST !!! */
+	{ PPP_LQR,	  rcv_proto_lqr	       },
+	{ PPP_CCP,	  rcv_proto_ccp	       },
+	{ 0,		  rcv_proto_unknown    }  /* !!! MUST BE LAST !!! */
 };
 
-/*
- * Values for FCS calculations.
- */
-
-#define PPP_INITFCS	0xffff	/* Initial FCS value */
-#define PPP_GOODFCS	0xf0b8	/* Good final FCS value */
-#define PPP_FCS(fcs, c)	(((fcs) >> 8) ^ ppp_crc16_table[((fcs) ^ (c)) & 0xff])
-
-unsigned short ppp_crc16_table[256] =
+__u16 ppp_crc16_table[256] =
 {
 	0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
 	0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
@@ -361,7 +309,7 @@
 };
 
 #ifdef CHECK_CHARACTERS
-static unsigned paritytab[8] =
+static __u32 paritytab[8] =
 {
 	0x96696996, 0x69969669, 0x69969669, 0x96696996,
 	0x69969669, 0x96696996, 0x96696996, 0x69969669
@@ -369,11 +317,11 @@
 #endif
 
 /* local function to store a value into the LQR frame */
-extern inline u_char * store_long (register u_char *p, register int value) {
-	*p++ = (u_char) (value >> 24);
-	*p++ = (u_char) (value >> 16);
-	*p++ = (u_char) (value >>  8);
-	*p++ = (u_char) value;
+extern inline __u8 * store_long (register __u8 *p, register int value) {
+	*p++ = (__u8) (value >> 24);
+	*p++ = (__u8) (value >> 16);
+	*p++ = (__u8) (value >>	 8);
+	*p++ = (__u8) value;
 	return p;
 }
 
@@ -394,9 +342,6 @@
 
 	printk (KERN_INFO
 		"PPP: version %s (dynamic channel allocation)"
-#ifdef NEW_SKBUFF
-		" NEW_SKBUFF"
-#endif
 		"\n", szVersion);
 
 #ifndef MODULE /* slhc module logic has its own copyright announcment */
@@ -409,24 +354,6 @@
 		"PPP Dynamic channel allocation code copyright 1995 "
 		"Caldera, Inc.\n");
 /*
- * Register the protocol for the device
- */
-
-#ifdef NEW_SKBUFF  
-	memset (&proto_ppp, 0, sizeof (proto_ppp));
-
-	proto_ppp.name          = "PPP";
-	proto_ppp.output        = ppp_dev_output;
-	proto_ppp.input         = ppp_dev_input;
-	proto_ppp.bh_input      = ppp_dev_input;
-	proto_ppp.control_event = default_protocol_control;
-	proto_ppp.get_binding   = ppp_dev_getkey;
-	proto_ppp.header_space  = 0; /* PPP_HARD_HDR_LEN; */
-
-	protocol_register(&proto_ppp);
-#endif
-
-/*
  * Register the tty dicipline
  */	
 	(void) memset (&ppp_ldisc, 0, sizeof (ppp_ldisc));
@@ -460,38 +387,31 @@
 ppp_init_dev (struct device *dev)
 {
 	int    indx;
-#ifdef NEW_SKBUFF  
-	dev->default_protocol = &proto_ppp;	/* Our protocol layer is PPP */
-#else
+
 	dev->hard_header      = ppp_dev_header;
-#if USE_SKB_PROTOCOL == 0
-	dev->type_trans       = ppp_dev_type;
-#endif
 	dev->rebuild_header   = ppp_dev_rebuild;
-	dev->hard_header_len  = 0; /* PPP_HARD_HDR_LEN; */
-#endif
+	dev->hard_header_len  = PPP_HARD_HDR_LEN;
 
 	/* device INFO */
-	dev->mtu              = PPP_MTU;
+	dev->mtu	      = PPP_MTU;
 	dev->hard_start_xmit  = ppp_dev_xmit;
-	dev->open             = ppp_dev_open;
-	dev->stop             = ppp_dev_close;
-	dev->get_stats        = ppp_dev_stats;
-	dev->do_ioctl         = ppp_dev_ioctl;
-	dev->addr_len         = 0;
-	dev->type             = ARPHRD_PPP;
-	dev->tx_queue_len     = 10;
-	
+	dev->open	      = ppp_dev_open;
+	dev->stop	      = ppp_dev_close;
+	dev->get_stats	      = ppp_dev_stats;
+	dev->do_ioctl	      = ppp_dev_ioctl;
+	dev->addr_len	      = 0;
+	dev->type	      = ARPHRD_PPP;
+
 	for (indx = 0; indx < DEV_NUMBUFFS; indx++)
 		skb_queue_head_init (&dev->buffs[indx]);
 
 	/* New-style flags */
-	dev->flags      = IFF_POINTOPOINT;
-	dev->family     = AF_INET;
-	dev->pa_addr    = 0;
+	dev->flags	= IFF_POINTOPOINT;
+	dev->family	= AF_INET;
+	dev->pa_addr	= 0;
 	dev->pa_brdaddr = 0;
-	dev->pa_mask    = 0;
-	dev->pa_alen    = 4; /* sizeof (unsigned long) */
+	dev->pa_mask	= 0;
+	dev->pa_alen	= 4; /* sizeof (__u32) */
 
 	return 0;
 }
@@ -516,20 +436,20 @@
 	ppp->xmit_async_map[3] = 0x60000000;
 	ppp->recv_async_map    = 0x00000000;
 
-	ppp->rbuf       = NULL;
-	ppp->wbuf       = NULL;
-	ppp->ubuf       = NULL;
-	ppp->cbuf       = NULL;
-	ppp->slcomp     = NULL;
-	ppp->read_wait  = NULL;
+	ppp->rbuf	= NULL;
+	ppp->wbuf	= NULL;
+	ppp->ubuf	= NULL;
+	ppp->cbuf	= NULL;
+	ppp->slcomp	= NULL;
+	ppp->read_wait	= NULL;
 	ppp->write_wait = NULL;
-	ppp->last_xmit  = jiffies - flag_time;
+	ppp->last_xmit	= jiffies - flag_time;
 
 	/* clear statistics */
 	memset (&ppp->stats, '\0', sizeof (struct pppstat));
 
 	/* Reset the demand dial information */
-	ppp->ddinfo.xmit_idle=         /* time since last NP packet sent */
+	ppp->ddinfo.xmit_idle=	       /* time since last NP packet sent */
 	ppp->ddinfo.recv_idle=jiffies; /* time since last NP packet received */
 
 	/* PPP compression data */
@@ -556,18 +476,13 @@
 
 	if (first_time) {
 		first_time = 0;
-		answer     = ppp_first_time();
+		answer	   = ppp_first_time();
 		if (answer == 0)
 			(void) register_symtab (&ppp_syms);
 	}
-	if (answer)
-		return answer;
-	/*
-	 * Return "not found", so that dev_init() will unlink
-	 * the placeholder device entry for us.
-	 */
-	return ENODEV;
-
+	if (answer == 0)
+		answer = -ENODEV;
+	return answer;
 }
 #endif
 
@@ -591,6 +506,7 @@
 		buf->head   = 0;
 		buf->tail   = 0;
 		buf->fcs    = PPP_INITFCS;
+
 	}
 	return (buf);
 }
@@ -623,9 +539,7 @@
 	state = buf->locked;
 	if (state == 0)
 		buf->locked = 2;
-/*
- * Restore the flags and return the previous state. 0 implies success.
- */
+
 	restore_flags (flags);
 	return (state);
 }
@@ -704,12 +618,12 @@
 	ppp->cbuf = new_cbuf;
 	ppp->tbuf = new_tbuf;
 
-	ppp->rbuf->size -= 80;  /* reserve space for vj header expansion */
+	ppp->rbuf->size -= 80;	/* reserve space for vj header expansion */
 
-	dev->mem_start  = (unsigned long) buf_base (new_wbuf);
-	dev->mem_end    = (unsigned long) (dev->mem_start + mtu);
-	dev->rmem_start = (unsigned long) buf_base (new_rbuf);
-	dev->rmem_end   = (unsigned long) (dev->rmem_start + mru);
+	dev->mem_start	= (__u32) buf_base (new_wbuf);
+	dev->mem_end	= (__u32) (dev->mem_start + mtu);
+	dev->rmem_start = (__u32) buf_base (new_rbuf);
+	dev->rmem_end	= (__u32) (dev->rmem_start + mru);
 /*
  *  Update the parameters for the new buffer sizes
  */
@@ -725,7 +639,7 @@
 	ppp->xbuf   = NULL;
 
 	ppp->tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP);
-	ppp->flags      &= ~SC_XMIT_BUSY;
+	ppp->flags	&= ~SC_XMIT_BUSY;
 
 	sti ();
 /*
@@ -774,12 +688,16 @@
 
 	ppp_ccp_closed (ppp);
 
+        /* Ensure that the pppd process is not hanging on select() */
+        wake_up_interruptible (&ppp->read_wait);
+        wake_up_interruptible (&ppp->write_wait);
+
 	if (tty != NULL && tty->disc_data == ppp)
 		tty->disc_data = NULL;	/* Break the tty->ppp link */
 
 	if (dev && dev->flags & IFF_UP) {
 		dev_close (dev); /* close the device properly */
-		dev->flags = 0;  /* prevent recursion */
+		dev->flags = 0;	 /* prevent recursion */
 	}
 
 	ppp_free_buf (ppp->rbuf);
@@ -813,7 +731,7 @@
  */
 
 static void
-ppp_tty_close (struct tty_struct *tty)
+ppp_tty_close_local (struct tty_struct *tty, int sc_xfer)
 {
 	struct ppp *ppp = tty2ppp (tty);
 
@@ -824,6 +742,7 @@
 				       "ppp: trying to close unopened tty!\n");
 		} else {
 			CHECK_PPP_VOID();
+			ppp->sc_xfer = sc_xfer;
 			if (ppp->flags & SC_DEBUG)
 				printk (KERN_INFO "ppp: channel %s closing.\n",
 					ppp2dev(ppp) -> name);
@@ -833,6 +752,12 @@
 	}
 }
 
+static void
+ppp_tty_close (struct tty_struct *tty)
+{
+	ppp_tty_close_local (tty, 0);
+}
+
 /*
  * TTY callback.
  *
@@ -843,6 +768,7 @@
 ppp_tty_open (struct tty_struct *tty)
 {
 	struct ppp *ppp = tty2ppp (tty);
+	int indx;
 /*
  * There should not be an existing table for this slot.
  */
@@ -857,7 +783,13 @@
 /*
  * Allocate the structure from the system
  */
-	ppp = ppp_alloc();
+	ppp = ppp_find(current->pid);
+	if (ppp == NULL) {
+                ppp = ppp_find(0);
+        	if (ppp == NULL)
+			ppp = ppp_alloc();
+        }
+
 	if (ppp == NULL) {
 		if (ppp->flags & SC_DEBUG)
 			printk (KERN_ERR
@@ -912,6 +844,9 @@
 		printk (KERN_INFO "ppp: channel %s open\n",
 			ppp2dev(ppp)->name);
 
+	for (indx = 0; indx < NUM_NP; ++indx)
+		ppp->sc_npmode[indx] = NPMODE_PASS;
+
 	MOD_INC_USE_COUNT;
 	return (ppp->line);
 }
@@ -953,7 +888,7 @@
  * This could occur should the carrier drop.
  */
 	if (actual < 0) {
-	        ppp->stats.ppp_oerrors++;
+		ppp->stats.ppp_oerrors++;
 		actual = count;
 	} else
 		ppp->bytes_sent += actual;
@@ -978,7 +913,7 @@
  */
 			if (ppp2dev (ppp) -> flags & IFF_UP) {
 				if (xbuf->type == BUFFER_TYPE_DEV_WR)
-				        ppp2dev (ppp)->tbusy = 0;
+					ppp2dev (ppp)->tbusy = 0;
 				mark_bh (NET_BH);
 			}
 /*
@@ -1061,8 +996,8 @@
 /*
  * Control the flags which are best performed with the interrupts masked.
  */
-	xbuf->locked     = 1;
-	xbuf->tail       = 0;
+	xbuf->locked	 = 1;
+	xbuf->tail	 = 0;
 /*
  * If the transmitter is busy then place the buffer on the appropriate
  * priority queue.
@@ -1078,9 +1013,9 @@
 /*
  * If the transmitter is not busy then this is the highest priority frame
  */
-	ppp->flags      &= ~SC_XMIT_BUSY;
+	ppp->flags	&= ~SC_XMIT_BUSY;
 	ppp->tty->flags |= (1 << TTY_DO_WRITE_WAKEUP);
-	ppp->xbuf        = xbuf;
+	ppp->xbuf	 = xbuf;
 	restore_flags (flags);
 /*
  * Do the "tty wakeup_code" to actually send this buffer.
@@ -1113,12 +1048,12 @@
  */
 
 static void
-ppp_tty_receive (struct tty_struct *tty, const u_char * data,
+ppp_tty_receive (struct tty_struct *tty, const __u8 * data,
 		 char *flags, int count)
 {
 	register struct ppp *ppp = tty2ppp (tty);
 	register struct ppp_buffer *buf = NULL;
-	u_char chr;
+	__u8 chr;
 /*
  * Fetch the pointer to the buffer. Be careful about race conditions.
  */
@@ -1249,7 +1184,7 @@
  */
 
 static int
-ppp_rcv_rx (struct ppp *ppp, unsigned short proto, u_char * data, int count)
+ppp_rcv_rx (struct ppp *ppp, __u16 proto, __u8 * data, int count)
 {
 	sk_buff *skb = dev_alloc_skb (count);
 /*
@@ -1265,13 +1200,9 @@
 /*
  * Move the received data from the input buffer to the skb buffer.
  */
-	skb->dev = ppp2dev (ppp);	/* We are the device */
-#if USE_SKB_PROTOCOL == 0
-	skb->len = count;
-#else
+	skb->dev      = ppp2dev (ppp);	/* We are the device */
 	skb->protocol = proto;
 	skb->mac.raw  = skb_data(skb);
-#endif
 	memcpy (skb_put(skb,count), data, count);	/* move data */
 /*
  * Tag the frame and kick it to the proper receive routine
@@ -1287,12 +1218,11 @@
  */
 
 static int
-rcv_proto_ip (struct ppp *ppp, unsigned short proto, u_char * data, int count)
+rcv_proto_ip (struct ppp *ppp, __u16 proto, __u8 * data, int count)
 {
-	if (ppp2dev (ppp)->flags & IFF_UP) {
-		if (count > 0)
+	if ((ppp2dev (ppp)->flags & IFF_UP) && (count > 0))
+		if (ppp->sc_npmode[NP_IP] == NPMODE_PASS)
 			return ppp_rcv_rx (ppp, htons (ETH_P_IP), data, count);
-	}
 	return 0;
 }
 
@@ -1301,14 +1231,10 @@
  */
 
 static int
-rcv_proto_ipx (struct ppp *ppp, unsigned short proto, u_char * data, int count)
+rcv_proto_ipx (struct ppp *ppp, __u16 proto, __u8 * data, int count)
 {
-#ifdef NEW_SKBUFF
-	if (ppp2dev (ppp)->flags & IFF_UP) {
-		if (count > 0)
-			return ppp_rcv_rx (ppp, htons (ETH_P_IPX), data, count);
-	} else
-#endif
+	if (((ppp2dev (ppp)->flags & IFF_UP) != 0) && (count > 0))
+		return ppp_rcv_rx (ppp, htons (ETH_P_IPX), data, count);
 	return 0;
 }
 
@@ -1317,8 +1243,8 @@
  */
 
 static int
-rcv_proto_vjc_comp (struct ppp *ppp, unsigned short proto,
-		    u_char *data, int count)
+rcv_proto_vjc_comp (struct ppp *ppp, __u16 proto,
+		    __u8 *data, int count)
 {
 	if ((ppp->flags & SC_REJ_COMP_TCP) == 0) {
 		int new_count = slhc_uncompress (ppp->slcomp, data, count);
@@ -1337,8 +1263,8 @@
  */
 
 static int
-rcv_proto_vjc_uncomp (struct ppp *ppp, unsigned short proto,
-		      u_char *data, int count)
+rcv_proto_vjc_uncomp (struct ppp *ppp, __u16 proto,
+		      __u8 *data, int count)
 {
 	if ((ppp->flags & SC_REJ_COMP_TCP) == 0) {
 		if (slhc_remember (ppp->slcomp, data, count) > 0) {
@@ -1356,15 +1282,15 @@
  */
 
 static int
-rcv_proto_unknown (struct ppp *ppp, unsigned short proto,
-		   u_char *data, int len)
+rcv_proto_unknown (struct ppp *ppp, __u16 proto,
+		   __u8 *data, int len)
 {
 	int totlen;
 	register int current_idx;
 
 #define PUTC(c)						 \
 {							 \
-    buf_base (ppp->ubuf) [current_idx++] = (u_char) (c); \
+    buf_base (ppp->ubuf) [current_idx++] = (__u8) (c); \
     current_idx &= ppp->ubuf->size;			 \
     if (current_idx == ppp->ubuf->tail)			 \
 	    goto failure;				 \
@@ -1439,10 +1365,10 @@
  * immediate or the compressors will become confused on the peer.
  */
 
-static void ppp_proto_ccp (struct ppp *ppp, u_char *dp, int len, int rcvd)
+static void ppp_proto_ccp (struct ppp *ppp, __u8 *dp, int len, int rcvd)
 {
 	int slen    = CCP_LENGTH(dp);
-	u_char *opt = dp   + CCP_HDRLEN;
+	__u8 *opt = dp	 + CCP_HDRLEN;
 	int opt_len = slen - CCP_HDRLEN;
 
 	if (slen > len)
@@ -1484,7 +1410,7 @@
 			     opt_len,
 			     ppp2dev (ppp)->base_addr,
 			     0,
-			     ppp->flags & SC_DEBUG))
+			     ppp->flags))
 				ppp->flags |= SC_COMP_RUN;
 			break;
 		}
@@ -1501,7 +1427,7 @@
 		     ppp2dev (ppp)->base_addr,
 		     0,
 		     ppp->mru,
-		     ppp->flags & SC_DEBUG)) {
+		     ppp->flags)) {
 			ppp->flags |= SC_DECOMP_RUN;
 			ppp->flags &= ~(SC_DC_ERROR | SC_DC_FERROR);
 		}
@@ -1527,7 +1453,7 @@
 }
 
 static int
-rcv_proto_ccp (struct ppp *ppp, unsigned short proto, u_char *dp, int len)
+rcv_proto_ccp (struct ppp *ppp, __u16 proto, __u8 *dp, int len)
 {
 	ppp_proto_ccp (ppp, dp, len, 1);
 	return rcv_proto_unknown (ppp, proto, dp, len);
@@ -1535,46 +1461,21 @@
 
 /*
  * Handle a LQR packet.
- *
- * The LQR packet is passed along to the pppd process just like any
- * other PPP frame. The difference is that some processing needs to be
- * performed to append the current data to the end of the frame.
  */
 
 static int
-rcv_proto_lqr (struct ppp *ppp, unsigned short proto, u_char * data, int len)
+rcv_proto_lqr (struct ppp *ppp, __u16 proto, __u8 * data, int len)
 {
-#if 0 /* until support is in the pppd process don't corrupt the reject. */
-	register u_char *p;
-	if (len > 8) {
-		if (len < 48)
-			memset (&data [len], '\0', 48 - len);
-/*
- * Fill in the fields from the driver data
- */
-		p = &data [48];
-		p = store_long (p, ++ppp->stats.ppp_ilqrs);
-		p = store_long (p, ppp->stats.ppp_ipackets);
-		p = store_long (p, ppp->stats.ppp_discards);
-		p = store_long (p, ppp->stats.ppp_ierrors);
-		p = store_long (p, ppp->stats.ppp_ioctects + len);
-
-		len = 68;
-	}
-#endif
-/*
- * Pass the frame to the pppd daemon.
- */
 	return rcv_proto_unknown (ppp, proto, data, len);
 }
 
 /* on entry, a received frame is in ppp->rbuf.bufr
    check it and dispose as appropriate */
 
-static void ppp_doframe_lower (struct ppp *ppp, u_char *data, int count)
+static void ppp_doframe_lower (struct ppp *ppp, __u8 *data, int count)
 {
-	u_short		proto = PPP_PROTOCOL (data);
-	ppp_proto_type  *proto_ptr;
+	__u16		proto = PPP_PROTOCOL (data);
+	ppp_proto_type	*proto_ptr;
 /*
  * Ignore empty frames
  */
@@ -1610,11 +1511,11 @@
 static int
 ppp_doframe (struct ppp *ppp)
 {
-	u_char	*data = buf_base (ppp->rbuf);
+	__u8	*data = buf_base (ppp->rbuf);
 	int	count = ppp->rbuf->count;
 	int	addr, ctrl, proto;
 	int	new_count;
-	u_char *new_data;
+	__u8 *new_data;
 /*
  * If there is a pending error from the receiver then log it and discard
  * the damaged frame.
@@ -1670,9 +1571,9 @@
 /*
  * Obtain the protocol from the frame
  */
-	proto = (u_short) *data++;
+	proto = (__u16) *data++;
 	if ((proto & 1) == 0) {
-		proto = (proto << 8) | (u_short) *data++;
+		proto = (proto << 8) | (__u16) *data++;
 		--count;
 	}
 /*
@@ -1684,7 +1585,7 @@
 	*(--data) = proto >> 8;
 	*(--data) = ctrl;
 	*(--data) = addr;
-	count    += 3;
+	count	 += 3;
 /*
  * Process the active decompressor.
  */
@@ -1770,11 +1671,11 @@
 */
 
 static int
-ppp_tty_read (struct tty_struct *tty, struct file *file, u_char * buf,
+ppp_tty_read (struct tty_struct *tty, struct file *file, __u8 * buf,
 	      unsigned int nr)
 {
 	struct ppp *ppp = tty2ppp (tty);
-	u_char c;
+	__u8 c;
 	int len, indx;
 
 #define GETC(c)						\
@@ -1784,7 +1685,7 @@
 }
 
 /*
- * Validate the pointer to the PPP structure
+ * Validate the pointers
  */
 	if (!ppp)
 		return -EIO;
@@ -1812,7 +1713,7 @@
 				     "ppp_tty_read: sleeping(ubuf)\n");
 
 			current->timeout = 0;
-			current->state   = TASK_INTERRUPTIBLE;
+			current->state	 = TASK_INTERRUPTIBLE;
 			schedule ();
 
 			if (current->signal & ~current->blocked)
@@ -1868,7 +1769,7 @@
 			printk (KERN_DEBUG "ppp_tty_read: len = %d\n", len);
 /*
  * Ensure that the frame will fit within the caller's buffer. If not, then
- * discard the frame from the input buffer and return an error to the caller.
+ * discard the frame from the input buffer.
  */
 		if (len + 2 > nr) {
 			/* Can't copy it, update us_rbuff_head */
@@ -1898,8 +1799,8 @@
  * Fake the insertion of the ADDRESS and CONTROL information because these
  * were not saved in the buffer.
  */
-		put_byte_user (PPP_ALLSTATIONS, buf++);
-		put_byte_user (PPP_UI,          buf++);
+		put_user (PPP_ALLSTATIONS, buf++);
+		put_user (PPP_UI,	   buf++);
 
 		indx = len;
 /*
@@ -1907,12 +1808,10 @@
  */
 		while (indx-- > 0) {
 			GETC (c);
-			put_byte_user (c, buf);
+			put_user (c, buf);
 			++buf;
 		}
-/*
- * Release the lock and return the character count in the buffer area.
- */
+
 		clear_bit (0, &ppp->ubuf->locked);
 		len += 2; /* Account for ADDRESS and CONTROL bytes */
 		if (ppp->flags & SC_DEBUG)
@@ -1929,7 +1828,7 @@
 
 extern inline void
 ppp_stuff_char (struct ppp *ppp, register struct ppp_buffer *buf,
-		register u_char chr)
+		register __u8 chr)
 {
 /*
  * The buffer should not be full.
@@ -1961,10 +1860,10 @@
 
 static void
 ppp_dev_xmit_lower (struct ppp *ppp, struct ppp_buffer *buf,
-		    u_char *data, int count, int non_ip)
+		    __u8 *data, int count, int non_ip)
 {
-	unsigned short int write_fcs;
-	int     address, control;
+	__u16   write_fcs;
+	int	address, control;
 	int	proto;
 /*
  * Insert the leading FLAG character
@@ -1984,7 +1883,7 @@
  */
 	address = PPP_ADDRESS  (data);
 	control = PPP_CONTROL  (data);
-	proto   = PPP_PROTOCOL (data);
+	proto	= PPP_PROTOCOL (data);
 
 	if (address != PPP_ALLSTATIONS ||
 	    control != PPP_UI ||
@@ -2044,17 +1943,17 @@
  * Send an frame to the remote with the proper bsd compression.
  *
  * Return 0 if frame was queued for transmission.
- *        1 if frame must be re-queued for later driver support.
+ *	  1 if frame must be re-queued for later driver support.
  */
 
 static int
 ppp_dev_xmit_frame (struct ppp *ppp, struct ppp_buffer *buf,
-		    u_char *data, int count)
+		    __u8 *data, int count)
 {
 	int	proto;
-	int     address, control;
-	u_char *new_data;
-	int     new_count;
+	int	address, control;
+	__u8 *new_data;
+	int	new_count;
 /*
  * Print the buffer
  */
@@ -2064,7 +1963,7 @@
  * Determine if the frame may be compressed. Attempt to compress the
  * frame if possible.
  */
-	proto   = PPP_PROTOCOL (data);
+	proto	= PPP_PROTOCOL (data);
 	address = PPP_ADDRESS  (data);
 	control = PPP_CONTROL  (data);
 
@@ -2120,9 +2019,9 @@
  */
 
 static int
-send_revise_frame (register struct ppp *ppp, u_char *data, int len)
+send_revise_frame (register struct ppp *ppp, __u8 *data, int len)
 {
-	u_char *p;
+	__u8 *p;
 
 	switch (PPP_PROTOCOL (data)) {
 /*
@@ -2131,7 +2030,7 @@
  */
 	case PPP_LQR:
 		len = 48;			/* total size of this frame */
-		p   = (u_char *) &data [40];	/* Point to last two items. */
+		p   = (__u8 *) &data [40];	/* Point to last two items. */
 		p   = store_long (p, ppp->stats.ppp_opackets + 1);
 		p   = store_long (p, ppp->stats.ppp_ooctects + len);
 		break;
@@ -2144,9 +2043,7 @@
 			       len  - PPP_HARD_HDR_LEN,
 			       0);
 		break;
-/*
- * All other frame types
- */
+
 	default:
 		break;
 	}
@@ -2160,14 +2057,14 @@
  */
 
 static int
-ppp_tty_write (struct tty_struct *tty, struct file *file, const u_char * data,
+ppp_tty_write (struct tty_struct *tty, struct file *file, const __u8 * data,
 	       unsigned int count)
 {
 	struct ppp *ppp = tty2ppp (tty);
-	u_char *new_data;
+	__u8 *new_data;
 	int status;
 /*
- * Verify the pointer to the PPP data and that the tty is still in PPP mode.
+ * Verify the pointers.
  */
 	if (!ppp)
 		return -EIO;
@@ -2251,8 +2148,8 @@
 	struct ppp_option_data data;
 	int error;
 	int nb;
-	u_char *ptr;
-	u_char ccp_option[CCP_MAX_OPTION_LENGTH];
+	__u8 *ptr;
+	__u8 ccp_option[CCP_MAX_OPTION_LENGTH];
 /*
  * Fetch the compression parameters
  */
@@ -2261,7 +2158,7 @@
 		memcpy_fromfs (&data, odp, sizeof (data));
 		nb  = data.length;
 		ptr = data.ptr;
-		if ((unsigned long) nb >= (unsigned long)CCP_MAX_OPTION_LENGTH)
+		if ((__u32) nb >= (__u32)CCP_MAX_OPTION_LENGTH)
 			nb = CCP_MAX_OPTION_LENGTH;
 	
 		error = verify_area (VERIFY_READ, ptr, nb);
@@ -2275,7 +2172,7 @@
 	if (ccp_option[1] < 2)	/* preliminary check on the length byte */
 		return (-EINVAL);
 
-	cp = find_compressor ((int) (unsigned) (unsigned char) ccp_option[0]);
+	cp = find_compressor ((int) (unsigned int) (__u8) ccp_option[0]);
 	if (cp != (struct compressor *) 0) {
 		/*
 		 * Found a handler for the protocol - try to allocate
@@ -2286,7 +2183,7 @@
 			if (ppp->sc_xc_state != NULL)
 				(*ppp->sc_xcomp->comp_free)(ppp->sc_xc_state);
 
-			ppp->sc_xcomp    = cp;
+			ppp->sc_xcomp	 = cp;
 			ppp->sc_xc_state = cp->comp_alloc(ccp_option, nb);
 
 			if (ppp->sc_xc_state == NULL) {
@@ -2299,7 +2196,7 @@
 		} else {
 			if (ppp->sc_rc_state != NULL)
 				(*ppp->sc_rcomp->decomp_free)(ppp->sc_rc_state);
-			ppp->sc_rcomp    = cp;
+			ppp->sc_rcomp	 = cp;
 			ppp->sc_rc_state = cp->decomp_alloc(ccp_option, nb);
 			if (ppp->sc_rc_state == NULL) {
 				if (ppp->flags & SC_DEBUG)
@@ -2324,12 +2221,12 @@
  */
 
 static int
-ppp_tty_ioctl (struct tty_struct *tty, struct file *file, unsigned int param2,
-	       unsigned long param3)
+ppp_tty_ioctl (struct tty_struct *tty, struct file * file,
+               unsigned int param2, unsigned long param3)
 {
 	struct ppp *ppp = tty2ppp (tty);
 	register int temp_i = 0;
-	int error;
+	int error = 0;
 /*
  * Verify the status of the PPP device.
  */
@@ -2353,7 +2250,7 @@
 		error = verify_area (VERIFY_READ, (void *) param3,
 				     sizeof (temp_i));
 		if (error == 0) {
-			temp_i = get_int_user ((int *) param3);
+			temp_i = get_user ((int *) param3);
 			if (ppp->flags & SC_DEBUG)
 				printk (KERN_INFO
 				 "ppp_tty_ioctl: set mru to %x\n", temp_i);
@@ -2374,7 +2271,7 @@
 			temp_i |= SC_RCV_B7_1 | SC_RCV_B7_0 |
 				  SC_RCV_ODDP | SC_RCV_EVNP;
 #endif
-			put_long_user ((long) temp_i, param3);
+			put_user (temp_i, (int *) param3);
 			if (ppp->flags & SC_DEBUG)
 				printk (KERN_DEBUG
 				"ppp_tty_ioctl: get flags: addr %lx flags "
@@ -2388,7 +2285,7 @@
 		error = verify_area (VERIFY_READ, (void *) param3,
 				     sizeof (temp_i));
 		if (error == 0) {
-			temp_i  = get_int_user (param3) & SC_MASK;
+			temp_i	= get_user ((int *) param3) & SC_MASK;
 			temp_i |= (ppp->flags & ~SC_MASK);
 
 			if ((ppp->flags & SC_CCP_OPEN) &&
@@ -2415,13 +2312,13 @@
 		error = verify_area (VERIFY_WRITE, (void *) param3,
 				     sizeof (temp_i));
 		if (error == 0) {
-			put_long_user (ppp->xmit_async_map[0], param3);
+			put_user (ppp->xmit_async_map[0], (int *) param3);
 			if (ppp->flags & SC_DEBUG)
 				printk (KERN_INFO
 				     "ppp_tty_ioctl: get asyncmap: addr "
-				     "%lx asyncmap %lx\n",
+				     "%lx asyncmap %x\n",
 				     param3,
-				     (unsigned long) ppp->xmit_async_map[0]);
+				     ppp->xmit_async_map[0]);
 		}
 		break;
 /*
@@ -2431,11 +2328,11 @@
 		error = verify_area (VERIFY_READ, (void *) param3,
 				     sizeof (temp_i));
 		if (error == 0) {
-			ppp->xmit_async_map[0] = get_long_user (param3);
+			ppp->xmit_async_map[0] = get_user ((int *) param3);
 			if (ppp->flags & SC_DEBUG)
 				printk (KERN_INFO
-				     "ppp_tty_ioctl: set xmit asyncmap %lx\n",
-				     (unsigned long) ppp->xmit_async_map[0]);
+				     "ppp_tty_ioctl: set xmit asyncmap %x\n",
+				     ppp->xmit_async_map[0]);
 		}
 		break;
 /*
@@ -2445,11 +2342,11 @@
 		error = verify_area (VERIFY_READ, (void *) param3,
 				     sizeof (temp_i));
 		if (error == 0) {
-			ppp->recv_async_map = get_long_user (param3);
+			ppp->recv_async_map = get_user ((int *) param3);
 			if (ppp->flags & SC_DEBUG)
 				printk (KERN_INFO
-				     "ppp_tty_ioctl: set rcv asyncmap %lx\n",
-				     (unsigned long) ppp->recv_async_map);
+				     "ppp_tty_ioctl: set rcv asyncmap %x\n",
+				     ppp->recv_async_map);
 		}
 		break;
 /*
@@ -2459,7 +2356,7 @@
 		error = verify_area (VERIFY_WRITE, (void *) param3,
 				     sizeof (temp_i));
 		if (error == 0) {
-			put_long_user (ppp2dev (ppp)->base_addr, param3);
+			put_user (ppp2dev (ppp)->base_addr, (int *) param3);
 			if (ppp->flags & SC_DEBUG)
 				printk (KERN_INFO
 					"ppp_tty_ioctl: get unit: %ld",
@@ -2473,7 +2370,7 @@
 		error = verify_area (VERIFY_READ, (void *) param3,
 				     sizeof (temp_i));
 		if (error == 0) {
-			temp_i  = (get_int_user (param3) & 0x1F) << 16;
+			temp_i	= (get_user ((int *) param3) & 0x1F) << 16;
 			temp_i |= (ppp->flags & ~0x1F0000);
 
 			if ((ppp->flags | temp_i) & SC_DEBUG)
@@ -2490,13 +2387,13 @@
 				     sizeof (temp_i));
 		if (error == 0) {
 			temp_i = (ppp->flags >> 16) & 0x1F;
-			put_long_user ((long) temp_i, param3);
+			put_user (temp_i, (int *) param3);
 
 			if (ppp->flags & SC_DEBUG)
 				printk (KERN_INFO
 					"ppp_tty_ioctl: get debug level %d\n",
 					temp_i);
-      		}
+		}
 		break;
 /*
  * Get the times since the last send/receive frame operation
@@ -2506,7 +2403,7 @@
 				     sizeof (struct ppp_idle));
 		if (error == 0) {
 			struct ppp_idle cur_ddinfo;
-			unsigned long cur_jiffies = jiffies;
+			__u32 cur_jiffies = jiffies;
 
 			/* change absolute times to relative times. */
 			cur_ddinfo.xmit_idle = (cur_jiffies - ppp->ddinfo.xmit_idle) / HZ;
@@ -2547,9 +2444,9 @@
 
 			memcpy_fromfs (temp_tbl, (void *) param3,
 				       sizeof (ppp->xmit_async_map));
-			temp_tbl[1]  =  0x00000000;
+			temp_tbl[1]  =	0x00000000;
 			temp_tbl[2] &= ~0x40000000;
-			temp_tbl[3] |=  0x60000000;
+			temp_tbl[3] |=	0x60000000;
 
 			if ((temp_tbl[2] & temp_tbl[3]) != 0 ||
 			    (temp_tbl[4] & temp_tbl[5]) != 0 ||
@@ -2572,7 +2469,7 @@
 		error = verify_area (VERIFY_READ, (void *) param3,
 				     sizeof (temp_i));
 		if (error == 0) {
-			temp_i = get_int_user (param3) + 1;
+			temp_i = get_user ((int *) param3) + 1;
 			if (ppp->flags & SC_DEBUG)
 				printk (KERN_INFO
 				     "ppp_tty_ioctl: set maxcid to %d\n",
@@ -2590,6 +2487,55 @@
 			}
 		}
 		break;
+
+    case PPPIOCXFERUNIT:
+		ppp_tty_close_local (tty, current->pid);
+		break;
+
+    case PPPIOCGNPMODE:
+    case PPPIOCSNPMODE:
+		error = verify_area (VERIFY_READ, (void *) param3,
+				     sizeof (struct npioctl));
+		if (error == 0) {
+			struct npioctl npi;
+			memcpy_fromfs (&npi,
+				       (void *) param3,
+				       sizeof (npi));
+
+			switch (npi.protocol) {
+			case PPP_IP:
+				npi.protocol = NP_IP;
+				break;
+			default:
+				error = -EINVAL;
+			}
+
+			if (error != 0)
+				break;
+
+			if (param2 == PPPIOCGNPMODE) {
+				npi.mode = ppp->sc_npmode[npi.protocol];
+				error = verify_area (VERIFY_WRITE,
+						     (void *) param3,
+						     sizeof (npi));
+				if (error != 0)
+					break;
+
+				memcpy_tofs ((void *) param3,
+					     &npi,
+					     sizeof (npi));
+				break;
+			}
+
+			if (npi.mode != ppp->sc_npmode[npi.protocol]) {
+				ppp->sc_npmode[npi.protocol] = npi.mode;
+				if (npi.mode != NPMODE_QUEUE) {
+					/* ppp_requeue(ppp); maybe needed */
+					ppp_tty_wakeup (ppp2tty(ppp));
+				}
+			}
+		}
+		break;
 /*
  * Allow users to read, but not set, the serial port parameters
  */
@@ -2597,6 +2543,19 @@
 	case TCGETA:
 		error = n_tty_ioctl (tty, file, param2, param3);
 		break;
+
+	case FIONREAD:
+		error = verify_area (VERIFY_WRITE,
+				     (void *) param3,
+				     sizeof (int));
+		if (error == 0) {
+			int count = ppp->ubuf->tail - ppp->ubuf->head;
+			if (count < 0)
+				count += (ppp->ubuf->size + 1);
+
+			put_user (count, (int *) param3);
+		}
+		break;
 /*
  *  All other ioctl() events will come here.
  */
@@ -2649,7 +2608,7 @@
 			}
 			clear_bit (0, &ppp->ubuf->locked);
 		}		/* fall through */
-		/*
+/*
  * Exceptions or read errors.
  */
 	case SEL_EX:
@@ -2749,7 +2708,7 @@
 static int
 ppp_dev_ioctl_version (struct ppp *ppp, struct ifreq *ifr)
 {
-        int error;
+	int error;
 	int len;
 	char *result;
 /*
@@ -2802,9 +2761,7 @@
 			temp.vj.vjs_compressedin   = ppp->slcomp->sls_i_compressed;
 		}
 	}
-/*
- * Move the data to the caller's buffer
- */
+
 	if (error == 0)
 		memcpy_tofs (result, &temp, sizeof (temp));
 	return error;
@@ -2883,15 +2840,15 @@
  * Send an IP frame to the remote with vj header compression.
  *
  * Return 0 if frame was queued for transmission.
- *        1 if frame must be re-queued for later driver support.
+ *	  1 if frame must be re-queued for later driver support.
  */
 
 static int
-ppp_dev_xmit_ip1 (struct device *dev, struct ppp *ppp, u_char *data)
+ppp_dev_xmit_ip (struct device *dev, struct ppp *ppp, __u8 *data)
 {
-	int      proto = PPP_IP;
-	int	 len;
-	struct ppp_hdr    *hdr;
+	int		  proto = PPP_IP;
+	int		  len;
+	struct ppp_hdr	  *hdr;
 	struct tty_struct *tty = ppp2tty (ppp);
 /*
  * Obtain the length from the IP header.
@@ -2920,6 +2877,38 @@
 		return 0;
 	}
 /*
+ * Branch on the type of processing for the IP frame.
+ */
+	switch (ppp->sc_npmode[NP_IP]) {
+	case NPMODE_PASS:
+		break;
+
+	case NPMODE_ERROR:
+		if (ppp->flags & SC_DEBUG)
+			printk (KERN_WARNING
+				"ppp_dev_xmit: npmode = NPMODE_ERROR on %s\n",
+				dev->name);
+		return 0;
+
+	case NPMODE_DROP:
+		if (ppp->flags & SC_DEBUG)
+			printk (KERN_WARNING
+				"ppp_dev_xmit: npmode = NPMODE_DROP on %s\n",
+				dev->name);
+		return 0;
+
+	case NPMODE_QUEUE:
+		break;
+
+	default:
+		if (ppp->flags & SC_DEBUG)
+			printk (KERN_WARNING
+				"ppp_dev_xmit: unknown npmode %d on %s\n",
+				ppp->sc_npmode[NP_IP],
+				dev->name);
+		return 0;
+	}
+/*
  * Detect a change in the transfer size
  */
 	if (ppp->mtu != ppp2dev (ppp)->mtu) {
@@ -2929,7 +2918,7 @@
 	}
 /*
  * Acquire the lock on the transmission buffer. If the buffer was busy then
- * mark the device as busy and return "failure to send, try back later" error.
+ * mark the device as busy.
  */
 	if (lock_buffer (ppp->wbuf) != 0) {
 		dev->tbusy = 1;
@@ -2952,7 +2941,7 @@
 				     (ppp->flags & SC_NO_TCP_CCID) == 0);
 
 		if (data[0] & SL_TYPE_COMPRESSED_TCP) {
-			proto    = PPP_VJC_COMP;
+			proto	 = PPP_VJC_COMP;
 			data[0] ^= SL_TYPE_COMPRESSED_TCP;
 		} else {
 			if (data[0] >= SL_TYPE_UNCOMPRESSED_TCP)
@@ -2966,61 +2955,26 @@
 	len  += PPP_HARD_HDR_LEN;
 	hdr   = &((struct ppp_hdr *) data)[-1];
 
-	hdr->address     = PPP_ALLSTATIONS;
-	hdr->control     = PPP_UI;
+	hdr->address	 = PPP_ALLSTATIONS;
+	hdr->control	 = PPP_UI;
 	hdr->protocol[0] = 0;
 	hdr->protocol[1] = proto;
 
-	return ppp_dev_xmit_frame (ppp, ppp->wbuf, (u_char *) hdr, len);
-}
-
-/*
- * This is just an interum solution until the 1.3 kernel's networking is
- * available. The 1.2 kernel has problems with device headers before the
- * buffers.
- *
- * This routine should be deleted, and the ppp_dev_xmit_ip1 routine called
- * by this name.
- */
-
-static int
-ppp_dev_xmit_ip (struct device *dev, struct ppp *ppp, u_char *data)
-{
-	struct ppp_hdr *hdr;
-	int     len;
-	int     answer;
-
-	len = ((struct iphdr *)data) -> tot_len;
-	len = ntohs (len);
-
-	hdr = (struct ppp_hdr *) kmalloc (len + sizeof (struct ppp_hdr),
-					  GFP_ATOMIC);
-
-	if (hdr == NULL)
-		answer = 1;
-	else {
-		memcpy (&hdr[1], data, len);
-		answer = ppp_dev_xmit_ip1 (dev, ppp, (u_char *) &hdr[1]);
-		kfree (hdr);
-	}
-
-	return answer;
+	return ppp_dev_xmit_frame (ppp, ppp->wbuf, (__u8 *) hdr, len);
 }
 
 /*
  * Send an IPX (or any other non-IP) frame to the remote.
  *
  * Return 0 if frame was queued for transmission.
- *        1 if frame must be re-queued for later driver support.
+ *	  1 if frame must be re-queued for later driver support.
  */
-
-#ifdef NEW_SKBUFF
 static int
-ppp_dev_xmit_ipx1 (struct device *dev, struct ppp *ppp,
-		  u_char *data, int len, int proto)
+ppp_dev_xmit_ipx (struct device *dev, struct ppp *ppp,
+		  __u8 *data, int len, int proto)
 {
 	struct tty_struct *tty = ppp2tty (ppp);
-	struct ppp_hdr    *hdr;
+	struct ppp_hdr	  *hdr;
 /*
  * Validate the tty interface
  */
@@ -3052,7 +3006,7 @@
 	}
 /*
  * Acquire the lock on the transmission buffer. If the buffer was busy then
- * mark the device as busy and return "failure to send, try back later" error.
+ * mark the device as busy.
  */
 	if (lock_buffer (ppp->wbuf) != 0) {
 		dev->tbusy = 1;
@@ -3069,44 +3023,13 @@
 	len  += PPP_HARD_HDR_LEN;
 	hdr   = &((struct ppp_hdr *) data)[-1];
 
-	hdr->address     = PPP_ALLSTATIONS;
-	hdr->control     = PPP_UI;
+	hdr->address	 = PPP_ALLSTATIONS;
+	hdr->control	 = PPP_UI;
 	hdr->protocol[0] = proto >> 8;
 	hdr->protocol[1] = proto;
 
-	return ppp_dev_xmit_frame (ppp, ppp->wbuf, (u_char *) hdr, len);
-}
-
-/*
- * This is just an interum solution until the 1.3 kernel's networking is
- * available. The 1.2 kernel has problems with device headers before the
- * buffers.
- *
- * This routine should be deleted, and the ppp_dev_xmit_ipx1 routine called
- * by this name.
- */
-
-static int
-ppp_dev_xmit_ipx (struct device *dev, struct ppp *ppp,
-		  u_char *data, int len, int proto)
-{
-	struct ppp_hdr    *hdr;
-	int     answer;
-
-	hdr = (struct ppp_hdr *) kmalloc (len + sizeof (struct ppp_hdr),
-					  GFP_ATOMIC);
-	if (hdr == NULL)
-		answer = 1;
-	else {
-		memcpy (&hdr[1], data, len);
-		answer = ppp_dev_xmit_ipx1 (dev, ppp, (u_char *) &hdr[1],
-					    len, proto);
-		kfree (hdr);
-	}
-
-	return answer;
+	return ppp_dev_xmit_frame (ppp, ppp->wbuf, (__u8 *) hdr, len);
 }
-#endif
 
 /*
  * Send a frame to the remote.
@@ -3116,8 +3039,8 @@
 ppp_dev_xmit (sk_buff *skb, struct device *dev)
 {
 	int answer, len;
-	u_char *data;
-	struct ppp        *ppp = dev2ppp (dev);
+	__u8		  *data;
+	struct ppp	  *ppp = dev2ppp (dev);
 	struct tty_struct *tty = ppp2tty (ppp);
 /*
  * just a little sanity check.
@@ -3161,14 +3084,12 @@
  * Look at the protocol in the skb to determine the difference between
  * an IP frame and an IPX frame.
  */
-
-#ifdef NEW_SKBUFF
-	switch (skb->protocol) {
-	case htons (ETH_P_IPX):
+	switch (ntohs (skb->protocol)) {
+	case ETH_P_IPX:
 		answer = ppp_dev_xmit_ipx (dev, ppp, data, len, PPP_IPX);
 		break;
 
-	case htons (ETH_P_IP):
+	case ETH_P_IP:
 		answer = ppp_dev_xmit_ip (dev, ppp, data);
 		break;
 
@@ -3176,10 +3097,6 @@
 		dev_kfree_skb (skb, FREE_WRITE);
 		return 0;
 	}
-#else
-	answer = ppp_dev_xmit_ip (dev, ppp, data);
-#endif
-
 /*
  * This is the end of the transmission. Release the buffer if it was sent.
  */
@@ -3200,19 +3117,19 @@
 	struct ppp *ppp = dev2ppp (dev);
 	static struct enet_statistics ppp_stats;
 
-	ppp_stats.rx_packets          = ppp->stats.ppp_ipackets;
-	ppp_stats.rx_errors           = ppp->stats.ppp_ierrors;
-	ppp_stats.rx_dropped          = ppp->stats.ppp_ierrors;
+	ppp_stats.rx_packets	      = ppp->stats.ppp_ipackets;
+	ppp_stats.rx_errors	      = ppp->stats.ppp_ierrors;
+	ppp_stats.rx_dropped	      = ppp->stats.ppp_ierrors;
 	ppp_stats.rx_fifo_errors      = 0;
 	ppp_stats.rx_length_errors    = 0;
 	ppp_stats.rx_over_errors      = 0;
-	ppp_stats.rx_crc_errors       = 0;
+	ppp_stats.rx_crc_errors	      = 0;
 	ppp_stats.rx_frame_errors     = 0;
-	ppp_stats.tx_packets          = ppp->stats.ppp_opackets;
-	ppp_stats.tx_errors           = ppp->stats.ppp_oerrors;
-	ppp_stats.tx_dropped          = 0;
+	ppp_stats.tx_packets	      = ppp->stats.ppp_opackets;
+	ppp_stats.tx_errors	      = ppp->stats.ppp_oerrors;
+	ppp_stats.tx_dropped	      = 0;
 	ppp_stats.tx_fifo_errors      = 0;
-	ppp_stats.collisions          = 0;
+	ppp_stats.collisions	      = 0;
 	ppp_stats.tx_carrier_errors   = 0;
 	ppp_stats.tx_aborted_errors   = 0;
 	ppp_stats.tx_window_errors    = 0;
@@ -3223,88 +3140,53 @@
 	return &ppp_stats;
 }
 
-#ifdef NEW_SKBUFF
-/*
- *	The PPP protocol is currently pure IP (no IPX yet). This defines
- *      the protocol layer which is blank since the driver does all the
- *      cooking.
- */
-
-static int ppp_dev_input (struct protocol *self, struct protocol *lower,
-			  sk_buff *skb, void *saddr, void *daddr)
-{
-	return protocol_pass_demultiplex(self, NULL, skb, NULL, NULL);
-}
-
-static int ppp_dev_output (struct protocol *self, sk_buff *skb, int type,
-			   int subid, void *saddr, void *daddr, void *opt)
-{
-	if(skb->dev==NULL)
-	{
-		printk("ppp_dev_output: No device.\n");
-		kfree_skb(skb, FREE_WRITE);
-		return -1;
-	}
-	dev_queue_xmit(skb, skb->dev, skb->priority);
-	return 0;
-}
-
-static int ppp_dev_getkey(int protocol, int subid, unsigned char *key)
-{
-	switch (protocol)
-	{
-	case htons (ETH_P_IP):
-	case htons (ETH_P_IPX):
-		return 0;
-
-	default:
-		break;
-	}
-
-	return -EAFNOSUPPORT;
-}
-
-#else
-
-#if USE_SKB_PROTOCOL == 0
-/*
- * Called to enquire about the type of the frame in the buffer. Return
- * ETH_P_IP for an IP frame, ETH_P_IPX for an IPX frame.
- */
-
-static unsigned short
-ppp_dev_type (sk_buff *skb, struct device *dev)
-{
-	return (htons (ETH_P_IP));
-}
-#endif
-
-#if USE_SKB_PROTOCOL == 0
-static int ppp_dev_header (unsigned char *buff, struct device *dev,
-			   unsigned short type, void *daddr, void *saddr,
-			   unsigned len, struct sk_buff *skb)
-#else
 static int ppp_dev_header (sk_buff *skb, struct device *dev,
-			   unsigned short type, void *daddr,
-			   void *saddr, unsigned len)
-#endif
+			   __u16 type, void *daddr,
+			   void *saddr, unsigned int len)
 {
 	return (0);
 }
 
 static int
-ppp_dev_rebuild (void *buff, struct device *dev, unsigned long raddr,
-		 sk_buff *skb)
+ppp_dev_rebuild (void *eth, struct device *dev,
+                 unsigned long raddr, struct sk_buff *skb)
 {
 	return (0);
 }
-#endif
 
 /*************************************************************
  * UTILITIES
  *    Miscellany called by various functions above.
  *************************************************************/
 
+/* Locate the previous instance of the PPP channel */
+static struct ppp *
+ppp_find (int pid_value)
+{
+	int		if_num;
+	ppp_ctrl_t	*ctl;
+	struct ppp	*ppp;
+
+	/* try to find the exact same free device which we had before */
+	ctl	 = ppp_list;
+	if_num	 = 0;
+  
+	while (ctl) {
+		ppp = ctl2ppp (ctl);
+		if (!set_bit(0, &ppp->inuse)) {
+			if (ppp->sc_xfer == pid_value) {
+				ppp->sc_xfer = 0;
+				return (ppp);
+			}
+			clear_bit (0, &ppp->inuse);
+		}
+		ctl = ctl->next;
+		if (++if_num == max_dev)
+			break;
+	}
+	return NULL;
+}
+
 /* allocate or create a PPP channel */
 static struct ppp *
 ppp_alloc (void)
@@ -3316,8 +3198,8 @@
 	struct ppp	*ppp;
 
 	/* try to find an free device */
-	ctl      = ppp_list;
-	if_num   = 0;
+	ctl	 = ppp_list;
+	if_num	 = 0;
   
 	while (ctl) {
 		ppp = ctl2ppp (ctl);
@@ -3346,7 +3228,7 @@
 		dev->next      = NULL;
 		dev->init      = ppp_init_dev;
 		dev->name      = ctl->name;
-		dev->base_addr = (unsigned long) if_num;
+		dev->base_addr = (__u32) if_num;
 		dev->priv      = (void *) ppp;
 
 		sprintf (dev->name, "ppp%d", if_num);
@@ -3355,8 +3237,8 @@
 		ctl->next      = ppp_list;
 		ppp_list       = ctl;
 
-/* register device so that we can be ifconfig'd */
-/* ppp_init_dev() will be called as a side-effect */
+		/* register device so that we can be ifconfig'd */
+		/* ppp_init_dev() will be called as a side-effect */
 
 		status = register_netdev (dev);
 		if (status == 0) {
@@ -3377,9 +3259,9 @@
  */
 
 static void
-ppp_print_hex (register u_char * out, const u_char * in, int count)
+ppp_print_hex (register __u8 * out, const __u8 * in, int count)
 {
-	register u_char next_ch;
+	register __u8 next_ch;
 	static char hex[] = "0123456789ABCDEF";
 
 	while (count-- > 0) {
@@ -3391,9 +3273,9 @@
 }
 
 static void
-ppp_print_char (register u_char * out, const u_char * in, int count)
+ppp_print_char (register __u8 * out, const __u8 * in, int count)
 {
-	register u_char next_ch;
+	register __u8 next_ch;
 
 	while (count-- > 0) {
 		next_ch = *in++;
@@ -3410,11 +3292,11 @@
 }
 
 static void
-ppp_print_buffer (const u_char * name, const u_char * buf, int count)
+ppp_print_buffer (const __u8 * name, const __u8 * buf, int count)
 {
-	u_char line[44];
+	__u8 line[44];
 
-	if (name != (u_char *) NULL)
+	if (name != (__u8 *) NULL)
 		printk (KERN_DEBUG "ppp: %s, count = %d\n", name, count);
 
 	while (count > 8) {
@@ -3448,14 +3330,14 @@
 static struct compressor *find_compressor (int type)
 {
 	struct compressor_link *lnk;
-	unsigned long flags;
+	__u32 flags;
 
 	save_flags(flags);
 	cli();
 
 	lnk = ppp_compressors;
 	while (lnk != (struct compressor_link *) 0) {
-		if ((int) (unsigned char) lnk->comp->compress_proto == type) {
+		if ((int) (__u8) lnk->comp->compress_proto == type) {
 			restore_flags(flags);
 			return lnk->comp;
 		}
@@ -3466,22 +3348,22 @@
 	return (struct compressor *) 0;
 }
 
-int ppp_register_compressor (struct compressor *cp)
+static int ppp_register_compressor (struct compressor *cp)
 {
 	struct compressor_link *new;
-	unsigned long flags;
+	__u32 flags;
 
 	new = (struct compressor_link *) kmalloc (sizeof (struct compressor_link), GFP_KERNEL);
 
 	if (new == (struct compressor_link *) 0)
-	  	return 1;
+		return 1;
 
 	save_flags(flags);
 	cli();
 
 	if (find_compressor (cp->compress_proto)) {
 		restore_flags(flags);
-	  	kfree (new);
+		kfree (new);
 		return 0;
 	}
 
@@ -3493,11 +3375,11 @@
 	return 0;
 }
 
-void ppp_unregister_compressor (struct compressor *cp)
+static void ppp_unregister_compressor (struct compressor *cp)
 {
 	struct compressor_link *prev = (struct compressor_link *) 0;
 	struct compressor_link *lnk;
-	unsigned long flags;
+	__u32 flags;
 
 	save_flags(flags);
 	cli();
@@ -3505,7 +3387,7 @@
 	lnk  = ppp_compressors;
 	while (lnk != (struct compressor_link *) 0) {
 		if (lnk->comp == cp) {
-		  	if (prev)
+			if (prev)
 				prev->next = lnk->next;
 			else
 				ppp_compressors = lnk->next;
@@ -3523,7 +3405,6 @@
  *************************************************************/
 
 #ifdef MODULE
-
 int
 init_module(void)
 {
@@ -3568,7 +3449,7 @@
 /*
  * Ensure that there are no compressor modules registered
  */
-	if (busy_flag == 0 && ppp_compressors != NULL)
+	if (ppp_compressors != NULL)
 		busy_flag = 1;
 
 	if (busy_flag) {
@@ -3593,12 +3474,12 @@
  */	
 	next_ctl = ppp_list;
 	while (next_ctl) {
-		ctl      = next_ctl;
+		ctl	 = next_ctl;
 		next_ctl = ctl->next;
-		ppp      = ctl2ppp (ctl);
-		dev      = ctl2dev (ctl);
+		ppp	 = ctl2ppp (ctl);
+		dev	 = ctl2dev (ctl);
 
-		ppp_release       (ppp);
+		ppp_release	  (ppp);
 		unregister_netdev (dev);
 		kfree (ctl);
 	}
diff -u --recursive --new-file v1.3.71/linux/drivers/net/smc9194.c linux/drivers/net/smc9194.c
--- v1.3.71/linux/drivers/net/smc9194.c	Thu Jan  1 02:00:00 1970
+++ linux/drivers/net/smc9194.c	Fri Mar  8 10:03:32 1996
@@ -0,0 +1,1779 @@
+/*------------------------------------------------------------------------
+ . smc9194.c
+ . This is a driver for SMC's 9000 series of Ethernet cards. 
+ .
+ . Copyright (C) 1996 by Erik Stahlman
+ . This software may be used and distributed according to the terms
+ . of the GNU Public License, incorporated herein by reference.
+ .
+ . "Features" of the SMC chip:  
+ .   4608 byte packet memory. ( for the 91C92.  Others have more ) 
+ .   EEPROM for configuration
+ .   AUI/TP selection  ( mine has 10Base2/10BaseT select )
+ .
+ . Arguments:
+ . 	io		 = for the base address
+ .	irq	 = for the IRQ 
+ .	ifport = 0 for autodetect, 1 for TP, 2 for AUI ( or 10base2 )   
+ .
+ . author:  
+ . 	Erik Stahlman				( erik@vt.edu )
+ .   
+ . Hardware multicast code from Peter Cammaert ( pc@denkart.be )
+ .
+ . Sources:
+ .    o   SMC databook
+ .    o   skeleton.c by Donald Becker ( becker@cesdis.gsfc.nasa.gov )
+ .    o   ( a LOT of advice from Becker as well )
+ .
+ . History:
+ .	12/07/95  Erik Stahlman  written, got recieve/xmit handled 
+ . 	01/03/96  Erik Stahlman  worked out some bugs, actually useable!!! :-)
+ .	01/06/96  Erik Stahlman	 cleaned up some, better testing, etc 
+ .	01/29/96  Erik Stahlman	 fixed autoirq, added multicast
+ . 	02/01/96  Erik Stahlman	 1. disabled all interrupts in smc_reset
+ .		   		 2. got rid of post-decrementing bug -- UGH.  
+ .	02/13/96  Erik Stahlman  Tried to fix autoirq failure.  Added more
+ .				 descriptive error messages.
+ .	02/15/96  Erik Stahlman  Fixed typo that caused detection failure
+ . 	02/23/96  Erik Stahlman	 Modified it to fit into kernel tree  
+ .				 Added support to change hardware address
+ .				 Cleared stats on opens
+ .	02/26/96  Erik Stahlman	 Trial support for Kernel 1.2.13
+ .				 Kludge for automatic IRQ detection
+ .	03/04/96  Erik Stahlman	 Fixed kernel 1.3.70 + 
+ .				 Fixed bug reported by Gardner Buchanan in 
+ .				   smc_enable, with outw instead of outb
+ .	03/06/96  Erik Stahlman  Added hardware multicast from Peter Cammaert
+ ----------------------------------------------------------------------------*/
+
+static const char *version =
+	"smc9194.c:v0.12 03/06/96 by Erik Stahlman (erik@vt.edu)\n";  
+
+#ifdef MODULE
+#include <linux/module.h>
+#include <linux/version.h>
+#endif 
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/in.h>
+#include <linux/malloc.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <asm/bitops.h>
+#include <asm/io.h>
+#include <linux/errno.h>
+
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+
+#include "smc9194.h"
+/*------------------------------------------------------------------------
+ .  
+ . Configuration options, for the experienced user to change. 
+ .
+ -------------------------------------------------------------------------*/
+
+/* 
+ . this is for kernels > 1.2.70 
+*/
+#define REALLY_NEW_KERNEL 
+#ifndef REALLY_NEW_KERNEL
+#define free_irq( x, y ) free_irq( x )
+#define request_irq( x, y, z, u, v ) request_irq( x, y, z, u )
+#endif
+
+/*
+ . Do you want to use this with old kernels.  
+ . WARNING: this is not well tested.  
+#define SUPPORT_OLD_KERNEL
+*/
+
+
+/*
+ . Do you want to use 32 bit xfers?  This should work on all chips, as
+ . the chipset is designed to accomodate them.   
+*/
+#define USE_32_BIT 1
+
+/* 
+ .the SMC9194 can be at any of the following port addresses.  To change,
+ .for a slightly different card, you can add it to the array.  Keep in 
+ .mind that the array must end in zero.
+*/
+static unsigned int smc_portlist[] =
+   { 0x200, 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, 0x2E0,
+	  0x300, 0x320, 0x340, 0x360, 0x380, 0x3A0, 0x3C0, 0x3E0, 0};
+
+/* 
+ . Wait time for memory to be free.  This probably shouldn't be 
+ . tuned that much, as waiting for this means nothing else happens 
+ . in the system 
+*/
+#define MEMORY_WAIT_TIME 16
+
+/*
+ . DEBUGGING LEVELS
+ . 
+ . 0 for normal operation
+ . 1 for slightly more details
+ . >2 for various levels of increasingly useless information
+ .    2 for interrupt tracking, status flags
+ .    3 for packet dumps, etc.
+*/
+#define SMC_DEBUG 0
+
+#if (SMC_DEBUG > 2 )
+#define PRINTK3(x) printk x 
+#else 
+#define PRINTK3(x) 
+#endif
+
+#if SMC_DEBUG > 1 
+#define PRINTK2(x) printk x 
+#else
+#define PRINTK2(x)
+#endif
+
+#ifdef SMC_DEBUG
+#define PRINTK(x) printk x
+#else
+#define PRINTK(x)
+#endif 
+
+
+/* the older versions of the kernel cannot support autoprobing */
+#ifdef SUPPORT_OLD_KERNEL
+#define NO_AUTOPROBE
+#endif
+
+
+/*------------------------------------------------------------------------
+ .
+ . The internal workings of the driver.  If you are changing anything 
+ . here with the SMC stuff, you should have the datasheet and known 
+ . what you are doing.  
+ .  
+ -------------------------------------------------------------------------*/
+#define CARDNAME "SMC9194"
+
+#ifdef SUPPORT_OLD_KERNEL
+char kernel_version[] = UTS_RELEASE;
+#endif 
+
+/* store this information for the driver.. */ 
+struct smc_local {
+	/*
+ 	   these are things that the kernel wants me to keep, so users
+	   can find out semi-useless statistics of how well the card is
+	   performing 
+ 	*/
+	struct enet_statistics stats;
+	
+	/* 
+	   If I have to wait until memory is available to send
+	   a packet, I will store the skbuff here, until I get the
+	   desired memory.  Then, I'll send it out and free it.    
+	*/
+	struct sk_buff * saved_skb;
+
+	/*
+ 	 . This keeps track of how many packets that I have
+ 	 . sent out.  When an TX_EMPTY interrupt comes, I know
+	 . that all of these have been sent.
+	*/
+	int	packets_waiting;
+};
+
+
+/*-----------------------------------------------------------------
+ .
+ .  The driver can be entered at any of the following entry points.
+ . 
+ .------------------------------------------------------------------  */
+
+/*
+ . This is called by  register_netdev().  It is responsible for 
+ . checking the portlist for the SMC9000 series chipset.  If it finds 
+ . one, then it will initialize the device, find the hardware information,
+ . and sets up the appropriate device parameters.   
+ . NOTE: Interrupts are *OFF* when this procedure is called.
+ .
+ . NB:This shouldn't be static since it is refered to externally.
+*/
+int smc_init(struct device *dev);
+
+/*
+ . The kernel calls this function when someone wants to use the device,
+ . typically 'ifconfig ethX up'.   
+*/
+static int smc_open(struct device *dev);
+
+/*
+ . This is called by the kernel to send a packet out into the net.  it's
+ . responsible for doing a best-effort send, but if it's simply not possible
+ . to send it, the packet gets dropped. 
+*/  
+static int smc_send_packet(struct sk_buff *skb, struct device *dev);
+
+/* 
+ . This is called by the kernel in response to 'ifconfig ethX down'.  It
+ . is responsible for cleaning up everything that the open routine 
+ . does, and maybe putting the card into a powerdown state. 
+*/
+static int smc_close(struct device *dev);
+
+/*
+ . This routine allows the proc file system to query the driver's 
+ . statistics.  
+*/
+static struct enet_statistics * smc_query_statistics( struct device *dev);
+
+/*
+ . Finally, a call to set promiscuous mode ( for TCPDUMP and related 
+ . programs ) and multicast modes.
+*/
+#ifdef SUPPORT_OLD_KERNEL
+static void smc_set_multicast_list(struct device *dev, int num_addrs,
+				 void *addrs);
+#else
+static void smc_set_multicast_list(struct device *dev);
+#endif 
+
+/*---------------------------------------------------------------
+ . 
+ . Interrupt level calls.. 
+ .
+ ----------------------------------------------------------------*/
+
+/*
+ . Handles the actual interrupt 
+*/
+#ifdef REALLY_NEW_KERNEL
+static void smc_interrupt(int irq, void *, struct pt_regs *regs);
+#else
+static void smc_interrupt(int irq, struct pt_regs *regs);
+#endif 
+/*
+ . This is a seperate procedure to handle the receipt of a packet, to
+ . leave the interrupt code looking slightly cleaner 
+*/ 
+inline static void smc_rcv( struct device *dev );
+/*
+ . This handles a TX interrupt, which is only called when an error
+ . relating to a packet is sent.  
+*/
+inline static void smc_tx( struct device * dev );
+
+/*
+ ------------------------------------------------------------
+ . 
+ . Internal routines
+ .
+ ------------------------------------------------------------
+*/
+
+/*
+ . Test if a given location contains a chip, trying to cause as 
+ . little damage as possible if it's not a SMC chip.
+*/
+static int smc_probe( int ioaddr );
+
+/*
+ . this routine initializes the cards hardware, prints out the configuration
+ . to the system log as well as the vanity message, and handles the setup
+ . of a device parameter. 
+ . It will give an error if it can't initialize the card.
+*/
+static int smc_initcard( struct device *, int ioaddr ); 
+
+/*
+ . A rather simple routine to print out a packet for debugging purposes.
+*/ 
+#if SMC_DEBUG > 2 
+static void print_packet( byte *, int );
+#endif  
+
+#define tx_done(dev) 1
+
+/* this is called to actually send the packet to the chip */ 
+static void smc_hardware_send_packet( struct device * dev );
+
+/* Since I am not sure if I will have enough room in the chip's ram
+ . to store the packet, I call this routine, which either sends it 
+ . now, or generates an interrupt when the card is ready for the 
+ . packet */
+static int  smc_wait_to_send_packet( struct sk_buff * skb, struct device *dev );
+
+/* this does a soft reset on the device */
+static void smc_reset( int ioaddr );
+
+/* Enable Interrupts, Receive, and Transmit */
+static void smc_enable( int ioaddr );
+
+/* this puts the device in an inactve state */
+static void smc_shutdown( int ioaddr );
+
+#ifndef NO_AUTOPROBE
+/* This routine will find the IRQ of the driver if one is not
+ . specified in the input to the device.  */
+static int smc_findirq( int ioaddr );
+#endif
+
+/*
+  this routine will set the hardware multicast table to the specified 
+  values given it by the higher level routines
+*/
+#ifndef SUPPORT_OLD_KERNEL
+static void smc_setmulticast( int ioaddr, int count, struct dev_mc_list *  );
+static int crc32( char *, int );
+#endif
+
+#ifdef SUPPORT_OLD_KERNEL
+extern struct device *init_etherdev(struct device *dev, int sizeof_private,
+ 			unsigned long *mem_startp );
+#endif 
+
+/*
+ . Function: smc_reset( int ioaddr )
+ . Purpose:
+ .  	This sets the SMC91xx chip to its normal state, hopefully from whatever
+ . 	mess that any other DOS driver has put it in.   
+ . 
+ . Maybe I should reset more registers to defaults in here?  SOFTRESET  should
+ . do that for me.  
+ . 
+ . Method:
+ .	1.  send a SOFT RESET 
+ .	2.  wait for it to finish
+ .	3.  enable autorelease mode
+ .	4.  reset the memory management unit
+ .	5.  clear all interrupts
+ .
+*/  
+static void smc_reset( int ioaddr ) 
+{ 
+	/* This resets the registers mostly to defaults, but doesn't
+	   affect EEPROM.  That seems unnecessary */
+	SMC_SELECT_BANK( 0 );
+	outw( RCR_SOFTRESET, ioaddr + RCR ); 
+	
+	/* this should pause enough for the chip to be happy */
+	SMC_DELAY( );
+
+	/* Set the transmit and receive configuration registers to 
+	   default values */
+	outw( RCR_CLEAR, ioaddr + RCR );
+	outw( TCR_CLEAR, ioaddr + TCR );
+
+	/* set the control register to automatically
+	   release succesfully transmitted packets, to make the best 
+	   use out of our limitted memory */
+	SMC_SELECT_BANK( 1 );
+	outw( inw( ioaddr + CONTROL ) | CTL_AUTO_RELEASE , ioaddr + CONTROL );	
+
+	/* Reset the MMU */
+	SMC_SELECT_BANK( 2 );
+	outw( MC_RESET, ioaddr + MMU_CMD );
+
+	/* Note:  It doesn't seem that waiting for the MMU busy is needed here, 
+	   but this is a place where future chipsets _COULD_ break.  Be wary
+ 	   of issuing another MMU command right after this */
+
+	outb( 0, ioaddr + INT_MASK );
+}
+
+/* 
+ . Function: smc_enable
+ . Purpose: let the chip talk to the outside work
+ . Method: 
+ .	1.  Enable the transmitter
+ .	2.  Enable the receiver
+ .	3.  Enable interrupts
+*/
+static void smc_enable( int ioaddr ) 
+{
+	SMC_SELECT_BANK( 0 );
+	/* see the header file for options in TCR/RCR NORMAL*/
+	outw( TCR_NORMAL, ioaddr + TCR );
+	outw( RCR_NORMAL, ioaddr + RCR );
+
+	/* now, enable interrupts */
+	SMC_SELECT_BANK( 2 );
+	outb( SMC_INTERRUPT_MASK, ioaddr + INT_MASK );
+} 	
+	
+/*
+ . Function: smc_shutdown
+ . Purpose:  closes down the SMC91xxx chip.
+ . Method:   
+ .	1. zero the interrupt mask
+ .	2. clear the enable recieve flag
+ .	3. clear the enable xmit flags
+ .
+ . TODO: 
+ .   (1) maybe utilize power down mode.
+ .	Why not yet?  Because while the chip will go into power down mode,
+ .	the manual says that it will wake up in response to any I/O requests
+ .	in the register space.   Empirical results do not show this working.
+*/
+static void smc_shutdown( int ioaddr ) 
+{
+	/* no more interrupts for me */
+	SMC_SELECT_BANK( 2 );
+	outb( 0, ioaddr + INT_MASK );
+
+	/* and tell the card to stay away from that nasty outside world */
+	SMC_SELECT_BANK( 0 );
+	outb( RCR_CLEAR, ioaddr + RCR );
+	outb( TCR_CLEAR, ioaddr + TCR );
+#if 0 
+	/* finally, shut the chip down */
+	SMC_SELECT_BANK( 1 ); 
+	outw( inw( ioaddr + CONTROL ), CTL_POWERDOWN, ioaddr + CONTROL  );
+#endif 
+}
+
+
+#ifndef SUPPORT_OLD_KERNEL 
+/* 
+ . Function: smc_setmulticast( int ioaddr, int count, dev_mc_list * adds )
+ . Purpose:
+ .    This sets the internal hardware table to filter out unwanted multicast
+ .    packets before they take up memory.  
+ .    
+ .    The SMC chip uses a hash table where the high 6 bits of the CRC of
+ .    address are the offset into the table.  If that bit is 1, then the 
+ .    multicast packet is accepted.  Otherwise, it's dropped silently.
+ .  
+ .    To use the 6 bits as an offset into the table, the high 3 bits are the
+ .    number of the 8 bit register, while the low 3 bits are the bit within
+ .    that register.
+ .
+ . This routine is based very heavily on the one provided by Peter Cammaert. 
+*/
+
+
+static void smc_setmulticast( int ioaddr, int count, struct dev_mc_list * addrs ) {
+	int			i;
+	unsigned char		multicast_table[ 8 ];
+	struct dev_mc_list	* cur_addr;
+	/* table for flipping the order of 3 bits */  
+	unsigned char invert3[] = { 0, 4, 2, 6, 1, 5, 3, 7 };
+
+	/* start with a table of all zeros: reject all */	
+	memset( multicast_table, 0, sizeof( multicast_table ) );
+
+	cur_addr = addrs;
+	for ( i = 0; i < count ; i ++, cur_addr = cur_addr->next  ) {
+		int position;
+		
+		/* do we have a pointer here? */
+		if ( !cur_addr ) 
+			break;
+		/* make sure this is a multicast address - shouldn't this
+		   be a given if we have it here ? */	
+		if ( !( *cur_addr->dmi_addr & 1 ) ) 	
+			continue;	
+
+		/* only use the low order bits */	
+		position = crc32( cur_addr->dmi_addr, 6 ) & 0x3f;
+				
+		/* do some messy swapping to put the bit in the right spot */
+		multicast_table[invert3[position&7]] |= 
+					(1<<invert3[(position>>3)&7]);
+
+	}
+	/* now, the table can be loaded into the chipset */
+	SMC_SELECT_BANK( 3 );
+	
+	for ( i = 0; i < 8 ; i++ ) {
+		outb( multicast_table[i], ioaddr + MULTICAST1 + i );
+	}
+}
+
+/*
+  Finds the CRC32 of a set of bytes.
+  Again, from Peter Cammaert's code. 
+*/
+static int crc32( char * s, int length ) { 
+	/* indices */
+	int perByte;
+	int perBit;
+	/* crc polynomial for Ethernet */
+	const unsigned long poly = 0xedb88320;
+	/* crc value - preinitialized to all 1's */
+	unsigned long crc_value = 0xffffffff; 
+
+	for ( perByte = 0; perByte < length; perByte ++ ) {
+		unsigned char	c;
+	
+		c = *(s++);
+		for ( perBit = 0; perBit < 8; perBit++ ) {
+			crc_value = (crc_value>>1)^
+				(((crc_value^c)&0x01)?poly:0);
+			c >>= 1;
+		}
+	}
+	return	crc_value;
+} 
+
+#endif 
+
+
+/* 
+ . Function: smc_wait_to_send_packet( struct sk_buff * skb, struct device * ) 
+ . Purpose: 
+ .    Attempt to allocate memory for a packet, if chip-memory is not
+ .    available, then tell the card to generate an interrupt when it 
+ .    is available.
+ .
+ . Algorithm:
+ .
+ . o	if the saved_skb is not currently null, then drop this packet
+ .	on the floor.  This should never happen, because of TBUSY.
+ . o	if the saved_skb is null, then replace it with the current packet,
+ . o	See if I can sending it now. 
+ . o 	(NO): Enable interrupts and let the interrupt handler deal with it.
+ . o	(YES):Send it now.
+*/
+static int smc_wait_to_send_packet( struct sk_buff * skb, struct device * dev )
+{ 
+	struct smc_local *lp 	= (struct smc_local *)dev->priv;
+	unsigned short ioaddr 	= dev->base_addr;
+	word 			length;
+	unsigned short 		numPages;
+	word			time_out;	
+	
+	if ( lp->saved_skb) {
+		/* THIS SHOULD NEVER HAPPEN. */
+		lp->stats.tx_aborted_errors++;
+		printk(CARDNAME": Bad Craziness - sent packet while busy.\n" );
+		return 1;
+	}
+	lp->saved_skb = skb;
+
+	length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN;
+		
+	/*
+	. the MMU wants the number of pages to be the number of 256 bytes 
+    	. 'pages', minus 1 ( since a packet can't ever have 0 pages :) ) 
+	*/
+	numPages = length / 256;
+
+	if (numPages > 7 ) {
+		printk(CARDNAME": Far too big packet error. \n");
+		/* freeing the packet is a good thing here... but should 		
+		 . any packets of this size get down here?   */
+		dev_kfree_skb (skb, FREE_WRITE);
+		lp->saved_skb = NULL;
+		/* this IS an error, but, i don't want the skb saved */
+		return 0; 
+	}
+	/* either way, a packet is waiting now */
+	lp->packets_waiting++;
+	 
+	/* now, try to allocate the memory */
+	SMC_SELECT_BANK( 2 );
+	outw( MC_ALLOC | numPages, ioaddr + MMU_CMD );
+	/*
+ 	. Performance Hack
+	.  
+ 	. wait a short amount of time.. if I can send a packet now, I send
+	. it now.  Otherwise, I enable an interrupt and wait for one to be
+	. available. 
+	.
+	. I could have handled this a slightly different way, by checking to
+	. see if any memory was available in the FREE MEMORY register.  However,
+	. either way, I need to generate an allocation, and the allocation works
+	. no matter what, so I saw no point in checking free memory.   
+	*/ 
+	time_out = MEMORY_WAIT_TIME;
+	do { 
+		word	status;
+
+		status = inb( ioaddr + INTERRUPT );
+		if ( status & IM_ALLOC_INT ) { 
+			/* acknowledge the interrupt */
+			outb( IM_ALLOC_INT, ioaddr + INTERRUPT );
+  			break;	
+		}
+   	} while ( -- time_out );
+
+   	if ( !time_out ) {
+		/* oh well, wait until the chip finds memory later */ 
+		SMC_ENABLE_INT( IM_ALLOC_INT );
+      		PRINTK2((CARDNAME": memory allocation deferred. \n"));
+		/* it's deferred, but I'll handle it later */
+      		return 0;
+   	}
+	/* or YES! I can send the packet now.. */
+	smc_hardware_send_packet(dev);
+	
+	return 0;
+}	
+
+/*
+ . Function:  smc_hardware_send_packet(struct device * )
+ . Purpose:	
+ .	This sends the actual packet to the SMC9xxx chip.   
+ . 
+ . Algorithm:
+ . 	First, see if a saved_skb is available.    
+ .		( this should NOT be called if there is no 'saved_skb'
+ .	Now, find the packet number that the chip allocated
+ .	Point the data pointers at it in memory 
+ .	Set the length word in the chip's memory
+ .	Dump the packet to chip memory
+ .	Check if a last byte is needed ( odd length packet )
+ .		if so, set the control flag right 
+ . 	Tell the card to send it 
+ .	Enable the transmit interrupt, so I know if it failed
+ . 	Free the kernel data if I actually sent it.
+*/
+static void smc_hardware_send_packet( struct device * dev ) 
+{
+	struct smc_local *lp = (struct smc_local *)dev->priv;
+	byte	 		packet_no;
+	struct sk_buff * 	skb = lp->saved_skb;
+	word			length;	
+	unsigned short		ioaddr;
+	byte			* buf;
+
+	ioaddr = dev->base_addr;	
+
+	if ( !skb ) {
+		PRINTK((CARDNAME": In XMIT with no packet to send \n"));
+		return;
+	}
+	length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN;
+	buf = skb->data;
+
+	/* If I get here, I _know_ there is a packet slot waiting for me */
+	packet_no = inb( ioaddr + PNR_ARR + 1 ); 
+	if ( packet_no & 0x80 ) { 
+		/* or isn't there?  BAD CHIP! */
+		printk(KERN_DEBUG CARDNAME": Memory allocation failed. \n");
+		kfree(skb);
+		lp->saved_skb = NULL;
+		dev->tbusy = 0;
+		return;
+	}
+
+	/* we have a packet address, so tell the card to use it */
+	outb( packet_no, ioaddr + PNR_ARR );
+
+	/* point to the beginning of the packet */	
+	outw( PTR_AUTOINC , ioaddr + POINTER );
+
+   	PRINTK3((CARDNAME": Trying to xmit packet of length %x\n", length ));
+#if SMC_DEBUG > 2
+	print_packet( buf, length );
+#endif
+
+	/* send the packet length ( +6 for status, length and ctl byte ) 
+ 	   and the status word ( set to zeros ) */ 
+#ifdef USE_32_BIT
+	outl(  (length +6 ) << 16 , ioaddr + DATA_1 );
+#else
+	outw( 0, ioaddr + DATA_1 );	 
+	/* send the packet length ( +6 for status words, length, and ctl*/		
+	outb( (length+6) & 0xFF,ioaddr + DATA_1 );
+	outb( (length+6) >> 8 , ioaddr + DATA_1 );
+#endif 
+
+	/* send the actual data 
+	 . I _think_ it's faster to send the longs first, and then 
+	 . mop up by sending the last word.  It depends heavily 
+ 	 . on alignment, at least on the 486.  Maybe it would be 
+ 	 . a good idea to check which is optimal?  But that could take
+	 . almost as much time as is saved? 
+	*/	
+#ifdef USE_32_BIT 
+	if ( length & 0x2  ) {	
+		outsl(ioaddr + DATA_1, buf,  length >> 2 ); 
+		outw( *((word *)(buf + (length & 0xFFFFFFFC))),ioaddr +DATA_1);
+	}
+	else
+		outsl(ioaddr + DATA_1, buf,  length >> 2 ); 
+#else
+	outsw(ioaddr + DATA_1 , buf, (length ) >> 1);
+#endif
+	/* Send the last byte, if there is one.   */
+	
+	if ( (length & 1) == 0 ) {
+		outw( 0, ioaddr + DATA_1 );
+	} else {
+		outb( buf[length -1 ], ioaddr + DATA_1 );
+		outb( 0x20, ioaddr + DATA_1);
+	}
+
+	/* enable the interrupts */
+	SMC_ENABLE_INT( (IM_TX_INT | IM_TX_EMPTY_INT) );
+
+	/* and let the chipset deal with it */
+	outw( MC_ENQUEUE , ioaddr + MMU_CMD );
+
+	PRINTK2((CARDNAME": Sent packet of length %d \n",length)); 
+
+	lp->saved_skb = NULL;
+	dev_kfree_skb (skb, FREE_WRITE);
+
+	dev->trans_start = jiffies;
+
+	/* we can send another packet */
+	dev->tbusy = 0;
+
+
+	return;
+}
+
+/*-------------------------------------------------------------------------
+ |
+ | smc_init( struct device * dev )  
+ |   Input parameters: 
+ |	dev->base_addr == 0, try to find all possible locations
+ |	dev->base_addr == 1, return failure code
+ |	dev->base_addr == 2, always allocate space,  and return sucess
+ |	dev->base_addr == <anything else>   this is the address to check 
+ |
+ |   Output: 
+ |	0 --> there is a device
+ |	anything else, error 
+ | 
+ ---------------------------------------------------------------------------
+*/ 
+int smc_init(struct device *dev)
+{
+	int i;
+	int base_addr = dev ? dev->base_addr : 0;
+
+	/*  try a specific location */
+	if (base_addr > 0x1ff)	{		 
+		int	error; 
+		error = smc_probe(base_addr);
+		if ( 0 == error ) {
+			return smc_initcard( dev, base_addr );
+		}
+		return error; 
+	} else {
+		if ( 0 != base_addr ) {
+			return -ENXIO;
+		}
+	}
+	
+	/* check every ethernet address */
+	for (i = 0; smc_portlist[i]; i++) {
+		int ioaddr = smc_portlist[i];
+
+		/* check if the area is available */
+		if (check_region( ioaddr , SMC_IO_EXTENT))
+			continue;
+
+		/* check this specific address */
+		if ( smc_probe( ioaddr ) == 0)  {  
+			return smc_initcard( dev, ioaddr  ); 
+		}
+	}
+
+	/* couldn't find anything */
+	return -ENODEV;
+}
+
+#ifndef NO_AUTOPROBE
+/*----------------------------------------------------------------------
+ . smc_findirq 
+ . 
+ . This routine has a simple purpose -- make the SMC chip generate an 
+ . interrupt, so an auto-detect routine can detect it, and find the IRQ,
+ ------------------------------------------------------------------------
+*/
+int smc_findirq( int ioaddr ) 
+{
+	int	timeout = 20;
+
+
+	/* I have to do a STI() here, because this is called from
+	   a routine that does an CLI during this process, making it 
+	   rather difficult to get interrupts for auto detection */
+	sti();
+
+	autoirq_setup( 0 );
+
+	/*
+	 * What I try to do here is trigger an ALLOC_INT. This is done
+	 * by allocating a small chunk of memory, which will give an interrupt
+	 * when done.
+	 */
+
+	  
+	SMC_SELECT_BANK(2);	
+	/* enable ALLOCation interrupts ONLY */
+	outb( IM_ALLOC_INT, ioaddr + INT_MASK );	
+
+	/*
+ 	 . Allocate 512 bytes of memory.  Note that the chip was just 
+	 . reset so all the memory is available
+	*/
+	outw( MC_ALLOC | 1, ioaddr + MMU_CMD );		
+
+	/*
+	 . Wait until positive that the interrupt has been generated
+	*/
+	while ( timeout ) {
+		byte	int_status;
+
+		int_status = inb( ioaddr + INTERRUPT );
+
+		if ( int_status & IM_ALLOC_INT ) 	
+			break;		/* got the interrupt */
+		timeout--;
+	}
+	/* there is really nothing that I can do here if timeout fails,
+	   as autoirq_report will return a 0 anyway, which is what I
+	   want in this case.   Plus, the clean up is needed in both
+	   cases.  */
+
+	/* DELAY HERE!
+	   On a fast machine, the status might change before the interrupt
+	   is given to the processor.  This means that the interrupt was 
+	   never detected, and autoirq_report fails to report anything.  
+	   This should fix autoirq_* problems. 
+	*/
+	SMC_DELAY();
+	SMC_DELAY();		
+	
+	/* and disable all interrupts again */
+	outb( 0, ioaddr + INT_MASK );
+
+	/* clear hardware interrupts again, because that's how it
+	   was when I was called... */
+	cli();
+
+	/* and return what I found */
+	return autoirq_report( 0 );	
+}
+#endif
+ 
+/*----------------------------------------------------------------------
+ . Function: smc_probe( int ioaddr )
+ . 
+ . Purpose:  
+ .	Tests to see if a given ioaddr points to an SMC9xxx chip.
+ .	Returns a 0 on success 	
+ . 
+ . Algorithm:
+ .	(1) see if the high byte of BANK_SELECT is 0x33
+ . 	(2) compare the ioaddr with the base register's address
+ .	(3) see if I recognize the chip ID in the appropriate register
+ . 
+ .---------------------------------------------------------------------
+ */ 
+
+static int smc_probe( int ioaddr ) 
+{
+	unsigned int	bank;
+	word	revision_register;	
+	word  base_address_register;
+
+	/* First, see if the high byte is 0x33 */
+	bank = inw( ioaddr + BANK_SELECT );
+	if ( (bank & 0xFF00) != 0x3300 ) {
+		return -ENODEV;
+	}
+	/* The above MIGHT indicate a device, but I need to write to further
+ 	 	test this.  */
+	outw( 0x0, ioaddr + BANK_SELECT );
+	bank = inw( ioaddr + BANK_SELECT );
+	if ( (bank & 0xFF00 ) != 0x3300 ) {
+		return -ENODEV;
+	}
+	/* well, we've already written once, so hopefully another time won't
+ 	   hurt.  This time, I need to switch the bank register to bank 1, 
+	   so I can access the base address register */
+	SMC_SELECT_BANK(1);
+	base_address_register = inw( ioaddr + BASE );
+	if ( ioaddr != ( base_address_register >> 3 & 0x3E0 ) )  {
+		printk(CARDNAME ": IOADDR %x doesn't match configuration (%x)." 
+			"Probably not a SMC chip\n",
+			ioaddr, base_address_register >> 3 & 0x3E0 );
+		/* well, the base address register didn't match.  Must not have
+		   been a SMC chip after all. */
+		return -ENODEV;
+	}
+
+	/*  check if the revision register is something that I recognize.  
+	    These might need to be added to later, as future revisions 
+	    could be added.  */
+	SMC_SELECT_BANK(3);
+	revision_register  = inw( ioaddr + REVISION );
+	if ( !chip_ids[ ( revision_register  >> 4 ) & 0xF  ] ) { 
+		/* I don't regonize this chip, so... */
+		printk(CARDNAME ": IO %x: Unrecognized revision register:"
+			" %x, Contact author. \n", ioaddr, revision_register );
+
+		return -ENODEV;
+	}
+
+	/* at this point I'll assume that the chip is an SMC9xxx.   
+	   It might be prudent to check a listing of MAC addresses 
+	   against the hardware address, or do some other tests. */ 	
+	return 0;
+}
+
+/*---------------------------------------------------------------
+ . Here I do typical initialization tasks. 
+ . 
+ . o  Initialize the structure if needed
+ . o  print out my vanity message if not done so already
+ . o  print out what type of hardware is detected
+ . o  print out the ethernet address
+ . o  find the IRQ 
+ . o  set up my private data 
+ . o  configure the dev structure with my subroutines
+ . o  actually GRAB the irq.
+ . o  GRAB the region 
+ .-----------------------------------------------------------------
+*/
+static int  smc_initcard(struct device *dev, int ioaddr)
+{
+	int i;
+
+	static unsigned version_printed = 0;
+
+	/* registers */
+	word	revision_register;	
+	word	configuration_register;	
+	word  	memory_info_register;
+	word 	memory_cfg_register;
+
+	const char *	version_string;
+	const char *	if_string;
+	int	memory;
+
+	int   irqval;
+
+	/* see if I need to initialize the ethernet card structure */
+	if (dev == NULL) {
+#ifdef SUPPORT_OLD_KERNEL
+#ifndef MODULE
+/* note: the old module interface does not support this call */
+		dev = init_etherdev( 0, sizeof( struct smc_local ), 0 );
+#endif 
+#else
+		dev = init_etherdev(0, 0);
+#endif 
+		if (dev == NULL)
+			return -ENOMEM;
+	}
+
+	if (version_printed++ == 0)
+		printk("%s", version);
+
+	/* fill in some of the fields */
+	dev->base_addr = ioaddr;
+
+	/*
+ 	 . Get the MAC address ( bank 1, regs 4 - 9 )
+	*/
+	SMC_SELECT_BANK( 1 );
+	for ( i = 0; i < 6; i += 2 ) { 
+		word	address;
+
+		address = inw( ioaddr + ADDR0 + i  );
+		dev->dev_addr[ i + 1] = address >> 8;
+		dev->dev_addr[ i ] = address & 0xFF;	
+	}
+
+	/* get the memory information */
+
+	SMC_SELECT_BANK( 0 );
+	memory_info_register = inw( ioaddr + MIR );
+	memory_cfg_register  = inw( ioaddr + MCR );
+	memory = ( memory_cfg_register >> 9 )  & 0x7;  /* multiplier */
+	memory *= 256 * ( memory_info_register & 0xFF );
+
+	/* 
+	 Now, I want to find out more about the chip.  This is sort of
+ 	 redundant, but it's cleaner to have it in both, rather than having
+ 	 one VERY long probe procedure.
+	*/	
+	SMC_SELECT_BANK(3);
+	revision_register  = inw( ioaddr + REVISION );
+	version_string = chip_ids[ ( revision_register  >> 4 ) & 0xF  ];
+	if ( !version_string ) {
+		/* I shouldnt' get here because this call was done before.... */ 
+		return -ENODEV;
+	}
+
+	/* is it using AUI or 10BaseT ? */
+	if ( dev->if_port == 0 ) {
+		SMC_SELECT_BANK(1);
+		configuration_register = inw( ioaddr + CONFIG );
+		if ( configuration_register & CFG_AUI_SELECT )  
+			dev->if_port = 2;
+		else
+			dev->if_port = 1;
+	}
+	if_string = interfaces[ dev->if_port - 1 ];
+
+	/* now, reset the chip, and put it into a known state */
+	smc_reset( ioaddr );
+
+	/*
+	 . If dev->irq is 0, then the device has to be banged on to see
+	 . what the IRQ is. 
+ 	 . 
+	 . This banging doesn't always detect the IRQ, for unknown reasons.
+	 . a workaround is to reset the chip and try again.  
+	 .    
+	 . Interestingly, the DOS packet driver *SETS* the IRQ on the card to
+	 . be what is requested on the command line.   I don't do that, mostly
+	 . because the card that I have uses a non-standard method of accessing
+	 . the IRQs, and because this _should_ work in most configurations.
+	 .
+	 . Specifying an IRQ is done with the assumption that the user knows 
+	 . what (s)he is doing.  No checking is done!!!!
+ 	 .
+	*/
+#ifndef NO_AUTOPROBE
+	if ( dev->irq < 2 ) {
+		int	trials;
+
+		trials = 3;
+		while ( trials-- ) { 
+			dev->irq = smc_findirq( ioaddr );
+			if ( dev->irq ) 
+				break;
+			/* kick the card and try again */
+			smc_reset( ioaddr );
+		}
+	} 
+	if (dev->irq == 0 ) {
+		printk(CARDNAME": Couldn't autodetect your IRQ. Use irq=xx.\n");
+		return -ENODEV;
+	}
+#else
+	if (dev->irq == 0 ) {
+		printk(CARDNAME
+		": Autoprobing IRQs is not supported for old kernels.\n");
+		return -ENODEV;
+	}
+#endif
+	if (dev->irq == 2) {
+		/* Fixup for users that don't know that IRQ 2 is really IRQ 9,
+		 * or don't know which one to set.
+		 */
+		dev->irq = 9;
+	}
+
+	/* now, print out the card info, in a short format.. */
+	
+	printk(CARDNAME ": %s(r:%d) at %#3x IRQ:%d INTF:%s MEM:%db ",
+		version_string, revision_register & 0xF, ioaddr, dev->irq, 
+		if_string, memory );
+	/*
+	 . Print the Ethernet address 
+	*/
+	printk("ADDR: ");
+	for (i = 0; i < 5; i++)
+		printk("%2.2x:", dev->dev_addr[i] );
+	printk("%2.2x \n", dev->dev_addr[5] );
+
+
+	/* Initialize the private structure. */
+	if (dev->priv == NULL) {
+		dev->priv = kmalloc(sizeof(struct smc_local), GFP_KERNEL);
+		if (dev->priv == NULL)
+			return -ENOMEM;
+	}
+	/* set the private data to zero by default */
+	memset(dev->priv, 0, sizeof(struct smc_local));
+
+	/* Fill in the fields of the device structure with ethernet values. */
+	ether_setup(dev);
+
+	/* Grab the IRQ */
+      	irqval = request_irq(dev->irq, &smc_interrupt, 0, CARDNAME, NULL);
+      	if (irqval) {
+       	  printk(CARDNAME": unable to get IRQ %d (irqval=%d).\n",
+		dev->irq, irqval);
+       	  return -EAGAIN;
+      	}
+	irq2dev_map[dev->irq] = dev;
+
+	/* Grab the region so that no one else tries to probe our ioports. */
+	request_region(ioaddr, SMC_IO_EXTENT, CARDNAME);
+
+	dev->open		        = smc_open;
+	dev->stop		        = smc_close;
+	dev->hard_start_xmit    	= smc_send_packet;
+	dev->get_stats			= smc_query_statistics;
+#ifdef	HAVE_MULTICAST
+	dev->set_multicast_list 	= &smc_set_multicast_list;
+#endif
+
+	return 0;
+}
+
+#if SMC_DEBUG > 2
+static void print_packet( byte * buf, int length ) 
+{ 
+#if 0
+	int i;
+	int remainder;
+	int lines;
+	
+	printk("Packet of length %d \n", length );
+	lines = length / 16;
+	remainder = length % 16;
+
+	for ( i = 0; i < lines ; i ++ ) { 
+		int cur;
+
+		for ( cur = 0; cur < 8; cur ++ ) { 
+			byte a, b;
+
+			a = *(buf ++ );
+			b = *(buf ++ );
+			printk("%02x%02x ", a, b );
+		}
+		printk("\n");
+	}
+	for ( i = 0; i < remainder/2 ; i++ ) {
+		byte a, b;
+
+		a = *(buf ++ );
+		b = *(buf ++ );
+		printk("%02x%02x ", a, b );
+	}
+	printk("\n");
+#endif
+}
+#endif	
+
+
+/*
+ * Open and Initialize the board
+ * 
+ * Set up everything, reset the card, etc ..
+ *
+ */
+static int smc_open(struct device *dev)
+{
+	int	ioaddr = dev->base_addr;
+
+	int	i;	/* used to set hw ethernet address */ 
+
+	/* clear out all the junk that was put here before... */
+	memset(dev->priv, 0, sizeof(struct smc_local));
+
+	dev->tbusy 	= 0;
+	dev->interrupt  = 0;
+	dev->start 	= 1;
+#ifdef MODULE
+	MOD_INC_USE_COUNT;
+#endif
+
+	/* reset the hardware */
+
+	smc_reset( ioaddr );
+	smc_enable( ioaddr );
+
+	/* Select which interface to use */
+ 	 
+	SMC_SELECT_BANK( 1 );
+	if ( dev->if_port == 1 ) { 	
+		outw( inw( ioaddr + CONFIG ) & ~CFG_AUI_SELECT, 
+			ioaddr + CONFIG );							
+	} 
+	else if ( dev->if_port == 2 ) { 
+		outw( inw( ioaddr + CONFIG ) | CFG_AUI_SELECT, 
+			ioaddr + CONFIG );							
+	}
+
+	/*
+  		According to Becker, I have to set the hardware address
+		at this point, because the (l)user can set it with an
+		ioctl.  Easily done... 
+	*/
+	SMC_SELECT_BANK( 1 );
+	for ( i = 0; i < 6; i += 2 ) { 
+		word	address;
+		
+		address = dev->dev_addr[ i + 1 ] << 8 ;
+		address  |= dev->dev_addr[ i ];
+		outw( address, ioaddr + ADDR0 + i );
+	}
+	return 0;
+}
+
+/*--------------------------------------------------------
+ . Called by the kernel to send a packet out into the void
+ . of the net.  This routine is largely based on 
+ . skeleton.c, from Becker.   
+ .--------------------------------------------------------
+*/
+static int smc_send_packet(struct sk_buff *skb, struct device *dev)
+{
+	if (dev->tbusy) {
+		/* If we get here, some higher level has decided we are broken.
+		   There should really be a "kick me" function call instead. */
+		int tickssofar = jiffies - dev->trans_start;
+		if (tickssofar < 5)
+			return 1;
+		printk(KERN_WARNING CARDNAME": transmit timed out, %s?\n",
+			tx_done(dev) ? "IRQ conflict" : 
+			"network cable problem");
+		/* "kick" the adaptor */
+		smc_reset( dev->base_addr );
+		smc_enable( dev->base_addr );
+
+		dev->tbusy = 0;
+		dev->trans_start = jiffies;
+		/* clear anything saved */
+		((struct smc_local *)dev->priv)->saved_skb = NULL;
+	}
+
+	/* If some higher layer thinks we've missed an tx-done interrupt
+	   we are passed NULL. Caution: dev_tint() handles the cli()/sti()
+	   itself. */
+	if (skb == NULL) {
+		dev_tint(dev);
+		return 0;
+	}
+
+	/* 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 (set_bit(0, (void*)&dev->tbusy) != 0) {
+		printk(KERN_WARNING CARDNAME": Transmitter access conflict.\n");
+		dev_kfree_skb (skb, FREE_WRITE);
+	} else {
+		/* Well, I want to send the packet.. but I don't know 
+		   if I can send it right now...  */	
+		return smc_wait_to_send_packet( skb, dev );
+	}
+	return 0;
+}
+
+/*--------------------------------------------------------------------
+ .
+ . This is the main routine of the driver, to handle the device when
+ . it needs some attention.
+ .
+ . So:
+ .   first, save state of the chipset
+ .   branch off into routines to handle each case, and acknowledge 
+ .	    each to the interrupt register
+ .   and finally restore state. 
+ .  
+ ---------------------------------------------------------------------*/
+#ifdef REALLY_NEW_KERNEL
+static void smc_interrupt(int irq, void * dev_id,  struct pt_regs * regs)
+#else 
+static void smc_interrupt(int irq, struct pt_regs * regs)
+#endif 
+{
+	struct device *dev 	= (struct device *)(irq2dev_map[irq]);
+	int ioaddr 		= dev->base_addr;
+	struct smc_local *lp 	= (struct smc_local *)dev->priv;
+
+	byte	status;
+	word	card_stats;
+	byte	mask;
+	int	timeout;
+	/* state registers */
+	word	saved_bank;
+	word	saved_pointer;
+
+
+	
+	PRINTK3((CARDNAME": SMC interrupt started \n"));
+
+	if (dev == NULL) {
+		printk(KERN_WARNING  CARDNAME": irq %d for unknown device.\n", 
+			irq);
+		return;
+	}
+
+/* will Linux let this happen ??  If not, this costs some speed */
+	if ( dev->interrupt ) { 
+		printk(KERN_WARNING CARDNAME": interrupt inside interrupt.\n");
+		return;
+	}
+		
+	dev->interrupt = 1;
+
+	saved_bank = inw( ioaddr + BANK_SELECT );
+
+	SMC_SELECT_BANK(2);
+	saved_pointer = inw( ioaddr + POINTER );
+
+	mask = inb( ioaddr + INT_MASK );
+	/* clear all interrupts */
+	outb( 0, ioaddr + INT_MASK );
+
+
+	/* set a timeout value, so I don't stay here forever */
+	timeout = 4;
+
+	PRINTK2((KERN_WARNING CARDNAME ": MASK IS %x \n", mask ));
+	do { 	
+		/* read the status flag, and mask it */
+		status = inb( ioaddr + INTERRUPT ) & mask;
+		if (!status )
+			break;
+
+		PRINTK3((KERN_WARNING CARDNAME
+			": Handling interrupt status %x \n", status )); 
+
+		if (status & IM_RCV_INT) {
+			/* Got a packet(s). */
+			PRINTK2((KERN_WARNING CARDNAME
+				": Recieve Interrupt\n"));
+			smc_rcv(dev);
+		} else if (status & IM_TX_INT ) {
+			PRINTK2((KERN_WARNING CARDNAME
+				": TX ERROR handled\n"));
+			smc_tx(dev);
+			outb(IM_TX_INT, ioaddr + INTERRUPT ); 
+		} else if (status & IM_TX_EMPTY_INT ) {
+			/* update stats */
+			SMC_SELECT_BANK( 0 );
+			card_stats = inw( ioaddr + COUNTER );
+			/* single collisions */
+			lp->stats.collisions += card_stats & 0xF;
+			card_stats >>= 4;
+			/* multiple collisions */
+			lp->stats.collisions += card_stats & 0xF;
+
+			/* these are for when linux supports these statistics */
+#if 0 
+			card_stats >>= 4;
+			/* deferred */
+			card_stats >>= 4;
+			/* excess deferred */
+#endif 
+			SMC_SELECT_BANK( 2 );
+			PRINTK2((KERN_WARNING CARDNAME 
+				": TX_BUFFER_EMPTY handled\n"));
+			outb( IM_TX_EMPTY_INT, ioaddr + INTERRUPT );
+			mask &= ~IM_TX_EMPTY_INT;
+			lp->stats.tx_packets += lp->packets_waiting;
+			lp->packets_waiting = 0;
+
+		} else if (status & IM_ALLOC_INT ) {
+			PRINTK2((KERN_DEBUG CARDNAME
+				": Allocation interrupt \n"));
+			/* clear this interrupt so it doesn't happen again */
+			mask &= ~IM_ALLOC_INT;
+		
+			smc_hardware_send_packet( dev );
+			
+			/* enable xmit interrupts based on this */
+			mask |= ( IM_TX_EMPTY_INT | IM_TX_INT );
+
+			/* and let the card send more packets to me */
+			mark_bh( NET_BH );
+
+			PRINTK2((CARDNAME": Handoff done successfully.\n"));	
+		} else if (status & IM_RX_OVRN_INT ) {
+			lp->stats.rx_errors++;
+			lp->stats.rx_fifo_errors++;			
+			outb( IM_RX_OVRN_INT, ioaddr + INTERRUPT );
+		} else if (status & IM_EPH_INT ) {
+			PRINTK((CARDNAME ": UNSUPPORTED: EPH INTERRUPT \n"));
+		} else if (status & IM_ERCV_INT ) {
+			PRINTK((CARDNAME ": UNSUPPORTED: ERCV INTERRUPT \n"));
+			outb( IM_ERCV_INT, ioaddr + INTERRUPT );
+		}
+	} while ( timeout -- ); 
+
+	
+	/* restore state register */
+	SMC_SELECT_BANK( 2 );
+	outb( mask, ioaddr + INT_MASK );
+	
+	PRINTK3(( KERN_WARNING CARDNAME ": MASK is now %x \n", mask ));
+	outw( saved_pointer, ioaddr + POINTER );
+
+	SMC_SELECT_BANK( saved_bank );
+
+	dev->interrupt = 0;
+	PRINTK3((CARDNAME ": Interrupt done\n"));
+	return;
+}
+
+/*-------------------------------------------------------------
+ .
+ . smc_rcv -  receive a packet from the card
+ .
+ . There is ( at least ) a packet waiting to be read from
+ . chip-memory.
+ . 
+ . o Read the status 
+ . o If an error, record it  
+ . o otherwise, read in the packet 
+ --------------------------------------------------------------
+*/
+static void smc_rcv(struct device *dev)
+{
+	struct smc_local *lp = (struct smc_local *)dev->priv;
+	int 	ioaddr = dev->base_addr;
+	int 	packet_number;
+	word	status;
+	word	packet_length; 
+	
+	/* assume bank 2 */
+
+	packet_number = inw( ioaddr + FIFO_PORTS );
+
+	if ( packet_number & FP_RXEMPTY ) {
+		/* we got called , but nothing was on the FIFO */
+		PRINTK((CARDNAME ": WARNING: smc_rcv with nothing on FIFO. \n"));
+		/* don't need to restore anything */
+		return;
+	}
+		
+	/*  start reading from the start of the packet */
+	outw( PTR_READ | PTR_RCV | PTR_AUTOINC, ioaddr + POINTER );
+
+	/* First two words are status and packet_length */	
+	status 		= inw( ioaddr + DATA_1 );
+	packet_length 	= inw( ioaddr + DATA_1 );
+	
+	packet_length &= 0x07ff;  /* mask off top bits */
+
+	PRINTK2(("RCV: STATUS %4x LENGTH %4x\n", status, packet_length ));
+	/* 
+	 . the packet length contains 3 extra words : 
+	 . status, length, and a extra word with an odd byte .
+	*/
+	packet_length -= 6; 
+	
+	if ( !(status & RS_ERRORS ) ){			
+		/* do stuff to make a new packet */
+		struct sk_buff  * skb;
+		byte		* data;
+
+		/* read one extra byte */
+		if ( status & RS_ODDFRAME ) 	
+			packet_length++; 	
+
+		/* set multicast stats */ 
+		if ( status & RS_MULTICAST )
+			lp->stats.multicast++;
+
+#ifdef SUPPORT_OLD_KERNEL
+		skb = alloc_skb( packet_length + 5, GFP_ATOMIC );
+#else	
+		skb = dev_alloc_skb( packet_length + 5); 
+#endif
+
+		if ( skb == NULL ) {	
+			printk(KERN_NOTICE CARDNAME
+			": Low memory, packet dropped.\n");
+			lp->stats.rx_dropped++;
+		}
+
+		/* 
+		 ! This should work without alignment, but it could be
+		 ! in the worse case 
+		*/
+#ifndef SUPPORT_OLD_KERNEL
+		/* TODO: Should I use 32bit alignment here ? */
+		skb_reserve( skb, 2 );   /* 16 bit alignment */
+#endif
+
+		skb->dev = dev;
+#ifdef SUPPORT_OLD_KERNEL
+		skb->len = packet_length;
+		data = skb->data;
+#else
+		data = skb_put( skb, packet_length);
+#endif
+#ifdef USE_32_BIT
+		/* QUESTION:  Like in the TX routine, do I want 		
+		   to send the DWORDs or the bytes first, or some
+		   mixture.  A mixture might improve already slow PIO
+		   performance  */
+		PRINTK3((" Reading %d dwords (and %d bytes) \n", 
+			packet_length >> 2, packet_length & 3 ));
+		insl(ioaddr + DATA_1 , data, packet_length >> 2 ); 
+		/* read the left over bytes */
+		insb( ioaddr + DATA_1, data + (packet_length & 0xFFFFFC), 
+			packet_length & 0x3  );
+#else
+		PRINTK3((" Reading %d words and %d byte(s) \n", 
+			(packet_length >> 1 ), packet_length & 1 );
+		if ( packet_length & 1 ) 
+			*(data++) = inb( ioaddr + DATA_1 ); 
+		insw(ioaddr + DATA_1 , data, (packet_length + 1 ) >> 1);
+		if ( packet_length & 1 ) {
+			data += packet_length & ~1;
+			*((data++) = inb( ioaddr + DATA_1 ); 
+		}
+#endif 		
+#if	SMC_DEBUG > 2 	
+			print_packet( data, packet_length );
+#endif
+
+#ifndef SUPPORT_OLD_KERNEL
+		skb->protocol = eth_type_trans(skb, dev ); 
+#endif
+		netif_rx(skb);
+		lp->stats.rx_packets++;
+	} else {
+		/* error ... */
+		lp->stats.rx_errors++;
+			
+		if ( status & RS_ALGNERR )  lp->stats.rx_frame_errors++;  
+		if ( status & (RS_TOOSHORT | RS_TOOLONG ) )  
+			lp->stats.rx_length_errors++;
+		if ( status & RS_BADCRC)	lp->stats.rx_crc_errors++;
+	}
+	/*  error or good, tell the card to get rid of this packet */
+	outw( MC_RELEASE, ioaddr + MMU_CMD );
+
+
+	return;
+}
+
+
+/************************************************************************* 
+ . smc_tx
+ . 
+ . Purpose:  Handle a transmit error message.   This will only be called
+ .   when an error, because of the AUTO_RELEASE mode. 
+ . 
+ . Algorithm:
+ .	Save pointer and packet no
+ .	Get the packet no from the top of the queue
+ .	check if it's valid ( if not, is this an error??? )
+ .	read the status word 
+ .	record the error
+ .	( resend?  Not really, since we don't want old packets around )
+ .	Restore saved values 
+ ************************************************************************/ 
+static void smc_tx( struct device * dev ) 
+{
+	int	ioaddr = dev->base_addr;
+	struct smc_local *lp = (struct smc_local *)dev->priv;
+	byte saved_packet;
+	byte packet_no;
+	word tx_status;
+
+
+	/* assume bank 2  */
+
+	saved_packet = inb( ioaddr + PNR_ARR );
+	packet_no = inw( ioaddr + FIFO_PORTS );
+	packet_no &= 0x7F;
+
+	/* select this as the packet to read from */
+	outb( packet_no, ioaddr + PNR_ARR ); 
+	
+	/* read the first word from this packet */	
+	outw( PTR_AUTOINC | PTR_READ, ioaddr + POINTER );
+
+	tx_status = inw( ioaddr + DATA_1 );
+	PRINTK3((CARDNAME": TX DONE STATUS: %4x \n", tx_status ));
+	
+	lp->stats.tx_errors++;
+	if ( tx_status & TS_LOSTCAR ) lp->stats.tx_carrier_errors++;
+	if ( tx_status & TS_LATCOL  ) {
+		printk(KERN_DEBUG CARDNAME 
+			": Late collision occured on last xmit.\n");
+		lp->stats.tx_window_errors++;
+	}
+#if 0
+		if ( tx_status & TS_16COL ) { ... }
+#endif 
+
+	if ( tx_status & TS_SUCCESS ) {  
+		printk(CARDNAME": Successful packet caused interrupt \n");
+	} 
+	/* re-enable transmit */
+	SMC_SELECT_BANK( 0 );
+	outw( inw( ioaddr + TCR ) | TCR_ENABLE, ioaddr + TCR );
+
+	/* kill the packet */			
+	SMC_SELECT_BANK( 2 );
+	outw( MC_FREEPKT, ioaddr + MMU_CMD );
+
+	/* one less packet waiting for me */
+	lp->packets_waiting--;
+		
+	outb( saved_packet, ioaddr + PNR_ARR );
+	return;
+}
+
+/*----------------------------------------------------
+ . smc_close
+ . 
+ . this makes the board clean up everything that it can
+ . and not talk to the outside world.   Caused by
+ . an 'ifconfig ethX down'
+ .
+ -----------------------------------------------------*/
+static int smc_close(struct device *dev)
+{
+	dev->tbusy = 1;
+	dev->start = 0;
+
+	/* clear everything */
+	smc_shutdown( dev->base_addr );
+
+	/* Update the statistics here. */
+#ifdef MODULE
+	MOD_DEC_USE_COUNT;
+#endif
+
+	return 0;
+}
+
+/*------------------------------------------------------------
+ . Get the current statistics.	
+ . This may be called with the card open or closed. 
+ .-------------------------------------------------------------*/
+static struct enet_statistics * smc_query_statistics(struct device *dev) {
+	struct smc_local *lp = (struct smc_local *)dev->priv;
+
+	return &lp->stats;
+}
+
+/*-----------------------------------------------------------
+ . smc_set_multicast_list
+ .  
+ . This routine will, depending on the values passed to it,
+ . either make it accept multicast packets, go into 
+ . promiscuous mode ( for TCPDUMP and cousins ) or accept
+ . a select set of multicast packets  
+*/
+#ifdef SUPPORT_OLD_KERNEL
+static void smc_set_multicast_list( struct device * dev, 
+			int num_addrs, void * addrs ) 	
+#else
+static void smc_set_multicast_list(struct device *dev) 
+#endif 
+{
+	short ioaddr = dev->base_addr;
+
+	SMC_SELECT_BANK(0);
+#ifdef  SUPPORT_OLD_KERNEL
+	if ( num_addrs < 0 )  
+#else
+	if ( dev->flags & IFF_PROMISC ) 
+#endif 
+		outw( inw(ioaddr + RCR ) | RCR_PROMISC, ioaddr + RCR );
+
+/* BUG?  I never disable promiscuous mode if multicasting was turned on. 
+   Now, I turn off promiscouos mode, but I don't do anything to multicasting
+   when promiscuous mode is turned on. 
+*/
+
+	/* Here, I am setting this to accept all multicast packets.  
+	   I don't need to zero the multicast table, because the flag is
+	   checked before the table is 
+	*/
+#ifdef  SUPPORT_OLD_KERNEL 
+	else if ( num_addrs > 20 )	/* arbitrary constant */
+#else
+	else if (dev->flags & IFF_ALLMULTI)  
+#endif 
+		outw( inw(ioaddr + RCR ) | RCR_ALMUL, ioaddr + RCR ); 
+
+	/* We just get all multicast packets even if we only want them
+	 . from one source.  This will be changed at some future
+	 . point. */
+#ifdef  SUPPORT_OLD_KERNEL
+	else if (num_addrs > 0 ) { 
+/* the old kernel support will not have hardware multicast support. It would
+   involve more kludges, and make the multicast setting code even worse.  
+   Instead, just use the ALMUL method.   This is reasonable, considering that
+   it is seldom used
+*/
+		outw( inw( ioaddr + RCR ) & ~RCR_PROMISC, ioaddr + RCR );
+		outw( inw( ioadddr + RCR ) | RCR_ALMUL, ioadddr + RCR );
+	}
+#else
+	else if (dev->mc_count )  { 
+		/* support hardware multicasting */
+		
+		/* be sure I get rid of flags I might have set */	
+		outw( inw( ioaddr + RCR ) & ~(RCR_PROMISC | RCR_ALMUL), 
+			ioaddr + RCR );
+		/* NOTE: this has to set the bank, so make sure it is the
+		   last thing called.  The bank is set to zero at the top */
+		smc_setmulticast( ioaddr, dev->mc_count, dev->mc_list );
+	}
+#endif
+	else  {
+		outw( inw( ioaddr + RCR ) & ~(RCR_PROMISC | RCR_ALMUL), 
+			ioaddr + RCR );
+
+		/* 
+		  since I'm disabling all multicast entirely, I need to 
+		  clear the multicast list 
+		*/
+		SMC_SELECT_BANK( 3 );
+		outw( 0, ioaddr + MULTICAST1 ); 
+		outw( 0, ioaddr + MULTICAST2 ); 
+		outw( 0, ioaddr + MULTICAST3 ); 
+		outw( 0, ioaddr + MULTICAST4 ); 
+	}
+}
+
+#ifdef MODULE
+
+static char devicename[9] = { 0, };
+static struct device devSMC9194 = {
+	devicename, /* device name is inserted by linux/drivers/net/net_init.c */
+	0, 0, 0, 0,
+	0, 0,  /* I/O address, IRQ */
+	0, 0, 0, NULL, smc_init };
+
+int io = 0;
+int irq = 0;
+int ifport = 0;
+
+int init_module(void)
+{
+	int result;
+
+	if (io == 0)
+		printk(KERN_WARNING 
+		CARDNAME": You shouldn't use auto-probing with insmod!\n" );
+
+	/* copy the parameters from insmod into the device structure */
+	devSMC9194.base_addr = io;
+	devSMC9194.irq       = irq;
+	devSMC9194.if_port	= ifport;
+	if ((result = register_netdev(&devSMC9194)) != 0)
+		return result;
+
+	return 0;
+}
+
+void cleanup_module(void)
+{
+	/* No need to check MOD_IN_USE, as sys_delete_module() checks. */
+	unregister_netdev(&devSMC9194);
+
+	free_irq(devSMC9194.irq, NULL );
+	irq2dev_map[devSMC9194.irq] = NULL;
+	release_region(devSMC9194.base_addr, SMC_IO_EXTENT);
+
+	if (devSMC9194.priv)
+		kfree_s(devSMC9194.priv, sizeof(struct smc_local));
+}
+
+#endif /* MODULE */
+
diff -u --recursive --new-file v1.3.71/linux/drivers/net/smc9194.h linux/drivers/net/smc9194.h
--- v1.3.71/linux/drivers/net/smc9194.h	Thu Jan  1 02:00:00 1970
+++ linux/drivers/net/smc9194.h	Fri Mar  8 10:03:32 1996
@@ -0,0 +1,240 @@
+/*------------------------------------------------------------------------
+ . smc9194.h
+ . Copyright (C) 1996 by Erik Stahlman 
+ .
+ . This software may be used and distributed according to the terms
+ . of the GNU Public License, incorporated herein by reference.
+ .
+ . This file contains register information and access macros for 
+ . the SMC91xxx chipset.   
+ . 
+ . Information contained in this file was obtained from the SMC91C94 
+ . manual from SMC.  To get a copy, if you really want one, you can find 
+ . information under www.smc.com in the components division.
+ . ( this thanks to advice from Donald Becker ).
+ . 
+ . Authors
+ . 	Erik Stahlman				( erik@vt.edu )
+ .
+ . History
+ . 01/06/96		 Erik Stahlman   moved definitions here from main .c file
+ . 01/19/96		 Erik Stahlman	  polished this up some, and added better
+ .										  error handling
+ .
+ ---------------------------------------------------------------------------*/
+#ifndef _SMC9194_H_
+#define _SMC9194_H_
+
+/* I want some simple types */
+
+typedef unsigned char			byte;
+typedef unsigned short			word;
+typedef unsigned long int 		dword;
+
+
+/* Because of bank switching, the SMC91xxx uses only 16 I/O ports */
+
+#define SMC_IO_EXTENT	16
+
+
+/*---------------------------------------------------------------
+ .  
+ . A description of the SMC registers is probably in order here,
+ . although for details, the SMC datasheet is invaluable.  
+ . 
+ . Basically, the chip has 4 banks of registers ( 0 to 3 ), which
+ . are accessed by writing a number into the BANK_SELECT register
+ . ( I also use a SMC_SELECT_BANK macro for this ).
+ . 
+ . The banks are configured so that for most purposes, bank 2 is all
+ . that is needed for simple run time tasks.  
+ -----------------------------------------------------------------------*/
+
+/*
+ . Bank Select Register: 
+ .
+ .		yyyy yyyy 0000 00xx  
+ .		xx 		= bank number
+ .		yyyy yyyy	= 0x33, for identification purposes.
+*/
+#define	BANK_SELECT		14
+
+/* BANK 0  */
+
+#define	TCR 		0    	/* transmit control register */
+#define TCR_ENABLE	0x0001	/* if this is 1, we can transmit */ 
+#define TCR_FDUPLX    	0x0800  /* receive packets sent out */
+#define TCR_STP_SQET	0x1000	/* stop transmitting if Signal quality error */
+#define	TCR_MON_CNS	0x0400	/* monitors the carrier status */
+#define	TCR_PAD_ENABLE	0x0080	/* pads short packets to 64 bytes */
+
+#define	TCR_CLEAR	0	/* do NOTHING */
+/* the normal settings for the TCR register : */ 
+/* QUESTION: do I want to enable padding of short packets ? */
+#define	TCR_NORMAL  	TCR_ENABLE 
+
+
+#define EPH_STATUS	2
+#define ES_LINK_OK	0x4000	/* is the link integrity ok ? */
+
+#define	RCR		4
+#define RCR_SOFTRESET	0x8000 	/* resets the chip */	
+#define	RCR_STRIP_CRC	0x200	/* strips CRC */
+#define RCR_ENABLE	0x100	/* IFF this is set, we can recieve packets */
+#define RCR_ALMUL	0x4 	/* receive all multicast packets */
+#define	RCR_PROMISC	0x2	/* enable promiscuous mode */
+
+/* the normal settings for the RCR register : */
+#define	RCR_NORMAL	(RCR_STRIP_CRC | RCR_ENABLE)
+#define RCR_CLEAR	0x0		/* set it to a base state */
+
+#define	COUNTER		6
+#define	MIR		8
+#define	MCR		10
+/* 12 is reserved */
+
+/* BANK 1 */
+#define CONFIG			0
+#define CFG_AUI_SELECT	 	0x100
+#define	BASE			2
+#define	ADDR0			4
+#define	ADDR1			6
+#define	ADDR2			8
+#define	GENERAL			10
+#define	CONTROL			12
+#define	CTL_POWERDOWN		0x2000
+#define	CTL_LE_ENABLE		0x80
+#define	CTL_CR_ENABLE		0x40
+#define	CTL_TE_ENABLE		0x0020
+#define CTL_AUTO_RELEASE	0x0800
+#define	CTL_EPROM_ACCESS	0x0003 /* high if Eprom is being read */
+
+/* BANK 2 */
+#define MMU_CMD		0
+#define MC_BUSY		1	/* only readable bit in the register */
+#define MC_NOP		0
+#define	MC_ALLOC	0x20  	/* or with number of 256 byte packets */
+#define	MC_RESET	0x40	
+#define	MC_REMOVE	0x60  	/* remove the current rx packet */
+#define MC_RELEASE  	0x80  	/* remove and release the current rx packet */
+#define MC_FREEPKT  	0xA0  	/* Release packet in PNR register */
+#define MC_ENQUEUE	0xC0 	/* Enqueue the packet for transmit */
+ 	
+#define	PNR_ARR		2
+#define FIFO_PORTS	4
+
+#define FP_RXEMPTY  0x8000
+#define FP_TXEMPTY  0x80
+
+#define	POINTER		6
+#define PTR_READ	0x2000
+#define	PTR_RCV		0x8000
+#define	PTR_AUTOINC 	0x4000
+#define PTR_AUTO_INC	0x0040
+
+#define	DATA_1		8
+#define	DATA_2		10
+#define	INTERRUPT	12
+
+#define INT_MASK	13
+#define IM_RCV_INT	0x1
+#define	IM_TX_INT	0x2
+#define	IM_TX_EMPTY_INT	0x4	
+#define	IM_ALLOC_INT	0x8
+#define	IM_RX_OVRN_INT	0x10
+#define	IM_EPH_INT	0x20
+#define	IM_ERCV_INT	0x40 /* not on SMC9192 */		
+
+/* BANK 3 */
+#define	MULTICAST1	0
+#define	MULTICAST2	2
+#define	MULTICAST3	4
+#define	MULTICAST4	6
+#define	MGMT		8
+#define	REVISION	10 /* ( hi: chip id   low: rev # ) */
+
+
+/* this is NOT on SMC9192 */
+#define	ERCV		12
+
+#define CHIP_9190	3
+#define CHIP_9194	4
+#define CHIP_9195	5
+#define CHIP_91100	7
+
+static const char * chip_ids[ 15 ] =  { 
+	NULL, NULL, NULL, 
+	/* 3 */ "SMC91C90/91C92",
+	/* 4 */ "SMC91C94",
+	/* 5 */ "SMC91C95",
+	NULL,
+	/* 7 */ "SMC91C100", 
+	NULL, NULL, NULL, NULL, 
+	NULL, NULL, NULL};  
+
+/* 
+ . Transmit status bits 
+*/
+#define TS_SUCCESS 0x0001
+#define TS_LOSTCAR 0x0400
+#define TS_LATCOL  0x0200
+#define TS_16COL   0x0010
+
+/*
+ . Receive status bits
+*/
+#define RS_ALGNERR	0x8000
+#define RS_BADCRC	0x2000
+#define RS_ODDFRAME	0x1000
+#define RS_TOOLONG	0x0800
+#define RS_TOOSHORT	0x0400
+#define RS_MULTICAST	0x0001
+#define RS_ERRORS	(RS_ALGNERR | RS_BADCRC | RS_TOOLONG | RS_TOOSHORT) 
+
+static const char * interfaces[ 2 ] = { "TP", "AUI" };
+
+/*-------------------------------------------------------------------------
+ .  I define some macros to make it easier to do somewhat common
+ . or slightly complicated, repeated tasks. 
+ --------------------------------------------------------------------------*/
+
+/* select a register bank, 0 to 3  */
+
+#define SMC_SELECT_BANK(x)  { outw( x, ioaddr + BANK_SELECT ); } 
+
+/* define a small delay for the reset */
+#define SMC_DELAY() { inw( ioaddr + RCR );\
+			inw( ioaddr + RCR );\
+			inw( ioaddr + RCR );  }
+
+/* this enables an interrupt in the interrupt mask register */
+#define SMC_ENABLE_INT(x) {\
+		unsigned char mask;\
+		SMC_SELECT_BANK(2);\
+		mask = inb( ioaddr + INT_MASK );\
+		mask |= (x);\
+		outb( mask, ioaddr + INT_MASK ); \
+}
+
+/* this disables an interrupt from the interrupt mask register */
+
+#define SMC_DISABLE_INT(x) {\
+		unsigned char mask;\
+		SMC_SELECT_BANK(2);\
+		mask = inb( ioaddr + INT_MASK );\
+		mask &= ~(x);\
+		outb( mask, ioaddr + INT_MASK ); \
+}
+
+/*----------------------------------------------------------------------
+ . Define the interrupts that I want to recieve from the card
+ . 
+ . I want: 
+ .  IM_EPH_INT, for nasty errors
+ .  IM_RCV_INT, for happy recieved packets
+ .  IM_RX_OVRN_INT, because I have to kick the reciever
+ --------------------------------------------------------------------------*/
+#define SMC_INTERRUPT_MASK   (IM_EPH_INT | IM_RX_OVRN_INT | IM_RCV_INT) 
+
+#endif  /* _SMC_9194_H_ */
+
diff -u --recursive --new-file v1.3.71/linux/drivers/pci/pci.c linux/drivers/pci/pci.c
--- v1.3.71/linux/drivers/pci/pci.c	Fri Feb 23 13:54:36 1996
+++ linux/drivers/pci/pci.c	Thu Mar  7 16:16:39 1996
@@ -89,6 +89,7 @@
 	DEVICE( MATROX,		MATROX_MIL     ,"Millenium"),
 	DEVICE( MATROX,		MATROX_MGA_IMP,	"MGA Impression"),
 	DEVICE( CT,		CT_65545,	"65545"),
+	DEVICE( CT,		CT_65548,	"65548"),
 	DEVICE( FD,		FD_36C70,	"TMC-18C30"),
 	DEVICE( SI,		SI_6201,	"6201"),
 	DEVICE( SI,		SI_6202,	"6202"),
@@ -100,6 +101,7 @@
 	DEVICE( SI,		SI_5513,		"85C5513"),
 	DEVICE( HP,		HP_J2585A,	"J2585A"),
 	DEVICE( PCTECH,		PCTECH_RZ1000,  "RZ1000 (buggy)"),
+	DEVICE( PCTECH,		PCTECH_RZ1001,  "RZ1000"),
 	DEVICE( DPT,		DPT,		"SmartCache/Raid"),
 	DEVICE( OPTI,		OPTI_92C178,	"92C178"),
 	DEVICE( OPTI,		OPTI_82C557,	"82C557"),
@@ -169,6 +171,7 @@
 	DEVICE( PLX,		PLX_9060,	"PCI9060 i960 bridge"),
 	DEVICE( ALLIANCE,	ALLIANCE_PROMOTIO, "Promotion-6410"),
 	DEVICE( ALLIANCE,	ALLIANCE_PROVIDEO, "Provideo"),
+	DEVICE( VMIC,		VMIC_VME,	"VMIVME-7587"),
 	DEVICE( MUTECH,		MUTECH_MV1000,	"MV-1000"),
 	DEVICE( ZEITNET,	ZEITNET_1221,	"1221"),
 	DEVICE( ZEITNET,	ZEITNET_1225,	"1225"),
@@ -204,6 +207,7 @@
 	DEVICE( INTEL,		INTEL_82371_1,	"82371 Triton PIIX"),
 	DEVICE( INTEL,		INTEL_P6,	"Orion P6"),
 	DEVICE( ADAPTEC,	ADAPTEC_7850,	"AIC-7850"),
+	DEVICE( ADAPTEC,	ADAPTEC_7855,	"AIC-7855"),
 	DEVICE( ADAPTEC,	ADAPTEC_7870,	"AIC-7870"),
 	DEVICE( ADAPTEC,	ADAPTEC_7871,	"AIC-7871"),
 	DEVICE( ADAPTEC,	ADAPTEC_7872,	"AIC-7872"),
@@ -428,6 +432,7 @@
 	      case PCI_VENDOR_ID_AI:		return "Acer Incorporated";
 	      case PCI_VENDOR_ID_MATROX:	return "Matrox";
 	      case PCI_VENDOR_ID_CT:		return "Chips & Technologies";
+	      case PCI_VENDOR_ID_MIRO:		return "Miro";
 	      case PCI_VENDOR_ID_FD:		return "Future Domain";
 	      case PCI_VENDOR_ID_SI:		return "Silicon Integrated Systems";
 	      case PCI_VENDOR_ID_HP:		return "Hewlett Packard";
@@ -468,7 +473,9 @@
 	      case PCI_VENDOR_ID_IMAGINGTECH:	return "Imaging Technology";
 	      case PCI_VENDOR_ID_PLX:		return "PLX";
 	      case PCI_VENDOR_ID_ALLIANCE:	return "Alliance";
+	      case PCI_VENDOR_ID_VMIC:		return "VMIC";
 	      case PCI_VENDOR_ID_MUTECH:	return "Mutech";
+	      case PCI_VENDOR_ID_TOSHIBA:	return "Toshiba";
 	      case PCI_VENDOR_ID_ZEITNET:	return "ZeitNet";
 	      case PCI_VENDOR_ID_SPECIALIX:	return "Specialix";
 	      case PCI_VENDOR_ID_RP:		return "Comtrol";
diff -u --recursive --new-file v1.3.71/linux/drivers/scsi/Config.in linux/drivers/scsi/Config.in
--- v1.3.71/linux/drivers/scsi/Config.in	Wed Feb  7 15:11:27 1996
+++ linux/drivers/scsi/Config.in	Wed Mar  6 14:55:18 1996
@@ -11,6 +11,8 @@
 
 bool 'Verbose SCSI error reporting (kernel size +=12K)' CONFIG_SCSI_CONSTANTS
 
+bool 'Automatic Disk Geometery determination' CONFIG_SCSI_AUTO_BIOSP
+
 mainmenu_option next_comment
 comment 'SCSI low-level drivers'
 
diff -u --recursive --new-file v1.3.71/linux/drivers/scsi/fdomain.c linux/drivers/scsi/fdomain.c
--- v1.3.71/linux/drivers/scsi/fdomain.c	Sat Mar  2 10:43:40 1996
+++ linux/drivers/scsi/fdomain.c	Fri Mar  8 09:43:26 1996
@@ -1,6 +1,6 @@
 /* fdomain.c -- Future Domain TMC-16x0 SCSI driver
  * Created: Sun May  3 18:53:19 1992 by faith@cs.unc.edu
- * Revised: Thu Oct 12 15:59:37 1995 by r.faith@ieee.org
+ * Revised: Thu Feb  8 08:21:33 1996 by r.faith@ieee.org
  * Author: Rickard E. Faith, faith@cs.unc.edu
  * Copyright 1992, 1993, 1994, 1995 Rickard E. Faith
  *
@@ -192,10 +192,18 @@
 
  **************************************************************************/
 
+#ifdef PCMCIA
+#define MODULE
+#endif
+
 #ifdef MODULE
 #include <linux/module.h>
 #endif
 
+#ifdef PCMCIA
+#undef MODULE
+#endif
+
 #include <linux/sched.h>
 #include <asm/io.h>
 #include <linux/blk.h>
@@ -346,7 +354,8 @@
    (void *)0xca000,
    (void *)0xce000,
    (void *)0xde000,
-   (void *)0xd0000,		/* Extra addresses for PCI boards */
+   (void *)0xcc000,		/* Extra addresses for PCI boards */
+   (void *)0xd0000,
    (void *)0xe0000,
 };
 #define ADDRESS_COUNT (sizeof( addresses ) / sizeof( unsigned ))
@@ -402,6 +411,7 @@
    { "IBM F1 P2 BIOS v1.0104/29/93",                        5, 28,  3, -1, 0 },
    { "Future Domain Corp. V1.0008/18/93",                   5, 33,  3,  4, 0 },
    { "Future Domain Corp. V1.0008/18/93",                  26, 33,  3,  4, 1 },
+   { "Adaptec AHA-2920 PCI-SCSI Card",                     42, 31,  3,  0, 1 },
 				/* This next signature may not be a 3.5 bios */
    { "Future Domain Corp. V2.0108/18/93",                   5, 33,  3,  5, 0 },
    { "FUTURE DOMAIN CORP.  V3.5008/18/93",                  5, 34,  3,  5, 0 },
diff -u --recursive --new-file v1.3.71/linux/drivers/scsi/scsi.c linux/drivers/scsi/scsi.c
--- v1.3.71/linux/drivers/scsi/scsi.c	Sat Mar  2 10:43:40 1996
+++ linux/drivers/scsi/scsi.c	Tue Mar  5 10:59:00 1996
@@ -208,6 +208,7 @@
 #define BLIST_BORKEN    0x04
 #define BLIST_KEY       0x08
 #define BLIST_SINGLELUN 0x10
+#define BLIST_NOTQ	0x20
 
 struct dev_info{
     const char * vendor;
@@ -235,6 +236,7 @@
 {"MAXTOR","XT-4170S","B5A", BLIST_NOLUN},       /* Locks-up sometimes when LUN>0 polled. */
 {"MAXTOR","XT-8760S","B7B", BLIST_NOLUN},       /* guess what? */
 {"MEDIAVIS","RENO CD-ROMX2A","2.03",BLIST_NOLUN},/*Responds to all lun */
+{"MICROP", "4110", "*", BLIST_NOTQ},		/* Buggy Tagged Queuing */
 {"NEC","CD-ROM DRIVE:841","1.0", BLIST_NOLUN},  /* Locks-up when LUN>0 polled. */
 {"RODIME","RO3000S","2.33", BLIST_NOLUN},       /* Locks up if polled for lun != 0 */
 {"SEAGATE", "ST157N", "\004|j", BLIST_NOLUN},   /* causes failed REQUEST SENSE on lun 1 
@@ -682,17 +684,6 @@
     SDpnt->scsi_level++;
 
   /*
-   * Set the tagged_queue flag for SCSI-II devices that purport to support
-   * tagged queuing in the INQUIRY data.
-   */
-  SDpnt->tagged_queue = 0;
-  if ((SDpnt->scsi_level >= SCSI_2) &&
-      (scsi_result[7] & 2)) {
-    SDpnt->tagged_supported = 1;
-    SDpnt->current_tag = 0;
-  }
-
-  /*
    * Accommodate drivers that want to sleep when they should be in a polling
    * loop.
    */
@@ -702,6 +693,18 @@
    * Get any flags for this device.
    */
   bflags = get_device_flags (scsi_result);
+
+  /*
+   * Set the tagged_queue flag for SCSI-II devices that purport to support
+   * tagged queuing in the INQUIRY data.
+   */
+  SDpnt->tagged_queue = 0;
+  if ((SDpnt->scsi_level >= SCSI_2) &&
+      (scsi_result[7] & 2) &&
+      !(bflags & BLIST_NOTQ)) {
+    SDpnt->tagged_supported = 1;
+    SDpnt->current_tag = 0;
+  }
 
   /*
    * Some revisions of the Texel CD ROM drives have handshaking problems when
diff -u --recursive --new-file v1.3.71/linux/drivers/scsi/sd_ioctl.c linux/drivers/scsi/sd_ioctl.c
--- v1.3.71/linux/drivers/scsi/sd_ioctl.c	Sat Mar  2 10:43:40 1996
+++ linux/drivers/scsi/sd_ioctl.c	Thu Mar  7 10:17:46 1996
@@ -4,6 +4,7 @@
  * ioctl handling for SCSI disks
  */
 
+#include <linux/config.h>
 #include <linux/kernel.h>
 #include <linux/sched.h>
 #include <linux/mm.h>
@@ -34,13 +35,25 @@
 	if (error)
 	    return error;
 	host = rscsi_disks[MINOR(dev) >> 4].device->host;
-	diskinfo[0] = 0;
-	diskinfo[1] = 0;
-	diskinfo[2] = 0;
+
+/* default to most commonly used values */
+
+        diskinfo[0] = 0x40;
+        diskinfo[1] = 0x20;
+        diskinfo[2] = rscsi_disks[MINOR(dev) >> 4].capacity >> 11;
+
+/* override with calculated, extended default, or driver values */
+
+#ifdef CONFIG_SCSI_AUTO_BIOSP
+        scsicam_bios_param(&rscsi_disks[MINOR(dev) >> 4],
+				    dev, &diskinfo[0]);
+#else
 	if(host->hostt->bios_param != NULL)
 	    host->hostt->bios_param(&rscsi_disks[MINOR(dev) >> 4],
 				    dev,
 				    &diskinfo[0]);
+#endif
+
 	put_user(diskinfo[0], &loc->heads);
 	put_user(diskinfo[1], &loc->sectors);
 	put_user(diskinfo[2], &loc->cylinders);
diff -u --recursive --new-file v1.3.71/linux/fs/exec.c linux/fs/exec.c
--- v1.3.71/linux/fs/exec.c	Tue Mar  5 10:11:06 1996
+++ linux/fs/exec.c	Tue Mar  5 09:45:28 1996
@@ -106,11 +106,6 @@
 	}
 	return -EINVAL;
 }
-
-struct linux_binfmt * get_binfmt_list()
-{
-	return formats;
-}
 #endif	/* CONFIG_MODULES */
 
 int open_inode(struct inode * inode, int mode)
@@ -535,7 +530,7 @@
 	}
 #endif
 	for (try=0; try<2; try++) {
-		for (fmt = get_binfmt_list() ; fmt ; fmt = fmt->next) {
+		for (fmt = formats ; fmt ; fmt = fmt->next) {
 			int (*fn)(struct linux_binprm *, struct pt_regs *) = fmt->load_binary;
 			if (!fn)
 				continue;
diff -u --recursive --new-file v1.3.71/linux/fs/fat/dir.c linux/fs/fat/dir.c
--- v1.3.71/linux/fs/fat/dir.c	Sun Feb 11 15:32:46 1996
+++ linux/fs/fat/dir.c	Tue Mar  5 13:03:26 1996
@@ -7,7 +7,8 @@
  *
  *  Hidden files 1995 by Albert Cahalan <albert@ccs.neu.edu> <adc@coe.neu.edu>
  *
- *  VFAT extensions by Gordon Chaffee, merged with msdos fs by Henrik Storner
+ *  VFAT extensions by Gordon Chaffee <chaffee@plateau.cs.berkeley.edu>
+ *  Merged with msdos fs by Henrik Storner <storner@osiris.ping.dk>
  */
 
 #include <linux/fs.h>
@@ -18,6 +19,7 @@
 #include <linux/string.h>
 #include <linux/ioctl.h>
 #include <linux/dirent.h>
+#include <linux/mm.h>
 
 #include <asm/segment.h>
 
@@ -45,11 +47,58 @@
 	file_fsync		/* fsync */
 };
 
+/* Convert Unicode string to ASCII.  If uni_xlate is enabled and we
+ * can't get a 1:1 conversion, use a colon as an escape character since
+ * it is normally invalid on the vfat filesystem.  The following three
+ * characters are a sort of uuencoded 16 bit Unicode value.  This lets
+ * us do a full dump and restore of Unicode filenames.  We could get
+ * into some trouble with long Unicode names, but ignore that right now.
+ */
+static int
+uni2ascii(unsigned char *uni, unsigned char *ascii, int uni_xlate)
+{
+	unsigned char *ip, *op;
+	unsigned char page, pg_off;
+	unsigned char *uni_page;
+	unsigned short val;
+
+	ip = uni;
+	op = ascii;
+
+	while (*ip || ip[1]) {
+		pg_off = *ip++;
+		page = *ip++;
+		
+		uni_page = fat_uni2asc_pg[page];
+		if (uni_page && uni_page[pg_off]) {
+			*op++ = uni_page[pg_off];
+		} else {
+			if (uni_xlate == 1) {
+				*op++ = ':';
+				val = (pg_off << 8) + page;
+				op[2] = fat_uni2code[val & 0x3f];
+				val >>= 6;
+				op[1] = fat_uni2code[val & 0x3f];
+				val >>= 6;
+				*op = fat_uni2code[val & 0x3f];
+				op += 3;
+			} else {
+				*op++ = '?';
+			}
+		}
+	}
+	*op = 0;
+	return (op - ascii);
+}
+
 int fat_readdirx(
 	struct inode *inode,
 	struct file *filp,
 	void *dirent,
+	fat_filldir_t fat_filldir,
 	filldir_t filldir,
+	int shortnames,
+	int longnames,
 	int both)
 {
 	struct super_block *sb = inode->i_sb;
@@ -58,18 +107,21 @@
 	struct buffer_head *bh;
 	struct msdos_dir_entry *de;
 	unsigned long oldpos = filp->f_pos;
+	unsigned long spos;
 	int is_long;
 	char longname[275];
 	unsigned char long_len = 0; /* Make compiler warning go away */
 	unsigned char alias_checksum = 0; /* Make compiler warning go away */
-
+	unsigned char long_slots = 0;
+	int uni_xlate = MSDOS_SB(sb)->unicode_xlate;
+	unsigned char *unicode = NULL;
 
 	if (!inode || !S_ISDIR(inode->i_mode))
 		return -EBADF;
 /* Fake . and .. for the root directory. */
 	if (inode->i_ino == MSDOS_ROOT_INO) {
 		while (oldpos < 2) {
-			if (filldir(dirent, "..", oldpos+1, oldpos, MSDOS_ROOT_INO) < 0)
+			if (fat_filldir(filldir, dirent, "..", oldpos+1, 0, oldpos, oldpos, 0, MSDOS_ROOT_INO) < 0)
 				return 0;
 			oldpos++;
 			filp->f_pos++;
@@ -81,23 +133,10 @@
 		return -ENOENT;
 
  	bh = NULL;
-	longname[0] = '\0';
+	longname[0] = longname[1] = 0;
 	is_long = 0;
 	ino = fat_get_entry(inode,&filp->f_pos,&bh,&de);
 	while (ino > -1) {
-		/* Should we warn about finding extended entries on fs
-		 * mounted non-vfat ? Deleting or renaming files will cause
-		 * corruption, as extended entries are not updated.
-		 */
-		if (!MSDOS_SB(sb)->vfat && 
-		    !MSDOS_SB(sb)->umsdos &&  /* umsdos is safe for us */
-		    !IS_RDONLY(inode) && 
-		    (de->attr == ATTR_EXT) &&
-		    !MSDOS_SB(sb)->quiet) {
-			printk("MSDOS-fs warning: vfat directory entry found on fs mounted non-vfat (device %s)\n",
-			       kdevname(sb->s_dev));
-		}
-
 		/* Check for long filename entry */
 		if (MSDOS_SB(sb)->vfat && (de->name[0] == (__s8) DELETED_FLAG)) {
 			is_long = 0;
@@ -105,19 +144,24 @@
 		} else if (MSDOS_SB(sb)->vfat && de->attr ==  ATTR_EXT) {
 			int get_new_entry;
 			struct msdos_dir_slot *ds;
-			unsigned char page, pg_off, ascii;
-			unsigned char *uni_page;
-			unsigned char offset;
+			int offset;
 			unsigned char id;
 			unsigned char slot;
 			unsigned char slots = 0;
-			int i;
+
+			if (!unicode) {
+				unicode = (unsigned char *)
+					__get_free_page(GFP_KERNEL);
+				if (!unicode)
+					return -ENOMEM;
+			}
 
 			offset = 0;
 			ds = (struct msdos_dir_slot *) de;
 			id = ds->id;
 			if (id & 0x40) {
 				slots = id & ~0x40;
+				long_slots = slots;
 				is_long = 1;
 				alias_checksum = ds->alias_checksum;
 			}
@@ -140,43 +184,18 @@
 					break;
 				}
 				slot--;
-				offset = slot * 13;
+				offset = slot * 26;
 				PRINTK(("2. get_new_entry: %d\n", get_new_entry));
-				for (i = 0; i < 10; i += 2) {
-					pg_off = ds->name0_4[i];
-					page = ds->name0_4[i+1];
-					if (pg_off == 0 && page == 0) {
-						goto found_end;
-					}
-					uni_page = fat_uni2asc_pg[page];
-					ascii = uni_page[pg_off];
-					longname[offset++] = ascii ? ascii : '?';
-				}
-				for (i = 0; i < 12; i += 2) {
-					pg_off = ds->name5_10[i];
-					page = ds->name5_10[i+1];
-					if (pg_off == 0 && page == 0) {
-						goto found_end;
-					}
-					uni_page = fat_uni2asc_pg[page];
-					ascii = uni_page[pg_off];
-					longname[offset++] = ascii ? ascii : '?';
-				}
-				for (i = 0; i < 4; i += 2) {
-					pg_off = ds->name11_12[i];
-					page = ds->name11_12[i+1];
-					if (pg_off == 0 && page == 0) {
-						goto found_end;
-					}
-					uni_page = fat_uni2asc_pg[page];
-					ascii = uni_page[pg_off];
-					longname[offset++] = ascii ? ascii : '?';
-				}
-				found_end:
-				PRINTK(("3. get_new_entry: %d\n", get_new_entry));
+				memcpy(&unicode[offset], ds->name0_4, 10);
+				offset += 10;
+				memcpy(&unicode[offset], ds->name5_10, 12);
+				offset += 12;
+				memcpy(&unicode[offset], ds->name11_12, 4);
+				offset += 4;
+
 				if (ds->id & 0x40) {
-					longname[offset] = '\0';
-					long_len = offset;
+					unicode[offset] = 0;
+					unicode[offset+1] = 0;
 				}
 				if (slot > 0) {
 					ino = fat_get_entry(inode,&filp->f_pos,&bh,&de);
@@ -190,7 +209,6 @@
 				}
 				PRINTK(("5. get_new_entry: %d\n", get_new_entry));
 			}
-			PRINTK(("Long filename: %s, get_new_entry: %d\n", longname, get_new_entry));
 		} else if (!IS_FREE(de->name) && !(de->attr & ATTR_VOLUME)) {
 			char bufname[14];
 			char *ptname = bufname;
@@ -198,8 +216,8 @@
 
 			if (is_long) {
 				unsigned char sum;
-
-				for (sum = i = 0; i < 11; i++) {
+				long_len = uni2ascii(unicode, longname, uni_xlate);
+				for (sum = 0, i = 0; i < 11; i++) {
 					sum = (((sum&1)<<7)|((sum&0xfe)>>1)) + de->name[i];
 				}
 
@@ -214,7 +232,7 @@
 				dotoffset = 1;
 				ptname = bufname+1;
 			}
-			for (i = last = 0; i < 8; i++) {
+			for (i = 0, last = 0; i < 8; i++) {
 				if (!(c = de->name[i])) break;
 				if (c >= 'A' && c <= 'Z') c += 32;
 				/* see namei.c, msdos_format_name */
@@ -240,22 +258,30 @@
 				else if (!strcmp(de->name,MSDOS_DOTDOT))
 					ino = fat_parent_ino(inode,0);
 
-				if (!is_long) {
+				if (shortnames || !is_long) {
 					dcache_add(inode, bufname, i+dotoffset, ino);
 					if (both) {
 						bufname[i+dotoffset] = '\0';
 					}
-					if (filldir(dirent, bufname, i+dotoffset, oldpos, ino) < 0) {
+					spos = oldpos;
+					if (is_long) {
+						spos = filp->f_pos - sizeof(struct msdos_dir_entry);
+					} else {
+						long_slots = 0;
+					}
+					if (fat_filldir(filldir, dirent, bufname, i+dotoffset, 0, oldpos, spos, long_slots, ino) < 0) {
 						filp->f_pos = oldpos;
 						break;
 					}
-				} else {
+				}
+				if (is_long && longnames) {
 					dcache_add(inode, longname, long_len, ino);
 					if (both) {
 						memcpy(&longname[long_len+1], bufname, i+dotoffset);
 						long_len += i+dotoffset;
 					}
-					if (filldir(dirent, longname, long_len, oldpos, ino) < 0) {
+					spos = filp->f_pos - sizeof(struct msdos_dir_entry);
+					if (fat_filldir(filldir, dirent, longname, long_len, 1, oldpos, spos, long_slots, ino) < 0) {
 						filp->f_pos = oldpos;
 						break;
 					}
@@ -270,22 +296,45 @@
 		ino = fat_get_entry(inode,&filp->f_pos,&bh,&de);	
 	}
 	if (bh) brelse(bh);
+	if (unicode) {
+		free_page((unsigned long) unicode);
+	}
 	return 0;
 }
 
+static int fat_filldir(
+	filldir_t filldir,
+	void * buf,
+	const char * name,
+	int name_len,
+	int is_long,
+	off_t offset,
+	off_t short_offset,
+	int long_slots,
+	ino_t ino)
+{
+	return filldir(buf, name, name_len, offset, ino);
+}
+
 int fat_readdir(
 	struct inode *inode,
 	struct file *filp,
 	void *dirent,
 	filldir_t filldir)
 {
-    return fat_readdirx(inode, filp, dirent, filldir, 0);
+	return fat_readdirx(inode, filp, dirent, fat_filldir, filldir,
+			    0, 1, 0);
 }
+
 static int vfat_ioctl_fill(
+	filldir_t filldir,
 	void * buf,
 	const char * name,
 	int name_len,
+	int is_long,
 	off_t offset,
+	off_t short_offset,
+	int long_slots,
 	ino_t ino)
 {
 	struct dirent *d1 = (struct dirent *)buf;
@@ -341,7 +390,14 @@
 	case VFAT_IOCTL_READDIR_BOTH: {
 		struct dirent *d1 = (struct dirent *)arg;
 		put_user(0, &d1->d_reclen);
-		return fat_readdirx(inode,filp,(void *)arg,vfat_ioctl_fill,1);
+		return fat_readdirx(inode,filp,(void *)arg,
+				    vfat_ioctl_fill, NULL, 0, 1, 1);
+	}
+	case VFAT_IOCTL_READDIR_SHORT: {
+		struct dirent *d1 = (struct dirent *)arg;
+		put_user(0, &d1->d_reclen);
+		return fat_readdirx(inode,filp,(void *)arg,
+				    vfat_ioctl_fill, NULL, 1, 0, 1);
 	}
 	default:
 		return -EINVAL;
@@ -349,3 +405,20 @@
 
 	return 0;
 }
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 8
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -8
+ * c-argdecl-indent: 8
+ * c-label-offset: -8
+ * c-continued-statement-offset: 8
+ * c-continued-brace-offset: 0
+ * End:
+ */
diff -u --recursive --new-file v1.3.71/linux/fs/fat/fatfs_syms.c linux/fs/fat/fatfs_syms.c
--- v1.3.71/linux/fs/fat/fatfs_syms.c	Fri Feb 16 11:34:09 1996
+++ linux/fs/fat/fatfs_syms.c	Tue Mar  5 13:03:26 1996
@@ -22,6 +22,7 @@
 	X(fat_bmap),
 	X(fat_brelse),
 	X(fat_cache_inval_inode),
+	X(fat_code2uni),
 	X(fat_date_unix2dos),
 	X(fat_dir_operations),
 	X(fat_file_read),
@@ -37,12 +38,14 @@
 	X(fat_put_super),
 	X(fat_read_inode),
 	X(fat_read_super),
+	X(fat_readdirx),
 	X(fat_readdir),
 	X(fat_scan),
 	X(fat_smap),
 	X(fat_statfs),
 	X(fat_truncate),
 	X(fat_uni2asc_pg),
+	X(fat_uni2code),
 	X(fat_unlock_creation),
 	X(fat_write_inode),
 #include <linux/symtab_end.h>
diff -u --recursive --new-file v1.3.71/linux/fs/fat/inode.c linux/fs/fat/inode.c
--- v1.3.71/linux/fs/fat/inode.c	Wed Feb 28 11:50:10 1996
+++ linux/fs/fat/inode.c	Tue Mar  5 13:03:26 1996
@@ -100,13 +100,13 @@
 			else return 0;
 		}
 		else if (!strcmp(this_char,"dots")) {
-		  *dotsOK = 1;
+			*dotsOK = 1;
 		}
 		else if (!strcmp(this_char,"nodots")) {
-		  *dotsOK = 0;
+			*dotsOK = 0;
 		}
 		else if (!strcmp(this_char,"showexec")) {
-		  *showexec = 1;
+			*showexec = 1;
 		}
 		else if (!strcmp(this_char,"dotsOK") && value) {
 			if (!strcmp(value,"yes")) *dotsOK = 1;
@@ -162,7 +162,6 @@
 				return 0;
 			*sys_immutable = 1;
 		}
-		else return 1;
 	}
 	return 1;
 }
@@ -385,6 +384,7 @@
 	MSDOS_I(inode)->i_binary = 1;
 	inode->i_uid = MSDOS_SB(inode->i_sb)->fs_uid;
 	inode->i_gid = MSDOS_SB(inode->i_sb)->fs_gid;
+	inode->i_version = ++event;
 	if (inode->i_ino == MSDOS_ROOT_INO) {
 		inode->i_mode = (S_IRWXUGO & ~MSDOS_SB(inode->i_sb)->fs_umask) |
 		    S_IFDIR;
diff -u --recursive --new-file v1.3.71/linux/fs/fat/tables.c linux/fs/fat/tables.c
--- v1.3.71/linux/fs/fat/tables.c	Fri Feb  9 17:53:06 1996
+++ linux/fs/fat/tables.c	Tue Mar  5 13:03:26 1996
@@ -12,7 +12,53 @@
 
 #include "tables.h"
 
-unsigned char page00[256] = {
+unsigned char fat_uni2code[64] = {
+	'0', '1', '2', '3', '4', '5', '6', '7',
+	'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
+	'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+	'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
+	'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
+	'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+	'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+	'u', 'v', 'w', 'x', 'y', 'z', '+', '-'
+};
+
+unsigned char fat_code2uni[256] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0x3e, 0xff, 0x3f, 0xff, 0xff, 
+	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 
+	0x08, 0x09, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 
+	0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 
+	0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 
+	0x21, 0x22, 0x23, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 
+	0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 
+	0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 
+	0x3b, 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+};
+
+static unsigned char page00[256] = {
 	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 0x00-0x07 */
 	0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, /* 0x08-0x0F */
 	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, /* 0x10-0x17 */
@@ -49,7 +95,7 @@
 };
 
 
-unsigned char page25[256] = {
+static unsigned char page25[256] = {
 	0xC4, 0x00, 0xB3, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x00-0x07 */
 	0x00, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x00, 0x00, /* 0x08-0x0F */
 	0xBF, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, /* 0x10-0x17 */
diff -u --recursive --new-file v1.3.71/linux/fs/fat/tables.h linux/fs/fat/tables.h
--- v1.3.71/linux/fs/fat/tables.h	Wed Feb  7 15:11:31 1996
+++ linux/fs/fat/tables.h	Tue Mar  5 13:03:27 1996
@@ -5,8 +5,17 @@
 
 extern unsigned char fat_a2alias[];		/* Ascii to alias name conversion table */
 extern struct unicode_value fat_a2uni[];	/* Ascii to Unicode conversion table */
-
 extern unsigned char *fat_uni2asc_pg[];
+
+/*
+ * Since Linux can't deal with Unicode in filenames, these provide
+ * a method to encode the Unicode names in a manner that the vfat
+ * filesystem can them decode back to Unicode.  This conversion
+ * only occurs when the filesystem was mounted with the 'uni_xlate' mount
+ * option.
+ */
+extern unsigned char fat_uni2code[];
+extern unsigned char fat_code2uni[];
 
 /*
  * Overrides for Emacs so that we follow Linus's tabbing style.
diff -u --recursive --new-file v1.3.71/linux/fs/msdos/namei.c linux/fs/msdos/namei.c
--- v1.3.71/linux/fs/msdos/namei.c	Fri Feb  9 17:53:06 1996
+++ linux/fs/msdos/namei.c	Tue Mar  5 13:03:27 1996
@@ -202,6 +202,14 @@
 		if (!(*result = iget(dir->i_sb,ino))) return -EACCES;
 		return 0;
 	}
+#if 0
+	if (dcache_lookup(dir, name, len, (unsigned long *) &ino)) {
+		iput(dir);
+		if (!(*result = iget(dir->i_sb, ino)))
+			return -EACCES;
+		return 0;
+	}
+#endif
 	PRINTK (("msdos_lookup 3\n"));
 	if ((res = msdos_find(dir,name,len,&bh,&de,&ino)) < 0) {
 		iput(dir);
@@ -245,7 +253,7 @@
 
 
 /***** Creates a directory entry (name is already formatted). */
-static int msdos_create_entry(struct inode *dir, const char *name,
+static int msdos_create_entry(struct inode *dir, const char *name,int len,
     int is_dir, int is_hid, struct inode **result)
 {
 	struct super_block *sb = dir->i_sb;
@@ -279,6 +287,7 @@
 	(*result)->i_mtime = (*result)->i_atime = (*result)->i_ctime =
 	    CURRENT_TIME;
 	(*result)->i_dirt = 1;
+	dcache_add(dir, name, len, ino);
 	return 0;
 }
 
@@ -316,7 +325,8 @@
 		iput(dir);
 		return is_hid ? -EINVAL : -EEXIST;
  	}
-	res = msdos_create_entry(dir,msdos_name,S_ISDIR(mode),is_hid,result);
+	res = msdos_create_entry(dir,msdos_name,len,S_ISDIR(mode),is_hid,
+				 result);
 	fat_unlock_creation();
 	iput(dir);
 	return res;
@@ -430,7 +440,8 @@
 		iput(dir);
 		return -EEXIST;
  	}
-	if ((res = msdos_create_entry(dir,msdos_name,1,is_hid,&inode)) < 0) {
+	if ((res = msdos_create_entry(dir,msdos_name,len,1,is_hid,
+				      &inode)) < 0) {
 		fat_unlock_creation();
 		iput(dir);
 		return res;
@@ -439,14 +450,14 @@
 	inode->i_nlink = 2; /* no need to mark them dirty */
 	MSDOS_I(inode)->i_busy = 1; /* prevent lookups */
 	if ((res = fat_add_cluster(inode)) < 0) goto mkdir_error;
-	if ((res = msdos_create_entry(inode,MSDOS_DOT,1,0,&dot)) < 0)
+	if ((res = msdos_create_entry(inode,MSDOS_DOT,1,1,0,&dot)) < 0)
 		goto mkdir_error;
 	dot->i_size = inode->i_size; /* doesn't grow in the 2nd create_entry */
 	MSDOS_I(dot)->i_start = MSDOS_I(inode)->i_start;
 	dot->i_nlink = inode->i_nlink;
 	dot->i_dirt = 1;
 	iput(dot);
-	if ((res = msdos_create_entry(inode,MSDOS_DOTDOT,1,0,&dot)) < 0)
+	if ((res = msdos_create_entry(inode,MSDOS_DOTDOT,2,1,0,&dot)) < 0)
 		goto mkdir_error;
 	fat_unlock_creation();
 	dot->i_size = dir->i_size;
@@ -521,8 +532,9 @@
 }
 
 /***** Rename within a directory */
-static int rename_same_dir(struct inode *old_dir,char *old_name,
-    struct inode *new_dir,char *new_name,struct buffer_head *old_bh,
+static int rename_same_dir(struct inode *old_dir,char *old_name,int old_len,
+    struct inode *new_dir,char *new_name,int new_len,
+    struct buffer_head *old_bh,
     struct msdos_dir_entry *old_de,int old_ino,int is_hid)
 {
 	struct super_block *sb = old_dir->i_sb;
@@ -564,6 +576,7 @@
 		new_inode->i_dirt = 1;
 		new_de->name[0] = DELETED_FLAG;
 		mark_buffer_dirty(new_bh, 1);
+		dcache_add(new_dir, new_name, new_len, new_ino);
 		iput(new_inode);
 		brelse(new_bh);
 	}
@@ -585,8 +598,9 @@
 }
 
 /***** Rename across directories - a nonphysical move */
-static int rename_diff_dir(struct inode *old_dir,char *old_name,
-    struct inode *new_dir,char *new_name,struct buffer_head *old_bh,
+static int rename_diff_dir(struct inode *old_dir,char *old_name,int old_len,
+    struct inode *new_dir,char *new_name,int new_len,
+    struct buffer_head *old_bh,
     struct msdos_dir_entry *old_de,int old_ino,int is_hid)
 {
 	struct super_block *sb = old_dir->i_sb;
@@ -686,6 +700,7 @@
 		MSDOS_I(free_inode)->i_old = new_inode;
 		/* free_inode is put when putting new_inode */
 		iput(new_inode);
+		dcache_add(new_dir, new_name, new_len, new_ino);
 		brelse(new_bh);
 	}
 	if (S_ISDIR(old_inode->i_mode)) {
@@ -737,10 +752,10 @@
 	    &old_ino,old_hid?SCAN_HID:SCAN_NOTHID)) < 0) goto rename_done;
 	fat_lock_creation();
 	if (old_dir == new_dir)
-		error = rename_same_dir(old_dir,old_msdos_name,new_dir,
-		    new_msdos_name,old_bh,old_de,old_ino,is_hid);
-	else error = rename_diff_dir(old_dir,old_msdos_name,new_dir,
-		    new_msdos_name,old_bh,old_de,old_ino,is_hid);
+		error = rename_same_dir(old_dir,old_msdos_name,old_len,new_dir,
+		    new_msdos_name,new_len,old_bh,old_de,old_ino,is_hid);
+	else error = rename_diff_dir(old_dir,old_msdos_name,old_len,new_dir,
+		    new_msdos_name,new_len,old_bh,old_de,old_ino,is_hid);
 	fat_unlock_creation();
 	brelse(old_bh);
 rename_done:
diff -u --recursive --new-file v1.3.71/linux/fs/nfs/dir.c linux/fs/nfs/dir.c
--- v1.3.71/linux/fs/nfs/dir.c	Tue Dec 26 04:45:38 1995
+++ linux/fs/nfs/dir.c	Fri Mar  8 15:52:22 1996
@@ -617,6 +617,10 @@
 	inode->i_nlink = fattr->nlink;
 	inode->i_uid = fattr->uid;
 	inode->i_gid = fattr->gid;
+
+	/* Size changed from outside: invalidate caches on next read */
+	if (inode->i_size != fattr->size)
+		NFS_CACHEINV(inode);
 	inode->i_size = fattr->size;
 	inode->i_blksize = fattr->blocksize;
 	if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode))
diff -u --recursive --new-file v1.3.71/linux/fs/nfs/file.c linux/fs/nfs/file.c
--- v1.3.71/linux/fs/nfs/file.c	Wed Feb 28 11:50:10 1996
+++ linux/fs/nfs/file.c	Fri Mar  8 16:28:42 1996
@@ -153,8 +153,9 @@
 static int nfs_file_write(struct inode *inode, struct file *file, const char *buf,
 			  int count)
 {
-	int result, hunk, i, n, pos;
+	int result, written, wsize;
 	struct nfs_fattr fattr;
+	unsigned long pos;
 
 	if (!inode) {
 		printk("nfs_file_write: inode = NULL\n");
@@ -171,24 +172,29 @@
 	pos = file->f_pos;
 	if (file->f_flags & O_APPEND)
 		pos = inode->i_size;
-	n = NFS_SERVER(inode)->wsize;
-	for (i = 0; i < count; i += n) {
-		hunk = count - i;
-		if (hunk >= n)
-			hunk = n;
+	wsize = NFS_SERVER(inode)->wsize;
+	result = 0;
+	written = 0;
+	while (written < count) {
+		int hunk = count - written;
+		if (hunk >= wsize)
+			hunk = wsize;
 		result = nfs_proc_write(inode,
 			pos, hunk, buf, &fattr);
 		if (result < 0)
-			return result;
+			break;
 		pos += hunk;
 		buf += hunk;
-		if (hunk < n) {
-			i += hunk;
+		written += hunk;
+		if (hunk < wsize)
 			break;
-		}
 	}
+	if (!written)
+		return result;
 	file->f_pos = pos;
+	if (pos > inode->i_size)
+		inode->i_size = pos;
 	nfs_refresh_inode(inode, &fattr);
-	return i;
+	return written;
 }
 
diff -u --recursive --new-file v1.3.71/linux/fs/nfs/inode.c linux/fs/nfs/inode.c
--- v1.3.71/linux/fs/nfs/inode.c	Tue Feb 20 14:37:27 1996
+++ linux/fs/nfs/inode.c	Fri Mar  8 15:52:02 1996
@@ -49,12 +49,13 @@
 /*
  * The "read_inode" function doesn't actually do anything:
  * the real data is filled in later in nfs_fhget. Here we
- * just mark the cache times invalid.
+ * just mark the cache times invalid, and zero out i_mode
+ * (the latter makes "nfs_refresh_inode" do the right thing)
  */
 static void nfs_read_inode(struct inode * inode)
 {
-	NFS_READTIME(inode) = jiffies - 1000000;
-	NFS_OLDMTIME(inode) = 0;
+	inode->i_mode = 0;
+	NFS_CACHEINV(inode);
 }
 
 static void nfs_put_inode(struct inode * inode)
diff -u --recursive --new-file v1.3.71/linux/fs/vfat/namei.c linux/fs/vfat/namei.c
--- v1.3.71/linux/fs/vfat/namei.c	Fri Feb 16 11:34:10 1996
+++ linux/fs/vfat/namei.c	Tue Mar  5 13:03:27 1996
@@ -4,7 +4,10 @@
  *  Written 1992,1993 by Werner Almesberger
  *
  *  Windows95/Windows NT compatible extended MSDOS filesystem
- *    by Gordon Chaffee Copyright (C) 1995
+ *    by Gordon Chaffee Copyright (C) 1995.  Send bug reports for the
+ *    VFAT filesystem to <chaffee@plateau.cs.berkeley.edu>.  Specify
+ *    what file operation caused you trouble and if you can duplicate
+ *    the problem, send a script that demonstrates it.
  */
 
 #include <linux/module.h>
@@ -15,6 +18,7 @@
 #include <linux/errno.h>
 #include <linux/string.h>
 #include <linux/stat.h>
+#include <linux/mm.h>
 
 #include <asm/segment.h>
 
@@ -22,14 +26,37 @@
 #include "../fat/tables.h"
 
 #if 0
-#define PRINTK(x) printk x
+# define PRINTK(x) printk x
 #else
-#define PRINTK(x)
+# define PRINTK(x)
 #endif
 
+#ifndef DEBUG
+# define CHECK_STACK
+#else
+# define CHECK_STACK check_stack(__FILE__, __LINE__)
+#endif
 
-void vfat_read_inode(struct inode *inode);
+/*
+ * XXX: It would be better to use the tolower from linux/ctype.h,
+ * but _ctype is needed and it is not exported.
+ */
+#define tolower(c) (((c) >= 'A' && (c) <= 'Z') ? (c)-('A'-'a') : (c))
 
+struct vfat_find_info {
+	const char *name;
+	int len;
+	int new_filename;
+	int found;
+	int is_long;
+	off_t offset;
+	off_t short_offset;
+	int long_slots;
+	ino_t ino;
+	int posix;
+};
+
+void vfat_read_inode(struct inode *inode);
 
 void vfat_put_super(struct super_block *sb)
 {
@@ -49,29 +76,93 @@
 	NULL
 };
 
+static int parse_options(char *options,	char *uni_xlate, char *posix,
+			 char *numtail)
+{
+	char *this_char,*value;
+
+	*uni_xlate = *posix = 0;
+	*numtail = 1;
+
+	if (!options) return 1;
+	for (this_char = strtok(options,","); this_char; this_char = strtok(NULL,",")) {
+		if ((value = strchr(this_char,'=')) != NULL)
+			*value++ = 0;
+		if (!strcmp(this_char,"uni_xlate")) {
+			if (value)
+				return 0;
+			*uni_xlate = 1;
+		}
+		else if (!strcmp(this_char,"posix")) {
+			if (value)
+				return 0;
+			*posix = 1;
+		}
+		else if (!strcmp(this_char,"nonumtail")) {
+			if (value)
+				return 0;
+			*numtail = 0;
+		}
+	}
+	return 1;
+}
+
 struct super_block *vfat_read_super(struct super_block *sb,void *data,
 				    int silent)
 {
 	struct super_block *res;
+	char uni_xlate, posix, numtail;
   
 	MOD_INC_USE_COUNT;
 	
 	sb->s_op = &vfat_sops;
+	if (!parse_options((char *) data,&uni_xlate,&posix,&numtail)) {
+		sb->s_dev = 0;
+		MOD_DEC_USE_COUNT;
+		return NULL;
+	}
+	MSDOS_SB(sb)->unicode_xlate = uni_xlate;
+	MSDOS_SB(sb)->posix = posix;
+	MSDOS_SB(sb)->numtail = numtail;
+
 	res = fat_read_super(sb, data, silent);
 	if (res == NULL) {
-	  MOD_DEC_USE_COUNT;
+		MOD_DEC_USE_COUNT;
 	} else {
-	  MSDOS_SB(sb)->vfat = 1;
-	  MSDOS_SB(sb)->dotsOK = 0;
+		MSDOS_SB(sb)->vfat = 1;
+		MSDOS_SB(sb)->dotsOK = 0;
 	}
 
 	return res;
 }
 
+#ifdef DEBUG
 
+static void
+check_stack(const char *fname, int lineno)
+{
+	int stack_level;
+	char *pg_dir;
 
-#ifdef DEBUG
+	stack_level = (long)(&pg_dir)-current->kernel_stack_page;
+	if (stack_level < 0)
+	        printk("*-*-*-* vfat kstack overflow in %s line %d: SL=%d\n",
+		       fname, lineno, stack_level);
+	else if (stack_level < 500)
+	        printk("*-*-*-* vfat kstack low in %s line %d: SL=%d\n",
+		       fname, lineno, stack_level);
+#if 0
+	else
+		printk("------- vfat kstack ok in %s line %d: SL=%d\n",
+		       fname, lineno, stack_level);
+#endif
+	if (*(unsigned long *) current->kernel_stack_page != STACK_MAGIC) {
+		printk("******* vfat stack corruption detected in %s at line %d\n",
+		       fname, lineno);
+	}
+}
 
+static int debug = 0;
 static void dump_fat(struct super_block *sb,int start)
 {
 	printk("[");
@@ -112,13 +203,8 @@
 
 /* Characters that are undesirable in an MS-DOS file name */
 
-#if 1  /* Steve Searle's characters */
 static char bad_chars[] = "*?<>|\":/\\";
 static char bad_if_strict[] = "+=,; []";
-#else
-static char bad_chars[] = "*?<>|\"";
-static char bad_if_strict[] = "+=,; ";
-#endif
 static char replace_chars[] = "[];,+=";
 
 static int vfat_find(struct inode *dir,const char *name,int len,
@@ -129,7 +215,8 @@
 /* Returns negative number on error, 0 for a normal
  * return, and 1 for . or .. */
 
-static int vfat_valid_longname(const char *name, int len, int dot_dirs)
+static int vfat_valid_longname(const char *name, int len, int dot_dirs,
+			       int xlate)
 {
 	const char **reserved;
 	unsigned char c;
@@ -145,9 +232,12 @@
 	if (len >= 256) return -EINVAL;
 	for (i = 0; i < len; i++) {
 		c = name[i];
-		if (strchr(bad_chars,c)) return -EINVAL;
+		if (xlate && c == ':') continue;
+		if (strchr(bad_chars,c)) {
+			return -EINVAL;
+		}
 	}
-	if (len == 3 || len == 4) {
+ 	if (len == 3 || len == 4) {
 		for (reserved = reserved_names; *reserved; reserved++)
 			if (!strncmp(name,*reserved,8)) return -EINVAL;
 	}
@@ -290,22 +380,18 @@
 {
 	const char *ip, *ext_start, *end;
 	char *p;
-	int valid;
 	int sz, extlen, baselen, totlen;
 	char msdos_name[13];
 	char base[9], ext[4];
 	int i;
 	int res;
-	int ino;
 	int spaces;
-	int count;
 	char buf[8];
 	struct slot_info sinfo;
 	const char *name_start;
 
 	PRINTK(("Entering vfat_create_shortname: name=%s, len=%d\n", name, len));
 	sz = 0;			/* Make compiler happy */
-	valid = 1;
 	if (len && name[len-1]==' ') return -EINVAL;
 	if (len <= 12) {
 		/* Do a case insensitive search if the name would be a valid
@@ -322,7 +408,6 @@
 		if (res > -1) {
 			PRINTK(("vfat_create_shortname 1\n"));
 			res = vfat_find(dir, msdos_name, len, 0, 0, 0, &sinfo);
-			ino = sinfo.ino;
 			PRINTK(("vfat_create_shortname 2\n"));
 			if (res > -1) return -EEXIST;
 			return 0;
@@ -373,6 +458,7 @@
 			} else {
 				*p = *ip;
 			}
+			if (strchr(replace_chars, *p)) *p='_';
 			p++; baselen++;
 		}
 		ip++;
@@ -403,18 +489,21 @@
 	ext[extlen] = '\0';
 	base[baselen] = '\0';
 
-	count = 0;
 	strcpy(msdos_name, base);
 	msdos_name[baselen] = '.';
 	strcpy(&msdos_name[baselen+1], ext);
 
 	totlen = baselen + extlen + 1;
 	res = 0;
+	if (MSDOS_SB(dir->i_sb)->numtail == 0) {
+		res = vfat_find(dir, msdos_name, totlen, 0, 0, 0, &sinfo);
+	}
+	i = 0;
 	while (res > -1) {
 		/* Create the next shortname to try */
-		count++;
-		if (count == 10000000) return -EEXIST;
-		sprintf(buf, "%d", count);
+		i++;
+		if (i == 10000000) return -EEXIST;
+		sprintf(buf, "%d", i);
 		sz = strlen(buf);
 		if (sz + 1 > spaces) {
 			baselen = baselen - (sz + 1 - spaces);
@@ -427,7 +516,7 @@
 		msdos_name[baselen+sz+1] = '.';
 		strcpy(&msdos_name[baselen+sz+2], ext);
 
-		totlen = baselen + sz + 2 + extlen;
+		totlen = baselen + sz + 1 + extlen + (extlen > 0);
 		res = vfat_find(dir, msdos_name, totlen, 0, 0, 0, &sinfo);
 	}
 	res = vfat_format_name('x', msdos_name, totlen, name_res, 1);
@@ -483,39 +572,146 @@
 		if ((res = fat_add_cluster(dir)) < 0) return res;
 		ino = fat_get_entry(dir,&curr,&bh,&de);
 	}
-	/* Should never get here, but if it does */
-	printk("vfat_find_free_slots: Unable to find any\n");
 	return -ENOSPC;
 }
-		
-static int vfat_build_slots(struct inode *dir,const char *name,int len,
-     int find_long, int new_filename,
-     struct msdos_dir_slot *ds, struct msdos_dir_slot *ds_mask,
-     int *slots, int *is_long)
+
+/* Translate a string, including coded sequences into Unicode */
+static int
+xlate_to_uni(const char *name, int len, char *outname, int *outlen, int escape)
 {
-	struct msdos_dir_slot *ps, *ps_mask;
-	struct msdos_dir_entry *de, *de_mask;
-	char msdos_name[MSDOS_NAME];
+	int i;
+	const unsigned char *ip;
+	char *op;
+	int fill;
+	unsigned char c1, c2, c3;
+
+	op = outname;
+	for (i = 0, ip = name, op = outname, *outlen = 0;
+	     i < len && *outlen <= 260; i++, *outlen += 1)
+	{
+		if (escape && (i < len - 4) && 
+		    (*ip == ':') &&
+		    ((c1 = fat_code2uni[ip[1]]) != 255) &&
+		    ((c2 = fat_code2uni[ip[2]]) != 255) &&
+		    ((c3 = fat_code2uni[ip[3]]) != 255)) {
+			*op++ = (c1 << 4) + (c2 >> 2);
+			*op++ = ((c2 & 0x3) << 6) + c3;
+			ip += 4;
+		} else {
+			*op++ = fat_a2uni[*ip].uni1;
+			*op++ = fat_a2uni[*ip].uni2;
+			ip++;
+		}
+	}
+	if (*outlen > 260)
+		return -ENAMETOOLONG;
+
+	if (*outlen % 13) {
+		*op++ = 0;
+		*op++ = 0;
+		*outlen += 1;
+		if (*outlen % 13) {
+			fill = 13 - (*outlen % 13);
+			for (i = 0; i < fill; i++) {
+				*op++ = 0xff;
+				*op++ = 0xff;
+			}
+			*outlen += fill;
+		}
+	}
+
+	return 0;
+}
+
+static int
+vfat_fill_long_slots(struct msdos_dir_slot *ds, const char *name, int len,
+		     char *msdos_name, int *slots, int uni_xlate)
+{
+	struct msdos_dir_slot *ps;
+	struct msdos_dir_entry *de;
 	int res;
 	int slot;
+	unsigned char cksum;
+	char *uniname;
+	const char *ip;
+	unsigned long page;
+	int unilen;
 	int i;
-	const unsigned char *ip;
 	loff_t offset;
-	unsigned char alias_checksum;
+
+	if(!(page = __get_free_page(GFP_KERNEL)))
+		return -ENOMEM;
+	uniname = (char *) page;
+	res = xlate_to_uni(name, len, uniname, &unilen, uni_xlate);
+	if (res < 0) {
+		free_page(page);
+		return res;
+	}
+
+	*slots = unilen / 13;
+	for (cksum = i = 0; i < 11; i++) {
+		cksum = (((cksum&1)<<7)|((cksum&0xfe)>>1)) + msdos_name[i];
+	}
+	PRINTK(("vfat_fill_long_slots 3: slots=%d\n",*slots));
+
+	for (ps = ds, slot = *slots; slot > 0; slot--, ps++) {
+		int end, j;
+
+		PRINTK(("vfat_fill_long_slots 4\n"));
+		ps->id = slot;
+		ps->attr = ATTR_EXT;
+		ps->reserved = 0;
+		ps->alias_checksum = cksum;
+		ps->start[0] = 0;
+		ps->start[1] = 0;
+		PRINTK(("vfat_fill_long_slots 5: uniname=%s\n",uniname));
+		offset = (slot - 1) * 26;
+		ip = &uniname[offset];
+		j = offset;
+		end = 0;
+		for (i = 0; i < 10; i += 2) {
+			ps->name0_4[i] = *ip++;
+			ps->name0_4[i+1] = *ip++;
+		}
+		PRINTK(("vfat_fill_long_slots 6\n"));
+		for (i = 0; i < 12; i += 2) {
+			ps->name5_10[i] = *ip++;
+			ps->name5_10[i+1] = *ip++;
+		}
+		PRINTK(("vfat_fill_long_slots 7\n"));
+		for (i = 0; i < 4; i += 2) {
+			ps->name11_12[i] = *ip++;
+			ps->name11_12[i+1] = *ip++;
+		}
+	}
+	PRINTK(("vfat_fill_long_slots 8\n"));
+	ds[0].id |= 0x40;
+
+	de = (struct msdos_dir_entry *) ps;
+	PRINTK(("vfat_fill_long_slots 9\n"));
+	strncpy(de->name, msdos_name, MSDOS_NAME);
+
+	free_page(page);
+	return 0;
+}
+		
+static int vfat_build_slots(struct inode *dir,const char *name,int len,
+     struct msdos_dir_slot *ds, int *slots, int *is_long)
+{
+	struct msdos_dir_entry *de;
+	char msdos_name[MSDOS_NAME];
+	int res, xlate;
 
 	PRINTK(("Entering vfat_build_slots: name=%s, len=%d\n", name, len));
 	de = (struct msdos_dir_entry *) ds;
-	de_mask = (struct msdos_dir_entry *) ds_mask;
+	xlate = MSDOS_SB(dir->i_sb)->unicode_xlate;
 
 	*slots = 1;
 	*is_long = 0;
-	memset(ds_mask, 0, sizeof(struct msdos_dir_slot) * MSDOS_SLOTS);
 	if (len == 1 && name[0] == '.') {
 		strncpy(de->name, MSDOS_DOT, MSDOS_NAME);
-		memset(de_mask, 0xff, MSDOS_NAME);
 	} else if (len == 2 && name[0] == '.' && name[1] == '.') {
 		strncpy(de->name, MSDOS_DOT, MSDOS_NAME);
-		memset(de_mask, 0xff, MSDOS_NAME);
 	} else {
 		PRINTK(("vfat_build_slots 4\n"));
 		res = vfat_valid_shortname('x', name, len, 1);
@@ -523,327 +719,120 @@
 			PRINTK(("vfat_build_slots 5a\n"));
 			res = vfat_format_name('x', name, len, de->name, 1);
 			PRINTK(("vfat_build_slots 5b\n"));
-			memset(de_mask->name, 0xff, MSDOS_NAME);
-			PRINTK(("vfat_build_slots 5c\n"));
 		} else {
-			PRINTK(("vfat_build_slots 5A: %s (len=%d) is an invalid shortname\n", name, len));
-			if (new_filename) {
-				unsigned char sum;
-
-				PRINTK(("vfat_build_slots 5Z\n"));
-				res = vfat_create_shortname(dir, name, len, msdos_name);
-				PRINTK(("vfat_build_slots 5Y\n"));
-				if (res < 0) {
-					return res;
-				}
-
-				for (sum = i = 0; i < 11; i++) {
-					sum = (((sum&1)<<7)|((sum&0xfe)>>1)) + msdos_name[i];
-				}
-				PRINTK(("vfat_build_slots 5X: msdos_name=%s\n", msdos_name));
-				alias_checksum = sum;
-			} else {
-				alias_checksum = 0;
+			res = vfat_create_shortname(dir, name, len, msdos_name);
+			if (res < 0) {
+				return res;
 			}
 
-			if (!find_long) return -EINVAL;
-			res = vfat_valid_longname(name, len, 1);
-			if (res < 0) return res;
+			res = vfat_valid_longname(name, len, 1, xlate);
+			if (res < 0) {
+				return res;
+			}
 
 			*is_long = 1;
-			*slots = (len + 12) / 13;
-			PRINTK(("vfat_build_slots 6: slots=%d\n",*slots));
-
-			for (ps = ds, slot = *slots, ps_mask = ds_mask;
-			     slot > 0; slot--, ps++, ps_mask++)
-			{
-				int end, j;
-
-				PRINTK(("vfat_build_slots 6a\n"));
-				ps->id = slot; ps_mask->id = 0xff;
-				ps->attr = ATTR_EXT; ps_mask->attr = 0xff;
-				ps->reserved = 0; ps_mask->reserved = 0xff;
-				ps->alias_checksum = alias_checksum;
-				ps_mask->alias_checksum = 0;
-				ps->start[0] = 0; ps_mask->start[0] = 0xff;
-				ps->start[1] = 0; ps_mask->start[1] = 0xff;
-				PRINTK(("vfat_build_slots 6b: name=%s\n",name));
-				offset = (slot - 1) * 13;
-				ip = &name[offset];
-				j = offset;
-				end = 0;
-				for (i = 0; i < 10; i += 2) {
-					if (!end && j == len) {
-						end = 1;
-						ps->name0_4[i] = 0;
-						ps_mask->name0_4[i] = 0xff;
-						ps->name0_4[i+1] = 0;
-						ps_mask->name0_4[i] = 0xff;
-						continue;
-					} else if (end) {
-						ps->name0_4[i] = 0xff;
-						ps_mask->name0_4[i] = 0xff;
-						ps->name0_4[i+1] = 0xff;
-						ps_mask->name0_4[i+1] = 0xff;
-						continue;
-					}
-					ps->name0_4[i] = fat_a2uni[*ip].uni1;
-					ps->name0_4[i+1] = fat_a2uni[*ip].uni2;
-					if ((*ip >= 'a' && *ip <= 'z') ||
-					    (*ip >= 'A' && *ip <= 'Z')) {
-						ps_mask->name0_4[i] = 0xdf;
-					} else {
-						ps_mask->name0_4[i] = 0xff;
-					}
-					ps_mask->name0_4[i+1] = 0xff;
-					j++; ip++;
-				}
-				PRINTK(("vfat_build_slots 6c\n"));
-				for (i = 0; i < 12; i += 2) {
-					if (!end && j == len) {
-						end = 1;
-						ps->name5_10[i] = 0;
-						ps->name5_10[i+1] = 0;
-						continue;
-					} else if (end) {
-						ps->name5_10[i] = 0xff;
-						ps_mask->name5_10[i] = 0xff;
-						ps->name5_10[i+1] = 0xff;
-						ps_mask->name5_10[i+1] = 0xff;
-						continue;
-					}
-					ps->name5_10[i] = fat_a2uni[*ip].uni1;
-					ps->name5_10[i+1] = fat_a2uni[*ip].uni2;
-					if ((*ip >= 'a' && *ip <= 'z') ||
-					    (*ip >= 'A' && *ip <= 'Z')) {
-						ps_mask->name5_10[i] = 0xdf;
-					} else {
-						ps_mask->name5_10[i] = 0xff;
-					}
-					ps_mask->name5_10[i+1] = 0xff;
-					j++; ip++;
-				}
-				PRINTK(("vfat_build_slots 6d\n"));
-				for (i = 0; i < 4; i += 2) {
-					if (!end && j == len) {
-						end = 1;
-						ps->name11_12[i] = 0;
-						ps->name11_12[i+1] = 0;
-						continue;
-					} else if (end) {
-						ps->name11_12[i] = 0xff;
-						ps_mask->name11_12[i] = 0xff;
-						ps->name11_12[i+1] = 0xff;
-						ps_mask->name11_12[i+1] = 0xff;
-						continue;
-					}
-					ps->name11_12[i] = fat_a2uni[*ip].uni1;
-					ps->name11_12[i+1] = fat_a2uni[*ip].uni2;
-					if ((*ip >= 'a' && *ip <= 'z') ||
-					    (*ip >= 'A' && *ip <= 'Z')) {
-						ps_mask->name11_12[i] = 0xdf;
-					} else {
-						ps_mask->name11_12[i] = 0xff;
-					}
-					ps_mask->name11_12[i+1] = 0xff;
-					j++; ip++;
-				}
-			}
-			PRINTK(("vfat_build_slots 6e\n"));
-			ds[0].id |= 0x40;
 
-			if (new_filename) {
-				de = (struct msdos_dir_entry *) ps;
-				de_mask = (struct msdos_dir_entry *) ps_mask;
-
-				PRINTK(("vfat_build_slots 10\n"));
-				strncpy(de->name, msdos_name, MSDOS_NAME);
-				memset(de_mask->name, 0xff, MSDOS_NAME);
-			}
+			return vfat_fill_long_slots(ds, name, len, msdos_name,
+						    slots, xlate);
 		}
 	}
 	return 0;
 }
 
-/* Given a shortname offset, see if there is an associated longname.  Returns
- * the number of slots in the longname if one is found, else 0 */
-static int vfat_get_longname(struct inode *dir,loff_t short_offset,
-     unsigned char checksum, loff_t *pos_out)
+static int vfat_readdir_cb(
+	filldir_t filldir,
+	void * buf,
+	const char * name,
+	int name_len,
+	int is_long,
+	off_t offset,
+	off_t short_offset,
+	int long_slots,
+	ino_t ino)
 {
-	struct super_block *sb = dir->i_sb;
-	struct msdos_dir_slot *ps;
-	struct msdos_dir_entry *de;
-	struct buffer_head *bh;
-	loff_t offset, temp;
-	int id, res, slots;
+	struct vfat_find_info *vf = (struct vfat_find_info *) buf;
+	const char *s1, *s2;
+	int i;
+
+#ifdef DEBUG
+	if (debug) printk("cb: vf.name=%s, len=%d, name=%s, name_len=%d\n",
+			  vf->name, vf->len, name, name_len);
+#endif
 
-	/* printk("Short offset: %d\n", short_offset); */
-	if (short_offset == 0) {
+	if (vf->len != name_len) {
 		return 0;
 	}
 
-	slots = 0;
-	id = 1;
-	bh = NULL;
-	offset = short_offset - sizeof(struct msdos_dir_slot);
-	while (offset > 0) {
-		temp = offset;
-		res = fat_get_entry(dir,&temp,&bh,&de);
-		if (res < 0) goto finish;
-		ps = (struct msdos_dir_slot *) de;
-		if (ps->alias_checksum != checksum) goto finish;
-		if ((ps->id &~ 0x40) != id) goto finish;
-		if (IS_FREE(de->name)) goto finish;
-		if (ps->id & 0x40) {
-			*pos_out = offset;
-			slots = ps->id &~ 0x40;
-			/* printk("Found a longname for the shortname: long_offset=%ld\n", offset); */
-			goto finish;
-		}
-		offset -= sizeof(struct msdos_dir_slot);
-		id++;
-	}
- finish:
-	if (bh) brelse(bh);
-	return slots;
+	s1 = name; s2 = vf->name;
+	for (i = 0; i < name_len; i++) {
+		if (vf->new_filename && !vf->posix) {
+			if (tolower(*s1) != tolower(*s2))
+				return 0;
+		} else {
+			if (*s1 != *s2)
+				return 0;
+		}
+		s1++; s2++;
+	}
+	vf->found = 1;
+	vf->is_long = is_long;
+	vf->offset = (offset == 2) ? 0 : offset;
+	vf->short_offset = (short_offset == 2) ? 0 : short_offset;
+	vf->long_slots = long_slots;
+	vf->ino = ino;
+	return -1;
 }
 
 static int vfat_find(struct inode *dir,const char *name,int len,
     int find_long, int new_filename,int is_dir,struct slot_info *sinfo_out)
 {
 	struct super_block *sb = dir->i_sb;
-	const char *ip;
-	char *op, *op_mask;
-	int res;
-	struct msdos_dir_slot ds[MSDOS_SLOTS], ds_mask[MSDOS_SLOTS];
-	struct msdos_dir_slot *ps;
-	struct msdos_dir_entry *de, *de_mask;
+	struct vfat_find_info vf;
+	struct file fil;
 	struct buffer_head *bh;
-	int i, slot, slots;
-	loff_t offset, start;
-	int match;
+	struct msdos_dir_entry *de;
+	struct msdos_dir_slot *ps;
+	loff_t offset;
+	struct msdos_dir_slot ds[MSDOS_SLOTS];
 	int is_long;
-	unsigned char alias_checksum = 0;
+	int slots, slot;
+	int res;
 
-	PRINTK(("vfat_find 1: name=%s, len=%d\n",name,len));
+	PRINTK(("Entering vfat_find\n"));
+	fil.f_pos = 0;
+	vf.name = name;
+	vf.len = len;
+	vf.new_filename = new_filename;
+	vf.found = 0;
+	vf.posix = MSDOS_SB(sb)->posix;
+	res = fat_readdirx(dir,&fil,(void *)&vf,vfat_readdir_cb,NULL,1,find_long,0);
+	PRINTK(("vfat_find: Debug 1\n"));
+	if (res < 0) return res;
+	if (vf.found) {
+		if (new_filename) {
+			return -EEXIST;
+		}
+		sinfo_out->longname_offset = vf.offset;
+		sinfo_out->shortname_offset = vf.short_offset;
+		sinfo_out->is_long = vf.is_long;
+		sinfo_out->long_slots = vf.long_slots;
+		sinfo_out->total_slots = vf.long_slots + 1;
+		sinfo_out->ino = vf.ino;
 
-	res = vfat_build_slots(dir, name, len, find_long, new_filename,
-				ds, ds_mask, &slots, &is_long);
+		PRINTK(("vfat_find: Debug 2\n"));
+		return 0;
+	}
+
+	PRINTK(("vfat_find: Debug 3\n"));
+	if (!vf.found && !new_filename)
+		return -ENOENT;
+	
+	res = vfat_build_slots(dir, name, len, ds, &slots, &is_long);
 	if (res < 0) return res;
 
 	de = (struct msdos_dir_entry *) ds;
-	de_mask = (struct msdos_dir_entry *) ds_mask;
 
-	PRINTK(("vfat_find 7\n"));
-	offset = start = 0;
 	bh = NULL;
-	sinfo_out->ino = fat_get_entry(dir,&offset,&bh,&de);
-	while (sinfo_out->ino > -1 && slots > 0) {
-		match = 1;
-
-		ps = (struct msdos_dir_slot *) de;
-		alias_checksum = ps->alias_checksum;
-
-		for (slot = 0; slot < slots; slot++) {
-			ip = (char *) de;
-			ps = (struct msdos_dir_slot *) de;
-			if (is_long && ps->alias_checksum != alias_checksum) {
-				printk("Checksums don't match 1\n");
-				match = 0;
-				start = offset;
-				break;
-			}
-
-			for (i = 0, ip = (char *) de, op = (char *) &ds[slot], op_mask = (char *) &ds_mask[slot];
-			     i < sizeof(struct msdos_dir_entry);
-			     i++, ip++, op++, op_mask++)
-			{
-#if 0
-				if (is_long && de->attr == ATTR_EXT)
-					printk("%02x?%02x ",
-					       (unsigned char) *ip,
-					       (unsigned char) *op);
-#endif
-				if ((*ip & *op_mask) != (*op & *op_mask)) {
-					start = offset;
-					match = 0;
-					break;
-				}
-			}
-#if 0
-			if (is_long && de->attr == ATTR_EXT) printk("\n");
-#endif
-			if ((!is_long && !match) ||
-			    (de->attr == ATTR_VOLUME) ||
-			    (is_long && (match || slot == 0)))
-			{
-				sinfo_out->ino = fat_get_entry(dir,&offset,&bh,&de);
-				/* if (ino >=0 && de->attr == ATTR_EXT) dump_de(de); */
-			}
-			if (!match) {
-				break;
-			}
-			if (sinfo_out->ino == -1) {
-				match = 0;
-				goto breakout;
-			}
-		}
-		if (match) {
-			unsigned char sum;
-
-			for (sum = i = 0; i < 11; i++) {
-				sum = (((sum&1)<<7)|((sum&0xfe)>>1)) + de->name[i];
-			}
-
-			if (is_long) {
-				if (sum != alias_checksum) {
-					PRINTK(("Checksums don't match %d != %d\n", sum, alias_checksum));
-					match = 0;
-				}
-			} else {
-				int long_slots;
-				long_slots = vfat_get_longname(dir, offset - sizeof(struct msdos_dir_entry), sum, &start);
-				if (long_slots > 0) {
-					slots = long_slots;
-					is_long = 1;
-				}
-			}
-
-				
-			if (match) {
-				PRINTK(("name: %s, alias: %c%c%c%c%c%c%c%c%c%c%c\n",
-					name,
-					de->name[0], de->name[1], de->name[2],
-					de->name[3], de->name[4], de->name[5],
-					de->name[6], de->name[7], de->name[8],
-					de->name[9], de->name[10]));
-				PRINTK(("vfat_find 10\n"));
-				res = CF_LE_W(de->start);
-
-				sinfo_out->shortname_offset = offset - sizeof(struct msdos_dir_slot);
-				sinfo_out->longname_offset = start;
-				sinfo_out->is_long = is_long;
-				if (is_long) {
-					sinfo_out->long_slots = slots;
-					slots++;
-				} else {
-					sinfo_out->long_slots = 0;
-				}
-
-				sinfo_out->total_slots = slots;
-				if (new_filename) {
-					if (bh) brelse(bh);
-					return -EEXIST;
-				}
-				if (bh) brelse(bh);
-				return res;
-			}
-		}
-	}
- breakout:
-	PRINTK(("breakout\n"));
-
-	if (bh) brelse(bh);
 	if (new_filename) {
 		PRINTK(("vfat_find: create file 1\n"));
 		if (is_long) slots++;
@@ -928,6 +917,12 @@
 		if (!(*result = iget(dir->i_sb,ino))) return -EACCES;
 		return 0;
 	}
+	if (dcache_lookup(dir, name, len, (unsigned long *) &ino) && ino) {
+		iput(dir);
+		if (!(*result = iget(dir->i_sb, ino)))
+			return -EACCES;
+		return 0;
+	}
 	PRINTK (("vfat_lookup 3\n"));
 	if ((res = vfat_find(dir,name,len,1,0,0,&sinfo)) < 0) {
 		iput(dir);
@@ -1000,6 +995,9 @@
 	(*result)->i_mtime = (*result)->i_atime = (*result)->i_ctime =
 	    CURRENT_TIME;
 	(*result)->i_dirt = 1;
+	(*result)->i_version = ++event;
+	dir->i_version = event;
+	dcache_add(dir, name, len, ino);
 
 	return 0;
 }
@@ -1012,12 +1010,8 @@
 	if (!dir) return -ENOENT;
 
 	fat_lock_creation();
-	if ((res = vfat_create_entry(dir,name,len,0,result)) < 0) {
-		printk("vfat_create: unable to get new entry\n");
-		fat_unlock_creation();
-		iput(dir);
-		return res;
-	}
+	res = vfat_create_entry(dir,name,len,0,result);
+	if (res < 0) PRINTK(("vfat_create: unable to get new entry\n"));
 
 	fat_unlock_creation();
 	iput(dir);
@@ -1121,7 +1115,7 @@
 	if (MSDOS_I(dir)->i_start) { /* may be zero in mkdir */
 		pos = 0;
 		bh = NULL;
-		while (fat_get_entry(dir,&pos,&bh,&de) > -1)
+		while (fat_get_entry(dir,&pos,&bh,&de) > -1) {
 			/* Skip extended filename entries */
 			if (de->attr == ATTR_EXT) continue;
 
@@ -1131,6 +1125,7 @@
 				brelse(bh);
 				return -ENOTEMPTY;
 			}
+		}
 		if (bh)
 			brelse(bh);
 	}
@@ -1177,17 +1172,14 @@
 	struct super_block *sb = dir->i_sb;
 	struct inode *inode;
 	if (!(inode = iget(dir->i_sb,ino))) return -ENOENT;
-	if (!S_ISREG(inode->i_mode) && nospc) {
-		iput(inode);
-		return -EPERM;
-	}
-	if (IS_IMMUTABLE(inode)){
+	if ((!S_ISREG(inode->i_mode) && nospc) || IS_IMMUTABLE(inode)) {
 		iput(inode);
 		return -EPERM;
 	}
 	inode->i_nlink = 0;
 	inode->i_mtime = dir->i_mtime = CURRENT_TIME;
 	inode->i_atime = dir->i_atime = CURRENT_TIME;
+	dir->i_version = ++event;
 	MSDOS_I(inode)->i_busy = 1;
 	inode->i_dirt = dir->i_dirt = 1;
 	de->name[0] = DELETED_FLAG;
@@ -1254,6 +1246,7 @@
 	} else {
 		printk("Problem in vfat_rmdirx\n");
 	}
+	dir->i_version = ++event;
 
 rmdir_done:
 	brelse(bh);
@@ -1385,9 +1378,18 @@
 		while (walk->i_ino != MSDOS_ROOT_INO) {
 			ino = fat_parent_ino(walk,1);
 			iput(walk);
-			if (ino < 0) return ino;
-			if (ino == old_ino) return -EINVAL;
-			if (!(walk = iget(new_dir->i_sb,ino))) return -EIO;
+			if (ino < 0) {
+				res = ino;
+				goto rename_done;
+			}
+			if (ino == old_ino) {
+				res = -EINVAL;
+				goto rename_done;
+			}
+			if (!(walk = iget(new_dir->i_sb,ino))) {
+				res = -EIO;
+				goto rename_done;
+			}
 		}
 		iput(walk);
 	}
@@ -1458,6 +1460,7 @@
 	fat_cache_inval_inode(old_inode);
 	PRINTK(("vfat_rename 15: old_slots=%d\n",old_slots));
 	old_inode->i_dirt = 1;
+	old_dir->i_version = ++event;
 
 	/* remove the old entry */
 	for (i = old_slots; i > 0; --i) {
@@ -1473,7 +1476,9 @@
 	PRINTK(("vfat_rename 15b\n"));
 
 	mark_buffer_dirty(new_bh, 1);
+	dcache_add(new_dir, new_name, new_len, new_ino);
 	iput(new_inode);
+
 	/* XXX: There is some code in the original MSDOS rename that
 	 * is not duplicated here and it might cause a problem in
 	 * certain circumstances.
diff -u --recursive --new-file v1.3.71/linux/include/asm-i386/irq.h linux/include/asm-i386/irq.h
--- v1.3.71/linux/include/asm-i386/irq.h	Wed Feb  7 15:11:37 1996
+++ linux/include/asm-i386/irq.h	Wed Mar  6 13:59:34 1996
@@ -14,6 +14,8 @@
 
 #define NR_IRQS 16
 
+#define TIMER_IRQ 0
+
 extern void disable_irq(unsigned int);
 extern void enable_irq(unsigned int);
 
@@ -237,6 +239,32 @@
 	LEAVE_KERNEL \
 	RESTORE_MOST);
 	
+ 
+#define BUILD_TIMER_IRQ(chip,nr,mask) \
+asmlinkage void IRQ_NAME(nr); \
+asmlinkage void FAST_IRQ_NAME(nr); \
+asmlinkage void BAD_IRQ_NAME(nr); \
+__asm__( \
+"\n"__ALIGN_STR"\n" \
+SYMBOL_NAME_STR(fast_IRQ) #nr "_interrupt:\n\t" \
+SYMBOL_NAME_STR(bad_IRQ) #nr "_interrupt:\n\t" \
+SYMBOL_NAME_STR(IRQ) #nr "_interrupt:\n\t" \
+	"pushl $-"#nr"-2\n\t" \
+	SAVE_ALL \
+	ENTER_KERNEL \
+	ACK_##chip(mask) \
+	"incl "SYMBOL_NAME_STR(intr_count)"\n\t"\
+	"movl %esp,%ebx\n\t" \
+	"pushl %ebx\n\t" \
+	"pushl $" #nr "\n\t" \
+	"call "SYMBOL_NAME_STR(do_IRQ)"\n\t" \
+	"addl $8,%esp\n\t" \
+	"cli\n\t" \
+	UNBLK_##chip(mask) \
+	"decl "SYMBOL_NAME_STR(intr_count)"\n\t" \
+	"incl "SYMBOL_NAME_STR(syscall_count)"\n\t" \
+	"jmp ret_from_sys_call\n");
+
 	
 /*
  *	Message pass must be a fast IRQ..
@@ -341,6 +369,29 @@
 	SAVE_MOST \
 	ACK_##chip(mask) \
 	RESTORE_MOST);
+	
+#define BUILD_TIMER_IRQ(chip,nr,mask) \
+asmlinkage void IRQ_NAME(nr); \
+asmlinkage void FAST_IRQ_NAME(nr); \
+asmlinkage void BAD_IRQ_NAME(nr); \
+__asm__( \
+"\n"__ALIGN_STR"\n" \
+SYMBOL_NAME_STR(fast_IRQ) #nr "_interrupt:\n\t" \
+SYMBOL_NAME_STR(bad_IRQ) #nr "_interrupt:\n\t" \
+SYMBOL_NAME_STR(IRQ) #nr "_interrupt:\n\t" \
+	"pushl $-"#nr"-2\n\t" \
+	SAVE_ALL \
+	ACK_##chip(mask) \
+	"incl "SYMBOL_NAME_STR(intr_count)"\n\t"\
+	"movl %esp,%ebx\n\t" \
+	"pushl %ebx\n\t" \
+	"pushl $" #nr "\n\t" \
+	"call "SYMBOL_NAME_STR(do_IRQ)"\n\t" \
+	"addl $8,%esp\n\t" \
+	"cli\n\t" \
+	UNBLK_##chip(mask) \
+	"decl "SYMBOL_NAME_STR(intr_count)"\n\t" \
+	"jmp ret_from_sys_call\n");
 
 #endif
 #endif
diff -u --recursive --new-file v1.3.71/linux/include/linux/binfmts.h linux/include/linux/binfmts.h
--- v1.3.71/linux/include/linux/binfmts.h	Tue Mar  5 10:11:12 1996
+++ linux/include/linux/binfmts.h	Tue Mar  5 09:45:40 1996
@@ -40,7 +40,6 @@
 
 extern int register_binfmt(struct linux_binfmt *);
 extern int unregister_binfmt(struct linux_binfmt *);
-extern struct linux_binfmt * get_binfmt_list(void);
 
 extern int read_exec(struct inode *inode, unsigned long offset,
 	char * addr, unsigned long count, int to_kmem);
diff -u --recursive --new-file v1.3.71/linux/include/linux/ftape.h linux/include/linux/ftape.h
--- v1.3.71/linux/include/linux/ftape.h	Thu Jan  1 02:00:00 1970
+++ linux/include/linux/ftape.h	Fri Mar  8 15:56:32 1996
@@ -0,0 +1,70 @@
+#ifndef _FTAPE_H
+#define _FTAPE_H
+
+/*
+ * Copyright (C) 1994-1995 Bas Laarhoven.
+
+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.
+
+ *
+ $Source: /home/bas/distr/ftape-2.03b/RCS/ftape.h,v $
+ $Author: bas $
+ *
+ $Revision: 1.18 $
+ $Date: 1995/05/06 16:11:53 $
+ $State: Beta $
+ *
+ *      This file contains global definitions, typedefs and macro's
+ *      for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include <linux/sched.h>
+#include <linux/mm.h>
+
+#define SECTOR(x)       (x+1)         /* sector offset into real sector */
+#define SECTOR_SIZE     (1024)
+#define SECTORS_PER_SEGMENT (32)
+#define BUFF_SIZE       (SECTORS_PER_SEGMENT * SECTOR_SIZE)
+#define FTAPE_UNIT      (ftape_unit & 3)
+#define RQM_DELAY       (12)
+#define MILLISECOND     (1)
+#define SECOND          (1000)
+#define FOREVER         (-1)
+#ifndef HZ
+# error "HZ undefined."
+#endif
+#define MSPT            (SECOND / HZ) /* milliseconds per tick */
+
+/* This defines the number of retries that the driver will allow
+ * before giving up (and letting a higher level handle the error).
+ */
+#ifdef TESTING
+# define SOFT_RETRIES 1          /* number of low level retries */
+# define RETRIES_ON_ECC_ERROR 3  /* ecc error when correcting segment */
+#else
+# define SOFT_RETRIES 6          /* number of low level retries (triple) */
+# define RETRIES_ON_ECC_ERROR 3  /* ecc error when correcting segment */
+#endif
+/*      some useful macro's
+ */
+#define ABS(a)          ((a) < 0 ? -(a) : (a))
+#define NR_ITEMS(x)     (sizeof(x)/ sizeof(*x))
+
+typedef unsigned char byte;
+
+extern int ftape_init(void);
+
+#endif
+
diff -u --recursive --new-file v1.3.71/linux/include/linux/hdreg.h linux/include/linux/hdreg.h
--- v1.3.71/linux/include/linux/hdreg.h	Fri Feb  9 17:53:08 1996
+++ linux/include/linux/hdreg.h	Fri Mar  8 11:17:24 1996
@@ -164,6 +164,8 @@
 #endif	/* CONFIG_BLK_DEV_HD */
 #ifdef CONFIG_BLK_DEV_IDE
 void ide_setup(char *);
+int ide_register(int io_port, int ctl_port, int irq);
+void ide_unregister(int h);
 #endif	/* CONFIG_BLK_DEV_IDE */
 
 #endif  /* __KERNEL__ */
diff -u --recursive --new-file v1.3.71/linux/include/linux/if_ppp.h linux/include/linux/if_ppp.h
--- v1.3.71/linux/include/linux/if_ppp.h	Fri Nov 17 08:42:28 1995
+++ linux/include/linux/if_ppp.h	Tue Mar  5 10:01:27 1996
@@ -21,10 +21,10 @@
  */
 
 /*
- *  ==FILEVERSION 5==
+ *  ==FILEVERSION 960109==
  *
  *  NOTE TO MAINTAINERS:
- *     If you modify this file at all, increment the number above.
+ *     If you modify this file at all, please set the above date.
  *     if_ppp.h is shipped with a PPP distribution as well as with the kernel;
  *     if everyone increases the FILEVERSION number above, then scripts
  *     can do the right thing when deciding whether to install a new if_ppp.h
@@ -98,8 +98,8 @@
 
 /* Structure describing a CCP configuration option, for PPPIOCSCOMPRESS */
 struct ppp_option_data {
-	u_char	*ptr;
-	u_int	length;
+	__u8	*ptr;
+	__u32	length;
 	int	transmit;
 };
 
diff -u --recursive --new-file v1.3.71/linux/include/linux/if_pppvar.h linux/include/linux/if_pppvar.h
--- v1.3.71/linux/include/linux/if_pppvar.h	Tue Oct 10 18:46:38 1995
+++ linux/include/linux/if_pppvar.h	Tue Mar  5 10:01:27 1996
@@ -42,10 +42,10 @@
  */
 
 /*
- *  ==FILEVERSION 3==
+ *  ==FILEVERSION 960302==
  *
  *  NOTE TO MAINTAINERS:
- *     If you modify this file at all, increment the number above.
+ *     If you modify this file at all, please set the above date.
  *     if_pppvar.h is shipped with a PPP distribution as well as with the kernel;
  *     if everyone increases the FILEVERSION number above, then scripts
  *     can do the right thing when deciding whether to install a new if_pppvar.h
@@ -65,62 +65,62 @@
  * Buffers for the PPP process have the following structure
  */
 
-#define RBUFSIZE  2048           /* MUST be a power of 2 and be <= 4095 */
+#define RBUFSIZE  2048		 /* MUST be a power of 2 and be <= 4095 */
 
 struct ppp_buffer {
-  int			size;		/* Size of the buffer area	*/
-  int			count;		/* Count of characters in bufr	*/
-  int			head;		/* index to head of list	*/
-  int			tail;		/* index to tail of list	*/
-  unsigned long		locked;		/* Buffer is being sent		*/
-  int			type;		/* Type of the buffer		*/
+	__s32		size;		/* Size of the buffer area	*/
+        __s32		count;		/* Count of characters in bufr	*/
+        __s32		head;		/* index to head of list	*/
+        __s32		tail;		/* index to tail of list	*/
+        __u32		locked;		/* Buffer is being sent		*/
+        __s32		type;		/* Type of the buffer		*/
 					/* =0, device read buffer	*/
 					/* =1, device write buffer	*/
 					/* =2, daemon write buffer	*/
 					/* =3, daemon read buffer	*/
-  unsigned short	fcs;		/* Frame Check Sequence (CRC)	*/
-  unsigned char         filler[4];      /* Extra space if needed        */
+        __u16		fcs;		/* Frame Check Sequence (CRC)	*/
+        __u8		filler[4];	/* Extra space if needed	*/
 };
 
 /* Given a pointer to the ppp_buffer then return base address of buffer */
-#define buf_base(buf) ((u_char *) (&buf[1]))
+#define buf_base(buf) ((__u8 *) (&buf[1]))
 
 /*
  * Structure describing each ppp unit.
  */
 
 struct ppp {
-	int		magic;		/* magic value for structure	*/
+	__s32		magic;		/* magic value for structure	*/
 
-  /* Bitmapped flag fields. */
-	char		inuse;		/* are we allocated?		*/
-	char		escape;		/* 0x20 if prev char was PPP_ESC*/
-	char		toss;		/* toss this frame		*/
+	/* Bitmapped flag fields. */
+	__u8		inuse;		/* are we allocated?		*/
+	__u8		escape;		/* 0x20 if prev char was PPP_ESC*/
+	__u8		toss;		/* toss this frame		*/
 
-	unsigned int	flags;		/* miscellany			*/
+	__u32		flags;		/* miscellany			*/
 
-	ext_accm	xmit_async_map; /* 1 bit means that given control 
+	__u32		xmit_async_map[8]; /* 1 bit means that given control 
 					   character is quoted on output*/
 
 	__u32		recv_async_map; /* 1 bit means that given control 
 					   character is ignored on input*/
-	int			mtu;	/* maximum xmit frame size	*/
-	int			mru;	/* maximum receive frame size	*/
+	__s32			mtu;	/* maximum xmit frame size	*/
+	__s32			mru;	/* maximum receive frame size	*/
 
-  /* Information about the current tty data */
-	int			line;		/* PPP channel number	*/
+	/* Information about the current tty data */
+	__s32			line;		/* PPP channel number	*/
 	struct tty_struct	*tty;		/* ptr to TTY structure	*/
-	int			bytes_sent;	/* Bytes sent on frame	*/
-	int			bytes_rcvd;	/* Bytes recvd on frame	*/
+	__s32			bytes_sent;	/* Bytes sent on frame	*/
+	__s32			bytes_rcvd;	/* Bytes recvd on frame	*/
 
-  /* Interface to the network layer */
-	struct device		*dev;	/* easy for intr handling	*/
+	/* Interface to the network layer */
+	struct device		*dev;	 /* easy for intr handling	*/
 
-  /* VJ Header compression data */
-	struct slcompress	*slcomp;/* for header compression	*/
+	/* VJ Header compression data */
+	struct slcompress	*slcomp; /* for header compression	*/
 
-  /* Transmission information */
-	struct ppp_buffer *xbuf;	/* Buffer currently being sent  */
+	/* Transmission information */
+	struct ppp_buffer *xbuf;	/* Buffer currently being sent	*/
 	struct ppp_buffer *s1buf;	/* Pointer to daemon buffer	*/
 	struct ppp_buffer *s2buf;	/* Pointer to device buffer	*/
 
@@ -131,26 +131,27 @@
      has to hang around for the user process to read it, it lingers in
      the user buffers below. */
 
-	struct ppp_buffer *wbuf;	/* Transmission information	*/
-	struct ppp_buffer *tbuf;	/* daemon transmission buffer	*/
-	struct ppp_buffer *rbuf;	/* Receive information		*/
-	struct ppp_buffer *ubuf;	/* User buffer information	*/
-	struct ppp_buffer *cbuf;	/* compression buffer		*/
-
-  /* Queues for select() functionality */
-	struct wait_queue *write_wait;	/* queue for reading processes	*/
-	struct wait_queue *read_wait;	/* queue for writing processes	*/
-
-  /* Statistic information */
-	struct pppstat        stats;	/* statistic information	*/
-	struct ppp_idle       ddinfo;	/* demand dial information	*/
-
-  /* PPP compression protocol information */
-	u_int	sc_bytessent;		  /* count of octets sent */
-	u_int	sc_bytesrcvd;		  /* count of octets received */
+	struct ppp_buffer *wbuf;	  /* Transmission information	*/
+	struct ppp_buffer *tbuf;	  /* daemon transmission buffer	*/
+	struct ppp_buffer *rbuf;	  /* Receive information	*/
+	struct ppp_buffer *ubuf;	  /* User buffer information	*/
+	struct ppp_buffer *cbuf;	  /* compression buffer		*/
+
+	/* Queues for select() functionality */
+	struct wait_queue *write_wait;	  /* queue for reading processes */
+	struct wait_queue *read_wait;	  /* queue for writing processes */
+
+	/* Statistic information */
+	struct pppstat	      stats;	  /* statistic information	*/
+	struct ppp_idle	      ddinfo;	  /* demand dial information	*/
+
+	/* PPP compression protocol information */
+	__u32	sc_bytessent;		  /* count of octets sent */
+	__u32	sc_bytesrcvd;		  /* count of octets received */
 	enum	NPmode sc_npmode[NUM_NP]; /* what to do with each NP */
 	struct	compressor *sc_xcomp;	  /* transmit compressor */
 	void	*sc_xc_state;		  /* transmit compressor state */
 	struct	compressor *sc_rcomp;	  /* receive decompressor */
 	void	*sc_rc_state;		  /* receive decompressor state */
+	__s32	 sc_xfer;		  /* PID of reserved PPP table */
 };
diff -u --recursive --new-file v1.3.71/linux/include/linux/msdos_fs.h linux/include/linux/msdos_fs.h
--- v1.3.71/linux/include/linux/msdos_fs.h	Sun Feb 11 15:32:46 1996
+++ linux/include/linux/msdos_fs.h	Fri Mar  8 15:59:22 1996
@@ -158,6 +158,9 @@
 
 #ifdef __KERNEL__
 
+typedef int (*fat_filldir_t)(filldir_t filldir, void *, const char *,
+			     int, int, off_t, off_t, int, ino_t);
+
 struct fat_cache {
 	kdev_t device; /* device number. 0 means unused. */
 	int ino; /* inode number. */
@@ -207,6 +210,9 @@
 
 /* dir.c */
 extern struct file_operations fat_dir_operations;
+extern int fat_readdirx(struct inode *inode, struct file *filp, void *dirent,
+			fat_filldir_t fat_filldir, filldir_t filldir,
+			int shortnames, int longnames, int both);
 extern int fat_readdir(struct inode *inode, struct file *filp,
 		       void *dirent, filldir_t);
 extern int fat_dir_ioctl(struct inode * inode, struct file * filp,
diff -u --recursive --new-file v1.3.71/linux/include/linux/msdos_fs_sb.h linux/include/linux/msdos_fs_sb.h
--- v1.3.71/linux/include/linux/msdos_fs_sb.h	Fri Feb 23 13:54:38 1996
+++ linux/include/linux/msdos_fs_sb.h	Tue Mar  5 13:03:27 1996
@@ -25,8 +25,13 @@
 	char dotsOK;
 	char showexec; /* 1 = only set x bit for com/exe/bat */
 	char sys_immutable; /* system files are immutable */
-	int vfat; /* 0=no vfat long filename support, 1=vfat support */
 	int umsdos; /* 1 if mounted by umsdos, 0 if not */
+
+	/* vfat specific flags follow */
+	int vfat; /* 0=no vfat long filename support, 1=vfat support */
+	char unicode_xlate; /* create escape sequences for unhandled Unicode */
+	char posix; /* Allow names like makefile and Makefile to coexist */
+	char numtail; /* Does first alias have a numeric '~1' type tail? */
 };
 
 #endif
diff -u --recursive --new-file v1.3.71/linux/include/linux/nfs_fs.h linux/include/linux/nfs_fs.h
--- v1.3.71/linux/include/linux/nfs_fs.h	Tue Feb 20 14:37:28 1996
+++ linux/include/linux/nfs_fs.h	Fri Mar  8 15:51:34 1996
@@ -45,6 +45,12 @@
 #define NFS_FH(inode)			(&(inode)->u.nfs_i.fhandle)
 #define NFS_READTIME(inode)		((inode)->u.nfs_i.read_cache_jiffies)
 #define NFS_OLDMTIME(inode)		((inode)->u.nfs_i.read_cache_mtime)
+#define NFS_CACHEINV(inode) \
+do { \
+	NFS_READTIME(inode) = jiffies - 1000000; \
+	NFS_OLDMTIME(inode) = 0; \
+} while (0)
+
 
 #ifdef __KERNEL__
 
diff -u --recursive --new-file v1.3.71/linux/include/linux/pci.h linux/include/linux/pci.h
--- v1.3.71/linux/include/linux/pci.h	Fri Feb 23 13:54:38 1996
+++ linux/include/linux/pci.h	Thu Mar  7 16:16:32 1996
@@ -296,6 +296,9 @@
 
 #define PCI_VENDOR_ID_CT		0x102c
 #define PCI_DEVICE_ID_CT_65545		0x00d8
+#define PCI_DEVICE_ID_CT_65548		0x00dc
+
+#define PCI_VENDOR_ID_MIRO		0x1031
 
 #define PCI_VENDOR_ID_FD		0x1036
 #define PCI_DEVICE_ID_FD_36C70		0x0000
@@ -315,6 +318,7 @@
 
 #define PCI_VENDOR_ID_PCTECH		0x1042
 #define PCI_DEVICE_ID_PCTECH_RZ1000	0x1000
+#define PCI_DEVICE_ID_PCTECH_RZ1001	0x1001
 
 #define PCI_VENDOR_ID_DPT               0x1044   
 #define PCI_DEVICE_ID_DPT               0xa400  
@@ -457,9 +461,14 @@
 #define PCI_DEVICE_ID_ALLIANCE_PROMOTIO	0x3210
 #define PCI_DEVICE_ID_ALLIANCE_PROVIDEO	0x6422
 
+#define PCI_VENDOR_ID_VMIC		0x114a
+#define PCI_DEVICE_ID_VMIC_VME		0x7587
+
 #define PCI_VENDOR_ID_MUTECH		0x1159
 #define PCI_DEVICE_ID_MUTECH_MV1000	0x0001
 
+#define PCI_VENDOR_ID_TOSHIBA		0x1179
+
 #define PCI_VENDOR_ID_ZEITNET		0x1193
 #define PCI_DEVICE_ID_ZEITNET_1221	0x0001
 #define PCI_DEVICE_ID_ZEITNET_1225	0x0002
@@ -513,6 +522,7 @@
 
 #define PCI_VENDOR_ID_ADAPTEC		0x9004
 #define PCI_DEVICE_ID_ADAPTEC_7850	0x5078
+#define PCI_DEVICE_ID_ADAPTEC_7855	0x5578
 #define PCI_DEVICE_ID_ADAPTEC_7870	0x7078
 #define PCI_DEVICE_ID_ADAPTEC_7871	0x7178
 #define PCI_DEVICE_ID_ADAPTEC_7872	0x7278
diff -u --recursive --new-file v1.3.71/linux/include/linux/ppp-comp.h linux/include/linux/ppp-comp.h
--- v1.3.71/linux/include/linux/ppp-comp.h	Tue Oct 10 18:46:38 1995
+++ linux/include/linux/ppp-comp.h	Tue Mar  5 10:01:27 1996
@@ -28,10 +28,10 @@
  */
 
 /*
- *  ==FILEVERSION 3==
+ *  ==FILEVERSION 960302==
  *
  *  NOTE TO MAINTAINERS:
- *     If you modify this file at all, increment the number above.
+ *     If you modify this file at all, please set the above date.
  *     ppp-comp.h is shipped with a PPP distribution as well as with the kernel;
  *     if everyone increases the FILEVERSION number above, then scripts
  *     can do the right thing when deciding whether to install a new ppp-comp.h
diff -u --recursive --new-file v1.3.71/linux/include/linux/ppp_defs.h linux/include/linux/ppp_defs.h
--- v1.3.71/linux/include/linux/ppp_defs.h	Wed Feb 28 11:50:14 1996
+++ linux/include/linux/ppp_defs.h	Tue Mar  5 10:01:28 1996
@@ -28,10 +28,10 @@
  */
 
 /*
- *  ==FILEVERSION 5==
+ *  ==FILEVERSION 960302==
  *
  *  NOTE TO MAINTAINERS:
- *     If you modify this file at all, increment the number above.
+ *     If you modify this file at all, please set the above date.
  *     ppp_defs.h is shipped with a PPP distribution as well as with the kernel;
  *     if everyone increases the FILEVERSION number above, then scripts
  *     can do the right thing when deciding whether to install a new ppp_defs.h
@@ -49,9 +49,9 @@
 #define PPP_FCSLEN	2	/* octets for FCS */
 #define PPP_MRU		1500	/* default MRU = max length of info field */
 
-#define PPP_ADDRESS(p)	(((u_char *)(p))[0])
-#define PPP_CONTROL(p)	(((u_char *)(p))[1])
-#define PPP_PROTOCOL(p)	((((u_char *)(p))[2] << 8) + ((u_char *)(p))[3])
+#define PPP_ADDRESS(p)	(((__u8 *)(p))[0])
+#define PPP_CONTROL(p)	(((__u8 *)(p))[1])
+#define PPP_PROTOCOL(p)	((((__u8 *)(p))[2] << 8) + ((__u8 *)(p))[3])
 
 /*
  * Significant octet values.
@@ -79,20 +79,18 @@
 #define PPP_CHAP	0xc223	/* Cryptographic Handshake Auth. Protocol */
 
 /*
- * A 32-bit unsigned integral type.
+ * Values for FCS calculations.
  */
-#ifndef __BIT_TYPES_DEFINED__
-#ifdef	UINT32_T
-typedef UINT32_T	u_int32_t;
-#else
-typedef unsigned int	u_int32_t;
-#endif
-#endif
+
+#define PPP_INITFCS	0xffff	/* Initial FCS value */
+#define PPP_GOODFCS	0xf0b8	/* Good final FCS value */
+#define PPP_FCS(fcs, c)	(((fcs) >> 8) ^ fcstab[((fcs) ^ (c)) & 0xff])
 
 /*
  * Extended asyncmap - allows any character to be escaped.
  */
-typedef u_int32_t	ext_accm[8];
+
+typedef __u32		ext_accm[8];
 
 /*
  * What to do with network protocol (NP) packets.
@@ -108,42 +106,44 @@
  * Statistics for LQRP and pppstats
  */
 struct pppstat	{
-    u_int	ppp_discards;	/* # frames discarded */
+    __u32	ppp_discards;	/* # frames discarded */
 
-    u_int	ppp_ibytes;	/* bytes received */
-    u_int	ppp_ioctects;	/* bytes received not in error */
-    u_int	ppp_ipackets;	/* packets received */
-    u_int	ppp_ierrors;	/* receive errors */
-    u_int	ppp_ilqrs;	/* # LQR frames received */
-
-    u_int	ppp_obytes;	/* raw bytes sent */
-    u_int	ppp_ooctects;	/* frame bytes sent */
-    u_int	ppp_opackets;	/* packets sent */
-    u_int	ppp_oerrors;	/* transmit errors */ 
-    u_int	ppp_olqrs;	/* # LQR frames sent */
+    __u32	ppp_ibytes;	/* bytes received */
+    __u32	ppp_ioctects;	/* bytes received not in error */
+    __u32	ppp_ipackets;	/* packets received */
+    __u32	ppp_ierrors;	/* receive errors */
+    __u32	ppp_ilqrs;	/* # LQR frames received */
+
+    __u32	ppp_obytes;	/* raw bytes sent */
+    __u32	ppp_ooctects;	/* frame bytes sent */
+    __u32	ppp_opackets;	/* packets sent */
+    __u32	ppp_oerrors;	/* transmit errors */ 
+    __u32	ppp_olqrs;	/* # LQR frames sent */
 };
 
 struct vjstat {
-    u_int	vjs_packets;	/* outbound packets */
-    u_int	vjs_compressed;	/* outbound compressed packets */
-    u_int	vjs_searches;	/* searches for connection state */
-    u_int	vjs_misses;	/* times couldn't find conn. state */
-    u_int	vjs_uncompressedin; /* inbound uncompressed packets */
-    u_int	vjs_compressedin;   /* inbound compressed packets */
-    u_int	vjs_errorin;	/* inbound unknown type packets */
-    u_int	vjs_tossed;	/* inbound packets tossed because of error */
+    __u32	vjs_packets;	/* outbound packets */
+    __u32	vjs_compressed;	/* outbound compressed packets */
+    __u32	vjs_searches;	/* searches for connection state */
+    __u32	vjs_misses;	/* times couldn't find conn. state */
+    __u32	vjs_uncompressedin; /* inbound uncompressed packets */
+    __u32	vjs_compressedin;   /* inbound compressed packets */
+    __u32	vjs_errorin;	/* inbound unknown type packets */
+    __u32	vjs_tossed;	/* inbound packets tossed because of error */
 };
 
 struct compstat {
-    u_int	unc_bytes;	/* total uncompressed bytes */
-    u_int	unc_packets;	/* total uncompressed packets */
-    u_int	comp_bytes;	/* compressed bytes */
-    u_int	comp_packets;	/* compressed packets */
-    u_int	inc_bytes;	/* incompressible bytes */
-    u_int	inc_packets;	/* incompressible packets */
+    __u32	unc_bytes;	/* total uncompressed bytes */
+    __u32	unc_packets;	/* total uncompressed packets */
+    __u32	comp_bytes;	/* compressed bytes */
+    __u32	comp_packets;	/* compressed packets */
+    __u32	inc_bytes;	/* incompressible bytes */
+    __u32	inc_packets;	/* incompressible packets */
+
     /* the compression ratio is defined as in_count / bytes_out */
-    u_int       in_count;	/* Bytes received */
-    u_int       bytes_out;	/* Bytes transmitted */
+    __u32       in_count;	/* Bytes received */
+    __u32       bytes_out;	/* Bytes transmitted */
+
     double	ratio;		/* not computed in kernel. */
 };
 
diff -u --recursive --new-file v1.3.71/linux/include/linux/serial.h linux/include/linux/serial.h
--- v1.3.71/linux/include/linux/serial.h	Sun Dec 17 11:43:32 1995
+++ linux/include/linux/serial.h	Fri Mar  8 09:43:24 1996
@@ -79,6 +79,7 @@
 #define ASYNC_CLOSING		0x08000000 /* Serial port is closing */
 #define ASYNC_CTS_FLOW		0x04000000 /* Do CTS flow control */
 #define ASYNC_CHECK_CD		0x02000000 /* i.e., CLOCAL */
+#define ASYNC_SHARE_IRQ		0x01000000 /* for multifunction cards */
 
 /*
  * Multiport serial configuration structure --- external structure
diff -u --recursive --new-file v1.3.71/linux/include/linux/socket.h linux/include/linux/socket.h
--- v1.3.71/linux/include/linux/socket.h	Tue Mar  5 10:11:13 1996
+++ linux/include/linux/socket.h	Wed Mar  6 08:06:05 1996
@@ -31,7 +31,7 @@
  */
  
 #define msg_control	msg_accrights	
-#define msg_controllen	msg_accrightslen;	
+#define msg_controllen	msg_accrightslen
 
 /* Control Messages */
 
diff -u --recursive --new-file v1.3.71/linux/include/linux/sysctl.h linux/include/linux/sysctl.h
--- v1.3.71/linux/include/linux/sysctl.h	Wed Feb 28 11:50:14 1996
+++ linux/include/linux/sysctl.h	Fri Mar  8 08:11:47 1996
@@ -55,6 +55,7 @@
 #define KERN_MAXFILE	12
 #define KERN_MAXID	13
 #define KERN_SECURELVL	14	/* int: system security level */
+#define KERN_PANIC	15	/* int: panic timeout */
 
 /* CTL_VM names: */
 #define VM_SWAPCTL	1	/* struct: Set vm swapping control */
diff -u --recursive --new-file v1.3.71/linux/kernel/ksyms.c linux/kernel/ksyms.c
--- v1.3.71/linux/kernel/ksyms.c	Tue Mar  5 10:11:13 1996
+++ linux/kernel/ksyms.c	Fri Mar  8 09:43:26 1996
@@ -41,6 +41,7 @@
 #include <linux/mount.h>
 #include <linux/pagemap.h>
 #include <linux/sysctl.h>
+#include <linux/hdreg.h>
 #include <linux/skbuff.h>
 #include <linux/genhd.h>
 #include <linux/swap.h>
@@ -246,7 +247,6 @@
 	/* executable format registration */
 	X(register_binfmt),
 	X(unregister_binfmt),
-	X(get_binfmt_list),
 	X(search_binary_handler),
 	X(prepare_binprm),
 
@@ -268,6 +268,7 @@
 	X(probe_irq_off),
 	X(bh_active),
 	X(bh_mask),
+	X(bh_mask_count),
 	X(bh_base),
 	X(add_timer),
 	X(del_timer),
@@ -467,6 +468,11 @@
 	X(tr_type_trans),
 #endif
 
+#ifdef CONFIG_BLK_DEV_IDE
+	X(ide_register),
+	X(ide_unregister),
+#endif
+ 	
 	/* bimfm_aout */
 	X(get_write_access),
 	X(put_write_access),
diff -u --recursive --new-file v1.3.71/linux/kernel/panic.c linux/kernel/panic.c
--- v1.3.71/linux/kernel/panic.c	Wed Feb 14 14:37:19 1996
+++ linux/kernel/panic.c	Fri Mar  8 08:11:47 1996
@@ -16,13 +16,15 @@
 
 asmlinkage void sys_sync(void);	/* it's really int */
 extern void hard_reset_now(void);
+extern void do_unblank_screen(void);
+extern int C_A_D;
 
-static int timeout = -1;
+int panic_timeout = 0;
 
 void panic_setup(char *str, int *ints)
 {
 	if (ints[0] == 1)
-		timeout = ints[1];
+		panic_timeout = ints[1];
 }
 
 NORET_TYPE void panic(const char * fmt, ...)
@@ -39,14 +41,17 @@
 		printk(KERN_EMERG "In swapper task - not syncing\n");
 	else
 		sys_sync();
-	if (timeout >= 0)
+
+	do_unblank_screen();
+
+	if (panic_timeout > 0)
 	{
 		/*
 	 	 * Delay timeout seconds before rebooting the machine. 
 		 * We can't use the "normal" timers since we just paniced..
 	 	 */
-		printk(KERN_EMERG "Rebooting in %d seconds..",timeout);
-		for(i = 0; i < (timeout*1000); i++)
+		printk(KERN_EMERG "Rebooting in %d seconds..",panic_timeout);
+		for(i = 0; i < (panic_timeout*1000); i++)
 			udelay(1000);
 		hard_reset_now();
 	}
diff -u --recursive --new-file v1.3.71/linux/kernel/sched.c linux/kernel/sched.c
--- v1.3.71/linux/kernel/sched.c	Tue Mar  5 10:11:14 1996
+++ linux/kernel/sched.c	Wed Mar  6 13:59:33 1996
@@ -882,12 +882,10 @@
 			continue;
 		mark_bh(TIMER_BH);
 	}
-	cli();
 	if (timer_head.next->expires <= jiffies)
 		mark_bh(TIMER_BH);
 	if (tq_timer != &tq_last)
 		mark_bh(TQUEUE_BH);
-	sti();
 }
 
 #ifndef __alpha__
diff -u --recursive --new-file v1.3.71/linux/kernel/sys.c linux/kernel/sys.c
--- v1.3.71/linux/kernel/sys.c	Sat Mar  2 10:43:45 1996
+++ linux/kernel/sys.c	Fri Mar  8 08:11:47 1996
@@ -26,7 +26,7 @@
 /*
  * this indicates whether you can reboot with ctrl-alt-del: the default is yes
  */
-static int C_A_D = 1;
+int C_A_D = 1;
 
 extern void adjust_clock(void);
 
diff -u --recursive --new-file v1.3.71/linux/kernel/sysctl.c linux/kernel/sysctl.c
--- v1.3.71/linux/kernel/sysctl.c	Wed Feb 28 11:50:15 1996
+++ linux/kernel/sysctl.c	Fri Mar  8 08:11:47 1996
@@ -21,6 +21,10 @@
 #include <linux/utsname.h>
 #include <linux/swapctl.h>
 
+/* External variables not in a header file. */
+extern int panic_timeout;
+
+
 static ctl_table root_table[];
 static struct ctl_table_header root_table_header = 
 	{root_table, DNODE_SINGLE(&root_table_header)};
@@ -116,6 +120,8 @@
 	 0644, NULL, &proc_dointvec},
 	{KERN_SECURELVL, "securelevel", &securelevel, sizeof(int),
 	 0444, NULL, &proc_dointvec, (ctl_handler *)&do_securelevel_strategy},
+	{KERN_PANIC, "panic", &panic_timeout, sizeof(int),
+	 0644, NULL, &proc_dointvec},
 	{0}
 };
 
diff -u --recursive --new-file v1.3.71/linux/scripts/Menuconfig linux/scripts/Menuconfig
--- v1.3.71/linux/scripts/Menuconfig	Tue Mar  5 10:11:16 1996
+++ linux/scripts/Menuconfig	Fri Mar  8 08:59:12 1996
@@ -62,12 +62,12 @@
 
      if [ -z "$text" ]
      then
-	  echo "There is no help available for kernel option."
+	  echo "There is no help available for this kernel option."
      else
 	  echo "$text"
      fi
   else
-	 echo "There is no help available for kernel option."
+	 echo "There is no help available for this kernel option."
   fi
 }
 
@@ -75,8 +75,14 @@
 # Activate a help dialog.
 #
 function help () {
-	extract_help $1 >help.out
-	$DIALOG --backtitle "$backtitle" --title "$2" --textbox help.out 20 75
+	if extract_help $1 >help.out
+	then
+		$DIALOG	--backtitle "$backtitle" --title "$2"\
+			--textbox help.out 20 75
+	else
+		$DIALOG	--backtitle "$backtitle" \
+			--textbox help.out 20 75
+	fi
 	rm help.out
 }
 
@@ -669,16 +675,16 @@
 
 	function dep_tristate () {
 		eval x=\${$2:-n}
-		if eval [ "_$3" != "_m" ]
+
+		if eval [ "_$3" = "_m" ]
 		then
-			tristate "$1" "$2"
-		else
 			if [ "$x" = "y" ]
 			then
 				x="m"
 			fi
-			define_bool "$2" "$x"
 		fi
+
+		define_bool "$2" "$x"
 	}
 
 	function int () {
@@ -694,6 +700,8 @@
 	}
 
 	function define_bool () {
+		eval $1="$2"
+
    		case "$2" in
          	y)
                 	echo "$1=y" 		>>$CONFIG
diff -u --recursive --new-file v1.3.71/linux/scripts/README.Menuconfig linux/scripts/README.Menuconfig
--- v1.3.71/linux/scripts/README.Menuconfig	Tue Mar  5 10:11:16 1996
+++ linux/scripts/README.Menuconfig	Thu Mar  7 09:06:41 1996
@@ -99,13 +99,24 @@
 complete rebuild of lxdialog by changing to it's directory and doing
 'make clean all'
 
+If you use Menuconfig in an XTERM window make sure you have your 
+$TERM variable set to point to a xterm definition which supports color.
+Otherwise, Menuconfig will look rather bad.  Menuconfig will not 
+display correctly in a RXVT window because rxvt display's only one
+intensity of color, bright.
+
+
 NOTICE:  lxdialog requires the ncurses libraries to compile.  If you
          don't already have ncurses you really should get it.
 
          The makefile for lxdialog attempts to find your ncurses
          header file.  Although it should find the header for older
          versions of ncurses, it is probably a good idea to get the
-         latest ncurses anyway.
+         latest ncurses anyway. 
+
+         If you have upgraded your ncurses libraries, MAKE SURE you
+         remove the old ncurses header files.  If you don't you
+         will most certainly get a segmentation fault.
 
 WARNING: It is not recommended that you change any defines in
          lxdialog's header files.  If you have a grayscale display and
diff -u --recursive --new-file v1.3.71/linux/scripts/lxdialog/Makefile linux/scripts/lxdialog/Makefile
--- v1.3.71/linux/scripts/lxdialog/Makefile	Tue Mar  5 10:11:16 1996
+++ linux/scripts/lxdialog/Makefile	Thu Mar  7 09:06:41 1996
@@ -10,7 +10,7 @@
         CFLAGS += -I/usr/include/ncurses -DCURSES_LOC="<ncurses.h>"
 else
 ifeq (/usr/include/ncurses/curses.h, $(wildcard /usr/include/ncurses/curses.h))
-        CFLAGS += -I/usr/include/ncurses -DCURSES_LOC="<curses.h>"
+        CFLAGS += -I/usr/include/ncurses -DCURSES_LOC="<ncurses/curses.h>"
 else
 ifeq (/usr/include/ncurses.h, $(wildcard /usr/include/ncurses.h))
         CFLAGS += -DCURSES_LOC="<ncurses.h>"
diff -u --recursive --new-file v1.3.71/linux/scripts/lxdialog/menubox.c linux/scripts/lxdialog/menubox.c
--- v1.3.71/linux/scripts/lxdialog/menubox.c	Tue Mar  5 10:11:17 1996
+++ linux/scripts/lxdialog/menubox.c	Fri Mar  8 08:59:12 1996
@@ -30,8 +30,11 @@
 print_item (WINDOW * win, const char *item, int choice, int selected, int hotkey)
 {
     int i, j;
+    char menu_item[menu_width+1];
 
-    j = first_alpha(item);
+    strncpy(menu_item, item, menu_width);
+    menu_item[menu_width] = 0;
+    j = first_alpha(menu_item);
 
     /* Clear 'residue' of last item */
     wattrset (win, menubox_attr);
@@ -39,10 +42,10 @@
     for (i = 0; i < menu_width; i++)
 	waddch (win, ' ');
     wattrset (win, selected ? item_selected_attr : item_attr);
-    mvwaddstr (win, choice, item_x, (char *)item);
+    mvwaddstr (win, choice, item_x, menu_item);
     if (hotkey) {
     	wattrset (win, selected ? tag_key_selected_attr : tag_key_attr);
-    	mvwaddch(win, choice, item_x+j, item[j]);
+    	mvwaddch(win, choice, item_x+j, menu_item[j]);
     }
 }
 
@@ -169,7 +172,7 @@
      */
     item_x = 0;
     for (i = 0; i < item_no; i++) {
-	item_x = MAX (item_x, strlen (items[i * 2 + 1]) + 2);
+	item_x = MAX (item_x, MIN(menu_width, strlen (items[i * 2 + 1]) + 2));
 	if (strcmp(current, items[i*2]) == 0) choice = i;
     }
 
@@ -301,7 +304,7 @@
 	case '\n':
 	    delwin (dialog);
 	    if (button == 2) 
-            	fprintf(stderr, "\"%s\" \"%s\"", 
+            	fprintf(stderr, "%s \"%s\"", 
 			items[(scroll + choice) * 2],
 			items[(scroll + choice) * 2 + 1] +
 			first_alpha(items[(scroll + choice) * 2 + 1]));
diff -u --recursive --new-file v1.3.71/linux/scripts/patch-kernel linux/scripts/patch-kernel
--- v1.3.71/linux/scripts/patch-kernel	Wed Feb 28 11:50:18 1996
+++ linux/scripts/patch-kernel	Thu Mar  7 08:33:19 1996
@@ -35,9 +35,11 @@
         break
     fi
 
-    echo "Applying $patch..."
+    echo -n "Applying $patch... "
     if (gunzip -dc $patchdir/$patch | grep -v '^\\' | patch -p1 -s -E -d $sourcedir)
     then
+	echo "done"
+    else
         echo "Patch failed.  Clean up yourself."
         break
     fi
