mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-03 18:20:25 +02:00 
			
		
		
		
	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:
		
							parent
							
								
									b32b78f892
								
							
						
					
					
						commit
						663586c0a8
					
				
					 4 changed files with 180 additions and 0 deletions
				
			
		| 
						 | 
					@ -974,6 +974,36 @@ static long ubi_cdev_ioctl(struct file *file, unsigned int cmd,
 | 
				
			||||||
		break;
 | 
							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:
 | 
						default:
 | 
				
			||||||
		err = -ENOTTY;
 | 
							err = -ENOTTY;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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);
 | 
					int ubi_is_erase_work(struct ubi_work *wrk);
 | 
				
			||||||
void ubi_refill_pools(struct ubi_device *ubi);
 | 
					void ubi_refill_pools(struct ubi_device *ubi);
 | 
				
			||||||
int ubi_ensure_anchor_pebs(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 */
 | 
					/* io.c */
 | 
				
			||||||
int ubi_io_read(const struct ubi_device *ubi, void *buf, int pnum, int offset,
 | 
					int ubi_io_read(const struct ubi_device *ubi, void *buf, int pnum, int offset,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1440,6 +1440,150 @@ int ubi_wl_flush(struct ubi_device *ubi, int vol_id, int lnum)
 | 
				
			||||||
	return err;
 | 
						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.
 | 
					 * tree_destroy - destroy an RB-tree.
 | 
				
			||||||
 * @ubi: UBI device description object
 | 
					 * @ubi: UBI device description object
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -171,6 +171,11 @@
 | 
				
			||||||
/* Re-name volumes */
 | 
					/* Re-name volumes */
 | 
				
			||||||
#define UBI_IOCRNVOL _IOW(UBI_IOC_MAGIC, 3, struct ubi_rnvol_req)
 | 
					#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 */
 | 
					/* ioctl commands of the UBI control character device */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define UBI_CTRL_IOC_MAGIC 'o'
 | 
					#define UBI_CTRL_IOC_MAGIC 'o'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue