mirror of
https://git.openwrt.org/openwrt/openwrt.git
synced 2024-11-28 07:44:32 +00:00
94e0190082
Allow a wider audience to test this pending series. Use about to be submitted v3 which factors out block notification support. Apart from dropping the no longer needed (and problematic) fallback for for the 'partitions' node being present at the device parent there are no intended functional changes. As opening a block device as file is not supported yet in Kernel v6.6, use the previous method as backporting seems a bit too involving. Fixes: #15642 Signed-off-by: Daniel Golle <daniel@makrotopia.org>
261 lines
6.7 KiB
Diff
261 lines
6.7 KiB
Diff
From 9703951cdfe868b130e64d6122420396c2807be8 Mon Sep 17 00:00:00 2001
|
|
From: Daniel Golle <daniel@makrotopia.org>
|
|
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 <daniel@makrotopia.org>
|
|
---
|
|
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 <daniel@makrotopia.org>
|
|
+ *
|
|
+ * 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 <daniel@makrotopia.org>");
|
|
+MODULE_DESCRIPTION("block device NVMEM provider");
|