use consortium_cfg::bridge::LoweredIr;
pub use consortium_cfg::components::dts_render::{RenderedDts, render_linux_dts};
pub const KCONFIG_NAME: &str = "consortium.cfg";
pub fn render_kernel_kconfig(chip: &str, ir: &LoweredIr) -> Option<String> {
let mut out = String::new();
if !ir.uio.is_empty() {
out.push_str(
"CONFIG_UIO=y\n\
CONFIG_UIO_PDRV_GENIRQ=y\n\
CONFIG_UIO_DMEM_GENIRQ=y\n",
);
}
if !ir.remoteproc.is_empty() {
let driver = if chip.starts_with("mimx9") {
Some("CONFIG_IMX_REMOTEPROC=y\n")
} else if chip.starts_with("stm32mp2") {
Some("CONFIG_STM32_RPROC=y\n")
} else {
None
};
if let Some(driver) = driver {
out.push_str("CONFIG_REMOTEPROC=y\n");
out.push_str(driver);
}
}
if out.is_empty() {
return None;
}
out.push_str(
"CONFIG_OF_OVERLAY=y\n\
CONFIG_OF_CONFIGFS=y\n",
);
Some(out)
}
pub fn kernel_bbappend_name(kernel_recipe: &str) -> String {
format!("{kernel_recipe}_%.bbappend")
}
pub fn render_kernel_bbappend(fragments: &[String]) -> Option<String> {
if fragments.is_empty() {
return None;
}
let src_uri = fragments
.iter()
.map(|name| format!(" file://{name} \\"))
.collect::<Vec<_>>()
.join("\n");
let merged = fragments
.iter()
.map(|name| format!(" ${{WORKDIR}}/{name} \\"))
.collect::<Vec<_>>()
.join("\n");
Some(format!(
"# Auto-generated by Consortium — do not edit by hand.\n\
#\n\
# Merges the UIO kernel config fragment(s) into the kernel build. Drop this\n\
# file and the sibling `.cfg`(s) into your BSP's kernel recipe directory\n\
# (e.g. recipes-kernel/linux/).\n\
\n\
FILESEXTRAPATHS:prepend := \"${{THISDIR}}:\"\n\
\n\
SRC_URI += \"\\\n{src_uri}\n\"\n\
\n\
KERNEL_CONFIG_FRAGMENTS:append = \"\\\n{merged}\n\"\n"
))
}
const UIO_BOOTARG: &str = "uio_pdrv_genirq.of_id=generic-uio";
pub fn render_boot(chip: &str, ir: &LoweredIr) -> Vec<(String, String)> {
if ir.uio.is_empty() {
return Vec::new();
}
if is_stm32(chip) {
vec![
(
"extlinux-uio.cfg".to_string(),
format!(
"# Auto-generated by Consortium — append the UIO kernel arg to the\n\
# OpenSTLinux extlinux command line. Add to a u-boot bbappend or a\n\
# machine/distro .conf so `extlinux.conf` is regenerated with it.\n\
UBOOT_EXTLINUX_KERNEL_ARGS:append = \" {UIO_BOOTARG}\"\n"
),
),
(
"README".to_string(),
format!(
"Consortium boot-args (STM32 / OpenSTLinux)\n\
=========================================\n\n\
The `generic-uio` driver only binds the Consortium UIO nodes when the\n\
kernel command line carries:\n\n \
{UIO_BOOTARG}\n\n\
OpenSTLinux boots via extlinux, so this is a build-time change:\n\n\
1. Copy `extlinux-uio.cfg` into your u-boot recipe (or a machine/distro\n\
`.conf`) so its `UBOOT_EXTLINUX_KERNEL_ARGS:append` takes effect.\n\
2. Rebuild; the generated `/boot/mmcN_extlinux/extlinux.conf` `APPEND`\n\
line now ends with the UIO arg.\n\n\
No manual `setenv` at the U-Boot prompt is required.\n"
),
),
]
} else {
vec![
(
"boot.cmd".to_string(),
format!(
"# Auto-generated by Consortium — U-Boot boot script source.\n\
# Appends the UIO kernel arg to the board's bootargs, then boots as usual.\n\
# Compile and stage it (no U-Boot rebuild needed):\n\
# mkimage -A arm64 -O linux -T script -C none -d boot.cmd boot.scr\n\
# then add `boot.scr` to IMAGE_BOOT_FILES so wic writes it to the FAT\n\
# boot partition; the i.MX distro bootcmd loads and runs it.\n\
setenv bootargs \"${{bootargs}} {UIO_BOOTARG}\"\n\
run distro_bootcmd\n"
),
),
(
"README".to_string(),
format!(
"Consortium boot-args (NXP i.MX)\n\
===============================\n\n\
The `generic-uio` driver only binds the Consortium UIO nodes when the\n\
kernel command line carries:\n\n \
{UIO_BOOTARG}\n\n\
i.MX U-Boot uses a scripted boot (no extlinux.conf), so ship a boot\n\
script rather than editing a `.conf`:\n\n\
1. Compile the script:\n\
mkimage -A arm64 -O linux -T script -C none -d boot.cmd boot.scr\n\
2. Stage `boot.scr` on the FAT boot partition, e.g. in your image recipe:\n\
IMAGE_BOOT_FILES += \"boot.scr\"\n\
3. Boot; U-Boot's distro bootcmd loads `boot.scr`, which appends the UIO\n\
arg to `bootargs` before booting.\n\n\
Alternative (baked into U-Boot): append the arg to `mmcargs` /\n\
`CONFIG_EXTRA_ENV_SETTINGS` via a `u-boot_%.bbappend`.\n"
),
),
]
}
}
fn is_stm32(chip: &str) -> bool {
chip.to_ascii_lowercase().starts_with("stm32")
}
#[cfg(test)]
mod tests {
use super::*;
fn ir_with_uio() -> LoweredIr {
use consortium_cfg::components::uio::{UioBacking, UioDevice};
LoweredIr::empty_with_uio(vec![(
"ipc-shm".to_string(),
UioDevice {
mmap: vec![(0x2040_0000, 0x8000)],
irq: None,
backing: UioBacking::ReservedMemory,
dma_coherent: Vec::new(),
interrupt_parent: None,
clock: None,
},
)])
}
#[test]
fn kconfig_enables_genirq_and_overlay_configfs() {
let cfg = render_kernel_kconfig("mimx9596xvt", &ir_with_uio()).unwrap();
assert!(cfg.contains("CONFIG_UIO=y"));
assert!(cfg.contains("CONFIG_UIO_PDRV_GENIRQ=y"));
assert!(cfg.contains("CONFIG_UIO_DMEM_GENIRQ=y"));
assert!(!cfg.contains("GENERIC"));
assert!(cfg.contains("CONFIG_OF_OVERLAY=y"));
assert!(cfg.contains("CONFIG_OF_CONFIGFS=y"));
}
#[test]
fn kconfig_none_without_uio() {
let empty = LoweredIr::empty_with_uio(vec![]);
assert!(render_kernel_kconfig("mimx9596xvt", &empty).is_none());
}
#[test]
fn remoteproc_kconfig_tracks_soc_family_without_uio() {
let ir = consortium_cfg::Session::from_toml(
r#"
[profile]
chip = "mimx9596xvt"
shared = "./shared"
deploy_root = "/opt/consortium"
[endpoints]
cm7 = "./mcu"
"#,
)
.unwrap()
.lower()
.unwrap();
let cfg = render_kernel_kconfig("mimx9596xvt", &ir).unwrap();
assert_eq!(
cfg,
"CONFIG_REMOTEPROC=y\nCONFIG_IMX_REMOTEPROC=y\nCONFIG_OF_OVERLAY=y\nCONFIG_OF_CONFIGFS=y\n"
);
}
#[test]
fn boot_stm32_emits_extlinux_fragment() {
let files = render_boot("stm32mp257fak3", &ir_with_uio());
let names: Vec<&str> = files.iter().map(|(n, _)| n.as_str()).collect();
assert!(names.contains(&"extlinux-uio.cfg"));
assert!(names.contains(&"README"));
let cfg = &files
.iter()
.find(|(n, _)| n == "extlinux-uio.cfg")
.unwrap()
.1;
assert!(cfg.contains("UBOOT_EXTLINUX_KERNEL_ARGS:append"));
assert!(cfg.contains(UIO_BOOTARG));
}
#[test]
fn boot_nxp_emits_boot_cmd() {
let files = render_boot("imx95", &ir_with_uio());
let names: Vec<&str> = files.iter().map(|(n, _)| n.as_str()).collect();
assert!(names.contains(&"boot.cmd"));
let cmd = &files.iter().find(|(n, _)| n == "boot.cmd").unwrap().1;
assert!(cmd.contains("setenv bootargs"));
assert!(cmd.contains(UIO_BOOTARG));
assert!(cmd.contains("mkimage"));
}
#[test]
fn boot_none_without_uio() {
assert!(render_boot("imx95", &LoweredIr::empty_with_uio(vec![])).is_empty());
}
#[test]
fn bbappend_name_wildcards_version() {
assert_eq!(
kernel_bbappend_name("linux-stm32mp"),
"linux-stm32mp_%.bbappend"
);
}
#[test]
fn bbappend_merges_fragments_at_build_time() {
let bb = render_kernel_bbappend(&[KCONFIG_NAME.to_string()])
.expect("fragment present yields a bbappend");
assert!(bb.contains("FILESEXTRAPATHS:prepend := \"${THISDIR}:\""));
assert!(bb.contains("file://consortium.cfg"));
assert!(bb.contains("KERNEL_CONFIG_FRAGMENTS:append"));
assert!(bb.contains("${WORKDIR}/consortium.cfg"));
assert!(!bb.contains("do_install"));
assert!(!bb.contains("${D}"));
}
#[test]
fn bbappend_none_without_fragments() {
assert!(render_kernel_bbappend(&[]).is_none());
}
}