Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ members = [
"crates/rflasher-linux-gpio",
"crates/rflasher-dummy",
"crates/rflasher-raiden",
"crates/rflasher-sunxi-fel",
"crates/rflasher-repl",
"crates/rflasher-wasm",
]
Expand All @@ -37,6 +38,7 @@ default-members = [
"crates/rflasher-linux-gpio",
"crates/rflasher-dummy",
"crates/rflasher-raiden",
"crates/rflasher-sunxi-fel",
"crates/rflasher-repl",
]

Expand Down Expand Up @@ -85,7 +87,7 @@ edition.workspace = true
license.workspace = true

[features]
default = ["dummy", "ch341a", "ch347", "dediprog", "serprog", "ft4222", "ftdi", "linux-spi", "linux-mtd", "internal", "raiden"]
default = ["dummy", "ch341a", "ch347", "dediprog", "serprog", "ft4222", "ftdi", "linux-spi", "linux-mtd", "internal", "raiden", "sunxi-fel"]

# Programmer features (passed through to rflasher-flash)
dummy = ["rflasher-flash/dummy"]
Expand All @@ -102,14 +104,15 @@ linux-mtd = ["rflasher-flash/linux-mtd"]
linux-gpio = ["rflasher-flash/linux-gpio"]
internal = ["rflasher-flash/internal"]
raiden = ["rflasher-flash/raiden"]
sunxi-fel = ["rflasher-flash/sunxi-fel"]

# REPL feature for scripting with Steel Scheme
repl = ["dep:rflasher-repl"]

# Enable all programmers
all-programmers = ["dummy", "ch341a", "ch347", "dediprog", "serprog", "ftdi", "ft4222", "linux-spi", "linux-mtd", "linux-gpio", "internal", "raiden"]
all-programmers = ["dummy", "ch341a", "ch347", "dediprog", "serprog", "ftdi", "ft4222", "linux-spi", "linux-mtd", "linux-gpio", "internal", "raiden", "sunxi-fel"]
# Enable all programmers with pure-Rust FTDI backend
all-programmers-native = ["dummy", "ch341a", "ch347", "dediprog", "serprog", "ftdi-native", "ft4222", "linux-spi", "linux-mtd", "linux-gpio", "internal", "raiden"]
all-programmers-native = ["dummy", "ch341a", "ch347", "dediprog", "serprog", "ftdi-native", "ft4222", "linux-spi", "linux-mtd", "linux-gpio", "internal", "raiden", "sunxi-fel"]

[dependencies]
rflasher-core = { workspace = true, features = ["std", "is_sync"] }
Expand Down
24 changes: 21 additions & 3 deletions crates/rflasher-core/src/flash/hybrid_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,15 +175,33 @@ impl<M: SpiMaster + OpaqueMaster> FlashDevice for HybridFlashDevice<M> {
}

// =========================================================================
// Erase: use SpiMaster (no bulk erase command on Dediprog)
// Erase: try OpaqueMaster first, fall back to SpiMaster
//
// Following flashprog's architecture: erase is a first-class operation
// on the opaque interface. Programmers with firmware-accelerated erase
// (e.g., SPI_CMD_SPINOR_WAIT) implement OpaqueMaster::erase(). Those
// without (e.g., Dediprog) return Err, triggering the SPI fallback.
// =========================================================================

async fn erase(&mut self, addr: u32, len: u32) -> Result<()> {
let ctx = self.context();
if !ctx.is_valid_range(addr, len as usize) {
// Bounds check (borrow ctx briefly, then drop before mutable borrow)
if !self.context().is_valid_range(addr, len as usize) {
return Err(Error::AddressOutOfBounds);
}

// Try opaque erase first — the programmer handles everything internally
// (block selection, busy-wait, etc.). If it returns Ok, we're done.
// Programmers without firmware erase (e.g., Dediprog) return Err,
// triggering the SPI fallback below.
if OpaqueMaster::erase(&mut self.master, addr, len)
.await
.is_ok()
{
return Ok(());
}

// Opaque erase not supported — fall back to SPI-based erase
let ctx = self.context();
let erase_block = select_erase_block(ctx.chip.erase_blocks(), addr, len)
.ok_or(Error::InvalidAlignment)?;

Expand Down
2 changes: 1 addition & 1 deletion crates/rflasher-core/src/flash/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub use spi_device::SpiFlashDevice;

// Re-export low-level SPI operations (work with SpiMaster directly)
// For high-level operations that work with any FlashDevice, use the `unified` module
pub use operations::{read, write};
pub use operations::{read, select_erase_block, write};

// Re-export detailed probe result
#[cfg(feature = "std")]
Expand Down
2 changes: 2 additions & 0 deletions crates/rflasher-flash/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ rflasher-linux-mtd = { path = "../rflasher-linux-mtd", optional = true }
rflasher-linux-gpio = { path = "../rflasher-linux-gpio", optional = true }
rflasher-internal = { path = "../rflasher-internal", optional = true }
rflasher-raiden = { path = "../rflasher-raiden", optional = true }
rflasher-sunxi-fel = { path = "../rflasher-sunxi-fel", optional = true }

[features]
default = ["std", "is_sync"]
Expand All @@ -43,3 +44,4 @@ linux-mtd = ["dep:rflasher-linux-mtd"]
linux-gpio = ["dep:rflasher-linux-gpio"]
internal = ["dep:rflasher-internal"]
raiden = ["dep:rflasher-raiden"]
sunxi-fel = ["dep:rflasher-sunxi-fel"]
57 changes: 57 additions & 0 deletions crates/rflasher-flash/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,18 @@ pub fn open_spi_programmer(programmer: &str) -> Result<BoxedSpiMaster, Box<dyn s
Ok(Box::new(master))
}

#[cfg(feature = "sunxi-fel")]
"sunxi_fel" | "sunxi-fel" | "fel" => {
log::info!("Opening sunxi FEL programmer for REPL...");
let master = rflasher_sunxi_fel::SunxiFel::open().map_err(|e| {
format!(
"Failed to open sunxi FEL device: {}\nMake sure the device is in FEL mode and you have USB permissions.",
e
)
})?;
Ok(Box::new(master))
}

// Internal and MTD are opaque-only or not SPI-based
#[cfg(feature = "internal")]
"internal" => {
Expand Down Expand Up @@ -452,6 +464,9 @@ pub fn open_flash(
#[cfg(feature = "raiden")]
"raiden_debug_spi" | "raiden" | "raiden_spi" => open_raiden(&params, db),

#[cfg(feature = "sunxi-fel")]
"sunxi_fel" | "sunxi-fel" | "fel" => open_sunxi_fel(&params, db),

_ => Err(format!("Unknown programmer: {}", params.name).into()),
}
}
Expand Down Expand Up @@ -864,6 +879,41 @@ fn open_raiden(
probe_and_create_handle(master, db)
}

#[cfg(feature = "sunxi-fel")]
fn open_sunxi_fel(
_params: &ProgrammerParams,
db: &ChipDatabase,
) -> Result<FlashHandle, Box<dyn std::error::Error>> {
log::info!("Opening sunxi FEL programmer...");

let mut master = rflasher_sunxi_fel::SunxiFel::open().map_err(|e| {
format!(
"Failed to open sunxi FEL device: {}\n\
Make sure the device is in FEL mode (hold FEL button while plugging in USB)\n\
and you have USB permissions (VID:1F3A PID:EFE8).",
e
)
})?;

log::info!("Connected to: {}", master.soc_name());

// Probe the flash chip via SpiMaster
let result = probe_detailed(&mut master, db)?;
log_probe_result(&result);
let chip_info = ChipInfo::from(result);
let ctx = rflasher_core::flash::FlashContext::new(chip_info.chip.clone().unwrap());

// Configure OpaqueMaster with chip info discovered during probe
master.set_use_4byte_addr(ctx.total_size() > 16 * 1024 * 1024);
master.set_erase_blocks(ctx.chip.erase_blocks().to_vec());

// Use HybridFlashDevice: OpaqueMaster for fast bulk read/write/erase
// (batched SPI commands with on-SoC busy-wait), SpiMaster for WP and
// status register access
let device = HybridFlashDevice::new(master, ctx);
Ok(FlashHandle::with_chip_info(Box::new(device), chip_info))
}

// Programmer information and listing
/// Information about a programmer
pub struct ProgrammerInfo {
Expand Down Expand Up @@ -966,6 +1016,13 @@ pub fn available_programmers() -> Vec<ProgrammerInfo> {
description: "Chrome OS EC USB SPI (serial=<sn>,target=<ap|ec|h1>)",
});

#[cfg(feature = "sunxi-fel")]
programmers.push(ProgrammerInfo {
name: "sunxi_fel",
aliases: &["sunxi-fel", "fel"],
description: "Allwinner sunxi FEL USB SPI NOR programmer (VID:1F3A PID:EFE8)",
});

programmers
}

Expand Down
17 changes: 17 additions & 0 deletions crates/rflasher-sunxi-fel/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "rflasher-sunxi-fel"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "Allwinner sunxi FEL SPI NOR programmer support for rflasher"

[features]
default = ["std"]
std = ["rflasher-core/std", "is_sync", "nusb"]
is_sync = ["rflasher-core/is_sync", "maybe-async/is_sync"]

[dependencies]
rflasher-core.workspace = true
nusb = { workspace = true, optional = true }
maybe-async.workspace = true
log.workspace = true
Loading
Loading