| Message ID | 20260829-spi-nor-platform-lock-v1-2-cd362d4914e2@protonmail.com |
|---|---|
| State | Superseded |
| Headers | show |
| Series | Report platform enforced SPI flash write protection | expand |
Hi, On Sat, Aug 29, 2026 at 03:04:33AM +0200, Tobias Jakobsen via B4 Relay wrote: > From: Tobias Jakobsen <tjakobsen84@protonmail.com> > > 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. This looks pretty much that it got assistance by LLM, if this is the case please add Assisted-by tag as well. > Link: https://bugzilla.kernel.org/show_bug.cgi?id=221927 > Signed-off-by: Tobias Jakobsen <tjakobsen84@protonmail.com> > --- > 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; > +} Can you use intel_spi_is_protected() or at least make a helper that reduces the code-duplication here? > + > +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 */ This is not too useful comment, it should be obvious so I would drop this. > + 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 >
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;