probe_rs/flashing/
download.rs

1use espflash::flasher::{FlashFrequency, FlashMode};
2use object::{
3    Endianness, Object, ObjectSection, elf::FileHeader32, elf::FileHeader64, elf::PT_LOAD,
4    read::elf::ElfFile, read::elf::FileHeader, read::elf::ProgramHeader,
5};
6use probe_rs_target::{InstructionSet, MemoryRange};
7use serde::{Deserialize, Serialize};
8
9use std::{
10    fs::File,
11    path::{Path, PathBuf},
12    str::FromStr,
13};
14
15use super::*;
16use crate::session::Session;
17
18/// Extended options for flashing a binary file.
19#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
20pub struct BinOptions {
21    /// The address in memory where the binary will be put at.
22    pub base_address: Option<u64>,
23    /// The number of bytes to skip at the start of the binary file.
24    pub skip: u32,
25}
26
27/// Extended options for flashing a ESP-IDF format file.
28#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
29pub struct IdfOptions {
30    /// The bootloader
31    pub bootloader: Option<PathBuf>,
32    /// The partition table
33    pub partition_table: Option<PathBuf>,
34    /// The target app partition
35    pub target_app_partition: Option<String>,
36    /// Flash SPI mode
37    pub flash_mode: Option<FlashMode>,
38    /// Flash SPI frequency
39    pub flash_frequency: Option<FlashFrequency>,
40}
41
42/// Extended options for flashing an ELF file.
43#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
44pub struct ElfOptions {
45    /// Sections to skip flashing
46    pub skip_sections: Vec<String>,
47}
48
49/// A finite list of all the available binary formats probe-rs understands.
50#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
51pub enum FormatKind {
52    /// Marks a file in binary format. This means that the file contains the contents of the flash 1:1.
53    /// [BinOptions] can be used to define the location in flash where the file contents should be put at.
54    /// Additionally using the same config struct, you can skip the first N bytes of the binary file to have them not put into the flash.
55    Bin,
56    /// Marks a file in [Intel HEX](https://en.wikipedia.org/wiki/Intel_HEX) format.
57    Hex,
58    /// Marks a file in the [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) format.
59    #[default]
60    Elf,
61    /// Marks a file in the [ESP-IDF bootloader](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/app_image_format.html#app-image-structures) format.
62    /// Use [IdfOptions] to configure flashing.
63    Idf,
64    /// Marks a file in the [UF2](https://github.com/microsoft/uf2) format.
65    Uf2,
66}
67
68impl FormatKind {
69    /// Creates a new Format from an optional string.
70    ///
71    /// If the string is `None`, the default format is returned.
72    pub fn from_optional(s: Option<&str>) -> Result<Self, String> {
73        match s {
74            Some(format) => Self::from_str(format),
75            None => Ok(Self::default()),
76        }
77    }
78}
79
80impl FromStr for FormatKind {
81    type Err = String;
82
83    fn from_str(s: &str) -> Result<Self, Self::Err> {
84        match &s.to_lowercase()[..] {
85            "bin" | "binary" => Ok(Self::Bin),
86            "hex" | "ihex" | "intelhex" => Ok(Self::Hex),
87            "elf" => Ok(Self::Elf),
88            "uf2" => Ok(Self::Uf2),
89            "idf" | "esp-idf" | "espidf" => Ok(Self::Idf),
90            _ => Err(format!("Format '{s}' is unknown.")),
91        }
92    }
93}
94
95/// A finite list of all the available binary formats probe-rs understands.
96#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
97pub enum Format {
98    /// Marks a file in binary format. This means that the file contains the contents of the flash 1:1.
99    /// [BinOptions] can be used to define the location in flash where the file contents should be put at.
100    /// Additionally using the same config struct, you can skip the first N bytes of the binary file to have them not put into the flash.
101    Bin(BinOptions),
102    /// Marks a file in [Intel HEX](https://en.wikipedia.org/wiki/Intel_HEX) format.
103    Hex,
104    /// Marks a file in the [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) format.
105    Elf(ElfOptions),
106    /// Marks a file in the [ESP-IDF bootloader](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/app_image_format.html#app-image-structures) format.
107    /// Use [IdfOptions] to configure flashing.
108    Idf(IdfOptions),
109    /// Marks a file in the [UF2](https://github.com/microsoft/uf2) format.
110    Uf2,
111}
112
113impl Default for Format {
114    fn default() -> Self {
115        Format::Elf(ElfOptions::default())
116    }
117}
118
119impl From<FormatKind> for Format {
120    fn from(kind: FormatKind) -> Self {
121        match kind {
122            FormatKind::Bin => Format::Bin(BinOptions::default()),
123            FormatKind::Hex => Format::Hex,
124            FormatKind::Elf => Format::Elf(ElfOptions::default()),
125            FormatKind::Uf2 => Format::Uf2,
126            FormatKind::Idf => Format::Idf(IdfOptions::default()),
127        }
128    }
129}
130
131/// A finite list of all the errors that can occur when flashing a given file.
132///
133/// This includes corrupt file issues,
134/// OS permission issues as well as chip connectivity and memory boundary issues.
135#[derive(Debug, thiserror::Error, docsplay::Display)]
136pub enum FileDownloadError {
137    /// An error with the flashing procedure has occurred.
138    #[ignore_extra_doc_attributes]
139    ///
140    /// This is mostly an error in the communication with the target inflicted by a bad hardware connection or a probe-rs bug.
141    Flash(#[from] FlashError),
142
143    /// Failed to read or decode the IHEX file.
144    IhexRead(#[from] ihex::ReaderError),
145
146    /// An IO error has occurred while reading the firmware file.
147    IO(#[from] std::io::Error),
148
149    /// Error while reading the object file: {0}.
150    Object(&'static str),
151
152    /// Failed to read or decode the ELF file.
153    Elf(#[from] object::read::Error),
154
155    /// Failed to format as esp-idf binary
156    Idf(#[from] espflash::Error),
157
158    /// Target {0} does not support the esp-idf format
159    IdfUnsupported(String),
160
161    /// No loadable segments were found in the ELF file.
162    #[ignore_extra_doc_attributes]
163    ///
164    /// This is most likely because of a bad linker script.
165    NoLoadableSegments,
166
167    /// Could not determine flash size.
168    FlashSizeDetection(#[source] FlashError),
169
170    /// The image ({image:?}) is not compatible with the target ({print_instr_sets(target)}).
171    IncompatibleImage {
172        /// The target's instruction set.
173        target: Vec<InstructionSet>,
174        /// The image's instruction set.
175        image: InstructionSet,
176    },
177
178    /// The target chip {target} is not compatible with the image. The image is compatible with: {image_chips.join(", ")}
179    IncompatibleImageChip {
180        /// The target chip.
181        target: String,
182        /// The chips compatible with the image.
183        image_chips: Vec<String>,
184    },
185
186    /// An error occurred during download.
187    Other(#[source] crate::Error),
188}
189
190fn print_instr_sets(instr_sets: &[InstructionSet]) -> String {
191    instr_sets
192        .iter()
193        .map(|instr_set| format!("{instr_set:?}"))
194        .collect::<Vec<_>>()
195        .join(", ")
196}
197
198/// Options for downloading a file onto a target chip.
199///
200/// This struct should be created using the [`DownloadOptions::default()`] function, and can be configured by setting
201/// the fields directly:
202///
203/// ```
204/// use probe_rs::flashing::DownloadOptions;
205///
206/// let mut options = DownloadOptions::default();
207///
208/// options.verify = true;
209/// ```
210#[derive(Default)]
211#[non_exhaustive]
212pub struct DownloadOptions<'p> {
213    /// An optional progress reporter which is used if this argument is set to `Some(...)`.
214    pub progress: FlashProgress<'p>,
215    /// If `keep_unwritten_bytes` is `true`, erased portions of the flash that are not overwritten by the ELF data
216    /// are restored afterwards, such that the old contents are untouched.
217    ///
218    /// This is necessary because the flash can only be erased in sectors. If only parts of the erased sector are written thereafter,
219    /// instead of the full sector, the excessively erased bytes wont match the contents before the erase which might not be intuitive
220    /// to the user or even worse, result in unexpected behavior if those contents contain important data.
221    pub keep_unwritten_bytes: bool,
222    /// Perform a dry run. This prepares everything for flashing, but does not write anything to flash.
223    pub dry_run: bool,
224    /// If this flag is set to true, probe-rs will try to use the chips built in method to do a full chip erase if one is available.
225    /// This is often faster than erasing a lot of single sectors.
226    /// So if you do not need the old contents of the flash, this is a good option.
227    pub do_chip_erase: bool,
228    /// If the chip was pre-erased with external erasers, this flag can set to true to skip erasing
229    /// It may be useful for mass production.
230    pub skip_erase: bool,
231    /// Before flashing, read back the flash contents to skip up-to-date regions.
232    pub preverify: bool,
233    /// After flashing, read back all the flashed data to verify it has been written correctly.
234    pub verify: bool,
235    /// Disable double buffering when loading flash.
236    pub disable_double_buffering: bool,
237    /// If there are multiple valid flash algorithms for a memory region, this list allows
238    /// overriding the default selection.
239    pub preferred_algos: Vec<String>,
240}
241
242impl DownloadOptions<'_> {
243    /// DownloadOptions with default values.
244    pub fn new() -> Self {
245        Self::default()
246    }
247}
248
249/// Builds a new flash loader for the given target and path. This
250/// will check the path for validity and check what pages have to be
251/// flashed etc.
252pub fn build_loader(
253    session: &mut Session,
254    path: impl AsRef<Path>,
255    format: Format,
256    image_instruction_set: Option<InstructionSet>,
257) -> Result<FlashLoader, FileDownloadError> {
258    // Create the flash loader
259    let mut loader = session.target().flash_loader();
260
261    // Add data from the BIN.
262    let mut file = File::open(path).map_err(FileDownloadError::IO)?;
263
264    loader.load_image(session, &mut file, format, image_instruction_set)?;
265
266    Ok(loader)
267}
268
269/// Downloads a file of given `format` at `path` to the flash of the target given in `session`.
270///
271/// This will ensure that memory boundaries are honored and does unlocking, erasing and programming of the flash for you.
272///
273/// If you are looking for more options, have a look at [download_file_with_options].
274pub fn download_file(
275    session: &mut Session,
276    path: impl AsRef<Path>,
277    format: impl Into<Format>,
278) -> Result<(), FileDownloadError> {
279    download_file_with_options(session, path, format, DownloadOptions::default())
280}
281
282/// Downloads a file of given `format` at `path` to the flash of the target given in `session`.
283///
284/// This will ensure that memory boundaries are honored and does unlocking, erasing and programming of the flash for you.
285///
286/// If you are looking for a simple version without many options, have a look at [download_file].
287pub fn download_file_with_options(
288    session: &mut Session,
289    path: impl AsRef<Path>,
290    format: impl Into<Format>,
291    options: DownloadOptions,
292) -> Result<(), FileDownloadError> {
293    let loader = build_loader(session, path, format.into(), None)?;
294
295    loader
296        .commit(session, options)
297        .map_err(FileDownloadError::Flash)
298}
299
300/// Flash data which was extracted from an ELF file.
301pub(super) struct ExtractedFlashData<'data> {
302    pub(super) section_names: Vec<String>,
303    pub(super) address: u32,
304    pub(super) data: &'data [u8],
305}
306
307impl std::fmt::Debug for ExtractedFlashData<'_> {
308    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309        let mut helper = f.debug_struct("ExtractedFlashData");
310
311        helper
312            .field("name", &self.section_names)
313            .field("address", &self.address);
314
315        if self.data.len() > 10 {
316            helper
317                .field("data", &format!("[..] ({} bytes)", self.data.len()))
318                .finish()
319        } else {
320            helper.field("data", &self.data).finish()
321        }
322    }
323}
324
325fn extract_from_elf_inner<'data, T: FileHeader>(
326    elf_header: &T,
327    binary: ElfFile<'_, T>,
328    elf_data: &'data [u8],
329    options: &ElfOptions,
330) -> Result<Vec<ExtractedFlashData<'data>>, FileDownloadError> {
331    let endian = elf_header.endian()?;
332
333    let mut extracted_data = Vec::new();
334    for segment in elf_header.program_headers(elf_header.endian()?, elf_data)? {
335        // Get the physical address of the segment. The data will be programmed to that location.
336        let p_paddr: u64 = segment.p_paddr(endian).into();
337
338        let p_vaddr: u64 = segment.p_vaddr(endian).into();
339
340        let flags = segment.p_flags(endian);
341
342        let segment_data = segment
343            .data(endian, elf_data)
344            .map_err(|_| FileDownloadError::Object("Failed to access data for an ELF segment."))?;
345
346        let mut elf_section = Vec::new();
347
348        if !segment_data.is_empty() && segment.p_type(endian) == PT_LOAD {
349            tracing::info!(
350                "Found loadable segment, physical address: {:#010x}, virtual address: {:#010x}, flags: {:#x}",
351                p_paddr,
352                p_vaddr,
353                flags
354            );
355
356            let (segment_offset, segment_filesize) = segment.file_range(endian);
357
358            let sector = segment_offset..segment_offset + segment_filesize;
359
360            for section in binary.sections() {
361                let (section_offset, section_filesize) = match section.file_range() {
362                    Some(range) => range,
363                    None => continue,
364                };
365
366                if sector.contains_range(&(section_offset..section_offset + section_filesize)) {
367                    let name = section.name()?;
368                    if options.skip_sections.iter().any(|skip| skip == name) {
369                        tracing::info!("Skipping section: {:?}", name);
370                        continue;
371                    }
372                    tracing::info!("Matching section: {:?}", name);
373
374                    #[cfg(feature = "hexdump")]
375                    for line in hexdump::hexdump_iter(section.data()?) {
376                        tracing::trace!("{}", line);
377                    }
378
379                    for (offset, relocation) in section.relocations() {
380                        tracing::info!(
381                            "Relocation: offset={}, relocation={:?}",
382                            offset,
383                            relocation
384                        );
385                    }
386
387                    elf_section.push(name.to_owned());
388                }
389            }
390
391            if elf_section.is_empty() {
392                tracing::info!("Not adding segment, no matching sections found.");
393            } else {
394                let section_data =
395                    &elf_data[segment_offset as usize..][..segment_filesize as usize];
396
397                extracted_data.push(ExtractedFlashData {
398                    section_names: elf_section,
399                    address: p_paddr as u32,
400                    data: section_data,
401                });
402            }
403        }
404    }
405
406    Ok(extracted_data)
407}
408
409pub(super) fn extract_from_elf<'a>(
410    elf_data: &'a [u8],
411    options: &ElfOptions,
412) -> Result<Vec<ExtractedFlashData<'a>>, FileDownloadError> {
413    let file_kind = object::FileKind::parse(elf_data)?;
414
415    match file_kind {
416        object::FileKind::Elf32 => {
417            let elf_header = FileHeader32::<Endianness>::parse(elf_data)?;
418            let binary = object::read::elf::ElfFile::<FileHeader32<Endianness>>::parse(elf_data)?;
419            extract_from_elf_inner(elf_header, binary, elf_data, options)
420        }
421        object::FileKind::Elf64 => {
422            let elf_header = FileHeader64::<Endianness>::parse(elf_data)?;
423            let binary = object::read::elf::ElfFile::<FileHeader64<Endianness>>::parse(elf_data)?;
424            extract_from_elf_inner(elf_header, binary, elf_data, options)
425        }
426        _ => Err(FileDownloadError::Object("Unsupported file type")),
427    }
428}
429
430#[cfg(test)]
431mod tests {
432    use std::str::FromStr;
433
434    use super::FormatKind;
435
436    #[test]
437    fn parse_format() {
438        assert_eq!(FormatKind::from_str("hex"), Ok(FormatKind::Hex));
439        assert_eq!(FormatKind::from_str("Hex"), Ok(FormatKind::Hex));
440        assert_eq!(FormatKind::from_str("Ihex"), Ok(FormatKind::Hex));
441        assert_eq!(FormatKind::from_str("IHex"), Ok(FormatKind::Hex));
442        assert_eq!(FormatKind::from_str("iHex"), Ok(FormatKind::Hex));
443        assert_eq!(FormatKind::from_str("IntelHex"), Ok(FormatKind::Hex));
444        assert_eq!(FormatKind::from_str("intelhex"), Ok(FormatKind::Hex));
445        assert_eq!(FormatKind::from_str("intelHex"), Ok(FormatKind::Hex));
446        assert_eq!(FormatKind::from_str("Intelhex"), Ok(FormatKind::Hex));
447        assert_eq!(FormatKind::from_str("bin"), Ok(FormatKind::Bin));
448        assert_eq!(FormatKind::from_str("Bin"), Ok(FormatKind::Bin));
449        assert_eq!(FormatKind::from_str("binary"), Ok(FormatKind::Bin));
450        assert_eq!(FormatKind::from_str("Binary"), Ok(FormatKind::Bin));
451        assert_eq!(FormatKind::from_str("Elf"), Ok(FormatKind::Elf));
452        assert_eq!(FormatKind::from_str("elf"), Ok(FormatKind::Elf));
453        assert_eq!(FormatKind::from_str("idf"), Ok(FormatKind::Idf));
454        assert_eq!(FormatKind::from_str("espidf"), Ok(FormatKind::Idf));
455        assert_eq!(FormatKind::from_str("esp-idf"), Ok(FormatKind::Idf));
456        assert_eq!(FormatKind::from_str("ESP-IDF"), Ok(FormatKind::Idf));
457        assert_eq!(
458            FormatKind::from_str("elfbin"),
459            Err("Format 'elfbin' is unknown.".to_string())
460        );
461        assert_eq!(
462            FormatKind::from_str(""),
463            Err("Format '' is unknown.".to_string())
464        );
465        assert_eq!(
466            FormatKind::from_str("asdasdf"),
467            Err("Format 'asdasdf' is unknown.".to_string())
468        );
469    }
470}