/*
 *      Copyright (C) 1993 Ning and David Mosberger.
 *      Original code:
 *      Copyright (C) 1993 Bas Laarhoven.
 *      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: /R/uni/uni/usr2/davidm/src/reed-solomon/RCS/ecc.c,v $
 * $Author: davidm $
 *
 * $Revision: 1.3 $
 * $Date: 1993/12/11 04:19:39 $
 * $State: Exp $
 *
 *      This file contains the Reed-Solomon error correction code 
 *      for the QIC-40/80 floppy-tape driver for Linux.
 */
static const char RCSid[] =
  "$Id: ecc.c,v 1.3 1993/12/11 04:19:39 davidm Exp $";

#include <stdio.h>
#include "fdc-io.h"
#include "ecc.h"
#ifdef LINUX
# include <linux/errno.h>
#else
# include <sys/errno.h>
#endif

#ifdef TEST

# define printk  printf
# undef TRACE()
# undef TRACEi()
# undef TRACElx()
# define TRACE(l,s1,s2) \
   {if (ftape_ecc_tracing > l) printk(__FILE__ " (%s) - %s.\n", s1, s2);}
# define TRACEi(l,s1,s2,i) \
   {if (ftape_ecc_tracing > l) printk(__FILE__ " (%s) - %s %d.\n", s1, s2, i);}
# define TRACElx(l,s1,s2,lx) \
   {if (ftape_ecc_tracing > l) printk(__FILE__ " (%s) - %s 0x%08lx.\n", \
				      s1, s2, lx);}
  int ftape_ecc_tracing = 1;

#endif /* TEST */

/*
 * Notice: to minimize the potential for confusion, we use r to
 *	   denote the independent variable of the polynomials
 *	   in the Galois Field GF(2^8).  We reserve x for polynomials
 *	   that that have coefficients in GF(2^8).
 *	   
 * The Galois Field in which coefficient arithmetic is performed are
 * the polynomials over Z_2 (i.e., 0 and 1) modulo the irreducible
 * polynomial f(r), where f(r)=r^8 + r^7 + r^2 + r + 1.  A polynomial
 * is represented as a byte with the MSB as the coefficient of r^7 and
 * the LSB as the coefficient of r^0.  For example, the binary
 * representation of f(x) is 0x187 (of course, this doesn't fit into 8
 * bits).  In this field, the polynomial r is a primitive element.
 * That is, r^i with i in 0,...,255 enumerates all elements in the
 * field.
 *
 * The generator polynomial for the QIC-80 ECC is
 *
 *	g(x) = x^3 + r^105*x^2 + r^105*x + 1
 *
 * which can be factored into:
 *
 *      g(x) = (x-r^254)(x-r^0)(x-r^1)
 *
 * the byte representation of the coefficients are:
 *
 *	r^105 = 0xc0
 *	r^254 = 0xc3
 *	r^0   = 0x01
 *	r^1   = 0x02
 *
 * Notice that r^254 = r^-1 as exponent arithmetic is performed
 * modulo 2^8-1 = 255.
 *
 * For more information on Galois Fields and Reed-Solomon codes,
 * refer to any good book.  I found _An Introduction to Error
 * Correcting Codes with Applications_ by S. A. Vanstone and
 * P. C. van Oorschot to be a good introduction into the former.
 * _CODING THEORY: The Essentials_ I found very useful for its
 * concise description of Reed-Solomon encoding/decoding.
 *
 */

typedef unsigned char Matrix[3][3];

/*
 * This array is a table of powers of r (modulo f(r)):
 */
static const unsigned char gf_powers[] = {
    /* first copy for negative indices [-255..-1] */
    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,
};

/*
 * gfpow[] is defined such that gfpow[i] returns r^i if
 * i is in the range [-255..509].
 */
static const unsigned char *const gfpow = &gf_powers[255];

/*
 * This is a log table.  That is, gflog[r^i] returns i (modulo f(r)).
 * gflog[0] is undefined and the first element is therefore not valid.
 */
static const unsigned char gflog[256] = {
    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,
};

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

/*
 * This is a multiplication table for the factor
 * 0xc3 (i.e., r^254 (modulo f(r)).
 * gfmul_c3[f] returns r^254 * f(r) (modulo f(r)).
 */
static unsigned char gfmul_c3[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,
};

/*
 * This is a multiplication table for the factor
 * 0x02 (i.e., r).
 * gfmul_02[f] returns r^254 * f(r) (modulo f(r)).
 */
static unsigned char gfmul_02[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,
};


/*
 * Add two numbers in the field.  Addition in this field is
 * equivalent to a bit-wise exclusive OR operation---subtraction
 * is therefore identical to addition.
 */
static inline unsigned char
gfadd(unsigned char a, unsigned char b)
{
    return a ^ b;
} /* gfadd */


/*
 * Multiply two numbers in the field:
 */
static inline unsigned char
gfmul(unsigned char a, unsigned char b)
{
    if (a && b) {
	return gfpow[(unsigned int)gflog[a] + gflog[b]];
    } else {
	return 0;
    } /* if */
} /* gfmul */


/*
 * Divide two numbers in the field.  Returns a/b (modulo f(x)).
 */
static inline unsigned char
gfdiv(unsigned char a, unsigned char b)
{
    if (!b) {
	TRACE(-1, "gfdiv", "Error, division by zero");
	return 0xff;
    } else if (a == 0) {
	return 0;
    } else {
	return gfpow[(int) gflog[a] - gflog[b]];
    } /* if */
} /* gfdiv */


/*
 * This is just like divide, except we have already looked up the log
 * of the second number.
 */
static inline unsigned char
gfdiv_exp(unsigned char a, unsigned char b)
{
    if (!a) {
	return 0;
    } else {
	return gfpow[(int) gflog[a] - b];
    } /* if */
} /* gfdiv_exp */


/*
 * The following functions return the inverse of the matrix of the
 * linear system that needs to be solved to determine the error
 * magnitudes.  The first deals with matrices of rank 3, while the
 * second deals with matrices of rank 2.  The error indices are passed
 * in arguments L0,..,L2 (0=first sector, 31=last sector).
 *
 * The linear system that needs to be solved for the error
 * magnitudes is A * b = s, where s is the known vector of
 * syndromes, b is the vector of error magnitudes and A in
 * the ORDER=3 case:
 *
 *    A_3 = {{1/r^LOC[0], 1/r^LOC[1], 1/r^LOC[2]},
 *	    {	       1,	   1,	       1},
 *	    {   r^LOC[0],    r^LOC[1],	r^LOC[2]}}
 */
static inline int
gfinv3(unsigned char l0, unsigned char l1, unsigned char l2, Matrix Ainv)
{
    unsigned char det, log_det;
    unsigned char t20, t10, t21, t12, t01, t02;

    /* compute some intermediate results: */
    t20 = gfpow[(int) l2 - l0];		/* t20 = r^l2/r^l0 */
    t10 = gfpow[(int) l1 - l0];		/* t10 = r^l1/r^l0 */
    t21 = gfpow[(int) l2 - l1];		/* t21 = r^l2/r^l1 */
    t12 = gfpow[(int) l1 - l2];		/* t12 = r^l1/r^l2 */
    t01 = gfpow[(int) l0 - l1];		/* t01 = r^l0/r^l1 */
    t02 = gfpow[(int) l0 - l2];		/* t02 = r^l0/r^l2 */
    /*
     * Calculate the determinant of matrix A_3 (sometimes called
     * the Vandermonde determinant):
     */
    det = gfadd(t20, gfadd(t10, gfadd(t21, gfadd(t12, gfadd(t01, t02)))));
    if (!det) {
	TRACE(-1, "gfinv3", "Singular matrix!  Inversion failed.");
	return 0;
    } /* if */

    log_det = gflog[det];
    /*
     * Now, calculate all of the coefficients:
     */
    Ainv[0][0] = gfdiv_exp(gfadd(gfpow[ l1], gfpow[ l2]), log_det);
    Ainv[0][1] = gfdiv_exp(gfadd(       t21,        t12), log_det);
    Ainv[0][2] = gfdiv_exp(gfadd(gfpow[-l1], gfpow[-l2]), log_det);

    Ainv[1][0] = gfdiv_exp(gfadd(gfpow[ l0],  gfpow[l2]), log_det);
    Ainv[1][1] = gfdiv_exp(gfadd(       t20,        t02), log_det);
    Ainv[1][2] = gfdiv_exp(gfadd(gfpow[-l0], gfpow[-l2]), log_det);

    Ainv[2][0] = gfdiv_exp(gfadd( gfpow[l0],  gfpow[l1]), log_det);
    Ainv[2][1] = gfdiv_exp(gfadd(       t10,        t01), log_det);
    Ainv[2][2] = gfdiv_exp(gfadd(gfpow[-l0], gfpow[-l1]), log_det);

    return 1;
} /* gfinv3 */


static inline int
gfinv2(unsigned char l0, unsigned char l1, Matrix Ainv)
{
    unsigned char det, log_det;
    unsigned char t1, t2;

    t1 = gfpow[(int) -l0];
    t2 = gfpow[(int) -l1];
    det = gfadd(t1, t2);
    if (!det) {
	TRACE(-1, "gfinv2", "Singular matrix!  Inversion failed.");
	return 0;
    } /* if */
    log_det = gflog[det];
    /*
     * Now, calculate all of the coefficients:
     */
    Ainv[0][0] = gfdiv_exp( 1, log_det);
    Ainv[0][1] = gfdiv_exp(t2, log_det);

    Ainv[1][0] = gfdiv_exp( 1, log_det);
    Ainv[1][1] = gfdiv_exp(t1, log_det);

    return 1;
} /* gfinv2 */


/*
 * Multiply matrix A by vector s and return result in vector b.
 * m is assumed to be of order nxn, s and b of order nx1.
 */
static inline void
gfmat_mul(int n, Matrix A, unsigned char *b, unsigned char *s)
{
    int i, j;
    unsigned char dot_prod;

    for (i = 0; i < n; ++i) {
	dot_prod = 0;
	for (j = 0; j < n; ++j) {
	    dot_prod = gfadd(dot_prod, gfmul(A[i][j], s[j]));
	} /* for */
	b[i] = dot_prod;
    } /* for */
} /* gfmat_mul */



/*
 * The Reed Solomon ECC codes are computed over the n-th byte of each
 * block.  There are up to 29 blocks of data, and 3 blocks of ECC.
 * The blocks are storred consecutively in memory.
 *
 * Notice: In QIC-80 speak, a CRC error is a sector with an incorrect
 *	   CRC.  A CRC failure is a sector with incorrect data, but
 *	   a valid CRC.  In the error control literature, the former
 *	   is usually called "erasure", the latter "error."
 */

/*
 * Compute the 3 syndrome values.  DATA should point to the first byte
 * of the column for which the syndromes are desired.  The syndromes
 * are computed over the first NBLOCKS of rows.  The three bytes will be
 * placed in s[0], s[1], and s[2].
 *
 * s[i] is the value of the "message" polynomial evaluated at the i-th root
 * of the generator polynomial g(x).  The message polynomial is defined as:
 *
 *	m(x) = sum_{i=0}^{NBLOCKS} DATA[i] * x^i.
 *
 * As g(x)=(x-c3)(x-1)(x-2) we evaluate the message polynomial at x=c3
 * to get s[0], at x=1 to get s[1], and at x=2 to get s[2].  The message
 * polynomial is evaluated by using Horner's rule, i.e.,
 *
 *	f(x) = sum_{i=0}^{degree(f)} f_i x^i =
 *		x * (sum_{i=1}^{degree(f)} f_i x^{i-1}) + f_0.
 */
static inline void
compute_syndromes (unsigned char *data, int nblocks, unsigned char *s)
{
    unsigned char *dp;

    s[0] = s[1] = s[2] = 0;

    /* Horner's rule requires us to step backwards through data: */
    dp = data + SECTOR_SIZE * (nblocks - 1);

    while (dp >= data) {
	s[0] = gfadd( gfmul_c3[s[0]], *dp);
	s[1] = gfadd(	       s[1],  *dp);
	s[2] = gfadd( gfmul_02[s[2]], *dp);
	dp -= SECTOR_SIZE;
    } /* while */
} /* compute_syndromes */


/*
 * Compute the three parity bytes for a single column of
 * data.  We use a linear feed-back register to do this.
 */
static inline void
set_parity(unsigned char *data, int nblocks)
{
    int i;
    unsigned char s0, s1, s2, t;

    s0 = s1 = s2 = 0;
    for (i = 0; i < nblocks - 3; ++i) {
	t  = gfadd(*data, s0);
	s0 = gfadd(gfmul_c0[t], s1);
	s1 = gfadd(gfmul_c0[t], s2);
	s2 = t;
	data += SECTOR_SIZE;
    } /* for */
    *data = s0;
    data += SECTOR_SIZE;
    *data = s1;
    data += SECTOR_SIZE;
    *data = s2;
} /* set_parity */


static inline int
correct_block(unsigned char *data, int nblocks,
	      int nerasures, int *erasure_loc, Matrix Ainv,
	      BAD_SECTOR *correction_map)
{
    int ncorrected = 0;
    int i;
    unsigned char a1, a2, t1, t2, s[3];
    unsigned char error_loc, error_mag[3];
    /*
     * CORRECT_BYTE(l,e) corrects the byte in sector L by the
     * error magnitude E.  CORRECTION_MAP and NCORRECTED get
     * update as a side-effect.
     */
#   define CORRECT_BYTE(l,e) {			\
        unsigned char *dp;			\
						\
        dp = &data[(l) * SECTOR_SIZE];		\
	*dp = gfadd(*dp, (e));			\
	*correction_map |= 1 << (l);		\
	++ncorrected;				\
    }

    compute_syndromes(data, nblocks, s);
    if (!(s[0] || s[1] || s[2])) {
	return 0;		/* fast case---neither errors nor erasures */
    } /* if */

    switch (nerasures) {
      case 0:
	/* might have a CRC failure: */
	t1 = 0;				/* to keep GCC happy... */
	if (s[0] == 0 || s[1] == 0
	    || (t1 = gfdiv(s[1], s[0])) != gfdiv(s[2], s[1]))
	{
	    /* more than one error */
	    TRACE(0, "correct_block", "no erasures, more than 1 error.");
	    return -1;
	} /* if */
	error_loc = gflog[t1];
	if (error_loc >= nblocks) {
	    TRACEi(-1, "correct_block", "attempt to correct data at",
		   error_loc);
	    return -1;
	} /* if */
	CORRECT_BYTE(error_loc, s[1]);
	break;

      case 1:
	a1 = gfpow[erasure_loc[0]];
	t1 = gfadd(gfmul(a1, s[1]), s[2]);
	t2 = gfadd(gfmul(a1, s[0]), s[1]);
	if (t1 == 0 && t2 == 0) {
	    /* one erasure, no error: */
	    if (s[0]) {
		/* non-zero error magnitude: */
		t1 = gfmul(a1, s[0]);
		CORRECT_BYTE(erasure_loc[0], t1);
	    } /* if */
	} else if (t1 == 0 || t2 == 0) {
	    /* more than one erasure and one error: */
	    TRACE(0, "correct_block", "more than 1 erasure and 1 error");
	    return -1;
	} else {
	    /* one erasure, one error: */
	    error_loc = gflog[gfdiv(t1, t2)];
	    if (error_loc >= nblocks) {
		TRACEi(-1, "correct_block", "attempt to correct data at",
		       error_loc);
		return -1;
	    } /* if */
	    if (!gfinv2(erasure_loc[0], error_loc, Ainv)) {
		/* inversion failed---must have more than one error */
		return -1;
	    } /* if */
	    gfmat_mul(2, Ainv, error_mag, s);
	    if (error_mag[0]) {
		CORRECT_BYTE(erasure_loc[0], error_mag[0]);
	    } /* if */
	    CORRECT_BYTE(error_loc, error_mag[1]);
	} /* if */
	break;

      case 2:
	a1 = gfpow[erasure_loc[0]];
	a2 = gfpow[erasure_loc[1]];
	t1 = gfadd(gfadd(gfmul(gfmul(a1, a2), s[0]),
			 gfmul(gfadd(a1, a2), s[1])),
		   s[2]);
	if (t1) {
	    TRACE(0, "correct_block",
		  "2 erasures and errors: too many errors");
	    return -1;
	} /* if */

	/* FALL TROUGH TO CORRECTION */

      case 3:
	gfmat_mul(nerasures, Ainv, error_mag, s);
	for (i = 0; i < nerasures; ++i) {
	    if (error_mag[i]) {
		CORRECT_BYTE(erasure_loc[i], error_mag[i]);
	    } /* if */
	} /* for */
	break;

      default:
	TRACE(-1, "correct_block",
	      "Internal Error: number of CRC errors > 3");
	return -1;
    } /* switch */
    return ncorrected;
} /* correct_block */



/*
 * Compute the parity for an entire segment of data.
 */
int
ecc_set_segment_parity(struct memory_segment* mseg)
{
    int i;

    for (i = 0; i < SECTOR_SIZE; ++i) {
	set_parity(&mseg->data[i], mseg->blocks);
#ifdef ECC_PARANOID
	{
	    unsigned char s[3];
	    /*
	     * Perform a sanity check on the computed parity bytes:
	     */
	    compute_syndromes(&mseg->data[i], mseg->blocks, s);
	    if (s[0] || s[1] || s[2]) {
		TRACE(-1, "ecc_set_segment_parity",
		      "Internal Error: syndrome self-check failed");
		return -1;
	    } /* if */
	}
#endif /* ECC_PARANOID */
    } /* for */
    return 0;
} /* ecc_set_segment_parity */


/*
 * Checks and corrects (if possible) the segment MSEG.  Returns one of
 * ECC_OK, ECC_CORRECTED, and ECC_FAILED.
 */
int
ecc_correct_data( struct memory_segment* mseg)
{
    int i, result;
    int ncorrected;
    int nerasures = 0;		/* # of erasures (CRC errors) */
    int erasure_loc[3];		/* erasure locations */
    Matrix Ainv;

    if (mseg->read_bad) {
	/* count the number of CRC errors: */
	for (i = 0; i < mseg->blocks; i++) {
	    if (BAD_CHECK(mseg->read_bad, i)) {
		if (nerasures >= 3) {
		    /* this is too much for ECC */
		    return ECC_FAILED;
		} /* if */
		erasure_loc[nerasures++] = i;
	    } /* if */
	} /* for */
    } /* if */

    switch (nerasures) {
      case 2:
	result = gfinv2(erasure_loc[0], erasure_loc[1], Ainv);
	break;
      case 3:
	result = gfinv3(erasure_loc[0], erasure_loc[1], erasure_loc[2], Ainv);
	break;
      default:
	/* this is not an error condition... */
	result = 1;
	break;
    } /* switch */
    if (!result) {
	TRACE(0, "ecc_correct_data", "unrecoverable ECC error");
	return ECC_FAILED;
    } /* if */

    ncorrected = 0;
    mseg->corrected = 0;
    for (i = 0; i < SECTOR_SIZE; i++) {
	result  = correct_block(&mseg->data[i], mseg->blocks,
				nerasures, erasure_loc, Ainv,
				&mseg->corrected);
	if (result < 0) {
	    return ECC_FAILED;
	} /* if */
	ncorrected += result;

#ifdef ECC_SANITY_CHECK
	if (result > 0) {
	    unsigned char s[3];
	    /*
	     * If data was modifed, ensure corrected data forms a valid
	     * code word (i.e., has a zero syndrome):
	     */
	    compute_syndromes(&mseg->data[i], mseg->blocks, s);
	    if (s[0] || s[1] || s[2]) {
		TRACE(-1, "ecc_correct_data",
		      "Internal Error: syndrome self-check failed");
		return ECC_FAILED;
	    } /* if */
	} /* if */
#endif /* ECC_SANITY_CHECK */
    } /* for */
    if (ncorrected && nerasures == 0) {
	TRACE(0, "ecc_correct_data",
	      "block contained error not caught by CRC");
    } /* if */
    TRACEi( (ncorrected > 0) ? 0 : 2, "ecc_correct_data",
           "number of corrections:", ncorrected);
    return ncorrected ? ECC_CORRECTED : ECC_OK;
} /* ecc_correct_data */

			/*** end of ecc.c ***/
