Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
2 changes: 1 addition & 1 deletion src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
43 changes: 43 additions & 0 deletions src/spi_nand_flash.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include <time.h>
#include "types.h"
#include "spi_nand_flash.h"
#include "snorcmd_api.h"
#include "spi_controller.h"
#include "nandcmd_api.h"
#include "timer.h"
Expand Down Expand Up @@ -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, &sector, &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;
Expand Down Expand Up @@ -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;
Expand Down
16 changes: 12 additions & 4 deletions src/spi_nor_flash.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
34 changes: 32 additions & 2 deletions tests/nandsim.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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; }
Expand Down Expand Up @@ -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;
Expand All @@ -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 */
Expand Down Expand Up @@ -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");
Expand Down
11 changes: 8 additions & 3 deletions tests/norsim.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
Loading