probe_rs_target/
chip_family.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
use crate::memory::RegionMergeIterator as _;
use crate::serialize::hex_jep106_option;
use crate::{chip_detection::ChipDetectionMethod, CoreAccessOptions};
use crate::{MemoryRange, MemoryRegion};

use super::chip::Chip;
use super::flash_algorithm::RawFlashAlgorithm;
use jep106::JEP106Code;

use serde::{Deserialize, Serialize};

/// Source of a target description.
///
/// This is used for diagnostics, when
/// an error related to a target description occurs.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TargetDescriptionSource {
    /// The target description is a generic target description,
    /// which just describes a core type (e.g. M4), without any
    /// flash algorithm or memory description.
    Generic,
    /// The target description is a built-in target description,
    /// which was included into probe-rs at compile time.
    BuiltIn,
    /// The target description was from an external source
    /// during runtime.
    External,
}

/// Type of a supported core.
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CoreType {
    /// ARMv6-M: Cortex M0, M0+, M1
    Armv6m,
    /// ARMv7-A: Cortex A7, A9, A15
    Armv7a,
    /// ARMv7-M: Cortex M3
    Armv7m,
    /// ARMv7e-M: Cortex M4, M7
    Armv7em,
    /// ARMv7-A: Cortex A35, A55, A72
    Armv8a,
    /// ARMv8-M: Cortex M23, M33
    Armv8m,
    /// RISC-V
    Riscv,
    /// Xtensa - TODO: may need to split into NX, LX6 and LX7
    Xtensa,
}

impl CoreType {
    /// Returns true if the core type is an ARM Cortex-M
    pub fn is_cortex_m(&self) -> bool {
        matches!(
            self,
            CoreType::Armv6m | CoreType::Armv7em | CoreType::Armv7m | CoreType::Armv8m
        )
    }

    fn is_riscv(&self) -> bool {
        matches!(self, CoreType::Riscv)
    }

    fn is_xtensa(&self) -> bool {
        matches!(self, CoreType::Xtensa)
    }

    fn is_arm(&self) -> bool {
        matches!(
            self,
            CoreType::Armv6m
                | CoreType::Armv7a
                | CoreType::Armv7em
                | CoreType::Armv7m
                | CoreType::Armv8a
                | CoreType::Armv8m
        )
    }
}

/// The architecture family of a specific [`CoreType`].
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Architecture {
    /// An ARM core of one of the specific types [`CoreType::Armv6m`], [`CoreType::Armv7m`], [`CoreType::Armv7em`] or [`CoreType::Armv8m`]
    Arm,
    /// A RISC-V core.
    Riscv,
    /// An Xtensa core.
    Xtensa,
}

impl CoreType {
    /// Returns the parent architecture family of this core type.
    pub fn architecture(&self) -> Architecture {
        match self {
            CoreType::Riscv => Architecture::Riscv,
            CoreType::Xtensa => Architecture::Xtensa,
            _ => Architecture::Arm,
        }
    }
}

/// Instruction set used by a core
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum InstructionSet {
    /// ARM Thumb 2 instruction set
    Thumb2,
    /// ARM A32 (often just called ARM) instruction set
    A32,
    /// ARM A64 (aarch64) instruction set
    A64,
    /// RISC-V 32-bit uncompressed instruction sets (RV32) - covers all ISA variants that use 32-bit instructions.
    RV32,
    /// RISC-V 32-bit compressed instruction sets (RV32C) - covers all ISA variants that allow compressed 16-bit instructions.
    RV32C,
    /// Xtensa instruction set
    Xtensa,
}

impl InstructionSet {
    /// Get the instruction set from a rustc target triple.
    pub fn from_target_triple(triple: &str) -> Option<Self> {
        match triple.split('-').next()? {
            "thumbv6m" | "thumbv7em" | "thumbv7m" | "thumbv8m" => Some(InstructionSet::Thumb2),
            "arm" => Some(InstructionSet::A32),
            "aarch64" => Some(InstructionSet::A64),
            "xtensa" => Some(InstructionSet::Xtensa),
            other => {
                if let Some(features) = other.strip_prefix("riscv32") {
                    if features.contains('c') {
                        Some(InstructionSet::RV32C)
                    } else {
                        Some(InstructionSet::RV32)
                    }
                } else {
                    None
                }
            }
        }
    }

    /// Get the minimum instruction size in bytes.
    pub fn get_minimum_instruction_size(&self) -> u8 {
        match self {
            InstructionSet::Thumb2 => {
                // 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.
                2
            }
            InstructionSet::A32 => 4,
            InstructionSet::A64 => 4,
            InstructionSet::RV32 => 4,
            InstructionSet::RV32C => 2,
            InstructionSet::Xtensa => 2,
        }
    }
    /// Get the maximum instruction size in bytes. All supported architectures have a maximum instruction size of 4 bytes.
    pub fn get_maximum_instruction_size(&self) -> u8 {
        // TODO: Xtensa may have wide instructions
        4
    }

    /// Returns whether a CPU with the `self` instruction set is compatible with a program compiled for `instr_set`.
    pub fn is_compatible(&self, instr_set: InstructionSet) -> bool {
        if *self == instr_set {
            return true;
        }

        matches!(
            (self, instr_set),
            (InstructionSet::RV32C, InstructionSet::RV32)
        )
    }
}

/// This describes a chip family with all its variants.
///
/// This struct is usually read from a target description
/// file.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ChipFamily {
    /// This is the name of the chip family in base form.
    /// E.g. `nRF52832`.
    pub name: String,
    /// The JEP106 code of the manufacturer.
    #[serde(serialize_with = "hex_jep106_option")]
    pub manufacturer: Option<JEP106Code>,
    /// The method(s) that may be able to identify targets in this family.
    #[serde(default)]
    pub chip_detection: Vec<ChipDetectionMethod>,
    /// The `target-gen` process will set this to `true`.
    /// Please change this to `false` if this file is modified from the generated, or is a manually created target description.
    #[serde(default)]
    pub generated_from_pack: bool,
    /// The latest release of the pack file from which this was generated.
    /// Values:
    /// - `Some("1.3.0")` if the latest pack file release was for example "1.3.0".
    /// - `None` if this was not generated from a pack file, or has been modified since it was generated.
    #[serde(default)]
    pub pack_file_release: Option<String>,
    /// This vector holds all the variants of the family.
    pub variants: Vec<Chip>,
    /// This vector holds all available algorithms.
    #[serde(default)]
    pub flash_algorithms: Vec<RawFlashAlgorithm>,
    #[serde(skip, default = "default_source")]
    /// Source of the target description, used for diagnostics
    pub source: TargetDescriptionSource,
}

fn default_source() -> TargetDescriptionSource {
    TargetDescriptionSource::External
}

impl ChipFamily {
    /// Validates the [`ChipFamily`] such that probe-rs can make assumptions about the correctness without validating thereafter.
    ///
    /// This method should be called right after the [`ChipFamily`] is created!
    pub fn validate(&self) -> Result<(), String> {
        self.reject_duplicate_target_names()?;
        self.ensure_algorithms_exists()?;
        self.ensure_at_least_one_core()?;
        self.reject_incorrect_core_access_options()?;
        self.validate_memory_regions()?;
        self.validate_rtt_scan_regions()?;

        Ok(())
    }

    /// Rejects target descriptions with duplicate target names. Only one of these targets can
    /// be selected, so having multiple is probably a mistake.
    fn reject_duplicate_target_names(&self) -> Result<(), String> {
        use std::collections::HashSet;

        let mut seen = HashSet::new();

        for chip in &self.variants {
            if !seen.insert(&chip.name) {
                return Err(format!(
                    "Target {} appears multiple times in {}",
                    chip.name, self.name,
                ));
            }
        }

        Ok(())
    }

    /// Make sure the algorithms used on the variant actually exist on the family (this is basically a check for typos).
    fn ensure_algorithms_exists(&self) -> Result<(), String> {
        for variant in &self.variants {
            for algorithm_name in variant.flash_algorithms.iter() {
                if !self
                    .flash_algorithms
                    .iter()
                    .any(|algorithm| &algorithm.name == algorithm_name)
                {
                    return Err(format!(
                        "unknown flash algorithm `{}` for variant `{}`",
                        algorithm_name, variant.name
                    ));
                }
            }
        }

        Ok(())
    }

    // Check that there is at least one core.
    fn ensure_at_least_one_core(&self) -> Result<(), String> {
        for variant in &self.variants {
            let Some(core) = variant.cores.first() else {
                return Err(format!(
                    "variant `{}` does not contain any cores",
                    variant.name
                ));
            };

            // Make sure that the core types (architectures) are not mixed.
            let architecture = core.core_type.architecture();
            if variant
                .cores
                .iter()
                .any(|core| core.core_type.architecture() != architecture)
            {
                return Err(format!(
                    "variant `{}` contains mixed core architectures",
                    variant.name
                ));
            }
        }

        Ok(())
    }

    fn reject_incorrect_core_access_options(&self) -> Result<(), String> {
        // We check each variant if it is valid.
        // If one is not valid, we abort with an appropriate error message.
        for variant in &self.variants {
            // Core specific validation logic based on type
            for core in variant.cores.iter() {
                // The core access options must match the core type specified
                match &core.core_access_options {
                    CoreAccessOptions::Arm(_) if !core.core_type.is_arm() => {
                        return Err(format!(
                            "Arm options don't match core type {:?} on core {}",
                            core.core_type, core.name
                        ));
                    }
                    CoreAccessOptions::Riscv(_) if !core.core_type.is_riscv() => {
                        return Err(format!(
                            "Riscv options don't match core type {:?} on core {}",
                            core.core_type, core.name
                        ));
                    }
                    CoreAccessOptions::Xtensa(_) if !core.core_type.is_xtensa() => {
                        return Err(format!(
                            "Xtensa options don't match core type {:?} on core {}",
                            core.core_type, core.name
                        ));
                    }
                    CoreAccessOptions::Arm(options) => {
                        if matches!(core.core_type, CoreType::Armv7a | CoreType::Armv8a)
                            && options.debug_base.is_none()
                        {
                            return Err(format!("Core {} requires setting debug_base", core.name));
                        }

                        if core.core_type == CoreType::Armv8a && options.cti_base.is_none() {
                            return Err(format!("Core {} requires setting cti_base", core.name));
                        }
                    }
                    _ => {}
                }
            }
        }

        Ok(())
    }

    /// Ensures that the memory is assigned to a core, and that all the cores exist
    fn validate_memory_regions(&self) -> Result<(), String> {
        for variant in &self.variants {
            let core_names = variant
                .cores
                .iter()
                .map(|core| &core.name)
                .collect::<Vec<_>>();

            for memory in &variant.memory_map {
                for core in memory.cores() {
                    if !core_names.contains(&core) {
                        return Err(format!(
                            "Variant {}, memory region {:?} is assigned to a non-existent core {}",
                            variant.name, memory, core
                        ));
                    }
                }

                if memory.cores().is_empty() {
                    return Err(format!(
                        "Variant {}, memory region {:?} is not assigned to a core",
                        variant.name, memory
                    ));
                }
            }
        }

        Ok(())
    }

    fn validate_rtt_scan_regions(&self) -> Result<(), String> {
        for variant in &self.variants {
            let Some(rtt_scan_ranges) = &variant.rtt_scan_ranges else {
                return Ok(());
            };

            let ram_regions = variant
                .memory_map
                .iter()
                .filter_map(MemoryRegion::as_ram_region)
                .merge_consecutive()
                .collect::<Vec<_>>();

            // The custom ranges must all be enclosed by exactly one of
            // the defined RAM regions.
            for scan_range in rtt_scan_ranges {
                if ram_regions
                    .iter()
                    .any(|region| region.range.contains_range(scan_range))
                {
                    continue;
                }

                return Err(format!(
                    "The RTT scan region ({:#010x?}) of {} is not enclosed by any single RAM region.",
                    scan_range, variant.name,
                ));
            }
        }

        Ok(())
    }
}

impl ChipFamily {
    /// Get the different [Chip]s which are part of this
    /// family.
    pub fn variants(&self) -> &[Chip] {
        &self.variants
    }

    /// Get all flash algorithms for this family of chips.
    pub fn algorithms(&self) -> &[RawFlashAlgorithm] {
        &self.flash_algorithms
    }

    /// Try to find a [RawFlashAlgorithm] with a given name.
    pub fn get_algorithm(&self, name: impl AsRef<str>) -> Option<&RawFlashAlgorithm> {
        let name = name.as_ref();
        self.flash_algorithms.iter().find(|elem| elem.name == name)
    }

    /// Tries to find a [RawFlashAlgorithm] with a given name and returns it with the
    /// core assignment fixed to the cores of the given chip.
    pub fn get_algorithm_for_chip(
        &self,
        name: impl AsRef<str>,
        chip: &Chip,
    ) -> Option<RawFlashAlgorithm> {
        self.get_algorithm(name).map(|algo| {
            let mut algo_cores = if algo.cores.is_empty() {
                chip.cores.iter().map(|core| core.name.clone()).collect()
            } else {
                algo.cores.clone()
            };

            // only keep cores in the algo that are also in the chip
            algo_cores.retain(|algo_core| {
                chip.cores
                    .iter()
                    .any(|chip_core| &chip_core.name == algo_core)
            });

            RawFlashAlgorithm {
                cores: algo_cores,
                ..algo.clone()
            }
        })
    }
}