diff mbox

Add dummy chips GD25Q128C, EN25QH128 and W25Q40.V

Message ID e17ae81a-eaa4-3d78-c739-ff6a23801936@hatimak.me
State New
Headers show

Commit Message

Hatim Kanchwala Aug. 23, 2016, 2:57 a.m. UTC
Hello,

In addition to new dummy chip definitions, this squashes a few bugs left over in CLI (--read-otp, --write-otp and
--lock-otp) and in otp.c (Eon specific). These 3 chips should completely emulate all kinds of new functionality added.

Thanks

Hatim

P.S. Sorry I had to send the email in this form, there seems to be a problem with my mail configuration which I was
sadly  unable to resolve.

--

GD25Q128C -
  - 3 status registers each with separate read and write opcodes for corresponding SRs
  - 3 security registers each of 512 bytes

EN25QH128 -
  - Single status register
  - OTP sector of 512 bytes

W25Q40.V (W25Q40BL) -
  - 2 status registers each with separate read but with single write opcode
  - 3 security registers each of 256 bytes

Signed-off-by: Hatim Kanchwala <hatim@hatimak.me>
---
 cli_classic.c  |  10 +-
 dummyflasher.c | 618 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 otp.c          |   4 +-
 3 files changed, 615 insertions(+), 17 deletions(-)
diff mbox

Patch

diff --git a/cli_classic.c b/cli_classic.c
index 146750e..300560c 100644
--- a/cli_classic.c
+++ b/cli_classic.c
@@ -459,36 +459,38 @@  int main(int argc, char *argv[])
 					"specified. Aborting.\n");
 				cli_classic_abort_usage();
 			}
 			write_otp_opt = strdup(optarg);
 			filename = extract_param(&write_otp_opt, "file", ",");
 			write_otp = 1;
 			break;
 		case LONGOPT_ERASE_OTP:
 			if (++operation_specified > 1) {
 				fprintf(stderr, "More than one operation "
 					"specified. Aborting.\n");
 				cli_classic_abort_usage();
 			}
-			erase_otp_opt = strdup(optarg);
+			if (optarg)
+				erase_otp_opt = strdup(optarg);
 			erase_otp = 1;
 			break;
 		case LONGOPT_LOCK_OTP:
 			if (++operation_specified > 1) {
 				fprintf(stderr, "More than one operation "
 					"specified. Aborting.\n");
 				cli_classic_abort_usage();
 			}
-			lock_otp_opt = strdup(optarg);
+			if (optarg)
+				lock_otp_opt = strdup(optarg);
 			lock_otp = 1;
 			break;
 		default:
 			cli_classic_abort_usage();
 			break;
 		}
 	}

 	if (optind < argc) {
 		fprintf(stderr, "Error: Extra parameter found.\n");
 		cli_classic_abort_usage();
 	}

@@ -909,52 +911,52 @@  int main(int argc, char *argv[])
 			msg_gdbg("Writing OTP memory done\n");
 			// FIXME(hatim): Verify written contents
 		} else
 			cli_infra_support(fill_flash, "OTP");

 		goto out_shutdown;
 	}

 	if (erase_otp) {
 		verbose_screen++;
 		if (fill_flash->chip->otp) {
 			char *otp_region_opt = NULL;
 			enum otp_region otp_region = OTP_REG_1;
-			if (!erase_otp_opt && (otp_region_opt = extract_param(&read_otp_opt, "reg", ","))) {
+			if (!erase_otp_opt && (otp_region_opt = extract_param(&erase_otp_opt, "reg", ","))) {
 				char *endptr = NULL;
 				// FIXME(hatim): Implement error-checking (?)
 				otp_region = (uint8_t)strtoul(otp_region_opt, &endptr, 0) - 1;
 			} else
 				msg_gdbg("OTP region not specified, using default region 1\n");

 			msg_gdbg("Erasing OTP memory ...\n");
 			ret = fill_flash->chip->otp->erase(fill_flash, otp_region);
 			if (ret) {
 				msg_gerr("Erasing OTP memory failed\n");
 				goto out_shutdown;
 			}
 			msg_gdbg("Erasing OTP memory done\n");
 		} else
 			cli_infra_support(fill_flash, "OTP");

 		goto out_shutdown;
 	}

 	if (lock_otp) {
 		verbose_screen++;
 		if (fill_flash->chip->otp) {
 			char *otp_region_opt = NULL;
 			enum otp_region otp_region = OTP_REG_1;
-			if (!lock_otp_opt && (otp_region_opt = extract_param(&read_otp_opt, "reg", ","))) {
+			if (!lock_otp_opt && (otp_region_opt = extract_param(&lock_otp_opt, "reg", ","))) {
 				char *endptr = NULL;
 				// FIXME(hatim): Implement error-checking (?)
 				otp_region = (uint8_t)strtoul(otp_region_opt, &endptr, 0) - 1;
 			} else
 				msg_gdbg("OTP region not specified, using default region 1\n");

 			msg_gdbg("Trying to lock OTP memory...\n");
 			ret = fill_flash->chip->otp->lock(fill_flash, otp_region);
 			if (ret) {
 				msg_gerr("Failed to lock\n");
 				goto out_shutdown;
 			}
 			msg_gdbg("OTP memory locked\n");
diff --git a/dummyflasher.c b/dummyflasher.c
index f171128..20f6ba5 100644
--- a/dummyflasher.c
+++ b/dummyflasher.c
@@ -1,17 +1,18 @@ 
 /*
  * This file is part of the flashrom project.
  *
  * Copyright (C) 2009,2010 Carl-Daniel Hailfinger
+ * Copyright (C) 2016 Hatim Kanchwala <hatim@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
  * 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
@@ -31,49 +32,68 @@ 

 #if EMULATE_SPI_CHIP
 #define EMULATE_CHIP 1
 #include "spi.h"
 #endif

 #if EMULATE_CHIP
 #include <sys/types.h>
 #include <sys/stat.h>
 #endif

 #if EMULATE_CHIP
 static uint8_t *flashchip_contents = NULL;
+static uint8_t *gd25q128c_otp_1_contents = NULL;
+static uint8_t *gd25q128c_otp_2_contents = NULL;
+static uint8_t *gd25q128c_otp_3_contents = NULL;
+static uint8_t *en25qh128_otp_contents = NULL;
+static uint8_t *w25q40v_otp_1_contents = NULL;
+static uint8_t *w25q40v_otp_2_contents = NULL;
+static uint8_t *w25q40v_otp_3_contents = NULL;
 enum emu_chip {
 	EMULATE_NONE,
 	EMULATE_ST_M25P10_RES,
 	EMULATE_SST_SST25VF040_REMS,
 	EMULATE_SST_SST25VF032B,
 	EMULATE_MACRONIX_MX25L6436,
+	EMULATE_GIGADEVICE_GD25Q128C,
+	EMULATE_EON_EN25QH128,
+	EMULATE_WINBOND_W25Q40_V,
 };
 static enum emu_chip emu_chip = EMULATE_NONE;
 static char *emu_persistent_image = NULL;
 static unsigned int emu_chip_size = 0;
 #if EMULATE_SPI_CHIP
 static unsigned int emu_max_byteprogram_size = 0;
 static unsigned int emu_max_aai_size = 0;
 static unsigned int emu_jedec_se_size = 0;
 static unsigned int emu_jedec_be_52_size = 0;
 static unsigned int emu_jedec_be_d8_size = 0;
 static unsigned int emu_jedec_ce_60_size = 0;
 static unsigned int emu_jedec_ce_c7_size = 0;
 unsigned char spi_blacklist[256];
 unsigned char spi_ignorelist[256];
 int spi_blacklist_size = 0;
 int spi_ignorelist_size = 0;
-static uint8_t emu_status = 0;
+static uint32_t emu_status = 0;
+static char *emu_persistent_gd25_otp_1_image = NULL;
+static char *emu_persistent_gd25_otp_2_image = NULL;
+static char *emu_persistent_gd25_otp_3_image = NULL;
+static char *emu_persistent_en25_otp_image = NULL;
+static char *emu_persistent_w25_otp_1_image = NULL;
+static char *emu_persistent_w25_otp_2_image = NULL;
+static char *emu_persistent_w25_otp_3_image = NULL;
+static uint8_t read_only_bits = 0;
+static uint8_t en25qh128_otp_mode = 0, en25qh128_otp_bit = 0;

 /* A legit complete SFDP table based on the MX25L6436E (rev. 1.8) datasheet. */
 static const uint8_t sfdp_table[] = {
 	0x53, 0x46, 0x44, 0x50, // @0x00: SFDP signature
 	0x00, 0x01, 0x01, 0xFF, // @0x04: revision 1.0, 2 headers
 	0x00, 0x00, 0x01, 0x09, // @0x08: JEDEC SFDP header rev. 1.0, 9 DW long
 	0x1C, 0x00, 0x00, 0xFF, // @0x0C: PTP0 = 0x1C (instead of 0x30)
 	0xC2, 0x00, 0x01, 0x04, // @0x10: Macronix header rev. 1.0, 4 DW long
 	0x48, 0x00, 0x00, 0xFF, // @0x14: PTP1 = 0x48 (instead of 0x60)
 	0xFF, 0xFF, 0xFF, 0xFF, // @0x18: hole.
 	0xE5, 0x20, 0xC9, 0xFF, // @0x1C: SFDP parameter table start
 	0xFF, 0xFF, 0xFF, 0x03, // @0x20
 	0x00, 0xFF, 0x08, 0x6B, // @0x24
@@ -81,26 +101,28 @@  static const uint8_t sfdp_table[] = {
 	0xEE, 0xFF, 0xFF, 0xFF, // @0x2C
 	0xFF, 0xFF, 0x00, 0x00, // @0x30
 	0xFF, 0xFF, 0x00, 0xFF, // @0x34
 	0x0C, 0x20, 0x0F, 0x52, // @0x38
 	0x10, 0xD8, 0x00, 0xFF, // @0x3C: SFDP parameter table end
 	0xFF, 0xFF, 0xFF, 0xFF, // @0x40: hole.
 	0xFF, 0xFF, 0xFF, 0xFF, // @0x44: hole.
 	0x00, 0x36, 0x00, 0x27, // @0x48: Macronix parameter table start
 	0xF4, 0x4F, 0xFF, 0xFF, // @0x4C
 	0xD9, 0xC8, 0xFF, 0xFF, // @0x50
 	0xFF, 0xFF, 0xFF, 0xFF, // @0x54: Macronix parameter table end
 };

+// TODO(hatim): Add SFDP table for GD25Q128C, EN25QH128 and W25Q40.V
+
 #endif
 #endif

 static unsigned int spi_write_256_chunksize = 256;

 static int dummy_spi_send_command(struct flashctx *flash, unsigned int writecnt, unsigned int readcnt,
 				  const unsigned char *writearr, unsigned char *readarr);
 static int dummy_spi_write_256(struct flashctx *flash, const uint8_t *buf,
 			       unsigned int start, unsigned int len);
 static void dummy_chip_writeb(const struct flashctx *flash, uint8_t val, chipaddr addr);
 static void dummy_chip_writew(const struct flashctx *flash, uint16_t val, chipaddr addr);
 static void dummy_chip_writel(const struct flashctx *flash, uint32_t val, chipaddr addr);
 static void dummy_chip_writen(const struct flashctx *flash, const uint8_t *buf, chipaddr addr, size_t len);
@@ -135,26 +157,81 @@  enum chipbustype dummy_buses_supported = BUS_NONE;

 static int dummy_shutdown(void *data)
 {
 	msg_pspew("%s\n", __func__);
 #if EMULATE_CHIP
 	if (emu_chip != EMULATE_NONE) {
 		if (emu_persistent_image) {
 			msg_pdbg("Writing %s\n", emu_persistent_image);
 			write_buf_to_file(flashchip_contents, emu_chip_size, emu_persistent_image);
 			free(emu_persistent_image);
 			emu_persistent_image = NULL;
 		}
 		free(flashchip_contents);
+		if (emu_chip == EMULATE_GIGADEVICE_GD25Q128C) {
+			if (emu_persistent_gd25_otp_1_image) {
+				msg_pdbg("Writing %s\n", emu_persistent_gd25_otp_1_image);
+				write_buf_to_file(gd25q128c_otp_1_contents, 512, emu_persistent_gd25_otp_1_image);
+				free(emu_persistent_gd25_otp_1_image);
+				emu_persistent_gd25_otp_1_image = NULL;
+			}
+			if (emu_persistent_gd25_otp_2_image) {
+				msg_pdbg("Writing %s\n", emu_persistent_gd25_otp_2_image);
+				write_buf_to_file(gd25q128c_otp_2_contents, 512, emu_persistent_gd25_otp_2_image);
+				free(emu_persistent_gd25_otp_2_image);
+				emu_persistent_gd25_otp_2_image = NULL;
+			}
+			if (emu_persistent_gd25_otp_3_image) {
+				msg_pdbg("Writing %s\n", emu_persistent_gd25_otp_3_image);
+				write_buf_to_file(gd25q128c_otp_3_contents, 512, emu_persistent_gd25_otp_3_image);
+				free(emu_persistent_gd25_otp_3_image);
+				emu_persistent_gd25_otp_3_image = NULL;
+			}
+			free(gd25q128c_otp_1_contents);
+			free(gd25q128c_otp_2_contents);
+			free(gd25q128c_otp_3_contents);
+		}
+		if (emu_chip == EMULATE_EON_EN25QH128) {
+			if (emu_persistent_en25_otp_image) {
+				msg_pdbg("Writing %s\n", emu_persistent_en25_otp_image);
+				write_buf_to_file(en25qh128_otp_contents, 512, emu_persistent_en25_otp_image);
+				free(emu_persistent_en25_otp_image);
+				emu_persistent_en25_otp_image = NULL;
+			}
+			free(en25qh128_otp_contents);
+		}
+		if (emu_chip == EMULATE_WINBOND_W25Q40_V) {
+			if (emu_persistent_w25_otp_1_image) {
+				msg_pdbg("Writing %s\n", emu_persistent_w25_otp_1_image);
+				write_buf_to_file(w25q40v_otp_1_contents, 256, emu_persistent_w25_otp_1_image);
+				free(emu_persistent_w25_otp_1_image);
+				emu_persistent_w25_otp_1_image = NULL;
+			}
+			if (emu_persistent_w25_otp_2_image) {
+				msg_pdbg("Writing %s\n", emu_persistent_w25_otp_2_image);
+				write_buf_to_file(w25q40v_otp_2_contents, 256, emu_persistent_w25_otp_2_image);
+				free(emu_persistent_w25_otp_2_image);
+				emu_persistent_w25_otp_2_image = NULL;
+			}
+			if (emu_persistent_w25_otp_3_image) {
+				msg_pdbg("Writing %s\n", emu_persistent_w25_otp_3_image);
+				write_buf_to_file(w25q40v_otp_3_contents, 256, emu_persistent_w25_otp_3_image);
+				free(emu_persistent_w25_otp_3_image);
+				emu_persistent_w25_otp_3_image = NULL;
+			}
+			free(w25q40v_otp_1_contents);
+			free(w25q40v_otp_2_contents);
+			free(w25q40v_otp_3_contents);
+		}
 	}
 #endif
 	return 0;
 }

 int dummy_init(void)
 {
 	char *bustext = NULL;
 	char *tmp = NULL;
 	int i;
 #if EMULATE_SPI_CHIP
 	char *status = NULL;
 #endif
@@ -324,84 +401,311 @@  int dummy_init(void)
 	if (!strcmp(tmp, "MX25L6436")) {
 		emu_chip = EMULATE_MACRONIX_MX25L6436;
 		emu_chip_size = 8 * 1024 * 1024;
 		emu_max_byteprogram_size = 256;
 		emu_max_aai_size = 0;
 		emu_jedec_se_size = 4 * 1024;
 		emu_jedec_be_52_size = 32 * 1024;
 		emu_jedec_be_d8_size = 64 * 1024;
 		emu_jedec_ce_60_size = emu_chip_size;
 		emu_jedec_ce_c7_size = emu_chip_size;
 		msg_pdbg("Emulating Macronix MX25L6436 SPI flash chip (RDID, "
 			 "SFDP)\n");
 	}
+	if (!strcmp(tmp, "GD25Q128C")) {
+		emu_chip = EMULATE_GIGADEVICE_GD25Q128C;
+		emu_chip_size = 16 * 1024 * 1024;
+		emu_max_byteprogram_size = 256;
+		emu_max_aai_size = 0;
+		emu_jedec_se_size = 4 * 1024;
+		emu_jedec_be_52_size = 32 * 1024;
+		emu_jedec_be_d8_size = 64 * 1024;
+		emu_jedec_ce_60_size = emu_chip_size;
+		emu_jedec_ce_c7_size = emu_chip_size;
+		msg_pdbg("Emulating GigaDevice GD25Q128C SPI flash chip (RDID)\n");
+	}
+	if (!strcmp(tmp, "EN25QH128")) {
+		emu_chip = EMULATE_EON_EN25QH128;
+		emu_chip_size = 16 * 1024 * 1024;
+		emu_max_byteprogram_size = 256;
+		emu_max_aai_size = 0;
+		emu_jedec_se_size = 4 * 1024;
+		emu_jedec_be_52_size = 32 * 1024;
+		emu_jedec_be_d8_size = 64 * 1024;
+		emu_jedec_ce_60_size = emu_chip_size;
+		emu_jedec_ce_c7_size = emu_chip_size;
+		msg_pdbg("Emulating Eon EN25QH128 SPI flash chip (RDID)\n");
+	}
+	if (!strcmp(tmp, "W25Q40.V")) {
+		emu_chip = EMULATE_WINBOND_W25Q40_V;
+		emu_chip_size = 512 * 1024;
+		emu_max_byteprogram_size = 256;
+		emu_max_aai_size = 0;
+		emu_jedec_se_size = 4 * 1024;
+		emu_jedec_be_52_size = 32 * 1024;
+		emu_jedec_be_d8_size = 64 * 1024;
+		emu_jedec_ce_60_size = emu_chip_size;
+		emu_jedec_ce_c7_size = emu_chip_size;
+		msg_pdbg("Emulating Winbond W25Q40.V SPI flash chip (RDID)\n");
+	}
 #endif
 	if (emu_chip == EMULATE_NONE) {
 		msg_perr("Invalid chip specified for emulation: %s\n", tmp);
 		free(tmp);
 		return 1;
 	}
 	free(tmp);
 	flashchip_contents = malloc(emu_chip_size);
 	if (!flashchip_contents) {
 		msg_perr("Out of memory!\n");
 		return 1;
 	}

+	/* Allocate memory for each of the 512 bytes Security Register of GD25Q128C. */
+	if (emu_chip == EMULATE_GIGADEVICE_GD25Q128C) {
+		gd25q128c_otp_1_contents = malloc(512);
+		if (!gd25q128c_otp_1_contents) {
+			msg_perr("Out of memory!\n");
+			return 1;
+		}
+		gd25q128c_otp_2_contents = malloc(512);
+		if (!gd25q128c_otp_2_contents) {
+			msg_perr("Out of memory!\n");
+			return 1;
+		}
+		gd25q128c_otp_3_contents = malloc(512);
+		if (!gd25q128c_otp_3_contents) {
+			msg_perr("Out of memory!\n");
+			return 1;
+		}
+	}
+
+	/* Allocate memory for the 512 byte security sector of EN25QH128. */
+	if (emu_chip == EMULATE_EON_EN25QH128) {
+		en25qh128_otp_contents = malloc(512);
+		if (!en25qh128_otp_contents) {
+			msg_perr("Out of memory!\n");
+			return 1;
+		}
+	}
+
+	/* Allocate memory for each of the 256 bytes Security Register of GD25Q128C. */
+	if (emu_chip == EMULATE_WINBOND_W25Q40_V) {
+		w25q40v_otp_1_contents = malloc(256);
+		if (!w25q40v_otp_1_contents) {
+			msg_perr("Out of memory!\n");
+			return 1;
+		}
+		w25q40v_otp_2_contents = malloc(256);
+		if (!w25q40v_otp_2_contents) {
+			msg_perr("Out of memory!\n");
+			return 1;
+		}
+		w25q40v_otp_3_contents = malloc(256);
+		if (!w25q40v_otp_3_contents) {
+			msg_perr("Out of memory!\n");
+			return 1;
+		}
+	}
+
 #ifdef EMULATE_SPI_CHIP
 	status = extract_programmer_param("spi_status");
 	if (status) {
 		char *endptr;
 		errno = 0;
 		emu_status = strtoul(status, &endptr, 0);
 		free(status);
 		if (errno != 0 || status == endptr) {
 			msg_perr("Error: initial status register specified, "
 				 "but the value could not be converted.\n");
 			return 1;
 		}
-		msg_pdbg("Initial status register is set to 0x%02x.\n",
+		if (emu_chip == EMULATE_GIGADEVICE_GD25Q128C) {
+			msg_pdbg("Initial status registers -\n"
+				"\tSR1 is set to 0x%02x\n"
+				"\tSR2 is set to 0x%02x\n"
+				"\tSR3 is set to 0x%02x\n",
+				emu_status & 0xff, (emu_status >> 8) & 0xff, (emu_status >> 16) & 0xff);
+		} else if (emu_chip == EMULATE_WINBOND_W25Q40_V) {
+			msg_pdbg("Initial status registers -\n"
+				"\tSR1 is set to 0x%02x\n"
+				"\tSR2 is set to 0x%02x\n",
+				emu_status & 0xff, (emu_status >> 8) & 0xff);
+		} else {
+			msg_pdbg("Initial status register is set to 0x%02x.\n",
 			 emu_status);
+		}
 	}
 #endif

 	msg_pdbg("Filling fake flash chip with 0xff, size %i\n", emu_chip_size);
 	memset(flashchip_contents, 0xff, emu_chip_size);

 	/* Will be freed by shutdown function if necessary. */
 	emu_persistent_image = extract_programmer_param("image");
-	if (!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(emu_persistent_image, &image_stat)) {
+	if (emu_persistent_image && !stat(emu_persistent_image, &image_stat)) {
 		msg_pdbg("Found persistent image %s, %jd B ",
 			 emu_persistent_image, (intmax_t)image_stat.st_size);
 		if (image_stat.st_size == emu_chip_size) {
 			msg_pdbg("matches.\n");
 			msg_pdbg("Reading %s\n", emu_persistent_image);
 			read_buf_from_file(flashchip_contents, emu_chip_size,
 					   emu_persistent_image);
 		} else {
 			msg_pdbg("doesn't match.\n");
 		}
 	}
+
+	/* Each OTP image will be freed by shutdown function, if necessary. GD25Q128C has 3 Security Registers
+	 * each of 512 bytes.
+	 * We will silently (in default verbosity) ignore the file(s) if it does not exist (yet)
+	 * or the size does not match the security register size on chip. */
+	if (emu_chip == EMULATE_GIGADEVICE_GD25Q128C) {
+		msg_pdbg("Filling fake security register 1, 2 and 3 with 0xff, size 512 bytes each\n");
+		memset(gd25q128c_otp_1_contents, 0xff, 512);
+		memset(gd25q128c_otp_2_contents, 0xff, 512);
+		memset(gd25q128c_otp_3_contents, 0xff, 512);
+
+		emu_persistent_gd25_otp_1_image = extract_programmer_param("otp_1");
+		emu_persistent_gd25_otp_2_image = extract_programmer_param("otp_2");
+		emu_persistent_gd25_otp_3_image = extract_programmer_param("otp_3");
+
+		if (emu_persistent_gd25_otp_1_image && !stat(emu_persistent_gd25_otp_1_image, &image_stat)) {
+			msg_pdbg("Found persistent image %s, %jd B for security register 1, ",
+				 emu_persistent_gd25_otp_1_image, (intmax_t)image_stat.st_size);
+			if (image_stat.st_size == 512) {
+				msg_pdbg("matches.\n");
+				msg_pdbg("Reading %s\n", emu_persistent_gd25_otp_1_image);
+				read_buf_from_file(gd25q128c_otp_1_contents, 512,
+						   emu_persistent_gd25_otp_1_image);
+			} else {
+				msg_pdbg("doesn't match.\n");
+			}
+		}
+		if (emu_persistent_gd25_otp_2_image && !stat(emu_persistent_gd25_otp_2_image, &image_stat)) {
+			msg_pdbg("Found persistent image %s, %jd B for security register 2, ",
+				 emu_persistent_gd25_otp_2_image, (intmax_t)image_stat.st_size);
+			if (image_stat.st_size == 512) {
+				msg_pdbg("matches.\n");
+				msg_pdbg("Reading %s\n", emu_persistent_gd25_otp_2_image);
+				read_buf_from_file(gd25q128c_otp_2_contents, 512,
+						   emu_persistent_gd25_otp_2_image);
+			} else {
+				msg_pdbg("doesn't match.\n");
+			}
+		}
+		if (emu_persistent_gd25_otp_3_image && !stat(emu_persistent_gd25_otp_3_image, &image_stat)) {
+			msg_pdbg("Found persistent image %s, %jd B for security register 3, ",
+				 emu_persistent_gd25_otp_3_image, (intmax_t)image_stat.st_size);
+			if (image_stat.st_size == 512) {
+				msg_pdbg("matches.\n");
+				msg_pdbg("Reading %s\n", emu_persistent_gd25_otp_3_image);
+				read_buf_from_file(gd25q128c_otp_3_contents, 512,
+						   emu_persistent_gd25_otp_3_image);
+			} else {
+				msg_pdbg("doesn't match.\n");
+			}
+		}
+	}
+
+	/* Eon EN25QH128 has a security sector of 512 bytes. OTP image will be freed by shutdown
+	 * function, if necessary. We will silently (in default verbosity) ignore the file(s)
+	 * if it does not exist (yet) or the size does not match the security register size on chip. */
+	if (emu_chip == EMULATE_EON_EN25QH128) {
+		msg_pdbg("Filling fake security sector with 0xff, size 512 bytes\n");
+		memset(en25qh128_otp_contents, 0xff, 512);
+		emu_persistent_en25_otp_image = extract_programmer_param("otp");
+		if (emu_persistent_en25_otp_image && !stat(emu_persistent_en25_otp_image, &image_stat)) {
+			msg_pdbg("Found persistent image %s, %jd B for security sector, ",
+				 emu_persistent_en25_otp_image, (intmax_t)image_stat.st_size);
+			if (image_stat.st_size == 512) {
+				msg_pdbg("matches.\n");
+				msg_pdbg("Reading %s\n", emu_persistent_en25_otp_image);
+				read_buf_from_file(en25qh128_otp_contents, 512,
+						   emu_persistent_en25_otp_image);
+			} else {
+				msg_pdbg("doesn't match.\n");
+			}
+		}
+	}
+
+	/* W25Q40.V has 3 Security Registers each of 256 bytes. */
+	if (emu_chip == EMULATE_WINBOND_W25Q40_V) {
+		msg_pdbg("Filling fake security register 1, 2 and 3 with 0xff, size 256 bytes each\n");
+		memset(w25q40v_otp_1_contents, 0xff, 256);
+		memset(w25q40v_otp_2_contents, 0xff, 256);
+		memset(w25q40v_otp_3_contents, 0xff, 256);
+
+		emu_persistent_w25_otp_1_image = extract_programmer_param("otp_1");
+		emu_persistent_w25_otp_2_image = extract_programmer_param("otp_2");
+		emu_persistent_w25_otp_3_image = extract_programmer_param("otp_3");
+
+		if (emu_persistent_w25_otp_1_image && !stat(emu_persistent_w25_otp_1_image, &image_stat)) {
+			msg_pdbg("Found persistent image %s, %jd B for security register 1, ",
+				 emu_persistent_w25_otp_1_image, (intmax_t)image_stat.st_size);
+			if (image_stat.st_size == 256) {
+				msg_pdbg("matches.\n");
+				msg_pdbg("Reading %s\n", emu_persistent_w25_otp_1_image);
+				read_buf_from_file(w25q40v_otp_1_contents, 256,
+						   emu_persistent_w25_otp_1_image);
+			} else {
+				msg_pdbg("doesn't match.\n");
+			}
+		}
+		if (emu_persistent_w25_otp_2_image && !stat(emu_persistent_w25_otp_2_image, &image_stat)) {
+			msg_pdbg("Found persistent image %s, %jd B for security register 2, ",
+				 emu_persistent_w25_otp_2_image, (intmax_t)image_stat.st_size);
+			if (image_stat.st_size == 256) {
+				msg_pdbg("matches.\n");
+				msg_pdbg("Reading %s\n", emu_persistent_w25_otp_2_image);
+				read_buf_from_file(w25q40v_otp_2_contents, 256,
+						   emu_persistent_w25_otp_2_image);
+			} else {
+				msg_pdbg("doesn't match.\n");
+			}
+		}
+		if (emu_persistent_w25_otp_3_image && !stat(emu_persistent_w25_otp_3_image, &image_stat)) {
+			msg_pdbg("Found persistent image %s, %jd B for security register 3, ",
+				 emu_persistent_w25_otp_3_image, (intmax_t)image_stat.st_size);
+			if (image_stat.st_size == 256) {
+				msg_pdbg("matches.\n");
+				msg_pdbg("Reading %s\n", emu_persistent_w25_otp_3_image);
+				read_buf_from_file(w25q40v_otp_3_contents, 256,
+						   emu_persistent_w25_otp_3_image);
+			} else {
+				msg_pdbg("doesn't match.\n");
+			}
+		}
+	}
 #endif

 dummy_init_out:
 	if (register_shutdown(dummy_shutdown, NULL)) {
 		free(flashchip_contents);
+		if (emu_chip == EMULATE_GIGADEVICE_GD25Q128C) {
+			free(gd25q128c_otp_1_contents);
+			free(gd25q128c_otp_2_contents);
+			free(gd25q128c_otp_3_contents);
+		}
+		if (emu_chip == EMULATE_EON_EN25QH128) {
+			free(en25qh128_otp_contents);
+		}
+		if (emu_chip == EMULATE_WINBOND_W25Q40_V) {
+			free(w25q40v_otp_1_contents);
+			free(w25q40v_otp_2_contents);
+			free(w25q40v_otp_3_contents);
+		}
 		return 1;
 	}
 	if (dummy_buses_supported & (BUS_PARALLEL | BUS_LPC | BUS_FWH))
 		register_par_master(&par_master_dummy,
 				    dummy_buses_supported & (BUS_PARALLEL | BUS_LPC | BUS_FWH));
 	if (dummy_buses_supported & BUS_SPI)
 		register_spi_master(&spi_master_dummyflasher);

 	return 0;
 }

 void *dummy_map(const char *descr, uintptr_t phys_addr, size_t len)
 {
@@ -457,36 +761,40 @@  static uint32_t dummy_chip_readl(const struct flashctx *flash, const chipaddr ad
 {
 	msg_pspew("%s:  addr=0x%" PRIxPTR ", returning 0xffffffff\n", __func__, addr);
 	return 0xffffffff;
 }

 static void dummy_chip_readn(const struct flashctx *flash, uint8_t *buf, const chipaddr addr, size_t len)
 {
 	msg_pspew("%s:  addr=0x%" PRIxPTR ", len=0x%zx, returning array of 0xff\n", __func__, addr, len);
 	memset(buf, 0xff, len);
 	return;
 }

 #if EMULATE_SPI_CHIP
+
 static int emulate_spi_chip_response(unsigned int writecnt,
 				     unsigned int readcnt,
 				     const unsigned char *writearr,
 				     unsigned char *readarr)
 {
 	unsigned int offs, i, toread;
 	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 gd25q128c_rems_response[2] = {0xc8, 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");
 		return 1;
 	}
 	/* spi_blacklist has precedence over spi_ignorelist. */
 	for (i = 0; i < spi_blacklist_size; i++) {
 		if (writearr[0] == spi_blacklist[i]) {
 			msg_pdbg("Refusing blacklisted SPI command 0x%02x\n",
 				 spi_blacklist[i]);
 			return SPI_INVALID_OPCODE;
 		}
 	}
@@ -525,101 +833,241 @@  static int emulate_spi_chip_response(unsigned int writecnt,
 			break;
 		case EMULATE_SST_SST25VF040_REMS:
 			for (i = 0; i < readcnt; i++)
 				readarr[i] = sst25vf040_rems_response[(offs + i) % 2];
 			break;
 		case EMULATE_SST_SST25VF032B:
 			for (i = 0; i < readcnt; i++)
 				readarr[i] = sst25vf032b_rems_response[(offs + i) % 2];
 			break;
 		case EMULATE_MACRONIX_MX25L6436:
 			if (readcnt > 0)
 				memset(readarr, 0x16, readcnt);
 			break;
+		case EMULATE_GIGADEVICE_GD25Q128C:
+			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;
 		}
 		break;
 	case JEDEC_REMS:
 		/* REMS response has wraparound and uses an address parameter. */
 		if (writecnt < JEDEC_REMS_OUTSIZE)
 			break;
 		offs = writearr[1] << 16 | writearr[2] << 8 | writearr[3];
 		offs += writecnt - JEDEC_REMS_OUTSIZE;
 		switch (emu_chip) {
 		case EMULATE_SST_SST25VF040_REMS:
 			for (i = 0; i < readcnt; i++)
 				readarr[i] = sst25vf040_rems_response[(offs + i) % 2];
 			break;
 		case EMULATE_SST_SST25VF032B:
 			for (i = 0; i < readcnt; i++)
 				readarr[i] = sst25vf032b_rems_response[(offs + i) % 2];
 			break;
 		case EMULATE_MACRONIX_MX25L6436:
 			for (i = 0; i < readcnt; i++)
 				readarr[i] = mx25l6436_rems_response[(offs + i) % 2];
 			break;
+		case EMULATE_GIGADEVICE_GD25Q128C:
+			for (i = 0; i < readcnt; i++)
+				readarr[i] = gd25q128c_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;
 		}
 		break;
 	case JEDEC_RDID:
 		switch (emu_chip) {
 		case EMULATE_SST_SST25VF032B:
 			if (readcnt > 0)
 				readarr[0] = 0xbf;
 			if (readcnt > 1)
 				readarr[1] = 0x25;
 			if (readcnt > 2)
 				readarr[2] = 0x4a;
 			break;
 		case EMULATE_MACRONIX_MX25L6436:
 			if (readcnt > 0)
 				readarr[0] = 0xc2;
 			if (readcnt > 1)
 				readarr[1] = 0x20;
 			if (readcnt > 2)
 				readarr[2] = 0x17;
 			break;
+		case EMULATE_GIGADEVICE_GD25Q128C:
+			if (readcnt > 0)
+				readarr[0] = 0xc8;
+			if (readcnt > 1)
+				readarr[1] = 0x40;
+			if (readcnt > 2)
+				readarr[2] = 0x18;
+			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 (emu_chip == EMULATE_EON_EN25QH128 && en25qh128_otp_mode) {
+			memset(readarr, (emu_status & 0x7F) | (en25qh128_otp_bit << 7), readcnt);
+			break;
+		}
 		memset(readarr, emu_status, readcnt);
 		break;
+	case JEDEC_RDSR2:
+		if (emu_chip != EMULATE_GIGADEVICE_GD25Q128C && emu_chip != EMULATE_WINBOND_W25Q40_V)
+			break;
+		memset(readarr, (emu_status >> 8) & 0xff, readcnt);
+		break;
+	case JEDEC_RDSR3:
+		if (emu_chip != EMULATE_GIGADEVICE_GD25Q128C)
+			break;
+		memset(readarr, (emu_status >> 16) & 0xff, readcnt);
+		break;
 	/* FIXME: this should be chip-specific. */
 	case JEDEC_EWSR:
 	case JEDEC_WREN:
 		emu_status |= SPI_SR_WEL;
 		break;
-	case JEDEC_WRSR:
+	case JEDEC_WRSR1:
+		if (!(emu_status & SPI_SR_WEL)) {
+			msg_perr("WRSR1 attempted, but WEL is 0!\n");
+			break;
+		}
+		if (emu_chip == EMULATE_EON_EN25QH128 && en25qh128_otp_mode) {
+			en25qh128_otp_bit = 1;
+			msg_pdbg("OTP bit set...\n");
+			break;
+		}
+		if (emu_chip == EMULATE_WINBOND_W25Q40_V) {
+			/* Make sure reserved bits and read-only bits are not set.
+			 * For W25Q40.V, SUS (bit_7) and reserved (bit_2) bits in SR2 are read-only. */
+			read_only_bits = 0x84;
+			/* If any of the Lock Bits in SR2, LB[1..3] are set, then they are read-only. */
+			if (emu_status & 0x00ff00 & (1 << 3))
+				read_only_bits |= 1 << 3;
+			if (emu_status & 0x00ff00 & (1 << 4))
+				read_only_bits |= 1 << 4;
+			if (emu_status & 0x00ff00 & (1 << 5))
+				read_only_bits |= 1 << 5;
+			if (writecnt == 3)
+				emu_status |= ((writearr[2] & ~read_only_bits) & 0xff) << 8;
+			else
+				emu_status &= 0x00ff;
+		}
+		/* FIXME: add some reasonable simulation of the busy flag */
+		emu_status |= (writearr[1] & ~SPI_SR_WIP) & 0xffff;
+		msg_pdbg2("WRSR1 wrote 0x%02x.\n", emu_status & 0xffff);
+		break;
+	case JEDEC_WRSR2:
+		if (emu_chip != EMULATE_GIGADEVICE_GD25Q128C)
+			break;
 		if (!(emu_status & SPI_SR_WEL)) {
-			msg_perr("WRSR attempted, but WEL is 0!\n");
+			msg_perr("WRSR2 attempted, but WEL is 0!\n");
 			break;
 		}
 		/* FIXME: add some reasonable simulation of the busy flag */
-		emu_status = writearr[1] & ~SPI_SR_WIP;
-		msg_pdbg2("WRSR wrote 0x%02x.\n", emu_status);
+		/* Make sure reserved bits and read-only bits are not set.
+		 * For GD25Q128C, SUS1 (bit_7) and SUS2 (bit_2) bits in SR2 are read-only. */
+		read_only_bits = 0x84;
+		/* If any of the Lock Bits, LB[1..3] are set, then they are read-only. */
+		if (emu_status & 0x00ff00 & (1 << 3))
+			read_only_bits |= 1 << 3;
+		if (emu_status & 0x00ff00 & (1 << 4))
+			read_only_bits |= 1 << 4;
+		if (emu_status & 0x00ff00 & (1 << 5))
+			read_only_bits |= 1 << 5;
+		emu_status |= (writearr[1] & ~read_only_bits) & 0xff00;
+		break;
+	case JEDEC_WRSR3:
+		if (emu_chip != EMULATE_GIGADEVICE_GD25Q128C)
+			break;
+		if (!(emu_status & SPI_SR_WEL)) {
+			msg_perr("WRSR3 attempted, but WEL is 0!\n");
+			break;
+		}
+		/* FIXME: add some reasonable simulation of the busy flag */
+		/* Make sure reserved bits and read-only bits are not set.
+		 * For GD25Q128C, bit_{0, 1, 3, 4} in SR3 are reserved. */
+		read_only_bits = 0x1b;
+		emu_status |= (writearr[1] & ~read_only_bits) &0xff0000;
 		break;
 	case JEDEC_READ:
 		offs = writearr[1] << 16 | writearr[2] << 8 | writearr[3];
+		if (emu_chip == EMULATE_EON_EN25QH128 && en25qh128_otp_mode) {
+			if (en25qh128_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;
+			}
+			memcpy(readarr, en25qh128_otp_contents + (offs & 0xfff), readcnt);
+			break;
+		}
 		/* Truncate to emu_chip_size. */
 		offs %= emu_chip_size;
 		if (readcnt > 0)
 			memcpy(readarr, flashchip_contents + offs, readcnt);
 		break;
 	case JEDEC_BYTE_PROGRAM:
 		offs = writearr[1] << 16 | writearr[2] << 8 | writearr[3];
+		if (emu_chip == EMULATE_EON_EN25QH128 && en25qh128_otp_mode) {
+			if (en25qh128_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(en25qh128_otp_contents + (offs & 0xfff), writearr + 4, writecnt - 4);
+			break;
+		}
 		/* Truncate to emu_chip_size. */
 		offs %= emu_chip_size;
 		if (writecnt < 5) {
 			msg_perr("BYTE PROGRAM size too short!\n");
 			return 1;
 		}
 		if (writecnt - 4 > emu_max_byteprogram_size) {
 			msg_perr("Max BYTE PROGRAM size exceeded!\n");
 			return 1;
 		}
 		memcpy(flashchip_contents + offs, writearr + 4, writecnt - 4);
 		break;
 	case JEDEC_AAI_WORD_PROGRAM:
@@ -651,39 +1099,53 @@  static int emulate_spi_chip_response(unsigned int writecnt,
 			}
 			if (writecnt > JEDEC_AAI_WORD_PROGRAM_CONT_OUTSIZE) {
 				msg_perr("Continuation AAI WORD PROGRAM size "
 					 "too long!\n");
 				return 1;
 			}
 			memcpy(flashchip_contents + aai_offs, writearr + 1, 2);
 			aai_offs += 2;
 		}
 		break;
 	case JEDEC_WRDI:
 		if (emu_max_aai_size)
 			emu_status &= ~SPI_SR_AAI;
+		if (emu_chip == EMULATE_EON_EN25QH128)
+			en25qh128_otp_mode = 0;
 		break;
 	case JEDEC_SE:
 		if (!emu_jedec_se_size)
 			break;
 		if (writecnt != JEDEC_SE_OUTSIZE) {
 			msg_perr("SECTOR ERASE 0x20 outsize invalid!\n");
 			return 1;
 		}
 		if (readcnt != JEDEC_SE_INSIZE) {
 			msg_perr("SECTOR ERASE 0x20 insize invalid!\n");
 			return 1;
 		}
 		offs = writearr[1] << 16 | writearr[2] << 8 | writearr[3];
+		if (emu_chip == EMULATE_EON_EN25QH128 && en25qh128_otp_mode) {
+			if (en25qh128_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(en25qh128_otp_contents, 0xff, 512);
+			break;
+		}
 		if (offs & (emu_jedec_se_size - 1))
 			msg_pdbg("Unaligned SECTOR ERASE 0x20: 0x%x\n", offs);
 		offs &= ~(emu_jedec_se_size - 1);
 		memset(flashchip_contents + offs, 0xff, emu_jedec_se_size);
 		break;
 	case JEDEC_BE_52:
 		if (!emu_jedec_be_52_size)
 			break;
 		if (writecnt != JEDEC_BE_52_OUTSIZE) {
 			msg_perr("BLOCK ERASE 0x52 outsize invalid!\n");
 			return 1;
 		}
 		if (readcnt != JEDEC_BE_52_INSIZE) {
@@ -734,26 +1196,27 @@  static int emulate_spi_chip_response(unsigned int writecnt,
 		if (writecnt != JEDEC_CE_C7_OUTSIZE) {
 			msg_perr("CHIP ERASE 0xc7 outsize invalid!\n");
 			return 1;
 		}
 		if (readcnt != JEDEC_CE_C7_INSIZE) {
 			msg_perr("CHIP ERASE 0xc7 insize invalid!\n");
 			return 1;
 		}
 		/* JEDEC_CE_C7_OUTSIZE is 1 (no address) -> no offset. */
 		/* emu_jedec_ce_c7_size is emu_chip_size. */
 		memset(flashchip_contents, 0xff, emu_jedec_ce_c7_size);
 		break;
 	case JEDEC_SFDP:
+		// TODO(hatim): SFDP for GD25Q128C, EN25QH128 and W25Q40.V
 		if (emu_chip != EMULATE_MACRONIX_MX25L6436)
 			break;
 		if (writecnt < 4)
 			break;
 		offs = writearr[1] << 16 | writearr[2] << 8 | writearr[3];

 		/* SFDP expects one dummy byte after the address. */
 		if (writecnt == 4) {
 			/* The dummy byte was not written, make sure it is read instead.
 			 * Shifting and shortening the read array does achieve this goal.
 			 */
 			readarr++;
 			readcnt--;
@@ -768,26 +1231,156 @@  static int emulate_spi_chip_response(unsigned int writecnt,
 		 * This is a reasonable implementation choice in hardware because it saves a few gates. */
 		if (offs >= sizeof(sfdp_table)) {
 			msg_pdbg("Wrapping the start address around the SFDP table boundary (using 0x%x "
 				 "instead of 0x%x).\n", (unsigned int)(offs % sizeof(sfdp_table)), offs);
 			offs %= sizeof(sfdp_table);
 		}
 		toread = min(sizeof(sfdp_table) - offs, readcnt);
 		memcpy(readarr, sfdp_table + offs, toread);
 		if (toread < readcnt)
 			msg_pdbg("Crossing the SFDP table boundary in a single "
 				 "continuous chunk produces undefined results "
 				 "after that point.\n");
 		break;
+	case JEDEC_READ_SEC_REG:
+		if (emu_chip != EMULATE_GIGADEVICE_GD25Q128C && 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, i.e., 512 bytes for GD25Q128C,
+		 * or 256 bytes for W25Q40.V. */
+		offs %= (emu_chip == EMULATE_GIGADEVICE_GD25Q128C) ? 512 : 256;
+		if (readcnt > 0) {
+			switch ((writearr[2] & 0xf0) >> 4) {
+			case 0x01:
+				memcpy(readarr, ((emu_chip == EMULATE_GIGADEVICE_GD25Q128C) ?
+					gd25q128c_otp_1_contents : w25q40v_otp_1_contents) + offs, readcnt);
+				break;
+			case 0x02:
+				memcpy(readarr, ((emu_chip == EMULATE_GIGADEVICE_GD25Q128C) ?
+					gd25q128c_otp_2_contents : w25q40v_otp_2_contents) + offs, readcnt);
+				break;
+			case 0x03:
+				memcpy(readarr, ((emu_chip == EMULATE_GIGADEVICE_GD25Q128C) ?
+					gd25q128c_otp_3_contents : w25q40v_otp_3_contents) + offs, readcnt);
+				break;
+			default:
+				break;
+			}
+		}
+		break;
+	case JEDEC_PROG_BYTE_SEC_REG:
+		if (emu_chip != EMULATE_GIGADEVICE_GD25Q128C && 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, i.e., 512 bytes for GD25Q128C,
+		 * or 256 bytes for W25Q40.V. */
+		offs %= (emu_chip == EMULATE_GIGADEVICE_GD25Q128C) ? 512 : 256;
+		/* If corresponding Lock Bits are set then the register is locked against
+		 * any further write attempts. */
+		switch ((writearr[2] & 0xf0) >> 4) {
+		case 0x01:
+			/* LB1 is NOT set */
+			if (!(emu_status & 0x00ff00 & (1 << 3)))
+				memcpy(((emu_chip == EMULATE_GIGADEVICE_GD25Q128C) ?
+					gd25q128c_otp_1_contents : w25q40v_otp_1_contents) + offs,
+					writearr + 4, writecnt - 4);
+			break;
+		case 0x02:
+			/* LB2 is NOT set */
+			if (!(emu_status & 0x00ff00 & (1 << 4)))
+				memcpy(((emu_chip == EMULATE_GIGADEVICE_GD25Q128C) ?
+					gd25q128c_otp_2_contents : w25q40v_otp_2_contents) + offs,
+					writearr + 4, writecnt - 4);
+			break;
+		case 0x03:
+			/* LB3 is NOT set */
+			if (!(emu_status & 0x00ff00 & (1 << 5)))
+				memcpy(((emu_chip == EMULATE_GIGADEVICE_GD25Q128C) ?
+					gd25q128c_otp_3_contents : w25q40v_otp_3_contents) + offs,
+					writearr + 4, writecnt - 4);
+			break;
+		default:
+			break;
+		}
+		break;
+	case JEDEC_ERASE_SEC_REG:
+		if (emu_chip != EMULATE_GIGADEVICE_GD25Q128C && 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;
+		/* If corresponding Lock Bits are set then the register is locked against
+		 * any further erase attempts. */
+		switch ((writearr[2] & 0xf0) >> 4) {
+		case 0x01:
+			/* LB1 is NOT set */
+			if (!(emu_status & 0x00ff00 & (1 << 3)))
+				memset((emu_chip == EMULATE_GIGADEVICE_GD25Q128C) ?
+					gd25q128c_otp_1_contents : w25q40v_otp_1_contents, 0xff,
+					(emu_chip == EMULATE_GIGADEVICE_GD25Q128C) ? 512 : 256);
+			break;
+		case 0x02:
+			/* LB2 is NOT set */
+			if (!(emu_status & 0x00ff00 & (1 << 4)))
+				memset((emu_chip == EMULATE_GIGADEVICE_GD25Q128C) ?
+					gd25q128c_otp_2_contents : w25q40v_otp_2_contents, 0xff,
+					(emu_chip == EMULATE_GIGADEVICE_GD25Q128C) ? 512 : 256);
+			break;
+		case 0x03:
+			/* LB3 is NOT set */
+			if (!(emu_status & 0x00ff00 & (1 << 5)))
+				memset((emu_chip == EMULATE_GIGADEVICE_GD25Q128C) ?
+					gd25q128c_otp_3_contents : w25q40v_otp_3_contents, 0xff,
+					(emu_chip == EMULATE_GIGADEVICE_GD25Q128C) ? 512 : 256);
+			break;
+		default:
+			break;
+		}
+		break;
+	case JEDEC_ENTER_OTP:
+		/* Eon chip specific opcode, not observed in other
+		 * manufacturers (yet, please update this when required). */
+		if (emu_chip != EMULATE_EON_EN25QH128)
+			break;
+		en25qh128_otp_mode = 1;
+		msg_pdbg("Entered OTP mode...\n");
+		break;
 	default:
 		/* No special response. */
 		break;
 	}
 	if (writearr[0] != JEDEC_WREN && writearr[0] != JEDEC_EWSR)
 		emu_status &= ~SPI_SR_WEL;
 	return 0;
 }
 #endif

 static int dummy_spi_send_command(struct flashctx *flash, unsigned int writecnt,
 				  unsigned int readcnt,
 				  const unsigned char *writearr,
@@ -799,26 +1392,29 @@  static int dummy_spi_send_command(struct flashctx *flash, unsigned int writecnt,

 	msg_pspew(" writing %u bytes:", writecnt);
 	for (i = 0; i < writecnt; i++)
 		msg_pspew(" 0x%02x", writearr[i]);

 	/* Response for unknown commands and missing chip is 0xff. */
 	memset(readarr, 0xff, readcnt);
 #if EMULATE_SPI_CHIP
 	switch (emu_chip) {
 	case EMULATE_ST_M25P10_RES:
 	case EMULATE_SST_SST25VF040_REMS:
 	case EMULATE_SST_SST25VF032B:
 	case EMULATE_MACRONIX_MX25L6436:
+	case EMULATE_GIGADEVICE_GD25Q128C:
+	case EMULATE_EON_EN25QH128:
+	case EMULATE_WINBOND_W25Q40_V:
 		if (emulate_spi_chip_response(writecnt, readcnt, writearr,
 					      readarr)) {
 			msg_pdbg("Invalid command sent to flash chip!\n");
 			return 1;
 		}
 		break;
 	default:
 		break;
 	}
 #endif
 	msg_pspew(" reading %u bytes:", readcnt);
 	for (i = 0; i < readcnt; i++)
 		msg_pspew(" 0x%02x", readarr[i]);
diff --git a/otp.c b/otp.c
index 4af4b84..20a8f84 100644
--- a/otp.c
+++ b/otp.c
@@ -124,52 +124,52 @@  int eon_print_status_generic(struct flashctx *flash) {
 }

 /* Read len bytes of the security register (corresponding to otp_region) into buf,
  * starting from start_byte. */
 int eon_read_generic(struct flashctx *flash, uint8_t *buf, enum otp_region otp_region,
 	uint32_t start_byte, uint32_t len) {
 	int result = otp_error_check(flash, otp_region, start_byte, len);
 	if (result) {
 		msg_cerr("%s failed\n", __func__);
 		return result;
 	}

 	enter_otp_mode(flash);
-	result = flash->chip->read(flash, buf, start_byte, len);
+	result = flash->chip->read(flash, buf, flash->chip->otp->region[otp_region].addr | start_byte, len);
 	exit_otp_mode(flash);

 	if (result)
 		msg_cerr("%s failed\n", __func__);
 	return result;
 }

 /* Write len bytes to the security register (corresponding to otp_region) form buf,
  * starting from start_byte. */
 int eon_write_generic(struct flashctx *flash, const uint8_t *buf, enum otp_region otp_region,
 	uint32_t start_byte, uint32_t len) {
 	int result = otp_error_check(flash, otp_region, start_byte, len);
 	if (result) {
 		msg_cerr("%s failed\n", __func__);
 		return result;
 	}
 	if (flash->chip->otp->status(flash, otp_region)) {
 		msg_cdbg("OTP memory region %d is permanently locked and cannot be written to\n",
 			otp_region + 1);
 		msg_cerr("%s failed\n", __func__);
 		return 1;
 	}

 	enter_otp_mode(flash);
-	result = flash->chip->write(flash, buf, start_byte, len);
+	result = flash->chip->write(flash, buf, flash->chip->otp->region[otp_region].addr | start_byte, len);
 	exit_otp_mode(flash);

 	if (result)
 		msg_cerr("%s failed\n", __func__);
 	return result;
 }

 /* Erase the security register corresponding to otp_region. */
 int eon_erase_generic(struct flashctx *flash, enum otp_region otp_region) {
 	int result = otp_error_check(flash, otp_region, 0x000000, 0);
 	if (result) {
 		msg_cerr("%s failed\n", __func__);
 		return result;