/* * 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 #include #define volatile /* */ #include #include #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; }