1use 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#[derive(Debug, thiserror::Error, docsplay::Display)]
14pub enum RegistryError {
15 ChipNotFound(String),
17 ChipNotUnique(String, String),
19 ChipAutodetectFailed,
21 UnknownCoreType(String),
23 Io(#[from] std::io::Error),
25 Yaml(#[from] yaml_serde::Error),
27 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#[derive(Default)]
151pub struct Registry {
152 families: Vec<ChipFamily>,
154}
155
156static 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 pub fn new() -> Self {
181 Self { families: vec![] }
182 }
183
184 pub fn from_builtin_families() -> Self {
186 let mut families = BUILTIN_TARGETS.read().to_vec();
190
191 add_generic_targets(&mut families);
192
193 Self { families }
198 }
199
200 pub fn families(&self) -> &[ChipFamily] {
202 &self.families
203 }
204
205 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 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 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 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 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 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 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 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 Target::new(family, chip)
398 }
399
400 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 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
423fn match_name_prefix(pattern: &str, name: &str) -> bool {
428 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 #[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 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 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 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 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 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_family(family).unwrap();
528
529 family
531 .variants()
532 .iter()
533 .map(|chip| registry.get_target(family, chip))
534 })
535 .for_each(|target| {
536 #[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 #[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 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 target = registry.get_target_by_name("NO_JTAG_INFO").unwrap();
580 assert_eq!(target.jtag, None);
581
582 target = registry.get_target_by_name("NO_SCAN_CHAIN").unwrap();
584 assert_eq!(target.jtag.unwrap().scan_chain, None);
585
586 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}