diff options
Diffstat (limited to 'drivers/char/ftape/lowlevel/fdc-isr.c')
-rw-r--r-- | drivers/char/ftape/lowlevel/fdc-isr.c | 1170 |
1 files changed, 1170 insertions, 0 deletions
diff --git a/drivers/char/ftape/lowlevel/fdc-isr.c b/drivers/char/ftape/lowlevel/fdc-isr.c new file mode 100644 index 000000000000..ad2bc733ae1b --- /dev/null +++ b/drivers/char/ftape/lowlevel/fdc-isr.c @@ -0,0 +1,1170 @@ +/* + * Copyright (C) 1994-1996 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + + * + * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/fdc-isr.c,v $ + * $Revision: 1.9 $ + * $Date: 1997/10/17 23:01:53 $ + * + * This file contains the interrupt service routine and + * associated code for the QIC-40/80/3010/3020 floppy-tape driver + * "ftape" for Linux. + */ + +#include <asm/io.h> +#include <asm/dma.h> + +#define volatile /* */ + +#include <linux/ftape.h> +#include <linux/qic117.h> +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/fdc-isr.h" +#include "../lowlevel/fdc-io.h" +#include "../lowlevel/ftape-ctl.h" +#include "../lowlevel/ftape-rw.h" +#include "../lowlevel/ftape-io.h" +#include "../lowlevel/ftape-calibr.h" +#include "../lowlevel/ftape-bsm.h" + +/* Global vars. + */ +volatile int ft_expected_stray_interrupts; +volatile int ft_interrupt_seen; +volatile int ft_seek_completed; +volatile int ft_hide_interrupt; +/* Local vars. + */ +typedef enum { + no_error = 0, id_am_error = 0x01, id_crc_error = 0x02, + data_am_error = 0x04, data_crc_error = 0x08, + no_data_error = 0x10, overrun_error = 0x20, +} error_cause; +static int stop_read_ahead; + + +static void print_error_cause(int cause) +{ + TRACE_FUN(ft_t_any); + + switch (cause) { + case no_data_error: + TRACE(ft_t_noise, "no data error"); + break; + case id_am_error: + TRACE(ft_t_noise, "id am error"); + break; + case id_crc_error: + TRACE(ft_t_noise, "id crc error"); + break; + case data_am_error: + TRACE(ft_t_noise, "data am error"); + break; + case data_crc_error: + TRACE(ft_t_noise, "data crc error"); + break; + case overrun_error: + TRACE(ft_t_noise, "overrun error"); + break; + default:; + } + TRACE_EXIT; +} + +static char *fdc_mode_txt(fdc_mode_enum mode) +{ + switch (mode) { + case fdc_idle: + return "fdc_idle"; + case fdc_reading_data: + return "fdc_reading_data"; + case fdc_seeking: + return "fdc_seeking"; + case fdc_writing_data: + return "fdc_writing_data"; + case fdc_reading_id: + return "fdc_reading_id"; + case fdc_recalibrating: + return "fdc_recalibrating"; + case fdc_formatting: + return "fdc_formatting"; + case fdc_verifying: + return "fdc_verifying"; + default: + return "unknown"; + } +} + +static inline error_cause decode_irq_cause(fdc_mode_enum mode, __u8 st[]) +{ + error_cause cause = no_error; + TRACE_FUN(ft_t_any); + + /* Valid st[], decode cause of interrupt. + */ + switch (st[0] & ST0_INT_MASK) { + case FDC_INT_NORMAL: + TRACE(ft_t_fdc_dma,"normal completion: %s",fdc_mode_txt(mode)); + break; + case FDC_INT_ABNORMAL: + TRACE(ft_t_flow, "abnormal completion %s", fdc_mode_txt(mode)); + TRACE(ft_t_fdc_dma, "ST0: 0x%02x, ST1: 0x%02x, ST2: 0x%02x", + st[0], st[1], st[2]); + TRACE(ft_t_fdc_dma, + "C: 0x%02x, H: 0x%02x, R: 0x%02x, N: 0x%02x", + st[3], st[4], st[5], st[6]); + if (st[1] & 0x01) { + if (st[2] & 0x01) { + cause = data_am_error; + } else { + cause = id_am_error; + } + } else if (st[1] & 0x20) { + if (st[2] & 0x20) { + cause = data_crc_error; + } else { + cause = id_crc_error; + } + } else if (st[1] & 0x04) { + cause = no_data_error; + } else if (st[1] & 0x10) { + cause = overrun_error; + } + print_error_cause(cause); + break; + case FDC_INT_INVALID: + TRACE(ft_t_flow, "invalid completion %s", fdc_mode_txt(mode)); + break; + case FDC_INT_READYCH: + if (st[0] & ST0_SEEK_END) { + TRACE(ft_t_flow, "drive poll completed"); + } else { + TRACE(ft_t_flow, "ready change %s",fdc_mode_txt(mode)); + } + break; + default: + break; + } + TRACE_EXIT cause; +} + +static void update_history(error_cause cause) +{ + switch (cause) { + case id_am_error: + ft_history.id_am_errors++; + break; + case id_crc_error: + ft_history.id_crc_errors++; + break; + case data_am_error: + ft_history.data_am_errors++; + break; + case data_crc_error: + ft_history.data_crc_errors++; + break; + case overrun_error: + ft_history.overrun_errors++; + break; + case no_data_error: + ft_history.no_data_errors++; + break; + default:; + } +} + +static void skip_bad_sector(buffer_struct * buff) +{ + TRACE_FUN(ft_t_any); + + /* Mark sector as soft error and skip it + */ + if (buff->remaining > 0) { + ++buff->sector_offset; + ++buff->data_offset; + --buff->remaining; + buff->ptr += FT_SECTOR_SIZE; + buff->bad_sector_map >>= 1; + } else { + /* Hey, what is this????????????? C code: if we shift + * more than 31 bits, we get no shift. That's bad!!!!!! + */ + ++buff->sector_offset; /* hack for error maps */ + TRACE(ft_t_warn, "skipping last sector in segment"); + } + TRACE_EXIT; +} + +static void update_error_maps(buffer_struct * buff, unsigned int error_offset) +{ + int hard = 0; + TRACE_FUN(ft_t_any); + + if (buff->retry < FT_SOFT_RETRIES) { + buff->soft_error_map |= (1 << error_offset); + } else { + buff->hard_error_map |= (1 << error_offset); + buff->soft_error_map &= ~buff->hard_error_map; + buff->retry = -1; /* will be set to 0 in setup_segment */ + hard = 1; + } + TRACE(ft_t_noise, "sector %d : %s error\n" + KERN_INFO "hard map: 0x%08lx\n" + KERN_INFO "soft map: 0x%08lx", + FT_SECTOR(error_offset), hard ? "hard" : "soft", + (long) buff->hard_error_map, (long) buff->soft_error_map); + TRACE_EXIT; +} + +static void print_progress(buffer_struct *buff, error_cause cause) +{ + TRACE_FUN(ft_t_any); + + switch (cause) { + case no_error: + TRACE(ft_t_flow,"%d Sector(s) transferred", buff->sector_count); + break; + case no_data_error: + TRACE(ft_t_flow, "Sector %d not found", + FT_SECTOR(buff->sector_offset)); + break; + case overrun_error: + /* got an overrun error on the first byte, must be a + * hardware problem + */ + TRACE(ft_t_bug, + "Unexpected error: failing DMA or FDC controller ?"); + break; + case data_crc_error: + TRACE(ft_t_flow, "Error in sector %d", + FT_SECTOR(buff->sector_offset - 1)); + break; + case id_crc_error: + case id_am_error: + case data_am_error: + TRACE(ft_t_flow, "Error in sector %d", + FT_SECTOR(buff->sector_offset)); + break; + default: + TRACE(ft_t_flow, "Unexpected error at sector %d", + FT_SECTOR(buff->sector_offset)); + break; + } + TRACE_EXIT; +} + +/* + * Error cause: Amount xferred: Action: + * + * id_am_error 0 mark bad and skip + * id_crc_error 0 mark bad and skip + * data_am_error 0 mark bad and skip + * data_crc_error % 1024 mark bad and skip + * no_data_error 0 retry on write + * mark bad and skip on read + * overrun_error [ 0..all-1 ] mark bad and skip + * no_error all continue + */ + +/* the arg `sector' is returned by the fdc and tells us at which sector we + * are positioned at (relative to starting sector of segment) + */ +static void determine_verify_progress(buffer_struct *buff, + error_cause cause, + __u8 sector) +{ + TRACE_FUN(ft_t_any); + + if (cause == no_error && sector == 1) { + buff->sector_offset = FT_SECTORS_PER_SEGMENT; + buff->remaining = 0; + if (TRACE_LEVEL >= ft_t_flow) { + print_progress(buff, cause); + } + } else { + buff->sector_offset = sector - buff->sect; + buff->remaining = FT_SECTORS_PER_SEGMENT - buff->sector_offset; + TRACE(ft_t_noise, "%ssector offset: 0x%04x", + (cause == no_error) ? "unexpected " : "", + buff->sector_offset); + switch (cause) { + case overrun_error: + break; +#if 0 + case no_data_error: + buff->retry = FT_SOFT_RETRIES; + if (buff->hard_error_map && + buff->sector_offset > 1 && + (buff->hard_error_map & + (1 << (buff->sector_offset-2)))) { + buff->retry --; + } + break; +#endif + default: + buff->retry = FT_SOFT_RETRIES; + break; + } + if (TRACE_LEVEL >= ft_t_flow) { + print_progress(buff, cause); + } + /* Sector_offset points to the problem area Now adjust + * sector_offset so it always points one past he failing + * sector. I.e. skip the bad sector. + */ + ++buff->sector_offset; + --buff->remaining; + update_error_maps(buff, buff->sector_offset - 1); + } + TRACE_EXIT; +} + +static void determine_progress(buffer_struct *buff, + error_cause cause, + __u8 sector) +{ + unsigned int dma_residue; + TRACE_FUN(ft_t_any); + + /* Using less preferred order of disable_dma and + * get_dma_residue because this seems to fail on at least one + * system if reversed! + */ + dma_residue = get_dma_residue(fdc.dma); + disable_dma(fdc.dma); + if (cause != no_error || dma_residue != 0) { + TRACE(ft_t_noise, "%sDMA residue: 0x%04x", + (cause == no_error) ? "unexpected " : "", + dma_residue); + /* adjust to actual value: */ + if (dma_residue == 0) { + /* this happens sometimes with overrun errors. + * I don't know whether we could ignore the + * overrun error. Play save. + */ + buff->sector_count --; + } else { + buff->sector_count -= ((dma_residue + + (FT_SECTOR_SIZE - 1)) / + FT_SECTOR_SIZE); + } + } + /* Update var's influenced by the DMA operation. + */ + if (buff->sector_count > 0) { + buff->sector_offset += buff->sector_count; + buff->data_offset += buff->sector_count; + buff->ptr += (buff->sector_count * + FT_SECTOR_SIZE); + buff->remaining -= buff->sector_count; + buff->bad_sector_map >>= buff->sector_count; + } + if (TRACE_LEVEL >= ft_t_flow) { + print_progress(buff, cause); + } + if (cause != no_error) { + if (buff->remaining == 0) { + TRACE(ft_t_warn, "foo?\n" + KERN_INFO "count : %d\n" + KERN_INFO "offset: %d\n" + KERN_INFO "soft : %08x\n" + KERN_INFO "hard : %08x", + buff->sector_count, + buff->sector_offset, + buff->soft_error_map, + buff->hard_error_map); + } + /* Sector_offset points to the problem area, except if we got + * a data_crc_error. In that case it points one past the + * failing sector. + * + * Now adjust sector_offset so it always points one past he + * failing sector. I.e. skip the bad sector. + */ + if (cause != data_crc_error) { + skip_bad_sector(buff); + } + update_error_maps(buff, buff->sector_offset - 1); + } + TRACE_EXIT; +} + +static int calc_steps(int cmd) +{ + if (ftape_current_cylinder > cmd) { + return ftape_current_cylinder - cmd; + } else { + return ftape_current_cylinder + cmd; + } +} + +static void pause_tape(int retry, int mode) +{ + int result; + __u8 out[3] = {FDC_SEEK, ft_drive_sel, 0}; + TRACE_FUN(ft_t_any); + + /* We'll use a raw seek command to get the tape to rewind and + * stop for a retry. + */ + ++ft_history.rewinds; + if (qic117_cmds[ftape_current_command].non_intr) { + TRACE(ft_t_warn, "motion command may be issued too soon"); + } + if (retry && (mode == fdc_reading_data || + mode == fdc_reading_id || + mode == fdc_verifying)) { + ftape_current_command = QIC_MICRO_STEP_PAUSE; + ftape_might_be_off_track = 1; + } else { + ftape_current_command = QIC_PAUSE; + } + out[2] = calc_steps(ftape_current_command); + result = fdc_command(out, 3); /* issue QIC_117 command */ + ftape_current_cylinder = out[ 2]; + if (result < 0) { + TRACE(ft_t_noise, "qic-pause failed, status = %d", result); + } else { + ft_location.known = 0; + ft_runner_status = idle; + ft_hide_interrupt = 1; + ftape_tape_running = 0; + } + TRACE_EXIT; +} + +static void continue_xfer(buffer_struct *buff, + fdc_mode_enum mode, + unsigned int skip) +{ + int write = 0; + TRACE_FUN(ft_t_any); + + if (mode == fdc_writing_data || mode == fdc_deleting) { + write = 1; + } + /* This part can be removed if it never happens + */ + if (skip > 0 && + (ft_runner_status != running || + (write && (buff->status != writing)) || + (!write && (buff->status != reading && + buff->status != verifying)))) { + TRACE(ft_t_err, "unexpected runner/buffer state %d/%d", + ft_runner_status, buff->status); + buff->status = error; + /* finish this buffer: */ + (void)ftape_next_buffer(ft_queue_head); + ft_runner_status = aborting; + fdc_mode = fdc_idle; + } else if (buff->remaining > 0 && ftape_calc_next_cluster(buff) > 0) { + /* still sectors left in current segment, continue + * with this segment + */ + if (fdc_setup_read_write(buff, mode) < 0) { + /* failed, abort operation + */ + buff->bytes = buff->ptr - buff->address; + buff->status = error; + /* finish this buffer: */ + (void)ftape_next_buffer(ft_queue_head); + ft_runner_status = aborting; + fdc_mode = fdc_idle; + } + } else { + /* current segment completed + */ + unsigned int last_segment = buff->segment_id; + int eot = ((last_segment + 1) % ft_segments_per_track) == 0; + unsigned int next = buff->next_segment; /* 0 means stop ! */ + + buff->bytes = buff->ptr - buff->address; + buff->status = done; + buff = ftape_next_buffer(ft_queue_head); + if (eot) { + /* finished last segment on current track, + * can't continue + */ + ft_runner_status = logical_eot; + fdc_mode = fdc_idle; + TRACE_EXIT; + } + if (next <= 0) { + /* don't continue with next segment + */ + TRACE(ft_t_noise, "no %s allowed, stopping tape", + (write) ? "write next" : "read ahead"); + pause_tape(0, mode); + ft_runner_status = idle; /* not quite true until + * next irq + */ + TRACE_EXIT; + } + /* continue with next segment + */ + if (buff->status != waiting) { + TRACE(ft_t_noise, "all input buffers %s, pausing tape", + (write) ? "empty" : "full"); + pause_tape(0, mode); + ft_runner_status = idle; /* not quite true until + * next irq + */ + TRACE_EXIT; + } + if (write && next != buff->segment_id) { + TRACE(ft_t_noise, + "segments out of order, aborting write"); + ft_runner_status = do_abort; + fdc_mode = fdc_idle; + TRACE_EXIT; + } + ftape_setup_new_segment(buff, next, 0); + if (stop_read_ahead) { + buff->next_segment = 0; + stop_read_ahead = 0; + } + if (ftape_calc_next_cluster(buff) == 0 || + fdc_setup_read_write(buff, mode) != 0) { + TRACE(ft_t_err, "couldn't start %s-ahead", + write ? "write" : "read"); + ft_runner_status = do_abort; + fdc_mode = fdc_idle; + } else { + /* keep on going */ + switch (ft_driver_state) { + case reading: buff->status = reading; break; + case verifying: buff->status = verifying; break; + case writing: buff->status = writing; break; + case deleting: buff->status = deleting; break; + default: + TRACE(ft_t_err, + "BUG: ft_driver_state %d should be one out of " + "{reading, writing, verifying, deleting}", + ft_driver_state); + buff->status = write ? writing : reading; + break; + } + } + } + TRACE_EXIT; +} + +static void retry_sector(buffer_struct *buff, + int mode, + unsigned int skip) +{ + TRACE_FUN(ft_t_any); + + TRACE(ft_t_noise, "%s error, will retry", + (mode == fdc_writing_data || mode == fdc_deleting) ? "write" : "read"); + pause_tape(1, mode); + ft_runner_status = aborting; + buff->status = error; + buff->skip = skip; + TRACE_EXIT; +} + +static unsigned int find_resume_point(buffer_struct *buff) +{ + int i = 0; + SectorMap mask; + SectorMap map; + TRACE_FUN(ft_t_any); + + /* This function is to be called after all variables have been + * updated to point past the failing sector. + * If there are any soft errors before the failing sector, + * find the first soft error and return the sector offset. + * Otherwise find the last hard error. + * Note: there should always be at least one hard or soft error ! + */ + if (buff->sector_offset < 1 || buff->sector_offset > 32) { + TRACE(ft_t_bug, "BUG: sector_offset = %d", + buff->sector_offset); + TRACE_EXIT 0; + } + if (buff->sector_offset >= 32) { /* C-limitation on shift ! */ + mask = 0xffffffff; + } else { + mask = (1 << buff->sector_offset) - 1; + } + map = buff->soft_error_map & mask; + if (map) { + while ((map & (1 << i)) == 0) { + ++i; + } + TRACE(ft_t_noise, "at sector %d", FT_SECTOR(i)); + } else { + map = buff->hard_error_map & mask; + i = buff->sector_offset - 1; + if (map) { + while ((map & (1 << i)) == 0) { + --i; + } + TRACE(ft_t_noise, "after sector %d", FT_SECTOR(i)); + ++i; /* first sector after last hard error */ + } else { + TRACE(ft_t_bug, "BUG: no soft or hard errors"); + } + } + TRACE_EXIT i; +} + +/* check possible dma residue when formatting, update position record in + * buffer struct. This is, of course, modelled after determine_progress(), but + * we don't need to set up for retries because the format process cannot be + * interrupted (except at the end of the tape track). + */ +static int determine_fmt_progress(buffer_struct *buff, error_cause cause) +{ + unsigned int dma_residue; + TRACE_FUN(ft_t_any); + + /* Using less preferred order of disable_dma and + * get_dma_residue because this seems to fail on at least one + * system if reversed! + */ + dma_residue = get_dma_residue(fdc.dma); + disable_dma(fdc.dma); + if (cause != no_error || dma_residue != 0) { + TRACE(ft_t_info, "DMA residue = 0x%04x", dma_residue); + fdc_mode = fdc_idle; + switch(cause) { + case no_error: + ft_runner_status = aborting; + buff->status = idle; + break; + case overrun_error: + /* got an overrun error on the first byte, must be a + * hardware problem + */ + TRACE(ft_t_bug, + "Unexpected error: failing DMA controller ?"); + ft_runner_status = do_abort; + buff->status = error; + break; + default: + TRACE(ft_t_noise, "Unexpected error at segment %d", + buff->segment_id); + ft_runner_status = do_abort; + buff->status = error; + break; + } + TRACE_EXIT -EIO; /* can only retry entire track in format mode + */ + } + /* Update var's influenced by the DMA operation. + */ + buff->ptr += FT_SECTORS_PER_SEGMENT * 4; + buff->bytes -= FT_SECTORS_PER_SEGMENT * 4; + buff->remaining -= FT_SECTORS_PER_SEGMENT; + buff->segment_id ++; /* done with segment */ + TRACE_EXIT 0; +} + +/* + * Continue formatting, switch buffers if there is no data left in + * current buffer. This is, of course, modelled after + * continue_xfer(), but we don't need to set up for retries because + * the format process cannot be interrupted (except at the end of the + * tape track). + */ +static void continue_formatting(buffer_struct *buff) +{ + TRACE_FUN(ft_t_any); + + if (buff->remaining <= 0) { /* no space left in dma buffer */ + unsigned int next = buff->next_segment; + + if (next == 0) { /* end of tape track */ + buff->status = done; + ft_runner_status = logical_eot; + fdc_mode = fdc_idle; + TRACE(ft_t_noise, "Done formatting track %d", + ft_location.track); + TRACE_EXIT; + } + /* + * switch to next buffer! + */ + buff->status = done; + buff = ftape_next_buffer(ft_queue_head); + + if (buff->status != waiting || next != buff->segment_id) { + goto format_setup_error; + } + } + if (fdc_setup_formatting(buff) < 0) { + goto format_setup_error; + } + buff->status = formatting; + TRACE(ft_t_fdc_dma, "Formatting segment %d on track %d", + buff->segment_id, ft_location.track); + TRACE_EXIT; + format_setup_error: + ft_runner_status = do_abort; + fdc_mode = fdc_idle; + buff->status = error; + TRACE(ft_t_err, "Error setting up for segment %d on track %d", + buff->segment_id, ft_location.track); + TRACE_EXIT; + +} + +/* this handles writing, read id, reading and formatting + */ +static void handle_fdc_busy(buffer_struct *buff) +{ + static int no_data_error_count; + int retry = 0; + error_cause cause; + __u8 in[7]; + int skip; + fdc_mode_enum fmode = fdc_mode; + TRACE_FUN(ft_t_any); + + if (fdc_result(in, 7) < 0) { /* better get it fast ! */ + TRACE(ft_t_err, + "Probably fatal error during FDC Result Phase\n" + KERN_INFO + "drive may hang until (power on) reset :-("); + /* what to do next ???? + */ + TRACE_EXIT; + } + cause = decode_irq_cause(fdc_mode, in); +#ifdef TESTING + { int i; + for (i = 0; i < (int)ft_nr_buffers; ++i) + TRACE(ft_t_any, "buffer[%d] status: %d, segment_id: %d", + i, ft_buffer[i]->status, ft_buffer[i]->segment_id); + } +#endif + if (fmode == fdc_reading_data && ft_driver_state == verifying) { + fmode = fdc_verifying; + } + switch (fmode) { + case fdc_verifying: + if (ft_runner_status == aborting || + ft_runner_status == do_abort) { + TRACE(ft_t_noise,"aborting %s",fdc_mode_txt(fdc_mode)); + break; + } + if (buff->retry > 0) { + TRACE(ft_t_flow, "this is retry nr %d", buff->retry); + } + switch (cause) { + case no_error: + no_data_error_count = 0; + determine_verify_progress(buff, cause, in[5]); + if (in[2] & 0x40) { + /* This should not happen when verifying + */ + TRACE(ft_t_warn, + "deleted data in segment %d/%d", + buff->segment_id, + FT_SECTOR(buff->sector_offset - 1)); + buff->remaining = 0; /* abort transfer */ + buff->hard_error_map = EMPTY_SEGMENT; + skip = 1; + } else { + skip = 0; + } + continue_xfer(buff, fdc_mode, skip); + break; + case no_data_error: + no_data_error_count ++; + case overrun_error: + retry ++; + case id_am_error: + case id_crc_error: + case data_am_error: + case data_crc_error: + determine_verify_progress(buff, cause, in[5]); + if (cause == no_data_error) { + if (no_data_error_count >= 2) { + TRACE(ft_t_warn, + "retrying because of successive " + "no data errors"); + no_data_error_count = 0; + } else { + retry --; + } + } else { + no_data_error_count = 0; + } + if (retry) { + skip = find_resume_point(buff); + } else { + skip = buff->sector_offset; + } + if (retry && skip < 32) { + retry_sector(buff, fdc_mode, skip); + } else { + continue_xfer(buff, fdc_mode, skip); + } + update_history(cause); + break; + default: + /* Don't know why this could happen + * but find out. + */ + determine_verify_progress(buff, cause, in[5]); + retry_sector(buff, fdc_mode, 0); + TRACE(ft_t_err, "Error: unexpected error"); + break; + } + break; + case fdc_reading_data: +#ifdef TESTING + /* I'm sorry, but: NOBODY ever used this trace + * messages for ages. I guess that Bas was the last person + * that ever really used this (thank you, between the lines) + */ + if (cause == no_error) { + TRACE(ft_t_flow,"reading segment %d",buff->segment_id); + } else { + TRACE(ft_t_noise, "error reading segment %d", + buff->segment_id); + TRACE(ft_t_noise, "\n" + KERN_INFO + "IRQ:C: 0x%02x, H: 0x%02x, R: 0x%02x, N: 0x%02x\n" + KERN_INFO + "BUF:C: 0x%02x, H: 0x%02x, R: 0x%02x", + in[3], in[4], in[5], in[6], + buff->cyl, buff->head, buff->sect); + } +#endif + if (ft_runner_status == aborting || + ft_runner_status == do_abort) { + TRACE(ft_t_noise,"aborting %s",fdc_mode_txt(fdc_mode)); + break; + } + if (buff->bad_sector_map == FAKE_SEGMENT) { + /* This condition occurs when reading a `fake' + * sector that's not accessible. Doesn't + * really matter as we would have ignored it + * anyway ! + * + * Chance is that we're past the next segment + * now, so the next operation may fail and + * result in a retry. + */ + buff->remaining = 0; /* skip failing sector */ + /* buff->ptr = buff->address; */ + /* fake success: */ + continue_xfer(buff, fdc_mode, 1); + /* trace calls are expensive: place them AFTER + * the real stuff has been done. + * + */ + TRACE(ft_t_noise, "skipping empty segment %d (read), size? %d", + buff->segment_id, buff->ptr - buff->address); + TRACE_EXIT; + } + if (buff->retry > 0) { + TRACE(ft_t_flow, "this is retry nr %d", buff->retry); + } + switch (cause) { + case no_error: + determine_progress(buff, cause, in[5]); + if (in[2] & 0x40) { + /* Handle deleted data in header segments. + * Skip segment and force read-ahead. + */ + TRACE(ft_t_warn, + "deleted data in segment %d/%d", + buff->segment_id, + FT_SECTOR(buff->sector_offset - 1)); + buff->deleted = 1; + buff->remaining = 0;/*abort transfer */ + buff->soft_error_map |= + (-1L << buff->sector_offset); + if (buff->segment_id == 0) { + /* stop on next segment */ + stop_read_ahead = 1; + } + /* force read-ahead: */ + buff->next_segment = + buff->segment_id + 1; + skip = (FT_SECTORS_PER_SEGMENT - + buff->sector_offset); + } else { + skip = 0; + } + continue_xfer(buff, fdc_mode, skip); + break; + case no_data_error: + /* Tape started too far ahead of or behind the + * right sector. This may also happen in the + * middle of a segment ! + * + * Handle no-data as soft error. If next + * sector fails too, a retry (with needed + * reposition) will follow. + */ + retry ++; + case id_am_error: + case id_crc_error: + case data_am_error: + case data_crc_error: + case overrun_error: + retry += (buff->soft_error_map != 0 || + buff->hard_error_map != 0); + determine_progress(buff, cause, in[5]); +#if 1 || defined(TESTING) + if (cause == overrun_error) retry ++; +#endif + if (retry) { + skip = find_resume_point(buff); + } else { + skip = buff->sector_offset; + } + /* Try to resume with next sector on single + * errors (let ecc correct it), but retry on + * no_data (we'll be past the target when we + * get here so we cannot retry) or on + * multiple errors (reduce chance on ecc + * failure). + */ + /* cH: 23/02/97: if the last sector in the + * segment was a hard error, then there is + * no sense in a retry. This occasion seldom + * occurs but ... @:³²¸`@%&§$ + */ + if (retry && skip < 32) { + retry_sector(buff, fdc_mode, skip); + } else { + continue_xfer(buff, fdc_mode, skip); + } + update_history(cause); + break; + default: + /* Don't know why this could happen + * but find out. + */ + determine_progress(buff, cause, in[5]); + retry_sector(buff, fdc_mode, 0); + TRACE(ft_t_err, "Error: unexpected error"); + break; + } + break; + case fdc_reading_id: + if (cause == no_error) { + fdc_cyl = in[3]; + fdc_head = in[4]; + fdc_sect = in[5]; + TRACE(ft_t_fdc_dma, + "id read: C: 0x%02x, H: 0x%02x, R: 0x%02x", + fdc_cyl, fdc_head, fdc_sect); + } else { /* no valid information, use invalid sector */ + fdc_cyl = fdc_head = fdc_sect = 0; + TRACE(ft_t_flow, "Didn't find valid sector Id"); + } + fdc_mode = fdc_idle; + break; + case fdc_deleting: + case fdc_writing_data: +#ifdef TESTING + if (cause == no_error) { + TRACE(ft_t_flow, "writing segment %d", buff->segment_id); + } else { + TRACE(ft_t_noise, "error writing segment %d", + buff->segment_id); + } +#endif + if (ft_runner_status == aborting || + ft_runner_status == do_abort) { + TRACE(ft_t_flow, "aborting %s",fdc_mode_txt(fdc_mode)); + break; + } + if (buff->retry > 0) { + TRACE(ft_t_flow, "this is retry nr %d", buff->retry); + } + if (buff->bad_sector_map == FAKE_SEGMENT) { + /* This condition occurs when trying to write to a + * `fake' sector that's not accessible. Doesn't really + * matter as it isn't used anyway ! Might be located + * at wrong segment, then we'll fail on the next + * segment. + */ + TRACE(ft_t_noise, "skipping empty segment (write)"); + buff->remaining = 0; /* skip failing sector */ + /* fake success: */ + continue_xfer(buff, fdc_mode, 1); + break; + } + switch (cause) { + case no_error: + determine_progress(buff, cause, in[5]); + continue_xfer(buff, fdc_mode, 0); + break; + case no_data_error: + case id_am_error: + case id_crc_error: + case data_am_error: + case overrun_error: + update_history(cause); + determine_progress(buff, cause, in[5]); + skip = find_resume_point(buff); + retry_sector(buff, fdc_mode, skip); + break; + default: + if (in[1] & 0x02) { + TRACE(ft_t_err, "media not writable"); + } else { + TRACE(ft_t_bug, "unforeseen write error"); + } + fdc_mode = fdc_idle; + break; + } + break; /* fdc_deleting || fdc_writing_data */ + case fdc_formatting: + /* The interrupt comes after formatting a segment. We then + * have to set up QUICKLY for the next segment. But + * afterwards, there is plenty of time. + */ + switch (cause) { + case no_error: + /* would like to keep most of the formatting stuff + * outside the isr code, but timing is too critical + */ + if (determine_fmt_progress(buff, cause) >= 0) { + continue_formatting(buff); + } + break; + case no_data_error: + case id_am_error: + case id_crc_error: + case data_am_error: + case overrun_error: + default: + determine_fmt_progress(buff, cause); + update_history(cause); + if (in[1] & 0x02) { + TRACE(ft_t_err, "media not writable"); + } else { + TRACE(ft_t_bug, "unforeseen write error"); + } + break; + } /* cause */ + break; + default: + TRACE(ft_t_warn, "Warning: unexpected irq during: %s", + fdc_mode_txt(fdc_mode)); + fdc_mode = fdc_idle; + break; + } + TRACE_EXIT; +} + +/* FDC interrupt service routine. + */ +void fdc_isr(void) +{ + static int isr_active; +#ifdef TESTING + unsigned int t0 = ftape_timestamp(); +#endif + TRACE_FUN(ft_t_any); + + if (isr_active++) { + --isr_active; + TRACE(ft_t_bug, "BUG: nested interrupt, not good !"); + *fdc.hook = fdc_isr; /* hook our handler into the fdc + * code again + */ + TRACE_EXIT; + } + sti(); + if (inb_p(fdc.msr) & FDC_BUSY) { /* Entering Result Phase */ + ft_hide_interrupt = 0; + handle_fdc_busy(ftape_get_buffer(ft_queue_head)); + if (ft_runner_status == do_abort) { + /* cease operation, remember tape position + */ + TRACE(ft_t_flow, "runner aborting"); + ft_runner_status = aborting; + ++ft_expected_stray_interrupts; + } + } else { /* !FDC_BUSY */ + /* clear interrupt, cause should be gotten by issuing + * a Sense Interrupt Status command. + */ + if (fdc_mode == fdc_recalibrating || fdc_mode == fdc_seeking) { + if (ft_hide_interrupt) { + int st0; + int pcn; + + if (fdc_sense_interrupt_status(&st0, &pcn) < 0) + TRACE(ft_t_err, + "sense interrupt status failed"); + ftape_current_cylinder = pcn; + TRACE(ft_t_flow, "handled hidden interrupt"); + } + ft_seek_completed = 1; + fdc_mode = fdc_idle; + } else if (!waitqueue_active(&ftape_wait_intr)) { + if (ft_expected_stray_interrupts == 0) { + TRACE(ft_t_warn, "unexpected stray interrupt"); + } else { + TRACE(ft_t_flow, "expected stray interrupt"); + --ft_expected_stray_interrupts; + } + } else { + if (fdc_mode == fdc_reading_data || + fdc_mode == fdc_verifying || + fdc_mode == fdc_writing_data || + fdc_mode == fdc_deleting || + fdc_mode == fdc_formatting || + fdc_mode == fdc_reading_id) { + if (inb_p(fdc.msr) & FDC_BUSY) { + TRACE(ft_t_bug, + "***** FDC failure, busy too late"); + } else { + TRACE(ft_t_bug, + "***** FDC failure, no busy"); + } + } else { + TRACE(ft_t_fdc_dma, "awaited stray interrupt"); + } + } + ft_hide_interrupt = 0; + } + /* Handle sleep code. + */ + if (!ft_hide_interrupt) { + ft_interrupt_seen ++; + if (waitqueue_active(&ftape_wait_intr)) { + wake_up_interruptible(&ftape_wait_intr); + } + } else { + TRACE(ft_t_flow, "hiding interrupt while %s", + waitqueue_active(&ftape_wait_intr) ? "waiting":"active"); + } +#ifdef TESTING + t0 = ftape_timediff(t0, ftape_timestamp()); + if (t0 >= 1000) { + /* only tell us about long calls */ + TRACE(ft_t_noise, "isr() duration: %5d usec", t0); + } +#endif + *fdc.hook = fdc_isr; /* hook our handler into the fdc code again */ + --isr_active; + TRACE_EXIT; +} |