/*
 *      Copyright (C) 1993-1995 Bas Laarhoven.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

 $Source: /home/bas/distr/ftape-2.03b/RCS/ftape-write.c,v $
 $Author: bas $
 *
 $Revision: 1.26 $
 $Date: 1995/05/27 08:55:27 $
 $State: Beta $
 *
 *      This file contains the writing code
 *      for the QIC-117 floppy-tape driver for Linux.
 */

static char RCSid[] =
"$Id: ftape-write.c,v 1.26 1995/05/27 08:55:27 bas Beta $";


#include "kernel-version.h"
#ifdef MODULE
# include <linux/config.h>
# include <linux/module.h>
#endif
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <asm/segment.h>

#include "ftape.h"
#include "ftape-write.h"
#include "ftape-read.h"
#include "qic117.h"
#include "ftape-io.h"
#include "ftape-ctl.h"
#include "ftape-rw.h"
#include "ftape-eof.h"
#include "ecc.h"
#include "ftape-bsm.h"


/*      Global vars.
 */

/*      Local vars.
 */
static int buf_pos_wr = 0;
static int last_write_failed = 0;
static int need_flush = 0;

#define WRITE_MULTI  0
#define WRITE_SINGLE 1

void
ftape_zap_write_buffers( void)
{
  int i;

  for (i = 0; i < ftape_num_buffers; ++i) {
    buffer[ i].status = done;
  }
  need_flush = 0;
}

int
copy_and_gen_ecc( char* destination, byte* source,
                 unsigned int bad_sector_map)
{
  TRACE_FUN( 8, "copy_and_gen_ecc");
  int result;
  struct memory_segment mseg;
  int bads = count_ones( bad_sector_map);

  if (bads > 0) {
    TRACEi( 4, "bad sectors in map:", bads);
  }
  if (bads + 3 >= SECTORS_PER_SEGMENT) {
    TRACE( 4, "empty segment");
    mseg.blocks = 0;            /* skip entire segment */
    result = 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( 1, "ecc_set_segment_parity failed");
    } else {
      result = (mseg.blocks - 3) * SECTOR_SIZE;
    }
  }
  TRACE_EXIT;
  return result;
}

void
prevent_flush( void)
{
  need_flush = 0;
  ftape_state = idle;
}

int
start_writing( int mode)
{
  TRACE_FUN( 5, "start_writing");
  int result = 0;
  buffer_struct* buff = &buffer[ head];
  int segment_id = buff->segment_id;

  if (ftape_state == writing && buff->status == waiting) {
    setup_new_segment( buff, segment_id, 1);
    if (mode == WRITE_SINGLE) {
      buffer[ head].next_segment = 0; /* stop tape instead of pause */
    }
    calc_next_cluster( buff);     /* prepare */
    buff->status = writing;
    if (runner_status == idle) {
      TRACEi( 5, "starting runner for segment", segment_id);
      result = ftape_start_tape( segment_id, buff->sector_offset);
      if (result >= 0) {
        runner_status = running;
      }
    }
    if (result >= 0) {
      result = setup_fdc_and_dma( buff, FDC_WRITE); /* go */
    }
    ftape_state = writing;
  }
  TRACE_EXIT;
  return result;
}

int
loop_until_writes_done( void)
{
  TRACE_FUN( 5, "loop_until_writes_done");
  int i;
  int result = 0;

  /*
   *  Wait until all data is actually written to tape.
   */
  while (ftape_state == writing && buffer[ head].status != done) {
    TRACEx2( 7, "tail: %d, head: %d", tail, head);
    for (i = 0; i < ftape_num_buffers; ++i) {
      TRACEx3( 8, "buffer[ %d] segment_id: %d, status: %d",
              i, buffer[ i].segment_id, buffer[ i].status);
    }
    result = fdc_interrupt_wait( 5 * SECOND);
    if (result < 0) {
      TRACE( 1, "fdc_interrupt_wait failed");
      last_write_failed = 1;
      break;
    }
    if (buffer[ head].status == error) {
      /* Allow escape from loop when signaled !
       */
      if (current->signal & _DONT_BLOCK) {
        TRACE( 2, "interrupted by signal");
        TRACE_EXIT;
        result = -EINTR;          /* is this the right return value ? */
        break;
      }
      if (buffer[ head].hard_error_map != 0) {
        /*  Implement hard write error recovery here
         */
      }
      buffer[ head].status = waiting; /* retry this one */
      if (runner_status == aborting) {
        ftape_dumb_stop();
        runner_status = idle;
      }
      if (runner_status != idle) {
        TRACE( 1, "unexpected state: runner_status != idle");
        result = -EIO;
        break;
      }
      start_writing( WRITE_MULTI);
    }
    TRACE( 5, "looping until writes done");
    result = 0;                 /* normal exit status */
  }
  TRACE_EXIT;
  return result;
}

/*      Write given segment from buffer at address onto tape.
 */
int
write_segment( unsigned segment_id, byte* address, int flushing)
{
  TRACE_FUN( 5, "write_segment");
  int result = 0;
  int bytes_written = 0;
  
  TRACEi( 5, "segment_id =", segment_id);
  if (ftape_state != writing) {
    if (ftape_state == reading) {
      TRACE( 5, "calling ftape_abort_operation");
      result = ftape_abort_operation();
      if (result < 0) {
        TRACE( 1, "ftape_abort_operation failed");
      }
    }
    ftape_zap_read_buffers();
    ftape_zap_write_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 = waiting;
    bytes_written = -EAGAIN;    /* force retry */
    if (buffer[ tail].hard_error_map != 0) {
      TRACEx1( 1, "warning: %d hard error(s) in written segment",
              count_ones( buffer[ tail].hard_error_map));
      TRACEx1( 4, "hard_error_map = 0x%08lx", buffer[ tail].hard_error_map);
      /*  Implement hard write error recovery here
       */
    }
  } else if (buffer[ tail].status == done) {
    history.defects += count_ones( buffer[ tail].hard_error_map);
  } else {
    TRACE( 1, "wait for empty segment failed");
    result = -EIO;
  }
  /*    If just passed last segment on tape: wait for BOT or EOT mark.
   */
  if (result >= 0 && runner_status == logical_eot) {
    int status;

    result = ftape_ready_wait( timeout.seek, &status);
    if (result < 0 || status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT) == 0) {
      TRACE( 1, "eot/bot not reached");
    } else {
      runner_status = end_of_tape;
    }
  }
  /*    should runner stop ?
   */
  if (result >= 0 &&
      (runner_status == aborting || runner_status == buffer_underrun ||
       runner_status == end_of_tape)) {
    if (runner_status != end_of_tape) {
      result = ftape_dumb_stop();
    }
    if (result >= 0) {
      if (runner_status == aborting) {
        if (buffer[ head].status == writing) {
          buffer[ head].status = done; /* ????? */
        }
      }
      runner_status = idle;       /* aborted ? */
    }
  }
  /*  Don't start tape if runner idle and segment empty.
   */
  if (result >= 0 && !(runner_status == idle &&
                       get_bad_sector_entry( segment_id) == EMPTY_SEGMENT)) {
    if (buffer[ tail].status == done) {
      /*    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,
                                get_bad_sector_entry( segment_id));
      if (result >= 0) {
        bytes_written = result;
        buffer[ tail].segment_id = segment_id;
        buffer[ tail].status = waiting;
        next_buffer( &tail);
      }
    }
    /*    Start tape only if all buffers full or flush mode.
     *    This will give higher probability of streaming.
     */
    if (result >= 0 && runner_status != running &&
        ((head == tail && buffer[ tail].status == waiting) || flushing)) {
      result = start_writing( WRITE_MULTI);
    }
  }
  TRACE_EXIT;
  return (result < 0) ? result : bytes_written;
}

/*  Write as much as fits from buffer to the given segment on tape
 *  and handle retries.
 *  Return the number of bytes written (>= 0), or:
 *      -EIO          write failed
 *      -EINTR        interrupted by signal
 *      -ENOSPC       device full
 */
int
_write_segment( unsigned int segment_id, byte* buffer, int flush)
{
  TRACE_FUN( 5, "_write_segment");
  int retry = 0;
  int result;

  history.used |= 2;
  for (;;) {
    if (segment_id > ftape_last_segment.id && !flush) {
      result = -ENOSPC;         /* tape full */
      break;
    }
    result = write_segment( segment_id, buffer, flush);
    if (result < 0) {
      if (result == -EAGAIN) {
        if (++retry > 100) {
          TRACE( 1, "write failed, >100 retries in segment");
          result = -EIO;        /* give up */
          break;
        } else {
          TRACEx1( 2, "write error, retry %d", retry);
        }
      } else {
        TRACEi( 1, "write_segment failed, error:", -result);
        break;
      }
    } else {                    /* success */
      if (result == 0) {        /* empty segment */
        TRACE( 4, "empty segment, nothing written");
      }
      break;
    }
    /* Allow escape from loop when signaled !
     */
    if (current->signal & _DONT_BLOCK) {
      TRACE( 2, "interrupted by signal");
      TRACE_EXIT;
      result = -EINTR;          /* is this the right return value ? */
      break;
    }
  }
  TRACE_EXIT;
  return result;
}

int
update_header_segment( unsigned segment, byte* buffer)
{
  TRACE_FUN( 5, "update_header_segment");
  int result = 0;
  int status;

  if (buffer == NULL) {
    TRACE( 5, "no input buffer specified");
    buffer = deblock_buffer;
    result = read_segment( used_header_segment, buffer, &status, 0);
    if (bad_sector_map_changed) {
      store_bad_sector_map( buffer);
    }
    if (failed_sector_log_changed) {
      update_failed_sector_log( buffer);
    }
  }
  if (result >= 0 && GET4( buffer, 0) != 0xaa55aa55) {
    TRACE( 1, "wrong header signature found, aborting");
    result = -EIO;
  }
  if (result >= 0) {
    result = _write_segment( segment, buffer, 0);
    if (result >= 0 && runner_status == idle) {
      /*  Force flush for single segment instead of relying on
       *  flush in read_segment for multiple segments.
       */
      result = start_writing( WRITE_SINGLE);
      if (result >= 0 && ftape_state == writing) {
        result = loop_until_writes_done();
        prevent_flush();
      }
    }
#ifdef VERIFY_HEADERS
    if (result >= 0) {          /* read back and verify */
      result = read_segment( segment, scratch_buffer, &status, 0);
      /*  Should retry if soft error during read !
       *  TO BE IMPLEMENTED
       */
      if (result >= 0) {
        if (memcmp( buffer, scratch_buffer, sizeof( buffer)) == 0) {
          result = 0;           /* verified */
          TRACE( 5, "verified");
        } else {
          result = -EIO;        /* verify failed */
          TRACE( 5, "verify failed");
        }
      }
    }
#endif
  }
  TRACE_EXIT;
  return result;
}

int
ftape_write_header_segments( byte* buffer)
{
  TRACE_FUN( 5, "ftape_write_header_segments");
  int result = 0;
  int retry = 0;
  int header_1_ok = 0;
  int header_2_ok = 0;

  do {
    if (!header_1_ok) {
      result = update_header_segment( header_segment_1, buffer);
      if (result < 0) {
        continue;
      }
      header_1_ok = 1;
    }
    if (!header_2_ok) {
      result = update_header_segment( header_segment_2, buffer);
      if (result < 0) {
        continue;
      }
      header_2_ok = 1;
    }
  } while (result < 0 && retry++ < 3);
  if (result < 0) {
    if (!header_1_ok) {
      TRACE( 1, "update of first header segment failed");
    }
    if (!header_2_ok) {
      TRACE( 1, "update of second header segment failed");
    }
    result = -EIO;
  }
  TRACE_EXIT;
  return result;
}

int
ftape_update_header_segments( byte* buffer, int update)
{
  TRACE_FUN( 5, "ftape_update_header_segments");
  int result = 0;
  int dummy;
  int header_changed = 1;

  if (ftape_state == writing) {
    result = loop_until_writes_done();
  }
  if (read_only) {
    result = 0;                 /* exit and fake success */
    TRACE( 4, "Tape set read-only: no update");
  } else if (result >= 0) {
    result = ftape_abort_operation();
    if (result >= 0) {
      if (buffer == NULL) {
        if (bad_sector_map_changed || failed_sector_log_changed) {
          ftape_seek_to_bot();  /* prevents extra rewind */
          buffer = deblock_buffer;
          result = read_segment( used_header_segment, buffer, &dummy, 0);
          if (result < 0) {
            TRACE_EXIT;
            return result;
          }
        }
        header_changed = 0;
      }
      if (update) {
        if (bad_sector_map_changed) {
          store_bad_sector_map( buffer);
          header_changed = 1;
        }
        if (failed_sector_log_changed) {
          update_failed_sector_log( buffer);
          header_changed = 1;
        }
      }
      if (header_changed) {
        ftape_seek_to_bot();    /* prevents extra rewind */
        result = ftape_write_header_segments( buffer);
      }
    }
  }
  TRACE_EXIT;
  return result;
}

int
ftape_flush_buffers( void)
{
  TRACE_FUN( 5, "ftape_flush_buffers");
  int result;
  int pad_count;
  int data_remaining;
  static int active = 0;

  if (active) {
    TRACE( 5, "nested call, abort");
    TRACE_EXIT;
    return 0;
  }
  active = 1;
  TRACEi( 5, "entered, ftape_state =", ftape_state);
  if (ftape_state != writing && !need_flush) {
    active = 0;
    TRACE( 5, "no need for flush");
    TRACE_EXIT;
    return 0;
  }
  data_remaining = buf_pos_wr;
  buf_pos_wr = 0;               /* prevent further writes if this fails */
  TRACE( 5, "flushing write buffers");
  if (last_write_failed) {
    ftape_zap_write_buffers();
    active = 0;
    TRACE_EXIT;
    return write_protected ? -EROFS : -EIO;
  }
  /*
   *    If there is any data not written to tape yet, append zero's
   *    up to the end of the sector. Then write the segment(s) to tape.
   */
  if (data_remaining > 0) {
    int written;

    do {
      TRACEi( 4, "remaining in buffer:", data_remaining);
      pad_count = sizeof( deblock_buffer) - data_remaining;
      TRACEi( 7, "flush, padding count:", pad_count);
      memset( deblock_buffer + data_remaining, 0, pad_count); /* pad buffer */
      result = _write_segment( ftape_seg_pos, deblock_buffer, 1);
      if (result < 0) {
        if (result != -ENOSPC) {
          last_write_failed = 1;
        }
        active = 0;
        TRACE_EXIT;
        return result;
      }
      written = result;
      clear_eof_mark_if_set( ftape_seg_pos, written);
      TRACEi( 7, "flush, moved out buffer:", written);
      if (written > 0) {
        data_remaining -= written;
        if (data_remaining > 0) {
          /*  Need another segment for remaining data, move the remainder
           *  to the beginning of the buffer
           */
          memmove( deblock_buffer, deblock_buffer + written, data_remaining);
        }
      }
      ++ftape_seg_pos;
    } while (data_remaining > 0);
    /*  Data written to last segment == data_remaining + written
     *  value is in range [1..29K].
     */
    TRACEx2( 4, "last write: %d, netto pad-count: %d",
            data_remaining + written, -data_remaining);
    if (-1024 < data_remaining && data_remaining <= 0) {
      /*  Last sector of segment was used for data, so put eof mark
       *  in next segment and position at second file mark.
       */
      if (ftape_weof( 2, ftape_seg_pos, 1) >= 0) {
        ++ftape_seg_pos;        /* position between file marks */
      }
    } else {
      /*  Put eof mark in previous segment after data and position
       *  at second file mark.
       */
      ftape_weof( 2, ftape_seg_pos - 1, 1 +
                 ((SECTOR_SIZE - 1 + result + data_remaining) / SECTOR_SIZE));
    }
  } else {
    TRACE( 7, "deblock_buffer empty");
    if (ftape_weof( 2, ftape_seg_pos, 1) >= 0) {
      ++ftape_seg_pos;          /* position between file marks */
    }
    start_writing( WRITE_MULTI);
  }
  TRACE( 7, "waiting");
  result = loop_until_writes_done();
  if (result < 0) {
    TRACE( 1, "flush buffers failed");
  }
  ftape_state = idle;
  last_write_failed = 0;
  need_flush = 0;
  active = 0;
  TRACE_EXIT;
  return result;
}

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

  if (write_protected) {
    TRACE( 1, "error: cartridge write protected");
    last_write_failed = 1;
    result = -EROFS;
  } else if (ftape_offline || !formatted || no_tape) {
    result = -EIO;
  } else if (first_data_segment == -1) {
  /*
   *    If we haven't read the header segment yet, do it now.
   *    This will verify the configuration, get the eof markers
   *    and the bad sector table.
   *    We'll use the deblock buffer for scratch.
   */
    result = read_header_segment( deblock_buffer);
    if (result >= 0 && ftape_seg_pos > ftape_last_segment.id) {
      result = -ENOSPC;         /* full is full */
    }
  }
  if (result < 0) {
    TRACE_EXIT;
    return result;
  }
  /*
   *    This part writes data blocks to tape until the
   *    requested amount is written.
   *    The data will go in a buffer until it's enough
   *    for a segment without bad sectors. Then we'll write
   *    that segment to tape.
   *    The bytes written will be removed from the buffer
   *    and the process is repeated until there is less
   *    than one segment to write left in the buffer.
   */
  while (req_len > 0) {
    int space_left = sizeof( deblock_buffer) - buf_pos_wr;

    TRACEi( 7, "remaining req_len:", req_len);
    TRACEi( 7, "          buf_pos:", buf_pos_wr);
    cnt = (req_len < space_left) ? req_len : space_left;
    if (cnt > 0) {
      result = verify_area( VERIFY_READ, buff, cnt);
      if (result) {
        TRACE( 1, "verify_area failed");
        last_write_failed = 1;
        TRACE_EXIT;
        return result;
      }
      memcpy_fromfs( deblock_buffer + buf_pos_wr, buff, cnt);
      buff += cnt;
      req_len -= cnt;
      buf_pos_wr += cnt;
    }
    TRACEi( 7, "moved into blocking buffer:", cnt);
    while (buf_pos_wr >= sizeof( deblock_buffer)) {
      /*  If this is the last buffer to be written, let flush handle it.
       */
      if (ftape_seg_pos >= ftape_last_segment.id) {
        TRACEi( 7, "remaining in blocking buffer:", buf_pos_wr);
        TRACEi( 7, "just written bytes:", written + cnt);
        TRACE_EXIT;
        return written + cnt;
      }
      /* Got one full buffer, write it to disk
       */
      result = _write_segment( ftape_seg_pos, deblock_buffer, 0);
      TRACEi( 5, "_write_segment result =", result);
      if (result < 0) {
        if (result == -EAGAIN) {
          TRACE( 5, "retry...");
          continue;             /* failed, retry same segment */
        }
        last_write_failed = 1;
        TRACE_EXIT;
        return result;
      } else {
        clear_eof_mark_if_set( ftape_seg_pos, 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( 7, "moved out of blocking buffer:", result);
      buf_pos_wr -= result;     /* remainder */
      ++ftape_seg_pos;
      /* Allow us to escape from this loop with a signal !
       */
      if (current->signal & _DONT_BLOCK) {
        TRACE( 2, "interrupted by signal");
        last_write_failed = 1;
        TRACE_EXIT;
        return -EINTR;          /* is this the right return value ? */
      }
    }
    written += cnt;
  }
  TRACEi( 7, "remaining in blocking buffer:", buf_pos_wr);
  TRACEi( 7, "just written bytes:", written);
  last_write_failed = 0;
  if (!need_flush && written > 0) {
    need_flush = 1;
  }
  TRACE_EXIT;
  return written;               /* bytes written */
}

int
ftape_fix( void)
{
  TRACE_FUN( 5, "ftape_fix");
  int result = 0;
  int dummy;
  int status;

  if (write_protected) {
    result = -EROFS;
  } else {
    /*  This will copy header segment 2 to header segment 1
     *  Spares us a tape format operation if header 2 is still good.
     */
    header_segment_1 = 0;
    header_segment_2 = 1;
    first_data_segment = 2;
    result = read_segment( header_segment_2, scratch_buffer, &dummy, 0);
    result = ftape_ready_wait( timeout.pause, &status);
    result = ftape_write_header_segments( scratch_buffer);
  }
  TRACE_EXIT;
  return result;
}
