85e1e1ac34
https://cdn.kernel.org/pub/linux/kernel/v6.x/ChangeLog-6.1.100 All patches automatically rebased. Build system: bcm53xx ath79 Signed-off-by: Zxl hhyccc <zxlhhy@gmail.com>
241 lines
6.9 KiB
Diff
241 lines
6.9 KiB
Diff
From 0331c611949fffdf486652450901a4dc52bc5cca Mon Sep 17 00:00:00 2001
|
|
From: Miquel Raynal <miquel.raynal@bootlin.com>
|
|
Date: Fri, 15 Dec 2023 11:15:34 +0000
|
|
Subject: [PATCH] nvmem: core: Expose cells through sysfs
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
The binary content of nvmem devices is available to the user so in the
|
|
easiest cases, finding the content of a cell is rather easy as it is
|
|
just a matter of looking at a known and fixed offset. However, nvmem
|
|
layouts have been recently introduced to cope with more advanced
|
|
situations, where the offset and size of the cells is not known in
|
|
advance or is dynamic. When using layouts, more advanced parsers are
|
|
used by the kernel in order to give direct access to the content of each
|
|
cell, regardless of its position/size in the underlying
|
|
device. Unfortunately, these information are not accessible by users,
|
|
unless by fully re-implementing the parser logic in userland.
|
|
|
|
Let's expose the cells and their content through sysfs to avoid these
|
|
situations. Of course the relevant NVMEM sysfs Kconfig option must be
|
|
enabled for this support to be available.
|
|
|
|
Not all nvmem devices expose cells. Indeed, the .bin_attrs attribute
|
|
group member will be filled at runtime only when relevant and will
|
|
remain empty otherwise. In this case, as the cells attribute group will
|
|
be empty, it will not lead to any additional folder/file creation.
|
|
|
|
Exposed cells are read-only. There is, in practice, everything in the
|
|
core to support a write path, but as I don't see any need for that, I
|
|
prefer to keep the interface simple (and probably safer). The interface
|
|
is documented as being in the "testing" state which means we can later
|
|
add a write attribute if though relevant.
|
|
|
|
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
|
|
Tested-by: Rafał Miłecki <rafal@milecki.pl>
|
|
Tested-by: Chen-Yu Tsai <wenst@chromium.org>
|
|
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|
|
Link: https://lore.kernel.org/r/20231215111536.316972-9-srinivas.kandagatla@linaro.org
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
---
|
|
drivers/nvmem/core.c | 135 +++++++++++++++++++++++++++++++++++++-
|
|
drivers/nvmem/internals.h | 1 +
|
|
2 files changed, 135 insertions(+), 1 deletion(-)
|
|
|
|
--- a/drivers/nvmem/core.c
|
|
+++ b/drivers/nvmem/core.c
|
|
@@ -300,6 +300,43 @@ static umode_t nvmem_bin_attr_is_visible
|
|
return nvmem_bin_attr_get_umode(nvmem);
|
|
}
|
|
|
|
+static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry,
|
|
+ const char *id, int index);
|
|
+
|
|
+static ssize_t nvmem_cell_attr_read(struct file *filp, struct kobject *kobj,
|
|
+ struct bin_attribute *attr, char *buf,
|
|
+ loff_t pos, size_t count)
|
|
+{
|
|
+ struct nvmem_cell_entry *entry;
|
|
+ struct nvmem_cell *cell = NULL;
|
|
+ size_t cell_sz, read_len;
|
|
+ void *content;
|
|
+
|
|
+ entry = attr->private;
|
|
+ cell = nvmem_create_cell(entry, entry->name, 0);
|
|
+ if (IS_ERR(cell))
|
|
+ return PTR_ERR(cell);
|
|
+
|
|
+ if (!cell)
|
|
+ return -EINVAL;
|
|
+
|
|
+ content = nvmem_cell_read(cell, &cell_sz);
|
|
+ if (IS_ERR(content)) {
|
|
+ read_len = PTR_ERR(content);
|
|
+ goto destroy_cell;
|
|
+ }
|
|
+
|
|
+ read_len = min_t(unsigned int, cell_sz - pos, count);
|
|
+ memcpy(buf, content + pos, read_len);
|
|
+ kfree(content);
|
|
+
|
|
+destroy_cell:
|
|
+ kfree_const(cell->id);
|
|
+ kfree(cell);
|
|
+
|
|
+ return read_len;
|
|
+}
|
|
+
|
|
/* default read/write permissions */
|
|
static struct bin_attribute bin_attr_rw_nvmem = {
|
|
.attr = {
|
|
@@ -321,11 +358,21 @@ static const struct attribute_group nvme
|
|
.is_bin_visible = nvmem_bin_attr_is_visible,
|
|
};
|
|
|
|
+/* Cell attributes will be dynamically allocated */
|
|
+static struct attribute_group nvmem_cells_group = {
|
|
+ .name = "cells",
|
|
+};
|
|
+
|
|
static const struct attribute_group *nvmem_dev_groups[] = {
|
|
&nvmem_bin_group,
|
|
NULL,
|
|
};
|
|
|
|
+static const struct attribute_group *nvmem_cells_groups[] = {
|
|
+ &nvmem_cells_group,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
static struct bin_attribute bin_attr_nvmem_eeprom_compat = {
|
|
.attr = {
|
|
.name = "eeprom",
|
|
@@ -380,6 +427,68 @@ static void nvmem_sysfs_remove_compat(st
|
|
device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom);
|
|
}
|
|
|
|
+static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem)
|
|
+{
|
|
+ struct bin_attribute **cells_attrs, *attrs;
|
|
+ struct nvmem_cell_entry *entry;
|
|
+ unsigned int ncells = 0, i = 0;
|
|
+ int ret = 0;
|
|
+
|
|
+ mutex_lock(&nvmem_mutex);
|
|
+
|
|
+ if (list_empty(&nvmem->cells) || nvmem->sysfs_cells_populated) {
|
|
+ nvmem_cells_group.bin_attrs = NULL;
|
|
+ goto unlock_mutex;
|
|
+ }
|
|
+
|
|
+ /* Allocate an array of attributes with a sentinel */
|
|
+ ncells = list_count_nodes(&nvmem->cells);
|
|
+ cells_attrs = devm_kcalloc(&nvmem->dev, ncells + 1,
|
|
+ sizeof(struct bin_attribute *), GFP_KERNEL);
|
|
+ if (!cells_attrs) {
|
|
+ ret = -ENOMEM;
|
|
+ goto unlock_mutex;
|
|
+ }
|
|
+
|
|
+ attrs = devm_kcalloc(&nvmem->dev, ncells, sizeof(struct bin_attribute), GFP_KERNEL);
|
|
+ if (!attrs) {
|
|
+ ret = -ENOMEM;
|
|
+ goto unlock_mutex;
|
|
+ }
|
|
+
|
|
+ /* Initialize each attribute to take the name and size of the cell */
|
|
+ list_for_each_entry(entry, &nvmem->cells, node) {
|
|
+ sysfs_bin_attr_init(&attrs[i]);
|
|
+ attrs[i].attr.name = devm_kasprintf(&nvmem->dev, GFP_KERNEL,
|
|
+ "%s@%x", entry->name,
|
|
+ entry->offset);
|
|
+ attrs[i].attr.mode = 0444;
|
|
+ attrs[i].size = entry->bytes;
|
|
+ attrs[i].read = &nvmem_cell_attr_read;
|
|
+ attrs[i].private = entry;
|
|
+ if (!attrs[i].attr.name) {
|
|
+ ret = -ENOMEM;
|
|
+ goto unlock_mutex;
|
|
+ }
|
|
+
|
|
+ cells_attrs[i] = &attrs[i];
|
|
+ i++;
|
|
+ }
|
|
+
|
|
+ nvmem_cells_group.bin_attrs = cells_attrs;
|
|
+
|
|
+ ret = devm_device_add_groups(&nvmem->dev, nvmem_cells_groups);
|
|
+ if (ret)
|
|
+ goto unlock_mutex;
|
|
+
|
|
+ nvmem->sysfs_cells_populated = true;
|
|
+
|
|
+unlock_mutex:
|
|
+ mutex_unlock(&nvmem_mutex);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
#else /* CONFIG_NVMEM_SYSFS */
|
|
|
|
static int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem,
|
|
@@ -739,11 +848,25 @@ static int nvmem_add_cells_from_fixed_la
|
|
|
|
int nvmem_layout_register(struct nvmem_layout *layout)
|
|
{
|
|
+ int ret;
|
|
+
|
|
if (!layout->add_cells)
|
|
return -EINVAL;
|
|
|
|
/* Populate the cells */
|
|
- return layout->add_cells(&layout->nvmem->dev, layout->nvmem);
|
|
+ ret = layout->add_cells(&layout->nvmem->dev, layout->nvmem);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+#ifdef CONFIG_NVMEM_SYSFS
|
|
+ ret = nvmem_populate_sysfs_cells(layout->nvmem);
|
|
+ if (ret) {
|
|
+ nvmem_device_remove_all_cells(layout->nvmem);
|
|
+ return ret;
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvmem_layout_register);
|
|
|
|
@@ -902,10 +1025,20 @@ struct nvmem_device *nvmem_register(cons
|
|
if (rval)
|
|
goto err_remove_dev;
|
|
|
|
+#ifdef CONFIG_NVMEM_SYSFS
|
|
+ rval = nvmem_populate_sysfs_cells(nvmem);
|
|
+ if (rval)
|
|
+ goto err_destroy_layout;
|
|
+#endif
|
|
+
|
|
blocking_notifier_call_chain(&nvmem_notifier, NVMEM_ADD, nvmem);
|
|
|
|
return nvmem;
|
|
|
|
+#ifdef CONFIG_NVMEM_SYSFS
|
|
+err_destroy_layout:
|
|
+ nvmem_destroy_layout(nvmem);
|
|
+#endif
|
|
err_remove_dev:
|
|
device_del(&nvmem->dev);
|
|
err_remove_cells:
|
|
--- a/drivers/nvmem/internals.h
|
|
+++ b/drivers/nvmem/internals.h
|
|
@@ -32,6 +32,7 @@ struct nvmem_device {
|
|
struct gpio_desc *wp_gpio;
|
|
struct nvmem_layout *layout;
|
|
void *priv;
|
|
+ bool sysfs_cells_populated;
|
|
};
|
|
|
|
#if IS_ENABLED(CONFIG_OF)
|