From 825f88e826bde4c144fcc02bed14137ab7e3e8d2 Mon Sep 17 00:00:00 2001 From: Compot Developer Date: Tue, 3 Nov 2026 19:37:50 +0000 Subject: [PATCH 1/4] SigmaStar ISP: the flash of a board through the I2C of its SoC -m sigmastar sends the SPI commands of the flash drivers through the ISP bridge of a SigmaStar SoC (I2C 0x49: 0x10 + bytes out, 0x11 then a read in, 0x12 the end of the command), after the debug port 0x59 lets the UART pins be the bridge and MSTAR starts it. The I2C master is the programmer's: the CH341A and the CH347 get I2C transactions (stream commands 0xAA; the CH347F switches its pins first). -S is the I2C clock in this mode, -q lists the addresses that answer. NOR, NAND and every operation work as with a chip in the socket; not in the web mode or the app. Tests: ispsim (the bridge with a SPI NOR behind it: READ ID, a read and a page program in I2C parts, the debug port unlock), and the options in the stub tests. Not checked on hardware yet. --- README.md | 28 ++++++++ README.ru.md | 28 ++++++++ README.zh-CN.md | 24 +++++++ src/Makefile | 2 +- src/ch341a_spi.c | 54 +++++++++++++++ src/ch341a_spi.h | 7 ++ src/ch347_spi.c | 83 ++++++++++++++++++++++++ src/ch347_spi.h | 7 ++ src/main.c | 51 ++++++++++++--- src/programmer.c | 6 +- src/programmer.h | 9 +++ src/sigmastar.c | 166 +++++++++++++++++++++++++++++++++++++++++++++++ src/sigmastar.h | 28 ++++++++ tests/ispsim.c | 107 ++++++++++++++++++++++++++++++ tests/run.sh | 17 ++++- tests/stub.c | 9 ++- 16 files changed, 612 insertions(+), 14 deletions(-) create mode 100644 src/sigmastar.c create mode 100644 src/sigmastar.h create mode 100644 tests/ispsim.c diff --git a/README.md b/README.md index 07da7d5..0027f84 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ sudo ./microsnander -w firmware.bin -v - [Examples](#examples) - [Web mode](#web-mode) - [Android app](#android-app) +- [SigmaStar ISP](#sigmastar-isp) - [Supported chips](#supported-chips) - [Chip voltage](#chip-voltage) - [Chips with uncertain data](#chips-with-uncertain-data) @@ -331,6 +332,33 @@ with the remote). Build: `cd android && gradle assembleDebug` with the Android SDK and NDK r27c. +## SigmaStar ISP + +`-m sigmastar` reaches the SPI flash of a SigmaStar (MStar) board on its SoC, without +desoldering: through the ISP bridge of the SoC, over I2C. It brings back a board with a broken +bootloader ([OpenIPC wiki: SigmaStar unbrick](https://github.com/OpenIPC/wiki/blob/master/en/sigmastar-unbrick.md)). + +- Wires: the UART of the SoC to the I2C of the programmer, TX to SDA, RX to SCL, and GND. The + board is powered on its own; when the bridge does not answer, a power cycle of the board helps. +- The I2C master is that of the CH341A or the CH347 (CH347T in mode 1, CH347F), chosen as always + with `-P` or by itself; the EZP programmers have no I2C. +- `-S` is the I2C clock in this mode: `20K`, `100K` (default), `400K`, `750K`. +- `-q` lists the I2C addresses that answer, to check the wiring (the bridge is at `0x49`, the + debug port of the SoC at `0x59`). +- Everything else works as with a chip in the socket: `-i`, `-r`, `-w`, `-e`, `-v`, `-a`, `-l`, + SPI NOR and SPI NAND. Not in the web mode or the Android app. + +```sh +microsnander -q # 0x49 and 0x59 must answer +microsnander -m sigmastar -i # the flash of the board +microsnander -m sigmastar -r backup.bin # back it up first +microsnander -m sigmastar -a 0 -l 0x40000 -e # the loader part, then +microsnander -m sigmastar -w u-boot.bin -v # a working loader +``` + +It is slow: the data goes over I2C, about 20-25 bytes a transaction. A loader of a few +hundred KB is minutes; a whole 16 MB chip is much longer. + ## Supported chips `microsnander -L` prints the list: 291 SPI NOR chips (Winbond, Eon, Fudan diff --git a/README.ru.md b/README.ru.md index 337b0a1..1dc7970 100644 --- a/README.ru.md +++ b/README.ru.md @@ -48,6 +48,7 @@ sudo ./microsnander -w firmware.bin -v - [Примеры](#примеры) - [Веб-режим](#веб-режим) - [Приложение для Android](#приложение-для-android) +- [SigmaStar ISP](#sigmastar-isp) - [Поддерживаемые микросхемы](#поддерживаемые-микросхемы) - [Напряжение микросхем](#напряжение-микросхем) - [Микросхемы с неточными данными](#микросхемы-с-неточными-данными) @@ -335,6 +336,33 @@ Android TV (Android 5.0 и новее, без root; на приставке он Сборка: `cd android && gradle assembleDebug` с Android SDK и NDK r27c. +## SigmaStar ISP + +`-m sigmastar` работает с флешкой платы SigmaStar (MStar) прямо на плате, без выпаивания: через +ISP-мост SoC по I2C. Так восстанавливают плату с испорченным загрузчиком +([OpenIPC wiki: SigmaStar unbrick](https://github.com/OpenIPC/wiki/blob/master/en/sigmastar-unbrick.md)). + +- Подключение: UART SoC к I2C программатора, TX к SDA, RX к SCL, и GND. Плата питается сама; + если мост не отвечает, помогает перезапуск питания платы. +- I2C — от CH341A или CH347 (CH347T в режиме 1, CH347F), программатор выбирается как обычно: + `-P` или сам; у программаторов EZP I2C нет. +- `-S` в этом режиме — частота I2C: `20K`, `100K` (по умолчанию), `400K`, `750K`. +- `-q` выводит адреса I2C, которые отвечают, — для проверки подключения (мост — `0x49`, порт + отладки SoC — `0x59`). +- Остальное — как с микросхемой в панельке: `-i`, `-r`, `-w`, `-e`, `-v`, `-a`, `-l`, SPI NOR и + SPI NAND. В веб-режиме и в приложении для Android этого режима нет. + +```sh +microsnander -q # должны ответить 0x49 и 0x59 +microsnander -m sigmastar -i # флешка платы +microsnander -m sigmastar -r backup.bin # сначала копия +microsnander -m sigmastar -a 0 -l 0x40000 -e # область загрузчика, затем +microsnander -m sigmastar -w u-boot.bin -v # рабочий загрузчик +``` + +Режим медленный: данные идут по I2C, примерно по 20–25 байт за транзакцию. Загрузчик в несколько +сотен КБ — минуты; вся микросхема на 16 МБ — намного дольше. + ## Поддерживаемые микросхемы `microsnander -L` выводит список: 291 микросхему SPI NOR (Winbond, Eon, Fudan diff --git a/README.zh-CN.md b/README.zh-CN.md index 5dd5a24..72a9141 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -46,6 +46,7 @@ sudo ./microsnander -w firmware.bin -v - [示例](#示例) - [Web 模式](#web-模式) - [Android 应用](#android-应用) +- [SigmaStar ISP](#sigmastar-isp) - [支持的芯片](#支持的芯片) - [芯片电压](#芯片电压) - [数据不确定的芯片](#数据不确定的芯片) @@ -298,6 +299,29 @@ root;在电视盒子上会出现在主屏幕,可用遥控器操作)。 构建:在安装了 Android SDK 和 NDK r27c 的环境中运行 `cd android && gradle assembleDebug`。 +## SigmaStar ISP + +`-m sigmastar` 无需拆焊,直接在板上通过 SoC 的 ISP 桥(I2C)访问 SigmaStar(MStar)板的 SPI 闪存, +用于修复引导程序损坏的板子([OpenIPC wiki:SigmaStar unbrick](https://github.com/OpenIPC/wiki/blob/master/en/sigmastar-unbrick.md))。 + +- 接线:SoC 的 UART 接编程器的 I2C,TX 接 SDA,RX 接 SCL,并连接 GND。板子自行供电;桥无响应时, + 给板子重新上电通常有效。 +- I2C 主机为 CH341A 或 CH347(CH347T 模式 1、CH347F),照常用 `-P` 选择或自动选择;EZP 编程器没有 I2C。 +- 此模式下 `-S` 为 I2C 时钟:`20K`、`100K`(默认)、`400K`、`750K`。 +- `-q` 列出有响应的 I2C 地址,用于检查接线(桥为 `0x49`,SoC 调试端口为 `0x59`)。 +- 其余与插座中的芯片相同:`-i`、`-r`、`-w`、`-e`、`-v`、`-a`、`-l`,SPI NOR 和 SPI NAND。Web 模式和 + Android 应用不支持此模式。 + +```sh +microsnander -q # 0x49 和 0x59 必须有响应 +microsnander -m sigmastar -i # 板上的闪存 +microsnander -m sigmastar -r backup.bin # 先备份 +microsnander -m sigmastar -a 0 -l 0x40000 -e # 引导程序区域,然后 +microsnander -m sigmastar -w u-boot.bin -v # 写入可用的引导程序 +``` + +此模式较慢:数据通过 I2C 传输,每次事务约 20–25 字节。几百 KB 的引导程序需要几分钟;整片 16 MB 芯片要久得多。 + ## 支持的芯片 `microsnander -L` 会列出支持的芯片:291 款 SPI NOR 芯片(Winbond、Eon、Fudan diff --git a/src/Makefile b/src/Makefile index 3d39322..dc0afd5 100644 --- a/src/Makefile +++ b/src/Makefile @@ -26,7 +26,7 @@ CFLAGS ?= -Os ALL_CFLAGS = $(CFLAGS) -std=gnu99 -Wall -D_FILE_OFFSET_BITS=64 $(USB_CFLAGS) LDLIBS = $(LIBUSB_LIBS) -pthread -OBJS = flashcmd_api.o spi_controller.o spi_nand_flash.o spi_nor_flash.o programmer.o ch341a_spi.o ch347_spi.o ezp.o usb.o $(USB_OBJS) voltage.o selftest.o ops.o web.o timer.o main.o +OBJS = flashcmd_api.o spi_controller.o spi_nand_flash.o spi_nor_flash.o programmer.o ch341a_spi.o ch347_spi.o ezp.o usb.o $(USB_OBJS) voltage.o sigmastar.o selftest.o ops.o web.o timer.o main.o all: microsnander diff --git a/src/ch341a_spi.c b/src/ch341a_spi.c index 3fa2656..5cecc0f 100644 --- a/src/ch341a_spi.c +++ b/src/ch341a_spi.c @@ -301,6 +301,60 @@ int config_stream(unsigned int speed) return ret; } +/* ---- I2C, for the SigmaStar ISP ---- + One stream command packet a transaction: START, the address byte, the data + or the reads, STOP. STM_OUT with a length sends bytes without answers; + STM_OUT without one sends one byte and answers its ACK (bit 7 set: NACK); + STM_IN with a length reads bytes with ACK, STM_IN alone one byte with NACK, + the last of a read. */ + +int ch341a_i2c_speed(unsigned int speed) +{ + return config_stream(speed) < 0 ? -1 : 0; /* the SPI does not run in this mode */ +} + +int ch341a_i2c_write(unsigned char addr, const unsigned char *data, unsigned int len) +{ + uint8_t buf[CH341_PACKET_LENGTH]; + unsigned int n = 0; + + if (len > CH341A_I2C_MAX) + return -1; + buf[n++] = CH341A_CMD_I2C_STREAM; + buf[n++] = CH341A_CMD_I2C_STM_START; + buf[n++] = CH341A_CMD_I2C_STM_OUT | (len + 1); + buf[n++] = addr << 1; + memcpy(buf + n, data, len); + n += len; + buf[n++] = CH341A_CMD_I2C_STM_STOP; + buf[n++] = CH341A_CMD_I2C_STM_END; + return usb_transfer(__func__, n, 0, buf, NULL) < 0 ? -1 : 0; +} + +int ch341a_i2c_read(unsigned char addr, unsigned char *data, unsigned int len) +{ + uint8_t buf[CH341_PACKET_LENGTH], in[CH341_PACKET_LENGTH]; + unsigned int n = 0; + + if (len > CH341A_I2C_MAX) + return -1; + buf[n++] = CH341A_CMD_I2C_STREAM; + buf[n++] = CH341A_CMD_I2C_STM_START; + buf[n++] = CH341A_CMD_I2C_STM_OUT; /* the address, with its ACK */ + buf[n++] = addr << 1 | 1; + if (len > 1) + buf[n++] = CH341A_CMD_I2C_STM_IN | (len - 1); + if (len) + buf[n++] = CH341A_CMD_I2C_STM_IN; + buf[n++] = CH341A_CMD_I2C_STM_STOP; + buf[n++] = CH341A_CMD_I2C_STM_END; + if (usb_transfer(__func__, n, len + 1, buf, in) < 0 || (in[0] & 0x80)) + return -1; + if (len) + memcpy(data, in + 1, len); + return 0; +} + /* ch341 requires LSB first, swap the bit order before send and after receive */ static uint8_t swap_byte(uint8_t x) { diff --git a/src/ch341a_spi.h b/src/ch341a_spi.h index 401924f..d5c6601 100644 --- a/src/ch341a_spi.h +++ b/src/ch341a_spi.h @@ -32,5 +32,12 @@ int enable_pins(bool enable); int ch341a_cs(bool low); /* sent with the next stream */ int config_stream(unsigned int speed); +/* the I2C master of the CH341A (SDA, SCL), for the SigmaStar ISP; a USB + packet of 32 bytes holds one transaction of up to CH341A_I2C_MAX bytes */ +#define CH341A_I2C_MAX 24 +int ch341a_i2c_speed(unsigned int speed); +int ch341a_i2c_write(unsigned char addr, const unsigned char *buf, unsigned int len); +int ch341a_i2c_read(unsigned char addr, unsigned char *buf, unsigned int len); + #endif /* __CH341_SPI_H__ */ /* End of [ch341a_spi.h] package */ diff --git a/src/ch347_spi.c b/src/ch347_spi.c index 0f6b9c5..30db3e6 100644 --- a/src/ch347_spi.c +++ b/src/ch347_spi.c @@ -274,3 +274,86 @@ int ch347_spi_send_command(unsigned int writecnt, unsigned int readcnt, const un return -1; return 0; } + +/* ---- I2C, for the SigmaStar ISP ---- + The stream commands of the CH341 (0xAA ...), on the same interface as the + SPI. Every byte sent with STM_OUT answers 0x01 for ACK; a read answers its + address ACK and then the data. The CH347F first switches its pins to I2C. */ + +#define CH347_I2C_STREAM 0xAA +#define CH347_I2C_STM_SET 0x60 +#define CH347_I2C_STM_START 0x74 +#define CH347_I2C_STM_STOP 0x75 +#define CH347_I2C_STM_OUT 0x80 +#define CH347_I2C_STM_IN 0xC0 +#define CH347_I2C_STM_END 0x00 + +int ch347_i2c_speed(unsigned int speed) +{ + unsigned char pkt[16] = { CH347_I2C_STREAM, CH347_I2C_STM_SET | (speed & 3), CH347_I2C_STM_END }; + + if (!ch347) + return -1; + if (ch347_iface == CH347F_IFACE) { + static const unsigned char to_i2c[11] = { 0xe2, 0x08, 0x00, 0x00, 0x00, 0x81, 0x81, 0x00, 0x00, 0x00, 0x00 }; + unsigned char cmd[sizeof(to_i2c)], ans[4]; + int n = 0; + + memcpy(cmd, to_i2c, sizeof(cmd)); + if (bulk(CH347_WRITE_EP, cmd, sizeof(cmd), NULL) || bulk(CH347_READ_EP, ans, sizeof(ans), &n) || n != 4) { + printf("CH347F: the pins do not switch to I2C.\n"); + return -1; + } + } + return bulk(CH347_WRITE_EP, pkt, 3, NULL) ? -1 : 0; +} + +int ch347_i2c_write(unsigned char addr, const unsigned char *data, unsigned int len) +{ + unsigned char pkt[CH347_I2C_MAX + 8], ans[CH347_PACKET_SIZE]; + unsigned int n = 0; + int got = 0; + + if (!ch347 || len > CH347_I2C_MAX) + return -1; + pkt[n++] = CH347_I2C_STREAM; + pkt[n++] = CH347_I2C_STM_START; + pkt[n++] = CH347_I2C_STM_OUT | (len + 1); + pkt[n++] = addr << 1; + memcpy(pkt + n, data, len); + n += len; + pkt[n++] = CH347_I2C_STM_STOP; + pkt[n++] = CH347_I2C_STM_END; + if (bulk(CH347_WRITE_EP, pkt, n, NULL) || bulk(CH347_READ_EP, ans, sizeof(ans), &got) || got != (int)len + 1) + return -1; + for (int i = 0; i < got; i++) + if (ans[i] != 0x01) /* NACK */ + return -1; + return 0; +} + +int ch347_i2c_read(unsigned char addr, unsigned char *data, unsigned int len) +{ + unsigned char pkt[16], ans[CH347_PACKET_SIZE]; + unsigned int n = 0; + int got = 0; + + if (!ch347 || len > CH347_I2C_MAX) + return -1; + pkt[n++] = CH347_I2C_STREAM; + pkt[n++] = CH347_I2C_STM_START; + pkt[n++] = CH347_I2C_STM_OUT | 1; + pkt[n++] = addr << 1 | 1; + if (len > 1) + pkt[n++] = CH347_I2C_STM_IN | (len - 1); + if (len) + pkt[n++] = CH347_I2C_STM_IN; + pkt[n++] = CH347_I2C_STM_STOP; + pkt[n++] = CH347_I2C_STM_END; + if (bulk(CH347_WRITE_EP, pkt, n, NULL) || bulk(CH347_READ_EP, ans, sizeof(ans), &got) || + got != (int)len + 1 || ans[0] != 0x01) + return -1; + if (len) + memcpy(data, ans + 1, len); + return 0; +} diff --git a/src/ch347_spi.h b/src/ch347_spi.h index 25c3305..42d0441 100644 --- a/src/ch347_spi.h +++ b/src/ch347_spi.h @@ -16,6 +16,13 @@ int ch347_spi_shutdown(void); int ch347_spi_send_command(unsigned int writecnt, unsigned int readcnt, const unsigned char *writearr, unsigned char *readarr); int ch347_cs(bool low); int ch347_set_speed(const char *speed); + +/* the I2C master of the CH347 (CH347T in mode 1, CH347F), for the SigmaStar + ISP: one transaction of up to CH347_I2C_MAX bytes a packet */ +#define CH347_I2C_MAX 60 +int ch347_i2c_speed(unsigned int speed); +int ch347_i2c_write(unsigned char addr, const unsigned char *buf, unsigned int len); +int ch347_i2c_read(unsigned char addr, unsigned char *buf, unsigned int len); const char *ch347_speed_list(void); #endif /* __CH347_SPI_H__ */ diff --git a/src/main.c b/src/main.c index cf8f5d3..9b38cd2 100644 --- a/src/main.c +++ b/src/main.c @@ -29,6 +29,7 @@ #include "flashcmd_api.h" #include "programmer.h" #include "ch347_spi.h" +#include "sigmastar.h" #include "selftest.h" #include "web.h" #include "spi_nand_flash.h" @@ -81,7 +82,11 @@ void usage(int code) " (the first; -a for another) and put its data back\n"\ " -v verify after write on chip\n"\ " -P programmer: ch341a, ch347, ezp, auto (default: the one connected)\n"\ - " -S SPI clock of the CH347: 60M, 30M (default), 15M, 7.5M, 3.75M, 1.875M\n"\ + " -S SPI clock of the CH347: 60M, 30M (default), 15M, 7.5M, 3.75M, 1.875M;\n"\ + " with -m sigmastar the I2C clock: 20K, 100K (default), 400K, 750K\n"\ + " -m sigmastar the flash of a SigmaStar board through the ISP of its SoC: the SoC's UART\n"\ + " (TX to SDA, RX to SCL, GND) on the I2C of the CH341A or the CH347\n"\ + " -q list the addresses that answer on the I2C of the programmer\n"\ " -W [addr:]port web mode: a page with Detect, Read, Erase, Write, Registers\n"\ " -K web mode: ask for this password (any user name)\n"\ " -o web mode: where Read saves the dumps (default: the current one)\n"; @@ -92,6 +97,7 @@ void usage(int code) int main(int argc, char* argv[]) { int c, vr = 0, ret = 0, rc = 1, speed_set = 0; + const char *speed = NULL; char *fname = NULL, op = 0, *web = NULL, *web_key = NULL, *web_dir = NULL; unsigned char *buf; int long long len = 0, addr = 0, flen = 0, wlen = 0; @@ -99,7 +105,7 @@ int main(int argc, char* argv[]) title(); - while ((c = getopt(argc, argv, "diIhveutkLl:a:w:r:P:S:W:K:o:")) != -1) + while ((c = getopt(argc, argv, "diIhveutkqLl:a:w:r:P:S:W:K:o:m:")) != -1) { switch(c) { @@ -115,10 +121,12 @@ int main(int argc, char* argv[]) exit(1); } break; - case 'S': - speed_set = 1; - if (ch347_set_speed(optarg) < 0) { - printf("Unknown SPI speed %s; use one of: %s.\n\n", optarg, ch347_speed_list()); + case 'S': /* SPI or I2C: known after -m */ + speed = optarg; + break; + case 'm': + if (sigmastar_mode(optarg) < 0) { + printf("Unknown mode %s; the one there is: sigmastar.\n\n", optarg); exit(1); } break; @@ -150,6 +158,7 @@ int main(int argc, char* argv[]) case 'e': case 'u': case 't': + case 'q': if(!op) op = c; else @@ -173,12 +182,23 @@ int main(int argc, char* argv[]) } } + if (speed) { + speed_set = 1; + if (sigmastar_on() ? sigmastar_set_speed(speed) < 0 : ch347_set_speed(speed) < 0) { + if (sigmastar_on()) + printf("Unknown I2C speed %s; use one of: %s.\n\n", speed, sigmastar_speed_list()); + else + printf("Unknown SPI speed %s; use one of: %s.\n\n", speed, ch347_speed_list()); + exit(1); + } + } + if (web) { /* the web mode: the page does the operations, one programmer, auto */ /* the page writes whole files from 0 with ECC on: no raw NAND (-d), no ECC ignore (-I), no BAD page skipping (-k) */ - if (op || vr || speed_set || !ECC_fcheck || ECC_ignore || Skip_BAD_page) { - printf("-W runs the web mode: give no -i, -r, -w, -e, -u, -t, -v, -S, -d, -I or -k with it.\n\n"); + if (op || vr || speed_set || !ECC_fcheck || ECC_ignore || Skip_BAD_page || sigmastar_on()) { + printf("-W runs the web mode: give no -i, -r, -w, -e, -u, -t, -q, -v, -S, -m, -d, -I or -k with it.\n\n"); return 1; } return web_serve(web, web_key, web_dir, addr, len); @@ -196,13 +216,26 @@ int main(int argc, char* argv[]) return 1; } - ret = programmer_init(speed_set); + /* in the SigmaStar mode -S is the I2C clock, not the SPI one */ + ret = programmer_init(speed_set && !sigmastar_on()); if (ret < 0) { if (ret == -1) printf("Programmer device not found!\n\n"); return 1; } + if (op == 'q') { + rc = i2c_scan(programmer) ? 1 : 0; + goto out; + } + if (sigmastar_on()) { + const struct programmer *isp = sigmastar_attach(programmer); + + if (!isp) + goto out; + programmer = isp; + } + if((flen = flash_cmd_init(&prog)) <= 0) goto out; diff --git a/src/programmer.c b/src/programmer.c index 15bad57..9306d81 100644 --- a/src/programmer.c +++ b/src/programmer.c @@ -18,8 +18,10 @@ static const struct programmer programmers[] = { - { "ch341a", "CH341A", 0x1a86, { 0x5512 }, ch341a_spi_init, ch341a_spi_shutdown, ch341a_spi_send_command, ch341a_cs, false }, - { "ch347", "CH347", 0x1a86, { 0x55db, 0x55de }, ch347_spi_init, ch347_spi_shutdown, ch347_spi_send_command, ch347_cs, true }, + { "ch341a", "CH341A", 0x1a86, { 0x5512 }, ch341a_spi_init, ch341a_spi_shutdown, ch341a_spi_send_command, ch341a_cs, false, + NULL, ch341a_i2c_speed, ch341a_i2c_write, ch341a_i2c_read, CH341A_I2C_MAX }, + { "ch347", "CH347", 0x1a86, { 0x55db, 0x55de }, ch347_spi_init, ch347_spi_shutdown, ch347_spi_send_command, ch347_cs, true, + NULL, ch347_i2c_speed, ch347_i2c_write, ch347_i2c_read, CH347_I2C_MAX }, { "ezp", "EZP", 0x1fc8, { 0x310b, 0x310c, 0x310d }, ezp_init, ezp_shutdown, NULL, NULL, false, ezp_flash_init }, }; #define NPROG (sizeof(programmers) / sizeof(programmers[0])) diff --git a/src/programmer.h b/src/programmer.h index 9384e88..f2588bc 100644 --- a/src/programmer.h +++ b/src/programmer.h @@ -25,6 +25,15 @@ struct programmer { /* a programmer that runs the SPI in its own firmware: sets up the flash operations itself (no send/cs); returns the chip size or -1 */ long (*flash_init)(struct flash_cmd *cmd); + /* its I2C master, for the SigmaStar ISP (-m sigmastar) and -q; NULL: none. + speed: 0 20 kHz, 1 100 kHz, 2 400 kHz, 3 750 kHz. A write or a read is + one I2C transaction (START, address, data, STOP) of up to i2c_max + bytes; a read of 0 bytes only tells whether the address answers. + 0, or -1 (also on NACK). */ + int (*i2c_speed)(unsigned int speed); + int (*i2c_write)(unsigned char addr, const unsigned char *buf, unsigned int len); + int (*i2c_read)(unsigned char addr, unsigned char *buf, unsigned int len); + unsigned int i2c_max; }; extern const struct programmer *programmer; /* the one in use */ diff --git a/src/sigmastar.c b/src/sigmastar.c new file mode 100644 index 0000000..b84736b --- /dev/null +++ b/src/sigmastar.c @@ -0,0 +1,166 @@ +/* + * sigmastar.c — the SigmaStar (MStar) ISP: the SPI flash of a board on its + * SoC, reached through the SoC's ISP bridge over I2C, without desoldering + * (-m sigmastar). The I2C master is the programmer's (CH341A, CH347): the + * UART of the SoC goes to it, TX to SDA, RX to SCL, and GND. + * + * The bridge answers at 0x49: 0x10 and bytes — they go out on the SPI bus; + * 0x11 — the next I2C read clocks bytes in from it; 0x12 — the end of the + * SPI command (CS high). Before that the SoC's debug port, 0x59, is told to + * let the pins of its UART be the bridge, and "MSTAR" to 0x49 starts it. + * The flash drivers do not know: the programmer's send and cs go here. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ +#include +#include +#include +#include "programmer.h" +#include "sigmastar.h" + +#define ISP_ADDR 0x49 +#define ISP_DEBUG 0x59 +#define ISP_WRITE 0x10 +#define ISP_READ 0x11 +#define ISP_END 0x12 + +static bool on; /* -m sigmastar */ +static int speed = 1; /* 100 kHz */ +static const struct programmer *bus; /* the programmer whose I2C it is */ +static struct programmer isp; /* bus with send and cs of the bridge */ + +static const struct { const char *name; int code; } speeds[] = { + { "20K", 0 }, { "100K", 1 }, { "400K", 2 }, { "750K", 3 }, +}; + +int sigmastar_mode(const char *name) +{ + if (strcasecmp(name, "sigmastar")) + return -1; + on = true; + return 0; +} + +bool sigmastar_on(void) { return on; } + +const char *sigmastar_speed_list(void) { return "20K, 100K (default), 400K, 750K"; } + +int sigmastar_set_speed(const char *name) +{ + for (unsigned i = 0; i < sizeof(speeds) / sizeof(speeds[0]); i++) + if (!strcasecmp(name, speeds[i].name)) { + speed = speeds[i].code; + return 0; + } + return -1; +} + +/* the SPI command: bytes out, then bytes in, in parts that fit the I2C of + the programmer; CS stays low until isp_cs(false) */ +static int isp_send(unsigned int writecnt, unsigned int readcnt, const unsigned char *writearr, unsigned char *readarr) +{ + unsigned char buf[64]; + unsigned int part = bus->i2c_max - 1; /* the command byte first */ + + for (unsigned int done = 0; done < writecnt; ) { + unsigned int n = writecnt - done < part ? writecnt - done : part; + + buf[0] = ISP_WRITE; + memcpy(buf + 1, writearr + done, n); + if (bus->i2c_write(ISP_ADDR, buf, n + 1)) + return -1; + done += n; + } + for (unsigned int done = 0; done < readcnt; ) { + unsigned int n = readcnt - done < bus->i2c_max ? readcnt - done : bus->i2c_max; + + buf[0] = ISP_READ; + if (bus->i2c_write(ISP_ADDR, buf, 1) || bus->i2c_read(ISP_ADDR, readarr + done, n)) + return -1; + done += n; + } + return 0; +} + +static int isp_cs(bool low) +{ + unsigned char end = ISP_END; + + return low ? 0 : bus->i2c_write(ISP_ADDR, &end, 1); +} + +/* the debug port lets the UART pins of the SoC go to the ISP bridge */ +static int debug_port_to_isp(void) +{ + static const unsigned char serdb[] = { 'S', 'E', 'R', 'D', 'B' }; + static const unsigned char init[] = { 0x81, 0x83, 0x84, 0x53, 0x7F, 0x35, 0x71 }; + static const unsigned char uart[] = { 0x10, 0x00, 0x00, 0x0E, 0x12, 0x00, 0x00 }; + static const unsigned char leave[] = { 0x34, 0x45 }; + unsigned char reg; + + if (bus->i2c_write(ISP_DEBUG, serdb, sizeof(serdb))) + return -1; + for (unsigned i = 0; i < sizeof(init); i++) + if (bus->i2c_write(ISP_DEBUG, init + i, 1)) + return -1; + if (bus->i2c_write(ISP_DEBUG, uart, sizeof(uart)) || bus->i2c_write(ISP_DEBUG, uart, 6) || + bus->i2c_read(ISP_DEBUG, ®, 1) || reg) + return -1; + for (unsigned i = 0; i < sizeof(leave); i++) + if (bus->i2c_write(ISP_DEBUG, leave + i, 1)) + return -1; + return 0; +} + +int i2c_scan(const struct programmer *p) +{ + if (!p->i2c_read) { + printf("The %s has no I2C: -q works with the CH341A and the CH347.\n", p->name); + return -1; + } + if (p->i2c_speed(speed)) + return -1; + printf("I2C devices on the %s:\n 0 1 2 3 4 5 6 7 8 9 a b c d e f", p->name); + for (int a = 0; a < 0x80; a++) { + if (a % 16 == 0) + printf("\n%02x: ", a); + if (p->i2c_read(a, NULL, 0)) + printf("-- "); + else + printf("%02x ", a); + } + printf("\n\n"); + return 0; +} + +const struct programmer *sigmastar_attach(const struct programmer *p) +{ + static const unsigned char start[] = { 'M', 'S', 'T', 'A', 'R' }; + + if (!p->i2c_write) { + printf("The %s has no I2C: -m sigmastar works with the CH341A and the CH347.\n", p->name); + return NULL; + } + bus = p; + if (p->i2c_speed(speed)) + return NULL; + if (p->i2c_read(ISP_ADDR, NULL, 0)) { + printf("SigmaStar ISP: nothing answers at I2C 0x%02x. Check the wires (TX to SDA, RX to SCL, GND)\n" + "and that the board is powered; a power cycle of the board can help.\n", ISP_ADDR); + return NULL; + } + if (p->i2c_read(ISP_DEBUG, NULL, 0) && debug_port_to_isp()) + printf("SigmaStar ISP: the debug port 0x%02x did not take the UART pins; going on.\n", ISP_DEBUG); + if (p->i2c_write(ISP_ADDR, start, sizeof(start)) && isp_cs(false)) + return NULL; + printf("SigmaStar ISP through the I2C of the %s at 0x%02x.\n", p->name, ISP_ADDR); + isp = *p; + isp.send = isp_send; + isp.cs = isp_cs; + isp.clock = false; + isp.flash_init = NULL; + return &isp; +} diff --git a/src/sigmastar.h b/src/sigmastar.h new file mode 100644 index 0000000..525bfbe --- /dev/null +++ b/src/sigmastar.h @@ -0,0 +1,28 @@ +/* + * sigmastar.h — the SigmaStar ISP (-m sigmastar) and the I2C bus scan (-q). + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef __MS_SIGMASTAR_H__ +#define __MS_SIGMASTAR_H__ + +#include + +struct programmer; + +int sigmastar_mode(const char *name); /* -m; -1: unknown mode */ +bool sigmastar_on(void); +int sigmastar_set_speed(const char *name); /* -S in this mode: the I2C clock */ +const char *sigmastar_speed_list(void); + +/* the open programmer p, with its send and cs going to the ISP bridge; + NULL (the message printed) when there is no bridge or no I2C */ +const struct programmer *sigmastar_attach(const struct programmer *p); + +/* -q: which I2C addresses answer on the bus of the open programmer p */ +int i2c_scan(const struct programmer *p); + +#endif /* __MS_SIGMASTAR_H__ */ diff --git a/tests/ispsim.c b/tests/ispsim.c new file mode 100644 index 0000000..e8ffa83 --- /dev/null +++ b/tests/ispsim.c @@ -0,0 +1,107 @@ +/* tests/ispsim.c — src/sigmastar.c against a fake I2C master with a SigmaStar + ISP bridge on it: 0x10 + bytes go out on the SPI bus, 0x11 makes the next + read clock bytes in, 0x12 ends the command (CS high); behind it a SPI NOR + that answers READ ID (9F) and READ (03) from a pattern. The debug port 0x59 + does not answer a probe, so the attach goes through its unlock sequence. */ +#include +#include +#include "../src/sigmastar.c" + +static int fail; +static void check(int ok, const char *what) { printf("%s %s\n", ok ? "ok " : "FAIL", what); if (!ok) fail = 1; } + +static unsigned char pattern(unsigned int i) { return (unsigned char)(i * 13 + 7); } + +static unsigned char cmd[600]; /* the SPI bytes of the command under CS */ +static unsigned int ncmd, clocked; /* bytes clocked in since the command */ +static int reading, ends, max_part, writes59, mstar, bad; +static unsigned char log59[64]; +static unsigned int nlog59; +static int answer59 = 1; /* the register read at 0x59 after the UART setup */ + +static int f_speed(unsigned int s) { return s > 3 ? -1 : 0; } + +static int f_write(unsigned char a, const unsigned char *b, unsigned int n) +{ + if ((int)n > max_part) max_part = n; + if (n > 24) bad = 1; /* past the I2C limit given below */ + if (a == 0x59) { + writes59++; + for (unsigned int i = 0; i < n && nlog59 < sizeof(log59); i++) log59[nlog59++] = b[i]; + if (n == 6) answer59 = 0; /* the second UART write: the register is clear */ + return 0; + } + if (a != 0x49) + return -1; + if (n == 5 && !memcmp(b, "MSTAR", 5)) { mstar++; return 0; } + switch (b[0]) { + case 0x10: if (reading) bad = 2; memcpy(cmd + ncmd, b + 1, n - 1); ncmd += n - 1; return 0; + case 0x11: if (n != 1) bad = 3; reading = 1; return 0; + case 0x12: if (n != 1) bad = 4; ends++; ncmd = clocked = 0; reading = 0; return 0; + default: bad = 5; return -1; + } +} + +static int f_read(unsigned char a, unsigned char *b, unsigned int n) +{ + if (a == 0x59) { + if (!n) return -1; /* the probe: no answer */ + b[0] = answer59; + return 0; + } + if (a != 0x49) return -1; + if (!n) return 0; + if (!reading) { bad = 6; return -1; } + if (n > 24) bad = 1; + for (unsigned int i = 0; i < n; i++, clocked++) + if (ncmd == 1 && cmd[0] == 0x9f) + b[i] = clocked < 3 ? (unsigned char[]){ 0xef, 0x40, 0x18 }[clocked] : 0; + else if (ncmd == 4 && cmd[0] == 0x03) + b[i] = pattern(((cmd[1] << 16) | (cmd[2] << 8) | cmd[3]) + clocked); + else + b[i] = 0xff; + return 0; +} + +static const struct programmer fake = { + .key = "ch341a", .name = "CH341A", .send = (void *)1, .cs = (void *)1, + .i2c_speed = f_speed, .i2c_write = f_write, .i2c_read = f_read, .i2c_max = 24, +}; +static const struct programmer noi2c = { .key = "ezp", .name = "EZP" }; + +int main(void) +{ + const struct programmer *p; + unsigned char id[3], buf[200], wr[300]; + + check(sigmastar_mode("mstar") < 0 && sigmastar_mode("SigmaStar") == 0 && sigmastar_on(), "-m sigmastar only"); + check(sigmastar_set_speed("400k") == 0 && sigmastar_set_speed("15M") < 0, "-S takes the I2C clocks"); + check(!sigmastar_attach(&noi2c), "a programmer without I2C is refused"); + + p = sigmastar_attach(&fake); + check(p && p->send != fake.send && p->cs != fake.cs && !p->clock && mstar == 1, "attach: send and cs go to the bridge, MSTAR sent once"); + check(nlog59 >= 5 && !memcmp(log59, "SERDB", 5) && writes59 == 12, " the silent debug port 0x59 got its unlock sequence"); + + /* READ ID: one byte out, three in, then CS high */ + check(!p->cs(true) && !p->send(1, 3, (const unsigned char[]){ 0x9f }, id) && !p->cs(false) && + id[0] == 0xef && id[1] == 0x40 && id[2] == 0x18 && ends == 1, "READ ID through the bridge: ef 40 18, then 0x12"); + + /* READ of 200 bytes at 0x1234: the read goes in parts of the I2C limit, each after a 0x11 */ + int same = 1; + p->cs(true); + check(!p->send(4, sizeof(buf), (const unsigned char[]){ 0x03, 0x00, 0x12, 0x34 }, buf) && !p->cs(false), "READ of 200 bytes"); + for (unsigned int i = 0; i < sizeof(buf); i++) same &= buf[i] == pattern(0x1234 + i); + check(same && max_part <= 24 && !bad, " the data comes back in order, no I2C transaction over 24 bytes"); + + /* a page program: 4 bytes of command and 256 of data out in parts of 23, all under one CS */ + for (unsigned int i = 0; i < sizeof(wr); i++) wr[i] = (unsigned char)i; + int before = ends; + ncmd = 0; + p->cs(true); + int ok = !p->send(260, 0, wr, NULL); + check(ok && ncmd == 260 && !memcmp(cmd, wr, 260) && ends == before, "a write of 260 bytes goes out whole, before the CS goes high"); + p->cs(false); + check(ends == before + 1 && !bad, " then one 0x12"); + + return fail; +} diff --git a/tests/run.sh b/tests/run.sh index 2812745..dccf228 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -19,7 +19,7 @@ set -u T=$(cd "$(dirname "$0")" && pwd) W=$(mktemp -d) trap 'rm -rf "$W"' EXIT -${CC:-cc} -std=gnu99 -Wall -Werror -I"$T/../src" -o "$W/ms" "$T/../src/main.c" "$T/../src/selftest.c" "$T/stub.c" || exit 1 +${CC:-cc} -std=gnu99 -Wall -Werror -I"$T/../src" -o "$W/ms" "$T/../src/main.c" "$T/../src/sigmastar.c" "$T/../src/selftest.c" "$T/stub.c" || exit 1 ${CC:-cc} -std=gnu99 -Wall -Werror -O1 -DSPI_NAND_OIP_TIMEOUT_MS=50 -o "$W/nandsim" "$T/nandsim.c" "$T/../src/timer.c" || exit 1 ${CC:-cc} -std=gnu99 -Wall -Werror -O1 -I"$T/../src" ${LIBUSB_CFLAGS:-} -o "$W/ch341sim" "$T/ch341sim.c" || exit 1 ${CC:-cc} -std=gnu99 -Wall -Werror -O1 -o "$W/norsim" "$T/norsim.c" "$T/../src/timer.c" || exit 1 @@ -27,6 +27,7 @@ ${CC:-cc} -std=gnu99 -Wall -Werror -O1 -I"$T/../src" ${LIBUSB_CFLAGS:-} -o "$W/c ${CC:-cc} -std=gnu99 -Wall -Werror -O1 -I"$T/../src" ${LIBUSB_CFLAGS:-} -o "$W/ezpsim" "$T/ezpsim.c" "$T/../src/timer.c" || exit 1 ${CC:-cc} -std=gnu99 -Wall -Werror -O1 -I"$T/../src" -o "$W/spisim" "$T/spisim.c" || exit 1 ${CC:-cc} -std=gnu99 -Wall -Werror -O1 -I"$T/../src" -o "$W/usbfssim" "$T/usbfssim.c" || exit 1 +${CC:-cc} -std=gnu99 -Wall -Werror -O1 -I"$T/../src" -o "$W/ispsim" "$T/ispsim.c" || exit 1 make -s -C "$T/../src" web_page.h || exit 1 ${CC:-cc} -std=gnu99 -Wall -Werror -O1 -I"$T/../src" -o "$W/websim" "$T/websim.c" -pthread || exit 1 cd "$W"; B=./ms; fail=0 @@ -61,6 +62,18 @@ t 1 "-S unknown speed" $B -S 100M -i t 0 "-P ch347 -S 15M" $B -P ch347 -S 15M -i t 1 "-S with the CH341A is an error" $B -P ch341a -S 15M -i t 1 "-S with auto (CH341A) is an error" $B -S 15M -i +t 1 "-m unknown mode" $B -m mstar -i +t 0 "-m sigmastar -i (CH341A, auto)" $B -m sigmastar -i +t 0 "-P ch347 -m sigmastar -i" $B -P ch347 -m sigmastar -i +t 1 "-P ezp -m sigmastar: no I2C" $B -P ezp -m sigmastar -i +t 1 "-m sigmastar, nothing at 0x49" MS_I2C=none $B -m sigmastar -i +t 0 "-m sigmastar -S 400K (CH341A)" $B -m sigmastar -S 400K -i +t 0 "-S before -m is the I2C clock" $B -S 750K -m sigmastar -i +t 1 "-m sigmastar -S 15M is an error" $B -m sigmastar -S 15M -i +t 1 "-W with -m is an error" $B -W 8080 -m sigmastar +t 1 "-P ezp -q: no I2C" $B -P ezp -q +$B -q | grep -q '^40: .* 49 ' && $B -q | grep -q '^50: .* 59 ' && $B -q | grep -q '^00: -- ' && echo "ok -q lists the I2C addresses that answer" || { echo "FAIL -q"; fail=1; } +$B -h | grep -q '^ -m sigmastar' && $B -h | grep -q '^ -q ' && echo "ok -h lists -m and -q" || { echo "FAIL -h without -m/-q"; fail=1; } MS_PROGRAMMER=1 $B -P ch347 -i | grep -q '^programmer: ch347' && MS_PROGRAMMER=1 $B -i | grep -q '^programmer: ch341a' && echo "ok -P chooses the programmer, auto by default" || { echo "FAIL programmer choice"; fail=1; } $B -h | grep -q '^ -P ' && $B -h | grep -q '^ -S ' && echo "ok -h lists -P and -S" || { echo "FAIL -h without -P/-S"; fail=1; } $B -h | grep -q '^ -k ' && echo "ok -h lists -k" || { echo "FAIL -h without -k"; fail=1; } @@ -133,6 +146,8 @@ grep -a '^ok\|^FAIL' ezpsim.log grep -a '^ok\|^FAIL' spisim.log ./usbfssim > usbfssim.log 2>&1 || fail=1 grep -a '^ok\|^FAIL' usbfssim.log +./ispsim > ispsim.log 2>&1 || fail=1 +grep -a '^ok\|^FAIL' ispsim.log ./websim > websim.log 2>&1 || fail=1 grep -a '^ok\|^FAIL' websim.log [ "$fail" = 0 ] && echo "all tests passed" diff --git a/tests/stub.c b/tests/stub.c index 307d94b..c5978de 100644 --- a/tests/stub.c +++ b/tests/stub.c @@ -15,7 +15,14 @@ static int e(unsigned long o, unsigned long l) { if (*env("MS_PROTECT")) return static int u(void) { return strcmp(env("MS_FAIL"),"unprotect") ? 0 : -3; } #include #include "programmer.h" -static const struct programmer stubprogs[] = { { .key = "ch341a", .name = "CH341A", .send = (void *)1 }, { .key = "ch347", .name = "CH347", .send = (void *)1, .clock = true }, { .key = "ezp", .name = "EZP" } }; +/* the I2C of the CH341A and the CH347: a SigmaStar ISP bridge at 0x49 and 0x59, or (MS_I2C=none) nothing */ +static int i2c_sp(unsigned int sp) { (void)sp; return 0; } +static int i2c_w(unsigned char a, const unsigned char *b, unsigned int n) { (void)b; (void)n; return strcmp(env("MS_I2C"), "none") && (a == 0x49 || a == 0x59) ? 0 : -1; } +static int i2c_r(unsigned char a, unsigned char *b, unsigned int n) { if (n) memset(b, 0, n); return i2c_w(a, NULL, 0); } +static const struct programmer stubprogs[] = { + { .key = "ch341a", .name = "CH341A", .send = (void *)1, .i2c_speed = i2c_sp, .i2c_write = i2c_w, .i2c_read = i2c_r, .i2c_max = 24 }, + { .key = "ch347", .name = "CH347", .send = (void *)1, .clock = true, .i2c_speed = i2c_sp, .i2c_write = i2c_w, .i2c_read = i2c_r, .i2c_max = 60 }, + { .key = "ezp", .name = "EZP" } }; static const struct programmer *chosen = &stubprogs[0]; /* auto: the stub has a CH341A */ const struct programmer *programmer; int programmer_select(const char *n) { From 0a9c2ff0b534e31b082efc893e39c8cc5ca79c19 Mon Sep 17 00:00:00 2001 From: Compot Developer Date: Tue, 3 Nov 2026 19:40:24 +0000 Subject: [PATCH 2/4] README: -m and -q in the usage, -q among the operations --- README.md | 6 +++++- README.ru.md | 6 +++++- README.zh-CN.md | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0027f84..ea423e2 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,11 @@ microsnander [options] (the first; -a for another) and put its data back -v verify after write on chip -P programmer: ch341a, ch347, ezp, auto (default: the one connected) - -S SPI clock of the CH347: 60M, 30M (default), 15M, 7.5M, 3.75M, 1.875M + -S SPI clock of the CH347: 60M, 30M (default), 15M, 7.5M, 3.75M, 1.875M; + with -m sigmastar the I2C clock: 20K, 100K (default), 400K, 750K + -m sigmastar the flash of a SigmaStar board through the ISP of its SoC: the SoC's UART + (TX to SDA, RX to SCL, GND) on the I2C of the CH341A or the CH347 + -q list the addresses that answer on the I2C of the programmer -W [addr:]port web mode: a page with Detect, Read, Erase, Write, Registers -K web mode: ask for this password (any user name) -o web mode: where Read saves the dumps (default: the current one) diff --git a/README.ru.md b/README.ru.md index 1dc7970..0883ecb 100644 --- a/README.ru.md +++ b/README.ru.md @@ -156,7 +156,11 @@ microsnander [options] (the first; -a for another) and put its data back -v verify after write on chip -P programmer: ch341a, ch347, ezp, auto (default: the one connected) - -S SPI clock of the CH347: 60M, 30M (default), 15M, 7.5M, 3.75M, 1.875M + -S SPI clock of the CH347: 60M, 30M (default), 15M, 7.5M, 3.75M, 1.875M; + with -m sigmastar the I2C clock: 20K, 100K (default), 400K, 750K + -m sigmastar the flash of a SigmaStar board through the ISP of its SoC: the SoC's UART + (TX to SDA, RX to SCL, GND) on the I2C of the CH341A or the CH347 + -q list the addresses that answer on the I2C of the programmer -W [addr:]port web mode: a page with Detect, Read, Erase, Write, Registers -K web mode: ask for this password (any user name) -o web mode: where Read saves the dumps (default: the current one) diff --git a/README.zh-CN.md b/README.zh-CN.md index 72a9141..9494823 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -143,7 +143,11 @@ microsnander [options] (the first; -a for another) and put its data back -v verify after write on chip -P programmer: ch341a, ch347, ezp, auto (default: the one connected) - -S SPI clock of the CH347: 60M, 30M (default), 15M, 7.5M, 3.75M, 1.875M + -S SPI clock of the CH347: 60M, 30M (default), 15M, 7.5M, 3.75M, 1.875M; + with -m sigmastar the I2C clock: 20K, 100K (default), 400K, 750K + -m sigmastar the flash of a SigmaStar board through the ISP of its SoC: the SoC's UART + (TX to SDA, RX to SCL, GND) on the I2C of the CH341A or the CH347 + -q list the addresses that answer on the I2C of the programmer -W [addr:]port web mode: a page with Detect, Read, Erase, Write, Registers -K web mode: ask for this password (any user name) -o web mode: where Read saves the dumps (default: the current one) From db81b480ece7d53168dfc29a823f31396bf439ee Mon Sep 17 00:00:00 2001 From: Compot Developer Date: Tue, 3 Nov 2026 19:42:28 +0000 Subject: [PATCH 3/4] -q is a flag: the bus before the operation, or alone As in the SigmaStar unbrick steps of the OpenIPC wiki (-i -q): -q lists the I2C addresses that answer and then the operation runs; -q alone lists them only. --- README.md | 8 ++++---- README.ru.md | 8 ++++---- README.zh-CN.md | 8 ++++---- src/main.c | 16 ++++++++++++---- tests/run.sh | 1 + 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index ea423e2..34e56f6 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,8 @@ microsnander [options] with -m sigmastar the I2C clock: 20K, 100K (default), 400K, 750K -m sigmastar the flash of a SigmaStar board through the ISP of its SoC: the SoC's UART (TX to SDA, RX to SCL, GND) on the I2C of the CH341A or the CH347 - -q list the addresses that answer on the I2C of the programmer + -q list the addresses that answer on the I2C of the programmer (alone, or + before the operation: -m sigmastar -i -q) -W [addr:]port web mode: a page with Detect, Read, Erase, Write, Registers -K web mode: ask for this password (any user name) -o web mode: where Read saves the dumps (default: the current one) @@ -348,13 +349,12 @@ bootloader ([OpenIPC wiki: SigmaStar unbrick](https://github.com/OpenIPC/wiki/bl with `-P` or by itself; the EZP programmers have no I2C. - `-S` is the I2C clock in this mode: `20K`, `100K` (default), `400K`, `750K`. - `-q` lists the I2C addresses that answer, to check the wiring (the bridge is at `0x49`, the - debug port of the SoC at `0x59`). + debug port of the SoC at `0x59`): alone, or before the operation, as `-m sigmastar -i -q`. - Everything else works as with a chip in the socket: `-i`, `-r`, `-w`, `-e`, `-v`, `-a`, `-l`, SPI NOR and SPI NAND. Not in the web mode or the Android app. ```sh -microsnander -q # 0x49 and 0x59 must answer -microsnander -m sigmastar -i # the flash of the board +microsnander -m sigmastar -i -q # 0x49 and 0x59 must answer, then the flash microsnander -m sigmastar -r backup.bin # back it up first microsnander -m sigmastar -a 0 -l 0x40000 -e # the loader part, then microsnander -m sigmastar -w u-boot.bin -v # a working loader diff --git a/README.ru.md b/README.ru.md index 0883ecb..17c143a 100644 --- a/README.ru.md +++ b/README.ru.md @@ -160,7 +160,8 @@ microsnander [options] with -m sigmastar the I2C clock: 20K, 100K (default), 400K, 750K -m sigmastar the flash of a SigmaStar board through the ISP of its SoC: the SoC's UART (TX to SDA, RX to SCL, GND) on the I2C of the CH341A or the CH347 - -q list the addresses that answer on the I2C of the programmer + -q list the addresses that answer on the I2C of the programmer (alone, or + before the operation: -m sigmastar -i -q) -W [addr:]port web mode: a page with Detect, Read, Erase, Write, Registers -K web mode: ask for this password (any user name) -o web mode: where Read saves the dumps (default: the current one) @@ -352,13 +353,12 @@ ISP-мост SoC по I2C. Так восстанавливают плату с `-P` или сам; у программаторов EZP I2C нет. - `-S` в этом режиме — частота I2C: `20K`, `100K` (по умолчанию), `400K`, `750K`. - `-q` выводит адреса I2C, которые отвечают, — для проверки подключения (мост — `0x49`, порт - отладки SoC — `0x59`). + отладки SoC — `0x59`): отдельно или перед операцией, например `-m sigmastar -i -q`. - Остальное — как с микросхемой в панельке: `-i`, `-r`, `-w`, `-e`, `-v`, `-a`, `-l`, SPI NOR и SPI NAND. В веб-режиме и в приложении для Android этого режима нет. ```sh -microsnander -q # должны ответить 0x49 и 0x59 -microsnander -m sigmastar -i # флешка платы +microsnander -m sigmastar -i -q # должны ответить 0x49 и 0x59, затем флешка microsnander -m sigmastar -r backup.bin # сначала копия microsnander -m sigmastar -a 0 -l 0x40000 -e # область загрузчика, затем microsnander -m sigmastar -w u-boot.bin -v # рабочий загрузчик diff --git a/README.zh-CN.md b/README.zh-CN.md index 9494823..d847720 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -147,7 +147,8 @@ microsnander [options] with -m sigmastar the I2C clock: 20K, 100K (default), 400K, 750K -m sigmastar the flash of a SigmaStar board through the ISP of its SoC: the SoC's UART (TX to SDA, RX to SCL, GND) on the I2C of the CH341A or the CH347 - -q list the addresses that answer on the I2C of the programmer + -q list the addresses that answer on the I2C of the programmer (alone, or + before the operation: -m sigmastar -i -q) -W [addr:]port web mode: a page with Detect, Read, Erase, Write, Registers -K web mode: ask for this password (any user name) -o web mode: where Read saves the dumps (default: the current one) @@ -312,13 +313,12 @@ root;在电视盒子上会出现在主屏幕,可用遥控器操作)。 给板子重新上电通常有效。 - I2C 主机为 CH341A 或 CH347(CH347T 模式 1、CH347F),照常用 `-P` 选择或自动选择;EZP 编程器没有 I2C。 - 此模式下 `-S` 为 I2C 时钟:`20K`、`100K`(默认)、`400K`、`750K`。 -- `-q` 列出有响应的 I2C 地址,用于检查接线(桥为 `0x49`,SoC 调试端口为 `0x59`)。 +- `-q` 列出有响应的 I2C 地址,用于检查接线(桥为 `0x49`,SoC 调试端口为 `0x59`):可单独使用,也可在操作之前,如 `-m sigmastar -i -q`。 - 其余与插座中的芯片相同:`-i`、`-r`、`-w`、`-e`、`-v`、`-a`、`-l`,SPI NOR 和 SPI NAND。Web 模式和 Android 应用不支持此模式。 ```sh -microsnander -q # 0x49 和 0x59 必须有响应 -microsnander -m sigmastar -i # 板上的闪存 +microsnander -m sigmastar -i -q # 0x49 和 0x59 必须有响应,然后是闪存 microsnander -m sigmastar -r backup.bin # 先备份 microsnander -m sigmastar -a 0 -l 0x40000 -e # 引导程序区域,然后 microsnander -m sigmastar -w u-boot.bin -v # 写入可用的引导程序 diff --git a/src/main.c b/src/main.c index 9b38cd2..020bf27 100644 --- a/src/main.c +++ b/src/main.c @@ -86,7 +86,8 @@ void usage(int code) " with -m sigmastar the I2C clock: 20K, 100K (default), 400K, 750K\n"\ " -m sigmastar the flash of a SigmaStar board through the ISP of its SoC: the SoC's UART\n"\ " (TX to SDA, RX to SCL, GND) on the I2C of the CH341A or the CH347\n"\ - " -q list the addresses that answer on the I2C of the programmer\n"\ + " -q list the addresses that answer on the I2C of the programmer (alone, or\n"\ + " before the operation: -m sigmastar -i -q)\n"\ " -W [addr:]port web mode: a page with Detect, Read, Erase, Write, Registers\n"\ " -K web mode: ask for this password (any user name)\n"\ " -o web mode: where Read saves the dumps (default: the current one)\n"; @@ -98,6 +99,7 @@ int main(int argc, char* argv[]) { int c, vr = 0, ret = 0, rc = 1, speed_set = 0; const char *speed = NULL; + int scan = 0; char *fname = NULL, op = 0, *web = NULL, *web_key = NULL, *web_dir = NULL; unsigned char *buf; int long long len = 0, addr = 0, flen = 0, wlen = 0; @@ -154,11 +156,13 @@ int main(int argc, char* argv[]) case 'o': web_dir = optarg; break; + case 'q': /* before the operation, or alone */ + scan = 1; + break; case 'i': case 'e': case 'u': case 't': - case 'q': if(!op) op = c; else @@ -197,7 +201,7 @@ int main(int argc, char* argv[]) /* the web mode: the page does the operations, one programmer, auto */ /* the page writes whole files from 0 with ECC on: no raw NAND (-d), no ECC ignore (-I), no BAD page skipping (-k) */ - if (op || vr || speed_set || !ECC_fcheck || ECC_ignore || Skip_BAD_page || sigmastar_on()) { + if (op || scan || vr || speed_set || !ECC_fcheck || ECC_ignore || Skip_BAD_page || sigmastar_on()) { printf("-W runs the web mode: give no -i, -r, -w, -e, -u, -t, -q, -v, -S, -m, -d, -I or -k with it.\n\n"); return 1; } @@ -208,6 +212,8 @@ int main(int argc, char* argv[]) return 1; } + if (op == 0 && scan) + op = 'q'; /* -q alone: only the bus */ if (op == 0) usage(1); if (op == 'x' || (ECC_ignore && !ECC_fcheck) || (ECC_ignore && Skip_BAD_page) || (op == 'w' && ECC_ignore) || @@ -224,8 +230,10 @@ int main(int argc, char* argv[]) return 1; } + if (scan && i2c_scan(programmer)) + goto out; if (op == 'q') { - rc = i2c_scan(programmer) ? 1 : 0; + rc = 0; goto out; } if (sigmastar_on()) { diff --git a/tests/run.sh b/tests/run.sh index dccf228..438f9e9 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -72,6 +72,7 @@ t 0 "-S before -m is the I2C clock" $B -S 750K -m sigmastar -i t 1 "-m sigmastar -S 15M is an error" $B -m sigmastar -S 15M -i t 1 "-W with -m is an error" $B -W 8080 -m sigmastar t 1 "-P ezp -q: no I2C" $B -P ezp -q +$B -m sigmastar -i -q | grep -q '^40: .* 49 ' && $B -m sigmastar -i -q | grep -q 'Status\|SigmaStar ISP' && echo "ok -q before -i: the bus, then the operation" || { echo "FAIL -q with -i"; fail=1; } $B -q | grep -q '^40: .* 49 ' && $B -q | grep -q '^50: .* 59 ' && $B -q | grep -q '^00: -- ' && echo "ok -q lists the I2C addresses that answer" || { echo "FAIL -q"; fail=1; } $B -h | grep -q '^ -m sigmastar' && $B -h | grep -q '^ -q ' && echo "ok -h lists -m and -q" || { echo "FAIL -h without -m/-q"; fail=1; } MS_PROGRAMMER=1 $B -P ch347 -i | grep -q '^programmer: ch347' && MS_PROGRAMMER=1 $B -i | grep -q '^programmer: ch341a' && echo "ok -P chooses the programmer, auto by default" || { echo "FAIL programmer choice"; fail=1; } From f614e17a2759b6d2ff053f880e14187879be4c9f Mon Sep 17 00:00:00 2001 From: Compot Developer Date: Tue, 3 Nov 2026 21:57:34 +0000 Subject: [PATCH 4/4] Version 1.20.0; README: -W refuses -q and -m too --- CHANGELOG.md | 11 +++++++++++ README.md | 4 ++-- README.ru.md | 4 ++-- README.zh-CN.md | 2 +- src/main.c | 2 +- 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 094b4bb..deb520e 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.20.0 — 2026-11-04 + +- SigmaStar ISP (`-m sigmastar`): the SPI flash of a SigmaStar (MStar) board through the ISP + bridge of its SoC over I2C, without desoldering, to bring back a board with a broken + loader. The I2C master is that of the CH341A or the CH347 (CH347T in mode 1, CH347F), + chosen with `-P` or by itself; the SoC's UART goes to it (TX to SDA, RX to SCL, GND). + `-S` is the I2C clock in this mode (20K, 100K, 400K, 750K); `-q` lists the I2C addresses + that answer, alone or before the operation (`-m sigmastar -i -q`). Every operation and + SPI NOR and SPI NAND work as with a chip in the socket; not in the web mode or the app. + The CH341A and the CH347 drivers get I2C transactions. + ## 1.19.1 — 2026-11-04 - SPI NAND: the FORESEE F35SQA001G and F35SQA002G are fully unlocked. They power up with the diff --git a/README.md b/README.md index 34e56f6..cae780c 100644 --- a/README.md +++ b/README.md @@ -275,8 +275,8 @@ How it works: can be replugged, or used by a normal microsnander run. - One operation runs at a time; a request while one runs gets `409`. The page polls the state twice a second while an operation runs. -- `-W` takes no operation of its own (`-i`, `-r`, `-w`, `-e`, `-u`, `-t`) and no `-v`, `-S`, `-d`, - `-I` or `-k`: the page does the operations, writes whole files from address 0 with ECC on +- `-W` takes no operation of its own (`-i`, `-r`, `-w`, `-e`, `-u`, `-t`) and no `-q`, `-m`, `-v`, + `-S`, `-d`, `-I` or `-k`: the page does the operations, writes whole files from address 0 with ECC on and always verifies. `-K` and `-o` go with `-W` only. - The service keeps running when a client disconnects; a slow client does not hold the others (up to 16 connections at a time). diff --git a/README.ru.md b/README.ru.md index 17c143a..172317f 100644 --- a/README.ru.md +++ b/README.ru.md @@ -279,8 +279,8 @@ microsnander -P ch347 -W 8080 -o /srv/dumps можно переподключить или работать с ним обычным запуском microsnander. - Операции идут по одной; запрос во время операции получает `409`. Пока операция идёт, страница спрашивает состояние два раза в секунду. -- С `-W` не задаются свои операции (`-i`, `-r`, `-w`, `-e`, `-u`, `-t`) и ключи `-v`, `-S`, `-d`, `-I`, - `-k`: операции делает страница, пишет целые файлы с адреса 0 с включённым ECC и всегда +- С `-W` не задаются свои операции (`-i`, `-r`, `-w`, `-e`, `-u`, `-t`) и ключи `-q`, `-m`, `-v`, `-S`, + `-d`, `-I`, `-k`: операции делает страница, пишет целые файлы с адреса 0 с включённым ECC и всегда проверяет запись. `-K` и `-o` — только вместе с `-W`. - Служба продолжает работать, когда клиент отключается; медленный клиент не держит остальных (до 16 соединений одновременно). diff --git a/README.zh-CN.md b/README.zh-CN.md index d847720..5c38d08 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -249,7 +249,7 @@ microsnander -P ch347 -W 8080 -o /srv/dumps - 只使用一个编程器,选择方式与不带 `-W` 时相同;连接了多个时请用 `-P`。 - 每个操作开始时打开编程器、结束后关闭,因此两次操作之间可以重新插拔,或用普通方式运行 microsnander。 - 每次只运行一个操作;操作进行中的请求会得到 `409`。操作进行时页面每秒查询两次状态。 -- `-W` 不接受自身的操作(`-i`、`-r`、`-w`、`-e`、`-u`、`-t`),也不接受 `-v`、`-S`、`-d`、`-I` 或 `-k`: +- `-W` 不接受自身的操作(`-i`、`-r`、`-w`、`-e`、`-u`、`-t`),也不接受 `-q`、`-m`、`-v`、`-S`、`-d`、`-I` 或 `-k`: 操作由页面完成,在开启 ECC 的情况下从地址 0 写入完整文件,并始终校验。`-K` 和 `-o` 只能与 `-W` 一起使用。 - 客户端断开时服务继续运行;慢速客户端不会阻塞其他客户端(最多同时 16 个连接)。 diff --git a/src/main.c b/src/main.c index 020bf27..745409e 100644 --- a/src/main.c +++ b/src/main.c @@ -37,7 +37,7 @@ struct flash_cmd prog; extern unsigned int bsize; -#define _VER "1.19.1" +#define _VER "1.20.0" const char *ms_version = _VER;