/*
 *      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/bas/distr/ftape-2.03b/RCS/fdc-isr.c,v $
 $Author: bas $
 *
 $Revision: 1.36 $
 $Date: 1995/05/27 08:54:21 $
 $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.36 1995/05/27 08:54:21 bas Beta $";

#include "kernel-version.h"
#ifdef MODULE
# include <linux/config.h>
# include <linux/module.h>
#endif
#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-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 allways 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 allways 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 < ftape_num_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, "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;
        TRACE( 5, "handled hidden interrupt");
      }
      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 {
      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;
}
