[PATCH 2/2] spi: spi-intel: report controller enforced write protection
From: Tobias Jakobsen via B4 Relay
Date: Fri Aug 28 2026 - 21:05:10 EST
From: Tobias Jakobsen <tjakobsen84@xxxxxxxxxxxxxx>
On Intel PCH platforms write protection is enforced by the controller's
protected range registers, not by the flash chip's block protection
bits, which are typically left clear. The SPI MEM conversion left
spi-intel without visibility of the MTD device, so it cannot supply MTD
locking operations directly.
Pass a write protection query through struct flash_platform_data, which
spi-intel already uses to hand the partition layout to spi-nor.
Note this asks the opposite question to intel_spi_is_protected(): rather
than whether a flash region contains a protected range, it asks whether
the queried range is itself entirely covered by one, which is what the
MTD layer means by locked.
Tested on a Coffee Lake i5 with PR0 covering 0x860000-0xffffff and
FLOCKDN set, querying MEMISLOCKED over four ranges:
PR0 range whole chip in PR0 below PR0
unpatched -95 -95 -95 -95
chip lock flags only 0 0 0 0
patched, no chip lock flags 1 0 1 0
patched + chip lock flags 1 0 1 0
The whole chip and below-PR0 columns stay unlocked because only
0x860000-0xffffff is covered by a protected range.
Link: https://bugzilla.kernel.org/show_bug.cgi?id=221927
Signed-off-by: Tobias Jakobsen <tjakobsen84@xxxxxxxxxxxxxx>
---
drivers/spi/spi-intel.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/drivers/spi/spi-intel.c b/drivers/spi/spi-intel.c
index 7494b921a..667291979 100644
--- a/drivers/spi/spi-intel.c
+++ b/drivers/spi/spi-intel.c
@@ -1223,6 +1223,46 @@ static bool intel_spi_is_protected(const struct intel_spi *ispi,
return false;
}
+/*
+ * Unlike intel_spi_is_protected(), which asks whether a flash region contains
+ * any protected range, this asks the opposite: whether the given range is
+ * itself entirely covered by a write protected range. That is what the MTD
+ * layer means by "locked".
+ */
+static bool intel_spi_is_range_protected(const struct intel_spi *ispi,
+ unsigned int base, unsigned int limit)
+{
+ int i;
+
+ for (i = 0; i < ispi->pr_num; i++) {
+ u32 pr_base, pr_limit, pr_value;
+
+ pr_value = readl(ispi->pregs + PR(i));
+ if (!(pr_value & PR_WPE))
+ continue;
+
+ pr_limit = (pr_value & PR_LIMIT_MASK) >> PR_LIMIT_SHIFT;
+ pr_base = pr_value & PR_BASE_MASK;
+
+ if (base >= pr_base && limit <= pr_limit)
+ return true;
+ }
+
+ return false;
+}
+
+static int intel_spi_is_locked(struct spi_device *spi, loff_t ofs, u64 len)
+{
+ struct intel_spi *ispi = spi_controller_get_devdata(spi->controller);
+
+ if (!len)
+ return 0;
+
+ /* Protected range registers work in 4k units */
+ return intel_spi_is_range_protected(ispi, ofs >> 12,
+ (ofs + len - 1) >> 12);
+}
+
/*
* There will be a single partition holding all enabled flash regions. We
* call this "BIOS".
@@ -1395,6 +1435,12 @@ static int intel_spi_populate_chip(struct intel_spi *ispi)
intel_spi_fill_partition(ispi, pdata->parts);
+ /*
+ * The protected range registers address the first chip, so only it can
+ * be queried this way.
+ */
+ pdata->is_locked = intel_spi_is_locked;
+
memset(&chip, 0, sizeof(chip));
snprintf(chip.modalias, 8, "spi-nor");
chip.platform_data = pdata;
--
2.53.0