/*
 *      Copyright (C) 1994-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-bsm.c,v $
 $Author: bas $
 *
 $Revision: 1.7 $
 $Date: 1995/04/30 13:15:14 $
 $State: Beta $
 *
 *      This file contains the bad-sector map handling code for
 *      the QIC-117 floppy tape driver for Linux.
 *      QIC-40, QIC-80, QIC-3010 and QIC-3020 maps are implemented.
 */

static char RCSid[] =
"$Id: ftape-bsm.c,v 1.7 1995/04/30 13:15:14 bas Beta $";


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

#include "ftape.h"
#include "ftape-bsm.h"
#include "ftape-ctl.h"
#include "ftape-rw.h"


/*      Global vars.
 */
int bad_sector_map_changed = 0;

/*      Local vars.
 */
static byte bad_sector_map[ BAD_SECTOR_MAP_SIZE];
typedef enum { forward, backward } mode_type;

#if 0
/*  fix_tape converts a normal QIC-80 tape into a 'wide' tape.
 *  For testing purposes only !
 */
void
fix_tape( byte* buffer)
{
  static byte list[ BAD_SECTOR_MAP_SIZE];
  unsigned long* src_ptr = (unsigned long*) list;
  byte* dst_ptr = bad_sector_map;
  unsigned long map;
  unsigned sector = 1;
  int i;

  memcpy( list, bad_sector_map, sizeof( list));
  memset( bad_sector_map, 0, sizeof( bad_sector_map));
  while ((byte*)src_ptr - list < sizeof( list)) {
    map = *src_ptr++;
    if (map == EMPTY_SEGMENT) {
      *(unsigned long*) dst_ptr = 0x800000 + sector;
      dst_ptr += 3;
      sector += SECTORS_PER_SEGMENT;
    } else {
      for (i = 0 ; i < SECTORS_PER_SEGMENT ; ++i) {
        if (map & 1) {
          *(unsigned long*) dst_ptr = sector;
          dst_ptr += 3;
        }
        map >>= 1;
        ++sector;
      }
    }
  }
  bad_sector_map_changed = 1;
  *(buffer + 4) = 4;            /* put new format code */
  format_code = 4;
}
#endif

byte*
find_end_of_bsm_list( byte* ptr, byte* limit)
{
  while (ptr + 2 < limit) {
    if (ptr[ 0] || ptr[ 1] || ptr[ 2]) {
      ptr += 3;
    } else {
      return ptr;
    }
  }
  return NULL;
}

void
store_bad_sector_map( byte* buffer)
{
  TRACE_FUN( 8, "store_bad_sector_map");
  size_t count;
  size_t offset;

  /*  Store the bad sector map in buffer.
   */
  if (format_code == 4) {
    offset = 256;
    count = sizeof( bad_sector_map);
  } else {
    offset = 2 * SECTOR_SIZE;   /* skip failed sector log */
    count = sizeof( bad_sector_map) - (offset - 256);
  }
  memcpy( buffer + offset, bad_sector_map, count);
  TRACE_EXIT;
}

void
put_sector( byte** ptr, unsigned long sector)
{
  *(*ptr)++ = sector & 0xff;
  sector >>= 8;
  *(*ptr)++ = sector & 0xff;
  sector >>= 8;
  *(*ptr)++ = sector & 0xff;
}

unsigned long
get_sector( byte** ptr, mode_type mode)
{
  unsigned long sector;

  if (mode == forward) {
    sector = *(*ptr)++;
    sector += *(*ptr)++ << 8;
    sector += *(*ptr)++ << 16;
  } else {
    sector = *--(*ptr) << 16;
    sector += *--(*ptr) << 8;
    sector += *--(*ptr);
  }
  return sector;
}

void
extract_bad_sector_map( byte* buffer)
{
  TRACE_FUN( 8, "extract_bad_sector_map");

  /*  Fill the bad sector map with the contents of buffer.
   */
  if (format_code == 4) {
    /* QIC-3010/3020 and wide QIC-80 tapes no longer have a failed
     * sector log but use this area to extend the bad sector map.
     */
    memcpy( bad_sector_map, buffer + 256, sizeof( bad_sector_map));
  } else {
    /* non-wide QIC-80 tapes have a failed sector log area that
     * mustn't be included in the bad sector map.
     */
    memcpy( bad_sector_map, buffer + 256 + FAILED_SECTOR_LOG_SIZE,
           sizeof( bad_sector_map) - FAILED_SECTOR_LOG_SIZE);
  }
#if 0
  /* for testing of bad sector handling at end of tape
   */
  ((unsigned long*)bad_sector_map)[ segments_per_track * tracks_per_tape - 3] = 0x000003e0;
  ((unsigned long*)bad_sector_map)[ segments_per_track * tracks_per_tape - 2] = 0xff3fffff;
  ((unsigned long*)bad_sector_map)[ segments_per_track * tracks_per_tape - 1] = 0xffffe000;
#endif
#if 0
  /*  Enable to test bad sector handling
   */
  ((unsigned long*)bad_sector_map)[ 30] = 0xfffffffe;
  ((unsigned long*)bad_sector_map)[ 32] = 0x7fffffff;
  ((unsigned long*)bad_sector_map)[ 34] = 0xfffeffff;
  ((unsigned long*)bad_sector_map)[ 36] = 0x55555555;
  ((unsigned long*)bad_sector_map)[ 38] = 0xffffffff;
  ((unsigned long*)bad_sector_map)[ 50] = 0xffff0000;
  ((unsigned long*)bad_sector_map)[ 51] = 0xffffffff;
  ((unsigned long*)bad_sector_map)[ 52] = 0xffffffff;
  ((unsigned long*)bad_sector_map)[ 53] = 0x0000ffff;
#endif
#if 0
  /*  Enable when testing multiple volume tar dumps.
   */
  for (i = first_data_segment; i <= ftape_last_segment.id - 7; ++i) {
    ((unsigned long*)bad_sector_map)[ i] = EMPTY_SEGMENT;
  }
#endif
#if 0
  /*  Enable when testing bit positions in *_error_map
   */
  for (i = first_data_segment; i <= ftape_last_segment.id; ++i) {
    ((unsigned long*)bad_sector_map)[ i] |= 0x00ff00ff;
  }
#endif
  if (tracing > 2) {    
    unsigned int map;
    int good_sectors = 0;
    int bad_sectors;
    unsigned int total_bad = 0;
    int i;

    if (format_code == 4 || format_code == 3) {
      byte* ptr = bad_sector_map;
      unsigned sector;

      do {
        sector = get_sector( &ptr, forward);
        if (sector != 0) {
          if (format_code == 4 && sector & 0x800000) {
            total_bad += SECTORS_PER_SEGMENT - 3;
            TRACEx1( 6, "bad segment at sector: %6d", sector & 0x7fffff);
          } else {
            ++total_bad;
            TRACEx1( 6, "bad sector: %6d", sector);
          }
        }
      } while (sector != 0);
      /*  Display end-of-file marks
       */
      do {
        sector = *((unsigned short*)ptr)++;
        if (sector) {
          TRACEx2( 4, "eof mark: %4d/%2d", sector,
                  *((unsigned short*)ptr)++);
        }
      } while (sector);
    } else {
      for (i = first_data_segment;
           i < segments_per_track * tracks_per_tape; ++i) {
        map = ((unsigned long*)bad_sector_map)[ i];
        bad_sectors = count_ones( map);
        if (bad_sectors > 0) {
          TRACEx2( 6, "bsm for segment %4d: 0x%08x", i, map);
          if (bad_sectors > SECTORS_PER_SEGMENT - 3) {
            bad_sectors = SECTORS_PER_SEGMENT - 3;
          }
          total_bad += bad_sectors;
        }
      }
    }
    good_sectors = ((segments_per_track * tracks_per_tape - first_data_segment)
                    * (SECTORS_PER_SEGMENT - 3)) - total_bad;
    TRACEx1( 3, "%d Kb usable on this tape",
            good_sectors - ftape_last_segment.free);
    if (total_bad == 0) {
      TRACE( 1, "WARNING: this tape has no bad blocks registered !");
    } else {
      TRACEx1( 2, "%d bad sectors", total_bad);
    }
  }
  TRACE_EXIT;
}

unsigned long
cvt2map( int sector)
{
  return 1 << (((sector & 0x7fffff) - 1) % SECTORS_PER_SEGMENT);
}

int
cvt2segment( int sector)
{
  return ((sector & 0x7fffff) - 1) / SECTORS_PER_SEGMENT;
}

int
forward_seek_entry( int segment_id, byte** ptr, unsigned long* map)
{
  byte* tmp_ptr;
  unsigned long sector;
  int segment;
  int count;

  do {
    sector = get_sector( ptr, forward);
    segment = cvt2segment( sector);
  } while (sector != 0 && segment < segment_id);
  tmp_ptr = *ptr - 3;        /* point to first sector >= segment_id */
  /*  Get all sectors in segment_id
   */
  if (format_code == 4 && (sector & 0x800000) && segment == segment_id) {
    *map = EMPTY_SEGMENT;
    count = 32;
  } else {
    *map = 0;
    count = 0;
    while (sector != 0 && segment == segment_id) {
      *map |= cvt2map( sector);
      sector = get_sector( ptr, forward);
      segment = cvt2segment( sector);
      ++count;
    }
  }
  *ptr = tmp_ptr;
  return count;
}

int
backwards_seek_entry( int segment_id, byte** ptr, unsigned long* map)
{
  unsigned long sector;
  int segment;
  int count;

  *map = 0;
  if (*ptr > bad_sector_map) {
    do {
      sector = get_sector( ptr, backward);
      segment = cvt2segment( sector);
    } while (*ptr > bad_sector_map && segment > segment_id);
    count = 0;
    if (segment > segment_id) {
      /*  at start of list, no entry found */
    } else if (segment < segment_id) {
      /*  before smaller entry, adjust for overshoot */
      *ptr += 3;
    } else {
      /*  get all sectors in segment_id */
      if (format_code == 4 && (sector & 0x800000)) {
        *map = EMPTY_SEGMENT;
        count = 32;
      } else {
        do {
          *map |= cvt2map( sector);
          ++count;
          if (*ptr <= bad_sector_map) {
            break;
          }
          sector = get_sector( ptr, backward);
          segment = cvt2segment( sector);
        } while (segment == segment_id);
        if (segment < segment_id) {
          *ptr += 3;
        }
      }
    }
  } else {
    count = 0;
  }
  return count;
}
  
void
put_bad_sector_entry( int segment_id, unsigned long new_map)
{
  byte* ptr = bad_sector_map;
  int count;
  int new_count;
  unsigned long map;

  if (format_code == 3 || format_code == 4) {
    count = forward_seek_entry( segment_id, &ptr, &map);
    new_count = count_ones( new_map);
    /*  If format code == 4 put empty segment instead of 32 bad sectors.
     */
    if (new_count == 32 && format_code == 4) {
      new_count = 1;
    }
    if (count != new_count) {
      /*  insert (or delete if < 0) new_count - count entries.
       *  Move trailing part of list including terminating 0.
       */
      byte* hi_ptr = ptr;

      do {
      } while( get_sector( &hi_ptr, forward) != 0);
      memmove( ptr + new_count, ptr + count, hi_ptr - (ptr + count));
    }
    if (new_count == 1 && new_map == EMPTY_SEGMENT) {
      put_sector( &ptr, 0x800001 + segment_id * SECTORS_PER_SEGMENT);
    } else {
      int i = 0;

      while (new_map) {
        if (new_map & 1) {
          put_sector( &ptr, 1 + segment_id * SECTORS_PER_SEGMENT + i);
        }
        ++i;
        new_map >>= 1;
      }
    }
  } else {
    ((unsigned long*) bad_sector_map)[ segment_id] = new_map;
  }
  bad_sector_map_changed = 1;
}

unsigned long
get_bad_sector_entry( int segment_id)
{
  TRACE_FUN( 8, "get_bad_sector_entry");
  static unsigned long map = 0;

  if (used_header_segment == -1) {
    /*  When reading header segment we'll need a blank map.
     */
    map = 0;
  } else if (format_code == 3 || format_code == 4) {
    /*  Invariants:
     *    map - mask value returned on last call.
     *    ptr - points to first sector greater or equal to
     *          first sector in last_referenced segment.
     *    last_referenced - segment id used in the last call,
     *                      sector and map belong to this id.
     *  This code is designed for sequential access and retries.
     *  For true random access it may have to be redesigned.
     */
    static int last_reference = -1;
    static byte* ptr = bad_sector_map;

    if (segment_id > last_reference) {
      /*  Skip all sectors before segment_id
       */
      forward_seek_entry( segment_id, &ptr, &map);
    } else if (segment_id < last_reference) {
      /*  Skip backwards until begin of buffer or first sector in segment_id
       */
      backwards_seek_entry( segment_id, &ptr, &map);
    } /* segment_id == last_reference : keep map */
    last_reference = segment_id;
  } else {
    map = ((unsigned long*)bad_sector_map)[ segment_id];
  }
  TRACE_EXIT;
  return map;
}

void
ftape_init_bsm( void)
{
  memset( bad_sector_map, 0, sizeof( bad_sector_map));
}
