123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- // 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>
- #include <linux/platform_device.h>
- #define EVENT(_name, _code) enum{_name = _code}
- /*
- * NVDIMM Events codes.
- */
- /* Controller Reset Count */
- EVENT(CTL_RES_CNT, 0x1);
- /* Controller Reset Elapsed Time */
- EVENT(CTL_RES_TM, 0x2);
- /* Power-on Seconds */
- EVENT(POWERON_SECS, 0x3);
- /* Life Remaining */
- EVENT(MEM_LIFE, 0x4);
- /* Critical Resource Utilization */
- EVENT(CRI_RES_UTIL, 0x5);
- /* Host Load Count */
- EVENT(HOST_L_CNT, 0x6);
- /* Host Store Count */
- EVENT(HOST_S_CNT, 0x7);
- /* Host Store Duration */
- EVENT(HOST_S_DUR, 0x8);
- /* Host Load Duration */
- EVENT(HOST_L_DUR, 0x9);
- /* Media Read Count */
- EVENT(MED_R_CNT, 0xa);
- /* Media Write Count */
- EVENT(MED_W_CNT, 0xb);
- /* Media Read Duration */
- EVENT(MED_R_DUR, 0xc);
- /* Media Write Duration */
- EVENT(MED_W_DUR, 0xd);
- /* Cache Read Hit Count */
- EVENT(CACHE_RH_CNT, 0xe);
- /* Cache Write Hit Count */
- EVENT(CACHE_WH_CNT, 0xf);
- /* Fast Write Count */
- EVENT(FAST_W_CNT, 0x10);
- NVDIMM_EVENT_ATTR(ctl_res_cnt, CTL_RES_CNT);
- NVDIMM_EVENT_ATTR(ctl_res_tm, CTL_RES_TM);
- NVDIMM_EVENT_ATTR(poweron_secs, POWERON_SECS);
- NVDIMM_EVENT_ATTR(mem_life, MEM_LIFE);
- NVDIMM_EVENT_ATTR(cri_res_util, CRI_RES_UTIL);
- NVDIMM_EVENT_ATTR(host_l_cnt, HOST_L_CNT);
- NVDIMM_EVENT_ATTR(host_s_cnt, HOST_S_CNT);
- NVDIMM_EVENT_ATTR(host_s_dur, HOST_S_DUR);
- NVDIMM_EVENT_ATTR(host_l_dur, HOST_L_DUR);
- NVDIMM_EVENT_ATTR(med_r_cnt, MED_R_CNT);
- NVDIMM_EVENT_ATTR(med_w_cnt, MED_W_CNT);
- NVDIMM_EVENT_ATTR(med_r_dur, MED_R_DUR);
- NVDIMM_EVENT_ATTR(med_w_dur, MED_W_DUR);
- NVDIMM_EVENT_ATTR(cache_rh_cnt, CACHE_RH_CNT);
- NVDIMM_EVENT_ATTR(cache_wh_cnt, CACHE_WH_CNT);
- NVDIMM_EVENT_ATTR(fast_w_cnt, FAST_W_CNT);
- static struct attribute *nvdimm_events_attr[] = {
- NVDIMM_EVENT_PTR(CTL_RES_CNT),
- NVDIMM_EVENT_PTR(CTL_RES_TM),
- NVDIMM_EVENT_PTR(POWERON_SECS),
- NVDIMM_EVENT_PTR(MEM_LIFE),
- NVDIMM_EVENT_PTR(CRI_RES_UTIL),
- NVDIMM_EVENT_PTR(HOST_L_CNT),
- NVDIMM_EVENT_PTR(HOST_S_CNT),
- NVDIMM_EVENT_PTR(HOST_S_DUR),
- NVDIMM_EVENT_PTR(HOST_L_DUR),
- NVDIMM_EVENT_PTR(MED_R_CNT),
- NVDIMM_EVENT_PTR(MED_W_CNT),
- NVDIMM_EVENT_PTR(MED_R_DUR),
- NVDIMM_EVENT_PTR(MED_W_DUR),
- NVDIMM_EVENT_PTR(CACHE_RH_CNT),
- NVDIMM_EVENT_PTR(CACHE_WH_CNT),
- NVDIMM_EVENT_PTR(FAST_W_CNT),
- NULL
- };
- static struct attribute_group nvdimm_pmu_events_group = {
- .name = "events",
- .attrs = nvdimm_events_attr,
- };
- PMU_FORMAT_ATTR(event, "config:0-4");
- static struct attribute *nvdimm_pmu_format_attr[] = {
- &format_attr_event.attr,
- NULL,
- };
- static struct attribute_group nvdimm_pmu_format_group = {
- .name = "format",
- .attrs = nvdimm_pmu_format_attr,
- };
- ssize_t nvdimm_events_sysfs_show(struct device *dev,
- struct device_attribute *attr, char *page)
- {
- struct perf_pmu_events_attr *pmu_attr;
- pmu_attr = container_of(attr, struct perf_pmu_events_attr, attr);
- return sprintf(page, "event=0x%02llx\n", pmu_attr->id);
- }
- 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_cpu_offline(unsigned int cpu, struct hlist_node *node)
- {
- struct nvdimm_pmu *nd_pmu;
- u32 target;
- int nodeid;
- const struct cpumask *cpumask;
- nd_pmu = hlist_entry_safe(node, struct nvdimm_pmu, node);
- /* Clear it, incase given cpu is set in nd_pmu->arch_cpumask */
- cpumask_test_and_clear_cpu(cpu, &nd_pmu->arch_cpumask);
- /*
- * If given cpu is not same as current designated cpu for
- * counter access, just return.
- */
- if (cpu != nd_pmu->cpu)
- return 0;
- /* Check for any active cpu in nd_pmu->arch_cpumask */
- target = cpumask_any(&nd_pmu->arch_cpumask);
- /*
- * Incase we don't have any active cpu in nd_pmu->arch_cpumask,
- * check in given cpu's numa node list.
- */
- if (target >= nr_cpu_ids) {
- nodeid = cpu_to_node(cpu);
- cpumask = cpumask_of_node(nodeid);
- target = cpumask_any_but(cpumask, cpu);
- }
- nd_pmu->cpu = target;
- /* Migrate nvdimm pmu events to the new target cpu if valid */
- if (target >= 0 && target < nr_cpu_ids)
- perf_pmu_migrate_context(&nd_pmu->pmu, cpu, target);
- return 0;
- }
- static int nvdimm_pmu_cpu_online(unsigned int cpu, struct hlist_node *node)
- {
- struct nvdimm_pmu *nd_pmu;
- nd_pmu = hlist_entry_safe(node, struct nvdimm_pmu, node);
- if (nd_pmu->cpu >= nr_cpu_ids)
- nd_pmu->cpu = cpu;
- return 0;
- }
- static int create_cpumask_attr_group(struct nvdimm_pmu *nd_pmu)
- {
- struct perf_pmu_events_attr *pmu_events_attr;
- struct attribute **attrs_group;
- struct attribute_group *nvdimm_pmu_cpumask_group;
- pmu_events_attr = kzalloc(sizeof(*pmu_events_attr), GFP_KERNEL);
- if (!pmu_events_attr)
- return -ENOMEM;
- attrs_group = kzalloc(2 * sizeof(struct attribute *), GFP_KERNEL);
- if (!attrs_group) {
- kfree(pmu_events_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(pmu_events_attr);
- kfree(attrs_group);
- return -ENOMEM;
- }
- sysfs_attr_init(&pmu_events_attr->attr.attr);
- pmu_events_attr->attr.attr.name = "cpumask";
- pmu_events_attr->attr.attr.mode = 0444;
- pmu_events_attr->attr.show = nvdimm_pmu_cpumask_show;
- attrs_group[0] = &pmu_events_attr->attr.attr;
- attrs_group[1] = NULL;
- nvdimm_pmu_cpumask_group->attrs = attrs_group;
- nd_pmu->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;
- /*
- * Incase of cpu hotplug feature, arch specific code
- * can provide required cpumask which can be used
- * to get designatd cpu for counter access.
- * Check for any active cpu in nd_pmu->arch_cpumask.
- */
- if (!cpumask_empty(&nd_pmu->arch_cpumask)) {
- nd_pmu->cpu = cpumask_any(&nd_pmu->arch_cpumask);
- } else {
- /* pick active cpu from the cpumask of device numa node. */
- nodeid = 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",
- nvdimm_pmu_cpu_online, nvdimm_pmu_cpu_offline);
- 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;
- }
- static 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->pmu.attr_groups[NVDIMM_PMU_CPUMASK_ATTR])
- kfree(nd_pmu->pmu.attr_groups[NVDIMM_PMU_CPUMASK_ATTR]->attrs);
- kfree(nd_pmu->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 and pmu name should not be NULL */
- if (WARN_ON_ONCE(!(nd_pmu->pmu.event_init && nd_pmu->pmu.add &&
- nd_pmu->pmu.del && nd_pmu->pmu.read && nd_pmu->pmu.name)))
- return -EINVAL;
- nd_pmu->pmu.attr_groups = kzalloc((NVDIMM_PMU_NULL_ATTR + 1) *
- sizeof(struct attribute_group *), GFP_KERNEL);
- if (!nd_pmu->pmu.attr_groups)
- return -ENOMEM;
- /*
- * Add platform_device->dev pointer to nvdimm_pmu to access
- * device data in events functions.
- */
- nd_pmu->dev = &pdev->dev;
- /* Fill attribute groups for the nvdimm pmu device */
- nd_pmu->pmu.attr_groups[NVDIMM_PMU_FORMAT_ATTR] = &nvdimm_pmu_format_group;
- nd_pmu->pmu.attr_groups[NVDIMM_PMU_EVENT_ATTR] = &nvdimm_pmu_events_group;
- nd_pmu->pmu.attr_groups[NVDIMM_PMU_NULL_ATTR] = NULL;
- /* Fill attribute group for cpumask */
- rc = nvdimm_pmu_cpu_hotplug_init(nd_pmu);
- if (rc) {
- pr_info("cpu hotplug feature failed for device: %s\n", nd_pmu->pmu.name);
- kfree(nd_pmu->pmu.attr_groups);
- return rc;
- }
- rc = perf_pmu_register(&nd_pmu->pmu, nd_pmu->pmu.name, -1);
- if (rc) {
- nvdimm_pmu_free_hotplug_memory(nd_pmu);
- kfree(nd_pmu->pmu.attr_groups);
- return rc;
- }
- pr_info("%s NVDIMM performance monitor support registered\n",
- nd_pmu->pmu.name);
- return 0;
- }
- EXPORT_SYMBOL_GPL(register_nvdimm_pmu);
- void unregister_nvdimm_pmu(struct nvdimm_pmu *nd_pmu)
- {
- perf_pmu_unregister(&nd_pmu->pmu);
- nvdimm_pmu_free_hotplug_memory(nd_pmu);
- kfree(nd_pmu->pmu.attr_groups);
- kfree(nd_pmu);
- }
- EXPORT_SYMBOL_GPL(unregister_nvdimm_pmu);
|