summaryrefslogtreecommitdiffstats
path: root/drivers/char/ftape/lowlevel
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/ftape/lowlevel')
-rw-r--r--drivers/char/ftape/lowlevel/Makefile43
-rw-r--r--drivers/char/ftape/lowlevel/fc-10.c175
-rw-r--r--drivers/char/ftape/lowlevel/fc-10.h39
-rw-r--r--drivers/char/ftape/lowlevel/fdc-io.c1352
-rw-r--r--drivers/char/ftape/lowlevel/fdc-io.h252
-rw-r--r--drivers/char/ftape/lowlevel/fdc-isr.c1170
-rw-r--r--drivers/char/ftape/lowlevel/fdc-isr.h55
-rw-r--r--drivers/char/ftape/lowlevel/ftape-bsm.c491
-rw-r--r--drivers/char/ftape/lowlevel/ftape-bsm.h66
-rw-r--r--drivers/char/ftape/lowlevel/ftape-buffer.c129
-rw-r--r--drivers/char/ftape/lowlevel/ftape-buffer.h32
-rw-r--r--drivers/char/ftape/lowlevel/ftape-calibr.c276
-rw-r--r--drivers/char/ftape/lowlevel/ftape-calibr.h37
-rw-r--r--drivers/char/ftape/lowlevel/ftape-ctl.c897
-rw-r--r--drivers/char/ftape/lowlevel/ftape-ctl.h162
-rw-r--r--drivers/char/ftape/lowlevel/ftape-ecc.c853
-rw-r--r--drivers/char/ftape/lowlevel/ftape-ecc.h84
-rw-r--r--drivers/char/ftape/lowlevel/ftape-format.c344
-rw-r--r--drivers/char/ftape/lowlevel/ftape-format.h37
-rw-r--r--drivers/char/ftape/lowlevel/ftape-init.c161
-rw-r--r--drivers/char/ftape/lowlevel/ftape-init.h43
-rw-r--r--drivers/char/ftape/lowlevel/ftape-io.c992
-rw-r--r--drivers/char/ftape/lowlevel/ftape-io.h90
-rw-r--r--drivers/char/ftape/lowlevel/ftape-proc.c215
-rw-r--r--drivers/char/ftape/lowlevel/ftape-proc.h35
-rw-r--r--drivers/char/ftape/lowlevel/ftape-read.c621
-rw-r--r--drivers/char/ftape/lowlevel/ftape-read.h51
-rw-r--r--drivers/char/ftape/lowlevel/ftape-rw.c1092
-rw-r--r--drivers/char/ftape/lowlevel/ftape-rw.h111
-rw-r--r--drivers/char/ftape/lowlevel/ftape-setup.c105
-rw-r--r--drivers/char/ftape/lowlevel/ftape-tracing.c118
-rw-r--r--drivers/char/ftape/lowlevel/ftape-tracing.h180
-rw-r--r--drivers/char/ftape/lowlevel/ftape-write.c336
-rw-r--r--drivers/char/ftape/lowlevel/ftape-write.h53
-rw-r--r--drivers/char/ftape/lowlevel/ftape_syms.c88
35 files changed, 10785 insertions, 0 deletions
diff --git a/drivers/char/ftape/lowlevel/Makefile b/drivers/char/ftape/lowlevel/Makefile
new file mode 100644
index 00000000000..febab07ba42
--- /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 00000000000..9bc1cddade7
--- /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 00000000000..da7b88bca88
--- /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 00000000000..1704a2a5704
--- /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(&current->sighand->siglock);
+ old_sigmask = current->blocked;
+ sigfillset(&current->blocked);
+ recalc_sigpending();
+ spin_unlock_irq(&current->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(&current->sighand->siglock);
+ current->blocked = old_sigmask;
+ recalc_sigpending();
+ spin_unlock_irq(&current->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(&reg[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 00000000000..7ec3c72178b
--- /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 00000000000..ad2bc733ae1
--- /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 00000000000..065aa978942
--- /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 00000000000..d1a301cc344
--- /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 00000000000..ed45465af4d
--- /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 00000000000..54af20cd9a2
--- /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 00000000000..eec99cee8f8
--- /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 00000000000..956b2586e13
--- /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 00000000000..0c7e75246c7
--- /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 00000000000..32e04391179
--- /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(&current->pending.signal, _DONT_BLOCK) &&
+ !(sigtestsetmask(&current->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 00000000000..5f5e30bc361
--- /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 00000000000..e5632f674bc
--- /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 00000000000..4829146fe9a
--- /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 00000000000..5dd4c59a3f3
--- /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 00000000000..f1516156664
--- /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 00000000000..b54260d457c
--- /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 00000000000..99a7b8ab086
--- /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 00000000000..259015aeff5
--- /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 00000000000..26a7baad871
--- /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 00000000000..c66251e997e
--- /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 00000000000..264dfcc1d22
--- /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 00000000000..d967d8cd86d
--- /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 00000000000..069f99f2a98
--- /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 00000000000..c0d6dc2cbfd
--- /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(&current->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(&current->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(&current->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(&current->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 00000000000..32f4feeb887
--- /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 00000000000..280a1a55d87
--- /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 00000000000..7fdc6567440
--- /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 00000000000..fa7cd20ee66
--- /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(&current->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 00000000000..45601ec801e
--- /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 00000000000..0e7f898b7af
--- /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 00000000000..5dc3a380c9b
--- /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
+