probe_rs/config/
registry.rs

1//! Internal target registry
2
3use super::{Chip, ChipFamily, ChipInfo, Core, Target, TargetDescriptionSource};
4use crate::config::CoreType;
5use probe_rs_target::{CoreAccessOptions, RiscvCoreAccessOptions};
6use std::cmp::Ordering;
7use std::collections::HashMap;
8
9/// Error type for all errors which occur when working
10/// with the internal registry of targets.
11#[derive(Debug, thiserror::Error, docsplay::Display)]
12pub enum RegistryError {
13    /// The requested chip '{0}' was not found in the list of known targets.
14    ChipNotFound(String),
15    /// Found multiple chips matching '{0}', unable to select a single chip. ({1})
16    ChipNotUnique(String, String),
17    /// The connected chip could not automatically be determined.
18    ChipAutodetectFailed,
19    /// The core type '{0}' is not supported in probe-rs.
20    UnknownCoreType(String),
21    /// An IO error occurred when trying to read a target description file.
22    Io(#[from] std::io::Error),
23    /// An error occurred while deserializing a YAML target description file.
24    Yaml(#[from] serde_yaml::Error),
25    /// Invalid chip family definition ({0.name}): {1}
26    InvalidChipFamilyDefinition(Box<ChipFamily>, String),
27}
28
29fn add_generic_targets(vec: &mut Vec<ChipFamily>) {
30    vec.extend_from_slice(&[
31        ChipFamily {
32            name: "Generic ARMv6-M".to_owned(),
33            manufacturer: None,
34            generated_from_pack: false,
35            pack_file_release: None,
36            chip_detection: vec![],
37            variants: vec![
38                Chip::generic_arm("Cortex-M0", CoreType::Armv6m),
39                Chip::generic_arm("Cortex-M0+", CoreType::Armv6m),
40                Chip::generic_arm("Cortex-M1", CoreType::Armv6m),
41            ],
42
43            flash_algorithms: vec![],
44            source: TargetDescriptionSource::Generic,
45        },
46        ChipFamily {
47            name: "Generic ARMv7-M".to_owned(),
48            manufacturer: None,
49            generated_from_pack: false,
50            pack_file_release: None,
51            chip_detection: vec![],
52            variants: vec![Chip::generic_arm("Cortex-M3", CoreType::Armv7m)],
53            flash_algorithms: vec![],
54            source: TargetDescriptionSource::Generic,
55        },
56        ChipFamily {
57            name: "Generic ARMv7E-M".to_owned(),
58            manufacturer: None,
59            generated_from_pack: false,
60            pack_file_release: None,
61            chip_detection: vec![],
62            variants: vec![
63                Chip::generic_arm("Cortex-M4", CoreType::Armv7em),
64                Chip::generic_arm("Cortex-M7", CoreType::Armv7em),
65            ],
66            flash_algorithms: vec![],
67            source: TargetDescriptionSource::Generic,
68        },
69        ChipFamily {
70            name: "Generic ARMv8-M".to_owned(),
71            manufacturer: None,
72            generated_from_pack: false,
73            pack_file_release: None,
74            chip_detection: vec![],
75            variants: vec![
76                Chip::generic_arm("Cortex-M23", CoreType::Armv8m),
77                Chip::generic_arm("Cortex-M33", CoreType::Armv8m),
78                Chip::generic_arm("Cortex-M35P", CoreType::Armv8m),
79                Chip::generic_arm("Cortex-M55", CoreType::Armv8m),
80            ],
81            flash_algorithms: vec![],
82            source: TargetDescriptionSource::Generic,
83        },
84        ChipFamily {
85            name: "Generic RISC-V".to_owned(),
86            manufacturer: None,
87            pack_file_release: None,
88            generated_from_pack: false,
89            chip_detection: vec![],
90            variants: vec![Chip {
91                name: "riscv".to_owned(),
92                part: None,
93                svd: None,
94                documentation: HashMap::new(),
95                package_variants: vec![],
96                cores: vec![Core {
97                    name: "core".to_owned(),
98                    core_type: CoreType::Riscv,
99                    core_access_options: CoreAccessOptions::Riscv(RiscvCoreAccessOptions {
100                        hart_id: None,
101                        jtag_tap: None,
102                    }),
103                }],
104                memory_map: vec![],
105                flash_algorithms: vec![],
106                rtt_scan_ranges: None,
107                jtag: None,
108                default_binary_format: None,
109            }],
110            flash_algorithms: vec![],
111            source: TargetDescriptionSource::Generic,
112        },
113    ]);
114}
115
116/// Registry of all available targets.
117#[derive(Default)]
118pub struct Registry {
119    /// All the available chips.
120    families: Vec<ChipFamily>,
121}
122
123#[cfg(feature = "builtin-targets")]
124fn builtin_targets() -> Vec<ChipFamily> {
125    const BUILTIN_TARGETS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/targets.bincode"));
126
127    bincode::serde::decode_from_slice(BUILTIN_TARGETS, bincode::config::standard())
128        .expect("Failed to deserialize builtin targets. This is a bug")
129        .0
130}
131
132#[cfg(not(feature = "builtin-targets"))]
133fn builtin_targets() -> Vec<ChipFamily> {
134    vec![]
135}
136
137impl Registry {
138    /// Create a new registry.
139    pub fn new() -> Self {
140        Self { families: vec![] }
141    }
142
143    /// Add a target from the built-in targets.
144    pub fn from_builtin_families() -> Self {
145        let mut families = builtin_targets();
146
147        add_generic_targets(&mut families);
148
149        // We skip validating the targets here as this is done at a later stage in `get_target`.
150        // Additionally, validation for existing targets is done in the tests `validate_generic_targets` and
151        // `validate_builtin` as well, to ensure we do not ship broken target definitions.
152
153        Self { families }
154    }
155
156    /// Returns the list of chip families.
157    pub fn families(&self) -> &[ChipFamily] {
158        &self.families
159    }
160
161    /// Returns a particular target by its name.
162    pub fn get_target_by_name(&self, name: impl AsRef<str>) -> Result<Target, RegistryError> {
163        let (target, _) = self.get_target_and_family_by_name(name.as_ref())?;
164        Ok(target)
165    }
166
167    fn get_target_and_family_by_name(
168        &self,
169        name: &str,
170    ) -> Result<(Target, ChipFamily), RegistryError> {
171        tracing::debug!("Searching registry for chip with name {name}");
172
173        // Try get the corresponding chip.
174        let mut selected_family_and_chip = None;
175        let mut exact_matches = 0;
176        let mut partial_matches = Vec::new();
177        for family in self.families.iter() {
178            for (variant, package) in family
179                .variants
180                .iter()
181                .flat_map(|chip| chip.package_variants().map(move |p| (chip, p)))
182            {
183                if match_name_prefix(package, name) {
184                    match package.len().cmp(&name.len()) {
185                        Ordering::Less => {
186                            // The user specified more than the current package name, so we can't
187                            // accept this as a match.
188                            continue;
189                        }
190                        Ordering::Equal => {
191                            tracing::debug!("Exact match for chip name: {package}");
192                            exact_matches += 1;
193                        }
194                        Ordering::Greater => {
195                            tracing::debug!("Partial match for chip name: {package}");
196                            partial_matches.push(package.as_str());
197                            // Only select partial match if we don't have an exact match yet
198                            if exact_matches > 0 {
199                                continue;
200                            }
201                        }
202                    }
203
204                    selected_family_and_chip = Some((family, variant, package));
205                }
206            }
207        }
208
209        let Some((family, chip, package)) = selected_family_and_chip else {
210            return Err(RegistryError::ChipNotFound(name.to_string()));
211        };
212
213        if exact_matches == 0 {
214            match partial_matches.len() {
215                0 => {}
216                1 => {
217                    tracing::warn!(
218                        "Found chip {} which matches given partial name {}. Consider specifying its full name.",
219                        package,
220                        name,
221                    );
222                }
223                matches => {
224                    const MAX_PRINTED_MATCHES: usize = 100;
225                    tracing::warn!(
226                        "Ignoring {matches} ambiguous matches for specified chip name {name}"
227                    );
228
229                    let (print, overflow) =
230                        partial_matches.split_at(MAX_PRINTED_MATCHES.min(matches));
231
232                    let mut suggestions = print.join(", ");
233
234                    // Avoid "and 1 more" by printing the last item.
235                    match overflow.len() {
236                        0 => {}
237                        1 => suggestions.push_str(&format!(", {}", overflow[0])),
238                        _ => suggestions.push_str(&format!("and {} more", overflow.len())),
239                    }
240
241                    return Err(RegistryError::ChipNotUnique(name.to_string(), suggestions));
242                }
243            }
244        }
245
246        if !package.eq_ignore_ascii_case(name) {
247            tracing::warn!(
248                "Matching {} based on wildcard. Consider specifying the chip as {} instead.",
249                name,
250                package,
251            );
252        }
253
254        let mut targ = self.get_target(family, chip);
255        targ.name = package.to_string();
256        Ok((targ, family.clone()))
257    }
258
259    /// Get all target names in a given family.
260    pub fn get_targets_by_family_name(&self, name: &str) -> Result<Vec<String>, RegistryError> {
261        let mut found_family = None;
262        let mut exact_matches = 0;
263        for family in self.families.iter() {
264            if match_name_prefix(&family.name, name) {
265                if family.name.len() == name.len() {
266                    tracing::debug!("Exact match for family name: {}", family.name);
267                    exact_matches += 1;
268                } else {
269                    tracing::debug!("Partial match for family name: {}", family.name);
270                    if exact_matches > 0 {
271                        continue;
272                    }
273                }
274                found_family = Some(family);
275            }
276        }
277        let Some(family) = found_family else {
278            return Err(RegistryError::ChipNotFound(name.to_string()));
279        };
280
281        Ok(family.variants.iter().map(|v| v.name.to_string()).collect())
282    }
283
284    /// Search for a chip.
285    ///
286    /// This function returns chips that have the given name as a prefix, with any lowercase `x`
287    /// characters in the prefix matching any character in the chip name.
288    pub fn search_chips(&self, name: &str) -> Vec<String> {
289        tracing::debug!("Searching registry for chip with name {name}");
290
291        let mut targets = Vec::new();
292
293        for family in &self.families {
294            for (variant, package) in family
295                .variants
296                .iter()
297                .flat_map(|chip| chip.package_variants().map(move |p| (chip, p)))
298            {
299                if match_name_prefix(name, package.as_str()) {
300                    targets.push(variant.name.to_string());
301                }
302            }
303        }
304
305        targets
306    }
307
308    pub(crate) fn get_target_by_chip_info(
309        &self,
310        chip_info: ChipInfo,
311    ) -> Result<Target, RegistryError> {
312        let (family, chip) = match chip_info {
313            ChipInfo::Arm(chip_info) => {
314                // Try get the corresponding chip.
315
316                let families = self.families.iter().filter(|f| {
317                    f.manufacturer
318                        .map(|m| m == chip_info.manufacturer)
319                        .unwrap_or(false)
320                });
321
322                let mut identified_chips = Vec::new();
323
324                for family in families {
325                    tracing::debug!("Checking family {}", family.name);
326
327                    let chips = family
328                        .variants()
329                        .iter()
330                        .filter(|v| v.part.map(|p| p == chip_info.part).unwrap_or(false))
331                        .map(|c| (family, c));
332
333                    identified_chips.extend(chips)
334                }
335
336                if identified_chips.len() != 1 {
337                    tracing::debug!(
338                        "Found {} matching chips for information {:?}, unable to determine chip",
339                        identified_chips.len(),
340                        chip_info
341                    );
342                    return Err(RegistryError::ChipAutodetectFailed);
343                }
344
345                identified_chips[0]
346            }
347        };
348        Ok(self.get_target(family, chip))
349    }
350
351    fn get_target(&self, family: &ChipFamily, chip: &Chip) -> Target {
352        // The validity of the given `ChipFamily` is checked in test time and in `add_target_from_yaml`.
353        Target::new(family, chip)
354    }
355
356    /// Add a target family to the registry.
357    pub fn add_target_family(&mut self, family: ChipFamily) -> Result<String, RegistryError> {
358        validate_family(&family).map_err(|error| {
359            RegistryError::InvalidChipFamilyDefinition(Box::new(family.clone()), error)
360        })?;
361
362        let family_name = family.name.clone();
363
364        self.families
365            .retain(|old_family| !old_family.name.eq_ignore_ascii_case(&family_name));
366
367        self.families.push(family);
368
369        Ok(family_name)
370    }
371
372    /// Add a target family to the registry from a YAML-formatted string.
373    pub fn add_target_family_from_yaml(&mut self, yaml: &str) -> Result<String, RegistryError> {
374        let family: ChipFamily = serde_yaml::from_str(yaml)?;
375        self.add_target_family(family)
376    }
377}
378
379/// See if `name` matches the start of `pattern`, treating any lower-case `x`
380/// character in `pattern` as a wildcard that matches any character in `name`.
381///
382/// Both `name` and `pattern` are compared case-insensitively.
383fn match_name_prefix(pattern: &str, name: &str) -> bool {
384    // If `name` is shorter than `pattern` but all characters in `name` match,
385    // the iterator will end early and the function returns true.
386    for (n, p) in name.chars().zip(pattern.chars()) {
387        if !n.eq_ignore_ascii_case(&p) && p != 'x' {
388            return false;
389        }
390    }
391    true
392}
393
394fn validate_family(family: &ChipFamily) -> Result<(), String> {
395    family.validate()?;
396
397    // We can't have this in the `validate` method as we need information that is not available in
398    // probe-rs-target.
399    for target in family.variants() {
400        crate::flashing::FormatKind::from_optional(target.default_binary_format.as_deref())?;
401    }
402
403    Ok(())
404}
405
406#[cfg(test)]
407mod tests {
408    use crate::flashing::FlashAlgorithm;
409
410    use super::*;
411    type TestResult = Result<(), RegistryError>;
412
413    // Need to synchronize this with probe-rs/tests/scan_chain_test.yaml
414    const FIRST_IR_LENGTH: u8 = 4;
415    const SECOND_IR_LENGTH: u8 = 6;
416
417    #[cfg(feature = "builtin-targets")]
418    #[test]
419    fn try_fetch_not_unique() {
420        let registry = Registry::from_builtin_families();
421        // ambiguous: partially matches STM32G081KBUx and STM32G081KBUxN
422        assert!(matches!(
423            registry.get_target_by_name("STM32G081KBU"),
424            Err(RegistryError::ChipNotUnique(_, _))
425        ));
426    }
427
428    #[test]
429    fn try_fetch_not_found() {
430        let registry = Registry::from_builtin_families();
431        assert!(matches!(
432            registry.get_target_by_name("not_a_real_chip"),
433            Err(RegistryError::ChipNotFound(_))
434        ));
435    }
436
437    #[cfg(feature = "builtin-targets")]
438    #[test]
439    fn try_fetch2() {
440        let registry = Registry::from_builtin_families();
441        // ok: matches both STM32G081KBUx and STM32G081KBUxN, but the first one is an exact match
442        assert!(registry.get_target_by_name("stm32G081KBUx").is_ok());
443    }
444
445    #[cfg(feature = "builtin-targets")]
446    #[test]
447    fn try_fetch3() {
448        let registry = Registry::from_builtin_families();
449        // ok: unique substring match
450        assert!(registry.get_target_by_name("STM32G081RBI").is_ok());
451    }
452
453    #[cfg(feature = "builtin-targets")]
454    #[test]
455    fn try_fetch4() {
456        let registry = Registry::from_builtin_families();
457        // ok: unique exact match
458        assert!(registry.get_target_by_name("nrf51822_Xxaa").is_ok());
459    }
460
461    #[test]
462    fn validate_generic_targets() {
463        let mut families = vec![];
464        add_generic_targets(&mut families);
465
466        families
467            .iter()
468            .map(|family| family.validate())
469            .collect::<Result<Vec<_>, _>>()
470            .unwrap();
471    }
472
473    #[test]
474    fn validate_builtin() {
475        let registry = Registry::from_builtin_families();
476        registry
477            .families
478            .iter()
479            .flat_map(|family| {
480                // Validate all chip descriptors.
481                validate_family(family).unwrap();
482
483                // Make additional checks by creating a target for each chip.
484                family
485                    .variants()
486                    .iter()
487                    .map(|chip| registry.get_target(family, chip))
488            })
489            .for_each(|target| {
490                // Walk through the flash algorithms and cores and try to create each one.
491                for raw_flash_algo in target.flash_algorithms.iter() {
492                    for core in raw_flash_algo.cores.iter() {
493                        FlashAlgorithm::assemble_from_raw_with_core(raw_flash_algo, core, &target)
494                            .unwrap_or_else(|error| {
495                                panic!(
496                                    "Failed to initialize flash algorithm ({}, {}, {core}): {}",
497                                    &target.name, &raw_flash_algo.name, error
498                                )
499                            });
500                    }
501                }
502            });
503    }
504
505    #[test]
506    fn add_targets_with_and_without_scanchain() -> TestResult {
507        let mut registry = Registry::new();
508
509        let file = std::fs::read_to_string("tests/scan_chain_test.yaml")?;
510        registry.add_target_family_from_yaml(&file)?;
511
512        // Check that the scan chain can read from a target correctly
513        let mut target = registry.get_target_by_name("FULL_SCAN_CHAIN").unwrap();
514        let scan_chain = target.jtag.unwrap().scan_chain.unwrap();
515        for device in scan_chain {
516            if device.name == Some("core0".to_string()) {
517                assert_eq!(device.ir_len, Some(FIRST_IR_LENGTH));
518            } else if device.name == Some("ICEPICK".to_string()) {
519                assert_eq!(device.ir_len, Some(SECOND_IR_LENGTH));
520            }
521        }
522
523        // Now check that a device without a scan chain is read correctly
524        target = registry.get_target_by_name("NO_JTAG_INFO").unwrap();
525        assert_eq!(target.jtag, None);
526
527        // Now check that a device without a scan chain is read correctly
528        target = registry.get_target_by_name("NO_SCAN_CHAIN").unwrap();
529        assert_eq!(target.jtag.unwrap().scan_chain, None);
530
531        // Check a device with a minimal scan chain
532        target = registry.get_target_by_name("PARTIAL_SCAN_CHAIN").unwrap();
533        let scan_chain = target.jtag.unwrap().scan_chain.unwrap();
534        assert_eq!(scan_chain[0].ir_len, Some(FIRST_IR_LENGTH));
535        assert_eq!(scan_chain[1].ir_len, Some(SECOND_IR_LENGTH));
536
537        Ok(())
538    }
539}