mail archive of the barebox mailing list
 help / color / mirror / Atom feed
* [PATCH v1 0/7] NVMEM: Introduce write protection support
@ 2025-05-30 11:40 Oleksij Rempel
  2025-05-30 11:41 ` [PATCH v1 1/7] nvmem: Add 'protect' operation to core framework Oleksij Rempel
                   ` (6 more replies)
  0 siblings, 7 replies; 8+ messages in thread
From: Oleksij Rempel @ 2025-05-30 11:40 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 (7):
  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: Use unique device name for NVMEM registration

 commands/nvmem.c               | 124 +++++++++-
 drivers/base/regmap/internal.h |   2 +
 drivers/base/regmap/regmap.c   |  31 +++
 drivers/nvmem/bsec.c           |  27 ++
 drivers/nvmem/core.c           |  48 ++++
 drivers/nvmem/partition.c      |   7 +
 drivers/nvmem/regmap.c         |  65 +++++
 drivers/nvmem/rmem.c           | 438 ++++++++++++++++++++++++++++++++-
 include/linux/nvmem-provider.h |   5 +
 include/linux/regmap.h         |  33 +++
 include/mach/stm32mp/bsec.h    |   1 +
 11 files changed, 774 insertions(+), 7 deletions(-)

--
2.39.5




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

* [PATCH v1 1/7] nvmem: Add 'protect' operation to core framework
  2025-05-30 11:40 [PATCH v1 0/7] NVMEM: Introduce write protection support Oleksij Rempel
@ 2025-05-30 11:41 ` Oleksij Rempel
  2025-05-30 11:41 ` [PATCH v1 2/7] nvmem: rmem: add write and protect support Oleksij Rempel
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Oleksij Rempel @ 2025-05-30 11:41 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>
---
 drivers/nvmem/core.c           | 48 ++++++++++++++++++++++++++++++++++
 drivers/nvmem/partition.c      |  7 +++++
 include/linux/nvmem-provider.h |  5 ++++
 3 files changed, 60 insertions(+)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 38dfb2cf2d1f..ac54a56f3c9f 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 {
@@ -93,9 +95,54 @@ 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;
+	int nvmem_prot;
+
+	if (cdev->master)
+		nvmem = container_of(cdev->master, struct nvmem_device, cdev);
+	else
+		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 >= nvmem->size || count > nvmem->size - offset) {
+		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;
+	}
+
+	switch (prot) {
+	case 0:
+		nvmem_prot = NVMEM_PROTECT_ENABLE_WRITE;
+		break;
+	case 1:
+		nvmem_prot = NVMEM_PROTECT_DISABLE_WRITE;
+		break;
+	default:
+		dev_err(cdev->dev, "unsuported protection type %d\n", prot);
+		return -EINVAL;
+	}
+
+	return nvmem->reg_protect(nvmem->priv, offset, count, nvmem_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)
@@ -213,6 +260,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..991f4f9d2fa0 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -17,6 +17,9 @@
 
 struct nvmem_device;
 
+#define NVMEM_PROTECT_ENABLE_WRITE	0
+#define NVMEM_PROTECT_DISABLE_WRITE	1
+
 /* 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,
@@ -34,6 +37,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] 8+ messages in thread

* [PATCH v1 2/7] nvmem: rmem: add write and protect support
  2025-05-30 11:40 [PATCH v1 0/7] NVMEM: Introduce write protection support Oleksij Rempel
  2025-05-30 11:41 ` [PATCH v1 1/7] nvmem: Add 'protect' operation to core framework Oleksij Rempel
@ 2025-05-30 11:41 ` Oleksij Rempel
  2025-05-30 11:41 ` [PATCH v1 3/7] commands: nvmem: Add support for creating dynamic rmem devices Oleksij Rempel
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Oleksij Rempel @ 2025-05-30 11:41 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:
  - NVMEM_PROTECT_DISABLE_WRITE: Marks range read-only. Merges
    overlapping/adjacent protected ranges.

  - NVMEM_PROTECT_ENABLE_WRITE: Makes range writable. Splits/shrinks
    existing protected ranges as needed.

  - Internal list (protected_ranges_list) and helpers manage read-only
    regions (overlap, adjacency, split, merge logic).

  - Probe function updated for new ops and list initialization.

The core range protection logic is generic and could be a candidate for
a future common NVMEM helper library.

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

diff --git a/drivers/nvmem/rmem.c b/drivers/nvmem/rmem.c
index afa0dd78c8f4..be3203de73d3 100644
--- a/drivers/nvmem/rmem.c
+++ b/drivers/nvmem/rmem.c
@@ -8,9 +8,17 @@
 #include <linux/nvmem-provider.h>
 #include <init.h>
 
+struct protected_range {
+	unsigned int offset;
+	size_t len;
+	struct list_head node;
+};
+
 struct rmem {
 	struct device *dev;
 	const struct resource *mem;
+	size_t total_size;
+	struct list_head protected_ranges_list;
 };
 
 static int rmem_read(void *context, unsigned int offset,
@@ -21,6 +29,429 @@ static int rmem_read(void *context, unsigned int offset,
 			bytes, offset, 0);
 }
 
+/**
+ * rmem_ranges_overlap - Check if two memory ranges overlap.
+ * @offset1: Starting offset of the first range.
+ * @len1:    Length of the first range.
+ * @offset2: Starting offset of the second range.
+ * @len2:    Length of the second range.
+ *
+ * Compares two memory ranges, defined by their starting offsets and
+ * lengths, to determine if they have any common region. Ranges with
+ * zero length are considered non-overlapping.
+ *
+ * The ranges are treated as inclusive, i.e., a range starting at @offset
+ * with length @len covers all bytes from @offset to @offset + @len - 1.
+ *
+ * Return: True if the ranges overlap, false otherwise.
+ */
+static bool rmem_ranges_overlap(unsigned int offset1, size_t len1,
+				unsigned int offset2, size_t len2)
+{
+	unsigned int end1_inclusive;
+	unsigned int end2_inclusive;
+
+	/* A range with zero length cannot overlap with anything. */
+	if (len1 == 0 || len2 == 0)
+		return false;
+
+	/* Calculate the inclusive end points of the ranges.
+	 * It's assumed that offset + len - 1 will not cause an
+	 * arithmetic overflow that wraps around in a problematic way
+	 * for typical NVMEM sizes and offsets.
+	 */
+	end1_inclusive = offset1 + len1 - 1;
+	end2_inclusive = offset2 + len2 - 1;
+
+	/*
+	 * Two ranges [A, B] and [C, D] overlap if A <= D and C <= B.
+	 * Here:
+	 * Range 1 is [offset1, end1_inclusive]
+	 * Range 2 is [offset2, end2_inclusive]
+	 */
+	return (offset1 <= end2_inclusive && offset2 <= end1_inclusive);
+}
+
+/**
+ * rmem_write - Write data to the NVMEM device.
+ * @context:	Pointer to the rmem private data (passed as void*).
+ * @offset:	Offset within the NVMEM device to write to.
+ * @val:	Buffer containing the data to write.
+ * @bytes:	Number of bytes to write.
+ *
+ * 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;
+	struct protected_range *pr;
+
+	/* Check against protected ranges */
+	list_for_each_entry(pr, &rmem->protected_ranges_list, node) {
+		if (rmem_ranges_overlap(offset, bytes, pr->offset, pr->len)) {
+			dev_warn(rmem->dev,
+				 "Write [0x%x, len %zu] overlaps with protected [0x%x, len %zu]\n",
+				 offset, bytes, pr->offset, pr->len);
+			return -EROFS;
+		}
+	}
+
+	return mem_copy(rmem->dev, (void *)rmem->mem->start + offset, val,
+			bytes, 0 /*offset_in_dest*/, 0 /*offset_in_val*/);
+}
+
+/**
+ * rmem_are_ranges_adjacent - Check if two memory ranges are adjacent.
+ * @offset1:	Offset of the first range.
+ * @len1:	Length of the first range.
+ * @offset2:	Offset of the second range.
+ * @len2:	Length of the second range.
+ *
+ * Ranges are adjacent if one ends immediately before the other begins.
+ * Zero-length ranges are not considered adjacent to anything.
+ *
+ * Return: True if the ranges are adjacent, false otherwise.
+ */
+static bool rmem_are_ranges_adjacent(unsigned int offset1, size_t len1,
+				     unsigned int offset2, size_t len2)
+{
+	unsigned int end1_inclusive, end2_inclusive;
+
+	if (len1 == 0 || len2 == 0)
+		return false;
+
+	end1_inclusive = offset1 + len1 - 1;
+	end2_inclusive = offset2 + len2 - 1;
+
+	if (end1_inclusive != ~0U && offset2 == end1_inclusive + 1)
+		return true; /* range2 is immediately after range1 */
+	if (end2_inclusive != ~0U && offset1 == end2_inclusive + 1)
+		return true; /* range1 is immediately after range2 */
+
+	return false;
+}
+
+/**
+ * rmem_protect_range - 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.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ *
+ * This function adds a new protected range. If the new range overlaps or
+ * is adjacent to existing protected ranges, they are merged into a single,
+ * larger protected range.
+ */
+static int rmem_protect_range(struct rmem *rmem, unsigned int offset,
+			      size_t bytes)
+{
+	struct protected_range *pr, *tmp_pr;
+	unsigned int current_prot_offset = offset;
+	size_t current_prot_len = bytes;
+	bool overlaps, adjacent;
+
+	/*
+	 * Iterate and merge with existing overlapping or adjacent ranges.
+	 * The new range
+	 * [current_prot_offset, current_prot_offset + current_prot_len - 1]
+	 * might grow by merging with existing protected ranges.
+	 */
+	list_for_each_entry_safe(pr, tmp_pr, &rmem->protected_ranges_list,
+				 node) {
+		overlaps = rmem_ranges_overlap(current_prot_offset,
+					       current_prot_len,
+					       pr->offset, pr->len);
+		adjacent = rmem_are_ranges_adjacent(current_prot_offset,
+						    current_prot_len,
+						    pr->offset, pr->len);
+
+		if (overlaps || adjacent) {
+			unsigned int merged_offset, merged_end_incl,
+				     pr_end_inclusive,
+				     current_prot_end_inclusive;
+
+			/*
+			 * Calculate the inclusive end of the existing protected
+			 * range ('pr') that we are currently examining in the
+			 * list. For example, if pr->offset is 10 and pr->len
+			 * is 5, the range is [10, 11, 12, 13, 14], so
+			 * pr_end_inclusive is 14.
+			 */
+			pr_end_inclusive = pr->offset + pr->len - 1;
+			/*
+			 * Calculate the inclusive end of the
+			 * 'current_prot_range'. This is the range we are trying
+			 * to add or the result of previous merges.
+			 */
+			current_prot_end_inclusive =
+				current_prot_offset + current_prot_len - 1;
+			/*
+			 * Determine the starting offset of the new, combined
+			 * (merged) range. This will be the minimum of the start
+			 * of the 'current_prot_range' and the start of the
+			 * existing protected range 'pr'.
+			 */
+			merged_offset = min(current_prot_offset, pr->offset);
+			/*
+			 * Determine the ending offset (inclusive) of the new,
+			 * combined range. This will be the maximum of the end
+			 * of the 'current_prot_range' and the end of the
+			 * existing protected range 'pr'.
+			 */
+			merged_end_incl = max(current_prot_end_inclusive,
+						   pr_end_inclusive);
+			/*
+			 * Update the 'current_prot_range' to reflect the merge.
+			 * The new offset is the calculated merged_offset.
+			 */
+			current_prot_offset = merged_offset;
+			/*
+			 * The new length is calculated from the merged start
+			 * and end. Length = (inclusive_end - start_offset) + 1.
+			 */
+			current_prot_len = merged_end_incl - merged_offset + 1;
+
+			/*
+			 * The existing protected range 'pr' has now been fully
+			 * incorporated into the (potentially expanded)
+			 * 'current_prot_range'. Therefore, 'pr' can be removed
+			 * from the list and its memory freed.
+			 */
+			list_del(&pr->node);
+			kfree(pr);
+		}
+	}
+
+	/* Add the final merged range */
+	pr = kzalloc(sizeof(*pr), GFP_KERNEL);
+	if (!pr)
+		return -ENOMEM;
+
+	pr->offset = current_prot_offset;
+	pr->len = current_prot_len;
+	list_add_tail(&pr->node, &rmem->protected_ranges_list);
+
+	dev_info(rmem->dev, "Protected range [0x%x, len %zu]\n",
+		 pr->offset, pr->len);
+	return 0;
+}
+
+/**
+ * rmem_create_sub_range - Helper to create a new protected_range structure.
+ * @offset: Offset of the sub-range.
+ * @len: Length of the sub-range.
+ * @new_ranges_list: List to add the new range to if successfully created.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+static int rmem_create_sub_range(unsigned int offset, size_t len,
+				 struct list_head *new_ranges_list)
+{
+	struct protected_range *part;
+
+	if (len == 0)
+		return 0;
+
+	part = kzalloc(sizeof(*part), GFP_KERNEL);
+	if (!part)
+		return -ENOMEM;
+
+	part->offset = offset;
+	part->len = len;
+	list_add_tail(&part->node, new_ranges_list);
+
+	return 0;
+}
+
+/**
+ * rmem_handle_unprotect_overlap - Adjust protected ranges due to unprotection.
+ * @pr_to_split: The existing protected range that overlaps with the
+ *		 unprotection request.
+ * @unprot_offset: Starting offset of the unprotection request.
+ * @unprot_len: Length of the unprotection request.
+ * @newly_created_ranges: List to add newly created (split) sub-ranges to.
+ *
+ * This function takes an existing protected range (@pr_to_split) that is known
+ * to overlap with an unprotection request. It removes @pr_to_split and creates
+ * up to two new protected ranges representing the parts of @pr_to_split that
+ * do *not* overlap with the unprotection request. These new sub-ranges are
+ * added to the @newly_created_ranges list.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int rmem_handle_unprotect_overlap(struct protected_range *pr_to_split,
+					 unsigned int unprot_offset,
+					 size_t unprot_len,
+					 struct list_head *newly_created_ranges)
+{
+	unsigned int p_orig_offset, p_orig_end_inclusive, unprot_end_inclusive;
+	unsigned int right_offset = 0;
+	size_t right_len = 0;
+	size_t left_len = 0;
+	size_t p_orig_len;
+	int ret = 0;
+
+	/* Store original properties of the range to be split */
+	p_orig_offset = pr_to_split->offset;
+	p_orig_len = pr_to_split->len;
+
+	/* Calculate inclusive end of the original protected range */
+	p_orig_end_inclusive = p_orig_offset + p_orig_len - 1;
+	/* Calculate inclusive end of the unprotection range */
+	unprot_end_inclusive = unprot_offset + unprot_len - 1;
+
+	/* The original protected range 'pr_to_split' is being affected.
+	 * It will be replaced by zero, one, or two new sub-ranges.
+	 * Remove it from the list and free its memory.
+	 */
+	list_del(&pr_to_split->node);
+	kfree(pr_to_split);
+
+	/*
+	 * Calculate the properties of the 'left part'.
+	 * This is the portion of the original protected range (P_orig)
+	 * that lies entirely before the unprotection region (U) starts.
+	 * P_orig: [p_orig_offset, ..., p_orig_end_inclusive]
+	 * U:      [unprot_offset, ..., unprot_end_inclusive]
+	 *
+	 * If p_orig_offset is less than unprot_offset, there's a potential left
+	 * part.
+	 * Example: P_orig = [10, len 10] (ends at 19), U = [15, len 3]
+	 *          (ends at 17)
+	 * Left part: offset 10, len (15 - 10) = 5.  [10,11,12,13,14]
+	 */
+	if (p_orig_offset < unprot_offset)
+		left_len = unprot_offset - p_orig_offset;
+
+	/*
+	 * Calculate the properties of the 'right part'.
+	 * This is the portion of the original protected range (P_orig)
+	 * that lies entirely after the unprotection region (U) ends.
+	 *
+	 * If p_orig_end_inclusive is greater than unprot_end_inclusive,
+	 * there's a potential right part.
+	 * Example: P_orig = [10, len 10] (ends at 19), U = [12, len 3]
+	 *          (ends at 14)
+	 * Right part: offset (14 + 1) = 15, len (19 - 14) = 5. [15,16,17,18,19]
+	 */
+	if (p_orig_end_inclusive > unprot_end_inclusive) {
+		right_offset = unprot_end_inclusive + 1;
+		right_len = p_orig_end_inclusive - unprot_end_inclusive;
+	}
+
+	/* Attempt to create and add the left sub-range, if it exists
+	 * (left_len > 0)
+	 */
+	ret = rmem_create_sub_range(p_orig_offset, left_len,
+				    newly_created_ranges);
+	if (ret)
+		return ret;
+
+	/* Attempt to create and add the right sub-range, if it exists
+	 * (right_len > 0)
+	 */
+	ret = rmem_create_sub_range(right_offset, right_len,
+				    newly_created_ranges);
+	return ret;
+}
+
+/**
+ * rmem_unprotect_range - Mark a memory range as writable (remove protection).
+ * @rmem:	Pointer to the rmem private data.
+ * @offset:	Starting offset of the range to unprotect.
+ * @bytes:	Length of the range to unprotect.
+ *
+ * Return: 0 on success, -ENOMEM if memory allocation fails.
+ *
+ * This function removes protection from the specified range. If this range
+ * overlaps with existing protected ranges, those ranges may be split or
+ * shrunk.
+ */
+static int rmem_unprotect_range(struct rmem *rmem, unsigned int offset,
+				size_t bytes)
+{
+	struct protected_range *pr, *tmp_pr;
+	int ret = 0;
+
+	/* Temporary list for ranges created by splitting existing protected
+	 * ranges
+	 */
+	LIST_HEAD(newly_created_ranges);
+
+	list_for_each_entry_safe(pr, tmp_pr, &rmem->protected_ranges_list,
+				 node) {
+		/* No overlap, this protected range is unaffected */
+		if (!rmem_ranges_overlap(pr->offset, pr->len, offset, bytes))
+			continue;
+
+		ret = rmem_handle_unprotect_overlap(pr, offset, bytes,
+						    &newly_created_ranges);
+		if (ret)
+			goto cleanup_new_ranges_unprotect;
+	}
+
+	/* Add all newly created (split) ranges to the main list */
+	list_splice_tail_init(&newly_created_ranges,
+			      &rmem->protected_ranges_list);
+
+	if (ret)
+		return ret;
+
+	dev_info(rmem->dev, "Unprotected range [0x%x, len %zu]\n",
+		 offset, bytes);
+	return 0;
+
+cleanup_new_ranges_unprotect:
+	/* Free any ranges allocated on the temporary list before the error
+	 * occurred
+	 */
+	list_for_each_entry_safe(pr, tmp_pr, &newly_created_ranges, node) {
+		list_del(&pr->node);
+		kfree(pr);
+	}
+	return ret;
+}
+
+/**
+ * rmem_protect - NVMEM operation to change protection status of a range.
+ * @context:	Pointer to the rmem private data (passed as void*).
+ * @offset:	Starting offset of the range.
+ * @bytes:	Length of the range.
+ * @prot:	Protection mode (NVMEM_PROT_READONLY or NVMEM_PROT_WRITABLE).
+ *
+ * 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)
+{
+	struct rmem *rmem = context;
+	int ret = 0;
+
+	switch (prot) {
+	case NVMEM_PROTECT_DISABLE_WRITE:
+		ret = rmem_protect_range(rmem, offset, bytes);
+		break;
+	case NVMEM_PROTECT_ENABLE_WRITE:
+		ret = rmem_unprotect_range(rmem, offset, bytes);
+		break;
+	default:
+		dev_warn(rmem->dev, "%s: Invalid protection mode %d\n",
+			 __func__, prot);
+		ret = -EINVAL;
+		break;
+	}
+
+	if (ret)
+		return ret;
+
+	dev_dbg(rmem->dev, "Protection operation completed for range [0x%x, len %zu], mode %d\n",
+		offset, bytes, prot);
+
+	return 0;
+}
+
 static int rmem_probe(struct device *dev)
 {
 	struct nvmem_config config = { };
@@ -36,12 +467,17 @@ static int rmem_probe(struct device *dev)
 		return -ENOMEM;
 
 	priv->mem = mem;
+	INIT_LIST_HEAD(&priv->protected_ranges_list);
 
 	config.dev = priv->dev = dev;
 	config.priv = priv;
 	config.name = "rmem";
 	config.size = resource_size(mem);
+	priv->total_size = config.size;
+
 	config.reg_read = rmem_read;
+	config.reg_write = rmem_write;
+	config.reg_protect = rmem_protect;
 
 	return PTR_ERR_OR_ZERO(nvmem_register(&config));
 }
-- 
2.39.5




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

* [PATCH v1 3/7] commands: nvmem: Add support for creating dynamic rmem devices
  2025-05-30 11:40 [PATCH v1 0/7] NVMEM: Introduce write protection support Oleksij Rempel
  2025-05-30 11:41 ` [PATCH v1 1/7] nvmem: Add 'protect' operation to core framework Oleksij Rempel
  2025-05-30 11:41 ` [PATCH v1 2/7] nvmem: rmem: add write and protect support Oleksij Rempel
@ 2025-05-30 11:41 ` Oleksij Rempel
  2025-05-30 11:41 ` [PATCH v1 4/7] regmap: Add reg_seal operation for hardware protection Oleksij Rempel
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Oleksij Rempel @ 2025-05-30 11:41 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>
---
 commands/nvmem.c | 124 ++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 118 insertions(+), 6 deletions(-)

diff --git a/commands/nvmem.c b/commands/nvmem.c
index a0e3d092e3cf..5f60e70e62c3 100644
--- a/commands/nvmem.c
+++ b/commands/nvmem.c
@@ -1,24 +1,136 @@
 // 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);
+	if (!buffer)
+		return -ENOMEM;
+
+	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 < 0)
+				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] 8+ messages in thread

* [PATCH v1 4/7] regmap: Add reg_seal operation for hardware protection
  2025-05-30 11:40 [PATCH v1 0/7] NVMEM: Introduce write protection support Oleksij Rempel
                   ` (2 preceding siblings ...)
  2025-05-30 11:41 ` [PATCH v1 3/7] commands: nvmem: Add support for creating dynamic rmem devices Oleksij Rempel
@ 2025-05-30 11:41 ` Oleksij Rempel
  2025-05-30 11:41 ` [PATCH v1 5/7] nvmem: regmap: Implement protect operation using regmap_seal Oleksij Rempel
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Oleksij Rempel @ 2025-05-30 11:41 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>
---
 drivers/base/regmap/internal.h |  2 ++
 drivers/base/regmap/regmap.c   | 31 +++++++++++++++++++++++++++++++
 include/linux/regmap.h         | 33 +++++++++++++++++++++++++++++++++
 3 files changed, 66 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..dc65b9e965ce 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
  *
@@ -124,6 +135,9 @@ struct regmap *regmap_init(struct device *dev,
 		}
 	}
 
+	if (!map->reg_seal)
+		map->reg_seal = _regmap_bus_reg_seal;
+
 	list_add_tail(&map->list, &regmaps);
 
 	return map;
@@ -186,6 +200,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] 8+ messages in thread

* [PATCH v1 5/7] nvmem: regmap: Implement protect operation using regmap_seal
  2025-05-30 11:40 [PATCH v1 0/7] NVMEM: Introduce write protection support Oleksij Rempel
                   ` (3 preceding siblings ...)
  2025-05-30 11:41 ` [PATCH v1 4/7] regmap: Add reg_seal operation for hardware protection Oleksij Rempel
@ 2025-05-30 11:41 ` Oleksij Rempel
  2025-05-30 11:41 ` [PATCH v1 6/7] nvmem: bsec: Implement NVMEM protect via regmap_seal for OTP locking Oleksij Rempel
  2025-05-30 11:41 ` [PATCH v1 7/7] nvmem: rmem: Use unique device name for NVMEM registration Oleksij Rempel
  6 siblings, 0 replies; 8+ messages in thread
From: Oleksij Rempel @ 2025-05-30 11:41 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..681cdf313e71 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 NVMEM_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 NVMEM_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] 8+ messages in thread

* [PATCH v1 6/7] nvmem: bsec: Implement NVMEM protect via regmap_seal for OTP locking
  2025-05-30 11:40 [PATCH v1 0/7] NVMEM: Introduce write protection support Oleksij Rempel
                   ` (4 preceding siblings ...)
  2025-05-30 11:41 ` [PATCH v1 5/7] nvmem: regmap: Implement protect operation using regmap_seal Oleksij Rempel
@ 2025-05-30 11:41 ` Oleksij Rempel
  2025-05-30 11:41 ` [PATCH v1 7/7] nvmem: rmem: Use unique device name for NVMEM registration Oleksij Rempel
  6 siblings, 0 replies; 8+ messages in thread
From: Oleksij Rempel @ 2025-05-30 11:41 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] 8+ messages in thread

* [PATCH v1 7/7] nvmem: rmem: Use unique device name for NVMEM registration
  2025-05-30 11:40 [PATCH v1 0/7] NVMEM: Introduce write protection support Oleksij Rempel
                   ` (5 preceding siblings ...)
  2025-05-30 11:41 ` [PATCH v1 6/7] nvmem: bsec: Implement NVMEM protect via regmap_seal for OTP locking Oleksij Rempel
@ 2025-05-30 11:41 ` Oleksij Rempel
  6 siblings, 0 replies; 8+ messages in thread
From: Oleksij Rempel @ 2025-05-30 11:41 UTC (permalink / raw)
  To: barebox; +Cc: Oleksij Rempel

Ensure that each rmem NVMEM instance is registered with a unique
name by using dev_name(dev) instead of a static "rmem" string
for config.name during probe.

Previously, all rmem instances would attempt to register with the
NVMEM framework using the same name ("rmem"). This caused issues
when multiple rmem devices were present, as the NVMEM framework
(and subsequently the char device layer) would create conflicting
device nodes or prevent the creation of additional rmem instances
beyond the first one.

By using the unique device name provided by dev_name(dev) (e.g.,
"rmem0", "rmem1"), each rmem instance is now distinctly identifiable
by the NVMEM core, allowing multiple rmem devices to be created and
used concurrently without naming collisions.

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 be3203de73d3..ddbfa766f03d 100644
--- a/drivers/nvmem/rmem.c
+++ b/drivers/nvmem/rmem.c
@@ -471,7 +471,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] 8+ messages in thread

end of thread, other threads:[~2025-05-30 11:44 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-05-30 11:40 [PATCH v1 0/7] NVMEM: Introduce write protection support Oleksij Rempel
2025-05-30 11:41 ` [PATCH v1 1/7] nvmem: Add 'protect' operation to core framework Oleksij Rempel
2025-05-30 11:41 ` [PATCH v1 2/7] nvmem: rmem: add write and protect support Oleksij Rempel
2025-05-30 11:41 ` [PATCH v1 3/7] commands: nvmem: Add support for creating dynamic rmem devices Oleksij Rempel
2025-05-30 11:41 ` [PATCH v1 4/7] regmap: Add reg_seal operation for hardware protection Oleksij Rempel
2025-05-30 11:41 ` [PATCH v1 5/7] nvmem: regmap: Implement protect operation using regmap_seal Oleksij Rempel
2025-05-30 11:41 ` [PATCH v1 6/7] nvmem: bsec: Implement NVMEM protect via regmap_seal for OTP locking Oleksij Rempel
2025-05-30 11:41 ` [PATCH v1 7/7] nvmem: rmem: Use unique device name for NVMEM registration Oleksij Rempel

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