[PATCH 4/5] perf/dummy_pmu: Tie pmu to device lifecycle
From: Lucas De Marchi
Date: Tue Oct 08 2024 - 14:36:57 EST
Allow to unregister the PMU from perf with active events. When driver is
being accessed perf keeps a reference that when released triggers the
device cleanup.
Signed-off-by: Lucas De Marchi <lucas.demarchi@xxxxxxxxx>
---
kernel/events/dummy_pmu.c | 56 ++++++++++++++++++++++++++++-----------
1 file changed, 41 insertions(+), 15 deletions(-)
diff --git a/kernel/events/dummy_pmu.c b/kernel/events/dummy_pmu.c
index cdba3a831e4a..c07e111bff01 100644
--- a/kernel/events/dummy_pmu.c
+++ b/kernel/events/dummy_pmu.c
@@ -49,6 +49,11 @@ static struct dummy_device *pmu_to_device(struct dummy_pmu *pmu)
return container_of(pmu, struct dummy_device, pmu);
}
+static struct dummy_pmu *pmu_to_dummy(struct pmu *pmu)
+{
+ return container_of(pmu, struct dummy_pmu, base);
+}
+
static ssize_t dummy_pmu_events_sysfs_show(struct device *dev,
struct device_attribute *attr,
char *page)
@@ -92,18 +97,9 @@ static const struct attribute_group *attr_groups[] = {
NULL,
};
-static void dummy_pmu_event_destroy(struct perf_event *event)
-{
- struct dummy_pmu *pmu = event_to_pmu(event);
- struct dummy_device *d = pmu_to_device(pmu);
-
- kref_put(&d->refcount, device_release);
-}
-
static int dummy_pmu_event_init(struct perf_event *event)
{
struct dummy_pmu *pmu = event_to_pmu(event);
- struct dummy_device *d = pmu_to_device(pmu);
if (!pmu->registered)
return -ENODEV;
@@ -121,10 +117,6 @@ static int dummy_pmu_event_init(struct perf_event *event)
if (event->cpu < 0)
return -EINVAL;
- /* Event keeps a ref to maintain PMU allocated, even if it's unregistered */
- kref_get(&d->refcount);
- event->destroy = dummy_pmu_event_destroy;
-
return 0;
}
@@ -195,10 +187,29 @@ static void dummy_pmu_event_del(struct perf_event *event, int flags)
dummy_pmu_event_stop(event, PERF_EF_UPDATE);
}
+static struct pmu *dummy_pmu_get(struct pmu *pmu)
+{
+ struct dummy_device *d = pmu_to_device(pmu_to_dummy(pmu));
+
+ kref_get(&d->refcount);
+
+ return pmu;
+}
+
+static void dummy_pmu_put(struct pmu *pmu)
+{
+ struct dummy_device *d = pmu_to_device(pmu_to_dummy(pmu));
+
+ kref_put(&d->refcount, device_release);
+}
+
static int device_init(struct dummy_device *d)
{
int ret;
+ if (WARN_ONCE(d->pmu.name, "Cannot re-register pmu.\n"))
+ return -EINVAL;
+
d->pmu.base = (struct pmu){
.attr_groups = attr_groups,
.module = THIS_MODULE,
@@ -209,6 +220,8 @@ static int device_init(struct dummy_device *d)
.start = dummy_pmu_event_start,
.stop = dummy_pmu_event_stop,
.read = dummy_pmu_event_read,
+ .get = dummy_pmu_get,
+ .put = dummy_pmu_put,
};
d->pmu.name = kasprintf(GFP_KERNEL, "dummy_pmu_%u", d->instance);
@@ -217,12 +230,22 @@ static int device_init(struct dummy_device *d)
ret = perf_pmu_register(&d->pmu.base, d->pmu.name, -1);
if (ret)
- return ret;
+ goto fail;
d->pmu.registered = true;
pr_info("Device registered: %s\n", d->pmu.name);
return 0;
+
+fail:
+ /*
+ * See device_release: if name is non-NULL, dummy_pmu was registered
+ * with perf and needs cleanup
+ */
+ kfree(d->pmu.name);
+ d->pmu.name = NULL;
+
+ return ret;
}
static void device_exit(struct dummy_device *d)
@@ -237,7 +260,10 @@ static void device_release(struct kref *ref)
{
struct dummy_device *d = container_of(ref, struct dummy_device, refcount);
- kfree(d->pmu.name);
+ if (d->pmu.name) {
+ perf_pmu_free(&d->pmu.base);
+ kfree(d->pmu.name);
+ }
kfree(d);
}
--
2.46.2