2024-06-14 15:24:12 +00:00
|
|
|
From 15fc7dc926c91c871f6c0305b2938dbdeb14203b Mon Sep 17 00:00:00 2001
|
2024-01-03 11:57:28 +00:00
|
|
|
From: Daniel Golle <daniel@makrotopia.org>
|
2024-06-14 15:24:12 +00:00
|
|
|
Date: Tue, 19 Dec 2023 02:33:48 +0000
|
|
|
|
Subject: [PATCH 7/8] mtd: ubi: provide NVMEM layer over UBI volumes
|
2024-01-03 11:57:28 +00:00
|
|
|
|
|
|
|
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>
|
2024-06-14 15:24:12 +00:00
|
|
|
Signed-off-by: Richard Weinberger <richard@nod.at>
|
2024-01-03 11:57:28 +00:00
|
|
|
---
|
|
|
|
drivers/mtd/ubi/Kconfig | 12 +++
|
|
|
|
drivers/mtd/ubi/Makefile | 1 +
|
|
|
|
drivers/mtd/ubi/nvmem.c | 188 +++++++++++++++++++++++++++++++++++++++
|
|
|
|
3 files changed, 201 insertions(+)
|
|
|
|
create mode 100644 drivers/mtd/ubi/nvmem.c
|
|
|
|
|
|
|
|
--- a/drivers/mtd/ubi/Kconfig
|
|
|
|
+++ b/drivers/mtd/ubi/Kconfig
|
|
|
|
@@ -104,4 +104,16 @@ config MTD_UBI_BLOCK
|
|
|
|
|
|
|
|
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
|
|
|
|
--- a/drivers/mtd/ubi/Makefile
|
|
|
|
+++ b/drivers/mtd/ubi/Makefile
|
|
|
|
@@ -7,3 +7,4 @@ ubi-$(CONFIG_MTD_UBI_FASTMAP) += fastmap
|
|
|
|
ubi-$(CONFIG_MTD_UBI_BLOCK) += block.o
|
|
|
|
|
|
|
|
obj-$(CONFIG_MTD_UBI_GLUEBI) += gluebi.o
|
|
|
|
+obj-$(CONFIG_MTD_UBI_NVMEM) += nvmem.o
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/drivers/mtd/ubi/nvmem.c
|
2024-06-14 15:24:12 +00:00
|
|
|
@@ -0,0 +1,188 @@
|
2024-01-03 11:57:28 +00:00
|
|
|
+// 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)
|
|
|
|
+{
|
2024-06-14 15:24:12 +00:00
|
|
|
+ int err = 0, lnum = from, offs, bytes_left = bytes, to_read;
|
2024-01-03 11:57:28 +00:00
|
|
|
+ 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");
|