Sergii Dmytruk has uploaded this change for review. ( https://review.coreboot.org/c/flashrom/+/59404 )
Change subject: [RFC] dummyflasher: add EN25QH128 and W25Q40.V with OTP ......................................................................
[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