consortium_builder/pipeline/
dts.rs1use consortium_cfg::bridge::LoweredIr;
28
29pub use consortium_cfg::components::dts_render::{RenderedDts, render_linux_dts};
30
31pub const KCONFIG_NAME: &str = "consortium.cfg";
33
34pub fn render_kernel_kconfig(chip: &str, ir: &LoweredIr) -> Option<String> {
51 let mut out = String::new();
52 if !ir.uio.is_empty() {
53 out.push_str(
54 "CONFIG_UIO=y\n\
55 CONFIG_UIO_PDRV_GENIRQ=y\n\
56 CONFIG_UIO_DMEM_GENIRQ=y\n",
57 );
58 }
59 if !ir.remoteproc.is_empty() {
60 let driver = if chip.starts_with("mimx9") {
61 Some("CONFIG_IMX_REMOTEPROC=y\n")
62 } else if chip.starts_with("stm32mp2") {
63 Some("CONFIG_STM32_RPROC=y\n")
64 } else {
65 None
66 };
67 if let Some(driver) = driver {
68 out.push_str("CONFIG_REMOTEPROC=y\n");
69 out.push_str(driver);
70 }
71 }
72 if out.is_empty() {
73 return None;
74 }
75 out.push_str(
76 "CONFIG_OF_OVERLAY=y\n\
77 CONFIG_OF_CONFIGFS=y\n",
78 );
79 Some(out)
80}
81
82pub fn kernel_bbappend_name(kernel_recipe: &str) -> String {
86 format!("{kernel_recipe}_%.bbappend")
87}
88
89pub fn render_kernel_bbappend(fragments: &[String]) -> Option<String> {
97 if fragments.is_empty() {
98 return None;
99 }
100
101 let src_uri = fragments
102 .iter()
103 .map(|name| format!(" file://{name} \\"))
104 .collect::<Vec<_>>()
105 .join("\n");
106 let merged = fragments
107 .iter()
108 .map(|name| format!(" ${{WORKDIR}}/{name} \\"))
109 .collect::<Vec<_>>()
110 .join("\n");
111
112 Some(format!(
113 "# Auto-generated by Consortium — do not edit by hand.\n\
114 #\n\
115 # Merges the UIO kernel config fragment(s) into the kernel build. Drop this\n\
116 # file and the sibling `.cfg`(s) into your BSP's kernel recipe directory\n\
117 # (e.g. recipes-kernel/linux/).\n\
118 \n\
119 FILESEXTRAPATHS:prepend := \"${{THISDIR}}:\"\n\
120 \n\
121 SRC_URI += \"\\\n{src_uri}\n\"\n\
122 \n\
123 KERNEL_CONFIG_FRAGMENTS:append = \"\\\n{merged}\n\"\n"
124 ))
125}
126
127const UIO_BOOTARG: &str = "uio_pdrv_genirq.of_id=generic-uio";
130
131pub fn render_boot(chip: &str, ir: &LoweredIr) -> Vec<(String, String)> {
147 if ir.uio.is_empty() {
148 return Vec::new();
149 }
150
151 if is_stm32(chip) {
152 vec![
153 (
154 "extlinux-uio.cfg".to_string(),
155 format!(
156 "# Auto-generated by Consortium — append the UIO kernel arg to the\n\
157 # OpenSTLinux extlinux command line. Add to a u-boot bbappend or a\n\
158 # machine/distro .conf so `extlinux.conf` is regenerated with it.\n\
159 UBOOT_EXTLINUX_KERNEL_ARGS:append = \" {UIO_BOOTARG}\"\n"
160 ),
161 ),
162 (
163 "README".to_string(),
164 format!(
165 "Consortium boot-args (STM32 / OpenSTLinux)\n\
166 =========================================\n\n\
167 The `generic-uio` driver only binds the Consortium UIO nodes when the\n\
168 kernel command line carries:\n\n \
169 {UIO_BOOTARG}\n\n\
170 OpenSTLinux boots via extlinux, so this is a build-time change:\n\n\
171 1. Copy `extlinux-uio.cfg` into your u-boot recipe (or a machine/distro\n\
172 `.conf`) so its `UBOOT_EXTLINUX_KERNEL_ARGS:append` takes effect.\n\
173 2. Rebuild; the generated `/boot/mmcN_extlinux/extlinux.conf` `APPEND`\n\
174 line now ends with the UIO arg.\n\n\
175 No manual `setenv` at the U-Boot prompt is required.\n"
176 ),
177 ),
178 ]
179 } else {
180 vec![
181 (
182 "boot.cmd".to_string(),
183 format!(
184 "# Auto-generated by Consortium — U-Boot boot script source.\n\
185 # Appends the UIO kernel arg to the board's bootargs, then boots as usual.\n\
186 # Compile and stage it (no U-Boot rebuild needed):\n\
187 # mkimage -A arm64 -O linux -T script -C none -d boot.cmd boot.scr\n\
188 # then add `boot.scr` to IMAGE_BOOT_FILES so wic writes it to the FAT\n\
189 # boot partition; the i.MX distro bootcmd loads and runs it.\n\
190 setenv bootargs \"${{bootargs}} {UIO_BOOTARG}\"\n\
191 run distro_bootcmd\n"
192 ),
193 ),
194 (
195 "README".to_string(),
196 format!(
197 "Consortium boot-args (NXP i.MX)\n\
198 ===============================\n\n\
199 The `generic-uio` driver only binds the Consortium UIO nodes when the\n\
200 kernel command line carries:\n\n \
201 {UIO_BOOTARG}\n\n\
202 i.MX U-Boot uses a scripted boot (no extlinux.conf), so ship a boot\n\
203 script rather than editing a `.conf`:\n\n\
204 1. Compile the script:\n\
205 mkimage -A arm64 -O linux -T script -C none -d boot.cmd boot.scr\n\
206 2. Stage `boot.scr` on the FAT boot partition, e.g. in your image recipe:\n\
207 IMAGE_BOOT_FILES += \"boot.scr\"\n\
208 3. Boot; U-Boot's distro bootcmd loads `boot.scr`, which appends the UIO\n\
209 arg to `bootargs` before booting.\n\n\
210 Alternative (baked into U-Boot): append the arg to `mmcargs` /\n\
211 `CONFIG_EXTRA_ENV_SETTINGS` via a `u-boot_%.bbappend`.\n"
212 ),
213 ),
214 ]
215 }
216}
217
218fn is_stm32(chip: &str) -> bool {
222 chip.to_ascii_lowercase().starts_with("stm32")
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 fn ir_with_uio() -> LoweredIr {
230 use consortium_cfg::components::uio::{UioBacking, UioDevice};
231 LoweredIr::empty_with_uio(vec![(
232 "ipc-shm".to_string(),
233 UioDevice {
234 mmap: vec![(0x2040_0000, 0x8000)],
235 irq: None,
236 backing: UioBacking::ReservedMemory,
237 dma_coherent: Vec::new(),
238 interrupt_parent: None,
239 clock: None,
240 },
241 )])
242 }
243
244 #[test]
245 fn kconfig_enables_genirq_and_overlay_configfs() {
246 let cfg = render_kernel_kconfig("mimx9596xvt", &ir_with_uio()).unwrap();
247 assert!(cfg.contains("CONFIG_UIO=y"));
248 assert!(cfg.contains("CONFIG_UIO_PDRV_GENIRQ=y"));
249 assert!(cfg.contains("CONFIG_UIO_DMEM_GENIRQ=y"));
250 assert!(!cfg.contains("GENERIC"));
251 assert!(cfg.contains("CONFIG_OF_OVERLAY=y"));
252 assert!(cfg.contains("CONFIG_OF_CONFIGFS=y"));
253 }
254
255 #[test]
256 fn kconfig_none_without_uio() {
257 let empty = LoweredIr::empty_with_uio(vec![]);
258 assert!(render_kernel_kconfig("mimx9596xvt", &empty).is_none());
259 }
260
261 #[test]
262 fn remoteproc_kconfig_tracks_soc_family_without_uio() {
263 let ir = consortium_cfg::Session::from_toml(
264 r#"
265 [profile]
266 chip = "mimx9596xvt"
267 shared = "./shared"
268 deploy_root = "/opt/consortium"
269
270 [endpoints]
271 cm7 = "./mcu"
272 "#,
273 )
274 .unwrap()
275 .lower()
276 .unwrap();
277 let cfg = render_kernel_kconfig("mimx9596xvt", &ir).unwrap();
278 assert_eq!(
279 cfg,
280 "CONFIG_REMOTEPROC=y\nCONFIG_IMX_REMOTEPROC=y\nCONFIG_OF_OVERLAY=y\nCONFIG_OF_CONFIGFS=y\n"
281 );
282 }
283
284 #[test]
285 fn boot_stm32_emits_extlinux_fragment() {
286 let files = render_boot("stm32mp257fak3", &ir_with_uio());
287 let names: Vec<&str> = files.iter().map(|(n, _)| n.as_str()).collect();
288 assert!(names.contains(&"extlinux-uio.cfg"));
289 assert!(names.contains(&"README"));
290 let cfg = &files
291 .iter()
292 .find(|(n, _)| n == "extlinux-uio.cfg")
293 .unwrap()
294 .1;
295 assert!(cfg.contains("UBOOT_EXTLINUX_KERNEL_ARGS:append"));
296 assert!(cfg.contains(UIO_BOOTARG));
297 }
298
299 #[test]
300 fn boot_nxp_emits_boot_cmd() {
301 let files = render_boot("imx95", &ir_with_uio());
302 let names: Vec<&str> = files.iter().map(|(n, _)| n.as_str()).collect();
303 assert!(names.contains(&"boot.cmd"));
304 let cmd = &files.iter().find(|(n, _)| n == "boot.cmd").unwrap().1;
305 assert!(cmd.contains("setenv bootargs"));
306 assert!(cmd.contains(UIO_BOOTARG));
307 assert!(cmd.contains("mkimage"));
308 }
309
310 #[test]
311 fn boot_none_without_uio() {
312 assert!(render_boot("imx95", &LoweredIr::empty_with_uio(vec![])).is_empty());
313 }
314
315 #[test]
316 fn bbappend_name_wildcards_version() {
317 assert_eq!(
318 kernel_bbappend_name("linux-stm32mp"),
319 "linux-stm32mp_%.bbappend"
320 );
321 }
322
323 #[test]
324 fn bbappend_merges_fragments_at_build_time() {
325 let bb = render_kernel_bbappend(&[KCONFIG_NAME.to_string()])
326 .expect("fragment present yields a bbappend");
327 assert!(bb.contains("FILESEXTRAPATHS:prepend := \"${THISDIR}:\""));
328 assert!(bb.contains("file://consortium.cfg"));
329 assert!(bb.contains("KERNEL_CONFIG_FRAGMENTS:append"));
330 assert!(bb.contains("${WORKDIR}/consortium.cfg"));
331 assert!(!bb.contains("do_install"));
333 assert!(!bb.contains("${D}"));
334 }
335
336 #[test]
337 fn bbappend_none_without_fragments() {
338 assert!(render_kernel_bbappend(&[]).is_none());
339 }
340}