1use 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#[derive(Debug, thiserror::Error, docsplay::Display)]
12pub enum RegistryError {
13 ChipNotFound(String),
15 ChipNotUnique(String, String),
17 ChipAutodetectFailed,
19 UnknownCoreType(String),
21 Io(#[from] std::io::Error),
23 Yaml(#[from] serde_yaml::Error),
25 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#[derive(Default)]
118pub struct Registry {
119 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 pub fn new() -> Self {
140 Self { families: vec![] }
141 }
142
143 pub fn from_builtin_families() -> Self {
145 let mut families = builtin_targets();
146
147 add_generic_targets(&mut families);
148
149 Self { families }
154 }
155
156 pub fn families(&self) -> &[ChipFamily] {
158 &self.families
159 }
160
161 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 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 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 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 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 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 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 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 Target::new(family, chip)
354 }
355
356 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 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
379fn match_name_prefix(pattern: &str, name: &str) -> bool {
384 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 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 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 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 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 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 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_family(family).unwrap();
482
483 family
485 .variants()
486 .iter()
487 .map(|chip| registry.get_target(family, chip))
488 })
489 .for_each(|target| {
490 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 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 target = registry.get_target_by_name("NO_JTAG_INFO").unwrap();
525 assert_eq!(target.jtag, None);
526
527 target = registry.get_target_by_name("NO_SCAN_CHAIN").unwrap();
529 assert_eq!(target.jtag.unwrap().scan_chain, None);
530
531 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}