reed_solomon.c 11.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2010  Free Software Foundation, Inc.
 *
 *  GRUB 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef TEST
#include <stdio.h>
21 22
#include <string.h>
#include <stdlib.h>
23 24 25 26 27
#define xmalloc malloc
#define grub_memset memset
#define grub_memcpy memcpy
#endif

28 29 30 31
#ifndef STANDALONE
#include <assert.h>
#endif

32 33
#ifndef STANDALONE
#ifdef TEST
34 35 36 37 38 39 40 41
typedef unsigned int grub_size_t;
typedef unsigned char grub_uint8_t;
#else
#include <grub/types.h>
#include <grub/reed_solomon.h>
#include <grub/util/misc.h>
#include <grub/misc.h>
#endif
42 43
#endif

44 45 46
#define SECTOR_SIZE 512
#define MAX_BLOCK_SIZE (200 * SECTOR_SIZE)

47 48 49 50 51 52
#ifdef STANDALONE
#ifdef TEST
typedef unsigned int grub_size_t;
typedef unsigned char grub_uint8_t;
#else
#include <grub/types.h>
53
#include <grub/misc.h>
54
#endif
55
#ifdef __i386__
56 57 58
#define REED_SOLOMON_ATTRIBUTE  __attribute__ ((regparm(3)))
#else
#define REED_SOLOMON_ATTRIBUTE
59
#endif
60 61 62 63 64
void
grub_reed_solomon_recover (void *ptr_, grub_size_t s, grub_size_t rs)
  REED_SOLOMON_ATTRIBUTE;
#else
#define REED_SOLOMON_ATTRIBUTE
65
#endif
66 67 68

#define GF_SIZE 8
typedef grub_uint8_t gf_single_t;
69 70
#define GF_POLYNOMIAL 0x1d
#define GF_INVERT2 0x8e
71
#if defined (STANDALONE) && !defined (TEST)
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88

#ifdef __APPLE__
#define ATTRIBUTE_TEXT __attribute__ ((section("_text,_text")))
#else
#define ATTRIBUTE_TEXT __attribute__ ((section(".text")))
#endif

static gf_single_t * const gf_powx ATTRIBUTE_TEXT = (void *) 0x100000;
static gf_single_t * const gf_powx_inv ATTRIBUTE_TEXT = (void *) 0x100200;
static int *const chosenstat ATTRIBUTE_TEXT = (void *) 0x100300;
static gf_single_t *const sigma ATTRIBUTE_TEXT = (void *) 0x100700;
static gf_single_t *const errpot ATTRIBUTE_TEXT = (void *) 0x100800;
static int *const errpos ATTRIBUTE_TEXT = (void *) 0x100900;
static gf_single_t *const sy ATTRIBUTE_TEXT = (void *) 0x100d00;
static gf_single_t *const mstat ATTRIBUTE_TEXT = (void *) 0x100e00;
static gf_single_t *const errvals ATTRIBUTE_TEXT = (void *) 0x100f00;
static gf_single_t *const eqstat ATTRIBUTE_TEXT = (void *) 0x101000;
89
/* Next available address: (void *) 0x112000.  */
90
#else
91

92 93
static gf_single_t gf_powx[255 * 2];
static gf_single_t gf_powx_inv[256];
94 95 96 97 98 99 100 101
static int chosenstat[256];
static gf_single_t sigma[256];
static gf_single_t errpot[256];
static int errpos[256];
static gf_single_t sy[256];
static gf_single_t mstat[256];
static gf_single_t errvals[256];
static gf_single_t eqstat[65536 + 256];
102
#endif
103 104

static gf_single_t
105
gf_mul (gf_single_t a, gf_single_t b)
106
{
107 108 109
  if (a == 0 || b == 0)
    return 0;
  return gf_powx[(int) gf_powx_inv[a] + (int) gf_powx_inv[b]];
110 111
}

112 113
static inline gf_single_t
gf_invert (gf_single_t a)
114
{
115
  return gf_powx[255 - (int) gf_powx_inv[a]];
116 117
}

118
static void
119
init_powx (void)
120
{
121 122 123
  int i;
  grub_uint8_t cur = 1;

124
  gf_powx_inv[0] = 0;
125
  for (i = 0; i < 255; i++)
126
    {
127 128 129 130 131 132 133
      gf_powx[i] = cur;
      gf_powx[i + 255] = cur;
      gf_powx_inv[cur] = i;
      if (cur & (1ULL << (GF_SIZE - 1)))
	cur = (cur << 1) ^ GF_POLYNOMIAL;
      else
	cur <<= 1;
134 135 136 137
    }
}

static gf_single_t
138
pol_evaluate (gf_single_t *pol, grub_size_t degree, int log_x)
139 140
{
  int i;
141
  gf_single_t s = 0;
142
  int log_xn = 0;
143 144
  for (i = degree; i >= 0; i--)
    {
145 146 147 148 149
      if (pol[i])
	s ^= gf_powx[(int) gf_powx_inv[pol[i]] + log_xn];
      log_xn += log_x;
      if (log_xn >= ((1 << GF_SIZE) - 1))
	log_xn -= ((1 << GF_SIZE) - 1);
150 151 152 153
    }
  return s;
}

154
#if !defined (STANDALONE)
155 156 157
static void
rs_encode (gf_single_t *data, grub_size_t s, grub_size_t rs)
{
158
  gf_single_t *rs_polynomial;
159 160 161 162 163 164 165 166 167 168 169 170
  int i, j;
  gf_single_t *m;
  m = xmalloc ((s + rs) * sizeof (gf_single_t));
  grub_memcpy (m, data, s * sizeof (gf_single_t));
  grub_memset (m + s, 0, rs * sizeof (gf_single_t));
  rs_polynomial = xmalloc ((rs + 1) * sizeof (gf_single_t));
  grub_memset (rs_polynomial, 0, (rs + 1) * sizeof (gf_single_t));
  rs_polynomial[rs] = 1;
  /* Multiply with X - a^r */
  for (j = 0; j < rs; j++)
    {
      for (i = 0; i < rs; i++)
171 172 173 174 175 176 177
	if (rs_polynomial[i])
	  rs_polynomial[i] = (rs_polynomial[i + 1]
			      ^ gf_powx[j + (int) gf_powx_inv[rs_polynomial[i]]]);
	else
	  rs_polynomial[i] = rs_polynomial[i + 1];
      if (rs_polynomial[rs])
	rs_polynomial[rs] = gf_powx[j + (int) gf_powx_inv[rs_polynomial[rs]]];
178 179 180 181 182 183 184 185 186 187 188 189
    }
  for (j = 0; j < s; j++)
    if (m[j])
      {
	gf_single_t f = m[j];
	for (i = 0; i <= rs; i++)
	  m[i+j] ^= gf_mul (rs_polynomial[i], f);
      }
  free (rs_polynomial);
  grub_memcpy (data + s, m + s, rs * sizeof (gf_single_t));
  free (m);
}
190
#endif
191

192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
static void
gauss_eliminate (gf_single_t *eq, int n, int m, int *chosen)
{
  int i, j;

  for (i = 0 ; i < n; i++)
    {
      int nzidx;
      int k;
      gf_single_t r;
      for (nzidx = 0; nzidx < m && (eq[i * (m + 1) + nzidx] == 0);
	   nzidx++);
      if (nzidx == m)
	continue;
      chosen[i] = nzidx;
207
      r = gf_invert (eq[i * (m + 1) + nzidx]);
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
      for (j = 0; j < m + 1; j++)
	eq[i * (m + 1) + j] = gf_mul (eq[i * (m + 1) + j], r);
      for (j = i + 1; j < n; j++)
	{
	  gf_single_t rr = eq[j * (m + 1) + nzidx];
	  for (k = 0; k < m + 1; k++)
	    eq[j * (m + 1) + k] ^= gf_mul (eq[i * (m + 1) + k], rr);
	}
    }
}

static void
gauss_solve (gf_single_t *eq, int n, int m, gf_single_t *sol)
{
  int i, j;

224
  for (i = 0; i < n; i++)
225
    chosenstat[i] = -1;
226 227
  for (i = 0; i < m; i++)
    sol[i] = 0;
228
  gauss_eliminate (eq, n, m, chosenstat);
229 230 231
  for (i = n - 1; i >= 0; i--)
    {
      gf_single_t s = 0;
232
      if (chosenstat[i] == -1)
233 234 235 236
	continue;
      for (j = 0; j < m; j++)
	s ^= gf_mul (eq[i * (m + 1) + j], sol[j]);
      s ^= eq[i * (m + 1) + m];
237
      sol[chosenstat[i]] = s;
238 239 240
    }
}

241
static void
242
rs_recover (gf_single_t *mm, grub_size_t s, grub_size_t rs)
243 244 245 246 247
{
  grub_size_t rs2 = rs / 2;
  int errnum = 0;
  int i, j;

248 249
  for (i = 0; i < (int) rs; i++)
    sy[i] = pol_evaluate (mm, s + rs - 1, i);
250

251 252 253 254 255 256
  for (i = 0; i < (int) rs; i++)
    if (sy[i] != 0)
      break;

  /* No error detected.  */
  if (i == (int) rs)
257
    return;
258

259 260
  {

261 262
    for (i = 0; i < (int) rs2; i++)
      for (j = 0; j < (int) rs2 + 1; j++)
263
	eqstat[i * (rs2 + 1) + j] = sy[i+j];
264

265 266
    for (i = 0; i < (int) rs2; i++)
      sigma[i] = 0;
267

268
    gauss_solve (eqstat, rs2, rs2, sigma);
269 270
  } 

271 272
  for (i = 0; i < (int) (rs + s); i++)
    if (pol_evaluate (sigma, rs2 - 1, 255 - i) == gf_powx[i])
273
      {
274 275
	errpot[errnum] = gf_powx[i];
	errpos[errnum++] = s + rs - i - 1;
276 277 278
      }
  {
    for (j = 0; j < errnum; j++)
279 280
      eqstat[j] = 1;
    eqstat[errnum] = sy[0];
281
    for (i = 1; i < (int) rs; i++)
282
      {
283
	for (j = 0; j < (int) errnum; j++)
284 285 286 287
	  eqstat[(errnum + 1) * i + j] = gf_mul (errpot[j],
						 eqstat[(errnum + 1) * (i - 1)
							+ j]);
	eqstat[(errnum + 1) * i + errnum] = sy[i];
288
      }
289

290
    gauss_solve (eqstat, rs, errnum, errvals);
291 292

    for (i = 0; i < (int) errnum; i++)
293
      mm[errpos[i]] ^= errvals[i];
294 295 296 297 298 299 300
  }
}

static void
decode_block (gf_single_t *ptr, grub_size_t s,
	      gf_single_t *rptr, grub_size_t rs)
{
301
  int i, j;
302 303 304 305 306
  for (i = 0; i < SECTOR_SIZE; i++)
    {
      grub_size_t ds = (s + SECTOR_SIZE - 1 - i) / SECTOR_SIZE;
      grub_size_t rr = (rs + SECTOR_SIZE - 1 - i) / SECTOR_SIZE;

307 308 309 310
      /* Nothing to do.  */
      if (!ds || !rr)
	continue;

311
      for (j = 0; j < (int) ds; j++)
312
	mstat[j] = ptr[SECTOR_SIZE * j + i];
313
      for (j = 0; j < (int) rr; j++)
314
	mstat[j + ds] = rptr[SECTOR_SIZE * j + i];
315

316
      rs_recover (mstat, ds, rr);
317

318
      for (j = 0; j < (int) ds; j++)
319
	ptr[SECTOR_SIZE * j + i] = mstat[j];
320 321 322
    }
}

323
#if !defined (STANDALONE)
324 325 326 327 328 329 330 331 332
static void
encode_block (gf_single_t *ptr, grub_size_t s,
	      gf_single_t *rptr, grub_size_t rs)
{
  int i, j;
  for (i = 0; i < SECTOR_SIZE; i++)
    {
      grub_size_t ds = (s + SECTOR_SIZE - 1 - i) / SECTOR_SIZE;
      grub_size_t rr = (rs + SECTOR_SIZE - 1 - i) / SECTOR_SIZE;
333 334 335 336 337 338
      gf_single_t *m;

      if (!ds || !rr)
	continue;

      m = xmalloc (ds + rr);
339 340 341 342 343
      for (j = 0; j < ds; j++)
	m[j] = ptr[SECTOR_SIZE * j + i];
      rs_encode (m, ds, rr);
      for (j = 0; j < rr; j++)      
	rptr[SECTOR_SIZE * j + i] = m[j + ds];
344
      free (m);
345 346
    }
}
347
#endif
348

349
#if !defined (STANDALONE)
350 351 352 353 354 355 356 357
void
grub_reed_solomon_add_redundancy (void *buffer, grub_size_t data_size,
				  grub_size_t redundancy)
{
  grub_size_t s = data_size;
  grub_size_t rs = redundancy;
  gf_single_t *ptr = buffer;
  gf_single_t *rptr = ptr + s;
358 359 360 361
  void *tmp;

  tmp = xmalloc (data_size);
  grub_memcpy (tmp, buffer, data_size);
362

363 364
  /* Nothing to do.  */
  if (!rs)
365
    goto exit;
366

367 368
  init_powx ();

369 370 371 372 373 374 375 376 377
  while (s > 0)
    {
      grub_size_t tt;
      grub_size_t cs, crs;
      cs = s;
      crs = rs;
      tt = cs + crs;
      if (tt > MAX_BLOCK_SIZE)
	{
378 379
	  cs = ((cs * (MAX_BLOCK_SIZE / 512)) / tt) * 512;
	  crs = ((crs * (MAX_BLOCK_SIZE / 512)) / tt) * 512;
380 381 382 383 384 385 386
	}
      encode_block (ptr, cs, rptr, crs);
      ptr += cs;
      rptr += crs;
      s -= cs;
      rs -= crs;
    }
387

388
#ifndef TEST
389
  assert (grub_memcmp (tmp, buffer, data_size) == 0);
390
#endif
391
exit:
392
  free (tmp);
393
}
394
#endif
395

396
void REED_SOLOMON_ATTRIBUTE
397
grub_reed_solomon_recover (void *ptr_, grub_size_t s, grub_size_t rs)
398
{
399
  gf_single_t *ptr = ptr_;
400
  gf_single_t *rptr = ptr + s;
401
  grub_uint8_t *cptr;
402

403 404 405 406
  /* Nothing to do.  */
  if (!rs)
    return;

407 408 409 410 411 412
  for (cptr = rptr + rs - 1; cptr >= rptr; cptr--)
    if (*cptr)
      break;
  if (rptr + rs - 1 - cptr > (grub_ssize_t) rs / 2)
    return;

413
  init_powx ();
414

415 416 417 418 419 420 421 422 423
  while (s > 0)
    {
      grub_size_t tt;
      grub_size_t cs, crs;
      cs = s;
      crs = rs;
      tt = cs + crs;
      if (tt > MAX_BLOCK_SIZE)
	{
424 425
	  cs = ((cs * (MAX_BLOCK_SIZE / 512)) / tt) * 512;
	  crs = ((crs * (MAX_BLOCK_SIZE / 512)) / tt) * 512;
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
	}
      decode_block (ptr, cs, rptr, crs);
      ptr += cs;
      rptr += crs;
      s -= cs;
      rs -= crs;
    }
}

#ifdef TEST
int
main (int argc, char **argv)
{
  FILE *in, *out;
  grub_size_t s, rs;
  char *buf;
442

443 444 445
  grub_memset (gf_powx, 0xee, sizeof (gf_powx));
  grub_memset (gf_powx_inv, 0xdd, sizeof (gf_powx_inv));

446
#ifndef STANDALONE
447
  init_powx ();
448
#endif
449

450
#ifndef STANDALONE
451
  in = grub_util_fopen ("tst.bin", "rb");
452 453 454 455 456
  if (!in)
    return 1;
  fseek (in, 0, SEEK_END);
  s = ftell (in);
  fseek (in, 0, SEEK_SET);
457
  rs = 0x7007;
458 459
  buf = xmalloc (s + rs + SECTOR_SIZE);
  fread (buf, 1, s, in);
460
  fclose (in);
461 462 463

  grub_reed_solomon_add_redundancy (buf, s, rs);

464
  out = grub_util_fopen ("tst_rs.bin", "wb");
465 466
  fwrite (buf, 1, s + rs, out);
  fclose (out);
467
#else
468
  out = grub_util_fopen ("tst_rs.bin", "rb");
469 470 471 472 473 474 475 476 477 478
  fseek (out, 0, SEEK_END);
  s = ftell (out);
  fseek (out, 0, SEEK_SET);
  rs = 0x7007;
  s -= rs;

  buf = xmalloc (s + rs + SECTOR_SIZE);
  fread (buf, 1, s + rs, out);
  fclose (out);  
#endif
479
#if 1
480
  grub_memset (buf + 512 * 15, 0, 512);
481
#endif
482

483
  out = grub_util_fopen ("tst_dam.bin", "wb");
484 485 486 487
  fwrite (buf, 1, s + rs, out);
  fclose (out);
  grub_reed_solomon_recover (buf, s, rs);

488
  out = grub_util_fopen ("tst_rec.bin", "wb");
489 490 491 492 493 494
  fwrite (buf, 1, s, out);
  fclose (out);

  return 0;
}
#endif