URL
https://opencores.org/ocsvn/or1k/or1k/trunk
Subversion Repositories or1k
[/] [or1k/] [trunk/] [rc203soc/] [sw/] [uClinux/] [drivers/] [char/] [ftape/] [fdc-isr.c] - Rev 1765
Compare with Previous | Blame | View Log
/* * Copyright (C) 1994-1995 Bas Laarhoven. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. $Source: /home/marcus/revision_ctrl_test/oc_cvs/cvs/or1k/rc203soc/sw/uClinux/drivers/char/ftape/fdc-isr.c,v $ $Author: jcastillo $ * $Revision: 1.1 $ $Date: 2005-12-20 10:16:52 $ $State: Exp $ * * This file contains the interrupt service routine and associated * code for the QIC-40/80 tape streamer device driver. */ #include <linux/ftape.h> #include <asm/io.h> #include <asm/dma.h> #define volatile /* */ #include "tracing.h" #include "fdc-isr.h" #include "qic117.h" #include "fdc-io.h" #include "ftape-ctl.h" #include "ftape-rw.h" #include "ftape-io.h" #include "calibr.h" #include "ftape-bsm.h" /* Global vars. */ volatile int expected_stray_interrupts = 0; volatile int seek_completed = 0; volatile int interrupt_seen = 0; volatile int expect_stray_interrupt = 0; int random_rw = 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 hide_interrupt; static int stop_read_ahead = 0; static void print_error_cause(int cause) { TRACE_FUN(8, "print_error_cause"); switch (cause) { case no_data_error: TRACE(4, "no data error"); break; case id_am_error: TRACE(4, "id am error"); break; case id_crc_error: TRACE(4, "id crc error"); break; case data_am_error: TRACE(4, "data am error"); break; case data_crc_error: TRACE(4, "data crc error"); break; case overrun_error: TRACE(4, "overrun error"); break; default: } TRACE_EXIT; } static char * get_fdc_mode_text(fdc_mode_enum fdc_mode) { switch (fdc_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"; default: return "unknown"; } } static void decode_irq_cause(fdc_mode_enum fdc_mode, byte st[], char **fdc_mode_txt, error_cause * cause) { TRACE_FUN(8, "decode_irq_cause"); /* Valid st[], decode cause of interrupt. */ *fdc_mode_txt = get_fdc_mode_text(fdc_mode); switch (st[0] & ST0_INT_MASK) { case FDC_INT_NORMAL: TRACEx1(fdc_mode == fdc_reading_id ? 6 : 5, "normal completion: %s", *fdc_mode_txt); *cause = no_error; break; case FDC_INT_ABNORMAL: TRACEx1(5, "abnormal completion %s", *fdc_mode_txt); TRACEx3(6, "ST0: 0x%02x, ST1: 0x%02x, ST2: 0x%02x", st[0], st[1], st[2]); TRACEx4(6, "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: TRACEx1(5, "invalid completion %s", *fdc_mode_txt); *cause = no_error; break; case FDC_INT_READYCH: TRACEx1(5, "ready change %s", *fdc_mode_txt); *cause = no_error; break; default: } TRACE_EXIT; } static void update_history(error_cause cause) { switch (cause) { case id_am_error: history.id_am_errors++; break; case id_crc_error: history.id_crc_errors++; break; case data_am_error: history.data_am_errors++; break; case data_crc_error: history.data_crc_errors++; break; case overrun_error: history.overrun_errors++; break; case no_data_error: history.no_data_errors++; break; default: } } static void skip_bad_sector(buffer_struct * buff) { TRACE_FUN(8, "skip_bad_sector"); /* Mark sector as soft error and skip it */ if (buff->remaining > 0) { ++buff->sector_offset; ++buff->data_offset; --buff->remaining; buff->ptr += SECTOR_SIZE; buff->bad_sector_map >>= 1; } else { ++buff->sector_offset; /* hack for error maps */ TRACE(1, "skipping last sector in segment"); } TRACE_EXIT; } static void update_error_maps(buffer_struct * buff, unsigned error_offset) { TRACE_FUN(8, "update_error_maps"); int hard = 0; /* error_offset is a sector offset ! */ if (buff->retry < 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; } TRACEx2(4, "sector %d : %s error", SECTOR(error_offset), hard ? "hard" : "soft"); TRACEx2(5, "hard map: 0x%08lx, soft map: 0x%08lx", buff->hard_error_map, buff->soft_error_map); 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 */ static void determine_progress(buffer_struct * buff, error_cause cause, int mode) { TRACE_FUN(8, "determine_progress"); unsigned nr_not_xferred; unsigned nr_xferred; unsigned dma_residue; /* 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); nr_xferred = buff->sector_count * SECTOR_SIZE - dma_residue; if (cause == no_error && dma_residue == 0) { nr_not_xferred = 0; } else { if (cause == no_error) { TRACEx1(4, "unexpected DMA residue: 0x%04x", dma_residue); } else { TRACEx1(6, "DMA residue = 0x%04x", dma_residue); } nr_not_xferred = ((dma_residue + (SECTOR_SIZE - 1)) / SECTOR_SIZE); buff->sector_count -= nr_not_xferred; /* adjust to actual value */ } /* 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 * SECTOR_SIZE; buff->remaining -= buff->sector_count; buff->bad_sector_map >>= buff->sector_count; } if (cause == no_error) { TRACEx1(5, "%d Sector(s) transferred", buff->sector_count); } else if (cause == no_data_error) { TRACEx1(5, "Sector %d not found", SECTOR(buff->sector_offset)); } else if (nr_xferred > 0 || cause == id_crc_error || cause == id_am_error || cause == data_am_error) { TRACEx1(5, "Error in sector %d", SECTOR(buff->sector_offset)); } else if (cause == overrun_error) { /* got an overrun error on the first byte, must be a hardware problem */ TRACE(-1, "Unexpected error: failing DMA controller ?"); } else { TRACEx1(4, "Unexpected error at sector %d", SECTOR(buff->sector_offset)); } /* 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 != no_error) { 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 (current_cylinder > cmd) { return current_cylinder - cmd; } else { return current_cylinder + cmd; } } static void pause_tape(unsigned segment, int retry, int fdc_mode) { TRACE_FUN(8, "pause_tape"); int result; /* The 3rd initializer needs to be explicit or else gcc will * generate a reference to memset :-( */ byte out[3] = {FDC_SEEK, FTAPE_UNIT, 0}; /* We'll use a raw seek command to get the tape to rewind * and stop for a retry. */ ++history.rewinds; if (qic117_cmds[current_command].non_intr) { TRACE(2, "motion command may be issued too soon"); } if (retry && (fdc_mode == fdc_reading_data || fdc_mode == fdc_reading_id)) { current_command = QIC_MICRO_STEP_PAUSE; might_be_off_track = 1; } else { current_command = QIC_PAUSE; } out[2] = calc_steps(current_command); result = fdc_command(out, 3); /* issue QIC_117 command */ if (result < 0) { TRACEx1(4, "qic-pause failed, status = %d", result); } else { location.known = 0; runner_status = idle; hide_interrupt = 1; tape_running = 0; } TRACE_EXIT; } static void stop_tape(unsigned segment) { TRACE_FUN(8, "stop_tape"); int result; byte out[3] = {FDC_SEEK, FTAPE_UNIT, calc_steps(QIC_STOP_TAPE)}; if (qic117_cmds[current_command].non_intr) { TRACE(2, "motion command may be issued too soon"); } current_command = QIC_STOP_TAPE; /* We'll use a raw seek command to get the tape to stop */ result = fdc_command(out, 3); /* issue QIC_117 command */ if (result < 0) { TRACEx1(4, "qic-stop failed, status = %d", result); } else { runner_status = idle; hide_interrupt = 1; tape_running = 0; } TRACE_EXIT; } static void continue_xfer(buffer_struct ** p_buff, error_cause cause, int fdc_mode, unsigned skip) { TRACE_FUN(8, "continue_xfer"); buffer_struct *buff = *p_buff; int write = (fdc_mode == fdc_writing_data); byte fdc_op = (write) ? FDC_WRITE : FDC_READ; if (skip > 0) { /* This part can be removed if it never happens */ if (runner_status != running || (buff->status != (write ? writing : reading))) { TRACEx2(1, "unexpected runner/buffer state %d/%d", runner_status, buff->status); buff->status = error; *p_buff = next_buffer(&head); /* finish this buffer */ runner_status = aborting; fdc_mode = fdc_idle; } } if (buff->remaining > 0 && calc_next_cluster(&buffer[head]) > 0) { /* still sectors left in current segment, continue with this segment */ if (setup_fdc_and_dma(&buffer[head], fdc_op) < 0) { /* failed, abort operation */ buff->bytes = buff->ptr - buff->address; buff->status = error; buff = *p_buff = next_buffer(&head); /* finish this buffer */ runner_status = aborting; fdc_mode = fdc_idle; } } else { /* current segment completed */ unsigned last_segment = buff->segment_id; int eot = ((last_segment + 1) % segments_per_track) == 0; int next = buff->next_segment; /* 0 means stop ! */ buff->bytes = buff->ptr - buff->address; buff->status = done; buff = *p_buff = next_buffer(&head); if (eot) { /* finished last segment on current track, can't continue */ runner_status = logical_eot; fdc_mode = fdc_idle; } else if (next > 0) { /* continue with next segment */ if (buff->status == waiting) { if (write && next != buff->segment_id) { TRACE(5, "segments out of order, aborting write"); runner_status = do_abort; fdc_mode = fdc_idle; } else { setup_new_segment(&buffer[head], next, 0); if (stop_read_ahead) { buff->next_segment = 0; stop_read_ahead = 0; } if (calc_next_cluster(&buffer[head]) == 0 || setup_fdc_and_dma(&buffer[head], fdc_op) != 0) { TRACEx1(1, "couldn't start %s-ahead", (write) ? "write" : "read"); runner_status = do_abort; fdc_mode = fdc_idle; } else { buff->status = (write) ? writing : reading; /* keep on going */ } } } else { TRACEx1(5, "all input buffers %s, pausing tape", (write) ? "empty" : "full"); pause_tape(last_segment, 0, fdc_mode); runner_status = idle; /* not quite true until next irq */ } } else { /* don't continue with next segment */ TRACEx1(5, "no %s allowed, stopping tape", (write) ? "write next" : "read ahead"); if (random_rw) { stop_tape(last_segment); } else { pause_tape(last_segment, 0, fdc_mode); } runner_status = idle; /* not quite true until next irq */ } } TRACE_EXIT; return; } static void retry_sector(buffer_struct ** p_buff, error_cause cause, int fdc_mode, unsigned skip) { TRACE_FUN(8, "retry_sector"); buffer_struct *buff = *p_buff; TRACEx1(4, "%s error, will retry", (fdc_mode == fdc_writing_data) ? "write" : "read"); pause_tape(buff->segment_id, 1, fdc_mode); runner_status = aborting; buff->status = error; buff->skip = skip; TRACE_EXIT; } static unsigned find_resume_point(buffer_struct * buff) { TRACE_FUN(8, "find_resume_point"); int i = 0; unsigned long mask; unsigned long map; /* 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) { TRACEx1(1, "bug: sector_offset = %d", buff->sector_offset); } else { 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; } TRACEx1(4, "at sector %d", SECTOR(i)); } else { map = buff->hard_error_map & mask; i = buff->sector_offset - 1; if (map) { while ((map & (1 << i)) == 0) { --i; } TRACEx1(4, "after sector %d", SECTOR(i)); ++i; /* first sector after last hard error */ } else { TRACE(1, "bug: no soft or hard errors"); } } } TRACE_EXIT; return i; } /* FDC interrupt service routine. */ void fdc_isr(void) { TRACE_FUN(8, "fdc_isr"); int result; int status; error_cause cause = no_error; byte in[7]; static int isr_active = 0; int t0; buffer_struct *buff = &buffer[head]; int skip; t0 = timestamp(); if (isr_active) { TRACE(-1, "nested interrupt, not good !"); *fdc.hook = fdc_isr; /* hook our handler into the fdc code again */ TRACE_EXIT; return; } ++isr_active; sti(); /* enables interrupts again */ status = inb_p(fdc.msr); if (status & FDC_BUSY) { /* Entering Result Phase */ hide_interrupt = 0; result = fdc_result(in, 7); /* better get it fast ! */ if (result < 0) { /* Entered unknown state... */ TRACE(1, "probably fatal error during FDC Result Phase"); TRACE(1, "drive may hang until (power) reset :-("); /* what to do next ???? */ } else { int i; char *fdc_mode_txt; decode_irq_cause(fdc_mode, in, &fdc_mode_txt, &cause); for (i = 0; i < NR_BUFFERS; ++i) { TRACEx3(8, "buffer[%d] status: %d, segment_id: %d", i, buffer[i].status, buffer[i].segment_id); } switch (fdc_mode) { case fdc_reading_data:{ if (cause == no_error) { TRACEi(5, "reading segment", buff->segment_id); } else { TRACEi(4, "error reading segment", buff->segment_id); } if (runner_status == aborting || runner_status == do_abort) { TRACEx1(4, "aborting %s", fdc_mode_txt); break; } if (buff->retry > 0) { TRACEx1(5, "this is retry nr %d", buff->retry); } 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. */ TRACE(4, "skipping empty segment (read)"); buff->remaining = 0; /* skip failing sector */ continue_xfer(&buff, no_error, fdc_mode, 1); /* fake success */ } else { switch (cause) { case no_error:{ determine_progress(buff, cause, fdc_reading_data); if (in[2] & 0x40) { /* Handle deleted data in header segments. * Skip segment and force read-ahead. */ TRACEx1(2, "deleted data in sector %d", 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_read_ahead = 1; /* stop on next segment */ } buff->next_segment = buff->segment_id + 1; /* force read-ahead */ skip = (SECTORS_PER_SEGMENT - buff->sector_offset); } else { skip = 0; } continue_xfer(&buff, cause, 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. */ case id_am_error: case id_crc_error: case data_am_error: case data_crc_error: case overrun_error:{ int first_error = (buff->soft_error_map == 0 && buff->hard_error_map == 0); update_history(cause); determine_progress(buff, cause, fdc_reading_data); if (first_error) { skip = buff->sector_offset; } else { skip = find_resume_point(buff); } /* 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). */ if (first_error && cause != no_data_error) { continue_xfer(&buff, cause, fdc_mode, skip); } else { retry_sector(&buff, cause, fdc_mode, skip); } break; } default:{ /* Don't know why this could happen but find out. */ TRACE(1, "unexpected error"); determine_progress(buff, cause, fdc_reading_data); retry_sector(&buff, cause, fdc_mode, 0); break; } } } break; } case fdc_reading_id:{ if (cause == no_error) { fdc_cyl = in[3]; fdc_head = in[4]; fdc_sect = in[5]; TRACEx3(6, "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(5, "Didn't find valid sector Id"); } fdc_mode = fdc_idle; break; } case fdc_writing_data:{ if (cause == no_error) { TRACEi(5, "writing segment", buff->segment_id); } else { TRACEi(4, "error writing segment", buff->segment_id); } if (runner_status == aborting || runner_status == do_abort) { TRACEx1(5, "aborting %s", fdc_mode_txt); break; } if (buff->retry > 0) { TRACEx1(5, "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(4, "skipping empty segment (write)"); buff->remaining = 0; /* skip failing sector */ continue_xfer(&buff, no_error, fdc_mode, 1); /* fake success */ } else { switch (cause) { case no_error:{ determine_progress(buff, cause, fdc_writing_data); continue_xfer(&buff, cause, 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, fdc_writing_data); skip = find_resume_point(buff); retry_sector(&buff, cause, fdc_mode, skip); break; } default:{ if (in[1] & 0x02) { TRACE(1, "media not writable"); } else { TRACE(-1, "unforeseen write error"); } fdc_mode = fdc_idle; break; } } } break; } default: TRACEx1(1, "Warning: unexpected irq during: %s", fdc_mode_txt); fdc_mode = fdc_idle; break; } } if (runner_status == do_abort) { /* cease operation, remember tape position */ TRACE(5, "runner aborting"); runner_status = aborting; ++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 (hide_interrupt) { int st0; int pcn; result = fdc_sense_interrupt_status(&st0, &pcn); current_cylinder = pcn; TRACE(5, "handled hidden interrupt"); } seek_completed = 1; fdc_mode = fdc_idle; } else if (!waitqueue_active(&wait_intr)) { if (expected_stray_interrupts == 0) { TRACE(2, "unexpected stray interrupt"); } else { TRACE(5, "expected stray interrupt"); --expected_stray_interrupts; } } else { if (fdc_mode == fdc_reading_data || fdc_mode == fdc_writing_data || fdc_mode == fdc_reading_id) { byte status = inb_p(fdc.msr); if (status & FDC_BUSY) { TRACE(-1, "***** FDC failure, busy too late"); } else { TRACE(-1, "***** FDC failure, no busy"); } } else { TRACE(6, "awaited stray interrupt"); } } hide_interrupt = 0; } /* Handle sleep code. */ if (!hide_interrupt) { ++interrupt_seen; if (wait_intr) { wake_up_interruptible(&wait_intr); } } else { TRACEx1(5, "hiding interrupt while %s", wait_intr ? "waiting" : "active"); } t0 = timediff(t0, timestamp()); if (t0 >= 1000) { /* only tell us about long calls */ TRACEx1(7, "isr() duration: %5d usec", t0); } *fdc.hook = fdc_isr; /* hook our handler into the fdc code again */ TRACE_EXIT; --isr_active; }