use std::collections::HashMap;
use std::path::PathBuf;
use consortium_cfg_common::config::{DbgMode, VisibleCore};
use proc_macro2::{Literal, TokenStream};
use quote::{format_ident, quote};
use syn::Ident;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DebugRing {
pub base: u64,
pub length: u64,
pub mtu: u32,
pub ap_uio_id: usize,
pub ap_mmap_id: usize,
pub elf: ElfLocation,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ElfLocation {
pub base_env: Option<String>,
pub path: PathBuf,
}
impl ElfLocation {
pub fn verbatim(path: impl Into<PathBuf>) -> Self {
Self {
base_env: None,
path: path.into(),
}
}
pub fn relative_to(base_env: impl Into<String>, path: impl Into<PathBuf>) -> Self {
Self {
base_env: Some(base_env.into()),
path: path.into(),
}
}
fn to_tokens(&self) -> TokenStream {
let path = self.path.to_string_lossy().replace('\\', "/");
match &self.base_env {
None => quote! { #path },
Some(var) => {
let suffix = format!("/{}", path.trim_start_matches("./"));
quote! { ::core::concat!(::core::env!(#var), #suffix) }
}
}
}
}
impl DebugRing {
pub fn env(&self) -> Vec<(&'static str, String)> {
vec![
("CONSORTIUM_DBG_BASE", self.base.to_string()),
("CONSORTIUM_DBG_LENGTH", self.length.to_string()),
]
}
}
#[derive(Debug, Clone, Default)]
pub struct DebugLogs {
rings: HashMap<VisibleCore, DebugRing>,
mode: DbgMode,
}
impl DebugLogs {
pub fn new(rings: HashMap<VisibleCore, DebugRing>, mode: DbgMode) -> Self {
Self { rings, mode }
}
pub fn rings(&self) -> &HashMap<VisibleCore, DebugRing> {
&self.rings
}
pub fn mode(&self) -> DbgMode {
self.mode
}
pub fn ring(&self, core: VisibleCore) -> Option<DebugRing> {
self.rings.get(&core).cloned()
}
pub fn with_resolved_elf_paths(mut self, resolved: &HashMap<VisibleCore, ElfLocation>) -> Self {
for (core, ring) in self.rings.iter_mut() {
if let Some(elf) = resolved.get(core) {
ring.elf = elf.clone();
}
}
self
}
pub fn initialize_host(&self, uio_ident: &Ident) -> Option<HostCodegen> {
if self.rings.is_empty() {
return None;
}
let mut rings: Vec<(&VisibleCore, &DebugRing)> = self.rings.iter().collect();
rings.sort_by_key(|(core, _)| core.to_string());
let specs = rings.iter().map(|(core, ring)| {
let uio_id = Literal::usize_unsuffixed(ring.ap_uio_id);
let mmap_id = Literal::usize_unsuffixed(ring.ap_mmap_id);
let len = Literal::usize_unsuffixed(ring.length as usize);
let elf_path = ring.elf.to_tokens();
let label = core.to_string();
quote! {
::consortium_runtime_app::dbg_console::DbgRingSpec {
uio_id: #uio_id,
mmap_id: #mmap_id,
len: #len,
elf_bytes: {
#[repr(align(16))]
struct Aligned<const N: usize>([u8; N]);
static ELF: Aligned<{ include_bytes!(#elf_path).len() }> =
Aligned(*include_bytes!(#elf_path));
&ELF.0
},
label: #label,
}
}
});
let specs = quote! { &[ #(#specs),* ] };
let field_name = format_ident!("dbg_console");
let (field, init) = match self.mode {
DbgMode::Auto => (
quote! {
pub #field_name: ::core::option::Option<
::consortium_runtime_app::dbg_console::DbgConsole
>
},
quote! {
let #field_name = if config.dbg_console {
::core::option::Option::Some(
::consortium_runtime_app::dbg_console::spawn(&#uio_ident, #specs)?
)
} else {
::core::option::Option::None
};
},
),
DbgMode::Interface => (
quote! {
pub #field_name: ::core::option::Option<
::std::vec::Vec<::consortium_runtime_app::dbg_console::DbgRingHandle>
>
},
quote! {
let #field_name = if config.dbg_console {
::core::option::Option::Some(
::consortium_runtime_app::dbg_console::open(&#uio_ident, #specs)?
)
} else {
::core::option::Option::None
};
},
),
};
Some(HostCodegen {
field,
field_name,
init,
})
}
pub fn env(&self) -> HashMap<VisibleCore, Vec<(&'static str, String)>> {
self.rings
.iter()
.map(|(core, ring)| (*core, ring.env()))
.collect()
}
}
pub struct HostCodegen {
pub field: TokenStream,
pub field_name: Ident,
pub init: TokenStream,
}
#[cfg(test)]
mod tests {
use super::*;
use consortium_cfg_common::config::McoreName;
fn cm33() -> VisibleCore {
VisibleCore::Mcore(McoreName::Cm33)
}
fn ring() -> DebugRing {
DebugRing {
base: 0x2040_8000,
length: 0x2000,
mtu: 64,
ap_uio_id: 3,
ap_mmap_id: 0,
elf: ElfLocation::verbatim("./firmware"),
}
}
#[test]
fn ring_emits_decimal_base_and_length_env() {
let env: HashMap<_, _> = ring().env().into_iter().collect();
assert_eq!(env["CONSORTIUM_DBG_BASE"], "541097984");
assert_eq!(env["CONSORTIUM_DBG_LENGTH"], "8192");
}
#[test]
fn logs_key_env_by_core() {
let logs = DebugLogs::new(HashMap::from([(cm33(), ring())]), DbgMode::Auto);
assert!(logs.ring(cm33()).is_some());
assert!(logs.ring(VisibleCore::Ap).is_none());
let env = logs.env();
let cm33_env: HashMap<_, _> = env[&cm33()].iter().cloned().collect();
assert_eq!(cm33_env["CONSORTIUM_DBG_LENGTH"], "8192");
}
#[test]
fn host_codegen_none_when_empty() {
let logs = DebugLogs::new(HashMap::new(), DbgMode::Auto);
let uio = format_ident!("uio");
assert!(logs.initialize_host(&uio).is_none());
}
#[test]
fn auto_mode_spawns_console_with_ring_spec() {
let logs = DebugLogs::new(HashMap::from([(cm33(), ring())]), DbgMode::Auto);
let uio = format_ident!("uio");
let host = logs.initialize_host(&uio).expect("rings present");
let init_str = host.init.to_string();
assert!(init_str.contains(":: consortium_runtime_app :: dbg_console :: spawn"));
assert!(init_str.contains("DbgRingSpec"));
assert!(init_str.contains("\"cm33\""));
assert!(init_str.contains("include_bytes ! (\"./firmware\")"));
assert!(!init_str.contains("elf_path"));
assert!(host.field.to_string().contains("DbgConsole"));
let init = &host.init;
let wrapped = quote! { fn _f(uio: ()) -> ::core::result::Result<(), ()> { #init Ok(()) } };
syn::parse_file(&wrapped.to_string()).expect("host init is valid Rust");
}
#[test]
fn with_resolved_elf_paths_embeds_the_build_host_path() {
let logs = DebugLogs::new(HashMap::from([(cm33(), ring())]), DbgMode::Auto);
let elf = ElfLocation::verbatim("/build/target/thumbv8m.main-none-eabihf/release/firmware");
let logs = logs.with_resolved_elf_paths(&HashMap::from([(cm33(), elf.clone())]));
assert_eq!(logs.ring(cm33()).expect("cm33 ring").elf, elf);
let uio = format_ident!("uio");
let host = logs.initialize_host(&uio).expect("rings present");
let init_str = host.init.to_string();
assert!(init_str.contains(
"include_bytes ! (\"/build/target/thumbv8m.main-none-eabihf/release/firmware\")"
));
let init = &host.init;
let wrapped = quote! { fn _f(uio: ()) -> ::core::result::Result<(), ()> { #init Ok(()) } };
syn::parse_file(&wrapped.to_string()).expect("host init is valid Rust");
}
#[test]
fn relative_elf_location_resolves_through_env() {
let logs = DebugLogs::new(HashMap::from([(cm33(), ring())]), DbgMode::Auto);
let elf = ElfLocation::relative_to(
"CSTI_MCU_PATH",
"target/thumbv8m.main-none-eabihf/release/firmware_original.elf",
);
let logs = logs.with_resolved_elf_paths(&HashMap::from([(cm33(), elf)]));
let uio = format_ident!("uio");
let host = logs.initialize_host(&uio).expect("rings present");
let init_str = host.init.to_string();
assert!(init_str.contains(
"include_bytes ! (:: core :: concat ! (:: core :: env ! (\"CSTI_MCU_PATH\") , \"/target/thumbv8m.main-none-eabihf/release/firmware_original.elf\"))"
));
let init = &host.init;
let wrapped = quote! { fn _f(uio: ()) -> ::core::result::Result<(), ()> { #init Ok(()) } };
syn::parse_file(&wrapped.to_string()).expect("host init is valid Rust");
}
#[test]
fn interface_mode_opens_reader_handles() {
let logs = DebugLogs::new(HashMap::from([(cm33(), ring())]), DbgMode::Interface);
let uio = format_ident!("uio");
let host = logs.initialize_host(&uio).expect("rings present");
assert!(
host.init
.to_string()
.contains(":: consortium_runtime_app :: dbg_console :: open")
);
assert!(host.field.to_string().contains("DbgRingHandle"));
}
}