/* SPDX-License-Identifier: GPL-2.0 */
/*
* BPF extensible scheduler class: Documentation/scheduler/sched-ext.rst
*
* Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
* Copyright (c) 2026 Tejun Heo <tj@kernel.org>
*/
#include <linux/cacheinfo.h>
#include "internal.h"
#include "cid.h"
/*
* cid tables. The cid kfuncs are available whether the root scheduler is
* cid-form or cpu-form, the latter to allow gradual migration to cids, so every
* root builds a default mapping. Each root enable allocates a fresh set, builds
* it privately and publishes the __rcu globals below once the layout is final.
* Root disable unpublishes and RCU-frees the set. kfuncs may run before the
* tables are published and must check for NULL.
*/
u32 scx_nr_cid_shards;
s16 __rcu *scx_cid_to_cpu_tbl;
s16 __rcu *scx_cpu_to_cid_tbl;
s32 __rcu *scx_cid_to_shard;
s32 __rcu *scx_shard_node;
struct scx_cid_shard __rcu *scx_cid_shard_ranges;
struct scx_cid_topo __rcu *scx_cid_topo;
static struct scx_cid_tables *scx_cid_tables; /* used only during alloc/free */
#define SCX_CID_TOPO_NEG (struct scx_cid_topo) { \
.core_cid = -1, .core_idx = -1, .llc_cid = -1, .llc_idx = -1, \
.node_cid = -1, .node_idx = -1, .shard_cid = -1, .shard_idx = -1, \
}
/*
* Return @cpu's LLC shared_cpu_map. If cacheinfo isn't populated (offline or
* !present), record @cpu in @fallbacks and return its node mask instead - the
* worst that can happen is that the cpu's LLC becomes coarser than reality.
*/
static const struct cpumask *cpu_llc_mask(int cpu, struct cpumask *fallbacks)
{
struct cpu_cacheinfo *ci = get_cpu_cacheinfo(cpu);
if (!ci || !ci->info_list || !ci->num_leaves) {
cpumask_set_cpu(cpu, fallbacks);
return cpumask_of_node(cpu_to_node(cpu));
}
return &ci->info_list[ci->num_leaves - 1].shared_cpu_map;
}
/*
* Compute per-LLC shard layout. Each shard holds at most @shard_size cids, and
* in any case no more than SCX_CID_SHARD_MAX_CPUS. Cores are spread as evenly
* as possible across shards so cpu count is balanced: the first *@nr_large_p
* shards get (*@cores_per_shard_p + 1) cores, the rest get *@cores_per_shard_p.
*/
static void calc_shard_layout(const struct cpumask *llc_cpus, u32 shard_size,
u32 *cores_per_shard_p, u32 *nr_large_p)
{
u32 nr_cores = 0, nr_cpus = 0, nr_shards;
int cpu;
for_each_cpu(cpu, llc_cpus) {
nr_cpus++;
if (cpumask_first(topology_sibling_cpumask(cpu)) == cpu)
nr_cores++;
}
nr_shards = max_t(u32, 1, DIV_ROUND_UP(nr_cpus, shard_size));
nr_shards = max_t(u32, nr_shards,
DIV_ROUND_UP(nr_cpus, SCX_CID_SHARD_MAX_CPUS));
*cores_per_shard_p = nr_cores / nr_shards;
*nr_large_p = nr_cores % nr_shards;
}
static void scx_cid_tables_free(struct scx_cid_tables *tbls)
{
if (!tbls)
return;
kvfree(tbls->cid_to_cpu);
kvfree(tbls->cpu_to_cid);
kvfree(tbls->cid_to_shard);
kvfree(tbls->shard_node);
kvfree(tbls->shard_ranges);
kvfree(tbls->topo);
kfree(tbls);
}
static void scx_cid_tables_free_rcufn(struct rcu_head *rcu)
{
scx_cid_tables_free(container_of(rcu, struct scx_cid_tables, rcu));
}
static struct scx_cid_tables *scx_cid_alloc_tables(void)
{
u32 npossible = num_possible_cpus();
struct scx_cid_tables *tbls;
tbls = kzalloc_obj(*tbls);
if (!tbls)
return NULL;
tbls->cid_to_cpu = kvzalloc_objs(*tbls->cid_to_cpu, npossible);
tbls->cpu_to_cid = kvzalloc_objs(*tbls->cpu_to_cid, nr_cpu_ids);
tbls->c