nvmem: core: fix bit offsets of more than one byte
authorDmitry Baryshkov <dmitry.baryshkov@linaro.org>
Fri, 11 Apr 2025 11:22:46 +0000 (12:22 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 11 Apr 2025 12:41:21 +0000 (14:41 +0200)
If the NVMEM specifies a stride to access data, reading particular cell
might require bit offset that is bigger than one byte. Rework NVMEM core
code to support bit offsets of more than 8 bits.

Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20250411112251.68002-9-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/nvmem/core.c

index fff85bbf0ecd0f638e21f127370105d9f79c00d2..7872903c08a112f11618a5aa6a42ba505106ef6d 100644 (file)
@@ -837,7 +837,9 @@ static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_nod
                if (addr && len == (2 * sizeof(u32))) {
                        info.bit_offset = be32_to_cpup(addr++);
                        info.nbits = be32_to_cpup(addr);
-                       if (info.bit_offset >= BITS_PER_BYTE || info.nbits < 1) {
+                       if (info.bit_offset >= BITS_PER_BYTE * info.bytes ||
+                           info.nbits < 1 ||
+                           info.bit_offset + info.nbits > BITS_PER_BYTE * info.bytes) {
                                dev_err(dev, "nvmem: invalid bits on %pOF\n", child);
                                of_node_put(child);
                                return -EINVAL;
@@ -1630,21 +1632,29 @@ EXPORT_SYMBOL_GPL(nvmem_cell_put);
 static void nvmem_shift_read_buffer_in_place(struct nvmem_cell_entry *cell, void *buf)
 {
        u8 *p, *b;
-       int i, extra, bit_offset = cell->bit_offset;
+       int i, extra, bytes_offset;
+       int bit_offset = cell->bit_offset;
 
        p = b = buf;
-       if (bit_offset) {
+
+       bytes_offset = bit_offset / BITS_PER_BYTE;
+       b += bytes_offset;
+       bit_offset %= BITS_PER_BYTE;
+
+       if (bit_offset % BITS_PER_BYTE) {
                /* First shift */
-               *b++ >>= bit_offset;
+               *p = *b++ >> bit_offset;
 
                /* setup rest of the bytes if any */
                for (i = 1; i < cell->bytes; i++) {
                        /* Get bits from next byte and shift them towards msb */
-                       *p |= *b << (BITS_PER_BYTE - bit_offset);
+                       *p++ |= *b << (BITS_PER_BYTE - bit_offset);
 
-                       p = b;
-                       *b++ >>= bit_offset;
+                       *p = *b++ >> bit_offset;
                }
+       } else if (p != b) {
+               memmove(p, b, cell->bytes - bytes_offset);
+               p += cell->bytes - 1;
        } else {
                /* point to the msb */
                p += cell->bytes - 1;