use std::num::{NonZeroU32, NonZeroU64};
use std::sync::Arc;
use crate::cpu::armv7m::Window;
use crate::elf::Machine;
use crate::emulator::{Alias, Boot, CoreSpec};
#[cfg(not(emulator_absent))]
pub(crate) const CORE: &str = "the ARMv7-M core";
const ELF: Machine = Machine::new(40, false);
const RESET_VECTOR: u64 = 0;
const SYSTEM_EXCEPTIONS: u64 = 16;
const VECTOR_BYTES: u64 = 4;
const TABLE_ALIGNMENT: u64 = 128;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Variant {
M3,
M4,
M7,
}
impl Variant {
pub const ALL: [Self; 3] = [Self::M3, Self::M4, Self::M7];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::M3 => "cortex-m3",
Self::M4 => "cortex-m4",
Self::M7 => "cortex-m7",
}
}
#[must_use]
pub(crate) const fn dsp(self) -> bool {
matches!(self, Self::M4 | Self::M7)
}
#[must_use]
pub(crate) const fn fpus(self) -> &'static [Fpu] {
match self {
Self::M3 => &[Fpu::None],
Self::M4 => &[Fpu::None, Fpu::FpV4Sp],
Self::M7 => &[Fpu::None, Fpu::FpV5Sp, Fpu::FpV5Dp],
}
}
#[must_use]
pub(crate) const fn mpus(self) -> &'static [MpuRegions] {
match self {
Self::M3 | Self::M4 => &[MpuRegions::None, MpuRegions::Eight],
Self::M7 => &[MpuRegions::None, MpuRegions::Eight, MpuRegions::Sixteen],
}
}
#[must_use]
pub(crate) const fn default_fpu(self) -> Fpu {
match self {
Self::M3 => Fpu::None,
Self::M4 => Fpu::FpV4Sp,
Self::M7 => Fpu::FpV5Dp,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Fpu {
None,
FpV4Sp,
FpV5Sp,
FpV5Dp,
}
impl Fpu {
#[must_use]
pub(crate) const fn name(self) -> &'static str {
match self {
Self::None => "none",
Self::FpV4Sp => "fpv4-sp",
Self::FpV5Sp => "fpv5-sp",
Self::FpV5Dp => "fpv5-dp",
}
}
}
impl std::fmt::Display for Fpu {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.name())
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum MpuRegions {
None = 0,
#[default]
Eight = 8,
Sixteen = 16,
}
impl MpuRegions {
#[must_use]
pub(crate) const fn count(self) -> u8 {
self as u8
}
}
const CORTEX_M4_CPUID: u32 = 0x410F_C241;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Caches {
pub clidr: u32,
pub ctr: u32,
pub ccsidr: &'static [u32],
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum BranchPrediction {
#[default]
Unsupported,
Always,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ImplementationRegister {
pub offset: u64,
pub reset: u32,
pub writable: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Core {
pub variant: Variant,
pub clock_hz: NonZeroU64,
pub interrupt_lines: NonZeroU32,
pub priority_bits: u8,
pub refclk_divider: NonZeroU32,
pub instructions_per_second: NonZeroU64,
pub boot: &'static str,
}
impl Core {
#[must_use]
pub fn spec(self, ppb: &[Window]) -> CoreSpec {
Config::new(self, ppb).spec()
}
#[must_use]
pub fn spec_with_parts(self, ppb: &[Window], fpu: Fpu, mpu_regions: MpuRegions) -> CoreSpec {
let mut config = Config::new(self, ppb);
config.fpu = fpu;
config.mpu_regions = mpu_regions;
config.spec()
}
pub(crate) fn table_alignment(self) -> NonZeroU64 {
let size = (SYSTEM_EXCEPTIONS + u64::from(self.interrupt_lines.get())) * VECTOR_BYTES;
NonZeroU64::new(size.next_power_of_two().max(TABLE_ALIGNMENT))
.expect("a power of two is above zero")
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Config {
pub core: Core,
pub ppb: Vec<Window>,
pub fpu: Fpu,
pub mpu_regions: MpuRegions,
pub cpuid: u32,
pub caches: Option<Caches>,
pub branch_prediction: BranchPrediction,
pub implementation_registers: Vec<ImplementationRegister>,
pub vector_table: Option<u32>,
}
impl Config {
#[must_use]
pub fn new(core: Core, ppb: &[Window]) -> Self {
Self {
core,
ppb: ppb.to_vec(),
fpu: core.variant.default_fpu(),
mpu_regions: MpuRegions::default(),
cpuid: CORTEX_M4_CPUID,
caches: None,
branch_prediction: BranchPrediction::Unsupported,
implementation_registers: Vec::new(),
vector_table: None,
}
}
#[must_use]
pub fn spec(self) -> CoreSpec {
let core = self.core;
let vector_table = self.vector_table;
let mut spec = CoreSpec::new(
Arc::new(move |endian| crate::cpu::armv7m::processor(&self, endian)),
core.instructions_per_second,
Boot::new(
vector_table.map_or(RESET_VECTOR, u64::from),
core.table_alignment(),
),
);
spec.boot.region = Some(core.boot);
if vector_table.is_none() {
spec.aliases = vec![Alias {
region: core.boot,
at: RESET_VECTOR,
}];
}
spec.elf = Some(ELF);
spec.code_address = crate::cpu::armv7m::code_address;
spec
}
}
#[cfg(test)]
mod tests {
use super::*;
fn core() -> Core {
Core {
variant: Variant::M4,
clock_hz: NonZeroU64::new(168_000_000).unwrap(),
interrupt_lines: NonZeroU32::new(91).unwrap(),
priority_bits: 4,
refclk_divider: NonZeroU32::new(8).unwrap(),
instructions_per_second: NonZeroU64::new(250_000_000).unwrap(),
boot: "flash",
}
}
const WINDOWS: [Window; 2] = [
Window {
base: 0xE000_0000,
size: 0x1000,
},
Window {
base: 0xE000_1000,
size: 0x8,
},
];
#[test]
fn a_config_takes_the_variants_fullest_unit_and_eight_regions_unless_a_part_names_them() {
let config = Config::new(core(), &WINDOWS);
assert_eq!(
(config.fpu, config.mpu_regions, &config.ppb[..]),
(Fpu::FpV4Sp, MpuRegions::Eight, &WINDOWS[..])
);
let m3 = Config::new(
Core {
variant: Variant::M3,
..core()
},
&[],
);
assert_eq!(m3.fpu, Fpu::None);
}
#[test]
fn a_vector_table_is_aligned_to_its_size_and_never_less_than_128_bytes() {
let alignment = |lines| {
Core {
interrupt_lines: NonZeroU32::new(lines).unwrap(),
..core()
}
.spec(&WINDOWS)
.boot
.alignment
.get()
};
assert_eq!(alignment(91), 512, "107 entries are 428 bytes");
assert_eq!(alignment(1), 128, "17 entries are 68 bytes");
assert_eq!(alignment(16), 128, "32 entries are 128 bytes");
assert_eq!(alignment(17), 256);
}
#[test]
fn the_boot_region_is_the_one_the_core_also_finds_at_its_reset_vector() {
let spec = core().spec(&WINDOWS);
assert_eq!(
spec.aliases,
[Alias {
region: "flash",
at: 0
}]
);
assert_eq!(spec.boot.reset, 0);
let from_system = Core {
boot: "system",
..core()
};
assert_eq!(
from_system.spec(&WINDOWS).aliases,
[Alias {
region: "system",
at: 0
}],
"the SoC's boot configuration says which region, not the family"
);
}
#[test]
fn a_vector_table_put_elsewhere_at_reset_is_where_the_core_boots_with_nothing_at_zero() {
let mut config = Config::new(core(), &WINDOWS);
config.vector_table = Some(0x0010_0000);
let spec = config.spec();
assert_eq!(
(spec.boot.reset, spec.boot.alignment.get(), spec.aliases),
(0x0010_0000, 512, Vec::new())
);
assert_eq!(
spec.boot.region,
Some("flash"),
"the region, which the session still checks"
);
}
}