Merge branch 'for-next/perf' into aarch64/for-next/core
Merge in ARM PMU and perf updates for 4.15: - Support for the Statistical Profiling Extension - Support for Hisilicon's SoC PMU Signed-off-by: Will Deacon <will.deacon@arm.com>
This commit is contained in:
Коммит
1e0c661f05
|
@ -0,0 +1,20 @@
|
|||
* ARMv8.2 Statistical Profiling Extension (SPE) Performance Monitor Units (PMU)
|
||||
|
||||
ARMv8.2 introduces the optional Statistical Profiling Extension for collecting
|
||||
performance sample data using an in-memory trace buffer.
|
||||
|
||||
** SPE Required properties:
|
||||
|
||||
- compatible : should be one of:
|
||||
"arm,statistical-profiling-extension-v1"
|
||||
|
||||
- interrupts : Exactly 1 PPI must be listed. For heterogeneous systems where
|
||||
SPE is only supported on a subset of the CPUs, please consult
|
||||
the arm,gic-v3 binding for details on describing a PPI partition.
|
||||
|
||||
** Example:
|
||||
|
||||
spe-pmu {
|
||||
compatible = "arm,statistical-profiling-extension-v1";
|
||||
interrupts = <GIC_PPI 05 IRQ_TYPE_LEVEL_HIGH &part1>;
|
||||
};
|
|
@ -0,0 +1,53 @@
|
|||
HiSilicon SoC uncore Performance Monitoring Unit (PMU)
|
||||
======================================================
|
||||
The HiSilicon SoC chip includes various independent system device PMUs
|
||||
such as L3 cache (L3C), Hydra Home Agent (HHA) and DDRC. These PMUs are
|
||||
independent and have hardware logic to gather statistics and performance
|
||||
information.
|
||||
|
||||
The HiSilicon SoC encapsulates multiple CPU and IO dies. Each CPU cluster
|
||||
(CCL) is made up of 4 cpu cores sharing one L3 cache; each CPU die is
|
||||
called Super CPU cluster (SCCL) and is made up of 6 CCLs. Each SCCL has
|
||||
two HHAs (0 - 1) and four DDRCs (0 - 3), respectively.
|
||||
|
||||
HiSilicon SoC uncore PMU driver
|
||||
---------------------------------------
|
||||
Each device PMU has separate registers for event counting, control and
|
||||
interrupt, and the PMU driver shall register perf PMU drivers like L3C,
|
||||
HHA and DDRC etc. The available events and configuration options shall
|
||||
be described in the sysfs, see :
|
||||
/sys/devices/hisi_sccl{X}_<l3c{Y}/hha{Y}/ddrc{Y}>/, or
|
||||
/sys/bus/event_source/devices/hisi_sccl{X}_<l3c{Y}/hha{Y}/ddrc{Y}>.
|
||||
The "perf list" command shall list the available events from sysfs.
|
||||
|
||||
Each L3C, HHA and DDRC is registered as a separate PMU with perf. The PMU
|
||||
name will appear in event listing as hisi_sccl<sccl-id>_module<index-id>.
|
||||
where "sccl-id" is the identifier of the SCCL and "index-id" is the index of
|
||||
module.
|
||||
e.g. hisi_sccl3_l3c0/rd_hit_cpipe is READ_HIT_CPIPE event of L3C index #0 in
|
||||
SCCL ID #3.
|
||||
e.g. hisi_sccl1_hha0/rx_operations is RX_OPERATIONS event of HHA index #0 in
|
||||
SCCL ID #1.
|
||||
|
||||
The driver also provides a "cpumask" sysfs attribute, which shows the CPU core
|
||||
ID used to count the uncore PMU event.
|
||||
|
||||
Example usage of perf:
|
||||
$# perf list
|
||||
hisi_sccl3_l3c0/rd_hit_cpipe/ [kernel PMU event]
|
||||
------------------------------------------
|
||||
hisi_sccl3_l3c0/wr_hit_cpipe/ [kernel PMU event]
|
||||
------------------------------------------
|
||||
hisi_sccl1_l3c0/rd_hit_cpipe/ [kernel PMU event]
|
||||
------------------------------------------
|
||||
hisi_sccl1_l3c0/wr_hit_cpipe/ [kernel PMU event]
|
||||
------------------------------------------
|
||||
|
||||
$# perf stat -a -e hisi_sccl3_l3c0/rd_hit_cpipe/ sleep 5
|
||||
$# perf stat -a -e hisi_sccl3_l3c0/config=0x02/ sleep 5
|
||||
|
||||
The current driver does not support sampling. So "perf record" is unsupported.
|
||||
Also attach to a task is unsupported as the events are all uncore.
|
||||
|
||||
Note: Please contact the maintainer for a complete list of events supported for
|
||||
the PMU devices in the SoC and its information if needed.
|
|
@ -6241,6 +6241,13 @@ S: Maintained
|
|||
F: drivers/net/ethernet/hisilicon/
|
||||
F: Documentation/devicetree/bindings/net/hisilicon*.txt
|
||||
|
||||
HISILICON PMU DRIVER
|
||||
M: Shaokun Zhang <zhangshaokun@hisilicon.com>
|
||||
W: http://www.hisilicon.com
|
||||
S: Supported
|
||||
F: drivers/perf/hisilicon
|
||||
F: Documentation/perf/hisi-pmu.txt
|
||||
|
||||
HISILICON ROCE DRIVER
|
||||
M: Lijun Ou <oulijun@huawei.com>
|
||||
M: Wei Hu(Xavier) <xavier.huwei@huawei.com>
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
#define dmb(opt) asm volatile("dmb " #opt : : : "memory")
|
||||
#define dsb(opt) asm volatile("dsb " #opt : : : "memory")
|
||||
|
||||
#define psb_csync() asm volatile("hint #17" : : : "memory")
|
||||
|
||||
#define mb() dsb(sy)
|
||||
#define rmb() dsb(ld)
|
||||
#define wmb() dsb(st)
|
||||
|
|
|
@ -172,6 +172,99 @@
|
|||
#define SYS_FAR_EL1 sys_reg(3, 0, 6, 0, 0)
|
||||
#define SYS_PAR_EL1 sys_reg(3, 0, 7, 4, 0)
|
||||
|
||||
/*** Statistical Profiling Extension ***/
|
||||
/* ID registers */
|
||||
#define SYS_PMSIDR_EL1 sys_reg(3, 0, 9, 9, 7)
|
||||
#define SYS_PMSIDR_EL1_FE_SHIFT 0
|
||||
#define SYS_PMSIDR_EL1_FT_SHIFT 1
|
||||
#define SYS_PMSIDR_EL1_FL_SHIFT 2
|
||||
#define SYS_PMSIDR_EL1_ARCHINST_SHIFT 3
|
||||
#define SYS_PMSIDR_EL1_LDS_SHIFT 4
|
||||
#define SYS_PMSIDR_EL1_ERND_SHIFT 5
|
||||
#define SYS_PMSIDR_EL1_INTERVAL_SHIFT 8
|
||||
#define SYS_PMSIDR_EL1_INTERVAL_MASK 0xfUL
|
||||
#define SYS_PMSIDR_EL1_MAXSIZE_SHIFT 12
|
||||
#define SYS_PMSIDR_EL1_MAXSIZE_MASK 0xfUL
|
||||
#define SYS_PMSIDR_EL1_COUNTSIZE_SHIFT 16
|
||||
#define SYS_PMSIDR_EL1_COUNTSIZE_MASK 0xfUL
|
||||
|
||||
#define SYS_PMBIDR_EL1 sys_reg(3, 0, 9, 10, 7)
|
||||
#define SYS_PMBIDR_EL1_ALIGN_SHIFT 0
|
||||
#define SYS_PMBIDR_EL1_ALIGN_MASK 0xfU
|
||||
#define SYS_PMBIDR_EL1_P_SHIFT 4
|
||||
#define SYS_PMBIDR_EL1_F_SHIFT 5
|
||||
|
||||
/* Sampling controls */
|
||||
#define SYS_PMSCR_EL1 sys_reg(3, 0, 9, 9, 0)
|
||||
#define SYS_PMSCR_EL1_E0SPE_SHIFT 0
|
||||
#define SYS_PMSCR_EL1_E1SPE_SHIFT 1
|
||||
#define SYS_PMSCR_EL1_CX_SHIFT 3
|
||||
#define SYS_PMSCR_EL1_PA_SHIFT 4
|
||||
#define SYS_PMSCR_EL1_TS_SHIFT 5
|
||||
#define SYS_PMSCR_EL1_PCT_SHIFT 6
|
||||
|
||||
#define SYS_PMSCR_EL2 sys_reg(3, 4, 9, 9, 0)
|
||||
#define SYS_PMSCR_EL2_E0HSPE_SHIFT 0
|
||||
#define SYS_PMSCR_EL2_E2SPE_SHIFT 1
|
||||
#define SYS_PMSCR_EL2_CX_SHIFT 3
|
||||
#define SYS_PMSCR_EL2_PA_SHIFT 4
|
||||
#define SYS_PMSCR_EL2_TS_SHIFT 5
|
||||
#define SYS_PMSCR_EL2_PCT_SHIFT 6
|
||||
|
||||
#define SYS_PMSICR_EL1 sys_reg(3, 0, 9, 9, 2)
|
||||
|
||||
#define SYS_PMSIRR_EL1 sys_reg(3, 0, 9, 9, 3)
|
||||
#define SYS_PMSIRR_EL1_RND_SHIFT 0
|
||||
#define SYS_PMSIRR_EL1_INTERVAL_SHIFT 8
|
||||
#define SYS_PMSIRR_EL1_INTERVAL_MASK 0xffffffUL
|
||||
|
||||
/* Filtering controls */
|
||||
#define SYS_PMSFCR_EL1 sys_reg(3, 0, 9, 9, 4)
|
||||
#define SYS_PMSFCR_EL1_FE_SHIFT 0
|
||||
#define SYS_PMSFCR_EL1_FT_SHIFT 1
|
||||
#define SYS_PMSFCR_EL1_FL_SHIFT 2
|
||||
#define SYS_PMSFCR_EL1_B_SHIFT 16
|
||||
#define SYS_PMSFCR_EL1_LD_SHIFT 17
|
||||
#define SYS_PMSFCR_EL1_ST_SHIFT 18
|
||||
|
||||
#define SYS_PMSEVFR_EL1 sys_reg(3, 0, 9, 9, 5)
|
||||
#define SYS_PMSEVFR_EL1_RES0 0x0000ffff00ff0f55UL
|
||||
|
||||
#define SYS_PMSLATFR_EL1 sys_reg(3, 0, 9, 9, 6)
|
||||
#define SYS_PMSLATFR_EL1_MINLAT_SHIFT 0
|
||||
|
||||
/* Buffer controls */
|
||||
#define SYS_PMBLIMITR_EL1 sys_reg(3, 0, 9, 10, 0)
|
||||
#define SYS_PMBLIMITR_EL1_E_SHIFT 0
|
||||
#define SYS_PMBLIMITR_EL1_FM_SHIFT 1
|
||||
#define SYS_PMBLIMITR_EL1_FM_MASK 0x3UL
|
||||
#define SYS_PMBLIMITR_EL1_FM_STOP_IRQ (0 << SYS_PMBLIMITR_EL1_FM_SHIFT)
|
||||
|
||||
#define SYS_PMBPTR_EL1 sys_reg(3, 0, 9, 10, 1)
|
||||
|
||||
/* Buffer error reporting */
|
||||
#define SYS_PMBSR_EL1 sys_reg(3, 0, 9, 10, 3)
|
||||
#define SYS_PMBSR_EL1_COLL_SHIFT 16
|
||||
#define SYS_PMBSR_EL1_S_SHIFT 17
|
||||
#define SYS_PMBSR_EL1_EA_SHIFT 18
|
||||
#define SYS_PMBSR_EL1_DL_SHIFT 19
|
||||
#define SYS_PMBSR_EL1_EC_SHIFT 26
|
||||
#define SYS_PMBSR_EL1_EC_MASK 0x3fUL
|
||||
|
||||
#define SYS_PMBSR_EL1_EC_BUF (0x0UL << SYS_PMBSR_EL1_EC_SHIFT)
|
||||
#define SYS_PMBSR_EL1_EC_FAULT_S1 (0x24UL << SYS_PMBSR_EL1_EC_SHIFT)
|
||||
#define SYS_PMBSR_EL1_EC_FAULT_S2 (0x25UL << SYS_PMBSR_EL1_EC_SHIFT)
|
||||
|
||||
#define SYS_PMBSR_EL1_FAULT_FSC_SHIFT 0
|
||||
#define SYS_PMBSR_EL1_FAULT_FSC_MASK 0x3fUL
|
||||
|
||||
#define SYS_PMBSR_EL1_BUF_BSC_SHIFT 0
|
||||
#define SYS_PMBSR_EL1_BUF_BSC_MASK 0x3fUL
|
||||
|
||||
#define SYS_PMBSR_EL1_BUF_BSC_FULL (0x1UL << SYS_PMBSR_EL1_BUF_BSC_SHIFT)
|
||||
|
||||
/*** End of Statistical Profiling Extension ***/
|
||||
|
||||
#define SYS_PMINTENSET_EL1 sys_reg(3, 0, 9, 14, 1)
|
||||
#define SYS_PMINTENCLR_EL1 sys_reg(3, 0, 9, 14, 2)
|
||||
|
||||
|
|
|
@ -480,14 +480,21 @@ set_hcr:
|
|||
|
||||
/* Statistical profiling */
|
||||
ubfx x0, x1, #32, #4 // Check ID_AA64DFR0_EL1 PMSVer
|
||||
cbz x0, 6f // Skip if SPE not present
|
||||
cbnz x2, 5f // VHE?
|
||||
cbz x0, 7f // Skip if SPE not present
|
||||
cbnz x2, 6f // VHE?
|
||||
mrs_s x4, SYS_PMBIDR_EL1 // If SPE available at EL2,
|
||||
and x4, x4, #(1 << SYS_PMBIDR_EL1_P_SHIFT)
|
||||
cbnz x4, 5f // then permit sampling of physical
|
||||
mov x4, #(1 << SYS_PMSCR_EL2_PCT_SHIFT | \
|
||||
1 << SYS_PMSCR_EL2_PA_SHIFT)
|
||||
msr_s SYS_PMSCR_EL2, x4 // addresses and physical counter
|
||||
5:
|
||||
mov x1, #(MDCR_EL2_E2PB_MASK << MDCR_EL2_E2PB_SHIFT)
|
||||
orr x3, x3, x1 // If we don't have VHE, then
|
||||
b 6f // use EL1&0 translation.
|
||||
5: // For VHE, use EL2 translation
|
||||
b 7f // use EL1&0 translation.
|
||||
6: // For VHE, use EL2 translation
|
||||
orr x3, x3, #MDCR_EL2_TPMS // and disable access from EL1
|
||||
6:
|
||||
7:
|
||||
msr mdcr_el2, x3 // Configure debug traps
|
||||
|
||||
/* Stage-2 translation */
|
||||
|
|
|
@ -65,16 +65,6 @@
|
|||
default: write_debug(ptr[0], reg, 0); \
|
||||
}
|
||||
|
||||
#define PMSCR_EL1 sys_reg(3, 0, 9, 9, 0)
|
||||
|
||||
#define PMBLIMITR_EL1 sys_reg(3, 0, 9, 10, 0)
|
||||
#define PMBLIMITR_EL1_E BIT(0)
|
||||
|
||||
#define PMBIDR_EL1 sys_reg(3, 0, 9, 10, 7)
|
||||
#define PMBIDR_EL1_P BIT(4)
|
||||
|
||||
#define psb_csync() asm volatile("hint #17")
|
||||
|
||||
static void __hyp_text __debug_save_spe_vhe(u64 *pmscr_el1)
|
||||
{
|
||||
/* The vcpu can run. but it can't hide. */
|
||||
|
@ -90,18 +80,18 @@ static void __hyp_text __debug_save_spe_nvhe(u64 *pmscr_el1)
|
|||
return;
|
||||
|
||||
/* Yes; is it owned by EL3? */
|
||||
reg = read_sysreg_s(PMBIDR_EL1);
|
||||
if (reg & PMBIDR_EL1_P)
|
||||
reg = read_sysreg_s(SYS_PMBIDR_EL1);
|
||||
if (reg & BIT(SYS_PMBIDR_EL1_P_SHIFT))
|
||||
return;
|
||||
|
||||
/* No; is the host actually using the thing? */
|
||||
reg = read_sysreg_s(PMBLIMITR_EL1);
|
||||
if (!(reg & PMBLIMITR_EL1_E))
|
||||
reg = read_sysreg_s(SYS_PMBLIMITR_EL1);
|
||||
if (!(reg & BIT(SYS_PMBLIMITR_EL1_E_SHIFT)))
|
||||
return;
|
||||
|
||||
/* Yes; save the control register and disable data generation */
|
||||
*pmscr_el1 = read_sysreg_s(PMSCR_EL1);
|
||||
write_sysreg_s(0, PMSCR_EL1);
|
||||
*pmscr_el1 = read_sysreg_s(SYS_PMSCR_EL1);
|
||||
write_sysreg_s(0, SYS_PMSCR_EL1);
|
||||
isb();
|
||||
|
||||
/* Now drain all buffered data to memory */
|
||||
|
@ -122,7 +112,7 @@ static void __hyp_text __debug_restore_spe(u64 pmscr_el1)
|
|||
isb();
|
||||
|
||||
/* Re-enable data generation */
|
||||
write_sysreg_s(pmscr_el1, PMSCR_EL1);
|
||||
write_sysreg_s(pmscr_el1, SYS_PMSCR_EL1);
|
||||
}
|
||||
|
||||
void __hyp_text __debug_save_state(struct kvm_vcpu *vcpu,
|
||||
|
|
|
@ -17,6 +17,13 @@ config ARM_PMU_ACPI
|
|||
depends on ARM_PMU && ACPI
|
||||
def_bool y
|
||||
|
||||
config HISI_PMU
|
||||
bool "HiSilicon SoC PMU"
|
||||
depends on ARM64 && ACPI
|
||||
help
|
||||
Support for HiSilicon SoC uncore performance monitoring
|
||||
unit (PMU), such as: L3C, HHA and DDRC.
|
||||
|
||||
config QCOM_L2_PMU
|
||||
bool "Qualcomm Technologies L2-cache PMU"
|
||||
depends on ARCH_QCOM && ARM64 && ACPI
|
||||
|
@ -43,4 +50,12 @@ config XGENE_PMU
|
|||
help
|
||||
Say y if you want to use APM X-Gene SoC performance monitors.
|
||||
|
||||
config ARM_SPE_PMU
|
||||
tristate "Enable support for the ARMv8.2 Statistical Profiling Extension"
|
||||
depends on PERF_EVENTS && ARM64
|
||||
help
|
||||
Enable perf support for the ARMv8.2 Statistical Profiling
|
||||
Extension, which provides periodic sampling of operations in
|
||||
the CPU pipeline and reports this via the perf AUX interface.
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
obj-$(CONFIG_ARM_PMU) += arm_pmu.o arm_pmu_platform.o
|
||||
obj-$(CONFIG_ARM_PMU_ACPI) += arm_pmu_acpi.o
|
||||
obj-$(CONFIG_HISI_PMU) += hisilicon/
|
||||
obj-$(CONFIG_QCOM_L2_PMU) += qcom_l2_pmu.o
|
||||
obj-$(CONFIG_QCOM_L3_PMU) += qcom_l3_pmu.o
|
||||
obj-$(CONFIG_XGENE_PMU) += xgene_pmu.o
|
||||
obj-$(CONFIG_ARM_SPE_PMU) += arm_spe_pmu.o
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1 @@
|
|||
obj-$(CONFIG_HISI_PMU) += hisi_uncore_pmu.o hisi_uncore_l3c_pmu.o hisi_uncore_hha_pmu.o hisi_uncore_ddrc_pmu.o
|
|
@ -0,0 +1,463 @@
|
|||
/*
|
||||
* HiSilicon SoC DDRC uncore Hardware event counters support
|
||||
*
|
||||
* Copyright (C) 2017 Hisilicon Limited
|
||||
* Author: Shaokun Zhang <zhangshaokun@hisilicon.com>
|
||||
* Anurup M <anurup.m@huawei.com>
|
||||
*
|
||||
* This code is based on the uncore PMUs like arm-cci and arm-ccn.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/cpuhotplug.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/smp.h>
|
||||
|
||||
#include "hisi_uncore_pmu.h"
|
||||
|
||||
/* DDRC register definition */
|
||||
#define DDRC_PERF_CTRL 0x010
|
||||
#define DDRC_FLUX_WR 0x380
|
||||
#define DDRC_FLUX_RD 0x384
|
||||
#define DDRC_FLUX_WCMD 0x388
|
||||
#define DDRC_FLUX_RCMD 0x38c
|
||||
#define DDRC_PRE_CMD 0x3c0
|
||||
#define DDRC_ACT_CMD 0x3c4
|
||||
#define DDRC_BNK_CHG 0x3c8
|
||||
#define DDRC_RNK_CHG 0x3cc
|
||||
#define DDRC_EVENT_CTRL 0x6C0
|
||||
#define DDRC_INT_MASK 0x6c8
|
||||
#define DDRC_INT_STATUS 0x6cc
|
||||
#define DDRC_INT_CLEAR 0x6d0
|
||||
|
||||
/* DDRC has 8-counters */
|
||||
#define DDRC_NR_COUNTERS 0x8
|
||||
#define DDRC_PERF_CTRL_EN 0x2
|
||||
|
||||
/*
|
||||
* For DDRC PMU, there are eight-events and every event has been mapped
|
||||
* to fixed-purpose counters which register offset is not consistent.
|
||||
* Therefore there is no write event type and we assume that event
|
||||
* code (0 to 7) is equal to counter index in PMU driver.
|
||||
*/
|
||||
#define GET_DDRC_EVENTID(hwc) (hwc->config_base & 0x7)
|
||||
|
||||
static const u32 ddrc_reg_off[] = {
|
||||
DDRC_FLUX_WR, DDRC_FLUX_RD, DDRC_FLUX_WCMD, DDRC_FLUX_RCMD,
|
||||
DDRC_PRE_CMD, DDRC_ACT_CMD, DDRC_BNK_CHG, DDRC_RNK_CHG
|
||||
};
|
||||
|
||||
/*
|
||||
* Select the counter register offset using the counter index.
|
||||
* In DDRC there are no programmable counter, the count
|
||||
* is readed form the statistics counter register itself.
|
||||
*/
|
||||
static u32 hisi_ddrc_pmu_get_counter_offset(int cntr_idx)
|
||||
{
|
||||
return ddrc_reg_off[cntr_idx];
|
||||
}
|
||||
|
||||
static u64 hisi_ddrc_pmu_read_counter(struct hisi_pmu *ddrc_pmu,
|
||||
struct hw_perf_event *hwc)
|
||||
{
|
||||
/* Use event code as counter index */
|
||||
u32 idx = GET_DDRC_EVENTID(hwc);
|
||||
|
||||
if (!hisi_uncore_pmu_counter_valid(ddrc_pmu, idx)) {
|
||||
dev_err(ddrc_pmu->dev, "Unsupported event index:%d!\n", idx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return readl(ddrc_pmu->base + hisi_ddrc_pmu_get_counter_offset(idx));
|
||||
}
|
||||
|
||||
static void hisi_ddrc_pmu_write_counter(struct hisi_pmu *ddrc_pmu,
|
||||
struct hw_perf_event *hwc, u64 val)
|
||||
{
|
||||
u32 idx = GET_DDRC_EVENTID(hwc);
|
||||
|
||||
if (!hisi_uncore_pmu_counter_valid(ddrc_pmu, idx)) {
|
||||
dev_err(ddrc_pmu->dev, "Unsupported event index:%d!\n", idx);
|
||||
return;
|
||||
}
|
||||
|
||||
writel((u32)val,
|
||||
ddrc_pmu->base + hisi_ddrc_pmu_get_counter_offset(idx));
|
||||
}
|
||||
|
||||
/*
|
||||
* For DDRC PMU, event has been mapped to fixed-purpose counter by hardware,
|
||||
* so there is no need to write event type.
|
||||
*/
|
||||
static void hisi_ddrc_pmu_write_evtype(struct hisi_pmu *hha_pmu, int idx,
|
||||
u32 type)
|
||||
{
|
||||
}
|
||||
|
||||
static void hisi_ddrc_pmu_start_counters(struct hisi_pmu *ddrc_pmu)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Set perf_enable in DDRC_PERF_CTRL to start event counting */
|
||||
val = readl(ddrc_pmu->base + DDRC_PERF_CTRL);
|
||||
val |= DDRC_PERF_CTRL_EN;
|
||||
writel(val, ddrc_pmu->base + DDRC_PERF_CTRL);
|
||||
}
|
||||
|
||||
static void hisi_ddrc_pmu_stop_counters(struct hisi_pmu *ddrc_pmu)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Clear perf_enable in DDRC_PERF_CTRL to stop event counting */
|
||||
val = readl(ddrc_pmu->base + DDRC_PERF_CTRL);
|
||||
val &= ~DDRC_PERF_CTRL_EN;
|
||||
writel(val, ddrc_pmu->base + DDRC_PERF_CTRL);
|
||||
}
|
||||
|
||||
static void hisi_ddrc_pmu_enable_counter(struct hisi_pmu *ddrc_pmu,
|
||||
struct hw_perf_event *hwc)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Set counter index(event code) in DDRC_EVENT_CTRL register */
|
||||
val = readl(ddrc_pmu->base + DDRC_EVENT_CTRL);
|
||||
val |= (1 << GET_DDRC_EVENTID(hwc));
|
||||
writel(val, ddrc_pmu->base + DDRC_EVENT_CTRL);
|
||||
}
|
||||
|
||||
static void hisi_ddrc_pmu_disable_counter(struct hisi_pmu *ddrc_pmu,
|
||||
struct hw_perf_event *hwc)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Clear counter index(event code) in DDRC_EVENT_CTRL register */
|
||||
val = readl(ddrc_pmu->base + DDRC_EVENT_CTRL);
|
||||
val &= ~(1 << GET_DDRC_EVENTID(hwc));
|
||||
writel(val, ddrc_pmu->base + DDRC_EVENT_CTRL);
|
||||
}
|
||||
|
||||
static int hisi_ddrc_pmu_get_event_idx(struct perf_event *event)
|
||||
{
|
||||
struct hisi_pmu *ddrc_pmu = to_hisi_pmu(event->pmu);
|
||||
unsigned long *used_mask = ddrc_pmu->pmu_events.used_mask;
|
||||
struct hw_perf_event *hwc = &event->hw;
|
||||
/* For DDRC PMU, we use event code as counter index */
|
||||
int idx = GET_DDRC_EVENTID(hwc);
|
||||
|
||||
if (test_bit(idx, used_mask))
|
||||
return -EAGAIN;
|
||||
|
||||
set_bit(idx, used_mask);
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
static void hisi_ddrc_pmu_enable_counter_int(struct hisi_pmu *ddrc_pmu,
|
||||
struct hw_perf_event *hwc)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Write 0 to enable interrupt */
|
||||
val = readl(ddrc_pmu->base + DDRC_INT_MASK);
|
||||
val &= ~(1 << GET_DDRC_EVENTID(hwc));
|
||||
writel(val, ddrc_pmu->base + DDRC_INT_MASK);
|
||||
}
|
||||
|
||||
static void hisi_ddrc_pmu_disable_counter_int(struct hisi_pmu *ddrc_pmu,
|
||||
struct hw_perf_event *hwc)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Write 1 to mask interrupt */
|
||||
val = readl(ddrc_pmu->base + DDRC_INT_MASK);
|
||||
val |= (1 << GET_DDRC_EVENTID(hwc));
|
||||
writel(val, ddrc_pmu->base + DDRC_INT_MASK);
|
||||
}
|
||||
|
||||
static irqreturn_t hisi_ddrc_pmu_isr(int irq, void *dev_id)
|
||||
{
|
||||
struct hisi_pmu *ddrc_pmu = dev_id;
|
||||
struct perf_event *event;
|
||||
unsigned long overflown;
|
||||
int idx;
|
||||
|
||||
/* Read the DDRC_INT_STATUS register */
|
||||
overflown = readl(ddrc_pmu->base + DDRC_INT_STATUS);
|
||||
if (!overflown)
|
||||
return IRQ_NONE;
|
||||
|
||||
/*
|
||||
* Find the counter index which overflowed if the bit was set
|
||||
* and handle it
|
||||
*/
|
||||
for_each_set_bit(idx, &overflown, DDRC_NR_COUNTERS) {
|
||||
/* Write 1 to clear the IRQ status flag */
|
||||
writel((1 << idx), ddrc_pmu->base + DDRC_INT_CLEAR);
|
||||
|
||||
/* Get the corresponding event struct */
|
||||
event = ddrc_pmu->pmu_events.hw_events[idx];
|
||||
if (!event)
|
||||
continue;
|
||||
|
||||
hisi_uncore_pmu_event_update(event);
|
||||
hisi_uncore_pmu_set_event_period(event);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int hisi_ddrc_pmu_init_irq(struct hisi_pmu *ddrc_pmu,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
int irq, ret;
|
||||
|
||||
/* Read and init IRQ */
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "DDRC PMU get irq fail; irq:%d\n", irq);
|
||||
return irq;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, irq, hisi_ddrc_pmu_isr,
|
||||
IRQF_NOBALANCING | IRQF_NO_THREAD,
|
||||
dev_name(&pdev->dev), ddrc_pmu);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev,
|
||||
"Fail to request IRQ:%d ret:%d\n", irq, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ddrc_pmu->irq = irq;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id hisi_ddrc_pmu_acpi_match[] = {
|
||||
{ "HISI0233", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, hisi_ddrc_pmu_acpi_match);
|
||||
|
||||
static int hisi_ddrc_pmu_init_data(struct platform_device *pdev,
|
||||
struct hisi_pmu *ddrc_pmu)
|
||||
{
|
||||
struct resource *res;
|
||||
|
||||
/*
|
||||
* Use the SCCL_ID and DDRC channel ID to identify the
|
||||
* DDRC PMU, while SCCL_ID is in MPIDR[aff2].
|
||||
*/
|
||||
if (device_property_read_u32(&pdev->dev, "hisilicon,ch-id",
|
||||
&ddrc_pmu->index_id)) {
|
||||
dev_err(&pdev->dev, "Can not read ddrc channel-id!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (device_property_read_u32(&pdev->dev, "hisilicon,scl-id",
|
||||
&ddrc_pmu->sccl_id)) {
|
||||
dev_err(&pdev->dev, "Can not read ddrc sccl-id!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
/* DDRC PMUs only share the same SCCL */
|
||||
ddrc_pmu->ccl_id = -1;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
ddrc_pmu->base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(ddrc_pmu->base)) {
|
||||
dev_err(&pdev->dev, "ioremap failed for ddrc_pmu resource\n");
|
||||
return PTR_ERR(ddrc_pmu->base);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct attribute *hisi_ddrc_pmu_format_attr[] = {
|
||||
HISI_PMU_FORMAT_ATTR(event, "config:0-4"),
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group hisi_ddrc_pmu_format_group = {
|
||||
.name = "format",
|
||||
.attrs = hisi_ddrc_pmu_format_attr,
|
||||
};
|
||||
|
||||
static struct attribute *hisi_ddrc_pmu_events_attr[] = {
|
||||
HISI_PMU_EVENT_ATTR(flux_wr, 0x00),
|
||||
HISI_PMU_EVENT_ATTR(flux_rd, 0x01),
|
||||
HISI_PMU_EVENT_ATTR(flux_wcmd, 0x02),
|
||||
HISI_PMU_EVENT_ATTR(flux_rcmd, 0x03),
|
||||
HISI_PMU_EVENT_ATTR(pre_cmd, 0x04),
|
||||
HISI_PMU_EVENT_ATTR(act_cmd, 0x05),
|
||||
HISI_PMU_EVENT_ATTR(rnk_chg, 0x06),
|
||||
HISI_PMU_EVENT_ATTR(rw_chg, 0x07),
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group hisi_ddrc_pmu_events_group = {
|
||||
.name = "events",
|
||||
.attrs = hisi_ddrc_pmu_events_attr,
|
||||
};
|
||||
|
||||
static DEVICE_ATTR(cpumask, 0444, hisi_cpumask_sysfs_show, NULL);
|
||||
|
||||
static struct attribute *hisi_ddrc_pmu_cpumask_attrs[] = {
|
||||
&dev_attr_cpumask.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group hisi_ddrc_pmu_cpumask_attr_group = {
|
||||
.attrs = hisi_ddrc_pmu_cpumask_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group *hisi_ddrc_pmu_attr_groups[] = {
|
||||
&hisi_ddrc_pmu_format_group,
|
||||
&hisi_ddrc_pmu_events_group,
|
||||
&hisi_ddrc_pmu_cpumask_attr_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct hisi_uncore_ops hisi_uncore_ddrc_ops = {
|
||||
.write_evtype = hisi_ddrc_pmu_write_evtype,
|
||||
.get_event_idx = hisi_ddrc_pmu_get_event_idx,
|
||||
.start_counters = hisi_ddrc_pmu_start_counters,
|
||||
.stop_counters = hisi_ddrc_pmu_stop_counters,
|
||||
.enable_counter = hisi_ddrc_pmu_enable_counter,
|
||||
.disable_counter = hisi_ddrc_pmu_disable_counter,
|
||||
.enable_counter_int = hisi_ddrc_pmu_enable_counter_int,
|
||||
.disable_counter_int = hisi_ddrc_pmu_disable_counter_int,
|
||||
.write_counter = hisi_ddrc_pmu_write_counter,
|
||||
.read_counter = hisi_ddrc_pmu_read_counter,
|
||||
};
|
||||
|
||||
static int hisi_ddrc_pmu_dev_probe(struct platform_device *pdev,
|
||||
struct hisi_pmu *ddrc_pmu)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hisi_ddrc_pmu_init_data(pdev, ddrc_pmu);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = hisi_ddrc_pmu_init_irq(ddrc_pmu, pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ddrc_pmu->num_counters = DDRC_NR_COUNTERS;
|
||||
ddrc_pmu->counter_bits = 32;
|
||||
ddrc_pmu->ops = &hisi_uncore_ddrc_ops;
|
||||
ddrc_pmu->dev = &pdev->dev;
|
||||
ddrc_pmu->on_cpu = -1;
|
||||
ddrc_pmu->check_event = 7;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hisi_ddrc_pmu_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct hisi_pmu *ddrc_pmu;
|
||||
char *name;
|
||||
int ret;
|
||||
|
||||
ddrc_pmu = devm_kzalloc(&pdev->dev, sizeof(*ddrc_pmu), GFP_KERNEL);
|
||||
if (!ddrc_pmu)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, ddrc_pmu);
|
||||
|
||||
ret = hisi_ddrc_pmu_dev_probe(pdev, ddrc_pmu);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = cpuhp_state_add_instance(CPUHP_AP_PERF_ARM_HISI_DDRC_ONLINE,
|
||||
&ddrc_pmu->node);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Error %d registering hotplug;\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "hisi_sccl%u_ddrc%u",
|
||||
ddrc_pmu->sccl_id, ddrc_pmu->index_id);
|
||||
ddrc_pmu->pmu = (struct pmu) {
|
||||
.name = name,
|
||||
.task_ctx_nr = perf_invalid_context,
|
||||
.event_init = hisi_uncore_pmu_event_init,
|
||||
.pmu_enable = hisi_uncore_pmu_enable,
|
||||
.pmu_disable = hisi_uncore_pmu_disable,
|
||||
.add = hisi_uncore_pmu_add,
|
||||
.del = hisi_uncore_pmu_del,
|
||||
.start = hisi_uncore_pmu_start,
|
||||
.stop = hisi_uncore_pmu_stop,
|
||||
.read = hisi_uncore_pmu_read,
|
||||
.attr_groups = hisi_ddrc_pmu_attr_groups,
|
||||
};
|
||||
|
||||
ret = perf_pmu_register(&ddrc_pmu->pmu, name, -1);
|
||||
if (ret) {
|
||||
dev_err(ddrc_pmu->dev, "DDRC PMU register failed!\n");
|
||||
cpuhp_state_remove_instance(CPUHP_AP_PERF_ARM_HISI_DDRC_ONLINE,
|
||||
&ddrc_pmu->node);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hisi_ddrc_pmu_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct hisi_pmu *ddrc_pmu = platform_get_drvdata(pdev);
|
||||
|
||||
perf_pmu_unregister(&ddrc_pmu->pmu);
|
||||
cpuhp_state_remove_instance(CPUHP_AP_PERF_ARM_HISI_DDRC_ONLINE,
|
||||
&ddrc_pmu->node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver hisi_ddrc_pmu_driver = {
|
||||
.driver = {
|
||||
.name = "hisi_ddrc_pmu",
|
||||
.acpi_match_table = ACPI_PTR(hisi_ddrc_pmu_acpi_match),
|
||||
},
|
||||
.probe = hisi_ddrc_pmu_probe,
|
||||
.remove = hisi_ddrc_pmu_remove,
|
||||
};
|
||||
|
||||
static int __init hisi_ddrc_pmu_module_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = cpuhp_setup_state_multi(CPUHP_AP_PERF_ARM_HISI_DDRC_ONLINE,
|
||||
"AP_PERF_ARM_HISI_DDRC_ONLINE",
|
||||
hisi_uncore_pmu_online_cpu,
|
||||
hisi_uncore_pmu_offline_cpu);
|
||||
if (ret) {
|
||||
pr_err("DDRC PMU: setup hotplug, ret = %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = platform_driver_register(&hisi_ddrc_pmu_driver);
|
||||
if (ret)
|
||||
cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_DDRC_ONLINE);
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(hisi_ddrc_pmu_module_init);
|
||||
|
||||
static void __exit hisi_ddrc_pmu_module_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&hisi_ddrc_pmu_driver);
|
||||
cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_DDRC_ONLINE);
|
||||
|
||||
}
|
||||
module_exit(hisi_ddrc_pmu_module_exit);
|
||||
|
||||
MODULE_DESCRIPTION("HiSilicon SoC DDRC uncore PMU driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_AUTHOR("Shaokun Zhang <zhangshaokun@hisilicon.com>");
|
||||
MODULE_AUTHOR("Anurup M <anurup.m@huawei.com>");
|
|
@ -0,0 +1,473 @@
|
|||
/*
|
||||
* HiSilicon SoC HHA uncore Hardware event counters support
|
||||
*
|
||||
* Copyright (C) 2017 Hisilicon Limited
|
||||
* Author: Shaokun Zhang <zhangshaokun@hisilicon.com>
|
||||
* Anurup M <anurup.m@huawei.com>
|
||||
*
|
||||
* This code is based on the uncore PMUs like arm-cci and arm-ccn.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/cpuhotplug.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/smp.h>
|
||||
|
||||
#include "hisi_uncore_pmu.h"
|
||||
|
||||
/* HHA register definition */
|
||||
#define HHA_INT_MASK 0x0804
|
||||
#define HHA_INT_STATUS 0x0808
|
||||
#define HHA_INT_CLEAR 0x080C
|
||||
#define HHA_PERF_CTRL 0x1E00
|
||||
#define HHA_EVENT_CTRL 0x1E04
|
||||
#define HHA_EVENT_TYPE0 0x1E80
|
||||
/*
|
||||
* Each counter is 48-bits and [48:63] are reserved
|
||||
* which are Read-As-Zero and Writes-Ignored.
|
||||
*/
|
||||
#define HHA_CNT0_LOWER 0x1F00
|
||||
|
||||
/* HHA has 16-counters */
|
||||
#define HHA_NR_COUNTERS 0x10
|
||||
|
||||
#define HHA_PERF_CTRL_EN 0x1
|
||||
#define HHA_EVTYPE_NONE 0xff
|
||||
|
||||
/*
|
||||
* Select the counter register offset using the counter index
|
||||
* each counter is 48-bits.
|
||||
*/
|
||||
static u32 hisi_hha_pmu_get_counter_offset(int cntr_idx)
|
||||
{
|
||||
return (HHA_CNT0_LOWER + (cntr_idx * 8));
|
||||
}
|
||||
|
||||
static u64 hisi_hha_pmu_read_counter(struct hisi_pmu *hha_pmu,
|
||||
struct hw_perf_event *hwc)
|
||||
{
|
||||
u32 idx = hwc->idx;
|
||||
|
||||
if (!hisi_uncore_pmu_counter_valid(hha_pmu, idx)) {
|
||||
dev_err(hha_pmu->dev, "Unsupported event index:%d!\n", idx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Read 64 bits and like L3C, top 16 bits are RAZ */
|
||||
return readq(hha_pmu->base + hisi_hha_pmu_get_counter_offset(idx));
|
||||
}
|
||||
|
||||
static void hisi_hha_pmu_write_counter(struct hisi_pmu *hha_pmu,
|
||||
struct hw_perf_event *hwc, u64 val)
|
||||
{
|
||||
u32 idx = hwc->idx;
|
||||
|
||||
if (!hisi_uncore_pmu_counter_valid(hha_pmu, idx)) {
|
||||
dev_err(hha_pmu->dev, "Unsupported event index:%d!\n", idx);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Write 64 bits and like L3C, top 16 bits are WI */
|
||||
writeq(val, hha_pmu->base + hisi_hha_pmu_get_counter_offset(idx));
|
||||
}
|
||||
|
||||
static void hisi_hha_pmu_write_evtype(struct hisi_pmu *hha_pmu, int idx,
|
||||
u32 type)
|
||||
{
|
||||
u32 reg, reg_idx, shift, val;
|
||||
|
||||
/*
|
||||
* Select the appropriate event select register(HHA_EVENT_TYPEx).
|
||||
* There are 4 event select registers for the 16 hardware counters.
|
||||
* Event code is 8-bits and for the first 4 hardware counters,
|
||||
* HHA_EVENT_TYPE0 is chosen. For the next 4 hardware counters,
|
||||
* HHA_EVENT_TYPE1 is chosen and so on.
|
||||
*/
|
||||
reg = HHA_EVENT_TYPE0 + 4 * (idx / 4);
|
||||
reg_idx = idx % 4;
|
||||
shift = 8 * reg_idx;
|
||||
|
||||
/* Write event code to HHA_EVENT_TYPEx register */
|
||||
val = readl(hha_pmu->base + reg);
|
||||
val &= ~(HHA_EVTYPE_NONE << shift);
|
||||
val |= (type << shift);
|
||||
writel(val, hha_pmu->base + reg);
|
||||
}
|
||||
|
||||
static void hisi_hha_pmu_start_counters(struct hisi_pmu *hha_pmu)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/*
|
||||
* Set perf_enable bit in HHA_PERF_CTRL to start event
|
||||
* counting for all enabled counters.
|
||||
*/
|
||||
val = readl(hha_pmu->base + HHA_PERF_CTRL);
|
||||
val |= HHA_PERF_CTRL_EN;
|
||||
writel(val, hha_pmu->base + HHA_PERF_CTRL);
|
||||
}
|
||||
|
||||
static void hisi_hha_pmu_stop_counters(struct hisi_pmu *hha_pmu)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/*
|
||||
* Clear perf_enable bit in HHA_PERF_CTRL to stop event
|
||||
* counting for all enabled counters.
|
||||
*/
|
||||
val = readl(hha_pmu->base + HHA_PERF_CTRL);
|
||||
val &= ~(HHA_PERF_CTRL_EN);
|
||||
writel(val, hha_pmu->base + HHA_PERF_CTRL);
|
||||
}
|
||||
|
||||
static void hisi_hha_pmu_enable_counter(struct hisi_pmu *hha_pmu,
|
||||
struct hw_perf_event *hwc)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Enable counter index in HHA_EVENT_CTRL register */
|
||||
val = readl(hha_pmu->base + HHA_EVENT_CTRL);
|
||||
val |= (1 << hwc->idx);
|
||||
writel(val, hha_pmu->base + HHA_EVENT_CTRL);
|
||||
}
|
||||
|
||||
static void hisi_hha_pmu_disable_counter(struct hisi_pmu *hha_pmu,
|
||||
struct hw_perf_event *hwc)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Clear counter index in HHA_EVENT_CTRL register */
|
||||
val = readl(hha_pmu->base + HHA_EVENT_CTRL);
|
||||
val &= ~(1 << hwc->idx);
|
||||
writel(val, hha_pmu->base + HHA_EVENT_CTRL);
|
||||
}
|
||||
|
||||
static void hisi_hha_pmu_enable_counter_int(struct hisi_pmu *hha_pmu,
|
||||
struct hw_perf_event *hwc)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Write 0 to enable interrupt */
|
||||
val = readl(hha_pmu->base + HHA_INT_MASK);
|
||||
val &= ~(1 << hwc->idx);
|
||||
writel(val, hha_pmu->base + HHA_INT_MASK);
|
||||
}
|
||||
|
||||
static void hisi_hha_pmu_disable_counter_int(struct hisi_pmu *hha_pmu,
|
||||
struct hw_perf_event *hwc)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Write 1 to mask interrupt */
|
||||
val = readl(hha_pmu->base + HHA_INT_MASK);
|
||||
val |= (1 << hwc->idx);
|
||||
writel(val, hha_pmu->base + HHA_INT_MASK);
|
||||
}
|
||||
|
||||
static irqreturn_t hisi_hha_pmu_isr(int irq, void *dev_id)
|
||||
{
|
||||
struct hisi_pmu *hha_pmu = dev_id;
|
||||
struct perf_event *event;
|
||||
unsigned long overflown;
|
||||
int idx;
|
||||
|
||||
/* Read HHA_INT_STATUS register */
|
||||
overflown = readl(hha_pmu->base + HHA_INT_STATUS);
|
||||
if (!overflown)
|
||||
return IRQ_NONE;
|
||||
|
||||
/*
|
||||
* Find the counter index which overflowed if the bit was set
|
||||
* and handle it
|
||||
*/
|
||||
for_each_set_bit(idx, &overflown, HHA_NR_COUNTERS) {
|
||||
/* Write 1 to clear the IRQ status flag */
|
||||
writel((1 << idx), hha_pmu->base + HHA_INT_CLEAR);
|
||||
|
||||
/* Get the corresponding event struct */
|
||||
event = hha_pmu->pmu_events.hw_events[idx];
|
||||
if (!event)
|
||||
continue;
|
||||
|
||||
hisi_uncore_pmu_event_update(event);
|
||||
hisi_uncore_pmu_set_event_period(event);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int hisi_hha_pmu_init_irq(struct hisi_pmu *hha_pmu,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
int irq, ret;
|
||||
|
||||
/* Read and init IRQ */
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "HHA PMU get irq fail; irq:%d\n", irq);
|
||||
return irq;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, irq, hisi_hha_pmu_isr,
|
||||
IRQF_NOBALANCING | IRQF_NO_THREAD,
|
||||
dev_name(&pdev->dev), hha_pmu);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev,
|
||||
"Fail to request IRQ:%d ret:%d\n", irq, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
hha_pmu->irq = irq;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id hisi_hha_pmu_acpi_match[] = {
|
||||
{ "HISI0243", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, hisi_hha_pmu_acpi_match);
|
||||
|
||||
static int hisi_hha_pmu_init_data(struct platform_device *pdev,
|
||||
struct hisi_pmu *hha_pmu)
|
||||
{
|
||||
unsigned long long id;
|
||||
struct resource *res;
|
||||
acpi_status status;
|
||||
|
||||
status = acpi_evaluate_integer(ACPI_HANDLE(&pdev->dev),
|
||||
"_UID", NULL, &id);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EINVAL;
|
||||
|
||||
hha_pmu->index_id = id;
|
||||
|
||||
/*
|
||||
* Use SCCL_ID and UID to identify the HHA PMU, while
|
||||
* SCCL_ID is in MPIDR[aff2].
|
||||
*/
|
||||
if (device_property_read_u32(&pdev->dev, "hisilicon,scl-id",
|
||||
&hha_pmu->sccl_id)) {
|
||||
dev_err(&pdev->dev, "Can not read hha sccl-id!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
/* HHA PMUs only share the same SCCL */
|
||||
hha_pmu->ccl_id = -1;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
hha_pmu->base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(hha_pmu->base)) {
|
||||
dev_err(&pdev->dev, "ioremap failed for hha_pmu resource\n");
|
||||
return PTR_ERR(hha_pmu->base);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct attribute *hisi_hha_pmu_format_attr[] = {
|
||||
HISI_PMU_FORMAT_ATTR(event, "config:0-7"),
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group hisi_hha_pmu_format_group = {
|
||||
.name = "format",
|
||||
.attrs = hisi_hha_pmu_format_attr,
|
||||
};
|
||||
|
||||
static struct attribute *hisi_hha_pmu_events_attr[] = {
|
||||
HISI_PMU_EVENT_ATTR(rx_ops_num, 0x00),
|
||||
HISI_PMU_EVENT_ATTR(rx_outer, 0x01),
|
||||
HISI_PMU_EVENT_ATTR(rx_sccl, 0x02),
|
||||
HISI_PMU_EVENT_ATTR(rx_ccix, 0x03),
|
||||
HISI_PMU_EVENT_ATTR(rx_wbi, 0x04),
|
||||
HISI_PMU_EVENT_ATTR(rx_wbip, 0x05),
|
||||
HISI_PMU_EVENT_ATTR(rx_wtistash, 0x11),
|
||||
HISI_PMU_EVENT_ATTR(rd_ddr_64b, 0x1c),
|
||||
HISI_PMU_EVENT_ATTR(wr_dr_64b, 0x1d),
|
||||
HISI_PMU_EVENT_ATTR(rd_ddr_128b, 0x1e),
|
||||
HISI_PMU_EVENT_ATTR(wr_ddr_128b, 0x1f),
|
||||
HISI_PMU_EVENT_ATTR(spill_num, 0x20),
|
||||
HISI_PMU_EVENT_ATTR(spill_success, 0x21),
|
||||
HISI_PMU_EVENT_ATTR(bi_num, 0x23),
|
||||
HISI_PMU_EVENT_ATTR(mediated_num, 0x32),
|
||||
HISI_PMU_EVENT_ATTR(tx_snp_num, 0x33),
|
||||
HISI_PMU_EVENT_ATTR(tx_snp_outer, 0x34),
|
||||
HISI_PMU_EVENT_ATTR(tx_snp_ccix, 0x35),
|
||||
HISI_PMU_EVENT_ATTR(rx_snprspdata, 0x38),
|
||||
HISI_PMU_EVENT_ATTR(rx_snprsp_outer, 0x3c),
|
||||
HISI_PMU_EVENT_ATTR(sdir-lookup, 0x40),
|
||||
HISI_PMU_EVENT_ATTR(edir-lookup, 0x41),
|
||||
HISI_PMU_EVENT_ATTR(sdir-hit, 0x42),
|
||||
HISI_PMU_EVENT_ATTR(edir-hit, 0x43),
|
||||
HISI_PMU_EVENT_ATTR(sdir-home-migrate, 0x4c),
|
||||
HISI_PMU_EVENT_ATTR(edir-home-migrate, 0x4d),
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group hisi_hha_pmu_events_group = {
|
||||
.name = "events",
|
||||
.attrs = hisi_hha_pmu_events_attr,
|
||||
};
|
||||
|
||||
static DEVICE_ATTR(cpumask, 0444, hisi_cpumask_sysfs_show, NULL);
|
||||
|
||||
static struct attribute *hisi_hha_pmu_cpumask_attrs[] = {
|
||||
&dev_attr_cpumask.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group hisi_hha_pmu_cpumask_attr_group = {
|
||||
.attrs = hisi_hha_pmu_cpumask_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group *hisi_hha_pmu_attr_groups[] = {
|
||||
&hisi_hha_pmu_format_group,
|
||||
&hisi_hha_pmu_events_group,
|
||||
&hisi_hha_pmu_cpumask_attr_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct hisi_uncore_ops hisi_uncore_hha_ops = {
|
||||
.write_evtype = hisi_hha_pmu_write_evtype,
|
||||
.get_event_idx = hisi_uncore_pmu_get_event_idx,
|
||||
.start_counters = hisi_hha_pmu_start_counters,
|
||||
.stop_counters = hisi_hha_pmu_stop_counters,
|
||||
.enable_counter = hisi_hha_pmu_enable_counter,
|
||||
.disable_counter = hisi_hha_pmu_disable_counter,
|
||||
.enable_counter_int = hisi_hha_pmu_enable_counter_int,
|
||||
.disable_counter_int = hisi_hha_pmu_disable_counter_int,
|
||||
.write_counter = hisi_hha_pmu_write_counter,
|
||||
.read_counter = hisi_hha_pmu_read_counter,
|
||||
};
|
||||
|
||||
static int hisi_hha_pmu_dev_probe(struct platform_device *pdev,
|
||||
struct hisi_pmu *hha_pmu)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hisi_hha_pmu_init_data(pdev, hha_pmu);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = hisi_hha_pmu_init_irq(hha_pmu, pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
hha_pmu->num_counters = HHA_NR_COUNTERS;
|
||||
hha_pmu->counter_bits = 48;
|
||||
hha_pmu->ops = &hisi_uncore_hha_ops;
|
||||
hha_pmu->dev = &pdev->dev;
|
||||
hha_pmu->on_cpu = -1;
|
||||
hha_pmu->check_event = 0x65;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hisi_hha_pmu_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct hisi_pmu *hha_pmu;
|
||||
char *name;
|
||||
int ret;
|
||||
|
||||
hha_pmu = devm_kzalloc(&pdev->dev, sizeof(*hha_pmu), GFP_KERNEL);
|
||||
if (!hha_pmu)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, hha_pmu);
|
||||
|
||||
ret = hisi_hha_pmu_dev_probe(pdev, hha_pmu);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = cpuhp_state_add_instance(CPUHP_AP_PERF_ARM_HISI_HHA_ONLINE,
|
||||
&hha_pmu->node);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Error %d registering hotplug\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "hisi_sccl%u_hha%u",
|
||||
hha_pmu->sccl_id, hha_pmu->index_id);
|
||||
hha_pmu->pmu = (struct pmu) {
|
||||
.name = name,
|
||||
.task_ctx_nr = perf_invalid_context,
|
||||
.event_init = hisi_uncore_pmu_event_init,
|
||||
.pmu_enable = hisi_uncore_pmu_enable,
|
||||
.pmu_disable = hisi_uncore_pmu_disable,
|
||||
.add = hisi_uncore_pmu_add,
|
||||
.del = hisi_uncore_pmu_del,
|
||||
.start = hisi_uncore_pmu_start,
|
||||
.stop = hisi_uncore_pmu_stop,
|
||||
.read = hisi_uncore_pmu_read,
|
||||
.attr_groups = hisi_hha_pmu_attr_groups,
|
||||
};
|
||||
|
||||
ret = perf_pmu_register(&hha_pmu->pmu, name, -1);
|
||||
if (ret) {
|
||||
dev_err(hha_pmu->dev, "HHA PMU register failed!\n");
|
||||
cpuhp_state_remove_instance(CPUHP_AP_PERF_ARM_HISI_HHA_ONLINE,
|
||||
&hha_pmu->node);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hisi_hha_pmu_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct hisi_pmu *hha_pmu = platform_get_drvdata(pdev);
|
||||
|
||||
perf_pmu_unregister(&hha_pmu->pmu);
|
||||
cpuhp_state_remove_instance(CPUHP_AP_PERF_ARM_HISI_HHA_ONLINE,
|
||||
&hha_pmu->node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver hisi_hha_pmu_driver = {
|
||||
.driver = {
|
||||
.name = "hisi_hha_pmu",
|
||||
.acpi_match_table = ACPI_PTR(hisi_hha_pmu_acpi_match),
|
||||
},
|
||||
.probe = hisi_hha_pmu_probe,
|
||||
.remove = hisi_hha_pmu_remove,
|
||||
};
|
||||
|
||||
static int __init hisi_hha_pmu_module_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = cpuhp_setup_state_multi(CPUHP_AP_PERF_ARM_HISI_HHA_ONLINE,
|
||||
"AP_PERF_ARM_HISI_HHA_ONLINE",
|
||||
hisi_uncore_pmu_online_cpu,
|
||||
hisi_uncore_pmu_offline_cpu);
|
||||
if (ret) {
|
||||
pr_err("HHA PMU: Error setup hotplug, ret = %d;\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = platform_driver_register(&hisi_hha_pmu_driver);
|
||||
if (ret)
|
||||
cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_HHA_ONLINE);
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(hisi_hha_pmu_module_init);
|
||||
|
||||
static void __exit hisi_hha_pmu_module_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&hisi_hha_pmu_driver);
|
||||
cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_HHA_ONLINE);
|
||||
}
|
||||
module_exit(hisi_hha_pmu_module_exit);
|
||||
|
||||
MODULE_DESCRIPTION("HiSilicon SoC HHA uncore PMU driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_AUTHOR("Shaokun Zhang <zhangshaokun@hisilicon.com>");
|
||||
MODULE_AUTHOR("Anurup M <anurup.m@huawei.com>");
|
|
@ -0,0 +1,463 @@
|
|||
/*
|
||||
* HiSilicon SoC L3C uncore Hardware event counters support
|
||||
*
|
||||
* Copyright (C) 2017 Hisilicon Limited
|
||||
* Author: Anurup M <anurup.m@huawei.com>
|
||||
* Shaokun Zhang <zhangshaokun@hisilicon.com>
|
||||
*
|
||||
* This code is based on the uncore PMUs like arm-cci and arm-ccn.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/cpuhotplug.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/smp.h>
|
||||
|
||||
#include "hisi_uncore_pmu.h"
|
||||
|
||||
/* L3C register definition */
|
||||
#define L3C_PERF_CTRL 0x0408
|
||||
#define L3C_INT_MASK 0x0800
|
||||
#define L3C_INT_STATUS 0x0808
|
||||
#define L3C_INT_CLEAR 0x080c
|
||||
#define L3C_EVENT_CTRL 0x1c00
|
||||
#define L3C_EVENT_TYPE0 0x1d00
|
||||
/*
|
||||
* Each counter is 48-bits and [48:63] are reserved
|
||||
* which are Read-As-Zero and Writes-Ignored.
|
||||
*/
|
||||
#define L3C_CNTR0_LOWER 0x1e00
|
||||
|
||||
/* L3C has 8-counters */
|
||||
#define L3C_NR_COUNTERS 0x8
|
||||
|
||||
#define L3C_PERF_CTRL_EN 0x20000
|
||||
#define L3C_EVTYPE_NONE 0xff
|
||||
|
||||
/*
|
||||
* Select the counter register offset using the counter index
|
||||
*/
|
||||
static u32 hisi_l3c_pmu_get_counter_offset(int cntr_idx)
|
||||
{
|
||||
return (L3C_CNTR0_LOWER + (cntr_idx * 8));
|
||||
}
|
||||
|
||||
static u64 hisi_l3c_pmu_read_counter(struct hisi_pmu *l3c_pmu,
|
||||
struct hw_perf_event *hwc)
|
||||
{
|
||||
u32 idx = hwc->idx;
|
||||
|
||||
if (!hisi_uncore_pmu_counter_valid(l3c_pmu, idx)) {
|
||||
dev_err(l3c_pmu->dev, "Unsupported event index:%d!\n", idx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Read 64-bits and the upper 16 bits are RAZ */
|
||||
return readq(l3c_pmu->base + hisi_l3c_pmu_get_counter_offset(idx));
|
||||
}
|
||||
|
||||
static void hisi_l3c_pmu_write_counter(struct hisi_pmu *l3c_pmu,
|
||||
struct hw_perf_event *hwc, u64 val)
|
||||
{
|
||||
u32 idx = hwc->idx;
|
||||
|
||||
if (!hisi_uncore_pmu_counter_valid(l3c_pmu, idx)) {
|
||||
dev_err(l3c_pmu->dev, "Unsupported event index:%d!\n", idx);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Write 64-bits and the upper 16 bits are WI */
|
||||
writeq(val, l3c_pmu->base + hisi_l3c_pmu_get_counter_offset(idx));
|
||||
}
|
||||
|
||||
static void hisi_l3c_pmu_write_evtype(struct hisi_pmu *l3c_pmu, int idx,
|
||||
u32 type)
|
||||
{
|
||||
u32 reg, reg_idx, shift, val;
|
||||
|
||||
/*
|
||||
* Select the appropriate event select register(L3C_EVENT_TYPE0/1).
|
||||
* There are 2 event select registers for the 8 hardware counters.
|
||||
* Event code is 8-bits and for the former 4 hardware counters,
|
||||
* L3C_EVENT_TYPE0 is chosen. For the latter 4 hardware counters,
|
||||
* L3C_EVENT_TYPE1 is chosen.
|
||||
*/
|
||||
reg = L3C_EVENT_TYPE0 + (idx / 4) * 4;
|
||||
reg_idx = idx % 4;
|
||||
shift = 8 * reg_idx;
|
||||
|
||||
/* Write event code to L3C_EVENT_TYPEx Register */
|
||||
val = readl(l3c_pmu->base + reg);
|
||||
val &= ~(L3C_EVTYPE_NONE << shift);
|
||||
val |= (type << shift);
|
||||
writel(val, l3c_pmu->base + reg);
|
||||
}
|
||||
|
||||
static void hisi_l3c_pmu_start_counters(struct hisi_pmu *l3c_pmu)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/*
|
||||
* Set perf_enable bit in L3C_PERF_CTRL register to start counting
|
||||
* for all enabled counters.
|
||||
*/
|
||||
val = readl(l3c_pmu->base + L3C_PERF_CTRL);
|
||||
val |= L3C_PERF_CTRL_EN;
|
||||
writel(val, l3c_pmu->base + L3C_PERF_CTRL);
|
||||
}
|
||||
|
||||
static void hisi_l3c_pmu_stop_counters(struct hisi_pmu *l3c_pmu)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/*
|
||||
* Clear perf_enable bit in L3C_PERF_CTRL register to stop counting
|
||||
* for all enabled counters.
|
||||
*/
|
||||
val = readl(l3c_pmu->base + L3C_PERF_CTRL);
|
||||
val &= ~(L3C_PERF_CTRL_EN);
|
||||
writel(val, l3c_pmu->base + L3C_PERF_CTRL);
|
||||
}
|
||||
|
||||
static void hisi_l3c_pmu_enable_counter(struct hisi_pmu *l3c_pmu,
|
||||
struct hw_perf_event *hwc)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Enable counter index in L3C_EVENT_CTRL register */
|
||||
val = readl(l3c_pmu->base + L3C_EVENT_CTRL);
|
||||
val |= (1 << hwc->idx);
|
||||
writel(val, l3c_pmu->base + L3C_EVENT_CTRL);
|
||||
}
|
||||
|
||||
static void hisi_l3c_pmu_disable_counter(struct hisi_pmu *l3c_pmu,
|
||||
struct hw_perf_event *hwc)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Clear counter index in L3C_EVENT_CTRL register */
|
||||
val = readl(l3c_pmu->base + L3C_EVENT_CTRL);
|
||||
val &= ~(1 << hwc->idx);
|
||||
writel(val, l3c_pmu->base + L3C_EVENT_CTRL);
|
||||
}
|
||||
|
||||
static void hisi_l3c_pmu_enable_counter_int(struct hisi_pmu *l3c_pmu,
|
||||
struct hw_perf_event *hwc)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = readl(l3c_pmu->base + L3C_INT_MASK);
|
||||
/* Write 0 to enable interrupt */
|
||||
val &= ~(1 << hwc->idx);
|
||||
writel(val, l3c_pmu->base + L3C_INT_MASK);
|
||||
}
|
||||
|
||||
static void hisi_l3c_pmu_disable_counter_int(struct hisi_pmu *l3c_pmu,
|
||||
struct hw_perf_event *hwc)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = readl(l3c_pmu->base + L3C_INT_MASK);
|
||||
/* Write 1 to mask interrupt */
|
||||
val |= (1 << hwc->idx);
|
||||
writel(val, l3c_pmu->base + L3C_INT_MASK);
|
||||
}
|
||||
|
||||
static irqreturn_t hisi_l3c_pmu_isr(int irq, void *dev_id)
|
||||
{
|
||||
struct hisi_pmu *l3c_pmu = dev_id;
|
||||
struct perf_event *event;
|
||||
unsigned long overflown;
|
||||
int idx;
|
||||
|
||||
/* Read L3C_INT_STATUS register */
|
||||
overflown = readl(l3c_pmu->base + L3C_INT_STATUS);
|
||||
if (!overflown)
|
||||
return IRQ_NONE;
|
||||
|
||||
/*
|
||||
* Find the counter index which overflowed if the bit was set
|
||||
* and handle it.
|
||||
*/
|
||||
for_each_set_bit(idx, &overflown, L3C_NR_COUNTERS) {
|
||||
/* Write 1 to clear the IRQ status flag */
|
||||
writel((1 << idx), l3c_pmu->base + L3C_INT_CLEAR);
|
||||
|
||||
/* Get the corresponding event struct */
|
||||
event = l3c_pmu->pmu_events.hw_events[idx];
|
||||
if (!event)
|
||||
continue;
|
||||
|
||||
hisi_uncore_pmu_event_update(event);
|
||||
hisi_uncore_pmu_set_event_period(event);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int hisi_l3c_pmu_init_irq(struct hisi_pmu *l3c_pmu,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
int irq, ret;
|
||||
|
||||
/* Read and init IRQ */
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "L3C PMU get irq fail; irq:%d\n", irq);
|
||||
return irq;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, irq, hisi_l3c_pmu_isr,
|
||||
IRQF_NOBALANCING | IRQF_NO_THREAD,
|
||||
dev_name(&pdev->dev), l3c_pmu);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev,
|
||||
"Fail to request IRQ:%d ret:%d\n", irq, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
l3c_pmu->irq = irq;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id hisi_l3c_pmu_acpi_match[] = {
|
||||
{ "HISI0213", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, hisi_l3c_pmu_acpi_match);
|
||||
|
||||
static int hisi_l3c_pmu_init_data(struct platform_device *pdev,
|
||||
struct hisi_pmu *l3c_pmu)
|
||||
{
|
||||
unsigned long long id;
|
||||
struct resource *res;
|
||||
acpi_status status;
|
||||
|
||||
status = acpi_evaluate_integer(ACPI_HANDLE(&pdev->dev),
|
||||
"_UID", NULL, &id);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EINVAL;
|
||||
|
||||
l3c_pmu->index_id = id;
|
||||
|
||||
/*
|
||||
* Use the SCCL_ID and CCL_ID to identify the L3C PMU, while
|
||||
* SCCL_ID is in MPIDR[aff2] and CCL_ID is in MPIDR[aff1].
|
||||
*/
|
||||
if (device_property_read_u32(&pdev->dev, "hisilicon,scl-id",
|
||||
&l3c_pmu->sccl_id)) {
|
||||
dev_err(&pdev->dev, "Can not read l3c sccl-id!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (device_property_read_u32(&pdev->dev, "hisilicon,ccl-id",
|
||||
&l3c_pmu->ccl_id)) {
|
||||
dev_err(&pdev->dev, "Can not read l3c ccl-id!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
l3c_pmu->base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(l3c_pmu->base)) {
|
||||
dev_err(&pdev->dev, "ioremap failed for l3c_pmu resource\n");
|
||||
return PTR_ERR(l3c_pmu->base);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct attribute *hisi_l3c_pmu_format_attr[] = {
|
||||
HISI_PMU_FORMAT_ATTR(event, "config:0-7"),
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group hisi_l3c_pmu_format_group = {
|
||||
.name = "format",
|
||||
.attrs = hisi_l3c_pmu_format_attr,
|
||||
};
|
||||
|
||||
static struct attribute *hisi_l3c_pmu_events_attr[] = {
|
||||
HISI_PMU_EVENT_ATTR(rd_cpipe, 0x00),
|
||||
HISI_PMU_EVENT_ATTR(wr_cpipe, 0x01),
|
||||
HISI_PMU_EVENT_ATTR(rd_hit_cpipe, 0x02),
|
||||
HISI_PMU_EVENT_ATTR(wr_hit_cpipe, 0x03),
|
||||
HISI_PMU_EVENT_ATTR(victim_num, 0x04),
|
||||
HISI_PMU_EVENT_ATTR(rd_spipe, 0x20),
|
||||
HISI_PMU_EVENT_ATTR(wr_spipe, 0x21),
|
||||
HISI_PMU_EVENT_ATTR(rd_hit_spipe, 0x22),
|
||||
HISI_PMU_EVENT_ATTR(wr_hit_spipe, 0x23),
|
||||
HISI_PMU_EVENT_ATTR(back_invalid, 0x29),
|
||||
HISI_PMU_EVENT_ATTR(retry_cpu, 0x40),
|
||||
HISI_PMU_EVENT_ATTR(retry_ring, 0x41),
|
||||
HISI_PMU_EVENT_ATTR(prefetch_drop, 0x42),
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group hisi_l3c_pmu_events_group = {
|
||||
.name = "events",
|
||||
.attrs = hisi_l3c_pmu_events_attr,
|
||||
};
|
||||
|
||||
static DEVICE_ATTR(cpumask, 0444, hisi_cpumask_sysfs_show, NULL);
|
||||
|
||||
static struct attribute *hisi_l3c_pmu_cpumask_attrs[] = {
|
||||
&dev_attr_cpumask.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group hisi_l3c_pmu_cpumask_attr_group = {
|
||||
.attrs = hisi_l3c_pmu_cpumask_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group *hisi_l3c_pmu_attr_groups[] = {
|
||||
&hisi_l3c_pmu_format_group,
|
||||
&hisi_l3c_pmu_events_group,
|
||||
&hisi_l3c_pmu_cpumask_attr_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct hisi_uncore_ops hisi_uncore_l3c_ops = {
|
||||
.write_evtype = hisi_l3c_pmu_write_evtype,
|
||||
.get_event_idx = hisi_uncore_pmu_get_event_idx,
|
||||
.start_counters = hisi_l3c_pmu_start_counters,
|
||||
.stop_counters = hisi_l3c_pmu_stop_counters,
|
||||
.enable_counter = hisi_l3c_pmu_enable_counter,
|
||||
.disable_counter = hisi_l3c_pmu_disable_counter,
|
||||
.enable_counter_int = hisi_l3c_pmu_enable_counter_int,
|
||||
.disable_counter_int = hisi_l3c_pmu_disable_counter_int,
|
||||
.write_counter = hisi_l3c_pmu_write_counter,
|
||||
.read_counter = hisi_l3c_pmu_read_counter,
|
||||
};
|
||||
|
||||
static int hisi_l3c_pmu_dev_probe(struct platform_device *pdev,
|
||||
struct hisi_pmu *l3c_pmu)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hisi_l3c_pmu_init_data(pdev, l3c_pmu);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = hisi_l3c_pmu_init_irq(l3c_pmu, pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
l3c_pmu->num_counters = L3C_NR_COUNTERS;
|
||||
l3c_pmu->counter_bits = 48;
|
||||
l3c_pmu->ops = &hisi_uncore_l3c_ops;
|
||||
l3c_pmu->dev = &pdev->dev;
|
||||
l3c_pmu->on_cpu = -1;
|
||||
l3c_pmu->check_event = 0x59;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hisi_l3c_pmu_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct hisi_pmu *l3c_pmu;
|
||||
char *name;
|
||||
int ret;
|
||||
|
||||
l3c_pmu = devm_kzalloc(&pdev->dev, sizeof(*l3c_pmu), GFP_KERNEL);
|
||||
if (!l3c_pmu)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, l3c_pmu);
|
||||
|
||||
ret = hisi_l3c_pmu_dev_probe(pdev, l3c_pmu);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = cpuhp_state_add_instance(CPUHP_AP_PERF_ARM_HISI_L3_ONLINE,
|
||||
&l3c_pmu->node);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Error %d registering hotplug\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "hisi_sccl%u_l3c%u",
|
||||
l3c_pmu->sccl_id, l3c_pmu->index_id);
|
||||
l3c_pmu->pmu = (struct pmu) {
|
||||
.name = name,
|
||||
.task_ctx_nr = perf_invalid_context,
|
||||
.event_init = hisi_uncore_pmu_event_init,
|
||||
.pmu_enable = hisi_uncore_pmu_enable,
|
||||
.pmu_disable = hisi_uncore_pmu_disable,
|
||||
.add = hisi_uncore_pmu_add,
|
||||
.del = hisi_uncore_pmu_del,
|
||||
.start = hisi_uncore_pmu_start,
|
||||
.stop = hisi_uncore_pmu_stop,
|
||||
.read = hisi_uncore_pmu_read,
|
||||
.attr_groups = hisi_l3c_pmu_attr_groups,
|
||||
};
|
||||
|
||||
ret = perf_pmu_register(&l3c_pmu->pmu, name, -1);
|
||||
if (ret) {
|
||||
dev_err(l3c_pmu->dev, "L3C PMU register failed!\n");
|
||||
cpuhp_state_remove_instance(CPUHP_AP_PERF_ARM_HISI_L3_ONLINE,
|
||||
&l3c_pmu->node);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hisi_l3c_pmu_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct hisi_pmu *l3c_pmu = platform_get_drvdata(pdev);
|
||||
|
||||
perf_pmu_unregister(&l3c_pmu->pmu);
|
||||
cpuhp_state_remove_instance(CPUHP_AP_PERF_ARM_HISI_L3_ONLINE,
|
||||
&l3c_pmu->node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver hisi_l3c_pmu_driver = {
|
||||
.driver = {
|
||||
.name = "hisi_l3c_pmu",
|
||||
.acpi_match_table = ACPI_PTR(hisi_l3c_pmu_acpi_match),
|
||||
},
|
||||
.probe = hisi_l3c_pmu_probe,
|
||||
.remove = hisi_l3c_pmu_remove,
|
||||
};
|
||||
|
||||
static int __init hisi_l3c_pmu_module_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = cpuhp_setup_state_multi(CPUHP_AP_PERF_ARM_HISI_L3_ONLINE,
|
||||
"AP_PERF_ARM_HISI_L3_ONLINE",
|
||||
hisi_uncore_pmu_online_cpu,
|
||||
hisi_uncore_pmu_offline_cpu);
|
||||
if (ret) {
|
||||
pr_err("L3C PMU: Error setup hotplug, ret = %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = platform_driver_register(&hisi_l3c_pmu_driver);
|
||||
if (ret)
|
||||
cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_L3_ONLINE);
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(hisi_l3c_pmu_module_init);
|
||||
|
||||
static void __exit hisi_l3c_pmu_module_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&hisi_l3c_pmu_driver);
|
||||
cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_L3_ONLINE);
|
||||
}
|
||||
module_exit(hisi_l3c_pmu_module_exit);
|
||||
|
||||
MODULE_DESCRIPTION("HiSilicon SoC L3C uncore PMU driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_AUTHOR("Anurup M <anurup.m@huawei.com>");
|
||||
MODULE_AUTHOR("Shaokun Zhang <zhangshaokun@hisilicon.com>");
|
|
@ -0,0 +1,447 @@
|
|||
/*
|
||||
* HiSilicon SoC Hardware event counters support
|
||||
*
|
||||
* Copyright (C) 2017 Hisilicon Limited
|
||||
* Author: Anurup M <anurup.m@huawei.com>
|
||||
* Shaokun Zhang <zhangshaokun@hisilicon.com>
|
||||
*
|
||||
* This code is based on the uncore PMUs like arm-cci and arm-ccn.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
#include <asm/local64.h>
|
||||
|
||||
#include "hisi_uncore_pmu.h"
|
||||
|
||||
#define HISI_GET_EVENTID(ev) (ev->hw.config_base & 0xff)
|
||||
#define HISI_MAX_PERIOD(nr) (BIT_ULL(nr) - 1)
|
||||
|
||||
/*
|
||||
* PMU format attributes
|
||||
*/
|
||||
ssize_t hisi_format_sysfs_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct dev_ext_attribute *eattr;
|
||||
|
||||
eattr = container_of(attr, struct dev_ext_attribute, attr);
|
||||
|
||||
return sprintf(buf, "%s\n", (char *)eattr->var);
|
||||
}
|
||||
|
||||
/*
|
||||
* PMU event attributes
|
||||
*/
|
||||
ssize_t hisi_event_sysfs_show(struct device *dev,
|
||||
struct device_attribute *attr, char *page)
|
||||
{
|
||||
struct dev_ext_attribute *eattr;
|
||||
|
||||
eattr = container_of(attr, struct dev_ext_attribute, attr);
|
||||
|
||||
return sprintf(page, "config=0x%lx\n", (unsigned long)eattr->var);
|
||||
}
|
||||
|
||||
/*
|
||||
* sysfs cpumask attributes. For uncore PMU, we only have a single CPU to show
|
||||
*/
|
||||
ssize_t hisi_cpumask_sysfs_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct hisi_pmu *hisi_pmu = to_hisi_pmu(dev_get_drvdata(dev));
|
||||
|
||||
return sprintf(buf, "%d\n", hisi_pmu->on_cpu);
|
||||
}
|
||||
|
||||
static bool hisi_validate_event_group(struct perf_event *event)
|
||||
{
|
||||
struct perf_event *sibling, *leader = event->group_leader;
|
||||
struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu);
|
||||
/* Include count for the event */
|
||||
int counters = 1;
|
||||
|
||||
if (!is_software_event(leader)) {
|
||||
/*
|
||||
* We must NOT create groups containing mixed PMUs, although
|
||||
* software events are acceptable
|
||||
*/
|
||||
if (leader->pmu != event->pmu)
|
||||
return false;
|
||||
|
||||
/* Increment counter for the leader */
|
||||
if (leader != event)
|
||||
counters++;
|
||||
}
|
||||
|
||||
list_for_each_entry(sibling, &event->group_leader->sibling_list,
|
||||
group_entry) {
|
||||
if (is_software_event(sibling))
|
||||
continue;
|
||||
if (sibling->pmu != event->pmu)
|
||||
return false;
|
||||
/* Increment counter for each sibling */
|
||||
counters++;
|
||||
}
|
||||
|
||||
/* The group can not count events more than the counters in the HW */
|
||||
return counters <= hisi_pmu->num_counters;
|
||||
}
|
||||
|
||||
int hisi_uncore_pmu_counter_valid(struct hisi_pmu *hisi_pmu, int idx)
|
||||
{
|
||||
return idx >= 0 && idx < hisi_pmu->num_counters;
|
||||
}
|
||||
|
||||
int hisi_uncore_pmu_get_event_idx(struct perf_event *event)
|
||||
{
|
||||
struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu);
|
||||
unsigned long *used_mask = hisi_pmu->pmu_events.used_mask;
|
||||
u32 num_counters = hisi_pmu->num_counters;
|
||||
int idx;
|
||||
|
||||
idx = find_first_zero_bit(used_mask, num_counters);
|
||||
if (idx == num_counters)
|
||||
return -EAGAIN;
|
||||
|
||||
set_bit(idx, used_mask);
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
static void hisi_uncore_pmu_clear_event_idx(struct hisi_pmu *hisi_pmu, int idx)
|
||||
{
|
||||
if (!hisi_uncore_pmu_counter_valid(hisi_pmu, idx)) {
|
||||
dev_err(hisi_pmu->dev, "Unsupported event index:%d!\n", idx);
|
||||
return;
|
||||
}
|
||||
|
||||
clear_bit(idx, hisi_pmu->pmu_events.used_mask);
|
||||
}
|
||||
|
||||
int hisi_uncore_pmu_event_init(struct perf_event *event)
|
||||
{
|
||||
struct hw_perf_event *hwc = &event->hw;
|
||||
struct hisi_pmu *hisi_pmu;
|
||||
|
||||
if (event->attr.type != event->pmu->type)
|
||||
return -ENOENT;
|
||||
|
||||
/*
|
||||
* We do not support sampling as the counters are all
|
||||
* shared by all CPU cores in a CPU die(SCCL). Also we
|
||||
* do not support attach to a task(per-process mode)
|
||||
*/
|
||||
if (is_sampling_event(event) || event->attach_state & PERF_ATTACH_TASK)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/* counters do not have these bits */
|
||||
if (event->attr.exclude_user ||
|
||||
event->attr.exclude_kernel ||
|
||||
event->attr.exclude_host ||
|
||||
event->attr.exclude_guest ||
|
||||
event->attr.exclude_hv ||
|
||||
event->attr.exclude_idle)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* The uncore counters not specific to any CPU, so cannot
|
||||
* support per-task
|
||||
*/
|
||||
if (event->cpu < 0)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Validate if the events in group does not exceed the
|
||||
* available counters in hardware.
|
||||
*/
|
||||
if (!hisi_validate_event_group(event))
|
||||
return -EINVAL;
|
||||
|
||||
hisi_pmu = to_hisi_pmu(event->pmu);
|
||||
if (event->attr.config > hisi_pmu->check_event)
|
||||
return -EINVAL;
|
||||
|
||||
if (hisi_pmu->on_cpu == -1)
|
||||
return -EINVAL;
|
||||
/*
|
||||
* We don't assign an index until we actually place the event onto
|
||||
* hardware. Use -1 to signify that we haven't decided where to put it
|
||||
* yet.
|
||||
*/
|
||||
hwc->idx = -1;
|
||||
hwc->config_base = event->attr.config;
|
||||
|
||||
/* Enforce to use the same CPU for all events in this PMU */
|
||||
event->cpu = hisi_pmu->on_cpu;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the counter to count the event that we're interested in,
|
||||
* and enable interrupt and counter.
|
||||
*/
|
||||
static void hisi_uncore_pmu_enable_event(struct perf_event *event)
|
||||
{
|
||||
struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu);
|
||||
struct hw_perf_event *hwc = &event->hw;
|
||||
|
||||
hisi_pmu->ops->write_evtype(hisi_pmu, hwc->idx,
|
||||
HISI_GET_EVENTID(event));
|
||||
|
||||
hisi_pmu->ops->enable_counter_int(hisi_pmu, hwc);
|
||||
hisi_pmu->ops->enable_counter(hisi_pmu, hwc);
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable counter and interrupt.
|
||||
*/
|
||||
static void hisi_uncore_pmu_disable_event(struct perf_event *event)
|
||||
{
|
||||
struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu);
|
||||
struct hw_perf_event *hwc = &event->hw;
|
||||
|
||||
hisi_pmu->ops->disable_counter(hisi_pmu, hwc);
|
||||
hisi_pmu->ops->disable_counter_int(hisi_pmu, hwc);
|
||||
}
|
||||
|
||||
void hisi_uncore_pmu_set_event_period(struct perf_event *event)
|
||||
{
|
||||
struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu);
|
||||
struct hw_perf_event *hwc = &event->hw;
|
||||
|
||||
/*
|
||||
* The HiSilicon PMU counters support 32 bits or 48 bits, depending on
|
||||
* the PMU. We reduce it to 2^(counter_bits - 1) to account for the
|
||||
* extreme interrupt latency. So we could hopefully handle the overflow
|
||||
* interrupt before another 2^(counter_bits - 1) events occur and the
|
||||
* counter overtakes its previous value.
|
||||
*/
|
||||
u64 val = BIT_ULL(hisi_pmu->counter_bits - 1);
|
||||
|
||||
local64_set(&hwc->prev_count, val);
|
||||
/* Write start value to the hardware event counter */
|
||||
hisi_pmu->ops->write_counter(hisi_pmu, hwc, val);
|
||||
}
|
||||
|
||||
void hisi_uncore_pmu_event_update(struct perf_event *event)
|
||||
{
|
||||
struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu);
|
||||
struct hw_perf_event *hwc = &event->hw;
|
||||
u64 delta, prev_raw_count, new_raw_count;
|
||||
|
||||
do {
|
||||
/* Read the count from the counter register */
|
||||
new_raw_count = hisi_pmu->ops->read_counter(hisi_pmu, hwc);
|
||||
prev_raw_count = local64_read(&hwc->prev_count);
|
||||
} while (local64_cmpxchg(&hwc->prev_count, prev_raw_count,
|
||||
new_raw_count) != prev_raw_count);
|
||||
/*
|
||||
* compute the delta
|
||||
*/
|
||||
delta = (new_raw_count - prev_raw_count) &
|
||||
HISI_MAX_PERIOD(hisi_pmu->counter_bits);
|
||||
local64_add(delta, &event->count);
|
||||
}
|
||||
|
||||
void hisi_uncore_pmu_start(struct perf_event *event, int flags)
|
||||
{
|
||||
struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu);
|
||||
struct hw_perf_event *hwc = &event->hw;
|
||||
|
||||
if (WARN_ON_ONCE(!(hwc->state & PERF_HES_STOPPED)))
|
||||
return;
|
||||
|
||||
WARN_ON_ONCE(!(hwc->state & PERF_HES_UPTODATE));
|
||||
hwc->state = 0;
|
||||
hisi_uncore_pmu_set_event_period(event);
|
||||
|
||||
if (flags & PERF_EF_RELOAD) {
|
||||
u64 prev_raw_count = local64_read(&hwc->prev_count);
|
||||
|
||||
hisi_pmu->ops->write_counter(hisi_pmu, hwc, prev_raw_count);
|
||||
}
|
||||
|
||||
hisi_uncore_pmu_enable_event(event);
|
||||
perf_event_update_userpage(event);
|
||||
}
|
||||
|
||||
void hisi_uncore_pmu_stop(struct perf_event *event, int flags)
|
||||
{
|
||||
struct hw_perf_event *hwc = &event->hw;
|
||||
|
||||
hisi_uncore_pmu_disable_event(event);
|
||||
WARN_ON_ONCE(hwc->state & PERF_HES_STOPPED);
|
||||
hwc->state |= PERF_HES_STOPPED;
|
||||
|
||||
if (hwc->state & PERF_HES_UPTODATE)
|
||||
return;
|
||||
|
||||
/* Read hardware counter and update the perf counter statistics */
|
||||
hisi_uncore_pmu_event_update(event);
|
||||
hwc->state |= PERF_HES_UPTODATE;
|
||||
}
|
||||
|
||||
int hisi_uncore_pmu_add(struct perf_event *event, int flags)
|
||||
{
|
||||
struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu);
|
||||
struct hw_perf_event *hwc = &event->hw;
|
||||
int idx;
|
||||
|
||||
hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE;
|
||||
|
||||
/* Get an available counter index for counting */
|
||||
idx = hisi_pmu->ops->get_event_idx(event);
|
||||
if (idx < 0)
|
||||
return idx;
|
||||
|
||||
event->hw.idx = idx;
|
||||
hisi_pmu->pmu_events.hw_events[idx] = event;
|
||||
|
||||
if (flags & PERF_EF_START)
|
||||
hisi_uncore_pmu_start(event, PERF_EF_RELOAD);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hisi_uncore_pmu_del(struct perf_event *event, int flags)
|
||||
{
|
||||
struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu);
|
||||
struct hw_perf_event *hwc = &event->hw;
|
||||
|
||||
hisi_uncore_pmu_stop(event, PERF_EF_UPDATE);
|
||||
hisi_uncore_pmu_clear_event_idx(hisi_pmu, hwc->idx);
|
||||
perf_event_update_userpage(event);
|
||||
hisi_pmu->pmu_events.hw_events[hwc->idx] = NULL;
|
||||
}
|
||||
|
||||
void hisi_uncore_pmu_read(struct perf_event *event)
|
||||
{
|
||||
/* Read hardware counter and update the perf counter statistics */
|
||||
hisi_uncore_pmu_event_update(event);
|
||||
}
|
||||
|
||||
void hisi_uncore_pmu_enable(struct pmu *pmu)
|
||||
{
|
||||
struct hisi_pmu *hisi_pmu = to_hisi_pmu(pmu);
|
||||
int enabled = bitmap_weight(hisi_pmu->pmu_events.used_mask,
|
||||
hisi_pmu->num_counters);
|
||||
|
||||
if (!enabled)
|
||||
return;
|
||||
|
||||
hisi_pmu->ops->start_counters(hisi_pmu);
|
||||
}
|
||||
|
||||
void hisi_uncore_pmu_disable(struct pmu *pmu)
|
||||
{
|
||||
struct hisi_pmu *hisi_pmu = to_hisi_pmu(pmu);
|
||||
|
||||
hisi_pmu->ops->stop_counters(hisi_pmu);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read Super CPU cluster and CPU cluster ID from MPIDR_EL1.
|
||||
* If multi-threading is supported, SCCL_ID is in MPIDR[aff3] and CCL_ID
|
||||
* is in MPIDR[aff2]; if not, SCCL_ID is in MPIDR[aff2] and CCL_ID is
|
||||
* in MPIDR[aff1]. If this changes in future, this shall be updated.
|
||||
*/
|
||||
static void hisi_read_sccl_and_ccl_id(int *sccl_id, int *ccl_id)
|
||||
{
|
||||
u64 mpidr = read_cpuid_mpidr();
|
||||
|
||||
if (mpidr & MPIDR_MT_BITMASK) {
|
||||
if (sccl_id)
|
||||
*sccl_id = MPIDR_AFFINITY_LEVEL(mpidr, 3);
|
||||
if (ccl_id)
|
||||
*ccl_id = MPIDR_AFFINITY_LEVEL(mpidr, 2);
|
||||
} else {
|
||||
if (sccl_id)
|
||||
*sccl_id = MPIDR_AFFINITY_LEVEL(mpidr, 2);
|
||||
if (ccl_id)
|
||||
*ccl_id = MPIDR_AFFINITY_LEVEL(mpidr, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether the CPU is associated with this uncore PMU
|
||||
*/
|
||||
static bool hisi_pmu_cpu_is_associated_pmu(struct hisi_pmu *hisi_pmu)
|
||||
{
|
||||
int sccl_id, ccl_id;
|
||||
|
||||
if (hisi_pmu->ccl_id == -1) {
|
||||
/* If CCL_ID is -1, the PMU only shares the same SCCL */
|
||||
hisi_read_sccl_and_ccl_id(&sccl_id, NULL);
|
||||
|
||||
return sccl_id == hisi_pmu->sccl_id;
|
||||
}
|
||||
|
||||
hisi_read_sccl_and_ccl_id(&sccl_id, &ccl_id);
|
||||
|
||||
return sccl_id == hisi_pmu->sccl_id && ccl_id == hisi_pmu->ccl_id;
|
||||
}
|
||||
|
||||
int hisi_uncore_pmu_online_cpu(unsigned int cpu, struct hlist_node *node)
|
||||
{
|
||||
struct hisi_pmu *hisi_pmu = hlist_entry_safe(node, struct hisi_pmu,
|
||||
node);
|
||||
|
||||
if (!hisi_pmu_cpu_is_associated_pmu(hisi_pmu))
|
||||
return 0;
|
||||
|
||||
cpumask_set_cpu(cpu, &hisi_pmu->associated_cpus);
|
||||
|
||||
/* If another CPU is already managing this PMU, simply return. */
|
||||
if (hisi_pmu->on_cpu != -1)
|
||||
return 0;
|
||||
|
||||
/* Use this CPU in cpumask for event counting */
|
||||
hisi_pmu->on_cpu = cpu;
|
||||
|
||||
/* Overflow interrupt also should use the same CPU */
|
||||
WARN_ON(irq_set_affinity(hisi_pmu->irq, cpumask_of(cpu)));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hisi_uncore_pmu_offline_cpu(unsigned int cpu, struct hlist_node *node)
|
||||
{
|
||||
struct hisi_pmu *hisi_pmu = hlist_entry_safe(node, struct hisi_pmu,
|
||||
node);
|
||||
cpumask_t pmu_online_cpus;
|
||||
unsigned int target;
|
||||
|
||||
if (!cpumask_test_and_clear_cpu(cpu, &hisi_pmu->associated_cpus))
|
||||
return 0;
|
||||
|
||||
/* Nothing to do if this CPU doesn't own the PMU */
|
||||
if (hisi_pmu->on_cpu != cpu)
|
||||
return 0;
|
||||
|
||||
/* Give up ownership of the PMU */
|
||||
hisi_pmu->on_cpu = -1;
|
||||
|
||||
/* Choose a new CPU to migrate ownership of the PMU to */
|
||||
cpumask_and(&pmu_online_cpus, &hisi_pmu->associated_cpus,
|
||||
cpu_online_mask);
|
||||
target = cpumask_any_but(&pmu_online_cpus, cpu);
|
||||
if (target >= nr_cpu_ids)
|
||||
return 0;
|
||||
|
||||
perf_pmu_migrate_context(&hisi_pmu->pmu, cpu, target);
|
||||
/* Use this CPU for event counting */
|
||||
hisi_pmu->on_cpu = target;
|
||||
WARN_ON(irq_set_affinity(hisi_pmu->irq, cpumask_of(target)));
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* HiSilicon SoC Hardware event counters support
|
||||
*
|
||||
* Copyright (C) 2017 Hisilicon Limited
|
||||
* Author: Anurup M <anurup.m@huawei.com>
|
||||
* Shaokun Zhang <zhangshaokun@hisilicon.com>
|
||||
*
|
||||
* This code is based on the uncore PMUs like arm-cci and arm-ccn.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef __HISI_UNCORE_PMU_H__
|
||||
#define __HISI_UNCORE_PMU_H__
|
||||
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#undef pr_fmt
|
||||
#define pr_fmt(fmt) "hisi_pmu: " fmt
|
||||
|
||||
#define HISI_MAX_COUNTERS 0x10
|
||||
#define to_hisi_pmu(p) (container_of(p, struct hisi_pmu, pmu))
|
||||
|
||||
#define HISI_PMU_ATTR(_name, _func, _config) \
|
||||
(&((struct dev_ext_attribute[]) { \
|
||||
{ __ATTR(_name, 0444, _func, NULL), (void *)_config } \
|
||||
})[0].attr.attr)
|
||||
|
||||
#define HISI_PMU_FORMAT_ATTR(_name, _config) \
|
||||
HISI_PMU_ATTR(_name, hisi_format_sysfs_show, (void *)_config)
|
||||
#define HISI_PMU_EVENT_ATTR(_name, _config) \
|
||||
HISI_PMU_ATTR(_name, hisi_event_sysfs_show, (unsigned long)_config)
|
||||
|
||||
struct hisi_pmu;
|
||||
|
||||
struct hisi_uncore_ops {
|
||||
void (*write_evtype)(struct hisi_pmu *, int, u32);
|
||||
int (*get_event_idx)(struct perf_event *);
|
||||
u64 (*read_counter)(struct hisi_pmu *, struct hw_perf_event *);
|
||||
void (*write_counter)(struct hisi_pmu *, struct hw_perf_event *, u64);
|
||||
void (*enable_counter)(struct hisi_pmu *, struct hw_perf_event *);
|
||||
void (*disable_counter)(struct hisi_pmu *, struct hw_perf_event *);
|
||||
void (*enable_counter_int)(struct hisi_pmu *, struct hw_perf_event *);
|
||||
void (*disable_counter_int)(struct hisi_pmu *, struct hw_perf_event *);
|
||||
void (*start_counters)(struct hisi_pmu *);
|
||||
void (*stop_counters)(struct hisi_pmu *);
|
||||
};
|
||||
|
||||
struct hisi_pmu_hwevents {
|
||||
struct perf_event *hw_events[HISI_MAX_COUNTERS];
|
||||
DECLARE_BITMAP(used_mask, HISI_MAX_COUNTERS);
|
||||
};
|
||||
|
||||
/* Generic pmu struct for different pmu types */
|
||||
struct hisi_pmu {
|
||||
struct pmu pmu;
|
||||
const struct hisi_uncore_ops *ops;
|
||||
struct hisi_pmu_hwevents pmu_events;
|
||||
/* associated_cpus: All CPUs associated with the PMU */
|
||||
cpumask_t associated_cpus;
|
||||
/* CPU used for counting */
|
||||
int on_cpu;
|
||||
int irq;
|
||||
struct device *dev;
|
||||
struct hlist_node node;
|
||||
int sccl_id;
|
||||
int ccl_id;
|
||||
void __iomem *base;
|
||||
/* the ID of the PMU modules */
|
||||
u32 index_id;
|
||||
int num_counters;
|
||||
int counter_bits;
|
||||
/* check event code range */
|
||||
int check_event;
|
||||
};
|
||||
|
||||
int hisi_uncore_pmu_counter_valid(struct hisi_pmu *hisi_pmu, int idx);
|
||||
int hisi_uncore_pmu_get_event_idx(struct perf_event *event);
|
||||
void hisi_uncore_pmu_read(struct perf_event *event);
|
||||
int hisi_uncore_pmu_add(struct perf_event *event, int flags);
|
||||
void hisi_uncore_pmu_del(struct perf_event *event, int flags);
|
||||
void hisi_uncore_pmu_start(struct perf_event *event, int flags);
|
||||
void hisi_uncore_pmu_stop(struct perf_event *event, int flags);
|
||||
void hisi_uncore_pmu_set_event_period(struct perf_event *event);
|
||||
void hisi_uncore_pmu_event_update(struct perf_event *event);
|
||||
int hisi_uncore_pmu_event_init(struct perf_event *event);
|
||||
void hisi_uncore_pmu_enable(struct pmu *pmu);
|
||||
void hisi_uncore_pmu_disable(struct pmu *pmu);
|
||||
ssize_t hisi_event_sysfs_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf);
|
||||
ssize_t hisi_format_sysfs_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf);
|
||||
ssize_t hisi_cpumask_sysfs_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf);
|
||||
int hisi_uncore_pmu_online_cpu(unsigned int cpu, struct hlist_node *node);
|
||||
int hisi_uncore_pmu_offline_cpu(unsigned int cpu, struct hlist_node *node);
|
||||
#endif /* __HISI_UNCORE_PMU_H__ */
|
|
@ -153,6 +153,9 @@ enum cpuhp_state {
|
|||
CPUHP_AP_PERF_S390_SF_ONLINE,
|
||||
CPUHP_AP_PERF_ARM_CCI_ONLINE,
|
||||
CPUHP_AP_PERF_ARM_CCN_ONLINE,
|
||||
CPUHP_AP_PERF_ARM_HISI_DDRC_ONLINE,
|
||||
CPUHP_AP_PERF_ARM_HISI_HHA_ONLINE,
|
||||
CPUHP_AP_PERF_ARM_HISI_L3_ONLINE,
|
||||
CPUHP_AP_PERF_ARM_L2X0_ONLINE,
|
||||
CPUHP_AP_PERF_ARM_QCOM_L2_ONLINE,
|
||||
CPUHP_AP_PERF_ARM_QCOM_L3_ONLINE,
|
||||
|
|
|
@ -941,6 +941,7 @@ enum perf_callchain_context {
|
|||
#define PERF_AUX_FLAG_TRUNCATED 0x01 /* record was truncated to fit */
|
||||
#define PERF_AUX_FLAG_OVERWRITE 0x02 /* snapshot from overwrite mode */
|
||||
#define PERF_AUX_FLAG_PARTIAL 0x04 /* record contains gaps */
|
||||
#define PERF_AUX_FLAG_COLLISION 0x08 /* sample collided with another */
|
||||
|
||||
#define PERF_FLAG_FD_NO_GROUP (1UL << 0)
|
||||
#define PERF_FLAG_FD_OUTPUT (1UL << 1)
|
||||
|
|
|
@ -411,6 +411,7 @@ err:
|
|||
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(perf_aux_output_begin);
|
||||
|
||||
static bool __always_inline rb_need_aux_wakeup(struct ring_buffer *rb)
|
||||
{
|
||||
|
@ -480,6 +481,7 @@ void perf_aux_output_end(struct perf_output_handle *handle, unsigned long size)
|
|||
rb_free_aux(rb);
|
||||
ring_buffer_put(rb);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(perf_aux_output_end);
|
||||
|
||||
/*
|
||||
* Skip over a given number of bytes in the AUX buffer, due to, for example,
|
||||
|
@ -505,6 +507,7 @@ int perf_aux_output_skip(struct perf_output_handle *handle, unsigned long size)
|
|||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(perf_aux_output_skip);
|
||||
|
||||
void *perf_get_aux(struct perf_output_handle *handle)
|
||||
{
|
||||
|
@ -514,6 +517,7 @@ void *perf_get_aux(struct perf_output_handle *handle)
|
|||
|
||||
return handle->rb->aux_priv;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(perf_get_aux);
|
||||
|
||||
#define PERF_AUX_GFP (GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN | __GFP_NORETRY)
|
||||
|
||||
|
|
|
@ -863,6 +863,7 @@ int irq_get_percpu_devid_partition(unsigned int irq, struct cpumask *affinity)
|
|||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(irq_get_percpu_devid_partition);
|
||||
|
||||
void kstat_incr_irq_this_cpu(unsigned int irq)
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче