elf2flash_core/
lib.rs

1use std::{
2    collections::HashSet,
3    io::{Cursor, Write},
4};
5
6use ::elf::{ElfBytes, ParseError, endian::AnyEndian};
7use assert_into::AssertInto;
8use log::debug;
9use thiserror::Error;
10use zerocopy::IntoBytes;
11
12use crate::{
13    address_range::AddressRangesFromElfError,
14    boards::BoardInfo,
15    elf::{get_page_fragments, realize_page},
16    uf2::{
17        UF2_FLAG_FAMILY_ID_PRESENT, UF2_MAGIC_END, UF2_MAGIC_START0, UF2_MAGIC_START1,
18        Uf2BlockData, Uf2BlockFooter, Uf2BlockHeader,
19    },
20};
21
22pub mod address_range;
23pub mod boards;
24pub mod elf;
25pub mod uf2;
26
27pub trait ProgressReporter {
28    fn start(&mut self, total_bytes: usize);
29    fn advance(&mut self, bytes: usize);
30    fn finish(&mut self);
31}
32
33pub struct NoProgress;
34impl ProgressReporter for NoProgress {
35    fn start(&mut self, _total_bytes: usize) {}
36    fn advance(&mut self, _bytes: usize) {}
37    fn finish(&mut self) {}
38}
39
40#[derive(Error, Debug)]
41pub enum Elf2Uf2Error {
42    #[error("Failed to get address ranges from elf")]
43    AddressRangesError(#[from] AddressRangesFromElfError),
44    #[error("Failed to parse elf file")]
45    ElfParseError(#[from] ParseError),
46    #[error("Failed to realize pages")]
47    RealizePageError(#[from] std::io::Error),
48    #[error("The input file has no memory pages")]
49    InputFileNoMemoryPagesError,
50}
51
52/// Convert a file to a uf2 file. Give an input, and it generates an output. If you don't want to provide a family_id or reporter, then the family_id defaults to
53/// the rp2040's family id. Just pass in the NoProgress struct to reporter you do not wish to have progress reporting.
54///
55/// # Examples
56///
57/// ```
58/// use std::io::Cursor;
59/// use elf2flash_core::{elf2uf2, boards, NoProgress};
60///
61/// log::set_max_level(log::LevelFilter::Debug);
62/// let bytes_in = &include_bytes!("../tests/rp2040/hello_usb.elf")[..];
63/// let mut bytes_out = Vec::new();
64/// let board = boards::RP2040::default();
65/// elf2uf2(bytes_in, &mut bytes_out, &board, NoProgress).unwrap();
66/// ```
67pub fn elf2uf2(
68    input: impl AsRef<[u8]>,
69    mut output: impl Write,
70    board: &dyn BoardInfo,
71    mut reporter: impl ProgressReporter,
72) -> Result<(), Elf2Uf2Error> {
73    let input = input.as_ref();
74    let file = ElfBytes::<AnyEndian>::minimal_parse(input)?;
75
76    let page_size = board.page_size();
77    let flash_sector_erase_size = board.flash_sector_erase_size();
78    let family_id = board.family_id();
79
80    let mut pages = get_page_fragments(&file, page_size)?;
81
82    if pages.is_empty() {
83        return Err(Elf2Uf2Error::InputFileNoMemoryPagesError);
84    }
85
86    let touched_sectors: HashSet<u64> = pages
87        .keys()
88        .map(|addr| addr / flash_sector_erase_size)
89        .collect();
90
91    let last_page_addr = *pages
92        .last_key_value()
93        .expect("Impossible error occurred since pages is garunteed to have a last page")
94        .0;
95    for sector in touched_sectors {
96        let mut page = sector * flash_sector_erase_size;
97
98        while page < (sector + 1) * flash_sector_erase_size {
99            if page < last_page_addr && !pages.contains_key(&page) {
100                pages.insert(page, Vec::new());
101            }
102            page += page_size as u64;
103        }
104    }
105
106    let mut block_header = Uf2BlockHeader {
107        magic_start0: UF2_MAGIC_START0,
108        magic_start1: UF2_MAGIC_START1,
109        flags: UF2_FLAG_FAMILY_ID_PRESENT,
110        target_addr: 0,
111        payload_size: page_size,
112        block_no: 0,
113        num_blocks: pages.len() as u32,
114        file_size: family_id,
115    };
116
117    let mut block_data: Uf2BlockData = [0; 476];
118
119    let block_footer = Uf2BlockFooter {
120        magic_end: UF2_MAGIC_END,
121    };
122
123    log::debug!("Writing program");
124
125    reporter.start(pages.len() * 512);
126
127    let last_page_num = pages.len() - 1;
128
129    for (page_num, (target_addr, fragments)) in pages.into_iter().enumerate() {
130        block_header.target_addr = target_addr as u32;
131        block_header.block_no = page_num.assert_into();
132
133        debug!(
134            "Page {} / {} {:#08x}",
135            block_header.block_no as u32,
136            block_header.num_blocks as u32,
137            block_header.target_addr as u32
138        );
139
140        block_data.iter_mut().for_each(|v| *v = 0);
141
142        realize_page(
143            &mut Cursor::new(input),
144            &fragments,
145            &mut block_data,
146            page_size,
147        )?;
148
149        output.write_all(block_header.as_bytes())?;
150        output.write_all(block_data.as_bytes())?;
151        output.write_all(block_footer.as_bytes())?;
152
153        if page_num != last_page_num {
154            reporter.advance(512);
155        }
156    }
157
158    // Drop the output before the progress bar is allowd to finish
159    drop(output);
160
161    reporter.advance(512);
162
163    reporter.finish();
164
165    Ok(())
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    pub fn hello_usb() {
174        log::set_max_level(log::LevelFilter::Debug);
175        let bytes_in = &include_bytes!("../tests/rp2040/hello_usb.elf")[..];
176        let mut bytes_out = Vec::new();
177        let board = boards::RP2040::default();
178        elf2uf2(bytes_in, &mut bytes_out, &board, NoProgress).unwrap();
179
180        assert_eq!(bytes_out, include_bytes!("../tests/rp2040/hello_usb.uf2"));
181    }
182
183    #[test]
184    pub fn hello_serial() {
185        log::set_max_level(log::LevelFilter::Debug);
186        let bytes_in = &include_bytes!("../tests/rp2040/hello_serial.elf")[..];
187        let mut bytes_out = Vec::new();
188        let board = boards::RP2040::default();
189        elf2uf2(bytes_in, &mut bytes_out, &board, NoProgress).unwrap();
190
191        assert_eq!(
192            bytes_out,
193            include_bytes!("../tests/rp2040/hello_serial.uf2")
194        );
195    }
196}