ubi: Expose the bitrot interface

Using UBI_IOCRPEB and UBI_IOCSPEB userspace can force
reading and scrubbing of PEBs.

In case of bitflips UBI will automatically take action
and move data to a different PEB.
This interface allows a daemon to foster your NAND.

Signed-off-by: Richard Weinberger <richard@nod.at>
This commit is contained in:
Richard Weinberger 2018-11-07 23:16:19 +01:00
parent b32b78f892
commit 663586c0a8
4 changed files with 180 additions and 0 deletions

View File

@ -974,6 +974,36 @@ static long ubi_cdev_ioctl(struct file *file, unsigned int cmd,
break;
}
/* Check a specific PEB for bitflips and scrub it if needed */
case UBI_IOCRPEB:
{
int pnum;
err = get_user(pnum, (__user int32_t *)argp);
if (err) {
err = -EFAULT;
break;
}
err = ubi_bitflip_check(ubi, pnum, 0);
break;
}
/* Force scrubbing for a specific PEB */
case UBI_IOCSPEB:
{
int pnum;
err = get_user(pnum, (__user int32_t *)argp);
if (err) {
err = -EFAULT;
break;
}
err = ubi_bitflip_check(ubi, pnum, 1);
break;
}
default:
err = -ENOTTY;
break;

View File

@ -929,6 +929,7 @@ int ubi_wl_put_fm_peb(struct ubi_device *ubi, struct ubi_wl_entry *used_e,
int ubi_is_erase_work(struct ubi_work *wrk);
void ubi_refill_pools(struct ubi_device *ubi);
int ubi_ensure_anchor_pebs(struct ubi_device *ubi);
int ubi_bitflip_check(struct ubi_device *ubi, int pnum, int force_scrub);
/* io.c */
int ubi_io_read(const struct ubi_device *ubi, void *buf, int pnum, int offset,

View File

@ -1440,6 +1440,150 @@ int ubi_wl_flush(struct ubi_device *ubi, int vol_id, int lnum)
return err;
}
static bool scrub_possible(struct ubi_device *ubi, struct ubi_wl_entry *e)
{
if (in_wl_tree(e, &ubi->scrub))
return false;
else if (in_wl_tree(e, &ubi->erroneous))
return false;
else if (ubi->move_from == e)
return false;
else if (ubi->move_to == e)
return false;
return true;
}
/**
* ubi_bitflip_check - Check an eraseblock for bitflips and scrub it if needed.
* @ubi: UBI device description object
* @pnum: the physical eraseblock to schedule
* @force: dont't read the block, assume bitflips happened and take action.
*
* This function reads the given eraseblock and checks if bitflips occured.
* In case of bitflips, the eraseblock is scheduled for scrubbing.
* If scrubbing is forced with @force, the eraseblock is not read,
* but scheduled for scrubbing right away.
*
* Returns:
* %EINVAL, PEB is out of range
* %ENOENT, PEB is no longer used by UBI
* %EBUSY, PEB cannot be checked now or a check is currently running on it
* %EAGAIN, bit flips happened but scrubbing is currently not possible
* %EUCLEAN, bit flips happened and PEB is scheduled for scrubbing
* %0, no bit flips detected
*/
int ubi_bitflip_check(struct ubi_device *ubi, int pnum, int force)
{
int err;
struct ubi_wl_entry *e;
if (pnum < 0 || pnum >= ubi->peb_count) {
err = -EINVAL;
goto out;
}
/*
* Pause all parallel work, otherwise it can happen that the
* erase worker frees a wl entry under us.
*/
down_write(&ubi->work_sem);
/*
* Make sure that the wl entry does not change state while
* inspecting it.
*/
spin_lock(&ubi->wl_lock);
e = ubi->lookuptbl[pnum];
if (!e) {
spin_unlock(&ubi->wl_lock);
err = -ENOENT;
goto out_resume;
}
/*
* Does it make sense to check this PEB?
*/
if (!scrub_possible(ubi, e)) {
spin_unlock(&ubi->wl_lock);
err = -EBUSY;
goto out_resume;
}
spin_unlock(&ubi->wl_lock);
if (!force) {
mutex_lock(&ubi->buf_mutex);
err = ubi_io_read(ubi, ubi->peb_buf, pnum, 0, ubi->peb_size);
mutex_unlock(&ubi->buf_mutex);
}
if (err == UBI_IO_BITFLIPS || force) {
/*
* Okay, bit flip happened, let's figure out what we can do.
*/
spin_lock(&ubi->wl_lock);
/*
* Recheck. We released wl_lock, UBI might have killed the
* wl entry under us.
*/
e = ubi->lookuptbl[pnum];
if (!e) {
spin_unlock(&ubi->wl_lock);
err = -ENOENT;
goto out_resume;
}
/*
* Need to re-check state
*/
if (!scrub_possible(ubi, e)) {
spin_unlock(&ubi->wl_lock);
err = -EBUSY;
goto out_resume;
}
if (in_pq(ubi, e)) {
prot_queue_del(ubi, e->pnum);
wl_tree_add(e, &ubi->scrub);
spin_unlock(&ubi->wl_lock);
err = ensure_wear_leveling(ubi, 1);
} else if (in_wl_tree(e, &ubi->used)) {
rb_erase(&e->u.rb, &ubi->used);
wl_tree_add(e, &ubi->scrub);
spin_unlock(&ubi->wl_lock);
err = ensure_wear_leveling(ubi, 1);
} else if (in_wl_tree(e, &ubi->free)) {
rb_erase(&e->u.rb, &ubi->free);
ubi->free_count--;
spin_unlock(&ubi->wl_lock);
/*
* This PEB is empty we can schedule it for
* erasure right away. No wear leveling needed.
*/
err = schedule_erase(ubi, e, UBI_UNKNOWN, UBI_UNKNOWN,
force ? 0 : 1, true);
} else {
spin_unlock(&ubi->wl_lock);
err = -EAGAIN;
}
if (!err && !force)
err = -EUCLEAN;
} else {
err = 0;
}
out_resume:
up_write(&ubi->work_sem);
out:
return err;
}
/**
* tree_destroy - destroy an RB-tree.
* @ubi: UBI device description object

View File

@ -171,6 +171,11 @@
/* Re-name volumes */
#define UBI_IOCRNVOL _IOW(UBI_IOC_MAGIC, 3, struct ubi_rnvol_req)
/* Read the specified PEB and scrub it if there are bitflips */
#define UBI_IOCRPEB _IOW(UBI_IOC_MAGIC, 4, __s32)
/* Force scrubbing on the specified PEB */
#define UBI_IOCSPEB _IOW(UBI_IOC_MAGIC, 5, __s32)
/* ioctl commands of the UBI control character device */
#define UBI_CTRL_IOC_MAGIC 'o'