diff options
Diffstat (limited to 'arch/arm64/kernel/perf_event.c')
| -rw-r--r-- | arch/arm64/kernel/perf_event.c | 198 | 
1 files changed, 155 insertions, 43 deletions
diff --git a/arch/arm64/kernel/perf_event.c b/arch/arm64/kernel/perf_event.c index cea1594ff93..baf5afb7e6a 100644 --- a/arch/arm64/kernel/perf_event.c +++ b/arch/arm64/kernel/perf_event.c @@ -22,6 +22,7 @@  #include <linux/bitmap.h>  #include <linux/interrupt.h> +#include <linux/irq.h>  #include <linux/kernel.h>  #include <linux/export.h>  #include <linux/perf_event.h> @@ -363,26 +364,53 @@ validate_group(struct perf_event *event)  }  static void +armpmu_disable_percpu_irq(void *data) +{ +	unsigned int irq = *(unsigned int *)data; +	disable_percpu_irq(irq); +} + +static void  armpmu_release_hardware(struct arm_pmu *armpmu)  { -	int i, irq, irqs; +	int irq; +	unsigned int i, irqs;  	struct platform_device *pmu_device = armpmu->plat_device;  	irqs = min(pmu_device->num_resources, num_possible_cpus()); +	if (!irqs) +		return; -	for (i = 0; i < irqs; ++i) { -		if (!cpumask_test_and_clear_cpu(i, &armpmu->active_irqs)) -			continue; -		irq = platform_get_irq(pmu_device, i); -		if (irq >= 0) -			free_irq(irq, armpmu); +	irq = platform_get_irq(pmu_device, 0); +	if (irq <= 0) +		return; + +	if (irq_is_percpu(irq)) { +		on_each_cpu(armpmu_disable_percpu_irq, &irq, 1); +		free_percpu_irq(irq, &cpu_hw_events); +	} else { +		for (i = 0; i < irqs; ++i) { +			if (!cpumask_test_and_clear_cpu(i, &armpmu->active_irqs)) +				continue; +			irq = platform_get_irq(pmu_device, i); +			if (irq > 0) +				free_irq(irq, armpmu); +		}  	}  } +static void +armpmu_enable_percpu_irq(void *data) +{ +	unsigned int irq = *(unsigned int *)data; +	enable_percpu_irq(irq, IRQ_TYPE_NONE); +} +  static int  armpmu_reserve_hardware(struct arm_pmu *armpmu)  { -	int i, err, irq, irqs; +	int err, irq; +	unsigned int i, irqs;  	struct platform_device *pmu_device = armpmu->plat_device;  	if (!pmu_device) { @@ -391,39 +419,59 @@ armpmu_reserve_hardware(struct arm_pmu *armpmu)  	}  	irqs = min(pmu_device->num_resources, num_possible_cpus()); -	if (irqs < 1) { +	if (!irqs) {  		pr_err("no irqs for PMUs defined\n");  		return -ENODEV;  	} -	for (i = 0; i < irqs; ++i) { -		err = 0; -		irq = platform_get_irq(pmu_device, i); -		if (irq < 0) -			continue; +	irq = platform_get_irq(pmu_device, 0); +	if (irq <= 0) { +		pr_err("failed to get valid irq for PMU device\n"); +		return -ENODEV; +	} -		/* -		 * If we have a single PMU interrupt that we can't shift, -		 * assume that we're running on a uniprocessor machine and -		 * continue. Otherwise, continue without this interrupt. -		 */ -		if (irq_set_affinity(irq, cpumask_of(i)) && irqs > 1) { -			pr_warning("unable to set irq affinity (irq=%d, cpu=%u)\n", -				    irq, i); -			continue; -		} +	if (irq_is_percpu(irq)) { +		err = request_percpu_irq(irq, armpmu->handle_irq, +				"arm-pmu", &cpu_hw_events); -		err = request_irq(irq, armpmu->handle_irq, -				  IRQF_NOBALANCING, -				  "arm-pmu", armpmu);  		if (err) { -			pr_err("unable to request IRQ%d for ARM PMU counters\n", -				irq); +			pr_err("unable to request percpu IRQ%d for ARM PMU counters\n", +					irq);  			armpmu_release_hardware(armpmu);  			return err;  		} -		cpumask_set_cpu(i, &armpmu->active_irqs); +		on_each_cpu(armpmu_enable_percpu_irq, &irq, 1); +	} else { +		for (i = 0; i < irqs; ++i) { +			err = 0; +			irq = platform_get_irq(pmu_device, i); +			if (irq <= 0) +				continue; + +			/* +			 * If we have a single PMU interrupt that we can't shift, +			 * assume that we're running on a uniprocessor machine and +			 * continue. Otherwise, continue without this interrupt. +			 */ +			if (irq_set_affinity(irq, cpumask_of(i)) && irqs > 1) { +				pr_warning("unable to set irq affinity (irq=%d, cpu=%u)\n", +						irq, i); +				continue; +			} + +			err = request_irq(irq, armpmu->handle_irq, +					IRQF_NOBALANCING, +					"arm-pmu", armpmu); +			if (err) { +				pr_err("unable to request IRQ%d for ARM PMU counters\n", +						irq); +				armpmu_release_hardware(armpmu); +				return err; +			} + +			cpumask_set_cpu(i, &armpmu->active_irqs); +		}  	}  	return 0; @@ -784,8 +832,8 @@ static const unsigned armv8_pmuv3_perf_cache_map[PERF_COUNT_HW_CACHE_MAX]  /*   * PMXEVTYPER: Event selection reg   */ -#define	ARMV8_EVTYPE_MASK	0xc80000ff	/* Mask for writable bits */ -#define	ARMV8_EVTYPE_EVENT	0xff		/* Mask for EVENT bits */ +#define	ARMV8_EVTYPE_MASK	0xc80003ff	/* Mask for writable bits */ +#define	ARMV8_EVTYPE_EVENT	0x3ff		/* Mask for EVENT bits */  /*   * Event filters for PMUv3 @@ -1044,7 +1092,7 @@ static irqreturn_t armv8pmu_handle_irq(int irq_num, void *dev)  	 */  	regs = get_irq_regs(); -	cpuc = &__get_cpu_var(cpu_hw_events); +	cpuc = this_cpu_ptr(&cpu_hw_events);  	for (idx = 0; idx < cpu_pmu->num_events; ++idx) {  		struct perf_event *event = cpuc->events[idx];  		struct hw_perf_event *hwc; @@ -1175,7 +1223,8 @@ static void armv8pmu_reset(void *info)  static int armv8_pmuv3_map_event(struct perf_event *event)  {  	return map_cpu_event(event, &armv8_pmuv3_perf_map, -				&armv8_pmuv3_perf_cache_map, 0xFF); +				&armv8_pmuv3_perf_cache_map, +				ARMV8_EVTYPE_EVENT);  }  static struct arm_pmu armv8pmu = { @@ -1257,7 +1306,7 @@ device_initcall(register_pmu_driver);  static struct pmu_hw_events *armpmu_get_cpu_events(void)  { -	return &__get_cpu_var(cpu_hw_events); +	return this_cpu_ptr(&cpu_hw_events);  }  static void __init cpu_pmu_init(struct arm_pmu *armpmu) @@ -1299,8 +1348,8 @@ early_initcall(init_hw_perf_events);   * Callchain handling code.   */  struct frame_tail { -	struct frame_tail   __user *fp; -	unsigned long	    lr; +	struct frame_tail	__user *fp; +	unsigned long		lr;  } __attribute__((packed));  /* @@ -1337,22 +1386,84 @@ user_backtrace(struct frame_tail __user *tail,  	return buftail.fp;  } +#ifdef CONFIG_COMPAT +/* + * The registers we're interested in are at the end of the variable + * length saved register structure. The fp points at the end of this + * structure so the address of this struct is: + * (struct compat_frame_tail *)(xxx->fp)-1 + * + * This code has been adapted from the ARM OProfile support. + */ +struct compat_frame_tail { +	compat_uptr_t	fp; /* a (struct compat_frame_tail *) in compat mode */ +	u32		sp; +	u32		lr; +} __attribute__((packed)); + +static struct compat_frame_tail __user * +compat_user_backtrace(struct compat_frame_tail __user *tail, +		      struct perf_callchain_entry *entry) +{ +	struct compat_frame_tail buftail; +	unsigned long err; + +	/* Also check accessibility of one struct frame_tail beyond */ +	if (!access_ok(VERIFY_READ, tail, sizeof(buftail))) +		return NULL; + +	pagefault_disable(); +	err = __copy_from_user_inatomic(&buftail, tail, sizeof(buftail)); +	pagefault_enable(); + +	if (err) +		return NULL; + +	perf_callchain_store(entry, buftail.lr); + +	/* +	 * Frame pointers should strictly progress back up the stack +	 * (towards higher addresses). +	 */ +	if (tail + 1 >= (struct compat_frame_tail __user *) +			compat_ptr(buftail.fp)) +		return NULL; + +	return (struct compat_frame_tail __user *)compat_ptr(buftail.fp) - 1; +} +#endif /* CONFIG_COMPAT */ +  void perf_callchain_user(struct perf_callchain_entry *entry,  			 struct pt_regs *regs)  { -	struct frame_tail __user *tail; -  	if (perf_guest_cbs && perf_guest_cbs->is_in_guest()) {  		/* We don't support guest os callchain now */  		return;  	}  	perf_callchain_store(entry, regs->pc); -	tail = (struct frame_tail __user *)regs->regs[29]; -	while (entry->nr < PERF_MAX_STACK_DEPTH && -	       tail && !((unsigned long)tail & 0xf)) -		tail = user_backtrace(tail, entry); +	if (!compat_user_mode(regs)) { +		/* AARCH64 mode */ +		struct frame_tail __user *tail; + +		tail = (struct frame_tail __user *)regs->regs[29]; + +		while (entry->nr < PERF_MAX_STACK_DEPTH && +		       tail && !((unsigned long)tail & 0xf)) +			tail = user_backtrace(tail, entry); +	} else { +#ifdef CONFIG_COMPAT +		/* AARCH32 compat mode */ +		struct compat_frame_tail __user *tail; + +		tail = (struct compat_frame_tail __user *)regs->compat_fp - 1; + +		while ((entry->nr < PERF_MAX_STACK_DEPTH) && +			tail && !((unsigned long)tail & 0x3)) +			tail = compat_user_backtrace(tail, entry); +#endif +	}  }  /* @@ -1380,6 +1491,7 @@ void perf_callchain_kernel(struct perf_callchain_entry *entry,  	frame.fp = regs->regs[29];  	frame.sp = regs->sp;  	frame.pc = regs->pc; +  	walk_stackframe(&frame, callchain_trace, entry);  }  | 
