// 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 #include #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) { kfree(nd_pmu->pmu.attr_groups); nvdimm_pmu_free_hotplug_memory(nd_pmu); 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); } EXPORT_SYMBOL_GPL(unregister_nvdimm_pmu);