/*
 *      Copyright (C) 1993,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/ftape-io.c,v $
 $Author: bas $
 *
 $Revision: 1.39 $
 $Date: 1994/11/27 16:35:04 $
 $State: Beta $
 *
 *      This file contains some general functions
 *      for the QIC-40/80 floppy-tape driver for Linux.
 */

static char RCSid[] =
"$Id: ftape-io.c,v 1.39 1994/11/27 16:35:04 bas Beta $";


#include <linux/errno.h>
#include <linux/sched.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <linux/ioctl.h>
#include <linux/mtio.h>

#include "ftape.h"
#include "fdc-io.h"
#include "qic117.h"
#include "ftape-io.h"
#include "ftape-rw.h"
#include "ftape-write.h"
#include "ftape-read.h"
#include "ftape-eof.h"
#include "kernel-interface.h"
#include "calibr.h"
#include "vendors.h"

/*      Global vars.
 */
/* NOTE: sectors start numbering at 1, all others at 0 ! */
int segments_per_track = 102;
int segments_per_head = 1020;
int segments_per_cylinder = 4;
int tracks_per_tape = 20;
int ftape_track = -1;
int ftape_failure = 1;
int ftape_seg_pos = 0;
int first_data_segment = -1;
int ftape_state = idle;         /* use buffer_state_enum */
int unknown_drive_config = 1;
struct error_info history;
int write_protected;
int ftape_offline = 0;
int no_tape = 1;
int formatted = 0;
int ftape_last_error = 0;
int ftape_data_rate = 0;

/*      Local vars.
 */
static int check_restrictions = 1;
/*      command-restrictions is a table according
 *      to the QIC-117 specs specifying the state
 *      the drive status should be in at command execution.
 */
struct qic117_command_restriction restrictions[] = QIC117_RESTRICTIONS;
static ftape_error ftape_errors[] = QIC117_ERRORS;
static char* ftape_cmds[] = QIC117_COMMANDS;
static vendor_struct vendors[] = QIC117_VENDORS;
static int ftape_udelay_count;
static int ftape_udelay_time;
static wakeup_method methods[] = WAKEUP_METHODS;
static vendor_struct drive_type = { UNKNOWN_VENDOR,
                                    unknown_wake_up, "Unknown" };
static const struct {
  char* text;
  int fdc_code;
  byte drive_code;
} rates[ 4] = {
  { "2 M",   -1, /*unsupported*/ QIC_CONFIG_RATE_2000 },
  { "1 M",   fdc_data_rate_1000, QIC_CONFIG_RATE_1000 },
  { "500 K", fdc_data_rate_500,  QIC_CONFIG_RATE_500 },
  { "250 K", fdc_data_rate_250,  QIC_CONFIG_RATE_250 },
};
static const drive_code2rate[] = { 3, 0, 2, 1 };
static int going_offline = 0;


void
udelay( int usecs)
{
  volatile int count = ( 1 + (usecs * ftape_udelay_count - 1) /
                        ftape_udelay_time);
  volatile int i;

  while (count-- > 0) {
    for (i = 0; i < 20 ; ++i)
    ;
  }
}

int
udelay_calibrate( void)
{
  return calibrate( "udelay", udelay, &ftape_udelay_count, &ftape_udelay_time);
}

/*      Delay (msec) routine.
 */
void
ftape_sleep( unsigned int time) {
  TRACE_FUN( 8, "ftape_sleep");
  unsigned long flags;
  int ticks = 1 + (time + MSPT - 1) / MSPT;

  /*    error in range [0..1] MSPT
   */
  if (time < MSPT) {
    /*  Time too small for scheduler, do a busy wait ! */
    udelay( 1000 * time);
  } else {
    TRACEx2( 8, "%d msec, %d ticks", time, ticks);
    current->timeout = jiffies + ticks;
    current->state = TASK_INTERRUPTIBLE;
    save_flags( flags);
    sti();
    do {
      while (current->state != TASK_RUNNING) {
        schedule();
      }
      if (current->signal & ~current->blocked) {
        TRACE( 1, "awoken by non-blocked signal :-(");
        break;                  /* exit on signal */
      }
    } while (current->timeout > 0);
    restore_flags( flags);
  }
  TRACE_EXIT;
}

/* forward */ int ftape_report_raw_drive_status( int *status);

/*      Issue a tape command:
 *      Generate command # of step pulses.
 */
int
ftape_command( int command)
{
  TRACE_FUN( 8, "ftape_command");
  int result;
  int track;
  int old_tracing = tracing;

  if (check_restrictions &&     /* only for commands, not parameters */
      (command == QIC_REPORT_NEXT_BIT || command == QIC_REPORT_DRIVE_STATUS)) {
    tracing = 0;                /* disable log of status call */
  }
  TRACEi( 5, "called with command =", command);
  if (check_restrictions) {
    ftape_sleep( MILLISECOND);
    if (restrictions[ command].mask & QIC_STATUS_READY &
        restrictions[ command].state) {
      int status;
      result = ftape_report_raw_drive_status( &status);
      if (result < 0) {
        TRACE( 1, "ftape_report_raw_drive_status failed");
        if (result == -EINTR) {
          TRACE_EXIT;
          return result;
        }
      }
      if ((status & QIC_STATUS_READY) == 0) {
        TRACEi( 1, "drive should be ready and isn't for command:", command);
      }
    }
  }
  tracing = old_tracing;
  check_restrictions = 1;       /* always turned on for next command */
  if (command == QIC_RESET || command == QIC_LOGICAL_FORWARD ||
      command == QIC_PHYSICAL_REVERSE || command == QIC_PHYSICAL_FORWARD ||
      command == QIC_SEEK_HEAD_TO_TRACK || command == QIC_SEEK_LOAD_POINT ||
      command == QIC_SKIP_REVERSE || command == QIC_SKIP_FORWARD) {
    location_known = 0;         /* no longer valid after these commands */
  }
  /*
   *    Keep cylinder nr within range, step towards home if possible.
   */
  if (current_cylinder >= command) {
    track = current_cylinder - command;
  } else {
    track = current_cylinder + command;
  }
  result = fdc_seek( track);
  /*    Verify whether we issued the right tape command.
   */
  if (result < 0) {
    TRACE( 1, "fdc_seek failed");
    result = -EIO;
  }
  TRACE_EXIT;
  return result;
}

/*      Send a tape command parameter:
 *      Generates command # of step pulses.
 *      Skips tape-status call !
 */
int
ftape_parameter( int command)
{
  check_restrictions = 0;
  return ftape_command( command);
}

/*      Wait for the drive to get ready.
 *      timeout time in milli-seconds
 *      Returned status is valid if result != -EIO
 */
int
ftape_ready_wait( int timeout, int* status)
{
  TRACE_FUN( 8, "ftape_ready_wait");
  int result;
  unsigned long t0;

  for (;;) {
    t0 = jiffies;
    result = ftape_report_raw_drive_status( status);
    if (result < 0) {
      TRACE( 1, "ftape_report_raw_drive_status failed");
      result = -EIO;
      break;
    }
    if (*status & QIC_STATUS_READY) {
      result = 0;
      break;
    }
    /* this will fail when jiffies wraps around about
     * once every year :-)
     */
    timeout -= ((jiffies - t0) * SECOND) / HZ;
    if (timeout <= 0) {
      TRACE( 1, "timeout");
      result = -ETIME;
      break;
    }
    ftape_sleep( SECOND / 10);
    timeout -= SECOND / 10;
    if (current->signal & ~current->blocked) {
      TRACE( 1, "awoken by non-blocked signal :-(");
      result = -EINTR;
      break;                    /* exit on signal */
    }
  }
  TRACE_EXIT;
  return result;
}

/*      Issue command and wait up to timeout seconds for drive ready
 */
int
ftape_command_wait( int command, int timeout, int* status)
{
  TRACE_FUN( 8, "ftape_command_wait");
  int result;

  /* Drive should be ready, issue command
   */
  result = ftape_command( command);
  if (result < 0) {
    TRACE( 1, "command failed");
  } else {
    /* Now wait for drive ready or timeout
     */
    result = ftape_ready_wait( timeout, status);
    if (result < 0) {
      TRACE( 1, "ready wait failed");
    }
  }
  TRACE_EXIT;
  return result;
}

int
ftape_parameter_wait( int command, int timeout, int* status)
{
  check_restrictions = 0;
  return ftape_command_wait( command, timeout, status);
}

/*--------------------------------------------------------------------------
 *      Report operations
 */

/* Query the drive about its status.  The command is sent and
   result_length bits of status are returned (2 extra bits are read
   for start and stop). */

static int
ftape_report_operation( int *status, int command, int result_length)
{
  TRACE_FUN( 8, "ftape_report_operation");
  int i, st3;
  int result;

  result = ftape_command( command);
  if (result < 0) {
    TRACE( 1, "ftape_command failed");
    TRACE_EXIT;
    return result;
  }
  ftape_sleep( 8 * MILLISECOND); /* Ttimeout + Tack */
  result = fdc_sense_drive_status( &st3);
  if (result < 0) {
    TRACE( 1, "fdc_sense_drive_status failed");
    TRACE_EXIT;
    return result;
  }
  if ((st3 & ST3_TRACK_0) == 0) {
    TRACE( 1, "timeout on Acknowledge");
    TRACE_EXIT;
    return -EIO;
  }
  *status = 0;
  for (i = 0; i < result_length + 1; i++) {
    result = ftape_command( QIC_REPORT_NEXT_BIT);
    if (result < 0) {
      TRACE( 1, "report next bit failed");
      TRACE_EXIT;
      return result;
    }
    ftape_sleep( 10 * MILLISECOND);
    result = fdc_sense_drive_status( &st3);
    if (result < 0) {
      TRACE( 1, "fdc_sense_drive_status (2) failed");
      TRACE_EXIT;
      return result;
    }
    if (i < result_length) {
      *status |= ((st3 & ST3_TRACK_0) ? 1 : 0) << i;
    } else {
      if ((st3 & ST3_TRACK_0) == 0) {
        TRACE( 1, "missing status stop bit");
        TRACE_EXIT;
        return -EIO;
      }
    }
  }
  /* this command will put track zero and index back into normal state */
  result = ftape_command( QIC_REPORT_NEXT_BIT);
  TRACE_EXIT;
  return 0;
}

/* Report the current drive status. */

int
ftape_report_raw_drive_status( int *status)
{
  TRACE_FUN( 8, "ftape_report_raw_drive_status");
  int result;
  int count = 0;

  do {
    result = ftape_report_operation( status, QIC_REPORT_DRIVE_STATUS, 8);
    if (++count > 3) {
      TRACE( 1, "report_operation failed");
      result = -EIO;
      break;
    }
  } while (result < 0);
  TRACE_EXIT;
  return result;
}

int
ftape_report_drive_status( int *status)
{
  TRACE_FUN( 8, "ftape_report_drive_status");
  int result;

  result = ftape_report_raw_drive_status( status);
  if (result < 0) {
    TRACE( 1, "ftape_report_raw_drive_status failed");
    TRACE_EXIT;
    return result;
  }
  if (*status & QIC_STATUS_NEW_CARTRIDGE ||
      !(*status & QIC_STATUS_CARTRIDGE_PRESENT)) {
    ftape_failure = 1;          /* will inhibit further operations */
    TRACE_EXIT;
    return -EIO;
  }
  if (*status & QIC_STATUS_READY && *status & QIC_STATUS_ERROR) {
    /*  Let caller handle all errors */
    TRACE( 2, "warning: error status set!");
    result = 1;
  }
  TRACE_EXIT;
  return result;
}

int
ftape_report_error( int* error, int* command, int report)
{
  TRACE_FUN( 8, "ftape_report_error");
  int code;
  int result;

  result = ftape_report_operation( &code, QIC_REPORT_ERROR_CODE, 16);
  if (result < 0) {
    result = -EIO;
  } else {
    *error = code & 0xff;
    *command = (code >> 8) & 0xff;
    if (report) {
      TRACEi( 3, "errorcode:", *error);
    }
    if (report && tracing > 3) {
      if (*error >= 0 && *error < NR_ITEMS( ftape_errors)) {
        TRACEx1( -1, "%sFatal ERROR:",
                (ftape_errors[ *error].fatal ? "" : "Non-"));
        TRACEx1( -1, "%s ...", ftape_errors[ *error].message);
      } else {
        TRACE( -1, "Unknown ERROR !");
      }
      if (*command >= 0 && *command < NR_ITEMS( ftape_cmds) &&
          ftape_cmds[ *command] != NULL) {
        TRACEx1( -1, "... caused by command \'%s\'", ftape_cmds[ *command]);
      } else {
        TRACEi( -1, "... caused by unknown command", *command);
      }
    }
  }
  TRACE_EXIT;
  return result;
}

int
ftape_in_error_state( int status)
{
  TRACE_FUN( 8, "ftape_in_error_state");
  int result = 0;

  if ((status & QIC_STATUS_READY) && (status & QIC_STATUS_ERROR)) {
    TRACE( 2, "warning: error status set!");
    result =  1;
  }
  TRACE_EXIT;
  return result;
}

int
ftape_report_configuration( int* rate, int* qic_80, int* ext_len)
{
  int result;
  int config;
  int status;
  enum { pre_qic117c, prehistoric, modern } model = pre_qic117c;

  TRACE_FUN( 8, "ftape_report_configuration");
  result = ftape_report_operation( &config, QIC_REPORT_DRIVE_CONFIGURATION, 8);
  if (result >= 0) {
    *rate = (config & QIC_CONFIG_RATE_MASK) >> QIC_CONFIG_RATE_SHIFT;
    result = ftape_report_operation( &status, QIC_REPORT_TAPE_STATUS, 8);
    if (result < 0) {
      /* pre- QIC117 rev C spec. drive, QIC_CONFIG_80 bit is valid.
       */
      *qic_80 = (config & QIC_CONFIG_80);
      *ext_len = (config & QIC_CONFIG_LONG);
      result = 0;
    } else {
      model = modern;
      TRACEx1( 8, "report tape status result = %02x", status);
      /* post- QIC117 rev C spec. drive, QIC_CONFIG_80 bit is invalid.
       */
      switch (status & QIC_TAPE_STD_MASK) {
      case QIC_TAPE_QIC40:
        *qic_80 = 0;
        break;
      case QIC_TAPE_QIC80:
        *qic_80 = 1;
        break;
      default:
        *qic_80 = -1;
        break;
      }
      switch (status & QIC_TAPE_LEN_MASK) {
      case QIC_TAPE_205FT:
        *ext_len = 0;
        break;
      case QIC_TAPE_307FT:
        *ext_len = 1;
        break;
      default:
        *ext_len = -1;
        break;
      }
      if (*qic_80 == -1 || *ext_len == -1) {
        TRACE( 2, "post- QIC-117B spec drive with unknown tape");
        result = -EIO;
      } else {
        result = 0;
      }
    }
  } else {
    model = prehistoric;
    *rate = QIC_CONFIG_RATE_500;
    *qic_80 = 0;
    *ext_len = 0;
    result = 0;
  }
  if (result == 0) {
    TRACEx4( 2, "%s drive with %s QIC%s tape @ %sb/s",
            (model == prehistoric) ? "prehistoric" :
            ((model == pre_qic117c) ? "pre- QIC117C" : "post- QIC117B"),
            (*ext_len) ? "long" : "short", (*qic_80) ? "80" : "40",
            rates[ drive_code2rate[ *rate]].text);
  }
  TRACE_EXIT;
  return (result < 0) ? -EIO : 0;
}

int
ftape_report_rom_version( int* version)
{
  int result;

  result = ftape_report_operation( version, QIC_REPORT_ROM_VERSION, 8);
  return (result < 0) ? -EIO : 0;
}

int
ftape_report_signature( int* signature)
{
  int result;

  result = ftape_command( 28);
  result = ftape_report_operation( signature, 9, 8);
  result = ftape_command( 30);
  return (result < 0) ? -EIO : 0;
}

void
ftape_report_vendor_id( unsigned int* id)
{
  TRACE_FUN( 8, "ftape_report_vendor_id");
  int result;

  /*
   *    We'll try to get a vendor id from the drive.
   *    First according to the QIC-117 spec, a 16-bit id is requested.
   *    If that fails we'll try an 8-bit version, otherwise we'll try
   *    an undocumented query.
   */
  result = ftape_report_operation( (int*) id, QIC_REPORT_VENDOR_ID, 16);
  if (result < 0) {
    result = ftape_report_operation( (int*) id, QIC_REPORT_VENDOR_ID, 8);
    if (result < 0) {
      result = ftape_report_operation( (int*) id, QIC_REPORT_VENDOR, 8);
      if (result < 0) {
        *id = UNKNOWN_VENDOR;
      } else {
        TRACEx1( 4, "got old 8 bit id: %04x", *id);
        *id |= 0x20000;
      }
    } else {
      TRACEx1( 4, "got 8 bit id: %04x", *id);
      *id |= 0x10000;
    }
  } else {
    TRACEx1( 4, "got 16 bit id: %04x", *id);
  }
  if (*id == 0x0047) {
    int version;
    int sign;

    result = ftape_report_rom_version( &version);
    if (result < 0) {
      TRACE( -1, "report rom version failed");
      TRACE_EXIT;
      return;
    }
    TRACEx1( 4, "CMS rom version: %d", version);
    ftape_command( QIC_ENTER_DIAGNOSTIC_1);
    ftape_command( QIC_ENTER_DIAGNOSTIC_1);
    result = ftape_report_operation( &sign, 9, 8);
    if (result < 0) {
      int error, command;

      ftape_report_error( &error, &command, 1);
      ftape_command( QIC_ENTER_PRIMARY_MODE);
      TRACE_EXIT;
      return;                   /* faalt hier ! */
    } else {
      TRACEx1( 4, "CMS signature: %02x", sign);
    }
    if (sign == 0xa5) {
      result = ftape_report_operation( &sign, 37, 8);
      if (result < 0) {
        if (version >= 63) {
          *id = 0x8880;
          TRACE( 4, "This is an Iomega drive !");
        } else {
          *id = 0x0047;
          TRACE( 4, "This is a real CMS drive !");
        }
      } else {
        *id = 0x0047;
        TRACEx1( 4, "CMS status: %d", sign);
      }
    } else {
      *id = UNKNOWN_VENDOR;
    }
    ftape_command( QIC_ENTER_PRIMARY_MODE);
  }
  TRACE_EXIT;
}

int
ftape_set_data_rate( int rate)
{
  TRACE_FUN( 8, "ftape_set_data_rate");
  int status;
  int result;
  int i;

  if (rates[ rate].fdc_code == -1) {
    TRACEx1( 1, "%sb/s data rate not supported by the fdc",
            rates[ rate].text);
    result = -EINVAL;
  } else {
    for (i = 0 ; i < 2 ; ++i) {
      result = ftape_command( QIC_SELECT_RATE);
      if (result >= 0) {
        result = ftape_parameter_wait( rates[ rate].drive_code + 2,
                                      1 * SECOND, &status);
        if (result >= 0) {        
          result = ftape_report_raw_drive_status( &status);
          if (result >= 0) {
            if (status & QIC_STATUS_ERROR) {
              int error;
              int command;
              
              result = ftape_report_error( &error, &command, 0);
              if (result >= 0  && error == 31 && command == QIC_SELECT_RATE) {
                TRACEx1( 1, "%sb/s data rate not allowed by the drive",
                        rates[ rate].text);
                result = -EINVAL;
                break;
              }
            } else {
              break;            /* new rate successfully selected */
            }
          }
        }
      }
    }
  }
  if (result != -EINVAL) {
    if (result < 0) {
      TRACE( 1, "Error selecting data rate");
      result = -EIO;
    } else {
      /*  Some older drives do not support the report_drive_status
       *  command, so we cannot verify the set rate.
       *  Assume they are all able to handle 500 Kb/s, so skip the
       *  verification if called with this value.
       */
      int data_rate;
      int qic_80;
      int ext_len;

      ftape_report_configuration( &data_rate, &qic_80, &ext_len);
      if (data_rate != rates[ rate].drive_code) {
        TRACE( 1, "Failed to set tape drive's data rate");
        result = -EINVAL;
      } else {
        fdc_set_data_rate( rates[ rate].fdc_code);
        TRACEx1( 4, "Data rate set to %sb/s", rates[ rate].text);
        ftape_data_rate = rate; /* store rate set */
      }
    }
  }
  TRACE_EXIT;
  return result;
}

/*      Seek the head to the specified track.
 */
int
ftape_seek_head_to_track( int track)
{
  TRACE_FUN( 8, "ftape_seek_head_to_track");
  int status;
  int result;

  ftape_track = -1;             /* remains set in case of error */
  if (track < 0 || track >= tracks_per_tape) {
    TRACE( -1, "track out of bounds");
    result = -EINVAL;
  } else {
    result = ftape_command( QIC_SEEK_HEAD_TO_TRACK);
    if (result < 0) {
      TRACE( 1, "ftape_command failed");
    } else {
      result = ftape_parameter_wait( track + 2, 20 * SECOND, &status);
      if (result < 0) {
        TRACE( 1, "ftape_parameter_wait failed");
      } else {
        ftape_track = track;
      }
    }
  }
  TRACE_EXIT;
  return result;
}

int
ftape_not_operational( int status)
{
  /* return true if status indicates tape can not be used.
   */
  return ( (status ^ QIC_STATUS_CARTRIDGE_PRESENT) &
          (QIC_STATUS_ERROR |
           QIC_STATUS_CARTRIDGE_PRESENT |
           QIC_STATUS_NEW_CARTRIDGE) );
}

int
ftape_seek_to_eot( void)
{
  TRACE_FUN( 8, "ftape_seek_to_eot");
  int result;
  int status;

  result = ftape_ready_wait( 5 * SECOND, &status);
  while ((status & QIC_STATUS_AT_EOT) == 0) {
    if (result < 0) {
      TRACE( 1, "failed");
      TRACE_EXIT;
      return result;
    }
    if (ftape_not_operational( status)) {
      TRACE_EXIT;
      return -EIO;
    }
    result = ftape_command_wait( QIC_PHYSICAL_FORWARD, 85 * SECOND, &status);
  }
  TRACE_EXIT;
  return 0;
}

int
ftape_seek_to_bot( void)
{
  TRACE_FUN( 8, "ftape_seek_to_bot");
  int result;
  int status;

  result = ftape_ready_wait( 5 * SECOND, &status);
  while ((status & QIC_STATUS_AT_BOT) == 0) {
    if (result < 0) {
      TRACE( 1, "failed");
      TRACE_EXIT;
      return result;
    }
    if (ftape_not_operational( status)) {
      TRACE_EXIT;
      return -EIO;
    }
    result = ftape_command_wait( QIC_PHYSICAL_REVERSE, 85 * SECOND, &status);
  }
  TRACE_EXIT;
  return 0;
}

void
ftape_reset_position( void)
{
  ftape_seg_pos = first_data_segment;
  reset_eof_list();
}

int
ftape_new_cartridge( void)
{
  ftape_track = -1;             /* force seek on first access */
  first_data_segment = -1;      /* unknown */
  ftape_zap_read_buffers();
  ftape_zap_write_buffers();
  ftape_reset_position();
  return 0;
}

/*      Extract tape parameters from drive configuration
 */
void
ftape_get_tape_parameters( void)
{
  TRACE_FUN( 8, "ftape_get_tape_parameters");
  int rate;
  int qic_80;
  int ext_len;

  if (ftape_report_configuration( &rate, &qic_80, &ext_len) < 0) {
    unknown_drive_config = 1;
    /* unknown or not formatted tape in drive !
     */
    TRACE( -1, "Bad tape, cannot proceed");
  } else {
    segments_per_cylinder = 4;
    if (qic_80) {
      segments_per_track = (ext_len) ? 150 : 100;
      segments_per_head = 600;
      tracks_per_tape = 28;
    } else {
      segments_per_track = (ext_len) ? 102 : 68;
      segments_per_head = 10 * segments_per_track;
      tracks_per_tape = 20;
    }
    TRACEx5( 4, "(spc/spt/sph/sps/tpt) = %d/%d/%d/%d/%d",
            segments_per_cylinder, segments_per_track, segments_per_head,
            SECTORS_PER_SEGMENT, tracks_per_tape);
  }
  TRACE_EXIT;
}

int
ftape_abort_operation( int* location)
{
  TRACE_FUN( 5, "ftape_abort_operation");
  int result = 0;
  int i;
  int status;

  if (runner_status == running) {
    TRACE( 5, "aborting running runner, waiting");
    runner_status = do_abort;
    /* set timeout so that the tape will run into at logical EOT
     * if we missed the last sector and there are no queue pulses.
     */
    result = ftape_smart_stop( &location_known, location);
    if (result == 0) {
      runner_status = idle;
    }
  }
  if (runner_status != idle) {
    if (runner_status == do_abort) {
      TRACE( 5, "forcing runner abort");
    }
    TRACE( 5, "stopping tape");
    result = ftape_command_wait( QIC_STOP_TAPE, 5 * SECOND, &status);
    location_known = 0;
    runner_status = idle;
  }
  for (i = 0; i < NR_ITEMS( buffer); ++i) {
    buffer[ i].status = empty;
  }
  head = tail = 0;
  TRACE_EXIT;
  return result;
}

int
ftape_wakeup_drive( wake_up_types method)
{
  TRACE_FUN( 8, "ftape_wakeup_drive");
  int result;
  int status;
  int motor_on = 0;

  TRACEx1( 5, "method: %s", methods[ method].name);
  switch (method) {
  case wake_up_colorado:
    result = ftape_command( QIC_COLORADO_ENABLE1);    
    if (result == 0) {
      result = ftape_parameter( 2); /* for unit nr 0 */
    }
    break;
  case wake_up_mountain:
    result = ftape_command( QIC_MOUNTAIN_ENABLE1);
    if (result == 0) {
      ftape_sleep( MILLISECOND);  /* NEEDED */
      result = ftape_command( QIC_MOUNTAIN_ENABLE2);
    }
    break;
  case wake_up_insight:
    ftape_sleep( 100 * MILLISECOND);
    motor_on = 1;
    fdc_motor( motor_on);       /* enable is done by motor-on */
  case no_wake_up:
    result = 0;
    break;
  default:
    result = -ENODEV;           /* unknown wakeup method */
  }
  /*  If wakeup succeeded we should't get and error here..
   */
  if (result == 0) {
    result = ftape_report_raw_drive_status( &status);
    if (result < 0 && motor_on) {
      fdc_motor( 0);              /* motor off if failed */
    }
  }
  TRACE_EXIT;
  return result;
}

int
ftape_put_drive_to_sleep( vendor_struct drive_type)
{
  TRACE_FUN( 8, "ftape_put_drive_to_sleep");
  int result;

  TRACEx1( 5, "method: %s", methods[ drive_type.wake_up].name);
  switch (drive_type.wake_up) {
  case wake_up_colorado:
    result = ftape_command( QIC_COLORADO_DISABLE);
    break;
  case wake_up_mountain:
    result = ftape_command( QIC_MOUNTAIN_DISABLE);
    break;
  case wake_up_insight:
    fdc_motor( 0);              /* enable is done by motor-on */
  case no_wake_up:              /* no wakeup / no sleep ! */
    result = 0;
    break;
  default:
    result = -ENODEV;           /* unknown wakeup method */
  }
  TRACE_EXIT;
  return result;
}

int
lookup_vendor_id( int vendor_id)
{
  int i = 0;

  while ( vendors[ i].vendor_id != vendor_id) {
    if (++i >= NR_ITEMS( vendors)) {
      return -1;
    }
  }
  return i;
}

void
ftape_detach_drive( void)
{
  TRACE_FUN( 8, "ftape_detach_drive");

  TRACE( 5, "disabling tape drive and fdc");
  ftape_put_drive_to_sleep( drive_type);
  fdc_catch_stray_interrupts( 1);   /* one always comes */
  fdc_disable();
  fdc_release_irq_and_dma();
  TRACE_EXIT;
}

int
ftape_reset_drive( void)
{
  TRACE_FUN( 4, "ftape_reset_drive");
  int result = 0;
  int status;
  int err_code;
  int err_command;
  int i;

  /*    We want to re-establish contact with our drive.
   *    Fire a number of reset commands (single step pulses)
   *    and pray for success.
   */
  for (i = 0; i < 2; ++i) {
    TRACE( 5, "Resetting fdc");
    fdc_reset();
    ftape_sleep( 10 * MILLISECOND);
    TRACE( 5, "Reset command to drive");
    result = ftape_command( QIC_RESET);
    if (result == 0) {
      ftape_sleep( 1 * SECOND);   /* drive not accessible during 1 second */
      TRACE( 5, "Re-selecting drive");
      /*  Strange, the QIC-117 specs don't mention this but the
       *  drive gets deselected after a soft reset !
       *  So we need to enable it again.
       */
      result = ftape_wakeup_drive( drive_type.wake_up);
      if (result < 0) {
        TRACE( 1, "Wakeup failed !");
      }
      TRACE( 5, "Waiting until drive gets ready");
      result = ftape_ready_wait( 105 * SECOND, &status);
      if (result == 0  && status & QIC_STATUS_ERROR) {
        result = ftape_report_error( &err_code, &err_command, 1);
        if (result == 0 && err_code == 27) {
          /* Okay, drive saw reset command and responded as it should
           */
          break;
        } else {
          result = -EIO;
        }
      } else {
        result = -EIO;
      }
    }
    if (current->signal & ~current->blocked) {
      TRACE( 1, "aborted by signal");
      result = -EINTR;
      break;                  /* exit on signal */
    }
  }
  if (result != 0) {
    TRACE( 1, "General failure to reset tape drive");
  }
  /*  Restore correct settings
   */
  ftape_set_data_rate( ftape_data_rate); /* keep original rate */
  TRACE_EXIT;
  return result;
}

static void
clear_history( void)
{
  history.used = 0;
  history.id_am_errors =
  history.id_crc_errors =
  history.data_am_errors =
  history.data_crc_errors =
  history.overrun_errors =
  history.no_data_errors =
  history.retries =
  history.crc_errors =
  history.crc_failures =
  history.ecc_failures =
  history.corrected =
  history.rewinds = 0;
}

int
ftape_activate_drive( vendor_struct* drive_type)
{
  TRACE_FUN( 5, "ftape_activate_drive");
  int result = 0;

  /* If we already know the drive type, wake it up.
   * Else try to find out what kind of drive is attached.
   */
  if (drive_type->wake_up != unknown_wake_up) {
    TRACE( 5, "enabling tape drive and fdc");
    result = ftape_wakeup_drive( drive_type->wake_up);
    if (result < 0) {
      TRACE( 1, "known wakeup method failed");
    }
  } else {
    int old_tracing = tracing;
    wake_up_types method = unknown_wake_up;

    /*  Try to awaken the drive using all known methods.
     *  Lower tracing for a while.
     */
    if (tracing <= 4) {
      tracing = 0;
    }
    for (method = no_wake_up; method < NR_ITEMS( methods); ++method) {
      drive_type->wake_up = method;
      result = ftape_wakeup_drive( drive_type->wake_up);
      if (result >= 0) {
        int tracing = old_tracing; /* fool TRACE */
        TRACEx1( 2, "drive wakeup method: %s",
                methods[ drive_type->wake_up].name);
        break;
      }
    }
    tracing = old_tracing;
    if (method >= NR_ITEMS( methods)) {
      /* no response at all, cannot open this drive */
      drive_type->wake_up = unknown_wake_up;
      TRACE( 1, "no tape drive found !");
      tracing = old_tracing;
      result = -ENODEV;
    }
  }
#if 0
  if (result < 0) {
    fdc_release_irq_and_dma();
  }
#endif
  TRACE_EXIT;
  return result;
}

int
ftape_get_drive_status( int* new_tape, int* no_tape, int* wp_tape)
{
  TRACE_FUN( 5, "ftape_get_drive_status");
  int result;
  int status;

  *no_tape =
  *wp_tape = 0;
  /*    Tape drive is activated now.
   *    First clear error status if present.
   */
  do {
    result = ftape_ready_wait( 105 * SECOND, &status);
    if (result < 0) {
      if (result == -ETIME) {
        TRACE( 1, "ftape_ready_wait timeout");
      } else if (result == -EINTR) {
        TRACE( 1, "ftape_ready_wait aborted");
      } else {
        TRACE( 1, "ftape_ready_wait failed");
      }
      result = -EIO;
      break;
    }
    /*  Clear error condition (drive is ready !)
     */
    if (status & (QIC_STATUS_NEW_CARTRIDGE | QIC_STATUS_ERROR)) {
      int error;
      int command;

      if (status & QIC_STATUS_ERROR) {
        TRACE( 1, "error status set");
      }
      result = ftape_report_error( &error, &command, 1);
      if (result < 0) {
        TRACEi( 1, "report_error_code failed:", result);
        ftape_reset_drive();    /* hope it's working next time */
        result = -EIO;
        break;
      }
      TRACEi( 4, "error code   :", error);
      TRACEi( 4, "error command:", command);
      if (status & QIC_STATUS_NEW_CARTRIDGE) {
        TRACE( 3, "status: new cartridge");
        *new_tape = 1;
      }
    }
  } while (status & QIC_STATUS_ERROR);

  *no_tape = !(status & QIC_STATUS_CARTRIDGE_PRESENT);
  if (*no_tape) {
    TRACE( 1, "no cartridge present");
  }
  *wp_tape = (status & QIC_STATUS_WRITE_PROTECT);
  if (*wp_tape) {
    TRACE( 2, "Write protected cartridge");
  }
  TRACE_EXIT;
  return result;
}

void
ftape_log_vendor_id( void)
{
  TRACE_FUN( 5, "ftape_log_vendor_id");
  int vendor_index;

  ftape_report_vendor_id( &drive_type.vendor_id);
  vendor_index = lookup_vendor_id( drive_type.vendor_id);
  if (drive_type.vendor_id == UNKNOWN_VENDOR && 
      drive_type.wake_up == wake_up_colorado) {
    vendor_index = 0;
    drive_type.vendor_id = 0; /* hack to get rid of all this mail */
  }
  if (vendor_index < 0) {
    /* Unknown vendor id, first time opening device.
     * The drive_type remains set to type found at wakeup time, this
     * will probably keep the driver operating for this new vendor.
     */
    TRACE( -1,   "============ unknown vendor id ===========");
    TRACE( -1,   "A new, yet unsupported tape drive is found");
    TRACE( -1,   "Please report the following values:");
    TRACEx1( -1, "   Vendor id     : 0x%05x", drive_type.vendor_id);
    TRACEx1( -1, "   Wakeup method : %s", methods[ drive_type.wake_up].name);
    TRACE( -1,   "And a description of your tape drive to:");
    TRACE( -1,   "Bas Laarhoven <bas@vimec.nl>");
    TRACE( -1,   "==========================================");
  } else {
    drive_type.name = vendors[ vendor_index].name;
    TRACEx1( 3, "ftape drive type is: %s", drive_type.name);
    /* scan all methods for this vendor_id in table */
    while (drive_type.wake_up != vendors[ vendor_index].wake_up) {
      if (vendor_index < NR_ITEMS( vendors) - 1 &&
          vendors[ vendor_index + 1].vendor_id == drive_type.vendor_id) {
        ++vendor_index;
      } else {
        break;
      }
    }
    if (drive_type.wake_up != vendors[ vendor_index].wake_up) {
      TRACE( -1, "==========================================");
      TRACE( -1, "wakeup type mismatch:");
      TRACEx2( -1, "found: %s, expected: %s",
              methods[ drive_type.wake_up].name,
              methods[ vendors[ vendor_index].wake_up].name);
      TRACE( -1, "please report this to <bas@vimec.nl>");
      TRACE( -1, "==========================================");
    }
  }
  TRACE_EXIT;
}

int
ftape_init_drive( int* formatted)
{
  TRACE_FUN( 5, "ftape_init_drive");
  int result = 0;
  int status;

  result = ftape_report_raw_drive_status( &status);
  if (result >= 0 && !(status & QIC_STATUS_REFERENCED)) {
    TRACE( 5, "starting seek_load_point");
    ftape_seek_to_bot();      /* needed for Insight drive */
    result = ftape_command_wait( QIC_SEEK_LOAD_POINT, 105 * SECOND, &status);
    if (result < 0) {
      TRACE( 1, "seek_load_point failed (command)");
    }
  }
  if (result >= 0) {
    int rate;

    *formatted = (status & QIC_STATUS_REFERENCED);
    if (!*formatted) {
      TRACE( 1, "Warning: tape is not formatted !");
    }
    /*  Select highest rate supported by both fdc and drive.
     *  Start with highest rate supported by the fdc.
     */
    for (rate = (fdc.type >= i82077) ? 1 : 2 ; rate < 4 ; ++rate) {
      result = ftape_set_data_rate( rate);
      if (result >= 0) {
        break;
      }
    }
    if (result < 0) {
      result = -EIO;
    }
  }
  if (result >= 0) {
    /* Tape should be at bot if new cartridge ! */
    ftape_new_cartridge();
    if (formatted) {
      ftape_get_tape_parameters();
    }
  }
  TRACE_EXIT;
  return result;
}

/*      OPEN routine called by kernel-interface code
 */
int
_ftape_open( void)
{
  TRACE_FUN( 8, "_ftape_open");
  int result;
  static int new_tape = 1;

  result = fdc_init();
  if (result >= 0) {
    result = ftape_activate_drive( &drive_type);
    if (result < 0) {
      fdc_release_irq_and_dma();
    } else {
      result = ftape_get_drive_status( &new_tape, &no_tape, &write_protected);
      if (result < 0) {
        ftape_detach_drive();
      } else {
        if (drive_type.vendor_id == UNKNOWN_VENDOR) {
          ftape_log_vendor_id();
        }
        if (new_tape) {
          ftape_offline = 0;
          result = ftape_init_drive( &formatted);
          if (result >= 0) {
            new_tape = 0;
          } else {
            ftape_detach_drive();
          }
        }
        if (result >= 0) {
          clear_history();
        }
      }
    }
  }
  TRACE_EXIT;
  return result;
}

/*      RELEASE routine called by kernel-interface code
 */
int
_ftape_close( void)
{
  TRACE_FUN( 8, "_ftape_close");
  int result;
  int last_segment;

  result = ftape_flush_buffers();
  last_segment = ftape_seg_pos - 1;
  if (!(ftape_unit & FTAPE_NO_REWIND) || going_offline) {
    if (result >= 0) {
      result = ftape_update_header_segments( NULL, 1);
      if (result < 0) {
        TRACE( 1, "error: update of header segments failed");
      }   
    } else {
      TRACE( 1, "error: unable to update header segments");
    }
  }
  ftape_abort_operation( &current_segment);
  if (!(ftape_unit & FTAPE_NO_REWIND) || going_offline) {
    TRACE( 5, "rewinding tape");
    result = ftape_seek_to_bot();
    ftape_reset_position();
    ftape_zap_read_buffers();
    ftape_zap_write_buffers();
  } 
  /* Gnu tar stops on the EOF as it knows there is no more data
   * and doesn't read the EOF, so inorder the next open doesn't start
   * on an EOF we clear the state of eof_mark now.
   */
  if (just_before_eof) {        /* nothing was left */
    eof_mark = 0;
    just_before_eof = 0;
  }
  ftape_detach_drive();
  if (history.used) {
    TRACE( 3,  "== Non-fatal errors this run: ==");
    TRACE( 3,  "fdc isr statistics:");
    TRACEi( 3, " id_am_errors     :", history.id_am_errors);
    TRACEi( 3, " id_crc_errors    :", history.id_crc_errors);
    TRACEi( 3, " data_am_errors   :", history.data_am_errors);
    TRACEi( 3, " data_crc_errors  :", history.data_crc_errors);
    TRACEi( 3, " overrun_errors   :", history.overrun_errors);
    TRACEi( 3, " no_data_errors   :", history.no_data_errors);
    TRACEi( 3, " retries          :", history.retries);
    if (history.used & 1) {
      TRACE( 3,  "ecc statistics:");
      TRACEi( 3, " crc_errors       :", history.crc_errors);
      TRACEi( 3, " crc_failures     :", history.crc_failures);
      TRACEi( 3, " ecc_failures     :", history.ecc_failures);
      TRACEi( 3, " sectors corrected:", history.corrected);
    }
    TRACEi( 3, "repositions       :", history.rewinds);
    TRACEi( 3, "last segment      :", last_segment);
  }
  if (going_offline) {
    going_offline = 0;
    ftape_offline = 1;
  }
  TRACE_EXIT;
  return result;
}

/*      IOCTL routine called by kernel-interface code
 */
int
_ftape_ioctl( unsigned int command, void * arg)
{
  TRACE_FUN( 8, "ftape_ioctl");
  int result = EINVAL;
  union { struct mtop mtop;
          struct mtget mtget;
  } krnl_arg;
  int arg_size = (command & IOCSIZE_MASK) >> IOCSIZE_SHIFT;

  /* This check will only catch arguments that are too large !
   */
  if ((command & IOC_INOUT) && arg_size > sizeof( krnl_arg)) {
    TRACEi( 1, "bad argument size:", arg_size);
    TRACE_EXIT;
    return -EINVAL;
  }
  if (command & IOC_IN) {
    int error = verify_area( VERIFY_READ, arg, arg_size);
    if (error) {
      TRACE_EXIT;
      return error;
    }
    memcpy_fromfs( &krnl_arg.mtop, arg, arg_size);
  }
  TRACEx1( 5, "called with ioctl command: 0x%08x", command);
  switch (command) {
    /* cpio compatibility
     * mtrasx and mtreset are mt extension by Hennus Bergman
     * mtseek and mttell are mt extension by eddy olk
     */
  case MTIOCTOP:
    TRACEx1( 5, "calling MTIOCTOP command: 0x%08x", krnl_arg.mtop.mt_op);
    switch (krnl_arg.mtop.mt_op) {
    case MTNOP:
      /* gnu mt calls MTNOP before MTIOCGET to set status */
      result = 0;
      break;
    case MTRESET:
      result = ftape_reset_drive();
      /* fall through */
    case MTREW:
    case MTOFFL:
      if (ftape_offline) {
        result = -EIO;
        break;
      }
      result = ftape_seek_to_bot();
      ftape_reset_position();
      ftape_zap_read_buffers();
      ftape_zap_write_buffers();
      if (krnl_arg.mtop.mt_op == MTOFFL) {
        going_offline = 1;
        TRACE( 4, "Putting tape drive offline");
      }
      result = 0;
      break;
    case MTRETEN:
      if (ftape_offline) {
        result = -EIO;
        break;
      }
      result = ftape_seek_to_eot();
      if (result >= 0) {
        result = ftape_seek_to_bot();
      }
      break;
    case MTERASE:
      if (ftape_offline) {
        result = -EIO;
        break;
      }
      result = ftape_erase();
      break;
    case MTEOM:
      if (ftape_offline) {
        result = -EIO;
        break;
      }
      result = ftape_seek_eom();
      break;
    case MTFSFM:
      if (ftape_offline) {
        result = -EIO;
        break;
      }
      eof_mark = 1; /* position ready to extend */
    case MTFSF:
      if (ftape_offline) {
        result = -EIO;
        break;
      }
      result = ftape_seek_eof( krnl_arg.mtop.mt_count);
      break;
    case MTBSFM:
      if (ftape_offline) {
        result = -EIO;
        break;
      }
      eof_mark = 1; /* position ready to extend */    
    case MTBSF:
      if (ftape_offline) {
        result = -EIO;
        break;
      }
      result = ftape_seek_eof( -krnl_arg.mtop.mt_count);
      break;
    case MTFSR:
      if (ftape_offline) {
        result = -EIO;
        break;
      }
      tracing = krnl_arg.mtop.mt_count;
      TRACEx1( 2, "tracing set to %d", tracing);
      result = 0;
      break;
    case MTBSR:
      if (ftape_offline) {
        result = -EIO;
        break;
      }
#if 0
      result = ftape_fix();
#else
      result = 0;
#endif
      break;
    case MTWEOF:
      if (ftape_offline) {
        result = -EIO;
        break;
      }
      result = ftape_weof( krnl_arg.mtop.mt_count, ftape_seg_pos, 1);
      break;
      /* MTRASx and MTRESET are mt extension by Hennus Bergman
       */
    case MTRAS1:
    case MTRAS2:
    case MTRAS3:
    case MTSEEK:
    case MTTELL:
    default:
      TRACEi( 1, "MTIOCTOP sub-command not implemented:", krnl_arg.mtop.mt_op);
      result = -EIO;
      break;
    }
    break;
  case MTIOCGET:
    krnl_arg.mtget.mt_type = drive_type.vendor_id + 0x800000;
    krnl_arg.mtget.mt_resid = 0; /* not implemented */
    krnl_arg.mtget.mt_dsreg = 0; /* status register */
    krnl_arg.mtget.mt_gstat =   /* device independent status */
    	((ftape_offline) ? 0 : GMT_ONLINE( -1L)) |
    	((write_protected) ? GMT_WR_PROT( -1L) : 0) |
        ((no_tape) ? GMT_DR_OPEN( -1L) : 0);
    krnl_arg.mtget.mt_erreg = ftape_last_error; /* error register */
    result = ftape_file_no( &krnl_arg.mtget.mt_fileno,
			   &krnl_arg.mtget.mt_blkno); 
    break;
  case MTIOCPOS:
    TRACE( 5, "Mag tape ioctl command: MTIOCPOS");
    TRACE( 1, "MTIOCPOS command not implemented");
    break;
  default:
    result = -EINVAL;
    break;
  }
  if (command & IOC_OUT) {
    int error = verify_area( VERIFY_WRITE, arg, arg_size);
    if (error) {
      TRACE_EXIT;
      return error;
    }
    memcpy_tofs( arg, &krnl_arg, arg_size);
  }
  TRACE_EXIT;
  return result;
}
