| // 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 char const *nest_pmus[] = { |
| "powerbus0", |
| "mcs0", |
| "mcs1", |
| "mcs2", |
| "mcs3", |
| "mcs4", |
| "mcs5", |
| "mcs6", |
| "mcs7", |
| "mba0", |
| "mba1", |
| "mba2", |
| "mba3", |
| "mba4", |
| "mba5", |
| "mba6", |
| "mba7", |
| "cen0", |
| "cen1", |
| "cen2", |
| "cen3", |
| "cen4", |
| "cen5", |
| "cen6", |
| "cen7", |
| "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 */ |
| }; |
| |
| /* |
| * 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; |
| 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; |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(nest_pmus); i++) { |
| if (!(PPC_BITMASK(i, i) & avl_vec)) { |
| /* Check if the device node exists */ |
| target = dt_find_by_name(dev, nest_pmus[i]); |
| if (!target) |
| continue; |
| /* Remove the device node */ |
| dt_free(target); |
| } |
| } |
| |
| /* |
| * 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) |
| 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) { |
| dev = dt_find_compatible_node(dt_root, NULL, |
| "ibm,opal-in-memory-counters"); |
| if (!dev) |
| return; |
| |
| goto imc_mambo; |
| } |
| |
| /* 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: |
| 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) |
| 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); |