]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ubi: Implement ioctl for detailed erase counters
authorRickard Andersson <rickard.andersson@axis.com>
Mon, 16 Dec 2024 08:54:20 +0000 (09:54 +0100)
committerRichard Weinberger <richard@nod.at>
Sat, 18 Jan 2025 14:32:52 +0000 (15:32 +0100)
Currently, "max_ec" can be read from sysfs, which provides a limited
view of the flash device’s wear. In certain cases, such as bugs in
the wear-leveling algorithm, specific blocks can be worn down more
than others, resulting in uneven wear distribution. Also some use cases
can wear the erase blocks of the fastmap area more heavily than other
parts of flash.
Providing detailed erase counter values give a better understanding of
the overall flash wear and is needed to be able to calculate for example
expected life time.
There exists more detailed info in debugfs, but this information is
only available for debug builds.

Signed-off-by: Rickard Andersson <rickard.andersson@axis.com>
Tested-by: Zhihao Cheng <chengzhihao1@huawei.com>
Reviewed-by: Zhihao Cheng <chengzhihao1@huawei.com>
Signed-off-by: Richard Weinberger <richard@nod.at>
drivers/mtd/ubi/cdev.c

index 6bb80d7714bc8ba61104b84166e0f313b152583a..4c3e4edb68532b758cffe425440db4e68a6cc191 100644 (file)
@@ -828,6 +828,69 @@ out_free:
        return err;
 }
 
+static int ubi_get_ec_info(struct ubi_device *ubi, struct ubi_ecinfo_req __user *ureq)
+{
+       struct ubi_ecinfo_req req;
+       struct ubi_wl_entry *wl;
+       int read_cnt;
+       int peb;
+       int end_peb;
+
+       /* Copy the input arguments */
+       if (copy_from_user(&req, ureq, sizeof(struct ubi_ecinfo_req)))
+               return -EFAULT;
+
+       /* Check input arguments */
+       if (req.length <= 0 || req.start < 0 || req.start >= ubi->peb_count)
+               return -EINVAL;
+
+       if (check_add_overflow(req.start, req.length, &end_peb))
+               return -EINVAL;
+
+       if (end_peb > ubi->peb_count)
+               end_peb = ubi->peb_count;
+
+       /* Check access rights before filling erase_counters array */
+       if (!access_ok(ureq->erase_counters, (end_peb-req.start) * sizeof(int32_t)))
+               return -EFAULT;
+
+       /* Fill erase counter array */
+       read_cnt = 0;
+       for (peb = req.start; peb < end_peb; read_cnt++, peb++) {
+               int ec;
+
+               if (ubi_io_is_bad(ubi, peb)) {
+                       if (__put_user(UBI_UNKNOWN, ureq->erase_counters+read_cnt))
+                               return -EFAULT;
+
+                       continue;
+               }
+
+               spin_lock(&ubi->wl_lock);
+
+               wl = ubi->lookuptbl[peb];
+               if (wl)
+                       ec = wl->ec;
+               else
+                       ec = UBI_UNKNOWN;
+
+               spin_unlock(&ubi->wl_lock);
+
+               if (__put_user(ec, ureq->erase_counters+read_cnt))
+                       return -EFAULT;
+
+       }
+
+       /* Return actual read length */
+       req.read_length = read_cnt;
+
+       /* Copy everything except erase counter array */
+       if (copy_to_user(ureq, &req, sizeof(struct ubi_ecinfo_req)))
+               return -EFAULT;
+
+       return 0;
+}
+
 static long ubi_cdev_ioctl(struct file *file, unsigned int cmd,
                           unsigned long arg)
 {
@@ -991,6 +1054,12 @@ static long ubi_cdev_ioctl(struct file *file, unsigned int cmd,
                break;
        }
 
+       case UBI_IOCECNFO:
+       {
+               err = ubi_get_ec_info(ubi, argp);
+               break;
+       }
+
        default:
                err = -ENOTTY;
                break;