📄 wbuf.c
字号:
/* * JFFS2 -- Journalling Flash File System, Version 2. * * Copyright © 2001-2007 Red Hat, Inc. * Copyright © 2004 Thomas Gleixner <tglx@linutronix.de> * * Created by David Woodhouse <dwmw2@infradead.org> * Modified debugged and enhanced by Thomas Gleixner <tglx@linutronix.de> * * For licensing information, see the file 'LICENCE' in this directory. * */#include <linux/kernel.h>#include <linux/slab.h>#include <linux/mtd/mtd.h>#include <linux/crc32.h>#include <linux/mtd/nand.h>#include <linux/jiffies.h>#include <linux/sched.h>#include "nodelist.h"/* For testing write failures */#undef BREAKME#undef BREAKMEHEADER#ifdef BREAKMEstatic unsigned char *brokenbuf;#endif#define PAGE_DIV(x) ( ((unsigned long)(x) / (unsigned long)(c->wbuf_pagesize)) * (unsigned long)(c->wbuf_pagesize) )#define PAGE_MOD(x) ( (unsigned long)(x) % (unsigned long)(c->wbuf_pagesize) )/* max. erase failures before we mark a block bad */#define MAX_ERASE_FAILURES 2struct jffs2_inodirty { uint32_t ino; struct jffs2_inodirty *next;};static struct jffs2_inodirty inodirty_nomem;static int jffs2_wbuf_pending_for_ino(struct jffs2_sb_info *c, uint32_t ino){ struct jffs2_inodirty *this = c->wbuf_inodes; /* If a malloc failed, consider _everything_ dirty */ if (this == &inodirty_nomem) return 1; /* If ino == 0, _any_ non-GC writes mean 'yes' */ if (this && !ino) return 1; /* Look to see if the inode in question is pending in the wbuf */ while (this) { if (this->ino == ino) return 1; this = this->next; } return 0;}static void jffs2_clear_wbuf_ino_list(struct jffs2_sb_info *c){ struct jffs2_inodirty *this; this = c->wbuf_inodes; if (this != &inodirty_nomem) { while (this) { struct jffs2_inodirty *next = this->next; kfree(this); this = next; } } c->wbuf_inodes = NULL;}static void jffs2_wbuf_dirties_inode(struct jffs2_sb_info *c, uint32_t ino){ struct jffs2_inodirty *new; /* Mark the superblock dirty so that kupdated will flush... */ jffs2_erase_pending_trigger(c); if (jffs2_wbuf_pending_for_ino(c, ino)) return; new = kmalloc(sizeof(*new), GFP_KERNEL); if (!new) { D1(printk(KERN_DEBUG "No memory to allocate inodirty. Fallback to all considered dirty\n")); jffs2_clear_wbuf_ino_list(c); c->wbuf_inodes = &inodirty_nomem; return; } new->ino = ino; new->next = c->wbuf_inodes; c->wbuf_inodes = new; return;}static inline void jffs2_refile_wbuf_blocks(struct jffs2_sb_info *c){ struct list_head *this, *next; static int n; if (list_empty(&c->erasable_pending_wbuf_list)) return; list_for_each_safe(this, next, &c->erasable_pending_wbuf_list) { struct jffs2_eraseblock *jeb = list_entry(this, struct jffs2_eraseblock, list); D1(printk(KERN_DEBUG "Removing eraseblock at 0x%08x from erasable_pending_wbuf_list...\n", jeb->offset)); list_del(this); if ((jiffies + (n++)) & 127) { /* Most of the time, we just erase it immediately. Otherwise we spend ages scanning it on mount, etc. */ D1(printk(KERN_DEBUG "...and adding to erase_pending_list\n")); list_add_tail(&jeb->list, &c->erase_pending_list); c->nr_erasing_blocks++; jffs2_erase_pending_trigger(c); } else { /* Sometimes, however, we leave it elsewhere so it doesn't get immediately reused, and we spread the load a bit. */ D1(printk(KERN_DEBUG "...and adding to erasable_list\n")); list_add_tail(&jeb->list, &c->erasable_list); } }}#define REFILE_NOTEMPTY 0#define REFILE_ANYWAY 1static void jffs2_block_refile(struct jffs2_sb_info *c, struct jffs2_eraseblock *jeb, int allow_empty){ D1(printk("About to refile bad block at %08x\n", jeb->offset)); /* File the existing block on the bad_used_list.... */ if (c->nextblock == jeb) c->nextblock = NULL; else /* Not sure this should ever happen... need more coffee */ list_del(&jeb->list); if (jeb->first_node) { D1(printk("Refiling block at %08x to bad_used_list\n", jeb->offset)); list_add(&jeb->list, &c->bad_used_list); } else { BUG_ON(allow_empty == REFILE_NOTEMPTY); /* It has to have had some nodes or we couldn't be here */ D1(printk("Refiling block at %08x to erase_pending_list\n", jeb->offset)); list_add(&jeb->list, &c->erase_pending_list); c->nr_erasing_blocks++; jffs2_erase_pending_trigger(c); } if (!jffs2_prealloc_raw_node_refs(c, jeb, 1)) { uint32_t oldfree = jeb->free_size; jffs2_link_node_ref(c, jeb, (jeb->offset+c->sector_size-oldfree) | REF_OBSOLETE, oldfree, NULL); /* convert to wasted */ c->wasted_size += oldfree; jeb->wasted_size += oldfree; c->dirty_size -= oldfree; jeb->dirty_size -= oldfree; } jffs2_dbg_dump_block_lists_nolock(c); jffs2_dbg_acct_sanity_check_nolock(c,jeb); jffs2_dbg_acct_paranoia_check_nolock(c, jeb);}static struct jffs2_raw_node_ref **jffs2_incore_replace_raw(struct jffs2_sb_info *c, struct jffs2_inode_info *f, struct jffs2_raw_node_ref *raw, union jffs2_node_union *node){ struct jffs2_node_frag *frag; struct jffs2_full_dirent *fd; dbg_noderef("incore_replace_raw: node at %p is {%04x,%04x}\n", node, je16_to_cpu(node->u.magic), je16_to_cpu(node->u.nodetype)); BUG_ON(je16_to_cpu(node->u.magic) != 0x1985 && je16_to_cpu(node->u.magic) != 0); switch (je16_to_cpu(node->u.nodetype)) { case JFFS2_NODETYPE_INODE: if (f->metadata && f->metadata->raw == raw) { dbg_noderef("Will replace ->raw in f->metadata at %p\n", f->metadata); return &f->metadata->raw; } frag = jffs2_lookup_node_frag(&f->fragtree, je32_to_cpu(node->i.offset)); BUG_ON(!frag); /* Find a frag which refers to the full_dnode we want to modify */ while (!frag->node || frag->node->raw != raw) { frag = frag_next(frag); BUG_ON(!frag); } dbg_noderef("Will replace ->raw in full_dnode at %p\n", frag->node); return &frag->node->raw; case JFFS2_NODETYPE_DIRENT: for (fd = f->dents; fd; fd = fd->next) { if (fd->raw == raw) { dbg_noderef("Will replace ->raw in full_dirent at %p\n", fd); return &fd->raw; } } BUG(); default: dbg_noderef("Don't care about replacing raw for nodetype %x\n", je16_to_cpu(node->u.nodetype)); break; } return NULL;}#ifdef CONFIG_JFFS2_FS_WBUF_VERIFYstatic int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf, uint32_t ofs){ int ret; size_t retlen; char *eccstr; ret = c->mtd->read(c->mtd, ofs, c->wbuf_pagesize, &retlen, c->wbuf_verify); if (ret && ret != -EUCLEAN && ret != -EBADMSG) { printk(KERN_WARNING "jffs2_verify_write(): Read back of page at %08x failed: %d\n", c->wbuf_ofs, ret); return ret; } else if (retlen != c->wbuf_pagesize) { printk(KERN_WARNING "jffs2_verify_write(): Read back of page at %08x gave short read: %zd not %d.\n", ofs, retlen, c->wbuf_pagesize); return -EIO; } if (!memcmp(buf, c->wbuf_verify, c->wbuf_pagesize)) return 0; if (ret == -EUCLEAN) eccstr = "corrected"; else if (ret == -EBADMSG) eccstr = "correction failed"; else eccstr = "OK or unused"; printk(KERN_WARNING "Write verify error (ECC %s) at %08x. Wrote:\n", eccstr, c->wbuf_ofs); print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1, c->wbuf, c->wbuf_pagesize, 0); printk(KERN_WARNING "Read back:\n"); print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1, c->wbuf_verify, c->wbuf_pagesize, 0); return -EIO;}#else#define jffs2_verify_write(c,b,o) (0)#endif/* Recover from failure to write wbuf. Recover the nodes up to the * wbuf, not the one which we were starting to try to write. */static void jffs2_wbuf_recover(struct jffs2_sb_info *c){ struct jffs2_eraseblock *jeb, *new_jeb; struct jffs2_raw_node_ref *raw, *next, *first_raw = NULL; size_t retlen; int ret; int nr_refile = 0; unsigned char *buf; uint32_t start, end, ofs, len; jeb = &c->blocks[c->wbuf_ofs / c->sector_size]; spin_lock(&c->erase_completion_lock); if (c->wbuf_ofs % c->mtd->erasesize) jffs2_block_refile(c, jeb, REFILE_NOTEMPTY); else jffs2_block_refile(c, jeb, REFILE_ANYWAY); spin_unlock(&c->erase_completion_lock); BUG_ON(!ref_obsolete(jeb->last_node)); /* Find the first node to be recovered, by skipping over every node which ends before the wbuf starts, or which is obsolete. */ for (next = raw = jeb->first_node; next; raw = next) { next = ref_next(raw); if (ref_obsolete(raw) || (next && ref_offset(next) <= c->wbuf_ofs)) { dbg_noderef("Skipping node at 0x%08x(%d)-0x%08x which is either before 0x%08x or obsolete\n", ref_offset(raw), ref_flags(raw), (ref_offset(raw) + ref_totlen(c, jeb, raw)), c->wbuf_ofs); continue; } dbg_noderef("First node to be recovered is at 0x%08x(%d)-0x%08x\n", ref_offset(raw), ref_flags(raw), (ref_offset(raw) + ref_totlen(c, jeb, raw))); first_raw = raw; break; } if (!first_raw) { /* All nodes were obsolete. Nothing to recover. */ D1(printk(KERN_DEBUG "No non-obsolete nodes to be recovered. Just filing block bad\n")); c->wbuf_len = 0; return; } start = ref_offset(first_raw); end = ref_offset(jeb->last_node); nr_refile = 1; /* Count the number of refs which need to be copied */ while ((raw = ref_next(raw)) != jeb->last_node) nr_refile++; dbg_noderef("wbuf recover %08x-%08x (%d bytes in %d nodes)\n", start, end, end - start, nr_refile); buf = NULL; if (start < c->wbuf_ofs) { /* First affected node was already partially written. * Attempt to reread the old data into our buffer. */ buf = kmalloc(end - start, GFP_KERNEL); if (!buf) { printk(KERN_CRIT "Malloc failure in wbuf recovery. Data loss ensues.\n"); goto read_failed; } /* Do the read... */ ret = c->mtd->read(c->mtd, start, c->wbuf_ofs - start, &retlen, buf); /* ECC recovered ? */ if ((ret == -EUCLEAN || ret == -EBADMSG) && (retlen == c->wbuf_ofs - start)) ret = 0; if (ret || retlen != c->wbuf_ofs - start) { printk(KERN_CRIT "Old data are already lost in wbuf recovery. Data loss ensues.\n"); kfree(buf); buf = NULL; read_failed: first_raw = ref_next(first_raw); nr_refile--; while (first_raw && ref_obsolete(first_raw)) { first_raw = ref_next(first_raw); nr_refile--; } /* If this was the only node to be recovered, give up */ if (!first_raw) { c->wbuf_len = 0; return; } /* It wasn't. Go on and try to recover nodes complete in the wbuf */ start = ref_offset(first_raw); dbg_noderef("wbuf now recover %08x-%08x (%d bytes in %d nodes)\n", start, end, end - start, nr_refile); } else { /* Read succeeded. Copy the remaining data from the wbuf */ memcpy(buf + (c->wbuf_ofs - start), c->wbuf, end - c->wbuf_ofs); } } /* OK... we're to rewrite (end-start) bytes of data from first_raw onwards. Either 'buf' contains the data, or we find it in the wbuf */ /* ... and get an allocation of space from a shiny new block instead */ ret = jffs2_reserve_space_gc(c, end-start, &len, JFFS2_SUMMARY_NOSUM_SIZE); if (ret) { printk(KERN_WARNING "Failed to allocate space for wbuf recovery. Data loss ensues.\n"); kfree(buf); return; } /* The summary is not recovered, so it must be disabled for this erase block */ jffs2_sum_disable_collecting(c->summary); ret = jffs2_prealloc_raw_node_refs(c, c->nextblock, nr_refile); if (ret) { printk(KERN_WARNING "Failed to allocate node refs for wbuf recovery. Data loss ensues.\n"); kfree(buf); return; } ofs = write_ofs(c); if (end-start >= c->wbuf_pagesize) { /* Need to do another write immediately, but it's possible that this is just because the wbuf itself is completely full, and there's nothing earlier read back from the flash. Hence 'buf' isn't necessarily what we're writing from. */ unsigned char *rewrite_buf = buf?:c->wbuf; uint32_t towrite = (end-start) - ((end-start)%c->wbuf_pagesize); D1(printk(KERN_DEBUG "Write 0x%x bytes at 0x%08x in wbuf recover\n", towrite, ofs));#ifdef BREAKMEHEADER static int breakme; if (breakme++ == 20) { printk(KERN_NOTICE "Faking write error at 0x%08x\n", ofs); breakme = 0; c->mtd->write(c->mtd, ofs, towrite, &retlen, brokenbuf); ret = -EIO; } else#endif ret = c->mtd->write(c->mtd, ofs, towrite, &retlen, rewrite_buf); if (ret || retlen != towrite || jffs2_verify_write(c, rewrite_buf, ofs)) { /* Argh. We tried. Really we did. */ printk(KERN_CRIT "Recovery of wbuf failed due to a second write error\n"); kfree(buf); if (retlen) jffs2_add_physical_node_ref(c, ofs | REF_OBSOLETE, ref_totlen(c, jeb, first_raw), NULL);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -