mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-27 19:29:37 +00:00
![Artem Bityutskiy](/assets/img/avatar_default.png)
This patch fixes merge conflict resolution breakage introduced by merge
d3712b9dfc
("Merge tag 'for-linus' of git://github.com/prasad-joshi/logfs_upstream").
The commit changed 'mtd_can_have_bb()' function and made it always
return zero, which is incorrect. Instead, we need it to return whether
the underlying flash device can have bad eraseblocks or not. UBI needs
this information because it affects how it handles the underlying flash.
E.g., if the underlying flash is NOR, it cannot have bad blocks and any
write or erase error is fatal, and all we can do is to switch to R/O
mode. We do not need to reserve a pool of good eraseblocks for bad
eraseblocks handling, and so on.
This patch also removes 'mtd_can_have_bb()' invocations from Logfs to
ensure correct Logfs behavior.
I've tested that with this patch UBI works on top of NOR and NAND
flashes emulated by mtdram and nandsim correspondingly.
This patch is based on patch from Linus Torvalds.
Signed-off-by: Artem Bityutskiy <artem.bityutskiy@linux.intel.com>
Acked-by: Jörn Engel <joern@logfs.org>
Acked-by: Prasad Joshi <prasadjoshi.linux@gmail.com>
Acked-by: Brian Norris <computersforpeace@gmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
274 lines
6.6 KiB
C
274 lines
6.6 KiB
C
/*
|
|
* fs/logfs/dev_mtd.c - Device access methods for MTD
|
|
*
|
|
* As should be obvious for Linux kernel code, license is GPLv2
|
|
*
|
|
* Copyright (c) 2005-2008 Joern Engel <joern@logfs.org>
|
|
*/
|
|
#include "logfs.h"
|
|
#include <linux/completion.h>
|
|
#include <linux/mount.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.h>
|
|
|
|
#define PAGE_OFS(ofs) ((ofs) & (PAGE_SIZE-1))
|
|
|
|
static int logfs_mtd_read(struct super_block *sb, loff_t ofs, size_t len,
|
|
void *buf)
|
|
{
|
|
struct mtd_info *mtd = logfs_super(sb)->s_mtd;
|
|
size_t retlen;
|
|
int ret;
|
|
|
|
ret = mtd_read(mtd, ofs, len, &retlen, buf);
|
|
BUG_ON(ret == -EINVAL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Not sure if we should loop instead. */
|
|
if (retlen != len)
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int loffs_mtd_write(struct super_block *sb, loff_t ofs, size_t len,
|
|
void *buf)
|
|
{
|
|
struct logfs_super *super = logfs_super(sb);
|
|
struct mtd_info *mtd = super->s_mtd;
|
|
size_t retlen;
|
|
loff_t page_start, page_end;
|
|
int ret;
|
|
|
|
if (super->s_flags & LOGFS_SB_FLAG_RO)
|
|
return -EROFS;
|
|
|
|
BUG_ON((ofs >= mtd->size) || (len > mtd->size - ofs));
|
|
BUG_ON(ofs != (ofs >> super->s_writeshift) << super->s_writeshift);
|
|
BUG_ON(len > PAGE_CACHE_SIZE);
|
|
page_start = ofs & PAGE_CACHE_MASK;
|
|
page_end = PAGE_CACHE_ALIGN(ofs + len) - 1;
|
|
ret = mtd_write(mtd, ofs, len, &retlen, buf);
|
|
if (ret || (retlen != len))
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* For as long as I can remember (since about 2001) mtd->erase has been an
|
|
* asynchronous interface lacking the first driver to actually use the
|
|
* asynchronous properties. So just to prevent the first implementor of such
|
|
* a thing from breaking logfs in 2350, we do the usual pointless dance to
|
|
* declare a completion variable and wait for completion before returning
|
|
* from logfs_mtd_erase(). What an exercise in futility!
|
|
*/
|
|
static void logfs_erase_callback(struct erase_info *ei)
|
|
{
|
|
complete((struct completion *)ei->priv);
|
|
}
|
|
|
|
static int logfs_mtd_erase_mapping(struct super_block *sb, loff_t ofs,
|
|
size_t len)
|
|
{
|
|
struct logfs_super *super = logfs_super(sb);
|
|
struct address_space *mapping = super->s_mapping_inode->i_mapping;
|
|
struct page *page;
|
|
pgoff_t index = ofs >> PAGE_SHIFT;
|
|
|
|
for (index = ofs >> PAGE_SHIFT; index < (ofs + len) >> PAGE_SHIFT; index++) {
|
|
page = find_get_page(mapping, index);
|
|
if (!page)
|
|
continue;
|
|
memset(page_address(page), 0xFF, PAGE_SIZE);
|
|
page_cache_release(page);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int logfs_mtd_erase(struct super_block *sb, loff_t ofs, size_t len,
|
|
int ensure_write)
|
|
{
|
|
struct mtd_info *mtd = logfs_super(sb)->s_mtd;
|
|
struct erase_info ei;
|
|
DECLARE_COMPLETION_ONSTACK(complete);
|
|
int ret;
|
|
|
|
BUG_ON(len % mtd->erasesize);
|
|
if (logfs_super(sb)->s_flags & LOGFS_SB_FLAG_RO)
|
|
return -EROFS;
|
|
|
|
memset(&ei, 0, sizeof(ei));
|
|
ei.mtd = mtd;
|
|
ei.addr = ofs;
|
|
ei.len = len;
|
|
ei.callback = logfs_erase_callback;
|
|
ei.priv = (long)&complete;
|
|
ret = mtd_erase(mtd, &ei);
|
|
if (ret)
|
|
return -EIO;
|
|
|
|
wait_for_completion(&complete);
|
|
if (ei.state != MTD_ERASE_DONE)
|
|
return -EIO;
|
|
return logfs_mtd_erase_mapping(sb, ofs, len);
|
|
}
|
|
|
|
static void logfs_mtd_sync(struct super_block *sb)
|
|
{
|
|
struct mtd_info *mtd = logfs_super(sb)->s_mtd;
|
|
|
|
mtd_sync(mtd);
|
|
}
|
|
|
|
static int logfs_mtd_readpage(void *_sb, struct page *page)
|
|
{
|
|
struct super_block *sb = _sb;
|
|
int err;
|
|
|
|
err = logfs_mtd_read(sb, page->index << PAGE_SHIFT, PAGE_SIZE,
|
|
page_address(page));
|
|
if (err == -EUCLEAN || err == -EBADMSG) {
|
|
/* -EBADMSG happens regularly on power failures */
|
|
err = 0;
|
|
/* FIXME: force GC this segment */
|
|
}
|
|
if (err) {
|
|
ClearPageUptodate(page);
|
|
SetPageError(page);
|
|
} else {
|
|
SetPageUptodate(page);
|
|
ClearPageError(page);
|
|
}
|
|
unlock_page(page);
|
|
return err;
|
|
}
|
|
|
|
static struct page *logfs_mtd_find_first_sb(struct super_block *sb, u64 *ofs)
|
|
{
|
|
struct logfs_super *super = logfs_super(sb);
|
|
struct address_space *mapping = super->s_mapping_inode->i_mapping;
|
|
filler_t *filler = logfs_mtd_readpage;
|
|
struct mtd_info *mtd = super->s_mtd;
|
|
|
|
*ofs = 0;
|
|
while (mtd_block_isbad(mtd, *ofs)) {
|
|
*ofs += mtd->erasesize;
|
|
if (*ofs >= mtd->size)
|
|
return NULL;
|
|
}
|
|
BUG_ON(*ofs & ~PAGE_MASK);
|
|
return read_cache_page(mapping, *ofs >> PAGE_SHIFT, filler, sb);
|
|
}
|
|
|
|
static struct page *logfs_mtd_find_last_sb(struct super_block *sb, u64 *ofs)
|
|
{
|
|
struct logfs_super *super = logfs_super(sb);
|
|
struct address_space *mapping = super->s_mapping_inode->i_mapping;
|
|
filler_t *filler = logfs_mtd_readpage;
|
|
struct mtd_info *mtd = super->s_mtd;
|
|
|
|
*ofs = mtd->size - mtd->erasesize;
|
|
while (mtd_block_isbad(mtd, *ofs)) {
|
|
*ofs -= mtd->erasesize;
|
|
if (*ofs <= 0)
|
|
return NULL;
|
|
}
|
|
*ofs = *ofs + mtd->erasesize - 0x1000;
|
|
BUG_ON(*ofs & ~PAGE_MASK);
|
|
return read_cache_page(mapping, *ofs >> PAGE_SHIFT, filler, sb);
|
|
}
|
|
|
|
static int __logfs_mtd_writeseg(struct super_block *sb, u64 ofs, pgoff_t index,
|
|
size_t nr_pages)
|
|
{
|
|
struct logfs_super *super = logfs_super(sb);
|
|
struct address_space *mapping = super->s_mapping_inode->i_mapping;
|
|
struct page *page;
|
|
int i, err;
|
|
|
|
for (i = 0; i < nr_pages; i++) {
|
|
page = find_lock_page(mapping, index + i);
|
|
BUG_ON(!page);
|
|
|
|
err = loffs_mtd_write(sb, page->index << PAGE_SHIFT, PAGE_SIZE,
|
|
page_address(page));
|
|
unlock_page(page);
|
|
page_cache_release(page);
|
|
if (err)
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void logfs_mtd_writeseg(struct super_block *sb, u64 ofs, size_t len)
|
|
{
|
|
struct logfs_super *super = logfs_super(sb);
|
|
int head;
|
|
|
|
if (super->s_flags & LOGFS_SB_FLAG_RO)
|
|
return;
|
|
|
|
if (len == 0) {
|
|
/* This can happen when the object fit perfectly into a
|
|
* segment, the segment gets written per sync and subsequently
|
|
* closed.
|
|
*/
|
|
return;
|
|
}
|
|
head = ofs & (PAGE_SIZE - 1);
|
|
if (head) {
|
|
ofs -= head;
|
|
len += head;
|
|
}
|
|
len = PAGE_ALIGN(len);
|
|
__logfs_mtd_writeseg(sb, ofs, ofs >> PAGE_SHIFT, len >> PAGE_SHIFT);
|
|
}
|
|
|
|
static void logfs_mtd_put_device(struct logfs_super *s)
|
|
{
|
|
put_mtd_device(s->s_mtd);
|
|
}
|
|
|
|
static int logfs_mtd_can_write_buf(struct super_block *sb, u64 ofs)
|
|
{
|
|
struct logfs_super *super = logfs_super(sb);
|
|
void *buf;
|
|
int err;
|
|
|
|
buf = kmalloc(super->s_writesize, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
err = logfs_mtd_read(sb, ofs, super->s_writesize, buf);
|
|
if (err)
|
|
goto out;
|
|
if (memchr_inv(buf, 0xff, super->s_writesize))
|
|
err = -EIO;
|
|
kfree(buf);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static const struct logfs_device_ops mtd_devops = {
|
|
.find_first_sb = logfs_mtd_find_first_sb,
|
|
.find_last_sb = logfs_mtd_find_last_sb,
|
|
.readpage = logfs_mtd_readpage,
|
|
.writeseg = logfs_mtd_writeseg,
|
|
.erase = logfs_mtd_erase,
|
|
.can_write_buf = logfs_mtd_can_write_buf,
|
|
.sync = logfs_mtd_sync,
|
|
.put_device = logfs_mtd_put_device,
|
|
};
|
|
|
|
int logfs_get_sb_mtd(struct logfs_super *s, int mtdnr)
|
|
{
|
|
struct mtd_info *mtd = get_mtd_device(NULL, mtdnr);
|
|
if (IS_ERR(mtd))
|
|
return PTR_ERR(mtd);
|
|
|
|
s->s_bdev = NULL;
|
|
s->s_mtd = mtd;
|
|
s->s_devops = &mtd_devops;
|
|
return 0;
|
|
}
|