forked from mirrors/linux
		
	perf: Add driver for Arm NI-700 interconnect PMU
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 <robin.murphy@arm.com> Link: https://lore.kernel.org/r/9933058d0ab8138c78a61cd6852ea5d5ff48e393.1725470837.git.robin.murphy@arm.com Signed-off-by: Will Deacon <will@kernel.org>
This commit is contained in:
		
							parent
							
								
									abbe74dd10
								
							
						
					
					
						commit
						4d5a7680f2
					
				
					 5 changed files with 807 additions and 0 deletions
				
			
		
							
								
								
									
										17
									
								
								Documentation/admin-guide/perf/arm-ni.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								Documentation/admin-guide/perf/arm-ni.rst
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | ||||||
|  | ==================================== | ||||||
|  | Arm Network-on Chip Interconnect PMU | ||||||
|  | ==================================== | ||||||
|  | 
 | ||||||
|  | NI-700 and friends implement a distinct PMU for each clock domain within the | ||||||
|  | interconnect. Correspondingly, the driver exposes multiple PMU devices named | ||||||
|  | arm_ni_<x>_cd_<y>, where <x> is an (arbitrary) instance identifier and <y> is | ||||||
|  | the clock domain ID within that particular instance. If multiple NI instances | ||||||
|  | exist within a system, the PMU devices can be correlated with the underlying | ||||||
|  | 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 register). | ||||||
|  | The exception is the "cycles" alias for the PMU cycle counter, which is encoded | ||||||
|  | with the PMU node type and needs no further qualification. | ||||||
|  | @ -16,6 +16,7 @@ Performance monitor support | ||||||
|    starfive_starlink_pmu |    starfive_starlink_pmu | ||||||
|    arm-ccn |    arm-ccn | ||||||
|    arm-cmn |    arm-cmn | ||||||
|  |    arm-ni | ||||||
|    xgene-pmu |    xgene-pmu | ||||||
|    arm_dsu_pmu |    arm_dsu_pmu | ||||||
|    thunderx2-pmu |    thunderx2-pmu | ||||||
|  |  | ||||||
|  | @ -48,6 +48,13 @@ config ARM_CMN | ||||||
| 	  Support for PMU events monitoring on the Arm CMN-600 Coherent Mesh | 	  Support for PMU events monitoring on the Arm CMN-600 Coherent Mesh | ||||||
| 	  Network interconnect. | 	  Network interconnect. | ||||||
| 
 | 
 | ||||||
|  | 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 | config ARM_PMU | ||||||
| 	depends on ARM || ARM64 | 	depends on ARM || ARM64 | ||||||
| 	bool "ARM PMU framework" | 	bool "ARM PMU framework" | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ obj-$(CONFIG_ARM_CCI_PMU) += arm-cci.o | ||||||
| obj-$(CONFIG_ARM_CCN) += arm-ccn.o | obj-$(CONFIG_ARM_CCN) += arm-ccn.o | ||||||
| obj-$(CONFIG_ARM_CMN) += arm-cmn.o | obj-$(CONFIG_ARM_CMN) += arm-cmn.o | ||||||
| obj-$(CONFIG_ARM_DSU_PMU) += arm_dsu_pmu.o | obj-$(CONFIG_ARM_DSU_PMU) += arm_dsu_pmu.o | ||||||
|  | obj-$(CONFIG_ARM_NI) += arm-ni.o | ||||||
| obj-$(CONFIG_ARM_PMU) += arm_pmu.o arm_pmu_platform.o | obj-$(CONFIG_ARM_PMU) += arm_pmu.o arm_pmu_platform.o | ||||||
| obj-$(CONFIG_ARM_PMU_ACPI) += arm_pmu_acpi.o | obj-$(CONFIG_ARM_PMU_ACPI) += arm_pmu_acpi.o | ||||||
| obj-$(CONFIG_ARM_PMUV3) += arm_pmuv3.o | obj-$(CONFIG_ARM_PMUV3) += arm_pmuv3.o | ||||||
|  |  | ||||||
							
								
								
									
										781
									
								
								drivers/perf/arm-ni.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										781
									
								
								drivers/perf/arm-ni.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,781 @@ | ||||||
|  | // SPDX-License-Identifier: GPL-2.0
 | ||||||
|  | // Copyright (C) 2022-2024 Arm Limited
 | ||||||
|  | // NI-700 Network-on-Chip PMU driver
 | ||||||
|  | 
 | ||||||
|  | #include <linux/acpi.h> | ||||||
|  | #include <linux/bitfield.h> | ||||||
|  | #include <linux/interrupt.h> | ||||||
|  | #include <linux/io.h> | ||||||
|  | #include <linux/io-64-nonatomic-lo-hi.h> | ||||||
|  | #include <linux/kernel.h> | ||||||
|  | #include <linux/module.h> | ||||||
|  | #include <linux/of.h> | ||||||
|  | #include <linux/perf_event.h> | ||||||
|  | #include <linux/platform_device.h> | ||||||
|  | #include <linux/slab.h> | ||||||
|  | 
 | ||||||
|  | /* 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.config) | ||||||
|  | #define NI_EVENT_NODEID(event)	FIELD_GET(NI_CONFIG_NODEID, (event)->attr.config) | ||||||
|  | #define NI_EVENT_EVENTID(event)	FIELD_GET(NI_CONFIG_EVENTID, (event)->attr.config) | ||||||
|  | 
 | ||||||
|  | enum ni_part { | ||||||
|  | 	PART_NI_700 = 0x43b, | ||||||
|  | 	PART_NI_710AE = 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 = 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 = __ATTR(_name, 0444, arm_ni_event_show, NULL),	\ | ||||||
|  | 		.type = _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 = container_of(attr, typeof(*eattr), attr); | ||||||
|  | 
 | ||||||
|  | 	if (eattr->type == NI_PMU) | ||||||
|  | 		return sysfs_emit(buf, "type=0x%x\n", eattr->type); | ||||||
|  | 
 | ||||||
|  | 	return sysfs_emit(buf, "type=0x%x,eventid=?,nodeid=?\n", eattr->type); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static umode_t arm_ni_event_attr_is_visible(struct kobject *kobj, | ||||||
|  | 					    struct attribute *attr, int unused) | ||||||
|  | { | ||||||
|  | 	struct device *dev = kobj_to_dev(kobj); | ||||||
|  | 	struct arm_ni_cd *cd = pmu_to_cd(dev_get_drvdata(dev)); | ||||||
|  | 	struct arm_ni_event_attr *eattr; | ||||||
|  | 
 | ||||||
|  | 	eattr = container_of(attr, typeof(*eattr), attr.attr); | ||||||
|  | 
 | ||||||
|  | 	cd_for_each_unit(cd, unit) { | ||||||
|  | 		if (unit->type == eattr->type && unit->ns) | ||||||
|  | 			return attr->mode; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static struct attribute *arm_ni_event_attrs[] = { | ||||||
|  | 	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 = { | ||||||
|  | 	.name = "events", | ||||||
|  | 	.attrs = arm_ni_event_attrs, | ||||||
|  | 	.is_visible = 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 = __ATTR(_name, 0444, arm_ni_format_show, NULL),	\ | ||||||
|  | 		.field = _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 = container_of(attr, typeof(*fmt), attr); | ||||||
|  | 
 | ||||||
|  | 	return sysfs_emit(buf, "config:%*pbl\n", 64, &fmt->field); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static struct attribute *arm_ni_format_attrs[] = { | ||||||
|  | 	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 = { | ||||||
|  | 	.name = "format", | ||||||
|  | 	.attrs = 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 = 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 = | ||||||
|  | 		__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 = cd_to_ni(pmu_to_cd(dev_get_drvdata(dev))); | ||||||
|  | 	u32 reg = readl_relaxed(ni->base + NI_PERIPHERAL_ID2); | ||||||
|  | 	int version = FIELD_GET(NI_PIDR2_VERSION, reg); | ||||||
|  | 
 | ||||||
|  | 	return sysfs_emit(buf, "%03x%02x\n", ni->part, version); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static struct device_attribute arm_ni_identifier_attr = | ||||||
|  | 		__ATTR(identifier, 0444, arm_ni_identifier_show, NULL); | ||||||
|  | 
 | ||||||
|  | static struct attribute *arm_ni_other_attrs[] = { | ||||||
|  | 	&arm_ni_cpumask_attr.attr, | ||||||
|  | 	&arm_ni_identifier_attr.attr, | ||||||
|  | 	NULL | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const struct attribute_group arm_ni_other_attr_group = { | ||||||
|  | 	.attrs = arm_ni_other_attrs, | ||||||
|  | 	NULL | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const struct attribute_group *arm_ni_attr_groups[] = { | ||||||
|  | 	&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_val_count_event(struct perf_event *evt, struct arm_ni_val *val) | ||||||
|  | { | ||||||
|  | 	if (is_software_event(evt)) | ||||||
|  | 		return true; | ||||||
|  | 
 | ||||||
|  | 	if (NI_EVENT_TYPE(evt) == NI_PMU) { | ||||||
|  | 		val->ccnt++; | ||||||
|  | 		return val->ccnt <= 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	val->evcnt++; | ||||||
|  | 	return val->evcnt <= NI_NUM_COUNTERS; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int arm_ni_validate_group(struct perf_event *event) | ||||||
|  | { | ||||||
|  | 	struct perf_event *sibling, *leader = event->group_leader; | ||||||
|  | 	struct arm_ni_val val = { 0 }; | ||||||
|  | 
 | ||||||
|  | 	if (leader == event) | ||||||
|  | 		return 0; | ||||||
|  | 
 | ||||||
|  | 	arm_ni_val_count_event(event, &val); | ||||||
|  | 	if (!arm_ni_val_count_event(leader, &val)) | ||||||
|  | 		return -EINVAL; | ||||||
|  | 
 | ||||||
|  | 	for_each_sibling_event(sibling, leader) { | ||||||
|  | 		if (!arm_ni_val_count_event(sibling, &val)) | ||||||
|  | 			return -EINVAL; | ||||||
|  | 	} | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int arm_ni_event_init(struct perf_event *event) | ||||||
|  | { | ||||||
|  | 	struct arm_ni_cd *cd = pmu_to_cd(event->pmu); | ||||||
|  | 
 | ||||||
|  | 	if (event->attr.type != event->pmu->type) | ||||||
|  | 		return -ENOENT; | ||||||
|  | 
 | ||||||
|  | 	if (is_sampling_event(event)) | ||||||
|  | 		return -EINVAL; | ||||||
|  | 
 | ||||||
|  | 	event->cpu = cd->cpu; | ||||||
|  | 	if (NI_EVENT_TYPE(event) == NI_PMU) | ||||||
|  | 		return arm_ni_validate_group(event); | ||||||
|  | 
 | ||||||
|  | 	cd_for_each_unit(cd, unit) { | ||||||
|  | 		if (unit->type == NI_EVENT_TYPE(event) && | ||||||
|  | 		    unit->id == NI_EVENT_NODEID(event) && unit->ns) { | ||||||
|  | 			event->hw.config_base = (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 = 3; /* 1st time unlucky, 2nd improbable, 3rd just broken */ | ||||||
|  | 
 | ||||||
|  | 	u_new = readl_relaxed(cd->pmu_base + NI_PMCCNTR_U); | ||||||
|  | 	do { | ||||||
|  | 		u_old = u_new; | ||||||
|  | 		l = readl_relaxed(cd->pmu_base + NI_PMCCNTR_L); | ||||||
|  | 		u_new = readl_relaxed(cd->pmu_base + NI_PMCCNTR_U); | ||||||
|  | 	} while (u_new != 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 = pmu_to_cd(event->pmu); | ||||||
|  | 	struct hw_perf_event *hw = &event->hw; | ||||||
|  | 	u64 count, prev; | ||||||
|  | 	bool ccnt = hw->idx == NI_CCNT_IDX; | ||||||
|  | 
 | ||||||
|  | 	do { | ||||||
|  | 		prev = local64_read(&hw->prev_count); | ||||||
|  | 		if (ccnt) | ||||||
|  | 			count = arm_ni_read_ccnt(cd); | ||||||
|  | 		else | ||||||
|  | 			count = readl_relaxed(cd->pmu_base + NI_PMEVCNTR(hw->idx)); | ||||||
|  | 	} while (local64_cmpxchg(&hw->prev_count, prev, count) != prev); | ||||||
|  | 
 | ||||||
|  | 	count -= prev; | ||||||
|  | 	if (!ccnt) | ||||||
|  | 		count = (u32)count; | ||||||
|  | 	local64_add(count, &event->count); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void arm_ni_event_start(struct perf_event *event, int flags) | ||||||
|  | { | ||||||
|  | 	struct arm_ni_cd *cd = 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 = 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 = pmu_to_cd(event->pmu); | ||||||
|  | 	struct hw_perf_event *hw = &event->hw; | ||||||
|  | 	struct arm_ni_unit *unit; | ||||||
|  | 	enum ni_node_type type = NI_EVENT_TYPE(event); | ||||||
|  | 	u32 reg; | ||||||
|  | 
 | ||||||
|  | 	if (type == NI_PMU) { | ||||||
|  | 		if (cd->ccnt) | ||||||
|  | 			return -ENOSPC; | ||||||
|  | 		hw->idx = NI_CCNT_IDX; | ||||||
|  | 		cd->ccnt = event; | ||||||
|  | 		arm_ni_init_ccnt(cd); | ||||||
|  | 	} else { | ||||||
|  | 		hw->idx = 0; | ||||||
|  | 		while (cd->evcnt[hw->idx]) { | ||||||
|  | 			if (++hw->idx == NI_NUM_COUNTERS) | ||||||
|  | 				return -ENOSPC; | ||||||
|  | 		} | ||||||
|  | 		cd->evcnt[hw->idx] = event; | ||||||
|  | 		unit = (void *)hw->config_base; | ||||||
|  | 		unit->event[hw->idx] = NI_EVENT_EVENTID(event); | ||||||
|  | 		arm_ni_init_evcnt(cd, hw->idx); | ||||||
|  | 		lo_hi_writeq_relaxed(le64_to_cpu(unit->pmusel), unit->pmusela); | ||||||
|  | 
 | ||||||
|  | 		reg = 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 = pmu_to_cd(event->pmu); | ||||||
|  | 	struct hw_perf_event *hw = &event->hw; | ||||||
|  | 
 | ||||||
|  | 	arm_ni_event_stop(event, PERF_EF_UPDATE); | ||||||
|  | 
 | ||||||
|  | 	if (hw->idx == NI_CCNT_IDX) | ||||||
|  | 		cd->ccnt = NULL; | ||||||
|  | 	else | ||||||
|  | 		cd->evcnt[hw->idx] = NULL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static irqreturn_t arm_ni_handle_irq(int irq, void *dev_id) | ||||||
|  | { | ||||||
|  | 	struct arm_ni_cd *cd = dev_id; | ||||||
|  | 	irqreturn_t ret = IRQ_NONE; | ||||||
|  | 	u32 reg = readl_relaxed(cd->pmu_base + NI_PMOVSCLR); | ||||||
|  | 
 | ||||||
|  | 	if (reg & (1U << NI_CCNT_IDX)) { | ||||||
|  | 		ret = IRQ_HANDLED; | ||||||
|  | 		if (!(WARN_ON(!cd->ccnt))) { | ||||||
|  | 			arm_ni_event_read(cd->ccnt); | ||||||
|  | 			arm_ni_init_ccnt(cd); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	for (int i = 0; i < NI_NUM_COUNTERS; i++) { | ||||||
|  | 		if (!(reg & (1U << i))) | ||||||
|  | 			continue; | ||||||
|  | 		ret = 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 = ni->cds + node->id; | ||||||
|  | 	const char *name; | ||||||
|  | 	int err; | ||||||
|  | 
 | ||||||
|  | 	cd->id = node->id; | ||||||
|  | 	cd->num_units = node->num_components; | ||||||
|  | 	cd->units = devm_kcalloc(ni->dev, cd->num_units, sizeof(*(cd->units)), GFP_KERNEL); | ||||||
|  | 	if (!cd->units) | ||||||
|  | 		return -ENOMEM; | ||||||
|  | 
 | ||||||
|  | 	for (int i = 0; i < cd->num_units; i++) { | ||||||
|  | 		u32 reg = readl_relaxed(node->base + NI_CHILD_PTR(i)); | ||||||
|  | 		void __iomem *unit_base = ni->base + reg; | ||||||
|  | 		struct arm_ni_unit *unit = cd->units + i; | ||||||
|  | 
 | ||||||
|  | 		reg = readl_relaxed(unit_base + NI_NODE_TYPE); | ||||||
|  | 		unit->type = FIELD_GET(NI_NODE_TYPE_NODE_TYPE, reg); | ||||||
|  | 		unit->id = FIELD_GET(NI_NODE_TYPE_NODE_ID, reg); | ||||||
|  | 
 | ||||||
|  | 		switch (unit->type) { | ||||||
|  | 		case NI_PMU: | ||||||
|  | 			reg = 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 = true; | ||||||
|  | 			cd->pmu_base = unit_base; | ||||||
|  | 			break; | ||||||
|  | 		case NI_ASNI: | ||||||
|  | 		case NI_AMNI: | ||||||
|  | 		case NI_HSNI: | ||||||
|  | 		case NI_HMNI: | ||||||
|  | 		case NI_PMNI: | ||||||
|  | 			unit->pmusela = unit_base + NI700_PMUSELA; | ||||||
|  | 			writel_relaxed(1, unit->pmusela); | ||||||
|  | 			if (readl_relaxed(unit->pmusela) != 1) | ||||||
|  | 				dev_info(ni->dev, "No access to node 0x%04x%04x\n", unit->id, unit->type); | ||||||
|  | 			else | ||||||
|  | 				unit->ns = 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 += 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 = platform_get_irq(to_platform_device(ni->dev), cd->id); | ||||||
|  | 	if (cd->irq < 0) | ||||||
|  | 		return cd->irq; | ||||||
|  | 
 | ||||||
|  | 	err = 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 = cpumask_local_spread(0, dev_to_node(ni->dev)); | ||||||
|  | 	cd->pmu = (struct pmu) { | ||||||
|  | 		.module = THIS_MODULE, | ||||||
|  | 		.parent = ni->dev, | ||||||
|  | 		.attr_groups = arm_ni_attr_groups, | ||||||
|  | 		.capabilities = PERF_PMU_CAP_NO_EXCLUDE, | ||||||
|  | 		.task_ctx_nr = perf_invalid_context, | ||||||
|  | 		.pmu_enable = arm_ni_pmu_enable, | ||||||
|  | 		.pmu_disable = arm_ni_pmu_disable, | ||||||
|  | 		.event_init = arm_ni_event_init, | ||||||
|  | 		.add = arm_ni_event_add, | ||||||
|  | 		.del = arm_ni_event_del, | ||||||
|  | 		.start = arm_ni_event_start, | ||||||
|  | 		.stop = arm_ni_event_stop, | ||||||
|  | 		.read = arm_ni_event_read, | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	name = devm_kasprintf(ni->dev, GFP_KERNEL, "arm_ni_%d_cd_%d", ni->id, cd->id); | ||||||
|  | 	if (!name) | ||||||
|  | 		return -ENOMEM; | ||||||
|  | 
 | ||||||
|  | 	err = cpuhp_state_add_instance_nocalls(arm_ni_hp_state, &cd->cpuhp_node); | ||||||
|  | 	if (err) | ||||||
|  | 		return err; | ||||||
|  | 
 | ||||||
|  | 	err = perf_pmu_register(&cd->pmu, name, -1); | ||||||
|  | 	if (err) | ||||||
|  | 		cpuhp_state_remove_instance_nocalls(arm_ni_hp_state, &cd->cpuhp_node); | ||||||
|  | 
 | ||||||
|  | 	return err; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void arm_ni_probe_domain(void __iomem *base, struct arm_ni_node *node) | ||||||
|  | { | ||||||
|  | 	u32 reg = readl_relaxed(base + NI_NODE_TYPE); | ||||||
|  | 
 | ||||||
|  | 	node->base = base; | ||||||
|  | 	node->type = FIELD_GET(NI_NODE_TYPE_NODE_TYPE, reg); | ||||||
|  | 	node->id = FIELD_GET(NI_NODE_TYPE_NODE_ID, reg); | ||||||
|  | 	node->num_components = 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 = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||||||
|  | 	base = 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 != NI_GLOBAL) | ||||||
|  | 		return -ENODEV; | ||||||
|  | 
 | ||||||
|  | 	reg = readl_relaxed(cfg.base + NI_PERIPHERAL_ID0); | ||||||
|  | 	part = FIELD_GET(NI_PIDR0_PART_7_0, reg); | ||||||
|  | 	reg = readl_relaxed(cfg.base + NI_PERIPHERAL_ID1); | ||||||
|  | 	part |= 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 = 0; | ||||||
|  | 	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; p++) { | ||||||
|  | 			reg = readl_relaxed(vd.base + NI_CHILD_PTR(p)); | ||||||
|  | 			arm_ni_probe_domain(base + reg, &pd); | ||||||
|  | 			num_cds += pd.num_components; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ni = devm_kzalloc(&pdev->dev, struct_size(ni, cds, num_cds), GFP_KERNEL); | ||||||
|  | 	if (!ni) | ||||||
|  | 		return -ENOMEM; | ||||||
|  | 
 | ||||||
|  | 	ni->dev = &pdev->dev; | ||||||
|  | 	ni->base = base; | ||||||
|  | 	ni->num_cds = num_cds; | ||||||
|  | 	ni->part = part; | ||||||
|  | 	ni->id = atomic_fetch_inc(&id); | ||||||
|  | 
 | ||||||
|  | 	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; p++) { | ||||||
|  | 			reg = readl_relaxed(vd.base + NI_CHILD_PTR(p)); | ||||||
|  | 			arm_ni_probe_domain(base + reg, &pd); | ||||||
|  | 			for (int c = 0; c < pd.num_components; c++) { | ||||||
|  | 				int ret; | ||||||
|  | 
 | ||||||
|  | 				reg = readl_relaxed(pd.base + NI_CHILD_PTR(c)); | ||||||
|  | 				arm_ni_probe_domain(base + reg, &cd); | ||||||
|  | 				ret = arm_ni_init_cd(ni, &cd, res->start); | ||||||
|  | 				if (ret) | ||||||
|  | 					return ret; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void arm_ni_remove(struct platform_device *pdev) | ||||||
|  | { | ||||||
|  | 	struct arm_ni *ni = platform_get_drvdata(pdev); | ||||||
|  | 
 | ||||||
|  | 	for (int i = 0; i < ni->num_cds; i++) { | ||||||
|  | 		struct arm_ni_cd *cd = 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); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #ifdef CONFIG_OF | ||||||
|  | static const struct of_device_id arm_ni_of_match[] = { | ||||||
|  | 	{ .compatible = "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[] = { | ||||||
|  | 	{ "ARMHCB70" }, | ||||||
|  | 	{} | ||||||
|  | }; | ||||||
|  | MODULE_DEVICE_TABLE(acpi, arm_ni_acpi_match); | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | static struct platform_driver arm_ni_driver = { | ||||||
|  | 	.driver = { | ||||||
|  | 		.name = "arm-ni", | ||||||
|  | 		.of_match_table = of_match_ptr(arm_ni_of_match), | ||||||
|  | 		.acpi_match_table = ACPI_PTR(arm_ni_acpi_match), | ||||||
|  | 	}, | ||||||
|  | 	.probe = arm_ni_probe, | ||||||
|  | 	.remove = 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 = cpu; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int arm_ni_pmu_online_cpu(unsigned int cpu, struct hlist_node *cpuhp_node) | ||||||
|  | { | ||||||
|  | 	struct arm_ni_cd *cd; | ||||||
|  | 	int node; | ||||||
|  | 
 | ||||||
|  | 	cd = hlist_entry_safe(cpuhp_node, struct arm_ni_cd, cpuhp_node); | ||||||
|  | 	node = dev_to_node(cd_to_ni(cd)->dev); | ||||||
|  | 	if (cpu_to_node(cd->cpu) != node && cpu_to_node(cpu) == node) | ||||||
|  | 		arm_ni_pmu_migrate(cd, cpu); | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int arm_ni_pmu_offline_cpu(unsigned int cpu, struct hlist_node *cpuhp_node) | ||||||
|  | { | ||||||
|  | 	struct arm_ni_cd *cd; | ||||||
|  | 	unsigned int target; | ||||||
|  | 	int node; | ||||||
|  | 
 | ||||||
|  | 	cd = hlist_entry_safe(cpuhp_node, struct arm_ni_cd, cpuhp_node); | ||||||
|  | 	if (cpu != cd->cpu) | ||||||
|  | 		return 0; | ||||||
|  | 
 | ||||||
|  | 	node = dev_to_node(cd_to_ni(cd)->dev); | ||||||
|  | 	target = cpumask_any_and_but(cpumask_of_node(node), cpu_online_mask, cpu); | ||||||
|  | 	if (target >= nr_cpu_ids) | ||||||
|  | 		target = 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 = 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 = ret; | ||||||
|  | 
 | ||||||
|  | 	ret = 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 <robin.murphy@arm.com>"); | ||||||
|  | MODULE_DESCRIPTION("Arm NI-700 PMU driver"); | ||||||
|  | MODULE_LICENSE("GPL v2"); | ||||||
		Loading…
	
		Reference in a new issue
	
	 Robin Murphy
						Robin Murphy