/*
 *      Copyright (C) 1993 Bas Laarhoven.
 *      Original code:
 *      Copyright (C) 1992 David L. Brown, Jr.

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/driver/RCS/ecc.c,v $
 $Author: bas $
 *
 $Revision: 1.6 $
 $Date: 1993/08/29 15:24:41 $
 $State: Alpha $
 *
 *      This file contains the Reed-Solomon error correction code 
 *      for the QIC-40/80 floppy-tape driver for Linux.
 */

static char RCSid[] = "$Id: ecc.c,v 1.6 1993/08/29 15:24:41 bas Alpha $";

#include <stdio.h>
#include <linux/errno.h>

#include "fdc-io.h"
#include "ecc.h"

#ifdef TEST
# undef TRACE()
# undef TRACEi()
# undef TRACElx()
# define TRACE(l,s1,s2) { if (tracing > l) fprintf( stderr, __FILE__ " (%s) - %s.\n", s1, s2); }
# define TRACEi(l,s1,s2,i) { if (tracing > l) fprintf( stderr, __FILE__ " (%s) - %s %d.\n", s1, s2, i); }
# define TRACElx(l,s1,s2,lx) { if (tracing > l) fprintf( stderr, __FILE__ " (%s) - %s 0x%08lx.\n", s1, s2, lx); }
static int tracing = 1;
#endif

/* The field used in this code is the polynomials over $Z_2$, packed
   in a byte with the coefficient of $x^7$ as the MSB and the
   coefficient of $x^0$ as the LSB.  Arithmetic is performed modulus
   $x^8 + x^7 + x^2 + x + 1$. */

/* This array is a table of powers of $x$, from 0 to 254. */

unsigned char field_powers[] = {
/* First copy for negative indices */
  0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
  0x87, 0x89, 0x95, 0xad, 0xdd, 0x3d, 0x7a, 0xf4,
  0x6f, 0xde, 0x3b, 0x76, 0xec, 0x5f, 0xbe, 0xfb,
  0x71, 0xe2, 0x43, 0x86, 0x8b, 0x91, 0xa5, 0xcd,
  0x1d, 0x3a, 0x74, 0xe8, 0x57, 0xae, 0xdb, 0x31,
  0x62, 0xc4, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, 0x67,
  0xce, 0x1b, 0x36, 0x6c, 0xd8, 0x37, 0x6e, 0xdc,
  0x3f, 0x7e, 0xfc, 0x7f, 0xfe, 0x7b, 0xf6, 0x6b,
  0xd6, 0x2b, 0x56, 0xac, 0xdf, 0x39, 0x72, 0xe4,
  0x4f, 0x9e, 0xbb, 0xf1, 0x65, 0xca, 0x13, 0x26,
  0x4c, 0x98, 0xb7, 0xe9, 0x55, 0xaa, 0xd3, 0x21,
  0x42, 0x84, 0x8f, 0x99, 0xb5, 0xed, 0x5d, 0xba,
  0xf3, 0x61, 0xc2, 0x03, 0x06, 0x0c, 0x18, 0x30,
  0x60, 0xc0, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0,
  0x47, 0x8e, 0x9b, 0xb1, 0xe5, 0x4d, 0x9a, 0xb3,
  0xe1, 0x45, 0x8a, 0x93, 0xa1, 0xc5, 0x0d, 0x1a,
  0x34, 0x68, 0xd0, 0x27, 0x4e, 0x9c, 0xbf, 0xf9,
  0x75, 0xea, 0x53, 0xa6, 0xcb, 0x11, 0x22, 0x44,
  0x88, 0x97, 0xa9, 0xd5, 0x2d, 0x5a, 0xb4, 0xef,
  0x59, 0xb2, 0xe3, 0x41, 0x82, 0x83, 0x81, 0x85,
  0x8d, 0x9d, 0xbd, 0xfd, 0x7d, 0xfa, 0x73, 0xe6,
  0x4b, 0x96, 0xab, 0xd1, 0x25, 0x4a, 0x94, 0xaf,
  0xd9, 0x35, 0x6a, 0xd4, 0x2f, 0x5e, 0xbc, 0xff,
  0x79, 0xf2, 0x63, 0xc6, 0x0b, 0x16, 0x2c, 0x58,
  0xb0, 0xe7, 0x49, 0x92, 0xa3, 0xc1, 0x05, 0x0a,
  0x14, 0x28, 0x50, 0xa0, 0xc7, 0x09, 0x12, 0x24,
  0x48, 0x90, 0xa7, 0xc9, 0x15, 0x2a, 0x54, 0xa8,
  0xd7, 0x29, 0x52, 0xa4, 0xcf, 0x19, 0x32, 0x64,
  0xc8, 0x17, 0x2e, 0x5c, 0xb8, 0xf7, 0x69, 0xd2,
  0x23, 0x46, 0x8c, 0x9f, 0xb9, 0xf5, 0x6d, 0xda,
  0x33, 0x66, 0xcc, 0x1f, 0x3e, 0x7c, 0xf8, 0x77,
  0xee, 0x5b, 0xb6, 0xeb, 0x51, 0xa2, 0xc3,
/* Second copy for indices in range [0..254] */
  0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
  0x87, 0x89, 0x95, 0xad, 0xdd, 0x3d, 0x7a, 0xf4,
  0x6f, 0xde, 0x3b, 0x76, 0xec, 0x5f, 0xbe, 0xfb,
  0x71, 0xe2, 0x43, 0x86, 0x8b, 0x91, 0xa5, 0xcd,
  0x1d, 0x3a, 0x74, 0xe8, 0x57, 0xae, 0xdb, 0x31,
  0x62, 0xc4, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, 0x67,
  0xce, 0x1b, 0x36, 0x6c, 0xd8, 0x37, 0x6e, 0xdc,
  0x3f, 0x7e, 0xfc, 0x7f, 0xfe, 0x7b, 0xf6, 0x6b,
  0xd6, 0x2b, 0x56, 0xac, 0xdf, 0x39, 0x72, 0xe4,
  0x4f, 0x9e, 0xbb, 0xf1, 0x65, 0xca, 0x13, 0x26,
  0x4c, 0x98, 0xb7, 0xe9, 0x55, 0xaa, 0xd3, 0x21,
  0x42, 0x84, 0x8f, 0x99, 0xb5, 0xed, 0x5d, 0xba,
  0xf3, 0x61, 0xc2, 0x03, 0x06, 0x0c, 0x18, 0x30,
  0x60, 0xc0, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0,
  0x47, 0x8e, 0x9b, 0xb1, 0xe5, 0x4d, 0x9a, 0xb3,
  0xe1, 0x45, 0x8a, 0x93, 0xa1, 0xc5, 0x0d, 0x1a,
  0x34, 0x68, 0xd0, 0x27, 0x4e, 0x9c, 0xbf, 0xf9,
  0x75, 0xea, 0x53, 0xa6, 0xcb, 0x11, 0x22, 0x44,
  0x88, 0x97, 0xa9, 0xd5, 0x2d, 0x5a, 0xb4, 0xef,
  0x59, 0xb2, 0xe3, 0x41, 0x82, 0x83, 0x81, 0x85,
  0x8d, 0x9d, 0xbd, 0xfd, 0x7d, 0xfa, 0x73, 0xe6,
  0x4b, 0x96, 0xab, 0xd1, 0x25, 0x4a, 0x94, 0xaf,
  0xd9, 0x35, 0x6a, 0xd4, 0x2f, 0x5e, 0xbc, 0xff,
  0x79, 0xf2, 0x63, 0xc6, 0x0b, 0x16, 0x2c, 0x58,
  0xb0, 0xe7, 0x49, 0x92, 0xa3, 0xc1, 0x05, 0x0a,
  0x14, 0x28, 0x50, 0xa0, 0xc7, 0x09, 0x12, 0x24,
  0x48, 0x90, 0xa7, 0xc9, 0x15, 0x2a, 0x54, 0xa8,
  0xd7, 0x29, 0x52, 0xa4, 0xcf, 0x19, 0x32, 0x64,
  0xc8, 0x17, 0x2e, 0x5c, 0xb8, 0xf7, 0x69, 0xd2,
  0x23, 0x46, 0x8c, 0x9f, 0xb9, 0xf5, 0x6d, 0xda,
  0x33, 0x66, 0xcc, 0x1f, 0x3e, 0x7c, 0xf8, 0x77,
  0xee, 0x5b, 0xb6, 0xeb, 0x51, 0xa2, 0xc3,
/* Third copy for indices in range [255..510] */
  0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
  0x87, 0x89, 0x95, 0xad, 0xdd, 0x3d, 0x7a, 0xf4,
  0x6f, 0xde, 0x3b, 0x76, 0xec, 0x5f, 0xbe, 0xfb,
  0x71, 0xe2, 0x43, 0x86, 0x8b, 0x91, 0xa5, 0xcd,
  0x1d, 0x3a, 0x74, 0xe8, 0x57, 0xae, 0xdb, 0x31,
  0x62, 0xc4, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, 0x67,
  0xce, 0x1b, 0x36, 0x6c, 0xd8, 0x37, 0x6e, 0xdc,
  0x3f, 0x7e, 0xfc, 0x7f, 0xfe, 0x7b, 0xf6, 0x6b,
  0xd6, 0x2b, 0x56, 0xac, 0xdf, 0x39, 0x72, 0xe4,
  0x4f, 0x9e, 0xbb, 0xf1, 0x65, 0xca, 0x13, 0x26,
  0x4c, 0x98, 0xb7, 0xe9, 0x55, 0xaa, 0xd3, 0x21,
  0x42, 0x84, 0x8f, 0x99, 0xb5, 0xed, 0x5d, 0xba,
  0xf3, 0x61, 0xc2, 0x03, 0x06, 0x0c, 0x18, 0x30,
  0x60, 0xc0, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0,
  0x47, 0x8e, 0x9b, 0xb1, 0xe5, 0x4d, 0x9a, 0xb3,
  0xe1, 0x45, 0x8a, 0x93, 0xa1, 0xc5, 0x0d, 0x1a,
  0x34, 0x68, 0xd0, 0x27, 0x4e, 0x9c, 0xbf, 0xf9,
  0x75, 0xea, 0x53, 0xa6, 0xcb, 0x11, 0x22, 0x44,
  0x88, 0x97, 0xa9, 0xd5, 0x2d, 0x5a, 0xb4, 0xef,
  0x59, 0xb2, 0xe3, 0x41, 0x82, 0x83, 0x81, 0x85,
  0x8d, 0x9d, 0xbd, 0xfd, 0x7d, 0xfa, 0x73, 0xe6,
  0x4b, 0x96, 0xab, 0xd1, 0x25, 0x4a, 0x94, 0xaf,
  0xd9, 0x35, 0x6a, 0xd4, 0x2f, 0x5e, 0xbc, 0xff,
  0x79, 0xf2, 0x63, 0xc6, 0x0b, 0x16, 0x2c, 0x58,
  0xb0, 0xe7, 0x49, 0x92, 0xa3, 0xc1, 0x05, 0x0a,
  0x14, 0x28, 0x50, 0xa0, 0xc7, 0x09, 0x12, 0x24,
  0x48, 0x90, 0xa7, 0xc9, 0x15, 0x2a, 0x54, 0xa8,
  0xd7, 0x29, 0x52, 0xa4, 0xcf, 0x19, 0x32, 0x64,
  0xc8, 0x17, 0x2e, 0x5c, 0xb8, 0xf7, 0x69, 0xd2,
  0x23, 0x46, 0x8c, 0x9f, 0xb9, 0xf5, 0x6d, 0xda,
  0x33, 0x66, 0xcc, 0x1f, 0x3e, 0x7c, 0xf8, 0x77,
  0xee, 0x5b, 0xb6, 0xeb, 0x51, 0xa2, 0xc3,
};

/* This is the reverse lookup table.  There is no log of 0, so the
   first element is not valid. */

unsigned char field_log[] = {
  0xff, 0x00, 0x01, 0x63, 0x02, 0xc6, 0x64, 0x6a,
  0x03, 0xcd, 0xc7, 0xbc, 0x65, 0x7e, 0x6b, 0x2a,
  0x04, 0x8d, 0xce, 0x4e, 0xc8, 0xd4, 0xbd, 0xe1,
  0x66, 0xdd, 0x7f, 0x31, 0x6c, 0x20, 0x2b, 0xf3,
  0x05, 0x57, 0x8e, 0xe8, 0xcf, 0xac, 0x4f, 0x83,
  0xc9, 0xd9, 0xd5, 0x41, 0xbe, 0x94, 0xe2, 0xb4,
  0x67, 0x27, 0xde, 0xf0, 0x80, 0xb1, 0x32, 0x35,
  0x6d, 0x45, 0x21, 0x12, 0x2c, 0x0d, 0xf4, 0x38,
  0x06, 0x9b, 0x58, 0x1a, 0x8f, 0x79, 0xe9, 0x70,
  0xd0, 0xc2, 0xad, 0xa8, 0x50, 0x75, 0x84, 0x48,
  0xca, 0xfc, 0xda, 0x8a, 0xd6, 0x54, 0x42, 0x24,
  0xbf, 0x98, 0x95, 0xf9, 0xe3, 0x5e, 0xb5, 0x15,
  0x68, 0x61, 0x28, 0xba, 0xdf, 0x4c, 0xf1, 0x2f,
  0x81, 0xe6, 0xb2, 0x3f, 0x33, 0xee, 0x36, 0x10,
  0x6e, 0x18, 0x46, 0xa6, 0x22, 0x88, 0x13, 0xf7,
  0x2d, 0xb8, 0x0e, 0x3d, 0xf5, 0xa4, 0x39, 0x3b,
  0x07, 0x9e, 0x9c, 0x9d, 0x59, 0x9f, 0x1b, 0x08,
  0x90, 0x09, 0x7a, 0x1c, 0xea, 0xa0, 0x71, 0x5a,
  0xd1, 0x1d, 0xc3, 0x7b, 0xae, 0x0a, 0xa9, 0x91,
  0x51, 0x5b, 0x76, 0x72, 0x85, 0xa1, 0x49, 0xeb,
  0xcb, 0x7c, 0xfd, 0xc4, 0xdb, 0x1e, 0x8b, 0xd2,
  0xd7, 0x92, 0x55, 0xaa, 0x43, 0x0b, 0x25, 0xaf,
  0xc0, 0x73, 0x99, 0x77, 0x96, 0x5c, 0xfa, 0x52,
  0xe4, 0xec, 0x5f, 0x4a, 0xb6, 0xa2, 0x16, 0x86,
  0x69, 0xc5, 0x62, 0xfe, 0x29, 0x7d, 0xbb, 0xcc,
  0xe0, 0xd3, 0x4d, 0x8c, 0xf2, 0x1f, 0x30, 0xdc,
  0x82, 0xab, 0xe7, 0x56, 0xb3, 0x93, 0x40, 0xd8,
  0x34, 0xb0, 0xef, 0x26, 0x37, 0x0c, 0x11, 0x44,
  0x6f, 0x78, 0x19, 0x9a, 0x47, 0x74, 0xa7, 0xc1,
  0x23, 0x53, 0x89, 0xfb, 0x14, 0x5d, 0xf8, 0x97,
  0x2e, 0x4b, 0xb9, 0x60, 0x0f, 0xed, 0x3e, 0xe5,
  0xf6, 0x87, 0xa5, 0x17, 0x3a, 0xa3, 0x3c, 0xb7,
};

static unsigned char ss0[ 256] = {
  0x00, 0xc3, 0x01, 0xc2, 0x02, 0xc1, 0x03, 0xc0,
  0x04, 0xc7, 0x05, 0xc6, 0x06, 0xc5, 0x07, 0xc4,
  0x08, 0xcb, 0x09, 0xca, 0x0a, 0xc9, 0x0b, 0xc8,
  0x0c, 0xcf, 0x0d, 0xce, 0x0e, 0xcd, 0x0f, 0xcc,
  0x10, 0xd3, 0x11, 0xd2, 0x12, 0xd1, 0x13, 0xd0,
  0x14, 0xd7, 0x15, 0xd6, 0x16, 0xd5, 0x17, 0xd4,
  0x18, 0xdb, 0x19, 0xda, 0x1a, 0xd9, 0x1b, 0xd8,
  0x1c, 0xdf, 0x1d, 0xde, 0x1e, 0xdd, 0x1f, 0xdc,
  0x20, 0xe3, 0x21, 0xe2, 0x22, 0xe1, 0x23, 0xe0,
  0x24, 0xe7, 0x25, 0xe6, 0x26, 0xe5, 0x27, 0xe4,
  0x28, 0xeb, 0x29, 0xea, 0x2a, 0xe9, 0x2b, 0xe8,
  0x2c, 0xef, 0x2d, 0xee, 0x2e, 0xed, 0x2f, 0xec,
  0x30, 0xf3, 0x31, 0xf2, 0x32, 0xf1, 0x33, 0xf0,
  0x34, 0xf7, 0x35, 0xf6, 0x36, 0xf5, 0x37, 0xf4,
  0x38, 0xfb, 0x39, 0xfa, 0x3a, 0xf9, 0x3b, 0xf8,
  0x3c, 0xff, 0x3d, 0xfe, 0x3e, 0xfd, 0x3f, 0xfc,
  0x40, 0x83, 0x41, 0x82, 0x42, 0x81, 0x43, 0x80,
  0x44, 0x87, 0x45, 0x86, 0x46, 0x85, 0x47, 0x84,
  0x48, 0x8b, 0x49, 0x8a, 0x4a, 0x89, 0x4b, 0x88,
  0x4c, 0x8f, 0x4d, 0x8e, 0x4e, 0x8d, 0x4f, 0x8c,
  0x50, 0x93, 0x51, 0x92, 0x52, 0x91, 0x53, 0x90,
  0x54, 0x97, 0x55, 0x96, 0x56, 0x95, 0x57, 0x94,
  0x58, 0x9b, 0x59, 0x9a, 0x5a, 0x99, 0x5b, 0x98,
  0x5c, 0x9f, 0x5d, 0x9e, 0x5e, 0x9d, 0x5f, 0x9c,
  0x60, 0xa3, 0x61, 0xa2, 0x62, 0xa1, 0x63, 0xa0,
  0x64, 0xa7, 0x65, 0xa6, 0x66, 0xa5, 0x67, 0xa4,
  0x68, 0xab, 0x69, 0xaa, 0x6a, 0xa9, 0x6b, 0xa8,
  0x6c, 0xaf, 0x6d, 0xae, 0x6e, 0xad, 0x6f, 0xac,
  0x70, 0xb3, 0x71, 0xb2, 0x72, 0xb1, 0x73, 0xb0,
  0x74, 0xb7, 0x75, 0xb6, 0x76, 0xb5, 0x77, 0xb4,
  0x78, 0xbb, 0x79, 0xba, 0x7a, 0xb9, 0x7b, 0xb8,
  0x7c, 0xbf, 0x7d, 0xbe, 0x7e, 0xbd, 0x7f, 0xbc,
  };

static unsigned char ss2[ 256] = {
  0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e,
  0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e,
  0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e,
  0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e,
  0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e,
  0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e,
  0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e,
  0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e,
  0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e,
  0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e,
  0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae,
  0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe,
  0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce,
  0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde,
  0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee,
  0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe,
  0x87, 0x85, 0x83, 0x81, 0x8f, 0x8d, 0x8b, 0x89,
  0x97, 0x95, 0x93, 0x91, 0x9f, 0x9d, 0x9b, 0x99,
  0xa7, 0xa5, 0xa3, 0xa1, 0xaf, 0xad, 0xab, 0xa9,
  0xb7, 0xb5, 0xb3, 0xb1, 0xbf, 0xbd, 0xbb, 0xb9,
  0xc7, 0xc5, 0xc3, 0xc1, 0xcf, 0xcd, 0xcb, 0xc9,
  0xd7, 0xd5, 0xd3, 0xd1, 0xdf, 0xdd, 0xdb, 0xd9,
  0xe7, 0xe5, 0xe3, 0xe1, 0xef, 0xed, 0xeb, 0xe9,
  0xf7, 0xf5, 0xf3, 0xf1, 0xff, 0xfd, 0xfb, 0xf9,
  0x07, 0x05, 0x03, 0x01, 0x0f, 0x0d, 0x0b, 0x09,
  0x17, 0x15, 0x13, 0x11, 0x1f, 0x1d, 0x1b, 0x19,
  0x27, 0x25, 0x23, 0x21, 0x2f, 0x2d, 0x2b, 0x29,
  0x37, 0x35, 0x33, 0x31, 0x3f, 0x3d, 0x3b, 0x39,
  0x47, 0x45, 0x43, 0x41, 0x4f, 0x4d, 0x4b, 0x49,
  0x57, 0x55, 0x53, 0x51, 0x5f, 0x5d, 0x5b, 0x59,
  0x67, 0x65, 0x63, 0x61, 0x6f, 0x6d, 0x6b, 0x69,
  0x77, 0x75, 0x73, 0x71, 0x7f, 0x7d, 0x7b, 0x79,
  };


/* Multiply two numbers in the field. */

inline unsigned char
multiply( unsigned char a, unsigned char b)
{
  if (a && b) {
    return field_powers[ (int) field_log[ a] + (int) field_log[ b]];
  }
  return 0;
}

inline unsigned char
divide( unsigned char a, unsigned char b)
{
  if (b == 0) {
    TRACE( -1, "divide", "Error, division by zero");
  }
  if (a == 0) {
    return 0;
  } else {
    return field_powers[ 255+ (int) field_log[ a] - (int) field_log[ b]];
  }
}

/* This is just like divide, except we have already looked up the log
   of the second number. */

inline unsigned char
divide_out( unsigned char a, unsigned char b)
{
  if (a == 0) {
    return 0;
  } else {
    return field_powers[ 255+ (int) field_log[ a] - (int) b];
  }
}

/* This returns the value $z^{a-b}$. */

inline unsigned char
z_of_ab( unsigned char a, unsigned char b)
{
  return field_powers[ 255+ (int) a - (int) b];
}

/* In order to speed up the correction and adjustment, we can compute
   a matrix of coefficients for the multiplication. */

/* Calculate the inverse matrix.  Returns 1 if the matrix is valid, or
   zero if there is no inverse.  The i's are the indices of the bytes
   to be corrected. */

int
calculate_inverse (unsigned char i0,
                   unsigned char i1,
                   unsigned char i2,
                   struct ecc_inverse_matrix *inv)
{
  unsigned char (*v)[3];
  /* First some variables to remember some of the results. */

  unsigned char z20, z10, z21, z12, z01, z02;
  z20 = z_of_ab (i2, i0);
  z10 = z_of_ab (i1, i0);
  z21 = z_of_ab (i2, i1);
  z12 = z_of_ab (i1, i2);
  z01 = z_of_ab (i0, i1);
  z02 = z_of_ab (i0, i2);
  inv->log_denom = (z20 ^ z10 ^ z21 ^ z12 ^ z01 ^ z02);
  if (inv->log_denom == 0)
    return 0;
  else
    {
      inv->log_denom = field_log[inv->log_denom];

      /* Calculate all of the coefficients on the top. */
      v = inv->zs;

      v[0][0] = field_powers[i1] ^ field_powers[i2];
      v[0][1] = z21 ^ z12;
      v[0][2] = field_powers[255-i1] ^ field_powers[255-i2];

      v[1][0] = field_powers[i0] ^ field_powers[i2];
      v[1][1] = z20 ^ z02;
      v[1][2] = field_powers[255-i0] ^ field_powers[255-i2];

      v[2][0] = field_powers[i0] ^ field_powers[i1];
      v[2][1] = z10 ^ z01;
      v[2][2] = field_powers[255-i0] ^ field_powers[255-i1];

      return 1;
    }
}

/* Determine the error values for a given inverse matrix and syndromes. */

void
determine3 (struct ecc_inverse_matrix *inv,
            unsigned char *es, unsigned char *ss)
{
  es[ 0] = divide_out( multiply( ss[ 0], inv->zs[ 0][ 0])
                      ^ multiply( ss[ 1], inv->zs[ 0][ 1])
                      ^ multiply( ss[ 2], inv->zs[ 0][ 2]),
                      inv->log_denom);
  es[ 1] = divide_out( multiply( ss[ 0], inv->zs[ 1][ 0])
                      ^ multiply( ss[ 1], inv->zs[ 1][ 1])
                      ^ multiply( ss[ 2], inv->zs[ 1][ 2]),
                      inv->log_denom);
  es[ 2] = divide_out( multiply( ss[ 0], inv->zs[ 2][ 0])
                      ^ multiply( ss[ 1], inv->zs[ 2][ 1])
                      ^ multiply( ss[ 2], inv->zs[ 2][ 2]),
                      inv->log_denom);
}


/* The Reed Solomon ECC codes are computed over the nth byte of each
   block.  There are up to 29 blocks of data, and 3 blocks of ECC.
   The blocks are storred consecutively in memory. */

/* Compute the 3 syndrome values.  The data pointer should point to
   the offset within the first block of the column to calculate.  The
   count of blocks is in blocks.  The three bytes will be placed in
   ss[0], ss[1], and ss[2]. */

void
compute_syndromes (unsigned char *data, int blocks, unsigned char *ss)
{
  int i;

  ss[0] = 0;
  ss[1] = 0;
  ss[2] = 0;
  data += SECTOR_SIZE * (blocks - 1);
  for (i = blocks - 1; i >= 0; i--) {
    ss[ 0] = ss0[ ss[ 0]] ^ *data;
    ss[ 1] = ss[ 1] ^ *data;
    ss[ 2] = ss2[ ss[ 2]] ^ *data;
    data -= SECTOR_SIZE;
  }
}

/* Compute the parity for an entire segment of data. */

int
set_parity (unsigned char *data, int blocks)
{
  int i;
  struct ecc_inverse_matrix inv;
  unsigned char ss[3], es[3];

  if (calculate_inverse (blocks - 3, blocks - 2, blocks - 1, &inv) == 0) {
    TRACE( -1, "set_parity", "no inverse matrix: no parity generated");
    return -EIO;
  }
  for (i = 0; i < SECTOR_SIZE; i++) {
    compute_syndromes( data, blocks - 3, ss);
    determine3( &inv, es, ss);
    data+= (blocks - 3) * SECTOR_SIZE;
    *data = es[ 0];
    data+= SECTOR_SIZE;
    *data = es[ 1];
    data+= SECTOR_SIZE;
    *data = es[ 2];
    data-= (blocks - 1) * SECTOR_SIZE - 1;
  }
  return 0;
}

int
ecc_set_segment_parity( struct memory_segment *data)
{
  int result;

  result = set_parity ((unsigned char *) data->data, data->blocks);
  if (result < 0) {
    TRACE( -1, "ecc_set_segment_parity", "set_parity failed");
    return result;
  }
#define TEST
#ifdef TEST
  data->read_bad = 0;
  if (ecc_correct_data (data) != ECC_CORRECT) {
    TRACE( -1, "ecc_set_segment_parity", "Internal ecc inconsistency");
    return -EIO;
  }
#endif
#undef TEST
  return 0;
}

/* Verify data, and correct if possible.  Returns one of ECC_CORRECT,
   ECC_CORRECTED, and ECC_FAILED. */

int
ecc_correct_data (struct memory_segment *data)
{
  int i, j;
  unsigned char *data_bytes;
  int corrected = ECC_CORRECT;
  struct ecc_inverse_matrix inv;
  unsigned char ss[3], es[3];
  int indices[3] = { 0, 0, 0};
  int error_count;
  unsigned char i1, i2;
  int errors_corrected = 0;

  /* Determine what errors exist. */

  error_count = 0;
  for (i = 0; i < data->blocks; i++) {
    if (BAD_CHECK (data->read_bad, i)) {
      if (error_count >= 3) {
        TRACE( -1, "ecc_correct_data", "unrecoverable ecc error");
        return ECC_FAILED;
      } else {
        indices[ error_count++] = i;
      }
    }
  }
  /* Fill in rest of errors with bogus values so that the matrix will
     have an inverse. */

#if 0
  /* This code can't deal with a crc error in sector 0.  If this is
     the case, pretend we don't know where the error is.  If there are
     more than one error, we're screwed. */

  if (error_count == 1 && indices[ 0] == 0)
    error_count = 0;
#endif

#if 1
  /* Next part of code makes sure each index has an unique, non-zero value.
   * This is needed because the calculate_inverse has a problem with
   * an index of zero. THIS SHOULD BE FIXED SOMETIME !
   * THIS DEGRADES THE ECC CAPABILITIES !!!
   */
  i = 0;
  for (j = error_count; j < 3; j++) {
    while (BAD_CHECK (data->read_bad, i)) {
      i++;
    }
    if (i >= data->blocks) { 
      /* This shouldn't happen!  The segment shouldn't have less than 4 blocks.
       * If there were more than three errors then the above code should catch it.
       */
      TRACE( -1, "ecc_correct_data", "BUG: segment too small");
      return ECC_FAILED;
    }
    indices[j] = 100 + i;
    i++;
  }
#endif

  if (calculate_inverse (indices[0], indices[1], indices[2], &inv) == 0) {
    /* The above code should assure that the indices are all unique.
     * If this is the case, then the matrix should always have an inverse.
     */
    TRACE( -1, "ecc_correct_data", "unrecoverable ecc error");
    return ECC_FAILED;
  }

  data_bytes = (unsigned char *) data->data;
  for (i = 0; i < SECTOR_SIZE; i++) {
    compute_syndromes (data_bytes, data->blocks, ss);

    /* If the syndromes aren't zero then there is an error. */

    if (ss[ 0] != 0 || ss[ 1] != 0 || ss[ 2] != 0) {
      if (error_count > 0) {
        determine3( &inv, es, ss);
        for (j = 0; j < 3; j++) {
          /* Correct the byte if necessary. */
#if 0
          if (j < error_count) {
#else
          if (j < error_count && indices[ j] < 100) {
#endif
            data_bytes[ SECTOR_SIZE * indices[ j]] ^= es[ j];
            if (corrected != ECC_FAILED) {
              ++errors_corrected;
              corrected = ECC_CORRECTED;
            } else if (es[j] != 0) {
              /* Inconsistency detected in ECC data. */
              TRACE( -1, "ecc_correct_data", "ecc inconsistency");
              corrected = ECC_FAILED;
            }
          }
        }
      } else {
        if (ss[0] == 0 || ss[1] == 0 || ss[2] == 0) {
          TRACE( 2, "ecc_correct_data", "zero syndrome");
          corrected = ECC_FAILED;
        } else {
          i1 = field_log[divide(ss[2], ss[1])];
          i2 = field_log[divide(ss[1], ss[0])];
          if (i1 != i2 || i1 >= data->blocks) {
            TRACE( 1, "ecc_correct_data", "bad index");
            corrected = ECC_FAILED;
          } else {
#if 1
            if (i1 >= 32 || i2 >= 32) {
              TRACE( -1, "ecc_correct_data", "index out of range");
            } else
#endif
            data_bytes[SECTOR_SIZE * i1] ^= ss[1];
            if (corrected != ECC_FAILED) {
              ++errors_corrected;
              corrected = ECC_CORRECTED;
            }
          }
        }
      }
    }
    data_bytes++;
  }
  
  if (corrected == ECC_CORRECTED) {
    TRACEi( 1, "ecc_correct_data", "ECC errors corrected", errors_corrected);
  } else if (corrected == ECC_FAILED) {
    TRACE( 0, "ecc_correct_data", "ECC error correction failed");
  }
  return corrected;
}

/*#define TEST*/

#ifdef TEST

generate_test_patterns( unsigned char data[])
{
  int i;

  /*    fill array with test codewords from QIC-40/80 specifications
   */
  for (i = 0; i < sizeof( data); ++i) {
    data[ i]= 0;
  }

  data[ 28 * SECTOR_SIZE] = 0x01;
  data[ 27 * SECTOR_SIZE + 1] = 0x01;
  data[ 26 * SECTOR_SIZE + 2] = 0x01;
  data[ 25 * SECTOR_SIZE + 3] = 0x01;
  data[ 24 * SECTOR_SIZE + 4] = 0x01;

  data[  2 * SECTOR_SIZE + 5] = 0x01;
  data[  3 * SECTOR_SIZE + 5] = 0xc0;
  data[  4 * SECTOR_SIZE + 5] = 0xc0;
  data[  5 * SECTOR_SIZE + 5] = 0x01;
  data[  6 * SECTOR_SIZE + 5] = 0x01;
  data[  8 * SECTOR_SIZE + 5] = 0x67;
  data[  9 * SECTOR_SIZE + 5] = 0xa6;
  data[ 10 * SECTOR_SIZE + 5] = 0xc0;
  data[ 11 * SECTOR_SIZE + 5] = 0x01;
  data[ 14 * SECTOR_SIZE + 5] = 0xff;
  data[ 15 * SECTOR_SIZE + 5] = 0x99;
  data[ 16 * SECTOR_SIZE + 5] = 0x67;
  data[ 17 * SECTOR_SIZE + 5] = 0x01;
  data[ 21 * SECTOR_SIZE + 5] = 0xa3;
  data[ 22 * SECTOR_SIZE + 5] = 0x5d;
  data[ 23 * SECTOR_SIZE + 5] = 0xff;
  data[ 24 * SECTOR_SIZE + 5] = 0x01;

  for (i = 0; i < 32; ++i) {
    data[ i * SECTOR_SIZE + 6] = i + 1;
  }
}

int
verify_parity( unsigned char data[])
{
  int i;
  unsigned char p1, p2, p3;
  int parity_errors = 0;
  static unsigned char test_parity[7][3] = {
    { 0xc0, 0xc0, 0x01, },
    { 0x67, 0xa6, 0xc0, },
    { 0xff, 0x99, 0x67, },
    { 0xa3, 0x5d, 0xff, },
    { 0xad, 0x0f, 0xa3, },
    { 0xad, 0x0f, 0xa3, },
    { 0x5d, 0xff, 0xa3, },
  };

  for (i = 0; i < 7; ++i) {
    p1 = data[ 29 * SECTOR_SIZE + i];
    p2 = data[ 30 * SECTOR_SIZE + i];
    p3 = data[ 31 * SECTOR_SIZE + i];
 
    fprintf( stderr, "%d :  0x%02x   0x%02x   0x%02x\n", i, p1, p2, p3);
    if (p1 != test_parity[ i][ 0]) {
      TRACE( 0, "verify_qic_40_80_specs", "wrong parity in position 1");
      ++parity_errors;
    }
    if (p2 != test_parity[ i][ 1]) {
      TRACE( 0, "verify_qic_40_80_specs", "wrong parity in position 2");
      ++parity_errors;
    }
    if (p3 != test_parity[ i][ 2]) {
      TRACE( 0, "verify_qic_40_80_specs", "wrong parity in position 3");
      ++parity_errors;
    }
  }
  return parity_errors;
}

void
verify_qic_40_80_specs( void)
{
  int i, j, k, l;
  unsigned char data[ SECTOR_SIZE * 32];
  struct memory_segment mseg, mseg2;
  int result;
  int parity_errors;
  int test_failed = 0;

  generate_test_patterns( data);

  /*    Generate parity values
   */
  mseg.read_bad = 0x00000000;
  mseg.marked_bad = 0;          /* not used... */
  mseg.blocks = 32;
  mseg.data = data;

  ecc_set_segment_parity( &mseg);

  parity_errors = verify_parity( data);
  if (parity_errors) {
    TRACE( -1, "verify_qic_40_80_specs", "QIC 40/80 spec. parity generation failed");
    test_failed = 1;
  }

  /*    Now test correction capabilities.
   *    Try to correct one sector with crc errors
   */
  TRACE( -1, "verify_qic_40_80_specs", "correcting one sector with crc error");
  for (j = 0; j < 32; ++j) {
    mseg2 = mseg;
    for (i = 0; i < SECTOR_SIZE; ++i) {
      mseg2.data[ j * SECTOR_SIZE + i] = 0xe5;
    }
    mseg2.read_bad = 1 << j;
    result = ecc_correct_data( &mseg2);
    if (result == ECC_CORRECTED) {
      for (i = 0; i < SECTOR_SIZE; ++i) {
        if (mseg2.data[ j * SECTOR_SIZE + i] != mseg.data[ j * SECTOR_SIZE + i]) {
          if (parity_errors == 0) {
            TRACE( -1, "verify_qic_40_80_specs", "bad ecc result");
            ++parity_errors;
          }
        }
      }
    } else if (result == ECC_CORRECT) {
      TRACE( 0, "verify_qic_40_80_specs", "ecc was correct");
      ++parity_errors;          /* WRONG !!! */
    } else {
      TRACE( 0, "verify_qic_40_80_specs", "ecc failed");
      ++parity_errors;
    }
  }
  if (parity_errors) {
    TRACE( -1, "verify_qic_40_80_specs", "ecc test failure");
    test_failed = 1;
    parity_errors = 0;
  }

  /*    Try to correct two sectors with crc errors
   */
  TRACE( 0, "verify_qic_40_80_specs", "correcting two sectors with crc errors");
  for (k = 0; k < 32; ++k) {
    for (j = k + 1; j < 32; ++j) {
      mseg2 = mseg;               /* restore correct data */
      for (i = 0; i < SECTOR_SIZE; ++i) {
        mseg2.data[ j * SECTOR_SIZE + i] = 0xe5;
        mseg2.data[ k * SECTOR_SIZE + i] = 0xe5;
      }
      mseg2.read_bad = 1 << j | 1 << k;
      result = ecc_correct_data( &mseg2);
      if (result == ECC_CORRECTED) {
        for (i = 0; i < SECTOR_SIZE; ++i) {
          if (mseg2.data[ j * SECTOR_SIZE + i] != mseg.data[ j * SECTOR_SIZE + i]
              || mseg2.data[ k * SECTOR_SIZE + i] != mseg.data[ k * SECTOR_SIZE + i]) {
            if (parity_errors == 0) {
              TRACE( -1, "verify_qic_40_80_specs", "bad ecc result");
              ++parity_errors;
            }
          }
        }
      } else if (result == ECC_CORRECT) {
        TRACE( -1, "verify_qic_40_80_specs", "ecc was correct");
        ++parity_errors;          /* WRONG !!! */
      } else {
        TRACE( -1, "verify_qic_40_80_specs", "ecc failed");
        ++parity_errors;
      }
    }
  }
  if (parity_errors) {
    TRACE( -1, "verify_qic_40_80_specs", "ecc test failure");
    test_failed = 1;
    parity_errors = 0;
  }

  /*    Try to correct three sectors with crc errors
   */
  TRACE( -1, "verify_qic_40_80_specs", "correcting three sectors with crc errors");
  for (l = 0; l < 32; ++l) {
    for (k = l + 1; k < 32; ++k) {
      for (j = k + 1; j < 32; ++j) {
        mseg2 = mseg;               /* restore correct data */
        for (i = 0; i < SECTOR_SIZE; ++i) {
          mseg2.data[ j * SECTOR_SIZE + i] =
          mseg2.data[ k * SECTOR_SIZE + i] =
          mseg2.data[ l * SECTOR_SIZE + i] = 0xe5;
        }
        mseg2.read_bad = 1 << j | 1 << k | 1 << l;
        result = ecc_correct_data( &mseg2);
        if (result == ECC_CORRECTED) {
          for (i = 0; i < SECTOR_SIZE; ++i) {
            if (mseg2.data[ j * SECTOR_SIZE + i] != mseg.data[ j * SECTOR_SIZE + i]
                || mseg2.data[ k * SECTOR_SIZE + i] != mseg.data[ k * SECTOR_SIZE + i]
                || mseg2.data[ l * SECTOR_SIZE + i] != mseg.data[ l * SECTOR_SIZE + i]) {
              if (error == 0) {
                TRACE( -1, "verify_qic_40_80_specs", "bad ecc result");
              }
            }
          }
        } else if (result == ECC_CORRECT) {
          TRACE( -1, "verify_qic_40_80_specs", "ecc was correct");
          ++parity_errors;          /* WRONG !!! */
        } else {
          TRACE( -1, "verify_qic_40_80_specs", "ecc failed");
          ++parity_errors;
        }
      }
    }
  }
  if (parity_errors) {
    TRACE( -1, "verify_qic_40_80_specs", "ecc test failure");
    test_failed = 1;
    parity_errors = 0;
  }

  /*    Try to correct one sector with crc failure
   */
  TRACE( -1, "verify_qic_40_80_specs", "correcting one sector with crc failure");
  for (j = 0; j < 32; ++j) {
    int error = 0;
    mseg2 = mseg;
    for (i = 0; i < SECTOR_SIZE; ++i) {
      mseg2.data[ j * SECTOR_SIZE + i] = 0xe5;
    }
    mseg2.read_bad = 0;
    result = ecc_correct_data( &mseg2);
    if (result == ECC_CORRECTED) {
      for (i = 0; i < SECTOR_SIZE; ++i) {
        if (mseg2.data[ j * SECTOR_SIZE + i] != mseg.data[ j * SECTOR_SIZE + i]) {
          if (error == 0) {
            TRACE( -1, "verify_qic_40_80_specs", "bad ecc result");
            ++error;
          }
          fprintf( stderr, "location (%d,%d) is 0x%02x, should be 0x%02x\n",
                  mseg2.data[ j * SECTOR_SIZE + i], mseg.data[ j * SECTOR_SIZE + i]);
        }
      }
    } else if (result == ECC_CORRECT) {
      TRACE( -1, "verify_qic_40_80_specs", "ecc was correct");
      ++parity_errors;          /* WRONG !!! */
    } else {
      TRACE( -1, "verify_qic_40_80_specs", "ecc failed");
      ++parity_errors;
    }
  }
  if (parity_errors) {
    TRACE( -1, "verify_qic_40_80_specs", "ecc test failure");
    test_failed = 1;
    parity_errors = 0;
  }

  /*    Try to correct two sectors, one with crc error, one with crc failure
   */
  TRACE( -1, "verify_qic_40_80_specs", "correcting two sectors with crc error and failure");
  for (k = 0; k < 32; ++k) {
    for (j = 0; j < 32; ++j) {
      int error = 0;
      if (j == k) {
        continue;
      }
      mseg2 = mseg;               /* restore correct data */
      for (i = 0; i < SECTOR_SIZE; ++i) {
        mseg2.data[ j * SECTOR_SIZE + i] = 0xe5;
        mseg2.data[ k * SECTOR_SIZE + i] = 0xe5;
      }
      mseg2.read_bad = 1 << j;
      result = ecc_correct_data( &mseg2);
      if (result == ECC_CORRECTED) {
        for (i = 0; i < SECTOR_SIZE; ++i) {
          if (mseg2.data[ j * SECTOR_SIZE + i] != mseg.data[ j * SECTOR_SIZE + i]) {
            if (error == 0) {
              TRACE( -1, "verify_qic_40_80_specs", "bad ecc result");
              ++error;
            }
            fprintf( stderr, "location (%d,%d) is 0x%02x, should be 0x%02x\n",
                    mseg2.data[ j * SECTOR_SIZE + i], mseg.data[ j * SECTOR_SIZE + i]);
          }
        }
      } else if (result == ECC_CORRECT) {
        TRACE( -1, "verify_qic_40_80_specs", "ecc was correct");
        ++parity_errors;          /* WRONG !!! */
      } else {
        TRACE( -1, "verify_qic_40_80_specs", "ecc failed");
        ++parity_errors;
      }
    }
  }
  if (parity_errors) {
    TRACE( -1, "verify_qic_40_80_specs", "ecc test failure");
    test_failed = 1;
    parity_errors = 0;
  }

  if (test_failed) {
    TRACE( -1, "verify_qic_40_80_specs", "QIC 40/80 spec. verification failed");
  } else {
    TRACE( -1, "verify_qic_40_80_specs", "passed QIC 40/80 spec. verification");
  }

}


main()
{
#if 0

  verify_qic_40_80_specs();

#else
  int i;
  int count;
  unsigned char data[SECTOR_SIZE * 32];
  struct memory_segment mseg;

  count = read (0, data, SECTOR_SIZE * 32);
  if (count != SECTOR_SIZE * 32) {
    abort();
  }

#if 0
/*
 *      Make an error:
 */
  TRACE( -1, "main", "3 errors created");
  data[ SECTOR_SIZE * 8 + 5] ^= 0x01;
  data[ SECTOR_SIZE * 2 + 5] ^= 0x01;
  data[ SECTOR_SIZE * 31 + 1] ^= 0x01;
  mseg.read_bad = 0x00000002;
#else
  mseg.read_bad = 0x00000000;
#endif

#if 1

  /*set_parity (data, 32);*/
  mseg.marked_bad = 0;          /* not used... */
  mseg.blocks = 32;
  mseg.data = data;
  ecc_correct_data (&mseg);

#endif

  count = write (1, data, SECTOR_SIZE * 32);
  if (count != SECTOR_SIZE * 32) {
    TRACE( -1, "main", "error writing results");
    exit( 1);
  }

#if 0
  {
    int i, j;
    unsigned char ss[3];
    unsigned char es[3];
    struct ecc_inverse_matrix inv;

    if (calculate_inverse (29, 30, 31, &inv) == 0)
      abort();

    for (j = 0; j < 1; j++)
      for (i = 0; i < SECTOR_SIZE; i++)
        {
          set_parity (data + i, 32);*/
          compute_syndromes (data + i, 32, ss);
          determine3 (&inv, es, ss);
          /*printf ("0x%02x: 0x%02x, 0x%02x, 0x%02x\n",
                  i, ss[0], ss[1], ss[2]);*/
          /*printf ("  0x%02x: 0x%02x, 0x%02x, 0x%02x\n",
                  i, es[0], es[1], es[2]);*/
        }
  }
#endif

#endif

}

#endif TEST
/*
 * Local Variables:
 * compile-command: "gcc -o ecc -gstabs -v -DTEST ecc.c"
 * End:
 */
