diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 02:20:36 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 02:20:36 +0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/char/ftape | |
download | linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.xz |
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/char/ftape')
62 files changed, 19417 insertions, 0 deletions
diff --git a/drivers/char/ftape/Kconfig b/drivers/char/ftape/Kconfig new file mode 100644 index 000000000000..7d3ecb56a1bd --- /dev/null +++ b/drivers/char/ftape/Kconfig @@ -0,0 +1,340 @@ +# +# Ftape configuration +# +config ZFTAPE + tristate "Zftape, the VFS interface" + depends on FTAPE + ---help--- + Normally, you want to say Y or M. DON'T say N here or you + WON'T BE ABLE TO USE YOUR FLOPPY TAPE DRIVE. + + The ftape module itself no longer contains the routines necessary + to interface with the kernel VFS layer (i.e. to actually write data + to and read data from the tape drive). Instead the file system + interface (i.e. the hardware independent part of the driver) has + been moved to a separate module. + + To compile this driver as a module, choose M here: the + module will be called zftape. + + Regardless of whether you say Y or M here, an additional runtime + loadable module called `zft-compressor' which contains code to + support user transparent on-the-fly compression based on Ross + William's lzrw3 algorithm will be produced. If you have enabled the + kernel module loader (i.e. have said Y to "Kernel module loader + support", above) then `zft-compressor' will be loaded + automatically by zftape when needed. + + Despite its name, zftape does NOT use compression by default. The + file <file:Documentation/ftape.txt> contains a short description of + the most important changes in the file system interface compared to + previous versions of ftape. The ftape home page + <http://www.instmath.rwth-aachen.de/~heine/ftape/> contains + further information. + + IMPORTANT NOTE: zftape can read archives created by previous + versions of ftape and provide file mark support (i.e. fast skipping + between tape archives) but previous version of ftape will lack file + mark support when reading archives produced by zftape. + +config ZFT_DFLT_BLK_SZ + int "Default block size" + depends on ZFTAPE + default "10240" + ---help--- + If unsure leave this at its default value, i.e. 10240. Note that + you specify only the default block size here. The block size can be + changed at run time using the MTSETBLK tape operation with the + MTIOCTOP ioctl (i.e. with "mt -f /dev/qft0 setblk #BLKSZ" from the + shell command line). + + The probably most striking difference between zftape and previous + versions of ftape is the fact that all data must be written or read + in multiples of a fixed block size. The block size defaults to + 10240 which is what GNU tar uses. The values for the block size + should be either 1 or multiples of 1024 up to a maximum value of + 63488 (i.e. 62 K). If you specify `1' then zftape's builtin + compression will be disabled. + + Reasonable values are `10240' (GNU tar's default block size), + `5120' (afio's default block size), `32768' (default block size some + backup programs assume for SCSI tape drives) or `1' (no restriction + on block size, but disables builtin compression). + +comment "The compressor will be built as a module only!" + depends on FTAPE && ZFTAPE + +config ZFT_COMPRESSOR + tristate + depends on FTAPE!=n && ZFTAPE!=n + default m + +config FT_NR_BUFFERS + int "Number of ftape buffers (EXPERIMENTAL)" + depends on FTAPE && EXPERIMENTAL + default "3" + help + Please leave this at `3' unless you REALLY know what you are doing. + It is not necessary to change this value. Values below 3 make the + proper use of ftape impossible, values greater than 3 are a waste of + memory. You can change the amount of DMA memory used by ftape at + runtime with "mt -f /dev/qft0 setdrvbuffer #NUMBUFFERS". Each buffer + wastes 32 KB of memory. Please note that this memory cannot be + swapped out. + +config FT_PROC_FS + bool "Enable procfs status report (+2kb)" + depends on FTAPE && PROC_FS + ---help--- + Optional. Saying Y will result in creation of a directory + `/proc/ftape' under the /proc file system. The files can be viewed + with your favorite pager (i.e. use "more /proc/ftape/history" or + "less /proc/ftape/history" or simply "cat /proc/ftape/history"). The + file will contain some status information about the inserted + cartridge, the kernel driver, your tape drive, the floppy disk + controller and the error history for the most recent use of the + kernel driver. Saying Y will enlarge the size of the ftape driver + by approximately 2 KB. + + WARNING: When compiling ftape as a module (i.e. saying M to "Floppy + tape drive") it is dangerous to use ftape's /proc file system + interface. Accessing `/proc/ftape' while the module is unloaded will + result in a kernel Oops. This cannot be fixed from inside ftape. + +choice + prompt "Debugging output" + depends on FTAPE + default FT_NORMAL_DEBUG + +config FT_NORMAL_DEBUG + bool "Normal" + ---help--- + This option controls the amount of debugging output the ftape driver + is ABLE to produce; it does not increase or diminish the debugging + level itself. If unsure, leave this at its default setting, + i.e. choose "Normal". + + Ftape can print lots of debugging messages to the system console + resp. kernel log files. Reducing the amount of possible debugging + output reduces the size of the kernel module by some KB, so it might + be a good idea to use "None" for emergency boot floppies. + + If you want to save memory then the following strategy is + recommended: leave this option at its default setting "Normal" until + you know that the driver works as expected, afterwards reconfigure + the kernel, this time specifying "Reduced" or "None" and recompile + and install the kernel as usual. Note that choosing "Excessive" + debugging output does not increase the amount of debugging output + printed to the console but only makes it possible to produce + "Excessive" debugging output. + + Please read <file:Documentation/ftape.txt> for a short description + how to control the amount of debugging output. + +config FT_FULL_DEBUG + bool "Excessive" + help + Extremely verbose output for driver debugging purposes. + +config FT_NO_TRACE + bool "Reduced" + help + Reduced tape driver debugging output. + +config FT_NO_TRACE_AT_ALL + bool "None" + help + Suppress all debugging output from the tape drive. + +endchoice + +comment "Hardware configuration" + depends on FTAPE + +choice + prompt "Floppy tape controllers" + depends on FTAPE + default FT_STD_FDC + +config FT_STD_FDC + bool "Standard" + ---help--- + Only change this setting if you have a special controller. If you + didn't plug any add-on card into your computer system but just + plugged the floppy tape cable into the already existing floppy drive + controller then you don't want to change the default setting, + i.e. choose "Standard". + + Choose "MACH-2" if you have a Mountain Mach-2 controller. + Choose "FC-10/FC-20" if you have a Colorado FC-10 or FC-20 + controller. + Choose "Alt/82078" if you have another controller that is located at + an IO base address different from the standard floppy drive + controller's base address of `0x3f0', or uses an IRQ (interrupt) + channel different from `6', or a DMA channel different from + `2'. This is necessary for any controller card that is based on + Intel's 82078 FDC such as Seagate's, Exabyte's and Iomega's "high + speed" controllers. + + If you choose something other than "Standard" then please make + sure that the settings for the IO base address and the IRQ and DMA + channel in the configuration menus below are correct. Use the manual + of your tape drive to determine the correct settings! + + If you are already successfully using your tape drive with another + operating system then you definitely should use the same settings + for the IO base, the IRQ and DMA channel that have proven to work + with that other OS. + + Note that this menu lets you specify only the default setting for + the hardware setup. The hardware configuration can be changed at + boot time (when ftape is compiled into the kernel, i.e. if you + have said Y to "Floppy tape drive") or module load time (i.e. if you + have said M to "Floppy tape drive"). + + Please read also the file <file:Documentation/ftape.txt> which + contains a short description of the parameters that can be set at + boot or load time. If you want to use your floppy tape drive on a + PCI-bus based system, please read the file + <file:drivers/char/ftape/README.PCI>. + +config FT_MACH2 + bool "MACH-2" + +config FT_PROBE_FC10 + bool "FC-10/FC-20" + +config FT_ALT_FDC + bool "Alt/82078" + +endchoice + +comment "Consult the manuals of your tape drive for the correct settings!" + depends on FTAPE && !FT_STD_FDC + +config FT_FDC_BASE + hex "IO base of the floppy disk controller" + depends on FTAPE && !FT_STD_FDC + default "0" + ---help--- + You don't need to specify a value if the following default + settings for the base IO address are correct: + <<< MACH-2 : 0x1E0 >>> + <<< FC-10/FC-20: 0x180 >>> + <<< Secondary : 0x370 >>> + Secondary refers to a secondary FDC controller like the "high speed" + controllers delivered by Seagate or Exabyte or Iomega's Ditto Dash. + Please make sure that the setting for the IO base address + specified here is correct. USE THE MANUAL OF YOUR TAPE DRIVE OR + CONTROLLER CARD TO DETERMINE THE CORRECT SETTING. If you are already + successfully using the tape drive with another operating system then + you definitely should use the same settings for the IO base that has + proven to work with that other OS. + + Note that this menu lets you specify only the default setting for + the IO base. The hardware configuration can be changed at boot time + (when ftape is compiled into the kernel, i.e. if you specified Y to + "Floppy tape drive") or module load time (i.e. if you have said M to + "Floppy tape drive"). + + Please read also the file <file:Documentation/ftape.txt> which + contains a short description of the parameters that can be set at + boot or load time. + +config FT_FDC_IRQ + int "IRQ channel of the floppy disk controller" + depends on FTAPE && !FT_STD_FDC + default "0" + ---help--- + You don't need to specify a value if the following default + settings for the interrupt channel are correct: + <<< MACH-2 : 6 >>> + <<< FC-10/FC-20: 9 >>> + <<< Secondary : 6 >>> + Secondary refers to secondary a FDC controller like the "high speed" + controllers delivered by Seagate or Exabyte or Iomega's Ditto Dash. + Please make sure that the setting for the IO base address + specified here is correct. USE THE MANUAL OF YOUR TAPE DRIVE OR + CONTROLLER CARD TO DETERMINE THE CORRECT SETTING. If you are already + successfully using the tape drive with another operating system then + you definitely should use the same settings for the IO base that has + proven to work with that other OS. + + Note that this menu lets you specify only the default setting for + the IRQ channel. The hardware configuration can be changed at boot + time (when ftape is compiled into the kernel, i.e. if you said Y to + "Floppy tape drive") or module load time (i.e. if you said M to + "Floppy tape drive"). + + Please read also the file <file:Documentation/ftape.txt> which + contains a short description of the parameters that can be set at + boot or load time. + +config FT_FDC_DMA + int "DMA channel of the floppy disk controller" + depends on FTAPE && !FT_STD_FDC + default "0" + ---help--- + You don't need to specify a value if the following default + settings for the DMA channel are correct: + <<< MACH-2 : 2 >>> + <<< FC-10/FC-20: 3 >>> + <<< Secondary : 2 >>> + Secondary refers to a secondary FDC controller like the "high speed" + controllers delivered by Seagate or Exabyte or Iomega's Ditto Dash. + Please make sure that the setting for the IO base address + specified here is correct. USE THE MANUAL OF YOUR TAPE DRIVE OR + CONTROLLER CARD TO DETERMINE THE CORRECT SETTING. If you are already + successfully using the tape drive with another operating system then + you definitely should use the same settings for the IO base that has + proven to work with that other OS. + + Note that this menu lets you specify only the default setting for + the DMA channel. The hardware configuration can be changed at boot + time (when ftape is compiled into the kernel, i.e. if you said Y to + "Floppy tape drive") or module load time (i.e. if you said M to + "Floppy tape drive"). + + Please read also the file <file:Documentation/ftape.txt> which + contains a short description of the parameters that can be set at + boot or load time. + +config FT_FDC_THR + int "Default FIFO threshold (EXPERIMENTAL)" + depends on FTAPE && EXPERIMENTAL + default "8" + help + Set the FIFO threshold of the FDC. If this is higher the DMA + controller may serve the FDC after a higher latency time. If this is + lower, fewer DMA transfers occur leading to less bus contention. + You may try to tune this if ftape annoys you with "reduced data + rate because of excessive overrun errors" messages. However, this + doesn't seem to have too much effect. + + If unsure, don't touch the initial value, i.e. leave it at "8". + +config FT_FDC_MAX_RATE + int "Maximal data rate to use (EXPERIMENTAL)" + depends on FTAPE && EXPERIMENTAL + default "2000" + ---help--- + With some motherboard/FDC combinations ftape will not be able to + run your FDC/tape drive combination at the highest available + speed. If this is the case you'll encounter "reduced data rate + because of excessive overrun errors" messages and lots of retries + before ftape finally decides to reduce the data rate. + + In this case it might be desirable to tell ftape beforehand that + it need not try to run the tape drive at the highest available + speed. If unsure, leave this disabled, i.e. leave it at 2000 + bits/sec. + +config FT_ALPHA_CLOCK + int "CPU clock frequency of your DEC Alpha" if ALPHA + depends on FTAPE + default "0" + help + On some DEC Alpha machines the CPU clock frequency cannot be + determined automatically, so you need to specify it here ONLY if + running a DEC Alpha, otherwise this setting has no effect. + diff --git a/drivers/char/ftape/Makefile b/drivers/char/ftape/Makefile new file mode 100644 index 000000000000..0e67d2f8b7ec --- /dev/null +++ b/drivers/char/ftape/Makefile @@ -0,0 +1,28 @@ +# +# Copyright (C) 1997 Claus Heine. +# +# 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: /homes/cvs/ftape-stacked/ftape/Makefile,v $ +# $Revision: 1.4 $ +# $Date: 1997/10/05 19:17:56 $ +# +# Makefile for the QIC-40/80/3010/3020 floppy-tape driver for +# Linux. +# + +obj-$(CONFIG_FTAPE) += lowlevel/ +obj-$(CONFIG_ZFTAPE) += zftape/ +obj-$(CONFIG_ZFT_COMPRESSOR) += compressor/ diff --git a/drivers/char/ftape/README.PCI b/drivers/char/ftape/README.PCI new file mode 100644 index 000000000000..18de159d36e0 --- /dev/null +++ b/drivers/char/ftape/README.PCI @@ -0,0 +1,81 @@ +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 data-rate 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. + + --//-- + LocalWords: ftape PCI bios GAT ISA DMA chipset Mbps Kbps FDC isa AF ok ASUS + LocalWords: SP linebuffer masterbuffer XPS http www com diff --git a/drivers/char/ftape/RELEASE-NOTES b/drivers/char/ftape/RELEASE-NOTES new file mode 100644 index 000000000000..03799dbc05a4 --- /dev/null +++ b/drivers/char/ftape/RELEASE-NOTES @@ -0,0 +1,966 @@ +Hey, Emacs, we're -*-Text-*- mode! + +===== Release notes for ftape-3.04d 25/11/97 ===== +- The correct pre-processor statement for "else if" is "#elif" not + "elsif". +- Need to call zft_reset_position() when overwriting cartridges + previously written with ftape-2.x, sftape, or ancient + (pre-ftape-3.x) versions of zftape. + +===== Release notes for ftape-3.04c 16/11/97 ===== +- fdc_probe() was calling DUMPREGS with a result length of "1" which + was just fine. Undo previous change. + +===== Release notes for ftape-3.04b 14/11/97 ===== + +- patches/2.x.x/floppy.c.diff was somewhat broken, releasing i/o + regions it never had allocated. +- fdc_probe() was calling DUMPREGS with a result length of "1" instead + of "10" +- Writing deleted data marks if the first segents on track zero are + should work now. +- ftformat should now be able to handle those cases where the tape + drive sets the read only status bit (QIC-40/80 cartridges with + QIC-3010/3020 tape drives) because the header segment is damaged. +- the MTIOCFTCMD ioctl may now be issued by the superuser ONLY. + +===== Release notes for ftape-3.04a 12/11/97 ===== +- Fix an "infinite loop can't be killed by signal" bug in + ftape_get_drive_status(). Only relevant when trying to access + buggy/misconfigured hardware +- Try to compensate a bug in the HP Colorado T3000's firmware: it + doesn't set the write protect bit for QIC80/QIC40 cartridges. + +===== Release notes for ftape-3.04 06/11/97 ===== +- If positioning with fast seeking fails fall back to a slow seek + before giving up. +- (nearly) no retries on "no data errors" when verifying after + formatting. Improved tuning of the bad sector map after formatting. +- the directory layout has changed again to allow for easier kernel + integration +- Module parameter "ftape_tracing" now is called "ft_tracing" because + the "ftape_tracing" variable has the version checksum attached to it. +- `/proc/ftape' interface for 2.0.* kernels. `/proc/ftape' no longer + is a directory but a file that contains all the information formerly + provided in separate files under the `/proc/ftape/' directory. +- Most of the configuration options have been prefixed by "CONFIG_FT_" + in preparation of the kernel inclusion. The Makefiles under + "./ftape/" should be directly usable by the kernel. +- The MODVERSIONS stuff is now auto-detected. +- Broke backslashed multi line options in MCONFIG into separate lines + using GNU-make's "+=" feature. +- The html and dvi version of the manual is now installed under + '/usr/doc/ftape` with 'make install` +- New SMP define in MCONFIG. ftape works with SMP if this is defined. +- attempt to cope with "excessive overrun errors" by gradually + increasing FDC FIFO threshold. But this doesn't seem to have too + much an effect. +- New load time configuration parameter "ft_fdc_rate_limit". If you + encounter too many overrun errors with a 2Mb controller then you + might want to set this to 1000. +- overrun errors on the last sector in a segment sometimes result in + a zero DMA residue. Dunno why, but compensate for it. +- there were still fdc_read() timeout errors. I think I have fixed it + now, please FIXME. +- Sometimes ftape_write() failed to re-start the tape drive when a + segment without a good sector was reached ("wait for empty segment + failed"). This is fixed. Especially important for > QIC-3010. +- sftape (aka ftape-2.x) has vanished. I didn't work on it for + ages. It is probably still possible to use the old code with + ftape-3.04, if one really needs it (BUT RECOMPILE IT) +- zftape no longer alters the contents of already existing volume + table entries, which makes it possible to fill in missing fields, + like time stamps using some user space program. +- ./contrib/vtblc/ contains such a program. +- new perl script ./contrib/scripts/listtape that list the contents of a + floppy tape cartridge parsing the output of "mt volinfo" + "mt fsf" +- the MTWEOF implementation has changed a little bit (after I had a + look at amanda). Calling MTWEOF while the tape is still held open + after writing something to the tape now will terminate the current + volume, and start a new one at the current position. +- the volume table maintained by zftape now is a doubly linked list + that grows dynamically as needed. + + formatting floppy tape cartridges + --------------------------------- + * there is a new user space formatting program that does most of the + dirty work in user space (auto-detect, computing the sector + coordinates, adjusting time stamps and statistics). It has a + simple command line interface. + * ftape-format.o has vanished, it has been folded into the low level + ftape.o module, and the ioctl interface into zftape.o. Most of the + complicated stuff has been moved to user space, so there was no + need for a separate module anymore. + * there is a new ioctl MTIOCFTCMD that sends a bare QIC-117 command + to the tape drive. + * there is a new mmap() feature to map the dma buffers into user + space to be used by the user level formatting program. + * Formatting of yet unformatted or totally degaussed cartridges + should be possible now. FIXME. + +===== Release notes for ftape-3.03b, <forgot the exact date> ==== + +ftape-3.03b was released as a beta release only. Its main new feature +was support of the DITTO-2GB drive. This was made possible by reverse +engineering done by <fill in his name> after Iomega failed to support +ftape. Although they had promised to do so (this makes me feel a bit +sad and uncomfortable about Iomega). + +===== Release notes for ftape-3.03a, 22/05/97 ==== + +- Finally fixed auto-un-loading of modules for kernels > 2.1.18 +- Add an "uninstall" target to the Makefile +- removed the kdtime hack +- texi2www didn't properly set the back-reference from a footnote back + to the regular text. + + zftape specific + --------------- + * hide the old compression map volume. Taper doesn't accept the + presence of non-Taper volumes and Taper-written volume on the same + tape. + * EOD (End Of Data) handling was still broken: the expected behavior + is to return a zero byte count at the first attempt to read past + EOD, return a zero byte count at the second attempt to read past + EOD and THEN return -EIO. + + ftape-format specific + --------------------- + * Detection of QIC-40 cartridges in select_tape_format() was broken + and made it impossible to format QIC-3010/3020 cartridges. + * There are strange "TR-1 Extra" cartridges out there which weren't + detected properly because the don't strictly conform to the + QIC-80, Rev. N, spec. + +===== Release notes for ftape-3.03, 30/04/97 ===== + +- Removed kernel integration code from the package. I plan to provide + a package that can be integrated into the stock kernel separately + (hopefully soon). + As a result, a simple `make' command now will build everything. +- ALL compile time configuration options have been moved to the file + `MCONFIG'. +- Quite a few `low level' changes to allow formatting of cartridges. +- formatting is implemented as a separate module `ftape-format.o'. The + modified `mt' program contains sample code that shows how to use it. +- The VFS interface has been moved from the `ftape.o' module to the + high level modules `zftape.o' resp. `sftape.o'. `ftape.o' contains + the hardware support only. +- A bit of /proc support for kernels > 2.1.28 +- Moved documentation to Doc subdir. INSTALL now contains some real + installation notes. +- `install' target in Makefile. + +zftape specific: +---------------- + +- zftape works for large cartridges now ( > 2^31 bytes) +- MTIOCVOLINFO and MTIOCGETSIZE now return the size in KILOBYTES, + NO LONGER in bytes. + +- permissions for write access to a cartridge have changed: + * zftape now also takes the file access mode into account + * zftape no longer allows writing in the middle of the recorded + media. The tape has to be positioned at BOT or EOD for write + access. + +- MTBSF has changed. It used to position at the beginning of the + previous file when called with count 1. This was different from the + expected behavior for other Un*x tape drivers (i.e. SCSI). MTBSF + with count 1 should merely position at the beginning of the current + volume. Fixed. As a result, `tar --verify' now produces the desired + result: it verifies the last written volume, not the pre-last + written volume. + +- The compression map has vanished --> no need for `mt erase' any + more. Fast seeking in a compressed volume is still be possible, but + takes slightly longer. As a side effect, you may experience an + additional volume showing up in front of all others for old + cartridges. This is the tape volume that holds the compression map. + +- The compression support for zftape has been moved to a separate + module `zft-compressor'. DON'T forget to load it before trying to + read back compressed volumes. The stock `zftape.o' module probes for + the module `zft-compressor' using the kerneld message channel; you + have to install `zft-compressor.o' in a place where modprobe can + find it if you want to use this. + +- New experimental feature that tries to get the broken down GMT time + from user space via a kernel daemon message channel. You need to + compile and start the `kdtime' daemon contained in the contrib + directory to use it. Needed (?) for time stamps in the header + segments and the volume table. + +- variable block size mode via MTSETBLK 0 + +- keep modules locked in memory after the block size has been changed + +sftape specific: +---------------- + +- end of tape handling should be fixed, i.e. multi volume archives + written with `afio' can be read back now. + + +===== Release notes for ftape-3.02a, 09/01/97 ===== + +No big news: +- call zft_init() resp. sft_init() when compiling the entire stuff + into the kernel image. +- fix bug in ftape-setup.c when NO_TRACE_AT_ALL was defined. +- fix bug in sftape-eof.c/zftape-eof.c for old kernels (1.2.*) +- add support for new module interface for recent kernels + +===== Release notes for ftape-3.02, 16/12/96 ===== +- Fixed the `FDC unlock command failed' bug in fdc-io.c. When the FIFO + was already locked when ftape was loaded, ftape failed to unlock it. +- Fixed compilation of `contrib/gnumt'. It now finds `mtio.h' even if + ftape is NOT included into the kernel source tree. +- fc-10.c: include <asm/io.h> for inb() and outb(). +- ftape/sftape/zftape: all global variable now have either a `ftape_', + a `ft_', `sft_', `zft_' or `qic_' prefix to prevent name clashes + with other parts of the kernel when including ftape into the kernel + source tree. +- Kerneld support has changed. `ftape' now searches for a module + `ftape-frontend' when none of the frontend (`sftape' or `zftape') is + loaded. Please refer to the `Installation/Loading ftape' section of + the TeXinfo manual. +- Add load resp. boot-time configuration of ftape. There are now + variables ft_fdc_base, ft_fdc_dma and ft_fdc_irq corresponding to + the former FDC_BASE etc. compile time definitions. One can also use + the kernel command line parameters to configure the driver if it is + compiled into the kernel. Also, the FC-10/FC-20 support is load-time + configurable now as well as the MACH-II hack (ft_probe_fc10, + resp. ft_mach2). Please refer to the section `Installation/Configure + ftape' of the TeXinfo manual. +- I removed the MODVERSIONS option from `Makefile.module'. Let me alone + with ftape and MODVERSIONS unless you include the ftape sources into + the kernel source tree. +- new vendors in `vendors.h': + * HP Colorado T3000 + * ComByte DoublePlay (including a bug fix for their broken + formatting software, thanks to whraven@njackn.com) + * Iomega DITTO 2GIG. NOTE: this drive cannot work with ftape because + the logical data layout of the cartridges used by this drive does + NOT conform to the QIC standards, it is a special Iomega specific + format. I've sent mail to Iomega but didn't receive an answer + yet. If you want this drive to be supported by ftape, ask Iomega + to give me information about it. +- zftape: + * re-introduced the MTIOC_ZFTAPE_GETBLKSZ ioctl for compatibility + with zftape 1.06a and earlier. Please don't use it when writing + new software, use the MTIOCVOLINFO ioctl instead. + * Major overhaul of the code that updates the header segments. Never + change the tape label unless erasing the tape. Thus we almost + never need to write the header segments, unless we would modify + the bad sector map which isn't done yet. Updating of volume table + and compression map more secure now although it takes a bit + longer. + * Fixed bug when aborting a write operation with a signal: zftape + now finishes the current volume (i.e. writes an eof marker) at the + current position. It didn't before which led to somehow *strange* + behavior in this cases. + * Keep module locked in memory when using it with the non-rewinding + devices and the tape is not logical at BOT. Needed for kerneld + support. +- sftape: + * Keep module locked in memory when using it with the non-rewinding + devices and the tape is not logical at BOT. Needed for kerneld + support. + +===== Release notes for ftape-3.01, 14/11/96 ===== + +- Fixed silly bugs in ftape-3.00: + * MAKEDEV.ftape: major device number must be 27, not 23 + * sftape/sftape-read.c: sftape_read_header_segments() called + itself recursively instead of calling ftape_read_header_segment() + * zftape/qic-vtbl.h: conversion of ftape's file marks to zftape's + internal volume table was broken. + * patches/2.x.x/linux-2.0.21.dif: my RCS (resp. CVS) system replaced + the `$Revison:' etc. macros in the `ftape.h' concerning part of the + patch :-( Fixed. + * info/ftape.info: Fixed misspellings (`cp' <-> `cp -r' etc.) + * when ftape/sftape or ftape/zftape was compiled into the kernel the + variable ftape_status was declared twice. Fixed. + * removed reference to undeclared variable kernel_version when not + compiling as module + * fixed a bug introduced by the use of bit-fields for some flags + (i.e. write_protected, no_cartridge, formatted) + * flag `header_read' is now reset correctly to zero when tape is + removed. +- fixed a bug in sftape/sftape-eof.c that was already in the original + ftape code. MTFSF/BSF was not handled correctly when positioned + right before the file mark (think of tar) +- Changed TRACE macros (following a suggestion of Marcin Dalecki) to use + the predefined __FUNCTION__ macro of GCC. Spares about 4k of code. +- added new vendor id for Iomega DITTO 2GIG +- fixed a bug already present in zftape-1.06 when aborting a write + with a signal: we now finish the current volume at that + position. Header segments remain NOT up to date until an explicit call + to MTREW or MTOFFL is done. + +===== Release notes for ftape-3.00, 14/10/96 ===== + +- Merged ftape with zftape. There are three modules now: + ftape for the hardware support, sftape for the implementation of the + original ftape eof mark stuff and zftape that implements zftape's way + of handling things (compression, volume table, tape blocks of + constant length) +- Documentation in TeXinfo format in the `info' subdirectory. +- New ioctls for zftape. See zftape/zftape.h +- Dummy formatting ioctl for ftape. See ftape.h +- Kernel patch files for the 2.*.* series to include ftape-3.00 in the + kernel source tree. These includes a kernel compatible Config.in + script and fairly large online information for the kernel configure + script. +- Support for compiling with Linux-1.2.13. +- Modified GNU mt from their cpio package that can handle the new + ioctls. +- ftape/sftape/zftape is kerneld save now! + +Notes on sftape: +- sftape implements the eof handling code of the original ftape. If + you like to stick with the original ftape stuff, you have to use + this module, not zftape. +- sftape is kerneld save, unlike the original ftape. +- we keep the entire header segment now in memory, so no need to read + it before updating the header segments. Additional memory + consumption: 256 bytes. + +Notes for zftape: +- zftape has support for tapes with format code 6 now, which use a + slightly different volume table format compared with other floppy + tapes. +- new ioctls for zftape. Have a look at zftape/zftape.h +- The internal volume table representation has changed for zftape. Old + cartridges are converted automatically. +- zftape no longer uses compression map segments, which have vanished + from the QIC specs, but creates volume table entry that reserves + enough space for the compression map. +- zftape is kerneld save now. +- we keep the entire header segment now in memory, so no need to read + it before updating the header segments. Additional memory + consumption: 256 bytes. + +Notes for contrib/gnumt: +- modified mt from the GNU cpio package that supports all the new + ioctls of zftape. +Notes for contrib/swapout: +- This contains the swapout.c program that was written by Kai + Harrekilde-Pederson. I simply added a Makefile. + +===== Release notes for ftape-2.10, 14/10/96 ===== + +The ftape maintainer has changed. +Kai Harrekilde-Petersen <khp@dolphinics.no> +has resigned from maintaining ftape, and I, +Claus-Justus Heine <claus@momo.math.rwth-aachen.de>, +have taken over. + +- Added support for tapes with `format code 6', i.e. QIC-3020 tapes + with more than 2^16 segments. +- merged changes made by Bas Laarhoven with ftape-2.09. Refer + to his release notes below. I've included them into this + file unchanged for your reference. +- disabled call stack back trace for now. This new feature + introduced by the interim release 2.0.x still seems to + be buggy. +- Tried to minimize differences between the ftape version + to be included into the kernel source tree and the standalone + module version. +- Reintroduced support for Linux-1.2.13. Please refer to the + Install-guide. + +===== Release notes for ftape-2.09, 16/06/96 ===== + +There aren't any really big news in this release, mostly just that I +(the maintainer) have changed my email address (due to a new job). My +new address is <khp@dolphinics.no> + +- The CLK_48MHZ and FDC_82078SL options has gone (all 2Mbps cards seem + to use a 48MHz oscillator anyway and I haven't heard of an 'SL + chip out there). +- The S82078B has been `downgraded' to i82077AA compability. +- TESTING option revived. Right now, it'll enable the (seriously broken) + 2Mbps code. If you enable it, you'll experience a tape drive that's + *really* out to lunch! +- Some (bold) changes in the init code. Please notify me if they + break things for you. + +===== Release notes for ftape-2.08, 14/03/96 ===== + +If you correct a problem with ftape, please send your patch to +khp@dolphinics.no too. + +- Updated to reflect that NR_MEM_LISTS is gone in 1.3.74 +- Teac 700 added to list of known drives. +- The registered device name is now "ft" rather than "ftape". + +===== Release notes for ftape-2.07a, 14/03/96 ===== + +Bugfixes by Marcin Dalecki <dalecki@namu03.gwdg.de>: +- In the last release it just compiled against 1.3.70; + now the params to request_irq() and free_irq are() are fixed, so it also + works in 1.3.73 :-) +- Support for modules is now correct for newer kernels. + +===== 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. +- 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 necessary. +- 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@dolphinics.no> + +- 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@dolphinics.no) 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 slightly 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 developed 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 graceful + 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 risk 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. +---- + LocalWords: ftape MCONFIG mt VFS zftape resp sftape proc subdir MTIOCVOLINFO + LocalWords: MTIOCGETSIZE BOT EOD MTBSF zft kerneld modprobe kdtime contrib TR + LocalWords: MTSETBLK afio uninstall texi www EIO QIC init sft eof aka dma GB + LocalWords: SIGKILL MTIOCFTCMD mmap Iomega FDC fdc io gnumt mtio fc asm inb + LocalWords: outb ft qic frontend TeXinfo irq mach MODVERSIONS CONFIG html dvi + LocalWords: usr doc SMP Mb Dunno FIXME vtblc perl listtape volinfo fsf MTWEOF + LocalWords: amanda degaussed ComByte DoublePlay whraven njackn com MTIOC vtbl + LocalWords: GETBLKSZ MAKEDEV zftape's linux dif CVS Revison cp MTREW MTOFFL + LocalWords: MTFSF BSF Marcin Dalecki GCC Config cpio swapout Kai Harrekilde + LocalWords: Pederson khp dolphinics Justus claus momo rwth aachen Laarhoven diff --git a/drivers/char/ftape/compressor/Makefile b/drivers/char/ftape/compressor/Makefile new file mode 100644 index 000000000000..1fbd6c4019db --- /dev/null +++ b/drivers/char/ftape/compressor/Makefile @@ -0,0 +1,31 @@ +# +# Copyright (C) 1997 Claus-Justus Heine. +# +# 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: /homes/cvs/ftape-stacked/ftape/compressor/Makefile,v $ +# $Revision: 1.1 $ +# $Date: 1997/10/05 19:12:28 $ +# +# Makefile for the optional compressor for th zftape VFS +# interface to the QIC-40/80/3010/3020 floppy-tape driver for +# Linux. +# + +obj-$(CONFIG_ZFT_COMPRESSOR) += zft-compressor.o + +zft-compressor-objs := zftape-compress.o lzrw3.o + +CFLAGS_lzrw3.o := -O6 -funroll-all-loops diff --git a/drivers/char/ftape/compressor/lzrw3.c b/drivers/char/ftape/compressor/lzrw3.c new file mode 100644 index 000000000000..a032a0ee2a99 --- /dev/null +++ b/drivers/char/ftape/compressor/lzrw3.c @@ -0,0 +1,743 @@ +/* + * $Source: /homes/cvs/ftape-stacked/ftape/compressor/lzrw3.c,v $ + * $Revision: 1.1 $ + * $Date: 1997/10/05 19:12:29 $ + * + * Implementation of Ross Williams lzrw3 algorithm. Adaption for zftape. + * + */ + +#include "../compressor/lzrw3.h" /* Defines single exported function "compress". */ + +/******************************************************************************/ +/* */ +/* LZRW3.C */ +/* */ +/******************************************************************************/ +/* */ +/* Author : Ross Williams. */ +/* Date : 30-Jun-1991. */ +/* Release : 1. */ +/* */ +/******************************************************************************/ +/* */ +/* This file contains an implementation of the LZRW3 data compression */ +/* algorithm in C. */ +/* */ +/* The algorithm is a general purpose compression algorithm that runs fast */ +/* and gives reasonable compression. The algorithm is a member of the Lempel */ +/* Ziv family of algorithms and bases its compression on the presence in the */ +/* data of repeated substrings. */ +/* */ +/* This algorithm is unpatented and the code is public domain. As the */ +/* algorithm is based on the LZ77 class of algorithms, it is unlikely to be */ +/* the subject of a patent challenge. */ +/* */ +/* Unlike the LZRW1 and LZRW1-A algorithms, the LZRW3 algorithm is */ +/* deterministic and is guaranteed to yield the same compressed */ +/* representation for a given file each time it is run. */ +/* */ +/* The LZRW3 algorithm was originally designed and implemented */ +/* by Ross Williams on 31-Dec-1990. */ +/* */ +/* Here are the results of applying this code, compiled under THINK C 4.0 */ +/* and running on a Mac-SE (8MHz 68000), to the standard calgary corpus. */ +/* */ +/* +----------------------------------------------------------------+ */ +/* | DATA COMPRESSION TEST | */ +/* | ===================== | */ +/* | Time of run : Sun 30-Jun-1991 09:31PM | */ +/* | Timing accuracy : One part in 100 | */ +/* | Context length : 262144 bytes (= 256.0000K) | */ +/* | Test suite : Calgary Corpus Suite | */ +/* | Files in suite : 14 | */ +/* | Algorithm : LZRW3 | */ +/* | Note: All averages are calculated from the un-rounded values. | */ +/* +----------------------------------------------------------------+ */ +/* | File Name Length CxB ComLen %Remn Bits Com K/s Dec K/s | */ +/* | ---------- ------ --- ------ ----- ---- ------- ------- | */ +/* | rpus:Bib.D 111261 1 55033 49.5 3.96 19.46 32.27 | */ +/* | us:Book1.D 768771 3 467962 60.9 4.87 17.03 31.07 | */ +/* | us:Book2.D 610856 3 317102 51.9 4.15 19.39 34.15 | */ +/* | rpus:Geo.D 102400 1 82424 80.5 6.44 11.65 18.18 | */ +/* | pus:News.D 377109 2 205670 54.5 4.36 17.14 27.47 | */ +/* | pus:Obj1.D 21504 1 13027 60.6 4.85 13.40 18.95 | */ +/* | pus:Obj2.D 246814 1 116286 47.1 3.77 19.31 30.10 | */ +/* | s:Paper1.D 53161 1 27522 51.8 4.14 18.60 31.15 | */ +/* | s:Paper2.D 82199 1 45160 54.9 4.40 18.45 32.84 | */ +/* | rpus:Pic.D 513216 2 122388 23.8 1.91 35.29 51.05 | */ +/* | us:Progc.D 39611 1 19669 49.7 3.97 18.87 30.64 | */ +/* | us:Progl.D 71646 1 28247 39.4 3.15 24.34 40.66 | */ +/* | us:Progp.D 49379 1 19377 39.2 3.14 23.91 39.23 | */ +/* | us:Trans.D 93695 1 33481 35.7 2.86 25.48 40.37 | */ +/* +----------------------------------------------------------------+ */ +/* | Average 224401 1 110953 50.0 4.00 20.17 32.72 | */ +/* +----------------------------------------------------------------+ */ +/* */ +/******************************************************************************/ + +/******************************************************************************/ + +/* The following structure is returned by the "compress" function below when */ +/* the user asks the function to return identifying information. */ +/* The most important field in the record is the working memory field which */ +/* tells the calling program how much working memory should be passed to */ +/* "compress" when it is called to perform a compression or decompression. */ +/* LZRW3 uses the same amount of memory during compression and decompression. */ +/* For more information on this structure see "compress.h". */ + +#define U(X) ((ULONG) X) +#define SIZE_P_BYTE (U(sizeof(UBYTE *))) +#define SIZE_WORD (U(sizeof(UWORD ))) +#define ALIGNMENT_FUDGE (U(16)) +#define MEM_REQ ( U(4096)*(SIZE_P_BYTE) + ALIGNMENT_FUDGE ) + +static struct compress_identity identity = +{ + U(0x032DDEA8), /* Algorithm identification number. */ + MEM_REQ, /* Working memory (bytes) required. */ + "LZRW3", /* Name of algorithm. */ + "1.0", /* Version number of algorithm. */ + "31-Dec-1990", /* Date of algorithm. */ + "Public Domain", /* Copyright notice. */ + "Ross N. Williams", /* Author of algorithm. */ + "Renaissance Software", /* Affiliation of author. */ + "Public Domain" /* Vendor of algorithm. */ +}; + +LOCAL void compress_compress (UBYTE *,UBYTE *,ULONG,UBYTE *, LONG *); +LOCAL void compress_decompress(UBYTE *,UBYTE *,LONG, UBYTE *, ULONG *); + +/******************************************************************************/ + +/* This function is the only function exported by this module. */ +/* Depending on its first parameter, the function can be requested to */ +/* compress a block of memory, decompress a block of memory, or to identify */ +/* itself. For more information, see the specification file "compress.h". */ + +EXPORT void lzrw3_compress( + UWORD action, /* Action to be performed. */ + UBYTE *wrk_mem, /* Address of working memory we can use.*/ + UBYTE *src_adr, /* Address of input data. */ + LONG src_len, /* Length of input data. */ + UBYTE *dst_adr, /* Address to put output data. */ + void *p_dst_len /* Address of longword for length of output data.*/ +) +{ + switch (action) + { + case COMPRESS_ACTION_IDENTITY: + *((struct compress_identity **)p_dst_len)= &identity; + break; + case COMPRESS_ACTION_COMPRESS: + compress_compress(wrk_mem,src_adr,src_len,dst_adr,(LONG *)p_dst_len); + break; + case COMPRESS_ACTION_DECOMPRESS: + compress_decompress(wrk_mem,src_adr,src_len,dst_adr,(LONG *)p_dst_len); + break; + } +} + +/******************************************************************************/ +/* */ +/* BRIEF DESCRIPTION OF THE LZRW3 ALGORITHM */ +/* ======================================== */ +/* The LZRW3 algorithm is identical to the LZRW1-A algorithm except that */ +/* instead of transmitting history offsets, it transmits hash table indexes. */ +/* In order to decode the indexes, the decompressor must maintain an */ +/* identical hash table. Copy items are straightforward:when the decompressor */ +/* receives a copy item, it simply looks up the hash table to translate the */ +/* index into a pointer into the data already decompressed. To update the */ +/* hash table, it replaces the same table entry with a pointer to the start */ +/* of the newly decoded phrase. The tricky part is with literal items, for at */ +/* the time that the decompressor receives a literal item the decompressor */ +/* does not have the three bytes in the Ziv (that the compressor has) to */ +/* perform the three-byte hash. To solve this problem, in LZRW3, both the */ +/* compressor and decompressor are wired up so that they "buffer" these */ +/* literals and update their hash tables only when three bytes are available. */ +/* This makes the maximum buffering 2 bytes. */ +/* */ +/* Replacement of offsets by hash table indexes yields a few percent extra */ +/* compression at the cost of some speed. LZRW3 is slower than LZRW1, LZRW1-A */ +/* and LZRW2, but yields better compression. */ +/* */ +/* Extra compression could be obtained by using a hash table of depth two. */ +/* However, increasing the depth above one incurs a significant decrease in */ +/* compression speed which was not considered worthwhile. Another reason for */ +/* keeping the depth down to one was to allow easy comparison with the */ +/* LZRW1-A and LZRW2 algorithms so as to demonstrate the exact effect of the */ +/* use of direct hash indexes. */ +/* */ +/* +---+ */ +/* |___|4095 */ +/* |___| */ +/* +---------------------*_|<---+ /----+---\ */ +/* | |___| +---|Hash | */ +/* | |___| |Function| */ +/* | |___| \--------/ */ +/* | |___|0 ^ */ +/* | +---+ | */ +/* | Hash +-----+ */ +/* | Table | */ +/* | --- */ +/* v ^^^ */ +/* +-------------------------------------|----------------+ */ +/* |||||||||||||||||||||||||||||||||||||||||||||||||||||||| */ +/* +-------------------------------------|----------------+ */ +/* | |1......18| | */ +/* |<------- Lempel=History ------------>|<--Ziv-->| | */ +/* | (=bytes already processed) |<-Still to go-->| */ +/* |<-------------------- INPUT BLOCK ------------------->| */ +/* */ +/* The diagram above for LZRW3 looks almost identical to the diagram for */ +/* LZRW1. The difference is that in LZRW3, the compressor transmits hash */ +/* table indices instead of Lempel offsets. For this to work, the */ +/* decompressor must maintain a hash table as well as the compressor and both */ +/* compressor and decompressor must "buffer" literals, as the decompressor */ +/* cannot hash phrases commencing with a literal until another two bytes have */ +/* arrived. */ +/* */ +/* LZRW3 Algorithm Execution Summary */ +/* --------------------------------- */ +/* 1. Hash the first three bytes of the Ziv to yield a hash table index h. */ +/* 2. Look up the hash table yielding history pointer p. */ +/* 3. Match where p points with the Ziv. If there is a match of three or */ +/* more bytes, code those bytes (in the Ziv) as a copy item, otherwise */ +/* code the next byte in the Ziv as a literal item. */ +/* 4. Update the hash table as possible subject to the constraint that only */ +/* phrases commencing three bytes back from the Ziv can be hashed and */ +/* entered into the hash table. (This enables the decompressor to keep */ +/* pace). See the description and code for more details. */ +/* */ +/******************************************************************************/ +/* */ +/* DEFINITION OF COMPRESSED FILE FORMAT */ +/* ==================================== */ +/* * A compressed file consists of a COPY FLAG followed by a REMAINDER. */ +/* * The copy flag CF uses up four bytes with the first byte being the */ +/* least significant. */ +/* * If CF=1, then the compressed file represents the remainder of the file */ +/* exactly. Otherwise CF=0 and the remainder of the file consists of zero */ +/* or more GROUPS, each of which represents one or more bytes. */ +/* * Each group consists of two bytes of CONTROL information followed by */ +/* sixteen ITEMs except for the last group which can contain from one */ +/* to sixteen items. */ +/* * An item can be either a LITERAL item or a COPY item. */ +/* * Each item corresponds to a bit in the control bytes. */ +/* * The first control byte corresponds to the first 8 items in the group */ +/* with bit 0 corresponding to the first item in the group and bit 7 to */ +/* the eighth item in the group. */ +/* * The second control byte corresponds to the second 8 items in the group */ +/* with bit 0 corresponding to the ninth item in the group and bit 7 to */ +/* the sixteenth item in the group. */ +/* * A zero bit in a control word means that the corresponding item is a */ +/* literal item. A one bit corresponds to a copy item. */ +/* * A literal item consists of a single byte which represents itself. */ +/* * A copy item consists of two bytes that represent from 3 to 18 bytes. */ +/* * The first byte in a copy item will be denoted C1. */ +/* * The second byte in a copy item will be denoted C2. */ +/* * Bits will be selected using square brackets. */ +/* For example: C1[0..3] is the low nibble of the first control byte. */ +/* of copy item C1. */ +/* * The LENGTH of a copy item is defined to be C1[0..3]+3 which is a number */ +/* in the range [3,18]. */ +/* * The INDEX of a copy item is defined to be C1[4..7]*256+C2[0..8] which */ +/* is a number in the range [0,4095]. */ +/* * A copy item represents the sequence of bytes */ +/* text[POS-OFFSET..POS-OFFSET+LENGTH-1] where */ +/* text is the entire text of the uncompressed string. */ +/* POS is the index in the text of the character following the */ +/* string represented by all the items preceeding the item */ +/* being defined. */ +/* OFFSET is obtained from INDEX by looking up the hash table. */ +/* */ +/******************************************************************************/ + +/* The following #define defines the length of the copy flag that appears at */ +/* the start of the compressed file. The value of four bytes was chosen */ +/* because the fast_copy routine on my Macintosh runs faster if the source */ +/* and destination blocks are relatively longword aligned. */ +/* The actual flag data appears in the first byte. The rest are zeroed so as */ +/* to normalize the compressed representation (i.e. not non-deterministic). */ +#define FLAG_BYTES 4 + +/* The following #defines define the meaning of the values of the copy */ +/* flag at the start of the compressed file. */ +#define FLAG_COMPRESS 0 /* Signals that output was result of compression. */ +#define FLAG_COPY 1 /* Signals that output was simply copied over. */ + +/* The 68000 microprocessor (on which this algorithm was originally developed */ +/* is fussy about non-aligned arrays of words. To avoid these problems the */ +/* following macro can be used to "waste" from 0 to 3 bytes so as to align */ +/* the argument pointer. */ +#define ULONG_ALIGN_UP(X) ((((ULONG)X)+sizeof(ULONG)-1)&~(sizeof(ULONG)-1)) + + +/* The following constant defines the maximum length of an uncompressed item. */ +/* This definition must not be changed; its value is hardwired into the code. */ +/* The longest number of bytes that can be spanned by a single item is 18 */ +/* for the longest copy item. */ +#define MAX_RAW_ITEM (18) + +/* The following constant defines the maximum length of an uncompressed group.*/ +/* This definition must not be changed; its value is hardwired into the code. */ +/* A group contains at most 16 items which explains this definition. */ +#define MAX_RAW_GROUP (16*MAX_RAW_ITEM) + +/* The following constant defines the maximum length of a compressed group. */ +/* This definition must not be changed; its value is hardwired into the code. */ +/* A compressed group consists of two control bytes followed by up to 16 */ +/* compressed items each of which can have a maximum length of two bytes. */ +#define MAX_CMP_GROUP (2+16*2) + +/* The following constant defines the number of entries in the hash table. */ +/* This definition must not be changed; its value is hardwired into the code. */ +#define HASH_TABLE_LENGTH (4096) + +/* LZRW3, unlike LZRW1(-A), must initialize its hash table so as to enable */ +/* the compressor and decompressor to stay in step maintaining identical hash */ +/* tables. In an early version of the algorithm, the tables were simply */ +/* initialized to zero and a check for zero was included just before the */ +/* matching code. However, this test costs time. A better solution is to */ +/* initialize all the entries in the hash table to point to a constant */ +/* string. The decompressor does the same. This solution requires no extra */ +/* test. The contents of the string do not matter so long as the string is */ +/* the same for the compressor and decompressor and contains at least */ +/* MAX_RAW_ITEM bytes. I chose consecutive decimal digits because they do not */ +/* have white space problems (e.g. there is no chance that the compiler will */ +/* replace more than one space by a TAB) and because they make the length of */ +/* the string obvious by inspection. */ +#define START_STRING_18 ((UBYTE *) "123456789012345678") + +/* In this algorithm, hash values have to be calculated at more than one */ +/* point. The following macro neatens the code up for this. */ +#define HASH(PTR) \ + (((40543*(((*(PTR))<<8)^((*((PTR)+1))<<4)^(*((PTR)+2))))>>4) & 0xFFF) + +/******************************************************************************/ + +/* Input : Hand over the required amount of working memory in p_wrk_mem. */ +/* Input : Specify input block using p_src_first and src_len. */ +/* Input : Point p_dst_first to the start of the output zone (OZ). */ +/* Input : Point p_dst_len to a ULONG to receive the output length. */ +/* Input : Input block and output zone must not overlap. */ +/* Output : Length of output block written to *p_dst_len. */ +/* Output : Output block in Mem[p_dst_first..p_dst_first+*p_dst_len-1]. May */ +/* Output : write in OZ=Mem[p_dst_first..p_dst_first+src_len+MAX_CMP_GROUP-1].*/ +/* Output : Upon completion guaranteed *p_dst_len<=src_len+FLAG_BYTES. */ +LOCAL void compress_compress(UBYTE *p_wrk_mem, + UBYTE *p_src_first, ULONG src_len, + UBYTE *p_dst_first, LONG *p_dst_len) +{ + /* p_src and p_dst step through the source and destination blocks. */ + register UBYTE *p_src = p_src_first; + register UBYTE *p_dst = p_dst_first; + + /* The following variables are never modified and are used in the */ + /* calculations that determine when the main loop terminates. */ + UBYTE *p_src_post = p_src_first+src_len; + UBYTE *p_dst_post = p_dst_first+src_len; + UBYTE *p_src_max1 = p_src_first+src_len-MAX_RAW_ITEM; + UBYTE *p_src_max16 = p_src_first+src_len-MAX_RAW_ITEM*16; + + /* The variables 'p_control' and 'control' are used to buffer control bits. */ + /* Before each group is processed, the next two bytes of the output block */ + /* are set aside for the control word for the group about to be processed. */ + /* 'p_control' is set to point to the first byte of that word. Meanwhile, */ + /* 'control' buffers the control bits being generated during the processing */ + /* of the group. Instead of having a counter to keep track of how many items */ + /* have been processed (=the number of bits in the control word), at the */ + /* start of each group, the top word of 'control' is filled with 1 bits. */ + /* As 'control' is shifted for each item, the 1 bits in the top word are */ + /* absorbed or destroyed. When they all run out (i.e. when the top word is */ + /* all zero bits, we know that we are at the end of a group. */ +# define TOPWORD 0xFFFF0000 + UBYTE *p_control; + register ULONG control=TOPWORD; + + /* THe variable 'hash' always points to the first element of the hash table. */ + UBYTE **hash= (UBYTE **) ULONG_ALIGN_UP(p_wrk_mem); + + /* The following two variables represent the literal buffer. p_h1 points to */ + /* the hash table entry corresponding to the youngest literal. p_h2 points */ + /* to the hash table entry corresponding to the second youngest literal. */ + /* Note: p_h1=0=>p_h2=0 because zero values denote absence of a pending */ + /* literal. The variables are initialized to zero meaning an empty "buffer". */ + UBYTE **p_h1=NULL; + UBYTE **p_h2=NULL; + + /* To start, we write the flag bytes. Being optimistic, we set the flag to */ + /* FLAG_COMPRESS. The remaining flag bytes are zeroed so as to keep the */ + /* algorithm deterministic. */ + *p_dst++=FLAG_COMPRESS; + {UWORD i; for (i=2;i<=FLAG_BYTES;i++) *p_dst++=0;} + + /* Reserve the first word of output as the control word for the first group. */ + /* Note: This is undone at the end if the input block is empty. */ + p_control=p_dst; p_dst+=2; + + /* Initialize all elements of the hash table to point to a constant string. */ + /* Use of an unrolled loop speeds this up considerably. */ + {UWORD i; UBYTE **p_h=hash; +# define ZH *p_h++=START_STRING_18 + for (i=0;i<256;i++) /* 256=HASH_TABLE_LENGTH/16. */ + {ZH;ZH;ZH;ZH; + ZH;ZH;ZH;ZH; + ZH;ZH;ZH;ZH; + ZH;ZH;ZH;ZH;} + } + + /* The main loop processes either 1 or 16 items per iteration. As its */ + /* termination logic is complicated, I have opted for an infinite loop */ + /* structure containing 'break' and 'goto' statements. */ + while (TRUE) + {/* Begin main processing loop. */ + + /* Note: All the variables here except unroll should be defined within */ + /* the inner loop. Unfortunately the loop hasn't got a block. */ + register UBYTE *p; /* Scans through targ phrase during matching. */ + register UBYTE *p_ziv= NULL ; /* Points to first byte of current Ziv. */ + register UWORD unroll; /* Loop counter for unrolled inner loop. */ + register UWORD index; /* Index of current hash table entry. */ + register UBYTE **p_h0 = NULL ; /* Pointer to current hash table entry. */ + + /* Test for overrun and jump to overrun code if necessary. */ + if (p_dst>p_dst_post) + goto overrun; + + /* The following cascade of if statements efficiently catches and deals */ + /* with varying degrees of closeness to the end of the input block. */ + /* When we get very close to the end, we stop updating the table and */ + /* code the remaining bytes as literals. This makes the code simpler. */ + unroll=16; + if (p_src>p_src_max16) + { + unroll=1; + if (p_src>p_src_max1) + { + if (p_src==p_src_post) + break; + else + goto literal; + } + } + + /* This inner unrolled loop processes 'unroll' (whose value is either 1 */ + /* or 16) items. I have chosen to implement this loop with labels and */ + /* gotos to heighten the ease with which the loop may be implemented with */ + /* a single decrement and branch instruction in assembly language and */ + /* also because the labels act as highly readable place markers. */ + /* (Also because we jump into the loop for endgame literals (see above)). */ + + begin_unrolled_loop: + + /* To process the next phrase, we hash the next three bytes and use */ + /* the resultant hash table index to look up the hash table. A pointer */ + /* to the entry is stored in p_h0 so as to avoid an array lookup. The */ + /* hash table entry *p_h0 is looked up yielding a pointer p to a */ + /* potential match of the Ziv in the history. */ + index=HASH(p_src); + p_h0=&hash[index]; + p=*p_h0; + + /* Having looked up the candidate position, we are in a position to */ + /* attempt a match. The match loop has been unrolled using the PS */ + /* macro so that failure within the first three bytes automatically */ + /* results in the literal branch being taken. The coding is simple. */ + /* p_ziv saves p_src so we can let p_src wander. */ +# define PS *p++!=*p_src++ + p_ziv=p_src; + if (PS || PS || PS) + { + /* Literal. */ + + /* Code the literal byte as itself and a zero control bit. */ + p_src=p_ziv; literal: *p_dst++=*p_src++; control&=0xFFFEFFFF; + + /* We have just coded a literal. If we had two pending ones, that */ + /* makes three and we can update the hash table. */ + if (p_h2!=0) + {*p_h2=p_ziv-2;} + + /* In any case, rotate the hash table pointers for next time. */ + p_h2=p_h1; p_h1=p_h0; + + } + else + { + /* Copy */ + + /* Match up to 15 remaining bytes using an unrolled loop and code. */ +#if 0 + PS || PS || PS || PS || PS || PS || PS || PS || + PS || PS || PS || PS || PS || PS || PS || p_src++; +#else + if ( + !( PS || PS || PS || PS || PS || PS || PS || PS || + PS || PS || PS || PS || PS || PS || PS ) + ) p_src++; +#endif + *p_dst++=((index&0xF00)>>4)|(--p_src-p_ziv-3); + *p_dst++=index&0xFF; + + /* As we have just coded three bytes, we are now in a position to */ + /* update the hash table with the literal bytes that were pending */ + /* upon the arrival of extra context bytes. */ + if (p_h1!=0) + { + if (p_h2) + {*p_h2=p_ziv-2; p_h2=NULL;} + *p_h1=p_ziv-1; p_h1=NULL; + } + + /* In any case, we can update the hash table based on the current */ + /* position as we just coded at least three bytes in a copy items. */ + *p_h0=p_ziv; + + } + control>>=1; + + /* This loop is all set up for a decrement and jump instruction! */ +#ifndef linux +` end_unrolled_loop: if (--unroll) goto begin_unrolled_loop; +#else + /* end_unrolled_loop: */ if (--unroll) goto begin_unrolled_loop; +#endif + + /* At this point it will nearly always be the end of a group in which */ + /* case, we have to do some control-word processing. However, near the */ + /* end of the input block, the inner unrolled loop is only executed once. */ + /* This necessitates the 'if' test. */ + if ((control&TOPWORD)==0) + { + /* Write the control word to the place we saved for it in the output. */ + *p_control++= control &0xFF; + *p_control = (control>>8) &0xFF; + + /* Reserve the next word in the output block for the control word */ + /* for the group about to be processed. */ + p_control=p_dst; p_dst+=2; + + /* Reset the control bits buffer. */ + control=TOPWORD; + } + + } /* End main processing loop. */ + + /* After the main processing loop has executed, all the input bytes have */ + /* been processed. However, the control word has still to be written to the */ + /* word reserved for it in the output at the start of the most recent group. */ + /* Before writing, the control word has to be shifted so that all the bits */ + /* are in the right place. The "empty" bit positions are filled with 1s */ + /* which partially fill the top word. */ + while(control&TOPWORD) control>>=1; + *p_control++= control &0xFF; + *p_control++=(control>>8) &0xFF; + + /* If the last group contained no items, delete the control word too. */ + if (p_control==p_dst) p_dst-=2; + + /* Write the length of the output block to the dst_len parameter and return. */ + *p_dst_len=p_dst-p_dst_first; + return; + + /* Jump here as soon as an overrun is detected. An overrun is defined to */ + /* have occurred if p_dst>p_dst_first+src_len. That is, the moment the */ + /* length of the output written so far exceeds the length of the input block.*/ + /* The algorithm checks for overruns at least at the end of each group */ + /* which means that the maximum overrun is MAX_CMP_GROUP bytes. */ + /* Once an overrun occurs, the only thing to do is to set the copy flag and */ + /* copy the input over. */ + overrun: +#if 0 + *p_dst_first=FLAG_COPY; + fast_copy(p_src_first,p_dst_first+FLAG_BYTES,src_len); + *p_dst_len=src_len+FLAG_BYTES; +#else + fast_copy(p_src_first,p_dst_first,src_len); + *p_dst_len= -src_len; /* return a negative number to indicate uncompressed data */ +#endif +} + +/******************************************************************************/ + +/* Input : Hand over the required amount of working memory in p_wrk_mem. */ +/* Input : Specify input block using p_src_first and src_len. */ +/* Input : Point p_dst_first to the start of the output zone. */ +/* Input : Point p_dst_len to a ULONG to receive the output length. */ +/* Input : Input block and output zone must not overlap. User knows */ +/* Input : upperbound on output block length from earlier compression. */ +/* Input : In any case, maximum expansion possible is nine times. */ +/* Output : Length of output block written to *p_dst_len. */ +/* Output : Output block in Mem[p_dst_first..p_dst_first+*p_dst_len-1]. */ +/* Output : Writes only in Mem[p_dst_first..p_dst_first+*p_dst_len-1]. */ +LOCAL void compress_decompress( UBYTE *p_wrk_mem, + UBYTE *p_src_first, LONG src_len, + UBYTE *p_dst_first, ULONG *p_dst_len) +{ + /* Byte pointers p_src and p_dst scan through the input and output blocks. */ + register UBYTE *p_src = p_src_first+FLAG_BYTES; + register UBYTE *p_dst = p_dst_first; + /* we need to avoid a SEGV when trying to uncompress corrupt data */ + register UBYTE *p_dst_post = p_dst_first + *p_dst_len; + + /* The following two variables are never modified and are used to control */ + /* the main loop. */ + UBYTE *p_src_post = p_src_first+src_len; + UBYTE *p_src_max16 = p_src_first+src_len-(MAX_CMP_GROUP-2); + + /* The hash table is the only resident of the working memory. The hash table */ + /* contains HASH_TABLE_LENGTH=4096 pointers to positions in the history. To */ + /* keep Macintoshes happy, it is longword aligned. */ + UBYTE **hash = (UBYTE **) ULONG_ALIGN_UP(p_wrk_mem); + + /* The variable 'control' is used to buffer the control bits which appear in */ + /* groups of 16 bits (control words) at the start of each compressed group. */ + /* When each group is read, bit 16 of the register is set to one. Whenever */ + /* a new bit is needed, the register is shifted right. When the value of the */ + /* register becomes 1, we know that we have reached the end of a group. */ + /* Initializing the register to 1 thus instructs the code to follow that it */ + /* should read a new control word immediately. */ + register ULONG control=1; + + /* The value of 'literals' is always in the range 0..3. It is the number of */ + /* consecutive literal items just seen. We have to record this number so as */ + /* to know when to update the hash table. When literals gets to 3, there */ + /* have been three consecutive literals and we can update at the position of */ + /* the oldest of the three. */ + register UWORD literals=0; + + /* Check the leading copy flag to see if the compressor chose to use a copy */ + /* operation instead of a compression operation. If a copy operation was */ + /* used, then all we need to do is copy the data over, set the output length */ + /* and return. */ +#if 0 + if (*p_src_first==FLAG_COPY) + { + fast_copy(p_src_first+FLAG_BYTES,p_dst_first,src_len-FLAG_BYTES); + *p_dst_len=src_len-FLAG_BYTES; + return; + } +#else + if ( src_len < 0 ) + { + fast_copy(p_src_first,p_dst_first,-src_len ); + *p_dst_len = (ULONG)-src_len; + return; + } +#endif + + /* Initialize all elements of the hash table to point to a constant string. */ + /* Use of an unrolled loop speeds this up considerably. */ + {UWORD i; UBYTE **p_h=hash; +# define ZJ *p_h++=START_STRING_18 + for (i=0;i<256;i++) /* 256=HASH_TABLE_LENGTH/16. */ + {ZJ;ZJ;ZJ;ZJ; + ZJ;ZJ;ZJ;ZJ; + ZJ;ZJ;ZJ;ZJ; + ZJ;ZJ;ZJ;ZJ;} + } + + /* The outer loop processes either 1 or 16 items per iteration depending on */ + /* how close p_src is to the end of the input block. */ + while (p_src!=p_src_post) + {/* Start of outer loop */ + + register UWORD unroll; /* Counts unrolled loop executions. */ + + /* When 'control' has the value 1, it means that the 16 buffered control */ + /* bits that were read in at the start of the current group have all been */ + /* shifted out and that all that is left is the 1 bit that was injected */ + /* into bit 16 at the start of the current group. When we reach the end */ + /* of a group, we have to load a new control word and inject a new 1 bit. */ + if (control==1) + { + control=0x10000|*p_src++; + control|=(*p_src++)<<8; + } + + /* If it is possible that we are within 16 groups from the end of the */ + /* input, execute the unrolled loop only once, else process a whole group */ + /* of 16 items by looping 16 times. */ + unroll= p_src<=p_src_max16 ? 16 : 1; + + /* This inner loop processes one phrase (item) per iteration. */ + while (unroll--) + { /* Begin unrolled inner loop. */ + + /* Process a literal or copy item depending on the next control bit. */ + if (control&1) + { + /* Copy item. */ + + register UBYTE *p; /* Points to place from which to copy. */ + register UWORD lenmt; /* Length of copy item minus three. */ + register UBYTE **p_hte; /* Pointer to current hash table entry.*/ + register UBYTE *p_ziv=p_dst; /* Pointer to start of current Ziv. */ + + /* Read and dismantle the copy word. Work out from where to copy. */ + lenmt=*p_src++; + p_hte=&hash[((lenmt&0xF0)<<4)|*p_src++]; + p=*p_hte; + lenmt&=0xF; + + /* Now perform the copy using a half unrolled loop. */ + *p_dst++=*p++; + *p_dst++=*p++; + *p_dst++=*p++; + while (lenmt--) + *p_dst++=*p++; + + /* Because we have just received 3 or more bytes in a copy item */ + /* (whose bytes we have just installed in the output), we are now */ + /* in a position to flush all the pending literal hashings that had */ + /* been postponed for lack of bytes. */ + if (literals>0) + { + register UBYTE *r=p_ziv-literals; + hash[HASH(r)]=r; + if (literals==2) + {r++; hash[HASH(r)]=r;} + literals=0; + } + + /* In any case, we can immediately update the hash table with the */ + /* current position. We don't need to do a HASH(...) to work out */ + /* where to put the pointer, as the compressor just told us!!! */ + *p_hte=p_ziv; + + } + else + { + /* Literal item. */ + + /* Copy over the literal byte. */ + *p_dst++=*p_src++; + + /* If we now have three literals waiting to be hashed into the hash */ + /* table, we can do one of them now (because there are three). */ + if (++literals == 3) + {register UBYTE *p=p_dst-3; hash[HASH(p)]=p; literals=2;} + } + + /* Shift the control buffer so the next control bit is in bit 0. */ + control>>=1; +#if 1 + if (p_dst > p_dst_post) + { + /* Shit: we tried to decompress corrupt data */ + *p_dst_len = 0; + return; + } +#endif + } /* End unrolled inner loop. */ + + } /* End of outer loop */ + + /* Write the length of the decompressed data before returning. */ + *p_dst_len=p_dst-p_dst_first; +} + +/******************************************************************************/ +/* End of LZRW3.C */ +/******************************************************************************/ diff --git a/drivers/char/ftape/compressor/lzrw3.h b/drivers/char/ftape/compressor/lzrw3.h new file mode 100644 index 000000000000..533feba47526 --- /dev/null +++ b/drivers/char/ftape/compressor/lzrw3.h @@ -0,0 +1,253 @@ +#ifndef _LZRW3_H +#define _LZRW3_H +/* + * $Source: /homes/cvs/ftape-stacked/ftape/compressor/lzrw3.h,v $ + * $Revision: 1.1 $ + * $Date: 1997/10/05 19:12:30 $ + * + * include files for lzrw3. Only slighty modified from the original + * version. Assembles the three include files compress.h, port.h and + * fastcopy.h from the original lzrw3 package. + * + */ + +#include <linux/types.h> +#include <linux/string.h> + +/******************************************************************************/ +/* */ +/* COMPRESS.H */ +/* */ +/******************************************************************************/ +/* */ +/* Author : Ross Williams. */ +/* Date : December 1989. */ +/* */ +/* This header file defines the interface to a set of functions called */ +/* 'compress', each member of which implements a particular data compression */ +/* algorithm. */ +/* */ +/* Normally in C programming, for each .H file, there is a corresponding .C */ +/* file that implements the functions promised in the .H file. */ +/* Here, there are many .C files corresponding to this header file. */ +/* Each comforming implementation file contains a single function */ +/* called 'compress' that implements a single data compression */ +/* algorithm that conforms with the interface specified in this header file. */ +/* Only one algorithm can be linked in at a time in this organization. */ +/* */ +/******************************************************************************/ +/* */ +/* DEFINITION OF FUNCTION COMPRESS */ +/* =============================== */ +/* */ +/* Summary of Function Compress */ +/* ---------------------------- */ +/* The action that 'compress' takes depends on its first argument called */ +/* 'action'. The function provides three actions: */ +/* */ +/* - Return information about the algorithm. */ +/* - Compress a block of memory. */ +/* - Decompress a block of memory. */ +/* */ +/* Parameters */ +/* ---------- */ +/* See the formal C definition later for a description of the parameters. */ +/* */ +/* Constants */ +/* --------- */ +/* COMPRESS_OVERRUN: The constant COMPRESS_OVERRUN defines by how many bytes */ +/* an algorithm is allowed to expand a block during a compression operation. */ +/* */ +/* Although compression algorithms usually compress data, there will always */ +/* be data that a given compressor will expand (this can be proven). */ +/* Fortunately, the degree of expansion can be limited to a single bit, by */ +/* copying over the input data if the data gets bigger during compression. */ +/* To allow for this possibility, the first bit of a compressed */ +/* representation can be used as a flag indicating whether the */ +/* input data was copied over, or truly compressed. In practice, the first */ +/* byte would be used to store this bit so as to maintain byte alignment. */ +/* */ +/* Unfortunately, in general, the only way to tell if an algorithm will */ +/* expand a particular block of data is to run the algorithm on the data. */ +/* If the algorithm does not continuously monitor how many output bytes it */ +/* has written, it might write an output block far larger than the input */ +/* block before realizing that it has done so. */ +/* On the other hand, continuous checks on output length are inefficient. */ +/* */ +/* To cater for all these problems, this interface definition: */ +/* > Allows a compression algorithm to return an output block that is up to */ +/* COMPRESS_OVERRUN bytes longer than the input block. */ +/* > Allows a compression algorithm to write up to COMPRESS_OVERRUN bytes */ +/* more than the length of the input block to the memory of the output */ +/* block regardless of the length of the output block eventually returned. */ +/* This allows an algorithm to overrun the length of the input block in the */ +/* output block by up to COMPRESS_OVERRUN bytes between expansion checks. */ +/* */ +/* The problem does not arise for decompression. */ +/* */ +/* Identity Action */ +/* --------------- */ +/* > action must be COMPRESS_ACTION_IDENTITY. */ +/* > p_dst_len must point to a longword to receive a longword address. */ +/* > The value of the other parameters does not matter. */ +/* > After execution, the longword that p_dst_len points to will be a pointer */ +/* to a structure of type compress_identity. */ +/* Thus, for example, after the call, (*p_dst_len)->memory will return the */ +/* number of bytes of working memory that the algorithm requires to run. */ +/* > The values of the identity structure returned are fixed constant */ +/* attributes of the algorithm and must not vary from call to call. */ +/* */ +/* Common Requirements for Compression and Decompression Actions */ +/* ------------------------------------------------------------- */ +/* > wrk_mem must point to an unused block of memory of a length specified in */ +/* the algorithm's identity block. The identity block can be obtained by */ +/* making a separate call to compress, specifying the identity action. */ +/* > The INPUT BLOCK is defined to be Memory[src_addr,src_addr+src_len-1]. */ +/* > dst_len will be used to denote *p_dst_len. */ +/* > dst_len is not read by compress, only written. */ +/* > The value of dst_len is defined only upon termination. */ +/* > The OUTPUT BLOCK is defined to be Memory[dst_addr,dst_addr+dst_len-1]. */ +/* */ +/* Compression Action */ +/* ------------------ */ +/* > action must be COMPRESS_ACTION_COMPRESS. */ +/* > src_len must be in the range [0,COMPRESS_MAX_ORG]. */ +/* > The OUTPUT ZONE is defined to be */ +/* Memory[dst_addr,dst_addr+src_len-1+COMPRESS_OVERRUN]. */ +/* > The function can modify any part of the output zone regardless of the */ +/* final length of the output block. */ +/* > The input block and the output zone must not overlap. */ +/* > dst_len will be in the range [0,src_len+COMPRESS_OVERRUN]. */ +/* > dst_len will be in the range [0,COMPRESS_MAX_COM] (from prev fact). */ +/* > The output block will consist of a representation of the input block. */ +/* */ +/* Decompression Action */ +/* -------------------- */ +/* > action must be COMPRESS_ACTION_DECOMPRESS. */ +/* > The input block must be the result of an earlier compression operation. */ +/* > If the previous fact is true, the following facts must also be true: */ +/* > src_len will be in the range [0,COMPRESS_MAX_COM]. */ +/* > dst_len will be in the range [0,COMPRESS_MAX_ORG]. */ +/* > The input and output blocks must not overlap. */ +/* > Only the output block is modified. */ +/* > Upon termination, the output block will consist of the bytes contained */ +/* in the input block passed to the earlier compression operation. */ +/* */ +/******************************************************************************/ + +/******************************************************************************/ +/* */ +/* PORT.H */ +/* */ +/******************************************************************************/ +/* */ +/* This module contains macro definitions and types that are likely to */ +/* change between computers. */ +/* */ +/******************************************************************************/ + +#ifndef DONE_PORT /* Only do this if not previously done. */ + + #ifdef THINK_C + #define UBYTE unsigned char /* Unsigned byte */ + #define UWORD unsigned int /* Unsigned word (2 bytes) */ + #define ULONG unsigned long /* Unsigned word (4 bytes) */ + #define BOOL unsigned char /* Boolean */ + #define FOPEN_BINARY_READ "rb" /* Mode string for binary reading. */ + #define FOPEN_BINARY_WRITE "wb" /* Mode string for binary writing. */ + #define FOPEN_TEXT_APPEND "a" /* Mode string for text appending. */ + #define REAL double /* USed for floating point stuff. */ + #endif + #if defined(LINUX) || defined(linux) + #define UBYTE __u8 /* Unsigned byte */ + #define UWORD __u16 /* Unsigned word (2 bytes) */ + #define ULONG __u32 /* Unsigned word (4 bytes) */ + #define LONG __s32 /* Signed word (4 bytes) */ + #define BOOL is not used here /* Boolean */ + #define FOPEN_BINARY_READ not used /* Mode string for binary reading. */ + #define FOPEN_BINARY_WRITE not used /* Mode string for binary writing. */ + #define FOPEN_TEXT_APPEND not used /* Mode string for text appending. */ + #define REAL not used /* USed for floating point stuff. */ + #ifndef TRUE + #define TRUE 1 + #endif + #endif + + #define DONE_PORT /* Don't do all this again. */ + #define MALLOC_FAIL NULL /* Failure status from malloc() */ + #define LOCAL static /* For non-exported routines. */ + #define EXPORT /* Signals exported function. */ + #define then /* Useful for aligning ifs. */ + +#endif + +/******************************************************************************/ +/* End of PORT.H */ +/******************************************************************************/ + +#define COMPRESS_ACTION_IDENTITY 0 +#define COMPRESS_ACTION_COMPRESS 1 +#define COMPRESS_ACTION_DECOMPRESS 2 + +#define COMPRESS_OVERRUN 1024 +#define COMPRESS_MAX_COM 0x70000000 +#define COMPRESS_MAX_ORG (COMPRESS_MAX_COM-COMPRESS_OVERRUN) + +#define COMPRESS_MAX_STRLEN 255 + +/* The following structure provides information about the algorithm. */ +/* > The top bit of id must be zero. The remaining bits must be chosen by */ +/* the author of the algorithm by tossing a coin 31 times. */ +/* > The amount of memory requested by the algorithm is specified in bytes */ +/* and must be in the range [0,0x70000000]. */ +/* > All strings s must be such that strlen(s)<=COMPRESS_MAX_STRLEN. */ +struct compress_identity + { + ULONG id; /* Identifying number of algorithm. */ + ULONG memory; /* Number of bytes of working memory required. */ + + char *name; /* Name of algorithm. */ + char *version; /* Version number. */ + char *date; /* Date of release of this version. */ + char *copyright; /* Copyright message. */ + + char *author; /* Author of algorithm. */ + char *affiliation; /* Affiliation of author. */ + char *vendor; /* Where the algorithm can be obtained. */ + }; + +void lzrw3_compress( /* Single function interface to compression algorithm. */ +UWORD action, /* Action to be performed. */ +UBYTE *wrk_mem, /* Working memory temporarily given to routine to use. */ +UBYTE *src_adr, /* Address of input data. */ +LONG src_len, /* Length of input data. */ +UBYTE *dst_adr, /* Address of output data. */ +void *p_dst_len /* Pointer to a longword where routine will write: */ + /* If action=..IDENTITY => Adr of id structure. */ + /* If action=..COMPRESS => Length of output data. */ + /* If action=..DECOMPRESS => Length of output data. */ +); + +/******************************************************************************/ +/* End of COMPRESS.H */ +/******************************************************************************/ + + +/******************************************************************************/ +/* fast_copy.h */ +/******************************************************************************/ + +/* This function copies a block of memory very quickly. */ +/* The exact speed depends on the relative alignment of the blocks of memory. */ +/* PRE : 0<=src_len<=(2^32)-1 . */ +/* PRE : Source and destination blocks must not overlap. */ +/* POST : MEM[dst_adr,dst_adr+src_len-1]=MEM[src_adr,src_adr+src_len-1]. */ +/* POST : MEM[dst_adr,dst_adr+src_len-1] is the only memory changed. */ + +#define fast_copy(src,dst,len) memcpy(dst,src,len) + +/******************************************************************************/ +/* End of fast_copy.h */ +/******************************************************************************/ + +#endif diff --git a/drivers/char/ftape/compressor/zftape-compress.c b/drivers/char/ftape/compressor/zftape-compress.c new file mode 100644 index 000000000000..220a227e6061 --- /dev/null +++ b/drivers/char/ftape/compressor/zftape-compress.c @@ -0,0 +1,1203 @@ +/* + * Copyright (C) 1994-1997 Claus-Justus Heine + + 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 implements a "generic" interface between the * + * zftape-driver and a compression-algorithm. The * + * compression-algorithm currently used is a LZ77. I use the * + * implementation lzrw3 by Ross N. Williams (Renaissance * + * Software). The compression program itself is in the file + * lzrw3.c * and lzrw3.h. To adopt another compression algorithm + * the functions * zft_compress() and zft_uncompress() must be + * changed * appropriately. See below. + */ + +#include <linux/errno.h> +#include <linux/mm.h> +#include <linux/module.h> + +#include <linux/zftape.h> + +#include <asm/uaccess.h> + +#include "../zftape/zftape-init.h" +#include "../zftape/zftape-eof.h" +#include "../zftape/zftape-ctl.h" +#include "../zftape/zftape-write.h" +#include "../zftape/zftape-read.h" +#include "../zftape/zftape-rw.h" +#include "../compressor/zftape-compress.h" +#include "../zftape/zftape-vtbl.h" +#include "../compressor/lzrw3.h" + +/* + * global variables + */ + +/* I handle the allocation of this buffer as a special case, because + * it's size varies depending on the tape length inserted. + */ + +/* local variables + */ +static void *zftc_wrk_mem = NULL; +static __u8 *zftc_buf = NULL; +static void *zftc_scratch_buf = NULL; + +/* compression statistics + */ +static unsigned int zftc_wr_uncompressed = 0; +static unsigned int zftc_wr_compressed = 0; +static unsigned int zftc_rd_uncompressed = 0; +static unsigned int zftc_rd_compressed = 0; + +/* forward */ +static int zftc_write(int *write_cnt, + __u8 *dst_buf, const int seg_sz, + const __u8 __user *src_buf, const int req_len, + const zft_position *pos, const zft_volinfo *volume); +static int zftc_read(int *read_cnt, + __u8 __user *dst_buf, const int to_do, + const __u8 *src_buf, const int seg_sz, + const zft_position *pos, const zft_volinfo *volume); +static int zftc_seek(unsigned int new_block_pos, + zft_position *pos, const zft_volinfo *volume, + __u8 *buffer); +static void zftc_lock (void); +static void zftc_reset (void); +static void zftc_cleanup(void); +static void zftc_stats (void); + +/* compressed segment. This conforms to QIC-80-MC, Revision K. + * + * Rev. K applies to tapes with `fixed length format' which is + * indicated by format code 2,3 and 5. See below for format code 4 and 6 + * + * 2 bytes: offset of compression segment structure + * 29k > offset >= 29k-18: data from previous segment ens in this + * segment and no compressed block starts + * in this segment + * offset == 0: data from previous segment occupies entire + * segment and continues in next segment + * n bytes: remainder from previous segment + * + * Rev. K: + * 4 bytes: 4 bytes: files set byte offset + * Post Rev. K and QIC-3020/3020: + * 8 bytes: 8 bytes: files set byte offset + * 2 bytes: byte count N (amount of data following) + * bit 15 is set if data is compressed, bit 15 is not + * set if data is uncompressed + * N bytes: data (as much as specified in the byte count) + * 2 bytes: byte count N_1 of next cluster + * N_1 bytes: data of next cluset + * 2 bytes: byte count N_2 of next cluster + * N_2 bytes: ... + * + * Note that the `N' byte count accounts only for the bytes that in the + * current segment if the cluster spans to the next segment. + */ + +typedef struct +{ + int cmpr_pos; /* actual position in compression buffer */ + int cmpr_sz; /* what is left in the compression buffer + * when copying the compressed data to the + * deblock buffer + */ + unsigned int first_block; /* location of header information in + * this segment + */ + unsigned int count; /* amount of data of current block + * contained in current segment + */ + unsigned int offset; /* offset in current segment */ + unsigned int spans:1; /* might continue in next segment */ + unsigned int uncmpr; /* 0x8000 if this block contains + * uncompressed data + */ + __s64 foffs; /* file set byte offset, same as in + * compression map segment + */ +} cmpr_info; + +static cmpr_info cseg; /* static data. Must be kept uptodate and shared by + * read, write and seek functions + */ + +#define DUMP_CMPR_INFO(level, msg, info) \ + TRACE(level, msg "\n" \ + KERN_INFO "cmpr_pos : %d\n" \ + KERN_INFO "cmpr_sz : %d\n" \ + KERN_INFO "first_block: %d\n" \ + KERN_INFO "count : %d\n" \ + KERN_INFO "offset : %d\n" \ + KERN_INFO "spans : %d\n" \ + KERN_INFO "uncmpr : 0x%04x\n" \ + KERN_INFO "foffs : " LL_X, \ + (info)->cmpr_pos, (info)->cmpr_sz, (info)->first_block, \ + (info)->count, (info)->offset, (info)->spans == 1, \ + (info)->uncmpr, LL((info)->foffs)) + +/* dispatch compression segment info, return error code + * + * afterwards, cseg->offset points to start of data of the NEXT + * compressed block, and cseg->count contains the amount of data + * left in the actual compressed block. cseg->spans is set to 1 if + * the block is continued in the following segment. Otherwise it is + * set to 0. + */ +static int get_cseg (cmpr_info *cinfo, const __u8 *buff, + const unsigned int seg_sz, + const zft_volinfo *volume) +{ + TRACE_FUN(ft_t_flow); + + cinfo->first_block = GET2(buff, 0); + if (cinfo->first_block == 0) { /* data spans to next segment */ + cinfo->count = seg_sz - sizeof(__u16); + cinfo->offset = seg_sz; + cinfo->spans = 1; + } else { /* cluster definetely ends in this segment */ + if (cinfo->first_block > seg_sz) { + /* data corrupted */ + TRACE_ABORT(-EIO, ft_t_err, "corrupted data:\n" + KERN_INFO "segment size: %d\n" + KERN_INFO "first block : %d", + seg_sz, cinfo->first_block); + } + cinfo->count = cinfo->first_block - sizeof(__u16); + cinfo->offset = cinfo->first_block; + cinfo->spans = 0; + } + /* now get the offset the first block should have in the + * uncompressed data stream. + * + * For this magic `18' refer to CRF-3 standard or QIC-80MC, + * Rev. K. + */ + if ((seg_sz - cinfo->offset) > 18) { + if (volume->qic113) { /* > revision K */ + TRACE(ft_t_data_flow, "New QIC-113 compliance"); + cinfo->foffs = GET8(buff, cinfo->offset); + cinfo->offset += sizeof(__s64); + } else { + TRACE(/* ft_t_data_flow */ ft_t_noise, "pre QIC-113 version"); + cinfo->foffs = (__s64)GET4(buff, cinfo->offset); + cinfo->offset += sizeof(__u32); + } + } + if (cinfo->foffs > volume->size) { + TRACE_ABORT(-EIO, ft_t_err, "Inconsistency:\n" + KERN_INFO "offset in current volume: %d\n" + KERN_INFO "size of current volume : %d", + (int)(cinfo->foffs>>10), (int)(volume->size>>10)); + } + if (cinfo->cmpr_pos + cinfo->count > volume->blk_sz) { + TRACE_ABORT(-EIO, ft_t_err, "Inconsistency:\n" + KERN_INFO "block size : %d\n" + KERN_INFO "data record: %d", + volume->blk_sz, cinfo->cmpr_pos + cinfo->count); + } + DUMP_CMPR_INFO(ft_t_noise /* ft_t_any */, "", cinfo); + TRACE_EXIT 0; +} + +/* This one is called, when a new cluster starts in same segment. + * + * Note: if this is the first cluster in the current segment, we must + * not check whether there are more than 18 bytes available because + * this have already been done in get_cseg() and there may be less + * than 18 bytes available due to header information. + * + */ +static void get_next_cluster(cmpr_info *cluster, const __u8 *buff, + const int seg_sz, const int finish) +{ + TRACE_FUN(ft_t_flow); + + if (seg_sz - cluster->offset > 18 || cluster->foffs != 0) { + cluster->count = GET2(buff, cluster->offset); + cluster->uncmpr = cluster->count & 0x8000; + cluster->count -= cluster->uncmpr; + cluster->offset += sizeof(__u16); + cluster->foffs = 0; + if ((cluster->offset + cluster->count) < seg_sz) { + cluster->spans = 0; + } else if (cluster->offset + cluster->count == seg_sz) { + cluster->spans = !finish; + } else { + /* either an error or a volume written by an + * old version. If this is a data error, then we'll + * catch it later. + */ + TRACE(ft_t_data_flow, "Either error or old volume"); + cluster->spans = 1; + cluster->count = seg_sz - cluster->offset; + } + } else { + cluster->count = 0; + cluster->spans = 0; + cluster->foffs = 0; + } + DUMP_CMPR_INFO(ft_t_noise /* ft_t_any */ , "", cluster); + TRACE_EXIT; +} + +static void zftc_lock(void) +{ +} + +/* this function is needed for zftape_reset_position in zftape-io.c + */ +static void zftc_reset(void) +{ + TRACE_FUN(ft_t_flow); + + memset((void *)&cseg, '\0', sizeof(cseg)); + zftc_stats(); + TRACE_EXIT; +} + +static int cmpr_mem_initialized = 0; +static unsigned int alloc_blksz = 0; + +static int zft_allocate_cmpr_mem(unsigned int blksz) +{ + TRACE_FUN(ft_t_flow); + + if (cmpr_mem_initialized && blksz == alloc_blksz) { + TRACE_EXIT 0; + } + TRACE_CATCH(zft_vmalloc_once(&zftc_wrk_mem, CMPR_WRK_MEM_SIZE), + zftc_cleanup()); + TRACE_CATCH(zft_vmalloc_always(&zftc_buf, blksz + CMPR_OVERRUN), + zftc_cleanup()); + alloc_blksz = blksz; + TRACE_CATCH(zft_vmalloc_always(&zftc_scratch_buf, blksz+CMPR_OVERRUN), + zftc_cleanup()); + cmpr_mem_initialized = 1; + TRACE_EXIT 0; +} + +static void zftc_cleanup(void) +{ + TRACE_FUN(ft_t_flow); + + zft_vfree(&zftc_wrk_mem, CMPR_WRK_MEM_SIZE); + zft_vfree(&zftc_buf, alloc_blksz + CMPR_OVERRUN); + zft_vfree(&zftc_scratch_buf, alloc_blksz + CMPR_OVERRUN); + cmpr_mem_initialized = alloc_blksz = 0; + TRACE_EXIT; +} + +/***************************************************************************** + * * + * The following two functions "ftape_compress()" and * + * "ftape_uncompress()" are the interface to the actual compression * + * algorithm (i.e. they are calling the "compress()" function from * + * the lzrw3 package for now). These routines could quite easily be * + * changed to adopt another compression algorithm instead of lzrw3, * + * which currently is used. * + * * + *****************************************************************************/ + +/* called by zft_compress_write() to perform the compression. Must + * return the size of the compressed data. + * + * NOTE: The size of the compressed data should not exceed the size of + * the uncompressed data. Most compression algorithms have means + * to store data unchanged if the "compressed" data amount would + * exceed the original one. Mostly this is done by storing some + * flag-bytes in front of the compressed data to indicate if it + * is compressed or not. Thus the worst compression result + * length is the original length plus those flag-bytes. + * + * We don't want that, as the QIC-80 standard provides a means + * of marking uncompressed blocks by simply setting bit 15 of + * the compressed block's length. Thus a compessed block can + * have at most a length of 2^15-1 bytes. The QIC-80 standard + * restricts the block-length even further, allowing only 29k - + * 6 bytes. + * + * Currently, the maximum blocksize used by zftape is 28k. + * + * In short: don't exceed the length of the input-package, set + * bit 15 of the compressed size to 1 if you have copied data + * instead of compressing it. + */ +static int zft_compress(__u8 *in_buffer, unsigned int in_sz, __u8 *out_buffer) +{ + __s32 compressed_sz; + TRACE_FUN(ft_t_flow); + + + lzrw3_compress(COMPRESS_ACTION_COMPRESS, zftc_wrk_mem, + in_buffer, in_sz, out_buffer, &compressed_sz); + if (TRACE_LEVEL >= ft_t_info) { + /* the compiler will optimize this away when + * compiled with NO_TRACE_AT_ALL option + */ + TRACE(ft_t_data_flow, "\n" + KERN_INFO "before compression: %d bytes\n" + KERN_INFO "after compresison : %d bytes", + in_sz, + (int)(compressed_sz < 0 + ? -compressed_sz : compressed_sz)); + /* for statistical purposes + */ + zftc_wr_compressed += (compressed_sz < 0 + ? -compressed_sz : compressed_sz); + zftc_wr_uncompressed += in_sz; + } + TRACE_EXIT (int)compressed_sz; +} + +/* called by zft_compress_read() to decompress the data. Must + * return the size of the decompressed data for sanity checks + * (compared with zft_blk_sz) + * + * NOTE: Read the note for zft_compress() above! If bit 15 of the + * parameter in_sz is set, then the data in in_buffer isn't + * compressed, which must be handled by the un-compression + * algorithm. (I changed lzrw3 to handle this.) + * + * The parameter max_out_sz is needed to prevent buffer overruns when + * uncompressing corrupt data. + */ +static unsigned int zft_uncompress(__u8 *in_buffer, + int in_sz, + __u8 *out_buffer, + unsigned int max_out_sz) +{ + TRACE_FUN(ft_t_flow); + + lzrw3_compress(COMPRESS_ACTION_DECOMPRESS, zftc_wrk_mem, + in_buffer, (__s32)in_sz, + out_buffer, (__u32 *)&max_out_sz); + + if (TRACE_LEVEL >= ft_t_info) { + TRACE(ft_t_data_flow, "\n" + KERN_INFO "before decompression: %d bytes\n" + KERN_INFO "after decompression : %d bytes", + in_sz < 0 ? -in_sz : in_sz,(int)max_out_sz); + /* for statistical purposes + */ + zftc_rd_compressed += in_sz < 0 ? -in_sz : in_sz; + zftc_rd_uncompressed += max_out_sz; + } + TRACE_EXIT (unsigned int)max_out_sz; +} + +/* print some statistics about the efficiency of the compression to + * the kernel log + */ +static void zftc_stats(void) +{ + TRACE_FUN(ft_t_flow); + + if (TRACE_LEVEL < ft_t_info) { + TRACE_EXIT; + } + if (zftc_wr_uncompressed != 0) { + if (zftc_wr_compressed > (1<<14)) { + TRACE(ft_t_info, "compression statistics (writing):\n" + KERN_INFO " compr./uncmpr. : %3d %%", + (((zftc_wr_compressed>>10) * 100) + / (zftc_wr_uncompressed>>10))); + } else { + TRACE(ft_t_info, "compression statistics (writing):\n" + KERN_INFO " compr./uncmpr. : %3d %%", + ((zftc_wr_compressed * 100) + / zftc_wr_uncompressed)); + } + } + if (zftc_rd_uncompressed != 0) { + if (zftc_rd_compressed > (1<<14)) { + TRACE(ft_t_info, "compression statistics (reading):\n" + KERN_INFO " compr./uncmpr. : %3d %%", + (((zftc_rd_compressed>>10) * 100) + / (zftc_rd_uncompressed>>10))); + } else { + TRACE(ft_t_info, "compression statistics (reading):\n" + KERN_INFO " compr./uncmpr. : %3d %%", + ((zftc_rd_compressed * 100) + / zftc_rd_uncompressed)); + } + } + /* only print it once: */ + zftc_wr_uncompressed = + zftc_wr_compressed = + zftc_rd_uncompressed = + zftc_rd_compressed = 0; + TRACE_EXIT; +} + +/* start new compressed block + */ +static int start_new_cseg(cmpr_info *cluster, + char *dst_buf, + const zft_position *pos, + const unsigned int blk_sz, + const char *src_buf, + const int this_segs_sz, + const int qic113) +{ + int size_left; + int cp_cnt; + int buf_pos; + TRACE_FUN(ft_t_flow); + + size_left = this_segs_sz - sizeof(__u16) - cluster->cmpr_sz; + TRACE(ft_t_data_flow,"\n" + KERN_INFO "segment size : %d\n" + KERN_INFO "compressed_sz: %d\n" + KERN_INFO "size_left : %d", + this_segs_sz, cluster->cmpr_sz, size_left); + if (size_left > 18) { /* start a new cluseter */ + cp_cnt = cluster->cmpr_sz; + cluster->cmpr_sz = 0; + buf_pos = cp_cnt + sizeof(__u16); + PUT2(dst_buf, 0, buf_pos); + + if (qic113) { + __s64 foffs = pos->volume_pos; + if (cp_cnt) foffs += (__s64)blk_sz; + + TRACE(ft_t_data_flow, "new style QIC-113 header"); + PUT8(dst_buf, buf_pos, foffs); + buf_pos += sizeof(__s64); + } else { + __u32 foffs = (__u32)pos->volume_pos; + if (cp_cnt) foffs += (__u32)blk_sz; + + TRACE(ft_t_data_flow, "old style QIC-80MC header"); + PUT4(dst_buf, buf_pos, foffs); + buf_pos += sizeof(__u32); + } + } else if (size_left >= 0) { + cp_cnt = cluster->cmpr_sz; + cluster->cmpr_sz = 0; + buf_pos = cp_cnt + sizeof(__u16); + PUT2(dst_buf, 0, buf_pos); + /* zero unused part of segment. */ + memset(dst_buf + buf_pos, '\0', size_left); + buf_pos = this_segs_sz; + } else { /* need entire segment and more space */ + PUT2(dst_buf, 0, 0); + cp_cnt = this_segs_sz - sizeof(__u16); + cluster->cmpr_sz -= cp_cnt; + buf_pos = this_segs_sz; + } + memcpy(dst_buf + sizeof(__u16), src_buf + cluster->cmpr_pos, cp_cnt); + cluster->cmpr_pos += cp_cnt; + TRACE_EXIT buf_pos; +} + +/* return-value: the number of bytes removed from the user-buffer + * `src_buf' or error code + * + * int *write_cnt : how much actually has been moved to the + * dst_buf. Need not be initialized when + * function returns with an error code + * (negativ return value) + * __u8 *dst_buf : kernel space buffer where the has to be + * copied to. The contents of this buffers + * goes to a specific segment. + * const int seg_sz : the size of the segment dst_buf will be + * copied to. + * const zft_position *pos : struct containing the coordinates in + * the current volume (byte position, + * segment id of current segment etc) + * const zft_volinfo *volume: information about the current volume, + * size etc. + * const __u8 *src_buf : user space buffer that contains the + * data the user wants to be written to + * tape. + * const int req_len : the amount of data the user wants to be + * written to tape. + */ +static int zftc_write(int *write_cnt, + __u8 *dst_buf, const int seg_sz, + const __u8 __user *src_buf, const int req_len, + const zft_position *pos, const zft_volinfo *volume) +{ + int req_len_left = req_len; + int result; + int len_left; + int buf_pos_write = pos->seg_byte_pos; + TRACE_FUN(ft_t_flow); + + /* Note: we do not unlock the module because + * there are some values cached in that `cseg' variable. We + * don't don't want to use this information when being + * unloaded by kerneld even when the tape is full or when we + * cannot allocate enough memory. + */ + if (pos->tape_pos > (volume->size-volume->blk_sz-ZFT_CMPR_OVERHEAD)) { + TRACE_EXIT -ENOSPC; + } + if (zft_allocate_cmpr_mem(volume->blk_sz) < 0) { + /* should we unlock the module? But it shouldn't + * be locked anyway ... + */ + TRACE_EXIT -ENOMEM; + } + if (buf_pos_write == 0) { /* fill a new segment */ + *write_cnt = buf_pos_write = start_new_cseg(&cseg, + dst_buf, + pos, + volume->blk_sz, + zftc_buf, + seg_sz, + volume->qic113); + if (cseg.cmpr_sz == 0 && cseg.cmpr_pos != 0) { + req_len_left -= result = volume->blk_sz; + cseg.cmpr_pos = 0; + } else { + result = 0; + } + } else { + *write_cnt = result = 0; + } + + len_left = seg_sz - buf_pos_write; + while ((req_len_left > 0) && (len_left > 18)) { + /* now we have some size left for a new compressed + * block. We know, that the compression buffer is + * empty (else there wouldn't be any space left). + */ + if (copy_from_user(zftc_scratch_buf, src_buf + result, + volume->blk_sz) != 0) { + TRACE_EXIT -EFAULT; + } + req_len_left -= volume->blk_sz; + cseg.cmpr_sz = zft_compress(zftc_scratch_buf, volume->blk_sz, + zftc_buf); + if (cseg.cmpr_sz < 0) { + cseg.uncmpr = 0x8000; + cseg.cmpr_sz = -cseg.cmpr_sz; + } else { + cseg.uncmpr = 0; + } + /* increment "result" iff we copied the entire + * compressed block to the zft_deblock_buf + */ + len_left -= sizeof(__u16); + if (len_left >= cseg.cmpr_sz) { + len_left -= cseg.count = cseg.cmpr_sz; + cseg.cmpr_pos = cseg.cmpr_sz = 0; + result += volume->blk_sz; + } else { + cseg.cmpr_sz -= + cseg.cmpr_pos = + cseg.count = len_left; + len_left = 0; + } + PUT2(dst_buf, buf_pos_write, cseg.uncmpr | cseg.count); + buf_pos_write += sizeof(__u16); + memcpy(dst_buf + buf_pos_write, zftc_buf, cseg.count); + buf_pos_write += cseg.count; + *write_cnt += cseg.count + sizeof(__u16); + FT_SIGNAL_EXIT(_DONT_BLOCK); + } + /* erase the remainder of the segment if less than 18 bytes + * left (18 bytes is due to the QIC-80 standard) + */ + if (len_left <= 18) { + memset(dst_buf + buf_pos_write, '\0', len_left); + (*write_cnt) += len_left; + } + TRACE(ft_t_data_flow, "returning %d", result); + TRACE_EXIT result; +} + +/* out: + * + * int *read_cnt: the number of bytes we removed from the zft_deblock_buf + * (result) + * int *to_do : the remaining size of the read-request. + * + * in: + * + * char *buff : buff is the address of the upper part of the user + * buffer, that hasn't been filled with data yet. + + * int buf_pos_read : copy of from _ftape_read() + * int buf_len_read : copy of buf_len_rd from _ftape_read() + * char *zft_deblock_buf: zft_deblock_buf + * unsigned short blk_sz: the block size valid for this volume, may differ + * from zft_blk_sz. + * int finish: if != 0 means that this is the last segment belonging + * to this volume + * returns the amount of data actually copied to the user-buffer + * + * to_do MUST NOT SHRINK except to indicate an EOF. In this case *to_do has to + * be set to 0 + */ +static int zftc_read (int *read_cnt, + __u8 __user *dst_buf, const int to_do, + const __u8 *src_buf, const int seg_sz, + const zft_position *pos, const zft_volinfo *volume) +{ + int uncompressed_sz; + int result = 0; + int remaining = to_do; + TRACE_FUN(ft_t_flow); + + TRACE_CATCH(zft_allocate_cmpr_mem(volume->blk_sz),); + if (pos->seg_byte_pos == 0) { + /* new segment just read + */ + TRACE_CATCH(get_cseg(&cseg, src_buf, seg_sz, volume), + *read_cnt = 0); + memcpy(zftc_buf + cseg.cmpr_pos, src_buf + sizeof(__u16), + cseg.count); + cseg.cmpr_pos += cseg.count; + *read_cnt = cseg.offset; + DUMP_CMPR_INFO(ft_t_noise /* ft_t_any */, "", &cseg); + } else { + *read_cnt = 0; + } + /* loop and uncompress until user buffer full or + * deblock-buffer empty + */ + TRACE(ft_t_data_flow, "compressed_sz: %d, compos : %d, *read_cnt: %d", + cseg.cmpr_sz, cseg.cmpr_pos, *read_cnt); + while ((cseg.spans == 0) && (remaining > 0)) { + if (cseg.cmpr_pos != 0) { /* cmpr buf is not empty */ + uncompressed_sz = + zft_uncompress(zftc_buf, + cseg.uncmpr == 0x8000 ? + -cseg.cmpr_pos : cseg.cmpr_pos, + zftc_scratch_buf, + volume->blk_sz); + if (uncompressed_sz != volume->blk_sz) { + *read_cnt = 0; + TRACE_ABORT(-EIO, ft_t_warn, + "Uncompressed blk (%d) != blk size (%d)", + uncompressed_sz, volume->blk_sz); + } + if (copy_to_user(dst_buf + result, + zftc_scratch_buf, + uncompressed_sz) != 0 ) { + TRACE_EXIT -EFAULT; + } + remaining -= uncompressed_sz; + result += uncompressed_sz; + cseg.cmpr_pos = 0; + } + if (remaining > 0) { + get_next_cluster(&cseg, src_buf, seg_sz, + volume->end_seg == pos->seg_pos); + if (cseg.count != 0) { + memcpy(zftc_buf, src_buf + cseg.offset, + cseg.count); + cseg.cmpr_pos = cseg.count; + cseg.offset += cseg.count; + *read_cnt += cseg.count + sizeof(__u16); + } else { + remaining = 0; + } + } + TRACE(ft_t_data_flow, "\n" + KERN_INFO "compressed_sz: %d\n" + KERN_INFO "compos : %d\n" + KERN_INFO "*read_cnt : %d", + cseg.cmpr_sz, cseg.cmpr_pos, *read_cnt); + } + if (seg_sz - cseg.offset <= 18) { + *read_cnt += seg_sz - cseg.offset; + TRACE(ft_t_data_flow, "expanding read cnt to: %d", *read_cnt); + } + TRACE(ft_t_data_flow, "\n" + KERN_INFO "segment size : %d\n" + KERN_INFO "read count : %d\n" + KERN_INFO "buf_pos_read : %d\n" + KERN_INFO "remaining : %d", + seg_sz, *read_cnt, pos->seg_byte_pos, + seg_sz - *read_cnt - pos->seg_byte_pos); + TRACE(ft_t_data_flow, "returning: %d", result); + TRACE_EXIT result; +} + +/* seeks to the new data-position. Reads sometimes a segment. + * + * start_seg and end_seg give the boundaries of the current volume + * blk_sz is the blk_sz of the current volume as stored in the + * volume label + * + * We don't allow blocksizes less than 1024 bytes, therefore we don't need + * a 64 bit argument for new_block_pos. + */ + +static int seek_in_segment(const unsigned int to_do, cmpr_info *c_info, + const char *src_buf, const int seg_sz, + const int seg_pos, const zft_volinfo *volume); +static int slow_seek_forward_until_error(const unsigned int distance, + cmpr_info *c_info, zft_position *pos, + const zft_volinfo *volume, __u8 *buf); +static int search_valid_segment(unsigned int segment, + const unsigned int end_seg, + const unsigned int max_foffs, + zft_position *pos, cmpr_info *c_info, + const zft_volinfo *volume, __u8 *buf); +static int slow_seek_forward(unsigned int dest, cmpr_info *c_info, + zft_position *pos, const zft_volinfo *volume, + __u8 *buf); +static int compute_seg_pos(unsigned int dest, zft_position *pos, + const zft_volinfo *volume); + +#define ZFT_SLOW_SEEK_THRESHOLD 10 /* segments */ +#define ZFT_FAST_SEEK_MAX_TRIALS 10 /* times */ +#define ZFT_FAST_SEEK_BACKUP 10 /* segments */ + +static int zftc_seek(unsigned int new_block_pos, + zft_position *pos, const zft_volinfo *volume, __u8 *buf) +{ + unsigned int dest; + int limit; + int distance; + int result = 0; + int seg_dist; + int new_seg; + int old_seg = 0; + int fast_seek_trials = 0; + TRACE_FUN(ft_t_flow); + + if (new_block_pos == 0) { + pos->seg_pos = volume->start_seg; + pos->seg_byte_pos = 0; + pos->volume_pos = 0; + zftc_reset(); + TRACE_EXIT 0; + } + dest = new_block_pos * (volume->blk_sz >> 10); + distance = dest - (pos->volume_pos >> 10); + while (distance != 0) { + seg_dist = compute_seg_pos(dest, pos, volume); + TRACE(ft_t_noise, "\n" + KERN_INFO "seg_dist: %d\n" + KERN_INFO "distance: %d\n" + KERN_INFO "dest : %d\n" + KERN_INFO "vpos : %d\n" + KERN_INFO "seg_pos : %d\n" + KERN_INFO "trials : %d", + seg_dist, distance, dest, + (unsigned int)(pos->volume_pos>>10), pos->seg_pos, + fast_seek_trials); + if (distance > 0) { + if (seg_dist < 0) { + TRACE(ft_t_bug, "BUG: distance %d > 0, " + "segment difference %d < 0", + distance, seg_dist); + result = -EIO; + break; + } + new_seg = pos->seg_pos + seg_dist; + if (new_seg > volume->end_seg) { + new_seg = volume->end_seg; + } + if (old_seg == new_seg || /* loop */ + seg_dist <= ZFT_SLOW_SEEK_THRESHOLD || + fast_seek_trials >= ZFT_FAST_SEEK_MAX_TRIALS) { + TRACE(ft_t_noise, "starting slow seek:\n" + KERN_INFO "fast seek failed too often: %s\n" + KERN_INFO "near target position : %s\n" + KERN_INFO "looping between two segs : %s", + (fast_seek_trials >= + ZFT_FAST_SEEK_MAX_TRIALS) + ? "yes" : "no", + (seg_dist <= ZFT_SLOW_SEEK_THRESHOLD) + ? "yes" : "no", + (old_seg == new_seg) + ? "yes" : "no"); + result = slow_seek_forward(dest, &cseg, + pos, volume, buf); + break; + } + old_seg = new_seg; + limit = volume->end_seg; + fast_seek_trials ++; + for (;;) { + result = search_valid_segment(new_seg, limit, + volume->size, + pos, &cseg, + volume, buf); + if (result == 0 || result == -EINTR) { + break; + } + if (new_seg == volume->start_seg) { + result = -EIO; /* set errror + * condition + */ + break; + } + limit = new_seg; + new_seg -= ZFT_FAST_SEEK_BACKUP; + if (new_seg < volume->start_seg) { + new_seg = volume->start_seg; + } + } + if (result < 0) { + TRACE(ft_t_warn, + "Couldn't find a readable segment"); + break; + } + } else /* if (distance < 0) */ { + if (seg_dist > 0) { + TRACE(ft_t_bug, "BUG: distance %d < 0, " + "segment difference %d >0", + distance, seg_dist); + result = -EIO; + break; + } + new_seg = pos->seg_pos + seg_dist; + if (fast_seek_trials > 0 && seg_dist == 0) { + /* this avoids sticking to the same + * segment all the time. On the other hand: + * if we got here for the first time, and the + * deblock_buffer still contains a valid + * segment, then there is no need to skip to + * the previous segment if the desired position + * is inside this segment. + */ + new_seg --; + } + if (new_seg < volume->start_seg) { + new_seg = volume->start_seg; + } + limit = pos->seg_pos; + fast_seek_trials ++; + for (;;) { + result = search_valid_segment(new_seg, limit, + pos->volume_pos, + pos, &cseg, + volume, buf); + if (result == 0 || result == -EINTR) { + break; + } + if (new_seg == volume->start_seg) { + result = -EIO; /* set errror + * condition + */ + break; + } + limit = new_seg; + new_seg -= ZFT_FAST_SEEK_BACKUP; + if (new_seg < volume->start_seg) { + new_seg = volume->start_seg; + } + } + if (result < 0) { + TRACE(ft_t_warn, + "Couldn't find a readable segment"); + break; + } + } + distance = dest - (pos->volume_pos >> 10); + } + TRACE_EXIT result; +} + + +/* advance inside the given segment at most to_do bytes. + * of kilobytes moved + */ + +static int seek_in_segment(const unsigned int to_do, + cmpr_info *c_info, + const char *src_buf, + const int seg_sz, + const int seg_pos, + const zft_volinfo *volume) +{ + int result = 0; + int blk_sz = volume->blk_sz >> 10; + int remaining = to_do; + TRACE_FUN(ft_t_flow); + + if (c_info->offset == 0) { + /* new segment just read + */ + TRACE_CATCH(get_cseg(c_info, src_buf, seg_sz, volume),); + c_info->cmpr_pos += c_info->count; + DUMP_CMPR_INFO(ft_t_noise, "", c_info); + } + /* loop and uncompress until user buffer full or + * deblock-buffer empty + */ + TRACE(ft_t_noise, "compressed_sz: %d, compos : %d", + c_info->cmpr_sz, c_info->cmpr_pos); + while (c_info->spans == 0 && remaining > 0) { + if (c_info->cmpr_pos != 0) { /* cmpr buf is not empty */ + result += blk_sz; + remaining -= blk_sz; + c_info->cmpr_pos = 0; + } + if (remaining > 0) { + get_next_cluster(c_info, src_buf, seg_sz, + volume->end_seg == seg_pos); + if (c_info->count != 0) { + c_info->cmpr_pos = c_info->count; + c_info->offset += c_info->count; + } else { + break; + } + } + /* Allow escape from this loop on signal! + */ + FT_SIGNAL_EXIT(_DONT_BLOCK); + DUMP_CMPR_INFO(ft_t_noise, "", c_info); + TRACE(ft_t_noise, "to_do: %d", remaining); + } + if (seg_sz - c_info->offset <= 18) { + c_info->offset = seg_sz; + } + TRACE(ft_t_noise, "\n" + KERN_INFO "segment size : %d\n" + KERN_INFO "buf_pos_read : %d\n" + KERN_INFO "remaining : %d", + seg_sz, c_info->offset, + seg_sz - c_info->offset); + TRACE_EXIT result; +} + +static int slow_seek_forward_until_error(const unsigned int distance, + cmpr_info *c_info, + zft_position *pos, + const zft_volinfo *volume, + __u8 *buf) +{ + unsigned int remaining = distance; + int seg_sz; + int seg_pos; + int result; + TRACE_FUN(ft_t_flow); + + seg_pos = pos->seg_pos; + do { + TRACE_CATCH(seg_sz = zft_fetch_segment(seg_pos, buf, + FT_RD_AHEAD),); + /* now we have the contents of the actual segment in + * the deblock buffer + */ + TRACE_CATCH(result = seek_in_segment(remaining, c_info, buf, + seg_sz, seg_pos,volume),); + remaining -= result; + pos->volume_pos += result<<10; + pos->seg_pos = seg_pos; + pos->seg_byte_pos = c_info->offset; + seg_pos ++; + if (seg_pos <= volume->end_seg && c_info->offset == seg_sz) { + pos->seg_pos ++; + pos->seg_byte_pos = 0; + c_info->offset = 0; + } + /* Allow escape from this loop on signal! + */ + FT_SIGNAL_EXIT(_DONT_BLOCK); + TRACE(ft_t_noise, "\n" + KERN_INFO "remaining: %d\n" + KERN_INFO "seg_pos: %d\n" + KERN_INFO "end_seg: %d\n" + KERN_INFO "result: %d", + remaining, seg_pos, volume->end_seg, result); + } while (remaining > 0 && seg_pos <= volume->end_seg); + TRACE_EXIT 0; +} + +/* return segment id of next segment containing valid data, -EIO otherwise + */ +static int search_valid_segment(unsigned int segment, + const unsigned int end_seg, + const unsigned int max_foffs, + zft_position *pos, + cmpr_info *c_info, + const zft_volinfo *volume, + __u8 *buf) +{ + cmpr_info tmp_info; + int seg_sz; + TRACE_FUN(ft_t_flow); + + memset(&tmp_info, 0, sizeof(cmpr_info)); + while (segment <= end_seg) { + FT_SIGNAL_EXIT(_DONT_BLOCK); + TRACE(ft_t_noise, + "Searching readable segment between %d and %d", + segment, end_seg); + seg_sz = zft_fetch_segment(segment, buf, FT_RD_AHEAD); + if ((seg_sz > 0) && + (get_cseg (&tmp_info, buf, seg_sz, volume) >= 0) && + (tmp_info.foffs != 0 || segment == volume->start_seg)) { + if ((tmp_info.foffs>>10) > max_foffs) { + TRACE_ABORT(-EIO, ft_t_noise, "\n" + KERN_INFO "cseg.foff: %d\n" + KERN_INFO "dest : %d", + (int)(tmp_info.foffs >> 10), + max_foffs); + } + DUMP_CMPR_INFO(ft_t_noise, "", &tmp_info); + *c_info = tmp_info; + pos->seg_pos = segment; + pos->volume_pos = c_info->foffs; + pos->seg_byte_pos = c_info->offset; + TRACE(ft_t_noise, "found segment at %d", segment); + TRACE_EXIT 0; + } + segment++; + } + TRACE_EXIT -EIO; +} + +static int slow_seek_forward(unsigned int dest, + cmpr_info *c_info, + zft_position *pos, + const zft_volinfo *volume, + __u8 *buf) +{ + unsigned int distance; + int result = 0; + TRACE_FUN(ft_t_flow); + + distance = dest - (pos->volume_pos >> 10); + while ((distance > 0) && + (result = slow_seek_forward_until_error(distance, + c_info, + pos, + volume, + buf)) < 0) { + if (result == -EINTR) { + break; + } + TRACE(ft_t_noise, "seg_pos: %d", pos->seg_pos); + /* the failing segment is either pos->seg_pos or + * pos->seg_pos + 1. There is no need to further try + * that segment, because ftape_read_segment() already + * has tried very much to read it. So we start with + * following segment, which is pos->seg_pos + 1 + */ + if(search_valid_segment(pos->seg_pos+1, volume->end_seg, dest, + pos, c_info, + volume, buf) < 0) { + TRACE(ft_t_noise, "search_valid_segment() failed"); + result = -EIO; + break; + } + distance = dest - (pos->volume_pos >> 10); + result = 0; + TRACE(ft_t_noise, "segment: %d", pos->seg_pos); + /* found valid segment, retry the seek */ + } + TRACE_EXIT result; +} + +static int compute_seg_pos(const unsigned int dest, + zft_position *pos, + const zft_volinfo *volume) +{ + int segment; + int distance = dest - (pos->volume_pos >> 10); + unsigned int raw_size; + unsigned int virt_size; + unsigned int factor; + TRACE_FUN(ft_t_flow); + + if (distance >= 0) { + raw_size = volume->end_seg - pos->seg_pos + 1; + virt_size = ((unsigned int)(volume->size>>10) + - (unsigned int)(pos->volume_pos>>10) + + FT_SECTORS_PER_SEGMENT - FT_ECC_SECTORS - 1); + virt_size /= FT_SECTORS_PER_SEGMENT - FT_ECC_SECTORS; + if (virt_size == 0 || raw_size == 0) { + TRACE_EXIT 0; + } + if (raw_size >= (1<<25)) { + factor = raw_size/(virt_size>>7); + } else { + factor = (raw_size<<7)/virt_size; + } + segment = distance/(FT_SECTORS_PER_SEGMENT-FT_ECC_SECTORS); + segment = (segment * factor)>>7; + } else { + raw_size = pos->seg_pos - volume->start_seg + 1; + virt_size = ((unsigned int)(pos->volume_pos>>10) + + FT_SECTORS_PER_SEGMENT - FT_ECC_SECTORS - 1); + virt_size /= FT_SECTORS_PER_SEGMENT - FT_ECC_SECTORS; + if (virt_size == 0 || raw_size == 0) { + TRACE_EXIT 0; + } + if (raw_size >= (1<<25)) { + factor = raw_size/(virt_size>>7); + } else { + factor = (raw_size<<7)/virt_size; + } + segment = distance/(FT_SECTORS_PER_SEGMENT-FT_ECC_SECTORS); + } + TRACE(ft_t_noise, "factor: %d/%d", factor, 1<<7); + TRACE_EXIT segment; +} + +static struct zft_cmpr_ops cmpr_ops = { + zftc_write, + zftc_read, + zftc_seek, + zftc_lock, + zftc_reset, + zftc_cleanup +}; + +int zft_compressor_init(void) +{ + TRACE_FUN(ft_t_flow); + +#ifdef MODULE + printk(KERN_INFO "zftape compressor v1.00a 970514 for " FTAPE_VERSION "\n"); + if (TRACE_LEVEL >= ft_t_info) { + printk( +KERN_INFO "(c) 1997 Claus-Justus Heine (claus@momo.math.rwth-aachen.de)\n" +KERN_INFO "Compressor for zftape (lzrw3 algorithm)\n"); + } +#else /* !MODULE */ + /* print a short no-nonsense boot message */ + printk("zftape compressor v1.00a 970514\n"); + printk("For use with " FTAPE_VERSION "\n"); +#endif /* MODULE */ + TRACE(ft_t_info, "zft_compressor_init @ 0x%p", zft_compressor_init); + TRACE(ft_t_info, "installing compressor for zftape ..."); + TRACE_CATCH(zft_cmpr_register(&cmpr_ops),); + TRACE_EXIT 0; +} + +#ifdef MODULE + +MODULE_AUTHOR( + "(c) 1996, 1997 Claus-Justus Heine (claus@momo.math.rwth-aachen.de"); +MODULE_DESCRIPTION( +"Compression routines for zftape. Uses the lzrw3 algorithm by Ross Williams"); +MODULE_LICENSE("GPL"); + +/* Called by modules package when installing the driver + */ +int init_module(void) +{ + return zft_compressor_init(); +} + +#endif /* MODULE */ diff --git a/drivers/char/ftape/compressor/zftape-compress.h b/drivers/char/ftape/compressor/zftape-compress.h new file mode 100644 index 000000000000..f200741e33bf --- /dev/null +++ b/drivers/char/ftape/compressor/zftape-compress.h @@ -0,0 +1,83 @@ +#ifndef _ZFTAPE_COMPRESS_H +#define _ZFTAPE_COMPRESS_H +/* + * Copyright (c) 1994-1997 Claus-Justus Heine + + 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: /homes/cvs/ftape-stacked/ftape/compressor/zftape-compress.h,v $ + * $Revision: 1.1 $ + * $Date: 1997/10/05 19:12:32 $ + * + * This file contains macros and definitions for zftape's + * builtin compression code. + * + */ + +#include "../zftape/zftape-buffers.h" +#include "../zftape/zftape-vtbl.h" +#include "../compressor/lzrw3.h" + +/* CMPR_WRK_MEM_SIZE gives the size of the compression wrk_mem */ +/* I got these out of lzrw3.c */ +#define U(X) ((__u32) X) +#define SIZE_P_BYTE (U(sizeof(__u8 *))) +#define ALIGNMENT_FUDGE (U(16)) + +#define CMPR_WRK_MEM_SIZE (U(4096)*(SIZE_P_BYTE) + ALIGNMENT_FUDGE) + +/* the maximum number of bytes the size of the "compressed" data can + * exceed the uncompressed data. As it is quite useless to compress + * data twice it is sometimes the case that it is more efficient to + * copy a block of data but to feed it to the "compression" + * algorithm. In this case there are some flag bytes or the like + * proceding the "compressed" data. THAT MUST NOT BE THE CASE for the + * algorithm we use for this driver. Instead, the high bit 15 of + * compressed_size: + * + * compressed_size = ftape_compress() + * + * must be set in such a case. + * + * Nevertheless, it might also be as for lzrw3 that there is an + * "intermediate" overrun that exceeds the amount of the compressed + * data that is actually produced. During the algorithm we need in the + * worst case MAX_CMP_GROUP bytes more than the input-size. + */ +#define MAX_CMP_GROUP (2+16*2) /* from lzrw3.c */ + +#define CMPR_OVERRUN MAX_CMP_GROUP /* during compression */ + +/****************************************************/ + +#define CMPR_BUFFER_SIZE (MAX_BLOCK_SIZE + CMPR_OVERRUN) + +/* the compression map stores the byte offset compressed blocks within + * the current volume for catridges with format code 2,3 and 5 + * (and old versions of zftape) and the offset measured in kilobytes for + * format code 4 and 6. This gives us a possible max. size of a + * compressed volume of 1024*4GIG which should be enough. + */ +typedef __u32 CmprMap; + +/* globals + */ + +/* exported functions + */ + +#endif /* _ZFTAPE_COMPRESS_H */ diff --git a/drivers/char/ftape/lowlevel/Makefile b/drivers/char/ftape/lowlevel/Makefile new file mode 100644 index 000000000000..febab07ba427 --- /dev/null +++ b/drivers/char/ftape/lowlevel/Makefile @@ -0,0 +1,43 @@ +# +# Copyright (C) 1996, 1997 Clau-Justus Heine. +# +# 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: /homes/cvs/ftape-stacked/ftape/lowlevel/Makefile,v $ +# $Revision: 1.4 $ +# $Date: 1997/10/07 09:26:02 $ +# +# Makefile for the lowlevel part QIC-40/80/3010/3020 floppy-tape +# driver for Linux. +# + +obj-$(CONFIG_FTAPE) += ftape.o + +ftape-objs := ftape-init.o fdc-io.o fdc-isr.o \ + ftape-bsm.o ftape-ctl.o ftape-read.o ftape-rw.o \ + ftape-write.o ftape-io.o ftape-calibr.o ftape-ecc.o fc-10.o \ + ftape-buffer.o ftape-format.o ftape_syms.o + +ifeq ($(CONFIG_FTAPE),y) +ftape-objs += ftape-setup.o +endif + +ifndef CONFIG_FT_NO_TRACE_AT_ALL +ftape-objs += ftape-tracing.o +endif + +ifeq ($(CONFIG_FT_PROC_FS),y) +ftape-objs += ftape-proc.o +endif diff --git a/drivers/char/ftape/lowlevel/fc-10.c b/drivers/char/ftape/lowlevel/fc-10.c new file mode 100644 index 000000000000..9bc1cddade76 --- /dev/null +++ b/drivers/char/ftape/lowlevel/fc-10.c @@ -0,0 +1,175 @@ +/* + * + + 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 independent 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. + + Modification on 11 December 96, by Claus Heine (claus@momo.math.rwth-aachen.de): + Modified a little to use variahle ft_fdc_base, ft_fdc_irq, ft_fdc_dma + instead of preprocessor symbols. Thus we can compile this into the module + or kernel and let the user specify the options as command line arguments. + + * + * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/fc-10.c,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:18:04 $ + * + * This file contains code for the CMS FC-10/FC-20 card. + */ + +#include <asm/io.h> +#include <linux/ftape.h> +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/fdc-io.h" +#include "../lowlevel/fc-10.h" + +static __u16 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 +}; + +static __u16 fc10_ports[] = { + 0x180, 0x210, 0x2A0, 0x300, 0x330, 0x340, 0x370 +}; + +int fc10_enable(void) +{ + int i; + __u8 cardConfig = 0x00; + __u8 x; + TRACE_FUN(ft_t_flow); + +/* 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 (ft_fdc_dma > 3) { + TRACE_ABORT(0, ft_t_err, +"Error: The FC-10/20 must be set to use DMA channels 1, 2, or 3!"); + } +/* 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 (ft_fdc_irq < 3 || ft_fdc_irq == 8 || ft_fdc_irq > 9) { + TRACE_ABORT(0, ft_t_err, +"Error: The FC-10/20 must be set to use IRQ levels 3 - 7, or 9!\n" +KERN_INFO "Note: IRQ 9 is the same as IRQ 2"); + } + /* Clear state machine ??? + */ + for (i = 0; i < NR_ITEMS(inbs_magic); i++) { + inb(ft_fdc_base + inbs_magic[i]); + } + outb(0x0, ft_fdc_base); + + x = inb(ft_fdc_base); + if (x == 0x13 || x == 0x93) { + for (i = 1; i < 8; i++) { + if (inb(ft_fdc_base + i) != x) { + TRACE_EXIT 0; + } + } + } else { + TRACE_EXIT 0; + } + + outb(0x8, ft_fdc_base); + + for (i = 0; i < 8; i++) { + if (inb(ft_fdc_base + i) != 0x0) { + TRACE_EXIT 0; + } + } + outb(0x10, ft_fdc_base); + + for (i = 0; i < 8; i++) { + if (inb(ft_fdc_base + i) != 0xff) { + TRACE_EXIT 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(ft_fdc_base + inbs_magic[i]); + } + /* Send io port */ + for (i = 0; i < NR_ITEMS(fc10_ports); i++) + if (ft_fdc_base == fc10_ports[i]) + cardConfig = i + 1; + if (cardConfig == 0) { + TRACE_EXIT 0; /* Invalid I/O Port */ + } + /* and IRQ - If using IRQ 9, tell the FC card it is actually IRQ 2 */ + if (ft_fdc_irq != 9) + cardConfig |= ft_fdc_irq << 3; + else + cardConfig |= 2 << 3; + + /* and finally DMA Channel */ + cardConfig |= ft_fdc_dma << 6; + outb(cardConfig, ft_fdc_base); /* DMA [2 bits]/IRQ [3 bits]/BASE [3 bits] */ + + /* Enable FC-10 ??? + */ + outb(0, fdc.ccr); + outb(0, fdc.dor2); + outb(FDC_DMA_MODE /* 8 */, fdc.dor); + outb(FDC_DMA_MODE /* 8 */, fdc.dor); + outb(1, fdc.dor2); + + /************************************* + * + * cH: why the hell should this be necessary? This is done + * by fdc_reset()!!! + * + *************************************/ + /* Initialize fdc, select drive B: + */ + outb(FDC_DMA_MODE, fdc.dor); /* assert reset, dma & irq enabled */ + /* 0x08 */ + outb(FDC_DMA_MODE|FDC_RESET_NOT, fdc.dor); /* release reset */ + /* 0x08 | 0x04 = 0x0c */ + outb(FDC_DMA_MODE|FDC_RESET_NOT|FDC_MOTOR_1|FTAPE_SEL_B, fdc.dor); + /* 0x08 | 0x04 | 0x20 | 0x01 = 0x2d */ + /* select drive 1 */ /* why not drive 0 ???? */ + TRACE_EXIT (x == 0x93) ? 2 : 1; +} diff --git a/drivers/char/ftape/lowlevel/fc-10.h b/drivers/char/ftape/lowlevel/fc-10.h new file mode 100644 index 000000000000..da7b88bca889 --- /dev/null +++ b/drivers/char/ftape/lowlevel/fc-10.h @@ -0,0 +1,39 @@ +#ifndef _FC_10_H +#define _FC_10_H + +/* + * Copyright (C) 1994-1996 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: /homes/cvs/ftape-stacked/ftape/lowlevel/fc-10.h,v $ + * $Revision: 1.1 $ + * $Date: 1997/09/19 09:05:22 $ + * + * 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 --git a/drivers/char/ftape/lowlevel/fdc-io.c b/drivers/char/ftape/lowlevel/fdc-io.c new file mode 100644 index 000000000000..1704a2a57048 --- /dev/null +++ b/drivers/char/ftape/lowlevel/fdc-io.c @@ -0,0 +1,1352 @@ +/* + * Copyright (C) 1993-1996 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/fdc-io.c,v $ + * $Revision: 1.7.4.2 $ + * $Date: 1997/11/16 14:48:17 $ + * + * This file contains the low-level floppy disk interface code + * for the QIC-40/80/3010/3020 floppy-tape driver "ftape" for + * Linux. + */ + +#include <linux/config.h> /* for CONFIG_FT_* */ +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/irq.h> + +#include <linux/ftape.h> +#include <linux/qic117.h> +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/fdc-io.h" +#include "../lowlevel/fdc-isr.h" +#include "../lowlevel/ftape-io.h" +#include "../lowlevel/ftape-rw.h" +#include "../lowlevel/ftape-ctl.h" +#include "../lowlevel/ftape-calibr.h" +#include "../lowlevel/fc-10.h" + +/* Global vars. + */ +static int ftape_motor; +volatile int ftape_current_cylinder = -1; +volatile fdc_mode_enum fdc_mode = fdc_idle; +fdc_config_info fdc; +DECLARE_WAIT_QUEUE_HEAD(ftape_wait_intr); + +unsigned int ft_fdc_base = CONFIG_FT_FDC_BASE; +unsigned int ft_fdc_irq = CONFIG_FT_FDC_IRQ; +unsigned int ft_fdc_dma = CONFIG_FT_FDC_DMA; +unsigned int ft_fdc_threshold = CONFIG_FT_FDC_THR; /* bytes */ +unsigned int ft_fdc_rate_limit = CONFIG_FT_FDC_MAX_RATE; /* bits/sec */ +int ft_probe_fc10 = CONFIG_FT_PROBE_FC10; +int ft_mach2 = CONFIG_FT_MACH2; + +/* Local vars. + */ +static spinlock_t fdc_io_lock; +static unsigned int fdc_calibr_count; +static unsigned int fdc_calibr_time; +static int fdc_status; +volatile __u8 fdc_head; /* FDC head from sector id */ +volatile __u8 fdc_cyl; /* FDC track from sector id */ +volatile __u8 fdc_sect; /* FDC sector from sector id */ +static int fdc_data_rate = 500; /* data rate (Kbps) */ +static int fdc_rate_code; /* data rate code (0 == 500 Kbps) */ +static int fdc_seek_rate = 2; /* step rate (msec) */ +static void (*do_ftape) (void); +static int fdc_fifo_state; /* original fifo setting - fifo enabled */ +static int fdc_fifo_thr; /* original fifo setting - threshold */ +static int fdc_lock_state; /* original lock setting - locked */ +static int fdc_fifo_locked; /* has fifo && lock set ? */ +static __u8 fdc_precomp; /* default precomp. value (nsec) */ +static __u8 fdc_prec_code; /* fdc precomp. select code */ + +static char ftape_id[] = "ftape"; /* used by request irq and free irq */ + +static int fdc_set_seek_rate(int seek_rate); + +void fdc_catch_stray_interrupts(int count) +{ + unsigned long flags; + + spin_lock_irqsave(&fdc_io_lock, flags); + if (count == 0) { + ft_expected_stray_interrupts = 0; + } else { + ft_expected_stray_interrupts += count; + } + spin_unlock_irqrestore(&fdc_io_lock, 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 ! + */ +static int fdc_wait(unsigned int usecs, __u8 mask, __u8 state) +{ + int count_1 = (fdc_calibr_count * usecs + + fdc_calibr_count - 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(unsigned int usecs) +{ + return fdc_wait(usecs, FDC_DATA_READY | FDC_BUSY, FDC_DATA_READY); +} + +/* Why can't we just use udelay()? + */ +static void fdc_usec_wait(unsigned int usecs) +{ + fdc_wait(usecs, 0, 1); /* will always timeout ! */ +} + +static int fdc_ready_out_wait(unsigned int usecs) +{ + fdc_usec_wait(FT_RQM_DELAY); /* wait for valid RQM status */ + return fdc_wait(usecs, FDC_DATA_OUT_READY, FDC_DATA_OUT_READY); +} + +void fdc_wait_calibrate(void) +{ + ftape_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!). + */ +static int fdc_write(const __u8 data) +{ + fdc_usec_wait(FT_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!). + */ +static int fdc_read(__u8 * data) +{ + fdc_usec_wait(FT_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 or ETIME) will occur. + */ +int fdc_command(const __u8 * cmd_data, int cmd_len) +{ + int result = 0; + unsigned long flags; + int count = cmd_len; + int retry = 0; +#ifdef TESTING + static unsigned int last_time; + unsigned int time; +#endif + TRACE_FUN(ft_t_any); + + fdc_usec_wait(FT_RQM_DELAY); /* wait for valid RQM status */ + spin_lock_irqsave(&fdc_io_lock, flags); + if (!in_interrupt()) + /* Yes, I know, too much comments inside this function + * ... + * + * Yet another bug in the original driver. All that + * havoc is caused by the fact that the isr() sends + * itself a command to the floppy tape driver (pause, + * micro step pause). Now, the problem is that + * commands are transmitted via the fdc_seek + * command. But: the fdc performs seeks in the + * background i.e. it doesn't signal busy while + * sending the step pulses to the drive. Therefore the + * non-interrupt level driver has no chance to tell + * whether the isr() just has issued a seek. Therefore + * we HAVE TO have a look at the ft_hide_interrupt + * flag: it signals the non-interrupt level part of + * the driver that it has to wait for the fdc until it + * has completet seeking. + * + * THIS WAS PRESUMABLY THE REASON FOR ALL THAT + * "fdc_read timeout" errors, I HOPE :-) + */ + if (ft_hide_interrupt) { + restore_flags(flags); + TRACE(ft_t_info, + "Waiting for the isr() completing fdc_seek()"); + if (fdc_interrupt_wait(2 * FT_SECOND) < 0) { + TRACE(ft_t_warn, + "Warning: timeout waiting for isr() seek to complete"); + } + if (ft_hide_interrupt || !ft_seek_completed) { + /* There cannot be another + * interrupt. The isr() only stops + * the tape and the next interrupt + * won't come until we have send our + * command to the drive. + */ + TRACE_ABORT(-EIO, ft_t_bug, + "BUG? isr() is still seeking?\n" + KERN_INFO "hide: %d\n" + KERN_INFO "seek: %d", + ft_hide_interrupt, + ft_seek_completed); + + } + fdc_usec_wait(FT_RQM_DELAY); /* wait for valid RQM status */ + spin_lock_irqsave(&fdc_io_lock, flags); + } + fdc_status = inb(fdc.msr); + if ((fdc_status & FDC_DATA_READY_MASK) != FDC_DATA_IN_READY) { + spin_unlock_irqrestore(&fdc_io_lock, flags); + TRACE_ABORT(-EBUSY, ft_t_err, "fdc not ready"); + } + fdc_mode = *cmd_data; /* used by isr */ +#ifdef TESTING + if (fdc_mode == FDC_SEEK) { + time = ftape_timediff(last_time, ftape_timestamp()); + if (time < 6000) { + TRACE(ft_t_bug,"Warning: short timeout between seek commands: %d", + time); + } + } +#endif + if (!in_interrupt()) { + /* shouldn't be cleared if called from isr + */ + ft_interrupt_seen = 0; + } + while (count) { + result = fdc_write(*cmd_data); + if (result < 0) { + TRACE(ft_t_fdc_dma, + "fdc_mode = %02x, status = %02x at index %d", + (int) fdc_mode, (int) fdc_status, + cmd_len - count); + if (++retry <= 3) { + TRACE(ft_t_warn, "fdc_write timeout, retry"); + } else { + TRACE(ft_t_err, "fdc_write timeout, fatal"); + /* recover ??? */ + break; + } + } else { + --count; + ++cmd_data; + } + } +#ifdef TESTING + if (fdc_mode == FDC_SEEK) { + last_time = ftape_timestamp(); + } +#endif + spin_unlock_irqrestore(&fdc_io_lock, flags); + TRACE_EXIT result; +} + +/* Input a res_len long result string from the FDC. + * The FDC should be ready to send the result or an error + * (EBUSY or ETIME) will occur. + */ +int fdc_result(__u8 * res_data, int res_len) +{ + int result = 0; + unsigned long flags; + int count = res_len; + int retry = 0; + TRACE_FUN(ft_t_any); + + spin_lock_irqsave(&fdc_io_lock, flags); + fdc_status = inb(fdc.msr); + if ((fdc_status & FDC_DATA_READY_MASK) != FDC_DATA_OUT_READY) { + TRACE(ft_t_err, "fdc not ready"); + result = -EBUSY; + } else while (count) { + if (!(fdc_status & FDC_BUSY)) { + spin_unlock_irqrestore(&fdc_io_lock, flags); + TRACE_ABORT(-EIO, ft_t_err, "premature end of result phase"); + } + result = fdc_read(res_data); + if (result < 0) { + TRACE(ft_t_fdc_dma, + "fdc_mode = %02x, status = %02x at index %d", + (int) fdc_mode, + (int) fdc_status, + res_len - count); + if (++retry <= 3) { + TRACE(ft_t_warn, "fdc_read timeout, retry"); + } else { + TRACE(ft_t_err, "fdc_read timeout, fatal"); + /* recover ??? */ + break; + ++retry; + } + } else { + --count; + ++res_data; + } + } + spin_unlock_irqrestore(&fdc_io_lock, flags); + fdc_usec_wait(FT_RQM_DELAY); /* allow FDC to negate BSY */ + TRACE_EXIT result; +} + +/* Handle command and result phases for + * commands without data phase. + */ +static int fdc_issue_command(const __u8 * out_data, int out_count, + __u8 * in_data, int in_count) +{ + TRACE_FUN(ft_t_any); + + if (out_count > 0) { + TRACE_CATCH(fdc_command(out_data, out_count),); + } + /* 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. + */ + TRACE_CATCH(fdc_ready_out_wait(500 /* usec */),); + if (in_count > 0) { + TRACE_CATCH(fdc_result(in_data, in_count), + TRACE(ft_t_err, "result phase aborted")); + } + TRACE_EXIT 0; +} + +/* Wait for FDC interrupt with timeout (in milliseconds). + * Signals are blocked so the wait will not be aborted. + * Note: interrupts must be enabled ! (23/05/93 SJL) + */ +int fdc_interrupt_wait(unsigned int time) +{ + DECLARE_WAITQUEUE(wait,current); + sigset_t old_sigmask; + static int resetting; + long timeout; + + TRACE_FUN(ft_t_fdc_dma); + + if (waitqueue_active(&ftape_wait_intr)) { + TRACE_ABORT(-EIO, ft_t_err, "error: nested call"); + } + /* timeout time will be up to USPT microseconds too long ! */ + timeout = (1000 * time + FT_USPT - 1) / FT_USPT; + + spin_lock_irq(¤t->sighand->siglock); + old_sigmask = current->blocked; + sigfillset(¤t->blocked); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&ftape_wait_intr, &wait); + while (!ft_interrupt_seen && timeout) { + set_current_state(TASK_INTERRUPTIBLE); + timeout = schedule_timeout(timeout); + } + + spin_lock_irq(¤t->sighand->siglock); + current->blocked = old_sigmask; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + remove_wait_queue(&ftape_wait_intr, &wait); + /* the following IS necessary. True: as well + * wake_up_interruptible() as the schedule() set TASK_RUNNING + * when they wakeup a task, BUT: it may very well be that + * ft_interrupt_seen is already set to 1 when we enter here + * in which case schedule() gets never called, and + * TASK_RUNNING never set. This has the funny effect that we + * execute all the code until we leave kernel space, but then + * the task is stopped (a task CANNOT be preempted while in + * kernel mode. Sending a pair of SIGSTOP/SIGCONT to the + * tasks wakes it up again. Funny! :-) + */ + current->state = TASK_RUNNING; + if (ft_interrupt_seen) { /* woken up by interrupt */ + ft_interrupt_seen = 0; + TRACE_EXIT 0; + } + /* Original comment: + * 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 ! + */ + /* My deeply sought of knowledge: + * Behold NO! It is obvious. fdc_reset() doesn't call fdc_command() + * but nevertheless uses fdc_interrupt_wait(). OF COURSE this needs to + * be reset here. + */ + ft_interrupt_seen = 0; /* clear for next call */ + if (!resetting) { + resetting = 1; /* break infinite recursion if reset fails */ + TRACE(ft_t_any, "cleanup reset"); + fdc_reset(); + resetting = 0; + } + TRACE_EXIT (signal_pending(current)) ? -EINTR : -ETIME; +} + +/* Start/stop drive motor. Enable DMA mode. + */ +void fdc_motor(int motor) +{ + int unit = ft_drive_sel; + int data = unit | FDC_RESET_NOT | FDC_DMA_MODE; + TRACE_FUN(ft_t_any); + + ftape_motor = motor; + if (ftape_motor) { + data |= FDC_MOTOR_0 << unit; + TRACE(ft_t_noise, "turning motor %d on", unit); + } else { + TRACE(ft_t_noise, "turning motor %d off", unit); + } + if (ft_mach2) { + outb_p(data, fdc.dor2); + } else { + outb_p(data, fdc.dor); + } + ftape_sleep(10 * FT_MILLISECOND); + TRACE_EXIT; +} + +static void fdc_update_dsr(void) +{ + TRACE_FUN(ft_t_any); + + TRACE(ft_t_flow, "rate = %d Kbps, precomp = %d ns", + fdc_data_rate, fdc_precomp); + if (fdc.type >= i82077) { + outb_p((fdc_rate_code & 0x03) | fdc_prec_code, fdc.dsr); + } else { + outb_p(fdc_rate_code & 0x03, fdc.ccr); + } + TRACE_EXIT; +} + +void fdc_set_write_precomp(int precomp) +{ + TRACE_FUN(ft_t_any); + + TRACE(ft_t_noise, "New precomp: %d nsec", precomp); + fdc_precomp = 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_prec_code = ((fdc_precomp + 21) / 42) << 2; + if (fdc_prec_code == 0 || fdc_prec_code > (6 << 2)) { + fdc_prec_code = 7 << 2; + } + fdc_update_dsr(); + TRACE_EXIT; +} + +/* Reprogram the 82078 registers to use Data Rate Table 1 on all drives. + */ +static void fdc_set_drive_specs(void) +{ + __u8 cmd[] = { FDC_DRIVE_SPEC, 0x00, 0x00, 0x00, 0x00, 0xc0}; + int result; + TRACE_FUN(ft_t_any); + + TRACE(ft_t_flow, "Setting of drive specs called"); + if (fdc.type >= i82078_1) { + cmd[1] = (0 << 5) | (2 << 2); + cmd[2] = (1 << 5) | (2 << 2); + cmd[3] = (2 << 5) | (2 << 2); + cmd[4] = (3 << 5) | (2 << 2); + result = fdc_command(cmd, NR_ITEMS(cmd)); + if (result < 0) { + TRACE(ft_t_err, "Setting 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. + */ +int fdc_set_data_rate(int rate) +{ + int bad_rate = 0; + TRACE_FUN(ft_t_any); + + /* Select clock for fdc, must correspond with tape drive setting ! + * This also influences the fdc timing so we must adjust some values. + */ + TRACE(ft_t_fdc_dma, "new rate = %d", rate); + switch (rate) { + case 250: + fdc_rate_code = fdc_data_rate_250; + break; + case 500: + fdc_rate_code = fdc_data_rate_500; + break; + case 1000: + if (fdc.type < i82077) { + bad_rate = 1; + } else { + fdc_rate_code = fdc_data_rate_1000; + } + break; + case 2000: + if (fdc.type < i82078_1) { + bad_rate = 1; + } else { + fdc_rate_code = fdc_data_rate_2000; + } + break; + default: + bad_rate = 1; + } + if (bad_rate) { + TRACE_ABORT(-EIO, + ft_t_fdc_dma, "%d is not a valid data rate", rate); + } + fdc_data_rate = rate; + fdc_update_dsr(); + fdc_set_seek_rate(fdc_seek_rate); /* clock changed! */ + ftape_udelay(1000); + TRACE_EXIT 0; +} + +/* keep the unit select if keep_select is != 0, + */ +static void fdc_dor_reset(int keep_select) +{ + __u8 fdc_ctl = ft_drive_sel; + + if (keep_select != 0) { + fdc_ctl |= FDC_DMA_MODE; + if (ftape_motor) { + fdc_ctl |= FDC_MOTOR_0 << ft_drive_sel; + } + } + ftape_udelay(10); /* ??? but seems to be necessary */ + if (ft_mach2) { + outb_p(fdc_ctl & 0x0f, fdc.dor); + outb_p(fdc_ctl, fdc.dor2); + } else { + outb_p(fdc_ctl, fdc.dor); + } + fdc_usec_wait(10); /* delay >= 14 fdc clocks */ + if (keep_select == 0) { + fdc_ctl = 0; + } + fdc_ctl |= FDC_RESET_NOT; + if (ft_mach2) { + outb_p(fdc_ctl & 0x0f, fdc.dor); + outb_p(fdc_ctl, fdc.dor2); + } else { + outb_p(fdc_ctl, fdc.dor); + } +} + +/* Reset the floppy disk controller. Leave the ftape_unit selected. + */ +void fdc_reset(void) +{ + int st0; + int i; + int dummy; + unsigned long flags; + TRACE_FUN(ft_t_any); + + spin_lock_irqsave(&fdc_io_lock, flags); + + fdc_dor_reset(1); /* keep unit selected */ + + fdc_mode = fdc_idle; + + /* maybe the cli()/sti() pair is not necessary, BUT: + * the following line MUST be here. Otherwise fdc_interrupt_wait() + * won't wait. Note that fdc_reset() is called from + * ftape_dumb_stop() when the fdc is busy transferring data. In this + * case fdc_isr() MOST PROBABLY sets ft_interrupt_seen, and tries + * to get the result bytes from the fdc etc. CLASH. + */ + ft_interrupt_seen = 0; + + /* Program data rate + */ + fdc_update_dsr(); /* restore data rate and precomp */ + + spin_unlock_irqrestore(&fdc_io_lock, flags); + + /* + * Wait for first polling cycle to complete + */ + if (fdc_interrupt_wait(1 * FT_SECOND) < 0) { + TRACE(ft_t_err, "no drive polling interrupt!"); + } else { /* clear all disk-changed statuses */ + for (i = 0; i < 4; ++i) { + if(fdc_sense_interrupt_status(&st0, &dummy) != 0) { + TRACE(ft_t_err, "sense failed for %d", i); + } + if (i == ft_drive_sel) { + ftape_current_cylinder = dummy; + } + } + TRACE(ft_t_noise, "drive polling completed"); + } + /* + * SPECIFY COMMAND + */ + fdc_set_seek_rate(fdc_seek_rate); + /* + * DRIVE SPECIFICATION COMMAND (if fdc type known) + */ + if (fdc.type >= i82078_1) { + fdc_set_drive_specs(); + } + TRACE_EXIT; +} + +#if !defined(CLK_48MHZ) +# define CLK_48MHZ 1 +#endif + +/* 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) +{ + __u8 cmd1[] = {FDC_CONFIGURE, 0x00, 0x00, 0x00}; + __u8 cmd2[] = {FDC_LOCK}; + __u8 cmd3[] = {FDC_UNLOCK}; + __u8 stat[1]; + TRACE_FUN(ft_t_flow); + + if (!fdc_fifo_locked) { + fdc_reset(); + TRACE_EXIT; + } + if (fdc_issue_command(cmd3, 1, stat, 1) < 0 || stat[0] != 0x00) { + fdc_dor_reset(0); + TRACE_ABORT(/**/, ft_t_bug, + "couldn't unlock fifo, configuration remains changed"); + } + fdc_fifo_locked = 0; + if (CLK_48MHZ && fdc.type >= i82078) { + cmd1[0] |= FDC_CLK48_BIT; + } + cmd1[2] = ((fdc_fifo_state) ? 0 : 0x20) + (fdc_fifo_thr - 1); + if (fdc_command(cmd1, NR_ITEMS(cmd1)) < 0) { + fdc_dor_reset(0); + TRACE_ABORT(/**/, ft_t_bug, + "couldn't reconfigure fifo to old state"); + } + if (fdc_lock_state && + fdc_issue_command(cmd2, 1, stat, 1) < 0) { + fdc_dor_reset(0); + TRACE_ABORT(/**/, ft_t_bug, "couldn't lock old state again"); + } + TRACE(ft_t_noise, "fifo restored: %sabled, thr. %d, %slocked", + fdc_fifo_state ? "en" : "dis", + fdc_fifo_thr, (fdc_lock_state) ? "" : "not "); + fdc_dor_reset(0); + TRACE_EXIT; +} + +/* Specify FDC seek-rate (milliseconds) + */ +static int fdc_set_seek_rate(int seek_rate) +{ + /* set step rate, dma mode, and minimal head load and unload times + */ + __u8 in[3] = { FDC_SPECIFY, 1, (1 << 1)}; + + fdc_seek_rate = seek_rate; + in[1] |= (16 - (fdc_data_rate * fdc_seek_rate) / 500) << 4; + + return fdc_command(in, 3); +} + +/* Sense drive status: get unit's drive status (ST3) + */ +int fdc_sense_drive_status(int *st3) +{ + __u8 out[2]; + __u8 in[1]; + TRACE_FUN(ft_t_any); + + out[0] = FDC_SENSED; + out[1] = ft_drive_sel; + TRACE_CATCH(fdc_issue_command(out, 2, in, 1),); + *st3 = in[0]; + TRACE_EXIT 0; +} + +/* 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) +{ + __u8 out[1]; + __u8 in[2]; + TRACE_FUN(ft_t_any); + + out[0] = FDC_SENSEI; + TRACE_CATCH(fdc_issue_command(out, 1, in, 2),); + *st0 = in[0]; + *current_cylinder = in[1]; + TRACE_EXIT 0; +} + +/* step to track + */ +int fdc_seek(int track) +{ + __u8 out[3]; + int st0, pcn; +#ifdef TESTING + unsigned int time; +#endif + TRACE_FUN(ft_t_any); + + out[0] = FDC_SEEK; + out[1] = ft_drive_sel; + out[2] = track; +#ifdef TESTING + time = ftape_timestamp(); +#endif + /* We really need this command to work ! + */ + ft_seek_completed = 0; + TRACE_CATCH(fdc_command(out, 3), + fdc_reset(); + TRACE(ft_t_noise, "destination was: %d, resetting FDC...", + track)); + /* Handle interrupts until ft_seek_completed or timeout. + */ + for (;;) { + TRACE_CATCH(fdc_interrupt_wait(2 * FT_SECOND),); + if (ft_seek_completed) { + TRACE_CATCH(fdc_sense_interrupt_status(&st0, &pcn),); + if ((st0 & ST0_SEEK_END) == 0) { + TRACE_ABORT(-EIO, ft_t_err, + "no seek-end after seek completion !??"); + } + break; + } + } +#ifdef TESTING + time = ftape_timediff(time, ftape_timestamp()) / abs(track - ftape_current_cylinder); + if ((time < 900 || time > 3100) && abs(track - ftape_current_cylinder) > 5) { + TRACE(ft_t_warn, "Wrong FDC STEP interval: %d usecs (%d)", + time, track - ftape_current_cylinder); + } +#endif + /* Verify whether we issued the right tape command. + */ + /* Verify that we seek to the proper track. */ + if (pcn != track) { + TRACE_ABORT(-EIO, ft_t_err, "bad seek.."); + } + ftape_current_cylinder = track; + TRACE_EXIT 0; +} + +static int perpend_mode; /* set if fdc is in perpendicular mode */ + +static int perpend_off(void) +{ + __u8 perpend[] = {FDC_PERPEND, 0x00}; + TRACE_FUN(ft_t_any); + + if (perpend_mode) { + /* Turn off perpendicular mode */ + perpend[1] = 0x80; + TRACE_CATCH(fdc_command(perpend, 2), + TRACE(ft_t_err,"Perpendicular mode exit failed!")); + perpend_mode = 0; + } + TRACE_EXIT 0; +} + +static int handle_perpend(int segment_id) +{ + __u8 perpend[] = {FDC_PERPEND, 0x00}; + TRACE_FUN(ft_t_any); + + /* When writing QIC-3020 tapes, turn on perpendicular mode + * if tape is moving in forward direction (even tracks). + */ + if (ft_qic_std == QIC_TAPE_QIC3020 && + ((segment_id / ft_segments_per_track) & 1) == 0) { +/* FIXME: some i82077 seem to support perpendicular mode as + * well. + */ +#if 0 + if (fdc.type < i82077AA) {} +#else + if (fdc.type < i82077 && ft_data_rate < 1000) { +#endif + /* fdc does not support perpendicular mode: complain + */ + TRACE_ABORT(-EIO, ft_t_err, + "Your FDC does not support QIC-3020."); + } + perpend[1] = 0x03 /* 0x83 + (0x4 << ft_drive_sel) */ ; + TRACE_CATCH(fdc_command(perpend, 2), + TRACE(ft_t_err,"Perpendicular mode entry failed!")); + TRACE(ft_t_flow, "Perpendicular mode set"); + perpend_mode = 1; + TRACE_EXIT 0; + } + TRACE_EXIT perpend_off(); +} + +static inline void fdc_setup_dma(char mode, + volatile void *addr, unsigned int count) +{ + /* Program the DMA controller. + */ + disable_dma(fdc.dma); + clear_dma_ff(fdc.dma); + set_dma_mode(fdc.dma, mode); + set_dma_addr(fdc.dma, virt_to_bus((void*)addr)); + set_dma_count(fdc.dma, count); + enable_dma(fdc.dma); +} + +/* Setup fdc and dma for formatting the next segment + */ +int fdc_setup_formatting(buffer_struct * buff) +{ + unsigned long flags; + __u8 out[6] = { + FDC_FORMAT, 0x00, 3, 4 * FT_SECTORS_PER_SEGMENT, 0x00, 0x6b + }; + TRACE_FUN(ft_t_any); + + TRACE_CATCH(handle_perpend(buff->segment_id),); + /* Program the DMA controller. + */ + TRACE(ft_t_fdc_dma, + "phys. addr. = %lx", virt_to_bus((void*) buff->ptr)); + spin_lock_irqsave(&fdc_io_lock, flags); + fdc_setup_dma(DMA_MODE_WRITE, buff->ptr, FT_SECTORS_PER_SEGMENT * 4); + /* Issue FDC command to start reading/writing. + */ + out[1] = ft_drive_sel; + out[4] = buff->gap3; + TRACE_CATCH(fdc_setup_error = fdc_command(out, sizeof(out)), + restore_flags(flags); fdc_mode = fdc_idle); + spin_unlock_irqrestore(&fdc_io_lock, flags); + TRACE_EXIT 0; +} + + +/* Setup Floppy Disk Controller and DMA to read or write the next cluster + * of good sectors from or to the current segment. + */ +int fdc_setup_read_write(buffer_struct * buff, __u8 operation) +{ + unsigned long flags; + __u8 out[9]; + int dma_mode; + TRACE_FUN(ft_t_any); + + switch(operation) { + case FDC_VERIFY: + if (fdc.type < i82077) { + operation = FDC_READ; + } + case FDC_READ: + case FDC_READ_DELETED: + dma_mode = DMA_MODE_READ; + TRACE(ft_t_fdc_dma, "xfer %d sectors to 0x%p", + buff->sector_count, buff->ptr); + TRACE_CATCH(perpend_off(),); + break; + case FDC_WRITE_DELETED: + TRACE(ft_t_noise, "deleting segment %d", buff->segment_id); + case FDC_WRITE: + dma_mode = DMA_MODE_WRITE; + /* When writing QIC-3020 tapes, turn on perpendicular mode + * if tape is moving in forward direction (even tracks). + */ + TRACE_CATCH(handle_perpend(buff->segment_id),); + TRACE(ft_t_fdc_dma, "xfer %d sectors from 0x%p", + buff->sector_count, buff->ptr); + break; + default: + TRACE_ABORT(-EIO, + ft_t_bug, "bug: invalid operation parameter"); + } + TRACE(ft_t_fdc_dma, "phys. addr. = %lx",virt_to_bus((void*)buff->ptr)); + spin_lock_irqsave(&fdc_io_lock, flags); + if (operation != FDC_VERIFY) { + fdc_setup_dma(dma_mode, buff->ptr, + FT_SECTOR_SIZE * buff->sector_count); + } + /* Issue FDC command to start reading/writing. + */ + out[0] = operation; + out[1] = ft_drive_sel; + 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. */ + TRACE(ft_t_fdc_dma, "C: 0x%02x, H: 0x%02x, R: 0x%02x, cnt: 0x%02x", + out[2], out[3], out[4], out[6] - out[4] + 1); + spin_unlock_irqrestore(&fdc_io_lock, flags); + TRACE_CATCH(fdc_setup_error = fdc_command(out, 9),fdc_mode = fdc_idle); + TRACE_EXIT 0; +} + +int fdc_fifo_threshold(__u8 threshold, + int *fifo_state, int *lock_state, int *fifo_thr) +{ + const __u8 cmd0[] = {FDC_DUMPREGS}; + __u8 cmd1[] = {FDC_CONFIGURE, 0, (0x0f & (threshold - 1)), 0}; + const __u8 cmd2[] = {FDC_LOCK}; + const __u8 cmd3[] = {FDC_UNLOCK}; + __u8 reg[10]; + __u8 stat; + int i; + int result; + TRACE_FUN(ft_t_any); + + if (CLK_48MHZ && fdc.type >= i82078) { + cmd1[0] |= FDC_CLK48_BIT; + } + /* Dump fdc internal registers for examination + */ + TRACE_CATCH(fdc_command(cmd0, NR_ITEMS(cmd0)), + TRACE(ft_t_warn, "dumpreg cmd failed, fifo unchanged")); + /* Now read fdc internal registers from fifo + */ + for (i = 0; i < (int)NR_ITEMS(reg); ++i) { + fdc_read(®[i]); + TRACE(ft_t_fdc_dma, "Register %d = 0x%02x", i, reg[i]); + } + if (fifo_state && lock_state && fifo_thr) { + *fifo_state = (reg[8] & 0x20) == 0; + *lock_state = reg[7] & 0x80; + *fifo_thr = 1 + (reg[8] & 0x0f); + } + TRACE(ft_t_noise, + "original fifo state: %sabled, threshold %d, %slocked", + ((reg[8] & 0x20) == 0) ? "en" : "dis", + 1 + (reg[8] & 0x0f), (reg[7] & 0x80) ? "" : "not "); + /* If fdc is already locked, unlock it first ! */ + if (reg[7] & 0x80) { + fdc_ready_wait(100); + TRACE_CATCH(fdc_issue_command(cmd3, NR_ITEMS(cmd3), &stat, 1), + TRACE(ft_t_bug, "FDC unlock command failed, " + "configuration unchanged")); + } + fdc_fifo_locked = 0; + /* Enable fifo and set threshold at xx bytes to allow a + * reasonably large latency and reduce number of dma bursts. + */ + fdc_ready_wait(100); + if ((result = fdc_command(cmd1, NR_ITEMS(cmd1))) < 0) { + TRACE(ft_t_bug, "configure cmd failed, fifo unchanged"); + } + /* Now lock configuration so reset will not change it + */ + if(fdc_issue_command(cmd2, NR_ITEMS(cmd2), &stat, 1) < 0 || + stat != 0x10) { + TRACE_ABORT(-EIO, ft_t_bug, + "FDC lock command failed, stat = 0x%02x", stat); + } + fdc_fifo_locked = 1; + TRACE_EXIT result; +} + +static int fdc_fifo_enable(void) +{ + TRACE_FUN(ft_t_any); + + if (fdc_fifo_locked) { + TRACE_ABORT(0, ft_t_warn, "Fifo not enabled because locked"); + } + TRACE_CATCH(fdc_fifo_threshold(ft_fdc_threshold /* bytes */, + &fdc_fifo_state, + &fdc_lock_state, + &fdc_fifo_thr),); + TRACE_CATCH(fdc_fifo_threshold(ft_fdc_threshold /* bytes */, + NULL, NULL, NULL),); + TRACE_EXIT 0; +} + +/* Determine fd controller type + */ +static __u8 fdc_save_state[2]; + +static int fdc_probe(void) +{ + __u8 cmd[1]; + __u8 stat[16]; /* must be able to hold dumpregs & save results */ + int i; + TRACE_FUN(ft_t_any); + + /* 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; + if (fdc_issue_command(cmd, 1, stat, 1) != 0) { + TRACE_ABORT(no_fdc, ft_t_bug, "No FDC found"); + } + if (stat[0] == 0x80) { + /* invalid command: must be pre 82072 */ + TRACE_ABORT(i8272, + ft_t_warn, "Type 8272A/765A compatible FDC found"); + } + fdc_result(&stat[1], 9); + fdc_save_state[0] = stat[7]; + fdc_save_state[1] = stat[8]; + cmd[0] = FDC_VERSION; + if (fdc_issue_command(cmd, 1, stat, 1) < 0 || stat[0] == 0x80) { + TRACE_ABORT(i8272, ft_t_warn, "Type 82072 FDC found"); + } + if (*stat != 0x90) { + TRACE_ABORT(i8272, ft_t_warn, "Unknown FDC found"); + } + cmd[0] = FDC_UNLOCK; + if(fdc_issue_command(cmd, 1, stat, 1) < 0 || stat[0] != 0x00) { + TRACE_ABORT(i8272, ft_t_warn, + "Type pre-1991 82077 FDC found, " + "treating it like a 82072"); + } + if (fdc_save_state[0] & 0x80) { /* was locked */ + cmd[0] = FDC_LOCK; /* restore lock */ + (void)fdc_issue_command(cmd, 1, stat, 1); + TRACE(ft_t_warn, "FDC is already locked"); + } + /* Test for a i82078 FDC */ + cmd[0] = FDC_PARTID; + if (fdc_issue_command(cmd, 1, stat, 1) < 0 || stat[0] == 0x80) { + /* invalid command: not a i82078xx type FDC */ + for (i = 0; i < 4; ++i) { + outb_p(i, fdc.tdr); + if ((inb_p(fdc.tdr) & 0x03) != i) { + TRACE_ABORT(i82077, + ft_t_warn, "Type 82077 FDC found"); + } + } + TRACE_ABORT(i82077AA, ft_t_warn, "Type 82077AA FDC found"); + } + /* 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; + if(fdc_issue_command(cmd, 1, stat, 16) < 0) { + TRACE(ft_t_err, "FDC_SAVE failed. Dunno why"); + /* guess we better claim the fdc to be a i82078 */ + TRACE_ABORT(i82078, + ft_t_warn, + "Type i82078 FDC (i suppose) found"); + } + if ((stat[0] & FDC_SEL3V_BIT)) { + /* fdc running off 5Volts; Pray that it's a i82078-1 + */ + TRACE_ABORT(i82078_1, ft_t_warn, + "Type i82078-1 or 5Volt i82078SL FDC found"); + } + TRACE_ABORT(i82078, ft_t_warn, + "Type 3Volt i82078SL FDC (1Mbps) found"); + case 0x1: + case 0x2: /* S82078B */ + /* The '78B isn't '78 compatible. Detect it as a '77AA */ + TRACE_ABORT(i82077AA, ft_t_warn, "Type i82077AA FDC found"); + case 0x3: /* NSC PC8744 core; used in several super-IO chips */ + TRACE_ABORT(i82077AA, + ft_t_warn, "Type 82077AA compatible FDC found"); + default: + TRACE(ft_t_warn, "A previously undetected FDC found"); + TRACE_ABORT(i82077AA, ft_t_warn, + "Treating it as a 82077AA. Please report partid= %d", + stat[0]); + } /* switch(stat[ 0] >> 5) */ + TRACE_EXIT no_fdc; +} + +static int fdc_request_regions(void) +{ + TRACE_FUN(ft_t_flow); + + if (ft_mach2 || ft_probe_fc10) { + if (!request_region(fdc.sra, 8, "fdc (ft)")) { +#ifndef BROKEN_FLOPPY_DRIVER + TRACE_EXIT -EBUSY; +#else + TRACE(ft_t_warn, +"address 0x%03x occupied (by floppy driver?), using it anyway", fdc.sra); +#endif + } + } else { + if (!request_region(fdc.sra, 6, "fdc (ft)")) { +#ifndef BROKEN_FLOPPY_DRIVER + TRACE_EXIT -EBUSY; +#else + TRACE(ft_t_warn, +"address 0x%03x occupied (by floppy driver?), using it anyway", fdc.sra); +#endif + } + if (!request_region(fdc.sra + 7, 1, "fdc (ft)")) { +#ifndef BROKEN_FLOPPY_DRIVER + release_region(fdc.sra, 6); + TRACE_EXIT -EBUSY; +#else + TRACE(ft_t_warn, +"address 0x%03x occupied (by floppy driver?), using it anyway", fdc.sra + 7); +#endif + } + } + TRACE_EXIT 0; +} + +void fdc_release_regions(void) +{ + TRACE_FUN(ft_t_flow); + + if (fdc.sra != 0) { + if (fdc.dor2 != 0) { + release_region(fdc.sra, 8); + } else { + release_region(fdc.sra, 6); + release_region(fdc.dir, 1); + } + } + TRACE_EXIT; +} + +static int fdc_config_regs(unsigned int fdc_base, + unsigned int fdc_irq, + unsigned int fdc_dma) +{ + TRACE_FUN(ft_t_flow); + + 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; + fdc.dir = fdc.ccr = fdc_base + 7; + fdc.dor2 = (ft_mach2 || ft_probe_fc10) ? fdc_base + 6 : 0; + TRACE_CATCH(fdc_request_regions(), fdc.sra = 0); + TRACE_EXIT 0; +} + +static int fdc_config(void) +{ + static int already_done; + TRACE_FUN(ft_t_any); + + if (already_done) { + TRACE_CATCH(fdc_request_regions(),); + *(fdc.hook) = fdc_isr; /* hook our handler in */ + TRACE_EXIT 0; + } + if (ft_probe_fc10) { + int fc_type; + + TRACE_CATCH(fdc_config_regs(ft_fdc_base, + ft_fdc_irq, ft_fdc_dma),); + fc_type = fc10_enable(); + if (fc_type != 0) { + TRACE(ft_t_warn, "FC-%c0 controller found", '0' + fc_type); + fdc.type = fc10; + fdc.hook = &do_ftape; + *(fdc.hook) = fdc_isr; /* hook our handler in */ + already_done = 1; + TRACE_EXIT 0; + } else { + TRACE(ft_t_warn, "FC-10/20 controller not found"); + fdc_release_regions(); + fdc.type = no_fdc; + ft_probe_fc10 = 0; + ft_fdc_base = 0x3f0; + ft_fdc_irq = 6; + ft_fdc_dma = 2; + } + } + TRACE(ft_t_warn, "fdc base: 0x%x, irq: %d, dma: %d", + ft_fdc_base, ft_fdc_irq, ft_fdc_dma); + TRACE_CATCH(fdc_config_regs(ft_fdc_base, ft_fdc_irq, ft_fdc_dma),); + fdc.hook = &do_ftape; + *(fdc.hook) = fdc_isr; /* hook our handler in */ + already_done = 1; + TRACE_EXIT 0; +} + +static irqreturn_t ftape_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + void (*handler) (void) = *fdc.hook; + int handled = 0; + TRACE_FUN(ft_t_any); + + *fdc.hook = NULL; + if (handler) { + handled = 1; + handler(); + } else { + TRACE(ft_t_bug, "Unexpected ftape interrupt"); + } + TRACE_EXIT IRQ_RETVAL(handled); +} + +static int fdc_grab_irq_and_dma(void) +{ + TRACE_FUN(ft_t_any); + + if (fdc.hook == &do_ftape) { + /* Get fast interrupt handler. + */ + if (request_irq(fdc.irq, ftape_interrupt, + SA_INTERRUPT, "ft", ftape_id)) { + TRACE_ABORT(-EIO, ft_t_bug, + "Unable to grab IRQ%d for ftape driver", + fdc.irq); + } + if (request_dma(fdc.dma, ftape_id)) { + free_irq(fdc.irq, ftape_id); + TRACE_ABORT(-EIO, ft_t_bug, + "Unable to grab DMA%d for ftape driver", + fdc.dma); + } + } + if (ft_fdc_base != 0x3f0 && (ft_fdc_dma == 2 || ft_fdc_irq == 6)) { + /* Using same dma channel or irq 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(ft_t_noise, "DMA-gate on standard fdc disabled"); + } + TRACE_EXIT 0; +} + +int fdc_release_irq_and_dma(void) +{ + TRACE_FUN(ft_t_any); + + if (fdc.hook == &do_ftape) { + disable_dma(fdc.dma); /* just in case... */ + free_dma(fdc.dma); + free_irq(fdc.irq, ftape_id); + } + if (ft_fdc_base != 0x3f0 && (ft_fdc_dma == 2 || ft_fdc_irq == 6)) { + /* 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(ft_t_noise, "DMA-gate on standard fdc enabled again"); + } + TRACE_EXIT 0; +} + +int fdc_init(void) +{ + TRACE_FUN(ft_t_any); + + /* find a FDC to use */ + TRACE_CATCH(fdc_config(),); + TRACE_CATCH(fdc_grab_irq_and_dma(), fdc_release_regions()); + ftape_motor = 0; + fdc_catch_stray_interrupts(0); /* clear number of awainted + * stray interrupte + */ + fdc_catch_stray_interrupts(1); /* one always comes (?) */ + TRACE(ft_t_flow, "resetting fdc"); + fdc_set_seek_rate(2); /* use nominal QIC step rate */ + fdc_reset(); /* init fdc & clear track counters */ + if (fdc.type == no_fdc) { /* no FC-10 or FC-20 found */ + fdc.type = fdc_probe(); + fdc_reset(); /* update with new knowledge */ + } + if (fdc.type == no_fdc) { + fdc_release_irq_and_dma(); + fdc_release_regions(); + TRACE_EXIT -ENXIO; + } + if (fdc.type >= i82077) { + if (fdc_fifo_enable() < 0) { + TRACE(ft_t_warn, "couldn't enable fdc fifo !"); + } else { + TRACE(ft_t_flow, "fdc fifo enabled and locked"); + } + } + TRACE_EXIT 0; +} diff --git a/drivers/char/ftape/lowlevel/fdc-io.h b/drivers/char/ftape/lowlevel/fdc-io.h new file mode 100644 index 000000000000..7ec3c72178bb --- /dev/null +++ b/drivers/char/ftape/lowlevel/fdc-io.h @@ -0,0 +1,252 @@ +#ifndef _FDC_IO_H +#define _FDC_IO_H + +/* + * Copyright (C) 1993-1996 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/fdc-io.h,v $ + * $Revision: 1.3 $ + * $Date: 1997/10/05 19:18:06 $ + * + * This file contains the declarations for the low level + * functions that communicate with the floppy disk controller, + * for the QIC-40/80/3010/3020 floppy-tape driver "ftape" for + * Linux. + */ + +#include <linux/fdreg.h> + +#include "../lowlevel/ftape-bsm.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_VERIFY (0x56) +#define FDC_READID (0x4a) +#define FDC_SENSED (0x04) +#define FDC_SENSEI (FD_SENSEI) +#define FDC_FORMAT (FD_FORMAT) +#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 int irq; /* FDC irq nr */ + unsigned int dma; /* FDC dma channel nr */ + __u16 sra; /* Status register A (PS/2 only) */ + __u16 srb; /* Status register B (PS/2 only) */ + __u16 dor; /* Digital output register */ + __u16 tdr; /* Tape Drive Register (82077SL-1 & + 82078 only) */ + __u16 msr; /* Main Status Register */ + __u16 dsr; /* Datarate Select Register (8207x only) */ + __u16 fifo; /* Data register / Fifo on 8207x */ + __u16 dir; /* Digital Input Register */ + __u16 ccr; /* Configuration Control Register */ + __u16 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_300 = 1, /* any fdc in default configuration */ + fdc_data_rate_500 = 0, + fdc_data_rate_1000 = 3, + fdc_data_rate_2000 = 1, /* i82078-1: when using Data Rate Table #2 */ +} fdc_data_rate_type; + +typedef enum { + fdc_idle = 0, + fdc_reading_data = FDC_READ, + fdc_seeking = FDC_SEEK, + fdc_writing_data = FDC_WRITE, + fdc_deleting = FDC_WRITE_DELETED, + fdc_reading_id = FDC_READID, + fdc_recalibrating = FDC_RECAL, + fdc_formatting = FDC_FORMAT, + fdc_verifying = FDC_VERIFY +} fdc_mode_enum; + +typedef enum { + waiting = 0, + reading, + writing, + formatting, + verifying, + deleting, + done, + error, + mmapped, +} buffer_state_enum; + +typedef struct { + __u8 *address; + volatile buffer_state_enum status; + volatile __u8 *ptr; + volatile unsigned int bytes; + volatile unsigned int segment_id; + + /* bitmap for remainder of segment not yet handled. + * one bit set for each bad sector that must be skipped. + */ + volatile SectorMap bad_sector_map; + + /* bitmap with bad data blocks in data buffer. + * the errors in this map may be retried. + */ + volatile SectorMap soft_error_map; + + /* bitmap with bad data blocks in data buffer + * the errors in this map may not be retried. + */ + volatile SectorMap 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 int data_offset; + + /* offset in segment for first sector to be handled. + */ + volatile unsigned int sector_offset; + + /* size of cluster of good sectors to be handled. + */ + volatile unsigned int sector_count; + + /* size of remaining part of segment to be handled. + */ + volatile unsigned int remaining; + + /* points to next segment (contiguous) to be handled, + * or is zero if no read-ahead is allowed. + */ + volatile unsigned int next_segment; + + /* flag being set if deleted data was read. + */ + volatile int deleted; + + /* floppy coordinates of first sector in segment */ + volatile __u8 head; + volatile __u8 cyl; + volatile __u8 sect; + + /* gap to use when formatting */ + __u8 gap3; + /* flag set when buffer is mmaped */ + int mmapped; +} buffer_struct; + +/* + * fdc-io.c defined public variables + */ +extern volatile fdc_mode_enum fdc_mode; +extern int fdc_setup_error; /* outdated ??? */ +extern wait_queue_head_t ftape_wait_intr; +extern volatile int ftape_current_cylinder; /* track nr FDC thinks we're on */ +extern volatile __u8 fdc_head; /* FDC head */ +extern volatile __u8 fdc_cyl; /* FDC track */ +extern volatile __u8 fdc_sect; /* FDC sector */ +extern fdc_config_info fdc; /* FDC hardware configuration */ + +extern unsigned int ft_fdc_base; +extern unsigned int ft_fdc_irq; +extern unsigned int ft_fdc_dma; +extern unsigned int ft_fdc_threshold; +extern unsigned int ft_fdc_rate_limit; +extern int ft_probe_fc10; +extern int ft_mach2; +/* + * fdc-io.c defined public functions + */ +extern void fdc_catch_stray_interrupts(int count); +extern int fdc_ready_wait(unsigned int timeout); +extern int fdc_command(const __u8 * cmd_data, int cmd_len); +extern int fdc_result(__u8 * res_data, int res_len); +extern int fdc_interrupt_wait(unsigned int time); +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 void fdc_disable(void); +extern int fdc_fifo_threshold(__u8 threshold, + int *fifo_state, int *lock_state, int *fifo_thr); +extern void 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 int fdc_set_data_rate(int rate); +extern void fdc_set_write_precomp(int precomp); +extern int fdc_release_irq_and_dma(void); +extern void fdc_release_regions(void); +extern int fdc_init(void); +extern int fdc_setup_read_write(buffer_struct * buff, __u8 operation); +extern int fdc_setup_formatting(buffer_struct * buff); +#endif diff --git a/drivers/char/ftape/lowlevel/fdc-isr.c b/drivers/char/ftape/lowlevel/fdc-isr.c new file mode 100644 index 000000000000..ad2bc733ae1b --- /dev/null +++ b/drivers/char/ftape/lowlevel/fdc-isr.c @@ -0,0 +1,1170 @@ +/* + * Copyright (C) 1994-1996 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/fdc-isr.c,v $ + * $Revision: 1.9 $ + * $Date: 1997/10/17 23:01:53 $ + * + * This file contains the interrupt service routine and + * associated code for the QIC-40/80/3010/3020 floppy-tape driver + * "ftape" for Linux. + */ + +#include <asm/io.h> +#include <asm/dma.h> + +#define volatile /* */ + +#include <linux/ftape.h> +#include <linux/qic117.h> +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/fdc-isr.h" +#include "../lowlevel/fdc-io.h" +#include "../lowlevel/ftape-ctl.h" +#include "../lowlevel/ftape-rw.h" +#include "../lowlevel/ftape-io.h" +#include "../lowlevel/ftape-calibr.h" +#include "../lowlevel/ftape-bsm.h" + +/* Global vars. + */ +volatile int ft_expected_stray_interrupts; +volatile int ft_interrupt_seen; +volatile int ft_seek_completed; +volatile int ft_hide_interrupt; +/* 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 stop_read_ahead; + + +static void print_error_cause(int cause) +{ + TRACE_FUN(ft_t_any); + + switch (cause) { + case no_data_error: + TRACE(ft_t_noise, "no data error"); + break; + case id_am_error: + TRACE(ft_t_noise, "id am error"); + break; + case id_crc_error: + TRACE(ft_t_noise, "id crc error"); + break; + case data_am_error: + TRACE(ft_t_noise, "data am error"); + break; + case data_crc_error: + TRACE(ft_t_noise, "data crc error"); + break; + case overrun_error: + TRACE(ft_t_noise, "overrun error"); + break; + default:; + } + TRACE_EXIT; +} + +static char *fdc_mode_txt(fdc_mode_enum mode) +{ + switch (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"; + case fdc_formatting: + return "fdc_formatting"; + case fdc_verifying: + return "fdc_verifying"; + default: + return "unknown"; + } +} + +static inline error_cause decode_irq_cause(fdc_mode_enum mode, __u8 st[]) +{ + error_cause cause = no_error; + TRACE_FUN(ft_t_any); + + /* Valid st[], decode cause of interrupt. + */ + switch (st[0] & ST0_INT_MASK) { + case FDC_INT_NORMAL: + TRACE(ft_t_fdc_dma,"normal completion: %s",fdc_mode_txt(mode)); + break; + case FDC_INT_ABNORMAL: + TRACE(ft_t_flow, "abnormal completion %s", fdc_mode_txt(mode)); + TRACE(ft_t_fdc_dma, "ST0: 0x%02x, ST1: 0x%02x, ST2: 0x%02x", + st[0], st[1], st[2]); + TRACE(ft_t_fdc_dma, + "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: + TRACE(ft_t_flow, "invalid completion %s", fdc_mode_txt(mode)); + break; + case FDC_INT_READYCH: + if (st[0] & ST0_SEEK_END) { + TRACE(ft_t_flow, "drive poll completed"); + } else { + TRACE(ft_t_flow, "ready change %s",fdc_mode_txt(mode)); + } + break; + default: + break; + } + TRACE_EXIT cause; +} + +static void update_history(error_cause cause) +{ + switch (cause) { + case id_am_error: + ft_history.id_am_errors++; + break; + case id_crc_error: + ft_history.id_crc_errors++; + break; + case data_am_error: + ft_history.data_am_errors++; + break; + case data_crc_error: + ft_history.data_crc_errors++; + break; + case overrun_error: + ft_history.overrun_errors++; + break; + case no_data_error: + ft_history.no_data_errors++; + break; + default:; + } +} + +static void skip_bad_sector(buffer_struct * buff) +{ + TRACE_FUN(ft_t_any); + + /* Mark sector as soft error and skip it + */ + if (buff->remaining > 0) { + ++buff->sector_offset; + ++buff->data_offset; + --buff->remaining; + buff->ptr += FT_SECTOR_SIZE; + buff->bad_sector_map >>= 1; + } else { + /* Hey, what is this????????????? C code: if we shift + * more than 31 bits, we get no shift. That's bad!!!!!! + */ + ++buff->sector_offset; /* hack for error maps */ + TRACE(ft_t_warn, "skipping last sector in segment"); + } + TRACE_EXIT; +} + +static void update_error_maps(buffer_struct * buff, unsigned int error_offset) +{ + int hard = 0; + TRACE_FUN(ft_t_any); + + if (buff->retry < FT_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; + } + TRACE(ft_t_noise, "sector %d : %s error\n" + KERN_INFO "hard map: 0x%08lx\n" + KERN_INFO "soft map: 0x%08lx", + FT_SECTOR(error_offset), hard ? "hard" : "soft", + (long) buff->hard_error_map, (long) buff->soft_error_map); + TRACE_EXIT; +} + +static void print_progress(buffer_struct *buff, error_cause cause) +{ + TRACE_FUN(ft_t_any); + + switch (cause) { + case no_error: + TRACE(ft_t_flow,"%d Sector(s) transferred", buff->sector_count); + break; + case no_data_error: + TRACE(ft_t_flow, "Sector %d not found", + FT_SECTOR(buff->sector_offset)); + break; + case overrun_error: + /* got an overrun error on the first byte, must be a + * hardware problem + */ + TRACE(ft_t_bug, + "Unexpected error: failing DMA or FDC controller ?"); + break; + case data_crc_error: + TRACE(ft_t_flow, "Error in sector %d", + FT_SECTOR(buff->sector_offset - 1)); + break; + case id_crc_error: + case id_am_error: + case data_am_error: + TRACE(ft_t_flow, "Error in sector %d", + FT_SECTOR(buff->sector_offset)); + break; + default: + TRACE(ft_t_flow, "Unexpected error at sector %d", + FT_SECTOR(buff->sector_offset)); + break; + } + 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 + */ + +/* the arg `sector' is returned by the fdc and tells us at which sector we + * are positioned at (relative to starting sector of segment) + */ +static void determine_verify_progress(buffer_struct *buff, + error_cause cause, + __u8 sector) +{ + TRACE_FUN(ft_t_any); + + if (cause == no_error && sector == 1) { + buff->sector_offset = FT_SECTORS_PER_SEGMENT; + buff->remaining = 0; + if (TRACE_LEVEL >= ft_t_flow) { + print_progress(buff, cause); + } + } else { + buff->sector_offset = sector - buff->sect; + buff->remaining = FT_SECTORS_PER_SEGMENT - buff->sector_offset; + TRACE(ft_t_noise, "%ssector offset: 0x%04x", + (cause == no_error) ? "unexpected " : "", + buff->sector_offset); + switch (cause) { + case overrun_error: + break; +#if 0 + case no_data_error: + buff->retry = FT_SOFT_RETRIES; + if (buff->hard_error_map && + buff->sector_offset > 1 && + (buff->hard_error_map & + (1 << (buff->sector_offset-2)))) { + buff->retry --; + } + break; +#endif + default: + buff->retry = FT_SOFT_RETRIES; + break; + } + if (TRACE_LEVEL >= ft_t_flow) { + print_progress(buff, cause); + } + /* Sector_offset points to the problem area Now adjust + * sector_offset so it always points one past he failing + * sector. I.e. skip the bad sector. + */ + ++buff->sector_offset; + --buff->remaining; + update_error_maps(buff, buff->sector_offset - 1); + } + TRACE_EXIT; +} + +static void determine_progress(buffer_struct *buff, + error_cause cause, + __u8 sector) +{ + unsigned int dma_residue; + TRACE_FUN(ft_t_any); + + /* 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); + if (cause != no_error || dma_residue != 0) { + TRACE(ft_t_noise, "%sDMA residue: 0x%04x", + (cause == no_error) ? "unexpected " : "", + dma_residue); + /* adjust to actual value: */ + if (dma_residue == 0) { + /* this happens sometimes with overrun errors. + * I don't know whether we could ignore the + * overrun error. Play save. + */ + buff->sector_count --; + } else { + buff->sector_count -= ((dma_residue + + (FT_SECTOR_SIZE - 1)) / + FT_SECTOR_SIZE); + } + } + /* 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 * + FT_SECTOR_SIZE); + buff->remaining -= buff->sector_count; + buff->bad_sector_map >>= buff->sector_count; + } + if (TRACE_LEVEL >= ft_t_flow) { + print_progress(buff, cause); + } + if (cause != no_error) { + if (buff->remaining == 0) { + TRACE(ft_t_warn, "foo?\n" + KERN_INFO "count : %d\n" + KERN_INFO "offset: %d\n" + KERN_INFO "soft : %08x\n" + KERN_INFO "hard : %08x", + buff->sector_count, + buff->sector_offset, + buff->soft_error_map, + buff->hard_error_map); + } + /* 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 always points one past he + * failing sector. I.e. skip the bad sector. + */ + 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 (ftape_current_cylinder > cmd) { + return ftape_current_cylinder - cmd; + } else { + return ftape_current_cylinder + cmd; + } +} + +static void pause_tape(int retry, int mode) +{ + int result; + __u8 out[3] = {FDC_SEEK, ft_drive_sel, 0}; + TRACE_FUN(ft_t_any); + + /* We'll use a raw seek command to get the tape to rewind and + * stop for a retry. + */ + ++ft_history.rewinds; + if (qic117_cmds[ftape_current_command].non_intr) { + TRACE(ft_t_warn, "motion command may be issued too soon"); + } + if (retry && (mode == fdc_reading_data || + mode == fdc_reading_id || + mode == fdc_verifying)) { + ftape_current_command = QIC_MICRO_STEP_PAUSE; + ftape_might_be_off_track = 1; + } else { + ftape_current_command = QIC_PAUSE; + } + out[2] = calc_steps(ftape_current_command); + result = fdc_command(out, 3); /* issue QIC_117 command */ + ftape_current_cylinder = out[ 2]; + if (result < 0) { + TRACE(ft_t_noise, "qic-pause failed, status = %d", result); + } else { + ft_location.known = 0; + ft_runner_status = idle; + ft_hide_interrupt = 1; + ftape_tape_running = 0; + } + TRACE_EXIT; +} + +static void continue_xfer(buffer_struct *buff, + fdc_mode_enum mode, + unsigned int skip) +{ + int write = 0; + TRACE_FUN(ft_t_any); + + if (mode == fdc_writing_data || mode == fdc_deleting) { + write = 1; + } + /* This part can be removed if it never happens + */ + if (skip > 0 && + (ft_runner_status != running || + (write && (buff->status != writing)) || + (!write && (buff->status != reading && + buff->status != verifying)))) { + TRACE(ft_t_err, "unexpected runner/buffer state %d/%d", + ft_runner_status, buff->status); + buff->status = error; + /* finish this buffer: */ + (void)ftape_next_buffer(ft_queue_head); + ft_runner_status = aborting; + fdc_mode = fdc_idle; + } else if (buff->remaining > 0 && ftape_calc_next_cluster(buff) > 0) { + /* still sectors left in current segment, continue + * with this segment + */ + if (fdc_setup_read_write(buff, mode) < 0) { + /* failed, abort operation + */ + buff->bytes = buff->ptr - buff->address; + buff->status = error; + /* finish this buffer: */ + (void)ftape_next_buffer(ft_queue_head); + ft_runner_status = aborting; + fdc_mode = fdc_idle; + } + } else { + /* current segment completed + */ + unsigned int last_segment = buff->segment_id; + int eot = ((last_segment + 1) % ft_segments_per_track) == 0; + unsigned int next = buff->next_segment; /* 0 means stop ! */ + + buff->bytes = buff->ptr - buff->address; + buff->status = done; + buff = ftape_next_buffer(ft_queue_head); + if (eot) { + /* finished last segment on current track, + * can't continue + */ + ft_runner_status = logical_eot; + fdc_mode = fdc_idle; + TRACE_EXIT; + } + if (next <= 0) { + /* don't continue with next segment + */ + TRACE(ft_t_noise, "no %s allowed, stopping tape", + (write) ? "write next" : "read ahead"); + pause_tape(0, mode); + ft_runner_status = idle; /* not quite true until + * next irq + */ + TRACE_EXIT; + } + /* continue with next segment + */ + if (buff->status != waiting) { + TRACE(ft_t_noise, "all input buffers %s, pausing tape", + (write) ? "empty" : "full"); + pause_tape(0, mode); + ft_runner_status = idle; /* not quite true until + * next irq + */ + TRACE_EXIT; + } + if (write && next != buff->segment_id) { + TRACE(ft_t_noise, + "segments out of order, aborting write"); + ft_runner_status = do_abort; + fdc_mode = fdc_idle; + TRACE_EXIT; + } + ftape_setup_new_segment(buff, next, 0); + if (stop_read_ahead) { + buff->next_segment = 0; + stop_read_ahead = 0; + } + if (ftape_calc_next_cluster(buff) == 0 || + fdc_setup_read_write(buff, mode) != 0) { + TRACE(ft_t_err, "couldn't start %s-ahead", + write ? "write" : "read"); + ft_runner_status = do_abort; + fdc_mode = fdc_idle; + } else { + /* keep on going */ + switch (ft_driver_state) { + case reading: buff->status = reading; break; + case verifying: buff->status = verifying; break; + case writing: buff->status = writing; break; + case deleting: buff->status = deleting; break; + default: + TRACE(ft_t_err, + "BUG: ft_driver_state %d should be one out of " + "{reading, writing, verifying, deleting}", + ft_driver_state); + buff->status = write ? writing : reading; + break; + } + } + } + TRACE_EXIT; +} + +static void retry_sector(buffer_struct *buff, + int mode, + unsigned int skip) +{ + TRACE_FUN(ft_t_any); + + TRACE(ft_t_noise, "%s error, will retry", + (mode == fdc_writing_data || mode == fdc_deleting) ? "write" : "read"); + pause_tape(1, mode); + ft_runner_status = aborting; + buff->status = error; + buff->skip = skip; + TRACE_EXIT; +} + +static unsigned int find_resume_point(buffer_struct *buff) +{ + int i = 0; + SectorMap mask; + SectorMap map; + TRACE_FUN(ft_t_any); + + /* 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 always be at least one hard or soft error ! + */ + if (buff->sector_offset < 1 || buff->sector_offset > 32) { + TRACE(ft_t_bug, "BUG: sector_offset = %d", + buff->sector_offset); + TRACE_EXIT 0; + } + 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; + } + TRACE(ft_t_noise, "at sector %d", FT_SECTOR(i)); + } else { + map = buff->hard_error_map & mask; + i = buff->sector_offset - 1; + if (map) { + while ((map & (1 << i)) == 0) { + --i; + } + TRACE(ft_t_noise, "after sector %d", FT_SECTOR(i)); + ++i; /* first sector after last hard error */ + } else { + TRACE(ft_t_bug, "BUG: no soft or hard errors"); + } + } + TRACE_EXIT i; +} + +/* check possible dma residue when formatting, update position record in + * buffer struct. This is, of course, modelled after determine_progress(), but + * we don't need to set up for retries because the format process cannot be + * interrupted (except at the end of the tape track). + */ +static int determine_fmt_progress(buffer_struct *buff, error_cause cause) +{ + unsigned int dma_residue; + TRACE_FUN(ft_t_any); + + /* 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); + if (cause != no_error || dma_residue != 0) { + TRACE(ft_t_info, "DMA residue = 0x%04x", dma_residue); + fdc_mode = fdc_idle; + switch(cause) { + case no_error: + ft_runner_status = aborting; + buff->status = idle; + break; + case overrun_error: + /* got an overrun error on the first byte, must be a + * hardware problem + */ + TRACE(ft_t_bug, + "Unexpected error: failing DMA controller ?"); + ft_runner_status = do_abort; + buff->status = error; + break; + default: + TRACE(ft_t_noise, "Unexpected error at segment %d", + buff->segment_id); + ft_runner_status = do_abort; + buff->status = error; + break; + } + TRACE_EXIT -EIO; /* can only retry entire track in format mode + */ + } + /* Update var's influenced by the DMA operation. + */ + buff->ptr += FT_SECTORS_PER_SEGMENT * 4; + buff->bytes -= FT_SECTORS_PER_SEGMENT * 4; + buff->remaining -= FT_SECTORS_PER_SEGMENT; + buff->segment_id ++; /* done with segment */ + TRACE_EXIT 0; +} + +/* + * Continue formatting, switch buffers if there is no data left in + * current buffer. This is, of course, modelled after + * continue_xfer(), but we don't need to set up for retries because + * the format process cannot be interrupted (except at the end of the + * tape track). + */ +static void continue_formatting(buffer_struct *buff) +{ + TRACE_FUN(ft_t_any); + + if (buff->remaining <= 0) { /* no space left in dma buffer */ + unsigned int next = buff->next_segment; + + if (next == 0) { /* end of tape track */ + buff->status = done; + ft_runner_status = logical_eot; + fdc_mode = fdc_idle; + TRACE(ft_t_noise, "Done formatting track %d", + ft_location.track); + TRACE_EXIT; + } + /* + * switch to next buffer! + */ + buff->status = done; + buff = ftape_next_buffer(ft_queue_head); + + if (buff->status != waiting || next != buff->segment_id) { + goto format_setup_error; + } + } + if (fdc_setup_formatting(buff) < 0) { + goto format_setup_error; + } + buff->status = formatting; + TRACE(ft_t_fdc_dma, "Formatting segment %d on track %d", + buff->segment_id, ft_location.track); + TRACE_EXIT; + format_setup_error: + ft_runner_status = do_abort; + fdc_mode = fdc_idle; + buff->status = error; + TRACE(ft_t_err, "Error setting up for segment %d on track %d", + buff->segment_id, ft_location.track); + TRACE_EXIT; + +} + +/* this handles writing, read id, reading and formatting + */ +static void handle_fdc_busy(buffer_struct *buff) +{ + static int no_data_error_count; + int retry = 0; + error_cause cause; + __u8 in[7]; + int skip; + fdc_mode_enum fmode = fdc_mode; + TRACE_FUN(ft_t_any); + + if (fdc_result(in, 7) < 0) { /* better get it fast ! */ + TRACE(ft_t_err, + "Probably fatal error during FDC Result Phase\n" + KERN_INFO + "drive may hang until (power on) reset :-("); + /* what to do next ???? + */ + TRACE_EXIT; + } + cause = decode_irq_cause(fdc_mode, in); +#ifdef TESTING + { int i; + for (i = 0; i < (int)ft_nr_buffers; ++i) + TRACE(ft_t_any, "buffer[%d] status: %d, segment_id: %d", + i, ft_buffer[i]->status, ft_buffer[i]->segment_id); + } +#endif + if (fmode == fdc_reading_data && ft_driver_state == verifying) { + fmode = fdc_verifying; + } + switch (fmode) { + case fdc_verifying: + if (ft_runner_status == aborting || + ft_runner_status == do_abort) { + TRACE(ft_t_noise,"aborting %s",fdc_mode_txt(fdc_mode)); + break; + } + if (buff->retry > 0) { + TRACE(ft_t_flow, "this is retry nr %d", buff->retry); + } + switch (cause) { + case no_error: + no_data_error_count = 0; + determine_verify_progress(buff, cause, in[5]); + if (in[2] & 0x40) { + /* This should not happen when verifying + */ + TRACE(ft_t_warn, + "deleted data in segment %d/%d", + buff->segment_id, + FT_SECTOR(buff->sector_offset - 1)); + buff->remaining = 0; /* abort transfer */ + buff->hard_error_map = EMPTY_SEGMENT; + skip = 1; + } else { + skip = 0; + } + continue_xfer(buff, fdc_mode, skip); + break; + case no_data_error: + no_data_error_count ++; + case overrun_error: + retry ++; + case id_am_error: + case id_crc_error: + case data_am_error: + case data_crc_error: + determine_verify_progress(buff, cause, in[5]); + if (cause == no_data_error) { + if (no_data_error_count >= 2) { + TRACE(ft_t_warn, + "retrying because of successive " + "no data errors"); + no_data_error_count = 0; + } else { + retry --; + } + } else { + no_data_error_count = 0; + } + if (retry) { + skip = find_resume_point(buff); + } else { + skip = buff->sector_offset; + } + if (retry && skip < 32) { + retry_sector(buff, fdc_mode, skip); + } else { + continue_xfer(buff, fdc_mode, skip); + } + update_history(cause); + break; + default: + /* Don't know why this could happen + * but find out. + */ + determine_verify_progress(buff, cause, in[5]); + retry_sector(buff, fdc_mode, 0); + TRACE(ft_t_err, "Error: unexpected error"); + break; + } + break; + case fdc_reading_data: +#ifdef TESTING + /* I'm sorry, but: NOBODY ever used this trace + * messages for ages. I guess that Bas was the last person + * that ever really used this (thank you, between the lines) + */ + if (cause == no_error) { + TRACE(ft_t_flow,"reading segment %d",buff->segment_id); + } else { + TRACE(ft_t_noise, "error reading segment %d", + buff->segment_id); + TRACE(ft_t_noise, "\n" + KERN_INFO + "IRQ:C: 0x%02x, H: 0x%02x, R: 0x%02x, N: 0x%02x\n" + KERN_INFO + "BUF:C: 0x%02x, H: 0x%02x, R: 0x%02x", + in[3], in[4], in[5], in[6], + buff->cyl, buff->head, buff->sect); + } +#endif + if (ft_runner_status == aborting || + ft_runner_status == do_abort) { + TRACE(ft_t_noise,"aborting %s",fdc_mode_txt(fdc_mode)); + break; + } + 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. + */ + buff->remaining = 0; /* skip failing sector */ + /* buff->ptr = buff->address; */ + /* fake success: */ + continue_xfer(buff, fdc_mode, 1); + /* trace calls are expensive: place them AFTER + * the real stuff has been done. + * + */ + TRACE(ft_t_noise, "skipping empty segment %d (read), size? %d", + buff->segment_id, buff->ptr - buff->address); + TRACE_EXIT; + } + if (buff->retry > 0) { + TRACE(ft_t_flow, "this is retry nr %d", buff->retry); + } + switch (cause) { + case no_error: + determine_progress(buff, cause, in[5]); + if (in[2] & 0x40) { + /* Handle deleted data in header segments. + * Skip segment and force read-ahead. + */ + TRACE(ft_t_warn, + "deleted data in segment %d/%d", + buff->segment_id, + FT_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 on next segment */ + stop_read_ahead = 1; + } + /* force read-ahead: */ + buff->next_segment = + buff->segment_id + 1; + skip = (FT_SECTORS_PER_SEGMENT - + buff->sector_offset); + } else { + skip = 0; + } + continue_xfer(buff, 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. + */ + retry ++; + case id_am_error: + case id_crc_error: + case data_am_error: + case data_crc_error: + case overrun_error: + retry += (buff->soft_error_map != 0 || + buff->hard_error_map != 0); + determine_progress(buff, cause, in[5]); +#if 1 || defined(TESTING) + if (cause == overrun_error) retry ++; +#endif + if (retry) { + skip = find_resume_point(buff); + } else { + skip = buff->sector_offset; + } + /* 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). + */ + /* cH: 23/02/97: if the last sector in the + * segment was a hard error, then there is + * no sense in a retry. This occasion seldom + * occurs but ... @:³²¸`@%&§$ + */ + if (retry && skip < 32) { + retry_sector(buff, fdc_mode, skip); + } else { + continue_xfer(buff, fdc_mode, skip); + } + update_history(cause); + break; + default: + /* Don't know why this could happen + * but find out. + */ + determine_progress(buff, cause, in[5]); + retry_sector(buff, fdc_mode, 0); + TRACE(ft_t_err, "Error: unexpected error"); + break; + } + break; + case fdc_reading_id: + if (cause == no_error) { + fdc_cyl = in[3]; + fdc_head = in[4]; + fdc_sect = in[5]; + TRACE(ft_t_fdc_dma, + "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(ft_t_flow, "Didn't find valid sector Id"); + } + fdc_mode = fdc_idle; + break; + case fdc_deleting: + case fdc_writing_data: +#ifdef TESTING + if (cause == no_error) { + TRACE(ft_t_flow, "writing segment %d", buff->segment_id); + } else { + TRACE(ft_t_noise, "error writing segment %d", + buff->segment_id); + } +#endif + if (ft_runner_status == aborting || + ft_runner_status == do_abort) { + TRACE(ft_t_flow, "aborting %s",fdc_mode_txt(fdc_mode)); + break; + } + if (buff->retry > 0) { + TRACE(ft_t_flow, "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(ft_t_noise, "skipping empty segment (write)"); + buff->remaining = 0; /* skip failing sector */ + /* fake success: */ + continue_xfer(buff, fdc_mode, 1); + break; + } + switch (cause) { + case no_error: + determine_progress(buff, cause, in[5]); + continue_xfer(buff, 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, in[5]); + skip = find_resume_point(buff); + retry_sector(buff, fdc_mode, skip); + break; + default: + if (in[1] & 0x02) { + TRACE(ft_t_err, "media not writable"); + } else { + TRACE(ft_t_bug, "unforeseen write error"); + } + fdc_mode = fdc_idle; + break; + } + break; /* fdc_deleting || fdc_writing_data */ + case fdc_formatting: + /* The interrupt comes after formatting a segment. We then + * have to set up QUICKLY for the next segment. But + * afterwards, there is plenty of time. + */ + switch (cause) { + case no_error: + /* would like to keep most of the formatting stuff + * outside the isr code, but timing is too critical + */ + if (determine_fmt_progress(buff, cause) >= 0) { + continue_formatting(buff); + } + break; + case no_data_error: + case id_am_error: + case id_crc_error: + case data_am_error: + case overrun_error: + default: + determine_fmt_progress(buff, cause); + update_history(cause); + if (in[1] & 0x02) { + TRACE(ft_t_err, "media not writable"); + } else { + TRACE(ft_t_bug, "unforeseen write error"); + } + break; + } /* cause */ + break; + default: + TRACE(ft_t_warn, "Warning: unexpected irq during: %s", + fdc_mode_txt(fdc_mode)); + fdc_mode = fdc_idle; + break; + } + TRACE_EXIT; +} + +/* FDC interrupt service routine. + */ +void fdc_isr(void) +{ + static int isr_active; +#ifdef TESTING + unsigned int t0 = ftape_timestamp(); +#endif + TRACE_FUN(ft_t_any); + + if (isr_active++) { + --isr_active; + TRACE(ft_t_bug, "BUG: nested interrupt, not good !"); + *fdc.hook = fdc_isr; /* hook our handler into the fdc + * code again + */ + TRACE_EXIT; + } + sti(); + if (inb_p(fdc.msr) & FDC_BUSY) { /* Entering Result Phase */ + ft_hide_interrupt = 0; + handle_fdc_busy(ftape_get_buffer(ft_queue_head)); + if (ft_runner_status == do_abort) { + /* cease operation, remember tape position + */ + TRACE(ft_t_flow, "runner aborting"); + ft_runner_status = aborting; + ++ft_expected_stray_interrupts; + } + } else { /* !FDC_BUSY */ + /* clear interrupt, cause should be gotten by issuing + * a Sense Interrupt Status command. + */ + if (fdc_mode == fdc_recalibrating || fdc_mode == fdc_seeking) { + if (ft_hide_interrupt) { + int st0; + int pcn; + + if (fdc_sense_interrupt_status(&st0, &pcn) < 0) + TRACE(ft_t_err, + "sense interrupt status failed"); + ftape_current_cylinder = pcn; + TRACE(ft_t_flow, "handled hidden interrupt"); + } + ft_seek_completed = 1; + fdc_mode = fdc_idle; + } else if (!waitqueue_active(&ftape_wait_intr)) { + if (ft_expected_stray_interrupts == 0) { + TRACE(ft_t_warn, "unexpected stray interrupt"); + } else { + TRACE(ft_t_flow, "expected stray interrupt"); + --ft_expected_stray_interrupts; + } + } else { + if (fdc_mode == fdc_reading_data || + fdc_mode == fdc_verifying || + fdc_mode == fdc_writing_data || + fdc_mode == fdc_deleting || + fdc_mode == fdc_formatting || + fdc_mode == fdc_reading_id) { + if (inb_p(fdc.msr) & FDC_BUSY) { + TRACE(ft_t_bug, + "***** FDC failure, busy too late"); + } else { + TRACE(ft_t_bug, + "***** FDC failure, no busy"); + } + } else { + TRACE(ft_t_fdc_dma, "awaited stray interrupt"); + } + } + ft_hide_interrupt = 0; + } + /* Handle sleep code. + */ + if (!ft_hide_interrupt) { + ft_interrupt_seen ++; + if (waitqueue_active(&ftape_wait_intr)) { + wake_up_interruptible(&ftape_wait_intr); + } + } else { + TRACE(ft_t_flow, "hiding interrupt while %s", + waitqueue_active(&ftape_wait_intr) ? "waiting":"active"); + } +#ifdef TESTING + t0 = ftape_timediff(t0, ftape_timestamp()); + if (t0 >= 1000) { + /* only tell us about long calls */ + TRACE(ft_t_noise, "isr() duration: %5d usec", t0); + } +#endif + *fdc.hook = fdc_isr; /* hook our handler into the fdc code again */ + --isr_active; + TRACE_EXIT; +} diff --git a/drivers/char/ftape/lowlevel/fdc-isr.h b/drivers/char/ftape/lowlevel/fdc-isr.h new file mode 100644 index 000000000000..065aa978942d --- /dev/null +++ b/drivers/char/ftape/lowlevel/fdc-isr.h @@ -0,0 +1,55 @@ +#ifndef _FDC_ISR_H +#define _FDC_ISR_H + +/* + * Copyright (C) 1993-1996 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/fdc-isr.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:18:07 $ + * + * This file declares the global variables necessary to + * synchronize the interrupt service routine (isr) with the + * remainder of the QIC-40/80/3010/3020 floppy-tape driver + * "ftape" for Linux. + */ + +/* + * fdc-isr.c defined public variables + */ +extern volatile int ft_expected_stray_interrupts; /* masks stray interrupts */ +extern volatile int ft_seek_completed; /* flag set by isr */ +extern volatile int ft_interrupt_seen; /* flag set by isr */ +extern volatile int ft_hide_interrupt; /* flag set by isr */ + +/* + * 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 --git a/drivers/char/ftape/lowlevel/ftape-bsm.c b/drivers/char/ftape/lowlevel/ftape-bsm.c new file mode 100644 index 000000000000..d1a301cc344f --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-bsm.c @@ -0,0 +1,491 @@ +/* + * Copyright (C) 1994-1996 Bas Laarhoven, + * (C) 1996-1997 Claus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-bsm.c,v $ + * $Revision: 1.3 $ + * $Date: 1997/10/05 19:15:15 $ + * + * 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/string.h> + +#include <linux/ftape.h> +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/ftape-bsm.h" +#include "../lowlevel/ftape-ctl.h" +#include "../lowlevel/ftape-rw.h" + +/* Global vars. + */ + +/* Local vars. + */ +static __u8 *bad_sector_map; +static SectorCount *bsm_hash_ptr; + +typedef enum { + forward, backward +} mode_type; + +#if 0 +static void ftape_put_bad_sector_entry(int segment_id, SectorMap new_map); +#endif + +#if 0 +/* fix_tape converts a normal QIC-80 tape into a 'wide' tape. + * For testing purposes only ! + */ +void fix_tape(__u8 * buffer, ft_format_type new_code) +{ + static __u8 list[BAD_SECTOR_MAP_SIZE]; + SectorMap *src_ptr = (SectorMap *) list; + __u8 *dst_ptr = bad_sector_map; + SectorMap map; + unsigned int sector = 1; + int i; + + if (format_code != fmt_var && format_code != fmt_big) { + memcpy(list, bad_sector_map, sizeof(list)); + memset(bad_sector_map, 0, sizeof(bad_sector_map)); + while ((__u8 *) src_ptr - list < sizeof(list)) { + map = *src_ptr++; + if (map == EMPTY_SEGMENT) { + *(SectorMap *) dst_ptr = 0x800000 + sector; + dst_ptr += 3; + sector += SECTORS_PER_SEGMENT; + } else { + for (i = 0; i < SECTORS_PER_SEGMENT; ++i) { + if (map & 1) { + *(SewctorMap *) dst_ptr = sector; + dst_ptr += 3; + } + map >>= 1; + ++sector; + } + } + } + } + bad_sector_map_changed = 1; + *(buffer + 4) = new_code; /* put new format code */ + if (format_code != fmt_var && new_code == fmt_big) { + PUT4(buffer, FT_6_HSEG_1, (__u32)GET2(buffer, 6)); + PUT4(buffer, FT_6_HSEG_2, (__u32)GET2(buffer, 8)); + PUT4(buffer, FT_6_FRST_SEG, (__u32)GET2(buffer, 10)); + PUT4(buffer, FT_6_LAST_SEG, (__u32)GET2(buffer, 12)); + memset(buffer+6, '\0', 8); + } + format_code = new_code; +} + +#endif + +/* given buffer that contains a header segment, find the end of + * of the bsm list + */ +__u8 * ftape_find_end_of_bsm_list(__u8 * address) +{ + __u8 *ptr = address + FT_HEADER_END; /* start of bsm list */ + __u8 *limit = address + FT_SEGMENT_SIZE; + while (ptr + 2 < limit) { + if (ptr[0] || ptr[1] || ptr[2]) { + ptr += 3; + } else { + return ptr; + } + } + return NULL; +} + +static inline void put_sector(SectorCount *ptr, unsigned int sector) +{ + ptr->bytes[0] = sector & 0xff; + sector >>= 8; + ptr->bytes[1] = sector & 0xff; + sector >>= 8; + ptr->bytes[2] = sector & 0xff; +} + +static inline unsigned int get_sector(SectorCount *ptr) +{ +#if 1 + unsigned int sector; + + sector = ptr->bytes[0]; + sector += ptr->bytes[1] << 8; + sector += ptr->bytes[2] << 16; + + return sector; +#else + /* GET4 gets the next four bytes in Intel little endian order + * and converts them to host byte order and handles unaligned + * access. + */ + return (GET4(ptr, 0) & 0x00ffffff); /* back to host byte order */ +#endif +} + +static void bsm_debug_fake(void) +{ + /* for testing of bad sector handling at end of tape + */ +#if 0 + ftape_put_bad_sector_entry(segments_per_track * tracks_per_tape - 3, + 0x000003e0; + ftape_put_bad_sector_entry(segments_per_track * tracks_per_tape - 2, + 0xff3fffff; + ftape_put_bad_sector_entry(segments_per_track * tracks_per_tape - 1, + 0xffffe000; +#endif + /* Enable to test bad sector handling + */ +#if 0 + ftape_put_bad_sector_entry(30, 0xfffffffe) + ftape_put_bad_sector_entry(32, 0x7fffffff); + ftape_put_bad_sector_entry(34, 0xfffeffff); + ftape_put_bad_sector_entry(36, 0x55555555); + ftape_put_bad_sector_entry(38, 0xffffffff); + ftape_put_bad_sector_entry(50, 0xffff0000); + ftape_put_bad_sector_entry(51, 0xffffffff); + ftape_put_bad_sector_entry(52, 0xffffffff); + ftape_put_bad_sector_entry(53, 0x0000ffff); +#endif + /* Enable when testing multiple volume tar dumps. + */ +#if 0 + { + int i; + + for (i = ft_first_data_segment; + i <= ft_last_data_segment - 7; ++i) { + ftape_put_bad_sector_entry(i, EMPTY_SEGMENT); + } + } +#endif + /* Enable when testing bit positions in *_error_map + */ +#if 0 + { + int i; + + for (i = first_data_segment; i <= last_data_segment; ++i) { + ftape_put_bad_sector_entry(i, + ftape_get_bad_sector_entry(i) + | 0x00ff00ff); + } + } +#endif +} + +static void print_bad_sector_map(void) +{ + unsigned int good_sectors; + unsigned int total_bad = 0; + int i; + TRACE_FUN(ft_t_flow); + + if (ft_format_code == fmt_big || + ft_format_code == fmt_var || + ft_format_code == fmt_1100ft) { + SectorCount *ptr = (SectorCount *)bad_sector_map; + unsigned int sector; + __u16 *ptr16; + + while((sector = get_sector(ptr++)) != 0) { + if ((ft_format_code == fmt_big || + ft_format_code == fmt_var) && + sector & 0x800000) { + total_bad += FT_SECTORS_PER_SEGMENT - 3; + TRACE(ft_t_noise, "bad segment at sector: %6d", + sector & 0x7fffff); + } else { + ++total_bad; + TRACE(ft_t_noise, "bad sector: %6d", sector); + } + } + /* Display old ftape's end-of-file marks + */ + ptr16 = (__u16*)ptr; + while ((sector = get_unaligned(ptr16++)) != 0) { + TRACE(ft_t_noise, "Old ftape eof mark: %4d/%2d", + sector, get_unaligned(ptr16++)); + } + } else { /* fixed size format */ + for (i = ft_first_data_segment; + i < (int)(ft_segments_per_track * ft_tracks_per_tape); ++i) { + SectorMap map = ((SectorMap *) bad_sector_map)[i]; + + if (map) { + TRACE(ft_t_noise, + "bsm for segment %4d: 0x%08x", i, (unsigned int)map); + total_bad += ((map == EMPTY_SEGMENT) + ? FT_SECTORS_PER_SEGMENT - 3 + : count_ones(map)); + } + } + } + good_sectors = + ((ft_segments_per_track * ft_tracks_per_tape - ft_first_data_segment) + * (FT_SECTORS_PER_SEGMENT - 3)) - total_bad; + TRACE(ft_t_info, "%d Kb usable on this tape", good_sectors); + if (total_bad == 0) { + TRACE(ft_t_info, + "WARNING: this tape has no bad blocks registered !"); + } else { + TRACE(ft_t_info, "%d bad sectors", total_bad); + } + TRACE_EXIT; +} + + +void ftape_extract_bad_sector_map(__u8 * buffer) +{ + TRACE_FUN(ft_t_any); + + /* Fill the bad sector map with the contents of buffer. + */ + if (ft_format_code == fmt_var || ft_format_code == fmt_big) { + /* 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. + */ + bad_sector_map = &buffer[FT_HEADER_END]; + } else { + /* non-wide QIC-80 tapes have a failed sector log area that + * mustn't be included in the bad sector map. + */ + bad_sector_map = &buffer[FT_FSL + FT_FSL_SIZE]; + } + if (ft_format_code == fmt_1100ft || + ft_format_code == fmt_var || + ft_format_code == fmt_big) { + bsm_hash_ptr = (SectorCount *)bad_sector_map; + } else { + bsm_hash_ptr = NULL; + } + bsm_debug_fake(); + if (TRACE_LEVEL >= ft_t_info) { + print_bad_sector_map(); + } + TRACE_EXIT; +} + +static inline SectorMap cvt2map(unsigned int sector) +{ + return 1 << (((sector & 0x7fffff) - 1) % FT_SECTORS_PER_SEGMENT); +} + +static inline int cvt2segment(unsigned int sector) +{ + return ((sector & 0x7fffff) - 1) / FT_SECTORS_PER_SEGMENT; +} + +static int forward_seek_entry(int segment_id, + SectorCount **ptr, + SectorMap *map) +{ + unsigned int sector; + int segment; + + do { + sector = get_sector((*ptr)++); + segment = cvt2segment(sector); + } while (sector != 0 && segment < segment_id); + (*ptr) --; /* point to first sector >= segment_id */ + /* Get all sectors in segment_id + */ + if (sector == 0 || segment != segment_id) { + *map = 0; + return 0; + } else if ((sector & 0x800000) && + (ft_format_code == fmt_var || ft_format_code == fmt_big)) { + *map = EMPTY_SEGMENT; + return FT_SECTORS_PER_SEGMENT; + } else { + int count = 1; + SectorCount *tmp_ptr = (*ptr) + 1; + + *map = cvt2map(sector); + while ((sector = get_sector(tmp_ptr++)) != 0 && + (segment = cvt2segment(sector)) == segment_id) { + *map |= cvt2map(sector); + ++count; + } + return count; + } +} + +static int backwards_seek_entry(int segment_id, + SectorCount **ptr, + SectorMap *map) +{ + unsigned int sector; + int segment; /* max unsigned int */ + + if (*ptr <= (SectorCount *)bad_sector_map) { + *map = 0; + return 0; + } + do { + sector = get_sector(--(*ptr)); + segment = cvt2segment(sector); + } while (*ptr > (SectorCount *)bad_sector_map && segment > segment_id); + if (segment > segment_id) { /* at start of list, no entry found */ + *map = 0; + return 0; + } else if (segment < segment_id) { + /* before smaller entry, adjust for overshoot */ + (*ptr) ++; + *map = 0; + return 0; + } else if ((sector & 0x800000) && + (ft_format_code == fmt_big || ft_format_code == fmt_var)) { + *map = EMPTY_SEGMENT; + return FT_SECTORS_PER_SEGMENT; + } else { /* get all sectors in segment_id */ + int count = 1; + + *map = cvt2map(sector); + while(*ptr > (SectorCount *)bad_sector_map) { + sector = get_sector(--(*ptr)); + segment = cvt2segment(sector); + if (segment != segment_id) { + break; + } + *map |= cvt2map(sector); + ++count; + } + if (segment < segment_id) { + (*ptr) ++; + } + return count; + } +} + +#if 0 +static void ftape_put_bad_sector_entry(int segment_id, SectorMap new_map) +{ + SectorCount *ptr = (SectorCount *)bad_sector_map; + int count; + int new_count; + SectorMap map; + TRACE_FUN(ft_t_any); + + if (ft_format_code == fmt_1100ft || + ft_format_code == fmt_var || + ft_format_code == fmt_big) { + 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 (ft_format_code == fmt_var || ft_format_code == fmt_big) { + if (new_count == FT_SECTORS_PER_SEGMENT) { + new_count = 1; + } + if (count == FT_SECTORS_PER_SEGMENT) { + count = 1; + } + } + if (count != new_count) { + /* insert (or delete if < 0) new_count - count + * entries. Move trailing part of list + * including terminating 0. + */ + SectorCount *hi_ptr = ptr; + + do { + } while (get_sector(hi_ptr++) != 0); + /* Note: ptr is of type byte *, and each bad sector + * consumes 3 bytes. + */ + memmove(ptr + new_count, ptr + count, + (size_t)(hi_ptr - (ptr + count))*sizeof(SectorCount)); + } + TRACE(ft_t_noise, "putting map 0x%08x at %p, segment %d", + (unsigned int)new_map, ptr, segment_id); + if (new_count == 1 && new_map == EMPTY_SEGMENT) { + put_sector(ptr++, (0x800001 + + segment_id * + FT_SECTORS_PER_SEGMENT)); + } else { + int i = 0; + + while (new_map) { + if (new_map & 1) { + put_sector(ptr++, + 1 + segment_id * + FT_SECTORS_PER_SEGMENT + i); + } + ++i; + new_map >>= 1; + } + } + } else { + ((SectorMap *) bad_sector_map)[segment_id] = new_map; + } + TRACE_EXIT; +} +#endif /* 0 */ + +SectorMap ftape_get_bad_sector_entry(int segment_id) +{ + if (ft_used_header_segment == -1) { + /* When reading header segment we'll need a blank map. + */ + return 0; + } else if (bsm_hash_ptr != NULL) { + /* Invariants: + * map - mask value returned on last call. + * bsm_hash_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 SectorMap map; + + if (segment_id > last_reference) { + /* Skip all sectors before segment_id + */ + forward_seek_entry(segment_id, &bsm_hash_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, &bsm_hash_ptr, &map); + } /* segment_id == last_reference : keep map */ + last_reference = segment_id; + return map; + } else { + return ((SectorMap *) bad_sector_map)[segment_id]; + } +} + +/* This is simply here to prevent us from overwriting other kernel + * data. Writes will result in NULL Pointer dereference. + */ +void ftape_init_bsm(void) +{ + bad_sector_map = NULL; + bsm_hash_ptr = NULL; +} diff --git a/drivers/char/ftape/lowlevel/ftape-bsm.h b/drivers/char/ftape/lowlevel/ftape-bsm.h new file mode 100644 index 000000000000..ed45465af4d4 --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-bsm.h @@ -0,0 +1,66 @@ +#ifndef _FTAPE_BSM_H +#define _FTAPE_BSM_H + +/* + * Copyright (C) 1994-1996 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-bsm.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:18:07 $ + * + * This file contains definitions for the bad sector map handling + * routines for the QIC-117 floppy-tape driver for Linux. + */ + +#include <linux/ftape.h> +#include <linux/ftape-header-segment.h> + +#define EMPTY_SEGMENT (0xffffffff) +#define FAKE_SEGMENT (0xfffffffe) + +/* maximum (format code 4) bad sector map size (bytes). + */ +#define BAD_SECTOR_MAP_SIZE (29 * SECTOR_SIZE - 256) + +/* format code 4 bad sector entry, ftape uses this + * internally for all format codes + */ +typedef __u32 SectorMap; +/* variable and 1100 ft bad sector map entry. These three bytes represent + * a single sector address measured from BOT. + */ +typedef struct NewSectorMap { + __u8 bytes[3]; +} SectorCount; + + +/* + * ftape-bsm.c defined global vars. + */ + +/* + * ftape-bsm.c defined global functions. + */ +extern void update_bad_sector_map(__u8 * buffer); +extern void ftape_extract_bad_sector_map(__u8 * buffer); +extern SectorMap ftape_get_bad_sector_entry(int segment_id); +extern __u8 *ftape_find_end_of_bsm_list(__u8 * address); +extern void ftape_init_bsm(void); + +#endif diff --git a/drivers/char/ftape/lowlevel/ftape-buffer.c b/drivers/char/ftape/lowlevel/ftape-buffer.c new file mode 100644 index 000000000000..54af20cd9a2c --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-buffer.c @@ -0,0 +1,129 @@ +/* + * Copyright (C) 1997 Claus-Justus Heine + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-buffer.c,v $ + * $Revision: 1.3 $ + * $Date: 1997/10/16 23:33:11 $ + * + * This file contains the allocator/dealloctor for ftape's dynamic dma + * buffer. + */ + +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <asm/dma.h> + +#include <linux/ftape.h> +#include "../lowlevel/ftape-rw.h" +#include "../lowlevel/ftape-read.h" +#include "../lowlevel/ftape-tracing.h" + +/* DMA'able memory allocation stuff. + */ + +static inline void *dmaalloc(size_t size) +{ + unsigned long addr; + + if (size == 0) { + return NULL; + } + addr = __get_dma_pages(GFP_KERNEL, get_order(size)); + if (addr) { + struct page *page; + + for (page = virt_to_page(addr); page < virt_to_page(addr+size); page++) + SetPageReserved(page); + } + return (void *)addr; +} + +static inline void dmafree(void *addr, size_t size) +{ + if (size > 0) { + struct page *page; + + for (page = virt_to_page((unsigned long)addr); + page < virt_to_page((unsigned long)addr+size); page++) + ClearPageReserved(page); + free_pages((unsigned long) addr, get_order(size)); + } +} + +static int add_one_buffer(void) +{ + TRACE_FUN(ft_t_flow); + + if (ft_nr_buffers >= FT_MAX_NR_BUFFERS) { + TRACE_EXIT -ENOMEM; + } + ft_buffer[ft_nr_buffers] = kmalloc(sizeof(buffer_struct), GFP_KERNEL); + if (ft_buffer[ft_nr_buffers] == NULL) { + TRACE_EXIT -ENOMEM; + } + memset(ft_buffer[ft_nr_buffers], 0, sizeof(buffer_struct)); + ft_buffer[ft_nr_buffers]->address = dmaalloc(FT_BUFF_SIZE); + if (ft_buffer[ft_nr_buffers]->address == NULL) { + kfree(ft_buffer[ft_nr_buffers]); + ft_buffer[ft_nr_buffers] = NULL; + TRACE_EXIT -ENOMEM; + } + ft_nr_buffers ++; + TRACE(ft_t_info, "buffer nr #%d @ %p, dma area @ %p", + ft_nr_buffers, + ft_buffer[ft_nr_buffers-1], + ft_buffer[ft_nr_buffers-1]->address); + TRACE_EXIT 0; +} + +static void del_one_buffer(void) +{ + TRACE_FUN(ft_t_flow); + if (ft_nr_buffers > 0) { + TRACE(ft_t_info, "releasing buffer nr #%d @ %p, dma area @ %p", + ft_nr_buffers, + ft_buffer[ft_nr_buffers-1], + ft_buffer[ft_nr_buffers-1]->address); + ft_nr_buffers --; + dmafree(ft_buffer[ft_nr_buffers]->address, FT_BUFF_SIZE); + kfree(ft_buffer[ft_nr_buffers]); + ft_buffer[ft_nr_buffers] = NULL; + } + TRACE_EXIT; +} + +int ftape_set_nr_buffers(int cnt) +{ + int delta = cnt - ft_nr_buffers; + TRACE_FUN(ft_t_flow); + + if (delta > 0) { + while (delta--) { + if (add_one_buffer() < 0) { + TRACE_EXIT -ENOMEM; + } + } + } else if (delta < 0) { + while (delta++) { + del_one_buffer(); + } + } + ftape_zap_read_buffers(); + TRACE_EXIT 0; +} diff --git a/drivers/char/ftape/lowlevel/ftape-buffer.h b/drivers/char/ftape/lowlevel/ftape-buffer.h new file mode 100644 index 000000000000..eec99cee8f82 --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-buffer.h @@ -0,0 +1,32 @@ +#ifndef _FTAPE_BUFFER_H +#define _FTAPE_BUFFER_H + +/* + * Copyright (C) 1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-buffer.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:18:08 $ + * + * This file contains the allocator/dealloctor for ftape's dynamic dma + * buffer. + */ + +extern int ftape_set_nr_buffers(int cnt); + +#endif diff --git a/drivers/char/ftape/lowlevel/ftape-calibr.c b/drivers/char/ftape/lowlevel/ftape-calibr.c new file mode 100644 index 000000000000..956b2586e138 --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-calibr.c @@ -0,0 +1,276 @@ +/* + * Copyright (C) 1993-1996 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-calibr.c,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:18:08 $ + * + * GP calibration routine for processor speed dependent + * functions. + */ + +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/jiffies.h> +#include <asm/system.h> +#include <asm/io.h> +#if defined(__alpha__) +# include <asm/hwrpb.h> +#elif defined(__x86_64__) +# include <asm/msr.h> +# include <asm/timex.h> +#elif defined(__i386__) +# include <linux/timex.h> +#endif +#include <linux/ftape.h> +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/ftape-calibr.h" +#include "../lowlevel/fdc-io.h" + +#undef DEBUG + +#if !defined(__alpha__) && !defined(__i386__) && !defined(__x86_64__) +# error Ftape is not implemented for this architecture! +#endif + +#if defined(__alpha__) || defined(__x86_64__) +static unsigned long ps_per_cycle = 0; +#endif + +static spinlock_t calibr_lock; + +/* + * Note: On Intel PCs, the clock ticks at 100 Hz (HZ==100) which is + * too slow for certain timeouts (and that clock doesn't even tick + * when interrupts are disabled). For that reason, the 8254 timer is + * used directly to implement fine-grained timeouts. However, on + * Alpha PCs, the 8254 is *not* used to implement the clock tick + * (which is 1024 Hz, normally) and the 8254 timer runs at some + * "random" frequency (it seems to run at 18Hz, but it's not safe to + * rely on this value). Instead, we use the Alpha's "rpcc" + * instruction to read cycle counts. As this is a 32 bit counter, + * it will overflow only once per 30 seconds (on a 200MHz machine), + * which is plenty. + */ + +unsigned int ftape_timestamp(void) +{ +#if defined(__alpha__) + unsigned long r; + + asm volatile ("rpcc %0" : "=r" (r)); + return r; +#elif defined(__x86_64__) + unsigned long r; + rdtscl(r); + return r; +#elif defined(__i386__) + +/* + * Note that there is some time between counter underflowing and jiffies + * increasing, so the code below won't always give correct output. + * -Vojtech + */ + + unsigned long flags; + __u16 lo; + __u16 hi; + + spin_lock_irqsave(&calibr_lock, flags); + outb_p(0x00, 0x43); /* latch the count ASAP */ + lo = inb_p(0x40); /* read the latched count */ + lo |= inb(0x40) << 8; + hi = jiffies; + spin_unlock_irqrestore(&calibr_lock, flags); + return ((hi + 1) * (unsigned int) LATCH) - lo; /* downcounter ! */ +#endif +} + +static unsigned int short_ftape_timestamp(void) +{ +#if defined(__alpha__) || defined(__x86_64__) + return ftape_timestamp(); +#elif defined(__i386__) + unsigned int count; + unsigned long flags; + + spin_lock_irqsave(&calibr_lock, flags); + outb_p(0x00, 0x43); /* latch the count ASAP */ + count = inb_p(0x40); /* read the latched count */ + count |= inb(0x40) << 8; + spin_unlock_irqrestore(&calibr_lock, flags); + return (LATCH - count); /* normal: downcounter */ +#endif +} + +static unsigned int diff(unsigned int t0, unsigned int t1) +{ +#if defined(__alpha__) || defined(__x86_64__) + return (t1 - t0); +#elif defined(__i386__) + /* + * This is tricky: to work for both short and full ftape_timestamps + * we'll have to discriminate between these. + * If it _looks_ like short stamps with wrapping around we'll + * asume it are. This will generate a small error if it really + * was a (very large) delta from full ftape_timestamps. + */ + return (t1 <= t0 && t0 <= LATCH) ? t1 + LATCH - t0 : t1 - t0; +#endif +} + +static unsigned int usecs(unsigned int count) +{ +#if defined(__alpha__) || defined(__x86_64__) + return (ps_per_cycle * count) / 1000000UL; +#elif defined(__i386__) + return (10000 * count) / ((CLOCK_TICK_RATE + 50) / 100); +#endif +} + +unsigned int ftape_timediff(unsigned int t0, unsigned int t1) +{ + /* + * Calculate difference in usec for ftape_timestamp results t0 & t1. + * Note that on the i386 platform with short time-stamps, the + * maximum allowed timespan is 1/HZ or we'll lose ticks! + */ + return usecs(diff(t0, t1)); +} + +/* To get an indication of the I/O performance, + * measure the duration of the inb() function. + */ +static void time_inb(void) +{ + int i; + int t0, t1; + unsigned long flags; + int status; + TRACE_FUN(ft_t_any); + + spin_lock_irqsave(&calibr_lock, flags); + t0 = short_ftape_timestamp(); + for (i = 0; i < 1000; ++i) { + status = inb(fdc.msr); + } + t1 = short_ftape_timestamp(); + spin_unlock_irqrestore(&calibr_lock, flags); + TRACE(ft_t_info, "inb() duration: %d nsec", ftape_timediff(t0, t1)); + TRACE_EXIT; +} + +static void init_clock(void) +{ + TRACE_FUN(ft_t_any); + +#if defined(__x86_64__) + ps_per_cycle = 1000000000UL / cpu_khz; +#elif defined(__alpha__) + extern struct hwrpb_struct *hwrpb; + ps_per_cycle = (1000*1000*1000*1000UL) / hwrpb->cycle_freq; +#endif + TRACE_EXIT; +} + +/* + * Input: function taking int count as parameter. + * pointers to calculated calibration variables. + */ +void ftape_calibrate(char *name, + void (*fun) (unsigned int), + unsigned int *calibr_count, + unsigned int *calibr_time) +{ + static int first_time = 1; + int i; + unsigned int tc = 0; + unsigned int count; + unsigned int time; +#if defined(__i386__) + unsigned int old_tc = 0; + unsigned int old_count = 1; + unsigned int old_time = 1; +#endif + TRACE_FUN(ft_t_flow); + + if (first_time) { /* get idea of I/O performance */ + init_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. + */ + + count = 40; + for (i = 0; i < 15; ++i) { + unsigned int t0; + unsigned int t1; + unsigned int once; + unsigned int multiple; + unsigned long flags; + + *calibr_count = + *calibr_time = count; /* set TC to 1 */ + spin_lock_irqsave(&calibr_lock, flags); + fun(0); /* dummy, get code into cache */ + t0 = short_ftape_timestamp(); + fun(0); /* overhead + one test */ + t1 = short_ftape_timestamp(); + once = diff(t0, t1); + t0 = short_ftape_timestamp(); + fun(count); /* overhead + count tests */ + t1 = short_ftape_timestamp(); + multiple = diff(t0, t1); + spin_unlock_irqrestore(&calibr_lock, flags); + time = ftape_timediff(0, multiple - once); + tc = (1000 * time) / (count - 1); + TRACE(ft_t_any, "once:%3d us,%6d times:%6d us, TC:%5d ns", + usecs(once), count - 1, usecs(multiple), tc); +#if defined(__alpha__) || defined(__x86_64__) + /* + * Increase the calibration count exponentially until the + * calibration time exceeds 100 ms. + */ + if (time >= 100*1000) { + break; + } +#elif defined(__i386__) + /* + * 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) { + time = old_time; + count = old_count; + break; + } + old_tc = tc; + old_count = count; + old_time = time; +#endif + count *= 2; + } + *calibr_count = count - 1; + *calibr_time = time; + TRACE(ft_t_info, "TC for `%s()' = %d nsec (at %d counts)", + name, (1000 * *calibr_time) / *calibr_count, *calibr_count); + TRACE_EXIT; +} diff --git a/drivers/char/ftape/lowlevel/ftape-calibr.h b/drivers/char/ftape/lowlevel/ftape-calibr.h new file mode 100644 index 000000000000..0c7e75246c7d --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-calibr.h @@ -0,0 +1,37 @@ +#ifndef _FTAPE_CALIBR_H +#define _FTAPE_CALIBR_H + +/* + * Copyright (C) 1993-1996 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-calibr.h,v $ + * $Revision: 1.1 $ + * $Date: 1997/09/19 09:05:26 $ + * + * This file contains a gp calibration routine for + * hardware dependent timeout functions. + */ + +extern void ftape_calibrate(char *name, + void (*fun) (unsigned int), + unsigned int *calibr_count, + unsigned int *calibr_time); +extern unsigned int ftape_timestamp(void); +extern unsigned int ftape_timediff(unsigned int t0, unsigned int t1); + +#endif /* _FTAPE_CALIBR_H */ diff --git a/drivers/char/ftape/lowlevel/ftape-ctl.c b/drivers/char/ftape/lowlevel/ftape-ctl.c new file mode 100644 index 000000000000..32e043911790 --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-ctl.c @@ -0,0 +1,897 @@ +/* + * Copyright (C) 1993-1996 Bas Laarhoven, + * 1996-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-ctl.c,v $ + * $Revision: 1.4 $ + * $Date: 1997/11/11 14:37:44 $ + * + * This file contains the non-read/write ftape functions for the + * QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux. + */ + +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/mm.h> +#include <linux/mman.h> + +#include <linux/ftape.h> +#include <linux/qic117.h> +#include <asm/uaccess.h> +#include <asm/io.h> + +/* ease porting between pre-2.4.x and later kernels */ +#define vma_get_pgoff(v) ((v)->vm_pgoff) + +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/ftape-io.h" +#include "../lowlevel/ftape-ctl.h" +#include "../lowlevel/ftape-write.h" +#include "../lowlevel/ftape-read.h" +#include "../lowlevel/ftape-rw.h" +#include "../lowlevel/ftape-bsm.h" + +/* Global vars. + */ +ftape_info ftape_status = { +/* vendor information */ + { 0, }, /* drive type */ +/* data rates */ + 500, /* used data rate */ + 500, /* drive max rate */ + 500, /* fdc max rate */ +/* drive selection, either FTAPE_SEL_A/B/C/D */ + -1, /* drive selection */ +/* flags set after decode the drive and tape status */ + 0, /* formatted */ + 1, /* no tape */ + 1, /* write protected */ + 1, /* new tape */ +/* values of last queried drive/tape status and error */ + {{0,}}, /* last error code */ + {{0,}}, /* drive status, configuration, tape status */ +/* cartridge geometry */ + 20, /* tracks_per_tape */ + 102, /* segments_per_track */ +/* location of header segments, etc. */ + -1, /* used_header_segment */ + -1, /* header_segment_1 */ + -1, /* header_segment_2 */ + -1, /* first_data_segment */ + -1, /* last_data_segment */ +/* the format code as stored in the header segment */ + fmt_normal, /* format code */ +/* the default for the qic std: unknown */ + -1, +/* is tape running? */ + idle, /* runner_state */ +/* is tape reading/writing/verifying/formatting/deleting */ + idle, /* driver state */ +/* flags fatal hardware error */ + 1, /* failure */ +/* history record */ + { 0, } /* history record */ +}; + +int ftape_segments_per_head = 1020; +int ftape_segments_per_cylinder = 4; +int ftape_init_drive_needed = 1; /* need to be global for ftape_reset_drive() + * in ftape-io.c + */ + +/* Local vars. + */ +static const vendor_struct vendors[] = QIC117_VENDORS; +static const wakeup_method methods[] = WAKEUP_METHODS; + +const ftape_info *ftape_get_status(void) +{ +#if defined(STATUS_PARANOYA) + static ftape_info get_status; + + get_status = ftape_status; + return &get_status; +#else + return &ftape_status; /* maybe return only a copy of it to assure + * read only access + */ +#endif +} + +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) +{ + int status; + TRACE_FUN(ft_t_any); + + TRACE_CATCH(ftape_ready_wait(ftape_timeout.pause, &status),); + while ((status & QIC_STATUS_AT_EOT) == 0) { + if (ftape_not_operational(status)) { + TRACE_EXIT -EIO; + } + TRACE_CATCH(ftape_command_wait(QIC_PHYSICAL_FORWARD, + ftape_timeout.rewind,&status),); + } + TRACE_EXIT 0; +} + +int ftape_seek_to_bot(void) +{ + int status; + TRACE_FUN(ft_t_any); + + TRACE_CATCH(ftape_ready_wait(ftape_timeout.pause, &status),); + while ((status & QIC_STATUS_AT_BOT) == 0) { + if (ftape_not_operational(status)) { + TRACE_EXIT -EIO; + } + TRACE_CATCH(ftape_command_wait(QIC_PHYSICAL_REVERSE, + ftape_timeout.rewind,&status),); + } + TRACE_EXIT 0; +} + +static int ftape_new_cartridge(void) +{ + ft_location.track = -1; /* force seek on first access */ + ftape_zap_read_buffers(); + ftape_zap_write_buffers(); + return 0; +} + +int ftape_abort_operation(void) +{ + int result = 0; + int status; + TRACE_FUN(ft_t_flow); + + if (ft_runner_status == running) { + TRACE(ft_t_noise, "aborting runner, waiting"); + + ft_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 (ft_runner_status != idle) { + if (ft_runner_status == do_abort) { + TRACE(ft_t_noise, "forcing runner abort"); + } + TRACE(ft_t_noise, "stopping tape"); + result = ftape_stop_tape(&status); + ft_location.known = 0; + ft_runner_status = idle; + } + ftape_reset_buffer(); + ftape_zap_read_buffers(); + ftape_set_state(idle); + TRACE_EXIT result; +} + +static int lookup_vendor_id(unsigned int vendor_id) +{ + int i = 0; + + while (vendors[i].vendor_id != vendor_id) { + if (++i >= NR_ITEMS(vendors)) { + return -1; + } + } + return i; +} + +static void ftape_detach_drive(void) +{ + TRACE_FUN(ft_t_any); + + TRACE(ft_t_flow, "disabling tape drive and fdc"); + ftape_put_drive_to_sleep(ft_drive_type.wake_up); + fdc_catch_stray_interrupts(1); /* one always comes */ + fdc_disable(); + fdc_release_irq_and_dma(); + fdc_release_regions(); + TRACE_EXIT; +} + +static void clear_history(void) +{ + ft_history.used = 0; + ft_history.id_am_errors = + ft_history.id_crc_errors = + ft_history.data_am_errors = + ft_history.data_crc_errors = + ft_history.overrun_errors = + ft_history.no_data_errors = + ft_history.retries = + ft_history.crc_errors = + ft_history.crc_failures = + ft_history.ecc_failures = + ft_history.corrected = + ft_history.defects = + ft_history.rewinds = 0; +} + +static int ftape_activate_drive(vendor_struct * drive_type) +{ + int result = 0; + TRACE_FUN(ft_t_flow); + + /* 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(ft_t_flow, "enabling tape drive and fdc"); + result = ftape_wakeup_drive(drive_type->wake_up); + if (result < 0) { + TRACE(ft_t_err, "known wakeup method failed"); + } + } else { + wake_up_types method; + const ft_trace_t old_tracing = TRACE_LEVEL; + if (TRACE_LEVEL < ft_t_flow) { + SET_TRACE_LEVEL(ft_t_bug); + } + + /* Try to awaken the drive using all known methods. + * Lower tracing for a while. + */ + for (method=no_wake_up; method < NR_ITEMS(methods); ++method) { + drive_type->wake_up = method; +#ifdef CONFIG_FT_TWO_DRIVES + /* Test setup for dual drive configuration. + * /dev/rft2 uses mountain wakeup + * /dev/rft3 uses colorado wakeup + * Other systems will use the normal scheme. + */ + if ((ft_drive_sel < 2) || + (ft_drive_sel == 2 && method == FT_WAKE_UP_1) || + (ft_drive_sel == 3 && method == FT_WAKE_UP_2)) { + result=ftape_wakeup_drive(drive_type->wake_up); + } else { + result = -EIO; + } +#else + result = ftape_wakeup_drive(drive_type->wake_up); +#endif + if (result >= 0) { + TRACE(ft_t_warn, "drive wakeup method: %s", + methods[drive_type->wake_up].name); + break; + } + } + SET_TRACE_LEVEL(old_tracing); + + if (method >= NR_ITEMS(methods)) { + /* no response at all, cannot open this drive */ + drive_type->wake_up = unknown_wake_up; + TRACE(ft_t_err, "no tape drive found !"); + result = -ENODEV; + } + } + TRACE_EXIT result; +} + +static int ftape_get_drive_status(void) +{ + int result; + int status; + TRACE_FUN(ft_t_flow); + + ft_no_tape = ft_write_protected = 0; + /* Tape drive is activated now. + * First clear error status if present. + */ + do { + result = ftape_ready_wait(ftape_timeout.reset, &status); + if (result < 0) { + if (result == -ETIME) { + TRACE(ft_t_err, "ftape_ready_wait timeout"); + } else if (result == -EINTR) { + TRACE(ft_t_err, "ftape_ready_wait aborted"); + } else { + TRACE(ft_t_err, "ftape_ready_wait failed"); + } + TRACE_EXIT -EIO; + } + /* Clear error condition (drive is ready !) + */ + if (status & QIC_STATUS_ERROR) { + unsigned int error; + qic117_cmd_t command; + + TRACE(ft_t_err, "error status set"); + result = ftape_report_error(&error, &command, 1); + if (result < 0) { + TRACE(ft_t_err, + "report_error_code failed: %d", result); + /* hope it's working next time */ + ftape_reset_drive(); + TRACE_EXIT -EIO; + } else if (error != 0) { + TRACE(ft_t_noise, "error code : %d", error); + TRACE(ft_t_noise, "error command: %d", command); + } + } + if (status & QIC_STATUS_NEW_CARTRIDGE) { + unsigned int error; + qic117_cmd_t command; + const ft_trace_t old_tracing = TRACE_LEVEL; + SET_TRACE_LEVEL(ft_t_bug); + + /* Undocumented feature: Must clear (not present!) + * error here or we'll fail later. + */ + ftape_report_error(&error, &command, 1); + + SET_TRACE_LEVEL(old_tracing); + TRACE(ft_t_info, "status: new cartridge"); + ft_new_tape = 1; + } else { + ft_new_tape = 0; + } + FT_SIGNAL_EXIT(_DONT_BLOCK); + } while (status & QIC_STATUS_ERROR); + + ft_no_tape = !(status & QIC_STATUS_CARTRIDGE_PRESENT); + ft_write_protected = (status & QIC_STATUS_WRITE_PROTECT) != 0; + if (ft_no_tape) { + TRACE(ft_t_warn, "no cartridge present"); + } else { + if (ft_write_protected) { + TRACE(ft_t_noise, "Write protected cartridge"); + } + } + TRACE_EXIT 0; +} + +static void ftape_log_vendor_id(void) +{ + int vendor_index; + TRACE_FUN(ft_t_flow); + + ftape_report_vendor_id(&ft_drive_type.vendor_id); + vendor_index = lookup_vendor_id(ft_drive_type.vendor_id); + if (ft_drive_type.vendor_id == UNKNOWN_VENDOR && + ft_drive_type.wake_up == wake_up_colorado) { + vendor_index = 0; + /* hack to get rid of all this mail */ + ft_drive_type.vendor_id = 0; + } + 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(ft_t_warn, "\n" + KERN_INFO "============ unknown vendor id ===========\n" + KERN_INFO "A new, yet unsupported tape drive is found\n" + KERN_INFO "Please report the following values:\n" + KERN_INFO " Vendor id : 0x%04x\n" + KERN_INFO " Wakeup method : %s\n" + KERN_INFO "And a description of your tape drive\n" + KERN_INFO "to "THE_FTAPE_MAINTAINER"\n" + KERN_INFO "==========================================", + ft_drive_type.vendor_id, + methods[ft_drive_type.wake_up].name); + ft_drive_type.speed = 0; /* unknown */ + } else { + ft_drive_type.name = vendors[vendor_index].name; + ft_drive_type.speed = vendors[vendor_index].speed; + TRACE(ft_t_info, "tape drive type: %s", ft_drive_type.name); + /* scan all methods for this vendor_id in table */ + while(ft_drive_type.wake_up != vendors[vendor_index].wake_up) { + if (vendor_index < NR_ITEMS(vendors) - 1 && + vendors[vendor_index + 1].vendor_id + == + ft_drive_type.vendor_id) { + ++vendor_index; + } else { + break; + } + } + if (ft_drive_type.wake_up != vendors[vendor_index].wake_up) { + TRACE(ft_t_warn, "\n" + KERN_INFO "==========================================\n" + KERN_INFO "wakeup type mismatch:\n" + KERN_INFO "found: %s, expected: %s\n" + KERN_INFO "please report this to "THE_FTAPE_MAINTAINER"\n" + KERN_INFO "==========================================", + methods[ft_drive_type.wake_up].name, + methods[vendors[vendor_index].wake_up].name); + } + } + TRACE_EXIT; +} + +void ftape_calc_timeouts(unsigned int qic_std, + unsigned int data_rate, + unsigned int tape_len) +{ + int speed; /* deci-ips ! */ + int ff_speed; + int length; + TRACE_FUN(ft_t_any); + + /* 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 = (data_rate == 250) ? 250 : 500; + break; + case QIC_TAPE_QIC80: + speed = (data_rate == 500) ? 340 : 680; + break; + case QIC_TAPE_QIC3010: + speed = (data_rate == 500) ? 226 : 452; + break; + case QIC_TAPE_QIC3020: + speed = (data_rate == 1000) ? 226 : 452; + break; + default: + TRACE(ft_t_bug, "Unknown qic_std (bug) ?"); + speed = 500; + break; + } + if (ft_drive_type.speed == 0) { + unsigned long t0; + static int dt = 0; /* keep gcc from complaining */ + static int first_time = 1; + + /* Measure the time it takes to wind to EOT and back to BOT. + * If the tape length is known, calculate the rewind speed. + * Else keep the time value for calculation of the rewind + * speed later on, when the length _is_ known. + * Ask for a report only when length and speed are both known. + */ + if (first_time) { + ftape_seek_to_bot(); + t0 = jiffies; + ftape_seek_to_eot(); + ftape_seek_to_bot(); + dt = (int) (((jiffies - t0) * FT_USPT) / 1000); + if (dt < 1) { + dt = 1; /* prevent div by zero on failures */ + } + first_time = 0; + TRACE(ft_t_info, + "trying to determine seek timeout, got %d msec", + dt); + } + if (tape_len != 0) { + ft_drive_type.speed = + (2 * 12 * tape_len * 1000) / dt; + TRACE(ft_t_warn, "\n" + KERN_INFO "==========================================\n" + KERN_INFO "drive type: %s\n" + KERN_INFO "delta time = %d ms, length = %d ft\n" + KERN_INFO "has a maximum tape speed of %d ips\n" + KERN_INFO "please report this to "THE_FTAPE_MAINTAINER"\n" + KERN_INFO "==========================================", + ft_drive_type.name, dt, tape_len, + ft_drive_type.speed); + } + } + /* Handle unknown length tapes as very long ones. We'll + * determine the actual length from a header segment later. + * This is normal for all modern (Wide,TR1/2/3) formats. + */ + if (tape_len <= 0) { + TRACE(ft_t_noise, + "Unknown tape length, using maximal timeouts"); + length = QIC_TOP_TAPE_LEN; /* use worst case values */ + } else { + length = tape_len; /* use actual values */ + } + if (ft_drive_type.speed == 0) { + ff_speed = speed; + } else { + ff_speed = ft_drive_type.speed; + } + /* 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. + */ + ftape_timeout.seek = (length * 132 * FT_SECOND) / speed; + ftape_timeout.rewind = (length * 144 * FT_SECOND) / (10 * ff_speed); + ftape_timeout.reset = 20 * FT_SECOND + ftape_timeout.rewind; + TRACE(ft_t_noise, "timeouts for speed = %d, length = %d\n" + KERN_INFO "seek timeout : %d sec\n" + KERN_INFO "rewind timeout: %d sec\n" + KERN_INFO "reset timeout : %d sec", + speed, length, + (ftape_timeout.seek + 500) / 1000, + (ftape_timeout.rewind + 500) / 1000, + (ftape_timeout.reset + 500) / 1000); + TRACE_EXIT; +} + +/* This function calibrates the datarate (i.e. determines the maximal + * usable data rate) and sets the global variable ft_qic_std to qic_std + * + */ +int ftape_calibrate_data_rate(unsigned int qic_std) +{ + int rate = ft_fdc_rate_limit; + int result; + TRACE_FUN(ft_t_flow); + + ft_qic_std = qic_std; + + if (ft_qic_std == -1) { + TRACE_ABORT(-EIO, ft_t_err, + "Unable to determine data rate if QIC standard is unknown"); + } + + /* Select highest rate supported by both fdc and drive. + * Start with highest rate supported by the fdc. + */ + while (fdc_set_data_rate(rate) < 0 && rate > 250) { + rate /= 2; + } + TRACE(ft_t_info, + "Highest FDC supported data rate: %d Kbps", rate); + ft_fdc_max_rate = rate; + do { + result = ftape_set_data_rate(rate, ft_qic_std); + } while (result == -EINVAL && (rate /= 2) > 250); + if (result < 0) { + TRACE_ABORT(-EIO, ft_t_err, "set datarate failed"); + } + ft_data_rate = rate; + TRACE_EXIT 0; +} + +static int ftape_init_drive(void) +{ + int status; + qic_model model; + unsigned int qic_std; + unsigned int data_rate; + TRACE_FUN(ft_t_flow); + + ftape_init_drive_needed = 0; /* don't retry if this fails ? */ + TRACE_CATCH(ftape_report_raw_drive_status(&status),); + if (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. + */ + /* Tape should be at bot if new cartridge ! */ + ftape_seek_to_bot(); + } + if (!(status & QIC_STATUS_REFERENCED)) { + TRACE(ft_t_flow, "starting seek_load_point"); + TRACE_CATCH(ftape_command_wait(QIC_SEEK_LOAD_POINT, + ftape_timeout.reset, + &status),); + } + } + ft_formatted = (status & QIC_STATUS_REFERENCED) != 0; + if (!ft_formatted) { + TRACE(ft_t_warn, "Warning: tape is not formatted !"); + } + + /* report configuration aborts when ftape_tape_len == -1 + * unknown qic_std is okay if not formatted. + */ + TRACE_CATCH(ftape_report_configuration(&model, + &data_rate, + &qic_std, + &ftape_tape_len),); + + /* Maybe add the following to the /proc entry + */ + TRACE(ft_t_info, "%s drive @ %d Kbps", + (model == prehistoric) ? "prehistoric" : + ((model == pre_qic117c) ? "pre QIC-117C" : + ((model == post_qic117b) ? "post QIC-117B" : + "post QIC-117D")), data_rate); + + if (ft_formatted) { + /* initialize ft_used_data_rate to maximum value + * and set ft_qic_std + */ + TRACE_CATCH(ftape_calibrate_data_rate(qic_std),); + if (ftape_tape_len == 0) { + TRACE(ft_t_info, "unknown length QIC-%s tape", + (ft_qic_std == QIC_TAPE_QIC40) ? "40" : + ((ft_qic_std == QIC_TAPE_QIC80) ? "80" : + ((ft_qic_std == QIC_TAPE_QIC3010) + ? "3010" : "3020"))); + } else { + TRACE(ft_t_info, "%d ft. QIC-%s tape", ftape_tape_len, + (ft_qic_std == QIC_TAPE_QIC40) ? "40" : + ((ft_qic_std == QIC_TAPE_QIC80) ? "80" : + ((ft_qic_std == QIC_TAPE_QIC3010) + ? "3010" : "3020"))); + } + ftape_calc_timeouts(ft_qic_std, ft_data_rate, ftape_tape_len); + /* soft write-protect QIC-40/QIC-80 cartridges used with a + * Colorado T3000 drive. Buggy hardware! + */ + if ((ft_drive_type.vendor_id == 0x011c6) && + ((ft_qic_std == QIC_TAPE_QIC40 || + ft_qic_std == QIC_TAPE_QIC80) && + !ft_write_protected)) { + TRACE(ft_t_warn, "\n" + KERN_INFO "The famous Colorado T3000 bug:\n" + KERN_INFO "%s drives can't write QIC40 and QIC80\n" + KERN_INFO "cartridges but don't set the write-protect flag!", + ft_drive_type.name); + ft_write_protected = 1; + } + } else { + /* Doesn't make too much sense to set the data rate + * because we don't know what to use for the write + * precompensation. + * Need to do this again when formatting the cartridge. + */ + ft_data_rate = data_rate; + ftape_calc_timeouts(QIC_TAPE_QIC40, + data_rate, + ftape_tape_len); + } + ftape_new_cartridge(); + TRACE_EXIT 0; +} + +static void ftape_munmap(void) +{ + int i; + TRACE_FUN(ft_t_flow); + + for (i = 0; i < ft_nr_buffers; i++) { + ft_buffer[i]->mmapped = 0; + } + TRACE_EXIT; +} + +/* Map the dma buffers into the virtual address range given by vma. + * We only check the caller doesn't map non-existent buffers. We + * don't check for multiple mappings. + */ +int ftape_mmap(struct vm_area_struct *vma) +{ + int num_buffers; + int i; + TRACE_FUN(ft_t_flow); + + if (ft_failure) { + TRACE_EXIT -ENODEV; + } + if (!(vma->vm_flags & (VM_READ|VM_WRITE))) { + TRACE_ABORT(-EINVAL, ft_t_err, "Undefined mmap() access"); + } + if (vma_get_pgoff(vma) != 0) { + TRACE_ABORT(-EINVAL, ft_t_err, "page offset must be 0"); + } + if ((vma->vm_end - vma->vm_start) % FT_BUFF_SIZE != 0) { + TRACE_ABORT(-EINVAL, ft_t_err, + "size = %ld, should be a multiple of %d", + vma->vm_end - vma->vm_start, + FT_BUFF_SIZE); + } + num_buffers = (vma->vm_end - vma->vm_start) / FT_BUFF_SIZE; + if (num_buffers > ft_nr_buffers) { + TRACE_ABORT(-EINVAL, + ft_t_err, "size = %ld, should be less than %d", + vma->vm_end - vma->vm_start, + ft_nr_buffers * FT_BUFF_SIZE); + } + if (ft_driver_state != idle) { + /* this also clears the buffer states + */ + ftape_abort_operation(); + } else { + ftape_reset_buffer(); + } + for (i = 0; i < num_buffers; i++) { + unsigned long pfn; + + pfn = virt_to_phys(ft_buffer[i]->address) >> PAGE_SHIFT; + TRACE_CATCH(remap_pfn_range(vma, vma->vm_start + + i * FT_BUFF_SIZE, + pfn, + FT_BUFF_SIZE, + vma->vm_page_prot), + _res = -EAGAIN); + TRACE(ft_t_noise, "remapped dma buffer @ %p to location @ %p", + ft_buffer[i]->address, + (void *)(vma->vm_start + i * FT_BUFF_SIZE)); + } + for (i = 0; i < num_buffers; i++) { + memset(ft_buffer[i]->address, 0xAA, FT_BUFF_SIZE); + ft_buffer[i]->mmapped++; + } + TRACE_EXIT 0; +} + +static void ftape_init_driver(void); /* forward declaration */ + +/* OPEN routine called by kernel-interface code + */ +int ftape_enable(int drive_selection) +{ + TRACE_FUN(ft_t_any); + + if (ft_drive_sel == -1 || ft_drive_sel != drive_selection) { + /* Other selection than last time + */ + ftape_init_driver(); + } + ft_drive_sel = FTAPE_SEL(drive_selection); + ft_failure = 0; + TRACE_CATCH(fdc_init(),); /* init & detect fdc */ + TRACE_CATCH(ftape_activate_drive(&ft_drive_type), + fdc_disable(); + fdc_release_irq_and_dma(); + fdc_release_regions()); + TRACE_CATCH(ftape_get_drive_status(), ftape_detach_drive()); + if (ft_drive_type.vendor_id == UNKNOWN_VENDOR) { + ftape_log_vendor_id(); + } + if (ft_new_tape) { + ftape_init_drive_needed = 1; + } + if (!ft_no_tape && ftape_init_drive_needed) { + TRACE_CATCH(ftape_init_drive(), ftape_detach_drive()); + } + ftape_munmap(); /* clear the mmap flag */ + clear_history(); + TRACE_EXIT 0; +} + +/* release routine called by the high level interface modules + * zftape or sftape. + */ +void ftape_disable(void) +{ + int i; + TRACE_FUN(ft_t_any); + + for (i = 0; i < ft_nr_buffers; i++) { + if (ft_buffer[i]->mmapped) { + TRACE(ft_t_noise, "first byte of buffer %d: 0x%02x", + i, *ft_buffer[i]->address); + } + } + if (sigtestsetmask(¤t->pending.signal, _DONT_BLOCK) && + !(sigtestsetmask(¤t->pending.signal, _NEVER_BLOCK)) && + ftape_tape_running) { + TRACE(ft_t_warn, + "Interrupted by fatal signal and tape still running"); + ftape_dumb_stop(); + ftape_abort_operation(); /* it's annoying */ + } else { + ftape_set_state(idle); + } + ftape_detach_drive(); + if (ft_history.used) { + TRACE(ft_t_info, "== Non-fatal errors this run: =="); + TRACE(ft_t_info, "fdc isr statistics:\n" + KERN_INFO " id_am_errors : %3d\n" + KERN_INFO " id_crc_errors : %3d\n" + KERN_INFO " data_am_errors : %3d\n" + KERN_INFO " data_crc_errors : %3d\n" + KERN_INFO " overrun_errors : %3d\n" + KERN_INFO " no_data_errors : %3d\n" + KERN_INFO " retries : %3d", + ft_history.id_am_errors, ft_history.id_crc_errors, + ft_history.data_am_errors, ft_history.data_crc_errors, + ft_history.overrun_errors, ft_history.no_data_errors, + ft_history.retries); + if (ft_history.used & 1) { + TRACE(ft_t_info, "ecc statistics:\n" + KERN_INFO " crc_errors : %3d\n" + KERN_INFO " crc_failures : %3d\n" + KERN_INFO " ecc_failures : %3d\n" + KERN_INFO " sectors corrected: %3d", + ft_history.crc_errors, ft_history.crc_failures, + ft_history.ecc_failures, ft_history.corrected); + } + if (ft_history.defects > 0) { + TRACE(ft_t_warn, "Warning: %d media defects!", + ft_history.defects); + } + if (ft_history.rewinds > 0) { + TRACE(ft_t_info, "tape motion statistics:\n" + KERN_INFO "repositions : %3d", + ft_history.rewinds); + } + } + ft_failure = 1; + TRACE_EXIT; +} + +static void ftape_init_driver(void) +{ + TRACE_FUN(ft_t_flow); + + ft_drive_type.vendor_id = UNKNOWN_VENDOR; + ft_drive_type.speed = 0; + ft_drive_type.wake_up = unknown_wake_up; + ft_drive_type.name = "Unknown"; + + ftape_timeout.seek = 650 * FT_SECOND; + ftape_timeout.reset = 670 * FT_SECOND; + ftape_timeout.rewind = 650 * FT_SECOND; + ftape_timeout.head_seek = 15 * FT_SECOND; + ftape_timeout.stop = 5 * FT_SECOND; + ftape_timeout.pause = 16 * FT_SECOND; + + ft_qic_std = -1; + ftape_tape_len = 0; /* unknown */ + ftape_current_command = 0; + ftape_current_cylinder = -1; + + ft_segments_per_track = 102; + ftape_segments_per_head = 1020; + ftape_segments_per_cylinder = 4; + ft_tracks_per_tape = 20; + + ft_failure = 1; + + ft_formatted = 0; + ft_no_tape = 1; + ft_write_protected = 1; + ft_new_tape = 1; + + ft_driver_state = idle; + + ft_data_rate = + ft_fdc_max_rate = 500; + ft_drive_max_rate = 0; /* triggers set_rate_test() */ + + ftape_init_drive_needed = 1; + + ft_header_segment_1 = -1; + ft_header_segment_2 = -1; + ft_used_header_segment = -1; + ft_first_data_segment = -1; + ft_last_data_segment = -1; + + ft_location.track = -1; + ft_location.known = 0; + + ftape_tape_running = 0; + ftape_might_be_off_track = 1; + + ftape_new_cartridge(); /* init some tape related variables */ + ftape_init_bsm(); + TRACE_EXIT; +} diff --git a/drivers/char/ftape/lowlevel/ftape-ctl.h b/drivers/char/ftape/lowlevel/ftape-ctl.h new file mode 100644 index 000000000000..5f5e30bc3615 --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-ctl.h @@ -0,0 +1,162 @@ +#ifndef _FTAPE_CTL_H +#define _FTAPE_CTL_H + +/* + * Copyright (C) 1993-1996 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-ctl.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:18:09 $ + * + * This file contains the non-standard IOCTL related definitions + * for the QIC-40/80/3010/3020 floppy-tape driver "ftape" for + * Linux. + */ + +#include <linux/ioctl.h> +#include <linux/mtio.h> +#include <linux/ftape-vendors.h> + +#include "../lowlevel/ftape-rw.h" +#include <linux/ftape-header-segment.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; + +/* this structure contains * ALL * information that we want + * our child modules to know about, but don't want them to + * modify. + */ +typedef struct { + /* vendor information */ + vendor_struct fti_drive_type; + /* data rates */ + unsigned int fti_used_data_rate; + unsigned int fti_drive_max_rate; + unsigned int fti_fdc_max_rate; + /* drive selection, either FTAPE_SEL_A/B/C/D */ + int fti_drive_sel; + /* flags set after decode the drive and tape status */ + unsigned int fti_formatted :1; + unsigned int fti_no_tape :1; + unsigned int fti_write_protected:1; + unsigned int fti_new_tape :1; + /* values of last queried drive/tape status and error */ + ft_drive_error fti_last_error; + ft_drive_status fti_last_status; + /* cartridge geometry */ + unsigned int fti_tracks_per_tape; + unsigned int fti_segments_per_track; + /* location of header segments, etc. */ + int fti_used_header_segment; + int fti_header_segment_1; + int fti_header_segment_2; + int fti_first_data_segment; + int fti_last_data_segment; + /* the format code as stored in the header segment */ + ft_format_type fti_format_code; + /* the following is the sole reason for the ftape_set_status() call */ + unsigned int fti_qic_std; + /* is tape running? */ + volatile enum runner_status_enum fti_runner_status; + /* is tape reading/writing/verifying/formatting/deleting */ + buffer_state_enum fti_state; + /* flags fatal hardware error */ + unsigned int fti_failure:1; + /* history record */ + history_record fti_history; +} ftape_info; + +/* vendor information */ +#define ft_drive_type ftape_status.fti_drive_type +/* data rates */ +#define ft_data_rate ftape_status.fti_used_data_rate +#define ft_drive_max_rate ftape_status.fti_drive_max_rate +#define ft_fdc_max_rate ftape_status.fti_fdc_max_rate +/* drive selection, either FTAPE_SEL_A/B/C/D */ +#define ft_drive_sel ftape_status.fti_drive_sel +/* flags set after decode the drive and tape status */ +#define ft_formatted ftape_status.fti_formatted +#define ft_no_tape ftape_status.fti_no_tape +#define ft_write_protected ftape_status.fti_write_protected +#define ft_new_tape ftape_status.fti_new_tape +/* values of last queried drive/tape status and error */ +#define ft_last_error ftape_status.fti_last_error +#define ft_last_status ftape_status.fti_last_status +/* cartridge geometry */ +#define ft_tracks_per_tape ftape_status.fti_tracks_per_tape +#define ft_segments_per_track ftape_status.fti_segments_per_track +/* the format code as stored in the header segment */ +#define ft_format_code ftape_status.fti_format_code +/* the qic status as returned by report drive configuration */ +#define ft_qic_std ftape_status.fti_qic_std +#define ft_used_header_segment ftape_status.fti_used_header_segment +#define ft_header_segment_1 ftape_status.fti_header_segment_1 +#define ft_header_segment_2 ftape_status.fti_header_segment_2 +#define ft_first_data_segment ftape_status.fti_first_data_segment +#define ft_last_data_segment ftape_status.fti_last_data_segment +/* is tape running? */ +#define ft_runner_status ftape_status.fti_runner_status +/* is tape reading/writing/verifying/formatting/deleting */ +#define ft_driver_state ftape_status.fti_state +/* flags fatal hardware error */ +#define ft_failure ftape_status.fti_failure +/* history record */ +#define ft_history ftape_status.fti_history + +/* + * ftape-ctl.c defined global vars. + */ +extern ftape_info ftape_status; +extern int ftape_segments_per_head; +extern int ftape_segments_per_cylinder; +extern int ftape_init_drive_needed; + +/* + * ftape-ctl.c defined global functions. + */ +extern int ftape_mmap(struct vm_area_struct *vma); +extern int ftape_enable(int drive_selection); +extern void ftape_disable(void); +extern int ftape_seek_to_bot(void); +extern int ftape_seek_to_eot(void); +extern int ftape_abort_operation(void); +extern void ftape_calc_timeouts(unsigned int qic_std, + unsigned int data_rate, + unsigned int tape_len); +extern int ftape_calibrate_data_rate(unsigned int qic_std); +extern const ftape_info *ftape_get_status(void); +#endif diff --git a/drivers/char/ftape/lowlevel/ftape-ecc.c b/drivers/char/ftape/lowlevel/ftape-ecc.c new file mode 100644 index 000000000000..e5632f674bc8 --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-ecc.c @@ -0,0 +1,853 @@ +/* + * + * 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-ecc.c,v $ + * $Revision: 1.3 $ + * $Date: 1997/10/05 19:18:10 $ + * + * This file contains the Reed-Solomon error correction code + * for the QIC-40/80 floppy-tape driver for Linux. + */ + +#include <linux/ftape.h> + +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/ftape-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 + +/* 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 __u8 Matrix[3][3]; + +/* + * gfpow[] is defined such that gfpow[i] returns r^i if + * i is in the range [0..255]. + */ +static const __u8 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 __u8 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 (mod f(r)). + * gfmul_c0[f] returns r^105 * f(r) (modulo f(r)). + */ +static const __u8 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 __u8 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 __u8 gfadd(__u8 a, __u8 b) +{ + return a ^ b; +} + + +/* Add two vectors of numbers in the field. Each byte in A and B gets + * 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 __u8 gfmul(__u8 a, __u8 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 __u8 gfmul_exp(__u8 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) +{ + __u8 t; + + if (sizeof(long) == 4) { + return ( + ((t = (__u32)a >> 24 & 0xff) ? + (((__u32) gfpow[mod255(gflog[t] + b)]) << 24) : 0) | + ((t = (__u32)a >> 16 & 0xff) ? + (((__u32) gfpow[mod255(gflog[t] + b)]) << 16) : 0) | + ((t = (__u32)a >> 8 & 0xff) ? + (((__u32) gfpow[mod255(gflog[t] + b)]) << 8) : 0) | + ((t = (__u32)a >> 0 & 0xff) ? + (((__u32) gfpow[mod255(gflog[t] + b)]) << 0) : 0)); + } else if (sizeof(long) == 8) { + return ( + ((t = (__u64)a >> 56 & 0xff) ? + (((__u64) gfpow[mod255(gflog[t] + b)]) << 56) : 0) | + ((t = (__u64)a >> 48 & 0xff) ? + (((__u64) gfpow[mod255(gflog[t] + b)]) << 48) : 0) | + ((t = (__u64)a >> 40 & 0xff) ? + (((__u64) gfpow[mod255(gflog[t] + b)]) << 40) : 0) | + ((t = (__u64)a >> 32 & 0xff) ? + (((__u64) gfpow[mod255(gflog[t] + b)]) << 32) : 0) | + ((t = (__u64)a >> 24 & 0xff) ? + (((__u64) gfpow[mod255(gflog[t] + b)]) << 24) : 0) | + ((t = (__u64)a >> 16 & 0xff) ? + (((__u64) gfpow[mod255(gflog[t] + b)]) << 16) : 0) | + ((t = (__u64)a >> 8 & 0xff) ? + (((__u64) gfpow[mod255(gflog[t] + b)]) << 8) : 0) | + ((t = (__u64)a >> 0 & 0xff) ? + (((__u64) gfpow[mod255(gflog[t] + b)]) << 0) : 0)); + } else { + TRACE_FUN(ft_t_any); + TRACE_ABORT(-1, ft_t_err, "Error: size of long is %d bytes", + (int)sizeof(long)); + } +} + + +/* Divide two numbers in the field. Returns a/b (modulo f(x)). + */ +static inline __u8 gfdiv(__u8 a, __u8 b) +{ + if (!b) { + TRACE_FUN(ft_t_any); + TRACE_ABORT(0xff, ft_t_bug, "Error: division by zero"); + } else if (a == 0) { + return 0; + } else { + return gfpow[mod255(gflog[a] - gflog[b])]; + } +} + + +/* 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(__u8 l0, + __u8 l1, + __u8 l2, + Matrix Ainv) +{ + __u8 det; + __u8 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_FUN(ft_t_any); + TRACE_ABORT(0, ft_t_err, + "Inversion failed (3 CRC errors, >0 CRC failures)"); + } + 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); + + return 1; +} + + +static inline int gfinv2(__u8 l0, __u8 l1, Matrix Ainv) +{ + __u8 det; + __u8 t1, t2; + int log_det; + + t1 = gfpow[255 - l0]; + t2 = gfpow[255 - l1]; + det = gfadd(t1, t2); + if (!det) { + TRACE_FUN(ft_t_any); + TRACE_ABORT(0, ft_t_err, + "Inversion failed (2 CRC errors, >0 CRC failures)"); + } + 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); + + 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, + __u8 *s, __u8 *b) +{ + int i, j; + __u8 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) +{ + unsigned long p0, p1, p2, t1, t2, *end; + + end = data + nblocks * (FT_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= (((__u32) gfmul_c0[(__u32)t1 >> 24 & 0xff]) << 24 | + ((__u32) gfmul_c0[(__u32)t1 >> 16 & 0xff]) << 16 | + ((__u32) gfmul_c0[(__u32)t1 >> 8 & 0xff]) << 8 | + ((__u32) gfmul_c0[(__u32)t1 >> 0 & 0xff]) << 0); + } else if (sizeof(long) == 8) { + t2= (((__u64) gfmul_c0[(__u64)t1 >> 56 & 0xff]) << 56 | + ((__u64) gfmul_c0[(__u64)t1 >> 48 & 0xff]) << 48 | + ((__u64) gfmul_c0[(__u64)t1 >> 40 & 0xff]) << 40 | + ((__u64) gfmul_c0[(__u64)t1 >> 32 & 0xff]) << 32 | + ((__u64) gfmul_c0[(__u64)t1 >> 24 & 0xff]) << 24 | + ((__u64) gfmul_c0[(__u64)t1 >> 16 & 0xff]) << 16 | + ((__u64) gfmul_c0[(__u64)t1 >> 8 & 0xff]) << 8 | + ((__u64) gfmul_c0[(__u64)t1 >> 0 & 0xff]) << 0); + } else { + TRACE_FUN(ft_t_any); + TRACE(ft_t_err, "Error: long is of size %d", + (int) sizeof(long)); + TRACE_EXIT; + } + p0 = gfadd_long(t2, p1); + p1 = gfadd_long(t2, p2); + p2 = t1; + data += FT_SECTOR_SIZE / sizeof(long); + } + *p = p0; + p += stride; + *p = p1; + p += stride; + *p = p2; + return; +} + + +/* 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(__u8 *data, int nblocks, + int nbad, int *bad_loc, Matrix Ainv, + __u8 *s, + SectorMap * correction_map) +{ + int ncorrected = 0; + int i; + __u8 t1, t2; + __u8 c0, c1, c2; /* check bytes */ + __u8 error_mag[3], log_error_mag; + __u8 *dp, l, e; + TRACE_FUN(ft_t_any); + + switch (nbad) { + case 0: + /* might have a CRC failure: */ + if (s[0] == 0) { + /* more than one error */ + TRACE_ABORT(-1, ft_t_err, + "ECC failed (0 CRC errors, >1 CRC failures)"); + } + t1 = gfdiv(s[1], s[0]); + if ((bad_loc[nbad++] = gflog[t1]) >= nblocks) { + TRACE(ft_t_err, + "ECC failed (0 CRC errors, >1 CRC failures)"); + TRACE_ABORT(-1, ft_t_err, + "attempt to correct data at %d", bad_loc[0]); + } + 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_ABORT(-1, ft_t_err, + "ECC failed (1 erasure, >1 error)"); + } else { + /* one erasure, one error: */ + if ((bad_loc[nbad++] = gflog[gfdiv(t1, t2)]) + >= nblocks) { + TRACE(ft_t_err, "ECC failed " + "(1 CRC errors, >1 CRC failures)"); + TRACE_ABORT(-1, ft_t_err, + "attempt to correct data at %d", + bad_loc[1]); + } + if (!gfinv2(bad_loc[0], bad_loc[1], Ainv)) { + /* inversion failed---must have more + * than one error + */ + TRACE_EXIT -1; + } + } + /* FALL THROUGH TO ERROR MAGNITUDE COMPUTATION: + */ + case 2: + case 3: + /* compute error magnitudes: */ + gfmat_mul(nbad, Ainv, s, error_mag); + break; + + default: + TRACE_ABORT(-1, ft_t_err, + "Internal Error: number of CRC errors > 3"); + } + + /* 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 * FT_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_ABORT(-1, ft_t_err, + "ECC self-check failed, too many errors"); + } + TRACE_EXIT 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(ft_t_any); + unsigned long s[3]; + + if (!compute_syndromes(data, nblocks, s)) { + TRACE_ABORT(0, ft_bug, + "Internal Error: syndrome self-check failed"); + } + TRACE_EXIT 1; +} + +#endif /* defined(ECC_SANITY_CHECK) || defined(ECC_PARANOID) */ + +/* Compute the parity for an entire segment of data. + */ +int ftape_ecc_set_segment_parity(struct memory_segment *mseg) +{ + int i; + __u8 *parity_bytes; + + parity_bytes = &mseg->data[(mseg->blocks - 3) * FT_SECTOR_SIZE]; + for (i = 0; i < FT_SECTOR_SIZE; i += sizeof(long)) { + set_parity((unsigned long *) &mseg->data[i], mseg->blocks - 3, + (unsigned long *) &parity_bytes[i], + FT_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 ftape_ecc_correct_data(struct memory_segment *mseg) +{ + int col, i, result; + int ncorrected = 0; + int nerasures = 0; /* # of erasures (CRC errors) */ + int erasure_loc[3]; /* erasure locations */ + unsigned long ss[3]; + __u8 s[3]; + Matrix Ainv; + TRACE_FUN(ft_t_flow); + + mseg->corrected = 0; + + /* find first column that has non-zero syndromes: */ + for (col = 0; col < FT_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 >= FT_SECTOR_SIZE) { + /* all syndromes are ok, therefore nothing to correct */ + TRACE_EXIT 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_ABORT(ECC_FAILED, ft_t_err, + "ECC failed (>3 CRC errors)"); + } /* 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 ECC_FAILED; + } + break; + case 3: + if (!gfinv3(erasure_loc[0], erasure_loc[1], + erasure_loc[2], Ainv)) { + TRACE_EXIT 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 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 ECC_FAILED; + } +#endif /* ECC_SANITY_CHECK */ + + /* find next column with non-zero syndromes: */ + while ((col += sizeof(long)) < FT_SECTOR_SIZE) { + if (!compute_syndromes((unsigned long *) + &mseg->data[col], mseg->blocks, ss)) { + /* something is wrong---have to fix things */ + break; + } + } + } while (col < FT_SECTOR_SIZE); + if (ncorrected && nerasures == 0) { + TRACE(ft_t_warn, "block contained error not caught by CRC"); + } + TRACE((ncorrected > 0) ? ft_t_noise : ft_t_any, "number of corrections: %d", ncorrected); + TRACE_EXIT ncorrected ? ECC_CORRECTED : ECC_OK; +} diff --git a/drivers/char/ftape/lowlevel/ftape-ecc.h b/drivers/char/ftape/lowlevel/ftape-ecc.h new file mode 100644 index 000000000000..4829146fe9a0 --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-ecc.h @@ -0,0 +1,84 @@ +#ifndef _FTAPE_ECC_H_ +#define _FTAPE_ECC_H_ + +/* + * 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-ecc.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:18:11 $ + * + * This file contains the definitions for the + * Reed-Solomon error correction code + * for the QIC-40/80 tape streamer device driver. + */ + +#include "../lowlevel/ftape-bsm.h" + +#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 { + SectorMap marked_bad; + SectorMap read_bad; + int blocks; + __u8 *data; + SectorMap corrected; + }; + +/* + * ecc.c defined global variables: + */ +#ifdef TEST +extern int ftape_ecc_tracing; +#endif + +/* + * ecc.c defined global functions: + */ +extern int ftape_ecc_correct_data(struct memory_segment *data); +extern int ftape_ecc_set_segment_parity(struct memory_segment *data); + +#endif /* _FTAPE_ECC_H_ */ diff --git a/drivers/char/ftape/lowlevel/ftape-format.c b/drivers/char/ftape/lowlevel/ftape-format.c new file mode 100644 index 000000000000..5dd4c59a3f34 --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-format.c @@ -0,0 +1,344 @@ +/* + * Copyright (C) 1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-format.c,v $ + * $Revision: 1.2.4.1 $ + * $Date: 1997/11/14 16:05:39 $ + * + * This file contains the code to support formatting of floppy + * tape cartridges with the QIC-40/80/3010/3020 floppy-tape + * driver "ftape" for Linux. + */ + +#include <linux/string.h> +#include <linux/errno.h> + +#include <linux/ftape.h> +#include <linux/qic117.h> +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/ftape-io.h" +#include "../lowlevel/ftape-ctl.h" +#include "../lowlevel/ftape-rw.h" +#include "../lowlevel/ftape-ecc.h" +#include "../lowlevel/ftape-bsm.h" +#include "../lowlevel/ftape-format.h" + +#if defined(TESTING) +#define FT_FMT_SEGS_PER_BUF 50 +#else +#define FT_FMT_SEGS_PER_BUF (FT_BUFF_SIZE/(4*FT_SECTORS_PER_SEGMENT)) +#endif + +static spinlock_t ftape_format_lock; + +/* + * first segment of the new buffer + */ +static int switch_segment; + +/* + * at most 256 segments fit into one 32 kb buffer. Even TR-1 cartridges have + * more than this many segments per track, so better be careful. + * + * buffer_struct *buff: buffer to store the formatting coordinates in + * int start: starting segment for this buffer. + * int spt: segments per track + * + * Note: segment ids are relative to the start of the track here. + */ +static void setup_format_buffer(buffer_struct *buff, int start, int spt, + __u8 gap3) +{ + int to_do = spt - start; + TRACE_FUN(ft_t_flow); + + if (to_do > FT_FMT_SEGS_PER_BUF) { + to_do = FT_FMT_SEGS_PER_BUF; + } + buff->ptr = buff->address; + buff->remaining = to_do * FT_SECTORS_PER_SEGMENT; /* # sectors */ + buff->bytes = buff->remaining * 4; /* need 4 bytes per sector */ + buff->gap3 = gap3; + buff->segment_id = start; + buff->next_segment = start + to_do; + if (buff->next_segment >= spt) { + buff->next_segment = 0; /* 0 means: stop runner */ + } + buff->status = waiting; /* tells the isr that it can use + * this buffer + */ + TRACE_EXIT; +} + + +/* + * start formatting a new track. + */ +int ftape_format_track(const unsigned int track, const __u8 gap3) +{ + unsigned long flags; + buffer_struct *tail, *head; + int status; + TRACE_FUN(ft_t_flow); + + TRACE_CATCH(ftape_ready_wait(ftape_timeout.pause, &status),); + if (track & 1) { + if (!(status & QIC_STATUS_AT_EOT)) { + TRACE_CATCH(ftape_seek_to_eot(),); + } + } else { + if (!(status & QIC_STATUS_AT_BOT)) { + TRACE_CATCH(ftape_seek_to_bot(),); + } + } + ftape_abort_operation(); /* this sets ft_head = ft_tail = 0 */ + ftape_set_state(formatting); + + TRACE(ft_t_noise, + "Formatting track %d, logical: from segment %d to %d", + track, track * ft_segments_per_track, + (track + 1) * ft_segments_per_track - 1); + + /* + * initialize the buffer switching protocol for this track + */ + head = ftape_get_buffer(ft_queue_head); /* tape isn't running yet */ + tail = ftape_get_buffer(ft_queue_tail); /* tape isn't running yet */ + switch_segment = 0; + do { + FT_SIGNAL_EXIT(_DONT_BLOCK); + setup_format_buffer(tail, switch_segment, + ft_segments_per_track, gap3); + switch_segment = tail->next_segment; + } while ((switch_segment != 0) && + ((tail = ftape_next_buffer(ft_queue_tail)) != head)); + /* go */ + head->status = formatting; + TRACE_CATCH(ftape_seek_head_to_track(track),); + TRACE_CATCH(ftape_command(QIC_LOGICAL_FORWARD),); + spin_lock_irqsave(&ftape_format_lock, flags); + TRACE_CATCH(fdc_setup_formatting(head), restore_flags(flags)); + spin_unlock_irqrestore(&ftape_format_lock, flags); + TRACE_EXIT 0; +} + +/* return segment id of segment currently being formatted and do the + * buffer switching stuff. + */ +int ftape_format_status(unsigned int *segment_id) +{ + buffer_struct *tail = ftape_get_buffer(ft_queue_tail); + int result; + TRACE_FUN(ft_t_flow); + + while (switch_segment != 0 && + ftape_get_buffer(ft_queue_head) != tail) { + FT_SIGNAL_EXIT(_DONT_BLOCK); + /* need more buffers, first wait for empty buffer + */ + TRACE_CATCH(ftape_wait_segment(formatting),); + /* don't worry for gap3. If we ever hit this piece of code, + * then all buffer already have the correct gap3 set! + */ + setup_format_buffer(tail, switch_segment, + ft_segments_per_track, tail->gap3); + switch_segment = tail->next_segment; + if (switch_segment != 0) { + tail = ftape_next_buffer(ft_queue_tail); + } + } + /* should runner stop ? + */ + if (ft_runner_status == aborting || ft_runner_status == do_abort) { + buffer_struct *head = ftape_get_buffer(ft_queue_head); + TRACE(ft_t_warn, "Error formatting segment %d", + ftape_get_buffer(ft_queue_head)->segment_id); + (void)ftape_abort_operation(); + TRACE_EXIT (head->status != error) ? -EAGAIN : -EIO; + } + /* + * don't care if the timer expires, this is just kind of a + * "select" operation that lets the calling process sleep + * until something has happened + */ + if (fdc_interrupt_wait(5 * FT_SECOND) < 0) { + TRACE(ft_t_noise, "End of track %d at segment %d", + ft_location.track, + ftape_get_buffer(ft_queue_head)->segment_id); + result = 1; /* end of track, unlock module */ + } else { + result = 0; + } + /* + * the calling process should use the seg id to determine + * which parts of the dma buffers can be safely overwritten + * with new data. + */ + *segment_id = ftape_get_buffer(ft_queue_head)->segment_id; + /* + * Internally we start counting segment ids from the start of + * each track when formatting, but externally we keep them + * relative to the start of the tape: + */ + *segment_id += ft_location.track * ft_segments_per_track; + TRACE_EXIT result; +} + +/* + * The segment id is relative to the start of the tape + */ +int ftape_verify_segment(const unsigned int segment_id, SectorMap *bsm) +{ + int result; + int verify_done = 0; + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_noise, "Verifying segment %d", segment_id); + + if (ft_driver_state != verifying) { + TRACE(ft_t_noise, "calling ftape_abort_operation"); + if (ftape_abort_operation() < 0) { + TRACE(ft_t_err, "ftape_abort_operation failed"); + TRACE_EXIT -EIO; + } + } + *bsm = 0x00000000; + ftape_set_state(verifying); + for (;;) { + buffer_struct *tail; + /* + * Allow escape from this loop on signal + */ + FT_SIGNAL_EXIT(_DONT_BLOCK); + /* + * Search all full buffers for the first matching the + * wanted segment. Clear other buffers on the fly. + */ + tail = ftape_get_buffer(ft_queue_tail); + while (!verify_done && tail->status == done) { + /* + * Allow escape from this loop on signal ! + */ + FT_SIGNAL_EXIT(_DONT_BLOCK); + if (tail->segment_id == segment_id) { + /* If out buffer is already full, + * return its contents. + */ + TRACE(ft_t_flow, "found segment in cache: %d", + segment_id); + if ((tail->soft_error_map | + tail->hard_error_map) != 0) { + TRACE(ft_t_info,"bsm[%d] = 0x%08lx", + segment_id, + (unsigned long) + (tail->soft_error_map | + tail->hard_error_map)); + *bsm = (tail->soft_error_map | + tail->hard_error_map); + } + verify_done = 1; + } else { + TRACE(ft_t_flow,"zapping segment in cache: %d", + tail->segment_id); + } + tail->status = waiting; + tail = ftape_next_buffer(ft_queue_tail); + } + if (!verify_done && tail->status == verifying) { + if (tail->segment_id == segment_id) { + switch(ftape_wait_segment(verifying)) { + case 0: + break; + case -EINTR: + TRACE_ABORT(-EINTR, ft_t_warn, + "interrupted by " + "non-blockable signal"); + break; + default: + ftape_abort_operation(); + ftape_set_state(verifying); + /* be picky */ + TRACE_ABORT(-EIO, ft_t_warn, + "wait_segment failed"); + } + } else { + /* We're reading the wrong segment, + * stop runner. + */ + TRACE(ft_t_noise, "verifying wrong segment"); + ftape_abort_operation(); + ftape_set_state(verifying); + } + } + /* should runner stop ? + */ + if (ft_runner_status == aborting) { + buffer_struct *head = ftape_get_buffer(ft_queue_head); + if (head->status == error || + head->status == verifying) { + /* no data or overrun error */ + head->status = waiting; + } + TRACE_CATCH(ftape_dumb_stop(),); + } else { + /* If just passed last segment on tape: wait + * for BOT or EOT mark. Sets ft_runner_status to + * idle if at lEOT and successful + */ + TRACE_CATCH(ftape_handle_logical_eot(),); + } + if (verify_done) { + TRACE_EXIT 0; + } + /* Now at least one buffer is idle! + * Restart runner & tape if needed. + */ + /* We could optimize the following a little bit. We know that + * the bad sector map is empty. + */ + tail = ftape_get_buffer(ft_queue_tail); + if (tail->status == waiting) { + buffer_struct *head = ftape_get_buffer(ft_queue_head); + + ftape_setup_new_segment(head, segment_id, -1); + ftape_calc_next_cluster(head); + if (ft_runner_status == idle) { + result = ftape_start_tape(segment_id, + head->sector_offset); + switch(result) { + case 0: + break; + case -ETIME: + case -EINTR: + TRACE_ABORT(result, ft_t_err, "Error: " + "segment %d unreachable", + segment_id); + break; + default: + *bsm = EMPTY_SEGMENT; + TRACE_EXIT 0; + break; + } + } + head->status = verifying; + fdc_setup_read_write(head, FDC_VERIFY); + } + } + /* not reached */ + TRACE_EXIT -EIO; +} diff --git a/drivers/char/ftape/lowlevel/ftape-format.h b/drivers/char/ftape/lowlevel/ftape-format.h new file mode 100644 index 000000000000..f15161566643 --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-format.h @@ -0,0 +1,37 @@ +#ifndef _FTAPE_FORMAT_H +#define _FTAPE_FORMAT_H + +/* + * Copyright (C) 1996-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-format.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:18:13 $ + * + * This file contains the low level definitions for the + * formatting support for the QIC-40/80/3010/3020 floppy-tape + * driver "ftape" for Linux. + */ + +#ifdef __KERNEL__ +extern int ftape_format_track(const unsigned int track, const __u8 gap3); +extern int ftape_format_status(unsigned int *segment_id); +extern int ftape_verify_segment(const unsigned int segment_id, SectorMap *bsm); +#endif /* __KERNEL__ */ + +#endif diff --git a/drivers/char/ftape/lowlevel/ftape-init.c b/drivers/char/ftape/lowlevel/ftape-init.c new file mode 100644 index 000000000000..b54260d457c2 --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-init.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 1993-1996 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + 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/3010/3020 floppy-tape driver for Linux. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/major.h> + +#include <linux/ftape.h> +#include <linux/init.h> +#include <linux/qic117.h> +#ifdef CONFIG_ZFTAPE +#include <linux/zftape.h> +#endif + +#include "../lowlevel/ftape-init.h" +#include "../lowlevel/ftape-io.h" +#include "../lowlevel/ftape-read.h" +#include "../lowlevel/ftape-write.h" +#include "../lowlevel/ftape-ctl.h" +#include "../lowlevel/ftape-rw.h" +#include "../lowlevel/fdc-io.h" +#include "../lowlevel/ftape-buffer.h" +#include "../lowlevel/ftape-proc.h" +#include "../lowlevel/ftape-tracing.h" + + +#if defined(MODULE) && !defined(CONFIG_FT_NO_TRACE_AT_ALL) +static int ft_tracing = -1; +#endif + + +/* Called by modules package when installing the driver + * or by kernel during the initialization phase + */ +static int __init ftape_init(void) +{ + TRACE_FUN(ft_t_flow); + +#ifdef MODULE +#ifndef CONFIG_FT_NO_TRACE_AT_ALL + if (ft_tracing != -1) { + ftape_tracing = ft_tracing; + } +#endif + printk(KERN_INFO FTAPE_VERSION "\n"); + if (TRACE_LEVEL >= ft_t_info) { + printk( +KERN_INFO "(c) 1993-1996 Bas Laarhoven (bas@vimec.nl)\n" +KERN_INFO "(c) 1995-1996 Kai Harrekilde-Petersen (khp@dolphinics.no)\n" +KERN_INFO "(c) 1996-1997 Claus-Justus Heine (claus@momo.math.rwth-aachen.de)\n" +KERN_INFO "QIC-117 driver for QIC-40/80/3010/3020 floppy tape drives\n"); + } +#else /* !MODULE */ + /* print a short no-nonsense boot message */ + printk(KERN_INFO FTAPE_VERSION "\n"); +#endif /* MODULE */ + TRACE(ft_t_info, "installing QIC-117 floppy tape hardware drive ... "); + TRACE(ft_t_info, "ftape_init @ 0x%p", ftape_init); + /* Allocate the DMA buffers. They are deallocated at cleanup() time. + */ +#ifdef TESTING +#ifdef MODULE + while (ftape_set_nr_buffers(CONFIG_FT_NR_BUFFERS) < 0) { + ftape_sleep(FT_SECOND/20); + if (signal_pending(current)) { + (void)ftape_set_nr_buffers(0); + TRACE(ft_t_bug, + "Killed by signal while allocating buffers."); + TRACE_ABORT(-EINTR, + ft_t_bug, "Free up memory and retry"); + } + } +#else + TRACE_CATCH(ftape_set_nr_buffers(CONFIG_FT_NR_BUFFERS), + (void)ftape_set_nr_buffers(0)); +#endif +#else + TRACE_CATCH(ftape_set_nr_buffers(CONFIG_FT_NR_BUFFERS), + (void)ftape_set_nr_buffers(0)); +#endif + ft_drive_sel = -1; + ft_failure = 1; /* inhibit any operation but open */ + ftape_udelay_calibrate(); /* must be before fdc_wait_calibrate ! */ + fdc_wait_calibrate(); +#if defined(CONFIG_PROC_FS) && defined(CONFIG_FT_PROC_FS) + (void)ftape_proc_init(); +#endif +#ifdef CONFIG_ZFTAPE + (void)zft_init(); +#endif + TRACE_EXIT 0; +} + +module_param(ft_fdc_base, uint, 0); +MODULE_PARM_DESC(ft_fdc_base, "Base address of FDC controller."); +module_param(ft_fdc_irq, uint, 0); +MODULE_PARM_DESC(ft_fdc_irq, "IRQ (interrupt channel) to use."); +module_param(ft_fdc_dma, uint, 0); +MODULE_PARM_DESC(ft_fdc_dma, "DMA channel to use."); +module_param(ft_fdc_threshold, uint, 0); +MODULE_PARM_DESC(ft_fdc_threshold, "Threshold of the FDC Fifo."); +module_param(ft_fdc_rate_limit, uint, 0); +MODULE_PARM_DESC(ft_fdc_rate_limit, "Maximal data rate for FDC."); +module_param(ft_probe_fc10, bool, 0); +MODULE_PARM_DESC(ft_probe_fc10, + "If non-zero, probe for a Colorado FC-10/FC-20 controller."); +module_param(ft_mach2, bool, 0); +MODULE_PARM_DESC(ft_mach2, + "If non-zero, probe for a Mountain MACH-2 controller."); +#if defined(MODULE) && !defined(CONFIG_FT_NO_TRACE_AT_ALL) +module_param(ft_tracing, int, 0644); +MODULE_PARM_DESC(ft_tracing, + "Amount of debugging output, 0 <= tracing <= 8, default 3."); +#endif + +MODULE_AUTHOR( + "(c) 1993-1996 Bas Laarhoven (bas@vimec.nl), " + "(c) 1995-1996 Kai Harrekilde-Petersen (khp@dolphinics.no), " + "(c) 1996, 1997 Claus-Justus Heine (claus@momo.math.rwth-aachen.de)"); +MODULE_DESCRIPTION( + "QIC-117 driver for QIC-40/80/3010/3020 floppy tape drives."); +MODULE_LICENSE("GPL"); + +static void __exit ftape_exit(void) +{ + TRACE_FUN(ft_t_flow); + +#if defined(CONFIG_PROC_FS) && defined(CONFIG_FT_PROC_FS) + ftape_proc_destroy(); +#endif + (void)ftape_set_nr_buffers(0); + printk(KERN_INFO "ftape: unloaded.\n"); + TRACE_EXIT; +} + +module_init(ftape_init); +module_exit(ftape_exit); diff --git a/drivers/char/ftape/lowlevel/ftape-init.h b/drivers/char/ftape/lowlevel/ftape-init.h new file mode 100644 index 000000000000..99a7b8ab086f --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-init.h @@ -0,0 +1,43 @@ +#ifndef _FTAPE_INIT_H +#define _FTAPE_INIT_H + +/* + * Copyright (C) 1993-1996 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-init.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:18:16 $ + * + * This file contains the definitions for the interface to + * the Linux kernel for floppy tape driver ftape. + * + */ + +#include <linux/linkage.h> +#include <linux/signal.h> + +#define _NEVER_BLOCK (sigmask(SIGKILL) | sigmask(SIGSTOP)) +#define _DONT_BLOCK (_NEVER_BLOCK | sigmask(SIGINT)) +#define _DO_BLOCK (sigmask(SIGPIPE)) + +#ifndef QIC117_TAPE_MAJOR +#define QIC117_TAPE_MAJOR 27 +#endif + +#endif diff --git a/drivers/char/ftape/lowlevel/ftape-io.c b/drivers/char/ftape/lowlevel/ftape-io.c new file mode 100644 index 000000000000..259015aeff55 --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-io.c @@ -0,0 +1,992 @@ +/* + * Copyright (C) 1993-1996 Bas Laarhoven, + * (C) 1996 Kai Harrekilde-Petersen, + * (C) 1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-io.c,v $ + * $Revision: 1.4 $ + * $Date: 1997/11/11 14:02:36 $ + * + * This file contains the general control functions for the + * QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux. + */ + +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <asm/system.h> +#include <linux/ioctl.h> +#include <linux/mtio.h> +#include <linux/delay.h> + +#include <linux/ftape.h> +#include <linux/qic117.h> +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/fdc-io.h" +#include "../lowlevel/ftape-io.h" +#include "../lowlevel/ftape-ctl.h" +#include "../lowlevel/ftape-rw.h" +#include "../lowlevel/ftape-write.h" +#include "../lowlevel/ftape-read.h" +#include "../lowlevel/ftape-init.h" +#include "../lowlevel/ftape-calibr.h" + +/* Global vars. + */ +/* NOTE: sectors start numbering at 1, all others at 0 ! */ +ft_timeout_table ftape_timeout; +unsigned int ftape_tape_len; +volatile qic117_cmd_t ftape_current_command; +const struct qic117_command_table qic117_cmds[] = QIC117_COMMANDS; +int ftape_might_be_off_track; + +/* Local vars. + */ +static int diagnostic_mode; +static unsigned int ftape_udelay_count; +static unsigned int ftape_udelay_time; + +void ftape_udelay(unsigned int usecs) +{ + volatile int count = (ftape_udelay_count * usecs + + ftape_udelay_count - 1) / ftape_udelay_time; + volatile int i; + + while (count-- > 0) { + for (i = 0; i < 20; ++i); + } +} + +void ftape_udelay_calibrate(void) +{ + ftape_calibrate("ftape_udelay", + ftape_udelay, &ftape_udelay_count, &ftape_udelay_time); +} + +/* Delay (msec) routine. + */ +void ftape_sleep(unsigned int time) +{ + TRACE_FUN(ft_t_any); + + time *= 1000; /* msecs -> usecs */ + if (time < FT_USPT) { + /* Time too small for scheduler, do a busy wait ! */ + ftape_udelay(time); + } else { + long timeout; + unsigned long flags; + unsigned int ticks = (time + FT_USPT - 1) / FT_USPT; + + TRACE(ft_t_any, "%d msec, %d ticks", time/1000, ticks); + timeout = ticks; + save_flags(flags); + sti(); + msleep_interruptible(jiffies_to_msecs(timeout)); + /* Mmm. Isn't current->blocked == 0xffffffff ? + */ + if (signal_pending(current)) { + TRACE(ft_t_err, "awoken by non-blocked signal :-("); + } + restore_flags(flags); + } + TRACE_EXIT; +} + +/* send a command or parameter to the drive + * Generates # of step pulses. + */ +static inline int ft_send_to_drive(int arg) +{ + /* Always wait for a command_timeout period to separate + * individuals commands and/or parameters. + */ + ftape_sleep(3 * FT_MILLISECOND); + /* Keep cylinder nr within range, step towards home if possible. + */ + if (ftape_current_cylinder >= arg) { + return fdc_seek(ftape_current_cylinder - arg); + } else { + return fdc_seek(ftape_current_cylinder + arg); + } +} + +/* forward */ int ftape_report_raw_drive_status(int *status); + +static int ft_check_cmd_restrictions(qic117_cmd_t command) +{ + int status = -1; + TRACE_FUN(ft_t_any); + + TRACE(ft_t_flow, "%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[ftape_current_command].non_intr) { + ftape_report_raw_drive_status(&status); + if ((status & QIC_STATUS_READY) == 0) { + TRACE(ft_t_noise, + "motion cmd (%d) during non-intr cmd (%d)", + command, ftape_current_command); + TRACE(ft_t_noise, "waiting until drive gets ready"); + ftape_ready_wait(ftape_timeout.seek, + &status); + } + } + if (qic117_cmds[command].mask != 0) { + __u8 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) { + TRACE(ft_t_noise, "command %d issued while not ready", + command); + TRACE(ft_t_noise, "waiting until drive gets ready"); + if (ftape_ready_wait(ftape_timeout.seek, + &status) == -EINTR) { + /* Bail out on signal ! + */ + TRACE_ABORT(-EINTR, ft_t_warn, + "interrupted by non-blockable signal"); + } + difference = ((status ^ qic117_cmds[command].state) & + qic117_cmds[command].mask); + } + while ((difference & QIC_STATUS_ERROR) != 0) { + int err; + qic117_cmd_t cmd; + + TRACE(ft_t_noise, + "command %d issued while error pending", + command); + TRACE(ft_t_noise, "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); + if ((difference & QIC_STATUS_ERROR) != 0) { + /* Bail out on fatal signal ! + */ + FT_SIGNAL_EXIT(_NEVER_BLOCK); + } + } + if (difference) { + /* Any remaining difference can't be solved + * here. + */ + if (difference & (QIC_STATUS_CARTRIDGE_PRESENT | + QIC_STATUS_NEW_CARTRIDGE | + QIC_STATUS_REFERENCED)) { + TRACE(ft_t_warn, + "Fatal: tape removed or reinserted !"); + ft_failure = 1; + } else { + TRACE(ft_t_err, "wrong state: 0x%02x should be: 0x%02x", + status & qic117_cmds[command].mask, + qic117_cmds[command].state); + } + TRACE_EXIT -EIO; + } + if (~status & QIC_STATUS_READY & qic117_cmds[command].mask) { + TRACE_ABORT(-EBUSY, ft_t_err, "Bad: still busy!"); + } + } + TRACE_EXIT 0; +} + +/* Issue a tape command: + */ +int ftape_command(qic117_cmd_t command) +{ + int result = 0; + static int level; + TRACE_FUN(ft_t_any); + + if ((unsigned int)command > NR_ITEMS(qic117_cmds)) { + /* This is a bug we'll want to know about too. + */ + TRACE_ABORT(-EIO, ft_t_bug, "bug - bad command: %d", command); + } + if (++level > 5) { /* This is a bug we'll want to know about. */ + --level; + TRACE_ABORT(-EIO, ft_t_bug, "bug - recursion for command: %d", + command); + } + /* disable logging and restriction check for some commands, + * check all other commands that have a prescribed starting + * status. + */ + if (diagnostic_mode) { + TRACE(ft_t_flow, "diagnostic command %d", command); + } else if (command == QIC_REPORT_DRIVE_STATUS || + command == QIC_REPORT_NEXT_BIT) { + TRACE(ft_t_any, "%s", qic117_cmds[command].name); + } else { + TRACE_CATCH(ft_check_cmd_restrictions(command), --level); + } + /* Now all conditions are met or result was < 0. + */ + result = ft_send_to_drive((unsigned int)command); + if (qic117_cmds[command].cmd_type == motion && + command != QIC_LOGICAL_FORWARD && command != QIC_STOP_TAPE) { + ft_location.known = 0; + } + ftape_current_command = command; + --level; + TRACE_EXIT result; +} + +/* Send a tape command parameter: + * Generates command # of step pulses. + * Skips tape-status call ! + */ +int ftape_parameter(unsigned int parameter) +{ + TRACE_FUN(ft_t_any); + + TRACE(ft_t_flow, "called with parameter = %d", parameter); + TRACE_EXIT ft_send_to_drive(parameter + 2); +} + +/* Wait for the drive to get ready. + * timeout time in milli-seconds + * Returned status is valid if result != -EIO + * + * Should we allow to be killed by SIGINT? (^C) + * Would be nice at least for large timeouts. + */ +int ftape_ready_wait(unsigned int timeout, int *status) +{ + unsigned long t0; + unsigned int poll_delay; + int signal_retries; + TRACE_FUN(ft_t_any); + + /* the following ** REALLY ** reduces the system load when + * e.g. one simply rewinds or retensions. The tape is slow + * anyway. It is really not necessary to detect error + * conditions with 1/10 seconds granularity + * + * On my AMD 133MHZ 486: 100 ms: 23% system load + * 1 sec: 5% + * 5 sec: 0.6%, yeah + */ + if (timeout <= FT_SECOND) { + poll_delay = 100 * FT_MILLISECOND; + signal_retries = 20; /* two seconds */ + } else if (timeout < 20 * FT_SECOND) { + TRACE(ft_t_flow, "setting poll delay to 1 second"); + poll_delay = FT_SECOND; + signal_retries = 2; /* two seconds */ + } else { + TRACE(ft_t_flow, "setting poll delay to 5 seconds"); + poll_delay = 5 * FT_SECOND; + signal_retries = 1; /* five seconds */ + } + for (;;) { + t0 = jiffies; + TRACE_CATCH(ftape_report_raw_drive_status(status),); + if (*status & QIC_STATUS_READY) { + TRACE_EXIT 0; + } + if (!signal_retries--) { + FT_SIGNAL_EXIT(_NEVER_BLOCK); + } + if ((int)timeout >= 0) { + /* this will fail when jiffies wraps around about + * once every year :-) + */ + timeout -= ((jiffies - t0) * FT_SECOND) / HZ; + if (timeout <= 0) { + TRACE_ABORT(-ETIME, ft_t_err, "timeout"); + } + ftape_sleep(poll_delay); + timeout -= poll_delay; + } else { + ftape_sleep(poll_delay); + } + } + TRACE_EXIT -ETIME; +} + +/* Issue command and wait up to timeout milli seconds for drive ready + */ +int ftape_command_wait(qic117_cmd_t command, unsigned int timeout, int *status) +{ + int result; + + /* Drive should be ready, issue command + */ + result = ftape_command(command); + if (result >= 0) { + result = ftape_ready_wait(timeout, status); + } + return result; +} + +static int ftape_parameter_wait(unsigned int parm, unsigned int timeout, int *status) +{ + int result; + + /* Drive should be ready, issue command + */ + result = ftape_parameter(parm); + if (result >= 0) { + result = ftape_ready_wait(timeout, status); + } + 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). */ + +int ftape_report_operation(int *status, + qic117_cmd_t command, + int result_length) +{ + int i, st3; + unsigned int t0; + unsigned int dt; + TRACE_FUN(ft_t_any); + + TRACE_CATCH(ftape_command(command),); + t0 = ftape_timestamp(); + i = 0; + do { + ++i; + ftape_sleep(3 * FT_MILLISECOND); /* see remark below */ + TRACE_CATCH(fdc_sense_drive_status(&st3),); + dt = ftape_timediff(t0, ftape_timestamp()); + /* 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)) { + TRACE(ft_t_err, + "No acknowledge after %u msec. (%i iter)", dt / 1000, i); + TRACE_ABORT(-EIO, ft_t_err, "timeout on Acknowledge"); + } + /* dt may be larger than expected because of other tasks + * scheduled while we were sleeping. + */ + if (i > 1 && dt > 6000) { + TRACE(ft_t_err, "Acknowledge after %u msec. (%i iter)", + dt / 1000, i); + } + *status = 0; + for (i = 0; i < result_length + 1; i++) { + TRACE_CATCH(ftape_command(QIC_REPORT_NEXT_BIT),); + TRACE_CATCH(fdc_sense_drive_status(&st3),); + if (i < result_length) { + *status |= ((st3 & ST3_TRACK_0) ? 1 : 0) << i; + } else if ((st3 & ST3_TRACK_0) == 0) { + TRACE_ABORT(-EIO, ft_t_err, "missing status stop bit"); + } + } + /* this command will put track zero and index back into normal state */ + (void)ftape_command(QIC_REPORT_NEXT_BIT); + TRACE_EXIT 0; +} + +/* Report the current drive status. */ + +int ftape_report_raw_drive_status(int *status) +{ + int result; + int count = 0; + TRACE_FUN(ft_t_any); + + do { + result = ftape_report_operation(status, + QIC_REPORT_DRIVE_STATUS, 8); + } while (result < 0 && ++count <= 3); + if (result < 0) { + TRACE_ABORT(-EIO, ft_t_err, + "report_operation failed after %d trials", count); + } + if ((*status & 0xff) == 0xff) { + TRACE_ABORT(-EIO, ft_t_err, + "impossible drive status 0xff"); + } + if (*status & QIC_STATUS_READY) { + ftape_current_command = QIC_NO_COMMAND; /* completed */ + } + ft_last_status.status.drive_status = (__u8)(*status & 0xff); + TRACE_EXIT 0; +} + +int ftape_report_drive_status(int *status) +{ + TRACE_FUN(ft_t_any); + + TRACE_CATCH(ftape_report_raw_drive_status(status),); + if (*status & QIC_STATUS_NEW_CARTRIDGE || + !(*status & QIC_STATUS_CARTRIDGE_PRESENT)) { + ft_failure = 1; /* will inhibit further operations */ + TRACE_EXIT -EIO; + } + if (*status & QIC_STATUS_READY && *status & QIC_STATUS_ERROR) { + /* Let caller handle all errors */ + TRACE_ABORT(1, ft_t_warn, "warning: error status set!"); + } + TRACE_EXIT 0; +} + +int ftape_report_error(unsigned int *error, + qic117_cmd_t *command, int report) +{ + static const ftape_error ftape_errors[] = QIC117_ERRORS; + int code; + TRACE_FUN(ft_t_any); + + TRACE_CATCH(ftape_report_operation(&code, QIC_REPORT_ERROR_CODE, 16),); + *error = (unsigned int)(code & 0xff); + *command = (qic117_cmd_t)((code>>8)&0xff); + /* remember hardware status, maybe useful for status ioctls + */ + ft_last_error.error.command = (__u8)*command; + ft_last_error.error.error = (__u8)*error; + if (!report) { + TRACE_EXIT 0; + } + if (*error == 0) { + TRACE_ABORT(0, ft_t_info, "No error"); + } + TRACE(ft_t_info, "errorcode: %d", *error); + if (*error < NR_ITEMS(ftape_errors)) { + TRACE(ft_t_noise, "%sFatal ERROR:", + (ftape_errors[*error].fatal ? "" : "Non-")); + TRACE(ft_t_noise, "%s ...", ftape_errors[*error].message); + } else { + TRACE(ft_t_noise, "Unknown ERROR !"); + } + if ((unsigned int)*command < NR_ITEMS(qic117_cmds) && + qic117_cmds[*command].name != NULL) { + TRACE(ft_t_noise, "... caused by command \'%s\'", + qic117_cmds[*command].name); + } else { + TRACE(ft_t_noise, "... caused by unknown command %d", + *command); + } + TRACE_EXIT 0; +} + +int ftape_report_configuration(qic_model *model, + unsigned int *rate, + int *qic_std, + int *tape_len) +{ + int result; + int config; + int status; + static const unsigned int qic_rates[ 4] = { 250, 2000, 500, 1000 }; + TRACE_FUN(ft_t_any); + + result = ftape_report_operation(&config, + QIC_REPORT_DRIVE_CONFIGURATION, 8); + if (result < 0) { + ft_last_status.status.drive_config = (__u8)0x00; + *model = prehistoric; + *rate = 500; + *qic_std = QIC_TAPE_QIC40; + *tape_len = 205; + TRACE_EXIT 0; + } else { + ft_last_status.status.drive_config = (__u8)(config & 0xff); + } + *rate = qic_rates[(config & QIC_CONFIG_RATE_MASK) >> QIC_CONFIG_RATE_SHIFT]; + result = ftape_report_operation(&status, QIC_REPORT_TAPE_STATUS, 8); + if (result < 0) { + ft_last_status.status.tape_status = (__u8)0x00; + /* pre- QIC117 rev C spec. drive, QIC_CONFIG_80 bit is valid. + */ + *qic_std = (config & QIC_CONFIG_80) ? + QIC_TAPE_QIC80 : QIC_TAPE_QIC40; + /* ?? how's about 425ft tapes? */ + *tape_len = (config & QIC_CONFIG_LONG) ? 307 : 0; + *model = pre_qic117c; + result = 0; + } else { + ft_last_status.status.tape_status = (__u8)(status & 0xff); + *model = post_qic117b; + TRACE(ft_t_any, "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: + /* 205 or 425+ ft 550 Oe tape */ + *tape_len = 0; + break; + case QIC_TAPE_307FT: + /* 307.5 ft 550 Oe Extended Length (XL) tape */ + *tape_len = 307; + break; + case QIC_TAPE_VARIABLE: + /* Variable length 550 Oe tape */ + *tape_len = 0; + break; + case QIC_TAPE_1100FT: + /* 1100 ft 550 Oe tape */ + *tape_len = 1100; + break; + case QIC_TAPE_FLEX: + /* Variable length 900 Oe tape */ + *tape_len = 0; + break; + default: + *tape_len = -1; + break; + } + if (*qic_std == -1 || *tape_len == -1) { + TRACE(ft_t_any, + "post qic-117b spec drive with unknown tape"); + } + result = *tape_len == -1 ? -EIO : 0; + if (status & QIC_TAPE_WIDE) { + switch (*qic_std) { + case QIC_TAPE_QIC80: + TRACE(ft_t_info, "TR-1 tape detected"); + break; + case QIC_TAPE_QIC3010: + TRACE(ft_t_info, "TR-2 tape detected"); + break; + case QIC_TAPE_QIC3020: + TRACE(ft_t_info, "TR-3 tape detected"); + break; + default: + TRACE(ft_t_warn, + "Unknown Travan tape type detected"); + break; + } + } + } + TRACE_EXIT (result < 0) ? -EIO : 0; +} + +static int ftape_report_rom_version(int *version) +{ + + if (ftape_report_operation(version, QIC_REPORT_ROM_VERSION, 8) < 0) { + return -EIO; + } else { + return 0; + } +} + +void ftape_report_vendor_id(unsigned int *id) +{ + int result; + TRACE_FUN(ft_t_any); + + /* 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 { + TRACE(ft_t_noise, "got old 8 bit id: %04x", + *id); + *id |= 0x20000; + } + } else { + TRACE(ft_t_noise, "got 8 bit id: %04x", *id); + *id |= 0x10000; + } + } else { + TRACE(ft_t_noise, "got 16 bit id: %04x", *id); + } + if (*id == 0x0047) { + int version; + int sign; + + if (ftape_report_rom_version(&version) < 0) { + TRACE(ft_t_bug, "report rom version failed"); + TRACE_EXIT; + } + TRACE(ft_t_noise, "CMS rom version: %d", version); + ftape_command(QIC_ENTER_DIAGNOSTIC_1); + ftape_command(QIC_ENTER_DIAGNOSTIC_1); + diagnostic_mode = 1; + if (ftape_report_operation(&sign, 9, 8) < 0) { + unsigned int error; + qic117_cmd_t command; + + ftape_report_error(&error, &command, 1); + ftape_command(QIC_ENTER_PRIMARY_MODE); + diagnostic_mode = 0; + TRACE_EXIT; /* failure ! */ + } else { + TRACE(ft_t_noise, "CMS signature: %02x", sign); + } + if (sign == 0xa5) { + result = ftape_report_operation(&sign, 37, 8); + if (result < 0) { + if (version >= 63) { + *id = 0x8880; + TRACE(ft_t_noise, + "This is an Iomega drive !"); + } else { + *id = 0x0047; + TRACE(ft_t_noise, + "This is a real CMS drive !"); + } + } else { + *id = 0x0047; + TRACE(ft_t_noise, "CMS status: %d", sign); + } + } else { + *id = UNKNOWN_VENDOR; + } + ftape_command(QIC_ENTER_PRIMARY_MODE); + diagnostic_mode = 0; + } + TRACE_EXIT; +} + +static int qic_rate_code(unsigned int rate) +{ + switch (rate) { + case 250: + return QIC_CONFIG_RATE_250; + case 500: + return QIC_CONFIG_RATE_500; + case 1000: + return QIC_CONFIG_RATE_1000; + case 2000: + return QIC_CONFIG_RATE_2000; + default: + return QIC_CONFIG_RATE_500; + } +} + +static int ftape_set_rate_test(unsigned int *max_rate) +{ + unsigned int error; + qic117_cmd_t command; + int status; + int supported = 0; + TRACE_FUN(ft_t_any); + + /* 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. + */ + for (*max_rate = 2000; *max_rate >= 250; *max_rate /= 2) { + if (ftape_command(QIC_SELECT_RATE) < 0) { + continue; + } + if (ftape_parameter_wait(qic_rate_code(*max_rate), + 1 * FT_SECOND, &status) < 0) { + continue; + } + if (status & QIC_STATUS_ERROR) { + ftape_report_error(&error, &command, 0); + continue; + } + supported = 1; /* did accept a request */ + break; + } + TRACE(ft_t_noise, "Select Rate command is%s supported", + supported ? "" : " not"); + TRACE_EXIT supported; +} + +int ftape_set_data_rate(unsigned int new_rate /* Kbps */, unsigned int qic_std) +{ + int status; + int result = 0; + unsigned int data_rate = new_rate; + static int supported; + int rate_changed = 0; + qic_model dummy_model; + unsigned int dummy_qic_std, dummy_tape_len; + TRACE_FUN(ft_t_any); + + if (ft_drive_max_rate == 0) { /* first time */ + supported = ftape_set_rate_test(&ft_drive_max_rate); + } + if (supported) { + ftape_command(QIC_SELECT_RATE); + result = ftape_parameter_wait(qic_rate_code(new_rate), + 1 * FT_SECOND, &status); + if (result >= 0 && !(status & QIC_STATUS_ERROR)) { + rate_changed = 1; + } + } + TRACE_CATCH(result = ftape_report_configuration(&dummy_model, + &data_rate, + &dummy_qic_std, + &dummy_tape_len),); + if (data_rate != new_rate) { + if (!supported) { + TRACE(ft_t_warn, "Rate change not supported!"); + } else if (rate_changed) { + TRACE(ft_t_warn, "Requested: %d, got %d", + new_rate, data_rate); + } else { + TRACE(ft_t_warn, "Rate change failed!"); + } + result = -EINVAL; + } + /* + * 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 && data_rate > 500) || + (qic_std == QIC_TAPE_QIC80 && data_rate > 1000)) { + TRACE_ABORT(-EINVAL, + ft_t_warn, "Datarate too high for QIC-mode"); + } + TRACE_CATCH(fdc_set_data_rate(data_rate),_res = -EINVAL); + ft_data_rate = data_rate; + if (qic_std == QIC_TAPE_QIC40 || qic_std == QIC_TAPE_QIC80) { + switch (data_rate) { + case 250: + fdc_set_write_precomp(250); + break; + default: + case 500: + fdc_set_write_precomp(125); + break; + case 1000: + fdc_set_write_precomp(42); + break; + } + } else { + fdc_set_write_precomp(0); + } + TRACE_EXIT result; +} + +/* The next two functions are used to cope with excessive overrun errors + */ +int ftape_increase_threshold(void) +{ + TRACE_FUN(ft_t_flow); + + if (fdc.type < i82077 || ft_fdc_threshold >= 12) { + TRACE_ABORT(-EIO, ft_t_err, "cannot increase fifo threshold"); + } + if (fdc_fifo_threshold(++ft_fdc_threshold, NULL, NULL, NULL) < 0) { + TRACE(ft_t_err, "cannot increase fifo threshold"); + ft_fdc_threshold --; + fdc_reset(); + } + TRACE(ft_t_info, "New FIFO threshold: %d", ft_fdc_threshold); + TRACE_EXIT 0; +} + +int ftape_half_data_rate(void) +{ + if (ft_data_rate < 500) { + return -1; + } + if (ftape_set_data_rate(ft_data_rate / 2, ft_qic_std) < 0) { + return -EIO; + } + ftape_calc_timeouts(ft_qic_std, ft_data_rate, ftape_tape_len); + return 0; +} + +/* Seek the head to the specified track. + */ +int ftape_seek_head_to_track(unsigned int track) +{ + int status; + TRACE_FUN(ft_t_any); + + ft_location.track = -1; /* remains set in case of error */ + if (track >= ft_tracks_per_tape) { + TRACE_ABORT(-EINVAL, ft_t_bug, "track out of bounds"); + } + TRACE(ft_t_flow, "seeking track %d", track); + TRACE_CATCH(ftape_command(QIC_SEEK_HEAD_TO_TRACK),); + TRACE_CATCH(ftape_parameter_wait(track, ftape_timeout.head_seek, + &status),); + ft_location.track = track; + ftape_might_be_off_track = 0; + TRACE_EXIT 0; +} + +int ftape_wakeup_drive(wake_up_types method) +{ + int status; + int motor_on = 0; + TRACE_FUN(ft_t_any); + + switch (method) { + case wake_up_colorado: + TRACE_CATCH(ftape_command(QIC_PHANTOM_SELECT),); + TRACE_CATCH(ftape_parameter(0 /* ft_drive_sel ?? */),); + break; + case wake_up_mountain: + TRACE_CATCH(ftape_command(QIC_SOFT_SELECT),); + ftape_sleep(FT_MILLISECOND); /* NEEDED */ + TRACE_CATCH(ftape_parameter(18),); + break; + case wake_up_insight: + ftape_sleep(100 * FT_MILLISECOND); + motor_on = 1; + fdc_motor(motor_on); /* enable is done by motor-on */ + case no_wake_up: + break; + default: + TRACE_EXIT -ENODEV; /* unknown wakeup method */ + break; + } + /* If wakeup succeeded we shouldn't get an error here.. + */ + TRACE_CATCH(ftape_report_raw_drive_status(&status), + if (motor_on) { + fdc_motor(0); + }); + TRACE_EXIT 0; +} + +int ftape_put_drive_to_sleep(wake_up_types method) +{ + TRACE_FUN(ft_t_any); + + switch (method) { + case wake_up_colorado: + TRACE_CATCH(ftape_command(QIC_PHANTOM_DESELECT),); + break; + case wake_up_mountain: + TRACE_CATCH(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 ! */ + break; + default: + TRACE_EXIT -ENODEV; /* unknown wakeup method */ + } + TRACE_EXIT 0; +} + +int ftape_reset_drive(void) +{ + int result = 0; + int status; + unsigned int err_code; + qic117_cmd_t err_command; + int i; + TRACE_FUN(ft_t_any); + + /* 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(ft_t_flow, "Resetting fdc"); + fdc_reset(); + ftape_sleep(10 * FT_MILLISECOND); + TRACE(ft_t_flow, "Reset command to drive"); + result = ftape_command(QIC_RESET); + if (result == 0) { + ftape_sleep(1 * FT_SECOND); /* drive not + * accessible + * during 1 second + */ + TRACE(ft_t_flow, "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. + */ + if (ftape_wakeup_drive(ft_drive_type.wake_up) < 0) { + TRACE(ft_t_err, "Wakeup failed !"); + } + TRACE(ft_t_flow, "Waiting until drive gets ready"); + result= ftape_ready_wait(ftape_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; + } + } + FT_SIGNAL_EXIT(_DONT_BLOCK); + } + if (result != 0) { + TRACE(ft_t_err, "General failure to reset tape drive"); + } else { + /* Restore correct settings: keep original rate + */ + ftape_set_data_rate(ft_data_rate, ft_qic_std); + } + ftape_init_drive_needed = 1; + TRACE_EXIT result; +} diff --git a/drivers/char/ftape/lowlevel/ftape-io.h b/drivers/char/ftape/lowlevel/ftape-io.h new file mode 100644 index 000000000000..26a7baad8717 --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-io.h @@ -0,0 +1,90 @@ +#ifndef _FTAPE_IO_H +#define _FTAPE_IO_H + +/* + * Copyright (C) 1993-1996 Bas Laarhoven, + * (C) 1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-io.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:18:18 $ + * + * This file contains definitions for the glue part of the + * QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux. + */ + +#include <linux/qic117.h> +#include <linux/ftape-vendors.h> + +typedef struct { + unsigned int seek; + unsigned int reset; + unsigned int rewind; + unsigned int head_seek; + unsigned int stop; + unsigned int pause; +} ft_timeout_table; + +typedef enum { + prehistoric, pre_qic117c, post_qic117b, post_qic117d +} qic_model; + +/* + * ftape-io.c defined global vars. + */ +extern ft_timeout_table ftape_timeout; +extern unsigned int ftape_tape_len; +extern volatile qic117_cmd_t ftape_current_command; +extern const struct qic117_command_table qic117_cmds[]; +extern int ftape_might_be_off_track; + +/* + * ftape-io.c defined global functions. + */ +extern void ftape_udelay(unsigned int usecs); +extern void ftape_udelay_calibrate(void); +extern void ftape_sleep(unsigned int time); +extern void ftape_report_vendor_id(unsigned int *id); +extern int ftape_command(qic117_cmd_t command); +extern int ftape_command_wait(qic117_cmd_t command, + unsigned int timeout, + int *status); +extern int ftape_parameter(unsigned int parameter); +extern int ftape_report_operation(int *status, + qic117_cmd_t command, + int result_length); +extern int ftape_report_configuration(qic_model *model, + unsigned int *rate, + int *qic_std, + int *tape_len); +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_ready_wait(unsigned int timeout, int *status); +extern int ftape_seek_head_to_track(unsigned int track); +extern int ftape_set_data_rate(unsigned int new_rate, unsigned int qic_std); +extern int ftape_report_error(unsigned int *error, + qic117_cmd_t *command, + int report); +extern int ftape_reset_drive(void); +extern int ftape_put_drive_to_sleep(wake_up_types method); +extern int ftape_wakeup_drive(wake_up_types method); +extern int ftape_increase_threshold(void); +extern int ftape_half_data_rate(void); + +#endif diff --git a/drivers/char/ftape/lowlevel/ftape-proc.c b/drivers/char/ftape/lowlevel/ftape-proc.c new file mode 100644 index 000000000000..c66251e997ed --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-proc.c @@ -0,0 +1,215 @@ +/* + * Copyright (C) 1997 Claus-Justus Heine + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-proc.c,v $ + * $Revision: 1.11 $ + * $Date: 1997/10/24 14:47:37 $ + * + * This file contains the procfs interface for the + * QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux. + + * Old code removed, switched to dynamic proc entry. + */ + +#include <linux/config.h> + +#if defined(CONFIG_PROC_FS) && defined(CONFIG_FT_PROC_FS) + +#include <linux/proc_fs.h> + +#include <linux/ftape.h> +#include <linux/init.h> +#include <linux/qic117.h> + +#include "../lowlevel/ftape-io.h" +#include "../lowlevel/ftape-ctl.h" +#include "../lowlevel/ftape-proc.h" +#include "../lowlevel/ftape-tracing.h" + +static size_t get_driver_info(char *buf) +{ + const char *debug_level[] = { "bugs" , + "errors", + "warnings", + "informational", + "noisy", + "program flow", + "fdc and dma", + "data flow", + "anything" }; + + return sprintf(buf, + "version : %s\n" + "used data rate: %d kbit/sec\n" + "dma memory : %d kb\n" + "debug messages: %s\n", + FTAPE_VERSION, + ft_data_rate, + FT_BUFF_SIZE * ft_nr_buffers >> 10, + debug_level[TRACE_LEVEL]); +} + +static size_t get_tapedrive_info(char *buf) +{ + return sprintf(buf, + "vendor id : 0x%04x\n" + "drive name: %s\n" + "wind speed: %d ips\n" + "wakeup : %s\n" + "max. rate : %d kbit/sec\n", + ft_drive_type.vendor_id, + ft_drive_type.name, + ft_drive_type.speed, + ((ft_drive_type.wake_up == no_wake_up) + ? "No wakeup needed" : + ((ft_drive_type.wake_up == wake_up_colorado) + ? "Colorado" : + ((ft_drive_type.wake_up == wake_up_mountain) + ? "Mountain" : + ((ft_drive_type.wake_up == wake_up_insight) + ? "Motor on" : + "Unknown")))), + ft_drive_max_rate); +} + +static size_t get_cartridge_info(char *buf) +{ + if (ftape_init_drive_needed) { + return sprintf(buf, "uninitialized\n"); + } + if (ft_no_tape) { + return sprintf(buf, "no cartridge inserted\n"); + } + return sprintf(buf, + "segments : %5d\n" + "tracks : %5d\n" + "length : %5dft\n" + "formatted : %3s\n" + "writable : %3s\n" + "QIC spec. : QIC-%s\n" + "fmt-code : %1d\n", + ft_segments_per_track, + ft_tracks_per_tape, + ftape_tape_len, + (ft_formatted == 1) ? "yes" : "no", + (ft_write_protected == 1) ? "no" : "yes", + ((ft_qic_std == QIC_TAPE_QIC40) ? "40" : + ((ft_qic_std == QIC_TAPE_QIC80) ? "80" : + ((ft_qic_std == QIC_TAPE_QIC3010) ? "3010" : + ((ft_qic_std == QIC_TAPE_QIC3020) ? "3020" : + "???")))), + ft_format_code); +} + +static size_t get_controller_info(char *buf) +{ + const char *fdc_name[] = { "no fdc", + "i8272", + "i82077", + "i82077AA", + "Colorado FC-10 or FC-20", + "i82078", + "i82078_1" }; + + return sprintf(buf, + "FDC type : %s\n" + "FDC base : 0x%03x\n" + "FDC irq : %d\n" + "FDC dma : %d\n" + "FDC thr. : %d\n" + "max. rate : %d kbit/sec\n", + ft_mach2 ? "Mountain MACH-2" : fdc_name[fdc.type], + fdc.sra, fdc.irq, fdc.dma, + ft_fdc_threshold, ft_fdc_max_rate); +} + +static size_t get_history_info(char *buf) +{ + size_t len; + + len = sprintf(buf, + "\nFDC isr statistics\n" + " id_am_errors : %3d\n" + " id_crc_errors : %3d\n" + " data_am_errors : %3d\n" + " data_crc_errors : %3d\n" + " overrun_errors : %3d\n" + " no_data_errors : %3d\n" + " retries : %3d\n", + ft_history.id_am_errors, ft_history.id_crc_errors, + ft_history.data_am_errors, ft_history.data_crc_errors, + ft_history.overrun_errors, ft_history.no_data_errors, + ft_history.retries); + len += sprintf(buf + len, + "\nECC statistics\n" + " crc_errors : %3d\n" + " crc_failures : %3d\n" + " ecc_failures : %3d\n" + " sectors corrected: %3d\n", + ft_history.crc_errors, ft_history.crc_failures, + ft_history.ecc_failures, ft_history.corrected); + len += sprintf(buf + len, + "\ntape quality statistics\n" + " media defects : %3d\n", + ft_history.defects); + len += sprintf(buf + len, + "\ntape motion statistics\n" + " repositions : %3d\n", + ft_history.rewinds); + return len; +} + +static int ftape_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + char *ptr = page; + size_t len; + + ptr += sprintf(ptr, "Kernel Driver\n\n"); + ptr += get_driver_info(ptr); + ptr += sprintf(ptr, "\nTape Drive\n\n"); + ptr += get_tapedrive_info(ptr); + ptr += sprintf(ptr, "\nFDC Controller\n\n"); + ptr += get_controller_info(ptr); + ptr += sprintf(ptr, "\nTape Cartridge\n\n"); + ptr += get_cartridge_info(ptr); + ptr += sprintf(ptr, "\nHistory Record\n\n"); + ptr += get_history_info(ptr); + + len = strlen(page); + *start = NULL; + if (off+count >= len) { + *eof = 1; + } else { + *eof = 0; + } + return len; +} + +int __init ftape_proc_init(void) +{ + return create_proc_read_entry("ftape", 0, &proc_root, + ftape_read_proc, NULL) != NULL; +} + +void ftape_proc_destroy(void) +{ + remove_proc_entry("ftape", &proc_root); +} + +#endif /* defined(CONFIG_PROC_FS) && defined(CONFIG_FT_PROC_FS) */ diff --git a/drivers/char/ftape/lowlevel/ftape-proc.h b/drivers/char/ftape/lowlevel/ftape-proc.h new file mode 100644 index 000000000000..264dfcc1d22d --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-proc.h @@ -0,0 +1,35 @@ +#ifndef _FTAPE_PROC_H +#define _FTAPE_PROC_H + +/* + * Copyright (C) 1997 Claus-Justus Heine + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-proc.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:18:20 $ + * + * This file contains definitions for the procfs interface of the + * QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux. + */ + +#include <linux/proc_fs.h> + +extern int ftape_proc_init(void); +extern void ftape_proc_destroy(void); + +#endif diff --git a/drivers/char/ftape/lowlevel/ftape-read.c b/drivers/char/ftape/lowlevel/ftape-read.c new file mode 100644 index 000000000000..d967d8cd86dc --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-read.c @@ -0,0 +1,621 @@ +/* + * Copyright (C) 1993-1996 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-read.c,v $ + * $Revision: 1.6 $ + * $Date: 1997/10/21 14:39:22 $ + * + * This file contains the reading code + * for the QIC-117 floppy-tape driver for Linux. + * + */ + +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/mm.h> + +#include <linux/ftape.h> +#include <linux/qic117.h> +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/ftape-read.h" +#include "../lowlevel/ftape-io.h" +#include "../lowlevel/ftape-ctl.h" +#include "../lowlevel/ftape-rw.h" +#include "../lowlevel/ftape-write.h" +#include "../lowlevel/ftape-ecc.h" +#include "../lowlevel/ftape-bsm.h" + +/* Global vars. + */ + +/* Local vars. + */ + +void ftape_zap_read_buffers(void) +{ + int i; + + for (i = 0; i < ft_nr_buffers; ++i) { +/* changed to "fit" with dynamic allocation of tape_buffer. --khp */ + ft_buffer[i]->status = waiting; + ft_buffer[i]->bytes = 0; + ft_buffer[i]->skip = 0; + ft_buffer[i]->retry = 0; + } +/* ftape_reset_buffer(); */ +} + +static SectorMap convert_sector_map(buffer_struct * buff) +{ + int i = 0; + SectorMap bad_map = ftape_get_bad_sector_entry(buff->segment_id); + SectorMap src_map = buff->soft_error_map | buff->hard_error_map; + SectorMap dst_map = 0; + TRACE_FUN(ft_t_any); + + if (bad_map || src_map) { + TRACE(ft_t_flow, "bad_map = 0x%08lx", (long) bad_map); + TRACE(ft_t_flow, "src_map = 0x%08lx", (long) 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) { + TRACE(ft_t_flow, "dst_map = 0x%08lx", (long) dst_map); + } + TRACE_EXIT dst_map; +} + +static int correct_and_copy_fraction(buffer_struct *buff, __u8 * destination, + int start, int size) +{ + struct memory_segment mseg; + int result; + SectorMap read_bad; + TRACE_FUN(ft_t_any); + + mseg.read_bad = convert_sector_map(buff); + mseg.marked_bad = 0; /* not used... */ + mseg.blocks = buff->bytes / FT_SECTOR_SIZE; + mseg.data = buff->address; + /* If there are no data sectors we can skip this segment. + */ + if (mseg.blocks <= 3) { + TRACE_ABORT(0, ft_t_noise, "empty segment"); + } + read_bad = mseg.read_bad; + ft_history.crc_errors += count_ones(read_bad); + result = ftape_ecc_correct_data(&mseg); + if (read_bad != 0 || mseg.corrected != 0) { + TRACE(ft_t_noise, "crc error map: 0x%08lx", (unsigned long)read_bad); + TRACE(ft_t_noise, "corrected map: 0x%08lx", (unsigned long)mseg.corrected); + ft_history.corrected += count_ones(mseg.corrected); + } + if (result == ECC_CORRECTED || result == ECC_OK) { + if (result == ECC_CORRECTED) { + TRACE(ft_t_info, "ecc corrected segment: %d", buff->segment_id); + } + if(start < 0) { + start= 0; + } + if((start+size) > ((mseg.blocks - 3) * FT_SECTOR_SIZE)) { + size = (mseg.blocks - 3) * FT_SECTOR_SIZE - start; + } + if (size < 0) { + size= 0; + } + if(size > 0) { + memcpy(destination + start, mseg.data + start, size); + } + if ((read_bad ^ mseg.corrected) & mseg.corrected) { + /* sectors corrected without crc errors set */ + ft_history.crc_failures++; + } + TRACE_EXIT size; /* (mseg.blocks - 3) * FT_SECTOR_SIZE; */ + } else { + ft_history.ecc_failures++; + TRACE_ABORT(-EAGAIN, + ft_t_err, "ecc failure on segment %d", + buff->segment_id); + } + TRACE_EXIT 0; +} + +/* Read given segment into buffer at address. + */ +int ftape_read_segment_fraction(const int segment_id, + void *address, + const ft_read_mode_t read_mode, + const int start, + const int size) +{ + int result = 0; + int retry = 0; + int bytes_read = 0; + int read_done = 0; + TRACE_FUN(ft_t_flow); + + ft_history.used |= 1; + TRACE(ft_t_data_flow, "segment_id = %d", segment_id); + if (ft_driver_state != reading) { + TRACE(ft_t_noise, "calling ftape_abort_operation"); + TRACE_CATCH(ftape_abort_operation(),); + ftape_set_state(reading); + } + for(;;) { + buffer_struct *tail; + /* Allow escape from this loop on signal ! + */ + FT_SIGNAL_EXIT(_DONT_BLOCK); + /* Search all full buffers for the first matching the + * wanted segment. Clear other buffers on the fly. + */ + tail = ftape_get_buffer(ft_queue_tail); + while (!read_done && tail->status == done) { + /* Allow escape from this loop on signal ! + */ + FT_SIGNAL_EXIT(_DONT_BLOCK); + if (tail->segment_id == segment_id) { + /* If out buffer is already full, + * return its contents. + */ + TRACE(ft_t_flow, "found segment in cache: %d", + segment_id); + if (tail->deleted) { + /* 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. + */ + TRACE_EXIT 0; + } + result = correct_and_copy_fraction( + tail, + address, + start, + size); + TRACE(ft_t_flow, "segment contains (bytes): %d", + result); + if (result < 0) { + if (result != -EAGAIN) { + TRACE_EXIT result; + } + /* keep read_done == 0, will + * trigger + * ftape_abort_operation + * because reading wrong + * segment. + */ + TRACE(ft_t_err, "ecc failed, retry"); + ++retry; + } else { + read_done = 1; + bytes_read = result; + } + } else { + TRACE(ft_t_flow,"zapping segment in cache: %d", + tail->segment_id); + } + tail->status = waiting; + tail = ftape_next_buffer(ft_queue_tail); + } + if (!read_done && tail->status == reading) { + if (tail->segment_id == segment_id) { + switch(ftape_wait_segment(reading)) { + case 0: + break; + case -EINTR: + TRACE_ABORT(-EINTR, ft_t_warn, + "interrupted by " + "non-blockable signal"); + break; + default: + TRACE(ft_t_noise, + "wait_segment failed"); + ftape_abort_operation(); + ftape_set_state(reading); + break; + } + } else { + /* We're reading the wrong segment, + * stop runner. + */ + TRACE(ft_t_noise, "reading wrong segment"); + ftape_abort_operation(); + ftape_set_state(reading); + } + } + /* should runner stop ? + */ + if (ft_runner_status == aborting) { + buffer_struct *head = ftape_get_buffer(ft_queue_head); + switch(head->status) { + case error: + ft_history.defects += + count_ones(head->hard_error_map); + case reading: + head->status = waiting; + break; + default: + break; + } + TRACE_CATCH(ftape_dumb_stop(),); + } else { + /* If just passed last segment on tape: wait + * for BOT or EOT mark. Sets ft_runner_status to + * idle if at lEOT and successful + */ + TRACE_CATCH(ftape_handle_logical_eot(),); + } + /* If we got a segment: quit, or else retry up to limit. + * + * If segment to read is empty, do not start runner for it, + * but wait for next read call. + */ + if (read_done || + ftape_get_bad_sector_entry(segment_id) == EMPTY_SEGMENT ) { + /* bytes_read = 0; should still be zero */ + TRACE_EXIT bytes_read; + + } + if (retry > FT_RETRIES_ON_ECC_ERROR) { + ft_history.defects++; + TRACE_ABORT(-ENODATA, ft_t_err, + "too many retries on ecc failure"); + } + /* Now at least one buffer is empty ! + * Restart runner & tape if needed. + */ + TRACE(ft_t_any, "head: %d, tail: %d, ft_runner_status: %d", + ftape_buffer_id(ft_queue_head), + ftape_buffer_id(ft_queue_tail), + ft_runner_status); + TRACE(ft_t_any, "buffer[].status, [head]: %d, [tail]: %d", + ftape_get_buffer(ft_queue_head)->status, + ftape_get_buffer(ft_queue_tail)->status); + tail = ftape_get_buffer(ft_queue_tail); + if (tail->status == waiting) { + buffer_struct *head = ftape_get_buffer(ft_queue_head); + + ftape_setup_new_segment(head, segment_id, -1); + if (read_mode == FT_RD_SINGLE) { + /* disable read-ahead */ + head->next_segment = 0; + } + ftape_calc_next_cluster(head); + if (ft_runner_status == idle) { + result = ftape_start_tape(segment_id, + head->sector_offset); + if (result < 0) { + TRACE_ABORT(result, ft_t_err, "Error: " + "segment %d unreachable", + segment_id); + } + } + head->status = reading; + fdc_setup_read_write(head, FDC_READ); + } + } + /* not reached */ + TRACE_EXIT -EIO; +} + +int ftape_read_header_segment(__u8 *address) +{ + int result; + int header_segment; + int first_failed = 0; + int status; + TRACE_FUN(ft_t_flow); + + ft_used_header_segment = -1; + TRACE_CATCH(ftape_report_drive_status(&status),); + TRACE(ft_t_flow, "reading..."); + /* 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. + */ + memset(address, '\0', (FT_SECTORS_PER_SEGMENT - 3) * FT_SECTOR_SIZE); + result = 0; +#define HEADER_SEGMENT_BOUNDARY 68 /* why not 42? */ + for (header_segment = 0; + header_segment < HEADER_SEGMENT_BOUNDARY && result == 0; + ++header_segment) { + /* Set no read-ahead, the isr will force read-ahead whenever + * it encounters deleted data ! + */ + result = ftape_read_segment(header_segment, + address, + FT_RD_SINGLE); + if (result < 0 && !first_failed) { + TRACE(ft_t_err, "header segment damaged, trying backup"); + first_failed = 1; + result = 0; /* force read of next (backup) segment */ + } + } + if (result < 0 || header_segment >= HEADER_SEGMENT_BOUNDARY) { + TRACE_ABORT(-EIO, ft_t_err, + "no readable header segment found"); + } + TRACE_CATCH(ftape_abort_operation(),); + ft_used_header_segment = header_segment; + result = ftape_decode_header_segment(address); + TRACE_EXIT result; +} + +int ftape_decode_header_segment(__u8 *address) +{ + unsigned int max_floppy_side; + unsigned int max_floppy_track; + unsigned int max_floppy_sector; + unsigned int new_tape_len; + TRACE_FUN(ft_t_flow); + + if (GET4(address, FT_SIGNATURE) == FT_D2G_MAGIC) { + /* Ditto 2GB header segment. They encrypt the bad sector map. + * We decrypt it and store them in normal format. + * I hope this is correct. + */ + int i; + TRACE(ft_t_warn, + "Found Ditto 2GB tape, " + "trying to decrypt bad sector map"); + for (i=256; i < 29 * FT_SECTOR_SIZE; i++) { + address[i] = ~(address[i] - (i&0xff)); + } + PUT4(address, 0,FT_HSEG_MAGIC); + } else if (GET4(address, FT_SIGNATURE) != FT_HSEG_MAGIC) { + TRACE_ABORT(-EIO, ft_t_err, + "wrong signature in header segment"); + } + ft_format_code = (ft_format_type) address[FT_FMT_CODE]; + if (ft_format_code != fmt_big) { + ft_header_segment_1 = GET2(address, FT_HSEG_1); + ft_header_segment_2 = GET2(address, FT_HSEG_2); + ft_first_data_segment = GET2(address, FT_FRST_SEG); + ft_last_data_segment = GET2(address, FT_LAST_SEG); + } else { + ft_header_segment_1 = GET4(address, FT_6_HSEG_1); + ft_header_segment_2 = GET4(address, FT_6_HSEG_2); + ft_first_data_segment = GET4(address, FT_6_FRST_SEG); + ft_last_data_segment = GET4(address, FT_6_LAST_SEG); + } + TRACE(ft_t_noise, "first data segment: %d", ft_first_data_segment); + TRACE(ft_t_noise, "last data segment: %d", ft_last_data_segment); + TRACE(ft_t_noise, "header segments are %d and %d", + ft_header_segment_1, ft_header_segment_2); + + /* 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) / + * ftape_segments_per_head + * max-floppy-track ftape_segments_per_head / + * ftape_segments_per_cylinder - 1 + * max-floppy-sector ftape_segments_per_cylinder * + * FT_SECTORS_PER_SEGMENT + */ + ft_segments_per_track = GET2(address, FT_SPT); + ft_tracks_per_tape = address[FT_TPC]; + max_floppy_side = address[FT_FHM]; + max_floppy_track = address[FT_FTM]; + max_floppy_sector = address[FT_FSM]; + TRACE(ft_t_noise, "(fmt/spt/tpc/fhm/ftm/fsm) = %d/%d/%d/%d/%d/%d", + ft_format_code, ft_segments_per_track, ft_tracks_per_tape, + max_floppy_side, max_floppy_track, max_floppy_sector); + new_tape_len = ftape_tape_len; + switch (ft_format_code) { + case fmt_425ft: + new_tape_len = 425; + break; + case fmt_normal: + if (ftape_tape_len == 0) { /* otherwise 307 ft */ + new_tape_len = 205; + } + break; + case fmt_1100ft: + new_tape_len = 1100; + break; + case fmt_var:{ + int segments_per_1000_inch = 1; /* non-zero default for switch */ + switch (ft_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 * ft_segments_per_track + + (segments_per_1000_inch - 1)) / segments_per_1000_inch; + break; + } + case fmt_big:{ + int segments_per_1000_inch = 1; /* non-zero default for switch */ + switch (ft_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; + default: + TRACE_ABORT(-EIO, ft_t_bug, + "%x QIC-standard with fmt-code %d, please report", + ft_qic_std, ft_format_code); + } + new_tape_len = ((1000 * ft_segments_per_track + + (segments_per_1000_inch - 1)) / + segments_per_1000_inch); + break; + } + default: + TRACE_ABORT(-EIO, ft_t_err, + "unknown tape format, please report !"); + } + if (new_tape_len != ftape_tape_len) { + ftape_tape_len = new_tape_len; + TRACE(ft_t_info, "calculated tape length is %d ft", + ftape_tape_len); + ftape_calc_timeouts(ft_qic_std, ft_data_rate, ftape_tape_len); + } + if (ft_segments_per_track == 0 && ft_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. + */ + ft_segments_per_track = 68; + ft_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 (ft_segments_per_track == 150 && + ft_tracks_per_tape == 28 && + max_floppy_side == 7 && + max_floppy_track == 149 && + max_floppy_sector == 128) { +TRACE(ft_t_info, "the famous CONNER bug: max_floppy_side off by one !"); + max_floppy_side = 6; + } + /* These tests will compensate for the wrong parameter on tapes + * formatted by ComByte Windows software. + * + * First, for 205 foot tapes + */ + if (ft_segments_per_track == 100 && + ft_tracks_per_tape == 28 && + max_floppy_side == 9 && + max_floppy_track == 149 && + max_floppy_sector == 128) { +TRACE(ft_t_info, "the ComByte bug: max_floppy_side incorrect!"); + max_floppy_side = 4; + } + /* Next, for 307 foot tapes. */ + if (ft_segments_per_track == 150 && + ft_tracks_per_tape == 28 && + max_floppy_side == 9 && + max_floppy_track == 149 && + max_floppy_sector == 128) { +TRACE(ft_t_info, "the ComByte bug: max_floppy_side incorrect!"); + max_floppy_side = 6; + } + /* This test will compensate for the wrong parameter on tapes + * formatted by Colorado Windows software. + */ + if (ft_segments_per_track == 150 && + ft_tracks_per_tape == 28 && + max_floppy_side == 6 && + max_floppy_track == 150 && + max_floppy_sector == 128) { +TRACE(ft_t_info, "the famous Colorado bug: max_floppy_track off by one !"); + max_floppy_track = 149; + } + ftape_segments_per_head = ((max_floppy_sector/FT_SECTORS_PER_SEGMENT) * + (max_floppy_track + 1)); + /* This test will compensate for some bug reported by Dima + * Brodsky. Seems to be a Colorado bug, either. (freebee + * Imation tape shipped together with Colorado T3000 + */ + if ((ft_format_code == fmt_var || ft_format_code == fmt_big) && + ft_tracks_per_tape == 50 && + max_floppy_side == 54 && + max_floppy_track == 255 && + max_floppy_sector == 128) { +TRACE(ft_t_info, "the famous ??? bug: max_floppy_track off by one !"); + max_floppy_track = 254; + } + /* + * Verify drive_configuration with tape parameters + */ + if (ftape_segments_per_head == 0 || ftape_segments_per_cylinder == 0 || + ((ft_segments_per_track * ft_tracks_per_tape - 1) / ftape_segments_per_head + != max_floppy_side) || + (ftape_segments_per_head / ftape_segments_per_cylinder - 1 != max_floppy_track) || + (ftape_segments_per_cylinder * FT_SECTORS_PER_SEGMENT != max_floppy_sector) +#ifdef TESTING + || ((ft_format_code == fmt_var || ft_format_code == fmt_big) && + (max_floppy_track != 254 || max_floppy_sector != 128)) +#endif + ) { + char segperheadz = ftape_segments_per_head ? ' ' : '?'; + char segpercylz = ftape_segments_per_cylinder ? ' ' : '?'; + TRACE(ft_t_err,"Tape parameters inconsistency, please report"); + TRACE(ft_t_err, "reported = %d/%d/%d/%d/%d/%d", + ft_format_code, + ft_segments_per_track, + ft_tracks_per_tape, + max_floppy_side, + max_floppy_track, + max_floppy_sector); + TRACE(ft_t_err, "required = %d/%d/%d/%d%c/%d%c/%d", + ft_format_code, + ft_segments_per_track, + ft_tracks_per_tape, + ftape_segments_per_head ? + ((ft_segments_per_track * ft_tracks_per_tape -1) / + ftape_segments_per_head ) : + (ft_segments_per_track * ft_tracks_per_tape -1), + segperheadz, + ftape_segments_per_cylinder ? + (ftape_segments_per_head / + ftape_segments_per_cylinder - 1 ) : + ftape_segments_per_head - 1, + segpercylz, + (ftape_segments_per_cylinder * FT_SECTORS_PER_SEGMENT)); + TRACE_EXIT -EIO; + } + ftape_extract_bad_sector_map(address); + TRACE_EXIT 0; +} diff --git a/drivers/char/ftape/lowlevel/ftape-read.h b/drivers/char/ftape/lowlevel/ftape-read.h new file mode 100644 index 000000000000..069f99f2a984 --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-read.h @@ -0,0 +1,51 @@ +#ifndef _FTAPE_READ_H +#define _FTAPE_READ_H + +/* + * Copyright (C) 1994-1996 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-read.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:18:22 $ + * + * This file contains the definitions for the read functions + * for the QIC-117 floppy-tape driver for Linux. + * + */ + +/* ftape-read.c defined global functions. + */ +typedef enum { + FT_RD_SINGLE = 0, + FT_RD_AHEAD = 1, +} ft_read_mode_t; + +extern int ftape_read_header_segment(__u8 *address); +extern int ftape_decode_header_segment(__u8 *address); +extern int ftape_read_segment_fraction(const int segment, + void *address, + const ft_read_mode_t read_mode, + const int start, + const int size); +#define ftape_read_segment(segment, address, read_mode) \ + ftape_read_segment_fraction(segment, address, read_mode, \ + 0, FT_SEGMENT_SIZE) +extern void ftape_zap_read_buffers(void); + +#endif /* _FTAPE_READ_H */ diff --git a/drivers/char/ftape/lowlevel/ftape-rw.c b/drivers/char/ftape/lowlevel/ftape-rw.c new file mode 100644 index 000000000000..c0d6dc2cbfd3 --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-rw.c @@ -0,0 +1,1092 @@ +/* + * Copyright (C) 1993-1996 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-rw.c,v $ + * $Revision: 1.7 $ + * $Date: 1997/10/28 14:26:49 $ + * + * 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/string.h> +#include <linux/errno.h> + +#include <linux/ftape.h> +#include <linux/qic117.h> +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/ftape-rw.h" +#include "../lowlevel/fdc-io.h" +#include "../lowlevel/ftape-init.h" +#include "../lowlevel/ftape-io.h" +#include "../lowlevel/ftape-ctl.h" +#include "../lowlevel/ftape-read.h" +#include "../lowlevel/ftape-ecc.h" +#include "../lowlevel/ftape-bsm.h" + +/* Global vars. + */ +int ft_nr_buffers; +buffer_struct *ft_buffer[FT_MAX_NR_BUFFERS]; +static volatile int ft_head; +static volatile int ft_tail; /* not volatile but need same type as head */ +int fdc_setup_error; +location_record ft_location = {-1, 0}; +volatile int ftape_tape_running; + +/* Local vars. + */ +static int overrun_count_offset; +static int inhibit_correction; + +/* maxmimal allowed overshoot when fast seeking + */ +#define OVERSHOOT_LIMIT 10 + +/* Increment cyclic buffer nr. + */ +buffer_struct *ftape_next_buffer(ft_buffer_queue_t pos) +{ + switch (pos) { + case ft_queue_head: + if (++ft_head >= ft_nr_buffers) { + ft_head = 0; + } + return ft_buffer[ft_head]; + case ft_queue_tail: + if (++ft_tail >= ft_nr_buffers) { + ft_tail = 0; + } + return ft_buffer[ft_tail]; + default: + return NULL; + } +} +int ftape_buffer_id(ft_buffer_queue_t pos) +{ + switch(pos) { + case ft_queue_head: return ft_head; + case ft_queue_tail: return ft_tail; + default: return -1; + } +} +buffer_struct *ftape_get_buffer(ft_buffer_queue_t pos) +{ + switch(pos) { + case ft_queue_head: return ft_buffer[ft_head]; + case ft_queue_tail: return ft_buffer[ft_tail]; + default: return NULL; + } +} +void ftape_reset_buffer(void) +{ + ft_head = ft_tail = 0; +} + +buffer_state_enum ftape_set_state(buffer_state_enum new_state) +{ + buffer_state_enum old_state = ft_driver_state; + + ft_driver_state = new_state; + return old_state; +} +/* 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, + int segment_id, + unsigned int sector_offset, + unsigned int sector_count, + int retry) +{ + SectorMap offset_mask; + SectorMap mask; + TRACE_FUN(ft_t_any); + + buff->segment_id = segment_id; + buff->sector_offset = sector_offset; + buff->remaining = sector_count; + buff->head = segment_id / ftape_segments_per_head; + buff->cyl = (segment_id % ftape_segments_per_head) / ftape_segments_per_cylinder; + buff->sect = (segment_id % ftape_segments_per_cylinder) * FT_SECTORS_PER_SEGMENT + 1; + buff->deleted = 0; + offset_mask = (1 << buff->sector_offset) - 1; + mask = ftape_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 * FT_SECTOR_SIZE; + TRACE(ft_t_flow, "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 = ftape_get_bad_sector_entry(buff->segment_id); + if (buff->bad_sector_map != 0) { + TRACE(ft_t_noise, "segment: %d, bad sector map: %08lx", + buff->segment_id, (long)buff->bad_sector_map); + } else { + TRACE(ft_t_flow, "segment: %d", buff->segment_id); + } + if (buff->sector_offset > 0) { + buff->bad_sector_map >>= buff->sector_offset; + } + if (buff->sector_offset != 0 || buff->remaining != FT_SECTORS_PER_SEGMENT) { + TRACE(ft_t_flow, "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(ft_t_flow, "empty segment %d, fake first sector good", + buff->segment_id); + if (buff->ptr != buff->address) { + TRACE(ft_t_bug, "This is a bug: %p/%p", + buff->ptr, buff->address); + } + buff->bad_sector_map = FAKE_SEGMENT; + } + fdc_setup_error = 0; + buff->next_segment = segment_id + 1; + TRACE_EXIT 0; +} + +/* Calculate Floppy Disk Controller and DMA parameters for a new segment. + */ +int ftape_setup_new_segment(buffer_struct * buff, int segment_id, int skip) +{ + int result = 0; + static int old_segment_id = -1; + static buffer_state_enum old_ft_driver_state = idle; + int retry = 0; + unsigned offset = 0; + int count = FT_SECTORS_PER_SEGMENT; + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_flow, "%s segment %d (old = %d)", + (ft_driver_state == reading || ft_driver_state == verifying) + ? "reading" : "writing", + segment_id, old_segment_id); + if (ft_driver_state != old_ft_driver_state) { /* when verifying */ + old_segment_id = -1; + old_ft_driver_state = ft_driver_state; + } + if (segment_id == old_segment_id) { + ++buff->retry; + ++ft_history.retries; + TRACE(ft_t_flow, "setting up for retry nr %d", buff->retry); + retry = 1; + if (skip && buff->skip > 0) { /* allow skip on retry */ + offset = buff->skip; + count -= offset; + TRACE(ft_t_flow, "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 result; +} + +/* Determine size of next cluster of good sectors. + */ +int ftape_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 { + SectorMap 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; +} + +/* if just passed the last segment on a track, wait for BOT + * or EOT mark. + */ +int ftape_handle_logical_eot(void) +{ + TRACE_FUN(ft_t_flow); + + if (ft_runner_status == logical_eot) { + int status; + + TRACE(ft_t_noise, "tape at logical EOT"); + TRACE_CATCH(ftape_ready_wait(ftape_timeout.seek, &status),); + if ((status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT)) == 0) { + TRACE_ABORT(-EIO, ft_t_err, "eot/bot not reached"); + } + ft_runner_status = end_of_tape; + } + if (ft_runner_status == end_of_tape) { + TRACE(ft_t_noise, "runner stopped because of logical EOT"); + ft_runner_status = idle; + } + TRACE_EXIT 0; +} + +static int check_bot_eot(int status) +{ + TRACE_FUN(ft_t_flow); + + if (status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT)) { + ft_location.bot = ((ft_location.track & 1) == 0 ? + (status & QIC_STATUS_AT_BOT) != 0: + (status & QIC_STATUS_AT_EOT) != 0); + ft_location.eot = !ft_location.bot; + ft_location.segment = (ft_location.track + + (ft_location.bot ? 0 : 1)) * ft_segments_per_track - 1; + ft_location.sector = -1; + ft_location.known = 1; + TRACE(ft_t_flow, "tape at logical %s", + ft_location.bot ? "bot" : "eot"); + TRACE(ft_t_flow, "segment = %d", ft_location.segment); + } else { + ft_location.known = 0; + } + TRACE_EXIT ft_location.known; +} + +/* Read Id of first sector passing tape head. + */ +static int ftape_read_id(void) +{ + int status; + __u8 out[2]; + TRACE_FUN(ft_t_any); + + /* Assume tape is running on entry, be able to handle + * situation where it stopped or is stopping. + */ + ft_location.known = 0; /* default is location not known */ + out[0] = FDC_READID; + out[1] = ft_drive_sel; + TRACE_CATCH(fdc_command(out, 2),); + switch (fdc_interrupt_wait(20 * FT_SECOND)) { + case 0: + if (fdc_sect == 0) { + if (ftape_report_drive_status(&status) >= 0 && + (status & QIC_STATUS_READY)) { + ftape_tape_running = 0; + TRACE(ft_t_flow, "tape has stopped"); + check_bot_eot(status); + } + } else { + ft_location.known = 1; + ft_location.segment = (ftape_segments_per_head + * fdc_head + + ftape_segments_per_cylinder + * fdc_cyl + + (fdc_sect - 1) + / FT_SECTORS_PER_SEGMENT); + ft_location.sector = ((fdc_sect - 1) + % FT_SECTORS_PER_SEGMENT); + ft_location.eot = ft_location.bot = 0; + } + break; + case -ETIME: + /* Didn't find id on tape, must be near end: Wait + * until stopped. + */ + if (ftape_ready_wait(FT_FOREVER, &status) >= 0) { + ftape_tape_running = 0; + TRACE(ft_t_flow, "tape has stopped"); + check_bot_eot(status); + } + break; + default: + /* Interrupted or otherwise failing + * fdc_interrupt_wait() + */ + TRACE(ft_t_err, "fdc_interrupt_wait failed"); + break; + } + if (!ft_location.known) { + TRACE_ABORT(-EIO, ft_t_flow, "no id found"); + } + if (ft_location.sector == 0) { + TRACE(ft_t_flow, "passing segment %d/%d", + ft_location.segment, ft_location.sector); + } else { + TRACE(ft_t_fdc_dma, "passing segment %d/%d", + ft_location.segment, ft_location.sector); + } + TRACE_EXIT 0; +} + +static int logical_forward(void) +{ + ftape_tape_running = 1; + return ftape_command(QIC_LOGICAL_FORWARD); +} + +int ftape_stop_tape(int *pstatus) +{ + int retry = 0; + int result; + TRACE_FUN(ft_t_flow); + + do { + result = ftape_command_wait(QIC_STOP_TAPE, + ftape_timeout.stop, pstatus); + if (result == 0) { + if ((*pstatus & QIC_STATUS_READY) == 0) { + result = -EIO; + } else { + ftape_tape_running = 0; + } + } + } while (result < 0 && ++retry <= 3); + if (result < 0) { + TRACE(ft_t_err, "failed ! (fatal)"); + } + TRACE_EXIT result; +} + +int ftape_dumb_stop(void) +{ + int result; + int status; + TRACE_FUN(ft_t_flow); + + /* Abort current fdc operation if it's busy (probably read + * or write operation pending) with a reset. + */ + if (fdc_ready_wait(100 /* usec */) < 0) { + TRACE(ft_t_noise, "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); + ft_location.known = 0; + do { + if (result == 0 && status & QIC_STATUS_READY) { + /* Tape is not running any more. + */ + TRACE(ft_t_noise, "tape already halted"); + check_bot_eot(status); + ftape_tape_running = 0; + } else if (ftape_tape_running) { + /* Tape is (was) still moving. + */ +#ifdef TESTING + ftape_read_id(); +#endif + result = ftape_stop_tape(&status); + } else { + /* Tape not yet ready but stopped. + */ + result = ftape_ready_wait(ftape_timeout.pause,&status); + } + } while (ftape_tape_running + && !(sigtestsetmask(¤t->pending.signal, _NEVER_BLOCK))); +#ifndef TESTING + ft_location.known = 0; +#endif + if (ft_runner_status == aborting || ft_runner_status == do_abort) { + ft_runner_status = idle; + } + TRACE_EXIT result; +} + +/* Wait until runner has finished tail buffer. + * + */ +int ftape_wait_segment(buffer_state_enum state) +{ + int status; + int result = 0; + TRACE_FUN(ft_t_flow); + + while (ft_buffer[ft_tail]->status == state) { + TRACE(ft_t_flow, "state: %d", ft_buffer[ft_tail]->status); + /* First buffer still being worked on, wait up to timeout. + * + * Note: we check two times for being killed. 50 + * seconds are quite long. Note that + * fdc_interrupt_wait() is not killable by any + * means. ftape_read_segment() wants us to return + * -EINTR in case of a signal. + */ + FT_SIGNAL_EXIT(_DONT_BLOCK); + result = fdc_interrupt_wait(50 * FT_SECOND); + FT_SIGNAL_EXIT(_DONT_BLOCK); + if (result < 0) { + TRACE_ABORT(result, + ft_t_err, "fdc_interrupt_wait failed"); + } + if (fdc_setup_error) { + /* recover... FIXME */ + TRACE_ABORT(-EIO, ft_t_err, "setup error"); + } + } + if (ft_buffer[ft_tail]->status != error) { + TRACE_EXIT 0; + } + TRACE_CATCH(ftape_report_drive_status(&status),); + TRACE(ft_t_noise, "ftape_report_drive_status: 0x%02x", status); + if ((status & QIC_STATUS_READY) && + (status & QIC_STATUS_ERROR)) { + unsigned int error; + qic117_cmd_t command; + + /* Report and clear error state. + * In case the drive can't operate at the selected + * rate, select the next lower data rate. + */ + ftape_report_error(&error, &command, 1); + if (error == 31 && command == QIC_LOGICAL_FORWARD) { + /* drive does not accept this data rate */ + if (ft_data_rate > 250) { + TRACE(ft_t_info, + "Probable data rate conflict"); + TRACE(ft_t_info, + "Lowering data rate to %d Kbps", + ft_data_rate / 2); + ftape_half_data_rate(); + if (ft_buffer[ft_tail]->retry > 0) { + /* give it a chance */ + --ft_buffer[ft_tail]->retry; + } + } else { + /* no rate is accepted... */ + TRACE(ft_t_err, "We're dead :("); + } + } else { + TRACE(ft_t_err, "Unknown error"); + } + TRACE_EXIT -EIO; /* g.p. error */ + } + TRACE_EXIT 0; +} + +/* forward */ static int seek_forward(int segment_id, int fast); + +static int fast_seek(int count, int reverse) +{ + int result = 0; + int status; + TRACE_FUN(ft_t_flow); + + 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 (ft_location.known && + ((ft_location.bot && !reverse) || + (ft_location.eot && reverse))) { + if (!reverse) { + /* (slow) skip to first segment on a track + */ + seek_forward(ft_location.track * ft_segments_per_track, 0); + --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 { + TRACE(ft_t_flow, "warning: zero or negative count: %d", count); + } + if (count > 0) { + int i; + int nibbles = count > 255 ? 3 : 2; + + if (count > 4095) { + TRACE(ft_t_noise, "skipping clipped at 4095 segment"); + count = 4095; + } + /* Issue this tape command first. */ + if (!reverse) { + TRACE(ft_t_noise, "skipping %d segment(s)", count); + result = ftape_command(nibbles == 3 ? + QIC_SKIP_EXTENDED_FORWARD : QIC_SKIP_FORWARD); + } else { + TRACE(ft_t_noise, "backing up %d segment(s)", count); + result = ftape_command(nibbles == 3 ? + QIC_SKIP_EXTENDED_REVERSE : QIC_SKIP_REVERSE); + } + if (result < 0) { + TRACE(ft_t_noise, "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(ftape_timeout.rewind, &status); + if (result >= 0) { + ftape_tape_running = 0; + } + } + } + TRACE_EXIT 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 * ft_segments_per_track. + */ + if (ft_location.track == -1) { + return 1; /* unforseen situation, don't generate error */ + } else { + /* Use margin of ft_segments_per_track on both sides + * because ftape needs some margin and the error we're + * looking for is much larger ! + */ + int lo = (ft_location.track - 1) * ft_segments_per_track; + int hi = (ft_location.track + 2) * ft_segments_per_track; + + return (id >= lo && id < hi); + } +} + +static int seek_forward(int segment_id, int fast) +{ + int failures = 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; + int prev_segment = ft_location.segment; + TRACE_FUN(ft_t_flow); + + if (!ft_location.known) { + TRACE_ABORT(-EIO, ft_t_err, + "fatal: cannot seek from unknown location"); + } + if (!validate(segment_id)) { + ftape_sleep(1 * FT_SECOND); + ft_failure = 1; + TRACE_ABORT(-EIO, ft_t_err, + "fatal: head off track (bad hardware?)"); + } + TRACE(ft_t_noise, "from %d/%d to %d/0 - %d", + ft_location.segment, ft_location.sector,segment_id,margin); + count = target - ft_location.segment - overshoot; + fast_seeking = (fast && + count > (min_count + (ft_location.bot ? 1 : 0))); + if (fast_seeking) { + TRACE(ft_t_noise, "fast skipping %d segments", count); + expected = segment_id - margin; + fast_seek(count, 0); + } + if (!ftape_tape_running) { + logical_forward(); + } + while (ft_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 || !ft_location.known || + sigtestsetmask(¤t->pending.signal, _DONT_BLOCK)) { + ft_location.known = 0; + if (!ftape_tape_running || + ++failures > FT_SECTORS_PER_SEGMENT) { + TRACE_ABORT(-EIO, ft_t_err, + "read_id failed completely"); + } + FT_SIGNAL_EXIT(_DONT_BLOCK); + TRACE(ft_t_flow, "read_id failed, retry (%d)", + failures); + continue; + } + if (fast_seeking) { + TRACE(ft_t_noise, "ended at %d/%d (%d,%d)", + ft_location.segment, ft_location.sector, + overshoot, inhibit_correction); + if (!inhibit_correction && + (ft_location.segment < expected || + ft_location.segment > expected + margin)) { + int error = ft_location.segment - expected; + TRACE(ft_t_noise, + "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 > OVERSHOOT_LIMIT) { + if (overshoot < 0) { + /* keep sane value */ + overshoot = -5; + } else { + /* keep sane value */ + overshoot = OVERSHOOT_LIMIT; + } + TRACE(ft_t_noise, + "clipped overshoot to %d", + overshoot); + } + } + fast_seeking = 0; + } + if (ft_location.known) { + if (ft_location.segment > prev_segment + 1) { + TRACE(ft_t_noise, + "missed segment %d while skipping", + prev_segment + 1); + } + prev_segment = ft_location.segment; + } + } + if (ft_location.segment > segment_id) { + TRACE_ABORT(-EIO, + ft_t_noise, "failed: skip ended at segment %d/%d", + ft_location.segment, ft_location.sector); + } + TRACE_EXIT 0; +} + +static int skip_reverse(int segment_id, int *pstatus) +{ + 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 = 1; + int short_seek; + int target = segment_id - margin; + TRACE_FUN(ft_t_flow); + + if (ft_location.known && !validate(segment_id)) { + ftape_sleep(1 * FT_SECOND); + ft_failure = 1; + TRACE_ABORT(-EIO, ft_t_err, + "fatal: head off track (bad hardware?)"); + } + do { + if (!ft_location.known) { + TRACE(ft_t_warn, "warning: location not known"); + } + TRACE(ft_t_noise, "from %d/%d to %d/0 - %d", + ft_location.segment, ft_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 (ft_location.segment - target - (min_rewind - 1) < 1) { + short_seek = 1; + } else { + count = ft_location.segment - target - overshoot; + short_seek = (count < 1); + } + if (short_seek) { + count = 1; /* do shortest rewind */ + expected = ft_location.segment - min_rewind; + if (expected/ft_segments_per_track != ft_location.track) { + expected = (ft_location.track * + ft_segments_per_track); + } + } else { + expected = target; + } + fast_seek(count, 1); + logical_forward(); + if (ftape_read_id() < 0 || !ft_location.known || + (sigtestsetmask(¤t->pending.signal, _DONT_BLOCK))) { + if ((!ftape_tape_running && !ft_location.known) || + ++failures > FT_SECTORS_PER_SEGMENT) { + TRACE_ABORT(-EIO, ft_t_err, + "read_id failed completely"); + } + FT_SIGNAL_EXIT(_DONT_BLOCK); + TRACE_CATCH(ftape_report_drive_status(pstatus),); + TRACE(ft_t_noise, "ftape_read_id failed, retry (%d)", + failures); + continue; + } + TRACE(ft_t_noise, "ended at %d/%d (%d,%d,%d)", + ft_location.segment, ft_location.sector, + min_rewind, overshoot, inhibit_correction); + if (!inhibit_correction && + (ft_location.segment < expected || + ft_location.segment > expected + margin)) { + int error = expected - ft_location.segment; + if (short_seek) { + TRACE(ft_t_noise, + "adjusting min_rewind from %d to %d", + min_rewind, min_rewind + error); + min_rewind += error; + if (min_rewind < -5) { + /* is this right ? FIXME ! */ + /* keep sane value */ + min_rewind = -5; + TRACE(ft_t_noise, + "clipped min_rewind to %d", + min_rewind); + } + } else { + TRACE(ft_t_noise, + "adjusting overshoot from %d to %d", + overshoot, overshoot + error); + overshoot += error; + if (overshoot < -5 || + overshoot > OVERSHOOT_LIMIT) { + if (overshoot < 0) { + /* keep sane value */ + overshoot = -5; + } else { + /* keep sane value */ + overshoot = OVERSHOOT_LIMIT; + } + TRACE(ft_t_noise, + "clipped overshoot to %d", + overshoot); + } + } + } + } while (ft_location.segment > segment_id); + if (ft_location.known) { + TRACE(ft_t_noise, "current location: %d/%d", + ft_location.segment, ft_location.sector); + } + TRACE_EXIT 0; +} + +static int determine_position(void) +{ + int retry = 0; + int status; + int result; + TRACE_FUN(ft_t_flow); + + if (!ftape_tape_running) { + /* This should only happen if tape is stopped by isr. + */ + TRACE(ft_t_flow, "waiting for tape stop"); + if (ftape_ready_wait(ftape_timeout.pause, &status) < 0) { + TRACE(ft_t_flow, "drive still running (fatal)"); + ftape_tape_running = 1; /* ? */ + } + } else { + ftape_report_drive_status(&status); + } + if (status & QIC_STATUS_READY) { + /* Drive must be ready to check error state ! + */ + TRACE(ft_t_flow, "drive is ready"); + if (status & QIC_STATUS_ERROR) { + unsigned int error; + qic117_cmd_t command; + + /* Report and clear error state, try to continue. + */ + TRACE(ft_t_flow, "error status set"); + ftape_report_error(&error, &command, 1); + ftape_ready_wait(ftape_timeout.reset, &status); + ftape_tape_running = 0; /* ? */ + } + if (check_bot_eot(status)) { + if (ft_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(ft_t_flow, + "start tape from logical bot"); + logical_forward(); /* start moving */ + } + } else { + if ((status & QIC_STATUS_READY) == 0) { + TRACE(ft_t_noise, "waiting for logical end of track"); + result = ftape_ready_wait(ftape_timeout.reset, &status); + /* error handling needed ? */ + } else { + TRACE(ft_t_noise, + "tape at logical end of track"); + } + } + } else { + TRACE(ft_t_flow, "start tape"); + logical_forward(); /* start moving */ + ft_location.known = 0; /* not cleared by logical forward ! */ + } + } + /* tape should be moving now, start reading id's + */ + while (!ft_location.known && + retry++ < FT_SECTORS_PER_SEGMENT && + (result = ftape_read_id()) < 0) { + + TRACE(ft_t_flow, "location unknown"); + + /* exit on signal + */ + FT_SIGNAL_EXIT(_DONT_BLOCK); + + /* read-id somehow failed, tape may + * have reached end or some other + * error happened. + */ + TRACE(ft_t_flow, "read-id failed"); + TRACE_CATCH(ftape_report_drive_status(&status),); + TRACE(ft_t_err, "ftape_report_drive_status: 0x%02x", status); + if (status & QIC_STATUS_READY) { + ftape_tape_running = 0; + TRACE(ft_t_noise, "tape stopped for unknown reason! " + "status = 0x%02x", status); + if (status & QIC_STATUS_ERROR || + !check_bot_eot(status)) { + /* oops, tape stopped but not at end! + */ + TRACE_EXIT -EIO; + } + } + } + TRACE(ft_t_flow, + "tape is positioned at segment %d", ft_location.segment); + TRACE_EXIT ft_location.known ? 0 : -EIO; +} + +/* 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) +{ + int track = segment_id / ft_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; + TRACE_FUN(ft_t_flow); + + /* 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. + */ + ftape_tape_running = 0; + TRACE(ft_t_noise, "target segment: %d/%d%s", segment_id, sector_offset, + ft_buffer[ft_head]->retry > 0 ? " retry" : ""); + if (ft_buffer[ft_head]->retry > 0) { /* this is a retry */ + int dist = segment_id - last_segment; + + if ((int)ft_history.overrun_errors < overrun_count_offset) { + overrun_count_offset = ft_history.overrun_errors; + } else if (dist < 0 || dist > 50) { + overrun_count_offset = ft_history.overrun_errors; + } else if ((ft_history.overrun_errors - + overrun_count_offset) >= 8) { + if (ftape_increase_threshold() >= 0) { + --ft_buffer[ft_head]->retry; + overrun_count_offset = + ft_history.overrun_errors; + TRACE(ft_t_warn, "increased threshold because " + "of excessive overrun errors"); + } else if (!bad_bus_timing && ft_data_rate >= 1000) { + ftape_half_data_rate(); + --ft_buffer[ft_head]->retry; + bad_bus_timing = 1; + overrun_count_offset = + ft_history.overrun_errors; + TRACE(ft_t_warn, "reduced datarate because " + "of excessive overrun errors"); + } + } + } + last_segment = segment_id; + if (ft_location.track != track || + (ftape_might_be_off_track && ft_buffer[ft_head]->retry== 0)) { + /* current track unknown or not equal to destination + */ + ftape_ready_wait(ftape_timeout.seek, &status); + ftape_seek_head_to_track(track); + /* overrun_count_offset = ft_history.overrun_errors; */ + } + result = -EIO; + retry = 0; + while (result < 0 && + retry++ <= 5 && + !ft_failure && + !(sigtestsetmask(¤t->pending.signal, _DONT_BLOCK))) { + + if (retry && start_offset < 5) { + start_offset ++; + } + /* Check if we are able to catch the requested + * segment in time. + */ + if ((ft_location.known || (determine_position() == 0)) && + ft_location.segment >= + (segment_id - + ((ftape_tape_running || ft_location.bot) + ? 0 : start_offset))) { + /* Too far ahead (in or past target segment). + */ + if (ftape_tape_running) { + if ((result = ftape_stop_tape(&status)) < 0) { + TRACE(ft_t_err, + "stop tape failed with code %d", + result); + break; + } + TRACE(ft_t_noise, "tape stopped"); + ftape_tape_running = 0; + } + TRACE(ft_t_noise, "repositioning"); + ++ft_history.rewinds; + if (segment_id % ft_segments_per_track < start_offset){ + TRACE(ft_t_noise, "end of track condition\n" + KERN_INFO "segment_id : %d\n" + KERN_INFO "ft_segments_per_track: %d\n" + KERN_INFO "start_offset : %d", + segment_id, ft_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( + (ft_location.track & 1) + ? QIC_PHYSICAL_FORWARD + : QIC_PHYSICAL_REVERSE, + ftape_timeout.rewind, &status); + check_bot_eot(status); /* update location */ + } else { + result= skip_reverse(segment_id - start_offset, + &status); + } + } + if (!ft_location.known) { + TRACE(ft_t_bug, "panic: location not known"); + result = -EIO; + continue; /* while() will check for failure */ + } + TRACE(ft_t_noise, "current segment: %d/%d", + ft_location.segment, ft_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 (ft_location.segment < + (segment_id - ((ftape_tape_running || ft_location.bot) + ? 0 : start_offset))) { + if (sector_offset > 0) { + result = seek_forward(segment_id, + retry <= 3); + } else { + result = seek_forward(segment_id - 1, + retry <= 3); + } + } + if (result == 0 && + ft_location.segment != + (segment_id - (sector_offset > 0 ? 0 : 1))) { + result = -EIO; + } + } + if (result < 0) { + TRACE(ft_t_err, "failed to reposition"); + } else { + ft_runner_status = running; + } + TRACE_EXIT result; +} diff --git a/drivers/char/ftape/lowlevel/ftape-rw.h b/drivers/char/ftape/lowlevel/ftape-rw.h new file mode 100644 index 000000000000..32f4feeb887c --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-rw.h @@ -0,0 +1,111 @@ +#ifndef _FTAPE_RW_H +#define _FTAPE_RW_H + +/* + * Copyright (C) 1993-1996 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-rw.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:18:25 $ + * + * This file contains the definitions for the read and write + * functions for the QIC-117 floppy-tape driver for Linux. + * + * Claus-Justus Heine (1996/09/20): Add definition of format code 6 + * Claus-Justus Heine (1996/10/04): Changed GET/PUT macros to cast to (__u8 *) + * + */ + +#include "../lowlevel/fdc-io.h" +#include "../lowlevel/ftape-init.h" +#include "../lowlevel/ftape-bsm.h" + +#include <asm/unaligned.h> + +#define GET2(address, offset) get_unaligned((__u16*)((__u8 *)address + offset)) +#define GET4(address, offset) get_unaligned((__u32*)((__u8 *)address + offset)) +#define GET8(address, offset) get_unaligned((__u64*)((__u8 *)address + offset)) +#define PUT2(address, offset , value) put_unaligned((value), (__u16*)((__u8 *)address + offset)) +#define PUT4(address, offset , value) put_unaligned((value), (__u32*)((__u8 *)address + offset)) +#define PUT8(address, offset , value) put_unaligned((value), (__u64*)((__u8 *)address + offset)) + +enum runner_status_enum { + idle = 0, + running, + do_abort, + aborting, + logical_eot, + end_of_tape, +}; + +typedef enum ft_buffer_queue { + ft_queue_head = 0, + ft_queue_tail = 1 +} ft_buffer_queue_t; + + +typedef struct { + int track; /* tape head position */ + volatile int segment; /* current segment */ + volatile int sector; /* sector offset within current segment */ + volatile unsigned int bot; /* logical begin of track */ + volatile unsigned int eot; /* logical end of track */ + volatile unsigned int known; /* validates bot, segment, sector */ +} location_record; + +/* Count nr of 1's in pattern. + */ +static inline int count_ones(unsigned long mask) +{ + int bits; + + for (bits = 0; mask != 0; mask >>= 1) { + if (mask & 1) { + ++bits; + } + } + return bits; +} + +#define FT_MAX_NR_BUFFERS 16 /* arbitrary value */ +/* ftape-rw.c defined global vars. + */ +extern buffer_struct *ft_buffer[FT_MAX_NR_BUFFERS]; +extern int ft_nr_buffers; +extern location_record ft_location; +extern volatile int ftape_tape_running; + +/* ftape-rw.c defined global functions. + */ +extern int ftape_setup_new_segment(buffer_struct * buff, + int segment_id, + int offset); +extern int ftape_calc_next_cluster(buffer_struct * buff); +extern buffer_struct *ftape_next_buffer (ft_buffer_queue_t pos); +extern buffer_struct *ftape_get_buffer (ft_buffer_queue_t pos); +extern int ftape_buffer_id (ft_buffer_queue_t pos); +extern void ftape_reset_buffer(void); +extern void ftape_tape_parameters(__u8 drive_configuration); +extern int ftape_wait_segment(buffer_state_enum state); +extern int ftape_dumb_stop(void); +extern int ftape_start_tape(int segment_id, int offset); +extern int ftape_stop_tape(int *pstatus); +extern int ftape_handle_logical_eot(void); +extern buffer_state_enum ftape_set_state(buffer_state_enum new_state); +#endif /* _FTAPE_RW_H */ diff --git a/drivers/char/ftape/lowlevel/ftape-setup.c b/drivers/char/ftape/lowlevel/ftape-setup.c new file mode 100644 index 000000000000..280a1a55d87e --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-setup.c @@ -0,0 +1,105 @@ +/* + * Copyright (C) 1996, 1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-setup.c,v $ + * $Revision: 1.7 $ + * $Date: 1997/10/10 09:57:06 $ + * + * This file contains the code for processing the kernel command + * line options for the QIC-40/80/3010/3020 floppy-tape driver + * "ftape" for Linux. + */ + +#include <linux/config.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/mm.h> + +#include <linux/ftape.h> +#include <linux/init.h> +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/fdc-io.h" + +static struct param_table { + const char *name; + int *var; + int def_param; + int min; + int max; +} config_params[] __initdata = { +#ifndef CONFIG_FT_NO_TRACE_AT_ALL + { "tracing", &ftape_tracing, 3, ft_t_bug, ft_t_any}, +#endif + { "ioport", &ft_fdc_base, CONFIG_FT_FDC_BASE, 0x0, 0xfff}, + { "irq", &ft_fdc_irq, CONFIG_FT_FDC_IRQ, 2, 15}, + { "dma", &ft_fdc_dma, CONFIG_FT_FDC_DMA, 0, 3}, + { "threshold", &ft_fdc_threshold, CONFIG_FT_FDC_THR, 1, 16}, + { "datarate", &ft_fdc_rate_limit, CONFIG_FT_FDC_MAX_RATE, 500, 2000}, + { "fc10", &ft_probe_fc10, CONFIG_FT_PROBE_FC10, 0, 1}, + { "mach2", &ft_mach2, CONFIG_FT_MACH2, 0, 1} +}; + +static int __init ftape_setup(char *str) +{ + int i; + int param; + int ints[2]; + + TRACE_FUN(ft_t_flow); + + str = get_options(str, ARRAY_SIZE(ints), ints); + if (str) { + for (i=0; i < NR_ITEMS(config_params); i++) { + if (strcmp(str,config_params[i].name) == 0){ + if (ints[0]) { + param = ints[1]; + } else { + param = config_params[i].def_param; + } + if (param < config_params[i].min || + param > config_params[i].max) { + TRACE(ft_t_err, + "parameter %s out of range %d ... %d", + config_params[i].name, + config_params[i].min, + config_params[i].max); + goto out; + } + if(config_params[i].var) { + TRACE(ft_t_info, "%s=%d", str, param); + *config_params[i].var = param; + } + goto out; + } + } + } + if (str) { + TRACE(ft_t_err, "unknown ftape option [%s]", str); + + TRACE(ft_t_err, "allowed options are:"); + for (i=0; i < NR_ITEMS(config_params); i++) { + TRACE(ft_t_err, " %s",config_params[i].name); + } + } else { + TRACE(ft_t_err, "botched ftape option"); + } + out: + TRACE_EXIT 1; +} + +__setup("ftape=", ftape_setup); diff --git a/drivers/char/ftape/lowlevel/ftape-tracing.c b/drivers/char/ftape/lowlevel/ftape-tracing.c new file mode 100644 index 000000000000..7fdc6567440b --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-tracing.c @@ -0,0 +1,118 @@ +/* + * Copyright (C) 1993-1996 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-tracing.c,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:18:27 $ + * + * This file contains the reading code + * for the QIC-117 floppy-tape driver for Linux. + */ + +#include <linux/ftape.h> +#include "../lowlevel/ftape-tracing.h" + +/* Global vars. + */ +/* tracing + * set it to: to log : + * 0 bugs + * 1 + errors + * 2 + warnings + * 3 + information + * 4 + more information + * 5 + program flow + * 6 + fdc/dma info + * 7 + data flow + * 8 + everything else + */ +ft_trace_t ftape_tracing = ft_t_info; /* Default level: information and up */ +int ftape_function_nest_level; + +/* Local vars. + */ +static __u8 trace_id; +static char spacing[] = "* "; + +void ftape_trace_call(const char *file, const char *name) +{ + char *indent; + + /* Since printk seems not to work with "%*s" format + * we'll use this work-around. + */ + if (ftape_function_nest_level < 0) { + printk(KERN_INFO "function nest level (%d) < 0\n", + ftape_function_nest_level); + ftape_function_nest_level = 0; + } + if (ftape_function_nest_level < sizeof(spacing)) { + indent = (spacing + + sizeof(spacing) - 1 - + ftape_function_nest_level); + } else { + indent = spacing; + } + printk(KERN_INFO "[%03d]%s+%s (%s)\n", + (int) trace_id++, indent, file, name); +} + +void ftape_trace_exit(const char *file, const char *name) +{ + char *indent; + + /* Since printk seems not to work with "%*s" format + * we'll use this work-around. + */ + if (ftape_function_nest_level < 0) { + printk(KERN_INFO "function nest level (%d) < 0\n", ftape_function_nest_level); + ftape_function_nest_level = 0; + } + if (ftape_function_nest_level < sizeof(spacing)) { + indent = (spacing + + sizeof(spacing) - 1 - + ftape_function_nest_level); + } else { + indent = spacing; + } + printk(KERN_INFO "[%03d]%s-%s (%s)\n", + (int) trace_id++, indent, file, name); +} + +void ftape_trace_log(const char *file, const char *function) +{ + char *indent; + + /* Since printk seems not to work with "%*s" format + * we'll use this work-around. + */ + if (ftape_function_nest_level < 0) { + printk(KERN_INFO "function nest level (%d) < 0\n", ftape_function_nest_level); + ftape_function_nest_level = 0; + } + if (ftape_function_nest_level < sizeof(spacing)) { + indent = (spacing + + sizeof(spacing) - 1 - + ftape_function_nest_level); + } else { + indent = spacing; + } + printk(KERN_INFO "[%03d]%s%s (%s) - ", + (int) trace_id++, indent, file, function); +} diff --git a/drivers/char/ftape/lowlevel/ftape-tracing.h b/drivers/char/ftape/lowlevel/ftape-tracing.h new file mode 100644 index 000000000000..fa7cd20ee66c --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-tracing.h @@ -0,0 +1,180 @@ +#ifndef _FTAPE_TRACING_H +#define _FTAPE_TRACING_H + +/* + * Copyright (C) 1994-1996 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-tracing.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:18:28 $ + * + * This file contains definitions that eases the debugging of the + * QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux. + */ + +#include <linux/config.h> +#include <linux/kernel.h> + +/* + * Be very careful with TRACE_EXIT and TRACE_ABORT. + * + * if (something) TRACE_EXIT error; + * + * will NOT work. Use + * + * if (something) { + * TRACE_EXIT error; + * } + * + * instead. Maybe a bit dangerous, but save lots of lines of code. + */ + +#define LL_X "%d/%d KB" +#define LL(x) (unsigned int)((__u64)(x)>>10), (unsigned int)((x)&1023) + +typedef enum { + ft_t_nil = -1, + ft_t_bug, + ft_t_err, + ft_t_warn, + ft_t_info, + ft_t_noise, + ft_t_flow, + ft_t_fdc_dma, + ft_t_data_flow, + ft_t_any +} ft_trace_t; + +#ifdef CONFIG_FT_NO_TRACE_AT_ALL +/* the compiler will optimize away most TRACE() macros + */ +#define FT_TRACE_TOP_LEVEL ft_t_bug +#define TRACE_FUN(level) do {} while(0) +#define TRACE_EXIT return +#define TRACE(l, m, i...) \ +{ \ + if ((ft_trace_t)(l) == FT_TRACE_TOP_LEVEL) { \ + printk(KERN_INFO"ftape%s(%s):\n" \ + KERN_INFO m".\n" ,__FILE__, __FUNCTION__ , ##i); \ + } \ +} +#define SET_TRACE_LEVEL(l) if ((l) == (l)) do {} while(0) +#define TRACE_LEVEL FT_TRACE_TOP_LEVEL + +#else + +#ifdef CONFIG_FT_NO_TRACE +/* the compiler will optimize away many TRACE() macros + * the ftape_simple_trace_call() function simply increments + * the function nest level. + */ +#define FT_TRACE_TOP_LEVEL ft_t_warn +#define TRACE_FUN(level) ftape_function_nest_level++ +#define TRACE_EXIT ftape_function_nest_level--; return + +#else +#ifdef CONFIG_FT_FULL_DEBUG +#define FT_TRACE_TOP_LEVEL ft_t_any +#else +#define FT_TRACE_TOP_LEVEL ft_t_flow +#endif +#define TRACE_FUN(level) \ + const ft_trace_t _tracing = level; \ + if (ftape_tracing >= (ft_trace_t)(level) && \ + (ft_trace_t)(level) <= FT_TRACE_TOP_LEVEL) \ + ftape_trace_call(__FILE__, __FUNCTION__); \ + ftape_function_nest_level ++; + +#define TRACE_EXIT \ + --ftape_function_nest_level; \ + if (ftape_tracing >= (ft_trace_t)(_tracing) && \ + (ft_trace_t)(_tracing) <= FT_TRACE_TOP_LEVEL) \ + ftape_trace_exit(__FILE__, __FUNCTION__); \ + return + +#endif + +#define TRACE(l, m, i...) \ +{ \ + if (ftape_tracing >= (ft_trace_t)(l) && \ + (ft_trace_t)(l) <= FT_TRACE_TOP_LEVEL) { \ + ftape_trace_log(__FILE__, __FUNCTION__); \ + printk(m".\n" ,##i); \ + } \ +} + +#define SET_TRACE_LEVEL(l) \ +{ \ + if ((ft_trace_t)(l) <= FT_TRACE_TOP_LEVEL) { \ + ftape_tracing = (ft_trace_t)(l); \ + } else { \ + ftape_tracing = FT_TRACE_TOP_LEVEL; \ + } \ +} +#define TRACE_LEVEL \ +((ftape_tracing <= FT_TRACE_TOP_LEVEL) ? ftape_tracing : FT_TRACE_TOP_LEVEL) + + +/* Global variables declared in tracing.c + */ +extern ft_trace_t ftape_tracing; /* sets default level */ +extern int ftape_function_nest_level; + +/* Global functions declared in tracing.c + */ +extern void ftape_trace_call(const char *file, const char *name); +extern void ftape_trace_exit(const char *file, const char *name); +extern void ftape_trace_log (const char *file, const char *name); + +#endif /* !defined(CONFIG_FT_NO_TRACE_AT_ALL) */ + +/* + * Abort with a message. + */ +#define TRACE_ABORT(res, i...) \ +{ \ + TRACE(i); \ + TRACE_EXIT res; \ +} + +/* The following transforms the common "if(result < 0) ... " into a + * one-liner. + */ +#define _TRACE_CATCH(level, fun, action) \ +{ \ + int _res = (fun); \ + if (_res < 0) { \ + do { action /* */ ; } while(0); \ + TRACE_ABORT(_res, level, "%s failed: %d", #fun, _res); \ + } \ +} + +#define TRACE_CATCH(fun, fail) _TRACE_CATCH(ft_t_err, fun, fail) + +/* Abort the current function when signalled. This doesn't belong here, + * but rather into ftape-rw.h (maybe) + */ +#define FT_SIGNAL_EXIT(sig_mask) \ + if (sigtestsetmask(¤t->pending.signal, sig_mask)) { \ + TRACE_ABORT(-EINTR, \ + ft_t_warn, \ + "interrupted by non-blockable signal"); \ + } + +#endif /* _FTAPE_TRACING_H */ diff --git a/drivers/char/ftape/lowlevel/ftape-write.c b/drivers/char/ftape/lowlevel/ftape-write.c new file mode 100644 index 000000000000..45601ec801ee --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-write.c @@ -0,0 +1,336 @@ +/* + * Copyright (C) 1993-1995 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-write.c,v $ + * $Revision: 1.3.4.1 $ + * $Date: 1997/11/14 18:07:04 $ + * + * This file contains the writing code + * for the QIC-117 floppy-tape driver for Linux. + */ + +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/mm.h> + +#include <linux/ftape.h> +#include <linux/qic117.h> +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/ftape-write.h" +#include "../lowlevel/ftape-read.h" +#include "../lowlevel/ftape-io.h" +#include "../lowlevel/ftape-ctl.h" +#include "../lowlevel/ftape-rw.h" +#include "../lowlevel/ftape-ecc.h" +#include "../lowlevel/ftape-bsm.h" +#include "../lowlevel/fdc-isr.h" + +/* Global vars. + */ + +/* Local vars. + */ +static int last_write_failed; + +void ftape_zap_write_buffers(void) +{ + int i; + + for (i = 0; i < ft_nr_buffers; ++i) { + ft_buffer[i]->status = done; + } + ftape_reset_buffer(); +} + +static int copy_and_gen_ecc(void *destination, + const void *source, + const SectorMap bad_sector_map) +{ + int result; + struct memory_segment mseg; + int bads = count_ones(bad_sector_map); + TRACE_FUN(ft_t_any); + + if (bads > 0) { + TRACE(ft_t_noise, "bad sectors in map: %d", bads); + } + if (bads + 3 >= FT_SECTORS_PER_SEGMENT) { + TRACE(ft_t_noise, "empty segment"); + mseg.blocks = 0; /* skip entire segment */ + result = 0; /* nothing written */ + } else { + mseg.blocks = FT_SECTORS_PER_SEGMENT - bads; + mseg.data = destination; + memcpy(mseg.data, source, (mseg.blocks - 3) * FT_SECTOR_SIZE); + result = ftape_ecc_set_segment_parity(&mseg); + if (result < 0) { + TRACE(ft_t_err, "ecc_set_segment_parity failed"); + } else { + result = (mseg.blocks - 3) * FT_SECTOR_SIZE; + } + } + TRACE_EXIT result; +} + + +int ftape_start_writing(const ft_write_mode_t mode) +{ + buffer_struct *head = ftape_get_buffer(ft_queue_head); + int segment_id = head->segment_id; + int result; + buffer_state_enum wanted_state = (mode == FT_WR_DELETE + ? deleting + : writing); + TRACE_FUN(ft_t_flow); + + if ((ft_driver_state != wanted_state) || head->status != waiting) { + TRACE_EXIT 0; + } + ftape_setup_new_segment(head, segment_id, 1); + if (mode == FT_WR_SINGLE) { + /* stop tape instead of pause */ + head->next_segment = 0; + } + ftape_calc_next_cluster(head); /* prepare */ + head->status = ft_driver_state; /* either writing or deleting */ + if (ft_runner_status == idle) { + TRACE(ft_t_noise, + "starting runner for segment %d", segment_id); + TRACE_CATCH(ftape_start_tape(segment_id,head->sector_offset),); + } else { + TRACE(ft_t_noise, "runner not idle, not starting tape"); + } + /* go */ + result = fdc_setup_read_write(head, (mode == FT_WR_DELETE + ? FDC_WRITE_DELETED : FDC_WRITE)); + ftape_set_state(wanted_state); /* should not be necessary */ + TRACE_EXIT result; +} + +/* Wait until all data is actually written to tape. + * + * There is a problem: when the tape runs into logical EOT, then this + * failes. We need to restart the runner in this case. + */ +int ftape_loop_until_writes_done(void) +{ + buffer_struct *head; + TRACE_FUN(ft_t_flow); + + while ((ft_driver_state == writing || ft_driver_state == deleting) && + ftape_get_buffer(ft_queue_head)->status != done) { + /* set the runner status to idle if at lEOT */ + TRACE_CATCH(ftape_handle_logical_eot(), last_write_failed = 1); + /* restart the tape if necessary */ + if (ft_runner_status == idle) { + TRACE(ft_t_noise, "runner is idle, restarting"); + if (ft_driver_state == deleting) { + TRACE_CATCH(ftape_start_writing(FT_WR_DELETE), + last_write_failed = 1); + } else { + TRACE_CATCH(ftape_start_writing(FT_WR_MULTI), + last_write_failed = 1); + } + } + TRACE(ft_t_noise, "tail: %d, head: %d", + ftape_buffer_id(ft_queue_tail), + ftape_buffer_id(ft_queue_head)); + TRACE_CATCH(fdc_interrupt_wait(5 * FT_SECOND), + last_write_failed = 1); + head = ftape_get_buffer(ft_queue_head); + if (head->status == error) { + /* Allow escape from loop when signaled ! + */ + FT_SIGNAL_EXIT(_DONT_BLOCK); + if (head->hard_error_map != 0) { + /* Implement hard write error recovery here + */ + } + /* retry this one */ + head->status = waiting; + if (ft_runner_status == aborting) { + ftape_dumb_stop(); + } + if (ft_runner_status != idle) { + TRACE_ABORT(-EIO, ft_t_err, + "unexpected state: " + "ft_runner_status != idle"); + } + ftape_start_writing(ft_driver_state == deleting + ? FT_WR_MULTI : FT_WR_DELETE); + } + TRACE(ft_t_noise, "looping until writes done"); + } + ftape_set_state(idle); + TRACE_EXIT 0; +} + +/* Write given segment from buffer at address to tape. + */ +static int write_segment(const int segment_id, + const void *address, + const ft_write_mode_t write_mode) +{ + int bytes_written = 0; + buffer_struct *tail; + buffer_state_enum wanted_state = (write_mode == FT_WR_DELETE + ? deleting : writing); + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_noise, "segment_id = %d", segment_id); + if (ft_driver_state != wanted_state) { + if (ft_driver_state == deleting || + wanted_state == deleting) { + TRACE_CATCH(ftape_loop_until_writes_done(),); + } + TRACE(ft_t_noise, "calling ftape_abort_operation"); + TRACE_CATCH(ftape_abort_operation(),); + ftape_zap_write_buffers(); + ftape_set_state(wanted_state); + } + /* if all buffers full we'll have to wait... + */ + ftape_wait_segment(wanted_state); + tail = ftape_get_buffer(ft_queue_tail); + switch(tail->status) { + case done: + ft_history.defects += count_ones(tail->hard_error_map); + break; + case waiting: + /* this could happen with multiple EMPTY_SEGMENTs, but + * shouldn't happen any more as we re-start the runner even + * with an empty segment. + */ + bytes_written = -EAGAIN; + break; + case error: + /* setup for a retry + */ + tail->status = waiting; + bytes_written = -EAGAIN; /* force retry */ + if (tail->hard_error_map != 0) { + TRACE(ft_t_warn, + "warning: %d hard error(s) in written segment", + count_ones(tail->hard_error_map)); + TRACE(ft_t_noise, "hard_error_map = 0x%08lx", + (long)tail->hard_error_map); + /* Implement hard write error recovery here + */ + } + break; + default: + TRACE_ABORT(-EIO, ft_t_err, + "wait for empty segment failed, tail status: %d", + tail->status); + } + /* should runner stop ? + */ + if (ft_runner_status == aborting) { + buffer_struct *head = ftape_get_buffer(ft_queue_head); + if (head->status == wanted_state) { + head->status = done; /* ???? */ + } + /* don't call abort_operation(), we don't want to zap + * the dma buffers + */ + TRACE_CATCH(ftape_dumb_stop(),); + } else { + /* If just passed last segment on tape: wait for BOT + * or EOT mark. Sets ft_runner_status to idle if at lEOT + * and successful + */ + TRACE_CATCH(ftape_handle_logical_eot(),); + } + if (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! + * + * Empty segments are handled inside coyp_and_gen_ecc() + */ + if (write_mode != FT_WR_DELETE) { + TRACE_CATCH(bytes_written = copy_and_gen_ecc( + tail->address, address, + ftape_get_bad_sector_entry(segment_id)),); + } + tail->segment_id = segment_id; + tail->status = waiting; + tail = ftape_next_buffer(ft_queue_tail); + } + /* Start tape only if all buffers full or flush mode. + * This will give higher probability of streaming. + */ + if (ft_runner_status != running && + ((tail->status == waiting && + ftape_get_buffer(ft_queue_head) == tail) || + write_mode != FT_WR_ASYNC)) { + TRACE_CATCH(ftape_start_writing(write_mode),); + } + TRACE_EXIT 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 ftape_write_segment(const int segment_id, + const void *buffer, + const ft_write_mode_t flush) +{ + int retry = 0; + int result; + TRACE_FUN(ft_t_flow); + + ft_history.used |= 2; + if (segment_id >= ft_tracks_per_tape*ft_segments_per_track) { + /* tape full */ + TRACE_ABORT(-ENOSPC, ft_t_err, + "invalid segment id: %d (max %d)", + segment_id, + ft_tracks_per_tape * ft_segments_per_track -1); + } + for (;;) { + if ((result = write_segment(segment_id, buffer, flush)) >= 0) { + if (result == 0) { /* empty segment */ + TRACE(ft_t_noise, + "empty segment, nothing written"); + } + TRACE_EXIT result; + } + if (result == -EAGAIN) { + if (++retry > 100) { /* give up */ + TRACE_ABORT(-EIO, ft_t_err, + "write failed, >100 retries in segment"); + } + TRACE(ft_t_warn, "write error, retry %d (%d)", + retry, + ftape_get_buffer(ft_queue_tail)->segment_id); + } else { + TRACE_ABORT(result, ft_t_err, + "write_segment failed, error: %d", result); + } + /* Allow escape from loop when signaled ! + */ + FT_SIGNAL_EXIT(_DONT_BLOCK); + } +} diff --git a/drivers/char/ftape/lowlevel/ftape-write.h b/drivers/char/ftape/lowlevel/ftape-write.h new file mode 100644 index 000000000000..0e7f898b7af9 --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-write.h @@ -0,0 +1,53 @@ +#ifndef _FTAPE_WRITE_H +#define _FTAPE_WRITE_H + +/* + * Copyright (C) 1994-1995 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-write.h,v $ + $Author: claus $ + * + $Revision: 1.2 $ + $Date: 1997/10/05 19:18:30 $ + $State: Exp $ + * + * This file contains the definitions for the write functions + * for the QIC-117 floppy-tape driver for Linux. + * + */ + + +/* ftape-write.c defined global functions. + */ +typedef enum { + FT_WR_ASYNC = 0, /* start tape only when all buffers are full */ + FT_WR_MULTI = 1, /* start tape, but don't necessarily stop */ + FT_WR_SINGLE = 2, /* write a single segment and stop afterwards */ + FT_WR_DELETE = 3 /* write deleted data marks */ +} ft_write_mode_t; + +extern int ftape_start_writing(const ft_write_mode_t mode); +extern int ftape_write_segment(const int segment, + const void *address, + const ft_write_mode_t flushing); +extern void ftape_zap_write_buffers(void); +extern int ftape_loop_until_writes_done(void); + +#endif /* _FTAPE_WRITE_H */ + diff --git a/drivers/char/ftape/lowlevel/ftape_syms.c b/drivers/char/ftape/lowlevel/ftape_syms.c new file mode 100644 index 000000000000..5dc3a380c9bf --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape_syms.c @@ -0,0 +1,88 @@ +/* + * Copyright (C) 1996-1997 Claus-Justus Heine + + 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape_syms.c,v $ + * $Revision: 1.4 $ + * $Date: 1997/10/17 00:03:51 $ + * + * This file contains the symbols that the ftape low level + * part of the QIC-40/80/3010/3020 floppy-tape driver "ftape" + * exports to its high level clients + */ + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/ftape.h> +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/ftape-init.h" +#include "../lowlevel/fdc-io.h" +#include "../lowlevel/ftape-read.h" +#include "../lowlevel/ftape-write.h" +#include "../lowlevel/ftape-io.h" +#include "../lowlevel/ftape-ctl.h" +#include "../lowlevel/ftape-rw.h" +#include "../lowlevel/ftape-bsm.h" +#include "../lowlevel/ftape-buffer.h" +#include "../lowlevel/ftape-format.h" + +/* bad sector handling from ftape-bsm.c */ +EXPORT_SYMBOL(ftape_get_bad_sector_entry); +EXPORT_SYMBOL(ftape_find_end_of_bsm_list); +/* from ftape-rw.c */ +EXPORT_SYMBOL(ftape_set_state); +/* from ftape-ctl.c */ +EXPORT_SYMBOL(ftape_seek_to_bot); +EXPORT_SYMBOL(ftape_seek_to_eot); +EXPORT_SYMBOL(ftape_abort_operation); +EXPORT_SYMBOL(ftape_get_status); +EXPORT_SYMBOL(ftape_enable); +EXPORT_SYMBOL(ftape_disable); +EXPORT_SYMBOL(ftape_mmap); +EXPORT_SYMBOL(ftape_calibrate_data_rate); +/* from ftape-io.c */ +EXPORT_SYMBOL(ftape_reset_drive); +EXPORT_SYMBOL(ftape_command); +EXPORT_SYMBOL(ftape_parameter); +EXPORT_SYMBOL(ftape_ready_wait); +EXPORT_SYMBOL(ftape_report_operation); +EXPORT_SYMBOL(ftape_report_error); +/* from ftape-read.c */ +EXPORT_SYMBOL(ftape_read_segment_fraction); +EXPORT_SYMBOL(ftape_zap_read_buffers); +EXPORT_SYMBOL(ftape_read_header_segment); +EXPORT_SYMBOL(ftape_decode_header_segment); +/* from ftape-write.c */ +EXPORT_SYMBOL(ftape_write_segment); +EXPORT_SYMBOL(ftape_start_writing); +EXPORT_SYMBOL(ftape_loop_until_writes_done); +/* from ftape-buffer.h */ +EXPORT_SYMBOL(ftape_set_nr_buffers); +/* from ftape-format.h */ +EXPORT_SYMBOL(ftape_format_track); +EXPORT_SYMBOL(ftape_format_status); +EXPORT_SYMBOL(ftape_verify_segment); +/* from tracing.c */ +#ifndef CONFIG_FT_NO_TRACE_AT_ALL +EXPORT_SYMBOL(ftape_tracing); +EXPORT_SYMBOL(ftape_function_nest_level); +EXPORT_SYMBOL(ftape_trace_call); +EXPORT_SYMBOL(ftape_trace_exit); +EXPORT_SYMBOL(ftape_trace_log); +#endif + diff --git a/drivers/char/ftape/zftape/Makefile b/drivers/char/ftape/zftape/Makefile new file mode 100644 index 000000000000..6d91c1f77c05 --- /dev/null +++ b/drivers/char/ftape/zftape/Makefile @@ -0,0 +1,36 @@ +# +# Copyright (C) 1996, 1997 Claus-Justus Heine. +# +# 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: /homes/cvs/ftape-stacked/ftape/zftape/Makefile,v $ +# $Revision: 1.4 $ +# $Date: 1997/10/05 19:18:58 $ +# +# Makefile for the QIC-40/80/3010/3020 zftape interface VFS to +# ftape +# + + +# ZFT_OBSOLETE - enable the MTIOC_ZFTAPE_GETBLKSZ ioctl. You should +# leave this enabled for compatibility with taper. + +obj-$(CONFIG_ZFTAPE) += zftape.o + +zftape-objs := zftape-rw.o zftape-ctl.o zftape-read.o \ + zftape-write.o zftape-vtbl.o zftape-eof.o \ + zftape-init.o zftape-buffers.o zftape_syms.o + +EXTRA_CFLAGS := -DZFT_OBSOLETE diff --git a/drivers/char/ftape/zftape/zftape-buffers.c b/drivers/char/ftape/zftape/zftape-buffers.c new file mode 100644 index 000000000000..da06f138334e --- /dev/null +++ b/drivers/char/ftape/zftape/zftape-buffers.c @@ -0,0 +1,149 @@ +/* + * Copyright (C) 1995-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/zftape/zftape-buffers.c,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:18:59 $ + * + * This file contains the dynamic buffer allocation routines + * of zftape + */ + +#include <linux/errno.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include <linux/zftape.h> + +#include <linux/vmalloc.h> + +#include "../zftape/zftape-init.h" +#include "../zftape/zftape-eof.h" +#include "../zftape/zftape-ctl.h" +#include "../zftape/zftape-write.h" +#include "../zftape/zftape-read.h" +#include "../zftape/zftape-rw.h" +#include "../zftape/zftape-vtbl.h" + +/* global variables + */ + +/* local varibales + */ +static unsigned int used_memory; +static unsigned int peak_memory; + +void zft_memory_stats(void) +{ + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_noise, "Memory usage (vmalloc allocations):\n" + KERN_INFO "total allocated: %d\n" + KERN_INFO "peak allocation: %d", + used_memory, peak_memory); + peak_memory = used_memory; + TRACE_EXIT; +} + +int zft_vcalloc_once(void *new, size_t size) +{ + TRACE_FUN(ft_t_flow); + if (zft_vmalloc_once(new, size) < 0) { + TRACE_EXIT -ENOMEM; + } + memset(*(void **)new, '\0', size); + TRACE_EXIT 0; +} +int zft_vmalloc_once(void *new, size_t size) +{ + TRACE_FUN(ft_t_flow); + + if (*(void **)new != NULL || size == 0) { + TRACE_EXIT 0; + } + if ((*(void **)new = vmalloc(size)) == NULL) { + TRACE_EXIT -ENOMEM; + } + used_memory += size; + if (peak_memory < used_memory) { + peak_memory = used_memory; + } + TRACE_ABORT(0, ft_t_noise, + "allocated buffer @ %p, %d bytes", *(void **)new, size); +} +int zft_vmalloc_always(void *new, size_t size) +{ + TRACE_FUN(ft_t_flow); + + zft_vfree(new, size); + TRACE_EXIT zft_vmalloc_once(new, size); +} +void zft_vfree(void *old, size_t size) +{ + TRACE_FUN(ft_t_flow); + + if (*(void **)old) { + vfree(*(void **)old); + used_memory -= size; + TRACE(ft_t_noise, "released buffer @ %p, %d bytes", + *(void **)old, size); + *(void **)old = NULL; + } + TRACE_EXIT; +} + +void *zft_kmalloc(size_t size) +{ + void *new; + + while ((new = kmalloc(size, GFP_KERNEL)) == NULL) { + msleep_interruptible(100); + } + memset(new, 0, size); + used_memory += size; + if (peak_memory < used_memory) { + peak_memory = used_memory; + } + return new; +} + +void zft_kfree(void *old, size_t size) +{ + kfree(old); + used_memory -= size; +} + +/* there are some more buffers that are allocated on demand. + * cleanup_module() calles this function to be sure to have released + * them + */ +void zft_uninit_mem(void) +{ + TRACE_FUN(ft_t_flow); + + zft_vfree(&zft_hseg_buf, FT_SEGMENT_SIZE); + zft_vfree(&zft_deblock_buf, FT_SEGMENT_SIZE); zft_deblock_segment = -1; + zft_free_vtbl(); + if (zft_cmpr_lock(0 /* don't load */) == 0) { + (*zft_cmpr_ops->cleanup)(); + (*zft_cmpr_ops->reset)(); /* unlock it again */ + } + zft_memory_stats(); + TRACE_EXIT; +} diff --git a/drivers/char/ftape/zftape/zftape-buffers.h b/drivers/char/ftape/zftape/zftape-buffers.h new file mode 100644 index 000000000000..798e3128c682 --- /dev/null +++ b/drivers/char/ftape/zftape/zftape-buffers.h @@ -0,0 +1,55 @@ +#ifndef _FTAPE_DYNMEM_H +#define _FTAPE_DYNMEM_H + +/* + * Copyright (C) 1995-1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/zftape/zftape-buffers.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:18:59 $ + * + * memory allocation routines. + * + */ + +/* we do not allocate all of the really large buffer memory before + * someone tries to open the drive. ftape_open() may fail with + * -ENOMEM, but that's better having 200k of vmalloced memory which + * cannot be swapped out. + */ + +extern void zft_memory_stats(void); +extern int zft_vmalloc_once(void *new, size_t size); +extern int zft_vcalloc_once(void *new, size_t size); +extern int zft_vmalloc_always(void *new, size_t size); +extern void zft_vfree(void *old, size_t size); +extern void *zft_kmalloc(size_t size); +extern void zft_kfree(void *old, size_t size); + +/* called by cleanup_module() + */ +extern void zft_uninit_mem(void); + +#endif + + + + + + + diff --git a/drivers/char/ftape/zftape/zftape-ctl.c b/drivers/char/ftape/zftape/zftape-ctl.c new file mode 100644 index 000000000000..6c7874e5c199 --- /dev/null +++ b/drivers/char/ftape/zftape/zftape-ctl.c @@ -0,0 +1,1418 @@ +/* + * Copyright (C) 1996, 1997 Claus-Justus Heine + + 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: /homes/cvs/ftape-stacked/ftape/zftape/zftape-ctl.c,v $ + * $Revision: 1.2.6.2 $ + * $Date: 1997/11/14 18:07:33 $ + * + * This file contains the non-read/write zftape functions + * for the QIC-40/80/3010/3020 floppy-tape driver for Linux. + */ + +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/fcntl.h> + +#include <linux/zftape.h> + +#include <asm/uaccess.h> + +#include "../zftape/zftape-init.h" +#include "../zftape/zftape-eof.h" +#include "../zftape/zftape-ctl.h" +#include "../zftape/zftape-write.h" +#include "../zftape/zftape-read.h" +#include "../zftape/zftape-rw.h" +#include "../zftape/zftape-vtbl.h" + +/* Global vars. + */ +int zft_write_protected; /* this is when cartridge rdonly or O_RDONLY */ +int zft_header_read; +int zft_offline; +unsigned int zft_unit; +int zft_resid; +int zft_mt_compression; + +/* Local vars. + */ +static int going_offline; + +typedef int (mt_fun)(int *argptr); +typedef int (*mt_funp)(int *argptr); +typedef struct +{ + mt_funp function; + unsigned offline : 1; /* op permitted if offline or no_tape */ + unsigned write_protected : 1; /* op permitted if write-protected */ + unsigned not_formatted : 1; /* op permitted if tape not formatted */ + unsigned raw_mode : 1; /* op permitted if zft_mode == 0 */ + unsigned need_idle_state : 1; /* need to call def_idle_state */ + char *name; +} fun_entry; + +static mt_fun mt_dummy, mt_reset, mt_fsr, mt_bsr, mt_rew, mt_offl, mt_nop, + mt_weof, mt_erase, mt_ras2, mt_setblk, mt_setdensity, + mt_seek, mt_tell, mt_reten, mt_eom, mt_fsf, mt_bsf, + mt_fsfm, mt_bsfm, mt_setdrvbuffer, mt_compression; + +static fun_entry mt_funs[]= +{ + {mt_reset , 1, 1, 1, 1, 0, "MT_RESET" }, /* 0 */ + {mt_fsf , 0, 1, 0, 0, 1, "MT_FSF" }, + {mt_bsf , 0, 1, 0, 0, 1, "MT_BSF" }, + {mt_fsr , 0, 1, 0, 1, 1, "MT_FSR" }, + {mt_bsr , 0, 1, 0, 1, 1, "MT_BSR" }, + {mt_weof , 0, 0, 0, 0, 0, "MT_WEOF" }, /* 5 */ + {mt_rew , 0, 1, 1, 1, 0, "MT_REW" }, + {mt_offl , 0, 1, 1, 1, 0, "MT_OFFL" }, + {mt_nop , 1, 1, 1, 1, 0, "MT_NOP" }, + {mt_reten , 0, 1, 1, 1, 0, "MT_RETEN" }, + {mt_bsfm , 0, 1, 0, 0, 1, "MT_BSFM" }, /* 10 */ + {mt_fsfm , 0, 1, 0, 0, 1, "MT_FSFM" }, + {mt_eom , 0, 1, 0, 0, 1, "MT_EOM" }, + {mt_erase , 0, 0, 0, 1, 0, "MT_ERASE" }, + {mt_dummy , 1, 1, 1, 1, 0, "MT_RAS1" }, + {mt_ras2 , 0, 0, 0, 1, 0, "MT_RAS2" }, + {mt_dummy , 1, 1, 1, 1, 0, "MT_RAS3" }, + {mt_dummy , 1, 1, 1, 1, 0, "UNKNOWN" }, + {mt_dummy , 1, 1, 1, 1, 0, "UNKNOWN" }, + {mt_dummy , 1, 1, 1, 1, 0, "UNKNOWN" }, + {mt_setblk , 1, 1, 1, 1, 1, "MT_SETBLK"}, /* 20 */ + {mt_setdensity , 1, 1, 1, 1, 0, "MT_SETDENSITY"}, + {mt_seek , 0, 1, 0, 1, 1, "MT_SEEK" }, + {mt_dummy , 0, 1, 0, 1, 1, "MT_TELL" }, /* wr-only ?! */ + {mt_setdrvbuffer, 1, 1, 1, 1, 0, "MT_SETDRVBUFFER" }, + {mt_dummy , 1, 1, 1, 1, 0, "MT_FSS" }, /* 25 */ + {mt_dummy , 1, 1, 1, 1, 0, "MT_BSS" }, + {mt_dummy , 1, 1, 1, 1, 0, "MT_WSM" }, + {mt_dummy , 1, 1, 1, 1, 0, "MT_LOCK" }, + {mt_dummy , 1, 1, 1, 1, 0, "MT_UNLOCK"}, + {mt_dummy , 1, 1, 1, 1, 0, "MT_LOAD" }, /* 30 */ + {mt_dummy , 1, 1, 1, 1, 0, "MT_UNLOAD"}, + {mt_compression , 1, 1, 1, 0, 1, "MT_COMPRESSION"}, + {mt_dummy , 1, 1, 1, 1, 0, "MT_SETPART"}, + {mt_dummy , 1, 1, 1, 1, 0, "MT_MKPART"} +}; + +#define NR_MT_CMDS NR_ITEMS(mt_funs) + +void zft_reset_position(zft_position *pos) +{ + TRACE_FUN(ft_t_flow); + + pos->seg_byte_pos = + pos->volume_pos = 0; + if (zft_header_read) { + /* need to keep track of the volume table and + * compression map. We therefor simply + * position at the beginning of the first + * volume. This covers old ftape archives as + * well has various flavours of the + * compression map segments. The worst case is + * that the compression map shows up as a + * additional volume in front of all others. + */ + pos->seg_pos = zft_find_volume(0)->start_seg; + pos->tape_pos = zft_calc_tape_pos(pos->seg_pos); + } else { + pos->tape_pos = 0; + pos->seg_pos = -1; + } + zft_just_before_eof = 0; + zft_deblock_segment = -1; + zft_io_state = zft_idle; + zft_zap_read_buffers(); + zft_prevent_flush(); + /* unlock the compresison module if it is loaded. + * The zero arg means not to try to load the module. + */ + if (zft_cmpr_lock(0) == 0) { + (*zft_cmpr_ops->reset)(); /* unlock */ + } + TRACE_EXIT; +} + +static void zft_init_driver(void) +{ + TRACE_FUN(ft_t_flow); + + zft_resid = + zft_header_read = + zft_old_ftape = + zft_offline = + zft_write_protected = + going_offline = + zft_mt_compression = + zft_header_changed = + zft_volume_table_changed = + zft_written_segments = 0; + zft_blk_sz = CONFIG_ZFT_DFLT_BLK_SZ; + zft_reset_position(&zft_pos); /* does most of the stuff */ + ftape_zap_read_buffers(); + ftape_set_state(idle); + TRACE_EXIT; +} + +int zft_def_idle_state(void) +{ + int result = 0; + TRACE_FUN(ft_t_flow); + + if (!zft_header_read) { + result = zft_read_header_segments(); + } else if ((result = zft_flush_buffers()) >= 0 && zft_qic_mode) { + /* don't move past eof + */ + (void)zft_close_volume(&zft_pos); + } + if (ftape_abort_operation() < 0) { + TRACE(ft_t_warn, "ftape_abort_operation() failed"); + result = -EIO; + } + /* clear remaining read buffers */ + zft_zap_read_buffers(); + zft_io_state = zft_idle; + TRACE_EXIT result; +} + +/***************************************************************************** + * * + * functions for the MTIOCTOP commands * + * * + *****************************************************************************/ + +static int mt_dummy(int *dummy) +{ + TRACE_FUN(ft_t_flow); + + TRACE_EXIT -ENOSYS; +} + +static int mt_reset(int *dummy) +{ + TRACE_FUN(ft_t_flow); + + (void)ftape_seek_to_bot(); + TRACE_CATCH(ftape_reset_drive(), + zft_init_driver(); zft_uninit_mem(); zft_offline = 1); + /* fake a re-open of the device. This will set all flage and + * allocate buffers as appropriate. The new tape condition will + * force the open routine to do anything we need. + */ + TRACE_CATCH(_zft_open(-1 /* fake reopen */, 0 /* dummy */),); + TRACE_EXIT 0; +} + +static int mt_fsf(int *arg) +{ + int result; + TRACE_FUN(ft_t_flow); + + result = zft_skip_volumes(*arg, &zft_pos); + zft_just_before_eof = 0; + TRACE_EXIT result; +} + +static int mt_bsf(int *arg) +{ + int result = 0; + TRACE_FUN(ft_t_flow); + + if (*arg != 0) { + result = zft_skip_volumes(-*arg + 1, &zft_pos); + } + TRACE_EXIT result; +} + +static int seek_block(__s64 data_offset, + __s64 block_increment, + zft_position *pos) +{ + int result = 0; + __s64 new_block_pos; + __s64 vol_block_count; + const zft_volinfo *volume; + int exceed; + TRACE_FUN(ft_t_flow); + + volume = zft_find_volume(pos->seg_pos); + if (volume->start_seg == 0 || volume->end_seg == 0) { + TRACE_EXIT -EIO; + } + new_block_pos = (zft_div_blksz(data_offset, volume->blk_sz) + + block_increment); + vol_block_count = zft_div_blksz(volume->size, volume->blk_sz); + if (new_block_pos < 0) { + TRACE(ft_t_noise, + "new_block_pos " LL_X " < 0", LL(new_block_pos)); + zft_resid = (int)new_block_pos; + new_block_pos = 0; + exceed = 1; + } else if (new_block_pos > vol_block_count) { + TRACE(ft_t_noise, + "new_block_pos " LL_X " exceeds size of volume " LL_X, + LL(new_block_pos), LL(vol_block_count)); + zft_resid = (int)(vol_block_count - new_block_pos); + new_block_pos = vol_block_count; + exceed = 1; + } else { + exceed = 0; + } + if (zft_use_compression && volume->use_compression) { + TRACE_CATCH(zft_cmpr_lock(1 /* try to load */),); + result = (*zft_cmpr_ops->seek)(new_block_pos, pos, volume, + zft_deblock_buf); + pos->tape_pos = zft_calc_tape_pos(pos->seg_pos); + pos->tape_pos += pos->seg_byte_pos; + } else { + pos->volume_pos = zft_mul_blksz(new_block_pos, volume->blk_sz); + pos->tape_pos = zft_calc_tape_pos(volume->start_seg); + pos->tape_pos += pos->volume_pos; + pos->seg_pos = zft_calc_seg_byte_coord(&pos->seg_byte_pos, + pos->tape_pos); + } + zft_just_before_eof = volume->size == pos->volume_pos; + if (zft_just_before_eof) { + /* why this? because zft_file_no checks agains start + * and end segment of a volume. We do not want to + * advance to the next volume with this function. + */ + TRACE(ft_t_noise, "set zft_just_before_eof"); + zft_position_before_eof(pos, volume); + } + TRACE(ft_t_noise, "\n" + KERN_INFO "new_seg_pos : %d\n" + KERN_INFO "new_tape_pos: " LL_X "\n" + KERN_INFO "vol_size : " LL_X "\n" + KERN_INFO "seg_byte_pos: %d\n" + KERN_INFO "blk_sz : %d", + pos->seg_pos, LL(pos->tape_pos), + LL(volume->size), pos->seg_byte_pos, + volume->blk_sz); + if (!exceed) { + zft_resid = new_block_pos - zft_div_blksz(pos->volume_pos, + volume->blk_sz); + } + if (zft_resid < 0) { + zft_resid = -zft_resid; + } + TRACE_EXIT ((exceed || zft_resid != 0) && result >= 0) ? -EINVAL : result; +} + +static int mt_fsr(int *arg) +{ + int result; + TRACE_FUN(ft_t_flow); + + result = seek_block(zft_pos.volume_pos, *arg, &zft_pos); + TRACE_EXIT result; +} + +static int mt_bsr(int *arg) +{ + int result; + TRACE_FUN(ft_t_flow); + + result = seek_block(zft_pos.volume_pos, -*arg, &zft_pos); + TRACE_EXIT result; +} + +static int mt_weof(int *arg) +{ + int result; + TRACE_FUN(ft_t_flow); + + TRACE_CATCH(zft_flush_buffers(),); + result = zft_weof(*arg, &zft_pos); + TRACE_EXIT result; +} + +static int mt_rew(int *dummy) +{ + int result; + TRACE_FUN(ft_t_flow); + + if(zft_header_read) { + (void)zft_def_idle_state(); + } + result = ftape_seek_to_bot(); + zft_reset_position(&zft_pos); + TRACE_EXIT result; +} + +static int mt_offl(int *dummy) +{ + int result; + TRACE_FUN(ft_t_flow); + + going_offline= 1; + result = mt_rew(NULL); + TRACE_EXIT result; +} + +static int mt_nop(int *dummy) +{ + TRACE_FUN(ft_t_flow); + /* should we set tape status? + */ + if (!zft_offline) { /* offline includes no_tape */ + (void)zft_def_idle_state(); + } + TRACE_EXIT 0; +} + +static int mt_reten(int *dummy) +{ + int result; + TRACE_FUN(ft_t_flow); + + if(zft_header_read) { + (void)zft_def_idle_state(); + } + result = ftape_seek_to_eot(); + if (result >= 0) { + result = ftape_seek_to_bot(); + } + TRACE_EXIT(result); +} + +static int fsfbsfm(int arg, zft_position *pos) +{ + const zft_volinfo *vtbl; + __s64 block_pos; + TRACE_FUN(ft_t_flow); + + /* What to do? This should seek to the next file-mark and + * position BEFORE. That is, a next write would just extend + * the current file. Well. Let's just seek to the end of the + * current file, if count == 1. If count > 1, then do a + * "mt_fsf(count - 1)", and then seek to the end of that file. + * If count == 0, do nothing + */ + if (arg == 0) { + TRACE_EXIT 0; + } + zft_just_before_eof = 0; + TRACE_CATCH(zft_skip_volumes(arg < 0 ? arg : arg-1, pos), + if (arg > 0) { + zft_resid ++; + }); + vtbl = zft_find_volume(pos->seg_pos); + block_pos = zft_div_blksz(vtbl->size, vtbl->blk_sz); + (void)seek_block(0, block_pos, pos); + if (pos->volume_pos != vtbl->size) { + zft_just_before_eof = 0; + zft_resid = 1; + /* we didn't managed to go there */ + TRACE_ABORT(-EIO, ft_t_err, + "wanted file position " LL_X ", arrived at " LL_X, + LL(vtbl->size), LL(pos->volume_pos)); + } + zft_just_before_eof = 1; + TRACE_EXIT 0; +} + +static int mt_bsfm(int *arg) +{ + int result; + TRACE_FUN(ft_t_flow); + + result = fsfbsfm(-*arg, &zft_pos); + TRACE_EXIT result; +} + +static int mt_fsfm(int *arg) +{ + int result; + TRACE_FUN(ft_t_flow); + + result = fsfbsfm(*arg, &zft_pos); + TRACE_EXIT result; +} + +static int mt_eom(int *dummy) +{ + TRACE_FUN(ft_t_flow); + + zft_skip_to_eom(&zft_pos); + TRACE_EXIT 0; +} + +static int mt_erase(int *dummy) +{ + int result; + TRACE_FUN(ft_t_flow); + + result = zft_erase(); + TRACE_EXIT result; +} + +static int mt_ras2(int *dummy) +{ + int result; + TRACE_FUN(ft_t_flow); + + result = -ENOSYS; + TRACE_EXIT result; +} + +/* Sets the new blocksize in BYTES + * + */ +static int mt_setblk(int *new_size) +{ + TRACE_FUN(ft_t_flow); + + if((unsigned int)(*new_size) > ZFT_MAX_BLK_SZ) { + TRACE_ABORT(-EINVAL, ft_t_info, + "desired blk_sz (%d) should be <= %d bytes", + *new_size, ZFT_MAX_BLK_SZ); + } + if ((*new_size & (FT_SECTOR_SIZE-1)) != 0) { + TRACE_ABORT(-EINVAL, ft_t_info, + "desired blk_sz (%d) must be a multiple of %d bytes", + *new_size, FT_SECTOR_SIZE); + } + if (*new_size == 0) { + if (zft_use_compression) { + TRACE_ABORT(-EINVAL, ft_t_info, + "Variable block size not yet " + "supported with compression"); + } + *new_size = 1; + } + zft_blk_sz = *new_size; + TRACE_EXIT 0; +} + +static int mt_setdensity(int *arg) +{ + TRACE_FUN(ft_t_flow); + + SET_TRACE_LEVEL(*arg); + TRACE(TRACE_LEVEL, "tracing set to %d", TRACE_LEVEL); + if ((int)TRACE_LEVEL != *arg) { + TRACE_EXIT -EINVAL; + } + TRACE_EXIT 0; +} + +static int mt_seek(int *new_block_pos) +{ + int result= 0; + TRACE_FUN(ft_t_any); + + result = seek_block(0, (__s64)*new_block_pos, &zft_pos); + TRACE_EXIT result; +} + +/* OK, this is totally different from SCSI, but the worst thing that can + * happen is that there is not enough defragmentated memory that can be + * allocated. Also, there is a hardwired limit of 16 dma buffers in the + * stock ftape module. This shouldn't bring the system down. + * + * NOTE: the argument specifies the total number of dma buffers to use. + * The driver needs at least 3 buffers to function at all. + * + */ +static int mt_setdrvbuffer(int *cnt) +{ + TRACE_FUN(ft_t_flow); + + if (*cnt < 3) { + TRACE_EXIT -EINVAL; + } + TRACE_CATCH(ftape_set_nr_buffers(*cnt),); + TRACE_EXIT 0; +} +/* return the block position from start of volume + */ +static int mt_tell(int *arg) +{ + TRACE_FUN(ft_t_flow); + + *arg = zft_div_blksz(zft_pos.volume_pos, + zft_find_volume(zft_pos.seg_pos)->blk_sz); + TRACE_EXIT 0; +} + +static int mt_compression(int *arg) +{ + TRACE_FUN(ft_t_flow); + + /* Ok. We could also check whether compression is available at + * all by trying to load the compression module. We could + * also check for a block size of 1 byte which is illegal + * with compression. Instead of doing it here we rely on + * zftape_write() to do the proper checks. + */ + if ((unsigned int)*arg > 1) { + TRACE_EXIT -EINVAL; + } + if (*arg != 0 && zft_blk_sz == 1) { /* variable block size */ + TRACE_ABORT(-EINVAL, ft_t_info, + "Compression not yet supported " + "with variable block size"); + } + zft_mt_compression = *arg; + if ((zft_unit & ZFT_ZIP_MODE) == 0) { + zft_use_compression = zft_mt_compression; + } + TRACE_EXIT 0; +} + +/* check whether write access is allowed. Write access is denied when + * + zft_write_protected == 1 -- this accounts for either hard write + * protection of the cartridge or for + * O_RDONLY access mode of the tape device + * + zft_offline == 1 -- this meany that there is either no tape + * or that the MTOFFLINE ioctl has been + * previously issued (`soft eject') + * + ft_formatted == 0 -- this means that the cartridge is not + * formatted + * Then we distinuguish two cases. When zft_qic_mode is TRUE, then we try + * to emulate a `traditional' (aka SCSI like) UN*X tape device. Therefore we + * deny writes when + * + zft_qic_mode ==1 && + * (!zft_tape_at_lbot() && -- tape no at logical BOT + * !(zft_tape_at_eom() || -- tape not at logical EOM (or EOD) + * (zft_tape_at_eom() && + * zft_old_ftape()))) -- we can't add new volume to tapes + * written by old ftape because ftape + * don't use the volume table + * + * when the drive is in true raw mode (aka /dev/rawft0) then we don't + * care about LBOT and EOM conditions. This device is intended for a + * user level program that wants to truly implement the QIC-80 compliance + * at the logical data layout level of the cartridge, i.e. implement all + * that volume table and volume directory stuff etc.< + */ +int zft_check_write_access(zft_position *pos) +{ + TRACE_FUN(ft_t_flow); + + if (zft_offline) { /* offline includes no_tape */ + TRACE_ABORT(-ENXIO, + ft_t_info, "tape is offline or no cartridge"); + } + if (!ft_formatted) { + TRACE_ABORT(-EACCES, ft_t_info, "tape is not formatted"); + } + if (zft_write_protected) { + TRACE_ABORT(-EACCES, ft_t_info, "cartridge write protected"); + } + if (zft_qic_mode) { + /* check BOT condition */ + if (!zft_tape_at_lbot(pos)) { + /* protect cartridges written by old ftape if + * not at BOT because they use the vtbl + * segment for storing data + */ + if (zft_old_ftape) { + TRACE_ABORT(-EACCES, ft_t_warn, + "Cannot write to cartridges written by old ftape when not at BOT"); + } + /* not at BOT, but allow writes at EOD, of course + */ + if (!zft_tape_at_eod(pos)) { + TRACE_ABORT(-EACCES, ft_t_info, + "tape not at BOT and not at EOD"); + } + } + /* fine. Now the tape is either at BOT or at EOD. */ + } + /* or in raw mode in which case we don't care about BOT and EOD */ + TRACE_EXIT 0; +} + +/* OPEN routine called by kernel-interface code + * + * NOTE: this is also called by mt_reset() with dev_minor == -1 + * to fake a reopen after a reset. + */ +int _zft_open(unsigned int dev_minor, unsigned int access_mode) +{ + static unsigned int tape_unit; + static unsigned int file_access_mode; + int result; + TRACE_FUN(ft_t_flow); + + if ((int)dev_minor == -1) { + /* fake reopen */ + zft_unit = tape_unit; + access_mode = file_access_mode; + zft_init_driver(); /* reset all static data to defaults */ + } else { + tape_unit = dev_minor; + file_access_mode = access_mode; + if ((result = ftape_enable(FTAPE_SEL(dev_minor))) < 0) { + TRACE_ABORT(-ENXIO, ft_t_err, + "ftape_enable failed: %d", result); + } + if (ft_new_tape || ft_no_tape || !ft_formatted || + (FTAPE_SEL(zft_unit) != FTAPE_SEL(dev_minor)) || + (zft_unit & ZFT_RAW_MODE) != (dev_minor & ZFT_RAW_MODE)) { + /* reset all static data to defaults, + */ + zft_init_driver(); + } + zft_unit = dev_minor; + } + zft_set_flags(zft_unit); /* decode the minor bits */ + if (zft_blk_sz == 1 && zft_use_compression) { + ftape_disable(); /* resets ft_no_tape */ + TRACE_ABORT(-ENODEV, ft_t_warn, "Variable block size not yet " + "supported with compression"); + } + /* no need for most of the buffers when no tape or not + * formatted. for the read/write operations, it is the + * regardless whether there is no tape, a not-formatted tape + * or the whether the driver is soft offline. + * Nevertheless we allow some ioctls with non-formatted tapes, + * like rewind and reset. + */ + if (ft_no_tape || !ft_formatted) { + zft_uninit_mem(); + } + if (ft_no_tape) { + zft_offline = 1; /* so we need not test two variables */ + } + if ((access_mode == O_WRONLY || access_mode == O_RDWR) && + (ft_write_protected || ft_no_tape)) { + ftape_disable(); /* resets ft_no_tape */ + TRACE_ABORT(ft_no_tape ? -ENXIO : -EROFS, + ft_t_warn, "wrong access mode %s cartridge", + ft_no_tape ? "without a" : "with write protected"); + } + zft_write_protected = (access_mode == O_RDONLY || + ft_write_protected != 0); + if (zft_write_protected) { + TRACE(ft_t_noise, + "read only access mode: %d, " + "drive write protected: %d", + access_mode == O_RDONLY, + ft_write_protected != 0); + } + if (!zft_offline) { + TRACE_CATCH(zft_vmalloc_once(&zft_deblock_buf,FT_SEGMENT_SIZE), + ftape_disable()); + } + /* zft_seg_pos should be greater than the vtbl segpos but not + * if in compatibility mode and only after we read in the + * header segments + * + * might also be a problem if the user makes a backup with a + * *qft* device and rewinds it with a raw device. + */ + if (zft_qic_mode && + !zft_old_ftape && + zft_pos.seg_pos >= 0 && + zft_header_read && + zft_pos.seg_pos <= ft_first_data_segment) { + TRACE(ft_t_noise, "you probably mixed up the zftape devices!"); + zft_reset_position(&zft_pos); + } + TRACE_EXIT 0; +} + +/* RELEASE routine called by kernel-interface code + */ +int _zft_close(void) +{ + int result = 0; + TRACE_FUN(ft_t_flow); + + if (zft_offline) { + /* call the hardware release routine. Puts the drive offline */ + ftape_disable(); + TRACE_EXIT 0; + } + if (!(ft_write_protected || zft_old_ftape)) { + result = zft_flush_buffers(); + TRACE(ft_t_noise, "writing file mark at current position"); + if (zft_qic_mode && zft_close_volume(&zft_pos) == 0) { + zft_move_past_eof(&zft_pos); + } + if ((zft_tape_at_lbot(&zft_pos) || + !(zft_unit & FTAPE_NO_REWIND))) { + if (result >= 0) { + result = zft_update_header_segments(); + } else { + TRACE(ft_t_err, + "Error: unable to update header segments"); + } + } + } + ftape_abort_operation(); + if (!(zft_unit & FTAPE_NO_REWIND)) { + TRACE(ft_t_noise, "rewinding tape"); + if (ftape_seek_to_bot() < 0 && result >= 0) { + result = -EIO; /* keep old value */ + } + zft_reset_position(&zft_pos); + } + zft_zap_read_buffers(); + /* now free up memory as much as possible. We don't destroy + * the deblock buffer if it containes a valid segment. + */ + if (zft_deblock_segment == -1) { + zft_vfree(&zft_deblock_buf, FT_SEGMENT_SIZE); + } + /* high level driver status, forces creation of a new volume + * when calling ftape_write again and not zft_just_before_eof + */ + zft_io_state = zft_idle; + if (going_offline) { + zft_init_driver(); + zft_uninit_mem(); + going_offline = 0; + zft_offline = 1; + } else if (zft_cmpr_lock(0 /* don't load */) == 0) { + (*zft_cmpr_ops->reset)(); /* unlock it again */ + } + zft_memory_stats(); + /* call the hardware release routine. Puts the drive offline */ + ftape_disable(); + TRACE_EXIT result; +} + +/* + * the wrapper function around the wrapper MTIOCTOP ioctl + */ +static int mtioctop(struct mtop *mtop, int arg_size) +{ + int result = 0; + fun_entry *mt_fun_entry; + TRACE_FUN(ft_t_flow); + + if (arg_size != sizeof(struct mtop) || mtop->mt_op >= NR_MT_CMDS) { + TRACE_EXIT -EINVAL; + } + TRACE(ft_t_noise, "calling MTIOCTOP command: %s", + mt_funs[mtop->mt_op].name); + mt_fun_entry= &mt_funs[mtop->mt_op]; + zft_resid = mtop->mt_count; + if (!mt_fun_entry->offline && zft_offline) { + if (ft_no_tape) { + TRACE_ABORT(-ENXIO, ft_t_info, "no tape present"); + } else { + TRACE_ABORT(-ENXIO, ft_t_info, "drive is offline"); + } + } + if (!mt_fun_entry->not_formatted && !ft_formatted) { + TRACE_ABORT(-EACCES, ft_t_info, "tape is not formatted"); + } + if (!mt_fun_entry->write_protected) { + TRACE_CATCH(zft_check_write_access(&zft_pos),); + } + if (mt_fun_entry->need_idle_state && !(zft_offline || !ft_formatted)) { + TRACE_CATCH(zft_def_idle_state(),); + } + if (!zft_qic_mode && !mt_fun_entry->raw_mode) { + TRACE_ABORT(-EACCES, ft_t_info, +"Drive needs to be in QIC-80 compatibility mode for this command"); + } + result = (mt_fun_entry->function)(&mtop->mt_count); + if (zft_tape_at_lbot(&zft_pos)) { + TRACE_CATCH(zft_update_header_segments(),); + } + if (result >= 0) { + zft_resid = 0; + } + TRACE_EXIT result; +} + +/* + * standard MTIOCGET ioctl + */ +static int mtiocget(struct mtget *mtget, int arg_size) +{ + const zft_volinfo *volume; + __s64 max_tape_pos; + TRACE_FUN(ft_t_flow); + + if (arg_size != sizeof(struct mtget)) { + TRACE_ABORT(-EINVAL, ft_t_info, "bad argument size: %d", + arg_size); + } + mtget->mt_type = ft_drive_type.vendor_id + 0x800000; + mtget->mt_dsreg = ft_last_status.space; + mtget->mt_erreg = ft_last_error.space; /* error register */ + mtget->mt_resid = zft_resid; /* residuum of writes, reads and + * MTIOCTOP commands + */ + if (!zft_offline) { /* neither no_tape nor soft offline */ + mtget->mt_gstat = GMT_ONLINE(~0UL); + /* should rather return the status of the cartridge + * than the access mode of the file, therefor use + * ft_write_protected, not zft_write_protected + */ + if (ft_write_protected) { + mtget->mt_gstat |= GMT_WR_PROT(~0UL); + } + if(zft_header_read) { /* this catches non-formatted */ + volume = zft_find_volume(zft_pos.seg_pos); + mtget->mt_fileno = volume->count; + max_tape_pos = zft_capacity - zft_blk_sz; + if (zft_use_compression) { + max_tape_pos -= ZFT_CMPR_OVERHEAD; + } + if (zft_tape_at_eod(&zft_pos)) { + mtget->mt_gstat |= GMT_EOD(~0UL); + } + if (zft_pos.tape_pos > max_tape_pos) { + mtget->mt_gstat |= GMT_EOT(~0UL); + } + mtget->mt_blkno = zft_div_blksz(zft_pos.volume_pos, + volume->blk_sz); + if (zft_just_before_eof) { + mtget->mt_gstat |= GMT_EOF(~0UL); + } + if (zft_tape_at_lbot(&zft_pos)) { + mtget->mt_gstat |= GMT_BOT(~0UL); + } + } else { + mtget->mt_fileno = mtget->mt_blkno = -1; + if (mtget->mt_dsreg & QIC_STATUS_AT_BOT) { + mtget->mt_gstat |= GMT_BOT(~0UL); + } + } + } else { + if (ft_no_tape) { + mtget->mt_gstat = GMT_DR_OPEN(~0UL); + } else { + mtget->mt_gstat = 0UL; + } + mtget->mt_fileno = mtget->mt_blkno = -1; + } + TRACE_EXIT 0; +} + +#ifdef MTIOCRDFTSEG +/* + * Read a floppy tape segment. This is useful for manipulating the + * volume table, and read the old header segment before re-formatting + * the cartridge. + */ +static int mtiocrdftseg(struct mtftseg * mtftseg, int arg_size) +{ + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCRDFTSEG"); + if (zft_qic_mode) { + TRACE_ABORT(-EACCES, ft_t_info, + "driver needs to be in raw mode for this ioctl"); + } + if (arg_size != sizeof(struct mtftseg)) { + TRACE_ABORT(-EINVAL, ft_t_info, "bad argument size: %d", + arg_size); + } + if (zft_offline) { + TRACE_EXIT -ENXIO; + } + if (mtftseg->mt_mode != FT_RD_SINGLE && + mtftseg->mt_mode != FT_RD_AHEAD) { + TRACE_ABORT(-EINVAL, ft_t_info, "invalid read mode"); + } + if (!ft_formatted) { + TRACE_EXIT -EACCES; /* -ENXIO ? */ + + } + if (!zft_header_read) { + TRACE_CATCH(zft_def_idle_state(),); + } + if (mtftseg->mt_segno > ft_last_data_segment) { + TRACE_ABORT(-EINVAL, ft_t_info, "segment number is too large"); + } + mtftseg->mt_result = ftape_read_segment(mtftseg->mt_segno, + zft_deblock_buf, + mtftseg->mt_mode); + if (mtftseg->mt_result < 0) { + /* a negativ result is not an ioctl error. if + * the user wants to read damaged tapes, + * it's up to her/him + */ + TRACE_EXIT 0; + } + if (copy_to_user(mtftseg->mt_data, + zft_deblock_buf, + mtftseg->mt_result) != 0) { + TRACE_EXIT -EFAULT; + } + TRACE_EXIT 0; +} +#endif + +#ifdef MTIOCWRFTSEG +/* + * write a floppy tape segment. This version features writing of + * deleted address marks, and gracefully ignores the (software) + * ft_formatted flag to support writing of header segments after + * formatting. + */ +static int mtiocwrftseg(struct mtftseg * mtftseg, int arg_size) +{ + int result; + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCWRFTSEG"); + if (zft_write_protected || zft_qic_mode) { + TRACE_EXIT -EACCES; + } + if (arg_size != sizeof(struct mtftseg)) { + TRACE_ABORT(-EINVAL, ft_t_info, "bad argument size: %d", + arg_size); + } + if (zft_offline) { + TRACE_EXIT -ENXIO; + } + if (mtftseg->mt_mode != FT_WR_ASYNC && + mtftseg->mt_mode != FT_WR_MULTI && + mtftseg->mt_mode != FT_WR_SINGLE && + mtftseg->mt_mode != FT_WR_DELETE) { + TRACE_ABORT(-EINVAL, ft_t_info, "invalid write mode"); + } + /* + * We don't check for ft_formatted, because this gives + * only the software status of the driver. + * + * We assume that the user knows what it is + * doing. And rely on the low level stuff to fail + * when the tape isn't formatted. We only make sure + * that The header segment buffer is allocated, + * because it holds the bad sector map. + */ + if (zft_hseg_buf == NULL) { + TRACE_EXIT -ENXIO; + } + if (mtftseg->mt_mode != FT_WR_DELETE) { + if (copy_from_user(zft_deblock_buf, + mtftseg->mt_data, + FT_SEGMENT_SIZE) != 0) { + TRACE_EXIT -EFAULT; + } + } + mtftseg->mt_result = ftape_write_segment(mtftseg->mt_segno, + zft_deblock_buf, + mtftseg->mt_mode); + if (mtftseg->mt_result >= 0 && mtftseg->mt_mode == FT_WR_SINGLE) { + /* + * a negativ result is not an ioctl error. if + * the user wants to write damaged tapes, + * it's up to her/him + */ + if ((result = ftape_loop_until_writes_done()) < 0) { + mtftseg->mt_result = result; + } + } + TRACE_EXIT 0; +} +#endif + +#ifdef MTIOCVOLINFO +/* + * get information about volume positioned at. + */ +static int mtiocvolinfo(struct mtvolinfo *volinfo, int arg_size) +{ + const zft_volinfo *volume; + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCVOLINFO"); + if (arg_size != sizeof(struct mtvolinfo)) { + TRACE_ABORT(-EINVAL, + ft_t_info, "bad argument size: %d", arg_size); + } + if (zft_offline) { + TRACE_EXIT -ENXIO; + } + if (!ft_formatted) { + TRACE_EXIT -EACCES; + } + TRACE_CATCH(zft_def_idle_state(),); + volume = zft_find_volume(zft_pos.seg_pos); + volinfo->mt_volno = volume->count; + volinfo->mt_blksz = volume->blk_sz == 1 ? 0 : volume->blk_sz; + volinfo->mt_size = volume->size >> 10; + volinfo->mt_rawsize = ((zft_calc_tape_pos(volume->end_seg + 1) >> 10) - + (zft_calc_tape_pos(volume->start_seg) >> 10)); + volinfo->mt_cmpr = volume->use_compression; + TRACE_EXIT 0; +} +#endif + +#ifdef ZFT_OBSOLETE +static int mtioc_zftape_getblksz(struct mtblksz *blksz, int arg_size) +{ + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_noise, "\n" + KERN_INFO "Mag tape ioctl command: MTIOC_ZTAPE_GETBLKSZ\n" + KERN_INFO "This ioctl is here merely for compatibility.\n" + KERN_INFO "Please use MTIOCVOLINFO instead"); + if (arg_size != sizeof(struct mtblksz)) { + TRACE_ABORT(-EINVAL, + ft_t_info, "bad argument size: %d", arg_size); + } + if (zft_offline) { + TRACE_EXIT -ENXIO; + } + if (!ft_formatted) { + TRACE_EXIT -EACCES; + } + TRACE_CATCH(zft_def_idle_state(),); + blksz->mt_blksz = zft_find_volume(zft_pos.seg_pos)->blk_sz; + TRACE_EXIT 0; +} +#endif + +#ifdef MTIOCGETSIZE +/* + * get the capacity of the tape cartridge. + */ +static int mtiocgetsize(struct mttapesize *size, int arg_size) +{ + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_noise, "Mag tape ioctl command: MTIOC_ZFTAPE_GETSIZE"); + if (arg_size != sizeof(struct mttapesize)) { + TRACE_ABORT(-EINVAL, + ft_t_info, "bad argument size: %d", arg_size); + } + if (zft_offline) { + TRACE_EXIT -ENXIO; + } + if (!ft_formatted) { + TRACE_EXIT -EACCES; + } + TRACE_CATCH(zft_def_idle_state(),); + size->mt_capacity = (unsigned int)(zft_capacity>>10); + size->mt_used = (unsigned int)(zft_get_eom_pos()>>10); + TRACE_EXIT 0; +} +#endif + +static int mtiocpos(struct mtpos *mtpos, int arg_size) +{ + int result; + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCPOS"); + if (arg_size != sizeof(struct mtpos)) { + TRACE_ABORT(-EINVAL, + ft_t_info, "bad argument size: %d", arg_size); + } + result = mt_tell((int *)&mtpos->mt_blkno); + TRACE_EXIT result; +} + +#ifdef MTIOCFTFORMAT +/* + * formatting of floppy tape cartridges. This is intended to be used + * together with the MTIOCFTCMD ioctl and the new mmap feature + */ + +/* + * This function uses ftape_decode_header_segment() to inform the low + * level ftape module about the new parameters. + * + * It erases the hseg_buf. The calling process must specify all + * parameters to assure proper operation. + * + * return values: -EINVAL - wrong argument size + * -EINVAL - if ftape_decode_header_segment() failed. + */ +static int set_format_parms(struct ftfmtparms *p, __u8 *hseg_buf) +{ + ft_trace_t old_level = TRACE_LEVEL; + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_noise, "MTIOCFTFORMAT operation FTFMT_SETPARMS"); + memset(hseg_buf, 0, FT_SEGMENT_SIZE); + PUT4(hseg_buf, FT_SIGNATURE, FT_HSEG_MAGIC); + + /* fill in user specified parameters + */ + hseg_buf[FT_FMT_CODE] = (__u8)p->ft_fmtcode; + PUT2(hseg_buf, FT_SPT, p->ft_spt); + hseg_buf[FT_TPC] = (__u8)p->ft_tpc; + hseg_buf[FT_FHM] = (__u8)p->ft_fhm; + hseg_buf[FT_FTM] = (__u8)p->ft_ftm; + + /* fill in sane defaults to make ftape happy. + */ + hseg_buf[FT_FSM] = (__u8)128; /* 128 is hard wired all over ftape */ + if (p->ft_fmtcode == fmt_big) { + PUT4(hseg_buf, FT_6_HSEG_1, 0); + PUT4(hseg_buf, FT_6_HSEG_2, 1); + PUT4(hseg_buf, FT_6_FRST_SEG, 2); + PUT4(hseg_buf, FT_6_LAST_SEG, p->ft_spt * p->ft_tpc - 1); + } else { + PUT2(hseg_buf, FT_HSEG_1, 0); + PUT2(hseg_buf, FT_HSEG_2, 1); + PUT2(hseg_buf, FT_FRST_SEG, 2); + PUT2(hseg_buf, FT_LAST_SEG, p->ft_spt * p->ft_tpc - 1); + } + + /* Synchronize with the low level module. This is particularly + * needed for unformatted cartridges as the QIC std was previously + * unknown BUT is needed to set data rate and to calculate timeouts. + */ + TRACE_CATCH(ftape_calibrate_data_rate(p->ft_qicstd&QIC_TAPE_STD_MASK), + _res = -EINVAL); + + /* The following will also recalcualte the timeouts for the tape + * length and QIC std we want to format to. + * abort with -EINVAL rather than -EIO + */ + SET_TRACE_LEVEL(ft_t_warn); + TRACE_CATCH(ftape_decode_header_segment(hseg_buf), + SET_TRACE_LEVEL(old_level); _res = -EINVAL); + SET_TRACE_LEVEL(old_level); + TRACE_EXIT 0; +} + +/* + * Return the internal SOFTWARE status of the kernel driver. This does + * NOT query the tape drive about its status. + */ +static int get_format_parms(struct ftfmtparms *p, __u8 *hseg_buffer) +{ + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_noise, "MTIOCFTFORMAT operation FTFMT_GETPARMS"); + p->ft_qicstd = ft_qic_std; + p->ft_fmtcode = ft_format_code; + p->ft_fhm = hseg_buffer[FT_FHM]; + p->ft_ftm = hseg_buffer[FT_FTM]; + p->ft_spt = ft_segments_per_track; + p->ft_tpc = ft_tracks_per_tape; + TRACE_EXIT 0; +} + +static int mtiocftformat(struct mtftformat *mtftformat, int arg_size) +{ + int result; + union fmt_arg *arg = &mtftformat->fmt_arg; + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCFTFORMAT"); + if (zft_offline) { + if (ft_no_tape) { + TRACE_ABORT(-ENXIO, ft_t_info, "no tape present"); + } else { + TRACE_ABORT(-ENXIO, ft_t_info, "drive is offline"); + } + } + if (zft_qic_mode) { + TRACE_ABORT(-EACCES, ft_t_info, + "driver needs to be in raw mode for this ioctl"); + } + if (zft_hseg_buf == NULL) { + TRACE_CATCH(zft_vcalloc_once(&zft_hseg_buf, FT_SEGMENT_SIZE),); + } + zft_header_read = 0; + switch(mtftformat->fmt_op) { + case FTFMT_SET_PARMS: + TRACE_CATCH(set_format_parms(&arg->fmt_parms, zft_hseg_buf),); + TRACE_EXIT 0; + case FTFMT_GET_PARMS: + TRACE_CATCH(get_format_parms(&arg->fmt_parms, zft_hseg_buf),); + TRACE_EXIT 0; + case FTFMT_FORMAT_TRACK: + if ((ft_formatted && zft_check_write_access(&zft_pos) < 0) || + (!ft_formatted && zft_write_protected)) { + TRACE_ABORT(-EACCES, ft_t_info, "Write access denied"); + } + TRACE_CATCH(ftape_format_track(arg->fmt_track.ft_track, + arg->fmt_track.ft_gap3),); + TRACE_EXIT 0; + case FTFMT_STATUS: + TRACE_CATCH(ftape_format_status(&arg->fmt_status.ft_segment),); + TRACE_EXIT 0; + case FTFMT_VERIFY: + TRACE_CATCH(ftape_verify_segment(arg->fmt_verify.ft_segment, + (SectorMap *)&arg->fmt_verify.ft_bsm),); + TRACE_EXIT 0; + default: + TRACE_ABORT(-EINVAL, ft_t_err, "Invalid format operation"); + } + TRACE_EXIT result; +} +#endif + +#ifdef MTIOCFTCMD +/* + * send a QIC-117 command to the drive, with optional timeouts, + * parameter and result bits. This is intended to be used together + * with the formatting ioctl. + */ +static int mtiocftcmd(struct mtftcmd *ftcmd, int arg_size) +{ + int i; + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCFTCMD"); + if (!capable(CAP_SYS_ADMIN)) { + TRACE_ABORT(-EPERM, ft_t_info, + "need CAP_SYS_ADMIN capability to send raw qic-117 commands"); + } + if (zft_qic_mode) { + TRACE_ABORT(-EACCES, ft_t_info, + "driver needs to be in raw mode for this ioctl"); + } + if (arg_size != sizeof(struct mtftcmd)) { + TRACE_ABORT(-EINVAL, + ft_t_info, "bad argument size: %d", arg_size); + } + if (ftcmd->ft_wait_before) { + TRACE_CATCH(ftape_ready_wait(ftcmd->ft_wait_before, + &ftcmd->ft_status),); + } + if (ftcmd->ft_status & QIC_STATUS_ERROR) + goto ftmtcmd_error; + if (ftcmd->ft_result_bits != 0) { + TRACE_CATCH(ftape_report_operation(&ftcmd->ft_result, + ftcmd->ft_cmd, + ftcmd->ft_result_bits),); + } else { + TRACE_CATCH(ftape_command(ftcmd->ft_cmd),); + if (ftcmd->ft_status & QIC_STATUS_ERROR) + goto ftmtcmd_error; + for (i = 0; i < ftcmd->ft_parm_cnt; i++) { + TRACE_CATCH(ftape_parameter(ftcmd->ft_parms[i]&0x0f),); + if (ftcmd->ft_status & QIC_STATUS_ERROR) + goto ftmtcmd_error; + } + } + if (ftcmd->ft_wait_after != 0) { + TRACE_CATCH(ftape_ready_wait(ftcmd->ft_wait_after, + &ftcmd->ft_status),); + } +ftmtcmd_error: + if (ftcmd->ft_status & QIC_STATUS_ERROR) { + TRACE(ft_t_noise, "error status set"); + TRACE_CATCH(ftape_report_error(&ftcmd->ft_error, + &ftcmd->ft_cmd, 1),); + } + TRACE_EXIT 0; /* this is not an i/o error */ +} +#endif + +/* IOCTL routine called by kernel-interface code + */ +int _zft_ioctl(unsigned int command, void __user * arg) +{ + int result; + union { struct mtop mtop; + struct mtget mtget; + struct mtpos mtpos; +#ifdef MTIOCRDFTSEG + struct mtftseg mtftseg; +#endif +#ifdef MTIOCVOLINFO + struct mtvolinfo mtvolinfo; +#endif +#ifdef MTIOCGETSIZE + struct mttapesize mttapesize; +#endif +#ifdef MTIOCFTFORMAT + struct mtftformat mtftformat; +#endif +#ifdef ZFT_OBSOLETE + struct mtblksz mtblksz; +#endif +#ifdef MTIOCFTCMD + struct mtftcmd mtftcmd; +#endif + } krnl_arg; + int arg_size = _IOC_SIZE(command); + int dir = _IOC_DIR(command); + TRACE_FUN(ft_t_flow); + + /* This check will only catch arguments that are too large ! + */ + if (dir & (_IOC_READ | _IOC_WRITE) && arg_size > sizeof(krnl_arg)) { + TRACE_ABORT(-EINVAL, + ft_t_info, "bad argument size: %d", arg_size); + } + if (dir & _IOC_WRITE) { + if (copy_from_user(&krnl_arg, arg, arg_size) != 0) { + TRACE_EXIT -EFAULT; + } + } + TRACE(ft_t_flow, "called with ioctl command: 0x%08x", command); + switch (command) { + case MTIOCTOP: + result = mtioctop(&krnl_arg.mtop, arg_size); + break; + case MTIOCGET: + result = mtiocget(&krnl_arg.mtget, arg_size); + break; + case MTIOCPOS: + result = mtiocpos(&krnl_arg.mtpos, arg_size); + break; +#ifdef MTIOCVOLINFO + case MTIOCVOLINFO: + result = mtiocvolinfo(&krnl_arg.mtvolinfo, arg_size); + break; +#endif +#ifdef ZFT_OBSOLETE + case MTIOC_ZFTAPE_GETBLKSZ: + result = mtioc_zftape_getblksz(&krnl_arg.mtblksz, arg_size); + break; +#endif +#ifdef MTIOCRDFTSEG + case MTIOCRDFTSEG: /* read a segment via ioctl */ + result = mtiocrdftseg(&krnl_arg.mtftseg, arg_size); + break; +#endif +#ifdef MTIOCWRFTSEG + case MTIOCWRFTSEG: /* write a segment via ioctl */ + result = mtiocwrftseg(&krnl_arg.mtftseg, arg_size); + break; +#endif +#ifdef MTIOCGETSIZE + case MTIOCGETSIZE: + result = mtiocgetsize(&krnl_arg.mttapesize, arg_size); + break; +#endif +#ifdef MTIOCFTFORMAT + case MTIOCFTFORMAT: + result = mtiocftformat(&krnl_arg.mtftformat, arg_size); + break; +#endif +#ifdef MTIOCFTCMD + case MTIOCFTCMD: + result = mtiocftcmd(&krnl_arg.mtftcmd, arg_size); + break; +#endif + default: + result = -EINVAL; + break; + } + if ((result >= 0) && (dir & _IOC_READ)) { + if (copy_to_user(arg, &krnl_arg, arg_size) != 0) { + TRACE_EXIT -EFAULT; + } + } + TRACE_EXIT result; +} diff --git a/drivers/char/ftape/zftape/zftape-ctl.h b/drivers/char/ftape/zftape/zftape-ctl.h new file mode 100644 index 000000000000..414159891990 --- /dev/null +++ b/drivers/char/ftape/zftape/zftape-ctl.h @@ -0,0 +1,59 @@ +#ifndef _ZFTAPE_CTL_H +#define _ZFTAPE_CTL_H + +/* + * Copyright (C) 1996, 1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/zftape/zftape-ctl.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:19:02 $ + * + * This file contains the non-standard IOCTL related definitions + * for the QIC-40/80 floppy-tape driver for Linux. + */ + +#include <linux/config.h> +#include <linux/ioctl.h> +#include <linux/mtio.h> + +#include "../zftape/zftape-rw.h" + +#ifdef CONFIG_ZFTAPE_MODULE +#define ftape_status (*zft_status) +#endif + +extern int zft_offline; +extern int zft_mt_compression; +extern int zft_write_protected; +extern int zft_header_read; +extern unsigned int zft_unit; +extern int zft_resid; + +extern void zft_reset_position(zft_position *pos); +extern int zft_check_write_access(zft_position *pos); +extern int zft_def_idle_state(void); + +/* hooks for the VFS interface + */ +extern int _zft_open(unsigned int dev_minor, unsigned int access_mode); +extern int _zft_close(void); +extern int _zft_ioctl(unsigned int command, void __user *arg); +#endif + + + diff --git a/drivers/char/ftape/zftape/zftape-eof.c b/drivers/char/ftape/zftape/zftape-eof.c new file mode 100644 index 000000000000..dcadcaee9ac1 --- /dev/null +++ b/drivers/char/ftape/zftape/zftape-eof.c @@ -0,0 +1,199 @@ +/* + * I use these routines just to decide when I have to fake a + * volume-table to preserve compatibility to original ftape. + */ +/* + * Copyright (C) 1994-1995 Bas Laarhoven. + * + * Modified for zftape 1996, 1997 Claus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/zftape/zftape-eof.c,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:19:02 $ + * + * This file contains the eof mark handling code + * for the QIC-40/80 floppy-tape driver for Linux. + */ + +#include <linux/string.h> +#include <linux/errno.h> + +#include <linux/zftape.h> + +#include "../zftape/zftape-init.h" +#include "../zftape/zftape-rw.h" +#include "../zftape/zftape-eof.h" + +/* Global vars. + */ + +/* a copy of the failed sector log from the header segment. + */ +eof_mark_union *zft_eof_map; + +/* number of eof marks (entries in bad sector log) on tape. + */ +int zft_nr_eof_marks = -1; + + +/* Local vars. + */ + +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 zft_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 (__u32) 0 entry. + */ + +int zft_ftape_validate_label(char *label) +{ + static char tmp_label[45]; + int result = 0; + TRACE_FUN(ft_t_any); + + memcpy(tmp_label, label, FT_LABEL_SZ); + tmp_label[FT_LABEL_SZ] = '\0'; + TRACE(ft_t_noise, "tape label = `%s'", tmp_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); + } + TRACE(ft_t_noise, "format version = %d", ftape_fmt_version); + TRACE_EXIT result; +} + +static __u8 * find_end_of_eof_list(__u8 * ptr, __u8 * limit) +{ + while (ptr + 3 < limit) { + + if (get_unaligned((__u32*)ptr)) { + ptr += sizeof(__u32); + } else { + return ptr; + } + } + return NULL; +} + +void zft_ftape_extract_file_marks(__u8* address) +{ + int i; + TRACE_FUN(ft_t_any); + + zft_eof_map = NULL; + if (ft_format_code == fmt_var || ft_format_code == fmt_big) { + __u8* end; + __u8* start = ftape_find_end_of_bsm_list(address); + + zft_nr_eof_marks = 0; + if (start) { + start += 3; /* skip end of list mark */ + end = find_end_of_eof_list(start, + address + FT_SEGMENT_SIZE); + if (end && end - start <= FT_FSL_SIZE) { + zft_nr_eof_marks = ((end - start) / + sizeof(eof_mark_union)); + zft_eof_map = (eof_mark_union *)start; + } else { + TRACE(ft_t_err, + "EOF Mark List is too long or damaged!"); + } + } else { + TRACE(ft_t_err, + "Bad Sector List is too long or damaged !"); + } + } else { + zft_eof_map = (eof_mark_union *)&address[FT_FSL]; + zft_nr_eof_marks = GET2(address, FT_FSL_CNT); + } + TRACE(ft_t_noise, "number of file marks: %d", zft_nr_eof_marks); + if (ftape_fmt_version == 1) { + TRACE(ft_t_info, "swapping version 1 fields"); + /* version 1 format uses swapped sector and segment + * fields, correct that ! + */ + for (i = 0; i < zft_nr_eof_marks; ++i) { + __u16 tmp = GET2(&zft_eof_map[i].mark.segment,0); + PUT2(&zft_eof_map[i].mark.segment, 0, + GET2(&zft_eof_map[i].mark.date,0)); + PUT2(&zft_eof_map[i].mark.date, 0, tmp); + } + } + for (i = 0; i < zft_nr_eof_marks; ++i) { + TRACE(ft_t_noise, "eof mark: %5d/%2d", + GET2(&zft_eof_map[i].mark.segment, 0), + GET2(&zft_eof_map[i].mark.date,0)); + } + TRACE_EXIT; +} + +void zft_clear_ftape_file_marks(void) +{ + TRACE_FUN(ft_t_flow); + /* Clear failed sector log: remove all tape marks. We + * don't use old ftape-style EOF-marks. + */ + TRACE(ft_t_info, "Clearing old ftape's eof map"); + memset(zft_eof_map, 0, zft_nr_eof_marks * sizeof(__u32)); + zft_nr_eof_marks = 0; + PUT2(zft_hseg_buf, FT_FSL_CNT, 0); /* nr of eof-marks */ + zft_header_changed = 1; + zft_update_label(zft_hseg_buf); + TRACE_EXIT; +} diff --git a/drivers/char/ftape/zftape/zftape-eof.h b/drivers/char/ftape/zftape/zftape-eof.h new file mode 100644 index 000000000000..26568c26c518 --- /dev/null +++ b/drivers/char/ftape/zftape/zftape-eof.h @@ -0,0 +1,52 @@ +#ifndef _ZFTAPE_EOF_H +#define _ZFTAPE_EOF_H + +/* + * Copyright (C) 1994-1995 Bas Laarhoven. + * adaptaed for zftape 1996, 1997 by Claus Heine + + 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: /homes/cvs/ftape-stacked/ftape/zftape/zftape-eof.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:19:03 $ + * + * Definitions and declarations for the end of file markers + * for the QIC-40/80 floppy-tape driver for Linux. + */ + +#include <linux/ftape-header-segment.h> +#include "../zftape/zftape-buffers.h" +/* failed sector log size (only used if format code != 4). + */ + +typedef union { + ft_fsl_entry mark; + __u32 entry; +} eof_mark_union; + +/* ftape-eof.c defined global vars. + */ +extern int zft_nr_eof_marks; +extern eof_mark_union *zft_eof_map; + +/* ftape-eof.c defined global functions. + */ +extern void zft_ftape_extract_file_marks(__u8* address); +extern int zft_ftape_validate_label(char* label); +extern void zft_clear_ftape_file_marks(void); + +#endif diff --git a/drivers/char/ftape/zftape/zftape-init.c b/drivers/char/ftape/zftape/zftape-init.c new file mode 100644 index 000000000000..dbac7e54e8e0 --- /dev/null +++ b/drivers/char/ftape/zftape/zftape-init.c @@ -0,0 +1,403 @@ +/* + * Copyright (C) 1996, 1997 Claus-Justus Heine. + + 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 registers the zftape frontend + * to the ftape floppy tape driver for Linux + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/major.h> +#include <linux/slab.h> +#ifdef CONFIG_KMOD +#include <linux/kmod.h> +#endif +#include <linux/fcntl.h> +#include <linux/smp_lock.h> +#include <linux/devfs_fs_kernel.h> + +#include <linux/zftape.h> +#include <linux/init.h> +#include <linux/device.h> + +#include "../zftape/zftape-init.h" +#include "../zftape/zftape-read.h" +#include "../zftape/zftape-write.h" +#include "../zftape/zftape-ctl.h" +#include "../zftape/zftape-buffers.h" + +MODULE_AUTHOR("(c) 1996, 1997 Claus-Justus Heine " + "(claus@momo.math.rwth-aachen.de)"); +MODULE_DESCRIPTION(ZFTAPE_VERSION " - " + "VFS interface for the Linux floppy tape driver. " + "Support for QIC-113 compatible volume table " + "and builtin compression (lzrw3 algorithm)"); +MODULE_SUPPORTED_DEVICE("char-major-27"); +MODULE_LICENSE("GPL"); + +/* Global vars. + */ +struct zft_cmpr_ops *zft_cmpr_ops = NULL; +const ftape_info *zft_status; + +/* Local vars. + */ +static unsigned long busy_flag; + +static sigset_t orig_sigmask; + +/* the interface to the kernel vfs layer + */ + +/* Note about llseek(): + * + * st.c and tpqic.c update fp->f_pos but don't implment llseek() and + * initialize the llseek component of the file_ops struct with NULL. + * This means that the user will get the default seek, but the tape + * device will not respect the new position, but happily read from the + * old position. Think a zftape specific llseek() function would be + * better, returning -ESPIPE. TODO. + */ + +static int zft_open (struct inode *ino, struct file *filep); +static int zft_close(struct inode *ino, struct file *filep); +static int zft_ioctl(struct inode *ino, struct file *filep, + unsigned int command, unsigned long arg); +static int zft_mmap(struct file *filep, struct vm_area_struct *vma); +static ssize_t zft_read (struct file *fp, char __user *buff, + size_t req_len, loff_t *ppos); +static ssize_t zft_write(struct file *fp, const char __user *buff, + size_t req_len, loff_t *ppos); + +static struct file_operations zft_cdev = +{ + .owner = THIS_MODULE, + .read = zft_read, + .write = zft_write, + .ioctl = zft_ioctl, + .mmap = zft_mmap, + .open = zft_open, + .release = zft_close, +}; + +static struct class_simple *zft_class; + +/* Open floppy tape device + */ +static int zft_open(struct inode *ino, struct file *filep) +{ + int result; + TRACE_FUN(ft_t_flow); + + nonseekable_open(ino, filep); + TRACE(ft_t_flow, "called for minor %d", iminor(ino)); + if ( test_and_set_bit(0,&busy_flag) ) { + TRACE_ABORT(-EBUSY, ft_t_warn, "failed: already busy"); + } + if ((iminor(ino) & ~(ZFT_MINOR_OP_MASK | FTAPE_NO_REWIND)) + > + FTAPE_SEL_D) { + clear_bit(0,&busy_flag); + TRACE_ABORT(-ENXIO, ft_t_err, "failed: invalid unit nr"); + } + orig_sigmask = current->blocked; + sigfillset(¤t->blocked); + result = _zft_open(iminor(ino), filep->f_flags & O_ACCMODE); + if (result < 0) { + current->blocked = orig_sigmask; /* restore mask */ + clear_bit(0,&busy_flag); + TRACE_ABORT(result, ft_t_err, "_ftape_open failed"); + } else { + /* Mask signals that will disturb proper operation of the + * program that is calling. + */ + current->blocked = orig_sigmask; + sigaddsetmask (¤t->blocked, _DO_BLOCK); + TRACE_EXIT 0; + } +} + +/* Close floppy tape device + */ +static int zft_close(struct inode *ino, struct file *filep) +{ + int result; + TRACE_FUN(ft_t_flow); + + if ( !test_bit(0,&busy_flag) || iminor(ino) != zft_unit) { + TRACE(ft_t_err, "failed: not busy or wrong unit"); + TRACE_EXIT 0; + } + sigfillset(¤t->blocked); + result = _zft_close(); + if (result < 0) { + TRACE(ft_t_err, "_zft_close failed"); + } + current->blocked = orig_sigmask; /* restore before open state */ + clear_bit(0,&busy_flag); + TRACE_EXIT 0; +} + +/* Ioctl for floppy tape device + */ +static int zft_ioctl(struct inode *ino, struct file *filep, + unsigned int command, unsigned long arg) +{ + int result = -EIO; + sigset_t old_sigmask; + TRACE_FUN(ft_t_flow); + + if ( !test_bit(0,&busy_flag) || iminor(ino) != zft_unit || ft_failure) { + TRACE_ABORT(-EIO, ft_t_err, + "failed: not busy, failure or wrong unit"); + } + old_sigmask = current->blocked; /* save mask */ + sigfillset(¤t->blocked); + /* This will work as long as sizeof(void *) == sizeof(long) */ + result = _zft_ioctl(command, (void __user *) arg); + current->blocked = old_sigmask; /* restore mask */ + TRACE_EXIT result; +} + +/* Ioctl for floppy tape device + */ +static int zft_mmap(struct file *filep, struct vm_area_struct *vma) +{ + int result = -EIO; + sigset_t old_sigmask; + TRACE_FUN(ft_t_flow); + + if ( !test_bit(0,&busy_flag) || + iminor(filep->f_dentry->d_inode) != zft_unit || + ft_failure) + { + TRACE_ABORT(-EIO, ft_t_err, + "failed: not busy, failure or wrong unit"); + } + old_sigmask = current->blocked; /* save mask */ + sigfillset(¤t->blocked); + if ((result = ftape_mmap(vma)) >= 0) { +#ifndef MSYNC_BUG_WAS_FIXED + static struct vm_operations_struct dummy = { NULL, }; + vma->vm_ops = &dummy; +#endif + } + current->blocked = old_sigmask; /* restore mask */ + TRACE_EXIT result; +} + +/* Read from floppy tape device + */ +static ssize_t zft_read(struct file *fp, char __user *buff, + size_t req_len, loff_t *ppos) +{ + int result = -EIO; + sigset_t old_sigmask; + struct inode *ino = fp->f_dentry->d_inode; + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_data_flow, "called with count: %ld", (unsigned long)req_len); + if (!test_bit(0,&busy_flag) || iminor(ino) != zft_unit || ft_failure) { + TRACE_ABORT(-EIO, ft_t_err, + "failed: not busy, failure or wrong unit"); + } + old_sigmask = current->blocked; /* save mask */ + sigfillset(¤t->blocked); + result = _zft_read(buff, req_len); + current->blocked = old_sigmask; /* restore mask */ + TRACE(ft_t_data_flow, "return with count: %d", result); + TRACE_EXIT result; +} + +/* Write to tape device + */ +static ssize_t zft_write(struct file *fp, const char __user *buff, + size_t req_len, loff_t *ppos) +{ + int result = -EIO; + sigset_t old_sigmask; + struct inode *ino = fp->f_dentry->d_inode; + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_flow, "called with count: %ld", (unsigned long)req_len); + if (!test_bit(0,&busy_flag) || iminor(ino) != zft_unit || ft_failure) { + TRACE_ABORT(-EIO, ft_t_err, + "failed: not busy, failure or wrong unit"); + } + old_sigmask = current->blocked; /* save mask */ + sigfillset(¤t->blocked); + result = _zft_write(buff, req_len); + current->blocked = old_sigmask; /* restore mask */ + TRACE(ft_t_data_flow, "return with count: %d", result); + TRACE_EXIT result; +} + +/* END OF VFS INTERFACE + * + *****************************************************************************/ + +/* driver/module initialization + */ + +/* the compression module has to call this function to hook into the zftape + * code + */ +int zft_cmpr_register(struct zft_cmpr_ops *new_ops) +{ + TRACE_FUN(ft_t_flow); + + if (zft_cmpr_ops != NULL) { + TRACE_EXIT -EBUSY; + } else { + zft_cmpr_ops = new_ops; + TRACE_EXIT 0; + } +} + +/* lock the zft-compressor() module. + */ +int zft_cmpr_lock(int try_to_load) +{ + if (zft_cmpr_ops == NULL) { +#ifdef CONFIG_KMOD + if (try_to_load) { + request_module("zft-compressor"); + if (zft_cmpr_ops == NULL) { + return -ENOSYS; + } + } else { + return -ENOSYS; + } +#else + return -ENOSYS; +#endif + } + (*zft_cmpr_ops->lock)(); + return 0; +} + +#ifdef CONFIG_ZFT_COMPRESSOR +extern int zft_compressor_init(void); +#endif + +/* Called by modules package when installing the driver or by kernel + * during the initialization phase + */ +int __init zft_init(void) +{ + int i; + TRACE_FUN(ft_t_flow); + +#ifdef MODULE + printk(KERN_INFO ZFTAPE_VERSION "\n"); + if (TRACE_LEVEL >= ft_t_info) { + printk( +KERN_INFO +"(c) 1996, 1997 Claus-Justus Heine (claus@momo.math.rwth-aachen.de)\n" +KERN_INFO +"vfs interface for ftape floppy tape driver.\n" +KERN_INFO +"Support for QIC-113 compatible volume table, dynamic memory allocation\n" +KERN_INFO +"and builtin compression (lzrw3 algorithm).\n"); + } +#else /* !MODULE */ + /* print a short no-nonsense boot message */ + printk(KERN_INFO ZFTAPE_VERSION "\n"); +#endif /* MODULE */ + TRACE(ft_t_info, "zft_init @ 0x%p", zft_init); + TRACE(ft_t_info, + "installing zftape VFS interface for ftape driver ..."); + TRACE_CATCH(register_chrdev(QIC117_TAPE_MAJOR, "zft", &zft_cdev),); + + zft_class = class_simple_create(THIS_MODULE, "zft"); + for (i = 0; i < 4; i++) { + class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i), NULL, "qft%i", i); + devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i), + S_IFCHR | S_IRUSR | S_IWUSR, + "qft%i", i); + class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i + 4), NULL, "nqft%i", i); + devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i + 4), + S_IFCHR | S_IRUSR | S_IWUSR, + "nqft%i", i); + class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i + 16), NULL, "zqft%i", i); + devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i + 16), + S_IFCHR | S_IRUSR | S_IWUSR, + "zqft%i", i); + class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i + 20), NULL, "nzqft%i", i); + devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i + 20), + S_IFCHR | S_IRUSR | S_IWUSR, + "nzqft%i", i); + class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i + 32), NULL, "rawqft%i", i); + devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i + 32), + S_IFCHR | S_IRUSR | S_IWUSR, + "rawqft%i", i); + class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i + 36), NULL, "nrawrawqft%i", i); + devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i + 36), + S_IFCHR | S_IRUSR | S_IWUSR, + "nrawqft%i", i); + } + +#ifdef CONFIG_ZFT_COMPRESSOR + (void)zft_compressor_init(); +#endif + zft_status = ftape_get_status(); /* fetch global data of ftape + * hardware driver + */ + TRACE_EXIT 0; +} + + +/* Called by modules package when removing the driver + */ +static void zft_exit(void) +{ + int i; + TRACE_FUN(ft_t_flow); + + if (unregister_chrdev(QIC117_TAPE_MAJOR, "zft") != 0) { + TRACE(ft_t_warn, "failed"); + } else { + TRACE(ft_t_info, "successful"); + } + for (i = 0; i < 4; i++) { + devfs_remove("qft%i", i); + class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i)); + devfs_remove("nqft%i", i); + class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i + 4)); + devfs_remove("zqft%i", i); + class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i + 16)); + devfs_remove("nzqft%i", i); + class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i + 20)); + devfs_remove("rawqft%i", i); + class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i + 32)); + devfs_remove("nrawqft%i", i); + class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i + 36)); + } + class_simple_destroy(zft_class); + zft_uninit_mem(); /* release remaining memory, if any */ + printk(KERN_INFO "zftape successfully unloaded.\n"); + TRACE_EXIT; +} + +module_init(zft_init); +module_exit(zft_exit); diff --git a/drivers/char/ftape/zftape/zftape-init.h b/drivers/char/ftape/zftape/zftape-init.h new file mode 100644 index 000000000000..937e5d48c20e --- /dev/null +++ b/drivers/char/ftape/zftape/zftape-init.h @@ -0,0 +1,77 @@ +#ifndef _ZFTAPE_INIT_H +#define _ZFTAPE_INIT_H + +/* + * Copyright (C) 1996, 1997 Claus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/zftape/zftape-init.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:19:05 $ + * + * This file contains definitions and macro for the vfs + * interface defined by zftape + * + */ + +#include <linux/ftape-header-segment.h> + +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/ftape-ctl.h" +#include "../lowlevel/ftape-read.h" +#include "../lowlevel/ftape-write.h" +#include "../lowlevel/ftape-bsm.h" +#include "../lowlevel/ftape-io.h" +#include "../lowlevel/ftape-buffer.h" +#include "../lowlevel/ftape-format.h" + +#include "../zftape/zftape-rw.h" + +#ifdef MODULE +#define ftape_status (*zft_status) +#endif + +extern const ftape_info *zft_status; /* needed for zftape-vtbl.h */ + +#include "../zftape/zftape-vtbl.h" + +struct zft_cmpr_ops { + int (*write)(int *write_cnt, + __u8 *dst_buf, const int seg_sz, + const __u8 __user *src_buf, const int req_len, + const zft_position *pos, const zft_volinfo *volume); + int (*read)(int *read_cnt, + __u8 __user *dst_buf, const int req_len, + const __u8 *src_buf, const int seg_sz, + const zft_position *pos, const zft_volinfo *volume); + int (*seek)(unsigned int new_block_pos, + zft_position *pos, const zft_volinfo *volume, + __u8 *buffer); + void (*lock) (void); + void (*reset) (void); + void (*cleanup)(void); +}; + +extern struct zft_cmpr_ops *zft_cmpr_ops; +/* zftape-init.c defined global functions. + */ +extern int zft_cmpr_register(struct zft_cmpr_ops *new_ops); +extern int zft_cmpr_lock(int try_to_load); + +#endif + + diff --git a/drivers/char/ftape/zftape/zftape-read.c b/drivers/char/ftape/zftape/zftape-read.c new file mode 100644 index 000000000000..214bf03dce68 --- /dev/null +++ b/drivers/char/ftape/zftape/zftape-read.c @@ -0,0 +1,377 @@ +/* + * Copyright (C) 1996, 1997 Claus-Justus Heine + + 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: /homes/cvs/ftape-stacked/ftape/zftape/zftape-read.c,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:19:06 $ + * + * This file contains the high level reading code + * for the QIC-117 floppy-tape driver for Linux. + */ + +#include <linux/errno.h> +#include <linux/mm.h> + +#include <linux/zftape.h> + +#include <asm/uaccess.h> + +#include "../zftape/zftape-init.h" +#include "../zftape/zftape-eof.h" +#include "../zftape/zftape-ctl.h" +#include "../zftape/zftape-write.h" +#include "../zftape/zftape-read.h" +#include "../zftape/zftape-rw.h" +#include "../zftape/zftape-vtbl.h" + +/* Global vars. + */ +int zft_just_before_eof; + +/* Local vars. + */ +static int buf_len_rd; + +void zft_zap_read_buffers(void) +{ + buf_len_rd = 0; +} + +int zft_read_header_segments(void) +{ + TRACE_FUN(ft_t_flow); + + zft_header_read = 0; + TRACE_CATCH(zft_vmalloc_once(&zft_hseg_buf, FT_SEGMENT_SIZE),); + TRACE_CATCH(ftape_read_header_segment(zft_hseg_buf),); + TRACE(ft_t_info, "Segments written since first format: %d", + (int)GET4(zft_hseg_buf, FT_SEG_CNT)); + zft_qic113 = (ft_format_code != fmt_normal && + ft_format_code != fmt_1100ft && + ft_format_code != fmt_425ft); + TRACE(ft_t_info, "ft_first_data_segment: %d, ft_last_data_segment: %d", + ft_first_data_segment, ft_last_data_segment); + zft_capacity = zft_get_capacity(); + zft_old_ftape = zft_ftape_validate_label(&zft_hseg_buf[FT_LABEL]); + if (zft_old_ftape) { + TRACE(ft_t_info, +"Found old ftaped tape, emulating eof marks, entering read-only mode"); + zft_ftape_extract_file_marks(zft_hseg_buf); + TRACE_CATCH(zft_fake_volume_headers(zft_eof_map, + zft_nr_eof_marks),); + } else { + /* the specs say that the volume table must be + * initialized with zeroes during formatting, so it + * MUST be readable, i.e. contain vaid ECC + * information. + */ + TRACE_CATCH(ftape_read_segment(ft_first_data_segment, + zft_deblock_buf, + FT_RD_SINGLE),); + TRACE_CATCH(zft_extract_volume_headers(zft_deblock_buf),); + } + zft_header_read = 1; + zft_set_flags(zft_unit); + zft_reset_position(&zft_pos); + TRACE_EXIT 0; +} + +int zft_fetch_segment_fraction(const unsigned int segment, void *buffer, + const ft_read_mode_t read_mode, + const unsigned int start, + const unsigned int size) +{ + int seg_sz; + TRACE_FUN(ft_t_flow); + + if (segment == zft_deblock_segment) { + TRACE(ft_t_data_flow, + "re-using segment %d already in deblock buffer", + segment); + seg_sz = zft_get_seg_sz(segment); + if (start > seg_sz) { + TRACE_ABORT(-EINVAL, ft_t_bug, + "trying to read beyond end of segment:\n" + KERN_INFO "seg_sz : %d\n" + KERN_INFO "start : %d\n" + KERN_INFO "segment: %d", + seg_sz, start, segment); + } + if ((start + size) > seg_sz) { + TRACE_EXIT seg_sz - start; + } + TRACE_EXIT size; + } + seg_sz = ftape_read_segment_fraction(segment, buffer, read_mode, + start, size); + TRACE(ft_t_data_flow, "segment %d, result %d", segment, seg_sz); + if ((int)seg_sz >= 0 && start == 0 && size == FT_SEGMENT_SIZE) { + /* this implicitly assumes that we are always called with + * buffer == zft_deblock_buf + */ + zft_deblock_segment = segment; + } else { + zft_deblock_segment = -1; + } + TRACE_EXIT seg_sz; +} + +/* + * out: + * + * int *read_cnt: the number of bytes we removed from the + * zft_deblock_buf (result) + * + * int *to_do : the remaining size of the read-request. Is changed. + * + * in: + * + * char *buff : buff is the address of the upper part of the user + * buffer, that hasn't been filled with data yet. + * int buf_pos_read: copy of buf_pos_rd + * int buf_len_read: copy of buf_len_rd + * char *zft_deblock_buf: ftape_zft_deblock_buf + * + * returns the amount of data actually copied to the user-buffer + * + * to_do MUST NOT SHRINK except to indicate an EOT. In this case to_do + * has to be set to 0. We cannot return -ENOSPC, because we return the + * amount of data actually * copied to the user-buffer + */ +static int zft_simple_read (int *read_cnt, + __u8 __user *dst_buf, + const int to_do, + const __u8 *src_buf, + const int seg_sz, + const zft_position *pos, + const zft_volinfo *volume) +{ + TRACE_FUN(ft_t_flow); + + if (seg_sz - pos->seg_byte_pos < to_do) { + *read_cnt = seg_sz - pos->seg_byte_pos; + } else { + *read_cnt = to_do; + } + if (copy_to_user(dst_buf, + src_buf + pos->seg_byte_pos, *read_cnt) != 0) { + TRACE_EXIT -EFAULT; + } + TRACE(ft_t_noise, "nr bytes just read: %d", *read_cnt); + TRACE_EXIT *read_cnt; +} + +/* req_len: gets clipped due to EOT of EOF. + * req_clipped: is a flag indicating whether req_len was clipped or not + * volume: contains information on current volume (blk_sz etc.) + */ +static int check_read_access(int *req_len, + const zft_volinfo **volume, + int *req_clipped, + const zft_position *pos) +{ + static __s64 remaining; + static int eod; + TRACE_FUN(ft_t_flow); + + if (zft_io_state != zft_reading) { + if (zft_offline) { /* offline includes no_tape */ + TRACE_ABORT(-ENXIO, ft_t_warn, + "tape is offline or no cartridge"); + } + if (!ft_formatted) { + TRACE_ABORT(-EACCES, + ft_t_warn, "tape is not formatted"); + } + /* now enter defined state, read header segment if not + * already done and flush write buffers + */ + TRACE_CATCH(zft_def_idle_state(),); + zft_io_state = zft_reading; + if (zft_tape_at_eod(pos)) { + eod = 1; + TRACE_EXIT 1; + } + eod = 0; + *volume = zft_find_volume(pos->seg_pos); + /* get the space left until EOF */ + remaining = zft_check_for_eof(*volume, pos); + buf_len_rd = 0; + TRACE(ft_t_noise, "remaining: " LL_X ", vol_no: %d", + LL(remaining), (*volume)->count); + } else if (zft_tape_at_eod(pos)) { + if (++eod > 2) { + TRACE_EXIT -EIO; /* st.c also returns -EIO */ + } else { + TRACE_EXIT 1; + } + } + if ((*req_len % (*volume)->blk_sz) != 0) { + /* this message is informational only. The user gets the + * proper return value + */ + TRACE_ABORT(-EINVAL, ft_t_info, + "req_len %d not a multiple of block size %d", + *req_len, (*volume)->blk_sz); + } + /* 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. + */ + remaining -= *req_len; + if (remaining <= 0) { + TRACE(ft_t_noise, + "clipped request from %d to %d.", + *req_len, (int)(*req_len + remaining)); + *req_len += remaining; + *req_clipped = 1; + } else { + *req_clipped = 0; + } + TRACE_EXIT 0; +} + +/* this_segs_size: the current segment's size. + * buff: the USER-SPACE buffer provided by the calling function. + * req_len: how much data should be read at most. + * volume: contains information on current volume (blk_sz etc.) + */ +static int empty_deblock_buf(__u8 __user *usr_buf, const int req_len, + const __u8 *src_buf, const int seg_sz, + zft_position *pos, + const zft_volinfo *volume) +{ + int cnt; + int result = 0; + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_data_flow, "this_segs_size: %d", seg_sz); + if (zft_use_compression && volume->use_compression) { + TRACE_CATCH(zft_cmpr_lock(1 /* try to load */),); + TRACE_CATCH(result= (*zft_cmpr_ops->read)(&cnt, + usr_buf, req_len, + src_buf, seg_sz, + pos, volume),); + } else { + TRACE_CATCH(result= zft_simple_read (&cnt, + usr_buf, req_len, + src_buf, seg_sz, + pos, volume),); + } + pos->volume_pos += result; + pos->tape_pos += cnt; + pos->seg_byte_pos += cnt; + buf_len_rd -= cnt; /* remaining bytes in buffer */ + TRACE(ft_t_data_flow, "buf_len_rd: %d, cnt: %d", buf_len_rd, cnt); + if(pos->seg_byte_pos >= seg_sz) { + pos->seg_pos++; + pos->seg_byte_pos = 0; + } + TRACE(ft_t_data_flow, "bytes moved out of deblock-buffer: %d", cnt); + TRACE_EXIT result; +} + + +/* note: we store the segment id of the segment that is inside the + * deblock buffer. This spares a lot of ftape_read_segment()s when we + * use small block-sizes. The block-size may be 1kb (SECTOR_SIZE). In + * this case a MTFSR 28 maybe still inside the same segment. + */ +int _zft_read(char __user *buff, int req_len) +{ + int req_clipped; + int result = 0; + int bytes_read = 0; + static unsigned int seg_sz = 0; + static const zft_volinfo *volume = NULL; + TRACE_FUN(ft_t_flow); + + zft_resid = req_len; + result = check_read_access(&req_len, &volume, + &req_clipped, &zft_pos); + switch(result) { + case 0: + break; /* nothing special */ + case 1: + TRACE(ft_t_noise, "EOD reached"); + TRACE_EXIT 0; /* EOD */ + default: + TRACE_ABORT(result, ft_t_noise, + "check_read_access() failed with result %d", + result); + TRACE_EXIT result; + } + while (req_len > 0) { + /* Allow escape from this loop on signal ! + */ + FT_SIGNAL_EXIT(_DONT_BLOCK); + /* buf_len_rd == 0 means that we need to read a new + * segment. + */ + if (buf_len_rd == 0) { + while((result = zft_fetch_segment(zft_pos.seg_pos, + zft_deblock_buf, + FT_RD_AHEAD)) == 0) { + zft_pos.seg_pos ++; + zft_pos.seg_byte_pos = 0; + } + if (result < 0) { + zft_resid -= bytes_read; + TRACE_ABORT(result, ft_t_noise, + "zft_fetch_segment(): %d", + result); + } + seg_sz = result; + buf_len_rd = seg_sz - zft_pos.seg_byte_pos; + } + TRACE_CATCH(result = empty_deblock_buf(buff, + req_len, + zft_deblock_buf, + seg_sz, + &zft_pos, + volume), + zft_resid -= bytes_read); + TRACE(ft_t_data_flow, "bytes just read: %d", result); + bytes_read += result; /* what we got so far */ + buff += result; /* index in user-buffer */ + req_len -= result; /* what's left from req_len */ + } /* while (req_len > 0) */ + if (req_clipped) { + TRACE(ft_t_data_flow, + "maybe partial count because of eof mark"); + if (zft_just_before_eof && bytes_read == 0) { + /* req_len was > 0, but user didn't get + * anything the user has read in the eof-mark + */ + zft_move_past_eof(&zft_pos); + ftape_abort_operation(); + } else { + /* don't skip to the next file before the user + * tried to read a second time past EOF Just + * mark that we are at EOF and maybe decrement + * zft_seg_pos to stay in the same volume; + */ + zft_just_before_eof = 1; + zft_position_before_eof(&zft_pos, volume); + TRACE(ft_t_noise, "just before eof"); + } + } + zft_resid -= result; /* for MTSTATUS */ + TRACE_EXIT bytes_read; +} diff --git a/drivers/char/ftape/zftape/zftape-read.h b/drivers/char/ftape/zftape/zftape-read.h new file mode 100644 index 000000000000..42941de0c23a --- /dev/null +++ b/drivers/char/ftape/zftape/zftape-read.h @@ -0,0 +1,53 @@ +#ifndef _ZFTAPE_READ_H +#define _ZFTAPE_READ_H + +/* + * Copyright (C) 1996, 1997 Claus-Justus Heine + + 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: /homes/cvs/ftape-stacked/ftape/zftape/zftape-read.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:19:07 $ + * + * This file contains the definitions for the read functions + * for the zftape driver for Linux. + * + */ + +#include "../lowlevel/ftape-read.h" + +/* ftape-read.c defined global vars. + */ +extern int zft_just_before_eof; + +/* ftape-read.c defined global functions. + */ +extern void zft_zap_read_buffers(void); +extern int zft_read_header_segments(void); +extern int zft_fetch_segment_fraction(const unsigned int segment, + void *buffer, + const ft_read_mode_t read_mode, + const unsigned int start, + const unsigned int size); +#define zft_fetch_segment(segment, address, read_mode) \ + zft_fetch_segment_fraction(segment, address, read_mode, \ + 0, FT_SEGMENT_SIZE) +/* hook for the VFS interface + */ +extern int _zft_read(char __user *buff, int req_len); + +#endif /* _ZFTAPE_READ_H */ diff --git a/drivers/char/ftape/zftape/zftape-rw.c b/drivers/char/ftape/zftape/zftape-rw.c new file mode 100644 index 000000000000..a61ef50f3dfc --- /dev/null +++ b/drivers/char/ftape/zftape/zftape-rw.c @@ -0,0 +1,376 @@ +/* + * Copyright (C) 1996, 1997 Claus-Justus Heine + + 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: /homes/cvs/ftape-stacked/ftape/zftape/zftape-rw.c,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:19:08 $ + * + * This file contains some common code for the r/w code for + * zftape. + */ + +#include <linux/config.h> /* for CONFIG_ZFT_DFLT_BLK_SZ */ +#include <linux/errno.h> +#include <linux/mm.h> + +#include <linux/zftape.h> +#include "../zftape/zftape-init.h" +#include "../zftape/zftape-eof.h" +#include "../zftape/zftape-ctl.h" +#include "../zftape/zftape-write.h" +#include "../zftape/zftape-read.h" +#include "../zftape/zftape-rw.h" +#include "../zftape/zftape-vtbl.h" + +/* Global vars. + */ + +__u8 *zft_deblock_buf; +__u8 *zft_hseg_buf; +int zft_deblock_segment = -1; +zft_status_enum zft_io_state = zft_idle; +int zft_header_changed; +int zft_qic113; /* conform to old specs. and old zftape */ +int zft_use_compression; +zft_position zft_pos = { + -1, /* seg_pos */ + 0, /* seg_byte_pos */ + 0, /* tape_pos */ + 0 /* volume_pos */ +}; +unsigned int zft_blk_sz = CONFIG_ZFT_DFLT_BLK_SZ; +__s64 zft_capacity; + +unsigned int zft_written_segments; +int zft_label_changed; + +/* Local vars. + */ + +unsigned int zft_get_seg_sz(unsigned int segment) +{ + int size; + TRACE_FUN(ft_t_any); + + size = FT_SEGMENT_SIZE - + count_ones(ftape_get_bad_sector_entry(segment))*FT_SECTOR_SIZE; + if (size > 0) { + TRACE_EXIT (unsigned)size; + } else { + TRACE_EXIT 0; + } +} + +/* ftape_set_flags(). Claus-Justus Heine, 1994/1995 + */ +void zft_set_flags(unsigned minor_unit) +{ + TRACE_FUN(ft_t_flow); + + zft_use_compression = zft_qic_mode = 0; + switch (minor_unit & ZFT_MINOR_OP_MASK) { + case (ZFT_Q80_MODE | ZFT_ZIP_MODE): + case ZFT_ZIP_MODE: + zft_use_compression = 1; + case 0: + case ZFT_Q80_MODE: + zft_qic_mode = 1; + if (zft_mt_compression) { /* override the default */ + zft_use_compression = 1; + } + break; + case ZFT_RAW_MODE: + TRACE(ft_t_noise, "switching to raw mode"); + break; + default: + TRACE(ft_t_warn, "Warning:\n" + KERN_INFO "Wrong combination of minor device bits.\n" + KERN_INFO "Switching to raw read-only mode."); + zft_write_protected = 1; + break; + } + TRACE_EXIT; +} + +/* computes the segment and byte offset inside the segment + * corresponding to tape_pos. + * + * tape_pos gives the offset in bytes from the beginning of the + * ft_first_data_segment *seg_byte_pos is the offset in the current + * segment in bytes + * + * Of, if this routine was called often one should cache the last data + * pos it was called with, but actually this is only needed in + * ftape_seek_block(), that is, almost never. + */ +int zft_calc_seg_byte_coord(int *seg_byte_pos, __s64 tape_pos) +{ + int segment; + int seg_sz; + TRACE_FUN(ft_t_flow); + + if (tape_pos == 0) { + *seg_byte_pos = 0; + segment = ft_first_data_segment; + } else { + seg_sz = 0; + + for (segment = ft_first_data_segment; + ((tape_pos > 0) && (segment <= ft_last_data_segment)); + segment++) { + seg_sz = zft_get_seg_sz(segment); + tape_pos -= seg_sz; + } + if(tape_pos >= 0) { + /* the case tape_pos > != 0 means that the + * argument tape_pos lies beyond the EOT. + */ + *seg_byte_pos= 0; + } else { /* tape_pos < 0 */ + segment--; + *seg_byte_pos= tape_pos + seg_sz; + } + } + TRACE_EXIT(segment); +} + +/* ftape_calc_tape_pos(). + * + * computes the offset in bytes from the beginning of the + * ft_first_data_segment inverse to ftape_calc_seg_byte_coord + * + * We should do some caching. But how: + * + * Each time the header segments are read in, this routine is called + * with ft_tracks_per_tape*segments_per_track argumnet. So this should be + * the time to reset the cache. + * + * Also, it might be in the future that the bad sector map gets + * changed. -> reset the cache + */ +static int seg_pos; +static __s64 tape_pos; + +__s64 zft_get_capacity(void) +{ + seg_pos = ft_first_data_segment; + tape_pos = 0; + + while (seg_pos <= ft_last_data_segment) { + tape_pos += zft_get_seg_sz(seg_pos ++); + } + return tape_pos; +} + +__s64 zft_calc_tape_pos(int segment) +{ + int d1, d2, d3; + TRACE_FUN(ft_t_any); + + if (segment > ft_last_data_segment) { + TRACE_EXIT zft_capacity; + } + if (segment < ft_first_data_segment) { + TRACE_EXIT 0; + } + d2 = segment - seg_pos; + if (-d2 > 10) { + d1 = segment - ft_first_data_segment; + if (-d2 > d1) { + tape_pos = 0; + seg_pos = ft_first_data_segment; + d2 = d1; + } + } + if (d2 > 10) { + d3 = ft_last_data_segment - segment; + if (d2 > d3) { + tape_pos = zft_capacity; + seg_pos = ft_last_data_segment + 1; + d2 = -d3; + } + } + if (d2 > 0) { + while (seg_pos < segment) { + tape_pos += zft_get_seg_sz(seg_pos++); + } + } else { + while (seg_pos > segment) { + tape_pos -= zft_get_seg_sz(--seg_pos); + } + } + TRACE(ft_t_noise, "new cached pos: %d", seg_pos); + + TRACE_EXIT tape_pos; +} + +/* copy Z-label string to buffer, keeps track of the correct offset in + * `buffer' + */ +void zft_update_label(__u8 *buffer) +{ + TRACE_FUN(ft_t_flow); + + if (strncmp(&buffer[FT_LABEL], ZFTAPE_LABEL, + sizeof(ZFTAPE_LABEL)-1) != 0) { + TRACE(ft_t_info, "updating label from \"%s\" to \"%s\"", + &buffer[FT_LABEL], ZFTAPE_LABEL); + strcpy(&buffer[FT_LABEL], ZFTAPE_LABEL); + memset(&buffer[FT_LABEL] + sizeof(ZFTAPE_LABEL) - 1, ' ', + FT_LABEL_SZ - sizeof(ZFTAPE_LABEL + 1)); + PUT4(buffer, FT_LABEL_DATE, 0); + zft_label_changed = zft_header_changed = 1; /* changed */ + } + TRACE_EXIT; +} + +int zft_verify_write_segments(unsigned int segment, + __u8 *data, size_t size, + __u8 *buffer) +{ + int result; + __u8 *write_buf; + __u8 *src_buf; + int single; + int seg_pos; + int seg_sz; + int remaining; + ft_write_mode_t write_mode; + TRACE_FUN(ft_t_flow); + + seg_pos = segment; + seg_sz = zft_get_seg_sz(seg_pos); + src_buf = data; + single = size <= seg_sz; + remaining = size; + do { + TRACE(ft_t_noise, "\n" + KERN_INFO "remaining: %d\n" + KERN_INFO "seg_sz : %d\n" + KERN_INFO "segment : %d", + remaining, seg_sz, seg_pos); + if (remaining == seg_sz) { + write_buf = src_buf; + write_mode = single ? FT_WR_SINGLE : FT_WR_MULTI; + remaining = 0; + } else if (remaining > seg_sz) { + write_buf = src_buf; + write_mode = FT_WR_ASYNC; /* don't start tape */ + remaining -= seg_sz; + } else { /* remaining < seg_sz */ + write_buf = buffer; + memcpy(write_buf, src_buf, remaining); + memset(&write_buf[remaining],'\0',seg_sz-remaining); + write_mode = single ? FT_WR_SINGLE : FT_WR_MULTI; + remaining = 0; + } + if ((result = ftape_write_segment(seg_pos, + write_buf, + write_mode)) != seg_sz) { + TRACE(ft_t_err, "Error: " + "Couldn't write segment %d", seg_pos); + TRACE_EXIT result < 0 ? result : -EIO; /* bail out */ + } + zft_written_segments ++; + seg_sz = zft_get_seg_sz(++seg_pos); + src_buf += result; + } while (remaining > 0); + if (ftape_get_status()->fti_state == writing) { + TRACE_CATCH(ftape_loop_until_writes_done(),); + TRACE_CATCH(ftape_abort_operation(),); + zft_prevent_flush(); + } + seg_pos = segment; + src_buf = data; + remaining = size; + do { + TRACE_CATCH(result = ftape_read_segment(seg_pos, buffer, + single ? FT_RD_SINGLE + : FT_RD_AHEAD),); + if (memcmp(src_buf, buffer, + remaining > result ? result : remaining) != 0) { + TRACE_ABORT(-EIO, ft_t_err, + "Failed to verify written segment %d", + seg_pos); + } + remaining -= result; + TRACE(ft_t_noise, "verify successful:\n" + KERN_INFO "segment : %d\n" + KERN_INFO "segsize : %d\n" + KERN_INFO "remaining: %d", + seg_pos, result, remaining); + src_buf += seg_sz; + seg_pos++; + } while (remaining > 0); + TRACE_EXIT size; +} + + +/* zft_erase(). implemented compression-handling + * + * calculate the first data-segment when using/not using compression. + * + * update header-segment and compression-map-segment. + */ +int zft_erase(void) +{ + int result = 0; + TRACE_FUN(ft_t_flow); + + if (!zft_header_read) { + TRACE_CATCH(zft_vmalloc_once((void **)&zft_hseg_buf, + FT_SEGMENT_SIZE),); + /* no need to read the vtbl and compression map */ + TRACE_CATCH(ftape_read_header_segment(zft_hseg_buf),); + if ((zft_old_ftape = + zft_ftape_validate_label(&zft_hseg_buf[FT_LABEL]))) { + zft_ftape_extract_file_marks(zft_hseg_buf); + } + TRACE(ft_t_noise, + "ft_first_data_segment: %d, ft_last_data_segment: %d", + ft_first_data_segment, ft_last_data_segment); + zft_qic113 = (ft_format_code != fmt_normal && + ft_format_code != fmt_1100ft && + ft_format_code != fmt_425ft); + } + if (zft_old_ftape) { + zft_clear_ftape_file_marks(); + zft_old_ftape = 0; /* no longer old ftape */ + } + PUT2(zft_hseg_buf, FT_CMAP_START, 0); + zft_volume_table_changed = 1; + zft_capacity = zft_get_capacity(); + zft_init_vtbl(); + /* the rest must be done in ftape_update_header_segments + */ + zft_header_read = 1; + zft_header_changed = 1; /* force update of timestamp */ + result = zft_update_header_segments(); + + ftape_abort_operation(); + + zft_reset_position(&zft_pos); + zft_set_flags (zft_unit); + TRACE_EXIT result; +} + +unsigned int zft_get_time(void) +{ + unsigned int date = FT_TIME_STAMP(2097, 11, 30, 23, 59, 59); /* fun */ + return date; +} diff --git a/drivers/char/ftape/zftape/zftape-rw.h b/drivers/char/ftape/zftape/zftape-rw.h new file mode 100644 index 000000000000..14c07f086575 --- /dev/null +++ b/drivers/char/ftape/zftape/zftape-rw.h @@ -0,0 +1,102 @@ +#ifndef _ZFTAPE_RW_H +#define _ZFTAPE_RW_H + +/* + * Copyright (C) 1996, 1997 Claus-Justus Heine. + + 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: /homes/cvs/ftape-stacked/ftape/zftape/zftape-rw.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:19:09 $ + * + * This file contains the definitions for the read and write + * functions for the QIC-117 floppy-tape driver for Linux. + * + */ + +#include <linux/config.h> /* for CONFIG_ZFT_DFLT_BLK_SZ */ +#include "../zftape/zftape-buffers.h" + +#define SEGMENTS_PER_TAPE (ft_segments_per_track * ft_tracks_per_tape) + +/* QIC-113 Rev. G says that `a maximum of 63488 raw bytes may be + * compressed into a single frame'. + * Maybe we should stick to 32kb to make it more `beautiful' + */ +#define ZFT_MAX_BLK_SZ (62*1024) /* bytes */ +#if !defined(CONFIG_ZFT_DFLT_BLK_SZ) +# define CONFIG_ZFT_DFLT_BLK_SZ (10*1024) /* bytes, default of gnu tar */ +#elif CONFIG_ZFT_DFLT_BLK_SZ == 0 +# undef CONFIG_ZFT_DFLT_BLK_SZ +# define CONFIG_ZFT_DFLT_BLK_SZ 1 +#elif (CONFIG_ZFT_DFLT_BLK_SZ % 1024) != 0 +# error CONFIG_ZFT_DFLT_BLK_SZ must be 1 or a multiple of 1024 +#endif +/* The *optional* compression routines need some overhead per tape + * block for their purposes. Instead of asking the actual compression + * implementation how much it needs, we restrict this overhead to be + * maximal of ZFT_CMPT_OVERHEAD size. We need this for EOT + * conditions. The tape is assumed to be logical at EOT when the + * distance from the physical EOT is less than + * one tape block + ZFT_CMPR_OVERHEAD + */ +#define ZFT_CMPR_OVERHEAD 16 /* bytes */ + +typedef enum +{ + zft_idle = 0, + zft_reading, + zft_writing, +} zft_status_enum; + +typedef struct /* all values measured in bytes */ +{ + int seg_pos; /* segment currently positioned at */ + int seg_byte_pos; /* offset in current segment */ + __s64 tape_pos; /* real offset from BOT */ + __s64 volume_pos; /* pos. in uncompressed data stream in + * current volume + */ +} zft_position; + +extern zft_position zft_pos; +extern __u8 *zft_deblock_buf; +extern __u8 *zft_hseg_buf; +extern int zft_deblock_segment; +extern zft_status_enum zft_io_state; +extern int zft_header_changed; +extern int zft_qic113; /* conform to old specs. and old zftape */ +extern int zft_use_compression; +extern unsigned int zft_blk_sz; +extern __s64 zft_capacity; +extern unsigned int zft_written_segments; +extern int zft_label_changed; + +/* zftape-rw.c exported functions + */ +extern unsigned int zft_get_seg_sz(unsigned int segment); +extern void zft_set_flags(unsigned int minor_unit); +extern int zft_calc_seg_byte_coord(int *seg_byte_pos, __s64 tape_pos); +extern __s64 zft_calc_tape_pos(int segment); +extern __s64 zft_get_capacity(void); +extern void zft_update_label(__u8 *buffer); +extern int zft_erase(void); +extern int zft_verify_write_segments(unsigned int segment, + __u8 *data, size_t size, __u8 *buffer); +extern unsigned int zft_get_time(void); +#endif /* _ZFTAPE_RW_H */ + diff --git a/drivers/char/ftape/zftape/zftape-vtbl.c b/drivers/char/ftape/zftape/zftape-vtbl.c new file mode 100644 index 000000000000..ad7f8be6340b --- /dev/null +++ b/drivers/char/ftape/zftape/zftape-vtbl.c @@ -0,0 +1,757 @@ +/* + * Copyright (c) 1995-1997 Claus-Justus Heine + + 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: /homes/cvs/ftape-stacked/ftape/zftape/zftape-vtbl.c,v $ + * $Revision: 1.7.6.1 $ + * $Date: 1997/11/24 13:48:31 $ + * + * This file defines a volume table as defined in various QIC + * standards. + * + * This is a minimal implementation, just allowing ordinary DOS + * :( prgrams to identify the cartridge as used. + */ + +#include <linux/errno.h> +#include <linux/mm.h> +#include <linux/slab.h> + +#include <linux/zftape.h> +#include "../zftape/zftape-init.h" +#include "../zftape/zftape-eof.h" +#include "../zftape/zftape-ctl.h" +#include "../zftape/zftape-write.h" +#include "../zftape/zftape-read.h" +#include "../zftape/zftape-rw.h" +#include "../zftape/zftape-vtbl.h" + +#define ZFT_CMAP_HACK /* leave this defined to hide the compression map */ + +/* + * global variables + */ +int zft_qic_mode = 1; /* use the vtbl */ +int zft_old_ftape; /* prevents old ftaped tapes to be overwritten */ +int zft_volume_table_changed; /* for write_header_segments() */ + +/* + * private variables (only exported for inline functions) + */ +LIST_HEAD(zft_vtbl); + +/* We could also allocate these dynamically when extracting the volume table + * sizeof(zft_volinfo) is about 32 or something close to that + */ +static zft_volinfo tape_vtbl; +static zft_volinfo eot_vtbl; +static zft_volinfo *cur_vtbl; + +static inline void zft_new_vtbl_entry(void) +{ + struct list_head *tmp = &zft_last_vtbl->node; + zft_volinfo *new = zft_kmalloc(sizeof(zft_volinfo)); + + list_add(&new->node, tmp); + new->count = zft_eom_vtbl->count ++; +} + +void zft_free_vtbl(void) +{ + for (;;) { + struct list_head *tmp = zft_vtbl.prev; + zft_volinfo *vtbl; + + if (tmp == &zft_vtbl) + break; + list_del(tmp); + vtbl = list_entry(tmp, zft_volinfo, node); + zft_kfree(vtbl, sizeof(zft_volinfo)); + } + INIT_LIST_HEAD(&zft_vtbl); + cur_vtbl = NULL; +} + +/* initialize vtbl, called by ftape_new_cartridge() + */ +void zft_init_vtbl(void) +{ + zft_volinfo *new; + + zft_free_vtbl(); + + /* Create the two dummy vtbl entries + */ + new = zft_kmalloc(sizeof(zft_volinfo)); + list_add(&new->node, &zft_vtbl); + new = zft_kmalloc(sizeof(zft_volinfo)); + list_add(&new->node, &zft_vtbl); + zft_head_vtbl->end_seg = ft_first_data_segment; + zft_head_vtbl->blk_sz = zft_blk_sz; + zft_head_vtbl->count = -1; + zft_eom_vtbl->start_seg = ft_first_data_segment + 1; + zft_eom_vtbl->end_seg = ft_last_data_segment + 1; + zft_eom_vtbl->blk_sz = zft_blk_sz; + zft_eom_vtbl->count = 0; + + /* Reset the pointer for zft_find_volume() + */ + cur_vtbl = zft_eom_vtbl; + + /* initialize the dummy vtbl entries for zft_qic_mode == 0 + */ + eot_vtbl.start_seg = ft_last_data_segment + 1; + eot_vtbl.end_seg = ft_last_data_segment + 1; + eot_vtbl.blk_sz = zft_blk_sz; + eot_vtbl.count = -1; + tape_vtbl.start_seg = ft_first_data_segment; + tape_vtbl.end_seg = ft_last_data_segment; + tape_vtbl.blk_sz = zft_blk_sz; + tape_vtbl.size = zft_capacity; + tape_vtbl.count = 0; +} + +/* check for a valid VTBL signature. + */ +static int vtbl_signature_valid(__u8 signature[4]) +{ + const char *vtbl_ids[] = VTBL_IDS; /* valid signatures */ + int j; + + for (j = 0; + (j < NR_ITEMS(vtbl_ids)) && (memcmp(signature, vtbl_ids[j], 4) != 0); + j++); + return j < NR_ITEMS(vtbl_ids); +} + +/* We used to store the block-size of the volume in the volume-label, + * using the keyword "blocksize". The blocksize written to the + * volume-label is in bytes. + * + * We use this now only for compatibility with old zftape version. We + * store the blocksize directly as binary number in the vendor + * extension part of the volume entry. + */ +static int check_volume_label(const char *label, int *blk_sz) +{ + int valid_format; + char *blocksize; + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_noise, "called with \"%s\" / \"%s\"", label, ZFT_VOL_NAME); + if (strncmp(label, ZFT_VOL_NAME, strlen(ZFT_VOL_NAME)) != 0) { + *blk_sz = 1; /* smallest block size that we allow */ + valid_format = 0; + } else { + TRACE(ft_t_noise, "got old style zftape vtbl entry"); + /* get the default blocksize */ + /* use the kernel strstr() */ + blocksize= strstr(label, " blocksize "); + if (blocksize) { + blocksize += strlen(" blocksize "); + for(*blk_sz= 0; + *blocksize >= '0' && *blocksize <= '9'; + blocksize++) { + *blk_sz *= 10; + *blk_sz += *blocksize - '0'; + } + if (*blk_sz > ZFT_MAX_BLK_SZ) { + *blk_sz= 1; + valid_format= 0; + } else { + valid_format = 1; + } + } else { + *blk_sz= 1; + valid_format= 0; + } + } + TRACE_EXIT valid_format; +} + +/* check for a zftape volume + */ +static int check_volume(__u8 *entry, zft_volinfo *volume) +{ + TRACE_FUN(ft_t_flow); + + if(strncmp(&entry[VTBL_EXT+EXT_ZFTAPE_SIG], ZFTAPE_SIG, + strlen(ZFTAPE_SIG)) == 0) { + TRACE(ft_t_noise, "got new style zftape vtbl entry"); + volume->blk_sz = GET2(entry, VTBL_EXT+EXT_ZFTAPE_BLKSZ); + volume->qic113 = entry[VTBL_EXT+EXT_ZFTAPE_QIC113]; + TRACE_EXIT 1; + } else { + TRACE_EXIT check_volume_label(&entry[VTBL_DESC], &volume->blk_sz); + } +} + + +/* create zftape specific vtbl entry, the volume bounds are inserted + * in the calling function, zft_create_volume_headers() + */ +static void create_zft_volume(__u8 *entry, zft_volinfo *vtbl) +{ + TRACE_FUN(ft_t_flow); + + memset(entry, 0, VTBL_SIZE); + memcpy(&entry[VTBL_SIG], VTBL_ID, 4); + sprintf(&entry[VTBL_DESC], ZFT_VOL_NAME" %03d", vtbl->count); + entry[VTBL_FLAGS] = (VTBL_FL_NOT_VERIFIED | VTBL_FL_SEG_SPANNING); + entry[VTBL_M_NO] = 1; /* multi_cartridge_count */ + strcpy(&entry[VTBL_EXT+EXT_ZFTAPE_SIG], ZFTAPE_SIG); + PUT2(entry, VTBL_EXT+EXT_ZFTAPE_BLKSZ, vtbl->blk_sz); + if (zft_qic113) { + PUT8(entry, VTBL_DATA_SIZE, vtbl->size); + entry[VTBL_CMPR] = VTBL_CMPR_UNREG; + if (vtbl->use_compression) { /* use compression: */ + entry[VTBL_CMPR] |= VTBL_CMPR_USED; + } + entry[VTBL_EXT+EXT_ZFTAPE_QIC113] = 1; + } else { + PUT4(entry, VTBL_DATA_SIZE, vtbl->size); + entry[VTBL_K_CMPR] = VTBL_CMPR_UNREG; + if (vtbl->use_compression) { /* use compression: */ + entry[VTBL_K_CMPR] |= VTBL_CMPR_USED; + } + } + if (ft_format_code == fmt_big) { + /* SCSI like vtbl, store the number of used + * segments as 4 byte value + */ + PUT4(entry, VTBL_SCSI_SEGS, vtbl->end_seg-vtbl->start_seg + 1); + } else { + /* normal, QIC-80MC like vtbl + */ + PUT2(entry, VTBL_START, vtbl->start_seg); + PUT2(entry, VTBL_END, vtbl->end_seg); + } + TRACE_EXIT; +} + +/* this one creates the volume headers for each volume. It is assumed + * that buffer already contains the old volume-table, so that vtbl + * entries without the zft_volume flag set can savely be ignored. + */ +static void zft_create_volume_headers(__u8 *buffer) +{ + __u8 *entry; + struct list_head *tmp; + zft_volinfo *vtbl; + TRACE_FUN(ft_t_flow); + +#ifdef ZFT_CMAP_HACK + if((strncmp(&buffer[VTBL_EXT+EXT_ZFTAPE_SIG], ZFTAPE_SIG, + strlen(ZFTAPE_SIG)) == 0) && + buffer[VTBL_EXT+EXT_ZFTAPE_CMAP] != 0) { + TRACE(ft_t_noise, "deleting cmap volume"); + memmove(buffer, buffer + VTBL_SIZE, + FT_SEGMENT_SIZE - VTBL_SIZE); + } +#endif + entry = buffer; + for (tmp = zft_head_vtbl->node.next; + tmp != &zft_eom_vtbl->node; + tmp = tmp->next) { + vtbl = list_entry(tmp, zft_volinfo, node); + /* we now fill in the values only for newly created volumes. + */ + if (vtbl->new_volume) { + create_zft_volume(entry, vtbl); + vtbl->new_volume = 0; /* clear the flag */ + } + + DUMP_VOLINFO(ft_t_noise, &entry[VTBL_DESC], vtbl); + entry += VTBL_SIZE; + } + memset(entry, 0, FT_SEGMENT_SIZE - zft_eom_vtbl->count * VTBL_SIZE); + TRACE_EXIT; +} + +/* write volume table to tape. Calls zft_create_volume_headers() + */ +int zft_update_volume_table(unsigned int segment) +{ + int result = 0; + __u8 *verify_buf = NULL; + TRACE_FUN(ft_t_flow); + + TRACE_CATCH(result = ftape_read_segment(ft_first_data_segment, + zft_deblock_buf, + FT_RD_SINGLE),); + zft_create_volume_headers(zft_deblock_buf); + TRACE(ft_t_noise, "writing volume table segment %d", segment); + if (zft_vmalloc_once(&verify_buf, FT_SEGMENT_SIZE) == 0) { + TRACE_CATCH(zft_verify_write_segments(segment, + zft_deblock_buf, result, + verify_buf), + zft_vfree(&verify_buf, FT_SEGMENT_SIZE)); + zft_vfree(&verify_buf, FT_SEGMENT_SIZE); + } else { + TRACE_CATCH(ftape_write_segment(segment, zft_deblock_buf, + FT_WR_SINGLE),); + } + TRACE_EXIT 0; +} + +/* non zftape volumes are handled in raw mode. Thus we need to + * calculate the raw amount of data contained in those segments. + */ +static void extract_alien_volume(__u8 *entry, zft_volinfo *vtbl) +{ + TRACE_FUN(ft_t_flow); + + vtbl->size = (zft_calc_tape_pos(zft_last_vtbl->end_seg+1) - + zft_calc_tape_pos(zft_last_vtbl->start_seg)); + vtbl->use_compression = 0; + vtbl->qic113 = zft_qic113; + if (vtbl->qic113) { + TRACE(ft_t_noise, + "Fake alien volume's size from " LL_X " to " LL_X, + LL(GET8(entry, VTBL_DATA_SIZE)), LL(vtbl->size)); + } else { + TRACE(ft_t_noise, + "Fake alien volume's size from %d to " LL_X, + (int)GET4(entry, VTBL_DATA_SIZE), LL(vtbl->size)); + } + TRACE_EXIT; +} + + +/* extract an zftape specific volume + */ +static void extract_zft_volume(__u8 *entry, zft_volinfo *vtbl) +{ + TRACE_FUN(ft_t_flow); + + if (vtbl->qic113) { + vtbl->size = GET8(entry, VTBL_DATA_SIZE); + vtbl->use_compression = + (entry[VTBL_CMPR] & VTBL_CMPR_USED) != 0; + } else { + vtbl->size = GET4(entry, VTBL_DATA_SIZE); + if (entry[VTBL_K_CMPR] & VTBL_CMPR_UNREG) { + vtbl->use_compression = + (entry[VTBL_K_CMPR] & VTBL_CMPR_USED) != 0; + } else if (entry[VTBL_CMPR] & VTBL_CMPR_UNREG) { + vtbl->use_compression = + (entry[VTBL_CMPR] & VTBL_CMPR_USED) != 0; + } else { + TRACE(ft_t_warn, "Geeh! There is something wrong:\n" + KERN_INFO "QIC compression (Rev = K): %x\n" + KERN_INFO "QIC compression (Rev > K): %x", + entry[VTBL_K_CMPR], entry[VTBL_CMPR]); + } + } + TRACE_EXIT; +} + +/* extract the volume table from buffer. "buffer" must already contain + * the vtbl-segment + */ +int zft_extract_volume_headers(__u8 *buffer) +{ + __u8 *entry; + TRACE_FUN(ft_t_flow); + + zft_init_vtbl(); + entry = buffer; +#ifdef ZFT_CMAP_HACK + if ((strncmp(&entry[VTBL_EXT+EXT_ZFTAPE_SIG], ZFTAPE_SIG, + strlen(ZFTAPE_SIG)) == 0) && + entry[VTBL_EXT+EXT_ZFTAPE_CMAP] != 0) { + TRACE(ft_t_noise, "ignoring cmap volume"); + entry += VTBL_SIZE; + } +#endif + /* the end of the vtbl is indicated by an invalid signature + */ + while (vtbl_signature_valid(&entry[VTBL_SIG]) && + (entry - buffer) < FT_SEGMENT_SIZE) { + zft_new_vtbl_entry(); + if (ft_format_code == fmt_big) { + /* SCSI like vtbl, stores only the number of + * segments used + */ + unsigned int num_segments= GET4(entry, VTBL_SCSI_SEGS); + zft_last_vtbl->start_seg = zft_eom_vtbl->start_seg; + zft_last_vtbl->end_seg = + zft_last_vtbl->start_seg + num_segments - 1; + } else { + /* `normal', QIC-80 like vtbl + */ + zft_last_vtbl->start_seg = GET2(entry, VTBL_START); + zft_last_vtbl->end_seg = GET2(entry, VTBL_END); + } + zft_eom_vtbl->start_seg = zft_last_vtbl->end_seg + 1; + /* check if we created this volume and get the + * blk_sz + */ + zft_last_vtbl->zft_volume = check_volume(entry, zft_last_vtbl); + if (zft_last_vtbl->zft_volume == 0) { + extract_alien_volume(entry, zft_last_vtbl); + } else { + extract_zft_volume(entry, zft_last_vtbl); + } + DUMP_VOLINFO(ft_t_noise, &entry[VTBL_DESC], zft_last_vtbl); + entry +=VTBL_SIZE; + } +#if 0 +/* + * undefine to test end of tape handling + */ + zft_new_vtbl_entry(); + zft_last_vtbl->start_seg = zft_eom_vtbl->start_seg; + zft_last_vtbl->end_seg = ft_last_data_segment - 10; + zft_last_vtbl->blk_sz = zft_blk_sz; + zft_last_vtbl->zft_volume = 1; + zft_last_vtbl->qic113 = zft_qic113; + zft_last_vtbl->size = (zft_calc_tape_pos(zft_last_vtbl->end_seg+1) + - zft_calc_tape_pos(zft_last_vtbl->start_seg)); +#endif + TRACE_EXIT 0; +} + +/* this functions translates the failed_sector_log, misused as + * EOF-marker list, into a virtual volume table. The table mustn't be + * written to tape, because this would occupy the first data segment, + * which should be the volume table, but is actually the first segment + * that is filled with data (when using standard ftape). We assume, + * that we get a non-empty failed_sector_log. + */ +int zft_fake_volume_headers (eof_mark_union *eof_map, int num_failed_sectors) +{ + unsigned int segment, sector; + int have_eom = 0; + int vol_no; + TRACE_FUN(ft_t_flow); + + if ((num_failed_sectors >= 2) && + (GET2(&eof_map[num_failed_sectors - 1].mark.segment, 0) + == + GET2(&eof_map[num_failed_sectors - 2].mark.segment, 0) + 1) && + (GET2(&eof_map[num_failed_sectors - 1].mark.date, 0) == 1)) { + /* this should be eom. We keep the remainder of the + * tape as another volume. + */ + have_eom = 1; + } + zft_init_vtbl(); + zft_eom_vtbl->start_seg = ft_first_data_segment; + for(vol_no = 0; vol_no < num_failed_sectors - have_eom; vol_no ++) { + zft_new_vtbl_entry(); + + segment = GET2(&eof_map[vol_no].mark.segment, 0); + sector = GET2(&eof_map[vol_no].mark.date, 0); + + zft_last_vtbl->start_seg = zft_eom_vtbl->start_seg; + zft_last_vtbl->end_seg = segment; + zft_eom_vtbl->start_seg = segment + 1; + zft_last_vtbl->blk_sz = 1; + zft_last_vtbl->size = + (zft_calc_tape_pos(zft_last_vtbl->end_seg) + - zft_calc_tape_pos(zft_last_vtbl->start_seg) + + (sector-1) * FT_SECTOR_SIZE); + TRACE(ft_t_noise, + "failed sector log: segment: %d, sector: %d", + segment, sector); + DUMP_VOLINFO(ft_t_noise, "Faked volume", zft_last_vtbl); + } + if (!have_eom) { + zft_new_vtbl_entry(); + zft_last_vtbl->start_seg = zft_eom_vtbl->start_seg; + zft_last_vtbl->end_seg = ft_last_data_segment; + zft_eom_vtbl->start_seg = ft_last_data_segment + 1; + zft_last_vtbl->size = zft_capacity; + zft_last_vtbl->size -= zft_calc_tape_pos(zft_last_vtbl->start_seg); + zft_last_vtbl->blk_sz = 1; + DUMP_VOLINFO(ft_t_noise, "Faked volume",zft_last_vtbl); + } + TRACE_EXIT 0; +} + +/* update the internal volume table + * + * if before start of last volume: erase all following volumes if + * inside a volume: set end of volume to infinity + * + * this function is intended to be called every time _ftape_write() is + * called + * + * return: 0 if no new volume was created, 1 if a new volume was + * created + * + * NOTE: we don't need to check for zft_mode as ftape_write() does + * that already. This function gets never called without accessing + * zftape via the *qft* devices + */ + +int zft_open_volume(zft_position *pos, int blk_sz, int use_compression) +{ + TRACE_FUN(ft_t_flow); + + if (!zft_qic_mode) { + TRACE_EXIT 0; + } + if (zft_tape_at_lbot(pos)) { + zft_init_vtbl(); + if(zft_old_ftape) { + /* clear old ftape's eof marks */ + zft_clear_ftape_file_marks(); + zft_old_ftape = 0; /* no longer old ftape */ + } + zft_reset_position(pos); + } + if (pos->seg_pos != zft_last_vtbl->end_seg + 1) { + TRACE_ABORT(-EIO, ft_t_bug, + "BUG: seg_pos: %d, zft_last_vtbl->end_seg: %d", + pos->seg_pos, zft_last_vtbl->end_seg); + } + TRACE(ft_t_noise, "create new volume"); + if (zft_eom_vtbl->count >= ZFT_MAX_VOLUMES) { + TRACE_ABORT(-ENOSPC, ft_t_err, + "Error: maxmimal number of volumes exhausted " + "(maxmimum is %d)", ZFT_MAX_VOLUMES); + } + zft_new_vtbl_entry(); + pos->volume_pos = pos->seg_byte_pos = 0; + zft_last_vtbl->start_seg = pos->seg_pos; + zft_last_vtbl->end_seg = ft_last_data_segment; /* infinity */ + zft_last_vtbl->blk_sz = blk_sz; + zft_last_vtbl->size = zft_capacity; + zft_last_vtbl->zft_volume = 1; + zft_last_vtbl->use_compression = use_compression; + zft_last_vtbl->qic113 = zft_qic113; + zft_last_vtbl->new_volume = 1; + zft_last_vtbl->open = 1; + zft_volume_table_changed = 1; + zft_eom_vtbl->start_seg = ft_last_data_segment + 1; + TRACE_EXIT 0; +} + +/* perform mtfsf, mtbsf, not allowed without zft_qic_mode + */ +int zft_skip_volumes(int count, zft_position *pos) +{ + const zft_volinfo *vtbl; + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_noise, "count: %d", count); + + vtbl= zft_find_volume(pos->seg_pos); + while (count > 0 && vtbl != zft_eom_vtbl) { + vtbl = list_entry(vtbl->node.next, zft_volinfo, node); + count --; + } + while (count < 0 && vtbl != zft_first_vtbl) { + vtbl = list_entry(vtbl->node.prev, zft_volinfo, node); + count ++; + } + pos->seg_pos = vtbl->start_seg; + pos->seg_byte_pos = 0; + pos->volume_pos = 0; + pos->tape_pos = zft_calc_tape_pos(pos->seg_pos); + zft_just_before_eof = vtbl->size == 0; + if (zft_cmpr_ops) { + (*zft_cmpr_ops->reset)(); + } + zft_deblock_segment = -1; /* no need to keep cache */ + TRACE(ft_t_noise, "repositioning to:\n" + KERN_INFO "zft_seg_pos : %d\n" + KERN_INFO "zft_seg_byte_pos : %d\n" + KERN_INFO "zft_tape_pos : " LL_X "\n" + KERN_INFO "zft_volume_pos : " LL_X "\n" + KERN_INFO "file number : %d", + pos->seg_pos, pos->seg_byte_pos, + LL(pos->tape_pos), LL(pos->volume_pos), vtbl->count); + zft_resid = count < 0 ? -count : count; + TRACE_EXIT zft_resid ? -EINVAL : 0; +} + +/* the following simply returns the raw data position of the EOM + * marker, MTIOCSIZE ioctl + */ +__s64 zft_get_eom_pos(void) +{ + if (zft_qic_mode) { + return zft_calc_tape_pos(zft_eom_vtbl->start_seg); + } else { + /* there is only one volume in raw mode */ + return zft_capacity; + } +} + +/* skip to eom, used for MTEOM + */ +void zft_skip_to_eom(zft_position *pos) +{ + TRACE_FUN(ft_t_flow); + pos->seg_pos = zft_eom_vtbl->start_seg; + pos->seg_byte_pos = + pos->volume_pos = + zft_just_before_eof = 0; + pos->tape_pos = zft_calc_tape_pos(pos->seg_pos); + TRACE(ft_t_noise, "ftape positioned to segment %d, data pos " LL_X, + pos->seg_pos, LL(pos->tape_pos)); + TRACE_EXIT; +} + +/* write an EOF-marker by setting zft_last_vtbl->end_seg to seg_pos. + * NOTE: this function assumes that zft_last_vtbl points to a valid + * vtbl entry + * + * NOTE: this routine always positions before the EOF marker + */ +int zft_close_volume(zft_position *pos) +{ + TRACE_FUN(ft_t_any); + + if (zft_vtbl_empty || !zft_last_vtbl->open) { /* should not happen */ + TRACE(ft_t_noise, "There are no volumes to finish"); + TRACE_EXIT -EIO; + } + if (pos->seg_byte_pos == 0 && + pos->seg_pos != zft_last_vtbl->start_seg) { + pos->seg_pos --; + pos->seg_byte_pos = zft_get_seg_sz(pos->seg_pos); + } + zft_last_vtbl->end_seg = pos->seg_pos; + zft_last_vtbl->size = pos->volume_pos; + zft_volume_table_changed = 1; + zft_just_before_eof = 1; + zft_eom_vtbl->start_seg = zft_last_vtbl->end_seg + 1; + zft_last_vtbl->open = 0; /* closed */ + TRACE_EXIT 0; +} + +/* write count file-marks at current position. + * + * The tape is positioned after the eof-marker, that is at byte 0 of + * the segment following the eof-marker + * + * this function is only allowed in zft_qic_mode + * + * Only allowed when tape is at BOT or EOD. + */ +int zft_weof(unsigned int count, zft_position *pos) +{ + + TRACE_FUN(ft_t_flow); + + if (!count) { /* write zero EOF marks should be a real no-op */ + TRACE_EXIT 0; + } + zft_volume_table_changed = 1; + if (zft_tape_at_lbot(pos)) { + zft_init_vtbl(); + if(zft_old_ftape) { + /* clear old ftape's eof marks */ + zft_clear_ftape_file_marks(); + zft_old_ftape = 0; /* no longer old ftape */ + } + } + if (zft_last_vtbl->open) { + zft_close_volume(pos); + zft_move_past_eof(pos); + count --; + } + /* now it's easy, just append eof-marks, that is empty + * volumes, to the end of the already recorded media. + */ + while (count > 0 && + pos->seg_pos <= ft_last_data_segment && + zft_eom_vtbl->count < ZFT_MAX_VOLUMES) { + TRACE(ft_t_noise, + "Writing zero sized file at segment %d", pos->seg_pos); + zft_new_vtbl_entry(); + zft_last_vtbl->start_seg = pos->seg_pos; + zft_last_vtbl->end_seg = pos->seg_pos; + zft_last_vtbl->size = 0; + zft_last_vtbl->blk_sz = zft_blk_sz; + zft_last_vtbl->zft_volume = 1; + zft_last_vtbl->use_compression = 0; + pos->tape_pos += zft_get_seg_sz(pos->seg_pos); + zft_eom_vtbl->start_seg = ++ pos->seg_pos; + count --; + } + if (count > 0) { + /* there are two possibilities: end of tape, or the + * maximum number of files is exhausted. + */ + zft_resid = count; + TRACE(ft_t_noise,"Number of marks NOT written: %d", zft_resid); + if (zft_eom_vtbl->count == ZFT_MAX_VOLUMES) { + TRACE_ABORT(-EINVAL, ft_t_warn, + "maximum allowed number of files " + "exhausted: %d", ZFT_MAX_VOLUMES); + } else { + TRACE_ABORT(-ENOSPC, + ft_t_noise, "reached end of tape"); + } + } + TRACE_EXIT 0; +} + +const zft_volinfo *zft_find_volume(unsigned int seg_pos) +{ + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_any, "called with seg_pos %d",seg_pos); + if (!zft_qic_mode) { + if (seg_pos > ft_last_data_segment) { + TRACE_EXIT &eot_vtbl; + } + tape_vtbl.blk_sz = zft_blk_sz; + TRACE_EXIT &tape_vtbl; + } + if (seg_pos < zft_first_vtbl->start_seg) { + TRACE_EXIT (cur_vtbl = zft_first_vtbl); + } + while (seg_pos > cur_vtbl->end_seg) { + cur_vtbl = list_entry(cur_vtbl->node.next, zft_volinfo, node); + TRACE(ft_t_noise, "%d - %d", cur_vtbl->start_seg, cur_vtbl->end_seg); + } + while (seg_pos < cur_vtbl->start_seg) { + cur_vtbl = list_entry(cur_vtbl->node.prev, zft_volinfo, node); + TRACE(ft_t_noise, "%d - %d", cur_vtbl->start_seg, cur_vtbl->end_seg); + } + if (seg_pos > cur_vtbl->end_seg || seg_pos < cur_vtbl->start_seg) { + TRACE(ft_t_bug, "This cannot happen"); + } + DUMP_VOLINFO(ft_t_noise, "", cur_vtbl); + TRACE_EXIT cur_vtbl; +} + +/* this function really assumes that we are just before eof + */ +void zft_move_past_eof(zft_position *pos) +{ + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_noise, "old seg. pos: %d", pos->seg_pos); + pos->tape_pos += zft_get_seg_sz(pos->seg_pos++) - pos->seg_byte_pos; + pos->seg_byte_pos = 0; + pos->volume_pos = 0; + if (zft_cmpr_ops) { + (*zft_cmpr_ops->reset)(); + } + zft_just_before_eof = 0; + zft_deblock_segment = -1; /* no need to cache it anymore */ + TRACE(ft_t_noise, "new seg. pos: %d", pos->seg_pos); + TRACE_EXIT; +} diff --git a/drivers/char/ftape/zftape/zftape-vtbl.h b/drivers/char/ftape/zftape/zftape-vtbl.h new file mode 100644 index 000000000000..f31d196d1759 --- /dev/null +++ b/drivers/char/ftape/zftape/zftape-vtbl.h @@ -0,0 +1,227 @@ +#ifndef _ZFTAPE_VTBL_H +#define _ZFTAPE_VTBL_H + +/* + * Copyright (c) 1995-1997 Claus-Justus Heine + + 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: /homes/cvs/ftape-stacked/ftape/zftape/zftape-vtbl.h,v $ + * $Revision: 1.3 $ + * $Date: 1997/10/28 14:30:09 $ + * + * This file defines a volume table as defined in the QIC-80 + * development standards. + */ + +#include <linux/list.h> + +#include "../lowlevel/ftape-tracing.h" + +#include "../zftape/zftape-eof.h" +#include "../zftape/zftape-ctl.h" +#include "../zftape/zftape-rw.h" + +#define VTBL_SIZE 128 /* bytes */ + +/* The following are offsets in the vtbl. */ +#define VTBL_SIG 0 +#define VTBL_START 4 +#define VTBL_END 6 +#define VTBL_DESC 8 +#define VTBL_DATE 52 +#define VTBL_FLAGS 56 +#define VTBL_FL_VENDOR_SPECIFIC (1<<0) +#define VTBL_FL_MUTLI_CARTRIDGE (1<<1) +#define VTBL_FL_NOT_VERIFIED (1<<2) +#define VTBL_FL_REDIR_INHIBIT (1<<3) +#define VTBL_FL_SEG_SPANNING (1<<4) +#define VTBL_FL_DIRECTORY_LAST (1<<5) +#define VTBL_FL_RESERVED_6 (1<<6) +#define VTBL_FL_RESERVED_7 (1<<7) +#define VTBL_M_NO 57 +#define VTBL_EXT 58 +#define EXT_ZFTAPE_SIG 0 +#define EXT_ZFTAPE_BLKSZ 10 +#define EXT_ZFTAPE_CMAP 12 +#define EXT_ZFTAPE_QIC113 13 +#define VTBL_PWD 84 +#define VTBL_DIR_SIZE 92 +#define VTBL_DATA_SIZE 96 +#define VTBL_OS_VERSION 104 +#define VTBL_SRC_DRIVE 106 +#define VTBL_DEV 122 +#define VTBL_RESERVED_1 123 +#define VTBL_CMPR 124 +#define VTBL_CMPR_UNREG 0x3f +#define VTBL_CMPR_USED 0x80 +#define VTBL_FMT 125 +#define VTBL_RESERVED_2 126 +#define VTBL_RESERVED_3 127 +/* compatibility with pre revision K */ +#define VTBL_K_CMPR 120 + +/* the next is used by QIC-3020 tapes with format code 6 (>2^16 + * segments) It is specified in QIC-113, Rev. G, Section 5 (SCSI + * volume table). The difference is simply, that we only store the + * number of segments used, not the starting segment. + */ +#define VTBL_SCSI_SEGS 4 /* is a 4 byte value */ + +/* one vtbl is 128 bytes, that results in a maximum number of + * 29*1024/128 = 232 volumes. + */ +#define ZFT_MAX_VOLUMES (FT_SEGMENT_SIZE/VTBL_SIZE) +#define VTBL_ID "VTBL" +#define VTBL_IDS { VTBL_ID, "XTBL", "UTID", "EXVT" } /* other valid ids */ +#define ZFT_VOL_NAME "zftape volume" /* volume label used by me */ +#define ZFTAPE_SIG "LINUX ZFT" + +/* global variables + */ +typedef struct zft_internal_vtbl +{ + struct list_head node; + int count; + unsigned int start_seg; /* 32 bits are enough for now */ + unsigned int end_seg; /* 32 bits are enough for now */ + __s64 size; /* uncompressed size */ + unsigned int blk_sz; /* block size for this volume */ + unsigned int zft_volume :1; /* zftape created this volume */ + unsigned int use_compression:1; /* compressed volume */ + unsigned int qic113 :1; /* layout of compressed block + * info and vtbl conforms to + * QIC-113, Rev. G + */ + unsigned int new_volume :1; /* it was created by us, this + * run. this allows the + * fields that aren't really + * used by zftape to be filled + * in by some user level + * program. + */ + unsigned int open :1; /* just in progress of being + * written + */ +} zft_volinfo; + +extern struct list_head zft_vtbl; +#define zft_head_vtbl list_entry(zft_vtbl.next, zft_volinfo, node) +#define zft_eom_vtbl list_entry(zft_vtbl.prev, zft_volinfo, node) +#define zft_last_vtbl list_entry(zft_eom_vtbl->node.prev, zft_volinfo, node) +#define zft_first_vtbl list_entry(zft_head_vtbl->node.next, zft_volinfo, node) +#define zft_vtbl_empty (zft_eom_vtbl->node.prev == &zft_head_vtbl->node) + +#define DUMP_VOLINFO(level, desc, info) \ +{ \ + char tmp[21]; \ + strlcpy(tmp, desc, sizeof(tmp)); \ + TRACE(level, "Volume %d:\n" \ + KERN_INFO "description : %s\n" \ + KERN_INFO "first segment: %d\n" \ + KERN_INFO "last segment: %d\n" \ + KERN_INFO "size : " LL_X "\n" \ + KERN_INFO "block size : %d\n" \ + KERN_INFO "compression : %d\n" \ + KERN_INFO "zftape volume: %d\n" \ + KERN_INFO "QIC-113 conf.: %d", \ + (info)->count, tmp, (info)->start_seg, (info)->end_seg, \ + LL((info)->size), (info)->blk_sz, \ + (info)->use_compression != 0, (info)->zft_volume != 0, \ + (info)->qic113 != 0); \ +} + +extern int zft_qic_mode; +extern int zft_old_ftape; +extern int zft_volume_table_changed; + +/* exported functions */ +extern void zft_init_vtbl (void); +extern void zft_free_vtbl (void); +extern int zft_extract_volume_headers(__u8 *buffer); +extern int zft_update_volume_table (unsigned int segment); +extern int zft_open_volume (zft_position *pos, + int blk_sz, int use_compression); +extern int zft_close_volume (zft_position *pos); +extern const zft_volinfo *zft_find_volume(unsigned int seg_pos); +extern int zft_skip_volumes (int count, zft_position *pos); +extern __s64 zft_get_eom_pos (void); +extern void zft_skip_to_eom (zft_position *pos); +extern int zft_fake_volume_headers (eof_mark_union *eof_map, + int num_failed_sectors); +extern int zft_weof (unsigned int count, zft_position *pos); +extern void zft_move_past_eof (zft_position *pos); + +static inline int zft_tape_at_eod (const zft_position *pos); +static inline int zft_tape_at_lbot (const zft_position *pos); +static inline void zft_position_before_eof (zft_position *pos, + const zft_volinfo *volume); +static inline __s64 zft_check_for_eof(const zft_volinfo *vtbl, + const zft_position *pos); + +/* this function decrements the zft_seg_pos counter if we are right + * at the beginning of a segment. This is to handle fsfm/bsfm -- we + * need to position before the eof mark. NOTE: zft_tape_pos is not + * changed + */ +static inline void zft_position_before_eof(zft_position *pos, + const zft_volinfo *volume) +{ + TRACE_FUN(ft_t_flow); + + if (pos->seg_pos == volume->end_seg + 1 && pos->seg_byte_pos == 0) { + pos->seg_pos --; + pos->seg_byte_pos = zft_get_seg_sz(pos->seg_pos); + } + TRACE_EXIT; +} + +/* Mmmh. Is the position at the end of the last volume, that is right + * before the last EOF mark also logical an EOD condition? + */ +static inline int zft_tape_at_eod(const zft_position *pos) +{ + TRACE_FUN(ft_t_any); + + if (zft_qic_mode) { + TRACE_EXIT (pos->seg_pos >= zft_eom_vtbl->start_seg || + zft_last_vtbl->open); + } else { + TRACE_EXIT pos->seg_pos > ft_last_data_segment; + } +} + +static inline int zft_tape_at_lbot(const zft_position *pos) +{ + if (zft_qic_mode) { + return (pos->seg_pos <= zft_first_vtbl->start_seg && + pos->volume_pos == 0); + } else { + return (pos->seg_pos <= ft_first_data_segment && + pos->volume_pos == 0); + } +} + +/* This one checks for EOF. return remaing space (may be negative) + */ +static inline __s64 zft_check_for_eof(const zft_volinfo *vtbl, + const zft_position *pos) +{ + return (__s64)(vtbl->size - pos->volume_pos); +} + +#endif /* _ZFTAPE_VTBL_H */ diff --git a/drivers/char/ftape/zftape/zftape-write.c b/drivers/char/ftape/zftape/zftape-write.c new file mode 100644 index 000000000000..94327b8c97b9 --- /dev/null +++ b/drivers/char/ftape/zftape/zftape-write.c @@ -0,0 +1,483 @@ +/* + * Copyright (C) 1996, 1997 Claus Heine + + 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: /homes/cvs/ftape-stacked/ftape/zftape/zftape-write.c,v $ + * $Revision: 1.3 $ + * $Date: 1997/11/06 00:50:29 $ + * + * This file contains the writing code + * for the QIC-117 floppy-tape driver for Linux. + */ + +#include <linux/errno.h> +#include <linux/mm.h> + +#include <linux/zftape.h> + +#include <asm/uaccess.h> + +#include "../zftape/zftape-init.h" +#include "../zftape/zftape-eof.h" +#include "../zftape/zftape-ctl.h" +#include "../zftape/zftape-write.h" +#include "../zftape/zftape-read.h" +#include "../zftape/zftape-rw.h" +#include "../zftape/zftape-vtbl.h" + +/* Global vars. + */ + +/* Local vars. + */ +static int last_write_failed; +static int need_flush; + +void zft_prevent_flush(void) +{ + need_flush = 0; +} + +static int zft_write_header_segments(__u8* buffer) +{ + int header_1_ok = 0; + int header_2_ok = 0; + unsigned int time_stamp; + TRACE_FUN(ft_t_noise); + + TRACE_CATCH(ftape_abort_operation(),); + ftape_seek_to_bot(); /* prevents extra rewind */ + if (GET4(buffer, 0) != FT_HSEG_MAGIC) { + TRACE_ABORT(-EIO, ft_t_err, + "wrong header signature found, aborting"); + } + /* Be optimistic: */ + PUT4(buffer, FT_SEG_CNT, + zft_written_segments + GET4(buffer, FT_SEG_CNT) + 2); + if ((time_stamp = zft_get_time()) != 0) { + PUT4(buffer, FT_WR_DATE, time_stamp); + if (zft_label_changed) { + PUT4(buffer, FT_LABEL_DATE, time_stamp); + } + } + TRACE(ft_t_noise, + "writing first header segment %d", ft_header_segment_1); + header_1_ok = zft_verify_write_segments(ft_header_segment_1, + buffer, FT_SEGMENT_SIZE, + zft_deblock_buf) >= 0; + TRACE(ft_t_noise, + "writing second header segment %d", ft_header_segment_2); + header_2_ok = zft_verify_write_segments(ft_header_segment_2, + buffer, FT_SEGMENT_SIZE, + zft_deblock_buf) >= 0; + if (!header_1_ok) { + TRACE(ft_t_warn, "Warning: " + "update of first header segment failed"); + } + if (!header_2_ok) { + TRACE(ft_t_warn, "Warning: " + "update of second header segment failed"); + } + if (!header_1_ok && !header_2_ok) { + TRACE_ABORT(-EIO, ft_t_err, "Error: " + "update of both header segments failed."); + } + TRACE_EXIT 0; +} + +int zft_update_header_segments(void) +{ + TRACE_FUN(ft_t_noise); + + /* must NOT use zft_write_protected, as it also includes the + * file access mode. But we also want to update when soft + * write protection is enabled (O_RDONLY) + */ + if (ft_write_protected || zft_old_ftape) { + TRACE_ABORT(0, ft_t_noise, "Tape set read-only: no update"); + } + if (!zft_header_read) { + TRACE_ABORT(0, ft_t_noise, "Nothing to update"); + } + if (!zft_header_changed) { + zft_header_changed = zft_written_segments > 0; + } + if (!zft_header_changed && !zft_volume_table_changed) { + TRACE_ABORT(0, ft_t_noise, "Nothing to update"); + } + TRACE(ft_t_noise, "Updating header segments"); + if (ftape_get_status()->fti_state == writing) { + TRACE_CATCH(ftape_loop_until_writes_done(),); + } + TRACE_CATCH(ftape_abort_operation(),); + + zft_deblock_segment = -1; /* invalidate the cache */ + if (zft_header_changed) { + TRACE_CATCH(zft_write_header_segments(zft_hseg_buf),); + } + if (zft_volume_table_changed) { + TRACE_CATCH(zft_update_volume_table(ft_first_data_segment),); + } + zft_header_changed = + zft_volume_table_changed = + zft_label_changed = + zft_written_segments = 0; + TRACE_CATCH(ftape_abort_operation(),); + ftape_seek_to_bot(); + TRACE_EXIT 0; +} + +static int read_merge_buffer(int seg_pos, __u8 *buffer, int offset, int seg_sz) +{ + int result = 0; + const ft_trace_t old_tracing = TRACE_LEVEL; + TRACE_FUN(ft_t_flow); + + if (zft_qic_mode) { + /* writing in the middle of a volume is NOT allowed + * + */ + TRACE(ft_t_noise, "No need to read a segment"); + memset(buffer + offset, 0, seg_sz - offset); + TRACE_EXIT 0; + } + TRACE(ft_t_any, "waiting"); + ftape_start_writing(FT_WR_MULTI); + TRACE_CATCH(ftape_loop_until_writes_done(),); + + TRACE(ft_t_noise, "trying to read segment %d from offset %d", + seg_pos, offset); + SET_TRACE_LEVEL(ft_t_bug); + result = zft_fetch_segment_fraction(seg_pos, buffer, + FT_RD_SINGLE, + offset, seg_sz - offset); + SET_TRACE_LEVEL(old_tracing); + if (result != (seg_sz - offset)) { + TRACE(ft_t_noise, "Ignore error: read_segment() result: %d", + result); + memset(buffer + offset, 0, seg_sz - offset); + } + TRACE_EXIT 0; +} + +/* flush the write buffer to tape and write an eof-marker at the + * current position if not in raw mode. This function always + * positions the tape before the eof-marker. _ftape_close() should + * then advance to the next segment. + * + * the parameter "finish_volume" describes whether to position before + * or after the possibly created file-mark. We always position after + * the file-mark when called from ftape_close() and a flush was needed + * (that is ftape_write() was the last tape operation before calling + * ftape_flush) But we always position before the file-mark when this + * function get's called from outside ftape_close() + */ +int zft_flush_buffers(void) +{ + int result; + int data_remaining; + int this_segs_size; + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_data_flow, + "entered, ftape_state = %d", ftape_get_status()->fti_state); + if (ftape_get_status()->fti_state != writing && !need_flush) { + TRACE_ABORT(0, ft_t_noise, "no need for flush"); + } + zft_io_state = zft_idle; /* triggers some initializations for the + * read and write routines + */ + if (last_write_failed) { + ftape_abort_operation(); + TRACE_EXIT -EIO; + } + TRACE(ft_t_noise, "flushing write buffers"); + this_segs_size = zft_get_seg_sz(zft_pos.seg_pos); + if (this_segs_size == zft_pos.seg_byte_pos) { + zft_pos.seg_pos ++; + data_remaining = zft_pos.seg_byte_pos = 0; + } else { + data_remaining = zft_pos.seg_byte_pos; + } + /* If there is any data not written to tape yet, append zero's + * up to the end of the sector (if using compression) or merge + * it with the data existing on the tape Then write the + * segment(s) to tape. + */ + TRACE(ft_t_noise, "Position:\n" + KERN_INFO "seg_pos : %d\n" + KERN_INFO "byte pos : %d\n" + KERN_INFO "remaining: %d", + zft_pos.seg_pos, zft_pos.seg_byte_pos, data_remaining); + if (data_remaining > 0) { + do { + this_segs_size = zft_get_seg_sz(zft_pos.seg_pos); + if (this_segs_size > data_remaining) { + TRACE_CATCH(read_merge_buffer(zft_pos.seg_pos, + zft_deblock_buf, + data_remaining, + this_segs_size), + last_write_failed = 1); + } + result = ftape_write_segment(zft_pos.seg_pos, + zft_deblock_buf, + FT_WR_MULTI); + if (result != this_segs_size) { + TRACE(ft_t_err, "flush buffers failed"); + zft_pos.tape_pos -= zft_pos.seg_byte_pos; + zft_pos.seg_byte_pos = 0; + + last_write_failed = 1; + TRACE_EXIT result; + } + zft_written_segments ++; + TRACE(ft_t_data_flow, + "flush, moved out buffer: %d", result); + /* need next segment for more data (empty segments?) + */ + if (result < data_remaining) { + if (result > 0) { + /* move remainder to buffer beginning + */ + memmove(zft_deblock_buf, + zft_deblock_buf + result, + FT_SEGMENT_SIZE - result); + } + } + data_remaining -= result; + zft_pos.seg_pos ++; + } while (data_remaining > 0); + TRACE(ft_t_any, "result: %d", result); + zft_deblock_segment = --zft_pos.seg_pos; + if (data_remaining == 0) { /* first byte next segment */ + zft_pos.seg_byte_pos = this_segs_size; + } else { /* after data previous segment, data_remaining < 0 */ + zft_pos.seg_byte_pos = data_remaining + result; + } + } else { + TRACE(ft_t_noise, "zft_deblock_buf empty"); + zft_pos.seg_pos --; + zft_pos.seg_byte_pos = zft_get_seg_sz (zft_pos.seg_pos); + ftape_start_writing(FT_WR_MULTI); + } + TRACE(ft_t_any, "waiting"); + if ((result = ftape_loop_until_writes_done()) < 0) { + /* that's really bad. What to to with zft_tape_pos? + */ + TRACE(ft_t_err, "flush buffers failed"); + } + TRACE(ft_t_any, "zft_seg_pos: %d, zft_seg_byte_pos: %d", + zft_pos.seg_pos, zft_pos.seg_byte_pos); + last_write_failed = + need_flush = 0; + TRACE_EXIT result; +} + +/* return-value: the number of bytes removed from the user-buffer + * + * out: + * int *write_cnt: how much actually has been moved to the + * zft_deblock_buf + * int req_len : MUST NOT BE CHANGED, except at EOT, in + * which case it may be adjusted + * in : + * char *buff : the user buffer + * int buf_pos_write : copy of buf_len_wr int + * this_segs_size : the size in bytes of the actual segment + * char + * *zft_deblock_buf : zft_deblock_buf + */ +static int zft_simple_write(int *cnt, + __u8 *dst_buf, const int seg_sz, + const __u8 __user *src_buf, const int req_len, + const zft_position *pos,const zft_volinfo *volume) +{ + int space_left; + TRACE_FUN(ft_t_flow); + + /* volume->size holds the tape capacity while volume is open */ + if (pos->tape_pos + volume->blk_sz > volume->size) { + TRACE_EXIT -ENOSPC; + } + /* remaining space in this segment, NOT zft_deblock_buf + */ + space_left = seg_sz - pos->seg_byte_pos; + *cnt = req_len < space_left ? req_len : space_left; + if (copy_from_user(dst_buf + pos->seg_byte_pos, src_buf, *cnt) != 0) { + TRACE_EXIT -EFAULT; + } + TRACE_EXIT *cnt; +} + +static int check_write_access(int req_len, + const zft_volinfo **volume, + zft_position *pos, + const unsigned int blk_sz) +{ + int result; + TRACE_FUN(ft_t_flow); + + if ((req_len % zft_blk_sz) != 0) { + TRACE_ABORT(-EINVAL, ft_t_info, + "write-count %d must be multiple of block-size %d", + req_len, blk_sz); + } + if (zft_io_state == zft_writing) { + /* all other error conditions have been checked earlier + */ + TRACE_EXIT 0; + } + zft_io_state = zft_idle; + TRACE_CATCH(zft_check_write_access(pos),); + /* If we haven't read the header segment yet, do it now. + * This will verify the configuration, get the bad sector + * table and read the volume table segment + */ + if (!zft_header_read) { + TRACE_CATCH(zft_read_header_segments(),); + } + /* fine. Now the tape is either at BOT or at EOD, + * Write start of volume now + */ + TRACE_CATCH(zft_open_volume(pos, blk_sz, zft_use_compression),); + *volume = zft_find_volume(pos->seg_pos); + DUMP_VOLINFO(ft_t_noise, "", *volume); + zft_just_before_eof = 0; + /* now merge with old data if necessary */ + if (!zft_qic_mode && pos->seg_byte_pos != 0){ + result = zft_fetch_segment(pos->seg_pos, + zft_deblock_buf, + FT_RD_SINGLE); + if (result < 0) { + if (result == -EINTR || result == -ENOSPC) { + TRACE_EXIT result; + } + TRACE(ft_t_noise, + "ftape_read_segment() result: %d. " + "This might be normal when using " + "a newly\nformatted tape", result); + memset(zft_deblock_buf, '\0', pos->seg_byte_pos); + } + } + zft_io_state = zft_writing; + TRACE_EXIT 0; +} + +static int fill_deblock_buf(__u8 *dst_buf, const int seg_sz, + zft_position *pos, const zft_volinfo *volume, + const char __user *usr_buf, const int req_len) +{ + int cnt = 0; + int result = 0; + TRACE_FUN(ft_t_flow); + + if (seg_sz == 0) { + TRACE_ABORT(0, ft_t_data_flow, "empty segment"); + } + TRACE(ft_t_data_flow, "\n" + KERN_INFO "remaining req_len: %d\n" + KERN_INFO " buf_pos: %d", + req_len, pos->seg_byte_pos); + /* zft_deblock_buf will not contain a valid segment any longer */ + zft_deblock_segment = -1; + if (zft_use_compression) { + TRACE_CATCH(zft_cmpr_lock(1 /* try to load */),); + TRACE_CATCH(result= (*zft_cmpr_ops->write)(&cnt, + dst_buf, seg_sz, + usr_buf, req_len, + pos, volume),); + } else { + TRACE_CATCH(result= zft_simple_write(&cnt, + dst_buf, seg_sz, + usr_buf, req_len, + pos, volume),); + } + pos->volume_pos += result; + pos->seg_byte_pos += cnt; + pos->tape_pos += cnt; + TRACE(ft_t_data_flow, "\n" + KERN_INFO "removed from user-buffer : %d bytes.\n" + KERN_INFO "copied to zft_deblock_buf: %d bytes.\n" + KERN_INFO "zft_tape_pos : " LL_X " bytes.", + result, cnt, LL(pos->tape_pos)); + TRACE_EXIT result; +} + + +/* called by the kernel-interface routine "zft_write()" + */ +int _zft_write(const char __user *buff, int req_len) +{ + int result = 0; + int written = 0; + int write_cnt; + int seg_sz; + static const zft_volinfo *volume = NULL; + TRACE_FUN(ft_t_flow); + + zft_resid = req_len; + last_write_failed = 1; /* reset to 0 when successful */ + /* check if write is allowed + */ + TRACE_CATCH(check_write_access(req_len, &volume,&zft_pos,zft_blk_sz),); + while (req_len > 0) { + /* Allow us to escape from this loop with a signal ! + */ + FT_SIGNAL_EXIT(_DONT_BLOCK); + seg_sz = zft_get_seg_sz(zft_pos.seg_pos); + if ((write_cnt = fill_deblock_buf(zft_deblock_buf, + seg_sz, + &zft_pos, + volume, + buff, + req_len)) < 0) { + zft_resid -= written; + if (write_cnt == -ENOSPC) { + /* leave the remainder to flush_buffers() + */ + TRACE(ft_t_info, "No space left on device"); + last_write_failed = 0; + if (!need_flush) { + need_flush = written > 0; + } + TRACE_EXIT written > 0 ? written : -ENOSPC; + } else { + TRACE_EXIT result; + } + } + if (zft_pos.seg_byte_pos == seg_sz) { + TRACE_CATCH(ftape_write_segment(zft_pos.seg_pos, + zft_deblock_buf, + FT_WR_ASYNC), + zft_resid -= written); + zft_written_segments ++; + zft_pos.seg_byte_pos = 0; + zft_deblock_segment = zft_pos.seg_pos; + ++zft_pos.seg_pos; + } + written += write_cnt; + buff += write_cnt; + req_len -= write_cnt; + } /* while (req_len > 0) */ + TRACE(ft_t_data_flow, "remaining in blocking buffer: %d", + zft_pos.seg_byte_pos); + TRACE(ft_t_data_flow, "just written bytes: %d", written); + last_write_failed = 0; + zft_resid -= written; + need_flush = need_flush || written > 0; + TRACE_EXIT written; /* bytes written */ +} diff --git a/drivers/char/ftape/zftape/zftape-write.h b/drivers/char/ftape/zftape/zftape-write.h new file mode 100644 index 000000000000..ea887015b493 --- /dev/null +++ b/drivers/char/ftape/zftape/zftape-write.h @@ -0,0 +1,38 @@ +#ifndef _ZFTAPE_WRITE_H +#define _ZFTAPE_WRITE_H + +/* + * Copyright (C) 1996, 1997 Claus-Justus Heine + + 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: /homes/cvs/ftape-stacked/ftape/zftape/zftape-write.h,v $ + * $Revision: 1.2 $ + * $Date: 1997/10/05 19:19:13 $ + * + * This file contains the definitions for the write functions + * for the zftape driver for Linux. + * + */ + +extern int zft_flush_buffers(void); +extern int zft_update_header_segments(void); +extern void zft_prevent_flush(void); + +/* hook for the VFS interface + */ +extern int _zft_write(const char __user *buff, int req_len); +#endif /* _ZFTAPE_WRITE_H */ diff --git a/drivers/char/ftape/zftape/zftape_syms.c b/drivers/char/ftape/zftape/zftape_syms.c new file mode 100644 index 000000000000..2db1401682df --- /dev/null +++ b/drivers/char/ftape/zftape/zftape_syms.c @@ -0,0 +1,43 @@ +/* + * Copyright (C) 1997 Claus-Justus Heine + + 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: /homes/cvs/ftape-stacked/ftape/zftape/zftape_syms.c,v $ + * $Revision: 1.3 $ + * $Date: 1997/10/05 19:19:14 $ + * + * This file contains the symbols that the zftape frontend to + * the ftape floppy tape driver exports + */ + +#include <linux/module.h> + +#include <linux/zftape.h> + +#include "../zftape/zftape-init.h" +#include "../zftape/zftape-read.h" +#include "../zftape/zftape-buffers.h" +#include "../zftape/zftape-ctl.h" + +/* zftape-init.c */ +EXPORT_SYMBOL(zft_cmpr_register); +/* zftape-read.c */ +EXPORT_SYMBOL(zft_fetch_segment_fraction); +/* zftape-buffers.c */ +EXPORT_SYMBOL(zft_vmalloc_once); +EXPORT_SYMBOL(zft_vmalloc_always); +EXPORT_SYMBOL(zft_vfree); |