blob: 0ede8ff627fbe9e42901780dc4ae4b8b5cd0f821 [file] [log] [blame]
/*
* Copyright (c) 2018-2025, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <arch.h>
#include <assert.h>
#include <debug.h>
#include <plat_topology.h>
#include <platform.h>
#include <stdlib.h>
#define CPU_INDEX_IS_VALID(_cpu_idx) \
(((_cpu_idx) - tftf_pwr_domain_start_idx[0]) < PLATFORM_CORE_COUNT)
#define IS_A_CPU_NODE(_cpu_idx) (tftf_pd_nodes[(_cpu_idx)].level == 0)
#define CPU_NODE_IS_VALID(_cpu_node) \
(CPU_INDEX_IS_VALID(_cpu_node) && IS_A_CPU_NODE(_cpu_node))
/*
* Global variable to check that the platform topology is not queried until it
* has been setup.
*/
static unsigned int topology_setup_done;
/*
* Store the start indices of power domains at various levels. This array makes it
* easier to traverse the topology tree if the power domain level is known.
*/
unsigned int tftf_pwr_domain_start_idx[PLATFORM_MAX_AFFLVL + 1];
/* The grand array to store the platform power domain topology */
tftf_pwr_domain_node_t tftf_pd_nodes[PLATFORM_NUM_AFFS];
#if DEBUG
/*
* Debug function to display the platform topology.
* Does not print absent affinity instances.
*/
static void dump_topology(void)
{
unsigned int cluster_idx, cpu_idx, count;
NOTICE("Platform topology:\n");
NOTICE(" %u cluster(s)\n", tftf_get_total_clusters_count());
NOTICE(" %u CPU(s) (total)\n\n", tftf_get_total_cpus_count());
for (cluster_idx = PWR_DOMAIN_INIT;
cluster_idx = tftf_get_next_peer_domain(cluster_idx, 1),
cluster_idx != PWR_DOMAIN_INIT;) {
count = 0;
for (cpu_idx = tftf_pd_nodes[cluster_idx].cpu_start_node;
cpu_idx < (tftf_pd_nodes[cluster_idx].cpu_start_node +
tftf_pd_nodes[cluster_idx].ncpus);
cpu_idx++) {
if (tftf_pd_nodes[cpu_idx].is_present)
count++;
}
NOTICE(" Cluster #%u [%u CPUs]\n",
cluster_idx - tftf_pwr_domain_start_idx[1],
count);
for (cpu_idx = PWR_DOMAIN_INIT;
cpu_idx = tftf_get_next_cpu_in_pwr_domain(cluster_idx, cpu_idx),
cpu_idx != PWR_DOMAIN_INIT;) {
NOTICE(" CPU #%u [MPID: 0x%x]\n",
cpu_idx - tftf_pwr_domain_start_idx[0],
tftf_get_mpidr_from_node(cpu_idx));
}
}
NOTICE("\n");
}
#endif
unsigned int tftf_get_total_aff_count(unsigned int aff_lvl)
{
unsigned int count = 0;
unsigned int node_idx;
assert(topology_setup_done == 1);
if (aff_lvl > PLATFORM_MAX_AFFLVL)
return count;
node_idx = tftf_pwr_domain_start_idx[aff_lvl];
while ((node_idx < PLATFORM_NUM_AFFS) &&
(tftf_pd_nodes[node_idx].level == aff_lvl)) {
if (tftf_pd_nodes[node_idx].is_present)
count++;
node_idx++;
}
return count;
}
unsigned int tftf_get_next_peer_domain(unsigned int pwr_domain_idx,
unsigned int pwr_lvl)
{
assert(topology_setup_done == 1);
assert(pwr_lvl <= PLATFORM_MAX_AFFLVL);
if (pwr_domain_idx == PWR_DOMAIN_INIT) {
pwr_domain_idx = tftf_pwr_domain_start_idx[pwr_lvl];
if (tftf_pd_nodes[pwr_domain_idx].is_present)
return pwr_domain_idx;
}
assert(pwr_domain_idx < PLATFORM_NUM_AFFS &&
tftf_pd_nodes[pwr_domain_idx].level == pwr_lvl);
for (++pwr_domain_idx; (pwr_domain_idx < PLATFORM_NUM_AFFS)
&& (tftf_pd_nodes[pwr_domain_idx].level == pwr_lvl);
pwr_domain_idx++) {
if (tftf_pd_nodes[pwr_domain_idx].is_present)
return pwr_domain_idx;
}
return PWR_DOMAIN_INIT;
}
unsigned int tftf_get_next_cpu_in_pwr_domain(unsigned int pwr_domain_idx,
unsigned int cpu_node)
{
unsigned int cpu_end_node;
assert(topology_setup_done == 1);
assert(pwr_domain_idx != PWR_DOMAIN_INIT
&& pwr_domain_idx < PLATFORM_NUM_AFFS);
if (cpu_node == PWR_DOMAIN_INIT) {
cpu_node = tftf_pd_nodes[pwr_domain_idx].cpu_start_node;
if (tftf_pd_nodes[cpu_node].is_present)
return cpu_node;
}
assert(CPU_NODE_IS_VALID(cpu_node));
cpu_end_node = tftf_pd_nodes[pwr_domain_idx].cpu_start_node
+ tftf_pd_nodes[pwr_domain_idx].ncpus - 1;
assert(cpu_end_node < PLATFORM_NUM_AFFS);
for (++cpu_node; cpu_node <= cpu_end_node; cpu_node++) {
if (tftf_pd_nodes[cpu_node].is_present)
return cpu_node;
}
return PWR_DOMAIN_INIT;
}
/*
* Helper function to get the parent nodes of a particular CPU power
* domain.
*/
static void get_parent_pwr_domain_nodes(unsigned int cpu_node,
unsigned int end_lvl,
unsigned int node_index[])
{
unsigned int parent_node = tftf_pd_nodes[cpu_node].parent_node;
unsigned int i;
for (i = 1; i <= end_lvl; i++) {
node_index[i - 1] = parent_node;
parent_node = tftf_pd_nodes[parent_node].parent_node;
}
}
/*******************************************************************************
* This function updates cpu_start_node and ncpus field for each of the nodes
* in tftf_pd_nodes[]. It does so by comparing the parent nodes of each of
* the CPUs and check whether they match with the parent of the previous
* CPU. The basic assumption for this work is that children of the same parent
* are allocated adjacent indices. The platform should ensure this through
* proper mapping of the CPUs to indices via platform_get_core_pos() API.
*
* It also updates the 'is_present' field for non-cpu power domains. It does
* this by checking the 'is_present' field of the child cpu nodes and updates
* it if any of the child cpu nodes are present.
*******************************************************************************/
static void update_pwrlvl_limits(void)
{
int cpu_id, j, is_present;
unsigned int nodes_idx[PLATFORM_MAX_AFFLVL];
unsigned int temp_index[PLATFORM_MAX_AFFLVL];
unsigned int cpu_node_offset = tftf_pwr_domain_start_idx[0];
for (j = 0; j < PLATFORM_MAX_AFFLVL; j++)
nodes_idx[j] = -1;
for (cpu_id = 0; cpu_id < PLATFORM_CORE_COUNT; cpu_id++) {
get_parent_pwr_domain_nodes(cpu_id + cpu_node_offset,
PLATFORM_MAX_AFFLVL,
temp_index);
is_present = tftf_pd_nodes[cpu_id + cpu_node_offset].is_present;
for (j = PLATFORM_MAX_AFFLVL - 1; j >= 0; j--) {
if (temp_index[j] != nodes_idx[j]) {
nodes_idx[j] = temp_index[j];
tftf_pd_nodes[nodes_idx[j]].cpu_start_node
= cpu_id + cpu_node_offset;
if (!tftf_pd_nodes[nodes_idx[j]].is_present)
tftf_pd_nodes[nodes_idx[j]].is_present = is_present;
}
tftf_pd_nodes[nodes_idx[j]].ncpus++;
}
}
}
/******************************************************************************
* This function populates the power domain topology array 'tftf_pd_nodes[]'
* based on the power domain description retrieved from the platform layer.
* It also updates the start index of each power domain level in
* tftf_pwr_domain_start_idx[]. The uninitialized fields of 'tftf_pd_nodes[]'
* for the non CPU power domain will be initialized in update_pwrlvl_limits().
*****************************************************************************/
static void populate_power_domain_tree(void)
{
unsigned int i, j = 0, num_nodes_at_lvl = 1, num_nodes_at_next_lvl,
node_index = 0, parent_idx = 0, num_children;
int num_level = PLATFORM_MAX_AFFLVL;
const unsigned char *plat_array;
plat_array = tftf_plat_get_pwr_domain_tree_desc();
/*
* For each level the inputs are:
* - number of nodes at this level in plat_array i.e. num_nodes_at_lvl
* This is the sum of values of nodes at the parent level.
* - Index of first entry at this level in the plat_array i.e.
* parent_idx.
* - Index of first free entry in tftf_pd_nodes[].
*/
while (num_level >= 0) {
num_nodes_at_next_lvl = 0;
/* Store the start index for every level */
tftf_pwr_domain_start_idx[num_level] = node_index;
/*
* For each entry (parent node) at this level in the plat_array:
* - Find the number of children
* - Allocate a node in a power domain array for each child
* - Set the parent of the child to the parent_node_index - 1
* - Increment parent_node_index to point to the next parent
* - Accumulate the number of children at next level.
*/
for (i = 0; i < num_nodes_at_lvl; i++) {
assert(parent_idx <=
PLATFORM_NUM_AFFS - PLATFORM_CORE_COUNT);
num_children = plat_array[parent_idx];
for (j = node_index;
j < node_index + num_children; j++) {
/* Initialize the power domain node */
tftf_pd_nodes[j].parent_node = parent_idx - 1;
tftf_pd_nodes[j].level = num_level;
/* Additional initializations for CPU power domains */
if (num_level == 0) {
/* Calculate the cpu id from node index */
int cpu_id = j - tftf_pwr_domain_start_idx[0];
assert(cpu_id < PLATFORM_CORE_COUNT);
/* Set the mpidr of cpu node */
tftf_pd_nodes[j].mpidr =
tftf_plat_get_mpidr(cpu_id);
if (tftf_pd_nodes[j].mpidr != INVALID_MPID)
tftf_pd_nodes[j].is_present = 1;
tftf_pd_nodes[j].cpu_start_node = j;
tftf_pd_nodes[j].ncpus = 1;
}
}
node_index = j;
num_nodes_at_next_lvl += num_children;
parent_idx++;
}
num_nodes_at_lvl = num_nodes_at_next_lvl;
num_level--;
}
/* Validate the sanity of array exported by the platform */
assert(j == PLATFORM_NUM_AFFS);
}
void tftf_init_topology(void)
{
populate_power_domain_tree();
update_pwrlvl_limits();
topology_setup_done = 1;
#if DEBUG
dump_topology();
#endif
}
unsigned int tftf_topology_next_cpu(unsigned int cpu_node)
{
assert(topology_setup_done == 1);
if (cpu_node == PWR_DOMAIN_INIT) {
cpu_node = tftf_pwr_domain_start_idx[0];
if (tftf_pd_nodes[cpu_node].is_present)
return cpu_node;
}
assert(CPU_NODE_IS_VALID(cpu_node));
for (++cpu_node; cpu_node < PLATFORM_NUM_AFFS; cpu_node++) {
if (tftf_pd_nodes[cpu_node].is_present)
return cpu_node;
}
return PWR_DOMAIN_INIT;
}
unsigned int tftf_get_parent_node_from_mpidr(unsigned int mpidr, unsigned int pwrlvl)
{
unsigned int core_pos = platform_get_core_pos(mpidr);
unsigned int node, i;
if (core_pos >= PLATFORM_CORE_COUNT)
return PWR_DOMAIN_INIT;
if (pwrlvl > PLAT_MAX_PWR_LEVEL)
return PWR_DOMAIN_INIT;
node = tftf_pwr_domain_start_idx[0] + core_pos;
for (i = 1; i <= pwrlvl; i++)
node = tftf_pd_nodes[node].parent_node;
return node;
}
unsigned int tftf_get_mpidr_from_node(unsigned int cpu_node)
{
assert(topology_setup_done == 1);
assert(CPU_NODE_IS_VALID(cpu_node));
if (tftf_pd_nodes[cpu_node].is_present)
return tftf_pd_nodes[cpu_node].mpidr;
return INVALID_MPID;
}
unsigned int tftf_find_any_cpu_other_than(unsigned exclude_mpid)
{
unsigned int cpu_node, mpidr;
for_each_cpu(cpu_node) {
mpidr = tftf_get_mpidr_from_node(cpu_node);
if (mpidr != exclude_mpid)
return mpidr;
}
return INVALID_MPID;
}
unsigned int tftf_find_any_cpu_in_other_cluster(unsigned exclude_mpid)
{
unsigned int cpu_node, cluster_node, mpidr, exclude_cluster_node;
exclude_cluster_node = tftf_get_parent_node_from_mpidr(exclude_mpid,
MPIDR_AFFLVL1);
for_each_cpu(cpu_node) {
mpidr = tftf_get_mpidr_from_node(cpu_node);
cluster_node = tftf_get_parent_node_from_mpidr(mpidr,
MPIDR_AFFLVL1);
if (cluster_node != exclude_cluster_node)
return mpidr;
}
return INVALID_MPID;
}
unsigned int tftf_find_random_cpu_other_than(unsigned int exclude_mpid)
{
#if (PLATFORM_CORE_COUNT == 1)
return INVALID_MPID;
#else
unsigned int cpu_node, mpidr;
unsigned int possible_cpus_cnt = 0;
unsigned int possible_cpus[PLATFORM_CORE_COUNT];
for_each_cpu(cpu_node) {
mpidr = tftf_get_mpidr_from_node(cpu_node);
if (mpidr != exclude_mpid)
possible_cpus[possible_cpus_cnt++] = mpidr;
}
if (possible_cpus_cnt == 0)
return INVALID_MPID;
return possible_cpus[rand() % possible_cpus_cnt];
#endif
}