dummyflasher: add SR2 and SR3 emulation harness

Prepare everything for emulating SR2 and SR3 for chips that have it.

This is needed for accessing SRP1 and WPS bits which are involved in
write protection. The emulated register doesn't affect anything yet
and will be tested by write-protection tests.

Tested: check how input value affects status registers of emulated chip

flashrom -V -p dummy:emulate=W25Q128FV,spi_status=0x12 |
        grep 'Initial status register'
flashrom -V -p dummy:emulate=W25Q128FV,spi_status=0x1234 |
        grep 'Initial status register'
flashrom -V -p dummy:emulate=W25Q128FV,spi_status=0x123456 |
        grep 'Initial status register'

Mind that at this point there are no chips that emulate more than one
status register.

Change-Id: I177ae3f068f03380f5b3941d9996a07205672e59
Signed-off-by: Sergii Dmytruk <sergii.dmytruk@3mdeb.com>
Original-Reviewed-on: https://review.coreboot.org/c/flashrom/+/59072
Original-Reviewed-by: Anastasia Klimchuk <aklm@chromium.org>
Original-Reviewed-by: Thomas Heijligen <src@posteo.de>
Reviewed-on: https://review.coreboot.org/c/flashrom-stable/+/71457
Reviewed-by: Nico Huber <nico.h@gmx.de>
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
diff --git a/dummyflasher.c b/dummyflasher.c
index 374290b..eaf8ccd 100644
--- a/dummyflasher.c
+++ b/dummyflasher.c
@@ -13,6 +13,7 @@
  * GNU General Public License for more details.
  */
 
+#include <assert.h>
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
@@ -39,8 +40,13 @@
 	enum emu_chip emu_chip;
 	char *emu_persistent_image;
 	unsigned int emu_chip_size;
+	/* Note: W25Q128FV doesn't change value of SR2 if it's not provided, but
+	 *       even its previous generations do, so don't forget to update
+	 *       WRSR code on enabling WRSR_EXT for more chips. */
+	bool emu_wrsr_ext;
 	int emu_modified;	/* is the image modified since reading it? */
-	uint8_t emu_status;
+	uint8_t emu_status[3];
+	uint8_t emu_status_len;	/* number of emulated status registers */
 	unsigned int emu_max_byteprogram_size;
 	unsigned int emu_max_aai_size;
 	unsigned int emu_jedec_se_size;
@@ -269,6 +275,7 @@
 		data->emu_chip_size = 128 * 1024;
 		data->emu_max_byteprogram_size = 128;
 		data->emu_max_aai_size = 0;
+		data->emu_status_len = 1;
 		data->emu_jedec_se_size = 0;
 		data->emu_jedec_be_52_size = 0;
 		data->emu_jedec_be_d8_size = 32 * 1024;
@@ -282,6 +289,7 @@
 		data->emu_chip_size = 512 * 1024;
 		data->emu_max_byteprogram_size = 1;
 		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 = 0;
@@ -295,6 +303,7 @@
 		data->emu_chip_size = 4 * 1024 * 1024;
 		data->emu_max_byteprogram_size = 1;
 		data->emu_max_aai_size = 2;
+		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;
@@ -308,6 +317,7 @@
 		data->emu_chip_size = 8 * 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;
@@ -321,6 +331,7 @@
 		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;
@@ -337,8 +348,10 @@
 
 	status = extract_programmer_param("spi_status");
 	if (status) {
+		unsigned int emu_status;
+
 		errno = 0;
-		data->emu_status = strtoul(status, &endptr, 0);
+		emu_status = strtoul(status, &endptr, 0);
 		if (errno != 0 || status == endptr) {
 			free(status);
 			msg_perr("Error: initial status register specified, "
@@ -346,8 +359,26 @@
 			return 1;
 		}
 		free(status);
-		msg_pdbg("Initial status register is set to 0x%02x.\n",
-			 data->emu_status);
+
+		data->emu_status[0] = emu_status;
+		data->emu_status[1] = emu_status >> 8;
+		data->emu_status[2] = emu_status >> 16;
+
+		if (data->emu_status_len == 3) {
+			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",
+				 data->emu_status[0], data->emu_status[1], data->emu_status[2]);
+		} else if (data->emu_status_len == 2) {
+			msg_pdbg("Initial status registers:\n"
+				 "\tSR1 is set to 0x%02x\n"
+				 "\tSR2 is set to 0x%02x\n",
+				 data->emu_status[0], data->emu_status[1]);
+		} else {
+			msg_pdbg("Initial status register is set to 0x%02x.\n",
+				 data->emu_status[0]);
+		}
 	}
 
 	data->flashchip_contents = malloc(data->emu_chip_size);
@@ -496,6 +527,15 @@
 	return;
 }
 
+static uint8_t get_reg_ro_bit_mask(enum flash_reg reg)
+{
+	/* Whoever adds a new register must not forget to update this function
+	   or at least shouldn't use it incorrectly. */
+	assert(reg == STATUS1 || reg == STATUS2 || reg == STATUS3);
+
+	return reg == STATUS1 ? SPI_SR_WIP : 0;
+}
+
 static int emulate_spi_chip_response(unsigned int writecnt,
 				     unsigned int readcnt,
 				     const unsigned char *writearr,
@@ -503,6 +543,8 @@
 				     struct emu_data *data)
 {
 	unsigned int offs, i, toread;
+	uint8_t ro_bits;
+	bool wrsr_ext;
 	static int unsigned aai_offs;
 	const unsigned char sst25vf040_rems_response[2] = {0xbf, 0x44};
 	const unsigned char sst25vf032b_rems_response[2] = {0xbf, 0x4a};
@@ -532,7 +574,7 @@
 		}
 	}
 
-	if (data->emu_max_aai_size && (data->emu_status & SPI_SR_AAI)) {
+	if (data->emu_max_aai_size && (data->emu_status[0] & SPI_SR_AAI)) {
 		if (writearr[0] != JEDEC_AAI_WORD_PROGRAM &&
 		    writearr[0] != JEDEC_WRDI &&
 		    writearr[0] != JEDEC_RDSR) {
@@ -632,21 +674,72 @@
 		}
 		break;
 	case JEDEC_RDSR:
-		memset(readarr, data->emu_status, readcnt);
+		memset(readarr, data->emu_status[0], readcnt);
+		break;
+	case JEDEC_RDSR2:
+		if (data->emu_status_len >= 2)
+			memset(readarr, data->emu_status[1], readcnt);
+		break;
+	case JEDEC_RDSR3:
+		if (data->emu_status_len >= 3)
+			memset(readarr, data->emu_status[2], readcnt);
 		break;
 	/* FIXME: this should be chip-specific. */
 	case JEDEC_EWSR:
 	case JEDEC_WREN:
-		data->emu_status |= SPI_SR_WEL;
+		data->emu_status[0] |= SPI_SR_WEL;
 		break;
 	case JEDEC_WRSR:
-		if (!(data->emu_status & SPI_SR_WEL)) {
+		if (!(data->emu_status[0] & SPI_SR_WEL)) {
 			msg_perr("WRSR attempted, but WEL is 0!\n");
 			break;
 		}
+
+		wrsr_ext = (writecnt == 3 && data->emu_wrsr_ext);
+
 		/* FIXME: add some reasonable simulation of the busy flag */
-		data->emu_status = writearr[1] & ~SPI_SR_WIP;
-		msg_pdbg2("WRSR wrote 0x%02x.\n", data->emu_status);
+
+		ro_bits = get_reg_ro_bit_mask(STATUS1);
+		data->emu_status[0] &= ro_bits;
+		data->emu_status[0] |= writearr[1] & ~ro_bits;
+		if (wrsr_ext) {
+			ro_bits = get_reg_ro_bit_mask(STATUS2);
+			data->emu_status[1] &= ro_bits;
+			data->emu_status[1] |= writearr[2] & ~ro_bits;
+		}
+
+		if (wrsr_ext)
+			msg_pdbg2("WRSR wrote 0x%02x%02x.\n", data->emu_status[1], data->emu_status[0]);
+		else
+			msg_pdbg2("WRSR wrote 0x%02x.\n", data->emu_status[0]);
+		break;
+	case JEDEC_WRSR2:
+		if (data->emu_status_len < 2)
+			break;
+		if (!(data->emu_status[0] & SPI_SR_WEL)) {
+			msg_perr("WRSR2 attempted, but WEL is 0!\n");
+			break;
+		}
+
+		ro_bits = get_reg_ro_bit_mask(STATUS2);
+		data->emu_status[1] &= ro_bits;
+		data->emu_status[1] |= (writearr[1] & ~ro_bits);
+
+		msg_pdbg2("WRSR2 wrote 0x%02x.\n", data->emu_status[1]);
+		break;
+	case JEDEC_WRSR3:
+		if (data->emu_status_len < 3)
+			break;
+		if (!(data->emu_status[0] & SPI_SR_WEL)) {
+			msg_perr("WRSR3 attempted, but WEL is 0!\n");
+			break;
+		}
+
+		ro_bits = get_reg_ro_bit_mask(STATUS3);
+		data->emu_status[2] &= ro_bits;
+		data->emu_status[2] |= (writearr[1] & ~ro_bits);
+
+		msg_pdbg2("WRSR3 wrote 0x%02x.\n", data->emu_status[2]);
 		break;
 	case JEDEC_READ:
 		offs = writearr[1] << 16 | writearr[2] << 8 | writearr[3];
@@ -673,7 +766,7 @@
 	case JEDEC_AAI_WORD_PROGRAM:
 		if (!data->emu_max_aai_size)
 			break;
-		if (!(data->emu_status & SPI_SR_AAI)) {
+		if (!(data->emu_status[0] & SPI_SR_AAI)) {
 			if (writecnt < JEDEC_AAI_WORD_PROGRAM_OUTSIZE) {
 				msg_perr("Initial AAI WORD PROGRAM size too "
 					 "short!\n");
@@ -684,7 +777,7 @@
 					 "long!\n");
 				return 1;
 			}
-			data->emu_status |= SPI_SR_AAI;
+			data->emu_status[0] |= SPI_SR_AAI;
 			aai_offs = writearr[1] << 16 | writearr[2] << 8 |
 				   writearr[3];
 			/* Truncate to emu_chip_size. */
@@ -709,7 +802,7 @@
 		break;
 	case JEDEC_WRDI:
 		if (data->emu_max_aai_size)
-			data->emu_status &= ~SPI_SR_AAI;
+			data->emu_status[0] &= ~SPI_SR_AAI;
 		break;
 	case JEDEC_SE:
 		if (!data->emu_jedec_se_size)
@@ -837,7 +930,7 @@
 		break;
 	}
 	if (writearr[0] != JEDEC_WREN && writearr[0] != JEDEC_EWSR)
-		data->emu_status &= ~SPI_SR_WEL;
+		data->emu_status[0] &= ~SPI_SR_WEL;
 	return 0;
 }