Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* |
| 2 | * |
| 3 | * Copyright (c) 1993 Ning and David Mosberger. |
| 4 | |
| 5 | This is based on code originally written by Bas Laarhoven (bas@vimec.nl) |
| 6 | and David L. Brown, Jr., and incorporates improvements suggested by |
| 7 | Kai Harrekilde-Petersen. |
| 8 | |
| 9 | This program is free software; you can redistribute it and/or |
| 10 | modify it under the terms of the GNU General Public License as |
| 11 | published by the Free Software Foundation; either version 2, or (at |
| 12 | your option) any later version. |
| 13 | |
| 14 | This program is distributed in the hope that it will be useful, but |
| 15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
| 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 17 | General Public License for more details. |
| 18 | |
| 19 | You should have received a copy of the GNU General Public License |
| 20 | along with this program; see the file COPYING. If not, write to |
| 21 | the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, |
| 22 | USA. |
| 23 | |
| 24 | * |
| 25 | * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-ecc.c,v $ |
| 26 | * $Revision: 1.3 $ |
| 27 | * $Date: 1997/10/05 19:18:10 $ |
| 28 | * |
| 29 | * This file contains the Reed-Solomon error correction code |
| 30 | * for the QIC-40/80 floppy-tape driver for Linux. |
| 31 | */ |
| 32 | |
| 33 | #include <linux/ftape.h> |
| 34 | |
| 35 | #include "../lowlevel/ftape-tracing.h" |
| 36 | #include "../lowlevel/ftape-ecc.h" |
| 37 | |
| 38 | /* Machines that are big-endian should define macro BIG_ENDIAN. |
| 39 | * Unfortunately, there doesn't appear to be a standard include file |
| 40 | * that works for all OSs. |
| 41 | */ |
| 42 | |
| 43 | #if defined(__sparc__) || defined(__hppa) |
| 44 | #define BIG_ENDIAN |
| 45 | #endif /* __sparc__ || __hppa */ |
| 46 | |
| 47 | #if defined(__mips__) |
| 48 | #error Find a smart way to determine the Endianness of the MIPS CPU |
| 49 | #endif |
| 50 | |
| 51 | /* Notice: to minimize the potential for confusion, we use r to |
| 52 | * denote the independent variable of the polynomials in the |
| 53 | * Galois Field GF(2^8). We reserve x for polynomials that |
| 54 | * that have coefficients in GF(2^8). |
| 55 | * |
| 56 | * The Galois Field in which coefficient arithmetic is performed are |
| 57 | * the polynomials over Z_2 (i.e., 0 and 1) modulo the irreducible |
| 58 | * polynomial f(r), where f(r)=r^8 + r^7 + r^2 + r + 1. A polynomial |
| 59 | * is represented as a byte with the MSB as the coefficient of r^7 and |
| 60 | * the LSB as the coefficient of r^0. For example, the binary |
| 61 | * representation of f(x) is 0x187 (of course, this doesn't fit into 8 |
| 62 | * bits). In this field, the polynomial r is a primitive element. |
| 63 | * That is, r^i with i in 0,...,255 enumerates all elements in the |
| 64 | * field. |
| 65 | * |
| 66 | * The generator polynomial for the QIC-80 ECC is |
| 67 | * |
| 68 | * g(x) = x^3 + r^105*x^2 + r^105*x + 1 |
| 69 | * |
| 70 | * which can be factored into: |
| 71 | * |
| 72 | * g(x) = (x-r^-1)(x-r^0)(x-r^1) |
| 73 | * |
| 74 | * the byte representation of the coefficients are: |
| 75 | * |
| 76 | * r^105 = 0xc0 |
| 77 | * r^-1 = 0xc3 |
| 78 | * r^0 = 0x01 |
| 79 | * r^1 = 0x02 |
| 80 | * |
| 81 | * Notice that r^-1 = r^254 as exponent arithmetic is performed |
| 82 | * modulo 2^8-1 = 255. |
| 83 | * |
| 84 | * For more information on Galois Fields and Reed-Solomon codes, refer |
| 85 | * to any good book. I found _An Introduction to Error Correcting |
| 86 | * Codes with Applications_ by S. A. Vanstone and P. C. van Oorschot |
| 87 | * to be a good introduction into the former. _CODING THEORY: The |
| 88 | * Essentials_ I found very useful for its concise description of |
| 89 | * Reed-Solomon encoding/decoding. |
| 90 | * |
| 91 | */ |
| 92 | |
| 93 | typedef __u8 Matrix[3][3]; |
| 94 | |
| 95 | /* |
| 96 | * gfpow[] is defined such that gfpow[i] returns r^i if |
| 97 | * i is in the range [0..255]. |
| 98 | */ |
| 99 | static const __u8 gfpow[] = |
| 100 | { |
| 101 | 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, |
| 102 | 0x87, 0x89, 0x95, 0xad, 0xdd, 0x3d, 0x7a, 0xf4, |
| 103 | 0x6f, 0xde, 0x3b, 0x76, 0xec, 0x5f, 0xbe, 0xfb, |
| 104 | 0x71, 0xe2, 0x43, 0x86, 0x8b, 0x91, 0xa5, 0xcd, |
| 105 | 0x1d, 0x3a, 0x74, 0xe8, 0x57, 0xae, 0xdb, 0x31, |
| 106 | 0x62, 0xc4, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, 0x67, |
| 107 | 0xce, 0x1b, 0x36, 0x6c, 0xd8, 0x37, 0x6e, 0xdc, |
| 108 | 0x3f, 0x7e, 0xfc, 0x7f, 0xfe, 0x7b, 0xf6, 0x6b, |
| 109 | 0xd6, 0x2b, 0x56, 0xac, 0xdf, 0x39, 0x72, 0xe4, |
| 110 | 0x4f, 0x9e, 0xbb, 0xf1, 0x65, 0xca, 0x13, 0x26, |
| 111 | 0x4c, 0x98, 0xb7, 0xe9, 0x55, 0xaa, 0xd3, 0x21, |
| 112 | 0x42, 0x84, 0x8f, 0x99, 0xb5, 0xed, 0x5d, 0xba, |
| 113 | 0xf3, 0x61, 0xc2, 0x03, 0x06, 0x0c, 0x18, 0x30, |
| 114 | 0x60, 0xc0, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0, |
| 115 | 0x47, 0x8e, 0x9b, 0xb1, 0xe5, 0x4d, 0x9a, 0xb3, |
| 116 | 0xe1, 0x45, 0x8a, 0x93, 0xa1, 0xc5, 0x0d, 0x1a, |
| 117 | 0x34, 0x68, 0xd0, 0x27, 0x4e, 0x9c, 0xbf, 0xf9, |
| 118 | 0x75, 0xea, 0x53, 0xa6, 0xcb, 0x11, 0x22, 0x44, |
| 119 | 0x88, 0x97, 0xa9, 0xd5, 0x2d, 0x5a, 0xb4, 0xef, |
| 120 | 0x59, 0xb2, 0xe3, 0x41, 0x82, 0x83, 0x81, 0x85, |
| 121 | 0x8d, 0x9d, 0xbd, 0xfd, 0x7d, 0xfa, 0x73, 0xe6, |
| 122 | 0x4b, 0x96, 0xab, 0xd1, 0x25, 0x4a, 0x94, 0xaf, |
| 123 | 0xd9, 0x35, 0x6a, 0xd4, 0x2f, 0x5e, 0xbc, 0xff, |
| 124 | 0x79, 0xf2, 0x63, 0xc6, 0x0b, 0x16, 0x2c, 0x58, |
| 125 | 0xb0, 0xe7, 0x49, 0x92, 0xa3, 0xc1, 0x05, 0x0a, |
| 126 | 0x14, 0x28, 0x50, 0xa0, 0xc7, 0x09, 0x12, 0x24, |
| 127 | 0x48, 0x90, 0xa7, 0xc9, 0x15, 0x2a, 0x54, 0xa8, |
| 128 | 0xd7, 0x29, 0x52, 0xa4, 0xcf, 0x19, 0x32, 0x64, |
| 129 | 0xc8, 0x17, 0x2e, 0x5c, 0xb8, 0xf7, 0x69, 0xd2, |
| 130 | 0x23, 0x46, 0x8c, 0x9f, 0xb9, 0xf5, 0x6d, 0xda, |
| 131 | 0x33, 0x66, 0xcc, 0x1f, 0x3e, 0x7c, 0xf8, 0x77, |
| 132 | 0xee, 0x5b, 0xb6, 0xeb, 0x51, 0xa2, 0xc3, 0x01 |
| 133 | }; |
| 134 | |
| 135 | /* |
| 136 | * This is a log table. That is, gflog[r^i] returns i (modulo f(r)). |
| 137 | * gflog[0] is undefined and the first element is therefore not valid. |
| 138 | */ |
| 139 | static const __u8 gflog[256] = |
| 140 | { |
| 141 | 0xff, 0x00, 0x01, 0x63, 0x02, 0xc6, 0x64, 0x6a, |
| 142 | 0x03, 0xcd, 0xc7, 0xbc, 0x65, 0x7e, 0x6b, 0x2a, |
| 143 | 0x04, 0x8d, 0xce, 0x4e, 0xc8, 0xd4, 0xbd, 0xe1, |
| 144 | 0x66, 0xdd, 0x7f, 0x31, 0x6c, 0x20, 0x2b, 0xf3, |
| 145 | 0x05, 0x57, 0x8e, 0xe8, 0xcf, 0xac, 0x4f, 0x83, |
| 146 | 0xc9, 0xd9, 0xd5, 0x41, 0xbe, 0x94, 0xe2, 0xb4, |
| 147 | 0x67, 0x27, 0xde, 0xf0, 0x80, 0xb1, 0x32, 0x35, |
| 148 | 0x6d, 0x45, 0x21, 0x12, 0x2c, 0x0d, 0xf4, 0x38, |
| 149 | 0x06, 0x9b, 0x58, 0x1a, 0x8f, 0x79, 0xe9, 0x70, |
| 150 | 0xd0, 0xc2, 0xad, 0xa8, 0x50, 0x75, 0x84, 0x48, |
| 151 | 0xca, 0xfc, 0xda, 0x8a, 0xd6, 0x54, 0x42, 0x24, |
| 152 | 0xbf, 0x98, 0x95, 0xf9, 0xe3, 0x5e, 0xb5, 0x15, |
| 153 | 0x68, 0x61, 0x28, 0xba, 0xdf, 0x4c, 0xf1, 0x2f, |
| 154 | 0x81, 0xe6, 0xb2, 0x3f, 0x33, 0xee, 0x36, 0x10, |
| 155 | 0x6e, 0x18, 0x46, 0xa6, 0x22, 0x88, 0x13, 0xf7, |
| 156 | 0x2d, 0xb8, 0x0e, 0x3d, 0xf5, 0xa4, 0x39, 0x3b, |
| 157 | 0x07, 0x9e, 0x9c, 0x9d, 0x59, 0x9f, 0x1b, 0x08, |
| 158 | 0x90, 0x09, 0x7a, 0x1c, 0xea, 0xa0, 0x71, 0x5a, |
| 159 | 0xd1, 0x1d, 0xc3, 0x7b, 0xae, 0x0a, 0xa9, 0x91, |
| 160 | 0x51, 0x5b, 0x76, 0x72, 0x85, 0xa1, 0x49, 0xeb, |
| 161 | 0xcb, 0x7c, 0xfd, 0xc4, 0xdb, 0x1e, 0x8b, 0xd2, |
| 162 | 0xd7, 0x92, 0x55, 0xaa, 0x43, 0x0b, 0x25, 0xaf, |
| 163 | 0xc0, 0x73, 0x99, 0x77, 0x96, 0x5c, 0xfa, 0x52, |
| 164 | 0xe4, 0xec, 0x5f, 0x4a, 0xb6, 0xa2, 0x16, 0x86, |
| 165 | 0x69, 0xc5, 0x62, 0xfe, 0x29, 0x7d, 0xbb, 0xcc, |
| 166 | 0xe0, 0xd3, 0x4d, 0x8c, 0xf2, 0x1f, 0x30, 0xdc, |
| 167 | 0x82, 0xab, 0xe7, 0x56, 0xb3, 0x93, 0x40, 0xd8, |
| 168 | 0x34, 0xb0, 0xef, 0x26, 0x37, 0x0c, 0x11, 0x44, |
| 169 | 0x6f, 0x78, 0x19, 0x9a, 0x47, 0x74, 0xa7, 0xc1, |
| 170 | 0x23, 0x53, 0x89, 0xfb, 0x14, 0x5d, 0xf8, 0x97, |
| 171 | 0x2e, 0x4b, 0xb9, 0x60, 0x0f, 0xed, 0x3e, 0xe5, |
| 172 | 0xf6, 0x87, 0xa5, 0x17, 0x3a, 0xa3, 0x3c, 0xb7 |
| 173 | }; |
| 174 | |
| 175 | /* This is a multiplication table for the factor 0xc0 (i.e., r^105 (mod f(r)). |
| 176 | * gfmul_c0[f] returns r^105 * f(r) (modulo f(r)). |
| 177 | */ |
| 178 | static const __u8 gfmul_c0[256] = |
| 179 | { |
| 180 | 0x00, 0xc0, 0x07, 0xc7, 0x0e, 0xce, 0x09, 0xc9, |
| 181 | 0x1c, 0xdc, 0x1b, 0xdb, 0x12, 0xd2, 0x15, 0xd5, |
| 182 | 0x38, 0xf8, 0x3f, 0xff, 0x36, 0xf6, 0x31, 0xf1, |
| 183 | 0x24, 0xe4, 0x23, 0xe3, 0x2a, 0xea, 0x2d, 0xed, |
| 184 | 0x70, 0xb0, 0x77, 0xb7, 0x7e, 0xbe, 0x79, 0xb9, |
| 185 | 0x6c, 0xac, 0x6b, 0xab, 0x62, 0xa2, 0x65, 0xa5, |
| 186 | 0x48, 0x88, 0x4f, 0x8f, 0x46, 0x86, 0x41, 0x81, |
| 187 | 0x54, 0x94, 0x53, 0x93, 0x5a, 0x9a, 0x5d, 0x9d, |
| 188 | 0xe0, 0x20, 0xe7, 0x27, 0xee, 0x2e, 0xe9, 0x29, |
| 189 | 0xfc, 0x3c, 0xfb, 0x3b, 0xf2, 0x32, 0xf5, 0x35, |
| 190 | 0xd8, 0x18, 0xdf, 0x1f, 0xd6, 0x16, 0xd1, 0x11, |
| 191 | 0xc4, 0x04, 0xc3, 0x03, 0xca, 0x0a, 0xcd, 0x0d, |
| 192 | 0x90, 0x50, 0x97, 0x57, 0x9e, 0x5e, 0x99, 0x59, |
| 193 | 0x8c, 0x4c, 0x8b, 0x4b, 0x82, 0x42, 0x85, 0x45, |
| 194 | 0xa8, 0x68, 0xaf, 0x6f, 0xa6, 0x66, 0xa1, 0x61, |
| 195 | 0xb4, 0x74, 0xb3, 0x73, 0xba, 0x7a, 0xbd, 0x7d, |
| 196 | 0x47, 0x87, 0x40, 0x80, 0x49, 0x89, 0x4e, 0x8e, |
| 197 | 0x5b, 0x9b, 0x5c, 0x9c, 0x55, 0x95, 0x52, 0x92, |
| 198 | 0x7f, 0xbf, 0x78, 0xb8, 0x71, 0xb1, 0x76, 0xb6, |
| 199 | 0x63, 0xa3, 0x64, 0xa4, 0x6d, 0xad, 0x6a, 0xaa, |
| 200 | 0x37, 0xf7, 0x30, 0xf0, 0x39, 0xf9, 0x3e, 0xfe, |
| 201 | 0x2b, 0xeb, 0x2c, 0xec, 0x25, 0xe5, 0x22, 0xe2, |
| 202 | 0x0f, 0xcf, 0x08, 0xc8, 0x01, 0xc1, 0x06, 0xc6, |
| 203 | 0x13, 0xd3, 0x14, 0xd4, 0x1d, 0xdd, 0x1a, 0xda, |
| 204 | 0xa7, 0x67, 0xa0, 0x60, 0xa9, 0x69, 0xae, 0x6e, |
| 205 | 0xbb, 0x7b, 0xbc, 0x7c, 0xb5, 0x75, 0xb2, 0x72, |
| 206 | 0x9f, 0x5f, 0x98, 0x58, 0x91, 0x51, 0x96, 0x56, |
| 207 | 0x83, 0x43, 0x84, 0x44, 0x8d, 0x4d, 0x8a, 0x4a, |
| 208 | 0xd7, 0x17, 0xd0, 0x10, 0xd9, 0x19, 0xde, 0x1e, |
| 209 | 0xcb, 0x0b, 0xcc, 0x0c, 0xc5, 0x05, 0xc2, 0x02, |
| 210 | 0xef, 0x2f, 0xe8, 0x28, 0xe1, 0x21, 0xe6, 0x26, |
| 211 | 0xf3, 0x33, 0xf4, 0x34, 0xfd, 0x3d, 0xfa, 0x3a |
| 212 | }; |
| 213 | |
| 214 | |
| 215 | /* Returns V modulo 255 provided V is in the range -255,-254,...,509. |
| 216 | */ |
| 217 | static inline __u8 mod255(int v) |
| 218 | { |
| 219 | if (v > 0) { |
| 220 | if (v < 255) { |
| 221 | return v; |
| 222 | } else { |
| 223 | return v - 255; |
| 224 | } |
| 225 | } else { |
| 226 | return v + 255; |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | |
| 231 | /* Add two numbers in the field. Addition in this field is equivalent |
| 232 | * to a bit-wise exclusive OR operation---subtraction is therefore |
| 233 | * identical to addition. |
| 234 | */ |
| 235 | static inline __u8 gfadd(__u8 a, __u8 b) |
| 236 | { |
| 237 | return a ^ b; |
| 238 | } |
| 239 | |
| 240 | |
| 241 | /* Add two vectors of numbers in the field. Each byte in A and B gets |
| 242 | * added individually. |
| 243 | */ |
| 244 | static inline unsigned long gfadd_long(unsigned long a, unsigned long b) |
| 245 | { |
| 246 | return a ^ b; |
| 247 | } |
| 248 | |
| 249 | |
| 250 | /* Multiply two numbers in the field: |
| 251 | */ |
| 252 | static inline __u8 gfmul(__u8 a, __u8 b) |
| 253 | { |
| 254 | if (a && b) { |
| 255 | return gfpow[mod255(gflog[a] + gflog[b])]; |
| 256 | } else { |
| 257 | return 0; |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | |
| 262 | /* Just like gfmul, except we have already looked up the log of the |
| 263 | * second number. |
| 264 | */ |
| 265 | static inline __u8 gfmul_exp(__u8 a, int b) |
| 266 | { |
| 267 | if (a) { |
| 268 | return gfpow[mod255(gflog[a] + b)]; |
| 269 | } else { |
| 270 | return 0; |
| 271 | } |
| 272 | } |
| 273 | |
| 274 | |
| 275 | /* Just like gfmul_exp, except that A is a vector of numbers. That |
| 276 | * is, each byte in A gets multiplied by gfpow[mod255(B)]. |
| 277 | */ |
| 278 | static inline unsigned long gfmul_exp_long(unsigned long a, int b) |
| 279 | { |
| 280 | __u8 t; |
| 281 | |
| 282 | if (sizeof(long) == 4) { |
| 283 | return ( |
| 284 | ((t = (__u32)a >> 24 & 0xff) ? |
| 285 | (((__u32) gfpow[mod255(gflog[t] + b)]) << 24) : 0) | |
| 286 | ((t = (__u32)a >> 16 & 0xff) ? |
| 287 | (((__u32) gfpow[mod255(gflog[t] + b)]) << 16) : 0) | |
| 288 | ((t = (__u32)a >> 8 & 0xff) ? |
| 289 | (((__u32) gfpow[mod255(gflog[t] + b)]) << 8) : 0) | |
| 290 | ((t = (__u32)a >> 0 & 0xff) ? |
| 291 | (((__u32) gfpow[mod255(gflog[t] + b)]) << 0) : 0)); |
| 292 | } else if (sizeof(long) == 8) { |
| 293 | return ( |
| 294 | ((t = (__u64)a >> 56 & 0xff) ? |
| 295 | (((__u64) gfpow[mod255(gflog[t] + b)]) << 56) : 0) | |
| 296 | ((t = (__u64)a >> 48 & 0xff) ? |
| 297 | (((__u64) gfpow[mod255(gflog[t] + b)]) << 48) : 0) | |
| 298 | ((t = (__u64)a >> 40 & 0xff) ? |
| 299 | (((__u64) gfpow[mod255(gflog[t] + b)]) << 40) : 0) | |
| 300 | ((t = (__u64)a >> 32 & 0xff) ? |
| 301 | (((__u64) gfpow[mod255(gflog[t] + b)]) << 32) : 0) | |
| 302 | ((t = (__u64)a >> 24 & 0xff) ? |
| 303 | (((__u64) gfpow[mod255(gflog[t] + b)]) << 24) : 0) | |
| 304 | ((t = (__u64)a >> 16 & 0xff) ? |
| 305 | (((__u64) gfpow[mod255(gflog[t] + b)]) << 16) : 0) | |
| 306 | ((t = (__u64)a >> 8 & 0xff) ? |
| 307 | (((__u64) gfpow[mod255(gflog[t] + b)]) << 8) : 0) | |
| 308 | ((t = (__u64)a >> 0 & 0xff) ? |
| 309 | (((__u64) gfpow[mod255(gflog[t] + b)]) << 0) : 0)); |
| 310 | } else { |
| 311 | TRACE_FUN(ft_t_any); |
| 312 | TRACE_ABORT(-1, ft_t_err, "Error: size of long is %d bytes", |
| 313 | (int)sizeof(long)); |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | |
| 318 | /* Divide two numbers in the field. Returns a/b (modulo f(x)). |
| 319 | */ |
| 320 | static inline __u8 gfdiv(__u8 a, __u8 b) |
| 321 | { |
| 322 | if (!b) { |
| 323 | TRACE_FUN(ft_t_any); |
| 324 | TRACE_ABORT(0xff, ft_t_bug, "Error: division by zero"); |
| 325 | } else if (a == 0) { |
| 326 | return 0; |
| 327 | } else { |
| 328 | return gfpow[mod255(gflog[a] - gflog[b])]; |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | |
| 333 | /* The following functions return the inverse of the matrix of the |
| 334 | * linear system that needs to be solved to determine the error |
| 335 | * magnitudes. The first deals with matrices of rank 3, while the |
| 336 | * second deals with matrices of rank 2. The error indices are passed |
| 337 | * in arguments L0,..,L2 (0=first sector, 31=last sector). The error |
| 338 | * indices must be sorted in ascending order, i.e., L0<L1<L2. |
| 339 | * |
| 340 | * The linear system that needs to be solved for the error magnitudes |
| 341 | * is A * b = s, where s is the known vector of syndromes, b is the |
| 342 | * vector of error magnitudes and A in the ORDER=3 case: |
| 343 | * |
| 344 | * A_3 = {{1/r^L[0], 1/r^L[1], 1/r^L[2]}, |
| 345 | * { 1, 1, 1}, |
| 346 | * { r^L[0], r^L[1], r^L[2]}} |
| 347 | */ |
| 348 | static inline int gfinv3(__u8 l0, |
| 349 | __u8 l1, |
| 350 | __u8 l2, |
| 351 | Matrix Ainv) |
| 352 | { |
| 353 | __u8 det; |
| 354 | __u8 t20, t10, t21, t12, t01, t02; |
| 355 | int log_det; |
| 356 | |
| 357 | /* compute some intermediate results: */ |
| 358 | t20 = gfpow[l2 - l0]; /* t20 = r^l2/r^l0 */ |
| 359 | t10 = gfpow[l1 - l0]; /* t10 = r^l1/r^l0 */ |
| 360 | t21 = gfpow[l2 - l1]; /* t21 = r^l2/r^l1 */ |
| 361 | t12 = gfpow[l1 - l2 + 255]; /* t12 = r^l1/r^l2 */ |
| 362 | t01 = gfpow[l0 - l1 + 255]; /* t01 = r^l0/r^l1 */ |
| 363 | t02 = gfpow[l0 - l2 + 255]; /* t02 = r^l0/r^l2 */ |
| 364 | /* Calculate the determinant of matrix A_3^-1 (sometimes |
| 365 | * called the Vandermonde determinant): |
| 366 | */ |
| 367 | det = gfadd(t20, gfadd(t10, gfadd(t21, gfadd(t12, gfadd(t01, t02))))); |
| 368 | if (!det) { |
| 369 | TRACE_FUN(ft_t_any); |
| 370 | TRACE_ABORT(0, ft_t_err, |
| 371 | "Inversion failed (3 CRC errors, >0 CRC failures)"); |
| 372 | } |
| 373 | log_det = 255 - gflog[det]; |
| 374 | |
| 375 | /* Now, calculate all of the coefficients: |
| 376 | */ |
| 377 | Ainv[0][0]= gfmul_exp(gfadd(gfpow[l1], gfpow[l2]), log_det); |
| 378 | Ainv[0][1]= gfmul_exp(gfadd(t21, t12), log_det); |
| 379 | Ainv[0][2]= gfmul_exp(gfadd(gfpow[255 - l1], gfpow[255 - l2]),log_det); |
| 380 | |
| 381 | Ainv[1][0]= gfmul_exp(gfadd(gfpow[l0], gfpow[l2]), log_det); |
| 382 | Ainv[1][1]= gfmul_exp(gfadd(t20, t02), log_det); |
| 383 | Ainv[1][2]= gfmul_exp(gfadd(gfpow[255 - l0], gfpow[255 - l2]),log_det); |
| 384 | |
| 385 | Ainv[2][0]= gfmul_exp(gfadd(gfpow[l0], gfpow[l1]), log_det); |
| 386 | Ainv[2][1]= gfmul_exp(gfadd(t10, t01), log_det); |
| 387 | Ainv[2][2]= gfmul_exp(gfadd(gfpow[255 - l0], gfpow[255 - l1]),log_det); |
| 388 | |
| 389 | return 1; |
| 390 | } |
| 391 | |
| 392 | |
| 393 | static inline int gfinv2(__u8 l0, __u8 l1, Matrix Ainv) |
| 394 | { |
| 395 | __u8 det; |
| 396 | __u8 t1, t2; |
| 397 | int log_det; |
| 398 | |
| 399 | t1 = gfpow[255 - l0]; |
| 400 | t2 = gfpow[255 - l1]; |
| 401 | det = gfadd(t1, t2); |
| 402 | if (!det) { |
| 403 | TRACE_FUN(ft_t_any); |
| 404 | TRACE_ABORT(0, ft_t_err, |
| 405 | "Inversion failed (2 CRC errors, >0 CRC failures)"); |
| 406 | } |
| 407 | log_det = 255 - gflog[det]; |
| 408 | |
| 409 | /* Now, calculate all of the coefficients: |
| 410 | */ |
| 411 | Ainv[0][0] = Ainv[1][0] = gfpow[log_det]; |
| 412 | |
| 413 | Ainv[0][1] = gfmul_exp(t2, log_det); |
| 414 | Ainv[1][1] = gfmul_exp(t1, log_det); |
| 415 | |
| 416 | return 1; |
| 417 | } |
| 418 | |
| 419 | |
| 420 | /* Multiply matrix A by vector S and return result in vector B. M is |
| 421 | * assumed to be of order NxN, S and B of order Nx1. |
| 422 | */ |
| 423 | static inline void gfmat_mul(int n, Matrix A, |
| 424 | __u8 *s, __u8 *b) |
| 425 | { |
| 426 | int i, j; |
| 427 | __u8 dot_prod; |
| 428 | |
| 429 | for (i = 0; i < n; ++i) { |
| 430 | dot_prod = 0; |
| 431 | for (j = 0; j < n; ++j) { |
| 432 | dot_prod = gfadd(dot_prod, gfmul(A[i][j], s[j])); |
| 433 | } |
| 434 | b[i] = dot_prod; |
| 435 | } |
| 436 | } |
| 437 | |
| 438 | |
| 439 | |
| 440 | /* The Reed Solomon ECC codes are computed over the N-th byte of each |
| 441 | * block, where N=SECTOR_SIZE. There are up to 29 blocks of data, and |
| 442 | * 3 blocks of ECC. The blocks are stored contiguously in memory. A |
| 443 | * segment, consequently, is assumed to have at least 4 blocks: one or |
| 444 | * more data blocks plus three ECC blocks. |
| 445 | * |
| 446 | * Notice: In QIC-80 speak, a CRC error is a sector with an incorrect |
| 447 | * CRC. A CRC failure is a sector with incorrect data, but |
| 448 | * a valid CRC. In the error control literature, the former |
| 449 | * is usually called "erasure", the latter "error." |
| 450 | */ |
| 451 | /* Compute the parity bytes for C columns of data, where C is the |
| 452 | * number of bytes that fit into a long integer. We use a linear |
| 453 | * feed-back register to do this. The parity bytes P[0], P[STRIDE], |
| 454 | * P[2*STRIDE] are computed such that: |
| 455 | * |
| 456 | * x^k * p(x) + m(x) = 0 (modulo g(x)) |
| 457 | * |
| 458 | * where k = NBLOCKS, |
| 459 | * p(x) = P[0] + P[STRIDE]*x + P[2*STRIDE]*x^2, and |
| 460 | * m(x) = sum_{i=0}^k m_i*x^i. |
| 461 | * m_i = DATA[i*SECTOR_SIZE] |
| 462 | */ |
| 463 | static inline void set_parity(unsigned long *data, |
| 464 | int nblocks, |
| 465 | unsigned long *p, |
| 466 | int stride) |
| 467 | { |
| 468 | unsigned long p0, p1, p2, t1, t2, *end; |
| 469 | |
| 470 | end = data + nblocks * (FT_SECTOR_SIZE / sizeof(long)); |
| 471 | p0 = p1 = p2 = 0; |
| 472 | while (data < end) { |
| 473 | /* The new parity bytes p0_i, p1_i, p2_i are computed |
| 474 | * from the old values p0_{i-1}, p1_{i-1}, p2_{i-1} |
| 475 | * recursively as: |
| 476 | * |
| 477 | * p0_i = p1_{i-1} + r^105 * (m_{i-1} - p0_{i-1}) |
| 478 | * p1_i = p2_{i-1} + r^105 * (m_{i-1} - p0_{i-1}) |
| 479 | * p2_i = (m_{i-1} - p0_{i-1}) |
| 480 | * |
| 481 | * With the initial condition: p0_0 = p1_0 = p2_0 = 0. |
| 482 | */ |
| 483 | t1 = gfadd_long(*data, p0); |
| 484 | /* |
| 485 | * Multiply each byte in t1 by 0xc0: |
| 486 | */ |
| 487 | if (sizeof(long) == 4) { |
| 488 | t2= (((__u32) gfmul_c0[(__u32)t1 >> 24 & 0xff]) << 24 | |
| 489 | ((__u32) gfmul_c0[(__u32)t1 >> 16 & 0xff]) << 16 | |
| 490 | ((__u32) gfmul_c0[(__u32)t1 >> 8 & 0xff]) << 8 | |
| 491 | ((__u32) gfmul_c0[(__u32)t1 >> 0 & 0xff]) << 0); |
| 492 | } else if (sizeof(long) == 8) { |
| 493 | t2= (((__u64) gfmul_c0[(__u64)t1 >> 56 & 0xff]) << 56 | |
| 494 | ((__u64) gfmul_c0[(__u64)t1 >> 48 & 0xff]) << 48 | |
| 495 | ((__u64) gfmul_c0[(__u64)t1 >> 40 & 0xff]) << 40 | |
| 496 | ((__u64) gfmul_c0[(__u64)t1 >> 32 & 0xff]) << 32 | |
| 497 | ((__u64) gfmul_c0[(__u64)t1 >> 24 & 0xff]) << 24 | |
| 498 | ((__u64) gfmul_c0[(__u64)t1 >> 16 & 0xff]) << 16 | |
| 499 | ((__u64) gfmul_c0[(__u64)t1 >> 8 & 0xff]) << 8 | |
| 500 | ((__u64) gfmul_c0[(__u64)t1 >> 0 & 0xff]) << 0); |
| 501 | } else { |
| 502 | TRACE_FUN(ft_t_any); |
| 503 | TRACE(ft_t_err, "Error: long is of size %d", |
| 504 | (int) sizeof(long)); |
| 505 | TRACE_EXIT; |
| 506 | } |
| 507 | p0 = gfadd_long(t2, p1); |
| 508 | p1 = gfadd_long(t2, p2); |
| 509 | p2 = t1; |
| 510 | data += FT_SECTOR_SIZE / sizeof(long); |
| 511 | } |
| 512 | *p = p0; |
| 513 | p += stride; |
| 514 | *p = p1; |
| 515 | p += stride; |
| 516 | *p = p2; |
| 517 | return; |
| 518 | } |
| 519 | |
| 520 | |
| 521 | /* Compute the 3 syndrome values. DATA should point to the first byte |
| 522 | * of the column for which the syndromes are desired. The syndromes |
| 523 | * are computed over the first NBLOCKS of rows. The three bytes will |
| 524 | * be placed in S[0], S[1], and S[2]. |
| 525 | * |
| 526 | * S[i] is the value of the "message" polynomial m(x) evaluated at the |
| 527 | * i-th root of the generator polynomial g(x). |
| 528 | * |
| 529 | * As g(x)=(x-r^-1)(x-1)(x-r^1) we evaluate the message polynomial at |
| 530 | * x=r^-1 to get S[0], at x=r^0=1 to get S[1], and at x=r to get S[2]. |
| 531 | * This could be done directly and efficiently via the Horner scheme. |
| 532 | * However, it would require multiplication tables for the factors |
| 533 | * r^-1 (0xc3) and r (0x02). The following scheme does not require |
| 534 | * any multiplication tables beyond what's needed for set_parity() |
| 535 | * anyway and is slightly faster if there are no errors and slightly |
| 536 | * slower if there are errors. The latter is hopefully the infrequent |
| 537 | * case. |
| 538 | * |
| 539 | * To understand the alternative algorithm, notice that set_parity(m, |
| 540 | * k, p) computes parity bytes such that: |
| 541 | * |
| 542 | * x^k * p(x) = m(x) (modulo g(x)). |
| 543 | * |
| 544 | * That is, to evaluate m(r^m), where r^m is a root of g(x), we can |
| 545 | * simply evaluate (r^m)^k*p(r^m). Also, notice that p is 0 if and |
| 546 | * only if s is zero. That is, if all parity bytes are 0, we know |
| 547 | * there is no error in the data and consequently there is no need to |
| 548 | * compute s(x) at all! In all other cases, we compute s(x) from p(x) |
| 549 | * by evaluating (r^m)^k*p(r^m) for m=-1, m=0, and m=1. The p(x) |
| 550 | * polynomial is evaluated via the Horner scheme. |
| 551 | */ |
| 552 | static int compute_syndromes(unsigned long *data, int nblocks, unsigned long *s) |
| 553 | { |
| 554 | unsigned long p[3]; |
| 555 | |
| 556 | set_parity(data, nblocks, p, 1); |
| 557 | if (p[0] | p[1] | p[2]) { |
| 558 | /* Some of the checked columns do not have a zero |
| 559 | * syndrome. For simplicity, we compute the syndromes |
| 560 | * for all columns that we have computed the |
| 561 | * remainders for. |
| 562 | */ |
| 563 | s[0] = gfmul_exp_long( |
| 564 | gfadd_long(p[0], |
| 565 | gfmul_exp_long( |
| 566 | gfadd_long(p[1], |
| 567 | gfmul_exp_long(p[2], -1)), |
| 568 | -1)), |
| 569 | -nblocks); |
| 570 | s[1] = gfadd_long(gfadd_long(p[2], p[1]), p[0]); |
| 571 | s[2] = gfmul_exp_long( |
| 572 | gfadd_long(p[0], |
| 573 | gfmul_exp_long( |
| 574 | gfadd_long(p[1], |
| 575 | gfmul_exp_long(p[2], 1)), |
| 576 | 1)), |
| 577 | nblocks); |
| 578 | return 0; |
| 579 | } else { |
| 580 | return 1; |
| 581 | } |
| 582 | } |
| 583 | |
| 584 | |
| 585 | /* Correct the block in the column pointed to by DATA. There are NBAD |
| 586 | * CRC errors and their indices are in BAD_LOC[0], up to |
| 587 | * BAD_LOC[NBAD-1]. If NBAD>1, Ainv holds the inverse of the matrix |
| 588 | * of the linear system that needs to be solved to determine the error |
| 589 | * magnitudes. S[0], S[1], and S[2] are the syndrome values. If row |
| 590 | * j gets corrected, then bit j will be set in CORRECTION_MAP. |
| 591 | */ |
| 592 | static inline int correct_block(__u8 *data, int nblocks, |
| 593 | int nbad, int *bad_loc, Matrix Ainv, |
| 594 | __u8 *s, |
| 595 | SectorMap * correction_map) |
| 596 | { |
| 597 | int ncorrected = 0; |
| 598 | int i; |
| 599 | __u8 t1, t2; |
| 600 | __u8 c0, c1, c2; /* check bytes */ |
| 601 | __u8 error_mag[3], log_error_mag; |
| 602 | __u8 *dp, l, e; |
| 603 | TRACE_FUN(ft_t_any); |
| 604 | |
| 605 | switch (nbad) { |
| 606 | case 0: |
| 607 | /* might have a CRC failure: */ |
| 608 | if (s[0] == 0) { |
| 609 | /* more than one error */ |
| 610 | TRACE_ABORT(-1, ft_t_err, |
| 611 | "ECC failed (0 CRC errors, >1 CRC failures)"); |
| 612 | } |
| 613 | t1 = gfdiv(s[1], s[0]); |
| 614 | if ((bad_loc[nbad++] = gflog[t1]) >= nblocks) { |
| 615 | TRACE(ft_t_err, |
| 616 | "ECC failed (0 CRC errors, >1 CRC failures)"); |
| 617 | TRACE_ABORT(-1, ft_t_err, |
| 618 | "attempt to correct data at %d", bad_loc[0]); |
| 619 | } |
| 620 | error_mag[0] = s[1]; |
| 621 | break; |
| 622 | case 1: |
| 623 | t1 = gfadd(gfmul_exp(s[1], bad_loc[0]), s[2]); |
| 624 | t2 = gfadd(gfmul_exp(s[0], bad_loc[0]), s[1]); |
| 625 | if (t1 == 0 && t2 == 0) { |
| 626 | /* one erasure, no error: */ |
| 627 | Ainv[0][0] = gfpow[bad_loc[0]]; |
| 628 | } else if (t1 == 0 || t2 == 0) { |
| 629 | /* one erasure and more than one error: */ |
| 630 | TRACE_ABORT(-1, ft_t_err, |
| 631 | "ECC failed (1 erasure, >1 error)"); |
| 632 | } else { |
| 633 | /* one erasure, one error: */ |
| 634 | if ((bad_loc[nbad++] = gflog[gfdiv(t1, t2)]) |
| 635 | >= nblocks) { |
| 636 | TRACE(ft_t_err, "ECC failed " |
| 637 | "(1 CRC errors, >1 CRC failures)"); |
| 638 | TRACE_ABORT(-1, ft_t_err, |
| 639 | "attempt to correct data at %d", |
| 640 | bad_loc[1]); |
| 641 | } |
| 642 | if (!gfinv2(bad_loc[0], bad_loc[1], Ainv)) { |
| 643 | /* inversion failed---must have more |
| 644 | * than one error |
| 645 | */ |
| 646 | TRACE_EXIT -1; |
| 647 | } |
| 648 | } |
| 649 | /* FALL THROUGH TO ERROR MAGNITUDE COMPUTATION: |
| 650 | */ |
| 651 | case 2: |
| 652 | case 3: |
| 653 | /* compute error magnitudes: */ |
| 654 | gfmat_mul(nbad, Ainv, s, error_mag); |
| 655 | break; |
| 656 | |
| 657 | default: |
| 658 | TRACE_ABORT(-1, ft_t_err, |
| 659 | "Internal Error: number of CRC errors > 3"); |
| 660 | } |
| 661 | |
| 662 | /* Perform correction by adding ERROR_MAG[i] to the byte at |
| 663 | * offset BAD_LOC[i]. Also add the value of the computed |
| 664 | * error polynomial to the syndrome values. If the correction |
| 665 | * was successful, the resulting check bytes should be zero |
| 666 | * (i.e., the corrected data is a valid code word). |
| 667 | */ |
| 668 | c0 = s[0]; |
| 669 | c1 = s[1]; |
| 670 | c2 = s[2]; |
| 671 | for (i = 0; i < nbad; ++i) { |
| 672 | e = error_mag[i]; |
| 673 | if (e) { |
| 674 | /* correct the byte at offset L by magnitude E: */ |
| 675 | l = bad_loc[i]; |
| 676 | dp = &data[l * FT_SECTOR_SIZE]; |
| 677 | *dp = gfadd(*dp, e); |
| 678 | *correction_map |= 1 << l; |
| 679 | ++ncorrected; |
| 680 | |
| 681 | log_error_mag = gflog[e]; |
| 682 | c0 = gfadd(c0, gfpow[mod255(log_error_mag - l)]); |
| 683 | c1 = gfadd(c1, e); |
| 684 | c2 = gfadd(c2, gfpow[mod255(log_error_mag + l)]); |
| 685 | } |
| 686 | } |
| 687 | if (c0 || c1 || c2) { |
| 688 | TRACE_ABORT(-1, ft_t_err, |
| 689 | "ECC self-check failed, too many errors"); |
| 690 | } |
| 691 | TRACE_EXIT ncorrected; |
| 692 | } |
| 693 | |
| 694 | |
| 695 | #if defined(ECC_SANITY_CHECK) || defined(ECC_PARANOID) |
| 696 | |
| 697 | /* Perform a sanity check on the computed parity bytes: |
| 698 | */ |
| 699 | static int sanity_check(unsigned long *data, int nblocks) |
| 700 | { |
| 701 | TRACE_FUN(ft_t_any); |
| 702 | unsigned long s[3]; |
| 703 | |
| 704 | if (!compute_syndromes(data, nblocks, s)) { |
| 705 | TRACE_ABORT(0, ft_bug, |
| 706 | "Internal Error: syndrome self-check failed"); |
| 707 | } |
| 708 | TRACE_EXIT 1; |
| 709 | } |
| 710 | |
| 711 | #endif /* defined(ECC_SANITY_CHECK) || defined(ECC_PARANOID) */ |
| 712 | |
| 713 | /* Compute the parity for an entire segment of data. |
| 714 | */ |
| 715 | int ftape_ecc_set_segment_parity(struct memory_segment *mseg) |
| 716 | { |
| 717 | int i; |
| 718 | __u8 *parity_bytes; |
| 719 | |
| 720 | parity_bytes = &mseg->data[(mseg->blocks - 3) * FT_SECTOR_SIZE]; |
| 721 | for (i = 0; i < FT_SECTOR_SIZE; i += sizeof(long)) { |
| 722 | set_parity((unsigned long *) &mseg->data[i], mseg->blocks - 3, |
| 723 | (unsigned long *) &parity_bytes[i], |
| 724 | FT_SECTOR_SIZE / sizeof(long)); |
| 725 | #ifdef ECC_PARANOID |
| 726 | if (!sanity_check((unsigned long *) &mseg->data[i], |
| 727 | mseg->blocks)) { |
| 728 | return -1; |
| 729 | } |
| 730 | #endif /* ECC_PARANOID */ |
| 731 | } |
| 732 | return 0; |
| 733 | } |
| 734 | |
| 735 | |
| 736 | /* Checks and corrects (if possible) the segment MSEG. Returns one of |
| 737 | * ECC_OK, ECC_CORRECTED, and ECC_FAILED. |
| 738 | */ |
| 739 | int ftape_ecc_correct_data(struct memory_segment *mseg) |
| 740 | { |
| 741 | int col, i, result; |
| 742 | int ncorrected = 0; |
| 743 | int nerasures = 0; /* # of erasures (CRC errors) */ |
| 744 | int erasure_loc[3]; /* erasure locations */ |
| 745 | unsigned long ss[3]; |
| 746 | __u8 s[3]; |
| 747 | Matrix Ainv; |
| 748 | TRACE_FUN(ft_t_flow); |
| 749 | |
| 750 | mseg->corrected = 0; |
| 751 | |
| 752 | /* find first column that has non-zero syndromes: */ |
| 753 | for (col = 0; col < FT_SECTOR_SIZE; col += sizeof(long)) { |
| 754 | if (!compute_syndromes((unsigned long *) &mseg->data[col], |
| 755 | mseg->blocks, ss)) { |
| 756 | /* something is wrong---have to fix things */ |
| 757 | break; |
| 758 | } |
| 759 | } |
| 760 | if (col >= FT_SECTOR_SIZE) { |
| 761 | /* all syndromes are ok, therefore nothing to correct */ |
| 762 | TRACE_EXIT ECC_OK; |
| 763 | } |
| 764 | /* count the number of CRC errors if there were any: */ |
| 765 | if (mseg->read_bad) { |
| 766 | for (i = 0; i < mseg->blocks; i++) { |
| 767 | if (BAD_CHECK(mseg->read_bad, i)) { |
| 768 | if (nerasures >= 3) { |
| 769 | /* this is too much for ECC */ |
| 770 | TRACE_ABORT(ECC_FAILED, ft_t_err, |
| 771 | "ECC failed (>3 CRC errors)"); |
| 772 | } /* if */ |
| 773 | erasure_loc[nerasures++] = i; |
| 774 | } |
| 775 | } |
| 776 | } |
| 777 | /* |
| 778 | * If there are at least 2 CRC errors, determine inverse of matrix |
| 779 | * of linear system to be solved: |
| 780 | */ |
| 781 | switch (nerasures) { |
| 782 | case 2: |
| 783 | if (!gfinv2(erasure_loc[0], erasure_loc[1], Ainv)) { |
| 784 | TRACE_EXIT ECC_FAILED; |
| 785 | } |
| 786 | break; |
| 787 | case 3: |
| 788 | if (!gfinv3(erasure_loc[0], erasure_loc[1], |
| 789 | erasure_loc[2], Ainv)) { |
| 790 | TRACE_EXIT ECC_FAILED; |
| 791 | } |
| 792 | break; |
| 793 | default: |
| 794 | /* this is not an error condition... */ |
| 795 | break; |
| 796 | } |
| 797 | |
| 798 | do { |
| 799 | for (i = 0; i < sizeof(long); ++i) { |
| 800 | s[0] = ss[0]; |
| 801 | s[1] = ss[1]; |
| 802 | s[2] = ss[2]; |
| 803 | if (s[0] | s[1] | s[2]) { |
| 804 | #ifdef BIG_ENDIAN |
| 805 | result = correct_block( |
| 806 | &mseg->data[col + sizeof(long) - 1 - i], |
| 807 | mseg->blocks, |
| 808 | nerasures, |
| 809 | erasure_loc, |
| 810 | Ainv, |
| 811 | s, |
| 812 | &mseg->corrected); |
| 813 | #else |
| 814 | result = correct_block(&mseg->data[col + i], |
| 815 | mseg->blocks, |
| 816 | nerasures, |
| 817 | erasure_loc, |
| 818 | Ainv, |
| 819 | s, |
| 820 | &mseg->corrected); |
| 821 | #endif |
| 822 | if (result < 0) { |
| 823 | TRACE_EXIT ECC_FAILED; |
| 824 | } |
| 825 | ncorrected += result; |
| 826 | } |
| 827 | ss[0] >>= 8; |
| 828 | ss[1] >>= 8; |
| 829 | ss[2] >>= 8; |
| 830 | } |
| 831 | |
| 832 | #ifdef ECC_SANITY_CHECK |
| 833 | if (!sanity_check((unsigned long *) &mseg->data[col], |
| 834 | mseg->blocks)) { |
| 835 | TRACE_EXIT ECC_FAILED; |
| 836 | } |
| 837 | #endif /* ECC_SANITY_CHECK */ |
| 838 | |
| 839 | /* find next column with non-zero syndromes: */ |
| 840 | while ((col += sizeof(long)) < FT_SECTOR_SIZE) { |
| 841 | if (!compute_syndromes((unsigned long *) |
| 842 | &mseg->data[col], mseg->blocks, ss)) { |
| 843 | /* something is wrong---have to fix things */ |
| 844 | break; |
| 845 | } |
| 846 | } |
| 847 | } while (col < FT_SECTOR_SIZE); |
| 848 | if (ncorrected && nerasures == 0) { |
| 849 | TRACE(ft_t_warn, "block contained error not caught by CRC"); |
| 850 | } |
| 851 | TRACE((ncorrected > 0) ? ft_t_noise : ft_t_any, "number of corrections: %d", ncorrected); |
| 852 | TRACE_EXIT ncorrected ? ECC_CORRECTED : ECC_OK; |
| 853 | } |