1use std::collections::HashMap;
41
42use consortium_cfg_common::config::{McoreName, Party, VisibleCore};
43use consortium_cfg_common::database::{
44 ChipDatabase, DoorbellWindow, HardwareDoorbellPeripheral, MemoryRegionType,
45};
46
47use crate::components::address::{Address, ApAddress};
48use crate::components::bella::{BellA, HardwareDoorbell};
49use crate::components::dbg::{DebugLogs, DebugRing, ElfLocation};
50use crate::components::ipc_shm::{Channel, DoorbellMeta, DoorbellRingerSide, ShmNode, Transceiver};
51use crate::components::irq::InterruptLine;
52use crate::components::memory_to_uio::SharedWindow;
53use crate::components::peripheral::{PeripheralInstance, PeripheralKind, Peripherals};
54use crate::components::region::Region;
55use crate::components::remoteproc::{
56 LinkerRegionAttributes, LinkerSectionBinding, RemoteprocMemoryLayout, RemoteprocMemoryWindow,
57 RemoteprocNamedRegion,
58};
59use crate::components::uio::{ToUio, UioBacking, UioDevice};
60use consortium_data::DataError;
61
62use crate::{Session, error::CfgError};
63
64#[derive(Debug, Clone)]
70pub struct LoweredIr {
71 pub ipc_shm: ShmNode,
73 pub bella: BellA,
75 pub uio: Vec<(String, UioDevice)>,
78 pub dbg: DebugLogs,
81 pub peripherals: Peripherals,
83 pub core_peripherals: HashMap<VisibleCore, Vec<String>>,
88 pub remoteproc: HashMap<VisibleCore, RemoteprocMemoryLayout>,
90 pub time_driver: bool,
93}
94
95impl LoweredIr {
96 #[doc(hidden)]
102 pub fn empty_with_uio(uio: Vec<(String, UioDevice)>) -> Self {
103 use crate::components::bella::BellA;
104 use crate::components::dbg::DebugLogs;
105 use crate::components::ipc_shm::ShmNode;
106 use crate::components::peripheral::Peripherals;
107 use consortium_cfg_common::config::DbgMode;
108 use std::collections::HashMap;
109
110 Self {
111 ipc_shm: ShmNode::new(Vec::new()),
112 bella: BellA { bells: Vec::new() },
113 uio,
114 dbg: DebugLogs::new(HashMap::new(), DbgMode::default()),
115 peripherals: Peripherals {
116 hal_crate: String::new(),
117 instances: Vec::new(),
118 },
119 core_peripherals: HashMap::new(),
120 remoteproc: HashMap::new(),
121 time_driver: true,
122 }
123 }
124
125 pub fn initialize(&self, core: VisibleCore, ty_crate: &syn::Path) -> proc_macro2::TokenStream {
135 use quote::{format_ident, quote};
136
137 let uio_ident = format_ident!("uio");
138
139 let participating: Vec<_> = self
141 .ipc_shm
142 .transceivers()
143 .iter()
144 .filter(|t| t.channel_primary.0 == core || t.channel_secondary.0 == core)
145 .cloned()
146 .collect();
147 let available = self
150 .core_peripherals
151 .get(&core)
152 .map(Vec::as_slice)
153 .unwrap_or_default();
154 let endpoints = crate::components::ipc_shm::initialize_instances(
155 &ShmNode::new(participating.clone()),
156 core,
157 &uio_ident,
158 ty_crate,
159 &self.peripherals.hal_crate,
160 available,
161 );
162
163 match core {
164 VisibleCore::Ap => {}
166 VisibleCore::Mcore(name) => {
169 let doorbells =
170 ShmNode::new(participating.clone()).doorbell_singletons(core, available);
171 return self.controller_init(core, name, &participating, endpoints, doorbells);
172 }
173 _ => return endpoints,
176 }
177
178 let opens = self.uio.iter().map(|(name, device)| {
187 let enable_irq = device.irq.is_some();
188 let base = u64::from(device.mmap.first().map_or(0, |(base, _)| *base));
189 quote! {
190 ::std::sync::Arc::new(
191 ::consortium_runtime_app::uio::UioDevice::open_named(
192 #name,
193 #base,
194 #enable_irq && config.uio_irqs,
195 )?
196 )
197 }
198 });
199 let uio_count = self.uio.len();
201
202 let host = self.dbg.initialize_host(&uio_ident);
206 let (dbg_field, dbg_field_name, dbg_init, dbg_error_arm) = match &host {
207 Some(host) => {
208 let field = &host.field;
209 let name = &host.field_name;
210 let init = &host.init;
211 (
212 quote! { #field, },
213 quote! { #name, },
214 quote! { #init },
215 quote! {
216 Dbg(::consortium_runtime_app::dbg_console::DbgConsoleError),
217 },
218 )
219 }
220 None => (quote! {}, quote! {}, quote! {}, quote! {}),
221 };
222 let dbg_from = if host.is_some() {
223 quote! {
224 impl ::core::convert::From<::consortium_runtime_app::dbg_console::DbgConsoleError>
225 for ConsortiumInitError
226 {
227 fn from(e: ::consortium_runtime_app::dbg_console::DbgConsoleError) -> Self {
228 ConsortiumInitError::Dbg(e)
229 }
230 }
231 }
232 } else {
233 quote! {}
234 };
235
236 quote! {
237 #endpoints
238
239 #[derive(Debug)]
246 #[allow(dead_code)]
247 enum ConsortiumInitError {
248 Uio(::consortium_runtime_app::uio::UioDeviceError),
250 #dbg_error_arm
251 }
252
253 impl ::core::convert::From<::consortium_runtime_app::uio::UioDeviceError>
254 for ConsortiumInitError
255 {
256 fn from(e: ::consortium_runtime_app::uio::UioDeviceError) -> Self {
257 ConsortiumInitError::Uio(e)
258 }
259 }
260
261 #dbg_from
262
263 #[non_exhaustive]
265 pub struct Config {
266 pub tracing: bool,
267 pub uio_irqs: bool,
268 pub dbg_console: bool,
269 }
270
271 impl ::core::default::Default for Config {
272 fn default() -> Self {
273 Self { tracing: true, uio_irqs: true, dbg_console: true }
274 }
275 }
276
277 pub struct Peripherals {
283 #dbg_field
284 pub #uio_ident: ::std::vec::Vec<
285 ::std::sync::Arc<::consortium_runtime_app::uio::UioDevice>
286 >,
287 }
288
289 fn init(
290 config: Config,
291 ) -> ::core::result::Result<(Peripherals, Channels), ConsortiumInitError> {
292 if config.tracing {
293 ::consortium_runtime_app::init_tracing();
294 }
295 ::consortium_runtime_app::log::trace!(
296 "consortium init: opening {} uio device(s)", #uio_count
297 );
298 let #uio_ident = ::std::vec![ #(#opens),* ];
299 ::consortium_runtime_app::log::trace!(
300 "consortium init: installing ipc endpoints"
301 );
302 __consortium_install_channels(&#uio_ident)?;
303 let channels = Channels::take();
304 #dbg_init
305 ::consortium_runtime_app::log::trace!("consortium init: resources ready");
306 ::core::result::Result::Ok((
307 Peripherals { #dbg_field_name #uio_ident },
308 channels,
309 ))
310 }
311
312 #[derive(Debug)]
317 #[allow(dead_code)]
318 enum ConsortiumError {
319 Init(ConsortiumInitError),
320 Connect(::consortium_ipc_transport_memory::ConnectError),
321 }
322
323 impl ::core::convert::From<ConsortiumInitError> for ConsortiumError {
324 fn from(e: ConsortiumInitError) -> Self { Self::Init(e) }
325 }
326
327 impl ::core::convert::From<::consortium_ipc_transport_memory::ConnectError>
328 for ConsortiumError
329 {
330 fn from(e: ::consortium_ipc_transport_memory::ConnectError) -> Self {
331 Self::Connect(e)
332 }
333 }
334 }
335 }
336
337 fn controller_init(
365 &self,
366 core: VisibleCore,
367 name: McoreName,
368 participating: &[Transceiver],
369 endpoints: proc_macro2::TokenStream,
370 doorbell_arguments: Vec<syn::Ident>,
371 ) -> proc_macro2::TokenStream {
372 use quote::quote;
373
374 let mut mpu_stmts = Vec::new();
377 if mcore_has_mpu_helper(name) {
378 let mut seen = std::collections::BTreeSet::new();
379 for transceiver in participating {
380 if transceiver.region.cache {
384 continue;
385 }
386 let Some(base) = transceiver.region.address.cp.get(&core).copied() else {
387 continue;
388 };
389 if !seen.insert(base) {
390 continue;
391 }
392 let size = transceiver.region.length;
393 let region = seen.len() as u8 - 1;
394 mpu_stmts.push(quote! {
395 unsafe {
399 ::consortium_runtime_mcu::mpu::disable_cache(
400 &mut __core.MPU,
401 #region,
402 #base,
403 #size,
404 );
405 ::consortium_runtime_mcu::log::trace!(
406 "consortium controller init: MPU carveout {} base={:#010x} size={:#010x}",
407 #region, #base, #size
408 );
409 }
410 });
411 }
412 }
413
414 let enables_dcache = name == McoreName::Cm7
415 && participating
416 .iter()
417 .any(|transceiver| transceiver.region.cache);
418 let enable_dcache = if enables_dcache {
419 quote! {
420 __core.SCB.enable_dcache(&mut __core.CPUID);
424 ::consortium_runtime_mcu::log::trace!(
425 "consortium controller init: Cortex-M7 data cache enabled"
426 );
427 }
428 } else {
429 quote! {}
430 };
431
432 let steal = if mpu_stmts.is_empty() && !enables_dcache {
436 quote! {}
437 } else {
438 quote! {
439 let mut __core = unsafe { ::cortex_m::Peripherals::steal() };
443 }
444 };
445
446 let nvic = self.bella.nvic_enable(&core, &self.peripherals.hal_crate);
448
449 let peripherals = self.peripherals.initialize(core);
451 let peripheral_vectors = self.peripherals.vectors(core);
452 let peripheral_arguments = self.peripherals.arguments(core);
453 let has_peripherals = !peripheral_arguments.is_empty();
454 let peripheral_nvic = self.peripherals.nvic(core);
455 let peripheral_value = if has_peripherals {
456 quote! { Peripherals { #(#peripheral_arguments: __p.#peripheral_arguments),* } }
457 } else {
458 quote! { Peripherals {} }
459 };
460
461 let dbg_ring = self.dbg.ring(core);
464 let dbg_present = dbg_ring.is_some();
465 let logger_init = dbg_ring
469 .map(|ring| ring.initialize_controller())
470 .unwrap_or_default();
471
472 let time = crate::components::time::time_driver_codegen(
477 &self.peripherals.hal_crate,
478 self.time_driver,
479 dbg_present,
480 self.core_peripherals.get(&core).map(Vec::as_slice),
481 );
482 let crate::components::time::TimeDriverCodegen {
483 timestamp,
484 isr: time_isr,
485 bringup: time_bringup,
486 } = time;
487
488 let hal: syn::Path =
493 syn::parse_str(&self.peripherals.hal_crate).expect("hal_crate must be a valid path");
494 let hal_peripherals =
495 if has_peripherals || !doorbell_arguments.is_empty() || !time_bringup.is_empty() {
496 quote! {
497 ::consortium_runtime_mcu::log::trace!("before chip init");
498 let __p = #hal::init();
499 ::consortium_runtime_mcu::log::trace!("after chip init");
500 }
501 } else {
502 quote! {}
503 };
504
505 let mut vectors = self.bella.vectors(&core, &self.peripherals.hal_crate);
513 vectors.extend(peripheral_vectors);
514 if !time_isr.is_empty() {
515 vectors.insert(0, time_isr);
516 }
517 let vectors = if vectors.is_empty() {
518 quote! {}
519 } else {
520 quote! {
521 #[cfg(target_arch = "arm")]
523 mod __consortium_vectors {
524 use #hal::interrupt;
525
526 #(#vectors)*
527 }
528 }
529 };
530
531 quote! {
532 #endpoints
533
534 #timestamp
535 #vectors
536
537 #[cfg(target_arch = "arm")]
539 #[non_exhaustive]
540 pub struct Config {
541 pub logger: bool,
542 pub mpu: bool,
543 pub nvic: bool,
544 pub time_driver: bool,
545 }
546
547 #[cfg(target_arch = "arm")]
548 impl ::core::default::Default for Config {
549 fn default() -> Self {
550 Self { logger: true, mpu: true, nvic: true, time_driver: true }
551 }
552 }
553
554 #[cfg(target_arch = "arm")]
557 fn init(config: Config) -> (Peripherals, Channels) {
558 if config.logger { #logger_init }
559 ::consortium_runtime_mcu::log::trace!("consortium controller init: mpu/nvic bring-up");
560 #steal
561 if config.mpu { #(#mpu_stmts)* }
562 #enable_dcache
563 if config.nvic {
564 #nvic
565 #peripheral_nvic
566 }
567 #hal_peripherals
568 if config.time_driver { #time_bringup }
569 __consortium_install_channels(#(__p.#doorbell_arguments),*);
570 let channels = Channels::take();
571 let peripherals = #peripheral_value;
572 ::consortium_runtime_mcu::log::trace!("consortium controller init: resources ready");
573 (peripherals, channels)
574 }
575
576 #[cfg(target_arch = "arm")]
578 #[cfg_attr(target_os = "none", derive(Debug, defmt::Format))]
579 #[cfg_attr(not(target_os = "none"), derive(Debug))]
580 #[allow(dead_code)]
581 enum ConsortiumError {
582 Connect(::consortium_ipc_transport_memory::ConnectError),
583 }
584
585 #[cfg(target_arch = "arm")]
586 impl ::core::convert::From<::consortium_ipc_transport_memory::ConnectError>
587 for ConsortiumError
588 {
589 fn from(e: ::consortium_ipc_transport_memory::ConnectError) -> Self {
590 Self::Connect(e)
591 }
592 }
593
594 #peripherals
595 }
596 }
597}
598
599fn mcore_has_mpu_helper(name: McoreName) -> bool {
603 !matches!(name, McoreName::Cm0p)
604}
605
606fn hal_crate_path(chip: &str) -> String {
610 if chip.to_ascii_lowercase().starts_with("stm32") {
611 "::consortium_hal_stm32mp2".to_string()
612 } else {
613 "::consortium_hal_imx9".to_string()
614 }
615}
616
617impl Session {
618 pub fn lower(&self) -> Result<LoweredIr, CfgError> {
632 consortium_log::trace!(
633 "lowering manifest: {} ipc channels, {} peripherals",
634 self.config.ipc.channels.len(),
635 self.config.peripheral.len()
636 );
637 let remoteproc = self.lower_remoteproc()?;
638 let mut transceivers = Vec::new();
639 let mut regions: Vec<SharedWindow> = Vec::new();
640
641 let mut doorbell_order: Vec<String> = Vec::new();
645 let mut bells: Vec<HardwareDoorbell> = Vec::new();
646
647 for channel in &self.config.ipc.channels {
648 consortium_log::trace!("lowering ipc channel '{}'", channel.name);
649 let primary_party = channel.primary;
650 let secondary_party = channel.secondary;
651 let primary = endpoint(channel, primary_party, "primary")?;
652 let secondary = endpoint(channel, secondary_party, "secondary")?;
653 let primary_core = primary_party.core();
654 let secondary_core = secondary_party.core();
655
656 let meta: DoorbellMeta = channel.doorbell.parse().map_err(|err: CfgError| {
657 consortium_log::trace!(
658 "cannot type doorbell '{}' for channel '{}': {}",
659 channel.doorbell,
660 channel.name,
661 err
662 );
663 CfgError::Manifest(err.to_string())
664 })?;
665 let db_doorbell = self.doorbell(&channel.doorbell).ok_or_else(|| {
666 consortium_log::trace!(
667 "unknown doorbell '{}' for channel '{}'",
668 channel.doorbell,
669 channel.name
670 );
671 CfgError::Manifest(format!("unknown doorbell '{}'", channel.doorbell))
672 })?;
673
674 let memory = channel.memory().ok_or_else(|| {
675 consortium_log::trace!(
676 "channel '{}' uses memory pattern but sets no config.memory",
677 channel.name
678 );
679 CfgError::Manifest(format!(
680 "ipc channel '{}' uses the memory pattern but sets no config.memory",
681 channel.name
682 ))
683 })?;
684
685 let region_mmap = regions.len();
687 regions.push(SharedWindow {
688 memory,
689 cache: channel.cache(),
690 });
691
692 let doorbell_key = channel.doorbell.to_ascii_lowercase();
695 let doorbell_uio = match doorbell_order.iter().position(|n| *n == doorbell_key) {
696 Some(index) => index + 1,
697 None => {
698 doorbell_order.push(doorbell_key.clone());
699 doorbell_order.len()
700 }
701 };
702
703 let region_cp =
710 controller_bases([primary_party, secondary_party], memory.base, |secure| {
711 memory_alias(&self.database, memory.base, secure)
712 });
713 let doorbell_cp = controller_doorbell_bases(
714 db_doorbell,
715 [primary_party, secondary_party],
716 |secure| peripheral_alias(&self.database, secure),
717 );
718
719 let region_address = Address {
720 ap: ApAddress {
721 uio_id: 0,
722 mmap_id: region_mmap,
723 },
724 cp: region_cp,
725 };
726 let doorbell_address = Address {
727 ap: ApAddress {
728 uio_id: doorbell_uio,
729 mmap_id: 0,
730 },
731 cp: doorbell_cp,
732 };
733
734 let channel_primary = Channel {
735 doorbell_chan: primary.chan as u8,
736 ack_chan: primary.ack_chan.map(|chan| chan as u8),
737 type_name: primary.r#type.clone(),
738 buffer_name: format!("{}_tx_buf", channel.name),
739 };
740 let channel_secondary = Channel {
741 doorbell_chan: secondary.chan as u8,
742 ack_chan: secondary.ack_chan.map(|chan| chan as u8),
743 type_name: secondary.r#type.clone(),
744 buffer_name: format!("{}_rx_buf", channel.name),
745 };
746
747 record_bell(
752 &mut bells,
753 &doorbell_key,
754 &doorbell_address,
755 meta,
756 [
757 (
758 primary_core,
759 party_irq(db_doorbell, primary_party),
760 primary.chan as u8,
761 party_window(db_doorbell, primary_party, DoorbellRingerSide::Primary),
762 ),
763 (
764 secondary_core,
765 party_irq(db_doorbell, secondary_party),
766 secondary.chan as u8,
767 party_window(db_doorbell, secondary_party, DoorbellRingerSide::Secondary),
768 ),
769 ],
770 );
771
772 transceivers.push(Transceiver {
773 name: channel.name.clone(),
774 doorbell_address,
775 mtu: channel.mtu,
776 codec: channel.codec,
777 region: Region {
778 r#type: memory_type(&self.database, memory.base),
779 cache: channel.cache(),
780 address: region_address,
781 length: memory.length,
782 },
783 channel_primary: (primary_core, channel_primary),
784 channel_secondary: (secondary_core, channel_secondary),
785 doorbell: meta,
786 doorbell_window: (
787 party_window(db_doorbell, primary_party, DoorbellRingerSide::Primary),
788 party_window(db_doorbell, secondary_party, DoorbellRingerSide::Secondary),
789 ),
790 });
791 }
792
793 let mut uio = Vec::new();
798 consortium_log::trace!("lowering {} shared-memory regions to UIO", regions.len());
799 if let Some(mut device) = regions.as_slice().to_uio()? {
800 for window in &mut device.mmap {
801 window.0 += memory_alias(&self.database, window.0, false);
802 }
803 uio.push(("ipc-shm".to_string(), device));
804 }
805 let doorbell_alias = peripheral_alias(&self.database, false);
806 for name in &doorbell_order {
807 consortium_log::trace!("lowering doorbell '{}' to UIO", name);
808 let db_doorbell = self.doorbell(name).ok_or_else(|| {
809 consortium_log::trace!("unknown doorbell '{}' during UIO assembly", name);
810 CfgError::Manifest(format!("unknown doorbell '{name}'"))
811 })?;
812 if let Some(mut device) = db_doorbell.to_uio()? {
813 for window in &mut device.mmap {
814 window.0 += doorbell_alias;
815 }
816 device.interrupt_parent = Some(self.database.interrupt_parent.clone());
820 uio.push((name.clone(), device));
821 }
822 }
823
824 let dbg_uio_id = uio.len();
834 let mut dbg_cores: Vec<_> = self.config.dbg.cores.iter().collect();
835 dbg_cores.sort_by_key(|(core, _)| core.to_string());
836
837 let mut dbg_windows = Vec::new();
838 let mut dbg_rings = HashMap::new();
839 for (mmap_id, (core, entry)) in dbg_cores.into_iter().enumerate() {
840 let dbg_alias = memory_alias(&self.database, entry.memory.base, false);
841 let base = u64::from(entry.memory.base) + u64::from(dbg_alias);
842 dbg_windows.push((base as u32, entry.memory.length));
843 dbg_rings.insert(
844 *core,
845 DebugRing {
846 base,
847 length: u64::from(entry.memory.length),
848 mtu: entry.mtu,
849 ap_uio_id: dbg_uio_id,
850 ap_mmap_id: mmap_id,
851 elf: ElfLocation::verbatim(self.endpoint_artifact(*core).unwrap_or_default()),
852 },
853 );
854 }
855 if !dbg_windows.is_empty() {
856 uio.push((
857 "dbg-shm".to_string(),
858 UioDevice {
859 mmap: dbg_windows,
860 irq: None,
861 backing: UioBacking::ReservedMemory,
862 dma_coherent: Vec::new(),
863 interrupt_parent: None,
864 clock: None,
865 },
866 ));
867 }
868
869 let device_base =
875 |device: &UioDevice| device.mmap.iter().map(|(base, _)| *base).min().unwrap_or(0);
876 let mut remap = vec![0usize; uio.len()];
877 let old_index: HashMap<String, usize> = uio
878 .iter()
879 .enumerate()
880 .map(|(id, (name, _))| (name.clone(), id))
881 .collect();
882 uio.sort_by_key(|(_, device)| device_base(device));
883 for (new_id, (name, _)) in uio.iter().enumerate() {
884 remap[old_index[name]] = new_id;
885 }
886 for transceiver in &mut transceivers {
887 transceiver.region.address.ap.uio_id = remap[transceiver.region.address.ap.uio_id];
888 transceiver.doorbell_address.ap.uio_id = remap[transceiver.doorbell_address.ap.uio_id];
889 }
890 for bell in &mut bells {
891 bell.address.ap.uio_id = remap[bell.address.ap.uio_id];
892 }
893 for ring in dbg_rings.values_mut() {
894 ring.ap_uio_id = remap[ring.ap_uio_id];
895 }
896
897 let mut bella = BellA { bells };
904 let mut maps: HashMap<VisibleCore, Option<crate::chipmap::ChipMap>> = HashMap::new();
905 bella.resolve_variants(|core, number| {
906 let map = maps
907 .entry(core)
908 .or_insert_with(|| {
909 crate::chipmap::load(&self.config.profile.chip, &core.to_string()).ok()
910 })
911 .as_ref()?;
912 if !map.has_pac_interrupts() {
913 return None;
914 }
915 map.irq_by_number(number).map(|entry| entry.name.clone())
916 });
917
918 let peripherals = self.lower_peripherals()?;
919 consortium_log::trace!(
920 "lowering complete: {} transceivers, {} uio devices, {} peripheral instances",
921 transceivers.len(),
922 uio.len(),
923 peripherals.instances.len()
924 );
925 let core_peripherals =
926 Self::lower_core_peripherals(&self.config.profile.chip, &transceivers, &peripherals);
927 Ok(LoweredIr {
928 ipc_shm: ShmNode::new(transceivers),
929 bella,
930 uio,
931 dbg: DebugLogs::new(dbg_rings, self.config.dbg.mode),
932 peripherals,
933 core_peripherals,
934 remoteproc,
935 time_driver: self.config.profile.time_driver,
936 })
937 }
938
939 pub(crate) fn lower_remoteproc(
942 &self,
943 ) -> Result<HashMap<VisibleCore, RemoteprocMemoryLayout>, CfgError> {
944 for core in self.config.remoteproc.keys() {
945 if !self.database.remoteproc.contains_key(core) {
946 return Err(CfgError::Manifest(format!(
947 "[remoteproc.{core}] has no loader contract for chip '{}'",
948 self.config.profile.chip
949 )));
950 }
951 let visible: VisibleCore = core.parse().map_err(|_| {
952 CfgError::Manifest(format!("[remoteproc.{core}] is not a visible core name"))
953 })?;
954 if !self
955 .config
956 .endpoints
957 .keys()
958 .any(|party| party.core() == visible)
959 {
960 return Err(CfgError::Manifest(format!(
961 "[remoteproc.{core}] requires an [endpoints] firmware entry for `{core}`"
962 )));
963 }
964 }
965
966 self.database
967 .remoteproc
968 .iter()
969 .filter_map(|(name, default)| {
970 let core: VisibleCore = match name.parse() {
971 Ok(core) => core,
972 Err(_) => {
973 return Some(Err(CfgError::Manifest(format!(
974 "chip database remoteproc entry '{name}' is not a visible core"
975 ))));
976 }
977 };
978 if !self
979 .config
980 .endpoints
981 .keys()
982 .any(|party| party.core() == core)
983 {
984 return None;
985 }
986 let overrides = self.config.remoteproc.get(name);
987 let window = |default: consortium_cfg_common::database::RemoteprocWindow,
988 override_window: Option<
989 &consortium_cfg_common::config::RemoteprocWindowOverride,
990 >| {
991 RemoteprocMemoryWindow {
992 origin: override_window
993 .and_then(|value| value.origin)
994 .unwrap_or(default.origin),
995 length: override_window
996 .and_then(|value| value.length)
997 .unwrap_or(default.length),
998 reserve: override_window
999 .and_then(|value| value.reserve)
1000 .unwrap_or(default.reserve),
1001 }
1002 };
1003 let regions = overrides
1004 .into_iter()
1005 .flat_map(|value| &value.regions)
1006 .map(|(region_name, region)| RemoteprocNamedRegion {
1007 name: region_name.clone(),
1008 attributes: LinkerRegionAttributes::new(
1009 region.read,
1010 region.write,
1011 region.execute,
1012 ),
1013 window: RemoteprocMemoryWindow {
1014 origin: region.origin,
1015 length: region.length,
1016 reserve: region.reserve,
1017 },
1018 sections: region
1019 .sections
1020 .iter()
1021 .map(|section| LinkerSectionBinding {
1022 output: section.output.clone(),
1023 inputs: section.inputs.clone(),
1024 keep: section.keep,
1025 insert_after: section.insert_after.clone(),
1026 })
1027 .collect(),
1028 })
1029 .collect();
1030 let layout = RemoteprocMemoryLayout {
1031 core,
1032 compatible: default.compatible.clone(),
1033 vector_section: default.vector_section.clone(),
1034 vector_placement: default.vector_placement,
1035 vector: RemoteprocNamedRegion {
1036 name: default.vector.name.clone(),
1037 attributes: LinkerRegionAttributes::new(
1038 default.vector.read,
1039 default.vector.write,
1040 default.vector.execute,
1041 ),
1042 window: window(default.vector.window, overrides.map(|value| &value.vector)),
1043 sections: Vec::new(),
1044 },
1045 flash: window(default.flash, overrides.map(|value| &value.flash)),
1046 shmem: RemoteprocNamedRegion {
1047 name: default.shmem.name.clone(),
1048 attributes: LinkerRegionAttributes::new(
1049 default.shmem.read,
1050 default.shmem.write,
1051 default.shmem.execute,
1052 ),
1053 window: window(default.shmem.window, overrides.map(|value| &value.shmem)),
1054 sections: Vec::new(),
1055 },
1056 ram: window(default.ram, overrides.map(|value| &value.ram)),
1057 regions,
1058 };
1059 if let Err(error) = layout.validate() {
1060 return Some(Err(CfgError::Manifest(format!(
1061 "[remoteproc.{name}] is invalid: {error}"
1062 ))));
1063 }
1064 Some(Ok((core, layout)))
1065 })
1066 .collect()
1067 }
1068
1069 fn lower_core_peripherals(
1074 chip: &str,
1075 transceivers: &[Transceiver],
1076 peripherals: &Peripherals,
1077 ) -> HashMap<VisibleCore, Vec<String>> {
1078 let mut cores: Vec<VisibleCore> = transceivers
1081 .iter()
1082 .flat_map(|t| [t.channel_primary.0, t.channel_secondary.0])
1083 .chain(peripherals.instances.iter().map(|p| p.owner))
1084 .filter(|core| matches!(core, VisibleCore::Mcore(_)))
1085 .collect();
1086 cores.sort_by_key(|core| core.to_string());
1087 cores.dedup();
1088
1089 cores
1090 .into_iter()
1091 .filter_map(|core| {
1092 let map = crate::chipmap::load(chip, &core.to_string()).ok()?;
1093 Some((core, map.peripheral_names()))
1094 })
1095 .collect()
1096 }
1097
1098 fn lower_peripherals(&self) -> Result<Peripherals, CfgError> {
1105 let chip = &self.config.profile.chip;
1106 let mut instances = Vec::new();
1107
1108 for (name, peripheral) in &self.config.peripheral {
1109 let VisibleCore::Mcore(_) = peripheral.owner else {
1110 continue;
1111 };
1112 let core = peripheral.owner.to_string();
1113 consortium_log::trace!("lowering peripheral '{}' on core '{}'", name, core);
1114 let map = match crate::chipmap::load(chip, &core) {
1115 Ok(map) => map,
1116 Err(DataError::ChipMapUnavailable { .. }) => {
1117 consortium_log::trace!(
1118 "no chip map for core '{}'; peripheral '{}' gets no bring-up",
1119 core,
1120 name
1121 );
1122 continue;
1123 }
1124 Err(err) => {
1125 consortium_log::trace!("chip map load failed for core '{}': {}", core, err);
1126 return Err(err.into());
1127 }
1128 };
1129 let Some(entry) = map.peripheral(name) else {
1130 consortium_log::trace!("peripheral '{}' absent from chip map for '{}'", name, core);
1131 continue;
1132 };
1133 if peripheral.secure {
1137 consortium_log::trace!(
1138 "peripheral '{}' is secure; the singleton path has no secure alias",
1139 name
1140 );
1141 return Err(CfgError::Manifest(format!(
1142 "peripheral '{name}' sets `secure = true`, but generated bring-up takes the \
1143 chip HAL's peripheral singleton, which names only the non-secure alias; \
1144 drive secure peripherals from firmware instead"
1145 )));
1146 }
1147 let singleton = entry.name.clone();
1148
1149 let kind = match entry.kind() {
1150 "i2c" | "lpi2c" => {
1151 let irq = map.irq(name).ok_or_else(|| {
1152 consortium_log::trace!(
1153 "i2c peripheral '{}' has no interrupt entry in chip map for '{}'",
1154 name,
1155 core
1156 );
1157 CfgError::Manifest(format!(
1158 "i2c peripheral '{name}' has no interrupt entry in the chip map for '{core}'"
1159 ))
1160 })?;
1161 let clock_hz = peripheral.clock.ok_or_else(|| {
1162 consortium_log::trace!(
1163 "i2c peripheral '{}' missing required `clock` field",
1164 name
1165 );
1166 CfgError::Manifest(format!(
1167 "i2c peripheral '{name}' needs `clock` (its input clock in Hz) to generate bring-up"
1168 ))
1169 })?;
1170 let frequency = peripheral.frequency.unwrap_or(100_000);
1171 if !map.has_pac_interrupts() {
1175 consortium_log::trace!(
1176 "core '{}' is not described by the chip PAC's interrupt table; \
1177 peripheral '{}' gets its NVIC enable but no generated vector, so \
1178 the firmware must install '{}' itself",
1179 core,
1180 name,
1181 irq.name,
1182 );
1183 }
1184 let line = InterruptLine {
1185 number: u32::from(irq.number),
1186 variant: map.has_pac_interrupts().then(|| irq.name.clone()),
1187 };
1188 PeripheralKind::I2c {
1189 clock_hz,
1190 frequency,
1191 irq: line,
1192 }
1193 }
1194 "gpio" | "rgpio" => PeripheralKind::Gpio,
1195 _ => PeripheralKind::Other,
1196 };
1197
1198 instances.push(PeripheralInstance {
1199 owner: peripheral.owner,
1200 key: name.clone(),
1201 singleton,
1202 kind,
1203 });
1204 }
1205
1206 instances.sort_by(|a, b| a.key.cmp(&b.key));
1208
1209 Ok(Peripherals {
1210 hal_crate: hal_crate_path(chip),
1211 instances,
1212 })
1213 }
1214
1215 fn endpoint_artifact(&self, core: VisibleCore) -> Option<String> {
1229 self.config
1230 .endpoints
1231 .iter()
1232 .find(|(party, _)| party.core() == core)
1233 .map(|(_, root)| root.clone())
1234 }
1235}
1236
1237fn endpoint<'c>(
1240 channel: &'c consortium_cfg_common::config::IpcChannel,
1241 party: Party,
1242 role: &str,
1243) -> Result<&'c consortium_cfg_common::config::IpcEndpoint, CfgError> {
1244 channel.endpoints.get(&party).ok_or_else(|| {
1245 consortium_log::trace!(
1246 "channel '{}' names {} party '{}' with no matching [party.{}]",
1247 channel.name,
1248 role,
1249 party,
1250 party
1251 );
1252 CfgError::Manifest(format!(
1253 "ipc channel '{}' names {role} party '{party}' but has no matching [party.{party}]",
1254 channel.name
1255 ))
1256 })
1257}
1258
1259fn party_irq(doorbell: &HardwareDoorbellPeripheral, party: Party) -> Option<u32> {
1262 doorbell
1263 .parties
1264 .iter()
1265 .find(|entry| entry.core == party)
1266 .map(|entry| entry.irq)
1267}
1268
1269fn party_window(
1276 doorbell: &HardwareDoorbellPeripheral,
1277 party: Party,
1278 role: DoorbellRingerSide,
1279) -> DoorbellRingerSide {
1280 if doorbell.window_length.is_none() {
1281 return role;
1282 }
1283 let window = doorbell
1284 .parties
1285 .iter()
1286 .find(|entry| entry.core == party)
1287 .map_or(DoorbellWindow::A, |entry| entry.window);
1288 match window {
1289 DoorbellWindow::A => DoorbellRingerSide::Primary,
1290 DoorbellWindow::B => DoorbellRingerSide::Secondary,
1291 }
1292}
1293
1294fn controller_doorbell_bases(
1299 doorbell: &HardwareDoorbellPeripheral,
1300 parties: [Party; 2],
1301 alias: impl Fn(bool) -> u32,
1302) -> HashMap<VisibleCore, u32> {
1303 parties
1304 .into_iter()
1305 .filter(|party| party.core() != VisibleCore::Ap)
1306 .filter_map(|party| {
1307 let entry = doorbell.parties.iter().find(|entry| entry.core == party)?;
1308 Some((party.core(), entry.memory + alias(party_secure(party))))
1309 })
1310 .collect()
1311}
1312
1313fn controller_bases(
1317 parties: [Party; 2],
1318 base: u32,
1319 alias: impl Fn(bool) -> u32,
1320) -> HashMap<VisibleCore, u32> {
1321 parties
1322 .into_iter()
1323 .filter(|party| party.core() != VisibleCore::Ap)
1324 .map(|party| (party.core(), base + alias(party_secure(party))))
1325 .collect()
1326}
1327
1328fn party_secure(party: Party) -> bool {
1330 match party {
1331 Party::AcoreSecure => true,
1332 Party::AcoreNonSecure => false,
1333 Party::Mcore { secure, .. } => secure,
1334 }
1335}
1336
1337fn memory_type(database: &ChipDatabase, base: u32) -> MemoryRegionType {
1341 database
1342 .memory
1343 .regions
1344 .iter()
1345 .find(|region| region.contains(base, 0))
1346 .map_or(MemoryRegionType::Sram, |region| region.r#type)
1347}
1348
1349fn memory_alias(database: &ChipDatabase, base: u32, secure: bool) -> u32 {
1353 if !memory_type(database, base).aliased() {
1354 return 0;
1355 }
1356 database
1357 .memory
1358 .aliases
1359 .iter()
1360 .find(|alias| alias.secure == secure)
1361 .map_or(0, |alias| alias.offset)
1362}
1363
1364fn peripheral_alias(database: &ChipDatabase, secure: bool) -> u32 {
1367 database
1368 .peripheral
1369 .aliases
1370 .iter()
1371 .find(|alias| alias.secure == secure)
1372 .map_or(0, |alias| alias.address)
1373}
1374
1375fn record_bell(
1382 bells: &mut Vec<HardwareDoorbell>,
1383 key: &str,
1384 address: &Address,
1385 meta: DoorbellMeta,
1386 entries: impl IntoIterator<Item = (VisibleCore, Option<u32>, u8, DoorbellRingerSide)>,
1387) {
1388 let bell = match bells.iter_mut().find(|bell| bell.name == key) {
1389 Some(bell) => bell,
1390 None => {
1391 bells.push(HardwareDoorbell {
1392 name: key.to_string(),
1393 address: address.clone(),
1394 r#type: meta.r#type,
1395 id: meta.id,
1396 channels: Vec::new(),
1397 irq: HashMap::new(),
1398 side: HashMap::new(),
1399 });
1400 bells.last_mut().expect("just pushed")
1401 }
1402 };
1403
1404 for (core, irq, channel, side) in entries {
1405 if !bell.channels.contains(&channel) {
1406 bell.channels.push(channel);
1407 }
1408 if let Some(irq) = irq {
1409 bell.irq.insert(core, InterruptLine::new(irq));
1410 }
1411 bell.side.insert(core, side);
1412 }
1413}
1414
1415#[cfg(test)]
1416mod tests {
1417 use super::*;
1418 use consortium_cfg_common::config::{Codec, McoreName};
1419 use consortium_cfg_common::database::VectorPlacement;
1420
1421 const STM32: &str = include_str!("../Consortium.example.stm32mp257.toml");
1422
1423 fn cm33() -> VisibleCore {
1424 VisibleCore::Mcore(McoreName::Cm33)
1425 }
1426
1427 fn cm0p() -> VisibleCore {
1428 VisibleCore::Mcore(McoreName::Cm0p)
1429 }
1430
1431 fn cm7() -> VisibleCore {
1432 VisibleCore::Mcore(McoreName::Cm7)
1433 }
1434
1435 const IMX95_DDR: &str = r#"
1438 [profile]
1439 chip = "mimx9596xvt"
1440 shared = "./shared"
1441 deploy_root = "/opt/consortium"
1442
1443 [endpoints]
1444 linux = "./app"
1445 cm7 = "./firmware"
1446
1447 [dbg.cm7]
1448 memory = { base = 0x88300000, length = 0x2000 }
1449 mtu = 64
1450
1451 [[ipc.channel]]
1452 name = "sensor"
1453 doorbell = "mu7"
1454 pattern = "memory"
1455 primary = "linux"
1456 secondary = "cm7"
1457 party.linux = { chan = 6, type = "SensorData" }
1458 party.cm7 = { chan = 7, type = "MotionControl" }
1459 config.memory = { base = 0x88304000, length = 0x4000 }
1460 mtu = 128
1461 "#;
1462
1463 fn lowered() -> LoweredIr {
1464 Session::from_toml(STM32)
1465 .expect("example manifest loads")
1466 .lower()
1467 .expect("session lowers")
1468 }
1469
1470 fn transceiver<'a>(ir: &'a LoweredIr, name: &str) -> &'a Transceiver {
1471 ir.ipc_shm
1472 .transceivers()
1473 .iter()
1474 .find(|t| t.name == name)
1475 .unwrap_or_else(|| panic!("transceiver '{name}' present"))
1476 }
1477
1478 #[test]
1479 fn orders_uio_devices_by_ascending_memory_base() {
1480 let ir = lowered();
1481 let names: Vec<_> = ir.uio.iter().map(|(name, _)| name.as_str()).collect();
1482 assert_eq!(names, ["dbg-shm", "ipc-shm", "ipcc1", "ipcc2"]);
1486 }
1487
1488 #[test]
1489 fn region_addresses_index_uio0_in_declaration_order() {
1490 let ir = lowered();
1491 let sensor = transceiver(&ir, "sensor");
1492 let lp = transceiver(&ir, "lp");
1493
1494 assert_eq!(
1497 sensor.region.address.ap,
1498 ApAddress {
1499 uio_id: 1,
1500 mmap_id: 0
1501 }
1502 );
1503 assert_eq!(
1504 lp.region.address.ap,
1505 ApAddress {
1506 uio_id: 1,
1507 mmap_id: 1
1508 }
1509 );
1510
1511 assert_eq!(sensor.region.address.cp[&cm33()], 0x2006_4000);
1515 assert_eq!(lp.region.address.cp[&cm0p()], 0x200C_4000);
1516 }
1517
1518 #[test]
1519 fn uio_windows_apply_non_secure_aliases() {
1520 let ir = lowered();
1521
1522 let (_, shm) = ir
1525 .uio
1526 .iter()
1527 .find(|(name, _)| name == "ipc-shm")
1528 .expect("shared-memory uio device");
1529 assert_eq!(shm.mmap[0].0, 0x2006_4000);
1530
1531 let (_, ipcc1) = ir
1534 .uio
1535 .iter()
1536 .find(|(name, _)| name == "ipcc1")
1537 .expect("ipcc1 uio device");
1538 assert_eq!(ipcc1.mmap[0].0, 0x4049_0000);
1539 }
1540
1541 #[test]
1542 fn controller_doorbell_base_applies_security_alias() {
1543 let ir = lowered();
1544 let sensor = transceiver(&ir, "sensor");
1545 assert_eq!(sensor.doorbell_address.cp[&cm33()], 0x4049_0000);
1547 }
1548
1549 #[test]
1550 fn doorbell_addresses_get_their_own_uio_devices() {
1551 let ir = lowered();
1552 let sensor = transceiver(&ir, "sensor");
1553 let lp = transceiver(&ir, "lp");
1554
1555 assert_eq!(
1558 sensor.doorbell_address.ap,
1559 ApAddress {
1560 uio_id: 2,
1561 mmap_id: 0
1562 }
1563 );
1564 assert_eq!(
1565 lp.doorbell_address.ap,
1566 ApAddress {
1567 uio_id: 3,
1568 mmap_id: 0
1569 }
1570 );
1571 }
1572
1573 #[test]
1574 fn uses_explicit_primary_secondary_parties() {
1575 let ir = lowered();
1576 let sensor = transceiver(&ir, "sensor");
1577
1578 assert_eq!(sensor.channel_primary.0, VisibleCore::Ap);
1580 assert_eq!(sensor.channel_primary.1.doorbell_chan, 0);
1581 assert_eq!(sensor.channel_primary.1.type_name, "SensorData");
1582 assert_eq!(sensor.channel_secondary.0, cm33());
1583 assert_eq!(sensor.channel_secondary.1.doorbell_chan, 1);
1584 assert_eq!(sensor.channel_secondary.1.type_name, "MotionControl");
1585 }
1586
1587 #[test]
1588 fn carries_mtu_and_codec_through() {
1589 let ir = lowered();
1590 assert_eq!(transceiver(&ir, "sensor").mtu, 128);
1591 assert_eq!(transceiver(&ir, "sensor").codec, Codec::Postcard);
1592 assert_eq!(transceiver(&ir, "lp").codec, Codec::Rkyv);
1593 }
1594
1595 #[test]
1601 fn imx93_doorbell_puts_the_cm33_on_mub() {
1602 let manifest = r#"
1603 [profile]
1604 chip = "mimx9352"
1605 shared = "./shared"
1606 deploy_root = "/opt/consortium"
1607
1608 [endpoints]
1609 linux = "./app"
1610 cm33 = "./firmware"
1611
1612 [[ipc.channel]]
1613 name = "sensor"
1614 doorbell = "mu1"
1615 pattern = "memory"
1616 primary = "linux"
1617 secondary = "cm33"
1618 party.linux = { chan = 0, type = "SensorData" }
1619 party.cm33 = { chan = 1, type = "MotionControl" }
1620 config.memory = { base = 0x00480000, length = 0x4000 }
1621 mtu = 128
1622 "#;
1623 let ir = Session::from_toml(manifest).unwrap().lower().unwrap();
1624 let sensor = transceiver(&ir, "sensor");
1625
1626 assert_eq!(sensor.channel_primary.0, VisibleCore::Ap);
1628 assert_eq!(sensor.channel_secondary.0, cm33());
1629 assert_eq!(
1631 sensor.doorbell_window,
1632 (DoorbellRingerSide::Primary, DoorbellRingerSide::Secondary)
1633 );
1634 assert_eq!(sensor.doorbell_address.cp[&cm33()], 0x4423_0000);
1638 }
1639
1640 #[test]
1643 fn imx95_controller_doorbell_base_is_its_own_window() {
1644 let ir = Session::from_toml(IMX95_DDR).unwrap().lower().unwrap();
1645 let sensor = transceiver(&ir, "sensor");
1646 assert_eq!(
1647 sensor.doorbell_window,
1648 (DoorbellRingerSide::Primary, DoorbellRingerSide::Secondary)
1649 );
1650 assert_eq!(sensor.doorbell_address.cp[&cm7()], 0x4244_0000);
1651 }
1652
1653 #[test]
1657 fn dram_windows_take_no_memory_alias() {
1658 let ir = Session::from_toml(IMX95_DDR).unwrap().lower().unwrap();
1659 let sensor = transceiver(&ir, "sensor");
1660 assert_eq!(sensor.region.r#type, MemoryRegionType::Dram);
1661 assert_eq!(sensor.region.address.cp[&cm7()], 0x8830_4000);
1662
1663 let mmap = |device: &str| {
1664 ir.uio
1665 .iter()
1666 .find(|(name, _)| name == device)
1667 .unwrap_or_else(|| panic!("{device} uio device"))
1668 .1
1669 .mmap
1670 .clone()
1671 };
1672 assert_eq!(mmap("ipc-shm"), vec![(0x8830_4000, 0x4000)]);
1673 assert_eq!(mmap("dbg-shm"), vec![(0x8830_0000, 0x2000)]);
1674 }
1675
1676 #[test]
1684 fn imx93_doorbell_vector_names_the_window_it_forwards() {
1685 let manifest = r#"
1686 [profile]
1687 chip = "mimx9352"
1688 shared = "./shared"
1689 deploy_root = "/opt/consortium"
1690
1691 [endpoints]
1692 linux = "./app"
1693 cm33 = "./firmware"
1694
1695 [[ipc.channel]]
1696 name = "sensor"
1697 doorbell = "mu1"
1698 pattern = "memory"
1699 primary = "linux"
1700 secondary = "cm33"
1701 party.linux = { chan = 0, type = "SensorData" }
1702 party.cm33 = { chan = 1, type = "MotionControl" }
1703 config.memory = { base = 0x00480000, length = 0x4000 }
1704 mtu = 128
1705 "#;
1706 let ir = Session::from_toml(manifest).unwrap().lower().unwrap();
1707 let tokens = ir.bella.vectors(&cm33(), "::consortium_hal_imx9")[0].to_string();
1708
1709 assert!(tokens.contains("fn MU1_B ()"), "{tokens}");
1710 assert!(
1711 tokens.contains(
1712 ":: consortium_core :: bell :: service_interrupt :: < :: \
1713 consortium_core :: bell :: mu :: Mu < :: consortium_ipc :: \
1714 Secondary , 1usize > > ()"
1715 ),
1716 "{tokens}"
1717 );
1718 }
1719
1720 #[test]
1724 fn single_window_doorbell_side_still_follows_the_role() {
1725 let ir = lowered();
1726 let sensor = transceiver(&ir, "sensor");
1727 assert_eq!(
1728 sensor.doorbell_window,
1729 (DoorbellRingerSide::Primary, DoorbellRingerSide::Secondary)
1730 );
1731 }
1732
1733 #[test]
1738 fn imx93_cm33_remoteproc_lowers_to_a_tcm_layout() {
1739 let manifest = r#"
1740 [profile]
1741 chip = "mimx9352"
1742 shared = "./shared"
1743 deploy_root = "/opt/consortium"
1744
1745 [endpoints]
1746 linux = "./app"
1747 cm33 = "./firmware"
1748
1749 [[ipc.channel]]
1750 name = "sensor"
1751 doorbell = "mu1"
1752 pattern = "memory"
1753 primary = "linux"
1754 secondary = "cm33"
1755 party.linux = { chan = 0, type = "SensorData" }
1756 party.cm33 = { chan = 1, type = "MotionControl" }
1757 config.memory = { base = 0x00480000, length = 0x4000 }
1758 mtu = 128
1759 "#;
1760 let ir = Session::from_toml(manifest).unwrap().lower().unwrap();
1761 let layout = &ir.remoteproc[&"cm33".parse().unwrap()];
1762
1763 assert_eq!(layout.compatible, "fsl,imx93-cm33");
1764 assert_eq!(layout.vector.window.origin, 0x0FFE_0000);
1765 assert_eq!(layout.ram.origin, 0x2000_3000);
1766 assert!(!layout.vector.window.reserve);
1768 assert!(!layout.flash.reserve);
1769 assert!(!layout.shmem.window.reserve);
1770 assert!(!layout.ram.reserve);
1771
1772 let memory_x = layout.linker_memory().unwrap().render();
1774 assert!(memory_x.contains("m_interrupts"), "{memory_x}");
1775 assert!(memory_x.contains("m_rsc_tbl_in_tcm"), "{memory_x}");
1776 }
1777
1778 #[test]
1779 fn remoteproc_defaults_and_partial_overrides_lower_once() {
1780 let manifest = r#"
1781 [profile]
1782 chip = "mimx9596xvt"
1783 shared = "./shared"
1784 deploy_root = "/opt/consortium"
1785
1786 [endpoints]
1787 cm7 = "./mcu"
1788
1789 [remoteproc.cm7]
1790 ram = { origin = 0x81000000, length = 0x00400000 }
1791 "#;
1792 let ir = Session::from_toml(manifest)
1793 .expect("manifest validates")
1794 .lower()
1795 .expect("session lowers");
1796 let layout = &ir.remoteproc[&"cm7".parse().unwrap()];
1797
1798 assert_eq!(layout.flash.origin, 0x8000_1000);
1799 assert_eq!(layout.flash.length, 0x007f_f000);
1800 assert_eq!(layout.ram.origin, 0x8100_0000);
1801 assert_eq!(layout.ram.length, 0x0040_0000);
1802 assert_eq!(layout.vector_section, ".interrupts");
1803 assert_eq!(layout.vector_placement, VectorPlacement::Flash);
1804 assert_eq!(layout.vector.name, "m_interrupts");
1805 assert_eq!(layout.vector.window.length, 0x1000);
1806 assert_eq!(layout.shmem.name, "m_rsc_tbl_in_dram");
1807 assert_eq!(layout.shmem.window.origin, 0x8822_0000);
1808 let memory = layout.linker_memory().unwrap().render();
1809 assert!(memory.contains("m_interrupts (rx) : ORIGIN = 0x80000000, LENGTH = 0x1000"));
1810 assert!(memory.contains("FLASH (rx) : ORIGIN = 0x80000000, LENGTH = 0x800000"));
1811 assert!(memory.contains("_stext = 0x80001000;"));
1812 assert!(memory.contains("RAM (rwx) : ORIGIN = 0x81000000, LENGTH = 0x400000"));
1813 assert!(memory.contains("m_rsc_tbl_in_dram (rw) : ORIGIN = 0x88220000"));
1814 assert!(memory.contains("} > m_rsc_tbl_in_dram\n} INSERT AFTER .rodata;"));
1815
1816 let stm = Session::from_toml(
1817 r#"
1818 [profile]
1819 chip = "stm32mp257fak3"
1820 shared = "./shared"
1821 deploy_root = "/opt/consortium"
1822
1823 [endpoints]
1824 cm33 = "./mcu"
1825 "#,
1826 )
1827 .unwrap()
1828 .lower()
1829 .unwrap();
1830 let stm_layout = &stm.remoteproc[&"cm33".parse().unwrap()];
1831 assert_eq!(stm_layout.vector_section, ".isr_vectors");
1832 assert_eq!(stm_layout.vector_placement, VectorPlacement::Separate);
1835 let stm_memory = stm_layout.linker_memory().unwrap().render();
1836 assert!(stm_memory.contains("NS_VECTOR_TBL (rwx) : ORIGIN = 0x80100000, LENGTH = 0x1000"));
1837 assert!(stm_memory.contains("FLASH (rx) : ORIGIN = 0x80101000, LENGTH = 0x7ff000"));
1838 assert!(!stm_memory.contains("_stext"));
1839 assert!(stm_memory.contains("SHMEM (rwx) : ORIGIN = 0x812f8000, LENGTH = 0x8000"));
1840 assert!(stm_memory.contains("RAM (rwx) : ORIGIN = 0x80a00000, LENGTH = 0x100000"));
1841 assert!(!stm_memory.contains("VIRTIO_SHMEM"));
1842 assert!(stm_memory.contains("} > SHMEM\n} INSERT AFTER .rodata;"));
1843 }
1844
1845 #[test]
1846 fn example_memory_x_files_match_structured_layouts() {
1847 let cases = [
1848 (
1849 include_str!("../../../examples/melt-pot/imx95/Consortium.toml"),
1850 "cm7",
1851 include_str!("../../../examples/melt-pot/imx95/mcu/memory.x"),
1852 ),
1853 (
1854 include_str!("../../../examples/melt-pot/imx93/Consortium.toml"),
1855 "cm33",
1856 include_str!("../../../examples/melt-pot/imx93/mcu/memory.x"),
1857 ),
1858 (
1859 include_str!("../../../examples/melt-pot/stm32mp25/Consortium.toml"),
1860 "cm33",
1861 include_str!("../../../examples/melt-pot/stm32mp25/mcu/memory.x"),
1862 ),
1863 ];
1864 for (manifest, core, expected) in cases {
1865 let ir = Session::from_toml(manifest).unwrap().lower().unwrap();
1866 let layout = &ir.remoteproc[&core.parse().unwrap()];
1867 assert_eq!(layout.linker_memory().unwrap().render(), expected);
1868 }
1869 }
1870
1871 #[test]
1872 fn rejects_party_without_endpoint() {
1873 let manifest = r#"
1875 [profile]
1876 chip = "stm32mp257fak3"
1877 shared = "./shared"
1878 deploy_root = "/opt/consortium"
1879
1880 [[ipc.channel]]
1881 name = "solo"
1882 doorbell = "ipcc1"
1883 pattern = "memory"
1884 primary = "linux"
1885 secondary = "cm33"
1886 party.linux = { chan = 0, type = "Ping" }
1887 config.memory = { base = 0x00040000, length = 0x00008000 }
1888 mtu = 128
1889 "#;
1890
1891 let session = Session::from_toml(manifest).expect("manifest validates");
1892 assert!(matches!(session.lower(), Err(CfgError::Manifest(_))));
1893 }
1894
1895 #[test]
1896 fn bella_collects_doorbell_channels_and_core_irqs() {
1897 let ir = lowered();
1898
1899 let ipcc1 = ir
1900 .bella
1901 .bells
1902 .iter()
1903 .find(|bell| bell.name == "ipcc1")
1904 .expect("ipcc1 doorbell");
1905 assert!(ipcc1.channels.contains(&0));
1907 assert!(ipcc1.channels.contains(&1));
1908 assert_eq!(ipcc1.id, Some(1));
1909 assert_eq!(ipcc1.irq[&cm33()].number, 171);
1912 assert_eq!(ipcc1.irq[&cm33()].variant.as_deref(), Some("IPCC1_RX"));
1913
1914 let ipcc2 = ir
1915 .bella
1916 .bells
1917 .iter()
1918 .find(|bell| bell.name == "ipcc2")
1919 .expect("ipcc2 doorbell");
1920 assert!(ipcc2.irq.contains_key(&cm0p()));
1921 }
1922
1923 #[test]
1924 fn lowers_dbg_ring_with_non_secure_alias_and_env() {
1925 let ir = lowered();
1926
1927 let ring = ir.dbg.ring(cm33()).expect("cm33 has a debug ring");
1931 assert_eq!(ring.base, 0x2006_2000);
1932 assert_eq!(ring.length, 0x2000);
1933 assert_eq!(ring.mtu, 64);
1934 assert!(ir.dbg.ring(cm0p()).is_none());
1935
1936 let init = ir
1938 .initialize(cm33(), &syn::parse_quote!(::melt_pot_shared))
1939 .to_string();
1940 assert!(
1941 init.contains("ConsortiumLogger :: init ((537272320usize) as * mut u8 , 8192u16)"),
1942 "{init}"
1943 );
1944 }
1945
1946 #[test]
1947 fn lowered_ir_emits_valid_rust() {
1948 let ir = lowered();
1949 let uio = quote::format_ident!("uio");
1950 let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
1951
1952 let tokens = crate::components::ipc_shm::initialize_instances(
1953 &ir.ipc_shm,
1954 VisibleCore::Ap,
1955 &uio,
1956 &ty_crate,
1957 &ir.peripherals.hal_crate,
1958 &[],
1959 );
1960 syn::parse_file(&tokens.to_string()).expect("lowered ShmNode is valid Rust");
1961
1962 let ap = ir.initialize(VisibleCore::Ap, &ty_crate);
1964 let ap_file =
1965 syn::parse_file(&ap.to_string()).expect("lowered AP init module is valid Rust");
1966 let ap_src = ap.to_string();
1967 assert!(ap_src.contains("channels !"));
1968 assert!(ap_src.contains("impl_channel !"));
1969 assert!(ap_src.contains("fn init"));
1970 assert!(ap_src.contains("Config"));
1971 assert!(ap_src.contains("Peripherals"));
1972 assert!(!ap_src.contains("impl Drop for Peripherals"));
1973 assert!(!ap_src.contains("impl Drop for Channels"));
1974 assert!(ap_src.contains("dbg_console :: spawn"));
1976 assert!(ap_src.contains("ConsortiumInitError"));
1978 let _ = ap_file;
1979
1980 let cm = ir.initialize(cm33(), &ty_crate);
1982 let cm_src = cm.to_string();
1983 syn::parse_file(&cm_src).expect("lowered controller init module is valid Rust");
1984 assert!(cm_src.contains("fn init"));
1985 assert!(!cm_src.contains("async fn init"));
1986 assert!(cm_src.contains("Secondary"));
1987
1988 let bella = ir
1989 .bella
1990 .nvic_enable(&cm33(), &ir.peripherals.hal_crate)
1991 .expect("cm33 has doorbell irqs");
1992 let wrapped = quote::quote! { fn _f() { #bella } };
1993 syn::parse_file(&wrapped.to_string()).expect("lowered Bella is valid Rust");
1994 }
1995
1996 #[test]
1997 fn cached_window_does_not_leave_gap_in_mpu_region_numbers() {
1998 let manifest = r#"
1999 [profile]
2000 chip = "mimx9596xvt"
2001 shared = "./shared"
2002 deploy_root = "/opt/consortium"
2003
2004 [[ipc.channel]]
2005 name = "cached"
2006 doorbell = "mu7"
2007 pattern = "memory"
2008 primary = "linux"
2009 secondary = "cm7"
2010 party.linux = { chan = 0, type = "Ping" }
2011 party.cm7 = { chan = 1, type = "Pong" }
2012 config.memory = { base = 0x88300000, length = 0x4000 }
2013 config.cache = true
2014 mtu = 128
2015
2016 [[ipc.channel]]
2017 name = "uncached"
2018 doorbell = "mu7"
2019 pattern = "memory"
2020 primary = "linux"
2021 secondary = "cm7"
2022 party.linux = { chan = 2, type = "Ping" }
2023 party.cm7 = { chan = 3, type = "Pong" }
2024 config.memory = { base = 0x88304000, length = 0x4000 }
2025 mtu = 128
2026 "#;
2027 let ir = Session::from_toml(manifest).unwrap().lower().unwrap();
2028 let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
2029 let cm7 = VisibleCore::Mcore(McoreName::Cm7);
2030 let source = ir.initialize(cm7, &ty_crate).to_string();
2031
2032 let disable_cache = source
2033 .split("disable_cache")
2034 .nth(1)
2035 .expect("uncached window emits an MPU carveout");
2036 assert!(disable_cache.contains("0u8"));
2037 assert!(!disable_cache.contains("1u8"));
2038 assert_eq!(source.matches("enable_dcache").count(), 1);
2039 }
2040}