diff options
| author | Joern Engel <joern@logfs.org> | 2010-05-07 13:38:40 -0400 |
|---|---|---|
| committer | Joern Engel <joern@logfs.org> | 2010-05-07 13:38:40 -0400 |
| commit | 6f485b41875dbf5160c1990322469c1f65f77b28 (patch) | |
| tree | 9912cee9517b57c2cb3c0318861f2a9eeb4139b2 | |
| parent | ccf31c10f125ab5233c8517f91d4b3bd0bd60936 (diff) | |
logfs: handle powerfail on NAND flash
The write buffer may not have been written and may no longer be written
due to an interrupted write in the affected page.
Signed-off-by: Joern Engel <joern@logfs.org>
| -rw-r--r-- | fs/logfs/dev_bdev.c | 6 | ||||
| -rw-r--r-- | fs/logfs/dev_mtd.c | 24 | ||||
| -rw-r--r-- | fs/logfs/gc.c | 49 | ||||
| -rw-r--r-- | fs/logfs/logfs.h | 2 |
4 files changed, 52 insertions, 29 deletions
diff --git a/fs/logfs/dev_bdev.c b/fs/logfs/dev_bdev.c index 243c00071f76..9bd2ce2a3040 100644 --- a/fs/logfs/dev_bdev.c +++ b/fs/logfs/dev_bdev.c | |||
| @@ -303,6 +303,11 @@ static void bdev_put_device(struct super_block *sb) | |||
| 303 | close_bdev_exclusive(logfs_super(sb)->s_bdev, FMODE_READ|FMODE_WRITE); | 303 | close_bdev_exclusive(logfs_super(sb)->s_bdev, FMODE_READ|FMODE_WRITE); |
| 304 | } | 304 | } |
| 305 | 305 | ||
| 306 | static int bdev_can_write_buf(struct super_block *sb, u64 ofs) | ||
| 307 | { | ||
| 308 | return 0; | ||
| 309 | } | ||
| 310 | |||
| 306 | static const struct logfs_device_ops bd_devops = { | 311 | static const struct logfs_device_ops bd_devops = { |
| 307 | .find_first_sb = bdev_find_first_sb, | 312 | .find_first_sb = bdev_find_first_sb, |
| 308 | .find_last_sb = bdev_find_last_sb, | 313 | .find_last_sb = bdev_find_last_sb, |
| @@ -310,6 +315,7 @@ static const struct logfs_device_ops bd_devops = { | |||
| 310 | .readpage = bdev_readpage, | 315 | .readpage = bdev_readpage, |
| 311 | .writeseg = bdev_writeseg, | 316 | .writeseg = bdev_writeseg, |
| 312 | .erase = bdev_erase, | 317 | .erase = bdev_erase, |
| 318 | .can_write_buf = bdev_can_write_buf, | ||
| 313 | .sync = bdev_sync, | 319 | .sync = bdev_sync, |
| 314 | .put_device = bdev_put_device, | 320 | .put_device = bdev_put_device, |
| 315 | }; | 321 | }; |
diff --git a/fs/logfs/dev_mtd.c b/fs/logfs/dev_mtd.c index b02a4020241a..a85d47d13e4b 100644 --- a/fs/logfs/dev_mtd.c +++ b/fs/logfs/dev_mtd.c | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include <linux/completion.h> | 9 | #include <linux/completion.h> |
| 10 | #include <linux/mount.h> | 10 | #include <linux/mount.h> |
| 11 | #include <linux/sched.h> | 11 | #include <linux/sched.h> |
| 12 | #include <linux/slab.h> | ||
| 12 | 13 | ||
| 13 | #define PAGE_OFS(ofs) ((ofs) & (PAGE_SIZE-1)) | 14 | #define PAGE_OFS(ofs) ((ofs) & (PAGE_SIZE-1)) |
| 14 | 15 | ||
| @@ -126,7 +127,8 @@ static int mtd_readpage(void *_sb, struct page *page) | |||
| 126 | 127 | ||
| 127 | err = mtd_read(sb, page->index << PAGE_SHIFT, PAGE_SIZE, | 128 | err = mtd_read(sb, page->index << PAGE_SHIFT, PAGE_SIZE, |
| 128 | page_address(page)); | 129 | page_address(page)); |
| 129 | if (err == -EUCLEAN) { | 130 | if (err == -EUCLEAN || err == -EBADMSG) { |
| 131 | /* -EBADMSG happens regularly on power failures */ | ||
| 130 | err = 0; | 132 | err = 0; |
| 131 | /* FIXME: force GC this segment */ | 133 | /* FIXME: force GC this segment */ |
| 132 | } | 134 | } |
| @@ -233,12 +235,32 @@ static void mtd_put_device(struct super_block *sb) | |||
| 233 | put_mtd_device(logfs_super(sb)->s_mtd); | 235 | put_mtd_device(logfs_super(sb)->s_mtd); |
| 234 | } | 236 | } |
| 235 | 237 | ||
| 238 | static int mtd_can_write_buf(struct super_block *sb, u64 ofs) | ||
| 239 | { | ||
| 240 | struct logfs_super *super = logfs_super(sb); | ||
| 241 | void *buf; | ||
| 242 | int err; | ||
| 243 | |||
| 244 | buf = kmalloc(super->s_writesize, GFP_KERNEL); | ||
| 245 | if (!buf) | ||
| 246 | return -ENOMEM; | ||
| 247 | err = mtd_read(sb, ofs, super->s_writesize, buf); | ||
| 248 | if (err) | ||
| 249 | goto out; | ||
| 250 | if (memchr_inv(buf, 0xff, super->s_writesize)) | ||
| 251 | err = -EIO; | ||
| 252 | kfree(buf); | ||
| 253 | out: | ||
| 254 | return err; | ||
| 255 | } | ||
| 256 | |||
| 236 | static const struct logfs_device_ops mtd_devops = { | 257 | static const struct logfs_device_ops mtd_devops = { |
| 237 | .find_first_sb = mtd_find_first_sb, | 258 | .find_first_sb = mtd_find_first_sb, |
| 238 | .find_last_sb = mtd_find_last_sb, | 259 | .find_last_sb = mtd_find_last_sb, |
| 239 | .readpage = mtd_readpage, | 260 | .readpage = mtd_readpage, |
| 240 | .writeseg = mtd_writeseg, | 261 | .writeseg = mtd_writeseg, |
| 241 | .erase = mtd_erase, | 262 | .erase = mtd_erase, |
| 263 | .can_write_buf = mtd_can_write_buf, | ||
| 242 | .sync = mtd_sync, | 264 | .sync = mtd_sync, |
| 243 | .put_device = mtd_put_device, | 265 | .put_device = mtd_put_device, |
| 244 | }; | 266 | }; |
diff --git a/fs/logfs/gc.c b/fs/logfs/gc.c index 76c242fbe1b0..caa4419285dc 100644 --- a/fs/logfs/gc.c +++ b/fs/logfs/gc.c | |||
| @@ -122,7 +122,7 @@ static void logfs_cleanse_block(struct super_block *sb, u64 ofs, u64 ino, | |||
| 122 | logfs_safe_iput(inode, cookie); | 122 | logfs_safe_iput(inode, cookie); |
| 123 | } | 123 | } |
| 124 | 124 | ||
| 125 | static u32 logfs_gc_segment(struct super_block *sb, u32 segno, u8 dist) | 125 | static u32 logfs_gc_segment(struct super_block *sb, u32 segno) |
| 126 | { | 126 | { |
| 127 | struct logfs_super *super = logfs_super(sb); | 127 | struct logfs_super *super = logfs_super(sb); |
| 128 | struct logfs_segment_header sh; | 128 | struct logfs_segment_header sh; |
| @@ -401,7 +401,7 @@ static int __logfs_gc_once(struct super_block *sb, struct gc_candidate *cand) | |||
| 401 | segno, (u64)segno << super->s_segshift, | 401 | segno, (u64)segno << super->s_segshift, |
| 402 | dist, no_free_segments(sb), valid, | 402 | dist, no_free_segments(sb), valid, |
| 403 | super->s_free_bytes); | 403 | super->s_free_bytes); |
| 404 | cleaned = logfs_gc_segment(sb, segno, dist); | 404 | cleaned = logfs_gc_segment(sb, segno); |
| 405 | log_gc("GC segment #%02x complete - now %x valid\n", segno, | 405 | log_gc("GC segment #%02x complete - now %x valid\n", segno, |
| 406 | valid - cleaned); | 406 | valid - cleaned); |
| 407 | BUG_ON(cleaned != valid); | 407 | BUG_ON(cleaned != valid); |
| @@ -632,38 +632,31 @@ static int check_area(struct super_block *sb, int i) | |||
| 632 | { | 632 | { |
| 633 | struct logfs_super *super = logfs_super(sb); | 633 | struct logfs_super *super = logfs_super(sb); |
| 634 | struct logfs_area *area = super->s_area[i]; | 634 | struct logfs_area *area = super->s_area[i]; |
| 635 | struct logfs_object_header oh; | 635 | gc_level_t gc_level; |
| 636 | u32 cleaned, valid, ec; | ||
| 636 | u32 segno = area->a_segno; | 637 | u32 segno = area->a_segno; |
| 637 | u32 ofs = area->a_used_bytes; | 638 | u64 ofs = dev_ofs(sb, area->a_segno, area->a_written_bytes); |
| 638 | __be32 crc; | ||
| 639 | int err; | ||
| 640 | 639 | ||
| 641 | if (!area->a_is_open) | 640 | if (!area->a_is_open) |
| 642 | return 0; | 641 | return 0; |
| 643 | 642 | ||
| 644 | for (ofs = area->a_used_bytes; | 643 | if (super->s_devops->can_write_buf(sb, ofs) == 0) |
| 645 | ofs <= super->s_segsize - sizeof(oh); | 644 | return 0; |
| 646 | ofs += (u32)be16_to_cpu(oh.len) + sizeof(oh)) { | ||
| 647 | err = wbuf_read(sb, dev_ofs(sb, segno, ofs), sizeof(oh), &oh); | ||
| 648 | if (err) | ||
| 649 | return err; | ||
| 650 | |||
| 651 | if (!memchr_inv(&oh, 0xff, sizeof(oh))) | ||
| 652 | break; | ||
| 653 | 645 | ||
| 654 | crc = logfs_crc32(&oh, sizeof(oh) - 4, 4); | 646 | printk(KERN_INFO"LogFS: Possibly incomplete write at %llx\n", ofs); |
| 655 | if (crc != oh.crc) { | 647 | /* |
| 656 | printk(KERN_INFO "interrupted header at %llx\n", | 648 | * The device cannot write back the write buffer. Most likely the |
| 657 | dev_ofs(sb, segno, ofs)); | 649 | * wbuf was already written out and the system crashed at some point |
| 658 | return 0; | 650 | * before the journal commit happened. In that case we wouldn't have |
| 659 | } | 651 | * to do anything. But if the crash happened before the wbuf was |
| 660 | } | 652 | * written out correctly, we must GC this segment. So assume the |
| 661 | if (ofs != area->a_used_bytes) { | 653 | * worst and always do the GC run. |
| 662 | printk(KERN_INFO "%x bytes unaccounted data found at %llx\n", | 654 | */ |
| 663 | ofs - area->a_used_bytes, | 655 | area->a_is_open = 0; |
| 664 | dev_ofs(sb, segno, area->a_used_bytes)); | 656 | valid = logfs_valid_bytes(sb, segno, &ec, &gc_level); |
| 665 | area->a_used_bytes = ofs; | 657 | cleaned = logfs_gc_segment(sb, segno); |
| 666 | } | 658 | if (cleaned != valid) |
| 659 | return -EIO; | ||
| 667 | return 0; | 660 | return 0; |
| 668 | } | 661 | } |
| 669 | 662 | ||
diff --git a/fs/logfs/logfs.h b/fs/logfs/logfs.h index 26a9458e6b13..93b55f337245 100644 --- a/fs/logfs/logfs.h +++ b/fs/logfs/logfs.h | |||
| @@ -144,6 +144,7 @@ struct logfs_area_ops { | |||
| 144 | * @erase: erase one segment | 144 | * @erase: erase one segment |
| 145 | * @read: read from the device | 145 | * @read: read from the device |
| 146 | * @erase: erase part of the device | 146 | * @erase: erase part of the device |
| 147 | * @can_write_buf: decide whether wbuf can be written to ofs | ||
| 147 | */ | 148 | */ |
| 148 | struct logfs_device_ops { | 149 | struct logfs_device_ops { |
| 149 | struct page *(*find_first_sb)(struct super_block *sb, u64 *ofs); | 150 | struct page *(*find_first_sb)(struct super_block *sb, u64 *ofs); |
| @@ -153,6 +154,7 @@ struct logfs_device_ops { | |||
| 153 | void (*writeseg)(struct super_block *sb, u64 ofs, size_t len); | 154 | void (*writeseg)(struct super_block *sb, u64 ofs, size_t len); |
| 154 | int (*erase)(struct super_block *sb, loff_t ofs, size_t len, | 155 | int (*erase)(struct super_block *sb, loff_t ofs, size_t len, |
| 155 | int ensure_write); | 156 | int ensure_write); |
| 157 | int (*can_write_buf)(struct super_block *sb, u64 ofs); | ||
| 156 | void (*sync)(struct super_block *sb); | 158 | void (*sync)(struct super_block *sb); |
| 157 | void (*put_device)(struct super_block *sb); | 159 | void (*put_device)(struct super_block *sb); |
| 158 | }; | 160 | }; |
