Skip to main content

probe_rs/config/
registry.rs

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