patch-2.1.66 linux/drivers/char/ftape/lowlevel/fdc-isr.c

Next file: linux/drivers/char/ftape/lowlevel/fdc-isr.h
Previous file: linux/drivers/char/ftape/lowlevel/fdc-io.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.65/linux/drivers/char/ftape/lowlevel/fdc-isr.c linux/drivers/char/ftape/lowlevel/fdc-isr.c
@@ -0,0 +1,1185 @@
+/*
+ *      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 = 0;
+volatile int ft_interrupt_seen = 0;
+volatile int ft_seek_completed = 0;
+volatile int ft_hide_interrupt = 0;
+/*      Local vars.
+ */
+typedef enum {
+	no_error = 0, id_am_error = 0x01, id_crc_error = 0x02,
+	data_am_error = 0x04, data_crc_error = 0x08,
+	no_data_error = 0x10, overrun_error = 0x20,
+} error_cause;
+static int stop_read_ahead = 0;
+
+
+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) transfered", 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 = 0;
+	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 = 0;
+#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;
+#if LINUX_VERSION_CODE >= KERNEL_VER(2,0,16)
+		} else if (!waitqueue_active(&ftape_wait_intr)) {
+#else
+		} else if (!ftape_wait_intr) {
+#endif
+			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 LINUX_VERSION_CODE >= KERNEL_VER(2,0,16)
+		if (waitqueue_active(&ftape_wait_intr)) {
+			wake_up_interruptible(&ftape_wait_intr);
+		}
+#else
+		if (ftape_wait_intr) {
+			wake_up_interruptible(&ftape_wait_intr);
+		}
+#endif
+	} else {
+#if LINUX_VERSION_CODE >= KERNEL_VER(2,0,16)
+		TRACE(ft_t_flow, "hiding interrupt while %s", 
+		      waitqueue_active(&ftape_wait_intr) ? "waiting":"active");
+#else
+		TRACE(ft_t_flow, "hiding interrupt while %s", 
+		      ftape_wait_intr ? "waiting" : "active");
+#endif
+	}
+#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;
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov