| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| * |
| */ |
| |
| /* Support for NVIDIA specific attributes. */ |
| |
| #include <linux/io.h> |
| #include <linux/module.h> |
| #include <linux/property.h> |
| #include <linux/topology.h> |
| |
| #include "arm_cspmu.h" |
| |
| #define NV_PCIE_PORT_COUNT 10ULL |
| #define NV_PCIE_FILTER_ID_MASK GENMASK_ULL(NV_PCIE_PORT_COUNT - 1, 0) |
| |
| #define NV_NVL_C2C_PORT_COUNT 2ULL |
| #define NV_NVL_C2C_FILTER_ID_MASK GENMASK_ULL(NV_NVL_C2C_PORT_COUNT - 1, 0) |
| |
| #define NV_CNVL_PORT_COUNT 4ULL |
| #define NV_CNVL_FILTER_ID_MASK GENMASK_ULL(NV_CNVL_PORT_COUNT - 1, 0) |
| |
| #define NV_UCF_SRC_COUNT 3ULL |
| #define NV_UCF_DST_COUNT 4ULL |
| #define NV_UCF_FILTER_ID_MASK GENMASK_ULL(11, 0) |
| #define NV_UCF_FILTER_SRC GENMASK_ULL(2, 0) |
| #define NV_UCF_FILTER_DST GENMASK_ULL(11, 8) |
| #define NV_UCF_FILTER_DEFAULT (NV_UCF_FILTER_SRC | NV_UCF_FILTER_DST) |
| |
| #define NV_PCIE_V2_PORT_COUNT 8ULL |
| #define NV_PCIE_V2_FILTER_ID_MASK GENMASK_ULL(24, 0) |
| #define NV_PCIE_V2_FILTER_PORT GENMASK_ULL(NV_PCIE_V2_PORT_COUNT - 1, 0) |
| #define NV_PCIE_V2_FILTER_BDF_VAL GENMASK_ULL(23, NV_PCIE_V2_PORT_COUNT) |
| #define NV_PCIE_V2_FILTER_BDF_EN BIT(24) |
| #define NV_PCIE_V2_FILTER_BDF_VAL_EN GENMASK_ULL(24, NV_PCIE_V2_PORT_COUNT) |
| #define NV_PCIE_V2_FILTER_DEFAULT NV_PCIE_V2_FILTER_PORT |
| |
| #define NV_PCIE_V2_DST_COUNT 5ULL |
| #define NV_PCIE_V2_FILTER2_ID_MASK GENMASK_ULL(4, 0) |
| #define NV_PCIE_V2_FILTER2_DST GENMASK_ULL(NV_PCIE_V2_DST_COUNT - 1, 0) |
| #define NV_PCIE_V2_FILTER2_DEFAULT NV_PCIE_V2_FILTER2_DST |
| |
| #define NV_PCIE_TGT_PORT_COUNT 8ULL |
| #define NV_PCIE_TGT_EV_TYPE_CC 0x4 |
| #define NV_PCIE_TGT_EV_TYPE_COUNT 3ULL |
| #define NV_PCIE_TGT_EV_TYPE_MASK GENMASK_ULL(NV_PCIE_TGT_EV_TYPE_COUNT - 1, 0) |
| #define NV_PCIE_TGT_FILTER2_MASK GENMASK_ULL(NV_PCIE_TGT_PORT_COUNT, 0) |
| #define NV_PCIE_TGT_FILTER2_PORT GENMASK_ULL(NV_PCIE_TGT_PORT_COUNT - 1, 0) |
| #define NV_PCIE_TGT_FILTER2_ADDR_EN BIT(NV_PCIE_TGT_PORT_COUNT) |
| #define NV_PCIE_TGT_FILTER2_ADDR GENMASK_ULL(15, NV_PCIE_TGT_PORT_COUNT) |
| #define NV_PCIE_TGT_FILTER2_DEFAULT NV_PCIE_TGT_FILTER2_PORT |
| |
| #define NV_PCIE_TGT_ADDR_COUNT 8ULL |
| #define NV_PCIE_TGT_ADDR_STRIDE 20 |
| #define NV_PCIE_TGT_ADDR_CTRL 0xD38 |
| #define NV_PCIE_TGT_ADDR_BASE_LO 0xD3C |
| #define NV_PCIE_TGT_ADDR_BASE_HI 0xD40 |
| #define NV_PCIE_TGT_ADDR_MASK_LO 0xD44 |
| #define NV_PCIE_TGT_ADDR_MASK_HI 0xD48 |
| |
| #define NV_GENERIC_FILTER_ID_MASK GENMASK_ULL(31, 0) |
| |
| #define NV_PRODID_MASK (PMIIDR_PRODUCTID | PMIIDR_VARIANT | PMIIDR_REVISION) |
| |
| #define NV_FORMAT_NAME_GENERIC 0 |
| |
| #define to_nv_cspmu_ctx(cspmu) ((struct nv_cspmu_ctx *)(cspmu->impl.ctx)) |
| |
| #define NV_CSPMU_EVENT_ATTR_4_INNER(_pref, _num, _suff, _config) \ |
| ARM_CSPMU_EVENT_ATTR(_pref##_num##_suff, _config) |
| |
| #define NV_CSPMU_EVENT_ATTR_4(_pref, _suff, _config) \ |
| NV_CSPMU_EVENT_ATTR_4_INNER(_pref, _0_, _suff, _config), \ |
| NV_CSPMU_EVENT_ATTR_4_INNER(_pref, _1_, _suff, _config + 1), \ |
| NV_CSPMU_EVENT_ATTR_4_INNER(_pref, _2_, _suff, _config + 2), \ |
| NV_CSPMU_EVENT_ATTR_4_INNER(_pref, _3_, _suff, _config + 3) |
| |
| struct nv_cspmu_ctx { |
| const char *name; |
| |
| struct attribute **event_attr; |
| struct attribute **format_attr; |
| |
| u32 filter_mask; |
| u32 filter_default_val; |
| u32 filter2_mask; |
| u32 filter2_default_val; |
| |
| u32 (*get_filter)(const struct perf_event *event); |
| u32 (*get_filter2)(const struct perf_event *event); |
| |
| void *data; |
| |
| int (*init_data)(struct arm_cspmu *cspmu); |
| }; |
| |
| static struct attribute *scf_pmu_event_attrs[] = { |
| ARM_CSPMU_EVENT_ATTR(bus_cycles, 0x1d), |
| |
| ARM_CSPMU_EVENT_ATTR(scf_cache_allocate, 0xF0), |
| ARM_CSPMU_EVENT_ATTR(scf_cache_refill, 0xF1), |
| ARM_CSPMU_EVENT_ATTR(scf_cache, 0xF2), |
| ARM_CSPMU_EVENT_ATTR(scf_cache_wb, 0xF3), |
| |
| NV_CSPMU_EVENT_ATTR_4(socket, rd_data, 0x101), |
| NV_CSPMU_EVENT_ATTR_4(socket, wb_data, 0x109), |
| |
| NV_CSPMU_EVENT_ATTR_4(socket, rd_outstanding, 0x115), |
| |
| NV_CSPMU_EVENT_ATTR_4(socket, rd_access, 0x12d), |
| NV_CSPMU_EVENT_ATTR_4(socket, wb_access, 0x135), |
| NV_CSPMU_EVENT_ATTR_4(socket, wr_access, 0x139), |
| |
| ARM_CSPMU_EVENT_ATTR(gmem_rd_data, 0x16d), |
| ARM_CSPMU_EVENT_ATTR(gmem_rd_access, 0x16e), |
| ARM_CSPMU_EVENT_ATTR(gmem_rd_outstanding, 0x16f), |
| ARM_CSPMU_EVENT_ATTR(gmem_wb_data, 0x173), |
| ARM_CSPMU_EVENT_ATTR(gmem_wb_access, 0x174), |
| ARM_CSPMU_EVENT_ATTR(gmem_wr_data, 0x179), |
| ARM_CSPMU_EVENT_ATTR(gmem_wr_access, 0x17b), |
| |
| NV_CSPMU_EVENT_ATTR_4(socket, wr_data, 0x17c), |
| |
| ARM_CSPMU_EVENT_ATTR(gmem_wr_total_bytes, 0x1a0), |
| ARM_CSPMU_EVENT_ATTR(remote_socket_wr_total_bytes, 0x1a1), |
| ARM_CSPMU_EVENT_ATTR(remote_socket_rd_data, 0x1a2), |
| ARM_CSPMU_EVENT_ATTR(remote_socket_rd_outstanding, 0x1a3), |
| ARM_CSPMU_EVENT_ATTR(remote_socket_rd_access, 0x1a4), |
| |
| ARM_CSPMU_EVENT_ATTR(cmem_rd_data, 0x1a5), |
| ARM_CSPMU_EVENT_ATTR(cmem_rd_access, 0x1a6), |
| ARM_CSPMU_EVENT_ATTR(cmem_rd_outstanding, 0x1a7), |
| ARM_CSPMU_EVENT_ATTR(cmem_wb_data, 0x1ab), |
| ARM_CSPMU_EVENT_ATTR(cmem_wb_access, 0x1ac), |
| ARM_CSPMU_EVENT_ATTR(cmem_wr_data, 0x1b1), |
| |
| ARM_CSPMU_EVENT_ATTR(cmem_wr_access, 0x1ca), |
| |
| ARM_CSPMU_EVENT_ATTR(cmem_wr_total_bytes, 0x1db), |
| |
| ARM_CSPMU_EVENT_ATTR(cycles, ARM_CSPMU_EVT_CYCLES_DEFAULT), |
| NULL, |
| }; |
| |
| static struct attribute *mcf_pmu_event_attrs[] = { |
| ARM_CSPMU_EVENT_ATTR(rd_bytes_loc, 0x0), |
| ARM_CSPMU_EVENT_ATTR(rd_bytes_rem, 0x1), |
| ARM_CSPMU_EVENT_ATTR(wr_bytes_loc, 0x2), |
| ARM_CSPMU_EVENT_ATTR(wr_bytes_rem, 0x3), |
| ARM_CSPMU_EVENT_ATTR(total_bytes_loc, 0x4), |
| ARM_CSPMU_EVENT_ATTR(total_bytes_rem, 0x5), |
| ARM_CSPMU_EVENT_ATTR(rd_req_loc, 0x6), |
| ARM_CSPMU_EVENT_ATTR(rd_req_rem, 0x7), |
| ARM_CSPMU_EVENT_ATTR(wr_req_loc, 0x8), |
| ARM_CSPMU_EVENT_ATTR(wr_req_rem, 0x9), |
| ARM_CSPMU_EVENT_ATTR(total_req_loc, 0xa), |
| ARM_CSPMU_EVENT_ATTR(total_req_rem, 0xb), |
| ARM_CSPMU_EVENT_ATTR(rd_cum_outs_loc, 0xc), |
| ARM_CSPMU_EVENT_ATTR(rd_cum_outs_rem, 0xd), |
| ARM_CSPMU_EVENT_ATTR(cycles, ARM_CSPMU_EVT_CYCLES_DEFAULT), |
| NULL, |
| }; |
| |
| static struct attribute *ucf_pmu_event_attrs[] = { |
| ARM_CSPMU_EVENT_ATTR(bus_cycles, 0x1D), |
| |
| ARM_CSPMU_EVENT_ATTR(slc_allocate, 0xF0), |
| ARM_CSPMU_EVENT_ATTR(slc_wb, 0xF3), |
| ARM_CSPMU_EVENT_ATTR(slc_refill_rd, 0x109), |
| ARM_CSPMU_EVENT_ATTR(slc_refill_wr, 0x10A), |
| ARM_CSPMU_EVENT_ATTR(slc_hit_rd, 0x119), |
| |
| ARM_CSPMU_EVENT_ATTR(slc_access_dataless, 0x183), |
| ARM_CSPMU_EVENT_ATTR(slc_access_atomic, 0x184), |
| |
| ARM_CSPMU_EVENT_ATTR(slc_access_rd, 0x111), |
| ARM_CSPMU_EVENT_ATTR(slc_access_wr, 0x112), |
| ARM_CSPMU_EVENT_ATTR(slc_bytes_rd, 0x113), |
| ARM_CSPMU_EVENT_ATTR(slc_bytes_wr, 0x114), |
| |
| ARM_CSPMU_EVENT_ATTR(mem_access_rd, 0x121), |
| ARM_CSPMU_EVENT_ATTR(mem_access_wr, 0x122), |
| ARM_CSPMU_EVENT_ATTR(mem_bytes_rd, 0x123), |
| ARM_CSPMU_EVENT_ATTR(mem_bytes_wr, 0x124), |
| |
| ARM_CSPMU_EVENT_ATTR(local_snoop, 0x180), |
| ARM_CSPMU_EVENT_ATTR(ext_snp_access, 0x181), |
| ARM_CSPMU_EVENT_ATTR(ext_snp_evict, 0x182), |
| |
| ARM_CSPMU_EVENT_ATTR(cycles, ARM_CSPMU_EVT_CYCLES_DEFAULT), |
| NULL |
| }; |
| |
| static struct attribute *pcie_v2_pmu_event_attrs[] = { |
| ARM_CSPMU_EVENT_ATTR(rd_bytes, 0x0), |
| ARM_CSPMU_EVENT_ATTR(wr_bytes, 0x1), |
| ARM_CSPMU_EVENT_ATTR(rd_req, 0x2), |
| ARM_CSPMU_EVENT_ATTR(wr_req, 0x3), |
| ARM_CSPMU_EVENT_ATTR(rd_cum_outs, 0x4), |
| ARM_CSPMU_EVENT_ATTR(cycles, ARM_CSPMU_EVT_CYCLES_DEFAULT), |
| NULL |
| }; |
| |
| static struct attribute *pcie_tgt_pmu_event_attrs[] = { |
| ARM_CSPMU_EVENT_ATTR(rd_bytes, 0x0), |
| ARM_CSPMU_EVENT_ATTR(wr_bytes, 0x1), |
| ARM_CSPMU_EVENT_ATTR(rd_req, 0x2), |
| ARM_CSPMU_EVENT_ATTR(wr_req, 0x3), |
| ARM_CSPMU_EVENT_ATTR(cycles, NV_PCIE_TGT_EV_TYPE_CC), |
| NULL |
| }; |
| |
| static struct attribute *generic_pmu_event_attrs[] = { |
| ARM_CSPMU_EVENT_ATTR(cycles, ARM_CSPMU_EVT_CYCLES_DEFAULT), |
| NULL, |
| }; |
| |
| static struct attribute *scf_pmu_format_attrs[] = { |
| ARM_CSPMU_FORMAT_EVENT_ATTR, |
| NULL, |
| }; |
| |
| static struct attribute *pcie_pmu_format_attrs[] = { |
| ARM_CSPMU_FORMAT_EVENT_ATTR, |
| ARM_CSPMU_FORMAT_ATTR(root_port, "config1:0-9"), |
| NULL, |
| }; |
| |
| static struct attribute *nvlink_c2c_pmu_format_attrs[] = { |
| ARM_CSPMU_FORMAT_EVENT_ATTR, |
| ARM_CSPMU_FORMAT_ATTR(port, "config1:0-1"), |
| NULL, |
| }; |
| |
| static struct attribute *cnvlink_pmu_format_attrs[] = { |
| ARM_CSPMU_FORMAT_EVENT_ATTR, |
| ARM_CSPMU_FORMAT_ATTR(rem_socket, "config1:0-3"), |
| NULL, |
| }; |
| |
| static struct attribute *ucf_pmu_format_attrs[] = { |
| ARM_CSPMU_FORMAT_EVENT_ATTR, |
| ARM_CSPMU_FORMAT_ATTR(src_loc_noncpu, "config1:0"), |
| ARM_CSPMU_FORMAT_ATTR(src_loc_cpu, "config1:1"), |
| ARM_CSPMU_FORMAT_ATTR(src_rem, "config1:2"), |
| ARM_CSPMU_FORMAT_ATTR(dst_loc_cmem, "config1:8"), |
| ARM_CSPMU_FORMAT_ATTR(dst_loc_gmem, "config1:9"), |
| ARM_CSPMU_FORMAT_ATTR(dst_loc_other, "config1:10"), |
| ARM_CSPMU_FORMAT_ATTR(dst_rem, "config1:11"), |
| NULL |
| }; |
| |
| static struct attribute *pcie_v2_pmu_format_attrs[] = { |
| ARM_CSPMU_FORMAT_EVENT_ATTR, |
| ARM_CSPMU_FORMAT_ATTR(src_rp_mask, "config1:0-7"), |
| ARM_CSPMU_FORMAT_ATTR(src_bdf, "config1:8-23"), |
| ARM_CSPMU_FORMAT_ATTR(src_bdf_en, "config1:24"), |
| ARM_CSPMU_FORMAT_ATTR(dst_loc_cmem, "config2:0"), |
| ARM_CSPMU_FORMAT_ATTR(dst_loc_gmem, "config2:1"), |
| ARM_CSPMU_FORMAT_ATTR(dst_loc_pcie_p2p, "config2:2"), |
| ARM_CSPMU_FORMAT_ATTR(dst_loc_pcie_cxl, "config2:3"), |
| ARM_CSPMU_FORMAT_ATTR(dst_rem, "config2:4"), |
| NULL |
| }; |
| |
| static struct attribute *pcie_tgt_pmu_format_attrs[] = { |
| ARM_CSPMU_FORMAT_ATTR(event, "config:0-2"), |
| ARM_CSPMU_FORMAT_ATTR(dst_rp_mask, "config:3-10"), |
| ARM_CSPMU_FORMAT_ATTR(dst_addr_en, "config:11"), |
| ARM_CSPMU_FORMAT_ATTR(dst_addr_base, "config1:0-63"), |
| ARM_CSPMU_FORMAT_ATTR(dst_addr_mask, "config2:0-63"), |
| NULL |
| }; |
| |
| static struct attribute *generic_pmu_format_attrs[] = { |
| ARM_CSPMU_FORMAT_EVENT_ATTR, |
| ARM_CSPMU_FORMAT_FILTER_ATTR, |
| ARM_CSPMU_FORMAT_FILTER2_ATTR, |
| NULL, |
| }; |
| |
| static struct attribute ** |
| nv_cspmu_get_event_attrs(const struct arm_cspmu *cspmu) |
| { |
| const struct nv_cspmu_ctx *ctx = to_nv_cspmu_ctx(cspmu); |
| |
| return ctx->event_attr; |
| } |
| |
| static struct attribute ** |
| nv_cspmu_get_format_attrs(const struct arm_cspmu *cspmu) |
| { |
| const struct nv_cspmu_ctx *ctx = to_nv_cspmu_ctx(cspmu); |
| |
| return ctx->format_attr; |
| } |
| |
| static const char * |
| nv_cspmu_get_name(const struct arm_cspmu *cspmu) |
| { |
| const struct nv_cspmu_ctx *ctx = to_nv_cspmu_ctx(cspmu); |
| |
| return ctx->name; |
| } |
| |
| #if defined(CONFIG_ACPI) && defined(CONFIG_ARM64) |
| static int nv_cspmu_get_inst_id(const struct arm_cspmu *cspmu, u32 *id) |
| { |
| struct fwnode_handle *fwnode; |
| struct acpi_device *adev; |
| int ret; |
| |
| adev = arm_cspmu_acpi_dev_get(cspmu); |
| if (!adev) |
| return -ENODEV; |
| |
| fwnode = acpi_fwnode_handle(adev); |
| ret = fwnode_property_read_u32(fwnode, "instance_id", id); |
| if (ret) |
| dev_err(cspmu->dev, "Failed to get instance ID\n"); |
| |
| acpi_dev_put(adev); |
| return ret; |
| } |
| #else |
| static int nv_cspmu_get_inst_id(const struct arm_cspmu *cspmu, u32 *id) |
| { |
| return -EINVAL; |
| } |
| #endif |
| |
| static u32 nv_cspmu_event_filter(const struct perf_event *event) |
| { |
| const struct nv_cspmu_ctx *ctx = |
| to_nv_cspmu_ctx(to_arm_cspmu(event->pmu)); |
| |
| const u32 filter_val = event->attr.config1 & ctx->filter_mask; |
| |
| if (filter_val == 0) |
| return ctx->filter_default_val; |
| |
| return filter_val; |
| } |
| |
| static u32 nv_cspmu_event_filter2(const struct perf_event *event) |
| { |
| const struct nv_cspmu_ctx *ctx = |
| to_nv_cspmu_ctx(to_arm_cspmu(event->pmu)); |
| |
| const u32 filter_val = event->attr.config2 & ctx->filter2_mask; |
| |
| if (filter_val == 0) |
| return ctx->filter2_default_val; |
| |
| return filter_val; |
| } |
| |
| static void nv_cspmu_set_ev_filter(struct arm_cspmu *cspmu, |
| const struct perf_event *event) |
| { |
| u32 filter, offset; |
| const struct nv_cspmu_ctx *ctx = |
| to_nv_cspmu_ctx(to_arm_cspmu(event->pmu)); |
| offset = 4 * event->hw.idx; |
| |
| if (ctx->get_filter) { |
| filter = ctx->get_filter(event); |
| writel(filter, cspmu->base0 + PMEVFILTR + offset); |
| } |
| |
| if (ctx->get_filter2) { |
| filter = ctx->get_filter2(event); |
| writel(filter, cspmu->base0 + PMEVFILT2R + offset); |
| } |
| } |
| |
| static void nv_cspmu_reset_ev_filter(struct arm_cspmu *cspmu, |
| const struct perf_event *event) |
| { |
| const struct nv_cspmu_ctx *ctx = |
| to_nv_cspmu_ctx(to_arm_cspmu(event->pmu)); |
| const u32 offset = 4 * event->hw.idx; |
| |
| if (ctx->get_filter) |
| writel(0, cspmu->base0 + PMEVFILTR + offset); |
| |
| if (ctx->get_filter2) |
| writel(0, cspmu->base0 + PMEVFILT2R + offset); |
| } |
| |
| static void nv_cspmu_set_cc_filter(struct arm_cspmu *cspmu, |
| const struct perf_event *event) |
| { |
| u32 filter = nv_cspmu_event_filter(event); |
| |
| writel(filter, cspmu->base0 + PMCCFILTR); |
| } |
| |
| static u32 ucf_pmu_event_filter(const struct perf_event *event) |
| { |
| u32 ret, filter, src, dst; |
| |
| filter = nv_cspmu_event_filter(event); |
| |
| /* Monitor all sources if none is selected. */ |
| src = FIELD_GET(NV_UCF_FILTER_SRC, filter); |
| if (src == 0) |
| src = GENMASK_ULL(NV_UCF_SRC_COUNT - 1, 0); |
| |
| /* Monitor all destinations if none is selected. */ |
| dst = FIELD_GET(NV_UCF_FILTER_DST, filter); |
| if (dst == 0) |
| dst = GENMASK_ULL(NV_UCF_DST_COUNT - 1, 0); |
| |
| ret = FIELD_PREP(NV_UCF_FILTER_SRC, src); |
| ret |= FIELD_PREP(NV_UCF_FILTER_DST, dst); |
| |
| return ret; |
| } |
| |
| static u32 pcie_v2_pmu_bdf_val_en(u32 filter) |
| { |
| const u32 bdf_en = FIELD_GET(NV_PCIE_V2_FILTER_BDF_EN, filter); |
| |
| /* Returns both BDF value and enable bit if BDF filtering is enabled. */ |
| if (bdf_en) |
| return FIELD_GET(NV_PCIE_V2_FILTER_BDF_VAL_EN, filter); |
| |
| /* Ignore the BDF value if BDF filter is not enabled. */ |
| return 0; |
| } |
| |
| static u32 pcie_v2_pmu_event_filter(const struct perf_event *event) |
| { |
| u32 filter, lead_filter, lead_bdf; |
| struct perf_event *leader; |
| const struct nv_cspmu_ctx *ctx = |
| to_nv_cspmu_ctx(to_arm_cspmu(event->pmu)); |
| |
| filter = event->attr.config1 & ctx->filter_mask; |
| if (filter != 0) |
| return filter; |
| |
| leader = event->group_leader; |
| |
| /* Use leader's filter value if its BDF filtering is enabled. */ |
| if (event != leader) { |
| lead_filter = pcie_v2_pmu_event_filter(leader); |
| lead_bdf = pcie_v2_pmu_bdf_val_en(lead_filter); |
| if (lead_bdf != 0) |
| return lead_filter; |
| } |
| |
| /* Otherwise, return default filter value. */ |
| return ctx->filter_default_val; |
| } |
| |
| static int pcie_v2_pmu_validate_event(struct arm_cspmu *cspmu, |
| struct perf_event *new_ev) |
| { |
| /* |
| * Make sure the events are using same BDF filter since the PCIE-SRC PMU |
| * only supports one common BDF filter setting for all of the counters. |
| */ |
| |
| int idx; |
| u32 new_filter, new_rp, new_bdf, new_lead_filter, new_lead_bdf; |
| struct perf_event *new_leader; |
| |
| if (cspmu->impl.ops.is_cycle_counter_event(new_ev)) |
| return 0; |
| |
| new_leader = new_ev->group_leader; |
| |
| new_filter = pcie_v2_pmu_event_filter(new_ev); |
| new_lead_filter = pcie_v2_pmu_event_filter(new_leader); |
| |
| new_bdf = pcie_v2_pmu_bdf_val_en(new_filter); |
| new_lead_bdf = pcie_v2_pmu_bdf_val_en(new_lead_filter); |
| |
| new_rp = FIELD_GET(NV_PCIE_V2_FILTER_PORT, new_filter); |
| |
| if (new_rp != 0 && new_bdf != 0) { |
| dev_err(cspmu->dev, |
| "RP and BDF filtering are mutually exclusive\n"); |
| return -EINVAL; |
| } |
| |
| if (new_bdf != new_lead_bdf) { |
| dev_err(cspmu->dev, |
| "sibling and leader BDF value should be equal\n"); |
| return -EINVAL; |
| } |
| |
| /* Compare BDF filter on existing events. */ |
| idx = find_first_bit(cspmu->hw_events.used_ctrs, |
| cspmu->cycle_counter_logical_idx); |
| |
| if (idx != cspmu->cycle_counter_logical_idx) { |
| struct perf_event *leader = cspmu->hw_events.events[idx]->group_leader; |
| |
| const u32 lead_filter = pcie_v2_pmu_event_filter(leader); |
| const u32 lead_bdf = pcie_v2_pmu_bdf_val_en(lead_filter); |
| |
| if (new_lead_bdf != lead_bdf) { |
| dev_err(cspmu->dev, "only one BDF value is supported\n"); |
| return -EINVAL; |
| } |
| } |
| |
| return 0; |
| } |
| |
| struct pcie_tgt_addr_filter { |
| u32 refcount; |
| u64 base; |
| u64 mask; |
| }; |
| |
| struct pcie_tgt_data { |
| struct pcie_tgt_addr_filter addr_filter[NV_PCIE_TGT_ADDR_COUNT]; |
| void __iomem *addr_filter_reg; |
| }; |
| |
| #if defined(CONFIG_ACPI) && defined(CONFIG_ARM64) |
| static int pcie_tgt_init_data(struct arm_cspmu *cspmu) |
| { |
| int ret; |
| struct acpi_device *adev; |
| struct pcie_tgt_data *data; |
| struct list_head resource_list; |
| struct resource_entry *rentry; |
| struct nv_cspmu_ctx *ctx = to_nv_cspmu_ctx(cspmu); |
| struct device *dev = cspmu->dev; |
| |
| data = devm_kzalloc(dev, sizeof(struct pcie_tgt_data), GFP_KERNEL); |
| if (!data) |
| return -ENOMEM; |
| |
| adev = arm_cspmu_acpi_dev_get(cspmu); |
| if (!adev) { |
| dev_err(dev, "failed to get associated PCIE-TGT device\n"); |
| return -ENODEV; |
| } |
| |
| INIT_LIST_HEAD(&resource_list); |
| ret = acpi_dev_get_memory_resources(adev, &resource_list); |
| if (ret < 0) { |
| dev_err(dev, "failed to get PCIE-TGT device memory resources\n"); |
| acpi_dev_put(adev); |
| return ret; |
| } |
| |
| rentry = list_first_entry_or_null( |
| &resource_list, struct resource_entry, node); |
| if (rentry) { |
| data->addr_filter_reg = devm_ioremap_resource(dev, rentry->res); |
| ret = 0; |
| } |
| |
| if (IS_ERR(data->addr_filter_reg)) { |
| dev_err(dev, "failed to get address filter resource\n"); |
| ret = PTR_ERR(data->addr_filter_reg); |
| } |
| |
| acpi_dev_free_resource_list(&resource_list); |
| acpi_dev_put(adev); |
| |
| ctx->data = data; |
| |
| return ret; |
| } |
| #else |
| static int pcie_tgt_init_data(struct arm_cspmu *cspmu) |
| { |
| return -ENODEV; |
| } |
| #endif |
| |
| static struct pcie_tgt_data *pcie_tgt_get_data(struct arm_cspmu *cspmu) |
| { |
| struct nv_cspmu_ctx *ctx = to_nv_cspmu_ctx(cspmu); |
| |
| return ctx->data; |
| } |
| |
| /* Find the first available address filter slot. */ |
| static int pcie_tgt_find_addr_idx(struct arm_cspmu *cspmu, u64 base, u64 mask, |
| bool is_reset) |
| { |
| int i; |
| struct pcie_tgt_data *data = pcie_tgt_get_data(cspmu); |
| |
| for (i = 0; i < NV_PCIE_TGT_ADDR_COUNT; i++) { |
| if (!is_reset && data->addr_filter[i].refcount == 0) |
| return i; |
| |
| if (data->addr_filter[i].base == base && |
| data->addr_filter[i].mask == mask) |
| return i; |
| } |
| |
| return -ENODEV; |
| } |
| |
| static u32 pcie_tgt_pmu_event_filter(const struct perf_event *event) |
| { |
| u32 filter; |
| |
| filter = (event->attr.config >> NV_PCIE_TGT_EV_TYPE_COUNT) & |
| NV_PCIE_TGT_FILTER2_MASK; |
| |
| return filter; |
| } |
| |
| static bool pcie_tgt_pmu_addr_en(const struct perf_event *event) |
| { |
| u32 filter = pcie_tgt_pmu_event_filter(event); |
| |
| return FIELD_GET(NV_PCIE_TGT_FILTER2_ADDR_EN, filter) != 0; |
| } |
| |
| static u32 pcie_tgt_pmu_port_filter(const struct perf_event *event) |
| { |
| u32 filter = pcie_tgt_pmu_event_filter(event); |
| |
| return FIELD_GET(NV_PCIE_TGT_FILTER2_PORT, filter); |
| } |
| |
| static u64 pcie_tgt_pmu_dst_addr_base(const struct perf_event *event) |
| { |
| return event->attr.config1; |
| } |
| |
| static u64 pcie_tgt_pmu_dst_addr_mask(const struct perf_event *event) |
| { |
| return event->attr.config2; |
| } |
| |
| static int pcie_tgt_pmu_validate_event(struct arm_cspmu *cspmu, |
| struct perf_event *new_ev) |
| { |
| u64 base, mask; |
| int idx; |
| |
| if (!pcie_tgt_pmu_addr_en(new_ev)) |
| return 0; |
| |
| /* Make sure there is a slot available for the address filter. */ |
| base = pcie_tgt_pmu_dst_addr_base(new_ev); |
| mask = pcie_tgt_pmu_dst_addr_mask(new_ev); |
| idx = pcie_tgt_find_addr_idx(cspmu, base, mask, false); |
| if (idx < 0) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static void pcie_tgt_pmu_config_addr_filter(struct arm_cspmu *cspmu, |
| bool en, u64 base, u64 mask, int idx) |
| { |
| struct pcie_tgt_data *data; |
| struct pcie_tgt_addr_filter *filter; |
| void __iomem *filter_reg; |
| |
| data = pcie_tgt_get_data(cspmu); |
| filter = &data->addr_filter[idx]; |
| filter_reg = data->addr_filter_reg + (idx * NV_PCIE_TGT_ADDR_STRIDE); |
| |
| if (en) { |
| filter->refcount++; |
| if (filter->refcount == 1) { |
| filter->base = base; |
| filter->mask = mask; |
| |
| writel(lower_32_bits(base), filter_reg + NV_PCIE_TGT_ADDR_BASE_LO); |
| writel(upper_32_bits(base), filter_reg + NV_PCIE_TGT_ADDR_BASE_HI); |
| writel(lower_32_bits(mask), filter_reg + NV_PCIE_TGT_ADDR_MASK_LO); |
| writel(upper_32_bits(mask), filter_reg + NV_PCIE_TGT_ADDR_MASK_HI); |
| writel(1, filter_reg + NV_PCIE_TGT_ADDR_CTRL); |
| } |
| } else { |
| filter->refcount--; |
| if (filter->refcount == 0) { |
| writel(0, filter_reg + NV_PCIE_TGT_ADDR_CTRL); |
| writel(0, filter_reg + NV_PCIE_TGT_ADDR_BASE_LO); |
| writel(0, filter_reg + NV_PCIE_TGT_ADDR_BASE_HI); |
| writel(0, filter_reg + NV_PCIE_TGT_ADDR_MASK_LO); |
| writel(0, filter_reg + NV_PCIE_TGT_ADDR_MASK_HI); |
| |
| filter->base = 0; |
| filter->mask = 0; |
| } |
| } |
| } |
| |
| static void pcie_tgt_pmu_set_ev_filter(struct arm_cspmu *cspmu, |
| const struct perf_event *event) |
| { |
| bool addr_filter_en; |
| int idx; |
| u32 filter2_val, filter2_offset, port_filter; |
| u64 base, mask; |
| |
| filter2_val = 0; |
| filter2_offset = PMEVFILT2R + (4 * event->hw.idx); |
| |
| addr_filter_en = pcie_tgt_pmu_addr_en(event); |
| if (addr_filter_en) { |
| base = pcie_tgt_pmu_dst_addr_base(event); |
| mask = pcie_tgt_pmu_dst_addr_mask(event); |
| idx = pcie_tgt_find_addr_idx(cspmu, base, mask, false); |
| |
| if (idx < 0) { |
| dev_err(cspmu->dev, |
| "Unable to find a slot for address filtering\n"); |
| writel(0, cspmu->base0 + filter2_offset); |
| return; |
| } |
| |
| /* Configure address range filter registers.*/ |
| pcie_tgt_pmu_config_addr_filter(cspmu, true, base, mask, idx); |
| |
| /* Config the counter to use the selected address filter slot. */ |
| filter2_val |= FIELD_PREP(NV_PCIE_TGT_FILTER2_ADDR, 1U << idx); |
| } |
| |
| port_filter = pcie_tgt_pmu_port_filter(event); |
| |
| /* Monitor all ports if no filter is selected. */ |
| if (!addr_filter_en && port_filter == 0) |
| port_filter = NV_PCIE_TGT_FILTER2_PORT; |
| |
| filter2_val |= FIELD_PREP(NV_PCIE_TGT_FILTER2_PORT, port_filter); |
| |
| writel(filter2_val, cspmu->base0 + filter2_offset); |
| } |
| |
| static void pcie_tgt_pmu_reset_ev_filter(struct arm_cspmu *cspmu, |
| const struct perf_event *event) |
| { |
| bool addr_filter_en; |
| u64 base, mask; |
| int idx; |
| |
| addr_filter_en = pcie_tgt_pmu_addr_en(event); |
| if (!addr_filter_en) |
| return; |
| |
| base = pcie_tgt_pmu_dst_addr_base(event); |
| mask = pcie_tgt_pmu_dst_addr_mask(event); |
| idx = pcie_tgt_find_addr_idx(cspmu, base, mask, true); |
| |
| if (idx < 0) { |
| dev_err(cspmu->dev, |
| "Unable to find the address filter slot to reset\n"); |
| return; |
| } |
| |
| pcie_tgt_pmu_config_addr_filter(cspmu, false, base, mask, idx); |
| } |
| |
| static u32 pcie_tgt_pmu_event_type(const struct perf_event *event) |
| { |
| return event->attr.config & NV_PCIE_TGT_EV_TYPE_MASK; |
| } |
| |
| static bool pcie_tgt_pmu_is_cycle_counter_event(const struct perf_event *event) |
| { |
| u32 event_type = pcie_tgt_pmu_event_type(event); |
| |
| return event_type == NV_PCIE_TGT_EV_TYPE_CC; |
| } |
| |
| enum nv_cspmu_name_fmt { |
| NAME_FMT_GENERIC, |
| NAME_FMT_SOCKET, |
| NAME_FMT_SOCKET_INST, |
| }; |
| |
| struct nv_cspmu_match { |
| u32 prodid; |
| u32 prodid_mask; |
| const char *name_pattern; |
| enum nv_cspmu_name_fmt name_fmt; |
| struct nv_cspmu_ctx template_ctx; |
| struct arm_cspmu_impl_ops ops; |
| }; |
| |
| static const struct nv_cspmu_match nv_cspmu_match[] = { |
| { |
| .prodid = 0x10300000, |
| .prodid_mask = NV_PRODID_MASK, |
| .name_pattern = "nvidia_pcie_pmu_%u", |
| .name_fmt = NAME_FMT_SOCKET, |
| .template_ctx = { |
| .event_attr = mcf_pmu_event_attrs, |
| .format_attr = pcie_pmu_format_attrs, |
| .filter_mask = NV_PCIE_FILTER_ID_MASK, |
| .filter_default_val = NV_PCIE_FILTER_ID_MASK, |
| .filter2_mask = 0x0, |
| .filter2_default_val = 0x0, |
| .get_filter = nv_cspmu_event_filter, |
| .get_filter2 = NULL, |
| .data = NULL, |
| .init_data = NULL |
| }, |
| }, |
| { |
| .prodid = 0x10400000, |
| .prodid_mask = NV_PRODID_MASK, |
| .name_pattern = "nvidia_nvlink_c2c1_pmu_%u", |
| .name_fmt = NAME_FMT_SOCKET, |
| .template_ctx = { |
| .event_attr = mcf_pmu_event_attrs, |
| .format_attr = nvlink_c2c_pmu_format_attrs, |
| .filter_mask = NV_NVL_C2C_FILTER_ID_MASK, |
| .filter_default_val = NV_NVL_C2C_FILTER_ID_MASK, |
| .filter2_mask = 0x0, |
| .filter2_default_val = 0x0, |
| .get_filter = nv_cspmu_event_filter, |
| .get_filter2 = NULL, |
| .data = NULL, |
| .init_data = NULL |
| }, |
| }, |
| { |
| .prodid = 0x10500000, |
| .prodid_mask = NV_PRODID_MASK, |
| .name_pattern = "nvidia_nvlink_c2c0_pmu_%u", |
| .name_fmt = NAME_FMT_SOCKET, |
| .template_ctx = { |
| .event_attr = mcf_pmu_event_attrs, |
| .format_attr = nvlink_c2c_pmu_format_attrs, |
| .filter_mask = NV_NVL_C2C_FILTER_ID_MASK, |
| .filter_default_val = NV_NVL_C2C_FILTER_ID_MASK, |
| .filter2_mask = 0x0, |
| .filter2_default_val = 0x0, |
| .get_filter = nv_cspmu_event_filter, |
| .get_filter2 = NULL, |
| .data = NULL, |
| .init_data = NULL |
| }, |
| }, |
| { |
| .prodid = 0x10600000, |
| .prodid_mask = NV_PRODID_MASK, |
| .name_pattern = "nvidia_cnvlink_pmu_%u", |
| .name_fmt = NAME_FMT_SOCKET, |
| .template_ctx = { |
| .event_attr = mcf_pmu_event_attrs, |
| .format_attr = cnvlink_pmu_format_attrs, |
| .filter_mask = NV_CNVL_FILTER_ID_MASK, |
| .filter_default_val = NV_CNVL_FILTER_ID_MASK, |
| .filter2_mask = 0x0, |
| .filter2_default_val = 0x0, |
| .get_filter = nv_cspmu_event_filter, |
| .get_filter2 = NULL, |
| .data = NULL, |
| .init_data = NULL |
| }, |
| }, |
| { |
| .prodid = 0x2CF00000, |
| .prodid_mask = NV_PRODID_MASK, |
| .name_pattern = "nvidia_scf_pmu_%u", |
| .name_fmt = NAME_FMT_SOCKET, |
| .template_ctx = { |
| .event_attr = scf_pmu_event_attrs, |
| .format_attr = scf_pmu_format_attrs, |
| .filter_mask = 0x0, |
| .filter_default_val = 0x0, |
| .filter2_mask = 0x0, |
| .filter2_default_val = 0x0, |
| .get_filter = nv_cspmu_event_filter, |
| .get_filter2 = NULL, |
| .data = NULL, |
| .init_data = NULL |
| }, |
| }, |
| { |
| .prodid = 0x2CF20000, |
| .prodid_mask = NV_PRODID_MASK, |
| .name_pattern = "nvidia_ucf_pmu_%u", |
| .name_fmt = NAME_FMT_SOCKET, |
| .template_ctx = { |
| .event_attr = ucf_pmu_event_attrs, |
| .format_attr = ucf_pmu_format_attrs, |
| .filter_mask = NV_UCF_FILTER_ID_MASK, |
| .filter_default_val = NV_UCF_FILTER_DEFAULT, |
| .filter2_mask = 0x0, |
| .filter2_default_val = 0x0, |
| .get_filter = ucf_pmu_event_filter, |
| }, |
| }, |
| { |
| .prodid = 0x10301000, |
| .prodid_mask = NV_PRODID_MASK, |
| .name_pattern = "nvidia_pcie_pmu_%u_rc_%u", |
| .name_fmt = NAME_FMT_SOCKET_INST, |
| .template_ctx = { |
| .event_attr = pcie_v2_pmu_event_attrs, |
| .format_attr = pcie_v2_pmu_format_attrs, |
| .filter_mask = NV_PCIE_V2_FILTER_ID_MASK, |
| .filter_default_val = NV_PCIE_V2_FILTER_DEFAULT, |
| .filter2_mask = NV_PCIE_V2_FILTER2_ID_MASK, |
| .filter2_default_val = NV_PCIE_V2_FILTER2_DEFAULT, |
| .get_filter = pcie_v2_pmu_event_filter, |
| .get_filter2 = nv_cspmu_event_filter2, |
| }, |
| .ops = { |
| .validate_event = pcie_v2_pmu_validate_event, |
| .reset_ev_filter = nv_cspmu_reset_ev_filter, |
| } |
| }, |
| { |
| .prodid = 0x10700000, |
| .prodid_mask = NV_PRODID_MASK, |
| .name_pattern = "nvidia_pcie_tgt_pmu_%u_rc_%u", |
| .name_fmt = NAME_FMT_SOCKET_INST, |
| .template_ctx = { |
| .event_attr = pcie_tgt_pmu_event_attrs, |
| .format_attr = pcie_tgt_pmu_format_attrs, |
| .filter_mask = 0x0, |
| .filter_default_val = 0x0, |
| .filter2_mask = NV_PCIE_TGT_FILTER2_MASK, |
| .filter2_default_val = NV_PCIE_TGT_FILTER2_DEFAULT, |
| .init_data = pcie_tgt_init_data |
| }, |
| .ops = { |
| .is_cycle_counter_event = pcie_tgt_pmu_is_cycle_counter_event, |
| .event_type = pcie_tgt_pmu_event_type, |
| .validate_event = pcie_tgt_pmu_validate_event, |
| .set_ev_filter = pcie_tgt_pmu_set_ev_filter, |
| .reset_ev_filter = pcie_tgt_pmu_reset_ev_filter, |
| } |
| }, |
| { |
| .prodid = 0, |
| .prodid_mask = 0, |
| .name_pattern = "nvidia_uncore_pmu_%u", |
| .name_fmt = NAME_FMT_GENERIC, |
| .template_ctx = { |
| .event_attr = generic_pmu_event_attrs, |
| .format_attr = generic_pmu_format_attrs, |
| .filter_mask = NV_GENERIC_FILTER_ID_MASK, |
| .filter_default_val = NV_GENERIC_FILTER_ID_MASK, |
| .filter2_mask = NV_GENERIC_FILTER_ID_MASK, |
| .filter2_default_val = NV_GENERIC_FILTER_ID_MASK, |
| .get_filter = nv_cspmu_event_filter, |
| .get_filter2 = nv_cspmu_event_filter2, |
| .data = NULL, |
| .init_data = NULL |
| }, |
| }, |
| }; |
| |
| static char *nv_cspmu_format_name(const struct arm_cspmu *cspmu, |
| const struct nv_cspmu_match *match) |
| { |
| char *name = NULL; |
| struct device *dev = cspmu->dev; |
| |
| static atomic_t pmu_generic_idx = {0}; |
| |
| switch (match->name_fmt) { |
| case NAME_FMT_SOCKET: { |
| const int cpu = cpumask_first(&cspmu->associated_cpus); |
| const int socket = cpu_to_node(cpu); |
| |
| name = devm_kasprintf(dev, GFP_KERNEL, match->name_pattern, |
| socket); |
| break; |
| } |
| case NAME_FMT_SOCKET_INST: { |
| const int cpu = cpumask_first(&cspmu->associated_cpus); |
| const int socket = cpu_to_node(cpu); |
| u32 inst_id; |
| |
| if (!nv_cspmu_get_inst_id(cspmu, &inst_id)) |
| name = devm_kasprintf(dev, GFP_KERNEL, |
| match->name_pattern, socket, inst_id); |
| break; |
| } |
| case NAME_FMT_GENERIC: |
| name = devm_kasprintf(dev, GFP_KERNEL, match->name_pattern, |
| atomic_fetch_inc(&pmu_generic_idx)); |
| break; |
| } |
| |
| return name; |
| } |
| |
| #define SET_OP(name, impl, match, default_op) \ |
| do { \ |
| if (match->ops.name) \ |
| impl->name = match->ops.name; \ |
| else if (default_op != NULL) \ |
| impl->name = default_op; \ |
| } while (false) |
| |
| static int nv_cspmu_init_ops(struct arm_cspmu *cspmu) |
| { |
| struct nv_cspmu_ctx *ctx; |
| struct device *dev = cspmu->dev; |
| struct arm_cspmu_impl_ops *impl_ops = &cspmu->impl.ops; |
| const struct nv_cspmu_match *match = nv_cspmu_match; |
| |
| ctx = devm_kzalloc(dev, sizeof(struct nv_cspmu_ctx), GFP_KERNEL); |
| if (!ctx) |
| return -ENOMEM; |
| |
| /* Find matching PMU. */ |
| for (; match->prodid; match++) { |
| const u32 prodid_mask = match->prodid_mask; |
| |
| if ((match->prodid & prodid_mask) == |
| (cspmu->impl.pmiidr & prodid_mask)) |
| break; |
| } |
| |
| /* Initialize the context with the matched template. */ |
| memcpy(ctx, &match->template_ctx, sizeof(struct nv_cspmu_ctx)); |
| ctx->name = nv_cspmu_format_name(cspmu, match); |
| |
| cspmu->impl.ctx = ctx; |
| |
| /* NVIDIA specific callbacks. */ |
| SET_OP(validate_event, impl_ops, match, NULL); |
| SET_OP(event_type, impl_ops, match, NULL); |
| SET_OP(is_cycle_counter_event, impl_ops, match, NULL); |
| SET_OP(set_cc_filter, impl_ops, match, nv_cspmu_set_cc_filter); |
| SET_OP(set_ev_filter, impl_ops, match, nv_cspmu_set_ev_filter); |
| SET_OP(reset_ev_filter, impl_ops, match, NULL); |
| SET_OP(get_event_attrs, impl_ops, match, nv_cspmu_get_event_attrs); |
| SET_OP(get_format_attrs, impl_ops, match, nv_cspmu_get_format_attrs); |
| SET_OP(get_name, impl_ops, match, nv_cspmu_get_name); |
| |
| if (ctx->init_data) |
| return ctx->init_data(cspmu); |
| |
| return 0; |
| } |
| |
| /* Match all NVIDIA Coresight PMU devices */ |
| static const struct arm_cspmu_impl_match nv_cspmu_param = { |
| .pmiidr_val = ARM_CSPMU_IMPL_ID_NVIDIA, |
| .module = THIS_MODULE, |
| .impl_init_ops = nv_cspmu_init_ops |
| }; |
| |
| static int __init nvidia_cspmu_init(void) |
| { |
| int ret; |
| |
| ret = arm_cspmu_impl_register(&nv_cspmu_param); |
| if (ret) |
| pr_err("nvidia_cspmu backend registration error: %d\n", ret); |
| |
| return ret; |
| } |
| |
| static void __exit nvidia_cspmu_exit(void) |
| { |
| arm_cspmu_impl_unregister(&nv_cspmu_param); |
| } |
| |
| module_init(nvidia_cspmu_init); |
| module_exit(nvidia_cspmu_exit); |
| |
| MODULE_DESCRIPTION("NVIDIA Coresight Architecture Performance Monitor Driver"); |
| MODULE_LICENSE("GPL v2"); |