From: Hongmei Xu <hongmei.xu@oss.qualcomm.com>
To: "Srinivas Kandagatla" <srini@kernel.org>,
"Rob Herring" <robh@kernel.org>,
"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
"Conor Dooley" <conor+dt@kernel.org>,
"Rafał Miłecki" <rafal@milecki.pl>
Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
Hongmei Xu <hongmei.xu@oss.qualcomm.com>
Subject: [PATCH 2/2] nvmem: core: add byte-mask support for non-contiguous cell bytes
Date: Fri, 13 Nov 2026 15:52:37 +0800 [thread overview]
Message-ID: <20261113-nvmem-byte-mask-for-split-macaddr-v1-2-138f715f18c1@oss.qualcomm.com> (raw)
In-Reply-To: <20261113-nvmem-byte-mask-for-split-macaddr-v1-0-138f715f18c1@oss.qualcomm.com>
Add support for a byte-mask cell property that selects non-contiguous
physical bytes from an NVMEM device region to form a logical cell value.
When byte-mask is set, the entire NVMEM device region is read starting
from the cell's base offset. Bit N set in the mask means physical byte N
is included; selected bytes are packed into the output buffer in
ascending byte order. If fewer bytes are selected than the cell's
logical size given by reg, the remaining output bytes are zero-filled.
Two checks are performed at cell registration:
- The number of set bits must not exceed the cell's logical size (reg),
to prevent output buffer overflow.
- The highest set bit must not reference a byte beyond the NVMEM device
size.
Signed-off-by: Hongmei Xu <hongmei.xu@oss.qualcomm.com>
---
drivers/nvmem/core.c | 101 ++++++++++++++++++++++++++++++++++++++---
include/linux/nvmem-provider.h | 7 +++
2 files changed, 101 insertions(+), 7 deletions(-)
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 7d4cce8e2742..6c9994abce39 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -33,6 +33,7 @@ struct nvmem_cell_entry {
int bytes;
int bit_offset;
int nbits;
+ u32 byte_mask;
nvmem_cell_post_process_t read_post_process;
void *priv;
struct device_node *np;
@@ -589,6 +590,47 @@ static void nvmem_cell_entry_add(struct nvmem_cell_entry *cell)
blocking_notifier_call_chain(&nvmem_notifier, NVMEM_CELL_ADD, cell);
}
+static int nvmem_validate_byte_mask(struct nvmem_device *nvmem,
+ const struct nvmem_cell_info *info)
+{
+ int out_bytes;
+
+ if (!info->byte_mask)
+ return 0;
+
+ if (info->nbits || info->bit_offset) {
+ dev_err(&nvmem->dev,
+ "cell %s byte-mask and bits are mutually exclusive\n",
+ info->name ?: "<unknown>");
+ return -EINVAL;
+ }
+
+ if (info->offset >= nvmem->size) {
+ dev_err(&nvmem->dev,
+ "cell %s offset %u exceeds device size %zu\n",
+ info->name ?: "<unknown>", info->offset, nvmem->size);
+ return -EINVAL;
+ }
+
+ out_bytes = hweight32(info->byte_mask);
+ if (out_bytes > info->bytes) {
+ dev_err(&nvmem->dev,
+ "cell %s byte-mask set-bit count %d exceeds reg size %d\n",
+ info->name ?: "<unknown>", out_bytes, info->bytes);
+ return -EINVAL;
+ }
+
+ if (fls(info->byte_mask) > nvmem->size - info->offset) {
+ dev_err(&nvmem->dev,
+ "cell %s byte-mask bit %d exceeds device size %zu\n",
+ info->name ?: "<unknown>",
+ fls(info->byte_mask) - 1, nvmem->size);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int nvmem_cell_info_to_nvmem_cell_entry_nodup(struct nvmem_device *nvmem,
const struct nvmem_cell_info *info,
struct nvmem_cell_entry *cell)
@@ -603,12 +645,19 @@ static int nvmem_cell_info_to_nvmem_cell_entry_nodup(struct nvmem_device *nvmem,
cell->bit_offset = info->bit_offset;
cell->nbits = info->nbits;
+ cell->byte_mask = info->byte_mask;
cell->np = info->np;
if (cell->nbits) {
cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset,
BITS_PER_BYTE);
cell->raw_len = ALIGN(cell->bytes, nvmem->word_size);
+ } else if (cell->byte_mask) {
+ int rc = nvmem_validate_byte_mask(nvmem, info);
+
+ if (rc)
+ return rc;
+ cell->raw_len = nvmem->size - cell->offset;
}
if (!IS_ALIGNED(cell->offset, nvmem->stride)) {
@@ -804,7 +853,7 @@ int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np)
{
struct device *dev = &nvmem->dev;
const __be32 *addr;
- int len, ret;
+ int len, ret = 0;
for_each_child_of_node_scoped(np, child) {
struct nvmem_cell_info info = {0};
@@ -829,16 +878,27 @@ int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np)
info.nbits < 1 ||
info.bit_offset + info.nbits > BITS_PER_BYTE * info.bytes) {
dev_err(dev, "nvmem: invalid bits on %pOF\n", child);
- return -EINVAL;
+ ret = -EINVAL;
+ goto err_free_name;
}
}
+ of_property_read_u32(child, "byte-mask", &info.byte_mask);
+
info.np = of_node_get(child);
+ if (info.byte_mask) {
+ ret = nvmem_validate_byte_mask(nvmem, &info);
+ if (ret)
+ goto err_free_name;
+ }
+
if (nvmem->fixup_dt_cell_info)
nvmem->fixup_dt_cell_info(nvmem, &info);
ret = nvmem_add_one_cell(nvmem, &info);
+
+err_free_name:
kfree(info.name);
if (ret) {
of_node_put(info.np);
@@ -1648,28 +1708,51 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
struct nvmem_cell_entry *cell,
void *buf, size_t *len, const char *id, int index)
{
+ u8 *rawbuf = NULL;
+ void *readbuf = buf;
int rc;
- rc = nvmem_reg_read(nvmem, cell->offset, buf, cell->raw_len);
+ if (cell->byte_mask) {
+ rawbuf = kzalloc(cell->raw_len, GFP_KERNEL);
+ if (!rawbuf)
+ return -ENOMEM;
+ readbuf = rawbuf;
+ }
+
+ rc = nvmem_reg_read(nvmem, cell->offset, readbuf, cell->raw_len);
if (rc)
- return rc;
+ goto err_exit;
/* shift bits in-place */
if (cell->bit_offset || cell->nbits)
nvmem_shift_read_buffer_in_place(cell, buf);
+ /* compact non-contiguous bytes selected by byte_mask */
+ if (cell->byte_mask) {
+ int out_idx = 0;
+ int i;
+
+ for (i = 0; i < cell->raw_len && i < 32; i++) {
+ if (cell->byte_mask & BIT(i))
+ ((u8 *)buf)[out_idx++] = rawbuf[i];
+ }
+ }
+
if (cell->read_post_process) {
rc = cell->read_post_process(cell->priv, id, index,
- cell->offset, buf, cell->raw_len);
+ cell->offset, buf,
+ cell->byte_mask ? cell->bytes : cell->raw_len);
if (rc)
- return rc;
+ goto err_exit;
}
if (len)
*len = cell->bytes;
- return 0;
+err_exit:
+ kfree(rawbuf);
+ return rc;
}
/**
@@ -1776,6 +1859,10 @@ static int __nvmem_cell_entry_write(struct nvmem_cell_entry *cell, void *buf, si
if (cell->read_post_process)
return -EINVAL;
+ /* byte-mask cells have no scatter-write implementation yet */
+ if (cell->byte_mask)
+ return -EINVAL;
+
if (cell->bit_offset || cell->nbits) {
if (len != BITS_TO_BYTES(cell->nbits) && len != cell->bytes)
return -EINVAL;
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index 6063fe5b7784..c9891595aac5 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -57,6 +57,12 @@ struct nvmem_keepout {
* @bytes: Length of the cell.
* @bit_offset: Bit offset if cell is smaller than a byte.
* @nbits: Number of bits.
+ * @byte_mask: Bitmask selecting physical bytes to include in the output.
+ * Bit N=1 means physical byte N relative to the cell's base
+ * offset is packed into the output buffer; bit N=0 means skip.
+ * The entire NVMEM device region from the cell's offset is read,
+ * only bytes within the first 32 bytes may be selected.
+ * unselected output positions are zero-filled. Zero means no masking.
* @np: Optional device_node pointer.
* @read_post_process: Callback for optional post processing of cell data
* on reads.
@@ -69,6 +75,7 @@ struct nvmem_cell_info {
unsigned int bytes;
unsigned int bit_offset;
unsigned int nbits;
+ u32 byte_mask;
struct device_node *np;
nvmem_cell_post_process_t read_post_process;
void *priv;
--
2.34.1
prev parent reply other threads:[~2026-11-13 7:52 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-13 7:52 [PATCH 0/2] nvmem: Add byte-mask support for non-contiguous byte selection Hongmei Xu
2026-11-13 7:52 ` [PATCH 1/2] dt-bindings: nvmem: add byte-mask property " Hongmei Xu
2026-11-13 7:52 ` Hongmei Xu [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261113-nvmem-byte-mask-for-split-macaddr-v1-2-138f715f18c1@oss.qualcomm.com \
--to=hongmei.xu@oss.qualcomm.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=rafal@milecki.pl \
--cc=robh@kernel.org \
--cc=srini@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®