Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+135
View File
@@ -0,0 +1,135 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* BPF extensible scheduler class: Documentation/scheduler/sched-ext.rst
*
* scx_arena_pool: kernel-side sub-allocator over BPF-arena pages.
*
* Each chunk added to @sch->arena_pool comes from one
* bpf_arena_alloc_pages_sleepable() call and is registered at the
* kernel-side mapping address. Callers translate to the BPF-arena form
* themselves if needed.
*
* Allocations grow the pool on demand. Underlying arena pages are released
* when the arena map itself is torn down.
*
* Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
* Copyright (c) 2026 Tejun Heo <tj@kernel.org>
*/
#include <linux/genalloc.h>
#include "internal.h"
#include "arena.h"
enum scx_arena_consts {
SCX_ARENA_MIN_ORDER = 3, /* 8-byte minimum sub-allocation */
SCX_ARENA_GROW_PAGES = 4, /* per growth */
};
s32 scx_arena_pool_init(struct scx_sched *sch)
{
if (!sch->arena_map)
return 0;
sch->arena_pool = gen_pool_create(SCX_ARENA_MIN_ORDER, NUMA_NO_NODE);
if (!sch->arena_pool)
return -ENOMEM;
return 0;
}
static void scx_arena_clear_chunk(struct gen_pool *pool, struct gen_pool_chunk *chunk,
void *data)
{
int order = pool->min_alloc_order;
size_t chunk_sz = chunk->end_addr - chunk->start_addr + 1;
unsigned long end_bit = chunk_sz >> order;
unsigned long b, e;
for_each_set_bitrange(b, e, chunk->bits, end_bit)
gen_pool_free(pool, chunk->start_addr + (b << order),
(e - b) << order);
}
/*
* Tear down the pool. Outstanding gen_pool allocations are freed via
* scx_arena_clear_chunk() so gen_pool_destroy() doesn't BUG. The underlying
* arena pages are released when the arena map itself is torn down.
*/
void scx_arena_pool_destroy(struct scx_sched *sch)
{
if (!sch->arena_pool)
return;
gen_pool_for_each_chunk(sch->arena_pool, scx_arena_clear_chunk, NULL);
gen_pool_destroy(sch->arena_pool);
sch->arena_pool = NULL;
}
/*
* Grow the pool by @page_cnt pages. bpf_arena_alloc_pages_sleepable() and
* gen_pool_add() (which calls vzalloc(GFP_KERNEL)) require a sleepable
* context.
*/
static int scx_arena_grow(struct scx_sched *sch, u32 page_cnt)
{
u64 kern_vm_start;
u32 uaddr32;
void *p;
int ret;
if (!sch->arena_map || !sch->arena_pool)
return -EINVAL;
p = bpf_arena_alloc_pages_sleepable(sch->arena_map, NULL,
page_cnt, NUMA_NO_NODE, 0);
if (!p)
return -ENOMEM;
uaddr32 = (u32)(unsigned long)p;
/* arena.o, which defines these, is built only on MMU && 64BIT */
#if defined(CONFIG_MMU) && defined(CONFIG_64BIT)
kern_vm_start = bpf_arena_map_kern_vm_start(sch->arena_map);
#else
kern_vm_start = 0;
#endif
ret = gen_pool_add(sch->arena_pool, kern_vm_start + uaddr32,
page_cnt * PAGE_SIZE, NUMA_NO_NODE);
if (ret) {
bpf_arena_free_pages_non_sleepable(sch->arena_map, p, page_cnt);
return ret;
}
return 0;
}
/*
* Allocate @size bytes from the arena pool. Returns kernel VA on success, NULL
* on failure. May grow the pool via scx_arena_grow() which sleeps. Caller must
* be in a GFP_KERNEL context.
*/
void *scx_arena_alloc(struct scx_sched *sch, size_t size)
{
unsigned long kern_va;
u32 page_cnt;
might_sleep();
if (!sch->arena_pool)
return NULL;
while (true) {
kern_va = gen_pool_alloc(sch->arena_pool, size);
if (kern_va)
break;
page_cnt = max_t(u32, SCX_ARENA_GROW_PAGES,
(size + PAGE_SIZE - 1) >> PAGE_SHIFT);
if (scx_arena_grow(sch, page_cnt))
return NULL;
}
return (void *)kern_va;
}
void scx_arena_free(struct scx_sched *sch, void *kern_va, size_t size)
{
if (sch->arena_pool && kern_va)
gen_pool_free(sch->arena_pool, (unsigned long)kern_va, size);
}
+20
View File
@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* BPF extensible scheduler class: Documentation/scheduler/sched-ext.rst
*
* Copyright (c) 2025 Meta Platforms, Inc. and affiliates.
* Copyright (c) 2025 Tejun Heo <tj@kernel.org>
*/
#ifndef _KERNEL_SCHED_EXT_ARENA_H
#define _KERNEL_SCHED_EXT_ARENA_H
#include <linux/types.h>
struct scx_sched;
s32 scx_arena_pool_init(struct scx_sched *sch);
void scx_arena_pool_destroy(struct scx_sched *sch);
void *scx_arena_alloc(struct scx_sched *sch, size_t size);
void scx_arena_free(struct scx_sched *sch, void *kern_va, size_t size);
#endif /* _KERNEL_SCHED_EXT_ARENA_H */
+710
View File
@@ -0,0 +1,710 @@
/* 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.
*
* Pointers are published once on first enable and never revoked. The default
* mapping is populated before ops.init() runs; scx_bpf_cid_override() commits
* before it returns. As long as the BPF scheduler only uses the tables from
* those points onward, it sees a consistent view.
*/
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, \
}
/*
* 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;
}
/* Allocate the cid tables once on first enable; never freed. */
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;
}
/**
* scx_cid_init - build the cid mapping
* @sch: the scx_sched being initialized; used as the scx_error() target
*
* See "Topological CPU IDs" in cid.h for the model. Walk online cpus by
* intersection at each level (parent_scratch & this_level_mask), which keeps
* containment correct by construction and naturally splits a physical LLC
* straddling two NUMA nodes into two LLC units. The caller must hold
* cpus_read_lock.
*/
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;
/* CMASK_MAX_WORDS in cid.bpf.h covers NR_CPUS up to 8192 */
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;
/* -1 sentinels for sparse-possible cpu id holes (0 is a valid cid) */
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;
/*
* No NUMA info: skip and let the tail loop assign a no-topo
* cid. cpumask_of_node(-1) is undefined.
*/
if (nid < 0) {
cpumask_clear_cpu(next_cpu, to_walk);
continue;
}
node_idx = next_node_idx++;
/* node_scratch = to_walk & this node */
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++;
/* llc_scratch = node_scratch & this llc */
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;
/* core_scratch = llc_scratch & this core */
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);
}
}
}
}
/*
* No-topo section: any possible cpu without a cid - normally just the
* not-online ones. Collect any currently-online cpus that land here in
* @online_no_topo so we can warn about them at the end.
*/
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;
}
/**
* scx_cmask_clear - Zero every bit in @m's active range
* @m: cmask to clear
*
* Storage past the active range is left as is.
*/
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));
}
/**
* scx_cmask_fill - Set every bit in @m's active range
* @m: cmask to fill
*
* Counterpart to scx_cmask_clear(). Storage past the active range is left as is.
*/
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));
/* clear word-0 bits below base */
head_bits = m->base & 63;
if (head_bits)
m->bits[0] &= ~((1ULL << head_bits) - 1);
/* clear last-word bits at or past base + nr_cids */
tail_bits = (m->base + m->nr_cids) & 63;
if (tail_bits)
m->bits[nr_words - 1] &= (1ULL << tail_bits) - 1;
}
/**
* scx_cpumask_to_cmask - Translate a kernel cpumask into a cmask
* @src: source cpumask
* @dst: cmask to write
*
* Clear @dst's active range and set the bit for each cid whose cpu is in
* @src and lies within that range. Out-of-range cids are silently ignored.
*/
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();
/**
* scx_bpf_cid_override - Install an explicit cpu->cid mapping
* @cpu_to_cid: array of nr_cpu_ids s32 entries (cid for each cpu)
* @cpu_to_cid__sz: must be nr_cpu_ids * sizeof(s32) bytes
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
*
* May only be called from ops.init() of the root scheduler. Replace the
* topology-probed cid mapping with the caller-provided one. Each possible cpu
* must map to a unique cid in [0, num_possible_cpus()). Topo info is cleared.
* On invalid input, trigger scx_error() to abort the scheduler.
*/
__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;
/* GFP_KERNEL alloc must happen before the rcu read section */
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;
}
/* Invalidate stale topo info - the override carries no topology. */
for (cid = 0; cid < num_possible_cpus(); cid++)
scx_cid_topo[cid] = SCX_CID_TOPO_NEG;
}
/**
* scx_bpf_cid_to_cpu - Return the raw CPU id for @cid
* @cid: cid to look up
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
*
* Return the raw CPU id for @cid. Trigger scx_error() and return -EINVAL if
* @cid is invalid. The cid<->cpu mapping is static for the lifetime of the
* loaded scheduler, so the BPF side can cache the result to avoid repeated
* kfunc invocations.
*/
__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);
}
/**
* scx_bpf_cpu_to_cid - Return the cid for @cpu
* @cpu: cpu to look up
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
*
* Return the cid for @cpu. Trigger scx_error() and return -EINVAL if @cpu is
* invalid. The cid<->cpu mapping is static for the lifetime of the loaded
* scheduler, so the BPF side can cache the result to avoid repeated kfunc
* invocations.
*/
__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);
}
/*
* Set ops on cmasks. cmask_walk_op2() shares one walk across mutating
* (and/or/copy/andnot) and predicate (subset/intersects) two-cmask forms;
* cmask_walk_op1() does the same shape over a single cmask range. Every public
* entry passes a compile-time-constant @op; cmask_walk_op{1,2}() and
* cmask_word_op{1,2}() are __always_inline so the inner switch collapses to the
* selected op and cmask_op2_is_pred() folds the predicate early-exit out of
* mutating ops.
*
* Two-cmask ops only touch @dst bits inside the intersection of the two ranges;
* bits outside stay untouched. In particular, scx_cmask_copy() does NOT zero
* @dst bits that lie outside @src's range.
*
* The _RACY variants are otherwise identical to their non-racy counterpart but
* read @src word-by-word via data_race(). Memory ordering with concurrent
* writers is the caller's responsibility.
*/
enum cmask_op2 {
/* mutating */
CMASK_OP2_AND,
CMASK_OP2_OR,
CMASK_OP2_OR_RACY,
CMASK_OP2_COPY,
CMASK_OP2_COPY_RACY,
CMASK_OP2_ANDNOT,
/* predicates - short-circuit when the per-word result is true */
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:
/* stop on the first bit in @sub not set in @super */
return (*bp & ~*av) & mask;
case CMASK_OP2_INTERSECTS:
return (*av & *bp) & mask;
}
unreachable();
}
/*
* Walk the intersection of [@a_base, @a_base + @a_nr_cids) with [@b_base,
* @b_base + @b_nr_cids) word by word, applying @op. Mutating ops walk all words
* and return false; predicates return true on the first word whose per-word
* test is true. Empty intersection returns false (matches "no bits to consider"
* for both mutate and predicate).
*
* Base/nr_cids are taken as parameters so callers with snapshotted bounds can
* drive the walk with values independent of the cmask's header.
*/
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();
}
/*
* Walk [@a_base, @a_base + @a_nr_cids) of @a_bits word by word, applying @op.
* Returns true on the first word whose per-word test is true; returns false if
* no word matches or the range is empty. All current op1s short-circuit on
* per-word true; if a non-predicate op1 lands here, add a cmask_op1_is_pred()
* guard analogous to cmask_op2_is_pred().
*/
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);
}
/**
* scx_cmask_or_racy - OR @src into @dst, reading @src without locking
*
* @src is read word-by-word through data_race(). Same per-bit independence
* rationale as scx_cmask_copy_racy(). Memory ordering with writers is the
* caller's responsibility.
*/
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);
}
/**
* scx_cmask_copy_racy - Snapshot @src into @dst without locking
*
* @src is read word-by-word through data_race(). Head/tail masking matches
* scx_cmask_copy(). Each bit in a cmask is independent, so partial updates
* just leave some bits fresher than others. Memory ordering with writers is
* the caller's responsibility.
*/
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);
}
/*
* Return true if @cm has any bit set in [@lo, @hi). Caller must ensure
* [@lo, @hi) is contained in @cm's range.
*/
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);
}
/**
* scx_cmask_subset - test whether @sub is a subset of @super
* @sub: cmask to test
* @super: cmask to test against
*
* Return true iff every set bit of @sub is also set in @super.
*/
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;
/*
* Set bits in @sub outside @super's range can't be in @super, so any
* such bit means not a subset. The walk below only visits words
* common to both ranges, so these need a separate scan.
*/
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);
}
/**
* scx_cmask_empty - Test whether @m has no bits set
* @m: cmask to test
*
* Return true iff @m's active range has no bits set.
*/
bool scx_cmask_empty(const struct scx_cmask *m)
{
return !cmask_any_set_in_range(m, m->base, m->base + m->nr_cids);
}
/**
* scx_bpf_cid_topo - Copy out per-cid topology info
* @cid: cid to look up
* @out__uninit: where to copy the topology info; fully written by this call
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
*
* Fill @out__uninit with the topology info for @cid. Trigger scx_error() if
* @cid is out of range. If @cid is valid but in the no-topo section, all fields
* are set to -1.
*/
__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);
}
+294
View File
@@ -0,0 +1,294 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Topological CPU IDs (cids)
* --------------------------
*
* Raw cpu numbers are clumsy for sharding work and communication across
* topology units, especially from BPF: the space can be sparse, numerical
* closeness doesn't imply topological closeness (x86 hyperthreading often puts
* SMT siblings far apart), and a range of cpu ids doesn't mean anything.
* Sub-scheds make this acute - cpu allocation, revocation and other state are
* constantly communicated across sub-scheds, and passing whole cpumasks scales
* poorly with cpu count. cpumasks are also awkward in BPF: a variable-length
* kernel type sized for the maximum NR_CPUS (4k), with verbose helper sequences
* for every op.
*
* cids give every cpu a dense, topology-ordered id. CPUs sharing a core, LLC or
* NUMA node get contiguous cid ranges, so a topology unit becomes a (start,
* length) slice of cid space. Communication can pass a slice instead of a
* cpumask, and BPF code can process, for example, a u64 word's worth of cids at
* a time.
*
* The mapping is built once at root scheduler enable time by walking the
* topology of online cpus only. Going by online cpus is out of necessity:
* depending on the arch, topology info isn't reliably available for offline
* cpus. The expected usage model is restarting the scheduler on hotplug events
* so the mapping is rebuilt against the new online set. A scheduler that wants
* to handle hotplug without a restart can provide its own cid and shard mapping
* through the override interface.
*
* Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
* Copyright (c) 2026 Tejun Heo <tj@kernel.org>
*/
#ifndef _KERNEL_SCHED_EXT_CID_H
#define _KERNEL_SCHED_EXT_CID_H
#include "internal.h"
struct scx_sched;
/*
* Cid space (total is always num_possible_cpus()) is laid out with
* topology-annotated cids first, then no-topo cids at the tail. The
* topology-annotated block covers the cpus that were online when scx_cid_init()
* ran and remains valid even after those cpus go offline. The tail block covers
* possible-but-not-online cpus and carries all-(-1) topo info (see
* scx_cid_topo); callers detect it via the -1 sentinels.
*
* See the comment above the table definitions in cid.c for the
* memory-ordering and visibility contract.
*/
extern s16 *scx_cid_to_cpu_tbl;
extern s16 *scx_cpu_to_cid_tbl;
extern struct scx_cid_topo *scx_cid_topo;
extern struct btf_id_set8 scx_kfunc_ids_init;
void scx_cmask_clear(struct scx_cmask *m);
void scx_cmask_fill(struct scx_cmask *m);
void scx_cmask_and(struct scx_cmask *dst, const struct scx_cmask *src);
void scx_cmask_or(struct scx_cmask *dst, const struct scx_cmask *src);
void scx_cmask_or_racy(struct scx_cmask *dst, const struct scx_cmask *src);
void scx_cmask_copy(struct scx_cmask *dst, const struct scx_cmask *src);
void scx_cmask_copy_racy(struct scx_cmask *dst, const struct scx_cmask *src);
void scx_cmask_andnot(struct scx_cmask *dst, const struct scx_cmask *src);
bool scx_cmask_subset(const struct scx_cmask *sub, const struct scx_cmask *super);
bool scx_cmask_intersects(const struct scx_cmask *a, const struct scx_cmask *b);
bool scx_cmask_empty(const struct scx_cmask *m);
s32 scx_cid_init(struct scx_sched *sch);
int scx_cid_kfunc_init(void);
void scx_cpumask_to_cmask(const struct cpumask *src, struct scx_cmask *dst);
/**
* cid_valid - Verify a cid value, to be used on ops input args
* @sch: scx_sched to abort on error
* @cid: cid which came from a BPF ops
*
* Return true if @cid is in [0, num_possible_cpus()). On failure, trigger
* scx_error() and return false.
*/
static inline bool cid_valid(struct scx_sched *sch, s32 cid)
{
if (likely(cid >= 0 && cid < num_possible_cpus()))
return true;
scx_error(sch, "invalid cid %d", cid);
return false;
}
/**
* __scx_cid_to_cpu - Unchecked cid->cpu table lookup
* @cid: cid to look up. Must be in [0, num_possible_cpus()).
*
* Intended for callsites that have already validated @cid and that hold a
* non-NULL @sch from scx_prog_sched() - a live sched implies the table has
* been allocated, so no NULL check is needed here.
*/
static inline s32 __scx_cid_to_cpu(s32 cid)
{
/* READ_ONCE pairs with WRITE_ONCE in scx_cid_arrays_alloc() */
return READ_ONCE(scx_cid_to_cpu_tbl)[cid];
}
/**
* __scx_cpu_to_cid - Unchecked cpu->cid table lookup
* @cpu: cpu to look up. Must be a valid possible cpu id.
*
* Same usage constraints as __scx_cid_to_cpu().
*/
static inline s32 __scx_cpu_to_cid(s32 cpu)
{
return READ_ONCE(scx_cpu_to_cid_tbl)[cpu];
}
/**
* scx_cid_to_cpu - Translate @cid to its cpu
* @sch: scx_sched for error reporting
* @cid: cid to look up
*
* Return the cpu for @cid or a negative errno on failure. Invalid cid triggers
* scx_error() on @sch. The cid arrays are allocated on first scheduler enable
* and never freed, so the returned cpu is stable for the lifetime of the loaded
* scheduler.
*/
static inline s32 scx_cid_to_cpu(struct scx_sched *sch, s32 cid)
{
if (!cid_valid(sch, cid))
return -EINVAL;
return __scx_cid_to_cpu(cid);
}
/**
* scx_cpu_to_cid - Translate @cpu to its cid
* @sch: scx_sched for error reporting
* @cpu: cpu to look up
*
* Return the cid for @cpu or a negative errno on failure. Invalid cpu triggers
* scx_error() on @sch. Same lifetime guarantee as scx_cid_to_cpu().
*/
static inline s32 scx_cpu_to_cid(struct scx_sched *sch, s32 cpu)
{
if (!scx_cpu_valid(sch, cpu, NULL))
return -EINVAL;
return __scx_cpu_to_cid(cpu);
}
/**
* scx_is_cid_type - Test whether the active scheduler hierarchy is cid-form
*/
static inline bool scx_is_cid_type(void)
{
return static_branch_unlikely(&__scx_is_cid_type);
}
static inline bool __scx_cmask_contains(u32 cid, const struct scx_cmask *m)
{
return likely(cid >= m->base && cid < m->base + m->nr_cids);
}
/* Word in bits[] covering @cid. @cid must satisfy __scx_cmask_contains(). */
static inline u64 *__scx_cmask_word(u32 cid, const struct scx_cmask *m)
{
return (u64 *)&m->bits[cid / 64 - m->base / 64];
}
/**
* __scx_cmask_init - Initialize @m with explicit storage capacity
* @m: cmask to initialize
* @base: first cid of the active range
* @nr_cids: number of cids in the active range
* @alloc_cids: storage capacity in cids, at least @nr_cids
*
* Use when storage is sized larger than the initial active range. All of
* bits[] is zeroed.
*/
static inline void __scx_cmask_init(struct scx_cmask *m, u32 base, u32 nr_cids,
u32 alloc_cids)
{
if (WARN_ON_ONCE(alloc_cids < nr_cids))
nr_cids = alloc_cids;
m->base = base;
m->nr_cids = nr_cids;
m->alloc_words = SCX_CMASK_NR_WORDS(alloc_cids);
memset(m->bits, 0, m->alloc_words * sizeof(u64));
}
/**
* scx_cmask_init - Initialize @m on tight storage
* @m: cmask to initialize
* @base: first cid of the active range
* @nr_cids: number of cids in the active range
*
* All of bits[] is zeroed.
*/
static inline void scx_cmask_init(struct scx_cmask *m, u32 base, u32 nr_cids)
{
__scx_cmask_init(m, base, nr_cids, nr_cids);
}
/**
* scx_cmask_reframe - Reshape @m's active range without resizing storage
* @m: cmask to reframe
* @base: new active range base
* @nr_cids: new active range length, must fit within @m->alloc_words
*
* Body bits within the new range become garbage - only the head and tail
* words are zeroed to keep the padding invariant.
*/
static inline void scx_cmask_reframe(struct scx_cmask *m, u32 base, u32 nr_cids)
{
if (WARN_ON_ONCE(SCX_CMASK_NR_WORDS(nr_cids) > m->alloc_words))
return;
if (nr_cids) {
u32 last_word = ((base & 63) + nr_cids - 1) / 64;
m->bits[0] = 0;
m->bits[last_word] = 0;
}
m->base = base;
m->nr_cids = nr_cids;
}
static inline void __scx_cmask_set(u32 cid, struct scx_cmask *m)
{
if (!__scx_cmask_contains(cid, m))
return;
*__scx_cmask_word(cid, m) |= BIT_U64(cid & 63);
}
/**
* scx_cmask_test - test whether @cid is set in @m
* @cid: cid to test
* @m: cmask to test
*
* Return %false if @cid is outside @m's active range. Otherwise return the
* bit's value. Read via READ_ONCE so callers can race set/clear writers.
*/
static inline bool scx_cmask_test(u32 cid, const struct scx_cmask *m)
{
if (!__scx_cmask_contains(cid, m))
return false;
return READ_ONCE(*__scx_cmask_word(cid, m)) & BIT_U64(cid & 63);
}
/*
* Words of bits[] the active range spans, 0 if empty. Tighter than the storage
* SCX_CMASK_NR_WORDS() sizes for the worst-case base alignment.
*/
static inline u32 scx_cmask_nr_used_words(const struct scx_cmask *m)
{
if (!m->nr_cids)
return 0;
return ((m->base & 63) + m->nr_cids - 1) / 64 + 1;
}
/**
* scx_cmask_for_each_cid - iterate set cids in @m
* @cid: s32 loop var that receives each set cid in turn
* @m: cmask to iterate
*
* Visits set bits within @m's active range in ascending order. Scans only the
* words the active range spans, where head and tail padding is kept zero, so
* no per-cid range check is needed.
*/
#define scx_cmask_for_each_cid(cid, m) \
for (u64 __bs = (m)->base & ~63u, __wi = 0, \
__nw = scx_cmask_nr_used_words(m); \
__wi < __nw; __wi++) \
for (u64 __w = READ_ONCE((m)->bits[__wi]); \
__w && ((cid) = __bs + __wi * 64 + __ffs64(__w), true); \
__w &= __w - 1)
/*
* scx_cpu_arg() wraps a cpu arg being handed to an SCX op. For cid-form
* schedulers it resolves to the matching cid; for cpu-form it passes @cpu
* through. scx_cpu_ret() is the inverse for a cpu/cid returned from an op
* (currently only ops.select_cpu); it validates the BPF-supplied cid and
* triggers scx_error() on @sch if invalid.
*/
static inline s32 scx_cpu_arg(s32 cpu)
{
if (scx_is_cid_type())
return __scx_cpu_to_cid(cpu);
return cpu;
}
static inline s32 scx_cpu_ret(struct scx_sched *sch, s32 cpu_or_cid)
{
if (cpu_or_cid < 0 || !scx_is_cid_type())
return cpu_or_cid;
return scx_cid_to_cpu(sch, cpu_or_cid);
}
#endif /* _KERNEL_SCHED_EXT_CID_H */
File diff suppressed because it is too large Load Diff
+95
View File
@@ -0,0 +1,95 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* BPF extensible scheduler class: Documentation/scheduler/sched-ext.rst
*
* Copyright (c) 2022 Meta Platforms, Inc. and affiliates.
* Copyright (c) 2022 Tejun Heo <tj@kernel.org>
* Copyright (c) 2022 David Vernet <dvernet@meta.com>
*/
#ifdef CONFIG_SCHED_CLASS_EXT
void scx_tick(struct rq *rq);
void init_scx_entity(struct sched_ext_entity *scx);
void scx_pre_fork(struct task_struct *p);
int scx_fork(struct task_struct *p, struct kernel_clone_args *kargs);
void scx_post_fork(struct task_struct *p);
void scx_cancel_fork(struct task_struct *p);
bool scx_can_stop_tick(struct rq *rq);
void scx_rq_activate(struct rq *rq);
void scx_rq_deactivate(struct rq *rq);
int scx_check_setscheduler(struct task_struct *p, int policy);
bool task_should_scx(int policy);
bool scx_allow_ttwu_queue(const struct task_struct *p);
void init_sched_ext_class(void);
static inline u32 scx_cpuperf_target(s32 cpu)
{
if (scx_enabled())
return cpu_rq(cpu)->scx.cpuperf_target;
else
return 0;
}
static inline bool task_on_scx(const struct task_struct *p)
{
return scx_enabled() && p->sched_class == &ext_sched_class;
}
#ifdef CONFIG_SCHED_CORE
bool scx_prio_less(const struct task_struct *a, const struct task_struct *b,
bool in_fi);
#endif
#else /* CONFIG_SCHED_CLASS_EXT */
static inline void scx_tick(struct rq *rq) {}
static inline void scx_pre_fork(struct task_struct *p) {}
static inline int scx_fork(struct task_struct *p, struct kernel_clone_args *kargs) { return 0; }
static inline void scx_post_fork(struct task_struct *p) {}
static inline void scx_cancel_fork(struct task_struct *p) {}
static inline u32 scx_cpuperf_target(s32 cpu) { return 0; }
static inline bool scx_can_stop_tick(struct rq *rq) { return true; }
static inline void scx_rq_activate(struct rq *rq) {}
static inline void scx_rq_deactivate(struct rq *rq) {}
static inline int scx_check_setscheduler(struct task_struct *p, int policy) { return 0; }
static inline bool task_on_scx(const struct task_struct *p) { return false; }
static inline bool scx_allow_ttwu_queue(const struct task_struct *p) { return true; }
static inline void init_sched_ext_class(void) {}
#endif /* CONFIG_SCHED_CLASS_EXT */
#ifdef CONFIG_SCHED_CLASS_EXT
void __scx_update_idle(struct rq *rq, bool idle, bool do_notify);
static inline void scx_update_idle(struct rq *rq, bool idle, bool do_notify)
{
if (scx_enabled())
__scx_update_idle(rq, idle, do_notify);
}
#else
static inline void scx_update_idle(struct rq *rq, bool idle, bool do_notify) {}
#endif
#ifdef CONFIG_CGROUP_SCHED
#ifdef CONFIG_EXT_GROUP_SCHED
void scx_tg_init(struct task_group *tg);
int scx_tg_online(struct task_group *tg);
void scx_tg_offline(struct task_group *tg);
int scx_cgroup_can_attach(struct cgroup_taskset *tset);
void scx_cgroup_move_task(struct task_struct *p);
void scx_cgroup_cancel_attach(struct cgroup_taskset *tset);
void scx_group_set_weight(struct task_group *tg, unsigned long cgrp_weight);
void scx_group_set_idle(struct task_group *tg, bool idle);
void scx_group_set_bandwidth(struct task_group *tg, u64 period_us, u64 quota_us, u64 burst_us);
#else /* CONFIG_EXT_GROUP_SCHED */
static inline void scx_tg_init(struct task_group *tg) {}
static inline int scx_tg_online(struct task_group *tg) { return 0; }
static inline void scx_tg_offline(struct task_group *tg) {}
static inline int scx_cgroup_can_attach(struct cgroup_taskset *tset) { return 0; }
static inline void scx_cgroup_move_task(struct task_struct *p) {}
static inline void scx_cgroup_cancel_attach(struct cgroup_taskset *tset) {}
static inline void scx_group_set_weight(struct task_group *tg, unsigned long cgrp_weight) {}
static inline void scx_group_set_idle(struct task_group *tg, bool idle) {}
static inline void scx_group_set_bandwidth(struct task_group *tg, u64 period_us, u64 quota_us, u64 burst_us) {}
#endif /* CONFIG_EXT_GROUP_SCHED */
#endif /* CONFIG_CGROUP_SCHED */
File diff suppressed because it is too large Load Diff
+31
View File
@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* BPF extensible scheduler class: Documentation/scheduler/sched-ext.rst
*
* Copyright (c) 2022 Meta Platforms, Inc. and affiliates.
* Copyright (c) 2022 Tejun Heo <tj@kernel.org>
* Copyright (c) 2022 David Vernet <dvernet@meta.com>
* Copyright (c) 2024 Andrea Righi <arighi@nvidia.com>
*/
#ifndef _KERNEL_SCHED_EXT_IDLE_H
#define _KERNEL_SCHED_EXT_IDLE_H
#include <linux/btf_ids.h>
struct cpumask;
struct sched_ext_ops;
struct task_struct;
extern struct btf_id_set8 scx_kfunc_ids_idle;
extern struct btf_id_set8 scx_kfunc_ids_select_cpu;
void scx_idle_update_selcpu_topology(struct sched_ext_ops *ops);
void scx_idle_init_masks(void);
s32 scx_select_cpu_dfl(struct task_struct *p, s32 prev_cpu, u64 wake_flags,
const struct cpumask *cpus_allowed, u64 flags);
void scx_idle_enable(struct sched_ext_ops *ops);
void scx_idle_disable(void);
int scx_idle_init(void);
#endif /* _KERNEL_SCHED_EXT_IDLE_H */
File diff suppressed because it is too large Load Diff
+150
View File
@@ -0,0 +1,150 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Early sched_ext type definitions.
*
* Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
* Copyright (c) 2026 Tejun Heo <tj@kernel.org>
*/
#ifndef _KERNEL_SCHED_EXT_TYPES_H
#define _KERNEL_SCHED_EXT_TYPES_H
#include <linux/types.h>
#include <linux/jiffies.h>
#include <linux/overflow.h>
#include <linux/time64.h>
#include <linux/sched/topology.h>
enum scx_consts {
SCX_DSP_DFL_MAX_BATCH = 32,
SCX_DSP_MAX_LOOPS = 32,
SCX_WATCHDOG_MAX_TIMEOUT = 30 * HZ,
/* per-CPU chunk size for p->scx.tid allocation, see scx_alloc_tid() */
SCX_TID_CHUNK = 1024,
SCX_EXIT_BT_LEN = 64,
SCX_EXIT_MSG_LEN = 1024,
SCX_EXIT_DUMP_DFL_LEN = 32768,
SCX_CPUPERF_ONE = SCHED_CAPACITY_SCALE,
/*
* Iterating all tasks may take a while. Periodically drop
* scx_tasks_lock to avoid causing e.g. CSD and RCU stalls.
*/
SCX_TASK_ITER_BATCH = 32,
SCX_BYPASS_HOST_NTH = 2,
SCX_BYPASS_LB_DFL_INTV_US = 500 * USEC_PER_MSEC,
SCX_BYPASS_LB_DONOR_PCT = 125,
SCX_BYPASS_LB_MIN_DELTA_DIV = 4,
SCX_BYPASS_LB_BATCH = 256,
SCX_REENQ_LOCAL_MAX_REPEAT = 256,
SCX_SUB_MAX_DEPTH = 4,
};
/*
* Per-cid topology info. For each topology level (core, LLC, node), records
* the first cid in the unit and its global index. Global indices are
* consecutive integers assigned in cid-walk order, so e.g. core_idx ranges
* over [0, nr_cores_at_init) with no gaps. No-topo cids have all fields set
* to -1.
*
* @core_cid: first cid of this cid's core (smt-sibling group)
* @core_idx: global index of that core, in [0, nr_cores_at_init)
* @llc_cid: first cid of this cid's LLC
* @llc_idx: global index of that LLC, in [0, nr_llcs_at_init)
* @node_cid: first cid of this cid's NUMA node
* @node_idx: global index of that node, in [0, nr_nodes_at_init)
*/
struct scx_cid_topo {
s32 core_cid;
s32 core_idx;
s32 llc_cid;
s32 llc_idx;
s32 node_cid;
s32 node_idx;
};
/*
* cmask: variable-length, base-windowed bitmap over cid space
* -----------------------------------------------------------
*
* A cmask covers the cid range [base, base + nr_cids). bits[] is aligned to the
* global 64-cid grid: bits[0] spans [base & ~63, (base & ~63) + 64), so the
* first (base & 63) bits of bits[0] are head padding and the trailing bits of
* the last active word past base + nr_cids are tail padding. Both stay zero;
* all mutating helpers preserve that. Words past the last active word are not
* read by any helper and have no constraint.
*
* Grid alignment means two cmasks always address bits[] against the same global
* 64-cid windows, so cross-cmask word ops (AND, OR, ...) reduce to
*
* dst->bits[i] OP= src->bits[i - delta]
*
* with no bit-shifting, regardless of how the two bases relate mod 64.
*/
struct scx_cmask {
u32 base;
u32 nr_cids;
u32 alloc_words;
u64 bits[] __counted_by(alloc_words);
};
/*
* Number of u64 words of bits[] storage that covers @nr_cids regardless of base
* alignment. The +1 absorbs up to 63 bits of head padding when base is not
* 64-aligned - always allocating one extra word beats branching on base or
* splitting the compute. The u64 cast keeps the +63 from wrapping when @nr_cids
* is near U32_MAX, so callers bounds-checking the result against @alloc_words
* catch the overflow instead of seeing a small value.
*/
#define SCX_CMASK_NR_WORDS(nr_cids) ((u32)(((u64)(nr_cids) + 63) / 64 + 1))
/**
* __SCX_CMASK_DEFINE - Define an on-stack cmask with explicit storage capacity
* @NAME: variable name to define
* @BASE: first cid of the active range
* @NR_CIDS: active range length
* @ALLOC_CIDS: storage capacity in cids, at least @NR_CIDS
*
* @NAME aliases zero-initialized storage with the active range set to
* [BASE, BASE + NR_CIDS). Use scx_cmask_reframe() to reshape later, up to
* @ALLOC_CIDS.
*/
#define __SCX_CMASK_DEFINE(NAME, BASE, NR_CIDS, ALLOC_CIDS) \
_DEFINE_FLEX(struct scx_cmask, NAME, bits, SCX_CMASK_NR_WORDS(ALLOC_CIDS), \
= { .base = (BASE), \
.nr_cids = (NR_CIDS), \
.alloc_words = SCX_CMASK_NR_WORDS(ALLOC_CIDS) })
/**
* SCX_CMASK_DEFINE - Define an on-stack cmask on tight storage
* @NAME: variable name to define
* @BASE: first cid of the active range
* @NR_CIDS: active range length, also storage capacity
*
* @NAME aliases zero-initialized storage with the active range and storage
* both [BASE, BASE + NR_CIDS).
*/
#define SCX_CMASK_DEFINE(NAME, BASE, NR_CIDS) \
__SCX_CMASK_DEFINE(NAME, BASE, NR_CIDS, NR_CIDS)
/**
* SCX_CMASK_DEFINE_SHARD - Define an on-stack cmask sized to one shard
* @NAME: variable name to define
* @BASE: first cid of the active range
* @NR_CIDS: active range length, must be <= SCX_CID_SHARD_MAX_CPUS
*
* Storage is fixed at SCX_CID_SHARD_MAX_CPUS, active range framed by
* (BASE, NR_CIDS). Passing NR_CIDS > SCX_CID_SHARD_MAX_CPUS leaves the
* cmask claiming more bits than storage holds and subsequent cmask
* operations will overrun.
*/
#define SCX_CMASK_DEFINE_SHARD(NAME, BASE, NR_CIDS) \
__SCX_CMASK_DEFINE(NAME, BASE, NR_CIDS, SCX_CID_SHARD_MAX_CPUS)
#endif /* _KERNEL_SCHED_EXT_TYPES_H */