1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23#include <linux/kernel.h>
24#include <asm/errno.h>
25#include <linux/delay.h>
26#include <linux/slab.h>
27#include <linux/mtd/mtd.h>
28#include <linux/mtd/nand.h>
29#include <linux/mtd/nftl.h>
30
31#define SECTORSIZE 512
32
33
34
35
36
37
38static int find_boot_record(struct NFTLrecord *nftl)
39{
40 struct nftl_uci1 h1;
41 unsigned int block, boot_record_count = 0;
42 size_t retlen;
43 u8 buf[SECTORSIZE];
44 struct NFTLMediaHeader *mh = &nftl->MediaHdr;
45 struct mtd_info *mtd = nftl->mbd.mtd;
46 unsigned int i;
47
48
49
50
51
52
53
54 nftl->EraseSize = nftl->mbd.mtd->erasesize;
55 nftl->nb_blocks = (u32)nftl->mbd.mtd->size / nftl->EraseSize;
56
57 nftl->MediaUnit = BLOCK_NIL;
58 nftl->SpareMediaUnit = BLOCK_NIL;
59
60
61 for (block = 0; block < nftl->nb_blocks; block++) {
62 int ret;
63
64
65
66 ret = mtd_read(mtd, block * nftl->EraseSize, SECTORSIZE,
67 &retlen, buf);
68
69
70 if (retlen != SECTORSIZE) {
71 static int warncount = 5;
72
73 if (warncount) {
74 printk(KERN_WARNING "Block read at 0x%x of mtd%d failed: %d\n",
75 block * nftl->EraseSize, nftl->mbd.mtd->index, ret);
76 if (!--warncount)
77 printk(KERN_WARNING "Further failures for this block will not be printed\n");
78 }
79 continue;
80 }
81
82 if (retlen < 6 || memcmp(buf, "ANAND", 6)) {
83
84#if 0
85 printk(KERN_DEBUG "ANAND header not found at 0x%x in mtd%d\n",
86 block * nftl->EraseSize, nftl->mbd.mtd->index);
87#endif
88 continue;
89 }
90
91
92 ret = nftl_read_oob(mtd, block * nftl->EraseSize +
93 SECTORSIZE + 8, 8, &retlen,
94 (char *)&h1);
95 if (ret < 0) {
96 printk(KERN_WARNING "ANAND header found at 0x%x in mtd%d, but OOB data read failed (err %d)\n",
97 block * nftl->EraseSize, nftl->mbd.mtd->index, ret);
98 continue;
99 }
100
101#if 0
102
103
104
105 if (le16_to_cpu(h1.EraseMark | h1.EraseMark1) != ERASE_MARK) {
106 printk(KERN_NOTICE "ANAND header found at 0x%x in mtd%d, but erase mark not present (0x%04x,0x%04x instead)\n",
107 block * nftl->EraseSize, nftl->mbd.mtd->index,
108 le16_to_cpu(h1.EraseMark), le16_to_cpu(h1.EraseMark1));
109 continue;
110 }
111
112
113 ret = mtd->read(mtd, block * nftl->EraseSize, SECTORSIZE,
114 &retlen, buf);
115 if (ret < 0) {
116 printk(KERN_NOTICE "ANAND header found at 0x%x in mtd%d, but ECC read failed (err %d)\n",
117 block * nftl->EraseSize, nftl->mbd.mtd->index, ret);
118 continue;
119 }
120
121
122 if (memcmp(buf, "ANAND", 6)) {
123 printk(KERN_NOTICE "ANAND header found at 0x%x in mtd%d, but went away on reread!\n",
124 block * nftl->EraseSize, nftl->mbd.mtd->index);
125 printk(KERN_NOTICE "New data are: %02x %02x %02x %02x %02x %02x\n",
126 buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
127 continue;
128 }
129#endif
130
131
132 if (boot_record_count) {
133
134
135 if (memcmp(mh, buf, sizeof(struct NFTLMediaHeader))) {
136 printk(KERN_NOTICE "NFTL Media Headers at 0x%x and 0x%x disagree.\n",
137 nftl->MediaUnit * nftl->EraseSize, block * nftl->EraseSize);
138
139 if (boot_record_count < 2) {
140
141 return -1;
142 }
143 continue;
144 }
145 if (boot_record_count == 1)
146 nftl->SpareMediaUnit = block;
147
148
149 nftl->ReplUnitTable[block] = BLOCK_RESERVED;
150
151
152 boot_record_count++;
153 continue;
154 }
155
156
157 memcpy(mh, buf, sizeof(struct NFTLMediaHeader));
158
159
160#if 0
161The new DiskOnChip driver scans the MediaHeader itself, and presents a virtual
162erasesize based on UnitSizeFactor. So the erasesize we read from the mtd
163device is already correct.
164 if (mh->UnitSizeFactor == 0) {
165 printk(KERN_NOTICE "NFTL: UnitSizeFactor 0x00 detected. This violates the spec but we think we know what it means...\n");
166 } else if (mh->UnitSizeFactor < 0xfc) {
167 printk(KERN_NOTICE "Sorry, we don't support UnitSizeFactor 0x%02x\n",
168 mh->UnitSizeFactor);
169 return -1;
170 } else if (mh->UnitSizeFactor != 0xff) {
171 printk(KERN_NOTICE "WARNING: Support for NFTL with UnitSizeFactor 0x%02x is experimental\n",
172 mh->UnitSizeFactor);
173 nftl->EraseSize = nftl->mbd.mtd->erasesize << (0xff - mh->UnitSizeFactor);
174 nftl->nb_blocks = (u32)nftl->mbd.mtd->size / nftl->EraseSize;
175 }
176#endif
177 nftl->nb_boot_blocks = le16_to_cpu(mh->FirstPhysicalEUN);
178 if ((nftl->nb_boot_blocks + 2) >= nftl->nb_blocks) {
179 printk(KERN_NOTICE "NFTL Media Header sanity check failed:\n");
180 printk(KERN_NOTICE "nb_boot_blocks (%d) + 2 > nb_blocks (%d)\n",
181 nftl->nb_boot_blocks, nftl->nb_blocks);
182 return -1;
183 }
184
185 nftl->numvunits = le32_to_cpu(mh->FormattedSize) / nftl->EraseSize;
186 if (nftl->numvunits > (nftl->nb_blocks - nftl->nb_boot_blocks - 2)) {
187 printk(KERN_NOTICE "NFTL Media Header sanity check failed:\n");
188 printk(KERN_NOTICE "numvunits (%d) > nb_blocks (%d) - nb_boot_blocks(%d) - 2\n",
189 nftl->numvunits, nftl->nb_blocks, nftl->nb_boot_blocks);
190 return -1;
191 }
192
193 nftl->mbd.size = nftl->numvunits * (nftl->EraseSize / SECTORSIZE);
194
195
196
197 nftl->nb_blocks = le16_to_cpu(mh->NumEraseUnits) + le16_to_cpu(mh->FirstPhysicalEUN);
198
199
200 nftl->lastEUN = nftl->nb_blocks - 1;
201
202
203 nftl->EUNtable = kmalloc(nftl->nb_blocks * sizeof(u16), GFP_KERNEL);
204 if (!nftl->EUNtable) {
205 printk(KERN_NOTICE "NFTL: allocation of EUNtable failed\n");
206 return -ENOMEM;
207 }
208
209 nftl->ReplUnitTable = kmalloc(nftl->nb_blocks * sizeof(u16), GFP_KERNEL);
210 if (!nftl->ReplUnitTable) {
211 kfree(nftl->EUNtable);
212 printk(KERN_NOTICE "NFTL: allocation of ReplUnitTable failed\n");
213 return -ENOMEM;
214 }
215
216
217 for (i = 0; i < nftl->nb_boot_blocks; i++)
218 nftl->ReplUnitTable[i] = BLOCK_RESERVED;
219
220 for (; i < nftl->nb_blocks; i++) {
221 nftl->ReplUnitTable[i] = BLOCK_NOTEXPLORED;
222 }
223
224
225 nftl->ReplUnitTable[block] = BLOCK_RESERVED;
226
227
228 for (i = 0; i < nftl->nb_blocks; i++) {
229#if 0
230The new DiskOnChip driver already scanned the bad block table. Just query it.
231 if ((i & (SECTORSIZE - 1)) == 0) {
232
233 ret = mtd->read(nftl->mbd.mtd,
234 block * nftl->EraseSize + i +
235 SECTORSIZE, SECTORSIZE,
236 &retlen, buf);
237 if (ret < 0) {
238 printk(KERN_NOTICE "Read of bad sector table failed (err %d)\n",
239 ret);
240 kfree(nftl->ReplUnitTable);
241 kfree(nftl->EUNtable);
242 return -1;
243 }
244 }
245
246 if (buf[i & (SECTORSIZE - 1)] != 0xff)
247 nftl->ReplUnitTable[i] = BLOCK_RESERVED;
248#endif
249 if (mtd_block_isbad(nftl->mbd.mtd,
250 i * nftl->EraseSize))
251 nftl->ReplUnitTable[i] = BLOCK_RESERVED;
252 }
253
254 nftl->MediaUnit = block;
255 boot_record_count++;
256
257 }
258
259 return boot_record_count?0:-1;
260}
261
262static int memcmpb(void *a, int c, int n)
263{
264 int i;
265 for (i = 0; i < n; i++) {
266 if (c != ((unsigned char *)a)[i])
267 return 1;
268 }
269 return 0;
270}
271
272
273static int check_free_sectors(struct NFTLrecord *nftl, unsigned int address, int len,
274 int check_oob)
275{
276 u8 buf[SECTORSIZE + nftl->mbd.mtd->oobsize];
277 struct mtd_info *mtd = nftl->mbd.mtd;
278 size_t retlen;
279 int i;
280
281 for (i = 0; i < len; i += SECTORSIZE) {
282 if (mtd_read(mtd, address, SECTORSIZE, &retlen, buf))
283 return -1;
284 if (memcmpb(buf, 0xff, SECTORSIZE) != 0)
285 return -1;
286
287 if (check_oob) {
288 if(nftl_read_oob(mtd, address, mtd->oobsize,
289 &retlen, &buf[SECTORSIZE]) < 0)
290 return -1;
291 if (memcmpb(buf + SECTORSIZE, 0xff, mtd->oobsize) != 0)
292 return -1;
293 }
294 address += SECTORSIZE;
295 }
296
297 return 0;
298}
299
300
301
302
303
304
305
306
307int NFTL_formatblock(struct NFTLrecord *nftl, int block)
308{
309 size_t retlen;
310 unsigned int nb_erases, erase_mark;
311 struct nftl_uci1 uci;
312 struct erase_info *instr = &nftl->instr;
313 struct mtd_info *mtd = nftl->mbd.mtd;
314
315
316 if (nftl_read_oob(mtd, block * nftl->EraseSize + SECTORSIZE + 8,
317 8, &retlen, (char *)&uci) < 0)
318 goto default_uci1;
319
320 erase_mark = le16_to_cpu ((uci.EraseMark | uci.EraseMark1));
321 if (erase_mark != ERASE_MARK) {
322 default_uci1:
323 uci.EraseMark = cpu_to_le16(ERASE_MARK);
324 uci.EraseMark1 = cpu_to_le16(ERASE_MARK);
325 uci.WearInfo = cpu_to_le32(0);
326 }
327
328 memset(instr, 0, sizeof(struct erase_info));
329
330
331 instr->mtd = nftl->mbd.mtd;
332 instr->addr = block * nftl->EraseSize;
333 instr->len = nftl->EraseSize;
334 mtd_erase(mtd, instr);
335
336 if (instr->state == MTD_ERASE_FAILED) {
337 printk("Error while formatting block %d\n", block);
338 goto fail;
339 }
340
341
342 nb_erases = le32_to_cpu(uci.WearInfo);
343 nb_erases++;
344
345
346 if (nb_erases == 0)
347 nb_erases = 1;
348
349
350
351
352 if (check_free_sectors(nftl, instr->addr, nftl->EraseSize, 1) != 0)
353 goto fail;
354
355 uci.WearInfo = le32_to_cpu(nb_erases);
356 if (nftl_write_oob(mtd, block * nftl->EraseSize + SECTORSIZE +
357 8, 8, &retlen, (char *)&uci) < 0)
358 goto fail;
359 return 0;
360fail:
361
362
363 mtd_block_markbad(nftl->mbd.mtd, instr->addr);
364 return -1;
365}
366
367
368
369
370
371
372
373
374
375
376static void check_sectors_in_chain(struct NFTLrecord *nftl, unsigned int first_block)
377{
378 struct mtd_info *mtd = nftl->mbd.mtd;
379 unsigned int block, i, status;
380 struct nftl_bci bci;
381 int sectors_per_block;
382 size_t retlen;
383
384 sectors_per_block = nftl->EraseSize / SECTORSIZE;
385 block = first_block;
386 for (;;) {
387 for (i = 0; i < sectors_per_block; i++) {
388 if (nftl_read_oob(mtd,
389 block * nftl->EraseSize + i * SECTORSIZE,
390 8, &retlen, (char *)&bci) < 0)
391 status = SECTOR_IGNORE;
392 else
393 status = bci.Status | bci.Status1;
394
395 switch(status) {
396 case SECTOR_FREE:
397
398
399 if (memcmpb(&bci, 0xff, 8) != 0 ||
400 check_free_sectors(nftl, block * nftl->EraseSize + i * SECTORSIZE,
401 SECTORSIZE, 0) != 0) {
402 printk("Incorrect free sector %d in block %d: "
403 "marking it as ignored\n",
404 i, block);
405
406
407 bci.Status = SECTOR_IGNORE;
408 bci.Status1 = SECTOR_IGNORE;
409 nftl_write_oob(mtd, block *
410 nftl->EraseSize +
411 i * SECTORSIZE, 8,
412 &retlen, (char *)&bci);
413 }
414 break;
415 default:
416 break;
417 }
418 }
419
420
421 block = nftl->ReplUnitTable[block];
422 if (!(block == BLOCK_NIL || block < nftl->nb_blocks))
423 printk("incorrect ReplUnitTable[] : %d\n", block);
424 if (block == BLOCK_NIL || block >= nftl->nb_blocks)
425 break;
426 }
427}
428
429
430static int calc_chain_length(struct NFTLrecord *nftl, unsigned int first_block)
431{
432 unsigned int length = 0, block = first_block;
433
434 for (;;) {
435 length++;
436
437
438 if (length >= nftl->nb_blocks) {
439 printk("nftl: length too long %d !\n", length);
440 break;
441 }
442
443 block = nftl->ReplUnitTable[block];
444 if (!(block == BLOCK_NIL || block < nftl->nb_blocks))
445 printk("incorrect ReplUnitTable[] : %d\n", block);
446 if (block == BLOCK_NIL || block >= nftl->nb_blocks)
447 break;
448 }
449 return length;
450}
451
452
453
454
455
456
457
458
459
460
461
462static void format_chain(struct NFTLrecord *nftl, unsigned int first_block)
463{
464 unsigned int block = first_block, block1;
465
466 printk("Formatting chain at block %d\n", first_block);
467
468 for (;;) {
469 block1 = nftl->ReplUnitTable[block];
470
471 printk("Formatting block %d\n", block);
472 if (NFTL_formatblock(nftl, block) < 0) {
473
474 nftl->ReplUnitTable[block] = BLOCK_RESERVED;
475 } else {
476 nftl->ReplUnitTable[block] = BLOCK_FREE;
477 }
478
479
480 block = block1;
481
482 if (!(block == BLOCK_NIL || block < nftl->nb_blocks))
483 printk("incorrect ReplUnitTable[] : %d\n", block);
484 if (block == BLOCK_NIL || block >= nftl->nb_blocks)
485 break;
486 }
487}
488
489
490
491
492
493
494
495static int check_and_mark_free_block(struct NFTLrecord *nftl, int block)
496{
497 struct mtd_info *mtd = nftl->mbd.mtd;
498 struct nftl_uci1 h1;
499 unsigned int erase_mark;
500 size_t retlen;
501
502
503 if (nftl_read_oob(mtd, block * nftl->EraseSize + SECTORSIZE + 8, 8,
504 &retlen, (char *)&h1) < 0)
505 return -1;
506
507 erase_mark = le16_to_cpu ((h1.EraseMark | h1.EraseMark1));
508 if (erase_mark != ERASE_MARK) {
509
510
511 if (check_free_sectors (nftl, block * nftl->EraseSize, nftl->EraseSize, 1) != 0)
512 return -1;
513
514
515 h1.EraseMark = cpu_to_le16(ERASE_MARK);
516 h1.EraseMark1 = cpu_to_le16(ERASE_MARK);
517 h1.WearInfo = cpu_to_le32(0);
518 if (nftl_write_oob(mtd,
519 block * nftl->EraseSize + SECTORSIZE + 8, 8,
520 &retlen, (char *)&h1) < 0)
521 return -1;
522 } else {
523#if 0
524
525 for (i = 0; i < nftl->EraseSize; i += SECTORSIZE) {
526
527 if (check_free_sectors (nftl, block * nftl->EraseSize + i,
528 SECTORSIZE, 0) != 0)
529 return -1;
530
531 if (nftl_read_oob(mtd, block * nftl->EraseSize + i,
532 16, &retlen, buf) < 0)
533 return -1;
534 if (i == SECTORSIZE) {
535
536 if (memcmpb(buf, 0xff, 8))
537 return -1;
538 } else {
539 if (memcmpb(buf, 0xff, 16))
540 return -1;
541 }
542 }
543#endif
544 }
545
546 return 0;
547}
548
549
550
551
552
553
554
555
556static int get_fold_mark(struct NFTLrecord *nftl, unsigned int block)
557{
558 struct mtd_info *mtd = nftl->mbd.mtd;
559 struct nftl_uci2 uci;
560 size_t retlen;
561
562 if (nftl_read_oob(mtd, block * nftl->EraseSize + 2 * SECTORSIZE + 8,
563 8, &retlen, (char *)&uci) < 0)
564 return 0;
565
566 return le16_to_cpu((uci.FoldMark | uci.FoldMark1));
567}
568
569int NFTL_mount(struct NFTLrecord *s)
570{
571 int i;
572 unsigned int first_logical_block, logical_block, rep_block, nb_erases, erase_mark;
573 unsigned int block, first_block, is_first_block;
574 int chain_length, do_format_chain;
575 struct nftl_uci0 h0;
576 struct nftl_uci1 h1;
577 struct mtd_info *mtd = s->mbd.mtd;
578 size_t retlen;
579
580
581 if (find_boot_record(s) < 0) {
582 printk("Could not find valid boot record\n");
583 return -1;
584 }
585
586
587 for (i = 0; i < s->nb_blocks; i++) {
588 s->EUNtable[i] = BLOCK_NIL;
589 }
590
591
592 first_logical_block = 0;
593 for (first_block = 0; first_block < s->nb_blocks; first_block++) {
594
595 if (s->ReplUnitTable[first_block] == BLOCK_NOTEXPLORED) {
596 block = first_block;
597 chain_length = 0;
598 do_format_chain = 0;
599
600 for (;;) {
601
602 if (nftl_read_oob(mtd,
603 block * s->EraseSize + 8, 8,
604 &retlen, (char *)&h0) < 0 ||
605 nftl_read_oob(mtd,
606 block * s->EraseSize +
607 SECTORSIZE + 8, 8,
608 &retlen, (char *)&h1) < 0) {
609 s->ReplUnitTable[block] = BLOCK_NIL;
610 do_format_chain = 1;
611 break;
612 }
613
614 logical_block = le16_to_cpu ((h0.VirtUnitNum | h0.SpareVirtUnitNum));
615 rep_block = le16_to_cpu ((h0.ReplUnitNum | h0.SpareReplUnitNum));
616 nb_erases = le32_to_cpu (h1.WearInfo);
617 erase_mark = le16_to_cpu ((h1.EraseMark | h1.EraseMark1));
618
619 is_first_block = !(logical_block >> 15);
620 logical_block = logical_block & 0x7fff;
621
622
623 if (erase_mark != ERASE_MARK || logical_block >= s->nb_blocks) {
624 if (chain_length == 0) {
625
626 if (check_and_mark_free_block(s, block) < 0) {
627
628 printk("Formatting block %d\n", block);
629 if (NFTL_formatblock(s, block) < 0) {
630
631 s->ReplUnitTable[block] = BLOCK_RESERVED;
632 } else {
633 s->ReplUnitTable[block] = BLOCK_FREE;
634 }
635 } else {
636
637 s->ReplUnitTable[block] = BLOCK_FREE;
638 }
639
640 goto examine_ReplUnitTable;
641 } else {
642
643
644 printk("Block %d: free but referenced in chain %d\n",
645 block, first_block);
646 s->ReplUnitTable[block] = BLOCK_NIL;
647 do_format_chain = 1;
648 break;
649 }
650 }
651
652
653 if (chain_length == 0) {
654
655
656
657 if (!is_first_block)
658 goto examine_ReplUnitTable;
659 first_logical_block = logical_block;
660 } else {
661 if (logical_block != first_logical_block) {
662 printk("Block %d: incorrect logical block: %d expected: %d\n",
663 block, logical_block, first_logical_block);
664
665
666 do_format_chain = 1;
667 }
668 if (is_first_block) {
669
670
671
672 if (get_fold_mark(s, block) != FOLD_MARK_IN_PROGRESS ||
673 rep_block != 0xffff) {
674 printk("Block %d: incorrectly marked as first block in chain\n",
675 block);
676
677
678 do_format_chain = 1;
679 } else {
680 printk("Block %d: folding in progress - ignoring first block flag\n",
681 block);
682 }
683 }
684 }
685 chain_length++;
686 if (rep_block == 0xffff) {
687
688 s->ReplUnitTable[block] = BLOCK_NIL;
689 break;
690 } else if (rep_block >= s->nb_blocks) {
691 printk("Block %d: referencing invalid block %d\n",
692 block, rep_block);
693 do_format_chain = 1;
694 s->ReplUnitTable[block] = BLOCK_NIL;
695 break;
696 } else if (s->ReplUnitTable[rep_block] != BLOCK_NOTEXPLORED) {
697
698
699
700
701
702 if (s->ReplUnitTable[rep_block] == BLOCK_NIL &&
703 s->EUNtable[first_logical_block] == rep_block &&
704 get_fold_mark(s, first_block) == FOLD_MARK_IN_PROGRESS) {
705
706 printk("Block %d: folding in progress - ignoring first block flag\n",
707 rep_block);
708 s->ReplUnitTable[block] = rep_block;
709 s->EUNtable[first_logical_block] = BLOCK_NIL;
710 } else {
711 printk("Block %d: referencing block %d already in another chain\n",
712 block, rep_block);
713
714 do_format_chain = 1;
715 s->ReplUnitTable[block] = BLOCK_NIL;
716 }
717 break;
718 } else {
719
720 s->ReplUnitTable[block] = rep_block;
721 block = rep_block;
722 }
723 }
724
725
726
727 if (do_format_chain) {
728
729 format_chain(s, first_block);
730 } else {
731 unsigned int first_block1, chain_to_format, chain_length1;
732 int fold_mark;
733
734
735 fold_mark = get_fold_mark(s, first_block);
736 if (fold_mark == 0) {
737
738 printk("Could read foldmark at block %d\n", first_block);
739 format_chain(s, first_block);
740 } else {
741 if (fold_mark == FOLD_MARK_IN_PROGRESS)
742 check_sectors_in_chain(s, first_block);
743
744
745
746
747
748 first_block1 = s->EUNtable[first_logical_block];
749 if (first_block1 != BLOCK_NIL) {
750
751 chain_length1 = calc_chain_length(s, first_block1);
752 printk("Two chains at blocks %d (len=%d) and %d (len=%d)\n",
753 first_block1, chain_length1, first_block, chain_length);
754
755 if (chain_length >= chain_length1) {
756 chain_to_format = first_block1;
757 s->EUNtable[first_logical_block] = first_block;
758 } else {
759 chain_to_format = first_block;
760 }
761 format_chain(s, chain_to_format);
762 } else {
763 s->EUNtable[first_logical_block] = first_block;
764 }
765 }
766 }
767 }
768 examine_ReplUnitTable:;
769 }
770
771
772 s->numfreeEUNs = 0;
773 s->LastFreeEUN = le16_to_cpu(s->MediaHdr.FirstPhysicalEUN);
774
775 for (block = 0; block < s->nb_blocks; block++) {
776 if (s->ReplUnitTable[block] == BLOCK_NOTEXPLORED) {
777 printk("Unreferenced block %d, formatting it\n", block);
778 if (NFTL_formatblock(s, block) < 0)
779 s->ReplUnitTable[block] = BLOCK_RESERVED;
780 else
781 s->ReplUnitTable[block] = BLOCK_FREE;
782 }
783 if (s->ReplUnitTable[block] == BLOCK_FREE) {
784 s->numfreeEUNs++;
785 s->LastFreeEUN = block;
786 }
787 }
788
789 return 0;
790}
791