123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- From 0331c611949fffdf486652450901a4dc52bc5cca Mon Sep 17 00:00:00 2001
- From: Miquel Raynal <[email protected]>
- 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 <[email protected]>
- Tested-by: Rafał Miłecki <[email protected]>
- Tested-by: Chen-Yu Tsai <[email protected]>
- Signed-off-by: Srinivas Kandagatla <[email protected]>
- Link: https://lore.kernel.org/r/[email protected]
- Signed-off-by: Greg Kroah-Hartman <[email protected]>
- ---
- 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
- @@ -299,6 +299,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 = {
- @@ -320,11 +357,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)
|