Felix Held has uploaded this change for review. ( https://review.coreboot.org/c/coreboot/+/84786?usp=email )
Change subject: [UNTESTED] drivers/spi: add SFDP support to get RPMC parameters ......................................................................
[UNTESTED] drivers/spi: add SFDP support to get RPMC parameters
Signed-off-by: Felix Held felix-coreboot@felixheld.de Change-Id: I5a1706acf7d60fd64292e8f0677992ab4aebf46a --- M src/drivers/spi/Kconfig M src/drivers/spi/Makefile.mk M src/drivers/spi/spi_flash.c M src/drivers/spi/spi_flash_internal.h A src/drivers/spi/spi_flash_sfdp.c M src/include/spi_flash.h 6 files changed, 379 insertions(+), 2 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/86/84786/1
diff --git a/src/drivers/spi/Kconfig b/src/drivers/spi/Kconfig index 86e609c..72fcec5 100644 --- a/src/drivers/spi/Kconfig +++ b/src/drivers/spi/Kconfig @@ -189,6 +189,11 @@ On some platforms with SPI flashes larger than 16MB, the SPI flash may need to remain in 4-byte addressing mode.
+config SPI_FLASH_SFDP + bool + help + Include serial flash discoverable parameters (SFDP) support. + endif # SPI_FLASH
config HAVE_EM100PRO_SPI_CONSOLE_SUPPORT diff --git a/src/drivers/spi/Makefile.mk b/src/drivers/spi/Makefile.mk index 97c3d63..d873c90 100644 --- a/src/drivers/spi/Makefile.mk +++ b/src/drivers/spi/Makefile.mk @@ -31,6 +31,7 @@ $(1)-$(CONFIG_SPI_FLASH_STMICRO) += stmicro.c $(1)-$(CONFIG_SPI_FLASH_WINBOND) += winbond.c $(1)-$(CONFIG_SPI_FLASH_ISSI) += issi.c +$(1)-$(CONFIG_SPI_FLASH_SFDP) += spi_flash_sfdp.c endef
$(eval $(call add_spi_stage,bootblock,_EARLY)) diff --git a/src/drivers/spi/spi_flash.c b/src/drivers/spi/spi_flash.c index 11597c6..c09855f 100644 --- a/src/drivers/spi/spi_flash.c +++ b/src/drivers/spi/spi_flash.c @@ -36,8 +36,8 @@ } }
-static int do_spi_flash_cmd(const struct spi_slave *spi, const u8 *dout, - size_t bytes_out, void *din, size_t bytes_in) +int do_spi_flash_cmd(const struct spi_slave *spi, const u8 *dout, size_t bytes_out, + void *din, size_t bytes_in) { int ret; /* diff --git a/src/drivers/spi/spi_flash_internal.h b/src/drivers/spi/spi_flash_internal.h index 3aea914..1df45d9 100644 --- a/src/drivers/spi/spi_flash_internal.h +++ b/src/drivers/spi/spi_flash_internal.h @@ -7,6 +7,8 @@ #ifndef SPI_FLASH_INTERNAL_H #define SPI_FLASH_INTERNAL_H
+#include <types.h> + /* Common commands */ #define CMD_READ_ID 0x9f
@@ -27,6 +29,10 @@ /* Common status */ #define STATUS_WIP 0x01
+/* Send a multi-byte command to the device and read the response */ +int do_spi_flash_cmd(const struct spi_slave *spi, const u8 *dout, size_t bytes_out, + void *din, size_t bytes_in); + /* 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);
@@ -130,4 +136,27 @@ /* Page Programming Command Set with 0xd8 Sector Erase command. */ extern const struct spi_flash_ops_descriptor spi_flash_pp_0xd8_sector_desc;
+struct sfdp_rpmc_info { + bool valid; + bool flash_hardening; + enum { + SFDP_RPMC_COUNTER_BITS_32 = 0, + SFDP_RPMC_COUNTER_BITS_RESERVED = 1, + } monotonic_counter_size; + enum { + SFDP_RPMC_POLL_OP2_EXTENDED_STATUS = 0, + SFDP_RPMC_POLL_READ_STATUS = 1, + } busy_polling_method; + uint8_t number_of_counters; + uint8_t op1_write_command; + uint8_t op2_read_command; + uint64_t update_rate_s; + uint64_t read_counter_polling_delay_us; + uint64_t write_counter_polling_short_delay_us; + uint64_t write_counter_polling_long_delay_us; +}; + +/* Get RPMC information from the SPI flash's SFDP table */ +enum cb_err get_sfdp_rpmc(const struct spi_flash *flash, struct sfdp_rpmc_info *rpmc_info); + #endif /* SPI_FLASH_INTERNAL_H */ diff --git a/src/drivers/spi/spi_flash_sfdp.c b/src/drivers/spi/spi_flash_sfdp.c new file mode 100644 index 0000000..577c6b1 --- /dev/null +++ b/src/drivers/spi/spi_flash_sfdp.c @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <console/console.h> +#include <spi-generic.h> +#include <spi_flash.h> +#include <types.h> + +#include "spi_flash_internal.h" + +/* + * Assumptions (not) made in the code: + * - code is supposed to be endian-safe + * - read SFDP instruction is sufficient and no fetch SFDP instruction needed + * - TODO: access protocol?! + * - 8 bit SPI isn't supported due to variable number of dummy bytes + * - requested SFDP data blocks are small enough to fit into the SPI controller buffer, so no + * spi_crop_chunk calls are needed + * - TODO?! +*/ + +#define CMD_READ_SFDP 0x5a + +static enum cb_err read_sfdp_data(const struct spi_flash *flash, u32 offset, size_t len, + uint8_t *buf) +{ + uint8_t buf_cmd[5]; + + buf_cmd[0] = CMD_READ_SFDP; + buf_cmd[1] = offset >> 16 & 0xff; + buf_cmd[2] = offset >> 8 & 0xff; + buf_cmd[3] = offset >> 0 & 0xff; + buf_cmd[4] = 0; /* dummy byte */ + + if (do_spi_flash_cmd(&flash->spi, buf_cmd, sizeof(buf_cmd), buf, len)) + return CB_ERR; + + return CB_SUCCESS; +} + +#define SFDP_HEADER_OFFSET 0 +#define SFDP_HEADER_LEN (2 * sizeof(uint32_t)) +#define SFDP_HEADER_SIGNATURE 0x50444653 +/* header byte offsets */ +#define SFDP_HEADER_SIGNATURE_0 0 +#define SFDP_HEADER_SIGNATURE_1 1 +#define SFDP_HEADER_SIGNATURE_2 2 +#define SFDP_HEADER_SIGNATURE_3 3 +#define SFDP_HEADER_MINOR_REV 4 +#define SFDP_HEADER_MAJOR_REV 5 +#define SFDP_HEADER_PARAMETER_HEADER_NUMBER 6 +#define SFDP_HEADER_ACCESS_PROTOCOL 7 + +static enum cb_err get_sfdp_header(const struct spi_flash *flash, uint16_t *sfdp_revision, + uint8_t *number_of_parameter_headers, + uint8_t *access_prototcol) +{ + uint8_t buf[SFDP_HEADER_LEN]; + uint32_t signature; + + if (read_sfdp_data(flash, SFDP_HEADER_OFFSET, SFDP_HEADER_LEN, buf) != CB_SUCCESS) + return CB_ERR; + + signature = buf[SFDP_HEADER_SIGNATURE_0] | buf[SFDP_HEADER_SIGNATURE_1] << 8 | + buf[SFDP_HEADER_SIGNATURE_2] << 16 | buf[SFDP_HEADER_SIGNATURE_3] << 24; + *sfdp_revision = buf[SFDP_HEADER_MINOR_REV] | buf[SFDP_HEADER_MAJOR_REV] << 8; + *number_of_parameter_headers = buf[SFDP_HEADER_PARAMETER_HEADER_NUMBER]; + *access_prototcol = buf[SFDP_HEADER_ACCESS_PROTOCOL]; + + if (signature != SFDP_HEADER_SIGNATURE) + return CB_ERR; + + return CB_SUCCESS; +} + +#define SFDP_PARAMETER_HEADER_LEN (2 * sizeof(uint32_t)) +#define SFDP_PARAMETER_HEADER_OFFSET(n) (SFDP_HEADER_OFFSET + SFDP_HEADER_LEN + \ + n * SFDP_PARAMETER_HEADER_LEN) +/* parameter header byte offsets */ +#define SFDP_PARAMETER_HEADER_ID_LSB 0 +#define SFDP_PARAMETER_HEADER_MINOR_REV 1 +#define SFDP_PARAMETER_HEADER_MAJOR_REV 2 +#define SFDP_PARAMETER_HEADER_LENGTH_DWORDS 3 +#define SFDP_PARAMETER_HEADER_POINTER_0 4 +#define SFDP_PARAMETER_HEADER_POINTER_1 5 +#define SFDP_PARAMETER_HEADER_POINTER_2 6 +#define SFDP_PARAMETER_HEADER_ID_MSB 7 + +/* get_sfdp_parameter_header_by_index must be called with a valid index */ +static enum cb_err get_sfdp_parameter_header_by_index(const struct spi_flash *flash, + uint8_t index, uint16_t *id, + uint16_t *revision, + uint8_t *length_dwords, + uint32_t *table_pointer) +{ + uint8_t buf[SFDP_PARAMETER_HEADER_LEN]; + + if (read_sfdp_data(flash, SFDP_PARAMETER_HEADER_OFFSET(index), + SFDP_PARAMETER_HEADER_LEN, buf) != CB_SUCCESS) + return CB_ERR; + + *id = buf[SFDP_PARAMETER_HEADER_ID_LSB] | buf[SFDP_PARAMETER_HEADER_ID_MSB] << 8; + *revision = buf[SFDP_PARAMETER_HEADER_MINOR_REV] | + buf[SFDP_PARAMETER_HEADER_MAJOR_REV] << 8; + *length_dwords = buf[SFDP_PARAMETER_HEADER_LENGTH_DWORDS]; + *table_pointer = buf[SFDP_PARAMETER_HEADER_POINTER_0] | + buf[SFDP_PARAMETER_HEADER_POINTER_1] << 8 | + buf[SFDP_PARAMETER_HEADER_POINTER_2] << 16; + + /* the parameter table pointer byte address must be DWORD-aligned */ + if (!IS_ALIGNED(*table_pointer, sizeof(uint32_t))) + return CB_ERR; + + /* TODO: check id validity? */ + + return CB_SUCCESS; +} + +void print_sfdp_headers(const struct spi_flash *flash) +{ + enum cb_err stat; + uint16_t sfdp_rev; + uint8_t param_header_count; + uint8_t access_protocol; + + if (get_sfdp_header(flash, &sfdp_rev, ¶m_header_count, &access_protocol) != + CB_SUCCESS) { + printk(BIOS_ERR, "Failed to read SFDP header from SPI flash\n"); + return; + } + + printk(BIOS_DEBUG, "SFDP header found in SPI flash.\n"); + printk(BIOS_DEBUG, "major rev %#x, minor rev %#x, access protocol %#x, " + "number of headers %d", sfdp_rev >> 8, sfdp_rev & 0xff, access_protocol, + param_header_count); + + uint16_t tbl_id; + uint16_t tbl_rev; + uint8_t tbl_len_dwords; + uint32_t tbl_pointer; + + for (unsigned int i = 0; i < param_header_count; i++) { + stat = get_sfdp_parameter_header_by_index(flash, i, &tbl_id, &tbl_rev, + &tbl_len_dwords, &tbl_pointer); + + if (stat == CB_SUCCESS) { + printk(BIOS_DEBUG, "SFPD header with index %d:\n", i); + printk(BIOS_DEBUG, " table ID %#x, major rev %#x, minor rev %#x\n", + tbl_id, tbl_rev >> 8, tbl_rev & 0xff); + printk(BIOS_DEBUG, " table pointer %#x, table length DWORDS %#x\n", + tbl_pointer, tbl_len_dwords); + } else { + printk(BIOS_ERR, "Cound't read SFPD header with index %d.\n", i); + } + } +} + +static enum cb_err find_sfdp_parameter_header(const struct spi_flash *flash, uint16_t table_id, + uint16_t *revision, uint8_t *length_dwords, + uint32_t *table_pointer) +{ + enum cb_err stat; + uint16_t sfdp_rev; + uint8_t param_header_count; + uint8_t access_protocol; + + if (get_sfdp_header(flash, &sfdp_rev, ¶m_header_count, &access_protocol) != CB_SUCCESS) + return CB_ERR; + + /* TODO: add version & access protocol check? */ + + uint16_t tbl_id; + uint16_t tbl_rev; + uint8_t tbl_len_dwords; + uint32_t tbl_pointer; + + for (unsigned int i = 0; i < param_header_count; i++) { + stat = get_sfdp_parameter_header_by_index(flash, i, &tbl_id, &tbl_rev, + &tbl_len_dwords, &tbl_pointer); + + if (stat == CB_SUCCESS && tbl_id == table_id) { + *revision = tbl_rev; + *length_dwords = tbl_len_dwords; + *table_pointer = tbl_pointer; + return CB_SUCCESS; + } + } + + return CB_ERR; +} + +#define SFDP_PARAMETER_ID_RPMC 0xff03 + +#define SFDP_RPMC_TABLE_LENGTH_DWORDS 2 +#define SFDP_RPMC_TABLE_SUPPORTED_MAJOR_REV 1 + +/* RPMC parameter table byte offsets and fields */ +#define SFDP_RPMC_TABLE_CONFIG 0 +#define SFDP_RPMC_TABLE_CONFIG_FLASH_HARDENING_BIT BIT(0) +#define SFDP_RPMC_TABLE_CONFIG_MONOTONIC_COUNTER_SIZE_BIT BIT(1) +#define SFDP_RPMC_TABLE_CONFIG_BUSY_POLLING_METHOD BIT(2) +#define SFDP_RPMC_TABLE_CONFIG_RESERVED BIT(3) +#define SFDP_RPMC_TABLE_CONFIG_RESERVED_VALUE 0x08 +#define SFDP_RPMC_TABLE_CONFIG_NUM_COUNTERS_MASK 0xf0 +#define SFDP_RPMC_TABLE_CONFIG_NUM_COUNTERS_SHIFT 4 +#define SFDP_RPMC_TABLE_RPMC_OP1 1 +#define SFDP_RPMC_TABLE_RPMC_OP2 2 +#define SFDP_RPMC_TABLE_UPDATE_RATE 3 +#define SFDP_RPMC_TABLE_UPDATE_RATE_MASK 0x0f +#define SFDP_RPMC_TABLE_UPDATE_RATE_RESERVED_MASK 0xf0 +#define SFDP_RPMC_TABLE_UPDATE_RATE_RESERVED_VALUE 0xf0 +#define SFDP_RPMC_TABLE_READ_COUNTER_POLLING_DELAY 4 +#define SFDP_RPMC_TABLE_WRITE_COUNTER_POLLING_SHORT_DELAY 5 +#define SFDP_RPMC_TABLE_WRITE_COUNTER_POLLING_LONG_DELAY 6 +#define SFDP_RPMC_TABLE_RESERVED_BYTE 7 +#define SFDP_RPMC_TABLE_RESERVED_BYTE_VALUE 0xff + +static uint64_t calc_rpmc_update_rate_s(uint8_t val) +{ + /* val is at most 15, so this won't overflow */ + return 5 * 1 << val; +} + +#define SPDF_RPMC_DELAY_VALUE_MASK 0x1f +#define SPDF_RPMC_DELAY_UNIT_MASK 0x60 +#define SPDF_RPMC_DELAY_UNIT_SHIFT 5 +#define SPDF_RPMC_DELAY_SHORT_UNIT_0_US 1 /* 1us */ +#define SPDF_RPMC_DELAY_SHORT_UNIT_1_US 16 /* 16us */ +#define SPDF_RPMC_DELAY_SHORT_UNIT_2_US 128 /* 128us */ +#define SPDF_RPMC_DELAY_SHORT_UNIT_3_US 1000 /* 1ms */ +#define SPDF_RPMC_DELAY_LONG_UNIT_0_US 1000 /* 1ms */ +#define SPDF_RPMC_DELAY_LONG_UNIT_1_US 16000 /* 16ms */ +#define SPDF_RPMC_DELAY_LONG_UNIT_2_US 128000 /* 128ms */ +#define SPDF_RPMC_DELAY_LONG_UNIT_3_US 1000000 /* 1s */ + +static uint64_t calc_rpmc_short_delay_us(uint8_t val) +{ + const uint8_t value = val & SPDF_RPMC_DELAY_VALUE_MASK; + const uint8_t shift = val & SPDF_RPMC_DELAY_UNIT_MASK >> SPDF_RPMC_DELAY_UNIT_SHIFT; + uint64_t multiplicator; + + switch(shift) { + case 0: + multiplicator = SPDF_RPMC_DELAY_SHORT_UNIT_0_US; + break; + case 1: + multiplicator = SPDF_RPMC_DELAY_SHORT_UNIT_1_US; + break; + case 2: + multiplicator = SPDF_RPMC_DELAY_SHORT_UNIT_2_US; + break; + default: + multiplicator = SPDF_RPMC_DELAY_SHORT_UNIT_3_US; + break; + } + + return value * multiplicator; +} + +static uint64_t calc_rpmc_long_delay_us(uint8_t val) +{ + const uint8_t value = val & SPDF_RPMC_DELAY_VALUE_MASK; + const uint8_t shift = val & SPDF_RPMC_DELAY_UNIT_MASK >> SPDF_RPMC_DELAY_UNIT_SHIFT; + uint64_t multiplicator; + + switch(shift) { + case 0: + multiplicator = SPDF_RPMC_DELAY_LONG_UNIT_0_US; + break; + case 1: + multiplicator = SPDF_RPMC_DELAY_LONG_UNIT_1_US; + break; + case 2: + multiplicator = SPDF_RPMC_DELAY_LONG_UNIT_2_US; + break; + default: + multiplicator = SPDF_RPMC_DELAY_LONG_UNIT_3_US; + break; + } + + return value * multiplicator; +} + +enum cb_err get_sfdp_rpmc(const struct spi_flash *flash, struct sfdp_rpmc_info *rpmc_info) +{ + uint16_t rev; + uint8_t length_dwords; + uint32_t table_pointer; + uint8_t buf[SFDP_RPMC_TABLE_LENGTH_DWORDS * sizeof(uint32_t)]; + + if (find_sfdp_parameter_header(flash, SFDP_PARAMETER_ID_RPMC, &rev, &length_dwords, + &table_pointer) != CB_SUCCESS) + return CB_ERR; + + if (length_dwords != SFDP_RPMC_TABLE_LENGTH_DWORDS) + return CB_ERR; + + if (rev >> 8 != SFDP_RPMC_TABLE_SUPPORTED_MAJOR_REV) { + printk(BIOS_ERR, "Unsupprted major RPMC table revision %#x\n", rev >> 8); + return CB_ERR; + } + + if (read_sfdp_data(flash, table_pointer, sizeof(buf), buf) != CB_SUCCESS) + return CB_ERR; + + if ((buf[SFDP_RPMC_TABLE_CONFIG] & SFDP_RPMC_TABLE_CONFIG_RESERVED) != + SFDP_RPMC_TABLE_CONFIG_RESERVED_VALUE || + (buf[SFDP_RPMC_TABLE_UPDATE_RATE] & SFDP_RPMC_TABLE_UPDATE_RATE_RESERVED_MASK) != + SFDP_RPMC_TABLE_UPDATE_RATE_RESERVED_VALUE || + buf[SFDP_RPMC_TABLE_RESERVED_BYTE] != SFDP_RPMC_TABLE_RESERVED_BYTE_VALUE) { + printk(BIOS_ERR, "Unexpected reserved values in RPMC table\n"); + return CB_ERR; + } + + rpmc_info->flash_hardening = !!(buf[SFDP_RPMC_TABLE_CONFIG] & + SFDP_RPMC_TABLE_CONFIG_FLASH_HARDENING_BIT); + rpmc_info->monotonic_counter_size = (buf[SFDP_RPMC_TABLE_CONFIG] & + SFDP_RPMC_TABLE_CONFIG_FLASH_HARDENING_BIT) ? + SFDP_RPMC_COUNTER_BITS_RESERVED : + SFDP_RPMC_COUNTER_BITS_32; + rpmc_info->busy_polling_method = (buf[SFDP_RPMC_TABLE_CONFIG] & + SFDP_RPMC_TABLE_CONFIG_BUSY_POLLING_METHOD) ? + SFDP_RPMC_POLL_READ_STATUS : + SFDP_RPMC_POLL_OP2_EXTENDED_STATUS; + rpmc_info->number_of_counters = ((buf[SFDP_RPMC_TABLE_CONFIG] & + SFDP_RPMC_TABLE_CONFIG_NUM_COUNTERS_MASK) >> + SFDP_RPMC_TABLE_CONFIG_NUM_COUNTERS_SHIFT) + 1; + rpmc_info->op1_write_command = buf[SFDP_RPMC_TABLE_RPMC_OP1]; + rpmc_info->op2_read_command = buf[SFDP_RPMC_TABLE_RPMC_OP2]; + rpmc_info->update_rate_s = calc_rpmc_update_rate_s(buf[SFDP_RPMC_TABLE_UPDATE_RATE] & + SFDP_RPMC_TABLE_UPDATE_RATE_MASK); + rpmc_info->read_counter_polling_delay_us = calc_rpmc_short_delay_us( + buf[SFDP_RPMC_TABLE_READ_COUNTER_POLLING_DELAY]); + rpmc_info->write_counter_polling_short_delay_us = calc_rpmc_short_delay_us( + buf[SFDP_RPMC_TABLE_WRITE_COUNTER_POLLING_SHORT_DELAY]); + rpmc_info->write_counter_polling_long_delay_us = calc_rpmc_long_delay_us( + buf[SFDP_RPMC_TABLE_WRITE_COUNTER_POLLING_LONG_DELAY]); + rpmc_info->valid = true; + return CB_SUCCESS; +} diff --git a/src/include/spi_flash.h b/src/include/spi_flash.h index e0d5b2b..0d3ebf0 100644 --- a/src/include/spi_flash.h +++ b/src/include/spi_flash.h @@ -249,4 +249,7 @@ */ uint32_t spi_flash_get_mmap_windows(struct flash_mmap_window *table);
+/* Print the SFDP headers read from the SPI flash */ +void print_sfdp_headers(const struct spi_flash *flash); + #endif /* _SPI_FLASH_H_ */