Back to Tools
C++20linuxstablev1.3.0MIT

Virtual CH341A

USB-SPI Bridge Emulator

Software emulation of the CH341A USB-to-SPI programmer for testing and development without physical hardware. Full Read/Write/Erase support with 16MB operations in seconds. Implements JEDEC flash model with fault injection capabilities.

Published December 28, 2025
Download Virtual CH341A

This tool is free to use. If you find it useful, consider supporting development.

Features

  • vch341a CLI - Standalone flash manipulation
  • 36 automated CLI unit tests
  • Full Read/Write/Erase support
  • Fast Mode - 16MB read ~1.3s, write ~3s
  • SPI flash emulation (JEDEC commands)
  • CH341A protocol decoding
  • Flashrom integration via LD_PRELOAD
  • Direct flash file mmap access
  • USB enumeration via FunctionFS
  • Unix socket interface for testing
  • Deterministic fault injection
  • Multiple chip profiles (W25Qxx, MX25L, GD25Q)
  • File-backed persistent storage
  • CI/CD friendly socket mode
hardware-emulationspi-flashusbtestingch341aflashrom

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

RequirementMinimumNotes
OSLinux (kernel 4.x+)Tested on Debian/Ubuntu
CompilerGCC 9+ or Clang 10+C++20 support required
libusb1.0Only for interceptor build
# Debian/Ubuntu
sudo apt install build-essential libusb-1.0-0-dev
 
# Fedora
sudo dnf install gcc-c++ libusb1-devel

Note: The CLI tool (vch341a) has no dependencies—just a C++20 compiler.

Download Verification

Verify your download with SHA256 checksums:

VersionSHA256
v1.3.042f050f81743bbbaa5e36f16f511ed9f3852ff6dc4e52da423c7288e2f971254
v1.2.0f78f9000c77c469553c1564b160f91d6d5b743f134f549962aa2479db22f7bf2
v1.1.0a01a6b3fc9891f5ede6f2c534a4baec4a1dd7a714d94ce2e4ab377fecb5d2560
v1.0.00f018eb4d329746cca78a30b761c369834facc62309d6fd818bfdbdc358e36bb
# Verify download
sha256sum virtual-ch341a-v1.3.0.tar.gz
# Expected: 42f050f81743bbbaa5e36f16f511ed9f3852ff6dc4e52da423c7288e2f971254

Quick 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.cpp

vch341a 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 chip

CLI Commands

CommandDescription
create SIZECreate new flash file (SIZE in MB)
infoDisplay 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 FILECompare two flash files, show diff regions
pattern TYPEFill with test pattern (aa55/random/ramp)
statsEntropy, empty sectors, byte distribution
search HEXBinary search for byte sequences

The CLI properly emulates NOR flash behavior: bits can only change 1→0, erase sets to 0xFF.

Operation Modes

ModeFlagDescription
Simulation--simBuilt-in tests, no external interface
Socket--socketUnix socket at /tmp/ch341a.sock
USB--usbReal 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 -E

Performance

OperationFast ModeSocket Mode
16MB Read~1.3 s60+ s
16MB Write~3 sN/A
Chip Erase~0.7 sN/A

Supported SPI Commands

OpcodeCommandDescription
0x03READRead data
0x0BFAST_READFast read with dummy byte
0x9FRDIDJEDEC ID
0x05RDSRStatus register
0x06WRENWrite enable
0x04WRDIWrite disable
0x02PPPage program (256 bytes)
0x20SESector erase (4KB)
0xD8BEBlock erase (64KB)
0xC7CEChip erase

How Fast Mode Works

  1. Set CH341A_FLASH_FILE to point to your flash image
  2. Interceptor memory-maps the file with read/write permissions
  3. READ commands stream directly from mapped memory
  4. WRITE commands modify mapped memory (NOR flash: bits only 1→0)
  5. ERASE commands set regions to 0xFF
  6. Automatic bit-reversal for flashrom's LSB-first format
  7. 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.bin

How the Interceptor Works

The libusb_intercept.so library intercepts libusb function calls:

  1. When flashrom calls libusb_open_device_with_vid_pid(0x1a86, 0x5512), the interceptor connects to the emulator's socket instead
  2. USB bulk transfers are translated to socket protocol messages
  3. 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:

  1. Detecting known bit-reversed SPI opcodes
  2. Un-reversing MOSI data to get actual commands
  3. Pre-reversing MISO responses for flashrom
CommandOpcodeBit-Reversed
RDID0x9F0xF9
READ0x030xC0
REMS0x900x09
RDP0xAB0xD5
RDSR0x050xA0

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.sock

Socket 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

ProfileJEDEC IDSize
w25q128EF 40 1816 MB
w25q64EF 40 178 MB
w25q32EF 40 164 MB
w25q16EF 40 152 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 CH341A

Then flashrom can talk to it directly without LD_PRELOAD:

flashrom -p ch341a_spi -r backup.bin

Technical 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_spi

Socket 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 0

USB mode: "Cannot open FunctionFS"

USB mode requires root and kernel modules:

sudo modprobe dummy_hcd libcomposite
sudo ./scripts/setup_gadget.sh
sudo ./virtual_ch341a --usb

Changelog

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-size option for configurable size limits
  • CLI unit tests - 36 automated tests for all CLI commands
  • Bug fixes:
    • Negative size handling (create -1 no longer wraps)
    • Clear "No command specified" error message
  • 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.md documentation
  • 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_FILE environment 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

Related Dossiers