diff --git a/src/drivers/spi/spi_flash.c b/src/drivers/spi/spi_flash.c index f2714791db..c4840886bb 100644 --- a/src/drivers/spi/spi_flash.c +++ b/src/drivers/spi/spi_flash.c @@ -432,6 +432,28 @@ int spi_flash_status(const struct spi_flash *flash, u8 *reg) return -1; } +int spi_flash_is_write_protected(const struct spi_flash *flash, + const struct region *region) +{ + struct region flash_region = { 0 }; + + if (!flash || !region) + return -1; + + flash_region.size = flash->size; + + if (!region_is_subregion(&flash_region, region)) + return -1; + + if (!flash->ops->get_write_protection) { + printk(BIOS_WARNING, "SPI: Write-protection gathering not " + "implemented for this vendor.\n"); + return 0; + } + + return flash->ops->get_write_protection(flash, region); +} + 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 a25c9a65ea..a0e3884e07 100644 --- a/src/drivers/spi/winbond.c +++ b/src/drivers/spi/winbond.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "spi_flash_internal.h" @@ -17,6 +18,8 @@ #define CMD_W25_WRDI 0x04 /* Write Disable */ #define CMD_W25_RDSR 0x05 /* Read Status Register */ #define CMD_W25_WRSR 0x01 /* Write Status Register */ +#define CMD_W25_RDSR2 0x35 /* Read Status2 Register */ +#define CMD_W25_WRSR2 0x31 /* Write Status2 Register */ #define CMD_W25_READ 0x03 /* Read Data Bytes */ #define CMD_W25_FAST_READ 0x0b /* Read Data Bytes at Higher Speed */ #define CMD_W25_PP 0x02 /* Page Program */ @@ -32,9 +35,46 @@ struct winbond_spi_flash_params { uint8_t pages_per_sector_shift : 4; uint8_t sectors_per_block_shift : 4; uint8_t nr_blocks_shift; + uint8_t bp_bits : 3; + uint8_t protection_granularity_shift : 5; char name[10]; }; +union status_reg1_bp3 { + uint8_t u; + struct { + uint8_t busy : 1; + uint8_t wel : 1; + uint8_t bp : 3; + uint8_t tb : 1; + uint8_t sec : 1; + uint8_t srp0 : 1; + }; +}; + +union status_reg1_bp4 { + uint8_t u; + struct { + uint8_t busy : 1; + uint8_t wel : 1; + uint8_t bp : 4; + uint8_t tb : 1; + uint8_t srp0 : 1; + }; +}; + +union status_reg2 { + uint8_t u; + struct { + uint8_t srp1 : 1; + uint8_t qe : 1; + uint8_t res : 1; + uint8_t lb : 3; + uint8_t cmp : 1; + uint8_t sus : 1; + }; +}; + static const struct winbond_spi_flash_params winbond_spi_flash_table[] = { { .id = 0x3015, @@ -75,6 +115,8 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = { .sectors_per_block_shift = 4, .nr_blocks_shift = 5, .name = "W25Q16", + .protection_granularity_shift = 16, + .bp_bits = 3, }, { .id = 0x4016, @@ -83,6 +125,8 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = { .sectors_per_block_shift = 4, .nr_blocks_shift = 6, .name = "W25Q32", + .protection_granularity_shift = 16, + .bp_bits = 3, }, { .id = 0x6016, @@ -91,6 +135,8 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = { .sectors_per_block_shift = 4, .nr_blocks_shift = 6, .name = "W25Q32DW", + .protection_granularity_shift = 16, + .bp_bits = 3, }, { .id = 0x4017, @@ -99,6 +145,8 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = { .sectors_per_block_shift = 4, .nr_blocks_shift = 7, .name = "W25Q64", + .protection_granularity_shift = 17, + .bp_bits = 3, }, { .id = 0x6017, @@ -107,6 +155,8 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = { .sectors_per_block_shift = 4, .nr_blocks_shift = 7, .name = "W25Q64DW", + .protection_granularity_shift = 17, + .bp_bits = 3, }, { .id = 0x4018, @@ -115,6 +165,8 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = { .sectors_per_block_shift = 4, .nr_blocks_shift = 8, .name = "W25Q128", + .protection_granularity_shift = 18, + .bp_bits = 3, }, { .id = 0x6018, @@ -123,6 +175,8 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = { .sectors_per_block_shift = 4, .nr_blocks_shift = 8, .name = "W25Q128FW", + .protection_granularity_shift = 18, + .bp_bits = 3, }, { .id = 0x4019, @@ -131,6 +185,8 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = { .sectors_per_block_shift = 4, .nr_blocks_shift = 9, .name = "W25Q256", + .protection_granularity_shift = 16, + .bp_bits = 4, }, }; @@ -191,6 +247,102 @@ out: return ret; } +/* + * Convert BPx, TB and CMP to a region. + * SEC (if available) must be zero. + */ +static void winbond_bpbits_to_region(const size_t granularity, + const u8 bp, + bool tb, + const bool cmp, + const size_t flash_size, + struct region *out) +{ + size_t protected_size = + min(bp ? granularity << (bp - 1) : 0, flash_size); + + if (cmp) { + protected_size = flash_size - protected_size; + tb = !tb; + } + + out->offset = tb ? flash_size - protected_size : 0; + out->size = protected_size; +} + +/* + * Available on all devices. + * Read block protect bits from Status/Status2 Reg. + * Converts block protection bits to a region. + * + * Returns: + * -1 on error + * 1 if region is covered by write protection + * 0 if a part of region isn't covered by write protection + */ +static int winbond_get_write_protection(const struct spi_flash *flash, + const struct region *region) +{ + const struct winbond_spi_flash_params *params; + struct region wp_region; + union status_reg2 reg2; + u8 bp, tb; + int ret; + + params = (const struct winbond_spi_flash_params *)flash->driver_private; + const size_t granularity = (1 << params->protection_granularity_shift); + + if (params->bp_bits == 3) { + union status_reg1_bp3 reg1_bp3; + + ret = spi_flash_cmd(&flash->spi, flash->status_cmd, ®1_bp3.u, + sizeof(reg1_bp3.u)); + if (ret) + return ret; + + if (reg1_bp3.sec) { + // FIXME: not supported + return -1; + } + + bp = reg1_bp3.bp; + tb = reg1_bp3.tb; + } else if (params->bp_bits == 4) { + union status_reg1_bp4 reg1_bp4; + + ret = spi_flash_cmd(&flash->spi, flash->status_cmd, ®1_bp4.u, + sizeof(reg1_bp4.u)); + if (ret) + return ret; + + bp = reg1_bp4.bp; + tb = reg1_bp4.tb; + } else { + // FIXME: not supported + return -1; + } + + ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, ®2.u, + sizeof(reg2.u)); + if (ret) + return ret; + + winbond_bpbits_to_region(granularity, bp, tb, reg2.cmp, flash->size, + &wp_region); + + if (!reg2.srp1 || !wp_region.size) { + printk(BIOS_DEBUG, "WINBOND: flash isn't protected\n"); + + return 0; + } + + printk(BIOS_DEBUG, "WINBOND: flash protected range 0x%08zx-0x%08zx\n", + wp_region.offset, wp_region.size); + + return region_is_subregion(&wp_region, region); +} + + static const struct spi_flash_ops spi_flash_ops = { .write = winbond_write, .erase = spi_flash_cmd_erase, @@ -200,6 +352,7 @@ static const struct spi_flash_ops spi_flash_ops = { #else .read = spi_flash_cmd_read_fast, #endif + .get_write_protection = winbond_get_write_protection, }; int spi_flash_probe_winbond(const struct spi_slave *spi, u8 *idcode, @@ -234,6 +387,7 @@ int spi_flash_probe_winbond(const struct spi_slave *spi, u8 *idcode, flash->status_cmd = CMD_W25_RDSR; flash->ops = &spi_flash_ops; + flash->driver_private = params; return 0; } diff --git a/src/include/spi_flash.h b/src/include/spi_flash.h index f7f3b3dbdf..9f8d2d06ea 100644 --- a/src/include/spi_flash.h +++ b/src/include/spi_flash.h @@ -40,6 +40,16 @@ struct spi_flash_ops { const void *buf); int (*erase)(const struct spi_flash *flash, u32 offset, size_t len); int (*status)(const struct spi_flash *flash, u8 *reg); + /* + * Returns 1 if the whole region is software write protected. + * 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. + */ + int (*get_write_protection)(const struct spi_flash *flash, + const struct region *region); + }; struct spi_flash { @@ -51,6 +61,7 @@ struct spi_flash { u8 erase_cmd; u8 status_cmd; const struct spi_flash_ops *ops; + const void *driver_private; }; void lb_spi_flash(struct lb_header *header); @@ -93,6 +104,21 @@ int spi_flash_write(const struct spi_flash *flash, u32 offset, size_t len, const void *buf); int spi_flash_erase(const struct spi_flash *flash, u32 offset, size_t len); int spi_flash_status(const struct spi_flash *flash, u8 *reg); + +/* + * Return the vendor dependent SPI flash write protection state. + * @param flash : A SPI flash device + * @param region: A subregion of the device's region + * + * Returns: + * -1 on error + * 0 if the device doesn't support block protection + * 0 if the device doesn't enable block protection + * 0 if given range isn't covered by block protection + * 1 if given range is covered by block protection + */ +int spi_flash_is_write_protected(const struct spi_flash *flash, + const struct region *region); /* * Some SPI controllers require exclusive access to SPI flash when volatile * operations like erase or write are being performed. In such cases,