/* * This file is part of the flashrom project. * * Copyright (C) 2009,2010 Carl-Daniel Hailfinger * * 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. */ #include #include #include #include #include #include "flash.h" #include "chipdrivers.h" #include "programmer.h" /* Remove the #define below if you don't want SPI flash chip emulation. */ #define EMULATE_SPI_CHIP 1 #if EMULATE_SPI_CHIP #define EMULATE_CHIP 1 #include "spi.h" #endif #if EMULATE_CHIP #include #include #endif #if EMULATE_CHIP static uint8_t *flashchip_contents = NULL; enum emu_chip { EMULATE_NONE, EMULATE_ST_M25P10_RES, EMULATE_SST_SST25VF040_REMS, EMULATE_SST_SST25VF032B, EMULATE_MACRONIX_MX25L6436, EMULATE_WINBOND_W25Q128FV, }; 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; static unsigned char spi_blacklist[256]; static unsigned char spi_ignorelist[256]; static unsigned int spi_blacklist_size = 0; static unsigned int spi_ignorelist_size = 0; static uint8_t emu_status = 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 0x08, 0x3B, 0x00, 0xFF, // @0x28 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 }; #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); static uint8_t dummy_chip_readb(const struct flashctx *flash, const chipaddr addr); static uint16_t dummy_chip_readw(const struct flashctx *flash, const chipaddr addr); static uint32_t dummy_chip_readl(const struct flashctx *flash, const chipaddr addr); static void dummy_chip_readn(const struct flashctx *flash, uint8_t *buf, const chipaddr addr, size_t len); static const struct spi_master spi_master_dummyflasher = { .features = SPI_MASTER_4BA, .max_data_read = MAX_DATA_READ_UNLIMITED, .max_data_write = MAX_DATA_UNSPECIFIED, .command = dummy_spi_send_command, .multicommand = default_spi_send_multicommand, .read = default_spi_read, .write_256 = dummy_spi_write_256, .write_aai = default_spi_write_aai, }; static const struct par_master par_master_dummy = { .chip_readb = dummy_chip_readb, .chip_readw = dummy_chip_readw, .chip_readl = dummy_chip_readl, .chip_readn = dummy_chip_readn, .chip_writeb = dummy_chip_writeb, .chip_writew = dummy_chip_writew, .chip_writel = dummy_chip_writel, .chip_writen = dummy_chip_writen, }; static 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); } #endif return 0; } int dummy_init(void) { char *bustext = NULL; char *tmp = NULL; unsigned int i; #if EMULATE_SPI_CHIP char *status = NULL; #endif #if EMULATE_CHIP struct stat image_stat; #endif msg_pspew("%s\n", __func__); bustext = extract_programmer_param("bus"); msg_pdbg("Requested buses are: %s\n", bustext ? bustext : "default"); if (!bustext) bustext = strdup("parallel+lpc+fwh+spi"); /* Convert the parameters to lowercase. */ tolower_string(bustext); dummy_buses_supported = BUS_NONE; if (strstr(bustext, "parallel")) { dummy_buses_supported |= BUS_PARALLEL; msg_pdbg("Enabling support for %s flash.\n", "parallel"); } if (strstr(bustext, "lpc")) { dummy_buses_supported |= BUS_LPC; msg_pdbg("Enabling support for %s flash.\n", "LPC"); } if (strstr(bustext, "fwh")) { dummy_buses_supported |= BUS_FWH; msg_pdbg("Enabling support for %s flash.\n", "FWH"); } if (strstr(bustext, "spi")) { dummy_buses_supported |= BUS_SPI; msg_pdbg("Enabling support for %s flash.\n", "SPI"); } if (dummy_buses_supported == BUS_NONE) msg_pdbg("Support for all flash bus types disabled.\n"); free(bustext); tmp = extract_programmer_param("spi_write_256_chunksize"); if (tmp) { spi_write_256_chunksize = atoi(tmp); free(tmp); if (spi_write_256_chunksize < 1) { msg_perr("invalid spi_write_256_chunksize\n"); return 1; } } tmp = extract_programmer_param("spi_blacklist"); if (tmp) { i = strlen(tmp); if (!strncmp(tmp, "0x", 2)) { i -= 2; memmove(tmp, tmp + 2, i + 1); } if ((i > 512) || (i % 2)) { msg_perr("Invalid SPI command blacklist length\n"); free(tmp); return 1; } spi_blacklist_size = i / 2; for (i = 0; i < spi_blacklist_size * 2; i++) { if (!isxdigit((unsigned char)tmp[i])) { msg_perr("Invalid char \"%c\" in SPI command " "blacklist\n", tmp[i]); free(tmp); return 1; } } for (i = 0; i < spi_blacklist_size; i++) { unsigned int tmp2; /* SCNx8 is apparently not supported by MSVC (and thus * MinGW), so work around it with an extra variable */ sscanf(tmp + i * 2, "%2x", &tmp2); spi_blacklist[i] = (uint8_t)tmp2; } msg_pdbg("SPI blacklist is "); for (i = 0; i < spi_blacklist_size; i++) msg_pdbg("%02x ", spi_blacklist[i]); msg_pdbg(", size %u\n", spi_blacklist_size); } free(tmp); tmp = extract_programmer_param("spi_ignorelist"); if (tmp) { i = strlen(tmp); if (!strncmp(tmp, "0x", 2)) { i -= 2; memmove(tmp, tmp + 2, i + 1); } if ((i > 512) || (i % 2)) { msg_perr("Invalid SPI command ignorelist length\n"); free(tmp); return 1; } spi_ignorelist_size = i / 2; for (i = 0; i < spi_ignorelist_size * 2; i++) { if (!isxdigit((unsigned char)tmp[i])) { msg_perr("Invalid char \"%c\" in SPI command " "ignorelist\n", tmp[i]); free(tmp); return 1; } } for (i = 0; i < spi_ignorelist_size; i++) { unsigned int tmp2; /* SCNx8 is apparently not supported by MSVC (and thus * MinGW), so work around it with an extra variable */ sscanf(tmp + i * 2, "%2x", &tmp2); spi_ignorelist[i] = (uint8_t)tmp2; } msg_pdbg("SPI ignorelist is "); for (i = 0; i < spi_ignorelist_size; i++) msg_pdbg("%02x ", spi_ignorelist[i]); msg_pdbg(", size %u\n", spi_ignorelist_size); } free(tmp); #if EMULATE_CHIP tmp = extract_programmer_param("emulate"); if (!tmp) { msg_pdbg("Not emulating any flash chip.\n"); /* Nothing else to do. */ goto dummy_init_out; } #if EMULATE_SPI_CHIP if (!strcmp(tmp, "M25P10.RES")) { emu_chip = EMULATE_ST_M25P10_RES; emu_chip_size = 128 * 1024; emu_max_byteprogram_size = 128; emu_max_aai_size = 0; emu_jedec_se_size = 0; emu_jedec_be_52_size = 0; emu_jedec_be_d8_size = 32 * 1024; emu_jedec_ce_60_size = 0; emu_jedec_ce_c7_size = emu_chip_size; msg_pdbg("Emulating ST M25P10.RES SPI flash chip (RES, page " "write)\n"); } if (!strcmp(tmp, "SST25VF040.REMS")) { emu_chip = EMULATE_SST_SST25VF040_REMS; emu_chip_size = 512 * 1024; emu_max_byteprogram_size = 1; emu_max_aai_size = 0; emu_jedec_se_size = 4 * 1024; emu_jedec_be_52_size = 32 * 1024; emu_jedec_be_d8_size = 0; emu_jedec_ce_60_size = emu_chip_size; emu_jedec_ce_c7_size = 0; msg_pdbg("Emulating SST SST25VF040.REMS SPI flash chip (REMS, " "byte write)\n"); } if (!strcmp(tmp, "SST25VF032B")) { emu_chip = EMULATE_SST_SST25VF032B; emu_chip_size = 4 * 1024 * 1024; emu_max_byteprogram_size = 1; emu_max_aai_size = 2; 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 SST SST25VF032B SPI flash chip (RDID, AAI " "write)\n"); } 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, "W25Q128FV")) { emu_chip = EMULATE_WINBOND_W25Q128FV; 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 Winbond W25Q128FV 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; } #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", 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)) { msg_pdbg("Found persistent image %s, %jd B ", emu_persistent_image, (intmax_t)image_stat.st_size); if ((uintmax_t)image_stat.st_size == emu_chip_size) { msg_pdbg("matches.\n"); msg_pdbg("Reading %s\n", emu_persistent_image); if (read_buf_from_file(flashchip_contents, emu_chip_size, emu_persistent_image)) { msg_perr("Unable to read %s\n", emu_persistent_image); free(flashchip_contents); return 1; } } else { msg_pdbg("doesn't match.\n"); } } #endif dummy_init_out: if (register_shutdown(dummy_shutdown, NULL)) { free(flashchip_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) { msg_pspew("%s: Mapping %s, 0x%zx bytes at 0x%0*" PRIxPTR "\n", __func__, descr, len, PRIxPTR_WIDTH, phys_addr); return (void *)phys_addr; } void dummy_unmap(void *virt_addr, size_t len) { msg_pspew("%s: Unmapping 0x%zx bytes at %p\n", __func__, len, virt_addr); } static void dummy_chip_writeb(const struct flashctx *flash, uint8_t val, chipaddr addr) { msg_pspew("%s: addr=0x%" PRIxPTR ", val=0x%02x\n", __func__, addr, val); } static void dummy_chip_writew(const struct flashctx *flash, uint16_t val, chipaddr addr) { msg_pspew("%s: addr=0x%" PRIxPTR ", val=0x%04x\n", __func__, addr, val); } static void dummy_chip_writel(const struct flashctx *flash, uint32_t val, chipaddr addr) { msg_pspew("%s: addr=0x%" PRIxPTR ", val=0x%08x\n", __func__, addr, val); } static void dummy_chip_writen(const struct flashctx *flash, const uint8_t *buf, chipaddr addr, size_t len) { size_t i; msg_pspew("%s: addr=0x%" PRIxPTR ", len=0x%zx, writing data (hex):", __func__, addr, len); for (i = 0; i < len; i++) { if ((i % 16) == 0) msg_pspew("\n"); msg_pspew("%02x ", buf[i]); } } static uint8_t dummy_chip_readb(const struct flashctx *flash, const chipaddr addr) { msg_pspew("%s: addr=0x%" PRIxPTR ", returning 0xff\n", __func__, addr); return 0xff; } static uint16_t dummy_chip_readw(const struct flashctx *flash, const chipaddr addr) { msg_pspew("%s: addr=0x%" PRIxPTR ", returning 0xffff\n", __func__, addr); return 0xffff; } static uint32_t dummy_chip_readl(const struct flashctx *flash, const chipaddr addr) { 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 w25q128fv_rems_response[2] = {0xef, 0x17}; 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; } } for (i = 0; i < spi_ignorelist_size; i++) { if (writearr[0] == spi_ignorelist[i]) { msg_cdbg("Ignoring ignorelisted SPI command 0x%02x\n", spi_ignorelist[i]); /* Return success because the command does not fail, * it is simply ignored. */ return 0; } } if (emu_max_aai_size && (emu_status & SPI_SR_AAI)) { if (writearr[0] != JEDEC_AAI_WORD_PROGRAM && writearr[0] != JEDEC_WRDI && writearr[0] != JEDEC_RDSR) { msg_perr("Forbidden opcode (0x%02x) attempted during " "AAI sequence!\n", writearr[0]); return 0; } } switch (writearr[0]) { case JEDEC_RES: if (writecnt < JEDEC_RES_OUTSIZE) break; /* offs calculation is only needed for SST chips which treat RES like REMS. */ offs = writearr[1] << 16 | writearr[2] << 8 | writearr[3]; offs += writecnt - JEDEC_REMS_OUTSIZE; switch (emu_chip) { case EMULATE_ST_M25P10_RES: if (readcnt > 0) memset(readarr, 0x10, readcnt); 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_WINBOND_W25Q128FV: if (readcnt > 0) memset(readarr, 0x17, 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_WINBOND_W25Q128FV: for (i = 0; i < readcnt; i++) readarr[i] = w25q128fv_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_WINBOND_W25Q128FV: if (readcnt > 0) readarr[0] = 0xef; if (readcnt > 1) readarr[1] = 0x40; if (readcnt > 2) readarr[2] = 0x18; break; default: /* ignore */ break; } break; case JEDEC_RDSR: memset(readarr, emu_status, readcnt); break; /* FIXME: this should be chip-specific. */ case JEDEC_EWSR: case JEDEC_WREN: emu_status |= SPI_SR_WEL; break; case JEDEC_WRSR: if (!(emu_status & SPI_SR_WEL)) { msg_perr("WRSR 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); break; case JEDEC_READ: offs = writearr[1] << 16 | writearr[2] << 8 | writearr[3]; /* 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]; /* 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: if (!emu_max_aai_size) break; if (!(emu_status & SPI_SR_AAI)) { if (writecnt < JEDEC_AAI_WORD_PROGRAM_OUTSIZE) { msg_perr("Initial AAI WORD PROGRAM size too " "short!\n"); return 1; } if (writecnt > JEDEC_AAI_WORD_PROGRAM_OUTSIZE) { msg_perr("Initial AAI WORD PROGRAM size too " "long!\n"); return 1; } emu_status |= SPI_SR_AAI; aai_offs = writearr[1] << 16 | writearr[2] << 8 | writearr[3]; /* Truncate to emu_chip_size. */ aai_offs %= emu_chip_size; memcpy(flashchip_contents + aai_offs, writearr + 4, 2); aai_offs += 2; } else { if (writecnt < JEDEC_AAI_WORD_PROGRAM_CONT_OUTSIZE) { msg_perr("Continuation AAI WORD PROGRAM size " "too short!\n"); return 1; } 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; 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 (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) { msg_perr("BLOCK ERASE 0x52 insize invalid!\n"); return 1; } offs = writearr[1] << 16 | writearr[2] << 8 | writearr[3]; if (offs & (emu_jedec_be_52_size - 1)) msg_pdbg("Unaligned BLOCK ERASE 0x52: 0x%x\n", offs); offs &= ~(emu_jedec_be_52_size - 1); memset(flashchip_contents + offs, 0xff, emu_jedec_be_52_size); break; case JEDEC_BE_D8: if (!emu_jedec_be_d8_size) break; if (writecnt != JEDEC_BE_D8_OUTSIZE) { msg_perr("BLOCK ERASE 0xd8 outsize invalid!\n"); return 1; } if (readcnt != JEDEC_BE_D8_INSIZE) { msg_perr("BLOCK ERASE 0xd8 insize invalid!\n"); return 1; } offs = writearr[1] << 16 | writearr[2] << 8 | writearr[3]; if (offs & (emu_jedec_be_d8_size - 1)) msg_pdbg("Unaligned BLOCK ERASE 0xd8: 0x%x\n", offs); offs &= ~(emu_jedec_be_d8_size - 1); memset(flashchip_contents + offs, 0xff, emu_jedec_be_d8_size); break; case JEDEC_CE_60: if (!emu_jedec_ce_60_size) break; if (writecnt != JEDEC_CE_60_OUTSIZE) { msg_perr("CHIP ERASE 0x60 outsize invalid!\n"); return 1; } if (readcnt != JEDEC_CE_60_INSIZE) { msg_perr("CHIP ERASE 0x60 insize invalid!\n"); return 1; } /* JEDEC_CE_60_OUTSIZE is 1 (no address) -> no offset. */ /* emu_jedec_ce_60_size is emu_chip_size. */ memset(flashchip_contents, 0xff, emu_jedec_ce_60_size); break; case JEDEC_CE_C7: if (!emu_jedec_ce_c7_size) break; 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: 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--; } else { /* The response is shifted if more than 5 bytes are written, because SFDP data is * already shifted out by the chip while those superfluous bytes are written. */ offs += writecnt - 5; } /* The SFDP spec implies that the start address of an SFDP read may be truncated to fit in the * SFDP table address space, i.e. the start address may be wrapped around at SFDP table size. * 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; 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, unsigned char *readarr) { unsigned int i; msg_pspew("%s:", __func__); 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_WINBOND_W25Q128FV: 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]); msg_pspew("\n"); return 0; } static int dummy_spi_write_256(struct flashctx *flash, const uint8_t *buf, unsigned int start, unsigned int len) { return spi_write_chunked(flash, buf, start, len, spi_write_256_chunksize); } he name of the author not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The author disclaims all warranties with regard to this software, including all implied warranties of merchantability and fitness. In no event shall the author be liable for any special, indirect or consequential damages or any damages whatsoever resulting from loss of use, data or profits, whether in an action of contract, negligence or other tortious action, arising out of or in connection with the use or performance of this software. */ /** \file * * XPROG Protocol handler, to process V2 Protocol wrapped XPROG commands used in Atmel programmer devices. */ #define INCLUDE_FROM_XPROGPROTOCOL_C #include "XPROGProtocol.h" #if defined(ENABLE_XPROG_PROTOCOL) || defined(__DOXYGEN__) /** Base absolute address for the target's NVM controller for PDI programming */ uint32_t XPROG_Param_NVMBase = 0x010001C0; /** Size in bytes of the target's EEPROM page */ uint16_t XPROG_Param_EEPageSize = 32; /** Address of the TPI device's NVMCMD register for TPI programming */ uint8_t XPROG_Param_NVMCMDRegAddr = 0x33; /** Address of the TPI device's NVMCSR register for TPI programming */ uint8_t XPROG_Param_NVMCSRRegAddr = 0x32; /** Currently selected XPROG programming protocol */ uint8_t XPROG_SelectedProtocol = XPROG_PROTOCOL_PDI; /** Handler for the CMD_XPROG_SETMODE command, which sets the programmer-to-target protocol used for PDI/TPI * programming. */ void XPROGProtocol_SetMode(void) { struct { uint8_t Protocol; } SetMode_XPROG_Params; Endpoint_Read_Stream_LE(&SetMode_XPROG_Params, sizeof(SetMode_XPROG_Params), NULL); Endpoint_ClearOUT(); Endpoint_SelectEndpoint(AVRISP_DATA_IN_EPADDR); Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN); XPROG_SelectedProtocol = SetMode_XPROG_Params.Protocol; Endpoint_Write_8(CMD_XPROG_SETMODE); Endpoint_Write_8((SetMode_XPROG_Params.Protocol != XPROG_PROTOCOL_JTAG) ? STATUS_CMD_OK : STATUS_CMD_FAILED); Endpoint_ClearIN(); } /** Handler for the CMD_XPROG command, which wraps up XPROG commands in a V2 wrapper which need to be * removed and processed so that the underlying XPROG command can be handled. */ void XPROGProtocol_Command(void) { uint8_t XPROGCommand = Endpoint_Read_8(); switch (XPROGCommand) { case XPROG_CMD_ENTER_PROGMODE: XPROGProtocol_EnterXPROGMode(); break; case XPROG_CMD_LEAVE_PROGMODE: XPROGProtocol_LeaveXPROGMode(); break; case XPROG_CMD_ERASE: XPROGProtocol_Erase(); break; case XPROG_CMD_WRITE_MEM: XPROGProtocol_WriteMemory(); break; case XPROG_CMD_READ_MEM: XPROGProtocol_ReadMemory(); break; case XPROG_CMD_CRC: XPROGProtocol_ReadCRC(); break; case XPROG_CMD_SET_PARAM: XPROGProtocol_SetParam(); break; } } /** Handler for the XPROG ENTER_PROGMODE command to establish a connection with the attached device. */ static void XPROGProtocol_EnterXPROGMode(void) { Endpoint_ClearOUT(); Endpoint_SelectEndpoint(AVRISP_DATA_IN_EPADDR); Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN); bool NVMBusEnabled = false; if (XPROG_SelectedProtocol == XPROG_PROTOCOL_PDI) NVMBusEnabled = XMEGANVM_EnablePDI(); else if (XPROG_SelectedProtocol == XPROG_PROTOCOL_TPI) NVMBusEnabled = TINYNVM_EnableTPI(); Endpoint_Write_8(CMD_XPROG); Endpoint_Write_8(XPROG_CMD_ENTER_PROGMODE); Endpoint_Write_8(NVMBusEnabled ? XPROG_ERR_OK : XPROG_ERR_FAILED); Endpoint_ClearIN(); } /** Handler for the XPROG LEAVE_PROGMODE command to terminate the PDI programming connection with * the attached device. */ static void XPROGProtocol_LeaveXPROGMode(void) { Endpoint_ClearOUT(); Endpoint_SelectEndpoint(AVRISP_DATA_IN_EPADDR); Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN); if (XPROG_SelectedProtocol == XPROG_PROTOCOL_PDI) XMEGANVM_DisablePDI(); else TINYNVM_DisableTPI(); #if defined(XCK_RESCUE_CLOCK_ENABLE) && defined(ENABLE_ISP_PROTOCOL) /* If the XCK rescue clock option is enabled, we need to restart it once the * XPROG mode has been exited, since the XPROG protocol stops it after use. */ ISPTarget_ConfigureRescueClock(); #endif Endpoint_Write_8(CMD_XPROG); Endpoint_Write_8(XPROG_CMD_LEAVE_PROGMODE); Endpoint_Write_8(XPROG_ERR_OK); Endpoint_ClearIN(); } /** Handler for the XPRG ERASE command to erase a specific memory address space in the attached device. */ static void XPROGProtocol_Erase(void) { uint8_t ReturnStatus = XPROG_ERR_OK; struct { uint8_t MemoryType; uint32_t Address; } Erase_XPROG_Params; Endpoint_Read_Stream_LE(&Erase_XPROG_Params, sizeof(Erase_XPROG_Params), NULL); Erase_XPROG_Params.Address = SwapEndian_32(Erase_XPROG_Params.Address); Endpoint_ClearOUT(); Endpoint_SelectEndpoint(AVRISP_DATA_IN_EPADDR); Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN); uint8_t EraseCommand; if (XPROG_SelectedProtocol == XPROG_PROTOCOL_PDI) { /* Determine which NVM command to send to the device depending on the memory to erase */ switch (Erase_XPROG_Params.MemoryType) { case XPROG_ERASE_CHIP: EraseCommand = XMEGA_NVM_CMD_CHIPERASE; break; case XPROG_ERASE_APP: EraseCommand = XMEGA_NVM_CMD_ERASEAPPSEC; break; case XPROG_ERASE_BOOT: EraseCommand = XMEGA_NVM_CMD_ERASEBOOTSEC; break; case XPROG_ERASE_EEPROM: EraseCommand = XMEGA_NVM_CMD_ERASEEEPROM; break; case XPROG_ERASE_APP_PAGE: EraseCommand = XMEGA_NVM_CMD_ERASEAPPSECPAGE; break; case XPROG_ERASE_BOOT_PAGE: EraseCommand = XMEGA_NVM_CMD_ERASEBOOTSECPAGE; break; case XPROG_ERASE_EEPROM_PAGE: EraseCommand = XMEGA_NVM_CMD_ERASEEEPROMPAGE; break; case XPROG_ERASE_USERSIG: EraseCommand = XMEGA_NVM_CMD_ERASEUSERSIG; break; default: EraseCommand = XMEGA_NVM_CMD_NOOP; break; } /* Erase the target memory, indicate timeout if occurred */ if (!(XMEGANVM_EraseMemory(EraseCommand, Erase_XPROG_Params.Address))) ReturnStatus = XPROG_ERR_TIMEOUT; } else { if (Erase_XPROG_Params.MemoryType == XPROG_ERASE_CHIP) EraseCommand = TINY_NVM_CMD_CHIPERASE; else EraseCommand = TINY_NVM_CMD_SECTIONERASE; /* Erase the target memory, indicate timeout if occurred */ if (!(TINYNVM_EraseMemory(EraseCommand, Erase_XPROG_Params.Address))) ReturnStatus = XPROG_ERR_TIMEOUT; } Endpoint_Write_8(CMD_XPROG); Endpoint_Write_8(XPROG_CMD_ERASE); Endpoint_Write_8(ReturnStatus); Endpoint_ClearIN(); } /** Handler for the XPROG WRITE_MEMORY command to write to a specific memory space within the attached device. */ static void XPROGProtocol_WriteMemory(void) { uint8_t ReturnStatus = XPROG_ERR_OK; struct { uint8_t MemoryType; uint8_t PageMode; uint32_t Address; uint16_t Length; uint8_t ProgData[256]; } WriteMemory_XPROG_Params; Endpoint_Read_Stream_LE(&WriteMemory_XPROG_Params, (sizeof(WriteMemory_XPROG_Params) - sizeof(WriteMemory_XPROG_Params).ProgData), NULL); WriteMemory_XPROG_Params.Address = SwapEndian_32(WriteMemory_XPROG_Params.Address); WriteMemory_XPROG_Params.Length = SwapEndian_16(WriteMemory_XPROG_Params.Length); Endpoint_Read_Stream_LE(&WriteMemory_XPROG_Params.ProgData, WriteMemory_XPROG_Params.Length, NULL); // The driver will terminate transfers that are a round multiple of the endpoint bank in size with a ZLP, need // to catch this and discard it before continuing on with packet processing to prevent communication issues if (((sizeof(uint8_t) + sizeof(WriteMemory_XPROG_Params) - sizeof(WriteMemory_XPROG_Params.ProgData)) + WriteMemory_XPROG_Params.Length) % AVRISP_DATA_EPSIZE == 0) { Endpoint_ClearOUT(); Endpoint_WaitUntilReady(); } Endpoint_ClearOUT(); Endpoint_SelectEndpoint(AVRISP_DATA_IN_EPADDR); Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN); if (XPROG_SelectedProtocol == XPROG_PROTOCOL_PDI) { /* Assume FLASH page programming by default, as it is the common case */ uint8_t WriteCommand = XMEGA_NVM_CMD_WRITEFLASHPAGE; uint8_t WriteBuffCommand = XMEGA_NVM_CMD_LOADFLASHPAGEBUFF; uint8_t EraseBuffCommand = XMEGA_NVM_CMD_ERASEFLASHPAGEBUFF; bool PagedMemory = true; switch (WriteMemory_XPROG_Params.MemoryType) { case XPROG_MEM_TYPE_APPL: WriteCommand = XMEGA_NVM_CMD_WRITEAPPSECPAGE; break; case XPROG_MEM_TYPE_BOOT: WriteCommand = XMEGA_NVM_CMD_WRITEBOOTSECPAGE; break; case XPROG_MEM_TYPE_EEPROM: WriteCommand = XMEGA_NVM_CMD_ERASEWRITEEEPROMPAGE; WriteBuffCommand = XMEGA_NVM_CMD_LOADEEPROMPAGEBUFF; EraseBuffCommand = XMEGA_NVM_CMD_ERASEEEPROMPAGEBUFF; break; case XPROG_MEM_TYPE_USERSIG: WriteCommand = XMEGA_NVM_CMD_WRITEUSERSIG; break; case XPROG_MEM_TYPE_FUSE: WriteCommand = XMEGA_NVM_CMD_WRITEFUSE; PagedMemory = false; break; case XPROG_MEM_TYPE_LOCKBITS: WriteCommand = XMEGA_NVM_CMD_WRITELOCK; PagedMemory = false; break; } /* Send the appropriate memory write commands to the device, indicate timeout if occurred */ if ((PagedMemory && !(XMEGANVM_WritePageMemory(WriteBuffCommand, EraseBuffCommand, WriteCommand, WriteMemory_XPROG_Params.PageMode, WriteMemory_XPROG_Params.Address, WriteMemory_XPROG_Params.ProgData, WriteMemory_XPROG_Params.Length))) || (!PagedMemory && !(XMEGANVM_WriteByteMemory(WriteCommand, WriteMemory_XPROG_Params.Address, WriteMemory_XPROG_Params.ProgData[0])))) { ReturnStatus = XPROG_ERR_TIMEOUT; } } else { /* Send write command to the TPI device, indicate timeout if occurred */ if (!(TINYNVM_WriteMemory(WriteMemory_XPROG_Params.Address, WriteMemory_XPROG_Params.ProgData, WriteMemory_XPROG_Params.Length))) { ReturnStatus = XPROG_ERR_TIMEOUT; } } Endpoint_Write_8(CMD_XPROG); Endpoint_Write_8(XPROG_CMD_WRITE_MEM); Endpoint_Write_8(ReturnStatus); Endpoint_ClearIN(); } /** Handler for the XPROG READ_MEMORY command to read data from a specific address space within the * attached device. */ static void XPROGProtocol_ReadMemory(void) { uint8_t ReturnStatus = XPROG_ERR_OK; struct { uint8_t MemoryType; uint32_t Address; uint16_t Length; } ReadMemory_XPROG_Params; Endpoint_Read_Stream_LE(&ReadMemory_XPROG_Params, sizeof(ReadMemory_XPROG_Params), NULL); ReadMemory_XPROG_Params.Address = SwapEndian_32(ReadMemory_XPROG_Params.Address); ReadMemory_XPROG_Params.Length = SwapEndian_16(ReadMemory_XPROG_Params.Length); Endpoint_ClearOUT(); Endpoint_SelectEndpoint(AVRISP_DATA_IN_EPADDR); Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN); uint8_t ReadBuffer[256]; if (XPROG_SelectedProtocol == XPROG_PROTOCOL_PDI) { /* Read the PDI target's memory, indicate timeout if occurred */ if (!(XMEGANVM_ReadMemory(ReadMemory_XPROG_Params.Address, ReadBuffer, ReadMemory_XPROG_Params.Length))) ReturnStatus = XPROG_ERR_TIMEOUT; } else { /* Read the TPI target's memory, indicate timeout if occurred */ if (!(TINYNVM_ReadMemory(ReadMemory_XPROG_Params.Address, ReadBuffer, ReadMemory_XPROG_Params.Length))) ReturnStatus = XPROG_ERR_TIMEOUT; } Endpoint_Write_8(CMD_XPROG); Endpoint_Write_8(XPROG_CMD_READ_MEM); Endpoint_Write_8(ReturnStatus); if (ReturnStatus == XPROG_ERR_OK) Endpoint_Write_Stream_LE(ReadBuffer, ReadMemory_XPROG_Params.Length, NULL); Endpoint_ClearIN(); } /** Handler for the XPROG CRC command to read a specific memory space's CRC value for comparison between the * attached device's memory and a data set on the host. */ static void XPROGProtocol_ReadCRC(void) { uint8_t ReturnStatus = XPROG_ERR_OK; struct { uint8_t CRCType; } ReadCRC_XPROG_Params; Endpoint_Read_Stream_LE(&ReadCRC_XPROG_Params, sizeof(ReadCRC_XPROG_Params), NULL); Endpoint_ClearOUT(); Endpoint_SelectEndpoint(AVRISP_DATA_IN_EPADDR); Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN); uint32_t MemoryCRC; if (XPROG_SelectedProtocol == XPROG_PROTOCOL_PDI) { uint8_t CRCCommand; /* Determine which NVM command to send to the device depending on the memory to CRC */ switch (ReadCRC_XPROG_Params.CRCType) { case XPROG_CRC_APP: CRCCommand = XMEGA_NVM_CMD_APPCRC; break; case XPROG_CRC_BOOT: CRCCommand = XMEGA_NVM_CMD_BOOTCRC; break; default: CRCCommand = XMEGA_NVM_CMD_FLASHCRC; break; } /* Perform and retrieve the memory CRC, indicate timeout if occurred */ if (!(XMEGANVM_GetMemoryCRC(CRCCommand, &MemoryCRC))) ReturnStatus = XPROG_ERR_TIMEOUT; } else { /* TPI does not support memory CRC */ ReturnStatus = XPROG_ERR_FAILED; } Endpoint_Write_8(CMD_XPROG); Endpoint_Write_8(XPROG_CMD_CRC); Endpoint_Write_8(ReturnStatus); if (ReturnStatus == XPROG_ERR_OK) { Endpoint_Write_8(MemoryCRC >> 16); Endpoint_Write_16_LE(MemoryCRC & 0xFFFF); } Endpoint_ClearIN(); } /** Handler for the XPROG SET_PARAM command to set a XPROG parameter for use when communicating with the * attached device. */ static void XPROGProtocol_SetParam(void) { uint8_t ReturnStatus = XPROG_ERR_OK; uint8_t XPROGParam = Endpoint_Read_8(); /* Determine which parameter is being set, store the new parameter value */ switch (XPROGParam) { case XPROG_PARAM_NVMBASE: XPROG_Param_NVMBase = Endpoint_Read_32_BE(); break; case XPROG_PARAM_EEPPAGESIZE: XPROG_Param_EEPageSize = Endpoint_Read_16_BE(); break; case XPROG_PARAM_NVMCMD_REG: XPROG_Param_NVMCMDRegAddr = Endpoint_Read_8(); break; case XPROG_PARAM_NVMCSR_REG: XPROG_Param_NVMCSRRegAddr = Endpoint_Read_8(); break; case XPROG_PARAM_UNKNOWN_1: /* TODO: Undocumented parameter added in AVRStudio 5.1, purpose unknown. Must ACK and discard or the communication with AVRStudio 5.1 will fail. */ Endpoint_Discard_16(); break; default: ReturnStatus = XPROG_ERR_FAILED; break; } Endpoint_ClearOUT(); Endpoint_SelectEndpoint(AVRISP_DATA_IN_EPADDR); Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN); Endpoint_Write_8(CMD_XPROG); Endpoint_Write_8(XPROG_CMD_SET_PARAM); Endpoint_Write_8(ReturnStatus); Endpoint_ClearIN(); } #endif