/*
* PS3 Logical Performance Monitor.
*
* Copyright (C) 2007 Sony Computer Entertainment Inc.
* Copyright 2007 Sony Corp.
*
* 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; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/uaccess.h>
#include <asm/smp.h>
#include <asm/time.h>
#include <asm/ps3.h>
#include <asm/lv1call.h>
#include <asm/cell-pmu.h>
/* BOOKMARK tag macros */
#define PS3_PM_BOOKMARK_START 0x8000000000000000ULL
#define PS3_PM_BOOKMARK_STOP 0x4000000000000000ULL
#define PS3_PM_BOOKMARK_TAG_KERNEL 0x1000000000000000ULL
#define PS3_PM_BOOKMARK_TAG_USER 0x3000000000000000ULL
#define PS3_PM_BOOKMARK_TAG_MASK_HI 0xF000000000000000ULL
#define PS3_PM_BOOKMARK_TAG_MASK_LO 0x0F00000000000000ULL
/* CBE PM CONTROL register macros */
#define PS3_PM_CONTROL_PPU_TH0_BOOKMARK 0x00001000
#define PS3_PM_CONTROL_PPU_TH1_BOOKMARK 0x00000800
#define PS3_PM_CONTROL_PPU_COUNT_MODE_MASK 0x000C0000
#define PS3_PM_CONTROL_PPU_COUNT_MODE_PROBLEM 0x00080000
#define PS3_WRITE_PM_MASK 0xFFFFFFFFFFFFFFFFULL
/* CBE PM START STOP register macros */
#define PS3_PM_START_STOP_PPU_TH0_BOOKMARK_START 0x02000000
#define PS3_PM_START_STOP_PPU_TH1_BOOKMARK_START 0x01000000
#define PS3_PM_START_STOP_PPU_TH0_BOOKMARK_STOP 0x00020000
#define PS3_PM_START_STOP_PPU_TH1_BOOKMARK_STOP 0x00010000
#define PS3_PM_START_STOP_START_MASK 0xFF000000
#define PS3_PM_START_STOP_STOP_MASK 0x00FF0000
/* CBE PM COUNTER register macres */
#define PS3_PM_COUNTER_MASK_HI 0xFFFFFFFF00000000ULL
#define PS3_PM_COUNTER_MASK_LO 0x00000000FFFFFFFFULL
/* BASE SIGNAL GROUP NUMBER macros */
#define PM_ISLAND2_BASE_SIGNAL_GROUP_NUMBER 0
#define PM_ISLAND2_SIGNAL_GROUP_NUMBER1 6
#define PM_ISLAND2_SIGNAL_GROUP_NUMBER2 7
#define PM_ISLAND3_BASE_SIGNAL_GROUP_NUMBER 7
#define PM_ISLAND4_BASE_SIGNAL_GROUP_NUMBER 15
#define PM_SPU_TRIGGER_SIGNAL_GROUP_NUMBER 17
#define PM_SPU_EVENT_SIGNAL_GROUP_NUMBER 18
#define PM_ISLAND5_BASE_SIGNAL_GROUP_NUMBER 18
#define PM_ISLAND6_BASE_SIGNAL_GROUP_NUMBER 24
#define PM_ISLAND7_BASE_SIGNAL_GROUP_NUMBER 49
#define PM_ISLAND8_BASE_SIGNAL_GROUP_NUMBER 52
#define PM_SIG_GROUP_SPU 41
#define PM_SIG_GROUP_SPU_TRIGGER 42
#define PM_SIG_GROUP_SPU_EVENT 43
#define PM_SIG_GROUP_MFC_MAX 60
/**
* struct ps3_lpm_shadow_regs - Performance monitor shadow registers.
*
* @pm_control: Shadow of the processor's pm_control register.
* @pm_start_stop: Shadow of the processor's pm_start_stop register.
* @group_control: Shadow of the processor's group_control register.
* @debug_bus_control: Shadow of the processor's debug_bus_control register.
*
* The logical performance monitor provides a write-only interface to
* these processor registers. These shadow variables cache the processor
* register values for reading.
*
* The initial value of the shadow registers at lpm creation is
* PS3_LPM_SHADOW_REG_INIT.
*/
struct ps3_lpm_shadow_regs {
u64 pm_control;
u64 pm_start_stop;
u64 group_control;
u64 debug_bus_control;
};
#define PS3_LPM_SHADOW_REG_INIT 0xFFFFFFFF00000000ULL
/**
* struct ps3_lpm_priv - Private lpm device data.
*
* @open: An atomic variable indicating the lpm driver has been opened.
* @rights: The lpm rigths granted by the system policy module. A logical
* OR of enum ps3_lpm_rights.
* @node_id: The node id of a BE processor whose performance monitor this
* lpar has the right to use.
* @pu_id: The lv1 id of the logical PU.
* @lpm_id: The lv1 id of this lpm instance.
* @outlet_id: The outlet created by lv1 for this lpm instance.
* @tb_count: The number of bytes of data held in the lv1 trace buffer.
* @tb_cache: Kernel buffer to receive the data from the lv1 trace buffer.
* Must be 128 byte aligned.
* @tb_cache_size: Size of the kernel @tb_cache buffer. Must be 128 byte
* aligned.
* @tb_cache_internal: An unaligned buffer allocated by this driver to be
* used for the trace buffer cache when ps3_lpm_open() is called with a
* NULL tb_cache argument. Otherwise unused.
* @shadow: Processor register shadow of type struct ps3_lpm_shadow_regs.
* @sbd: The struct ps3_system_bus_device attached to this driver.
*
* The trace buffer is a buffer allocated and used internally to the lv1
* hypervisor to collect trace data. The trace buffer cache is a guest
* buffer that accepts the trace data from the trace buffer.
*/
struct ps3_lpm_priv {
atomic_t open;
u64 rights;
u64 node_id;
u64 pu_id;
u64 lpm_id;
u64 outlet_id;
u64 tb_count;
void *tb_cache;
u64 tb_cache_size;
void *tb_cache_internal;
struct ps3_lpm_shadow_regs shadow;
struct ps3_system_bus_device *sbd;
};
enum {
PS3_LPM_DEFAULT_TB_CACHE_SIZE = 0x4000,
};
/**
* lpm_priv - Static instance of the lpm data.
*
* Since the exported routines don't support the notion of a device
* instance we need to hold the instance in this static variable
* and then only allow at most one instance at a time to be created.
*/
static struct ps3_lpm_priv *lpm_priv;
static struct device *sbd_core(void)
{
BUG_ON(!lpm_priv || !lpm_priv->sbd);
return &lpm_priv->sbd->core;
}
/**
* use_start_stop_bookmark - Enable the PPU bookmark trace.
*
* And it enables PPU bookmark triggers ONLY if the other triggers are not set.
* The start/stop bookmarks are inserted at ps3_enable_pm() and ps3_disable_pm()
* to start/stop LPM.
*
* Used to get good quality of the performance counter.
*/
enum {use_start_stop_bookmark = 1,};
void ps3_set_bookmark(u64 bookmark)
{
/*
* As per the PPE book IV, to avoid bookmark loss there must
* not be a traced branch within 10 cycles of setting the
* SPRN_BKMK register. The actual text is unclear if 'within'
* includes cycles before the call.
*/
asm volatile("nop;nop;nop;nop;nop;nop;nop;nop;nop;");
mtspr(SPRN_BKMK, bookmark);
asm volatile("nop;nop;nop;nop;nop;nop;nop;nop;nop;");
}
EXPORT_SYMBOL_GPL(ps3_set_bookmark);
void ps3_set_pm_bookmark(u64 tag, u64