/*
 *      Copyright (C) 1994 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/bas/distr/ftape-1.14d/RCS/fdc-isr.c,v $
 $Author: bas $
 *
 $Revision: 1.19 $
 $Date: 1994/12/07 20:49:11 $
 $State: Beta $
 *
 *      This file contains the interrupt service routine and associated
 *      code for the QIC-40/80 tape streamer device driver.
 */

static char RCSid[] = "$Id: fdc-isr.c,v 1.19 1994/12/07 20:49:11 bas Beta bas $";

#include <asm/io.h>
#include <asm/dma.h>

#define volatile /* */

#include "ftape.h"
#include "fdc-isr.h"
#include "qic117.h"
#include "fdc-io.h"
#include "ftape-io.h"
#include "ftape-rw.h"
#include "calibr.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;

/*      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;
int hide_interrupt;


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");
  char* fdc_mode_text[] = {
    "fdc_idle", "fdc_reading_data", "fdc_seeking",
    "fdc_writing_data", "fdc_reading_id", "fdc_recalibrating",
  };

  /*  Valid in, decode cause of interrupt.
   */
  switch (fdc_mode) {
  case fdc_idle:
    *fdc_mode_txt = fdc_mode_text[ 0];
    break;
  case fdc_reading_data:
    *fdc_mode_txt = fdc_mode_text[ 1];
    break;
  case fdc_seeking:
    *fdc_mode_txt = fdc_mode_text[ 2];
    break;
  case fdc_writing_data:
    *fdc_mode_txt = fdc_mode_text[ 3];
    break;
  case fdc_reading_id:
    *fdc_mode_txt = fdc_mode_text[ 4];
    break;
  case fdc_recalibrating:
    *fdc_mode_txt = fdc_mode_text[ 5];
    break;
  default:
    *fdc_mode_txt = "unknown";
    break;
  }
  switch (st[ 0] & ST0_INT_MASK) {
  case FDC_INT_NORMAL:
    TRACEx1( 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;
    }
    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:
  }
}

/*
 *  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
 *  overrun_error  [ 0..all-1 ]     mark bad and skip
 *  no_error           all          continue
 */
static void
determine_progress( buffer_struct* buff, error_cause cause)
{
  TRACE_FUN( 8, "determine_progress");
  unsigned nr_not_xferred;
  unsigned dma_residue;

  disable_dma( fdc.dma);
  dma_residue = get_dma_residue( fdc.dma);
  if (cause == no_error && dma_residue == 0) {
    nr_not_xferred = 0;
  } else {
    if (cause == no_error) {
      TRACEx1( 6, "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);
  }
  if (cause == id_am_error || cause == id_crc_error ||
      cause == data_am_error || cause == overrun_error) {
    /*  Operation failed before any data transfer, skip failing sector.
     *  This should include the no_data_error but that is handled
     *  separately because we can't continue in that case.
     */
    if (nr_not_xferred > 0) {
      --nr_not_xferred;         /* skip error in sector not yet done */
    } else {
      TRACEx1( -1, "this is probably a bug :-( cause == %d", cause);
    }
  }
  if (nr_not_xferred > 0) {     /* compensate for sectors not transferred */
    buff->sector_count -= nr_not_xferred;
    /* bad_sector_map is already adjusted for the situation if there
     * were sector_count sectors done without error (calc_next_cluster()).
     */
    buff->bad_sector_map <<= nr_not_xferred; /* re-adjust */
  }
  /*  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;
  }
  if (cause == no_error) {
    TRACEx1( 5, "%d Sector(s) successfully transferred", buff->sector_count);
  } else if (cause == no_data_error) {
    TRACEx1( 4, "Sector at offset %d not found", buff->data_offset);
  } else if (buff->data_offset > 0) {
    /* merge position of bad sector in bitmap
     */
    buff->crc_error_map |=  (1 << (buff->data_offset - 1));
    TRACEx1( 4, "Error at sector offset %d", buff->data_offset - 1);
  } 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 offset %d", buff->data_offset);
  }
  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)
{
  TRACE_FUN( 8, "pause_tape");
  int result;
  byte out[ 3] = { FDC_SEEK, FTAPE_UNIT, calc_steps( QIC_PAUSE)};
  
  /*  We'll use a raw seek command to get the tape to rewind
   *  and stop for a retry.
   */
  ++history.rewinds;
  result = fdc_command( out, 3); /* issue QIC_117 command */
  if (result < 0) {
    TRACEx1( 5, "qic-pause failed, status = %d", result);
  }
  runner_status = idle;
  hide_interrupt = 1;
  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)};
  
  /*  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( 5, "qic-stop failed, status = %d", result);
  } else {
    runner_status = idle;
    hide_interrupt = 1;
    /*  Can't say where we are if at end of a track, start_tape will find out.
     */
    if ((segment + 1) % segments_per_track != 0) { /* not last on track */
      current_segment = ++segment;  /* better too high than too low */
      location_known = 1;
      TRACEx1( 5, "stopping at segment = %d", segment);
    } else {
      TRACEx1( 5, "stopping at end of track %d", segment / segments_per_track);
    }
  }
  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) {
    if (runner_status != running ||
        (write && buff->status != writing) ||
        (!write && buff->status != 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;
      TRACE_EXIT;
      return;                   /* dunno anything better to do */
    }
  }
  if (buff->remaining > 0 && calc_next_cluster( &buffer[ head]) > 0) {
    /* next sector(s) */
    if (setup_fdc_and_dma( &buffer[ head], fdc_op) < 0) {
      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 {                      /* next segment */
    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 = (write) ? empty : full; /* segment done */
    buff = *p_buff = next_buffer( &head);
    if (!eot) {                 /* read ahead / write next */
      if (buff->status == ((write) ? full : empty) && (next > 0)) {
        if (write && next != buff->segment_id) {
          TRACE( 5, "segments out of order, aborting write");
          runner_status = do_abort;
          fdc_mode = fdc_idle;
          TRACE_EXIT;
          return;
        }
        setup_new_segment( &buffer[ head], next, 0, SECTORS_PER_SEGMENT);
        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 {
        if (next > 0) {
          TRACEx1( 5, "all input buffers %s, pausing tape",
                (write) ? "empty" : "full");
          pause_tape( last_segment);
        } else {
          TRACEx1( 5, "no %s allowed, stopping tape",
                (write) ? "write next" : "read ahead");
          stop_tape( last_segment);
        }
        runner_status = idle;   /* not quite true until next irq */
      }
    } else {
      runner_status = logical_eot; /* just got last segment on track */
      fdc_mode = fdc_idle;
    }
  }
}

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;

#if 0
  if (buff->retry >= RETRIES_ON_CRC_ERROR) {
    TRACE( 2, "too many retries, continue");
    continue_xfer( p_buff, cause, fdc_mode, 0);
  } else {
#endif
    TRACEx1( 5, "%s error, preparing for retry",
            (fdc_mode == fdc_writing_data) ? "write" : "read");
#if 1
    runner_status = aborting;
    fdc_mode = fdc_idle;
#else
    /*  problematic: hangs Linux in read_segment after wait_segment.
     *  can ^C to exit :-)
     */
    pause_tape( buff->segment_id);
    runner_status = idle;
#endif
    buff->status = error;
    if (skip > 0) {
      buff->skip = skip;
      TRACEx1( 4, "request skipping of %d sector(s)", buff->skip);
    } else {
      buff->skip = 0;
    }
#if 0
  }
#endif
  TRACE_EXIT;
}

/*      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, t1;
  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_ITEMS( buffer); ++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: {
        
        TRACEi( 5, "fdc irq reading 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 == ((unsigned) FAKE_SEGMENT) >> 1) {
          /* This condition occurs when reading a `fake' sector that's
           * not accessible. Doesn't really matter as we would have
           * ignored it anyway ! Might be located at wrong segment,
           * then we'll fail on the next segment.
           */
          TRACE( 5, "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);
            if (in[ 2] & 0x40) {
              /*  Handle deleted data in header segments.
               *  Skip segment and force read-ahead.
               */
              TRACEi( 2, "deleted data at offset", buff->sector_offset - 1);
              buff->deleted = 1;
              buff->remaining = 0; /* abort transfer */
              buff->crc_error_map |= (-1L << buff->data_offset);
              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 !
             */
            update_history( cause);
            TRACE( 4, "no data error");
            determine_progress( buff, cause);
            if (buff->sector_count > 0) {
              /*  Get here if at least one sector was read successfully
               *  before some sector id couldn't be found.
               */
              TRACEx1( 5, "partial success: %d sector(s) done up to now",
                      buff->sector_count);
              skip = buff->sector_offset; /* retry error sector */
            } else if (buff->data_offset > 0 &&
                buff->crc_error_map & (1 << (buff->data_offset - 1))) {
              /*  Get here if the sector id not found comes right
               *  after a failing sector.
               */
              TRACE( 5, "error in previous sector: resume problem");
              if (buff->data_offset > 1 &&
                  buff->crc_error_map & ((1 << (buff->data_offset - 1)) - 1)) {
                TRACE( 5, "not first error in this segment");
                skip = buff->sector_offset - 1; /* retry error sector */
              } else {
                TRACE( 5, "first error in this segment");
                skip = buff->sector_offset; /* skip error sector */
              }
            } else {
              TRACE( 5, "no previous error: resume problem");
              if (buff->data_offset == 0 /*&& buff->retry > 0*/) {
                ++buff->start_offset;
                TRACE( 5, "incremented start offset");
              }
              skip = buff->sector_offset;
            }
            retry_sector( &buff, cause, fdc_mode, skip);
            break;
          }
          case id_am_error:
          case id_crc_error:
          case data_am_error: {
            int first_error = (buff->crc_error_map == 0);

            update_history( cause);
            TRACE( 4, "am/crc error, let ecc handle it");
            determine_progress( buff, cause);
            if (first_error) {
              /*  Let ecc code correct this single error
               */
              skip = buff->sector_offset - 1;
              continue_xfer( &buff, cause, fdc_mode, skip);
            } else {
              /*  Retry to minimize risk of failing ecc code
               */
              TRACE( 4, "too many errors, must retry");
              for (skip = 0 ; skip < buff->sector_offset ; ++skip) {
                if (buff->crc_error_map & (1 << skip)) {
                  break;        /* find first failing sector */
                }
              }
              retry_sector( &buff, cause, fdc_mode, skip);
            }
            break;
          }
          case data_crc_error:
          case overrun_error: {
            int first_error = (buff->crc_error_map == 0);

            if (cause == data_crc_error) {
              TRACE( 4, "crc error in data");
            } else {
              TRACE( 4, "overrun error");
            }
            determine_progress( buff, cause);
            update_history( cause);
            if (first_error) {
              skip = buff->sector_offset - 1;
              continue_xfer( &buff, cause, fdc_mode, skip);
            } else {
              TRACE( 4, "too many errors, must retry");
              for (skip = 0 ; skip < buff->sector_offset ; ++skip) {
                if (buff->crc_error_map & (1 << skip)) {
                  break;        /* find first failing sector */
                }
              }
              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);
            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];
        } else {              /* no valid information, use invalid sector */
          fdc_cyl =
          fdc_head =
          fdc_sect = 0;
        }
        TRACEx3( 6, "id read: C: 0x%02x, H: 0x%02x, R: 0x%02x",
                fdc_cyl, fdc_head, fdc_sect);
        fdc_mode = fdc_idle;
        break;
      }
        
      case fdc_writing_data: {

        TRACEi( 5, "fdc irq 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 == ((unsigned) FAKE_SEGMENT) >> 1) {
          /* 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( 5, "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);
            continue_xfer( &buff, cause, fdc_mode, 0);
            break;
          }     
          case no_data_error: {
            /*  Tape started too far ahead of or behind the right sector.
             *  This happens in the middle of a segment too !
             */
            update_history( cause);
            TRACE( 4, "no data error");
            determine_progress( buff, cause);
            if (buff->sector_count > 0) {
              TRACEx1( 5, "partial success: %d sector(s) done up to now",
                      buff->sector_count);
            } else if (buff->data_offset == 0) {
              TRACE( 5, "sector not found: start problem");
              if (buff->retry > 1) {
                ++buff->start_offset;
                TRACE( 5, "incremented start offset");
              }
            } else {
              TRACE( 5, "sector not found: resume problem");
            }
            retry_sector( &buff, cause, fdc_mode, buff->sector_offset);
            break;
          }
          case id_am_error:
          case id_crc_error:
          case data_am_error: {
            switch (cause) {
            case id_am_error:
              TRACE( 4, "id am error");
              break;
            case id_crc_error:
              TRACE( 4, "crc error");
              break;
            case data_am_error:
              TRACE( 4, "data am error");
              break;
            default:
              break;
            }
            update_history( cause);
            determine_progress( buff, cause);
            /* sector_offset is one too high because of determine_progress */
            skip = buff->sector_offset - 1;
            retry_sector( &buff, cause, fdc_mode, skip);
            break;
          }
          case overrun_error: {
            update_history( cause);
            TRACE( 4, "overrun error");
            determine_progress( buff, cause);
            skip = buff->sector_offset - 1;
            retry_sector( &buff, cause, fdc_mode, skip);
            break;
          }
          default: {
            if (in[ 1] & 0x02) {          
              TRACE( 1, "media not writable");
            } else {
              TRACE( -1, "unforseen 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 issueing
     *  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;
      }
      seek_completed = 1;
      fdc_mode = fdc_idle;
    } else if (!wait_intr) {
      if (expected_stray_interrupts == 0) {
        TRACE( 2, "unexpected stray interrupt");
      } else {
        TRACE( 5, "expected stray interrupt");
        --expected_stray_interrupts;
      }
    } else {
        TRACE( 5, "awaited stray interrupt");
    }
    hide_interrupt = 0;
  }
  /*    Handle sleep code.
   */
  if (!hide_interrupt) {
    ++interrupt_seen;
    if (wait_intr) {
      wake_up_interruptible( &wait_intr);
    }
  }
  t1 = timestamp();
  if (t1 < t0) {
    t1 += LATCH;
  }
  t0 = (10000 * (t1 - t0)) / (CLOCK_TICK_RATE / 100);
  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;
}




