probe_rs/flashing/
download.rs

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