Introduce support for exposing and enabling APERF/MPERF MSR passthrough
for x86 QEMU guests when running under KVM.

The Linux kernel supports a KVM capability, KVM_X86_DISABLE_EXITS_APERFMPERF
under KVM_CAP_X86_DISABLE_EXITS, allowing the hypervisor to disable read
intercepts on the IA32_APERF and IA32_MPERF MSRs. This lets guests track
effective frequency directly, without VM exits. QEMU currently lacks a
way to request this capability or expose the corresponding feature bit
to the guest.

This patch adds:

- the `aperfmperf` CPU feature flag via FEAT_6_ECX (CPUID.06H:ECX[0]),
  marked unmigratable and excluded from auto-enable;
- the `aperfmperf` property on the KVM accelerator.

`-accel kvm,aperfmperf=on` makes QEMU enable the KVM capability at VM
creation and report the CPUID bit as supported. QEMU fails to start if
the host KVM does not support the capability. `-cpu
...,migratable=off,aperfmperf=on` exposes the bit to the guest. Both are
required for the guest to use the MSRs.

This allows guest operating systems (such as FreeBSD or Linux) to
compute effective CPU frequency from APERF/MPERF, which is not possible
today since the bit is never exposed and KVM does not emulate these
MSRs. Since the guest reads the raw host counters, pinning vCPUs to
host CPUs is recommended for meaningful results.

Signed-off-by: Anderson Nascimento <[email protected]>
---
Changes in v3:
- Added it as a standalone accel option, no longer depending on cpu-pm.
- Added the flag to no_autoenable_flags.
- Link to v2: 
https://lore.kernel.org/qemu-devel/[email protected]/

Changes in v2:
- Marked aperfmperf as unmigratable.
- Link to v1: 
https://lore.kernel.org/all/[email protected]/
---
 accel/kvm/kvm-all.c      | 13 +++++++++++++
 include/system/kvm_int.h |  1 +
 target/i386/cpu.c        | 19 ++++++++++++++++++-
 target/i386/cpu.h        |  2 ++
 target/i386/kvm/kvm.c    | 25 +++++++++++++++++++++++++
 5 files changed, 59 insertions(+), 1 deletion(-)

diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index 411f28dde2..bff66b2381 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -4264,6 +4264,12 @@ static void kvm_set_kvm_rapl_socket_path(Object *obj,
     s->msr_energy.socket_path = g_strdup(str);
 }
 
+static void kvm_set_kvm_aperfmperf(Object *obj, bool value, Error **errp)
+{
+    KVMState *s = KVM_STATE(obj);
+    s->aperfmperf_enable = value;
+}
+
 static void kvm_accel_instance_init(Object *obj)
 {
     KVMState *s = KVM_STATE(obj);
@@ -4284,6 +4290,7 @@ static void kvm_accel_instance_init(Object *obj)
     s->xen_evtchn_max_pirq = 256;
     s->device = NULL;
     s->msr_energy.enable = false;
+    s->aperfmperf_enable = false;
     s->honor_guest_pat = ON_OFF_AUTO_OFF;
 }
 
@@ -4330,6 +4337,12 @@ static void kvm_accel_class_init(ObjectClass *oc, const 
void *data)
     object_class_property_set_description(oc, "rapl-helper-socket",
         "Socket Path for comminucating with the Virtual MSR helper daemon");
 
+    object_class_property_add_bool(oc, "aperfmperf",
+                                   NULL,
+                                   kvm_set_kvm_aperfmperf);
+    object_class_property_set_description(oc, "aperfmperf",
+        "Allow frequency related MSRs for APERF/MPERF interface in Guest");
+
     kvm_arch_accel_class_init(oc);
 }
 
diff --git a/include/system/kvm_int.h b/include/system/kvm_int.h
index 0876aac938..651f5e7b83 100644
--- a/include/system/kvm_int.h
+++ b/include/system/kvm_int.h
@@ -160,6 +160,7 @@ struct KVMState
     uint64_t kvm_eager_split_size;  /* Eager Page Splitting chunk size */
     struct KVMDirtyRingReaper reaper;
     struct KVMMsrEnergy msr_energy;
+    bool aperfmperf_enable;
     NotifyVmexitOption notify_vmexit;
     uint32_t notify_window;
     uint32_t xen_version;
diff --git a/target/i386/cpu.c b/target/i386/cpu.c
index eab0d5a4d4..ca5b321df4 100644
--- a/target/i386/cpu.c
+++ b/target/i386/cpu.c
@@ -1548,6 +1548,23 @@ FeatureWordInfo feature_word_info[FEATURE_WORDS] = {
         .cpuid = { .eax = 6, .reg = R_EAX, },
         .tcg_features = TCG_6_EAX_FEATURES,
     },
+    [FEAT_6_ECX] = {
+        .type = CPUID_FEATURE_WORD,
+        .feat_names = {
+            "aperfmperf", NULL, NULL, NULL,
+            NULL, NULL, NULL, NULL,
+            NULL, NULL, NULL, NULL,
+            NULL, NULL, NULL, NULL,
+            NULL, NULL, NULL, NULL,
+            NULL, NULL, NULL, NULL,
+            NULL, NULL, NULL, NULL,
+            NULL, NULL, NULL, NULL,
+        },
+        .cpuid = { .eax = 6, .reg = R_ECX, },
+        .tcg_features = 0,
+        .unmigratable_flags = CPUID_6_ECX_APERFMPERF,
+        .no_autoenable_flags = CPUID_6_ECX_APERFMPERF,
+    },
     [FEAT_XSAVE_XCR0_LO] = {
         .type = CPUID_FEATURE_WORD,
         .cpuid = {
@@ -8781,7 +8798,7 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, 
uint32_t count,
         /* Thermal and Power Leaf */
         *eax = env->features[FEAT_6_EAX];
         *ebx = 0;
-        *ecx = 0;
+        *ecx = env->features[FEAT_6_ECX];
         *edx = 0;
         break;
     case 7:
diff --git a/target/i386/cpu.h b/target/i386/cpu.h
index 3945e90371..4b730c5e30 100644
--- a/target/i386/cpu.h
+++ b/target/i386/cpu.h
@@ -709,6 +709,7 @@ typedef enum FeatureWord {
     FEAT_SVM,           /* CPUID[8000_000A].EDX */
     FEAT_XSAVE,         /* CPUID[EAX=0xd,ECX=1].EAX */
     FEAT_6_EAX,         /* CPUID[6].EAX */
+    FEAT_6_ECX,         /* CPUID[6].ECX */
     FEAT_XSAVE_XCR0_LO, /* CPUID[EAX=0xd,ECX=0].EAX */
     FEAT_XSAVE_XCR0_HI, /* CPUID[EAX=0xd,ECX=0].EDX */
     FEAT_ARCH_CAPABILITIES,
@@ -1241,6 +1242,7 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, 
FeatureWord w);
 #define CPUID_XSAVE_XFD        (1U << 4)
 
 #define CPUID_6_EAX_ARAT       (1U << 2)
+#define CPUID_6_ECX_APERFMPERF (1U << 0)
 
 /* CPUID[0x80000007].EDX flags: */
 #define CPUID_APM_INVTSC       (1U << 8)
diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c
index e29ecaba57..72634d2c3b 100644
--- a/target/i386/kvm/kvm.c
+++ b/target/i386/kvm/kvm.c
@@ -501,6 +501,12 @@ uint32_t kvm_arch_get_supported_cpuid(KVMState *s, 
uint32_t function,
         }
     } else if (function == 6 && reg == R_EAX) {
         ret |= CPUID_6_EAX_ARAT; /* safe to allow because of emulated APIC */
+    } else if (function == 6 && reg == R_ECX) {
+        uint32_t ecx;
+        if (s->aperfmperf_enable) {
+            host_cpuid(6, 0, &unused, &unused, &ecx, &unused);
+            ret |= ecx & CPUID_6_ECX_APERFMPERF;
+        }
     } else if (function == 7 && index == 0 && reg == R_EBX) {
         /* Not new instructions, just an optimization.  */
         uint32_t ebx;
@@ -3401,6 +3407,18 @@ static int kvm_vm_enable_energy_msrs(KVMState *s)
     return 0;
 }
 
+static int kvm_vm_enable_aperfmperf_msrs(KVMState *s)
+{
+    int supported = kvm_check_extension(s, KVM_CAP_X86_DISABLE_EXITS);
+
+    if (!(supported & KVM_X86_DISABLE_EXITS_APERFMPERF)) {
+        error_report("kvm: APERF/MPERF passthrough is not supported by this 
host");
+        return -ENOTSUP;
+    }
+    return kvm_vm_enable_cap(s, KVM_CAP_X86_DISABLE_EXITS, 0,
+                             KVM_X86_DISABLE_EXITS_APERFMPERF);
+}
+
 int kvm_arch_on_vmfd_change(MachineState *ms, KVMState *s)
 {
     int ret;
@@ -3586,6 +3604,13 @@ int kvm_arch_init(MachineState *ms, KVMState *s)
         }
     }
 
+    if (s->aperfmperf_enable) {
+        ret = kvm_vm_enable_aperfmperf_msrs(s);
+        if (ret < 0) {
+            return ret;
+        }
+    }
+
     pmu_cap = kvm_check_extension(s, KVM_CAP_PMU_CAPABILITY);
 
     if (first) {

base-commit: 99e6fa0053ba1b1454b8c4446f857c430547a702
-- 
2.56.0


Reply via email to