|
|
@@ -0,0 +1,267 @@
|
|
|
+From fd3bb8f54a88107570334c156efb0c724a261003 Mon Sep 17 00:00:00 2001
|
|
|
+From: Evan Green <[email protected]>
|
|
|
+Date: Fri, 27 Nov 2020 10:28:34 +0000
|
|
|
+Subject: [PATCH] nvmem: core: Add support for keepout regions
|
|
|
+
|
|
|
+Introduce support into the nvmem core for arrays of register ranges
|
|
|
+that should not result in actual device access. For these regions a
|
|
|
+constant byte (repeated) is returned instead on read, and writes are
|
|
|
+quietly ignored and returned as successful.
|
|
|
+
|
|
|
+This is useful for instance if certain efuse regions are protected
|
|
|
+from access by Linux because they contain secret info to another part
|
|
|
+of the system (like an integrated modem).
|
|
|
+
|
|
|
+Signed-off-by: Evan Green <[email protected]>
|
|
|
+Signed-off-by: Srinivas Kandagatla <[email protected]>
|
|
|
+Link: https://lore.kernel.org/r/[email protected]
|
|
|
+Signed-off-by: Greg Kroah-Hartman <[email protected]>
|
|
|
+---
|
|
|
+ drivers/nvmem/core.c | 153 ++++++++++++++++++++++++++++++++-
|
|
|
+ include/linux/nvmem-provider.h | 17 ++++
|
|
|
+ 2 files changed, 166 insertions(+), 4 deletions(-)
|
|
|
+
|
|
|
+--- a/drivers/nvmem/core.c
|
|
|
++++ b/drivers/nvmem/core.c
|
|
|
+@@ -34,6 +34,8 @@ struct nvmem_device {
|
|
|
+ struct bin_attribute eeprom;
|
|
|
+ struct device *base_dev;
|
|
|
+ struct list_head cells;
|
|
|
++ const struct nvmem_keepout *keepout;
|
|
|
++ unsigned int nkeepout;
|
|
|
+ nvmem_reg_read_t reg_read;
|
|
|
+ nvmem_reg_write_t reg_write;
|
|
|
+ struct gpio_desc *wp_gpio;
|
|
|
+@@ -66,8 +68,8 @@ static LIST_HEAD(nvmem_lookup_list);
|
|
|
+
|
|
|
+ static BLOCKING_NOTIFIER_HEAD(nvmem_notifier);
|
|
|
+
|
|
|
+-static int nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
|
|
|
+- void *val, size_t bytes)
|
|
|
++static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
|
|
|
++ void *val, size_t bytes)
|
|
|
+ {
|
|
|
+ if (nvmem->reg_read)
|
|
|
+ return nvmem->reg_read(nvmem->priv, offset, val, bytes);
|
|
|
+@@ -75,8 +77,8 @@ static int nvmem_reg_read(struct nvmem_d
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+-static int nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset,
|
|
|
+- void *val, size_t bytes)
|
|
|
++static int __nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset,
|
|
|
++ void *val, size_t bytes)
|
|
|
+ {
|
|
|
+ int ret;
|
|
|
+
|
|
|
+@@ -90,6 +92,88 @@ static int nvmem_reg_write(struct nvmem_
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
++static int nvmem_access_with_keepouts(struct nvmem_device *nvmem,
|
|
|
++ unsigned int offset, void *val,
|
|
|
++ size_t bytes, int write)
|
|
|
++{
|
|
|
++
|
|
|
++ unsigned int end = offset + bytes;
|
|
|
++ unsigned int kend, ksize;
|
|
|
++ const struct nvmem_keepout *keepout = nvmem->keepout;
|
|
|
++ const struct nvmem_keepout *keepoutend = keepout + nvmem->nkeepout;
|
|
|
++ int rc;
|
|
|
++
|
|
|
++ /*
|
|
|
++ * Skip all keepouts before the range being accessed.
|
|
|
++ * Keepouts are sorted.
|
|
|
++ */
|
|
|
++ while ((keepout < keepoutend) && (keepout->end <= offset))
|
|
|
++ keepout++;
|
|
|
++
|
|
|
++ while ((offset < end) && (keepout < keepoutend)) {
|
|
|
++ /* Access the valid portion before the keepout. */
|
|
|
++ if (offset < keepout->start) {
|
|
|
++ kend = min(end, keepout->start);
|
|
|
++ ksize = kend - offset;
|
|
|
++ if (write)
|
|
|
++ rc = __nvmem_reg_write(nvmem, offset, val, ksize);
|
|
|
++ else
|
|
|
++ rc = __nvmem_reg_read(nvmem, offset, val, ksize);
|
|
|
++
|
|
|
++ if (rc)
|
|
|
++ return rc;
|
|
|
++
|
|
|
++ offset += ksize;
|
|
|
++ val += ksize;
|
|
|
++ }
|
|
|
++
|
|
|
++ /*
|
|
|
++ * Now we're aligned to the start of this keepout zone. Go
|
|
|
++ * through it.
|
|
|
++ */
|
|
|
++ kend = min(end, keepout->end);
|
|
|
++ ksize = kend - offset;
|
|
|
++ if (!write)
|
|
|
++ memset(val, keepout->value, ksize);
|
|
|
++
|
|
|
++ val += ksize;
|
|
|
++ offset += ksize;
|
|
|
++ keepout++;
|
|
|
++ }
|
|
|
++
|
|
|
++ /*
|
|
|
++ * If we ran out of keepouts but there's still stuff to do, send it
|
|
|
++ * down directly
|
|
|
++ */
|
|
|
++ if (offset < end) {
|
|
|
++ ksize = end - offset;
|
|
|
++ if (write)
|
|
|
++ return __nvmem_reg_write(nvmem, offset, val, ksize);
|
|
|
++ else
|
|
|
++ return __nvmem_reg_read(nvmem, offset, val, ksize);
|
|
|
++ }
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static int nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
|
|
|
++ void *val, size_t bytes)
|
|
|
++{
|
|
|
++ if (!nvmem->nkeepout)
|
|
|
++ return __nvmem_reg_read(nvmem, offset, val, bytes);
|
|
|
++
|
|
|
++ return nvmem_access_with_keepouts(nvmem, offset, val, bytes, false);
|
|
|
++}
|
|
|
++
|
|
|
++static int nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset,
|
|
|
++ void *val, size_t bytes)
|
|
|
++{
|
|
|
++ if (!nvmem->nkeepout)
|
|
|
++ return __nvmem_reg_write(nvmem, offset, val, bytes);
|
|
|
++
|
|
|
++ return nvmem_access_with_keepouts(nvmem, offset, val, bytes, true);
|
|
|
++}
|
|
|
++
|
|
|
+ #ifdef CONFIG_NVMEM_SYSFS
|
|
|
+ static const char * const nvmem_type_str[] = {
|
|
|
+ [NVMEM_TYPE_UNKNOWN] = "Unknown",
|
|
|
+@@ -535,6 +619,59 @@ nvmem_find_cell_by_name(struct nvmem_dev
|
|
|
+ return cell;
|
|
|
+ }
|
|
|
+
|
|
|
++static int nvmem_validate_keepouts(struct nvmem_device *nvmem)
|
|
|
++{
|
|
|
++ unsigned int cur = 0;
|
|
|
++ const struct nvmem_keepout *keepout = nvmem->keepout;
|
|
|
++ const struct nvmem_keepout *keepoutend = keepout + nvmem->nkeepout;
|
|
|
++
|
|
|
++ while (keepout < keepoutend) {
|
|
|
++ /* Ensure keepouts are sorted and don't overlap. */
|
|
|
++ if (keepout->start < cur) {
|
|
|
++ dev_err(&nvmem->dev,
|
|
|
++ "Keepout regions aren't sorted or overlap.\n");
|
|
|
++
|
|
|
++ return -ERANGE;
|
|
|
++ }
|
|
|
++
|
|
|
++ if (keepout->end < keepout->start) {
|
|
|
++ dev_err(&nvmem->dev,
|
|
|
++ "Invalid keepout region.\n");
|
|
|
++
|
|
|
++ return -EINVAL;
|
|
|
++ }
|
|
|
++
|
|
|
++ /*
|
|
|
++ * Validate keepouts (and holes between) don't violate
|
|
|
++ * word_size constraints.
|
|
|
++ */
|
|
|
++ if ((keepout->end - keepout->start < nvmem->word_size) ||
|
|
|
++ ((keepout->start != cur) &&
|
|
|
++ (keepout->start - cur < nvmem->word_size))) {
|
|
|
++
|
|
|
++ dev_err(&nvmem->dev,
|
|
|
++ "Keepout regions violate word_size constraints.\n");
|
|
|
++
|
|
|
++ return -ERANGE;
|
|
|
++ }
|
|
|
++
|
|
|
++ /* Validate keepouts don't violate stride (alignment). */
|
|
|
++ if (!IS_ALIGNED(keepout->start, nvmem->stride) ||
|
|
|
++ !IS_ALIGNED(keepout->end, nvmem->stride)) {
|
|
|
++
|
|
|
++ dev_err(&nvmem->dev,
|
|
|
++ "Keepout regions violate stride.\n");
|
|
|
++
|
|
|
++ return -EINVAL;
|
|
|
++ }
|
|
|
++
|
|
|
++ cur = keepout->end;
|
|
|
++ keepout++;
|
|
|
++ }
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
+ static int nvmem_add_cells_from_of(struct nvmem_device *nvmem)
|
|
|
+ {
|
|
|
+ struct device_node *parent, *child;
|
|
|
+@@ -655,6 +792,8 @@ struct nvmem_device *nvmem_register(cons
|
|
|
+ nvmem->type = config->type;
|
|
|
+ nvmem->reg_read = config->reg_read;
|
|
|
+ nvmem->reg_write = config->reg_write;
|
|
|
++ nvmem->keepout = config->keepout;
|
|
|
++ nvmem->nkeepout = config->nkeepout;
|
|
|
+ if (!config->no_of_node)
|
|
|
+ nvmem->dev.of_node = config->dev->of_node;
|
|
|
+
|
|
|
+@@ -679,6 +818,12 @@ struct nvmem_device *nvmem_register(cons
|
|
|
+ nvmem->dev.groups = nvmem_dev_groups;
|
|
|
+ #endif
|
|
|
+
|
|
|
++ if (nvmem->nkeepout) {
|
|
|
++ rval = nvmem_validate_keepouts(nvmem);
|
|
|
++ if (rval)
|
|
|
++ goto err_put_device;
|
|
|
++ }
|
|
|
++
|
|
|
+ dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name);
|
|
|
+
|
|
|
+ rval = device_register(&nvmem->dev);
|
|
|
+--- a/include/linux/nvmem-provider.h
|
|
|
++++ b/include/linux/nvmem-provider.h
|
|
|
+@@ -31,6 +31,19 @@ enum nvmem_type {
|
|
|
+ #define NVMEM_DEVID_AUTO (-2)
|
|
|
+
|
|
|
+ /**
|
|
|
++ * struct nvmem_keepout - NVMEM register keepout range.
|
|
|
++ *
|
|
|
++ * @start: The first byte offset to avoid.
|
|
|
++ * @end: One beyond the last byte offset to avoid.
|
|
|
++ * @value: The byte to fill reads with for this region.
|
|
|
++ */
|
|
|
++struct nvmem_keepout {
|
|
|
++ unsigned int start;
|
|
|
++ unsigned int end;
|
|
|
++ unsigned char value;
|
|
|
++};
|
|
|
++
|
|
|
++/**
|
|
|
+ * struct nvmem_config - NVMEM device configuration
|
|
|
+ *
|
|
|
+ * @dev: Parent device.
|
|
|
+@@ -39,6 +52,8 @@ enum nvmem_type {
|
|
|
+ * @owner: Pointer to exporter module. Used for refcounting.
|
|
|
+ * @cells: Optional array of pre-defined NVMEM cells.
|
|
|
+ * @ncells: Number of elements in cells.
|
|
|
++ * @keepout: Optional array of keepout ranges (sorted ascending by start).
|
|
|
++ * @nkeepout: Number of elements in the keepout array.
|
|
|
+ * @type: Type of the nvmem storage
|
|
|
+ * @read_only: Device is read-only.
|
|
|
+ * @root_only: Device is accessibly to root only.
|
|
|
+@@ -66,6 +81,8 @@ struct nvmem_config {
|
|
|
+ struct gpio_desc *wp_gpio;
|
|
|
+ const struct nvmem_cell_info *cells;
|
|
|
+ int ncells;
|
|
|
++ const struct nvmem_keepout *keepout;
|
|
|
++ unsigned int nkeepout;
|
|
|
+ enum nvmem_type type;
|
|
|
+ bool read_only;
|
|
|
+ bool root_only;
|