Daniel Gröber (dxld) would like Daniel Gröber to review this change.

View Change

spi/winbond: Add a test for block protection status bits

With refactoring out of the way we can add a test to make sure the
region<>bpbits mapping is correct. Indeed we found that a number of
configurations are incorrect.

The datasheets say that min/max bp values should protect either all or
none of the flash depending on the cmp and tb bits. However we currently
always return no protection for these values.

The test merely documents the current behavour, a fix will come in a
later commit.

Change-Id: Ieb0632221203166288f9608c8492c1d4bd77dd36
Signed-off-by: Daniel Gröber <dxld@darkboxed.org>
---
M src/drivers/spi/spi_flash.c
M src/drivers/spi/spi_flash_internal.h
M src/drivers/spi/spi_winbond.h
M src/drivers/spi/winbond.c
A tests/drivers/Makefile.inc
A tests/drivers/winbond-spi-test.c
6 files changed, 344 insertions(+), 13 deletions(-)

git pull ssh://review.coreboot.org:29418/coreboot refs/changes/17/42117/1
diff --git a/src/drivers/spi/spi_flash.c b/src/drivers/spi/spi_flash.c
index a389bc4..33416b5 100644
--- a/src/drivers/spi/spi_flash.c
+++ b/src/drivers/spi/spi_flash.c
@@ -337,7 +337,7 @@
};
#define IDCODE_LEN 5

-static int fill_spi_flash(const struct spi_slave *spi, struct spi_flash *flash,
+int fill_spi_flash(const struct spi_slave *spi, struct spi_flash *flash,
const struct spi_flash_vendor_info *vi,
const struct spi_flash_part_id *part)
{
diff --git a/src/drivers/spi/spi_flash_internal.h b/src/drivers/spi/spi_flash_internal.h
index b4d39b3..1ed0ab3 100644
--- a/src/drivers/spi/spi_flash_internal.h
+++ b/src/drivers/spi/spi_flash_internal.h
@@ -24,6 +24,13 @@
/* Common status */
#define STATUS_WIP 0x01

+struct spi_flash_vendor_info;
+struct spi_flash_part_id;
+
+int fill_spi_flash(const struct spi_slave *spi, struct spi_flash *flash,
+ const struct spi_flash_vendor_info *vi,
+ const struct spi_flash_part_id *part);
+
/* Send a single-byte command to the device and read the response */
int spi_flash_cmd(const struct spi_slave *spi, u8 cmd, void *response, size_t len);

diff --git a/src/drivers/spi/spi_winbond.h b/src/drivers/spi/spi_winbond.h
index bdf6694..ff799ff 100644
--- a/src/drivers/spi/spi_winbond.h
+++ b/src/drivers/spi/spi_winbond.h
@@ -21,3 +21,16 @@

/* tw: Maximum time to write a flash cell in milliseconds */
#define WINBOND_FLASH_TIMEOUT 30
+
+int winbond_region_to_bpbits(
+ const struct spi_flash *flash,
+ const struct spi_flash_part_id *params,
+ const struct region *region,
+ const enum spi_flash_status_reg_lockdown mode,
+ struct spi_flash_bpbits *bits);
+
+void winbond_bpbits_to_region(
+ const size_t granularity,
+ const struct spi_flash_bpbits *bits,
+ const size_t flash_size,
+ struct region *out);
diff --git a/src/drivers/spi/winbond.c b/src/drivers/spi/winbond.c
index f68d9d9..d7a750b 100644
--- a/src/drivers/spi/winbond.c
+++ b/src/drivers/spi/winbond.c
@@ -211,21 +211,14 @@
static int winbond_get_bpbits(const struct spi_flash *flash,
struct spi_flash_bpbits *bpbits);

-static int winbond_region_to_bpbits(
- const struct spi_flash *flash,
- const struct spi_flash_part_id *params,
- const struct region *region,
- const enum spi_flash_status_reg_lockdown mode,
- struct spi_flash_bpbits *bits);
-
/*
* 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 struct spi_flash_bpbits *bits,
- const size_t flash_size,
- struct region *out)
+void winbond_bpbits_to_region(const size_t granularity,
+ const struct spi_flash_bpbits *bits,
+ const size_t flash_size,
+ struct region *out)
{
size_t protected_size =
MIN(bits->bp ? granularity << (bits->bp - 1) : 0, flash_size);
@@ -514,7 +507,7 @@
return ret;
}

-static int winbond_region_to_bpbits(
+int winbond_region_to_bpbits(
const struct spi_flash *flash,
const struct spi_flash_part_id *params,
const struct region *region,
diff --git a/tests/drivers/Makefile.inc b/tests/drivers/Makefile.inc
new file mode 100644
index 0000000..6e5dff3
--- /dev/null
+++ b/tests/drivers/Makefile.inc
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+tests-y += winbond-spi-test
+
+winbond-spi-test-srcs += tests/drivers/winbond-spi-test.c
+winbond-spi-test-srcs += src/drivers/spi/winbond.c
+winbond-spi-test-srcs += src/drivers/spi/spi_flash.c
+winbond-spi-test-srcs += src/commonlib/region.c
+winbond-spi-test-srcs += src/commonlib/mem_pool.c
diff --git a/tests/drivers/winbond-spi-test.c b/tests/drivers/winbond-spi-test.c
new file mode 100644
index 0000000..da7177b
--- /dev/null
+++ b/tests/drivers/winbond-spi-test.c
@@ -0,0 +1,309 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <string.h>
+
+#include <stdio.h> /* debug */
+
+#include <commonlib/helpers.h>
+#include <spi_flash.h>
+#include <spi-generic.h>
+#include "../drivers/spi/spi_flash_internal.h"
+#include "../drivers/spi/spi_winbond.h"
+
+int fill_spi_flash(const struct spi_slave *spi, struct spi_flash *flash,
+ const struct spi_flash_vendor_info *vi,
+ const struct spi_flash_part_id *part);
+
+struct block_region {
+ /* these are in units of 64k-byte blocks */
+ size_t offset;
+ size_t size;
+};
+
+struct bp_table {
+ uint16_t id;
+ size_t size;
+ struct block_region wp_blocks[2][2][1<<4];
+} bp_table[] = {
+ {
+ /* W25Q256J */
+ .id = 0x7019,
+ .wp_blocks = {
+ /* [cmp][tb][bp] */
+ /* off size */
+
+ /* cmp=0, tb=x, bp=0b0000: NONE */
+ [0][0][0] = { 0, 0 },
+ [0][1][0] = { 0, 0 },
+
+ /* cmp=1, tb=x, bp=0b0000: FIXME: ALL */
+ [1][0][0] = { 0, 0 },
+ [1][1][0] = { 0, 0 },
+
+ /* cmp=0, tb=x, bp=0b110x|0b1x1x: FIXME: ALL */
+ [0][0 ... 1][10 ... 15] = { 0, 0 },
+
+ /* cmp=1, tb=x, bp=0b110x|0b1x1x: NONE */
+ [1][0 ... 1][10 ... 15] = { 0, 0 },
+
+ [0][0][1] = { 511, 1 },
+ [0][0][2] = { 510, 2 },
+ [0][0][3] = { 508, 4 },
+ [0][0][4] = { 504, 8 },
+ [0][0][5] = { 496, 16 },
+ [0][0][6] = { 480, 32 },
+ [0][0][7] = { 448, 64 },
+ [0][0][8] = { 384, 128 },
+ [0][0][9] = { 256, 256 },
+
+ [0][1][1] = { 0, 1 },
+ [0][1][2] = { 0, 2 },
+ [0][1][3] = { 0, 4 },
+ [0][1][4] = { 0, 8 },
+ [0][1][5] = { 0, 16 },
+ [0][1][6] = { 0, 32 },
+ [0][1][7] = { 0, 64 },
+ [0][1][8] = { 0, 128 },
+ [0][1][9] = { 0, 256 },
+
+ [1][0][1] = { 0, 512-1 },
+ [1][0][2] = { 0, 512-2 },
+ [1][0][3] = { 0, 512-4 },
+ [1][0][4] = { 0, 512-8 },
+ [1][0][5] = { 0, 512-16 },
+ [1][0][6] = { 0, 512-32 },
+ [1][0][7] = { 0, 512-64 },
+ [1][0][8] = { 0, 512-128 },
+ [1][0][9] = { 0, 512-256 },
+
+ [1][1][1] = { 1, 512-1 },
+ [1][1][2] = { 2, 512-2 },
+ [1][1][3] = { 4, 512-4 },
+ [1][1][4] = { 8, 512-8 },
+ [1][1][5] = { 16, 512-16 },
+ [1][1][6] = { 32, 512-32 },
+ [1][1][7] = { 64, 512-64 },
+ [1][1][8] = { 128, 512-128 },
+ [1][1][9] = { 256, 512-256 },
+ }
+ },
+
+ {
+ /* W25Q128FW */
+ .id = 0x6018,
+ .wp_blocks = {
+ /* [cmp][tb][bp] */
+ /* off size */
+ [0][0][0] = { 0, 0 },
+
+ /* cmp=0, tb=x, bp=0b000: NONE */
+ [0][0][0] = { 0, 0 },
+ [0][1][0] = { 0, 0 },
+
+ /* cmp=0, tb=x, bp=0b111: FIXME: ALL */
+ [0][0][7] = { 0, 0 },
+ [0][1][7] = { 0, 0 },
+
+ [0][0][1] = { 252, 4 },
+ [0][0][2] = { 248, 8 },
+ [0][0][3] = { 240, 16 },
+ [0][0][4] = { 224, 32 },
+ [0][0][5] = { 192, 64 },
+ [0][0][6] = { 128, 128 },
+
+ [0][1][1] = { 0, 4 },
+ [0][1][2] = { 0, 8 },
+ [0][1][3] = { 0, 16 },
+ [0][1][4] = { 0, 32 },
+ [0][1][5] = { 0, 64 },
+ [0][1][6] = { 0, 128 },
+
+ /* cmp=1, tb=x, bp=0b000: FIXME: ALL */
+ [1][0][0] = { 0, 0 },
+ [1][1][0] = { 0, 0 },
+
+ /* cmp=1, tb=x, bp=0b111: NONE */
+ [1][0][7] = { 0, 0 },
+ [1][1][7] = { 0, 0 },
+
+ [1][0][1] = { 0, 256-4 },
+ [1][0][2] = { 0, 256-8 },
+ [1][0][3] = { 0, 256-16 },
+ [1][0][4] = { 0, 256-32 },
+ [1][0][5] = { 0, 256-64 },
+ [1][0][6] = { 0, 256-128 },
+
+ [1][1][1] = { 4, 256-4 },
+ [1][1][2] = { 8, 256-8 },
+ [1][1][3] = { 16, 256-16 },
+ [1][1][4] = { 32, 256-32 },
+ [1][1][5] = { 64, 256-64 },
+ [1][1][6] = { 128, 256-128 },
+ }
+ }
+};
+
+static void test_one_config(
+ void **state,
+ struct spi_flash flash,
+ const struct spi_flash_part_id *part,
+ const struct bp_table *table,
+ const struct spi_flash_bpbits *bpbits)
+{
+ const size_t granularity =
+ 1<<part->protection_granularity_shift;
+
+ struct region expected, out = {-1, -1};
+ struct region roundtripped = {-1, -1};
+
+ winbond_bpbits_to_region(granularity, bpbits,
+ flash.size, &out);
+
+ enum spi_flash_status_reg_lockdown mode =
+ SPI_WRITE_PROTECTION_PIN;
+ struct spi_flash_bpbits bpbits1;
+ winbond_region_to_bpbits(&flash, part, &out, mode,
+ &bpbits1);
+
+ winbond_bpbits_to_region(granularity, &bpbits1,
+ flash.size, &roundtripped);
+
+ const struct block_region *wp_blocks =
+ &table->wp_blocks[bpbits->cmp][bpbits->tb][bpbits->bp];
+ const size_t block_size = 1<<16;
+
+ expected.offset = wp_blocks->offset * block_size;
+ expected.size = wp_blocks->size * block_size;
+
+#if 0
+ /* debugging */
+ print_message("\nfor part 0x%04x cmp=%d tb=%d bp=%d:\n",
+ part->id[0], (int)bpbits->cmp, (int)bpbits->tb,
+ (int)bpbits->bp);
+ print_message("should: 0x%08zx-0x%08zx\n",
+ region_offset(&expected),
+ region_end(&expected));
+ print_message("is: 0x%08zx-0x%08zx\n",
+ region_offset(&out),
+ region_end(&out));
+ print_message("rt'ed: 0x%08zx-0x%08zx\n",
+ region_offset(&roundtripped),
+ region_end(&roundtripped));
+ print_message("bp1: %d\n", bpbits1.bp);
+#endif
+
+ assert_true(expected.size == out.size);
+ assert_true(expected.offset == out.offset);
+
+ assert_true(expected.size == roundtripped.size);
+ assert_true(expected.offset == roundtripped.offset);
+}
+
+static void winbond_region_bpbits_roundtrip_test(void **state)
+{
+ int n_cfgs_tested = 0;
+
+ const struct spi_flash_vendor_info *vi = &spi_flash_winbond_vi;
+
+ for (size_t i = 0; i < vi->nr_part_ids; i++) {
+ const struct spi_flash_part_id *part = &vi->ids[i];
+
+ if (!part->protection_granularity_shift)
+ continue;
+
+ /* FIXME: Add tables for the other parts supporting block
+ * protection */
+ if (part->id[0] != 0x7019 && part->id[0] != 0x6018)
+ continue;
+
+ struct spi_slave spi;
+ struct spi_flash flash;
+ int ret = fill_spi_flash(&spi, &flash, vi, part);
+ assert_int_equal(ret, 0);
+
+ const struct bp_table *table = NULL;
+ for (uint16_t j = 0; j < ARRAY_SIZE(bp_table); j++) {
+ if (bp_table[j].id == part->id[0]) {
+ table = &bp_table[j];
+ break;
+ }
+ }
+
+ assert_non_null(table);
+
+ /* FIXME: Max values below are clamped to 6 and 9
+ * respectively because the implementation currently thinks
+ * everything above those is fully protected when the
+ * datasheets say the protection range is empty in those
+ * cases. */
+ u8 bp_max;
+ if (part->bp_bits == 3)
+ bp_max = 6;
+ else if (part->bp_bits == 4)
+ bp_max = 9;
+ else
+ fail_msg("Unrecognized bp_bits");
+
+ for (u8 tbcmp = 0; tbcmp < 4; tbcmp++) {
+ /* FIXME: bp=0 isn't tested because the
+ * implementation is currently wrong */
+ for (u8 bp = 1; bp <= bp_max; bp++) {
+ struct spi_flash_bpbits bpbits = {
+ .cmp = tbcmp & (1<<1),
+ .tb = tbcmp & (1<<0),
+ .bp = bp,
+ };
+
+ test_one_config(
+ state, flash, part, table, &bpbits);
+
+ n_cfgs_tested++;
+ }
+ }
+ }
+
+ assert_true(n_cfgs_tested == 60);
+}
+
+ /* Mocks */
+int do_printk(int msg_level, const char *fmt, ...)
+{
+ return -1;
+}
+int spi_claim_bus(const struct spi_slave *slave)
+{
+ return -1;
+}
+int spi_xfer_vector(const struct spi_slave *slave,
+ struct spi_op vectors[], size_t count)
+{
+ return -1;
+}
+void spi_release_bus(const struct spi_slave *slave)
+{
+}
+unsigned int spi_crop_chunk(const struct spi_slave *slave, unsigned int cmd_len,
+ unsigned int buf_len)
+{
+ return 0;
+}
+struct mono_time;
+void timer_monotonic_get(struct mono_time *mt)
+{
+}
+
+const struct spi_flash_ops_descriptor spi_flash_pp_0x20_sector_desc;
+
+int main(void)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(winbond_region_bpbits_roundtrip_test),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}

To view, visit change 42117. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: coreboot
Gerrit-Branch: master
Gerrit-Change-Id: Ieb0632221203166288f9608c8492c1d4bd77dd36
Gerrit-Change-Number: 42117
Gerrit-PatchSet: 1
Gerrit-Owner: Daniel Gröber (dxld)
Gerrit-Reviewer: Daniel Gröber <dxld@darkboxed.org>
Gerrit-Reviewer: Martin Roth <martinroth@google.com>
Gerrit-Reviewer: Patrick Georgi <pgeorgi@google.com>
Gerrit-MessageType: newchange