diff --git a/src/drivers/spi/spi_flash.c b/src/drivers/spi/spi_flash.c index c4840886bb..cc21ccb20e 100644 --- a/src/drivers/spi/spi_flash.c +++ b/src/drivers/spi/spi_flash.c @@ -454,6 +454,55 @@ int spi_flash_is_write_protected(const struct spi_flash *flash, 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 e2c653865d..9eb335262d 100644 --- a/src/drivers/spi/winbond.c +++ b/src/drivers/spi/winbond.c @@ -10,6 +10,8 @@ #include #include #include +#include +#include #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 @@ union status_reg2 { }; }; +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 @@ static int winbond_get_write_protection(const struct spi_flash *flash, 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, ®8, sizeof(reg8)); + if (ret) + return ret; + + cmdbuf.sreg = reg8; + + ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, ®8, 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, ®8, sizeof(reg8)); + if (ret) + return ret; + + cmdbuf.sreg = reg8; + + ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, ®8, 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 @@ static const struct spi_flash_ops spi_flash_ops = { .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 9f8d2d06ea..64ad7fe04a 100644 --- a/src/include/spi_flash.h +++ b/src/include/spi_flash.h @@ -26,6 +26,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. @@ -45,10 +64,26 @@ struct spi_flash_ops { * 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); }; @@ -119,6 +154,27 @@ int spi_flash_status(const struct spi_flash *flash, u8 *reg); */ 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,