diff mbox series

[v7] arm/kvm: Enable support for KVM_ARM_VCPU_PMU_V3_FILTER

Message ID 20240221063431.76992-1-shahuang@redhat.com
State New
Headers show
Series [v7] arm/kvm: Enable support for KVM_ARM_VCPU_PMU_V3_FILTER | expand

Commit Message

Shaoqin Huang Feb. 21, 2024, 6:34 a.m. UTC
The KVM_ARM_VCPU_PMU_V3_FILTER provides the ability to let the VMM decide
which PMU events are provided to the guest. Add a new option
`kvm-pmu-filter` as -cpu sub-option to set the PMU Event Filtering.
Without the filter, all PMU events are exposed from host to guest by
default. The usage of the new sub-option can be found from the updated
document (docs/system/arm/cpu-features.rst).

Here is an example which shows how to use the PMU Event Filtering, when
we launch a guest by use kvm, add such command line:

  # qemu-system-aarch64 \
        -accel kvm \
        -cpu host,kvm-pmu-filter="D:0x11-0x11"

Since the first action is deny, we have a global allow policy. This
filters out the cycle counter (event 0x11 being CPU_CYCLES).

And then in guest, use the perf to count the cycle:

  # perf stat sleep 1

   Performance counter stats for 'sleep 1':

              1.22 msec task-clock                       #    0.001 CPUs utilized
                 1      context-switches                 #  820.695 /sec
                 0      cpu-migrations                   #    0.000 /sec
                55      page-faults                      #   45.138 K/sec
   <not supported>      cycles
           1128954      instructions
            227031      branches                         #  186.323 M/sec
              8686      branch-misses                    #    3.83% of all branches

       1.002492480 seconds time elapsed

       0.001752000 seconds user
       0.000000000 seconds sys

As we can see, the cycle counter has been disabled in the guest, but
other pmu events do still work.

Reviewed-by: Sebastian Ott <sebott@redhat.com>
Signed-off-by: Shaoqin Huang <shahuang@redhat.com>
---
v6->v7:
  - Check return value of sscanf.
  - Improve the check condition.

v5->v6:
  - Commit message improvement.
  - Remove some unused code.
  - Collect Reviewed-by, thanks Sebastian.
  - Use g_auto(Gstrv) to replace the gchar **.          [Eric]

v4->v5:
  - Change the kvm-pmu-filter as a -cpu sub-option.     [Eric]
  - Comment tweak.                                      [Gavin]
  - Rebase to the latest branch.

v3->v4:
  - Fix the wrong check for pmu_filter_init.            [Sebastian]
  - Fix multiple alignment issue.                       [Gavin]
  - Report error by warn_report() instead of error_report(), and don't use
  abort() since the PMU Event Filter is an add-on and best-effort feature.
                                                        [Gavin]
  - Add several missing {  } for single line of code.   [Gavin]
  - Use the g_strsplit() to replace strtok().           [Gavin]

v2->v3:
  - Improve commits message, use kernel doc wording, add more explaination on
    filter example, fix some typo error.                [Eric]
  - Add g_free() in kvm_arch_set_pmu_filter() to prevent memory leak. [Eric]
  - Add more precise error message report.              [Eric]
  - In options doc, add pmu-filter rely on KVM_ARM_VCPU_PMU_V3_FILTER support in
    KVM.                                                [Eric]

v1->v2:
  - Add more description for allow and deny meaning in 
    commit message.                                     [Sebastian]
  - Small improvement.                                  [Sebastian]

 docs/system/arm/cpu-features.rst | 23 +++++++++
 target/arm/cpu.h                 |  3 ++
 target/arm/kvm.c                 | 80 ++++++++++++++++++++++++++++++++
 3 files changed, 106 insertions(+)


base-commit: 760b4dcdddba4a40b9fa0eb78fdfc7eda7cb83d0

Comments

Eric Auger Feb. 22, 2024, 9:45 a.m. UTC | #1
Hi Shaoqin,
On 2/21/24 07:34, Shaoqin Huang wrote:
> The KVM_ARM_VCPU_PMU_V3_FILTER provides the ability to let the VMM decide
> which PMU events are provided to the guest. Add a new option
> `kvm-pmu-filter` as -cpu sub-option to set the PMU Event Filtering.
> Without the filter, all PMU events are exposed from host to guest by
> default. The usage of the new sub-option can be found from the updated
> document (docs/system/arm/cpu-features.rst).
> 
> Here is an example which shows how to use the PMU Event Filtering, when
> we launch a guest by use kvm, add such command line:
> 
>   # qemu-system-aarch64 \
>         -accel kvm \
>         -cpu host,kvm-pmu-filter="D:0x11-0x11"
> 
> Since the first action is deny, we have a global allow policy. This
> filters out the cycle counter (event 0x11 being CPU_CYCLES).
> 
> And then in guest, use the perf to count the cycle:
> 
>   # perf stat sleep 1
> 
>    Performance counter stats for 'sleep 1':
> 
>               1.22 msec task-clock                       #    0.001 CPUs utilized
>                  1      context-switches                 #  820.695 /sec
>                  0      cpu-migrations                   #    0.000 /sec
>                 55      page-faults                      #   45.138 K/sec
>    <not supported>      cycles
>            1128954      instructions
>             227031      branches                         #  186.323 M/sec
>               8686      branch-misses                    #    3.83% of all branches
> 
>        1.002492480 seconds time elapsed
> 
>        0.001752000 seconds user
>        0.000000000 seconds sys
> 
> As we can see, the cycle counter has been disabled in the guest, but
> other pmu events do still work.
> 
> Reviewed-by: Sebastian Ott <sebott@redhat.com>
> Signed-off-by: Shaoqin Huang <shahuang@redhat.com>
Reviewed-by: Eric Auger <eric.auger@redhat.com>

Eric
> ---
> v6->v7:
>   - Check return value of sscanf.
>   - Improve the check condition.
> 
> v5->v6:
>   - Commit message improvement.
>   - Remove some unused code.
>   - Collect Reviewed-by, thanks Sebastian.
>   - Use g_auto(Gstrv) to replace the gchar **.          [Eric]
> 
> v4->v5:
>   - Change the kvm-pmu-filter as a -cpu sub-option.     [Eric]
>   - Comment tweak.                                      [Gavin]
>   - Rebase to the latest branch.
> 
> v3->v4:
>   - Fix the wrong check for pmu_filter_init.            [Sebastian]
>   - Fix multiple alignment issue.                       [Gavin]
>   - Report error by warn_report() instead of error_report(), and don't use
>   abort() since the PMU Event Filter is an add-on and best-effort feature.
>                                                         [Gavin]
>   - Add several missing {  } for single line of code.   [Gavin]
>   - Use the g_strsplit() to replace strtok().           [Gavin]
> 
> v2->v3:
>   - Improve commits message, use kernel doc wording, add more explaination on
>     filter example, fix some typo error.                [Eric]
>   - Add g_free() in kvm_arch_set_pmu_filter() to prevent memory leak. [Eric]
>   - Add more precise error message report.              [Eric]
>   - In options doc, add pmu-filter rely on KVM_ARM_VCPU_PMU_V3_FILTER support in
>     KVM.                                                [Eric]
> 
> v1->v2:
>   - Add more description for allow and deny meaning in 
>     commit message.                                     [Sebastian]
>   - Small improvement.                                  [Sebastian]
> 
>  docs/system/arm/cpu-features.rst | 23 +++++++++
>  target/arm/cpu.h                 |  3 ++
>  target/arm/kvm.c                 | 80 ++++++++++++++++++++++++++++++++
>  3 files changed, 106 insertions(+)
> 
> diff --git a/docs/system/arm/cpu-features.rst b/docs/system/arm/cpu-features.rst
> index a5fb929243..7c8f6a60ef 100644
> --- a/docs/system/arm/cpu-features.rst
> +++ b/docs/system/arm/cpu-features.rst
> @@ -204,6 +204,29 @@ the list of KVM VCPU features and their descriptions.
>    the guest scheduler behavior and/or be exposed to the guest
>    userspace.
>  
> +``kvm-pmu-filter``
> +  By default kvm-pmu-filter is disabled. This means that by default all pmu
> +  events will be exposed to guest.
> +
> +  KVM implements PMU Event Filtering to prevent a guest from being able to
> +  sample certain events. It depends on the KVM_ARM_VCPU_PMU_V3_FILTER
> +  attribute supported in KVM. It has the following format:
> +
> +  kvm-pmu-filter="{A,D}:start-end[;{A,D}:start-end...]"
> +
> +  The A means "allow" and D means "deny", start is the first event of the
> +  range and the end is the last one. The first registered range defines
> +  the global policy(global ALLOW if the first @action is DENY, global DENY
> +  if the first @action is ALLOW). The start and end only support hexadecimal
> +  format. For example:
> +
> +  kvm-pmu-filter="A:0x11-0x11;A:0x23-0x3a;D:0x30-0x30"
> +
> +  Since the first action is allow, we have a global deny policy. It
> +  will allow event 0x11 (The cycle counter), events 0x23 to 0x3a are
> +  also allowed except the event 0x30 which is denied, and all the other
> +  events are denied.
> +
>  TCG VCPU Features
>  =================
>  
> diff --git a/target/arm/cpu.h b/target/arm/cpu.h
> index 63f31e0d98..f7f2431755 100644
> --- a/target/arm/cpu.h
> +++ b/target/arm/cpu.h
> @@ -948,6 +948,9 @@ struct ArchCPU {
>  
>      /* KVM steal time */
>      OnOffAuto kvm_steal_time;
> +
> +    /* KVM PMU Filter */
> +    char *kvm_pmu_filter;
>  #endif /* CONFIG_KVM */
>  
>      /* Uniprocessor system with MP extensions */
> diff --git a/target/arm/kvm.c b/target/arm/kvm.c
> index 81813030a5..5c62580d34 100644
> --- a/target/arm/kvm.c
> +++ b/target/arm/kvm.c
> @@ -496,6 +496,22 @@ static void kvm_steal_time_set(Object *obj, bool value, Error **errp)
>      ARM_CPU(obj)->kvm_steal_time = value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF;
>  }
>  
> +static char *kvm_pmu_filter_get(Object *obj, Error **errp)
> +{
> +    ARMCPU *cpu = ARM_CPU(obj);
> +
> +    return g_strdup(cpu->kvm_pmu_filter);
> +}
> +
> +static void kvm_pmu_filter_set(Object *obj, const char *pmu_filter,
> +                               Error **errp)
> +{
> +    ARMCPU *cpu = ARM_CPU(obj);
> +
> +    g_free(cpu->kvm_pmu_filter);
> +    cpu->kvm_pmu_filter = g_strdup(pmu_filter);
> +}
> +
>  /* KVM VCPU properties should be prefixed with "kvm-". */
>  void kvm_arm_add_vcpu_properties(ARMCPU *cpu)
>  {
> @@ -517,6 +533,12 @@ void kvm_arm_add_vcpu_properties(ARMCPU *cpu)
>                               kvm_steal_time_set);
>      object_property_set_description(obj, "kvm-steal-time",
>                                      "Set off to disable KVM steal time.");
> +
> +    object_property_add_str(obj, "kvm-pmu-filter", kvm_pmu_filter_get,
> +                            kvm_pmu_filter_set);
> +    object_property_set_description(obj, "kvm-pmu-filter",
> +                                    "PMU Event Filtering description for "
> +                                    "guest PMU. (default: NULL, disabled)");
>  }
>  
>  bool kvm_arm_pmu_supported(void)
> @@ -1706,6 +1728,62 @@ static bool kvm_arm_set_device_attr(ARMCPU *cpu, struct kvm_device_attr *attr,
>      return true;
>  }
>  
> +static void kvm_arm_pmu_filter_init(ARMCPU *cpu)
> +{
> +    static bool pmu_filter_init;
> +    struct kvm_pmu_event_filter filter;
> +    struct kvm_device_attr attr = {
> +        .group      = KVM_ARM_VCPU_PMU_V3_CTRL,
> +        .attr       = KVM_ARM_VCPU_PMU_V3_FILTER,
> +        .addr       = (uint64_t)&filter,
> +    };
> +    int i;
> +    g_auto(GStrv) event_filters;
> +
> +    if (!cpu->kvm_pmu_filter) {
> +        return;
> +    }
> +    if (kvm_vcpu_ioctl(CPU(cpu), KVM_HAS_DEVICE_ATTR, &attr)) {
> +        warn_report("The KVM doesn't support the PMU Event Filter!");
> +        return;
> +    }
> +
> +    /*
> +     * The filter only needs to be initialized through one vcpu ioctl and it
> +     * will affect all other vcpu in the vm.
> +     */
> +    if (pmu_filter_init) {
> +        return;
> +    } else {
> +        pmu_filter_init = true;
> +    }
> +
> +    event_filters = g_strsplit(cpu->kvm_pmu_filter, ";", -1);
> +    for (i = 0; event_filters[i]; i++) {
> +        unsigned short start = 0, end = 0;
> +        char act;
> +
> +        if (sscanf(event_filters[i], "%c:%hx-%hx", &act, &start, &end) != 3) {
> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
> +            continue;
> +        }
> +
> +        if ((act != 'A' && act != 'D') || start > end) {
> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
> +            continue;
> +        }
> +
> +        filter.base_event = start;
> +        filter.nevents = end - start + 1;
> +        filter.action = (act == 'A') ? KVM_PMU_EVENT_ALLOW :
> +                                       KVM_PMU_EVENT_DENY;
> +
> +        if (!kvm_arm_set_device_attr(cpu, &attr, "PMU_V3_FILTER")) {
> +            break;
> +        }
> +    }
> +}
> +
>  void kvm_arm_pmu_init(ARMCPU *cpu)
>  {
>      struct kvm_device_attr attr = {
> @@ -1716,6 +1794,8 @@ void kvm_arm_pmu_init(ARMCPU *cpu)
>      if (!cpu->has_pmu) {
>          return;
>      }
> +
> +    kvm_arm_pmu_filter_init(cpu);
>      if (!kvm_arm_set_device_attr(cpu, &attr, "PMU")) {
>          error_report("failed to init PMU");
>          abort();
> 
> base-commit: 760b4dcdddba4a40b9fa0eb78fdfc7eda7cb83d0
Peter Maydell Feb. 22, 2024, 2:28 p.m. UTC | #2
On Wed, 21 Feb 2024 at 06:34, Shaoqin Huang <shahuang@redhat.com> wrote:
>
> The KVM_ARM_VCPU_PMU_V3_FILTER provides the ability to let the VMM decide
> which PMU events are provided to the guest. Add a new option
> `kvm-pmu-filter` as -cpu sub-option to set the PMU Event Filtering.
> Without the filter, all PMU events are exposed from host to guest by
> default. The usage of the new sub-option can be found from the updated
> document (docs/system/arm/cpu-features.rst).
>
> Here is an example which shows how to use the PMU Event Filtering, when
> we launch a guest by use kvm, add such command line:
>
>   # qemu-system-aarch64 \
>         -accel kvm \
>         -cpu host,kvm-pmu-filter="D:0x11-0x11"
>
> Since the first action is deny, we have a global allow policy. This
> filters out the cycle counter (event 0x11 being CPU_CYCLES).
>
> And then in guest, use the perf to count the cycle:
>
>   # perf stat sleep 1
>
>    Performance counter stats for 'sleep 1':
>
>               1.22 msec task-clock                       #    0.001 CPUs utilized
>                  1      context-switches                 #  820.695 /sec
>                  0      cpu-migrations                   #    0.000 /sec
>                 55      page-faults                      #   45.138 K/sec
>    <not supported>      cycles
>            1128954      instructions
>             227031      branches                         #  186.323 M/sec
>               8686      branch-misses                    #    3.83% of all branches
>
>        1.002492480 seconds time elapsed
>
>        0.001752000 seconds user
>        0.000000000 seconds sys
>
> As we can see, the cycle counter has been disabled in the guest, but
> other pmu events do still work.
>
> Reviewed-by: Sebastian Ott <sebott@redhat.com>
> Signed-off-by: Shaoqin Huang <shahuang@redhat.com>
> ---
> v6->v7:
>   - Check return value of sscanf.
>   - Improve the check condition.
>
> v5->v6:
>   - Commit message improvement.
>   - Remove some unused code.
>   - Collect Reviewed-by, thanks Sebastian.
>   - Use g_auto(Gstrv) to replace the gchar **.          [Eric]
>
> v4->v5:
>   - Change the kvm-pmu-filter as a -cpu sub-option.     [Eric]
>   - Comment tweak.                                      [Gavin]
>   - Rebase to the latest branch.
>
> v3->v4:
>   - Fix the wrong check for pmu_filter_init.            [Sebastian]
>   - Fix multiple alignment issue.                       [Gavin]
>   - Report error by warn_report() instead of error_report(), and don't use
>   abort() since the PMU Event Filter is an add-on and best-effort feature.
>                                                         [Gavin]
>   - Add several missing {  } for single line of code.   [Gavin]
>   - Use the g_strsplit() to replace strtok().           [Gavin]
>
> v2->v3:
>   - Improve commits message, use kernel doc wording, add more explaination on
>     filter example, fix some typo error.                [Eric]
>   - Add g_free() in kvm_arch_set_pmu_filter() to prevent memory leak. [Eric]
>   - Add more precise error message report.              [Eric]
>   - In options doc, add pmu-filter rely on KVM_ARM_VCPU_PMU_V3_FILTER support in
>     KVM.                                                [Eric]
>
> v1->v2:
>   - Add more description for allow and deny meaning in
>     commit message.                                     [Sebastian]
>   - Small improvement.                                  [Sebastian]
>
>  docs/system/arm/cpu-features.rst | 23 +++++++++
>  target/arm/cpu.h                 |  3 ++
>  target/arm/kvm.c                 | 80 ++++++++++++++++++++++++++++++++
>  3 files changed, 106 insertions(+)

The new syntax for the filter property seems quite complicated.
I think it would be worth testing it with a new test in
tests/qtest/arm-cpu-features.c.


> diff --git a/docs/system/arm/cpu-features.rst b/docs/system/arm/cpu-features.rst
> index a5fb929243..7c8f6a60ef 100644
> --- a/docs/system/arm/cpu-features.rst
> +++ b/docs/system/arm/cpu-features.rst
> @@ -204,6 +204,29 @@ the list of KVM VCPU features and their descriptions.
>    the guest scheduler behavior and/or be exposed to the guest
>    userspace.
>
> +``kvm-pmu-filter``
> +  By default kvm-pmu-filter is disabled. This means that by default all pmu

"PMU"

> +  events will be exposed to guest.
> +
> +  KVM implements PMU Event Filtering to prevent a guest from being able to
> +  sample certain events. It depends on the KVM_ARM_VCPU_PMU_V3_FILTER
> +  attribute supported in KVM. It has the following format:
> +
> +  kvm-pmu-filter="{A,D}:start-end[;{A,D}:start-end...]"
> +
> +  The A means "allow" and D means "deny", start is the first event of the
> +  range and the end is the last one. The first registered range defines
> +  the global policy(global ALLOW if the first @action is DENY, global DENY

Missing space before '('.

Why the '@' before 'action'?

> +  if the first @action is ALLOW). The start and end only support hexadecimal
> +  format. For example:
> +
> +  kvm-pmu-filter="A:0x11-0x11;A:0x23-0x3a;D:0x30-0x30"
> +
> +  Since the first action is allow, we have a global deny policy. It
> +  will allow event 0x11 (The cycle counter), events 0x23 to 0x3a are

lowercase "the".

> +  also allowed except the event 0x30 which is denied, and all the other
> +  events are denied.
> +

Did you check that the documentation builds and that this new
documentation renders into HTML the way you want it?

>  TCG VCPU Features
>  =================
>
> diff --git a/target/arm/cpu.h b/target/arm/cpu.h
> index 63f31e0d98..f7f2431755 100644
> --- a/target/arm/cpu.h
> +++ b/target/arm/cpu.h
> @@ -948,6 +948,9 @@ struct ArchCPU {
>
>      /* KVM steal time */
>      OnOffAuto kvm_steal_time;
> +
> +    /* KVM PMU Filter */
> +    char *kvm_pmu_filter;
>  #endif /* CONFIG_KVM */
>
>      /* Uniprocessor system with MP extensions */
> diff --git a/target/arm/kvm.c b/target/arm/kvm.c
> index 81813030a5..5c62580d34 100644
> --- a/target/arm/kvm.c
> +++ b/target/arm/kvm.c
> @@ -496,6 +496,22 @@ static void kvm_steal_time_set(Object *obj, bool value, Error **errp)
>      ARM_CPU(obj)->kvm_steal_time = value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF;
>  }
>
> +static char *kvm_pmu_filter_get(Object *obj, Error **errp)
> +{
> +    ARMCPU *cpu = ARM_CPU(obj);
> +
> +    return g_strdup(cpu->kvm_pmu_filter);
> +}
> +
> +static void kvm_pmu_filter_set(Object *obj, const char *pmu_filter,
> +                               Error **errp)
> +{
> +    ARMCPU *cpu = ARM_CPU(obj);
> +
> +    g_free(cpu->kvm_pmu_filter);
> +    cpu->kvm_pmu_filter = g_strdup(pmu_filter);
> +}
> +
>  /* KVM VCPU properties should be prefixed with "kvm-". */
>  void kvm_arm_add_vcpu_properties(ARMCPU *cpu)
>  {
> @@ -517,6 +533,12 @@ void kvm_arm_add_vcpu_properties(ARMCPU *cpu)
>                               kvm_steal_time_set);
>      object_property_set_description(obj, "kvm-steal-time",
>                                      "Set off to disable KVM steal time.");
> +
> +    object_property_add_str(obj, "kvm-pmu-filter", kvm_pmu_filter_get,
> +                            kvm_pmu_filter_set);
> +    object_property_set_description(obj, "kvm-pmu-filter",
> +                                    "PMU Event Filtering description for "
> +                                    "guest PMU. (default: NULL, disabled)");
>  }
>
>  bool kvm_arm_pmu_supported(void)
> @@ -1706,6 +1728,62 @@ static bool kvm_arm_set_device_attr(ARMCPU *cpu, struct kvm_device_attr *attr,
>      return true;
>  }
>
> +static void kvm_arm_pmu_filter_init(ARMCPU *cpu)
> +{
> +    static bool pmu_filter_init;
> +    struct kvm_pmu_event_filter filter;
> +    struct kvm_device_attr attr = {
> +        .group      = KVM_ARM_VCPU_PMU_V3_CTRL,
> +        .attr       = KVM_ARM_VCPU_PMU_V3_FILTER,
> +        .addr       = (uint64_t)&filter,
> +    };
> +    int i;
> +    g_auto(GStrv) event_filters;
> +
> +    if (!cpu->kvm_pmu_filter) {
> +        return;
> +    }
> +    if (kvm_vcpu_ioctl(CPU(cpu), KVM_HAS_DEVICE_ATTR, &attr)) {
> +        warn_report("The KVM doesn't support the PMU Event Filter!");

Drop "The ".

Should this really only be a warning, rather than an error?

> +        return;
> +    }
> +
> +    /*
> +     * The filter only needs to be initialized through one vcpu ioctl and it
> +     * will affect all other vcpu in the vm.

Weird. Why isn't it a VM ioctl if it affects the whole VM ?

> +     */
> +    if (pmu_filter_init) {
> +        return;
> +    } else {
> +        pmu_filter_init = true;
> +    }

Shouldn't we do this before we do the kvm_vcpu_ioctl check
for whether the kernel supports the filter? Otherwise presumably
we'll print the warning once per vCPU, rather than only once.

> +
> +    event_filters = g_strsplit(cpu->kvm_pmu_filter, ";", -1);
> +    for (i = 0; event_filters[i]; i++) {
> +        unsigned short start = 0, end = 0;
> +        char act;
> +
> +        if (sscanf(event_filters[i], "%c:%hx-%hx", &act, &start, &end) != 3) {
> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
> +            continue;
> +        }
> +
> +        if ((act != 'A' && act != 'D') || start > end) {
> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
> +            continue;
> +        }

It would be better to do the syntax checking up-front when
the user tries to set the property. Then you can make the
property-setting return an error for invalid strings.

> +
> +        filter.base_event = start;
> +        filter.nevents = end - start + 1;
> +        filter.action = (act == 'A') ? KVM_PMU_EVENT_ALLOW :
> +                                       KVM_PMU_EVENT_DENY;
> +
> +        if (!kvm_arm_set_device_attr(cpu, &attr, "PMU_V3_FILTER")) {

Shouldn't we arrange for an error message if this fails?

> +            break;
> +        }
> +    }
> +}
> +
>  void kvm_arm_pmu_init(ARMCPU *cpu)
>  {
>      struct kvm_device_attr attr = {
> @@ -1716,6 +1794,8 @@ void kvm_arm_pmu_init(ARMCPU *cpu)
>      if (!cpu->has_pmu) {
>          return;
>      }
> +
> +    kvm_arm_pmu_filter_init(cpu);
>      if (!kvm_arm_set_device_attr(cpu, &attr, "PMU")) {
>          error_report("failed to init PMU");
>          abort();
>
> base-commit: 760b4dcdddba4a40b9fa0eb78fdfc7eda7cb83d0
> --
> 2.40.1

thanks
-- PMM
Shaoqin Huang Feb. 29, 2024, 2:32 a.m. UTC | #3
Hi Peter,

On 2/22/24 22:28, Peter Maydell wrote:
> On Wed, 21 Feb 2024 at 06:34, Shaoqin Huang <shahuang@redhat.com> wrote:
>>
>> The KVM_ARM_VCPU_PMU_V3_FILTER provides the ability to let the VMM decide
>> which PMU events are provided to the guest. Add a new option
>> `kvm-pmu-filter` as -cpu sub-option to set the PMU Event Filtering.
>> Without the filter, all PMU events are exposed from host to guest by
>> default. The usage of the new sub-option can be found from the updated
>> document (docs/system/arm/cpu-features.rst).
>>
>> Here is an example which shows how to use the PMU Event Filtering, when
>> we launch a guest by use kvm, add such command line:
>>
>>    # qemu-system-aarch64 \
>>          -accel kvm \
>>          -cpu host,kvm-pmu-filter="D:0x11-0x11"
>>
>> Since the first action is deny, we have a global allow policy. This
>> filters out the cycle counter (event 0x11 being CPU_CYCLES).
>>
>> And then in guest, use the perf to count the cycle:
>>
>>    # perf stat sleep 1
>>
>>     Performance counter stats for 'sleep 1':
>>
>>                1.22 msec task-clock                       #    0.001 CPUs utilized
>>                   1      context-switches                 #  820.695 /sec
>>                   0      cpu-migrations                   #    0.000 /sec
>>                  55      page-faults                      #   45.138 K/sec
>>     <not supported>      cycles
>>             1128954      instructions
>>              227031      branches                         #  186.323 M/sec
>>                8686      branch-misses                    #    3.83% of all branches
>>
>>         1.002492480 seconds time elapsed
>>
>>         0.001752000 seconds user
>>         0.000000000 seconds sys
>>
>> As we can see, the cycle counter has been disabled in the guest, but
>> other pmu events do still work.
>>
>> Reviewed-by: Sebastian Ott <sebott@redhat.com>
>> Signed-off-by: Shaoqin Huang <shahuang@redhat.com>
>> ---
>> v6->v7:
>>    - Check return value of sscanf.
>>    - Improve the check condition.
>>
>> v5->v6:
>>    - Commit message improvement.
>>    - Remove some unused code.
>>    - Collect Reviewed-by, thanks Sebastian.
>>    - Use g_auto(Gstrv) to replace the gchar **.          [Eric]
>>
>> v4->v5:
>>    - Change the kvm-pmu-filter as a -cpu sub-option.     [Eric]
>>    - Comment tweak.                                      [Gavin]
>>    - Rebase to the latest branch.
>>
>> v3->v4:
>>    - Fix the wrong check for pmu_filter_init.            [Sebastian]
>>    - Fix multiple alignment issue.                       [Gavin]
>>    - Report error by warn_report() instead of error_report(), and don't use
>>    abort() since the PMU Event Filter is an add-on and best-effort feature.
>>                                                          [Gavin]
>>    - Add several missing {  } for single line of code.   [Gavin]
>>    - Use the g_strsplit() to replace strtok().           [Gavin]
>>
>> v2->v3:
>>    - Improve commits message, use kernel doc wording, add more explaination on
>>      filter example, fix some typo error.                [Eric]
>>    - Add g_free() in kvm_arch_set_pmu_filter() to prevent memory leak. [Eric]
>>    - Add more precise error message report.              [Eric]
>>    - In options doc, add pmu-filter rely on KVM_ARM_VCPU_PMU_V3_FILTER support in
>>      KVM.                                                [Eric]
>>
>> v1->v2:
>>    - Add more description for allow and deny meaning in
>>      commit message.                                     [Sebastian]
>>    - Small improvement.                                  [Sebastian]
>>
>>   docs/system/arm/cpu-features.rst | 23 +++++++++
>>   target/arm/cpu.h                 |  3 ++
>>   target/arm/kvm.c                 | 80 ++++++++++++++++++++++++++++++++
>>   3 files changed, 106 insertions(+)
> 
> The new syntax for the filter property seems quite complicated.
> I think it would be worth testing it with a new test in
> tests/qtest/arm-cpu-features.c.

I was trying to add a test in tests/qtest/arm-cpu-features.c. But I 
found all other cpu-feature is bool property.

When I use the 'query-cpu-model-expansion' to query the cpu-features, 
the kvm-pmu-filter will not shown in the returned results, just like below.

{'execute': 'query-cpu-model-expansion', 'arguments': {'type': 'full', 
'model': { 'name': 'host'}}}{"return": {}}

{"return": {"model": {"name": "host", "props": {"sve768": false, 
"sve128": false, "sve1024": false, "sve1280": false, "sve896": false, 
"sve256": false, "sve1536": false, "sve1792": false, "sve384": false, 
"sve": false, "sve2048": false, "pauth": false, "kvm-no-adjvtime": 
false, "sve512": false, "aarch64": true, "pmu": true, "sve1920": false, 
"sve1152": false, "kvm-steal-time": true, "sve640": false, "sve1408": 
false, "sve1664": false}}}}

I'm not sure if it's because the `query-cpu-model-expansion` only return 
the feature which is bool. Since the kvm-pmu-filter is a str, it won't 
be recognized as a feature.

So I want to ask how can I add the kvm-pmu-filter which is str property 
into the cpu-feature.c test.

> 
> 
>> diff --git a/docs/system/arm/cpu-features.rst b/docs/system/arm/cpu-features.rst
>> index a5fb929243..7c8f6a60ef 100644
>> --- a/docs/system/arm/cpu-features.rst
>> +++ b/docs/system/arm/cpu-features.rst
>> @@ -204,6 +204,29 @@ the list of KVM VCPU features and their descriptions.
>>     the guest scheduler behavior and/or be exposed to the guest
>>     userspace.
>>
>> +``kvm-pmu-filter``
>> +  By default kvm-pmu-filter is disabled. This means that by default all pmu
> 
> "PMU"
> 

Got it.

>> +  events will be exposed to guest.
>> +
>> +  KVM implements PMU Event Filtering to prevent a guest from being able to
>> +  sample certain events. It depends on the KVM_ARM_VCPU_PMU_V3_FILTER
>> +  attribute supported in KVM. It has the following format:
>> +
>> +  kvm-pmu-filter="{A,D}:start-end[;{A,D}:start-end...]"
>> +
>> +  The A means "allow" and D means "deny", start is the first event of the
>> +  range and the end is the last one. The first registered range defines
>> +  the global policy(global ALLOW if the first @action is DENY, global DENY
> 
> Missing space before '('.
> 
> Why the '@' before 'action'?
> 

I copied the description from kvm document. And it has the @ before 
action, I think I can delete @ at there.

>> +  if the first @action is ALLOW). The start and end only support hexadecimal
>> +  format. For example:
>> +
>> +  kvm-pmu-filter="A:0x11-0x11;A:0x23-0x3a;D:0x30-0x30"
>> +
>> +  Since the first action is allow, we have a global deny policy. It
>> +  will allow event 0x11 (The cycle counter), events 0x23 to 0x3a are
> 
> lowercase "the".
> 

Gotta.

>> +  also allowed except the event 0x30 which is denied, and all the other
>> +  events are denied.
>> +
> 
> Did you check that the documentation builds and that this new
> documentation renders into HTML the way you want it?
> 

I can double check it.

>>   TCG VCPU Features
>>   =================
>>
>> diff --git a/target/arm/cpu.h b/target/arm/cpu.h
>> index 63f31e0d98..f7f2431755 100644
>> --- a/target/arm/cpu.h
>> +++ b/target/arm/cpu.h
>> @@ -948,6 +948,9 @@ struct ArchCPU {
>>
>>       /* KVM steal time */
>>       OnOffAuto kvm_steal_time;
>> +
>> +    /* KVM PMU Filter */
>> +    char *kvm_pmu_filter;
>>   #endif /* CONFIG_KVM */
>>
>>       /* Uniprocessor system with MP extensions */
>> diff --git a/target/arm/kvm.c b/target/arm/kvm.c
>> index 81813030a5..5c62580d34 100644
>> --- a/target/arm/kvm.c
>> +++ b/target/arm/kvm.c
>> @@ -496,6 +496,22 @@ static void kvm_steal_time_set(Object *obj, bool value, Error **errp)
>>       ARM_CPU(obj)->kvm_steal_time = value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF;
>>   }
>>
>> +static char *kvm_pmu_filter_get(Object *obj, Error **errp)
>> +{
>> +    ARMCPU *cpu = ARM_CPU(obj);
>> +
>> +    return g_strdup(cpu->kvm_pmu_filter);
>> +}
>> +
>> +static void kvm_pmu_filter_set(Object *obj, const char *pmu_filter,
>> +                               Error **errp)
>> +{
>> +    ARMCPU *cpu = ARM_CPU(obj);
>> +
>> +    g_free(cpu->kvm_pmu_filter);
>> +    cpu->kvm_pmu_filter = g_strdup(pmu_filter);
>> +}
>> +
>>   /* KVM VCPU properties should be prefixed with "kvm-". */
>>   void kvm_arm_add_vcpu_properties(ARMCPU *cpu)
>>   {
>> @@ -517,6 +533,12 @@ void kvm_arm_add_vcpu_properties(ARMCPU *cpu)
>>                                kvm_steal_time_set);
>>       object_property_set_description(obj, "kvm-steal-time",
>>                                       "Set off to disable KVM steal time.");
>> +
>> +    object_property_add_str(obj, "kvm-pmu-filter", kvm_pmu_filter_get,
>> +                            kvm_pmu_filter_set);
>> +    object_property_set_description(obj, "kvm-pmu-filter",
>> +                                    "PMU Event Filtering description for "
>> +                                    "guest PMU. (default: NULL, disabled)");
>>   }
>>
>>   bool kvm_arm_pmu_supported(void)
>> @@ -1706,6 +1728,62 @@ static bool kvm_arm_set_device_attr(ARMCPU *cpu, struct kvm_device_attr *attr,
>>       return true;
>>   }
>>
>> +static void kvm_arm_pmu_filter_init(ARMCPU *cpu)
>> +{
>> +    static bool pmu_filter_init;
>> +    struct kvm_pmu_event_filter filter;
>> +    struct kvm_device_attr attr = {
>> +        .group      = KVM_ARM_VCPU_PMU_V3_CTRL,
>> +        .attr       = KVM_ARM_VCPU_PMU_V3_FILTER,
>> +        .addr       = (uint64_t)&filter,
>> +    };
>> +    int i;
>> +    g_auto(GStrv) event_filters;
>> +
>> +    if (!cpu->kvm_pmu_filter) {
>> +        return;
>> +    }
>> +    if (kvm_vcpu_ioctl(CPU(cpu), KVM_HAS_DEVICE_ATTR, &attr)) {
>> +        warn_report("The KVM doesn't support the PMU Event Filter!");
> 
> Drop "The ".
> 
> Should this really only be a warning, rather than an error?
> 

I think this is an add-on feature, and shouldn't block the qemu init 
process. If we want to set the wrong pmu filter and it doesn't take 
affect to the VM, it can be detected in the VM.

>> +        return;
>> +    }
>> +
>> +    /*
>> +     * The filter only needs to be initialized through one vcpu ioctl and it
>> +     * will affect all other vcpu in the vm.
> 
> Weird. Why isn't it a VM ioctl if it affects the whole VM ?
> 
 From (kernel) commit d7eec2360e3 ("KVM: arm64: Add PMU event filtering
infrastructure"):
   Note that although the ioctl is per-vcpu, the map of allowed events is
   global to the VM (it can be setup from any vcpu until the vcpu PMU is
   initialized).

>> +     */
>> +    if (pmu_filter_init) {
>> +        return;
>> +    } else {
>> +        pmu_filter_init = true;
>> +    }
> 
> Shouldn't we do this before we do the kvm_vcpu_ioctl check
> for whether the kernel supports the filter? Otherwise presumably
> we'll print the warning once per vCPU, rather than only once.
> 

Yes. I will move this to the beginning of the function.

>> +
>> +    event_filters = g_strsplit(cpu->kvm_pmu_filter, ";", -1);
>> +    for (i = 0; event_filters[i]; i++) {
>> +        unsigned short start = 0, end = 0;
>> +        char act;
>> +
>> +        if (sscanf(event_filters[i], "%c:%hx-%hx", &act, &start, &end) != 3) {
>> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
>> +            continue;
>> +        }
>> +
>> +        if ((act != 'A' && act != 'D') || start > end) {
>> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
>> +            continue;
>> +        }
> 
> It would be better to do the syntax checking up-front when
> the user tries to set the property. Then you can make the
> property-setting return an error for invalid strings.
> 

Ok. I guess you mean to say move the syntax checking to the 
kvm_pmu_filter_set() function. But wouldn't it cause some code 
duplication? Since it should first check syntax of the string in 
kvm_pmu_filter_set() and then parse the string in this function.

>> +
>> +        filter.base_event = start;
>> +        filter.nevents = end - start + 1;
>> +        filter.action = (act == 'A') ? KVM_PMU_EVENT_ALLOW :
>> +                                       KVM_PMU_EVENT_DENY;
>> +
>> +        if (!kvm_arm_set_device_attr(cpu, &attr, "PMU_V3_FILTER")) {
> 
> Shouldn't we arrange for an error message if this fails?
> 

If it fails, the kvm_arm_set_device_attr() function would help us to 
report the error. So I think there is no need to report the error again.

Thanks,
Shaoqin

>> +            break;
>> +        }
>> +    }
>> +}
>> +
>>   void kvm_arm_pmu_init(ARMCPU *cpu)
>>   {
>>       struct kvm_device_attr attr = {
>> @@ -1716,6 +1794,8 @@ void kvm_arm_pmu_init(ARMCPU *cpu)
>>       if (!cpu->has_pmu) {
>>           return;
>>       }
>> +
>> +    kvm_arm_pmu_filter_init(cpu);
>>       if (!kvm_arm_set_device_attr(cpu, &attr, "PMU")) {
>>           error_report("failed to init PMU");
>>           abort();
>>
>> base-commit: 760b4dcdddba4a40b9fa0eb78fdfc7eda7cb83d0
>> --
>> 2.40.1
> 
> thanks
> -- PMM
>
Peter Maydell Feb. 29, 2024, 11 a.m. UTC | #4
On Thu, 29 Feb 2024 at 02:32, Shaoqin Huang <shahuang@redhat.com> wrote:
>
> Hi Peter,
>
> On 2/22/24 22:28, Peter Maydell wrote:
> > On Wed, 21 Feb 2024 at 06:34, Shaoqin Huang <shahuang@redhat.com> wrote:
> >>
> >> The KVM_ARM_VCPU_PMU_V3_FILTER provides the ability to let the VMM decide
> >> which PMU events are provided to the guest. Add a new option
> >> `kvm-pmu-filter` as -cpu sub-option to set the PMU Event Filtering.
> >> Without the filter, all PMU events are exposed from host to guest by
> >> default. The usage of the new sub-option can be found from the updated
> >> document (docs/system/arm/cpu-features.rst).
> >>
> >> Here is an example which shows how to use the PMU Event Filtering, when
> >> we launch a guest by use kvm, add such command line:
> >>
> >>    # qemu-system-aarch64 \
> >>          -accel kvm \
> >>          -cpu host,kvm-pmu-filter="D:0x11-0x11"
> >>
> >> Since the first action is deny, we have a global allow policy. This
> >> filters out the cycle counter (event 0x11 being CPU_CYCLES).
> >>
> >> And then in guest, use the perf to count the cycle:
> >>
> >>    # perf stat sleep 1
> >>
> >>     Performance counter stats for 'sleep 1':
> >>
> >>                1.22 msec task-clock                       #    0.001 CPUs utilized
> >>                   1      context-switches                 #  820.695 /sec
> >>                   0      cpu-migrations                   #    0.000 /sec
> >>                  55      page-faults                      #   45.138 K/sec
> >>     <not supported>      cycles
> >>             1128954      instructions
> >>              227031      branches                         #  186.323 M/sec
> >>                8686      branch-misses                    #    3.83% of all branches
> >>
> >>         1.002492480 seconds time elapsed
> >>
> >>         0.001752000 seconds user
> >>         0.000000000 seconds sys
> >>
> >> As we can see, the cycle counter has been disabled in the guest, but
> >> other pmu events do still work.

> >
> > The new syntax for the filter property seems quite complicated.
> > I think it would be worth testing it with a new test in
> > tests/qtest/arm-cpu-features.c.
>
> I was trying to add a test in tests/qtest/arm-cpu-features.c. But I
> found all other cpu-feature is bool property.
>
> When I use the 'query-cpu-model-expansion' to query the cpu-features,
> the kvm-pmu-filter will not shown in the returned results, just like below.
>
> {'execute': 'query-cpu-model-expansion', 'arguments': {'type': 'full',
> 'model': { 'name': 'host'}}}{"return": {}}
>
> {"return": {"model": {"name": "host", "props": {"sve768": false,
> "sve128": false, "sve1024": false, "sve1280": false, "sve896": false,
> "sve256": false, "sve1536": false, "sve1792": false, "sve384": false,
> "sve": false, "sve2048": false, "pauth": false, "kvm-no-adjvtime":
> false, "sve512": false, "aarch64": true, "pmu": true, "sve1920": false,
> "sve1152": false, "kvm-steal-time": true, "sve640": false, "sve1408":
> false, "sve1664": false}}}}
>
> I'm not sure if it's because the `query-cpu-model-expansion` only return
> the feature which is bool. Since the kvm-pmu-filter is a str, it won't
> be recognized as a feature.
>
> So I want to ask how can I add the kvm-pmu-filter which is str property
> into the cpu-feature.c test.

It doesn't appear because the list of properties that we advertise
via query-cpu-model-expansion is set in the cpu_model_advertised_features[]
array in target/arm/arm-qmp-cmds.c, and this patch doesn't add
'kvm-pmu-filter' to it. But you have a good point about all the
others being bool properties: I don't know enough about that
mechanism to know if simply adding this to the list is right.

This does raise a more general question: do we need to advertise
the existence of this property to libvirt via QMP? Eric, Sebastian:
do you know ?

If we don't care about this being visible to libvirt then the
importance of having a test case covering the command line
syntax goes down a bit.

> >>
> >> +static void kvm_arm_pmu_filter_init(ARMCPU *cpu)
> >> +{
> >> +    static bool pmu_filter_init;
> >> +    struct kvm_pmu_event_filter filter;
> >> +    struct kvm_device_attr attr = {
> >> +        .group      = KVM_ARM_VCPU_PMU_V3_CTRL,
> >> +        .attr       = KVM_ARM_VCPU_PMU_V3_FILTER,
> >> +        .addr       = (uint64_t)&filter,
> >> +    };
> >> +    int i;
> >> +    g_auto(GStrv) event_filters;
> >> +
> >> +    if (!cpu->kvm_pmu_filter) {
> >> +        return;
> >> +    }
> >> +    if (kvm_vcpu_ioctl(CPU(cpu), KVM_HAS_DEVICE_ATTR, &attr)) {
> >> +        warn_report("The KVM doesn't support the PMU Event Filter!");
> >
> > Drop "The ".
> >
> > Should this really only be a warning, rather than an error?
> >
>
> I think this is an add-on feature, and shouldn't block the qemu init
> process. If we want to set the wrong pmu filter and it doesn't take
> affect to the VM, it can be detected in the VM.

But if the user explicitly asked for it, it's not optional
for them, it's something they want. We should fail if the user
passes us an option that we can't actually carry out.

> >> +        return;
> >> +    }
> >> +
> >> +    /*
> >> +     * The filter only needs to be initialized through one vcpu ioctl and it
> >> +     * will affect all other vcpu in the vm.
> >
> > Weird. Why isn't it a VM ioctl if it affects the whole VM ?
> >
>  From (kernel) commit d7eec2360e3 ("KVM: arm64: Add PMU event filtering
> infrastructure"):
>    Note that although the ioctl is per-vcpu, the map of allowed events is
>    global to the VM (it can be setup from any vcpu until the vcpu PMU is
>    initialized).

That just says it is a per-vcpu ioctl, it doesn't say why...

> >> +    event_filters = g_strsplit(cpu->kvm_pmu_filter, ";", -1);
> >> +    for (i = 0; event_filters[i]; i++) {
> >> +        unsigned short start = 0, end = 0;
> >> +        char act;
> >> +
> >> +        if (sscanf(event_filters[i], "%c:%hx-%hx", &act, &start, &end) != 3) {
> >> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
> >> +            continue;
> >> +        }
> >> +
> >> +        if ((act != 'A' && act != 'D') || start > end) {
> >> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
> >> +            continue;
> >> +        }
> >
> > It would be better to do the syntax checking up-front when
> > the user tries to set the property. Then you can make the
> > property-setting return an error for invalid strings.
> >
>
> Ok. I guess you mean to say move the syntax checking to the
> kvm_pmu_filter_set() function. But wouldn't it cause some code
> duplication? Since it should first check syntax of the string in
> kvm_pmu_filter_set() and then parse the string in this function.

No, you should check syntax and parse the string in
kvm_pmu_filter_set(), and fill in a data structure so you don't
have to do any string parsing here. kvm_pmu_filter_get()
will need to reconstitute a string from the data structure.

thanks
-- PMM
Eric Auger March 19, 2024, 2:57 p.m. UTC | #5
Hi Peter,

On 2/29/24 12:00, Peter Maydell wrote:
> On Thu, 29 Feb 2024 at 02:32, Shaoqin Huang <shahuang@redhat.com> wrote:
>>
>> Hi Peter,
>>
>> On 2/22/24 22:28, Peter Maydell wrote:
>>> On Wed, 21 Feb 2024 at 06:34, Shaoqin Huang <shahuang@redhat.com> wrote:
>>>>
>>>> The KVM_ARM_VCPU_PMU_V3_FILTER provides the ability to let the VMM decide
>>>> which PMU events are provided to the guest. Add a new option
>>>> `kvm-pmu-filter` as -cpu sub-option to set the PMU Event Filtering.
>>>> Without the filter, all PMU events are exposed from host to guest by
>>>> default. The usage of the new sub-option can be found from the updated
>>>> document (docs/system/arm/cpu-features.rst).
>>>>
>>>> Here is an example which shows how to use the PMU Event Filtering, when
>>>> we launch a guest by use kvm, add such command line:
>>>>
>>>>    # qemu-system-aarch64 \
>>>>          -accel kvm \
>>>>          -cpu host,kvm-pmu-filter="D:0x11-0x11"
>>>>
>>>> Since the first action is deny, we have a global allow policy. This
>>>> filters out the cycle counter (event 0x11 being CPU_CYCLES).
>>>>
>>>> And then in guest, use the perf to count the cycle:
>>>>
>>>>    # perf stat sleep 1
>>>>
>>>>     Performance counter stats for 'sleep 1':
>>>>
>>>>                1.22 msec task-clock                       #    0.001 CPUs utilized
>>>>                   1      context-switches                 #  820.695 /sec
>>>>                   0      cpu-migrations                   #    0.000 /sec
>>>>                  55      page-faults                      #   45.138 K/sec
>>>>     <not supported>      cycles
>>>>             1128954      instructions
>>>>              227031      branches                         #  186.323 M/sec
>>>>                8686      branch-misses                    #    3.83% of all branches
>>>>
>>>>         1.002492480 seconds time elapsed
>>>>
>>>>         0.001752000 seconds user
>>>>         0.000000000 seconds sys
>>>>
>>>> As we can see, the cycle counter has been disabled in the guest, but
>>>> other pmu events do still work.
> 
>>>
>>> The new syntax for the filter property seems quite complicated.
>>> I think it would be worth testing it with a new test in
>>> tests/qtest/arm-cpu-features.c.
>>
>> I was trying to add a test in tests/qtest/arm-cpu-features.c. But I
>> found all other cpu-feature is bool property.
>>
>> When I use the 'query-cpu-model-expansion' to query the cpu-features,
>> the kvm-pmu-filter will not shown in the returned results, just like below.
>>
>> {'execute': 'query-cpu-model-expansion', 'arguments': {'type': 'full',
>> 'model': { 'name': 'host'}}}{"return": {}}
>>
>> {"return": {"model": {"name": "host", "props": {"sve768": false,
>> "sve128": false, "sve1024": false, "sve1280": false, "sve896": false,
>> "sve256": false, "sve1536": false, "sve1792": false, "sve384": false,
>> "sve": false, "sve2048": false, "pauth": false, "kvm-no-adjvtime":
>> false, "sve512": false, "aarch64": true, "pmu": true, "sve1920": false,
>> "sve1152": false, "kvm-steal-time": true, "sve640": false, "sve1408":
>> false, "sve1664": false}}}}
>>
>> I'm not sure if it's because the `query-cpu-model-expansion` only return
>> the feature which is bool. Since the kvm-pmu-filter is a str, it won't
>> be recognized as a feature.
>>
>> So I want to ask how can I add the kvm-pmu-filter which is str property
>> into the cpu-feature.c test.
> 
> It doesn't appear because the list of properties that we advertise
> via query-cpu-model-expansion is set in the cpu_model_advertised_features[]
> array in target/arm/arm-qmp-cmds.c, and this patch doesn't add
> 'kvm-pmu-filter' to it. But you have a good point about all the
> others being bool properties: I don't know enough about that
> mechanism to know if simply adding this to the list is right.
> 
> This does raise a more general question: do we need to advertise
> the existence of this property to libvirt via QMP? Eric, Sebastian:
> do you know ?
sorry I missed this question. yes I think it is sensible to expose that
option to libvirt. There is no good default value to be set at qemu
level so to me it really depends on the upper stack to choose the
correct value (depending on the sensitiveness of the data that justified
the kernel uapi).
> 
> If we don't care about this being visible to libvirt then the
> importance of having a test case covering the command line
> syntax goes down a bit.
> 
>>>>
>>>> +static void kvm_arm_pmu_filter_init(ARMCPU *cpu)
>>>> +{
>>>> +    static bool pmu_filter_init;
>>>> +    struct kvm_pmu_event_filter filter;
>>>> +    struct kvm_device_attr attr = {
>>>> +        .group      = KVM_ARM_VCPU_PMU_V3_CTRL,
>>>> +        .attr       = KVM_ARM_VCPU_PMU_V3_FILTER,
>>>> +        .addr       = (uint64_t)&filter,
>>>> +    };
>>>> +    int i;
>>>> +    g_auto(GStrv) event_filters;
>>>> +
>>>> +    if (!cpu->kvm_pmu_filter) {
>>>> +        return;
>>>> +    }
>>>> +    if (kvm_vcpu_ioctl(CPU(cpu), KVM_HAS_DEVICE_ATTR, &attr)) {
>>>> +        warn_report("The KVM doesn't support the PMU Event Filter!");
>>>
>>> Drop "The ".
>>>
>>> Should this really only be a warning, rather than an error?
>>>
>>
>> I think this is an add-on feature, and shouldn't block the qemu init
>> process. If we want to set the wrong pmu filter and it doesn't take
>> affect to the VM, it can be detected in the VM.
> 
> But if the user explicitly asked for it, it's not optional
> for them, it's something they want. We should fail if the user
> passes us an option that we can't actually carry out.
> 
>>>> +        return;
>>>> +    }
>>>> +
>>>> +    /*
>>>> +     * The filter only needs to be initialized through one vcpu ioctl and it
>>>> +     * will affect all other vcpu in the vm.
>>>
>>> Weird. Why isn't it a VM ioctl if it affects the whole VM ?
>>>
>>  From (kernel) commit d7eec2360e3 ("KVM: arm64: Add PMU event filtering
>> infrastructure"):
>>    Note that although the ioctl is per-vcpu, the map of allowed events is
>>    global to the VM (it can be setup from any vcpu until the vcpu PMU is
>>    initialized).
> 
> That just says it is a per-vcpu ioctl, it doesn't say why...

Marc said in a his cover letter
"
The filter state is global to the guest, despite the PMU being per CPU.
I'm not sure whether it would be worth it making it CPU-private."

I guess this is because Marc wanted to reuse the
KVM_ARM_VCPU_PMU_V3_CTRL group and just added a new attribute on top of
existing
KVM_ARM_VCPU_PMU_V3_IRQ, KVM_ARM_VCPU_PMU_V3_INIT

Thanks

Eric
> 
>>>> +    event_filters = g_strsplit(cpu->kvm_pmu_filter, ";", -1);
>>>> +    for (i = 0; event_filters[i]; i++) {
>>>> +        unsigned short start = 0, end = 0;
>>>> +        char act;
>>>> +
>>>> +        if (sscanf(event_filters[i], "%c:%hx-%hx", &act, &start, &end) != 3) {
>>>> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
>>>> +            continue;
>>>> +        }
>>>> +
>>>> +        if ((act != 'A' && act != 'D') || start > end) {
>>>> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
>>>> +            continue;
>>>> +        }
>>>
>>> It would be better to do the syntax checking up-front when
>>> the user tries to set the property. Then you can make the
>>> property-setting return an error for invalid strings.
>>>
>>
>> Ok. I guess you mean to say move the syntax checking to the
>> kvm_pmu_filter_set() function. But wouldn't it cause some code
>> duplication? Since it should first check syntax of the string in
>> kvm_pmu_filter_set() and then parse the string in this function.
> 
> No, you should check syntax and parse the string in
> kvm_pmu_filter_set(), and fill in a data structure so you don't
> have to do any string parsing here. kvm_pmu_filter_get()
> will need to reconstitute a string from the data structure.
> 
> thanks
> -- PMM
>
Peter Maydell March 19, 2024, 3 p.m. UTC | #6
On Tue, 19 Mar 2024 at 14:57, Eric Auger <eauger@redhat.com> wrote:
>
> Hi Peter,
>
> On 2/29/24 12:00, Peter Maydell wrote:
> > On Thu, 29 Feb 2024 at 02:32, Shaoqin Huang <shahuang@redhat.com> wrote:
> >> I was trying to add a test in tests/qtest/arm-cpu-features.c. But I
> >> found all other cpu-feature is bool property.
> >>
> >> When I use the 'query-cpu-model-expansion' to query the cpu-features,
> >> the kvm-pmu-filter will not shown in the returned results, just like below.
> >>
> >> {'execute': 'query-cpu-model-expansion', 'arguments': {'type': 'full',
> >> 'model': { 'name': 'host'}}}{"return": {}}
> >>
> >> {"return": {"model": {"name": "host", "props": {"sve768": false,
> >> "sve128": false, "sve1024": false, "sve1280": false, "sve896": false,
> >> "sve256": false, "sve1536": false, "sve1792": false, "sve384": false,
> >> "sve": false, "sve2048": false, "pauth": false, "kvm-no-adjvtime":
> >> false, "sve512": false, "aarch64": true, "pmu": true, "sve1920": false,
> >> "sve1152": false, "kvm-steal-time": true, "sve640": false, "sve1408":
> >> false, "sve1664": false}}}}
> >>
> >> I'm not sure if it's because the `query-cpu-model-expansion` only return
> >> the feature which is bool. Since the kvm-pmu-filter is a str, it won't
> >> be recognized as a feature.
> >>
> >> So I want to ask how can I add the kvm-pmu-filter which is str property
> >> into the cpu-feature.c test.
> >
> > It doesn't appear because the list of properties that we advertise
> > via query-cpu-model-expansion is set in the cpu_model_advertised_features[]
> > array in target/arm/arm-qmp-cmds.c, and this patch doesn't add
> > 'kvm-pmu-filter' to it. But you have a good point about all the
> > others being bool properties: I don't know enough about that
> > mechanism to know if simply adding this to the list is right.
> >
> > This does raise a more general question: do we need to advertise
> > the existence of this property to libvirt via QMP? Eric, Sebastian:
> > do you know ?
> sorry I missed this question. yes I think it is sensible to expose that
> option to libvirt. There is no good default value to be set at qemu
> level so to me it really depends on the upper stack to choose the
> correct value (depending on the sensitiveness of the data that justified
> the kernel uapi).

In that case we should definitely have a mechanism for libvirt
to be able to say "does this QEMU (and this CPU) implement
this property?". Unfortunately my QMP/libvirt expertise is
too low to be able to suggest what that mechanism should be...

thanks
-- PMM
Daniel P. Berrangé March 19, 2024, 3:22 p.m. UTC | #7
On Wed, Feb 21, 2024 at 01:34:31AM -0500, Shaoqin Huang wrote:
> The KVM_ARM_VCPU_PMU_V3_FILTER provides the ability to let the VMM decide
> which PMU events are provided to the guest. Add a new option
> `kvm-pmu-filter` as -cpu sub-option to set the PMU Event Filtering.
> Without the filter, all PMU events are exposed from host to guest by
> default. The usage of the new sub-option can be found from the updated
> document (docs/system/arm/cpu-features.rst).
> 
> Here is an example which shows how to use the PMU Event Filtering, when
> we launch a guest by use kvm, add such command line:
> 
>   # qemu-system-aarch64 \
>         -accel kvm \
>         -cpu host,kvm-pmu-filter="D:0x11-0x11"

snip

> @@ -517,6 +533,12 @@ void kvm_arm_add_vcpu_properties(ARMCPU *cpu)
>                               kvm_steal_time_set);
>      object_property_set_description(obj, "kvm-steal-time",
>                                      "Set off to disable KVM steal time.");
> +
> +    object_property_add_str(obj, "kvm-pmu-filter", kvm_pmu_filter_get,
> +                            kvm_pmu_filter_set);
> +    object_property_set_description(obj, "kvm-pmu-filter",
> +                                    "PMU Event Filtering description for "
> +                                    "guest PMU. (default: NULL, disabled)");
>  }

Passing a string property, but....[1]

>  
>  bool kvm_arm_pmu_supported(void)
> @@ -1706,6 +1728,62 @@ static bool kvm_arm_set_device_attr(ARMCPU *cpu, struct kvm_device_attr *attr,
>      return true;
>  }
>  
> +static void kvm_arm_pmu_filter_init(ARMCPU *cpu)
> +{
> +    static bool pmu_filter_init;
> +    struct kvm_pmu_event_filter filter;
> +    struct kvm_device_attr attr = {
> +        .group      = KVM_ARM_VCPU_PMU_V3_CTRL,
> +        .attr       = KVM_ARM_VCPU_PMU_V3_FILTER,
> +        .addr       = (uint64_t)&filter,
> +    };
> +    int i;
> +    g_auto(GStrv) event_filters;
> +
> +    if (!cpu->kvm_pmu_filter) {
> +        return;
> +    }
> +    if (kvm_vcpu_ioctl(CPU(cpu), KVM_HAS_DEVICE_ATTR, &attr)) {
> +        warn_report("The KVM doesn't support the PMU Event Filter!");

If the user requested a filter and it can't be supported, QEMU
must exit with an error, not ignore the user's request.

> +        return;
> +    }
> +
> +    /*
> +     * The filter only needs to be initialized through one vcpu ioctl and it
> +     * will affect all other vcpu in the vm.
> +     */
> +    if (pmu_filter_init) {
> +        return;
> +    } else {
> +        pmu_filter_init = true;
> +    }
> +
> +    event_filters = g_strsplit(cpu->kvm_pmu_filter, ";", -1);
> +    for (i = 0; event_filters[i]; i++) {
> +        unsigned short start = 0, end = 0;
> +        char act;
> +
> +        if (sscanf(event_filters[i], "%c:%hx-%hx", &act, &start, &end) != 3) {
> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
> +            continue;

Warning on user syntax errors is undesirable - it should be a fatal
error of the user gets this wrong.

> +        }
> +
> +        if ((act != 'A' && act != 'D') || start > end) {
> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
> +            continue;

Likewise should be fatal.

> +        }
> +
> +        filter.base_event = start;
> +        filter.nevents = end - start + 1;
> +        filter.action = (act == 'A') ? KVM_PMU_EVENT_ALLOW :
> +                                       KVM_PMU_EVENT_DENY;
> +
> +        if (!kvm_arm_set_device_attr(cpu, &attr, "PMU_V3_FILTER")) {
> +            break;
> +        }
> +    }
> +}

..[1] then implementing a custom parser is rather a QEMU design anti-pattern,
especially when the proposed syntax is incapable of being mapped into the
normal QAPI syntax for a list of structs should we want to fully convert
-cpu to QAPI parsing later. I wonder if can we model this property with
QAPI now ?

With regards,
Daniel
Daniel P. Berrangé March 19, 2024, 3:30 p.m. UTC | #8
On Tue, Mar 19, 2024 at 03:00:40PM +0000, Peter Maydell wrote:
> On Tue, 19 Mar 2024 at 14:57, Eric Auger <eauger@redhat.com> wrote:
> >
> > Hi Peter,
> >
> > On 2/29/24 12:00, Peter Maydell wrote:
> > >
> > > It doesn't appear because the list of properties that we advertise
> > > via query-cpu-model-expansion is set in the cpu_model_advertised_features[]
> > > array in target/arm/arm-qmp-cmds.c, and this patch doesn't add
> > > 'kvm-pmu-filter' to it. But you have a good point about all the
> > > others being bool properties: I don't know enough about that
> > > mechanism to know if simply adding this to the list is right.
> > >
> > > This does raise a more general question: do we need to advertise
> > > the existence of this property to libvirt via QMP? Eric, Sebastian:
> > > do you know ?
> > sorry I missed this question. yes I think it is sensible to expose that
> > option to libvirt. There is no good default value to be set at qemu
> > level so to me it really depends on the upper stack to choose the
> > correct value (depending on the sensitiveness of the data that justified
> > the kernel uapi).
> 
> In that case we should definitely have a mechanism for libvirt
> to be able to say "does this QEMU (and this CPU) implement
> this property?". Unfortunately my QMP/libvirt expertise is
> too low to be able to suggest what that mechanism should be...

Libvirt uses 'qom-list' on '/machine/unattached/device[0]' to
identify CPU properties.

If 'kvm-pmu-filter' appears with that, then detection will be
fine.

With regards,
Daniel
Daniel P. Berrangé March 19, 2024, 3:33 p.m. UTC | #9
On Wed, Feb 21, 2024 at 01:34:31AM -0500, Shaoqin Huang wrote:

> diff --git a/docs/system/arm/cpu-features.rst b/docs/system/arm/cpu-features.rst
> index a5fb929243..7c8f6a60ef 100644
> --- a/docs/system/arm/cpu-features.rst
> +++ b/docs/system/arm/cpu-features.rst
> @@ -204,6 +204,29 @@ the list of KVM VCPU features and their descriptions.
>    the guest scheduler behavior and/or be exposed to the guest
>    userspace.
>  
> +``kvm-pmu-filter``
> +  By default kvm-pmu-filter is disabled. This means that by default all pmu
> +  events will be exposed to guest.
> +
> +  KVM implements PMU Event Filtering to prevent a guest from being able to
> +  sample certain events. It depends on the KVM_ARM_VCPU_PMU_V3_FILTER
> +  attribute supported in KVM. It has the following format:
> +
> +  kvm-pmu-filter="{A,D}:start-end[;{A,D}:start-end...]"
> +
> +  The A means "allow" and D means "deny", start is the first event of the
> +  range and the end is the last one. The first registered range defines
> +  the global policy(global ALLOW if the first @action is DENY, global DENY
> +  if the first @action is ALLOW). The start and end only support hexadecimal
> +  format. For example:
> +
> +  kvm-pmu-filter="A:0x11-0x11;A:0x23-0x3a;D:0x30-0x30"
> +
> +  Since the first action is allow, we have a global deny policy. It
> +  will allow event 0x11 (The cycle counter), events 0x23 to 0x3a are
> +  also allowed except the event 0x30 which is denied, and all the other
> +  events are denied.


Can you document whether the policy evaluation stops at the first
matching range, or checks all ranges

ie, if you have

  kvm-pmu-filter="A:0x1-0x9;D=0x7-0x7"

will an input of '0x7' be allowed (because it matches the
first range and stops), or denied (because the second range
overrides the result of the first)



With regards,
Daniel
Eric Auger March 19, 2024, 5:58 p.m. UTC | #10
Hi Daniel,

On 3/19/24 16:22, Daniel P. Berrangé wrote:
> On Wed, Feb 21, 2024 at 01:34:31AM -0500, Shaoqin Huang wrote:
>> The KVM_ARM_VCPU_PMU_V3_FILTER provides the ability to let the VMM decide
>> which PMU events are provided to the guest. Add a new option
>> `kvm-pmu-filter` as -cpu sub-option to set the PMU Event Filtering.
>> Without the filter, all PMU events are exposed from host to guest by
>> default. The usage of the new sub-option can be found from the updated
>> document (docs/system/arm/cpu-features.rst).
>>
>> Here is an example which shows how to use the PMU Event Filtering, when
>> we launch a guest by use kvm, add such command line:
>>
>>   # qemu-system-aarch64 \
>>         -accel kvm \
>>         -cpu host,kvm-pmu-filter="D:0x11-0x11"
> 
> snip
> 
>> @@ -517,6 +533,12 @@ void kvm_arm_add_vcpu_properties(ARMCPU *cpu)
>>                               kvm_steal_time_set);
>>      object_property_set_description(obj, "kvm-steal-time",
>>                                      "Set off to disable KVM steal time.");
>> +
>> +    object_property_add_str(obj, "kvm-pmu-filter", kvm_pmu_filter_get,
>> +                            kvm_pmu_filter_set);
>> +    object_property_set_description(obj, "kvm-pmu-filter",
>> +                                    "PMU Event Filtering description for "
>> +                                    "guest PMU. (default: NULL, disabled)");
>>  }
> 
> Passing a string property, but....[1]
> 
>>  
>>  bool kvm_arm_pmu_supported(void)
>> @@ -1706,6 +1728,62 @@ static bool kvm_arm_set_device_attr(ARMCPU *cpu, struct kvm_device_attr *attr,
>>      return true;
>>  }
>>  
>> +static void kvm_arm_pmu_filter_init(ARMCPU *cpu)
>> +{
>> +    static bool pmu_filter_init;
>> +    struct kvm_pmu_event_filter filter;
>> +    struct kvm_device_attr attr = {
>> +        .group      = KVM_ARM_VCPU_PMU_V3_CTRL,
>> +        .attr       = KVM_ARM_VCPU_PMU_V3_FILTER,
>> +        .addr       = (uint64_t)&filter,
>> +    };
>> +    int i;
>> +    g_auto(GStrv) event_filters;
>> +
>> +    if (!cpu->kvm_pmu_filter) {
>> +        return;
>> +    }
>> +    if (kvm_vcpu_ioctl(CPU(cpu), KVM_HAS_DEVICE_ATTR, &attr)) {
>> +        warn_report("The KVM doesn't support the PMU Event Filter!");
> 
> If the user requested a filter and it can't be supported, QEMU
> must exit with an error, not ignore the user's request.
> 
>> +        return;
>> +    }
>> +
>> +    /*
>> +     * The filter only needs to be initialized through one vcpu ioctl and it
>> +     * will affect all other vcpu in the vm.
>> +     */
>> +    if (pmu_filter_init) {
>> +        return;
>> +    } else {
>> +        pmu_filter_init = true;
>> +    }
>> +
>> +    event_filters = g_strsplit(cpu->kvm_pmu_filter, ";", -1);
>> +    for (i = 0; event_filters[i]; i++) {
>> +        unsigned short start = 0, end = 0;
>> +        char act;
>> +
>> +        if (sscanf(event_filters[i], "%c:%hx-%hx", &act, &start, &end) != 3) {
>> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
>> +            continue;
> 
> Warning on user syntax errors is undesirable - it should be a fatal
> error of the user gets this wrong.
> 
>> +        }
>> +
>> +        if ((act != 'A' && act != 'D') || start > end) {
>> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
>> +            continue;
> 
> Likewise should be fatal.
> 
>> +        }
>> +
>> +        filter.base_event = start;
>> +        filter.nevents = end - start + 1;
>> +        filter.action = (act == 'A') ? KVM_PMU_EVENT_ALLOW :
>> +                                       KVM_PMU_EVENT_DENY;
>> +
>> +        if (!kvm_arm_set_device_attr(cpu, &attr, "PMU_V3_FILTER")) {
>> +            break;
>> +        }
>> +    }
>> +}
> 
> ..[1] then implementing a custom parser is rather a QEMU design anti-pattern,
> especially when the proposed syntax is incapable of being mapped into the
> normal QAPI syntax for a list of structs should we want to fully convert
> -cpu to QAPI parsing later. I wonder if can we model this property with
> QAPI now ?
I guess you mean creating a new property like those in
hw/core/qdev-properties-system.c for instance  and populating an array
of those at CPU object level?

Note there is v8 but most of your comments still apply
https://lore.kernel.org/all/20240312074849.71475-1-shahuang@redhat.com/

Thanks

Eric
> 
> With regards,
> Daniel
Daniel P. Berrangé March 19, 2024, 6:07 p.m. UTC | #11
On Tue, Mar 19, 2024 at 06:58:33PM +0100, Eric Auger wrote:
> Hi Daniel,
> 
> On 3/19/24 16:22, Daniel P. Berrangé wrote:
> > On Wed, Feb 21, 2024 at 01:34:31AM -0500, Shaoqin Huang wrote:
> >> The KVM_ARM_VCPU_PMU_V3_FILTER provides the ability to let the VMM decide
> >> which PMU events are provided to the guest. Add a new option
> >> `kvm-pmu-filter` as -cpu sub-option to set the PMU Event Filtering.
> >> Without the filter, all PMU events are exposed from host to guest by
> >> default. The usage of the new sub-option can be found from the updated
> >> document (docs/system/arm/cpu-features.rst).
> >>
> >> Here is an example which shows how to use the PMU Event Filtering, when
> >> we launch a guest by use kvm, add such command line:
> >>
> >>   # qemu-system-aarch64 \
> >>         -accel kvm \
> >>         -cpu host,kvm-pmu-filter="D:0x11-0x11"
> > 
> > snip
> > 
> >> @@ -517,6 +533,12 @@ void kvm_arm_add_vcpu_properties(ARMCPU *cpu)
> >>                               kvm_steal_time_set);
> >>      object_property_set_description(obj, "kvm-steal-time",
> >>                                      "Set off to disable KVM steal time.");
> >> +
> >> +    object_property_add_str(obj, "kvm-pmu-filter", kvm_pmu_filter_get,
> >> +                            kvm_pmu_filter_set);
> >> +    object_property_set_description(obj, "kvm-pmu-filter",
> >> +                                    "PMU Event Filtering description for "
> >> +                                    "guest PMU. (default: NULL, disabled)");
> >>  }
> > 
> > Passing a string property, but....[1]
> > 
> >>  
> >>  bool kvm_arm_pmu_supported(void)
> >> @@ -1706,6 +1728,62 @@ static bool kvm_arm_set_device_attr(ARMCPU *cpu, struct kvm_device_attr *attr,
> >>      return true;
> >>  }
> >>  
> >> +static void kvm_arm_pmu_filter_init(ARMCPU *cpu)
> >> +{
> >> +    static bool pmu_filter_init;
> >> +    struct kvm_pmu_event_filter filter;
> >> +    struct kvm_device_attr attr = {
> >> +        .group      = KVM_ARM_VCPU_PMU_V3_CTRL,
> >> +        .attr       = KVM_ARM_VCPU_PMU_V3_FILTER,
> >> +        .addr       = (uint64_t)&filter,
> >> +    };
> >> +    int i;
> >> +    g_auto(GStrv) event_filters;
> >> +
> >> +    if (!cpu->kvm_pmu_filter) {
> >> +        return;
> >> +    }
> >> +    if (kvm_vcpu_ioctl(CPU(cpu), KVM_HAS_DEVICE_ATTR, &attr)) {
> >> +        warn_report("The KVM doesn't support the PMU Event Filter!");
> > 
> > If the user requested a filter and it can't be supported, QEMU
> > must exit with an error, not ignore the user's request.
> > 
> >> +        return;
> >> +    }
> >> +
> >> +    /*
> >> +     * The filter only needs to be initialized through one vcpu ioctl and it
> >> +     * will affect all other vcpu in the vm.
> >> +     */
> >> +    if (pmu_filter_init) {
> >> +        return;
> >> +    } else {
> >> +        pmu_filter_init = true;
> >> +    }
> >> +
> >> +    event_filters = g_strsplit(cpu->kvm_pmu_filter, ";", -1);
> >> +    for (i = 0; event_filters[i]; i++) {
> >> +        unsigned short start = 0, end = 0;
> >> +        char act;
> >> +
> >> +        if (sscanf(event_filters[i], "%c:%hx-%hx", &act, &start, &end) != 3) {
> >> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
> >> +            continue;
> > 
> > Warning on user syntax errors is undesirable - it should be a fatal
> > error of the user gets this wrong.
> > 
> >> +        }
> >> +
> >> +        if ((act != 'A' && act != 'D') || start > end) {
> >> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
> >> +            continue;
> > 
> > Likewise should be fatal.
> > 
> >> +        }
> >> +
> >> +        filter.base_event = start;
> >> +        filter.nevents = end - start + 1;
> >> +        filter.action = (act == 'A') ? KVM_PMU_EVENT_ALLOW :
> >> +                                       KVM_PMU_EVENT_DENY;
> >> +
> >> +        if (!kvm_arm_set_device_attr(cpu, &attr, "PMU_V3_FILTER")) {
> >> +            break;
> >> +        }
> >> +    }
> >> +}
> > 
> > ..[1] then implementing a custom parser is rather a QEMU design anti-pattern,
> > especially when the proposed syntax is incapable of being mapped into the
> > normal QAPI syntax for a list of structs should we want to fully convert
> > -cpu to QAPI parsing later. I wonder if can we model this property with
> > QAPI now ?
> I guess you mean creating a new property like those in
> hw/core/qdev-properties-system.c for instance  and populating an array
> of those at CPU object level?

Yeah, something like the IOThreadVirtQueueMapping data type would
be the more QAPI like code pattern.

> Note there is v8 but most of your comments still apply
> https://lore.kernel.org/all/20240312074849.71475-1-shahuang@redhat.com/

Yes, sorry I just saw Peter's query about libvirt on this v7 and
didn't think to look for a newer version

With regards,
Daniel
Eric Auger March 19, 2024, 6:18 p.m. UTC | #12
Hi,

On 3/19/24 19:07, Daniel P. Berrangé wrote:
> On Tue, Mar 19, 2024 at 06:58:33PM +0100, Eric Auger wrote:
>> Hi Daniel,
>>
>> On 3/19/24 16:22, Daniel P. Berrangé wrote:
>>> On Wed, Feb 21, 2024 at 01:34:31AM -0500, Shaoqin Huang wrote:
>>>> The KVM_ARM_VCPU_PMU_V3_FILTER provides the ability to let the VMM decide
>>>> which PMU events are provided to the guest. Add a new option
>>>> `kvm-pmu-filter` as -cpu sub-option to set the PMU Event Filtering.
>>>> Without the filter, all PMU events are exposed from host to guest by
>>>> default. The usage of the new sub-option can be found from the updated
>>>> document (docs/system/arm/cpu-features.rst).
>>>>
>>>> Here is an example which shows how to use the PMU Event Filtering, when
>>>> we launch a guest by use kvm, add such command line:
>>>>
>>>>   # qemu-system-aarch64 \
>>>>         -accel kvm \
>>>>         -cpu host,kvm-pmu-filter="D:0x11-0x11"
>>>
>>> snip
>>>
>>>> @@ -517,6 +533,12 @@ void kvm_arm_add_vcpu_properties(ARMCPU *cpu)
>>>>                               kvm_steal_time_set);
>>>>      object_property_set_description(obj, "kvm-steal-time",
>>>>                                      "Set off to disable KVM steal time.");
>>>> +
>>>> +    object_property_add_str(obj, "kvm-pmu-filter", kvm_pmu_filter_get,
>>>> +                            kvm_pmu_filter_set);
>>>> +    object_property_set_description(obj, "kvm-pmu-filter",
>>>> +                                    "PMU Event Filtering description for "
>>>> +                                    "guest PMU. (default: NULL, disabled)");
>>>>  }
>>>
>>> Passing a string property, but....[1]
>>>
>>>>  
>>>>  bool kvm_arm_pmu_supported(void)
>>>> @@ -1706,6 +1728,62 @@ static bool kvm_arm_set_device_attr(ARMCPU *cpu, struct kvm_device_attr *attr,
>>>>      return true;
>>>>  }
>>>>  
>>>> +static void kvm_arm_pmu_filter_init(ARMCPU *cpu)
>>>> +{
>>>> +    static bool pmu_filter_init;
>>>> +    struct kvm_pmu_event_filter filter;
>>>> +    struct kvm_device_attr attr = {
>>>> +        .group      = KVM_ARM_VCPU_PMU_V3_CTRL,
>>>> +        .attr       = KVM_ARM_VCPU_PMU_V3_FILTER,
>>>> +        .addr       = (uint64_t)&filter,
>>>> +    };
>>>> +    int i;
>>>> +    g_auto(GStrv) event_filters;
>>>> +
>>>> +    if (!cpu->kvm_pmu_filter) {
>>>> +        return;
>>>> +    }
>>>> +    if (kvm_vcpu_ioctl(CPU(cpu), KVM_HAS_DEVICE_ATTR, &attr)) {
>>>> +        warn_report("The KVM doesn't support the PMU Event Filter!");
>>>
>>> If the user requested a filter and it can't be supported, QEMU
>>> must exit with an error, not ignore the user's request.
>>>
>>>> +        return;
>>>> +    }
>>>> +
>>>> +    /*
>>>> +     * The filter only needs to be initialized through one vcpu ioctl and it
>>>> +     * will affect all other vcpu in the vm.
>>>> +     */
>>>> +    if (pmu_filter_init) {
>>>> +        return;
>>>> +    } else {
>>>> +        pmu_filter_init = true;
>>>> +    }
>>>> +
>>>> +    event_filters = g_strsplit(cpu->kvm_pmu_filter, ";", -1);
>>>> +    for (i = 0; event_filters[i]; i++) {
>>>> +        unsigned short start = 0, end = 0;
>>>> +        char act;
>>>> +
>>>> +        if (sscanf(event_filters[i], "%c:%hx-%hx", &act, &start, &end) != 3) {
>>>> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
>>>> +            continue;
>>>
>>> Warning on user syntax errors is undesirable - it should be a fatal
>>> error of the user gets this wrong.
>>>
>>>> +        }
>>>> +
>>>> +        if ((act != 'A' && act != 'D') || start > end) {
>>>> +            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
>>>> +            continue;
>>>
>>> Likewise should be fatal.
>>>
>>>> +        }
>>>> +
>>>> +        filter.base_event = start;
>>>> +        filter.nevents = end - start + 1;
>>>> +        filter.action = (act == 'A') ? KVM_PMU_EVENT_ALLOW :
>>>> +                                       KVM_PMU_EVENT_DENY;
>>>> +
>>>> +        if (!kvm_arm_set_device_attr(cpu, &attr, "PMU_V3_FILTER")) {
>>>> +            break;
>>>> +        }
>>>> +    }
>>>> +}
>>>
>>> ..[1] then implementing a custom parser is rather a QEMU design anti-pattern,
>>> especially when the proposed syntax is incapable of being mapped into the
>>> normal QAPI syntax for a list of structs should we want to fully convert
>>> -cpu to QAPI parsing later. I wonder if can we model this property with
>>> QAPI now ?
>> I guess you mean creating a new property like those in
>> hw/core/qdev-properties-system.c for instance  and populating an array
>> of those at CPU object level?
> 
> Yeah, something like the IOThreadVirtQueueMapping data type would
> be the more QAPI like code pattern.
OK thank you for the confirmation. Then if we create such kind of
property it would be nice that this latter also matches the need of x86
PMU filtering. I think the uapi exists at KVM level but has never been
integrated in qemu.
> 
>> Note there is v8 but most of your comments still apply
>> https://lore.kernel.org/all/20240312074849.71475-1-shahuang@redhat.com/
> 
> Yes, sorry I just saw Peter's query about libvirt on this v7 and
> didn't think to look for a newer version

no problem. Thank you for your time

Eric
> 
> With regards,
> Daniel
diff mbox series

Patch

diff --git a/docs/system/arm/cpu-features.rst b/docs/system/arm/cpu-features.rst
index a5fb929243..7c8f6a60ef 100644
--- a/docs/system/arm/cpu-features.rst
+++ b/docs/system/arm/cpu-features.rst
@@ -204,6 +204,29 @@  the list of KVM VCPU features and their descriptions.
   the guest scheduler behavior and/or be exposed to the guest
   userspace.
 
+``kvm-pmu-filter``
+  By default kvm-pmu-filter is disabled. This means that by default all pmu
+  events will be exposed to guest.
+
+  KVM implements PMU Event Filtering to prevent a guest from being able to
+  sample certain events. It depends on the KVM_ARM_VCPU_PMU_V3_FILTER
+  attribute supported in KVM. It has the following format:
+
+  kvm-pmu-filter="{A,D}:start-end[;{A,D}:start-end...]"
+
+  The A means "allow" and D means "deny", start is the first event of the
+  range and the end is the last one. The first registered range defines
+  the global policy(global ALLOW if the first @action is DENY, global DENY
+  if the first @action is ALLOW). The start and end only support hexadecimal
+  format. For example:
+
+  kvm-pmu-filter="A:0x11-0x11;A:0x23-0x3a;D:0x30-0x30"
+
+  Since the first action is allow, we have a global deny policy. It
+  will allow event 0x11 (The cycle counter), events 0x23 to 0x3a are
+  also allowed except the event 0x30 which is denied, and all the other
+  events are denied.
+
 TCG VCPU Features
 =================
 
diff --git a/target/arm/cpu.h b/target/arm/cpu.h
index 63f31e0d98..f7f2431755 100644
--- a/target/arm/cpu.h
+++ b/target/arm/cpu.h
@@ -948,6 +948,9 @@  struct ArchCPU {
 
     /* KVM steal time */
     OnOffAuto kvm_steal_time;
+
+    /* KVM PMU Filter */
+    char *kvm_pmu_filter;
 #endif /* CONFIG_KVM */
 
     /* Uniprocessor system with MP extensions */
diff --git a/target/arm/kvm.c b/target/arm/kvm.c
index 81813030a5..5c62580d34 100644
--- a/target/arm/kvm.c
+++ b/target/arm/kvm.c
@@ -496,6 +496,22 @@  static void kvm_steal_time_set(Object *obj, bool value, Error **errp)
     ARM_CPU(obj)->kvm_steal_time = value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF;
 }
 
+static char *kvm_pmu_filter_get(Object *obj, Error **errp)
+{
+    ARMCPU *cpu = ARM_CPU(obj);
+
+    return g_strdup(cpu->kvm_pmu_filter);
+}
+
+static void kvm_pmu_filter_set(Object *obj, const char *pmu_filter,
+                               Error **errp)
+{
+    ARMCPU *cpu = ARM_CPU(obj);
+
+    g_free(cpu->kvm_pmu_filter);
+    cpu->kvm_pmu_filter = g_strdup(pmu_filter);
+}
+
 /* KVM VCPU properties should be prefixed with "kvm-". */
 void kvm_arm_add_vcpu_properties(ARMCPU *cpu)
 {
@@ -517,6 +533,12 @@  void kvm_arm_add_vcpu_properties(ARMCPU *cpu)
                              kvm_steal_time_set);
     object_property_set_description(obj, "kvm-steal-time",
                                     "Set off to disable KVM steal time.");
+
+    object_property_add_str(obj, "kvm-pmu-filter", kvm_pmu_filter_get,
+                            kvm_pmu_filter_set);
+    object_property_set_description(obj, "kvm-pmu-filter",
+                                    "PMU Event Filtering description for "
+                                    "guest PMU. (default: NULL, disabled)");
 }
 
 bool kvm_arm_pmu_supported(void)
@@ -1706,6 +1728,62 @@  static bool kvm_arm_set_device_attr(ARMCPU *cpu, struct kvm_device_attr *attr,
     return true;
 }
 
+static void kvm_arm_pmu_filter_init(ARMCPU *cpu)
+{
+    static bool pmu_filter_init;
+    struct kvm_pmu_event_filter filter;
+    struct kvm_device_attr attr = {
+        .group      = KVM_ARM_VCPU_PMU_V3_CTRL,
+        .attr       = KVM_ARM_VCPU_PMU_V3_FILTER,
+        .addr       = (uint64_t)&filter,
+    };
+    int i;
+    g_auto(GStrv) event_filters;
+
+    if (!cpu->kvm_pmu_filter) {
+        return;
+    }
+    if (kvm_vcpu_ioctl(CPU(cpu), KVM_HAS_DEVICE_ATTR, &attr)) {
+        warn_report("The KVM doesn't support the PMU Event Filter!");
+        return;
+    }
+
+    /*
+     * The filter only needs to be initialized through one vcpu ioctl and it
+     * will affect all other vcpu in the vm.
+     */
+    if (pmu_filter_init) {
+        return;
+    } else {
+        pmu_filter_init = true;
+    }
+
+    event_filters = g_strsplit(cpu->kvm_pmu_filter, ";", -1);
+    for (i = 0; event_filters[i]; i++) {
+        unsigned short start = 0, end = 0;
+        char act;
+
+        if (sscanf(event_filters[i], "%c:%hx-%hx", &act, &start, &end) != 3) {
+            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
+            continue;
+        }
+
+        if ((act != 'A' && act != 'D') || start > end) {
+            warn_report("Skipping invalid PMU filter %s", event_filters[i]);
+            continue;
+        }
+
+        filter.base_event = start;
+        filter.nevents = end - start + 1;
+        filter.action = (act == 'A') ? KVM_PMU_EVENT_ALLOW :
+                                       KVM_PMU_EVENT_DENY;
+
+        if (!kvm_arm_set_device_attr(cpu, &attr, "PMU_V3_FILTER")) {
+            break;
+        }
+    }
+}
+
 void kvm_arm_pmu_init(ARMCPU *cpu)
 {
     struct kvm_device_attr attr = {
@@ -1716,6 +1794,8 @@  void kvm_arm_pmu_init(ARMCPU *cpu)
     if (!cpu->has_pmu) {
         return;
     }
+
+    kvm_arm_pmu_filter_init(cpu);
     if (!kvm_arm_set_device_attr(cpu, &attr, "PMU")) {
         error_report("failed to init PMU");
         abort();