#include <linux/cacheinfo.h>
#include "internal.h"
#include "cid.h"
s16 *scx_cid_to_cpu_tbl;
s16 *scx_cpu_to_cid_tbl;
struct scx_cid_topo *scx_cid_topo;
#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, \
}
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;
}
static s32 scx_cid_arrays_alloc(void)
{
u32 npossible = num_possible_cpus();
s16 *cid_to_cpu, *cpu_to_cid;
struct scx_cid_topo *cid_topo;
if (scx_cid_to_cpu_tbl)
return 0;
cid_to_cpu = kzalloc_objs(*scx_cid_to_cpu_tbl, npossible, GFP_KERNEL);
cpu_to_cid = kzalloc_objs(*scx_cpu_to_cid_tbl, nr_cpu_ids, GFP_KERNEL);
cid_topo = kmalloc_objs(*scx_cid_topo, npossible, GFP_KERNEL);
if (!cid_to_cpu || !cpu_to_cid || !cid_topo) {
kfree(cid_to_cpu);
kfree(cpu_to_cid);
kfree(cid_topo);
return -ENOMEM;
}
WRITE_ONCE(scx_cid_to_cpu_tbl, cid_to_cpu);
WRITE_ONCE(scx_cpu_to_cid_tbl, cpu_to_cid);
WRITE_ONCE(scx_cid_topo, cid_topo);
return 0;
}
s32 scx_cid_init(struct scx_sched *sch)
{
cpumask_var_t to_walk __free(free_cpumask_var) = CPUMASK_VAR_NULL;
cpumask_var_t node_scratch __free(free_cpumask_var) = CPUMASK_VAR_NULL;
cpumask_var_t llc_scratch __free(free_cpumask_var) = CPUMASK_VAR_NULL;
cpumask_var_t core_scratch __free(free_cpumask_var) = CPUMASK_VAR_NULL;
cpumask_var_t llc_fallback __free(free_cpumask_var) = CPUMASK_VAR_NULL;
cpumask_var_t online_no_topo __free(free_cpumask_var) = CPUMASK_VAR_NULL;
u32 next_cid = 0;
s32 next_node_idx = 0, next_llc_idx = 0, next_core_idx = 0;
s32 cpu, ret;
BUILD_BUG_ON(NR_CPUS > 8192);
lockdep_assert_cpus_held();
ret = scx_cid_arrays_alloc();
if (ret)
return ret;
if (!zalloc_cpumask_var(&to_walk, GFP_KERNEL) ||
!zalloc_cpumask_var(&node_scratch, GFP_KERNEL) ||
!zalloc_cpumask_var(&llc_scratch, GFP_KERNEL) ||
!zalloc_cpumask_var(&core_scratch, GFP_KERNEL) ||
!zalloc_cpumask_var(&llc_fallback, GFP_KERNEL) ||
!zalloc_cpumask_var(&online_no_topo, GFP_KERNEL))
return -ENOMEM;
for (cpu = 0; cpu < nr_cpu_ids; cpu++)
scx_cpu_to_cid_tbl[cpu] = -1;
cpumask_copy(to_walk, cpu_online_mask);
while (!cpumask_empty(to_walk)) {
s32 next_cpu = cpumask_first(to_walk);
s32 nid = cpu_to_node(next_cpu);
s32 node_cid = next_cid;
s32 node_idx;
if (nid < 0) {
cpumask_clear_cpu(next_cpu, to_walk);
continue;
}
node_idx = next_node_idx++;
cpumask_and(node_scratch, to_walk, cpumask_of_node(nid));
if (WARN_ON_ONCE(!cpumask_test_cpu(next_cpu, node_scratch)))
return -EINVAL;
while (!cpumask_empty(node_scratch)) {
s32 ncpu = cpumask_first(node_scratch);
const struct cpumask *llc_mask = cpu_llc_mask(ncpu, llc_fallback);
s32 llc_cid = next_cid;
s32 llc_idx = next_llc_idx++;
cpumask_and(llc_scratch, node_scratch, llc_mask);
if (WARN_ON_ONCE(!cpumask_test_cpu(ncpu, llc_scratch)))
return -EINVAL;
while (!cpumask_empty(llc_scratch)) {
s32 lcpu = cpumask_first(llc_scratch);
const struct cpumask *sib = topology_sibling_cpumask(lcpu);
s32 core_cid = next_cid;
s32 core_idx = next_core_idx++;
s32 ccpu;
cpumask_and(core_scratch, llc_scratch, sib);
if (WARN_ON_ONCE(!cpumask_test_cpu(lcpu, core_scratch)))
return -EINVAL;
for_each_cpu(ccpu, core_scratch) {
s32 cid = next_cid++;
scx_cid_to_cpu_tbl[cid] = ccpu;
scx_cpu_to_cid_tbl[ccpu] = cid;
scx_cid_topo[cid] = (struct scx_cid_topo){
.core_cid = core_cid,
.core_idx = core_idx,
.llc_cid = llc_cid,
.llc_idx = llc_idx,
.node_cid = node_cid,
.node_idx = node_idx,
};
cpumask_clear_cpu(ccpu, llc_scratch);
cpumask_clear_cpu(ccpu, node_scratch);
cpumask_clear_cpu(ccpu, to_walk);
}
}
}
}
for_each_cpu(cpu, cpu_possible_mask) {
s32 cid;
if (__scx_cpu_to_cid(cpu) != -1)
continue;
if (cpu_online(cpu))
cpumask_set_cpu(cpu, online_no_topo);
cid = next_cid++;
scx_cid_to_cpu_tbl[cid] = cpu;
scx_cpu_to_cid_tbl[cpu] = cid;
scx_cid_topo[cid] = SCX_CID_TOPO_NEG;
}
if (!cpumask_empty(llc_fallback))
pr_warn("scx_cid: cpus without cacheinfo, using node mask as llc: %*pbl\n",
cpumask_pr_args(llc_fallback));
if (!cpumask_empty(online_no_topo))
pr_warn("scx_cid: online cpus with no usable topology: %*pbl\n",
cpumask_pr_args(online_no_topo));
return 0;
}
void scx_cmask_clear(struct scx_cmask *m)
{
u32 nr_words;
if (!m->nr_cids)
return;
nr_words = (m->base + m->nr_cids - 1) / 64 - m->base / 64 + 1;
memset(m->bits, 0, nr_words * sizeof(u64));
}
void scx_cmask_fill(struct scx_cmask *m)
{
u32 nr_words, head_bits, tail_bits;
if (!m->nr_cids)
return;
nr_words = (m->base + m->nr_cids - 1) / 64 - m->base / 64 + 1;
memset(m->bits, 0xff, nr_words * sizeof(u64));
head_bits = m->base & 63;
if (head_bits)
m->bits[0] &= ~((1ULL << head_bits) - 1);
tail_bits = (m->base + m->nr_cids) & 63;
if (tail_bits)
m->bits[nr_words - 1] &= (1ULL << tail_bits) - 1;
}
void scx_cpumask_to_cmask(const struct cpumask *src, struct scx_cmask *dst)
{
s32 cpu;
scx_cmask_clear(dst);
for_each_cpu(cpu, src) {
s32 cid = __scx_cpu_to_cid(cpu);
if (cid >= 0)
__scx_cmask_set(cid, dst);
}
}
__bpf_kfunc_start_defs();
__bpf_kfunc void scx_bpf_cid_override(const s32 *cpu_to_cid, u32 cpu_to_cid__sz,
const struct bpf_prog_aux *aux)
{
cpumask_var_t seen __free(free_cpumask_var) = CPUMASK_VAR_NULL;
struct scx_sched *sch;
bool alloced;
s32 cpu, cid;
alloced = zalloc_cpumask_var(&seen, GFP_KERNEL);
guard(rcu)();
sch = scx_prog_sched(aux);
if (unlikely(!sch))
return;
if (!alloced) {
scx_error(sch, "scx_bpf_cid_override: failed to allocate cpumask");
return;
}
if (scx_parent(sch)) {
scx_error(sch, "scx_bpf_cid_override() only allowed from root sched");
return;
}
if (cpu_to_cid__sz != nr_cpu_ids * sizeof(s32)) {
scx_error(sch, "scx_bpf_cid_override: expected %zu bytes, got %u",
nr_cpu_ids * sizeof(s32), cpu_to_cid__sz);
return;
}
for_each_possible_cpu(cpu) {
s32 c = cpu_to_cid[cpu];
if (!cid_valid(sch, c))
return;
if (cpumask_test_and_set_cpu(c, seen)) {
scx_error(sch, "cid %d assigned to multiple cpus", c);
return;
}
scx_cpu_to_cid_tbl[cpu] = c;
scx_cid_to_cpu_tbl[c] = cpu;
}
for (cid = 0; cid < num_possible_cpus(); cid++)
scx_cid_topo[cid] = SCX_CID_TOPO_NEG;
}
__bpf_kfunc s32 scx_bpf_cid_to_cpu(s32 cid, const struct bpf_prog_aux *aux)
{
struct scx_sched *sch;
guard(rcu)();
sch = scx_prog_sched(aux);
if (unlikely(!sch))
return -EINVAL;
return scx_cid_to_cpu(sch, cid);
}
__bpf_kfunc s32 scx_bpf_cpu_to_cid(s32 cpu, const struct bpf_prog_aux *aux)
{
struct scx_sched *sch;
guard(rcu)();
sch = scx_prog_sched(aux);
if (unlikely(!sch))
return -EINVAL;
return scx_cpu_to_cid(sch, cpu);
}
enum cmask_op2 {
CMASK_OP2_AND,
CMASK_OP2_OR,
CMASK_OP2_OR_RACY,
CMASK_OP2_COPY,
CMASK_OP2_COPY_RACY,
CMASK_OP2_ANDNOT,
CMASK_OP2_SUBSET,
CMASK_OP2_INTERSECTS,
};
static __always_inline bool cmask_op2_is_pred(const enum cmask_op2 op)
{
return op == CMASK_OP2_SUBSET || op == CMASK_OP2_INTERSECTS;
}
static __always_inline bool cmask_word_op2(u64 *av, const u64 *bp, u64 mask,
const enum cmask_op2 op)
{
switch (op) {
case CMASK_OP2_AND:
*av &= ~mask | *bp;
return false;
case CMASK_OP2_OR:
*av |= *bp & mask;
return false;
case CMASK_OP2_OR_RACY:
*av |= data_race(*bp) & mask;
return false;
case CMASK_OP2_COPY:
*av = (*av & ~mask) | (*bp & mask);
return false;
case CMASK_OP2_COPY_RACY:
*av = (*av & ~mask) | (data_race(*bp) & mask);
return false;
case CMASK_OP2_ANDNOT:
*av &= ~(*bp & mask);
return false;
case CMASK_OP2_SUBSET:
return (*bp & ~*av) & mask;
case CMASK_OP2_INTERSECTS:
return (*av & *bp) & mask;
}
unreachable();
}
static __always_inline bool cmask_walk_op2(u64 *a_bits, u32 a_base, u32 a_nr_cids,
const u64 *b_bits, u32 b_base, u32 b_nr_cids,
const enum cmask_op2 op)
{
u32 lo = max(a_base, b_base);
u32 hi = min(a_base + a_nr_cids, b_base + b_nr_cids);
u32 a_word_off = a_base / 64;
u32 b_word_off = b_base / 64;
u32 lo_word = lo / 64;
u32 hi_word = (hi - 1) / 64;
u64 head_mask = GENMASK_U64(63, lo & 63);
u64 tail_mask = GENMASK_U64((hi - 1) & 63, 0);
u32 w;
if (lo >= hi)
return false;
if (lo_word == hi_word)
return cmask_word_op2(&a_bits[lo_word - a_word_off],
&b_bits[lo_word - b_word_off],
head_mask & tail_mask, op);
if (cmask_word_op2(&a_bits[lo_word - a_word_off],
&b_bits[lo_word - b_word_off], head_mask, op) &&
cmask_op2_is_pred(op))
return true;
for (w = lo_word + 1; w < hi_word; w++)
if (cmask_word_op2(&a_bits[w - a_word_off],
&b_bits[w - b_word_off], ~0ULL, op) &&
cmask_op2_is_pred(op))
return true;
return cmask_word_op2(&a_bits[hi_word - a_word_off],
&b_bits[hi_word - b_word_off], tail_mask, op);
}
enum cmask_op1 {
CMASK_OP1_ANY_SET,
};
static __always_inline bool cmask_word_op1(const u64 *ap, u64 mask,
const enum cmask_op1 op)
{
switch (op) {
case CMASK_OP1_ANY_SET:
return *ap & mask;
}
unreachable();
}
static __always_inline bool cmask_walk_op1(const u64 *a_bits, u32 a_base,
u32 a_nr_cids,
const enum cmask_op1 op)
{
u32 lo = a_base;
u32 hi = a_base + a_nr_cids;
u32 a_word_off = a_base / 64;
u32 lo_word = lo / 64;
u32 hi_word = (hi - 1) / 64;
u64 head_mask = GENMASK_U64(63, lo & 63);
u64 tail_mask = GENMASK_U64((hi - 1) & 63, 0);
u32 w;
if (lo >= hi)
return false;
if (lo_word == hi_word)
return cmask_word_op1(&a_bits[lo_word - a_word_off],
head_mask & tail_mask, op);
if (cmask_word_op1(&a_bits[lo_word - a_word_off], head_mask, op))
return true;
for (w = lo_word + 1; w < hi_word; w++)
if (cmask_word_op1(&a_bits[w - a_word_off], ~0ULL, op))
return true;
return cmask_word_op1(&a_bits[hi_word - a_word_off], tail_mask, op);
}
void scx_cmask_and(struct scx_cmask *dst, const struct scx_cmask *src)
{
cmask_walk_op2(dst->bits, dst->base, dst->nr_cids,
src->bits, src->base, src->nr_cids, CMASK_OP2_AND);
}
void scx_cmask_or(struct scx_cmask *dst, const struct scx_cmask *src)
{
cmask_walk_op2(dst->bits, dst->base, dst->nr_cids,
src->bits, src->base, src->nr_cids, CMASK_OP2_OR);
}
void scx_cmask_or_racy(struct scx_cmask *dst, const struct scx_cmask *src)
{
cmask_walk_op2(dst->bits, dst->base, dst->nr_cids,
src->bits, src->base, src->nr_cids, CMASK_OP2_OR_RACY);
}
void scx_cmask_copy(struct scx_cmask *dst, const struct scx_cmask *src)
{
cmask_walk_op2(dst->bits, dst->base, dst->nr_cids,
src->bits, src->base, src->nr_cids, CMASK_OP2_COPY);
}
void scx_cmask_copy_racy(struct scx_cmask *dst, const struct scx_cmask *src)
{
cmask_walk_op2(dst->bits, dst->base, dst->nr_cids,
src->bits, src->base, src->nr_cids, CMASK_OP2_COPY_RACY);
}
void scx_cmask_andnot(struct scx_cmask *dst, const struct scx_cmask *src)
{
cmask_walk_op2(dst->bits, dst->base, dst->nr_cids,
src->bits, src->base, src->nr_cids, CMASK_OP2_ANDNOT);
}
static bool cmask_any_set_in_range(const struct scx_cmask *cm, u32 lo, u32 hi)
{
if (lo >= hi)
return false;
return cmask_walk_op1(&cm->bits[lo / 64 - cm->base / 64], lo, hi - lo,
CMASK_OP1_ANY_SET);
}
bool scx_cmask_subset(const struct scx_cmask *sub, const struct scx_cmask *super)
{
u32 super_end = super->base + super->nr_cids;
u32 sub_end = sub->base + sub->nr_cids;
if (sub->base < super->base &&
cmask_any_set_in_range(sub, sub->base, min(super->base, sub_end)))
return false;
if (sub_end > super_end &&
cmask_any_set_in_range(sub, max(sub->base, super_end), sub_end))
return false;
return !cmask_walk_op2((u64 *)super->bits, super->base, super->nr_cids,
sub->bits, sub->base, sub->nr_cids, CMASK_OP2_SUBSET);
}
bool scx_cmask_intersects(const struct scx_cmask *a, const struct scx_cmask *b)
{
return cmask_walk_op2((u64 *)a->bits, a->base, a->nr_cids,
b->bits, b->base, b->nr_cids, CMASK_OP2_INTERSECTS);
}
bool scx_cmask_empty(const struct scx_cmask *m)
{
return !cmask_any_set_in_range(m, m->base, m->base + m->nr_cids);
}
__bpf_kfunc void scx_bpf_cid_topo(s32 cid, struct scx_cid_topo *out__uninit,
const struct bpf_prog_aux *aux)
{
struct scx_sched *sch;
guard(rcu)();
sch = scx_prog_sched(aux);
if (unlikely(!sch) || !cid_valid(sch, cid)) {
*out__uninit = SCX_CID_TOPO_NEG;
return;
}
*out__uninit = READ_ONCE(scx_cid_topo)[cid];
}
__bpf_kfunc_end_defs();
BTF_KFUNCS_START(scx_kfunc_ids_init)
BTF_ID_FLAGS(func, scx_bpf_cid_override, KF_IMPLICIT_ARGS | KF_SLEEPABLE)
BTF_KFUNCS_END(scx_kfunc_ids_init)
static const struct btf_kfunc_id_set scx_kfunc_set_init = {
.owner = THIS_MODULE,
.set = &scx_kfunc_ids_init,
.filter = scx_kfunc_context_filter,
};
BTF_KFUNCS_START(scx_kfunc_ids_cid)
BTF_ID_FLAGS(func, scx_bpf_cid_to_cpu, KF_IMPLICIT_ARGS)
BTF_ID_FLAGS(func, scx_bpf_cpu_to_cid, KF_IMPLICIT_ARGS)
BTF_ID_FLAGS(func, scx_bpf_cid_topo, KF_IMPLICIT_ARGS)
BTF_KFUNCS_END(scx_kfunc_ids_cid)
static const struct btf_kfunc_id_set scx_kfunc_set_cid = {
.owner = THIS_MODULE,
.set = &scx_kfunc_ids_cid,
};
int scx_cid_kfunc_init(void)
{
return register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, &scx_kfunc_set_init) ?:
register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, &scx_kfunc_set_cid) ?:
register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &scx_kfunc_set_cid) ?:
register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, &scx_kfunc_set_cid);
}