From: Sean Christopherson <seanjc@google.com>
To: Aaron Lewis <aaronlewis@google.com>
Cc: kvm@vger.kernel.org, pbonzini@redhat.com, jmattson@google.com
Subject: Re: [PATCH v5 4/7] kvm: x86/pmu: Introduce masked events to the pmu event filter
Date: Fri, 21 Oct 2022 17:50:49 +0000 [thread overview]
Message-ID: <Y1Lb+bfaO1Y+skUD@google.com> (raw)
In-Reply-To: <CAAAPnDGvnC4uP5Q_yio+m8Q-cu+5anZTLwYRpro9E+W=U5gcTA@mail.gmail.com>
On Fri, Oct 21, 2022, Aaron Lewis wrote:
> Here's what I came up with. Let me know if this is what you were thinking:
A purely mechanical suggestions, but overall looks awesome!
> static int filter_sort_cmp(const void *pa, const void *pb)
> {
> u64 a = *(u64 *)pa & (KVM_PMU_MASKED_ENTRY_EVENT_SELECT |
> KVM_PMU_MASKED_ENTRY_EXCLUDE);
> u64 b = *(u64 *)pb & (KVM_PMU_MASKED_ENTRY_EVENT_SELECT |
> KVM_PMU_MASKED_ENTRY_EXCLUDE);
>
> return (a > b) - (a < b);
> }
>
> /*
> * For the event filter, searching is done on the 'includes' list and
> * 'excludes' list separately rather than on the 'events' list (which
> * has both). As a result the exclude bit can be ignored.
> */
> static int filter_event_cmp(const void *pa, const void *pb)
> {
> u64 a = *(u64 *)pa & KVM_PMU_MASKED_ENTRY_EVENT_SELECT;
> u64 b = *(u64 *)pb & KVM_PMU_MASKED_ENTRY_EVENT_SELECT;
>
> return (a > b) - (a < b);
> }
To dedup code slightly and make this a little more readable, what about adding a
common helper to do the compare? That also makes it quite obvious that the only
difference is the inclusion (heh) of the EXCLUDE flag.
static int filter_cmp(u64 *pa, u64 *pb, u64 mask)
{
u64 a = *pa & mask;
u64 b = *pb & mask;
return (a > b) - (a < b);
}
static int filter_sort_cmp(const void *pa, const void *pb)
{
return filter_cmp(pa, pb, (KVM_PMU_MASKED_ENTRY_EVENT_SELECT |
KVM_PMU_MASKED_ENTRY_EXCLUDE);
}
/*
* For the event filter, searching is done on the 'includes' list and
* 'excludes' list separately rather than on the 'events' list (which
* has both). As a result the exclude bit can be ignored.
*/
static int filter_event_cmp(const void *pa, const void *pb)
{
return filter_cmp(pa, pb, (KVM_PMU_MASKED_ENTRY_EVENT_SELECT);
}
> static bool filter_contains_match(u64 *events, u64 nevents, u64 eventsel)
> {
> u64 event_select = eventsel & kvm_pmu_ops.EVENTSEL_EVENT;
> u64 umask = eventsel & ARCH_PERFMON_EVENTSEL_UMASK;
> int i, index;
>
> index = find_filter_index(events, nevents, event_select);
> if (index < 0)
> return false;
>
> /*
> * Entries are sorted by the event select. Walk the list in both
> * directions to process all entries with the targeted event select.
> */
> for (i = index; i < nevents; i++) {
> if (filter_event_cmp(&events[i], &event_select) != 0)
Preferred kernel style is to omit comparisons against zero, i.e. just
if (filter_event_cmp(&events[i], &event_select))
break;
> break;
>
> if (is_filter_entry_match(events[i], umask))
> return true;
> }
>
> for (i = index - 1; i >= 0; i--) {
> if (filter_event_cmp(&events[i], &event_select) != 0)
> break;
>
> if (is_filter_entry_match(events[i], umask))
> return true;
> }
>
> return false;
> }
>
> static bool is_gp_event_allowed(struct kvm_x86_pmu_event_filter *filter,
> u64 eventsel)
> {
> if (filter_contains_match(filter->includes,
> filter->nr_includes, eventsel) &&
> !filter_contains_match(filter->excludes,
> filter->nr_excludes, eventsel))
> return filter->action == KVM_PMU_EVENT_ALLOW;
>
> return filter->action == KVM_PMU_EVENT_DENY;
Might be worth using a single char for the filter param, e.g. 'f' yields:
static bool is_gp_event_allowed(struct kvm_x86_pmu_event_filter *f,
u64 eventsel)
{
if (filter_contains_match(f->includes, f->nr_includes, eventsel) &&
!filter_contains_match(f->excludes, f->nr_excludes, eventsel))
return f->action == KVM_PMU_EVENT_ALLOW;
return f->action == KVM_PMU_EVENT_DENY;
}
> static void setup_filter_lists(struct kvm_x86_pmu_event_filter *filter)
> {
> int i;
>
> for (i = 0; i < filter->nevents; i++) {
> if(filter->events[i] & KVM_PMU_MASKED_ENTRY_EXCLUDE)a
Space after the if
if (filter-> ...)
> break;
> }
>
> filter->nr_includes = i;
> filter->nr_excludes = filter->nevents - filter->nr_includes;
> filter->includes = filter->events;
> filter->excludes = filter->events + filter->nr_includes;
> }
>
...
> static void prepare_filter_events(struct kvm_x86_pmu_event_filter *filter)
> {
> int i, j;
>
> if (filter->flags & KVM_PMU_EVENT_FLAG_MASKED_EVENTS)
> return;
>
> for (i = 0, j = 0; i < filter->nevents; i++) {
> /*
> * Skip events that are impossible to match against a guest
> * event. When filtering, only the event select + unit mask
> * of the guest event is used.
This is a good place for calling out that impossible filters can't be rejected
for backwards compatibility reasons.
> */
> if (filter->events[i] & ~(kvm_pmu_ops.EVENTSEL_EVENT |
> ARCH_PERFMON_EVENTSEL_UMASK))
> continue;
>
> /*
> * Convert userspace events to a common in-kernel event so
> * only one code path is needed to support both events. For
> * the in-kernel events use masked events because they are
> * flexible enough to handle both cases. To convert to masked
> * events all that's needed is to add the umask_mask.
I think it's worth calling out this creates an "all ones" umask_mask, and that
EXCLUDE isn't supported.
> */
> filter->events[j++] =
> filter->events[i] |
> (0xFFULL << KVM_PMU_MASKED_ENTRY_UMASK_MASK_SHIFT);
> }
>
> filter->nevents = j;
> }
...
> - /* Restore the verified state to guard against TOCTOU attacks. */
> - *filter = tmp;
> + r = -EINVAL;
> + if (!is_filter_valid(filter))
> + goto cleanup;
>
> - remove_impossible_events(filter);
> + prepare_filter_events(filter);
>
> /*
> - * Sort the in-kernel list so that we can search it with bsearch.
> + * Sort entries by event select so that all entries for a given
> + * event select can be processed efficiently during filtering.
> */
> - sort(&filter->events, filter->nevents, sizeof(__u64), cmp_u64, NULL);
> + sort(&filter->events, filter->nevents, sizeof(filter->events[0]),
> + filter_sort_cmp, NULL);
> +
> + setup_filter_lists(filter);
The sort+setup should definitely go in a single helper. Rather than have the
helpers deal with masked vs. legacy, what about putting that logic in a top-level
helper? Then this code is simply:
r = prepare_filter_lists(filter);
if (r)
goto cleanup;
And the helper names can be more explicit, i.e. can call out that they validate
a masked filter and convert to a masked filter.
E.g. (completely untested)
static bool is_masked_filter_valid(const struct kvm_x86_pmu_event_filter *filter)
{
u64 mask = kvm_pmu_ops.EVENTSEL_EVENT |
KVM_PMU_MASKED_ENTRY_UMASK_MASK |
KVM_PMU_MASKED_ENTRY_UMASK_MATCH |
KVM_PMU_MASKED_ENTRY_EXCLUDE;
int i;
for (i = 0; i < filter->nevents; i++) {
if (filter->events[i] & ~mask)
return false;
}
return true;
}
static void convert_to_masked_filter(struct kvm_x86_pmu_event_filter *filter)
{
int i, j;
for (i = 0, j = 0; i < filter->nevents; i++) {
/*
* Skip events that are impossible to match against a guest
* event. When filtering, only the event select + unit mask
* of the guest event is used. To maintain backwards
* compatibility, impossible filters can't be rejected :-(
*/
if (filter->events[i] & ~(kvm_pmu_ops.EVENTSEL_EVENT |
ARCH_PERFMON_EVENTSEL_UMASK))
continue;
/*
* Convert userspace events to a common in-kernel event so
* only one code path is needed to support both events. For
* the in-kernel events use masked events because they are
* flexible enough to handle both cases. To convert to masked
* events all that's needed is to add an "all ones" umask_mask,
* (unmasked filter events don't support EXCLUDE).
*/
filter->events[j++] = filter->events[i] |
(0xFFULL << KVM_PMU_MASKED_ENTRY_UMASK_MASK_SHIFT);
}
filter->nevents = j;
}
static int prepare_filter_lists(struct kvm_x86_pmu_event_filter *filter)
{
int i;
if (!(filter->flags & KVM_PMU_EVENT_FLAG_MASKED_EVENTS)
convert_to_masked_filter(filter)
else if (!is_masked_filter_valid(filter))
return -EINVAL;
/*
* Sort entries by event select and includes vs. excludes so that all
* entries for a given event select can be processed efficiently during
* filtering. The EXCLUDE flag uses a more significant bit than the
* event select, and so the sorted list is also effectively split into
* includes and excludes sub-lists.
*/
sort(&filter->events, filter->nevents, sizeof(filter->events[0]),
filter_sort_cmp, NULL);
/* Find the first EXCLUDE event (only supported for masked events). */
if (filter->flags & KVM_PMU_EVENT_FLAG_MASKED_EVENTS) {
for (i = 0; i < filter->nevents; i++) {
if (filter->events[i] & KVM_PMU_MASKED_ENTRY_EXCLUDE)
break;
}
}
filter->nr_includes = i;
filter->nr_excludes = filter->nevents - filter->nr_includes;
filter->includes = filter->events;
filter->excludes = filter->events + filter->nr_includes;
}
next prev parent reply other threads:[~2022-10-21 17:51 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-09-20 17:45 [PATCH v5 0/7] Introduce and test masked events Aaron Lewis
2022-09-20 17:45 ` [PATCH v5 1/7] kvm: x86/pmu: Correct the mask used in a pmu event filter lookup Aaron Lewis
2022-10-07 23:25 ` Sean Christopherson
2022-10-15 15:43 ` Aaron Lewis
2022-10-17 18:20 ` Sean Christopherson
2022-10-21 1:33 ` Aaron Lewis
2022-09-20 17:45 ` [PATCH v5 2/7] kvm: x86/pmu: Remove invalid raw events from the pmu event filter Aaron Lewis
2022-10-07 23:48 ` Sean Christopherson
2022-09-20 17:45 ` [PATCH v5 3/7] kvm: x86/pmu: prepare the pmu event filter for masked events Aaron Lewis
2022-10-08 0:08 ` Sean Christopherson
2022-09-20 17:46 ` [PATCH v5 4/7] kvm: x86/pmu: Introduce masked events to the pmu event filter Aaron Lewis
2022-10-08 0:37 ` Sean Christopherson
2022-10-10 15:42 ` Sean Christopherson
2022-10-15 15:43 ` Aaron Lewis
2022-10-17 17:47 ` Sean Christopherson
2022-10-19 1:06 ` Aaron Lewis
2022-10-21 1:33 ` Aaron Lewis
2022-10-21 17:50 ` Sean Christopherson [this message]
2022-09-20 17:46 ` [PATCH v5 5/7] selftests: kvm/x86: Add flags when creating a " Aaron Lewis
2022-09-20 17:46 ` [PATCH v5 6/7] selftests: kvm/x86: Add testing for KVM_SET_PMU_EVENT_FILTER Aaron Lewis
2022-09-20 17:56 ` Jim Mattson
2022-09-20 17:46 ` [PATCH v5 7/7] selftests: kvm/x86: Test masked events Aaron Lewis
2022-09-20 17:59 ` Jim Mattson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=Y1Lb+bfaO1Y+skUD@google.com \
--to=seanjc@google.com \
--cc=aaronlewis@google.com \
--cc=jmattson@google.com \
--cc=kvm@vger.kernel.org \
--cc=pbonzini@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.