| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2013 Advanced Micro Devices, Inc. |
| * |
| * Author: Jacob Shin <jacob.shin@amd.com> |
| */ |
| |
| #include <linux/perf_event.h> |
| #include <linux/percpu.h> |
| #include <linux/types.h> |
| #include <linux/slab.h> |
| #include <linux/init.h> |
| #include <linux/cpu.h> |
| #include <linux/cpumask.h> |
| #include <linux/cpufeature.h> |
| #include <linux/smp.h> |
| |
| #include <asm/perf_event.h> |
| #include <asm/msr.h> |
| |
| #define NUM_COUNTERS_NB 4 |
| #define NUM_COUNTERS_L2 4 |
| #define NUM_COUNTERS_L3 6 |
| |
| #define RDPMC_BASE_NB 6 |
| #define RDPMC_BASE_LLC 10 |
| |
| #define COUNTER_SHIFT 16 |
| #define UNCORE_NAME_LEN 16 |
| #define UNCORE_GROUP_MAX 256 |
| |
| #undef pr_fmt |
| #define pr_fmt(fmt) "amd_uncore: " fmt |
| |
| static int pmu_version; |
| |
| struct amd_uncore_ctx { |
| int refcnt; |
| int cpu; |
| struct perf_event **events; |
| struct hlist_node node; |
| }; |
| |
| struct amd_uncore_pmu { |
| char name[UNCORE_NAME_LEN]; |
| int num_counters; |
| int rdpmc_base; |
| u32 msr_base; |
| int group; |
| cpumask_t active_mask; |
| struct pmu pmu; |
| struct amd_uncore_ctx * __percpu *ctx; |
| }; |
| |
| enum { |
| UNCORE_TYPE_DF, |
| UNCORE_TYPE_L3, |
| UNCORE_TYPE_UMC, |
| |
| UNCORE_TYPE_MAX |
| }; |
| |
| union amd_uncore_info { |
| struct { |
| u64 aux_data:32; /* auxiliary data */ |
| u64 num_pmcs:8; /* number of counters */ |
| u64 gid:8; /* group id */ |
| u64 cid:8; /* context id */ |
| } split; |
| u64 full; |
| }; |
| |
| struct amd_uncore { |
| union amd_uncore_info __percpu *info; |
| struct amd_uncore_pmu *pmus; |
| unsigned int num_pmus; |
| bool init_done; |
| void (*scan)(struct amd_uncore *uncore, unsigned int cpu); |
| int (*init)(struct amd_uncore *uncore, unsigned int cpu); |
| void (*move)(struct amd_uncore *uncore, unsigned int cpu); |
| void (*free)(struct amd_uncore *uncore, unsigned int cpu); |
| }; |
| |
| static struct amd_uncore uncores[UNCORE_TYPE_MAX]; |
| |
| static struct amd_uncore_pmu *event_to_amd_uncore_pmu(struct perf_event *event) |
| { |
| return container_of(event->pmu, struct amd_uncore_pmu, pmu); |
| } |
| |
| static void amd_uncore_read(struct perf_event *event) |
| { |
| struct hw_perf_event *hwc = &event->hw; |
| u64 prev, new; |
| s64 delta; |
| |
| /* |
| * since we do not enable counter overflow interrupts, |
| * we do not have to worry about prev_count changing on us |
| */ |
| |
| prev = local64_read(&hwc->prev_count); |
| |
| /* |
| * Some uncore PMUs do not have RDPMC assignments. In such cases, |
| * read counts directly from the corresponding PERF_CTR. |
| */ |
| if (hwc->event_base_rdpmc < 0) |
| rdmsrl(hwc->event_base, new); |
| else |
| rdpmcl(hwc->event_base_rdpmc, new); |
| |
| local64_set(&hwc->prev_count, new); |
| delta = (new << COUNTER_SHIFT) - (prev << COUNTER_SHIFT); |
| delta >>= COUNTER_SHIFT; |
| local64_add(delta, &event->count); |
| } |
| |
| static void amd_uncore_start(struct perf_event *event, int flags) |
| { |
| struct hw_perf_event *hwc = &event->hw; |
| |
| if (flags & PERF_EF_RELOAD) |
| wrmsrl(hwc->event_base, (u64)local64_read(&hwc->prev_count)); |
| |
| hwc->state = 0; |
| wrmsrl(hwc->config_base, (hwc->config | ARCH_PERFMON_EVENTSEL_ENABLE)); |
| perf_event_update_userpage(event); |
| } |
| |
| static void amd_uncore_stop(struct perf_event *event, int flags) |
| { |
| struct hw_perf_event *hwc = &event->hw; |
| |
| wrmsrl(hwc->config_base, hwc->config); |
| hwc->state |= PERF_HES_STOPPED; |
| |
| if ((flags & PERF_EF_UPDATE) && !(hwc->state & PERF_HES_UPTODATE)) { |
| event->pmu->read(event); |
| hwc->state |= PERF_HES_UPTODATE; |
| } |
| } |
| |
| static int amd_uncore_add(struct perf_event *event, int flags) |
| { |
| int i; |
| struct amd_uncore_pmu *pmu = event_to_amd_uncore_pmu(event); |
| struct amd_uncore_ctx *ctx = *per_cpu_ptr(pmu->ctx, event->cpu); |
| struct hw_perf_event *hwc = &event->hw; |
| |
| /* are we already assigned? */ |
| if (hwc->idx != -1 && ctx->events[hwc->idx] == event) |
| goto out; |
| |
| for (i = 0; i < pmu->num_counters; i++) { |
| if (ctx->events[i] == event) { |
| hwc->idx = i; |
| goto out; |
| } |
| } |
| |
| /* if not, take the first available counter */ |
| hwc->idx = -1; |
| for (i = 0; i < pmu->num_counters; i++) { |
| struct perf_event *tmp = NULL; |
| |
| if (try_cmpxchg(&ctx->events[i], &tmp, event)) { |
| hwc->idx = i; |
| break; |
| } |
| } |
| |
| out: |
| if (hwc->idx == -1) |
| return -EBUSY; |
| |
| hwc->config_base = pmu->msr_base + (2 * hwc->idx); |
| hwc->event_base = pmu->msr_base + 1 + (2 * hwc->idx); |
| hwc->event_base_rdpmc = pmu->rdpmc_base + hwc->idx; |
| hwc->state = PERF_HES_UPTODATE | PERF_HES_STOPPED; |
| |
| if (pmu->rdpmc_base < 0) |
| hwc->event_base_rdpmc = -1; |
| |
| if (flags & PERF_EF_START) |
| event->pmu->start(event, PERF_EF_RELOAD); |
| |
| return 0; |
| } |
| |
| static void amd_uncore_del(struct perf_event *event, int flags) |
| { |
| int i; |
| struct amd_uncore_pmu *pmu = event_to_amd_uncore_pmu(event); |
| struct amd_uncore_ctx *ctx = *per_cpu_ptr(pmu->ctx, event->cpu); |
| struct hw_perf_event *hwc = &event->hw; |
| |
| event->pmu->stop(event, PERF_EF_UPDATE); |
| |
| for (i = 0; i < pmu->num_counters; i++) { |
| struct perf_event *tmp = event; |
| |
| if (try_cmpxchg(&ctx->events[i], &tmp, NULL)) |
| break; |
| } |
| |
| hwc->idx = -1; |
| } |
| |
| static int amd_uncore_event_init(struct perf_event *event) |
| { |
| struct amd_uncore_pmu *pmu; |
| struct amd_uncore_ctx *ctx; |
| struct hw_perf_event *hwc = &event->hw; |
| |
| if (event->attr.type != event->pmu->type) |
| return -ENOENT; |
| |
| if (event->cpu < 0) |
| return -EINVAL; |
| |
| pmu = event_to_amd_uncore_pmu(event); |
| ctx = *per_cpu_ptr(pmu->ctx, event->cpu); |
| if (!ctx) |
| return -ENODEV; |
| |
| /* |
| * NB and Last level cache counters (MSRs) are shared across all cores |
| * that share the same NB / Last level cache. On family 16h and below, |
| * Interrupts can be directed to a single target core, however, event |
| * counts generated by processes running on other cores cannot be masked |
| * out. So we do not support sampling and per-thread events via |
| * CAP_NO_INTERRUPT, and we do not enable counter overflow interrupts: |
| */ |
| hwc->config = event->attr.config; |
| hwc->idx = -1; |
| |
| /* |
| * since request can come in to any of the shared cores, we will remap |
| * to a single common cpu. |
| */ |
| event->cpu = ctx->cpu; |
| |
| return 0; |
| } |
| |
| static umode_t |
| amd_f17h_uncore_is_visible(struct kobject *kobj, struct attribute *attr, int i) |
| { |
| return boot_cpu_data.x86 >= 0x17 && boot_cpu_data.x86 < 0x19 ? |
| attr->mode : 0; |
| } |
| |
| static umode_t |
| amd_f19h_uncore_is_visible(struct kobject *kobj, struct attribute *attr, int i) |
| { |
| return boot_cpu_data.x86 >= 0x19 ? attr->mode : 0; |
| } |
| |
| static ssize_t amd_uncore_attr_show_cpumask(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct pmu *ptr = dev_get_drvdata(dev); |
| struct amd_uncore_pmu *pmu = container_of(ptr, struct amd_uncore_pmu, pmu); |
| |
| return cpumap_print_to_pagebuf(true, buf, &pmu->active_mask); |
| } |
| static DEVICE_ATTR(cpumask, S_IRUGO, amd_uncore_attr_show_cpumask, NULL); |
| |
| static struct attribute *amd_uncore_attrs[] = { |
| &dev_attr_cpumask.attr, |
| NULL, |
| }; |
| |
| static struct attribute_group amd_uncore_attr_group = { |
| .attrs = amd_uncore_attrs, |
| }; |
| |
| #define DEFINE_UNCORE_FORMAT_ATTR(_var, _name, _format) \ |
| static ssize_t __uncore_##_var##_show(struct device *dev, \ |
| struct device_attribute *attr, \ |
| char *page) \ |
| { \ |
| BUILD_BUG_ON(sizeof(_format) >= PAGE_SIZE); \ |
| return sprintf(page, _format "\n"); \ |
| } \ |
| static struct device_attribute format_attr_##_var = \ |
| __ATTR(_name, 0444, __uncore_##_var##_show, NULL) |
| |
| DEFINE_UNCORE_FORMAT_ATTR(event12, event, "config:0-7,32-35"); |
| DEFINE_UNCORE_FORMAT_ATTR(event14, event, "config:0-7,32-35,59-60"); /* F17h+ DF */ |
| DEFINE_UNCORE_FORMAT_ATTR(event14v2, event, "config:0-7,32-37"); /* PerfMonV2 DF */ |
| DEFINE_UNCORE_FORMAT_ATTR(event8, event, "config:0-7"); /* F17h+ L3, PerfMonV2 UMC */ |
| DEFINE_UNCORE_FORMAT_ATTR(umask8, umask, "config:8-15"); |
| DEFINE_UNCORE_FORMAT_ATTR(umask12, umask, "config:8-15,24-27"); /* PerfMonV2 DF */ |
| DEFINE_UNCORE_FORMAT_ATTR(coreid, coreid, "config:42-44"); /* F19h L3 */ |
| DEFINE_UNCORE_FORMAT_ATTR(slicemask, slicemask, "config:48-51"); /* F17h L3 */ |
| DEFINE_UNCORE_FORMAT_ATTR(threadmask8, threadmask, "config:56-63"); /* F17h L3 */ |
| DEFINE_UNCORE_FORMAT_ATTR(threadmask2, threadmask, "config:56-57"); /* F19h L3 */ |
| DEFINE_UNCORE_FORMAT_ATTR(enallslices, enallslices, "config:46"); /* F19h L3 */ |
| DEFINE_UNCORE_FORMAT_ATTR(enallcores, enallcores, "config:47"); /* F19h L3 */ |
| DEFINE_UNCORE_FORMAT_ATTR(sliceid, sliceid, "config:48-50"); /* F19h L3 */ |
| DEFINE_UNCORE_FORMAT_ATTR(rdwrmask, rdwrmask, "config:8-9"); /* PerfMonV2 UMC */ |
| |
| /* Common DF and NB attributes */ |
| static struct attribute *amd_uncore_df_format_attr[] = { |
| &format_attr_event12.attr, /* event */ |
| &format_attr_umask8.attr, /* umask */ |
| NULL, |
| }; |
| |
| /* Common L2 and L3 attributes */ |
| static struct attribute *amd_uncore_l3_format_attr[] = { |
| &format_attr_event12.attr, /* event */ |
| &format_attr_umask8.attr, /* umask */ |
| NULL, /* threadmask */ |
| NULL, |
| }; |
| |
| /* Common UMC attributes */ |
| static struct attribute *amd_uncore_umc_format_attr[] = { |
| &format_attr_event8.attr, /* event */ |
| &format_attr_rdwrmask.attr, /* rdwrmask */ |
| NULL, |
| }; |
| |
| /* F17h unique L3 attributes */ |
| static struct attribute *amd_f17h_uncore_l3_format_attr[] = { |
| &format_attr_slicemask.attr, /* slicemask */ |
| NULL, |
| }; |
| |
| /* F19h unique L3 attributes */ |
| static struct attribute *amd_f19h_uncore_l3_format_attr[] = { |
| &format_attr_coreid.attr, /* coreid */ |
| &format_attr_enallslices.attr, /* enallslices */ |
| &format_attr_enallcores.attr, /* enallcores */ |
| &format_attr_sliceid.attr, /* sliceid */ |
| NULL, |
| }; |
| |
| static struct attribute_group amd_uncore_df_format_group = { |
| .name = "format", |
| .attrs = amd_uncore_df_format_attr, |
| }; |
| |
| static struct attribute_group amd_uncore_l3_format_group = { |
| .name = "format", |
| .attrs = amd_uncore_l3_format_attr, |
| }; |
| |
| static struct attribute_group amd_f17h_uncore_l3_format_group = { |
| .name = "format", |
| .attrs = amd_f17h_uncore_l3_format_attr, |
| .is_visible = amd_f17h_uncore_is_visible, |
| }; |
| |
| static struct attribute_group amd_f19h_uncore_l3_format_group = { |
| .name = "format", |
| .attrs = amd_f19h_uncore_l3_format_attr, |
| .is_visible = amd_f19h_uncore_is_visible, |
| }; |
| |
| static struct attribute_group amd_uncore_umc_format_group = { |
| .name = "format", |
| .attrs = amd_uncore_umc_format_attr, |
| }; |
| |
| static const struct attribute_group *amd_uncore_df_attr_groups[] = { |
| &amd_uncore_attr_group, |
| &amd_uncore_df_format_group, |
| NULL, |
| }; |
| |
| static const struct attribute_group *amd_uncore_l3_attr_groups[] = { |
| &amd_uncore_attr_group, |
| &amd_uncore_l3_format_group, |
| NULL, |
| }; |
| |
| static const struct attribute_group *amd_uncore_l3_attr_update[] = { |
| &amd_f17h_uncore_l3_format_group, |
| &amd_f19h_uncore_l3_format_group, |
| NULL, |
| }; |
| |
| static const struct attribute_group *amd_uncore_umc_attr_groups[] = { |
| &amd_uncore_attr_group, |
| &amd_uncore_umc_format_group, |
| NULL, |
| }; |
| |
| static __always_inline |
| int amd_uncore_ctx_cid(struct amd_uncore *uncore, unsigned int cpu) |
| { |
| union amd_uncore_info *info = per_cpu_ptr(uncore->info, cpu); |
| return info->split.cid; |
| } |
| |
| static __always_inline |
| int amd_uncore_ctx_gid(struct amd_uncore *uncore, unsigned int cpu) |
| { |
| union amd_uncore_info *info = per_cpu_ptr(uncore->info, cpu); |
| return info->split.gid; |
| } |
| |
| static __always_inline |
| int amd_uncore_ctx_num_pmcs(struct amd_uncore *uncore, unsigned int cpu) |
| { |
| union amd_uncore_info *info = per_cpu_ptr(uncore->info, cpu); |
| return info->split.num_pmcs; |
| } |
| |
| static void amd_uncore_ctx_free(struct amd_uncore *uncore, unsigned int cpu) |
| { |
| struct amd_uncore_pmu *pmu; |
| struct amd_uncore_ctx *ctx; |
| int i; |
| |
| if (!uncore->init_done) |
| return; |
| |
| for (i = 0; i < uncore->num_pmus; i++) { |
| pmu = &uncore->pmus[i]; |
| ctx = *per_cpu_ptr(pmu->ctx, cpu); |
| if (!ctx) |
| continue; |
| |
| if (cpu == ctx->cpu) |
| cpumask_clear_cpu(cpu, &pmu->active_mask); |
| |
| if (!--ctx->refcnt) { |
| kfree(ctx->events); |
| kfree(ctx); |
| } |
| |
| *per_cpu_ptr(pmu->ctx, cpu) = NULL; |
| } |
| } |
| |
| static int amd_uncore_ctx_init(struct amd_uncore *uncore, unsigned int cpu) |
| { |
| struct amd_uncore_ctx *curr, *prev; |
| struct amd_uncore_pmu *pmu; |
| int node, cid, gid, i, j; |
| |
| if (!uncore->init_done || !uncore->num_pmus) |
| return 0; |
| |
| cid = amd_uncore_ctx_cid(uncore, cpu); |
| gid = amd_uncore_ctx_gid(uncore, cpu); |
| |
| for (i = 0; i < uncore->num_pmus; i++) { |
| pmu = &uncore->pmus[i]; |
| *per_cpu_ptr(pmu->ctx, cpu) = NULL; |
| curr = NULL; |
| |
| /* Check for group exclusivity */ |
| if (gid != pmu->group) |
| continue; |
| |
| /* Find a sibling context */ |
| for_each_online_cpu(j) { |
| if (cpu == j) |
| continue; |
| |
| prev = *per_cpu_ptr(pmu->ctx, j); |
| if (!prev) |
| continue; |
| |
| if (cid == amd_uncore_ctx_cid(uncore, j)) { |
| curr = prev; |
| break; |
| } |
| } |
| |
| /* Allocate context if sibling does not exist */ |
| if (!curr) { |
| node = cpu_to_node(cpu); |
| curr = kzalloc_node(sizeof(*curr), GFP_KERNEL, node); |
| if (!curr) |
| goto fail; |
| |
| curr->cpu = cpu; |
| curr->events = kzalloc_node(sizeof(*curr->events) * |
| pmu->num_counters, |
| GFP_KERNEL, node); |
| if (!curr->events) { |
| kfree(curr); |
| goto fail; |
| } |
| |
| cpumask_set_cpu(cpu, &pmu->active_mask); |
| } |
| |
| curr->refcnt++; |
| *per_cpu_ptr(pmu->ctx, cpu) = curr; |
| } |
| |
| return 0; |
| |
| fail: |
| amd_uncore_ctx_free(uncore, cpu); |
| |
| return -ENOMEM; |
| } |
| |
| static void amd_uncore_ctx_move(struct amd_uncore *uncore, unsigned int cpu) |
| { |
| struct amd_uncore_ctx *curr, *next; |
| struct amd_uncore_pmu *pmu; |
| int i, j; |
| |
| if (!uncore->init_done) |
| return; |
| |
| for (i = 0; i < uncore->num_pmus; i++) { |
| pmu = &uncore->pmus[i]; |
| curr = *per_cpu_ptr(pmu->ctx, cpu); |
| if (!curr) |
| continue; |
| |
| /* Migrate to a shared sibling if possible */ |
| for_each_online_cpu(j) { |
| next = *per_cpu_ptr(pmu->ctx, j); |
| if (!next || cpu == j) |
| continue; |
| |
| if (curr == next) { |
| perf_pmu_migrate_context(&pmu->pmu, cpu, j); |
| cpumask_clear_cpu(cpu, &pmu->active_mask); |
| cpumask_set_cpu(j, &pmu->active_mask); |
| next->cpu = j; |
| break; |
| } |
| } |
| } |
| } |
| |
| static int amd_uncore_cpu_starting(unsigned int cpu) |
| { |
| struct amd_uncore *uncore; |
| int i; |
| |
| for (i = 0; i < UNCORE_TYPE_MAX; i++) { |
| uncore = &uncores[i]; |
| uncore->scan(uncore, cpu); |
| } |
| |
| return 0; |
| } |
| |
| static int amd_uncore_cpu_online(unsigned int cpu) |
| { |
| struct amd_uncore *uncore; |
| int i; |
| |
| for (i = 0; i < UNCORE_TYPE_MAX; i++) { |
| uncore = &uncores[i]; |
| if (uncore->init(uncore, cpu)) |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int amd_uncore_cpu_down_prepare(unsigned int cpu) |
| { |
| struct amd_uncore *uncore; |
| int i; |
| |
| for (i = 0; i < UNCORE_TYPE_MAX; i++) { |
| uncore = &uncores[i]; |
| uncore->move(uncore, cpu); |
| } |
| |
| return 0; |
| } |
| |
| static int amd_uncore_cpu_dead(unsigned int cpu) |
| { |
| struct amd_uncore *uncore; |
| int i; |
| |
| for (i = 0; i < UNCORE_TYPE_MAX; i++) { |
| uncore = &uncores[i]; |
| uncore->free(uncore, cpu); |
| } |
| |
| return 0; |
| } |
| |
| static int amd_uncore_df_event_init(struct perf_event *event) |
| { |
| struct hw_perf_event *hwc = &event->hw; |
| int ret = amd_uncore_event_init(event); |
| |
| if (ret || pmu_version < 2) |
| return ret; |
| |
| hwc->config = event->attr.config & |
| (pmu_version >= 2 ? AMD64_PERFMON_V2_RAW_EVENT_MASK_NB : |
| AMD64_RAW_EVENT_MASK_NB); |
| |
| return 0; |
| } |
| |
| static int amd_uncore_df_add(struct perf_event *event, int flags) |
| { |
| int ret = amd_uncore_add(event, flags & ~PERF_EF_START); |
| struct hw_perf_event *hwc = &event->hw; |
| |
| if (ret) |
| return ret; |
| |
| /* |
| * The first four DF counters are accessible via RDPMC index 6 to 9 |
| * followed by the L3 counters from index 10 to 15. For processors |
| * with more than four DF counters, the DF RDPMC assignments become |
| * discontiguous as the additional counters are accessible starting |
| * from index 16. |
| */ |
| if (hwc->idx >= NUM_COUNTERS_NB) |
| hwc->event_base_rdpmc += NUM_COUNTERS_L3; |
| |
| /* Delayed start after rdpmc base update */ |
| if (flags & PERF_EF_START) |
| amd_uncore_start(event, PERF_EF_RELOAD); |
| |
| return 0; |
| } |
| |
| static |
| void amd_uncore_df_ctx_scan(struct amd_uncore *uncore, unsigned int cpu) |
| { |
| union cpuid_0x80000022_ebx ebx; |
| union amd_uncore_info info; |
| |
| if (!boot_cpu_has(X86_FEATURE_PERFCTR_NB)) |
| return; |
| |
| info.split.aux_data = 0; |
| info.split.num_pmcs = NUM_COUNTERS_NB; |
| info.split.gid = 0; |
| info.split.cid = topology_logical_package_id(cpu); |
| |
| if (pmu_version >= 2) { |
| ebx.full = cpuid_ebx(EXT_PERFMON_DEBUG_FEATURES); |
| info.split.num_pmcs = ebx.split.num_df_pmc; |
| } |
| |
| *per_cpu_ptr(uncore->info, cpu) = info; |
| } |
| |
| static |
| int amd_uncore_df_ctx_init(struct amd_uncore *uncore, unsigned int cpu) |
| { |
| struct attribute **df_attr = amd_uncore_df_format_attr; |
| struct amd_uncore_pmu *pmu; |
| int num_counters; |
| |
| /* Run just once */ |
| if (uncore->init_done) |
| return amd_uncore_ctx_init(uncore, cpu); |
| |
| num_counters = amd_uncore_ctx_num_pmcs(uncore, cpu); |
| if (!num_counters) |
| goto done; |
| |
| /* No grouping, single instance for a system */ |
| uncore->pmus = kzalloc(sizeof(*uncore->pmus), GFP_KERNEL); |
| if (!uncore->pmus) |
| goto done; |
| |
| /* |
| * For Family 17h and above, the Northbridge counters are repurposed |
| * as Data Fabric counters. The PMUs are exported based on family as |
| * either NB or DF. |
| */ |
| pmu = &uncore->pmus[0]; |
| strscpy(pmu->name, boot_cpu_data.x86 >= 0x17 ? "amd_df" : "amd_nb", |
| sizeof(pmu->name)); |
| pmu->num_counters = num_counters; |
| pmu->msr_base = MSR_F15H_NB_PERF_CTL; |
| pmu->rdpmc_base = RDPMC_BASE_NB; |
| pmu->group = amd_uncore_ctx_gid(uncore, cpu); |
| |
| if (pmu_version >= 2) { |
| *df_attr++ = &format_attr_event14v2.attr; |
| *df_attr++ = &format_attr_umask12.attr; |
| } else if (boot_cpu_data.x86 >= 0x17) { |
| *df_attr = &format_attr_event14.attr; |
| } |
| |
| pmu->ctx = alloc_percpu(struct amd_uncore_ctx *); |
| if (!pmu->ctx) |
| goto done; |
| |
| pmu->pmu = (struct pmu) { |
| .task_ctx_nr = perf_invalid_context, |
| .attr_groups = amd_uncore_df_attr_groups, |
| .name = pmu->name, |
| .event_init = amd_uncore_df_event_init, |
| .add = amd_uncore_df_add, |
| .del = amd_uncore_del, |
| .start = amd_uncore_start, |
| .stop = amd_uncore_stop, |
| .read = amd_uncore_read, |
| .capabilities = PERF_PMU_CAP_NO_EXCLUDE | PERF_PMU_CAP_NO_INTERRUPT, |
| .module = THIS_MODULE, |
| }; |
| |
| if (perf_pmu_register(&pmu->pmu, pmu->pmu.name, -1)) { |
| free_percpu(pmu->ctx); |
| pmu->ctx = NULL; |
| goto done; |
| } |
| |
| pr_info("%d %s%s counters detected\n", pmu->num_counters, |
| boot_cpu_data.x86_vendor == X86_VENDOR_HYGON ? "HYGON " : "", |
| pmu->pmu.name); |
| |
| uncore->num_pmus = 1; |
| |
| done: |
| uncore->init_done = true; |
| |
| return amd_uncore_ctx_init(uncore, cpu); |
| } |
| |
| static int amd_uncore_l3_event_init(struct perf_event *event) |
| { |
| int ret = amd_uncore_event_init(event); |
| struct hw_perf_event *hwc = &event->hw; |
| u64 config = event->attr.config; |
| u64 mask; |
| |
| hwc->config = config & AMD64_RAW_EVENT_MASK_NB; |
| |
| /* |
| * SliceMask and ThreadMask need to be set for certain L3 events. |
| * For other events, the two fields do not affect the count. |
| */ |
| if (ret || boot_cpu_data.x86 < 0x17) |
| return ret; |
| |
| mask = config & (AMD64_L3_F19H_THREAD_MASK | AMD64_L3_SLICEID_MASK | |
| AMD64_L3_EN_ALL_CORES | AMD64_L3_EN_ALL_SLICES | |
| AMD64_L3_COREID_MASK); |
| |
| if (boot_cpu_data.x86 <= 0x18) |
| mask = ((config & AMD64_L3_SLICE_MASK) ? : AMD64_L3_SLICE_MASK) | |
| ((config & AMD64_L3_THREAD_MASK) ? : AMD64_L3_THREAD_MASK); |
| |
| /* |
| * If the user doesn't specify a ThreadMask, they're not trying to |
| * count core 0, so we enable all cores & threads. |
| * We'll also assume that they want to count slice 0 if they specify |
| * a ThreadMask and leave SliceId and EnAllSlices unpopulated. |
| */ |
| else if (!(config & AMD64_L3_F19H_THREAD_MASK)) |
| mask = AMD64_L3_F19H_THREAD_MASK | AMD64_L3_EN_ALL_SLICES | |
| AMD64_L3_EN_ALL_CORES; |
| |
| hwc->config |= mask; |
| |
| return 0; |
| } |
| |
| static |
| void amd_uncore_l3_ctx_scan(struct amd_uncore *uncore, unsigned int cpu) |
| { |
| union amd_uncore_info info; |
| |
| if (!boot_cpu_has(X86_FEATURE_PERFCTR_LLC)) |
| return; |
| |
| info.split.aux_data = 0; |
| info.split.num_pmcs = NUM_COUNTERS_L2; |
| info.split.gid = 0; |
| info.split.cid = per_cpu_llc_id(cpu); |
| |
| if (boot_cpu_data.x86 >= 0x17) |
| info.split.num_pmcs = NUM_COUNTERS_L3; |
| |
| *per_cpu_ptr(uncore->info, cpu) = info; |
| } |
| |
| static |
| int amd_uncore_l3_ctx_init(struct amd_uncore *uncore, unsigned int cpu) |
| { |
| struct attribute **l3_attr = amd_uncore_l3_format_attr; |
| struct amd_uncore_pmu *pmu; |
| int num_counters; |
| |
| /* Run just once */ |
| if (uncore->init_done) |
| return amd_uncore_ctx_init(uncore, cpu); |
| |
| num_counters = amd_uncore_ctx_num_pmcs(uncore, cpu); |
| if (!num_counters) |
| goto done; |
| |
| /* No grouping, single instance for a system */ |
| uncore->pmus = kzalloc(sizeof(*uncore->pmus), GFP_KERNEL); |
| if (!uncore->pmus) |
| goto done; |
| |
| /* |
| * For Family 17h and above, L3 cache counters are available instead |
| * of L2 cache counters. The PMUs are exported based on family as |
| * either L2 or L3. |
| */ |
| pmu = &uncore->pmus[0]; |
| strscpy(pmu->name, boot_cpu_data.x86 >= 0x17 ? "amd_l3" : "amd_l2", |
| sizeof(pmu->name)); |
| pmu->num_counters = num_counters; |
| pmu->msr_base = MSR_F16H_L2I_PERF_CTL; |
| pmu->rdpmc_base = RDPMC_BASE_LLC; |
| pmu->group = amd_uncore_ctx_gid(uncore, cpu); |
| |
| if (boot_cpu_data.x86 >= 0x17) { |
| *l3_attr++ = &format_attr_event8.attr; |
| *l3_attr++ = &format_attr_umask8.attr; |
| *l3_attr++ = boot_cpu_data.x86 >= 0x19 ? |
| &format_attr_threadmask2.attr : |
| &format_attr_threadmask8.attr; |
| } |
| |
| pmu->ctx = alloc_percpu(struct amd_uncore_ctx *); |
| if (!pmu->ctx) |
| goto done; |
| |
| pmu->pmu = (struct pmu) { |
| .task_ctx_nr = perf_invalid_context, |
| .attr_groups = amd_uncore_l3_attr_groups, |
| .attr_update = amd_uncore_l3_attr_update, |
| .name = pmu->name, |
| .event_init = amd_uncore_l3_event_init, |
| .add = amd_uncore_add, |
| .del = amd_uncore_del, |
| .start = amd_uncore_start, |
| .stop = amd_uncore_stop, |
| .read = amd_uncore_read, |
| .capabilities = PERF_PMU_CAP_NO_EXCLUDE | PERF_PMU_CAP_NO_INTERRUPT, |
| .module = THIS_MODULE, |
| }; |
| |
| if (perf_pmu_register(&pmu->pmu, pmu->pmu.name, -1)) { |
| free_percpu(pmu->ctx); |
| pmu->ctx = NULL; |
| goto done; |
| } |
| |
| pr_info("%d %s%s counters detected\n", pmu->num_counters, |
| boot_cpu_data.x86_vendor == X86_VENDOR_HYGON ? "HYGON " : "", |
| pmu->pmu.name); |
| |
| uncore->num_pmus = 1; |
| |
| done: |
| uncore->init_done = true; |
| |
| return amd_uncore_ctx_init(uncore, cpu); |
| } |
| |
| static int amd_uncore_umc_event_init(struct perf_event *event) |
| { |
| struct hw_perf_event *hwc = &event->hw; |
| int ret = amd_uncore_event_init(event); |
| |
| if (ret) |
| return ret; |
| |
| hwc->config = event->attr.config & AMD64_PERFMON_V2_RAW_EVENT_MASK_UMC; |
| |
| return 0; |
| } |
| |
| static void amd_uncore_umc_start(struct perf_event *event, int flags) |
| { |
| struct hw_perf_event *hwc = &event->hw; |
| |
| if (flags & PERF_EF_RELOAD) |
| wrmsrl(hwc->event_base, (u64)local64_read(&hwc->prev_count)); |
| |
| hwc->state = 0; |
| wrmsrl(hwc->config_base, (hwc->config | AMD64_PERFMON_V2_ENABLE_UMC)); |
| perf_event_update_userpage(event); |
| } |
| |
| static |
| void amd_uncore_umc_ctx_scan(struct amd_uncore *uncore, unsigned int cpu) |
| { |
| union cpuid_0x80000022_ebx ebx; |
| union amd_uncore_info info; |
| unsigned int eax, ecx, edx; |
| |
| if (pmu_version < 2) |
| return; |
| |
| cpuid(EXT_PERFMON_DEBUG_FEATURES, &eax, &ebx.full, &ecx, &edx); |
| info.split.aux_data = ecx; /* stash active mask */ |
| info.split.num_pmcs = ebx.split.num_umc_pmc; |
| info.split.gid = topology_logical_package_id(cpu); |
| info.split.cid = topology_logical_package_id(cpu); |
| *per_cpu_ptr(uncore->info, cpu) = info; |
| } |
| |
| static |
| int amd_uncore_umc_ctx_init(struct amd_uncore *uncore, unsigned int cpu) |
| { |
| DECLARE_BITMAP(gmask, UNCORE_GROUP_MAX) = { 0 }; |
| u8 group_num_pmus[UNCORE_GROUP_MAX] = { 0 }; |
| u8 group_num_pmcs[UNCORE_GROUP_MAX] = { 0 }; |
| union amd_uncore_info info; |
| struct amd_uncore_pmu *pmu; |
| int index = 0, gid, i; |
| |
| if (pmu_version < 2) |
| return 0; |
| |
| /* Run just once */ |
| if (uncore->init_done) |
| return amd_uncore_ctx_init(uncore, cpu); |
| |
| /* Find unique groups */ |
| for_each_online_cpu(i) { |
| info = *per_cpu_ptr(uncore->info, i); |
| gid = info.split.gid; |
| if (test_bit(gid, gmask)) |
| continue; |
| |
| __set_bit(gid, gmask); |
| group_num_pmus[gid] = hweight32(info.split.aux_data); |
| group_num_pmcs[gid] = info.split.num_pmcs; |
| uncore->num_pmus += group_num_pmus[gid]; |
| } |
| |
| uncore->pmus = kzalloc(sizeof(*uncore->pmus) * uncore->num_pmus, |
| GFP_KERNEL); |
| if (!uncore->pmus) { |
| uncore->num_pmus = 0; |
| goto done; |
| } |
| |
| for_each_set_bit(gid, gmask, UNCORE_GROUP_MAX) { |
| for (i = 0; i < group_num_pmus[gid]; i++) { |
| pmu = &uncore->pmus[index]; |
| snprintf(pmu->name, sizeof(pmu->name), "amd_umc_%d", index); |
| pmu->num_counters = group_num_pmcs[gid] / group_num_pmus[gid]; |
| pmu->msr_base = MSR_F19H_UMC_PERF_CTL + i * pmu->num_counters * 2; |
| pmu->rdpmc_base = -1; |
| pmu->group = gid; |
| |
| pmu->ctx = alloc_percpu(struct amd_uncore_ctx *); |
| if (!pmu->ctx) |
| goto done; |
| |
| pmu->pmu = (struct pmu) { |
| .task_ctx_nr = perf_invalid_context, |
| .attr_groups = amd_uncore_umc_attr_groups, |
| .name = pmu->name, |
| .event_init = amd_uncore_umc_event_init, |
| .add = amd_uncore_add, |
| .del = amd_uncore_del, |
| .start = amd_uncore_umc_start, |
| .stop = amd_uncore_stop, |
| .read = amd_uncore_read, |
| .capabilities = PERF_PMU_CAP_NO_EXCLUDE | PERF_PMU_CAP_NO_INTERRUPT, |
| .module = THIS_MODULE, |
| }; |
| |
| if (perf_pmu_register(&pmu->pmu, pmu->pmu.name, -1)) { |
| free_percpu(pmu->ctx); |
| pmu->ctx = NULL; |
| goto done; |
| } |
| |
| pr_info("%d %s counters detected\n", pmu->num_counters, |
| pmu->pmu.name); |
| |
| index++; |
| } |
| } |
| |
| done: |
| uncore->num_pmus = index; |
| uncore->init_done = true; |
| |
| return amd_uncore_ctx_init(uncore, cpu); |
| } |
| |
| static struct amd_uncore uncores[UNCORE_TYPE_MAX] = { |
| /* UNCORE_TYPE_DF */ |
| { |
| .scan = amd_uncore_df_ctx_scan, |
| .init = amd_uncore_df_ctx_init, |
| .move = amd_uncore_ctx_move, |
| .free = amd_uncore_ctx_free, |
| }, |
| /* UNCORE_TYPE_L3 */ |
| { |
| .scan = amd_uncore_l3_ctx_scan, |
| .init = amd_uncore_l3_ctx_init, |
| .move = amd_uncore_ctx_move, |
| .free = amd_uncore_ctx_free, |
| }, |
| /* UNCORE_TYPE_UMC */ |
| { |
| .scan = amd_uncore_umc_ctx_scan, |
| .init = amd_uncore_umc_ctx_init, |
| .move = amd_uncore_ctx_move, |
| .free = amd_uncore_ctx_free, |
| }, |
| }; |
| |
| static int __init amd_uncore_init(void) |
| { |
| struct amd_uncore *uncore; |
| int ret = -ENODEV; |
| int i; |
| |
| if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD && |
| boot_cpu_data.x86_vendor != X86_VENDOR_HYGON) |
| return -ENODEV; |
| |
| if (!boot_cpu_has(X86_FEATURE_TOPOEXT)) |
| return -ENODEV; |
| |
| if (boot_cpu_has(X86_FEATURE_PERFMON_V2)) |
| pmu_version = 2; |
| |
| for (i = 0; i < UNCORE_TYPE_MAX; i++) { |
| uncore = &uncores[i]; |
| |
| BUG_ON(!uncore->scan); |
| BUG_ON(!uncore->init); |
| BUG_ON(!uncore->move); |
| BUG_ON(!uncore->free); |
| |
| uncore->info = alloc_percpu(union amd_uncore_info); |
| if (!uncore->info) { |
| ret = -ENOMEM; |
| goto fail; |
| } |
| }; |
| |
| /* |
| * Install callbacks. Core will call them for each online cpu. |
| */ |
| ret = cpuhp_setup_state(CPUHP_PERF_X86_AMD_UNCORE_PREP, |
| "perf/x86/amd/uncore:prepare", |
| NULL, amd_uncore_cpu_dead); |
| if (ret) |
| goto fail; |
| |
| ret = cpuhp_setup_state(CPUHP_AP_PERF_X86_AMD_UNCORE_STARTING, |
| "perf/x86/amd/uncore:starting", |
| amd_uncore_cpu_starting, NULL); |
| if (ret) |
| goto fail_prep; |
| |
| ret = cpuhp_setup_state(CPUHP_AP_PERF_X86_AMD_UNCORE_ONLINE, |
| "perf/x86/amd/uncore:online", |
| amd_uncore_cpu_online, |
| amd_uncore_cpu_down_prepare); |
| if (ret) |
| goto fail_start; |
| |
| return 0; |
| |
| fail_start: |
| cpuhp_remove_state(CPUHP_AP_PERF_X86_AMD_UNCORE_STARTING); |
| fail_prep: |
| cpuhp_remove_state(CPUHP_PERF_X86_AMD_UNCORE_PREP); |
| fail: |
| for (i = 0; i < UNCORE_TYPE_MAX; i++) { |
| uncore = &uncores[i]; |
| if (uncore->info) { |
| free_percpu(uncore->info); |
| uncore->info = NULL; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static void __exit amd_uncore_exit(void) |
| { |
| struct amd_uncore *uncore; |
| struct amd_uncore_pmu *pmu; |
| int i, j; |
| |
| cpuhp_remove_state(CPUHP_AP_PERF_X86_AMD_UNCORE_ONLINE); |
| cpuhp_remove_state(CPUHP_AP_PERF_X86_AMD_UNCORE_STARTING); |
| cpuhp_remove_state(CPUHP_PERF_X86_AMD_UNCORE_PREP); |
| |
| for (i = 0; i < UNCORE_TYPE_MAX; i++) { |
| uncore = &uncores[i]; |
| if (!uncore->info) |
| continue; |
| |
| free_percpu(uncore->info); |
| uncore->info = NULL; |
| |
| for (j = 0; j < uncore->num_pmus; j++) { |
| pmu = &uncore->pmus[j]; |
| if (!pmu->ctx) |
| continue; |
| |
| perf_pmu_unregister(&pmu->pmu); |
| free_percpu(pmu->ctx); |
| pmu->ctx = NULL; |
| } |
| |
| kfree(uncore->pmus); |
| uncore->pmus = NULL; |
| } |
| } |
| |
| module_init(amd_uncore_init); |
| module_exit(amd_uncore_exit); |
| |
| MODULE_DESCRIPTION("AMD Uncore Driver"); |
| MODULE_LICENSE("GPL v2"); |