From 6cd8c1c353de8cf16e3527016fdd2b1b9c83afc6 Mon Sep 17 00:00:00 2001 From: IkerGalardi Date: Thu, 26 Feb 2026 11:35:55 +0100 Subject: [PATCH 1/8] Add qemu_virt_aarch64_kvm platform This board differs from the standard qemu_virt_aarch64 board in that the entry point is in EL2 instead of EL1. This is necessary due to the lack of nested virtualization support on the KVM subsystem. Signed-off-by: IkerGalardi --- build_sdk.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/build_sdk.py b/build_sdk.py index c98143173..e9db01b1a 100644 --- a/build_sdk.py +++ b/build_sdk.py @@ -281,6 +281,21 @@ class KernelPath: "KernelArmExportPTMRUser": True, } | DEFAULT_KERNEL_OPTIONS_AARCH64, ), + BoardInfo( + name="qemu_virt_aarch64_kvm", + arch=KernelArch.AARCH64, + gcc_cpu="cortex-a53", + loader_link_address=0x70000000, + smp_cores=4, + kernel_options={ + "KernelPlatform": "qemu-arm-virt", + "KernelArmHypervisorSupport": False, + "QEMU_MEMORY": "2048", + # There is no peripheral timer, so we use the ARM + # architectural timer + "KernelArmExportPTMRUser": True, + } | DEFAULT_KERNEL_OPTIONS_AARCH64, + ), BoardInfo( name="qemu_virt_riscv64", arch=KernelArch.RISCV64, From d2031213f1f38b7d16ca1a8ed031c15b02607904 Mon Sep 17 00:00:00 2001 From: IkerGalardi Date: Thu, 26 Feb 2026 12:24:16 +0100 Subject: [PATCH 2/8] Correctly default intialize supported boards Previously the defaults where applied ALWAYS, meaning that if some board tried to specialize a kernel building parameter that the default configuration did, it would get overwriten by the default configuration. This patch applies the specialization to the default configuration instead of doing it the other way around. Signed-off-by: IkerGalardi --- build_sdk.py | 104 +++++++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/build_sdk.py b/build_sdk.py index e9db01b1a..97b4e345c 100644 --- a/build_sdk.py +++ b/build_sdk.py @@ -169,9 +169,9 @@ class KernelPath: gcc_cpu="cortex-a35", loader_link_address=0x90000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "tqma8xqp1gb", - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="zcu102", @@ -179,10 +179,10 @@ class KernelPath: gcc_cpu="cortex-a53", loader_link_address=0x40000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "zynqmp", "KernelARMPlatform": "zcu102", - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="maaxboard", @@ -190,9 +190,9 @@ class KernelPath: gcc_cpu="cortex-a53", loader_link_address=0x50000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "maaxboard", - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="imx8mm_evk", @@ -200,9 +200,9 @@ class KernelPath: gcc_cpu="cortex-a53", loader_link_address=0x41000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "imx8mm-evk", - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="imx8mp_evk", @@ -210,9 +210,9 @@ class KernelPath: gcc_cpu="cortex-a53", loader_link_address=0x41000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "imx8mp-evk", - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="imx8mq_evk", @@ -220,9 +220,9 @@ class KernelPath: gcc_cpu="cortex-a53", loader_link_address=0x41000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "imx8mq-evk", - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="imx8mp_iotgate", @@ -230,11 +230,11 @@ class KernelPath: gcc_cpu="cortex-a53", loader_link_address=0x50000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "imx8mp-evk", "KernelCustomDTS": Path("custom_dts/iot-gate.dts"), "KernelCustomDTSOverlay": KernelPath(path="src/plat/imx8m-evk/overlay-imx8mp-evk.dts"), - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="odroidc2", @@ -242,9 +242,9 @@ class KernelPath: gcc_cpu="cortex-a53", loader_link_address=0x20000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "odroidc2", - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="odroidc4", @@ -252,9 +252,9 @@ class KernelPath: gcc_cpu="cortex-a55", loader_link_address=0x20000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "odroidc4", - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="ultra96v2", @@ -262,10 +262,10 @@ class KernelPath: gcc_cpu="cortex-a53", loader_link_address=0x40000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "zynqmp", "KernelARMPlatform": "ultra96v2", - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="qemu_virt_aarch64", @@ -273,13 +273,13 @@ class KernelPath: gcc_cpu="cortex-a53", loader_link_address=0x70000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "qemu-arm-virt", "QEMU_MEMORY": "2048", # There is no peripheral timer, so we use the ARM # architectural timer "KernelArmExportPTMRUser": True, - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="qemu_virt_aarch64_kvm", @@ -287,14 +287,14 @@ class KernelPath: gcc_cpu="cortex-a53", loader_link_address=0x70000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "qemu-arm-virt", "KernelArmHypervisorSupport": False, "QEMU_MEMORY": "2048", # There is no peripheral timer, so we use the ARM # architectural timer "KernelArmExportPTMRUser": True, - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="qemu_virt_riscv64", @@ -302,10 +302,10 @@ class KernelPath: gcc_cpu=None, loader_link_address=0x90000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_RISCV64 | { "KernelPlatform": "qemu-riscv-virt", "QEMU_MEMORY": "2048", - } | DEFAULT_KERNEL_OPTIONS_RISCV64, + }, ), BoardInfo( name="rpi4b_1gb", @@ -313,10 +313,10 @@ class KernelPath: gcc_cpu="cortex-a72", loader_link_address=0x10000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "bcm2711", "RPI4_MEMORY": 1024, - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="rpi4b_2gb", @@ -324,10 +324,10 @@ class KernelPath: gcc_cpu="cortex-a72", loader_link_address=0x10000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "bcm2711", "RPI4_MEMORY": 2048, - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="rpi4b_4gb", @@ -335,10 +335,10 @@ class KernelPath: gcc_cpu="cortex-a72", loader_link_address=0x10000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "bcm2711", "RPI4_MEMORY": 4096, - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="rpi4b_8gb", @@ -346,10 +346,10 @@ class KernelPath: gcc_cpu="cortex-a72", loader_link_address=0x10000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "bcm2711", "RPI4_MEMORY": 8192, - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="rockpro64", @@ -359,18 +359,18 @@ class KernelPath: # ROCKPRO64 has 4 Cortex-A53 cores and 2 Cortex-A72 cores, # we always run on the Cortex-A53s. smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "rockpro64", - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="rock3b", arch=KernelArch.AARCH64, gcc_cpu="cortex-a55", loader_link_address=0x30000000, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_AARCH64 | { "KernelPlatform": "rk3568", - } | DEFAULT_KERNEL_OPTIONS_AARCH64, + }, ), BoardInfo( name="hifive_p550", @@ -378,9 +378,9 @@ class KernelPath: gcc_cpu=None, loader_link_address=0x90000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_RISCV64 | { "KernelPlatform": "hifive-p550", - } | DEFAULT_KERNEL_OPTIONS_RISCV64, + }, ), BoardInfo( name="star64", @@ -388,36 +388,36 @@ class KernelPath: gcc_cpu=None, loader_link_address=0x60000000, smp_cores=4, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_RISCV64 | { "KernelPlatform": "star64", - } | DEFAULT_KERNEL_OPTIONS_RISCV64, + }, ), BoardInfo( name="ariane", arch=KernelArch.RISCV64, gcc_cpu=None, loader_link_address=0x90000000, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_RISCV64 | { "KernelPlatform": "ariane", - } | DEFAULT_KERNEL_OPTIONS_RISCV64, + }, ), BoardInfo( name="cheshire", arch=KernelArch.RISCV64, gcc_cpu=None, loader_link_address=0x90000000, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_RISCV64 | { "KernelPlatform": "cheshire", - } | DEFAULT_KERNEL_OPTIONS_RISCV64, + }, ), BoardInfo( name="serengeti", arch=KernelArch.RISCV64, gcc_cpu=None, loader_link_address=0x90000000, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_RISCV64 | { "KernelPlatform": "cheshire", - } | DEFAULT_KERNEL_OPTIONS_RISCV64, + }, ), BoardInfo( name="x86_64_generic", @@ -425,10 +425,10 @@ class KernelPath: gcc_cpu="generic", loader_link_address=None, smp_cores=DEFAULT_X86_NUM_CPUS, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_X86_64 | { "KernelSupportPCID": False, "KernelVTX": False, - } | DEFAULT_KERNEL_OPTIONS_X86_64, + }, ), BoardInfo( name="x86_64_generic_vtx", @@ -436,11 +436,11 @@ class KernelPath: gcc_cpu="generic", loader_link_address=None, smp_cores=DEFAULT_X86_NUM_CPUS, - kernel_options={ + kernel_options=DEFAULT_KERNEL_OPTIONS_X86_64 | { "KernelSupportPCID": False, "KernelVTX": True, "KernelX86_64VTX64BitGuests": True, - } | DEFAULT_KERNEL_OPTIONS_X86_64, + }, ), ) From 735c25b8e0b02f8e6cf9c5b3fa903ecfe7f9b383 Mon Sep 17 00:00:00 2001 From: IkerGalardi Date: Thu, 26 Feb 2026 15:50:39 +0100 Subject: [PATCH 3/8] tool: remove arm hypervisor mode requirement Signed-off-by: IkerGalardi --- tool/microkit/src/main.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tool/microkit/src/main.rs b/tool/microkit/src/main.rs index 5b9f0a220..a2a9d14e4 100644 --- a/tool/microkit/src/main.rs +++ b/tool/microkit/src/main.rs @@ -401,13 +401,6 @@ fn main() -> Result<(), String> { std::process::exit(1); } - if let Arch::Aarch64 = kernel_config.arch { - assert!( - kernel_config.hypervisor, - "Microkit tool expects a kernel with hypervisor mode enabled on AArch64." - ); - } - assert!( kernel_config.word_size == 64, "Microkit tool has various assumptions about the word size being 64-bits." From 0b5c871048389d3c54ebe620c448ed3e3511156b Mon Sep 17 00:00:00 2001 From: IkerGalardi Date: Mon, 2 Mar 2026 16:38:37 +0100 Subject: [PATCH 4/8] loader: only configure interrupt groups on hypervisor mode EL1 software can not access interrupt group registers on the GIC distributor. Signed-off-by: IkerGalardi --- loader/src/aarch64/init.c | 61 ++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/loader/src/aarch64/init.c b/loader/src/aarch64/init.c index bc09276c6..252daffc5 100644 --- a/loader/src/aarch64/init.c +++ b/loader/src/aarch64/init.c @@ -25,37 +25,40 @@ #if defined(CONFIG_PLAT_ZYNQMP_ZCU102) || defined(CONFIG_PLAT_ZYNQMP_ULTRA96V2) || defined(CONFIG_PLAT_QEMU_ARM_VIRT) static void configure_gicv2(void) { - /* The ZCU102 start in EL3, and then we drop to EL1(NS). - * - * The GICv2 supports security extensions (as does the CPU). - * - * The GIC sets any interrupt as either Group 0 or Group 1. - * A Group 0 interrupt can only be configured in secure mode, - * while Group 1 interrupts can be configured from non-secure mode. - * - * As seL4 runs in non-secure mode, and we want seL4 to have - * the ability to configure interrupts, at this point we need - * to put all interrupts into Group 1. - * - * GICD_IGROUPn starts at offset 0x80. - * - * 0xF901_0000. - * - * Future work: On multicore systems the distributor setup - * only needs to be called once, while the GICC registers - * should be set for each CPU. - */ - puts("LDR|INFO: Setting all interrupts to Group 1\n"); - uint32_t gicd_typer = *((volatile uint32_t *)(GICD_BASE + 0x4)); - uint32_t it_lines_number = gicd_typer & 0x1f; - puts("LDR|INFO: GICv2 ITLinesNumber: "); - puthex32(it_lines_number); - puts("\n"); - - for (uint32_t i = 0; i <= it_lines_number; i++) { - *((volatile uint32_t *)(GICD_BASE + 0x80 + (i * 4))) = 0xFFFFFFFF; + if (is_set(CONFIG_ARM_HYPERVISOR_SUPPORT)) { + /* The ZCU102 start in EL3, and then we drop to EL1(NS). + * + * The GICv2 supports security extensions (as does the CPU). + * + * The GIC sets any interrupt as either Group 0 or Group 1. + * A Group 0 interrupt can only be configured in secure mode, + * while Group 1 interrupts can be configured from non-secure mode. + * + * As seL4 runs in non-secure mode, and we want seL4 to have + * the ability to configure interrupts, at this point we need + * to put all interrupts into Group 1. + * + * GICD_IGROUPn starts at offset 0x80. + * + * 0xF901_0000. + * + * Future work: On multicore systems the distributor setup + * only needs to be called once, while the GICC registers + * should be set for each CPU. + */ + puts("LDR|INFO: Setting all interrupts to Group 1\n"); + uint32_t gicd_typer = *((volatile uint32_t *)(GICD_BASE + 0x4)); + uint32_t it_lines_number = gicd_typer & 0x1f; + puts("LDR|INFO: GICv2 ITLinesNumber: "); + puthex32(it_lines_number); + puts("\n"); + + for (uint32_t i = 0; i <= it_lines_number; i++) { + *((volatile uint32_t *)(GICD_BASE + 0x80 + (i * 4))) = 0xFFFFFFFF; + } } + /* For any interrupts to go through the interrupt priority mask * must be set appropriately. Only interrupts with priorities less * than this mask will interrupt the CPU. From f0ee09516f944b225d03ab00a2f5f4528fb19d5d Mon Sep 17 00:00:00 2001 From: IkerGalardi Date: Fri, 6 Mar 2026 17:20:25 +0100 Subject: [PATCH 5/8] loader: fix el1_mmu_disable stack management The el1_mmu_disable function at the start pushed both x29 and x30 (frame pointer and link register) into the stack. But when ending the function, before the RET instruction, it poped 4 values instead of just the pushed 2, eating the stack frame of the parent scope. Probably copy-pasted from the el2_mmu_disable, which pushes x27, x28, x29 and x30 and pops all of them. Both functions are identical, but el1 version for some reason does not push x27 and x28. Signed-off-by: IkerGalardi --- loader/src/aarch64/util64.S | 1 - 1 file changed, 1 deletion(-) diff --git a/loader/src/aarch64/util64.S b/loader/src/aarch64/util64.S index 5137c76d9..ffec6da1d 100644 --- a/loader/src/aarch64/util64.S +++ b/loader/src/aarch64/util64.S @@ -302,7 +302,6 @@ BEGIN_FUNC(el1_mmu_disable) */ bl invalidate_icache - ldp x27, x28, [sp], #16 ldp x29, x30, [sp], #16 ret END_FUNC(el1_mmu_disable) From 27d89719920066dc2bcd1bf6f221a585cd60320b Mon Sep 17 00:00:00 2001 From: IkerGalardi Date: Mon, 9 Mar 2026 14:40:22 +0100 Subject: [PATCH 6/8] loader: fix stack management on el1_mmu_enable There was a missing ldp instruction poping the x27 and x28 values from the stack. Signed-off-by: IkerGalardi --- loader/src/aarch64/util64.S | 1 + 1 file changed, 1 insertion(+) diff --git a/loader/src/aarch64/util64.S b/loader/src/aarch64/util64.S index ffec6da1d..ccf1889b0 100644 --- a/loader/src/aarch64/util64.S +++ b/loader/src/aarch64/util64.S @@ -371,6 +371,7 @@ BEGIN_FUNC(el1_mmu_enable) enable_mmu sctlr_el1, x8 + ldp x27, x28, [sp], #16 ldp x29, x30, [sp], #16 ret From 61a9da0fb99e6e5f52154c0ab3d15b146408889f Mon Sep 17 00:00:00 2001 From: Julia Vassiliki Date: Wed, 20 May 2026 16:19:25 +1000 Subject: [PATCH 7/8] tool: generate page tables for EL1 support This is as per the documentation given above the function, just for some reason only lvl0_lower ever had anything placed in it. This makes the code dual-use for hypervisor/not. Signed-off-by: Julia Vassiliki --- tool/microkit/src/loader.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tool/microkit/src/loader.rs b/tool/microkit/src/loader.rs index fcf875e43..6ae3567d1 100644 --- a/tool/microkit/src/loader.rs +++ b/tool/microkit/src/loader.rs @@ -933,11 +933,14 @@ impl<'a> Loader<'a> { boot_lvl2_lower[start..end].copy_from_slice(&pt_entry.to_le_bytes()); } - let boot_lvl0_upper: [u8; PAGE_TABLE_SIZE] = [0; PAGE_TABLE_SIZE]; + let mut boot_lvl0_upper: [u8; PAGE_TABLE_SIZE] = [0; PAGE_TABLE_SIZE]; { let pt_entry = aarch64::table_descriptor(boot_lvl1_upper_addr); let idx = aarch64::lvl0_index(first_vaddr); + // For EL2. boot_lvl0_lower[8 * idx..8 * (idx + 1)].copy_from_slice(&pt_entry.to_le_bytes()); + // For EL1. + boot_lvl0_upper[8 * idx..8 * (idx + 1)].copy_from_slice(&pt_entry.to_le_bytes()); } let mut boot_lvl1_upper: [u8; PAGE_TABLE_SIZE] = [0; PAGE_TABLE_SIZE]; From e636f5194e3bb6242c23eb5edfcadf49131b2699 Mon Sep 17 00:00:00 2001 From: Julia Vassiliki Date: Wed, 20 May 2026 15:30:27 +1000 Subject: [PATCH 8/8] loader: hotfix SMP https://github.com/seL4/microkit/issues/493 Signed-off-by: Julia Vassiliki --- tool/microkit/src/loader.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/microkit/src/loader.rs b/tool/microkit/src/loader.rs index 6ae3567d1..4359af15d 100644 --- a/tool/microkit/src/loader.rs +++ b/tool/microkit/src/loader.rs @@ -105,7 +105,7 @@ pub mod aarch64 { pub const NORMAL_INNER_NC_OUTER_WBC: u64 = 0b1101; pub const NORMAL_INNER_WTC_OUTER_WBC: u64 = 0b1110; - pub const NORMAL_INNER_WBC_OUTER_WBC: u64 = 0b1111; + pub const NORMAL_INNER_WBC_OUTER_WBC: u64 = super::s1_mair_attr_index::MT_NORMAL; } pub mod descriptor_type {