Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ default-members = ["crates/elf2uf2-rs"]
[workspace.package]
version = "2.2.0"
authors = ["Jonathan Nilsson <jonathan@voysys.se>"]
edition = "2021"
edition = "2024"
readme = "README.md"
license = "0BSD"
repository = "https://github.com/JoNil/elf2uf2-rs"
Expand All @@ -25,3 +25,4 @@ elf = "0.8"
thiserror = "2"
log = "0.4"
env_logger = "0.11"
nusb = "0.2"
Comment thread
BjornTheProgrammer marked this conversation as resolved.
Outdated
6 changes: 5 additions & 1 deletion crates/elf2uf2-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ readme = "README.md"
description = "The core library behind elf2uf2-rs"
documentation = "https://docs.rs/elf2uf2-core"

[features]
default = ["clap"]

[dependencies]
assert_into = { workspace = true }
clap = { workspace = true, optional = true }
static_assertions = { workspace = true }
zerocopy = { workspace = true }
elf = { workspace = true }
thiserror = { workspace = true }
log = { workspace = true }

clap = { workspace = true, optional = true }
Comment thread
BjornTheProgrammer marked this conversation as resolved.
114 changes: 36 additions & 78 deletions crates/elf2uf2-core/src/address_range.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use std::io::{Read, Seek};

use elf::{ElfStream, abi::PT_LOAD, endian::EndianParse};

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum AddressRangeType {
/// May have contents
Expand Down Expand Up @@ -31,85 +35,39 @@ impl Default for AddressRange {
}
}

pub const FLASH_SECTOR_ERASE_SIZE: u64 = 4096;
pub const MAIN_RAM_START_RP2040: u64 = 0x20000000;
pub const MAIN_RAM_END_RP2040: u64 = 0x20042000;
pub const MAIN_RAM_START_RP2350: u64 = 0x20000000;
pub const MAIN_RAM_END_RP2350: u64 = 0x20082000;
pub const FLASH_START_RP2040: u64 = 0x10000000;
pub const FLASH_END_RP2040: u64 = 0x15000000;
// From RP2350 datasheet:
// RP2040 required images to be stored at the beginning of flash (0x10000000). RP2350 supports storing executable images
// in a partitions at arbitrary locations, to support more robust upgrade cycles via A/B versions, among other uses.
// Therefore, the values below are possibly incorrect but FLASH_END_RP2040 appears to be incorrect too
pub const FLASH_START_RP2350: u64 = 0x10000000;
pub const FLASH_END_RP2350: u64 = 0x15000000;
pub const XIP_SRAM_START_RP2040: u64 = 0x15000000;
pub const XIP_SRAM_END_RP2040: u64 = 0x15004000;
pub const XIP_SRAM_START_RP2350: u64 = 0x13ffc000;
pub const XIP_SRAM_END_RP2350: u64 = 0x14000000;
pub const MAIN_RAM_BANKED_START_RP2040: u64 = 0x21000000;
pub const MAIN_RAM_BANKED_END_RP2040: u64 = 0x21040000;
pub const ROM_START_RP2040: u64 = 0x00000000;
pub const ROM_END_RP2040: u64 = 0x00004000;
pub const ROM_START_RP2350: u64 = 0x00000000;
pub const ROM_END_RP2350: u64 = 0x00008000;
pub fn address_ranges_from_elf<E: EndianParse, S: Read + Seek>(
file: &ElfStream<E, S>,
) -> Vec<AddressRange> {
let segments = file.segments();

pub const RP2040_ADDRESS_RANGES_FLASH: &[AddressRange] = &[
AddressRange::new(
FLASH_START_RP2040,
FLASH_END_RP2040,
AddressRangeType::Contents,
),
AddressRange::new(
MAIN_RAM_START_RP2040,
MAIN_RAM_END_RP2040,
AddressRangeType::NoContents,
),
AddressRange::new(
MAIN_RAM_BANKED_START_RP2040,
MAIN_RAM_BANKED_END_RP2040,
AddressRangeType::NoContents,
),
];
let mut ranges = Vec::new();

pub const RP2040_ADDRESS_RANGES_RAM: &[AddressRange] = &[
AddressRange::new(
MAIN_RAM_START_RP2040,
MAIN_RAM_END_RP2040,
AddressRangeType::Contents,
),
AddressRange::new(
XIP_SRAM_START_RP2040,
XIP_SRAM_END_RP2040,
AddressRangeType::Contents,
),
AddressRange::new(ROM_START_RP2040, ROM_END_RP2040, AddressRangeType::Ignore), // for now we ignore the bootrom if present
];
for seg in segments {
if seg.p_type != PT_LOAD || seg.p_memsz == 0 {
continue;
}

pub const RP2350_ADDRESS_RANGES_FLASH: &[AddressRange] = &[
AddressRange::new(
FLASH_START_RP2350,
FLASH_END_RP2350,
AddressRangeType::Contents,
),
AddressRange::new(
MAIN_RAM_START_RP2350,
MAIN_RAM_END_RP2350,
AddressRangeType::NoContents,
),
];
let start = seg.p_paddr;
let end = start + seg.p_memsz;

if seg.p_filesz > 0 {
// initialized contents
ranges.push(AddressRange::new(
start,
start + seg.p_filesz,
AddressRangeType::Contents,
));
}

pub const RP2350_ADDRESS_RANGES_RAM: &[AddressRange] = &[
AddressRange::new(
MAIN_RAM_START_RP2350,
MAIN_RAM_END_RP2350,
AddressRangeType::Contents,
),
AddressRange::new(
XIP_SRAM_START_RP2350,
XIP_SRAM_END_RP2350,
AddressRangeType::Contents,
),
AddressRange::new(ROM_START_RP2350, ROM_END_RP2350, AddressRangeType::Ignore), // for now we ignore the bootrom if present
];
if seg.p_memsz > seg.p_filesz {
// uninitialized (BSS)
ranges.push(AddressRange::new(
start + seg.p_filesz,
end,
AddressRangeType::NoContents,
));
}
}

ranges
}
88 changes: 88 additions & 0 deletions crates/elf2uf2-core/src/boards/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
pub use rp2040::RP2040;
pub use rp2350::RP2350;

use crate::address_range::AddressRange;

pub mod rp2040;
pub mod rp2350;

/// This is a helper struct, which allows you to iterate over every board defined
#[derive(Default)]
pub struct BoardIter {
inner: std::vec::IntoIter<Box<dyn BoardInfo>>,
}

impl BoardIter {
/// Creates a new BoardIter
pub fn new() -> Self {
Self {
inner: vec![Box::new(RP2040) as Box<dyn BoardInfo>, Box::new(RP2350)].into_iter(),
}
}

pub fn find_by_name(name: &str) -> Option<Box<dyn BoardInfo>> {
Self::new()
.find(|board| board.board_name().eq_ignore_ascii_case(name))
.map(|v| v as _)
}
}

impl Iterator for BoardIter {
type Item = Box<dyn BoardInfo>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
}

/// This is the version of the firmware on the usb device
#[allow(unused)]
#[derive(Debug, Clone)]
pub struct UsbVersion(pub u8, pub u8, pub u8);

/// This is the usb device information from the usb device. It is possible to generate this information with something like
/// nusb
#[allow(unused)]
#[derive(Debug, Clone)]
pub struct UsbDevice {
pub bus_number: u8,
pub address: u8,
pub vendor_id: u16,
pub product_id: u16,
pub version: UsbVersion,
}

#[derive(Debug, Clone, Default)]
pub struct AddressLocations<'a> {
pub address_ranges_ram: Option<&'a [AddressRange]>,
pub address_ranges_flash: Option<&'a [AddressRange]>,
pub main_ram_start: Option<u64>,
Comment thread
datdenkikniet marked this conversation as resolved.
pub main_ram_end: Option<u64>,
pub xip_sram_start: Option<u64>,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like XIP-RAM is a somewhat RP2xxx specific thing. Does it belong in this struct, or can we do without it, somehow? E.g. abstract away whatever meaning we attach it behind some function?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably should abstract it out into some function instead.

pub xip_sram_end: Option<u64>,
}

/// This trait helps by allowing for definitions of multiple different boards.
pub trait BoardInfo {
Copy link
Copy Markdown
Collaborator

@datdenkikniet datdenkikniet Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that we &dyn BoardInfo everywhere, I'm thinking: would it make more sense to define a GetBoardInfo trait that has a single function returning a concrete struct BoardInfo with the relevant fields (including something like an fn(device: &UsbDevice) -> bool for USB detection) and passing around (references) to that concrete type everywhere instead?

Since the required info is quite concrete and practically none of it actually depends on the concrete instantiation of our impl BoardInfo, I feel like that could work just as well.

It would prevent us from having an implementor of BoardInfo that can do "more runtime magic", but given that all these functions take &self that wouldn't really happen anyways.

Mostly asking because all of these functions only take &self, and unless we plan to support boards that can do some sort of crazy runtime detection with interior mutability, this trait is not much different from a "static trait" that only has fn my_func() -> val functions instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes perfect sense to make a trait to get the info! Will do so!

/// Check if the board is connected to the specified UsbDevice
fn is_device_board(&self, device: &UsbDevice) -> bool;

/// Returns the proper family id to use for the uf2 device
fn family_id(&self) -> u32;

/// Optional, just sent to a sensible default of 256, as long as it is less than 512 - 32 it should be okay, but boards very, and so does the bootloader firmware
fn page_size(&self) -> u32 {
256
}

/// Optional, with a default erase size of 4096
fn flash_sector_erase_size(&self) -> u64 {
4096
}

fn address_locations<'a>(&'a self) -> AddressLocations<'a> {
AddressLocations::default()
}

/// Get the board's name
fn board_name(&self) -> String;
Comment thread
BjornTheProgrammer marked this conversation as resolved.
Outdated
}
81 changes: 81 additions & 0 deletions crates/elf2uf2-core/src/boards/rp2040.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use crate::{
address_range::{AddressRange, AddressRangeType},
boards::{AddressLocations, BoardInfo, UsbDevice},
};

#[derive(Debug, Default, Clone)]
pub struct RP2040;

impl BoardInfo for RP2040 {
fn is_device_board(&self, device: &UsbDevice) -> bool {
if device.vendor_id == 0x2e8a || device.product_id == 0x0003 {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be an &&

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I don't really think this should be a double &&. Why should we use a double &&?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm referring to the condition in the if statement, not the function. For the function I agree, a double-reference makes very little sense :P

Suggested change
if device.vendor_id == 0x2e8a || device.product_id == 0x0003 {
if device.vendor_id == 0x2e8a && device.product_id == 0x0003 {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh 😅, that makes a lot more sense.

return true;
}
false
}

fn family_id(&self) -> u32 {
0xe48bff56
}

fn address_locations<'a>(&'a self) -> AddressLocations<'a> {
AddressLocations {
address_ranges_ram: Some(RP2040_ADDRESS_RANGES_RAM),
address_ranges_flash: Some(RP2040_ADDRESS_RANGES_FLASH),
main_ram_start: Some(MAIN_RAM_START_RP2040),
main_ram_end: Some(MAIN_RAM_END_RP2040),
xip_sram_start: Some(XIP_SRAM_START_RP2040),
xip_sram_end: Some(XIP_SRAM_END_RP2040),
}
}

fn board_name(&self) -> String {
"rp2040".to_string()
}
}

pub const MAIN_RAM_START_RP2040: u64 = 0x20000000;
pub const MAIN_RAM_END_RP2040: u64 = 0x20042000;
pub const FLASH_START_RP2040: u64 = 0x10000000;
pub const FLASH_END_RP2040: u64 = 0x15000000;

pub const XIP_SRAM_START_RP2040: u64 = 0x15000000;
pub const XIP_SRAM_END_RP2040: u64 = 0x15004000;

pub const MAIN_RAM_BANKED_START_RP2040: u64 = 0x21000000;
pub const MAIN_RAM_BANKED_END_RP2040: u64 = 0x21040000;

pub const ROM_START_RP2040: u64 = 0x00000000;
pub const ROM_END_RP2040: u64 = 0x00004000;

pub const RP2040_ADDRESS_RANGES_FLASH: &[AddressRange] = &[
AddressRange::new(
FLASH_START_RP2040,
FLASH_END_RP2040,
AddressRangeType::Contents,
),
AddressRange::new(
MAIN_RAM_START_RP2040,
MAIN_RAM_END_RP2040,
AddressRangeType::NoContents,
),
AddressRange::new(
MAIN_RAM_BANKED_START_RP2040,
MAIN_RAM_BANKED_END_RP2040,
AddressRangeType::NoContents,
),
];

pub const RP2040_ADDRESS_RANGES_RAM: &[AddressRange] = &[
AddressRange::new(
MAIN_RAM_START_RP2040,
MAIN_RAM_END_RP2040,
AddressRangeType::Contents,
),
AddressRange::new(
XIP_SRAM_START_RP2040,
XIP_SRAM_END_RP2040,
AddressRangeType::Contents,
),
AddressRange::new(ROM_START_RP2040, ROM_END_RP2040, AddressRangeType::Ignore), // for now we ignore the bootrom if present
];
Loading