2022-09-01 22:22:43

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 00/20] nvmem: core: introduce NVMEM layouts

This is now the third attempt to fetch the MAC addresses from the VPD
for the Kontron sl28 boards. Previous discussions can be found here:
https://lore.kernel.org/lkml/[email protected]/


NVMEM cells are typically added by board code or by the devicetree. But
as the cells get more complex, there is (valid) push back from the
devicetree maintainers to not put that handling in the devicetree.

Therefore, introduce NVMEM layouts. They operate on the NVMEM device and
can add cells during runtime. That way it is possible to add more complex
cells than it is possible right now with the offset/length/bits
description in the device tree. For example, you can have post processing
for individual cells (think of endian swapping, or ethernet offset
handling).

The imx-ocotp driver is the only user of the global post processing hook,
convert it to nvmem layouts and drop the global post pocessing hook. Please
note, that this change is only compile-time tested.

You can also have cells which have no static offset, like the
ones in an u-boot environment. The last patches will convert the current
u-boot environment driver to a NVMEM layout and lifting the restriction
that it only works with mtd devices. But as it will change the required
compatible strings, it is marked as RFC for now. It also needs to have
its device tree schema update which is left out here. These two patches
are not expected to be applied, but rather to show another example of
how to use the layouts.

For now, the layouts are selected by a specific compatible string in a
device tree. E.g. the VPD on the kontron sl28 do (within a SPI flash node):
compatible = "kontron,sl28-vpd", "user-otp";
or if you'd use the u-boot environment (within an MTD patition):
compatible = "u-boot,env", "nvmem";

The "user-otp" (or "nvmem") will lead to a NVMEM device, the
"kontron,sl28-vpd" (or "u-boot,env") will then apply the specific layout
on top of the NVMEM device.

NVMEM layouts as modules?
While possible in principle, it doesn't make any sense because the NVMEM
core can't be compiled as a module. The layouts needs to be available at
probe time. (That is also the reason why they get registered with
subsys_initcall().) So if the NVMEM core would be a module, the layouts
could be modules, too.

Michael Walle (20):
net: add helper eth_addr_add()
of: base: add of_parse_phandle_with_optional_args()
nvmem: core: add an index parameter to the cell
nvmem: core: move struct nvmem_cell_info to nvmem-provider.h
nvmem: core: drop the removal of the cells in nvmem_add_cells()
nvmem: core: add nvmem_add_one_cell()
nvmem: core: use nvmem_add_one_cell() in nvmem_add_cells_from_of()
nvmem: core: introduce NVMEM layouts
nvmem: core: add per-cell post processing
nvmem: core: allow to modify a cell before adding it
nvmem: imx-ocotp: replace global post processing with layouts
nvmem: cell: drop global cell_post_process
nvmem: core: drop priv pointer in post process callback
dt-bindings: mtd: relax the nvmem compatible string
dt-bindings: nvmem: add YAML schema for the sl28 vpd layout
nvmem: layouts: add sl28vpd layout
nvmem: core: export nvmem device size
arm64: dts: ls1028a: sl28: get MAC addresses from VPD
RFC nvmem: layouts: rewrite the u-boot-env driver as a NVMEM layout
RFC nvmem: layouts: u-boot-env: add device node

.../devicetree/bindings/mtd/mtd.yaml | 7 +-
.../nvmem/layouts/kontron,sl28-vpd.yaml | 67 +++++
Documentation/driver-api/nvmem.rst | 15 ++
.../fsl-ls1028a-kontron-kbox-a-230-ls.dts | 8 +
.../fsl-ls1028a-kontron-sl28-var1.dts | 2 +
.../fsl-ls1028a-kontron-sl28-var2.dts | 4 +
.../fsl-ls1028a-kontron-sl28-var4.dts | 2 +
.../freescale/fsl-ls1028a-kontron-sl28.dts | 13 +
drivers/nvmem/Kconfig | 4 +
drivers/nvmem/Makefile | 1 +
drivers/nvmem/core.c | 251 +++++++++++++-----
drivers/nvmem/imx-ocotp.c | 32 ++-
drivers/nvmem/layouts/Kconfig | 22 ++
drivers/nvmem/layouts/Makefile | 7 +
drivers/nvmem/layouts/sl28vpd.c | 144 ++++++++++
drivers/nvmem/layouts/u-boot-env.c | 146 ++++++++++
drivers/nvmem/u-boot-env.c | 218 ---------------
include/linux/etherdevice.h | 14 +
include/linux/nvmem-consumer.h | 16 +-
include/linux/nvmem-provider.h | 90 ++++++-
include/linux/of.h | 25 ++
21 files changed, 775 insertions(+), 313 deletions(-)
create mode 100644 Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml
create mode 100644 drivers/nvmem/layouts/Kconfig
create mode 100644 drivers/nvmem/layouts/Makefile
create mode 100644 drivers/nvmem/layouts/sl28vpd.c
create mode 100644 drivers/nvmem/layouts/u-boot-env.c
delete mode 100644 drivers/nvmem/u-boot-env.c

--
2.30.2


2022-09-01 22:22:51

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 02/20] of: base: add of_parse_phandle_with_optional_args()

Add a new variant of the of_parse_phandle_with_args() which treats the
cells name as optional. If it's missing, it is assumed that the phandle
has no arguments.

Up until now, a nvmem node didn't have any arguments, so all the device
trees haven't any '#*-cells' property. But there is a need for an
additional argument for the phandle, for which we need a '#*-cells'
property. Therefore, we need to support nvmem nodes with and without
this property.

Signed-off-by: Michael Walle <[email protected]>
Reviewed-by: Rob Herring <[email protected]>
---
changes since v1:
- none

include/linux/of.h | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)

diff --git a/include/linux/of.h b/include/linux/of.h
index 766d002bddb9..a0fe1a017452 100644
--- a/include/linux/of.h
+++ b/include/linux/of.h
@@ -1008,6 +1008,31 @@ static inline int of_parse_phandle_with_fixed_args(const struct device_node *np,
index, out_args);
}

+/**
+ * of_parse_phandle_with_optional_args() - Find a node pointed by phandle in a list
+ * @np: pointer to a device tree node containing a list
+ * @list_name: property name that contains a list
+ * @cells_name: property name that specifies phandles' arguments count
+ * @index: index of a phandle to parse out
+ * @out_args: optional pointer to output arguments structure (will be filled)
+ *
+ * Same as of_parse_phandle_with_args() except that if the cells_name property
+ * is not found, cell_count of 0 is assumed.
+ *
+ * This is used to useful, if you have a phandle which didn't have arguments
+ * before and thus doesn't have a '#*-cells' property but is now migrated to
+ * having arguments while retaining backwards compatibility.
+ */
+static inline int of_parse_phandle_with_optional_args(const struct device_node *np,
+ const char *list_name,
+ const char *cells_name,
+ int index,
+ struct of_phandle_args *out_args)
+{
+ return __of_parse_phandle_with_args(np, list_name, cells_name,
+ 0, index, out_args);
+}
+
/**
* of_property_count_u8_elems - Count the number of u8 elements in a property
*
--
2.30.2

2022-09-01 22:22:52

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 03/20] nvmem: core: add an index parameter to the cell

Sometimes a cell can represend multiple values. For example, a base
ethernet address stored in the NVMEM can be expanded into multiple
discreet ones by adding an offset.

For this use case, introduce an index parameter which is then used to
distiguish between values. This parameter will then be passed to the
post process hook which can then use it to create different values
during reading.

At the moment, there is only support for the device tree path. You can
add the index to the phandle, e.g.

&net {
nvmem-cells = <&base_mac_address 2>;
nvmem-cell-names = "mac-address";
};

&nvmem_provider {
base_mac_address: [email protected] {
#nvmem-cell-cells = <1>;
reg = <0 6>;
};
};

Signed-off-by: Michael Walle <[email protected]>
---
changes since v1:
- none

drivers/nvmem/core.c | 37 ++++++++++++++++++++++++----------
drivers/nvmem/imx-ocotp.c | 4 ++--
include/linux/nvmem-provider.h | 4 ++--
3 files changed, 30 insertions(+), 15 deletions(-)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 321d7d63e068..ab055e4fc409 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -60,6 +60,7 @@ struct nvmem_cell_entry {
struct nvmem_cell {
struct nvmem_cell_entry *entry;
const char *id;
+ int index;
};

static DEFINE_MUTEX(nvmem_mutex);
@@ -1127,7 +1128,8 @@ struct nvmem_device *devm_nvmem_device_get(struct device *dev, const char *id)
}
EXPORT_SYMBOL_GPL(devm_nvmem_device_get);

-static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry, const char *id)
+static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry,
+ const char *id, int index)
{
struct nvmem_cell *cell;
const char *name = NULL;
@@ -1146,6 +1148,7 @@ static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry, cons

cell->id = name;
cell->entry = entry;
+ cell->index = index;

return cell;
}
@@ -1184,7 +1187,7 @@ nvmem_cell_get_from_lookup(struct device *dev, const char *con_id)
__nvmem_device_put(nvmem);
cell = ERR_PTR(-ENOENT);
} else {
- cell = nvmem_create_cell(cell_entry, con_id);
+ cell = nvmem_create_cell(cell_entry, con_id, 0);
if (IS_ERR(cell))
__nvmem_device_put(nvmem);
}
@@ -1232,15 +1235,27 @@ struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, const char *id)
struct nvmem_device *nvmem;
struct nvmem_cell_entry *cell_entry;
struct nvmem_cell *cell;
+ struct of_phandle_args cell_spec;
int index = 0;
+ int cell_index = 0;
+ int ret;

/* if cell name exists, find index to the name */
if (id)
index = of_property_match_string(np, "nvmem-cell-names", id);

- cell_np = of_parse_phandle(np, "nvmem-cells", index);
- if (!cell_np)
- return ERR_PTR(-ENOENT);
+ ret = of_parse_phandle_with_optional_args(np, "nvmem-cells",
+ "#nvmem-cell-cells",
+ index, &cell_spec);
+ if (ret)
+ return ERR_PTR(ret);
+
+ if (cell_spec.args_count > 1)
+ return ERR_PTR(-EINVAL);
+
+ cell_np = cell_spec.np;
+ if (cell_spec.args_count)
+ cell_index = cell_spec.args[0];

nvmem_np = of_get_next_parent(cell_np);
if (!nvmem_np)
@@ -1257,7 +1272,7 @@ struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, const char *id)
return ERR_PTR(-ENOENT);
}

- cell = nvmem_create_cell(cell_entry, id);
+ cell = nvmem_create_cell(cell_entry, id, cell_index);
if (IS_ERR(cell))
__nvmem_device_put(nvmem);

@@ -1410,8 +1425,8 @@ static void nvmem_shift_read_buffer_in_place(struct nvmem_cell_entry *cell, void
}

static int __nvmem_cell_read(struct nvmem_device *nvmem,
- struct nvmem_cell_entry *cell,
- void *buf, size_t *len, const char *id)
+ struct nvmem_cell_entry *cell,
+ void *buf, size_t *len, const char *id, int index)
{
int rc;

@@ -1425,7 +1440,7 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
nvmem_shift_read_buffer_in_place(cell, buf);

if (nvmem->cell_post_process) {
- rc = nvmem->cell_post_process(nvmem->priv, id,
+ rc = nvmem->cell_post_process(nvmem->priv, id, index,
cell->offset, buf, cell->bytes);
if (rc)
return rc;
@@ -1460,7 +1475,7 @@ void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len)
if (!buf)
return ERR_PTR(-ENOMEM);

- rc = __nvmem_cell_read(nvmem, cell->entry, buf, len, cell->id);
+ rc = __nvmem_cell_read(nvmem, cell->entry, buf, len, cell->id, cell->index);
if (rc) {
kfree(buf);
return ERR_PTR(rc);
@@ -1773,7 +1788,7 @@ ssize_t nvmem_device_cell_read(struct nvmem_device *nvmem,
if (rc)
return rc;

- rc = __nvmem_cell_read(nvmem, &cell, buf, &len, NULL);
+ rc = __nvmem_cell_read(nvmem, &cell, buf, &len, NULL, 0);
if (rc)
return rc;

diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c
index 14284e866f26..e9b52ecb3f72 100644
--- a/drivers/nvmem/imx-ocotp.c
+++ b/drivers/nvmem/imx-ocotp.c
@@ -222,8 +222,8 @@ static int imx_ocotp_read(void *context, unsigned int offset,
return ret;
}

-static int imx_ocotp_cell_pp(void *context, const char *id, unsigned int offset,
- void *data, size_t bytes)
+static int imx_ocotp_cell_pp(void *context, const char *id, int index,
+ unsigned int offset, void *data, size_t bytes)
{
struct ocotp_priv *priv = context;

diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index 50caa117cb62..8f964b394292 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -20,8 +20,8 @@ typedef int (*nvmem_reg_read_t)(void *priv, unsigned int offset,
typedef int (*nvmem_reg_write_t)(void *priv, unsigned int offset,
void *val, size_t bytes);
/* used for vendor specific post processing of cell data */
-typedef int (*nvmem_cell_post_process_t)(void *priv, const char *id, unsigned int offset,
- void *buf, size_t bytes);
+typedef int (*nvmem_cell_post_process_t)(void *priv, const char *id, int index,
+ unsigned int offset, void *buf, size_t bytes);

enum nvmem_type {
NVMEM_TYPE_UNKNOWN = 0,
--
2.30.2

2022-09-01 22:23:06

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 01/20] net: add helper eth_addr_add()

Add a helper to add an offset to a ethernet address. This comes in handy
if you have a base ethernet address for multiple interfaces.

Signed-off-by: Michael Walle <[email protected]>
Reviewed-by: Andrew Lunn <[email protected]>
Acked-by: Jakub Kicinski <[email protected]>
---
changes since v1:
- none

include/linux/etherdevice.h | 14 ++++++++++++++
1 file changed, 14 insertions(+)

diff --git a/include/linux/etherdevice.h b/include/linux/etherdevice.h
index a541f0c4f146..f144cadbe99d 100644
--- a/include/linux/etherdevice.h
+++ b/include/linux/etherdevice.h
@@ -507,6 +507,20 @@ static inline void eth_addr_inc(u8 *addr)
u64_to_ether_addr(u, addr);
}

+/**
+ * eth_addr_add() - Add (or subtract) and offset to/from the given MAC address.
+ *
+ * @offset: Offset to add.
+ * @addr: Pointer to a six-byte array containing Ethernet address to increment.
+ */
+static inline void eth_addr_add(u8 *addr, long offset)
+{
+ u64 u = ether_addr_to_u64(addr);
+
+ u += offset;
+ u64_to_ether_addr(u, addr);
+}
+
/**
* is_etherdev_addr - Tell if given Ethernet address belongs to the device.
* @dev: Pointer to a device structure
--
2.30.2

2022-09-01 22:23:22

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 04/20] nvmem: core: move struct nvmem_cell_info to nvmem-provider.h

struct nvmem_cell_info is used to describe a cell. Thus this should
really be in the nvmem-provider's header. There are two (unused) nvmem
access methods which use the nvmem_cell_info to describe the cell to be
accesses. One can argue, that they will create a cell before accessing,
thus they are both a provider and a consumer.

struct nvmem_cell_info will get used more and more by nvmem-providers,
don't force them to also include the consumer header, although they are
not.

Signed-off-by: Michael Walle <[email protected]>
---
changes since v1:
- new patch

include/linux/nvmem-consumer.h | 10 +---------
include/linux/nvmem-provider.h | 19 ++++++++++++++++++-
2 files changed, 19 insertions(+), 10 deletions(-)

diff --git a/include/linux/nvmem-consumer.h b/include/linux/nvmem-consumer.h
index 980f9c9ac0bc..1f62f7ba71ca 100644
--- a/include/linux/nvmem-consumer.h
+++ b/include/linux/nvmem-consumer.h
@@ -18,15 +18,7 @@ struct device_node;
/* consumer cookie */
struct nvmem_cell;
struct nvmem_device;
-
-struct nvmem_cell_info {
- const char *name;
- unsigned int offset;
- unsigned int bytes;
- unsigned int bit_offset;
- unsigned int nbits;
- struct device_node *np;
-};
+struct nvmem_cell_info;

/**
* struct nvmem_cell_lookup - cell lookup entry
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index 8f964b394292..14a32a1bc249 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -14,7 +14,6 @@
#include <linux/gpio/consumer.h>

struct nvmem_device;
-struct nvmem_cell_info;
typedef int (*nvmem_reg_read_t)(void *priv, unsigned int offset,
void *val, size_t bytes);
typedef int (*nvmem_reg_write_t)(void *priv, unsigned int offset,
@@ -47,6 +46,24 @@ struct nvmem_keepout {
unsigned char value;
};

+/**
+ * struct nvmem_cell_info - NVMEM cell description
+ * @name: Name.
+ * @offset: Offset within the NVMEM device.
+ * @bytes: Length of the cell.
+ * @bit_offset: Bit offset if cell is smaller than a byte.
+ * @nbits: Number of bits.
+ * @np: Optional device_node pointer.
+ */
+struct nvmem_cell_info {
+ const char *name;
+ unsigned int offset;
+ unsigned int bytes;
+ unsigned int bit_offset;
+ unsigned int nbits;
+ struct device_node *np;
+};
+
/**
* struct nvmem_config - NVMEM device configuration
*
--
2.30.2

2022-09-01 22:23:27

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 06/20] nvmem: core: add nvmem_add_one_cell()

Add a new function to add exactly one cell. This will be used by the
nvmem layout drivers to add custom cells. In contrast to the
nvmem_add_cells(), this has the advantage that we don't have to assemble
a list of cells on runtime.

Signed-off-by: Michael Walle <[email protected]>
---
changes since v1:
- none

drivers/nvmem/core.c | 58 ++++++++++++++++++++--------------
include/linux/nvmem-provider.h | 8 +++++
2 files changed, 42 insertions(+), 24 deletions(-)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index be38e62fd190..3dfd149374a8 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -501,6 +501,35 @@ static int nvmem_cell_info_to_nvmem_cell_entry(struct nvmem_device *nvmem,
return 0;
}

+/**
+ * nvmem_add_one_cell() - Add one cell information to an nvmem device
+ *
+ * @nvmem: nvmem device to add cells to.
+ * @info: nvmem cell info to add to the device
+ *
+ * Return: 0 or negative error code on failure.
+ */
+int nvmem_add_one_cell(struct nvmem_device *nvmem,
+ const struct nvmem_cell_info *info)
+{
+ struct nvmem_cell_entry *cell;
+ int rval;
+
+ cell = kzalloc(sizeof(*cell), GFP_KERNEL);
+ if (!cell)
+ return -ENOMEM;
+
+ rval = nvmem_cell_info_to_nvmem_cell_entry(nvmem, info, cell);
+ if (rval) {
+ kfree(cell);
+ return rval;
+ }
+
+ nvmem_cell_entry_add(cell);
+
+ return 0;
+}
+
/**
* nvmem_add_cells() - Add cell information to an nvmem device
*
@@ -514,34 +543,15 @@ static int nvmem_add_cells(struct nvmem_device *nvmem,
const struct nvmem_cell_info *info,
int ncells)
{
- struct nvmem_cell_entry **cells;
- int i, rval = 0;
-
- cells = kcalloc(ncells, sizeof(*cells), GFP_KERNEL);
- if (!cells)
- return -ENOMEM;
+ int i, rval;

for (i = 0; i < ncells; i++) {
- cells[i] = kzalloc(sizeof(**cells), GFP_KERNEL);
- if (!cells[i]) {
- rval = -ENOMEM;
- goto out;
- }
-
- rval = nvmem_cell_info_to_nvmem_cell_entry(nvmem, &info[i], cells[i]);
- if (rval) {
- kfree(cells[i]);
- goto out;
- }
-
- nvmem_cell_entry_add(cells[i]);
+ rval = nvmem_add_one_cell(nvmem, &info[i]);
+ if (rval)
+ return rval;
}

-out:
- /* remove tmp array */
- kfree(cells);
-
- return rval;
+ return 0;
}

/**
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index 14a32a1bc249..385d29168008 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -155,6 +155,9 @@ struct nvmem_device *devm_nvmem_register(struct device *dev,
void nvmem_add_cell_table(struct nvmem_cell_table *table);
void nvmem_del_cell_table(struct nvmem_cell_table *table);

+int nvmem_add_one_cell(struct nvmem_device *nvmem,
+ const struct nvmem_cell_info *info);
+
#else

static inline struct nvmem_device *nvmem_register(const struct nvmem_config *c)
@@ -172,6 +175,11 @@ devm_nvmem_register(struct device *dev, const struct nvmem_config *c)

static inline void nvmem_add_cell_table(struct nvmem_cell_table *table) {}
static inline void nvmem_del_cell_table(struct nvmem_cell_table *table) {}
+static inline int nvmem_add_one_cell(struct nvmem_device *nvmem,
+ const struct nvmem_cell_info *info)
+{
+ return -EOPNOTSUPP;
+}

#endif /* CONFIG_NVMEM */
#endif /* ifndef _LINUX_NVMEM_PROVIDER_H */
--
2.30.2

2022-09-01 22:23:29

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 08/20] nvmem: core: introduce NVMEM layouts

NVMEM layouts are used to generate NVMEM cells during runtime. Think of
an EEPROM with a well-defined conent. For now, the content can be
described by a device tree or a board file. But this only works if the
offsets and lengths are static and don't change. One could also argue
that putting the layout of the EEPROM in the device tree is the wrong
place. Instead, the device tree should just have a specific compatible
string.

Right now there are two use cases:
(1) The NVMEM cell needs special processing. E.g. if it only specifies
a base MAC address offset and you need to add an offset, or it
needs to parse a MAC from ASCII format or some proprietary format.
(Post processing of cells is added in a later commit).
(2) u-boot environment parsing. The cells don't have a particular
offset but it needs parsing the content to determine the offsets
and length.

Signed-off-by: Michael Walle <[email protected]>
---
changes since v1:
- add documentation in nvmem.rst
- add nvmem_layout_unregister() + necessary module tracking
- make it possible to supply a layout via nvmem_register()
- check add_cells, before calling

Documentation/driver-api/nvmem.rst | 15 ++++++
drivers/nvmem/Kconfig | 4 ++
drivers/nvmem/Makefile | 1 +
drivers/nvmem/core.c | 85 ++++++++++++++++++++++++++++++
drivers/nvmem/layouts/Kconfig | 5 ++
drivers/nvmem/layouts/Makefile | 4 ++
include/linux/nvmem-provider.h | 51 ++++++++++++++++++
7 files changed, 165 insertions(+)
create mode 100644 drivers/nvmem/layouts/Kconfig
create mode 100644 drivers/nvmem/layouts/Makefile

diff --git a/Documentation/driver-api/nvmem.rst b/Documentation/driver-api/nvmem.rst
index e3366322d46c..de221e91c8e3 100644
--- a/Documentation/driver-api/nvmem.rst
+++ b/Documentation/driver-api/nvmem.rst
@@ -185,3 +185,18 @@ ex::
=====================

See Documentation/devicetree/bindings/nvmem/nvmem.txt
+
+8. NVMEM layouts
+================
+
+NVMEM layouts are yet another mechanism to create cells. With the device
+tree binding it is possible to specify simple cells by using an offset
+and a length. Sometimes, the cells doesn't have a static offset, but
+the content is still well defined, e.g. tag-length-values. In this case,
+the NVMEM device content has to be first parsed and the cells need to
+be added accordingly. Layouts let you read the content of the NVMEM device
+and let you add cells dynamically.
+
+Another use case for layouts is the post processing of cells. With layouts,
+it is possible to associate a custom post processing hook to a cell. It
+even possible to add this hook to cells not created by the layout itself.
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index 7f2557934834..adacb113cb94 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -21,6 +21,10 @@ config NVMEM_SYSFS
This interface is mostly used by userspace applications to
read/write directly into nvmem.

+# Layouts
+
+source "drivers/nvmem/layouts/Kconfig"
+
# Devices

config NVMEM_APPLE_EFUSES
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
index bac799b2fa8d..b8bd1199686e 100644
--- a/drivers/nvmem/Makefile
+++ b/drivers/nvmem/Makefile
@@ -5,6 +5,7 @@

obj-$(CONFIG_NVMEM) += nvmem_core.o
nvmem_core-y := core.o
+obj-y += layouts/

# Devices
obj-$(CONFIG_NVMEM_APPLE_EFUSES) += nvmem-apple-efuses.o
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index c640d411ab15..164511537082 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -40,6 +40,7 @@ struct nvmem_device {
nvmem_reg_write_t reg_write;
nvmem_cell_post_process_t cell_post_process;
struct gpio_desc *wp_gpio;
+ struct nvmem_layout *layout;
void *priv;
};

@@ -74,6 +75,9 @@ static LIST_HEAD(nvmem_lookup_list);

static BLOCKING_NOTIFIER_HEAD(nvmem_notifier);

+static DEFINE_SPINLOCK(nvmem_layout_lock);
+static LIST_HEAD(nvmem_layouts);
+
static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
void *val, size_t bytes)
{
@@ -734,6 +738,75 @@ static int nvmem_add_cells_from_of(struct nvmem_device *nvmem)
return 0;
}

+int __nvmem_layout_register(struct nvmem_layout *layout, struct module *owner)
+{
+ layout->owner = owner;
+
+ spin_lock(&nvmem_layout_lock);
+ list_add(&layout->node, &nvmem_layouts);
+ spin_unlock(&nvmem_layout_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(__nvmem_layout_register);
+
+void nvmem_layout_unregister(struct nvmem_layout *layout)
+{
+ spin_lock(&nvmem_layout_lock);
+ list_del(&layout->node);
+ spin_unlock(&nvmem_layout_lock);
+}
+EXPORT_SYMBOL_GPL(nvmem_layout_unregister);
+
+static struct nvmem_layout *nvmem_layout_get(struct nvmem_device *nvmem)
+{
+ struct device_node *np = nvmem->dev.of_node;
+ struct nvmem_layout *l, *layout = NULL;
+
+ spin_lock(&nvmem_layout_lock);
+
+ list_for_each_entry(l, &nvmem_layouts, node) {
+ if (!of_match_node(l->of_match_table, np))
+ continue;
+
+ if (try_module_get(l->owner))
+ layout = l;
+
+ break;
+ }
+
+ spin_unlock(&nvmem_layout_lock);
+
+ return layout;
+}
+
+static void nvmem_layout_put(struct nvmem_layout *layout)
+{
+ if (layout)
+ module_put(layout->owner);
+}
+
+static int nvmem_add_cells_from_layout(struct nvmem_device *nvmem)
+{
+ struct nvmem_layout *layout = nvmem->layout;
+
+ if (layout && layout->add_cells)
+ layout->add_cells(&nvmem->dev, nvmem, layout);
+
+ return 0;
+}
+
+const void *nvmem_layout_get_match_data(struct nvmem_device *nvmem,
+ struct nvmem_layout *layout)
+{
+ const struct of_device_id *match;
+
+ match = of_match_node(layout->of_match_table, nvmem->dev.of_node);
+
+ return match ? match->data : NULL;
+}
+EXPORT_SYMBOL_GPL(nvmem_layout_get_match_data);
+
/**
* nvmem_register() - Register a nvmem device for given nvmem_config.
* Also creates a binary entry in /sys/bus/nvmem/devices/dev-name/nvmem
@@ -848,6 +921,12 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
goto err_device_del;
}

+ /*
+ * If the driver supplied a layout by config->layout, the module
+ * pointer will be NULL and nvmem_layout_put() will be a noop.
+ */
+ nvmem->layout = config->layout ?: nvmem_layout_get(nvmem);
+
if (config->cells) {
rval = nvmem_add_cells(nvmem, config->cells, config->ncells);
if (rval)
@@ -862,6 +941,10 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
if (rval)
goto err_remove_cells;

+ rval = nvmem_add_cells_from_layout(nvmem);
+ if (rval)
+ goto err_remove_cells;
+
blocking_notifier_call_chain(&nvmem_notifier, NVMEM_ADD, nvmem);

return nvmem;
@@ -871,6 +954,7 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
err_teardown_compat:
if (config->compat)
nvmem_sysfs_remove_compat(nvmem, config);
+ nvmem_layout_put(nvmem->layout);
err_device_del:
device_del(&nvmem->dev);
err_put_device:
@@ -892,6 +976,7 @@ static void nvmem_device_release(struct kref *kref)
device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom);

nvmem_device_remove_all_cells(nvmem);
+ nvmem_layout_put(nvmem->layout);
device_unregister(&nvmem->dev);
}

diff --git a/drivers/nvmem/layouts/Kconfig b/drivers/nvmem/layouts/Kconfig
new file mode 100644
index 000000000000..9ad3911d1605
--- /dev/null
+++ b/drivers/nvmem/layouts/Kconfig
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+menu "Layout Types"
+
+endmenu
diff --git a/drivers/nvmem/layouts/Makefile b/drivers/nvmem/layouts/Makefile
new file mode 100644
index 000000000000..6fdb3c60a4fa
--- /dev/null
+++ b/drivers/nvmem/layouts/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for nvmem layouts.
+#
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index 72accdbe1d96..8a41bb30fc87 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -89,6 +89,7 @@ struct nvmem_cell_info {
* @priv: User context passed to read/write callbacks.
* @wp-gpio: Write protect pin
* @ignore_wp: Write Protect pin is managed by the provider.
+ * @layout: Fixed layout associated with this nvmem device.
*
* Note: A default "nvmem<id>" name will be assigned to the device if
* no name is specified in its configuration. In such case "<id>" is
@@ -111,6 +112,7 @@ struct nvmem_config {
bool read_only;
bool root_only;
bool ignore_wp;
+ struct nvmem_layout *layout;
struct device_node *of_node;
bool no_of_node;
nvmem_reg_read_t reg_read;
@@ -144,6 +146,33 @@ struct nvmem_cell_table {
struct list_head node;
};

+/**
+ * struct nvmem_layout - NVMEM layout definitions
+ *
+ * @name: Layout name.
+ * @of_match_table: Open firmware match table.
+ * @add_cells: Will be called if a nvmem device is found which
+ * has this layout. The function will add layout
+ * specific cells with nvmem_add_one_cell().
+ * @owner: Pointer to struct module.
+ * @node: List node.
+ *
+ * A nvmem device can hold a well defined structure which can just be
+ * evaluated during runtime. For example a TLV list, or a list of "name=val"
+ * pairs. A nvmem layout can parse the nvmem device and add appropriate
+ * cells.
+ */
+struct nvmem_layout {
+ const char *name;
+ const struct of_device_id *of_match_table;
+ int (*add_cells)(struct device *dev, struct nvmem_device *nvmem,
+ struct nvmem_layout *layout);
+
+ /* private */
+ struct module *owner;
+ struct list_head node;
+};
+
#if IS_ENABLED(CONFIG_NVMEM)

struct nvmem_device *nvmem_register(const struct nvmem_config *cfg);
@@ -158,6 +187,14 @@ void nvmem_del_cell_table(struct nvmem_cell_table *table);
int nvmem_add_one_cell(struct nvmem_device *nvmem,
const struct nvmem_cell_info *info);

+int __nvmem_layout_register(struct nvmem_layout *layout, struct module *owner);
+#define nvmem_layout_register(layout) \
+ __nvmem_layout_register(layout, THIS_MODULE)
+void nvmem_layout_unregister(struct nvmem_layout *layout);
+
+const void *nvmem_layout_get_match_data(struct nvmem_device *nvmem,
+ struct nvmem_layout *layout);
+
#else

static inline struct nvmem_device *nvmem_register(const struct nvmem_config *c)
@@ -178,5 +215,19 @@ static inline void nvmem_del_cell_table(struct nvmem_cell_table *table) {}
static inline int nvmem_add_one_cell(struct nvmem_device *nvmem,
const struct nvmem_cell_info *info) {}

+static inline int nvmem_layout_register(struct nvmem_layout *layout)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void nvmem_layout_unregister(struct nvmem_layout *layout) {}
+
+static inline const void *
+nvmem_layout_get_match_data(struct nvmem_device *nvmem,
+ struct nvmem_layout *layout)
+{
+ return NULL;
+}
+
#endif /* CONFIG_NVMEM */
#endif /* ifndef _LINUX_NVMEM_PROVIDER_H */
--
2.30.2

2022-09-01 22:23:37

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 05/20] nvmem: core: drop the removal of the cells in nvmem_add_cells()

If nvmem_add_cells() fails, the whole nvmem_register() will fail
and the cells will then be removed anyway. This is a prepartion
to introduce a nvmem_add_one_cell() which can then be used by
nvmem_add_cells().

This is then the same to what nvmem_add_cells_from_table() and
nvmem_add_cells_from_of() do.

Signed-off-by: Michael Walle <[email protected]>
---
changes since v1:
- none

drivers/nvmem/core.c | 14 ++++----------
1 file changed, 4 insertions(+), 10 deletions(-)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index ab055e4fc409..be38e62fd190 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -515,7 +515,7 @@ static int nvmem_add_cells(struct nvmem_device *nvmem,
int ncells)
{
struct nvmem_cell_entry **cells;
- int i, rval;
+ int i, rval = 0;

cells = kcalloc(ncells, sizeof(*cells), GFP_KERNEL);
if (!cells)
@@ -525,28 +525,22 @@ static int nvmem_add_cells(struct nvmem_device *nvmem,
cells[i] = kzalloc(sizeof(**cells), GFP_KERNEL);
if (!cells[i]) {
rval = -ENOMEM;
- goto err;
+ goto out;
}

rval = nvmem_cell_info_to_nvmem_cell_entry(nvmem, &info[i], cells[i]);
if (rval) {
kfree(cells[i]);
- goto err;
+ goto out;
}

nvmem_cell_entry_add(cells[i]);
}

+out:
/* remove tmp array */
kfree(cells);

- return 0;
-err:
- while (i--)
- nvmem_cell_entry_drop(cells[i]);
-
- kfree(cells);
-
return rval;
}

--
2.30.2

2022-09-01 22:24:02

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 09/20] nvmem: core: add per-cell post processing

Instead of relying on the name the consumer is using for the cell, like
it is done for the nvmem .cell_post_process configuration parameter,
provide a per-cell post processing hook. This can then be populated by
the NVMEM provider (or the NVMEM layout) when adding the cell.

Signed-off-by: Michael Walle <[email protected]>
---
changes since v1:
- rename hook to read_post_process

drivers/nvmem/core.c | 17 +++++++++++++++++
include/linux/nvmem-provider.h | 3 +++
2 files changed, 20 insertions(+)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 164511537082..02d0a7099d9a 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -53,6 +53,7 @@ struct nvmem_cell_entry {
int bytes;
int bit_offset;
int nbits;
+ nvmem_cell_post_process_t read_post_process;
struct device_node *np;
struct nvmem_device *nvmem;
struct list_head node;
@@ -469,6 +470,7 @@ static int nvmem_cell_info_to_nvmem_cell_entry_nodup(struct nvmem_device *nvmem,
cell->offset = info->offset;
cell->bytes = info->bytes;
cell->name = info->name;
+ cell->read_post_process = info->read_post_process;

cell->bit_offset = info->bit_offset;
cell->nbits = info->nbits;
@@ -1518,6 +1520,13 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
if (cell->bit_offset || cell->nbits)
nvmem_shift_read_buffer_in_place(cell, buf);

+ if (cell->read_post_process) {
+ rc = cell->read_post_process(nvmem->priv, id, index,
+ cell->offset, buf, cell->bytes);
+ if (rc)
+ return rc;
+ }
+
if (nvmem->cell_post_process) {
rc = nvmem->cell_post_process(nvmem->priv, id, index,
cell->offset, buf, cell->bytes);
@@ -1626,6 +1635,14 @@ static int __nvmem_cell_entry_write(struct nvmem_cell_entry *cell, void *buf, si
(cell->bit_offset == 0 && len != cell->bytes))
return -EINVAL;

+ /*
+ * Any cells which have a read_post_process hook are read-only because
+ * we cannot reverse the operation and it might affect other cells,
+ * too.
+ */
+ if (cell->read_post_process)
+ return -EINVAL;
+
if (cell->bit_offset || cell->nbits) {
buf = nvmem_cell_prepare_write_buffer(cell, buf, len);
if (IS_ERR(buf))
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index 8a41bb30fc87..cb488b338d6f 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -54,6 +54,8 @@ struct nvmem_keepout {
* @bit_offset: Bit offset if cell is smaller than a byte.
* @nbits: Number of bits.
* @np: Optional device_node pointer.
+ * @read_post_process: Callback for optional post processing of cell data
+ * on reads.
*/
struct nvmem_cell_info {
const char *name;
@@ -62,6 +64,7 @@ struct nvmem_cell_info {
unsigned int bit_offset;
unsigned int nbits;
struct device_node *np;
+ nvmem_cell_post_process_t read_post_process;
};

/**
--
2.30.2

2022-09-01 22:24:12

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 07/20] nvmem: core: use nvmem_add_one_cell() in nvmem_add_cells_from_of()

Convert nvmem_add_cells_from_of() to use the new nvmem_add_one_cell().
This will remove duplicate code and it will make it possible to add a
hook to a nvmem layout in between, which can change fields before the
cell is finally added.

Signed-off-by: Michael Walle <[email protected]>
---
changes since v1:
- new patch

drivers/nvmem/core.c | 38 ++++++++++++++------------------------
1 file changed, 14 insertions(+), 24 deletions(-)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 3dfd149374a8..c640d411ab15 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -687,15 +687,15 @@ static int nvmem_validate_keepouts(struct nvmem_device *nvmem)

static int nvmem_add_cells_from_of(struct nvmem_device *nvmem)
{
- struct device_node *parent, *child;
struct device *dev = &nvmem->dev;
struct nvmem_cell_entry *cell;
+ struct device_node *child;
const __be32 *addr;
- int len;
+ int len, ret;

- parent = dev->of_node;
+ for_each_child_of_node(dev->of_node, child) {
+ struct nvmem_cell_info info = {0};

- for_each_child_of_node(parent, child) {
addr = of_get_property(child, "reg", &len);
if (!addr)
continue;
@@ -711,34 +711,24 @@ static int nvmem_add_cells_from_of(struct nvmem_device *nvmem)
return -ENOMEM;
}

- cell->nvmem = nvmem;
- cell->offset = be32_to_cpup(addr++);
- cell->bytes = be32_to_cpup(addr);
- cell->name = kasprintf(GFP_KERNEL, "%pOFn", child);
+ info.offset = be32_to_cpup(addr++);
+ info.bytes = be32_to_cpup(addr);
+ info.name = kasprintf(GFP_KERNEL, "%pOFn", child);

addr = of_get_property(child, "bits", &len);
if (addr && len == (2 * sizeof(u32))) {
- cell->bit_offset = be32_to_cpup(addr++);
- cell->nbits = be32_to_cpup(addr);
+ info.bit_offset = be32_to_cpup(addr++);
+ info.nbits = be32_to_cpup(addr);
}

- if (cell->nbits)
- cell->bytes = DIV_ROUND_UP(
- cell->nbits + cell->bit_offset,
- BITS_PER_BYTE);
+ info.np = of_node_get(child);

- if (!IS_ALIGNED(cell->offset, nvmem->stride)) {
- dev_err(dev, "cell %s unaligned to nvmem stride %d\n",
- cell->name, nvmem->stride);
- /* Cells already added will be freed later. */
- kfree_const(cell->name);
- kfree(cell);
+ ret = nvmem_add_one_cell(nvmem, &info);
+ kfree(info.name);
+ if (ret) {
of_node_put(child);
- return -EINVAL;
+ return ret;
}
-
- cell->np = of_node_get(child);
- nvmem_cell_entry_add(cell);
}

return 0;
--
2.30.2

2022-09-01 22:46:08

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 14/20] dt-bindings: mtd: relax the nvmem compatible string

The "user-otp" and "factory-otp" compatible string just depicts a
generic NVMEM device. But an actual device tree node might as well
contain a more specific compatible string. Make it possible to add
more specific binding elsewhere and just match part of the compatibles
here.

Signed-off-by: Michael Walle <[email protected]>
---
changes since v1:
- fix typo in commit message

Documentation/devicetree/bindings/mtd/mtd.yaml | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/Documentation/devicetree/bindings/mtd/mtd.yaml b/Documentation/devicetree/bindings/mtd/mtd.yaml
index 3498e485679b..25b91f25fcf4 100644
--- a/Documentation/devicetree/bindings/mtd/mtd.yaml
+++ b/Documentation/devicetree/bindings/mtd/mtd.yaml
@@ -33,9 +33,10 @@ patternProperties:

properties:
compatible:
- enum:
- - user-otp
- - factory-otp
+ contains:
+ enum:
+ - user-otp
+ - factory-otp

required:
- compatible
--
2.30.2

2022-09-01 22:46:29

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 11/20] nvmem: imx-ocotp: replace global post processing with layouts

In preparation of retiring the global post processing hook change this
driver to use layouts. The layout will be supplied during registration
and will be used to add the post processing hook to all added cells.

Signed-off-by: Michael Walle <[email protected]>
---
Complile-time tested only! Please test.

changes since v1:
- new patch

drivers/nvmem/imx-ocotp.c | 30 +++++++++++++++++++-----------
1 file changed, 19 insertions(+), 11 deletions(-)

diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c
index e9b52ecb3f72..ac0edb6398f1 100644
--- a/drivers/nvmem/imx-ocotp.c
+++ b/drivers/nvmem/imx-ocotp.c
@@ -225,18 +225,13 @@ static int imx_ocotp_read(void *context, unsigned int offset,
static int imx_ocotp_cell_pp(void *context, const char *id, int index,
unsigned int offset, void *data, size_t bytes)
{
- struct ocotp_priv *priv = context;
+ u8 *buf = data;
+ int i;

/* Deal with some post processing of nvmem cell data */
- if (id && !strcmp(id, "mac-address")) {
- if (priv->params->reverse_mac_address) {
- u8 *buf = data;
- int i;
-
- for (i = 0; i < bytes/2; i++)
- swap(buf[i], buf[bytes - i - 1]);
- }
- }
+ if (id && !strcmp(id, "mac-address"))
+ for (i = 0; i < bytes / 2; i++)
+ swap(buf[i], buf[bytes - i - 1]);

return 0;
}
@@ -488,7 +483,6 @@ static struct nvmem_config imx_ocotp_nvmem_config = {
.stride = 1,
.reg_read = imx_ocotp_read,
.reg_write = imx_ocotp_write,
- .cell_post_process = imx_ocotp_cell_pp,
};

static const struct ocotp_params imx6q_params = {
@@ -595,6 +589,17 @@ static const struct of_device_id imx_ocotp_dt_ids[] = {
};
MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids);

+static void imx_ocotp_fixup_cell_info(struct nvmem_device *nvmem,
+ struct nvmem_layout *layout,
+ struct nvmem_cell_info *cell)
+{
+ cell->read_post_process = imx_ocotp_cell_pp;
+}
+
+struct nvmem_layout imx_ocotp_layout = {
+ .fixup_cell_info = imx_ocotp_fixup_cell_info,
+};
+
static int imx_ocotp_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -619,6 +624,9 @@ static int imx_ocotp_probe(struct platform_device *pdev)
imx_ocotp_nvmem_config.size = 4 * priv->params->nregs;
imx_ocotp_nvmem_config.dev = dev;
imx_ocotp_nvmem_config.priv = priv;
+ if (priv->params->reverse_mac_address)
+ imx_ocotp_nvmem_config.layout = &imx_ocotp_layout;
+
priv->config = &imx_ocotp_nvmem_config;

clk_prepare_enable(priv->clk);
--
2.30.2

2022-09-01 22:46:36

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 15/20] dt-bindings: nvmem: add YAML schema for the sl28 vpd layout

Add a schema for the NVMEM layout on Kontron's sl28 boards.

Signed-off-by: Michael Walle <[email protected]>
---
changes since v1:
- add custom select
- add description
- add "additionalProperties: false", I wasn't sure if all the
subnodes needs it. I'd say yes, but the brcm,nvram binding
doesn't have them neither.

.../nvmem/layouts/kontron,sl28-vpd.yaml | 67 +++++++++++++++++++
1 file changed, 67 insertions(+)
create mode 100644 Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml

diff --git a/Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml b/Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml
new file mode 100644
index 000000000000..0c180f29e880
--- /dev/null
+++ b/Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/nvmem/layouts/kontron,sl28-vpd.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NVMEM layout of the Kontron SMARC-sAL28 vital product data
+
+maintainers:
+ - Michael Walle <[email protected]>
+
+description:
+ The vital product data (VPD) of the sl28 boards contains a serial
+ number and a base MAC address. The actual MAC addresses for the
+ on-board ethernet devices are derived from this base MAC address by
+ adding an offset.
+
+# We need a select here so we don't match all nodes with 'user-otp'
+select:
+ properties:
+ compatible:
+ contains:
+ const: kontron,sl28-vpd
+ required:
+ - compatible
+
+properties:
+ compatible:
+ items:
+ - const: kontron,sl28-vpd
+ - const: user-otp
+
+ serial-number:
+ type: object
+ description: The board's serial number
+
+ base-mac-address:
+ type: object
+ description:
+ Base MAC address for all on-module network interfaces. The first
+ argument of the phandle will be treated as an offset.
+
+ properties:
+ "#nvmem-cell-cells":
+ const: 1
+
+ additionalProperties: false
+
+required:
+ - compatible
+
+additionalProperties: false
+
+examples:
+ - |
+ otp-1 {
+ compatible = "kontron,sl28-vpd", "user-otp";
+
+ serial_number: serial-number {
+ };
+
+ base_mac_address: base-mac-address {
+ #nvmem-cell-cells = <1>;
+ };
+ };
+
+...
--
2.30.2

2022-09-01 22:47:59

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 13/20] nvmem: core: drop priv pointer in post process callback

It doesn't make any more sense to have a opaque pointer set up by the
nvmem device. Usually, the layout isn't associated with a particular
nvmem device.

Signed-off-by: Michael Walle <[email protected]>
---
changes since v1:
- new patch

drivers/nvmem/core.c | 4 ++--
drivers/nvmem/imx-ocotp.c | 4 ++--
include/linux/nvmem-provider.h | 5 +++--
3 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index d31d3f0ab517..6910796937f9 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -1523,8 +1523,8 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
nvmem_shift_read_buffer_in_place(cell, buf);

if (cell->read_post_process) {
- rc = cell->read_post_process(nvmem->priv, id, index,
- cell->offset, buf, cell->bytes);
+ rc = cell->read_post_process(id, index, cell->offset, buf,
+ cell->bytes);
if (rc)
return rc;
}
diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c
index ac0edb6398f1..5e869d4a81c5 100644
--- a/drivers/nvmem/imx-ocotp.c
+++ b/drivers/nvmem/imx-ocotp.c
@@ -222,8 +222,8 @@ static int imx_ocotp_read(void *context, unsigned int offset,
return ret;
}

-static int imx_ocotp_cell_pp(void *context, const char *id, int index,
- unsigned int offset, void *data, size_t bytes)
+static int imx_ocotp_cell_pp(const char *id, int index, unsigned int offset,
+ void *data, size_t bytes)
{
u8 *buf = data;
int i;
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index 9d22dc5a3fa5..46067a6a0395 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -19,8 +19,9 @@ typedef int (*nvmem_reg_read_t)(void *priv, unsigned int offset,
typedef int (*nvmem_reg_write_t)(void *priv, unsigned int offset,
void *val, size_t bytes);
/* used for vendor specific post processing of cell data */
-typedef int (*nvmem_cell_post_process_t)(void *priv, const char *id, int index,
- unsigned int offset, void *buf, size_t bytes);
+typedef int (*nvmem_cell_post_process_t)(const char *id, int index,
+ unsigned int offset, void *buf,
+ size_t bytes);

enum nvmem_type {
NVMEM_TYPE_UNKNOWN = 0,
--
2.30.2

2022-09-01 23:13:09

by Michael Walle

[permalink] [raw]
Subject: [RFC PATCH v2 20/20] nvmem: layouts: u-boot-env: add device node

Register the device node so we can actually make use of the cells from
within the device tree.

This obviously only works if the environment variable name can be mapped
to the device node, which isn't always the case. Think of "_" vs "-".
But for simple things like ethaddr, this will work.

Signed-off-by: Michael Walle <[email protected]>
---
changes since v1:
- none

drivers/nvmem/layouts/u-boot-env.c | 2 ++
1 file changed, 2 insertions(+)

diff --git a/drivers/nvmem/layouts/u-boot-env.c b/drivers/nvmem/layouts/u-boot-env.c
index f184d1424b1e..d2adc246c93a 100644
--- a/drivers/nvmem/layouts/u-boot-env.c
+++ b/drivers/nvmem/layouts/u-boot-env.c
@@ -8,6 +8,7 @@
#include <linux/device.h>
#include <linux/nvmem-consumer.h>
#include <linux/nvmem-provider.h>
+#include <linux/of.h>
#include <linux/slab.h>

enum u_boot_env_format {
@@ -47,6 +48,7 @@ static int u_boot_env_add_cells(struct device *dev,
info.name = var;
info.offset = data_offset + value - data;
info.bytes = strlen(value);
+ info.np = of_get_child_by_name(dev->of_node, var);

err = nvmem_add_one_cell(nvmem, &info);
if (err)
--
2.30.2

2022-09-01 23:14:04

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 18/20] arm64: dts: ls1028a: sl28: get MAC addresses from VPD

Now that it is finally possible to get the MAC addresses from the OTP
memory, use it to set the addresses of the network devices.

There are 8 reserved MAC addresses in total per board. Distribute them
as follows:

+----------+------+------+------+------+------+
| | var1 | var2 | var3 | var4 | kbox |
+----------+------+------+------+------+------+
| enetc #0 | +0 | | | +0 | +0 |
| enetc #1 | | | +0 | +1 | +1 |
| enetc #2 | rand | rand | rand | rand | rand |
| enetc #3 | | | | | |
| felix p0 | | +0 | | | +4 |
| felix p1 | | +1 | | | +5 |
| felix p2 | | | | | +6 |
| felix p3 | | | | | +7 |
| felix p4 | | | | | |
| felix p5 | | | | | |
+----------+------+------+------+------+------+

An empty cell means, the port is not available and thus doesn't need an
ethernet address.

Signed-off-by: Michael Walle <[email protected]>
---
.../freescale/fsl-ls1028a-kontron-kbox-a-230-ls.dts | 8 ++++++++
.../dts/freescale/fsl-ls1028a-kontron-sl28-var1.dts | 2 ++
.../dts/freescale/fsl-ls1028a-kontron-sl28-var2.dts | 4 ++++
.../dts/freescale/fsl-ls1028a-kontron-sl28-var4.dts | 2 ++
.../boot/dts/freescale/fsl-ls1028a-kontron-sl28.dts | 13 +++++++++++++
5 files changed, 29 insertions(+)

diff --git a/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-kbox-a-230-ls.dts b/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-kbox-a-230-ls.dts
index 6b575efd84a7..b5c874c145d3 100644
--- a/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-kbox-a-230-ls.dts
+++ b/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-kbox-a-230-ls.dts
@@ -76,6 +76,8 @@ &mscc_felix_port0 {
managed = "in-band-status";
phy-handle = <&qsgmii_phy0>;
phy-mode = "qsgmii";
+ nvmem-cells = <&base_mac_address 4>;
+ nvmem-cell-names = "mac-address";
status = "okay";
};

@@ -84,6 +86,8 @@ &mscc_felix_port1 {
managed = "in-band-status";
phy-handle = <&qsgmii_phy1>;
phy-mode = "qsgmii";
+ nvmem-cells = <&base_mac_address 5>;
+ nvmem-cell-names = "mac-address";
status = "okay";
};

@@ -92,6 +96,8 @@ &mscc_felix_port2 {
managed = "in-band-status";
phy-handle = <&qsgmii_phy2>;
phy-mode = "qsgmii";
+ nvmem-cells = <&base_mac_address 6>;
+ nvmem-cell-names = "mac-address";
status = "okay";
};

@@ -100,6 +106,8 @@ &mscc_felix_port3 {
managed = "in-band-status";
phy-handle = <&qsgmii_phy3>;
phy-mode = "qsgmii";
+ nvmem-cells = <&base_mac_address 7>;
+ nvmem-cell-names = "mac-address";
status = "okay";
};

diff --git a/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28-var1.dts b/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28-var1.dts
index 7cd29ab970d9..1f34c7553459 100644
--- a/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28-var1.dts
+++ b/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28-var1.dts
@@ -55,5 +55,7 @@ &enetc_port0 {
&enetc_port1 {
phy-handle = <&phy0>;
phy-mode = "rgmii-id";
+ nvmem-cells = <&base_mac_address 0>;
+ nvmem-cell-names = "mac-address";
status = "okay";
};
diff --git a/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28-var2.dts b/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28-var2.dts
index 330e34f933a3..0ed0d2545922 100644
--- a/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28-var2.dts
+++ b/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28-var2.dts
@@ -48,6 +48,8 @@ &mscc_felix_port0 {
managed = "in-band-status";
phy-handle = <&phy0>;
phy-mode = "sgmii";
+ nvmem-cells = <&base_mac_address 0>;
+ nvmem-cell-names = "mac-address";
status = "okay";
};

@@ -56,6 +58,8 @@ &mscc_felix_port1 {
managed = "in-band-status";
phy-handle = <&phy1>;
phy-mode = "sgmii";
+ nvmem-cells = <&base_mac_address 1>;
+ nvmem-cell-names = "mac-address";
status = "okay";
};

diff --git a/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28-var4.dts b/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28-var4.dts
index 9b5e92fb753e..a4421db3784e 100644
--- a/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28-var4.dts
+++ b/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28-var4.dts
@@ -43,5 +43,7 @@ vddh: vddh-regulator {
&enetc_port1 {
phy-handle = <&phy1>;
phy-mode = "rgmii-id";
+ nvmem-cells = <&base_mac_address 1>;
+ nvmem-cell-names = "mac-address";
status = "okay";
};
diff --git a/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28.dts b/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28.dts
index 4ab17b984b03..72429b37a8b4 100644
--- a/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28.dts
+++ b/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28.dts
@@ -92,6 +92,8 @@ &enetc_port0 {
phy-handle = <&phy0>;
phy-mode = "sgmii";
managed = "in-band-status";
+ nvmem-cells = <&base_mac_address 0>;
+ nvmem-cell-names = "mac-address";
status = "okay";
};

@@ -154,6 +156,17 @@ [email protected] {
label = "bootloader environment";
};
};
+
+ otp-1 {
+ compatible = "kontron,sl28-vpd", "user-otp";
+
+ serial_number: serial-number {
+ };
+
+ base_mac_address: base-mac-address {
+ #nvmem-cell-cells = <1>;
+ };
+ };
};
};

--
2.30.2

2022-09-01 23:17:16

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 12/20] nvmem: cell: drop global cell_post_process

There are no users anymore for the global cell_post_process callback
anymore. New users should use proper nvmem layouts.

Signed-off-by: Michael Walle <[email protected]>
---
changes since v1:
- new patch

drivers/nvmem/core.c | 9 ---------
include/linux/nvmem-provider.h | 2 --
2 files changed, 11 deletions(-)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 3b65512762b9..d31d3f0ab517 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -38,7 +38,6 @@ struct nvmem_device {
unsigned int nkeepout;
nvmem_reg_read_t reg_read;
nvmem_reg_write_t reg_write;
- nvmem_cell_post_process_t cell_post_process;
struct gpio_desc *wp_gpio;
struct nvmem_layout *layout;
void *priv;
@@ -874,7 +873,6 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
nvmem->type = config->type;
nvmem->reg_read = config->reg_read;
nvmem->reg_write = config->reg_write;
- nvmem->cell_post_process = config->cell_post_process;
nvmem->keepout = config->keepout;
nvmem->nkeepout = config->nkeepout;
if (config->of_node)
@@ -1531,13 +1529,6 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
return rc;
}

- if (nvmem->cell_post_process) {
- rc = nvmem->cell_post_process(nvmem->priv, id, index,
- cell->offset, buf, cell->bytes);
- if (rc)
- return rc;
- }
-
if (len)
*len = cell->bytes;

diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index 9cb340764b89..9d22dc5a3fa5 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -85,7 +85,6 @@ struct nvmem_cell_info {
* @no_of_node: Device should not use the parent's of_node even if it's !NULL.
* @reg_read: Callback to read data.
* @reg_write: Callback to write data.
- * @cell_post_process: Callback for vendor specific post processing of cell data
* @size: Device size.
* @word_size: Minimum read/write access granularity.
* @stride: Minimum read/write access stride.
@@ -120,7 +119,6 @@ struct nvmem_config {
bool no_of_node;
nvmem_reg_read_t reg_read;
nvmem_reg_write_t reg_write;
- nvmem_cell_post_process_t cell_post_process;
int size;
int word_size;
int stride;
--
2.30.2

2022-09-01 23:17:21

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 16/20] nvmem: layouts: add sl28vpd layout

This layout applies to the VPD of the Kontron sl28 boards. The VPD only
contains a base MAC address. Therefore, we have to add an individual
offset to it. This is done by taking the second argument of the nvmem
phandle into account. Also this let us checking the VPD version and the
checksum.

Signed-off-by: Michael Walle <[email protected]>
---
changes since v1:
- none

drivers/nvmem/layouts/Kconfig | 9 ++
drivers/nvmem/layouts/Makefile | 2 +
drivers/nvmem/layouts/sl28vpd.c | 144 ++++++++++++++++++++++++++++++++
3 files changed, 155 insertions(+)
create mode 100644 drivers/nvmem/layouts/sl28vpd.c

diff --git a/drivers/nvmem/layouts/Kconfig b/drivers/nvmem/layouts/Kconfig
index 9ad3911d1605..75082f6b471d 100644
--- a/drivers/nvmem/layouts/Kconfig
+++ b/drivers/nvmem/layouts/Kconfig
@@ -2,4 +2,13 @@

menu "Layout Types"

+config NVMEM_LAYOUT_SL28_VPD
+ bool "Kontron sl28 VPD layout support"
+ select CRC8
+ help
+ Say Y here if you want to support the VPD layout of the Kontron
+ SMARC-sAL28 boards.
+
+ If unsure, say N.
+
endmenu
diff --git a/drivers/nvmem/layouts/Makefile b/drivers/nvmem/layouts/Makefile
index 6fdb3c60a4fa..fc617b9e87d0 100644
--- a/drivers/nvmem/layouts/Makefile
+++ b/drivers/nvmem/layouts/Makefile
@@ -2,3 +2,5 @@
#
# Makefile for nvmem layouts.
#
+
+obj-$(CONFIG_NVMEM_LAYOUT_SL28_VPD) += sl28vpd.o
diff --git a/drivers/nvmem/layouts/sl28vpd.c b/drivers/nvmem/layouts/sl28vpd.c
new file mode 100644
index 000000000000..8cef2633de26
--- /dev/null
+++ b/drivers/nvmem/layouts/sl28vpd.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/crc8.h>
+#include <linux/etherdevice.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/nvmem-provider.h>
+#include <linux/of.h>
+#include <uapi/linux/if_ether.h>
+
+#define SL28VPD_MAGIC 'V'
+
+struct sl28vpd_header {
+ u8 magic;
+ u8 version;
+} __packed;
+
+struct sl28vpd_v1 {
+ struct sl28vpd_header header;
+ char serial_number[15];
+ u8 base_mac_address[ETH_ALEN];
+ u8 crc8;
+} __packed;
+
+static int sl28vpd_mac_address_pp(const char *id, int index,
+ unsigned int offset, void *buf,
+ size_t bytes)
+{
+ if (bytes != ETH_ALEN)
+ return -EINVAL;
+
+ if (index < 0)
+ return -EINVAL;
+
+ if (!is_valid_ether_addr(buf))
+ return -EINVAL;
+
+ eth_addr_add(buf, index);
+
+ return 0;
+}
+
+static const struct nvmem_cell_info sl28vpd_v1_entries[] = {
+ {
+ .name = "serial-number",
+ .offset = offsetof(struct sl28vpd_v1, serial_number),
+ .bytes = sizeof_field(struct sl28vpd_v1, serial_number),
+ },
+ {
+ .name = "base-mac-address",
+ .offset = offsetof(struct sl28vpd_v1, base_mac_address),
+ .bytes = sizeof_field(struct sl28vpd_v1, base_mac_address),
+ .read_post_process = sl28vpd_mac_address_pp,
+ },
+};
+
+static int sl28vpd_v1_check_crc(struct device *dev, struct nvmem_device *nvmem)
+{
+ struct sl28vpd_v1 data_v1;
+ u8 table[CRC8_TABLE_SIZE];
+ int ret;
+ u8 crc;
+
+ crc8_populate_msb(table, 0x07);
+
+ ret = nvmem_device_read(nvmem, 0, sizeof(data_v1), &data_v1);
+ if (ret < 0)
+ return ret;
+ else if (ret != sizeof(data_v1))
+ return -EIO;
+
+ crc = crc8(table, (void *)&data_v1, sizeof(data_v1) - 1, 0);
+
+ if (crc != data_v1.crc8) {
+ dev_err(dev,
+ "Checksum is invalid (got %02x, expected %02x).\n",
+ crc, data_v1.crc8);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sl28vpd_add_cells(struct device *dev, struct nvmem_device *nvmem,
+ struct nvmem_layout *layout)
+{
+ const struct nvmem_cell_info *pinfo;
+ struct nvmem_cell_info info = {0};
+ struct sl28vpd_header hdr;
+ int ret, i;
+
+ /* check header */
+ ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr);
+ if (ret < 0)
+ return ret;
+ else if (ret != sizeof(hdr))
+ return -EIO;
+
+ if (hdr.magic != SL28VPD_MAGIC) {
+ dev_err(dev, "Invalid magic value (%02x)\n", hdr.magic);
+ return -EINVAL;
+ }
+
+ if (hdr.version != 1) {
+ dev_err(dev, "Version %d is unsupported.\n", hdr.version);
+ return -EINVAL;
+ }
+
+ ret = sl28vpd_v1_check_crc(dev, nvmem);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(sl28vpd_v1_entries); i++) {
+ pinfo = &sl28vpd_v1_entries[i];
+
+ info.name = pinfo->name;
+ info.offset = pinfo->offset;
+ info.bytes = pinfo->bytes;
+ info.read_post_process = pinfo->read_post_process;
+ info.np = of_get_child_by_name(dev->of_node, pinfo->name);
+
+ ret = nvmem_add_one_cell(nvmem, &info);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id sl28vpd_of_match_table[] = {
+ { .compatible = "kontron,sl28-vpd" },
+ {},
+};
+
+struct nvmem_layout sl28vpd_layout = {
+ .name = "sl28-vpd",
+ .of_match_table = sl28vpd_of_match_table,
+ .add_cells = sl28vpd_add_cells,
+};
+
+static int __init sl28vpd_init(void)
+{
+ return nvmem_layout_register(&sl28vpd_layout);
+}
+subsys_initcall(sl28vpd_init);
--
2.30.2

2022-09-01 23:17:22

by Michael Walle

[permalink] [raw]
Subject: [RFC PATCH v2 19/20] nvmem: layouts: rewrite the u-boot-env driver as a NVMEM layout

Instead of hardcoding the underlying access method mtd_read() and
duplicating all the error handling, rewrite the driver as a nvmem
layout which just uses nvmem_device_read() and thus works with any
NVMEM device.

But because this is now not a device anymore, the compatible string
will have to be changed so the device will still be probed:
compatible = "u-boot,env";
to
compatible = "u-boot,env", "nvmem-cells";

"nvmem-cells" will tell the mtd layer to register a nvmem_device().
"u-boot,env" will tell the NVMEM that it should apply the u-boot
environment layout to the NVMEM device.

Signed-off-by: Michael Walle <[email protected]>
---
changes since v1:
- none

drivers/nvmem/layouts/Kconfig | 8 ++
drivers/nvmem/layouts/Makefile | 1 +
drivers/nvmem/layouts/u-boot-env.c | 144 +++++++++++++++++++
drivers/nvmem/u-boot-env.c | 218 -----------------------------
4 files changed, 153 insertions(+), 218 deletions(-)
create mode 100644 drivers/nvmem/layouts/u-boot-env.c
delete mode 100644 drivers/nvmem/u-boot-env.c

diff --git a/drivers/nvmem/layouts/Kconfig b/drivers/nvmem/layouts/Kconfig
index 75082f6b471d..3db0c139a8b7 100644
--- a/drivers/nvmem/layouts/Kconfig
+++ b/drivers/nvmem/layouts/Kconfig
@@ -11,4 +11,12 @@ config NVMEM_LAYOUT_SL28_VPD

If unsure, say N.

+config NVMEM_LAYOUT_U_BOOT_ENV
+ bool "U-Boot environment support"
+ select CRC32
+ help
+ Say Y here if you want to support u-boot's environment.
+
+ If unsure, say N.
+
endmenu
diff --git a/drivers/nvmem/layouts/Makefile b/drivers/nvmem/layouts/Makefile
index fc617b9e87d0..dae93fff2b85 100644
--- a/drivers/nvmem/layouts/Makefile
+++ b/drivers/nvmem/layouts/Makefile
@@ -4,3 +4,4 @@
#

obj-$(CONFIG_NVMEM_LAYOUT_SL28_VPD) += sl28vpd.o
+obj-$(CONFIG_NVMEM_LAYOUT_U_BOOT_ENV) += u-boot-env.o
diff --git a/drivers/nvmem/layouts/u-boot-env.c b/drivers/nvmem/layouts/u-boot-env.c
new file mode 100644
index 000000000000..f184d1424b1e
--- /dev/null
+++ b/drivers/nvmem/layouts/u-boot-env.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2022 Rafał Miłecki <[email protected]>
+ */
+
+#include <linux/crc32.h>
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/nvmem-provider.h>
+#include <linux/slab.h>
+
+enum u_boot_env_format {
+ U_BOOT_FORMAT_SINGLE,
+ U_BOOT_FORMAT_REDUNDANT,
+};
+
+struct u_boot_env_image_single {
+ __le32 crc32;
+ u8 data[];
+} __packed;
+
+struct u_boot_env_image_redundant {
+ __le32 crc32;
+ u8 mark;
+ u8 data[];
+} __packed;
+
+static int u_boot_env_add_cells(struct device *dev,
+ struct nvmem_device *nvmem, uint8_t *buf,
+ size_t data_offset, size_t data_len)
+{
+ struct nvmem_cell_info info = {0};
+ char *data = buf + data_offset;
+ char *var, *value, *eq;
+ int err;
+
+ for (var = data;
+ var < data + data_len && *var;
+ var = value + strlen(value) + 1) {
+ eq = strchr(var, '=');
+ if (!eq)
+ break;
+ *eq = '\0';
+ value = eq + 1;
+
+ info.name = var;
+ info.offset = data_offset + value - data;
+ info.bytes = strlen(value);
+
+ err = nvmem_add_one_cell(nvmem, &info);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int u_boot_add_cells(struct device *dev, struct nvmem_device *nvmem,
+ struct nvmem_layout *layout)
+{
+ size_t size = nvmem_device_size(nvmem);
+ enum u_boot_env_format format;
+ size_t crc32_data_offset;
+ size_t crc32_data_len;
+ size_t crc32_offset;
+ size_t data_offset;
+ size_t data_len;
+ u32 crc32;
+ u32 calc;
+ u8 *buf;
+ int err;
+
+ format = (uintptr_t)nvmem_layout_get_match_data(nvmem, layout);
+
+ buf = kzalloc(size, GFP_KERNEL);
+ if (!buf) {
+ err = -ENOMEM;
+ goto err_out;
+ }
+
+ err = nvmem_device_read(nvmem, 0, size, buf);
+ if (err < 0) {
+ dev_err(dev, "Failed to read from nvmem device (%pe)\n",
+ ERR_PTR(err));
+ goto err_kfree;
+ } else if (err != size) {
+ dev_err(dev, "Short read from nvmem device.\n");
+ err = -EIO;
+ goto err_kfree;
+ }
+
+ switch (format) {
+ case U_BOOT_FORMAT_SINGLE:
+ crc32_offset = offsetof(struct u_boot_env_image_single, crc32);
+ crc32_data_offset = offsetof(struct u_boot_env_image_single, data);
+ data_offset = offsetof(struct u_boot_env_image_single, data);
+ break;
+ case U_BOOT_FORMAT_REDUNDANT:
+ crc32_offset = offsetof(struct u_boot_env_image_redundant, crc32);
+ crc32_data_offset = offsetof(struct u_boot_env_image_redundant, mark);
+ data_offset = offsetof(struct u_boot_env_image_redundant, data);
+ break;
+ }
+ crc32 = le32_to_cpu(*(u32 *)(buf + crc32_offset));
+ crc32_data_len = size - crc32_data_offset;
+ data_len = size - data_offset;
+
+ calc = crc32(~0, buf + crc32_data_offset, crc32_data_len) ^ ~0L;
+ if (calc != crc32) {
+ dev_err(dev, "Invalid calculated CRC32: 0x%08x (expected: 0x%08x)\n", calc, crc32);
+ err = -EINVAL;
+ goto err_kfree;
+ }
+
+ buf[size - 1] = '\0';
+ err = u_boot_env_add_cells(dev, nvmem, buf, data_offset, data_len);
+ if (err)
+ dev_err(dev, "Failed to add cells: %d\n", err);
+
+err_kfree:
+ kfree(buf);
+err_out:
+ return err;
+}
+
+static const struct of_device_id u_boot_env_of_match_table[] = {
+ { .compatible = "u-boot,env", .data = (void *)U_BOOT_FORMAT_SINGLE, },
+ { .compatible = "u-boot,env-redundant-bool", .data = (void *)U_BOOT_FORMAT_REDUNDANT, },
+ { .compatible = "u-boot,env-redundant-count", .data = (void *)U_BOOT_FORMAT_REDUNDANT, },
+ {},
+};
+
+static struct nvmem_layout u_boot_env_layout = {
+ .name = "u_boot_env",
+ .of_match_table = u_boot_env_of_match_table,
+ .add_cells = u_boot_add_cells,
+};
+
+static int __init u_boot_env_init(void)
+{
+ return nvmem_layout_register(&u_boot_env_layout);
+}
+subsys_initcall(u_boot_env_init);
diff --git a/drivers/nvmem/u-boot-env.c b/drivers/nvmem/u-boot-env.c
deleted file mode 100644
index 9b9abfb8f187..000000000000
--- a/drivers/nvmem/u-boot-env.c
+++ /dev/null
@@ -1,218 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Copyright (C) 2022 Rafał Miłecki <[email protected]>
- */
-
-#include <linux/crc32.h>
-#include <linux/mod_devicetable.h>
-#include <linux/module.h>
-#include <linux/mtd/mtd.h>
-#include <linux/nvmem-consumer.h>
-#include <linux/nvmem-provider.h>
-#include <linux/of_device.h>
-#include <linux/platform_device.h>
-#include <linux/slab.h>
-
-enum u_boot_env_format {
- U_BOOT_FORMAT_SINGLE,
- U_BOOT_FORMAT_REDUNDANT,
-};
-
-struct u_boot_env {
- struct device *dev;
- enum u_boot_env_format format;
-
- struct mtd_info *mtd;
-
- /* Cells */
- struct nvmem_cell_info *cells;
- int ncells;
-};
-
-struct u_boot_env_image_single {
- __le32 crc32;
- uint8_t data[];
-} __packed;
-
-struct u_boot_env_image_redundant {
- __le32 crc32;
- u8 mark;
- uint8_t data[];
-} __packed;
-
-static int u_boot_env_read(void *context, unsigned int offset, void *val,
- size_t bytes)
-{
- struct u_boot_env *priv = context;
- struct device *dev = priv->dev;
- size_t bytes_read;
- int err;
-
- err = mtd_read(priv->mtd, offset, bytes, &bytes_read, val);
- if (err && !mtd_is_bitflip(err)) {
- dev_err(dev, "Failed to read from mtd: %d\n", err);
- return err;
- }
-
- if (bytes_read != bytes) {
- dev_err(dev, "Failed to read %zu bytes\n", bytes);
- return -EIO;
- }
-
- return 0;
-}
-
-static int u_boot_env_add_cells(struct u_boot_env *priv, uint8_t *buf,
- size_t data_offset, size_t data_len)
-{
- struct device *dev = priv->dev;
- char *data = buf + data_offset;
- char *var, *value, *eq;
- int idx;
-
- priv->ncells = 0;
- for (var = data; var < data + data_len && *var; var += strlen(var) + 1)
- priv->ncells++;
-
- priv->cells = devm_kcalloc(dev, priv->ncells, sizeof(*priv->cells), GFP_KERNEL);
- if (!priv->cells)
- return -ENOMEM;
-
- for (var = data, idx = 0;
- var < data + data_len && *var;
- var = value + strlen(value) + 1, idx++) {
- eq = strchr(var, '=');
- if (!eq)
- break;
- *eq = '\0';
- value = eq + 1;
-
- priv->cells[idx].name = devm_kstrdup(dev, var, GFP_KERNEL);
- if (!priv->cells[idx].name)
- return -ENOMEM;
- priv->cells[idx].offset = data_offset + value - data;
- priv->cells[idx].bytes = strlen(value);
- }
-
- if (WARN_ON(idx != priv->ncells))
- priv->ncells = idx;
-
- return 0;
-}
-
-static int u_boot_env_parse(struct u_boot_env *priv)
-{
- struct device *dev = priv->dev;
- size_t crc32_data_offset;
- size_t crc32_data_len;
- size_t crc32_offset;
- size_t data_offset;
- size_t data_len;
- uint32_t crc32;
- uint32_t calc;
- size_t bytes;
- uint8_t *buf;
- int err;
-
- buf = kcalloc(1, priv->mtd->size, GFP_KERNEL);
- if (!buf) {
- err = -ENOMEM;
- goto err_out;
- }
-
- err = mtd_read(priv->mtd, 0, priv->mtd->size, &bytes, buf);
- if ((err && !mtd_is_bitflip(err)) || bytes != priv->mtd->size) {
- dev_err(dev, "Failed to read from mtd: %d\n", err);
- goto err_kfree;
- }
-
- switch (priv->format) {
- case U_BOOT_FORMAT_SINGLE:
- crc32_offset = offsetof(struct u_boot_env_image_single, crc32);
- crc32_data_offset = offsetof(struct u_boot_env_image_single, data);
- data_offset = offsetof(struct u_boot_env_image_single, data);
- break;
- case U_BOOT_FORMAT_REDUNDANT:
- crc32_offset = offsetof(struct u_boot_env_image_redundant, crc32);
- crc32_data_offset = offsetof(struct u_boot_env_image_redundant, mark);
- data_offset = offsetof(struct u_boot_env_image_redundant, data);
- break;
- }
- crc32 = le32_to_cpu(*(uint32_t *)(buf + crc32_offset));
- crc32_data_len = priv->mtd->size - crc32_data_offset;
- data_len = priv->mtd->size - data_offset;
-
- calc = crc32(~0, buf + crc32_data_offset, crc32_data_len) ^ ~0L;
- if (calc != crc32) {
- dev_err(dev, "Invalid calculated CRC32: 0x%08x (expected: 0x%08x)\n", calc, crc32);
- err = -EINVAL;
- goto err_kfree;
- }
-
- buf[priv->mtd->size - 1] = '\0';
- err = u_boot_env_add_cells(priv, buf, data_offset, data_len);
- if (err)
- dev_err(dev, "Failed to add cells: %d\n", err);
-
-err_kfree:
- kfree(buf);
-err_out:
- return err;
-}
-
-static int u_boot_env_probe(struct platform_device *pdev)
-{
- struct nvmem_config config = {
- .name = "u-boot-env",
- .reg_read = u_boot_env_read,
- };
- struct device *dev = &pdev->dev;
- struct device_node *np = dev->of_node;
- struct u_boot_env *priv;
- int err;
-
- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
- priv->dev = dev;
-
- priv->format = (uintptr_t)of_device_get_match_data(dev);
-
- priv->mtd = of_get_mtd_device_by_node(np);
- if (IS_ERR(priv->mtd)) {
- dev_err_probe(dev, PTR_ERR(priv->mtd), "Failed to get %pOF MTD\n", np);
- return PTR_ERR(priv->mtd);
- }
-
- err = u_boot_env_parse(priv);
- if (err)
- return err;
-
- config.dev = dev;
- config.cells = priv->cells;
- config.ncells = priv->ncells;
- config.priv = priv;
- config.size = priv->mtd->size;
-
- return PTR_ERR_OR_ZERO(devm_nvmem_register(dev, &config));
-}
-
-static const struct of_device_id u_boot_env_of_match_table[] = {
- { .compatible = "u-boot,env", .data = (void *)U_BOOT_FORMAT_SINGLE, },
- { .compatible = "u-boot,env-redundant-bool", .data = (void *)U_BOOT_FORMAT_REDUNDANT, },
- { .compatible = "u-boot,env-redundant-count", .data = (void *)U_BOOT_FORMAT_REDUNDANT, },
- {},
-};
-
-static struct platform_driver u_boot_env_driver = {
- .probe = u_boot_env_probe,
- .driver = {
- .name = "u_boot_env",
- .of_match_table = u_boot_env_of_match_table,
- },
-};
-module_platform_driver(u_boot_env_driver);
-
-MODULE_AUTHOR("Rafał Miłecki");
-MODULE_LICENSE("GPL");
-MODULE_DEVICE_TABLE(of, u_boot_env_of_match_table);
--
2.30.2

2022-09-01 23:17:57

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 10/20] nvmem: core: allow to modify a cell before adding it

Provide a way to modify a cell before it will get added. This is useful
to attach a custom post processing hook via a layout.

Signed-off-by: Michael Walle <[email protected]>
---
changes since v1:
- new patch

drivers/nvmem/core.c | 4 ++++
include/linux/nvmem-provider.h | 5 +++++
2 files changed, 9 insertions(+)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 02d0a7099d9a..3b65512762b9 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -693,6 +693,7 @@ static int nvmem_validate_keepouts(struct nvmem_device *nvmem)

static int nvmem_add_cells_from_of(struct nvmem_device *nvmem)
{
+ struct nvmem_layout *layout = nvmem->layout;
struct device *dev = &nvmem->dev;
struct nvmem_cell_entry *cell;
struct device_node *child;
@@ -729,6 +730,9 @@ static int nvmem_add_cells_from_of(struct nvmem_device *nvmem)

info.np = of_node_get(child);

+ if (layout && layout->fixup_cell_info)
+ layout->fixup_cell_info(nvmem, layout, &info);
+
ret = nvmem_add_one_cell(nvmem, &info);
kfree(info.name);
if (ret) {
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index cb488b338d6f..9cb340764b89 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -157,6 +157,8 @@ struct nvmem_cell_table {
* @add_cells: Will be called if a nvmem device is found which
* has this layout. The function will add layout
* specific cells with nvmem_add_one_cell().
+ * @fixup_cell_info: Will be called before a cell is added. Can be
+ * used to modify the nvmem_cell_info.
* @owner: Pointer to struct module.
* @node: List node.
*
@@ -170,6 +172,9 @@ struct nvmem_layout {
const struct of_device_id *of_match_table;
int (*add_cells)(struct device *dev, struct nvmem_device *nvmem,
struct nvmem_layout *layout);
+ void (*fixup_cell_info)(struct nvmem_device *nvmem,
+ struct nvmem_layout *layout,
+ struct nvmem_cell_info *cell);

/* private */
struct module *owner;
--
2.30.2

2022-09-01 23:18:35

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 17/20] nvmem: core: export nvmem device size

Export the size of the nvmem device. NVMEM layout drivers might need it
and might not have access to the device which is registering the NVMEM
device.

Signed-off-by: Michael Walle <[email protected]>
---
changes since v1:
- none

drivers/nvmem/core.c | 13 +++++++++++++
include/linux/nvmem-consumer.h | 6 ++++++
2 files changed, 19 insertions(+)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 6910796937f9..81fca32a7418 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -2045,6 +2045,19 @@ const char *nvmem_dev_name(struct nvmem_device *nvmem)
}
EXPORT_SYMBOL_GPL(nvmem_dev_name);

+/**
+ * nvmem_device_size() - Get the size of a given nvmem device.
+ *
+ * @nvmem: nvmem device.
+ *
+ * Return: size of the nvmem device.
+ */
+size_t nvmem_device_size(struct nvmem_device *nvmem)
+{
+ return nvmem->size;
+}
+EXPORT_SYMBOL_GPL(nvmem_device_size);
+
static int __init nvmem_init(void)
{
return bus_register(&nvmem_bus_type);
diff --git a/include/linux/nvmem-consumer.h b/include/linux/nvmem-consumer.h
index 1f62f7ba71ca..6607f9a1d6dc 100644
--- a/include/linux/nvmem-consumer.h
+++ b/include/linux/nvmem-consumer.h
@@ -77,6 +77,7 @@ ssize_t nvmem_device_cell_read(struct nvmem_device *nvmem,
struct nvmem_cell_info *info, void *buf);
int nvmem_device_cell_write(struct nvmem_device *nvmem,
struct nvmem_cell_info *info, void *buf);
+size_t nvmem_device_size(struct nvmem_device *nvmem);

const char *nvmem_dev_name(struct nvmem_device *nvmem);

@@ -206,6 +207,11 @@ static inline int nvmem_device_write(struct nvmem_device *nvmem,
return -EOPNOTSUPP;
}

+static inline size_t nvmem_device_size(struct nvmem_device *nvmem)
+{
+ return 0;
+}
+
static inline const char *nvmem_dev_name(struct nvmem_device *nvmem)
{
return NULL;
--
2.30.2

2022-09-02 00:04:02

by Randy Dunlap

[permalink] [raw]
Subject: Re: [PATCH v2 01/20] net: add helper eth_addr_add()



On 9/1/22 15:18, Michael Walle wrote:
> Add a helper to add an offset to a ethernet address. This comes in handy
> if you have a base ethernet address for multiple interfaces.
>
> Signed-off-by: Michael Walle <[email protected]>
> Reviewed-by: Andrew Lunn <[email protected]>
> Acked-by: Jakub Kicinski <[email protected]>
> ---
> changes since v1:
> - none
>
> include/linux/etherdevice.h | 14 ++++++++++++++
> 1 file changed, 14 insertions(+)
>
> diff --git a/include/linux/etherdevice.h b/include/linux/etherdevice.h
> index a541f0c4f146..f144cadbe99d 100644
> --- a/include/linux/etherdevice.h
> +++ b/include/linux/etherdevice.h
> @@ -507,6 +507,20 @@ static inline void eth_addr_inc(u8 *addr)
> u64_to_ether_addr(u, addr);
> }
>
> +/**
> + * eth_addr_add() - Add (or subtract) and offset to/from the given MAC address.

an offset
?

> + *
> + * @offset: Offset to add.
> + * @addr: Pointer to a six-byte array containing Ethernet address to increment.
> + */
> +static inline void eth_addr_add(u8 *addr, long offset)
> +{
> + u64 u = ether_addr_to_u64(addr);
> +
> + u += offset;
> + u64_to_ether_addr(u, addr);
> +}
> +
> /**
> * is_etherdev_addr - Tell if given Ethernet address belongs to the device.
> * @dev: Pointer to a device structure

--
~Randy

2022-09-02 07:40:38

by Michael Walle

[permalink] [raw]
Subject: Re: [PATCH v2 01/20] net: add helper eth_addr_add()

Am 2022-09-02 01:22, schrieb Randy Dunlap:

>> +/**
>> + * eth_addr_add() - Add (or subtract) and offset to/from the given
>> MAC address.
>
> an offset
> ?

yes thanks, I'll change that in the next version.

-michael

2022-09-07 12:52:55

by Michael Walle

[permalink] [raw]
Subject: Re: [PATCH v2 11/20] nvmem: imx-ocotp: replace global post processing with layouts

Am 2022-09-02 00:18, schrieb Michael Walle:
> In preparation of retiring the global post processing hook change this
> driver to use layouts. The layout will be supplied during registration
> and will be used to add the post processing hook to all added cells.
>
> Signed-off-by: Michael Walle <[email protected]>
> ---
> Complile-time tested only! Please test.

Tested-by: Michael Walle <[email protected]> # on kontron-pitx-imx8m

-michael

2022-09-08 12:24:12

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v2 15/20] dt-bindings: nvmem: add YAML schema for the sl28 vpd layout

On 02/09/2022 00:18, Michael Walle wrote:
> Add a schema for the NVMEM layout on Kontron's sl28 boards.
>
> Signed-off-by: Michael Walle <[email protected]>
> ---
> changes since v1:
> - add custom select
> - add description
> - add "additionalProperties: false", I wasn't sure if all the
> subnodes needs it. I'd say yes, but the brcm,nvram binding
> doesn't have them neither.
>
> .../nvmem/layouts/kontron,sl28-vpd.yaml | 67 +++++++++++++++++++
> 1 file changed, 67 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml
>
> diff --git a/Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml b/Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml
> new file mode 100644
> index 000000000000..0c180f29e880
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml
> @@ -0,0 +1,67 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/nvmem/layouts/kontron,sl28-vpd.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: NVMEM layout of the Kontron SMARC-sAL28 vital product data
> +
> +maintainers:
> + - Michael Walle <[email protected]>
> +
> +description:
> + The vital product data (VPD) of the sl28 boards contains a serial
> + number and a base MAC address. The actual MAC addresses for the
> + on-board ethernet devices are derived from this base MAC address by
> + adding an offset.
> +
> +# We need a select here so we don't match all nodes with 'user-otp'
> +select:
> + properties:
> + compatible:
> + contains:
> + const: kontron,sl28-vpd
> + required:
> + - compatible
> +
> +properties:
> + compatible:
> + items:
> + - const: kontron,sl28-vpd
> + - const: user-otp
> +
> + serial-number:

I will leave it for Rob to ack...

Best regards,
Krzysztof

2022-09-08 12:30:31

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v2 14/20] dt-bindings: mtd: relax the nvmem compatible string

On 02/09/2022 00:18, Michael Walle wrote:
> The "user-otp" and "factory-otp" compatible string just depicts a
> generic NVMEM device. But an actual device tree node might as well
> contain a more specific compatible string. Make it possible to add
> more specific binding elsewhere and just match part of the compatibles
> here.
>
> Signed-off-by: Michael Walle <[email protected]>
> ---
> changes since v1:
> - fix typo in commit message


Reviewed-by: Krzysztof Kozlowski <[email protected]>


Best regards,
Krzysztof

2022-09-09 08:29:57

by Srinivas Kandagatla

[permalink] [raw]
Subject: Re: [PATCH v2 03/20] nvmem: core: add an index parameter to the cell



On 01/09/2022 23:18, Michael Walle wrote:
> + ret = of_parse_phandle_with_optional_args(np, "nvmem-cells",
> + "#nvmem-cell-cells",
> + index, &cell_spec);
where is the bindings for this new #nvmem-cell-cells property?


--srini

2022-09-09 08:56:47

by Srinivas Kandagatla

[permalink] [raw]
Subject: Re: [PATCH v2 06/20] nvmem: core: add nvmem_add_one_cell()



On 01/09/2022 23:18, Michael Walle wrote:
> Add a new function to add exactly one cell. This will be used by the
> nvmem layout drivers to add custom cells. In contrast to the
> nvmem_add_cells(), this has the advantage that we don't have to assemble
> a list of cells on runtime.
>
> Signed-off-by: Michael Walle <[email protected]>
> ---
> changes since v1:
> - none
>
> drivers/nvmem/core.c | 58 ++++++++++++++++++++--------------
> include/linux/nvmem-provider.h | 8 +++++
> 2 files changed, 42 insertions(+), 24 deletions(-)
>
> diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
> index be38e62fd190..3dfd149374a8 100644
> --- a/drivers/nvmem/core.c
> +++ b/drivers/nvmem/core.c
> @@ -501,6 +501,35 @@ static int nvmem_cell_info_to_nvmem_cell_entry(struct nvmem_device *nvmem,
> return 0;
> }
>
> +/**
> + * nvmem_add_one_cell() - Add one cell information to an nvmem device
> + *
> + * @nvmem: nvmem device to add cells to.
> + * @info: nvmem cell info to add to the device
> + *
> + * Return: 0 or negative error code on failure.
> + */
> +int nvmem_add_one_cell(struct nvmem_device *nvmem,
> + const struct nvmem_cell_info *info)
> +{
> + struct nvmem_cell_entry *cell;
> + int rval;
> +
> + cell = kzalloc(sizeof(*cell), GFP_KERNEL);
> + if (!cell)
> + return -ENOMEM;
> +
> + rval = nvmem_cell_info_to_nvmem_cell_entry(nvmem, info, cell);
> + if (rval) {
> + kfree(cell);
> + return rval;
> + }
> +
> + nvmem_cell_entry_add(cell);
> +
> + return 0;
> +}
> +

EXPORT_SYMBOL_GPL ???

> /**
> * nvmem_add_cells() - Add cell information to an nvmem device
> *
> @@ -514,34 +543,15 @@ static int nvmem_add_cells(struct nvmem_device *nvmem,
> const struct nvmem_cell_info *info,
> int ncells)
> {
> - struct nvmem_cell_entry **cells;
> - int i, rval = 0;
> -
> - cells = kcalloc(ncells, sizeof(*cells), GFP_KERNEL);
> - if (!cells)
> - return -ENOMEM;
> + int i, rval;
>
> for (i = 0; i < ncells; i++) {
> - cells[i] = kzalloc(sizeof(**cells), GFP_KERNEL);
> - if (!cells[i]) {
> - rval = -ENOMEM;
> - goto out;
> - }
> -
> - rval = nvmem_cell_info_to_nvmem_cell_entry(nvmem, &info[i], cells[i]);
> - if (rval) {
> - kfree(cells[i]);
> - goto out;
> - }
> -
> - nvmem_cell_entry_add(cells[i]);
> + rval = nvmem_add_one_cell(nvmem, &info[i]);
> + if (rval)
> + return rval;
> }
>
> -out:
> - /* remove tmp array */
> - kfree(cells);
> -
> - return rval;
> + return 0;
> }
>
> /**
> diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
> index 14a32a1bc249..385d29168008 100644
> --- a/include/linux/nvmem-provider.h
> +++ b/include/linux/nvmem-provider.h
> @@ -155,6 +155,9 @@ struct nvmem_device *devm_nvmem_register(struct device *dev,
> void nvmem_add_cell_table(struct nvmem_cell_table *table);
> void nvmem_del_cell_table(struct nvmem_cell_table *table);
>
> +int nvmem_add_one_cell(struct nvmem_device *nvmem,
> + const struct nvmem_cell_info *info);
> +
> #else
>
> static inline struct nvmem_device *nvmem_register(const struct nvmem_config *c)
> @@ -172,6 +175,11 @@ devm_nvmem_register(struct device *dev, const struct nvmem_config *c)
>
> static inline void nvmem_add_cell_table(struct nvmem_cell_table *table) {}
> static inline void nvmem_del_cell_table(struct nvmem_cell_table *table) {}
> +static inline int nvmem_add_one_cell(struct nvmem_device *nvmem,
> + const struct nvmem_cell_info *info)
> +{
> + return -EOPNOTSUPP;
> +}
>
> #endif /* CONFIG_NVMEM */
> #endif /* ifndef _LINUX_NVMEM_PROVIDER_H */

2022-09-09 09:22:37

by Michael Walle

[permalink] [raw]
Subject: Re: [PATCH v2 13/20] nvmem: core: drop priv pointer in post process callback

Am 2022-09-09 10:52, schrieb Srinivas Kandagatla:
> On 01/09/2022 23:18, Michael Walle wrote:
>> It doesn't make any more sense to have a opaque pointer set up by the
>> nvmem device. Usually, the layout isn't associated with a particular
>> nvmem device.
>>
> This is really not a good idea to remove the context pointer, as this
> is the only way for callback to get context which it can make use of.

In which case? As I mentioned it's the priv to the nvmem driver and all
the "normal" callbacks can do very little with it. If there will be a
future need, then there should be a proper opaque pointer associated
with the layout and not the nvmem driver.

-michael

> I would prefer this to be left as it is.
>
> --srini
>
>> Signed-off-by: Michael Walle <[email protected]>
>> ---
>> changes since v1:
>> - new patch
>>
>> drivers/nvmem/core.c | 4 ++--
>> drivers/nvmem/imx-ocotp.c | 4 ++--
>> include/linux/nvmem-provider.h | 5 +++--
>> 3 files changed, 7 insertions(+), 6 deletions(-)
>>
>> diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
>> index d31d3f0ab517..6910796937f9 100644
>> --- a/drivers/nvmem/core.c
>> +++ b/drivers/nvmem/core.c
>> @@ -1523,8 +1523,8 @@ static int __nvmem_cell_read(struct nvmem_device
>> *nvmem,
>> nvmem_shift_read_buffer_in_place(cell, buf);
>> if (cell->read_post_process) {
>> - rc = cell->read_post_process(nvmem->priv, id, index,
>> - cell->offset, buf, cell->bytes);
>> + rc = cell->read_post_process(id, index, cell->offset, buf,
>> + cell->bytes);
>> if (rc)
>> return rc;
>> }
>> diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c
>> index ac0edb6398f1..5e869d4a81c5 100644
>> --- a/drivers/nvmem/imx-ocotp.c
>> +++ b/drivers/nvmem/imx-ocotp.c
>> @@ -222,8 +222,8 @@ static int imx_ocotp_read(void *context, unsigned
>> int offset,
>> return ret;
>> }
>> -static int imx_ocotp_cell_pp(void *context, const char *id, int
>> index,
>> - unsigned int offset, void *data, size_t bytes)
>> +static int imx_ocotp_cell_pp(const char *id, int index, unsigned int
>> offset,
>> + void *data, size_t bytes)
>> {
>> u8 *buf = data;
>> int i;
>> diff --git a/include/linux/nvmem-provider.h
>> b/include/linux/nvmem-provider.h
>> index 9d22dc5a3fa5..46067a6a0395 100644
>> --- a/include/linux/nvmem-provider.h
>> +++ b/include/linux/nvmem-provider.h
>> @@ -19,8 +19,9 @@ typedef int (*nvmem_reg_read_t)(void *priv, unsigned
>> int offset,
>> typedef int (*nvmem_reg_write_t)(void *priv, unsigned int offset,
>> void *val, size_t bytes);
>> /* used for vendor specific post processing of cell data */
>> -typedef int (*nvmem_cell_post_process_t)(void *priv, const char *id,
>> int index,
>> - unsigned int offset, void *buf, size_t bytes);
>> +typedef int (*nvmem_cell_post_process_t)(const char *id, int index,
>> + unsigned int offset, void *buf,
>> + size_t bytes);
>> enum nvmem_type {
>> NVMEM_TYPE_UNKNOWN = 0,

2022-09-09 09:46:22

by Srinivas Kandagatla

[permalink] [raw]
Subject: Re: [PATCH v2 13/20] nvmem: core: drop priv pointer in post process callback



On 01/09/2022 23:18, Michael Walle wrote:
> It doesn't make any more sense to have a opaque pointer set up by the
> nvmem device. Usually, the layout isn't associated with a particular
> nvmem device.
>
This is really not a good idea to remove the context pointer, as this is
the only way for callback to get context which it can make use of.

I would prefer this to be left as it is.

--srini

> Signed-off-by: Michael Walle <[email protected]>
> ---
> changes since v1:
> - new patch
>
> drivers/nvmem/core.c | 4 ++--
> drivers/nvmem/imx-ocotp.c | 4 ++--
> include/linux/nvmem-provider.h | 5 +++--
> 3 files changed, 7 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
> index d31d3f0ab517..6910796937f9 100644
> --- a/drivers/nvmem/core.c
> +++ b/drivers/nvmem/core.c
> @@ -1523,8 +1523,8 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
> nvmem_shift_read_buffer_in_place(cell, buf);
>
> if (cell->read_post_process) {
> - rc = cell->read_post_process(nvmem->priv, id, index,
> - cell->offset, buf, cell->bytes);
> + rc = cell->read_post_process(id, index, cell->offset, buf,
> + cell->bytes);
> if (rc)
> return rc;
> }
> diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c
> index ac0edb6398f1..5e869d4a81c5 100644
> --- a/drivers/nvmem/imx-ocotp.c
> +++ b/drivers/nvmem/imx-ocotp.c
> @@ -222,8 +222,8 @@ static int imx_ocotp_read(void *context, unsigned int offset,
> return ret;
> }
>
> -static int imx_ocotp_cell_pp(void *context, const char *id, int index,
> - unsigned int offset, void *data, size_t bytes)
> +static int imx_ocotp_cell_pp(const char *id, int index, unsigned int offset,
> + void *data, size_t bytes)
> {
> u8 *buf = data;
> int i;
> diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
> index 9d22dc5a3fa5..46067a6a0395 100644
> --- a/include/linux/nvmem-provider.h
> +++ b/include/linux/nvmem-provider.h
> @@ -19,8 +19,9 @@ typedef int (*nvmem_reg_read_t)(void *priv, unsigned int offset,
> typedef int (*nvmem_reg_write_t)(void *priv, unsigned int offset,
> void *val, size_t bytes);
> /* used for vendor specific post processing of cell data */
> -typedef int (*nvmem_cell_post_process_t)(void *priv, const char *id, int index,
> - unsigned int offset, void *buf, size_t bytes);
> +typedef int (*nvmem_cell_post_process_t)(const char *id, int index,
> + unsigned int offset, void *buf,
> + size_t bytes);
>
> enum nvmem_type {
> NVMEM_TYPE_UNKNOWN = 0,

2022-09-09 09:46:43

by Srinivas Kandagatla

[permalink] [raw]
Subject: Re: [PATCH v2 13/20] nvmem: core: drop priv pointer in post process callback



On 09/09/2022 09:58, Michael Walle wrote:
> Am 2022-09-09 10:52, schrieb Srinivas Kandagatla:
>> On 01/09/2022 23:18, Michael Walle wrote:
>>> It doesn't make any more sense to have a opaque pointer set up by the
>>> nvmem device. Usually, the layout isn't associated with a particular
>>> nvmem device.
>>>
>> This is really not a good idea to remove the context pointer, as this
>> is the only way for callback to get context which it can make use of.
>
> In which case? As I mentioned it's the priv to the nvmem driver and all
> the "normal" callbacks can do very little with it. If there will be a
> future need, then there should be a proper opaque pointer associated
> with the layout and not the nvmem driver.

Yes, the opaque object here is the layout priv which I agree with, but
removing the context totally from the callback is not a good idea.

We should have some context to callbacks to be able to allow them to
deal with some private info.


--srini

>
> -michael
>
>> I would prefer this to be left as it is.
>>
>> --srini
>>
>>> Signed-off-by: Michael Walle <[email protected]>
>>> ---
>>> changes since v1:
>>>   - new patch
>>>
>>>   drivers/nvmem/core.c           | 4 ++--
>>>   drivers/nvmem/imx-ocotp.c      | 4 ++--
>>>   include/linux/nvmem-provider.h | 5 +++--
>>>   3 files changed, 7 insertions(+), 6 deletions(-)
>>>
>>> diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
>>> index d31d3f0ab517..6910796937f9 100644
>>> --- a/drivers/nvmem/core.c
>>> +++ b/drivers/nvmem/core.c
>>> @@ -1523,8 +1523,8 @@ static int __nvmem_cell_read(struct
>>> nvmem_device *nvmem,
>>>           nvmem_shift_read_buffer_in_place(cell, buf);
>>>         if (cell->read_post_process) {
>>> -        rc = cell->read_post_process(nvmem->priv, id, index,
>>> -                         cell->offset, buf, cell->bytes);
>>> +        rc = cell->read_post_process(id, index, cell->offset, buf,
>>> +                         cell->bytes);
>>>           if (rc)
>>>               return rc;
>>>       }
>>> diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c
>>> index ac0edb6398f1..5e869d4a81c5 100644
>>> --- a/drivers/nvmem/imx-ocotp.c
>>> +++ b/drivers/nvmem/imx-ocotp.c
>>> @@ -222,8 +222,8 @@ static int imx_ocotp_read(void *context, unsigned
>>> int offset,
>>>       return ret;
>>>   }
>>>   -static int imx_ocotp_cell_pp(void *context, const char *id, int
>>> index,
>>> -                 unsigned int offset, void *data, size_t bytes)
>>> +static int imx_ocotp_cell_pp(const char *id, int index, unsigned int
>>> offset,
>>> +                 void *data, size_t bytes)
>>>   {
>>>       u8 *buf = data;
>>>       int i;
>>> diff --git a/include/linux/nvmem-provider.h
>>> b/include/linux/nvmem-provider.h
>>> index 9d22dc5a3fa5..46067a6a0395 100644
>>> --- a/include/linux/nvmem-provider.h
>>> +++ b/include/linux/nvmem-provider.h
>>> @@ -19,8 +19,9 @@ typedef int (*nvmem_reg_read_t)(void *priv,
>>> unsigned int offset,
>>>   typedef int (*nvmem_reg_write_t)(void *priv, unsigned int offset,
>>>                    void *val, size_t bytes);
>>>   /* used for vendor specific post processing of cell data */
>>> -typedef int (*nvmem_cell_post_process_t)(void *priv, const char *id,
>>> int index,
>>> -                     unsigned int offset, void *buf, size_t bytes);
>>> +typedef int (*nvmem_cell_post_process_t)(const char *id, int index,
>>> +                     unsigned int offset, void *buf,
>>> +                     size_t bytes);
>>>     enum nvmem_type {
>>>       NVMEM_TYPE_UNKNOWN = 0,

2022-09-09 10:16:52

by Michael Walle

[permalink] [raw]
Subject: Re: [PATCH v2 13/20] nvmem: core: drop priv pointer in post process callback

Am 2022-09-09 11:08, schrieb Srinivas Kandagatla:
> On 09/09/2022 09:58, Michael Walle wrote:
>> Am 2022-09-09 10:52, schrieb Srinivas Kandagatla:
>>> On 01/09/2022 23:18, Michael Walle wrote:
>>>> It doesn't make any more sense to have a opaque pointer set up by
>>>> the
>>>> nvmem device. Usually, the layout isn't associated with a particular
>>>> nvmem device.
>>>>
>>> This is really not a good idea to remove the context pointer, as this
>>> is the only way for callback to get context which it can make use of.
>>
>> In which case? As I mentioned it's the priv to the nvmem driver and
>> all
>> the "normal" callbacks can do very little with it. If there will be a
>> future need, then there should be a proper opaque pointer associated
>> with the layout and not the nvmem driver.
>
> Yes, the opaque object here is the layout priv which I agree with, but
> removing the context totally from the callback is not a good idea.
>
> We should have some context to callbacks to be able to allow them to
> deal with some private info.

I agree, but my thinking was this: as the old priv pointer doesn't
make any sense and no one is using it at the moment for now, we can
remove it it. If it's needed again it can easily added together with
a user.

That being said, I could leave the pointer argument and just pass NULL,
so if someone (re)adds that, we don't have to modify all the callbacks.
Because we don't have any user for now, I could only speculate who
should
or could set that pointer.

-michael

2022-09-12 19:31:29

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH v2 15/20] dt-bindings: nvmem: add YAML schema for the sl28 vpd layout

On Fri, Sep 02, 2022 at 12:18:52AM +0200, Michael Walle wrote:
> Add a schema for the NVMEM layout on Kontron's sl28 boards.
>
> Signed-off-by: Michael Walle <[email protected]>
> ---
> changes since v1:
> - add custom select
> - add description
> - add "additionalProperties: false", I wasn't sure if all the
> subnodes needs it. I'd say yes, but the brcm,nvram binding
> doesn't have them neither.
>
> .../nvmem/layouts/kontron,sl28-vpd.yaml | 67 +++++++++++++++++++
> 1 file changed, 67 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml
>
> diff --git a/Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml b/Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml
> new file mode 100644
> index 000000000000..0c180f29e880
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml
> @@ -0,0 +1,67 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/nvmem/layouts/kontron,sl28-vpd.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: NVMEM layout of the Kontron SMARC-sAL28 vital product data
> +
> +maintainers:
> + - Michael Walle <[email protected]>
> +
> +description:
> + The vital product data (VPD) of the sl28 boards contains a serial
> + number and a base MAC address. The actual MAC addresses for the
> + on-board ethernet devices are derived from this base MAC address by
> + adding an offset.
> +
> +# We need a select here so we don't match all nodes with 'user-otp'
> +select:
> + properties:
> + compatible:
> + contains:
> + const: kontron,sl28-vpd
> + required:
> + - compatible
> +
> +properties:
> + compatible:
> + items:
> + - const: kontron,sl28-vpd
> + - const: user-otp
> +
> + serial-number:
> + type: object
> + description: The board's serial number
> +
> + base-mac-address:
> + type: object
> + description:
> + Base MAC address for all on-module network interfaces. The first
> + argument of the phandle will be treated as an offset.
> +
> + properties:
> + "#nvmem-cell-cells":

You can't just add a new #.*-cells buried in a device binding. I'm fine
with the concept though having more than 1 user would be nice.

Any case that doesn't match foos->#foo-cells or has a default # of
cells if missing (as this does) has to be added to dtschema to decode it
properly. It won't really matter until there's a user with 2 or more
entries. I'm happy to do update the dtschema part, but I'd prefer to see
the schema in dtschema rather than the kernel.

Rob

2022-09-13 15:15:24

by Michael Walle

[permalink] [raw]
Subject: Re: [PATCH v2 15/20] dt-bindings: nvmem: add YAML schema for the sl28 vpd layout

Am 2022-09-12 21:20, schrieb Rob Herring:

>> + base-mac-address:
>> + type: object
>> + description:
>> + Base MAC address for all on-module network interfaces. The
>> first
>> + argument of the phandle will be treated as an offset.
>> +
>> + properties:
>> + "#nvmem-cell-cells":
>
> You can't just add a new #.*-cells buried in a device binding. I'm fine
> with the concept though having more than 1 user would be nice.

I was under the impression the tooling will handle it, but as you
pointed out below, this isn't the case for a missing default. The
statement above should only be to validate that there is one
additional argument if the base-mac-address node is used in a
phandle.

> Any case that doesn't match foos->#foo-cells or has a default # of
> cells if missing (as this does) has to be added to dtschema to decode
> it
> properly. It won't really matter until there's a user with 2 or more
> entries. I'm happy to do update the dtschema part, but I'd prefer to
> see
> the schema in dtschema rather than the kernel.

Ok, but I'm not sure I understand you correctly here. You will
update the dtschema tooling (I guess it's about fixup_phandles() in
dtb.py) and which schema should be in dtschema? nvmem.yaml
and/or nvmem-consumer.yaml? The entire schema or only a
subset of it?

-michael

2022-09-21 10:41:32

by Miquel Raynal

[permalink] [raw]
Subject: Re: [PATCH v2 00/20] nvmem: core: introduce NVMEM layouts

Hi Michael, Srinivas,

+ Thomas and Robert

[email protected] wrote on Fri, 2 Sep 2022 00:18:37 +0200:

> This is now the third attempt to fetch the MAC addresses from the VPD
> for the Kontron sl28 boards. Previous discussions can be found here:
> https://lore.kernel.org/lkml/[email protected]/
>
>
> NVMEM cells are typically added by board code or by the devicetree. But
> as the cells get more complex, there is (valid) push back from the
> devicetree maintainers to not put that handling in the devicetree.
>
> Therefore, introduce NVMEM layouts. They operate on the NVMEM device and
> can add cells during runtime. That way it is possible to add more complex
> cells than it is possible right now with the offset/length/bits
> description in the device tree. For example, you can have post processing
> for individual cells (think of endian swapping, or ethernet offset
> handling).
>
> The imx-ocotp driver is the only user of the global post processing hook,
> convert it to nvmem layouts and drop the global post pocessing hook. Please
> note, that this change is only compile-time tested.

These layouts are an excellent idea. I actually have a new use case for
them. In modern Ethernet switches which follow the ONIE standard [1]
there is an nvmem device which contains a standardized
type-length-value array with many information about manufacturing and
mac addresses. There is no "static" pattern there and anyway so many
possible entries that it would be very tedious to list all of them in
the bindings, as each manufacturer chooses what it want to export on
each of its devices (although reading the data sequentially and
extracting the cells is rather straightforward).

Moreover, the specification [1] does not define any storage device
type, so it can be eg. an MTD device or an EEPROM. Having an
nvmem device provider separated from the nvmem cells provider makes
complete sense, the "layout" drivers idea proposed by Michael seem to be
a perfect fit.

Srinivas, can you give us an update on what you think about this
series (not a commitment, just how you feel it overall)?

Michael, is there a v3 in preparation? I'll try to write something on
top of your v2 otherwise.

> You can also have cells which have no static offset, like the
> ones in an u-boot environment. The last patches will convert the current
> u-boot environment driver to a NVMEM layout and lifting the restriction
> that it only works with mtd devices. But as it will change the required
> compatible strings, it is marked as RFC for now. It also needs to have
> its device tree schema update which is left out here. These two patches
> are not expected to be applied, but rather to show another example of
> how to use the layouts.

Actually I think these two matches make complete sense, right now one
can only use the u-boot-env cells if the environment is stored in an
mtd device, of course this covers many cases but not all of them and it
would be really nice to have this first layout example merged, not only
on the mailing list.

> For now, the layouts are selected by a specific compatible string in a
> device tree. E.g. the VPD on the kontron sl28 do (within a SPI flash node):
> compatible = "kontron,sl28-vpd", "user-otp";
> or if you'd use the u-boot environment (within an MTD patition):
> compatible = "u-boot,env", "nvmem";
>
> The "user-otp" (or "nvmem") will lead to a NVMEM device, the
> "kontron,sl28-vpd" (or "u-boot,env") will then apply the specific layout
> on top of the NVMEM device.

Thanks,
Miquèl

2022-09-21 10:53:17

by Miquel Raynal

[permalink] [raw]
Subject: Re: [RFC PATCH v2 19/20] nvmem: layouts: rewrite the u-boot-env driver as a NVMEM layout

Hi Michael,

[email protected] wrote on Fri, 2 Sep 2022 00:18:56 +0200:

> Instead of hardcoding the underlying access method mtd_read() and
> duplicating all the error handling, rewrite the driver as a nvmem
> layout which just uses nvmem_device_read() and thus works with any
> NVMEM device.
>
> But because this is now not a device anymore, the compatible string
> will have to be changed so the device will still be probed:
> compatible = "u-boot,env";
> to
> compatible = "u-boot,env", "nvmem-cells";
>
> "nvmem-cells" will tell the mtd layer to register a nvmem_device().
> "u-boot,env" will tell the NVMEM that it should apply the u-boot
> environment layout to the NVMEM device.
>
> Signed-off-by: Michael Walle <[email protected]>
> ---
> changes since v1:
> - none
>
> drivers/nvmem/layouts/Kconfig | 8 ++
> drivers/nvmem/layouts/Makefile | 1 +
> drivers/nvmem/layouts/u-boot-env.c | 144 +++++++++++++++++++
> drivers/nvmem/u-boot-env.c | 218 -----------------------------

Nit: IIRC there is a MAINTAINERS entry to update as well.

Thanks,
Miquèl

2022-09-22 10:04:41

by Miquel Raynal

[permalink] [raw]
Subject: Re: [PATCH v2 15/20] dt-bindings: nvmem: add YAML schema for the sl28 vpd layout

Hello Rob & Krzysztof,

[email protected] wrote on Tue, 13 Sep 2022 16:21:24 +0200:

> Am 2022-09-12 21:20, schrieb Rob Herring:
>
> >> + base-mac-address:
> >> + type: object
> >> + description:
> >> + Base MAC address for all on-module network interfaces. The >> first
> >> + argument of the phandle will be treated as an offset.
> >> +
> >> + properties:
> >> + "#nvmem-cell-cells":
> >
> > You can't just add a new #.*-cells buried in a device binding. I'm fine
> > with the concept though having more than 1 user would be nice.
>
> I was under the impression the tooling will handle it, but as you
> pointed out below, this isn't the case for a missing default. The
> statement above should only be to validate that there is one
> additional argument if the base-mac-address node is used in a
> phandle.
>
> > Any case that doesn't match foos->#foo-cells or has a default # of
> > cells if missing (as this does) has to be added to dtschema to decode > it
> > properly. It won't really matter until there's a user with 2 or more
> > entries. I'm happy to do update the dtschema part, but I'd prefer to > see
> > the schema in dtschema rather than the kernel.
>
> Ok, but I'm not sure I understand you correctly here. You will
> update the dtschema tooling (I guess it's about fixup_phandles() in
> dtb.py) and which schema should be in dtschema? nvmem.yaml
> and/or nvmem-consumer.yaml? The entire schema or only a
> subset of it?

I currently see this as the main "blocking point", although Rob told he
was happy with the overall idea, so let's try to move forward together.

We discussed on IRC with Michael, I guess what's remaining is:
- Michael: Move #nvmem-cell-cells to nvmem.yaml in the core dtschema.
- Rob/Krzysztof: Add the necessary tooling to use this new
property and enforce the right # of cells cells (may be added later
as anyway for now we only have single consumer cases).

Is this what you meant?

Thanks,
Miquèl

2022-09-22 22:14:14

by Srinivas Kandagatla

[permalink] [raw]
Subject: Re: [PATCH v2 00/20] nvmem: core: introduce NVMEM layouts



On 21/09/2022 10:58, Miquel Raynal wrote:
>
> Srinivas, can you give us an update on what you think about this
> series (not a commitment, just how you feel it overall)?
>
Overall this is going in right direction, there are few bindings related
comments once those are sorted out it should be good to go.

From NVMEM side am happy with this feature, which has been a long
pending one.

We have few discussions on ONIE standard before, layouts would fit in
nicely.


--srini

> Michael, is there a v3 in preparation? I'll try to write something on
> top of your v2 otherwise.

2022-09-23 09:09:49

by Miquel Raynal

[permalink] [raw]
Subject: Re: [PATCH v2 00/20] nvmem: core: introduce NVMEM layouts

Hi Srinivas,

Thanks for the quick feedback.

[email protected] wrote on Thu, 22 Sep 2022 22:22:17 +0100:

> On 21/09/2022 10:58, Miquel Raynal wrote:
> >
> > Srinivas, can you give us an update on what you think about this
> > series (not a commitment, just how you feel it overall)?
> >
> Overall this is going in right direction, there are few bindings related comments once those are sorted out it should be good to go.

Ok, let's tackle those.

> From NVMEM side am happy with this feature, which has been a long pending one.
>
> We have few discussions on ONIE standard before, layouts would fit in nicely.

I agree they would.

Thanks,
Miquèl

2022-09-23 16:17:18

by Miquel Raynal

[permalink] [raw]
Subject: Re: [PATCH v2 00/20] nvmem: core: introduce NVMEM layouts

Hi Michael,

I have a few additional questions regarding the bindings.

[email protected] wrote on Fri, 2 Sep 2022 00:18:37 +0200:

> This is now the third attempt to fetch the MAC addresses from the VPD
> for the Kontron sl28 boards. Previous discussions can be found here:
> https://lore.kernel.org/lkml/[email protected]/
>
>
> NVMEM cells are typically added by board code or by the devicetree. But
> as the cells get more complex, there is (valid) push back from the
> devicetree maintainers to not put that handling in the devicetree.
>
> Therefore, introduce NVMEM layouts. They operate on the NVMEM device and
> can add cells during runtime. That way it is possible to add more complex
> cells than it is possible right now with the offset/length/bits
> description in the device tree. For example, you can have post processing
> for individual cells (think of endian swapping, or ethernet offset
> handling).
>
> The imx-ocotp driver is the only user of the global post processing hook,
> convert it to nvmem layouts and drop the global post pocessing hook. Please
> note, that this change is only compile-time tested.
>
> You can also have cells which have no static offset, like the
> ones in an u-boot environment. The last patches will convert the current
> u-boot environment driver to a NVMEM layout and lifting the restriction
> that it only works with mtd devices. But as it will change the required
> compatible strings, it is marked as RFC for now. It also needs to have
> its device tree schema update which is left out here. These two patches
> are not expected to be applied, but rather to show another example of
> how to use the layouts.
>
> For now, the layouts are selected by a specific compatible string in a
> device tree. E.g. the VPD on the kontron sl28 do (within a SPI flash node):
> compatible = "kontron,sl28-vpd", "user-otp";
> or if you'd use the u-boot environment (within an MTD patition):
> compatible = "u-boot,env", "nvmem";
>
> The "user-otp" (or "nvmem") will lead to a NVMEM device, the
> "kontron,sl28-vpd" (or "u-boot,env") will then apply the specific layout
> on top of the NVMEM device.

So if I understand correctly, there should be:
- one DT node defining the storage medium eeprom/mtd/whatever,
- another DT node defining the nvmem device with two compatibles, the
"nvmem" (or "user-otp") and the layout.
Is this correct? Actually I was a bit surprised because generally
speaking, DT maintainers (rightfully) do not want to describe how we
use devices, the nvmem abstraction looks like a Linux thing when on top
of mtd devices for instance, so I just wanted to confirm this point.

Then, as we have an nvmem device described in the DT, why not just
pointing at the nvmem device from the cell consumer, rather than still
having the need to define all the cells that the nvmem device will
produce in the DT?

Maybe an example to show what I mean. Here is the current way:

nvmem_provider: nvmem-provider {
properties;

mycell: my_cell {
[properties;]
};
};

And we point to a cell with:

nvmem-cells = <&mycell>;

But, as for the tlv tables, there are many cells that will be produced,
and the driver may anyway just ask for the cell name (eg. performing a
lookup of the "mac-address" cell name), so why bothering to describe all
the cells in the DT, like:

nvmem-cells-providers = <&nvmem_provider>;

What do you think?

Maybe for the mac addresses this is a bit limiting as, in practice, we
often have base mac addresses available and using:

nvmem-cells = <&mycell INDEX>;

allows to dynamically create many different mac addresses, but I wonder
if the approach would be interesting for other cell types. Just an open
question.

Thanks,
Miquèl

2022-09-23 17:52:09

by Michael Walle

[permalink] [raw]
Subject: Re: [PATCH v2 00/20] nvmem: core: introduce NVMEM layouts

Hi,

Am 2022-09-23 17:47, schrieb Miquel Raynal:
> I have a few additional questions regarding the bindings.
>
> [email protected] wrote on Fri, 2 Sep 2022 00:18:37 +0200:
>
>> This is now the third attempt to fetch the MAC addresses from the VPD
>> for the Kontron sl28 boards. Previous discussions can be found here:
>> https://lore.kernel.org/lkml/[email protected]/
>>
>>
>> NVMEM cells are typically added by board code or by the devicetree.
>> But
>> as the cells get more complex, there is (valid) push back from the
>> devicetree maintainers to not put that handling in the devicetree.
>>
>> Therefore, introduce NVMEM layouts. They operate on the NVMEM device
>> and
>> can add cells during runtime. That way it is possible to add more
>> complex
>> cells than it is possible right now with the offset/length/bits
>> description in the device tree. For example, you can have post
>> processing
>> for individual cells (think of endian swapping, or ethernet offset
>> handling).
>>
>> The imx-ocotp driver is the only user of the global post processing
>> hook,
>> convert it to nvmem layouts and drop the global post pocessing hook.
>> Please
>> note, that this change is only compile-time tested.
>>
>> You can also have cells which have no static offset, like the
>> ones in an u-boot environment. The last patches will convert the
>> current
>> u-boot environment driver to a NVMEM layout and lifting the
>> restriction
>> that it only works with mtd devices. But as it will change the
>> required
>> compatible strings, it is marked as RFC for now. It also needs to have
>> its device tree schema update which is left out here. These two
>> patches
>> are not expected to be applied, but rather to show another example of
>> how to use the layouts.
>>
>> For now, the layouts are selected by a specific compatible string in a
>> device tree. E.g. the VPD on the kontron sl28 do (within a SPI flash
>> node):
>> compatible = "kontron,sl28-vpd", "user-otp";
>> or if you'd use the u-boot environment (within an MTD patition):
>> compatible = "u-boot,env", "nvmem";
>>
>> The "user-otp" (or "nvmem") will lead to a NVMEM device, the
>> "kontron,sl28-vpd" (or "u-boot,env") will then apply the specific
>> layout
>> on top of the NVMEM device.
>
> So if I understand correctly, there should be:
> - one DT node defining the storage medium eeprom/mtd/whatever,
> - another DT node defining the nvmem device with two compatibles, the
> "nvmem" (or "user-otp") and the layout.
> Is this correct? Actually I was a bit surprised because generally
> speaking, DT maintainers (rightfully) do not want to describe how we
> use devices, the nvmem abstraction looks like a Linux thing when on top
> of mtd devices for instance, so I just wanted to confirm this point.

What do you mean by two nodes? Two separate ones or one being a
subnode of the other?

There is only one (storage) node and nvmem cells as subnodes.
The two compatibles aren't strictly needed. But it will simplify
the drivers in linux greatly. Otherwise the storage driver would
need to know for which compatibles it needs to register a
nvmem device. E.g. MTD devices determine that by the "nvmem"
compatible. The OTP driver will probe by "{user,factory}-otp".
If you'd only have one compatible, the storage driver would need
a list of all the layouts so it can register a nvmem device.

But also from a device tree POV this makes sense IMHO because
the second compatible is a more specific one. With only
the more generic compatible you just get a nvmem device without
any cells - or only the cells described in the device tree.

Regarding "describe how the devices are used": then there shouldn't
be nvmem (cell) bindings at all, because you are actually describing
how you are using the nvmem provider. So IMHO having for example
the compatible "kontron,sl28-vpd" is actually fits more than having
a nvmem provider compatible with cells subnodes.

> Then, as we have an nvmem device described in the DT, why not just
> pointing at the nvmem device from the cell consumer, rather than still
> having the need to define all the cells that the nvmem device will
> produce in the DT?

See also
https://lore.kernel.org/linux-devicetree/[email protected]/

> Maybe an example to show what I mean. Here is the current way:
>
> nvmem_provider: nvmem-provider {
> properties;
>
> mycell: my_cell {
> [properties;]
> };
> };
>
> And we point to a cell with:
>
> nvmem-cells = <&mycell>;
>
> But, as for the tlv tables, there are many cells that will be produced,
> and the driver may anyway just ask for the cell name (eg. performing a
> lookup of the "mac-address" cell name), so why bothering to describe
> all
> the cells in the DT, like:
>
> nvmem-cells-providers = <&nvmem_provider>;
>
> What do you think?

Ok, you even go one step further and even removing the argument
of the phandle and you are proposing to use the nvmem-cell-name,
right? That might work with simple cells created by a layout. But
what if there are two consumers with different names for the same
cell? Consumer bindings might already be present, e.g. the ethernet
bindings will use "mac-address". What if there is another binding
which want to use that cell but doesn't name it "mac-address"?
IMHO to reference a nvmem cell you shouldn't rely on the consumer.

Also keep in mind, that the number of arguments is fixed and given
by the "#.*-cells" property found on the target node. Therefore,
that won't work if you have cells where one has an argument and
another don't.

> Maybe for the mac addresses this is a bit limiting as, in practice, we
> often have base mac addresses available and using:
>
> nvmem-cells = <&mycell INDEX>;
>
> allows to dynamically create many different mac addresses, but I wonder
> if the approach would be interesting for other cell types. Just an open
> question.

So how would your idea work with that? Maybe we could support both?
But again, I'm not sure if it is a good idea to mix the consumer
with the provider.

-michael