docs/system/arm/emulation.rst | 1 + target/arm/cpu-features.h | 12 ++++++++++++ target/arm/helper.c | 16 +++++++++++++--- 3 files changed, 26 insertions(+), 3 deletions(-)
FEAT_E2H0 is a formalisation of the existing behaviour of HCR_EL2.E2H
being programmable to switch between EL2 host mode and the
"traditional" nVHE EL2 mode. This implies at some point we might want
to model CPUs without FEAT_E2H0 which will always have EL2 host mode
enabled.
There are two values to represent no E2H0 systems of which 0b1110 will
make HCR_EL2.NV1 RES0 for FEAT_NV systems. For FEAT_NV2 the NV1 bit is
always valid.
Message-ID: <20260130181648.628364-1-alex.bennee@linaro.org>
Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
---
v2
- new helper and properly handling NV1
---
docs/system/arm/emulation.rst | 1 +
target/arm/cpu-features.h | 12 ++++++++++++
target/arm/helper.c | 16 +++++++++++++---
3 files changed, 26 insertions(+), 3 deletions(-)
diff --git a/docs/system/arm/emulation.rst b/docs/system/arm/emulation.rst
index e0d5f9886e1..7787691853e 100644
--- a/docs/system/arm/emulation.rst
+++ b/docs/system/arm/emulation.rst
@@ -54,6 +54,7 @@ the following architecture extensions:
- FEAT_DotProd (Advanced SIMD dot product instructions)
- FEAT_DoubleFault (Double Fault Extension)
- FEAT_E0PD (Preventing EL0 access to halves of address maps)
+- FEAT_E2H0 (Programming of HCR_EL2.E2H)
- FEAT_EBF16 (AArch64 Extended BFloat16 instructions)
- FEAT_ECV (Enhanced Counter Virtualization)
- FEAT_EL0 (Support for execution at EL0)
diff --git a/target/arm/cpu-features.h b/target/arm/cpu-features.h
index e0b7a45b7bd..78ff761ae06 100644
--- a/target/arm/cpu-features.h
+++ b/target/arm/cpu-features.h
@@ -347,6 +347,7 @@ FIELD(ID_AA64MMFR3, ADERR, 56, 4)
FIELD(ID_AA64MMFR3, SPEC_FPACC, 60, 4)
FIELD(ID_AA64MMFR4, ASID2, 8, 4)
+FIELD(ID_AA64MMFR4, E2H0, 24, 4)
FIELD(ID_AA64DFR0, DEBUGVER, 0, 4)
FIELD(ID_AA64DFR0, TRACEVER, 4, 4)
@@ -1378,6 +1379,17 @@ static inline bool isar_feature_aa64_asid2(const ARMISARegisters *id)
return FIELD_EX64_IDREG(id, ID_AA64MMFR4, ASID2) != 0;
}
+static inline bool isar_feature_aa64_e2h0(const ARMISARegisters *id)
+{
+ return FIELD_EX64_IDREG(id, ID_AA64MMFR4, E2H0) == 0;
+}
+
+static inline bool isar_feature_aa64_noe2h0_and_nv1_res0(const ARMISARegisters *id)
+{
+ /* 0b1110 is not permitted unless we have FEAT_NV */
+ return isar_feature_aa64_nv(id) && FIELD_EX64_IDREG(id, ID_AA64MMFR4, E2H0) == 0b1110;
+}
+
static inline bool isar_feature_aa64_mec(const ARMISARegisters *id)
{
return FIELD_EX64_IDREG(id, ID_AA64MMFR3, MEC) != 0;
diff --git a/target/arm/helper.c b/target/arm/helper.c
index 390ea32c218..c3f4054a0b0 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -3776,7 +3776,8 @@ static void do_hcr_write(CPUARMState *env, uint64_t value, uint64_t valid_mask)
}
if (arm_feature(env, ARM_FEATURE_AARCH64)) {
- if (cpu_isar_feature(aa64_vh, cpu)) {
+ if (cpu_isar_feature(aa64_vh, cpu) &&
+ cpu_isar_feature(aa64_e2h0, cpu)) {
valid_mask |= HCR_E2H;
}
if (cpu_isar_feature(aa64_ras, cpu)) {
@@ -3801,10 +3802,13 @@ static void do_hcr_write(CPUARMState *env, uint64_t value, uint64_t valid_mask)
valid_mask |= HCR_GPF;
}
if (cpu_isar_feature(aa64_nv, cpu)) {
- valid_mask |= HCR_NV | HCR_NV1 | HCR_AT;
+ valid_mask |= HCR_NV | HCR_AT;
+ if (!cpu_isar_feature(aa64_noe2h0_and_nv1_res0, cpu)) {
+ valid_mask |= HCR_NV1;
+ }
}
if (cpu_isar_feature(aa64_nv2, cpu)) {
- valid_mask |= HCR_NV2;
+ valid_mask |= HCR_NV1 | HCR_NV2;
}
}
@@ -3823,6 +3827,12 @@ static void do_hcr_write(CPUARMState *env, uint64_t value, uint64_t valid_mask)
value |= HCR_RW;
}
+ /* Strictly E2H is RES1 unless FEAT_E2H0 relaxes the requirement */
+ if (arm_feature(env, ARM_FEATURE_AARCH64) &&
+ !cpu_isar_feature(aa64_e2h0, cpu)) {
+ value |= HCR_E2H;
+ }
+
/*
* These bits change the MMU setup:
* HCR_VM enables stage 2 translation
--
2.47.3
On 2/4/26 23:13, Alex Bennée wrote:
> FEAT_E2H0 is a formalisation of the existing behaviour of HCR_EL2.E2H
> being programmable to switch between EL2 host mode and the
> "traditional" nVHE EL2 mode. This implies at some point we might want
> to model CPUs without FEAT_E2H0 which will always have EL2 host mode
> enabled.
>
> There are two values to represent no E2H0 systems of which 0b1110 will
> make HCR_EL2.NV1 RES0 for FEAT_NV systems. For FEAT_NV2 the NV1 bit is
> always valid.
>
> Message-ID: <20260130181648.628364-1-alex.bennee@linaro.org>
> Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
>
> ---
> v2
> - new helper and properly handling NV1
> ---
> docs/system/arm/emulation.rst | 1 +
> target/arm/cpu-features.h | 12 ++++++++++++
> target/arm/helper.c | 16 +++++++++++++---
> 3 files changed, 26 insertions(+), 3 deletions(-)
>
> diff --git a/docs/system/arm/emulation.rst b/docs/system/arm/emulation.rst
> index e0d5f9886e1..7787691853e 100644
> --- a/docs/system/arm/emulation.rst
> +++ b/docs/system/arm/emulation.rst
> @@ -54,6 +54,7 @@ the following architecture extensions:
> - FEAT_DotProd (Advanced SIMD dot product instructions)
> - FEAT_DoubleFault (Double Fault Extension)
> - FEAT_E0PD (Preventing EL0 access to halves of address maps)
> +- FEAT_E2H0 (Programming of HCR_EL2.E2H)
> - FEAT_EBF16 (AArch64 Extended BFloat16 instructions)
> - FEAT_ECV (Enhanced Counter Virtualization)
> - FEAT_EL0 (Support for execution at EL0)
> diff --git a/target/arm/cpu-features.h b/target/arm/cpu-features.h
> index e0b7a45b7bd..78ff761ae06 100644
> --- a/target/arm/cpu-features.h
> +++ b/target/arm/cpu-features.h
> @@ -347,6 +347,7 @@ FIELD(ID_AA64MMFR3, ADERR, 56, 4)
> FIELD(ID_AA64MMFR3, SPEC_FPACC, 60, 4)
>
> FIELD(ID_AA64MMFR4, ASID2, 8, 4)
> +FIELD(ID_AA64MMFR4, E2H0, 24, 4)
>
> FIELD(ID_AA64DFR0, DEBUGVER, 0, 4)
> FIELD(ID_AA64DFR0, TRACEVER, 4, 4)
> @@ -1378,6 +1379,17 @@ static inline bool isar_feature_aa64_asid2(const ARMISARegisters *id)
> return FIELD_EX64_IDREG(id, ID_AA64MMFR4, ASID2) != 0;
> }
>
> +static inline bool isar_feature_aa64_e2h0(const ARMISARegisters *id)
> +{
> + return FIELD_EX64_IDREG(id, ID_AA64MMFR4, E2H0) == 0;
> +}
From D24.1.3 Principles of the ID scheme for fields in ID registers, this field is
signed, so: FIELD_SEX64_IDREG(...) >= 0
> +
> +static inline bool isar_feature_aa64_noe2h0_and_nv1_res0(const ARMISARegisters *id)
> +{
> + /* 0b1110 is not permitted unless we have FEAT_NV */
> + return isar_feature_aa64_nv(id) && FIELD_EX64_IDREG(id, ID_AA64MMFR4, E2H0) == 0b1110;
> +}
FIELD_SEX64_IDREG(...) <= -2
And you don't need the nv check because that would otherwise be an invalid value, as you
say. We don't need to double-check invalid ID register settings at this point.
I would probably drop the noe2h0_and_ substring, because it's not really relevant to what
you're wanting to test.
> @@ -3801,10 +3802,13 @@ static void do_hcr_write(CPUARMState *env, uint64_t value, uint64_t valid_mask)
> valid_mask |= HCR_GPF;
> }
> if (cpu_isar_feature(aa64_nv, cpu)) {
> - valid_mask |= HCR_NV | HCR_NV1 | HCR_AT;
> + valid_mask |= HCR_NV | HCR_AT;
> + if (!cpu_isar_feature(aa64_noe2h0_and_nv1_res0, cpu)) {
> + valid_mask |= HCR_NV1;
> + }
> }
> if (cpu_isar_feature(aa64_nv2, cpu)) {
> - valid_mask |= HCR_NV2;
> + valid_mask |= HCR_NV1 | HCR_NV2;
Why add NV1 here?
> }
> }
>
> @@ -3823,6 +3827,12 @@ static void do_hcr_write(CPUARMState *env, uint64_t value, uint64_t valid_mask)
> value |= HCR_RW;
> }
>
> + /* Strictly E2H is RES1 unless FEAT_E2H0 relaxes the requirement */
> + if (arm_feature(env, ARM_FEATURE_AARCH64) &&
> + !cpu_isar_feature(aa64_e2h0, cpu)) {
> + value |= HCR_E2H;
> + }
Merge the aarch64 test with the previous block.
r~
Richard Henderson <richard.henderson@linaro.org> writes:
> On 2/4/26 23:13, Alex Bennée wrote:
>> FEAT_E2H0 is a formalisation of the existing behaviour of HCR_EL2.E2H
>> being programmable to switch between EL2 host mode and the
>> "traditional" nVHE EL2 mode. This implies at some point we might want
>> to model CPUs without FEAT_E2H0 which will always have EL2 host mode
>> enabled.
>> There are two values to represent no E2H0 systems of which 0b1110
>> will
>> make HCR_EL2.NV1 RES0 for FEAT_NV systems. For FEAT_NV2 the NV1 bit is
>> always valid.
>> Message-ID: <20260130181648.628364-1-alex.bennee@linaro.org>
>> Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
>> ---
>> v2
>> - new helper and properly handling NV1
>> ---
<snip>
>
>> @@ -3801,10 +3802,13 @@ static void do_hcr_write(CPUARMState *env, uint64_t value, uint64_t valid_mask)
>> valid_mask |= HCR_GPF;
>> }
>> if (cpu_isar_feature(aa64_nv, cpu)) {
>> - valid_mask |= HCR_NV | HCR_NV1 | HCR_AT;
>> + valid_mask |= HCR_NV | HCR_AT;
>> + if (!cpu_isar_feature(aa64_noe2h0_and_nv1_res0, cpu)) {
>> + valid_mask |= HCR_NV1;
>> + }
>> }
>> if (cpu_isar_feature(aa64_nv2, cpu)) {
>> - valid_mask |= HCR_NV2;
>> + valid_mask |= HCR_NV1 | HCR_NV2;
>
> Why add NV1 here?
My reading was if you had FEAT_NV2 then HCR_NV1 was always valid
whatever E2H0 might say. Unless we can validate we don't get
contradictory ID values elsewhere?
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
> On 5. Feb 2026, at 13:23, Alex Bennée <alex.bennee@linaro.org> wrote:
>
> Richard Henderson <richard.henderson@linaro.org> writes:
>
>> On 2/4/26 23:13, Alex Bennée wrote:
>>> FEAT_E2H0 is a formalisation of the existing behaviour of HCR_EL2.E2H
>>> being programmable to switch between EL2 host mode and the
>>> "traditional" nVHE EL2 mode. This implies at some point we might want
>>> to model CPUs without FEAT_E2H0 which will always have EL2 host mode
>>> enabled.
>>> There are two values to represent no E2H0 systems of which 0b1110
>>> will
>>> make HCR_EL2.NV1 RES0 for FEAT_NV systems. For FEAT_NV2 the NV1 bit is
>>> always valid.
>>> Message-ID: <20260130181648.628364-1-alex.bennee@linaro.org>
>>> Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
>>> ---
>>> v2
>>> - new helper and properly handling NV1
>>> ---
> <snip>
>>
>>> @@ -3801,10 +3802,13 @@ static void do_hcr_write(CPUARMState *env, uint64_t value, uint64_t valid_mask)
>>> valid_mask |= HCR_GPF;
>>> }
>>> if (cpu_isar_feature(aa64_nv, cpu)) {
>>> - valid_mask |= HCR_NV | HCR_NV1 | HCR_AT;
>>> + valid_mask |= HCR_NV | HCR_AT;
>>> + if (!cpu_isar_feature(aa64_noe2h0_and_nv1_res0, cpu)) {
>>> + valid_mask |= HCR_NV1;
>>> + }
>>> }
>>> if (cpu_isar_feature(aa64_nv2, cpu)) {
>>> - valid_mask |= HCR_NV2;
>>> + valid_mask |= HCR_NV1 | HCR_NV2;
>>
>> Why add NV1 here?
>
> My reading was if you had FEAT_NV2 then HCR_NV1 was always valid
> whatever E2H0 might say. Unless we can validate we don't get
> contradictory ID values elsewhere?
KVM with nested virt enabled out of the box exposes a virtual machine with
VHE-only L1 and above, with no allowance for an nVHE nested guest past L1.
In the Linux kernel in arch/arm64/kvm/nested.c:
case SYS_ID_AA64MMFR4_EL1:
/*
* You get EITHER
*
* - FEAT_VHE without FEAT_E2H0
* - FEAT_NV limited to FEAT_NV2
* - HCR_EL2.NV1 being RES0
*
* OR
*
* - FEAT_E2H0 without FEAT_VHE nor FEAT_NV
*
* Life is too short for anything else.
*/
And after taking a further look at the machine readable spec*, 0b1110 is valid together
with FEAT_NV2, as nothing there says otherwise.
* https://developer.arm.com/Architectures/A-Profile%20Architecture#Downloads
>
>
> --
> Alex Bennée
> Virtualisation Tech Lead @ Linaro
Mohamed Mediouni <mohamed@unpredictable.fr> writes:
>> On 5. Feb 2026, at 13:23, Alex Bennée <alex.bennee@linaro.org> wrote:
>>
>> Richard Henderson <richard.henderson@linaro.org> writes:
>>
>>> On 2/4/26 23:13, Alex Bennée wrote:
>>>> FEAT_E2H0 is a formalisation of the existing behaviour of HCR_EL2.E2H
>>>> being programmable to switch between EL2 host mode and the
>>>> "traditional" nVHE EL2 mode. This implies at some point we might want
>>>> to model CPUs without FEAT_E2H0 which will always have EL2 host mode
>>>> enabled.
>>>> There are two values to represent no E2H0 systems of which 0b1110
>>>> will
>>>> make HCR_EL2.NV1 RES0 for FEAT_NV systems. For FEAT_NV2 the NV1 bit is
>>>> always valid.
>>>> Message-ID: <20260130181648.628364-1-alex.bennee@linaro.org>
>>>> Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
>>>> ---
>>>> v2
>>>> - new helper and properly handling NV1
>>>> ---
>> <snip>
>>>
>>>> @@ -3801,10 +3802,13 @@ static void do_hcr_write(CPUARMState *env, uint64_t value, uint64_t valid_mask)
>>>> valid_mask |= HCR_GPF;
>>>> }
>>>> if (cpu_isar_feature(aa64_nv, cpu)) {
>>>> - valid_mask |= HCR_NV | HCR_NV1 | HCR_AT;
>>>> + valid_mask |= HCR_NV | HCR_AT;
>>>> + if (!cpu_isar_feature(aa64_noe2h0_and_nv1_res0, cpu)) {
>>>> + valid_mask |= HCR_NV1;
>>>> + }
>>>> }
>>>> if (cpu_isar_feature(aa64_nv2, cpu)) {
>>>> - valid_mask |= HCR_NV2;
>>>> + valid_mask |= HCR_NV1 | HCR_NV2;
>>>
>>> Why add NV1 here?
>>
>> My reading was if you had FEAT_NV2 then HCR_NV1 was always valid
>> whatever E2H0 might say. Unless we can validate we don't get
>> contradictory ID values elsewhere?
> KVM with nested virt enabled out of the box exposes a virtual machine with
> VHE-only L1 and above, with no allowance for an nVHE nested guest past L1.
>
> In the Linux kernel in arch/arm64/kvm/nested.c:
>
> case SYS_ID_AA64MMFR4_EL1:
> /*
> * You get EITHER
> *
> * - FEAT_VHE without FEAT_E2H0
> * - FEAT_NV limited to FEAT_NV2
> * - HCR_EL2.NV1 being RES0
> *
> * OR
> *
> * - FEAT_E2H0 without FEAT_VHE nor FEAT_NV
> *
> * Life is too short for anything else.
> */
This is a choice a host can make to simplify things but we want to make
sure we could represent hardware that makes a different choice.
>
> And after taking a further look at the machine readable spec*, 0b1110 is valid together
> with FEAT_NV2, as nothing there says otherwise.
>
> *
> https://developer.arm.com/Architectures/A-Profile%20Architecture#Downloads
Is this from the XML or the registers description?
>>
>>
>> --
>> Alex Bennée
>> Virtualisation Tech Lead @ Linaro
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
> On 4. Feb 2026, at 14:13, Alex Bennée <alex.bennee@linaro.org> wrote:
>
> FEAT_E2H0 is a formalisation of the existing behaviour of HCR_EL2.E2H
> being programmable to switch between EL2 host mode and the
> "traditional" nVHE EL2 mode. This implies at some point we might want
> to model CPUs without FEAT_E2H0 which will always have EL2 host mode
> enabled.
>
> There are two values to represent no E2H0 systems of which 0b1110 will
> make HCR_EL2.NV1 RES0 for FEAT_NV systems.
Hello,
That part looks good.
> For FEAT_NV2 the NV1 bit is
> always valid.
At section A2.2.5 of the Arm ARM:
> If FEAT_NV2 is implemented, then FEAT_NV is implemented.
My reading of the spec is that 0b1110 is allowed on a system that implements FEAT_NV2 as those always implement FEAT_NV - even if the trap mode is gone because of using NV_frac to represent nested virtualisation support.
As the code looks fine,
Reviewed-by: Mohamed Mediouni <mohamed@unpredictable.fr>
>
> Message-ID: <20260130181648.628364-1-alex.bennee@linaro.org>
> Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
>
> ---
> v2
> - new helper and properly handling NV1
> ---
> docs/system/arm/emulation.rst | 1 +
> target/arm/cpu-features.h | 12 ++++++++++++
> target/arm/helper.c | 16 +++++++++++++---
> 3 files changed, 26 insertions(+), 3 deletions(-)
>
> diff --git a/docs/system/arm/emulation.rst b/docs/system/arm/emulation.rst
> index e0d5f9886e1..7787691853e 100644
> --- a/docs/system/arm/emulation.rst
> +++ b/docs/system/arm/emulation.rst
> @@ -54,6 +54,7 @@ the following architecture extensions:
> - FEAT_DotProd (Advanced SIMD dot product instructions)
> - FEAT_DoubleFault (Double Fault Extension)
> - FEAT_E0PD (Preventing EL0 access to halves of address maps)
> +- FEAT_E2H0 (Programming of HCR_EL2.E2H)
> - FEAT_EBF16 (AArch64 Extended BFloat16 instructions)
> - FEAT_ECV (Enhanced Counter Virtualization)
> - FEAT_EL0 (Support for execution at EL0)
> diff --git a/target/arm/cpu-features.h b/target/arm/cpu-features.h
> index e0b7a45b7bd..78ff761ae06 100644
> --- a/target/arm/cpu-features.h
> +++ b/target/arm/cpu-features.h
> @@ -347,6 +347,7 @@ FIELD(ID_AA64MMFR3, ADERR, 56, 4)
> FIELD(ID_AA64MMFR3, SPEC_FPACC, 60, 4)
>
> FIELD(ID_AA64MMFR4, ASID2, 8, 4)
> +FIELD(ID_AA64MMFR4, E2H0, 24, 4)
>
> FIELD(ID_AA64DFR0, DEBUGVER, 0, 4)
> FIELD(ID_AA64DFR0, TRACEVER, 4, 4)
> @@ -1378,6 +1379,17 @@ static inline bool isar_feature_aa64_asid2(const ARMISARegisters *id)
> return FIELD_EX64_IDREG(id, ID_AA64MMFR4, ASID2) != 0;
> }
>
> +static inline bool isar_feature_aa64_e2h0(const ARMISARegisters *id)
> +{
> + return FIELD_EX64_IDREG(id, ID_AA64MMFR4, E2H0) == 0;
> +}
> +
> +static inline bool isar_feature_aa64_noe2h0_and_nv1_res0(const ARMISARegisters *id)
> +{
> + /* 0b1110 is not permitted unless we have FEAT_NV */
> + return isar_feature_aa64_nv(id) && FIELD_EX64_IDREG(id, ID_AA64MMFR4, E2H0) == 0b1110;
> +}
> +
> static inline bool isar_feature_aa64_mec(const ARMISARegisters *id)
> {
> return FIELD_EX64_IDREG(id, ID_AA64MMFR3, MEC) != 0;
> diff --git a/target/arm/helper.c b/target/arm/helper.c
> index 390ea32c218..c3f4054a0b0 100644
> --- a/target/arm/helper.c
> +++ b/target/arm/helper.c
> @@ -3776,7 +3776,8 @@ static void do_hcr_write(CPUARMState *env, uint64_t value, uint64_t valid_mask)
> }
>
> if (arm_feature(env, ARM_FEATURE_AARCH64)) {
> - if (cpu_isar_feature(aa64_vh, cpu)) {
> + if (cpu_isar_feature(aa64_vh, cpu) &&
> + cpu_isar_feature(aa64_e2h0, cpu)) {
> valid_mask |= HCR_E2H;
> }
> if (cpu_isar_feature(aa64_ras, cpu)) {
> @@ -3801,10 +3802,13 @@ static void do_hcr_write(CPUARMState *env, uint64_t value, uint64_t valid_mask)
> valid_mask |= HCR_GPF;
> }
> if (cpu_isar_feature(aa64_nv, cpu)) {
> - valid_mask |= HCR_NV | HCR_NV1 | HCR_AT;
> + valid_mask |= HCR_NV | HCR_AT;
> + if (!cpu_isar_feature(aa64_noe2h0_and_nv1_res0, cpu)) {
> + valid_mask |= HCR_NV1;
> + }
> }
> if (cpu_isar_feature(aa64_nv2, cpu)) {
> - valid_mask |= HCR_NV2;
> + valid_mask |= HCR_NV1 | HCR_NV2;
> }
> }
>
> @@ -3823,6 +3827,12 @@ static void do_hcr_write(CPUARMState *env, uint64_t value, uint64_t valid_mask)
> value |= HCR_RW;
> }
>
> + /* Strictly E2H is RES1 unless FEAT_E2H0 relaxes the requirement */
> + if (arm_feature(env, ARM_FEATURE_AARCH64) &&
> + !cpu_isar_feature(aa64_e2h0, cpu)) {
> + value |= HCR_E2H;
> + }
> +
> /*
> * These bits change the MMU setup:
> * HCR_VM enables stage 2 translation
> --
> 2.47.3
>
>
© 2016 - 2026 Red Hat, Inc.