1171 lines
31 KiB
C
1171 lines
31 KiB
C
|
/*
|
|||
|
* 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 ... @:<EFBFBD><EFBFBD><EFBFBD>`@%&<EFBFBD>$
|
|||
|
*/
|
|||
|
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;
|
|||
|
}
|