Sergii Dmytruk has uploaded this change for review.

View Change

[RFC] dummyflasher: add EN25QH128 and W25Q40.V with OTP

These chips demonstrate two kinds of OTP: mode (Eon) and security
registers (Winbond) and will be used in tests.

Change-Id: I1af3ac22a66104517f1fe035e034404c6b249257
Signed-off-by: Hatim Kanchwala <hatim at hatimak.me>
Signed-off-by: Sergii Dmytruk <sergii.dmytruk@3mdeb.com>
---
M dummyflasher.c
M flashrom.8.tmpl
2 files changed, 308 insertions(+), 24 deletions(-)

git pull ssh://review.coreboot.org:29418/flashrom refs/changes/04/59404/1
diff --git a/dummyflasher.c b/dummyflasher.c
index 7bdcba4..82db268 100644
--- a/dummyflasher.c
+++ b/dummyflasher.c
@@ -2,6 +2,7 @@
* This file is part of the flashrom project.
*
* Copyright (C) 2009,2010 Carl-Daniel Hailfinger
+ * Copyright (C) 2016 Hatim Kanchwala <hatim at hatimak.me>
*
* 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
@@ -26,14 +27,17 @@
#include "flashchips.h"
#include "spi.h"

+/* Comments reflect what's implemented for a specific chip (might be incomplete) */
enum emu_chip {
- EMULATE_NONE,
- EMULATE_ST_M25P10_RES,
- EMULATE_SST_SST25VF040_REMS,
- EMULATE_SST_SST25VF032B,
- EMULATE_MACRONIX_MX25L6436,
- EMULATE_WINBOND_W25Q128FV,
- EMULATE_VARIABLE_SIZE,
+ EMULATE_NONE, /* SR1 */
+ EMULATE_ST_M25P10_RES, /* SR1 */
+ EMULATE_SST_SST25VF040_REMS, /* SR1 */
+ EMULATE_SST_SST25VF032B, /* SR1, SFDP */
+ EMULATE_MACRONIX_MX25L6436, /* SR1 */
+ EMULATE_WINBOND_W25Q128FV, /* SR1-2 */
+ EMULATE_VARIABLE_SIZE, /* SR1 */
+ EMULATE_EON_EN25QH128, /* SR1, OTP (mode) */
+ EMULATE_WINBOND_W25Q40_V, /* SR1-2, OTP (sec. regs) */
};

struct emu_data {
@@ -67,6 +71,15 @@

unsigned int spi_write_256_chunksize;
uint8_t *flashchip_contents;
+
+ /* Specific to EN25QH128 */
+ uint8_t otp_mode;
+ uint8_t otp_bit;
+
+ uint8_t otp_region_count;
+ uint16_t otp_region_size; /* this assumes regions of equal size */
+ char *otp_image_path[FLASHROM_OTP_MAX_REGIONS]; /* path to a persistent image on disk */
+ uint8_t *otp_region[FLASHROM_OTP_MAX_REGIONS]; /* data of the region */
};

/* A legit complete SFDP table based on the MX25L6436E (rev. 1.8) datasheet. */
@@ -170,7 +183,8 @@
{
uint8_t ro_bits = (status_reg == 1 ? SPI_SR_WEL | SPI_SR_WIP : 0);

- if (data->emu_chip == EMULATE_WINBOND_W25Q128FV) {
+ if (data->emu_chip == EMULATE_WINBOND_W25Q128FV ||
+ data->emu_chip == EMULATE_WINBOND_W25Q40_V) {
const bool srp0 = (data->emu_status >> 7);
const bool srp1 = (data->emu_status2 & 1);

@@ -179,7 +193,7 @@
if (wp_active) {
ro_bits = 0xff;
} else if (status_reg == 2) {
- /* SUS1 (bit_7) and (R) (bit_2). */
+ /* SUS (bit_7) and (R) (bit_2). */
ro_bits = 0x84;
/* Once any of the lock bits (LB[1..3]) are set, they
* can't be unset. */
@@ -275,11 +289,14 @@
{
unsigned int offs, i, toread;
uint8_t ro_bits;
+ uint8_t region;
static int unsigned aai_offs;
const unsigned char sst25vf040_rems_response[2] = {0xbf, 0x44};
const unsigned char sst25vf032b_rems_response[2] = {0xbf, 0x4a};
const unsigned char mx25l6436_rems_response[2] = {0xc2, 0x16};
const unsigned char w25q128fv_rems_response[2] = {0xef, 0x17};
+ const unsigned char en25qh128_rems_response[2] = {0x1c, 0x17};
+ const unsigned char w25q40v_rems_response[2] = {0xef, 0x12};

if (writecnt == 0) {
msg_perr("No command sent to the chip!\n");
@@ -342,6 +359,14 @@
if (readcnt > 0)
memset(readarr, 0x17, readcnt);
break;
+ case EMULATE_EON_EN25QH128:
+ if (readcnt > 0)
+ memset(readarr, 0x17, readcnt);
+ break;
+ case EMULATE_WINBOND_W25Q40_V:
+ if (readcnt > 0)
+ memset(readarr, 0x12, readcnt);
+ break;
default: /* ignore */
break;
}
@@ -369,6 +394,14 @@
for (i = 0; i < readcnt; i++)
readarr[i] = w25q128fv_rems_response[(offs + i) % 2];
break;
+ case EMULATE_EON_EN25QH128:
+ for (i = 0; i < readcnt; i++)
+ readarr[i] = en25qh128_rems_response[(offs + i) % 2];
+ break;
+ case EMULATE_WINBOND_W25Q40_V:
+ for (i = 0; i < readcnt; i++)
+ readarr[i] = w25q40v_rems_response[(offs + i) % 2];
+ break;
default: /* ignore */
break;
}
@@ -409,11 +442,31 @@
if (readcnt > 3)
readarr[3] = PROGDEV_ID & 0xff;
break;
+ case EMULATE_EON_EN25QH128:
+ if (readcnt > 0)
+ readarr[0] = 0x1c;
+ if (readcnt > 1)
+ readarr[1] = 0x70;
+ if (readcnt > 2)
+ readarr[2] = 0x18;
+ break;
+ case EMULATE_WINBOND_W25Q40_V:
+ if (readcnt > 0)
+ readarr[0] = 0xef;
+ if (readcnt > 1)
+ readarr[1] = 0x40;
+ if (readcnt > 2)
+ readarr[2] = 0x13;
+ break;
default: /* ignore */
break;
}
break;
case JEDEC_RDSR:
+ if (data->emu_chip == EMULATE_EON_EN25QH128 && data->otp_mode) {
+ memset(readarr, (data->emu_status & 0x7F) | (data->otp_bit << 7), readcnt);
+ break;
+ }
memset(readarr, data->emu_status, readcnt);
break;
case JEDEC_RDSR2:
@@ -431,6 +484,12 @@
break;
}

+ if (data->emu_chip == EMULATE_EON_EN25QH128 && data->otp_mode) {
+ data->otp_bit = 1;
+ msg_pdbg("OTP bit set...\n");
+ break;
+ }
+
/* FIXME: add some reasonable simulation of the busy flag */
ro_bits = get_status_ro_bits(data, 1);
data->emu_status &= ro_bits;
@@ -465,6 +524,14 @@
break;
case JEDEC_READ:
offs = writearr[1] << 16 | writearr[2] << 8 | writearr[3];
+ if (data->emu_chip == EMULATE_EON_EN25QH128 && data->otp_mode) {
+ if ((offs >> 12) != 0xfff || (offs & 0xfff) >= 0x200) {
+ msg_perr("Address out of range\n");
+ break;
+ }
+ memcpy(readarr, data->otp_region[0] + (offs & 0xfff), readcnt);
+ break;
+ }
/* Truncate to emu_chip_size. */
offs %= data->emu_chip_size;
if (readcnt > 0)
@@ -479,6 +546,18 @@
break;
case JEDEC_BYTE_PROGRAM:
offs = writearr[1] << 16 | writearr[2] << 8 | writearr[3];
+ if (data->emu_chip == EMULATE_EON_EN25QH128 && data->otp_mode) {
+ if (data->otp_bit) {
+ msg_perr("OTP bit is set, cannot program OTP sector anymore\n");
+ break;
+ }
+ if ((offs >> 12) != 0xfff || (offs & 0xfff) >= 0x200) {
+ msg_perr("Address out of range\n");
+ break;
+ }
+ memcpy(data->otp_region[0] + (offs & 0xfff), writearr + 4, writecnt - 4);
+ break;
+ }
/* Truncate to emu_chip_size. */
offs %= data->emu_chip_size;
if (writecnt < 5) {
@@ -556,6 +635,10 @@
case JEDEC_WRDI:
if (data->emu_max_aai_size)
data->emu_status &= ~SPI_SR_AAI;
+ if (data->emu_chip == EMULATE_EON_EN25QH128) {
+ data->otp_mode = 0;
+ msg_pdbg("Left OTP mode...\n");
+ }
break;
case JEDEC_SE:
if (!data->emu_jedec_se_size)
@@ -569,6 +652,18 @@
return 1;
}
offs = writearr[1] << 16 | writearr[2] << 8 | writearr[3];
+ if (data->emu_chip == EMULATE_EON_EN25QH128 && data->otp_mode) {
+ if (data->otp_bit) {
+ msg_perr("OTP bit is set, cannot erase OTP sector anymore\n");
+ break;
+ }
+ if ((offs >> 12) != 0xfff || (offs & 0xfff) >= 0x200) {
+ msg_perr("Address out of range\n");
+ break;
+ }
+ memset(data->otp_region[0], 0xff, data->otp_region_size);
+ break;
+ }
if (offs & (data->emu_jedec_se_size - 1))
msg_pdbg("Unaligned SECTOR ERASE 0x20: 0x%x\n", offs);
offs &= ~(data->emu_jedec_se_size - 1);
@@ -688,6 +783,88 @@
"continuous chunk produces undefined results "
"after that point.\n");
break;
+ case JEDEC_READ_SEC_REG:
+ if (data->emu_chip != EMULATE_WINBOND_W25Q40_V)
+ break;
+
+ if (writecnt != JEDEC_READ_SEC_REG_OUTSIZE) {
+ msg_perr("READ SECURITY REGISTER size not proper!\n");
+ break;
+ }
+ /* writearr[1..3] holds the address, writearr[1] must be 0x00,
+ * (writearr[2..3] & 01ff) holds the byte address pointing to within
+ * the security register range, and (writearr[2] & 0xf0) must be either
+ * of 0x01, 0x02 or 0x03 corresponding to security register 1, 2 or 3 resp. */
+ if (writearr[1] || (writearr[2] & 0x0e))
+ break;
+ offs = (writearr[2] & 0x01) << 8 | writearr[3];
+ /* Truncate to security register size. */
+ offs %= data->otp_region_size;
+ if (readcnt > 0) {
+ region = (writearr[2] & 0xf0) >> 4;
+ if (region > 0 && region < FLASHROM_OTP_MAX_REGIONS)
+ memcpy(readarr, data->otp_region[region - 1] + offs, readcnt);
+ }
+ break;
+ case JEDEC_PROG_BYTE_SEC_REG:
+ if (data->emu_chip != EMULATE_WINBOND_W25Q40_V)
+ break;
+
+ if (writecnt < JEDEC_PROG_BYTE_SEC_REG_OUTSIZE) {
+ msg_perr("PROGRAM SECURITY REGISTER size too short!\n");
+ break;
+ }
+ /* writearr[1..3] holds the address, writearr[1] must be 0x00,
+ * (writearr[2..3] & 01ff) holds the byte address pointing to within
+ * the security register range, and (writearr[2] & 0xf0) must be either
+ * of 0x01, 0x02 or 0x03 corresponding to security register 1, 2 or 3 resp. */
+ if (writearr[1] || (writearr[2] & 0x0e))
+ break;
+ offs = (writearr[2] & 0x01) << 8 | writearr[3];
+ /* Truncate to security register size. */
+ offs %= data->otp_region_size;
+
+ region = (writearr[2] >> 4);
+ if (region > 0 && region < FLASHROM_OTP_MAX_REGIONS) {
+ --region;
+ uint8_t ls_bit_pos = 3 + region;
+ /* If corresponding Lock Bits are set then the register is locked against
+ * any further write attempts. */
+ if (!(data->emu_status2 & (1 << ls_bit_pos)))
+ memcpy(data->otp_region[region] + offs, writearr + 4, writecnt - 4);
+ }
+ break;
+ case JEDEC_ERASE_SEC_REG:
+ if (data->emu_chip != EMULATE_WINBOND_W25Q40_V)
+ break;
+
+ if (writecnt != JEDEC_ERASE_SEC_REG_OUTSIZE) {
+ msg_perr("ERASE SECURITY REGISTER size not proper!\n");
+ break;
+ }
+ /* writearr[1..3] holds the address, writearr[1] must be 0x00,
+ * (writearr[2..3] & 01ff) holds the byte address pointing to within
+ * the security register range, and (writearr[2] & 0xf0) must be either
+ * of 0x01, 0x02 or 0x03 corresponding to security register 1, 2 or 3 resp. */
+ if (writearr[1] || (writearr[2] & 0x0e))
+ break;
+
+ region = (writearr[2] >> 4);
+ if (region > 0 && region < FLASHROM_OTP_MAX_REGIONS) {
+ --region;
+ uint8_t ls_bit_pos = 3 + region;
+ /* If corresponding Lock Bits are set then the register
+ * is locked against any further erase attempts. */
+ if (!(data->emu_status2 & (1 << ls_bit_pos)))
+ memset(data->otp_region[region], 0xff, data->otp_region_size);
+ }
+ break;
+ case JEDEC_ENTER_OTP: /* enter OTP for Eon chips */
+ if (data->emu_chip != EMULATE_EON_EN25QH128)
+ break;
+ data->otp_mode = 1;
+ msg_pdbg("Entered OTP mode...\n");
+ break;
default:
/* No special response. */
break;
@@ -724,6 +901,8 @@
case EMULATE_MACRONIX_MX25L6436:
case EMULATE_WINBOND_W25Q128FV:
case EMULATE_VARIABLE_SIZE:
+ case EMULATE_EON_EN25QH128:
+ case EMULATE_WINBOND_W25Q40_V:
if (emulate_spi_chip_response(writecnt, readcnt, writearr,
readarr, emu_data)) {
msg_pdbg("Invalid command sent to flash chip!\n");
@@ -746,16 +925,32 @@
{
msg_pspew("%s\n", __func__);
struct emu_data *emu_data = (struct emu_data *)data;
- if (emu_data->emu_chip != EMULATE_NONE) {
- if (emu_data->emu_persistent_image && emu_data->emu_modified) {
- msg_pdbg("Writing %s\n", emu_data->emu_persistent_image);
- write_buf_to_file(emu_data->flashchip_contents,
- emu_data->emu_chip_size,
- emu_data->emu_persistent_image);
- }
- free(emu_data->emu_persistent_image);
- free(emu_data->flashchip_contents);
+ if (emu_data->emu_chip == EMULATE_NONE) {
+ free(data);
+ return 0;
}
+
+ if (emu_data->emu_persistent_image && emu_data->emu_modified) {
+ msg_pdbg("Writing %s\n", emu_data->emu_persistent_image);
+ write_buf_to_file(emu_data->flashchip_contents,
+ emu_data->emu_chip_size,
+ emu_data->emu_persistent_image);
+ }
+ free(emu_data->emu_persistent_image);
+ free(emu_data->flashchip_contents);
+
+ for (uint8_t region = 0; region < emu_data->otp_region_count; ++region) {
+ if (emu_data->otp_image_path[region]) {
+ msg_pdbg("Writing %s\n", emu_data->otp_image_path[region]);
+ write_buf_to_file(emu_data->otp_region[region],
+ emu_data->otp_region_size,
+ emu_data->otp_image_path[region]);
+ }
+
+ free(emu_data->otp_region[region]);
+ free(emu_data->otp_image_path[region]);
+ }
+
free(data);
return 0;
}
@@ -1052,6 +1247,36 @@
data->emu_jedec_ce_c7_size = data->emu_chip_size;
msg_pdbg("Emulating Winbond W25Q128FV SPI flash chip (RDID)\n");
}
+ if (!strcmp(tmp, "EN25QH128")) {
+ data->emu_chip = EMULATE_EON_EN25QH128;
+ data->emu_chip_size = 16 * 1024 * 1024;
+ data->emu_max_byteprogram_size = 256;
+ data->emu_max_aai_size = 0;
+ data->emu_status_len = 1;
+ data->emu_jedec_se_size = 4 * 1024;
+ data->emu_jedec_be_52_size = 32 * 1024;
+ data->emu_jedec_be_d8_size = 64 * 1024;
+ data->emu_jedec_ce_60_size = data->emu_chip_size;
+ data->emu_jedec_ce_c7_size = data->emu_chip_size;
+ data->otp_region_count = 1;
+ data->otp_region_size = 512;
+ msg_pdbg("Emulating Eon EN25QH128 SPI flash chip (RDID)\n");
+ }
+ if (!strcmp(tmp, "W25Q40.V")) {
+ data->emu_chip = EMULATE_WINBOND_W25Q40_V;
+ data->emu_chip_size = 512 * 1024;
+ data->emu_max_byteprogram_size = 256;
+ data->emu_max_aai_size = 0;
+ data->emu_status_len = 2;
+ data->emu_jedec_se_size = 4 * 1024;
+ data->emu_jedec_be_52_size = 32 * 1024;
+ data->emu_jedec_be_d8_size = 64 * 1024;
+ data->emu_jedec_ce_60_size = data->emu_chip_size;
+ data->emu_jedec_ce_c7_size = data->emu_chip_size;
+ data->otp_region_count = 3;
+ data->otp_region_size = 256;
+ msg_pdbg("Emulating Winbond W25Q40.V SPI flash chip (RDID)\n");
+ }

/* The name of variable-size virtual chip. A 4 MiB flash example:
* flashrom -p dummy:emulate=VARIABLE_SIZE,size=4194304
@@ -1133,6 +1358,14 @@
return 1;
}

+ for (uint8_t region = 0; region < data->otp_region_count; ++region) {
+ data->otp_region[region] = malloc(data->otp_region_size);
+ if (!data->otp_region[region]) {
+ msg_perr("Out of memory!\n");
+ return 1;
+ }
+ }
+
return 0;
}

@@ -1169,13 +1402,9 @@

/* Will be freed by shutdown function if necessary. */
data->emu_persistent_image = extract_programmer_param("image");
- if (!data->emu_persistent_image) {
- /* Nothing else to do. */
- goto dummy_init_out;
- }
/* We will silently (in default verbosity) ignore the file if it does not exist (yet) or the size does
* not match the emulated chip. */
- if (!stat(data->emu_persistent_image, &image_stat)) {
+ if (data->emu_persistent_image && !stat(data->emu_persistent_image, &image_stat)) {
msg_pdbg("Found persistent image %s, %jd B ",
data->emu_persistent_image, (intmax_t)image_stat.st_size);
if ((uintmax_t)image_stat.st_size == data->emu_chip_size) {
@@ -1194,10 +1423,51 @@
}
}

+ /* Silently (in default verbosity) ignore files that don't exist (yet)
+ * or whose size does not match the security register size on chip. */
+ for (uint8_t region = 0; region < data->otp_region_count; ++region) {
+ const uint16_t region_size = data->otp_region_size;
+
+ char param_name[8];
+ char *image_path;
+
+ msg_pdbg("Filling fake security sector with 0xff, size %d bytes\n", region_size);
+ memset(data->otp_region[region], 0xff, region_size);
+
+ snprintf(param_name, sizeof(param_name), "otp%d", region + 1);
+
+ image_path = extract_programmer_param(param_name);
+ if (!image_path)
+ continue;
+
+ data->otp_image_path[region] = image_path;
+
+ if (stat(image_path, &image_stat)) {
+ msg_pdbg("OTP region image doesn't exist: %s\n", image_path);
+ continue;
+ }
+
+ msg_pdbg("Found persistent image %s, %jd B for security sector, ", image_path,
+ (intmax_t)image_stat.st_size);
+ if (image_stat.st_size == region_size) {
+ msg_pdbg("matches.\n");
+ msg_pdbg("Reading %s\n", image_path);
+ read_buf_from_file(data->otp_region[region], region_size, image_path);
+ } else {
+ msg_pdbg("doesn't match.\n");
+ }
+ }
+
dummy_init_out:
if (register_shutdown(dummy_shutdown, data)) {
free(data->emu_persistent_image);
free(data->flashchip_contents);
+
+ for (uint8_t region = 0; region < data->otp_region_count; ++region) {
+ free(data->otp_region[region]);
+ free(data->otp_image_path[region]);
+ }
+
free(data);
return 1;
}
diff --git a/flashrom.8.tmpl b/flashrom.8.tmpl
index 000a67a..126e50c 100644
--- a/flashrom.8.tmpl
+++ b/flashrom.8.tmpl
@@ -695,6 +695,10 @@
.sp
.RB "* Winbond " W25Q128FV " SPI flash chip (16384 kB, RDID)"
.sp
+.RB "* Eon " EN25QH128 " SPI flash chip (16384 kB, RDID, OTP)"
+.sp
+.RB "* Winbond " W25Q40.V " SPI flash chip (512 kB, RDID, OTP)"
+.sp
.RB "* Dummy vendor " VARIABLE_SIZE " SPI flash chip (configurable size, page write)"
.sp
Example:
@@ -722,7 +726,17 @@
where the chip contents on flashrom shutdown are written to.
.sp
Example:
-.B "flashrom -p dummy:emulate=M25P10.RES,image=dummy.bin"
+.sp
+.B " flashrom -p dummy:emulate=M25P10.RES,image=dummy.bin"
+.sp
+Chips that support OTP allow specifying images of OTP regions via parameters of
+the form
+.B otpN
+where
+.B N
+stands for region number (base 1):
+.sp
+.B " flashrom \-p dummy:emulate=W25Q40.V,otp1=otp1.bin,otp3=otp3.bin"
.TP
.B SPI write chunk size
.sp

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

Gerrit-Project: flashrom
Gerrit-Branch: master
Gerrit-Change-Id: I1af3ac22a66104517f1fe035e034404c6b249257
Gerrit-Change-Number: 59404
Gerrit-PatchSet: 1
Gerrit-Owner: Sergii Dmytruk <sergii.dmytruk@3mdeb.com>
Gerrit-MessageType: newchange