Skip to content

Commit eae7b16

Browse files
authored
Merge pull request #42 from esp-rs/flash-targets
Allow for per-chip flash logic and implement compression for esp32
2 parents 0700010 + cd3da1a commit eae7b16

File tree

8 files changed

+461
-127
lines changed

8 files changed

+461
-127
lines changed

espflash/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ strum = "0.21.0"
3232
strum_macros = "0.21.1"
3333
csv = "1.1.6"
3434
regex = "1.5.4"
35+
flate2 = "1"
3536

3637
[dev-dependencies]
3738
pretty_assertions = "0.7.1"

espflash/src/chip/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use crate::{
99

1010
use std::{io::Write, str::FromStr};
1111

12+
use crate::flash_target::{Esp32Target, Esp8266Target, FlashTarget, RamTarget};
13+
use crate::flasher::SpiAttachParams;
1214
pub use esp32::Esp32;
1315
pub use esp32c3::Esp32c3;
1416
pub use esp8266::Esp8266;
@@ -139,6 +141,17 @@ impl Chip {
139141
Chip::Esp8266 => Esp8266::SPI_REGISTERS,
140142
}
141143
}
144+
145+
pub fn ram_target(&self) -> Box<dyn FlashTarget> {
146+
Box::new(RamTarget::new())
147+
}
148+
149+
pub fn flash_target(&self, spi_params: SpiAttachParams) -> Box<dyn FlashTarget> {
150+
match self {
151+
Chip::Esp8266 => Box::new(Esp8266Target::new()),
152+
_ => Box::new(Esp32Target::new(*self, spi_params)),
153+
}
154+
}
142155
}
143156

144157
impl FromStr for Chip {

espflash/src/flash_target/esp32.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use crate::connection::Connection;
2+
use crate::elf::{FirmwareImage, RomSegment};
3+
use crate::error::Error;
4+
use crate::flash_target::{begin_command, block_command_with_timeout, FlashTarget};
5+
use crate::flasher::{Command, SpiAttachParams, FLASH_SECTOR_SIZE, FLASH_WRITE_SIZE};
6+
use crate::Chip;
7+
use flate2::write::{ZlibDecoder, ZlibEncoder};
8+
use flate2::Compression;
9+
use indicatif::{ProgressBar, ProgressStyle};
10+
use std::io::Write;
11+
12+
pub struct Esp32Target {
13+
chip: Chip,
14+
spi_attach_params: SpiAttachParams,
15+
}
16+
17+
impl Esp32Target {
18+
pub fn new(chip: Chip, spi_attach_params: SpiAttachParams) -> Self {
19+
Esp32Target {
20+
chip,
21+
spi_attach_params,
22+
}
23+
}
24+
}
25+
26+
impl FlashTarget for Esp32Target {
27+
fn begin(&mut self, connection: &mut Connection, _image: &FirmwareImage) -> Result<(), Error> {
28+
let spi_params = self.spi_attach_params.encode();
29+
connection.with_timeout(Command::SpiAttach.timeout(), |connection| {
30+
connection.command(Command::SpiAttach as u8, spi_params.as_slice(), 0)
31+
})?;
32+
Ok(())
33+
}
34+
35+
fn write_segment(
36+
&mut self,
37+
connection: &mut Connection,
38+
segment: RomSegment,
39+
) -> Result<(), Error> {
40+
let addr = segment.addr;
41+
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::best());
42+
encoder.write_all(&segment.data)?;
43+
let compressed = encoder.finish()?;
44+
let block_count = (compressed.len() + FLASH_WRITE_SIZE - 1) / FLASH_WRITE_SIZE;
45+
let erase_count = (segment.data.len() + FLASH_SECTOR_SIZE - 1) / FLASH_SECTOR_SIZE;
46+
47+
// round up to sector size
48+
let erase_size = (erase_count * FLASH_SECTOR_SIZE) as u32;
49+
50+
begin_command(
51+
connection,
52+
Command::FlashDeflateBegin,
53+
erase_size,
54+
block_count as u32,
55+
FLASH_WRITE_SIZE as u32,
56+
addr,
57+
self.chip != Chip::Esp32,
58+
)?;
59+
60+
let chunks = compressed.chunks(FLASH_WRITE_SIZE);
61+
62+
let (_, chunk_size) = chunks.size_hint();
63+
let chunk_size = chunk_size.unwrap_or(0) as u64;
64+
let pb_chunk = ProgressBar::new(chunk_size);
65+
pb_chunk.set_style(
66+
ProgressStyle::default_bar()
67+
.template("[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}")
68+
.progress_chars("#>-"),
69+
);
70+
71+
// decode the chunks to see how much data the device will have to save
72+
let mut decoder = ZlibDecoder::new(Vec::new());
73+
let mut decoded_size = 0;
74+
75+
for (i, block) in chunks.enumerate() {
76+
decoder.write_all(block)?;
77+
decoder.flush()?;
78+
let size = decoder.get_ref().len() - decoded_size;
79+
decoded_size = decoder.get_ref().len();
80+
81+
pb_chunk.set_message(format!("segment 0x{:X} writing chunks", addr));
82+
block_command_with_timeout(
83+
connection,
84+
Command::FlashDeflateData,
85+
block,
86+
0,
87+
0xff,
88+
i as u32,
89+
Command::FlashDeflateData.timeout_for_size(size as u32),
90+
)?;
91+
pb_chunk.inc(1);
92+
}
93+
94+
pb_chunk.finish_with_message(format!("segment 0x{:X}", addr));
95+
96+
Ok(())
97+
}
98+
99+
fn finish(&mut self, connection: &mut Connection, reboot: bool) -> Result<(), Error> {
100+
connection.with_timeout(Command::FlashDeflateEnd.timeout(), |connection| {
101+
connection.write_command(Command::FlashDeflateEnd as u8, &[1][..], 0)
102+
})?;
103+
if reboot {
104+
connection.reset()
105+
} else {
106+
Ok(())
107+
}
108+
}
109+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use crate::connection::Connection;
2+
use crate::elf::{FirmwareImage, RomSegment};
3+
use crate::error::Error;
4+
use crate::flash_target::{begin_command, block_command, FlashTarget};
5+
use crate::flasher::{get_erase_size, Command, FLASH_WRITE_SIZE};
6+
use indicatif::{ProgressBar, ProgressStyle};
7+
8+
pub struct Esp8266Target;
9+
10+
impl Esp8266Target {
11+
pub fn new() -> Self {
12+
Esp8266Target
13+
}
14+
}
15+
16+
impl FlashTarget for Esp8266Target {
17+
fn begin(&mut self, connection: &mut Connection, _image: &FirmwareImage) -> Result<(), Error> {
18+
begin_command(
19+
connection,
20+
Command::FlashBegin,
21+
0,
22+
0,
23+
FLASH_WRITE_SIZE as u32,
24+
0,
25+
false,
26+
)
27+
}
28+
29+
fn write_segment(
30+
&mut self,
31+
connection: &mut Connection,
32+
segment: RomSegment,
33+
) -> Result<(), Error> {
34+
let addr = segment.addr;
35+
let block_count = (segment.data.len() + FLASH_WRITE_SIZE - 1) / FLASH_WRITE_SIZE;
36+
37+
let erase_size = get_erase_size(addr as usize, segment.data.len()) as u32;
38+
39+
begin_command(
40+
connection,
41+
Command::FlashBegin,
42+
erase_size,
43+
block_count as u32,
44+
FLASH_WRITE_SIZE as u32,
45+
addr,
46+
false,
47+
)?;
48+
49+
let chunks = segment.data.chunks(FLASH_WRITE_SIZE);
50+
51+
let (_, chunk_size) = chunks.size_hint();
52+
let chunk_size = chunk_size.unwrap_or(0) as u64;
53+
let pb_chunk = ProgressBar::new(chunk_size);
54+
pb_chunk.set_style(
55+
ProgressStyle::default_bar()
56+
.template("[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}")
57+
.progress_chars("#>-"),
58+
);
59+
60+
for (i, block) in chunks.enumerate() {
61+
pb_chunk.set_message(format!("segment 0x{:X} writing chunks", addr));
62+
let block_padding = FLASH_WRITE_SIZE - block.len();
63+
block_command(
64+
connection,
65+
Command::FlashData,
66+
block,
67+
block_padding,
68+
0xff,
69+
i as u32,
70+
)?;
71+
pb_chunk.inc(1);
72+
}
73+
74+
pb_chunk.finish_with_message(format!("segment 0x{:X}", addr));
75+
76+
Ok(())
77+
}
78+
79+
fn finish(&mut self, connection: &mut Connection, reboot: bool) -> Result<(), Error> {
80+
connection.with_timeout(Command::FlashEnd.timeout(), |connection| {
81+
connection.write_command(Command::FlashEnd as u8, &[1][..], 0)
82+
})?;
83+
if reboot {
84+
connection.reset()
85+
} else {
86+
Ok(())
87+
}
88+
}
89+
}

espflash/src/flash_target/mod.rs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
mod esp32;
2+
mod esp8266;
3+
mod ram;
4+
5+
use crate::connection::Connection;
6+
use crate::elf::{FirmwareImage, RomSegment};
7+
use crate::error::Error;
8+
use crate::flasher::{checksum, Command, Encoder, CHECKSUM_INIT, FLASH_WRITE_SIZE};
9+
use bytemuck::{bytes_of, Pod, Zeroable};
10+
pub use esp32::Esp32Target;
11+
pub use esp8266::Esp8266Target;
12+
pub use ram::RamTarget;
13+
use std::mem::size_of;
14+
use std::time::Duration;
15+
16+
pub trait FlashTarget {
17+
fn begin(&mut self, connection: &mut Connection, image: &FirmwareImage) -> Result<(), Error>;
18+
fn write_segment(
19+
&mut self,
20+
connection: &mut Connection,
21+
segment: RomSegment,
22+
) -> Result<(), Error>;
23+
fn finish(&mut self, connection: &mut Connection, reboot: bool) -> Result<(), Error>;
24+
}
25+
26+
#[derive(Zeroable, Pod, Copy, Clone, Debug)]
27+
#[repr(C)]
28+
struct BeginParams {
29+
size: u32,
30+
blocks: u32,
31+
block_size: u32,
32+
offset: u32,
33+
encrypted: u32,
34+
}
35+
36+
fn begin_command(
37+
connection: &mut Connection,
38+
command: Command,
39+
size: u32,
40+
blocks: u32,
41+
block_size: u32,
42+
offset: u32,
43+
supports_encrypted: bool,
44+
) -> Result<(), Error> {
45+
let params = BeginParams {
46+
size,
47+
blocks,
48+
block_size,
49+
offset,
50+
encrypted: 0,
51+
};
52+
53+
let bytes = bytes_of(&params);
54+
let data = if !supports_encrypted {
55+
// The ESP32 and ESP8266 do not take the `encrypted` field, so truncate the last
56+
// 4 bytes of the slice where it resides.
57+
let end = bytes.len() - 4;
58+
&bytes[0..end]
59+
} else {
60+
bytes
61+
};
62+
63+
connection.with_timeout(command.timeout_for_size(size), |connection| {
64+
connection.command(command as u8, data, 0)?;
65+
Ok(())
66+
})
67+
}
68+
69+
#[derive(Zeroable, Pod, Copy, Clone, Debug)]
70+
#[repr(C)]
71+
struct BlockParams {
72+
size: u32,
73+
sequence: u32,
74+
dummy1: u32,
75+
dummy2: u32,
76+
}
77+
78+
fn block_command(
79+
connection: &mut Connection,
80+
command: Command,
81+
data: &[u8],
82+
padding: usize,
83+
padding_byte: u8,
84+
sequence: u32,
85+
) -> Result<(), Error> {
86+
block_command_with_timeout(
87+
connection,
88+
command,
89+
data,
90+
padding,
91+
padding_byte,
92+
sequence,
93+
command.timeout_for_size(data.len() as u32),
94+
)
95+
}
96+
97+
fn block_command_with_timeout(
98+
connection: &mut Connection,
99+
command: Command,
100+
data: &[u8],
101+
padding: usize,
102+
padding_byte: u8,
103+
sequence: u32,
104+
timout: Duration,
105+
) -> Result<(), Error> {
106+
let params = BlockParams {
107+
size: (data.len() + padding) as u32,
108+
sequence,
109+
dummy1: 0,
110+
dummy2: 0,
111+
};
112+
113+
let length = size_of::<BlockParams>() + data.len() + padding;
114+
115+
let mut check = checksum(data, CHECKSUM_INIT);
116+
117+
for _ in 0..padding {
118+
check = checksum(&[padding_byte], check);
119+
}
120+
121+
connection.with_timeout(timout, |connection| {
122+
connection.command(
123+
command as u8,
124+
(length as u16, |encoder: &mut Encoder| {
125+
encoder.write(bytes_of(&params))?;
126+
encoder.write(data)?;
127+
let padding = &[padding_byte; FLASH_WRITE_SIZE][0..padding];
128+
encoder.write(padding)?;
129+
Ok(())
130+
}),
131+
check as u32,
132+
)?;
133+
Ok(())
134+
})
135+
}

0 commit comments

Comments
 (0)