target/arm/hvf/hvf.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)
Apple headers say:
> When EL2 is disabled, PMU register accesses trigger "Trapped MSR, MRS, or
> System Instruction" exceptions. When this happens, hv_vcpu_run() returns, and the
> hv_vcpu_exit_t object contains the information about this exception.
> When EL2 is enabled, the handling of PMU register accesses is determined by the PMUVer
> field of ID_AA64DFR0_EL1 register.
> If the PMUVer field value is zero or is invalid, PMU register accesses generate "Undefined"
> exceptions, which are sent to the guest.
> If the PMUVer field value is non-zero and valid, PMU register accesses are emulated by the framework.
> The ID_AA64DFR0_EL1 register can be modified via hv_vcpu_set_sys_reg API.
That documentation turns out to not be so accurate, with the determining factor being whether the vGIC
is enabled instead of nested virtualisation. If ID_AA64DFR0_EL1.PMUVer = 0, Hypervisor.framework doesn't
give us the luxury of having PMU sysreg accesses trapped to the VMM when the vGIC is enabled.
As Windows really wants at least a single counter PMU, give it even when
using the kernel-irqchip with nested virt off.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
---
target/arm/hvf/hvf.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
index 8aa59a1f77..136761b429 100644
--- a/target/arm/hvf/hvf.c
+++ b/target/arm/hvf/hvf.c
@@ -1187,8 +1187,12 @@ static bool hvf_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf)
clamp_id_aa64mmfr0_parange_to_ipa_size(&host_isar);
- if (hvf_nested_virt_enabled()) {
+ /* Windows really wants at least a single counter PMU... */
+ if (hvf_irqchip_in_kernel()) {
FIELD_DP64_IDREG(&host_isar, ID_AA64DFR0, PMUVER, 0x1);
+ }
+
+ if (hvf_nested_virt_enabled()) {
/* SME is not implemented with nested virt on the Apple side */
FIELD_DP64_IDREG(&host_isar, ID_AA64PFR1, SME, 0);
}
@@ -1660,7 +1664,7 @@ static int hvf_sysreg_read(CPUState *cpu, uint32_t reg, uint64_t *val)
ARMCPU *arm_cpu = ARM_CPU(cpu);
CPUARMState *env = &arm_cpu->env;
- if (!hvf_nested_virt_enabled() && arm_feature(env, ARM_FEATURE_PMU)) {
+ if (!hvf_irqchip_in_kernel() && arm_feature(env, ARM_FEATURE_PMU)) {
switch (reg) {
case SYSREG_PMCR_EL0:
*val = env->cp15.c9_pmcr;
@@ -1930,7 +1934,7 @@ static int hvf_sysreg_write(CPUState *cpu, uint32_t reg, uint64_t val)
SYSREG_OP2(reg),
val);
- if (!hvf_nested_virt_enabled() && arm_feature(env, ARM_FEATURE_PMU)) {
+ if (!hvf_irqchip_in_kernel() && arm_feature(env, ARM_FEATURE_PMU)) {
switch (reg) {
case SYSREG_PMCCNTR_EL0:
pmu_op_start(env);
--
2.50.1 (Apple Git-155)
On Mon, 9 Mar 2026 at 11:46, Mohamed Mediouni <mohamed@unpredictable.fr> wrote:
>
> Apple headers say:
>
> > When EL2 is disabled, PMU register accesses trigger "Trapped MSR, MRS, or
> > System Instruction" exceptions. When this happens, hv_vcpu_run() returns, and the
> > hv_vcpu_exit_t object contains the information about this exception.
>
> > When EL2 is enabled, the handling of PMU register accesses is determined by the PMUVer
> > field of ID_AA64DFR0_EL1 register.
> > If the PMUVer field value is zero or is invalid, PMU register accesses generate "Undefined"
> > exceptions, which are sent to the guest.
> > If the PMUVer field value is non-zero and valid, PMU register accesses are emulated by the framework.
> > The ID_AA64DFR0_EL1 register can be modified via hv_vcpu_set_sys_reg API.
>
> That documentation turns out to not be so accurate, with the determining factor being whether the vGIC
> is enabled instead of nested virtualisation. If ID_AA64DFR0_EL1.PMUVer = 0, Hypervisor.framework doesn't
> give us the luxury of having PMU sysreg accesses trapped to the VMM when the vGIC is enabled.
>
> As Windows really wants at least a single counter PMU, give it even when
> using the kernel-irqchip with nested virt off.
>
> Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
> ---
> target/arm/hvf/hvf.c | 10 +++++++---
> 1 file changed, 7 insertions(+), 3 deletions(-)
>
> diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
> index 8aa59a1f77..136761b429 100644
> --- a/target/arm/hvf/hvf.c
> +++ b/target/arm/hvf/hvf.c
> @@ -1187,8 +1187,12 @@ static bool hvf_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf)
>
> clamp_id_aa64mmfr0_parange_to_ipa_size(&host_isar);
>
> - if (hvf_nested_virt_enabled()) {
> + /* Windows really wants at least a single counter PMU... */
> + if (hvf_irqchip_in_kernel()) {
> FIELD_DP64_IDREG(&host_isar, ID_AA64DFR0, PMUVER, 0x1);
> + }
This comment and the code don't line up. We say "Windows wants a PMU"
but then we only conditionally enable the PMU. If the irqchip isn't
in the kernel then presumably the PMU is still disabled (or at least
not advertised to the guest) and Windows still falls over ?
-- PMM
> On 13. Mar 2026, at 11:00, Peter Maydell <peter.maydell@linaro.org> wrote:
>
> On Mon, 9 Mar 2026 at 11:46, Mohamed Mediouni <mohamed@unpredictable.fr> wrote:
>>
>> Apple headers say:
>>
>>> When EL2 is disabled, PMU register accesses trigger "Trapped MSR, MRS, or
>>> System Instruction" exceptions. When this happens, hv_vcpu_run() returns, and the
>>> hv_vcpu_exit_t object contains the information about this exception.
>>
>>> When EL2 is enabled, the handling of PMU register accesses is determined by the PMUVer
>>> field of ID_AA64DFR0_EL1 register.
>>> If the PMUVer field value is zero or is invalid, PMU register accesses generate "Undefined"
>>> exceptions, which are sent to the guest.
>>> If the PMUVer field value is non-zero and valid, PMU register accesses are emulated by the framework.
>>> The ID_AA64DFR0_EL1 register can be modified via hv_vcpu_set_sys_reg API.
>>
>> That documentation turns out to not be so accurate, with the determining factor being whether the vGIC
>> is enabled instead of nested virtualisation. If ID_AA64DFR0_EL1.PMUVer = 0, Hypervisor.framework doesn't
>> give us the luxury of having PMU sysreg accesses trapped to the VMM when the vGIC is enabled.
>>
>> As Windows really wants at least a single counter PMU, give it even when
>> using the kernel-irqchip with nested virt off.
>>
>> Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
>> ---
>> target/arm/hvf/hvf.c | 10 +++++++---
>> 1 file changed, 7 insertions(+), 3 deletions(-)
>>
>> diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
>> index 8aa59a1f77..136761b429 100644
>> --- a/target/arm/hvf/hvf.c
>> +++ b/target/arm/hvf/hvf.c
>> @@ -1187,8 +1187,12 @@ static bool hvf_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf)
>>
>> clamp_id_aa64mmfr0_parange_to_ipa_size(&host_isar);
>>
>> - if (hvf_nested_virt_enabled()) {
>> + /* Windows really wants at least a single counter PMU... */
>> + if (hvf_irqchip_in_kernel()) {
>> FIELD_DP64_IDREG(&host_isar, ID_AA64DFR0, PMUVER, 0x1);
>> + }
>
> This comment and the code don't line up. We say "Windows wants a PMU"
> but then we only conditionally enable the PMU. If the irqchip isn't
> in the kernel then presumably the PMU is still disabled (or at least
> not advertised to the guest) and Windows still falls over ?
>
Hello,
If there’s no Apple vGIC enabled, accesses to PMU registers are routed to the VMM. Windows tries to access to the PMU registers anyway in that case. And that allows to get away with (very) stub-like handling that does “”emulate”” the cycle counter just to boot Windows: And as that has exactly zero accuracy we don’t want to expose it for anything else.
If the Apple vGIC is enabled and there wasn’t an ID register write made to enable the Apple vPMU, then instead of PMU writes
being trappable to the VMM, what happens is that an undefined instruction exception is injected to the guest. And that makes Windows crash early.
© 2016 - 2026 Red Hat, Inc.