probe_rs_target/
chip_family.rs

1use crate::memory::RegionMergeIterator as _;
2use crate::serialize::hex_jep106_option;
3use crate::{CoreAccessOptions, chip_detection::ChipDetectionMethod};
4use crate::{MemoryRange, MemoryRegion};
5
6use super::chip::Chip;
7use super::flash_algorithm::RawFlashAlgorithm;
8use jep106::JEP106Code;
9
10use serde::{Deserialize, Serialize};
11
12/// Source of a target description.
13///
14/// This is used for diagnostics, when
15/// an error related to a target description occurs.
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub enum TargetDescriptionSource {
18    /// The target description is a generic target description,
19    /// which just describes a core type (e.g. M4), without any
20    /// flash algorithm or memory description.
21    Generic,
22    /// The target description is a built-in target description,
23    /// which was included into probe-rs at compile time.
24    BuiltIn,
25    /// The target description was from an external source
26    /// during runtime.
27    External,
28}
29
30/// Type of a supported core.
31#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(rename_all = "snake_case")]
33pub enum CoreType {
34    /// ARMv6-M: Cortex M0, M0+, M1
35    Armv6m,
36    /// ARMv7-A: Cortex A7, A9, A15
37    Armv7a,
38    /// ARMv7-M: Cortex M3
39    Armv7m,
40    /// ARMv7e-M: Cortex M4, M7
41    Armv7em,
42    /// ARMv7-A: Cortex A35, A55, A72
43    Armv8a,
44    /// ARMv8-M: Cortex M23, M33
45    Armv8m,
46    /// RISC-V
47    Riscv,
48    /// Xtensa - TODO: may need to split into NX, LX6 and LX7
49    Xtensa,
50}
51
52impl CoreType {
53    /// Returns true if the core type is an ARM Cortex-M
54    pub fn is_cortex_m(&self) -> bool {
55        matches!(
56            self,
57            CoreType::Armv6m | CoreType::Armv7em | CoreType::Armv7m | CoreType::Armv8m
58        )
59    }
60
61    fn is_riscv(&self) -> bool {
62        matches!(self, CoreType::Riscv)
63    }
64
65    fn is_xtensa(&self) -> bool {
66        matches!(self, CoreType::Xtensa)
67    }
68
69    fn is_arm(&self) -> bool {
70        matches!(
71            self,
72            CoreType::Armv6m
73                | CoreType::Armv7a
74                | CoreType::Armv7em
75                | CoreType::Armv7m
76                | CoreType::Armv8a
77                | CoreType::Armv8m
78        )
79    }
80
81    /// Returns the parent architecture family of this core type.
82    pub fn architecture(&self) -> Architecture {
83        match self {
84            CoreType::Riscv => Architecture::Riscv,
85            CoreType::Xtensa => Architecture::Xtensa,
86            _ => Architecture::Arm,
87        }
88    }
89}
90
91/// The architecture family of a specific [`CoreType`].
92#[derive(Clone, Copy, Debug, PartialEq, Eq)]
93pub enum Architecture {
94    /// An ARM core of one of the specific types [`CoreType::Armv6m`], [`CoreType::Armv7m`], [`CoreType::Armv7em`] or [`CoreType::Armv8m`]
95    Arm,
96    /// A RISC-V core.
97    Riscv,
98    /// An Xtensa core.
99    Xtensa,
100}
101
102/// Instruction set used by a core
103#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
104pub enum InstructionSet {
105    /// ARM Thumb 2 instruction set
106    Thumb2,
107    /// ARM A32 (often just called ARM) instruction set
108    A32,
109    /// ARM A64 (aarch64) instruction set
110    A64,
111    /// RISC-V 32-bit uncompressed instruction sets (RV32) - covers all ISA variants that use 32-bit instructions.
112    RV32,
113    /// RISC-V 32-bit compressed instruction sets (RV32C) - covers all ISA variants that allow compressed 16-bit instructions.
114    RV32C,
115    /// Xtensa instruction set
116    Xtensa,
117}
118
119impl InstructionSet {
120    /// Get the instruction set from a rustc target triple.
121    pub fn from_target_triple(triple: &str) -> Option<Self> {
122        match triple.split('-').next()? {
123            "thumbv6m" | "thumbv7em" | "thumbv7m" | "thumbv8m" => Some(InstructionSet::Thumb2),
124            "arm" => Some(InstructionSet::A32),
125            "aarch64" => Some(InstructionSet::A64),
126            "xtensa" => Some(InstructionSet::Xtensa),
127            other => {
128                if let Some(features) = other.strip_prefix("riscv32") {
129                    if features.contains('c') {
130                        Some(InstructionSet::RV32C)
131                    } else {
132                        Some(InstructionSet::RV32)
133                    }
134                } else {
135                    None
136                }
137            }
138        }
139    }
140
141    /// Get the minimum instruction size in bytes.
142    pub fn get_minimum_instruction_size(&self) -> u8 {
143        match self {
144            InstructionSet::Thumb2 => {
145                // Thumb2 uses a variable size (2 or 4) instruction set. For our purposes, we set it as 2, so that we don't accidentally read outside of addressable memory.
146                2
147            }
148            InstructionSet::A32 => 4,
149            InstructionSet::A64 => 4,
150            InstructionSet::RV32 => 4,
151            InstructionSet::RV32C => 2,
152            InstructionSet::Xtensa => 2,
153        }
154    }
155    /// Get the maximum instruction size in bytes. All supported architectures have a maximum instruction size of 4 bytes.
156    pub fn get_maximum_instruction_size(&self) -> u8 {
157        // TODO: Xtensa may have wide instructions
158        4
159    }
160
161    /// Returns whether a CPU with the `self` instruction set is compatible with a program compiled for `instr_set`.
162    pub fn is_compatible(&self, instr_set: InstructionSet) -> bool {
163        if *self == instr_set {
164            return true;
165        }
166
167        matches!(
168            (self, instr_set),
169            (InstructionSet::RV32C, InstructionSet::RV32)
170        )
171    }
172}
173
174/// The current endianness of a core
175#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
176pub enum Endian {
177    /// Little endian mode -- this is the most common mode
178    Little,
179    /// Big endian mode -- this is common on network hardware
180    Big,
181}
182
183/// This describes a chip family with all its variants.
184///
185/// This struct is usually read from a target description
186/// file.
187#[derive(Debug, Clone, Serialize, Deserialize)]
188#[serde(deny_unknown_fields)]
189pub struct ChipFamily {
190    /// This is the name of the chip family in base form.
191    /// E.g. `nRF52832`.
192    pub name: String,
193    /// The JEP106 code of the manufacturer.
194    #[serde(serialize_with = "hex_jep106_option")]
195    pub manufacturer: Option<JEP106Code>,
196    /// The method(s) that may be able to identify targets in this family.
197    #[serde(default)]
198    pub chip_detection: Vec<ChipDetectionMethod>,
199    /// The `target-gen` process will set this to `true`.
200    /// Please change this to `false` if this file is modified from the generated, or is a manually created target description.
201    #[serde(default)]
202    pub generated_from_pack: bool,
203    /// The latest release of the pack file from which this was generated.
204    /// Values:
205    /// - `Some("1.3.0")` if the latest pack file release was for example "1.3.0".
206    /// - `None` if this was not generated from a pack file, or has been modified since it was generated.
207    #[serde(default)]
208    pub pack_file_release: Option<String>,
209    /// This vector holds all the variants of the family.
210    pub variants: Vec<Chip>,
211    /// This vector holds all available algorithms.
212    #[serde(default)]
213    pub flash_algorithms: Vec<RawFlashAlgorithm>,
214    #[serde(skip, default = "default_source")]
215    /// Source of the target description, used for diagnostics
216    pub source: TargetDescriptionSource,
217}
218
219fn default_source() -> TargetDescriptionSource {
220    TargetDescriptionSource::External
221}
222
223impl ChipFamily {
224    /// Validates the [`ChipFamily`] such that probe-rs can make assumptions about the correctness without validating thereafter.
225    ///
226    /// This method should be called right after the [`ChipFamily`] is created!
227    pub fn validate(&self) -> Result<(), String> {
228        self.reject_duplicate_target_names()?;
229        self.ensure_algorithms_exist()?;
230        self.ensure_at_least_one_core()?;
231        self.reject_incorrect_core_access_options()?;
232        self.validate_memory_regions()?;
233        self.validate_rtt_scan_regions()?;
234
235        Ok(())
236    }
237
238    /// Rejects target descriptions with duplicate target names. Only one of these targets can
239    /// be selected, so having multiple is probably a mistake.
240    fn reject_duplicate_target_names(&self) -> Result<(), String> {
241        use std::collections::HashSet;
242
243        let mut seen = HashSet::new();
244
245        for chip in &self.variants {
246            if !seen.insert(&chip.name) {
247                return Err(format!(
248                    "Target {} appears multiple times in {}",
249                    chip.name, self.name,
250                ));
251            }
252        }
253
254        Ok(())
255    }
256
257    /// Make sure the algorithms used on the variant actually exist on the family (this is basically a check for typos).
258    fn ensure_algorithms_exist(&self) -> Result<(), String> {
259        for variant in &self.variants {
260            for algorithm_name in variant.flash_algorithms.iter() {
261                if !self
262                    .flash_algorithms
263                    .iter()
264                    .any(|algorithm| &algorithm.name == algorithm_name)
265                {
266                    return Err(format!(
267                        "The chip variant {chip_name} refers to a flash algorithm {algorithm_name}, \
268                        which is not defined in the {family_name} family.",
269                        chip_name = variant.name,
270                        family_name = self.name,
271                    ));
272                }
273            }
274        }
275
276        Ok(())
277    }
278
279    // Check that there is at least one core.
280    fn ensure_at_least_one_core(&self) -> Result<(), String> {
281        for variant in &self.variants {
282            let Some(core) = variant.cores.first() else {
283                return Err(format!(
284                    "variant `{}` does not contain any cores",
285                    variant.name
286                ));
287            };
288
289            // Make sure that the core types (architectures) are not mixed.
290            let architecture = core.core_type.architecture();
291            if variant
292                .cores
293                .iter()
294                .any(|core| core.core_type.architecture() != architecture)
295            {
296                return Err(format!(
297                    "variant `{}` contains mixed core architectures",
298                    variant.name
299                ));
300            }
301        }
302
303        Ok(())
304    }
305
306    fn reject_incorrect_core_access_options(&self) -> Result<(), String> {
307        // We check each variant if it is valid.
308        // If one is not valid, we abort with an appropriate error message.
309        for variant in &self.variants {
310            // Core specific validation logic based on type
311            for core in variant.cores.iter() {
312                // The core access options must match the core type specified
313                match &core.core_access_options {
314                    CoreAccessOptions::Arm(_) if !core.core_type.is_arm() => {
315                        return Err(format!(
316                            "Arm options don't match core type {:?} on core {}",
317                            core.core_type, core.name
318                        ));
319                    }
320                    CoreAccessOptions::Riscv(_) if !core.core_type.is_riscv() => {
321                        return Err(format!(
322                            "Riscv options don't match core type {:?} on core {}",
323                            core.core_type, core.name
324                        ));
325                    }
326                    CoreAccessOptions::Xtensa(_) if !core.core_type.is_xtensa() => {
327                        return Err(format!(
328                            "Xtensa options don't match core type {:?} on core {}",
329                            core.core_type, core.name
330                        ));
331                    }
332                    CoreAccessOptions::Arm(options) => {
333                        if matches!(core.core_type, CoreType::Armv7a | CoreType::Armv8a)
334                            && options.debug_base.is_none()
335                        {
336                            return Err(format!("Core {} requires setting debug_base", core.name));
337                        }
338
339                        if core.core_type == CoreType::Armv8a && options.cti_base.is_none() {
340                            return Err(format!("Core {} requires setting cti_base", core.name));
341                        }
342                    }
343                    _ => {}
344                }
345            }
346        }
347
348        Ok(())
349    }
350
351    /// Ensures that the memory is assigned to a core, and that all the cores exist
352    fn validate_memory_regions(&self) -> Result<(), String> {
353        for variant in &self.variants {
354            let core_names = variant
355                .cores
356                .iter()
357                .map(|core| &core.name)
358                .collect::<Vec<_>>();
359
360            if variant.memory_map.is_empty() && self.source != TargetDescriptionSource::Generic {
361                return Err(format!(
362                    "Variant {} does not contain any memory regions",
363                    variant.name
364                ));
365            }
366
367            for memory in &variant.memory_map {
368                for core in memory.cores() {
369                    if !core_names.contains(&core) {
370                        return Err(format!(
371                            "Variant {}, memory region {:?} is assigned to a non-existent core {}",
372                            variant.name, memory, core
373                        ));
374                    }
375                }
376
377                if memory.cores().is_empty() {
378                    return Err(format!(
379                        "Variant {}, memory region {:?} is not assigned to a core",
380                        variant.name, memory
381                    ));
382                }
383            }
384        }
385
386        Ok(())
387    }
388
389    fn validate_rtt_scan_regions(&self) -> Result<(), String> {
390        for variant in &self.variants {
391            let Some(rtt_scan_ranges) = &variant.rtt_scan_ranges else {
392                return Ok(());
393            };
394
395            let ram_regions = variant
396                .memory_map
397                .iter()
398                .filter_map(MemoryRegion::as_ram_region)
399                .merge_consecutive()
400                .collect::<Vec<_>>();
401
402            // The custom ranges must all be enclosed by exactly one of
403            // the defined RAM regions.
404            for scan_range in rtt_scan_ranges {
405                if ram_regions
406                    .iter()
407                    .any(|region| region.range.contains_range(scan_range))
408                {
409                    continue;
410                }
411
412                return Err(format!(
413                    "The RTT scan region ({:#010x?}) of {} is not enclosed by any single RAM region.",
414                    scan_range, variant.name,
415                ));
416            }
417        }
418
419        Ok(())
420    }
421}
422
423impl ChipFamily {
424    /// Get the different [Chip]s which are part of this
425    /// family.
426    pub fn variants(&self) -> &[Chip] {
427        &self.variants
428    }
429
430    /// Get all flash algorithms for this family of chips.
431    pub fn algorithms(&self) -> &[RawFlashAlgorithm] {
432        &self.flash_algorithms
433    }
434
435    /// Try to find a [RawFlashAlgorithm] with a given name.
436    pub fn get_algorithm(&self, name: impl AsRef<str>) -> Option<&RawFlashAlgorithm> {
437        let name = name.as_ref();
438        self.flash_algorithms.iter().find(|elem| elem.name == name)
439    }
440
441    /// Tries to find a [RawFlashAlgorithm] with a given name and returns it with the
442    /// core assignment fixed to the cores of the given chip.
443    pub fn get_algorithm_for_chip(
444        &self,
445        name: impl AsRef<str>,
446        chip: &Chip,
447    ) -> Option<RawFlashAlgorithm> {
448        self.get_algorithm(name).map(|algo| {
449            let mut algo_cores = if algo.cores.is_empty() {
450                chip.cores.iter().map(|core| core.name.clone()).collect()
451            } else {
452                algo.cores.clone()
453            };
454
455            // only keep cores in the algo that are also in the chip
456            algo_cores.retain(|algo_core| {
457                chip.cores
458                    .iter()
459                    .any(|chip_core| &chip_core.name == algo_core)
460            });
461
462            RawFlashAlgorithm {
463                cores: algo_cores,
464                ..algo.clone()
465            }
466        })
467    }
468}