Similar to modules using the opaque programmer framework (e.g. ICH Hardware Sequencing) this uses a template struct flashchip element in flashchips.c with a special probe function that fills the obtained values into that struct.
This allows yet unknown SPI chips to be supported (read, erase, write) almost as if it was already added to flashchips.c.
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 spi25.c for further use like spi_get_erasefn_from_opcode? - is setting the generic SPI unlock method safe? - what page_size should be set?
Tested-by: David Hendricks dhendrix@google.com on W25Q64CV
Signed-off-by: Stefan Tauner stefan.tauner@student.tuwien.ac.at --- Makefile | 2 +- chipdrivers.h | 4 + flash.h | 2 + flashchips.c | 23 ++++ flashchips.h | 1 + flashrom.c | 28 +++++- sfdp.c | 355 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ spi.h | 5 + spi25.c | 31 +++++ 9 files changed, 449 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 0c8257b..b22e709 100644 --- a/flashchips.c +++ b/flashchips.c @@ -8872,6 +8872,29 @@ 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, + .unlock = spi_disable_blockprotect, /* is this safe? */ + .read = spi_chip_read, + .page_size = 256, /* ? */ + /* FIXME: some vendor extensions define this */ + .voltage = {}, + /* Everything below will be set by the probing function. */ + .write = NULL, + .total_size = 0, + .feature_bits = 0, + .block_erasers = {}, + },
{ .vendor = "Unknown", 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@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..7c00ff5 --- /dev/null +++ b/sfdp.c @@ -0,0 +1,355 @@ +/* + * 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 || !eraser->block_erase) + 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@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_cdbg("Parsing JEDEC SFDP parameter table... "); + if (len != 9 * 4 && len != 4 * 4) { + msg_cerr("%s: len out of spec\n", __func__); + return 1; + } + msg_cdbg2("\n"); + + /* 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)) { + 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->write = spi_chip_write_256; + } else { + msg_cdbg2("1 B only.\n"); + 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/512 MB not supported.\n"); + return 1; + } + total_size = ((tmp32 & 0x7FFFFFFF) + 1) / 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); + } + msg_cdbg("done.\n"); + 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 d81616c..8b3887e 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 0xff: + case 0x00: + /* 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@flashrom.org\n", __func__, opcode); + return NULL; + } +} + static int spi_write_status_register_flag(struct flashctx *flash, int status, const unsigned char opcode) { int result;