target_gen/
generate.rs

1use anyhow::{Context, Error, Result, anyhow, bail};
2use cmsis_pack::pdsc::{AccessPort, Algorithm, Core, Device, Package, Processor};
3use cmsis_pack::{pack_index::PdscRef, utils::FromElem};
4use futures::StreamExt;
5use jep106::JEP106Code;
6use probe_rs::config::Registry;
7use probe_rs::flashing::FlashAlgorithm;
8use probe_rs_target::{
9    Architecture, ArmCoreAccessOptions, Chip, ChipFamily, Core as ProbeCore, CoreAccessOptions,
10    CoreType, GenericRegion, MemoryAccess, MemoryRegion, NvmRegion, RamRegion, RawFlashAlgorithm,
11    RiscvCoreAccessOptions, TargetDescriptionSource, XtensaCoreAccessOptions,
12};
13use std::collections::HashMap;
14use std::io::BufReader;
15use std::{fs, io::Read, path::Path};
16
17pub enum Kind<'a, T>
18where
19    T: std::io::Seek + std::io::Read,
20{
21    Archive(&'a mut zip::ZipArchive<T>),
22    Directory(&'a Path),
23}
24
25impl<T> Kind<'_, T>
26where
27    T: std::io::Seek + std::io::Read,
28{
29    /// Read binary data from the given path.
30    fn read_bytes(&mut self, path: &Path) -> Result<Vec<u8>> {
31        let buffer = match self {
32            Kind::Archive(archive) => {
33                let reader = BufReader::new(archive.by_name(&path.to_string_lossy())?);
34                reader.bytes().collect::<std::io::Result<Vec<u8>>>()?
35            }
36            Kind::Directory(dir) => fs::read(dir.join(path))?,
37        };
38
39        Ok(buffer)
40    }
41}
42
43fn process_flash_algo<T>(
44    flash_algorithm: &Algorithm,
45    kind: &mut Kind<T>,
46) -> Result<RawFlashAlgorithm>
47where
48    T: std::io::Seek + std::io::Read,
49{
50    let algo_bytes = kind.read_bytes(&flash_algorithm.file_name)?;
51    let mut algo = crate::parser::extract_flash_algo(
52        None,
53        &algo_bytes,
54        &flash_algorithm.file_name,
55        flash_algorithm.default,
56        false, // Algorithms from CMSIS-Pack files are position independent
57    )?;
58
59    // If the algo specifies `RAMstart` and/or `RAMsize` fields, then use them.
60    // - See https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_family_pg.html#element_algorithm for more information.
61    algo.load_address = flash_algorithm
62        .ram_start
63        .map(|ram_start| ram_start + FlashAlgorithm::get_max_algorithm_header_size());
64
65    // This algo will still be added to the specific chip algos by name.
66    // We just need to deduplicate the entire flash algorithm and reference to it by name at other places.
67
68    Ok(algo)
69}
70
71pub(crate) fn extract_families<T>(
72    pdsc: Package,
73    mut kind: Kind<T>,
74    families: &mut Vec<ChipFamily>,
75    only_supported_familes: bool,
76) -> Result<()>
77where
78    T: std::io::Seek + std::io::Read,
79{
80    // Forge a definition file for each device in the .pdsc file.
81    let mut devices = pdsc.devices.0.into_iter().collect::<Vec<_>>();
82    devices.sort_by(|a, b| a.0.cmp(&b.0));
83
84    // Only process this, if this belongs to a supported family.
85    let registry = Registry::from_builtin_families();
86    let currently_supported_chip_families = registry.families();
87
88    for (device_name, device) in devices {
89        if only_supported_familes
90            && !currently_supported_chip_families
91                .iter()
92                .any(|supported_family| supported_family.name == device.family)
93        {
94            // We only want to continue if the chip family is already represented as supported probe_rs target chip family.
95            log::debug!("Unsupprted chip family {}. Skipping ...", device.family);
96            return Ok(());
97        }
98
99        // Check if this device family is already known.
100        let mut potential_family = families
101            .iter_mut()
102            .find(|family| family.name == device.family);
103
104        let family = if let Some(ref mut family) = potential_family {
105            family
106        } else {
107            families.push(ChipFamily {
108                name: device.family.clone(),
109                manufacturer: try_parse_vendor(device.vendor.as_deref()),
110                generated_from_pack: true,
111                chip_detection: vec![],
112                pack_file_release: Some(pdsc.releases.latest_release().version.clone()),
113                variants: Vec::new(),
114                flash_algorithms: Vec::new(),
115                source: TargetDescriptionSource::BuiltIn,
116            });
117            // This unwrap is always safe as we insert at least one item previously.
118            families.last_mut().unwrap()
119        };
120
121        // Extract the flash algorithm, block & sector size and the erased byte value from the ELF binary.
122        let flash_algorithm_names = device
123            .algorithms
124            .iter()
125            .filter_map(|flash_algorithm| {
126                match process_flash_algo(flash_algorithm, &mut kind) {
127                    Ok(algo) => {
128                        // We add this algo directly to the algos of the family if it's not already added.
129                        // Make sure we never add an algo twice to save file size.
130                        let algo_name = algo.name.clone();
131                        if !family.flash_algorithms.contains(&algo) {
132                            family.flash_algorithms.push(algo);
133                        }
134
135                        Some(algo_name)
136                    }
137                    Err(e) => {
138                        log::warn!(
139                            "Failed to process flash algorithm {}.",
140                            flash_algorithm.file_name.display()
141                        );
142                        log::warn!("Reason: {e:?}");
143                        None
144                    }
145                }
146            })
147            .collect::<Vec<_>>();
148
149        // Sometimes the algos are referenced twice, for example in the multicore H7s
150        // Deduplicate while keeping order.
151        let flash_algorithm_names = flash_algorithm_names
152            .iter()
153            .enumerate()
154            .filter(|(i, s)| !flash_algorithm_names[..*i].contains(s))
155            .map(|(_, s)| s.clone())
156            .collect::<Vec<_>>();
157
158        let cores = device
159            .processors
160            .iter()
161            .map(create_core)
162            .collect::<Result<Vec<_>>>()?;
163
164        let mut memory_map = get_mem_map(&device, &cores);
165        patch_memmap(&mut memory_map);
166
167        family.variants.push(Chip {
168            name: device_name,
169            part: None,
170            svd: None,
171            documentation: HashMap::new(),
172            package_variants: vec![],
173            cores,
174            memory_map,
175            flash_algorithms: flash_algorithm_names,
176            rtt_scan_ranges: None,
177            jtag: None, // TODO, parse scan chain from sdf
178            default_binary_format: None,
179        });
180    }
181
182    Ok(())
183}
184
185fn try_parse_vendor(vendor: Option<&str>) -> Option<JEP106Code> {
186    let jep = match vendor? {
187        "Atmel:3" => JEP106Code::new(0, 0x1f),
188        "NXP:11" => JEP106Code::new(0, 0x15),
189        "STMicroelectronics:13" => JEP106Code::new(0, 0x20),
190        _ => return None,
191    };
192
193    Some(jep)
194}
195
196fn create_core(processor: &Processor) -> Result<ProbeCore> {
197    let core_type = core_to_probe_core(&processor.core)?;
198    Ok(ProbeCore {
199        name: processor
200            .name
201            .as_ref()
202            .map(|s| s.to_ascii_lowercase())
203            .unwrap_or_else(|| "main".to_string()),
204        core_type,
205        core_access_options: match core_type.architecture() {
206            Architecture::Arm => CoreAccessOptions::Arm(ArmCoreAccessOptions {
207                ap: match processor.ap {
208                    AccessPort::Index(id) => probe_rs_target::ApAddress::V1(id),
209                    AccessPort::Address(addr) => probe_rs_target::ApAddress::V2(addr),
210                },
211                targetsel: None,
212                debug_base: None,
213                cti_base: None,
214                jtag_tap: None,
215            }),
216            Architecture::Riscv => CoreAccessOptions::Riscv(RiscvCoreAccessOptions {
217                hart_id: None,
218                jtag_tap: None,
219            }),
220            Architecture::Xtensa => {
221                CoreAccessOptions::Xtensa(XtensaCoreAccessOptions { jtag_tap: None })
222            }
223        },
224    })
225}
226
227fn core_to_probe_core(value: &Core) -> Result<CoreType, Error> {
228    Ok(match value {
229        Core::CortexM0 => CoreType::Armv6m,
230        Core::CortexM0Plus => CoreType::Armv6m,
231        Core::CortexM4 => CoreType::Armv7em,
232        Core::CortexM3 => CoreType::Armv7m,
233        Core::CortexM23 => CoreType::Armv8m,
234        Core::CortexM33 => CoreType::Armv8m,
235        Core::CortexM55 => CoreType::Armv8m,
236        Core::CortexM85 => CoreType::Armv8m,
237        Core::CortexM7 => CoreType::Armv7em,
238        Core::StarMC1 => CoreType::Armv8m,
239        c => bail!("Core '{c:?}' is not yet supported for target generation."),
240    })
241}
242
243// Process all `.pdsc` files in the given directory.
244pub fn visit_dirs(path: &Path, families: &mut Vec<ChipFamily>) -> Result<()> {
245    walk_files(path, &mut |file_path| {
246        if has_extension(file_path, "pdsc") {
247            log::info!("Found .pdsc file: {}", file_path.display());
248
249            let package = Package::from_path(file_path).context(format!(
250                "Failed to open .pdsc file {}.",
251                file_path.display()
252            ))?;
253
254            extract_families::<fs::File>(package, Kind::Directory(path), families, false).context(
255                format!("Failed to process .pdsc file {}.", file_path.display()),
256            )?;
257        }
258
259        Ok(())
260    })
261}
262
263fn walk_files(path: &Path, callback: &mut impl FnMut(&Path) -> Result<()>) -> Result<()> {
264    for entry in fs::read_dir(path)? {
265        let entry_path = entry?.path();
266
267        if entry_path.is_dir() {
268            walk_files(&entry_path, callback)?;
269        } else {
270            callback(&entry_path)?;
271        }
272    }
273
274    Ok(())
275}
276
277fn has_extension(path: &Path, ext: &str) -> bool {
278    path.extension().is_some_and(|e| e == ext)
279}
280
281pub fn visit_file(path: &Path, families: &mut Vec<ChipFamily>) -> Result<()> {
282    log::info!("Trying to open pack file: {}.", path.display());
283    // If we get a file, try to unpack it.
284    let file = fs::File::open(path)?;
285    let mut archive = zip::ZipArchive::new(file)?;
286
287    let mut pdsc_file = find_pdsc_in_archive(&mut archive)?
288        .ok_or_else(|| anyhow!("Failed to find .pdsc file in archive {}", path.display()))?;
289
290    let mut pdsc = String::new();
291    pdsc_file.read_to_string(&mut pdsc)?;
292
293    let package = Package::from_string(&pdsc).map_err(|e| {
294        anyhow!(
295            "Failed to parse pdsc file '{}' in CMSIS Pack {}: {}",
296            pdsc_file.name(),
297            path.display(),
298            e
299        )
300    })?;
301
302    drop(pdsc_file);
303
304    extract_families(package, Kind::Archive(&mut archive), families, false)
305}
306
307pub async fn visit_arm_files(families: &mut Vec<ChipFamily>, filter: Option<String>) -> Result<()> {
308    //TODO: The multi-threaded logging makes it very difficult to track which errors/warnings belong where - needs some rework.
309    let packs = crate::fetch::get_vidx().await?;
310
311    let mut stream =
312        futures::stream::iter(packs.pdsc_index.iter().enumerate().filter_map(|(i, pack)| {
313            let only_supported_familes = if let Some(ref filter) = filter {
314                // If we are filtering for specific filter patterns, then skip all the ones we don't want.
315                if !pack.name.contains(filter) {
316                    log::debug!("Ignoring filtered {} ...", pack.name);
317                    return None;
318                }
319
320                log::info!("Found matching chip family: {}", pack.name);
321
322                // If we are filtering for specific filter patterns, then do not restrict these to the list of supported families.
323                false
324            } else {
325                // If we are not filtering for specific filter patterns, then only include the supported families.
326                true
327            };
328            if pack.deprecated.is_none() {
329                // We only want to download the pack if it is not deprecated.
330                log::info!("Working PACK {}/{} ...", i, packs.pdsc_index.len());
331                Some(visit_arm_file(pack, only_supported_familes))
332            } else {
333                log::warn!("Ignoring deprecated {} ...", pack.name);
334                None
335            }
336        }))
337        .buffer_unordered(32);
338    while let Some(result) = stream.next().await {
339        families.extend(result);
340    }
341
342    Ok(())
343}
344
345pub(crate) async fn visit_arm_file(
346    pack: &PdscRef,
347    only_supported_familes: bool,
348) -> Vec<ChipFamily> {
349    let url = format!(
350        "{url}/{vendor}.{name}.{version}.pack",
351        url = pack.url.trim_end_matches('/'),
352        vendor = pack.vendor,
353        name = pack.name,
354        version = pack.version
355    );
356
357    log::info!("Downloading {url}");
358
359    let response = match reqwest::get(&url).await {
360        Ok(response) => response,
361        Err(error) => {
362            log::error!("Failed to download pack '{url}': {error}");
363            return vec![];
364        }
365    };
366    let bytes = match response.bytes().await {
367        Ok(bytes) => bytes,
368        Err(error) => {
369            log::error!("Failed to get bytes from pack '{url}': {error}");
370            return vec![];
371        }
372    };
373
374    log::info!("Trying to open pack file: {url}.");
375    let zip = std::io::Cursor::new(bytes);
376    let mut archive = match zip::ZipArchive::new(zip) {
377        Ok(archive) => archive,
378        Err(error) => {
379            log::error!("Failed to open pack '{url}': {error}");
380            return vec![];
381        }
382    };
383
384    let mut pdsc_file = match find_pdsc_in_archive(&mut archive) {
385        Ok(Some(file)) => file,
386        Ok(None) => {
387            log::error!("Failed to find .pdsc file in archive {url}");
388            return vec![];
389        }
390        Err(error) => {
391            log::error!("Error handling archive {url}: {error}");
392            return vec![];
393        }
394    };
395
396    let mut pdsc = String::new();
397    if let Err(error) = pdsc_file.read_to_string(&mut pdsc) {
398        log::error!("Failed to read .pdsc file '{url}': {error}");
399        return vec![];
400    };
401
402    let package = match Package::from_string(&pdsc) {
403        Ok(package) => package,
404        Err(error) => {
405            log::error!(
406                "Failed to parse pdsc file '{}' in CMSIS Pack {url}: {error}",
407                pdsc_file.name(),
408            );
409            return vec![];
410        }
411    };
412
413    let pdsc_name = pdsc_file.name().to_owned();
414
415    drop(pdsc_file);
416
417    let mut families = vec![];
418
419    match extract_families(
420        package,
421        Kind::Archive(&mut archive),
422        &mut families,
423        only_supported_familes,
424    ) {
425        Ok(_) => log::info!("Processed package {pdsc_name}"),
426        Err(error) => log::error!("Something went wrong while handling pack {url}: {error}"),
427    };
428
429    families
430}
431
432/// Extracts the pdsc out of a ZIP archive.
433pub(crate) fn find_pdsc_in_archive<T>(
434    archive: &mut zip::ZipArchive<T>,
435) -> Result<Option<zip::read::ZipFile<'_, T>>>
436where
437    T: std::io::Seek + std::io::Read,
438{
439    let mut index = None;
440    for i in 0..archive.len() {
441        let file = archive.by_index(i)?;
442        let outpath = file.enclosed_name().ok_or_else(|| {
443            anyhow!(
444                "Error handling the ZIP file content with path '{}': Path seems to be malformed",
445                file.name()
446            )
447        })?;
448
449        if has_extension(&outpath, "pdsc") {
450            // We cannot return the file directly here,
451            // because this leads to lifetime problems.
452
453            index = Some(i);
454            break;
455        }
456    }
457
458    if let Some(index) = index {
459        let file = archive.by_index(index)?;
460
461        Ok(Some(file))
462    } else {
463        Ok(None)
464    }
465}
466
467/// A flag to indicate what type of memory this is.
468#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
469enum MemoryType {
470    /// A RAM memory.
471    Ram,
472    /// A Non Volatile memory.
473    Nvm,
474    /// Generic
475    Generic,
476}
477
478/// A struct to combine essential information from [`cmsis_pack::pdsc::Device::memories`].
479/// This is used to apply the necessary sorting and filtering in creating [`MemoryRegion`]s.
480// The sequence of the fields is important for the sorting by derived natural order.
481#[derive(Debug, Clone, PartialEq, Eq)]
482struct DeviceMemory {
483    memory_type: MemoryType,
484    p_name: Option<String>,
485    memory_start: u64,
486    memory_end: u64,
487    name: String,
488    access: MemoryAccess,
489}
490
491impl DeviceMemory {
492    fn access(&self) -> Option<MemoryAccess> {
493        fn is_default(access: &MemoryAccess) -> bool {
494            access == &MemoryAccess::default()
495        }
496
497        if is_default(&self.access) {
498            None
499        } else {
500            Some(self.access)
501        }
502    }
503}
504
505/// Extracts the memory regions in the package.
506/// The new memory regions are sorted by memory type, then by boot memory, then by start address,
507/// with correctly assigned cores/processor names.
508pub(crate) fn get_mem_map(device: &Device, cores: &[probe_rs_target::Core]) -> Vec<MemoryRegion> {
509    let mut device_memories: Vec<DeviceMemory> = device
510        .memories
511        .0
512        .iter()
513        .map(|(name, memory)| DeviceMemory {
514            name: name.clone(),
515            p_name: memory.p_name.clone(),
516            memory_type: if memory.default && memory.access.read && memory.access.write {
517                MemoryType::Ram
518            } else if memory.default
519                && memory.access.read
520                && memory.access.execute
521                && !memory.access.write
522            {
523                MemoryType::Nvm
524            } else {
525                MemoryType::Generic
526            },
527            memory_start: memory.start,
528            memory_end: memory.start + memory.size,
529            access: MemoryAccess {
530                read: memory.access.read,
531                write: memory.access.write,
532                execute: memory.access.execute,
533                boot: memory.startup,
534            },
535        })
536        .collect();
537
538    // Sort by memory type, then by processor name, then by boot memory, then by start address.
539    device_memories.sort_by_key(|memory| {
540        (
541            memory.memory_type,
542            memory.p_name.clone(),
543            memory.access.boot,
544            memory.memory_start,
545        )
546    });
547
548    let all_cores: Vec<_> = cores.iter().map(|core| core.name.clone()).collect();
549
550    let is_multi_core = cores.len() > 1;
551
552    // Convert DeviceMemory's to MemoryRegion's, and assign cores to shared reqions.
553    let mut mem_map = vec![];
554    for region in device_memories {
555        if is_multi_core && region.p_name.is_none() {
556            log::warn!(
557                "Device {}, memory region {} has no processor name, but this is required for a multicore device. Assigning memory to all cores!",
558                device.name,
559                region.name
560            );
561        }
562
563        let cores = region
564            .p_name
565            .as_ref()
566            .map(|s| vec![s.to_ascii_lowercase()])
567            .unwrap_or_else(|| all_cores.clone());
568
569        match region.memory_type {
570            MemoryType::Ram => {
571                if let Some(MemoryRegion::Ram(existing_region)) = mem_map.iter_mut().find(|existing_region| {
572                        matches!(existing_region, MemoryRegion::Ram(ram_region) if ram_region.name.as_deref() == Some(&region.name) && ram_region.access == region.access())
573                    })
574                {
575                    existing_region.cores.extend_from_slice(&cores);
576                } else {
577                    mem_map.push(MemoryRegion::Ram(RamRegion {
578                        access: region.access(),
579                        name: Some(region.name),
580                        range: region.memory_start..region.memory_end,
581                        cores,
582                    }));
583                }
584            },
585            MemoryType::Nvm => {
586                if let Some(MemoryRegion::Nvm(existing_region)) = mem_map.iter_mut().find(|existing_region| {
587                        matches!(existing_region, MemoryRegion::Nvm(nvm_region) if nvm_region.name.as_deref() == Some(&region.name) && nvm_region.access == region.access())
588                    })
589                {
590                    existing_region.cores.extend_from_slice(&cores);
591                } else {
592                    mem_map.push(MemoryRegion::Nvm(NvmRegion {
593                        access: region.access(),
594                        name: Some(region.name),
595                        range: region.memory_start..region.memory_end,
596                        cores,
597                        is_alias: false,
598                    }));
599                }
600            },
601            MemoryType::Generic => {
602                if let Some(MemoryRegion::Generic(existing_region)) = mem_map.iter_mut().find(|existing_region| {
603                        matches!(existing_region, MemoryRegion::Generic(generic_region) if generic_region.name.as_deref() == Some(&region.name) && generic_region.access == region.access())
604                    })
605                {
606                    existing_region.cores.extend_from_slice(&cores);
607                } else {
608                    mem_map.push(MemoryRegion::Generic(GenericRegion {
609                        access: region.access(),
610                        name: Some(region.name),
611                        range: region.memory_start..region.memory_end,
612                        cores,
613                    }));
614                }
615            },
616        };
617    }
618
619    mem_map
620}
621
622fn patch_memmap(mem_map: &mut [MemoryRegion]) {
623    ensure_single_ram_region_is_executable(mem_map);
624}
625
626/// Ensure that at least one RAM region is executable.
627fn ensure_single_ram_region_is_executable(mem_map: &mut [MemoryRegion]) {
628    // If the device only has one Ram region, mark that region as executable. This is necessary
629    // as we rely on RAM-loaded flashing algorithms and so at least some of the RAM must be
630    // executable.
631    let ram_regions = mem_map
632        .iter()
633        .filter_map(MemoryRegion::as_ram_region)
634        .count();
635
636    if ram_regions == 1
637        && let Some(MemoryRegion::Ram(ram_region)) = mem_map
638            .iter_mut()
639            .find(|region| matches!(region, MemoryRegion::Ram(_)))
640        && let Some(ref mut access) = ram_region.access
641    {
642        access.execute = true;
643    }
644}