/*
 *      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-rw.c,v $
 $Author: bas $
 *
 $Revision: 1.10 $
 $Date: 1993/12/04 11:52:41 $
 $State: Alpha $
 *
 *      This file contains the reading and writing code
 *      for the QIC-40/80 floppy-tape driver for Linux.
 */

static char RCSid[] = "$Id: ftape-rw.c,v 1.10 1993/12/04 11:52:41 bas Alpha $";


#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 "kernel-interface.h"
#include "qic117.h"
#include "fdc-io.h"
#include "ftape-io.h"
#include "ftape-rw.h"
#include "ecc.h"

int tracing = 2;                /* for debugging: higher -> more verbose */

fdc_mode_enum fdc_mode = fdc_idle;
volatile enum runner_status_enum runner_status = idle;

unsigned int bad_sector_table[ 4200]; /* max size for QIC-80, 307.5 ft tape */
volatile int head;
volatile int tail;              /* not volatile but need same type as head */
int fdc_setup_error;
buffer_struct buffer[ NR_FTAPE_BUFFERS];
volatile unsigned char fdc_head; /* FDC head */
volatile unsigned char fdc_cyl; /* FDC track */
volatile unsigned char fdc_sect; /* FDC sector */
volatile unsigned int bad_sector_map; /* bad sector map for one segment */
volatile unsigned int next_segment; /* next segment for read ahead */
volatile unsigned int sector_count; /* nr of sectors to read from segment */
volatile unsigned int remaining; /* nr of sector remaining in segment */
volatile unsigned long int buffer_ptr; /* where to put next sector(s) */
volatile unsigned char sector_offset; /* offset for first sector to read */

struct wait_queue *wait_intr = NULL;
unsigned char global_in[ 7];
unsigned char deblock_buffer[ 29 * SECTOR_SIZE];

static int buf_pos_rd = 0;
static int buf_pos_wr = 0;
static int buf_len_rd = 0;
static int last_write_failed = 0;
static int need_flush = 0;

#define REVERSE_MARGIN 1

static int start_tape( int segment_id, int start_margin);

/*      Increment cyclic buffer nr.
 */
void
next_buffer( volatile int* x)
{
  if (++*x >= NR_ITEMS( buffer)) {
    *x = 0;
  }
}

/*      Calculate Floppy Disk Controller and DMA parameters for a new segment.
 */
int
setup_new_segment( unsigned int head, unsigned int segment_id,
                  unsigned int offset, unsigned int count)
{
  buffer[ head].segment_id = segment_id;
  buffer[ head].crc_error_map = 0; /* bad bits will get merged in */
  next_segment= segment_id + 1; /* for read ahead */
  buffer_ptr = (long) buffer[ head].address;
  /*
   *    Segments with 3 or less sectors are not written with
   *    valid data because there is no space 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.
   */
  bad_sector_map = bad_sector_table[ segment_id];
  if (bad_sector_map == 0xffffffff) {
    TRACE( 2, "setup_new_segment", "empty segment, faking one useable sector");
    bad_sector_map = EMPTY_SEGMENT; /* fake one readable/writeable sector */
  }
  remaining = count;
  sector_offset = offset;

  fdc_head = segment_id / segments_per_head;
  fdc_cyl = (segment_id % segments_per_head) / segments_per_cylinder;
  fdc_sect = (segment_id % segments_per_cylinder) * sectors_per_segment + 1;

  fdc_setup_error = 0;
  TRACElx( 4, "setup_new_segment", "bad_sector_map =", bad_sector_map);
  return 0;
}

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

/*      Setup Floppy Disk Controller and DMA to read or write the next cluster
 *      of good sectors from or to the current segment.
 */
int
setup_fdc_and_dma( unsigned char operation)
{
  unsigned long flags;
  unsigned char out[ 9];
  int result;
  int dma_mode;

  if (operation == FDC_READ || operation == FDC_READ_DELETED) {
    dma_mode = DMA_MODE_READ;
  } else if (operation == FDC_WRITE || operation == FDC_WRITE_DELETED) {
    dma_mode = DMA_MODE_WRITE;
  } else {
    TRACE( 0, "setup_fdc_and_dma", "illegal operation parameter");
    return -EIO;
  }

  if (tracing > 8) {  
    TRACE( -1, "setup_fdc_and_dma", "Status:");
    printk( "-- remaining: %d\n", remaining);
    printk( "-- transfer count = %d sectors\n", sector_count);
    printk( "-- destination address = 0x%08lx\n", (unsigned long) buffer_ptr);
  }
  
  /* Program the DMA controller.
   */
  save_flags( flags);
  cli();                      /* could be called from ISR ! */
  disable_dma( FLOPPY_DMA);
  clear_dma_ff( FLOPPY_DMA);
  set_dma_mode( FLOPPY_DMA, dma_mode);
  set_dma_addr( FLOPPY_DMA, buffer_ptr);
  set_dma_count( FLOPPY_DMA, SECTOR_SIZE * sector_count);
  enable_dma( FLOPPY_DMA);
  
  /* Issue FDC command to start reading/writing.
   */
  out[0] = operation;
  out[1] = ftape_unit;
  out[2] = fdc_cyl;
  out[3] = fdc_head;
  out[4] = fdc_sect + sector_offset;
  out[5] = 3;                   /* Sector size of 1K. */
  out[6] = fdc_sect + sector_offset + sector_count - 1; /* last sector */
  out[7] = 116;                 /* Gap length. */
  out[8] = 0xff;		/* No limit to transfer size. */
  restore_flags( flags);

  if (tracing > 8) {
    TRACE( -1, "setup_fdc_and_dma", "FDC programming info:");
    printk( "-- cyl.=0x%02x, head=0x%02x, sect.=0x%02x, count=0x%02x\n",
           out[ 2], out[ 3], out[ 4], out[ 6]- out[ 4]+ 1);
  }

  /* After all sectors are done an interrupt will follow.
   */
  result = fdc_command( out, 9);
  if (result != 0) {
    fdc_mode = fdc_idle;
    TRACE( 0, "setup_fdc_and_dma", "fdc_command failed");
  }
  fdc_setup_error = result;
  return result;
}

/*      Read Id of first sector passing tape head.
 */
int
ftape_read_id( struct _fdt_id *location)
{
  int result;
  unsigned char out[2];
  unsigned char reason;
  int retry;

  for (retry = 0; retry < 3; ++retry) {

    out[ 0] = FDC_READID;
    out[ 1] = ftape_unit;
    result = fdc_command( out, 2);
    if (result < 0) {
      TRACE( 0, "ftape_read_id", "fdc_command failed");
      break;
    }
  
    result = fdc_interrupt_wait( 5 * SECOND);
    if (result < 0) {
      TRACE( 1, "ftape_read_id", "fdc_interrupt_wait failed");
      break;
    }

    /*    result set by isr in global_in.
     */
    reason = global_in[ 0] & ST0_INT_MASK;
    if (reason == FDC_INT_NORMAL) {
      location->cylinder = global_in[ 3];
      location->head = global_in[ 4];
      location->sector = global_in[ 5];
      result = 0;
      break;
    } else if (reason == FDC_INT_ABNORMAL && (global_in[ 1] & 0x01) != 0) {
      /* missing address mark, retry */
      result = -EIO;            /* if too many retries */
      continue;
    } else {
      if (tracing > 1) {
        TRACE( -1, "ftape_read_id", "Error status:");
        printk( "   st0=0x%02x, st1=0x%02x, st2=0x%02x\n",
               global_in[ 0], global_in[ 1], global_in[ 2]);
      }
      result = -EIO;
      break;
    }
  }
  return result;
}


int smart_stop_tape( void)
{
  int result;

  result = ftape_read_id( &location);
  location_known = (result == 0);

  ftape_command( QIC_STOP_TAPE);

  return 0;
}


/*      Wait until runner has finished tail buffer.
 */
int
wait_segment( buffer_state_enum state)
{
  int result;

  while (buffer[ tail].status == state) {
    /*  First buffer still being done, wait...
     */
    result = fdc_interrupt_wait( 5 * SECOND);
    if (result < 0) {
      if (result == -EINTR) {
        return result;
      }
      TRACE( 1, "wait_segment", "fdc_interrupt_wait failed");
      return -ETIME;
    }
    if (fdc_setup_error) {
      TRACE( 0, "wait_segment", "setup error");
      /* recover... */
      return -EIO;
    }
  }
  return 0;
}


static unsigned char org_buffer[ 0x8000];
static struct memory_segment org_data = {
  0, 0, 0, org_buffer, 0
};

int
correct_and_copy( unsigned int tail, unsigned char* destination)
{
  struct memory_segment mseg;
  int result;

  mseg.read_bad = buffer[ tail].crc_error_map;
  mseg.marked_bad = 0;          /* not used... */
  mseg.blocks = buffer[ tail].bytes / SECTOR_SIZE;
  mseg.data = buffer[ tail].address;

  /*    If there are no data sectors we can skip this segment.
   */
  if (mseg.blocks <= 3) {
    TRACE( 1, "correct_and_copy", "empty segment");
    return 0;
  }

#ifdef DEBUG_ECC
  memcpy( org_data.data, mseg.data, sizeof( org_buffer));
#endif
  org_data.blocks = mseg.blocks;
  org_data.read_bad = mseg.read_bad;

  result = ecc_correct_data( &mseg);

  if (org_data.read_bad != 0 || mseg.corrected != 0) {
    TRACElx( 4, "correct_and_copy", "org_data.read_bad =", org_data.read_bad);
    TRACElx( 4, "correct_and_copy", "mseg.corrected    =", mseg.corrected);
  }

  if (result == ECC_CORRECTED || result == ECC_OK) {
    if (result == ECC_CORRECTED) {
      TRACEi( 0, "correct_and_copy", "corrected segment nr", buffer[ tail].segment_id);
    }
    memcpy( destination, mseg.data, (mseg.blocks - 3) * SECTOR_SIZE);
    if ((org_data.read_bad ^ mseg.corrected) & mseg.corrected) {
      /* corrected sector(s) without crc error(s) */
      history.CRC_failures++;
    }
    return (mseg.blocks - 3) * SECTOR_SIZE;
  } else {
    TRACEi( 0, "correct_and_copy", "ecc failure on segment", buffer[ tail].segment_id);
    history.ECC_failures++;
    return -EAGAIN;             /* should retry */
  }
}

inline int
get1( unsigned char* address, unsigned int offset)
{
  return address[ offset];
}

inline int
get2( unsigned char* address, unsigned int offset)
{
  return address[ offset] + 256 * address[ offset + 1];
}

inline int
get4( unsigned char* address, unsigned int offset)
{
  return get2( address, offset) + 256 * 256 * get2( address, offset + 2);
}

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

/* forward */ int
read_segment( unsigned int segment_id, unsigned char* address);

int
read_header_segment( unsigned char* address)
{
  int i;
  int result;
  int header_segment = -1;
  unsigned int tape_segments_per_track;
  unsigned int tape_tracks_per_cartridge;
  unsigned int maximum_floppy_side;
  unsigned int maximum_floppy_track;
  unsigned int maximum_floppy_sector;
  unsigned int failed_sector_count;

  /*    Find first header segment on tape.
   *      THIS IS NOT TESTED !!! Can only be tested with
   *      a tape that has bad sectors in first segment(s).
   *    Get start of data area and verify tape parameters.
   */
  TRACE( 4, "read_header_segment", "reading...");
  do {
    ++header_segment;
    bad_sector_table[ header_segment] = 0x00000000;
    result = read_segment( header_segment, address);
    if (header_segment > 5) {
      /* probably something else wrong, don't keep stuck
       * in this loop !
       */
      TRACE( 0, "read_header_segment", "header segment not found");
      return -EIO;
    }
  } while (result < 0);

  if (get4( address, 0) != 0xaa55aa55) {
    TRACE( 1, "read_header_segment", "wrong signature");
    return -EIO;
  }
  if (get2( address, 6) != header_segment && /* first header */
      get2( address, 8) != header_segment) { /* duplicate */
    TRACE( 1, "read_header_segment", "wrong segment nr");
    return -EIO;
  }

  /*    Verify tape parameters...
   *    QIC-40/80 spec:                 tape_parameters:
   *
   *    segments-per-track              segments_per_track
   *    tracks-per-cartridge            tracks_per_tape
   *    max-floppy-side                 (segments_per_track *
   *                                    tracks_per_tape - 1) /
   *                                    segments_per_head
   *    max-floppy-track                segments_per_head /
   *                                    segments_per_cylinder - 1
   *    max-floppy-sector               segments_per_cylinder *
   *                                    sectors_per_segment
   */

  tape_segments_per_track = get2( address, 24);
  tape_tracks_per_cartridge = get1( address, 26);
  maximum_floppy_side = get1( address, 27);
  maximum_floppy_track = get1( address, 28);
  maximum_floppy_sector = get1( address, 29);

  TRACEi( 2, "read_header_segment", "segments-per-track:  ", tape_segments_per_track);
  TRACEi( 2, "read_header_segment", "tracks-per-cartridge:", tape_tracks_per_cartridge);
  TRACEi( 2, "read_header_segment", "max-floppy-side:     ", maximum_floppy_side);
  TRACEi( 2, "read_header_segment", "max-floppy-track:    ", maximum_floppy_track);
  TRACEi( 2, "read_header_segment", "max-floppy-sector:   ", maximum_floppy_sector);

  if (tape_segments_per_track == 0 && tape_tracks_per_cartridge == 0 &&
      maximum_floppy_side == 0 && maximum_floppy_track == 0 &&
      maximum_floppy_sector == 0) {
    /*
     *  QIC-40 Rev E and earlier
     */
    tape_segments_per_track = 68; 
    tape_tracks_per_cartridge = 20;
    maximum_floppy_side = 1;
    maximum_floppy_track = 169;
    maximum_floppy_sector = 128;
  }

  /*
   *    in case report_drive_configuration command failed,
   *    we set the parameters according to the tape data.
   */
  if (unknown_drive_config) {
    segments_per_track = tape_segments_per_track;
    tracks_per_tape = tape_tracks_per_cartridge;
    segments_per_head = ( (tracks_per_tape + maximum_floppy_side) /
                         (maximum_floppy_side + 1) ) * segments_per_track;
  }

  /*
   *    Verify drive_configuration with tape parameters
   */
  if ( (segments_per_track != tape_segments_per_track) ||
      (tracks_per_tape != tape_tracks_per_cartridge) ||
      ((segments_per_track * tracks_per_tape - 1) / segments_per_head
       != maximum_floppy_side) ||
      (segments_per_head / segments_per_cylinder - 1 != maximum_floppy_track) ||
      (segments_per_cylinder * sectors_per_segment != maximum_floppy_sector) ) {
    TRACE( 0, "read_header_segment", "Tape parameters error");
    return -EIO;
  }

  first_data_segment = get2( address, 10); /* first data segment */
  TRACEi( 4, "read_header_segment", "first data segment:", first_data_segment);

  /* First copy the bad sector map from sector 2 and up into our own table.
   * Then merge the failed sector log into this map.
   */
  memcpy( bad_sector_table, address + 2 * SECTOR_SIZE, sizeof( bad_sector_table));
  failed_sector_count = get2( address, 144);
  if (failed_sector_count > 0) {
    TRACE( -1, "read_header_segment", "Gosh, somebody is using the failed sector list !");
    TRACE( -1, "read_header_segment", "So it makes sense to implement it, sometime...");
  }

#if 0
  /* test set for damaged Verbatim tape
   * with magnetically destroyed area
   */
  bad_sector_table[ 16] = 0xffffff80;
  bad_sector_table[ 17] = 0xffffffff;
  bad_sector_table[ 18] = 0x0000ffff;
#endif
#if 0
  /* use this to simulate bad sectors */
  bad_sector_table[ 10] = 0xfffffffe;
  bad_sector_table[ 12] = 0x7fffffff;
  bad_sector_table[ 14] = 0xfffeffff;
  bad_sector_table[ 16] = 0x55555555;
  bad_sector_table[ 18] = 0xffffffff;
  bad_sector_table[ 30] = 0xffff0000;
  bad_sector_table[ 31] = 0xffffffff;
  bad_sector_table[ 32] = 0xffffffff;
  bad_sector_table[ 33] = 0x0000ffff;
#endif
  if (tracing > 2) {    
    unsigned int map;
    int good_sectors = 0;
    int bad_sectors;
    unsigned int total_bad = 0;

    for (i = first_data_segment; i < segments_per_track * tracks_per_tape; ++i) {
      map = bad_sector_table[ i];
      bad_sectors = count_bits( map);
      if (bad_sectors > 0) {
        printk( "Bad sector(s) in segment %d, map = 0x%08x\n", i, map);
        if (bad_sectors > sectors_per_segment - 3) {
          bad_sectors = sectors_per_segment - 3;
        }
        total_bad += bad_sectors;
      }
      good_sectors += sectors_per_segment - 3 - bad_sectors;
    }
    TRACEi( 2, "read_header_segment", "nr of Kb's useable on this tape:", good_sectors);
    if (total_bad == 0) {
      TRACE( -1, "read_header_segment", "WARNING: this tape has no bad blocks registered !");
    }
  }

  ftape_reset_position();

  return 0;
}

/*      Read given segment into buffer at address.
 */
int
read_segment( unsigned int segment_id, unsigned char* address)
{
  int read_done= 0;
  int result;
  int bytes_read = 0;
  int retry = 0;
  
  TRACEi( 4, "read_segment", "entered, segment_id =", segment_id);

  if (ftape_state != reading) {
    if (ftape_state == writing) {
      ftape_flush_buffers();    /* flush write buffer */
      TRACE( 0, "read_segment", "calling ftape_abort_operation");
      result = ftape_abort_operation();
      if (result < 0) {
        TRACE( 0, "read_segment", "ftape_abort_operation failed");
        return -EIO;
      }
    } else {
      /* clear remaining read buffers */
      ftape_zap_buffers();
    }
    ftape_state = reading;
  }

  do {
    /*    Search all full buffers for the first matching the wanted segment.
     *    Clear other buffers on the fly.
     */
    while (!read_done && buffer[ tail].status == full) {
      if (buffer[ tail].segment_id == segment_id) {
        /*        If out buffer is yet full, return its contents.
         */
        TRACEi( 4, "read_segment", "found segment in cache :", segment_id);
        if (retry > 0) {
          history.retries++;
        }
        bytes_read = correct_and_copy( tail, address);
        TRACEi( 4, "read_segment", "segment contains (bytes) :", bytes_read);
        if (bytes_read < 0) {
          if (bytes_read == -EAGAIN) {
            TRACE( 0, "read_segment", "should retry, not yet implemented");
            /* IMPLEMENT RETRY HERE ! */
            return -EIO;
          } else {
            return bytes_read;
          }
        }
        read_done = 1;
      } else {
        TRACEi( 4, "read_segment", "zapping segment in cache :",
               buffer[ tail].segment_id);
      }
      buffer[ tail].status = empty;
      next_buffer( &tail);
    }
    if (!read_done && buffer[ tail].status == reading) {
      if (buffer[ tail].segment_id == segment_id) {
        int result = wait_segment( reading);
        if (result == -EINTR) {
          return result;
        }
        if (result < 0) {
          TRACE( 0, "read_segment", "wait_segment failed while reading");
          return -EIO;
        }
      } else {
        /*        We're reading the wrong segment, stop runner.
         */
        ftape_abort_operation();
      }
    }
    /*    if just passed the last segment on a track, wait for BOT or EOT mark.
     */
    if (runner_status == logical_eot) {
      int status;
      result = ftape_ready_wait( 10 * SECOND, &status);
      if (result < 0) {
        TRACE( 0, "read_segment", "ftape_ready_wait waiting for eot/bot failed");
      }
      if (status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT) == 0) {
        TRACE( 1, "read_segment", "eot/bot not reached");
      }
      runner_status = end_of_tape;
    }
    /*    should runner stop ?
     */
    if (runner_status == aborting || runner_status == buffer_overrun ||
        runner_status == end_of_tape) {
      smart_stop_tape();
      if (runner_status == aborting) {
        expect_stray_interrupt = 0;
        if (buffer[ head].status == reading || buffer[ head].status == error) {
          buffer[ head].status = empty;
          ++retry;
        }
      }
      runner_status = idle;       /* aborted ? */
    }
    /*    If segment to read is empty, do not start runner for it,
     *    but wait for next read call.
     */
    if (bad_sector_table[ segment_id] == 0xffffffff) {
      bytes_read = 0;
      read_done = 1;
    }
    if (read_done) {
      break;
    }
    
    /*    Now at least one buffer is empty !
     *    Restart runner & tape if needed.
     */
    if (tracing > 12) {
      TRACE( -1, "read_segment", "State:");
      printk( "-- head = %d, tail = %d\n", head, tail);
      printk( "-- runner_status = %d\n", runner_status);
      printk( "-- buffer[tail].status = %d\n", buffer[ tail].status);
    }
    
    if (runner_status == idle && buffer[ tail].status != full) {
      result= start_tape( segment_id, 10);
      if (result < 0) {
        TRACEi( 1, "read_segment", "Error - couldn't reach segment", segment_id);
        return result;
      }
      runner_status = running;
      
      /*  Start runner reading.
       */
      setup_new_segment( head, segment_id, 0, sectors_per_segment);
      buffer[ head].status = reading;
      if (calc_next_cluster() == 0) {
        TRACE( 1, "read_segment", "BUG: empty segment");
        return -EIO;
      }
      if (setup_fdc_and_dma( FDC_READ)) {
        buffer[ head].status = error;
        TRACE( 0, "read_segment", "setup error while starting");
        /* recover ...? */
        return -EIO;
      }
    }
    
    /*    If we got a segment quit, or else retry up to limit.
     */
  } while (!read_done && retry <= 2);

  if (read_done) {
    return bytes_read;
  } else {
    return -EIO;
  }
}

int
seek_reverse( int count)
{
  int result;
  int status;

  result = ftape_ready_wait( 5, &status);  /* timeout for stop-tape command */
  if (result < 0) {
    TRACE( 1, "seek_reverse", "drive not ready");
    return -EIO;
  }

  /* Issue this tape command first. */
  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, &status);
      }
    }
  }
  return result;
}

/*      Start the tape moving and get our position.
 *      If wanted segment is no more than margin ahead return the
 *      moment the segment preceding our target passes the head.
 *      If we must change from one tape track to another an error
 *      is returned.
 *      Presume ftape_track is correct !
 */

static int
start_tape( int segment_id, int start_margin)
{
  int track = segment_id / segments_per_track;
  int next_segment = -1;
  int result;
  int status;
  int tape_running = 0;

  TRACEi( 4, "start_tape", "seek segment", segment_id);

  result = ftape_ready_wait( 15, &status);
  if (result < 0) {
    TRACEi( 0, "start_tape", "wait for ready failed with code", result);
    return result;
  }
  result = ftape_report_drive_status( &status);
  if (result < 0) {
    TRACEi( 0, "start_tape", "ftape_report_drive_status failed with code", result);
    return result;
  }
  if (ftape_track != track) {   /* current unknown or not equal to destination */
    TRACEi( 4, "start_tape", "seeking head to track", track);
    ftape_seek_head_to_track( track);
  }
  if (location_known) {
    TRACE( 4, "start_tape", "position known");
    next_segment = 1 + (segments_per_head * location.head
                        + segments_per_cylinder * location.cylinder
                        + (location.sector - 1) / sectors_per_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( 4, "start_tape", "positioned at end of track");
        next_segment = (ftape_track + 1) * segments_per_track;
      } else {
        TRACE( 4, "start_tape", "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( 4,"start_tape", "tape started at unknown position");
        tape_running = 1;
      }
      result = ftape_read_id( &location);
      if (result == 0) {
        next_segment = 1 + (segments_per_head * location.head
                            + segments_per_cylinder * location.cylinder
                            + (location.sector - 1) / sectors_per_segment);
        location_known = 1;
      } else {
        result = ftape_report_drive_status( &status);
        if (result < 0) {
          TRACEi( 0, "start_tape", "ftape_report_drive_status failed with code", result);
          return result;
        }
      }
    }
  } /* while (!location_known) */

  if (next_segment < 0) {
    TRACE( -1,"start_tape", "bug: next_segment < 0");
    return -EIO;
  }
  /*
   * We're on the right track now and our location is also known
   */
  if (next_segment > segment_id) { /* past target segment */

    if (tape_running) {
      ftape_command( QIC_STOP_TAPE);
      TRACE( 4,"start_tape", "tape stopped");
    }
    TRACE( 4, "start_tape", "repositioning");
    seek_reverse( REVERSE_MARGIN + next_segment - segment_id);
    next_segment = segment_id - REVERSE_MARGIN;
    tape_running = 0;
    result = ftape_ready_wait( 15, &status);
    if (result < 0) {
      TRACEi( 0, "start_tape", "wait for ready failed with code", result);
      return result;
    }
    result = ftape_report_drive_status( &status);
    if (result < 0) {
      TRACEi( 0, "start_tape", "ftape_report_drive_status failed with code", result);
      return result;
    }
    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;
    }
  }
  /*
   * We're on track, before the right segment now
   */
  if (!tape_running) {
    ftape_command( QIC_LOGICAL_FORWARD);
    TRACEi( 4,"start_tape", "starting tape before segment", next_segment);
    tape_running = 1;
  }
  while (next_segment != segment_id) {
    result = ftape_read_id( &location);
    if (result == 0) {
      next_segment = 1 + (segments_per_head * location.head
                          + segments_per_cylinder * location.cylinder
                          + (location.sector - 1) / sectors_per_segment);
      TRACEi( 4, "start_tape", "skipping segment", next_segment);
    }
  }
  return 0;
}

int
_ftape_read( char* buff, int req_len)
{
  int result;
  int cnt;

  if (first_data_segment == -1) {
    result = read_header_segment( deblock_buffer);
    if (result < 0) {
      return result;
    }
  }

  while (buf_len_rd == 0) {     /* may not return with no bytes read ! */
    result = read_segment( ftape_seg_pos, deblock_buffer);
    if (result < 0) {
      printk( "ftape read error, result = %d (0x%08x)\n", result, result);
      return -EIO;
    }
    buf_pos_rd = 0;
    buf_len_rd = result;
    ++ftape_seg_pos;
  }

  if (req_len > buf_len_rd) {
    cnt = buf_len_rd;
  } else {
    cnt = req_len;
  }
  TRACEi( 10, "_ftape_read", "just read bytes:", cnt);

  result = verify_area( VERIFY_WRITE, buff, cnt);
  if (result) {
    TRACE( 0, "_ftape_read", "verify_area failed");
    return result;
  }
  memcpy_tofs( buff, deblock_buffer + buf_pos_rd, cnt);
  
  buf_pos_rd += cnt;            /* index in buffer */
  buf_len_rd -= cnt;            /* remaining bytes in buffer */
  ftape_chr_pos += cnt;         /* tape position */

  return cnt;                   /* bytes read */
}

int
copy_and_gen_ecc( char* destination, unsigned char* source,
                 unsigned int bad_sector_map)
{
  int result;
  struct memory_segment mseg;
  int bads = count_bits( bad_sector_map);

  if (bads > 0) {
    TRACEi( 4, "copy_and_gen_ecc", "bad sectors in map:", bads);
  }
  if (bads + 3 >= sectors_per_segment) {
    TRACE( 1, "copy_and_gen_ecc", "empty segment");
    mseg.blocks = 0;            /* skip entire segment */
    return 0;                   /* nothing written */
  } else {
    mseg.blocks = sectors_per_segment - bads;
  }
  mseg.data = destination;
  memcpy( mseg.data, source, (mseg.blocks - 3) * SECTOR_SIZE);
  result = ecc_set_segment_parity( &mseg);
  if (result < 0) {
    TRACE( 0, "copy_and_gen_ecc", "ecc_set_segment_parity failed");
    return result;
  }

  return (mseg.blocks - 3) * SECTOR_SIZE;
}

/*      Write given segment from buffer at address onto tape.
 */
int
write_segment( unsigned int segment_id, unsigned char* address, int flushing)
{
  int result;
  int bytes_written = 0;
  
  TRACEi( 4, "write_segment", "entered, segment_id =", segment_id);

  if (ftape_state != writing) {
    if (ftape_state == reading) {
      TRACE( 0, "write_segment", "calling ftape_abort_operation");
      result = ftape_abort_operation();
      if (result < 0) {
        TRACE( 0, "write_segment", "ftape_abort_operation failed");
        return -EIO;
      }
    }
    /* clear remaining read buffers */
    ftape_zap_buffers();
    ftape_state = writing;
  }
 
  /*    if all buffers full we'll have to wait...
   */
  wait_segment( writing);
  if (buffer[ tail].status == error) {
    /*  setup for a retry
     */
    buffer[ tail].status = full;
    bytes_written = -EAGAIN;    /* force retry */
  } else if (buffer[ tail].status != empty) {
    TRACE( 0, "write_segment", "wait for empty segment failed");
    return -EIO;
  }

  /*    If just passed last segment on tape: wait for BOT or EOT mark.
   */
  if (runner_status == logical_eot) {
    int status;
    result = ftape_ready_wait( 10 * SECOND, &status);
    if (result < 0) {
      TRACE( 0,"write_segment", "ftape_ready_wait waiting for eot/bot failed");
    }
    if (status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT) == 0) {
      TRACE( 1, "write_segment", "eot/bot not reached");
    }
    runner_status = end_of_tape;
  }
  /*    should runner stop ?
   */
  if (runner_status == aborting || runner_status == buffer_underrun ||
      runner_status == end_of_tape) {
    smart_stop_tape();
    if (runner_status == aborting) {
      expect_stray_interrupt = 0;
      if (buffer[ head].status == writing) {
        buffer[ head].status = empty; /* ????? */
      }
    }
    runner_status = idle;       /* aborted ? */
  }

  if (buffer[ tail].status == empty) {

    /*    now at least one buffer is empty, fill it with our data.
     *    skip bad sectors and generate ecc.
     *    copy_and_gen_ecc return nr of bytes written,
     *    range 0..29 Kb inclusive !
     */
    result = copy_and_gen_ecc( buffer[ tail].address, address,
                              bad_sector_table[ segment_id]);
    if (result < 0) {
      TRACE( 0, "write_segment", "copy_and_gen_ecc failed");
      return result;
    }
    bytes_written = result;
    buffer[ tail].segment_id = segment_id;
    buffer[ tail].status = full;
    next_buffer( &tail);
  }

  if (runner_status == running) {
    /* isr will keep things running */
    return bytes_written;
  }
  /*    Start tape only if all buffers full or flush mode.
   *    This will give higher probability of streaming.
   */
  if (buffer[ tail].status == full || flushing) {

    int segment_id = buffer[ head].segment_id;

    if (!flushing && head != tail) {
      TRACE( 0, "write_segment", "head != tail");
      return -EIO;
    }
    setup_new_segment( head, segment_id, 0, sectors_per_segment);
    if (buffer[ head].status != full) {
      TRACE( 0, "write_segment", "unexpected head status");
      return -EIO;
    }
    buffer[ head].status = writing;

    if (runner_status == idle) {
      int track = segment_id / segments_per_track;
      TRACEi( 4, "write_segment", "starting runner for segment", segment_id);
      if (ftape_track != track) {
        int status;
        TRACEi( 4, "write_segment", "seeking head to track", track);
        ftape_ready_wait( 5, &status);
        ftape_seek_head_to_track( track);
      }
      result= start_tape( segment_id, 10);
      if (result < 0) {
        TRACEi( 0, "write_segment", "Error - couldn't reach segment", segment_id);
        return result;
      }
      runner_status = running;
    }
    /*  Start runner writing.
     *  if there are 3 or less useable sectors in the segment
     *  they are not filled with valid data, and it must be
     *  ignored when reading.
     */
    if (calc_next_cluster() == 0) {
      /* could happen ! must skip this segment... TODO */
      TRACE( 1, "write_segment", "empty segment");
      return 0;
    }
    if (setup_fdc_and_dma( FDC_WRITE)) {
      buffer[ head].status = error;
      TRACE( 0, "write_segment", "setup error while starting");
      /* recover ... */
      return -EIO;
    }
  }
  return bytes_written;
}

int
_write_segment( unsigned int segment_id, unsigned char* buffer, int flush)
{
  static int retry = 0;
  int result = write_segment( segment_id, buffer, flush);

  if (result >= 0) {            /* success */
    retry = 0;                  /* clear after success */
  }
  if (result == 0) {            /* empty segment */
    TRACE( 4, "_write_segment", "empty segment, skipping");
  }
  if (result < 0) {
    if (result == -EAGAIN) {
      if (++retry > 3) {        /* only this often */
        TRACE( 4, "_write_segment", "write error, all retries failed");
        result = -EIO;
      } else {
        TRACE( 4, "_write_segment", "write error, retrying");
      }
    } else {
      TRACEi( 0, "_write_segment", "write_segment failed, error:", -result);
    }
  }
  return result;
}

void
ftape_zap_buffers( void)
{
  int i;

  for (i = 0; i < NR_ITEMS( buffer); ++i) {
    buffer[ i].address = ((char*)tape_buffer)+ i* BUFF_SIZE;
    buffer[ i].status = empty;
    buffer[ i].bytes = 0;
  }
  buf_len_rd = 0;
  buf_pos_rd = 0;

  ftape_state = idle;
}

int
ftape_flush_buffers( void)
{
  int result;
  int cnt;
  int buf_data_len;
  int _buf_pos_wr;

  TRACEi( -1, "ftape_flush_buffers", "entered, ftape_state =", ftape_state);

  if (ftape_state != writing && !need_flush) {
    return 0;
  }

  /*  Make copy of buf_pos_wr and clear original so if write
   *  fails once, further write attempts will be prevented.
   */
  _buf_pos_wr = buf_pos_wr;
  buf_pos_wr = 0;

  TRACE( 1, "ftape_flush_buffers", "flushing write buffers");

  if (last_write_failed) {
    ftape_zap_buffers();
    return -EIO;
  }

  buf_data_len = _buf_pos_wr;
  /*
   *    If there is any data not written to tape yet, append zero's
   *    to the buffer to fill the remainder of the segment.
   *    Then write the segment(s) to tape.
   */
  TRACEi( 4, "ftape_flush_buffers", "flush, remainder in buffer:", _buf_pos_wr);

  do {
    cnt = sizeof( deblock_buffer) - _buf_pos_wr; /* remaining */
    TRACEi( 4, "ftape_flush_buffers", "flush, padding count:", cnt);
    /* pad buffer with 0's */
    memset( deblock_buffer + _buf_pos_wr, 0, cnt);

    result = _write_segment( ftape_seg_pos, deblock_buffer, 1);
    if (result < 0) {
      if (result == -EAGAIN) {
        continue;
      }
      last_write_failed = 1;
      return result;
    }
    TRACEi( 4, "ftape_flush_buffers", "flush, moved out buffer:", result);
    if (result < buf_data_len) {
      /* Partial write, we'll need another segment
       */
      _buf_pos_wr = sizeof( deblock_buffer) - result;
      if (result > 0) {
        memmove( deblock_buffer, deblock_buffer + result, _buf_pos_wr);
      }
    } else {
      _buf_pos_wr = 0;
    }
    buf_data_len -= result;
    ++ftape_seg_pos;
  } while (buf_data_len > 0);
  TRACEi( 4, "ftape_flush_buffers", "flush buffer netto pad-count:", -buf_data_len);
  /*
   *  Wait until all data is actually written to tape.
   */
  while (head != tail || buffer[ head].status != empty) {
    int i;
    
    if (tracing > 1) {
      printk( "tail=%d, head=%d\n", tail, head);
      for (i = 0; i < NR_ITEMS( buffer); ++i) {
        printk( "buffer[%d].status = %d, ", i, buffer[i].status);
        printk( "buffer[%d].segment_id = %d\n", i, buffer[i].segment_id);
      }
    }
    result = fdc_interrupt_wait( 5 * SECOND);
    if (result < 0) {
      TRACE( 1, "wait_segment", "fdc_interrupt_wait failed");
      last_write_failed = 1;
      return result;
    }
    TRACE( 1, "ftape_flush_buffers", "looping until writes done");
  }
  ftape_state = idle;
  last_write_failed = 0;
  need_flush = 0;
  return 0;
}

int
_ftape_write( char* buff, int req_len)
{
  int result;
  int cnt;
  int written = 0;

  /*
   *    If we haven't read the header segment yet, do it now.
   *    This will verify the configuration and get the
   *    bad sector table. We'll use the buffer for scratch.
   */
  if (first_data_segment == -1) {
    result = read_header_segment( deblock_buffer);
    if (result < 0) {
      return result;
    }
  }

  /*
   *    This part writes data blocks to tape until the
   *    requested amount is written.
   *    The data will go in a buffer until we think it's
   *    enough for a segment (max segment size). Then
   *    the data in the buffer will be written to tape.
   *    The part not written to tape will remain in the
   *    buffer and will be prepended to new incoming data.
   */

  while (req_len > 0) {
    int space_left = sizeof( deblock_buffer) - buf_pos_wr;

    TRACEi( 10, "_ftape_write", "remaining req_len:", req_len);
    TRACEi( 10, "_ftape_write", "          buf_pos:", buf_pos_wr);

    cnt = (req_len < space_left) ? req_len : space_left;
    result = verify_area( VERIFY_READ, buff, cnt);
    if (result) {
      TRACE( 0, "_ftape_write", "verify_area failed");
      last_write_failed = 1;
      return result;
    }
    memcpy_fromfs( deblock_buffer + buf_pos_wr, buff, cnt);
    buff += cnt;
    req_len -= cnt;
    buf_pos_wr += cnt;
    TRACEi( 10, "_ftape_write", "moved into blocking buffer:", cnt);

    while (buf_pos_wr >= sizeof( deblock_buffer)) {
      /* Got one full buffer, write it to disk
       */
      result = _write_segment( ftape_seg_pos, deblock_buffer, 0);
      TRACEi( 10, "_ftape_write", "_write_segment result =", result);
      if (result < 0) {
        if (result == -EAGAIN) {
          TRACE( -1, "_ftape_write", "retry...");
          continue;             /* failed, retry same segment */
        }
        last_write_failed = 1;
        return result;
      }
      if (result > 0 && result < buf_pos_wr) {
        /* Partial write: move remainder in lower part of buffer
         */
        memmove( deblock_buffer, deblock_buffer + result, buf_pos_wr - result);
      }
      TRACEi( 10, "_ftape_write", "moved out of blocking buffer:", result);
      buf_pos_wr -= result;     /* remainder */
      ++ftape_seg_pos;
    }
    ftape_chr_pos += cnt;       /* tape position */
    written += cnt;
  }
  TRACEi( 10, "_ftape_write", "remaining in blocking buffer:", buf_pos_wr);
  TRACEi( 10, "_ftape_write", "just written bytes:", written);

  last_write_failed = 0;
  if (written > 0) {
    need_flush = 1;
  }
  return written;               /* bytes written */
}
