What is This?
The Virtual CH341A is a software emulator that pretends to be a real CH341A USB-to-SPI programmer. When you run it, your computer sees a USB device that looks exactly like the real thing—same vendor ID (1A86), same product ID (5512), same protocol.
But instead of connecting to a physical flash chip, it talks to a file on your disk that acts as the flash memory.
Why Would You Want This?
Testing without hardware. If you're developing firmware update tools, you can test them without a real chip. Break things safely.
Fault injection. Real flash chips don't fail on command. This one does. Enable write-protect, inject bit errors, simulate timeouts—all deterministically reproducible.
CI/CD pipelines. Automated testing of flash programming workflows. No USB permissions issues, no hardware to manage.
Learning. See exactly what bytes flow over the SPI bus. Understand the CH341A protocol. Experiment freely.
Architecture
┌──────────────┐ ┌───────────────────────────────────┐
│ flashrom │ USB │ Virtual CH341A │
│ (or other │◄───────►│ │
│ tools) │ │ ┌─────────────┐ ┌─────────────┐ │
└──────────────┘ │ │ FunctionFS │ │ Socket │ │
│ │ │ (USB mode) │ │ (test mode) │ │
│ LD_PRELOAD │ └──────┬──────┘ └──────┬──────┘ │
▼ │ │ │ │
┌──────────────┐ │ │ ┌─────────►│ │
│ libusb │ │ │ │ │ │
│ intercept │─────────┼─────────┼────┘ │ │
└──────────────┘ Socket │ └───────┬───────┘ │
│ ▼ │
┌──────────────┐ Socket │ ┌─────────────────────────────┐ │
│ test_socket │◄───────►│ │ CH341A Protocol │ │
│ _client │ │ │ (SPI stream + Control) │ │
└──────────────┘ │ └──────────────┬──────────────┘ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ Flash Model │ │
│ │ (JEDEC SPI NOR + Faults) │ │
│ └──────────────┬──────────────┘ │
│ ▼ │
│ chip.bin │
└───────────────────────────────────┘
Prerequisites
| Requirement | Minimum | Notes |
|---|---|---|
| OS | Linux (kernel 4.x+) | Tested on Debian/Ubuntu |
| Compiler | GCC 9+ or Clang 10+ | C++20 support required |
| libusb | 1.0 | Only for interceptor build |
# Debian/Ubuntu
sudo apt install build-essential libusb-1.0-0-dev
# Fedora
sudo dnf install gcc-c++ libusb1-develNote: The CLI tool (vch341a) has no dependencies—just a C++20 compiler.
Download Verification
Verify your download with SHA256 checksums:
| Version | SHA256 |
|---|---|
| v1.3.0 | 42f050f81743bbbaa5e36f16f511ed9f3852ff6dc4e52da423c7288e2f971254 |
| v1.2.0 | f78f9000c77c469553c1564b160f91d6d5b743f134f549962aa2479db22f7bf2 |
| v1.1.0 | a01a6b3fc9891f5ede6f2c534a4baec4a1dd7a714d94ce2e4ab377fecb5d2560 |
| v1.0.0 | 0f018eb4d329746cca78a30b761c369834facc62309d6fd818bfdbdc358e36bb |
# Verify download
sha256sum virtual-ch341a-v1.3.0.tar.gz
# Expected: 42f050f81743bbbaa5e36f16f511ed9f3852ff6dc4e52da423c7288e2f971254Quick Start
# Build emulator
g++ -std=c++20 -O2 -pthread -I src \
src/main.cpp src/usb/*.cpp src/ch341a/*.cpp \
src/flash/*.cpp src/faults/*.cpp src/socket/*.cpp \
-o virtual_ch341a
# Build the libusb interceptor
g++ -std=c++20 -shared -fPIC -o libusb_intercept.so \
src/intercept/libusb_intercept.cpp -ldl -lpthread
# Build CLI tool (standalone, no dependencies)
g++ -std=c++20 -O2 -o vch341a src/cli/vch341a.cppvch341a CLI Tool (v1.3.0)
Standalone command-line tool for flash file manipulation. No flashrom, no LD_PRELOAD, no dependencies.
# Create a 16MB flash file
./vch341a -f chip.bin create 16
# Show info with CRC32
./vch341a -f chip.bin info
# Hex dump at address 0x1000
./vch341a -f chip.bin hexdump 0x1000 256
# Write firmware at address 0
./vch341a -f chip.bin write firmware.bin 0
# Verify write
./vch341a -f chip.bin verify firmware.bin 0
# Read back to file
./vch341a -f chip.bin read backup.bin 0 16384
# Erase sector at 0x10000
./vch341a -f chip.bin erase sector 0x10000
# Erase entire chip
./vch341a -f chip.bin erase chipCLI Commands
| Command | Description |
|---|---|
create SIZE | Create new flash file (SIZE in MB) |
info | Display size, CRC32, empty state |
hexdump [ADDR] [LEN] | Hex dump with ASCII |
read FILE [ADDR] [LEN] | Read to file or stdout |
write FILE [ADDR] | Write with NOR behavior |
erase TYPE [ADDR] | sector/block/chip erase |
verify FILE [ADDR] | Compare flash with file |
fill VALUE [ADDR] [LEN] | Fill with byte value |
diff FILE | Compare two flash files, show diff regions |
pattern TYPE | Fill with test pattern (aa55/random/ramp) |
stats | Entropy, empty sectors, byte distribution |
search HEX | Binary search for byte sequences |
The CLI properly emulates NOR flash behavior: bits can only change 1→0, erase sets to 0xFF.
Operation Modes
| Mode | Flag | Description |
|---|---|---|
| Simulation | --sim | Built-in tests, no external interface |
| Socket | --socket | Unix socket at /tmp/ch341a.sock |
| USB | --usb | Real USB via FunctionFS (requires root) |
R&D Fast Mode (v1.2.0)
The fastest way to use flashrom—direct memory-mapped flash access without socket overhead. Full Read/Write/Erase support!
# Read flash
CH341A_FLASH_FILE=chip.bin LD_PRELOAD=./libusb_intercept.so \
flashrom -p ch341a_spi -r dump.bin
# Write flash (includes erase + verify)
CH341A_FLASH_FILE=chip.bin LD_PRELOAD=./libusb_intercept.so \
flashrom -p ch341a_spi -w firmware.bin
# Erase only
CH341A_FLASH_FILE=chip.bin LD_PRELOAD=./libusb_intercept.so \
flashrom -p ch341a_spi -EPerformance
| Operation | Fast Mode | Socket Mode |
|---|---|---|
| 16MB Read | ~1.3 s | 60+ s |
| 16MB Write | ~3 s | N/A |
| Chip Erase | ~0.7 s | N/A |
Supported SPI Commands
| Opcode | Command | Description |
|---|---|---|
0x03 | READ | Read data |
0x0B | FAST_READ | Fast read with dummy byte |
0x9F | RDID | JEDEC ID |
0x05 | RDSR | Status register |
0x06 | WREN | Write enable |
0x04 | WRDI | Write disable |
0x02 | PP | Page program (256 bytes) |
0x20 | SE | Sector erase (4KB) |
0xD8 | BE | Block erase (64KB) |
0xC7 | CE | Chip erase |
How Fast Mode Works
- Set
CH341A_FLASH_FILEto point to your flash image - Interceptor memory-maps the file with read/write permissions
- READ commands stream directly from mapped memory
- WRITE commands modify mapped memory (NOR flash: bits only 1→0)
- ERASE commands set regions to 0xFF
- Automatic bit-reversal for flashrom's LSB-first format
- No socket connection or emulator process needed
Socket Mode (v1.1.0)
For testing with fault injection, use socket mode:
# Terminal 1: Start the emulator
./virtual_ch341a --socket
# Terminal 2: Run flashrom with the interceptor
LD_PRELOAD=./libusb_intercept.so flashrom -p ch341a_spi
# Output:
# Found Winbond flash chip "W25Q128.V" (16384 kB, SPI) on ch341a_spi.Reading and Writing
# Read flash contents
LD_PRELOAD=./libusb_intercept.so flashrom -p ch341a_spi -r backup.bin
# Write firmware
LD_PRELOAD=./libusb_intercept.so flashrom -p ch341a_spi -w firmware.bin
# Verify
LD_PRELOAD=./libusb_intercept.so flashrom -p ch341a_spi -v firmware.binHow the Interceptor Works
The libusb_intercept.so library intercepts libusb function calls:
- When flashrom calls
libusb_open_device_with_vid_pid(0x1a86, 0x5512), the interceptor connects to the emulator's socket instead - USB bulk transfers are translated to socket protocol messages
- Responses from the emulator are returned as USB bulk IN data
This approach works with any CH341A-based tool, not just flashrom.
Bit Reversal
The CH341A hardware does LSB-first SPI clocking. Flashrom pre-reverses bits before sending. The emulator handles this automatically by:
- Detecting known bit-reversed SPI opcodes
- Un-reversing MOSI data to get actual commands
- Pre-reversing MISO responses for flashrom
| Command | Opcode | Bit-Reversed |
|---|---|---|
| RDID | 0x9F | 0xF9 |
| READ | 0x03 | 0xC0 |
| REMS | 0x90 | 0x09 |
| RDP | 0xAB | 0xD5 |
| RDSR | 0x05 | 0xA0 |
Socket Mode Testing
# Start emulator
./virtual_ch341a --socket
# In another terminal, run the test client
./test_socket_client
# Or connect manually
nc -U /tmp/ch341a.sockSocket mode is ideal for automated testing and CI/CD.
Fault Injection
Unlike real hardware, faults here are reproducible. Same inputs, same failures, every time.
Write Protect (--wp)
Simulates the hardware WP pin being active. All write and erase operations silently fail.
Verify Noise (--noise N)
Every N bytes read, one bit gets flipped. Simulates flash degradation or communication errors.
Timeout (--timeout N)
Every N operations, the emulator doesn't respond. Tests timeout handling.
Chip Profiles
| Profile | JEDEC ID | Size |
|---|---|---|
w25q128 | EF 40 18 | 16 MB |
w25q64 | EF 40 17 | 8 MB |
w25q32 | EF 40 16 | 4 MB |
w25q16 | EF 40 15 | 2 MB |
Real USB Enumeration
For the emulator to appear as a real USB device (alternative to LD_PRELOAD):
# 1. Load kernel modules
sudo modprobe dummy_hcd
sudo modprobe libcomposite
# 2. Setup ConfigFS gadget
sudo ./scripts/setup_gadget.sh
# 3. Run the emulator in USB mode
sudo ./virtual_ch341a --usb -v
# 4. Verify it appears
lsusb | grep 1a86:5512
# Bus 003 Device 002: ID 1a86:5512 QinHeng Electronics CH341AThen flashrom can talk to it directly without LD_PRELOAD:
flashrom -p ch341a_spi -r backup.binTechnical Details
CH341A Protocol
The CH341A uses a simple packet protocol over USB bulk endpoints:
SPI Transfer (0xA8)
[0xA8] [len-1] [MOSI data...] (Format A - length encoded)
[0xA8] [MOSI data...] (Format B - flashrom style)
Response contains MISO data of the same length.
Chip Select Control (0xAB)
[0xAB] [0x80] [GPIO state] [0x20]
CS0 is bit 0. Low = active (SPI selected).
Flash Model
The flash model accurately simulates NOR flash behavior:
- Erase sets bits to 1 (0xFF)
- Program can only clear bits (1→0)
- Page boundary wrapping (256-byte pages)
- Status register with WIP/WEL bits
This means if you try to write 0xFF to a byte that contains 0x00, it stays 0x00. Just like real flash.
What This Is NOT
This is not a replacement for real hardware testing. It's a development and testing tool.
- Does not simulate electrical characteristics
- Does not simulate timing margins
- Does not simulate chip-specific quirks
- Cannot program real flash chips
Use it for development. Validate on real hardware before production.
Troubleshooting
"error: 'std::format' is not a member of 'std'"
Your compiler doesn't support C++20. Upgrade to GCC 13+ or use g++ -std=c++20.
flashrom says "No EEPROM/flash device found"
Check that CH341A_FLASH_FILE points to an existing file:
# Create flash file first
./vch341a -f chip.bin create 16
# Then use flashrom
CH341A_FLASH_FILE=chip.bin LD_PRELOAD=./libusb_intercept.so flashrom -p ch341a_spiSocket connection refused
The emulator isn't running. Start it first:
./virtual_ch341a --socket &Write succeeds but verify fails
This is NOR flash behavior. You must erase before writing new data:
./vch341a -f chip.bin erase chip
./vch341a -f chip.bin write firmware.bin 0USB mode: "Cannot open FunctionFS"
USB mode requires root and kernel modules:
sudo modprobe dummy_hcd libcomposite
sudo ./scripts/setup_gadget.sh
sudo ./virtual_ch341a --usbChangelog
v1.3.0 (2025-12-30)
- vch341a CLI tool - Standalone flash file manipulation
- create, info, read, write, erase, hexdump, verify, fill commands
- diff - Compare two flash files, show diff regions
- pattern - Fill with test patterns (aa55, random, ramp)
- stats - Entropy, empty sectors, byte distribution
- search - Binary search for byte sequences
- No external dependencies (no flashrom, no LD_PRELOAD)
- Proper NOR flash emulation (bits only 1→0, erase = 0xFF)
- CRC32 checksum calculation
--max-sizeoption for configurable size limits
- CLI unit tests - 36 automated tests for all CLI commands
- Bug fixes:
- Negative size handling (
create -1no longer wraps) - Clear "No command specified" error message
- Negative size handling (
- Security hardening - Size limit validation, negative number rejection
- Code quality - Refactored
try_direct_spi()into focused functions
v1.2.0 (2025-12-29)
- Full Write/Erase support in Fast Mode
- Page Program (PP) with CH341A segment marker handling
- Sector Erase (4KB), Block Erase (64KB), Chip Erase
- Write Enable / Write Disable commands
- Fixed Page Program data corruption (0xA8 segment markers)
- 16MB Write in ~3 seconds, Chip Erase in ~0.7 seconds
- Comprehensive
libusb_intercept.mddocumentation - CH341A packet segmentation documented in protocol notes
v1.1.0 (2025-12-29)
- R&D Fast Mode: Direct mmap flash access, 16MB read in ~1.3 seconds
- Streaming read optimization bypasses socket round-trips
CH341A_FLASH_FILEenvironment variable for direct access- All probe commands handled directly (RDID, RES, REMS, RDSFDP)
- Added flashrom integration via libusb interception
v1.0.0 (2025-12-28)
- Initial release
- Full JEDEC flash model
- Socket and USB modes
- Fault injection