2024-01-31 07:11:07

by Yang Jialong 杨佳龙

[permalink] [raw]
Subject: [PATCH v2] perf/hx_arm_ni: Support uncore ARM NI-700 PMU

This code is based on uncore PMUs arm_smmuv3_pmu and arm-cmn.
One ni-700 can have many clock domains. Each of them has only one PMU.
Here one PMU corresponds to one 'struct ni_pmu' instance.
PMU name will be ni_pmu_N_M, which N means different NI-700s and M means
different PMU in one NI-700. If only one NI-700 found in NI-700, name will
be ni_pmu_N.
Node interface event name will be xxni_N_eventname, such as
asni_0_rdreq_any. There are many kinds of type of nodes in one clock
domain. Also means that there are many kinds of that in one PMU. So we
distinguish them by xxni string. Besides, maybe there are many nodes
have same type. So we have number N in event name.
By ni_pmu_0_0/asni_0_rdreq_any/, we can pinpoint accurate bus traffic.
Example1: perf stat -a -e ni_pmu_0_0/asni_0_rdreq_any/,ni_pmu_0_0/cycles/
EXample2: perf stat -a -e ni_pmu_0_0/asni,id=0,event=0x0/

Signed-off-by: JiaLong.Yang <[email protected]>
---
v1 --> v2:
1. Submit MAINTANER Documentation/ files seperately.
2. Delete some useless info printing.
3. Change print from pr_xxx to dev_xxx.
4. Fix more than 75 length log info.
5. Fix dts attribute pccs-id.
6. Fix generic name according to DT specification.
7. Some indentation.
8. Del of_match_ptr macro.

drivers/perf/Kconfig | 11 +
drivers/perf/Makefile | 1 +
drivers/perf/hx_arm_ni.c | 1284 ++++++++++++++++++++++++++++++++++++++
3 files changed, 1296 insertions(+)
create mode 100644 drivers/perf/hx_arm_ni.c

diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig
index ec6e0d9194a1..95ef8b13730f 100644
--- a/drivers/perf/Kconfig
+++ b/drivers/perf/Kconfig
@@ -241,4 +241,15 @@ config CXL_PMU

If unsure say 'm'.

+config HX_ARM_NI_PMU
+ tristate "HX ARM NI-700 PMU"
+ depends on PPC_HX_C2000 && 64BIT
+ default y
+ help
+ Support for NI-700(Network-on-chip Interconnect) PMUs, which
+ provide monitoring of transactions passing through between
+ CMN and other buses or periapherals.
+
+source "drivers/perf/hisilicon/Kconfig"
+
endmenu
diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile
index a06338e3401c..ec8b9c08577d 100644
--- a/drivers/perf/Makefile
+++ b/drivers/perf/Makefile
@@ -27,3 +27,4 @@ obj-$(CONFIG_DWC_PCIE_PMU) += dwc_pcie_pmu.o
obj-$(CONFIG_ARM_CORESIGHT_PMU_ARCH_SYSTEM_PMU) += arm_cspmu/
obj-$(CONFIG_MESON_DDR_PMU) += amlogic/
obj-$(CONFIG_CXL_PMU) += cxl_pmu.o
+obj-$(CONFIG_HX_ARM_NI_PMU) += hx_arm_ni.o
diff --git a/drivers/perf/hx_arm_ni.c b/drivers/perf/hx_arm_ni.c
new file mode 100644
index 000000000000..619e3b789dda
--- /dev/null
+++ b/drivers/perf/hx_arm_ni.c
@@ -0,0 +1,1284 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HX ARM-NI-700 uncore PMU support
+ *
+ * This code is based on uncore PMUs arm_smmuv3_pmu and arm-cmn.
+ *
+ * One ni-700 can have many clock domains. Each of them has only one PMU.
+ * Here one PMU corresponds to one 'struct ni_pmu' instance.
+ *
+ * PMU name will be ni_pmu_N_M, which N means different NI-700s and M means
+ * different PMU in one NI-700. If only one NI-700 found in NI-700, name
+ * will be ni_pmu_N.
+ *
+ * Node interface event name will be xxni_N_eventname, such as
+ * asni_0_rdreq_any. There are many kinds of type of nodes in one clock
+ * domain. Also means that there are many kinds of that in one PMU. So we
+ * distinguish them by xxni string. Besides, maybe there are many nodes
+ * have same type. So we have number N in event name.
+ * By ni_pmu_0_0/asni_0_rdreq_any/, we can pinpoint accurate bus traffic.
+ *
+ * Example1: perf stat -a -e ni_pmu_0_0/asni_0_rdreq_any/,ni_pmu_0_0/cycles/
+ * Example2: perf stat -a -e ni_pmu_0_0/asni,id=0,event=0x0/
+ *
+ * TODO: Secure or non-secure attribute in all event omitted now.
+ *
+ */
+
+#define dev_fmt(fmt) "ni-700 pmu: " fmt
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cpuhotplug.h>
+#include <linux/cpumask.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/msi.h>
+#include <linux/of.h>
+#include <linux/perf_event.h>
+#include <linux/platform_device.h>
+#include <linux/smp.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/build_bug.h>
+
+/* number of counters in one ni pmu */
+#define NI_PMU_COUNTER_NUM 8
+
+/* node type values */
+enum ni_node_type {
+ NI_BASE = 0x0,
+ NI_VD,
+ NI_PD,
+ NI_CD,
+ NI_ASNI = 0x4,
+ NI_AMNI,
+ NI_PMU,
+ NI_HSNI,
+ NI_HMNI,
+ NI_PMNI = 0x9,
+};
+
+/* event format */
+/**
+ * config:
+ * 0-5 31 32-47 48-63
+ * event cycles node_type node_id
+ *
+ */
+#define NI_EVENT_FORMAT_EVENT GENMASK_ULL(5, 0)
+#define NI_EVENT_FORMAT_CYCLES (1ULL << 31)
+#define NI_EVENT_FORMAT_NODETYPE GENMASK_ULL(32 + NI_PMNI, 32)
+#define NI_EVENT_FORMAT_ASNI BIT(32 + NI_ASNI)
+#define NI_EVENT_FORMAT_AMNI BIT(32 + NI_AMNI)
+#define NI_EVENT_FORMAT_HSNI BIT(32 + NI_HSNI)
+#define NI_EVENT_FORMAT_HMNI BIT(32 + NI_HMNI)
+#define NI_EVENT_FORMAT_PMNI BIT(32 + NI_PMNI)
+#define NI_EVENT_FORMAT_NODEID GENMASK_ULL(63, 48)
+
+#define NI_EVENT_FORMAT_NODE_TYPE GENMASK_ULL(63, 32)
+
+#define ni_event_config_eventid(_config) FIELD_GET(NI_EVENT_FORMAT_EVENT, _config)
+#define ni_event_config_cc(_config) FIELD_GET(NI_EVENT_FORMAT_CYCLES, _config)
+#define _ni_event_config_nodetype(_config) FIELD_GET(NI_EVENT_FORMAT_NODETYPE, _config)
+#define ni_event_config_nodeid(_config) FIELD_GET(NI_EVENT_FORMAT_NODEID, _config)
+
+#define NI_NODE_TYPE_MASK GENMASK(15, 0)
+#define NI_NODE_ID_MASK GENMASK(31, 16)
+
+#define NI_PMU_PMCR_RST_CYC_CNTR BIT(2)
+#define NI_PMU_PMCR_RST_EV_CNTR BIT(1)
+#define NI_PMU_PMCR_ENABLE BIT(0)
+
+static const char *const ni_node_name[] = {
+ [NI_ASNI] = "asni",
+ [NI_AMNI] = "amni",
+ [NI_PMU] = "pmu",
+ [NI_HSNI] = "hsni",
+ [NI_HMNI] = "hmni",
+ [NI_PMNI] = "pmni",
+};
+
+/* one instance for one node */
+struct ni_node {
+ void __iomem *base;
+ union {
+ struct {
+ u32 type:16;
+ u32 id:16;
+ };
+ u32 node_type;
+ };
+};
+
+/* xxx_reg_map only used to provide offset by using offsetof(). */
+struct ni_node_reg_map {
+ union {
+ struct {
+ u32 type:16;
+ u32 id:16;
+ };
+ u32 node_type;
+ };
+
+ union {
+ u32 child_num;
+ u32 node_info;
+ };
+
+ union {
+ struct {
+ u32 secr_acc;
+ u32 pmusela;
+ u32 pmuselb;
+ };
+ DECLARE_FLEX_ARRAY(u32, child_offset);
+ };
+};
+
+#define ni_node_offsetof(member) \
+ offsetof(struct ni_node_reg_map, member)
+
+#define ni_node_pmuevsel(node, config) \
+ do { \
+ writel(config, node->base + ni_node_offsetof(pmusela)); \
+ writel(config >> 32, node->base + ni_node_offsetof(pmuselb)); \
+ } while (0)
+
+#define ni_node_read(base, member, readx) \
+ readx((void __iomem *)base + ni_node_offsetof(member))
+
+#define ni_node_type(base) \
+ FIELD_GET(NI_NODE_TYPE_MASK, ni_node_read(base, node_type, readl_relaxed))
+
+#define ni_node_id(base) \
+ FIELD_GET(NI_NODE_ID_MASK, ni_node_read(base, node_type, readl_relaxed))
+
+#define ni_node_node_type(base) \
+ ni_node_read(base, node_type, readl_relaxed)
+
+#define ni_child_number(base) \
+ (ni_node_type(base) < NI_ASNI ? ni_node_read(base, child_num, readl_relaxed) : 0)
+
+#define ni_child_pointer(periphbase, base, idx) \
+ ((void __iomem *)periphbase + ni_node_read(base, child_offset[idx], readl_relaxed))
+
+struct ni_pmu;
+struct ni_pmu_reg_map {
+ u32 node_type; /* offset: 0x000 */
+ u32 secr_acc; /* offset: 0x004 */
+ struct {
+ u32 counter;
+ u32 reserved;
+ } pmevcntr[8]; /* offset: 0x008 */
+ u8 reserved_1[0xF8 - 0x48]; /* offset: 0x048 */
+ u32 pmccntr_lower; /* offset: 0x0F8 */
+ u32 pmccntr_upper; /* offset: 0x0FC */
+ u8 reserved_2[0x400 - 0x100]; /* offset: 0x100 */
+ u32 pmevtyper[8]; /* offset: 0x400 */
+ u8 reserved_3[0x610 - 0x420]; /* offset: 0x420 */
+ u32 pmssr; /* offset: 0x610 */
+ u32 pmovssr; /* offset: 0x614 */
+ u32 pmccntsr_lower; /* offset: 0x618 */
+ u32 pmccntsr_upper; /* offset: 0x61C */
+ u32 pmevcntsr[8]; /* offset: 0x620 */
+ u8 reserved_4[0x6F0 - 0x640]; /* offset: 0x640 */
+ u32 pmsscr; /* offset: 0x6F0 */
+ u8 reserved_5[0xC00 - 0x6F4]; /* offset: 0x6F4 */
+ u32 pmcntenset; /* offset: 0xC00 */
+ u8 reserved_6[0xC20 - 0xC04]; /* offset: 0xC04 */
+ u32 pmcntenclr; /* offset: 0xC20 */
+ u8 reserved_7[0xC40 - 0xC24]; /* offset: 0xC24 */
+ u32 pmintenset; /* offset: 0xC40 */
+ u8 reserved_8[0xC60 - 0xC44]; /* offset: 0xC44 */
+ u32 pmintenclr; /* offset: 0xC60 */
+ u8 reserved_9[0xC80 - 0xC64]; /* offset: 0xC64 */
+ u32 pmovsclr; /* offset: 0xC80 */
+ u8 reserved_10[0xCC0 - 0xC84]; /* offset: 0xC84 */
+ u32 pmovsset; /* offset: 0xCC0 */
+ u8 reserved_11[0xD80 - 0xCC4]; /* offset: 0xCC4 */
+ u32 pmcccgr; /* offset: 0xD80 */
+ u8 reserved_12[0xE00 - 0xD84]; /* offset: 0xD84 */
+ u32 pmcfgr; /* offset: 0xE00 */
+ u32 pmcr; /* offset: 0xE04 */
+};
+
+/* Not read or write registers directly. */
+#define ni_pmu_offset(ni_pmu, member) \
+ ((void __iomem *)ni_pmu->pmu_node.base + offsetof(struct ni_pmu_reg_map, member))
+
+#define ni_pmu_interrupt_enable(ni_pmu, en_bit_mask) \
+ writel(en_bit_mask, ni_pmu_offset(ni_pmu, pmintenset))
+
+#define ni_pmu_interrupt_disable(ni_pmu, en_bit_mask) \
+ writel(en_bit_mask, ni_pmu_offset(ni_pmu, pmintenclr))
+
+#define ni_pmu_counter_enable(ni_pmu, en_bit_mask) \
+ writel(en_bit_mask, ni_pmu_offset(ni_pmu, pmcntenset))
+
+#define ni_pmu_counter_disable(ni_pmu, en_bit_mask) \
+ writel(en_bit_mask, ni_pmu_offset(ni_pmu, pmcntenclr))
+
+#define ni_pmu_pmevtyper_sel_node(ni_pmu, ev_typer, cnt_idx) \
+ writel(ev_typer, ni_pmu_offset(ni_pmu, pmevtyper[cnt_idx]))
+
+struct global_ni {
+ void __iomem *base;
+ struct hlist_node node;
+ struct device *dev;
+ union {
+ unsigned int pmu_num;
+ unsigned int cd_num;
+ };
+ unsigned int on_cpu;
+ int irq_num;
+ struct ni_pmu *ni_pmus[];
+};
+
+
+struct ni_pmu {
+ struct ni_node pmu_node;
+ struct perf_event *events[NI_PMU_COUNTER_NUM + 1];
+ struct pmu pmu;
+ struct device *dev;
+ unsigned int irq;
+ struct global_ni *ni;
+ int ev_src_num;
+ struct ni_node ev_src_nodes[];
+};
+
+#define to_ni_pmu(_pmu) container_of(_pmu, struct ni_pmu, pmu)
+
+struct ni_hw_perf_event {
+ /* cycle event */
+ bool is_cc;
+ /* The event corresponds to idxth counter */
+ int idx;
+ /* Enable bit field in pmcntenset */
+#define NI_PMU_CC_EN_BIT 31
+ u32 en_bit_mask;
+ /* value writen in counter */
+ u64 init_val;
+ /* If no cc event, config will be writen in pmusela/b */
+ u64 config;
+ /* The event corresponds to ni_pmu::ev_src_nodes[node_idx] */
+ int node_idx;
+ /* overwrite state in hw_perf_event */
+ int state;
+ /* value writen in pmevtyper */
+#define NI_PMU_PMEVTYPER_NDTP_OFFSET 9
+ union {
+ struct {
+ u32 id: 9;
+ u32 type: 4;
+ } node;
+ u32 ev_typer;
+ };
+};
+
+#define to_ni_hw(event) ((struct ni_hw_perf_event *)&event->hw)
+
+struct ni_event_desc {
+ u64 eventid;
+ const char *name;
+};
+
+struct ni_event_attr {
+ struct device_attribute attr;
+ struct ni_event_desc *ev_desc;
+ struct ni_node *node;
+};
+
+#define to_ni_event_attr(p) \
+ container_of(p, struct ni_event_attr, attr)
+
+#define NI_EVENT_DESC(_eventid, _name) \
+ (&((struct ni_event_desc[]) { \
+ { .name = __stringify(_name), \
+ .eventid = _eventid,} \
+ })[0])
+
+
+static struct ni_event_desc *ni_asni_event_descs[] = {
+ NI_EVENT_DESC(0x00, rdreq_any),
+ NI_EVENT_DESC(0x01, rdreq_dev_arcache),
+ NI_EVENT_DESC(0x02, rdreq_rns),
+ NI_EVENT_DESC(0x03, rdreq_ro),
+ NI_EVENT_DESC(0x04, req_cache_clr),
+ NI_EVENT_DESC(0x05, rdreq_beat_any),
+ NI_EVENT_DESC(0x06, rdreq_handshake_rlast),
+ NI_EVENT_DESC(0x07, wtreq_any),
+ NI_EVENT_DESC(0x08, wtreq_dev),
+ NI_EVENT_DESC(0x09, wtreq_wns),
+ NI_EVENT_DESC(0x0a, wtreq_wlu),
+ NI_EVENT_DESC(0x0b, wtreq_wu),
+ NI_EVENT_DESC(0x0c, wtreq_atomic),
+ NI_EVENT_DESC(0x0d, wtreq_beat_any),
+ NI_EVENT_DESC(0x0e, rdreq_stall),
+ NI_EVENT_DESC(0x0f, rddata_stall),
+ NI_EVENT_DESC(0x10, wtreq_stall),
+ NI_EVENT_DESC(0x11, wtdata_stall),
+ NI_EVENT_DESC(0x12, wtresp_stall),
+ NI_EVENT_DESC(0x13, wtreq_cst),
+ NI_EVENT_DESC(0x14, wtchann_nopersist),
+ NI_EVENT_DESC(0x15, wtchann_persist),
+ NI_EVENT_DESC(0x16, rdreq_nzero_mem_ops),
+ NI_EVENT_DESC(0x17, wtreq_nzero_mem_ops),
+ NI_EVENT_DESC(0x20, req_stall_cc_ot_limit),
+ NI_EVENT_DESC(0x21, req_stall_cc_tspec_limit),
+ NI_EVENT_DESC(0x22, req_stall_arbit),
+ NI_EVENT_DESC(0x23, req_stall_rd_tracker),
+ NI_EVENT_DESC(0x24, req_stall_wt_tracker),
+ NI_EVENT_DESC(0x25, aw_stall_wdatafifo_full),
+ NI_EVENT_DESC(0x26, ar_stall_reorderbuf_full),
+ NI_EVENT_DESC(0x27, aw_cdas_stall),
+ NI_EVENT_DESC(0x28, ar_cdas_stall),
+ NI_EVENT_DESC(0x29, atomic_rd_stall),
+ NI_EVENT_DESC(0x2a, wtchann_wtreq_stall),
+ NI_EVENT_DESC(0x2b, rdchann_rdreq_stall),
+ NI_EVENT_DESC(0x2c, aw_stall_ot),
+ NI_EVENT_DESC(0x2d, ar_stall_ot),
+ NI_EVENT_DESC(0x2e, aw_stall_tspec),
+ NI_EVENT_DESC(0x2f, ar_stall_tspec),
+ NI_EVENT_DESC(0x30, lwmd_arbit_stall_wchann),
+ NI_EVENT_DESC(0x31, lwmd_arbit_stall_rchann),
+};
+
+static struct ni_event_desc *ni_amni_event_descs[] = {
+ NI_EVENT_DESC(0x00, rdreq_any),
+ NI_EVENT_DESC(0x01, rdreq_dev_arcache),
+ NI_EVENT_DESC(0x02, rdreq_rns),
+ NI_EVENT_DESC(0x03, rdreq_ro),
+ NI_EVENT_DESC(0x04, req_cache_clr),
+ NI_EVENT_DESC(0x05, rdreq_beat_any),
+ NI_EVENT_DESC(0x06, rdreq_handshake_rlast),
+ NI_EVENT_DESC(0x07, wtreq_any),
+ NI_EVENT_DESC(0x08, wtreq_dev),
+ NI_EVENT_DESC(0x09, wtreq_wns),
+ NI_EVENT_DESC(0x0a, wtreq_wlu),
+ NI_EVENT_DESC(0x0b, wtreq_wu),
+ NI_EVENT_DESC(0x0c, wtreq_atomic),
+ NI_EVENT_DESC(0x0d, wtreq_beat_any),
+ NI_EVENT_DESC(0x0e, rdreq_stall),
+ NI_EVENT_DESC(0x0f, rddata_stall),
+ NI_EVENT_DESC(0x10, wtreq_stall),
+ NI_EVENT_DESC(0x11, wtdata_stall),
+ NI_EVENT_DESC(0x12, wtresp_stall),
+ NI_EVENT_DESC(0x13, wtreq_cst),
+ NI_EVENT_DESC(0x14, wtchann_nopersist),
+ NI_EVENT_DESC(0x15, wtchann_persist),
+ NI_EVENT_DESC(0x16, rdreq_nzero_mem_ops),
+ NI_EVENT_DESC(0x17, wtreq_nzero_mem_ops),
+ NI_EVENT_DESC(0x20, req_stall_rd_tracker),
+ NI_EVENT_DESC(0x21, req_stall_wt_tracker),
+ NI_EVENT_DESC(0x22, wtchann_b_resp),
+ NI_EVENT_DESC(0x23, rdchann_rd_resp),
+ NI_EVENT_DESC(0x24, lwmd_arbit_stall_wchann),
+ NI_EVENT_DESC(0x25, lwmd_arbit_stall_rchann),
+};
+
+static struct ni_event_desc *ni_hsni_event_descs[] = {
+ NI_EVENT_DESC(0x00, rdreq_any),
+ NI_EVENT_DESC(0x01, rdreq_dev),
+ NI_EVENT_DESC(0x02, rdreq_noshare),
+ NI_EVENT_DESC(0x03, rdreq_share),
+ NI_EVENT_DESC(0x04, rdreq_share_nonormal),
+ NI_EVENT_DESC(0x05, rdreq_beat_any),
+ NI_EVENT_DESC(0x07, wtreq_any),
+ NI_EVENT_DESC(0x08, wtreq_dev),
+ NI_EVENT_DESC(0x09, wtreq_noshare),
+ NI_EVENT_DESC(0x0a, wtreq_all),
+ NI_EVENT_DESC(0x0b, wtreq_share),
+ NI_EVENT_DESC(0x0c, wtreq_share_nonormal),
+ NI_EVENT_DESC(0x0d, wtreq_beat_any),
+ NI_EVENT_DESC(0x0f, rddata_stall),
+ NI_EVENT_DESC(0x11, wtdata_stall),
+ NI_EVENT_DESC(0x20, req_stall_cc_ot_limit),
+ NI_EVENT_DESC(0x21, req_stall_cc_tspec_limit),
+ NI_EVENT_DESC(0x22, rdreq_stall_cc_ely_wtresp),
+ NI_EVENT_DESC(0x24, req_stall_nzero_wtcnt),
+ NI_EVENT_DESC(0x25, w_stall_wdatafifo_full),
+ NI_EVENT_DESC(0x2a, wtreq_stall_lack_gt),
+ NI_EVENT_DESC(0x2b, rdreq_stall_lack_gt),
+};
+
+static struct ni_event_desc *ni_hmni_event_descs[] = {
+ NI_EVENT_DESC(0x00, rdreq_any),
+ NI_EVENT_DESC(0x01, rdreq_dev),
+ NI_EVENT_DESC(0x02, rdreq_noshare),
+ NI_EVENT_DESC(0x03, rdreq_share),
+ NI_EVENT_DESC(0x04, rdreq_share_nonormal),
+ NI_EVENT_DESC(0x05, rdreq_beat_any),
+ NI_EVENT_DESC(0x07, wtreq_any),
+ NI_EVENT_DESC(0x08, wtreq_dev),
+ NI_EVENT_DESC(0x09, wtreq_noshare),
+ NI_EVENT_DESC(0x0a, wtreq_all),
+ NI_EVENT_DESC(0x0b, wtreq_share),
+ NI_EVENT_DESC(0x0c, wtreq_share_nonormal),
+ NI_EVENT_DESC(0x0d, wtreq_beat_any),
+ NI_EVENT_DESC(0x0e, rd_addr_phase_stall),
+ NI_EVENT_DESC(0x0f, rd_data_phase_stall),
+ NI_EVENT_DESC(0x10, wt_addr_phase_stall),
+ NI_EVENT_DESC(0x11, wt_data_phase_stall),
+ NI_EVENT_DESC(0x22, wtresp_stall_lack_gt),
+ NI_EVENT_DESC(0x23, rdresp_stall_lack_gt),
+};
+
+static struct ni_event_desc *ni_pmni_event_descs[] = {
+ NI_EVENT_DESC(0x00, rdreq_any),
+ NI_EVENT_DESC(0x01, rdreq_dev_arcache),
+ NI_EVENT_DESC(0x02, rdreq_noshared),
+ NI_EVENT_DESC(0x05, rd_prdata_any),
+ NI_EVENT_DESC(0x07, wtreq_any),
+ NI_EVENT_DESC(0x08, wtreq_dev),
+ NI_EVENT_DESC(0x09, wtreq_noshared),
+ NI_EVENT_DESC(0x0d, wtdata_beat_any),
+ NI_EVENT_DESC(0x0e, rdreq_stall),
+ NI_EVENT_DESC(0x0f, rddata_stall),
+ NI_EVENT_DESC(0x10, wtreq_stall),
+ NI_EVENT_DESC(0x11, wtdata_stall),
+ NI_EVENT_DESC(0x22, wtresp_stall_lack_gt),
+ NI_EVENT_DESC(0x23, rdresp_stall_lack_gt),
+};
+
+static int ni_ev_desc_array_size(enum ni_node_type type,
+ struct ni_event_desc ***descs)
+{
+ switch (type) {
+ case NI_ASNI:
+ if (descs)
+ *descs = ni_asni_event_descs;
+ return ARRAY_SIZE(ni_asni_event_descs);
+ case NI_AMNI:
+ if (descs)
+ *descs = ni_amni_event_descs;
+ return ARRAY_SIZE(ni_amni_event_descs);
+ case NI_HSNI:
+ if (descs)
+ *descs = ni_hsni_event_descs;
+ return ARRAY_SIZE(ni_hsni_event_descs);
+ case NI_HMNI:
+ if (descs)
+ *descs = ni_hmni_event_descs;
+ return ARRAY_SIZE(ni_hmni_event_descs);
+ case NI_PMNI:
+ if (descs)
+ *descs = ni_pmni_event_descs;
+ return ARRAY_SIZE(ni_pmni_event_descs);
+ default:
+ return 0;
+ }
+}
+
+static ssize_t ni_event_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ni_event_attr *eattr;
+
+ eattr = to_ni_event_attr(attr);
+
+ if (eattr->ev_desc)
+ return sysfs_emit(buf,
+ "%s,id=0x%x,event=0x%llx\n",
+ ni_node_name[eattr->node->type],
+ eattr->node->id,
+ eattr->ev_desc->eventid);
+
+ return sysfs_emit(buf, "cycles\n");
+}
+
+struct ni_format_attr {
+ struct device_attribute attr;
+ u64 field;
+};
+
+static ssize_t ni_format_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ni_format_attr *fmt = container_of(attr, struct ni_format_attr, attr);
+ int lo = __ffs(fmt->field), hi = __fls(fmt->field);
+
+ if (lo == hi)
+ return sysfs_emit(buf, "config:%d\n", lo);
+
+ return sysfs_emit(buf, "config:%d-%d\n", lo, hi);
+}
+
+
+#define NI_FORMAT_ATTR(_name, _fld) \
+ (&((struct ni_format_attr[]) {{ \
+ .attr = __ATTR(_name, 0444, ni_format_show, NULL), \
+ .field = _fld, \
+ }})[0].attr.attr)
+
+static struct attribute *ni_format_attrs[] = {
+ NI_FORMAT_ATTR(event, NI_EVENT_FORMAT_EVENT),
+ NI_FORMAT_ATTR(cycles, NI_EVENT_FORMAT_CYCLES),
+ NI_FORMAT_ATTR(asni, NI_EVENT_FORMAT_ASNI),
+ NI_FORMAT_ATTR(amni, NI_EVENT_FORMAT_AMNI),
+ NI_FORMAT_ATTR(hsni, NI_EVENT_FORMAT_HSNI),
+ NI_FORMAT_ATTR(hmni, NI_EVENT_FORMAT_HMNI),
+ NI_FORMAT_ATTR(pmni, NI_EVENT_FORMAT_PMNI),
+ NI_FORMAT_ATTR(id, NI_EVENT_FORMAT_NODEID),
+ NULL
+};
+
+static const struct attribute_group ni_format_attrs_group = {
+ .name = "format",
+ .attrs = ni_format_attrs,
+};
+
+static ssize_t ni_cpumask_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ni_pmu *ni_pmu = to_ni_pmu(dev_get_drvdata(dev));
+
+ return cpumap_print_to_pagebuf(true, buf, cpumask_of(ni_pmu->ni->on_cpu));
+}
+
+static struct device_attribute ni_cpumask_attr =
+ __ATTR(cpumask, 0444, ni_cpumask_show, NULL);
+
+static struct attribute *ni_addition_attrs[] = {
+ &ni_cpumask_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group ni_addition_attrs_group = {
+ .attrs = ni_addition_attrs,
+};
+
+static u64 ni_cntr_get_and_init_optionally(struct perf_event *event, bool init)
+{
+ u64 old_val, new_val;
+ struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
+ struct ni_hw_perf_event *hwc = to_ni_hw(event);
+
+ if (!hwc->is_cc)
+ old_val = readl(ni_pmu_offset(ni_pmu, pmevcntr[hwc->idx].counter));
+ else
+ old_val = readl(ni_pmu_offset(ni_pmu, pmccntr_lower))
+ | (((u64)readl(ni_pmu_offset(ni_pmu, pmccntr_upper))) << 32);
+
+ if (!init)
+ return old_val;
+
+ new_val = hwc->init_val;
+ if (!hwc->is_cc)
+ writel(new_val, ni_pmu_offset(ni_pmu, pmevcntr[hwc->idx].counter));
+ else {
+ writel(new_val, ni_pmu_offset(ni_pmu, pmccntr_lower));
+ writel(new_val >> 32, ni_pmu_offset(ni_pmu, pmccntr_upper));
+ }
+
+ return old_val;
+}
+
+static void ni_pmu_event_update(struct perf_event *event)
+{
+ struct ni_hw_perf_event *hwc = to_ni_hw(event);
+ u64 delta, prev, now;
+
+ do {
+ prev = local64_read(&event->hw.prev_count);
+ now = ni_cntr_get_and_init_optionally(event, false);
+ } while (local64_cmpxchg(&event->hw.prev_count, prev, now) != prev);
+
+ delta = now - prev;
+
+ if (!hwc->is_cc)
+ delta &= 0xFFFFFFFFULL;
+
+ local64_add(delta, &event->count);
+}
+
+static void ni_pmu_set_period(struct perf_event *event)
+{
+ struct ni_hw_perf_event *hwc = to_ni_hw(event);
+
+ ni_cntr_get_and_init_optionally(event, true);
+
+ local64_set(&event->hw.prev_count, hwc->init_val);
+}
+
+static void ni_pmu_enable(struct pmu *pmu)
+{
+ struct ni_pmu *ni_pmu = to_ni_pmu(pmu);
+
+ writel(NI_PMU_PMCR_ENABLE, ni_pmu_offset(ni_pmu, pmcr));
+}
+
+static inline void ni_pmu_disable(struct pmu *pmu)
+{
+ struct ni_pmu *ni_pmu = to_ni_pmu(pmu);
+
+ writel(0, ni_pmu_offset(ni_pmu, pmcr));
+}
+
+static int ni_pmu_find_ev_src(struct ni_pmu *ni_pmu, u32 node_type)
+{
+ int idx;
+
+ for (idx = 0; idx < ni_pmu->ev_src_num; idx++)
+ if (ni_pmu->ev_src_nodes[idx].node_type == node_type)
+ break;
+
+ return idx;
+}
+
+static bool is_event_supported(u64 eventid, enum ni_node_type type)
+{
+ int num;
+ int idx;
+ struct ni_event_desc **descs;
+
+ num = ni_ev_desc_array_size(type, &descs);
+
+ for (idx = 0; idx < num; idx++)
+ if (eventid == descs[idx]->eventid)
+ break;
+
+ return idx == num ? false : true;
+}
+
+static enum ni_node_type ni_event_config_nodetype(u64 config)
+{
+ u64 nodetype = _ni_event_config_nodetype(config);
+ unsigned long lo = __ffs(nodetype), hi = __fls(nodetype);
+
+ if (!nodetype || lo != hi)
+ return 0;
+
+ return lo;
+
+}
+
+static int ni_pmu_event_init(struct perf_event *event)
+{
+ struct ni_hw_perf_event *hwc = to_ni_hw(event);
+ struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
+ u64 config;
+ enum ni_node_type nodetype;
+ u32 node_type;
+
+ memset(hwc, 0, sizeof(*hwc));
+
+ if (event->attr.type != event->pmu->type)
+ return -ENOENT;
+
+ if (is_sampling_event(event))
+ return -EINVAL;
+
+ event->cpu = ni_pmu->ni->on_cpu;
+
+ config = event->attr.config;
+
+ hwc->is_cc = ni_event_config_cc(config);
+
+ if (hwc->is_cc)
+ return 0;
+
+ nodetype = ni_event_config_nodetype(config);
+ if (!nodetype)
+ return -EINVAL;
+
+ hwc->node.id = ni_event_config_nodeid(config);
+ hwc->node.type = nodetype;
+ hwc->config = ni_event_config_eventid(config);
+
+ node_type = hwc->node.id << 16 | nodetype;
+ hwc->node_idx = ni_pmu_find_ev_src(ni_pmu, node_type);
+ if (hwc->node_idx == ni_pmu->ev_src_num)
+ return -EINVAL;
+
+ if (!is_event_supported(hwc->config, nodetype))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void ni_pmu_event_start(struct perf_event *event, int flags)
+{
+ struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
+ struct ni_hw_perf_event *hwc = to_ni_hw(event);
+
+ hwc->state = 0;
+
+ ni_pmu_set_period(event);
+
+ if (!hwc->is_cc) {
+ ni_node_pmuevsel((&ni_pmu->ev_src_nodes[hwc->node_idx]), hwc->config);
+ ni_pmu_pmevtyper_sel_node(ni_pmu, hwc->ev_typer, hwc->idx);
+ }
+
+ ni_pmu_counter_enable(ni_pmu, hwc->en_bit_mask);
+}
+
+static void ni_pmu_event_stop(struct perf_event *event, int flags)
+{
+ struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
+ struct ni_hw_perf_event *hwc = to_ni_hw(event);
+
+ if (hwc->state & PERF_HES_STOPPED)
+ return;
+
+ ni_pmu_counter_disable(ni_pmu, hwc->en_bit_mask);
+
+ ni_pmu_event_update(event);
+
+ hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE;
+}
+
+static void ni_pmu_event_read(struct perf_event *event)
+{
+ ni_pmu_event_update(event);
+}
+
+static int ni_pmu_event_add(struct perf_event *event, int flags)
+{
+ struct ni_hw_perf_event *hwc = to_ni_hw(event);
+ struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
+ int idx;
+
+ idx = 0;
+ if (hwc->is_cc && ni_pmu->events[NI_PMU_COUNTER_NUM])
+ return -EAGAIN; /* The cycle counter is in use. */
+
+ idx = 0;
+ if (hwc->is_cc)
+ idx = NI_PMU_COUNTER_NUM;
+ else
+ while ((idx < NI_PMU_COUNTER_NUM) && ni_pmu->events[idx])
+ idx++;
+
+ if (!hwc->is_cc && idx == NI_PMU_COUNTER_NUM)
+ return -EAGAIN; /* All general counter is in use. */
+
+ hwc->idx = idx;
+
+ hwc->en_bit_mask = hwc->is_cc ? BIT(NI_PMU_CC_EN_BIT) : BIT(idx);
+ hwc->init_val = hwc->is_cc ? (0x1ULL << 63) : (0x1ULL << 31);
+ hwc->config = hwc->config << idx * 8; /* including is_cc */
+ hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE;
+
+ local64_set(&event->hw.prev_count, 0);
+
+ ni_pmu->events[idx] = event;
+
+ ni_pmu_interrupt_enable(ni_pmu, hwc->en_bit_mask);
+
+ if (flags & PERF_EF_START)
+ ni_pmu_event_start(event, flags);
+
+ return 0;
+}
+
+static void ni_pmu_event_del(struct perf_event *event, int flags)
+{
+ struct ni_hw_perf_event *hwc = to_ni_hw(event);
+ struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
+
+ ni_pmu_event_stop(event, flags);
+ ni_pmu_interrupt_disable(ni_pmu, hwc->en_bit_mask);
+ ni_pmu->events[hwc->idx] = NULL;
+}
+
+static irqreturn_t _ni_pmu_handle_irq(struct ni_pmu *ni_pmu)
+{
+ u64 ovsr;
+ int idx;
+ struct perf_event *event;
+ struct ni_hw_perf_event *hwc;
+
+ ovsr = readl(ni_pmu_offset(ni_pmu, pmovsclr));
+ if (!ovsr)
+ return IRQ_NONE;
+
+ writel(ovsr, ni_pmu_offset(ni_pmu, pmovsclr));
+
+ for_each_set_bit(idx, (unsigned long *)&ovsr, 32) {
+ if (idx >= NI_PMU_COUNTER_NUM)
+ idx = NI_PMU_COUNTER_NUM;
+
+ event = ni_pmu->events[idx];
+ if (WARN_ON_ONCE(!event))
+ continue;
+
+ hwc = to_ni_hw(event);
+ ni_pmu_event_update(event);
+ ni_pmu_set_period(event);
+ if (idx == NI_PMU_COUNTER_NUM)
+ break;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t ni_pmu_handle_irq(int irq_num, void *data)
+{
+ struct ni_pmu *ni_pmu = data;
+ int idx, ret = IRQ_NONE;
+
+ if (ni_pmu->ni->irq_num != 1)
+ return _ni_pmu_handle_irq(ni_pmu);
+
+ for (idx = 0; idx < ni_pmu->ni->pmu_num; idx++)
+ ret |= _ni_pmu_handle_irq(ni_pmu->ni->ni_pmus[idx]);
+
+ return ret;
+}
+
+static int ni_hp_state;
+static int ni_pmu_offline_cpu(unsigned int cpu, struct hlist_node *node)
+{
+ struct global_ni *ni;
+ unsigned int target;
+ int idx;
+
+
+ ni = hlist_entry_safe(node, struct global_ni, node);
+ if (cpu != ni->on_cpu)
+ return 0;
+
+
+ target = cpumask_any_but(cpu_online_mask, cpu);
+ if (target >= nr_cpu_ids)
+ return 0;
+
+
+ for (idx = 0; idx < ni->pmu_num; idx++) {
+ perf_pmu_migrate_context(&ni->ni_pmus[idx]->pmu, cpu, target);
+#ifndef CONFIG_PPC_HX_C2000
+ WARN_ON(irq_set_affinity(ni->ni_pmus[idx]->irq, cpumask_of(target)));
+#endif
+ }
+
+ ni->on_cpu = target;
+
+ return 0;
+}
+
+static u32 ni_child_number_total(void __iomem *periphbase,
+ void __iomem *from, enum ni_node_type type)
+{
+ enum ni_node_type node_type;
+ int total, idx;
+ void __iomem *child_base;
+
+ node_type = ni_node_type(from);
+
+ if (node_type == type)
+ return 1;
+
+ if (node_type >= NI_ASNI)
+ return 0;
+
+ total = 0;
+ for (idx = 0; idx < ni_child_number(from); idx++) {
+ child_base = ni_child_pointer(periphbase, from, idx);
+ total += ni_child_number_total(periphbase, child_base, type);
+ }
+
+ return total;
+}
+
+static void ni_pmu_reset(struct ni_pmu *ni_pmu)
+{
+ ni_pmu_disable(&ni_pmu->pmu);
+
+#define clear_reg(name) \
+ writel(readl(ni_pmu_offset(ni_pmu, name)), ni_pmu_offset(ni_pmu, name))
+
+ clear_reg(pmcntenclr);
+ clear_reg(pmintenclr);
+ clear_reg(pmovsclr);
+
+ writel_relaxed(NI_PMU_PMCR_RST_CYC_CNTR & NI_PMU_PMCR_RST_EV_CNTR,
+ ni_pmu_offset(ni_pmu, pmcr));
+}
+
+static int ni_pmu_irq_setup(struct ni_pmu *ni_pmu, int irq_idx)
+{
+ int err;
+ unsigned long flags = IRQF_NOBALANCING | IRQF_SHARED | IRQF_NO_THREAD;
+
+ ni_pmu->irq = platform_get_irq(to_platform_device(ni_pmu->dev), irq_idx);
+ if (ni_pmu->irq < 0)
+ return ni_pmu->irq;
+
+ err = devm_request_irq(ni_pmu->dev, ni_pmu->irq, ni_pmu_handle_irq,
+ flags, dev_name(ni_pmu->dev), ni_pmu);
+ if (err)
+ return err;
+
+#ifndef CONFIG_PPC_HX_C2000
+ err = irq_set_affinity(ni_pmu->irq, cpumask_of(ni_pmu->ni->on_cpu));
+ if (err)
+ return err;
+#endif
+
+ return 0;
+}
+
+static int ni_event_attr_init(struct device *dev,
+ struct ni_event_attr *eattr,
+ struct ni_node *node,
+ struct ni_event_desc *desc)
+{
+ struct attribute *attr;
+ const char *name;
+
+ attr = &eattr->attr.attr;
+
+ sysfs_attr_init(attr);
+
+ eattr->ev_desc = desc;
+ eattr->node = node;
+
+ if (desc && node)
+ name = devm_kasprintf(dev,
+ GFP_KERNEL,
+ "%s_%d_%s",
+ ni_node_name[node->type],
+ node->id,
+ desc->name);
+ else if (!desc && !node)
+ name = "cycles";
+ else {
+ WARN(1, "No such type attr. Discovery Error!");
+ return -EINVAL;
+ }
+
+ if (!name)
+ return -ENOMEM;
+
+ eattr->attr = (struct device_attribute){
+ .attr = {
+ .name = name,
+ .mode = VERIFY_OCTAL_PERMISSIONS(0444)
+ },
+ .show = ni_event_show,
+ .store = NULL,
+ };
+
+ return 0;
+}
+
+static int ni_pmu_init_attr_groups(struct ni_pmu *ni_pmu)
+{
+ int idx, ev_idx, ev_num, ret, ev_num_tmp;
+ struct ni_node *node;
+ struct ni_event_desc **descs;
+ struct attribute **eattrs;
+ struct ni_event_attr *ni_eattrs;
+ struct device *dev;
+ struct attribute_group *eattr_group;
+ const struct attribute_group **attr_groups;
+ const struct attribute_group *ni_attr_groups_template[4];
+
+ dev = ni_pmu->dev;
+
+ eattr_group = devm_kzalloc(dev, sizeof(*eattr_group), GFP_KERNEL);
+
+ ev_num = 0;
+ for (idx = 0; idx < ni_pmu->ev_src_num; idx++) {
+ node = &ni_pmu->ev_src_nodes[idx];
+
+ ev_num += ni_ev_desc_array_size(node->type, NULL);
+ }
+
+ ev_num++;
+
+ eattrs = devm_kmalloc(dev, sizeof(eattrs[0]) * (ev_num + 1), GFP_KERNEL);
+ if (!eattrs)
+ return -ENOMEM;
+
+ ni_eattrs = devm_kzalloc(dev, sizeof(ni_eattrs[0]) * ev_num, GFP_KERNEL);
+ if (!ni_eattrs)
+ return -ENOMEM;
+
+ ev_num = 0;
+ ret = ni_event_attr_init(dev, &ni_eattrs[ev_num++], NULL, NULL);
+ if (ret)
+ return ret;
+
+ for (idx = 0; idx < ni_pmu->ev_src_num; idx++) {
+ node = &ni_pmu->ev_src_nodes[idx];
+
+ ev_num_tmp = ni_ev_desc_array_size(node->type, &descs);
+ for (ev_idx = 0; ev_idx < ev_num_tmp; ev_idx++) {
+ struct ni_event_desc *desc;
+
+ desc = descs[ev_idx];
+
+ ret = ni_event_attr_init(dev, &ni_eattrs[ev_num++], node, desc);
+ if (ret)
+ return ret;
+ }
+ }
+
+ for (idx = 0; idx < ev_num; idx++)
+ eattrs[idx] = &ni_eattrs[idx].attr.attr;
+
+ eattrs[idx] = NULL;
+
+ eattr_group->name = "events";
+ eattr_group->attrs = eattrs;
+
+ ni_attr_groups_template[0] = eattr_group;
+ ni_attr_groups_template[1] = &ni_format_attrs_group;
+ ni_attr_groups_template[2] = &ni_addition_attrs_group;
+ ni_attr_groups_template[3] = NULL;
+
+ attr_groups = devm_kmemdup(dev,
+ ni_attr_groups_template,
+ sizeof(ni_attr_groups_template),
+ GFP_KERNEL);
+ if (!attr_groups)
+ return -ENOMEM;
+
+ ni_pmu->pmu.attr_groups = attr_groups;
+
+ return 0;
+}
+
+static int ni_discovery(struct global_ni *ni)
+{
+ u32 vd_idx, pd_idx, cd_idx, nd_idx, num_idx = 0;
+ void __iomem *vd, *pd, *cd, *nd, **cd_arrays;
+ int num;
+ struct ni_pmu *ni_pmu;
+ struct ni_node node;
+ void __iomem *pbase;
+ struct device *dev = ni->dev;
+
+ pbase = ni->base;
+
+ cd_arrays = devm_kmalloc(dev, ni->cd_num * sizeof(typeof(cd)), GFP_KERNEL);
+
+ /* Step1: Get all clock domains. */
+ for (vd_idx = 0; vd_idx < ni_child_number(ni->base); vd_idx++) {
+ vd = ni_child_pointer(pbase, ni->base, vd_idx);
+
+ for (pd_idx = 0; pd_idx < ni_child_number(vd); pd_idx++) {
+ pd = ni_child_pointer(pbase, vd, pd_idx);
+
+ dev_dbg(dev, "The %dth power domain has %d clock domain",
+ pd_idx,
+ ni_child_number(pd));
+
+ for (cd_idx = 0; cd_idx < ni_child_number(pd); cd_idx++) {
+ cd_arrays[num_idx++] =
+ ni_child_pointer(pbase, pd, cd_idx);
+ }
+ }
+ }
+
+ /* Step2: Traverse all clock domains. */
+ for (cd_idx = 0; cd_idx < ni->cd_num; cd_idx++) {
+ cd = cd_arrays[cd_idx];
+
+ num = ni_child_number(cd);
+ dev_dbg(dev, "The %dth clock domain has %d child nodes:", cd_idx, num);
+
+ /* Omit pmu node */
+ ni_pmu = devm_kzalloc(dev, struct_size(ni_pmu, ev_src_nodes, num - 1),
+ GFP_KERNEL);
+ ni_pmu->ev_src_num = num - 1;
+
+ if (!ni_pmu)
+ return -ENOMEM;
+
+ num_idx = 0;
+ for (nd_idx = 0; nd_idx < num; nd_idx++) {
+ nd = ni_child_pointer(pbase, cd, nd_idx);
+
+ node.base = nd;
+ node.node_type = ni_node_node_type(nd);
+
+ if (unlikely(ni_node_type(nd) == NI_PMU))
+ ni_pmu->pmu_node = node;
+ else
+ ni_pmu->ev_src_nodes[num_idx++] = node;
+ dev_dbg(dev, " name: %s id: %d", ni_node_name[node.type], node.id);
+ }
+
+ ni_pmu->dev = dev;
+ ni_pmu->ni = ni;
+ ni->ni_pmus[cd_idx] = ni_pmu;
+ }
+
+ devm_kfree(dev, cd_arrays);
+
+ return 0;
+}
+
+static int ni_pmu_probe(struct platform_device *pdev)
+{
+ int ret, cd_num, idx, irq_num, irq_idx;
+ void __iomem *periphbase;
+ struct global_ni *ni;
+ struct device *dev = &pdev->dev;
+ char *name;
+ static int id;
+ struct ni_pmu *ni_pmu;
+
+ BUILD_BUG_ON(sizeof(struct ni_hw_perf_event) >
+ offsetof(struct hw_perf_event, target));
+#define NI_PMU_REG_MAP_SIZE 0xE08
+ BUILD_BUG_ON(sizeof(struct ni_pmu_reg_map) != NI_PMU_REG_MAP_SIZE);
+
+ periphbase = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(periphbase)) {
+ dev_err_probe(dev, PTR_ERR(periphbase), "Couldn't get ioremap\n");
+ return PTR_ERR(periphbase);
+ }
+
+ cd_num = ni_child_number_total(periphbase, periphbase, NI_CD);
+
+ /* Each clock domain contains one PMU. So cd_num == pmu_num. */
+ ni = devm_kzalloc(dev,
+ struct_size(ni, ni_pmus, cd_num),
+ GFP_KERNEL);
+ if (!ni)
+ return -ENOMEM;
+
+ ni->cd_num = cd_num;
+ ni->base = periphbase;
+ ni->dev = dev;
+ ni->on_cpu = raw_smp_processor_id();
+ platform_set_drvdata(pdev, ni);
+
+ ret = ni_discovery(ni);
+ if (ret) {
+ dev_err(dev, "%s: discovery error.", __func__);
+ return ret;
+ }
+
+ irq_num = platform_irq_count(pdev);
+ /* Support that one NI with one irq or one clock domain with one irq. */
+ if (irq_num < 0 || (irq_num != 1 && irq_num != ni->cd_num)) {
+ dev_err(dev, "Error in irq number: %d.", irq_num);
+ return -EINVAL;
+ }
+
+ if (irq_num != cd_num) {
+ dev_warn(dev, "Only one IRQ found for all PMU.");
+ ret = ni_pmu_irq_setup(ni->ni_pmus[0], 0);
+ if (ret)
+ return ret;
+ }
+
+ ni->irq_num = irq_num;
+
+ for (idx = 0, irq_idx = 0; idx < ni->pmu_num; idx++) {
+ ni_pmu = ni->ni_pmus[idx];
+ ret = ni_pmu_init_attr_groups(ni_pmu);
+ if (ret)
+ return ret;
+
+ if (irq_num == cd_num) {
+ ret = ni_pmu_irq_setup(ni_pmu, irq_idx++);
+ if (ret)
+ return ret;
+ }
+
+ ni_pmu_reset(ni_pmu);
+
+ ni_pmu->pmu = (struct pmu) {
+ .module = THIS_MODULE,
+ .task_ctx_nr = perf_invalid_context,
+ .pmu_enable = ni_pmu_enable,
+ .pmu_disable = ni_pmu_disable,
+ .event_init = ni_pmu_event_init,
+ .add = ni_pmu_event_add,
+ .del = ni_pmu_event_del,
+ .start = ni_pmu_event_start,
+ .stop = ni_pmu_event_stop,
+ .read = ni_pmu_event_read,
+ .attr_groups = ni_pmu->pmu.attr_groups,
+ .capabilities = PERF_PMU_CAP_NO_EXCLUDE,
+ };
+
+ of_property_read_u32(pdev->dev.of_node, "pccs-id", &id);
+
+ if (cd_num > 1)
+ name = devm_kasprintf(dev, GFP_KERNEL, "ni_pmu_%d_%d", id++, idx);
+ else
+ name = devm_kasprintf(dev, GFP_KERNEL, "ni_pmu_%d", id++);
+
+ ret = perf_pmu_register(&ni_pmu->pmu, name, -1);
+ if (ret) {
+ dev_err(dev, "Error %d_%d registering PMU", id - 1, idx);
+ return ret;
+ }
+ }
+
+ ret = cpuhp_state_add_instance_nocalls(ni_hp_state,
+ &ni->node);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ni_pmu_remove(struct platform_device *pdev)
+{
+ struct global_ni *ni = platform_get_drvdata(pdev);
+ int idx;
+
+ for (idx = 0; idx < ni->pmu_num; idx++)
+ perf_pmu_unregister(&ni->ni_pmus[idx]->pmu);
+
+ cpuhp_remove_multi_state(ni_hp_state);
+ return 0;
+}
+
+static const struct of_device_id ni_pmu_of_match[] = {
+ { .compatible = "hx,c2000-arm-ni" },
+ {},
+};
+
+static struct platform_driver ni_pmu_driver = {
+ .driver = {
+ .name = "ni-pmu",
+ .of_match_table = ni_pmu_of_match,
+ },
+ .remove = ni_pmu_remove,
+ .probe = ni_pmu_probe,
+};
+
+static int __init ni_pmu_init(void)
+{
+ int ret;
+
+ ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN,
+ "perf/arm/ni:online",
+ NULL,
+ ni_pmu_offline_cpu);
+ if (ret < 0)
+ return ret;
+
+ ni_hp_state = ret;
+
+ ret = platform_driver_register(&ni_pmu_driver);
+
+ if (ret)
+ cpuhp_remove_multi_state(ni_hp_state);
+
+ return ret;
+}
+
+static void __exit ni_pmu_exit(void)
+{
+ platform_driver_unregister(&ni_pmu_driver);
+}
+
+module_init(ni_pmu_init);
+module_exit(ni_pmu_exit);
+
+MODULE_AUTHOR("Jialong Yang <[email protected]>");
+MODULE_DESCRIPTION("PMU driver for ARM NI-700 Performance Monitors Unit");
+MODULE_LICENSE("GPL");
--
2.25.1



2024-01-31 07:59:26

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v2] perf/hx_arm_ni: Support uncore ARM NI-700 PMU

On 31/01/2024 08:08, JiaLong.Yang wrote:
> This code is based on uncore PMUs arm_smmuv3_pmu and arm-cmn.
> One ni-700 can have many clock domains. Each of them has only one PMU.
> Here one PMU corresponds to one 'struct ni_pmu' instance.
> PMU name will be ni_pmu_N_M, which N means different NI-700s and M means
> different PMU in one NI-700. If only one NI-700 found in NI-700, name will
> be ni_pmu_N.
> Node interface event name will be xxni_N_eventname, such as
> asni_0_rdreq_any. There are many kinds of type of nodes in one clock
> domain. Also means that there are many kinds of that in one PMU. So we
> distinguish them by xxni string. Besides, maybe there are many nodes
> have same type. So we have number N in event name.
> By ni_pmu_0_0/asni_0_rdreq_any/, we can pinpoint accurate bus traffic.
> Example1: perf stat -a -e ni_pmu_0_0/asni_0_rdreq_any/,ni_pmu_0_0/cycles/
> EXample2: perf stat -a -e ni_pmu_0_0/asni,id=0,event=0x0/
>
> Signed-off-by: JiaLong.Yang <[email protected]>
> ---
> v1 --> v2:
> 1. Submit MAINTANER Documentation/ files seperately.

SEPARATE PATCHES, not patchsets. You have now checkpatch warnings
because of this...

> 2. Delete some useless info printing.
> 3. Change print from pr_xxx to dev_xxx.
> 4. Fix more than 75 length log info.
> 5. Fix dts attribute pccs-id.
> 6. Fix generic name according to DT specification.
> 7. Some indentation.
> 8. Del of_match_ptr macro.
>
> drivers/perf/Kconfig | 11 +
> drivers/perf/Makefile | 1 +
> drivers/perf/hx_arm_ni.c | 1284 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 1296 insertions(+)
> create mode 100644 drivers/perf/hx_arm_ni.c
>
> diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig
> index ec6e0d9194a1..95ef8b13730f 100644
> --- a/drivers/perf/Kconfig
> +++ b/drivers/perf/Kconfig
> @@ -241,4 +241,15 @@ config CXL_PMU
>
> If unsure say 'm'.
>
> +config HX_ARM_NI_PMU
> + tristate "HX ARM NI-700 PMU"
> + depends on PPC_HX_C2000 && 64BIT

1. There is no PPC_HX_C2000.
2. Nothing justified dependency on 64bit. Drop or explain. Your previous
message did not provide real rationale.
3. Your indentation here is entirely mismatched. Read the coding style.

> + default y
> + help
> + Support for NI-700(Network-on-chip Interconnect) PMUs, which
> + provide monitoring of transactions passing through between
> + CMN and other buses or periapherals.
> +
> +source "drivers/perf/hisilicon/Kconfig"
> +
> endmenu
> diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile
> index a06338e3401c..ec8b9c08577d 100644
> --- a/drivers/perf/Makefile
> +++ b/drivers/perf/Makefile
> @@ -27,3 +27,4 @@ obj-$(CONFIG_DWC_PCIE_PMU) += dwc_pcie_pmu.o
> obj-$(CONFIG_ARM_CORESIGHT_PMU_ARCH_SYSTEM_PMU) += arm_cspmu/
> obj-$(CONFIG_MESON_DDR_PMU) += amlogic/
> obj-$(CONFIG_CXL_PMU) += cxl_pmu.o
> +obj-$(CONFIG_HX_ARM_NI_PMU) += hx_arm_ni.o
> diff --git a/drivers/perf/hx_arm_ni.c b/drivers/perf/hx_arm_ni.c
> new file mode 100644
> index 000000000000..619e3b789dda
> --- /dev/null
> +++ b/drivers/perf/hx_arm_ni.c
> @@ -0,0 +1,1284 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * HX ARM-NI-700 uncore PMU support
> + *
> + * This code is based on uncore PMUs arm_smmuv3_pmu and arm-cmn.
> + *
> + * One ni-700 can have many clock domains. Each of them has only one PMU.
> + * Here one PMU corresponds to one 'struct ni_pmu' instance.
> + *
> + * PMU name will be ni_pmu_N_M, which N means different NI-700s and M means
> + * different PMU in one NI-700. If only one NI-700 found in NI-700, name
> + * will be ni_pmu_N.
> + *
> + * Node interface event name will be xxni_N_eventname, such as
> + * asni_0_rdreq_any. There are many kinds of type of nodes in one clock
> + * domain. Also means that there are many kinds of that in one PMU. So we
> + * distinguish them by xxni string. Besides, maybe there are many nodes
> + * have same type. So we have number N in event name.
> + * By ni_pmu_0_0/asni_0_rdreq_any/, we can pinpoint accurate bus traffic.
> + *
> + * Example1: perf stat -a -e ni_pmu_0_0/asni_0_rdreq_any/,ni_pmu_0_0/cycles/
> + * Example2: perf stat -a -e ni_pmu_0_0/asni,id=0,event=0x0/
> + *
> + * TODO: Secure or non-secure attribute in all event omitted now.
> + *
> + */
> +
> +#define dev_fmt(fmt) "ni-700 pmu: " fmt
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/cpuhotplug.h>
> +#include <linux/cpumask.h>
> +#include <linux/device.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/kernel.h>
> +#include <linux/msi.h>
> +#include <linux/of.h>
> +#include <linux/perf_event.h>
> +#include <linux/platform_device.h>
> +#include <linux/smp.h>
> +#include <linux/sysfs.h>
> +#include <linux/types.h>
> +#include <linux/build_bug.h>
> +
> +/* number of counters in one ni pmu */
> +#define NI_PMU_COUNTER_NUM 8
> +
> +/* node type values */
> +enum ni_node_type {
> + NI_BASE = 0x0,
> + NI_VD,
> + NI_PD,
> + NI_CD,
> + NI_ASNI = 0x4,
> + NI_AMNI,
> + NI_PMU,
> + NI_HSNI,
> + NI_HMNI,
> + NI_PMNI = 0x9,
> +};
> +
> +/* event format */
> +/**

That's not kerneldoc.

You must test your code with W=1 build, sparse and smatch.

> + * config:
> + * 0-5 31 32-47 48-63
> + * event cycles node_type node_id
> + *
> + */
> +#define NI_EVENT_FORMAT_EVENT GENMASK_ULL(5, 0)
> +#define NI_EVENT_FORMAT_CYCLES (1ULL << 31)
> +#define NI_EVENT_FORMAT_NODETYPE GENMASK_ULL(32 + NI_PMNI, 32)
> +#define NI_EVENT_FORMAT_ASNI BIT(32 + NI_ASNI)
> +#define NI_EVENT_FORMAT_AMNI BIT(32 + NI_AMNI)
> +#define NI_EVENT_FORMAT_HSNI BIT(32 + NI_HSNI)
> +#define NI_EVENT_FORMAT_HMNI BIT(32 + NI_HMNI)
> +#define NI_EVENT_FORMAT_PMNI BIT(32 + NI_PMNI)
> +#define NI_EVENT_FORMAT_NODEID GENMASK_ULL(63, 48)
> +

..

> +
> +static ssize_t ni_event_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct ni_event_attr *eattr;
> +
> + eattr = to_ni_event_attr(attr);
> +
> + if (eattr->ev_desc)
> + return sysfs_emit(buf,
> + "%s,id=0x%x,event=0x%llx\n",
> + ni_node_name[eattr->node->type],
> + eattr->node->id,
> + eattr->ev_desc->eventid);
> +
> + return sysfs_emit(buf, "cycles\n");
> +}
> +
> +struct ni_format_attr {
> + struct device_attribute attr;
> + u64 field;
> +};

Declarations go to the top of the file.

> +

..

> +
> +static bool is_event_supported(u64 eventid, enum ni_node_type type)
> +{
> + int num;
> + int idx;
> + struct ni_event_desc **descs;
> +
> + num = ni_ev_desc_array_size(type, &descs);
> +
> + for (idx = 0; idx < num; idx++)
> + if (eventid == descs[idx]->eventid)
> + break;
> +
> + return idx == num ? false : true;
> +}
> +
> +static enum ni_node_type ni_event_config_nodetype(u64 config)
> +{
> + u64 nodetype = _ni_event_config_nodetype(config);
> + unsigned long lo = __ffs(nodetype), hi = __fls(nodetype);
> +
> + if (!nodetype || lo != hi)
> + return 0;
> +
> + return lo;
> +

Redundant blank line. Clean it up from the code.

> +}
> +

..

> +
> +static irqreturn_t ni_pmu_handle_irq(int irq_num, void *data)
> +{
> + struct ni_pmu *ni_pmu = data;
> + int idx, ret = IRQ_NONE;
> +
> + if (ni_pmu->ni->irq_num != 1)
> + return _ni_pmu_handle_irq(ni_pmu);
> +
> + for (idx = 0; idx < ni_pmu->ni->pmu_num; idx++)
> + ret |= _ni_pmu_handle_irq(ni_pmu->ni->ni_pmus[idx]);
> +
> + return ret;
> +}
> +
> +static int ni_hp_state;

Drop. No file-scope variables. And for 100% no file scope variables
hidden in the middle of something else.

> +static int ni_pmu_offline_cpu(unsigned int cpu, struct hlist_node *node)
> +{
> + struct global_ni *ni;
> + unsigned int target;
> + int idx;
> +
> +
> + ni = hlist_entry_safe(node, struct global_ni, node);
> + if (cpu != ni->on_cpu)
> + return 0;
> +
> +
> + target = cpumask_any_but(cpu_online_mask, cpu);
> + if (target >= nr_cpu_ids)
> + return 0;
> +
> +
> + for (idx = 0; idx < ni->pmu_num; idx++) {
> + perf_pmu_migrate_context(&ni->ni_pmus[idx]->pmu, cpu, target);
> +#ifndef CONFIG_PPC_HX_C2000

Drop, it does not exist.

> + WARN_ON(irq_set_affinity(ni->ni_pmus[idx]->irq, cpumask_of(target)));
> +#endif
> + }
> +
> + ni->on_cpu = target;
> +
> + return 0;
> +}
> +
> +static u32 ni_child_number_total(void __iomem *periphbase,
> + void __iomem *from, enum ni_node_type type)
> +{
> + enum ni_node_type node_type;
> + int total, idx;
> + void __iomem *child_base;
> +
> + node_type = ni_node_type(from);
> +
> + if (node_type == type)
> + return 1;
> +
> + if (node_type >= NI_ASNI)
> + return 0;
> +
> + total = 0;
> + for (idx = 0; idx < ni_child_number(from); idx++) {
> + child_base = ni_child_pointer(periphbase, from, idx);
> + total += ni_child_number_total(periphbase, child_base, type);
> + }
> +
> + return total;
> +}
> +
> +static void ni_pmu_reset(struct ni_pmu *ni_pmu)
> +{
> + ni_pmu_disable(&ni_pmu->pmu);
> +
> +#define clear_reg(name) \
> + writel(readl(ni_pmu_offset(ni_pmu, name)), ni_pmu_offset(ni_pmu, name))
> +
> + clear_reg(pmcntenclr);
> + clear_reg(pmintenclr);
> + clear_reg(pmovsclr);
> +
> + writel_relaxed(NI_PMU_PMCR_RST_CYC_CNTR & NI_PMU_PMCR_RST_EV_CNTR,
> + ni_pmu_offset(ni_pmu, pmcr));
> +}
> +
> +static int ni_pmu_irq_setup(struct ni_pmu *ni_pmu, int irq_idx)
> +{
> + int err;
> + unsigned long flags = IRQF_NOBALANCING | IRQF_SHARED | IRQF_NO_THREAD;
> +
> + ni_pmu->irq = platform_get_irq(to_platform_device(ni_pmu->dev), irq_idx);
> + if (ni_pmu->irq < 0)
> + return ni_pmu->irq;
> +
> + err = devm_request_irq(ni_pmu->dev, ni_pmu->irq, ni_pmu_handle_irq,
> + flags, dev_name(ni_pmu->dev), ni_pmu);
> + if (err)
> + return err;
> +
> +#ifndef CONFIG_PPC_HX_C2000

Drop, it does not exist.

> + err = irq_set_affinity(ni_pmu->irq, cpumask_of(ni_pmu->ni->on_cpu));
> + if (err)
> + return err;
> +#endif
> +
> + return 0;
> +}

..

> +static int ni_discovery(struct global_ni *ni)
> +{
> + u32 vd_idx, pd_idx, cd_idx, nd_idx, num_idx = 0;
> + void __iomem *vd, *pd, *cd, *nd, **cd_arrays;
> + int num;
> + struct ni_pmu *ni_pmu;
> + struct ni_node node;
> + void __iomem *pbase;
> + struct device *dev = ni->dev;
> +
> + pbase = ni->base;
> +
> + cd_arrays = devm_kmalloc(dev, ni->cd_num * sizeof(typeof(cd)), GFP_KERNEL);
> +
> + /* Step1: Get all clock domains. */
> + for (vd_idx = 0; vd_idx < ni_child_number(ni->base); vd_idx++) {
> + vd = ni_child_pointer(pbase, ni->base, vd_idx);
> +
> + for (pd_idx = 0; pd_idx < ni_child_number(vd); pd_idx++) {
> + pd = ni_child_pointer(pbase, vd, pd_idx);
> +
> + dev_dbg(dev, "The %dth power domain has %d clock domain",
> + pd_idx,
> + ni_child_number(pd));
> +
> + for (cd_idx = 0; cd_idx < ni_child_number(pd); cd_idx++) {
> + cd_arrays[num_idx++] =
> + ni_child_pointer(pbase, pd, cd_idx);
> + }
> + }
> + }
> +
> + /* Step2: Traverse all clock domains. */
> + for (cd_idx = 0; cd_idx < ni->cd_num; cd_idx++) {
> + cd = cd_arrays[cd_idx];
> +
> + num = ni_child_number(cd);
> + dev_dbg(dev, "The %dth clock domain has %d child nodes:", cd_idx, num);
> +
> + /* Omit pmu node */
> + ni_pmu = devm_kzalloc(dev, struct_size(ni_pmu, ev_src_nodes, num - 1),
> + GFP_KERNEL);
> + ni_pmu->ev_src_num = num - 1;
> +
> + if (!ni_pmu)
> + return -ENOMEM;
> +
> + num_idx = 0;
> + for (nd_idx = 0; nd_idx < num; nd_idx++) {
> + nd = ni_child_pointer(pbase, cd, nd_idx);
> +
> + node.base = nd;
> + node.node_type = ni_node_node_type(nd);
> +
> + if (unlikely(ni_node_type(nd) == NI_PMU))
> + ni_pmu->pmu_node = node;
> + else
> + ni_pmu->ev_src_nodes[num_idx++] = node;
> + dev_dbg(dev, " name: %s id: %d", ni_node_name[node.type], node.id);
> + }
> +
> + ni_pmu->dev = dev;
> + ni_pmu->ni = ni;
> + ni->ni_pmus[cd_idx] = ni_pmu;
> + }
> +
> + devm_kfree(dev, cd_arrays);

Why? If it is not device-lifetime then allocate with usual way.

> +
> + return 0;
> +}
> +
> +static int ni_pmu_probe(struct platform_device *pdev)
> +{
> + int ret, cd_num, idx, irq_num, irq_idx;
> + void __iomem *periphbase;
> + struct global_ni *ni;
> + struct device *dev = &pdev->dev;
> + char *name;
> + static int id;
> + struct ni_pmu *ni_pmu;
> +
> + BUILD_BUG_ON(sizeof(struct ni_hw_perf_event) >
> + offsetof(struct hw_perf_event, target));
> +#define NI_PMU_REG_MAP_SIZE 0xE08
> + BUILD_BUG_ON(sizeof(struct ni_pmu_reg_map) != NI_PMU_REG_MAP_SIZE);
> +
> + periphbase = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(periphbase)) {
> + dev_err_probe(dev, PTR_ERR(periphbase), "Couldn't get ioremap\n");
> + return PTR_ERR(periphbase);

I wrote you the syntax last time:
return dev_err_probe

> + }
> +


..

> +
> +static const struct of_device_id ni_pmu_of_match[] = {
> + { .compatible = "hx,c2000-arm-ni" },

Don't send undocumented compatibles.

Best regards,
Krzysztof


2024-01-31 09:11:23

by Yang Jialong 杨佳龙

[permalink] [raw]
Subject: Re: [PATCH v2] perf/hx_arm_ni: Support uncore ARM NI-700 PMU



在 2024/1/31 15:59, Krzysztof Kozlowski 写道:
> On 31/01/2024 08:08, JiaLong.Yang wrote:
>> This code is based on uncore PMUs arm_smmuv3_pmu and arm-cmn.
>> One ni-700 can have many clock domains. Each of them has only one PMU.
>> Here one PMU corresponds to one 'struct ni_pmu' instance.
>> PMU name will be ni_pmu_N_M, which N means different NI-700s and M means
>> different PMU in one NI-700. If only one NI-700 found in NI-700, name will
>> be ni_pmu_N.
>> Node interface event name will be xxni_N_eventname, such as
>> asni_0_rdreq_any. There are many kinds of type of nodes in one clock
>> domain. Also means that there are many kinds of that in one PMU. So we
>> distinguish them by xxni string. Besides, maybe there are many nodes
>> have same type. So we have number N in event name.
>> By ni_pmu_0_0/asni_0_rdreq_any/, we can pinpoint accurate bus traffic.
>> Example1: perf stat -a -e ni_pmu_0_0/asni_0_rdreq_any/,ni_pmu_0_0/cycles/
>> EXample2: perf stat -a -e ni_pmu_0_0/asni,id=0,event=0x0/
>>
>> Signed-off-by: JiaLong.Yang <[email protected]>
>> ---
>> v1 --> v2:
>> 1. Submit MAINTANER Documentation/ files seperately.
>
> SEPARATE PATCHES, not patchsets. You have now checkpatch warnings
> because of this...

..OK. But the MAINTANER file changing should be given in which one
patches.
I will submit patch v3 after talking and your permission.

>
>> 2. Delete some useless info printing.
>> 3. Change print from pr_xxx to dev_xxx.
>> 4. Fix more than 75 length log info.
>> 5. Fix dts attribute pccs-id.
>> 6. Fix generic name according to DT specification.
>> 7. Some indentation.
>> 8. Del of_match_ptr macro.
>>
>> drivers/perf/Kconfig | 11 +
>> drivers/perf/Makefile | 1 +
>> drivers/perf/hx_arm_ni.c | 1284 ++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 1296 insertions(+)
>> create mode 100644 drivers/perf/hx_arm_ni.c
>>
>> diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig
>> index ec6e0d9194a1..95ef8b13730f 100644
>> --- a/drivers/perf/Kconfig
>> +++ b/drivers/perf/Kconfig
>> @@ -241,4 +241,15 @@ config CXL_PMU
>>
>> If unsure say 'm'.
>>
>> +config HX_ARM_NI_PMU
>> + tristate "HX ARM NI-700 PMU"
>> + depends on PPC_HX_C2000 && 64BIT
>
> 1. There is no PPC_HX_C2000.

I have been used to using this macro. However this macro is not existed
in mainline.
I will replace it with ARM64. And del involved C code if OK.

64bit:
__ffs(unsigned long) and __fls(unsigned long) will be wrong in 32bit. I
pass a u64 argument.
struct ni_hw_perf_event will be big than limit.
BUILD_BUG_ON(sizeof(struct ni_hw_perf_event) > offsetof(struct
hw_perf_event, target));

> 2. Nothing justified dependency on 64bit. Drop or explain. Your previous
> message did not provide real rationale.

If ARM64, then drop.

> 3. Your indentation here is entirely mismatched. Read the coding style.
>

OK.

>> + default y
>> + help
>> + Support for NI-700(Network-on-chip Interconnect) PMUs, which
>> + provide monitoring of transactions passing through between
>> + CMN and other buses or periapherals.
>> +
>> +source "drivers/perf/hisilicon/Kconfig"
>> +
>> endmenu
>> diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile
>> index a06338e3401c..ec8b9c08577d 100644
>> --- a/drivers/perf/Makefile
>> +++ b/drivers/perf/Makefile
>> @@ -27,3 +27,4 @@ obj-$(CONFIG_DWC_PCIE_PMU) += dwc_pcie_pmu.o
>> obj-$(CONFIG_ARM_CORESIGHT_PMU_ARCH_SYSTEM_PMU) += arm_cspmu/
>> obj-$(CONFIG_MESON_DDR_PMU) += amlogic/
>> obj-$(CONFIG_CXL_PMU) += cxl_pmu.o
>> +obj-$(CONFIG_HX_ARM_NI_PMU) += hx_arm_ni.o
>> diff --git a/drivers/perf/hx_arm_ni.c b/drivers/perf/hx_arm_ni.c
>> new file mode 100644
>> index 000000000000..619e3b789dda
>> --- /dev/null
>> +++ b/drivers/perf/hx_arm_ni.c
>> @@ -0,0 +1,1284 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * HX ARM-NI-700 uncore PMU support
>> + *
>> + * This code is based on uncore PMUs arm_smmuv3_pmu and arm-cmn.
>> + *
>> + * One ni-700 can have many clock domains. Each of them has only one PMU.
>> + * Here one PMU corresponds to one 'struct ni_pmu' instance.
>> + *
>> + * PMU name will be ni_pmu_N_M, which N means different NI-700s and M means
>> + * different PMU in one NI-700. If only one NI-700 found in NI-700, name
>> + * will be ni_pmu_N.
>> + *
>> + * Node interface event name will be xxni_N_eventname, such as
>> + * asni_0_rdreq_any. There are many kinds of type of nodes in one clock
>> + * domain. Also means that there are many kinds of that in one PMU. So we
>> + * distinguish them by xxni string. Besides, maybe there are many nodes
>> + * have same type. So we have number N in event name.
>> + * By ni_pmu_0_0/asni_0_rdreq_any/, we can pinpoint accurate bus traffic.
>> + *
>> + * Example1: perf stat -a -e ni_pmu_0_0/asni_0_rdreq_any/,ni_pmu_0_0/cycles/
>> + * Example2: perf stat -a -e ni_pmu_0_0/asni,id=0,event=0x0/
>> + *
>> + * TODO: Secure or non-secure attribute in all event omitted now.
>> + *
>> + */
>> +
>> +#define dev_fmt(fmt) "ni-700 pmu: " fmt
>> +
>> +#include <linux/bitfield.h>
>> +#include <linux/bitops.h>
>> +#include <linux/cpuhotplug.h>
>> +#include <linux/cpumask.h>
>> +#include <linux/device.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/irq.h>
>> +#include <linux/kernel.h>
>> +#include <linux/msi.h>
>> +#include <linux/of.h>
>> +#include <linux/perf_event.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/smp.h>
>> +#include <linux/sysfs.h>
>> +#include <linux/types.h>
>> +#include <linux/build_bug.h>
>> +
>> +/* number of counters in one ni pmu */
>> +#define NI_PMU_COUNTER_NUM 8
>> +
>> +/* node type values */
>> +enum ni_node_type {
>> + NI_BASE = 0x0,
>> + NI_VD,
>> + NI_PD,
>> + NI_CD,
>> + NI_ASNI = 0x4,
>> + NI_AMNI,
>> + NI_PMU,
>> + NI_HSNI,
>> + NI_HMNI,
>> + NI_PMNI = 0x9,
>> +};
>> +
>> +/* event format */
>> +/**
>
> That's not kerneldoc.
>
> You must test your code with W=1 build, sparse and smatch.

Changed. Pass W=1.

>
>> + * config:
>> + * 0-5 31 32-47 48-63
>> + * event cycles node_type node_id
>> + *
>> + */
>> +#define NI_EVENT_FORMAT_EVENT GENMASK_ULL(5, 0)
>> +#define NI_EVENT_FORMAT_CYCLES (1ULL << 31)
>> +#define NI_EVENT_FORMAT_NODETYPE GENMASK_ULL(32 + NI_PMNI, 32)
>> +#define NI_EVENT_FORMAT_ASNI BIT(32 + NI_ASNI)
>> +#define NI_EVENT_FORMAT_AMNI BIT(32 + NI_AMNI)
>> +#define NI_EVENT_FORMAT_HSNI BIT(32 + NI_HSNI)
>> +#define NI_EVENT_FORMAT_HMNI BIT(32 + NI_HMNI)
>> +#define NI_EVENT_FORMAT_PMNI BIT(32 + NI_PMNI)
>> +#define NI_EVENT_FORMAT_NODEID GENMASK_ULL(63, 48)
>> +
>
> ...
>
>> +
>> +static ssize_t ni_event_show(struct device *dev,
>> + struct device_attribute *attr, char *buf)
>> +{
>> + struct ni_event_attr *eattr;
>> +
>> + eattr = to_ni_event_attr(attr);
>> +
>> + if (eattr->ev_desc)
>> + return sysfs_emit(buf,
>> + "%s,id=0x%x,event=0x%llx\n",
>> + ni_node_name[eattr->node->type],
>> + eattr->node->id,
>> + eattr->ev_desc->eventid);
>> +
>> + return sysfs_emit(buf, "cycles\n");
>> +}
>> +
>> +struct ni_format_attr {
>> + struct device_attribute attr;
>> + u64 field;
>> +};
>
> Declarations go to the top of the file.

Go.

>
>> +
>
> ...
>
>> +
>> +static bool is_event_supported(u64 eventid, enum ni_node_type type)
>> +{
>> + int num;
>> + int idx;
>> + struct ni_event_desc **descs;
>> +
>> + num = ni_ev_desc_array_size(type, &descs);
>> +
>> + for (idx = 0; idx < num; idx++)
>> + if (eventid == descs[idx]->eventid)
>> + break;
>> +
>> + return idx == num ? false : true;
>> +}
>> +
>> +static enum ni_node_type ni_event_config_nodetype(u64 config)
>> +{
>> + u64 nodetype = _ni_event_config_nodetype(config);
>> + unsigned long lo = __ffs(nodetype), hi = __fls(nodetype);
>> +
>> + if (!nodetype || lo != hi)
>> + return 0;
>> +
>> + return lo;
>> +
>
> Redundant blank line. Clean it up from the code.

done.

>
>> +}
>> +
>
> ...
>
>> +
>> +static irqreturn_t ni_pmu_handle_irq(int irq_num, void *data)
>> +{
>> + struct ni_pmu *ni_pmu = data;
>> + int idx, ret = IRQ_NONE;
>> +
>> + if (ni_pmu->ni->irq_num != 1)
>> + return _ni_pmu_handle_irq(ni_pmu);
>> +
>> + for (idx = 0; idx < ni_pmu->ni->pmu_num; idx++)
>> + ret |= _ni_pmu_handle_irq(ni_pmu->ni->ni_pmus[idx]);
>> +
>> + return ret;
>> +}
>> +
>> +static int ni_hp_state;
>
> Drop. No file-scope variables. And for 100% no file scope variables
> hidden in the middle of something else.

I will place it in the top.
Remind me that one struct global_ni should have one hp_state.
Now:
struct global_ni{
int hp_state; // new add.
};

>
>> +static int ni_pmu_offline_cpu(unsigned int cpu, struct hlist_node *node)
>> +{
>> + struct global_ni *ni;
>> + unsigned int target;
>> + int idx;
>> +
>> +
>> + ni = hlist_entry_safe(node, struct global_ni, node);
>> + if (cpu != ni->on_cpu)
>> + return 0;
>> +
>> +
>> + target = cpumask_any_but(cpu_online_mask, cpu);
>> + if (target >= nr_cpu_ids)
>> + return 0;
>> +
>> +
>> + for (idx = 0; idx < ni->pmu_num; idx++) {
>> + perf_pmu_migrate_context(&ni->ni_pmus[idx]->pmu, cpu, target);
>> +#ifndef CONFIG_PPC_HX_C2000
>
> Drop, it does not exist.

Drop.

>
>> + WARN_ON(irq_set_affinity(ni->ni_pmus[idx]->irq, cpumask_of(target)));
>> +#endif
>> + }
>> +
>> + ni->on_cpu = target;
>> +
>> + return 0;
>> +}
>> +
>> +static u32 ni_child_number_total(void __iomem *periphbase,
>> + void __iomem *from, enum ni_node_type type)
>> +{
>> + enum ni_node_type node_type;
>> + int total, idx;
>> + void __iomem *child_base;
>> +
>> + node_type = ni_node_type(from);
>> +
>> + if (node_type == type)
>> + return 1;
>> +
>> + if (node_type >= NI_ASNI)
>> + return 0;
>> +
>> + total = 0;
>> + for (idx = 0; idx < ni_child_number(from); idx++) {
>> + child_base = ni_child_pointer(periphbase, from, idx);
>> + total += ni_child_number_total(periphbase, child_base, type);
>> + }
>> +
>> + return total;
>> +}
>> +
>> +static void ni_pmu_reset(struct ni_pmu *ni_pmu)
>> +{
>> + ni_pmu_disable(&ni_pmu->pmu);
>> +
>> +#define clear_reg(name) \
>> + writel(readl(ni_pmu_offset(ni_pmu, name)), ni_pmu_offset(ni_pmu, name))
>> +
>> + clear_reg(pmcntenclr);
>> + clear_reg(pmintenclr);
>> + clear_reg(pmovsclr);
>> +
>> + writel_relaxed(NI_PMU_PMCR_RST_CYC_CNTR & NI_PMU_PMCR_RST_EV_CNTR,
>> + ni_pmu_offset(ni_pmu, pmcr));
>> +}
>> +
>> +static int ni_pmu_irq_setup(struct ni_pmu *ni_pmu, int irq_idx)
>> +{
>> + int err;
>> + unsigned long flags = IRQF_NOBALANCING | IRQF_SHARED | IRQF_NO_THREAD;
>> +
>> + ni_pmu->irq = platform_get_irq(to_platform_device(ni_pmu->dev), irq_idx);
>> + if (ni_pmu->irq < 0)
>> + return ni_pmu->irq;
>> +
>> + err = devm_request_irq(ni_pmu->dev, ni_pmu->irq, ni_pmu_handle_irq,
>> + flags, dev_name(ni_pmu->dev), ni_pmu);
>> + if (err)
>> + return err;
>> +
>> +#ifndef CONFIG_PPC_HX_C2000
>
> Drop, it does not exist.

Done.

>
>> + err = irq_set_affinity(ni_pmu->irq, cpumask_of(ni_pmu->ni->on_cpu));
>> + if (err)
>> + return err;
>> +#endif
>> +
>> + return 0;
>> +}
>
> ...
>
>> +static int ni_discovery(struct global_ni *ni)
>> +{
>> + u32 vd_idx, pd_idx, cd_idx, nd_idx, num_idx = 0;
>> + void __iomem *vd, *pd, *cd, *nd, **cd_arrays;
>> + int num;
>> + struct ni_pmu *ni_pmu;
>> + struct ni_node node;
>> + void __iomem *pbase;
>> + struct device *dev = ni->dev;
>> +
>> + pbase = ni->base;
>> +
>> + cd_arrays = devm_kmalloc(dev, ni->cd_num * sizeof(typeof(cd)), GFP_KERNEL);
>> +
>> + /* Step1: Get all clock domains. */
>> + for (vd_idx = 0; vd_idx < ni_child_number(ni->base); vd_idx++) {
>> + vd = ni_child_pointer(pbase, ni->base, vd_idx);
>> +
>> + for (pd_idx = 0; pd_idx < ni_child_number(vd); pd_idx++) {
>> + pd = ni_child_pointer(pbase, vd, pd_idx);
>> +
>> + dev_dbg(dev, "The %dth power domain has %d clock domain",
>> + pd_idx,
>> + ni_child_number(pd));
>> +
>> + for (cd_idx = 0; cd_idx < ni_child_number(pd); cd_idx++) {
>> + cd_arrays[num_idx++] =
>> + ni_child_pointer(pbase, pd, cd_idx);
>> + }
>> + }
>> + }
>> +
>> + /* Step2: Traverse all clock domains. */
>> + for (cd_idx = 0; cd_idx < ni->cd_num; cd_idx++) {
>> + cd = cd_arrays[cd_idx];
>> +
>> + num = ni_child_number(cd);
>> + dev_dbg(dev, "The %dth clock domain has %d child nodes:", cd_idx, num);
>> +
>> + /* Omit pmu node */
>> + ni_pmu = devm_kzalloc(dev, struct_size(ni_pmu, ev_src_nodes, num - 1),
>> + GFP_KERNEL);
>> + ni_pmu->ev_src_num = num - 1;
>> +
>> + if (!ni_pmu)
>> + return -ENOMEM;
>> +
>> + num_idx = 0;
>> + for (nd_idx = 0; nd_idx < num; nd_idx++) {
>> + nd = ni_child_pointer(pbase, cd, nd_idx);
>> +
>> + node.base = nd;
>> + node.node_type = ni_node_node_type(nd);
>> +
>> + if (unlikely(ni_node_type(nd) == NI_PMU))
>> + ni_pmu->pmu_node = node;
>> + else
>> + ni_pmu->ev_src_nodes[num_idx++] = node;
>> + dev_dbg(dev, " name: %s id: %d", ni_node_name[node.type], node.id);
>> + }
>> +
>> + ni_pmu->dev = dev;
>> + ni_pmu->ni = ni;
>> + ni->ni_pmus[cd_idx] = ni_pmu;
>> + }
>> +
>> + devm_kfree(dev, cd_arrays);
>
> Why? If it is not device-lifetime then allocate with usual way.
>

No device-lifetime.
Will allocate in stack.

>> +
>> + return 0;
>> +}
>> +
>> +static int ni_pmu_probe(struct platform_device *pdev)
>> +{
>> + int ret, cd_num, idx, irq_num, irq_idx;
>> + void __iomem *periphbase;
>> + struct global_ni *ni;
>> + struct device *dev = &pdev->dev;
>> + char *name;
>> + static int id;
>> + struct ni_pmu *ni_pmu;
>> +
>> + BUILD_BUG_ON(sizeof(struct ni_hw_perf_event) >
>> + offsetof(struct hw_perf_event, target));
>> +#define NI_PMU_REG_MAP_SIZE 0xE08
>> + BUILD_BUG_ON(sizeof(struct ni_pmu_reg_map) != NI_PMU_REG_MAP_SIZE);
>> +
>> + periphbase = devm_platform_ioremap_resource(pdev, 0);
>> + if (IS_ERR(periphbase)) {
>> + dev_err_probe(dev, PTR_ERR(periphbase), "Couldn't get ioremap\n");
>> + return PTR_ERR(periphbase);
>
> I wrote you the syntax last time:
> return dev_err_probe

done.

>
>> + }
>> +
>
>
> ...
>
>> +
>> +static const struct of_device_id ni_pmu_of_match[] = {
>> + { .compatible = "hx,c2000-arm-ni" },
>
> Don't send undocumented compatibles.

OK. Means I should send doc and code in one patch thread with more than
one patch?

>
> Best regards,
> Krzysztof
>
>

2024-01-31 09:38:53

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v2] perf/hx_arm_ni: Support uncore ARM NI-700 PMU

On 31/01/2024 10:07, Yang Jialong 杨佳龙 wrote:
>
>
> 在 2024/1/31 15:59, Krzysztof Kozlowski 写道:
>> On 31/01/2024 08:08, JiaLong.Yang wrote:
>>> This code is based on uncore PMUs arm_smmuv3_pmu and arm-cmn.
>>> One ni-700 can have many clock domains. Each of them has only one PMU.
>>> Here one PMU corresponds to one 'struct ni_pmu' instance.
>>> PMU name will be ni_pmu_N_M, which N means different NI-700s and M means
>>> different PMU in one NI-700. If only one NI-700 found in NI-700, name will
>>> be ni_pmu_N.
>>> Node interface event name will be xxni_N_eventname, such as
>>> asni_0_rdreq_any. There are many kinds of type of nodes in one clock
>>> domain. Also means that there are many kinds of that in one PMU. So we
>>> distinguish them by xxni string. Besides, maybe there are many nodes
>>> have same type. So we have number N in event name.
>>> By ni_pmu_0_0/asni_0_rdreq_any/, we can pinpoint accurate bus traffic.
>>> Example1: perf stat -a -e ni_pmu_0_0/asni_0_rdreq_any/,ni_pmu_0_0/cycles/
>>> EXample2: perf stat -a -e ni_pmu_0_0/asni,id=0,event=0x0/
>>>
>>> Signed-off-by: JiaLong.Yang <[email protected]>
>>> ---
>>> v1 --> v2:
>>> 1. Submit MAINTANER Documentation/ files seperately.
>>
>> SEPARATE PATCHES, not patchsets. You have now checkpatch warnings
>> because of this...
>
> ...OK. But the MAINTANER file changing should be given in which one
> patches.
> I will submit patch v3 after talking and your permission.
>
>>
>>> 2. Delete some useless info printing.
>>> 3. Change print from pr_xxx to dev_xxx.
>>> 4. Fix more than 75 length log info.
>>> 5. Fix dts attribute pccs-id.
>>> 6. Fix generic name according to DT specification.
>>> 7. Some indentation.
>>> 8. Del of_match_ptr macro.
>>>
>>> drivers/perf/Kconfig | 11 +
>>> drivers/perf/Makefile | 1 +
>>> drivers/perf/hx_arm_ni.c | 1284 ++++++++++++++++++++++++++++++++++++++
>>> 3 files changed, 1296 insertions(+)
>>> create mode 100644 drivers/perf/hx_arm_ni.c
>>>
>>> diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig
>>> index ec6e0d9194a1..95ef8b13730f 100644
>>> --- a/drivers/perf/Kconfig
>>> +++ b/drivers/perf/Kconfig
>>> @@ -241,4 +241,15 @@ config CXL_PMU
>>>
>>> If unsure say 'm'.
>>>
>>> +config HX_ARM_NI_PMU
>>> + tristate "HX ARM NI-700 PMU"
>>> + depends on PPC_HX_C2000 && 64BIT
>>
>> 1. There is no PPC_HX_C2000.
>
> I have been used to using this macro. However this macro is not existed
> in mainline.
> I will replace it with ARM64. And del involved C code if OK.
>
> 64bit:
> __ffs(unsigned long) and __fls(unsigned long) will be wrong in 32bit. I
> pass a u64 argument.

One thing is where the code is supposed to run, second thing is compile
testing.

Why do you use __ffs, not __ffs64 which takes u64 if you really want
only 64bit argument? unsigned long != u64, so your code is not
architecture independent. You claim you wrote it on purpose as
non-architecture-independent, but then I claim it's a bug. We are
supposed to write code which is portable, as much as possible, assuming
it does not affect readability.


> struct ni_hw_perf_event will be big than limit.
> BUILD_BUG_ON(sizeof(struct ni_hw_perf_event) > offsetof(struct
> hw_perf_event, target));

And why do you need to use any of such code? Please open one of hundreds
of other drivers which work correctly on 32 and 64-bit platforms.

>
>> 2. Nothing justified dependency on 64bit. Drop or explain. Your previous
>> message did not provide real rationale.
>
> If ARM64, then drop.

..

..

>>> + /* Step2: Traverse all clock domains. */
>>> + for (cd_idx = 0; cd_idx < ni->cd_num; cd_idx++) {
>>> + cd = cd_arrays[cd_idx];
>>> +
>>> + num = ni_child_number(cd);
>>> + dev_dbg(dev, "The %dth clock domain has %d child nodes:", cd_idx, num);
>>> +
>>> + /* Omit pmu node */
>>> + ni_pmu = devm_kzalloc(dev, struct_size(ni_pmu, ev_src_nodes, num - 1),
>>> + GFP_KERNEL);
>>> + ni_pmu->ev_src_num = num - 1;
>>> +
>>> + if (!ni_pmu)
>>> + return -ENOMEM;
>>> +
>>> + num_idx = 0;
>>> + for (nd_idx = 0; nd_idx < num; nd_idx++) {
>>> + nd = ni_child_pointer(pbase, cd, nd_idx);
>>> +
>>> + node.base = nd;
>>> + node.node_type = ni_node_node_type(nd);
>>> +
>>> + if (unlikely(ni_node_type(nd) == NI_PMU))
>>> + ni_pmu->pmu_node = node;
>>> + else
>>> + ni_pmu->ev_src_nodes[num_idx++] = node;
>>> + dev_dbg(dev, " name: %s id: %d", ni_node_name[node.type], node.id);
>>> + }
>>> +
>>> + ni_pmu->dev = dev;
>>> + ni_pmu->ni = ni;
>>> + ni->ni_pmus[cd_idx] = ni_pmu;
>>> + }
>>> +
>>> + devm_kfree(dev, cd_arrays);
>>
>> Why? If it is not device-lifetime then allocate with usual way.
>>
>
> No device-lifetime.
> Will allocate in stack.

I was thinking about kzalloc. But if array is small, stack could be as well.

..

>>
>>> +
>>> +static const struct of_device_id ni_pmu_of_match[] = {
>>> + { .compatible = "hx,c2000-arm-ni" },
>>
>> Don't send undocumented compatibles.
>
> OK. Means I should send doc and code in one patch thread with more than
> one patch?

Yes. Please open lore.kernel.org and look at any other submissions
involving bindings or other type of ABI documentation (like sysfs).

Best regards,
Krzysztof


2024-01-31 10:09:26

by Yang Jialong 杨佳龙

[permalink] [raw]
Subject: Re: [PATCH v2] perf/hx_arm_ni: Support uncore ARM NI-700 PMU



在 2024/1/31 17:38, Krzysztof Kozlowski 写道:
> On 31/01/2024 10:07, Yang Jialong 杨佳龙 wrote:
>>
>>
>> 在 2024/1/31 15:59, Krzysztof Kozlowski 写道:
>>> On 31/01/2024 08:08, JiaLong.Yang wrote:
>>>> This code is based on uncore PMUs arm_smmuv3_pmu and arm-cmn.
>>>> One ni-700 can have many clock domains. Each of them has only one PMU.
>>>> Here one PMU corresponds to one 'struct ni_pmu' instance.
>>>> PMU name will be ni_pmu_N_M, which N means different NI-700s and M means
>>>> different PMU in one NI-700. If only one NI-700 found in NI-700, name will
>>>> be ni_pmu_N.
>>>> Node interface event name will be xxni_N_eventname, such as
>>>> asni_0_rdreq_any. There are many kinds of type of nodes in one clock
>>>> domain. Also means that there are many kinds of that in one PMU. So we
>>>> distinguish them by xxni string. Besides, maybe there are many nodes
>>>> have same type. So we have number N in event name.
>>>> By ni_pmu_0_0/asni_0_rdreq_any/, we can pinpoint accurate bus traffic.
>>>> Example1: perf stat -a -e ni_pmu_0_0/asni_0_rdreq_any/,ni_pmu_0_0/cycles/
>>>> EXample2: perf stat -a -e ni_pmu_0_0/asni,id=0,event=0x0/
>>>>
>>>> Signed-off-by: JiaLong.Yang <[email protected]>
>>>> ---
>>>> v1 --> v2:
>>>> 1. Submit MAINTANER Documentation/ files seperately.
>>>
>>> SEPARATE PATCHES, not patchsets. You have now checkpatch warnings
>>> because of this...
>>
>> ...OK. But the MAINTANER file changing should be given in which one
>> patches.
>> I will submit patch v3 after talking and your permission.
>>
>>>
>>>> 2. Delete some useless info printing.
>>>> 3. Change print from pr_xxx to dev_xxx.
>>>> 4. Fix more than 75 length log info.
>>>> 5. Fix dts attribute pccs-id.
>>>> 6. Fix generic name according to DT specification.
>>>> 7. Some indentation.
>>>> 8. Del of_match_ptr macro.
>>>>
>>>> drivers/perf/Kconfig | 11 +
>>>> drivers/perf/Makefile | 1 +
>>>> drivers/perf/hx_arm_ni.c | 1284 ++++++++++++++++++++++++++++++++++++++
>>>> 3 files changed, 1296 insertions(+)
>>>> create mode 100644 drivers/perf/hx_arm_ni.c
>>>>
>>>> diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig
>>>> index ec6e0d9194a1..95ef8b13730f 100644
>>>> --- a/drivers/perf/Kconfig
>>>> +++ b/drivers/perf/Kconfig
>>>> @@ -241,4 +241,15 @@ config CXL_PMU
>>>>
>>>> If unsure say 'm'.
>>>>
>>>> +config HX_ARM_NI_PMU
>>>> + tristate "HX ARM NI-700 PMU"
>>>> + depends on PPC_HX_C2000 && 64BIT
>>>
>>> 1. There is no PPC_HX_C2000.
>>
>> I have been used to using this macro. However this macro is not existed
>> in mainline.
>> I will replace it with ARM64. And del involved C code if OK.
>>
>> 64bit:
>> __ffs(unsigned long) and __fls(unsigned long) will be wrong in 32bit. I
>> pass a u64 argument.
>
> One thing is where the code is supposed to run, second thing is compile
> testing.
>

Now run on my company product, a 64bit PowerPC...
But I think it's general for 64bit systems.

> Why do you use __ffs, not __ffs64 which takes u64 if you really want
> only 64bit argument? unsigned long != u64, so your code is not
> architecture independent. You claim you wrote it on purpose as
> non-architecture-independent, but then I claim it's a bug. We are
> supposed to write code which is portable, as much as possible, assuming
> it does not affect readability.
>

I write code in v5.18, there are __ffs64() and fls64(). Asymmetric.
There are some difference in return val between __ffs() and ffs64().
__ffs(0) and ffs64(0) will give different value.

And I'm sure code run in 64bit. So I choose to use __ffs and __fls.

Maybe it could be compatbile with 32bit. But I don't have a environment
to test this.
>
>> struct ni_hw_perf_event will be big than limit.
>> BUILD_BUG_ON(sizeof(struct ni_hw_perf_event) > offsetof(struct
>> hw_perf_event, target));
>
> And why do you need to use any of such code? Please open one of hundreds
> of other drivers which work correctly on 32 and 64-bit platforms.
>

Code for 64bit.
This code is to avoid struct ni_hw_perf_event is too big than struct
hw_perf_event::target.
I learn it from arm-cmn.c.
ni_hw_perf_event will replace hw_perf_event.
I will put some useful information in it with less space and good field
names.
But I can't exceed a limit.

>>
>>> 2. Nothing justified dependency on 64bit. Drop or explain. Your previous
>>> message did not provide real rationale.
>>
>> If ARM64, then drop.
>
> ...
>
> ...
>
>>>> + /* Step2: Traverse all clock domains. */
>>>> + for (cd_idx = 0; cd_idx < ni->cd_num; cd_idx++) {
>>>> + cd = cd_arrays[cd_idx];
>>>> +
>>>> + num = ni_child_number(cd);
>>>> + dev_dbg(dev, "The %dth clock domain has %d child nodes:", cd_idx, num);
>>>> +
>>>> + /* Omit pmu node */
>>>> + ni_pmu = devm_kzalloc(dev, struct_size(ni_pmu, ev_src_nodes, num - 1),
>>>> + GFP_KERNEL);
>>>> + ni_pmu->ev_src_num = num - 1;
>>>> +
>>>> + if (!ni_pmu)
>>>> + return -ENOMEM;
>>>> +
>>>> + num_idx = 0;
>>>> + for (nd_idx = 0; nd_idx < num; nd_idx++) {
>>>> + nd = ni_child_pointer(pbase, cd, nd_idx);
>>>> +
>>>> + node.base = nd;
>>>> + node.node_type = ni_node_node_type(nd);
>>>> +
>>>> + if (unlikely(ni_node_type(nd) == NI_PMU))
>>>> + ni_pmu->pmu_node = node;
>>>> + else
>>>> + ni_pmu->ev_src_nodes[num_idx++] = node;
>>>> + dev_dbg(dev, " name: %s id: %d", ni_node_name[node.type], node.id);
>>>> + }
>>>> +
>>>> + ni_pmu->dev = dev;
>>>> + ni_pmu->ni = ni;
>>>> + ni->ni_pmus[cd_idx] = ni_pmu;
>>>> + }
>>>> +
>>>> + devm_kfree(dev, cd_arrays);
>>>
>>> Why? If it is not device-lifetime then allocate with usual way.
>>>
>>
>> No device-lifetime.
>> Will allocate in stack.
>
> I was thinking about kzalloc. But if array is small, stack could be as well.
>

If I have to return before devm_kfree because of wrong, I will have to use:

goto out;

out:
kfree();

But if I use devm_kzalloc, I will not be worried about that. Even if no
device-lifetime.
Isn't this a good way?

> ...
>
>>>
>>>> +
>>>> +static const struct of_device_id ni_pmu_of_match[] = {
>>>> + { .compatible = "hx,c2000-arm-ni" },
>>>
>>> Don't send undocumented compatibles.
>>
>> OK. Means I should send doc and code in one patch thread with more than
>> one patch?
>
> Yes. Please open lore.kernel.org and look at any other submissions
> involving bindings or other type of ABI documentation (like sysfs).

Get.

>
> Best regards,
> Krzysztof
>
>


2024-01-31 10:36:27

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v2] perf/hx_arm_ni: Support uncore ARM NI-700 PMU

On 31/01/2024 11:07, Yang Jialong 杨佳龙 wrote:
>
>
> 在 2024/1/31 17:38, Krzysztof Kozlowski 写道:
>> On 31/01/2024 10:07, Yang Jialong 杨佳龙 wrote:
>>>
>>>
>>> 在 2024/1/31 15:59, Krzysztof Kozlowski 写道:
>>>> On 31/01/2024 08:08, JiaLong.Yang wrote:
>>>>> This code is based on uncore PMUs arm_smmuv3_pmu and arm-cmn.
>>>>> One ni-700 can have many clock domains. Each of them has only one PMU.
>>>>> Here one PMU corresponds to one 'struct ni_pmu' instance.
>>>>> PMU name will be ni_pmu_N_M, which N means different NI-700s and M means
>>>>> different PMU in one NI-700. If only one NI-700 found in NI-700, name will
>>>>> be ni_pmu_N.
>>>>> Node interface event name will be xxni_N_eventname, such as
>>>>> asni_0_rdreq_any. There are many kinds of type of nodes in one clock
>>>>> domain. Also means that there are many kinds of that in one PMU. So we
>>>>> distinguish them by xxni string. Besides, maybe there are many nodes
>>>>> have same type. So we have number N in event name.
>>>>> By ni_pmu_0_0/asni_0_rdreq_any/, we can pinpoint accurate bus traffic.
>>>>> Example1: perf stat -a -e ni_pmu_0_0/asni_0_rdreq_any/,ni_pmu_0_0/cycles/
>>>>> EXample2: perf stat -a -e ni_pmu_0_0/asni,id=0,event=0x0/
>>>>>
>>>>> Signed-off-by: JiaLong.Yang <[email protected]>
>>>>> ---
>>>>> v1 --> v2:
>>>>> 1. Submit MAINTANER Documentation/ files seperately.
>>>>
>>>> SEPARATE PATCHES, not patchsets. You have now checkpatch warnings
>>>> because of this...
>>>
>>> ...OK. But the MAINTANER file changing should be given in which one
>>> patches.
>>> I will submit patch v3 after talking and your permission.
>>>
>>>>
>>>>> 2. Delete some useless info printing.
>>>>> 3. Change print from pr_xxx to dev_xxx.
>>>>> 4. Fix more than 75 length log info.
>>>>> 5. Fix dts attribute pccs-id.
>>>>> 6. Fix generic name according to DT specification.
>>>>> 7. Some indentation.
>>>>> 8. Del of_match_ptr macro.
>>>>>
>>>>> drivers/perf/Kconfig | 11 +
>>>>> drivers/perf/Makefile | 1 +
>>>>> drivers/perf/hx_arm_ni.c | 1284 ++++++++++++++++++++++++++++++++++++++
>>>>> 3 files changed, 1296 insertions(+)
>>>>> create mode 100644 drivers/perf/hx_arm_ni.c
>>>>>
>>>>> diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig
>>>>> index ec6e0d9194a1..95ef8b13730f 100644
>>>>> --- a/drivers/perf/Kconfig
>>>>> +++ b/drivers/perf/Kconfig
>>>>> @@ -241,4 +241,15 @@ config CXL_PMU
>>>>>
>>>>> If unsure say 'm'.
>>>>>
>>>>> +config HX_ARM_NI_PMU
>>>>> + tristate "HX ARM NI-700 PMU"
>>>>> + depends on PPC_HX_C2000 && 64BIT
>>>>
>>>> 1. There is no PPC_HX_C2000.
>>>
>>> I have been used to using this macro. However this macro is not existed
>>> in mainline.
>>> I will replace it with ARM64. And del involved C code if OK.
>>>
>>> 64bit:
>>> __ffs(unsigned long) and __fls(unsigned long) will be wrong in 32bit. I
>>> pass a u64 argument.
>>
>> One thing is where the code is supposed to run, second thing is compile
>> testing.
>>
>
> Now run on my company product, a 64bit PowerPC...
> But I think it's general for 64bit systems.
>
>> Why do you use __ffs, not __ffs64 which takes u64 if you really want
>> only 64bit argument? unsigned long != u64, so your code is not
>> architecture independent. You claim you wrote it on purpose as
>> non-architecture-independent, but then I claim it's a bug. We are
>> supposed to write code which is portable, as much as possible, assuming
>> it does not affect readability.
>>
>
> I write code in v5.18, there are __ffs64() and fls64(). Asymmetric.

Sorry, that's a no go.

That's some very, very old kernel. Do not develop on old kernels, but on
mainline. I also suspect that by basing your work on old kernel, you
duplicate a lot of issues already fixed.

> There are some difference in return val between __ffs() and ffs64().
> __ffs(0) and ffs64(0) will give different value.

__ffs64 calls __ffs, so why would results be different?

Anyway, that's not really excuse.


>
> And I'm sure code run in 64bit. So I choose to use __ffs and __fls.
>
> Maybe it could be compatbile with 32bit. But I don't have a environment
> to test this.
>>
>>> struct ni_hw_perf_event will be big than limit.
>>> BUILD_BUG_ON(sizeof(struct ni_hw_perf_event) > offsetof(struct
>>> hw_perf_event, target));
>>
>> And why do you need to use any of such code? Please open one of hundreds
>> of other drivers which work correctly on 32 and 64-bit platforms.
>>
>
> Code for 64bit.
> This code is to avoid struct ni_hw_perf_event is too big than struct
> hw_perf_event::target.

1. Why would that matter? target is task_struct. It's size does not
matter. Maybe its offset matters, but not size.

2. So you claim that on 32-bit system the structure will be bigger than
on 64-bit system?

> I learn it from arm-cmn.c.

Are you copying patterns because they are good patterns or just because
you decided to copy?

> ni_hw_perf_event will replace hw_perf_event.
> I will put some useful information in it with less space and good field
> names.
> But I can't exceed a limit.
>
>>>
>>>> 2. Nothing justified dependency on 64bit. Drop or explain. Your previous
>>>> message did not provide real rationale.
>>>
>>> If ARM64, then drop.
>>
>> ...
>>
>> ...
>>
>>>>> + /* Step2: Traverse all clock domains. */
>>>>> + for (cd_idx = 0; cd_idx < ni->cd_num; cd_idx++) {
>>>>> + cd = cd_arrays[cd_idx];
>>>>> +
>>>>> + num = ni_child_number(cd);
>>>>> + dev_dbg(dev, "The %dth clock domain has %d child nodes:", cd_idx, num);
>>>>> +
>>>>> + /* Omit pmu node */
>>>>> + ni_pmu = devm_kzalloc(dev, struct_size(ni_pmu, ev_src_nodes, num - 1),
>>>>> + GFP_KERNEL);
>>>>> + ni_pmu->ev_src_num = num - 1;
>>>>> +
>>>>> + if (!ni_pmu)
>>>>> + return -ENOMEM;
>>>>> +
>>>>> + num_idx = 0;
>>>>> + for (nd_idx = 0; nd_idx < num; nd_idx++) {
>>>>> + nd = ni_child_pointer(pbase, cd, nd_idx);
>>>>> +
>>>>> + node.base = nd;
>>>>> + node.node_type = ni_node_node_type(nd);
>>>>> +
>>>>> + if (unlikely(ni_node_type(nd) == NI_PMU))
>>>>> + ni_pmu->pmu_node = node;
>>>>> + else
>>>>> + ni_pmu->ev_src_nodes[num_idx++] = node;
>>>>> + dev_dbg(dev, " name: %s id: %d", ni_node_name[node.type], node.id);
>>>>> + }
>>>>> +
>>>>> + ni_pmu->dev = dev;
>>>>> + ni_pmu->ni = ni;
>>>>> + ni->ni_pmus[cd_idx] = ni_pmu;
>>>>> + }
>>>>> +
>>>>> + devm_kfree(dev, cd_arrays);
>>>>
>>>> Why? If it is not device-lifetime then allocate with usual way.
>>>>
>>>
>>> No device-lifetime.
>>> Will allocate in stack.
>>
>> I was thinking about kzalloc. But if array is small, stack could be as well.
>>
>
> If I have to return before devm_kfree because of wrong, I will have to use:
>
> goto out;
>
> out:
> kfree();
>
> But if I use devm_kzalloc, I will not be worried about that. Even if no

devm* is not for that purpose. devm is for device-managed allocations.
Device does not manage your allocation.

> device-lifetime.
> Isn't this a good way?

Then you want cleanup.h and use proper __free().

Best regards,
Krzysztof


2024-01-31 16:51:47

by Robin Murphy

[permalink] [raw]
Subject: Re: [PATCH v2] perf/hx_arm_ni: Support uncore ARM NI-700 PMU

On 31/01/2024 7:08 am, JiaLong.Yang wrote:
> This code is based on uncore PMUs arm_smmuv3_pmu and arm-cmn.
> One ni-700 can have many clock domains. Each of them has only one PMU.
> Here one PMU corresponds to one 'struct ni_pmu' instance.
> PMU name will be ni_pmu_N_M, which N means different NI-700s and M means
> different PMU in one NI-700. If only one NI-700 found in NI-700, name will
> be ni_pmu_N.
> Node interface event name will be xxni_N_eventname, such as
> asni_0_rdreq_any. There are many kinds of type of nodes in one clock
> domain. Also means that there are many kinds of that in one PMU. So we
> distinguish them by xxni string. Besides, maybe there are many nodes
> have same type. So we have number N in event name.
> By ni_pmu_0_0/asni_0_rdreq_any/, we can pinpoint accurate bus traffic.
> Example1: perf stat -a -e ni_pmu_0_0/asni_0_rdreq_any/,ni_pmu_0_0/cycles/
> EXample2: perf stat -a -e ni_pmu_0_0/asni,id=0,event=0x0/

Oh! I've had a driver for this thing sat around for ages waiting to find
someone with an interest in testing it. Given that from a quick skim of
this patch I'd also have several concerns with this implementation, may
I ask that you have a look at my branch and see if it works for you?

https://gitlab.arm.com/linux-arm/linux-rm/-/tree/ni-dev?ref_type=heads

In particular, after the pain of maintaining event aliases in arm-cmn
I'd really like to get away from doing that again and instead move over
to jevents this time (especially now that system PMU support is a bit
more developed there) - I just haven't yet got round to hooking up the
identifier and writing the JSON files, since it hasn't seemed like much
of a priority before I know whether the code even works.

Thanks,
Robin.

>
> Signed-off-by: JiaLong.Yang <[email protected]>
> ---
> v1 --> v2:
> 1. Submit MAINTANER Documentation/ files seperately.
> 2. Delete some useless info printing.
> 3. Change print from pr_xxx to dev_xxx.
> 4. Fix more than 75 length log info.
> 5. Fix dts attribute pccs-id.
> 6. Fix generic name according to DT specification.
> 7. Some indentation.
> 8. Del of_match_ptr macro.
>
> drivers/perf/Kconfig | 11 +
> drivers/perf/Makefile | 1 +
> drivers/perf/hx_arm_ni.c | 1284 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 1296 insertions(+)
> create mode 100644 drivers/perf/hx_arm_ni.c
>
> diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig
> index ec6e0d9194a1..95ef8b13730f 100644
> --- a/drivers/perf/Kconfig
> +++ b/drivers/perf/Kconfig
> @@ -241,4 +241,15 @@ config CXL_PMU
>
> If unsure say 'm'.
>
> +config HX_ARM_NI_PMU
> + tristate "HX ARM NI-700 PMU"
> + depends on PPC_HX_C2000 && 64BIT
> + default y
> + help
> + Support for NI-700(Network-on-chip Interconnect) PMUs, which
> + provide monitoring of transactions passing through between
> + CMN and other buses or periapherals.
> +
> +source "drivers/perf/hisilicon/Kconfig"
> +
> endmenu
> diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile
> index a06338e3401c..ec8b9c08577d 100644
> --- a/drivers/perf/Makefile
> +++ b/drivers/perf/Makefile
> @@ -27,3 +27,4 @@ obj-$(CONFIG_DWC_PCIE_PMU) += dwc_pcie_pmu.o
> obj-$(CONFIG_ARM_CORESIGHT_PMU_ARCH_SYSTEM_PMU) += arm_cspmu/
> obj-$(CONFIG_MESON_DDR_PMU) += amlogic/
> obj-$(CONFIG_CXL_PMU) += cxl_pmu.o
> +obj-$(CONFIG_HX_ARM_NI_PMU) += hx_arm_ni.o
> diff --git a/drivers/perf/hx_arm_ni.c b/drivers/perf/hx_arm_ni.c
> new file mode 100644
> index 000000000000..619e3b789dda
> --- /dev/null
> +++ b/drivers/perf/hx_arm_ni.c
> @@ -0,0 +1,1284 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * HX ARM-NI-700 uncore PMU support
> + *
> + * This code is based on uncore PMUs arm_smmuv3_pmu and arm-cmn.
> + *
> + * One ni-700 can have many clock domains. Each of them has only one PMU.
> + * Here one PMU corresponds to one 'struct ni_pmu' instance.
> + *
> + * PMU name will be ni_pmu_N_M, which N means different NI-700s and M means
> + * different PMU in one NI-700. If only one NI-700 found in NI-700, name
> + * will be ni_pmu_N.
> + *
> + * Node interface event name will be xxni_N_eventname, such as
> + * asni_0_rdreq_any. There are many kinds of type of nodes in one clock
> + * domain. Also means that there are many kinds of that in one PMU. So we
> + * distinguish them by xxni string. Besides, maybe there are many nodes
> + * have same type. So we have number N in event name.
> + * By ni_pmu_0_0/asni_0_rdreq_any/, we can pinpoint accurate bus traffic.
> + *
> + * Example1: perf stat -a -e ni_pmu_0_0/asni_0_rdreq_any/,ni_pmu_0_0/cycles/
> + * Example2: perf stat -a -e ni_pmu_0_0/asni,id=0,event=0x0/
> + *
> + * TODO: Secure or non-secure attribute in all event omitted now.
> + *
> + */
> +
> +#define dev_fmt(fmt) "ni-700 pmu: " fmt
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/cpuhotplug.h>
> +#include <linux/cpumask.h>
> +#include <linux/device.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/kernel.h>
> +#include <linux/msi.h>
> +#include <linux/of.h>
> +#include <linux/perf_event.h>
> +#include <linux/platform_device.h>
> +#include <linux/smp.h>
> +#include <linux/sysfs.h>
> +#include <linux/types.h>
> +#include <linux/build_bug.h>
> +
> +/* number of counters in one ni pmu */
> +#define NI_PMU_COUNTER_NUM 8
> +
> +/* node type values */
> +enum ni_node_type {
> + NI_BASE = 0x0,
> + NI_VD,
> + NI_PD,
> + NI_CD,
> + NI_ASNI = 0x4,
> + NI_AMNI,
> + NI_PMU,
> + NI_HSNI,
> + NI_HMNI,
> + NI_PMNI = 0x9,
> +};
> +
> +/* event format */
> +/**
> + * config:
> + * 0-5 31 32-47 48-63
> + * event cycles node_type node_id
> + *
> + */
> +#define NI_EVENT_FORMAT_EVENT GENMASK_ULL(5, 0)
> +#define NI_EVENT_FORMAT_CYCLES (1ULL << 31)
> +#define NI_EVENT_FORMAT_NODETYPE GENMASK_ULL(32 + NI_PMNI, 32)
> +#define NI_EVENT_FORMAT_ASNI BIT(32 + NI_ASNI)
> +#define NI_EVENT_FORMAT_AMNI BIT(32 + NI_AMNI)
> +#define NI_EVENT_FORMAT_HSNI BIT(32 + NI_HSNI)
> +#define NI_EVENT_FORMAT_HMNI BIT(32 + NI_HMNI)
> +#define NI_EVENT_FORMAT_PMNI BIT(32 + NI_PMNI)
> +#define NI_EVENT_FORMAT_NODEID GENMASK_ULL(63, 48)
> +
> +#define NI_EVENT_FORMAT_NODE_TYPE GENMASK_ULL(63, 32)
> +
> +#define ni_event_config_eventid(_config) FIELD_GET(NI_EVENT_FORMAT_EVENT, _config)
> +#define ni_event_config_cc(_config) FIELD_GET(NI_EVENT_FORMAT_CYCLES, _config)
> +#define _ni_event_config_nodetype(_config) FIELD_GET(NI_EVENT_FORMAT_NODETYPE, _config)
> +#define ni_event_config_nodeid(_config) FIELD_GET(NI_EVENT_FORMAT_NODEID, _config)
> +
> +#define NI_NODE_TYPE_MASK GENMASK(15, 0)
> +#define NI_NODE_ID_MASK GENMASK(31, 16)
> +
> +#define NI_PMU_PMCR_RST_CYC_CNTR BIT(2)
> +#define NI_PMU_PMCR_RST_EV_CNTR BIT(1)
> +#define NI_PMU_PMCR_ENABLE BIT(0)
> +
> +static const char *const ni_node_name[] = {
> + [NI_ASNI] = "asni",
> + [NI_AMNI] = "amni",
> + [NI_PMU] = "pmu",
> + [NI_HSNI] = "hsni",
> + [NI_HMNI] = "hmni",
> + [NI_PMNI] = "pmni",
> +};
> +
> +/* one instance for one node */
> +struct ni_node {
> + void __iomem *base;
> + union {
> + struct {
> + u32 type:16;
> + u32 id:16;
> + };
> + u32 node_type;
> + };
> +};
> +
> +/* xxx_reg_map only used to provide offset by using offsetof(). */
> +struct ni_node_reg_map {
> + union {
> + struct {
> + u32 type:16;
> + u32 id:16;
> + };
> + u32 node_type;
> + };
> +
> + union {
> + u32 child_num;
> + u32 node_info;
> + };
> +
> + union {
> + struct {
> + u32 secr_acc;
> + u32 pmusela;
> + u32 pmuselb;
> + };
> + DECLARE_FLEX_ARRAY(u32, child_offset);
> + };
> +};
> +
> +#define ni_node_offsetof(member) \
> + offsetof(struct ni_node_reg_map, member)
> +
> +#define ni_node_pmuevsel(node, config) \
> + do { \
> + writel(config, node->base + ni_node_offsetof(pmusela)); \
> + writel(config >> 32, node->base + ni_node_offsetof(pmuselb)); \
> + } while (0)
> +
> +#define ni_node_read(base, member, readx) \
> + readx((void __iomem *)base + ni_node_offsetof(member))
> +
> +#define ni_node_type(base) \
> + FIELD_GET(NI_NODE_TYPE_MASK, ni_node_read(base, node_type, readl_relaxed))
> +
> +#define ni_node_id(base) \
> + FIELD_GET(NI_NODE_ID_MASK, ni_node_read(base, node_type, readl_relaxed))
> +
> +#define ni_node_node_type(base) \
> + ni_node_read(base, node_type, readl_relaxed)
> +
> +#define ni_child_number(base) \
> + (ni_node_type(base) < NI_ASNI ? ni_node_read(base, child_num, readl_relaxed) : 0)
> +
> +#define ni_child_pointer(periphbase, base, idx) \
> + ((void __iomem *)periphbase + ni_node_read(base, child_offset[idx], readl_relaxed))
> +
> +struct ni_pmu;
> +struct ni_pmu_reg_map {
> + u32 node_type; /* offset: 0x000 */
> + u32 secr_acc; /* offset: 0x004 */
> + struct {
> + u32 counter;
> + u32 reserved;
> + } pmevcntr[8]; /* offset: 0x008 */
> + u8 reserved_1[0xF8 - 0x48]; /* offset: 0x048 */
> + u32 pmccntr_lower; /* offset: 0x0F8 */
> + u32 pmccntr_upper; /* offset: 0x0FC */
> + u8 reserved_2[0x400 - 0x100]; /* offset: 0x100 */
> + u32 pmevtyper[8]; /* offset: 0x400 */
> + u8 reserved_3[0x610 - 0x420]; /* offset: 0x420 */
> + u32 pmssr; /* offset: 0x610 */
> + u32 pmovssr; /* offset: 0x614 */
> + u32 pmccntsr_lower; /* offset: 0x618 */
> + u32 pmccntsr_upper; /* offset: 0x61C */
> + u32 pmevcntsr[8]; /* offset: 0x620 */
> + u8 reserved_4[0x6F0 - 0x640]; /* offset: 0x640 */
> + u32 pmsscr; /* offset: 0x6F0 */
> + u8 reserved_5[0xC00 - 0x6F4]; /* offset: 0x6F4 */
> + u32 pmcntenset; /* offset: 0xC00 */
> + u8 reserved_6[0xC20 - 0xC04]; /* offset: 0xC04 */
> + u32 pmcntenclr; /* offset: 0xC20 */
> + u8 reserved_7[0xC40 - 0xC24]; /* offset: 0xC24 */
> + u32 pmintenset; /* offset: 0xC40 */
> + u8 reserved_8[0xC60 - 0xC44]; /* offset: 0xC44 */
> + u32 pmintenclr; /* offset: 0xC60 */
> + u8 reserved_9[0xC80 - 0xC64]; /* offset: 0xC64 */
> + u32 pmovsclr; /* offset: 0xC80 */
> + u8 reserved_10[0xCC0 - 0xC84]; /* offset: 0xC84 */
> + u32 pmovsset; /* offset: 0xCC0 */
> + u8 reserved_11[0xD80 - 0xCC4]; /* offset: 0xCC4 */
> + u32 pmcccgr; /* offset: 0xD80 */
> + u8 reserved_12[0xE00 - 0xD84]; /* offset: 0xD84 */
> + u32 pmcfgr; /* offset: 0xE00 */
> + u32 pmcr; /* offset: 0xE04 */
> +};
> +
> +/* Not read or write registers directly. */
> +#define ni_pmu_offset(ni_pmu, member) \
> + ((void __iomem *)ni_pmu->pmu_node.base + offsetof(struct ni_pmu_reg_map, member))
> +
> +#define ni_pmu_interrupt_enable(ni_pmu, en_bit_mask) \
> + writel(en_bit_mask, ni_pmu_offset(ni_pmu, pmintenset))
> +
> +#define ni_pmu_interrupt_disable(ni_pmu, en_bit_mask) \
> + writel(en_bit_mask, ni_pmu_offset(ni_pmu, pmintenclr))
> +
> +#define ni_pmu_counter_enable(ni_pmu, en_bit_mask) \
> + writel(en_bit_mask, ni_pmu_offset(ni_pmu, pmcntenset))
> +
> +#define ni_pmu_counter_disable(ni_pmu, en_bit_mask) \
> + writel(en_bit_mask, ni_pmu_offset(ni_pmu, pmcntenclr))
> +
> +#define ni_pmu_pmevtyper_sel_node(ni_pmu, ev_typer, cnt_idx) \
> + writel(ev_typer, ni_pmu_offset(ni_pmu, pmevtyper[cnt_idx]))
> +
> +struct global_ni {
> + void __iomem *base;
> + struct hlist_node node;
> + struct device *dev;
> + union {
> + unsigned int pmu_num;
> + unsigned int cd_num;
> + };
> + unsigned int on_cpu;
> + int irq_num;
> + struct ni_pmu *ni_pmus[];
> +};
> +
> +
> +struct ni_pmu {
> + struct ni_node pmu_node;
> + struct perf_event *events[NI_PMU_COUNTER_NUM + 1];
> + struct pmu pmu;
> + struct device *dev;
> + unsigned int irq;
> + struct global_ni *ni;
> + int ev_src_num;
> + struct ni_node ev_src_nodes[];
> +};
> +
> +#define to_ni_pmu(_pmu) container_of(_pmu, struct ni_pmu, pmu)
> +
> +struct ni_hw_perf_event {
> + /* cycle event */
> + bool is_cc;
> + /* The event corresponds to idxth counter */
> + int idx;
> + /* Enable bit field in pmcntenset */
> +#define NI_PMU_CC_EN_BIT 31
> + u32 en_bit_mask;
> + /* value writen in counter */
> + u64 init_val;
> + /* If no cc event, config will be writen in pmusela/b */
> + u64 config;
> + /* The event corresponds to ni_pmu::ev_src_nodes[node_idx] */
> + int node_idx;
> + /* overwrite state in hw_perf_event */
> + int state;
> + /* value writen in pmevtyper */
> +#define NI_PMU_PMEVTYPER_NDTP_OFFSET 9
> + union {
> + struct {
> + u32 id: 9;
> + u32 type: 4;
> + } node;
> + u32 ev_typer;
> + };
> +};
> +
> +#define to_ni_hw(event) ((struct ni_hw_perf_event *)&event->hw)
> +
> +struct ni_event_desc {
> + u64 eventid;
> + const char *name;
> +};
> +
> +struct ni_event_attr {
> + struct device_attribute attr;
> + struct ni_event_desc *ev_desc;
> + struct ni_node *node;
> +};
> +
> +#define to_ni_event_attr(p) \
> + container_of(p, struct ni_event_attr, attr)
> +
> +#define NI_EVENT_DESC(_eventid, _name) \
> + (&((struct ni_event_desc[]) { \
> + { .name = __stringify(_name), \
> + .eventid = _eventid,} \
> + })[0])
> +
> +
> +static struct ni_event_desc *ni_asni_event_descs[] = {
> + NI_EVENT_DESC(0x00, rdreq_any),
> + NI_EVENT_DESC(0x01, rdreq_dev_arcache),
> + NI_EVENT_DESC(0x02, rdreq_rns),
> + NI_EVENT_DESC(0x03, rdreq_ro),
> + NI_EVENT_DESC(0x04, req_cache_clr),
> + NI_EVENT_DESC(0x05, rdreq_beat_any),
> + NI_EVENT_DESC(0x06, rdreq_handshake_rlast),
> + NI_EVENT_DESC(0x07, wtreq_any),
> + NI_EVENT_DESC(0x08, wtreq_dev),
> + NI_EVENT_DESC(0x09, wtreq_wns),
> + NI_EVENT_DESC(0x0a, wtreq_wlu),
> + NI_EVENT_DESC(0x0b, wtreq_wu),
> + NI_EVENT_DESC(0x0c, wtreq_atomic),
> + NI_EVENT_DESC(0x0d, wtreq_beat_any),
> + NI_EVENT_DESC(0x0e, rdreq_stall),
> + NI_EVENT_DESC(0x0f, rddata_stall),
> + NI_EVENT_DESC(0x10, wtreq_stall),
> + NI_EVENT_DESC(0x11, wtdata_stall),
> + NI_EVENT_DESC(0x12, wtresp_stall),
> + NI_EVENT_DESC(0x13, wtreq_cst),
> + NI_EVENT_DESC(0x14, wtchann_nopersist),
> + NI_EVENT_DESC(0x15, wtchann_persist),
> + NI_EVENT_DESC(0x16, rdreq_nzero_mem_ops),
> + NI_EVENT_DESC(0x17, wtreq_nzero_mem_ops),
> + NI_EVENT_DESC(0x20, req_stall_cc_ot_limit),
> + NI_EVENT_DESC(0x21, req_stall_cc_tspec_limit),
> + NI_EVENT_DESC(0x22, req_stall_arbit),
> + NI_EVENT_DESC(0x23, req_stall_rd_tracker),
> + NI_EVENT_DESC(0x24, req_stall_wt_tracker),
> + NI_EVENT_DESC(0x25, aw_stall_wdatafifo_full),
> + NI_EVENT_DESC(0x26, ar_stall_reorderbuf_full),
> + NI_EVENT_DESC(0x27, aw_cdas_stall),
> + NI_EVENT_DESC(0x28, ar_cdas_stall),
> + NI_EVENT_DESC(0x29, atomic_rd_stall),
> + NI_EVENT_DESC(0x2a, wtchann_wtreq_stall),
> + NI_EVENT_DESC(0x2b, rdchann_rdreq_stall),
> + NI_EVENT_DESC(0x2c, aw_stall_ot),
> + NI_EVENT_DESC(0x2d, ar_stall_ot),
> + NI_EVENT_DESC(0x2e, aw_stall_tspec),
> + NI_EVENT_DESC(0x2f, ar_stall_tspec),
> + NI_EVENT_DESC(0x30, lwmd_arbit_stall_wchann),
> + NI_EVENT_DESC(0x31, lwmd_arbit_stall_rchann),
> +};
> +
> +static struct ni_event_desc *ni_amni_event_descs[] = {
> + NI_EVENT_DESC(0x00, rdreq_any),
> + NI_EVENT_DESC(0x01, rdreq_dev_arcache),
> + NI_EVENT_DESC(0x02, rdreq_rns),
> + NI_EVENT_DESC(0x03, rdreq_ro),
> + NI_EVENT_DESC(0x04, req_cache_clr),
> + NI_EVENT_DESC(0x05, rdreq_beat_any),
> + NI_EVENT_DESC(0x06, rdreq_handshake_rlast),
> + NI_EVENT_DESC(0x07, wtreq_any),
> + NI_EVENT_DESC(0x08, wtreq_dev),
> + NI_EVENT_DESC(0x09, wtreq_wns),
> + NI_EVENT_DESC(0x0a, wtreq_wlu),
> + NI_EVENT_DESC(0x0b, wtreq_wu),
> + NI_EVENT_DESC(0x0c, wtreq_atomic),
> + NI_EVENT_DESC(0x0d, wtreq_beat_any),
> + NI_EVENT_DESC(0x0e, rdreq_stall),
> + NI_EVENT_DESC(0x0f, rddata_stall),
> + NI_EVENT_DESC(0x10, wtreq_stall),
> + NI_EVENT_DESC(0x11, wtdata_stall),
> + NI_EVENT_DESC(0x12, wtresp_stall),
> + NI_EVENT_DESC(0x13, wtreq_cst),
> + NI_EVENT_DESC(0x14, wtchann_nopersist),
> + NI_EVENT_DESC(0x15, wtchann_persist),
> + NI_EVENT_DESC(0x16, rdreq_nzero_mem_ops),
> + NI_EVENT_DESC(0x17, wtreq_nzero_mem_ops),
> + NI_EVENT_DESC(0x20, req_stall_rd_tracker),
> + NI_EVENT_DESC(0x21, req_stall_wt_tracker),
> + NI_EVENT_DESC(0x22, wtchann_b_resp),
> + NI_EVENT_DESC(0x23, rdchann_rd_resp),
> + NI_EVENT_DESC(0x24, lwmd_arbit_stall_wchann),
> + NI_EVENT_DESC(0x25, lwmd_arbit_stall_rchann),
> +};
> +
> +static struct ni_event_desc *ni_hsni_event_descs[] = {
> + NI_EVENT_DESC(0x00, rdreq_any),
> + NI_EVENT_DESC(0x01, rdreq_dev),
> + NI_EVENT_DESC(0x02, rdreq_noshare),
> + NI_EVENT_DESC(0x03, rdreq_share),
> + NI_EVENT_DESC(0x04, rdreq_share_nonormal),
> + NI_EVENT_DESC(0x05, rdreq_beat_any),
> + NI_EVENT_DESC(0x07, wtreq_any),
> + NI_EVENT_DESC(0x08, wtreq_dev),
> + NI_EVENT_DESC(0x09, wtreq_noshare),
> + NI_EVENT_DESC(0x0a, wtreq_all),
> + NI_EVENT_DESC(0x0b, wtreq_share),
> + NI_EVENT_DESC(0x0c, wtreq_share_nonormal),
> + NI_EVENT_DESC(0x0d, wtreq_beat_any),
> + NI_EVENT_DESC(0x0f, rddata_stall),
> + NI_EVENT_DESC(0x11, wtdata_stall),
> + NI_EVENT_DESC(0x20, req_stall_cc_ot_limit),
> + NI_EVENT_DESC(0x21, req_stall_cc_tspec_limit),
> + NI_EVENT_DESC(0x22, rdreq_stall_cc_ely_wtresp),
> + NI_EVENT_DESC(0x24, req_stall_nzero_wtcnt),
> + NI_EVENT_DESC(0x25, w_stall_wdatafifo_full),
> + NI_EVENT_DESC(0x2a, wtreq_stall_lack_gt),
> + NI_EVENT_DESC(0x2b, rdreq_stall_lack_gt),
> +};
> +
> +static struct ni_event_desc *ni_hmni_event_descs[] = {
> + NI_EVENT_DESC(0x00, rdreq_any),
> + NI_EVENT_DESC(0x01, rdreq_dev),
> + NI_EVENT_DESC(0x02, rdreq_noshare),
> + NI_EVENT_DESC(0x03, rdreq_share),
> + NI_EVENT_DESC(0x04, rdreq_share_nonormal),
> + NI_EVENT_DESC(0x05, rdreq_beat_any),
> + NI_EVENT_DESC(0x07, wtreq_any),
> + NI_EVENT_DESC(0x08, wtreq_dev),
> + NI_EVENT_DESC(0x09, wtreq_noshare),
> + NI_EVENT_DESC(0x0a, wtreq_all),
> + NI_EVENT_DESC(0x0b, wtreq_share),
> + NI_EVENT_DESC(0x0c, wtreq_share_nonormal),
> + NI_EVENT_DESC(0x0d, wtreq_beat_any),
> + NI_EVENT_DESC(0x0e, rd_addr_phase_stall),
> + NI_EVENT_DESC(0x0f, rd_data_phase_stall),
> + NI_EVENT_DESC(0x10, wt_addr_phase_stall),
> + NI_EVENT_DESC(0x11, wt_data_phase_stall),
> + NI_EVENT_DESC(0x22, wtresp_stall_lack_gt),
> + NI_EVENT_DESC(0x23, rdresp_stall_lack_gt),
> +};
> +
> +static struct ni_event_desc *ni_pmni_event_descs[] = {
> + NI_EVENT_DESC(0x00, rdreq_any),
> + NI_EVENT_DESC(0x01, rdreq_dev_arcache),
> + NI_EVENT_DESC(0x02, rdreq_noshared),
> + NI_EVENT_DESC(0x05, rd_prdata_any),
> + NI_EVENT_DESC(0x07, wtreq_any),
> + NI_EVENT_DESC(0x08, wtreq_dev),
> + NI_EVENT_DESC(0x09, wtreq_noshared),
> + NI_EVENT_DESC(0x0d, wtdata_beat_any),
> + NI_EVENT_DESC(0x0e, rdreq_stall),
> + NI_EVENT_DESC(0x0f, rddata_stall),
> + NI_EVENT_DESC(0x10, wtreq_stall),
> + NI_EVENT_DESC(0x11, wtdata_stall),
> + NI_EVENT_DESC(0x22, wtresp_stall_lack_gt),
> + NI_EVENT_DESC(0x23, rdresp_stall_lack_gt),
> +};
> +
> +static int ni_ev_desc_array_size(enum ni_node_type type,
> + struct ni_event_desc ***descs)
> +{
> + switch (type) {
> + case NI_ASNI:
> + if (descs)
> + *descs = ni_asni_event_descs;
> + return ARRAY_SIZE(ni_asni_event_descs);
> + case NI_AMNI:
> + if (descs)
> + *descs = ni_amni_event_descs;
> + return ARRAY_SIZE(ni_amni_event_descs);
> + case NI_HSNI:
> + if (descs)
> + *descs = ni_hsni_event_descs;
> + return ARRAY_SIZE(ni_hsni_event_descs);
> + case NI_HMNI:
> + if (descs)
> + *descs = ni_hmni_event_descs;
> + return ARRAY_SIZE(ni_hmni_event_descs);
> + case NI_PMNI:
> + if (descs)
> + *descs = ni_pmni_event_descs;
> + return ARRAY_SIZE(ni_pmni_event_descs);
> + default:
> + return 0;
> + }
> +}
> +
> +static ssize_t ni_event_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct ni_event_attr *eattr;
> +
> + eattr = to_ni_event_attr(attr);
> +
> + if (eattr->ev_desc)
> + return sysfs_emit(buf,
> + "%s,id=0x%x,event=0x%llx\n",
> + ni_node_name[eattr->node->type],
> + eattr->node->id,
> + eattr->ev_desc->eventid);
> +
> + return sysfs_emit(buf, "cycles\n");
> +}
> +
> +struct ni_format_attr {
> + struct device_attribute attr;
> + u64 field;
> +};
> +
> +static ssize_t ni_format_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct ni_format_attr *fmt = container_of(attr, struct ni_format_attr, attr);
> + int lo = __ffs(fmt->field), hi = __fls(fmt->field);
> +
> + if (lo == hi)
> + return sysfs_emit(buf, "config:%d\n", lo);
> +
> + return sysfs_emit(buf, "config:%d-%d\n", lo, hi);
> +}
> +
> +
> +#define NI_FORMAT_ATTR(_name, _fld) \
> + (&((struct ni_format_attr[]) {{ \
> + .attr = __ATTR(_name, 0444, ni_format_show, NULL), \
> + .field = _fld, \
> + }})[0].attr.attr)
> +
> +static struct attribute *ni_format_attrs[] = {
> + NI_FORMAT_ATTR(event, NI_EVENT_FORMAT_EVENT),
> + NI_FORMAT_ATTR(cycles, NI_EVENT_FORMAT_CYCLES),
> + NI_FORMAT_ATTR(asni, NI_EVENT_FORMAT_ASNI),
> + NI_FORMAT_ATTR(amni, NI_EVENT_FORMAT_AMNI),
> + NI_FORMAT_ATTR(hsni, NI_EVENT_FORMAT_HSNI),
> + NI_FORMAT_ATTR(hmni, NI_EVENT_FORMAT_HMNI),
> + NI_FORMAT_ATTR(pmni, NI_EVENT_FORMAT_PMNI),
> + NI_FORMAT_ATTR(id, NI_EVENT_FORMAT_NODEID),
> + NULL
> +};
> +
> +static const struct attribute_group ni_format_attrs_group = {
> + .name = "format",
> + .attrs = ni_format_attrs,
> +};
> +
> +static ssize_t ni_cpumask_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct ni_pmu *ni_pmu = to_ni_pmu(dev_get_drvdata(dev));
> +
> + return cpumap_print_to_pagebuf(true, buf, cpumask_of(ni_pmu->ni->on_cpu));
> +}
> +
> +static struct device_attribute ni_cpumask_attr =
> + __ATTR(cpumask, 0444, ni_cpumask_show, NULL);
> +
> +static struct attribute *ni_addition_attrs[] = {
> + &ni_cpumask_attr.attr,
> + NULL,
> +};
> +
> +static const struct attribute_group ni_addition_attrs_group = {
> + .attrs = ni_addition_attrs,
> +};
> +
> +static u64 ni_cntr_get_and_init_optionally(struct perf_event *event, bool init)
> +{
> + u64 old_val, new_val;
> + struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
> + struct ni_hw_perf_event *hwc = to_ni_hw(event);
> +
> + if (!hwc->is_cc)
> + old_val = readl(ni_pmu_offset(ni_pmu, pmevcntr[hwc->idx].counter));
> + else
> + old_val = readl(ni_pmu_offset(ni_pmu, pmccntr_lower))
> + | (((u64)readl(ni_pmu_offset(ni_pmu, pmccntr_upper))) << 32);
> +
> + if (!init)
> + return old_val;
> +
> + new_val = hwc->init_val;
> + if (!hwc->is_cc)
> + writel(new_val, ni_pmu_offset(ni_pmu, pmevcntr[hwc->idx].counter));
> + else {
> + writel(new_val, ni_pmu_offset(ni_pmu, pmccntr_lower));
> + writel(new_val >> 32, ni_pmu_offset(ni_pmu, pmccntr_upper));
> + }
> +
> + return old_val;
> +}
> +
> +static void ni_pmu_event_update(struct perf_event *event)
> +{
> + struct ni_hw_perf_event *hwc = to_ni_hw(event);
> + u64 delta, prev, now;
> +
> + do {
> + prev = local64_read(&event->hw.prev_count);
> + now = ni_cntr_get_and_init_optionally(event, false);
> + } while (local64_cmpxchg(&event->hw.prev_count, prev, now) != prev);
> +
> + delta = now - prev;
> +
> + if (!hwc->is_cc)
> + delta &= 0xFFFFFFFFULL;
> +
> + local64_add(delta, &event->count);
> +}
> +
> +static void ni_pmu_set_period(struct perf_event *event)
> +{
> + struct ni_hw_perf_event *hwc = to_ni_hw(event);
> +
> + ni_cntr_get_and_init_optionally(event, true);
> +
> + local64_set(&event->hw.prev_count, hwc->init_val);
> +}
> +
> +static void ni_pmu_enable(struct pmu *pmu)
> +{
> + struct ni_pmu *ni_pmu = to_ni_pmu(pmu);
> +
> + writel(NI_PMU_PMCR_ENABLE, ni_pmu_offset(ni_pmu, pmcr));
> +}
> +
> +static inline void ni_pmu_disable(struct pmu *pmu)
> +{
> + struct ni_pmu *ni_pmu = to_ni_pmu(pmu);
> +
> + writel(0, ni_pmu_offset(ni_pmu, pmcr));
> +}
> +
> +static int ni_pmu_find_ev_src(struct ni_pmu *ni_pmu, u32 node_type)
> +{
> + int idx;
> +
> + for (idx = 0; idx < ni_pmu->ev_src_num; idx++)
> + if (ni_pmu->ev_src_nodes[idx].node_type == node_type)
> + break;
> +
> + return idx;
> +}
> +
> +static bool is_event_supported(u64 eventid, enum ni_node_type type)
> +{
> + int num;
> + int idx;
> + struct ni_event_desc **descs;
> +
> + num = ni_ev_desc_array_size(type, &descs);
> +
> + for (idx = 0; idx < num; idx++)
> + if (eventid == descs[idx]->eventid)
> + break;
> +
> + return idx == num ? false : true;
> +}
> +
> +static enum ni_node_type ni_event_config_nodetype(u64 config)
> +{
> + u64 nodetype = _ni_event_config_nodetype(config);
> + unsigned long lo = __ffs(nodetype), hi = __fls(nodetype);
> +
> + if (!nodetype || lo != hi)
> + return 0;
> +
> + return lo;
> +
> +}
> +
> +static int ni_pmu_event_init(struct perf_event *event)
> +{
> + struct ni_hw_perf_event *hwc = to_ni_hw(event);
> + struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
> + u64 config;
> + enum ni_node_type nodetype;
> + u32 node_type;
> +
> + memset(hwc, 0, sizeof(*hwc));
> +
> + if (event->attr.type != event->pmu->type)
> + return -ENOENT;
> +
> + if (is_sampling_event(event))
> + return -EINVAL;
> +
> + event->cpu = ni_pmu->ni->on_cpu;
> +
> + config = event->attr.config;
> +
> + hwc->is_cc = ni_event_config_cc(config);
> +
> + if (hwc->is_cc)
> + return 0;
> +
> + nodetype = ni_event_config_nodetype(config);
> + if (!nodetype)
> + return -EINVAL;
> +
> + hwc->node.id = ni_event_config_nodeid(config);
> + hwc->node.type = nodetype;
> + hwc->config = ni_event_config_eventid(config);
> +
> + node_type = hwc->node.id << 16 | nodetype;
> + hwc->node_idx = ni_pmu_find_ev_src(ni_pmu, node_type);
> + if (hwc->node_idx == ni_pmu->ev_src_num)
> + return -EINVAL;
> +
> + if (!is_event_supported(hwc->config, nodetype))
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static void ni_pmu_event_start(struct perf_event *event, int flags)
> +{
> + struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
> + struct ni_hw_perf_event *hwc = to_ni_hw(event);
> +
> + hwc->state = 0;
> +
> + ni_pmu_set_period(event);
> +
> + if (!hwc->is_cc) {
> + ni_node_pmuevsel((&ni_pmu->ev_src_nodes[hwc->node_idx]), hwc->config);
> + ni_pmu_pmevtyper_sel_node(ni_pmu, hwc->ev_typer, hwc->idx);
> + }
> +
> + ni_pmu_counter_enable(ni_pmu, hwc->en_bit_mask);
> +}
> +
> +static void ni_pmu_event_stop(struct perf_event *event, int flags)
> +{
> + struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
> + struct ni_hw_perf_event *hwc = to_ni_hw(event);
> +
> + if (hwc->state & PERF_HES_STOPPED)
> + return;
> +
> + ni_pmu_counter_disable(ni_pmu, hwc->en_bit_mask);
> +
> + ni_pmu_event_update(event);
> +
> + hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE;
> +}
> +
> +static void ni_pmu_event_read(struct perf_event *event)
> +{
> + ni_pmu_event_update(event);
> +}
> +
> +static int ni_pmu_event_add(struct perf_event *event, int flags)
> +{
> + struct ni_hw_perf_event *hwc = to_ni_hw(event);
> + struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
> + int idx;
> +
> + idx = 0;
> + if (hwc->is_cc && ni_pmu->events[NI_PMU_COUNTER_NUM])
> + return -EAGAIN; /* The cycle counter is in use. */
> +
> + idx = 0;
> + if (hwc->is_cc)
> + idx = NI_PMU_COUNTER_NUM;
> + else
> + while ((idx < NI_PMU_COUNTER_NUM) && ni_pmu->events[idx])
> + idx++;
> +
> + if (!hwc->is_cc && idx == NI_PMU_COUNTER_NUM)
> + return -EAGAIN; /* All general counter is in use. */
> +
> + hwc->idx = idx;
> +
> + hwc->en_bit_mask = hwc->is_cc ? BIT(NI_PMU_CC_EN_BIT) : BIT(idx);
> + hwc->init_val = hwc->is_cc ? (0x1ULL << 63) : (0x1ULL << 31);
> + hwc->config = hwc->config << idx * 8; /* including is_cc */
> + hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE;
> +
> + local64_set(&event->hw.prev_count, 0);
> +
> + ni_pmu->events[idx] = event;
> +
> + ni_pmu_interrupt_enable(ni_pmu, hwc->en_bit_mask);
> +
> + if (flags & PERF_EF_START)
> + ni_pmu_event_start(event, flags);
> +
> + return 0;
> +}
> +
> +static void ni_pmu_event_del(struct perf_event *event, int flags)
> +{
> + struct ni_hw_perf_event *hwc = to_ni_hw(event);
> + struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
> +
> + ni_pmu_event_stop(event, flags);
> + ni_pmu_interrupt_disable(ni_pmu, hwc->en_bit_mask);
> + ni_pmu->events[hwc->idx] = NULL;
> +}
> +
> +static irqreturn_t _ni_pmu_handle_irq(struct ni_pmu *ni_pmu)
> +{
> + u64 ovsr;
> + int idx;
> + struct perf_event *event;
> + struct ni_hw_perf_event *hwc;
> +
> + ovsr = readl(ni_pmu_offset(ni_pmu, pmovsclr));
> + if (!ovsr)
> + return IRQ_NONE;
> +
> + writel(ovsr, ni_pmu_offset(ni_pmu, pmovsclr));
> +
> + for_each_set_bit(idx, (unsigned long *)&ovsr, 32) {
> + if (idx >= NI_PMU_COUNTER_NUM)
> + idx = NI_PMU_COUNTER_NUM;
> +
> + event = ni_pmu->events[idx];
> + if (WARN_ON_ONCE(!event))
> + continue;
> +
> + hwc = to_ni_hw(event);
> + ni_pmu_event_update(event);
> + ni_pmu_set_period(event);
> + if (idx == NI_PMU_COUNTER_NUM)
> + break;
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t ni_pmu_handle_irq(int irq_num, void *data)
> +{
> + struct ni_pmu *ni_pmu = data;
> + int idx, ret = IRQ_NONE;
> +
> + if (ni_pmu->ni->irq_num != 1)
> + return _ni_pmu_handle_irq(ni_pmu);
> +
> + for (idx = 0; idx < ni_pmu->ni->pmu_num; idx++)
> + ret |= _ni_pmu_handle_irq(ni_pmu->ni->ni_pmus[idx]);
> +
> + return ret;
> +}
> +
> +static int ni_hp_state;
> +static int ni_pmu_offline_cpu(unsigned int cpu, struct hlist_node *node)
> +{
> + struct global_ni *ni;
> + unsigned int target;
> + int idx;
> +
> +
> + ni = hlist_entry_safe(node, struct global_ni, node);
> + if (cpu != ni->on_cpu)
> + return 0;
> +
> +
> + target = cpumask_any_but(cpu_online_mask, cpu);
> + if (target >= nr_cpu_ids)
> + return 0;
> +
> +
> + for (idx = 0; idx < ni->pmu_num; idx++) {
> + perf_pmu_migrate_context(&ni->ni_pmus[idx]->pmu, cpu, target);
> +#ifndef CONFIG_PPC_HX_C2000
> + WARN_ON(irq_set_affinity(ni->ni_pmus[idx]->irq, cpumask_of(target)));
> +#endif
> + }
> +
> + ni->on_cpu = target;
> +
> + return 0;
> +}
> +
> +static u32 ni_child_number_total(void __iomem *periphbase,
> + void __iomem *from, enum ni_node_type type)
> +{
> + enum ni_node_type node_type;
> + int total, idx;
> + void __iomem *child_base;
> +
> + node_type = ni_node_type(from);
> +
> + if (node_type == type)
> + return 1;
> +
> + if (node_type >= NI_ASNI)
> + return 0;
> +
> + total = 0;
> + for (idx = 0; idx < ni_child_number(from); idx++) {
> + child_base = ni_child_pointer(periphbase, from, idx);
> + total += ni_child_number_total(periphbase, child_base, type);
> + }
> +
> + return total;
> +}
> +
> +static void ni_pmu_reset(struct ni_pmu *ni_pmu)
> +{
> + ni_pmu_disable(&ni_pmu->pmu);
> +
> +#define clear_reg(name) \
> + writel(readl(ni_pmu_offset(ni_pmu, name)), ni_pmu_offset(ni_pmu, name))
> +
> + clear_reg(pmcntenclr);
> + clear_reg(pmintenclr);
> + clear_reg(pmovsclr);
> +
> + writel_relaxed(NI_PMU_PMCR_RST_CYC_CNTR & NI_PMU_PMCR_RST_EV_CNTR,
> + ni_pmu_offset(ni_pmu, pmcr));
> +}
> +
> +static int ni_pmu_irq_setup(struct ni_pmu *ni_pmu, int irq_idx)
> +{
> + int err;
> + unsigned long flags = IRQF_NOBALANCING | IRQF_SHARED | IRQF_NO_THREAD;
> +
> + ni_pmu->irq = platform_get_irq(to_platform_device(ni_pmu->dev), irq_idx);
> + if (ni_pmu->irq < 0)
> + return ni_pmu->irq;
> +
> + err = devm_request_irq(ni_pmu->dev, ni_pmu->irq, ni_pmu_handle_irq,
> + flags, dev_name(ni_pmu->dev), ni_pmu);
> + if (err)
> + return err;
> +
> +#ifndef CONFIG_PPC_HX_C2000
> + err = irq_set_affinity(ni_pmu->irq, cpumask_of(ni_pmu->ni->on_cpu));
> + if (err)
> + return err;
> +#endif
> +
> + return 0;
> +}
> +
> +static int ni_event_attr_init(struct device *dev,
> + struct ni_event_attr *eattr,
> + struct ni_node *node,
> + struct ni_event_desc *desc)
> +{
> + struct attribute *attr;
> + const char *name;
> +
> + attr = &eattr->attr.attr;
> +
> + sysfs_attr_init(attr);
> +
> + eattr->ev_desc = desc;
> + eattr->node = node;
> +
> + if (desc && node)
> + name = devm_kasprintf(dev,
> + GFP_KERNEL,
> + "%s_%d_%s",
> + ni_node_name[node->type],
> + node->id,
> + desc->name);
> + else if (!desc && !node)
> + name = "cycles";
> + else {
> + WARN(1, "No such type attr. Discovery Error!");
> + return -EINVAL;
> + }
> +
> + if (!name)
> + return -ENOMEM;
> +
> + eattr->attr = (struct device_attribute){
> + .attr = {
> + .name = name,
> + .mode = VERIFY_OCTAL_PERMISSIONS(0444)
> + },
> + .show = ni_event_show,
> + .store = NULL,
> + };
> +
> + return 0;
> +}
> +
> +static int ni_pmu_init_attr_groups(struct ni_pmu *ni_pmu)
> +{
> + int idx, ev_idx, ev_num, ret, ev_num_tmp;
> + struct ni_node *node;
> + struct ni_event_desc **descs;
> + struct attribute **eattrs;
> + struct ni_event_attr *ni_eattrs;
> + struct device *dev;
> + struct attribute_group *eattr_group;
> + const struct attribute_group **attr_groups;
> + const struct attribute_group *ni_attr_groups_template[4];
> +
> + dev = ni_pmu->dev;
> +
> + eattr_group = devm_kzalloc(dev, sizeof(*eattr_group), GFP_KERNEL);
> +
> + ev_num = 0;
> + for (idx = 0; idx < ni_pmu->ev_src_num; idx++) {
> + node = &ni_pmu->ev_src_nodes[idx];
> +
> + ev_num += ni_ev_desc_array_size(node->type, NULL);
> + }
> +
> + ev_num++;
> +
> + eattrs = devm_kmalloc(dev, sizeof(eattrs[0]) * (ev_num + 1), GFP_KERNEL);
> + if (!eattrs)
> + return -ENOMEM;
> +
> + ni_eattrs = devm_kzalloc(dev, sizeof(ni_eattrs[0]) * ev_num, GFP_KERNEL);
> + if (!ni_eattrs)
> + return -ENOMEM;
> +
> + ev_num = 0;
> + ret = ni_event_attr_init(dev, &ni_eattrs[ev_num++], NULL, NULL);
> + if (ret)
> + return ret;
> +
> + for (idx = 0; idx < ni_pmu->ev_src_num; idx++) {
> + node = &ni_pmu->ev_src_nodes[idx];
> +
> + ev_num_tmp = ni_ev_desc_array_size(node->type, &descs);
> + for (ev_idx = 0; ev_idx < ev_num_tmp; ev_idx++) {
> + struct ni_event_desc *desc;
> +
> + desc = descs[ev_idx];
> +
> + ret = ni_event_attr_init(dev, &ni_eattrs[ev_num++], node, desc);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + for (idx = 0; idx < ev_num; idx++)
> + eattrs[idx] = &ni_eattrs[idx].attr.attr;
> +
> + eattrs[idx] = NULL;
> +
> + eattr_group->name = "events";
> + eattr_group->attrs = eattrs;
> +
> + ni_attr_groups_template[0] = eattr_group;
> + ni_attr_groups_template[1] = &ni_format_attrs_group;
> + ni_attr_groups_template[2] = &ni_addition_attrs_group;
> + ni_attr_groups_template[3] = NULL;
> +
> + attr_groups = devm_kmemdup(dev,
> + ni_attr_groups_template,
> + sizeof(ni_attr_groups_template),
> + GFP_KERNEL);
> + if (!attr_groups)
> + return -ENOMEM;
> +
> + ni_pmu->pmu.attr_groups = attr_groups;
> +
> + return 0;
> +}
> +
> +static int ni_discovery(struct global_ni *ni)
> +{
> + u32 vd_idx, pd_idx, cd_idx, nd_idx, num_idx = 0;
> + void __iomem *vd, *pd, *cd, *nd, **cd_arrays;
> + int num;
> + struct ni_pmu *ni_pmu;
> + struct ni_node node;
> + void __iomem *pbase;
> + struct device *dev = ni->dev;
> +
> + pbase = ni->base;
> +
> + cd_arrays = devm_kmalloc(dev, ni->cd_num * sizeof(typeof(cd)), GFP_KERNEL);
> +
> + /* Step1: Get all clock domains. */
> + for (vd_idx = 0; vd_idx < ni_child_number(ni->base); vd_idx++) {
> + vd = ni_child_pointer(pbase, ni->base, vd_idx);
> +
> + for (pd_idx = 0; pd_idx < ni_child_number(vd); pd_idx++) {
> + pd = ni_child_pointer(pbase, vd, pd_idx);
> +
> + dev_dbg(dev, "The %dth power domain has %d clock domain",
> + pd_idx,
> + ni_child_number(pd));
> +
> + for (cd_idx = 0; cd_idx < ni_child_number(pd); cd_idx++) {
> + cd_arrays[num_idx++] =
> + ni_child_pointer(pbase, pd, cd_idx);
> + }
> + }
> + }
> +
> + /* Step2: Traverse all clock domains. */
> + for (cd_idx = 0; cd_idx < ni->cd_num; cd_idx++) {
> + cd = cd_arrays[cd_idx];
> +
> + num = ni_child_number(cd);
> + dev_dbg(dev, "The %dth clock domain has %d child nodes:", cd_idx, num);
> +
> + /* Omit pmu node */
> + ni_pmu = devm_kzalloc(dev, struct_size(ni_pmu, ev_src_nodes, num - 1),
> + GFP_KERNEL);
> + ni_pmu->ev_src_num = num - 1;
> +
> + if (!ni_pmu)
> + return -ENOMEM;
> +
> + num_idx = 0;
> + for (nd_idx = 0; nd_idx < num; nd_idx++) {
> + nd = ni_child_pointer(pbase, cd, nd_idx);
> +
> + node.base = nd;
> + node.node_type = ni_node_node_type(nd);
> +
> + if (unlikely(ni_node_type(nd) == NI_PMU))
> + ni_pmu->pmu_node = node;
> + else
> + ni_pmu->ev_src_nodes[num_idx++] = node;
> + dev_dbg(dev, " name: %s id: %d", ni_node_name[node.type], node.id);
> + }
> +
> + ni_pmu->dev = dev;
> + ni_pmu->ni = ni;
> + ni->ni_pmus[cd_idx] = ni_pmu;
> + }
> +
> + devm_kfree(dev, cd_arrays);
> +
> + return 0;
> +}
> +
> +static int ni_pmu_probe(struct platform_device *pdev)
> +{
> + int ret, cd_num, idx, irq_num, irq_idx;
> + void __iomem *periphbase;
> + struct global_ni *ni;
> + struct device *dev = &pdev->dev;
> + char *name;
> + static int id;
> + struct ni_pmu *ni_pmu;
> +
> + BUILD_BUG_ON(sizeof(struct ni_hw_perf_event) >
> + offsetof(struct hw_perf_event, target));
> +#define NI_PMU_REG_MAP_SIZE 0xE08
> + BUILD_BUG_ON(sizeof(struct ni_pmu_reg_map) != NI_PMU_REG_MAP_SIZE);
> +
> + periphbase = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(periphbase)) {
> + dev_err_probe(dev, PTR_ERR(periphbase), "Couldn't get ioremap\n");
> + return PTR_ERR(periphbase);
> + }
> +
> + cd_num = ni_child_number_total(periphbase, periphbase, NI_CD);
> +
> + /* Each clock domain contains one PMU. So cd_num == pmu_num. */
> + ni = devm_kzalloc(dev,
> + struct_size(ni, ni_pmus, cd_num),
> + GFP_KERNEL);
> + if (!ni)
> + return -ENOMEM;
> +
> + ni->cd_num = cd_num;
> + ni->base = periphbase;
> + ni->dev = dev;
> + ni->on_cpu = raw_smp_processor_id();
> + platform_set_drvdata(pdev, ni);
> +
> + ret = ni_discovery(ni);
> + if (ret) {
> + dev_err(dev, "%s: discovery error.", __func__);
> + return ret;
> + }
> +
> + irq_num = platform_irq_count(pdev);
> + /* Support that one NI with one irq or one clock domain with one irq. */
> + if (irq_num < 0 || (irq_num != 1 && irq_num != ni->cd_num)) {
> + dev_err(dev, "Error in irq number: %d.", irq_num);
> + return -EINVAL;
> + }
> +
> + if (irq_num != cd_num) {
> + dev_warn(dev, "Only one IRQ found for all PMU.");
> + ret = ni_pmu_irq_setup(ni->ni_pmus[0], 0);
> + if (ret)
> + return ret;
> + }
> +
> + ni->irq_num = irq_num;
> +
> + for (idx = 0, irq_idx = 0; idx < ni->pmu_num; idx++) {
> + ni_pmu = ni->ni_pmus[idx];
> + ret = ni_pmu_init_attr_groups(ni_pmu);
> + if (ret)
> + return ret;
> +
> + if (irq_num == cd_num) {
> + ret = ni_pmu_irq_setup(ni_pmu, irq_idx++);
> + if (ret)
> + return ret;
> + }
> +
> + ni_pmu_reset(ni_pmu);
> +
> + ni_pmu->pmu = (struct pmu) {
> + .module = THIS_MODULE,
> + .task_ctx_nr = perf_invalid_context,
> + .pmu_enable = ni_pmu_enable,
> + .pmu_disable = ni_pmu_disable,
> + .event_init = ni_pmu_event_init,
> + .add = ni_pmu_event_add,
> + .del = ni_pmu_event_del,
> + .start = ni_pmu_event_start,
> + .stop = ni_pmu_event_stop,
> + .read = ni_pmu_event_read,
> + .attr_groups = ni_pmu->pmu.attr_groups,
> + .capabilities = PERF_PMU_CAP_NO_EXCLUDE,
> + };
> +
> + of_property_read_u32(pdev->dev.of_node, "pccs-id", &id);
> +
> + if (cd_num > 1)
> + name = devm_kasprintf(dev, GFP_KERNEL, "ni_pmu_%d_%d", id++, idx);
> + else
> + name = devm_kasprintf(dev, GFP_KERNEL, "ni_pmu_%d", id++);
> +
> + ret = perf_pmu_register(&ni_pmu->pmu, name, -1);
> + if (ret) {
> + dev_err(dev, "Error %d_%d registering PMU", id - 1, idx);
> + return ret;
> + }
> + }
> +
> + ret = cpuhp_state_add_instance_nocalls(ni_hp_state,
> + &ni->node);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +static int ni_pmu_remove(struct platform_device *pdev)
> +{
> + struct global_ni *ni = platform_get_drvdata(pdev);
> + int idx;
> +
> + for (idx = 0; idx < ni->pmu_num; idx++)
> + perf_pmu_unregister(&ni->ni_pmus[idx]->pmu);
> +
> + cpuhp_remove_multi_state(ni_hp_state);
> + return 0;
> +}
> +
> +static const struct of_device_id ni_pmu_of_match[] = {
> + { .compatible = "hx,c2000-arm-ni" },
> + {},
> +};
> +
> +static struct platform_driver ni_pmu_driver = {
> + .driver = {
> + .name = "ni-pmu",
> + .of_match_table = ni_pmu_of_match,
> + },
> + .remove = ni_pmu_remove,
> + .probe = ni_pmu_probe,
> +};
> +
> +static int __init ni_pmu_init(void)
> +{
> + int ret;
> +
> + ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN,
> + "perf/arm/ni:online",
> + NULL,
> + ni_pmu_offline_cpu);
> + if (ret < 0)
> + return ret;
> +
> + ni_hp_state = ret;
> +
> + ret = platform_driver_register(&ni_pmu_driver);
> +
> + if (ret)
> + cpuhp_remove_multi_state(ni_hp_state);
> +
> + return ret;
> +}
> +
> +static void __exit ni_pmu_exit(void)
> +{
> + platform_driver_unregister(&ni_pmu_driver);
> +}
> +
> +module_init(ni_pmu_init);
> +module_exit(ni_pmu_exit);
> +
> +MODULE_AUTHOR("Jialong Yang <[email protected]>");
> +MODULE_DESCRIPTION("PMU driver for ARM NI-700 Performance Monitors Unit");
> +MODULE_LICENSE("GPL");

2024-02-01 02:41:00

by Yang Jialong 杨佳龙

[permalink] [raw]
Subject: Re: [PATCH v2] perf/hx_arm_ni: Support uncore ARM NI-700 PMU



在 2024/2/1 0:50, Robin Murphy 写道:
> On 31/01/2024 7:08 am, JiaLong.Yang wrote:
>> This code is based on uncore PMUs arm_smmuv3_pmu and arm-cmn.
>> One ni-700 can have many clock domains. Each of them has only one PMU.
>> Here one PMU corresponds to one 'struct ni_pmu' instance.
>> PMU name will be ni_pmu_N_M, which N means different NI-700s and M means
>> different PMU in one NI-700. If only one NI-700 found in NI-700, name
>> will
>> be ni_pmu_N.
>> Node interface event name will be xxni_N_eventname, such as
>> asni_0_rdreq_any. There are many kinds of type of nodes in one clock
>> domain. Also means that there are many kinds of that in one PMU. So we
>> distinguish them by xxni string. Besides, maybe there are many nodes
>> have same type. So we have number N in event name.
>> By ni_pmu_0_0/asni_0_rdreq_any/, we can pinpoint accurate bus traffic.
>> Example1: perf stat -a -e ni_pmu_0_0/asni_0_rdreq_any/,ni_pmu_0_0/cycles/
>> EXample2: perf stat -a -e ni_pmu_0_0/asni,id=0,event=0x0/
>
> Oh! I've had a driver for this thing sat around for ages waiting to find
> someone with an interest in testing it. Given that from a quick skim of
> this patch I'd also have several concerns with this implementation, may
> I ask that you have a look at my branch and see if it works for you?

If permission I will test.

>
> https://gitlab.arm.com/linux-arm/linux-rm/-/tree/ni-dev?ref_type=heads
>
> In particular, after the pain of maintaining event aliases in arm-cmn
> I'd really like to get away from doing that again and instead move over
> to jevents this time (especially now that system PMU support is a bit
> more developed there) - I just haven't yet got round to hooking up the
> identifier and writing the JSON files, since it hasn't seemed like much
> of a priority before I know whether the code even works.
>

It's a useful way.
Uncore PMU is increasing. Not only CPU event can be writen in jevents.
I have not considered it when writing code in a low version.

Finally, I have opened a case in arm suppport for linux ni pmu driver.
They tell me no driver.

> Thanks,
> Robin.
>
>>
>> Signed-off-by: JiaLong.Yang <[email protected]>
>> ---
>> v1 --> v2:
>> 1. Submit MAINTANER Documentation/ files seperately.
>> 2. Delete some useless info printing.
>> 3. Change print from pr_xxx to dev_xxx.
>> 4. Fix more than 75 length log info.
>> 5. Fix dts attribute pccs-id.
>> 6. Fix generic name according to DT specification.
>> 7. Some indentation.
>> 8. Del of_match_ptr macro.
>>
>>   drivers/perf/Kconfig     |   11 +
>>   drivers/perf/Makefile    |    1 +
>>   drivers/perf/hx_arm_ni.c | 1284 ++++++++++++++++++++++++++++++++++++++
>>   3 files changed, 1296 insertions(+)
>>   create mode 100644 drivers/perf/hx_arm_ni.c
>>
>> diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig
>> index ec6e0d9194a1..95ef8b13730f 100644
>> --- a/drivers/perf/Kconfig
>> +++ b/drivers/perf/Kconfig
>> @@ -241,4 +241,15 @@ config CXL_PMU
>>         If unsure say 'm'.
>> +config HX_ARM_NI_PMU
>> +       tristate "HX ARM NI-700 PMU"
>> +       depends on PPC_HX_C2000 && 64BIT
>> +       default y
>> +       help
>> +     Support for NI-700(Network-on-chip Interconnect) PMUs, which
>> +     provide monitoring of transactions passing through between
>> +     CMN and other buses or periapherals.
>> +
>> +source "drivers/perf/hisilicon/Kconfig"
>> +
>>   endmenu
>> diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile
>> index a06338e3401c..ec8b9c08577d 100644
>> --- a/drivers/perf/Makefile
>> +++ b/drivers/perf/Makefile
>> @@ -27,3 +27,4 @@ obj-$(CONFIG_DWC_PCIE_PMU) += dwc_pcie_pmu.o
>>   obj-$(CONFIG_ARM_CORESIGHT_PMU_ARCH_SYSTEM_PMU) += arm_cspmu/
>>   obj-$(CONFIG_MESON_DDR_PMU) += amlogic/
>>   obj-$(CONFIG_CXL_PMU) += cxl_pmu.o
>> +obj-$(CONFIG_HX_ARM_NI_PMU) += hx_arm_ni.o
>> diff --git a/drivers/perf/hx_arm_ni.c b/drivers/perf/hx_arm_ni.c
>> new file mode 100644
>> index 000000000000..619e3b789dda
>> --- /dev/null
>> +++ b/drivers/perf/hx_arm_ni.c
>> @@ -0,0 +1,1284 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * HX ARM-NI-700 uncore PMU support
>> + *
>> + * This code is based on uncore PMUs arm_smmuv3_pmu and arm-cmn.
>> + *
>> + * One ni-700 can have many clock domains. Each of them has only one
>> PMU.
>> + * Here one PMU corresponds to one 'struct ni_pmu' instance.
>> + *
>> + * PMU name will be ni_pmu_N_M, which N means different NI-700s and M
>> means
>> + * different PMU in one NI-700. If only one NI-700 found in NI-700, name
>> + * will be ni_pmu_N.
>> + *
>> + * Node interface event name will be xxni_N_eventname, such as
>> + * asni_0_rdreq_any. There are many kinds of type of nodes in one clock
>> + * domain. Also means that there are many kinds of that in one PMU.
>> So we
>> + * distinguish them by xxni string. Besides, maybe there are many nodes
>> + * have same type. So we have number N in event name.
>> + * By ni_pmu_0_0/asni_0_rdreq_any/, we can pinpoint accurate bus
>> traffic.
>> + *
>> + * Example1: perf stat -a -e
>> ni_pmu_0_0/asni_0_rdreq_any/,ni_pmu_0_0/cycles/
>> + * Example2: perf stat -a -e ni_pmu_0_0/asni,id=0,event=0x0/
>> + *
>> + * TODO: Secure or non-secure attribute in all event omitted now.
>> + *
>> + */
>> +
>> +#define dev_fmt(fmt) "ni-700 pmu: " fmt
>> +
>> +#include <linux/bitfield.h>
>> +#include <linux/bitops.h>
>> +#include <linux/cpuhotplug.h>
>> +#include <linux/cpumask.h>
>> +#include <linux/device.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/irq.h>
>> +#include <linux/kernel.h>
>> +#include <linux/msi.h>
>> +#include <linux/of.h>
>> +#include <linux/perf_event.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/smp.h>
>> +#include <linux/sysfs.h>
>> +#include <linux/types.h>
>> +#include <linux/build_bug.h>
>> +
>> +/* number of counters in one ni pmu */
>> +#define NI_PMU_COUNTER_NUM 8
>> +
>> +/* node type values */
>> +enum ni_node_type {
>> +    NI_BASE = 0x0,
>> +    NI_VD,
>> +    NI_PD,
>> +    NI_CD,
>> +    NI_ASNI = 0x4,
>> +    NI_AMNI,
>> +    NI_PMU,
>> +    NI_HSNI,
>> +    NI_HMNI,
>> +    NI_PMNI = 0x9,
>> +};
>> +
>> +/* event format */
>> +/**
>> + * config:
>> + * 0-5    31      32-47      48-63
>> + * event  cycles  node_type  node_id
>> + *
>> + */
>> +#define NI_EVENT_FORMAT_EVENT    GENMASK_ULL(5, 0)
>> +#define NI_EVENT_FORMAT_CYCLES   (1ULL << 31)
>> +#define NI_EVENT_FORMAT_NODETYPE GENMASK_ULL(32 + NI_PMNI, 32)
>> +#define NI_EVENT_FORMAT_ASNI     BIT(32 + NI_ASNI)
>> +#define NI_EVENT_FORMAT_AMNI     BIT(32 + NI_AMNI)
>> +#define NI_EVENT_FORMAT_HSNI     BIT(32 + NI_HSNI)
>> +#define NI_EVENT_FORMAT_HMNI     BIT(32 + NI_HMNI)
>> +#define NI_EVENT_FORMAT_PMNI     BIT(32 + NI_PMNI)
>> +#define NI_EVENT_FORMAT_NODEID   GENMASK_ULL(63, 48)
>> +
>> +#define NI_EVENT_FORMAT_NODE_TYPE GENMASK_ULL(63, 32)
>> +
>> +#define ni_event_config_eventid(_config)
>> FIELD_GET(NI_EVENT_FORMAT_EVENT,       _config)
>> +#define ni_event_config_cc(_config)
>> FIELD_GET(NI_EVENT_FORMAT_CYCLES,      _config)
>> +#define _ni_event_config_nodetype(_config)
>> FIELD_GET(NI_EVENT_FORMAT_NODETYPE,    _config)
>> +#define ni_event_config_nodeid(_config)
>> FIELD_GET(NI_EVENT_FORMAT_NODEID,      _config)
>> +
>> +#define NI_NODE_TYPE_MASK GENMASK(15, 0)
>> +#define NI_NODE_ID_MASK   GENMASK(31, 16)
>> +
>> +#define NI_PMU_PMCR_RST_CYC_CNTR BIT(2)
>> +#define NI_PMU_PMCR_RST_EV_CNTR  BIT(1)
>> +#define NI_PMU_PMCR_ENABLE       BIT(0)
>> +
>> +static const char *const ni_node_name[] = {
>> +    [NI_ASNI] = "asni",
>> +    [NI_AMNI] = "amni",
>> +    [NI_PMU]  = "pmu",
>> +    [NI_HSNI] = "hsni",
>> +    [NI_HMNI] = "hmni",
>> +    [NI_PMNI] = "pmni",
>> +};
>> +
>> +/* one instance for one node */
>> +struct ni_node {
>> +    void __iomem *base;
>> +    union {
>> +        struct {
>> +            u32 type:16;
>> +            u32 id:16;
>> +        };
>> +        u32 node_type;
>> +    };
>> +};
>> +
>> +/* xxx_reg_map only used to provide offset by using offsetof(). */
>> +struct ni_node_reg_map {
>> +    union {
>> +        struct {
>> +            u32 type:16;
>> +            u32 id:16;
>> +        };
>> +        u32 node_type;
>> +    };
>> +
>> +    union {
>> +        u32 child_num;
>> +        u32 node_info;
>> +    };
>> +
>> +    union {
>> +        struct {
>> +            u32 secr_acc;
>> +            u32 pmusela;
>> +            u32 pmuselb;
>> +        };
>> +        DECLARE_FLEX_ARRAY(u32, child_offset);
>> +    };
>> +};
>> +
>> +#define ni_node_offsetof(member)            \
>> +    offsetof(struct ni_node_reg_map, member)
>> +
>> +#define ni_node_pmuevsel(node, config)                    \
>> +    do {                                \
>> +        writel(config,       node->base + ni_node_offsetof(pmusela)); \
>> +        writel(config >> 32, node->base + ni_node_offsetof(pmuselb)); \
>> +    } while (0)
>> +
>> +#define ni_node_read(base, member, readx)            \
>> +    readx((void __iomem *)base + ni_node_offsetof(member))
>> +
>> +#define ni_node_type(base)                        \
>> +    FIELD_GET(NI_NODE_TYPE_MASK, ni_node_read(base, node_type,
>> readl_relaxed))
>> +
>> +#define ni_node_id(base)                        \
>> +    FIELD_GET(NI_NODE_ID_MASK, ni_node_read(base, node_type,
>> readl_relaxed))
>> +
>> +#define ni_node_node_type(base)                \
>> +    ni_node_read(base, node_type, readl_relaxed)
>> +
>> +#define ni_child_number(base)                        \
>> +    (ni_node_type(base) < NI_ASNI ? ni_node_read(base, child_num,
>> readl_relaxed) : 0)
>> +
>> +#define ni_child_pointer(periphbase, base, idx)                \
>> +    ((void __iomem *)periphbase + ni_node_read(base,
>> child_offset[idx], readl_relaxed))
>> +
>> +struct ni_pmu;
>> +struct ni_pmu_reg_map {
>> +    u32 node_type;                   /* offset: 0x000 */
>> +    u32 secr_acc;                    /* offset: 0x004 */
>> +    struct {
>> +        u32 counter;
>> +        u32 reserved;
>> +    } pmevcntr[8];                    /* offset: 0x008 */
>> +    u8  reserved_1[0xF8 - 0x48];     /* offset: 0x048 */
>> +    u32 pmccntr_lower;               /* offset: 0x0F8 */
>> +    u32 pmccntr_upper;               /* offset: 0x0FC */
>> +    u8  reserved_2[0x400 - 0x100];   /* offset: 0x100 */
>> +    u32 pmevtyper[8];                /* offset: 0x400 */
>> +    u8  reserved_3[0x610 - 0x420];   /* offset: 0x420 */
>> +    u32 pmssr;                       /* offset: 0x610 */
>> +    u32 pmovssr;                     /* offset: 0x614 */
>> +    u32 pmccntsr_lower;              /* offset: 0x618 */
>> +    u32 pmccntsr_upper;              /* offset: 0x61C */
>> +    u32 pmevcntsr[8];                /* offset: 0x620 */
>> +    u8  reserved_4[0x6F0 - 0x640];   /* offset: 0x640 */
>> +    u32 pmsscr;                      /* offset: 0x6F0 */
>> +    u8  reserved_5[0xC00 - 0x6F4];   /* offset: 0x6F4 */
>> +    u32 pmcntenset;                  /* offset: 0xC00 */
>> +    u8  reserved_6[0xC20 - 0xC04];   /* offset: 0xC04 */
>> +    u32 pmcntenclr;                  /* offset: 0xC20 */
>> +    u8  reserved_7[0xC40 - 0xC24];   /* offset: 0xC24 */
>> +    u32 pmintenset;                  /* offset: 0xC40 */
>> +    u8  reserved_8[0xC60 - 0xC44];   /* offset: 0xC44 */
>> +    u32 pmintenclr;                  /* offset: 0xC60 */
>> +    u8  reserved_9[0xC80 - 0xC64];   /* offset: 0xC64 */
>> +    u32 pmovsclr;                    /* offset: 0xC80 */
>> +    u8  reserved_10[0xCC0 - 0xC84];  /* offset: 0xC84 */
>> +    u32 pmovsset;                    /* offset: 0xCC0 */
>> +    u8  reserved_11[0xD80 - 0xCC4];  /* offset: 0xCC4 */
>> +    u32 pmcccgr;                     /* offset: 0xD80 */
>> +    u8  reserved_12[0xE00 - 0xD84];  /* offset: 0xD84 */
>> +    u32 pmcfgr;                      /* offset: 0xE00 */
>> +    u32 pmcr;                        /* offset: 0xE04 */
>> +};
>> +
>> +/* Not read or write registers directly. */
>> +#define ni_pmu_offset(ni_pmu, member)                    \
>> +    ((void __iomem *)ni_pmu->pmu_node.base + offsetof(struct
>> ni_pmu_reg_map, member))
>> +
>> +#define ni_pmu_interrupt_enable(ni_pmu, en_bit_mask)        \
>> +    writel(en_bit_mask, ni_pmu_offset(ni_pmu, pmintenset))
>> +
>> +#define ni_pmu_interrupt_disable(ni_pmu, en_bit_mask)        \
>> +    writel(en_bit_mask, ni_pmu_offset(ni_pmu, pmintenclr))
>> +
>> +#define ni_pmu_counter_enable(ni_pmu, en_bit_mask)        \
>> +    writel(en_bit_mask, ni_pmu_offset(ni_pmu, pmcntenset))
>> +
>> +#define ni_pmu_counter_disable(ni_pmu, en_bit_mask)        \
>> +    writel(en_bit_mask, ni_pmu_offset(ni_pmu, pmcntenclr))
>> +
>> +#define ni_pmu_pmevtyper_sel_node(ni_pmu, ev_typer, cnt_idx)        \
>> +    writel(ev_typer, ni_pmu_offset(ni_pmu, pmevtyper[cnt_idx]))
>> +
>> +struct global_ni {
>> +    void __iomem *base;
>> +    struct hlist_node node;
>> +    struct device *dev;
>> +    union {
>> +        unsigned int pmu_num;
>> +        unsigned int cd_num;
>> +    };
>> +    unsigned int on_cpu;
>> +    int irq_num;
>> +    struct ni_pmu *ni_pmus[];
>> +};
>> +
>> +
>> +struct ni_pmu {
>> +    struct ni_node pmu_node;
>> +    struct perf_event *events[NI_PMU_COUNTER_NUM + 1];
>> +    struct pmu pmu;
>> +    struct device *dev;
>> +    unsigned int irq;
>> +    struct global_ni *ni;
>> +    int ev_src_num;
>> +    struct ni_node ev_src_nodes[];
>> +};
>> +
>> +#define to_ni_pmu(_pmu) container_of(_pmu, struct ni_pmu, pmu)
>> +
>> +struct ni_hw_perf_event {
>> +    /* cycle event */
>> +    bool is_cc;
>> +    /* The event corresponds to idxth counter */
>> +    int idx;
>> +    /* Enable bit field in pmcntenset */
>> +#define NI_PMU_CC_EN_BIT 31
>> +    u32 en_bit_mask;
>> +    /* value writen in counter */
>> +    u64 init_val;
>> +    /* If no cc event, config will be writen in pmusela/b */
>> +    u64 config;
>> +    /* The event corresponds to ni_pmu::ev_src_nodes[node_idx] */
>> +    int node_idx;
>> +    /* overwrite state in hw_perf_event */
>> +    int state;
>> +    /* value writen in pmevtyper */
>> +#define NI_PMU_PMEVTYPER_NDTP_OFFSET 9
>> +    union {
>> +        struct {
>> +            u32 id: 9;
>> +            u32 type: 4;
>> +        } node;
>> +        u32 ev_typer;
>> +    };
>> +};
>> +
>> +#define to_ni_hw(event) ((struct ni_hw_perf_event *)&event->hw)
>> +
>> +struct ni_event_desc {
>> +    u64 eventid;
>> +    const char *name;
>> +};
>> +
>> +struct ni_event_attr {
>> +    struct device_attribute attr;
>> +    struct ni_event_desc *ev_desc;
>> +    struct ni_node *node;
>> +};
>> +
>> +#define to_ni_event_attr(p) \
>> +    container_of(p, struct ni_event_attr, attr)
>> +
>> +#define NI_EVENT_DESC(_eventid, _name)            \
>> +    (&((struct ni_event_desc[]) {            \
>> +            { .name = __stringify(_name),    \
>> +              .eventid = _eventid,}        \
>> +        })[0])
>> +
>> +
>> +static struct ni_event_desc *ni_asni_event_descs[] = {
>> +    NI_EVENT_DESC(0x00, rdreq_any),
>> +    NI_EVENT_DESC(0x01, rdreq_dev_arcache),
>> +    NI_EVENT_DESC(0x02, rdreq_rns),
>> +    NI_EVENT_DESC(0x03, rdreq_ro),
>> +    NI_EVENT_DESC(0x04, req_cache_clr),
>> +    NI_EVENT_DESC(0x05, rdreq_beat_any),
>> +    NI_EVENT_DESC(0x06, rdreq_handshake_rlast),
>> +    NI_EVENT_DESC(0x07, wtreq_any),
>> +    NI_EVENT_DESC(0x08, wtreq_dev),
>> +    NI_EVENT_DESC(0x09, wtreq_wns),
>> +    NI_EVENT_DESC(0x0a, wtreq_wlu),
>> +    NI_EVENT_DESC(0x0b, wtreq_wu),
>> +    NI_EVENT_DESC(0x0c, wtreq_atomic),
>> +    NI_EVENT_DESC(0x0d, wtreq_beat_any),
>> +    NI_EVENT_DESC(0x0e, rdreq_stall),
>> +    NI_EVENT_DESC(0x0f, rddata_stall),
>> +    NI_EVENT_DESC(0x10, wtreq_stall),
>> +    NI_EVENT_DESC(0x11, wtdata_stall),
>> +    NI_EVENT_DESC(0x12, wtresp_stall),
>> +    NI_EVENT_DESC(0x13, wtreq_cst),
>> +    NI_EVENT_DESC(0x14, wtchann_nopersist),
>> +    NI_EVENT_DESC(0x15, wtchann_persist),
>> +    NI_EVENT_DESC(0x16, rdreq_nzero_mem_ops),
>> +    NI_EVENT_DESC(0x17, wtreq_nzero_mem_ops),
>> +    NI_EVENT_DESC(0x20, req_stall_cc_ot_limit),
>> +    NI_EVENT_DESC(0x21, req_stall_cc_tspec_limit),
>> +    NI_EVENT_DESC(0x22, req_stall_arbit),
>> +    NI_EVENT_DESC(0x23, req_stall_rd_tracker),
>> +    NI_EVENT_DESC(0x24, req_stall_wt_tracker),
>> +    NI_EVENT_DESC(0x25, aw_stall_wdatafifo_full),
>> +    NI_EVENT_DESC(0x26, ar_stall_reorderbuf_full),
>> +    NI_EVENT_DESC(0x27, aw_cdas_stall),
>> +    NI_EVENT_DESC(0x28, ar_cdas_stall),
>> +    NI_EVENT_DESC(0x29, atomic_rd_stall),
>> +    NI_EVENT_DESC(0x2a, wtchann_wtreq_stall),
>> +    NI_EVENT_DESC(0x2b, rdchann_rdreq_stall),
>> +    NI_EVENT_DESC(0x2c, aw_stall_ot),
>> +    NI_EVENT_DESC(0x2d, ar_stall_ot),
>> +    NI_EVENT_DESC(0x2e, aw_stall_tspec),
>> +    NI_EVENT_DESC(0x2f, ar_stall_tspec),
>> +    NI_EVENT_DESC(0x30, lwmd_arbit_stall_wchann),
>> +    NI_EVENT_DESC(0x31, lwmd_arbit_stall_rchann),
>> +};
>> +
>> +static struct ni_event_desc *ni_amni_event_descs[] = {
>> +    NI_EVENT_DESC(0x00, rdreq_any),
>> +    NI_EVENT_DESC(0x01, rdreq_dev_arcache),
>> +    NI_EVENT_DESC(0x02, rdreq_rns),
>> +    NI_EVENT_DESC(0x03, rdreq_ro),
>> +    NI_EVENT_DESC(0x04, req_cache_clr),
>> +    NI_EVENT_DESC(0x05, rdreq_beat_any),
>> +    NI_EVENT_DESC(0x06, rdreq_handshake_rlast),
>> +    NI_EVENT_DESC(0x07, wtreq_any),
>> +    NI_EVENT_DESC(0x08, wtreq_dev),
>> +    NI_EVENT_DESC(0x09, wtreq_wns),
>> +    NI_EVENT_DESC(0x0a, wtreq_wlu),
>> +    NI_EVENT_DESC(0x0b, wtreq_wu),
>> +    NI_EVENT_DESC(0x0c, wtreq_atomic),
>> +    NI_EVENT_DESC(0x0d, wtreq_beat_any),
>> +    NI_EVENT_DESC(0x0e, rdreq_stall),
>> +    NI_EVENT_DESC(0x0f, rddata_stall),
>> +    NI_EVENT_DESC(0x10, wtreq_stall),
>> +    NI_EVENT_DESC(0x11, wtdata_stall),
>> +    NI_EVENT_DESC(0x12, wtresp_stall),
>> +    NI_EVENT_DESC(0x13, wtreq_cst),
>> +    NI_EVENT_DESC(0x14, wtchann_nopersist),
>> +    NI_EVENT_DESC(0x15, wtchann_persist),
>> +    NI_EVENT_DESC(0x16, rdreq_nzero_mem_ops),
>> +    NI_EVENT_DESC(0x17, wtreq_nzero_mem_ops),
>> +    NI_EVENT_DESC(0x20, req_stall_rd_tracker),
>> +    NI_EVENT_DESC(0x21, req_stall_wt_tracker),
>> +    NI_EVENT_DESC(0x22, wtchann_b_resp),
>> +    NI_EVENT_DESC(0x23, rdchann_rd_resp),
>> +    NI_EVENT_DESC(0x24, lwmd_arbit_stall_wchann),
>> +    NI_EVENT_DESC(0x25, lwmd_arbit_stall_rchann),
>> +};
>> +
>> +static struct ni_event_desc *ni_hsni_event_descs[] = {
>> +    NI_EVENT_DESC(0x00, rdreq_any),
>> +    NI_EVENT_DESC(0x01, rdreq_dev),
>> +    NI_EVENT_DESC(0x02, rdreq_noshare),
>> +    NI_EVENT_DESC(0x03, rdreq_share),
>> +    NI_EVENT_DESC(0x04, rdreq_share_nonormal),
>> +    NI_EVENT_DESC(0x05, rdreq_beat_any),
>> +    NI_EVENT_DESC(0x07, wtreq_any),
>> +    NI_EVENT_DESC(0x08, wtreq_dev),
>> +    NI_EVENT_DESC(0x09, wtreq_noshare),
>> +    NI_EVENT_DESC(0x0a, wtreq_all),
>> +    NI_EVENT_DESC(0x0b, wtreq_share),
>> +    NI_EVENT_DESC(0x0c, wtreq_share_nonormal),
>> +    NI_EVENT_DESC(0x0d, wtreq_beat_any),
>> +    NI_EVENT_DESC(0x0f, rddata_stall),
>> +    NI_EVENT_DESC(0x11, wtdata_stall),
>> +    NI_EVENT_DESC(0x20, req_stall_cc_ot_limit),
>> +    NI_EVENT_DESC(0x21, req_stall_cc_tspec_limit),
>> +    NI_EVENT_DESC(0x22, rdreq_stall_cc_ely_wtresp),
>> +    NI_EVENT_DESC(0x24, req_stall_nzero_wtcnt),
>> +    NI_EVENT_DESC(0x25, w_stall_wdatafifo_full),
>> +    NI_EVENT_DESC(0x2a, wtreq_stall_lack_gt),
>> +    NI_EVENT_DESC(0x2b, rdreq_stall_lack_gt),
>> +};
>> +
>> +static struct ni_event_desc *ni_hmni_event_descs[] = {
>> +    NI_EVENT_DESC(0x00, rdreq_any),
>> +    NI_EVENT_DESC(0x01, rdreq_dev),
>> +    NI_EVENT_DESC(0x02, rdreq_noshare),
>> +    NI_EVENT_DESC(0x03, rdreq_share),
>> +    NI_EVENT_DESC(0x04, rdreq_share_nonormal),
>> +    NI_EVENT_DESC(0x05, rdreq_beat_any),
>> +    NI_EVENT_DESC(0x07, wtreq_any),
>> +    NI_EVENT_DESC(0x08, wtreq_dev),
>> +    NI_EVENT_DESC(0x09, wtreq_noshare),
>> +    NI_EVENT_DESC(0x0a, wtreq_all),
>> +    NI_EVENT_DESC(0x0b, wtreq_share),
>> +    NI_EVENT_DESC(0x0c, wtreq_share_nonormal),
>> +    NI_EVENT_DESC(0x0d, wtreq_beat_any),
>> +    NI_EVENT_DESC(0x0e, rd_addr_phase_stall),
>> +    NI_EVENT_DESC(0x0f, rd_data_phase_stall),
>> +    NI_EVENT_DESC(0x10, wt_addr_phase_stall),
>> +    NI_EVENT_DESC(0x11, wt_data_phase_stall),
>> +    NI_EVENT_DESC(0x22, wtresp_stall_lack_gt),
>> +    NI_EVENT_DESC(0x23, rdresp_stall_lack_gt),
>> +};
>> +
>> +static struct ni_event_desc *ni_pmni_event_descs[] = {
>> +    NI_EVENT_DESC(0x00, rdreq_any),
>> +    NI_EVENT_DESC(0x01, rdreq_dev_arcache),
>> +    NI_EVENT_DESC(0x02, rdreq_noshared),
>> +    NI_EVENT_DESC(0x05, rd_prdata_any),
>> +    NI_EVENT_DESC(0x07, wtreq_any),
>> +    NI_EVENT_DESC(0x08, wtreq_dev),
>> +    NI_EVENT_DESC(0x09, wtreq_noshared),
>> +    NI_EVENT_DESC(0x0d, wtdata_beat_any),
>> +    NI_EVENT_DESC(0x0e, rdreq_stall),
>> +    NI_EVENT_DESC(0x0f, rddata_stall),
>> +    NI_EVENT_DESC(0x10, wtreq_stall),
>> +    NI_EVENT_DESC(0x11, wtdata_stall),
>> +    NI_EVENT_DESC(0x22, wtresp_stall_lack_gt),
>> +    NI_EVENT_DESC(0x23, rdresp_stall_lack_gt),
>> +};
>> +
>> +static int ni_ev_desc_array_size(enum ni_node_type type,
>> +                 struct ni_event_desc ***descs)
>> +{
>> +    switch (type) {
>> +    case NI_ASNI:
>> +        if (descs)
>> +            *descs = ni_asni_event_descs;
>> +        return ARRAY_SIZE(ni_asni_event_descs);
>> +    case NI_AMNI:
>> +        if (descs)
>> +            *descs = ni_amni_event_descs;
>> +        return ARRAY_SIZE(ni_amni_event_descs);
>> +    case NI_HSNI:
>> +        if (descs)
>> +            *descs = ni_hsni_event_descs;
>> +        return ARRAY_SIZE(ni_hsni_event_descs);
>> +    case NI_HMNI:
>> +        if (descs)
>> +            *descs = ni_hmni_event_descs;
>> +        return ARRAY_SIZE(ni_hmni_event_descs);
>> +    case NI_PMNI:
>> +        if (descs)
>> +            *descs = ni_pmni_event_descs;
>> +        return ARRAY_SIZE(ni_pmni_event_descs);
>> +    default:
>> +        return 0;
>> +    }
>> +}
>> +
>> +static ssize_t ni_event_show(struct device *dev,
>> +                   struct device_attribute *attr, char *buf)
>> +{
>> +    struct ni_event_attr *eattr;
>> +
>> +    eattr = to_ni_event_attr(attr);
>> +
>> +    if (eattr->ev_desc)
>> +        return sysfs_emit(buf,
>> +                  "%s,id=0x%x,event=0x%llx\n",
>> +                  ni_node_name[eattr->node->type],
>> +                  eattr->node->id,
>> +                  eattr->ev_desc->eventid);
>> +
>> +    return sysfs_emit(buf, "cycles\n");
>> +}
>> +
>> +struct ni_format_attr {
>> +    struct device_attribute attr;
>> +    u64 field;
>> +};
>> +
>> +static ssize_t ni_format_show(struct device *dev,
>> +                   struct device_attribute *attr, char *buf)
>> +{
>> +    struct ni_format_attr *fmt = container_of(attr, struct
>> ni_format_attr, attr);
>> +    int lo = __ffs(fmt->field), hi = __fls(fmt->field);
>> +
>> +    if (lo == hi)
>> +        return sysfs_emit(buf, "config:%d\n", lo);
>> +
>> +    return sysfs_emit(buf, "config:%d-%d\n", lo, hi);
>> +}
>> +
>> +
>> +#define NI_FORMAT_ATTR(_name, _fld)                    \
>> +    (&((struct ni_format_attr[]) {{                    \
>> +                .attr = __ATTR(_name, 0444, ni_format_show, NULL), \
>> +                .field = _fld,                \
>> +            }})[0].attr.attr)
>> +
>> +static struct attribute *ni_format_attrs[] = {
>> +    NI_FORMAT_ATTR(event, NI_EVENT_FORMAT_EVENT),
>> +    NI_FORMAT_ATTR(cycles, NI_EVENT_FORMAT_CYCLES),
>> +    NI_FORMAT_ATTR(asni, NI_EVENT_FORMAT_ASNI),
>> +    NI_FORMAT_ATTR(amni, NI_EVENT_FORMAT_AMNI),
>> +    NI_FORMAT_ATTR(hsni, NI_EVENT_FORMAT_HSNI),
>> +    NI_FORMAT_ATTR(hmni, NI_EVENT_FORMAT_HMNI),
>> +    NI_FORMAT_ATTR(pmni, NI_EVENT_FORMAT_PMNI),
>> +    NI_FORMAT_ATTR(id, NI_EVENT_FORMAT_NODEID),
>> +    NULL
>> +};
>> +
>> +static const struct attribute_group ni_format_attrs_group = {
>> +    .name = "format",
>> +    .attrs = ni_format_attrs,
>> +};
>> +
>> +static ssize_t ni_cpumask_show(struct device *dev,
>> +                    struct device_attribute *attr, char *buf)
>> +{
>> +    struct ni_pmu *ni_pmu = to_ni_pmu(dev_get_drvdata(dev));
>> +
>> +    return cpumap_print_to_pagebuf(true, buf,
>> cpumask_of(ni_pmu->ni->on_cpu));
>> +}
>> +
>> +static struct device_attribute ni_cpumask_attr =
>> +        __ATTR(cpumask, 0444, ni_cpumask_show, NULL);
>> +
>> +static struct attribute *ni_addition_attrs[] = {
>> +    &ni_cpumask_attr.attr,
>> +    NULL,
>> +};
>> +
>> +static const struct attribute_group ni_addition_attrs_group = {
>> +    .attrs = ni_addition_attrs,
>> +};
>> +
>> +static u64 ni_cntr_get_and_init_optionally(struct perf_event *event,
>> bool init)
>> +{
>> +    u64 old_val, new_val;
>> +    struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
>> +    struct ni_hw_perf_event *hwc = to_ni_hw(event);
>> +
>> +    if (!hwc->is_cc)
>> +        old_val = readl(ni_pmu_offset(ni_pmu,
>> pmevcntr[hwc->idx].counter));
>> +    else
>> +        old_val = readl(ni_pmu_offset(ni_pmu, pmccntr_lower))
>> +            | (((u64)readl(ni_pmu_offset(ni_pmu, pmccntr_upper))) <<
>> 32);
>> +
>> +    if (!init)
>> +        return old_val;
>> +
>> +    new_val = hwc->init_val;
>> +    if (!hwc->is_cc)
>> +        writel(new_val, ni_pmu_offset(ni_pmu,
>> pmevcntr[hwc->idx].counter));
>> +    else {
>> +        writel(new_val, ni_pmu_offset(ni_pmu, pmccntr_lower));
>> +        writel(new_val >> 32, ni_pmu_offset(ni_pmu, pmccntr_upper));
>> +    }
>> +
>> +    return old_val;
>> +}
>> +
>> +static void ni_pmu_event_update(struct perf_event *event)
>> +{
>> +    struct ni_hw_perf_event *hwc = to_ni_hw(event);
>> +    u64 delta, prev, now;
>> +
>> +    do {
>> +        prev = local64_read(&event->hw.prev_count);
>> +        now = ni_cntr_get_and_init_optionally(event, false);
>> +    } while (local64_cmpxchg(&event->hw.prev_count, prev, now) != prev);
>> +
>> +    delta = now - prev;
>> +
>> +    if (!hwc->is_cc)
>> +        delta &= 0xFFFFFFFFULL;
>> +
>> +    local64_add(delta, &event->count);
>> +}
>> +
>> +static void ni_pmu_set_period(struct perf_event *event)
>> +{
>> +    struct ni_hw_perf_event *hwc = to_ni_hw(event);
>> +
>> +    ni_cntr_get_and_init_optionally(event, true);
>> +
>> +    local64_set(&event->hw.prev_count, hwc->init_val);
>> +}
>> +
>> +static void ni_pmu_enable(struct pmu *pmu)
>> +{
>> +    struct ni_pmu *ni_pmu = to_ni_pmu(pmu);
>> +
>> +    writel(NI_PMU_PMCR_ENABLE, ni_pmu_offset(ni_pmu, pmcr));
>> +}
>> +
>> +static inline void ni_pmu_disable(struct pmu *pmu)
>> +{
>> +    struct ni_pmu *ni_pmu = to_ni_pmu(pmu);
>> +
>> +    writel(0, ni_pmu_offset(ni_pmu, pmcr));
>> +}
>> +
>> +static int ni_pmu_find_ev_src(struct ni_pmu *ni_pmu, u32 node_type)
>> +{
>> +    int idx;
>> +
>> +    for (idx = 0; idx < ni_pmu->ev_src_num; idx++)
>> +        if (ni_pmu->ev_src_nodes[idx].node_type == node_type)
>> +            break;
>> +
>> +    return idx;
>> +}
>> +
>> +static bool is_event_supported(u64 eventid, enum ni_node_type type)
>> +{
>> +    int num;
>> +    int idx;
>> +    struct ni_event_desc **descs;
>> +
>> +    num = ni_ev_desc_array_size(type, &descs);
>> +
>> +    for (idx = 0; idx < num; idx++)
>> +        if (eventid == descs[idx]->eventid)
>> +            break;
>> +
>> +    return idx == num ? false : true;
>> +}
>> +
>> +static enum ni_node_type ni_event_config_nodetype(u64 config)
>> +{
>> +    u64 nodetype = _ni_event_config_nodetype(config);
>> +    unsigned long lo = __ffs(nodetype), hi = __fls(nodetype);
>> +
>> +    if (!nodetype || lo != hi)
>> +        return 0;
>> +
>> +    return lo;
>> +
>> +}
>> +
>> +static int ni_pmu_event_init(struct perf_event *event)
>> +{
>> +    struct ni_hw_perf_event *hwc = to_ni_hw(event);
>> +    struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
>> +    u64 config;
>> +    enum ni_node_type nodetype;
>> +    u32 node_type;
>> +
>> +    memset(hwc, 0, sizeof(*hwc));
>> +
>> +    if (event->attr.type != event->pmu->type)
>> +        return -ENOENT;
>> +
>> +    if (is_sampling_event(event))
>> +        return -EINVAL;
>> +
>> +    event->cpu = ni_pmu->ni->on_cpu;
>> +
>> +    config = event->attr.config;
>> +
>> +    hwc->is_cc = ni_event_config_cc(config);
>> +
>> +    if (hwc->is_cc)
>> +        return 0;
>> +
>> +    nodetype = ni_event_config_nodetype(config);
>> +    if (!nodetype)
>> +        return -EINVAL;
>> +
>> +    hwc->node.id = ni_event_config_nodeid(config);
>> +    hwc->node.type = nodetype;
>> +    hwc->config = ni_event_config_eventid(config);
>> +
>> +    node_type = hwc->node.id << 16 | nodetype;
>> +    hwc->node_idx = ni_pmu_find_ev_src(ni_pmu, node_type);
>> +    if (hwc->node_idx == ni_pmu->ev_src_num)
>> +        return -EINVAL;
>> +
>> +    if (!is_event_supported(hwc->config, nodetype))
>> +        return -EINVAL;
>> +
>> +    return 0;
>> +}
>> +
>> +static void ni_pmu_event_start(struct perf_event *event, int flags)
>> +{
>> +    struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
>> +    struct ni_hw_perf_event *hwc = to_ni_hw(event);
>> +
>> +    hwc->state = 0;
>> +
>> +    ni_pmu_set_period(event);
>> +
>> +    if (!hwc->is_cc) {
>> +        ni_node_pmuevsel((&ni_pmu->ev_src_nodes[hwc->node_idx]),
>> hwc->config);
>> +        ni_pmu_pmevtyper_sel_node(ni_pmu, hwc->ev_typer, hwc->idx);
>> +    }
>> +
>> +    ni_pmu_counter_enable(ni_pmu, hwc->en_bit_mask);
>> +}
>> +
>> +static void ni_pmu_event_stop(struct perf_event *event, int flags)
>> +{
>> +    struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
>> +    struct ni_hw_perf_event *hwc = to_ni_hw(event);
>> +
>> +    if (hwc->state & PERF_HES_STOPPED)
>> +        return;
>> +
>> +    ni_pmu_counter_disable(ni_pmu, hwc->en_bit_mask);
>> +
>> +    ni_pmu_event_update(event);
>> +
>> +    hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE;
>> +}
>> +
>> +static void ni_pmu_event_read(struct perf_event *event)
>> +{
>> +    ni_pmu_event_update(event);
>> +}
>> +
>> +static int ni_pmu_event_add(struct perf_event *event, int flags)
>> +{
>> +    struct ni_hw_perf_event *hwc = to_ni_hw(event);
>> +    struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
>> +    int idx;
>> +
>> +    idx = 0;
>> +    if (hwc->is_cc && ni_pmu->events[NI_PMU_COUNTER_NUM])
>> +        return -EAGAIN; /* The cycle counter is in use. */
>> +
>> +    idx = 0;
>> +    if (hwc->is_cc)
>> +        idx = NI_PMU_COUNTER_NUM;
>> +    else
>> +        while ((idx < NI_PMU_COUNTER_NUM) && ni_pmu->events[idx])
>> +            idx++;
>> +
>> +    if (!hwc->is_cc && idx == NI_PMU_COUNTER_NUM)
>> +        return -EAGAIN; /* All general counter is in use. */
>> +
>> +    hwc->idx = idx;
>> +
>> +    hwc->en_bit_mask = hwc->is_cc ? BIT(NI_PMU_CC_EN_BIT) : BIT(idx);
>> +    hwc->init_val = hwc->is_cc ? (0x1ULL << 63) : (0x1ULL << 31);
>> +    hwc->config = hwc->config << idx * 8; /* including is_cc */
>> +    hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE;
>> +
>> +    local64_set(&event->hw.prev_count, 0);
>> +
>> +    ni_pmu->events[idx] = event;
>> +
>> +    ni_pmu_interrupt_enable(ni_pmu, hwc->en_bit_mask);
>> +
>> +    if (flags & PERF_EF_START)
>> +        ni_pmu_event_start(event, flags);
>> +
>> +    return 0;
>> +}
>> +
>> +static void ni_pmu_event_del(struct perf_event *event, int flags)
>> +{
>> +    struct ni_hw_perf_event *hwc = to_ni_hw(event);
>> +    struct ni_pmu *ni_pmu = to_ni_pmu(event->pmu);
>> +
>> +    ni_pmu_event_stop(event, flags);
>> +    ni_pmu_interrupt_disable(ni_pmu, hwc->en_bit_mask);
>> +    ni_pmu->events[hwc->idx] = NULL;
>> +}
>> +
>> +static irqreturn_t _ni_pmu_handle_irq(struct ni_pmu *ni_pmu)
>> +{
>> +    u64 ovsr;
>> +    int idx;
>> +    struct perf_event *event;
>> +    struct ni_hw_perf_event *hwc;
>> +
>> +    ovsr = readl(ni_pmu_offset(ni_pmu, pmovsclr));
>> +    if (!ovsr)
>> +        return IRQ_NONE;
>> +
>> +    writel(ovsr, ni_pmu_offset(ni_pmu, pmovsclr));
>> +
>> +    for_each_set_bit(idx, (unsigned long *)&ovsr, 32) {
>> +        if (idx >= NI_PMU_COUNTER_NUM)
>> +            idx = NI_PMU_COUNTER_NUM;
>> +
>> +        event = ni_pmu->events[idx];
>> +        if (WARN_ON_ONCE(!event))
>> +            continue;
>> +
>> +        hwc = to_ni_hw(event);
>> +        ni_pmu_event_update(event);
>> +        ni_pmu_set_period(event);
>> +        if (idx == NI_PMU_COUNTER_NUM)
>> +            break;
>> +    }
>> +
>> +    return IRQ_HANDLED;
>> +}
>> +
>> +static irqreturn_t ni_pmu_handle_irq(int irq_num, void *data)
>> +{
>> +    struct ni_pmu *ni_pmu = data;
>> +    int idx, ret = IRQ_NONE;
>> +
>> +    if (ni_pmu->ni->irq_num != 1)
>> +        return _ni_pmu_handle_irq(ni_pmu);
>> +
>> +    for (idx = 0; idx < ni_pmu->ni->pmu_num; idx++)
>> +        ret |= _ni_pmu_handle_irq(ni_pmu->ni->ni_pmus[idx]);
>> +
>> +    return ret;
>> +}
>> +
>> +static int ni_hp_state;
>> +static int ni_pmu_offline_cpu(unsigned int cpu, struct hlist_node *node)
>> +{
>> +    struct global_ni *ni;
>> +    unsigned int target;
>> +    int idx;
>> +
>> +
>> +    ni = hlist_entry_safe(node, struct global_ni, node);
>> +    if (cpu != ni->on_cpu)
>> +        return 0;
>> +
>> +
>> +    target = cpumask_any_but(cpu_online_mask, cpu);
>> +    if (target >= nr_cpu_ids)
>> +        return 0;
>> +
>> +
>> +    for (idx = 0; idx < ni->pmu_num; idx++) {
>> +        perf_pmu_migrate_context(&ni->ni_pmus[idx]->pmu, cpu, target);
>> +#ifndef CONFIG_PPC_HX_C2000
>> +        WARN_ON(irq_set_affinity(ni->ni_pmus[idx]->irq,
>> cpumask_of(target)));
>> +#endif
>> +    }
>> +
>> +    ni->on_cpu = target;
>> +
>> +    return 0;
>> +}
>> +
>> +static u32 ni_child_number_total(void __iomem *periphbase,
>> +                 void __iomem *from, enum ni_node_type type)
>> +{
>> +    enum ni_node_type node_type;
>> +    int total, idx;
>> +    void __iomem *child_base;
>> +
>> +    node_type = ni_node_type(from);
>> +
>> +    if (node_type == type)
>> +        return 1;
>> +
>> +    if (node_type >= NI_ASNI)
>> +        return 0;
>> +
>> +    total = 0;
>> +    for (idx = 0; idx < ni_child_number(from); idx++) {
>> +        child_base = ni_child_pointer(periphbase, from, idx);
>> +        total += ni_child_number_total(periphbase, child_base, type);
>> +    }
>> +
>> +    return total;
>> +}
>> +
>> +static void ni_pmu_reset(struct ni_pmu *ni_pmu)
>> +{
>> +    ni_pmu_disable(&ni_pmu->pmu);
>> +
>> +#define clear_reg(name) \
>> +    writel(readl(ni_pmu_offset(ni_pmu, name)), ni_pmu_offset(ni_pmu,
>> name))
>> +
>> +    clear_reg(pmcntenclr);
>> +    clear_reg(pmintenclr);
>> +    clear_reg(pmovsclr);
>> +
>> +    writel_relaxed(NI_PMU_PMCR_RST_CYC_CNTR & NI_PMU_PMCR_RST_EV_CNTR,
>> +               ni_pmu_offset(ni_pmu, pmcr));
>> +}
>> +
>> +static int ni_pmu_irq_setup(struct ni_pmu *ni_pmu, int irq_idx)
>> +{
>> +    int err;
>> +    unsigned long flags = IRQF_NOBALANCING | IRQF_SHARED |
>> IRQF_NO_THREAD;
>> +
>> +    ni_pmu->irq = platform_get_irq(to_platform_device(ni_pmu->dev),
>> irq_idx);
>> +    if (ni_pmu->irq < 0)
>> +        return ni_pmu->irq;
>> +
>> +    err = devm_request_irq(ni_pmu->dev, ni_pmu->irq, ni_pmu_handle_irq,
>> +                   flags, dev_name(ni_pmu->dev), ni_pmu);
>> +    if (err)
>> +        return err;
>> +
>> +#ifndef CONFIG_PPC_HX_C2000
>> +    err = irq_set_affinity(ni_pmu->irq, cpumask_of(ni_pmu->ni->on_cpu));
>> +    if (err)
>> +        return err;
>> +#endif
>> +
>> +    return 0;
>> +}
>> +
>> +static int ni_event_attr_init(struct device *dev,
>> +                  struct ni_event_attr *eattr,
>> +                   struct ni_node *node,
>> +                   struct ni_event_desc *desc)
>> +{
>> +    struct attribute *attr;
>> +    const char *name;
>> +
>> +    attr = &eattr->attr.attr;
>> +
>> +    sysfs_attr_init(attr);
>> +
>> +    eattr->ev_desc = desc;
>> +    eattr->node = node;
>> +
>> +    if (desc && node)
>> +        name = devm_kasprintf(dev,
>> +                  GFP_KERNEL,
>> +                  "%s_%d_%s",
>> +                  ni_node_name[node->type],
>> +                  node->id,
>> +                  desc->name);
>> +    else if (!desc && !node)
>> +        name = "cycles";
>> +    else {
>> +        WARN(1, "No such type attr. Discovery Error!");
>> +        return -EINVAL;
>> +    }
>> +
>> +    if (!name)
>> +        return -ENOMEM;
>> +
>> +    eattr->attr = (struct device_attribute){
>> +        .attr = {
>> +            .name = name,
>> +            .mode = VERIFY_OCTAL_PERMISSIONS(0444)
>> +        },
>> +        .show    = ni_event_show,
>> +        .store    = NULL,
>> +    };
>> +
>> +    return 0;
>> +}
>> +
>> +static int ni_pmu_init_attr_groups(struct ni_pmu *ni_pmu)
>> +{
>> +    int idx, ev_idx, ev_num, ret, ev_num_tmp;
>> +    struct ni_node *node;
>> +    struct ni_event_desc **descs;
>> +    struct attribute **eattrs;
>> +    struct ni_event_attr *ni_eattrs;
>> +    struct device *dev;
>> +    struct attribute_group *eattr_group;
>> +    const struct attribute_group **attr_groups;
>> +    const struct attribute_group *ni_attr_groups_template[4];
>> +
>> +    dev = ni_pmu->dev;
>> +
>> +    eattr_group = devm_kzalloc(dev, sizeof(*eattr_group), GFP_KERNEL);
>> +
>> +    ev_num = 0;
>> +    for (idx = 0; idx < ni_pmu->ev_src_num; idx++) {
>> +        node = &ni_pmu->ev_src_nodes[idx];
>> +
>> +        ev_num += ni_ev_desc_array_size(node->type, NULL);
>> +    }
>> +
>> +    ev_num++;
>> +
>> +    eattrs = devm_kmalloc(dev, sizeof(eattrs[0]) * (ev_num + 1),
>> GFP_KERNEL);
>> +    if (!eattrs)
>> +        return -ENOMEM;
>> +
>> +    ni_eattrs = devm_kzalloc(dev, sizeof(ni_eattrs[0]) * ev_num,
>> GFP_KERNEL);
>> +    if (!ni_eattrs)
>> +        return -ENOMEM;
>> +
>> +    ev_num = 0;
>> +    ret = ni_event_attr_init(dev, &ni_eattrs[ev_num++], NULL, NULL);
>> +    if (ret)
>> +        return ret;
>> +
>> +    for (idx = 0; idx < ni_pmu->ev_src_num; idx++) {
>> +        node = &ni_pmu->ev_src_nodes[idx];
>> +
>> +        ev_num_tmp = ni_ev_desc_array_size(node->type, &descs);
>> +        for (ev_idx = 0; ev_idx < ev_num_tmp; ev_idx++) {
>> +            struct ni_event_desc *desc;
>> +
>> +            desc = descs[ev_idx];
>> +
>> +            ret = ni_event_attr_init(dev, &ni_eattrs[ev_num++], node,
>> desc);
>> +            if (ret)
>> +                return ret;
>> +        }
>> +    }
>> +
>> +    for (idx = 0; idx < ev_num; idx++)
>> +        eattrs[idx] = &ni_eattrs[idx].attr.attr;
>> +
>> +    eattrs[idx] = NULL;
>> +
>> +    eattr_group->name = "events";
>> +    eattr_group->attrs = eattrs;
>> +
>> +    ni_attr_groups_template[0] = eattr_group;
>> +    ni_attr_groups_template[1] = &ni_format_attrs_group;
>> +    ni_attr_groups_template[2] = &ni_addition_attrs_group;
>> +    ni_attr_groups_template[3] = NULL;
>> +
>> +    attr_groups = devm_kmemdup(dev,
>> +                   ni_attr_groups_template,
>> +                   sizeof(ni_attr_groups_template),
>> +                   GFP_KERNEL);
>> +    if (!attr_groups)
>> +        return -ENOMEM;
>> +
>> +    ni_pmu->pmu.attr_groups = attr_groups;
>> +
>> +    return 0;
>> +}
>> +
>> +static int ni_discovery(struct global_ni *ni)
>> +{
>> +    u32 vd_idx, pd_idx, cd_idx, nd_idx, num_idx = 0;
>> +    void __iomem *vd, *pd, *cd, *nd, **cd_arrays;
>> +    int num;
>> +    struct ni_pmu *ni_pmu;
>> +    struct ni_node node;
>> +    void __iomem *pbase;
>> +    struct device *dev = ni->dev;
>> +
>> +    pbase = ni->base;
>> +
>> +    cd_arrays = devm_kmalloc(dev, ni->cd_num * sizeof(typeof(cd)),
>> GFP_KERNEL);
>> +
>> +    /* Step1: Get all clock domains. */
>> +    for (vd_idx = 0; vd_idx < ni_child_number(ni->base); vd_idx++) {
>> +        vd = ni_child_pointer(pbase, ni->base, vd_idx);
>> +
>> +        for (pd_idx = 0; pd_idx < ni_child_number(vd); pd_idx++) {
>> +            pd = ni_child_pointer(pbase, vd, pd_idx);
>> +
>> +            dev_dbg(dev, "The %dth power domain has %d clock domain",
>> +                pd_idx,
>> +                ni_child_number(pd));
>> +
>> +            for (cd_idx = 0; cd_idx < ni_child_number(pd); cd_idx++) {
>> +                cd_arrays[num_idx++] =
>> +                    ni_child_pointer(pbase, pd, cd_idx);
>> +            }
>> +        }
>> +    }
>> +
>> +    /* Step2: Traverse all clock domains. */
>> +    for (cd_idx = 0; cd_idx < ni->cd_num; cd_idx++) {
>> +        cd = cd_arrays[cd_idx];
>> +
>> +        num = ni_child_number(cd);
>> +        dev_dbg(dev, "The %dth clock domain has %d child nodes:",
>> cd_idx, num);
>> +
>> +        /* Omit pmu node */
>> +        ni_pmu = devm_kzalloc(dev, struct_size(ni_pmu, ev_src_nodes,
>> num - 1),
>> +                      GFP_KERNEL);
>> +        ni_pmu->ev_src_num = num - 1;
>> +
>> +        if (!ni_pmu)
>> +            return -ENOMEM;
>> +
>> +        num_idx = 0;
>> +        for (nd_idx = 0; nd_idx < num; nd_idx++) {
>> +            nd = ni_child_pointer(pbase, cd, nd_idx);
>> +
>> +            node.base = nd;
>> +            node.node_type = ni_node_node_type(nd);
>> +
>> +            if (unlikely(ni_node_type(nd) == NI_PMU))
>> +                ni_pmu->pmu_node = node;
>> +            else
>> +                ni_pmu->ev_src_nodes[num_idx++] = node;
>> +            dev_dbg(dev, "  name: %s   id: %d",
>> ni_node_name[node.type], node.id);
>> +        }
>> +
>> +        ni_pmu->dev = dev;
>> +        ni_pmu->ni = ni;
>> +        ni->ni_pmus[cd_idx] = ni_pmu;
>> +    }
>> +
>> +    devm_kfree(dev, cd_arrays);
>> +
>> +    return 0;
>> +}
>> +
>> +static int ni_pmu_probe(struct platform_device *pdev)
>> +{
>> +    int ret, cd_num, idx, irq_num, irq_idx;
>> +    void __iomem *periphbase;
>> +    struct global_ni *ni;
>> +    struct device *dev = &pdev->dev;
>> +    char *name;
>> +    static int id;
>> +    struct ni_pmu *ni_pmu;
>> +
>> +    BUILD_BUG_ON(sizeof(struct ni_hw_perf_event) >
>> +             offsetof(struct hw_perf_event, target));
>> +#define NI_PMU_REG_MAP_SIZE 0xE08
>> +    BUILD_BUG_ON(sizeof(struct ni_pmu_reg_map) != NI_PMU_REG_MAP_SIZE);
>> +
>> +    periphbase = devm_platform_ioremap_resource(pdev, 0);
>> +    if (IS_ERR(periphbase)) {
>> +        dev_err_probe(dev, PTR_ERR(periphbase), "Couldn't get
>> ioremap\n");
>> +        return PTR_ERR(periphbase);
>> +    }
>> +
>> +    cd_num = ni_child_number_total(periphbase, periphbase, NI_CD);
>> +
>> +    /* Each clock domain contains one PMU. So cd_num == pmu_num. */
>> +    ni = devm_kzalloc(dev,
>> +              struct_size(ni, ni_pmus, cd_num),
>> +              GFP_KERNEL);
>> +    if (!ni)
>> +        return -ENOMEM;
>> +
>> +    ni->cd_num = cd_num;
>> +    ni->base = periphbase;
>> +    ni->dev = dev;
>> +    ni->on_cpu = raw_smp_processor_id();
>> +    platform_set_drvdata(pdev, ni);
>> +
>> +    ret = ni_discovery(ni);
>> +    if (ret) {
>> +        dev_err(dev, "%s: discovery error.", __func__);
>> +        return ret;
>> +    }
>> +
>> +    irq_num = platform_irq_count(pdev);
>> +    /* Support that one NI with one irq or one clock domain with one
>> irq. */
>> +    if (irq_num < 0 || (irq_num != 1 && irq_num != ni->cd_num)) {
>> +        dev_err(dev, "Error in irq number: %d.", irq_num);
>> +        return -EINVAL;
>> +    }
>> +
>> +    if (irq_num != cd_num) {
>> +        dev_warn(dev, "Only one IRQ found for all PMU.");
>> +        ret = ni_pmu_irq_setup(ni->ni_pmus[0], 0);
>> +        if (ret)
>> +            return ret;
>> +    }
>> +
>> +    ni->irq_num = irq_num;
>> +
>> +    for (idx = 0, irq_idx = 0; idx < ni->pmu_num; idx++) {
>> +        ni_pmu = ni->ni_pmus[idx];
>> +        ret = ni_pmu_init_attr_groups(ni_pmu);
>> +        if (ret)
>> +            return ret;
>> +
>> +        if (irq_num == cd_num) {
>> +            ret = ni_pmu_irq_setup(ni_pmu, irq_idx++);
>> +            if (ret)
>> +                return ret;
>> +        }
>> +
>> +        ni_pmu_reset(ni_pmu);
>> +
>> +        ni_pmu->pmu = (struct pmu) {
>> +            .module        = THIS_MODULE,
>> +            .task_ctx_nr    = perf_invalid_context,
>> +            .pmu_enable    = ni_pmu_enable,
>> +            .pmu_disable    = ni_pmu_disable,
>> +            .event_init    = ni_pmu_event_init,
>> +            .add        = ni_pmu_event_add,
>> +            .del        = ni_pmu_event_del,
>> +            .start        = ni_pmu_event_start,
>> +            .stop        = ni_pmu_event_stop,
>> +            .read        = ni_pmu_event_read,
>> +            .attr_groups    = ni_pmu->pmu.attr_groups,
>> +            .capabilities    = PERF_PMU_CAP_NO_EXCLUDE,
>> +        };
>> +
>> +        of_property_read_u32(pdev->dev.of_node, "pccs-id", &id);
>> +
>> +        if (cd_num > 1)
>> +            name = devm_kasprintf(dev, GFP_KERNEL, "ni_pmu_%d_%d",
>> id++, idx);
>> +        else
>> +            name = devm_kasprintf(dev, GFP_KERNEL, "ni_pmu_%d", id++);
>> +
>> +        ret = perf_pmu_register(&ni_pmu->pmu, name, -1);
>> +        if (ret) {
>> +            dev_err(dev, "Error %d_%d registering PMU", id - 1, idx);
>> +            return ret;
>> +        }
>> +    }
>> +
>> +    ret = cpuhp_state_add_instance_nocalls(ni_hp_state,
>> +                           &ni->node);
>> +    if (ret)
>> +        return ret;
>> +
>> +    return 0;
>> +}
>> +
>> +static int ni_pmu_remove(struct platform_device *pdev)
>> +{
>> +    struct global_ni *ni = platform_get_drvdata(pdev);
>> +    int idx;
>> +
>> +    for (idx = 0; idx < ni->pmu_num; idx++)
>> +        perf_pmu_unregister(&ni->ni_pmus[idx]->pmu);
>> +
>> +    cpuhp_remove_multi_state(ni_hp_state);
>> +    return 0;
>> +}
>> +
>> +static const struct of_device_id ni_pmu_of_match[] = {
>> +    { .compatible = "hx,c2000-arm-ni" },
>> +    {},
>> +};
>> +
>> +static struct platform_driver ni_pmu_driver = {
>> +    .driver = {
>> +        .name = "ni-pmu",
>> +        .of_match_table = ni_pmu_of_match,
>> +    },
>> +    .remove = ni_pmu_remove,
>> +    .probe = ni_pmu_probe,
>> +};
>> +
>> +static int __init ni_pmu_init(void)
>> +{
>> +    int ret;
>> +
>> +    ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN,
>> +                      "perf/arm/ni:online",
>> +                      NULL,
>> +                      ni_pmu_offline_cpu);
>> +    if (ret < 0)
>> +        return ret;
>> +
>> +    ni_hp_state = ret;
>> +
>> +    ret = platform_driver_register(&ni_pmu_driver);
>> +
>> +    if (ret)
>> +        cpuhp_remove_multi_state(ni_hp_state);
>> +
>> +    return ret;
>> +}
>> +
>> +static void __exit ni_pmu_exit(void)
>> +{
>> +    platform_driver_unregister(&ni_pmu_driver);
>> +}
>> +
>> +module_init(ni_pmu_init);
>> +module_exit(ni_pmu_exit);
>> +
>> +MODULE_AUTHOR("Jialong Yang <[email protected]>");
>> +MODULE_DESCRIPTION("PMU driver for ARM NI-700 Performance Monitors
>> Unit");
>> +MODULE_LICENSE("GPL");
>

2024-02-01 03:02:00

by Yang Jialong 杨佳龙

[permalink] [raw]
Subject: Re: [PATCH v2] perf/hx_arm_ni: Support uncore ARM NI-700 PMU



在 2024/1/31 18:36, Krzysztof Kozlowski 写道:
> On 31/01/2024 11:07, Yang Jialong 杨佳龙 wrote:
>>
>>
>> 在 2024/1/31 17:38, Krzysztof Kozlowski 写道:
>>> On 31/01/2024 10:07, Yang Jialong 杨佳龙 wrote:
>>>>
>>>>
>>>> 在 2024/1/31 15:59, Krzysztof Kozlowski 写道:
>>>>> On 31/01/2024 08:08, JiaLong.Yang wrote:
>>>>>> This code is based on uncore PMUs arm_smmuv3_pmu and arm-cmn.
>>>>>> One ni-700 can have many clock domains. Each of them has only one PMU.
>>>>>> Here one PMU corresponds to one 'struct ni_pmu' instance.
>>>>>> PMU name will be ni_pmu_N_M, which N means different NI-700s and M means
>>>>>> different PMU in one NI-700. If only one NI-700 found in NI-700, name will
>>>>>> be ni_pmu_N.
>>>>>> Node interface event name will be xxni_N_eventname, such as
>>>>>> asni_0_rdreq_any. There are many kinds of type of nodes in one clock
>>>>>> domain. Also means that there are many kinds of that in one PMU. So we
>>>>>> distinguish them by xxni string. Besides, maybe there are many nodes
>>>>>> have same type. So we have number N in event name.
>>>>>> By ni_pmu_0_0/asni_0_rdreq_any/, we can pinpoint accurate bus traffic.
>>>>>> Example1: perf stat -a -e ni_pmu_0_0/asni_0_rdreq_any/,ni_pmu_0_0/cycles/
>>>>>> EXample2: perf stat -a -e ni_pmu_0_0/asni,id=0,event=0x0/
>>>>>>
>>>>>> Signed-off-by: JiaLong.Yang <[email protected]>
>>>>>> ---
>>>>>> v1 --> v2:
>>>>>> 1. Submit MAINTANER Documentation/ files seperately.
>>>>>
>>>>> SEPARATE PATCHES, not patchsets. You have now checkpatch warnings
>>>>> because of this...
>>>>
>>>> ...OK. But the MAINTANER file changing should be given in which one
>>>> patches.
>>>> I will submit patch v3 after talking and your permission.
>>>>
>>>>>
>>>>>> 2. Delete some useless info printing.
>>>>>> 3. Change print from pr_xxx to dev_xxx.
>>>>>> 4. Fix more than 75 length log info.
>>>>>> 5. Fix dts attribute pccs-id.
>>>>>> 6. Fix generic name according to DT specification.
>>>>>> 7. Some indentation.
>>>>>> 8. Del of_match_ptr macro.
>>>>>>
>>>>>> drivers/perf/Kconfig | 11 +
>>>>>> drivers/perf/Makefile | 1 +
>>>>>> drivers/perf/hx_arm_ni.c | 1284 ++++++++++++++++++++++++++++++++++++++
>>>>>> 3 files changed, 1296 insertions(+)
>>>>>> create mode 100644 drivers/perf/hx_arm_ni.c
>>>>>>
>>>>>> diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig
>>>>>> index ec6e0d9194a1..95ef8b13730f 100644
>>>>>> --- a/drivers/perf/Kconfig
>>>>>> +++ b/drivers/perf/Kconfig
>>>>>> @@ -241,4 +241,15 @@ config CXL_PMU
>>>>>>
>>>>>> If unsure say 'm'.
>>>>>>
>>>>>> +config HX_ARM_NI_PMU
>>>>>> + tristate "HX ARM NI-700 PMU"
>>>>>> + depends on PPC_HX_C2000 && 64BIT
>>>>>
>>>>> 1. There is no PPC_HX_C2000.
>>>>
>>>> I have been used to using this macro. However this macro is not existed
>>>> in mainline.
>>>> I will replace it with ARM64. And del involved C code if OK.
>>>>
>>>> 64bit:
>>>> __ffs(unsigned long) and __fls(unsigned long) will be wrong in 32bit. I
>>>> pass a u64 argument.
>>>
>>> One thing is where the code is supposed to run, second thing is compile
>>> testing.
>>>
>>
>> Now run on my company product, a 64bit PowerPC...
>> But I think it's general for 64bit systems.
>>
>>> Why do you use __ffs, not __ffs64 which takes u64 if you really want
>>> only 64bit argument? unsigned long != u64, so your code is not
>>> architecture independent. You claim you wrote it on purpose as
>>> non-architecture-independent, but then I claim it's a bug. We are
>>> supposed to write code which is portable, as much as possible, assuming
>>> it does not affect readability.
>>>
>>
>> I write code in v5.18, there are __ffs64() and fls64(). Asymmetric.
>
> Sorry, that's a no go.
>
> That's some very, very old kernel. Do not develop on old kernels, but on
> mainline. I also suspect that by basing your work on old kernel, you
> duplicate a lot of issues already fixed.
>
>> There are some difference in return val between __ffs() and ffs64().
>> __ffs(0) and ffs64(0) will give different value.
>
> __ffs64 calls __ffs, so why would results be different?
>
> Anyway, that's not really excuse.
>

OK. Follow mainline.

>
>>
>> And I'm sure code run in 64bit. So I choose to use __ffs and __fls.
>>
>> Maybe it could be compatbile with 32bit. But I don't have a environment
>> to test this.
>>>
>>>> struct ni_hw_perf_event will be big than limit.
>>>> BUILD_BUG_ON(sizeof(struct ni_hw_perf_event) > offsetof(struct
>>>> hw_perf_event, target));
>>>
>>> And why do you need to use any of such code? Please open one of hundreds
>>> of other drivers which work correctly on 32 and 64-bit platforms.
>>>
>>
>> Code for 64bit.
>> This code is to avoid struct ni_hw_perf_event is too big than struct
>> hw_perf_event::target.
>
> 1. Why would that matter? target is task_struct. It's size does not
> matter. Maybe its offset matters, but not size.
>

Offset.

> 2. So you claim that on 32-bit system the structure will be bigger than
> on 64-bit system?

The structure will exceed the offset in 32bit. Maybe because the latter
changed more.
OK. Dont care please.

>
>> I learn it from arm-cmn.c.
>
> Are you copying patterns because they are good patterns or just because
> you decided to copy?

Maybe this way is not very good for event framework.
OK. Not an official way.

>
>> ni_hw_perf_event will replace hw_perf_event.
>> I will put some useful information in it with less space and good field
>> names.
>> But I can't exceed a limit.
>>
>>>>
>>>>> 2. Nothing justified dependency on 64bit. Drop or explain. Your previous
>>>>> message did not provide real rationale.
>>>>
>>>> If ARM64, then drop.
>>>
>>> ...
>>>
>>> ...
>>>
>>>>>> + /* Step2: Traverse all clock domains. */
>>>>>> + for (cd_idx = 0; cd_idx < ni->cd_num; cd_idx++) {
>>>>>> + cd = cd_arrays[cd_idx];
>>>>>> +
>>>>>> + num = ni_child_number(cd);
>>>>>> + dev_dbg(dev, "The %dth clock domain has %d child nodes:", cd_idx, num);
>>>>>> +
>>>>>> + /* Omit pmu node */
>>>>>> + ni_pmu = devm_kzalloc(dev, struct_size(ni_pmu, ev_src_nodes, num - 1),
>>>>>> + GFP_KERNEL);
>>>>>> + ni_pmu->ev_src_num = num - 1;
>>>>>> +
>>>>>> + if (!ni_pmu)
>>>>>> + return -ENOMEM;
>>>>>> +
>>>>>> + num_idx = 0;
>>>>>> + for (nd_idx = 0; nd_idx < num; nd_idx++) {
>>>>>> + nd = ni_child_pointer(pbase, cd, nd_idx);
>>>>>> +
>>>>>> + node.base = nd;
>>>>>> + node.node_type = ni_node_node_type(nd);
>>>>>> +
>>>>>> + if (unlikely(ni_node_type(nd) == NI_PMU))
>>>>>> + ni_pmu->pmu_node = node;
>>>>>> + else
>>>>>> + ni_pmu->ev_src_nodes[num_idx++] = node;
>>>>>> + dev_dbg(dev, " name: %s id: %d", ni_node_name[node.type], node.id);
>>>>>> + }
>>>>>> +
>>>>>> + ni_pmu->dev = dev;
>>>>>> + ni_pmu->ni = ni;
>>>>>> + ni->ni_pmus[cd_idx] = ni_pmu;
>>>>>> + }
>>>>>> +
>>>>>> + devm_kfree(dev, cd_arrays);
>>>>>
>>>>> Why? If it is not device-lifetime then allocate with usual way.
>>>>>
>>>>
>>>> No device-lifetime.
>>>> Will allocate in stack.
>>>
>>> I was thinking about kzalloc. But if array is small, stack could be as well.
>>>
>>
>> If I have to return before devm_kfree because of wrong, I will have to use:
>>
>> goto out;
>>
>> out:
>> kfree();
>>
>> But if I use devm_kzalloc, I will not be worried about that. Even if no
>
> devm* is not for that purpose. devm is for device-managed allocations.
> Device does not manage your allocation.
>
>> device-lifetime.
>> Isn't this a good way?
>
> Then you want cleanup.h and use proper __free().

Good NEW API. It does what I want.
Learned more. Thanks.

>
> Best regards,
> Krzysztof
>
>


2024-02-01 06:37:29

by Yang Jialong 杨佳龙

[permalink] [raw]
Subject: Re: [PATCH v2] perf/hx_arm_ni: Support uncore ARM NI-700 PMU



在 2024/2/1 11:00, Yang Jialong 杨佳龙 写道:
>
>
> 在 2024/1/31 18:36, Krzysztof Kozlowski 写道:
>> On 31/01/2024 11:07, Yang Jialong 杨佳龙 wrote:
>>>
>>>
>>> 在 2024/1/31 17:38, Krzysztof Kozlowski 写道:
>>>> On 31/01/2024 10:07, Yang Jialong 杨佳龙 wrote:
>>>>>
>>>>>
>>>>> 在 2024/1/31 15:59, Krzysztof Kozlowski 写道:
>>>>>> On 31/01/2024 08:08, JiaLong.Yang wrote:
>>>>>>> This code is based on uncore PMUs arm_smmuv3_pmu and arm-cmn.
>>>>>>> One ni-700 can have many clock domains. Each of them has only one
>>>>>>> PMU.
>>>>>>> Here one PMU corresponds to one 'struct ni_pmu' instance.
>>>>>>> PMU name will be ni_pmu_N_M, which N means different NI-700s and
>>>>>>> M means
>>>>>>> different PMU in one NI-700. If only one NI-700 found in NI-700,
>>>>>>> name will
>>>>>>> be ni_pmu_N.
>>>>>>> Node interface event name will be xxni_N_eventname, such as
>>>>>>> asni_0_rdreq_any. There are many kinds of type of nodes in one clock
>>>>>>> domain. Also means that there are many kinds of that in one PMU.
>>>>>>> So we
>>>>>>> distinguish them by xxni string. Besides, maybe there are many nodes
>>>>>>> have same type. So we have number N in event name.
>>>>>>> By ni_pmu_0_0/asni_0_rdreq_any/, we can pinpoint accurate bus
>>>>>>> traffic.
>>>>>>> Example1: perf stat -a -e
>>>>>>> ni_pmu_0_0/asni_0_rdreq_any/,ni_pmu_0_0/cycles/
>>>>>>> EXample2: perf stat -a -e ni_pmu_0_0/asni,id=0,event=0x0/
>>>>>>>
>>>>>>> Signed-off-by: JiaLong.Yang <[email protected]>
>>>>>>> ---
>>>>>>> v1 --> v2:
>>>>>>> 1. Submit MAINTANER Documentation/ files seperately.
>>>>>>
>>>>>> SEPARATE PATCHES, not patchsets. You have now checkpatch warnings
>>>>>> because of this...
>>>>>
>>>>> ...OK. But the MAINTANER file changing should be given in which one
>>>>> patches.
>>>>> I will submit patch v3 after talking and your permission.
>>>>>
>>>>>>
>>>>>>> 2. Delete some useless info printing.
>>>>>>> 3. Change print from pr_xxx to dev_xxx.
>>>>>>> 4. Fix more than 75 length log info.
>>>>>>> 5. Fix dts attribute pccs-id.
>>>>>>> 6. Fix generic name according to DT specification.
>>>>>>> 7. Some indentation.
>>>>>>> 8. Del of_match_ptr macro.
>>>>>>>
>>>>>>>     drivers/perf/Kconfig     |   11 +
>>>>>>>     drivers/perf/Makefile    |    1 +
>>>>>>>     drivers/perf/hx_arm_ni.c | 1284
>>>>>>> ++++++++++++++++++++++++++++++++++++++
>>>>>>>     3 files changed, 1296 insertions(+)
>>>>>>>     create mode 100644 drivers/perf/hx_arm_ni.c
>>>>>>>
>>>>>>> diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig
>>>>>>> index ec6e0d9194a1..95ef8b13730f 100644
>>>>>>> --- a/drivers/perf/Kconfig
>>>>>>> +++ b/drivers/perf/Kconfig
>>>>>>> @@ -241,4 +241,15 @@ config CXL_PMU
>>>>>>>           If unsure say 'm'.
>>>>>>> +config HX_ARM_NI_PMU
>>>>>>> +       tristate "HX ARM NI-700 PMU"
>>>>>>> +       depends on PPC_HX_C2000 && 64BIT
>>>>>>
>>>>>> 1. There is no PPC_HX_C2000.
>>>>>
>>>>> I have been used to using this macro. However this macro is not
>>>>> existed
>>>>> in mainline.
>>>>> I will replace it with ARM64. And del involved C code if OK.
>>>>>
>>>>> 64bit:
>>>>> __ffs(unsigned long) and __fls(unsigned long) will be wrong in
>>>>> 32bit. I
>>>>> pass a u64 argument.
>>>>
>>>> One thing is where the code is supposed to run, second thing is compile
>>>> testing.
>>>>
>>>
>>> Now run on my company product, a 64bit PowerPC...
>>> But I think it's general for 64bit systems.
>>>
>>>> Why do you use __ffs, not __ffs64 which takes u64 if you really want
>>>> only 64bit argument? unsigned long != u64, so your code is not
>>>> architecture independent. You claim you wrote it on purpose as
>>>> non-architecture-independent, but then I claim it's a bug. We are
>>>> supposed to write code which is portable, as much as possible, assuming
>>>> it does not affect readability.
>>>>
>>>
>>> I write code in v5.18, there are __ffs64() and fls64(). Asymmetric.
>>
>> Sorry, that's a no go.
>>
>> That's some very, very old kernel. Do not develop on old kernels, but on
>> mainline. I also suspect that by basing your work on old kernel, you
>> duplicate a lot of issues already fixed.
>>
>>> There are some difference in return val between __ffs() and ffs64().
>>> __ffs(0) and ffs64(0) will give different value.
>>
>> __ffs64 calls __ffs, so why would results be different?
>>
>> Anyway, that's not really excuse.
>>
>
> OK. Follow mainline.
>
>>
>>>
>>> And I'm sure code run in 64bit. So I choose to use __ffs and __fls.
>>>
>>> Maybe it could be compatbile with 32bit. But I don't have a environment
>>> to test this.
>>>>
>>>>> struct ni_hw_perf_event will be big than limit.
>>>>> BUILD_BUG_ON(sizeof(struct ni_hw_perf_event) > offsetof(struct
>>>>> hw_perf_event, target));
>>>>
>>>> And why do you need to use any of such code? Please open one of
>>>> hundreds
>>>> of other drivers which work correctly on 32 and 64-bit platforms.
>>>>
>>>
>>> Code for 64bit.
>>> This code is to avoid struct ni_hw_perf_event is too big than struct
>>> hw_perf_event::target.
>>
>> 1. Why would that matter? target is task_struct. It's size does not
>> matter. Maybe its offset matters, but not size.
>>
>
> Offset.
>
>> 2. So you claim that on 32-bit system the structure will be bigger than
>> on 64-bit system?
>
> The structure will exceed the offset in 32bit. Maybe because the latter
> changed more.
> OK. Dont care please.
>
>>
>>> I learn it from arm-cmn.c.
>>
>> Are you copying patterns because they are good patterns or just because
>> you decided to copy?
>
> Maybe this way is not very good for event framework.
> OK. Not an official way.
>

First, the struct hw_perf_event is originally used to record some
general information about its event. Example, struct
hw_perf_event::state is used to record status by driver developer.

Second, But there are some field I don't need to use. And instread I
want to use another name, just like en_bit_mask or is_cc, depending on hw.

Finally, considering the lifetime keeping of struct ni_hw_perf_event,
this way is very well. I don't need to keep an eye on it. And I just
need to promise the size. So I choose BUILD_BUG_ON();

What do you think?

Dont care about 32, 64 or offset.

>>
>>> ni_hw_perf_event will replace hw_perf_event.
>>> I will put some useful information in it with less space and good field
>>> names.
>>> But I can't exceed a limit.
>>>
>>>>>
>>>>>> 2. Nothing justified dependency on 64bit. Drop or explain. Your
>>>>>> previous
>>>>>> message did not provide real rationale.
>>>>>
>>>>> If ARM64, then drop.
>>>>
>>>> ...
>>>>
>>>> ...
>>>>
>>>>>>> +    /* Step2: Traverse all clock domains. */
>>>>>>> +    for (cd_idx = 0; cd_idx < ni->cd_num; cd_idx++) {
>>>>>>> +        cd = cd_arrays[cd_idx];
>>>>>>> +
>>>>>>> +        num = ni_child_number(cd);
>>>>>>> +        dev_dbg(dev, "The %dth clock domain has %d child
>>>>>>> nodes:", cd_idx, num);
>>>>>>> +
>>>>>>> +        /* Omit pmu node */
>>>>>>> +        ni_pmu = devm_kzalloc(dev, struct_size(ni_pmu,
>>>>>>> ev_src_nodes, num - 1),
>>>>>>> +                      GFP_KERNEL);
>>>>>>> +        ni_pmu->ev_src_num = num - 1;
>>>>>>> +
>>>>>>> +        if (!ni_pmu)
>>>>>>> +            return -ENOMEM;
>>>>>>> +
>>>>>>> +        num_idx = 0;
>>>>>>> +        for (nd_idx = 0; nd_idx < num; nd_idx++) {
>>>>>>> +            nd = ni_child_pointer(pbase, cd, nd_idx);
>>>>>>> +
>>>>>>> +            node.base = nd;
>>>>>>> +            node.node_type = ni_node_node_type(nd);
>>>>>>> +
>>>>>>> +            if (unlikely(ni_node_type(nd) == NI_PMU))
>>>>>>> +                ni_pmu->pmu_node = node;
>>>>>>> +            else
>>>>>>> +                ni_pmu->ev_src_nodes[num_idx++] = node;
>>>>>>> +            dev_dbg(dev, "  name: %s   id: %d",
>>>>>>> ni_node_name[node.type], node.id);
>>>>>>> +        }
>>>>>>> +
>>>>>>> +        ni_pmu->dev = dev;
>>>>>>> +        ni_pmu->ni = ni;
>>>>>>> +        ni->ni_pmus[cd_idx] = ni_pmu;
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    devm_kfree(dev, cd_arrays);
>>>>>>
>>>>>> Why? If it is not device-lifetime then allocate with usual way.
>>>>>>
>>>>>
>>>>> No device-lifetime.
>>>>> Will allocate in stack.
>>>>
>>>> I was thinking about kzalloc. But if array is small, stack could be
>>>> as well.
>>>>
>>>
>>> If I have to return before devm_kfree because of wrong, I will have
>>> to use:
>>>
>>> goto out;
>>>
>>> out:
>>> kfree();
>>>
>>> But if I use devm_kzalloc, I will not be worried about that. Even if no
>>
>> devm* is not for that purpose. devm is for device-managed allocations.
>> Device does not manage your allocation.
>>
>>> device-lifetime.
>>> Isn't this a good way?
>>
>> Then you want cleanup.h and use proper __free().
>
> Good NEW API. It does what I want.
> Learned more. Thanks.
>
>>
>> Best regards,
>> Krzysztof
>>
>>

2024-02-02 20:12:02

by Robin Murphy

[permalink] [raw]
Subject: Re: [PATCH v2] perf/hx_arm_ni: Support uncore ARM NI-700 PMU

On 2024-02-01 2:40 am, Yang Jialong 杨佳龙 wrote:
>
>
> 在 2024/2/1 0:50, Robin Murphy 写道:
>> On 31/01/2024 7:08 am, JiaLong.Yang wrote:
>>> This code is based on uncore PMUs arm_smmuv3_pmu and arm-cmn.
>>> One ni-700 can have many clock domains. Each of them has only one PMU.
>>> Here one PMU corresponds to one 'struct ni_pmu' instance.
>>> PMU name will be ni_pmu_N_M, which N means different NI-700s and M means
>>> different PMU in one NI-700. If only one NI-700 found in NI-700, name
>>> will
>>> be ni_pmu_N.
>>> Node interface event name will be xxni_N_eventname, such as
>>> asni_0_rdreq_any. There are many kinds of type of nodes in one clock
>>> domain. Also means that there are many kinds of that in one PMU. So we
>>> distinguish them by xxni string. Besides, maybe there are many nodes
>>> have same type. So we have number N in event name.
>>> By ni_pmu_0_0/asni_0_rdreq_any/, we can pinpoint accurate bus traffic.
>>> Example1: perf stat -a -e
>>> ni_pmu_0_0/asni_0_rdreq_any/,ni_pmu_0_0/cycles/
>>> EXample2: perf stat -a -e ni_pmu_0_0/asni,id=0,event=0x0/
>>
>> Oh! I've had a driver for this thing sat around for ages waiting to
>> find someone with an interest in testing it. Given that from a quick
>> skim of this patch I'd also have several concerns with this
>> implementation, may I ask that you have a look at my branch and see if
>> it works for you?
>
> If permission I will test.

Thanks! I see you've also tried to support having the IRQs combined
together - does your platform need that right away? That's another thing
I've been anticipating but haven't got round to wiring up yet (it's a
bit more fiddly than just IRQF_SHARED since perf has some expectations
around affinity to event->cpu), but I can have a go at getting it done.

>>
>> https://gitlab.arm.com/linux-arm/linux-rm/-/tree/ni-dev?ref_type=heads
>>
>> In particular, after the pain of maintaining event aliases in arm-cmn
>> I'd really like to get away from doing that again and instead move
>> over to jevents this time (especially now that system PMU support is a
>> bit more developed there) - I just haven't yet got round to hooking up
>> the identifier and writing the JSON files, since it hasn't seemed like
>> much of a priority before I know whether the code even works.
>>
>
> It's a useful way.
> Uncore PMU is increasing. Not only CPU event can be writen in jevents.
> I have not considered it when writing code in a low version.
>
> Finally, I have opened a case in arm suppport for linux ni pmu driver.
> They tell me no driver.

Oh dear, sorry about that - I'd have hoped that a question about a Linux
PMU driver might have found its way to our team, even if they weren't
aware that NI-700 has already been a specific item on our roadmap for
some time :(

Thanks,
Robin.

2024-02-29 02:23:47

by Yang Jialong 杨佳龙

[permalink] [raw]
Subject: Re: [PATCH v2] perf/hx_arm_ni: Support uncore ARM NI-700 PMU



在 2024/2/3 4:11, Robin Murphy 写道:
> On 2024-02-01 2:40 am, Yang Jialong 杨佳龙 wrote:
>>
>>
>> 在 2024/2/1 0:50, Robin Murphy 写道:
>>> On 31/01/2024 7:08 am, JiaLong.Yang wrote:
>>>> This code is based on uncore PMUs arm_smmuv3_pmu and arm-cmn.
>>>> One ni-700 can have many clock domains. Each of them has only one PMU.
>>>> Here one PMU corresponds to one 'struct ni_pmu' instance.
>>>> PMU name will be ni_pmu_N_M, which N means different NI-700s and M
>>>> means
>>>> different PMU in one NI-700. If only one NI-700 found in NI-700,
>>>> name will
>>>> be ni_pmu_N.
>>>> Node interface event name will be xxni_N_eventname, such as
>>>> asni_0_rdreq_any. There are many kinds of type of nodes in one clock
>>>> domain. Also means that there are many kinds of that in one PMU. So we
>>>> distinguish them by xxni string. Besides, maybe there are many nodes
>>>> have same type. So we have number N in event name.
>>>> By ni_pmu_0_0/asni_0_rdreq_any/, we can pinpoint accurate bus traffic.
>>>> Example1: perf stat -a -e
>>>> ni_pmu_0_0/asni_0_rdreq_any/,ni_pmu_0_0/cycles/
>>>> EXample2: perf stat -a -e ni_pmu_0_0/asni,id=0,event=0x0/
>>>
>>> Oh! I've had a driver for this thing sat around for ages waiting to
>>> find someone with an interest in testing it. Given that from a quick
>>> skim of this patch I'd also have several concerns with this
>>> implementation, may I ask that you have a look at my branch and see
>>> if it works for you?
>>
>> If permission I will test.
>
> Thanks! I see you've also tried to support having the IRQs combined
> together - does your platform need that right away? That's another thing
> I've been anticipating but haven't got round to wiring up yet (it's a
> bit more fiddly than just IRQF_SHARED since perf has some expectations
> around affinity to event->cpu), but I can have a go at getting it done.
>

I have not seen the expectations. I will study it.

>>>
>>> https://gitlab.arm.com/linux-arm/linux-rm/-/tree/ni-dev?ref_type=heads
>>>
>>> In particular, after the pain of maintaining event aliases in arm-cmn
>>> I'd really like to get away from doing that again and instead move
>>> over to jevents this time (especially now that system PMU support is
>>> a bit more developed there) - I just haven't yet got round to hooking
>>> up the identifier and writing the JSON files, since it hasn't seemed
>>> like much of a priority before I know whether the code even works.
>>>
>>
>> It's a useful way.
>> Uncore PMU is increasing. Not only CPU event can be writen in jevents.
>> I have not considered it when writing code in a low version.
>>
>> Finally, I have opened a case in arm suppport for linux ni pmu driver.
>> They tell me no driver.
>
> Oh dear, sorry about that - I'd have hoped that a question about a Linux
> PMU driver might have found its way to our team, even if they weren't
> aware that NI-700 has already been a specific item on our roadmap for
> some time :(
>
> Thanks,
> Robin.
>

I have run the code and thanks to the support of [email protected].
It's sorry for the late.

2024-02-29 02:28:09

by Yang Jialong 杨佳龙

[permalink] [raw]
Subject: Re: [PATCH v2] perf/hx_arm_ni: Support uncore ARM NI-700 PMU

From d11d3c01978ef0fd8bfd125189c671c9bcc3c096 Mon Sep 17 00:00:00 2001
From: yjl00405 <[email protected]>
Date: Thu, 29 Feb 2024 09:39:51 +0800
Subject: [PATCH] arm-ni:Some changes.

---
1. Some small mistakes.
2. Have not given 'NI_PMU unit->ns = true'. So no cycles event exists
in arm_ni_0_cd_0/events.

Test(multiple cores server):
1. hotplug test pass.
~ # cat /sys/bus/event_source/devices/arm_ni_0_cd_0/cpumask
0
~ # echo 0 > /sys/devices/system/cpu/cpu0/online
~ # cat /sys/bus/event_source/devices/arm_ni_0_cd_0/cpumask
1
~ # # Test cycles event
~ # perf stat -e arm_ni_0_cd_0/type=6/ echo
Performance counter stats for 'system wide':

397282 arm_ni_0_cd_0/type=6/

0.000228680 seconds time elapsed
2. Normal event test and overflow interrupt handler test pass.
~ # perf stat -e arm_ni_0_cd_0/type=4,nodeid=0,eventid=1/
Performance counter stats for 'system wide':

8192 arm_ni_0_cd_0/type=4,nodeid=0,eventid=1/

0.123881102 seconds time elapsed

There is no device after the interface. So I use devmem tool give a
0x80001000
first time. Then trigger the ovsr. See the counter value 0x80000000.
Then
I give 0x80001000 again and exit.

It's very hard to write such code without machine with NI component.
Could we merge our code and push it into community together?

drivers/perf/arm-ni.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/perf/arm-ni.c b/drivers/perf/arm-ni.c
index 4a769a421c81..d14ebcbeb598 100644
--- a/drivers/perf/arm-ni.c
+++ b/drivers/perf/arm-ni.c
@@ -171,7 +171,7 @@ static umode_t arm_ni_event_attr_is_visible(struct
kobject *kobj,
eattr = container_of(attr, typeof(*eattr), attr.attr);

cd_for_each_unit(cd, unit) {
- if (unit->type == eattr->type && unit->ns)
+ if (unit->type == eattr->type && (unit->ns | unit->type
== NI_PMU))
return attr->mode;
}

@@ -592,7 +592,7 @@ static int arm_ni_probe(struct platform_device *pdev)
for (int v = 0; v < cfg.num_components; v++) {
reg = readl_relaxed(cfg.base + NI_CHILD_PTR(v));
arm_ni_probe_domain(base + reg, &vd);
- for (int p = 0; p < vd.num_components; v++) {
+ for (int p = 0; p < vd.num_components; p++) {
reg = readl_relaxed(vd.base + NI_CHILD_PTR(p));
arm_ni_probe_domain(base + reg, &pd);
num_cds += pd.num_components;
@@ -612,13 +612,13 @@ static int arm_ni_probe(struct platform_device *pdev)
for (int v = 0; v < cfg.num_components; v++) {
reg = readl_relaxed(cfg.base + NI_CHILD_PTR(v));
arm_ni_probe_domain(base + reg, &vd);
- for (int p = 0; p < pd.num_components; v++) {
+ for (int p = 0; p < pd.num_components; p++) {
reg = readl_relaxed(vd.base + NI_CHILD_PTR(p));
arm_ni_probe_domain(base + reg, &pd);
for (int c = 0; c < vd.num_components; c++) {
int ret;

- reg = readl_relaxed(vd.base +
NI_CHILD_PTR(c));
+ reg = readl_relaxed(pd.base +
NI_CHILD_PTR(c));
arm_ni_probe_domain(base + reg, &cd);
ret = arm_ni_init_cd(ni, &cd);
if (ret)
--
2.27.0