mtd: ubi: provide NVMEM layer over UBI volumes
authorDaniel Golle <daniel@makrotopia.org>
Tue, 19 Dec 2023 02:33:48 +0000 (02:33 +0000)
committerRichard Weinberger <richard@nod.at>
Sun, 25 Feb 2024 21:42:23 +0000 (22:42 +0100)
In an ideal world we would like UBI to be used where ever possible on a
NAND chip. And with UBI support in ARM Trusted Firmware and U-Boot it
is possible to achieve an (almost-)all-UBI flash layout. Hence the need
for a way to also use UBI volumes to store board-level constants, such
as MAC addresses and calibration data of wireless interfaces.

Add UBI volume NVMEM driver module exposing UBI volumes as NVMEM
providers. Allow UBI devices to have a "volumes" firmware subnode with
volumes which may be compatible with "nvmem-cells".
Access to UBI volumes via the NVMEM interface at this point is
read-only, and it is slow, opening and closing the UBI volume for each
access due to limitations of the NVMEM provider API.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Signed-off-by: Richard Weinberger <richard@nod.at>
drivers/mtd/ubi/Kconfig
drivers/mtd/ubi/Makefile
drivers/mtd/ubi/nvmem.c [new file with mode: 0644]

index 7499a540121e8b2b41e2941f1448e0691028b1e0..e28a3af83c0eb8dd6d13c382049c188bdaa8fd04 100644 (file)
@@ -113,4 +113,17 @@ config MTD_UBI_FAULT_INJECTION
           testing purposes.
 
           If in doubt, say "N".
+
+config MTD_UBI_NVMEM
+       tristate "UBI virtual NVMEM"
+       default n
+       depends on NVMEM
+       help
+          This option enabled an additional driver exposing UBI volumes as NVMEM
+          providers, intended for platforms where UBI is part of the firmware
+          specification and used to store also e.g. MAC addresses or board-
+          specific Wi-Fi calibration data.
+
+          If in doubt, say "N".
+
 endif # MTD_UBI
index 543673605ca7298c4804874b82a3907397499f4e..4b51aaf00d1a2aa8bb591bd281ccf7e96c18e7ff 100644 (file)
@@ -7,3 +7,4 @@ ubi-$(CONFIG_MTD_UBI_FASTMAP) += fastmap.o
 ubi-$(CONFIG_MTD_UBI_BLOCK) += block.o
 
 obj-$(CONFIG_MTD_UBI_GLUEBI) += gluebi.o
+obj-$(CONFIG_MTD_UBI_NVMEM) += nvmem.o
diff --git a/drivers/mtd/ubi/nvmem.c b/drivers/mtd/ubi/nvmem.c
new file mode 100644 (file)
index 0000000..b7a93c4
--- /dev/null
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Daniel Golle <daniel@makrotopia.org>
+ */
+
+/* UBI NVMEM provider */
+#include "ubi.h"
+#include <linux/nvmem-provider.h>
+#include <asm/div64.h>
+
+/* List of all NVMEM devices */
+static LIST_HEAD(nvmem_devices);
+static DEFINE_MUTEX(devices_mutex);
+
+struct ubi_nvmem {
+       struct nvmem_device *nvmem;
+       int ubi_num;
+       int vol_id;
+       int usable_leb_size;
+       struct list_head list;
+};
+
+static int ubi_nvmem_reg_read(void *priv, unsigned int from,
+                             void *val, size_t bytes)
+{
+       int err = 0, lnum = from, offs, bytes_left = bytes, to_read;
+       struct ubi_nvmem *unv = priv;
+       struct ubi_volume_desc *desc;
+
+       desc = ubi_open_volume(unv->ubi_num, unv->vol_id, UBI_READONLY);
+       if (IS_ERR(desc))
+               return PTR_ERR(desc);
+
+       offs = do_div(lnum, unv->usable_leb_size);
+       while (bytes_left) {
+               to_read = unv->usable_leb_size - offs;
+
+               if (to_read > bytes_left)
+                       to_read = bytes_left;
+
+               err = ubi_read(desc, lnum, val, offs, to_read);
+               if (err)
+                       break;
+
+               lnum += 1;
+               offs = 0;
+               bytes_left -= to_read;
+               val += to_read;
+       }
+       ubi_close_volume(desc);
+
+       if (err)
+               return err;
+
+       return bytes_left == 0 ? 0 : -EIO;
+}
+
+static int ubi_nvmem_add(struct ubi_volume_info *vi)
+{
+       struct device_node *np = dev_of_node(vi->dev);
+       struct nvmem_config config = {};
+       struct ubi_nvmem *unv;
+       int ret;
+
+       if (!np)
+               return 0;
+
+       if (!of_get_child_by_name(np, "nvmem-layout"))
+               return 0;
+
+       if (WARN_ON_ONCE(vi->usable_leb_size <= 0) ||
+           WARN_ON_ONCE(vi->size <= 0))
+               return -EINVAL;
+
+       unv = kzalloc(sizeof(struct ubi_nvmem), GFP_KERNEL);
+       if (!unv)
+               return -ENOMEM;
+
+       config.id = NVMEM_DEVID_NONE;
+       config.dev = vi->dev;
+       config.name = dev_name(vi->dev);
+       config.owner = THIS_MODULE;
+       config.priv = unv;
+       config.reg_read = ubi_nvmem_reg_read;
+       config.size = vi->usable_leb_size * vi->size;
+       config.word_size = 1;
+       config.stride = 1;
+       config.read_only = true;
+       config.root_only = true;
+       config.ignore_wp = true;
+       config.of_node = np;
+
+       unv->ubi_num = vi->ubi_num;
+       unv->vol_id = vi->vol_id;
+       unv->usable_leb_size = vi->usable_leb_size;
+       unv->nvmem = nvmem_register(&config);
+       if (IS_ERR(unv->nvmem)) {
+               ret = dev_err_probe(vi->dev, PTR_ERR(unv->nvmem),
+                                   "Failed to register NVMEM device\n");
+               kfree(unv);
+               return ret;
+       }
+
+       mutex_lock(&devices_mutex);
+       list_add_tail(&unv->list, &nvmem_devices);
+       mutex_unlock(&devices_mutex);
+
+       return 0;
+}
+
+static void ubi_nvmem_remove(struct ubi_volume_info *vi)
+{
+       struct ubi_nvmem *unv_c, *unv = NULL;
+
+       mutex_lock(&devices_mutex);
+       list_for_each_entry(unv_c, &nvmem_devices, list)
+               if (unv_c->ubi_num == vi->ubi_num && unv_c->vol_id == vi->vol_id) {
+                       unv = unv_c;
+                       break;
+               }
+
+       if (!unv) {
+               mutex_unlock(&devices_mutex);
+               return;
+       }
+
+       list_del(&unv->list);
+       mutex_unlock(&devices_mutex);
+       nvmem_unregister(unv->nvmem);
+       kfree(unv);
+}
+
+/**
+ * nvmem_notify - UBI notification handler.
+ * @nb: registered notifier block
+ * @l: notification type
+ * @ns_ptr: pointer to the &struct ubi_notification object
+ */
+static int nvmem_notify(struct notifier_block *nb, unsigned long l,
+                        void *ns_ptr)
+{
+       struct ubi_notification *nt = ns_ptr;
+
+       switch (l) {
+       case UBI_VOLUME_RESIZED:
+               ubi_nvmem_remove(&nt->vi);
+               fallthrough;
+       case UBI_VOLUME_ADDED:
+               ubi_nvmem_add(&nt->vi);
+               break;
+       case UBI_VOLUME_SHUTDOWN:
+               ubi_nvmem_remove(&nt->vi);
+               break;
+       default:
+               break;
+       }
+       return NOTIFY_OK;
+}
+
+static struct notifier_block nvmem_notifier = {
+       .notifier_call = nvmem_notify,
+};
+
+static int __init ubi_nvmem_init(void)
+{
+       return ubi_register_volume_notifier(&nvmem_notifier, 0);
+}
+
+static void __exit ubi_nvmem_exit(void)
+{
+       struct ubi_nvmem *unv, *tmp;
+
+       mutex_lock(&devices_mutex);
+       list_for_each_entry_safe(unv, tmp, &nvmem_devices, list) {
+               nvmem_unregister(unv->nvmem);
+               list_del(&unv->list);
+               kfree(unv);
+       }
+       mutex_unlock(&devices_mutex);
+
+       ubi_unregister_volume_notifier(&nvmem_notifier);
+}
+
+module_init(ubi_nvmem_init);
+module_exit(ubi_nvmem_exit);
+MODULE_DESCRIPTION("NVMEM layer over UBI volumes");
+MODULE_AUTHOR("Daniel Golle");
+MODULE_LICENSE("GPL");