Blame view

lib/reed_solomon/decode_rs.c 6.8 KB
03ead8427   Thomas Gleixner   [LIB] reed_solomo...
1
  /*
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
2
3
4
5
   * lib/reed_solomon/decode_rs.c
   *
   * Overview:
   *   Generic Reed Solomon encoder / decoder library
03ead8427   Thomas Gleixner   [LIB] reed_solomo...
6
   *
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
7
8
9
10
11
   * Copyright 2002, Phil Karn, KA9Q
   * May be used under the terms of the GNU General Public License (GPL)
   *
   * Adaption to the kernel by Thomas Gleixner (tglx@linutronix.de)
   *
03ead8427   Thomas Gleixner   [LIB] reed_solomo...
12
   * $Id: decode_rs.c,v 1.7 2005/11/07 11:14:59 gleixner Exp $
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
13
14
   *
   */
03ead8427   Thomas Gleixner   [LIB] reed_solomo...
15
  /* Generic data width independent code which is included by the
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
16
17
   * wrappers.
   */
03ead8427   Thomas Gleixner   [LIB] reed_solomo...
18
  {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
  	int deg_lambda, el, deg_omega;
  	int i, j, r, k, pad;
  	int nn = rs->nn;
  	int nroots = rs->nroots;
  	int fcr = rs->fcr;
  	int prim = rs->prim;
  	int iprim = rs->iprim;
  	uint16_t *alpha_to = rs->alpha_to;
  	uint16_t *index_of = rs->index_of;
  	uint16_t u, q, tmp, num1, num2, den, discr_r, syn_error;
  	/* Err+Eras Locator poly and syndrome poly The maximum value
  	 * of nroots is 8. So the necessary stack size will be about
  	 * 220 bytes max.
  	 */
  	uint16_t lambda[nroots + 1], syn[nroots];
  	uint16_t b[nroots + 1], t[nroots + 1], omega[nroots + 1];
  	uint16_t root[nroots], reg[nroots + 1], loc[nroots];
  	int count = 0;
  	uint16_t msk = (uint16_t) rs->nn;
  
  	/* Check length parameter for validity */
  	pad = nn - nroots - len;
1dd7fdb16   Jörn Engel   [RSLIB] BUG() whe...
41
  	BUG_ON(pad < 0 || pad >= nn);
03ead8427   Thomas Gleixner   [LIB] reed_solomo...
42

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
43
  	/* Does the caller provide the syndrome ? */
03ead8427   Thomas Gleixner   [LIB] reed_solomo...
44
  	if (s != NULL)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
45
46
47
48
49
50
51
52
53
54
  		goto decode;
  
  	/* form the syndromes; i.e., evaluate data(x) at roots of
  	 * g(x) */
  	for (i = 0; i < nroots; i++)
  		syn[i] = (((uint16_t) data[0]) ^ invmsk) & msk;
  
  	for (j = 1; j < len; j++) {
  		for (i = 0; i < nroots; i++) {
  			if (syn[i] == 0) {
03ead8427   Thomas Gleixner   [LIB] reed_solomo...
55
  				syn[i] = (((uint16_t) data[j]) ^
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
56
57
58
  					  invmsk) & msk;
  			} else {
  				syn[i] = ((((uint16_t) data[j]) ^
03ead8427   Thomas Gleixner   [LIB] reed_solomo...
59
  					   invmsk) & msk) ^
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
60
61
62
63
64
65
66
67
68
69
70
  					alpha_to[rs_modnn(rs, index_of[syn[i]] +
  						       (fcr + i) * prim)];
  			}
  		}
  	}
  
  	for (j = 0; j < nroots; j++) {
  		for (i = 0; i < nroots; i++) {
  			if (syn[i] == 0) {
  				syn[i] = ((uint16_t) par[j]) & msk;
  			} else {
03ead8427   Thomas Gleixner   [LIB] reed_solomo...
71
  				syn[i] = (((uint16_t) par[j]) & msk) ^
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
  					alpha_to[rs_modnn(rs, index_of[syn[i]] +
  						       (fcr+i)*prim)];
  			}
  		}
  	}
  	s = syn;
  
  	/* Convert syndromes to index form, checking for nonzero condition */
  	syn_error = 0;
  	for (i = 0; i < nroots; i++) {
  		syn_error |= s[i];
  		s[i] = index_of[s[i]];
  	}
  
  	if (!syn_error) {
  		/* if syndrome is zero, data[] is a codeword and there are no
  		 * errors to correct. So return data[] unmodified
  		 */
  		count = 0;
  		goto finish;
  	}
  
   decode:
  	memset(&lambda[1], 0, nroots * sizeof(lambda[0]));
  	lambda[0] = 1;
  
  	if (no_eras > 0) {
  		/* Init lambda to be the erasure locator polynomial */
03ead8427   Thomas Gleixner   [LIB] reed_solomo...
100
  		lambda[1] = alpha_to[rs_modnn(rs,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
101
102
103
104
105
106
  					      prim * (nn - 1 - eras_pos[0]))];
  		for (i = 1; i < no_eras; i++) {
  			u = rs_modnn(rs, prim * (nn - 1 - eras_pos[i]));
  			for (j = i + 1; j > 0; j--) {
  				tmp = index_of[lambda[j - 1]];
  				if (tmp != nn) {
03ead8427   Thomas Gleixner   [LIB] reed_solomo...
107
  					lambda[j] ^=
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
  						alpha_to[rs_modnn(rs, u + tmp)];
  				}
  			}
  		}
  	}
  
  	for (i = 0; i < nroots + 1; i++)
  		b[i] = index_of[lambda[i]];
  
  	/*
  	 * Begin Berlekamp-Massey algorithm to determine error+erasure
  	 * locator polynomial
  	 */
  	r = no_eras;
  	el = no_eras;
  	while (++r <= nroots) {	/* r is the step number */
  		/* Compute discrepancy at the r-th step in poly-form */
  		discr_r = 0;
  		for (i = 0; i < r; i++) {
  			if ((lambda[i] != 0) && (s[r - i - 1] != nn)) {
03ead8427   Thomas Gleixner   [LIB] reed_solomo...
128
129
  				discr_r ^=
  					alpha_to[rs_modnn(rs,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
130
131
132
133
134
135
136
137
138
139
140
141
142
143
  							  index_of[lambda[i]] +
  							  s[r - i - 1])];
  			}
  		}
  		discr_r = index_of[discr_r];	/* Index form */
  		if (discr_r == nn) {
  			/* 2 lines below: B(x) <-- x*B(x) */
  			memmove (&b[1], b, nroots * sizeof (b[0]));
  			b[0] = nn;
  		} else {
  			/* 7 lines below: T(x) <-- lambda(x)-discr_r*x*b(x) */
  			t[0] = lambda[0];
  			for (i = 0; i < nroots; i++) {
  				if (b[i] != nn) {
03ead8427   Thomas Gleixner   [LIB] reed_solomo...
144
  					t[i + 1] = lambda[i + 1] ^
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
  						alpha_to[rs_modnn(rs, discr_r +
  								  b[i])];
  				} else
  					t[i + 1] = lambda[i + 1];
  			}
  			if (2 * el <= r + no_eras - 1) {
  				el = r + no_eras - el;
  				/*
  				 * 2 lines below: B(x) <-- inv(discr_r) *
  				 * lambda(x)
  				 */
  				for (i = 0; i <= nroots; i++) {
  					b[i] = (lambda[i] == 0) ? nn :
  						rs_modnn(rs, index_of[lambda[i]]
  							 - discr_r + nn);
  				}
  			} else {
  				/* 2 lines below: B(x) <-- x*B(x) */
  				memmove(&b[1], b, nroots * sizeof(b[0]));
  				b[0] = nn;
  			}
  			memcpy(lambda, t, (nroots + 1) * sizeof(t[0]));
  		}
  	}
  
  	/* Convert lambda to index form and compute deg(lambda(x)) */
  	deg_lambda = 0;
  	for (i = 0; i < nroots + 1; i++) {
  		lambda[i] = index_of[lambda[i]];
  		if (lambda[i] != nn)
  			deg_lambda = i;
  	}
  	/* Find roots of error+erasure locator polynomial by Chien search */
  	memcpy(&reg[1], &lambda[1], nroots * sizeof(reg[0]));
  	count = 0;		/* Number of roots of lambda(x) */
  	for (i = 1, k = iprim - 1; i <= nn; i++, k = rs_modnn(rs, k + iprim)) {
  		q = 1;		/* lambda[0] is always 0 */
  		for (j = deg_lambda; j > 0; j--) {
  			if (reg[j] != nn) {
  				reg[j] = rs_modnn(rs, reg[j] + j);
  				q ^= alpha_to[reg[j]];
  			}
  		}
  		if (q != 0)
  			continue;	/* Not a root */
  		/* store root (index-form) and error location number */
  		root[count] = i;
  		loc[count] = k;
  		/* If we've already found max possible roots,
  		 * abort the search to save time
  		 */
  		if (++count == deg_lambda)
  			break;
  	}
  	if (deg_lambda != count) {
  		/*
  		 * deg(lambda) unequal to number of roots => uncorrectable
  		 * error detected
  		 */
eb6845071   Jörn Engel   [MTD] [NAND] Repl...
204
  		count = -EBADMSG;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
  		goto finish;
  	}
  	/*
  	 * Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo
  	 * x**nroots). in index form. Also find deg(omega).
  	 */
  	deg_omega = deg_lambda - 1;
  	for (i = 0; i <= deg_omega; i++) {
  		tmp = 0;
  		for (j = i; j >= 0; j--) {
  			if ((s[i - j] != nn) && (lambda[j] != nn))
  				tmp ^=
  				    alpha_to[rs_modnn(rs, s[i - j] + lambda[j])];
  		}
  		omega[i] = index_of[tmp];
  	}
  
  	/*
  	 * Compute error values in poly-form. num1 = omega(inv(X(l))), num2 =
  	 * inv(X(l))**(fcr-1) and den = lambda_pr(inv(X(l))) all in poly-form
  	 */
  	for (j = count - 1; j >= 0; j--) {
  		num1 = 0;
  		for (i = deg_omega; i >= 0; i--) {
  			if (omega[i] != nn)
03ead8427   Thomas Gleixner   [LIB] reed_solomo...
230
  				num1 ^= alpha_to[rs_modnn(rs, omega[i] +
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
231
232
233
234
235
236
237
238
239
  							i * root[j])];
  		}
  		num2 = alpha_to[rs_modnn(rs, root[j] * (fcr - 1) + nn)];
  		den = 0;
  
  		/* lambda[i+1] for i even is the formal derivative
  		 * lambda_pr of lambda[i] */
  		for (i = min(deg_lambda, nroots - 1) & ~1; i >= 0; i -= 2) {
  			if (lambda[i + 1] != nn) {
03ead8427   Thomas Gleixner   [LIB] reed_solomo...
240
  				den ^= alpha_to[rs_modnn(rs, lambda[i + 1] +
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
241
242
243
244
245
  						       i * root[j])];
  			}
  		}
  		/* Apply error to data */
  		if (num1 != 0 && loc[j] >= pad) {
03ead8427   Thomas Gleixner   [LIB] reed_solomo...
246
  			uint16_t cor = alpha_to[rs_modnn(rs,index_of[num1] +
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
  						       index_of[num2] +
  						       nn - index_of[den])];
  			/* Store the error correction pattern, if a
  			 * correction buffer is available */
  			if (corr) {
  				corr[j] = cor;
  			} else {
  				/* If a data buffer is given and the
  				 * error is inside the message,
  				 * correct it */
  				if (data && (loc[j] < (nn - nroots)))
  					data[loc[j] - pad] ^= cor;
  			}
  		}
  	}
  
  finish:
  	if (eras_pos != NULL) {
  		for (i = 0; i < count; i++)
  			eras_pos[i] = loc[i] - pad;
  	}
  	return count;
  
  }