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