123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- From 7eb6666348f3f2d1f7308c712fa5903cbe189401 Mon Sep 17 00:00:00 2001
- From: Daniel Golle <[email protected]>
- Date: Thu, 8 Jun 2023 17:22:04 +0100
- Subject: [PATCH 07/15] mtd: ubi: provide NVMEM layer over UBI volumes
- 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 <[email protected]>
- ---
- 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
- @@ -0,0 +1,188 @@
- +// SPDX-License-Identifier: GPL-2.0-or-later
- +/*
- + * Copyright (c) 2023 Daniel Golle <[email protected]>
- + */
- +
- +/* 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");
|