1use std::{collections::HashSet, error::Error, io::{Read, Write, Seek}};
2
3use assert_into::AssertInto;
4use log::debug;
5use static_assertions::const_assert;
6use zerocopy::IntoBytes;
7
8use crate::{address_range::{FLASH_SECTOR_ERASE_SIZE, MAIN_RAM_END, MAIN_RAM_START, RP2040_ADDRESS_RANGES_FLASH, RP2040_ADDRESS_RANGES_RAM, XIP_SRAM_END, XIP_SRAM_START}, elf::{realize_page, AddressRangesExt, Elf32Header, PAGE_SIZE}, uf2::{Uf2BlockData, Uf2BlockFooter, Uf2BlockHeader, RP2040_FAMILY_ID, UF2_FLAG_FAMILY_ID_PRESENT, UF2_MAGIC_END, UF2_MAGIC_START0, UF2_MAGIC_START1}};
9
10pub mod address_range;
11pub mod elf;
12pub mod uf2;
13
14pub trait ProgressReporter {
15 fn start(&mut self, total_bytes: usize);
16 fn advance(&mut self, bytes: usize);
17 fn finish(&mut self);
18}
19
20pub struct NoProgress;
21impl ProgressReporter for NoProgress {
22 fn start(&mut self, _total_bytes: usize) {}
23 fn advance(&mut self, _bytes: usize) {}
24 fn finish(&mut self) {}
25}
26
27pub fn elf2uf2(mut input: impl Read + Seek, mut output: impl Write, family_id: Option<u32>, mut reporter: impl ProgressReporter) -> Result<(), Box<dyn Error>> {
41 let eh = Elf32Header::from_read(&mut input)?;
42
43 let entries = eh.read_elf32_ph_entries(&mut input)?;
44
45 let ram_style = eh
46 .is_ram_binary(&entries)
47 .ok_or("entry point is not in mapped part of file".to_string())?;
48
49 if ram_style {
50 debug!("Detected RAM binary");
51 } else {
52 debug!("Detected FLASH binary");
53 }
54
55 let valid_ranges = if ram_style {
56 RP2040_ADDRESS_RANGES_RAM
57 } else {
58 RP2040_ADDRESS_RANGES_FLASH
59 };
60
61 let mut pages = valid_ranges.check_elf32_ph_entries(&entries)?;
62
63 if pages.is_empty() {
64 return Err("The input file has no memory pages".into());
65 }
66
67 if ram_style {
68 let mut expected_ep_main_ram = u32::MAX;
69 let mut expected_ep_xip_sram = u32::MAX;
70
71 #[allow(clippy::manual_range_contains)]
72 pages.keys().copied().for_each(|addr| {
73 if addr >= MAIN_RAM_START && addr <= MAIN_RAM_END {
74 expected_ep_main_ram = expected_ep_main_ram.min(addr) | 0x1;
75 } else if addr >= XIP_SRAM_START && addr < XIP_SRAM_END {
76 expected_ep_xip_sram = expected_ep_xip_sram.min(addr) | 0x1;
77 }
78 });
79
80 let expected_ep = if expected_ep_main_ram != u32::MAX {
81 expected_ep_main_ram
82 } else {
83 expected_ep_xip_sram
84 };
85
86 if expected_ep == expected_ep_xip_sram {
87 return Err("B0/B1 Boot ROM does not support direct entry into XIP_SRAM".into());
88 } else if eh.entry != expected_ep {
89 #[allow(clippy::unnecessary_cast)]
90 return Err(format!(
91 "A RAM binary should have an entry point at the beginning: {:#08x} (not {:#08x})",
92 expected_ep, eh.entry as u32
93 )
94 .into());
95 }
96 const_assert!(0 == (MAIN_RAM_START & (PAGE_SIZE - 1)));
97
98 } else {
101 let touched_sectors: HashSet<u32> = pages
107 .keys()
108 .map(|addr| addr / FLASH_SECTOR_ERASE_SIZE)
109 .collect();
110
111 let last_page_addr = *pages.last_key_value().unwrap().0;
112 for sector in touched_sectors {
113 let mut page = sector * FLASH_SECTOR_ERASE_SIZE;
114
115 while page < (sector + 1) * FLASH_SECTOR_ERASE_SIZE {
116 if page < last_page_addr && !pages.contains_key(&page) {
117 pages.insert(page, Vec::new());
118 }
119 page += PAGE_SIZE;
120 }
121 }
122 }
123
124 let mut block_header = Uf2BlockHeader {
125 magic_start0: UF2_MAGIC_START0,
126 magic_start1: UF2_MAGIC_START1,
127 flags: UF2_FLAG_FAMILY_ID_PRESENT,
128 target_addr: 0,
129 payload_size: PAGE_SIZE,
130 block_no: 0,
131 num_blocks: pages.len().assert_into(),
132 file_size: family_id.unwrap_or(RP2040_FAMILY_ID),
133 };
134
135 let mut block_data: Uf2BlockData = [0; 476];
136
137 let block_footer = Uf2BlockFooter {
138 magic_end: UF2_MAGIC_END,
139 };
140
141 log::debug!("Writing program");
142
143 reporter.start(pages.len() * 512);
144
145 let last_page_num = pages.len() - 1;
146
147 for (page_num, (target_addr, fragments)) in pages.into_iter().enumerate() {
148 block_header.target_addr = target_addr;
149 block_header.block_no = page_num.assert_into();
150
151 debug!(
152 "Page {} / {} {:#08x}",
153 block_header.block_no as u32,
154 block_header.num_blocks as u32,
155 block_header.target_addr as u32
156 );
157
158 block_data.iter_mut().for_each(|v| *v = 0);
159
160 realize_page(&mut input, &fragments, &mut block_data)?;
161
162 output.write_all(block_header.as_bytes())?;
163 output.write_all(block_data.as_bytes())?;
164 output.write_all(block_footer.as_bytes())?;
165
166 if page_num != last_page_num {
167 reporter.advance(512);
168 }
169 }
170
171 drop(output);
173
174 reporter.advance(512);
175
176 reporter.finish();
177
178 Ok(())
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use std::io;
185
186 #[test]
187 pub fn hello_usb() {
188 log::set_max_level(log::LevelFilter::Debug);
189 let bytes_in = io::Cursor::new(&include_bytes!("../tests/rp2040/hello_usb.elf")[..]);
190 let mut bytes_out = Vec::new();
191 elf2uf2(bytes_in, &mut bytes_out, None, NoProgress).unwrap();
192
193 assert_eq!(bytes_out, include_bytes!("../tests/rp2040/hello_usb.uf2"));
194 }
195
196 #[test]
197 pub fn hello_serial() {
198 log::set_max_level(log::LevelFilter::Debug);
199 let bytes_in = io::Cursor::new(&include_bytes!("../tests/rp2040/hello_serial.elf")[..]);
200 let mut bytes_out = Vec::new();
201 elf2uf2(bytes_in, &mut bytes_out, None, NoProgress).unwrap();
202
203 assert_eq!(bytes_out, include_bytes!("../tests/rp2040/hello_serial.uf2"));
204 }
205}