From nobody Mon Sep 17 00:00:00 2001 From: Haavard Skinnemoen <hskinnemoen@atmel.com> Date: Wed Jan 4 17:26:23 2006 +0100 Subject: [PATCH] AVR32 oprofile implementation This adds support for oprofile on the AVR32 architecture. --- arch/avr32/Kconfig | 2 arch/avr32/Makefile | 1 arch/avr32/oprofile/Kconfig | 23 +++ arch/avr32/oprofile/Makefile | 10 + arch/avr32/oprofile/common.c | 169 +++++++++++++++++++++++++++ arch/avr32/oprofile/init.c | 29 ++++ arch/avr32/oprofile/op_avr32_model.h | 25 +++ arch/avr32/oprofile/op_counter.h | 29 ++++ arch/avr32/oprofile/op_model_avr32.c | 219 +++++++++++++++++++++++++++++++++++ arch/avr32/oprofile/op_model_avr32.h | 21 +++ 10 files changed, 528 insertions(+) Index: linux-2.6.18-avr32/arch/avr32/oprofile/Kconfig =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.18-avr32/arch/avr32/oprofile/Kconfig 2006-10-20 14:08:20.000000000 +0200 @@ -0,0 +1,23 @@ + +menu "Profiling support" + depends on EXPERIMENTAL + +config PROFILING + bool "Profiling support (EXPERIMENTAL)" + help + Say Y here to enable the extended profiling support mechanisms used + by profilers such as OProfile. + + +config OPROFILE + tristate "OProfile system profiling (EXPERIMENTAL)" + depends on PROFILING + help + OProfile is a profiling system capable of profiling the + whole system, including the kernel, kernel modules, libraries, + and applications. + + If unsure, say N. + +endmenu + Index: linux-2.6.18-avr32/arch/avr32/oprofile/Makefile =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.18-avr32/arch/avr32/oprofile/Makefile 2006-10-20 14:08:20.000000000 +0200 @@ -0,0 +1,10 @@ +obj-$(CONFIG_OPROFILE) += oprofile.o + +DRIVER_OBJS = $(addprefix ../../../drivers/oprofile/, \ + oprof.o cpu_buffer.o buffer_sync.o \ + event_buffer.o oprofile_files.o \ + oprofilefs.o oprofile_stats.o \ + timer_int.o ) + +oprofile-y := $(DRIVER_OBJS) init.o common.o +oprofile-y += op_model_avr32.o Index: linux-2.6.18-avr32/arch/avr32/oprofile/common.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.18-avr32/arch/avr32/oprofile/common.c 2006-10-20 14:08:20.000000000 +0200 @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2005-2006 Atmel Corporation + * + * 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. + * + * Author: Ronny Pedersen + */ + +#define DEBUG +#include <linux/init.h> +#include <linux/oprofile.h> +#include <linux/errno.h> +#include <asm/semaphore.h> +#include <linux/sysdev.h> + +#include "op_avr32_model.h" +#include "op_counter.h" + +static struct op_avr32_model_spec *pc_model; +static int pc_enabled = 0; +static struct semaphore pc_sem; + + +static int pc_start(void); +static int pc_setup(void); +static void pc_stop(void); +static int pc_create_files(struct super_block *, struct dentry *); + + +struct op_counter_config counter_config[OP_MAX_COUNTER]; + +static int pc_suspend(struct sys_device *dev, u32 state) +{ + if (pc_enabled) + pc_stop(); + return 0; +} + +static int pc_resume(struct sys_device *dev) +{ + if (pc_enabled) + pc_start(); + return 0; +} + +static struct sysdev_class oprofile_sysclass = { + set_kset_name("oprofile"), + .resume = pc_resume, + .suspend = pc_suspend, +}; + +static struct sys_device device_oprofile = { + .id = 0, + .cls = &oprofile_sysclass, +}; + +static int __init init_driverfs(void) +{ + int ret; + + if (!(ret = sysdev_class_register(&oprofile_sysclass))) + ret = sysdev_register(&device_oprofile); + + return ret; +} + +static void exit_driverfs(void) +{ + sysdev_unregister(&device_oprofile); + sysdev_class_unregister(&oprofile_sysclass); +} + +static int pc_create_files(struct super_block *sb, struct dentry *root) +{ + unsigned int i; + + pr_debug("AVR32 Peformance Counters: create files\n"); + for (i = 0; i < pc_model->num_counters; i++) { + struct dentry *dir; + char buf[2]; + + snprintf(buf, sizeof buf, "%d", i); + dir = oprofilefs_mkdir(sb, root, buf); + oprofilefs_create_ulong(sb, dir, "enabled", + &counter_config[i].enabled); + oprofilefs_create_ulong(sb, dir, "event", + &counter_config[i].event); + oprofilefs_create_ulong(sb, dir, "count", + &counter_config[i].count); + oprofilefs_create_ulong(sb, dir, "unit_mask", + &counter_config[i].unit_mask); + oprofilefs_create_ulong(sb, dir, "kernel", + &counter_config[i].kernel); + oprofilefs_create_ulong(sb, dir, "user", + &counter_config[i].user); + } + + return 0; +} + +static int pc_setup(void) +{ + int ret; + + spin_lock(&oprofilefs_lock); + pr_debug("AVR32 Peformance Counters: setup\n"); + ret = pc_model->setup_ctrs(); + spin_unlock(&oprofilefs_lock); + return ret; +} + +static int pc_start(void) +{ + int ret = -EBUSY; + + down(&pc_sem); + if (!pc_enabled) { + pr_debug("AVR32 Peformance Counters: start\n"); + ret = pc_model->start(); + pc_enabled = !ret; + } + up(&pc_sem); + return ret; +} + +static void pc_stop(void) +{ + down(&pc_sem); + pr_debug("AVR32 Peformance Counters: stop\n"); + if (pc_enabled) + pc_model->stop(); + pc_enabled = 0; + up(&pc_sem); +} + +int __init pc_init(struct oprofile_operations *ops, + struct op_avr32_model_spec *spec) +{ + init_MUTEX(&pc_sem); + + if ( spec->init ) + if (spec->init() < 0) + return -ENODEV; + + pc_model = spec; + init_driverfs(); + ops->create_files = pc_create_files; + ops->setup = pc_setup; + ops->shutdown = pc_stop; + ops->start = pc_start; + ops->stop = pc_stop; + ops->cpu_type = pc_model->name; + printk(KERN_INFO "oprofile: using %s Performance Counters\n", + spec->name); + pr_debug("AVR32 Peformance Counters: pc_init\n"); + + return 0; +} + +void pc_exit(void) +{ + if (pc_model) { + pr_debug("AVR32 Peformance Counters: exit\n"); + exit_driverfs(); + pc_model = NULL; + } +} Index: linux-2.6.18-avr32/arch/avr32/oprofile/init.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.18-avr32/arch/avr32/oprofile/init.c 2006-10-20 14:08:20.000000000 +0200 @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2005-2006 Atmel Corporation + * + * 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. + * + * Author: Ronny Pedersen + */ + +#include <linux/oprofile.h> +#include <linux/init.h> +#include <linux/errno.h> +#include "op_avr32_model.h" +#include "op_model_avr32.h" + +int __init oprofile_arch_init(struct oprofile_operations *ops) +{ + int ret = -ENODEV; + + ret = pc_init(ops, &op_avr32_spec); + + return ret; +} + +void oprofile_arch_exit(void) +{ + pc_exit(); +} Index: linux-2.6.18-avr32/arch/avr32/oprofile/op_avr32_model.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.18-avr32/arch/avr32/oprofile/op_avr32_model.h 2006-10-20 14:08:20.000000000 +0200 @@ -0,0 +1,25 @@ +/* + * interface to AVR32 machine specific operations + * + * Copyright (C) 2005-2006 Atmel Corporation + * + * 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. + * + * Author: Ronny Pedersen + */ + +#ifndef OP_AVR32_MODEL_H +#define OP_AVR32_MODEL_H + +struct op_avr32_model_spec { + int (*init)(void); + unsigned int num_counters; + int (*setup_ctrs)(void); + int (*start)(void); + void (*stop)(void); + char *name; +}; + +#endif /* OP_AVR32_MODEL_H */ Index: linux-2.6.18-avr32/arch/avr32/oprofile/op_counter.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.18-avr32/arch/avr32/oprofile/op_counter.h 2006-10-20 14:08:20.000000000 +0200 @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2005-2006 Atmel Corporation + * + * 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. + * + * Author: Ronny Pedersen + */ +#ifndef OP_COUNTER_H +#define OP_COUNTER_H + +#define OP_MAX_COUNTER 3 + +/* Per performance monitor configuration as set via + * oprofilefs. + */ +struct op_counter_config { + unsigned long count; + unsigned long enabled; + unsigned long event; + unsigned long unit_mask; + unsigned long kernel; + unsigned long user; +}; + +extern struct op_counter_config counter_config[]; + +#endif /* OP_COUNTER_H */ Index: linux-2.6.18-avr32/arch/avr32/oprofile/op_model_avr32.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.18-avr32/arch/avr32/oprofile/op_model_avr32.c 2006-10-20 14:08:20.000000000 +0200 @@ -0,0 +1,219 @@ +/* + * AVR32 Performance Counter Driver + * + * Copyright (C) 2005-2006 Atmel Corporation + * + * 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. + * + * Author: Ronny Pedersen + */ + +#define DEBUG + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/oprofile.h> +#include <linux/interrupt.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/sysreg.h> + +#include "op_counter.h" +#include "op_avr32_model.h" + + +#define PC_ENABLE 0x001 /* Enable counters */ +#define PCNT_RESET 0x002 /* Reset event counters */ +#define CCNT_RESET 0x004 /* Reset clock counter */ +#define PC_RESET (CCNT_RESET | PCNT_RESET) +#define PC_CNT64 0x008 /* Make CCNT count every 64th cycle */ + + +#define EVT_UNUSED 0xFF + +struct pc_counter { + volatile unsigned long ovf; + unsigned long reset_counter; +}; + +enum { PCCNT, PCNT0, PCNT1, MAX_COUNTERS }; + +#define PCCNT_IE (1 << 4) +#define PCNT0_IE (1 << 5) +#define PCNT1_IE (1 << 6) + +#define PCCNT_F (1 << 8) +#define PCNT0_F (1 << 9) +#define PCNT1_F (1 << 10) + +#define AVR32_PC_IRQ 1 + +static const u32 int_mask[MAX_COUNTERS] = { PCCNT_IE, PCNT0_IE, PCNT1_IE }; +static const u32 ovf_mask[MAX_COUNTERS] = { PCCNT_F, PCNT0_F, PCNT1_F }; + +static struct pc_counter results[MAX_COUNTERS]; + +static void write_pccr(u32 val) +{ + __builtin_mtsr(SYSREG_PCCR, val); +} + +static u32 read_pccr(void) +{ + return __builtin_mfsr(SYSREG_PCCR); +} + +static u32 read_counter(int counter) +{ + switch (counter) { + case PCCNT: + return __builtin_mfsr(SYSREG_PCCNT); + case PCNT0: + return __builtin_mfsr(SYSREG_PCNT0); + case PCNT1: + return __builtin_mfsr(SYSREG_PCNT0); + default: + return 0; + } +} + + +static void write_counter(int counter, u32 val) +{ + switch (counter) { + case PCCNT: + __builtin_mtsr(SYSREG_PCCNT, val); + case PCNT0: + __builtin_mtsr(SYSREG_PCNT0, val); + case PCNT1: + __builtin_mtsr(SYSREG_PCNT0, val); + default: + break; + } +} + +static int avr32_setup_ctrs(void) +{ + u32 pccr; + int i; + + for (i = PCCNT; i < MAX_COUNTERS; i++) { + if (counter_config[i].enabled) + continue; + + counter_config[i].event = EVT_UNUSED; + } + + pccr = ((counter_config[PCNT1].event << 18) + | (counter_config[PCNT0].event << 12)); + pr_debug("avr32_setup_ctrs: pccr: %#08x\n", pccr); + write_pccr(pccr); + + for (i = PCCNT; i < MAX_COUNTERS; i++) { + if (counter_config[i].event == EVT_UNUSED) { + counter_config[i].event = 0; + continue; + } + + results[i].reset_counter = counter_config[i].count; + write_counter(i, -(u32)counter_config[i].count); + pr_debug("avr32_setup_ctrs: counter%d %#08x from %#08lx\n", + i, read_counter(i), counter_config[i].count); + } + + return 0; +} + +static void inline check_ctrs(void) +{ + int i; + u32 pccr = read_pccr(); + + /* Writeback clears overflow flag */ + write_pccr(pccr & ~PC_ENABLE); + + for (i = PCCNT; i < MAX_COUNTERS; i++) { + if (!(int_mask[i] & pccr)) + continue; + + if (pccr & ovf_mask[i]) + results[i].ovf++; + } +} + + +static irqreturn_t avr32_pc_interrupt(int irq, void *arg, + struct pt_regs *regs) +{ + int i; + + check_ctrs(); + + for (i = PCCNT; i < MAX_COUNTERS; i++) { + if (!results[i].ovf) + continue; + + write_counter(i, -(u32)results[i].reset_counter); + oprofile_add_sample(regs, i); + results[i].ovf--; + } + + /* Enable Performance Counter */ + write_pccr(read_pccr() | PC_ENABLE); + + return IRQ_HANDLED; +} + +static void avr32_pc_stop(void) +{ + write_pccr(read_pccr() & ~PC_ENABLE); + + free_irq(AVR32_PC_IRQ, results); +} + +static int avr32_pc_start(void) +{ + int i, ret; + u32 pccr = read_pccr(); + + ret = request_irq(AVR32_PC_IRQ, avr32_pc_interrupt, SA_INTERRUPT, + "AVR32 Performance Counter", (void *)results); + + if (ret < 0) { + printk(KERN_ERR + "oprofile: unable to request IRQ%d for AVR32" + " Performance Counter\n", + AVR32_PC_IRQ); + return ret; + } + + /* Enable interrupts */ + for (i = PCCNT; i < MAX_COUNTERS; i++) { + if (counter_config[i].enabled) + pccr |= int_mask[i]; + } + + /* Disable scaler */ + pccr &= ~PC_CNT64; + + /* Enable Performance Counter */ + pccr |= PC_ENABLE; + + write_pccr(pccr); + pr_debug("avr32_pc_start: pc: %#08x\n", pccr); + return 0; +} + + +struct op_avr32_model_spec op_avr32_spec = { + .init = 0, + .setup_ctrs = avr32_setup_ctrs, + .start = avr32_pc_start, + .stop = avr32_pc_stop, + .num_counters = MAX_COUNTERS, + .name = "avr32", +}; + Index: linux-2.6.18-avr32/arch/avr32/oprofile/op_model_avr32.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.18-avr32/arch/avr32/oprofile/op_model_avr32.h 2006-10-20 14:08:20.000000000 +0200 @@ -0,0 +1,21 @@ +/** + * AVR32 Machine Specific Operations + * + * Copyright (C) 2005-2006 Atmel Corporation + * + * 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. + * + * Author: Ronny Pedersen + */ +#ifndef OP_MODEL_AVR32_H +#define OP_MODEL_AVR32_H + +extern struct op_avr32_model_spec op_avr32_spec; +extern int pc_init(struct oprofile_operations *ops, + struct op_avr32_model_spec *spec); +extern void pc_exit(void); + + +#endif Index: linux-2.6.18-avr32/arch/avr32/Kconfig =================================================================== --- linux-2.6.18-avr32.orig/arch/avr32/Kconfig 2006-10-20 14:04:43.000000000 +0200 +++ linux-2.6.18-avr32/arch/avr32/Kconfig 2006-10-20 14:08:20.000000000 +0200 @@ -190,6 +190,8 @@ source "drivers/Kconfig" source "fs/Kconfig" +source "arch/avr32/oprofile/Kconfig" + source "arch/avr32/Kconfig.debug" source "security/Kconfig" Index: linux-2.6.18-avr32/arch/avr32/Makefile =================================================================== --- linux-2.6.18-avr32.orig/arch/avr32/Makefile 2006-10-20 14:04:54.000000000 +0200 +++ linux-2.6.18-avr32/arch/avr32/Makefile 2006-10-20 14:09:10.000000000 +0200 @@ -30,6 +30,7 @@ core-$(CONFIG_BOARD_ATSTK1000) += arch/ core-$(CONFIG_LOADER_U_BOOT) += arch/avr32/boot/u-boot/ core-y += arch/avr32/kernel/ core-y += arch/avr32/mm/ +drivers-$(CONFIG_OPROFILE) += arch/avr32/oprofile/ libs-y += arch/avr32/lib/ archincdir-$(CONFIG_PLATFORM_AT32AP) := arch-at32ap