mail archive of the barebox mailing list
 help / color / mirror / Atom feed
* [PATCH v2 0/9] NVMEM: Introduce write protection support
@ 2025-06-05  7:47 Oleksij Rempel
  2025-06-05  7:47 ` [PATCH v2 1/9] nvmem: Add 'protect' operation to core framework Oleksij Rempel
                   ` (8 more replies)
  0 siblings, 9 replies; 10+ messages in thread
From: Oleksij Rempel @ 2025-06-05  7:47 UTC (permalink / raw)
  To: barebox; +Cc: Oleksij Rempel

This series adds a generic protect op to NVMEM core, enabling
standardized write protection. This makes NVMEM behave more like other
flash interfaces (e.g., CFI block protection), which helps with common
tooling.

Oleksij Rempel (9):
  nvmem: Add 'protect' operation to core framework
  nvmem: rmem: add write and protect support
  commands: nvmem: Add support for creating dynamic rmem devices
  regmap: Add reg_seal operation for hardware protection
  nvmem: regmap: Implement protect operation using regmap_seal
  nvmem: bsec: Implement NVMEM protect via regmap_seal for OTP locking
  nvmem: rmem: generate unic device name
  fs: Report errors for out-of-bounds protect operations
  test: Add pytest suite for NVMEM framework

 commands/nvmem.c               |  121 +++-
 drivers/base/regmap/internal.h |    2 +
 drivers/base/regmap/regmap.c   |   30 +
 drivers/nvmem/bsec.c           |   27 +
 drivers/nvmem/core.c           |   32 +
 drivers/nvmem/partition.c      |    7 +
 drivers/nvmem/regmap.c         |   65 ++
 drivers/nvmem/rmem.c           |  197 +++++-
 fs/fs.c                        |    6 +-
 include/driver.h               |    3 +
 include/linux/nvmem-provider.h |    2 +
 include/linux/regmap.h         |   33 ++
 include/mach/stm32mp/bsec.h    |    1 +
 test/py/test_nvmem.py          | 1019 ++++++++++++++++++++++++++++++++
 14 files changed, 1535 insertions(+), 10 deletions(-)
 create mode 100644 test/py/test_nvmem.py

--
2.39.5




^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 1/9] nvmem: Add 'protect' operation to core framework
  2025-06-05  7:47 [PATCH v2 0/9] NVMEM: Introduce write protection support Oleksij Rempel
@ 2025-06-05  7:47 ` Oleksij Rempel
  2025-06-05  7:47 ` [PATCH v2 2/9] nvmem: rmem: add write and protect support Oleksij Rempel
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2025-06-05  7:47 UTC (permalink / raw)
  To: barebox; +Cc: Oleksij Rempel

Introduce a generic "protect" operation to the NVMEM framework.
This allows NVMEM providers to expose hardware-specific protection or
locking mechanisms through the character device interface. Existing
read/write operations do not cover this type of state-altering
protection.

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
changes v2:
- rebase against latest nvmem changes
- use  (offset + count > nvmem->size) instead of (offset >= nvmem->size
  || count > nvmem->size - offset)
- remove NVMEM_PROTECT_ENABLE_WRITE and NVMEM_PROTECT_DISABLE_WRITE
  they will be added later as non nvmem specific defines
---
 drivers/nvmem/core.c           | 32 ++++++++++++++++++++++++++++++++
 drivers/nvmem/partition.c      |  7 +++++++
 include/linux/nvmem-provider.h |  2 ++
 3 files changed, 41 insertions(+)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index fd3c39fd8e43..cfe9770a5f1b 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -30,6 +30,8 @@ struct nvmem_device {
 					     const void *val, size_t val_size);
 	int			(*reg_read)(void *ctx, unsigned int reg,
 					    void *val, size_t val_size);
+	int			(*reg_protect)(void *ctx, unsigned int reg,
+					       size_t bytes, int prot);
 };
 
 struct nvmem_cell {
@@ -87,9 +89,38 @@ static ssize_t nvmem_cdev_write(struct cdev *cdev, const void *buf, size_t count
 	return retlen;
 }
 
+static int nvmem_cdev_protect(struct cdev *cdev, size_t count, loff_t offset,
+			      int prot)
+{
+	struct nvmem_device *nvmem;
+
+	nvmem = container_of(cdev, struct nvmem_device, cdev);
+
+	dev_dbg(cdev->dev, "protect ofs: 0x%08llx count: 0x%08zx prot: %d\n",
+		offset, count, prot);
+
+	if (!nvmem->reg_protect) {
+		dev_warn(cdev->dev, "NVMEM device %s does not support protect operation\n",
+			 nvmem->name);
+		return -EOPNOTSUPP;
+	}
+
+	if (!count)
+		return 0;
+
+	if (offset + count > nvmem->size) {
+		dev_err(cdev->dev, "protect range out of bounds (ofs: 0x%08llx, count 0x%08zx, size 0x%08zx)\n",
+			offset, count, nvmem->size);
+		return -EINVAL;
+	}
+
+	return nvmem->reg_protect(nvmem->priv, offset, count, prot);
+}
+
 static struct cdev_operations nvmem_chrdev_ops = {
 	.read  = nvmem_cdev_read,
 	.write  = nvmem_cdev_write,
+	.protect = nvmem_cdev_protect,
 };
 
 static int nvmem_register_cdev(struct nvmem_device *nvmem, const char *name)
@@ -207,6 +238,7 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
 	nvmem->dev.parent = config->dev;
 	nvmem->reg_read = config->reg_read;
 	nvmem->reg_write = config->reg_write;
+	nvmem->reg_protect = config->reg_protect;
 	np = config->cdev ? cdev_of_node(config->cdev) : config->dev->of_node;
 	nvmem->dev.of_node = np;
 	nvmem->priv = config->priv;
diff --git a/drivers/nvmem/partition.c b/drivers/nvmem/partition.c
index d1127c1401af..bb6dbad7b5f6 100644
--- a/drivers/nvmem/partition.c
+++ b/drivers/nvmem/partition.c
@@ -18,6 +18,12 @@ static int nvmem_cdev_read(void *ctx, unsigned offset, void *buf, size_t bytes)
 	return cdev_read(ctx, buf, bytes, offset, 0);
 }
 
+static int nvmem_cdev_protect(void *ctx, unsigned int offset, size_t bytes,
+			      int prot)
+{
+	return cdev_protect(ctx, bytes, offset, prot);
+}
+
 static int nvmem_cells_probe(struct device *dev)
 {
 	struct nvmem_config config = {};
@@ -37,6 +43,7 @@ static int nvmem_cells_probe(struct device *dev)
 	config.size = cdev->size;
 	config.reg_read = nvmem_cdev_read;
 	config.reg_write = nvmem_cdev_write;
+	config.reg_protect = nvmem_cdev_protect;
 
 	return PTR_ERR_OR_ZERO(nvmem_register(&config));
 }
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index c1765673eb23..4d44468456f0 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -34,6 +34,8 @@ struct nvmem_config {
 					     const void *val, size_t val_size);
 	int			(*reg_read)(void *ctx, unsigned int reg,
 					    void *val, size_t val_size);
+	int			(*reg_protect)(void *ctx, unsigned int offset,
+					       size_t bytes, int prot);
 	void			*priv;
 	nvmem_cell_post_process_t cell_post_process;
 };
-- 
2.39.5




^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 2/9] nvmem: rmem: add write and protect support
  2025-06-05  7:47 [PATCH v2 0/9] NVMEM: Introduce write protection support Oleksij Rempel
  2025-06-05  7:47 ` [PATCH v2 1/9] nvmem: Add 'protect' operation to core framework Oleksij Rempel
@ 2025-06-05  7:47 ` Oleksij Rempel
  2025-06-05  7:47 ` [PATCH v2 3/9] commands: nvmem: Add support for creating dynamic rmem devices Oleksij Rempel
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2025-06-05  7:47 UTC (permalink / raw)
  To: barebox; +Cc: Oleksij Rempel

Add reg_write and reg_protect operations to the rmem NVMEM driver.
This makes rmem devices writable (they were previously read-only via the
NVMEM interface due to lacking reg_write) and allows specific regions
to be marked read-only.

The primary motivation is to improve testing of NVMEM consumer code that
handles write protection, by enabling rmem to emulate such hardware,
particularly in sandbox environments.

Key changes:

- reg_write implemented: Enables writes. Writes to protected regions
  return -EROFS.

- reg_protect implemented:
  - PROTECT_DISABLE_WRITE: Marks range read-only.

  - PROTECT_ENABLE_WRITE: Makes range writable.

  - Probe function updated for new ops and list initialization.

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
changes v2:
- use a bitmap instead of lists of ranges.
---
 drivers/nvmem/rmem.c | 195 +++++++++++++++++++++++++++++++++++++++++++
 include/driver.h     |   3 +
 2 files changed, 198 insertions(+)

diff --git a/drivers/nvmem/rmem.c b/drivers/nvmem/rmem.c
index afa0dd78c8f4..fd7389cd39e3 100644
--- a/drivers/nvmem/rmem.c
+++ b/drivers/nvmem/rmem.c
@@ -11,6 +11,10 @@
 struct rmem {
 	struct device *dev;
 	const struct resource *mem;
+	size_t total_size;
+	/* Bitmap: 1 bit per byte of rmem. 1=protected, 0=writable. */
+	unsigned char *protection_bitmap;
+	size_t protection_bitmap_bytes;
 };
 
 static int rmem_read(void *context, unsigned int offset,
@@ -21,6 +25,190 @@ static int rmem_read(void *context, unsigned int offset,
 			bytes, offset, 0);
 }
 
+/**
+ * rmem_is_byte_protected() - Check if a specific byte is write-protected.
+ * @rmem:	Pointer to the rmem private data.
+ * @offset:	Offset of the byte to check.
+ *
+ * Helper function to consult the protection bitmap.
+ *
+ * Return: True if the byte at @offset is protected, false otherwise.
+ */
+static inline bool rmem_is_byte_protected(const struct rmem *rmem,
+					  unsigned int offset)
+{
+	unsigned char bit_mask;
+	unsigned int byte_idx;
+
+	byte_idx = offset / 8;
+	bit_mask = 1U << (offset % 8);
+	return (rmem->protection_bitmap[byte_idx] & bit_mask) != 0;
+}
+
+/**
+ * rmem_do_protect_byte() - Mark a specific byte as write-protected.
+ * @rmem:	Pointer to the rmem private data.
+ * @offset:	Offset of the byte to protect.
+ *
+ * Helper function to set a bit in the protection bitmap. Assumes @offset
+ * is valid and rmem->protection_bitmap is allocated.
+ */
+static inline void rmem_do_protect_byte(struct rmem *rmem, unsigned int offset)
+{
+	unsigned char bit_mask = 1U << (offset % 8);
+	unsigned int byte_idx = offset / 8;
+
+	rmem->protection_bitmap[byte_idx] |= bit_mask;
+}
+
+/**
+ * rmem_do_unprotect_byte() - Mark a specific byte as writable.
+ * @rmem:	Pointer to the rmem private data.
+ * @offset:	Offset of the byte to unprotect.
+ *
+ * Helper function to clear a bit in the protection bitmap. Assumes @offset
+ * is valid and rmem->protection_bitmap is allocated.
+ */
+static inline void rmem_do_unprotect_byte(struct rmem *rmem,
+					  unsigned int offset)
+{
+	unsigned int byte_idx = offset / 8;
+	unsigned char bit_mask = 1U << (offset % 8);
+
+	rmem->protection_bitmap[byte_idx] &= ~bit_mask;
+}
+
+/**
+ * rmem_write() - Write data to the NVMEM device.
+ * @context:	Pointer to the rmem private data (struct rmem).
+ * @offset:	Offset within the NVMEM device to write to.
+ * @val:	Buffer containing the data to write.
+ * @bytes:	Number of bytes to write.
+ *
+ * This function is called by the NVMEM core to write data to the
+ * reserved memory region. It first checks the protection bitmap to ensure
+ * the target range is not write-protected. If writable, it uses the
+ * custom 'mem_copy' function.
+ * Specific error codes include -EROFS if protected or -EINVAL for bad params.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int rmem_write(void *context, unsigned int offset, const void *val,
+		      size_t bytes)
+{
+	struct rmem *rmem = context;
+	unsigned int i;
+
+	for (i = 0; i < bytes; ++i) {
+		if (rmem_is_byte_protected(rmem, offset + i)) {
+			dev_warn(rmem->dev,
+				 "Write [0x%x, len %zu] denied at 0x%x (protected)\n",
+				 offset, bytes, offset + i);
+			return -EROFS;
+		}
+	}
+
+	/*
+	 * The last two arguments to mem_copy (0, 0) are specific to
+	 * the custom mem_copy implementation.
+	 */
+	return mem_copy(rmem->dev, (void *)rmem->mem->start + offset, val,
+			bytes, 0, 0);
+}
+
+/**
+ * rmem_set_protection_bits() - Mark a memory range as read-only.
+ * @rmem:	Pointer to the rmem private data.
+ * @offset:	Starting offset of the range to protect.
+ * @bytes:	Length of the range to protect in bytes.
+ *
+ * Sets the corresponding bits in the protection_bitmap to mark the
+ * specified memory range as write-protected (read-only).
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int rmem_set_protection_bits(struct rmem *rmem, unsigned int offset,
+				    size_t bytes)
+{
+	unsigned int end_offset = offset + bytes;
+	unsigned int i;
+
+	for (i = offset; i < end_offset; ++i)
+		rmem_do_protect_byte(rmem, i);
+
+	dev_info(rmem->dev, "Protected range [0x%x, len %zu]\n", offset,
+		 bytes);
+	return 0;
+}
+
+/**
+ * rmem_clear_protection_bits() - Mark a memory range as writable.
+ * @rmem:	Pointer to the rmem private data.
+ * @offset:	Starting offset of the range to unprotect.
+ * @bytes:	Length of the range to unprotect in bytes.
+ *
+ * Clears the corresponding bits in the protection_bitmap to mark the
+ * specified memory range as writable.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int rmem_clear_protection_bits(struct rmem *rmem, unsigned int offset,
+				      size_t bytes)
+{
+	unsigned int end_offset = offset + bytes;
+	unsigned int i;
+
+	for (i = offset; i < end_offset; ++i)
+		rmem_do_unprotect_byte(rmem, i);
+
+	dev_info(rmem->dev, "Unprotected range [0x%x, len %zu]\n", offset,
+		 bytes);
+	return 0;
+}
+
+/**
+ * rmem_protect() - NVMEM callback to change protection status of a range.
+ * @context:	Pointer to the rmem private data (struct rmem).
+ * @offset:	Starting offset of the range.
+ * @bytes:	Length of the range in bytes.
+ * @prot_mode:	Protection mode to apply (PROTECT_DISABLE_WRITE or
+ * PROTECT_ENABLE_WRITE).
+ *
+ * This function is called by the NVMEM core to enable or disable
+ * write protection for a specified memory range.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int rmem_protect(void *context, unsigned int offset, size_t bytes,
+			int prot_mode)
+{
+	struct rmem *rmem = context;
+	int ret;
+
+	switch (prot_mode) {
+	case PROTECT_DISABLE_WRITE: /* Make read-only */
+		ret = rmem_set_protection_bits(rmem, offset, bytes);
+		break;
+	case PROTECT_ENABLE_WRITE: /* Make writable */
+		ret = rmem_clear_protection_bits(rmem, offset, bytes);
+		break;
+	default:
+		dev_warn(rmem->dev, "%s: Invalid protection mode %d\n",
+			 __func__, prot_mode);
+		ret = -EINVAL;
+		break;
+	}
+
+	if (ret)
+		return ret;
+
+	dev_dbg(rmem->dev,
+		"Protection op complete [0x%x, len %zu], mode %d\n", offset,
+		bytes, prot_mode);
+
+	return 0;
+}
+
 static int rmem_probe(struct device *dev)
 {
 	struct nvmem_config config = { };
@@ -41,7 +229,14 @@ static int rmem_probe(struct device *dev)
 	config.priv = priv;
 	config.name = "rmem";
 	config.size = resource_size(mem);
+	priv->total_size = config.size;
+
+	priv->protection_bitmap_bytes = DIV_ROUND_UP(priv->total_size, 8);
+	priv->protection_bitmap = xzalloc(priv->protection_bitmap_bytes);
+
 	config.reg_read = rmem_read;
+	config.reg_write = rmem_write;
+	config.reg_protect = rmem_protect;
 
 	return PTR_ERR_OR_ZERO(nvmem_register(&config));
 }
diff --git a/include/driver.h b/include/driver.h
index e9a919f9bbb5..8e738f076c30 100644
--- a/include/driver.h
+++ b/include/driver.h
@@ -431,6 +431,9 @@ int platform_driver_register(struct driver *drv);
 
 int platform_device_register(struct device *new_device);
 
+#define PROTECT_ENABLE_WRITE		0
+#define PROTECT_DISABLE_WRITE		1
+
 struct cdev_operations {
 	/*! Called in response of reading from this device. Required */
 	ssize_t (*read)(struct cdev*, void* buf, size_t count, loff_t offset, ulong flags);
-- 
2.39.5




^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 3/9] commands: nvmem: Add support for creating dynamic rmem devices
  2025-06-05  7:47 [PATCH v2 0/9] NVMEM: Introduce write protection support Oleksij Rempel
  2025-06-05  7:47 ` [PATCH v2 1/9] nvmem: Add 'protect' operation to core framework Oleksij Rempel
  2025-06-05  7:47 ` [PATCH v2 2/9] nvmem: rmem: add write and protect support Oleksij Rempel
@ 2025-06-05  7:47 ` Oleksij Rempel
  2025-06-05  7:47 ` [PATCH v2 4/9] regmap: Add reg_seal operation for hardware protection Oleksij Rempel
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2025-06-05  7:47 UTC (permalink / raw)
  To: barebox; +Cc: Oleksij Rempel

The 'nvmem' command is extended to allow the creation of dynamic
RAM-backed NVMEM devices (rmem). This functionality is useful for
testing NVMEM operations, for scripts that require temporary, writable
NVMEM areas, or when a persistent NVMEM device is not available or
necessary.

This patch introduces the following new options to the 'nvmem' command:

-c : Creates a new rmem NVMEM device of the specified . The size can be
given with common suffixes (e.g., K, M). The command enforces that the
size is non-zero and does not exceed a defined maximum (NVMEM_MAX_SIZE,
defaulting to 1MB). This option requires CONFIG_NVMEM_RMEM to be
enabled.

-v : When used in conjunction with the -c option, this stores the name
of the newly created rmem device (e.g., "rmem0") into the environment
variable specified by . This allows subsequent commands or scripts to
easily reference the dynamic NVMEM device.

If invoked without any options, the 'nvmem' command retains its original
behavior of listing all currently available NVMEM devices.

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
changes v2:
- do not check for error after xzalloc()
- use 'if (ret)' after nvmem_parse_and_validate_create_size()
---
 commands/nvmem.c | 121 ++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 115 insertions(+), 6 deletions(-)

diff --git a/commands/nvmem.c b/commands/nvmem.c
index a0e3d092e3cf..3bfa1ff19a19 100644
--- a/commands/nvmem.c
+++ b/commands/nvmem.c
@@ -1,24 +1,133 @@
 // SPDX-License-Identifier: GPL-2.0
 // SPDX-FileCopyrightText: © 2021 Ahmad Fatoum, Pengutronix
 
-#include <common.h>
 #include <command.h>
+#include <common.h>
+#include <environment.h>
+#include <getopt.h>
 #include <linux/nvmem-consumer.h>
+#include <linux/sizes.h>
 
-static int do_nvmem(int argc, char *argv[])
+/* Maximum size for dynamically created NVMEM devices.
+ * This is a reasonable limit for RAM-backed devices, but can be adjusted
+ * based on system capabilities and requirements.
+ */
+#define NVMEM_MAX_SIZE		SZ_1M
+
+/* Static counter to ensure unique device IDs for dynamically created rmem
+ * devices
+ */
+static int dynamic_rmem_idx;
+
+/**
+ * nvmem_create_dynamic_rmem - Creates a dynamic RAM-backed NVMEM device.
+ * @create_size: Size of the NVMEM device to create.
+ * @var_name: Optional environment variable name to store the created device's
+ *            name.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+static int nvmem_create_dynamic_rmem(unsigned long create_size,
+				     const char *var_name)
+{
+	struct device *dev;
+	void *buffer;
+	int ret = 0;
+
+	buffer = xzalloc(create_size);
+	dev = add_generic_device("rmem", dynamic_rmem_idx, NULL,
+				 (resource_size_t)(uintptr_t)buffer,
+				 (resource_size_t)create_size,
+				 IORESOURCE_MEM, NULL);
+
+	if (var_name)
+		ret = setenv(var_name, dev_name(dev));
+
+	dynamic_rmem_idx++;
+
+	return ret;
+}
+
+static int nvmem_parse_and_validate_create_size(const char *size_arg,
+						unsigned long *out_size)
 {
-	nvmem_devices_print();
+
+	if (!IS_ENABLED(CONFIG_NVMEM_RMEM)) {
+		printf("Error: rmem NVMEM driver (CONFIG_NVMEM_RMEM) is not enabled in this build.\n");
+		return -EINVAL;
+	}
+
+	*out_size = strtoul_suffix(optarg, NULL, 0);
+	if (!*out_size) {
+		printf("Error: Invalid size '%s' for -c option. Must be non-zero.\n",
+		       optarg);
+		return COMMAND_ERROR_USAGE;
+	}
+
+	if (*out_size > NVMEM_MAX_SIZE) {
+		printf("Error: Size '%lu' exceeds maximum allowed size of %d bytes.\n",
+		       *out_size, NVMEM_MAX_SIZE);
+		return COMMAND_ERROR_USAGE;
+	}
 
 	return 0;
 }
 
+static int do_nvmem(int argc, char *argv[])
+{
+	unsigned long create_size = 0;
+	char *optarg_v = NULL;
+	int ret = 0;
+	int opt;
+
+	while ((opt = getopt(argc, argv, "c:v:h")) != -1) {
+		switch (opt) {
+		case 'c':
+			ret = nvmem_parse_and_validate_create_size(optarg,
+								&create_size);
+			if (ret)
+				return ret;
+			break;
+		case 'v':
+			optarg_v = optarg;
+			break;
+		case 'h': /* fallthrough */
+		default:
+			return COMMAND_ERROR_USAGE;
+		}
+	}
+
+	if (optarg_v && !create_size) {
+		printf("Error: -v <VARNAME> option requires -c <SIZE> to create a device.\n");
+		return COMMAND_ERROR_USAGE;
+	}
+
+	if (create_size > 0)
+		ret = nvmem_create_dynamic_rmem(create_size, optarg_v);
+	else
+		nvmem_devices_print();
+
+	return ret;
+}
+
 BAREBOX_CMD_HELP_START(nvmem)
-BAREBOX_CMD_HELP_TEXT("Usage: nvmem")
-BAREBOX_CMD_HELP_END
+BAREBOX_CMD_HELP_TEXT("List NVMEM devices or create dynamic RAM-backed NVMEM")
+BAREBOX_CMD_HELP_TEXT("devices. If no arguments are provided, it lists all")
+BAREBOX_CMD_HELP_TEXT("available NVMEM devices.")
+BAREBOX_CMD_HELP_TEXT("")
+BAREBOX_CMD_HELP_TEXT("Options:")
+BAREBOX_CMD_HELP_OPT("-c <size>", "Create a new RAM-backed NVMEM device of")
+BAREBOX_CMD_HELP_OPT("         ", "<size> bytes. (Requires CONFIG_NVMEM_RMEM")
+BAREBOX_CMD_HELP_OPT("         ", "to be enabled). <size> must be a non-zero.")
+BAREBOX_CMD_HELP_OPT("-v <variable>", "When using -c, set environment variable")
+BAREBOX_CMD_HELP_OPT("             ", "<variable> to the name of the created")
+BAREBOX_CMD_HELP_OPT("             ", "NVMEM device (e.g., rmem0).")
+BAREBOX_CMD_HELP_END;
 
 BAREBOX_CMD_START(nvmem)
 	.cmd		= do_nvmem,
-	BAREBOX_CMD_DESC("list nvmem devices")
+	BAREBOX_CMD_DESC("list or create NVMEM devices")
+	BAREBOX_CMD_OPTS("[-c <size> [-v <varname>]]")
 	BAREBOX_CMD_GROUP(CMD_GRP_HWMANIP)
 	BAREBOX_CMD_HELP(cmd_nvmem_help)
 BAREBOX_CMD_END
-- 
2.39.5




^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 4/9] regmap: Add reg_seal operation for hardware protection
  2025-06-05  7:47 [PATCH v2 0/9] NVMEM: Introduce write protection support Oleksij Rempel
                   ` (2 preceding siblings ...)
  2025-06-05  7:47 ` [PATCH v2 3/9] commands: nvmem: Add support for creating dynamic rmem devices Oleksij Rempel
@ 2025-06-05  7:47 ` Oleksij Rempel
  2025-06-05  7:47 ` [PATCH v2 5/9] nvmem: regmap: Implement protect operation using regmap_seal Oleksij Rempel
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2025-06-05  7:47 UTC (permalink / raw)
  To: barebox; +Cc: Oleksij Rempel

Add a new 'reg_seal' operation to the regmap bus interface, along
with a public API `regmap_seal()`. This is needed for drivers that
must perform hardware-level "sealing" or permanent write-protection
on registers/words, a capability not covered by standard regmap
read/write ops.

The initial use case is for the STM32 BSEC driver (coming in a
follow-up patch) to lock One-Time Programmable (OTP) memory words.
This gives explicit control to make OTP entries permanently read-only
after programming.

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
changes v2:
- do not check for existing 'map->reg_seal'
---
 drivers/base/regmap/internal.h |  2 ++
 drivers/base/regmap/regmap.c   | 30 ++++++++++++++++++++++++++++++
 include/linux/regmap.h         | 33 +++++++++++++++++++++++++++++++++
 3 files changed, 65 insertions(+)

diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h
index 6f6a34edc7a8..f2b95cc6361e 100644
--- a/drivers/base/regmap/internal.h
+++ b/drivers/base/regmap/internal.h
@@ -39,6 +39,8 @@ struct regmap {
 			unsigned int *val);
 	int (*reg_write)(void *context, unsigned int reg,
 			 unsigned int val);
+	int (*reg_seal)(void *context, unsigned int reg,
+			unsigned int flags);
 };
 
 struct regmap_field {
diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c
index 777636c0b319..e27ba53dec58 100644
--- a/drivers/base/regmap/regmap.c
+++ b/drivers/base/regmap/regmap.c
@@ -81,6 +81,17 @@ static int _regmap_bus_reg_write(void *context, unsigned int reg,
 	return map->bus->reg_write(map->bus_context, reg, val);
 }
 
+static int _regmap_bus_reg_seal(void *context, unsigned int reg,
+				unsigned int flags)
+{
+	struct regmap *map = context;
+
+	if (!map->bus->reg_seal)
+		return -EOPNOTSUPP;
+
+	return map->bus->reg_seal(map->bus_context, reg, flags);
+}
+
 /*
  * regmap_init - initialize and register a regmap
  *
@@ -113,6 +124,8 @@ struct regmap *regmap_init(struct device *dev,
 	map->reg_shift = config->pad_bits % 8;
 	map->max_register = config->max_register;
 
+	map->reg_seal = _regmap_bus_reg_seal;
+
 	if (!bus->read || !bus->write) {
 		map->reg_read = _regmap_bus_reg_read;
 		map->reg_write = _regmap_bus_reg_write;
@@ -186,6 +199,23 @@ int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val)
 	return map->reg_read(map, reg, val);
 }
 
+/**
+ * regmap_seal() - Seal a register in a map
+ *
+ * @map: Register map to seal
+ * @reg: Register to seal
+ * @flag: Flag to set in the register
+ *
+ * This function is used to seal a register, preventing further writes to it.
+ * The flag is typically used to indicate that the register is sealed.
+ *
+ * Returns zero for success, a negative number on error.
+ */
+int regmap_seal(struct regmap *map, unsigned int reg, unsigned int flags)
+{
+	return map->reg_seal(map, reg, flags);
+}
+
 /**
  * regmap_update_bits() - Perform a read/modify/write cycle on a register
  *
diff --git a/include/linux/regmap.h b/include/linux/regmap.h
index c24b877712cd..0a460f9f541b 100644
--- a/include/linux/regmap.h
+++ b/include/linux/regmap.h
@@ -65,6 +65,35 @@ typedef int (*regmap_hw_reg_read)(void *context, unsigned int reg,
 				  unsigned int *val);
 typedef int (*regmap_hw_reg_write)(void *context, unsigned int reg,
 				   unsigned int val);
+typedef int (*regmap_hw_reg_seal)(void *context, unsigned int reg,
+				  unsigned int flags);
+
+/**
+ * @REGMAP_SEAL_WRITE_PROTECT: Request to make the register(s) write-protected.
+ * If this flag is set, the bus-specific seal operation should attempt to
+ * prevent further writes to the specified register or range.
+ */
+#define REGMAP_SEAL_WRITE_PROTECT	BIT(0)
+
+/**
+ * @REGMAP_SEAL_PERMANENT: Signifies the sealing operation is intended to be
+ * permanent and irreversible. If this flag is not set (and REGMAP_SEAL_CLEAR
+ * is also not set), the protection might be temporary or its permanence
+ * is undefined by this generic flag (bus-specific behavior would dictate).
+ * For operations like OTP locking, this flag should be used with
+ * REGMAP_SEAL_WRITE_PROTECT.
+ */
+#define REGMAP_SEAL_PERMANENT		BIT(1)
+
+/**
+ * @REGMAP_SEAL_CLEAR: Request to clear a previously set protection.
+ * This flag should be used in conjunction with other flags (e.g.,
+ * REGMAP_SEAL_WRITE_PROTECT) to specify which type of protection to attempt
+ * to clear. The underlying hardware must support clearing the protection.
+ * If the protection is permanent, an attempt to clear it should fail with
+ * an appropriate error code (e.g., -EOPNOTSUPP or -EPERM).
+ */
+#define REGMAP_SEAL_CLEAR		BIT(2)
 
 /**
  * struct regmap_bus - Description of a hardware bus for the register map
@@ -73,6 +102,8 @@ typedef int (*regmap_hw_reg_write)(void *context, unsigned int reg,
  * @reg_write: Write a single register value to the given register address. This
  *             write operation has to complete when returning from the function.
  * @reg_read: Read a single register value from a given register address.
+ * @reg_seal: Optional. Perform a hardware operation to seal or permanently
+ *            protect a register or region (e.g., OTP write-locking).
  * @read: Read operation.  Data is returned in the buffer used to transmit
  *         data.
  * @write: Write operation.
@@ -88,6 +119,7 @@ typedef int (*regmap_hw_reg_write)(void *context, unsigned int reg,
 struct regmap_bus {
 	regmap_hw_reg_write reg_write;
 	regmap_hw_reg_read reg_read;
+	regmap_hw_reg_seal reg_seal;
 
 	int (*read)(void *context,
 		    const void *reg_buf, size_t reg_size,
@@ -202,6 +234,7 @@ int regmap_multi_register_cdev(struct regmap *map8,
 
 int regmap_write(struct regmap *map, unsigned int reg, unsigned int val);
 int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val);
+int regmap_seal(struct regmap *map, unsigned int reg, unsigned int flags);
 
 #ifndef regmap_bulk_read
 #define regmap_bulk_read regmap_bulk_read
-- 
2.39.5




^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 5/9] nvmem: regmap: Implement protect operation using regmap_seal
  2025-06-05  7:47 [PATCH v2 0/9] NVMEM: Introduce write protection support Oleksij Rempel
                   ` (3 preceding siblings ...)
  2025-06-05  7:47 ` [PATCH v2 4/9] regmap: Add reg_seal operation for hardware protection Oleksij Rempel
@ 2025-06-05  7:47 ` Oleksij Rempel
  2025-06-05  7:47 ` [PATCH v2 6/9] nvmem: bsec: Implement NVMEM protect via regmap_seal for OTP locking Oleksij Rempel
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2025-06-05  7:47 UTC (permalink / raw)
  To: barebox; +Cc: Oleksij Rempel

Implement the NVMEM 'protect' operation for devices registered via
regmap. This adds a new static function, nvmem_regmap_protect, which
acts as an adapter between the NVMEM core's reg_protect callback
and the recently added regmap_seal() API.

The nvmem_regmap_protect function:
  - Translates the NVMEM 'prot' parameter (0 for unprotect, 1 for
    protect) into the corresponding REGMAP_SEAL_CLEAR |
    REGMAP_SEAL_WRITE_PROTECT or REGMAP_SEAL_WRITE_PROTECT |
    REGMAP_SEAL_PERMANENT flags for the regmap_seal() call.
  - Enforces that the NVMEM operation's offset and size are aligned
    to the regmap's value byte size (obtained via
    regmap_get_val_bytes()).
  - Iterates over the specified byte range, calling regmap_seal() for
    each underlying hardware word.

By assigning nvmem_regmap_protect to config.reg_protect within
nvmem_regmap_register_with_pp, NVMEM devices that are backed by a
regmap can now expose hardware-level protection capabilities. This
is essential for drivers like the STM32 BSEC (in a subsequent patch)
to enable features such as OTP (One-Time Programmable) memory locking
through the standard NVMEM 'protect' cdev operation, provided their
underlying regmap_bus implements the necessary reg_seal method.

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
 drivers/nvmem/regmap.c | 65 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 65 insertions(+)

diff --git a/drivers/nvmem/regmap.c b/drivers/nvmem/regmap.c
index 24712fbb0f33..a7b18856f0bb 100644
--- a/drivers/nvmem/regmap.c
+++ b/drivers/nvmem/regmap.c
@@ -63,6 +63,70 @@ static int nvmem_regmap_read(void *ctx, unsigned offset, void *buf, size_t bytes
 	return 0;
 }
 
+static int nvmem_regmap_protect(void *ctx, unsigned int offset, size_t bytes,
+				int prot)
+{
+	unsigned int seal_flags = 0;
+	struct regmap *map = ctx;
+	size_t reg_val_bytes;
+	unsigned int i;
+	int ret = 0;
+
+	reg_val_bytes = regmap_get_val_bytes(map);
+	if (reg_val_bytes == 0) {
+		dev_err(regmap_get_device(map), "Invalid regmap value byte size (0)\n");
+		return -EINVAL;
+	}
+
+	/* NVMEM protect operations should typically be on aligned boundaries
+	 * matching the hardware's lockable unit (which is regmap's val_bytes
+	 * here).
+	 */
+	if ((offset % reg_val_bytes) != 0 || (bytes % reg_val_bytes) != 0) {
+		dev_warn(regmap_get_device(map),
+			 "NVMEM protect op for regmap: offset (0x%x) or size (0x%zx) not aligned to register size (%zu bytes).\n",
+			 offset, bytes, reg_val_bytes);
+		return -EINVAL;
+	}
+
+	switch (prot) {
+	case PROTECT_ENABLE_WRITE:
+		/* NVMEM protect mode 0 = Unlock/Make-writable
+		 * Attempt to clear write protection.
+		 * The underlying bus->reg_seal must support clearing.
+		 * For BSEC OTPs, this will (and should) fail with -EOPNOTSUPP
+		 * or -EPERM.
+		 */
+		seal_flags = REGMAP_SEAL_CLEAR | REGMAP_SEAL_WRITE_PROTECT;
+		break;
+	case PROTECT_DISABLE_WRITE:
+		/* NVMEM protect mode 1 = Lock/Write-protect */
+		/* For OTPs like BSEC, permanent is implied */
+		seal_flags = REGMAP_SEAL_WRITE_PROTECT | REGMAP_SEAL_PERMANENT;
+		break;
+	default:
+		dev_warn(regmap_get_device(map), "Unsupported NVMEM protect mode: %d\n",
+			 prot);
+		return -EOPNOTSUPP;
+	}
+
+	for (i = 0; i < bytes; i += reg_val_bytes) {
+		unsigned int current_reg_offset = offset + i;
+
+		ret = regmap_seal(map, current_reg_offset, seal_flags);
+		if (ret) {
+			dev_err(regmap_get_device(map), "regmap_seal failed for offset 0x%x: %pe\n",
+				current_reg_offset, ERR_PTR(ret));
+			/* No error handling for partial failures, we messed up
+			 * the HW state and can't recover.
+			 */
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
 struct nvmem_device *
 nvmem_regmap_register_with_pp(struct regmap *map, const char *name,
 			      nvmem_cell_post_process_t cell_post_process)
@@ -82,6 +146,7 @@ nvmem_regmap_register_with_pp(struct regmap *map, const char *name,
 	config.cell_post_process = cell_post_process;
 	config.reg_write = nvmem_regmap_write;
 	config.reg_read = nvmem_regmap_read;
+	config.reg_protect = nvmem_regmap_protect;
 
 	return nvmem_register(&config);
 }
-- 
2.39.5




^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 6/9] nvmem: bsec: Implement NVMEM protect via regmap_seal for OTP locking
  2025-06-05  7:47 [PATCH v2 0/9] NVMEM: Introduce write protection support Oleksij Rempel
                   ` (4 preceding siblings ...)
  2025-06-05  7:47 ` [PATCH v2 5/9] nvmem: regmap: Implement protect operation using regmap_seal Oleksij Rempel
@ 2025-06-05  7:47 ` Oleksij Rempel
  2025-06-05  7:47 ` [PATCH v2 7/9] nvmem: rmem: generate unic device name Oleksij Rempel
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2025-06-05  7:47 UTC (permalink / raw)
  To: barebox; +Cc: Oleksij Rempel

Enable the NVMEM 'protect' cdev operation for the STM32 BSEC driver.
This allows One-Time Programmable (OTP) memory words managed by the
BSEC driver to be permanently write-locked using the standard NVMEM
protection mechanism.

This change implements the recently added `regmap_bus->reg_seal`
interface for the BSEC driver. A new function,
`stm32_bsec_do_reg_seal_otp`, calls the `BSEC_SMC_WRLOCK_OTP` Secure
Monitor Call to perform the hardware OTP word lock. This function is
triggered when `regmap_seal` is called with the
`REGMAP_SEAL_WRITE_PROTECT | REGMAP_SEAL_PERMANENT` flags, which the
NVMEM-regmap bridge uses for `prot=1` (protect) requests. The
`BSEC_SMC_WRLOCK_OTP` enum value is also added.

This explicit sealing via `regmap_seal` is currently implemented for
the SMC path.

Testing from Barebox CLI:

This functionality allows explicit locking of already programmed OTP
words.  The following example demonstrates programming an OTP word,
locking it, and then verifying the write protection.

Assume the BSEC NVMEM device is `/dev/bsec0` and its parameters are
accessed via `bsec0`.

1. Enable permanent OTP writes:
   bsec0.permanent_write_enable=1

2. Program an OTP word (e.g., byte offset 0x170 with value 0x12345678):
   mw -d /dev/bsec0 -l 0x170 0x12345678
   md -s /dev/bsec0 -l 0x170+4  # Expected: Shows 0x12345678

3. Lock this specific OTP word using the 'protect' command:
   # Protects 4 bytes (one 32-bit word) starting at byte offset 0x170
   protect /dev/bsec0 0x170+4
   # Check dmesg for BSEC driver logs confirming the lock via SMC.

4. Verify the write protection:
   mw -d /dev/bsec0 -l 0x170 0xAABBCCDD
   # This write should fail or have no effect on the hardware's fuses
   # value. The mw command itself might not report an error for OTPs if
   # the underlying hardware silently ignores writes to locked bits/words.

   md -s /dev/bsec0 -l 0x170+4
   # Expected: Should still show the original locked value (0x12345678),
   # confirming the write attempt was blocked or ineffective.

5. Verify unprotection is not possible:
   unprotect /dev/bsec0 0x170 4
   # This command should fail, returning an error (e.g., -EOPNOTSUPP or
   # -EPERM) from the driver, as BSEC OTP locks are permanent.

6. (Optional) Disable permanent OTP writes when done:
   bsec0.permanent_write_enable=0

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
 drivers/nvmem/bsec.c        | 27 +++++++++++++++++++++++++++
 include/mach/stm32mp/bsec.h |  1 +
 2 files changed, 28 insertions(+)

diff --git a/drivers/nvmem/bsec.c b/drivers/nvmem/bsec.c
index 9ca98e6180f8..b67b55addfe0 100644
--- a/drivers/nvmem/bsec.c
+++ b/drivers/nvmem/bsec.c
@@ -74,9 +74,36 @@ static int stm32_bsec_reg_write(void *ctx, unsigned reg, unsigned val)
 		return bsec_smc(BSEC_SMC_WRITE_SHADOW, reg, val, NULL);
 }
 
+static int stm32_bsec_do_reg_seal_otp(void *ctx, unsigned int reg,
+				      unsigned int flags)
+{
+	struct bsec_priv *priv = ctx;
+	struct device *dev = &priv->dev;
+
+	if (!priv->permanent_write_enable) {
+		dev_warn(dev, "BSEC seal: permanent_write_enable is OFF.\n");
+		return -EACCES;
+	}
+
+	/* Only REGMAP_SEAL_WRITE_PROTECT and REGMAP_SEAL_PERMANENT
+	 * flags are supported for BSEC OTP sealing.
+	 */
+	if ((flags & (REGMAP_SEAL_WRITE_PROTECT | REGMAP_SEAL_PERMANENT)) !=
+	    (REGMAP_SEAL_WRITE_PROTECT | REGMAP_SEAL_PERMANENT)) {
+		dev_warn(dev, "BSEC seal: unsupported flags 0x%x.\n", flags);
+		return -EINVAL;
+	}
+
+	dev_dbg(dev, "BSEC seal: Locking OTP word at byte offset 0x%x via SMC.\n",
+		reg);
+
+	return bsec_smc(BSEC_SMC_WRLOCK_OTP, reg, 0, NULL);
+}
+
 static struct regmap_bus stm32_bsec_regmap_bus = {
 	.reg_write = stm32_bsec_reg_write,
 	.reg_read = stm32_bsec_read_shadow,
+	.reg_seal = stm32_bsec_do_reg_seal_otp,
 };
 
 static void stm32_bsec_set_unique_machine_id(struct regmap *map)
diff --git a/include/mach/stm32mp/bsec.h b/include/mach/stm32mp/bsec.h
index 45eb0a3f4523..be8cec536a40 100644
--- a/include/mach/stm32mp/bsec.h
+++ b/include/mach/stm32mp/bsec.h
@@ -26,6 +26,7 @@ enum bsec_op {
 	BSEC_SMC_READ_OTP	= 4,
 	BSEC_SMC_READ_ALL	= 5,
 	BSEC_SMC_WRITE_ALL	= 6,
+	BSEC_SMC_WRLOCK_OTP	= 7,
 };
 
 static inline enum bsec_smc bsec_read_field(unsigned field, unsigned *val)
-- 
2.39.5




^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 7/9] nvmem: rmem: generate unic device name
  2025-06-05  7:47 [PATCH v2 0/9] NVMEM: Introduce write protection support Oleksij Rempel
                   ` (5 preceding siblings ...)
  2025-06-05  7:47 ` [PATCH v2 6/9] nvmem: bsec: Implement NVMEM protect via regmap_seal for OTP locking Oleksij Rempel
@ 2025-06-05  7:47 ` Oleksij Rempel
  2025-06-05  7:47 ` [PATCH v2 8/9] fs: Report errors for out-of-bounds protect operations Oleksij Rempel
  2025-06-05  7:47 ` [PATCH v2 9/9] test: Add pytest suite for NVMEM framework Oleksij Rempel
  8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2025-06-05  7:47 UTC (permalink / raw)
  To: barebox; +Cc: Oleksij Rempel

otherwise mnvmem framework will generate same char device for all
instances. This will prevent creation of additional devices

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
 drivers/nvmem/rmem.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/nvmem/rmem.c b/drivers/nvmem/rmem.c
index fd7389cd39e3..adac414e84d8 100644
--- a/drivers/nvmem/rmem.c
+++ b/drivers/nvmem/rmem.c
@@ -227,7 +227,7 @@ static int rmem_probe(struct device *dev)
 
 	config.dev = priv->dev = dev;
 	config.priv = priv;
-	config.name = "rmem";
+	config.name = dev_name(dev);
 	config.size = resource_size(mem);
 	priv->total_size = config.size;
 
-- 
2.39.5




^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 8/9] fs: Report errors for out-of-bounds protect operations
  2025-06-05  7:47 [PATCH v2 0/9] NVMEM: Introduce write protection support Oleksij Rempel
                   ` (6 preceding siblings ...)
  2025-06-05  7:47 ` [PATCH v2 7/9] nvmem: rmem: generate unic device name Oleksij Rempel
@ 2025-06-05  7:47 ` Oleksij Rempel
  2025-06-05  7:47 ` [PATCH v2 9/9] test: Add pytest suite for NVMEM framework Oleksij Rempel
  8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2025-06-05  7:47 UTC (permalink / raw)
  To: barebox; +Cc: Oleksij Rempel

The protect() function previously masked errors for out-of-bounds
requests. It would either return success for offsets already beyond
file/partition size or clamp the count for ranges extending beyond the
boundary. This behavior was introduced by commit 6815e0d05480 ("fs:
limit flash erase and protect to the partiton boundary") to address SPI
flash wrap-around issues.

This masking prevented shell commands from reporting failures for
invalid ranges, which could mislead users and scripts. For example,
underlying driver errors like -EINVAL from NVMEM were not propagated.

This patch modifies protect() to return appropriate error codes (-ENXIO
or -EINVAL) for such out-of-bounds conditions, instead of silently
succeeding or clamping.

Fixes: 6815e0d05480 ("fs: limit flash erase and protect to the partiton boundary")
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
 fs/fs.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/fs/fs.c b/fs/fs.c
index 465fd617c2ba..6d60a1ae918b 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -652,9 +652,9 @@ int protect(int fd, size_t count, loff_t offset, int prot)
 	if (IS_ERR(f))
 		return -errno;
 	if (offset >= f->f_size)
-		return 0;
-	if (count > f->f_size - offset)
-		count = f->f_size - offset;
+		return errno_set(-ENXIO);
+	if (!count  || count > f->f_size - offset)
+		return errno_set(-EINVAL);
 
 	fsdrv = f->fsdev->driver;
 
-- 
2.39.5




^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 9/9] test: Add pytest suite for NVMEM framework
  2025-06-05  7:47 [PATCH v2 0/9] NVMEM: Introduce write protection support Oleksij Rempel
                   ` (7 preceding siblings ...)
  2025-06-05  7:47 ` [PATCH v2 8/9] fs: Report errors for out-of-bounds protect operations Oleksij Rempel
@ 2025-06-05  7:47 ` Oleksij Rempel
  8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2025-06-05  7:47 UTC (permalink / raw)
  To: barebox; +Cc: Oleksij Rempel

Introduce test_nvmem.py, a pytest-based suite for
validating Barebox's NVMEM framework and shell commands.
It uses pytest-labgrid for target interaction.

Tests cover:

- nvmem command: Listing, dynamic rmem creation (-c, -v), parameter
  validation, help.

- CDEV operations (/dev/rmemX): md/mw for read/write with various access
  sizes (byte, word, long), byte swapping (-x), string/numerical data,
  and wrapped md output.

- Protection: protect/unprotect commands, including writes to
  protected/unprotected areas, out-of-bounds protection attempts, and
merging of overlapping protected ranges.

Includes helpers for parsing md output and checks command return
codes and data integrity. A session-wide fixture skips tests if
essential related CONFIGs are disabled.

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
 test/py/test_nvmem.py | 1019 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1019 insertions(+)
 create mode 100644 test/py/test_nvmem.py

diff --git a/test/py/test_nvmem.py b/test/py/test_nvmem.py
new file mode 100644
index 000000000000..03f31108c498
--- /dev/null
+++ b/test/py/test_nvmem.py
@@ -0,0 +1,1019 @@
+import pytest
+import re
+
+# --- Essential Configuration Check ---
+
+# This list should contain the CONFIG strings for all commands and features
+# that are absolutely essential for the NVMEM test suite to run.
+ESSENTIAL_CONFIGS = [
+    "CONFIG_CMD_NVMEM",       # For the 'nvmem' command itself
+    "CONFIG_CMD_MD",          # For the 'md' (memory display) command
+    "CONFIG_CMD_MW",          # For the 'mw' (memory write) command
+    "CONFIG_CMD_FLASH",       # For 'protect' and 'unprotect' commands
+    "CONFIG_NVMEM_RMEM",      # For creating dynamic rmem NVMEM devices
+    "CONFIG_FS_DEVFS"         # For /dev/ device character file paths
+]
+
+@pytest.fixture(scope="session", autouse=True)
+def check_essential_barebox_configs(barebox_config):
+    """
+    Session-scoped autouse fixture to check for essential Barebox CONFIGs.
+    If any essential CONFIG is not enabled in 'barebox_config',
+    this fixture will skip all tests in the current session.
+    """
+    missing_configs = []
+    if not barebox_config:
+        pytest.skip(
+            "Skipping all NVMEM tests: Barebox config not available."
+        )
+        return
+
+    for config_opt in ESSENTIAL_CONFIGS:
+        if config_opt not in barebox_config:
+            missing_configs.append(config_opt)
+
+    if missing_configs:
+        pytest.skip(
+            "Skipping all NVMEM tests: Essential Barebox CONFIGs missing: "
+            f"{', '.join(missing_configs)}"
+        )
+
+# --- Individual Test Skip Helper ---
+def skip_disabled(barebox_config, *config_options_needed):
+    if not barebox_config:
+        pytest.skip(
+            "Test skipped: Barebox config not available for this test."
+        )
+        return
+    missing_for_this_test = []
+    for config_opt in config_options_needed:
+        if config_opt not in barebox_config:
+            missing_for_this_test.append(config_opt)
+    if missing_for_this_test:
+        pytest.skip(
+            "Test skipped, requires specific Barebox CONFIGs: "
+            f"{', '.join(missing_for_this_test)}"
+        )
+
+# --- Constants ---
+NVMEM_MAX_DYNAMIC_SIZE = 1024 * 1024
+TEST_ENV_VAR_NAME = "TESTNVMEMDEV"
+DEFAULT_DEVICE_SIZE = 1024
+
+# --- Helper Functions ---
+def string_to_hex_bytes_for_mw(s: str) -> str:
+    return " ".join([f"0x{ord(c):02x}" for c in s])
+
+def parse_md_output(output_lines: list[str],
+                    access_size_char: str = 'b') -> str:
+    hex_data = []
+
+    unit_hex_len = 0
+    if access_size_char == 'b': unit_hex_len = 2
+    elif access_size_char == 'w': unit_hex_len = 4
+    elif access_size_char == 'l': unit_hex_len = 8
+    elif access_size_char == 'q': unit_hex_len = 16 # For 64-bit quad
+    else:
+        return ""
+
+    hex_part_regex_str = r"^[0-9a-fA-F]+:\s+((?:[0-9a-fA-F]{" + str(unit_hex_len) + r"}\s*)+)"
+    hex_part_regex = re.compile(hex_part_regex_str)
+
+    for i, line in enumerate(output_lines):
+        match = hex_part_regex.match(line)
+        if match:
+            captured_group1 = match.group(1)
+            data_part = "".join(captured_group1.split())
+            hex_data.append(data_part)
+
+    full_hex_string = "".join(hex_data)
+    return full_hex_string.lower()
+
+def parse_md_word_value(output_lines: list[str]) -> int | None: # For 16-bit
+    hex_string = parse_md_output(output_lines, 'w')
+    if hex_string and len(hex_string) == 4: # 16-bit word is 4 hex chars
+        try:
+            return int(hex_string, 16)
+        except ValueError:
+            return None
+    return None
+
+def parse_md_long_value(output_lines: list[str]) -> int | None: # For 32-bit
+    hex_string = parse_md_output(output_lines, 'l')
+    if hex_string and len(hex_string) == 8:
+        try:
+            return int(hex_string, 16)
+        except ValueError:
+            return None
+    return None
+
+def parse_md_bytes_to_string(output_lines: list[str], expected_num_bytes: int) -> str | None:
+    hex_string = parse_md_output(output_lines, 'b')
+
+    expected_hex_len = expected_num_bytes * 2
+    if len(hex_string) > expected_hex_len:
+        hex_string = hex_string[:expected_hex_len]
+
+    if hex_string and len(hex_string) == expected_hex_len :
+        try:
+            if len(hex_string) % 2 != 0:
+                return None
+
+            bytes_obj = bytes.fromhex(hex_string)
+            decoded_string = bytes_obj.decode('ascii', errors='replace')
+            return decoded_string
+        except ValueError:
+            return None
+    return None
+
+# --- Pytest Fixtures (test-specific setup) ---
+@pytest.fixture(scope="function")
+def created_nvmem_device_name(barebox, barebox_config):
+    """
+    Creates a new dynamic NVMEM rmem device and yields its actual name
+    suitable for /dev/ path construction (e.g., "rmem0").
+    This fixture assumes sequential execution and that it can reliably
+    determine the newly created device by diffing `nvmem` output.
+    """
+    stdout_before, _, _ = barebox.run('nvmem')
+    rmem_devices_before = {
+        line.strip() for line in stdout_before if line.strip().startswith('rmem')
+    }
+
+    create_cmd = f'nvmem -c {DEFAULT_DEVICE_SIZE}'
+    barebox.run_check(create_cmd)
+
+    stdout_after, _, _ = barebox.run('nvmem')
+    rmem_devices_after = {
+        line.strip() for line in stdout_after if line.strip().startswith('rmem')
+    }
+
+    newly_created_devices = rmem_devices_after - rmem_devices_before
+    if not newly_created_devices:
+        pytest.fail("Failed to identify newly created NVMEM device: "
+                    "No new 'rmem' devices found in 'nvmem' list.")
+
+    actual_device_name_listed = ""
+    if len(newly_created_devices) > 1:
+        for dev_name in newly_created_devices:
+            if re.match(r"rmem\d+0$", dev_name):
+                actual_device_name_listed = dev_name
+                break
+        if not actual_device_name_listed:
+            # Fallback if no "rmemX0" found among multiple new devices
+            actual_device_name_listed = list(newly_created_devices)[0]
+    else:
+        actual_device_name_listed = list(newly_created_devices)[0]
+
+    # Derive the cdev name (e.g., "rmem0") from the listed name (e.g., "rmem00")
+    cdev_name_match = re.match(r"(rmem\d+)0$", actual_device_name_listed)
+    if cdev_name_match:
+        final_cdev_name = cdev_name_match.group(1)
+    else:
+        # If it doesn't end with '0' (e.g. it's already 'rmemX')
+        cdev_name_match_direct = re.match(r"(rmem\d+)$", actual_device_name_listed)
+        if not cdev_name_match_direct:
+            pytest.fail(
+                f"Could not derive cdev base name from listed name "
+                f"'{actual_device_name_listed}' using patterns (rmemX)0 or (rmemX)."
+            )
+        final_cdev_name = cdev_name_match_direct.group(1)
+    yield final_cdev_name
+
+
+# --- Test Functions ---
+
+# == NVMEM Command Tests ==
+def test_nvmem_list_empty_or_static(barebox, barebox_config):
+    """
+    Tests the `nvmem` command without arguments.
+    It should successfully execute and list any currently available NVMEM devices
+    (which might include static ones or be empty if none are configured/created).
+
+    Manual Reproduction:
+    1. In Barebox shell, run: `nvmem`
+    2. Observe the output. The command should not error.
+    """
+    _, _, returncode = barebox.run('nvmem')
+    assert returncode == 0, "`nvmem` command failed"
+
+def test_nvmem_create_dynamic_device(barebox, barebox_config):
+    """
+    Tests creating a dynamic NVMEM rmem device using `nvmem -c <size>`.
+    It verifies that after creation, a new rmem device appears in the `nvmem` list.
+
+    Manual Reproduction:
+    1. In Barebox shell, run: `nvmem` (to see initial list)
+    2. Run: `nvmem -c 1k`
+    3. Run: `nvmem` (again, to see the new device, e.g., "rmem00")
+    """
+    stdout_before, _, _ = barebox.run('nvmem')
+    rmem_devices_before = {
+        line.strip() for line in stdout_before if line.strip().startswith('rmem')
+    }
+    barebox.run_check(f'nvmem -c 1k') # Create a 1KB device
+    stdout_after, _, returncode = barebox.run('nvmem')
+    assert returncode == 0, "`nvmem` command failed after creating a device"
+    rmem_devices_after = {
+        line.strip() for line in stdout_after if line.strip().startswith('rmem')
+    }
+    new_devices = rmem_devices_after - rmem_devices_before
+    assert len(new_devices) == 1, (
+        f"Expected 1 new rmem device, found {len(new_devices)}. "
+        f"New: {new_devices}"
+    )
+    new_device_name = list(new_devices)[0]
+    assert re.match(r"rmem\d+0?$", new_device_name), (
+        f"New device name '{new_device_name}' does not match expected "
+        "'rmemX' or 'rmemX0' pattern."
+    )
+
+def test_nvmem_create_dynamic_device_with_var(barebox, barebox_config):
+    """
+    Tests `nvmem -c <size> -v <varname>` functionality.
+    It verifies:
+    1. The specified environment variable is set to the created device's cdev name (e.g., "rmem1").
+    2. The device (e.g., "rmem1" or "rmem10") is listed by the `nvmem` command.
+    This test uses a compound command to ensure the `echo` sees the variable set by `nvmem -v`.
+
+    Manual Reproduction:
+    1. In Barebox shell, run: `nvmem` (to see current rmem device count, e.g., rmem0 exists)
+    2. Run: `nvmem -c 2k -v MYVAR` (this should create, e.g., rmem1)
+    3. Run: `echo ${MYVAR}` (should output, e.g., "rmem1")
+    4. Run: `nvmem` (should list the new device, e.g., "rmem10")
+    """
+    var_to_set_in_barebox = "VTESTVAR_COMPOUND"
+
+    stdout_list_b, _, _ = barebox.run('nvmem')
+    # Count existing 'rmem' entries to predict the next index.
+    num_current_rmem = sum(1 for l in stdout_list_b if l.strip().startswith('rmem'))
+    expected_dev_name_in_var = f"rmem{num_current_rmem}" # e.g., rmem0, rmem1
+
+    # Compound command to create device and echo the variable in the same shell context
+    compound_cmd = (
+        f'nvmem -c 2k -v {var_to_set_in_barebox} && '
+        f'echo ${{{var_to_set_in_barebox}}}'
+    )
+
+    stdout_compound_raw = barebox.run_check(compound_cmd)
+
+    actual_dev_name_in_var = stdout_compound_raw[0].strip() if stdout_compound_raw else "'' (compound cmd gave no stdout lines)"
+
+    assert actual_dev_name_in_var == expected_dev_name_in_var, (
+        f"Environment variable {var_to_set_in_barebox} (from compound cmd) was "
+        f"'{actual_dev_name_in_var}', expected "
+        f"'{expected_dev_name_in_var}'"
+    )
+
+    # Verify the device is listed by `nvmem`
+    stdout_list_a, _, returncode = barebox.run('nvmem')
+    assert returncode == 0
+
+    # The 'nvmem' list might show "rmemX0" while the var stores "rmemX".
+    # The pattern checks for this.
+    list_pattern_check = actual_dev_name_in_var
+    pattern = re.compile(f"^{re.escape(list_pattern_check)}0?$")
+    assert any(pattern.match(line.strip()) for line in stdout_list_a), (
+        f"Device corresponding to '{list_pattern_check}' (pattern: '{pattern.pattern}') "
+        f"not found in `nvmem` list. List was: {stdout_list_a}"
+    )
+
+
+def test_nvmem_create_invalid_size_zero(barebox, barebox_config):
+    """
+    Tests `nvmem -c 0` (creating a device with zero size).
+    This operation should fail and the command should return a non-zero exit status
+    and print an error message.
+
+    Manual Reproduction:
+    1. In Barebox shell, run: `nvmem -c 0`
+    2. Observe the error message (e.g., "Error: Invalid size '0'...")
+    3. Run: `echo $?` (should be non-zero)
+    """
+    stdout, _, returncode = barebox.run('nvmem -c 0')
+    assert returncode != 0, "`nvmem -c 0` should fail (non-zero return code)"
+    assert any("Error: Invalid size '0'" in line for line in stdout), \
+        "Expected error message for zero size not found in stdout."
+
+def test_nvmem_create_invalid_size_too_large(barebox, barebox_config):
+    """
+    Tests `nvmem -c <size>` where size exceeds NVMEM_MAX_DYNAMIC_SIZE.
+    This operation should fail, return a non-zero exit status, and print an error.
+
+    Manual Reproduction:
+    1. In Barebox shell, run: `nvmem -c 2000000` (assuming 2MB > NVMEM_MAX_SIZE)
+    2. Observe the error message (e.g., "Error: Size '...' exceeds maximum...")
+    3. Run: `echo $?` (should be non-zero)
+    """
+    invalid_size = NVMEM_MAX_DYNAMIC_SIZE + 1
+    stdout, stderr, returncode = barebox.run(f'nvmem -c {invalid_size}')
+
+    assert returncode != 0, (
+        f"`nvmem -c {invalid_size}` was expected to fail (non-zero returncode), "
+        f"but got {returncode}."
+    )
+    assert any(f"Error: Size '{invalid_size}' exceeds maximum" in line
+               for line in stdout), (
+        f"Expected error message for excessive size not found in stdout. "
+        f"Stdout was: {stdout!r}"
+    )
+
+
+def test_nvmem_option_v_without_c(barebox, barebox_config):
+    """
+    Tests using `nvmem -v <varname>` without the required `-c <size>` option.
+    This should be a usage error, returning a non-zero exit status and an error message.
+
+    Manual Reproduction:
+    1. In Barebox shell, run: `nvmem -v somevar`
+    2. Observe the error message (e.g., "Error: -v <VARNAME> option requires -c <SIZE>...")
+    3. Run: `echo $?` (should be non-zero)
+    """
+    stdout, _, returncode = barebox.run('nvmem -v somevarname')
+    assert returncode != 0, "`nvmem -v` without `-c` should fail"
+    assert any("Error: -v <VARNAME> option requires -c <SIZE>" in line
+               for line in stdout), \
+        "Expected error for missing -c with -v not found."
+
+def test_nvmem_help_on_h_option(barebox, barebox_config):
+    """
+    Tests `nvmem -h` for displaying help/usage information.
+    Typically, help options result in a non-zero exit status after printing usage.
+
+    Manual Reproduction:
+    1. In Barebox shell, run: `nvmem -h`
+    2. Observe the usage information.
+    3. Run: `echo $?` (often non-zero for help options)
+    """
+    stdout, _, returncode = barebox.run('nvmem -h')
+    assert returncode != 0, "`nvmem -h` should return non-zero"
+    assert any("Usage: nvmem" in line for line in stdout) or \
+           any("Options:" in line for line in stdout), \
+           "Help output (Usage:/Options:) not found for `nvmem -h`."
+
+# == CDEV Read/Write Tests (using mw and md) ==
+def test_nvmem_cdev_mw_md_long(barebox, barebox_config,
+                               created_nvmem_device_name):
+    """
+    Tests writing a 32-bit long value to an NVMEM device using `mw -l`
+    and reading it back using `md -l` for verification.
+
+    Manual Reproduction (assuming rmem0 is created):
+    1. Create device: `nvmem -c 1k` (note the device name, e.g., rmem0)
+    2. Write: `mw -d /dev/rmem0 -l 0x170 0x12345678`
+    3. Read: `md -s /dev/rmem0 -l 0x170+4` (verify output shows 12345678)
+    """
+    actual_dev_name = created_nvmem_device_name
+
+    if not actual_dev_name:
+        pytest.fail(f"Fixture created_nvmem_device_name returned empty. Cannot proceed.")
+
+    dev_path = f"/dev/{actual_dev_name}"
+
+    test_val_hex = "0x12345678"
+    test_addr_hex = "0x170"
+
+    mw_cmd = f'mw -d {dev_path} -l {test_addr_hex} {test_val_hex}'
+    try:
+        barebox.run_check(mw_cmd)
+    except Exception as e:
+        pytest.fail(f"mw command '{mw_cmd}' failed: {e}")
+
+    md_cmd = f'md -s {dev_path} -l {test_addr_hex}+4'
+    stdout_md = barebox.run_check(md_cmd)
+
+    read_val_int = parse_md_long_value(stdout_md)
+    assert read_val_int is not None, (
+        f"Failed to parse long value from md output: {stdout_md}"
+    )
+    assert read_val_int == int(test_val_hex, 16), \
+        "Read long value does not match written value."
+
+
+def test_nvmem_cdev_mw_md_bytes_string(barebox, barebox_config,
+                                      created_nvmem_device_name):
+    """
+    Tests writing a string as a sequence of bytes to an NVMEM device using `mw -b`
+    and reading it back using `md -b`, then verifying the string content.
+
+    Manual Reproduction (assuming rmem0 is created):
+    1. Create device: `nvmem -c 1k`
+    2. Write "Test": `mw -d /dev/rmem0 -b 0x40 0x54 0x65 0x73 0x74`
+    3. Read: `md -s /dev/rmem0 -b 0x40+4` (verify ASCII part shows "Test")
+    """
+    actual_dev_name = created_nvmem_device_name
+    if not actual_dev_name:
+         pytest.fail(f"Fixture created_nvmem_device_name returned empty for test_nvmem_cdev_mw_md_bytes_string.")
+    dev_path = f"/dev/{actual_dev_name}"
+
+    test_string = "BareboxNVMEMTest" # Length 16
+    hex_bytes_for_mw_cmd = string_to_hex_bytes_for_mw(test_string)
+    num_bytes = len(test_string)
+    test_addr_hex = "0x40"
+
+    mw_cmd = f'mw -d {dev_path} -b {test_addr_hex} {hex_bytes_for_mw_cmd}'
+    try:
+        barebox.run_check(mw_cmd)
+    except Exception as e:
+        pytest.fail(f"mw command failed in test_nvmem_cdev_mw_md_bytes_string: {e}")
+
+    md_cmd = f'md -s {dev_path} -b {test_addr_hex}+{num_bytes}'
+    stdout_md = barebox.run_check(md_cmd)
+
+    read_string = parse_md_bytes_to_string(stdout_md, num_bytes)
+
+    assert read_string is not None, (
+        f"Failed to parse byte string from md output: {stdout_md}"
+    )
+    assert read_string == test_string, \
+        "Read byte string does not match written string."
+
+def test_nvmem_cdev_mw_md_long_bytes_string_wrapped(barebox, barebox_config,
+                                                   created_nvmem_device_name):
+    """
+    Tests writing a longer string (34 bytes) to an NVMEM device,
+    which is likely to cause `md -b` output to wrap over multiple lines.
+    Verifies that the parser correctly handles wrapped output.
+
+    Manual Reproduction (assuming rmem0 is created):
+    1. Create device: `nvmem -c 1k`
+    2. Write 34-byte string (e.g., "ThisIsALongerTestStringForNVMEM012"):
+       `mw -d /dev/rmem0 -b 0x80 0x54 0x68 ... 0x32` (full hex string)
+    3. Read: `md -s /dev/rmem0 -b 0x80+34` (observe wrapped output and verify ASCII)
+    """
+    actual_dev_name = created_nvmem_device_name
+    if not actual_dev_name:
+         pytest.fail(f"Fixture created_nvmem_device_name returned empty for test_nvmem_cdev_mw_md_long_bytes_string_wrapped.")
+    dev_path = f"/dev/{actual_dev_name}"
+
+    test_string = "ThisIsALongerTestStringForNVMEM012"
+    assert len(test_string) == 34
+
+    hex_bytes_for_mw_cmd = string_to_hex_bytes_for_mw(test_string)
+    num_bytes = len(test_string)
+    test_addr_hex = "0x80"
+
+    mw_cmd = f'mw -d {dev_path} -b {test_addr_hex} {hex_bytes_for_mw_cmd}'
+    try:
+        barebox.run_check(mw_cmd)
+    except Exception as e:
+        pytest.fail(f"mw command failed in test_nvmem_cdev_mw_md_long_bytes_string_wrapped: {e}")
+
+    md_cmd = f'md -s {dev_path} -b {test_addr_hex}+{num_bytes}'
+    stdout_md = barebox.run_check(md_cmd)
+
+    read_string = parse_md_bytes_to_string(stdout_md, num_bytes)
+
+    assert read_string is not None, (
+        f"Failed to parse byte string from md output (wrapped test): {stdout_md}"
+    )
+    assert read_string == test_string, \
+        "Read byte string (wrapped test) does not match written string."
+
+# --- New tests for different parameters ---
+def test_nvmem_cdev_mw_md_word(barebox, barebox_config, created_nvmem_device_name):
+    """
+    Tests writing a 16-bit word value to an NVMEM device using `mw -w`
+    and reading it back using `md -w`.
+
+    Manual Reproduction (assuming rmem0 is created):
+    1. Create device: `nvmem -c 1k`
+    2. Write: `mw -d /dev/rmem0 -w 0xB0 0xABCD`
+    3. Read: `md -s /dev/rmem0 -w 0xB0+2` (verify output shows ABCD)
+    """
+    actual_dev_name = created_nvmem_device_name
+    if not actual_dev_name:
+         pytest.fail(f"Fixture created_nvmem_device_name returned empty for test_nvmem_cdev_mw_md_word.")
+    dev_path = f"/dev/{actual_dev_name}"
+
+    test_val_hex = "0xABCD"
+    test_addr_hex = "0xB0"
+
+    mw_cmd = f'mw -d {dev_path} -w {test_addr_hex} {test_val_hex}'
+    try:
+        barebox.run_check(mw_cmd)
+    except Exception as e:
+        pytest.fail(f"mw -w command failed: {e}")
+
+    md_cmd = f'md -s {dev_path} -w {test_addr_hex}+2'
+    stdout_md = barebox.run_check(md_cmd)
+
+    read_val_int = parse_md_word_value(stdout_md)
+
+    assert read_val_int is not None, (
+        f"Failed to parse word value from md output: {stdout_md}"
+    )
+    assert read_val_int == int(test_val_hex, 16), \
+        "Read word value does not match written value."
+
+def test_nvmem_cdev_mw_md_long_swapped(barebox, barebox_config, created_nvmem_device_name):
+    """
+    Tests writing a 32-bit long value with byte swapping (`mw -l -x`)
+    and verifies the content by reading back with and without byte swap (`md -l` and `md -l -x`).
+
+    Manual Reproduction (assuming rmem0 is created):
+    1. Create device: `nvmem -c 1k`
+    2. Write with swap: `mw -d /dev/rmem0 -l -x 0xC0 0x12345678`
+    3. Read without swap: `md -s /dev/rmem0 -l 0xC0+4` (should show 78563412)
+    4. Read with swap: `md -s /dev/rmem0 -l -x 0xC0+4` (should show 12345678)
+    """
+    actual_dev_name = created_nvmem_device_name
+    if not actual_dev_name:
+         pytest.fail(f"Fixture created_nvmem_device_name returned empty for test_nvmem_cdev_mw_md_long_swapped.")
+    dev_path = f"/dev/{actual_dev_name}"
+
+    original_val_hex = "0x12345678"
+    swapped_val_on_device_hex = "0x78563412"
+    test_addr_hex = "0xC0"
+
+    mw_cmd = f'mw -d {dev_path} -l -x {test_addr_hex} {original_val_hex}'
+    try:
+        barebox.run_check(mw_cmd)
+    except Exception as e:
+        pytest.fail(f"mw -l -x command failed: {e}")
+
+    md_cmd_no_swap = f'md -s {dev_path} -l {test_addr_hex}+4'
+    stdout_md_no_swap = barebox.run_check(md_cmd_no_swap)
+    read_val_no_swap = parse_md_long_value(stdout_md_no_swap)
+    assert read_val_no_swap is not None, \
+        f"Failed to parse md (no swap) output: {stdout_md_no_swap}"
+    assert read_val_no_swap == int(swapped_val_on_device_hex, 16), \
+        "Reading without swap did not yield the byte-swapped value."
+
+    md_cmd_with_swap = f'md -s {dev_path} -l -x {test_addr_hex}+4'
+    stdout_md_with_swap = barebox.run_check(md_cmd_with_swap)
+    read_val_with_swap = parse_md_long_value(stdout_md_with_swap)
+    assert read_val_with_swap is not None, \
+        f"Failed to parse md -x (with swap) output: {stdout_md_with_swap}"
+    assert read_val_with_swap == int(original_val_hex, 16), \
+        "Reading with swap did not yield the original value."
+
+
+# == Protection Mechanism Tests ==
+def test_nvmem_protect_then_write_fail(barebox, barebox_config,
+                                      created_nvmem_device_name):
+    """
+    Tests NVMEM protection:
+    1. Writes an initial value to a memory region.
+    2. Protects that region.
+    3. Attempts to write a new value to the protected region (should fail).
+    4. Verifies that the region still contains the original value.
+
+    Manual Reproduction (assuming rmem0 is created):
+    1. Create device: `nvmem -c 1k`
+    2. Write initial: `mw -d /dev/rmem0 -l 0x10 0x11223344`
+    3. Protect: `protect /dev/rmem0 0x10+4`
+    4. Attempt write (fail): `mw -d /dev/rmem0 -l 0x10 0xAABBCCDD` (observe error)
+    5. Verify: `md -s /dev/rmem0 -l 0x10+4` (should still be 11223344)
+    """
+    actual_dev_name = created_nvmem_device_name
+    if not actual_dev_name:
+         pytest.fail(f"Fixture created_nvmem_device_name returned empty for test_nvmem_protect_then_write_fail.")
+    dev_path = f"/dev/{actual_dev_name}"
+
+    addr_hex = "0x10"
+    length = 4
+    val_before_hex = "0x11223344"
+    val_attempt_hex = "0xAABBCCDD"
+    try:
+        barebox.run_check(
+            f'mw -d {dev_path} -l {addr_hex} {val_before_hex}'
+        )
+    except Exception as e:
+        pytest.fail(f"Initial mw command failed in test_nvmem_protect_then_write_fail: {e}")
+
+    stdout_protect, _, ret_protect = barebox.run(
+        f'protect {dev_path} {addr_hex}+{length}'
+    )
+    assert ret_protect == 0, "Protect command failed."
+
+    stdout_mw, stderr_mw, returncode_mw = barebox.run(
+        f'mw -d {dev_path} -l {addr_hex} {val_attempt_hex}'
+    )
+    assert returncode_mw != 0, "mw to protected area should fail."
+    full_output_mw = "".join(stdout_mw) + "".join(stderr_mw)
+    assert "overlaps with protected" in full_output_mw or \
+           "Read-only file system" in full_output_mw, \
+        "Expected error/warning for writing to protected area not found."
+
+    md_cmd_after = f'md -s {dev_path} -l {addr_hex}+{length}'
+    stdout_md_after = barebox.run_check(md_cmd_after)
+
+    read_val_after_int = parse_md_long_value(stdout_md_after)
+
+    if read_val_after_int is None:
+        pytest.fail(
+            f"Failed to parse 'md' output after failed write attempt. "
+            f"Raw 'md' output was: {stdout_md_after!r}"
+        )
+    assert read_val_after_int == int(val_before_hex, 16), \
+        "Value in protected area changed after failed write attempt."
+
+def test_nvmem_unprotect_then_write_ok(barebox, barebox_config,
+                                      created_nvmem_device_name):
+    """
+    Tests NVMEM unprotection:
+    1. Writes an initial value and protects a region.
+    2. Unprotects the region.
+    3. Writes a new value to the (now unprotected) region (should succeed).
+    4. Verifies the new value is present.
+
+    Manual Reproduction (assuming rmem0 is created):
+    1. Create device: `nvmem -c 1k`
+    2. Write initial: `mw -d /dev/rmem0 -l 0x20 0xDEADBEEF`
+    3. Protect: `protect /dev/rmem0 0x20+4`
+    4. Unprotect: `unprotect /dev/rmem0 0x20 4` (observe "Unprotected range..." log)
+    5. Write new: `mw -d /dev/rmem0 -l 0x20 0x12345678`
+    6. Verify: `md -s /dev/rmem0 -l 0x20+4` (should be 12345678)
+    """
+    actual_dev_name = created_nvmem_device_name
+    if not actual_dev_name:
+         pytest.fail(f"Fixture created_nvmem_device_name returned empty for test_nvmem_unprotect_then_write_ok.")
+    dev_path = f"/dev/{actual_dev_name}"
+
+    device_size = DEFAULT_DEVICE_SIZE
+    addr_hex = "0x20"
+    length = 4
+    val_initial_hex = "0xDEADBEEF"
+    val_after_unprotect_hex = "0x12345678"
+    try:
+        barebox.run_check(
+            f'mw -d {dev_path} -l {addr_hex} {val_initial_hex}'
+        )
+        barebox.run_check(f'protect {dev_path} {addr_hex}+{length}')
+    except Exception as e:
+        pytest.fail(f"Setup mw or protect failed in test_nvmem_unprotect_then_write_ok: {e}")
+
+    stdout_unprotect, _, ret_unprotect = barebox.run(
+        f'unprotect {dev_path} {addr_hex} {length}'
+    )
+    assert ret_unprotect == 0, "Unprotect command failed."
+    assert any(f"Unprotected range [0x0, len {device_size}]" in line
+               for line in stdout_unprotect), \
+        "Expected 'Unprotected range [0x0, len <size>]' log not found."
+
+    try:
+        barebox.run_check(
+            f'mw -d {dev_path} -l {addr_hex} {val_after_unprotect_hex}'
+        )
+    except Exception as e:
+        pytest.fail(f"Final mw command failed in test_nvmem_unprotect_then_write_ok: {e}")
+
+    stdout_md_final = barebox.run_check(
+        f'md -s {dev_path} -l {addr_hex}+{length}'
+    )
+    read_val_final_int = parse_md_long_value(stdout_md_final)
+    assert read_val_final_int == int(val_after_unprotect_hex, 16), \
+        "Value after unprotect and write is incorrect."
+
+def test_nvmem_protect_write_outside_range_ok(barebox, barebox_config,
+                                             created_nvmem_device_name):
+    """
+    Tests that writing outside a protected range is successful,
+    while the protected range itself remains inaccessible.
+
+    Manual Reproduction (assuming rmem0 is created):
+    1. Create device: `nvmem -c 1k`
+    2. Write initial to protected area: `mw -d /dev/rmem0 -l 0x30 0x00000000`
+    3. Protect: `protect /dev/rmem0 0x30+4`
+    4. Write outside: `mw -d /dev/rmem0 -l 0x50 0xABCDEF01`
+    5. Verify outside: `md -s /dev/rmem0 -l 0x50+4` (should be ABCDEF01)
+    6. Attempt write to protected (fail): `mw -d /dev/rmem0 -l 0x30 0xFFFFFFFF`
+    7. Verify protected unchanged: `md -s /dev/rmem0 -l 0x30+4` (should be 00000000)
+    """
+    actual_dev_name = created_nvmem_device_name
+    if not actual_dev_name:
+         pytest.fail(f"Fixture created_nvmem_device_name returned empty for test_nvmem_protect_write_outside_range_ok.")
+    dev_path = f"/dev/{actual_dev_name}"
+
+    protected_addr_hex = "0x30"
+    protected_len = 4
+    initial_val_prot_hex = "0x00000000"
+    write_addr_outside_hex = "0x50"
+    val_to_write_outside_hex = "0xABCDEF01"
+    try:
+        barebox.run_check(
+            f'mw -d {dev_path} -l {protected_addr_hex} '
+            f'{initial_val_prot_hex}'
+        )
+        barebox.run_check(
+            f'protect {dev_path} {protected_addr_hex}+{protected_len}'
+        )
+        barebox.run_check(
+            f'mw -d {dev_path} -l {write_addr_outside_hex} '
+            f'{val_to_write_outside_hex}'
+        )
+    except Exception as e:
+        pytest.fail(f"Setup command failed in test_nvmem_protect_write_outside_range_ok: {e}")
+
+    stdout_md_out = barebox.run_check(
+        f'md -s {dev_path} -l {write_addr_outside_hex}+4'
+    )
+    read_val_out_int = parse_md_long_value(stdout_md_out)
+    assert read_val_out_int == int(val_to_write_outside_hex, 16), \
+        "Value written outside protected range is incorrect."
+    _, _, ret_mw_prot = barebox.run(
+        f'mw -d {dev_path} -l {protected_addr_hex} 0xFFFFFFFF'
+    )
+    assert ret_mw_prot != 0, "Write to protected area should have failed."
+    stdout_md_prot = barebox.run_check(
+        f'md -s {dev_path} -l {protected_addr_hex}+{protected_len}'
+    )
+    read_val_prot_int = parse_md_long_value(stdout_md_prot)
+    assert read_val_prot_int == int(initial_val_prot_hex, 16), \
+        "Protected area content changed or was not initially set."
+
+def test_nvmem_write_span_unprotected_protected(barebox, barebox_config,
+                                               created_nvmem_device_name):
+    """
+    Tests writing a sequence of bytes (`mw -b`) that spans from an unprotected
+    area into a protected area. Expects the write to fail for bytes entering
+    the protected region, but bytes in the unprotected part might be written.
+
+    Manual Reproduction (assuming rmem0 is created):
+    1. Create device: `nvmem -c 1k`
+    2. Protect e.g. `0x10` to `0x13`: `protect /dev/rmem0 0x10+4`
+    3. Attempt to write 4 bytes `AA BB CC DD` starting at `0x0E`:
+       `mw -d /dev/rmem0 -b 0x0E 0xAA 0xBB 0xCC 0xDD`
+       (Expect `AA` at `0x0E`, `BB` at `0x0F` to be written. `CC` at `0x10` fails.)
+    4. Verify: `md -s /dev/rmem0 -b 0x0E+4` (should show `AA BB 00 00` if area was zeroed)
+    """
+    actual_dev_name = created_nvmem_device_name
+    if not actual_dev_name:
+         pytest.fail(f"Fixture created_nvmem_device_name returned empty for test_nvmem_write_span_unprotected_protected.")
+    dev_path = f"/dev/{actual_dev_name}"
+
+    prot_start_addr_val = 0x10
+    prot_start_addr_hex = f"0x{prot_start_addr_val:x}"
+    prot_len = 4
+    try:
+        barebox.run_check(
+            f'protect {dev_path} {prot_start_addr_hex}+{prot_len}'
+        )
+    except Exception as e:
+        pytest.fail(f"Protect command failed in test_nvmem_write_span_unprotected_protected: {e}")
+
+    write_start_addr_val = prot_start_addr_val - 2
+    write_start_addr_hex = f"0x{write_start_addr_val:x}"
+    hex_vals_to_write = "0xAA 0xBB 0xCC 0xDD"
+    write_span_len = 4
+    stdout_mw, stderr_mw, returncode_mw = barebox.run(
+        f'mw -d {dev_path} -b {write_start_addr_hex} '
+        f'{hex_vals_to_write}'
+    )
+    assert returncode_mw != 0, "mw spanning protected boundary should fail."
+    full_output_mw = "".join(stdout_mw) + "".join(stderr_mw)
+    assert "overlaps with protected" in full_output_mw or \
+           "Read-only file system" in full_output_mw, \
+        "Expected error/warning for spanning write not found."
+    stdout_md_verify = barebox.run_check(
+        f'md -s {dev_path} -b {write_start_addr_hex}+{write_span_len}'
+    )
+    read_hex_verify = parse_md_output(stdout_md_verify, 'b')
+    assert read_hex_verify == "aabb0000", \
+        "Data in spanned region is not as expected after partial write."
+
+def test_nvmem_write_at_start_and_end_exact(barebox, barebox_config,
+                                           created_nvmem_device_name):
+    """
+    Tests writing exactly at the start (offset 0) and filling up to the
+    exact end of the NVMEM device. Also tests that writing 1 byte past
+    the end fails.
+
+    Manual Reproduction (assuming rmem0 is created, size 1024 bytes):
+    1. Create device: `nvmem -c 1024`
+    2. Write at start: `mw -d /dev/rmem0 -l 0x0 0x1A2B3C4D`
+    3. Verify: `md -s /dev/rmem0 -l 0x0+4`
+    4. Write at end (last 4 bytes): `mw -d /dev/rmem0 -l 0x3FC 0x5E6F7A8B` (1020 = 0x3FC)
+    5. Verify: `md -s /dev/rmem0 -l 0x3FC+4`
+    6. Attempt write past end: `mw -d /dev/rmem0 -b 0x400 0xFF` (should fail)
+    """
+    actual_dev_name = created_nvmem_device_name
+    if not actual_dev_name:
+         pytest.fail(f"Fixture created_nvmem_device_name returned empty for test_nvmem_write_at_start_and_end_exact.")
+    dev_path = f"/dev/{actual_dev_name}"
+
+    device_size = DEFAULT_DEVICE_SIZE
+    val_start_hex = "0x1A2B3C4D"
+    addr_start_hex = "0x0"
+    try:
+        barebox.run_check(
+            f'mw -d {dev_path} -l {addr_start_hex} {val_start_hex}'
+        )
+    except Exception as e:
+        pytest.fail(f"mw to start of device failed in test_nvmem_write_at_start_and_end_exact: {e}")
+
+    stdout_md_start = barebox.run_check(
+        f'md -s {dev_path} -l {addr_start_hex}+4'
+    )
+    assert parse_md_long_value(stdout_md_start) == int(val_start_hex, 16), \
+        "Value at device start is incorrect."
+    val_end_hex = "0x5E6F7A8B"
+    addr_end_val = device_size - 4
+    addr_end_hex = f"0x{addr_end_val:x}"
+    try:
+        barebox.run_check(
+            f'mw -d {dev_path} -l {addr_end_hex} {val_end_hex}'
+        )
+    except Exception as e:
+        pytest.fail(f"mw to end of device failed in test_nvmem_write_at_start_and_end_exact: {e}")
+
+    stdout_md_end = barebox.run_check(
+        f'md -s {dev_path} -l {addr_end_hex}+4'
+    )
+    assert parse_md_long_value(stdout_md_end) == int(val_end_hex, 16), \
+        "Value at device end is incorrect."
+    addr_past_end_val = device_size
+    addr_past_end_hex = f"0x{addr_past_end_val:x}"
+    _, _, ret_mw_past = barebox.run(
+        f'mw -d {dev_path} -b {addr_past_end_hex} 0xFF'
+    )
+    assert ret_mw_past != 0, "mw write past end should fail."
+
+def test_nvmem_protect_beyond_end_fail(barebox, barebox_config,
+                                      created_nvmem_device_name):
+    """
+    Tests attempting to protect a range that extends beyond the device's
+    boundaries or starts entirely outside. Expects these operations to fail
+    with a non-zero return code from the `protect` command.
+
+    Manual Reproduction (assuming rmem0 is created, size 1024 bytes):
+    1. Create device: `nvmem -c 1k`
+    2. Case 1 (partially out): `protect /dev/rmem0 0x3f0+32`
+       Expected: Command fails, `echo $?` is non-zero. Observe error message.
+    3. Case 2 (fully out): `protect /dev/rmem0 0x400+4`
+       Expected: Command fails, `echo $?` is non-zero. Observe error message.
+    """
+    actual_dev_name = created_nvmem_device_name
+    if not actual_dev_name:
+         pytest.fail(f"Fixture created_nvmem_device_name returned empty for test_nvmem_protect_beyond_end_fail.")
+    dev_path = f"/dev/{actual_dev_name}"
+
+    device_size = DEFAULT_DEVICE_SIZE
+    # Case 1: Protection starts within bounds but length extends beyond.
+    prot_addr1_hex = f"0x{device_size - 16:x}"
+    prot_len1_requested = 32
+
+    protect_cmd1 = f'protect {dev_path} {prot_addr1_hex}+{prot_len1_requested}'
+    stdout_protect1, stderr_protect1, ret1 = barebox.run(protect_cmd1)
+
+    assert ret1 != 0, (
+        f"Protect partially beyond boundary expected to fail (non-zero return), "
+        f"but got {ret1}. Stdout: {stdout_protect1!r}, Stderr: {stderr_protect1!r}"
+    )
+    # Error message check removed as per user request.
+
+    # Case 2: Protection starts entirely outside the device.
+    prot_addr2_hex = f"0x{device_size:x}"
+    prot_len2 = 4
+    protect_cmd2 = f'protect {dev_path} {prot_addr2_hex}+{prot_len2}'
+    stdout_protect2, stderr_protect2, ret2 = barebox.run(protect_cmd2)
+
+    assert ret2 != 0, (
+        f"Protect starting entirely beyond boundary expected to fail (non-zero return), "
+        f"but got {ret2}. Stdout: {stdout_protect2!r}, Stderr: {stderr_protect2!r}"
+    )
+    # Error message check removed as per user request.
+
+
+def test_nvmem_protect_zero_length_noop(barebox, barebox_config,
+                                       created_nvmem_device_name):
+    """
+    Tests protecting a range with zero length (`protect <dev> <addr>+0`).
+    This is expected to be an invalid operation and should fail with a non-zero
+    return code. The area should remain writable.
+
+    Manual Reproduction (assuming rmem0 is created):
+    1. Create device: `nvmem -c 1k`
+    2. Attempt protect with zero length: `protect /dev/rmem0 0x50+0`
+       Expected: Command fails, `echo $?` is non-zero. Observe error message.
+    3. Verify writable: `mw -d /dev/rmem0 -l 0x50 0xCAFED00D` (should succeed)
+    4. Read back: `md -s /dev/rmem0 -l 0x50+4` (should show CAFED00D)
+    """
+    actual_dev_name = created_nvmem_device_name
+    if not actual_dev_name:
+         pytest.fail(f"Fixture created_nvmem_device_name returned empty for test_nvmem_protect_zero_length_noop.")
+    dev_path = f"/dev/{actual_dev_name}"
+
+    addr_hex = "0x50"
+    val_hex = "0xCAFED00D"
+    protect_cmd = f'protect {dev_path} {addr_hex}+0'
+    stdout_prot, stderr_prot, ret_prot = barebox.run(protect_cmd)
+
+    assert ret_prot != 0, "Protect with zero length should now fail (return non-zero)."
+    # Error message check removed as per user request.
+
+    # Since protection should have failed, the area should still be writable.
+    try:
+        barebox.run_check(
+            f'mw -d {dev_path} -l {addr_hex} {val_hex}'
+        )
+    except Exception as e:
+        pytest.fail(f"mw command failed in test_nvmem_protect_zero_length_noop after failed protect: {e}")
+
+    stdout_md = barebox.run_check(
+        f'md -s {dev_path} -l {addr_hex}+4'
+    )
+    assert parse_md_long_value(stdout_md) == int(val_hex, 16), \
+        "Value should be writable after failed zero-length protect."
+
+def test_nvmem_protect_merge_overlapping(barebox, barebox_config,
+                                        created_nvmem_device_name):
+    """
+    Tests if two overlapping `protect` calls result in a single, correctly
+    merged protected range. The log message from `rmem.c` should reflect
+    the final merged range.
+
+    Manual Reproduction (assuming rmem0 is created):
+    1. Create device: `nvmem -c 1k`
+    2. Protect [0x10, len 4]: `protect /dev/rmem0 0x10+4` (observe log)
+    3. Protect [0x12, len 4]: `protect /dev/rmem0 0x12+4`
+       (observe log, should show merged range, e.g., [0x10, len 6])
+    4. Attempt writes to 0x10 and 0x14 (should fail).
+    """
+    actual_dev_name = created_nvmem_device_name
+    if not actual_dev_name:
+         pytest.fail(f"Fixture created_nvmem_device_name returned empty for test_nvmem_protect_merge_overlapping.")
+    dev_path = f"/dev/{actual_dev_name}"
+
+    addr1_hex, len1 = "0x10", 4
+    stdout_p1, _, ret_p1 = barebox.run(
+        f'protect {dev_path} {addr1_hex}+{len1}'
+    )
+    assert ret_p1 == 0, "First protect call failed."
+    assert any(f"Protected range [{addr1_hex}, len {len1}]" in line
+               for line in stdout_p1), "Log for first protection incorrect."
+    addr2_hex, len2 = "0x12", 4
+    exp_merged_addr_hex, exp_merged_len = "0x10", 6
+    stdout_p2, _, ret_p2 = barebox.run(
+        f'protect {dev_path} {addr2_hex}+{len2}'
+    )
+    assert ret_p2 == 0, "Second (overlapping) protect call failed."
+    _, _, ret_mw_0x10 = barebox.run(f'mw -d {dev_path} -b 0x10 0xFF')
+    assert ret_mw_0x10 != 0, "Write to start of merged range (0x10) fail."
+    _, _, ret_mw_0x14 = barebox.run(f'mw -d {dev_path} -b 0x14 0xFF')
+    assert ret_mw_0x14 != 0, "Write to middle of merged range (0x14) fail."
+    barebox.run_check(f'mw -d {dev_path} -b 0x0F 0xAA')
+    barebox.run_check(f'mw -d {dev_path} -b 0x16 0xBB')
+
+def test_nvmem_mw_multiple_bytes_single_command(barebox, barebox_config,
+                                               created_nvmem_device_name):
+    """
+    Tests `mw -b` with multiple hexadecimal byte values provided in a single command line,
+    e.g., `mw -d /dev/rmem0 -b 0x90 0x4F 0x4B`.
+
+    Manual Reproduction (assuming rmem0 is created):
+    1. Create device: `nvmem -c 1k`
+    2. Write "OK": `mw -d /dev/rmem0 -b 0x90 0x4F 0x4B`
+    3. Verify: `md -s /dev/rmem0 -b 0x90+2` (should show "OK")
+    """
+    actual_dev_name = created_nvmem_device_name
+    if not actual_dev_name:
+         pytest.fail(f"Fixture created_nvmem_device_name returned empty for test_nvmem_mw_multiple_bytes_single_command.")
+    dev_path = f"/dev/{actual_dev_name}"
+
+    hex_bytes_for_mw_cmd = "0x4F 0x4B"
+    num_bytes = 2
+    test_addr_hex = "0x90"
+    try:
+        barebox.run_check(
+            f'mw -d {dev_path} -b {test_addr_hex} {hex_bytes_for_mw_cmd}'
+        )
+    except Exception as e:
+        pytest.fail(f"mw command failed in test_nvmem_mw_multiple_bytes_single_command: {e}")
+
+    stdout_md = barebox.run_check(
+        f'md -s {dev_path} -b {test_addr_hex}+{num_bytes}'
+    )
+    read_hex_string = parse_md_output(stdout_md, 'b')
+    expected_hex_string = "4f4b"
+    assert read_hex_string == expected_hex_string, (
+        f"Read hex string '{read_hex_string}' does not match expected "
+        f"'{expected_hex_string}'."
+    )
+
+def test_nvmem_unprotect_noop_if_not_protected(barebox, barebox_config,
+                                             created_nvmem_device_name):
+    """
+    Tests that calling `unprotect` on a device with no active protections
+    is a no-op and succeeds. It should still log the standard "Unprotected range..."
+    message for the whole device.
+
+    Manual Reproduction (assuming rmem0 is created):
+    1. Create device: `nvmem -c 1k` (ensure no protections are set on it)
+    2. Run: `unprotect /dev/rmem0 0x0 1024`
+       Expected: Command succeeds, prints "Unprotected range [0x0, len 1024]"
+    """
+    actual_dev_name = created_nvmem_device_name
+    if not actual_dev_name:
+         pytest.fail(f"Fixture created_nvmem_device_name returned empty for test_nvmem_unprotect_noop_if_not_protected.")
+    dev_path = f"/dev/{actual_dev_name}"
+
+    device_size = DEFAULT_DEVICE_SIZE
+    stdout_unprot, _, ret_unprot = barebox.run(
+        f'unprotect {dev_path} 0x0 {device_size}'
+    )
+    assert ret_unprot == 0, "Unprotect should succeed if no ranges protected."
+    assert any(f"Unprotected range [0x0, len {device_size}]" in line
+               for line in stdout_unprot), \
+        "Expected 'Unprotected range' log for whole device not found."
-- 
2.39.5




^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2025-06-05  7:49 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-06-05  7:47 [PATCH v2 0/9] NVMEM: Introduce write protection support Oleksij Rempel
2025-06-05  7:47 ` [PATCH v2 1/9] nvmem: Add 'protect' operation to core framework Oleksij Rempel
2025-06-05  7:47 ` [PATCH v2 2/9] nvmem: rmem: add write and protect support Oleksij Rempel
2025-06-05  7:47 ` [PATCH v2 3/9] commands: nvmem: Add support for creating dynamic rmem devices Oleksij Rempel
2025-06-05  7:47 ` [PATCH v2 4/9] regmap: Add reg_seal operation for hardware protection Oleksij Rempel
2025-06-05  7:47 ` [PATCH v2 5/9] nvmem: regmap: Implement protect operation using regmap_seal Oleksij Rempel
2025-06-05  7:47 ` [PATCH v2 6/9] nvmem: bsec: Implement NVMEM protect via regmap_seal for OTP locking Oleksij Rempel
2025-06-05  7:47 ` [PATCH v2 7/9] nvmem: rmem: generate unic device name Oleksij Rempel
2025-06-05  7:47 ` [PATCH v2 8/9] fs: Report errors for out-of-bounds protect operations Oleksij Rempel
2025-06-05  7:47 ` [PATCH v2 9/9] test: Add pytest suite for NVMEM framework Oleksij Rempel

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox