From nobody Sat Feb 7 17:55:34 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 8D0DD194C7E; Wed, 10 Jul 2024 16:09:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720627789; cv=none; b=KVYXGiKjt+sgUQsDx9OBF2CHDNzsLGvHDdKfGxvVOHe57IAo/wTsKKQPvLBsRsbSOSeRrYdEehp2zpOsiIJrdJUmS2GZ+V5E+Ezhl0ERK5JKt3njf6LOa6pmyPLOi8+RbR19Fu/IsqIDW3LoUE7HaTtx5cb7zEY5MhdOXmtR0fg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720627789; c=relaxed/simple; bh=/k3BZTILZPQfWSq8N8whojUGz/Qyk9P58hWymAT7c6M=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=ut9G0/gKNMUx9xxagnJ/5HIsz0FmlXaB8JxekAGptqFvHEC2nDvnwwQ/LkPm5GvRrAAcsRDJZz8rYqOUeXnXzD77oNfWHynYPO5/y5gTh0ApDFBRLQXyYTpxG2bKARBKJXNc7lWr9CCqHz1ZdBAXi73hEIq8A/jiwB/KF2JAoy8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 17EEB106F; Wed, 10 Jul 2024 09:10:10 -0700 (PDT) Received: from e121345-lin.cambridge.arm.com (e121345-lin.cambridge.arm.com [10.1.196.40]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 022563F762; Wed, 10 Jul 2024 09:09:43 -0700 (PDT) From: Robin Murphy To: will@kernel.org Cc: mark.rutland@arm.com, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, jialong.yang@shingroup.cn, devicetree@vger.kernel.org Subject: [PATCH v2 1/3] dt-bindings/perf: Add Arm NI-700 PMU Date: Wed, 10 Jul 2024 17:09:33 +0100 Message-Id: X-Mailer: git-send-email 2.39.2.101.g768bb238c484.dirty In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add an initial binding for the Arm NI-700 interconnect PMU. As with the Arm CMN family, there are already future NI products on the roadmap, so the overall binding is named generically just in case any non-discoverable incompatibility between generations crops up. Cc: Signed-off-by: Robin Murphy Reviewed-by: Rob Herring (Arm) --- v2: No change .../devicetree/bindings/perf/arm,ni.yaml | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Documentation/devicetree/bindings/perf/arm,ni.yaml diff --git a/Documentation/devicetree/bindings/perf/arm,ni.yaml b/Documenta= tion/devicetree/bindings/perf/arm,ni.yaml new file mode 100644 index 000000000000..d66fffa256d5 --- /dev/null +++ b/Documentation/devicetree/bindings/perf/arm,ni.yaml @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/perf/arm,ni.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Arm NI (Network-on-Chip Interconnect) Performance Monitors + +maintainers: + - Robin Murphy + +properties: + compatible: + const: arm,ni-700 + + reg: + items: + - description: Complete configuration register space + + interrupts: + minItems: 1 + maxItems: 32 + description: Overflow interrupts, one per clock domain, in order of do= main ID + +required: + - compatible + - reg + - interrupts + +additionalProperties: false --=20 2.39.2.101.g768bb238c484.dirty From nobody Sat Feb 7 17:55:34 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 755A0194C94 for ; Wed, 10 Jul 2024 16:09:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720627790; cv=none; b=ECvbXFWwuNyuMCRlfotUL5m42WNfnyOkVeYgm32l16+0UoZG2MU3vnPwxY3M9CmEvNF27UTMlWWk6aTUeJJNIE8KfsRSM2s0Sd1LlC/uLXanb7puGOSWAiSszPharl+Lhq6kTrzE/TNhapYgg+gq4qFp+8Zr5L2kWZTtBT2gdjY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720627790; c=relaxed/simple; bh=F/lSJHflYrDilWtnyl+4WBy/QOlm+ZY+ZR62nTLHwLI=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=NslmxJaOXjgWmmT6d06bc6aTtCEpWutvMK7vCZWYUEM4IaRy7VKWLNVmwiEzhRSOC+CNZjSkrpil/LsPjYitvssn1Ui8knhfNxGCm6otBZtoqaAozS/TEvnPQOBrvMz8fMuaPyvQQ9J+puCfoxppHMHpBuKcYEt42q5/dSPtNG0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 1B528113E; Wed, 10 Jul 2024 09:10:11 -0700 (PDT) Received: from e121345-lin.cambridge.arm.com (e121345-lin.cambridge.arm.com [10.1.196.40]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 13F7C3F762; Wed, 10 Jul 2024 09:09:44 -0700 (PDT) From: Robin Murphy To: will@kernel.org Cc: mark.rutland@arm.com, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, jialong.yang@shingroup.cn Subject: [PATCH v2 2/3] perf: Add driver for Arm NI-700 interconnect PMU Date: Wed, 10 Jul 2024 17:09:34 +0100 Message-Id: X-Mailer: git-send-email 2.39.2.101.g768bb238c484.dirty In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The Arm NI-700 Network-on-Chip Interconnect has a relatively straightforward design with a hierarchy of voltage, power, and clock domains, where each clock domain then contains a number of interface units and a PMU which can monitor events thereon. As such, it begets a relatively straightforward driver to interface those PMUs with perf. Even more so than with arm-cmn, users will require detailed knowledge of the wider system topology in order to meaningfully analyse anything, since the interconnect itself cannot know what lies beyond the boundary of each inscrutably-numbered interface. Given that, for now they are also expected to refer to the NI-700 documentation for the relevant event IDs to provide as well. An identifier is implemented so we can come back and add jevents if anyone really wants to. Signed-off-by: Robin Murphy --- v2: - Add basic usage documentation - Use __counted_by attribute - Make group validation logic clearer (and drop PMU type check which perf_event_open() already takes care of) - Add retry limit to arm_ni_read_ccnt() --- Documentation/admin-guide/perf/arm-ni.rst | 17 + Documentation/admin-guide/perf/index.rst | 1 + drivers/perf/Kconfig | 7 + drivers/perf/Makefile | 1 + drivers/perf/arm-ni.c | 778 ++++++++++++++++++++++ 5 files changed, 804 insertions(+) create mode 100644 Documentation/admin-guide/perf/arm-ni.rst create mode 100644 drivers/perf/arm-ni.c diff --git a/Documentation/admin-guide/perf/arm-ni.rst b/Documentation/admi= n-guide/perf/arm-ni.rst new file mode 100644 index 000000000000..3cd7d0f75f0f --- /dev/null +++ b/Documentation/admin-guide/perf/arm-ni.rst @@ -0,0 +1,17 @@ +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Arm Network-on Chip Interconnect PMU +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +NI-700 and friends implement a distinct PMU for each clock domain within t= he +interconnect. Correspondingly, the driver exposes multiple PMU devices nam= ed +arm_ni__cd_, where is an (abritrary) instance identifier and = is +the clock domain ID within that particular instance. If multiple NI instan= ces +exist within a system, the PMU devices can be correlated with the underlyi= ng +hardware instance via sysfs parentage. + +Each PMU exposes base event aliases for the interface types present in its= clock +domain. These require qualifying with the "eventid" and "nodeid" parameters +to specify the event code to count and the interface at which to count it +(per the configured hardware ID as reflected in the xxNI_NODE_INFO registe= r). +The exception is the "cycles" alias for the PMU cycle counter, which is en= coded +with the PMU node type and needs no further qualification. diff --git a/Documentation/admin-guide/perf/index.rst b/Documentation/admin= -guide/perf/index.rst index 7eb3dcd6f4da..8502bc174640 100644 --- a/Documentation/admin-guide/perf/index.rst +++ b/Documentation/admin-guide/perf/index.rst @@ -16,6 +16,7 @@ Performance monitor support starfive_starlink_pmu arm-ccn arm-cmn + arm-ni xgene-pmu arm_dsu_pmu thunderx2-pmu diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig index 7526a9e714fa..adc9365b041b 100644 --- a/drivers/perf/Kconfig +++ b/drivers/perf/Kconfig @@ -48,6 +48,13 @@ config ARM_CMN Support for PMU events monitoring on the Arm CMN-600 Coherent Mesh Network interconnect. =20 +config ARM_NI + tristate "Arm NI-700 PMU support" + depends on ARM64 || COMPILE_TEST + help + Support for PMU events monitoring on the Arm NI-700 Network-on-Chip + interconnect and family. + config ARM_PMU depends on ARM || ARM64 bool "ARM PMU framework" diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile index 29b1c28203ef..d21ed2896bc7 100644 --- a/drivers/perf/Makefile +++ b/drivers/perf/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_ARM_CCI_PMU) +=3D arm-cci.o obj-$(CONFIG_ARM_CCN) +=3D arm-ccn.o obj-$(CONFIG_ARM_CMN) +=3D arm-cmn.o obj-$(CONFIG_ARM_DSU_PMU) +=3D arm_dsu_pmu.o +obj-$(CONFIG_ARM_NI) +=3D arm-ni.o obj-$(CONFIG_ARM_PMU) +=3D arm_pmu.o arm_pmu_platform.o obj-$(CONFIG_ARM_PMU_ACPI) +=3D arm_pmu_acpi.o obj-$(CONFIG_ARM_PMUV3) +=3D arm_pmuv3.o diff --git a/drivers/perf/arm-ni.c b/drivers/perf/arm-ni.c new file mode 100644 index 000000000000..1f7b7a2eaae1 --- /dev/null +++ b/drivers/perf/arm-ni.c @@ -0,0 +1,778 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2022-2024 Arm Limited +// NI-700 Network-on-Chip PMU driver + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Common registers */ +#define NI_NODE_TYPE 0x000 +#define NI_NODE_TYPE_NODE_ID GENMASK(31, 16) +#define NI_NODE_TYPE_NODE_TYPE GENMASK(15, 0) + +#define NI_CHILD_NODE_INFO 0x004 +#define NI_CHILD_PTR(n) (0x008 + (n) * 4) + +#define NI700_PMUSELA 0x00c + +/* Config node */ +#define NI_PERIPHERAL_ID0 0xfe0 +#define NI_PIDR0_PART_7_0 GENMASK(7, 0) +#define NI_PERIPHERAL_ID1 0xfe4 +#define NI_PIDR1_PART_11_8 GENMASK(3, 0) +#define NI_PERIPHERAL_ID2 0xfe8 +#define NI_PIDR2_VERSION GENMASK(7, 4) + +/* PMU node */ +#define NI_PMEVCNTR(n) (0x008 + (n) * 8) +#define NI_PMCCNTR_L 0x0f8 +#define NI_PMCCNTR_U 0x0fc +#define NI_PMEVTYPER(n) (0x400 + (n) * 4) +#define NI_PMEVTYPER_NODE_TYPE GENMASK(12, 9) +#define NI_PMEVTYPER_NODE_ID GENMASK(8, 0) +#define NI_PMCNTENSET 0xc00 +#define NI_PMCNTENCLR 0xc20 +#define NI_PMINTENSET 0xc40 +#define NI_PMINTENCLR 0xc60 +#define NI_PMOVSCLR 0xc80 +#define NI_PMOVSSET 0xcc0 +#define NI_PMCFGR 0xe00 +#define NI_PMCR 0xe04 +#define NI_PMCR_RESET_CCNT BIT(2) +#define NI_PMCR_RESET_EVCNT BIT(1) +#define NI_PMCR_ENABLE BIT(0) + +#define NI_NUM_COUNTERS 8 +#define NI_CCNT_IDX 31 + +/* Event attributes */ +#define NI_CONFIG_TYPE GENMASK_ULL(15, 0) +#define NI_CONFIG_NODEID GENMASK_ULL(31, 16) +#define NI_CONFIG_EVENTID GENMASK_ULL(47, 32) + +#define NI_EVENT_TYPE(event) FIELD_GET(NI_CONFIG_TYPE, (event)->attr.confi= g) +#define NI_EVENT_NODEID(event) FIELD_GET(NI_CONFIG_NODEID, (event)->attr.c= onfig) +#define NI_EVENT_EVENTID(event) FIELD_GET(NI_CONFIG_EVENTID, (event)->attr= .config) + +enum ni_part { + PART_NI_700 =3D 0x43b, + PART_NI_710AE =3D 0x43d, +}; + +enum ni_node_type { + NI_GLOBAL, + NI_VOLTAGE, + NI_POWER, + NI_CLOCK, + NI_ASNI, + NI_AMNI, + NI_PMU, + NI_HSNI, + NI_HMNI, + NI_PMNI, +}; + +struct arm_ni_node { + void __iomem *base; + enum ni_node_type type; + u16 id; + u32 num_components; +}; + +struct arm_ni_unit { + void __iomem *pmusela; + enum ni_node_type type; + u16 id; + bool ns; + union { + __le64 pmusel; + u8 event[8]; + }; +}; + +struct arm_ni_cd { + void __iomem *pmu_base; + u16 id; + int num_units; + int irq; + int cpu; + struct hlist_node cpuhp_node; + struct pmu pmu; + struct arm_ni_unit *units; + struct perf_event *evcnt[NI_NUM_COUNTERS]; + struct perf_event *ccnt; +}; + +struct arm_ni { + struct device *dev; + void __iomem *base; + enum ni_part part; + int id; + int num_cds; + struct arm_ni_cd cds[] __counted_by(num_cds); +}; + +#define cd_to_ni(cd) container_of((cd), struct arm_ni, cds[(cd)->id]) +#define pmu_to_cd(p) container_of((p), struct arm_ni_cd, pmu) + +#define cd_for_each_unit(cd, u) \ + for (struct arm_ni_unit *u =3D cd->units; u < cd->units + cd->num_units; = u++) + +static int arm_ni_hp_state; + +struct arm_ni_event_attr { + struct device_attribute attr; + enum ni_node_type type; +}; + +#define NI_EVENT_ATTR(_name, _type) \ + (&((struct arm_ni_event_attr[]) {{ \ + .attr =3D __ATTR(_name, 0444, arm_ni_event_show, NULL), \ + .type =3D _type, \ + }})[0].attr.attr) + +static ssize_t arm_ni_event_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct arm_ni_event_attr *eattr =3D container_of(attr, typeof(*eattr), at= tr); + + if (eattr->type =3D=3D NI_PMU) + return sysfs_emit(buf, "type=3D0x%x\n", eattr->type); + + return sysfs_emit(buf, "type=3D0x%x,eventid=3D?,nodeid=3D?\n", eattr->typ= e); +} + +static umode_t arm_ni_event_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int unused) +{ + struct device *dev =3D kobj_to_dev(kobj); + struct arm_ni_cd *cd =3D pmu_to_cd(dev_get_drvdata(dev)); + struct arm_ni_event_attr *eattr; + + eattr =3D container_of(attr, typeof(*eattr), attr.attr); + + cd_for_each_unit(cd, unit) { + if (unit->type =3D=3D eattr->type && unit->ns) + return attr->mode; + } + + return 0; +} + +static struct attribute *arm_ni_event_attrs[] =3D { + NI_EVENT_ATTR(asni, NI_ASNI), + NI_EVENT_ATTR(amni, NI_AMNI), + NI_EVENT_ATTR(cycles, NI_PMU), + NI_EVENT_ATTR(hsni, NI_HSNI), + NI_EVENT_ATTR(hmni, NI_HMNI), + NI_EVENT_ATTR(pmni, NI_PMNI), + NULL +}; + +static const struct attribute_group arm_ni_event_attrs_group =3D { + .name =3D "events", + .attrs =3D arm_ni_event_attrs, + .is_visible =3D arm_ni_event_attr_is_visible, +}; + +struct arm_ni_format_attr { + struct device_attribute attr; + u64 field; +}; + +#define NI_FORMAT_ATTR(_name, _fld) \ + (&((struct arm_ni_format_attr[]) {{ \ + .attr =3D __ATTR(_name, 0444, arm_ni_format_show, NULL), \ + .field =3D _fld, \ + }})[0].attr.attr) + +static ssize_t arm_ni_format_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct arm_ni_format_attr *fmt =3D container_of(attr, typeof(*fmt), attr); + int lo =3D __ffs(fmt->field), hi =3D __fls(fmt->field); + + return sysfs_emit(buf, "config:%d-%d\n", lo, hi); +} + +static struct attribute *arm_ni_format_attrs[] =3D { + NI_FORMAT_ATTR(type, NI_CONFIG_TYPE), + NI_FORMAT_ATTR(nodeid, NI_CONFIG_NODEID), + NI_FORMAT_ATTR(eventid, NI_CONFIG_EVENTID), + NULL +}; + +static const struct attribute_group arm_ni_format_attrs_group =3D { + .name =3D "format", + .attrs =3D arm_ni_format_attrs, +}; + +static ssize_t arm_ni_cpumask_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct arm_ni_cd *cd =3D pmu_to_cd(dev_get_drvdata(dev)); + + return cpumap_print_to_pagebuf(true, buf, cpumask_of(cd->cpu)); +} + +static struct device_attribute arm_ni_cpumask_attr =3D + __ATTR(cpumask, 0444, arm_ni_cpumask_show, NULL); + +static ssize_t arm_ni_identifier_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct arm_ni *ni =3D cd_to_ni(pmu_to_cd(dev_get_drvdata(dev))); + u32 reg =3D readl_relaxed(ni->base + NI_PERIPHERAL_ID2); + int version =3D FIELD_GET(NI_PIDR2_VERSION, reg); + + return sysfs_emit(buf, "%03x%02x\n", ni->part, version); +} + +static struct device_attribute arm_ni_identifier_attr =3D + __ATTR(identifier, 0444, arm_ni_identifier_show, NULL); + +static struct attribute *arm_ni_other_attrs[] =3D { + &arm_ni_cpumask_attr.attr, + &arm_ni_identifier_attr.attr, + NULL +}; + +static const struct attribute_group arm_ni_other_attr_group =3D { + .attrs =3D arm_ni_other_attrs, + NULL +}; + +static const struct attribute_group *arm_ni_attr_groups[] =3D { + &arm_ni_event_attrs_group, + &arm_ni_format_attrs_group, + &arm_ni_other_attr_group, + NULL +}; + +static void arm_ni_pmu_enable(struct pmu *pmu) +{ + writel_relaxed(NI_PMCR_ENABLE, pmu_to_cd(pmu)->pmu_base + NI_PMCR); +} + +static void arm_ni_pmu_disable(struct pmu *pmu) +{ + writel_relaxed(0, pmu_to_cd(pmu)->pmu_base + NI_PMCR); +} + +struct arm_ni_val { + unsigned int evcnt; + unsigned int ccnt; +}; + +static bool arm_ni_validate_event(struct perf_event *evt, struct arm_ni_va= l *val) +{ + if (is_software_event(evt)) + return true; + + if (NI_EVENT_TYPE(evt) =3D=3D NI_PMU) { + val->ccnt++; + return val->ccnt <=3D 1; + } + + val->evcnt++; + return val->evcnt <=3D NI_NUM_COUNTERS; +} + +static int arm_ni_validate_group(struct perf_event *event) +{ + struct perf_event *sibling, *leader; + struct arm_ni_val val =3D { 0 }; + + arm_ni_validate_event(event, &val); + + leader =3D event->group_leader; + if (leader !=3D event && !arm_ni_validate_event(leader, &val)) + return - EINVAL; + + for_each_sibling_event(sibling, leader) { + if (!arm_ni_validate_event(sibling, &val)) + return - EINVAL; + } + return 0; +} + +static int arm_ni_event_init(struct perf_event *event) +{ + struct arm_ni_cd *cd =3D pmu_to_cd(event->pmu); + + if (event->attr.type !=3D event->pmu->type) + return -ENOENT; + + if (is_sampling_event(event)) + return -EINVAL; + + event->cpu =3D cd->cpu; + if (NI_EVENT_TYPE(event) =3D=3D NI_PMU) + return arm_ni_validate_group(event); + + cd_for_each_unit(cd, unit) { + if (unit->type =3D=3D NI_EVENT_TYPE(event) && + unit->id =3D=3D NI_EVENT_NODEID(event) && unit->ns) { + event->hw.config_base =3D (unsigned long)unit; + return arm_ni_validate_group(event); + } + } + return -EINVAL; +} + +static u64 arm_ni_read_ccnt(struct arm_ni_cd *cd) +{ + u64 l, u_old, u_new; + int retries =3D 3; /* 1st time unlucky, 2nd improbable, 3rd just broken */ + + u_new =3D readl_relaxed(cd->pmu_base + NI_PMCCNTR_U); + do { + u_old =3D u_new; + l =3D readl_relaxed(cd->pmu_base + NI_PMCCNTR_L); + u_new =3D readl_relaxed(cd->pmu_base + NI_PMCCNTR_U); + } while (u_new !=3D u_old && --retries); + WARN_ON(!retries); + + return (u_new << 32) | l; +} + +static void arm_ni_event_read(struct perf_event *event) +{ + struct arm_ni_cd *cd =3D pmu_to_cd(event->pmu); + struct hw_perf_event *hw =3D &event->hw; + u64 count, prev; + bool ccnt =3D hw->idx =3D=3D NI_CCNT_IDX; + + do { + prev =3D local64_read(&hw->prev_count); + if (ccnt) + count =3D arm_ni_read_ccnt(cd); + else + count =3D readl_relaxed(cd->pmu_base + NI_PMEVCNTR(hw->idx)); + } while (local64_cmpxchg(&hw->prev_count, prev, count) !=3D prev); + + count -=3D prev; + if (!ccnt) + count =3D (u32)count; + local64_add(count, &event->count); +} + +static void arm_ni_event_start(struct perf_event *event, int flags) +{ + struct arm_ni_cd *cd =3D pmu_to_cd(event->pmu); + + writel_relaxed(1U << event->hw.idx, cd->pmu_base + NI_PMCNTENSET); +} + +static void arm_ni_event_stop(struct perf_event *event, int flags) +{ + struct arm_ni_cd *cd =3D pmu_to_cd(event->pmu); + + writel_relaxed(1U << event->hw.idx, cd->pmu_base + NI_PMCNTENCLR); + if (flags & PERF_EF_UPDATE) + arm_ni_event_read(event); +} + +static void arm_ni_init_ccnt(struct arm_ni_cd *cd) +{ + local64_set(&cd->ccnt->hw.prev_count, S64_MIN); + lo_hi_writeq_relaxed(S64_MIN, cd->pmu_base + NI_PMCCNTR_L); +} + +static void arm_ni_init_evcnt(struct arm_ni_cd *cd, int idx) +{ + local64_set(&cd->evcnt[idx]->hw.prev_count, S32_MIN); + writel_relaxed(S32_MIN, cd->pmu_base + NI_PMEVCNTR(idx)); +} + +static int arm_ni_event_add(struct perf_event *event, int flags) +{ + struct arm_ni_cd *cd =3D pmu_to_cd(event->pmu); + struct hw_perf_event *hw =3D &event->hw; + struct arm_ni_unit *unit; + enum ni_node_type type =3D NI_EVENT_TYPE(event); + u32 reg; + + if (type =3D=3D NI_PMU) { + if (cd->ccnt) + return -ENOSPC; + hw->idx =3D NI_CCNT_IDX; + cd->ccnt =3D event; + arm_ni_init_ccnt(cd); + } else { + hw->idx =3D 0; + while (cd->evcnt[hw->idx]) { + if (++hw->idx =3D=3D NI_NUM_COUNTERS) + return -ENOSPC; + } + cd->evcnt[hw->idx] =3D event; + unit =3D (void *)hw->config_base; + unit->event[hw->idx] =3D NI_EVENT_EVENTID(event); + arm_ni_init_evcnt(cd, hw->idx); + lo_hi_writeq_relaxed(le64_to_cpu(unit->pmusel), unit->pmusela); + + reg =3D FIELD_PREP(NI_PMEVTYPER_NODE_TYPE, type) | + FIELD_PREP(NI_PMEVTYPER_NODE_ID, NI_EVENT_NODEID(event)); + writel_relaxed(reg, cd->pmu_base + NI_PMEVTYPER(hw->idx)); + } + if (flags & PERF_EF_START) + arm_ni_event_start(event, 0); + return 0; +} + +static void arm_ni_event_del(struct perf_event *event, int flags) +{ + struct arm_ni_cd *cd =3D pmu_to_cd(event->pmu); + struct hw_perf_event *hw =3D &event->hw; + + arm_ni_event_stop(event, PERF_EF_UPDATE); + + if (hw->idx =3D=3D NI_CCNT_IDX) + cd->ccnt =3D NULL; + else + cd->evcnt[hw->idx] =3D NULL; +} + +static irqreturn_t arm_ni_handle_irq(int irq, void *dev_id) +{ + struct arm_ni_cd *cd =3D dev_id; + irqreturn_t ret =3D IRQ_NONE; + u32 reg =3D readl_relaxed(cd->pmu_base + NI_PMOVSCLR); + + if (reg & (1U << NI_CCNT_IDX)) { + ret =3D IRQ_HANDLED; + if (!(WARN_ON(!cd->ccnt))) { + arm_ni_event_read(cd->ccnt); + arm_ni_init_ccnt(cd); + } + } + for (int i =3D 0; i < NI_NUM_COUNTERS; i++) { + if (!(reg & (1U << i))) + continue; + ret =3D IRQ_HANDLED; + if (!(WARN_ON(!cd->evcnt[i]))) { + arm_ni_event_read(cd->evcnt[i]); + arm_ni_init_evcnt(cd, i); + } + } + writel_relaxed(reg, cd->pmu_base + NI_PMOVSCLR); + return ret; +} + +static int arm_ni_init_cd(struct arm_ni *ni, struct arm_ni_node *node, u64= res_start) +{ + struct arm_ni_cd *cd =3D ni->cds + node->id; + const char *name; + int err; + + cd->id =3D node->id; + cd->num_units =3D node->num_components; + cd->units =3D devm_kcalloc(ni->dev, cd->num_units, sizeof(*(cd->units)), = GFP_KERNEL); + if (!cd->units) + return -ENOMEM; + + for (int i =3D 0; i < cd->num_units; i++) { + u32 reg =3D readl_relaxed(node->base + NI_CHILD_PTR(i)); + void __iomem *unit_base =3D ni->base + reg; + struct arm_ni_unit *unit =3D cd->units + i; + + reg =3D readl_relaxed(unit_base + NI_NODE_TYPE); + unit->type =3D FIELD_GET(NI_NODE_TYPE_NODE_TYPE, reg); + unit->id =3D FIELD_GET(NI_NODE_TYPE_NODE_ID, reg); + + switch (unit->type) { + case NI_PMU: + reg =3D readl_relaxed(unit_base + NI_PMCFGR); + if (!reg) { + dev_info(ni->dev, "No access to PMU %d\n", cd->id); + devm_kfree(ni->dev, cd->units); + return 0; + } + unit->ns =3D true; + cd->pmu_base =3D unit_base; + break; + case NI_ASNI: + case NI_AMNI: + case NI_HSNI: + case NI_HMNI: + case NI_PMNI: + unit->pmusela =3D unit_base + NI700_PMUSELA; + writel_relaxed(1, unit->pmusela); + if (readl_relaxed(unit->pmusela) !=3D 1) + dev_info(ni->dev, "No access to node 0x%04x%04x\n", unit->id, unit->ty= pe); + else + unit->ns =3D true; + break; + default: + /* + * e.g. FMU - thankfully bits 3:2 of FMU_ERR_FR0 are RES0 so + * can't alias any of the leaf node types we're looking for. + */ + dev_dbg(ni->dev, "Mystery node 0x%04x%04x\n", unit->id, unit->type); + break; + } + } + + res_start +=3D cd->pmu_base - ni->base; + if (!devm_request_mem_region(ni->dev, res_start, SZ_4K, dev_name(ni->dev)= )) { + dev_err(ni->dev, "Failed to request PMU region 0x%llx\n", res_start); + return -EBUSY; + } + + writel_relaxed(NI_PMCR_RESET_CCNT | NI_PMCR_RESET_EVCNT, + cd->pmu_base + NI_PMCR); + writel_relaxed(U32_MAX, cd->pmu_base + NI_PMCNTENCLR); + writel_relaxed(U32_MAX, cd->pmu_base + NI_PMOVSCLR); + writel_relaxed(U32_MAX, cd->pmu_base + NI_PMINTENSET); + + cd->irq =3D platform_get_irq(to_platform_device(ni->dev), cd->id); + if (cd->irq < 0) + return cd->irq; + + err =3D devm_request_irq(ni->dev, cd->irq, arm_ni_handle_irq, + IRQF_NOBALANCING | IRQF_NO_THREAD, + dev_name(ni->dev), cd); + if (err) + return err; + + cd->cpu =3D cpumask_local_spread(0, dev_to_node(ni->dev)); + cd->pmu =3D (struct pmu) { + .module =3D THIS_MODULE, + .parent =3D ni->dev, + .attr_groups =3D arm_ni_attr_groups, + .capabilities =3D PERF_PMU_CAP_NO_EXCLUDE, + .task_ctx_nr =3D perf_invalid_context, + .pmu_enable =3D arm_ni_pmu_enable, + .pmu_disable =3D arm_ni_pmu_disable, + .event_init =3D arm_ni_event_init, + .add =3D arm_ni_event_add, + .del =3D arm_ni_event_del, + .start =3D arm_ni_event_start, + .stop =3D arm_ni_event_stop, + .read =3D arm_ni_event_read, + }; + + name =3D devm_kasprintf(ni->dev, GFP_KERNEL, "arm_ni_%d_cd_%d", ni->id, c= d->id); + if (!name) + return -ENOMEM; + + err =3D cpuhp_state_add_instance(arm_ni_hp_state, &cd->cpuhp_node); + if (err) + return err; + + return perf_pmu_register(&cd->pmu, name, -1); +} + +static void arm_ni_probe_domain(void __iomem *base, struct arm_ni_node *no= de) +{ + u32 reg =3D readl_relaxed(base + NI_NODE_TYPE); + + node->base =3D base; + node->type =3D FIELD_GET(NI_NODE_TYPE_NODE_TYPE, reg); + node->id =3D FIELD_GET(NI_NODE_TYPE_NODE_ID, reg); + node->num_components =3D readl_relaxed(base + NI_CHILD_NODE_INFO); +} + +static int arm_ni_probe(struct platform_device *pdev) +{ + struct arm_ni_node cfg, vd, pd, cd; + struct arm_ni *ni; + struct resource *res; + void __iomem *base; + static atomic_t id; + int num_cds; + u32 reg, part; + + /* + * We want to map the whole configuration space for ease of discovery, + * but the PMU pages are the only ones for which we can honestly claim + * exclusive ownership, so we'll request them explicitly once found. + */ + res =3D platform_get_resource(pdev, IORESOURCE_MEM, 0); + base =3D devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (IS_ERR(base)) + return PTR_ERR(base); + + arm_ni_probe_domain(base, &cfg); + if (cfg.type !=3D NI_GLOBAL) + return -ENODEV; + + reg =3D readl_relaxed(cfg.base + NI_PERIPHERAL_ID0); + part =3D FIELD_GET(NI_PIDR0_PART_7_0, reg); + reg =3D readl_relaxed(cfg.base + NI_PERIPHERAL_ID1); + part |=3D FIELD_GET(NI_PIDR1_PART_11_8, reg) << 8; + + switch (part) { + case PART_NI_700: + case PART_NI_710AE: + break; + default: + dev_WARN(&pdev->dev, "Unknown part number: 0x%03x, this may go badly\n",= part); + break; + } + + num_cds =3D 0; + for (int v =3D 0; v < cfg.num_components; v++) { + reg =3D readl_relaxed(cfg.base + NI_CHILD_PTR(v)); + arm_ni_probe_domain(base + reg, &vd); + for (int p =3D 0; p < vd.num_components; p++) { + reg =3D readl_relaxed(vd.base + NI_CHILD_PTR(p)); + arm_ni_probe_domain(base + reg, &pd); + num_cds +=3D pd.num_components; + } + } + + ni =3D devm_kzalloc(&pdev->dev, struct_size(ni, cds, num_cds), GFP_KERNEL= ); + if (!ni) + return -ENOMEM; + + ni->dev =3D &pdev->dev; + ni->base =3D base; + ni->num_cds =3D num_cds; + ni->part =3D part; + ni->id =3D atomic_fetch_inc(&id); + + for (int v =3D 0; v < cfg.num_components; v++) { + reg =3D readl_relaxed(cfg.base + NI_CHILD_PTR(v)); + arm_ni_probe_domain(base + reg, &vd); + for (int p =3D 0; p < vd.num_components; p++) { + reg =3D readl_relaxed(vd.base + NI_CHILD_PTR(p)); + arm_ni_probe_domain(base + reg, &pd); + for (int c =3D 0; c < pd.num_components; c++) { + int ret; + + reg =3D readl_relaxed(pd.base + NI_CHILD_PTR(c)); + arm_ni_probe_domain(base + reg, &cd); + ret =3D arm_ni_init_cd(ni, &cd, res->start); + if (ret) + return ret; + } + } + } + + return 0; +} + +static int arm_ni_remove(struct platform_device *pdev) +{ + struct arm_ni *ni =3D platform_get_drvdata(pdev); + + for (int i =3D 0; i < ni->num_cds; i++) { + struct arm_ni_cd *cd =3D ni->cds + i; + + if (!cd->pmu_base) + continue; + + writel_relaxed(0, cd->pmu_base + NI_PMCR); + writel_relaxed(U32_MAX, cd->pmu_base + NI_PMINTENCLR); + perf_pmu_unregister(&cd->pmu); + cpuhp_state_remove_instance_nocalls(arm_ni_hp_state, &cd->cpuhp_node); + } + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id arm_ni_of_match[] =3D { + { .compatible =3D "arm,ni-700" }, + {} +}; +MODULE_DEVICE_TABLE(of, arm_ni_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id arm_ni_acpi_match[] =3D { + { "ARMHCB70" }, + {} +}; +MODULE_DEVICE_TABLE(acpi, arm_ni_acpi_match); +#endif + +static struct platform_driver arm_ni_driver =3D { + .driver =3D { + .name =3D "arm-ni", + .of_match_table =3D of_match_ptr(arm_ni_of_match), + .acpi_match_table =3D ACPI_PTR(arm_ni_acpi_match), + }, + .probe =3D arm_ni_probe, + .remove =3D arm_ni_remove, +}; + +static void arm_ni_pmu_migrate(struct arm_ni_cd *cd, unsigned int cpu) +{ + perf_pmu_migrate_context(&cd->pmu, cd->cpu, cpu); + irq_set_affinity(cd->irq, cpumask_of(cpu)); + cd->cpu =3D cpu; +} + +static int arm_ni_pmu_online_cpu(unsigned int cpu, struct hlist_node *cpuh= p_node) +{ + struct arm_ni_cd *cd; + int node; + + cd =3D hlist_entry_safe(cpuhp_node, struct arm_ni_cd, cpuhp_node); + node =3D dev_to_node(cd_to_ni(cd)->dev); + if (node !=3D NUMA_NO_NODE && cpu_to_node(cd->cpu) !=3D node && cpu_to_no= de(cpu) =3D=3D node) + arm_ni_pmu_migrate(cd, cpu); + return 0; +} + +static int arm_ni_pmu_offline_cpu(unsigned int cpu, struct hlist_node *cpu= hp_node) +{ + struct arm_ni_cd *cd; + unsigned int target; + int node; + + cd =3D hlist_entry_safe(cpuhp_node, struct arm_ni_cd, cpuhp_node); + if (cpu !=3D cd->cpu) + return 0; + + node =3D dev_to_node(cd_to_ni(cd)->dev); + target =3D cpumask_any_and_but(cpumask_of_node(node), cpu_online_mask, cp= u); + if (target >=3D nr_cpu_ids) + target =3D cpumask_any_but(cpu_online_mask, cpu); + + if (target < nr_cpu_ids) + arm_ni_pmu_migrate(cd, target); + return 0; +} + +static int __init arm_ni_init(void) +{ + int ret; + + ret =3D cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, + "perf/arm/ni:online", + arm_ni_pmu_online_cpu, + arm_ni_pmu_offline_cpu); + if (ret < 0) + return ret; + + arm_ni_hp_state =3D ret; + + ret =3D platform_driver_register(&arm_ni_driver); + if (ret) + cpuhp_remove_multi_state(arm_ni_hp_state); + return ret; +} + +static void __exit arm_ni_exit(void) +{ + platform_driver_unregister(&arm_ni_driver); + cpuhp_remove_multi_state(arm_ni_hp_state); +} + +module_init(arm_ni_init); +module_exit(arm_ni_exit); + +MODULE_AUTHOR("Robin Murphy "); +MODULE_DESCRIPTION("Arm NI-700 PMU driver"); +MODULE_LICENSE("GPL v2"); --=20 2.39.2.101.g768bb238c484.dirty From nobody Sat Feb 7 17:55:34 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 890D4197A6A for ; Wed, 10 Jul 2024 16:09:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720627789; cv=none; b=pu5moKyaqovvuPtes3UhZopPbmSM3ThbYAUro99NlNmzxQCgnfE8rz1idxoDLgg2u/2onB6DN+dq3gkPPn/6bAw3MwkFjSmiih9XVJyV0qKBDEweOdXNUU+s25gpmUUjj2fg99qvcxiXQA65Vpd5FZOYAQ9dgLSOolry8w+mCkE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720627789; c=relaxed/simple; bh=+pRCWtu1oW2Aq+fFY6HXVsvpcuDlSczUz3gkpKQ6SSw=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=li4KphUP+iVtLUUFTN+cUrJb3VuILDTPEJ0Bl/SDxjBiyQlP60Z3bzoLPnWVTxVBqVa8OItbwkAvw/5vpUnGv5QSXMDLkiyxl+ihyn72h+0+IuzZNv4/kaKoObVnO0lrwwFREsGtEHxUTC283nelkVgtTTy6OzTg/EbJwlrKHkU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 22B2B367; Wed, 10 Jul 2024 09:10:13 -0700 (PDT) Received: from e121345-lin.cambridge.arm.com (e121345-lin.cambridge.arm.com [10.1.196.40]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id EC92E3F762; Wed, 10 Jul 2024 09:09:46 -0700 (PDT) From: Robin Murphy To: will@kernel.org Cc: mark.rutland@arm.com, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, jialong.yang@shingroup.cn, Ilkka Koskinen , Jing Zhang Subject: [PATCH v2 3/3] MAINTAINERS: List Arm interconnect PMUs as supported Date: Wed, 10 Jul 2024 17:09:35 +0100 Message-Id: <69e234440d40c236f80a60308fb4fa527cba93b3.1720625639.git.robin.murphy@arm.com> X-Mailer: git-send-email 2.39.2.101.g768bb238c484.dirty In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Whatever I may or may not have hoped for, looking after these drivers seems to have firmly stuck as one of the responsibilities of the job Arm pays me for, and I would still like to be aware of any other patches, so make it official. CC: Ilkka Koskinen CC: Jing Zhang Signed-off-by: Robin Murphy --- v2: Add new arm-ni.rst path MAINTAINERS | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index cf9c9221c388..60ce17fbed7f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1724,6 +1724,17 @@ F: drivers/mtd/maps/physmap-versatile.* F: drivers/power/reset/arm-versatile-reboot.c F: drivers/soc/versatile/ =20 +ARM INTERCONNECT PMU DRIVERS +M: Robin Murphy +S: Supported +F: Documentation/admin-guide/perf/arm-cmn.rst +F: Documentation/admin-guide/perf/arm-ni.rst +F: Documentation/devicetree/bindings/perf/arm,cmn.yaml +F: Documentation/devicetree/bindings/perf/arm,ni.yaml +F: drivers/perf/arm-cmn.c +F: drivers/perf/arm-ni.c +F: tools/perf/pmu-events/arch/arm64/arm/cmn/ + ARM KOMEDA DRM-KMS DRIVER M: Liviu Dudau S: Supported --=20 2.39.2.101.g768bb238c484.dirty