| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 | 
							- 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,191 @@
 
- +// 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)
 
- +{
 
- +	uint32_t offs, to_read, bytes_left;
 
- +	struct ubi_nvmem *unv = priv;
 
- +	struct ubi_volume_desc *desc;
 
- +	uint64_t lnum = from;
 
- +	int err = 0;
 
- +
 
- +	desc = ubi_open_volume(unv->ubi_num, unv->vol_id, UBI_READONLY);
 
- +	if (IS_ERR(desc))
 
- +		return PTR_ERR(desc);
 
- +
 
- +	bytes_left = bytes;
 
- +	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");
 
 
  |