Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enable support for loader stubs (via --use-stub arg) #216

Merged
merged 9 commits into from
Aug 17, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions espflash/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ bin-dir = "{ bin }{ binary-ext }"
pkg-fmt = "zip"

[dependencies]
base64 = "0.13.0"
binread = "2.2"
bytemuck = { version = "1.11", features = ["derive"] }
clap = { version = "3.2", features = ["derive"] }
Expand All @@ -47,6 +48,7 @@ parse_int = "0.6"
regex = "1.6"
serde = { version = "1.0", features = ["derive"] }
serde-hex = "0.1"
serde_json = "1.0.83"
serde_plain = "1.0"
serialport = "4.2"
sha2 = "0.10"
Expand Down
19 changes: 13 additions & 6 deletions espflash/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,21 @@ pub mod monitor;

mod serial;

#[derive(Parser)]
#[derive(Parser, Debug)]
pub struct ConnectOpts {
/// Serial port connected to target device
pub serial: Option<String>,

/// Baud rate at which to flash target device
#[clap(long)]
pub speed: Option<u32>,

/// Use RAM stub for loading
#[clap(long)]
pub use_stub: bool,
}

#[derive(Parser)]
#[derive(Parser, Debug)]
pub struct FlashOpts {
/// Load the application to RAM instead of Flash
#[clap(long)]
Expand All @@ -56,7 +61,7 @@ pub struct FlashOpts {
pub monitor: bool,
}

#[derive(Parser)]
#[derive(Parser, Debug)]
pub struct FlashConfigOpts {
/// Flash mode to use
#[clap(short = 'm', long, possible_values = FlashMode::VARIANTS, value_name = "MODE")]
Expand All @@ -69,7 +74,7 @@ pub struct FlashConfigOpts {
pub flash_freq: Option<FlashFrequency>,
}

#[derive(Parser)]
#[derive(Parser, Debug)]
pub struct PartitionTableOpts {
/// Convert CSV parition table to binary representation
#[clap(long, required_unless_present_any = ["info", "to-csv"])]
Expand All @@ -87,13 +92,15 @@ pub struct PartitionTableOpts {
output: Option<PathBuf>,
}

#[derive(Parser)]
#[derive(Parser, Debug)]
pub struct WriteBinToFlashOpts {
/// Address at which to write the binary file
#[clap(value_parser = parse_u32)]
addr: u32,

/// File containing the binary data to write
bin_file: String,

#[clap(flatten)]
connect_opts: ConnectOpts,
}
Expand Down Expand Up @@ -131,7 +138,7 @@ pub fn connect(opts: &ConnectOpts, config: &Config) -> Result<Flasher> {
_ => unreachable!(),
};

Ok(Flasher::connect(serial, port_info, opts.speed)?)
Ok(Flasher::connect(serial, port_info, opts.speed, opts.use_stub)?)
}

pub fn board_info(opts: ConnectOpts, config: Config) -> Result<()> {
Expand Down
4 changes: 2 additions & 2 deletions espflash/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ impl Connection {
Err(Error::Connection(ConnectionError::ConnectionFailed))
}

fn sync(&mut self) -> Result<(), Error> {
pub(crate) fn sync(&mut self) -> Result<(), Error> {
self.with_timeout(CommandType::Sync.timeout(), |connection| {
connection.write_command(Command::Sync)?;
connection.flush()?;
Expand Down Expand Up @@ -254,7 +254,7 @@ impl Connection {
Ok(())
}

fn read(&mut self, len: usize) -> Result<Option<Vec<u8>>, Error> {
pub(crate) fn read(&mut self, len: usize) -> Result<Option<Vec<u8>>, Error> {
let mut tmp = Vec::with_capacity(1024);
loop {
self.decoder.decode(&mut self.serial, &mut tmp)?;
Expand Down
2 changes: 1 addition & 1 deletion espflash/src/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::{

pub const ESP_CHECKSUM_MAGIC: u8 = 0xef;

#[derive(Copy, Clone, EnumVariantNames)]
#[derive(Copy, Clone, Debug, EnumVariantNames)]
#[strum(serialize_all = "UPPERCASE")]
pub enum FlashMode {
Qio,
Expand Down
66 changes: 64 additions & 2 deletions espflash/src/flasher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
elf::{ElfFirmwareImage, FirmwareImage, FlashFrequency, FlashMode, RomSegment},
error::{ConnectionError, FlashDetectError, ResultExt},
image_format::ImageFormatId,
Error, PartitionTable,
Error, PartitionTable, stubs::FlashStub,
};

const DEFAULT_TIMEOUT: Duration = Duration::from_secs(3);
Expand Down Expand Up @@ -192,6 +192,7 @@ impl Flasher {
serial: Box<dyn SerialPort>,
port_info: UsbPortInfo,
speed: Option<u32>,
use_stub: bool,
) -> Result<Self, Error> {
// Establish a connection to the device using the default baud rate of 115,200
// and timeout of 3 seconds.
Expand All @@ -209,6 +210,13 @@ impl Flasher {
flash_size: FlashSize::Flash4Mb,
spi_params: SpiAttachParams::default(),
};

// Load flash stub if enabled
if use_stub {
println!("Using flash stub");
flasher.load_stub()?;
}

flasher.spi_autodetect()?;

// Now that we have established a connection and detected the chip and flash
Expand All @@ -228,11 +236,65 @@ impl Flasher {
Ok(flasher)
}

/// Load flash stub
fn load_stub(&mut self) -> Result<(), Error> {

println!("Loading flash stub for chip: {:?}", self.chip);

// Load flash stub
let stub = FlashStub::get(self.chip).unwrap();

let mut ram_target = self.chip.ram_target(Some(stub.entry()));
ram_target.begin(&mut self.connection).flashing()?;

let (text_addr, text) = stub.text();
println!("Write {} byte stub text", text.len());

ram_target.write_segment(
&mut self.connection,
RomSegment {
addr: text_addr,
data: Cow::Borrowed(&text),
},
)
.flashing()?;


let (data_addr, data) = stub.data();
println!("Write {} byte stub data", data.len());

ram_target.write_segment(
&mut self.connection,
RomSegment {
addr: data_addr,
data: Cow::Borrowed(&data),
},
)
.flashing()?;

println!("Finish stub write");
ram_target.finish(&mut self.connection, true).flashing()?;

println!("Stub written...");

// Re-sync connection
self.connection.sync()?;

// Re-detect chip to check stub is up
let magic = self.connection.read_reg(CHIP_DETECT_MAGIC_REG_ADDR)?;
let chip = Chip::from_magic(magic)?;
println!("Re-detected chip: {:?}", chip);

Ok(())
}

fn spi_autodetect(&mut self) -> Result<(), Error> {
// Loop over all available SPI parameters until we find one that successfully
// reads the flash size.
for spi_params in TRY_SPI_PARAMS.iter().copied() {
self.enable_flash(spi_params)?;
if let Err(_e) = self.enable_flash(spi_params) {
continue;
}
if let Some(flash_size) = self.flash_detect()? {
// Flash detection was successful, so save the flash size and SPI parameters and
// return.
Expand Down
2 changes: 2 additions & 0 deletions espflash/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ pub mod partition_table;

#[doc(hidden)]
pub mod cli;

pub mod stubs;
11 changes: 8 additions & 3 deletions espflash/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,30 @@ use espflash::{
use miette::{IntoDiagnostic, Result, WrapErr};
use strum::VariantNames;

#[derive(Parser)]
#[derive(Debug, Parser)]
#[clap(version, propagate_version = true)]
struct Opts {
/// Image format to flash
#[clap(long, possible_values = &["bootloader", "direct-boot"])]
pub format: Option<String>,

#[clap(flatten)]
pub flash_config_opts: FlashConfigOpts,

#[clap(flatten)]
flash_opts: FlashOpts,

#[clap(flatten)]
connect_opts: ConnectOpts,

/// ELF image to flash
image: Option<String>,

#[clap(subcommand)]
subcommand: Option<SubCommand>,
}

#[derive(Parser)]
#[derive(Debug, Parser)]
pub enum SubCommand {
/// Display information about the connected board and exit without flashing
BoardInfo(ConnectOpts),
Expand All @@ -44,7 +49,7 @@ pub enum SubCommand {
WriteBinToFlash(WriteBinToFlashOpts),
}

#[derive(Parser)]
#[derive(Debug, Parser)]
pub struct SaveImageOpts {
#[clap(flatten)]
pub flash_config_opts: FlashConfigOpts,
Expand Down
56 changes: 56 additions & 0 deletions espflash/src/stubs/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use serde::{Serialize, Deserialize};

use crate::Chip;


#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub struct FlashStub {
entry: u32,
text: String,
text_start: u32,
data: String,
data_start: u32,
}

const STUB_32: &str = include_str!("../../../stubs/stub_flasher_32.json");
const STUB_32C2: &str = include_str!("../../../stubs/stub_flasher_32c2.json");
const STUB_32C3: &str = include_str!("../../../stubs/stub_flasher_32c3.json");
const STUB_32S2: &str = include_str!("../../../stubs/stub_flasher_32s2.json");
const STUB_32S3: &str = include_str!("../../../stubs/stub_flasher_32s3.json");
const STUB_8266: &str = include_str!("../../../stubs/stub_flasher_8266.json");

impl FlashStub {
/// Fetch flash stub for the provided chip
pub fn get(chip: Chip) -> Result<FlashStub, ()> {
let s = match chip {
Chip::Esp32 => STUB_32,
Chip::Esp32c2 => STUB_32C2,
Chip::Esp32c3 => STUB_32C3,
Chip::Esp32s2 => STUB_32S2,
Chip::Esp32s3 => STUB_32S3,
Chip::Esp8266 => STUB_8266,
};

let stub: FlashStub = serde_json::from_str(s)
.map_err(|_| () )?;

Ok(stub)
}

/// Fetch stub entry point
pub fn entry(&self) -> u32 {
self.entry
}

/// Fetch text start address and bytes
pub fn text(&self) -> (u32, Vec<u8>) {
let v = base64::decode(&self.text).unwrap();
(self.text_start, v)
}

/// Fetch data start address and bytes
pub fn data(&self) -> (u32, Vec<u8>) {
let v = base64::decode(&self.data).unwrap();
(self.data_start, v)
}
}
2 changes: 2 additions & 0 deletions stubs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

Flasher stubs from https://github.com/espressif/esptool/tree/master/esptool/targets/stub_flasher
igrr marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 7 additions & 0 deletions stubs/stub_flasher_32.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"entry": 1074521516,
"text": "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",
"text_start": 1074520064,
"data": "CMD8Pw==",
"data_start": 1073605544
}
7 changes: 7 additions & 0 deletions stubs/stub_flasher_32c2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"entry": 1077413328,
"text": "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",
"text_start": 1077411840,
"data": "DEDKPw==",
"data_start": 1070295976
}
Loading