Skip to content
ArthurHeymansPublic

About

Blazing fast SPI NOR flash programming

Resources

Stars

21 stars

Watchers

1 watching

Forks

Repository files navigation

rflasher

rflasher logo

rflasher reads, writes, and erases SPI flash chips from Rust. It is heavily inspired by flashprog and supports 480+ chips (rflasher list-chips) and most common programmers: CH341A/CH347, Dediprog, FTDI (MPSSE and FT4222H), serprog, Raiden debug hardware, internal Intel/AMD chipset controllers, Allwinner FEL, and Linux spidev/MTD/GPIO. The same codebase also runs in the browser via WebSerial/WebUSB, and the core crates are no_std for reuse in firmware.

Only SPI NOR flash is in scope.

⚠️ Alpha software

rflasher is not ready for production use. For anything critical, use flashprog instead.

Before writing: make sure the programmer voltage matches the chip (3.3 V for most SPI flash), check write protection status, and always read a backup first. Corrupting the wrong region (e.g. the Intel ME region) can brick the device permanently.

Installation

Build from source (Rust 1.85+):

git clone https://github.com/ArthurHeymans/rflasher
cd rflasher
cargo build --release                                          # default programmers
cargo build --release --features all-programmers               # everything
cargo build --release --no-default-features --features ch341a,serprog  # pick your own
cargo install --path .

USB device permissions

USB programmers (CH341A, CH347, FTDI, Dediprog, Raiden) need udev rules for non-root access, e.g.:

# CH341A
SUBSYSTEM=="usb", ATTR{idVendor}=="1a86", ATTR{idProduct}=="5512", MODE="0660", GROUP="plugdev"

# Raiden Debug SPI / Cr50 (Google debug hardware)
# The backend matches on vendor ID alone, so this rule covers all Google
# devices; add ATTR{idProduct} matches if you want it narrower.
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", MODE="0660", GROUP="plugdev"

Install to /etc/udev/rules.d/, run udevadm control --reload-rules && udevadm trigger, and add your user to the plugdev group (create the group first if your distro doesn't have it).

Man page

The man page is generated from the CLI definitions:

cargo run --bin gen-manpage   # writes man/rflasher.1
man -l man/rflasher.1

Usage

rflasher probe -p ch341a                # detect the flash chip
rflasher info -p ch341a                 # detailed chip information
rflasher read -p ch341a backup.bin      # read flash to a file
rflasher write -p ch341a firmware.bin   # erase, write and verify
rflasher erase -p ch341a
rflasher verify -p ch341a firmware.bin
rflasher list-chips                     # supported chips
rflasher list-programmers               # available programmers

Short aliases exist (r/w/v, E for erase), the programmer can be set with the RFLASHER_PROGRAMMER environment variable, and -v/-vv increase log verbosity. rflasher write -p ch341a firmware.bin --no-verify skips verification.

When several connected USB programmers match the -p specification, rflasher lists them (with USB bus/address and serial number) and asks which one to use. Selector options narrow the list, e.g. -p dediprog:id=SF123456 or -p ftdi:serial=FT123456. With --non-interactive, or when stdin or stderr is not a terminal, several matches are an error instead.

Programmers

Programmer Parameters Example
ch341a — rflasher probe -p ch341a
ch347 — rflasher probe -p ch347
serprog dev=, baud rate, or ip= rflasher probe -p serprog:dev=/dev/ttyUSB0:115200
rflasher probe -p serprog:ip=192.168.1.100:5000
dediprog spispeed= rflasher probe -p dediprog:spispeed=12M
ftdi type=, port=, divisor= rflasher probe -p ftdi:type=2232h,port=B,divisor=10
ft4222 spispeed=, cs= rflasher probe -p ft4222:spispeed=20000,cs=0
raiden — rflasher probe -p raiden
internal — rflasher probe -p internal
ati_spi pci=DDDD:BB:DD.F Experimental GPU VBIOS access; read the preparation instructions before probing
linux_spi dev=, spispeed= rflasher probe -p linux_spi:dev=/dev/spidev0.0,spispeed=4000
linux_gpio_spi gpiochip=, cs=, sck=, mosi=, miso=, spispeed= rflasher probe -p linux_gpio_spi:gpiochip=0,cs=25,sck=11,mosi=10,miso=9
linux_mtd dev= rflasher read -p linux_mtd:dev=0 backup.bin
sunxi_fel — rflasher probe -p sunxi_fel (Allwinner SoC in FEL mode)
dummy — in-memory flash emulator for testing

Run rflasher <command> --help or see the man page for the full option list.

The internal programmer uses the chipset's SPI controller on Linux via PCI sysfs and /dev/mem, so it usually requires root. On Intel PCH100 and newer, the SPI PCI function must be visible and accessible through sysfs, with a validated device ID and platform mapping. If firmware hides it, or kernel permissions deny access, rflasher refuses initialization (including reads); it does not bypass the kernel with direct PCI I/O or write to the LPC bridge instead. Where PCI reads are allowed but write access is denied, read-only operation may still be possible. Its controller code is no_std and can be reused in firmware; see crates/rflasher-internal.

Chip database

Chips are defined as RON files in crates/rflasher-chips/data/vendors/ and compiled into the CLI by default, so installed binaries work without the source tree or separate data files. --chip-db <path> replaces the bundled database with a RON file or a directory of RON files.

JEDEC IDs are not unique, so a probed chip can match several definitions. Identical operational definitions are interchangeable. Otherwise validated SFDP data narrows the candidates, and unresolved aliases use only their common erase/program/read capabilities. Contradictory sizes or essential program/addressing procedures still require explicit selection with -c/--chip NAME. A synthesized common profile disables write protection when candidates disagree on its register layout. Names use flashprog's pattern syntax but, unlike flashprog, match regardless of case, so type the part number printed on the chip: database names may contain alternatives (A/B), optional parts (GD25Q64(B)) and . wildcards (W25Q128.V matches W25Q128FV):

rflasher probe -p ch341a -c EN25P40

If the selected definition contradicts the chip's SFDP data on size, or on a page size larger than the chip's, rflasher refuses to operate on it. Pick the right definition with --chip, or override with --force if the SFDP data is known to be wrong.

Safety overrides

Authorizations are independent:

  • --allow-dangerous-regions acknowledges modifying ME/TXE/IE, the flash descriptor, or PTT. The shared core preflight detects IFD regions on the device even without --ifd or a supplied layout; if the on-device descriptor is unusable (damaged, or describing a second chip), only this flag lets the operation proceed. Prefer selecting only BIOS instead.
  • --force overrides contradictory chip/SFDP size or page geometry; it does not authorize dangerous regions or execute disputed erase opcodes. Opaque hwseq/MTD devices reject irrelevant --chip/--force options.

Erase blocks can straddle selected region boundaries. The neighboring data is then read first, restored after the erase and verified; erasing into a dangerous or read-only region is refused as above. --recovery-backup FILE additionally writes and synchronizes a full-device image before any mutation (it refuses to overwrite an existing file), which covers power loss in the middle of such an operation. To recover, use it as the input image with dangerous-region authorization if required. Library users supply a RecoveryBackup through MutationPolicy.

The web UI offers chip selection and separate SFDP/region acknowledgements, with one-operation dangerous-region authorization. SFDP-only chips have no documented protection-register layout, so WP mutation is unavailable rather than guessed.

Layouts and regions

Intel Flash Descriptor (IFD) and FMAP layouts let you operate on specific flash regions (BIOS, ME, GbE, ...).

--region/--include accept NAME[:FILE]. With a FILE, each region reads to / writes from its own file instead of a full chip image, and the positional file can be omitted. Per-region files must not exceed their region's size; a smaller file covers only the start of the region. When a positional file is combined with per-region files, it must be a full chip image and supplies the data for regions without their own file.

Read destinations are opened and checked before reading flash, without truncating existing files. If validation or the flash read fails, newly created empty destination files remain in place. Existing regular files are truncated only after the read succeeds; devices and pipes can also be used as destinations.

# Extract a layout from an image
rflasher layout ifd flash.bin -o layout.toml
rflasher layout fmap chromebook.bin -o layout.toml
rflasher layout show layout.toml
rflasher layout create custom.toml --size "16 MiB"

# Read the BIOS region into its own file (IFD parsed from the chip)
rflasher read -p ch341a --ifd -r bios:bios.bin

# Read several regions, each to its own file
rflasher read -p ch341a --ifd --include bios:bios.bin,me:me.bin

# Write a region from its own file
rflasher write -p ch341a --layout layout.toml -r bios:bios_update.bin

# Or write a region out of a full chip image
rflasher write -p ch341a --ifd -r bios full_image.bin

Write protection

rflasher wp status -p ch341a                       # current protection status
rflasher wp list -p ch341a                         # available protection ranges
rflasher wp enable -p ch341a                       # hardware protection (WP# pin)
rflasher wp disable -p ch341a
rflasher wp range -p ch341a 0,0x100000             # protect start,length
rflasher wp region -p ch341a --ifd bios            # protect a named region
rflasher wp disable -p ch341a --temporary          # until the next power cycle only

Changes are persistent by default. --temporary uses the chip's volatile status register write (EWSR, 0x50) and fails on chips without one.

Erase/write operations temporarily remove documented software protection and restore it on success or failure, including Dediprog bulk writes. Hardware and irreversible locks are not bypassed. Unknown/SFDP-only chips never receive guessed status-register writes, and erase verification remains mandatory. Do not interrupt a mutation: power loss or cancellation can prevent restoration.

Automatic unlocking does not imply support for wp configuration. Those commands require an explicitly audited register map, range decoder and write procedure; other chips report unsupported rather than using Winbond's layout. See chip protection procedures for coverage and limitations.

Web interface

A browser-based UI (egui) can drive a programmer straight from the browser: serprog over WebSerial, and CH341A, CH347, FTDI, FT4222H, Dediprog, and Raiden over WebUSB. Both APIs require Chrome/Edge (or Opera); Firefox and Safari support neither, and a secure context (HTTPS or localhost) is mandatory.

rflasher Web Interface

cargo install trunk
rustup target add wasm32-unknown-unknown
cd crates/rflasher-wasm
trunk serve          # dev server with auto-reload on http://localhost:8080
trunk build --release

See crates/rflasher-wasm/README.md for details.

REPL (experimental)

An experimental Steel Scheme REPL can script raw SPI commands (build with --features repl):

cargo build --release --features repl
rflasher repl -p serprog:dev=/dev/ttyACM0
λ > (read-jedec-id)
=> (239 16389)

λ > (bytes->hex (spi-read READ 0 16))
=> "ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff"

λ > (define data (make-bytes 256 #xAA))
λ > (write-enable)
=> #t
λ > (spi-write PP #x1000 data)
=> #t
λ > (wait-ready)
=> #t

Type (rflasher-help) in the REPL for the full command list.

Architecture

Crate Purpose
rflasher-chip-types no_std SPI NOR chip data model and provider trait
rflasher-core no_std SPI protocol, probing, and flash operations (async, runtime-neutral)
rflasher-chips RON loading and compiled chip database provider
rflasher-chips-codegen build-time generator for the compiled chip database
rflasher-programmers feature-gated programmer backends and registry
rflasher-internal internal chipset SPI controllers, no_std-capable
rflasher-pci no_std PCI configuration-space abstraction
rflasher-repl Steel Scheme scripting
rflasher-wasm browser UI (egui, WebSerial, WebUSB)

All operational APIs are async and executor-independent on every target: the CLI blocks once in main, the browser event loop drives the same code over WebUSB/WebSerial. See docs/architecture.md for the design details.

Contributing

Adding a flash chip

  1. Find the datasheet for your chip.
  2. Create or update the vendor file under crates/rflasher-chips/data/vendors/.
  3. Add the chip definition with JEDEC ID, size, erase blocks, and features.
  4. Submit a pull request.
(
    name: "W25Q128.V",
    device_id: 0x4018,
    total_size: MiB(16),
    features: (
        wrsr_wren: true,
        fast_read: true,
        quad_io: true,
    ),
    voltage: (min: 2700, max: 3600),
    erase_blocks: [
        (opcode: 0x20, size: KiB(4)),
        (opcode: 0xD8, size: KiB(64)),
        (opcode: 0xC7, size: MiB(16)),
    ],
    tested: (probe: Ok, read: Ok, erase: Ok, write: Ok, wp: Ok),
)

Adding a programmer

  1. Add a module under crates/rflasher-programmers/src/backends/.
  2. Implement SpiMaster from rflasher-core (overriding read_flash / program_flash if the hardware has a faster flash path), or FlashDevice if it has no raw SPI access.
  3. Add a feature and optional dependencies in crates/rflasher-programmers/Cargo.toml.
  4. Register the programmer in rflasher-programmers/src/registry.rs.
  5. Update the CLI/WASM feature forwarding.

The largest remaining gap is the SPI programmers not yet ported from flashprog.

License

Licensed under either the MIT license or the Apache License, Version 2.0, at your option.

Third-party components retain their own licenses and notices, including the MIT-licensed xfel payloads.

Acknowledgments

rflasher is heavily inspired by flashprog, itself a fork of flashrom, and much of its chip database and programmer support derives from them. Thanks to all contributors of those projects.

About

Blazing fast SPI NOR flash programming

Resources

Stars

21 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages