/*
 *      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-rw.c,v $
 $Author: bas $
 *
 $Revision: 1.37 $
 $Date: 1994/12/07 20:49:11 $
 $State: Beta $
 *
 *      This file contains some common code for the segment read and segment
 *      write routines for the QIC-117 floppy-tape driver for Linux.
 */

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


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

#include "ftape.h"
#include "ftape-rw.h"
#include "fdc-io.h"
#include "kernel-interface.h"
#include "qic117.h"
#include "ftape-io.h"
#include "ftape-read.h"
#include "ftape-eof.h"
#include "ecc.h"

/*      Global vars.
 */
volatile enum runner_status_enum runner_status = idle;
byte deblock_buffer[ (SECTORS_PER_SEGMENT - 3) * SECTOR_SIZE];
byte scratch_buffer[ (SECTORS_PER_SEGMENT - 3) * SECTOR_SIZE];
buffer_struct buffer[ NR_FTAPE_BUFFERS];
struct wait_queue *wait_intr = NULL;
volatile int head;
volatile int tail;              /* not volatile but need same type as head */
int fdc_setup_error;
int location_known = 0;
int current_segment = -1;
ftape_last_segment_struct ftape_last_segment;
int header_segment_1 = -1;
int header_segment_2 = -1;
unsigned long bad_sector_map[ 4200]; /* max size for QIC-80, 307.5 ft tape */

/*      Local vars.
 */
int overrun_count_offset = 0;


/*      Increment cyclic buffer nr.
 */
buffer_struct*
next_buffer( volatile int* x)
{
  if (++*x >= NR_ITEMS( buffer)) {
    *x = 0;
  }
  return &buffer[ *x];
}

int
valid_segment_no( unsigned segment)
{
  return (segment >= first_data_segment && segment <= ftape_last_segment.id);
}

void
update_bad_sector_map( byte* buffer)
{
  memcpy( buffer + 2 * SECTOR_SIZE, bad_sector_map, sizeof( bad_sector_map));
}

/*      Count nr of 1's in pattern.
 */
int
count_ones( unsigned long mask)
{
  int bits;

  for (bits = 0; mask != 0; mask >>= 1) {
    if (mask & 1) {
      ++bits;
    }
  }
  return bits;
}

/*      Calculate Floppy Disk Controller and DMA parameters for a segment.
 *      head:   selects buffer struct in array.
 *      offset: number of physical sectors to skip (including bad's).
 *      count:  number of physical sectors to handle (including bad's).
 */
int
setup_segment( buffer_struct* buff, unsigned int segment_id,
              unsigned int sector_offset, unsigned int sector_count, int retry)
{
  TRACE_FUN( 8, "setup_segment");
  unsigned long offset_mask;
  unsigned long mask;

  buff->segment_id = segment_id;
  buff->sector_offset = sector_offset;
  buff->remaining = sector_count;
  buff->head = segment_id / segments_per_head;
  buff->cyl = (segment_id % segments_per_head) / segments_per_cylinder;
  buff->sect = (segment_id % segments_per_cylinder) * SECTORS_PER_SEGMENT + 1;
  buff->deleted = 0;
  offset_mask = (1 << buff->sector_offset) - 1;
  mask = bad_sector_map[ segment_id] & offset_mask;
  while (mask) {
    if (mask & 1) {
      offset_mask >>= 1;          /* don't count bad sector */
    }
    mask >>= 1;
  }
  buff->data_offset = count_ones( offset_mask); /* good sectors to skip */
  buff->ptr = buff->address + buff->data_offset * SECTOR_SIZE;
  TRACEx1( 5, "data offset = %d sectors", buff->data_offset);
  if (retry) {
    buff->crc_error_map &= offset_mask; /* keep skipped part */
  } else {
    buff->crc_error_map = 0;
  }
  buff->bad_sector_map = bad_sector_map[ buff->segment_id];
  if (buff->bad_sector_map != 0) {
    TRACEx2( 4, "segment: %d, bad sector map: %08lx",
            buff->segment_id, buff->bad_sector_map);
  } else {
    TRACEx1( 5, "segment: %d", buff->segment_id);
  }
  if (buff->sector_offset > 0) {
    buff->bad_sector_map >>= buff->sector_offset;
  }
  if (buff->sector_offset != 0 || buff->remaining != SECTORS_PER_SEGMENT) {
    TRACEx2( 5, "sector offset = %d, count = %d",
            buff->sector_offset, buff->remaining);
  }
  /*
   *    Segments with 3 or less sectors are not written with
   *    valid data because there is no space left for the ecc.
   *    The data written is whatever happens to be in the buffer.
   *    Reading such a segment will return a zero byte-count.
   *    To allow us to read/write segments with all bad sectors
   *    we fake one readable sector in the segment. This prevents
   *    having to handle these segments in a very special way.
   *    It is not important if the reading of this bad sector
   *    fails or not (the data is ignored). It is only read to
   *    keep the driver running.
   *    The QIC-40/80 spec. has no information on how to handle
   *    this case, so this is my interpretation.
   */
  if (buff->bad_sector_map == EMPTY_SEGMENT) {
    TRACE( 5, "empty segment, fake first sector good");
    buff->bad_sector_map = FAKE_SEGMENT;
  }
  fdc_setup_error = 0;
  buff->next_segment = segment_id + 1;
  TRACE_EXIT;
  return 0;
}

/*      Calculate Floppy Disk Controller and DMA parameters for a new segment.
 */
int
setup_new_segment( buffer_struct* buff, unsigned int segment_id,
                  int offset, unsigned int count)
{
  TRACE_FUN( 5, "setup_new_segment");
  int result = 0;
  static int old_segment_id = -1;
  static int old_ftape_state = idle;
  int skip = 0;

  TRACEx3( 5, "%s segment %d (old = %d)",
          (ftape_state == reading) ? "reading" : "writing",
          segment_id, old_segment_id);
  if (ftape_state != old_ftape_state) { /* when verifying */
    old_segment_id = -1;
    old_ftape_state = ftape_state;
  }
  if (segment_id == old_segment_id) {
    ++buff->retry;
    ++history.retries;
    TRACEx1( 5, "setting up for retry nr %d", buff->retry);
    if (offset < 0 && buff->skip > 0) { /* allow skip on retry */
      offset = buff->skip;
      count -= offset;
      skip = 1;
      TRACEx1( 5, "skipping %d sectors", offset);
    }
  } else {
    buff->retry = 0;
    buff->skip = 0;
    buff->start_offset = 0;
    old_segment_id = segment_id;
  }
  if (offset < 0) {             /* if still don't care, set to 0 */
    offset = 0;
  }
  result = setup_segment( buff, segment_id, offset, count, skip);
  TRACE_EXIT;
  return result;
}

/*      Determine size of next cluster of good sectors.
 */
int
calc_next_cluster( buffer_struct* buff)
{
  /* Skip bad sectors.
   */
  while (buff->remaining > 0 && (buff->bad_sector_map & 1) != 0) {
    buff->bad_sector_map >>= 1;
    ++buff->sector_offset;
    --buff->remaining;
  }
  /* Find next cluster of good sectors
   */
  if (buff->bad_sector_map == 0) {  /* speed up */
    buff->sector_count = buff->remaining;
  } else {
    buff->sector_count = 0;
    while (buff->sector_count < buff->remaining &&
           (buff->bad_sector_map & 1) == 0) {
      ++buff->sector_count;
      buff->bad_sector_map >>= 1;
    }
  }
  return buff->sector_count;
}

/*      Read Id of first sector passing tape head.
 */
int
ftape_read_id( int *location)
{
  TRACE_FUN( 8, "ftape_read_id");
  int result;
  int status;
  byte out[2];

  /* Assume tape is running on entry, be able to handle
   * situation where it stopped or is stopping.
   */
  *location = -1;               /* default is location not known */
  out[ 0] = FDC_READID;
  out[1] = FTAPE_UNIT;
  result = fdc_command( out, 2);
  if (result < 0) {
    TRACE( 1, "fdc_command failed");
  } else {
    /* set timeout so that the tape will run to logical EOT if
     * we missed the last sector and there are no INDEX cue pulses.
     */
    result = fdc_interrupt_wait( 4 * SECOND);
    if (result == -ETIME) {
      ftape_report_drive_status( &status);
      if (status & QIC_STATUS_READY) {
        /* not running any more !
         */
        TRACE( 5, "tape has stopped");
        if (((ftape_track & 1) == 0 && (status & QIC_STATUS_AT_EOT)) ||
            ((ftape_track & 1) != 0 && (status & QIC_STATUS_AT_BOT))) {
          /* Say we're passing last segment on track
           */
          *location = (ftape_track + 1) * segments_per_track - 1;
        }
      } else {
        TRACE( 1, "weird, drive busy but no data");
      }
      result = 0;
    } else if (result == 0) {
      /*    fdc_sect is set by fdc_isr(), will be 0 on error.
       */
      if (fdc_sect != 0) {
        *location = (segments_per_head * fdc_head
                     + segments_per_cylinder * fdc_cyl
                     + (fdc_sect - 1) / SECTORS_PER_SEGMENT);
      }
    } else {
      /* Interrupted or otherwise failing fdc_interrupt_wait()
       */
    }
  }
  if (*location == -1) {
    TRACE( 5, "no id found");
  } else {
    TRACEi( 5, "passing segment ", *location);
  }
  TRACE_EXIT;
  return result;
}


int
ftape_smart_stop( int* location_known, int* location)
{
  TRACE_FUN( 5, "ftape_smart_stop");
  int result;
  int status;

  /* Abort current fdc operation if it's busy (probably read
   * or write operation pending) with a reset.
   */
  result = fdc_ready_wait( 100 /* usec */);
  if (result < 0) {
    TRACE( 1, "aborting fdc operation");
    fdc_reset();
  }
  ftape_report_drive_status( &status);
  if (status & QIC_STATUS_READY) {
    /* Tape is not running any more.
     */
    TRACE( 5, "tape already halted");
    if (((ftape_track & 1) == 0 && (status & QIC_STATUS_AT_EOT)) ||
        ((ftape_track & 1) != 0 && (status & QIC_STATUS_AT_BOT))) {
      /* Say we're passing last segment on track
       */
      *location = (ftape_track + 1) * segments_per_track - 1;
      *location_known = 1;
    } else {
      *location_known = 0;
    }
  } else {
    /* Tape is (was) still moving.
     */
    result = ftape_read_id( location);
    *location_known = (*location != -1);
#if 0
    if (*location_known && fdc_sect > 1) {
      /*  If not at first sector of segment, pretend we're one ahead.
       *  This will cure some start problems causing no-data errors.
       */
      ++*location;
      TRACEx1( 2, "incrementing location because sector = %d", fdc_sect);
    }
#endif
    result = ftape_command_wait( QIC_STOP_TAPE, 5 * SECOND, &status);
    if (result < 0) {
      TRACE( 1, "problem stopping tape");
      *location_known = 0;
    }
  }
  if (*location_known) {
    TRACEi( 4, "tape stopped passing segment:", *location);
  } else {
    TRACE( 4, "tape stopped at undetermined location");
  }
  TRACE_EXIT;
  return result;
}

/*      Wait until runner has finished tail buffer.
 */
int
wait_segment( buffer_state_enum state)
{
  TRACE_FUN( 5, "wait_segment");
  int result = 0;

  while (buffer[ tail].status == state) {
    /*  First buffer still being worked on, wait up to timeout.
     */
    result = fdc_interrupt_wait( 15 * SECOND);
    if (result < 0) {
      if (result != -EINTR) {
        TRACE( 1, "fdc_interrupt_wait failed");
        result = -ETIME;
      }
      break;
    }
    if (fdc_setup_error) {
      TRACE( 1, "setup error");
      /* recover... */
      result = -EIO;
      break;
    }
  }
  TRACE_EXIT;
  return result;
}

int
fast_seek( int count)
{
  TRACE_FUN( 5, "fast_seek");
  int result;
  int status;

  /* timeout for stop-tape command
   */
  result = ftape_ready_wait( 5 * SECOND, &status);
  if (result < 0) {
    TRACE( 1, "drive not ready");
    result = -EIO;
  } else {
    /* Issue this tape command first. */
    if (count >= 0) {
      TRACEx1( 5, "skipping %d segments", count);
      result = ftape_command( QIC_SKIP_FORWARD);
    } else {
      TRACEx1( 5, "backing up %d segments", -count);
      count = -count;
      result = ftape_command( QIC_SKIP_REVERSE);
    }
    if (result >= 0) {
      /* Issue the low nibble of the command. */
      result = ftape_parameter( 2 + (count & 0x0f));
      if (result >= 0) {
        /* Issue the high nibble and wait for the command to complete. */
        result = ftape_parameter( 2 + ((count >> 4) & 0x0f));
        if (result >= 0) {
          result = ftape_ready_wait( 85 * SECOND, &status);
        }
      }
    }
  }
  TRACE_EXIT;
  return result;
}

/*      Get the tape running and position it just before the
 *      requested segment.
 *      Seek tape-track and reposition as needed.
 *      Use information from smart_stop and ftape_track.
 */
int
ftape_start_tape( int segment_id)
{
  TRACE_FUN( 5, "ftape_start_tape");
  int track = segment_id / segments_per_track;
  int next_segment = -1;
  int result;
  int status;
  int at_end_of_track;
  int tape_running = 0;
  int rewind_gaps;
  static int reverse_margin;
  static int last_segment = -1;
  int ids_read;
  static int bad_bus_timing = 0;

  if (buffer[ head].retry > 0) { /* this is a retry */
    TRACEx1( 5, "seek segment %d (again)", segment_id);
    if (!bad_bus_timing && ftape_data_rate == 1 && 
        history.overrun_errors - overrun_count_offset >= 8) {
      ftape_set_data_rate( ++ftape_data_rate);
      bad_bus_timing = 1;
      TRACE( 2, "reduced datarate because of excessive overrun errors");
    }
  } else {
#ifdef SLOW_STARTER
    reverse_margin = 1;
#else
    reverse_margin = 0;
#endif
    TRACEi( 5, "seek segment", segment_id);
  }
  last_segment = segment_id;
  do {
    if (!tape_running) {
      result = ftape_ready_wait( 15 * SECOND, &status);
      if (result < 0) {
        TRACEi( 1, "wait for ready failed with code", result);
        break;
      }
    } else {
      result = ftape_report_drive_status( &status);
      if (result < 0) {
        TRACEi( 1, "ftape_report_drive_status failed with code", result);
        break;
      }
    }
    if (ftape_track != track) {
      /* current unknown or not equal to destination
       */
      int save_track = ftape_track;

      TRACEi( 5, "seeking head to track", track);
      result = ftape_ready_wait( 15 * SECOND, &status);
      if (result >= 0) {
        result = ftape_seek_head_to_track( track);
      }
      if (result < 0) {
        ftape_track = save_track;
        TRACE( 2, "seeking head to track failed, may cause big trouble !");
      }
      overrun_count_offset = history.overrun_errors;
      location_known = 0;
    }
    at_end_of_track = 0;
    if (location_known) {
      TRACE( 5, "position known");
      next_segment = 1 + current_segment;
      if (!tape_running && segment_id == next_segment &&
          segment_id % segments_per_track != 0) {
        /*  Pretend we're one further ahead than we really are.
         *  This will cure some no-data errors when starting.
         */
        ++next_segment;
      }
    } else while (!location_known) {
      if (status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT)) {
        if (((ftape_track & 1) == 0 && (status & QIC_STATUS_AT_EOT)) ||
            ((ftape_track & 1) != 0 && (status & QIC_STATUS_AT_BOT))) {
          /* tape at end of track, will stop or is already stopped */
          tape_running = 0;
          TRACE( 5, "positioned at end of track");
          next_segment = (ftape_track + 1) * segments_per_track;
          at_end_of_track = 1;
        } else {
          TRACE( 5, "positioned at begin of track");
          next_segment = ftape_track * segments_per_track;
        }
        location_known = 1;
      } else {
        if (!tape_running) {
          ftape_command( QIC_LOGICAL_FORWARD);
          TRACE( 5, "tape started at unknown position");
          tape_running = 1;
        }
        result = ftape_read_id( &current_segment);
        if (result == 0 && current_segment >= 0) {
          next_segment = 1 + current_segment;
          location_known = 1;
        } else {
          TRACE( 5, "ftape_read_id failed");
          result = ftape_report_drive_status( &status);
          if (result < 0) {
            TRACEi( 1, "ftape_report_drive_status failed with code", result);
            TRACE_EXIT;
            return result;
          }
        }
      }
    } /* while (!location_known) */
    /*
     * We're on the right track now and our position is also known
     */
    rewind_gaps = next_segment - segment_id;
    if (rewind_gaps > 0) {        /* in or past target segment */
      if (tape_running) {
        result = ftape_command_wait( QIC_STOP_TAPE, 5 * SECOND, &status);
        if (result < 0) {
          TRACEi( 1, "stop tape failed failed with code", result);
          break;
        }
        TRACE( 5, "tape stopped");
        tape_running = 0;
      }
      TRACE( 5, "repositioning");
      if (segment_id % segments_per_track == 0) {
        /*  If seeking to first segment on track better do a complete
         *  rewind to logical begin of track to get a more steady tape
         *  motion.
         */
        result = ftape_command_wait( (ftape_track & 1) ?
                                    QIC_PHYSICAL_FORWARD :
                                    QIC_PHYSICAL_REVERSE,
                                    85 * SECOND, &status);
        next_segment = segment_id;
      } else {
        fast_seek( -(rewind_gaps + reverse_margin +
                     buffer[ head].start_offset - 1));
        /* to get actual position
         */
        if (!tape_running) {
          ftape_command( QIC_LOGICAL_FORWARD);
          TRACE( 5, "tape started at unknown position");
          tape_running = 1;
        }
        result = ftape_read_id( &current_segment);
        if (result == 0 && current_segment >= 0) {
          next_segment = 1 + current_segment;
        } else {
          TRACE( 5, "ftape_read_id failed");
          result = ftape_report_drive_status( &status);
          if (result < 0) {
            TRACEi( 1, "ftape_report_drive_status failed with code", result);
            TRACE_EXIT;
            return result;
          }
        }
      }
      ++history.rewinds;
      if (!tape_running) {
        result = ftape_ready_wait( 15 * SECOND, &status);
        if (result < 0) {
          TRACEi( 1, "wait for ready failed with code", result);
          break;
        }
        result = ftape_report_drive_status( &status);
        if (result < 0) {
          TRACEi( 1, "ftape_report_drive_status failed with code", result);
          break;
        } 
        if (status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT)) {
          /* can only be at the beginning of a track */
          next_segment = ftape_track * segments_per_track;
          tape_running = 0;       /* probably stopped */
        }
      }
    }
    /*
     * We're on track, before the right segment now
     */
    if (!tape_running) {
      ftape_command( QIC_LOGICAL_FORWARD);
      TRACEi( 5, "starting tape before segment", next_segment);
      tape_running = 1;
    }
    ids_read = 0;
    while (next_segment < segment_id) {
      result = ftape_read_id( &current_segment);
      if (result == 0 && current_segment >= 0) {
        next_segment = 1 + current_segment;
        TRACEi( 5, "skipping segment", current_segment);
        if (segment_id - next_segment >= 8) {
          result = ftape_command_wait( QIC_STOP_TAPE, 5 * SECOND, &status);
          if (result >= 0) {
            TRACE( 5, "tape stopped");
            fast_seek( segment_id - next_segment - 3);
            result = ftape_command_wait( QIC_STOP_TAPE, 5 * SECOND, &status);
            if (result >= 0) {
              TRACE( 5, "tape stopped");
              ftape_command( QIC_LOGICAL_FORWARD);
              TRACEi( 5, "starting tape before segment", next_segment);
              tape_running = 1;
              result = ftape_read_id( &current_segment);
              if (result == 0 && current_segment >= 0) {
                next_segment = 1 + current_segment;
              }
            }
          }
        }
      } else {
        if (ids_read++ > 25) {  /* must be enough */
          TRACE( 1, "read_id failed completely");
          TRACE_EXIT;
          return -EIO;
        }
      }
    }
    if (next_segment == segment_id) {
      TRACE_EXIT;
      return 0;                   /* on target */
    }
    if (reverse_margin == 0) {
      reverse_margin = 1;
    } else {
      reverse_margin *= 2;        /* for retry */
    }
    result = -EIO;
  } while (reverse_margin <= 8 && !(current->signal & ~current->blocked));
  TRACE( 1, "failed to reposition");
  TRACE_EXIT;
  return result;
}
