// SPDX-License-Identifier: GPL-2.0
/*
* CPU PMU driver for the Apple M1 and derivatives
*
* Copyright ( C ) 2021 Google LLC
*
* Author : Marc Zyngier < maz @ kernel . org >
*
* Most of the information used in this driver was provided by the
* Asahi Linux project . The rest was experimentally discovered .
*/
#include <linux/of.h>
#include <linux/perf/arm_pmu.h>
#include <linux/perf/arm_pmuv3.h>
#include <linux/platform_device.h>
#include <asm /apple_m1_pmu.h>
#include <asm /irq_regs.h>
#include <asm /perf_event.h>
#define M1_PMU_NR_COUNTERS 10
#define M1_PMU_CFG_EVENT GENMASK(7 , 0 )
#define ANY_BUT_0_1 GENMASK(9 , 2 )
#define ONLY_2_TO_7 GENMASK(7 , 2 )
#define ONLY_2_4_6 (BIT(2 ) | BIT(4 ) | BIT(6 ))
#define ONLY_5_6_7 (BIT(5 ) | BIT(6 ) | BIT(7 ))
/*
* Description of the events we actually know about , as well as those with
* a specific counter affinity . Yes , this is a grand total of two known
* counters , and the rest is anybody ' s guess .
*
* Not all counters can count all events . Counters # 0 and # 1 are wired to
* count cycles and instructions respectively , and some events have
* bizarre mappings ( every other counter , or even * one * counter ) . These
* restrictions equally apply to both P and E cores .
*
* It is worth noting that the PMUs attached to P and E cores are likely
* to be different because the underlying uarches are different . At the
* moment , we don ' t really need to distinguish between the two because we
* know next to nothing about the events themselves , and we already have
* per cpu - type PMU abstractions .
*
* If we eventually find out that the events are different across
* implementations , we ' ll have to introduce per cpu - type tables .
*/
enum m1_pmu_events {
M1_PMU_PERFCTR_RETIRE_UOP = 0 x1,
M1_PMU_PERFCTR_CORE_ACTIVE_CYCLE = 0 x2,
M1_PMU_PERFCTR_L1I_TLB_FILL = 0 x4,
M1_PMU_PERFCTR_L1D_TLB_FILL = 0 x5,
M1_PMU_PERFCTR_MMU_TABLE_WALK_INSTRUCTION = 0 x7,
M1_PMU_PERFCTR_MMU_TABLE_WALK_DATA = 0 x8,
M1_PMU_PERFCTR_L2_TLB_MISS_INSTRUCTION = 0 xa,
M1_PMU_PERFCTR_L2_TLB_MISS_DATA = 0 xb,
M1_PMU_PERFCTR_MMU_VIRTUAL_MEMORY_FAULT_NONSPEC = 0 xd,
M1_PMU_PERFCTR_SCHEDULE_UOP = 0 x52,
M1_PMU_PERFCTR_INTERRUPT_PENDING = 0 x6c,
M1_PMU_PERFCTR_MAP_STALL_DISPATCH = 0 x70,
M1_PMU_PERFCTR_MAP_REWIND = 0 x75,
M1_PMU_PERFCTR_MAP_STALL = 0 x76,
M1_PMU_PERFCTR_MAP_INT_UOP = 0 x7c,
M1_PMU_PERFCTR_MAP_LDST_UOP = 0 x7d,
M1_PMU_PERFCTR_MAP_SIMD_UOP = 0 x7e,
M1_PMU_PERFCTR_FLUSH_RESTART_OTHER_NONSPEC = 0 x84,
M1_PMU_PERFCTR_INST_ALL = 0 x8c,
M1_PMU_PERFCTR_INST_BRANCH = 0 x8d,
M1_PMU_PERFCTR_INST_BRANCH_CALL = 0 x8e,
M1_PMU_PERFCTR_INST_BRANCH_RET = 0 x8f,
M1_PMU_PERFCTR_INST_BRANCH_TAKEN = 0 x90,
M1_PMU_PERFCTR_INST_BRANCH_INDIR = 0 x93,
M1_PMU_PERFCTR_INST_BRANCH_COND = 0 x94,
M1_PMU_PERFCTR_INST_INT_LD = 0 x95,
M1_PMU_PERFCTR_INST_INT_ST = 0 x96,
M1_PMU_PERFCTR_INST_INT_ALU = 0 x97,
M1_PMU_PERFCTR_INST_SIMD_LD = 0 x98,
M1_PMU_PERFCTR_INST_SIMD_ST = 0 x99,
M1_PMU_PERFCTR_INST_SIMD_ALU = 0 x9a,
M1_PMU_PERFCTR_INST_LDST = 0 x9b,
M1_PMU_PERFCTR_INST_BARRIER = 0 x9c,
M1_PMU_PERFCTR_UNKNOWN_9f = 0 x9f,
M1_PMU_PERFCTR_L1D_TLB_ACCESS = 0 xa0,
M1_PMU_PERFCTR_L1D_TLB_MISS = 0 xa1,
M1_PMU_PERFCTR_L1D_CACHE_MISS_ST = 0 xa2,
M1_PMU_PERFCTR_L1D_CACHE_MISS_LD = 0 xa3,
M1_PMU_PERFCTR_LD_UNIT_UOP = 0 xa6,
M1_PMU_PERFCTR_ST_UNIT_UOP = 0 xa7,
M1_PMU_PERFCTR_L1D_CACHE_WRITEBACK = 0 xa8,
M1_PMU_PERFCTR_LDST_X64_UOP = 0 xb1,
M1_PMU_PERFCTR_LDST_XPG_UOP = 0 xb2,
M1_PMU_PERFCTR_ATOMIC_OR_EXCLUSIVE_SUCC = 0 xb3,
M1_PMU_PERFCTR_ATOMIC_OR_EXCLUSIVE_FAIL = 0 xb4,
M1_PMU_PERFCTR_L1D_CACHE_MISS_LD_NONSPEC = 0 xbf,
M1_PMU_PERFCTR_L1D_CACHE_MISS_ST_NONSPEC = 0 xc0,
M1_PMU_PERFCTR_L1D_TLB_MISS_NONSPEC = 0 xc1,
M1_PMU_PERFCTR_ST_MEMORY_ORDER_VIOLATION_NONSPEC = 0 xc4,
M1_PMU_PERFCTR_BRANCH_COND_MISPRED_NONSPEC = 0 xc5,
M1_PMU_PERFCTR_BRANCH_INDIR_MISPRED_NONSPEC = 0 xc6,
M1_PMU_PERFCTR_BRANCH_RET_INDIR_MISPRED_NONSPEC = 0 xc8,
M1_PMU_PERFCTR_BRANCH_CALL_INDIR_MISPRED_NONSPEC = 0 xca,
M1_PMU_PERFCTR_BRANCH_MISPRED_NONSPEC = 0 xcb,
M1_PMU_PERFCTR_L1I_TLB_MISS_DEMAND = 0 xd4,
M1_PMU_PERFCTR_MAP_DISPATCH_BUBBLE = 0 xd6,
M1_PMU_PERFCTR_L1I_CACHE_MISS_DEMAND = 0 xdb,
M1_PMU_PERFCTR_FETCH_RESTART = 0 xde,
M1_PMU_PERFCTR_ST_NT_UOP = 0 xe5,
M1_PMU_PERFCTR_LD_NT_UOP = 0 xe6,
M1_PMU_PERFCTR_UNKNOWN_f5 = 0 xf5,
M1_PMU_PERFCTR_UNKNOWN_f6 = 0 xf6,
M1_PMU_PERFCTR_UNKNOWN_f7 = 0 xf7,
M1_PMU_PERFCTR_UNKNOWN_f8 = 0 xf8,
M1_PMU_PERFCTR_UNKNOWN_fd = 0 xfd,
M1_PMU_PERFCTR_LAST = M1_PMU_CFG_EVENT,
/*
* From this point onwards , these are not actual HW events ,
* but attributes that get stored in hw - > config_base .
*/
M1_PMU_CFG_COUNT_USER = BIT(8 ),
M1_PMU_CFG_COUNT_KERNEL = BIT(9 ),
M1_PMU_CFG_COUNT_HOST = BIT(10 ),
M1_PMU_CFG_COUNT_GUEST = BIT(11 ),
};
/*
* Per - event affinity table . Most events can be installed on counter
* 2 - 9 , but there are a number of exceptions . Note that this table
* has been created experimentally , and I wouldn ' t be surprised if more
* counters had strange affinities .
*/
static const u16 m1_pmu_event_affinity[M1_PMU_PERFCTR_LAST + 1 ] = {
[0 ... M1_PMU_PERFCTR_LAST] = ANY_BUT_0_1,
[M1_PMU_PERFCTR_RETIRE_UOP] = BIT(7 ),
[M1_PMU_PERFCTR_CORE_ACTIVE_CYCLE] = ANY_BUT_0_1 | BIT(0 ),
[M1_PMU_PERFCTR_INST_ALL] = BIT(7 ) | BIT(1 ),
[M1_PMU_PERFCTR_INST_BRANCH] = ONLY_5_6_7,
[M1_PMU_PERFCTR_INST_BRANCH_CALL] = ONLY_5_6_7,
[M1_PMU_PERFCTR_INST_BRANCH_RET] = ONLY_5_6_7,
[M1_PMU_PERFCTR_INST_BRANCH_TAKEN] = ONLY_5_6_7,
[M1_PMU_PERFCTR_INST_BRANCH_INDIR] = ONLY_5_6_7,
[M1_PMU_PERFCTR_INST_BRANCH_COND] = ONLY_5_6_7,
[M1_PMU_PERFCTR_INST_INT_LD] = ONLY_5_6_7,
[M1_PMU_PERFCTR_INST_INT_ST] = BIT(7 ),
[M1_PMU_PERFCTR_INST_INT_ALU] = BIT(7 ),
[M1_PMU_PERFCTR_INST_SIMD_LD] = ONLY_5_6_7,
[M1_PMU_PERFCTR_INST_SIMD_ST] = ONLY_5_6_7,
[M1_PMU_PERFCTR_INST_SIMD_ALU] = BIT(7 ),
[M1_PMU_PERFCTR_INST_LDST] = BIT(7 ),
[M1_PMU_PERFCTR_INST_BARRIER] = ONLY_5_6_7,
[M1_PMU_PERFCTR_UNKNOWN_9f] = BIT(7 ),
[M1_PMU_PERFCTR_L1D_CACHE_MISS_LD_NONSPEC] = ONLY_5_6_7,
[M1_PMU_PERFCTR_L1D_CACHE_MISS_ST_NONSPEC] = ONLY_5_6_7,
[M1_PMU_PERFCTR_L1D_TLB_MISS_NONSPEC] = ONLY_5_6_7,
[M1_PMU_PERFCTR_ST_MEMORY_ORDER_VIOLATION_NONSPEC] = ONLY_5_6_7,
[M1_PMU_PERFCTR_BRANCH_COND_MISPRED_NONSPEC] = ONLY_5_6_7,
[M1_PMU_PERFCTR_BRANCH_INDIR_MISPRED_NONSPEC] = ONLY_5_6_7,
[M1_PMU_PERFCTR_BRANCH_RET_INDIR_MISPRED_NONSPEC] = ONLY_5_6_7,
[M1_PMU_PERFCTR_BRANCH_CALL_INDIR_MISPRED_NONSPEC] = ONLY_5_6_7,
[M1_PMU_PERFCTR_BRANCH_MISPRED_NONSPEC] = ONLY_5_6_7,
[M1_PMU_PERFCTR_UNKNOWN_f5] = ONLY_2_4_6,
[M1_PMU_PERFCTR_UNKNOWN_f6] = ONLY_2_4_6,
[M1_PMU_PERFCTR_UNKNOWN_f7] = ONLY_2_4_6,
[M1_PMU_PERFCTR_UNKNOWN_f8] = ONLY_2_TO_7,
[M1_PMU_PERFCTR_UNKNOWN_fd] = ONLY_2_4_6,
};
static const unsigned m1_pmu_perf_map[PERF_COUNT_HW_MAX] = {
PERF_MAP_ALL_UNSUPPORTED,
[PERF_COUNT_HW_CPU_CYCLES] = M1_PMU_PERFCTR_CORE_ACTIVE_CYCLE,
[PERF_COUNT_HW_INSTRUCTIONS] = M1_PMU_PERFCTR_INST_ALL,
[PERF_COUNT_HW_BRANCH_INSTRUCTIONS] = M1_PMU_PERFCTR_INST_BRANCH,
[PERF_COUNT_HW_BRANCH_MISSES] = M1_PMU_PERFCTR_BRANCH_MISPRED_NONSPEC,
};
#define M1_PMUV3_EVENT_MAP(pmuv3_event, m1_event) \
[ARMV8_PMUV3_PERFCTR_## pmuv3_event] = M1_PMU_PERFCTR_## m1_event
static const u16 m1_pmu_pmceid_map[ARMV8_PMUV3_MAX_COMMON_EVENTS] = {
[0 ... ARMV8_PMUV3_MAX_COMMON_EVENTS - 1 ] = HW_OP_UNSUPPORTED,
M1_PMUV3_EVENT_MAP(INST_RETIRED, INST_ALL),
M1_PMUV3_EVENT_MAP(CPU_CYCLES, CORE_ACTIVE_CYCLE),
M1_PMUV3_EVENT_MAP(BR_RETIRED, INST_BRANCH),
M1_PMUV3_EVENT_MAP(BR_MIS_PRED_RETIRED, BRANCH_MISPRED_NONSPEC),
};
/* sysfs definitions */
static ssize_t m1_pmu_events_sysfs_show(struct device *dev,
struct device_attribute *attr,
char *page)
{
struct perf_pmu_events_attr *pmu_attr;
pmu_attr = container_of(attr, struct perf_pmu_events_attr, attr);
return sprintf(page, "event=0x%04llx\n" , pmu_attr->id);
}
#define M1_PMU_EVENT_ATTR(name, config) \
PMU_EVENT_ATTR_ID(name, m1_pmu_events_sysfs_show, config)
static struct attribute *m1_pmu_event_attrs[] = {
M1_PMU_EVENT_ATTR(cycles, M1_PMU_PERFCTR_CORE_ACTIVE_CYCLE),
M1_PMU_EVENT_ATTR(instructions, M1_PMU_PERFCTR_INST_ALL),
NULL,
};
static const struct attribute_group m1_pmu_events_attr_group = {
.name = "events" ,
.attrs = m1_pmu_event_attrs,
};
PMU_FORMAT_ATTR(event, "config:0-7" );
static struct attribute *m1_pmu_format_attrs[] = {
&format_attr_event.attr,
NULL,
};
static const struct attribute_group m1_pmu_format_attr_group = {
.name = "format" ,
.attrs = m1_pmu_format_attrs,
};
/* Low level accessors. No synchronisation. */
#define PMU_READ_COUNTER(_idx) \
case _idx: return read_sysreg_s(SYS_IMP_APL_PMC## _idx ## _EL1)
#define PMU_WRITE_COUNTER(_val, _idx) \
case _idx: \
write_sysreg_s(_val, SYS_IMP_APL_PMC## _idx ## _EL1); \
return
static u64 m1_pmu_read_hw_counter(unsigned int index)
{
switch (index) {
PMU_READ_COUNTER(0 );
PMU_READ_COUNTER(1 );
PMU_READ_COUNTER(2 );
PMU_READ_COUNTER(3 );
PMU_READ_COUNTER(4 );
PMU_READ_COUNTER(5 );
PMU_READ_COUNTER(6 );
PMU_READ_COUNTER(7 );
PMU_READ_COUNTER(8 );
PMU_READ_COUNTER(9 );
}
BUG();
}
static void m1_pmu_write_hw_counter(u64 val, unsigned int index)
{
switch (index) {
PMU_WRITE_COUNTER(val, 0 );
PMU_WRITE_COUNTER(val, 1 );
PMU_WRITE_COUNTER(val, 2 );
PMU_WRITE_COUNTER(val, 3 );
PMU_WRITE_COUNTER(val, 4 );
PMU_WRITE_COUNTER(val, 5 );
PMU_WRITE_COUNTER(val, 6 );
PMU_WRITE_COUNTER(val, 7 );
PMU_WRITE_COUNTER(val, 8 );
PMU_WRITE_COUNTER(val, 9 );
}
BUG();
}
#define get_bit_offset(index, mask) (__ffs(mask) + (index))
static void __m1_pmu_enable_counter(unsigned int index, bool en)
{
u64 val, bit;
switch (index) {
case 0 ... 7 :
bit = BIT(get_bit_offset(index, PMCR0_CNT_ENABLE_0_7));
break ;
case 8 ... 9 :
bit = BIT(get_bit_offset(index - 8 , PMCR0_CNT_ENABLE_8_9));
break ;
default :
BUG();
}
val = read_sysreg_s(SYS_IMP_APL_PMCR0_EL1);
if (en)
val |= bit;
else
val &= ~bit;
write_sysreg_s(val, SYS_IMP_APL_PMCR0_EL1);
}
static void m1_pmu_enable_counter(unsigned int index)
{
__m1_pmu_enable_counter(index, true );
}
static void m1_pmu_disable_counter(unsigned int index)
{
__m1_pmu_enable_counter(index, false );
}
static void __m1_pmu_enable_counter_interrupt(unsigned int index, bool en)
{
u64 val, bit;
switch (index) {
case 0 ... 7 :
bit = BIT(get_bit_offset(index, PMCR0_PMI_ENABLE_0_7));
break ;
case 8 ... 9 :
bit = BIT(get_bit_offset(index - 8 , PMCR0_PMI_ENABLE_8_9));
break ;
default :
BUG();
}
val = read_sysreg_s(SYS_IMP_APL_PMCR0_EL1);
if (en)
val |= bit;
else
val &= ~bit;
write_sysreg_s(val, SYS_IMP_APL_PMCR0_EL1);
}
static void m1_pmu_enable_counter_interrupt(unsigned int index)
{
__m1_pmu_enable_counter_interrupt(index, true );
}
static void m1_pmu_disable_counter_interrupt(unsigned int index)
{
__m1_pmu_enable_counter_interrupt(index, false );
}
static void __m1_pmu_configure_event_filter(unsigned int index, bool user,
bool kernel, bool host)
{
u64 clear, set, user_bit, kernel_bit;
switch (index) {
case 0 ... 7 :
user_bit = BIT(get_bit_offset(index, PMCR1_COUNT_A64_EL0_0_7));
kernel_bit = BIT(get_bit_offset(index, PMCR1_COUNT_A64_EL1_0_7));
break ;
case 8 ... 9 :
user_bit = BIT(get_bit_offset(index - 8 , PMCR1_COUNT_A64_EL0_8_9));
kernel_bit = BIT(get_bit_offset(index - 8 , PMCR1_COUNT_A64_EL1_8_9));
break ;
default :
BUG();
}
clear = set = 0 ;
if (user)
set |= user_bit;
else
clear |= user_bit;
if (kernel)
set |= kernel_bit;
else
clear |= kernel_bit;
if (host)
sysreg_clear_set_s(SYS_IMP_APL_PMCR1_EL1, clear, set);
else if (is_kernel_in_hyp_mode())
sysreg_clear_set_s(SYS_IMP_APL_PMCR1_EL12, clear, set);
}
static void __m1_pmu_configure_eventsel(unsigned int index, u8 event)
{
u64 clear = 0 , set = 0 ;
int shift;
/*
* Counters 0 and 1 have fixed events . For anything else ,
* place the event at the expected location in the relevant
* register ( PMESR0 holds the event configuration for counters
* 2 - 5 , resp . PMESR1 for counters 6 - 9 ) .
*/
switch (index) {
case 0 ... 1 :
break ;
case 2 ... 5 :
shift = (index - 2 ) * 8 ;
clear |= (u64)0 xff << shift;
set |= (u64)event << shift;
sysreg_clear_set_s(SYS_IMP_APL_PMESR0_EL1, clear, set);
break ;
case 6 ... 9 :
shift = (index - 6 ) * 8 ;
clear |= (u64)0 xff << shift;
set |= (u64)event << shift;
sysreg_clear_set_s(SYS_IMP_APL_PMESR1_EL1, clear, set);
break ;
}
}
static void m1_pmu_configure_counter(unsigned int index, unsigned long config_base)
{
bool kernel = config_base & M1_PMU_CFG_COUNT_KERNEL;
bool guest = config_base & M1_PMU_CFG_COUNT_GUEST;
bool host = config_base & M1_PMU_CFG_COUNT_HOST;
bool user = config_base & M1_PMU_CFG_COUNT_USER;
u8 evt = config_base & M1_PMU_CFG_EVENT;
__m1_pmu_configure_event_filter(index, user && host, kernel && host, true );
__m1_pmu_configure_event_filter(index, user && guest, kernel && guest, false );
__m1_pmu_configure_eventsel(index, evt);
}
/* arm_pmu backend */
static void m1_pmu_enable_event(struct perf_event *event)
{
bool user, kernel;
u8 evt;
evt = event->hw.config_base & M1_PMU_CFG_EVENT;
user = event->hw.config_base & M1_PMU_CFG_COUNT_USER;
kernel = event->hw.config_base & M1_PMU_CFG_COUNT_KERNEL;
m1_pmu_configure_counter(event->hw.idx, event->hw.config_base);
m1_pmu_enable_counter(event->hw.idx);
m1_pmu_enable_counter_interrupt(event->hw.idx);
isb();
}
static void m1_pmu_disable_event(struct perf_event *event)
{
m1_pmu_disable_counter_interrupt(event->hw.idx);
m1_pmu_disable_counter(event->hw.idx);
isb();
}
static irqreturn_t m1_pmu_handle_irq(struct arm_pmu *cpu_pmu)
{
struct pmu_hw_events *cpuc = this_cpu_ptr(cpu_pmu->hw_events);
struct pt_regs *regs;
u64 overflow, state;
int idx;
overflow = read_sysreg_s(SYS_IMP_APL_PMSR_EL1);
if (!overflow) {
/* Spurious interrupt? */
state = read_sysreg_s(SYS_IMP_APL_PMCR0_EL1);
state &= ~PMCR0_IACT;
write_sysreg_s(state, SYS_IMP_APL_PMCR0_EL1);
isb();
return IRQ_NONE;
}
cpu_pmu->stop(cpu_pmu);
regs = get_irq_regs();
for_each_set_bit(idx, cpu_pmu->cntr_mask, M1_PMU_NR_COUNTERS) {
struct perf_event *event = cpuc->events[idx];
struct perf_sample_data data;
if (!event)
continue ;
armpmu_event_update(event);
perf_sample_data_init(&data, 0 , event->hw.last_period);
if (!armpmu_event_set_period(event))
continue ;
perf_event_overflow(event, &data, regs);
}
cpu_pmu->start(cpu_pmu);
return IRQ_HANDLED;
}
static u64 m1_pmu_read_counter(struct perf_event *event)
{
return m1_pmu_read_hw_counter(event->hw.idx);
}
static void m1_pmu_write_counter(struct perf_event *event, u64 value)
{
m1_pmu_write_hw_counter(value, event->hw.idx);
isb();
}
static int m1_pmu_get_event_idx(struct pmu_hw_events *cpuc,
struct perf_event *event)
{
unsigned long evtype = event->hw.config_base & M1_PMU_CFG_EVENT;
unsigned long affinity = m1_pmu_event_affinity[evtype];
int idx;
/*
* Place the event on the first free counter that can count
* this event .
*
* We could do a better job if we had a view of all the events
* counting on the PMU at any given time , and by placing the
* most constraining events first .
*/
for_each_set_bit(idx, &affinity, M1_PMU_NR_COUNTERS) {
if (!test_and_set_bit(idx, cpuc->used_mask))
return idx;
}
return -EAGAIN;
}
static void m1_pmu_clear_event_idx(struct pmu_hw_events *cpuc,
struct perf_event *event)
{
clear_bit(event->hw.idx, cpuc->used_mask);
}
static void __m1_pmu_set_mode(u8 mode)
{
u64 val;
val = read_sysreg_s(SYS_IMP_APL_PMCR0_EL1);
val &= ~(PMCR0_IMODE | PMCR0_IACT);
val |= FIELD_PREP(PMCR0_IMODE, mode);
write_sysreg_s(val, SYS_IMP_APL_PMCR0_EL1);
isb();
}
static void m1_pmu_start(struct arm_pmu *cpu_pmu)
{
__m1_pmu_set_mode(PMCR0_IMODE_FIQ);
}
static void m1_pmu_stop(struct arm_pmu *cpu_pmu)
{
__m1_pmu_set_mode(PMCR0_IMODE_OFF);
}
static int m1_pmu_map_event(struct perf_event *event)
{
/*
* Although the counters are 48 bit wide , bit 47 is what
* triggers the overflow interrupt . Advertise the counters
* being 47 bit wide to mimick the behaviour of the ARM PMU .
*/
event->hw.flags |= ARMPMU_EVT_47BIT;
return armpmu_map_event(event, &m1_pmu_perf_map, NULL, M1_PMU_CFG_EVENT);
}
static int m2_pmu_map_event(struct perf_event *event)
{
/*
* Same deal as the above , except that M2 has 64 bit counters .
* Which , as far as we ' re concerned , actually means 63 bits .
* Yes , this is getting awkward .
*/
event->hw.flags |= ARMPMU_EVT_63BIT;
return armpmu_map_event(event, &m1_pmu_perf_map, NULL, M1_PMU_CFG_EVENT);
}
static int m1_pmu_map_pmuv3_event(unsigned int eventsel)
{
u16 m1_event = HW_OP_UNSUPPORTED;
if (eventsel < ARMV8_PMUV3_MAX_COMMON_EVENTS)
m1_event = m1_pmu_pmceid_map[eventsel];
return m1_event == HW_OP_UNSUPPORTED ? -EOPNOTSUPP : m1_event;
}
static void m1_pmu_init_pmceid(struct arm_pmu *pmu)
{
unsigned int event;
for (event = 0 ; event < ARMV8_PMUV3_MAX_COMMON_EVENTS; event++) {
if (m1_pmu_map_pmuv3_event(event) >= 0 )
set_bit(event, pmu->pmceid_bitmap);
}
}
static void m1_pmu_reset(void *info)
{
int i;
__m1_pmu_set_mode(PMCR0_IMODE_OFF);
for (i = 0 ; i < M1_PMU_NR_COUNTERS; i++) {
m1_pmu_disable_counter(i);
m1_pmu_disable_counter_interrupt(i);
m1_pmu_write_hw_counter(0 , i);
}
isb();
}
static int m1_pmu_set_event_filter(struct hw_perf_event *event,
struct perf_event_attr *attr)
{
unsigned long config_base = 0 ;
if (!attr->exclude_guest && !is_kernel_in_hyp_mode()) {
pr_debug("ARM performance counters do not support mode exclusion\n" );
return -EOPNOTSUPP;
}
if (!attr->exclude_kernel)
config_base |= M1_PMU_CFG_COUNT_KERNEL;
if (!attr->exclude_user)
config_base |= M1_PMU_CFG_COUNT_USER;
if (!attr->exclude_host)
config_base |= M1_PMU_CFG_COUNT_HOST;
if (!attr->exclude_guest)
config_base |= M1_PMU_CFG_COUNT_GUEST;
event->config_base = config_base;
return 0 ;
}
static int m1_pmu_init(struct arm_pmu *cpu_pmu, u32 flags)
{
cpu_pmu->handle_irq = m1_pmu_handle_irq;
cpu_pmu->enable = m1_pmu_enable_event;
cpu_pmu->disable = m1_pmu_disable_event;
cpu_pmu->read_counter = m1_pmu_read_counter;
cpu_pmu->write_counter = m1_pmu_write_counter;
cpu_pmu->get_event_idx = m1_pmu_get_event_idx;
cpu_pmu->clear_event_idx = m1_pmu_clear_event_idx;
cpu_pmu->start = m1_pmu_start;
cpu_pmu->stop = m1_pmu_stop;
if (flags & ARMPMU_EVT_47BIT)
cpu_pmu->map_event = m1_pmu_map_event;
else if (flags & ARMPMU_EVT_63BIT)
cpu_pmu->map_event = m2_pmu_map_event;
else
return WARN_ON(-EINVAL);
cpu_pmu->reset = m1_pmu_reset;
cpu_pmu->set_event_filter = m1_pmu_set_event_filter;
cpu_pmu->map_pmuv3_event = m1_pmu_map_pmuv3_event;
m1_pmu_init_pmceid(cpu_pmu);
bitmap_set(cpu_pmu->cntr_mask, 0 , M1_PMU_NR_COUNTERS);
cpu_pmu->attr_groups[ARMPMU_ATTR_GROUP_EVENTS] = &m1_pmu_events_attr_group;
cpu_pmu->attr_groups[ARMPMU_ATTR_GROUP_FORMATS] = &m1_pmu_format_attr_group;
return 0 ;
}
/* Device driver gunk */
static int m1_pmu_ice_init(struct arm_pmu *cpu_pmu)
{
cpu_pmu->name = "apple_icestorm_pmu" ;
return m1_pmu_init(cpu_pmu, ARMPMU_EVT_47BIT);
}
static int m1_pmu_fire_init(struct arm_pmu *cpu_pmu)
{
cpu_pmu->name = "apple_firestorm_pmu" ;
return m1_pmu_init(cpu_pmu, ARMPMU_EVT_47BIT);
}
static int m2_pmu_avalanche_init(struct arm_pmu *cpu_pmu)
{
cpu_pmu->name = "apple_avalanche_pmu" ;
return m1_pmu_init(cpu_pmu, ARMPMU_EVT_63BIT);
}
static int m2_pmu_blizzard_init(struct arm_pmu *cpu_pmu)
{
cpu_pmu->name = "apple_blizzard_pmu" ;
return m1_pmu_init(cpu_pmu, ARMPMU_EVT_63BIT);
}
static const struct of_device_id m1_pmu_of_device_ids[] = {
{ .compatible = "apple,avalanche-pmu" , .data = m2_pmu_avalanche_init, },
{ .compatible = "apple,blizzard-pmu" , .data = m2_pmu_blizzard_init, },
{ .compatible = "apple,icestorm-pmu" , .data = m1_pmu_ice_init, },
{ .compatible = "apple,firestorm-pmu" , .data = m1_pmu_fire_init, },
{ },
};
MODULE_DEVICE_TABLE(of, m1_pmu_of_device_ids);
static int m1_pmu_device_probe(struct platform_device *pdev)
{
return arm_pmu_device_probe(pdev, m1_pmu_of_device_ids, NULL);
}
static struct platform_driver m1_pmu_driver = {
.driver = {
.name = "apple-m1-cpu-pmu" ,
.of_match_table = m1_pmu_of_device_ids,
.suppress_bind_attrs = true ,
},
.probe = m1_pmu_device_probe,
};
module_platform_driver(m1_pmu_driver);
Messung V0.5 in Prozent C=99 H=97 G=97
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland