/*
 *      Copyright (C) 1993 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: /usr/src/distr/ftape-0.9.7/RCS/ftape-io.c,v $
 $Author: bas $
 *
 $Revision: 1.10 $
 $Date: 1993/12/04 11:52:41 $
 $State: Alpha $
 *
 *      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.10 1993/12/04 11:52:41 bas Alpha $";


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

#include "qic117.h"
#include "fdc-io.h"
#include "ftape-io.h"
#include "ftape-rw.h"
#include "ftape-ioctl.h"
#include "kernel-interface.h"
#include "calibr.h"
#include "vendors.h"

extern void (*do_floppy)(void); /* declared in ../../blk_drv/blk.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 sectors_per_segment = 32;
int tracks_per_tape = 20;
int ftape_track = -1;
int ftape_failure = 1;
int ftape_chr_pos = 0;
int ftape_seg_pos = 0;
int first_data_segment = -1;
int ftape_state = idle;         /* use buffer_state_enum */
struct _fdt_id location;
int location_known = 0;
int unknown_drive_config = 1;
struct error_info history;

/*      Local vars.
 */
static vendor_struct drive_type = { 0, 0, "unknown" };

static void (*oldvect)(void);
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;


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)
{
  int result = calibrate( "udelay", udelay, &ftape_udelay_count, &ftape_udelay_time);

  if (result < 0) {
    ftape_udelay_count = 1000;
    ftape_udelay_time = 2800;      /* defaults for 486/33 with old opti */
    TRACE( 0, "udelay_calibrate", "calibration of udelay failed, using default values");
  }
  return result;
}

/*      Delay (msec) routine.
 */
void
ftape_sleep( unsigned int time) {

  unsigned long flags;
  int ticks = 1 + (time + MSPT - 1) / MSPT;
  /*    minimum error: + 0   MSPT
   *    average error: + 0.5 MSPT
   *    maximum error: + 1.0 MSPT
   */
  if (time < MSPT) {
    /*  Time too small for scheduler, do a busy wait ! */
    udelay( 1000 * time);
  } else {

    if (tracing > 25) {
      printk( "ftape-io.c (ftape_sleep) - %d msec, %d ticks\n", 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) {
        break;                  /* exit on signal */
      }
    } while (current->timeout > 0);
    
    restore_flags(flags);
  }
}

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

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

  if (command == QIC_REPORT_NEXT_BIT || command == QIC_REPORT_DRIVE_STATUS) {
    tracing = 0;                /* disable log of status call */
  }

  TRACEi( 4, "ftape_command", "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( 0, "ftape_command", "ftape_report_raw_drive_status failed");
        if (result == -EINTR) {
          return result;
        }
      }
      if ((status & QIC_STATUS_READY) == 0) {
        TRACEi( 1, "ftape_command", "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( 0, "ftape_command", "fdc_seek failed");
    return -EIO;
  }
  return 0;
}

/*      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 seconds
 */
int
ftape_ready_wait( int timeout, int* status)
{
  int result;

  timeout *= 10;                /* 0.1 secs */
  for (;;) {
    result = ftape_report_raw_drive_status( status);
    if (result < 0) {
      TRACE( 0, "ftape_ready_wait", "ftape_report_raw_drive_status failed");
      return -EIO;
    }
    if (*status & QIC_STATUS_READY) {
      return 0;
    }
    if (--timeout < 0) {
      TRACE( 1, "ftape_ready_wait", "timeout");
      return -ETIME;
    }
    ftape_sleep( HZ / 10);      /* 0.1 second */
  }
}

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

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

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)
{
  int i, st3;
  int result;

  result = ftape_command( command);
  if (result < 0) {
    TRACE( 0, "ftape_report_operation", "ftape_command failed");
    return result;
  }

  ftape_sleep( 8 * MILLISECOND); /* Ttimeout + Tack */
  result = fdc_sense_drive_status( &st3);
  if (result < 0) {
    TRACE( 0, "ftape_report_operation", "fdc_sense_drive_status failed");
    return result;
  }
  if ((st3 & ST3_TRACK_0) == 0) {
    TRACE( 0, "ftape_report_operation", "timeout on Acknowledge");
    return -EIO;
  }

  *status = 0;
  for (i = 0; i < result_length + 1; i++) {
    result = ftape_command( QIC_REPORT_NEXT_BIT);
    if (result < 0) {
      TRACE( 0, "ftape_report_operation", "report next bit failed");
      return result;
    }
    ftape_sleep( 10 * MILLISECOND);
    result = fdc_sense_drive_status( &st3);
    if (result < 0) {
      TRACE( 0, "ftape_report_operation", "fdc_sense_drive_status (2) failed");
      return result;
    }
    if (i < result_length) {
      *status |= ((st3 & ST3_TRACK_0) ? 1 : 0) << i;
    } else {
      if ((st3 & ST3_TRACK_0) == 0) {
        TRACE( 0, "ftape_report_operation", "missing status stop bit");
        return -EIO;
      }
    }
  }
  return 0;
}

/* Report the current drive status. */

int
ftape_report_raw_drive_status( int *status)
{
  int result;
  int count = 0;

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

  return result;
}

int
ftape_report_drive_status( int *status)
{
  int result;

  result = ftape_report_raw_drive_status( status);
  if (result < 0) {
    TRACE( 0, "ftape_report_drive_status", "ftape_report_raw_drive_status failed");
    return result;
  }

  if (*status & QIC_STATUS_NEW_CARTRIDGE ||
      !(*status & QIC_STATUS_CARTRIDGE_PRESENT)) {
    ftape_failure = 1;          /* will inhibit further operations */
    return -EIO;
  }
  if (*status & QIC_STATUS_READY && *status & QIC_STATUS_ERROR) {
    /*  Let caller handle all other errors
     */
    result = 1 * 0;             /* WHAT'S THIS ???? FIX IT */
  }
   
  return result;
}

int
ftape_report_error( struct fdt_error* error)
{
  int code;
  int result;

  result = ftape_report_operation( &code, QIC_REPORT_ERROR_CODE, 16);
  if (result < 0) {
    return -EIO;
  }
  error->error = code & 0xff;
  error->command = (code >> 8) & 0xff;

  TRACEi( 2, "ftape_report_error", "errorcode:", error->error);
  if (tracing > 2) {
    if (error->error >= 0 && error->error < NR_ITEMS( ftape_errors)) {
      printk( "%sFatal ERROR: %s,\n",
             (ftape_errors[ error->error].fatal ? "" : "Non-"),
             ftape_errors[ error->error].message);
    } else {
      printk( "Unknown ERROR !\n");
    }
    if (error->command >= 0 && error->command < NR_ITEMS( ftape_cmds) &&
        ftape_cmds[ error->command] != NULL) {
      printk( "  caused by command \'%s\'\n", ftape_cmds[ error->command]);
    } else {
      printk( "  caused by unknown command: %d.\n", error->command);
    }
  }
  return 0;
}

int
ftape_report_configuration( int* config)
{
  int result;
  result = ftape_report_operation( config, QIC_REPORT_DRIVE_CONFIGURATION, 8);
  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;
}

void
ftape_report_vendor_id( unsigned int* 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
   *    a fixed vendor, indicating a pre-QIC-117 spec drive, is returned.
   */
  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);
    *id |= 0x10000;
    if (result < 0) {
      *id = 0;
    }
  }
}

int
ftape_report_tape_status( int* status)
{
  int result;
  result = ftape_report_operation( status, QIC_REPORT_TAPE_STATUS, 8);
  return (result < 0) ? -EIO : 0;
}

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

  ftape_track = -1;             /* remains set in case of error */

  if (track < 0 || track > tracks_per_tape) {
    TRACE( 0, "ftape_seek_head_to_track", "track out of bounds");
    return -EINVAL;
  }
  result = ftape_command( QIC_SEEK_HEAD_TO_TRACK);
  if (result < 0) {
    TRACE( 0, "ftape_seek_head_to_track", "ftape_command failed");
    return result;
  }
  result = ftape_parameter_wait( track + 2, 5, &status);
  if (result < 0) {
    TRACE( 0, "ftape_seek_head_to_track", "ftape_parameter_wait failed");
    return result;
  }
  ftape_track = track;
  return 0;
}

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)
{
  int result;
  int status;

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

int
ftape_seek_to_bot( void)
{
  int result;
  int status;

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

void
ftape_reset_position( void)
{
  ftape_chr_pos = 0;
  ftape_seg_pos = first_data_segment;
}

int
ftape_new_cartridge( void)
{
  ftape_track = -1;             /* force seek on first access */
  first_data_segment = -1;      /* unknown */
  ftape_zap_buffers();
  ftape_reset_position();

  return 0;
}

/*      Extract tape parameters from drive configuration
 */
void
ftape_get_tape_parameters( void)
{
  int result;
  int drive_configuration;

  result = ftape_report_configuration( &drive_configuration);
  unknown_drive_config = (result < 0);
  if (unknown_drive_config) {
    /* report_configuration probably not supported ! We'll try to extract
     * them from the tape header later on, for now set up a minimum configuration,
     * 205 foot QIC-40 tape, that allows us to read the tape header segment.
     */
    drive_configuration = QIC_CONFIG_RATE_500; /* wild guess... */
    TRACE( 0, "ftape_get_tape_parameters", "ftape_report_configuration not supported");
  }
  segments_per_cylinder = 4;
  sectors_per_segment = 32;
  if ((drive_configuration & QIC_CONFIG_80) != 0) {
    if ((drive_configuration & QIC_CONFIG_LONG) != 0) {
      segments_per_track = 150;
    } else {
      segments_per_track = 100;
    }
    segments_per_head = 600;
    tracks_per_tape = 28;
  } else {                      /* QIC-40 */
    if ((drive_configuration & QIC_CONFIG_LONG) != 0) {
      segments_per_track = 102;
      segments_per_head = 1020;
    } else {
      segments_per_track = 68;
      segments_per_head = 680;
    }
    tracks_per_tape = 20;
  }
  TRACEi( 2, "ftape_tape_parameters", "segments_per_cylinder", segments_per_cylinder);
  TRACEi( 2, "ftape_tape_parameters", "segments_per_track", segments_per_track);
  TRACEi( 2, "ftape_tape_parameters", "segments_per_head", segments_per_head);
  TRACEi( 2, "ftape_tape_parameters", "sectors_per_segment", sectors_per_segment);
  TRACEi( 2, "ftape_tape_parameters", "track_per_tape", tracks_per_tape);
}

int
ftape_abort_operation( void)
{
  int result;
  int i;
  int status;

  TRACE( 4, "ftape_abort_operation", "called");
  if (runner_status == running) {
    TRACE( 4, "ftape_abort_operation", "aborting runner, waiting");
    runner_status = do_abort;
    result = fdc_interrupt_wait( 10 * SECOND);
    if (result < 0) {
      return result;
    }
    if (ftape_read_id( &location) >= 0) {
      location_known = 1;
      TRACE( 4, "ftape_abort_operation", "stopping at known location");
    }
  }
  if (runner_status == do_abort) {
    TRACE( 0, "ftape_abort_operation", "forcing runner abort");
  }
  TRACE( 4, "ftape_abort_operation", "stopping tape");
  result = ftape_command_wait( QIC_STOP_TAPE, 5 * SECOND, &status);
  runner_status = idle;
  for (i = 0; i < NR_ITEMS( buffer); ++i) {
    buffer[ i].status = empty;
  }
  head = tail = 0;
  return result;
}

int
ftape_wakeup_drive( vendor_struct drive_type)
{
  int result;
  int status;
  int motor_on = 0;

  switch (drive_type.wake_up) {
  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 */
    result = 0;
    break;
  default:
    return -ENODEV;             /* unknown wakeup method */
  }

  /*  If wakeup succeeded we should't get and error here..
   */
  result = ftape_report_raw_drive_status( &status);

  if (result < 0 && motor_on) {
    fdc_motor( 0);              /* motor off if failed */
  }
  return result;
}

int
ftape_put_drive_to_sleep( vendor_struct drive_type)
{
  int result;

  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 */
    result = 0;
    break;
  default:
    return -ENODEV;             /* unknown wakeup method */
  }
  return result;
}

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

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

/*      OPEN routine called by kernel-interface code
 */
int
_ftape_open( void)
{
  int result = 0;
  int value;
  struct fdt_error error;
  int status;
  static int new_tape = 1;
  unsigned int vendor_id;

  cli();
  oldvect = *do_floppy;         /* Wedge in interrupt. */
  do_floppy = fdc_interrupt;
  sti();

  ftape_motor = 0;

  fdc_reset();                  /* init fdc */
  if (fdc_recalibrate() != 0) {
    TRACE( 0, "_ftape_open", "recalibrate failed");
    result = -EIO;
    goto error_exit;
  }

  /* If we already know the drive type, wake it up.
   * Else try to find out what kind of drive is attached.
   */
  if (drive_type.vendor_id != 0) {
    result = ftape_wakeup_drive( drive_type);
  } else {
    int old_tracing = tracing;
    if (tracing <= 5) {
      tracing = 0;
    }

    /*  Try to awaken the drive using all knows methods.
     */
    drive_type.wake_up = no_wake_up;
    for (;;) {
      int i;
      result = ftape_wakeup_drive( drive_type);
      if (result >= 0) {
        if (old_tracing > 0) {
          if (drive_type.wake_up == no_wake_up) {
            printk( __FILE__ " (_ftape_open) - drive doesn't need wake-up !???.\n");
          } else {
            printk( __FILE__ " (_ftape_open) - drive wake-up method: %s.\n", drive_type.name);
          }
        }
        break;
      }
      do {
        drive_type.wake_up = (wake_up_types) ((int) drive_type.wake_up + 1);
        if (drive_type.wake_up >= wake_up_limit) {
          /* no response at all, cannot open this drive */
          TRACE( -1, "_ftape_open", "unknown drive type, no response");
          TRACE( -1, "_ftape_open", "bad luck: unsupported drive !");
          result = -ENODEV;
          tracing = old_tracing;
          goto error_exit;
        }
        /*  Find a vendor that supports the next wake-up method.
         */
        for (i = 0; i < sizeof( vendors) / sizeof( *vendors); ++i) {
          if (vendors[ i].wake_up == drive_type.wake_up) {
            drive_type.name = vendors[ i].name;
            break;
          }
        }
      } while (i >= sizeof( vendors) / sizeof( *vendors));
    }
    tracing = old_tracing;
  }
  /*    Tape drive is activated now.
   *    First clear error status if present.
   */
  do {
    ftape_ready_wait( 105, &status);

    /*  Exit if no tape inserted
     */
    if ((status & QIC_STATUS_CARTRIDGE_PRESENT) == 0) {
      TRACE( 1, "_ftape_open", "no cartridge present");
      return -EIO;
    }
    /*  Clear error condition (drive is ready !)
     */
    if (status & (QIC_STATUS_NEW_CARTRIDGE | QIC_STATUS_ERROR)) {
      if (status & QIC_STATUS_ERROR) {
        TRACE( 2, "_ftape_open", "status: error bit set");
      }
      value = ftape_report_error( &error);
      if (value < 0) {
        TRACE( 0, "_ftape_open", "report_error_code failed");
        return result;
      }
      TRACEi( 2, "_ftape_open", "error code   :", error.error);
      TRACEi( 2, "_ftape_open", "error command:", error.command);
      if (status & QIC_STATUS_NEW_CARTRIDGE) {
        TRACE( -1, "_ftape_open", "status: new cartridge");
        new_tape = 1;
      }
    }
  } while (status & QIC_STATUS_ERROR);

  if (drive_type.vendor_id == 0) {

    ftape_report_vendor_id( &vendor_id);
    result = lookup_vendor_id( vendor_id);
    if (result < 0) {
      /* Unknown vendor id, first time opening device.
       * The drive_type remains set to type found at wake-up time, this
       * will probably keep the driver operating for this new vendor.
       */
      drive_type.vendor_id = vendor_id;
      drive_type.name = "Unknown";
      TRACE( -1, "_ftape_open",  "========= unknown vendor id ========");
      TRACEi( -1, "_ftape_open", "    report vendor id    :", drive_type.vendor_id);
      TRACEi( -1, "_ftape_open", "    with wake-up method :", drive_type.wake_up);
      TRACE( -1, "_ftape_open",  "    and your drive vendor & type");
      TRACE( -1, "_ftape_open",  "    to: `bas@vimec.nl'");
      TRACE( -1, "_ftape_open",  "====================================");
    } else {
      drive_type = vendors[ result];
      if (tracing > -1) {
        printk( __FILE__ " (_ftape_open) - ftape drive type is : %s.\n", drive_type.name);
      }
    }
  }

  if (new_tape) {
    if (!(status & QIC_STATUS_REFERENCED)) {
      TRACE( 4, "_ftape_open", "starting seek_load_point");
      ftape_seek_to_bot();      /* needed for Insight drive */
      result = ftape_command_wait( QIC_SEEK_LOAD_POINT, 105, &status);
      if (result < 0) {
        TRACE( 0, "_ftape_open", "seek_load_point failed (command)");
        return result;
      } else if (!(status & QIC_STATUS_REFERENCED)) {
        TRACE( 1, "_ftape_open", "seek_load_point failed (status)");
        return -EIO;
      }
    }
    /* tape should be at bot if new cartridge ! */
    ftape_new_cartridge();
    ftape_get_tape_parameters();
    new_tape = 0;
  }

  history.AM_errors = 0;
  history.CRC_errors = 0;
  history.CRC_failures = 0;
  history.retries = 0;
  history.ECC_failures = 0;

  return 0;

error_exit:
  cli();
  do_floppy = oldvect;
  sti();
  return result;
}

/*      RELEASE routine called by kernel-interface code
 */
int
_ftape_close( void)
{
  int result;

  ftape_flush_buffers();      /* flush write buffer */

  TRACE( 1, "_ftape_close", "calling ftape_abort_operation");
  ftape_abort_operation();

  if ((ftape_unit & 4) == 0) {
    TRACE( 1, "_ftape_close", "rewinding tape");
    /* rewinding device */
    result = ftape_seek_to_bot();
    ftape_reset_position();
    ftape_zap_buffers();
  }

  TRACE( 1, "_ftape_close", "disabling drive");
  ftape_put_drive_to_sleep( drive_type);

  result = 0;

  expect_stray_interrupt = 1;   /* one always comes */

  fdc_disable();

  cli();
  do_floppy = oldvect;
  sti();

  expect_stray_interrupt = 0;

  if (first_data_segment >= 0) { /* tape referenced */
    TRACE( -1, "_ftape_close", "== Non-fatal errors this run: ==");
    TRACEi( -1, "_ftape_close", "AM_errors   :", history.AM_errors);
    TRACEi( -1, "_ftape_close", "CRC_errors  :", history.CRC_errors);
    TRACEi( -1, "_ftape_close", "CRC_failures:", history.CRC_failures);
    TRACEi( -1, "_ftape_close", "retries     :", history.retries);
    TRACEi( -1, "_ftape_close", "ECC_failures:", history.ECC_failures);
  }
  return result;
}

/*      IOCTL routine called by kernel-interface code
 */
int
_ftape_ioctl( unsigned int command, void * arg)
{
  int result = EINVAL;
  union {
    int word;
    struct fdt_error error;
    struct _fdt_find_me find;
    struct tape_read read;
    struct mtop _mtop;
  } krnl_arg;
  int arg_size = (command & IOCSIZE_MASK) >> IOCSIZE_SHIFT;

  /*    Filter commands depending on runner status and command type
   */
  if (command != FT_READ && runner_status != idle && command != FT_STOP_TAPE) {
    return -EBUSY;
  }

  /* This check will only catch arguments that are too large !
   */
  if ((command & IOC_INOUT) && arg_size > sizeof( krnl_arg)) {
    TRACEi( 0, "ftape_ioctl", "bad argument size:", arg_size);
    return -EINVAL;
  }

  if (command & IOC_IN) {
    int error = verify_area( VERIFY_READ, arg, arg_size);
    if (error) {
      return error;
    }
    memcpy_fromfs( &krnl_arg, arg, arg_size);
  }

  TRACElx( 4, "_ftape_ioctl", "called with command:", command);

  switch (command) {
/* cpio compatibility
 */
  case MTIOCTOP:
    TRACE( 4, "_ftape_ioctl", "Mag tape ioctl command: MTIOCTOP");
    switch (krnl_arg._mtop.mt_op) {
    case MTREW:
      result = ftape_seek_to_bot();
      ftape_reset_position();
      ftape_zap_buffers();
      result = 0;
      break;
    case MTFSF:
      tracing = krnl_arg._mtop.mt_count;
      result = 0;
      break;
    case MTRETEN:
      result = ftape_seek_to_eot();
      if (result >= 0) {
        result = ftape_seek_to_bot();
      }
      break;
    case MTRESET:
    case MTBSF:
    case MTFSR:
    case MTBSR:
    case MTWEOF:
    case MTOFFL:
    case MTNOP:
    case MTFSFM:
    case MTBSFM:
    case MTEOM:
    case MTERASE:
    case MTRAS1:
    case MTRAS2:
    case MTSEEK:
    default:
      TRACEi( 1, "ftape_ioctl", "MTIOCTOP sub-command not implemented:", krnl_arg._mtop.mt_op);
      result = -EIO;
      break;
    }
    break;
  case MTIOCGET:
    TRACE( 4, "_ftape_ioctl", "Mag tape ioctl command: MTIOCGET");
    TRACE( 1, "ftape_ioctl", "MTIOCGET command not implemented");
    break;
  case MTIOCPOS:
    TRACE( 4, "_ftape_ioctl", "Mag tape ioctl command: MTIOCPOS");
    TRACE( 1, "ftape_ioctl", "MTIOCPOS command not implemented");
    break;

/* userft compatibility
 */
  case FT_REPORT_STATUS:
    result = ftape_report_drive_status( &krnl_arg.word);
    break;
  case FT_REPORT_ERROR_CODE:
    result = ftape_report_error( &krnl_arg.error);
    break;
  case FT_REPORT_CONFIGURATION:
    result = ftape_report_configuration( &krnl_arg.word);
    break;
  case FT_REPORT_ROM_VERSION:
    result = ftape_report_rom_version( &krnl_arg.word);
    break;
  case FT_REPORT_VENDOR_ID: {
    unsigned int i;
    ftape_report_vendor_id( &i);
    krnl_arg.word = (int) i;
    result = 0;
    break;
  }
  case FT_SEEK_TO_TRACK:
    result = ftape_seek_head_to_track( krnl_arg.word);
    break;
  case FT_STOP_TAPE:
    result = ftape_abort_operation();
    break;
  case FT_SEEK_TO_END:
    result = ftape_seek_to_eot();
    break;
  case FT_SEEK_TO_BEGINNING:
    result = ftape_seek_to_bot();
    break;
  case FT_SET_TRACE_LEVEL:
    tracing = krnl_arg.word;
    TRACEi( 2, "_ftape_ioctl", "tracing:", tracing);
    result = 0;
    break;
  default:
    result = EINVAL;
    break;
  }

  if (command & IOC_OUT) {
    int error = verify_area( VERIFY_WRITE, arg, arg_size);
    if (error) {
      return error;
    }
    memcpy_tofs( arg, &krnl_arg, arg_size);
  }
  return result;
}
