* [RFC PATCH 1/7] target/arm: Flush TLB on MECID write
2026-03-19 2:23 [RFC PATCH 0/7] Add FEAT_MEC (encryption) Gustavo Romero
@ 2026-03-19 2:23 ` Gustavo Romero
2026-03-26 23:29 ` Richard Henderson
2026-03-19 2:23 ` [RFC PATCH 2/7] target/cpu: Add two new address spaces for FEAT_MEC Gustavo Romero
` (5 subsequent siblings)
6 siblings, 1 reply; 19+ messages in thread
From: Gustavo Romero @ 2026-03-19 2:23 UTC (permalink / raw)
To: qemu-devel, qemu-arm, richard.henderson
Cc: alex.bennee, peter.maydell, gustavo.romero
Flush TLB on write to any MECID register. This allow us to take the slow
path and check if the MECID is the correct.
Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
---
target/arm/helper.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/target/arm/helper.c b/target/arm/helper.c
index 6bfab90981..ec25ec8444 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -5125,6 +5125,8 @@ static CPAccessResult mecid_access(CPUARMState *env,
static void mecid_write(CPUARMState *env, const ARMCPRegInfo *ri,
uint64_t value)
{
+ tlb_flush(CPU(env_archcpu(env)));
+
value = extract64(value, 0, MECID_WIDTH);
raw_write(env, ri, value);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 19+ messages in thread* Re: [RFC PATCH 1/7] target/arm: Flush TLB on MECID write
2026-03-19 2:23 ` [RFC PATCH 1/7] target/arm: Flush TLB on MECID write Gustavo Romero
@ 2026-03-26 23:29 ` Richard Henderson
0 siblings, 0 replies; 19+ messages in thread
From: Richard Henderson @ 2026-03-26 23:29 UTC (permalink / raw)
To: Gustavo Romero, qemu-devel, qemu-arm; +Cc: alex.bennee, peter.maydell
On 3/19/26 12:23, Gustavo Romero wrote:
> Flush TLB on write to any MECID register. This allow us to take the slow
> path and check if the MECID is the correct.
>
> Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
> ---
> target/arm/helper.c | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/target/arm/helper.c b/target/arm/helper.c
> index 6bfab90981..ec25ec8444 100644
> --- a/target/arm/helper.c
> +++ b/target/arm/helper.c
> @@ -5125,6 +5125,8 @@ static CPAccessResult mecid_access(CPUARMState *env,
> static void mecid_write(CPUARMState *env, const ARMCPRegInfo *ri,
> uint64_t value)
> {
> + tlb_flush(CPU(env_archcpu(env)));
> +
> value = extract64(value, 0, MECID_WIDTH);
> raw_write(env, ri, value);
> }
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
r~
^ permalink raw reply [flat|nested] 19+ messages in thread
* [RFC PATCH 2/7] target/cpu: Add two new address spaces for FEAT_MEC
2026-03-19 2:23 [RFC PATCH 0/7] Add FEAT_MEC (encryption) Gustavo Romero
2026-03-19 2:23 ` [RFC PATCH 1/7] target/arm: Flush TLB on MECID write Gustavo Romero
@ 2026-03-19 2:23 ` Gustavo Romero
2026-03-26 23:30 ` Richard Henderson
2026-03-19 2:23 ` [RFC PATCH 3/7] memattrs: Add new attribute 'encrypted' Gustavo Romero
` (4 subsequent siblings)
6 siblings, 1 reply; 19+ messages in thread
From: Gustavo Romero @ 2026-03-19 2:23 UTC (permalink / raw)
To: qemu-devel, qemu-arm, richard.henderson
Cc: alex.bennee, peter.maydell, gustavo.romero
Add two new address spaces for FEAT_MEC. One is used to store the
substitute encrypted page, which is returned instead of the actual
encrypted page in case of a MECID mismatch. The other is used to store
the MECIDs themselves, associated with physical addresses.
Signed-off-by: Gustavo Bueno <gustavo.romero@linaro.org>
---
target/arm/cpu.h | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/target/arm/cpu.h b/target/arm/cpu.h
index 657ff4ab20..87c1787c9b 100644
--- a/target/arm/cpu.h
+++ b/target/arm/cpu.h
@@ -2364,7 +2364,9 @@ typedef enum ARMASIdx {
ARMASIdx_S = 1,
ARMASIdx_TagNS = 2,
ARMASIdx_TagS = 3,
- ARMASIdx_MAX = ARMASIdx_TagS
+ ARMASIdx_MEC = 4,
+ ARMASIdx_MEC_PAGE = 5,
+ ARMASIdx_MAX = ARMASIdx_MEC_PAGE
} ARMASIdx;
static inline ARMMMUIdx arm_space_to_phys(ARMSecuritySpace space)
--
2.34.1
^ permalink raw reply related [flat|nested] 19+ messages in thread* Re: [RFC PATCH 2/7] target/cpu: Add two new address spaces for FEAT_MEC
2026-03-19 2:23 ` [RFC PATCH 2/7] target/cpu: Add two new address spaces for FEAT_MEC Gustavo Romero
@ 2026-03-26 23:30 ` Richard Henderson
0 siblings, 0 replies; 19+ messages in thread
From: Richard Henderson @ 2026-03-26 23:30 UTC (permalink / raw)
To: Gustavo Romero, qemu-devel, qemu-arm; +Cc: alex.bennee, peter.maydell
On 3/19/26 12:23, Gustavo Romero wrote:
> Add two new address spaces for FEAT_MEC. One is used to store the
> substitute encrypted page, which is returned instead of the actual
> encrypted page in case of a MECID mismatch. The other is used to store
> the MECIDs themselves, associated with physical addresses.
>
> Signed-off-by: Gustavo Bueno <gustavo.romero@linaro.org>
> ---
> target/arm/cpu.h | 4 +++-
> 1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/target/arm/cpu.h b/target/arm/cpu.h
> index 657ff4ab20..87c1787c9b 100644
> --- a/target/arm/cpu.h
> +++ b/target/arm/cpu.h
> @@ -2364,7 +2364,9 @@ typedef enum ARMASIdx {
> ARMASIdx_S = 1,
> ARMASIdx_TagNS = 2,
> ARMASIdx_TagS = 3,
> - ARMASIdx_MAX = ARMASIdx_TagS
> + ARMASIdx_MEC = 4,
> + ARMASIdx_MEC_PAGE = 5,
> + ARMASIdx_MAX = ARMASIdx_MEC_PAGE
> } ARMASIdx;
>
> static inline ARMMMUIdx arm_space_to_phys(ARMSecuritySpace space)
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
r~
^ permalink raw reply [flat|nested] 19+ messages in thread
* [RFC PATCH 3/7] memattrs: Add new attribute 'encrypted'
2026-03-19 2:23 [RFC PATCH 0/7] Add FEAT_MEC (encryption) Gustavo Romero
2026-03-19 2:23 ` [RFC PATCH 1/7] target/arm: Flush TLB on MECID write Gustavo Romero
2026-03-19 2:23 ` [RFC PATCH 2/7] target/cpu: Add two new address spaces for FEAT_MEC Gustavo Romero
@ 2026-03-19 2:23 ` Gustavo Romero
2026-03-26 23:39 ` Richard Henderson
2026-03-19 2:23 ` [RFC PATCH 4/7] target/arm/cpu: Add FEAT_MEC object properties Gustavo Romero
` (3 subsequent siblings)
6 siblings, 1 reply; 19+ messages in thread
From: Gustavo Romero @ 2026-03-19 2:23 UTC (permalink / raw)
To: qemu-devel, qemu-arm, richard.henderson
Cc: alex.bennee, peter.maydell, gustavo.romero
Add new attribute to the memory transaction attributes that is used
on Arm to select the address space where the encrypted page resides.
Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
---
include/exec/memattrs.h | 7 +++++++
target/arm/cpu.h | 6 +++++-
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/include/exec/memattrs.h b/include/exec/memattrs.h
index 52ee955249..efe9d9c7ba 100644
--- a/include/exec/memattrs.h
+++ b/include/exec/memattrs.h
@@ -57,6 +57,13 @@ typedef struct MemTxAttrs {
/* PCI - IOMMU operations, see PCIAddressType */
unsigned int address_type:1;
+ /*
+ * Memory is encrypted. This is used to acccess the substitute
+ * encrypted page that has its own address space. This is required
+ * by FEAT_MEC on ARM.
+ */
+ unsigned int encrypted:1;
+
/*
* Bus masters which don't specify any attributes will get this
* (via the MEMTXATTRS_UNSPECIFIED constant), so that we can
diff --git a/target/arm/cpu.h b/target/arm/cpu.h
index 87c1787c9b..ebf4e4e01c 100644
--- a/target/arm/cpu.h
+++ b/target/arm/cpu.h
@@ -2596,7 +2596,11 @@ enum {
/* Return the address space index to use for a memory access */
static inline int arm_asidx_from_attrs(CPUState *cs, MemTxAttrs attrs)
{
- return attrs.secure ? ARMASIdx_S : ARMASIdx_NS;
+ if (attrs.encrypted) {
+ return ARMASIdx_MEC_PAGE;
+ } else {
+ return attrs.secure ? ARMASIdx_S : ARMASIdx_NS;
+ }
}
/* Return the AddressSpace to use for a memory access
--
2.34.1
^ permalink raw reply related [flat|nested] 19+ messages in thread* Re: [RFC PATCH 3/7] memattrs: Add new attribute 'encrypted'
2026-03-19 2:23 ` [RFC PATCH 3/7] memattrs: Add new attribute 'encrypted' Gustavo Romero
@ 2026-03-26 23:39 ` Richard Henderson
0 siblings, 0 replies; 19+ messages in thread
From: Richard Henderson @ 2026-03-26 23:39 UTC (permalink / raw)
To: Gustavo Romero, qemu-devel, qemu-arm; +Cc: alex.bennee, peter.maydell
On 3/19/26 12:23, Gustavo Romero wrote:
> Add new attribute to the memory transaction attributes that is used
> on Arm to select the address space where the encrypted page resides.
>
> Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
> ---
> include/exec/memattrs.h | 7 +++++++
> target/arm/cpu.h | 6 +++++-
> 2 files changed, 12 insertions(+), 1 deletion(-)
>
> diff --git a/include/exec/memattrs.h b/include/exec/memattrs.h
> index 52ee955249..efe9d9c7ba 100644
> --- a/include/exec/memattrs.h
> +++ b/include/exec/memattrs.h
> @@ -57,6 +57,13 @@ typedef struct MemTxAttrs {
> /* PCI - IOMMU operations, see PCIAddressType */
> unsigned int address_type:1;
>
> + /*
> + * Memory is encrypted. This is used to acccess the substitute
> + * encrypted page that has its own address space. This is required
> + * by FEAT_MEC on ARM.
> + */
> + unsigned int encrypted:1;
> +
> /*
> * Bus masters which don't specify any attributes will get this
> * (via the MEMTXATTRS_UNSPECIFIED constant), so that we can
> diff --git a/target/arm/cpu.h b/target/arm/cpu.h
> index 87c1787c9b..ebf4e4e01c 100644
> --- a/target/arm/cpu.h
> +++ b/target/arm/cpu.h
> @@ -2596,7 +2596,11 @@ enum {
> /* Return the address space index to use for a memory access */
> static inline int arm_asidx_from_attrs(CPUState *cs, MemTxAttrs attrs)
> {
> - return attrs.secure ? ARMASIdx_S : ARMASIdx_NS;
> + if (attrs.encrypted) {
> + return ARMASIdx_MEC_PAGE;
> + } else {
> + return attrs.secure ? ARMASIdx_S : ARMASIdx_NS;
> + }
I wish there were another way besides modifying MemTXAttrs, but at present there is not.
Longer term, it might be better to store the AddressSpace directly in CPUTLBEntryFull,
allowing tlb_fill to set it directly, and then all of the asidx_from_attrs functions go
away. Anyway, that's future work.
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
r~
^ permalink raw reply [flat|nested] 19+ messages in thread
* [RFC PATCH 4/7] target/arm/cpu: Add FEAT_MEC object properties
2026-03-19 2:23 [RFC PATCH 0/7] Add FEAT_MEC (encryption) Gustavo Romero
` (2 preceding siblings ...)
2026-03-19 2:23 ` [RFC PATCH 3/7] memattrs: Add new attribute 'encrypted' Gustavo Romero
@ 2026-03-19 2:23 ` Gustavo Romero
2026-03-26 23:40 ` Richard Henderson
2026-03-19 2:23 ` [RFC PATCH 5/7] hw/arm/virt: Add machine option 'mec' Gustavo Romero
` (2 subsequent siblings)
6 siblings, 1 reply; 19+ messages in thread
From: Gustavo Romero @ 2026-03-19 2:23 UTC (permalink / raw)
To: qemu-devel, qemu-arm, richard.henderson
Cc: alex.bennee, peter.maydell, gustavo.romero
Add 'mec' and 'mec-page' object properties to the Arm CPU object that
allows setting at machine level the memories used by FEAT_MEC.
Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
---
target/arm/cpu.c | 20 ++++++++++++++++++++
target/arm/cpu.h | 4 ++++
2 files changed, 24 insertions(+)
diff --git a/target/arm/cpu.c b/target/arm/cpu.c
index 7e3e84b4bb..98bb559eb2 100644
--- a/target/arm/cpu.c
+++ b/target/arm/cpu.c
@@ -1542,6 +1542,21 @@ static void arm_cpu_post_init(Object *obj)
OBJ_PROP_LINK_STRONG);
}
}
+
+ if (arm_feature(&cpu->env, ARM_FEATURE_AARCH64) &&
+ cpu_isar_feature(aa64_mec, cpu)) {
+
+ object_property_add_link(obj, "mec", TYPE_MEMORY_REGION,
+ (Object **)&cpu->mec,
+ qdev_prop_allow_set_link_before_realize,
+ OBJ_PROP_LINK_STRONG);
+
+ object_property_add_link(obj, "mec-page",
+ TYPE_MEMORY_REGION,
+ (Object **)&cpu->mec_page,
+ qdev_prop_allow_set_link_before_realize,
+ OBJ_PROP_LINK_STRONG);
+ }
#endif
qdev_property_add_static(DEVICE(obj), &arm_cpu_cfgend_property);
}
@@ -2169,6 +2184,11 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp)
}
}
+ if (cpu->mec != NULL) {
+ cpu_address_space_init(cs, ARMASIdx_MEC, "cpu-tuple-memory", cpu->mec);
+ cpu_address_space_init(cs, ARMASIdx_MEC_PAGE, "cpu-pseudo-encrypted-page", cpu->mec_page);
+ }
+
/* No core_count specified, default to smp_cpus. */
if (cpu->core_count == -1) {
cpu->core_count = smp_cpus;
diff --git a/target/arm/cpu.h b/target/arm/cpu.h
index ebf4e4e01c..a91db25d68 100644
--- a/target/arm/cpu.h
+++ b/target/arm/cpu.h
@@ -977,6 +977,10 @@ struct ArchCPU {
MemoryRegion *tag_memory;
MemoryRegion *secure_tag_memory;
+ /* MemoryRegion use by FEAT_MEC. */
+ MemoryRegion *mec;
+ MemoryRegion *mec_page;
+
/* For v8M, pointer to the IDAU interface provided by board/SoC */
Object *idau;
--
2.34.1
^ permalink raw reply related [flat|nested] 19+ messages in thread* [RFC PATCH 5/7] hw/arm/virt: Add machine option 'mec'
2026-03-19 2:23 [RFC PATCH 0/7] Add FEAT_MEC (encryption) Gustavo Romero
` (3 preceding siblings ...)
2026-03-19 2:23 ` [RFC PATCH 4/7] target/arm/cpu: Add FEAT_MEC object properties Gustavo Romero
@ 2026-03-19 2:23 ` Gustavo Romero
2026-03-19 9:46 ` Jonathan Cameron via qemu development
2026-03-27 0:10 ` Richard Henderson
2026-03-19 2:23 ` [RFC PATCH 6/7] target/arm/ptw: Add MECID checks Gustavo Romero
2026-03-19 2:23 ` [RFC PATCH 7/7] target/arm/ptw: Print MECID if not zero [TO BE DISCARDED] Gustavo Romero
6 siblings, 2 replies; 19+ messages in thread
From: Gustavo Romero @ 2026-03-19 2:23 UTC (permalink / raw)
To: qemu-devel, qemu-arm, richard.henderson
Cc: alex.bennee, peter.maydell, gustavo.romero
Add new machine option 'mec' that enables and sets the memory used by
FEAT_MEC.
Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
---
hw/arm/virt.c | 76 +++++++++++++++++++++++++++++++++++++++++++
include/hw/arm/virt.h | 1 +
2 files changed, 77 insertions(+)
diff --git a/hw/arm/virt.c b/hw/arm/virt.c
index cab2e21e8a..7d127a4205 100644
--- a/hw/arm/virt.c
+++ b/hw/arm/virt.c
@@ -1762,6 +1762,27 @@ static void create_tag_ram(MemoryRegion *tag_sysmem,
memory_region_add_subregion(tag_sysmem, base / 32, tagram);
}
+static void create_mec_ram(MemoryRegion *tag_sysmem,
+ hwaddr base, hwaddr size,
+ const char *name)
+{
+ memory_region_init_ram(tag_sysmem, NULL, name, size / 32, &error_fatal);
+}
+
+static void create_mec_ram_fake_page(MemoryRegion *mr, uint64_t size, const char *name)
+{
+ assert(is_power_of_2(size) );
+ assert(size >= sizeof(uint32_t));
+
+ uint32_t *ptr = g_malloc(size);
+
+ for (int i = 0; i < size / sizeof(uint32_t); i++) {
+ ptr[i] = 0xDEADBEEF;
+ }
+
+ memory_region_init_ram_ptr(mr, NULL, name, size, ptr);
+}
+
static void create_secure_ram(VirtMachineState *vms,
MemoryRegion *secure_sysmem,
MemoryRegion *secure_tag_sysmem)
@@ -2267,6 +2288,8 @@ static void machvirt_init(MachineState *machine)
MemoryRegion *secure_sysmem = NULL;
MemoryRegion *tag_sysmem = NULL;
MemoryRegion *secure_tag_sysmem = NULL;
+ MemoryRegion *pseudo_encrypted_page = NULL;
+ MemoryRegion *tuple_memory = NULL;
int n, virt_max_cpus;
bool firmware_loaded;
bool aarch64 = true;
@@ -2495,6 +2518,28 @@ static void machvirt_init(MachineState *machine)
}
}
+ if (vms->mec) {
+ if (tcg_enabled()) {
+ if (tuple_memory == NULL) {
+ /* XXX(gromero): Add object_property_find(cpuobj, "tuple-memory", ...) here. */
+
+ tuple_memory = g_new(MemoryRegion, 1);
+ memory_region_init(tuple_memory, OBJECT(machine), "mec", UINT64_MAX / 32);
+
+ pseudo_encrypted_page = g_new(MemoryRegion, 1);
+ memory_region_init(pseudo_encrypted_page, OBJECT(machine), "mec-page", 4 * 1024 /* 4 KiB */);
+ }
+
+ object_property_set_link(cpuobj, "mec", OBJECT(tuple_memory), &error_abort);
+ object_property_set_link(cpuobj, "mec-page", OBJECT(pseudo_encrypted_page), &error_abort);
+
+ } else {
+ /* Check for other accels here. */
+ error_report("MEC requested, but not supported");
+ exit(1);
+ }
+ }
+
qdev_realize(DEVICE(cpuobj), NULL, &error_fatal);
object_unref(cpuobj);
}
@@ -2561,6 +2606,14 @@ static void machvirt_init(MachineState *machine)
machine->ram_size, "mach-virt.tag");
}
+ if (tuple_memory) {
+ create_mec_ram(tuple_memory, vms->memmap[VIRT_MEM].base, machine->ram_size, "tuple-memory");
+ }
+
+ if(pseudo_encrypted_page) {
+ create_mec_ram_fake_page(pseudo_encrypted_page, 4 * 1024, "mec-fake-page");
+ }
+
vms->highmem_ecam &= (!firmware_loaded || aarch64);
create_rtc(vms);
@@ -2966,6 +3019,20 @@ static void virt_set_mte(Object *obj, bool value, Error **errp)
vms->mte = value;
}
+static bool virt_get_mec(Object *obj, Error **errp)
+{
+ VirtMachineState *vms = VIRT_MACHINE(obj);
+
+ return vms->mec;
+}
+
+static void virt_set_mec(Object *obj, bool value, Error **errp)
+{
+ VirtMachineState *vms = VIRT_MACHINE(obj);
+
+ vms->mec = value;
+}
+
static char *virt_get_gic_version(Object *obj, Error **errp)
{
VirtMachineState *vms = VIRT_MACHINE(obj);
@@ -3602,6 +3669,12 @@ static void virt_machine_class_init(ObjectClass *oc, const void *data)
"guest CPU which implements the ARM "
"Memory Tagging Extension");
+ object_class_property_add_bool(oc, "mec", virt_get_mec, virt_set_mec);
+ object_class_property_set_description(oc, "mec",
+ "Set on/off to enable/disable emulating a"
+ "guest CPU which implements the ARM"
+ "Memory Encryption Extention");
+
object_class_property_add_bool(oc, "its", virt_get_its,
virt_set_its);
object_class_property_set_description(oc, "its",
@@ -3686,6 +3759,9 @@ static void virt_instance_init(Object *obj)
/* MTE is disabled by default. */
vms->mte = false;
+ /* FEAT_MEC is disabled by default. */
+ vms->mec = false;
+
/* Supply kaslr-seed and rng-seed by default */
vms->dtb_randomness = true;
diff --git a/include/hw/arm/virt.h b/include/hw/arm/virt.h
index dba8ac7f2f..4e49eb1d13 100644
--- a/include/hw/arm/virt.h
+++ b/include/hw/arm/virt.h
@@ -154,6 +154,7 @@ struct VirtMachineState {
bool virt;
bool ras;
bool mte;
+ bool mec;
bool dtb_randomness;
bool second_ns_uart_present;
OnOffAuto acpi;
--
2.34.1
^ permalink raw reply related [flat|nested] 19+ messages in thread* Re: [RFC PATCH 5/7] hw/arm/virt: Add machine option 'mec'
2026-03-19 2:23 ` [RFC PATCH 5/7] hw/arm/virt: Add machine option 'mec' Gustavo Romero
@ 2026-03-19 9:46 ` Jonathan Cameron via qemu development
2026-03-27 0:10 ` Richard Henderson
1 sibling, 0 replies; 19+ messages in thread
From: Jonathan Cameron via @ 2026-03-19 9:46 UTC (permalink / raw)
To: Gustavo Romero
Cc: qemu-devel, qemu-arm, richard.henderson, alex.bennee,
peter.maydell
On Wed, 18 Mar 2026 23:23:33 -0300
Gustavo Romero <gustavo.romero@linaro.org> wrote:
> Add new machine option 'mec' that enables and sets the memory used by
> FEAT_MEC.
>
> Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
Drive by comments only. I'm curious enough to read the patches
but no idea if this is how people would like to see this implemented!
Jonathan
> static void create_secure_ram(VirtMachineState *vms,
> MemoryRegion *secure_sysmem,
> MemoryRegion *secure_tag_sysmem)
> @@ -2267,6 +2288,8 @@ static void machvirt_init(MachineState *machine)
> MemoryRegion *secure_sysmem = NULL;
> MemoryRegion *tag_sysmem = NULL;
> MemoryRegion *secure_tag_sysmem = NULL;
> + MemoryRegion *pseudo_encrypted_page = NULL;
> + MemoryRegion *tuple_memory = NULL;
> int n, virt_max_cpus;
> bool firmware_loaded;
> bool aarch64 = true;
> @@ -2495,6 +2518,28 @@ static void machvirt_init(MachineState *machine)
> }
> }
>
> + if (vms->mec) {
> + if (tcg_enabled()) {
> + if (tuple_memory == NULL) {
> + /* XXX(gromero): Add object_property_find(cpuobj, "tuple-memory", ...) here. */
> +
> + tuple_memory = g_new(MemoryRegion, 1);
> + memory_region_init(tuple_memory, OBJECT(machine), "mec", UINT64_MAX / 32);
> +
> + pseudo_encrypted_page = g_new(MemoryRegion, 1);
> + memory_region_init(pseudo_encrypted_page, OBJECT(machine), "mec-page", 4 * 1024 /* 4 KiB */);
> + }
> +
> + object_property_set_link(cpuobj, "mec", OBJECT(tuple_memory), &error_abort);
> + object_property_set_link(cpuobj, "mec-page", OBJECT(pseudo_encrypted_page), &error_abort);
Trivial but some bonus spaces.
> +
> + } else {
Indent seems off.
> + /* Check for other accels here. */
> + error_report("MEC requested, but not supported");
> + exit(1);
> + }
> + }
> +
> qdev_realize(DEVICE(cpuobj), NULL, &error_fatal);
> object_unref(cpuobj);
> }
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [RFC PATCH 5/7] hw/arm/virt: Add machine option 'mec'
@ 2026-03-19 9:46 ` Jonathan Cameron via qemu development
0 siblings, 0 replies; 19+ messages in thread
From: Jonathan Cameron via qemu development @ 2026-03-19 9:46 UTC (permalink / raw)
To: Gustavo Romero
Cc: qemu-devel, qemu-arm, richard.henderson, alex.bennee,
peter.maydell
On Wed, 18 Mar 2026 23:23:33 -0300
Gustavo Romero <gustavo.romero@linaro.org> wrote:
> Add new machine option 'mec' that enables and sets the memory used by
> FEAT_MEC.
>
> Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
Drive by comments only. I'm curious enough to read the patches
but no idea if this is how people would like to see this implemented!
Jonathan
> static void create_secure_ram(VirtMachineState *vms,
> MemoryRegion *secure_sysmem,
> MemoryRegion *secure_tag_sysmem)
> @@ -2267,6 +2288,8 @@ static void machvirt_init(MachineState *machine)
> MemoryRegion *secure_sysmem = NULL;
> MemoryRegion *tag_sysmem = NULL;
> MemoryRegion *secure_tag_sysmem = NULL;
> + MemoryRegion *pseudo_encrypted_page = NULL;
> + MemoryRegion *tuple_memory = NULL;
> int n, virt_max_cpus;
> bool firmware_loaded;
> bool aarch64 = true;
> @@ -2495,6 +2518,28 @@ static void machvirt_init(MachineState *machine)
> }
> }
>
> + if (vms->mec) {
> + if (tcg_enabled()) {
> + if (tuple_memory == NULL) {
> + /* XXX(gromero): Add object_property_find(cpuobj, "tuple-memory", ...) here. */
> +
> + tuple_memory = g_new(MemoryRegion, 1);
> + memory_region_init(tuple_memory, OBJECT(machine), "mec", UINT64_MAX / 32);
> +
> + pseudo_encrypted_page = g_new(MemoryRegion, 1);
> + memory_region_init(pseudo_encrypted_page, OBJECT(machine), "mec-page", 4 * 1024 /* 4 KiB */);
> + }
> +
> + object_property_set_link(cpuobj, "mec", OBJECT(tuple_memory), &error_abort);
> + object_property_set_link(cpuobj, "mec-page", OBJECT(pseudo_encrypted_page), &error_abort);
Trivial but some bonus spaces.
> +
> + } else {
Indent seems off.
> + /* Check for other accels here. */
> + error_report("MEC requested, but not supported");
> + exit(1);
> + }
> + }
> +
> qdev_realize(DEVICE(cpuobj), NULL, &error_fatal);
> object_unref(cpuobj);
> }
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [RFC PATCH 5/7] hw/arm/virt: Add machine option 'mec'
2026-03-19 9:46 ` Jonathan Cameron via qemu development
(?)
@ 2026-03-19 17:27 ` Gustavo Romero
2026-03-20 9:31 ` Jonathan Cameron via qemu development
-1 siblings, 1 reply; 19+ messages in thread
From: Gustavo Romero @ 2026-03-19 17:27 UTC (permalink / raw)
To: Jonathan Cameron
Cc: qemu-devel, qemu-arm, richard.henderson, alex.bennee,
peter.maydell
Hi Jonathan,
On 3/19/26 06:46, Jonathan Cameron wrote:
> On Wed, 18 Mar 2026 23:23:33 -0300
> Gustavo Romero <gustavo.romero@linaro.org> wrote:
>
>> Add new machine option 'mec' that enables and sets the memory used by
>> FEAT_MEC.
>>
>> Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
>
> Drive by comments only. I'm curious enough to read the patches
> but no idea if this is how people would like to see this implemented!
Thanks for taking a look at it and for you comments.
In which sense do you mean exactly? Do mind to elaborate a bit more on
it? If it's about the whole implementation, would it be about not
really encrypting data or something else? Please help me to understand it :)
I'm not sure if you're talking about just this patch or the whole
FEAT_MEC design.
Cheers,
Gustavo
> Jonathan
>
>> static void create_secure_ram(VirtMachineState *vms,
>> MemoryRegion *secure_sysmem,
>> MemoryRegion *secure_tag_sysmem)
>> @@ -2267,6 +2288,8 @@ static void machvirt_init(MachineState *machine)
>> MemoryRegion *secure_sysmem = NULL;
>> MemoryRegion *tag_sysmem = NULL;
>> MemoryRegion *secure_tag_sysmem = NULL;
>> + MemoryRegion *pseudo_encrypted_page = NULL;
>> + MemoryRegion *tuple_memory = NULL;
>> int n, virt_max_cpus;
>> bool firmware_loaded;
>> bool aarch64 = true;
>> @@ -2495,6 +2518,28 @@ static void machvirt_init(MachineState *machine)
>> }
>> }
>>
>> + if (vms->mec) {
>> + if (tcg_enabled()) {
>> + if (tuple_memory == NULL) {
>> + /* XXX(gromero): Add object_property_find(cpuobj, "tuple-memory", ...) here. */
>> +
>> + tuple_memory = g_new(MemoryRegion, 1);
>> + memory_region_init(tuple_memory, OBJECT(machine), "mec", UINT64_MAX / 32);
>> +
>> + pseudo_encrypted_page = g_new(MemoryRegion, 1);
>> + memory_region_init(pseudo_encrypted_page, OBJECT(machine), "mec-page", 4 * 1024 /* 4 KiB */);
>> + }
>> +
>> + object_property_set_link(cpuobj, "mec", OBJECT(tuple_memory), &error_abort);
>> + object_property_set_link(cpuobj, "mec-page", OBJECT(pseudo_encrypted_page), &error_abort);
> Trivial but some bonus spaces.
>
>> +
>> + } else {
>
> Indent seems off.
>
>> + /* Check for other accels here. */
>> + error_report("MEC requested, but not supported");
>> + exit(1);
>> + }
>> + }
>> +
>> qdev_realize(DEVICE(cpuobj), NULL, &error_fatal);
>> object_unref(cpuobj);
>> }
>
>
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [RFC PATCH 5/7] hw/arm/virt: Add machine option 'mec'
2026-03-19 17:27 ` Gustavo Romero
@ 2026-03-20 9:31 ` Jonathan Cameron via qemu development
0 siblings, 0 replies; 19+ messages in thread
From: Jonathan Cameron via @ 2026-03-20 9:31 UTC (permalink / raw)
To: Gustavo Romero
Cc: qemu-devel, qemu-arm, richard.henderson, alex.bennee,
peter.maydell
On Thu, 19 Mar 2026 14:27:57 -0300
Gustavo Romero <gustavo.romero@linaro.org> wrote:
> Hi Jonathan,
>
> On 3/19/26 06:46, Jonathan Cameron wrote:
> > On Wed, 18 Mar 2026 23:23:33 -0300
> > Gustavo Romero <gustavo.romero@linaro.org> wrote:
> >
> >> Add new machine option 'mec' that enables and sets the memory used by
> >> FEAT_MEC.
> >>
> >> Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
> >
> > Drive by comments only. I'm curious enough to read the patches
> > but no idea if this is how people would like to see this implemented!
>
> Thanks for taking a look at it and for you comments.
>
> In which sense do you mean exactly? Do mind to elaborate a bit more on
> it? If it's about the whole implementation, would it be about not
> really encrypting data or something else? Please help me to understand it :)
>
> I'm not sure if you're talking about just this patch or the whole
> FEAT_MEC design.
I was failing to express that this is fine for me but out of my area of expertise
wrt to QEMU so would leave the questions of 'is this the best way to do it?'
for others!
>
>
> Cheers,
> Gustavo
>
> > Jonathan
> >
> >> static void create_secure_ram(VirtMachineState *vms,
> >> MemoryRegion *secure_sysmem,
> >> MemoryRegion *secure_tag_sysmem)
> >> @@ -2267,6 +2288,8 @@ static void machvirt_init(MachineState *machine)
> >> MemoryRegion *secure_sysmem = NULL;
> >> MemoryRegion *tag_sysmem = NULL;
> >> MemoryRegion *secure_tag_sysmem = NULL;
> >> + MemoryRegion *pseudo_encrypted_page = NULL;
> >> + MemoryRegion *tuple_memory = NULL;
> >> int n, virt_max_cpus;
> >> bool firmware_loaded;
> >> bool aarch64 = true;
> >> @@ -2495,6 +2518,28 @@ static void machvirt_init(MachineState *machine)
> >> }
> >> }
> >>
> >> + if (vms->mec) {
> >> + if (tcg_enabled()) {
> >> + if (tuple_memory == NULL) {
> >> + /* XXX(gromero): Add object_property_find(cpuobj, "tuple-memory", ...) here. */
> >> +
> >> + tuple_memory = g_new(MemoryRegion, 1);
> >> + memory_region_init(tuple_memory, OBJECT(machine), "mec", UINT64_MAX / 32);
> >> +
> >> + pseudo_encrypted_page = g_new(MemoryRegion, 1);
> >> + memory_region_init(pseudo_encrypted_page, OBJECT(machine), "mec-page", 4 * 1024 /* 4 KiB */);
> >> + }
> >> +
> >> + object_property_set_link(cpuobj, "mec", OBJECT(tuple_memory), &error_abort);
> >> + object_property_set_link(cpuobj, "mec-page", OBJECT(pseudo_encrypted_page), &error_abort);
> > Trivial but some bonus spaces.
> >
> >> +
> >> + } else {
> >
> > Indent seems off.
> >
> >> + /* Check for other accels here. */
> >> + error_report("MEC requested, but not supported");
> >> + exit(1);
> >> + }
> >> + }
> >> +
> >> qdev_realize(DEVICE(cpuobj), NULL, &error_fatal);
> >> object_unref(cpuobj);
> >> }
> >
> >
>
>
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [RFC PATCH 5/7] hw/arm/virt: Add machine option 'mec'
@ 2026-03-20 9:31 ` Jonathan Cameron via qemu development
0 siblings, 0 replies; 19+ messages in thread
From: Jonathan Cameron via qemu development @ 2026-03-20 9:31 UTC (permalink / raw)
To: Gustavo Romero
Cc: qemu-devel, qemu-arm, richard.henderson, alex.bennee,
peter.maydell
On Thu, 19 Mar 2026 14:27:57 -0300
Gustavo Romero <gustavo.romero@linaro.org> wrote:
> Hi Jonathan,
>
> On 3/19/26 06:46, Jonathan Cameron wrote:
> > On Wed, 18 Mar 2026 23:23:33 -0300
> > Gustavo Romero <gustavo.romero@linaro.org> wrote:
> >
> >> Add new machine option 'mec' that enables and sets the memory used by
> >> FEAT_MEC.
> >>
> >> Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
> >
> > Drive by comments only. I'm curious enough to read the patches
> > but no idea if this is how people would like to see this implemented!
>
> Thanks for taking a look at it and for you comments.
>
> In which sense do you mean exactly? Do mind to elaborate a bit more on
> it? If it's about the whole implementation, would it be about not
> really encrypting data or something else? Please help me to understand it :)
>
> I'm not sure if you're talking about just this patch or the whole
> FEAT_MEC design.
I was failing to express that this is fine for me but out of my area of expertise
wrt to QEMU so would leave the questions of 'is this the best way to do it?'
for others!
>
>
> Cheers,
> Gustavo
>
> > Jonathan
> >
> >> static void create_secure_ram(VirtMachineState *vms,
> >> MemoryRegion *secure_sysmem,
> >> MemoryRegion *secure_tag_sysmem)
> >> @@ -2267,6 +2288,8 @@ static void machvirt_init(MachineState *machine)
> >> MemoryRegion *secure_sysmem = NULL;
> >> MemoryRegion *tag_sysmem = NULL;
> >> MemoryRegion *secure_tag_sysmem = NULL;
> >> + MemoryRegion *pseudo_encrypted_page = NULL;
> >> + MemoryRegion *tuple_memory = NULL;
> >> int n, virt_max_cpus;
> >> bool firmware_loaded;
> >> bool aarch64 = true;
> >> @@ -2495,6 +2518,28 @@ static void machvirt_init(MachineState *machine)
> >> }
> >> }
> >>
> >> + if (vms->mec) {
> >> + if (tcg_enabled()) {
> >> + if (tuple_memory == NULL) {
> >> + /* XXX(gromero): Add object_property_find(cpuobj, "tuple-memory", ...) here. */
> >> +
> >> + tuple_memory = g_new(MemoryRegion, 1);
> >> + memory_region_init(tuple_memory, OBJECT(machine), "mec", UINT64_MAX / 32);
> >> +
> >> + pseudo_encrypted_page = g_new(MemoryRegion, 1);
> >> + memory_region_init(pseudo_encrypted_page, OBJECT(machine), "mec-page", 4 * 1024 /* 4 KiB */);
> >> + }
> >> +
> >> + object_property_set_link(cpuobj, "mec", OBJECT(tuple_memory), &error_abort);
> >> + object_property_set_link(cpuobj, "mec-page", OBJECT(pseudo_encrypted_page), &error_abort);
> > Trivial but some bonus spaces.
> >
> >> +
> >> + } else {
> >
> > Indent seems off.
> >
> >> + /* Check for other accels here. */
> >> + error_report("MEC requested, but not supported");
> >> + exit(1);
> >> + }
> >> + }
> >> +
> >> qdev_realize(DEVICE(cpuobj), NULL, &error_fatal);
> >> object_unref(cpuobj);
> >> }
> >
> >
>
>
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [RFC PATCH 5/7] hw/arm/virt: Add machine option 'mec'
2026-03-19 2:23 ` [RFC PATCH 5/7] hw/arm/virt: Add machine option 'mec' Gustavo Romero
2026-03-19 9:46 ` Jonathan Cameron via qemu development
@ 2026-03-27 0:10 ` Richard Henderson
1 sibling, 0 replies; 19+ messages in thread
From: Richard Henderson @ 2026-03-27 0:10 UTC (permalink / raw)
To: Gustavo Romero, qemu-devel, qemu-arm; +Cc: alex.bennee, peter.maydell
On 3/19/26 12:23, Gustavo Romero wrote:
> Add new machine option 'mec' that enables and sets the memory used by
> FEAT_MEC.
>
> Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
> ---
> hw/arm/virt.c | 76 +++++++++++++++++++++++++++++++++++++++++++
> include/hw/arm/virt.h | 1 +
> 2 files changed, 77 insertions(+)
>
> diff --git a/hw/arm/virt.c b/hw/arm/virt.c
> index cab2e21e8a..7d127a4205 100644
> --- a/hw/arm/virt.c
> +++ b/hw/arm/virt.c
> @@ -1762,6 +1762,27 @@ static void create_tag_ram(MemoryRegion *tag_sysmem,
> memory_region_add_subregion(tag_sysmem, base / 32, tagram);
> }
>
> +static void create_mec_ram(MemoryRegion *tag_sysmem,
> + hwaddr base, hwaddr size,
> + const char *name)
> +{
> + memory_region_init_ram(tag_sysmem, NULL, name, size / 32, &error_fatal);
> +}
The "32" divisor in create_tag_ram is "2 * TAG_GRANULE", but due to placement that
constant isn't actually visible in virt.c. That could certainly be fixed.
Here, you want "size / TARGET_PAGE_SIZE * sizeof(mec data)", so that you store 1 "mec
data" per page. (IIRC the mecid is 8 bits?)
> @@ -2495,6 +2518,28 @@ static void machvirt_init(MachineState *machine)
> }
> }
>
> + if (vms->mec) {
> + if (tcg_enabled()) {
> + if (tuple_memory == NULL) {
> + /* XXX(gromero): Add object_property_find(cpuobj, "tuple-memory", ...) here. */
> +
> + tuple_memory = g_new(MemoryRegion, 1);
> + memory_region_init(tuple_memory, OBJECT(machine), "mec", UINT64_MAX / 32);
What is UINT64_MAX / 32?
Tuple memory should be tied to ram, so this should be the size of ram.
> + "Memory Encryption Extention");
Extension.
r~
^ permalink raw reply [flat|nested] 19+ messages in thread
* [RFC PATCH 6/7] target/arm/ptw: Add MECID checks
2026-03-19 2:23 [RFC PATCH 0/7] Add FEAT_MEC (encryption) Gustavo Romero
` (4 preceding siblings ...)
2026-03-19 2:23 ` [RFC PATCH 5/7] hw/arm/virt: Add machine option 'mec' Gustavo Romero
@ 2026-03-19 2:23 ` Gustavo Romero
2026-03-27 1:28 ` Richard Henderson
2026-03-19 2:23 ` [RFC PATCH 7/7] target/arm/ptw: Print MECID if not zero [TO BE DISCARDED] Gustavo Romero
6 siblings, 1 reply; 19+ messages in thread
From: Gustavo Romero @ 2026-03-19 2:23 UTC (permalink / raw)
To: qemu-devel, qemu-arm, richard.henderson
Cc: alex.bennee, peter.maydell, gustavo.romero
Add MECID checks. The MECID registers were introduced before and this
commit uses them to perform the proper MECID checks, which is part of
FEAT_MEC implementation.
On MECID mismatches (wrong MECID) we return a substitute encrypted page
instead of the real encrypted page, which doesn't hurt performance on
memory accesses and behaves like FEAT_MEC design on the physical
hardware.
Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
---
target/arm/internals.h | 2 +
target/arm/ptw.c | 215 ++++++++++++++++++++++++++++++++++++++++-
2 files changed, 216 insertions(+), 1 deletion(-)
diff --git a/target/arm/internals.h b/target/arm/internals.h
index 8ec2750847..d668948949 100644
--- a/target/arm/internals.h
+++ b/target/arm/internals.h
@@ -1473,6 +1473,8 @@ typedef struct GetPhysAddrResult {
* with the stage1 protection.
*/
int s2prot;
+ /* FEAT_MEC AMEC found in Block or Page descriptor. */
+ bool amec;
} GetPhysAddrResult;
/**
diff --git a/target/arm/ptw.c b/target/arm/ptw.c
index 8b8dc09e72..aacc32ba33 100644
--- a/target/arm/ptw.c
+++ b/target/arm/ptw.c
@@ -2090,6 +2090,17 @@ static bool get_phys_addr_lpae(CPUARMState *env, S1Translate *ptw,
goto do_fault;
}
+ /*
+ * XXX(gromero): Add checks for NS bit and AMEC0/1 bits, generating the due
+ * fault in case AMEC0/1 = 0 and AMEC bit is 1. So far we just get the AMEC
+ * bit directly in case of a Block or Page descriptor.
+ *
+ */
+ if (((level == 1 || level == 2) && (extract64(descriptor, 0, 2) == 1)) ||
+ level == 3) {
+ result->amec = extract64(descriptor, 63, 1);
+ }
+
if ((descriptor & 2) && (level < 3)) {
/*
* Table entry. The top five bits are attributes which may
@@ -3766,6 +3777,208 @@ static bool get_phys_addr_nogpc(CPUARMState *env, S1Translate *ptw,
}
}
+static uint32_t *get_mecid_ptr(CPUARMState *env, hwaddr pa)
+{
+ MemoryRegion *mr;
+ AddressSpace *mec_as;
+ hwaddr mec_paddr, xlat;
+ MemTxAttrs memattrs = { 0x0 };
+
+ /* Find out the page number to use it as an offset in mec AS. */
+ mec_paddr = pa >> TARGET_PAGE_BITS;
+ mec_paddr *= 4; /* Assume MECID maximum size is 4 bytes. */
+ /* MECIDs are kept in their own Address Space. */
+ mec_as = cpu_get_address_space(env_cpu(env), ARMASIdx_MEC);
+ mr = address_space_translate(mec_as, mec_paddr, &xlat, NULL, true, memattrs);
+
+ /*
+ * Return pointer in the mec AS associated to physical address 'pa', which
+ * is used to store the MECID.
+ */
+ return memory_region_get_ram_ptr(mr) + xlat;
+}
+
+static uint32_t get_mecid(CPUARMState *env, hwaddr pa)
+{
+ return *get_mecid_ptr(env, pa);
+}
+
+static void set_mecid(CPUARMState *env, hwaddr pa, uint32_t mecid)
+{
+ *get_mecid_ptr(env, pa) = mecid;
+}
+
+/*
+ * Returns 'false' on MECID mismatch and 'true' on MECID match (success).
+ */
+static bool mecid_check(CPUARMState *env, S1Translate *ptw, hwaddr va,
+ MMUAccessType access_type, GetPhysAddrResult *result,
+ ARMMMUIdx s1_mmu_idx)
+{
+ ARMSecuritySpace ss = ptw->out_space;
+ /* Final physical address after translation. */
+ hwaddr pa = result->f.phys_addr;
+
+ switch (s1_mmu_idx) {
+ case ARMMMUIdx_Phys_S:
+ case ARMMMUIdx_Phys_NS:
+ case ARMMMUIdx_Phys_Root:
+ case ARMMMUIdx_Phys_Realm:
+ case ARMMMUIdx_Stage2:
+ case ARMMMUIdx_Stage2_S:
+ /* In the middle of a translation, so result->f.phys_addr has no PA yet. */
+ return true;
+ break;
+ default:
+ /* Pass */
+ break;
+ }
+
+ /* Find out which EL controls EMEC for Stage 1 translations. */
+ uint32_t el = regime_el(s1_mmu_idx) < 3 ? 2 : 3;
+
+ /* XXX(gromero): Do we need to check for SCR_EL3.SCTLR2En when el == 2? */
+ if (!(cpu_isar_feature(aa64_mec, env_archcpu(env)) &&
+ (env->cp15.sctlr2_el[el] & SCTLR2_EMEC))) {
+ /* FEAT_MEC is disabled. */
+ return true;
+ }
+
+ if (ss != ARMSS_Realm) {
+ /*
+ * GPT checks are already done, so if SS is Root here MECIDs are
+ * irrelevants for EL3 accesses. If SS is Secure or NonSecure that's not
+ * pertinent to FEAT_MEC. Hence, only proceed with MECID checks if SS is
+ * Realm.
+ */
+ return true;
+ }
+
+ bool amec = result->amec;
+ bool varange_lower = extract64(va, 55, 1) ? false : true;
+ /* MECID in register set given a translation regime. */
+ uint32_t mecid;
+
+ /* ARMMMUIdx after full ptw (all stages). */
+ ARMMMUIdx ptw_mmu_idx = ptw->in_mmu_idx;
+
+ switch (ptw_mmu_idx) {
+ case ARMMMUIdx_Stage2:
+ case ARMMMUIdx_Stage2_S:
+ /* In the middle of a two stage translation, so result->f.phys_addr has no PA yet. */
+ return true;
+ break;
+ default:
+ /* Pass */
+ break;
+ }
+
+ /*
+ * NB: No MECID is used for the ptw itself, i.e., used for the level
+ * lookups, so AArch64.S1TTWalkMECID() and AArch64.S2TTWalkMECID() are not
+ * relevant for our FEAT_MEC implementation.
+ */
+ bool is_pa_from_s2 = regime_is_stage2(ptw_mmu_idx);
+ bool is_mmu_disabled = regime_translation_disabled(env, ptw_mmu_idx, ss);
+ if (is_pa_from_s2) { /* PA from Stage 2. */
+ /* As per AArch64.S2OutputMECID(). */
+ mecid = amec ? env->cp15.vmecid_a_el2 : env->cp15.vmecid_p_el2;
+
+ } else { /* PA from Stage 1. */
+ if (is_mmu_disabled) { /* PA from Stage 1 and MMU is disabled. */
+ /* As per AArch64.S1DisabledOutputMECID(). */
+ switch (s1_mmu_idx) {
+ case ARMMMUIdx_E3:
+ /* MECID = 0 (default), so return without any check. */
+ return true;
+ break;
+ case ARMMMUIdx_E2:
+ case ARMMMUIdx_E20_0:
+ case ARMMMUIdx_E20_2:
+ case ARMMMUIdx_E20_2_PAN:
+ mecid = env->cp15.mecid_p0_el2;
+ break;
+ case ARMMMUIdx_E10_0:
+ case ARMMMUIdx_E10_1:
+ mecid = env->cp15.vmecid_p_el2;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ } else { /* PA from Stage 1 and MMU is enabled. */
+ /* As per AArch64.S1OutputMECID(). */
+ switch (s1_mmu_idx) {
+ case ARMMMUIdx_E3:
+ mecid = env->cp15.mecid_rl_a_el3;
+ break;
+ case ARMMMUIdx_E2:
+ mecid = amec ? env->cp15.mecid_a0_el2 : env->cp15.mecid_p0_el2;
+ break;
+ case ARMMMUIdx_E20_0:
+ case ARMMMUIdx_E20_2:
+ case ARMMMUIdx_E20_2_PAN:
+ if (varange_lower) {
+ mecid = amec ? env->cp15.mecid_a0_el2 : env->cp15.mecid_p0_el2;
+ } else {
+ mecid = amec ? env->cp15.mecid_a1_el2 : env->cp15.mecid_p1_el2;
+ }
+ break;
+ case ARMMMUIdx_E10_0:
+ case ARMMMUIdx_E10_1:
+ mecid = env->cp15.vmecid_p_el2;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ }
+ }
+
+ /*
+ * MECID is updated on data store and checked on other access types.
+ */
+ if (access_type == MMU_DATA_STORE) {
+ /* Store MECID for physical address 'pa'. */
+ set_mecid(env, pa, mecid);
+ return true;
+ } else {
+ uint32_t stored_mecid;
+ /* Load the MECID stored in memory for physical address 'pa'. */
+ stored_mecid = get_mecid(env, pa);
+ if (stored_mecid == mecid) {
+ /* MECID is correct. */
+ return true;
+ } else {
+ /* MECID is incorrect, so return the substitute encrypted page. */
+ result->f.phys_addr = 0x0; /* Start of the page. */
+ result->f.attrs.encrypted = true; /* Substitute encrypted page. */
+ return false;
+ }
+ }
+}
+
+static bool get_phys_addr_mec(CPUARMState *env, S1Translate *ptw,
+ vaddr address,
+ MMUAccessType access_type, MemOp memop,
+ GetPhysAddrResult *result,
+ ARMMMUFaultInfo *fi)
+
+{
+ /*
+ * After 'address' is resolved by get_phys_addr_nogpc(), ptw->in_mmu_idx can
+ * change depending on the translation stages, hence stash it.
+ */
+ ARMMMUIdx s1_mmu_idx = ptw->in_mmu_idx;
+
+ if (get_phys_addr_gpc(env, ptw, address, access_type, memop, result, fi)) {
+ return true; /* Translation fault. */
+ }
+ if (!mecid_check(env, ptw, address, access_type, result, s1_mmu_idx)) {
+ return true; /* MECID mismatch. */
+ }
+ return false;
+}
+
static bool get_phys_addr_gpc(CPUARMState *env, S1Translate *ptw,
vaddr address,
MMUAccessType access_type, MemOp memop,
@@ -3907,7 +4120,7 @@ bool get_phys_addr(CPUARMState *env, vaddr address,
.in_prot_check = 1 << access_type,
};
- return get_phys_addr_gpc(env, &ptw, address, access_type,
+ return get_phys_addr_mec(env, &ptw, address, access_type,
memop, result, fi);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 19+ messages in thread* Re: [RFC PATCH 6/7] target/arm/ptw: Add MECID checks
2026-03-19 2:23 ` [RFC PATCH 6/7] target/arm/ptw: Add MECID checks Gustavo Romero
@ 2026-03-27 1:28 ` Richard Henderson
0 siblings, 0 replies; 19+ messages in thread
From: Richard Henderson @ 2026-03-27 1:28 UTC (permalink / raw)
To: Gustavo Romero, qemu-devel, qemu-arm; +Cc: alex.bennee, peter.maydell
On 3/19/26 12:23, Gustavo Romero wrote:
> Add MECID checks. The MECID registers were introduced before and this
> commit uses them to perform the proper MECID checks, which is part of
> FEAT_MEC implementation.
>
> On MECID mismatches (wrong MECID) we return a substitute encrypted page
> instead of the real encrypted page, which doesn't hurt performance on
> memory accesses and behaves like FEAT_MEC design on the physical
> hardware.
>
> Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
> ---
> target/arm/internals.h | 2 +
> target/arm/ptw.c | 215 ++++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 216 insertions(+), 1 deletion(-)
>
> diff --git a/target/arm/internals.h b/target/arm/internals.h
> index 8ec2750847..d668948949 100644
> --- a/target/arm/internals.h
> +++ b/target/arm/internals.h
> @@ -1473,6 +1473,8 @@ typedef struct GetPhysAddrResult {
> * with the stage1 protection.
> */
> int s2prot;
> + /* FEAT_MEC AMEC found in Block or Page descriptor. */
> + bool amec;
> } GetPhysAddrResult;
>
> /**
> diff --git a/target/arm/ptw.c b/target/arm/ptw.c
> index 8b8dc09e72..aacc32ba33 100644
> --- a/target/arm/ptw.c
> +++ b/target/arm/ptw.c
> @@ -2090,6 +2090,17 @@ static bool get_phys_addr_lpae(CPUARMState *env, S1Translate *ptw,
> goto do_fault;
> }
>
> + /*
> + * XXX(gromero): Add checks for NS bit and AMEC0/1 bits, generating the due
> + * fault in case AMEC0/1 = 0 and AMEC bit is 1. So far we just get the AMEC
> + * bit directly in case of a Block or Page descriptor.
> + *
> + */
> + if (((level == 1 || level == 2) && (extract64(descriptor, 0, 2) == 1)) ||
> + level == 3) {
This test is exactly ...
> + result->amec = extract64(descriptor, 63, 1);
> + }
> +
> if ((descriptor & 2) && (level < 3)) {
... the inverse of this one. Notice the comment that directly follows:
/*
* Block entry at level 1 or 2, or page entry at level 3.
* These are basically the same thing, although the number
So move the assignment of amec later, with no additional test.
> /*
> * Table entry. The top five bits are attributes which may
> @@ -3766,6 +3777,208 @@ static bool get_phys_addr_nogpc(CPUARMState *env, S1Translate *ptw,
> }
> }
>
> +static uint32_t *get_mecid_ptr(CPUARMState *env, hwaddr pa)
> +{
> + MemoryRegion *mr;
> + AddressSpace *mec_as;
> + hwaddr mec_paddr, xlat;
> + MemTxAttrs memattrs = { 0x0 };
> +
> + /* Find out the page number to use it as an offset in mec AS. */
> + mec_paddr = pa >> TARGET_PAGE_BITS;
> + mec_paddr *= 4; /* Assume MECID maximum size is 4 bytes. */
> + /* MECIDs are kept in their own Address Space. */
> + mec_as = cpu_get_address_space(env_cpu(env), ARMASIdx_MEC);
> + mr = address_space_translate(mec_as, mec_paddr, &xlat, NULL, true, memattrs);
> +
> + /*
> + * Return pointer in the mec AS associated to physical address 'pa', which
> + * is used to store the MECID.
> + */
> + return memory_region_get_ram_ptr(mr) + xlat;
> +}
In allocation_tag_mem_probe, where we're performing a similar function, I test
memory_region_is_ram and log an error. In addition, we have to manually mark these ram
pages dirty for migration.
Alternately...
> +
> +static uint32_t get_mecid(CPUARMState *env, hwaddr pa)
> +{
> + return *get_mecid_ptr(env, pa);
> +}
> +
> +static void set_mecid(CPUARMState *env, hwaddr pa, uint32_t mecid)
> +{
> + *get_mecid_ptr(env, pa) = mecid;
> +}
... drop the direct use of pointers and use address_space_{ldl,stl} instead.
For MTE, there was the additional complication of user-only, which doesn't have these
separate address spaces, as well as dealing with larger blocks of tag memory.
For MEC, you're only ever touching one entry at a time.
> +
> +/*
> + * Returns 'false' on MECID mismatch and 'true' on MECID match (success).
> + */
> +static bool mecid_check(CPUARMState *env, S1Translate *ptw, hwaddr va,
> + MMUAccessType access_type, GetPhysAddrResult *result,
> + ARMMMUIdx s1_mmu_idx)
> +{
> + ARMSecuritySpace ss = ptw->out_space;
> + /* Final physical address after translation. */
> + hwaddr pa = result->f.phys_addr;
> +
> + switch (s1_mmu_idx) {
> + case ARMMMUIdx_Phys_S:
> + case ARMMMUIdx_Phys_NS:
> + case ARMMMUIdx_Phys_Root:
> + case ARMMMUIdx_Phys_Realm:
> + case ARMMMUIdx_Stage2:
> + case ARMMMUIdx_Stage2_S:
> + /* In the middle of a translation, so result->f.phys_addr has no PA yet. */
> + return true;
> + break;
> + default:
> + /* Pass */
> + break;
> + }
Why do you need to test s1_mmu_idx?
Surely the ptw->in_mmu_idx test below is better?
> +
> + /* Find out which EL controls EMEC for Stage 1 translations. */
> + uint32_t el = regime_el(s1_mmu_idx) < 3 ? 2 : 3;
> +
> + /* XXX(gromero): Do we need to check for SCR_EL3.SCTLR2En when el == 2? */
> + if (!(cpu_isar_feature(aa64_mec, env_archcpu(env)) &&
> + (env->cp15.sctlr2_el[el] & SCTLR2_EMEC))) {
> + /* FEAT_MEC is disabled. */
> + return true;
> + }
> +
> + if (ss != ARMSS_Realm) {
> + /*
> + * GPT checks are already done, so if SS is Root here MECIDs are
> + * irrelevants for EL3 accesses. If SS is Secure or NonSecure that's not
> + * pertinent to FEAT_MEC. Hence, only proceed with MECID checks if SS is
> + * Realm.
> + */
> + return true;
> + }
Sort these tests in order of likelyhood and simplicity.
For instance, ss == Realm is least likely and is a trivial test.
In addition, ss == Realm implies ARM_FEATURE_AARCH64, which you have forgotten to test
before testing isar feature aa64_mec.
> +
> + bool amec = result->amec;
> + bool varange_lower = extract64(va, 55, 1) ? false : true;
Use "!" not "?:".
But extracting varange_upper instead would simplify merging E2 with E2&0 regimes, like so:
varange_upper = regime_has_2_ranges(idx) && extract64(va, 55, 1)
> + /* MECID in register set given a translation regime. */
> + uint32_t mecid;
> +
> + /* ARMMMUIdx after full ptw (all stages). */
> + ARMMMUIdx ptw_mmu_idx = ptw->in_mmu_idx;
> +
> + switch (ptw_mmu_idx) {
> + case ARMMMUIdx_Stage2:
> + case ARMMMUIdx_Stage2_S:
> + /* In the middle of a two stage translation, so result->f.phys_addr has no PA yet. */
> + return true;
> + break;
> + default:
> + /* Pass */
> + break;
> + }
> +
> + /*
> + * NB: No MECID is used for the ptw itself, i.e., used for the level
> + * lookups, so AArch64.S1TTWalkMECID() and AArch64.S2TTWalkMECID() are not
> + * relevant for our FEAT_MEC implementation.
> + */
> + bool is_pa_from_s2 = regime_is_stage2(ptw_mmu_idx);
You just rejected the stage2 mmu indexes.
> + bool is_mmu_disabled = regime_translation_disabled(env, ptw_mmu_idx, ss);
> + if (is_pa_from_s2) { /* PA from Stage 2. */
> + /* As per AArch64.S2OutputMECID(). */
> + mecid = amec ? env->cp15.vmecid_a_el2 : env->cp15.vmecid_p_el2;
So this isn't reachable.
This suggests the switch above is wrong?
> +
> + } else { /* PA from Stage 1. */
> + if (is_mmu_disabled) { /* PA from Stage 1 and MMU is disabled. */
> + /* As per AArch64.S1DisabledOutputMECID(). */
> + switch (s1_mmu_idx) {
> + case ARMMMUIdx_E3:
> + /* MECID = 0 (default), so return without any check. */
> + return true;
> + break;
Unreachable break.
> + case ARMMMUIdx_E2:
> + case ARMMMUIdx_E20_0:
> + case ARMMMUIdx_E20_2:
> + case ARMMMUIdx_E20_2_PAN:
> + mecid = env->cp15.mecid_p0_el2;
> + break;
> + case ARMMMUIdx_E10_0:
> + case ARMMMUIdx_E10_1:
> + mecid = env->cp15.vmecid_p_el2;
> + break;
There are lots of these you've missed.
Better to test regime_el instead.
> + default:
> + g_assert_not_reached();
> + }
> +
> + } else { /* PA from Stage 1 and MMU is enabled. */
> + /* As per AArch64.S1OutputMECID(). */
> + switch (s1_mmu_idx) {
> + case ARMMMUIdx_E3:
> + mecid = env->cp15.mecid_rl_a_el3;
> + break;
> + case ARMMMUIdx_E2:
> + mecid = amec ? env->cp15.mecid_a0_el2 : env->cp15.mecid_p0_el2;
> + break;
> + case ARMMMUIdx_E20_0:
> + case ARMMMUIdx_E20_2:
> + case ARMMMUIdx_E20_2_PAN:
> + if (varange_lower) {
> + mecid = amec ? env->cp15.mecid_a0_el2 : env->cp15.mecid_p0_el2;
> + } else {
> + mecid = amec ? env->cp15.mecid_a1_el2 : env->cp15.mecid_p1_el2;
> + }
> + break;
E2 and E20_* can be merged with varange_upper, per above.
> +static bool get_phys_addr_mec(CPUARMState *env, S1Translate *ptw,
> + vaddr address,
> + MMUAccessType access_type, MemOp memop,
> + GetPhysAddrResult *result,
> + ARMMMUFaultInfo *fi)
> +
> +{
> + /*
> + * After 'address' is resolved by get_phys_addr_nogpc(), ptw->in_mmu_idx can
> + * change depending on the translation stages, hence stash it.
> + */
> + ARMMMUIdx s1_mmu_idx = ptw->in_mmu_idx;
> +
> + if (get_phys_addr_gpc(env, ptw, address, access_type, memop, result, fi)) {
> + return true; /* Translation fault. */
> + }
> + if (!mecid_check(env, ptw, address, access_type, result, s1_mmu_idx)) {
> + return true; /* MECID mismatch. */
> + }
MECID mismatch does *not* raise an exception, therefore returning true is incorrect.
Thus the return value from mecid_check is really unused.
r~
^ permalink raw reply [flat|nested] 19+ messages in thread
* [RFC PATCH 7/7] target/arm/ptw: Print MECID if not zero [TO BE DISCARDED]
2026-03-19 2:23 [RFC PATCH 0/7] Add FEAT_MEC (encryption) Gustavo Romero
` (5 preceding siblings ...)
2026-03-19 2:23 ` [RFC PATCH 6/7] target/arm/ptw: Add MECID checks Gustavo Romero
@ 2026-03-19 2:23 ` Gustavo Romero
6 siblings, 0 replies; 19+ messages in thread
From: Gustavo Romero @ 2026-03-19 2:23 UTC (permalink / raw)
To: qemu-devel, qemu-arm, richard.henderson
Cc: alex.bennee, peter.maydell, gustavo.romero
It's easy to get the sw stack wrongly configured and end up having no
MECIDs being setup different from zero. If this happens FEAT_MEC isn't
stressed because a MECID 0 won't cause mismatches. This commit is just
a printf to allow double checking that MECIDs being provided by TF-RMM
are not zero.
This commit is just for debugging purposes and will be discarded.
Please uncomment the line to make it effective.
Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
---
target/arm/ptw.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/target/arm/ptw.c b/target/arm/ptw.c
index aacc32ba33..bf58ac656f 100644
--- a/target/arm/ptw.c
+++ b/target/arm/ptw.c
@@ -3939,6 +3939,9 @@ static bool mecid_check(CPUARMState *env, S1Translate *ptw, hwaddr va,
*/
if (access_type == MMU_DATA_STORE) {
/* Store MECID for physical address 'pa'. */
+ if (mecid != 0) {
+ // printf("MECID (in register) stored for va = %lx pa= %lx is %d | s1_mmu_idx = %d and ptw->in_mmu_idx = %d\n", va, pa, mecid, s1_mmu_idx, ptw_mmu_idx);
+ }
set_mecid(env, pa, mecid);
return true;
} else {
--
2.34.1
^ permalink raw reply related [flat|nested] 19+ messages in thread