| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- From 9703951cdfe868b130e64d6122420396c2807be8 Mon Sep 17 00:00:00 2001
- From: Daniel Golle <[email protected]>
- Date: Thu, 30 May 2024 03:15:02 +0100
- Subject: [PATCH 5/9] nvmem: implement block NVMEM provider
- On embedded devices using an eMMC it is common that one or more partitions
- on the eMMC are used to store MAC addresses and Wi-Fi calibration EEPROM
- data. Allow referencing any block device or partition in Device Tree to
- allow e.g. Ethernet and Wi-Fi drivers accessing them via the NVMEM layer.
- Signed-off-by: Daniel Golle <[email protected]>
- ---
- drivers/nvmem/Kconfig | 11 +++
- drivers/nvmem/Makefile | 2 +
- drivers/nvmem/block.c | 197 +++++++++++++++++++++++++++++++++++++++++
- 3 files changed, 210 insertions(+)
- create mode 100644 drivers/nvmem/block.c
- --- a/drivers/nvmem/Kconfig
- +++ b/drivers/nvmem/Kconfig
- @@ -40,6 +40,17 @@ config NVMEM_APPLE_EFUSES
- This driver can also be built as a module. If so, the module will
- be called nvmem-apple-efuses.
-
- +config NVMEM_BLOCK
- + tristate "Block device NVMEM provider"
- + depends on BLOCK
- + depends on OF
- + depends on NVMEM
- + select BLOCK_NOTIFIERS
- + help
- + Allow block devices (or partitions) to act as NVMEM prodivers,
- + typically used with eMMC to store MAC addresses or Wi-Fi
- + calibration data on embedded devices.
- +
- config NVMEM_BCM_OCOTP
- tristate "Broadcom On-Chip OTP Controller support"
- depends on ARCH_BCM_IPROC || COMPILE_TEST
- --- a/drivers/nvmem/Makefile
- +++ b/drivers/nvmem/Makefile
- @@ -14,6 +14,8 @@ obj-$(CONFIG_NVMEM_APPLE_EFUSES) += nvme
- nvmem-apple-efuses-y := apple-efuses.o
- obj-$(CONFIG_NVMEM_BCM_OCOTP) += nvmem-bcm-ocotp.o
- nvmem-bcm-ocotp-y := bcm-ocotp.o
- +obj-$(CONFIG_NVMEM_BLOCK) += nvmem-block.o
- +nvmem-block-y := block.o
- obj-$(CONFIG_NVMEM_BRCM_NVRAM) += nvmem_brcm_nvram.o
- nvmem_brcm_nvram-y := brcm_nvram.o
- obj-$(CONFIG_NVMEM_IMX_IIM) += nvmem-imx-iim.o
- --- /dev/null
- +++ b/drivers/nvmem/block.c
- @@ -0,0 +1,208 @@
- +// SPDX-License-Identifier: GPL-2.0-or-later
- +/*
- + * block device NVMEM provider
- + *
- + * Copyright (c) 2024 Daniel Golle <[email protected]>
- + *
- + * Useful on devices using a partition on an eMMC for MAC addresses or
- + * Wi-Fi calibration EEPROM data.
- + */
- +
- +#include <linux/blkdev.h>
- +#include <linux/nvmem-provider.h>
- +#include <linux/of.h>
- +#include <linux/pagemap.h>
- +#include <linux/property.h>
- +
- +/* List of all NVMEM devices */
- +static LIST_HEAD(nvmem_devices);
- +static DEFINE_MUTEX(devices_mutex);
- +
- +struct blk_nvmem {
- + struct nvmem_device *nvmem;
- + struct block_device *bdev;
- + struct list_head list;
- +};
- +
- +static int blk_nvmem_reg_read(void *priv, unsigned int from,
- + void *val, size_t bytes)
- +{
- + unsigned long offs = from & ~PAGE_MASK, to_read;
- + pgoff_t f_index = from >> PAGE_SHIFT;
- + struct address_space *mapping;
- + struct blk_nvmem *bnv = priv;
- + size_t bytes_left = bytes;
- + struct folio *folio;
- + void *p;
- + int ret;
- +
- + if (!bnv->bdev)
- + return -ENODEV;
- +
- + if (!bnv->bdev->bd_disk)
- + return -EINVAL;
- +
- + if (!bnv->bdev->bd_disk->fops)
- + return -EIO;
- +
- + if (!bnv->bdev->bd_disk->fops->open)
- + return -EIO;
- +
- + ret = bnv->bdev->bd_disk->fops->open(bnv->bdev->bd_disk, BLK_OPEN_READ);
- + if (ret)
- + return ret;
- +
- + mapping = bnv->bdev->bd_inode->i_mapping;
- +
- + while (bytes_left) {
- + folio = read_mapping_folio(mapping, f_index++, NULL);
- + if (IS_ERR(folio)) {
- + ret = PTR_ERR(folio);
- + goto err_release_bdev;
- + }
- + to_read = min_t(unsigned long, bytes_left, PAGE_SIZE - offs);
- + p = folio_address(folio) + offset_in_folio(folio, offs);
- + memcpy(val, p, to_read);
- + offs = 0;
- + bytes_left -= to_read;
- + val += to_read;
- + folio_put(folio);
- + }
- +
- +err_release_bdev:
- + bnv->bdev->bd_disk->fops->release(bnv->bdev->bd_disk);
- +
- + return ret;
- +}
- +
- +static int blk_nvmem_register(struct device *dev)
- +{
- + struct device_node *np = dev_of_node(dev);
- + struct block_device *bdev = dev_to_bdev(dev);
- + struct nvmem_config config = {};
- + struct blk_nvmem *bnv;
- +
- + /* skip devices which do not have a device tree node */
- + if (!np)
- + return 0;
- +
- + /* skip devices without an nvmem layout defined */
- + if (!of_get_child_by_name(np, "nvmem-layout"))
- + return 0;
- +
- + /*
- + * skip devices which don't have GENHD_FL_NVMEM set
- + *
- + * This flag is used for mtdblock and ubiblock devices because
- + * both, MTD and UBI already implement their own NVMEM provider.
- + * To avoid registering multiple NVMEM providers for the same
- + * device node, don't register the block NVMEM provider for them.
- + */
- + if (!(bdev->bd_disk->flags & GENHD_FL_NVMEM))
- + return 0;
- +
- + /*
- + * skip block device too large to be represented as NVMEM devices
- + * which are using an 'int' as address
- + */
- + if (bdev_nr_bytes(bdev) > INT_MAX)
- + return -EFBIG;
- +
- + bnv = kzalloc(sizeof(struct blk_nvmem), GFP_KERNEL);
- + if (!bnv)
- + return -ENOMEM;
- +
- + config.id = NVMEM_DEVID_NONE;
- + config.dev = &bdev->bd_device;
- + config.name = dev_name(&bdev->bd_device);
- + config.owner = THIS_MODULE;
- + config.priv = bnv;
- + config.reg_read = blk_nvmem_reg_read;
- + config.size = bdev_nr_bytes(bdev);
- + config.word_size = 1;
- + config.stride = 1;
- + config.read_only = true;
- + config.root_only = true;
- + config.ignore_wp = true;
- + config.of_node = to_of_node(dev->fwnode);
- +
- + bnv->bdev = bdev;
- + bnv->nvmem = nvmem_register(&config);
- + if (IS_ERR(bnv->nvmem)) {
- + dev_err_probe(&bdev->bd_device, PTR_ERR(bnv->nvmem),
- + "Failed to register NVMEM device\n");
- +
- + kfree(bnv);
- + return PTR_ERR(bnv->nvmem);
- + }
- +
- + mutex_lock(&devices_mutex);
- + list_add_tail(&bnv->list, &nvmem_devices);
- + mutex_unlock(&devices_mutex);
- +
- + return 0;
- +}
- +
- +static void blk_nvmem_unregister(struct device *dev)
- +{
- + struct block_device *bdev = dev_to_bdev(dev);
- + struct blk_nvmem *bnv_c, *bnv = NULL;
- +
- + mutex_lock(&devices_mutex);
- + list_for_each_entry(bnv_c, &nvmem_devices, list) {
- + if (bnv_c->bdev == bdev) {
- + bnv = bnv_c;
- + break;
- + }
- + }
- +
- + if (!bnv) {
- + mutex_unlock(&devices_mutex);
- + return;
- + }
- +
- + list_del(&bnv->list);
- + mutex_unlock(&devices_mutex);
- + nvmem_unregister(bnv->nvmem);
- + kfree(bnv);
- +}
- +
- +static int blk_nvmem_handler(struct notifier_block *this, unsigned long code, void *obj)
- +{
- + struct device *dev = (struct device *)obj;
- +
- + switch (code) {
- + case BLK_DEVICE_ADD:
- + return blk_nvmem_register(dev);
- + case BLK_DEVICE_REMOVE:
- + blk_nvmem_unregister(dev);
- + break;
- + default:
- + return -EINVAL;
- + }
- +
- + return 0;
- +}
- +
- +static struct notifier_block blk_nvmem_notifier = {
- + .notifier_call = blk_nvmem_handler,
- +};
- +
- +static int __init blk_nvmem_init(void)
- +{
- + blk_register_notify(&blk_nvmem_notifier);
- +
- + return 0;
- +}
- +
- +static void __exit blk_nvmem_exit(void)
- +{
- + blk_unregister_notify(&blk_nvmem_notifier);
- +}
- +
- +module_init(blk_nvmem_init);
- +module_exit(blk_nvmem_exit);
- +
- +MODULE_LICENSE("GPL");
- +MODULE_AUTHOR("Daniel Golle <[email protected]>");
- +MODULE_DESCRIPTION("block device NVMEM provider");
|