Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0cd099a0 authored by Dolev Raviv's avatar Dolev Raviv
Browse files

mtd: ubi: modify scan_peb sysfs to scrub_all



One of the limitations of the NAND devices is the method used to read NAND
flash memory may cause bit-flips on the surrounding cells and result in
uncorrectable ECC errors. This is known as the read disturb or data
retention. In order to prevent data loss due to the above, all PEBs should
be scrubbed periodically.
In order to solve the problem above, using the 'scan all' sysfs, all the
device PEBs are scheduled for erasure if free, and scheduled for scrubbing
otherwise. To make sure all the PEBs are erased/scrubbed after reset, the
fastmap is flushed at the end.

Change-Id: I0c38907f7086195d64c3f5e5d867b1c965a859c9
Signed-off-by: default avatarDolev Raviv <draviv@codeaurora.org>
parent dca34e2b
Loading
Loading
Loading
Loading
+11 −9
Original line number Diff line number Diff line
/*
 * Copyright (c) International Business Machines Corp., 2006
 * Copyright (c) Nokia Corporation, 2007
 * Copyright (c) 2014, Linux Foundation. All rights reserved.
 * Copyright (c) 2014 - 2015, Linux Foundation. All rights reserved.
 * Linux Foundation chooses to take subject only to the GPLv2
 * license terms, and distributes only under these terms.
 *
@@ -152,8 +152,8 @@ static struct device_attribute dev_dt_threshold =
static struct device_attribute dev_rd_threshold =
	__ATTR(rd_threshold, S_IRUGO | S_IWUGO, dev_attribute_show,
		   dev_attribute_store);
static struct device_attribute dev_mtd_trigger_scan =
	__ATTR(peb_scan, S_IRUGO | S_IWUSR,
static struct device_attribute dev_mtd_trigger_scrub =
	__ATTR(peb_scrub, S_IRUGO | S_IWUSR,
		dev_attribute_show, dev_attribute_store);

/**
@@ -396,8 +396,8 @@ static ssize_t dev_attribute_show(struct device *dev,
		ret = sprintf(buf, "%d\n", ubi->dt_threshold);
	else if (attr == &dev_rd_threshold)
		ret = sprintf(buf, "%d\n", ubi->rd_threshold);
	else if (attr == &dev_mtd_trigger_scan)
		ret = sprintf(buf, "%d\n", ubi->scan_in_progress);
	else if (attr == &dev_mtd_trigger_scrub)
		ret = snprintf(buf, 3, "%d\n", ubi->scrub_in_progress);
	else
		ret = -EINVAL;

@@ -435,7 +435,7 @@ static ssize_t dev_attribute_store(struct device *dev,
		else
			pr_err("Max supported threshold value is %d",
				   UBI_MAX_READCOUNTER);
	} else if (attr == &dev_mtd_trigger_scan) {
	} else if (attr == &dev_mtd_trigger_scrub) {
		if (value != 1) {
			pr_err("Invalid input. Echo 1 to start trigger");
			goto out;
@@ -444,10 +444,12 @@ static ssize_t dev_attribute_store(struct device *dev,
			pr_err("lookuptbl is null");
			goto out;
		}
		ret = ubi_wl_scan_all(ubi);
		ret = ubi_wl_scrub_all(ubi);
	}

out:
	if (ret == 0)
		ret = count;
	ubi_put_device(ubi);
	return ret;
}
@@ -520,7 +522,7 @@ static int ubi_sysfs_init(struct ubi_device *ubi, int *ref)
	err = device_create_file(&ubi->dev, &dev_rd_threshold);
	if (err)
		return err;
	err = device_create_file(&ubi->dev, &dev_mtd_trigger_scan);
	err = device_create_file(&ubi->dev, &dev_mtd_trigger_scrub);
	return err;
}

@@ -530,7 +532,7 @@ static int ubi_sysfs_init(struct ubi_device *ubi, int *ref)
 */
static void ubi_sysfs_close(struct ubi_device *ubi)
{
	device_remove_file(&ubi->dev, &dev_mtd_trigger_scan);
	device_remove_file(&ubi->dev, &dev_mtd_trigger_scrub);
	device_remove_file(&ubi->dev, &dev_mtd_num);
	device_remove_file(&ubi->dev, &dev_dt_threshold);
	device_remove_file(&ubi->dev, &dev_rd_threshold);
+13 −4
Original line number Diff line number Diff line
/*
 * Copyright (c) International Business Machines Corp., 2006
 * Copyright (c) Nokia Corporation, 2006, 2007
 * Copyright (c) 2014, Linux Foundation. All rights reserved.
 * Copyright (c) 2014 - 2015, Linux Foundation. All rights reserved.
 * Linux Foundation chooses to take subject only to the GPLv2
 * license terms, and distributes only under these terms.
 *
@@ -124,6 +124,14 @@
#define UBI_DFS_DIR_NAME "ubi%d"
#define UBI_DFS_DIR_LEN  (3 + 2 + 1)

/*
 * When scrub_all is triggered, all free PEBs will be scheduled for erasure.
 * Until the bg thread performs the work, we are left with now free PEBs.
 * To make sure we can flush the fm, UBI_FM_MAX_BLOCKS are erased synchroniusly
 * before scrub_all is returning.
 */
#define NUM_PEBS_TO_SYNC_ERASE UBI_FM_MAX_BLOCKS

/*
 * Error codes returned by the I/O sub-system.
 *
@@ -493,7 +501,8 @@ struct ubi_debug_info {
 *				for more info
 * @dt_threshold: data retention threshold. See UBI_DT_THRESHOLD
 *				for more info
 * @scan_in_progress: true if scanning of device PEBs is in progress
 * @scrub_in_progress: true while scheduling all device PEBs for scrub/erase
 * is in progress
 *
 * @flash_size: underlying MTD device size (in bytes)
 * @peb_count: count of physical eraseblocks on the MTD device
@@ -598,7 +607,7 @@ struct ubi_device {
	char bgt_name[sizeof(UBI_BGT_NAME_PATTERN)+2];
	int rd_threshold;
	int dt_threshold;
	bool scan_in_progress;
	bool scrub_in_progress;


	/* I/O sub-system's stuff */
@@ -879,7 +888,7 @@ 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_in_wl_tree(struct ubi_wl_entry *e, struct rb_root *root);
int ubi_wl_scan_all(struct ubi_device *ubi);
int ubi_wl_scrub_all(struct ubi_device *ubi);

/* io.c */
int ubi_io_read(const struct ubi_device *ubi, void *buf, int pnum, int offset,
+109 −93
Original line number Diff line number Diff line
/*
 * Copyright (c) International Business Machines Corp., 2006
 * Copyright (c) 2014, Linux Foundation. All rights reserved.
 * Copyright (c) 2014 - 2015, Linux Foundation. All rights reserved.
 * Linux Foundation chooses to take subject only to the GPLv2
 * license terms, and distributes only under these terms.
 *
@@ -145,6 +145,8 @@ static int self_check_in_pq(const struct ubi_device *ubi,
			    struct ubi_wl_entry *e);
static int schedule_erase(struct ubi_device *ubi, struct ubi_wl_entry *e,
			  int vol_id, int lnum, int torture);
static int sync_erase(struct ubi_device *ubi, struct ubi_wl_entry *e,
		      int torture);

#ifdef CONFIG_MTD_UBI_FASTMAP
/**
@@ -561,11 +563,8 @@ retry:
static void return_unused_pool_pebs(struct ubi_device *ubi,
				    struct ubi_fm_pool *pool)
{
	int i, err;
	int i;
	struct ubi_wl_entry *e;
	struct timeval tv;

	do_gettimeofday(&tv);

	for (i = pool->used; i < pool->size; i++) {
		e = ubi->lookuptbl[pool->pebs[i]];
@@ -576,24 +575,10 @@ static void return_unused_pool_pebs(struct ubi_device *ubi,
			self_check_in_wl_tree(ubi, e, &ubi->scrub);
			rb_erase(&e->u.rb, &ubi->scrub);
		}
		if (e->last_erase_time + UBI_DT_THRESHOLD <
			 (tv.tv_sec / NUM_SEC_IN_DAY)) {
			spin_unlock(&ubi->wl_lock);
			err = schedule_erase(ubi, e, UBI_UNKNOWN,
					 UBI_UNKNOWN, 0);
			spin_lock(&ubi->wl_lock);
			if (err) {
				ubi_err(ubi->ubi_num,
				"Failed to schedule erase for PEB %d (err=%d)",
					e->pnum, err);
				ubi_ro_mode(ubi);
			}
		} else {
		wl_tree_add(e, &ubi->free);
		ubi->free_count++;
	}
}
}

/**
 * refill_wl_pool - refills all the fastmap pool used by the
@@ -757,120 +742,151 @@ int ubi_wl_get_peb(struct ubi_device *ubi)
 * ubi_wl_scan_all - Scan all PEB's
 * @ubi: UBI device description object
 *
 * This function scans all device PEBs in order to locate once
 * need scrubbing; due to read disturb threashold or last erase
 * timestamp.
 * This function schedules all device PEBs for erasure if free, or for
 * scrubbing otherwise. This trigger is used to prevent data loss due to read
 * disturb, data retention.
 *
 * Return 0 in case of sucsess, (negative) error code otherwise
 * Return 0 in case of success, (negative) error code otherwise
 *
 */
int ubi_wl_scan_all(struct ubi_device *ubi)
int ubi_wl_scrub_all(struct ubi_device *ubi)
{
	struct timeval tv;
	struct rb_node *node;
	struct ubi_wl_entry *wl_e, *tmp;
	int used_cnt, free_cnt;
	int err;
	int i, err = 0;
	struct ubi_wl_entry *sync_erase_q[NUM_PEBS_TO_SYNC_ERASE] = {0};
	int sync_erase_pos = 0;

	do_gettimeofday(&tv);
	if (!ubi->lookuptbl) {
		ubi_err(ubi->ubi_num, "lookuptbl is null");
		return -ENOENT;
	}

	spin_lock(&ubi->wl_lock);
	if (ubi->scan_in_progress) {
	if (ubi->scrub_in_progress) {
		ubi_err(ubi->ubi_num,
			"Scan already in progress, ignoring the trigger");
		err = -EPERM;
		goto out;
		spin_unlock(&ubi->wl_lock);
		return err;
	}
	ubi->scan_in_progress = true;
	ubi->scrub_in_progress = true;
	/* stop all works in order to freeze system state */
	ubi->thread_enabled = 0;
	spin_unlock(&ubi->wl_lock);
	down_write(&ubi->work_sem);
	up_write(&ubi->work_sem);

	ubi_msg(ubi->ubi_num,
		"Scanning all PEBs for read-disturb/erasures");
	/* For PEBs in free list rc=0 */
	free_cnt = 0;
	node = rb_first(&ubi->free);
	while (node) {
	/*
	 * fm_mutex prevents fastmap flush.
	 * Without FM flush there is no pools refill.
	 * When the pools are empty, there are no available PEBSs for write.
	 * Thus prevent PEBS's from moving under our feet.
	 *
	 * Keep the wl_lock, while iterating the wl data structures.
	 */
	mutex_lock(&ubi->fm_mutex);
	spin_lock(&ubi->wl_lock);

	ubi_msg(ubi->ubi_num, "Scheduling all PEBs for scrub/erasure");

	/*
	 * Flush the pools into the free list before erasing all the
	 * PEBS in the free list.
	 */
	return_unused_pool_pebs(ubi, &ubi->fm_wl_pool);
	ubi->fm_wl_pool.used = ubi->fm_wl_pool.size = 0;
	return_unused_pool_pebs(ubi, &ubi->fm_pool);
	ubi->fm_pool.used = ubi->fm_pool.size = 0;

	/* PEBs in free list */
	while ((node = rb_first(&ubi->free)) != NULL) {
		wl_e = rb_entry(node, struct ubi_wl_entry, u.rb);
		node = rb_next(node);
		if (wl_e->last_erase_time + UBI_DT_THRESHOLD <
			 (tv.tv_sec / NUM_SEC_IN_DAY)) {
		/* Sanity check to verify consistency */
		if (self_check_in_wl_tree(ubi, wl_e, &ubi->free)) {
				ubi_err(ubi->ubi_num,
					"PEB %d moved from free tree",
			ubi_err(ubi->ubi_num, "PEB %d moved from free tree",
				wl_e->pnum);
			err = -EAGAIN;
			spin_unlock(&ubi->wl_lock);
			goto out;
		}
		rb_erase(&wl_e->u.rb, &ubi->free);
		ubi->free_count--;
		if (sync_erase_pos < NUM_PEBS_TO_SYNC_ERASE) {
			sync_erase_q[sync_erase_pos++] = wl_e;
		} else {
			spin_unlock(&ubi->wl_lock);
			err = schedule_erase(ubi, wl_e, UBI_UNKNOWN,
					 UBI_UNKNOWN, 0);
			spin_lock(&ubi->wl_lock);
		}
		if (err) {
			ubi_err(ubi->ubi_num,
				"Failed to schedule erase for PEB %d (err=%d)",
				wl_e->pnum, err);
			ubi_ro_mode(ubi);
			spin_unlock(&ubi->wl_lock);
			goto out;
		}
			free_cnt++;
		}
	}

	used_cnt = 0;
	node = rb_first(&ubi->used);
	while (node) {
	/* Move all used pebs to scrub tree */
	while ((node = rb_first(&ubi->used)) != NULL) {
		wl_e = rb_entry(node, struct ubi_wl_entry, u.rb);
		node = rb_next(node);
		if ((wl_e->rc >= UBI_RD_THRESHOLD) ||
			(wl_e->last_erase_time +
			 UBI_DT_THRESHOLD < (tv.tv_sec / NUM_SEC_IN_DAY))) {
		rb_erase(&wl_e->u.rb, &ubi->used);
		wl_tree_add(wl_e, &ubi->scrub);
	}

	/* Go over protection queue */
	for (i = 0; i < UBI_PROT_QUEUE_LEN; i++) {
		list_for_each_entry_safe(wl_e, tmp, &ubi->pq[i], u.list) {
			spin_unlock(&ubi->wl_lock);
			err = ubi_wl_scrub_peb(ubi, wl_e->pnum);
			spin_lock(&ubi->wl_lock);
			if (err)
				ubi_err(ubi->ubi_num,
					"Failed to schedule scrub for PEB %d (err=%d)",
					wl_e->pnum, err);
			else
				used_cnt++;
			spin_lock(&ubi->wl_lock);
		}
	}

	/* Go over protection queue */
	list_for_each_entry_safe(wl_e, tmp, &ubi->pq[ubi->pq_head], u.list) {
		if ((wl_e->rc >= UBI_RD_THRESHOLD) ||
			(wl_e->last_erase_time +
			 UBI_DT_THRESHOLD < (tv.tv_sec / NUM_SEC_IN_DAY))) {
	spin_unlock(&ubi->wl_lock);
			err = ubi_wl_scrub_peb(ubi, wl_e->pnum);
	for (i = 0; i < sync_erase_pos; i++) {
		wl_e = sync_erase_q[i];
		err = sync_erase(ubi, wl_e, 0);
		if (err) {
			ubi_err(ubi->ubi_num, "Failed to erase PEB %d (err=%d)",
				wl_e->pnum, err);
			err = schedule_erase(ubi, wl_e, UBI_UNKNOWN,
					UBI_UNKNOWN, 0);
			if (err)
				ubi_err(ubi->ubi_num,
				"Failed to schedule scrub for PEB %d (err=%d)",
				ubi_err(ubi->ubi_num, "Failed to schedule scrub for PEB %d (err=%d)",
					wl_e->pnum, err);
			else
				used_cnt++;
			spin_lock(&ubi->wl_lock);
		}
		/* even if have errors we still have to return those PEB's */
		spin_lock(&ubi->wl_lock);
		wl_tree_add(wl_e, &ubi->free);
		ubi->free_count++;
		spin_unlock(&ubi->wl_lock);
	}

out:
	mutex_unlock(&ubi->fm_mutex);

	/* Resume the worker thread */
	spin_lock(&ubi->wl_lock);
	ubi->thread_enabled = 1;
	spin_unlock(&ubi->wl_lock);
	ubi_msg(ubi->ubi_num, "Scheduled %d for erasure", free_cnt);
	ubi_msg(ubi->ubi_num, "Scehduled %d for scrubbing", used_cnt);
	err = ubi_wl_flush(ubi, UBI_ALL, UBI_ALL);
	if (err)
		ubi_err(ubi->ubi_num, "Failed to flush ubi wq. err = %d", err);
	else
		ubi_msg(ubi->ubi_num, "Flashed ubi wq");
	if (!ubi_dbg_is_bgt_disabled(ubi))
		wake_up_process(ubi->bgt_thread);

	/* Make sure all PEBs are scrubed after reset */
	err = ubi_update_fastmap(ubi);

	spin_lock(&ubi->wl_lock);
out:
	ubi->scan_in_progress = false;
	ubi->scrub_in_progress = false;
	spin_unlock(&ubi->wl_lock);
	ubi_msg(ubi->ubi_num, "Scanning all PEBs completed. err = %d", err);
	ubi_msg(ubi->ubi_num, "Scrubbing all PEBs completed. err = %d", err);

	return err;
}