Shared memory
IPCC · MU · HSEM · GPIO doorbellsA descriptor plus 64-byte-aligned slots in a shared window. Splits into Tx/Rx halves; config.ack adds acknowledged sends on their own doorbell lines.
pattern = "memory"Consortium pairs a Linux-capable A-core with Cortex-M real-time cores, the heterogeneous and asymmetric multiprocessing (HAMPU) pattern behind SoCs such as i.MX 9 and STM32MP2, with typed IPC, explicit cross-core contracts, and code generated from one manifest.
cargo install csti
#[consortium_runtime_mcu::main]
async fn main(_s: Spawner) -> Result<(), ConsortiumError> {
let (_p, c) = init(Config::default());
let mut sensor = Transceiver::connect(c.SENSOR).await?;
for seq in 0u32.. {
let temperature_mdc = read_sensor();
let reading = SensorReading { temperature_mdc, seq };
sensor.send(&reading).await?;
// IPCC1 rings; the CA35 wakes and decodes it
Timer::after_secs(1).await;
}
Ok(())
}
The Linux host sits at the center: talking down to the real-time controller, up into the secure world, left to the kiosk interface, and right into the broader ecosystem.
Badged parts are not production-ready: TEE code is generated to compile against Teaclave but is not yet formally tested, the HMI is untested, and the integration layers are in development.
csti reads Consortium.toml, validates it against the embedded chip database, and generates a consortium.gen.rs for every endpoint, including memory map, doorbell vectors, time driver, and a synchronous init() that hands back typed Peripherals and Channels.
Each channel then runs its own async Transceiver::connect handshake, so bring-up is one line per side with no Primary/Secondary branch.
csti new my-soc --chip stm32mp257fScaffold a manifest plus app, firmware and shared crates; -i asks instead.csti checkValidate the manifest and cargo check every endpoint.csti build --linux <DIR>Build app, firmware, TA and device tree into a deployable tree.csti fetchPrefetch dependencies so build can run --offline.[profile]
chip = "stm32mp257fak3"
devkit = "stm32mp257f-dk"
shared = "../shared"
deploy_root = "/opt/consortium"
[endpoints]
linux = "./app"
optee = "./ta"
cm33 = "./mcu"
[[ipc.channel]]
name = "sensor"
doorbell = "ipcc1"
pattern = "memory"
primary = "linux"
secondary = "cm33"
party.linux = { chan = 0, type = "Command" }
party.cm33 = { chan = 1, type = "SensorReading" }
config.memory = { base = 0x00064000, length = 0x4000 }
mtu = 128#[derive(Clone, Copy, Debug, IpcSafe, Serialize, Deserialize)]
pub struct SensorReading {
pub temperature_mdc: i32,
pub seq: u32,
}#[consortium_runtime_app::main]
async fn main() -> Result<(), ConsortiumError> {
let (_p, c) = init(Config::default())?;
let mut sensor = Transceiver::connect(c.SENSOR).await?;
let mut ctx = Context::new()?;
let mut session = ctx.open_session(Uuid::parse_str(TA_UUID)?)?;
loop {
let reading: SensorReading = sensor.recv().await?.into_inner();
let ok = call_check_temperature(&mut session, reading.temperature_mdc)?;
tracing::info!(seq = reading.seq, ok, "verdict from the secure world");
}
}use consortium_tee::{tee_command, tee_service};
/// Runs inside OP-TEE. The macro also emits `call_check_temperature`
/// for the Linux side, so both halves share one signature.
#[tee_command]
pub fn check_temperature(temperature_mdc: i32) -> bool {
(-20_000..=85_000).contains(&temperature_mdc)
}
tee_service! {
commands { check_temperature }
}Every transport implements the same SendTransport, RecvTransport and Connect traits, so Channel and Transceiver code is unchanged whichever one carries it.
A descriptor plus 64-byte-aligned slots in a shared window. Splits into Tx/Rx halves; config.ack adds acknowledged sends on their own doorbell lines.
pattern = "memory"COBS(chan | payload | CRC32) framing. Tokio serial on Linux, an IRQ-buffered BufferedUart on firmware.
One header byte segments messages across classic or FD frames. Each direction binds its own CAN identifier.
Message types derive IpcSafe, which rejects pointers, references, usize and anything else that means something different on the other core.
consortium-dbg carries defmt frames off the M-core through a shared-memory ring; the generated app decodes them into tracing events beside the host’s own logs.
2026-10-29T13:18:17.601238Z TRACE consortium_dbg::host: HostRing::from_raw_parts(base = 0xffffb3646000, len = 0x2000)
2026-10-29T13:18:17.601463Z TRACE consortium_dbg::host: HostRing::from_raw_parts: descriptor capacity = 0x2000, read_ptr = 0x10, write_ptr = 0x552
2026-10-29T13:18:17.601790Z INFO consortium_dbg::host: defmt table parsed: encoding = Rzcobs, has_timestamp = true
2026-10-29T13:18:17.602114Z INFO melt_pot_stm32mp25_app: melt-pot: CA35 application started; IPC bring-up complete
2026-10-29T13:18:18.603871Z INFO consortium::dbg: sensor seq=0 temp_mdc=20000 core="cm33"
2026-10-29T13:18:19.603902Z INFO consortium::dbg: sensor seq=1 temp_mdc=20500 core="cm33"
From wire protocol to human interface, each concern is its own crate.
Typed Channel and Transceiver, transport and doorbell traits, and IpcSafe.
Descriptor/slot shared memory with IPCC, MU and HSEM doorbells.
Docs →COBS/CRC-framed IPC over a serial line, for Linux and firmware.
Docs →Segmented IPC over classic and FD CAN frames.
Docs →postcard, prost and rkyv codec families for IPC and TEE.
Docs →Consortium.toml validation, code generation, and DTS/UIO transpilers.
Singleton-owned drivers and generated PACs for i.MX 9 and STM32MP2.
Docs →The CLI: check, fetch and build every endpoint from one manifest.
Docs →Shared-memory defmt logging with an optional panic handler.
One defmt/tracing facade so generated and cross-platform code share a call site.
Write OP-TEE commands as plain Rust functions; #[tee_command] generates code that compiles against Teaclave.
Backend-neutral command bridge for WPE WebKit and PocketJS kiosks.
Docs →STM32MP25 and i.MX 93 are officially supported and tested in hardware. STM32MP23 and STM32MP21 reuse the STM32MP25 support without formal verification, i.MX 95 is still being brought up, and i.MX 94 is planned.
aarch64-unknown-linux-gnu and thumbv8m.main-none-eabihf run on real hardware. The other listed targets compile the code, but have not run on a board yet. Dashed targets are not built yet; support is coming.