Skip to main content

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