diff --git a/CHANGELOG.md b/CHANGELOG.md index c950f9f..cdf6881 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ The version lives in `_VER` in `src/main.c`. Binaries are published only as the [`latest`](https://github.com/OpenIPC/microsnander/releases/latest) release, rebuilt from every push to `main`; this file is the change history. +## 1.21.1 — 2026-11-04 + +- SPI NOR chips of Macronix with the ID `c2 20 xx` (MX25L4005A up to MX25L51245G) were taken + for the SPI NAND MX35LF2G14AC since 1.10.0: that entry compares only `c2 20`, and SPI NAND + is tried first. Erases and writes would have gone out as SPI NAND commands. An ID of the + NOR table now wins over a 2-byte SPI NAND entry, and a chip whose feature registers all + read 0xff is not a SPI NAND, whatever its ID. Both come before any write to the chip. +- Macronix SPI NOR: `-u`, erases and writes failed with `status register 3 stays 0x07`. On + these chips the reads of status registers 2 and 3 return something else, and they are no + longer taken for CMP, SRP1 and WPS. BP3 (bit 5) is cleared with the other BP bits. + ## 1.21.0 — 2026-11-04 - SPI NOR: `-uu` clears the protection as `-u` does, and then QE (quad enable), which `-u` diff --git a/src/main.c b/src/main.c index ff599f2..955d663 100644 --- a/src/main.c +++ b/src/main.c @@ -36,7 +36,7 @@ struct flash_cmd prog; extern unsigned int bsize; -#define _VER "1.21.0" +#define _VER "1.21.1" const char *ms_version = _VER; diff --git a/src/spi_nand_flash.c b/src/spi_nand_flash.c index ccbf654..8ded7b0 100644 --- a/src/spi_nand_flash.c +++ b/src/spi_nand_flash.c @@ -36,6 +36,7 @@ #include #include "types.h" #include "spi_nand_flash.h" +#include "snorcmd_api.h" #include "spi_controller.h" #include "nandcmd_api.h" #include "timer.h" @@ -5141,6 +5142,44 @@ static SPI_NAND_FLASH_RTN_T spi_nand_compare( const struct SPI_NAND_FLASH_INFO_T * *------------------------------------------------------------------------------------ */ +/* A table entry with a 2-byte ID also matches a SPI NOR chip that starts with those + bytes (MX35LF2G14AC, c2 20; the MX25L chips, c2 20 xx). The third byte tells them + apart when the whole ID is in the NOR table. Any other chip: a SPI NAND has reserved + bits that read 0, a SPI NOR does not answer the feature read and all of them read + 0xff. Nothing is written to the chip here. */ +static int spi_nand_is_nor( const struct SPI_NAND_FLASH_INFO_T *ptr_id ) +{ + const unsigned char nor_id[3] = { ptr_id->mfr_id, ptr_id->dev_id, ptr_id->dev_id_2 }; + static const u8 addrs[] = { _SPI_NAND_ADDR_PROTECTION, _SPI_NAND_ADDR_FEATURE, _SPI_NAND_ADDR_STATUS }; + unsigned long size, sector; + int addr4b, all_ff = 1; + u32 i; + + for ( i = 0; i < (sizeof(spi_nand_flash_tables)/sizeof(struct SPI_NAND_FLASH_INFO_T)); i++) + { + if ( spi_nand_compare( ptr_id, &spi_nand_flash_tables[i] ) != SPI_NAND_FLASH_RTN_NO_ERROR ) + continue; + if ( spi_nand_flash_tables[i].dev_id_2 == 0 && snor_lookup(nor_id, &size, §or, &addr4b) ) + { + _SPI_NAND_PRINTF("ID %02x %02x %02x is a SPI NOR chip, not %s.\n", nor_id[0], nor_id[1], nor_id[2], + spi_nand_flash_tables[i].ptr_name); + return 1; + } + break; + } + + for ( i = 0; i < sizeof(addrs); i++) + { + u8 feature = 0; + + if ( spi_nand_protocol_get_feature(addrs[i], &feature) != SPI_NAND_FLASH_RTN_NO_ERROR || feature != 0xff ) + all_ff = 0; + } + if ( all_ff ) + _SPI_NAND_PRINTF("The feature registers read 0xff: this is not a SPI NAND chip.\n"); + return all_ff; +} + static SPI_NAND_FLASH_RTN_T spi_nand_probe( struct SPI_NAND_FLASH_INFO_T *ptr_rtn_device_t ) { u32 i = 0; @@ -5248,6 +5287,10 @@ static SPI_NAND_FLASH_RTN_T spi_nand_probe( struct SPI_NAND_FLASH_INFO_T *ptr_rt ptr_rtn_device_t->mfr_id, ptr_rtn_device_t->dev_id, ptr_rtn_device_t->dev_id_2); } + /* before anything is written to the chip */ + if( rtn_status == SPI_NAND_FLASH_RTN_NO_ERROR && spi_nand_is_nor(ptr_rtn_device_t) ) + return SPI_NAND_FLASH_RTN_PROBE_ERROR; + if(rtn_status == SPI_NAND_FLASH_RTN_NO_ERROR) { unsigned char feature = 0; diff --git a/src/spi_nor_flash.c b/src/spi_nor_flash.c index 537989c..21803cf 100644 --- a/src/spi_nor_flash.c +++ b/src/spi_nor_flash.c @@ -141,6 +141,11 @@ static int snor_is_sst26(void) int snor_block_locks(void) { return snor_is_sst26(); } /* see snorcmd_api.h */ +static int snor_is_macronix(void) +{ + return spi_chip_info->id == 0xc2; +} + /* * Set write enable latch with Write Enable command. * Returns negative if error occurred. @@ -187,7 +192,8 @@ char snor_unprotect_why[200]; static int unprotect(int all) { u8 sr = 0; - const u8 sr1_mask = all ? SR1_PROTECT : (SR_BP0 | SR_BP1 | SR_BP2); + /* Macronix: BP3 in bit 5 */ + const u8 sr1_mask = (all ? SR1_PROTECT : (SR_BP0 | SR_BP1 | SR_BP2)) | (snor_is_macronix() ? 0x20 : 0); const u8 sr2_mask = all ? (SR2_SRP1 | SR2_CMP) : SR2_CMP; snor_unprotect_why[0] = 0; @@ -232,8 +238,10 @@ static int unprotect(int all) } } - /* SST25-type chips have one status register (BP3 and BPL in it) */ - if (snor_is_sst25_aai()) + /* SST25-type chips have one status register (BP3 and BPL in it). On Macronix + chips 0x35 and 0x15 read something else, or nothing: bits there are not CMP, + SRP1 or WPS (MX25L12805D reads ff and 07), and they take no such write. */ + if (snor_is_sst25_aai() || snor_is_macronix()) return 0; /* Status registers 2 and 3: a chip without them leaves MISO high and @@ -322,7 +330,7 @@ static int snor_clear_qe_bit(u8 wr, u8 rd, u8 *sr, u8 qe) */ int snor_clear_qe(void) { - const int macronix = spi_chip_info->id == 0xc2; + const int macronix = snor_is_macronix(); const u8 rd = macronix ? OPCODE_RDSR : OPCODE_RDSR2; const u8 wr = macronix ? OPCODE_WRSR : OPCODE_WRSR2; const u8 qe = macronix ? 0x40 : SR2_QE; diff --git a/tests/nandsim.c b/tests/nandsim.c index 408565b..8690ebe 100644 --- a/tests/nandsim.c +++ b/tests/nandsim.c @@ -15,6 +15,13 @@ #include "../src/voltage.c" const char *flash_chip_name; +/* the part of the NOR table that shares its first bytes with a SPI NAND entry */ +const char *snor_lookup(const unsigned char i[3], unsigned long *size, unsigned long *sector, int *addr4b) +{ + (void)size; (void)sector; (void)addr4b; + return i[0] == 0xc2 && i[1] == 0x20 && i[2] >= 0x13 && i[2] <= 0x1a ? "MX25L" : NULL; +} + int snand_read(unsigned char *buf, unsigned long from, unsigned long len); int snand_write(unsigned char *buf, unsigned long to, unsigned long len); long snand_init(void); @@ -42,6 +49,8 @@ static int fail_feature = -1; /* the USB transfer of a read of this feature reg static int fail_after_program; /* status reads fail from the next program execute on */ static int fail_set_feature; /* CS high fails after a write of a feature register */ static int fail_load; /* the USB transfer of the page data of a program load fails */ +static int nor_chip; /* a SPI NOR: no answer to the feature read, 0xff */ +static unsigned int nset; /* writes of a feature register (0x1f) */ static int in(const long *set, long v) { for (int i = 0; i < MAXBAD; i++) if (set[i] == v) return 1; return 0; } static unsigned long row(void) { return ((cmd[1] << 16) | (cmd[2] << 8) | cmd[3]) % PAGES; } @@ -87,7 +96,7 @@ SPI_CONTROLLER_RTN_T SPI_CONTROLLER_Read_NByte(u8 *d, u32 len, SPI_CONTROLLER_SP d[i] = (ncmd == 1 && nread == 0) || k >= 3 ? 0 : id[k]; break; } - case 0x0f: d[i] = feat[cmd[1]] | (stuck_busy && cmd[1] == 0xc0 ? 0x01 : 0); break; + case 0x0f: d[i] = nor_chip ? 0xff : feat[cmd[1]] | (stuck_busy && cmd[1] == 0xc0 ? 0x01 : 0); break; case 0x03: case 0x0b: case 0x3b: case 0x6b: { unsigned int c = col() + nread; d[i] = c < sizeof(cache) ? cache[c] : 0xff; @@ -109,7 +118,7 @@ SPI_CONTROLLER_RTN_T SPI_CONTROLLER_Chip_Select_High(void) switch (cmd[0]) { case 0x03: case 0x0b: case 0x3b: case 0x6b: col_hi_read = cmd[1]; break; case 0x02: case 0x32: case 0x84: case 0x34: col_hi_load = cmd[1]; break; - case 0x1f: feat[cmd[1]] = cmd[2]; break; + case 0x1f: feat[cmd[1]] = cmd[2]; nset++; break; case 0x06: feat[0xc0] |= 0x02; break; case 0x04: feat[0xc0] &= ~0x02; break; case 0x13: /* page read: ECC result in status bits 5:4 */ @@ -485,6 +494,27 @@ int main(void) memset(page(663), 0x5a, PAGE + OOB); check(snand_read(got, 663L * PAGE, PAGE) == PAGE && got[0] == 0x5a, " the next read brings the chip's data"); + /* a SPI NOR that shares the first ID bytes with a SPI NAND entry is left alone */ + for (int third = 0x13; third <= 0x1a; third++) { + chip(0xc2, 0x20, third); + nor_chip = 1; + nset = 0; + if (snand_init() > 0 || nset) { check(0, "MX25L c2 20 xx is not a SPI NAND"); break; } + if (third == 0x1a) check(1, "MX25L c2 20 13..1a: not a SPI NAND, no feature written"); + } + chip(0xc2, 0x20, 0x18); + nor_chip = 0; + check(snand_init() <= 0, " by the NOR table alone, the registers answering"); + chip(0xc2, 0x20, 0x00); + check(snand_init() == 256L << 20, "MX35LF2G14AC with its own third byte is still found"); + chip(0xef, 0xaa, 0x21); + nor_chip = 1; + nset = 0; + check(snand_init() <= 0 && !nset, "feature registers all 0xff: not a SPI NAND, whatever the ID"); + nor_chip = 0; + feat[0xa0] = 0xff; feat[0xb0] = 0xff; feat[0xc0] = 0x00; + check(snand_init() > 0, " one register that is not 0xff is enough"); + /* the ECC setup of the detection: a chip it could not set is not reported as ready */ fail_feature = 0xb0; check(snand_init() <= 0, "detection fails when the USB transfer of the ECC setup fails"); diff --git a/tests/norsim.c b/tests/norsim.c index 110ef97..2aeea5a 100644 --- a/tests/norsim.c +++ b/tests/norsim.c @@ -76,8 +76,8 @@ SPI_CONTROLLER_RTN_T SPI_CONTROLLER_Read_NByte(u8 *d, u32 len, SPI_CONTROLLER_SP switch (cmd[0]) { case OPCODE_RDID: d[i] = nread < 5 ? id[nread] : 0; break; case OPCODE_RDSR: d[i] = sr | (wel ? SR_WEL : 0) | (aai ? 0x40 : 0) | (busy ? SR_WIP : 0); if (busy) busy--; break; - case OPCODE_RDSR2: d[i] = kind == NO_SR23 ? 0xff : sr2; sr2_reads++; break; - case OPCODE_RDSR3: d[i] = kind == NO_SR23 ? 0xff : 0; break; + case OPCODE_RDSR2: d[i] = kind == NO_SR23 || kind == QE_SR1 ? 0xff : sr2; sr2_reads++; break; + case OPCODE_RDSR3: d[i] = kind == NO_SR23 ? 0xff : kind == QE_SR1 ? 0x07 : 0; break; /* another register on Macronix */ case OPCODE_READ: d[i] = mem[(addr() + nread) % sizeof(mem)]; break; default: d[i] = 0; } @@ -209,7 +209,7 @@ static void write_read_erase(const char *name, int k) if (k != SST25 && k != SST26) { snprintf(what, sizeof(what), "%s: the write of 17 pages clears the protection once", name); - check(unprotects == 1, what); + if (kind != QE_SR1) check(unprotects == 1, what); /* counted by the reads of register 2, Macronix has none */ } snprintf(what, sizeof(what), "%s: nothing is sent while the chip is busy, the latch is clear at the end", name); check(!busy_cmds && !wel, what); @@ -313,6 +313,11 @@ int main(void) snor_init(); sr = 0x40; sr2 = 0x02; check(snor_clear_qe() == 0 && sr == 0 && sr2 == 0x02, "MX25L3205D: QE is in status register 1, register 2 is not touched"); + sr = 0x20; + check(snor_unprotect() == 0 && sr == 0, " BP3 in bit 5 is cleared"); + sr = 0x40; + check(snor_unprotect() == 0 && sr == 0x40, " -u keeps QE, and 0x07 from the 0x15 read is not WPS"); + sr = 0; chip(find("W25Q32BV"), ORDINARY); snor_init(); sr = 0x80; sr2 = 0x02; wp_low = 1;