blob: a794cc28f6a2a2ef3fb55551727468b3c037e0bd [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
/*
* In-Memory Counters (IMC)
* Sometimes called IMA, but that's also a different thing.
*
* Copyright 2016-2019 IBM Corp.
*/
#define pr_fmt(fmt) "IMC: " fmt
#include <skiboot.h>
#include <slw.h>
#include <xscom.h>
#include <imc.h>
#include <chip.h>
#include <libxz/xz.h>
#include <device.h>
#include <p9_stop_api.H>
/*
* IMC trace scom values
*/
#define IMC_TRACE_CPMC1 0 /* select cpmc1 */
#define IMC_TRACE_CPMC2 1 /* select cpmc2 */
#define IMC_TRACE_CPMCLOAD_VAL 0xfa /*
* Value to be loaded into cpmc2
* at sampling start
*/
/* Event: CPM_32MHZ_CYC */
#define IMC_TRACE_CPMC2SEL_VAL 2
#define IMC_TRACE_CPMC1SEL_VAL 4
#define IMC_TRACE_BUFF_SIZE 0 /*
* b’000’- 4K entries * 64 per
* entry = 256K buffersize
*/
static uint64_t TRACE_IMC_ADDR;
static uint64_t CORE_IMC_EVENT_MASK_ADDR;
static uint64_t trace_scom_val;
/*
* Initialise these with the pdbar and htm scom port address array
* at run time, based on the processor version.
*/
static unsigned int *pdbar_scom_index;
static unsigned int *htm_scom_index;
/*
* Nest IMC PMU names along with their bit values as represented in the
* imc_chip_avl_vector(in struct imc_chip_cb, look at include/imc.h).
* nest_pmus[] is an array containing all the possible nest IMC PMU node names.
*/
static const char *nest_pmus_p9[] = {
"powerbus0",
"mcs0",
"mcs1",
"mcs2",
"mcs3",
"mcs4",
"mcs5",
"mcs6",
"mcs7",
"mba0",
"mba1",
"mba2",
"mba3",
"mba4",
"mba5",
"mba6",
"mba7",
"centaur0",
"centaur1",
"centaur2",
"centaur3",
"centaur4",
"centaur5",
"centaur6",
"centaur7",
"xlink0",
"xlink1",
"xlink2",
"mcd0",
"mcd1",
"phb0",
"phb1",
"phb2",
"phb3",
"phb4",
"phb5",
"nx",
"capp0",
"capp1",
"vas",
"int",
"alink0",
"alink1",
"alink2",
"alink3",
"nvlink0",
"nvlink1",
"nvlink2",
"nvlink3",
"nvlink4",
"nvlink5",
/* reserved bits : 51 - 63 */
};
static const char *nest_pmus_p10[] = {
"pb",
"mcs0",
"mcs1",
"mcs2",
"mcs3",
"mcs4",
"mcs5",
"mcs6",
"mcs7",
"pec0",
"pec1",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"NA",
"phb0",
"phb1",
"phb2",
"phb3",
"phb4",
"phb5",
"ocmb0",
"ocmb1",
"ocmb2",
"ocmb3",
"ocmb4",
"ocmb5",
"ocmb6",
"ocmb7",
"ocmb8",
"ocmb9",
"ocmb10",
"ocmb11",
"ocmb12",
"ocmb13",
"ocmb14",
"ocmb15",
"nx",
};
/*
* Due to Nest HW/OCC restriction, microcode will not support individual unit
* events for these nest units mcs0, mcs1 ... mcs7 in the accumulation mode.
* And events to monitor each mcs units individually will be supported only
* in the debug mode (which will be supported by microcode in the future).
* These will be advertised only when OPAL provides interface for the it.
*/
static char const *debug_mode_units[] = {
"mcs0",
"mcs1",
"mcs2",
"mcs3",
"mcs4",
"mcs5",
"mcs6",
"mcs7",
};
/*
* Combined unit node events are counted when any of the individual
* unit is enabled in the availability vector. That is,
* ex, mcs01 unit node should be enabled only when mcs0 or mcs1 enabled.
* mcs23 unit node should be enabled only when mcs2 or mcs3 is enabled
*/
static struct combined_units_node cu_node[] = {
{ .name = "mcs01", .unit1 = PPC_BIT(1), .unit2 = PPC_BIT(2) },
{ .name = "mcs23", .unit1 = PPC_BIT(3), .unit2 = PPC_BIT(4) },
{ .name = "mcs45", .unit1 = PPC_BIT(5), .unit2 = PPC_BIT(6) },
{ .name = "mcs67", .unit1 = PPC_BIT(7), .unit2 = PPC_BIT(8) },
};
static char *compress_buf;
static size_t compress_buf_size;
const char **prop_to_fix(struct dt_node *node);
static const char *props_to_fix[] = {"events", NULL};
static bool is_nest_mem_initialized(struct imc_chip_cb *ptr)
{
/*
* Non zero value in "Status" field indicate memory initialized.
*/
if (!ptr->imc_chip_run_status)
return false;
return true;
}
/*
* A Quad contains 4 cores in Power 9, and there are 4 addresses for
* the Core Hardware Trace Macro (CHTM) attached to each core.
* So, for core index 0 to core index 3, we have a sequential range of
* SCOM port addresses in the arrays below, each for Hardware Trace Macro (HTM)
* mode and PDBAR.
*/
static unsigned int pdbar_scom_index_p9[] = {
0x1001220B,
0x1001230B,
0x1001260B,
0x1001270B
};
static unsigned int htm_scom_index_p9[] = {
0x10012200,
0x10012300,
0x10012600,
0x10012700
};
static unsigned int pdbar_scom_index_p10[] = {
0x2001868B,
0x2001468B,
0x2001268B,
0x2001168B
};
static unsigned int htm_scom_index_p10[] = {
0x20018680,
0x20014680,
0x20012680,
0x20011680
};
static struct imc_chip_cb *get_imc_cb(uint32_t chip_id)
{
struct proc_chip *chip = get_chip(chip_id);
struct imc_chip_cb *cb;
if (!chip->homer_base)
return NULL; /* The No Homers Club */
cb = (struct imc_chip_cb *)(chip->homer_base + P9_CB_STRUCT_OFFSET);
if (!is_nest_mem_initialized(cb))
return NULL;
return cb;
}
static int pause_microcode_at_boot(void)
{
struct proc_chip *chip;
struct imc_chip_cb *cb;
for_each_chip(chip) {
cb = get_imc_cb(chip->id);
if (cb)
cb->imc_chip_command = cpu_to_be64(NEST_IMC_DISABLE);
else
return -1; /* ucode is not init-ed */
}
return 0;
}
/*
* Function return list of properties names for the fixup
*/
const char **prop_to_fix(struct dt_node *node)
{
if (dt_node_is_compatible(node, "ibm,imc-counters"))
return props_to_fix;
return NULL;
}
/* Helper to get the IMC device type for a device node */
static int get_imc_device_type(struct dt_node *node)
{
const struct dt_property *type;
u32 val=0;
if (!node)
return -1;
type = dt_find_property(node, "type");
if (!type)
return -1;
val = dt_prop_get_u32(node, "type");
switch (val){
case IMC_COUNTER_CHIP:
return IMC_COUNTER_CHIP;
case IMC_COUNTER_CORE:
return IMC_COUNTER_CORE;
case IMC_COUNTER_THREAD:
return IMC_COUNTER_THREAD;
case IMC_COUNTER_TRACE:
return IMC_COUNTER_TRACE;
default:
break;
}
/* Unknown/Unsupported IMC device type */
return -1;
}
static bool is_nest_node(struct dt_node *node)
{
if (get_imc_device_type(node) == IMC_COUNTER_CHIP)
return true;
return false;
}
static bool is_imc_device_type_supported(struct dt_node *node)
{
u32 val = get_imc_device_type(node);
struct proc_chip *chip = get_chip(this_cpu()->chip_id);
uint64_t pvr;
if ((val == IMC_COUNTER_CHIP) || (val == IMC_COUNTER_CORE) ||
(val == IMC_COUNTER_THREAD))
return true;
if (val == IMC_COUNTER_TRACE) {
pvr = mfspr(SPR_PVR);
switch (chip->type) {
case PROC_CHIP_P9_NIMBUS:
/*
* Trace mode is supported in Nimbus DD2.2
* and later versions.
*/
if ((PVR_VERS_MAJ(pvr) == 2) &&
(PVR_VERS_MIN(pvr) >= 2))
return true;
break;
case PROC_CHIP_P10:
return true;
default:
return false;
}
}
return false;
}
/*
* Helper to check for the imc device type in the incoming device tree.
* Remove unsupported device node.
*/
static void check_imc_device_type(struct dt_node *dev)
{
struct dt_node *node;
dt_for_each_compatible(dev, node, "ibm,imc-counters") {
if (!is_imc_device_type_supported(node)) {
/*
* ah nice, found a device type which I didnt know.
* Remove it and also mark node as NULL, since dt_next
* will try to fetch info for "prev" which is removed
* by dt_free.
*/
dt_free(node);
node = NULL;
}
}
return;
}
static void imc_dt_exports_prop_add(struct dt_node *dev)
{
struct dt_node *node;
struct proc_chip *chip;
const struct dt_property *type;
uint32_t offset = 0, size = 0;
uint64_t baddr;
char namebuf[32];
dt_for_each_compatible(dev, node, "ibm,imc-counters") {
type = dt_find_property(node, "type");
if (type && is_nest_node(node)) {
offset = dt_prop_get_u32(node, "offset");
size = dt_prop_get_u32(node, "size");
}
}
/*
* Enable only if we have valid values.
*/
if (!size && !offset)
return;
node = dt_find_by_name(opal_node, "exports");
if (!node)
return;
for_each_chip(chip) {
snprintf(namebuf, sizeof(namebuf), "imc_nest_chip_%x", chip->id);
baddr = chip->homer_base;
baddr += offset;
dt_add_property_u64s(node, namebuf, baddr, size);
}
}
/*
* Remove the PMU device nodes from the incoming new subtree, if they are not
* available in the hardware. The availability is described by the
* control block's imc_chip_avl_vector.
* Each bit represents a device unit. If the device is available, then
* the bit is set else its unset.
*/
static void disable_unavailable_units(struct dt_node *dev)
{
uint64_t avl_vec;
struct imc_chip_cb *cb;
struct dt_node *target;
int i, j;
bool disable_all_nests = false;
struct proc_chip *chip;
/*
* Check the state of ucode in all the chip.
* Disable the nest unit if ucode is not initialized
* in any of the chip.
*/
for_each_chip(chip) {
cb = get_imc_cb(chip->id);
if (!cb) {
/*
* At least currently, if one chip isn't functioning,
* none of the IMC Nest units will be functional.
* So while you may *think* this should be per chip,
* it isn't.
*/
disable_all_nests = true;
break;
}
}
/* Add a property to "exports" node in opal_node */
imc_dt_exports_prop_add(dev);
/* Fetch the IMC control block structure */
cb = get_imc_cb(this_cpu()->chip_id);
if (cb && !disable_all_nests)
avl_vec = be64_to_cpu(cb->imc_chip_avl_vector);
else {
avl_vec = 0; /* Remove only nest imc device nodes */
/* Incase of mambo, just fake it */
if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS)
avl_vec = (0xffULL) << 56;
}
if (proc_gen == proc_gen_p9) {
for (i = 0; i < ARRAY_SIZE(nest_pmus_p9); i++) {
if (!(PPC_BIT(i) & avl_vec)) {
/* Check if the device node exists */
target = dt_find_by_name_before_addr(dev, nest_pmus_p9[i]);
if (!target)
continue;
/* Remove the device node */
dt_free(target);
}
}
} else if (proc_gen == proc_gen_p10) {
int val;
char name[8];
for (i = 0; i < 11; i++) {
if (!(PPC_BIT(i) & avl_vec)) {
/* Check if the device node exists */
target = dt_find_by_name_before_addr(dev, nest_pmus_p10[i]);
if (!target)
continue;
/* Remove the device node */
dt_free(target);
}
}
for (i = 35; i < 41; i++) {
if (!(PPC_BIT(i) & avl_vec)) {
/* Check if the device node exists for phb */
for (j = 0; j < 3; j++) {
snprintf(name, sizeof(name), "phb%d_%d", (i-35), j);
target = dt_find_by_name_before_addr(dev, name);
if (!target)
continue;
/* Remove the device node */
dt_free(target);
}
}
}
for (i = 41; i < 58; i++) {
if (!(PPC_BIT(i) & avl_vec)) {
/* Check if the device node exists */
target = dt_find_by_name_before_addr(dev, nest_pmus_p10[i]);
if (!target)
continue;
/* Remove the device node */
dt_free(target);
}
}
for (i = 0; i < 8; i++) {
val = ((avl_vec & (0x7ULL << (29 + (3 * i)))) >> (29 + (3 * i)));
switch (val) {
case 0x5: //xlink configured and functional
snprintf(name, sizeof(name), "alink%1d", (7-i));
target = dt_find_by_name_before_addr(dev, name);
if (target)
dt_free(target);
snprintf(name, sizeof(name), "otl%1d_0", (7-i));
target = dt_find_by_name_before_addr(dev, name);
if (target)
dt_free(target);
snprintf(name, sizeof(name), "otl%1d_1", (7-i));
target = dt_find_by_name_before_addr(dev, name);
if (target)
dt_free(target);
break;
case 0x6: //alink configured and functional
snprintf(name, sizeof(name), "xlink%1d", (7-i));
target = dt_find_by_name_before_addr(dev, name);
if (target)
dt_free(target);
snprintf(name, sizeof(name), "otl%1d_0", (7-i));
target = dt_find_by_name_before_addr(dev, name);
if (target)
dt_free(target);
snprintf(name, sizeof(name), "otl%1d_1", (7-i));
target = dt_find_by_name_before_addr(dev, name);
if (target)
dt_free(target);
break;
case 0x7: //CAPI configured and functional
snprintf(name, sizeof(name), "alink%1d", (7-i));
target = dt_find_by_name_before_addr(dev, name);
if (target)
dt_free(target);
snprintf(name, sizeof(name), "xlink%1d", (7-i));
target = dt_find_by_name_before_addr(dev, name);
if (target)
dt_free(target);
break;
default:
snprintf(name, sizeof(name), "xlink%1d", (7-i));
target = dt_find_by_name_before_addr(dev, name);
if (target)
dt_free(target);
snprintf(name, sizeof(name), "alink%1d", (7-i));
target = dt_find_by_name_before_addr(dev, name);
if (target)
dt_free(target);
snprintf(name, sizeof(name), "otl%1d_0", (7-i));
target = dt_find_by_name_before_addr(dev, name);
if (target)
dt_free(target);
snprintf(name, sizeof(name), "otl%1d_1", (7-i));
target = dt_find_by_name_before_addr(dev, name);
if (target)
dt_free(target);
break;
}
}
}
/*
* Loop to detect debug mode units and remove them
* since the microcode does not support debug mode function yet.
*/
for (i = 0; i < ARRAY_SIZE(debug_mode_units); i++) {
target = dt_find_by_name(dev, debug_mode_units[i]);
if (!target)
continue;
/* Remove the device node */
dt_free(target);
}
/*
* Based on availability unit vector from control block,
* check and enable combined unit nodes in the device tree.
*/
for (i = 0; i < MAX_NEST_COMBINED_UNITS ; i++ ) {
if (!(cu_node[i].unit1 & avl_vec) &&
!(cu_node[i].unit2 & avl_vec)) {
target = dt_find_by_name(dev, cu_node[i].name);
if (!target)
continue;
/* Remove the device node */
dt_free(target);
}
}
return;
}
static void disable_imc_type_from_dt(struct dt_node *dev, int imc_type)
{
struct dt_node *node;
dt_for_each_compatible(dev, node, "ibm,imc-counters") {
if (get_imc_device_type(node) == imc_type) {
dt_free(node);
node = NULL;
}
}
return;
}
/*
* Function to queue the loading of imc catalog data
* from the IMC pnor partition.
*/
void imc_catalog_preload(void)
{
uint32_t pvr = (mfspr(SPR_PVR) & ~(0xf0ff));
int ret = OPAL_SUCCESS;
compress_buf_size = MAX_COMPRESSED_IMC_DTB_SIZE;
if (proc_chip_quirks & (QUIRK_MAMBO_CALLOUTS | QUIRK_BML))
return;
/* Enable only for power 9/10 */
if (proc_gen < proc_gen_p9)
return;
compress_buf = malloc(MAX_COMPRESSED_IMC_DTB_SIZE);
if (!compress_buf) {
prerror("Memory allocation for catalog failed\n");
return;
}
ret = start_preload_resource(RESOURCE_ID_IMA_CATALOG,
pvr, compress_buf, &compress_buf_size);
if (ret != OPAL_SUCCESS) {
prerror("Failed to load IMA_CATALOG: %d\n", ret);
free(compress_buf);
compress_buf = NULL;
}
return;
}
static void imc_dt_update_nest_node(struct dt_node *dev)
{
struct proc_chip *chip;
__be64 *base_addr = NULL;
__be32 *chipids = NULL;
int i=0, nr_chip = nr_chips();
struct dt_node *node;
const struct dt_property *type;
/* Add the base_addr and chip-id properties for the nest node */
base_addr = malloc(sizeof(u64) * nr_chip);
chipids = malloc(sizeof(u32) * nr_chip);
for_each_chip(chip) {
base_addr[i] = cpu_to_be64(chip->homer_base);
chipids[i] = cpu_to_be32(chip->id);
i++;
}
dt_for_each_compatible(dev, node, "ibm,imc-counters") {
type = dt_find_property(node, "type");
if (type && is_nest_node(node)) {
dt_add_property(node, "base-addr", base_addr, (i * sizeof(u64)));
dt_add_property(node, "chip-id", chipids, (i * sizeof(u32)));
}
}
}
static struct xz_decompress *imc_xz;
void imc_decompress_catalog(void)
{
void *decompress_buf = NULL;
uint32_t pvr = (mfspr(SPR_PVR) & ~(0xf0ff));
int ret;
/* Check we succeeded in starting the preload */
if (compress_buf == NULL)
return;
ret = wait_for_resource_loaded(RESOURCE_ID_IMA_CATALOG, pvr);
if (ret != OPAL_SUCCESS) {
prerror("IMC Catalog load failed\n");
return;
}
/*
* Memory for decompression.
*/
decompress_buf = malloc(MAX_DECOMPRESSED_IMC_DTB_SIZE);
if (!decompress_buf) {
prerror("No memory for decompress_buf \n");
return;
}
/*
* Decompress the compressed buffer
*/
imc_xz = malloc(sizeof(struct xz_decompress));
if (!imc_xz) {
prerror("No memory to decompress IMC catalog\n");
free(decompress_buf);
return;
}
imc_xz->dst = decompress_buf;
imc_xz->src = compress_buf;
imc_xz->dst_size = MAX_DECOMPRESSED_IMC_DTB_SIZE;
imc_xz->src_size = compress_buf_size;
xz_start_decompress(imc_xz);
}
static int setup_imc_scoms(void)
{
switch (proc_gen) {
case proc_gen_p9:
CORE_IMC_EVENT_MASK_ADDR = CORE_IMC_EVENT_MASK_ADDR_P9;
TRACE_IMC_ADDR = TRACE_IMC_ADDR_P9;
pdbar_scom_index = pdbar_scom_index_p9;
htm_scom_index = htm_scom_index_p9;
trace_scom_val = TRACE_IMC_SCOM(IMC_TRACE_CPMC2,
IMC_TRACE_CPMCLOAD_VAL,
IMC_TRACE_CPMC1SEL_VAL,
IMC_TRACE_CPMC2SEL_VAL,
IMC_TRACE_BUFF_SIZE);
return 0;
case proc_gen_p10:
CORE_IMC_EVENT_MASK_ADDR = CORE_IMC_EVENT_MASK_ADDR_P10;
TRACE_IMC_ADDR = TRACE_IMC_ADDR_P10;
pdbar_scom_index = pdbar_scom_index_p10;
htm_scom_index = htm_scom_index_p10;
trace_scom_val = TRACE_IMC_SCOM(IMC_TRACE_CPMC1,
IMC_TRACE_CPMCLOAD_VAL,
IMC_TRACE_CPMC1SEL_VAL,
IMC_TRACE_CPMC2SEL_VAL,
IMC_TRACE_BUFF_SIZE);
return 0;
default:
prerror("%s: Unknown cpu type\n", __func__);
break;
}
return -1;
}
/*
* Load the IMC pnor partition and find the appropriate sub-partition
* based on the platform's PVR.
* Decompress the sub-partition and link the imc device tree to the
* existing device tree.
*/
void imc_init(void)
{
struct dt_node *dev;
int err_flag = -1;
if (proc_chip_quirks & (QUIRK_MAMBO_CALLOUTS | QUIRK_BML)) {
dev = dt_find_compatible_node(dt_root, NULL,
"ibm,opal-in-memory-counters");
if (!dev)
return;
goto imc_mambo_bml;
}
/* Enable only for power 9/10 */
if (proc_gen < proc_gen_p9)
return;
if (!imc_xz)
return;
wait_xz_decompress(imc_xz);
if (imc_xz->status != OPAL_SUCCESS) {
prerror("IMC: xz_decompress failed\n");
goto err;
}
/*
* Flow of the data from PNOR to main device tree:
*
* PNOR -> compressed local buffer (compress_buf)
* compressed local buffer -> decompressed local buf (decompress_buf)
* decompress local buffer -> main device tree
* free compressed local buffer
*/
/* Create a device tree entry for imc counters */
dev = dt_new_root("imc-counters");
if (!dev) {
prerror("IMC: Failed to add an imc-counters root node\n");
goto err;
}
/*
* Attach the new decompress_buf to the imc-counters node.
* dt_expand_node() does sanity checks for fdt_header, piggyback
*/
if (dt_expand_node(dev, imc_xz->dst, 0) < 0) {
dt_free(dev);
prerror("IMC: dt_expand_node failed\n");
goto err;
}
imc_mambo_bml:
if (setup_imc_scoms()) {
prerror("IMC: Failed to setup the scoms\n");
goto err;
}
/* Check and remove unsupported imc device types */
check_imc_device_type(dev);
/*
* Check and remove unsupported nest unit nodes by the microcode,
* from the incoming device tree.
*/
disable_unavailable_units(dev);
/* Fix the phandle in the incoming device tree */
dt_adjust_subtree_phandle(dev, prop_to_fix);
/* Update the base_addr and chip-id for nest nodes */
imc_dt_update_nest_node(dev);
if (proc_chip_quirks & (QUIRK_MAMBO_CALLOUTS | QUIRK_BML))
return;
/*
* IMC nest counters has both in-band (ucode access) and out of band
* access to it. Since not all nest counter configurations are supported
* by ucode, out of band tools are used to characterize other
* configuration.
*
* If the ucode not paused and OS does not have IMC driver support,
* then out to band tools will race with ucode and end up getting
* undesirable values. Hence pause the ucode if it is already running.
*/
if (pause_microcode_at_boot()) {
prerror("IMC: Pausing ucode failed, disabling nest imc\n");
disable_imc_type_from_dt(dev, IMC_COUNTER_CHIP);
}
/*
* If the dt_attach_root() fails, "imc-counters" node will not be
* seen in the device-tree and hence OS should not make any
* OPAL_IMC_* calls.
*/
if (!dt_attach_root(dt_root, dev)) {
dt_free(dev);
prerror("IMC: Failed to attach imc-counter node to dt root\n");
goto err;
}
err_flag = OPAL_SUCCESS;
err:
if (err_flag != OPAL_SUCCESS)
prerror("IMC Devices not added\n");
free(compress_buf);
free(imc_xz->dst);
free(imc_xz);
}
static int stop_api_init(struct proc_chip *chip, int phys_core_id,
uint32_t scoms, uint64_t data,
const ScomOperation_t operation,
const ScomSection_t section,
const char *type)
{
int ret;
prlog(PR_DEBUG, "Configuring stopapi for IMC\n");
ret = p9_stop_save_scom((void *)chip->homer_base, scoms,
data, operation, section);
if (ret) {
prerror("IMC %s stopapi ret = %d, scoms = %x (core id = %x)\n",\
type, ret, scoms, phys_core_id);
if (ret != STOP_SAVE_SCOM_ENTRY_UPDATE_FAILED)
wakeup_engine_state = WAKEUP_ENGINE_FAILED;
else
prerror("SCOM entries are full\n");
return OPAL_HARDWARE;
}
return ret;
}
/* Function to return the scom address for the specified core */
static uint32_t get_imc_scom_addr_for_core(int core, uint64_t addr)
{
uint32_t scom_addr;
switch (proc_gen) {
case proc_gen_p9:
scom_addr = XSCOM_ADDR_P9_EC(core, addr);
return scom_addr;
case proc_gen_p10:
scom_addr = XSCOM_ADDR_P10_EC(core, addr);
return scom_addr;
default:
return 0;
}
}
/* Function to return the scom address for the specified core in the quad */
static uint32_t get_imc_scom_addr_for_quad(int core, uint64_t addr)
{
uint32_t scom_addr;
switch (proc_gen) {
case proc_gen_p9:
scom_addr = XSCOM_ADDR_P9_EQ(core, addr);
return scom_addr;
case proc_gen_p10:
scom_addr = XSCOM_ADDR_P10_EQ(core, addr);
return scom_addr;
default:
return 0;
}
}
static int64_t core_imc_counters_init(uint64_t addr, int port_id,
int phys_core_id, struct cpu_thread *c)
{
uint32_t pdbar_addr, event_mask_addr, htm_addr;
int ret;
/* Get the scom address for this core, based on the platform */
pdbar_addr = get_imc_scom_addr_for_quad(phys_core_id,
pdbar_scom_index[port_id]);
event_mask_addr = get_imc_scom_addr_for_core(phys_core_id,
CORE_IMC_EVENT_MASK_ADDR);
/*
* Core IMC hardware mandate initing of three scoms
* to enbale or disable of the Core IMC engine.
*
* PDBAR: Scom contains the real address to store per-core
* counter data in memory along with other bits.
*
* EventMask: Scom contain bits to denote event to multiplex
* at different MSR[HV PR] values, along with bits for
* sampling duration.
*
* HTM Scom: scom to enable counter data movement to memory.
*/
if (xscom_write(c->chip_id, pdbar_addr,
(u64)(CORE_IMC_PDBAR_MASK & addr))) {
prerror("error in xscom_write for pdbar\n");
return OPAL_HARDWARE;
}
if (has_deep_states) {
if (wakeup_engine_state == WAKEUP_ENGINE_PRESENT) {
struct proc_chip *chip = get_chip(c->chip_id);
ret = stop_api_init(chip, phys_core_id, pdbar_addr,
(u64)(CORE_IMC_PDBAR_MASK & addr),
P9_STOP_SCOM_REPLACE,
P9_STOP_SECTION_EQ_SCOM,
"pdbar");
if (ret)
return ret;
ret = stop_api_init(chip, phys_core_id,
event_mask_addr,
(u64)CORE_IMC_EVENT_MASK,
P9_STOP_SCOM_REPLACE,
P9_STOP_SECTION_CORE_SCOM,
"event_mask");
if (ret)
return ret;
} else {
prerror("IMC: Wakeup engine not present!");
return OPAL_HARDWARE;
}
}
if (xscom_write(c->chip_id, event_mask_addr,
(u64)CORE_IMC_EVENT_MASK)) {
prerror("error in xscom_write for event mask\n");
return OPAL_HARDWARE;
}
/* Get the scom address for htm_mode scom based on the platform */
htm_addr = get_imc_scom_addr_for_quad(phys_core_id,
htm_scom_index[port_id]);
if (xscom_write(c->chip_id, htm_addr,
(u64)CORE_IMC_HTM_MODE_DISABLE)) {
prerror("error in xscom_write for htm mode\n");
return OPAL_HARDWARE;
}
return OPAL_SUCCESS;
}
/*
* opal_imc_counters_init : This call initialize the IMC engine.
*
* For Nest IMC, this is no-op and returns OPAL_SUCCESS at this point.
* For Core IMC, this initializes core IMC Engine, by initializing
* these scoms "PDBAR", "HTM_MODE" and the "EVENT_MASK" in a given cpu.
*/
static int64_t opal_imc_counters_init(uint32_t type, uint64_t addr, uint64_t cpu_pir)
{
struct cpu_thread *c = find_cpu_by_pir(cpu_pir);
int port_id, phys_core_id;
int ret;
uint32_t htm_addr, trace_addr;
switch (type) {
case OPAL_IMC_COUNTERS_NEST:
return OPAL_SUCCESS;
case OPAL_IMC_COUNTERS_CORE:
if (!c)
return OPAL_PARAMETER;
/*
* Core IMC hardware mandates setting of htm_mode and
* pdbar in specific scom ports. port_id are in
* pdbar_scom_index[] and htm_scom_index[].
*/
phys_core_id = pir_to_core_id(c->pir);
port_id = phys_core_id % 4;
if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS)
return OPAL_SUCCESS;
ret = core_imc_counters_init(addr, port_id, phys_core_id, c);
if (ret < 0)
return ret;
/*
* If fused core is supported, do the scoms for the
* secondary core also.
*/
if (this_cpu()->is_fused_core) {
struct cpu_thread *c1 = find_cpu_by_pir(cpu_pir ^ 1);
phys_core_id = pir_to_core_id(c1->pir);
port_id = phys_core_id % 4;
ret = core_imc_counters_init(addr, port_id, phys_core_id, c1);
if (ret < 0)
return ret;
}
return ret;
case OPAL_IMC_COUNTERS_TRACE:
if (!c)
return OPAL_PARAMETER;
phys_core_id = pir_to_core_id(c->pir);
port_id = phys_core_id % 4;
if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS)
return OPAL_SUCCESS;
trace_addr = get_imc_scom_addr_for_core(phys_core_id,
TRACE_IMC_ADDR);
htm_addr = get_imc_scom_addr_for_quad(phys_core_id,
htm_scom_index[port_id]);
if (has_deep_states) {
if (wakeup_engine_state == WAKEUP_ENGINE_PRESENT) {
struct proc_chip *chip = get_chip(c->chip_id);
ret = stop_api_init(chip, phys_core_id,
trace_addr,
trace_scom_val,
P9_STOP_SCOM_REPLACE,
P9_STOP_SECTION_CORE_SCOM,
"trace_imc");
if (ret)
return ret;
} else {
prerror("IMC-trace:Wakeup engine not present!");
return OPAL_HARDWARE;
}
}
if (xscom_write(c->chip_id, htm_addr, (u64)CORE_IMC_HTM_MODE_DISABLE)) {
prerror("IMC-trace: error in xscom_write for htm mode\n");
return OPAL_HARDWARE;
}
if (xscom_write(c->chip_id, trace_addr, trace_scom_val)) {
prerror("IMC-trace: error in xscom_write for trace mode\n");
return OPAL_HARDWARE;
}
return OPAL_SUCCESS;
}
return OPAL_SUCCESS;
}
opal_call(OPAL_IMC_COUNTERS_INIT, opal_imc_counters_init, 3);
/* opal_imc_counters_control_start: This call starts the nest/core imc engine. */
static int64_t opal_imc_counters_start(uint32_t type, uint64_t cpu_pir)
{
u64 op;
struct cpu_thread *c = find_cpu_by_pir(cpu_pir);
struct imc_chip_cb *cb;
int port_id, phys_core_id;
uint32_t htm_addr;
if (!c)
return OPAL_PARAMETER;
switch (type) {
case OPAL_IMC_COUNTERS_NEST:
/* Fetch the IMC control block structure */
cb = get_imc_cb(c->chip_id);
if (!cb)
return OPAL_HARDWARE;
/* Set the run command */
op = NEST_IMC_ENABLE;
if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS)
return OPAL_SUCCESS;
/* Write the command to the control block now */
cb->imc_chip_command = cpu_to_be64(op);
return OPAL_SUCCESS;
case OPAL_IMC_COUNTERS_CORE:
case OPAL_IMC_COUNTERS_TRACE:
/*
* Core IMC hardware mandates setting of htm_mode in specific
* scom ports (port_id are in htm_scom_index[])
*/
phys_core_id = pir_to_core_id(c->pir);
port_id = phys_core_id % 4;
if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS)
return OPAL_SUCCESS;
htm_addr = get_imc_scom_addr_for_quad(phys_core_id,
htm_scom_index[port_id]);
/*
* Enables the core imc engine by appropriately setting
* bits 4-9 of the HTM_MODE scom port. No initialization
* is done in this call. This just enables the the counters
* to count with the previous initialization.
*/
if (xscom_write(c->chip_id, htm_addr, (u64)CORE_IMC_HTM_MODE_ENABLE)) {
prerror("IMC OPAL_start: error in xscom_write for htm_mode\n");
return OPAL_HARDWARE;
}
return OPAL_SUCCESS;
}
return OPAL_SUCCESS;
}
opal_call(OPAL_IMC_COUNTERS_START, opal_imc_counters_start, 2);
/* opal_imc_counters_control_stop: This call stops the nest imc engine. */
static int64_t opal_imc_counters_stop(uint32_t type, uint64_t cpu_pir)
{
u64 op;
struct imc_chip_cb *cb;
struct cpu_thread *c = find_cpu_by_pir(cpu_pir);
int port_id, phys_core_id;
uint32_t htm_addr;
if (!c)
return OPAL_PARAMETER;
switch (type) {
case OPAL_IMC_COUNTERS_NEST:
/* Fetch the IMC control block structure */
cb = get_imc_cb(c->chip_id);
if (!cb)
return OPAL_HARDWARE;
/* Set the run command */
op = NEST_IMC_DISABLE;
if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS)
return OPAL_SUCCESS;
/* Write the command to the control block */
cb->imc_chip_command = cpu_to_be64(op);
return OPAL_SUCCESS;
case OPAL_IMC_COUNTERS_CORE:
case OPAL_IMC_COUNTERS_TRACE:
/*
* Core IMC hardware mandates setting of htm_mode in specific
* scom ports (port_id are in htm_scom_index[])
*/
phys_core_id = pir_to_core_id(c->pir);
port_id = phys_core_id % 4;
if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS)
return OPAL_SUCCESS;
htm_addr = get_imc_scom_addr_for_quad(phys_core_id,
htm_scom_index[port_id]);
/*
* Disables the core imc engine by clearing
* bits 4-9 of the HTM_MODE scom port.
*/
if (xscom_write(c->chip_id, htm_addr, (u64) CORE_IMC_HTM_MODE_DISABLE)) {
prerror("error in xscom_write for htm_mode\n");
return OPAL_HARDWARE;
}
return OPAL_SUCCESS;
}
return OPAL_SUCCESS;
}
opal_call(OPAL_IMC_COUNTERS_STOP, opal_imc_counters_stop, 2);