[flashrom] [PATCH] add support for SFDP (JESD216)
Stefan Tauner
stefan.tauner at student.tuwien.ac.at
Sun Jan 29 14:52:47 CET 2012
Similar to modules using the opaque programmer framework (e.g. 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 sfdp_add_uniform_eraser to flashchips.c?
Signed-off-by: Stefan Tauner <stefan.tauner at student.tuwien.ac.at>
---
Makefile | 2 +-
chipdrivers.h | 4 +
flash.h | 2 +
flashchips.c | 24 ++++
flashchips.h | 1 +
flashrom.c | 28 +++++-
sfdp.c | 356 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
spi.h | 5 +
spi25.c | 31 +++++
9 files changed, 451 insertions(+), 2 deletions(-)
create mode 100644 sfdp.c
diff --git a/Makefile b/Makefile
index 83aa038..b890d8a 100644
--- a/Makefile
+++ b/Makefile
@@ -252,7 +252,7 @@ endif
CHIP_OBJS = jedec.o stm50flw0x0x.o w39.o w29ee011.o \
sst28sf040.o m29f400bt.o 82802ab.o pm49fl00x.o \
sst49lfxxxc.o sst_fwhub.o flashchips.o spi.o spi25.o sharplhf00l04.o \
- a25.o at25.o opaque.o
+ a25.o at25.o opaque.o sfdp.o
LIB_OBJS = layout.o
diff --git a/chipdrivers.h b/chipdrivers.h
index a1d0cd9..bd81098 100644
--- a/chipdrivers.h
+++ b/chipdrivers.h
@@ -41,6 +41,7 @@ int spi_block_erase_d7(struct flashctx *flash, unsigned int addr, unsigned int b
int spi_block_erase_d8(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
int spi_block_erase_60(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
int spi_block_erase_c7(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
+erasefunc_t *spi_get_erasefn_from_opcode(uint8_t opcode);
int spi_chip_write_1(struct flashctx *flash, uint8_t *buf, unsigned int start, unsigned int len);
int spi_chip_write_256(struct flashctx *flash, uint8_t *buf, unsigned int start, unsigned int len);
int spi_chip_read(struct flashctx *flash, uint8_t *buf, unsigned int start, int unsigned len);
@@ -58,6 +59,9 @@ int spi_read_chunked(struct flashctx *flash, uint8_t *buf, unsigned int start, u
int spi_write_chunked(struct flashctx *flash, uint8_t *buf, unsigned int start, unsigned int len, unsigned int chunksize);
int spi_aai_write(struct flashctx *flash, uint8_t *buf, unsigned int start, unsigned int len);
+/* sfdp.c */
+int probe_spi_sfdp(struct flashctx *flash);
+
/* opaque.c */
int probe_opaque(struct flashctx *flash);
int read_opaque(struct flashctx *flash, uint8_t *buf, unsigned int start, unsigned int len);
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 ee68344..84fb3fc 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/sfdp.c b/sfdp.c
new file mode 100644
index 0000000..4687737
--- /dev/null
+++ b/sfdp.c
@@ -0,0 +1,356 @@
+/*
+ * This file is part of the flashrom project.
+ *
+ * Copyright (C) 2011-2012 Stefan Tauner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include "flash.h"
+#include "spi.h"
+#include "chipdrivers.h"
+
+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 sfdp_add_uniform_eraser(struct flashctx *f, uint8_t opcode, uint32_t bsize)
+{
+ uint32_t total_size = f->total_size * 1024;
+ int i;
+ erasefunc_t *erasefn = spi_get_erasefn_from_opcode(opcode);
+ if (erasefn == NULL)
+ return 1;
+ for (i = 0; i < NUM_ERASEFUNCTIONS; i++) {
+ struct block_eraser eraser = f->block_erasers[i];
+ if (eraser.eraseblocks[0].size != 0)
+ continue;
+ eraser.block_erase = erasefn;
+ eraser.eraseblocks[0].size = bsize;
+ eraser.eraseblocks[0].count = total_size/bsize;
+ msg_cdbg2(" Block eraser %d: %d x %d B with opcode "
+ "0x%02x\n", i, total_size/bsize, bsize,
+ opcode);
+ return 0;
+ }
+ msg_cinfo("%s: Not enough space to store another eraser (i=%d)."
+ " Please report this at flashrom at flashrom.org\n",
+ __func__, i);
+ return 1;
+}
+
+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;
+
+ msg_cdbg2("Parsing JEDEC SFDP parameter table...\n");
+ if (len != 9 * 4 && len != 4 * 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_cerr(" 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);
+
+ /* FIXME: double words 3-7 contain unused fast read information */
+
+ if (len == 4 * 4) {
+ msg_cdbg("It seems like this chip supports the preliminary "
+ "Intel version of SFDP, skipping processing of double "
+ "words 3-9.\n");
+ goto proc_4k;
+ }
+
+ 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];
+ if(sfdp_add_uniform_eraser(f, tmp8, bsize))
+ continue;
+ /* If there is a valid 4k value in the last double words,
+ * we want to override the value from double word 1, hence force
+ * skipping its processing: */
+ if (bsize == 4 * 1024)
+ opcode_4k = 0xFF;
+ }
+
+proc_4k:
+ if (opcode_4k != 0xFF) {
+ sfdp_add_uniform_eraser(f, opcode_4k, 4 * 1024);
+ }
+ 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];
+ 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[2];
+ 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 {
+ uint16_t len;
+ 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);
+ len = hdrs[i].len * 4;
+ tmp32 = hdrs[i].ptp;
+ msg_cdbg2(" Length %d B, Parameter Table Pointer 0x%06x\n",
+ len, tmp32);
+
+ if (len + 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(len);
+ if (tbuf == NULL) {
+ msg_gerr("Out of memory!\n");
+ exit(1); /* FIXME: shutdown gracefully */
+ }
+ if (sfdp_fetch_pt(flash, tmp32, tbuf, len)){
+ 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 (len != 9 * 4 && len != 4 * 4) {
+ msg_cdbg("Length of the mandatory JEDEC SFDP "
+ "parameter table is %d B instead of "
+ "36 B (i.e. 9 double words) as "
+ "demanded by JESD216, skipping "
+ "parsing.\n", len);
+ } else if (sfdp_fill_flash(flash, tbuf, len) == 0)
+ ret = 1;
+ }
+
+ free(tbuf);
+ i++;
+ } while(i <= nph);
+
+cleanup_hdrs:
+ free(hdrs);
+ free(hbuf);
+ return ret;
+}
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..15ec300 100644
--- a/spi25.c
+++ b/spi25.c
@@ -720,6 +720,37 @@ int spi_block_erase_c7(struct flashctx *flash, unsigned int addr,
return spi_chip_erase_c7(flash);
}
+erasefunc_t *spi_get_erasefn_from_opcode(uint8_t opcode)
+{
+ 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 erase opcode (0x%02x). Please report "
+ "this at flashrom at flashrom.org\n", __func__, opcode);
+ return NULL;
+ }
+}
+
int spi_write_status_enable(struct flashctx *flash)
{
static const unsigned char cmd[JEDEC_EWSR_OUTSIZE] = { JEDEC_EWSR };
--
1.7.1
More information about the flashrom
mailing list