[flashrom] [PATCH] add support for SFDP (JESD216)

Stefan Tauner stefan.tauner at student.tuwien.ac.at
Tue Jan 10 23:58:19 CET 2012


Similar to ICH Hardware Sequencing this uses a generic struct
flashchip element in flashchips.c with dummy values and a special
probe function that fills the obtained values into that generic
struct.

Documentation used:
http://www.jedec.org/standards-documents/docs/jesd216 (2011-04)
W25Q32BV data sheet Revision F (2011-04-01)
EN25QH16 data sheet Revision F (2011-06-01)

todo:
 - handle programmers which have a problem with the dummy bytes needed
 - move the code from spi25.c to its own file
 - rephrase the 'default' message of get_erasefn_from_opcode so that it becomes universally usable?

Signed-off-by: Stefan Tauner <stefan.tauner at student.tuwien.ac.at>
---
 chipdrivers.h |    1 +
 flash.h       |    2 +
 flashchips.c  |   24 ++++
 flashchips.h  |    1 +
 flashrom.c    |   28 +++++-
 spi.h         |    5 +
 spi25.c       |  355 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 415 insertions(+), 1 deletions(-)

diff --git a/chipdrivers.h b/chipdrivers.h
index a1d0cd9..bbc403f 100644
--- a/chipdrivers.h
+++ b/chipdrivers.h
@@ -28,6 +28,7 @@
 #include "flash.h"	/* for chipaddr and flashctx */
 
 /* spi.c, should probably be in spi_chip.c */
+int probe_spi_sfdp(struct flashctx *flash);
 int probe_spi_rdid(struct flashctx *flash);
 int probe_spi_rdid4(struct flashctx *flash);
 int probe_spi_rems(struct flashctx *flash);
diff --git a/flash.h b/flash.h
index e51b6d4..6bcae71 100644
--- a/flash.h
+++ b/flash.h
@@ -174,6 +174,8 @@ struct flashctx {
 	struct registered_programmer *pgm;
 };
 
+typedef int (erasefunc_t)(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
+
 #define TEST_UNTESTED	0
 
 #define TEST_OK_PROBE	(1 << 0)
diff --git a/flashchips.c b/flashchips.c
index ca1c57f..f0fde20 100644
--- a/flashchips.c
+++ b/flashchips.c
@@ -8872,6 +8872,30 @@ const struct flashchip flashchips[] = {
 		.read		= read_memmapped,
 		.voltage	= {3000, 3600}, /* Also has 12V fast program */
 	},
+	
+	{
+		.vendor		= "Unknown",
+		.name		= "SFDP device",
+		.bustype	= BUS_SPI,
+		.manufacture_id	= GENERIC_MANUF_ID,
+		.model_id	= SFDP_DEVICE_ID,
+		/* We present our own "report this" text hence we do not
+		 * want the default "This flash part has status UNTESTED..."
+		 * text to be printed. */
+		.tested		= TEST_OK_PREW,
+		.probe		= probe_spi_sfdp,
+		.read		= spi_chip_read,
+		/* FIXME: some vendor extensions define this */
+		.voltage	= {},
+		 /* Everything below will be set by the probing function. */
+		.page_size	= 0,
+		.write		= NULL,
+		.total_size	= 0,
+		.feature_bits	= 0,
+		.block_erasers	= {},
+		.unlock		= NULL,
+		.printlock	= NULL,
+	},
 
 	{
 		.vendor		= "Programmer",
diff --git a/flashchips.h b/flashchips.h
index 03efb86..1f2a8ca 100644
--- a/flashchips.h
+++ b/flashchips.h
@@ -36,6 +36,7 @@
 
 #define GENERIC_MANUF_ID	0xffff	/* Check if there is a vendor ID */
 #define GENERIC_DEVICE_ID	0xffff	/* Only match the vendor ID */
+#define SFDP_DEVICE_ID		0xfffe
 
 #define ALLIANCE_ID		0x52	/* Alliance Semiconductor */
 #define ALLIANCE_AS29F002B	0x34
diff --git a/flashrom.c b/flashrom.c
index f1a6165..38998dd 100644
--- a/flashrom.c
+++ b/flashrom.c
@@ -986,7 +986,33 @@ int probe_flash(struct registered_programmer *pgm, int startchip,
 		 * probe_flash() is the first one and thus no chip has been
 		 * found before.
 		 */
-		if (startchip == 0 || fill_flash->model_id != GENERIC_DEVICE_ID)
+		if (startchip == 0 && fill_flash->model_id == SFDP_DEVICE_ID) {
+			msg_cinfo("===\n"
+				  "SFDP has autodetected a flash chip which is "
+				  "not natively supported by flashrom yet.\n");
+			if (count_usable_erasers(fill_flash) == 0)
+				msg_cinfo("The standard operations read and "
+					  "verify should work, but to support "
+					  "erase, write and all other "
+					  "possible features");
+			else
+				msg_cinfo("All standard operations (read, "
+					  "verify, erase and write) should "
+					  "work, but to support all possible "
+					  "features");
+
+			msg_cinfo(" we need to add them manually.\nYou "
+				  "can help us by mailing us the output of "
+				  "the following command to flashrom at flashrom."
+				  "org: \n'flashrom -VV [plus the "
+				  "-p/--programmer parameter (if needed)]"
+				  "'\nThanks for your help!\n"
+				  "===\n");
+		}
+
+		if (startchip == 0 ||
+		    ((fill_flash->model_id != GENERIC_DEVICE_ID) &&
+		     (fill_flash->model_id != SFDP_DEVICE_ID)))
 			break;
 
 notfound:
diff --git a/spi.h b/spi.h
index b908603..5f07eae 100644
--- a/spi.h
+++ b/spi.h
@@ -40,6 +40,11 @@
 #define JEDEC_REMS_OUTSIZE	0x04
 #define JEDEC_REMS_INSIZE	0x02
 
+/* Read Serial Flash Discoverable Parameters (SFDP) */
+#define JEDEC_SFDP		0x5a
+#define JEDEC_SFDP_OUTSIZE	0x05	/* 8b op, 24b addr, 8b dummy */
+/*      JEDEC_SFDP_INSIZE : any length */
+
 /* Read Electronic Signature */
 #define JEDEC_RES		0xab
 #define JEDEC_RES_OUTSIZE	0x04
diff --git a/spi25.c b/spi25.c
index 3ce7f08..07c716a 100644
--- a/spi25.c
+++ b/spi25.c
@@ -23,6 +23,7 @@
  */
 
 #include <string.h>
+#include <stdlib.h>
 #include "flash.h"
 #include "flashchips.h"
 #include "chipdrivers.h"
@@ -115,6 +116,360 @@ int spi_write_disable(struct flashctx *flash)
 	return spi_send_command(flash, sizeof(cmd), 0, cmd, NULL);
 }
 
+static int spi_sfdp_wrapper(struct flashctx *flash, uint32_t address, uint8_t *buf, int len)
+{
+	int i, ret;
+	const unsigned char cmd[JEDEC_SFDP_OUTSIZE] = {
+		JEDEC_SFDP,
+		(address >> 16) & 0xff,
+		(address >> 8) & 0xff,
+		(address >> 0) & 0xff,
+		0
+	};
+	msg_cspew("spi_sfdp_wrapper: addr=0x%x, len=%d, data:\n", address, len);
+	ret = spi_send_command(flash, sizeof(cmd), len, cmd, buf);
+	for (i = 0; i < len; i++)
+		msg_cspew(" 0x%02x", buf[i]);
+	msg_cspew("\n");
+	return ret;
+}
+
+/* FIXME: eventually something similar like this but more generic should be
+ * available to split up spi commands. use that then instead */
+static int spi_sfdp(struct flashctx *flash, uint32_t address, uint8_t *buf, int len)
+{
+	/* FIXME: this is wrong. */
+	int maxstep = 8;
+	int ret = 0;
+	while (len > 0) {
+		int step = min(len, maxstep);
+		ret = spi_sfdp_wrapper(flash, address, buf, step);
+		if (ret)
+			return ret;
+		address += step;
+		buf += step;
+		len -= step;
+	}
+	return ret;
+}
+
+struct sfdp_tbl_hdr {
+	uint8_t id;
+	uint8_t v_minor;
+	uint8_t v_major;
+	uint8_t len;
+	uint32_t ptp; /* 24b pointer */
+};
+
+static int (*get_erasefn_from_opcode(uint8_t opcode)) (struct flashctx *flash, unsigned int blockaddr, unsigned int blocklen)
+{
+	switch(opcode){
+	case 0x00:
+	case 0xff:
+		/* Not specified, assuming "not supported". */
+		return NULL;
+	case 0x20:
+		return &spi_block_erase_20;
+		break;
+	case 0x52:
+		return &spi_block_erase_52;
+		break;
+	case 0x60:
+		return &spi_block_erase_60;
+		break;
+	case 0xc7:
+		return &spi_block_erase_c7;
+		break;
+	case 0xd7:
+		return &spi_block_erase_d7;
+		break;
+	case 0xd8:
+		return &spi_block_erase_d8;
+	default:
+		msg_cinfo("%s: unknown opcode (0x%02x) in SFDP table. "
+			 "Please report this at "
+			 "flashrom at flashrom.org\n",
+			 __func__, opcode);
+		return NULL;
+	}
+}
+
+static int sfdp_fill_flash(struct flashctx *f, uint8_t *buf, uint16_t len)
+{
+	uint32_t tmp32;
+	uint8_t tmp8;
+	uint32_t total_size; /* in bytes */
+	uint32_t bsize;
+	uint8_t opcode_4k = 0xFF;
+	int dw, j;
+	int (*erasefn) (struct flashctx *flash, unsigned int blockaddr, unsigned int blocklen);
+
+
+	msg_cdbg2("Parsing JEDEC SFDP parameter table...\n");
+	if (len == 9 * 4) {
+		msg_cerr("%s: len out of spec\n", __func__);
+		return 1;
+	}
+
+	/* 1. double word */
+	dw = 0;
+	tmp32 = buf[(4 * dw) + 0];
+	tmp32 |= ((unsigned int)buf[(4 * dw) + 1]) << 8;
+	tmp32 |= ((unsigned int)buf[(4 * dw) + 2]) << 16;
+	tmp32 |= ((unsigned int)buf[(4 * dw) + 3]) << 24;
+
+	tmp8 = (tmp32 >> 17) & 0x3;
+	switch (tmp8) {
+	case 0x0:
+		msg_cdbg2("  3-Byte only addressing.\n");
+		break;
+	case 0x1:
+		msg_cdbg2("  3-Byte (and optionally 4-Byte) addressing.\n");
+		break;
+	case 0x2:
+		msg_cdbg("  4-Byte only addressing not supported.\n");
+		return 1;
+	default:
+		msg_cdbg("  Required addressing mode (0x%x) not supported.\n",
+			 tmp8);
+		return 1;
+	}
+
+	msg_cdbg2("  Writes to the status register have ");
+	if (tmp32 & (1 << 3)) {
+		f->unlock = spi_disable_blockprotect;
+		msg_cdbg2("to be enabled with ");
+		if (tmp32 & (1 << 4)) {
+			f->feature_bits = FEATURE_WRSR_WREN;
+			msg_cdbg2("WREN (0x06).\n");
+		} else {
+			f->feature_bits = FEATURE_WRSR_EWSR;
+			msg_cdbg2("EWSR (0x50).\n");
+		}
+	} else
+		msg_cdbg2("not to be especially enabled.\n");
+
+	msg_cdbg2("  Write granularity is ");
+	if (tmp32 & (1 << 2)) {
+		msg_cdbg2(" at least 64 B.\n");
+		f->page_size = 64;
+		f->write = spi_chip_write_256;
+	} else {
+		msg_cdbg2(" 1 B only.\n");
+		f->page_size = 256; /* ? */
+		f->write = spi_chip_write_1;
+	}
+
+	if ((tmp32 & 0x3) == 0x1) {
+		opcode_4k = (tmp32 >> 8) & 0xFF; /* will be dealt with later */
+	}
+
+	/* 2. double word */
+	dw = 1;
+	tmp32 = buf[(4 * dw) + 0];
+	tmp32 |= ((unsigned int)buf[(4 * dw) + 1]) << 8;
+	tmp32 |= ((unsigned int)buf[(4 * dw) + 2]) << 16;
+	tmp32 |= ((unsigned int)buf[(4 * dw) + 3]) << 24;
+
+	if (tmp32 & (1 << 31)) {
+		msg_cdbg("  Flash chip size >= 4 Gb/500 MB not supported.\n");
+		return 1;
+	}
+	total_size = (tmp32 & 0x7FFFFFFF) / 8;
+	f->total_size = total_size / 1024;
+	msg_cdbg2("  Flash chip size is %d kB.\n", f->total_size);
+
+	dw = 8;
+	for(j = 0; j < 4; j++) {
+		/* 8 double words from the start + 2 words for every eraser */
+		tmp32 = buf[(4 * dw) + (2 * j)];
+		if (tmp32 == 0) {
+			msg_cdbg2("  Block eraser %d is unused.\n", j);
+			continue;
+		}
+		if (tmp32 >= 31) {
+			msg_cdbg2("  Block size of eraser %d (2^%d) is too big."
+				  "\n", j, tmp32);
+			continue;
+		}
+		bsize = 1 << (tmp32); /* bsize = 2 ^ field */
+
+		tmp8 = buf[(4 * dw) + (2 * j) + 1];
+		erasefn = get_erasefn_from_opcode(tmp8);
+		if (erasefn == NULL)
+			continue;
+		f->block_erasers[j].block_erase = erasefn;
+		f->block_erasers[j].eraseblocks[0].size = bsize;
+		f->block_erasers[j].eraseblocks[0].count = total_size/bsize;
+		msg_cdbg2("  Block eraser %d: %d x %d B with opcode 0x%02x\n",
+			  j, total_size/bsize, bsize, tmp8);
+		/* If there is a valid 4k value in the last double words,
+		 * we override the value from double word 1. */
+		if (bsize == 4 * 1024)
+			opcode_4k = 0xFF;
+	}
+	if (opcode_4k != 0xFF &&
+	    ((erasefn = get_erasefn_from_opcode(opcode_4k)) != NULL)) {
+		for (j = 0; j < NUM_ERASEFUNCTIONS; j++) {
+			struct block_eraser eraser = f->block_erasers[j];
+			if (eraser.eraseblocks[0].size != 0)
+				continue;
+			eraser.block_erase = erasefn;
+			eraser.eraseblocks[0].size = 4 * 1024;
+			eraser.eraseblocks[0].count = total_size/bsize;
+			msg_cdbg2("  Block eraser %d: %d x %d B with opcode "
+				  "0x%02x\n", j, total_size/bsize, bsize,
+				  opcode_4k);
+			break;
+		}
+		msg_cinfo("%s: Not enough space to store another eraser (j=%d)."
+			  " Please report this at flashrom at flashrom.org\n",
+			  __func__, j);
+	}
+	return 0;
+}
+
+static int sfdp_fetch_pt(struct flashctx *flash, uint32_t addr, uint8_t *buf, uint16_t len)
+{
+	uint16_t i;
+	if (spi_sfdp(flash, addr, buf, len)) {
+		msg_cerr("Receiving SFDP parameter table failed.\n");
+		return 1;
+	}
+	msg_cspew("  Parameter table contents:\n");
+	for(i = 0; i < len; i++) {
+		if ((i % 8) == 0) {
+			msg_cspew("    0x%03x: ", i);
+		}
+		msg_cspew(" 0x%02x", buf[i]);
+		if ((i % 8) == 7) {
+			msg_cspew("\n");
+			continue;
+		}
+		if ((i % 8) == 3) {
+			msg_cspew(" ");
+			continue;
+		}
+	}
+	msg_cspew("\n");
+	return 0;
+}
+
+int probe_spi_sfdp(struct flashctx *flash)
+{
+	int ret = 0;
+	uint8_t buf[8];
+	uint16_t tmp16;
+	uint32_t tmp32;
+	uint8_t nph;
+	/* need to limit the table loop by comparing i to uint8_t nph hence: */
+	uint16_t i;
+	struct sfdp_tbl_hdr *hdrs;
+	uint8_t *hbuf;
+	uint8_t *tbuf;
+
+	if (spi_sfdp(flash, 0x00, buf, 4)) {
+		msg_cerr("Receiving SFDP signature failed.\n");
+		return 0;
+	}
+	tmp32 = buf[0];
+	tmp32 |= ((unsigned int)buf[1]) << 8;
+	tmp32 |= ((unsigned int)buf[2]) << 16;
+	tmp32 |= ((unsigned int)buf[3]) << 24;
+
+	msg_cdbg2("SFDP signature = 0x%08x (should be 0x50444653)\n", tmp32);
+	if (tmp32 != 0x50444653) {
+		msg_cdbg("No SFDP signature found.\n");
+		return 0;
+	}
+	if (spi_sfdp(flash, 0x04, buf, 3)) {
+		msg_cerr("Receiving SFDP revision and number of parameter "
+			 "headers (NPH) failed. ");
+		return 0;
+	}
+	msg_cdbg2("SFDP revision = %d.%d\n", buf[1], buf[0]);
+	nph = buf[3];
+	msg_cdbg2("SFDP number of parameter headers (NPH) = %d (+ 1 mandatory)"
+		  "\n", nph);
+
+	/* Fetch all parameter headers, even if we don't use them all (yet). */
+	hbuf = malloc(sizeof(struct sfdp_tbl_hdr) * (nph + 1));
+	hdrs = malloc((nph + 1) * 8);
+	if (hbuf == NULL || hdrs == NULL ) {
+		msg_gerr("Out of memory!\n");
+		exit(1); /* FIXME: shutdown gracefully */
+	}
+	if (spi_sfdp(flash, 0x08, hbuf, (nph + 1) * 8)) {
+		msg_cerr("Receiving SFDP parameter table headers failed.\n");
+		goto cleanup_hdrs;
+	}
+
+	i = 0;
+	do{
+		hdrs[i].id = hbuf[(8 * i) + 0];
+		hdrs[i].v_minor = hbuf[(8 * i) + 1];
+		hdrs[i].v_major = hbuf[(8 * i) + 2];
+		hdrs[i].len = hbuf[(8 * i) + 3];
+		hdrs[i].ptp = hbuf[(8 * i) + 4];
+		hdrs[i].ptp |= ((unsigned int)hbuf[(8 * i) + 5]) << 8;
+		hdrs[i].ptp |= ((unsigned int)hbuf[(8 * i) + 6]) << 16;
+		msg_cdbg2("SFDP parameter table header %d/%d:\n", i, nph);
+		msg_cdbg2("  ID 0x%02x, version %d.%d\n", hdrs[i].id,
+			  hdrs[i].v_major, hdrs[i].v_minor);
+		tmp16 = hdrs[i].len * 4;
+		tmp32 = hdrs[i].ptp;
+		msg_cdbg2("  Length %d B, Parameter Table Pointer 0x%06x\n",
+			  tmp16, tmp32);
+
+		if (tmp16 + tmp32 > UINT16_MAX) {
+			msg_cerr("SFDP Parameter Table %d supposedly "
+				  "overflows addressable SFDP area. This most\n"
+				  "probably indicates a corrupt SFDP parameter "
+				  "table header. Aborting SFDP probe!\n", i);
+			ret = 0;
+			goto cleanup_hdrs;
+		}
+
+		tbuf = malloc(tmp16);
+		if (tbuf == NULL) {
+			msg_gerr("Out of memory!\n");
+			exit(1); /* FIXME: shutdown gracefully */
+		}
+		if (sfdp_fetch_pt(flash, tmp32, tbuf, tmp16)){
+			msg_cerr("Fetching SFDP parameter table %d failed.\n",
+				 i);
+			free(tbuf);
+			break;
+		}
+		if (i == 0) { /* Mandatory JEDEC SFDP parameter table */
+			if (hdrs[i].id != 0)
+				msg_cdbg("ID of the mandatory JEDEC SFDP "
+					 "parameter table is not 0 as demanded "
+					 "by JESD216 (warning only).\n");
+
+			if (hdrs[i].len != (9 * 4)) {
+				msg_cdbg("Length of the mandatory JEDEC SFDP "
+					 "parameter table is not 24 B as "
+					 "demanded by JESD216.\n");
+				if (hdrs[i].len == (4 * 4))
+					msg_cdbg("It seems like it is the "
+						 "preliminary Intel version of "
+						 "SFDP, which we don't support."
+						 "\n");
+			} else if (sfdp_fill_flash(flash, tbuf, tmp16) == 0)
+				ret = 1;
+		}
+		
+		free(tbuf);
+		i++;
+	} while(i <= nph);
+
+cleanup_hdrs:
+	free(hdrs);
+	free(hbuf);
+	return ret;
+}
+
 static int probe_spi_rdid_generic(struct flashctx *flash, int bytes)
 {
 	unsigned char readarr[4];
-- 
1.7.1





More information about the flashrom mailing list