factbird-common 0.2.1

Common files for the Factbird Duo & Bootloader
Documentation
//! # `memory_regions`
//!
//! This module automatically takes linker flags and makes functions which return slices mapping to
//! the memory region.

use core::convert::TryInto;
use core::slice;
use core::{cmp::Ordering, fmt::Debug};

use crate::HardwareRevision;

#[cfg(not(feature = "firmware"))]
include!(concat!(env!("OUT_DIR"), "/memory_map.rs"));

/// Contains the version of a firmware
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct FirmwareVersion {
    pub semver_major: u8,
    pub semver_minor: u8,
    pub semver_patch: u8,
}

#[cfg(feature = "defmt")]
impl defmt::Format for FirmwareVersion {
    fn format(&self, fmt: defmt::Formatter) {
        defmt::write!(
            fmt,
            "{=u8}.{=u8}.{=u8}",
            self.semver_major,
            self.semver_minor,
            self.semver_patch
        )
    }
}

impl PartialOrd for FirmwareVersion {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        match self.semver_major.cmp(&other.semver_major) {
            Ordering::Equal => {}
            ord => return Some(ord),
        }

        match self.semver_minor.cmp(&other.semver_minor) {
            Ordering::Equal => {}
            ord => return Some(ord),
        }

        Some(self.semver_patch.cmp(&other.semver_patch))
    }
}

/// Contains the metadata of a firmware
#[derive(Debug, Clone)]
pub struct FirmwareHeader {
    /// The CRC is an CRC32-IEEE
    pub crc: u32,
    pub firmware_size: u32,
    pub version: FirmwareVersion,
    pub hw_version: HardwareRevision,
    pub git_sha: heapless::String<20>,
}

impl FirmwareVersion {
    #[must_use]
    pub const fn new(major: u8, minor: u8, patch: u8) -> Self {
        // This might become more advanced in the future
        Self {
            semver_major: major,
            semver_minor: minor,
            semver_patch: patch,
        }
    }

    #[must_use]
    pub const fn as_bytes(self) -> [u8; 3] {
        [
            self.semver_patch as u8,
            self.semver_minor as u8,
            self.semver_major as u8,
        ]
    }
}

/// Layout of the header: | CRC32 (4b) | `FIRMWARE_SIZE` (4b) | VERSION (4b) | GIT_SHA (20b) | `HEADER_SIZE` - 32 0s |
impl FirmwareHeader {
    /// Creates a firmware header from a section of memory
    #[must_use]
    pub fn new_from_slice(s: &[u8]) -> Self {
        let sha_len = s[15] as usize;
        let git_sha = if sha_len < 20 {
            heapless::String::from(core::str::from_utf8(&s[16..16 + sha_len]).unwrap_or(""))
        } else {
            heapless::String::new()
        };

        Self {
            crc: u32::from_le_bytes(s[0..4].try_into().unwrap()),
            firmware_size: u32::from_le_bytes(s[4..8].try_into().unwrap()),
            hw_version: HardwareRevision::from(u32::from_le_bytes(s[8..12].try_into().unwrap())),
            version: FirmwareVersion::new(s[14], s[13], s[12]),
            git_sha,
        }
    }

    #[must_use]
    pub fn as_bytes(&self) -> [u8; 32] {
        let mut ret = [0_u8; 32];
        let sha = self.git_sha.as_bytes();
        ret[0..4].copy_from_slice(&self.crc.to_le_bytes());
        ret[4..8].copy_from_slice(&self.firmware_size.to_le_bytes());
        ret[8..12].copy_from_slice(&(self.hw_version as u32).to_le_bytes());
        ret[12..15].copy_from_slice(&self.version.as_bytes());
        ret[15] = sha.len() as u8;
        ret[16..16 + sha.len()].copy_from_slice(&sha);
        ret
    }
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[repr(C)]
pub enum ImageFlag {
    /// If the application image is running for the first time and never executed before.
    New = 0xFF,
    /// The application image is marked to execute for test boot.
    CommitPending = 0xFE,
    /// The application image is marked valid and committed.
    Valid = 0xFC,
    /// The application image is marked invalid.
    Invalid = 0xF8,
}

impl From<u8> for ImageFlag {
    fn from(v: u8) -> Self {
        match v {
            0xFF => Self::New,
            0xFE => Self::CommitPending,
            0xFC => Self::Valid,
            _ => Self::Invalid,
        }
    }
}

/// Contains run-time metadata of the firmware, generated by OTA
pub struct ImageDescriptor {
    pub magic: [u8; 7],
    pub image_flags: ImageFlag,
    pub sequence_nr: u32,
    pub signature_size: usize,
    pub signature: [u8; 256],
}

impl Debug for ImageDescriptor {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        if self.magic_valid() {
            write!(
                f,
                "ImageDescriptor {{ image_flags: {:?}, sequence_nr: {}, signature_size: {} }}",
                self.image_flags, self.sequence_nr, self.signature_size
            )
        } else {
            write!(f, "ImageDescriptor {{ Invalid }}",)
        }
    }
}

const MAGIC: [u8; 7] = *b"@FB-DUO";

impl ImageDescriptor {
    pub fn new(sequence_nr: u32, signature_size: usize, signature: [u8; 256]) -> Self {
        Self {
            magic: MAGIC,
            image_flags: ImageFlag::New,
            sequence_nr,
            signature_size,
            signature,
        }
    }

    /// Creates a firmware header from a section of memory
    pub fn new_from_slice(s: &[u8]) -> Self {
        Self {
            magic: s[0..7].try_into().unwrap(),
            image_flags: s[7].into(),
            sequence_nr: u32::from_le_bytes(s[8..12].try_into().unwrap()),
            signature_size: u32::from_le_bytes(s[12..16].try_into().unwrap()) as usize,
            signature: s[16..272].try_into().unwrap(),
        }
    }

    pub fn magic_valid(&self) -> bool {
        self.magic == MAGIC
    }

    pub fn as_bytes(&self) -> [u8; 512] {
        let mut ret = [0u8; 512];
        ret[0..7].copy_from_slice(&self.magic);
        ret[7] = self.image_flags as u8;
        ret[8..12].copy_from_slice(&self.sequence_nr.to_le_bytes());
        ret[12..16].copy_from_slice(&(self.signature_size as u32).to_le_bytes());
        ret[16..272].copy_from_slice(&self.signature);
        ret
    }
}

/// The available flash regions in the chip
#[derive(Debug, Copy, Clone)]
pub enum FlashRegion {
    Firmware,
    UpdateFirmware,
    Swap,
}

impl FlashRegion {
    pub fn as_slice(&self) -> &'static [u8] {
        unsafe {
            slice::from_raw_parts(
                self.start_addr() as *const u32 as *const u8,
                self.len() as *const u32 as usize,
            )
        }
    }

    pub fn header(&self) -> FirmwareHeader {
        FirmwareHeader::new_from_slice(unsafe {
            slice::from_raw_parts((self.start_addr() + 0x200) as *const u8, header_len())
        })
    }

    pub fn image_descriptor(&self) -> ImageDescriptor {
        ImageDescriptor::new_from_slice(unsafe {
            slice::from_raw_parts(self.start_addr() as *const u8, image_descriptor_len())
        })
    }

    pub fn header_start_offset(&self) -> u32 {
        self.start_offset() + image_descriptor_len() as u32
    }

    pub fn start_offset(&self) -> u32 {
        self.start_addr() - 0x0800_0000
    }

    pub fn start_addr(&self) -> u32 {
        #[cfg(feature = "firmware")]
        {
            extern "C" {
                static __firmware_start__: u32;
                static __fwupdate_start__: u32;
                static __fwswap_start__: u32;
            }
            return match self {
                Self::Firmware => unsafe { &__firmware_start__ as *const u32 as u32 },
                Self::UpdateFirmware => unsafe { &__fwupdate_start__ as *const u32 as u32 },
                Self::Swap => unsafe { &__fwswap_start__ as *const u32 as u32 },
            };
        }
        #[cfg(not(feature = "firmware"))]
        match self {
            Self::Firmware => __firmware_start__,
            Self::UpdateFirmware => __fwupdate_start__,
            Self::Swap => __fwswap_start__,
        }
    }

    pub fn end_offset(&self) -> u32 {
        self.end_addr() - 0x08000000
    }

    pub fn end_addr(&self) -> u32 {
        #[cfg(feature = "firmware")]
        {
            extern "C" {
                static __firmware_end__: u32;
                static __fwupdate_end__: u32;
                static __fwswap_end__: u32;
            }
            return match self {
                Self::Firmware => unsafe { &__firmware_end__ as *const u32 as u32 },
                Self::UpdateFirmware => unsafe { &__fwupdate_end__ as *const u32 as u32 },
                Self::Swap => unsafe { &__fwswap_end__ as *const u32 as u32 },
            };
        }

        #[cfg(not(feature = "firmware"))]
        match self {
            Self::Firmware => __firmware_end__,
            Self::UpdateFirmware => __fwupdate_end__,
            Self::Swap => __fwswap_end__,
        }
    }

    pub fn len(&self) -> usize {
        (self.end_addr() - self.start_addr()) as usize
    }

    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }
}

/// Get the size of the header
#[inline(always)]
pub fn full_header_len() -> usize {
    #[cfg(feature = "firmware")]
    {
        extern "C" {
            static __header_start__: u32;
            static __header_end__: u32;
        }

        return unsafe {
            &__header_end__ as *const u32 as usize - &__header_start__ as *const u32 as usize
        };
    }

    #[cfg(not(feature = "firmware"))]
    return (__header_end__ - __header_start__) as usize;
}

pub fn header_len() -> usize {
    full_header_len() - image_descriptor_len()
}

pub fn image_descriptor_len() -> usize {
    0x200
}