# etch341 **Repository Path**: WalkingWithASnail/etch341 ## Basic Information - **Project Name**: etch341 - **Description**: etch341 - **Primary Language**: Unknown - **License**: GPL-3.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-10 - **Last Updated**: 2026-08-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README

etch341 icon — wireframe SOIC-8 chip with gold leads

# etch341 [![CI](https://img.shields.io/github/actions/workflow/status/packetThrower/etch341/ci.yml?branch=main&style=flat-square&logo=github&label=CI)](https://github.com/packetThrower/etch341/actions/workflows/ci.yml) [![Release](https://img.shields.io/github/v/release/packetThrower/etch341?style=flat-square&logo=github&label=release&include_prereleases)](https://github.com/packetThrower/etch341/releases/latest) [![Downloads](https://img.shields.io/github/downloads/packetThrower/etch341/total?style=flat-square&logo=github&label=downloads)](https://github.com/packetThrower/etch341/releases) [![Rust](https://img.shields.io/badge/Rust-stable-CE422B?style=flat-square&logo=rust&logoColor=white)](Cargo.toml) [![License: GPL v3+](https://img.shields.io/badge/license-GPLv3%2B-blue?style=flat-square)](LICENSE)

etch341 GUI showing the Detect pane with a chip identified, the workflow sidebar, and the activity log

## Minimum OS versions **macOS** (Apple Silicon and Intel) [![macOS 11+](https://img.shields.io/badge/macOS-11%2B-333?style=flat-square&logo=apple&logoColor=white)](#install) [![Apple Silicon](https://img.shields.io/badge/Apple%20Silicon-arm64-333?style=flat-square&logo=apple&logoColor=white)](#install) [![Intel](https://img.shields.io/badge/Intel-x86__64-333?style=flat-square&logo=apple&logoColor=white)](#install) **Windows** (x64 and ARM64) [![Windows 10 21H2+ x64](https://img.shields.io/badge/Windows%2010%2021H2%2B-x64-0078D4?style=flat-square&logo=windows&logoColor=white)](#windows) [![Windows 11 ARM64](https://img.shields.io/badge/Windows%2011-ARM64-0078D4?style=flat-square&logo=windows11&logoColor=white)](#windows) **Linux** (amd64 and arm64) [![Ubuntu 22.04+](https://img.shields.io/badge/Ubuntu-22.04%2B-E95420?style=flat-square&logo=ubuntu&logoColor=white)](#linux) [![Debian 12+](https://img.shields.io/badge/Debian-12%2B-A81D33?style=flat-square&logo=debian&logoColor=white)](#linux) [![Fedora 40+](https://img.shields.io/badge/Fedora-40%2B-294172?style=flat-square&logo=fedora&logoColor=white)](#linux) [![Arch rolling](https://img.shields.io/badge/Arch-rolling-1793D1?style=flat-square&logo=archlinux&logoColor=white)](#linux) [![openSUSE Tumbleweed](https://img.shields.io/badge/openSUSE-Tumbleweed-73BA25?style=flat-square&logo=opensuse&logoColor=white)](#linux) The GUI uses the GPUI rendering stack, which on Linux requires a Vulkan-capable GPU with current Mesa drivers. The headless CLI (`cargo install --no-default-features` or `etch341 ` on the released binary) has no graphics requirements. Cross-platform CLI + GUI flash programmer for the **CH341A** USB SPI/I²C interface. Userspace USB on Linux + macOS (no driver to install). Windows uses the built-in **WinUSB** kernel driver, but it needs a one-time Zadig bind to the CH341A — see [Install → Windows](#windows). ## Status Working programmer for SPI NOR up to 32 MB+, on both 3.3V and 1.8V chips (with a CH341A V1.7+ module for the 1.8V parts). Chips ≤ 16 MB use standard 3-byte addressing; > 16 MB chips use the 4-byte opcode variants (0x13 / 0x12 / 0x21 / 0xDC) automatically based on the chip's size. Round-trip validated against a real Macronix MX25U4033E on an NVIDIA GTX 1060 — full erase → blank-check → write → verify cycle landed byte-identical (SHA-256 match) to the original VBIOS. | Feature | CLI | GUI | | --- | --- | --- | | Detect (JEDEC ID → chip lookup) | ✅ | ✅ | | Read | ✅ | ✅ | | Erase (full + range) | ✅ | ✅ (arm/confirm) | | Write (with erase + verify) | ✅ | ✅ (arm/confirm + file picker) | | Verify | ✅ | ✅ (file picker) | | Blank check | ✅ | ✅ | | Status registers (SR1/2/3 decode) | ✅ (`sr`) | ✅ | | SFDP parameter table | ✅ (`sfdp`) | ✅ (Detect pane) | | Security registers / OTP (read / erase / write) | ✅ (`otp`) | ✅ (arm/confirm) | | Settings (clock, accent, updates, …) | ✅ (`--speed`) | ✅ | | 4-byte addressing (>16 MB chips) | ✅ | ✅ | | I²C scan / read / write / verify / blank-check / erase \* | ✅ | ✅ | | UEFI BIOS Setup explorer (read-only) | ✅ (`bios`) | ✅ (BIOS explorer pane) | | Intel Flash Descriptor / region map | ✅ (`ifd`) | ✅ (BIOS explorer strip) | | Offline dump inspection (strings / search / diff) | ✅ | ✅ (Hex viewer) | > \* **Silicon-validated on a 24C02** (1-byte address, no > bit-stuffing): scan / read / write / verify / blank-check / erase > all round-trip byte-exact, tested at 100 kHz and 20 kHz. Still > mock-only pending a chip: the 2-byte-address parts (24C32+) and the > bit-stuffed 24C04 / 08 / 16. I²C defaults to 100 kHz and refuses > anything above 400 kHz — over-clocking bricked a part during > bring-up. 103 unit tests covering the SPI / I²C protocols (including the SFDP parser and the OTP / security-register ops), the high-level ops, the inspect/search primitives, the UEFI firmware parsers (firmware-volume walk, IFR/HII decode, NVRAM stores) and the Intel Flash Descriptor parser — all running against mock transports or pure inputs. Hardware-touching tests are gated behind `--features hardware`. ### Hardware-validated - **Macronix MX25U4033E** (1.8V, 4 Mbit) on a GTX 1060 VBIOS chip (GP106 PG410). Full erase → write → verify cycle returns the chip to a byte-identical state (matching SHA-256 across pre- and post-cycle reads). - **CH341A V1.7** mini programmer with on-board ZIF socket + SOIC-8 clip. The V1.7 has a 1.8V mode that older V1.3 boards lack — required for the U-series Macronix chips and most modern GPU VBIOS. Other chips in `chips/chips.toml` are entered from datasheets but haven't been individually exercised against silicon. If you run a JEDEC `detect` on a chip and the response decodes correctly to a named entry, the rest of the operations are very likely to work (they're chip-agnostic at the protocol level). ### Partially hardware-validated - **I²C / 24Cxx EEPROMs.** Confirmed end-to-end against a real **AT24C02**: `read` / `write` / `erase` / `verify` / `blank-check` all round-trip byte-exact, at both 100 kHz and 20 kHz. Getting there shook out two real bugs (writes always timed out polling an ACK the CH341 never exposes; multi-byte reads weren't NACK- terminated and corrupted past ~30 bytes) — both fixed. Still **mock-only**, pending silicon: the 2-byte-address parts (24C32 and up) and the bit-stuffed 24C04 / 08 / 16 sub-families. `scan` can't see a *blank* EEPROM — the CH341 doesn't expose the ACK bit, so an all-`0xFF` chip is indistinguishable from an empty bus; address it directly with `--chip`. ## Install ### Prerequisites - Rust 1.85+ (uses 2024 edition) - A C compiler (cc / clang) — `rusb` builds libusb-1.0 from source and links it statically into the binary, so there's no system-wide libusb install needed at build or runtime - A CH341A USB programmer (the common "black module" or the "V1.3 mini" with on-board ZIF socket both work) If you'd rather grab a pre-built artifact instead of compiling, head to the [Releases page](https://github.com/packetThrower/etch341/releases) — each release ships native installers for arm64 + amd64 on all three platforms: | Platform | Installer / Package | Also | |---|---|---| | macOS | `etch341---macos.dmg` (drag-to-install) | `etch341-macOS--.zip` (bare `.app` bundle) | | Windows | `etch341---windows-setup.exe` (NSIS) | `etch341---windows.msi` (MSI, stable tags only) | | Windows | — | `etch341---windows.zip` (portable bare `.exe`) | | Linux (Debian/Ubuntu) | `etch341---linux.deb` | — | | Linux (Fedora/openSUSE/RHEL) | `etch341--1..rpm` | — | | Linux (Arch) | `etch341--1-.pkg.tar.zst` | — | | Linux (any) | `etch341---linux.AppImage` (universal) | — | That's 19 artifacts per release (6 macOS + 6 Windows + 8 Linux, plus a `SHA256SUMS` line for everything). The Linux `.deb` / `.rpm` / `.pkg.tar.zst` installs drop the udev rule into `/usr/lib/udev/rules.d/` automatically; the AppImage and bare binaries don't (run the manual `sudo cp` step from the Linux section below the first time). ### macOS The easiest path is the Homebrew tap, which always tracks the latest stable release and auto-updates with `brew upgrade`: ```sh brew tap packetThrower/tap brew trust packetThrower/tap # Homebrew 6.0.0+: trust the tap before installing brew install packetThrower/tap/etch341 ``` Or build from source: ```sh cargo install --path . ``` No driver setup needed — macOS leaves the CH341A's vendor interface alone, and libusb is bundled into the binary. ### Linux ```sh sudo cp platform/udev/99-ch341a.rules /etc/udev/rules.d/ sudo udevadm control --reload cargo install --path . ``` The udev rule lets unprivileged users open the device. Without it you'll hit `PermissionDenied`. ### Windows Windows doesn't ship a generic userspace USB driver, so the CH341A either enumerates as an unknown device or gets claimed by a vendor serial-port driver — either way, libusb can't open it. The one-time fix is to bind the **WinUSB** generic driver to the device: 1. Plug in the CH341A. 2. Run [Zadig](https://zadig.akeo.ie/) (≈600 KB, no installer). 3. In Zadig's `Options` menu, enable `List All Devices`. 4. Select the entry with VID `0x1A86` / PID `0x5512`, choose **WinUSB** from the driver dropdown, and click `Install Driver`. 5. Install etch341 from the Scoop bucket (auto-updates via `scoop update`): ```powershell scoop bucket add packetThrower https://github.com/packetThrower/scoop-bucket scoop install etch341 ``` Or build from source: `cargo install --path .`. You only need to do steps 1–4 once per machine. If `etch341 detect` reports `DeviceNotFound` on Windows after running it once, the driver binding is usually the cause — re-check in Zadig that the device is still bound to WinUSB and not to a vendor driver that took over after an update. ## Usage ### CLI ```sh etch341 detect # identify the chip etch341 read -o bios.bin # dump entire chip to file etch341 read -o - # dump to stdout (pipe to anything) etch341 read -o - | sha256sum # hash a chip without a temp file etch341 read -o head.bin --length 0x1000 # first 4 KB only etch341 write -i bios.bin # erase + program + verify etch341 write -i bios.bin --no-erase --no-verify # raw program etch341 erase # full chip erase etch341 erase --range 0x10000:0x10000 # erase one 64 KB block etch341 verify -i bios.bin # compare without writing etch341 verify -i bios.bin --diff # ...and show where, as a hex diff etch341 blank-check # confirm all 0xFF etch341 sr # dump SR1/SR2/SR3 with decoded bits etch341 sfdp # decode the chip's SFDP table etch341 otp read # dump the security / OTP registers ``` I²C EEPROMs (24Cxx family) use the nested `i2c` subcommand. > ✅ **The I²C path is silicon-validated on a 24C02** (1-byte > address): scan / read / write / verify / blank-check / erase are > byte-exact at 100 kHz and 20 kHz. One gotcha — `i2c scan` can't see > a *blank* EEPROM: the CH341 never exposes the I²C ACK bit, so an > all-`0xFF` chip reads like an empty bus; address it directly with > `-c`. The 2-byte-address parts (24C32+) and the bit-stuffed > 24C04 / 08 / 16 are implemented and mock-tested but not yet > confirmed on silicon. Unlike SPI NOR there's no JEDEC ID register, so the chip must be selected explicitly with `-c`: ```sh etch341 i2c scan # list 7-bit addrs that ACK etch341 -c 24C256 i2c read -o eeprom.bin # dump entire chip etch341 -c 24C256 i2c write -i eeprom.bin # program + verify etch341 -c 24C256 i2c verify -i eeprom.bin # compare without writing etch341 -c 24C02 i2c blank-check # confirm all 0xFF etch341 -c 24C02 i2c erase # write 0xFF to every byte ``` `--straps <0..7>` selects the A0/A1/A2 pin value if the chip is wired non-default. The 24C04/08/16 use bit-stuffing in the slave address for their high memory bits; this is handled automatically. Supported families: 24C01 / 02 / 04 / 08 / 16 / 32 / 64 / 128 / 256 / 512. Other 24Cxx chips work if you add an entry to `chips/i2c_chips.toml`. The CLI also has four offline inspection commands that work on flash dump files (no hardware required): ```sh etch341 chips # list every supported chip etch341 chips --find mx25 # substring filter on name or JEDEC etch341 chips --bus i2c # filter to one bus family etch341 strings -i dump.bin # printable ASCII strings ≥4 chars etch341 strings -i dump.bin --min-len 8 # noisier-but-richer threshold etch341 search "55 AA" -i dump.bin # find hex pattern (spaces optional) etch341 search "Award" -i dump.bin # ASCII (case-insensitive) etch341 search "DEADBEEF" -i dump.bin --context 32 # widen the gutter etch341 diff old.bin new.bin # side-by-side hex diff of two dumps ``` `search` parses the pattern as hex when the condensed form is even-length and all hex digits (`55AA`, `DE AD BE EF`); anything else is taken as ASCII. Matched bytes print in upper-case hex; surrounding context stays lower-case for an at-a-glance visual contrast. `diff` compares two files and prints only the differing regions as a side-by-side hex view — red for the left file, green for the right — with a couple of context lines around each run. It exits 1 on any difference and 0 when identical, so it drops into scripts like `diff(1)`/`cmp(1)`. Colour is automatic: on for a terminal, off when piped or when `NO_COLOR` is set. The same byte-level view is available against live hardware with `verify --diff` (file vs chip read-back), and the GUI's Verify pane offers it as "View diff in Hex" — all three share one region-grouping core, so they highlight identically. Two more offline commands decode the *structure* of a BIOS/firmware dump rather than just its bytes: ```sh etch341 bios settings -i dump.bin # UEFI Setup options: label, value, choices etch341 bios settings -i dump.bin --find vt-d # filter by label substring etch341 bios settings -i dump.bin --changed # only options that differ from default etch341 bios settings -i dump.bin --json # machine-readable, for archival/diffing etch341 bios diff -a old.bin -b new.bin # Setup options that changed between two dumps etch341 bios boot -i dump.bin # decode BootOrder + Boot#### entries etch341 bios id -i dump.bin # vendor / project / platform identity etch341 ifd -i dump.bin # Intel Flash Descriptor: region map + lock state ``` `bios` reads a UEFI BIOS image (a full flash dump) and reconstructs the Setup menu you'd see in firmware setup: it walks the firmware volumes, decompresses the section trees (LZMA / EFI-Tiano / Insyde), parses the IFR Setup-form bytecode and HII string packages, and joins them against the on-chip NVRAM store (AMI `NVAR` and the standard EDK2 `$VSS`) for live values. Options the firmware may hide or lock at runtime are flagged. It's **read-only** — there's no Setup-write path. AMI Aptio is validated end-to-end; Insyde/Phoenix images parse, but their factory values only populate from a live chip dump (an update image ships blank NVRAM). `ifd` parses the Intel Flash Descriptor at the start of an Intel-chipset flash and prints the region layout (Descriptor / BIOS / ME / GbE / …) with exact offsets and sizes, the chip density, the per-master write-access matrix, and a plain-language lock summary (whether the host can write the Descriptor and ME regions). Handy before touching a modern Intel board, where writing the ME region can brick it. Global flags: - `-v, --verbose` — log every SPI or I²C transaction to stderr. Invaluable for debugging in-circuit issues and for spotting wiring problems (every `-> OUT` line should be followed by a sensible `<- IN`; missing IN bytes mean either the chip isn't responding or the bus is mis-wired). - `-c, --chip ` — for SPI, overrides JEDEC autodetect with a chip name from `chips/chips.toml` (e.g. `W25Q128JV`). For I²C and for `--dry-run` it's **required** (there's no JEDEC equivalent on I²C, and dry-run has no hardware to autodetect). - `-s, --speed ` — bus clock speed. Supported rates on the CH341A: 20, 100, 400, 750. SPI defaults to 750; **I²C defaults to 100 and rejects anything above 400** (the 24Cxx family is spec'd at 400 kHz max — over-clocking one bricked a part during bring-up). - `-n, --dry-run` — for hardware-touching commands, validate everything possible (chip name in DB, input file is readable, start + length fits the chip) and print a `[dry-run]` summary of what would happen. Never opens the CH341. Useful for sanity- checking flags before you actually pull the trigger on an erase or write. Offline commands (`chips`, `strings`, `search`, `diff`) ignore the flag because they don't touch hardware anyway. ### GUI ```sh etch341 # no subcommand → opens the GUI window ``` Build the CLI-only variant (no GPUI fetch, much smaller binary, faster build) with: ```sh cargo build --release --no-default-features ``` ## Hardware notes ### In-circuit programming on enterprise hardware In-circuit attempts on **server-class boards, dual-BIOS systems, and firewalls** frequently fail. The host's SPI controller actively drives MISO low even when the board is "powered off" — `etch341 detect` returns `JEDEC ID : 0x000000` and the verbose log shows clean command bytes going out but nothing meaningful coming back. Diagnose with the loopback test: ```sh etch341 detect -v # with clip OFF the chip; nothing else changed ``` - `<- IN [4]: ffffffff` → CH341A is healthy; the target board is fighting us - `<- IN [4]: 00000000` → CH341A or wiring problem, not the target Remedies, in order of effort: 1. Use a loose chip in the CH341A's on-board ZIF socket 2. Lift pin 8 (VCC) of the in-circuit chip and inject 3.3V externally 3. Hot-air the chip off and use the ZIF ### Voltage The black-module CH341A has a 3.3V/5V jumper near the USB end. **3.3V is correct for every 3.3V family in `chips/chips.toml`** (W25Q, W25X, MX25L, GD25Q, SST25VF, AT25SF, EN25QH, P25Q, IS25LP). The 1.8V families (W25Q*JW, MX25U, GD25LQ) need a 1.8V-capable programmer — either the V1.7 module's separate 1.8V switch or a level-shifter adapter; running them at 3.3V will damage them. **5V will damage every chip in the DB** — don't flip the jumper to 5V unless you know exactly why. ### Pin 1 The SOIC-8 clip's red wire = pin 1. The chip's pin 1 is marked with a dot or notch on the package. About half of first-attempt failures are clip-reversed. ## Architecture ``` src/ ├── main.rs entry point; no-args → GUI, subcommand → CLI ├── cli.rs clap derive definitions + dispatch ├── error.rs thiserror enum ├── ch341.rs USB layer; impls both SpiTransport and I2cTransport ├── spi.rs SPI NOR opcodes + SpiTransport trait + helpers ├── ops.rs high-level SPI read / erase / write / verify / blank / detect ├── i2c.rs 24Cxx protocol + I2cTransport trait + helpers ├── i2c_ops.rs high-level I²C scan / read / write / verify / blank / erase ├── chipdb.rs TOML chip DB loader (SPI + I²C, embedded at build) ├── inspect.rs parse-pattern / extract-strings / find-pattern shared by CLI + GUI ├── ifd.rs Intel Flash Descriptor parser (region map + master access) ├── uefi/ UEFI BIOS Setup explorer: FV walk, IFR/HII decode, NVRAM stores ├── prefs.rs ~/.config/etch341/prefs.toml load/save (GUI settings) └── gui/ GPUI frontend; behind the `gui` cargo feature (default-on) chips/chips.toml 116 SPI NOR entries across Winbond (W25X, W25Q, W25Q*JW 1.8V), Macronix (MX25L, MX25U 1.8V, MX25R low-power, MX66), GigaDevice (GD25Q, GD25LQ 1.8V), Spansion/Cypress S25FL, Micron N25Q/MT25Q, SST (SST25VF, SST26VF), ISSI (IS25LP, IS25WP 1.8V), Adesto AT25, EON (EN25QH, EN25Q), PUYA P25Q, XTX, Zbit, Boya chips/i2c_chips.toml 10 I²C EEPROM entries (24C01 .. 24C512) ``` The `SpiTransport` trait abstracts the USB layer so the high-level ops can be unit-tested against a deterministic mock (`src/spi.rs::test_support::MockSpi`). The `Ch341` struct is the production implementation. ## Development ```sh just build # full build (CLI + GUI; first time pulls the gpui git dep) just build-cli # CLI only, much faster just test # unit tests, no hardware just run -- detect -v ``` Or use Cargo directly: ```sh cargo build --no-default-features # CLI only cargo test --no-default-features cargo run # GUI cargo run --no-default-features -- detect -v ``` The app icon is `build/appicon.svg` (a top-down wireframe of a SOIC-8 in the family palette of Baudrun + PortFinder). The other icon files in `build/` and `resources/icons/` are generated from it by `build/make-icon.sh` — re-run that script after editing the SVG (requires `rsvg-convert`, ImageMagick's `magick`, and `iconutil` for the macOS `.icns`). ## License GPL-3.0-or-later. See [LICENSE](LICENSE) for the full text.