[coreboot-gerrit] Change in coreboot[master]: drivers/spi: Winbond specific write-protection enable

Philipp Deppenwiese (Code Review) gerrit at coreboot.org
Tue Oct 30 13:16:01 CET 2018


Philipp Deppenwiese has submitted this change and it was merged. ( https://review.coreboot.org/25105 )

Change subject: drivers/spi: Winbond specific write-protection enable
......................................................................

drivers/spi: Winbond specific write-protection enable

Extend the SPI interface to enable write-protection.

Tested on Cavium EVB CN81xx using W25Q128.

Change-Id: Ie3765b013855538eca37bc7800d3f9d5d09b8402
Signed-off-by: Patrick Rudolph <patrick.rudolph at 9elements.com>
Reviewed-on: https://review.coreboot.org/25105
Tested-by: build bot (Jenkins) <no-reply at coreboot.org>
Reviewed-by: Philipp Deppenwiese <zaolin.daisuki at gmail.com>
---
M src/drivers/spi/spi_flash.c
M src/drivers/spi/winbond.c
M src/include/spi_flash.h
3 files changed, 356 insertions(+), 1 deletion(-)

Approvals:
  build bot (Jenkins): Verified
  Philipp Deppenwiese: Looks good to me, approved



diff --git a/src/drivers/spi/spi_flash.c b/src/drivers/spi/spi_flash.c
index c484088..cc21ccb 100644
--- a/src/drivers/spi/spi_flash.c
+++ b/src/drivers/spi/spi_flash.c
@@ -454,6 +454,55 @@
 	return flash->ops->get_write_protection(flash, region);
 }
 
+int spi_flash_set_write_protected(const struct spi_flash *flash,
+				  const struct region *region,
+				  const bool non_volatile,
+				  const enum spi_flash_status_reg_lockdown mode)
+{
+	struct region flash_region = { 0 };
+	int ret;
+
+	if (!flash)
+		return -1;
+
+	flash_region.size = flash->size;
+
+	if (!region_is_subregion(&flash_region, region))
+		return -1;
+
+	if (!flash->ops->set_write_protection) {
+		printk(BIOS_WARNING, "SPI: Setting write-protection is not "
+		       "implemented for this vendor.\n");
+		return 0;
+	}
+
+	ret = flash->ops->set_write_protection(flash, region, non_volatile,
+					       mode);
+
+	if (ret == 0 && mode != SPI_WRITE_PROTECTION_PRESERVE) {
+		printk(BIOS_INFO, "SPI: SREG lock-down was set to ");
+		switch (mode) {
+		case SPI_WRITE_PROTECTION_NONE:
+			printk(BIOS_INFO, "NEVER\n");
+		break;
+		case SPI_WRITE_PROTECTION_PIN:
+			printk(BIOS_INFO, "WP\n");
+		break;
+		case SPI_WRITE_PROTECTION_REBOOT:
+			printk(BIOS_INFO, "REBOOT\n");
+		break;
+		case SPI_WRITE_PROTECTION_PERMANENT:
+			printk(BIOS_INFO, "PERMANENT\n");
+		break;
+		default:
+			printk(BIOS_INFO, "UNKNOWN\n");
+		break;
+		}
+	}
+
+	return ret;
+}
+
 static uint32_t volatile_group_count CAR_GLOBAL;
 
 int spi_flash_volatile_group_begin(const struct spi_flash *flash)
diff --git a/src/drivers/spi/winbond.c b/src/drivers/spi/winbond.c
index e2c6538..9eb3352 100644
--- a/src/drivers/spi/winbond.c
+++ b/src/drivers/spi/winbond.c
@@ -10,6 +10,8 @@
 #include <spi-generic.h>
 #include <string.h>
 #include <assert.h>
+#include <delay.h>
+#include <lib.h>
 
 #include "spi_flash_internal.h"
 
@@ -28,6 +30,10 @@
 #define CMD_W25_CE		0xc7	/* Chip Erase */
 #define CMD_W25_DP		0xb9	/* Deep Power-down */
 #define CMD_W25_RES		0xab	/* Release from DP, and Read Signature */
+#define CMD_VOLATILE_SREG_WREN	0x50	/* Write Enable for Volatile SREG */
+
+/* tw: Maximum time to write a flash cell in milliseconds */
+#define WINBOND_FLASH_TIMEOUT 30
 
 struct winbond_spi_flash_params {
 	uint16_t id;
@@ -75,6 +81,27 @@
 	};
 };
 
+struct status_regs {
+	union {
+		struct {
+#if defined(__BIG_ENDIAN)
+			union status_reg2 reg2;
+			union {
+				union status_reg1_bp3 reg1_bp3;
+				union status_reg1_bp4 reg1_bp4;
+			};
+#else
+			union {
+				union status_reg1_bp3 reg1_bp3;
+				union status_reg1_bp4 reg1_bp4;
+			};
+			union status_reg2 reg2;
+#endif
+		};
+		u16 u;
+	};
+};
+
 static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
 	{
 		.id				= 0x3015,
@@ -351,6 +378,228 @@
 	return region_is_subregion(&wp_region, region);
 }
 
+/**
+ * Common method to write some bit of the status register 1 & 2 at the same
+ * time. Only change bits that are one in @mask.
+ * Compare the final result to make sure that the register isn't locked.
+ *
+ * @param mask: The bits that are affected by @val
+ * @param val: The bits to write
+ * @param non_volatile: Make setting permanent
+ *
+ * @return 0 on success
+ */
+static int winbond_flash_cmd_status(const struct spi_flash *flash,
+				    const u16 mask,
+				    const u16 val,
+				    const bool non_volatile)
+{
+	struct {
+		u8 cmd;
+		u16 sreg;
+	} __packed cmdbuf;
+	u8 reg8;
+	int ret;
+
+	if (!flash)
+		return -1;
+
+	ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR, &reg8, sizeof(reg8));
+	if (ret)
+		return ret;
+
+	cmdbuf.sreg = reg8;
+
+	ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, &reg8, sizeof(reg8));
+	if (ret)
+		return ret;
+
+	cmdbuf.sreg |= reg8 << 8;
+
+	if ((val & mask) == (cmdbuf.sreg & mask))
+		return 0;
+
+	if (non_volatile) {
+		ret = spi_flash_cmd(&flash->spi, CMD_W25_WREN, NULL, 0);
+	} else {
+		ret = spi_flash_cmd(&flash->spi, CMD_VOLATILE_SREG_WREN, NULL,
+				    0);
+	}
+	if (ret)
+		return ret;
+
+	cmdbuf.sreg &= ~mask;
+	cmdbuf.sreg |= val & mask;
+	cmdbuf.cmd = CMD_W25_WRSR;
+
+	/* Legacy method of writing status register 1 & 2 */
+	ret = spi_flash_cmd_write(&flash->spi, (u8 *)&cmdbuf, sizeof(cmdbuf),
+				  NULL, 0);
+	if (ret)
+		return ret;
+
+	if (non_volatile) {
+		/* Wait tw */
+		ret = spi_flash_cmd_wait_ready(flash, WINBOND_FLASH_TIMEOUT);
+		if (ret)
+			return ret;
+	} else {
+		/* Wait tSHSL */
+		udelay(1);
+	}
+
+	/* Now read the status register to make sure it's not locked */
+	ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR, &reg8, sizeof(reg8));
+	if (ret)
+		return ret;
+
+	cmdbuf.sreg = reg8;
+
+	ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, &reg8, sizeof(reg8));
+	if (ret)
+		return ret;
+
+	cmdbuf.sreg |= reg8 << 8;
+
+	printk(BIOS_DEBUG, "WINBOND: SREG=%02x SREG2=%02x\n",
+	       cmdbuf.sreg & 0xff,
+	       cmdbuf.sreg >> 8);
+
+	/* Compare against expected result */
+	if ((val & mask) != (cmdbuf.sreg & mask)) {
+		printk(BIOS_ERR, "WINBOND: SREG is locked!\n");
+		ret = -1;
+	}
+
+	return ret;
+}
+
+/*
+ * Available on all devices.
+ * Protect a region starting from start of flash or end of flash.
+ * The caller must provide a supported protected region size.
+ * SEC isn't supported and set to zero.
+ * Write block protect bits to Status/Status2 Reg.
+ * Optionally lock the status register if lock_sreg is set with the provided
+ * mode.
+ *
+ * @param flash: The flash to operate on
+ * @param region: The region to write protect
+ * @param non_volatile: Make setting permanent
+ * @param mode: Optional status register lock-down mode
+ *
+ * @return 0 on success
+ */
+static int
+winbond_set_write_protection(const struct spi_flash *flash,
+			     const struct region *region,
+			     const bool non_volatile,
+			     const enum spi_flash_status_reg_lockdown mode)
+{
+	const struct winbond_spi_flash_params *params;
+	struct status_regs mask, val;
+	struct region wp_region;
+	u8 cmp, bp, tb;
+	int ret;
+
+	/* Need to touch TOP or BOTTOM */
+	if (region_offset(region) != 0 &&
+	    (region_offset(region) + region_sz(region)) != flash->size)
+		return -1;
+
+	params = (const struct winbond_spi_flash_params *)flash->driver_private;
+	if (!params)
+		return -1;
+
+	if (params->bp_bits != 3 && params->bp_bits != 4) {
+		/* FIXME: not implemented */
+		return -1;
+	}
+
+	wp_region = *region;
+
+	if (region_offset(&wp_region) == 0)
+		tb = 0;
+	else
+		tb = 1;
+
+	if (region_sz(&wp_region) > flash->size / 2) {
+		cmp = 1;
+		wp_region.offset = tb ? 0 : region_sz(&wp_region);
+		wp_region.size = flash->size - region_sz(&wp_region);
+		tb = !tb;
+	} else {
+		cmp = 0;
+	}
+
+	if (region_sz(&wp_region) == 0) {
+		bp = 0;
+	} else if (IS_POWER_OF_2(region_sz(&wp_region)) &&
+		   (region_sz(&wp_region) >=
+		    (1 << params->protection_granularity_shift))) {
+		bp = log2(region_sz(&wp_region)) -
+			  params->protection_granularity_shift + 1;
+	} else {
+		printk(BIOS_ERR, "WINBOND: ERROR: unsupported region size\n");
+		return -1;
+	}
+
+	/* Write block protection bits */
+
+	if (params->bp_bits == 3) {
+		val.reg1_bp3 = (union status_reg1_bp3) { .bp = bp, .tb = tb,
+							.sec = 0 };
+		mask.reg1_bp3 = (union status_reg1_bp3) { .bp = ~0, .tb = 1,
+							.sec = 1 };
+	} else {
+		val.reg1_bp4 = (union status_reg1_bp4) { .bp = bp, .tb = tb };
+		mask.reg1_bp4 = (union status_reg1_bp4) { .bp = ~0, .tb = 1 };
+	}
+
+	val.reg2 = (union status_reg2) { .cmp = cmp };
+	mask.reg2 = (union status_reg2) { .cmp = 1 };
+
+	if (mode != SPI_WRITE_PROTECTION_PRESERVE) {
+		u8 srp;
+		switch (mode) {
+		case SPI_WRITE_PROTECTION_NONE:
+			srp = 0;
+		break;
+		case SPI_WRITE_PROTECTION_PIN:
+			srp = 1;
+		break;
+		case SPI_WRITE_PROTECTION_REBOOT:
+			srp = 2;
+		break;
+		case SPI_WRITE_PROTECTION_PERMANENT:
+			srp = 3;
+		break;
+		default:
+			return -1;
+		}
+
+		if (params->bp_bits == 3) {
+			val.reg1_bp3.srp0 = !!(srp & 1);
+			mask.reg1_bp3.srp0 = 1;
+		} else {
+			val.reg1_bp4.srp0 = !!(srp & 1);
+			mask.reg1_bp4.srp0 = 1;
+		}
+
+		val.reg2.srp1 = !!(srp & 2);
+		mask.reg2.srp1 = 1;
+	}
+
+	ret = winbond_flash_cmd_status(flash, mask.u, val.u, non_volatile);
+	if (ret)
+		return ret;
+
+	printk(BIOS_DEBUG, "WINBOND: write-protection set to range "
+	       "0x%08zx-0x%08zx\n", region_offset(region),
+	       region_offset(region) + region_sz(region));
+
+	return ret;
+}
 
 static const struct spi_flash_ops spi_flash_ops = {
 	.write = winbond_write,
@@ -362,6 +611,7 @@
 	.read = spi_flash_cmd_read_fast,
 #endif
 	.get_write_protection = winbond_get_write_protection,
+	.set_write_protection = winbond_set_write_protection,
 };
 
 int spi_flash_probe_winbond(const struct spi_slave *spi, u8 *idcode,
diff --git a/src/include/spi_flash.h b/src/include/spi_flash.h
index 9f8d2d0..64ad7fe 100644
--- a/src/include/spi_flash.h
+++ b/src/include/spi_flash.h
@@ -27,6 +27,25 @@
 struct spi_flash;
 
 /*
+ * SPI write protection is enforced by locking the status register.
+ * The following modes are known. It depends on the flash chip if the
+ * mode is actually supported.
+ *
+ * PRESERVE : Keep the previous status register lock-down setting (noop)
+ * NONE     : Status register isn't locked
+ * PIN      : Status register is locked as long as the ~WP pin is active
+ * REBOOT   : Status register is locked until power failure
+ * PERMANENT: Status register is permanently locked
+ */
+enum spi_flash_status_reg_lockdown {
+	SPI_WRITE_PROTECTION_PRESERVE = -1,
+	SPI_WRITE_PROTECTION_NONE = 0,
+	SPI_WRITE_PROTECTION_PIN,
+	SPI_WRITE_PROTECTION_REBOOT,
+	SPI_WRITE_PROTECTION_PERMANENT
+};
+
+/*
  * Representation of SPI flash operations:
  * read:	Flash read operation.
  * write:	Flash write operation.
@@ -45,10 +64,26 @@
 	 * Hardware write protection mechanism aren't accounted.
 	 * If the write protection could be changed, due to unlocked status
 	 * register for example, 0 should be returned.
-	 * Returns -1 on error.
+	 * Returns 0 on success.
 	 */
 	int (*get_write_protection)(const struct spi_flash *flash,
 				    const struct region *region);
+	/*
+	 * Enable the status register write protection, if supported on the
+	 * requested region, and optionally enable status register lock-down.
+	 * Returns 0 if the whole region was software write protected.
+	 * Hardware write protection mechanism aren't accounted.
+	 * If the status register is locked and the requested configuration
+	 * doesn't match the selected one, return an error.
+	 * Only a single region is supported !
+	 *
+	 * @return 0 on success
+	 */
+	int
+	(*set_write_protection)(const struct spi_flash *flash,
+				const struct region *region,
+				const bool non_volatile,
+				const enum spi_flash_status_reg_lockdown mode);
 
 };
 
@@ -120,6 +155,27 @@
 int spi_flash_is_write_protected(const struct spi_flash *flash,
 				 const struct region *region);
 /*
+ * Enable the vendor dependent SPI flash write protection. The region not
+ * covered by write-protection will be set to write-able state.
+ * Only a single write-protected region is supported.
+ * Some flash ICs require the region to be aligned in the block size, sector
+ * size or page size.
+ * Some flash ICs require the region to start at TOP or BOTTOM.
+ *
+ * @param flash : A SPI flash device
+ * @param region: A subregion of the device's region
+ * @param non_volatile: Write status register non-volatile
+ * @param mode: Optional lock-down of status register
+
+ * @return 0 on success
+ */
+int
+spi_flash_set_write_protected(const struct spi_flash *flash,
+			      const struct region *region,
+			      const bool non_volatile,
+			      const enum spi_flash_status_reg_lockdown mode);
+
+/*
  * Some SPI controllers require exclusive access to SPI flash when volatile
  * operations like erase or write are being performed. In such cases,
  * volatile_group_begin will gain exclusive access to SPI flash if not already

-- 
To view, visit https://review.coreboot.org/25105
To unsubscribe, or for help writing mail filters, visit https://review.coreboot.org/settings

Gerrit-Project: coreboot
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: Ie3765b013855538eca37bc7800d3f9d5d09b8402
Gerrit-Change-Number: 25105
Gerrit-PatchSet: 35
Gerrit-Owner: Patrick Rudolph <patrick.rudolph at 9elements.com>
Gerrit-Reviewer: Aaron Durbin <adurbin at chromium.org>
Gerrit-Reviewer: David Hendricks <david.hendricks at gmail.com>
Gerrit-Reviewer: Julius Werner <jwerner at chromium.org>
Gerrit-Reviewer: Patrick Georgi <pgeorgi at google.com>
Gerrit-Reviewer: Patrick Rudolph <patrick.rudolph at 9elements.com>
Gerrit-Reviewer: Paul Menzel <paulepanter at users.sourceforge.net>
Gerrit-Reviewer: Philipp Deppenwiese <zaolin.daisuki at gmail.com>
Gerrit-Reviewer: build bot (Jenkins) <no-reply at coreboot.org>
Gerrit-CC: Patrick Rudolph (1001864)
Gerrit-CC: Stefan Reinauer <stefan.reinauer at coreboot.org>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.coreboot.org/pipermail/coreboot-gerrit/attachments/20181030/825200df/attachment.html>


More information about the coreboot-gerrit mailing list