Subject: oprofile support for Cell From: David Erb This provides oprofile functions for Cell Signed-off-by: Arnd Bergmann Index: linus-2.6/arch/powerpc/kernel/cputable.c =================================================================== --- linus-2.6.orig/arch/powerpc/kernel/cputable.c +++ linus-2.6/arch/powerpc/kernel/cputable.c @@ -274,6 +274,9 @@ struct cpu_spec cpu_specs[] = { PPC_FEATURE_SMT, .icache_bsize = 128, .dcache_bsize = 128, + .num_pmcs = 4, + .oprofile_cpu_type = "ppc64/cell-be", + .oprofile_type = PPC_OPROFILE_CELL, .platform = "ppc-cell-be", }, { /* default match */ Index: linus-2.6/arch/powerpc/oprofile/op_model_cell.c =================================================================== --- /dev/null +++ linus-2.6/arch/powerpc/oprofile/op_model_cell.c @@ -0,0 +1,463 @@ +/* + * Cell Broadband Engine OProfile Support + * + * (C) Copyright IBM Corporation 2006 + * + * Author: David Erb (djerb@us.ibm.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../platforms/cell/cbe_regs.h" +#include "../platforms/cell/perfmon.h" +#include "../platforms/cell/interrupt.h" + +#define dbg(args...) printk(args) +#define dbg_entry(args...) +#define dbg_exit(args...) +#define dbg_hw(args...) + + +/* There is only 1 Performance Monitor for every dual-threaded processor */ +#define NR_NODES (NR_CPUS >> 1) + +/* + * ibm,cbe-perftools rtas parameters + */ + +typedef struct { + u16 cpu; /* Processor to modify */ + u16 sub_unit; /* hw subunit this applies to (if applicable) */ + u16 signal_group; /* Signal Group to Enable/Disable */ + u8 bus_word; /* Enable/Disable on this Trace/Trigger/Event + Bus Word(s) (bitmask) */ + u8 bit; /* Trigger/Event bit (if applicable) */ +} PM_Signal; + +typedef struct { + u32 subfunc; + #define subfunc_RESET 1 + #define subfunc_ACTIVATE 2 + #define subfunc_DEACTIVATE 3 + + u32 passthru; + #define passthru_IGNORE 0 + #define passthru_ENABLE 1 + #define passthru_DISABLE 2 + + u32 buffer_addr_hi; /* addr of buffer containing PM_Signal array */ + u32 buffer_addr_lo; + u32 buffer_length; /* buffer size (in bytes) */ + + PM_Signal signal[NR_NODES * OP_MAX_COUNTER]; +} PM_Rtas_Args; + +static PM_Rtas_Args rtas_args; + +static u32 reset_value[OP_MAX_COUNTER]; +static int num_counters; +static int oprofile_running; +static char ctrSize[4]; +static u32 ctr_enabled = 0; + +static struct { + u32 group_control; + u32 debug_bus_control; + struct pm_control pm_control; + struct pm07_control pm07_control[OP_MAX_COUNTER]; + u32 passthru; +} pm_regs; + +static signed char trace_bus[4]; +static signed char input_bus[2]; +#define UNUSED_WORD ~0 + +struct oprofile_umask { + union { + u32 val; + struct { + u32 pad1:16; + u32 sub_unit:4; + u32 pad2:2; + u32 bus_type:2; + u32 bus_word:4; + u32 pad3:1; + u32 input_control:1; + u32 polarity:1; + u32 count_cycles:1; + }; + }; +}; + + +/* + * Firmware interface functions + */ + +static void FW_Call_Rtas(PM_Rtas_Args *pArgs) +{ + int error; + + error = rtas_call(rtas_token("ibm,cbe-perftools"), 5, 1, NULL, + pArgs->subfunc, + pArgs->passthru, + pArgs->buffer_addr_hi, + pArgs->buffer_addr_lo, + pArgs->buffer_length); + + if (error) + printk(KERN_WARNING "error: ibm,cbe-perftools returned: %d\n", + error); +} + +static void FW_ResetSignals(u32 cpu) +{ + PM_Rtas_Args args; + u64 real_address; + + real_address = (u64) virt_to_phys(&(args.signal[0])); + + args.subfunc = subfunc_RESET; + args.passthru = passthru_DISABLE; + args.buffer_addr_hi = real_address >> 32; + args.buffer_addr_lo = real_address & 0xffffffff; + args.buffer_length = sizeof(PM_Signal); + args.signal[0].cpu = cpu; + + pm_regs.passthru = passthru_DISABLE; + + FW_Call_Rtas(&args); +} + +static void FW_ActivateSignals(u32 cpu, u32 count) +{ + PM_Rtas_Args *pArgs; + u64 real_address; + + pArgs = &rtas_args; + + real_address = (u64) virt_to_phys(&(pArgs->signal[0])); + + pArgs->subfunc = subfunc_ACTIVATE; + pArgs->buffer_addr_hi = real_address >> 32; + pArgs->buffer_addr_lo = real_address & 0xffffffff; + pArgs->buffer_length = count * sizeof(PM_Signal); + + if (pm_regs.passthru == passthru_DISABLE) { + pm_regs.passthru = passthru_ENABLE; + pArgs->passthru = passthru_ENABLE; + } + + FW_Call_Rtas(pArgs); +} + +/* + * PM Signal functions + */ + +static void set_pm_event(u32 ctr, int event, u32 unit_mask) +{ + PM_Signal *p; + u32 bus_word; + u32 bus_type; + u32 signal_bit; + struct oprofile_umask umask; + int i, j; + + if (event == 1) { + /* Special Event: Count all cpu cycles */ + pm_regs.pm07_control[ctr].val = CBE_COUNT_ALL_CYCLES; + return; + } + + umask.val = unit_mask; + + bus_word = umask.bus_word; + bus_type = umask.bus_type; + + signal_bit = (event % 100); + + p = &(rtas_args.signal[ctr]); + + p->signal_group = event / 100; + p->bus_word = bus_word; + p->sub_unit = umask.sub_unit; + + pm_regs.pm07_control[ctr].val = 0; + pm_regs.pm07_control[ctr].count_cycles = umask.count_cycles; + pm_regs.pm07_control[ctr].polarity = umask.polarity; + pm_regs.pm07_control[ctr].input_control = umask.input_control; + + if (umask.input_control == 0) { + if (signal_bit > 31) { + signal_bit -= 32; + if (bus_word == 0x3) + bus_word = 0x2; + else if (bus_word == 0xc) + bus_word = 0x8; + } + + if ((bus_type == 0) && p->signal_group >= 60) + bus_type = 2; + if ((bus_type == 1) && p->signal_group >= 50) + bus_type = 0; + + pm_regs.pm07_control[ctr].multiplexor = signal_bit; + } + else { + pm_regs.pm07_control[ctr].val = 0; + p->bit = signal_bit; + } + + for (i = 0; i < 4; i++) { + if (bus_word & (1 << i)) { + pm_regs.debug_bus_control |= + (bus_type << (31-(2*i)+1)); + + for (j = 0; j < 2; j++) { + if (input_bus[j] == UNUSED_WORD) { + input_bus[j] = i; + pm_regs.group_control |= (i << (31-i)); + break; + } + } + } + } +} + +static inline void set_count_mode(u32 kernel, u32 user) +{ + pm_regs.pm_control.trace_mode = (kernel ? + (user ? CBE_COUNT_ALL_MODES : CBE_COUNT_SUPERVISOR_MODE) : + (user ? CBE_COUNT_PROBLEM_MODE : CBE_COUNT_HYPERVISOR_MODE)); +} + +static inline void enable_ctr(u32 cpu, u32 ctr) +{ + pm_regs.pm07_control[ctr].count_enable = 1; + cbe_write_pm07_control (cpu, ctr, pm_regs.pm07_control[ctr].val); +} + +static inline void disable_ctr(u32 cpu, u32 ctr) +{ + pm_regs.pm07_control[ctr].count_enable = 0; + cbe_write_pm07_control (cpu, ctr, pm_regs.pm07_control[ctr].val); +} + + +/* + * Oprofile setup functions + */ + +static void cell_reg_setup(struct op_counter_config *ctr, + struct op_system_config *sys, + int num_ctrs) +{ + int nEnabled = 0; + int i; + + /* This function is called once for all cpus combined */ + + num_counters = num_ctrs; + + pm_regs.group_control = 0; + pm_regs.debug_bus_control = 0; + pm_regs.pm_control.val = 0; + pm_regs.pm_control.freeze = 1; + + set_count_mode(sys->enable_kernel, sys->enable_user); + + for (i = 0; i < 4; i++) + trace_bus[i] = UNUSED_WORD; + + for (i = 0; i < 2; i++) + input_bus[i] = UNUSED_WORD; + + /* Our counters count up, and "count" refers to + * how much before the next interrupt, and we interrupt + * on overflow. So we calculate the starting value + * which will give us "count" until overflow. + * Then we set the events on the enabled counters. + */ + + for (i = 0; i < num_counters; ++i) { + if (ctr[i].enabled) { + /* Divide count by 2 so interrupt handler can + toggle between threads to allow a full interval + per thread with the 2 threads offset by a + half-interval. + */ + reset_value[i] = ~(ctr[i].count / 2) + 1; + set_pm_event(nEnabled, ctr[i].event, ctr[i].unit_mask); + nEnabled++; + ctr_enabled |= (1 << i); + } + } + + for (i = 4; i < 8; ++i) { + ctrSize[i-4] = (ctr_enabled & (1 << i)) ? 16 : 32; + } + + iic_setup_iic_is(); +} + +static void cell_cpu_setup(void *unused) +{ + u32 cpu = smp_processor_id(); + u32 nEnabled = 0; + struct pm_status mask; + int i; + + /* This function is called once for each cpu */ + + /* There is one performance monitor for cpu 0/1 and one for cpu 2/3 */ + + if (cpu & 1) + return; + + /* Stop all counters */ + cbe_disable_pm(cpu); + cbe_disable_pm_interrupts(cpu); + + cbe_write_pm (cpu, pm_interval, 0); + cbe_write_pm (cpu, pm_start_stop, 0); + cbe_write_pm (cpu, group_control, pm_regs.group_control); + cbe_write_pm (cpu, debug_bus_control, pm_regs.debug_bus_control); + cbe_write_pm (cpu, pm_control, pm_regs.pm_control.val); + + mask.val = 0; + + for (i = 0; i < num_counters; ++i) { + if (ctr_enabled & (1 << i)) { + rtas_args.signal[nEnabled].cpu = cpu; + nEnabled++; + mask.ctr_overflow |= CBE_CTR_OVERFLOW(i); + } + } + + + FW_ActivateSignals(cpu, nEnabled); +} + +static void cell_start(struct op_counter_config *ctr) +{ + u32 cpu = smp_processor_id(); + struct pm_status mask; + int i; + + /* There is one performance monitor for cpu 0/1 and one for cpu 2/3 */ + + if (cpu & 1) + return; + + mask.val = 0; + + for (i = 0; i < num_counters; ++i) { + if (ctr_enabled & (1 << i)) { + cbe_write_ctr(cpu, i, reset_value[i]); + enable_ctr(cpu, i); + mask.ctr_overflow |= CBE_CTR_OVERFLOW(i); + } else { + disable_ctr(cpu, i); + } + } + + cbe_clear_pm_interrupts(cpu); + cbe_enable_pm_interrupts(cpu, 0, mask.val); + cbe_enable_pm(cpu); + + oprofile_running = 1; + + dbg("oprofile started on BE %d\n", cpu >> 1); +} + +static void cell_stop(void) +{ + u32 cpu = smp_processor_id(); + + /* There is one performance monitor for cpu 0/1 and one for cpu 2/3 */ + + if (cpu & 1) + return; + + /* Stop the counters */ + cbe_disable_pm(cpu); + + /* Deactivate the signals */ + FW_ResetSignals(cpu); + + /* Deactivate interrupts */ + cbe_disable_pm_interrupts(cpu); + + oprofile_running = 0; + + dbg("oprofile stopped on BE %d\n", cpu >> 1); + + mb(); +} + +static void cell_handle_interrupt(struct pt_regs *regs, + struct op_counter_config *ctr) +{ + u32 cpu; + u32 handler_thread; + u64 pc; + int is_kernel; + struct pm_status mask; + int i; + + cpu = smp_processor_id(); + handler_thread = cpu & 1; + + dbg_hw ("\n---------> cell_handle_interrupt\n"); + dbg_hw (" interrupt on: node %d\n", node); + dbg_hw (" handled by: node %d, cpu %d, thread %d\n", + cpu >> 1, cpu, handler_thread); + + cbe_disable_pm(cpu); + + pc = regs->nip; + is_kernel = is_kernel_addr(pc); + + mask.val = cbe_clear_pm_interrupts(cpu); + + for (i = 0; i < num_counters; ++i) { + if (mask.ctr_overflow & CBE_CTR_OVERFLOW(i)) { + if (ctr[i].enabled) { + oprofile_add_pc(pc, is_kernel, i); + cbe_write_ctr(cpu, i, reset_value[i]); + } + } + } + + /* The counters were frozen by the interrupt. + * Reenable the interrupt and restart the counters. + */ + cbe_enable_pm_interrupts(cpu, ! handler_thread, 0); + cbe_enable_pm(cpu); +} + +struct op_powerpc_model op_model_cell = { + .reg_setup = cell_reg_setup, + .cpu_setup = cell_cpu_setup, + .start = cell_start, + .stop = cell_stop, + .handle_interrupt = cell_handle_interrupt, +}; Index: linus-2.6/include/asm-powerpc/cputable.h =================================================================== --- linus-2.6.orig/include/asm-powerpc/cputable.h +++ linus-2.6/include/asm-powerpc/cputable.h @@ -40,6 +40,7 @@ enum powerpc_oprofile_type { PPC_OPROFILE_POWER4 = 2, PPC_OPROFILE_G4 = 3, PPC_OPROFILE_BOOKE = 4, + PPC_OPROFILE_CELL = 5, }; struct cpu_spec {