diff mbox series

[1/5] qapi/qom: Introduce kvm-pmu-filter object

Message ID 20250409082649.14733-2-zhao1.liu@intel.com (mailing list archive)
State New
Headers show
Series accel/kvm: Support KVM PMU filter | expand

Commit Message

Zhao Liu April 9, 2025, 8:26 a.m. UTC
Introduce the kvm-pmu-filter object and support the PMU event with raw
format.

The raw format, as a native PMU event code representation, can be used
for several architectures.

Signed-off-by: Zhao Liu <zhao1.liu@intel.com>
Tested-by: Yi Lai <yi1.lai@intel.com>
---
Changes since RFC v2:
 * Drop hexadecimal variants and support numeric version in QAPI
   directly. (Daniel)
 * Define three-level sections with new accelerator.json. (Markus)
 * QAPI style fixes:
   - KVMPMU* stuff -> KvmPmu*.
   - KVMPMUFilterProperty -> KVMPMUFilterProperties.
   - KVMPMUEventEncodeFmt -> KvmPmuEventFormat.
   - drop prefix in KvmPmuFilterAction and KvmPmuEventFormat.
 * Bump up the supported QAPI version to v10.1.
 * Add Tested-by from Yi.

Changes since RFC v1:
 * Make "action" as a global (per filter object) item, not a per-event
   parameter. (Dapeng)
 * Bump up the supported QAPI version to v10.0.
---
 MAINTAINERS              |   2 +
 accel/kvm/kvm-pmu.c      | 114 +++++++++++++++++++++++++++++++++++++++
 accel/kvm/meson.build    |   1 +
 include/system/kvm-pmu.h |  35 ++++++++++++
 qapi/accelerator.json    |  14 +++++
 qapi/kvm.json            |  84 +++++++++++++++++++++++++++++
 qapi/meson.build         |   1 +
 qapi/qapi-schema.json    |   1 +
 qapi/qom.json            |   3 ++
 9 files changed, 255 insertions(+)
 create mode 100644 accel/kvm/kvm-pmu.c
 create mode 100644 include/system/kvm-pmu.h
 create mode 100644 qapi/accelerator.json
 create mode 100644 qapi/kvm.json

Comments

Markus Armbruster April 10, 2025, 2:21 p.m. UTC | #1
Zhao Liu <zhao1.liu@intel.com> writes:

> Introduce the kvm-pmu-filter object and support the PMU event with raw
> format.

Remind me, what does the kvm-pmu-filter object do, and why would we want
to use it?

> The raw format, as a native PMU event code representation, can be used
> for several architectures.
>
> Signed-off-by: Zhao Liu <zhao1.liu@intel.com>
> Tested-by: Yi Lai <yi1.lai@intel.com>
Zhao Liu April 11, 2025, 4:03 a.m. UTC | #2
Hi Markus

On Thu, Apr 10, 2025 at 04:21:01PM +0200, Markus Armbruster wrote:
> Date: Thu, 10 Apr 2025 16:21:01 +0200
> From: Markus Armbruster <armbru@redhat.com>
> Subject: Re: [PATCH 1/5] qapi/qom: Introduce kvm-pmu-filter object
> 
> Zhao Liu <zhao1.liu@intel.com> writes:
> 
> > Introduce the kvm-pmu-filter object and support the PMU event with raw
> > format.
> 
> Remind me, what does the kvm-pmu-filter object do, and why would we
> want to use it?

KVM PMU filter allows user space to set PMU event whitelist / blacklist
for Guest. Both ARM and x86's KVMs accept a list of PMU events, and x86
also accpets other formats & fixed counter field.

The earliest version uses custom parsing rules, which is not flexible
enough to scale. So to support such complex configuration in cli, it's
best to define and parse it via QAPI, and it's best to support the JSON
way.

Based on these considerations, I found "object" to be a suitable enough
choice.

Thus kvm-pmu-filter object handles all the complexity of parsing values
from cli, and it can include some checks that QAPI cannot include (such
as the 12-bit limit).

Thanks,
Zhao
Markus Armbruster April 11, 2025, 4:38 a.m. UTC | #3
Zhao Liu <zhao1.liu@intel.com> writes:

> Hi Markus
>
> On Thu, Apr 10, 2025 at 04:21:01PM +0200, Markus Armbruster wrote:
>> Date: Thu, 10 Apr 2025 16:21:01 +0200
>> From: Markus Armbruster <armbru@redhat.com>
>> Subject: Re: [PATCH 1/5] qapi/qom: Introduce kvm-pmu-filter object
>> 
>> Zhao Liu <zhao1.liu@intel.com> writes:
>> 
>> > Introduce the kvm-pmu-filter object and support the PMU event with raw
>> > format.
>> 
>> Remind me, what does the kvm-pmu-filter object do, and why would we
>> want to use it?
>
> KVM PMU filter allows user space to set PMU event whitelist / blacklist
> for Guest. Both ARM and x86's KVMs accept a list of PMU events, and x86
> also accpets other formats & fixed counter field.

But what does the system *do* with these event lists?

> The earliest version uses custom parsing rules, which is not flexible
> enough to scale. So to support such complex configuration in cli, it's
> best to define and parse it via QAPI, and it's best to support the JSON
> way.
>
> Based on these considerations, I found "object" to be a suitable enough
> choice.
>
> Thus kvm-pmu-filter object handles all the complexity of parsing values
> from cli, and it can include some checks that QAPI cannot include (such
> as the 12-bit limit).
>
> Thanks,
> Zhao
Zhao Liu April 11, 2025, 6:34 a.m. UTC | #4
On Fri, Apr 11, 2025 at 06:38:35AM +0200, Markus Armbruster wrote:
> Date: Fri, 11 Apr 2025 06:38:35 +0200
> From: Markus Armbruster <armbru@redhat.com>
> Subject: Re: [PATCH 1/5] qapi/qom: Introduce kvm-pmu-filter object
> 
> Zhao Liu <zhao1.liu@intel.com> writes:
> 
> > Hi Markus
> >
> > On Thu, Apr 10, 2025 at 04:21:01PM +0200, Markus Armbruster wrote:
> >> Date: Thu, 10 Apr 2025 16:21:01 +0200
> >> From: Markus Armbruster <armbru@redhat.com>
> >> Subject: Re: [PATCH 1/5] qapi/qom: Introduce kvm-pmu-filter object
> >> 
> >> Zhao Liu <zhao1.liu@intel.com> writes:
> >> 
> >> > Introduce the kvm-pmu-filter object and support the PMU event with raw
> >> > format.
> >> 
> >> Remind me, what does the kvm-pmu-filter object do, and why would we
> >> want to use it?
> >
> > KVM PMU filter allows user space to set PMU event whitelist / blacklist
> > for Guest. Both ARM and x86's KVMs accept a list of PMU events, and x86
> > also accpets other formats & fixed counter field.
> 
> But what does the system *do* with these event lists?

This is for security purposes, and can restrict Guest users from
accessing certain sensitive hardware information on the Host via perf or
PMU counter.

When a PMU event is blocked by KVM, Guest users can't get the
corresponding event count via perf/PMU counter.

EMM, if ‘system’ refers to the QEMU part, then QEMU is responsible
for checking the format and passing the list to KVM.

Thanks,
Zhao
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index d54b5578f883..3ca551025fb8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -434,6 +434,7 @@  F: accel/kvm/
 F: accel/stubs/kvm-stub.c
 F: include/hw/kvm/
 F: include/system/kvm*.h
+F: qapi/kvm.json
 F: scripts/kvm/kvm_flightrecorder
 
 ARM KVM CPUs
@@ -503,6 +504,7 @@  F: accel/Makefile.objs
 F: accel/stubs/Makefile.objs
 F: cpu-common.c
 F: cpu-target.c
+F: qapi/accelerator.c
 F: system/cpus.c
 
 Apple Silicon HVF CPUs
diff --git a/accel/kvm/kvm-pmu.c b/accel/kvm/kvm-pmu.c
new file mode 100644
index 000000000000..22f749bf9183
--- /dev/null
+++ b/accel/kvm/kvm-pmu.c
@@ -0,0 +1,114 @@ 
+/*
+ * QEMU KVM PMU Related Abstractions
+ *
+ * Copyright (C) 2025 Intel Corporation.
+ *
+ * Author: Zhao Liu <zhao1.liu@intel.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+
+#include "qapi/error.h"
+#include "qapi/qapi-visit-kvm.h"
+#include "qemu/cutils.h"
+#include "qom/object_interfaces.h"
+#include "system/kvm-pmu.h"
+
+static void kvm_pmu_filter_set_action(Object *obj, int value,
+                                      Error **errp G_GNUC_UNUSED)
+{
+    KVMPMUFilter *filter = KVM_PMU_FILTER(obj);
+
+    filter->action = value;
+}
+
+static int kvm_pmu_filter_get_action(Object *obj,
+                                     Error **errp G_GNUC_UNUSED)
+{
+    KVMPMUFilter *filter = KVM_PMU_FILTER(obj);
+
+    return filter->action;
+}
+
+static void kvm_pmu_filter_get_event(Object *obj, Visitor *v, const char *name,
+                                     void *opaque, Error **errp)
+{
+    KVMPMUFilter *filter = KVM_PMU_FILTER(obj);
+
+    visit_type_KvmPmuFilterEventList(v, name, &filter->events, errp);
+}
+
+static void kvm_pmu_filter_set_event(Object *obj, Visitor *v, const char *name,
+                                     void *opaque, Error **errp)
+{
+    KVMPMUFilter *filter = KVM_PMU_FILTER(obj);
+    KvmPmuFilterEventList *head = NULL, *old_head, *node;
+    int nevents = 0;
+
+    old_head = filter->events;
+    if (!visit_type_KvmPmuFilterEventList(v, name, &head, errp)) {
+        return;
+    }
+
+    for (node = head; node; node = node->next) {
+        switch (node->value->format) {
+        case KVM_PMU_EVENT_FORMAT_RAW:
+            break;
+        default:
+            g_assert_not_reached();
+        }
+
+        nevents++;
+    }
+
+    filter->nevents = nevents;
+    filter->events = head;
+    qapi_free_KvmPmuFilterEventList(old_head);
+    return;
+}
+
+static void kvm_pmu_filter_class_init(ObjectClass *oc, void *data)
+{
+    object_class_property_add_enum(oc, "action", "KvmPmuFilterAction",
+                                   &KvmPmuFilterAction_lookup,
+                                   kvm_pmu_filter_get_action,
+                                   kvm_pmu_filter_set_action);
+    object_class_property_set_description(oc, "action",
+                                          "KVM PMU event action");
+
+    object_class_property_add(oc, "events", "KvmPmuFilterEventList",
+                              kvm_pmu_filter_get_event,
+                              kvm_pmu_filter_set_event,
+                              NULL, NULL);
+    object_class_property_set_description(oc, "events",
+                                          "KVM PMU event list");
+}
+
+static void kvm_pmu_filter_instance_init(Object *obj)
+{
+    KVMPMUFilter *filter = KVM_PMU_FILTER(obj);
+
+    filter->action = KVM_PMU_FILTER_ACTION_ALLOW;
+    filter->nevents = 0;
+}
+
+static const TypeInfo kvm_pmu_filter_info = {
+    .parent = TYPE_OBJECT,
+    .name = TYPE_KVM_PMU_FILTER,
+    .class_init = kvm_pmu_filter_class_init,
+    .instance_size = sizeof(KVMPMUFilter),
+    .instance_init = kvm_pmu_filter_instance_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_USER_CREATABLE },
+        { }
+    }
+};
+
+static void kvm_pmu_event_register_type(void)
+{
+    type_register_static(&kvm_pmu_filter_info);
+}
+
+type_init(kvm_pmu_event_register_type);
diff --git a/accel/kvm/meson.build b/accel/kvm/meson.build
index 397a1fe1fd1e..dfab2854f3a8 100644
--- a/accel/kvm/meson.build
+++ b/accel/kvm/meson.build
@@ -2,6 +2,7 @@  kvm_ss = ss.source_set()
 kvm_ss.add(files(
   'kvm-all.c',
   'kvm-accel-ops.c',
+  'kvm-pmu.c',
 ))
 
 specific_ss.add_all(when: 'CONFIG_KVM', if_true: kvm_ss)
diff --git a/include/system/kvm-pmu.h b/include/system/kvm-pmu.h
new file mode 100644
index 000000000000..818fa309c191
--- /dev/null
+++ b/include/system/kvm-pmu.h
@@ -0,0 +1,35 @@ 
+/*
+ * QEMU KVM PMU Related Abstraction Header
+ *
+ * Copyright (C) 2025 Intel Corporation.
+ *
+ * Author: Zhao Liu <zhao1.liu@intel.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef KVM_PMU_H
+#define KVM_PMU_H
+
+#include "qapi/qapi-types-kvm.h"
+#include "qom/object.h"
+
+#define TYPE_KVM_PMU_FILTER "kvm-pmu-filter"
+OBJECT_DECLARE_SIMPLE_TYPE(KVMPMUFilter, KVM_PMU_FILTER)
+
+/**
+ * KVMPMUFilter:
+ * @action: action that KVM PMU filter will take for selected PMU events.
+ * @nevents: number of PMU event entries listed in @events
+ * @events: list of PMU event entries.  A PMU event entry may represent one
+ *    event or multiple events due to its format.
+ */
+struct KVMPMUFilter {
+    Object parent_obj;
+
+    KvmPmuFilterAction action;
+    uint32_t nevents;
+    KvmPmuFilterEventList *events;
+};
+
+#endif /* KVM_PMU_H */
diff --git a/qapi/accelerator.json b/qapi/accelerator.json
new file mode 100644
index 000000000000..1fe0d64be113
--- /dev/null
+++ b/qapi/accelerator.json
@@ -0,0 +1,14 @@ 
+# -*- Mode: Python -*-
+# vim: filetype=python
+#
+# Copyright (C) 2025 Intel Corporation.
+#
+# Author: Zhao Liu <zhao1.liu@intel.com>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+##
+# = Accelerators
+##
+
+{ 'include': 'kvm.json' }
diff --git a/qapi/kvm.json b/qapi/kvm.json
new file mode 100644
index 000000000000..1861d86a9726
--- /dev/null
+++ b/qapi/kvm.json
@@ -0,0 +1,84 @@ 
+# -*- Mode: Python -*-
+# vim: filetype=python
+#
+# Copyright (C) 2025 Intel Corporation.
+#
+# Author: Zhao Liu <zhao1.liu@intel.com>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+##
+# == KVM
+##
+
+##
+# === PMU stuff (KVM related)
+##
+
+##
+# @KvmPmuFilterAction:
+#
+# Actions that KVM PMU filter supports.
+#
+# @deny: disable the PMU event/counter in KVM PMU filter.
+#
+# @allow: enable the PMU event/counter in KVM PMU filter.
+#
+# Since 10.1
+##
+{ 'enum': 'KvmPmuFilterAction',
+  'data': ['allow', 'deny'] }
+
+##
+# @KvmPmuEventFormat:
+#
+# Encoding formats of PMU event that QEMU/KVM supports.
+#
+# @raw: the encoded event code that KVM can directly consume.
+#
+# Since 10.1
+##
+{ 'enum': 'KvmPmuEventFormat',
+  'data': ['raw'] }
+
+##
+# @KvmPmuRawEvent:
+#
+# Raw PMU event code.
+#
+# @code: the raw value that has been encoded, and QEMU could deliver
+#     to KVM directly.
+#
+# Since 10.1
+##
+{ 'struct': 'KvmPmuRawEvent',
+  'data': { 'code': 'uint64' } }
+
+##
+# @KvmPmuFilterEvent:
+#
+# PMU event filtered by KVM.
+#
+# @format: PMU event format.
+#
+# Since 10.1
+##
+{ 'union': 'KvmPmuFilterEvent',
+  'base': { 'format': 'KvmPmuEventFormat' },
+  'discriminator': 'format',
+  'data': { 'raw': 'KvmPmuRawEvent' } }
+
+##
+# @KvmPmuFilterProperties:
+#
+# Properties of KVM PMU Filter.
+#
+# @action: action that KVM PMU filter will take for selected PMU events.
+#
+# @events: list of selected PMU events.
+#
+# Since 10.1
+##
+{ 'struct': 'KvmPmuFilterProperties',
+  'data': { 'action': 'KvmPmuFilterAction',
+            '*events': ['KvmPmuFilterEvent'] } }
diff --git a/qapi/meson.build b/qapi/meson.build
index eadde4db307f..dba27ebc7489 100644
--- a/qapi/meson.build
+++ b/qapi/meson.build
@@ -37,6 +37,7 @@  qapi_all_modules = [
   'error',
   'introspect',
   'job',
+  'kvm',
   'machine-common',
   'machine',
   'machine-target',
diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json
index c41c01eb2ab9..c7fed7940af7 100644
--- a/qapi/qapi-schema.json
+++ b/qapi/qapi-schema.json
@@ -66,6 +66,7 @@ 
 { 'include': 'compat.json' }
 { 'include': 'control.json' }
 { 'include': 'introspect.json' }
+{ 'include': 'accelerator.json' }
 { 'include': 'qom.json' }
 { 'include': 'qdev.json' }
 { 'include': 'machine-common.json' }
diff --git a/qapi/qom.json b/qapi/qom.json
index 28ce24cd8d08..517f4c06c260 100644
--- a/qapi/qom.json
+++ b/qapi/qom.json
@@ -8,6 +8,7 @@ 
 { 'include': 'block-core.json' }
 { 'include': 'common.json' }
 { 'include': 'crypto.json' }
+{ 'include': 'kvm.json' }
 
 ##
 # = QEMU Object Model (QOM)
@@ -1108,6 +1109,7 @@ 
       'if': 'CONFIG_LINUX' },
     'iommufd',
     'iothread',
+    'kvm-pmu-filter',
     'main-loop',
     { 'name': 'memory-backend-epc',
       'if': 'CONFIG_LINUX' },
@@ -1183,6 +1185,7 @@ 
                                       'if': 'CONFIG_LINUX' },
       'iommufd':                    'IOMMUFDProperties',
       'iothread':                   'IothreadProperties',
+      'kvm-pmu-filter':             'KvmPmuFilterProperties',
       'main-loop':                  'MainLoopProperties',
       'memory-backend-epc':         { 'type': 'MemoryBackendEpcProperties',
                                       'if': 'CONFIG_LINUX' },