pub const ITEM_1BS_VECTOR_TABLE: u8 = 0x03;
pub const ITEM_1BS_ROLLING_WINDOW_DELTA: u8 = 0x05;
pub const ITEM_1BS_SIGNATURE: u8 = 0x09;
pub const ITEM_1BS_SALT: u8 = 0x0c;
pub const ITEM_1BS_IMAGE_TYPE: u8 = 0x42;
pub const ITEM_1BS_ENTRY_POINT: u8 = 0x44;
pub const ITEM_2BS_HASH_DEF: u8 = 0x47;
pub const ITEM_1BS_VERSION: u8 = 0x48;
pub const ITEM_1BS_HASH_VALUE: u8 = 0x4b;
pub const ITEM_2BS_LOAD_MAP: u8 = 0x06;
pub const ITEM_2BS_PARTITION_TABLE: u8 = 0x0a;
pub const ITEM_2BS_IGNORED: u8 = 0xfe;
pub const ITEM_2BS_LAST: u8 = 0xff;
pub const IMAGE_TYPE_INVALID: u16 = 0x0000;
pub const IMAGE_TYPE_EXE: u16 = 0x0001;
pub const IMAGE_TYPE_DATA: u16 = 0x0002;
pub const IMAGE_TYPE_EXE_TYPE_SECURITY_UNSPECIFIED: u16 = 0x0000;
pub const IMAGE_TYPE_EXE_TYPE_SECURITY_NS: u16 = 0x0010;
pub const IMAGE_TYPE_EXE_TYPE_SECURITY_S: u16 = 0x0020;
pub const IMAGE_TYPE_EXE_CPU_ARM: u16 = 0x0000;
pub const IMAGE_TYPE_EXE_CPU_RISCV: u16 = 0x0100;
pub const IMAGE_TYPE_EXE_CHIP_RP2040: u16 = 0x0000;
pub const IMAGE_TYPE_EXE_CHIP_RP2350: u16 = 0x1000;
pub const IMAGE_TYPE_TBYB: u16 = 0x8000;
const BLOCK_MARKER_START: u32 = 0xffffded3;
const BLOCK_MARKER_END: u32 = 0xab123579;
pub type ImageDef = Block<1>;
#[derive(Debug)]
#[repr(C)]
pub struct Block<const N: usize> {
marker_start: u32,
items: [u32; N],
length: u32,
offset: *const u32,
marker_end: u32,
}
unsafe impl<const N: usize> Sync for Block<N> {}
impl<const N: usize> Block<N> {
pub const fn new(items: [u32; N]) -> Block<N> {
Block {
marker_start: BLOCK_MARKER_START,
items,
length: item_last(N as u16),
offset: core::ptr::null(),
marker_end: BLOCK_MARKER_END,
}
}
pub const fn with_offset(self, offset: *const u32) -> Block<N> {
Block { offset, ..self }
}
}
impl Block<0> {
pub const fn empty() -> Block<0> {
Block::new([])
}
pub const fn extend(self, word: u32) -> Block<1> {
Block::new([word])
}
}
impl Block<1> {
pub const fn extend(self, word: u32) -> Block<2> {
Block::new([self.items[0], word])
}
}
impl Block<2> {
pub const fn extend(self, word: u32) -> Block<3> {
Block::new([self.items[0], self.items[1], word])
}
}
impl ImageDef {
pub const fn arch_exe(security: Security, architecture: Architecture) -> Self {
Self::new([item_image_type_exe(security, architecture)])
}
pub const fn exe(security: Security) -> Self {
if cfg!(all(target_arch = "riscv32", target_os = "none")) {
Self::arch_exe(security, Architecture::Riscv)
} else {
Self::arch_exe(security, Architecture::Arm)
}
}
pub const fn non_secure_exe() -> Self {
Self::exe(Security::NonSecure)
}
pub const fn secure_exe() -> Self {
Self::exe(Security::Secure)
}
}
pub const PARTITION_TABLE_MAX_ITEMS: usize = 128;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct UnpartitionedSpace {
permissions_and_location: u32,
permissions_and_flags: u32,
}
impl UnpartitionedSpace {
pub const fn new() -> Self {
Self {
permissions_and_location: 0,
permissions_and_flags: 0,
}
}
pub const fn from_raw(permissions_and_location: u32, permissions_and_flags: u32) -> Self {
Self {
permissions_and_location,
permissions_and_flags,
}
}
pub const fn with_permission(self, permission: Permission) -> Self {
Self {
permissions_and_flags: self.permissions_and_flags | permission as u32,
permissions_and_location: self.permissions_and_location | permission as u32,
}
}
pub const fn with_flag(self, flag: UnpartitionedFlag) -> Self {
Self {
permissions_and_flags: self.permissions_and_flags | flag as u32,
..self
}
}
pub fn get_first_last_sectors(&self) -> (u16, u16) {
(
(self.permissions_and_location & 0x0000_1FFF) as u16,
((self.permissions_and_location >> 13) & 0x0000_1FFF) as u16,
)
}
pub fn get_first_last_bytes(&self) -> (u32, u32) {
let (first, last) = self.get_first_last_sectors();
(u32::from(first) * 4096, (u32::from(last) * 4096) + 4095)
}
pub fn has_permission(&self, permission: Permission) -> bool {
let mask = permission as u32;
(self.permissions_and_flags & mask) != 0
}
pub fn has_flag(&self, flag: UnpartitionedFlag) -> bool {
let mask = flag as u32;
(self.permissions_and_flags & mask) != 0
}
}
impl core::fmt::Display for UnpartitionedSpace {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let (first, last) = self.get_first_last_bytes();
write!(
f,
"{:#010x}..{:#010x} S:{}{} NS:{}{} B:{}{}",
first,
last,
if self.has_permission(Permission::SecureRead) {
'R'
} else {
'_'
},
if self.has_permission(Permission::SecureWrite) {
'W'
} else {
'_'
},
if self.has_permission(Permission::NonSecureRead) {
'R'
} else {
'_'
},
if self.has_permission(Permission::NonSecureWrite) {
'W'
} else {
'_'
},
if self.has_permission(Permission::BootRead) {
'R'
} else {
'_'
},
if self.has_permission(Permission::BootWrite) {
'W'
} else {
'_'
}
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Partition {
permissions_and_location: u32,
permissions_and_flags: u32,
id: Option<u64>,
extra_families: [u32; 4],
extra_families_len: usize,
name: [u8; 128],
}
impl Partition {
const FLAGS_HAS_ID: u32 = 0b1;
const FLAGS_LINK_TYPE_A_PARTITION: u32 = 0b01 << 1;
const FLAGS_LINK_TYPE_OWNER: u32 = 0b10 << 1;
const FLAGS_LINK_MASK: u32 = 0b111111 << 1;
const FLAGS_HAS_NAME: u32 = 0b1 << 12;
const FLAGS_HAS_EXTRA_FAMILIES_SHIFT: u8 = 7;
const FLAGS_HAS_EXTRA_FAMILIES_MASK: u32 = 0b11 << Self::FLAGS_HAS_EXTRA_FAMILIES_SHIFT;
pub const fn new(first_sector: u16, last_sector: u16) -> Self {
assert!(first_sector < 0x2000);
assert!(last_sector < 0x2000);
assert!(first_sector <= last_sector);
Self {
permissions_and_location: ((last_sector as u32) << 13) | first_sector as u32,
permissions_and_flags: 0,
id: None,
extra_families: [0; 4],
extra_families_len: 0,
name: [0; 128],
}
}
pub const fn from_raw(permissions_and_location: u32, permissions_and_flags: u32) -> Self {
Self {
permissions_and_location,
permissions_and_flags,
id: None,
extra_families: [0; 4],
extra_families_len: 0,
name: [0; 128],
}
}
pub const fn with_permission(self, permission: Permission) -> Self {
Self {
permissions_and_location: self.permissions_and_location | permission as u32,
permissions_and_flags: self.permissions_and_flags | permission as u32,
..self
}
}
pub const fn with_name(self, name: &str) -> Self {
let mut new_name = [0u8; 128];
let name = name.as_bytes();
let mut idx = 0;
new_name[0] = name.len() as u8;
while idx < name.len() {
new_name[idx + 1] = name[idx];
idx += 1;
}
Self {
name: new_name,
permissions_and_flags: self.permissions_and_flags | Self::FLAGS_HAS_NAME,
..self
}
}
pub const fn with_extra_families(self, extra_families: &[u32]) -> Self {
assert!(extra_families.len() <= 4);
let mut new_extra_families = [0u32; 4];
let mut idx = 0;
while idx < extra_families.len() {
new_extra_families[idx] = extra_families[idx];
idx += 1;
}
Self {
extra_families: new_extra_families,
extra_families_len: extra_families.len(),
permissions_and_flags: (self.permissions_and_flags
& !Self::FLAGS_HAS_EXTRA_FAMILIES_MASK)
| ((extra_families.len() as u32) << Self::FLAGS_HAS_EXTRA_FAMILIES_SHIFT),
..self
}
}
pub const fn with_id(self, id: u64) -> Self {
Self {
id: Some(id),
permissions_and_flags: self.permissions_and_flags | Self::FLAGS_HAS_ID,
..self
}
}
pub const fn with_link(self, link: Link) -> Self {
let mut new_flags = self.permissions_and_flags & !Self::FLAGS_LINK_MASK;
match link {
Link::Nothing => {}
Link::ToA { partition_idx } => {
assert!(partition_idx < 16);
new_flags |= Self::FLAGS_LINK_TYPE_A_PARTITION;
new_flags |= (partition_idx as u32) << 3;
}
Link::ToOwner { partition_idx } => {
assert!(partition_idx < 16);
new_flags |= Self::FLAGS_LINK_TYPE_OWNER;
new_flags |= (partition_idx as u32) << 3;
}
}
Self {
permissions_and_flags: new_flags,
..self
}
}
pub const fn with_flag(self, flag: PartitionFlag) -> Self {
Self {
permissions_and_flags: self.permissions_and_flags | flag as u32,
..self
}
}
pub fn get_first_last_sectors(&self) -> (u16, u16) {
(
(self.permissions_and_location & 0x0000_1FFF) as u16,
((self.permissions_and_location >> 13) & 0x0000_1FFF) as u16,
)
}
pub fn get_first_last_bytes(&self) -> (u32, u32) {
let (first, last) = self.get_first_last_sectors();
(u32::from(first) * 4096, (u32::from(last) * 4096) + 4095)
}
pub fn has_permission(&self, permission: Permission) -> bool {
let mask = permission as u32;
(self.permissions_and_flags & mask) != 0
}
pub fn get_extra_families(&self) -> &[u32] {
&self.extra_families[0..self.extra_families_len]
}
pub fn get_name(&self) -> Option<&str> {
let len = self.name[0] as usize;
if len == 0 {
None
} else {
core::str::from_utf8(&self.name[1..=len]).ok()
}
}
pub fn get_id(&self) -> Option<u64> {
self.id
}
pub fn get_link(&self) -> Link {
if (self.permissions_and_flags & Self::FLAGS_LINK_TYPE_A_PARTITION) != 0 {
let partition_idx = ((self.permissions_and_flags >> 3) & 0x0F) as u8;
Link::ToA { partition_idx }
} else if (self.permissions_and_flags & Self::FLAGS_LINK_TYPE_OWNER) != 0 {
let partition_idx = ((self.permissions_and_flags >> 3) & 0x0F) as u8;
Link::ToOwner { partition_idx }
} else {
Link::Nothing
}
}
pub fn has_flag(&self, flag: PartitionFlag) -> bool {
let mask = flag as u32;
(self.permissions_and_flags & mask) != 0
}
}
impl core::fmt::Display for Partition {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let (first, last) = self.get_first_last_bytes();
write!(
f,
"{:#010x}..{:#010x} S:{}{} NS:{}{} B:{}{}",
first,
last,
if self.has_permission(Permission::SecureRead) {
'R'
} else {
'_'
},
if self.has_permission(Permission::SecureWrite) {
'W'
} else {
'_'
},
if self.has_permission(Permission::NonSecureRead) {
'R'
} else {
'_'
},
if self.has_permission(Permission::NonSecureWrite) {
'W'
} else {
'_'
},
if self.has_permission(Permission::BootRead) {
'R'
} else {
'_'
},
if self.has_permission(Permission::BootWrite) {
'W'
} else {
'_'
}
)
}
}
#[derive(Clone)]
pub struct PartitionTableBlock {
contents: [u32; PARTITION_TABLE_MAX_ITEMS],
num_items: usize,
}
impl PartitionTableBlock {
pub const fn new() -> PartitionTableBlock {
let mut contents = [0; PARTITION_TABLE_MAX_ITEMS];
contents[0] = BLOCK_MARKER_START;
contents[1] = item_last(0);
contents[2] = 0;
contents[3] = BLOCK_MARKER_END;
PartitionTableBlock {
contents,
num_items: 0,
}
}
pub const fn add_partition_item(
self,
unpartitioned: UnpartitionedSpace,
partitions: &[Partition],
) -> Self {
let mut new_table = PartitionTableBlock::new();
let mut idx = 0;
while idx < self.num_items + 1 {
new_table.contents[idx] = self.contents[idx];
idx += 1;
}
let header_idx = idx;
new_table.contents[idx] = 0;
idx += 1;
new_table.contents[idx] = unpartitioned.permissions_and_flags;
idx += 1;
let mut partition_no = 0;
while partition_no < partitions.len() {
new_table.contents[idx] = partitions[partition_no].permissions_and_location;
idx += 1;
new_table.contents[idx] = partitions[partition_no].permissions_and_flags;
idx += 1;
if let Some(id) = partitions[partition_no].id {
new_table.contents[idx] = id as u32;
new_table.contents[idx + 1] = (id >> 32) as u32;
idx += 2;
}
let mut extra_families_idx = 0;
while extra_families_idx < partitions[partition_no].extra_families_len {
new_table.contents[idx] =
partitions[partition_no].extra_families[extra_families_idx];
idx += 1;
extra_families_idx += 1;
}
let mut name_idx = 0;
while name_idx < partitions[partition_no].name[0] as usize {
let name_chunk = [
partitions[partition_no].name[name_idx],
partitions[partition_no].name[name_idx + 1],
partitions[partition_no].name[name_idx + 2],
partitions[partition_no].name[name_idx + 3],
];
new_table.contents[idx] = u32::from_le_bytes(name_chunk);
name_idx += 4;
idx += 1;
}
partition_no += 1;
}
let len = idx - header_idx;
new_table.contents[header_idx] =
item_generic_2bs(partitions.len() as u8, len as u16, ITEM_2BS_PARTITION_TABLE);
new_table.contents[idx] = item_last(idx as u16 - 1);
new_table.contents[idx + 1] = 0;
new_table.contents[idx + 2] = BLOCK_MARKER_END;
new_table.num_items = idx - 1;
new_table
}
pub const fn with_version(self, major: u16, minor: u16) -> Self {
let mut new_table = PartitionTableBlock::new();
let mut idx = 0;
while idx < self.num_items + 1 {
new_table.contents[idx] = self.contents[idx];
idx += 1;
}
new_table.contents[idx] = item_generic_2bs(0, 2, ITEM_1BS_VERSION);
idx += 1;
new_table.contents[idx] = ((major as u32) << 16) | minor as u32;
idx += 1;
new_table.contents[idx] = item_last(idx as u16 - 1);
new_table.contents[idx + 1] = 0;
new_table.contents[idx + 2] = BLOCK_MARKER_END;
new_table.num_items = idx - 1;
new_table
}
pub const fn with_sha256(self) -> Self {
let mut new_table = PartitionTableBlock::new();
let mut idx = 0;
while idx < self.num_items + 1 {
new_table.contents[idx] = self.contents[idx];
idx += 1;
}
new_table.contents[idx] = item_generic_2bs(1, 2, ITEM_2BS_HASH_DEF);
idx += 1;
new_table.contents[idx] = (idx + 1) as u32;
idx += 1;
let input = unsafe {
core::slice::from_raw_parts(new_table.contents.as_ptr() as *const u8, idx * 4)
};
let hash: [u8; 32] = sha2_const_stable::Sha256::new().update(input).finalize();
new_table.contents[idx] = item_generic_2bs(0, 9, ITEM_1BS_HASH_VALUE);
idx += 1;
let mut hash_idx = 0;
while hash_idx < hash.len() {
new_table.contents[idx] = u32::from_le_bytes([
hash[hash_idx],
hash[hash_idx + 1],
hash[hash_idx + 2],
hash[hash_idx + 3],
]);
idx += 1;
hash_idx += 4;
}
new_table.contents[idx] = item_last(idx as u16 - 1);
new_table.contents[idx + 1] = 0;
new_table.contents[idx + 2] = BLOCK_MARKER_END;
new_table.num_items = idx - 1;
new_table
}
}
impl Default for PartitionTableBlock {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u32)]
#[allow(missing_docs)]
pub enum PartitionFlag {
NotBootableArm = 1 << 9,
NotBootableRiscv = 1 << 10,
Uf2DownloadAbNonBootableOwnerAffinity = 1 << 11,
Uf2DownloadNoReboot = 1 << 13,
AcceptsDefaultFamilyRp2040 = 1 << 14,
AcceptsDefaultFamilyData = 1 << 16,
AcceptsDefaultFamilyRp2350ArmS = 1 << 17,
AcceptsDefaultFamilyRp2350Riscv = 1 << 18,
AcceptsDefaultFamilyRp2350ArmNs = 1 << 19,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u32)]
#[allow(missing_docs)]
pub enum UnpartitionedFlag {
Uf2DownloadNoReboot = 1 << 13,
AcceptsDefaultFamilyRp2040 = 1 << 14,
AcceptsDefaultFamilyAbsolute = 1 << 15,
AcceptsDefaultFamilyData = 1 << 16,
AcceptsDefaultFamilyRp2350ArmS = 1 << 17,
AcceptsDefaultFamilyRp2350Riscv = 1 << 18,
AcceptsDefaultFamilyRp2350ArmNs = 1 << 19,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Link {
Nothing,
ToA {
partition_idx: u8,
},
ToOwner {
partition_idx: u8,
},
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u32)]
pub enum Permission {
SecureRead = 1 << 26,
SecureWrite = 1 << 27,
NonSecureRead = 1 << 28,
NonSecureWrite = 1 << 29,
BootRead = 1 << 30,
BootWrite = 1 << 31,
}
impl Permission {
pub const fn is_in(self, mask: u32) -> bool {
(mask & (self as u32)) != 0
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Architecture {
Arm,
Riscv,
}
#[derive(Debug, Copy, Clone)]
pub enum Security {
Unspecified,
NonSecure,
Secure,
}
pub const fn item_generic_1bs(value: u16, length: u8, command: u8) -> u32 {
((value as u32) << 16) | ((length as u32) << 8) | (command as u32)
}
pub const fn item_generic_2bs(value: u8, length: u16, command: u8) -> u32 {
((value as u32) << 24) | ((length as u32) << 8) | (command as u32)
}
pub const fn item_ignored() -> u32 {
item_generic_2bs(0, 1, ITEM_2BS_IGNORED)
}
pub const fn item_image_type_invalid() -> u32 {
let value = IMAGE_TYPE_INVALID;
item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE)
}
pub const fn item_image_type_data() -> u32 {
let value = IMAGE_TYPE_DATA;
item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE)
}
pub const fn item_image_type_exe(security: Security, arch: Architecture) -> u32 {
let mut value = IMAGE_TYPE_EXE | IMAGE_TYPE_EXE_CHIP_RP2350;
match arch {
Architecture::Arm => {
value |= IMAGE_TYPE_EXE_CPU_ARM;
}
Architecture::Riscv => {
value |= IMAGE_TYPE_EXE_CPU_RISCV;
}
}
match security {
Security::Unspecified => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_UNSPECIFIED,
Security::NonSecure => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_NS,
Security::Secure => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_S,
}
item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE)
}
pub const fn item_last(length: u16) -> u32 {
item_generic_2bs(0, length, ITEM_2BS_LAST)
}
pub const fn item_vector_table(table_ptr: u32) -> [u32; 2] {
[item_generic_1bs(0, 2, ITEM_1BS_VECTOR_TABLE), table_ptr]
}
pub const fn item_entry_point(entry_point: u32, initial_sp: u32) -> [u32; 3] {
[
item_generic_1bs(0, 3, ITEM_1BS_ENTRY_POINT),
entry_point,
initial_sp,
]
}
pub const fn item_rolling_window(delta: u32) -> [u32; 2] {
[item_generic_1bs(0, 3, ITEM_1BS_ROLLING_WINDOW_DELTA), delta]
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn make_hashed_partition_table() {
let table = PartitionTableBlock::new()
.add_partition_item(
UnpartitionedSpace::new()
.with_permission(Permission::SecureRead)
.with_permission(Permission::SecureWrite)
.with_permission(Permission::NonSecureRead)
.with_permission(Permission::NonSecureWrite)
.with_permission(Permission::BootRead)
.with_permission(Permission::BootWrite)
.with_flag(UnpartitionedFlag::AcceptsDefaultFamilyAbsolute),
&[
Partition::new(2, 512)
.with_id(0)
.with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350ArmS)
.with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350Riscv)
.with_permission(Permission::SecureRead)
.with_permission(Permission::SecureWrite)
.with_permission(Permission::NonSecureRead)
.with_permission(Permission::NonSecureWrite)
.with_permission(Permission::BootRead)
.with_permission(Permission::BootWrite)
.with_name("A"),
Partition::new(513, 1023)
.with_id(1)
.with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350ArmS)
.with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350Riscv)
.with_link(Link::ToA { partition_idx: 0 })
.with_permission(Permission::SecureRead)
.with_permission(Permission::SecureWrite)
.with_permission(Permission::NonSecureRead)
.with_permission(Permission::NonSecureWrite)
.with_permission(Permission::BootRead)
.with_permission(Permission::BootWrite)
.with_name("B"),
],
)
.with_version(1, 0)
.with_sha256();
let expected = &[
0xffffded3, 0x02000c0a, 0xfc008000, 0xfc400002, 0xfc061001, 0x00000000, 0x00000000, 0x00004101, 0xfc7fe201, 0xfc061003, 0x00000001, 0x00000000, 0x00004201, 0x00000248, 0x00010000, 0x01000247, 0x00000011, 0x0000094b, 0x1945cdad, 0x6b5f9773, 0xe2bf39bd, 0xb243e599, 0xab2f0e9a, 0x4d5d6d0b, 0xf973050f, 0x5ab6dadb, 0x000019ff, 0x00000000, 0xab123579, ];
assert_eq!(
&table.contents[..29],
expected,
"{:#010x?}\n != \n{:#010x?}",
&table.contents[0..29],
expected,
);
}
}