| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- // 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>
- /* 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)
- {
- size_t to_read, bytes_left = bytes;
- struct ubi_nvmem *unv = priv;
- struct ubi_volume_desc *desc;
- uint32_t offs;
- uint32_t lnum;
- int err = 0;
- desc = ubi_open_volume(unv->ubi_num, unv->vol_id, UBI_READONLY);
- if (IS_ERR(desc))
- return PTR_ERR(desc);
- offs = from % unv->usable_leb_size;
- lnum = from / 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 0;
- }
- 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_obj(struct ubi_nvmem);
- 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");
|