A common interface is added to get performance stats reporting
support for nvdimm devices. Added interface includes support for
pmu register/unregister functions which can be used to set
arch/platform specific data such as supported events and pmu
event functions like event_init/add/read/del.
User could use the standard perf tool to access perf
events exposed via pmu.

Interface also adds cpu hotplug functions to nvdimm pmu incase
its not added by arch/platform specific code. It will adds
cpumask to the nvdimm pmu to designate a cpu to collect the
counter data.

Signed-off-by: Kajol Jain <kj...@linux.ibm.com>
---
 drivers/nvdimm/Makefile  |   1 +
 drivers/nvdimm/nd_perf.c | 195 +++++++++++++++++++++++++++++++++++++++
 include/linux/nd.h       |   3 +
 3 files changed, 199 insertions(+)
 create mode 100644 drivers/nvdimm/nd_perf.c

diff --git a/drivers/nvdimm/Makefile b/drivers/nvdimm/Makefile
index 29203f3d3069..25dba6095612 100644
--- a/drivers/nvdimm/Makefile
+++ b/drivers/nvdimm/Makefile
@@ -18,6 +18,7 @@ nd_e820-y := e820.o
 libnvdimm-y := core.o
 libnvdimm-y += bus.o
 libnvdimm-y += dimm_devs.o
+libnvdimm-y += nd_perf.o
 libnvdimm-y += dimm.o
 libnvdimm-y += region_devs.o
 libnvdimm-y += region.o
diff --git a/drivers/nvdimm/nd_perf.c b/drivers/nvdimm/nd_perf.c
new file mode 100644
index 000000000000..a7679699118a
--- /dev/null
+++ b/drivers/nvdimm/nd_perf.c
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * nd_perf.c: NVDIMM Device Performance Monitoring Unit support
+ *
+ * Perf interface to expose nvdimm performance stats.
+ *
+ * Copyright (C) 2021 IBM Corporation
+ */
+
+#define pr_fmt(fmt) "nvdimm_pmu: " fmt
+
+#include <linux/nd.h>
+
+static ssize_t nvdimm_pmu_cpumask_show(struct device *dev,
+                           struct device_attribute *attr, char *buf)
+{
+       struct pmu *pmu = dev_get_drvdata(dev);
+       struct nvdimm_pmu *nd_pmu;
+
+       nd_pmu = container_of(pmu, struct nvdimm_pmu, pmu);
+
+       return cpumap_print_to_pagebuf(true, buf, cpumask_of(nd_pmu->cpu));
+}
+
+static int nvdimm_pmu_offline_cpu(unsigned int cpu, struct hlist_node *node)
+{
+       struct nvdimm_pmu *nd_pmu;
+       int target, nodeid;
+       const struct cpumask *cpumask;
+
+       nd_pmu = hlist_entry_safe(node, struct nvdimm_pmu, node);
+
+       if (!nd_pmu->dev)
+               return -1;
+
+       if (cpu != nd_pmu->cpu)
+               return 0;
+
+       /*
+        * here function dev_to_node will return node id for given nvdimm device
+        * which need to be assigned to variable nodeid but incase that node is
+        * offline, numa_map_to_online_node function will lookup for next
+        * closest online node and assigned that value to nodeid.
+        */
+       nodeid = numa_map_to_online_node(dev_to_node(nd_pmu->dev));
+
+       cpumask = cpumask_of_node(nodeid);
+       target = cpumask_any_but(cpumask, cpu);
+       nd_pmu->cpu = target;
+
+       if (target < 0 || target >= nr_cpu_ids)
+               return -1;
+
+       return 0;
+}
+
+static int create_cpumask_attr_group(struct nvdimm_pmu *nd_pmu)
+{
+       struct perf_pmu_events_attr *attr;
+       struct attribute **attrs;
+       struct attribute_group *nvdimm_pmu_cpumask_group;
+
+       attr = kzalloc(sizeof(*attr), GFP_KERNEL);
+       if (!attr)
+               return -ENOMEM;
+
+       attrs = kzalloc(2 * sizeof(struct attribute *), GFP_KERNEL);
+       if (!attrs) {
+               kfree(attr);
+               return -ENOMEM;
+       }
+
+       /* Allocate memory for cpumask attribute group */
+       nvdimm_pmu_cpumask_group = kzalloc(sizeof(*nvdimm_pmu_cpumask_group), 
GFP_KERNEL);
+       if (!nvdimm_pmu_cpumask_group) {
+               kfree(attr);
+               kfree(attrs);
+               return -ENOMEM;
+       }
+
+       sysfs_attr_init(&attr->attr.attr);
+       attr->attr.attr.name = "cpumask";
+       attr->attr.attr.mode = 0444;
+       attr->attr.show = nvdimm_pmu_cpumask_show;
+       attrs[0] = &attr->attr.attr;
+       attrs[1] = NULL;
+
+       nvdimm_pmu_cpumask_group->attrs = attrs;
+       nd_pmu->attr_groups[NVDIMM_PMU_CPUMASK_ATTR] = nvdimm_pmu_cpumask_group;
+       return 0;
+}
+
+static int nvdimm_pmu_cpu_hotplug_init(struct nvdimm_pmu *nd_pmu)
+{
+       int nodeid,  rc;
+       const struct cpumask *cpumask;
+
+       nodeid = numa_map_to_online_node(dev_to_node(nd_pmu->dev));
+       cpumask = cpumask_of_node(nodeid);
+       nd_pmu->cpu = cpumask_any(cpumask);
+
+       rc = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, "perf/nvdimm:online",
+                                    NULL, nvdimm_pmu_offline_cpu);
+
+       if (rc < 0)
+               return rc;
+
+       nd_pmu->cpuhp_state = rc;
+
+       /* Register the pmu instance for cpu hotplug */
+       rc = cpuhp_state_add_instance_nocalls(nd_pmu->cpuhp_state, 
&nd_pmu->node);
+       if (rc) {
+               cpuhp_remove_multi_state(nd_pmu->cpuhp_state);
+               return rc;
+       }
+
+       /* Create cpumask attribute group */
+       rc = create_cpumask_attr_group(nd_pmu);
+       if (rc) {
+               cpuhp_state_remove_instance_nocalls(nd_pmu->cpuhp_state, 
&nd_pmu->node);
+               cpuhp_remove_multi_state(nd_pmu->cpuhp_state);
+               return rc;
+       }
+
+       return 0;
+}
+
+void nvdimm_pmu_free_hotplug_memory(struct nvdimm_pmu *nd_pmu)
+{
+       cpuhp_state_remove_instance_nocalls(nd_pmu->cpuhp_state, &nd_pmu->node);
+       cpuhp_remove_multi_state(nd_pmu->cpuhp_state);
+
+       if (nd_pmu->attr_groups[NVDIMM_PMU_CPUMASK_ATTR])
+               kfree(nd_pmu->attr_groups[NVDIMM_PMU_CPUMASK_ATTR]->attrs);
+       kfree(nd_pmu->attr_groups[NVDIMM_PMU_CPUMASK_ATTR]);
+}
+
+int register_nvdimm_pmu(struct nvdimm_pmu *nd_pmu, struct platform_device 
*pdev)
+{
+       int rc;
+
+       if (!nd_pmu || !pdev)
+               return -EINVAL;
+
+       /* event functions like add/del/read/event_init should not be NULL */
+       if (WARN_ON_ONCE(!(nd_pmu->event_init && nd_pmu->add && nd_pmu->del && 
nd_pmu->read)))
+               return -EINVAL;
+
+       nd_pmu->pmu.task_ctx_nr = perf_invalid_context;
+       nd_pmu->pmu.name = nd_pmu->name;
+       nd_pmu->pmu.event_init = nd_pmu->event_init;
+       nd_pmu->pmu.add = nd_pmu->add;
+       nd_pmu->pmu.del = nd_pmu->del;
+       nd_pmu->pmu.read = nd_pmu->read;
+
+       nd_pmu->pmu.attr_groups = nd_pmu->attr_groups;
+       nd_pmu->pmu.capabilities = PERF_PMU_CAP_NO_INTERRUPT |
+                               PERF_PMU_CAP_NO_EXCLUDE;
+
+       /*
+        * Add platform_device->dev pointer to nvdimm_pmu to access
+        * device data in events functions.
+        */
+       nd_pmu->dev = &pdev->dev;
+
+       if (!nd_pmu->attr_groups[NVDIMM_PMU_CPUMASK_ATTR]) {
+               /* init cpuhotplug */
+               rc = nvdimm_pmu_cpu_hotplug_init(nd_pmu);
+               if (rc) {
+                       pr_info("cpu hotplug feature failed for device: %s\n", 
nd_pmu->name);
+                       return rc;
+               }
+       }
+
+       rc = perf_pmu_register(&nd_pmu->pmu, nd_pmu->name, -1);
+       if (rc) {
+               nvdimm_pmu_free_hotplug_memory(nd_pmu);
+               return rc;
+       }
+
+       pr_info("%s NVDIMM performance monitor support registered\n",
+               nd_pmu->name);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(register_nvdimm_pmu);
+
+void unregister_nvdimm_pmu(struct nvdimm_pmu *nd_pmu)
+{
+
+       /* handle freeing of memory nd_pmu in arch specific code */
+       perf_pmu_unregister(&nd_pmu->pmu);
+       nvdimm_pmu_free_hotplug_memory(nd_pmu);
+}
+EXPORT_SYMBOL_GPL(unregister_nvdimm_pmu);
diff --git a/include/linux/nd.h b/include/linux/nd.h
index 3baeeb7c3c41..f71744daa234 100644
--- a/include/linux/nd.h
+++ b/include/linux/nd.h
@@ -62,6 +62,9 @@ struct nvdimm_pmu {
        enum cpuhp_state cpuhp_state;
 };
 
+int register_nvdimm_pmu(struct nvdimm_pmu *nvdimm, struct platform_device 
*pdev);
+void unregister_nvdimm_pmu(struct nvdimm_pmu *nd_pmu);
+
 struct nd_device_driver {
        struct device_driver drv;
        unsigned long type;
-- 
2.27.0

Reply via email to