From 360b26c296e91ac2a065fde0b8931be0d7c9074b Mon Sep 17 00:00:00 2001 From: Piotr Gorski Date: Mon, 17 Aug 2026 17:46:40 +0200 Subject: [PATCH 1/3] Revert POC Signed-off-by: Piotr Gorski --- include/linux/sched/topology.h | 46 - init/Kconfig | 13 - kernel/sched/ext/ext.c | 7 - kernel/sched/fair.c | 159 +-- kernel/sched/idle.c | 10 - kernel/sched/poc_selector.c | 2038 -------------------------------- kernel/sched/sched.h | 118 -- kernel/sched/topology.c | 3 - 8 files changed, 36 insertions(+), 2358 deletions(-) delete mode 100644 kernel/sched/poc_selector.c diff --git a/include/linux/sched/topology.h b/include/linux/sched/topology.h index 2d939fa46..b5d9d7c2b 100644 --- a/include/linux/sched/topology.h +++ b/include/linux/sched/topology.h @@ -86,52 +86,6 @@ struct sched_domain_shared { unsigned long util_avg; unsigned long capacity; #endif -#ifdef CONFIG_SCHED_POC_SELECTOR - /* - * POC Selector: per-LLC idle CPU tracking - */ - u64 poc_llc_members; /* bitmask of valid CPUs (relative to base) */ - int poc_cpu_base; /* smallest CPU ID in this LLC */ - u8 poc_affinity_shift; /* bit shift for cpumask alignment */ - bool poc_fast_eligible; /* true when LLC CPU count <= 64 */ - bool poc_cluster_valid; /* true when cluster mask is usable */ -#ifdef CONFIG_SCHED_SMT - u8 poc_smt_shift; /* bit distance between SMT siblings */ - u64 poc_primary_mask; /* bitmask of core representative CPUs */ -#endif - - /* - * Hot write path: idle state flag arrays (lock-free mode). - * Each array = exactly 1 cache line (64B). - * Writers: WRITE_ONCE (plain MOV, no LOCK prefix). - * Readers: snapshot to stack, then multiply-and-shift aggregation. - * Active only when sched_poc_atomic_bitmap=0. - */ - u8 poc_idle_cpus[64] ____cacheline_aligned; -#ifdef CONFIG_SCHED_SMT - u8 poc_idle_cores[64] ____cacheline_aligned; -#endif /* CONFIG_SCHED_SMT */ - - /* - * Hot read/write path: idle state bitmaps (bitmap mode, default). - * Readers: single atomic64_read (MOV on x86). - * Writers: atomic64_or / atomic64_andnot (LOCK'd on x86). - * Active only when sched_poc_atomic_bitmap=1. - */ - atomic64_t poc_idle_cpus_mask ____cacheline_aligned; -#ifdef CONFIG_SCHED_SMT - atomic64_t poc_idle_cores_mask ____cacheline_aligned; -#endif /* CONFIG_SCHED_SMT */ - - /* - * Read-only lookup tables (written once at init). - * Cacheline-aligned for exact prefetch targeting. - */ - u64 poc_cluster_mask[64] ____cacheline_aligned; -#ifdef CONFIG_SCHED_SMT - u64 poc_smt_mask[64] ____cacheline_aligned; -#endif /* CONFIG_SCHED_SMT */ -#endif /* CONFIG_SCHED_POC_SELECTOR */ }; struct sched_domain { diff --git a/init/Kconfig b/init/Kconfig index 2cd2ab2d7..8937400a3 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1496,19 +1496,6 @@ config SCHED_AUTOGROUP desktop applications. Task group autogeneration is currently based upon task session. -config SCHED_POC_SELECTOR - bool "Piece-Of-Cake Fast Idle CPU Selector" - depends on SMP - default y - help - Idle CPU selector using cached bitmasks inspired by the scx_cake BPF - scheduler. Reduces select_idle_cpu overhead by using bitmap scanning. - - This optimization does not affect scheduler fairness - it only - speeds up the process of finding an idle CPU for task wakeup. - - If unsure, say Y. - config RELAY bool "Kernel->user space relay support (formerly relayfs)" select IRQ_WORK diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index e0132294b..18183062f 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -6236,9 +6236,6 @@ static void scx_root_disable(struct scx_sched *sch) mutex_unlock(&scx_enable_mutex); WARN_ON_ONCE(scx_set_enable_state(SCX_DISABLED) != SCX_DISABLING); -#ifdef CONFIG_SCHED_POC_SELECTOR - poc_notify_scx(false); -#endif done: scx_bypass(sch, false); } @@ -7453,10 +7450,6 @@ static void scx_root_enable_workfn(struct kthread_work *work) } } -#ifdef CONFIG_SCHED_POC_SELECTOR - poc_notify_scx(true); -#endif - pr_info("sched_ext: BPF scheduler \"%s\" enabled%s\n", sch->ops.name, scx_switched_all() ? "" : " (partial)"); kobject_uevent(&sch->kobj, KOBJ_ADD); diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index c4d098bdb..d309809ec 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -50,7 +50,6 @@ #include #include #include -#include #include @@ -1240,7 +1239,7 @@ static bool update_deadline(struct cfs_rq *cfs_rq, struct sched_entity *se) #include "pelt.h" -static int select_idle_sibling(struct task_struct *p, int prev_cpu, int cpu, int sync); +static int select_idle_sibling(struct task_struct *p, int prev_cpu, int cpu); static unsigned long task_h_load(struct task_struct *p); static unsigned long capacity_of(int cpu); @@ -8538,20 +8537,6 @@ void __update_idle_core(struct rq *rq) rcu_read_unlock(); } -/* - * Check if the entire core (all SMT siblings) containing @cpu is idle. - */ -static inline bool is_idle_core(int cpu) -{ - int sibling; - - for_each_cpu(sibling, cpu_smt_mask(cpu)) { - if (!available_idle_cpu(sibling)) - return false; - } - return true; -} - /* * Scan the entire LLC domain for idle cores; this dynamically switches off if * there are no idle cores left in the system; tracked through @@ -8879,38 +8864,16 @@ static inline bool asym_fits_cpu(unsigned long util, return true; } -#ifdef CONFIG_SCHED_POC_SELECTOR -#include "poc_selector.c" -#endif /* * Try and locate an idle core/thread in the LLC cache domain. */ -static int select_idle_sibling(struct task_struct *p, int prev, int target, int sync) +static int select_idle_sibling(struct task_struct *p, int prev, int target) { bool has_idle_core = false; struct sched_domain *sd; unsigned long task_util, util_min, util_max; int i, recent_used_cpu, prev_aff = -1; - /* Check a recently used CPU as a potential idle candidate: */ - recent_used_cpu = p->recent_used_cpu; - p->recent_used_cpu = prev; - if (recent_used_cpu != prev && - recent_used_cpu != target && - cpus_share_cache(recent_used_cpu, target) && - choose_idle_cpu(recent_used_cpu, p) && - cpumask_test_cpu(recent_used_cpu, p->cpus_ptr)) { -#ifdef CONFIG_SCHED_POC_SELECTOR - if (!static_branch_likely(&poc_selector_active) || - static_branch_unlikely(&sched_poc_early_select)) -#endif - if ((unsigned int)recent_used_cpu < nr_cpumask_bits && - is_idle_core(recent_used_cpu)) - return recent_used_cpu; - } else { - recent_used_cpu = -1; - } - /* * On asymmetric system, update task utilization because we will check * that the task fits with CPU's capacity. @@ -8927,13 +8890,23 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target, int */ lockdep_assert_irqs_disabled(); -#ifdef CONFIG_SCHED_POC_SELECTOR - if (static_branch_likely(&poc_selector_active) && - static_branch_unlikely(&sched_poc_early_select) && - is_idle_core(target) && + if (choose_idle_cpu(target, p) && asym_fits_cpu(task_util, util_min, util_max, target)) return target; -#endif + + /* + * If the previous CPU is cache affine and idle, don't be stupid: + */ + if (prev != target && cpus_share_cache(prev, target) && + choose_idle_cpu(prev, p) && + asym_fits_cpu(task_util, util_min, util_max, prev)) { + + if (!static_branch_unlikely(&sched_cluster_active) || + cpus_share_resources(prev, target)) + return prev; + + prev_aff = prev; + } /* * Allow a per-cpu kthread to stack with the wakee if the @@ -8951,6 +8924,24 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target, int return prev; } + /* Check a recently used CPU as a potential idle candidate: */ + recent_used_cpu = p->recent_used_cpu; + p->recent_used_cpu = prev; + if (recent_used_cpu != prev && + recent_used_cpu != target && + cpus_share_cache(recent_used_cpu, target) && + choose_idle_cpu(recent_used_cpu, p) && + cpumask_test_cpu(recent_used_cpu, p->cpus_ptr) && + asym_fits_cpu(task_util, util_min, util_max, recent_used_cpu)) { + + if (!static_branch_unlikely(&sched_cluster_active) || + cpus_share_resources(recent_used_cpu, target)) + return recent_used_cpu; + + } else { + recent_used_cpu = -1; + } + /* * For asymmetric CPU capacity systems, our domain of interest is * sd_asym_cpucapacity rather than sd_llc. @@ -8975,74 +8966,6 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target, int if (!sd) return target; -#ifdef CONFIG_SCHED_POC_SELECTOR - { - struct sched_domain_shared *sd_share = - rcu_dereference(per_cpu(sd_llc_shared, target)); - if (static_branch_likely(&poc_selector_active) - && !sched_asym_cpucap_active() - && sd_share && likely(sd_share->poc_fast_eligible)) { - int poc_cpu = select_idle_cpu_poc(target, prev, - recent_used_cpu, sync, - sd_share, p->cpus_ptr); - if (poc_cpu >= 0) { - return poc_cpu; - } - /* - * POC returns -2 when the SIS_UTIL overload gate fires - * (smt_fallback=0 only). POC has already checked - * prev's SMT sibling (Level 4) and decided broader - * search is not worthwhile. CFS would reach the same - * conclusion, so skip select_idle_smt/select_idle_cpu. - * - * POC returns -1 for Level 0 saturation (no idle CPUs - * in bitmap), but CFS may still find sched_idle CPUs, - * so we must NOT skip CFS in that case. - */ - if (poc_cpu == -2) - goto give_up; - } else { - /* - * poc_selector_active is off — POC is either disabled - * by sysctl or suppressed while scx is running. - * If an scx scheduler called us, flip poc_selector_skip - * and schedule a workqueue item to re-enable POC with - * bitmap resync. - */ - poc_check_skip_fallback(); - } - } - poc_count(POC_FALLBACK); -#endif /* CONFIG_SCHED_POC_SELECTOR */ - - if ((unsigned int)recent_used_cpu < nr_cpumask_bits) { - if (choose_idle_cpu(recent_used_cpu, p)) { - if (is_idle_core(recent_used_cpu)) - return recent_used_cpu; - /* idle CPU but not idle core → preserve for give_up */ - } else { - recent_used_cpu = -1; /* not idle → discard */ - } - } - - if (sync && is_idle_core(target) && - asym_fits_cpu(task_util, util_min, util_max, target)) - return target; - - /* - * If the previous CPU is cache affine and idle, don't be stupid: - */ - if (prev != target && cpus_share_cache(prev, target) && - choose_idle_cpu(prev, p) && - asym_fits_cpu(task_util, util_min, util_max, prev)) { - - if (!static_branch_unlikely(&sched_cluster_active) || - cpus_share_resources(prev, target)) - return prev; - - prev_aff = prev; - } - if (sched_smt_active()) { has_idle_core = test_idle_cores(target); @@ -9057,9 +8980,6 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target, int if ((unsigned)i < nr_cpumask_bits) return i; -#ifdef CONFIG_SCHED_POC_SELECTOR -give_up: -#endif /* * For cluster machines which have lower sharing cache like L2 or * LLC Tag, we tend to find an idle CPU in the target's cluster @@ -9071,13 +8991,6 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target, int if ((unsigned int)recent_used_cpu < nr_cpumask_bits) return recent_used_cpu; -#ifdef CONFIG_SCHED_POC_SELECTOR - /* Last resort: avoid enqueuing behind RT/DL tasks on target */ - if (static_branch_likely(&poc_selector_active) && - rt_task(cpu_rq(target)->curr) && - prev != target && !rt_task(cpu_rq(prev)->curr)) - return prev; -#endif return target; } @@ -9760,7 +9673,7 @@ select_task_rq_fair(struct task_struct *p, int prev_cpu, int wake_flags) /* Fast path */ if (wake_flags & WF_TTWU) - return select_idle_sibling(p, prev_cpu, new_cpu, sync); + return select_idle_sibling(p, prev_cpu, new_cpu); return new_cpu; } diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c index d4d77f281..052435f4d 100644 --- a/kernel/sched/idle.c +++ b/kernel/sched/idle.c @@ -305,11 +305,6 @@ static void do_idle(void) __current_set_polling(); tick_nohz_idle_enter(); -#ifdef CONFIG_SCHED_POC_SELECTOR - /* POC Selector: mark CPU as idle */ - set_cpu_idle_state_poc(cpu, 1); -#endif /* CONFIG_SCHED_POC_SELECTOR */ - while (!need_resched()) { /* @@ -363,11 +358,6 @@ static void do_idle(void) arch_cpu_idle_exit(); } -#ifdef CONFIG_SCHED_POC_SELECTOR - /* POC Selector: mark CPU as busy */ - set_cpu_idle_state_poc(cpu, 0); -#endif /* CONFIG_SCHED_POC_SELECTOR */ - /* * Since we fell out of the loop above, we know TIF_NEED_RESCHED must * be set, propagate it into PREEMPT_NEED_RESCHED. diff --git a/kernel/sched/poc_selector.c b/kernel/sched/poc_selector.c deleted file mode 100644 index d0f1a4532..000000000 --- a/kernel/sched/poc_selector.c +++ /dev/null @@ -1,2038 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Piece-Of-Cake (POC) CPU Selector - * - * Fast idle CPU selector inspired by RitzDaCat's scx_cake scheduler - * "Piece of Cake" - making idle CPU search a piece of cake! - * - * Tracks idle state in per-LLC atomic64_t bitmaps with lock-free - * atomic64_read/or/andnot for O(1) idle CPU lookup. - * Supports up to 64 CPUs per LLC (single 64-bit word). - * Includes affinity-aware filtering via cpumask intersection. - * - * When the fast path is not eligible (LLC exceeds 64 CPUs), - * returns -1 to let CFS standard select_idle_cpu handle it. - * - * Copyright (C) 2026 Masahito Suzuki - * - * Acknowledgements: - * This work is heavily inspired by RitzDaCat's scx_cake scheduler. - * - * The select_idle_sibling() restructuring on which the POC fast - * path depends (is_idle_core() helper, @sync parameter, hoisting - * the recent_used_cpu check to the function top, switching the - * target fast-return to is_idle_core()) is adapted from: - * - * Andrea Righi, Mario Roy, and Eric Naim - * ("sched/fair: Prefer the previous cpu for wakeup") - * - * - Andrea Righi: is_idle_core() helper and idle-core - * target selection in select_idle_sibling() - * - Mario Roy: prev-cpu preference and recent_used_cpu - * hoisting with idle-core check - * - * Special thanks to the algorithm inventors whose research enabled - * the O(1) techniques used in this implementation: - * - * - Prashant Pandey, Michael A. Bender, Rob Johnson - * ("A Fast x86 Implementation of Select") - * - * - Daniel Lemire - * ("Fast Random Integer Generation in an Interval") - */ - -#ifdef CONFIG_SCHED_POC_SELECTOR - -/************************************************************** - * Version Information: - */ - -#define SCHED_POC_SELECTOR_AUTHOR "Masahito Suzuki" -#define SCHED_POC_SELECTOR_PROGNAME "Piece-Of-Cake (POC) CPU Selector" - -#define SCHED_POC_SELECTOR_VERSION "2.6.3" - -/************************************************************** - * Static keys: - */ - -/* - * Runtime control: poc_selector_active (static key) - * Derived from: sched_poc_selector && !poc_selector_skip - * - * sched_poc_selector: user-visible sysctl (kernel.sched_poc_selector), - * plain bool, default true. - * poc_selector_skip: set true while sched_ext is active to avoid - * idle-bitmap overhead in do_idle. - * poc_selector_active: the actual static key gating all POC hot paths. - * Enabled only when sched_poc_selector && !poc_selector_skip. - * On enable transition, poc_resync_idle_state() is called. - */ -DEFINE_STATIC_KEY_TRUE(poc_selector_active); -static bool sched_poc_selector = true; -static bool poc_selector_skip; - -/* - * SMT fallback control: sched_poc_smt_fallback - * (sysctl kernel.sched_poc_smt_fallback) - * - * When enabled, POC bails out to CFS when no idle - * cores exist (has_idle_cores == false). CFS then handles - * SMT sibling selection via select_idle_smt(prev) and - * nr_idle_scan-limited select_idle_cpu(). - * - * When disabled (default), POC handles SMT sibling selection - * itself, trying prev's SMT sibling for cache locality, then - * LLC-wide RR search for remaining idle CPUs. Level 5/6 is - * gated by nr_idle_scan (SIS_UTIL): when LLC utilization - * exceeds ~85%, broader SMT search is skipped. - */ -DEFINE_STATIC_KEY_FALSE(sched_poc_smt_fallback); - -/* - * SMT consecutive layout: sched_poc_smt_consecutive - * - * When true (default), SMT siblings occupy consecutive LLC-relative - * positions (e.g., CPU 0,1 / 2,3 / ...). The idle core mask is - * derived from the idle CPU mask via bit-parallel operations: - * core_mask = cpu_mask & (cpu_mask >> 1) & 0x5555555555555555ULL - * - * Disabled at boot if non-consecutive 2-way SMT or >2-way SMT - * is detected on any LLC. - */ -DEFINE_STATIC_KEY_TRUE(sched_poc_smt_consecutive); - -/* - * SMT uniform 2-way layout: sched_poc_smt_uniform - * - * When true (default), all cores in every LLC have uniform 2-way SMT - * with a constant stride between siblings. The idle core mask is - * derived at read time via: - * core_mask = cpu_mask & (cpu_mask >> poc_smt_shift) & poc_primary_mask - * - * This covers both consecutive (stride=1) and stride-N (e.g., Intel - * Xeon) layouts without write-path overhead. - * - * When false (>2-way SMT or non-uniform topology), falls back to - * write-time maintenance of poc_idle_cores_mask atomic64_t. - * - * Disabled at boot if any LLC contains non-2-way or non-uniform SMT. - */ -DEFINE_STATIC_KEY_TRUE(sched_poc_smt_uniform); - -/* - * Target CPU sticky: sched_poc_target_sticky - * (sysctl kernel.sched_poc_target_sticky) - * - * When enabled, if the target CPU is idle in the bitmap, return it - * immediately — regardless of whether its core is fully idle. - * This provides L1 cache affinity: the waking task reuses the CPU - * it ran on last, keeping warm TLB/L1/L2 state. - * - * Checked after Level 0 (saturation) and before core_mask derivation. - * Default: disabled. - */ -DEFINE_STATIC_KEY_FALSE(sched_poc_target_sticky); - -/* - * Early select: sched_poc_early_select - * (sysctl kernel.sched_poc_early_select) - * - * When enabled, select_idle_sibling performs idle-core checks - * for recent_used_cpu and target BEFORE entering POC search: - * - recent_used_cpu with fully idle core → return immediately - * (matches upstream CFS Gate 4 behavior) - * - target with fully idle core → return immediately - * (avoids POC overhead: RCU deref, bitmap read, mask ops) - * - * These two checks must be toggled together to preserve POC's - * internal priority order (Level 1r before 1t). Enabling only - * one would let the pre-POC path return a lower-priority result - * before POC can evaluate the higher-priority candidate. - * - * Default: enabled. - */ -DEFINE_STATIC_KEY_TRUE(sched_poc_early_select); - -/* - * Greedy search: sched_poc_greedy_search - * (sysctl kernel.sched_poc_greedy_search) - * - * When enabled, POC always attempts Level 5/6 (LLC-wide SMT sibling - * search) regardless of utilization, ignoring the SIS_UTIL overload - * gate (nr_idle_scan == 0). This may benefit latency-sensitive - * workloads that want to find any idle CPU at all costs. - * - * When disabled, POC skips Level 5/6 under overload, - * returning -2 to also skip CFS fallback search. - * - * Default: enabled. - */ -DEFINE_STATIC_KEY_TRUE(sched_poc_greedy_search); - -/* - * sched_poc_aligned: true when all LLCs have poc_cpu_base aligned to 64 - * - * When true, cpumask-to-POC conversion is a simple word load (zero shift). - * When false (e.g., Threadripper CCDs at CPU 8, 16, ...), bit shifting - * is needed to align cpumask bits with POC's LLC-relative positions. - * Defaults to true; disabled at boot if any LLC has non-aligned base. - */ -DEFINE_STATIC_KEY_TRUE(sched_poc_aligned); - -/* - * Packed priority search: sched_poc_packed - * - * When true (default), per-LLC CPU count is ≤ 32, enabling packed - * priority search. Cluster candidates (Level 2) and LLC-wide - * candidates (Level 3) are packed into a single 64-bit word: - * - * bits [31:0]: cluster idle candidates (high priority) - * bits [63:32]: all LLC idle candidates (low priority) - * - * A single TZCNT resolves both levels simultaneously. - * ror32-based rotation distributes selections across idle CPUs. - * - * When false (LLC > 32 CPUs), falls back to separate cluster - * search + PTSELECT-based RR. - * - * Disabled at boot if any LLC has > 32 CPUs. - */ -DEFINE_STATIC_KEY_TRUE(sched_poc_packed); - -/* - * Improved RR strategy: sched_poc_rr_improved - * (sysctl kernel.sched_poc_rr_improved) - * - * When enabled (default), idle CPU selection in poc_select_rr, - * poc_cluster_search, and the packed priority search uses an - * improved RR strategy combining two techniques: - * 1. total size case-split (1/2/>=3): direct / interleave / full - * 2. golden-ratio scrambling (Lemire fastrange) - * - * When disabled, the current strategy is used unchanged: - * - poc_select_rr: poc_rr_step[] table (perfect RR) - * - poc_cluster_search: ctz lowest-bit selection (no RR) - * - packed search: ror32(counter & 31) - * - * The current path is preserved as the A/B-testing baseline; - * once the improved path is validated, the legacy code will - * be removed in a follow-up. - */ -DEFINE_STATIC_KEY_TRUE(sched_poc_rr_improved); - -/* - * Lockless bitmap mode: sched_poc_lockless_bitmap - * (sysctl kernel.sched_poc_lockless_bitmap) - * - * When enabled, idle state is tracked in u8[64] flag arrays. - * Writers use plain WRITE_ONCE (no LOCK prefix); readers snapshot - * the 64-byte cache line to the stack, then use multiply-and-shift - * aggregation to assemble a u64 bitmask. - * - * When disabled (default), idle state is tracked in atomic64_t bitmaps. - * Readers use a single atomic64_read (MOV on x86); writers use - * atomic64_or / atomic64_andnot (LOCK'd on x86). - * - * Only one representation is maintained at a time (single-write). - * Switching via sysctl resyncs the newly-active representation - * before readers can observe it. - * - * Default: disabled. - */ -DEFINE_STATIC_KEY_FALSE(sched_poc_lockless_bitmap); - -/************************************************************** - * Debug counters (sysctl kernel.sched_poc_count): - * - * Per-CPU counters for each selection level hit. - * Guarded by static key — zero overhead when disabled (default). - * Aggregated across all CPUs and exposed via sysfs. - */ -enum poc_level { - POC_LV1S = 0, /* target CPU sticky (L1/TLB affinity) */ - POC_LV1T, /* target core idle */ - POC_LV1P, /* prev core idle */ - POC_LV1R, /* recent core idle */ - POC_LV2, /* idle core in L2 cluster */ - POC_LV3, /* idle core across LLC (RR) */ - POC_LV4S, /* sync + target CPU idle (no idle cores) */ - POC_LV4P, /* prev's SMT sibling (cache locality) */ - POC_LV4R, /* recent's SMT sibling (warm cache) */ - POC_LV4T, /* target's SMT sibling */ - POC_LV5, /* idle CPU in L2 cluster */ - POC_LV6, /* idle CPU across LLC (RR) */ - POC_FALLBACK, /* POC returned -1, CFS fallback */ - POC_NR_LEVELS -}; - -#define POC_SMT_LEVEL_OFFSET (POC_LV5 - POC_LV2) - -DEFINE_STATIC_KEY_FALSE(sched_poc_count_enabled); - -static DEFINE_PER_CPU(unsigned long[POC_NR_LEVELS], poc_debug_cnt); - -static __always_inline void poc_count(enum poc_level lv) -{ - if (static_branch_unlikely(&sched_poc_count_enabled)) - __this_cpu_inc(poc_debug_cnt[lv]); -} - -/************************************************************** - * Per-CPU round-robin counter and division-free mapping: - */ - -/* - * POC_HASH_MULT / POC_SCRAMBLE — Golden-ratio scrambling - * - * Multiplying a 32-bit counter by ⌊2^32 / φ⌋ = 0x9E3779B9 scatters - * consecutive values across the 32-bit output space with good - * avalanche properties (Knuth's multiplicative hash, TAOCP Vol. 3). - * The scrambled value feeds POC_FASTRANGE for uniform [0, range) - * mapping in the improved RR path, or is used directly with a bit - * shift to derive an uncorrelated rotation amount in packed search. - */ -#define POC_HASH_MULT 0x9E3779B9U /* golden ratio * 2^32 */ -#define POC_SCRAMBLE(counter) ((u32)(counter) * POC_HASH_MULT) - -/* - * Per-CPU round-robin counter for idle CPU selection. - * Each CPU starts at a different offset to reduce cross-CPU - * collision probability. Combined with poc_rr_step[] and - * POC_FIXED_MOD16, consecutive calls on the same CPU produce - * perfect round-robin: each call picks a different idle CPU - * until all candidates have been visited. - */ -static DEFINE_PER_CPU(u32, poc_rr_counter); - -/* - * Division-free modulo via 16-bit fixed-point reciprocal multiplication - * - * The multiply-and-shift technique is inspired by: - * D. Lemire, "Fast Random Integer Generation in an Interval", - * ACM Trans. Model. Comput. Simul. 29, 1, Article 3, 2019. - * - * Combined with poc_rr_step[], this replaces modulo with two - * multiplications and a shift: - * phase = (u16)(counter * poc_rr_step[total - 1]) - * pick = POC_FIXED_MOD16(phase, total) - * - * Proof that pick == counter % total (for total ≤ 64): - * Let S = ceil(2^16 / N). For k in [0, N): - * k*S*N / 2^16 ∈ [k, k + kN/2^16) - * Since kN < N² ≤ 64² = 4096 ≪ 2^16, floor(kN/2^16) = 0, - * so floor(k*S*N / 2^16) = k. QED. - */ -#define POC_FIXED_MOD16(phase, range) ((u32)(((u32)(phase) * (u32)(range)) >> 16)) - -/* - * POC_FASTRANGE — Map a 32-bit scrambled value to [0, range) - * - * Implements Lemire's fastrange technique: - * D. Lemire, "Fast Random Integer Generation in an Interval", - * ACM Trans. Model. Comput. Simul. 29, 1, Article 3, 2019. - * - * Computes (seed * range) >> 32, giving a uniform mapping of - * a 32-bit seed into [0, range) using only one 64-bit multiply - * and a shift. Used with golden-ratio hashing for pseudo-random - * RR distribution in the improved RR path. - */ -#define POC_FASTRANGE(seed, range) ((u32)(((u64)(seed) * (u32)(range)) >> 32)) - -/* - * RR step table: poc_rr_step[n-1] = ceil(2^16 / n) for n = 1..64 - * - * Indexed by (total - 1) where total = popcount(idle mask). - * total == 0 is unreachable (caller guarantees mask != 0). - * 64 entries × 2 bytes = 128 bytes = exactly 2 cache lines. - */ -static const u16 poc_rr_step[64] = { - 0, 0x8000, 0x5556, 0x4000, 0x3334, 0x2AAB, 0x2493, 0x2000, /* 1.. 8 */ - 0x1C72, 0x199A, 0x1746, 0x1556, 0x13B2, 0x124A, 0x1112, 0x1000, /* 9..16 */ - 0x0F10, 0x0E39, 0x0D7A, 0x0CCD, 0x0C31, 0x0BA3, 0x0B22, 0x0AAB, /* 17..24 */ - 0x0A3E, 0x09D9, 0x097C, 0x0925, 0x08D4, 0x0889, 0x0843, 0x0800, /* 25..32 */ - 0x07C2, 0x0788, 0x0751, 0x071D, 0x06EC, 0x06BD, 0x0691, 0x0667, /* 33..40 */ - 0x063F, 0x0619, 0x05F5, 0x05D2, 0x05B1, 0x0591, 0x0573, 0x0556, /* 41..48 */ - 0x053A, 0x051F, 0x0506, 0x04ED, 0x04D5, 0x04BE, 0x04A8, 0x0493, /* 49..56 */ - 0x047E, 0x046A, 0x0457, 0x0445, 0x0433, 0x0422, 0x0411, 0x0400, /* 57..64 */ -}; - -/************************************************************** - * Bit manipulation primitives: - */ - -/* - * POC_CTZ64 — Portable Count Trailing Zeros (64-bit) - * - * Three-tier architecture detection: - * - * Tier 1: Native hardware CTZ with well-defined zero semantics - * x86-64 + BMI1 (__BMI__): TZCNT — returns 64 for input 0 - * ARM64: RBIT + CLZ - * RISC-V Zbb: CTZ instruction - * - * Tier 2: x86-64 without BMI1 (Bulldozer, pre-Haswell, etc.) - * BSF is fast (~3 cyc) but UNDEFINED for input 0. - * On AMD Bulldozer: BSF(0) leaves dest register unchanged (stale value). - * On Intel pre-Haswell: BSF(0) is architecturally undefined. - * Wrap with explicit zero check to guarantee returning 64. - * - * Tier 3: De Bruijn fallback (BPF, unknown architectures) - * Software multiply + 64-entry table lookup, branchless O(1). - */ - -/* - * POC_CTZ64 is defined in sched.h for use by load balancer functions. - * Here we only define POC_CTZ64_NAME for sysfs hardware info display. - */ -#if defined(__x86_64__) && defined(__BMI__) -#define POC_CTZ64_NAME "HW (TZCNT)" -#elif defined(__aarch64__) -#define POC_CTZ64_NAME "HW (RBIT+CLZ)" -#elif defined(__riscv) && defined(__riscv_zbb) -#define POC_CTZ64_NAME "HW (ctz)" -#elif defined(__x86_64__) -#define POC_CTZ64_NAME "HW (BSF)" -#else -#define POC_CTZ64_NAME "SW (De Bruijn)" -#endif - -/* - * POC_PTSELECT — Select position of the j-th set bit in a 64-bit word - * - * Based on the algorithm described in: - * P. Pandey, M. A. Bender, R. Johnson, - * "A Fast x86 Implementation of Select", arXiv:1706.00990, 2017. - * - * Returns the bit position (0-indexed) of the j-th set bit in v. - * Undefined behavior if j >= popcount(v). - * - * Tier 1 (x86-64 + BMI2, excluding AMD Zen 1/2 slow microcode PDEP): - * PDEP + TZCNT — 4 instructions total. - * PDEP deposits the j-th source bit at the j-th mask position. - * - * Tier 2 (fallback): Iterative bit-clear — O(j) iterations - * Clears the lowest set bit j times, then CTZ on remainder. - */ - -#if defined(__x86_64__) && defined(__BMI2__) && \ - !defined(__znver1) && !defined(__znver2) -static __always_inline int poc_ptselect(u64 v, int j) -{ - u64 deposited; - - asm("pdep %2, %1, %0" : "=r"(deposited) : "r"(1ULL << j), "rm"(v)); - return POC_CTZ64(deposited); -} -#define POC_PTSELECT(v, j) poc_ptselect(v, j) -#define POC_PTSELECT_NAME "HW (PDEP)" - -/* - * Tier 2 (fallback): Iterative bit-clear — O(j) iterations. - * Clears the lowest set bit j times, then returns its position via CTZ. - */ -#else -static __always_inline int poc_ptselect_sw(u64 v, int j) -{ - int k; - - for (k = 0; k < j; k++) - v &= v - 1; /* clear lowest set bit */ - return POC_CTZ64(v); -} -#define POC_PTSELECT(v, j) poc_ptselect_sw(v, j) -#define POC_PTSELECT_NAME "SW (loop)" - -#endif /* POC_PTSELECT */ - -/************************************************************** - * Flag array to bitmask conversion (lock-free mode): - */ - -/* - * POC_BYTE_EXTRACT / POC_BYTE_PACK - constants for multiply-and-shift trick. - * - * Isolates bit 0 of each byte in a u64 word, then packs the 8 bits - * into the most significant byte via multiply. - */ -#define POC_BYTE_EXTRACT 0x0101010101010101ULL -#define POC_BYTE_PACK 0x0102040810204080ULL - -/* - * POC_BMP8 - Convert one 8-byte slice of the flag array to 8 packed bits. - * - * Tier 1 (x86-64 + BMI2, excluding AMD Zen 1/2 slow microcode PEXT): - * PEXT extracts bit 0 of each byte directly into 8 contiguous bits. - * Single instruction replaces AND + MUL + SHR. - * - * Tier 2 (fallback): Multiply-and-shift trick. - * Isolates bit 0 of each byte (AND), packs via MUL, shifts to position. - */ -#if defined(__x86_64__) && defined(__BMI2__) && \ - !defined(__znver1) && !defined(__znver2) - -static __always_inline u64 poc_bmp8_pext(u64 word, int i) -{ - u64 extracted; - - asm("pext %2, %1, %0" : "=r"(extracted) : "r"(word), "r"(POC_BYTE_EXTRACT)); - return extracted << (i * 8); -} -#define POC_BMP8(w, i) poc_bmp8_pext((w)[i], i) - -#else - -#define POC_BMP8(w, i) \ - ((((w)[i] & POC_BYTE_EXTRACT) * POC_BYTE_PACK >> 56) << ((i) * 8)) - -#endif /* POC_BMP8 */ - -/* - * poc_flags_to_u64 - Convert u8[64] flag array to u64 bitmask - * @flags: pointer to 64-byte flag array (cacheline-aligned) - * - * Phase 1 (memcpy): snapshot the 64-byte cache line to the stack. - * This eliminates the window in which a concurrent MESI invalidation - * could cause a re-fetch mid-computation. All 64 bytes land in one - * or two cache line transfers; subsequent computation is purely local. - * - * Phase 2: pack the stack-local copy into a u64 bitmask via - * multiply-and-shift (or PEXT on BMI2 x86). Always processes all - * 8 chunks — the extra iterations for small LLCs are negligible - * on stack-local data and avoid the poc_chunks_bit* dispatch tree. - * - * Returns: u64 bitmask with bit N set iff flags[N] != 0 - */ -static __always_inline u64 poc_flags_to_u64(const u8 *flags) -{ - u64 w[8]; - - /* Phase 1: snapshot shared cache line to stack */ - memcpy(w, flags, 64); - - /* Phase 2: pack stack-local copy into bitmask */ - return POC_BMP8(w, 0) | POC_BMP8(w, 1) | POC_BMP8(w, 2) | POC_BMP8(w, 3) | - POC_BMP8(w, 4) | POC_BMP8(w, 5) | POC_BMP8(w, 6) | POC_BMP8(w, 7); -} - -/************************************************************** - * Idle mask accessors: - */ - -/* - * poc_idle_cpu_mask - Get idle CPU bitmask filtered by LLC and affinity - * @affinity: task's allowed CPU mask (poc-relative, from poc_cpumask_to_u64) - * @sd_share: per-LLC shared data - * - * Returns a snapshot of idle CPUs within this LLC, masked by - * llc_members (valid CPUs) and @affinity (task placement). - * - * bitmap mode (default): single atomic64_read (MOV on x86). - * flag array mode: stack-snapshot + multiply-and-shift aggregation. - */ -static __always_inline u64 poc_idle_cpu_mask(u64 affinity, - struct sched_domain_shared *sd_share) -{ - u64 cpus; - - if (static_branch_unlikely(&sched_poc_lockless_bitmap)) - cpus = poc_flags_to_u64(sd_share->poc_idle_cpus); - else - cpus = (u64)atomic64_read(&sd_share->poc_idle_cpus_mask); - - return cpus & sd_share->poc_llc_members & affinity; -} - -#ifdef CONFIG_SCHED_SMT -/* - * poc_idle_core_mask - Get idle core bitmask - * @cpu_mask: snapshot of idle CPUs (already masked by llc_members & affinity) - * @sd_share: per-LLC shared data - * - * Returns a bitmask with bits set at core representative positions - * (lowest-numbered sibling) for cores where ALL SMT siblings are idle. - * - * Three-tier derivation: - * - * Tier 1 (consecutive 2-way SMT): 3 register ops with compile-time - * constants — AND, SHR 1, AND 0x5555... No memory loads. - * - * Tier 2 (uniform stride-N 2-way SMT): 3 register ops with - * precomputed per-LLC shift and primary mask — AND, SHR N, AND. - * Two extra loads (poc_smt_shift, poc_primary_mask) from sd_share, - * but no write-path overhead. - * - * Tier 3 (exotic: >2-way SMT or non-uniform topology): reads the - * separately-maintained poc_idle_cores_mask atomic64_t. Write path - * maintains this bitmap on every idle transition. - */ -static __always_inline u64 poc_idle_core_mask(u64 cpu_mask, - struct sched_domain_shared *sd_share) -{ - /* Tier 1: consecutive — constants only, zero loads */ - if (static_branch_likely(&sched_poc_smt_consecutive)) - return cpu_mask & (cpu_mask >> 1) & 0x5555555555555555ULL; - - /* Tier 2: uniform stride-N — precomputed shift + mask */ - if (static_branch_likely(&sched_poc_smt_uniform)) - return cpu_mask & (cpu_mask >> sd_share->poc_smt_shift) - & sd_share->poc_primary_mask; - - /* Tier 3: exotic — bitmap or flag array based on mode */ - if (static_branch_unlikely(&sched_poc_lockless_bitmap)) - return poc_flags_to_u64(sd_share->poc_idle_cores) & cpu_mask; - - return (u64)atomic64_read(&sd_share->poc_idle_cores_mask) & cpu_mask; -} -#endif /* CONFIG_SCHED_SMT */ - -/* - * __set_cpu_idle_state_poc - Update idle state in atomic64_t bitmap - * @cpu: CPU number - * @state: 0=busy, 1=idle - * - * Updates the atomic64_t cpus bitmap via atomic64_or/andnot (LOCK'd on x86). - * - * On uniform 2-way SMT (Tier 1 & 2: consecutive or stride-N), only - * the cpus state is updated; core idle state is derived at read time - * via bit-parallel operations. - * - * On exotic SMT (Tier 3: >2-way or non-uniform), also maintains the - * separate cores state (bitmap or flag array) for O(1) read-time lookup. - * - * Only one representation is maintained at a time (single-write), - * selected by sched_poc_lockless_bitmap. - * - * Caller (inline wrapper in sched.h) ensures poc_selector_active is on - * and sched_asym_cpucap_active() is false before calling here. - */ -void __set_cpu_idle_state_poc(int cpu, int state) -{ - struct rq *rq = cpu_rq(cpu); - if (!static_branch_unlikely(&sched_poc_lockless_bitmap) && - !state && READ_ONCE(rq->poc_idle_committed)) - return; - - guard(rcu)(); - struct sched_domain_shared *sd_share = - rcu_dereference(per_cpu(sd_llc_shared, cpu)); - if (!sd_share || !sd_share->poc_fast_eligible) - return; - - int bit = cpu - sd_share->poc_cpu_base; - u64 bit_mask = 1ULL << bit; - - if (static_branch_unlikely(&sched_poc_lockless_bitmap)) { - WRITE_ONCE(sd_share->poc_idle_cpus[bit], state > 0 ? 1 : 0); - } else if (state > 0) { - /* Entering idle: clear any stale committed flag */ - WRITE_ONCE(rq->poc_idle_committed, 0); - atomic64_or(bit_mask, &sd_share->poc_idle_cpus_mask); - } else { - /* - * Exiting idle: if a waker already committed (cleared the - * bitmap bit), skip the redundant atomic on the shared - * cacheline. The flag lives in rq's first cacheline — - * same line the waker already dirtied via ttwu_pending. - */ - atomic64_andnot(bit_mask, &sd_share->poc_idle_cpus_mask); - WRITE_ONCE(rq->poc_idle_committed, 1); - } - -#ifdef CONFIG_SCHED_SMT - if (sched_smt_active()) { - /* Tier 1 & 2: read-time derivation, no write-path cost */ - if (static_branch_likely(&sched_poc_smt_uniform)) - return; - /* - * Tier 3 (exotic SMT): maintain separate cores state. - * Check whether all SMT siblings are idle. - */ - u64 smt = sd_share->poc_smt_mask[bit]; - u64 core_bitmask = smt & (-smt); /* core representative */ - int core_bit = __builtin_ctzll(core_bitmask); - bool core_idle; - - if (static_branch_unlikely(&sched_poc_lockless_bitmap)) { - /* - * Flag array mode: check siblings via WRITE_ONCE-stored - * flags. smp_wmb() ensures our store to poc_idle_cpus[] - * is visible before we read sibling flags. - * On x86 TSO: compiler barrier only (~0 cyc). - * On ARM64: dmb ishst. - */ - smp_wmb(); - u64 tmp = smt; - - core_idle = state > 0; - while (core_idle && tmp) { - int s = __builtin_ctzll(tmp); - - if (!READ_ONCE(sd_share->poc_idle_cpus[s])) - core_idle = false; - tmp &= tmp - 1; - } - WRITE_ONCE(sd_share->poc_idle_cores[core_bit], - core_idle ? 1 : 0); - } else { - /* - * smp_mb__after_atomic() ensures our atomic store is - * visible before we read sibling bits. On x86 TSO this - * is a compiler barrier (~0 cyc); on ARM64: dmb ish. - */ - smp_mb__after_atomic(); - u64 cpus = (u64)atomic64_read(&sd_share->poc_idle_cpus_mask); - core_idle = (cpus & smt) == smt; - u64 cores = (u64)atomic64_read(&sd_share->poc_idle_cores_mask); - - if (core_idle) { - if (!(cores & core_bitmask)) - atomic64_or(core_bitmask, - &sd_share->poc_idle_cores_mask); - } else { - if (cores & core_bitmask) - atomic64_andnot(core_bitmask, - &sd_share->poc_idle_cores_mask); - } - } - } -#endif /* CONFIG_SCHED_SMT */ -} - -/************************************************************** - * Idle CPU selection helpers: - */ - -/* Test whether a single CPU is idle in a POC bitmap snapshot. - * Assumes cpu_mask is in scope — works in any function with that variable. */ -#define POC_IDLE_CPU(bit) (cpu_mask & (1ULL << (bit))) -/* Scope-free validity checks — usable in any function. */ -#define POC_CPU_VALID(cpu) ((cpu) >= 0) -#define POC_CPU_IN_LLC(bit) ((unsigned int)(bit) < 64) - -/* - * poc_select_rr_improved - Improved round-robin idle CPU selection - * @base: poc_cpu_base (smallest CPU ID in this LLC) - * @mask: idle bitmask (snapshot, caller guarantees non-zero) - * @counter: per-CPU round-robin counter value - * - * Improved RR with two techniques: - * 1. Case-split by total: - * total=1: direct ctz - * total=2: interleave by counter LSB (guarantees non-repeat), - * single CTZ via cmov-selected source mask - * total>=3: golden-ratio scramble + Lemire fastrange - * 2. Golden-ratio scrambling (counter * 0x9E3779B9) mapped via - * Lemire fastrange for pseudo-random uniform distribution. - * - * eager_commit (unconditional) already prevents burst wake-ups from - * re-selecting the same CPU by clearing the bitmap bit at selection - * time, so no previous-pick exclusion state is needed here. - * - * Returns: selected CPU number. - */ -static __always_inline int poc_select_rr_improved( - int base, u64 mask, unsigned int counter) -{ - int total = hweight64(mask); - - if (total <= 2) { - /* - * Pick the lower or upper set bit via counter LSB if total == 2. - * Select the mask first (cmov), then one CTZ — halves the - * cost on archs where CTZ64 is a SW fallback (De Bruijn). - */ - if ((total == 2) && (counter & 1)) - mask &= mask - 1; - - return base + POC_CTZ64(mask); - } - - /* total >= 3: golden-ratio scramble + Lemire fastrange */ - { - u32 scrambled = POC_SCRAMBLE(counter); - int pick = POC_FASTRANGE(scrambled, total); - - return base + POC_PTSELECT(mask, pick); - } -} - -/* - * poc_select_rr - Round-robin idle CPU selection from a single-word mask - * @base: poc_cpu_base (smallest CPU ID in this LLC) - * @mask: idle bitmask (snapshot) - * @counter: per-CPU round-robin counter value - * - * Division-free perfect round-robin via FASTRANGE16 + PTSELECT. - * Consecutive calls on the same CPU never repeat an idle CPU - * until all candidates have been visited. - * Caller must ensure at least one bit is set in mask. - * Returns: selected CPU number. - */ -static __always_inline int poc_select_rr(int base, u64 mask, unsigned int counter) -{ - if (static_branch_likely(&sched_poc_rr_improved)) - return poc_select_rr_improved(base, mask, counter); - - /* Current strategy: poc_rr_step[] table (perfect RR), unchanged */ - { - int total = hweight64(mask); - u16 phase = (u16)(counter * (u32)poc_rr_step[total - 1]); - int pick = POC_FIXED_MOD16(phase, total); - - return POC_PTSELECT(mask, pick) + base; - } -} - -/* - * poc_cluster_search - Search for an idle CPU within the target's L2 cluster - * @base: poc_cpu_base (smallest CPU ID in this LLC) - * @tgt_bit: target CPU's POC-relative bit position - * @sd_share: per-LLC shared data containing cluster geometry - * @mask: snapshot of idle bitmask (cores or cpus, caller decides) - * - * Uses pre-computed cluster mask for O(1) lookup via CTZ. - * Returns: idle CPU number if found within cluster, -1 otherwise. - */ -static __always_inline int poc_cluster_search(int base, int tgt_bit, - struct sched_domain_shared *sd_share, u64 mask) -{ - u64 cls_idle = mask & sd_share->poc_cluster_mask[tgt_bit]; - - if (!cls_idle) - return -1; - - if (static_branch_likely(&sched_poc_rr_improved)) { - /* Improved path: inc counter here so LV3 fallback sees fresh value */ - unsigned int counter = __this_cpu_inc_return(poc_rr_counter); - return poc_select_rr_improved(base, cls_idle, counter); - } - - /* Current strategy: ctz lowest-bit (no RR), unchanged */ - return base + POC_CTZ64(cls_idle); -} - -#ifdef CONFIG_SCHED_SMT -/* - * poc_smt_sibling_mask - Get SMT sibling bitmask for a given CPU - * @bit: POC-relative bit position - * @sd_share: per-LLC shared data - * - * Three-tier computation matching poc_idle_core_mask(): - * - * Tier 1 (consecutive): 3ULL << (bit & ~1) — shift only, zero loads. - * - * Tier 2 (uniform stride-N): determine sibling via poc_smt_shift - * and poc_primary_mask. Avoids poc_smt_mask[] array lookup. - * - * Tier 3 (exotic): loads from pre-computed poc_smt_mask[] table. - */ -static __always_inline u64 poc_smt_sibling_mask(int bit, - struct sched_domain_shared *sd_share) -{ - if (static_branch_likely(&sched_poc_smt_consecutive)) - return 3ULL << (bit & ~1); - - if (static_branch_likely(&sched_poc_smt_uniform)) { - u8 shift = sd_share->poc_smt_shift; - int sib = (sd_share->poc_primary_mask & (1ULL << bit)) - ? bit + shift : bit - shift; - if (unlikely(sib < 0 || sib >= 64)) - return 1ULL << bit; - return (1ULL << bit) | (1ULL << sib); - } - - return sd_share->poc_smt_mask[bit]; -} - -/* - * poc_find_idle_smt_sibling - Find an idle CPU among target and its SMT siblings - * @base: poc_cpu_base (smallest CPU ID in this LLC) - * @tgt_bit: target CPU's POC-relative bit position - * @cpu_mask: snapshot of idle CPU bitmask - * @smt_mask: pre-computed SMT sibling mask for target (includes self) - * - * Searches target itself and its SMT siblings for an idle CPU. - * Target is checked first for cache locality. - * Returns: idle CPU number if found, -1 otherwise - */ -static __always_inline int poc_find_idle_smt_sibling( - int base, int tgt_bit, u64 cpu_mask, u64 smt_mask) -{ - /* Check target first for cache locality */ - if (POC_IDLE_CPU(tgt_bit)) - return base + tgt_bit; - - u64 idle_sibs = cpu_mask & smt_mask; - - if (idle_sibs) - return base + POC_CTZ64(idle_sibs); - - return -1; -} -/* - * poc_try_idle_smt - Find an idle CPU among a CPU and its SMT siblings - * @base: poc_cpu_base (smallest CPU ID in this LLC) - * @cpu: the CPU to check (and its SMT siblings) - * @cpu_mask: snapshot of idle CPU bitmask - * @sd_share: per-LLC shared data - * - * Checks if the given CPU or any of its SMT siblings is idle. - * Caller is responsible for poc_count() and poc_commit_selection(). - * Returns: idle CPU number if found, -1 otherwise - */ -static __always_inline int poc_try_idle_smt(int base, int cpu, - u64 cpu_mask, struct sched_domain_shared *sd_share) -{ - int bit = cpu - base; - - if (sd_share->poc_llc_members & (1ULL << bit)) { - int smt_cpu = poc_find_idle_smt_sibling(base, bit, - cpu_mask, poc_smt_sibling_mask(bit, sd_share)); - if (POC_CPU_VALID(smt_cpu)) - return smt_cpu; - } - return -1; -} - -#endif /* CONFIG_SCHED_SMT */ - -/* - * poc_commit_selection - Atomically clear selected CPU from idle bitmap - * @cpu: the CPU number selected by POC - * @sd_share: per-LLC shared data - * - * Clears the selected CPU's bit in poc_idle_cpus_mask at selection - * time to close the race window where multiple waker CPUs read the - * same stale bitmap and select the same idle CPU. The do_idle() - * exit path performs an idempotent clear as a safety net for - * non-POC wakeups; poc_idle_committed gates that path so the atomic - * fires at most once per selection. - */ -static __always_inline void poc_commit_selection(int cpu, - struct sched_domain_shared *sd_share) -{ - if (cpu_rq(cpu)->nr_running <= 2) { - int bit = cpu - sd_share->poc_cpu_base; - - if (static_branch_unlikely(&sched_poc_lockless_bitmap)) { - WRITE_ONCE(sd_share->poc_idle_cpus[bit], 0); - smp_wmb(); - } else { - atomic64_andnot(1ULL << bit, &sd_share->poc_idle_cpus_mask); - smp_mb__after_atomic(); - /* Mark committed so target skips redundant andnot on wakeup */ - WRITE_ONCE(cpu_rq(cpu)->poc_idle_committed, 1); - } - } -} - -/* - * POC_IDLE_CORE - Test whether a CPU's core is fully idle. - * POC_IDLE_SMT - Find an idle CPU among @cpu and its SMT siblings. - * - * POC_RETURN - Record hit counter, clear bitmap, return selected CPU. - * POC_RETURN_IF - Same, but only if @cpu >= 0 (used after POC_IDLE_SMT). - * - * These assume core_mask, base, sd_share are in scope - * (only used inside select_idle_cpu_poc). - */ -#define POC_IDLE_CORE(bit) (core_mask & poc_smt_sibling_mask((bit), sd_share)) -#define POC_IDLE_SMT(cpu) poc_try_idle_smt(base, (cpu), cpu_mask, sd_share) - -#define POC_RETURN(cpu, level) do { \ - poc_count(level); \ - poc_commit_selection(cpu, sd_share); \ - return cpu; \ -} while (0) - -#define POC_RETURN_IF(cpu, level) do { \ - if ((cpu) >= 0) \ - POC_RETURN(cpu, level); \ -} while (0) - -/************************************************************** - * Fast path dispatcher: - */ - -/* - * select_idle_cpu_poc - Fast idle CPU selector (atomic64 bitmap path) - * @target: CPU chosen by wake_affine (Level 1 preferred CPU; - * search origin for L2/L3/L5/L6) - * @prev: task's previous CPU (Level 4 cache locality preference) - * @recent: task's recent_used_cpu (-1 if none; pre-filtered by caller) - * @sync: 1 if synchronous wakeup (Level 4s: waker yields CPU) - * @sd_share: per-LLC shared data (caller provides; never NULL) - * @allowed: task's cpumask (p->cpus_ptr) for affinity filtering - * - * Two operating modes (sysctl kernel.sched_poc_smt_fallback): - * - * smt_fallback=0 (default): POC handles all idle CPU - * selection itself, including SMT siblings. Prioritizes - * prev's SMT sibling for cache locality. Uses CFS's - * nr_idle_scan (SIS_UTIL) to gate Level 5/6 under overload. - * - * smt_fallback=1: Bails out to CFS when has_idle_cores is - * false. CFS handles SMT sibling selection via - * select_idle_smt(prev) and nr_idle_scan-limited - * select_idle_cpu(). - * - * Selection levels: - * - * Level 0: Saturation check -- no idle CPUs → return -1 - * (smt_fallback: also when has_idle_cores == false) - * Level 1r: Recent's core is fully idle → return recent (!early_select) - * Level 1s: Target CPU idle in bitmap → return target (L1/TLB affinity) - * Level 1t: Target CPU's core is fully idle → return target - * Level 1p: Prev's core is fully idle → return prev (prev != target) - * --- core_mask != 0: search idle-core bitmap --- - * Level 2: Idle core in L2 cluster (CTZ) - * Level 3: Idle core across LLC (RR PTSELECT) - * --- core_mask == 0: search idle-CPU bitmap --- - * Level 4s: sync + target CPU idle (waker frees core) - * Level 4p: Prev's SMT sibling (cache locality) - * Level 4t: Target's SMT sibling - * Level 4r: Recent's SMT sibling (warm cache, always) - * [SIS_UTIL gate: nr_idle_scan == 0 → return -2] - * Level 5: Idle CPU in L2 cluster (CTZ) - * Level 6: Idle CPU across LLC (RR PTSELECT) - * - * Non-SMT: Level 1r → 1t → 1p → Level 2 → Level 3 (core = CPU). - * - * Returns: idle CPU number if found, -1 if not found (CFS may retry), - * -2 if SIS_UTIL overload (caller should skip CFS) - */ -static __always_inline int select_idle_cpu_poc(int target, int prev, - int recent, int sync, - struct sched_domain_shared *sd_share, - const struct cpumask *allowed) -{ - int base = sd_share->poc_cpu_base; - int rct_bit = recent - base; - int tgt_bit = target - base; - int prv_bit = prev - base; -#ifdef CONFIG_SCHED_SMT - u64 core_mask __maybe_unused; -#endif - u64 affinity; - u64 cpu_mask; - int level_offset = 0; - -#ifdef CONFIG_SCHED_SMT - /* SMT fallback: bail to CFS for SMT sibling selection */ - if (sched_smt_active() && - static_branch_unlikely(&sched_poc_smt_fallback) && - !READ_ONCE(sd_share->has_idle_cores)) - return -1; -#endif - - if (static_branch_unlikely(&sched_poc_lockless_bitmap)) - prefetch(sd_share->poc_idle_cpus); - else - prefetch(&sd_share->poc_idle_cpus_mask); -#ifdef CONFIG_SCHED_SMT - if (sched_smt_active()) { - if (!static_branch_likely(&sched_poc_smt_uniform)) { - if (static_branch_unlikely(&sched_poc_lockless_bitmap)) - prefetch(sd_share->poc_idle_cores); - else - prefetch(&sd_share->poc_idle_cores_mask); - if (POC_CPU_VALID(recent)) - prefetch(&sd_share->poc_smt_mask[rct_bit]); - prefetch(&sd_share->poc_smt_mask[tgt_bit]); - prefetch(&sd_share->poc_smt_mask[prv_bit]); - } - } -#endif - if (static_branch_likely(&sched_cluster_active)) - prefetch(&sd_share->poc_cluster_mask[tgt_bit]); - - affinity = poc_cpumask_to_u64(allowed, sd_share); - cpu_mask = poc_idle_cpu_mask(affinity, sd_share); - - /* Level 0: Saturation — no idle CPU */ - if (!cpu_mask) - return -1; - -#ifdef CONFIG_SCHED_SMT - if (sched_smt_active()) { - core_mask = poc_idle_core_mask(cpu_mask, sd_share); - - /* Level 1r: recent's core is idle (warm cache) */ - if (!static_branch_likely(&sched_poc_early_select) && - core_mask && POC_CPU_IN_LLC(rct_bit) && POC_IDLE_CORE(rct_bit)) - POC_RETURN(recent, POC_LV1R); - - /* Level 1s: target CPU sticky — L1/TLB affinity shortcut */ - if (static_branch_unlikely(&sched_poc_target_sticky) && POC_IDLE_CPU(tgt_bit)) - POC_RETURN(target, POC_LV1S); - - if (core_mask) { - /* - * Idle core path: T → P order. - * Target first — wake_affine chose it for data sharing - * and the full core is free. - */ - - /* Level 1t: target CPU's core is idle → return it */ - if (!static_branch_likely(&sched_poc_early_select) && - POC_IDLE_CORE(tgt_bit)) - POC_RETURN(target, POC_LV1T); - - /* Level 1p: prev's core is idle (task's L1/L2 warm) */ - if (prev != target && POC_CPU_IN_LLC(prv_bit) && POC_IDLE_CORE(prv_bit)) - POC_RETURN(prev, POC_LV1P); - - cpu_mask = core_mask; - } else { - int cpu; - - /* Level 4s: sync wakeup + target CPU idle → - * waker will sleep imminently, freeing the core */ - if (sync && POC_IDLE_CPU(tgt_bit)) - POC_RETURN(target, POC_LV4S); - - /* - * No-idle-core path: P → T → R order. - * Target itself was already tried at Level 1s/4s; - * prioritize task's own cache (prev, recent) over - * waker locality (target's sibling). - */ - - /* Level 4p: prev's SMT sibling (cache locality) */ - if (prev != target && POC_CPU_IN_LLC(prv_bit)) { - cpu = POC_IDLE_SMT(prev); - POC_RETURN_IF(cpu, POC_LV4P); - } - - /* Level 4t: target's SMT sibling */ - cpu = POC_IDLE_SMT(target); - POC_RETURN_IF(cpu, POC_LV4T); - - /* Level 4r: recent's SMT sibling (warm cache) */ - if (POC_CPU_IN_LLC(rct_bit)) { - cpu = POC_IDLE_SMT(recent); - POC_RETURN_IF(cpu, POC_LV4R); - } - - /* SIS_UTIL overload gate for Level 5/6 */ - if (!static_branch_likely(&sched_poc_greedy_search) && - sched_feat(SIS_UTIL) && !READ_ONCE(sd_share->nr_idle_scan)) - return -2; - - level_offset = POC_SMT_LEVEL_OFFSET; - } - } - else -#endif - { - /* Level 1r: recent CPU is idle (non-SMT) */ - if (!static_branch_likely(&sched_poc_early_select) && - POC_CPU_IN_LLC(rct_bit) && POC_IDLE_CPU(rct_bit)) - POC_RETURN(recent, POC_LV1R); - /* Level 1t: target CPU is idle → return (non-SMT) */ - if (POC_IDLE_CPU(tgt_bit)) - POC_RETURN(target, POC_LV1T); - /* Level 1p: prev CPU is idle (non-SMT) */ - if (prev != target && POC_CPU_IN_LLC(prv_bit) && POC_IDLE_CPU(prv_bit)) - POC_RETURN(prev, POC_LV1P); - } - - if (static_branch_likely(&sched_poc_packed)) { - /* - * Level 2+3 / 5+6: packed priority search (≤32 CPUs/LLC) - * - * Packs cluster candidates (high priority) into lower 32 bits - * and all LLC candidates (low priority) into upper 32 bits. - * A single TZCNT resolves the highest-priority idle CPU. - * Level discrimination: (raw >> 5) yields 0 (cluster) or 1 (LLC). - * - * rr_improved=ON: rotation amount via golden-ratio scramble. - * rr_improved=OFF: rotation amount is (counter & 31). - */ - unsigned int counter = __this_cpu_inc_return(poc_rr_counter); - int rot; - u32 cls = 0; - u32 all; - u64 packed; - int raw, bit; - - if (static_branch_likely(&sched_poc_rr_improved)) - rot = (int)(POC_SCRAMBLE(counter) >> 27); - else - rot = counter & 31; - - if (static_branch_likely(&sched_cluster_active) && - sd_share->poc_cluster_valid) - cls = ror32((u32)(cpu_mask & - sd_share->poc_cluster_mask[tgt_bit]), rot); - - all = ror32((u32)cpu_mask, rot); - packed = (u64)cls | ((u64)all << 32); - - raw = POC_CTZ64(packed); - bit = ((raw & 31) + rot) & 31; - - POC_RETURN(base + bit, POC_LV2 + (raw >> 5) + level_offset); - } else { - /* Level 2/5: idle core/cpu in target's L2 cluster */ - if (static_branch_likely(&sched_cluster_active) - && sd_share->poc_cluster_valid) { - int cpu = poc_cluster_search( - base, tgt_bit, sd_share, cpu_mask); - if (POC_CPU_VALID(cpu)) - POC_RETURN(cpu, POC_LV2 + level_offset); - } - - /* Level 3/6: idle core/cpu across LLC via RR */ - { - unsigned int counter = __this_cpu_inc_return(poc_rr_counter); - int rr_cpu = poc_select_rr(base, cpu_mask, counter); - POC_RETURN(rr_cpu, POC_LV3 + level_offset); - } - } -} - -/************************************************************** - * Topology setup: - * - * poc_sd_shared_init - Initialize POC fields in sched_domain_shared - * @sd: the LLC-sharing sched_domain whose ->shared was just attached - * @sd_id: first CPU of @sd's span (used as poc_cpu_base) - * - * Called from build_sched_domains() right after sd->shared is attached - * for an SD_SHARE_LLC domain. Computes per-LLC bit-base and pre-builds - * member/SMT/cluster masks for O(1) lookup at wakeup time. - */ -void poc_sd_shared_init(struct sched_domain *sd, int sd_id) -{ - struct cpumask *sd_span = sched_domain_span(sd); - int range = cpumask_last(sd_span) - sd_id + 1; - - sd->shared->poc_cpu_base = sd_id; - sd->shared->poc_affinity_shift = sd_id & 63; - - if (range <= 64) { - sd->shared->poc_fast_eligible = true; - /* - * Disable aligned optimization if this LLC's base CPU - * is not 64-aligned (e.g., Threadripper CCDs). - */ - if (sd_id & 63) - static_branch_disable_cpuslocked(&sched_poc_aligned); - /* - * Disable packed priority search if this LLC - * has more than 32 CPUs. - */ - if (range > 32) - static_branch_disable_cpuslocked(&sched_poc_packed); - } else { - sd->shared->poc_fast_eligible = false; - static_branch_disable_cpuslocked(&sched_poc_packed); - } - memset(sd->shared->poc_idle_cpus, 0, - sizeof(sd->shared->poc_idle_cpus)); - atomic64_set(&sd->shared->poc_idle_cpus_mask, 0); -#ifdef CONFIG_SCHED_SMT - memset(sd->shared->poc_idle_cores, 0, - sizeof(sd->shared->poc_idle_cores)); - atomic64_set(&sd->shared->poc_idle_cores_mask, 0); -#endif - - /* Build LLC member bitmask for reader-side aggregation */ - { - u64 members = 0; - int cpu_iter; - - for_each_cpu(cpu_iter, sd_span) { - int bit = cpu_iter - sd_id; - - if ((unsigned int)bit < 64) - members |= 1ULL << bit; - } - sd->shared->poc_llc_members = members; - } - -#ifdef CONFIG_SCHED_SMT - /* - * Pre-compute SMT sibling masks for Level 4. - * Each entry contains a bitmask of SMT siblings (including self) - * for O(1) lookup via CTZ during wakeup. - */ - memset(sd->shared->poc_smt_mask, 0, - sizeof(sd->shared->poc_smt_mask)); - if (sd->shared->poc_fast_eligible) { - int cpu_iter; - - for_each_cpu(cpu_iter, sd_span) { - int bit = cpu_iter - sd_id; - int sibling; - u64 mask = 0; - - for_each_cpu(sibling, cpu_smt_mask(cpu_iter)) { - int sib_bit; - - sib_bit = sibling - sd_id; - if (sib_bit >= 0 && sib_bit < 64) - mask |= 1ULL << sib_bit; - } - if (bit >= 0 && bit < 64) - sd->shared->poc_smt_mask[bit] = mask; - } - } - - /* - * Detect SMT topology and classify for poc_idle_core_mask(): - * - * Tier 1 (consecutive): uniform 2-way SMT, siblings at - * consecutive bit positions (e.g., 0,1 / 2,3). - * Uses compile-time constants: shift=1, mask=0x5555... - * - * Tier 2 (uniform stride-N): uniform 2-way SMT with - * constant stride between siblings (e.g., Intel Xeon - * stride-8: CPU 0,8 / 1,9 / ...). Uses precomputed - * poc_smt_shift and poc_primary_mask for read-time - * derivation without write-path overhead. - * - * Tier 3 (exotic): >2-way SMT, non-uniform topology, - * or mixed SMT ways. Falls back to write-time - * maintenance of poc_idle_cores_mask atomic64_t. - * - * On pure non-SMT systems, the key values are irrelevant - * because sched_smt_active() gates all SMT paths. - */ - sd->shared->poc_smt_shift = 1; - sd->shared->poc_primary_mask = 0; - - if (sd->shared->poc_fast_eligible) { - int cpu_iter; - bool all_2way = true; - bool all_consecutive = true; - int uniform_stride = -1; - u64 primary_mask = 0; - - for_each_cpu(cpu_iter, sd_span) { - int bit = cpu_iter - sd_id; - - if (bit < 0 || bit >= 64) - continue; - u64 mask = sd->shared->poc_smt_mask[bit]; - int ways = hweight64(mask); - - if (ways != 2) { - all_2way = false; - all_consecutive = false; - break; - } - - int lo = __ffs(mask); - int hi = __fls(mask); - int stride = hi - lo; - - /* Track primary (lowest-numbered sibling) */ - primary_mask |= 1ULL << lo; - - /* Check consecutive: 0b11 at even position */ - if ((lo & 1) || mask != (3ULL << lo)) - all_consecutive = false; - - /* Check uniform stride */ - if (uniform_stride < 0) - uniform_stride = stride; - else if (stride != uniform_stride) - all_2way = false; - } - - if (!all_consecutive) - static_branch_disable_cpuslocked( - &sched_poc_smt_consecutive); - - if (all_2way && uniform_stride > 0) { - sd->shared->poc_smt_shift = (u8)uniform_stride; - sd->shared->poc_primary_mask = primary_mask; - } else { - static_branch_disable_cpuslocked( - &sched_poc_smt_consecutive); - static_branch_disable_cpuslocked( - &sched_poc_smt_uniform); - } - } -#endif /* CONFIG_SCHED_SMT */ - - memset(sd->shared->poc_cluster_mask, 0, - sizeof(sd->shared->poc_cluster_mask)); - - sd->shared->poc_cluster_valid = false; - -#ifdef CONFIG_SCHED_CLUSTER - /* - * Detect cluster (L2-sharing) topology for Level 2/5 - * cluster-local search in POC selector. - * - * Uses cpu_clustergroup_mask() which returns the L2 - * cache sharing mask on x86. Validates that all - * clusters are uniform (same size, power-of-2, and - * naturally aligned in POC bit space). - */ - if (sd->shared->poc_fast_eligible) { - const struct cpumask *cls_mask = cpu_clustergroup_mask(sd_id); - int cls_size = cpumask_weight(cls_mask); - int smt_size = cpumask_weight(cpu_smt_mask(sd_id)); - - if (cls_size > smt_size && is_power_of_2(cls_size)) { - bool valid = true; - int cpu_iter; - - for_each_cpu(cpu_iter, sd_span) { - const struct cpumask *m = - cpu_clustergroup_mask(cpu_iter); - int first = cpumask_first(m); - int rel = first - sd_id; - - if (cpumask_weight(m) != cls_size || - (rel & (cls_size - 1)) != 0) { - valid = false; - break; - } - } - if (valid) { - sd->shared->poc_cluster_valid = true; - - /* - * Pre-compute cluster masks for O(1) lookup. - * Each entry contains a bitmask of cluster - * members (excluding self) for fast search. - */ - for_each_cpu(cpu_iter, sd_span) { - const struct cpumask *m = - cpu_clustergroup_mask(cpu_iter); - int bit = cpu_iter - sd_id; - int member; - u64 cmask = 0; - - for_each_cpu(member, m) { - int mbit; - - if (member == cpu_iter) - continue; - mbit = member - sd_id; - if (mbit >= 0 && mbit < 64) - cmask |= 1ULL << mbit; - } - if (bit >= 0 && bit < 64) - sd->shared->poc_cluster_mask[bit] = cmask; - } - } - } - } -#endif /* CONFIG_SCHED_CLUSTER */ -} - -/************************************************************** - * Sysctl interface and initialization: - */ - -#if defined(CONFIG_SYSCTL) || defined(CONFIG_SCHED_CLASS_EXT) -/* - * poc_resync_idle_state - Resync POC idle bitmaps after re-enable - * - * When POC is re-enabled after a period of being disabled, - * the idle bitmaps may be stale. Walk all online CPUs and push - * the current idle state into poc_idle_cpus_mask (and poc_idle_cores_mask - * on non-consecutive SMT). - * - * Must be called AFTER static_branch_enable() so that concurrent - * idle transitions are also updating the flags. - * Caller must hold cpus_read_lock(). - */ -static void poc_resync_idle_state(void) -{ - int cpu; - - for_each_online_cpu(cpu) { - WRITE_ONCE(cpu_rq(cpu)->poc_idle_committed, 0); - __set_cpu_idle_state_poc(cpu, idle_cpu(cpu)); - } -} - -/* - * poc_reevaluate_active - Recompute poc_selector_active from inputs - * - * poc_selector_active = sched_poc_selector && !poc_selector_skip - * - * On transition to active: enable static key, then resync idle bitmaps. - * On transition to inactive: disable static key. - * Caller must hold cpus_read_lock(). - */ -static void poc_reevaluate_active(void) -{ - bool want = sched_poc_selector && !poc_selector_skip; - bool now = static_branch_likely(&poc_selector_active); - - if (want == now) - return; - - if (want) { - static_branch_enable_cpuslocked(&poc_selector_active); - poc_resync_idle_state(); - } else { - static_branch_disable_cpuslocked(&poc_selector_active); - } -} -#endif /* CONFIG_SYSCTL || CONFIG_SCHED_CLASS_EXT */ - -#ifdef CONFIG_SCHED_CLASS_EXT -/* - * poc_notify_scx - Called by sched_ext on enable/disable transitions - * @scx_active: true when scx scheduler is being enabled - */ -void poc_notify_scx(bool scx_active) -{ - cpus_read_lock(); - poc_selector_skip = scx_active; - poc_reevaluate_active(); - cpus_read_unlock(); -} - -/* - * poc_skip_fallback_work - Workqueue item to re-enable POC after scx fallback. - * - * Scheduled by poc_check_skip_fallback() when an scx scheduler calls - * select_idle_sibling. Runs poc_reevaluate_active() outside the hot path - * to avoid updating the static key and resyncing bitmaps inline. - */ -static void poc_skip_fallback_fn(struct work_struct *work); -static DECLARE_WORK(poc_skip_fallback_work, poc_skip_fallback_fn); - -static void poc_skip_fallback_fn(struct work_struct *work) -{ - cpus_read_lock(); - poc_reevaluate_active(); - cpus_read_unlock(); -} - -/* - * poc_check_skip_fallback - Hot-path detection for scx calling select_idle_sibling - * - * While scx is active, poc_selector_skip=true suppresses idle bitmap updates - * in do_idle. Some scx schedulers still call select_idle_sibling; when that - * happens, flip poc_selector_skip back to false and schedule a workqueue item - * to re-enable poc_selector_active and resync stale bitmaps. - * - * WRITE_ONCE(false) is idempotent across concurrent callers; schedule_work() - * silently drops duplicate requests when the item is already queued. - */ -void poc_check_skip_fallback(void) -{ - if (!sched_poc_selector || !READ_ONCE(poc_selector_skip)) - return; - WRITE_ONCE(poc_selector_skip, false); - schedule_work(&poc_skip_fallback_work); -} -#endif - -#ifdef CONFIG_SYSCTL -static int sched_poc_sysctl_handler(const struct ctl_table *table, int write, - void *buffer, size_t *lenp, loff_t *ppos) -{ - unsigned int val = sched_poc_selector ? 1 : 0; - struct ctl_table tmp = { - .data = &val, - .maxlen = sizeof(val), - .extra1 = SYSCTL_ZERO, - .extra2 = SYSCTL_ONE, - }; - int ret = proc_douintvec_minmax(&tmp, write, buffer, lenp, ppos); - - if (!ret && write) { - cpus_read_lock(); - sched_poc_selector = !!val; - poc_reevaluate_active(); - cpus_read_unlock(); - } - return ret; -} - -static int sched_poc_smt_fallback_sysctl_handler(const struct ctl_table *table, - int write, void *buffer, - size_t *lenp, loff_t *ppos) -{ - unsigned int val = static_branch_unlikely(&sched_poc_smt_fallback) ? 1 : 0; - struct ctl_table tmp = { - .data = &val, - .maxlen = sizeof(val), - .extra1 = SYSCTL_ZERO, - .extra2 = SYSCTL_ONE, - }; - int ret = proc_douintvec_minmax(&tmp, write, buffer, lenp, ppos); - - if (!ret && write) { - if (val) - static_branch_enable(&sched_poc_smt_fallback); - else - static_branch_disable(&sched_poc_smt_fallback); - } - return ret; -} - -static int sched_poc_rr_improved_sysctl_handler(const struct ctl_table *table, - int write, void *buffer, - size_t *lenp, loff_t *ppos) -{ - unsigned int val = static_branch_likely(&sched_poc_rr_improved) ? 1 : 0; - struct ctl_table tmp = { - .data = &val, - .maxlen = sizeof(val), - .extra1 = SYSCTL_ZERO, - .extra2 = SYSCTL_ONE, - }; - int ret = proc_douintvec_minmax(&tmp, write, buffer, lenp, ppos); - - if (!ret && write) { - if (val) - static_branch_enable(&sched_poc_rr_improved); - else - static_branch_disable(&sched_poc_rr_improved); - } - return ret; -} - -static int sched_poc_target_sticky_sysctl_handler(const struct ctl_table *table, - int write, void *buffer, - size_t *lenp, loff_t *ppos) -{ - unsigned int val = static_branch_unlikely(&sched_poc_target_sticky) ? 1 : 0; - struct ctl_table tmp = { - .data = &val, - .maxlen = sizeof(val), - .extra1 = SYSCTL_ZERO, - .extra2 = SYSCTL_ONE, - }; - int ret = proc_douintvec_minmax(&tmp, write, buffer, lenp, ppos); - - if (!ret && write) { - if (val) - static_branch_enable(&sched_poc_target_sticky); - else - static_branch_disable(&sched_poc_target_sticky); - } - return ret; -} - -static int sched_poc_early_select_handler(const struct ctl_table *table, - int write, void *buffer, - size_t *lenp, loff_t *ppos) -{ - unsigned int val = static_branch_likely(&sched_poc_early_select) ? 1 : 0; - struct ctl_table tmp = { - .data = &val, - .maxlen = sizeof(val), - .extra1 = SYSCTL_ZERO, - .extra2 = SYSCTL_ONE, - }; - int ret = proc_douintvec_minmax(&tmp, write, buffer, lenp, ppos); - - if (!ret && write) { - if (val) - static_branch_enable(&sched_poc_early_select); - else - static_branch_disable(&sched_poc_early_select); - } - return ret; -} - -static int sched_poc_greedy_search_handler(const struct ctl_table *table, - int write, void *buffer, - size_t *lenp, loff_t *ppos) -{ - unsigned int val = static_branch_likely(&sched_poc_greedy_search) ? 1 : 0; - struct ctl_table tmp = { - .data = &val, - .maxlen = sizeof(val), - .extra1 = SYSCTL_ZERO, - .extra2 = SYSCTL_ONE, - }; - int ret = proc_douintvec_minmax(&tmp, write, buffer, lenp, ppos); - - if (!ret && write) { - if (val) - static_branch_enable(&sched_poc_greedy_search); - else - static_branch_disable(&sched_poc_greedy_search); - } - return ret; -} - -static int sched_poc_count_sysctl_handler(const struct ctl_table *table, - int write, void *buffer, - size_t *lenp, loff_t *ppos) -{ - unsigned int val = static_branch_unlikely(&sched_poc_count_enabled) ? 1 : 0; - struct ctl_table tmp = { - .data = &val, - .maxlen = sizeof(val), - .extra1 = SYSCTL_ZERO, - .extra2 = SYSCTL_ONE, - }; - int ret = proc_douintvec_minmax(&tmp, write, buffer, lenp, ppos); - - if (!ret && write) { - if (val) - static_branch_enable(&sched_poc_count_enabled); - else - static_branch_disable(&sched_poc_count_enabled); - } - return ret; -} - -static int sched_poc_lockless_bitmap_sysctl_handler(const struct ctl_table *table, - int write, void *buffer, - size_t *lenp, loff_t *ppos) -{ - unsigned int val = static_branch_unlikely(&sched_poc_lockless_bitmap) ? 1 : 0; - struct ctl_table tmp = { - .data = &val, - .maxlen = sizeof(val), - .extra1 = SYSCTL_ZERO, - .extra2 = SYSCTL_ONE, - }; - int ret = proc_douintvec_minmax(&tmp, write, buffer, lenp, ppos); - - if (!ret && write) { - cpus_read_lock(); - if (val) - static_branch_enable_cpuslocked(&sched_poc_lockless_bitmap); - else - static_branch_disable_cpuslocked(&sched_poc_lockless_bitmap); - /* - * Resync the newly-active representation so readers see - * consistent state immediately after the mode switch. - */ - poc_resync_idle_state(); - cpus_read_unlock(); - } - return ret; -} - -static struct ctl_table sched_poc_sysctls[] = { - { - .procname = "sched_poc_selector", - .data = NULL, - .maxlen = sizeof(unsigned int), - .mode = 0644, - .proc_handler = sched_poc_sysctl_handler, - }, - { - .procname = "sched_poc_smt_fallback", - .data = NULL, - .maxlen = sizeof(unsigned int), - .mode = 0644, - .proc_handler = sched_poc_smt_fallback_sysctl_handler, - }, - { - .procname = "sched_poc_rr_improved", - .data = NULL, - .maxlen = sizeof(unsigned int), - .mode = 0644, - .proc_handler = sched_poc_rr_improved_sysctl_handler, - }, - { - .procname = "sched_poc_target_sticky", - .data = NULL, - .maxlen = sizeof(unsigned int), - .mode = 0644, - .proc_handler = sched_poc_target_sticky_sysctl_handler, - }, - { - .procname = "sched_poc_early_select", - .data = NULL, - .maxlen = sizeof(unsigned int), - .mode = 0644, - .proc_handler = sched_poc_early_select_handler, - }, - { - .procname = "sched_poc_greedy_search", - .data = NULL, - .maxlen = sizeof(unsigned int), - .mode = 0644, - .proc_handler = sched_poc_greedy_search_handler, - }, - { - .procname = "sched_poc_count", - .data = NULL, - .maxlen = sizeof(unsigned int), - .mode = 0644, - .proc_handler = sched_poc_count_sysctl_handler, - }, - { - .procname = "sched_poc_lockless_bitmap", - .data = NULL, - .maxlen = sizeof(unsigned int), - .mode = 0644, - .proc_handler = sched_poc_lockless_bitmap_sysctl_handler, - }, -}; - -static int __init sched_poc_sysctl_init(void) -{ - printk(KERN_INFO "%s %s by %s [CTZ: %s, PTSelect: %s]\n", - SCHED_POC_SELECTOR_PROGNAME, SCHED_POC_SELECTOR_VERSION, - SCHED_POC_SELECTOR_AUTHOR, POC_CTZ64_NAME, POC_PTSELECT_NAME); - - register_sysctl_init("kernel", sched_poc_sysctls); - return 0; -} -late_initcall(sched_poc_sysctl_init); - -#endif /* CONFIG_SYSCTL */ - -/* - * Initialize per-CPU RR counters with CPU ID offset. - * Different starting values shift the FASTRANGE16 phase per CPU, - * reducing cross-CPU collision probability when multiple CPUs - * perform burst wakeups against the same idle bitmap snapshot. - */ -static int __init sched_poc_rr_init(void) -{ - int cpu; - - for_each_possible_cpu(cpu) - per_cpu(poc_rr_counter, cpu) = (u32)cpu; - return 0; -} -early_initcall(sched_poc_rr_init); - -/************************************************************** - * Status: sysfs interface (always available) - * - * Exported at /sys/kernel/poc_selector/status/ for runtime status queries. - * Reports whether POC is actually active (combining all conditions). - */ - -#ifdef CONFIG_SYSFS - -/* Root kobject shared with debug section */ -static struct kobject *kobj_poc_root; - -static bool poc_check_all_llc_eligible(void) -{ - int cpu; - - for_each_online_cpu(cpu) { - struct sched_domain_shared *sd_share; - - scoped_guard(rcu) { - sd_share = rcu_dereference(per_cpu(sd_llc_shared, cpu)); - if (sd_share && !sd_share->poc_fast_eligible) - return false; - } - } - return true; -} - -static ssize_t active_show(struct kobject *kobj, - struct kobj_attribute *attr, char *buf) -{ - bool active = static_branch_likely(&poc_selector_active) && - !sched_asym_cpucap_active() && - poc_check_all_llc_eligible(); - return sysfs_emit(buf, "%d\n", active ? 1 : 0); -} - -static ssize_t symmetric_cpucap_show(struct kobject *kobj, - struct kobj_attribute *attr, char *buf) -{ - return sysfs_emit(buf, "%d\n", sched_asym_cpucap_active() ? 0 : 1); -} - -static ssize_t all_llc_eligible_show(struct kobject *kobj, - struct kobj_attribute *attr, char *buf) -{ - return sysfs_emit(buf, "%d\n", poc_check_all_llc_eligible() ? 1 : 0); -} - -static ssize_t version_show(struct kobject *kobj, - struct kobj_attribute *attr, char *buf) -{ - return sysfs_emit(buf, "%s\n", SCHED_POC_SELECTOR_VERSION); -} - -static struct kobj_attribute poc_status_active_attr = __ATTR_RO(active); -static struct kobj_attribute poc_status_asym_attr = __ATTR_RO(symmetric_cpucap); -static struct kobj_attribute poc_status_eligible_attr = __ATTR_RO(all_llc_eligible); -static struct kobj_attribute poc_status_version_attr = __ATTR_RO(version); - -static struct attribute *poc_status_attrs[] = { - &poc_status_active_attr.attr, - &poc_status_asym_attr.attr, - &poc_status_eligible_attr.attr, - &poc_status_version_attr.attr, - NULL, -}; - -static const struct attribute_group poc_status_group = { - .name = "status", - .attrs = poc_status_attrs, -}; - -/* --- hw_accel: expose which hardware acceleration is in use --- */ - -#define DEFINE_POC_HW_ATTR(fname, namestr) \ -static ssize_t poc_hw_##fname##_show(struct kobject *kobj, \ - struct kobj_attribute *attr, char *buf) \ -{ \ - return sysfs_emit(buf, "%s\n", namestr); \ -} \ -static struct kobj_attribute poc_hw_attr_##fname = { \ - .attr = { .name = #fname, .mode = 0444 }, \ - .show = poc_hw_##fname##_show, \ -} - -DEFINE_POC_HW_ATTR(ctz, POC_CTZ64_NAME); -DEFINE_POC_HW_ATTR(ptselect, POC_PTSELECT_NAME); - -/* popcnt: x86 uses runtime alternatives, detect via boot_cpu_has */ -static ssize_t poc_hw_popcnt_show(struct kobject *kobj, - struct kobj_attribute *attr, char *buf) -{ -#if defined(__x86_64__) - return sysfs_emit(buf, "%s\n", - boot_cpu_has(X86_FEATURE_POPCNT) ? "HW (POPCNT)" : "SW"); -#elif defined(__aarch64__) - return sysfs_emit(buf, "HW (CNT)\n"); -#elif defined(__riscv) && defined(__riscv_zbb) - return sysfs_emit(buf, "HW (cpop)\n"); -#else - return sysfs_emit(buf, "SW\n"); -#endif -} - -static struct kobj_attribute poc_hw_attr_popcnt = { - .attr = { .name = "popcnt", .mode = 0444 }, - .show = poc_hw_popcnt_show, -}; - -static struct attribute *poc_hw_attrs[] = { - &poc_hw_attr_popcnt.attr, - &poc_hw_attr_ctz.attr, - &poc_hw_attr_ptselect.attr, - NULL, -}; - -static const struct attribute_group poc_hw_group = { - .name = "hw_accel", - .attrs = poc_hw_attrs, -}; - -/* --- count: per-level hit counters (sysctl kernel.sched_poc_count) --- */ - -static unsigned long poc_sum_level(enum poc_level lvl) -{ - unsigned long sum = 0; - int cpu; - - for_each_possible_cpu(cpu) - sum += per_cpu(poc_debug_cnt[lvl], cpu); - return sum; -} - -#define DEFINE_POC_COUNT_ATTR(fname, level) \ -static ssize_t poc_count_##fname##_show(struct kobject *kobj, \ - struct kobj_attribute *attr, char *buf) \ -{ \ - return sysfs_emit(buf, "%lu\n", poc_sum_level(level)); \ -} \ -static struct kobj_attribute poc_count_##fname##_attr = { \ - .attr = { .name = #fname, .mode = 0444 }, \ - .show = poc_count_##fname##_show, \ -} - -DEFINE_POC_COUNT_ATTR(l1s, POC_LV1S); -DEFINE_POC_COUNT_ATTR(l1t, POC_LV1T); -DEFINE_POC_COUNT_ATTR(l1p, POC_LV1P); -DEFINE_POC_COUNT_ATTR(l1r, POC_LV1R); -DEFINE_POC_COUNT_ATTR(l2, POC_LV2); -DEFINE_POC_COUNT_ATTR(l3, POC_LV3); -DEFINE_POC_COUNT_ATTR(l4s, POC_LV4S); -DEFINE_POC_COUNT_ATTR(l4p, POC_LV4P); -DEFINE_POC_COUNT_ATTR(l4r, POC_LV4R); -DEFINE_POC_COUNT_ATTR(l4t, POC_LV4T); -DEFINE_POC_COUNT_ATTR(l5, POC_LV5); -DEFINE_POC_COUNT_ATTR(l6, POC_LV6); -DEFINE_POC_COUNT_ATTR(fallback, POC_FALLBACK); - -static ssize_t poc_count_reset_store(struct kobject *kobj, - struct kobj_attribute *attr, - const char *buf, size_t count) -{ - int cpu; - - for_each_possible_cpu(cpu) - memset(per_cpu_ptr(poc_debug_cnt, cpu), 0, - sizeof(poc_debug_cnt)); - return count; -} - -static struct kobj_attribute poc_count_reset_attr = { - .attr = { .name = "reset", .mode = 0200 }, - .store = poc_count_reset_store, -}; - -static struct attribute *poc_count_attrs[] = { - &poc_count_l1s_attr.attr, - &poc_count_l1t_attr.attr, - &poc_count_l1p_attr.attr, - &poc_count_l1r_attr.attr, - &poc_count_l2_attr.attr, - &poc_count_l3_attr.attr, - &poc_count_l4s_attr.attr, - &poc_count_l4p_attr.attr, - &poc_count_l4r_attr.attr, - &poc_count_l4t_attr.attr, - &poc_count_l5_attr.attr, - &poc_count_l6_attr.attr, - &poc_count_fallback_attr.attr, - &poc_count_reset_attr.attr, - NULL, -}; - -static const struct attribute_group poc_count_group = { - .name = "count", - .attrs = poc_count_attrs, -}; - -static int __init sched_poc_status_init(void) -{ - int ret; - - kobj_poc_root = kobject_create_and_add("poc_selector", kernel_kobj); - if (!kobj_poc_root) - return -ENOMEM; - - ret = sysfs_create_group(kobj_poc_root, &poc_status_group); - if (ret) - goto err_status; - - ret = sysfs_create_group(kobj_poc_root, &poc_hw_group); - if (ret) - goto err_hw; - - ret = sysfs_create_group(kobj_poc_root, &poc_count_group); - if (ret) - goto err_selected; - - return 0; - -err_selected: - sysfs_remove_group(kobj_poc_root, &poc_hw_group); -err_hw: - sysfs_remove_group(kobj_poc_root, &poc_status_group); -err_status: - kobject_put(kobj_poc_root); - kobj_poc_root = NULL; - return ret; -} -late_initcall(sched_poc_status_init); - -#endif /* CONFIG_SYSFS */ -#endif /* CONFIG_SCHED_POC_SELECTOR */ diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index e25c5a986..4664869d8 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -1180,10 +1180,6 @@ struct rq { call_single_data_t nohz_csd; #endif /* CONFIG_NO_HZ_COMMON */ -#ifdef CONFIG_SCHED_POC_SELECTOR - unsigned int poc_idle_committed; -#endif - #ifdef CONFIG_UCLAMP_TASK /* Utilization clamp values based on CPU's RUNNABLE tasks */ struct uclamp_rq uclamp[UCLAMP_CNT] ____cacheline_aligned; @@ -2378,119 +2374,6 @@ static inline struct task_group *task_group(struct task_struct *p) #endif /* !CONFIG_CGROUP_SCHED */ -#ifdef CONFIG_SCHED_POC_SELECTOR -extern struct static_key_true poc_selector_active; -#ifdef CONFIG_SCHED_CLASS_EXT -extern void poc_notify_scx(bool scx_active); -extern void poc_check_skip_fallback(void); -#else -static inline void poc_check_skip_fallback(void) {} -#endif -extern struct static_key_true sched_poc_aligned; -extern struct static_key_true sched_poc_smt_consecutive; -extern struct static_key_true sched_poc_smt_uniform; -extern struct static_key_false sched_poc_target_sticky; -extern struct static_key_true sched_poc_packed; -extern struct static_key_false sched_poc_lockless_bitmap; -extern void __set_cpu_idle_state_poc(int cpu, int state); -extern void poc_sd_shared_init(struct sched_domain *sd, int sd_id); -static __always_inline void set_cpu_idle_state_poc(int cpu, int state) -{ - if (static_branch_likely(&poc_selector_active) && - !sched_asym_cpucap_active()) - __set_cpu_idle_state_poc(cpu, state); -} - -/* - * POC_CTZ64 - Count trailing zeros (find first set bit) - * - * Architecture-optimized CTZ for POC idle CPU selection. - * Returns 64 for input 0 (important for BSF-based implementations). - */ -#if defined(__x86_64__) && defined(__BMI__) -/* Tier 1: x86-64 with BMI1 - TZCNT is zero-safe */ -#define POC_CTZ64(v) ((int)__builtin_ctzll(v)) - -#elif defined(__aarch64__) -/* Tier 1: ARM64 - RBIT+CLZ is zero-safe */ -#define POC_CTZ64(v) ((int)__builtin_ctzll(v)) - -#elif defined(__riscv) && defined(__riscv_zbb) -/* Tier 1: RISC-V with Zbb - CTZ is zero-safe */ -#define POC_CTZ64(v) ((int)__builtin_ctzll(v)) - -#elif defined(__x86_64__) -/* Tier 2: x86-64 without BMI1 - BSF needs zero check */ -static __always_inline int poc_ctz64_bsf(u64 v) -{ - if (unlikely(!v)) - return 64; - return (int)__builtin_ctzll(v); -} -#define POC_CTZ64(v) poc_ctz64_bsf(v) - -#else -/* Tier 3: De Bruijn fallback for other architectures */ -#define POC_DEBRUIJN_CTZ64_CONST 0x03F79D71B4CA8B09ULL -static const u8 poc_debruijn_ctz64_tab[64] = { - 0, 1, 56, 2, 57, 49, 28, 3, - 61, 58, 42, 50, 38, 29, 17, 4, - 62, 47, 59, 36, 45, 43, 51, 22, - 53, 39, 33, 30, 24, 18, 12, 5, - 63, 55, 48, 27, 60, 41, 37, 16, - 46, 35, 44, 21, 52, 32, 23, 11, - 54, 26, 40, 15, 34, 20, 31, 10, - 25, 14, 19, 9, 13, 8, 7, 6, -}; -static __always_inline int poc_debruijn_ctz64(u64 v) -{ - u64 lsb; - u32 idx; - - if (unlikely(!v)) - return 64; - lsb = v & (-(s64)v); - idx = (u32)((lsb * POC_DEBRUIJN_CTZ64_CONST) >> 58); - return (int)poc_debruijn_ctz64_tab[idx & 63]; -} -#define POC_CTZ64(v) poc_debruijn_ctz64(v) - -#endif /* POC_CTZ64 */ - -/* - * POC helper: convert cpumask region to POC-relative u64 - * - * Extracts the 64-bit region of @mask corresponding to this LLC's - * CPU range and shifts it to align with POC's bit positions. - * - * Used by load balancer functions that need to intersect cpumasks - * with POC idle bitmaps. - */ -static __always_inline u64 poc_cpumask_to_u64(const struct cpumask *mask, - struct sched_domain_shared *sd_share) -{ - int base = sd_share->poc_cpu_base; - int base_word = base >> 6; - int shift = sd_share->poc_affinity_shift; - - if (likely(shift == 0)) { - /* - * Fast path: this LLC's own base is 64-aligned, so no - * cross-word shift is needed here even if some other LLC - * in the system is misaligned (sched_poc_aligned is a - * single system-wide flag and must not gate this branch). - */ - return cpumask_bits(mask)[base_word]; - } else { - /* Slow path: shift required (e.g., Threadripper) */ - u64 lo = cpumask_bits(mask)[base_word]; - u64 hi = (base_word + 1 < BITS_TO_LONGS(nr_cpumask_bits)) ? - cpumask_bits(mask)[base_word + 1] : 0; - return (lo >> shift) | (hi << (64 - shift)); - } -} -#endif /* CONFIG_SCHED_POC_SELECTOR */ - static inline void __set_task_cpu(struct task_struct *p, unsigned int cpu) { set_task_rq(p, cpu); @@ -3570,7 +3453,6 @@ extern void nohz_run_idle_balance(int cpu); static inline void nohz_run_idle_balance(int cpu) { } #endif - #include "stats.h" #if defined(CONFIG_SCHED_CORE) && defined(CONFIG_SCHEDSTATS) diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c index 339d505c8..622e2e019 100644 --- a/kernel/sched/topology.c +++ b/kernel/sched/topology.c @@ -3170,9 +3170,6 @@ build_sched_domains(const struct cpumask *cpu_map, struct sched_domain_attr *att if (sd->flags & SD_SHARE_LLC) { init_sched_domain_shared(&d, sd, SD_SHARE_LLC); -#ifdef CONFIG_SCHED_POC_SELECTOR - poc_sd_shared_init(sd, cpumask_first(sched_domain_span(sd))); -#endif /* * In presence of higher domains, adjust the -- 2.55.0 From 80ed9d1e8f33fee8d00184d7f907f2061ea111db Mon Sep 17 00:00:00 2001 From: Piotr Gorski Date: Mon, 17 Aug 2026 17:47:25 +0200 Subject: [PATCH 2/3] Revert gaming-sched Signed-off-by: Piotr Gorski --- include/linux/cpuset.h | 6 - include/linux/sched.h | 1 - kernel/cgroup/cpuset.c | 22 - kernel/sched/core.c | 5 +- kernel/sched/debug.c | 89 +--- kernel/sched/fair.c | 941 ++++++++++++++++++++--------------------- kernel/sched/pelt.c | 6 +- kernel/sched/sched.h | 30 +- 8 files changed, 487 insertions(+), 613 deletions(-) diff --git a/include/linux/cpuset.h b/include/linux/cpuset.h index 9db2d4fce..65d76a389 100644 --- a/include/linux/cpuset.h +++ b/include/linux/cpuset.h @@ -80,7 +80,6 @@ extern void lockdep_assert_cpuset_lock_held(void); extern void cpuset_cpus_allowed_locked(struct task_struct *p, struct cpumask *mask); extern void cpuset_cpus_allowed(struct task_struct *p, struct cpumask *mask); extern bool cpuset_cpus_allowed_fallback(struct task_struct *p); -extern int cpuset_num_cpus(struct cgroup *cgroup); extern nodemask_t cpuset_mems_allowed(struct task_struct *p); #define cpuset_current_mems_allowed (current->mems_allowed) void cpuset_init_current_mems_allowed(void); @@ -217,11 +216,6 @@ static inline bool cpuset_cpus_allowed_fallback(struct task_struct *p) return false; } -static inline int cpuset_num_cpus(struct cgroup *cgroup) -{ - return num_online_cpus(); -} - static inline nodemask_t cpuset_mems_allowed(struct task_struct *p) { return node_possible_map; diff --git a/include/linux/sched.h b/include/linux/sched.h index 63536356d..5738c54eb 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -575,7 +575,6 @@ struct sched_statistics { struct sched_entity { /* For load-balancing: */ struct load_weight load; - struct load_weight h_load; struct rb_node run_node; u64 deadline; u64 min_vruntime; diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index e42f81a61..45944b3e3 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -4129,28 +4129,6 @@ bool cpuset_cpus_allowed_fallback(struct task_struct *tsk) return changed; } -/* - * Returns the number of CPUs available for this cgroup. - * - * This only really works for cgroup-v2 where all the controllers are mounted - * in the same hierarchy. If not cgroup-v2 or no cpuset controller is - * configured it reverts to num_online_cpus(). - */ -int cpuset_num_cpus(struct cgroup *cgrp) -{ - int nr = num_online_cpus(); - struct cpuset *cs; - - if (is_in_v2_mode()) { - guard(rcu)(); - cs = css_cs(cgroup_e_css(cgrp, &cpuset_cgrp_subsys)); - if (cs) - nr = cpumask_weight(cs->effective_cpus); - } - - return nr; -} - void __init cpuset_init_current_mems_allowed(void) { nodes_setall(current->mems_allowed); diff --git a/kernel/sched/core.c b/kernel/sched/core.c index edd1a3fc7..e55a8f257 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -5660,8 +5660,11 @@ EXPORT_PER_CPU_SYMBOL(kernel_cpustat); */ static inline void prefetch_curr_exec_start(struct task_struct *p) { +#ifdef CONFIG_FAIR_GROUP_SCHED + struct sched_entity *curr = p->se.cfs_rq->curr; +#else struct sched_entity *curr = task_rq(p)->cfs.curr; - +#endif prefetch(curr); prefetch(&curr->exec_start); } diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c index c15291cbc..40584b27e 100644 --- a/kernel/sched/debug.c +++ b/kernel/sched/debug.c @@ -633,82 +633,6 @@ static void debugfs_fair_server_init(void) } } -#ifdef CONFIG_FAIR_GROUP_SCHED -static int cgroup_mode = 2; - -/* See __sched_cgroup_mode_update(). */ -static const char *cgroup_mode_str[] = { - "up", - "smp", - "concur", - "max", - "tasks", -}; - -static int sched_cgroup_mode(const char *str) -{ - for (int i = 0; i < ARRAY_SIZE(cgroup_mode_str); i++) { - if (!strcmp(str, cgroup_mode_str[i])) - return i; - } - return -EINVAL; -} - -static ssize_t sched_cgroup_write(struct file *filp, const char __user *ubuf, - size_t cnt, loff_t *ppos) -{ - char buf[16]; - int mode; - - if (cnt > 15) - cnt = 15; - - if (copy_from_user(buf, ubuf, cnt)) - return -EFAULT; - - buf[cnt] = 0; - mode = sched_cgroup_mode(strstrip(buf)); - if (mode < 0) - return mode; - - __sched_cgroup_mode_update(mode); - WRITE_ONCE(cgroup_mode, mode); - - *ppos += cnt; - return cnt; -} - -static int sched_cgroup_show(struct seq_file *m, void *v) -{ - int mode = READ_ONCE(cgroup_mode); - - for (int i = 0; i < ARRAY_SIZE(cgroup_mode_str); i++) { - if (mode == i) - seq_puts(m, "("); - seq_puts(m, cgroup_mode_str[i]); - if (mode == i) - seq_puts(m, ")"); - - seq_puts(m, " "); - } - seq_puts(m, "\n"); - return 0; -} - -static int sched_cgroup_open(struct inode *inode, struct file *filp) -{ - return single_open(filp, sched_cgroup_show, NULL); -} - -static const struct file_operations sched_cgroup_fops = { - .open = sched_cgroup_open, - .write = sched_cgroup_write, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; -#endif - static __init int sched_init_debug(void) { struct dentry __maybe_unused *numa, *llc; @@ -762,10 +686,6 @@ static __init int sched_init_debug(void) debugfs_create_file("debug", 0444, debugfs_sched, NULL, &sched_debug_fops); -#ifdef CONFIG_FAIR_GROUP_SCHED - debugfs_create_file("cgroup_mode", 0644, debugfs_sched, NULL, &sched_cgroup_fops); -#endif - debugfs_fair_server_init(); #ifdef CONFIG_SCHED_CLASS_EXT debugfs_ext_server_init(); @@ -975,11 +895,10 @@ print_task(struct seq_file *m, struct rq *rq, struct task_struct *p) else SEQ_printf(m, " %c", task_state_to_char(p)); - SEQ_printf(m, " %15s %5d %10ld %9Ld.%06ld %c %9Ld.%06ld %c %9Ld.%06ld %9Ld.%06ld %9Ld %5d ", + SEQ_printf(m, " %15s %5d %9Ld.%06ld %c %9Ld.%06ld %c %9Ld.%06ld %9Ld.%06ld %9Ld %5d ", p->comm, task_pid_nr(p), - p->se.h_load.weight, SPLIT_NS(p->se.vruntime), - entity_eligible(&rq->cfs, &p->se) ? 'E' : 'N', + entity_eligible(cfs_rq_of(&p->se), &p->se) ? 'E' : 'N', SPLIT_NS(p->se.deadline), p->se.custom_slice ? 'S' : ' ', SPLIT_NS(p->se.slice), @@ -1008,7 +927,7 @@ static void print_rq(struct seq_file *m, struct rq *rq, int rq_cpu) SEQ_printf(m, "\n"); SEQ_printf(m, "runnable tasks:\n"); - SEQ_printf(m, " S task PID weight vruntime eligible " + SEQ_printf(m, " S task PID vruntime eligible " "deadline slice sum-exec switches " "prio wait-time sum-sleep sum-block" #ifdef CONFIG_NUMA_BALANCING @@ -1116,8 +1035,6 @@ void print_cfs_rq(struct seq_file *m, int cpu, struct cfs_rq *cfs_rq) cfs_rq->tg_load_avg_contrib); SEQ_printf(m, " .%-30s: %ld\n", "tg_load_avg", atomic_long_read(&cfs_rq->tg->load_avg)); - SEQ_printf(m, " .%-30s: %lu\n", "h_load", - cfs_rq->h_load); #endif /* CONFIG_FAIR_GROUP_SCHED */ #ifdef CONFIG_CFS_BANDWIDTH SEQ_printf(m, " .%-30s: %d\n", "throttled", diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index d309809ec..bf5c9a01c 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -38,7 +38,6 @@ #include #include #include -#include #include #include @@ -306,8 +305,8 @@ static u64 __calc_delta(u64 delta_exec, unsigned long weight, struct load_weight */ static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se) { - if (se->h_load.weight != NICE_0_LOAD) - delta = __calc_delta(delta, NICE_0_LOAD, &se->h_load); + if (unlikely(se->load.weight != NICE_0_LOAD)) + delta = __calc_delta(delta, NICE_0_LOAD, &se->load); return delta; } @@ -437,6 +436,38 @@ static inline struct sched_entity *parent_entity(const struct sched_entity *se) return se->parent; } +static void +find_matching_se(struct sched_entity **se, struct sched_entity **pse) +{ + int se_depth, pse_depth; + + /* + * preemption test can be made between sibling entities who are in the + * same cfs_rq i.e who have a common parent. Walk up the hierarchy of + * both tasks until we find their ancestors who are siblings of common + * parent. + */ + + /* First walk up until both entities are at same depth */ + se_depth = (*se)->depth; + pse_depth = (*pse)->depth; + + while (se_depth > pse_depth) { + se_depth--; + *se = parent_entity(*se); + } + + while (pse_depth > se_depth) { + pse_depth--; + *pse = parent_entity(*pse); + } + + while (!is_same_group(*se, *pse)) { + *se = parent_entity(*se); + *pse = parent_entity(*pse); + } +} + static int tg_is_idle(struct task_group *tg) { return tg->idle > 0; @@ -480,6 +511,11 @@ static inline struct sched_entity *parent_entity(struct sched_entity *se) return NULL; } +static inline void +find_matching_se(struct sched_entity **se, struct sched_entity **pse) +{ +} + static inline int tg_is_idle(struct task_group *tg) { return 0; @@ -658,7 +694,7 @@ static inline unsigned long avg_vruntime_weight(struct cfs_rq *cfs_rq, unsigned static inline void __sum_w_vruntime_add(struct cfs_rq *cfs_rq, struct sched_entity *se) { - unsigned long weight = avg_vruntime_weight(cfs_rq, se->h_load.weight); + unsigned long weight = avg_vruntime_weight(cfs_rq, se->load.weight); s64 w_vruntime, key = entity_key(cfs_rq, se); w_vruntime = key * weight; @@ -675,7 +711,7 @@ sum_w_vruntime_add_paranoid(struct cfs_rq *cfs_rq, struct sched_entity *se) s64 key, tmp; again: - weight = avg_vruntime_weight(cfs_rq, se->h_load.weight); + weight = avg_vruntime_weight(cfs_rq, se->load.weight); key = entity_key(cfs_rq, se); if (check_mul_overflow(key, weight, &key)) @@ -721,7 +757,7 @@ sum_w_vruntime_add(struct cfs_rq *cfs_rq, struct sched_entity *se) static void sum_w_vruntime_sub(struct cfs_rq *cfs_rq, struct sched_entity *se) { - unsigned long weight = avg_vruntime_weight(cfs_rq, se->h_load.weight); + unsigned long weight = avg_vruntime_weight(cfs_rq, se->load.weight); s64 key = entity_key(cfs_rq, se); cfs_rq->sum_w_vruntime -= key * weight; @@ -763,7 +799,7 @@ u64 avg_vruntime(struct cfs_rq *cfs_rq) s64 runtime = cfs_rq->sum_w_vruntime; if (curr) { - unsigned long w = avg_vruntime_weight(cfs_rq, curr->h_load.weight); + unsigned long w = avg_vruntime_weight(cfs_rq, curr->load.weight); runtime += entity_key(cfs_rq, curr) * w; weight += w; @@ -834,6 +870,8 @@ bool update_entity_lag(struct cfs_rq *cfs_rq, struct sched_entity *se) u64 avruntime = avg_vruntime(cfs_rq); s64 vlag = entity_lag(cfs_rq, se, avruntime); + WARN_ON_ONCE(!se->on_rq); + if (se->sched_delayed) { /* previous vlag < 0 otherwise se would not be delayed */ vlag = max(vlag, se->vlag); @@ -869,7 +907,7 @@ static int vruntime_eligible(struct cfs_rq *cfs_rq, u64 vruntime) long load = cfs_rq->sum_weight; if (curr && curr->on_rq) { - unsigned long weight = avg_vruntime_weight(cfs_rq, curr->h_load.weight); + unsigned long weight = avg_vruntime_weight(cfs_rq, curr->load.weight); avg += entity_key(cfs_rq, curr) * weight; load += weight; @@ -1010,9 +1048,6 @@ RB_DECLARE_CALLBACKS(static, min_vruntime_cb, struct sched_entity, */ static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) { - WARN_ON_ONCE(&rq_of(cfs_rq)->cfs != cfs_rq); - WARN_ON_ONCE(!entity_is_task(se)); - sum_w_vruntime_add(cfs_rq, se); se->min_vruntime = se->vruntime; se->min_slice = se->slice; @@ -1022,9 +1057,6 @@ static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) static void __dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) { - WARN_ON_ONCE(&rq_of(cfs_rq)->cfs != cfs_rq); - WARN_ON_ONCE(!entity_is_task(se)); - rb_erase_augmented_cached(&se->run_node, &cfs_rq->tasks_timeline, &min_vruntime_cb); sum_w_vruntime_sub(cfs_rq, se); @@ -1121,7 +1153,7 @@ static struct sched_entity *pick_eevdf(struct cfs_rq *cfs_rq, bool protect) * We can safely skip eligibility check if there is only one entity * in this cfs_rq, saving some cycles. */ - if (cfs_rq->h_nr_queued == 1) + if (cfs_rq->nr_queued == 1) return curr && curr->on_rq ? curr : se; /* @@ -1371,6 +1403,8 @@ static s64 update_se(struct rq *rq, struct sched_entity *se) return delta_exec; } +static void set_next_buddy(struct sched_entity *se); + #ifdef CONFIG_SCHED_CACHE /* @@ -1965,7 +1999,7 @@ static void update_curr(struct cfs_rq *cfs_rq) * not necessarily be the actual task running * (rq->curr.se). This is easy to confuse! */ - struct sched_entity *curr = cfs_rq->h_curr; + struct sched_entity *curr = cfs_rq->curr; struct rq *rq = rq_of(cfs_rq); s64 delta_exec; bool resched; @@ -1977,29 +2011,26 @@ static void update_curr(struct cfs_rq *cfs_rq) if (unlikely(delta_exec <= 0)) return; - account_cfs_rq_runtime(cfs_rq, delta_exec); - - if (!entity_is_task(curr)) - return; - - cfs_rq = &rq->cfs; - curr->vruntime += calc_delta_fair(delta_exec, curr); resched = update_deadline(cfs_rq, curr); - /* - * If the fair_server is active, we need to account for the - * fair_server time whether or not the task is running on - * behalf of fair_server or not: - * - If the task is running on behalf of fair_server, we need - * to limit its time based on the assigned runtime. - * - Fair task that runs outside of fair_server should account - * against fair_server such that it can account for this time - * and possibly avoid running this period. - */ - dl_server_update(&rq->fair_server, delta_exec); + if (entity_is_task(curr)) { + /* + * If the fair_server is active, we need to account for the + * fair_server time whether or not the task is running on + * behalf of fair_server or not: + * - If the task is running on behalf of fair_server, we need + * to limit its time based on the assigned runtime. + * - Fair task that runs outside of fair_server should account + * against fair_server such that it can account for this time + * and possibly avoid running this period. + */ + dl_server_update(&rq->fair_server, delta_exec); + } - if (cfs_rq->h_nr_queued == 1) + account_cfs_rq_runtime(cfs_rq, delta_exec); + + if (cfs_rq->nr_queued == 1) return; if (resched || !protect_slice(curr)) { @@ -2010,10 +2041,7 @@ static void update_curr(struct cfs_rq *cfs_rq) static void update_curr_fair(struct rq *rq) { - struct sched_entity *se = &rq->donor->se; - - for_each_sched_entity(se) - update_curr(cfs_rq_of(se)); + update_curr(cfs_rq_of(&rq->donor->se)); } static inline void @@ -2089,7 +2117,7 @@ update_stats_enqueue_fair(struct cfs_rq *cfs_rq, struct sched_entity *se, int fl * Are we enqueueing a waiting task? (for current tasks * a dequeue/enqueue event is a NOP) */ - if (se != cfs_rq->h_curr) + if (se != cfs_rq->curr) update_stats_wait_start_fair(cfs_rq, se); if (flags & ENQUEUE_WAKEUP) @@ -2107,7 +2135,7 @@ update_stats_dequeue_fair(struct cfs_rq *cfs_rq, struct sched_entity *se, int fl * Mark the end of the wait period if dequeueing a * waiting task: */ - if (se != cfs_rq->h_curr) + if (se != cfs_rq->curr) update_stats_wait_end_fair(cfs_rq, se); if ((flags & DEQUEUE_SLEEP) && entity_is_task(se)) { @@ -4448,7 +4476,6 @@ static inline void update_scan_period(struct task_struct *p, int new_cpu) static void account_entity_enqueue(struct cfs_rq *cfs_rq, struct sched_entity *se) { - WARN_ON_ONCE(cfs_rq != cfs_rq_of(se)); update_load_add(&cfs_rq->load, se->load.weight); if (entity_is_task(se)) { struct task_struct *p = task_of(se); @@ -4464,7 +4491,6 @@ account_entity_enqueue(struct cfs_rq *cfs_rq, struct sched_entity *se) static void account_entity_dequeue(struct cfs_rq *cfs_rq, struct sched_entity *se) { - WARN_ON_ONCE(cfs_rq != cfs_rq_of(se)); update_load_sub(&cfs_rq->load, se->load.weight); if (entity_is_task(se)) { struct task_struct *p = task_of(se); @@ -4546,7 +4572,7 @@ dequeue_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se) static void rescale_entity(struct sched_entity *se, unsigned long weight, bool rel_vprot) { - long old_weight = se->h_load.weight; + unsigned long old_weight = se->load.weight; /* * VRUNTIME @@ -4646,17 +4672,16 @@ rescale_entity(struct sched_entity *se, unsigned long weight, bool rel_vprot) se->vprot = div64_long(se->vprot * old_weight, weight); } -static void reweight_eevdf(struct cfs_rq *cfs_rq, struct sched_entity *se, - unsigned long weight, bool on_rq) +static void reweight_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, + unsigned long weight) { bool curr = cfs_rq->curr == se; bool rel_vprot = false; u64 avruntime = 0; - if (se->h_load.weight == weight) - return; - - if (on_rq) { + if (se->on_rq) { + /* commit outstanding execution time */ + update_curr(cfs_rq); avruntime = avg_vruntime(cfs_rq); se->vlag = entity_lag(cfs_rq, se, avruntime); se->deadline -= avruntime; @@ -4666,40 +4691,15 @@ static void reweight_eevdf(struct cfs_rq *cfs_rq, struct sched_entity *se, rel_vprot = true; } - cfs_rq->h_nr_queued--; + cfs_rq->nr_queued--; if (!curr) __dequeue_entity(cfs_rq, se); - } - - rescale_entity(se, weight, rel_vprot); - - update_load_set(&se->h_load, weight); - - if (on_rq) { - if (rel_vprot) - se->vprot += avruntime; - se->deadline += avruntime; - se->rel_deadline = 0; - se->vruntime = avruntime - se->vlag; - - if (!curr) - __enqueue_entity(cfs_rq, se); - cfs_rq->h_nr_queued++; - } -} - -static void reweight_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, - unsigned long weight) -{ - if (se->load.weight == weight) - return; - - if (se->on_rq) { - WARN_ON_ONCE(cfs_rq != cfs_rq_of(se)); update_load_sub(&cfs_rq->load, se->load.weight); } dequeue_load_avg(cfs_rq, se); + rescale_entity(se, weight, rel_vprot); + update_load_set(&se->load, weight); do { @@ -4708,48 +4708,29 @@ static void reweight_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, } while (0); enqueue_load_avg(cfs_rq, se); + if (se->on_rq) { + if (rel_vprot) + se->vprot += avruntime; + se->deadline += avruntime; + se->rel_deadline = 0; + se->vruntime = avruntime - se->vlag; - if (se->on_rq) update_load_add(&cfs_rq->load, se->load.weight); -} - -/* - * weight = NICE_0_LOAD; - * for_each_entity_se(se) - * weight = __calc_prop_weight(cfs_rq_of(se), se, weight); - */ -static __always_inline -unsigned long __calc_prop_weight(struct cfs_rq *cfs_rq, struct sched_entity *se, - unsigned long weight) -{ - weight *= se->load.weight; - if (parent_entity(se)) - weight /= cfs_rq->load.weight; - else - weight /= NICE_0_LOAD; - - return max(weight, MIN_SHARES); + if (!curr) + __enqueue_entity(cfs_rq, se); + cfs_rq->nr_queued++; + } } static void reweight_task_fair(struct rq *rq, struct task_struct *p, const struct load_weight *lw) { struct sched_entity *se = &p->se; - unsigned long weight = NICE_0_LOAD; + struct cfs_rq *cfs_rq = cfs_rq_of(se); + struct load_weight *load = &se->load; - if (se->on_rq) - update_curr_fair(rq); - - reweight_entity(cfs_rq_of(se), se, lw->weight); - se->load.inv_weight = lw->inv_weight; - - if (!se->on_rq) - return; - - for_each_sched_entity(se) - weight = __calc_prop_weight(cfs_rq_of(se), se, weight); - - reweight_eevdf(&rq->cfs, &p->se, weight, p->se.on_rq); + reweight_entity(cfs_rq, se, lw->weight); + load->inv_weight = lw->inv_weight; } static inline int throttled_hierarchy(struct cfs_rq *cfs_rq); @@ -4828,10 +4809,12 @@ static inline int throttled_hierarchy(struct cfs_rq *cfs_rq); * * hence icky! */ -static long __calc_smp_shares(struct cfs_rq *cfs_rq, long tg_shares, long shares_max) +static long calc_group_shares(struct cfs_rq *cfs_rq) { + long tg_weight, tg_shares, load, shares; struct task_group *tg = cfs_rq->tg; - long tg_weight, load, shares; + + tg_shares = READ_ONCE(tg->shares); load = max(scale_load_down(cfs_rq->load.weight), cfs_rq->avg.load_avg); @@ -4857,115 +4840,7 @@ static long __calc_smp_shares(struct cfs_rq *cfs_rq, long tg_shares, long shares * case no task is runnable on a CPU MIN_SHARES=2 should be returned * instead of 0. */ - return clamp_t(long, shares, MIN_SHARES, shares_max); -} - -static int tg_cpus(struct task_group *tg) -{ - int nr = num_online_cpus(); - - if (cpusets_enabled()) { - struct cgroup *cgrp = tg->css.cgroup; - if (cgrp) - nr = cpuset_num_cpus(cgrp); - } - - return nr; -} - -static inline int tg_tasks(struct task_group *tg) -{ - return max(1, atomic_long_read(&tg->runnable_avg) >> SCHED_CAPACITY_SHIFT); -} - -/* - * Func: fraction(nr_tasks * tg->shares) - * - * Scale tg->shares by the number of tasks. - */ -static long calc_tasks_shares(struct cfs_rq *cfs_rq) -{ - struct task_group *tg = cfs_rq->tg; - int nr = tg_tasks(tg); - long tg_shares = READ_ONCE(tg->shares); - return __calc_smp_shares(cfs_rq, nr * tg_shares, nr * tg_shares); -} - -/* - * Func: min(fraction(nr_cpus * tg->shares), nice -20) - * - * Scale tg->shares by the maximal number of CPUs; but clip the max shares at - * nice -20, otherwise a single spinner on a 512 CPU machine would result in - * 512*NICE_0_LOAD, which is also crazy. - */ -static long calc_max_shares(struct cfs_rq *cfs_rq) -{ - struct task_group *tg = cfs_rq->tg; - int nr = tg_cpus(tg); - long tg_shares = READ_ONCE(tg->shares); - long max_shares = scale_load(sched_prio_to_weight[0]); - return __calc_smp_shares(cfs_rq, tg_shares * nr, max_shares); -} - -/* - * Func: fraction(nr * tg->shares); nr = min(nr_tasks, nr_cpus) - * - * Scales between "smp" and "max" in a natural way. No longer needs clipping - * since there are no unnatural inflations like with "max". - */ -static long calc_concur_shares(struct cfs_rq *cfs_rq) -{ - struct task_group *tg = cfs_rq->tg; - int nr = min(tg_tasks(tg), tg_cpus(tg)); - long tg_shares = READ_ONCE(tg->shares); - return __calc_smp_shares(cfs_rq, nr * tg_shares, nr * tg_shares); -} - -/* - * Func: fraction(tg->shares) - * - * This infamously results in tiny shares when you have many CPUs. - */ -static long calc_smp_shares(struct cfs_rq *cfs_rq) -{ - struct task_group *tg = cfs_rq->tg; - long tg_shares = READ_ONCE(tg->shares); - return __calc_smp_shares(cfs_rq, tg_shares, tg_shares); -} - -/* - * Ignore this pesky SMP stuff, use (4). - */ -static long calc_up_shares(struct cfs_rq *cfs_rq) -{ - struct task_group *tg = cfs_rq->tg; - return READ_ONCE(tg->shares); -} - -DEFINE_STATIC_CALL(calc_group_shares, calc_concur_shares); - -void __sched_cgroup_mode_update(int mode) -{ - long (*func)(struct cfs_rq *); - switch (mode) { - case 0: - func = &calc_up_shares; - break; - case 1: - func = &calc_smp_shares; - break; - case 2: - default: - func = &calc_concur_shares; - break; - case 3: - func = &calc_max_shares; - break; - case 4: - func = &calc_tasks_shares; - break; - } - static_call_update(calc_group_shares, func); + return clamp_t(long, shares, MIN_SHARES, tg_shares); } /* @@ -4984,8 +4859,9 @@ static void update_cfs_group(struct sched_entity *se) if (!gcfs_rq || !gcfs_rq->load.weight) return; - shares = static_call(calc_group_shares)(gcfs_rq); - reweight_entity(cfs_rq_of(se), se, shares); + shares = calc_group_shares(gcfs_rq); + if (unlikely(se->load.weight != shares)) + reweight_entity(cfs_rq_of(se), se, shares); } #else /* !CONFIG_FAIR_GROUP_SCHED: */ @@ -5103,11 +4979,11 @@ static inline bool cfs_rq_is_decayed(struct cfs_rq *cfs_rq) * differential update where we store the last value we propagated. This in * turn allows skipping updates if the differential is 'small'. * - * Updating tg's load_avg is necessary before update_cfs_group(). + * Updating tg's load_avg is necessary before update_cfs_share(). */ static inline void update_tg_load_avg(struct cfs_rq *cfs_rq) { - long dl, dr; + long delta; u64 now; /* @@ -5128,21 +5004,17 @@ static inline void update_tg_load_avg(struct cfs_rq *cfs_rq) if (now - cfs_rq->last_update_tg_load_avg < NSEC_PER_MSEC) return; - dl = cfs_rq->avg.load_avg - cfs_rq->tg_load_avg_contrib; - dr = cfs_rq->avg.runnable_avg - cfs_rq->tg_runnable_avg_contrib; - if (abs(dl) > cfs_rq->tg_load_avg_contrib / 64 || - abs(dr) > cfs_rq->tg_runnable_avg_contrib / 64) { - atomic_long_add(dl, &cfs_rq->tg->load_avg); - atomic_long_add(dr, &cfs_rq->tg->runnable_avg); + delta = cfs_rq->avg.load_avg - cfs_rq->tg_load_avg_contrib; + if (abs(delta) > cfs_rq->tg_load_avg_contrib / 64) { + atomic_long_add(delta, &cfs_rq->tg->load_avg); cfs_rq->tg_load_avg_contrib = cfs_rq->avg.load_avg; - cfs_rq->tg_runnable_avg_contrib = cfs_rq->avg.runnable_avg; cfs_rq->last_update_tg_load_avg = now; } } static inline void clear_tg_load_avg(struct cfs_rq *cfs_rq) { - long dl, dr; + long delta; u64 now; /* @@ -5152,12 +5024,9 @@ static inline void clear_tg_load_avg(struct cfs_rq *cfs_rq) return; now = rq_clock(rq_of(cfs_rq)); - dl = 0 - cfs_rq->tg_load_avg_contrib; - dr = 0 - cfs_rq->tg_runnable_avg_contrib; - atomic_long_add(dl, &cfs_rq->tg->load_avg); - atomic_long_add(dr, &cfs_rq->tg->runnable_avg); + delta = 0 - cfs_rq->tg_load_avg_contrib; + atomic_long_add(delta, &cfs_rq->tg->load_avg); cfs_rq->tg_load_avg_contrib = 0; - cfs_rq->tg_runnable_avg_contrib = 0; cfs_rq->last_update_tg_load_avg = now; } @@ -5570,7 +5439,7 @@ static void migrate_se_pelt_lag(struct sched_entity *se) {} * The cfs_rq avg is the direct sum of all its entities (blocked and runnable) * avg. The immediate corollary is that all (fair) tasks must be attached. * - * cfs_rq->avg is used for task_h_load() and update_cfs_group() for example. + * cfs_rq->avg is used for task_h_load() and update_cfs_share() for example. * * Return: true if the load decayed or we removed load. * @@ -6108,7 +5977,6 @@ static void place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) { u64 vslice, vruntime = avg_vruntime(cfs_rq); - unsigned int nr_queued = cfs_rq->h_nr_queued; bool update_zero = false; s64 lag = 0; @@ -6116,9 +5984,6 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) se->slice = sysctl_sched_base_slice; vslice = calc_delta_fair(se->slice, se); - if (flags & ENQUEUE_QUEUED) - nr_queued -= 1; - /* * Due to how V is constructed as the weighted average of entities, * adding tasks with positive lag, or removing tasks with negative lag @@ -6127,7 +5992,7 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) * * EEVDF: placement strategy #1 / #2 */ - if (sched_feat(PLACE_LAG) && nr_queued && se->vlag) { + if (sched_feat(PLACE_LAG) && cfs_rq->nr_queued && se->vlag) { struct sched_entity *curr = cfs_rq->curr; long load, weight; @@ -6187,9 +6052,9 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) */ load = cfs_rq->sum_weight; if (curr && curr->on_rq) - load += avg_vruntime_weight(cfs_rq, curr->h_load.weight); + load += avg_vruntime_weight(cfs_rq, curr->load.weight); - weight = avg_vruntime_weight(cfs_rq, se->h_load.weight); + weight = avg_vruntime_weight(cfs_rq, se->load.weight); lag *= load + weight; if (WARN_ON_ONCE(!load)) load = 1; @@ -6247,9 +6112,23 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) static void check_enqueue_throttle(struct cfs_rq *cfs_rq); static inline int cfs_rq_throttled(struct cfs_rq *cfs_rq); +static void +requeue_delayed_entity(struct sched_entity *se); + static void enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) { + bool curr = cfs_rq->curr == se; + + /* + * If we're the current task, we must renormalise before calling + * update_curr(). + */ + if (curr) + place_entity(cfs_rq, se, flags); + + update_curr(cfs_rq); + /* * When enqueuing a sched_entity, we must: * - Update loads to have both entity and cfs_rq synced with now. @@ -6268,6 +6147,13 @@ enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) */ update_cfs_group(se); + /* + * XXX now that the entity has been re-weighted, and it's lag adjusted, + * we can place the entity. + */ + if (!curr) + place_entity(cfs_rq, se, flags); + account_entity_enqueue(cfs_rq, se); /* Entity has migrated, no longer consider this task hot */ @@ -6276,6 +6162,8 @@ enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) check_schedstat_required(); update_stats_enqueue_fair(cfs_rq, se, flags); + if (!curr) + __enqueue_entity(cfs_rq, se); se->on_rq = 1; if (cfs_rq->nr_queued == 1) { @@ -6293,19 +6181,21 @@ enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) } } -static void set_next_buddy(struct cfs_rq *cfs_rq, struct sched_entity *se) +static void __clear_buddies_next(struct sched_entity *se) { - if (WARN_ON_ONCE(!se->on_rq || se->sched_delayed)) - return; - if (se_is_idle(se)) - return; - cfs_rq->next = se; + for_each_sched_entity(se) { + struct cfs_rq *cfs_rq = cfs_rq_of(se); + if (cfs_rq->next != se) + break; + + cfs_rq->next = NULL; + } } static void clear_buddies(struct cfs_rq *cfs_rq, struct sched_entity *se) { if (cfs_rq->next == se) - cfs_rq->next = NULL; + __clear_buddies_next(se); } static __always_inline void return_cfs_rq_runtime(struct cfs_rq *cfs_rq); @@ -6316,7 +6206,7 @@ static void set_delayed(struct sched_entity *se) /* * Delayed se of cfs_rq have no tasks queued on them. - * Do not adjust h_nr_runnable since __dequeue_task() + * Do not adjust h_nr_runnable since dequeue_entities() * will account it for blocked tasks. */ if (!entity_is_task(se)) @@ -6349,16 +6239,45 @@ static void clear_delayed(struct sched_entity *se) } } -static void +static bool dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) { - int action = UPDATE_TG; + bool sleep = flags & DEQUEUE_SLEEP; + int action = 0; + update_curr(cfs_rq); + clear_buddies(cfs_rq, se); + + if (flags & DEQUEUE_DELAYED) { + WARN_ON_ONCE(!se->sched_delayed); + } else { + bool delay = sleep; + /* + * DELAY_DEQUEUE relies on spurious wakeups, special task + * states must not suffer spurious wakeups, excempt them. + */ + if (flags & (DEQUEUE_SPECIAL | DEQUEUE_THROTTLE)) + delay = false; + + WARN_ON_ONCE(delay && se->sched_delayed); + + if (sched_feat(DELAY_DEQUEUE) && delay && + !entity_eligible(cfs_rq, se)) { + if (entity_is_task(se)) + action |= UPDATE_UTIL_EST; + update_load_avg(cfs_rq, se, action); + update_entity_lag(cfs_rq, se); + set_delayed(se); + return false; + } + } + + action = UPDATE_TG; if (entity_is_task(se)) { if (task_on_rq_migrating(task_of(se))) action |= DO_DETACH; - if ((flags & DEQUEUE_SLEEP) && !(flags & DEQUEUE_DELAYED)) + if (sleep && !(flags & DEQUEUE_DELAYED)) action |= UPDATE_UTIL_EST; } @@ -6376,6 +6295,14 @@ dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) update_stats_dequeue_fair(cfs_rq, se, flags); + update_entity_lag(cfs_rq, se); + if (sched_feat(PLACE_REL_DEADLINE) && !sleep) { + se->deadline -= se->vruntime; + se->rel_deadline = 1; + } + + if (se != cfs_rq->curr) + __dequeue_entity(cfs_rq, se); se->on_rq = 0; account_entity_dequeue(cfs_rq, se); @@ -6384,6 +6311,9 @@ dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) update_cfs_group(se); + if (flags & DEQUEUE_DELAYED) + clear_delayed(se); + if (cfs_rq->nr_queued == 0) { update_idle_cfs_rq_clock_pelt(cfs_rq); #ifdef CONFIG_CFS_BANDWIDTH @@ -6396,11 +6326,15 @@ dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) } #endif } + + return true; } static void -set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) +set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, bool first) { + clear_buddies(cfs_rq, se); + /* 'current' is not kept within the tree. */ if (se->on_rq) { /* @@ -6409,12 +6343,16 @@ set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) * runqueue. */ update_stats_wait_end_fair(cfs_rq, se); + __dequeue_entity(cfs_rq, se); update_load_avg(cfs_rq, se, UPDATE_TG); + + if (first) + set_protect_slice(cfs_rq, se); } update_stats_curr_start(cfs_rq, se); - WARN_ON_ONCE(cfs_rq->h_curr); - cfs_rq->h_curr = se; + WARN_ON_ONCE(cfs_rq->curr); + cfs_rq->curr = se; /* * Track our maximum slice length, if the CPU's load is at @@ -6434,17 +6372,23 @@ set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) se->prev_sum_exec_runtime = se->sum_exec_runtime; } -static bool __dequeue_task(struct rq *rq, struct task_struct *p, int flags); +static int dequeue_entities(struct rq *rq, struct sched_entity *se, int flags); +/* + * Pick the next process, keeping these things in mind, in this order: + * 1) keep things fair between processes/task groups + * 2) pick the "next" process, since someone really wants that to run + * 3) pick the "last" process, for cache locality + * 4) do not run the "skip" process, if something else is available + */ static struct sched_entity * -pick_next_entity(struct rq *rq, bool protect) +pick_next_entity(struct rq *rq, struct cfs_rq *cfs_rq, bool protect) { - struct cfs_rq *cfs_rq = &rq->cfs; struct sched_entity *se; se = pick_eevdf(cfs_rq, protect); if (se->sched_delayed) { - __dequeue_task(rq, task_of(se), DEQUEUE_SLEEP | DEQUEUE_DELAYED); + dequeue_entities(rq, se, DEQUEUE_SLEEP | DEQUEUE_DELAYED); /* * Must not reference @se again, see __block_task(). */ @@ -6464,11 +6408,13 @@ static void put_prev_entity(struct cfs_rq *cfs_rq, struct sched_entity *prev) if (prev->on_rq) { update_stats_wait_start_fair(cfs_rq, prev); + /* Put 'current' back into the tree. */ + __enqueue_entity(cfs_rq, prev); /* in !on_rq case, update occurred at dequeue */ update_load_avg(cfs_rq, prev, 0); } - WARN_ON_ONCE(cfs_rq->h_curr != prev); - cfs_rq->h_curr = NULL; + WARN_ON_ONCE(cfs_rq->curr != prev); + cfs_rq->curr = NULL; } static void @@ -7023,7 +6969,7 @@ void unthrottle_cfs_rq(struct cfs_rq *cfs_rq) assert_list_leaf_cfs_rq(rq); /* Determine whether we need to wake up potentially idle CPU: */ - if (rq->curr == rq->idle && rq->cfs.h_nr_queued) + if (rq->curr == rq->idle && rq->cfs.nr_queued) resched_curr(rq); } @@ -7358,7 +7304,7 @@ static void check_enqueue_throttle(struct cfs_rq *cfs_rq) return; /* an active group must be handled by the update_curr() path */ - if (!cfs_rq->runtime_enabled || cfs_rq->h_curr) + if (!cfs_rq->runtime_enabled || cfs_rq->curr) return; /* ensure the group is not already throttled */ @@ -7730,7 +7676,7 @@ static void hrtick_start_fair(struct rq *rq, struct task_struct *p) resched_curr(rq); return; } - delta = (se->h_load.weight * vdelta) / NICE_0_LOAD; + delta = (se->load.weight * vdelta) / NICE_0_LOAD; /* * Correct for instantaneous load of other classes. @@ -7830,8 +7776,10 @@ static int choose_idle_cpu(int cpu, struct task_struct *p) } static void -requeue_delayed_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) +requeue_delayed_entity(struct sched_entity *se) { + struct cfs_rq *cfs_rq = cfs_rq_of(se); + /* * se->sched_delayed should imply: se->on_rq == 1. * Because a delayed entity is one that is still on @@ -7843,58 +7791,19 @@ requeue_delayed_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) update_curr(cfs_rq); if (update_entity_lag(cfs_rq, se)) { - cfs_rq->h_nr_queued--; + cfs_rq->nr_queued--; if (se != cfs_rq->curr) __dequeue_entity(cfs_rq, se); place_entity(cfs_rq, se, 0); if (se != cfs_rq->curr) __enqueue_entity(cfs_rq, se); - cfs_rq->h_nr_queued++; + cfs_rq->nr_queued++; } update_load_avg(cfs_rq, se, 0); clear_delayed(se); } -static unsigned long enqueue_hierarchy(struct task_struct *p, int flags) -{ - unsigned long weight = NICE_0_LOAD; - int task_new = !(flags & ENQUEUE_WAKEUP); - struct sched_entity *se = &p->se; - int h_nr_idle = task_has_idle_policy(p); - int h_nr_runnable = 1; - - if (task_new && se->sched_delayed) - h_nr_runnable = 0; - - for_each_sched_entity(se) { - struct cfs_rq *cfs_rq = cfs_rq_of(se); - - update_curr(cfs_rq); - - if (!se->on_rq) { - enqueue_entity(cfs_rq, se, flags); - } else { - update_load_avg(cfs_rq, se, UPDATE_TG); - se_update_runnable(se); - update_cfs_group(se); - } - - cfs_rq->h_nr_runnable += h_nr_runnable; - cfs_rq->h_nr_queued++; - cfs_rq->h_nr_idle += h_nr_idle; - - if (cfs_rq_is_idle(cfs_rq)) - h_nr_idle = 1; - - weight = __calc_prop_weight(cfs_rq, se, weight); - - flags = ENQUEUE_WAKEUP; - } - - return weight; -} - /* * The enqueue_task method is called before nr_running is * increased. Here we update the fair scheduling stats and @@ -7903,12 +7812,13 @@ static unsigned long enqueue_hierarchy(struct task_struct *p, int flags) static void enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags) { - int rq_h_nr_queued = rq->cfs.h_nr_queued; - int task_new = !(flags & ENQUEUE_WAKEUP); + struct cfs_rq *cfs_rq; struct sched_entity *se = &p->se; - struct cfs_rq *cfs_rq = &rq->cfs; - unsigned long weight; - bool curr; + int h_nr_idle = task_has_idle_policy(p); + int h_nr_runnable = 1; + int task_new = !(flags & ENQUEUE_WAKEUP); + int rq_h_nr_queued = rq->cfs.h_nr_queued; + u64 slice = 0; if (task_is_throttled(p) && enqueue_throttled_task(p)) return; @@ -7920,10 +7830,10 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags) * estimated utilization, before we update schedutil. */ if (!p->se.sched_delayed || (flags & ENQUEUE_DELAYED)) - util_est_enqueue(cfs_rq, p); + util_est_enqueue(&rq->cfs, p); if (flags & ENQUEUE_DELAYED) { - requeue_delayed_entity(cfs_rq, se); + requeue_delayed_entity(se); return; } @@ -7935,22 +7845,57 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags) if (p->in_iowait) cpufreq_update_util(rq, SCHED_CPUFREQ_IOWAIT); - /* - * XXX comment on the curr thing - */ - curr = (cfs_rq->curr == se); - if (curr) - place_entity(cfs_rq, se, flags); + if (task_new && se->sched_delayed) + h_nr_runnable = 0; - if (se->on_rq && se->sched_delayed) - requeue_delayed_entity(cfs_rq, se); + for_each_sched_entity(se) { + if (se->on_rq) { + if (se->sched_delayed) + requeue_delayed_entity(se); + break; + } + cfs_rq = cfs_rq_of(se); - weight = enqueue_hierarchy(p, flags); + /* + * Basically set the slice of group entries to the min_slice of + * their respective cfs_rq. This ensures the group can service + * its entities in the desired time-frame. + */ + if (slice) { + se->slice = slice; + se->custom_slice = 1; + } + enqueue_entity(cfs_rq, se, flags); + slice = cfs_rq_min_slice(cfs_rq); - if (!curr) { - reweight_eevdf(cfs_rq, se, weight, false); - place_entity(cfs_rq, se, flags | ENQUEUE_QUEUED); - __enqueue_entity(cfs_rq, se); + cfs_rq->h_nr_runnable += h_nr_runnable; + cfs_rq->h_nr_queued++; + cfs_rq->h_nr_idle += h_nr_idle; + + if (cfs_rq_is_idle(cfs_rq)) + h_nr_idle = 1; + + flags = ENQUEUE_WAKEUP; + } + + for_each_sched_entity(se) { + cfs_rq = cfs_rq_of(se); + + update_load_avg(cfs_rq, se, UPDATE_TG); + se_update_runnable(se); + update_cfs_group(se); + + se->slice = slice; + if (se != cfs_rq->curr) + min_vruntime_cb_propagate(&se->run_node, NULL); + slice = cfs_rq_min_slice(cfs_rq); + + cfs_rq->h_nr_runnable += h_nr_runnable; + cfs_rq->h_nr_queued++; + cfs_rq->h_nr_idle += h_nr_idle; + + if (cfs_rq_is_idle(cfs_rq)) + h_nr_idle = 1; } if (!rq_h_nr_queued && rq->cfs.h_nr_queued) @@ -7981,109 +7926,105 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags) hrtick_update(rq); } -static void dequeue_hierarchy(struct task_struct *p, int flags) +/* + * Basically dequeue_task_fair(), except it can deal with dequeue_entity() + * failing half-way through and resume the dequeue later. + * + * Returns: + * -1 - dequeue delayed + * 0 - dequeue throttled + * 1 - dequeue complete + */ +static int dequeue_entities(struct rq *rq, struct sched_entity *se, int flags) { - struct sched_entity *se = &p->se; + bool was_sched_idle = sched_idle_rq(rq); bool task_sleep = flags & DEQUEUE_SLEEP; bool task_delayed = flags & DEQUEUE_DELAYED; bool task_throttled = flags & DEQUEUE_THROTTLE; + struct task_struct *p = NULL; + int h_nr_idle = 0; + int h_nr_queued = 0; int h_nr_runnable = 0; - int h_nr_idle = task_has_idle_policy(p); - bool dequeue = true; + struct cfs_rq *cfs_rq; + u64 slice = 0; - if (task_sleep || task_delayed || !se->sched_delayed) - h_nr_runnable = 1; + if (entity_is_task(se)) { + p = task_of(se); + h_nr_queued = 1; + h_nr_idle = task_has_idle_policy(p); + if (task_sleep || task_delayed || !se->sched_delayed) + h_nr_runnable = 1; + } for_each_sched_entity(se) { - struct cfs_rq *cfs_rq = cfs_rq_of(se); + cfs_rq = cfs_rq_of(se); - update_curr(cfs_rq); + if (!dequeue_entity(cfs_rq, se, flags)) { + if (p && &p->se == se) + return -1; - if (dequeue) { - dequeue_entity(cfs_rq, se, flags); - /* Don't dequeue parent if it has other entities besides us */ - if (cfs_rq->load.weight) - dequeue = false; - } else { - update_load_avg(cfs_rq, se, UPDATE_TG); - se_update_runnable(se); - update_cfs_group(se); + slice = cfs_rq_min_slice(cfs_rq); + break; } cfs_rq->h_nr_runnable -= h_nr_runnable; - cfs_rq->h_nr_queued--; + cfs_rq->h_nr_queued -= h_nr_queued; cfs_rq->h_nr_idle -= h_nr_idle; if (cfs_rq_is_idle(cfs_rq)) - h_nr_idle = 1; + h_nr_idle = h_nr_queued; if (throttled_hierarchy(cfs_rq) && task_throttled) record_throttle_clock(cfs_rq); + /* Don't dequeue parent if it has other entities besides us */ + if (cfs_rq->load.weight) { + slice = cfs_rq_min_slice(cfs_rq); + + /* Avoid re-evaluating load for this entity: */ + se = parent_entity(se); + /* + * Bias pick_next to pick a task from this cfs_rq, as + * p is sleeping when it is within its sched_slice. + */ + if (task_sleep && se) + set_next_buddy(se); + break; + } flags |= DEQUEUE_SLEEP; flags &= ~(DEQUEUE_DELAYED | DEQUEUE_SPECIAL); } -} -/* - * The part of dequeue_task_fair() that is needed to dequeue delayed tasks. - * - * Returns: - * true - dequeued - * false - delayed - */ -static bool __dequeue_task(struct rq *rq, struct task_struct *p, int flags) -{ - struct sched_entity *se = &p->se; - struct cfs_rq *cfs_rq = &rq->cfs; - bool was_sched_idle = sched_idle_rq(rq); - bool task_sleep = flags & DEQUEUE_SLEEP; - bool task_delayed = flags & DEQUEUE_DELAYED; + for_each_sched_entity(se) { + cfs_rq = cfs_rq_of(se); - clear_buddies(cfs_rq, se); + update_load_avg(cfs_rq, se, UPDATE_TG); + se_update_runnable(se); + update_cfs_group(se); - update_curr(cfs_rq_of(se)); - update_entity_lag(cfs_rq, se); + se->slice = slice; + if (se != cfs_rq->curr) + min_vruntime_cb_propagate(&se->run_node, NULL); + slice = cfs_rq_min_slice(cfs_rq); - if (flags & DEQUEUE_DELAYED) { - WARN_ON_ONCE(!se->sched_delayed); - } else { - bool delay = task_sleep; - /* - * DELAY_DEQUEUE relies on spurious wakeups, special task - * states must not suffer spurious wakeups, excempt them. - */ - if (flags & (DEQUEUE_SPECIAL | DEQUEUE_THROTTLE)) - delay = false; + cfs_rq->h_nr_runnable -= h_nr_runnable; + cfs_rq->h_nr_queued -= h_nr_queued; + cfs_rq->h_nr_idle -= h_nr_idle; - WARN_ON_ONCE(delay && se->sched_delayed); + if (cfs_rq_is_idle(cfs_rq)) + h_nr_idle = h_nr_queued; - if (sched_feat(DELAY_DEQUEUE) && delay && - !entity_eligible(cfs_rq, se)) { - update_load_avg(cfs_rq_of(se), se, UPDATE_UTIL_EST); - set_delayed(se); - return false; - } + if (throttled_hierarchy(cfs_rq) && task_throttled) + record_throttle_clock(cfs_rq); } - dequeue_hierarchy(p, flags); - - if (sched_feat(PLACE_REL_DEADLINE) && !task_sleep) { - se->deadline -= se->vruntime; - se->rel_deadline = 1; - } - if (se != cfs_rq->curr) - __dequeue_entity(cfs_rq, se); - - sub_nr_running(rq, 1); + sub_nr_running(rq, h_nr_queued); /* balance early to pull high priority tasks */ if (unlikely(!was_sched_idle && sched_idle_rq(rq))) rq->next_balance = jiffies; - if (task_delayed) { - clear_delayed(se); - + if (p && task_delayed) { WARN_ON_ONCE(!task_sleep); WARN_ON_ONCE(p->on_rq != 1); @@ -8095,7 +8036,7 @@ static bool __dequeue_task(struct rq *rq, struct task_struct *p, int flags) __block_task(rq, p); } - return true; + return 1; } /* @@ -8113,11 +8054,11 @@ static bool dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags) if (!p->se.sched_delayed) util_est_dequeue(&rq->cfs, p); - if (!__dequeue_task(rq, p, flags)) + if (dequeue_entities(rq, &p->se, flags) < 0) return false; /* - * Must not reference @p after __dequeue_task(DEQUEUE_DELAYED). + * Must not reference @p after dequeue_entities(DEQUEUE_DELAYED). */ return true; } @@ -9712,6 +9653,19 @@ static void migrate_task_rq_fair(struct task_struct *p, int new_cpu) static void task_dead_fair(struct task_struct *p) { struct sched_entity *se = &p->se; + + if (se->sched_delayed) { + struct rq_flags rf; + struct rq *rq; + + rq = task_rq_lock(p, &rf); + if (se->sched_delayed) { + update_rq_clock(rq); + dequeue_entities(rq, se, DEQUEUE_SLEEP | DEQUEUE_DELAYED); + } + task_rq_unlock(rq, p, &rf); + } + remove_entity_load_avg(se); } @@ -9745,10 +9699,21 @@ static void set_cpus_allowed_fair(struct task_struct *p, struct affinity_context set_task_max_allowed_capacity(p); } +static void set_next_buddy(struct sched_entity *se) +{ + for_each_sched_entity(se) { + if (WARN_ON_ONCE(!se->on_rq)) + return; + if (se_is_idle(se)) + return; + cfs_rq_of(se)->next = se; + } +} + enum preempt_wakeup_action { PREEMPT_WAKEUP_NONE, /* No preemption. */ PREEMPT_WAKEUP_SHORT, /* Ignore slice protection. */ - PREEMPT_WAKEUP_PICK, /* Let pick_eevdf() decide. */ + PREEMPT_WAKEUP_PICK, /* Let __pick_eevdf() decide. */ PREEMPT_WAKEUP_RESCHED, /* Force reschedule. */ }; @@ -9765,7 +9730,7 @@ set_preempt_buddy(struct cfs_rq *cfs_rq, int wake_flags, if (cfs_rq->next && entity_before(cfs_rq->next, pse)) return false; - set_next_buddy(cfs_rq, pse); + set_next_buddy(pse); return true; } @@ -9818,7 +9783,7 @@ static void wakeup_preempt_fair(struct rq *rq, struct task_struct *p, int wake_f enum preempt_wakeup_action preempt_action = PREEMPT_WAKEUP_PICK; struct task_struct *donor = rq->donor; struct sched_entity *nse, *se = &donor->se, *pse = &p->se; - struct cfs_rq *cfs_rq = &rq->cfs; + struct cfs_rq *cfs_rq = task_cfs_rq(donor); int cse_is_idle, pse_is_idle; /* @@ -9855,6 +9820,7 @@ static void wakeup_preempt_fair(struct rq *rq, struct task_struct *p, int wake_f if (!sched_feat(WAKEUP_PREEMPTION)) return; + find_matching_se(&se, &pse); WARN_ON_ONCE(!pse); cse_is_idle = se_is_idle(se); @@ -9882,7 +9848,8 @@ static void wakeup_preempt_fair(struct rq *rq, struct task_struct *p, int wake_f if (unlikely(!normal_policy(p->policy))) return; - update_curr_fair(rq); + cfs_rq = cfs_rq_of(se); + update_curr(cfs_rq); /* * If @p has a shorter slice than current and @p is eligible, override * current's slice protection in order to allow preemption. @@ -9926,15 +9893,18 @@ static void wakeup_preempt_fair(struct rq *rq, struct task_struct *p, int wake_f } pick: - if (cfs_rq->h_nr_queued) { - nse = pick_next_entity(rq, preempt_action != PREEMPT_WAKEUP_SHORT); - if (unlikely(!nse)) - goto pick; + nse = pick_next_entity(rq, cfs_rq, preempt_action != PREEMPT_WAKEUP_SHORT); + /* If @p has become the most eligible task, force preemption */ + if (nse == pse) + goto preempt; - /* If @p has become the most eligible task, force preemption */ - if (nse == pse) - goto preempt; - } + /* + * Because p is enqueued, nse being null can only mean that we + * dequeued a delayed task. If there are still entities queued in + * cfs, check if the next one will be p. + */ + if (!nse && cfs_rq->nr_queued) + goto pick; if (sched_feat(RUN_TO_PARITY)) update_protect_slice(cfs_rq, se); @@ -9953,24 +9923,33 @@ static void wakeup_preempt_fair(struct rq *rq, struct task_struct *p, int wake_f struct task_struct *pick_task_fair(struct rq *rq, struct rq_flags *rf) __must_hold(__rq_lockp(rq)) { - struct cfs_rq *cfs_rq = &rq->cfs; struct sched_entity *se; + struct cfs_rq *cfs_rq; struct task_struct *p; + bool throttled; int new_tasks; again: - if (!cfs_rq->h_nr_queued) + cfs_rq = &rq->cfs; + if (!cfs_rq->nr_queued) goto idle; - /* Might not have done put_prev_entity() */ - if (cfs_rq->curr && cfs_rq->curr->on_rq) - update_curr(cfs_rq); + throttled = false; - se = pick_next_entity(rq, true); - if (!se) - goto again; + do { + /* Might not have done put_prev_entity() */ + if (cfs_rq->curr && cfs_rq->curr->on_rq) + update_curr(cfs_rq); + + se = pick_next_entity(rq, cfs_rq, true); + if (!se) + goto again; + cfs_rq = group_cfs_rq(se); + } while (cfs_rq); p = task_of(se); + if (unlikely(throttled)) + task_throttle_setup_work(p); return p; idle: @@ -10007,7 +9986,7 @@ void fair_server_init(struct rq *rq) static void put_prev_task_fair(struct rq *rq, struct task_struct *prev, struct task_struct *next) { struct sched_entity *se = &prev->se; - struct cfs_rq *cfs_rq = &rq->cfs; + struct cfs_rq *cfs_rq; struct sched_entity *nse = NULL; #ifdef CONFIG_FAIR_GROUP_SCHED @@ -10017,7 +9996,7 @@ static void put_prev_task_fair(struct rq *rq, struct task_struct *prev, struct t while (se) { cfs_rq = cfs_rq_of(se); - if (!nse || cfs_rq->h_curr) + if (!nse || cfs_rq->curr) put_prev_entity(cfs_rq, se); #ifdef CONFIG_FAIR_GROUP_SCHED if (nse) { @@ -10036,14 +10015,6 @@ static void put_prev_task_fair(struct rq *rq, struct task_struct *prev, struct t #endif se = parent_entity(se); } - - /* Put 'current' back into the tree. */ - cfs_rq = &rq->cfs; - se = &prev->se; - WARN_ON_ONCE(cfs_rq->curr != se); - cfs_rq->curr = NULL; - if (se->on_rq) - __enqueue_entity(cfs_rq, se); } /* @@ -10052,8 +10023,8 @@ static void put_prev_task_fair(struct rq *rq, struct task_struct *prev, struct t static void yield_task_fair(struct rq *rq) { struct task_struct *curr = rq->donor; + struct cfs_rq *cfs_rq = task_cfs_rq(curr); struct sched_entity *se = &curr->se; - struct cfs_rq *cfs_rq = &rq->cfs; /* * Are we the only task in the tree? @@ -10094,11 +10065,11 @@ static bool yield_to_task_fair(struct rq *rq, struct task_struct *p) struct sched_entity *se = &p->se; /* !se->on_rq also covers throttled task */ - if (!se->on_rq || se->sched_delayed) + if (!se->on_rq) return false; /* Tell the scheduler that we'd really like se to run next. */ - set_next_buddy(&task_rq(p)->cfs, se); + set_next_buddy(se); yield_task_fair(rq); @@ -10437,10 +10408,15 @@ static inline long migrate_degrades_locality(struct task_struct *p, */ static inline int task_is_ineligible_on_dst_cpu(struct task_struct *p, int dest_cpu) { - struct cfs_rq *dst_cfs_rq = &cpu_rq(dest_cpu)->cfs; + struct cfs_rq *dst_cfs_rq; - if (sched_feat(PLACE_LAG) && dst_cfs_rq->h_nr_queued && - !entity_eligible(&task_rq(p)->cfs, &p->se)) +#ifdef CONFIG_FAIR_GROUP_SCHED + dst_cfs_rq = tg_cfs_rq(task_group(p), dest_cpu); +#else + dst_cfs_rq = &cpu_rq(dest_cpu)->cfs; +#endif + if (sched_feat(PLACE_LAG) && dst_cfs_rq->nr_queued && + !entity_eligible(task_cfs_rq(p), &p->se)) return 1; return 0; @@ -11223,7 +11199,7 @@ static void update_cfs_rq_h_load(struct cfs_rq *cfs_rq) while ((se = READ_ONCE(cfs_rq->h_load_next)) != NULL) { load = cfs_rq->h_load; load = div64_ul(load * se->avg.load_avg, - cfs_rq_load_avg(cfs_rq) + 1); + cfs_rq_load_avg(cfs_rq) + 1); cfs_rq = group_cfs_rq(se); cfs_rq->h_load = load; cfs_rq->last_h_load_update = now; @@ -14618,7 +14594,7 @@ static inline void task_tick_core(struct rq *rq, struct task_struct *curr) * MIN_NR_TASKS_DURING_FORCEIDLE - 1 tasks and use that to check * if we need to give up the CPU. */ - if (rq->core->core_forceidle_count && rq->cfs.h_nr_queued == 1 && + if (rq->core->core_forceidle_count && rq->cfs.nr_queued == 1 && __entity_slice_used(&curr->se, MIN_NR_TASKS_DURING_FORCEIDLE)) resched_curr(rq); } @@ -14827,8 +14803,30 @@ bool cfs_prio_less(const struct task_struct *a, const struct task_struct *b, WARN_ON_ONCE(task_rq(b)->core != rq->core); +#ifdef CONFIG_FAIR_GROUP_SCHED + /* + * Find an se in the hierarchy for tasks a and b, such that the se's + * are immediate siblings. + */ + while (sea->cfs_rq->tg != seb->cfs_rq->tg) { + int sea_depth = sea->depth; + int seb_depth = seb->depth; + + if (sea_depth >= seb_depth) + sea = parent_entity(sea); + if (sea_depth <= seb_depth) + seb = parent_entity(seb); + } + + se_fi_update(sea, rq->core->core_forceidle_seq, in_fi); + se_fi_update(seb, rq->core->core_forceidle_seq, in_fi); + + cfs_rqa = sea->cfs_rq; + cfs_rqb = seb->cfs_rq; +#else /* !CONFIG_FAIR_GROUP_SCHED: */ cfs_rqa = &task_rq(a)->cfs; cfs_rqb = &task_rq(b)->cfs; +#endif /* !CONFIG_FAIR_GROUP_SCHED */ /* * Find delta after normalizing se's vruntime with its cfs_rq's @@ -14867,20 +14865,11 @@ static inline void task_tick_core(struct rq *rq, struct task_struct *curr) {} static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued) { struct sched_entity *se = &curr->se; + struct cfs_rq *cfs_rq; - if (se->on_rq) { - unsigned long weight = NICE_0_LOAD; - struct cfs_rq *cfs_rq; - - for_each_sched_entity(se) { - cfs_rq = cfs_rq_of(se); - entity_tick(cfs_rq, se, queued); - - weight = __calc_prop_weight(cfs_rq, se, weight); - } - - se = &curr->se; - reweight_eevdf(cfs_rq, se, weight, se->on_rq); + for_each_sched_entity(se) { + cfs_rq = cfs_rq_of(se); + entity_tick(cfs_rq, se, queued); } if (queued) @@ -14920,7 +14909,7 @@ prio_changed_fair(struct rq *rq, struct task_struct *p, u64 oldprio) if (p->prio == oldprio) return; - if (rq->cfs.h_nr_queued == 1) + if (rq->cfs.nr_queued == 1) return; /* @@ -15049,44 +15038,33 @@ static void switched_to_fair(struct rq *rq, struct task_struct *p) } } +/* + * Account for a task changing its policy or group. + * + * This routine is mostly called to set cfs_rq->curr field when a task + * migrates between groups/classes. + */ static void set_next_task_fair(struct rq *rq, struct task_struct *p, bool first) { struct sched_entity *se = &p->se; bool throttled = false; - struct cfs_rq *cfs_rq = &rq->cfs; - unsigned long weight = NICE_0_LOAD; - bool on_rq = se->on_rq; - - clear_buddies(cfs_rq, se); - - if (on_rq) - __dequeue_entity(cfs_rq, se); for_each_sched_entity(se) { - cfs_rq = cfs_rq_of(se); + struct cfs_rq *cfs_rq = cfs_rq_of(se); - if (!IS_ENABLED(CONFIG_FAIR_GROUP_SCHED) || - !first || !cfs_rq->h_curr) - set_next_entity(cfs_rq, se); + if (IS_ENABLED(CONFIG_FAIR_GROUP_SCHED) && + first && cfs_rq->curr) + break; + set_next_entity(cfs_rq, se, first); /* ensure bandwidth has been allocated on our new cfs_rq */ throttled |= account_cfs_rq_runtime(cfs_rq, 0); - - if (on_rq) - weight = __calc_prop_weight(cfs_rq, se, weight); } if (throttled) task_throttle_setup_work(p); se = &p->se; - cfs_rq->curr = se; - - if (on_rq) { - reweight_eevdf(cfs_rq, se, weight, se->on_rq); - if (first) - set_protect_slice(cfs_rq, se); - } if (task_on_rq_queued(p)) { /* @@ -15199,8 +15177,17 @@ void unregister_fair_sched_group(struct task_group *tg) struct sched_entity *se = tg_se(tg, cpu); struct rq *rq = cpu_rq(cpu); - if (se) + if (se) { + if (se->sched_delayed) { + guard(rq_lock_irqsave)(rq); + if (se->sched_delayed) { + update_rq_clock(rq); + dequeue_entities(rq, se, DEQUEUE_SLEEP | DEQUEUE_DELAYED); + } + list_del_leaf_cfs_rq(cfs_rq); + } remove_entity_load_avg(se); + } /* * Only empty task groups can be destroyed; so we can speculatively diff --git a/kernel/sched/pelt.c b/kernel/sched/pelt.c index 779eb58a4..897790889 100644 --- a/kernel/sched/pelt.c +++ b/kernel/sched/pelt.c @@ -206,7 +206,7 @@ ___update_load_sum(u64 now, struct sched_avg *sa, /* * running is a subset of runnable (weight) so running can't be set if * runnable is clear. But there are some corner cases where the current - * se has been already dequeued but cfs_rq->h_curr still points to it. + * se has been already dequeued but cfs_rq->curr still points to it. * This means that weight will be 0 but not running for a sched_entity * but also for a cfs_rq if the latter becomes idle. As an example, * this happens during sched_balance_newidle() which calls @@ -307,7 +307,7 @@ int __update_load_avg_blocked_se(u64 now, struct sched_entity *se) int __update_load_avg_se(u64 now, struct cfs_rq *cfs_rq, struct sched_entity *se) { if (___update_load_sum(now, &se->avg, !!se->on_rq, se_runnable(se), - cfs_rq->h_curr == se)) { + cfs_rq->curr == se)) { ___update_load_avg(&se->avg, se_weight(se)); cfs_se_util_change(&se->avg); @@ -323,7 +323,7 @@ int __update_load_avg_cfs_rq(u64 now, struct cfs_rq *cfs_rq) if (___update_load_sum(now, &cfs_rq->avg, scale_load_down(cfs_rq->load.weight), cfs_rq->h_nr_runnable, - cfs_rq->h_curr != NULL)) { + cfs_rq->curr != NULL)) { ___update_load_avg(&cfs_rq->avg, 1); trace_pelt_cfs_tp(cfs_rq); diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index 4664869d8..a4a1eb57a 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -493,8 +493,6 @@ struct task_group { * will also be accessed at each tick. */ atomic_long_t load_avg ____cacheline_aligned; - atomic_long_t runnable_avg; - #endif /* CONFIG_FAIR_GROUP_SCHED */ #ifdef CONFIG_RT_GROUP_SCHED @@ -530,8 +528,21 @@ struct task_group { }; +#ifdef CONFIG_GROUP_SCHED_WEIGHT #define ROOT_TASK_GROUP_LOAD NICE_0_LOAD +/* + * A weight of 0 or 1 can cause arithmetics problems. + * A weight of a cfs_rq is the sum of weights of which entities + * are queued on this cfs_rq, so a weight of a entity should not be + * too large, so as the shares value of a task group. + * (The default weight is 1024 - so there's no practical + * limitation from this.) + */ +#define MIN_SHARES (1UL << 1) +#define MAX_SHARES (1UL << 18) +#endif + typedef int (*tg_visitor)(struct task_group *, void *); extern int walk_tg_tree_from(struct task_group *from, @@ -560,7 +571,6 @@ extern void free_fair_sched_group(struct task_group *tg); extern int alloc_fair_sched_group(struct task_group *tg, struct task_group *parent); extern void online_fair_sched_group(struct task_group *tg); extern void unregister_fair_sched_group(struct task_group *tg); -extern void __sched_cgroup_mode_update(int mode); #else /* !CONFIG_FAIR_GROUP_SCHED: */ static inline void free_fair_sched_group(struct task_group *tg) { } static inline int alloc_fair_sched_group(struct task_group *tg, struct task_group *parent) @@ -618,17 +628,6 @@ static inline bool cfs_task_bw_constrained(struct task_struct *p) { return false #endif /* !CONFIG_CGROUP_SCHED */ -/* - * A weight of 0 or 1 can cause arithmetics problems. - * A weight of a cfs_rq is the sum of weights of which entities - * are queued on this cfs_rq, so a weight of a entity should not be - * too large, so as the shares value of a task group. - * (The default weight is 1024 - so there's no practical - * limitation from this.) - */ -#define MIN_SHARES (1UL << 1) -#define MAX_SHARES (1UL << 18) - extern void unregister_rt_sched_group(struct task_group *tg); extern void free_rt_sched_group(struct task_group *tg); extern int alloc_rt_sched_group(struct task_group *tg, struct task_group *parent); @@ -707,7 +706,6 @@ struct cfs_rq { /* * CFS load tracking */ - struct sched_entity *h_curr; struct sched_avg avg; #ifndef CONFIG_64BIT u64 last_update_time_copy; @@ -723,7 +721,6 @@ struct cfs_rq { #ifdef CONFIG_FAIR_GROUP_SCHED u64 last_update_tg_load_avg; unsigned long tg_load_avg_contrib; - unsigned long tg_runnable_avg_contrib; long propagate; long prop_runnable_sum; @@ -2574,7 +2571,6 @@ extern const u32 sched_prio_to_wmult[40]; #define ENQUEUE_MIGRATED 0x00040000 #define ENQUEUE_INITIAL 0x00080000 #define ENQUEUE_RQ_SELECTED 0x00100000 -#define ENQUEUE_QUEUED 0x00200000 #define RETRY_TASK ((void *)-1UL) -- 2.55.0 From fc239de764d1c202e3ac943a70b8dead61bd99fe Mon Sep 17 00:00:00 2001 From: Piotr Gorski Date: Mon, 17 Aug 2026 13:14:14 +0200 Subject: [PATCH 3/3] PRJC for 7.2 Signed-off-by: Piotr Gorski --- Documentation/admin-guide/sysctl/kernel.rst | 9 + Documentation/scheduler/sched-BMQ.txt | 110 + fs/f2fs/checkpoint.c | 2 +- fs/proc/base.c | 2 +- include/linux/rseq_entry.h | 3 + include/linux/sched.h | 75 + include/linux/sched/deadline.h | 20 + include/linux/sched/prio.h | 22 + include/linux/sched/rt.h | 2 + include/linux/sched/topology.h | 3 +- init/Kconfig | 35 +- init/init_task.c | 20 + kernel/Kconfig.preempt | 4 +- kernel/cgroup/cpuset.c | 14 +- kernel/delayacct.c | 2 +- kernel/exit.c | 6 +- kernel/locking/rtmutex.c | 16 +- kernel/locking/ww_mutex.h | 2 + kernel/sched/Makefile | 5 + kernel/sched/alt_core.c | 7694 +++++++++++++++++++ kernel/sched/alt_core.h | 197 + kernel/sched/alt_debug.c | 32 + kernel/sched/alt_sched.h | 1067 +++ kernel/sched/alt_topology.c | 287 + kernel/sched/alt_topology.h | 113 + kernel/sched/bmq.h | 104 + kernel/sched/build_policy.c | 8 +- kernel/sched/build_utility.c | 10 +- kernel/sched/cpufreq_schedutil.c | 12 +- kernel/sched/cputime.c | 10 +- kernel/sched/debug.c | 21 + kernel/sched/idle.c | 2 + kernel/sched/pds.h | 139 + kernel/sched/pelt.c | 6 +- kernel/sched/pelt.h | 6 +- kernel/sched/sched.h | 9 + kernel/sched/stats.c | 4 + kernel/sched/stats.h | 2 + kernel/sched/syscalls.c | 170 +- kernel/sched/topology.c | 33 + kernel/sysctl.c | 15 + kernel/time/posix-cpu-timers.c | 10 +- kernel/trace/trace_osnoise.c | 4 + kernel/trace/trace_selftest.c | 5 + kernel/workqueue.c | 15 + 45 files changed, 10292 insertions(+), 35 deletions(-) create mode 100644 Documentation/scheduler/sched-BMQ.txt create mode 100644 kernel/sched/alt_core.c create mode 100644 kernel/sched/alt_core.h create mode 100644 kernel/sched/alt_debug.c create mode 100644 kernel/sched/alt_sched.h create mode 100644 kernel/sched/alt_topology.c create mode 100644 kernel/sched/alt_topology.h create mode 100644 kernel/sched/bmq.h create mode 100644 kernel/sched/pds.h diff --git a/Documentation/admin-guide/sysctl/kernel.rst b/Documentation/admin-guide/sysctl/kernel.rst index c6994e55d..cf28e5f29 100644 --- a/Documentation/admin-guide/sysctl/kernel.rst +++ b/Documentation/admin-guide/sysctl/kernel.rst @@ -1734,3 +1734,12 @@ is 10 seconds. The softlockup threshold is (``2 * watchdog_thresh``). Setting this tunable to zero will disable lockup detection altogether. + +yield_type: +=========== + +BMQ/PDS CPU scheduler only. This determines what type of yield calls +to sched_yield() will be performed. + + 0 - No yield. + 1 - Requeue task. (default) diff --git a/Documentation/scheduler/sched-BMQ.txt b/Documentation/scheduler/sched-BMQ.txt new file mode 100644 index 000000000..05c84eec0 --- /dev/null +++ b/Documentation/scheduler/sched-BMQ.txt @@ -0,0 +1,110 @@ + BitMap queue CPU Scheduler + -------------------------- + +CONTENT +======== + + Background + Design + Overview + Task policy + Priority management + BitMap Queue + CPU Assignment and Migration + + +Background +========== + +BitMap Queue CPU scheduler, referred to as BMQ from here on, is an evolution +of previous Priority and Deadline based Skiplist multiple queue scheduler(PDS), +and inspired by Zircon scheduler. The goal of it is to keep the scheduler code +simple, while efficiency and scalable for interactive tasks, such as desktop, +movie playback and gaming etc. + +Design +====== + +Overview +-------- + +BMQ use per CPU run queue design, each CPU(logical) has it's own run queue, +each CPU is responsible for scheduling the tasks that are putting into it's +run queue. + +The run queue is a set of priority queues. Note that these queues are fifo +queue for non-rt tasks or priority queue for rt tasks in data structure. See +BitMap Queue below for details. BMQ is optimized for non-rt tasks in the fact +that most applications are non-rt tasks. No matter the queue is fifo or +priority, In each queue is an ordered list of runnable tasks awaiting execution +and the data structures are the same. When it is time for a new task to run, +the scheduler simply looks the lowest numbered queueue that contains a task, +and runs the first task from the head of that queue. And per CPU idle task is +also in the run queue, so the scheduler can always find a task to run on from +its run queue. + +Each task will assigned the same timeslice(default 4ms) when it is picked to +start running. Task will be reinserted at the end of the appropriate priority +queue when it uses its whole timeslice. When the scheduler selects a new task +from the priority queue it sets the CPU's preemption timer for the remainder of +the previous timeslice. When that timer fires the scheduler will stop execution +on that task, select another task and start over again. + +If a task blocks waiting for a shared resource then it's taken out of its +priority queue and is placed in a wait queue for the shared resource. When it +is unblocked it will be reinserted in the appropriate priority queue of an +eligible CPU. + +Task policy +----------- + +BMQ supports DEADLINE, FIFO, RR, NORMAL, BATCH and IDLE task policy like the +mainline CFS scheduler. But BMQ is heavy optimized for non-rt task, that's +NORMAL/BATCH/IDLE policy tasks. Below is the implementation detail of each +policy. + +DEADLINE + It is squashed as priority 0 FIFO task. + +FIFO/RR + All RT tasks share one single priority queue in BMQ run queue designed. The +complexity of insert operation is O(n). BMQ is not designed for system runs +with major rt policy tasks. + +NORMAL/BATCH/IDLE + BATCH and IDLE tasks are treated as the same policy. They compete CPU with +NORMAL policy tasks, but they just don't boost. To control the priority of +NORMAL/BATCH/IDLE tasks, simply use nice level. + +ISO + ISO policy is not supported in BMQ. Please use nice level -20 NORMAL policy +task instead. + +Priority management +------------------- + +RT tasks have priority from 0-99. For non-rt tasks, there are three different +factors used to determine the effective priority of a task. The effective +priority being what is used to determine which queue it will be in. + +The first factor is simply the task’s static priority. Which is assigned from +task's nice level, within [-20, 19] in userland's point of view and [0, 39] +internally. + +The second factor is the priority boost. This is a value bounded between +[-MAX_PRIORITY_ADJ, MAX_PRIORITY_ADJ] used to offset the base priority, it is +modified by the following cases: + +*When a thread has used up its entire timeslice, always deboost its boost by +increasing by one. +*When a thread gives up cpu control(voluntary or non-voluntary) to reschedule, +and its switch-in time(time after last switch and run) below the thredhold +based on its priority boost, will boost its boost by decreasing by one buti is +capped at 0 (won’t go negative). + +The intent in this system is to ensure that interactive threads are serviced +quickly. These are usually the threads that interact directly with the user +and cause user-perceivable latency. These threads usually do little work and +spend most of their time blocked awaiting another user event. So they get the +priority boost from unblocking while background threads that do most of the +processing receive the priority penalty for using their entire timeslice. diff --git a/fs/f2fs/checkpoint.c b/fs/f2fs/checkpoint.c index 064f5b537..a81cbd7b8 100644 --- a/fs/f2fs/checkpoint.c +++ b/fs/f2fs/checkpoint.c @@ -28,7 +28,7 @@ static inline void get_lock_elapsed_time(struct f2fs_time_stat *ts) { ts->total_time = ktime_get(); #ifdef CONFIG_64BIT - ts->running_time = current->se.sum_exec_runtime; + ts->running_time = tsk_seruntime(current); #endif #if defined(CONFIG_SCHED_INFO) && defined(CONFIG_SCHEDSTATS) ts->runnable_time = current->sched_info.run_delay; diff --git a/fs/proc/base.c b/fs/proc/base.c index 780f81259..6e4cc1d92 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -513,7 +513,7 @@ static int proc_pid_schedstat(struct seq_file *m, struct pid_namespace *ns, seq_puts(m, "0 0 0\n"); else seq_printf(m, "%llu %llu %lu\n", - (unsigned long long)task->se.sum_exec_runtime, + (unsigned long long)tsk_seruntime(task), (unsigned long long)task->sched_info.run_delay, task->sched_info.pcount); diff --git a/include/linux/rseq_entry.h b/include/linux/rseq_entry.h index 31ce349ed..23db2b3d8 100644 --- a/include/linux/rseq_entry.h +++ b/include/linux/rseq_entry.h @@ -730,7 +730,10 @@ static __always_inline void rseq_syscall_exit_to_user_mode(void) /* Needed to remove the store for the !lockdep case */ if (IS_ENABLED(CONFIG_LOCKDEP)) { +#ifndef CONFIG_SCHED_ALT +/* WA for sched/alt: [Sync] 9a723ed7facf sched/mmcid: Provide new scheduler CID mechanism */ WARN_ON_ONCE(ev->sched_switch); +#endif /* !CONFIG_SCHED_ALT */ ev->events = 0; } } diff --git a/include/linux/sched.h b/include/linux/sched.h index 5738c54eb..bcff2f6c1 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -869,6 +869,7 @@ struct task_struct { u8 __pad; struct __call_single_node wake_entry; +#ifndef CONFIG_SCHED_ALT unsigned int wakee_flips; unsigned long wakee_flip_decay_ts; struct task_struct *last_wakee; @@ -882,12 +883,26 @@ struct task_struct { */ int recent_used_cpu; int wake_cpu; +#endif /* !CONFIG_SCHED_ALT */ int prio; int static_prio; int normal_prio; unsigned int rt_priority; +#ifdef CONFIG_SCHED_ALT + u64 last_ran; + s64 time_slice; + struct list_head sq_node; +#ifdef CONFIG_SCHED_BMQ + int boost_prio; +#endif /* CONFIG_SCHED_BMQ */ +#ifdef CONFIG_SCHED_PDS + u64 deadline; +#endif /* CONFIG_SCHED_PDS */ + /* sched_clock time spent running */ + u64 sched_time; +#else /* !CONFIG_SCHED_ALT */ struct sched_entity se; struct sched_rt_entity rt; struct sched_dl_entity dl; @@ -902,6 +917,7 @@ struct task_struct { unsigned long core_cookie; unsigned int core_occupation; #endif +#endif /* !CONFIG_SCHED_ALT */ #ifdef CONFIG_CGROUP_SCHED struct task_group *sched_task_group; @@ -943,9 +959,13 @@ struct task_struct { const cpumask_t *cpus_ptr; cpumask_t *user_cpus_ptr; cpumask_t cpus_mask; +#ifndef CONFIG_SCHED_ALT void *migration_pending; +#endif unsigned short migration_disabled; +#ifndef CONFIG_SCHED_ALT unsigned short migration_flags; +#endif #ifdef CONFIG_PREEMPT_RCU int rcu_read_lock_nesting; @@ -976,8 +996,10 @@ struct task_struct { struct sched_info sched_info; struct list_head tasks; +#ifndef CONFIG_SCHED_ALT struct plist_node pushable_tasks; struct rb_node pushable_dl_tasks; +#endif struct mm_struct *mm; struct mm_struct *active_mm; @@ -1694,6 +1716,15 @@ static inline bool sched_proxy_exec(void) } #endif +#ifdef CONFIG_SCHED_ALT +#define tsk_seruntime(t) ((t)->sched_time) +/* replace the uncertian rt_timeout with 0UL */ +#define tsk_rttimeout(t) (0UL) +#else /* !CONFIG_SCHED_ALT: */ +#define tsk_seruntime(t) ((t)->se.sum_exec_runtime) +#define tsk_rttimeout(t) ((t)->rt.timeout) +#endif /* !CONFIG_SCHED_ALT */ + #define TASK_REPORT_IDLE (TASK_REPORT + 1) #define TASK_REPORT_MAX (TASK_REPORT_IDLE << 1) @@ -2297,7 +2328,11 @@ static inline void set_task_cpu(struct task_struct *p, unsigned int cpu) static inline bool task_is_runnable(struct task_struct *p) { +#ifdef CONFIG_SCHED_ALT + return p->on_rq; +#else return p->on_rq && !p->se.sched_delayed; +#endif /* !CONFIG_SCHED_ALT */ } extern bool sched_task_on_rq(struct task_struct *p); @@ -2441,6 +2476,25 @@ DECLARE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues); #endif #define this_rq_pinned() (*(unsigned int *)((void *)this_rq_raw() + RQ_nr_pinned)) +static inline void +__do_set_cpus_ptr(struct task_struct *p, const struct cpumask *new_mask) +{ + /* + * This here violates the locking rules for affinity, since we're only + * supposed to change these variables while holding both rq->lock and + * p->pi_lock. + * + * HOWEVER, it magically works, because ttwu() is the only code that + * accesses these variables under p->pi_lock and only does so after + * smp_cond_load_acquire(&p->on_cpu, !VAL), and we're in __schedule() + * before finish_task(). + * + * XXX do further audits, this smells like something putrid. + */ + WARN_ON_ONCE(!p->on_cpu); + p->cpus_ptr = new_mask; +} + static inline void __migrate_enable(void) { struct task_struct *p = current; @@ -2464,8 +2518,17 @@ static inline void __migrate_enable(void) * __set_cpus_allowed_ptr(SCA_MIGRATE_ENABLE) doesn't schedule(). */ guard(preempt)(); +#ifdef CONFIG_SCHED_ALT + /* + * Assumption: current should be running on allowed cpu + */ + WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &p->cpus_mask)); + if (p->cpus_ptr != &p->cpus_mask) + __do_set_cpus_ptr(p, &p->cpus_mask); +#else if (unlikely(p->cpus_ptr != &p->cpus_mask)) ___migrate_enable(); +#endif /* * Mustn't clear migration_disabled() until cpus_ptr points back at the * regular cpus_mask, otherwise things that race (eg. @@ -2492,8 +2555,20 @@ static inline void __migrate_disable(void) } guard(preempt)(); +#ifdef CONFIG_SCHED_ALT + int cpu = smp_processor_id(); + if (cpumask_test_cpu(cpu, &p->cpus_mask)) { +#endif this_rq_pinned()++; p->migration_disabled = 1; +#ifdef CONFIG_SCHED_ALT + /* + * Violates locking rules! see comment in __do_set_cpus_ptr(). + */ + if (p->cpus_ptr == &p->cpus_mask) + __do_set_cpus_ptr(p, cpumask_of(cpu)); + } +#endif } #else /* !COMPILE_OFFSETS */ static inline void __migrate_disable(void) { } diff --git a/include/linux/sched/deadline.h b/include/linux/sched/deadline.h index 273538200..12b41502a 100644 --- a/include/linux/sched/deadline.h +++ b/include/linux/sched/deadline.h @@ -2,6 +2,25 @@ #ifndef _LINUX_SCHED_DEADLINE_H #define _LINUX_SCHED_DEADLINE_H +#ifdef CONFIG_SCHED_ALT + +static inline int dl_task(struct task_struct *p) +{ + return 0; +} + +#ifdef CONFIG_SCHED_BMQ +#define __tsk_deadline(p) (0UL) +#endif + +#ifdef CONFIG_SCHED_PDS +#define __tsk_deadline(p) ((((u64) ((p)->prio))<<56) | (p)->deadline) +#endif + +#else + +#define __tsk_deadline(p) ((p)->dl.deadline) + /* * SCHED_DEADLINE tasks has negative priorities, reflecting * the fact that any of them has higher prio than RT and @@ -72,5 +91,6 @@ static inline bool dl_is_implicit(struct sched_dl_entity *dl_se) { return dl_se->dl_deadline == dl_se->dl_period; } +#endif /* !CONFIG_SCHED_ALT */ #endif /* _LINUX_SCHED_DEADLINE_H */ diff --git a/include/linux/sched/prio.h b/include/linux/sched/prio.h index 6ab43b4f7..ef1cff556 100644 --- a/include/linux/sched/prio.h +++ b/include/linux/sched/prio.h @@ -19,6 +19,28 @@ #define MAX_PRIO (MAX_RT_PRIO + NICE_WIDTH) #define DEFAULT_PRIO (MAX_RT_PRIO + NICE_WIDTH / 2) +#ifdef CONFIG_SCHED_ALT + +/* Undefine MAX_PRIO and DEFAULT_PRIO */ +#undef MAX_PRIO +#undef DEFAULT_PRIO + +/* +/- priority levels from the base priority */ +#ifdef CONFIG_SCHED_BMQ +#define MAX_PRIORITY_ADJ (12) +#endif + +#ifdef CONFIG_SCHED_PDS +#define MAX_PRIORITY_ADJ (0) +#endif + +#define MIN_NORMAL_PRIO (128) +#define NORMAL_PRIO_NUM (64) +#define MAX_PRIO (MIN_NORMAL_PRIO + NORMAL_PRIO_NUM) +#define DEFAULT_PRIO (MAX_PRIO - MAX_PRIORITY_ADJ - NICE_WIDTH / 2) + +#endif /* CONFIG_SCHED_ALT */ + /* * Convert user-nice values [ -20 ... 0 ... 19 ] * to static priority [ MAX_RT_PRIO..MAX_PRIO-1 ], diff --git a/include/linux/sched/rt.h b/include/linux/sched/rt.h index 4e3338103..6dfef878f 100644 --- a/include/linux/sched/rt.h +++ b/include/linux/sched/rt.h @@ -45,8 +45,10 @@ static inline bool rt_or_dl_task_policy(struct task_struct *tsk) if (policy == SCHED_FIFO || policy == SCHED_RR) return true; +#ifndef CONFIG_SCHED_ALT if (policy == SCHED_DEADLINE) return true; +#endif return false; } diff --git a/include/linux/sched/topology.h b/include/linux/sched/topology.h index b5d9d7c2b..40175a3de 100644 --- a/include/linux/sched/topology.h +++ b/include/linux/sched/topology.h @@ -226,7 +226,8 @@ extern void sched_update_asym_prefer_cpu(int cpu, int old_prio, int new_prio); #define SDTL_INIT(maskfn, flagsfn, dname) ((struct sched_domain_topology_level) \ { .mask = maskfn, .sd_flags = flagsfn, .name = #dname }) -#if defined(CONFIG_ENERGY_MODEL) && defined(CONFIG_CPU_FREQ_GOV_SCHEDUTIL) +#if defined(CONFIG_ENERGY_MODEL) && defined(CONFIG_CPU_FREQ_GOV_SCHEDUTIL) && \ + !defined(CONFIG_SCHED_ALT) extern void rebuild_sched_domains_energy(void); #else static inline void rebuild_sched_domains_energy(void) diff --git a/init/Kconfig b/init/Kconfig index 8937400a3..f344a8f73 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -726,6 +726,7 @@ config TASK_IO_ACCOUNTING config PSI bool "Pressure stall information tracking" + depends on !SCHED_ALT select KERNFS help Collect metrics that indicate how overcommitted the CPU, memory, @@ -949,6 +950,35 @@ config SCHED_PROXY_EXEC This option enables proxy execution, a mechanism for mutex-owning tasks to inherit the scheduling context of higher priority waiters. +menuconfig SCHED_ALT + bool "Alternative CPU Schedulers" + default y + help + This feature enable alternative CPU scheduler" + +if SCHED_ALT + +choice + prompt "Alternative CPU Scheduler" + default SCHED_BMQ + +config SCHED_BMQ + bool "BMQ CPU scheduler" + help + The BitMap Queue CPU scheduler for excellent interactivity and + responsiveness on the desktop and solid scalability on normal + hardware and commodity servers. + +config SCHED_PDS + bool "PDS CPU scheduler" + help + The Priority and Deadline based Skip list multiple queue CPU + Scheduler. + +endchoice + +endif + endmenu # @@ -1019,6 +1049,7 @@ config NUMA_BALANCING depends on ARCH_SUPPORTS_NUMA_BALANCING depends on !ARCH_WANT_NUMA_VARIABLE_LOCALITY depends on SMP && NUMA_MIGRATION && !PREEMPT_RT + depends on !SCHED_ALT help This option adds support for automatic NUMA aware memory/task placement. The mechanism is quite primitive and is based on migrating memory when @@ -1030,6 +1061,7 @@ config SCHED_CACHE bool "Cache aware load balance" default y depends on SMP + depends on !SCHED_ALT help When enabled, the scheduler will attempt to aggregate tasks from the same process onto a single Last Level Cache (LLC) domain when @@ -1316,7 +1348,7 @@ config CPUSETS config CPUSETS_V1 bool "Legacy cgroup v1 cpusets controller" - depends on CPUSETS + depends on (CPUSETS && !SCHED_ALT) default n help Legacy cgroup v1 cpusets controller which has been deprecated by @@ -1486,6 +1518,7 @@ config CHECKPOINT_RESTORE config SCHED_AUTOGROUP bool "Automatic process group scheduling" + depends on !SCHED_ALT select CGROUPS select CGROUP_SCHED select FAIR_GROUP_SCHED diff --git a/init/init_task.c b/init/init_task.c index b67ef6040..37cb9c899 100644 --- a/init/init_task.c +++ b/init/init_task.c @@ -111,9 +111,16 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = { .stack = init_stack, .usage = REFCOUNT_INIT(2), .flags = PF_KTHREAD, +#ifdef CONFIG_SCHED_ALT + .on_cpu = 1, + .prio = DEFAULT_PRIO, + .static_prio = DEFAULT_PRIO, + .normal_prio = DEFAULT_PRIO, +#else .prio = MAX_PRIO - 20, .static_prio = MAX_PRIO - 20, .normal_prio = MAX_PRIO - 20, +#endif .policy = SCHED_NORMAL, .cpus_ptr = &init_task.cpus_mask, .user_cpus_ptr = NULL, @@ -126,6 +133,16 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = { .restart_block = { .fn = do_no_restart_syscall, }, +#ifdef CONFIG_SCHED_ALT + .sq_node = LIST_HEAD_INIT(init_task.sq_node), +#ifdef CONFIG_SCHED_BMQ + .boost_prio = 0, +#endif +#ifdef CONFIG_SCHED_PDS + .deadline = 0, +#endif + .time_slice = HZ, +#else .se = { .group_node = LIST_HEAD_INIT(init_task.se.group_node), }, @@ -133,10 +150,13 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = { .run_list = LIST_HEAD_INIT(init_task.rt.run_list), .time_slice = RR_TIMESLICE, }, +#endif .tasks = LIST_HEAD_INIT(init_task.tasks), +#ifndef CONFIG_SCHED_ALT #ifdef CONFIG_SMP .pushable_tasks = PLIST_NODE_INIT(init_task.pushable_tasks, MAX_PRIO), #endif +#endif #ifdef CONFIG_CGROUP_SCHED .sched_task_group = &root_task_group, #endif diff --git a/kernel/Kconfig.preempt b/kernel/Kconfig.preempt index 2471a502c..67aaddc46 100644 --- a/kernel/Kconfig.preempt +++ b/kernel/Kconfig.preempt @@ -150,7 +150,7 @@ config PREEMPT_DYNAMIC config SCHED_CORE bool "Core Scheduling for SMT" - depends on SCHED_SMT + depends on SCHED_SMT && !SCHED_ALT help This option permits Core Scheduling, a means of coordinated task selection across SMT siblings. When enabled -- see @@ -168,7 +168,7 @@ config SCHED_CORE config SCHED_CLASS_EXT bool "Extensible Scheduling Class" - depends on BPF_SYSCALL && BPF_JIT && DEBUG_INFO_BTF + depends on BPF_SYSCALL && BPF_JIT && DEBUG_INFO_BTF && !SCHED_ALT select STACKTRACE if STACKTRACE_SUPPORT help This option enables a new scheduler class sched_ext (SCX), which diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 45944b3e3..4f00f28ad 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -767,7 +767,7 @@ static int validate_change(struct cpuset *cur, struct cpuset *trial) return ret; } -#ifdef CONFIG_SMP +#if defined(CONFIG_SMP) && !defined(CONFIG_SCHED_ALT) /* * generate_sched_domains() @@ -1014,7 +1014,7 @@ void rebuild_sched_domains_locked(void) /* Have scheduler rebuild the domains */ partition_sched_domains(ndoms, doms, attr); } -#else /* !CONFIG_SMP */ +#else /* !CONFIG_SMP || CONFIG_SCHED_ALT */ void rebuild_sched_domains_locked(void) { } @@ -3003,7 +3003,7 @@ static int cpuset_can_attach(struct cgroup_taskset *tset) struct cpuset *cs, *oldcs; struct task_struct *task; bool setsched_check; - int cpu, ret; + int ret; /* used later by cpuset_attach() */ cpuset_attach_old_cs = task_cs(cgroup_taskset_first(tset, &css)); @@ -3047,6 +3047,7 @@ static int cpuset_can_attach(struct cgroup_taskset *tset) goto out_unlock; } +#ifndef CONFIG_SCHED_ALT if (dl_task(task)) { /* * Count all migrating DL tasks for cpuset task accounting. @@ -3057,12 +3058,14 @@ static int cpuset_can_attach(struct cgroup_taskset *tset) if (dl_task_needs_bw_move(task, cs->effective_cpus)) cs->sum_migrate_dl_bw += task->dl.dl_bw; } +#endif } +#ifndef CONFIG_SCHED_ALT if (!cs->sum_migrate_dl_bw) goto out_success; - cpu = cpumask_any_and(cpu_active_mask, cs->effective_cpus); + int cpu = cpumask_any_and(cpu_active_mask, cs->effective_cpus); if (unlikely(cpu >= nr_cpu_ids)) { ret = -EINVAL; goto out_unlock; @@ -3075,6 +3078,7 @@ static int cpuset_can_attach(struct cgroup_taskset *tset) cs->dl_bw_cpu = cpu; out_success: +#endif /* * Mark attach is in progress. This makes validate_change() fail * changes which zero cpus/mems_allowed. @@ -3099,11 +3103,13 @@ static void cpuset_cancel_attach(struct cgroup_taskset *tset) mutex_lock(&cpuset_mutex); dec_attach_in_progress_locked(cs); +#ifndef CONFIG_SCHED_ALT if (cs->dl_bw_cpu >= 0) dl_bw_free(cs->dl_bw_cpu, cs->sum_migrate_dl_bw); if (cs->nr_migrate_dl_tasks) reset_migrate_dl_data(cs); +#endif mutex_unlock(&cpuset_mutex); } diff --git a/kernel/delayacct.c b/kernel/delayacct.c index 2e55c493c..5c2262a24 100644 --- a/kernel/delayacct.c +++ b/kernel/delayacct.c @@ -170,7 +170,7 @@ int delayacct_add_tsk(struct taskstats *d, struct task_struct *tsk) */ t1 = tsk->sched_info.pcount; t2 = tsk->sched_info.run_delay; - t3 = tsk->se.sum_exec_runtime; + t3 = tsk_seruntime(tsk); d->cpu_count += t1; diff --git a/kernel/exit.c b/kernel/exit.c index 2c0b1c029..68e767fa2 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -207,7 +207,7 @@ static void __exit_signal(struct release_task_post *post, struct task_struct *ts sig->inblock += task_io_get_inblock(tsk); sig->oublock += task_io_get_oublock(tsk); task_io_accounting_add(&sig->ioac, &tsk->ioac); - sig->sum_sched_runtime += tsk->se.sum_exec_runtime; + sig->sum_sched_runtime += tsk_seruntime(tsk); sig->nr_threads--; __unhash_process(post, tsk, group_dead); write_sequnlock(&sig->stats_lock); @@ -295,8 +295,8 @@ void release_task(struct task_struct *p) /* @thread_pid can't go away until free_pids() below */ proc_flush_pid(thread_pid); exit_cred_namespaces(p); - add_device_randomness(&p->se.sum_exec_runtime, - sizeof(p->se.sum_exec_runtime)); + add_device_randomness((const void*) &tsk_seruntime(p), + sizeof(unsigned long long)); free_pids(post.pids); release_thread(p); /* diff --git a/kernel/locking/rtmutex.c b/kernel/locking/rtmutex.c index 4728631ae..15772f680 100644 --- a/kernel/locking/rtmutex.c +++ b/kernel/locking/rtmutex.c @@ -372,7 +372,7 @@ waiter_update_prio(struct rt_mutex_waiter *waiter, struct task_struct *task) lockdep_assert(RB_EMPTY_NODE(&waiter->tree.entry)); waiter->tree.prio = __waiter_prio(task); - waiter->tree.deadline = task->dl.deadline; + waiter->tree.deadline = __tsk_deadline(task); } /* @@ -393,16 +393,20 @@ waiter_clone_prio(struct rt_mutex_waiter *waiter, struct task_struct *task) * Only use with rt_waiter_node_{less,equal}() */ #define task_to_waiter_node(p) \ - &(struct rt_waiter_node){ .prio = __waiter_prio(p), .deadline = (p)->dl.deadline } + &(struct rt_waiter_node){ .prio = __waiter_prio(p), .deadline = __tsk_deadline(p) } #define task_to_waiter(p) \ &(struct rt_mutex_waiter){ .tree = *task_to_waiter_node(p) } static __always_inline int rt_waiter_node_less(struct rt_waiter_node *left, struct rt_waiter_node *right) { +#ifdef CONFIG_SCHED_PDS + return (left->deadline < right->deadline); +#else if (left->prio < right->prio) return 1; +#ifndef CONFIG_SCHED_BMQ /* * If both waiters have dl_prio(), we check the deadlines of the * associated tasks. @@ -411,16 +415,22 @@ static __always_inline int rt_waiter_node_less(struct rt_waiter_node *left, */ if (dl_prio(left->prio)) return dl_time_before(left->deadline, right->deadline); +#endif return 0; +#endif } static __always_inline int rt_waiter_node_equal(struct rt_waiter_node *left, struct rt_waiter_node *right) { +#ifdef CONFIG_SCHED_PDS + return (left->deadline == right->deadline); +#else if (left->prio != right->prio) return 0; +#ifndef CONFIG_SCHED_BMQ /* * If both waiters have dl_prio(), we check the deadlines of the * associated tasks. @@ -429,8 +439,10 @@ static __always_inline int rt_waiter_node_equal(struct rt_waiter_node *left, */ if (dl_prio(left->prio)) return left->deadline == right->deadline; +#endif return 1; +#endif } static inline bool rt_mutex_steal(struct rt_mutex_waiter *waiter, diff --git a/kernel/locking/ww_mutex.h b/kernel/locking/ww_mutex.h index d62b49b53..a487885c3 100644 --- a/kernel/locking/ww_mutex.h +++ b/kernel/locking/ww_mutex.h @@ -282,6 +282,7 @@ __ww_ctx_less(struct ww_acquire_ctx *a, struct ww_acquire_ctx *b) /* equal static prio */ +#ifndef CONFIG_SCHED_ALT if (dl_prio(a_prio)) { if (dl_time_before(b->task->dl.deadline, a->task->dl.deadline)) @@ -291,6 +292,7 @@ __ww_ctx_less(struct ww_acquire_ctx *a, struct ww_acquire_ctx *b) b->task->dl.deadline)) return false; } +#endif /* equal prio */ } diff --git a/kernel/sched/Makefile b/kernel/sched/Makefile index b1f1a3670..b7f5f9931 100644 --- a/kernel/sched/Makefile +++ b/kernel/sched/Makefile @@ -36,7 +36,12 @@ endif # These compilation units have roughly the same size and complexity - so their # build parallelizes well and finishes roughly at once: # +ifdef CONFIG_SCHED_ALT +obj-y += alt_core.o +obj-$(CONFIG_SCHED_DEBUG) += alt_debug.o +else obj-y += core.o obj-y += fair.o +endif obj-y += build_policy.o obj-y += build_utility.o diff --git a/kernel/sched/alt_core.c b/kernel/sched/alt_core.c new file mode 100644 index 000000000..66919055f --- /dev/null +++ b/kernel/sched/alt_core.c @@ -0,0 +1,7694 @@ +/* + * kernel/sched/alt_core.c + * + * Core alternative kernel scheduler code and related syscalls + * + * Copyright (C) 1991-2002 Linus Torvalds + * + * 2009-08-13 Brainfuck deadline scheduling policy by Con Kolivas deletes + * a whole lot of those previous things. + * 2017-09-06 Priority and Deadline based Skip list multiple queue kernel + * scheduler by Alfred Chen. + * 2019-02-20 BMQ(BitMap Queue) kernel scheduler by Alfred Chen. + */ +#define INSTANTIATE_EXPORTED_MIGRATE_DISABLE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#define CREATE_TRACE_POINTS +#include +#include +#undef CREATE_TRACE_POINTS + +#include "sched.h" +#include "smp.h" + +#include "pelt.h" + +#include "../../io_uring/io-wq.h" +#include "../smpboot.h" + +/* Use CONFIG_PARAVIRT as this will avoid more #ifdef in arch code. */ +#ifdef CONFIG_PARAVIRT +struct static_key paravirt_steal_rq_enabled; +#endif + +EXPORT_TRACEPOINT_SYMBOL_GPL(ipi_send_cpu); +EXPORT_TRACEPOINT_SYMBOL_GPL(ipi_send_cpumask); + +/* + * Export tracepoints that act as a bare tracehook (ie: have no trace event + * associated with them) to allow external modules to probe them. + */ +EXPORT_TRACEPOINT_SYMBOL_GPL(pelt_irq_tp); + +#define sched_feat(x) (1) +/* + * Print a warning if need_resched is set for the given duration (if + * LATENCY_WARN is enabled). + * + * If sysctl_resched_latency_warn_once is set, only one warning will be shown + * per boot. + */ +__read_mostly int sysctl_resched_latency_warn_ms = 100; +__read_mostly int sysctl_resched_latency_warn_once = 1; + +#define ALT_SCHED_VERSION "v7.2-r0" + +#define STOP_PRIO (MAX_RT_PRIO - 1) + +/* + * Time slice + * (default: 4 msec, units: nanoseconds) + */ +unsigned int sysctl_sched_base_slice __read_mostly = (4 << 20); + +#include "alt_core.h" +#include "alt_topology.h" + +/* Reschedule if less than this many μs left */ +#define RESCHED_NS (100 << 10) + +/** + * sched_yield_type - Type of sched_yield() will be performed. + * 0: No yield. + * 1: Requeue task. (default) + */ +int sched_yield_type __read_mostly = 1; + +cpumask_t sched_rq_pending_mask ____cacheline_aligned_in_smp; + +DEFINE_PER_CPU_ALIGNED(cpumask_t [NR_CPU_AFFINITY_LEVELS], sched_cpu_topo_masks); +DEFINE_PER_CPU_ALIGNED(cpumask_t *, sched_cpu_llc_mask); +DEFINE_PER_CPU_ALIGNED(cpumask_t *, sched_cpu_topo_end_mask); + +DEFINE_STATIC_KEY_FALSE(sched_smt_present); +EXPORT_SYMBOL_GPL(sched_smt_present); + +cpumask_t sched_smt_mask ____cacheline_aligned_in_smp; + +/* + * Keep a unique ID per domain (we use the first CPUs number in the cpumask of + * the domain), this allows us to quickly tell if two cpus are in the same cache + * domain, see cpus_share_cache(). + */ +static DEFINE_PER_CPU_READ_MOSTLY(int, sd_llc_id); + +DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues); + +#ifndef prepare_arch_switch +# define prepare_arch_switch(next) do { } while (0) +#endif +#ifndef finish_arch_post_lock_switch +# define finish_arch_post_lock_switch() do { } while (0) +#endif + +static cpumask_t sched_preempt_mask[SCHED_QUEUE_BITS + 2] ____cacheline_aligned_in_smp; + +cpumask_t *const sched_idle_mask = &sched_preempt_mask[SCHED_QUEUE_BITS - 1]; +cpumask_t *const sched_sg_idle_mask = &sched_preempt_mask[SCHED_QUEUE_BITS]; +cpumask_t *const sched_pcore_idle_mask = &sched_preempt_mask[SCHED_QUEUE_BITS]; +cpumask_t *const sched_ecore_idle_mask = &sched_preempt_mask[SCHED_QUEUE_BITS + 1]; + +/* task function */ +static inline const struct cpumask *task_user_cpus(struct task_struct *p) +{ + if (!p->user_cpus_ptr) + return cpu_possible_mask; /* &init_task.cpus_mask */ + return p->user_cpus_ptr; +} + +/* sched_queue related functions */ +static inline void sched_queue_init(struct sched_queue *q) +{ + int i; + + bitmap_zero(q->bitmap, SCHED_QUEUE_BITS); + for(i = 0; i < SCHED_LEVELS; i++) + INIT_LIST_HEAD(&q->heads[i]); +} + +/* + * Init idle task and put into queue structure of rq + * IMPORTANT: may be called multiple times for a single cpu + */ +static inline void sched_queue_init_idle(struct sched_queue *q, + struct task_struct *idle) +{ + INIT_LIST_HEAD(&q->heads[IDLE_TASK_SCHED_PRIO]); + list_add_tail(&idle->sq_node, &q->heads[IDLE_TASK_SCHED_PRIO]); + idle->on_rq = TASK_ON_RQ_QUEUED; +} + +static atomic_t sched_prio_record = ATOMIC_INIT(0); + +/* water mark related functions */ +static inline void update_sched_preempt_mask(struct rq *rq) +{ + int prio = find_first_bit(rq->queue.bitmap, SCHED_QUEUE_BITS); + int last_prio = rq->prio; + + if (prio == last_prio) + return; + + rq->prio = prio; +#ifdef CONFIG_SCHED_PDS + rq->prio_idx = sched_prio2idx(rq->prio, rq); +#endif + + int cpu = cpu_of(rq); + bool set = prio > last_prio; + int low = set ? last_prio : prio; + int high = set ? prio : last_prio; + + if (IDLE_TASK_SCHED_PRIO == high) { + high -= 2; + if (set) + sched_set_idle_mask(cpu); + else + sched_clear_idle_mask(cpu); + } + int pr = atomic_read(&sched_prio_record); + if (low < pr && pr <= high) { + if (set) + cpumask_set_cpu(cpu, sched_preempt_mask + pr); + else + cpumask_clear_cpu(cpu, sched_preempt_mask + pr); + } +} + +/* need a wrapper since we may need to trace from modules */ +EXPORT_TRACEPOINT_SYMBOL(sched_set_state_tp); + +/* + * Call via the helper macro trace_set_current_state. + * Calls to this function MUST be guarded by a + * tracepoint_enabled(sched_set_state_tp) + */ +void __trace_set_current_state(int state_value) +{ + trace_call__sched_set_state_tp(current, state_value); +} +EXPORT_SYMBOL(__trace_set_current_state); + +/* + * Serialization rules: + * + * Lock order: + * + * p->pi_lock + * rq->lock + * hrtimer_cpu_base->lock (hrtimer_start() for bandwidth controls) + * + * rq1->lock + * rq2->lock where: rq1 < rq2 + * + * Regular state: + * + * Normal scheduling state is serialized by rq->lock. __schedule() takes the + * local CPU's rq->lock, it optionally removes the task from the runqueue and + * always looks at the local rq data structures to find the most eligible task + * to run next. + * + * Task enqueue is also under rq->lock, possibly taken from another CPU. + * Wakeups from another LLC domain might use an IPI to transfer the enqueue to + * the local CPU to avoid bouncing the runqueue state around [ see + * ttwu_queue_wakelist() ] + * + * Task wakeup, specifically wakeups that involve migration, are horribly + * complicated to avoid having to take two rq->locks. + * + * Special state: + * + * System-calls and anything external will use task_rq_lock() which acquires + * both p->pi_lock and rq->lock. As a consequence the state they change is + * stable while holding either lock: + * + * - sched_setaffinity()/ + * set_cpus_allowed_ptr(): p->cpus_ptr, p->nr_cpus_allowed + * - set_user_nice(): p->se.load, p->*prio + * - __sched_setscheduler(): p->sched_class, p->policy, p->*prio, + * p->se.load, p->rt_priority, + * p->dl.dl_{runtime, deadline, period, flags, bw, density} + * - sched_setnuma(): p->numa_preferred_nid + * - sched_move_task(): p->sched_task_group + * - uclamp_update_active() p->uclamp* + * + * p->state <- TASK_*: + * + * is changed locklessly using set_current_state(), __set_current_state() or + * set_special_state(), see their respective comments, or by + * try_to_wake_up(). This latter uses p->pi_lock to serialize against + * concurrent self. + * + * p->on_rq <- { 0, 1 = TASK_ON_RQ_QUEUED, 2 = TASK_ON_RQ_MIGRATING }: + * + * is set by activate_task() and cleared by deactivate_task()/block_task(), + * under rq->lock. Non-zero indicates the task is runnable, the special + * ON_RQ_MIGRATING state is used for migration without holding both + * rq->locks. It indicates task_cpu() is not stable, see task_rq_lock(). + * + * Additionally it is possible to be ->on_rq but still be considered not + * runnable when p->se.sched_delayed is true. These tasks are on the runqueue + * but will be dequeued as soon as they get picked again. See the + * task_is_runnable() helper. + * + * p->on_cpu <- { 0, 1 }: + * + * is set by prepare_task() and cleared by finish_task() such that it will be + * set before p is scheduled-in and cleared after p is scheduled-out, both + * under rq->lock. Non-zero indicates the task is running on its CPU. + * + * [ The astute reader will observe that it is possible for two tasks on one + * CPU to have ->on_cpu = 1 at the same time. ] + * + * p->is_blocked <- { 0, 1 }: + * + * is set by try_to_block_task() and cleared by ttwu_do_wakeup() and tracks + * if the task is blocked. Traditionally this would mirror p->on_rq, however + * due things like DELAY_DEQUEUE and PROXY_EXEC, this can diverge. + * + * task_cpu(p): is changed by set_task_cpu(), the rules are: + * + * - Don't call set_task_cpu() on a blocked task: + * + * We don't care what CPU we're not running on, this simplifies hotplug, + * the CPU assignment of blocked tasks isn't required to be valid. + * + * - for try_to_wake_up(), called under p->pi_lock: + * + * This allows try_to_wake_up() to only take one rq->lock, see its comment. + * + * - for migration called under rq->lock: + * [ see task_on_rq_migrating() in task_rq_lock() ] + * + * o move_queued_task() + * o detach_task() + * + * - for migration called under double_rq_lock(): + * + * o __migrate_swap_task() + * o push_rt_task() / pull_rt_task() + * o push_dl_task() / pull_dl_task() + * o dl_task_offline_migration() + * + */ + +/* + * Context: p->pi_lock + */ +static inline struct rq * +task_access_lock_irqsave(struct task_struct *p, raw_spinlock_t **plock, unsigned long *flags) +{ + struct rq *rq; + for (;;) { + rq = task_rq(p); + if (p->on_cpu || task_on_rq_queued(p)) { + raw_spin_lock_irqsave(&rq->lock, *flags); + if (likely((p->on_cpu || task_on_rq_queued(p)) && rq == task_rq(p))) { + *plock = &rq->lock; + return rq; + } + raw_spin_unlock_irqrestore(&rq->lock, *flags); + } else if (task_on_rq_migrating(p)) { + do { + cpu_relax(); + } while (unlikely(task_on_rq_migrating(p))); + } else { + raw_spin_lock_irqsave(&p->pi_lock, *flags); + if (likely(!p->on_cpu && !p->on_rq && rq == task_rq(p))) { + *plock = &p->pi_lock; + return rq; + } + raw_spin_unlock_irqrestore(&p->pi_lock, *flags); + } + } +} + +static inline void +task_access_unlock_irqrestore(struct task_struct *p, raw_spinlock_t *lock, unsigned long *flags) +{ + raw_spin_unlock_irqrestore(lock, *flags); +} + +/* + * __task_rq_lock - lock the rq @p resides on. + */ +struct rq *__task_rq_lock(struct task_struct *p, struct rq_flags *rf) + __acquires(rq->lock) +{ + struct rq *rq; + + lockdep_assert_held(&p->pi_lock); + + for (;;) { + rq = task_rq(p); + raw_spin_lock(&rq->lock); + if (likely(rq == task_rq(p) && !task_on_rq_migrating(p))) + return rq; + raw_spin_unlock(&rq->lock); + + while (unlikely(task_on_rq_migrating(p))) + cpu_relax(); + } +} + +/* + * task_rq_lock - lock p->pi_lock and lock the rq @p resides on. + */ +struct rq *task_rq_lock(struct task_struct *p, struct rq_flags *rf) + __acquires(p->pi_lock) + __acquires(rq->lock) +{ + struct rq *rq; + + for (;;) { + raw_spin_lock_irqsave(&p->pi_lock, rf->flags); + rq = task_rq(p); + raw_spin_lock(&rq->lock); + /* + * move_queued_task() task_rq_lock() + * + * ACQUIRE (rq->lock) + * [S] ->on_rq = MIGRATING [L] rq = task_rq() + * WMB (__set_task_cpu()) ACQUIRE (rq->lock); + * [S] ->cpu = new_cpu [L] task_rq() + * [L] ->on_rq + * RELEASE (rq->lock) + * + * If we observe the old CPU in task_rq_lock(), the acquire of + * the old rq->lock will fully serialize against the stores. + * + * If we observe the new CPU in task_rq_lock(), the address + * dependency headed by '[L] rq = task_rq()' and the acquire + * will pair with the WMB to ensure we then also see migrating. + */ + if (likely(rq == task_rq(p) && !task_on_rq_migrating(p))) { + return rq; + } + raw_spin_unlock(&rq->lock); + raw_spin_unlock_irqrestore(&p->pi_lock, rf->flags); + + while (unlikely(task_on_rq_migrating(p))) + cpu_relax(); + } +} + +static inline void rq_lock_irqsave(struct rq *rq, struct rq_flags *rf) + __acquires(rq->lock) +{ + raw_spin_lock_irqsave(&rq->lock, rf->flags); +} + +static inline void rq_unlock_irqrestore(struct rq *rq, struct rq_flags *rf) + __releases(rq->lock) +{ + raw_spin_unlock_irqrestore(&rq->lock, rf->flags); +} + +DEFINE_LOCK_GUARD_1(rq_lock_irqsave, struct rq, + rq_lock_irqsave(_T->lock, &_T->rf), + rq_unlock_irqrestore(_T->lock, &_T->rf), + struct rq_flags rf) + +void raw_spin_rq_lock_nested(struct rq *rq, int subclass) +{ + raw_spinlock_t *lock; + + /* Matches synchronize_rcu() in __sched_core_enable() */ + preempt_disable(); + + for (;;) { + lock = __rq_lockp(rq); + raw_spin_lock_nested(lock, subclass); + if (likely(lock == __rq_lockp(rq))) { + /* preempt_count *MUST* be > 1 */ + preempt_enable_no_resched(); + return; + } + raw_spin_unlock(lock); + } +} + +/* + * RQ-clock updating methods: + */ + +static void update_rq_clock_task(struct rq *rq, s64 delta) +{ +/* + * In theory, the compile should just see 0 here, and optimize out the call + * to sched_rt_avg_update. But I don't trust it... + */ + s64 __maybe_unused steal = 0, irq_delta = 0; + +#ifdef CONFIG_IRQ_TIME_ACCOUNTING + if (irqtime_enabled()) { + irq_delta = irq_time_read(cpu_of(rq)) - rq->prev_irq_time; + + /* + * Since irq_time is only updated on {soft,}irq_exit, we might run into + * this case when a previous update_rq_clock() happened inside a + * {soft,}IRQ region. + * + * When this happens, we stop ->clock_task and only update the + * prev_irq_time stamp to account for the part that fit, so that a next + * update will consume the rest. This ensures ->clock_task is + * monotonic. + * + * It does however cause some slight miss-attribution of {soft,}IRQ + * time, a more accurate solution would be to update the irq_time using + * the current rq->clock timestamp, except that would require using + * atomic ops. + */ + if (irq_delta > delta) + irq_delta = delta; + + rq->prev_irq_time += irq_delta; + delta -= irq_delta; + delayacct_irq(rq->curr, irq_delta); + } +#endif +#ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING + if (static_key_false((¶virt_steal_rq_enabled))) { + u64 prev_steal; + + steal = prev_steal = paravirt_steal_clock(cpu_of(rq)); + steal -= rq->prev_steal_time_rq; + + if (unlikely(steal > delta)) + steal = delta; + + rq->prev_steal_time_rq = prev_steal; + delta -= steal; + } +#endif + + rq->clock_task += delta; + +#ifdef CONFIG_HAVE_SCHED_AVG_IRQ + if ((irq_delta + steal)) + update_irq_load_avg(rq, irq_delta + steal); +#endif +} + +static inline void update_rq_clock(struct rq *rq) +{ + s64 delta = sched_clock_cpu(cpu_of(rq)) - rq->clock; + + if (unlikely(delta <= 0)) + return; + rq->clock += delta; + sched_update_rq_clock(rq); + update_rq_clock_task(rq, delta); +} + +/* + * RQ Load update routine + */ +#define RQ_LOAD_HISTORY_BITS (sizeof(s32) * 8ULL) +#define RQ_UTIL_SHIFT (8) +#define RQ_LOAD_HISTORY_TO_UTIL(l) (((l) >> (RQ_LOAD_HISTORY_BITS - 1 - RQ_UTIL_SHIFT)) & 0xff) + +#define LOAD_BLOCK(t) ((t) >> 17) +#define LOAD_HALF_BLOCK(t) ((t) >> 16) +#define BLOCK_MASK(t) ((t) & ((0x01 << 18) - 1)) +#define LOAD_BLOCK_BIT(b) (1UL << (RQ_LOAD_HISTORY_BITS - 1 - (b))) +#define CURRENT_LOAD_BIT LOAD_BLOCK_BIT(0) + +static inline void rq_load_update(struct rq *rq) +{ + u64 time = rq->clock; + u64 delta = min(LOAD_BLOCK(time) - LOAD_BLOCK(rq->load_stamp), RQ_LOAD_HISTORY_BITS - 1); + u64 prev = !!(rq->load_history & CURRENT_LOAD_BIT); + u64 curr = !!rq->nr_running; + + if (delta) { + rq->load_history = rq->load_history >> delta; + + if (delta < RQ_UTIL_SHIFT) { + rq->load_block += (~BLOCK_MASK(rq->load_stamp)) * prev; + if (!!LOAD_HALF_BLOCK(rq->load_block) ^ curr) + rq->load_history ^= LOAD_BLOCK_BIT(delta); + } + + rq->load_block = BLOCK_MASK(time) * prev; + } else { + rq->load_block += (time - rq->load_stamp) * prev; + } + if (prev ^ curr) + rq->load_history ^= CURRENT_LOAD_BIT; + rq->load_stamp = time; +} + +unsigned long rq_load_util(struct rq *rq, unsigned long max) +{ + return RQ_LOAD_HISTORY_TO_UTIL(rq->load_history) * (max >> RQ_UTIL_SHIFT); +} + +unsigned long sched_cpu_util(int cpu) +{ + return rq_load_util(cpu_rq(cpu), arch_scale_cpu_capacity(cpu)); +} + +#ifdef CONFIG_CPU_FREQ +/** + * cpufreq_update_util - Take a note about CPU utilization changes. + * @rq: Runqueue to carry out the update for. + * @flags: Update reason flags. + * + * This function is called by the scheduler on the CPU whose utilization is + * being updated. + * + * It can only be called from RCU-sched read-side critical sections. + * + * The way cpufreq is currently arranged requires it to evaluate the CPU + * performance state (frequency/voltage) on a regular basis to prevent it from + * being stuck in a completely inadequate performance level for too long. + * That is not guaranteed to happen if the updates are only triggered from CFS + * and DL, though, because they may not be coming in if only RT tasks are + * active all the time (or there are RT tasks only). + * + * As a workaround for that issue, this function is called periodically by the + * RT sched class to trigger extra cpufreq updates to prevent it from stalling, + * but that really is a band-aid. Going forward it should be replaced with + * solutions targeted more specifically at RT tasks. + */ +static inline void cpufreq_update_util(struct rq *rq, unsigned int flags) +{ + struct update_util_data *data; + + rq_load_update(rq); + data = rcu_dereference_sched(*per_cpu_ptr(&cpufreq_update_util_data, cpu_of(rq))); + if (data) + data->func(data, rq_clock(rq), flags); +} +#else /* !CONFIG_CPU_FREQ: */ +static inline void cpufreq_update_util(struct rq *rq, unsigned int flags) +{ + rq_load_update(rq); +} +#endif /* !CONFIG_CPU_FREQ */ + +#ifdef CONFIG_NO_HZ_FULL +/* + * Tick may be needed by tasks in the runqueue depending on their policy and + * requirements. If tick is needed, lets send the target an IPI to kick it out + * of nohz mode if necessary. + */ +static inline void sched_update_tick_dependency(struct rq *rq) +{ + int cpu = cpu_of(rq); + + if (!tick_nohz_full_cpu(cpu)) + return; + + if (rq->nr_running < 2) + tick_nohz_dep_clear_cpu(cpu, TICK_DEP_BIT_SCHED); + else + tick_nohz_dep_set_cpu(cpu, TICK_DEP_BIT_SCHED); +} +#else /* !CONFIG_NO_HZ_FULL: */ +static inline void sched_update_tick_dependency(struct rq *rq) { } +#endif /* !CONFIG_NO_HZ_FULL */ + +static inline void add_nr_running(struct rq *rq, unsigned count) +{ + rq->nr_running += count; + if (rq->nr_running > 1) { + cpumask_set_cpu(cpu_of(rq), &sched_rq_pending_mask); + rq->prio_balance_time = rq->clock; + } + + sched_update_tick_dependency(rq); +} + +static inline void sub_nr_running(struct rq *rq, unsigned count) +{ + rq->nr_running -= count; + if (rq->nr_running < 2) { + cpumask_clear_cpu(cpu_of(rq), &sched_rq_pending_mask); + rq->prio_balance_time = 0; + } + + sched_update_tick_dependency(rq); +} + +bool sched_task_on_rq(struct task_struct *p) +{ + return task_on_rq_queued(p); +} + +unsigned long get_wchan(struct task_struct *p) +{ + unsigned long ip = 0; + unsigned int state; + + if (!p || p == current) + return 0; + + /* Only get wchan if task is blocked and we can keep it that way. */ + raw_spin_lock_irq(&p->pi_lock); + state = READ_ONCE(p->__state); + smp_rmb(); /* see try_to_wake_up() */ + if (state != TASK_RUNNING && state != TASK_WAKING && !p->on_rq) + ip = __get_wchan(p); + raw_spin_unlock_irq(&p->pi_lock); + + return ip; +} + +/* + * Add/Remove/Requeue task to/from the runqueue routines + * Context: rq->lock + */ +#define __SCHED_DEQUEUE_TASK(p, rq, flags, func) \ + sched_info_dequeue(rq, p); \ + \ + __list_del_entry(&p->sq_node); \ + if (p->sq_node.prev == p->sq_node.next) { \ + clear_bit(sched_idx2prio(p->sq_node.next - &rq->queue.heads[0], rq), \ + rq->queue.bitmap); \ + func; \ + } + +#define __SCHED_ENQUEUE_TASK(p, rq, flags, func) \ + sched_info_enqueue(rq, p); \ + { \ + int idx, prio; \ + TASK_SCHED_PRIO_IDX(p, rq, idx, prio); \ + list_add_tail(&p->sq_node, &rq->queue.heads[idx]); \ + if (list_is_first(&p->sq_node, &rq->queue.heads[idx])) { \ + set_bit(prio, rq->queue.bitmap); \ + func; \ + } \ + } + +static inline void __dequeue_task(struct task_struct *p, struct rq *rq) +{ +#ifdef ALT_SCHED_DEBUG + lockdep_assert_held(&rq->lock); + + /*printk(KERN_INFO "sched: dequeue(%d) %px %016llx\n", cpu_of(rq), p, p->deadline);*/ + WARN_ONCE(task_rq(p) != rq, "sched: dequeue task reside on cpu%d from cpu%d\n", + task_cpu(p), cpu_of(rq)); +#endif + + __SCHED_DEQUEUE_TASK(p, rq, flags, update_sched_preempt_mask(rq)); +} + +static inline void dequeue_task(struct task_struct *p, struct rq *rq, int flags) +{ + __dequeue_task(p, rq); + sub_nr_running(rq, 1); +} + +static inline void __enqueue_task(struct task_struct *p, struct rq *rq) +{ +#ifdef ALT_SCHED_DEBUG + lockdep_assert_held(&rq->lock); + + /*printk(KERN_INFO "sched: enqueue(%d) %px %d\n", cpu_of(rq), p, p->prio);*/ + WARN_ONCE(task_rq(p) != rq, "sched: enqueue task reside on cpu%d to cpu%d\n", + task_cpu(p), cpu_of(rq)); +#endif + + __SCHED_ENQUEUE_TASK(p, rq, flags, update_sched_preempt_mask(rq)); +} + +static inline void enqueue_task(struct task_struct *p, struct rq *rq, int flags) +{ + __enqueue_task(p, rq); + add_nr_running(rq, 1); +} + +void requeue_task(struct task_struct *p, struct rq *rq) +{ + struct list_head *node = &p->sq_node; + int deq_idx, idx, prio; + + TASK_SCHED_PRIO_IDX(p, rq, idx, prio); +#ifdef ALT_SCHED_DEBUG + lockdep_assert_held(&rq->lock); + /*printk(KERN_INFO "sched: requeue(%d) %px %016llx\n", cpu_of(rq), p, p->deadline);*/ + WARN_ONCE(task_rq(p) != rq, "sched: cpu[%d] requeue task reside on cpu%d\n", + cpu_of(rq), task_cpu(p)); +#endif + if (list_is_last(node, &rq->queue.heads[idx])) + return; + + __list_del_entry(node); + if (node->prev == node->next && (deq_idx = node->next - &rq->queue.heads[0]) != idx) + clear_bit(sched_idx2prio(deq_idx, rq), rq->queue.bitmap); + + list_add_tail(node, &rq->queue.heads[idx]); + if (list_is_first(node, &rq->queue.heads[idx])) + set_bit(prio, rq->queue.bitmap); + update_sched_preempt_mask(rq); +} + +/* + * try_cmpxchg based fetch_or() macro so it works for different integer types: + */ +#define fetch_or(ptr, mask) \ + ({ \ + typeof(ptr) _ptr = (ptr); \ + typeof(mask) _mask = (mask); \ + typeof(*_ptr) _val = *_ptr; \ + \ + do { \ + } while (!try_cmpxchg(_ptr, &_val, _val | _mask)); \ + _val; \ +}) + +#ifdef TIF_POLLING_NRFLAG +/* + * Atomically set TIF_NEED_RESCHED and test for TIF_POLLING_NRFLAG, + * this avoids any races wrt polling state changes and thereby avoids + * spurious IPIs. + */ +static inline bool set_nr_and_not_polling(struct thread_info *ti, int tif) +{ + return !(fetch_or(&ti->flags, 1 << tif) & _TIF_POLLING_NRFLAG); +} + +/* + * Atomically set TIF_NEED_RESCHED if TIF_POLLING_NRFLAG is set. + * + * If this returns true, then the idle task promises to call + * sched_ttwu_pending() and reschedule soon. + */ +static bool set_nr_if_polling(struct task_struct *p) +{ + struct thread_info *ti = task_thread_info(p); + typeof(ti->flags) val = READ_ONCE(ti->flags); + + do { + if (!(val & _TIF_POLLING_NRFLAG)) + return false; + if (val & _TIF_NEED_RESCHED) + return true; + } while (!try_cmpxchg(&ti->flags, &val, val | _TIF_NEED_RESCHED)); + + return true; +} + +#else /* !TIF_POLLING_NRFLAG: */ +static inline bool set_nr_and_not_polling(struct thread_info *ti, int tif) +{ + set_ti_thread_flag(ti, tif); + return true; +} + +static inline bool set_nr_if_polling(struct task_struct *p) +{ + return false; +} +#endif /* !TIF_POLLING_NRFLAG */ + +static bool __wake_q_add(struct wake_q_head *head, struct task_struct *task) +{ + struct wake_q_node *node = &task->wake_q; + + /* + * Atomically grab the task, if ->wake_q is !nil already it means + * it's already queued (either by us or someone else) and will get the + * wakeup due to that. + * + * In order to ensure that a pending wakeup will observe our pending + * state, even in the failed case, an explicit smp_mb() must be used. + */ + smp_mb__before_atomic(); + if (unlikely(cmpxchg_relaxed(&node->next, NULL, WAKE_Q_TAIL))) + return false; + + /* + * The head is context local, there can be no concurrency. + */ + *head->lastp = node; + head->lastp = &node->next; + return true; +} + +/** + * wake_q_add() - queue a wakeup for 'later' waking. + * @head: the wake_q_head to add @task to + * @task: the task to queue for 'later' wakeup + * + * Queue a task for later wakeup, most likely by the wake_up_q() call in the + * same context, _HOWEVER_ this is not guaranteed, the wakeup can come + * instantly. + * + * This function must be used as-if it were wake_up_process(); IOW the task + * must be ready to be woken at this location. + */ +void wake_q_add(struct wake_q_head *head, struct task_struct *task) +{ + if (__wake_q_add(head, task)) + get_task_struct(task); +} + +/** + * wake_q_add_safe() - safely queue a wakeup for 'later' waking. + * @head: the wake_q_head to add @task to + * @task: the task to queue for 'later' wakeup + * + * Queue a task for later wakeup, most likely by the wake_up_q() call in the + * same context, _HOWEVER_ this is not guaranteed, the wakeup can come + * instantly. + * + * This function must be used as-if it were wake_up_process(); IOW the task + * must be ready to be woken at this location. + * + * This function is essentially a task-safe equivalent to wake_q_add(). Callers + * that already hold reference to @task can call the 'safe' version and trust + * wake_q to do the right thing depending whether or not the @task is already + * queued for wakeup. + */ +void wake_q_add_safe(struct wake_q_head *head, struct task_struct *task) +{ + if (!__wake_q_add(head, task)) + put_task_struct(task); +} + +void wake_up_q(struct wake_q_head *head) +{ + struct wake_q_node *node = head->first; + + while (node != WAKE_Q_TAIL) { + struct task_struct *task; + + task = container_of(node, struct task_struct, wake_q); + node = node->next; + /* pairs with cmpxchg_relaxed() in __wake_q_add() */ + WRITE_ONCE(task->wake_q.next, NULL); + /* Task can safely be re-inserted now. */ + + /* + * wake_up_process() executes a full barrier, which pairs with + * the queueing in wake_q_add() so as not to miss wakeups. + */ + wake_up_process(task); + put_task_struct(task); + } +} + +/* + * resched_curr - mark rq's current task 'to be rescheduled now'. + * + * On UP this means the setting of the need_resched flag, on SMP it + * might also involve a cross-CPU call to trigger the scheduler on + * the target CPU. + */ +static inline void __resched_curr(struct rq *rq, int tif) +{ + struct task_struct *curr = rq->curr; + struct thread_info *cti = task_thread_info(curr); + int cpu; + + lockdep_assert_held(&rq->lock); + + /* + * Always immediately preempt the idle task; no point in delaying doing + * actual work. + */ + if (is_idle_task(curr) && tif == TIF_NEED_RESCHED_LAZY) + tif = TIF_NEED_RESCHED; + + if (cti->flags & ((1 << tif) | _TIF_NEED_RESCHED)) + return; + + cpu = cpu_of(rq); + + trace_sched_set_need_resched_tp(curr, cpu, tif); + if (cpu == smp_processor_id()) { + set_ti_thread_flag(cti, tif); + if (tif == TIF_NEED_RESCHED) + set_preempt_need_resched(); + return; + } + + if (set_nr_and_not_polling(cti, tif)) { + if (tif == TIF_NEED_RESCHED) + smp_send_reschedule(cpu); + } else { + trace_sched_wake_idle_without_ipi(cpu); + } +} + +/* + * Calls to this function MUST be guarded by a + * tracepoint_enabled(sched_set_need_resched_tp) + */ +void __trace_set_need_resched(struct task_struct *curr, int tif) +{ + trace_call__sched_set_need_resched_tp(curr, smp_processor_id(), tif); +} +EXPORT_SYMBOL_GPL(__trace_set_need_resched); + +static inline void resched_curr(struct rq *rq) +{ + __resched_curr(rq, TIF_NEED_RESCHED); +} + +#ifdef CONFIG_PREEMPT_DYNAMIC +static DEFINE_STATIC_KEY_FALSE(sk_dynamic_preempt_lazy); +static __always_inline bool dynamic_preempt_lazy(void) +{ + return static_branch_unlikely(&sk_dynamic_preempt_lazy); +} +#else /* !CONFIG_PREEMPT_DYNAMIC: */ +static __always_inline bool dynamic_preempt_lazy(void) +{ + return IS_ENABLED(CONFIG_PREEMPT_LAZY); +} +#endif /* !CONFIG_PREEMPT_DYNAMIC */ + +static __always_inline int get_lazy_tif_bit(void) +{ + if (dynamic_preempt_lazy()) + return TIF_NEED_RESCHED_LAZY; + + return TIF_NEED_RESCHED; +} + +static inline void resched_curr_lazy(struct rq *rq) +{ + __resched_curr(rq, get_lazy_tif_bit()); +} + +void resched_cpu(int cpu) +{ + struct rq *rq = cpu_rq(cpu); + unsigned long flags; + + raw_spin_lock_irqsave(&rq->lock, flags); + if (cpu_online(cpu) || cpu == smp_processor_id()) + resched_curr(cpu_rq(cpu)); + raw_spin_unlock_irqrestore(&rq->lock, flags); +} + +#ifdef CONFIG_NO_HZ_COMMON +/* + * This routine will record that the CPU is going idle with tick stopped. + * This info will be used in performing idle load balancing in the future. + */ +void nohz_balance_enter_idle(int cpu) {} + +/* + * In the semi idle case, use the nearest busy CPU for migrating timers + * from an idle CPU. This is good for power-savings. + * + * We don't do similar optimization for completely idle system, as + * selecting an idle CPU will add more delays to the timers than intended + * (as that CPU's timer base may not be up to date wrt jiffies etc). + */ +int get_nohz_timer_target(void) +{ + int i, cpu = smp_processor_id(), default_cpu = -1; + struct cpumask *mask; + const struct cpumask *hk_mask; + + if (housekeeping_cpu(cpu, HK_TYPE_KERNEL_NOISE)) { + if (!idle_cpu(cpu)) + return cpu; + default_cpu = cpu; + } + + hk_mask = housekeeping_cpumask(HK_TYPE_KERNEL_NOISE); + + for (mask = per_cpu(sched_cpu_topo_masks, cpu); + mask < per_cpu(sched_cpu_topo_end_mask, cpu); mask++) + for_each_cpu_and(i, mask, hk_mask) + if (!idle_cpu(i)) + return i; + + if (default_cpu == -1) + default_cpu = housekeeping_any_cpu(HK_TYPE_KERNEL_NOISE); + cpu = default_cpu; + + return cpu; +} + +/* + * When add_timer_on() enqueues a timer into the timer wheel of an + * idle CPU then this timer might expire before the next timer event + * which is scheduled to wake up that CPU. In case of a completely + * idle system the next event might even be infinite time into the + * future. wake_up_idle_cpu() ensures that the CPU is woken up and + * leaves the inner idle loop so the newly added timer is taken into + * account when the CPU goes back to idle and evaluates the timer + * wheel for the next timer event. + */ +static inline void wake_up_idle_cpu(int cpu) +{ + struct rq *rq = cpu_rq(cpu); + + if (cpu == smp_processor_id()) + return; + + /* + * Set TIF_NEED_RESCHED and send an IPI if in the non-polling + * part of the idle loop. This forces an exit from the idle loop + * and a round trip to schedule(). Now this could be optimized + * because a simple new idle loop iteration is enough to + * re-evaluate the next tick. Provided some re-ordering of tick + * nohz functions that would need to follow TIF_NR_POLLING + * clearing: + * + * - On most architectures, a simple fetch_or on ti::flags with a + * "0" value would be enough to know if an IPI needs to be sent. + * + * - x86 needs to perform a last need_resched() check between + * monitor and mwait which doesn't take timers into account. + * There a dedicated TIF_TIMER flag would be required to + * fetch_or here and be checked along with TIF_NEED_RESCHED + * before mwait(). + * + * However, remote timer enqueue is not such a frequent event + * and testing of the above solutions didn't appear to report + * much benefits. + */ + if (set_nr_and_not_polling(task_thread_info(rq->idle), TIF_NEED_RESCHED)) + smp_send_reschedule(cpu); + else + trace_sched_wake_idle_without_ipi(cpu); +} + +static inline bool wake_up_full_nohz_cpu(int cpu) +{ + /* + * We just need the target to call irq_exit() and re-evaluate + * the next tick. The nohz full kick at least implies that. + * If needed we can still optimize that later with an + * empty IRQ. + */ + if (cpu_is_offline(cpu)) + return true; /* Don't try to wake offline CPUs. */ + if (tick_nohz_full_cpu(cpu)) { + if (cpu != smp_processor_id() || + tick_nohz_tick_stopped()) + tick_nohz_full_kick_cpu(cpu); + return true; + } + + return false; +} + +void wake_up_nohz_cpu(int cpu) +{ + if (!wake_up_full_nohz_cpu(cpu)) + wake_up_idle_cpu(cpu); +} + +static void nohz_csd_func(void *info) +{ + struct rq *rq = info; + int cpu = cpu_of(rq); + unsigned int flags; + + /* + * Release the rq::nohz_csd. + */ + flags = atomic_fetch_andnot(NOHZ_KICK_MASK, nohz_flags(cpu)); + WARN_ON(!(flags & NOHZ_KICK_MASK)); + + rq->idle_balance = idle_cpu(cpu); + if (rq->idle_balance) { + rq->nohz_idle_balance = flags; + __raise_softirq_irqoff(SCHED_SOFTIRQ); + } +} + +#endif /* CONFIG_NO_HZ_COMMON */ + +static inline void wakeup_preempt(struct rq *rq) +{ + if (sched_rq_first_task(rq) != rq->curr) + resched_curr(rq); +} + +static __always_inline +int __task_state_match(struct task_struct *p, unsigned int state) +{ + if (READ_ONCE(p->__state) & state) + return 1; + + if (READ_ONCE(p->saved_state) & state) + return -1; + + return 0; +} + +static __always_inline +int task_state_match(struct task_struct *p, unsigned int state) +{ + /* + * Serialize against current_save_and_set_rtlock_wait_state(), + * current_restore_rtlock_saved_state(), and __refrigerator(). + */ + guard(raw_spinlock_irq)(&p->pi_lock); + + return __task_state_match(p, state); +} + +/* + * wait_task_inactive - wait for a thread to unschedule. + * + * Wait for the thread to block in any of the states set in @match_state. + * If it changes, i.e. @p might have woken up, then return zero. When we + * succeed in waiting for @p to be off its CPU, we return a positive number + * (its total switch count). If a second call a short while later returns the + * same number, the caller can be sure that @p has remained unscheduled the + * whole time. + * + * The caller must ensure that the task *will* unschedule sometime soon, + * else this function might spin for a *long* time. This function can't + * be called with interrupts off, or it may introduce deadlock with + * smp_call_function() if an IPI is sent by the same process we are + * waiting to become inactive. + */ +unsigned long wait_task_inactive(struct task_struct *p, unsigned int match_state) +{ + unsigned long flags; + int running, queued, match; + unsigned long ncsw; + struct rq *rq; + raw_spinlock_t *lock; + + for (;;) { + rq = task_rq(p); + + /* + * If the task is actively running on another CPU + * still, just relax and busy-wait without holding + * any locks. + * + * NOTE! Since we don't hold any locks, it's not + * even sure that "rq" stays as the right runqueue! + * But we don't care, since this will return false + * if the runqueue has changed and p is actually now + * running somewhere else! + */ + while (task_on_cpu(p)) { + if (!task_state_match(p, match_state)) + return 0; + cpu_relax(); + } + + /* + * Ok, time to look more closely! We need the rq + * lock now, to be *sure*. If we're wrong, we'll + * just go back and repeat. + */ + task_access_lock_irqsave(p, &lock, &flags); + trace_sched_wait_task(p); + running = task_on_cpu(p); + queued = p->on_rq; + ncsw = 0; + if ((match = __task_state_match(p, match_state))) { + /* + * When matching on p->saved_state, consider this task + * still queued so it will wait. + */ + if (match < 0) + queued = 1; + ncsw = p->nvcsw | LONG_MIN; /* sets MSB */ + } + task_access_unlock_irqrestore(p, lock, &flags); + + /* + * If it changed from the expected state, bail out now. + */ + if (unlikely(!ncsw)) + break; + + /* + * Was it really running after all now that we + * checked with the proper locks actually held? + * + * Oops. Go back and try again.. + */ + if (unlikely(running)) { + cpu_relax(); + continue; + } + + /* + * It's not enough that it's not actively running, + * it must be off the runqueue _entirely_, and not + * preempted! + * + * So if it was still runnable (but just not actively + * running right now), it's preempted, and we should + * yield - it could be a while. + */ + if (unlikely(queued)) { + ktime_t to = NSEC_PER_SEC / HZ; + + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_hrtimeout(&to, HRTIMER_MODE_REL_HARD); + continue; + } + + /* + * Ahh, all good. It wasn't running, and it wasn't + * runnable, which means that it will never become + * running in the future either. We're all done! + */ + break; + } + + return ncsw; +} + +#ifdef CONFIG_SCHED_HRTICK +/* + * Use HR-timers to deliver accurate preemption points. + */ + +enum { + HRTICK_SCHED_NONE = 0, + HRTICK_SCHED_DEFER = BIT(1), + HRTICK_SCHED_START = BIT(2), + HRTICK_SCHED_REARM_HRTIMER = BIT(3) +}; + +static void __used hrtick_clear(struct rq *rq) +{ + if (hrtimer_active(&rq->hrtick_timer)) + hrtimer_cancel(&rq->hrtick_timer); +} + +/* + * High-resolution timer tick. + * Runs from hardirq context with interrupts disabled. + */ +static enum hrtimer_restart hrtick(struct hrtimer *timer) +{ + struct rq *rq = container_of(timer, struct rq, hrtick_timer); + + WARN_ON_ONCE(cpu_of(rq) != smp_processor_id()); + + raw_spin_lock(&rq->lock); + resched_curr(rq); + raw_spin_unlock(&rq->lock); + + return HRTIMER_NORESTART; +} + +/* + * Use hrtick when: + * - enabled by features + * - hrtimer is actually high res + */ +static inline bool hrtick_enabled(struct rq *rq) +{ + return cpu_active(cpu_of(rq)) && hrtimer_highres_enabled(); +} + +static inline bool hrtick_needs_rearm(struct hrtimer *timer, ktime_t expires) +{ + /* + * Queued is false when the timer is not started or currently + * running the callback. In both cases, restart. If queued check + * whether the expiry time actually changes substantially. + */ + return !hrtimer_is_queued(timer) || + abs(expires - hrtimer_get_expires(timer)) > 5000; +} + +static void hrtick_cond_restart(struct rq *rq) +{ + struct hrtimer *timer = &rq->hrtick_timer; + ktime_t time = rq->hrtick_time; + + if (hrtick_needs_rearm(timer, time)) + hrtimer_start(timer, time, HRTIMER_MODE_ABS_PINNED_HARD); +} + +/* + * called from hardirq (IPI) context + */ +static void __hrtick_start(void *arg) +{ + struct rq *rq = arg; + + raw_spin_lock(&rq->lock); + hrtick_cond_restart(rq); + raw_spin_unlock(&rq->lock); +} + +/* + * Called to set the hrtick timer state. + * + * called with rq->lock held and IRQs disabled + */ +static inline void hrtick_start(struct rq *rq, u64 delay) +{ + s64 delta; + + /* + * Don't schedule slices shorter than 10000ns, that just + * doesn't make sense and can cause timer DoS. + */ + delta = max_t(s64, delay, 10000LL); + + rq->hrtick_time = ktime_add_ns(ktime_get(), delta); + if (!hrtick_needs_rearm(&rq->hrtick_timer, rq->hrtick_time)) + return; + + /* + * If this is in the middle of schedule() only note the delay + * and let hrtick_schedule_exit() deal with it. + */ + if (rq->hrtick_sched) { + rq->hrtick_sched |= HRTICK_SCHED_START; + rq->hrtick_delay = delta; + return; + } + + if (rq == this_rq()) + hrtimer_start(&rq->hrtick_timer, rq->hrtick_time, HRTIMER_MODE_ABS_PINNED_HARD); + else + smp_call_function_single_async(cpu_of(rq), &rq->hrtick_csd); +} + +static inline void hrtick_schedule_enter(struct rq *rq) +{ + rq->hrtick_sched = HRTICK_SCHED_DEFER; + if (hrtimer_test_and_clear_rearm_deferred()) + rq->hrtick_sched |= HRTICK_SCHED_REARM_HRTIMER; +} + +static inline void hrtick_schedule_exit(struct rq *rq) +{ + if (rq->hrtick_sched & HRTICK_SCHED_START) { + rq->hrtick_time = ktime_add_ns(ktime_get(), rq->hrtick_delay); + hrtick_cond_restart(rq); + } else if (idle_rq(rq)) { + /* + * No need for using hrtimer_is_active(). The timer is CPU local + * and interrupts are disabled, so the callback cannot be + * running and the queued state is valid. + */ + if (hrtimer_is_queued(&rq->hrtick_timer)) + hrtimer_cancel(&rq->hrtick_timer); + } + + if (rq->hrtick_sched & HRTICK_SCHED_REARM_HRTIMER) + __hrtimer_rearm_deferred(); + + rq->hrtick_sched = HRTICK_SCHED_NONE; +} + +static void hrtick_rq_init(struct rq *rq) +{ + INIT_CSD(&rq->hrtick_csd, __hrtick_start, rq); + rq->hrtick_sched = HRTICK_SCHED_NONE; + hrtimer_setup(&rq->hrtick_timer, hrtick, CLOCK_MONOTONIC, + HRTIMER_MODE_REL_HARD | HRTIMER_MODE_LAZY_REARM); +} +#else /* !CONFIG_SCHED_HRTICK: */ +static inline void hrtick_clear(struct rq *rq) { } +static inline void hrtick_rq_init(struct rq *rq) { } +static inline void hrtick_schedule_enter(struct rq *rq) { } +static inline void hrtick_schedule_exit(struct rq *rq) { } +#endif /* !CONFIG_SCHED_HRTICK */ + +/* + * activate_task - move a task to the runqueue. + * + * Context: rq->lock + */ +static void activate_task(struct task_struct *p, struct rq *rq) +{ + enqueue_task(p, rq, ENQUEUE_WAKEUP); + + WRITE_ONCE(p->on_rq, TASK_ON_RQ_QUEUED); + ASSERT_EXCLUSIVE_WRITER(p->on_rq); + + /* + * If in_iowait is set, the code below may not trigger any cpufreq + * utilization updates, so do it here explicitly with the IOWAIT flag + * passed. + */ + cpufreq_update_util(rq, SCHED_CPUFREQ_IOWAIT * p->in_iowait); +} + +static void block_task(struct rq *rq, struct task_struct *p, unsigned long task_state) +{ + p->sched_contributes_to_load = + (task_state & TASK_UNINTERRUPTIBLE) && + !(task_state & TASK_NOLOAD) && + !(task_state & TASK_FROZEN); + + /* + * __schedule() ttwu() + * prev_state = prev->state; if (p->on_rq && ...) + * if (prev_state) goto out; + * p->on_rq = 0; smp_acquire__after_ctrl_dep(); + * p->state = TASK_WAKING + * + * Where __schedule() and ttwu() have matching control dependencies. + * + * After this, schedule() must not care about p->state any more. + */ + dequeue_task(p, rq, DEQUEUE_SLEEP); + + if (p->sched_contributes_to_load) + rq->nr_uninterruptible++; + + if (p->in_iowait) { + atomic_inc(&rq->nr_iowait); + delayacct_blkio_start(); + } + + ASSERT_EXCLUSIVE_WRITER(p->on_rq); + + /* + * The moment this write goes through, ttwu() can swoop in and migrate + * this task, rendering our rq->__lock ineffective. + * + * __schedule() try_to_wake_up() + * LOCK rq->__lock LOCK p->pi_lock + * pick_next_task() + * pick_next_task_fair() + * pick_next_entity() + * dequeue_entities() + * __block_task() + * RELEASE p->on_rq = 0 if (p->on_rq && ...) + * break; + * + * ACQUIRE (after ctrl-dep) + * + * cpu = select_task_rq(); + * set_task_cpu(p, cpu); + * ttwu_queue() + * ttwu_do_activate() + * LOCK rq->__lock + * activate_task() + * STORE p->on_rq = 1 + * UNLOCK rq->__lock + * + * Callers must ensure to not reference @p after this -- we no longer + * own it. + */ + smp_store_release(&p->on_rq, 0); +} + +static inline void __set_task_cpu(struct task_struct *p, unsigned int cpu) +{ + /* + * After ->cpu is set up to a new value, task_access_lock(p, ...) can be + * successfully executed on another CPU. We must ensure that updates of + * per-task data have been completed by this moment. + */ + smp_wmb(); + + WRITE_ONCE(task_thread_info(p)->cpu, cpu); + rseq_sched_set_ids_changed(p); +} + +void set_task_cpu(struct task_struct *p, unsigned int new_cpu) +{ + unsigned int state = READ_ONCE(p->__state); + + /* + * We should never call set_task_cpu() on a blocked task, + * ttwu() will sort out the placement. + */ + WARN_ON_ONCE(state != TASK_RUNNING && state != TASK_WAKING && !p->on_rq); + +#ifdef CONFIG_LOCKDEP + /* + * The caller should hold either p->pi_lock or rq->lock, when changing + * a task's CPU. ->pi_lock for waking tasks, rq->lock for runnable tasks. + * + * sched_move_task() holds both and thus holding either pins the cgroup, + * see task_group(). + */ + WARN_ON_ONCE(debug_locks && !(lockdep_is_held(&p->pi_lock) || + lockdep_is_held(&task_rq(p)->lock))); +#endif + /* + * Clearly, migrating tasks to offline CPUs is a fairly daft thing. + */ + WARN_ON_ONCE(!cpu_online(new_cpu)); + + WARN_ON_ONCE(is_migration_disabled(p)); + trace_sched_migrate_task(p, new_cpu); + + if (task_cpu(p) != new_cpu) + perf_event_task_migrate(p); + + __set_task_cpu(p, new_cpu); +} + +void ___migrate_enable(void) +{ + struct task_struct *p = current; + __do_set_cpus_ptr(p, &p->cpus_mask); +} +EXPORT_SYMBOL_GPL(___migrate_enable); + +void migrate_disable(void) +{ + __migrate_disable(); +} +EXPORT_SYMBOL_GPL(migrate_disable); + +void migrate_enable(void) +{ + __migrate_enable(); +} +EXPORT_SYMBOL_GPL(migrate_enable); + +static void __migrate_force_enable(struct task_struct *p, struct rq *rq) +{ + if (likely(p->cpus_ptr != &p->cpus_mask)) + __do_set_cpus_ptr(p, &p->cpus_mask); + p->migration_disabled = 0; + /* When p is migrate_disabled, rq->lock should be held */ + rq->nr_pinned--; +} + +static inline bool rq_has_pinned_tasks(struct rq *rq) +{ + return rq->nr_pinned; +} + +/* + * Per-CPU kthreads are allowed to run on !active && online CPUs, see + * __set_cpus_allowed_ptr() and select_fallback_rq(). + */ +static inline bool is_cpu_allowed(struct task_struct *p, int cpu) +{ + /* When not in the task's cpumask, no point in looking further. */ + if (!cpumask_test_cpu(cpu, p->cpus_ptr)) + return false; + + /* migrate_disabled() must be allowed to finish. */ + if (is_migration_disabled(p)) + return cpu_online(cpu); + + /* Non kernel threads are not allowed during either online or offline. */ + if (!(p->flags & PF_KTHREAD)) + return cpu_active(cpu) && task_cpu_possible(cpu, p); + + /* KTHREAD_IS_PER_CPU is always allowed. */ + if (kthread_is_per_cpu(p)) + return cpu_online(cpu); + + /* Regular kernel threads don't get to stay during offline. */ + if (cpu_dying(cpu)) + return false; + + /* But are allowed during online. */ + return cpu_online(cpu); +} + +/* + * This is how migration works: + * + * 1) we invoke migration_cpu_stop() on the target CPU using + * stop_one_cpu(). + * 2) stopper starts to run (implicitly forcing the migrated thread + * off the CPU) + * 3) it checks whether the migrated task is still in the wrong runqueue. + * 4) if it's in the wrong runqueue then the migration thread removes + * it and puts it into the right queue. + * 5) stopper completes and stop_one_cpu() returns and the migration + * is done. + */ + +/* + * move_queued_task - move a queued task to new rq. + * + * Returns (locked) new rq. Old rq's lock is released. + */ +struct rq *move_queued_task(struct rq *rq, struct task_struct *p, int new_cpu) +{ + lockdep_assert_held(&rq->lock); + + WRITE_ONCE(p->on_rq, TASK_ON_RQ_MIGRATING); + dequeue_task(p, rq, 0); + set_task_cpu(p, new_cpu); + raw_spin_unlock(&rq->lock); + + rq = cpu_rq(new_cpu); + + raw_spin_lock(&rq->lock); + WARN_ON_ONCE(task_cpu(p) != new_cpu); + + sched_task_sanity_check(p, rq); + enqueue_task(p, rq, 0); + WRITE_ONCE(p->on_rq, TASK_ON_RQ_QUEUED); + wakeup_preempt(rq); + + return rq; +} + +struct migration_arg { + struct task_struct *task; + int dest_cpu; +}; + +/* + * Move (not current) task off this CPU, onto the destination CPU. We're doing + * this because either it can't run here any more (set_cpus_allowed() + * away from this CPU, or CPU going down), or because we're + * attempting to rebalance this task on exec (sched_exec). + * + * So we race with normal scheduler movements, but that's OK, as long + * as the task is no longer on this CPU. + */ +static struct rq *__migrate_task(struct rq *rq, struct task_struct *p, int dest_cpu) +{ + /* Affinity changed (again). */ + if (!is_cpu_allowed(p, dest_cpu)) + return rq; + + return move_queued_task(rq, p, dest_cpu); +} + +/* + * migration_cpu_stop - this will be executed by a high-prio stopper thread + * and performs thread migration by bumping thread off CPU then + * 'pushing' onto another runqueue. + */ +static int migration_cpu_stop(void *data) +{ + struct migration_arg *arg = data; + struct task_struct *p = arg->task; + struct rq *rq = this_rq(); + unsigned long flags; + + /* + * The original target CPU might have gone down and we might + * be on another CPU but it doesn't matter. + */ + local_irq_save(flags); + /* + * We need to explicitly wake pending tasks before running + * __migrate_task() such that we will not miss enforcing cpus_ptr + * during wakeups, see set_cpus_allowed_ptr()'s TASK_WAKING test. + */ + flush_smp_call_function_queue(); + + raw_spin_lock(&p->pi_lock); + raw_spin_lock(&rq->lock); + /* + * If task_rq(p) != rq, it cannot be migrated here, because we're + * holding rq->lock, if p->on_rq == 0 it cannot get enqueued because + * we're holding p->pi_lock. + */ + if (task_rq(p) == rq && task_on_rq_queued(p)) { + update_rq_clock(rq); + rq = __migrate_task(rq, p, arg->dest_cpu); + } + raw_spin_unlock(&rq->lock); + raw_spin_unlock_irqrestore(&p->pi_lock, flags); + + return 0; +} + +static inline void mm_update_cpus_allowed(struct mm_struct *mm, const cpumask_t *affmask); + +static inline void +set_cpus_allowed_common(struct task_struct *p, struct affinity_context *ctx) +{ + cpumask_copy(&p->cpus_mask, ctx->new_mask); + p->nr_cpus_allowed = cpumask_weight(ctx->new_mask); + mm_update_cpus_allowed(p->mm, ctx->new_mask); + + /* + * Swap in a new user_cpus_ptr if SCA_USER flag set + */ + if (ctx->flags & SCA_USER) + swap(p->user_cpus_ptr, ctx->user_mask); +} + +static void +do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx) +{ + lockdep_assert_held(&p->pi_lock); + set_cpus_allowed_common(p, ctx); +} + +/* + * Used for kthread_bind() and select_fallback_rq(), in both cases the user + * affinity (if any) should be destroyed too. + */ +void set_cpus_allowed_force(struct task_struct *p, const struct cpumask *new_mask) +{ + struct affinity_context ac = { + .new_mask = new_mask, + .user_mask = NULL, + .flags = SCA_USER, /* clear the user requested mask */ + }; + union cpumask_rcuhead { + cpumask_t cpumask; + struct rcu_head rcu; + }; + + do_set_cpus_allowed(p, &ac); + + if (is_migration_disabled(p) && !cpumask_test_cpu(task_cpu(p), &p->cpus_mask)) + __migrate_force_enable(p, task_rq(p)); + + /* + * Because this is called with p->pi_lock held, it is not possible + * to use kfree() here (when PREEMPT_RT=y), therefore punt to using + * kfree_rcu(). + */ + kfree_rcu((union cpumask_rcuhead *)ac.user_mask, rcu); +} + +int dup_user_cpus_ptr(struct task_struct *dst, struct task_struct *src, + int node) +{ + cpumask_t *user_mask; + unsigned long flags; + + /* + * Always clear dst->user_cpus_ptr first as their user_cpus_ptr's + * may differ by now due to racing. + */ + dst->user_cpus_ptr = NULL; + + /* + * This check is racy and losing the race is a valid situation. + * It is not worth the extra overhead of taking the pi_lock on + * every fork/clone. + */ + if (data_race(!src->user_cpus_ptr)) + return 0; + + user_mask = alloc_user_cpus_ptr(node); + if (!user_mask) + return -ENOMEM; + + /* + * Use pi_lock to protect content of user_cpus_ptr + * + * Though unlikely, user_cpus_ptr can be reset to NULL by a concurrent + * do_set_cpus_allowed(). + */ + raw_spin_lock_irqsave(&src->pi_lock, flags); + if (src->user_cpus_ptr) { + swap(dst->user_cpus_ptr, user_mask); + cpumask_copy(dst->user_cpus_ptr, src->user_cpus_ptr); + } + raw_spin_unlock_irqrestore(&src->pi_lock, flags); + + if (unlikely(user_mask)) + kfree(user_mask); + + return 0; +} + +static inline struct cpumask *clear_user_cpus_ptr(struct task_struct *p) +{ + struct cpumask *user_mask = NULL; + + swap(p->user_cpus_ptr, user_mask); + + return user_mask; +} + +void release_user_cpus_ptr(struct task_struct *p) +{ + kfree(clear_user_cpus_ptr(p)); +} + +/** + * task_curr - is this task currently executing on a CPU? + * @p: the task in question. + * + * Return: 1 if the task is currently executing. 0 otherwise. + */ +inline int task_curr(const struct task_struct *p) +{ + return cpu_curr(task_cpu(p)) == p; +} + +/*** + * kick_process - kick a running thread to enter/exit the kernel + * @p: the to-be-kicked thread + * + * Cause a process which is running on another CPU to enter + * kernel-mode, without any delay. (to get signals handled.) + * + * NOTE: this function doesn't have to take the runqueue lock, + * because all it wants to ensure is that the remote task enters + * the kernel. If the IPI races and the task has been migrated + * to another CPU then no harm is done and the purpose has been + * achieved as well. + */ +void kick_process(struct task_struct *p) +{ + guard(preempt)(); + int cpu = task_cpu(p); + + if ((cpu != smp_processor_id()) && task_curr(p)) + smp_send_reschedule(cpu); +} +EXPORT_SYMBOL_GPL(kick_process); + +/* + * ->cpus_ptr is protected by both rq->lock and p->pi_lock + * + * A few notes on cpu_active vs cpu_online: + * + * - cpu_active must be a subset of cpu_online + * + * - on CPU-up we allow per-CPU kthreads on the online && !active CPU, + * see __set_cpus_allowed_ptr(). At this point the newly online + * CPU isn't yet part of the sched domains, and balancing will not + * see it. + * + * - on cpu-down we clear cpu_active() to mask the sched domains and + * avoid the load balancer to place new tasks on the to be removed + * CPU. Existing tasks will remain running there and will be taken + * off. + * + * This means that fallback selection must not select !active CPUs. + * And can assume that any active CPU must be online. Conversely + * select_task_rq() below may allow selection of !active CPUs in order + * to satisfy the above rules. + */ +static int select_fallback_rq(int cpu, struct task_struct *p) +{ + int nid = cpu_to_node(cpu); + const struct cpumask *nodemask = NULL; + enum { cpuset, possible, fail } state = cpuset; + int dest_cpu; + + /* + * If the node that the CPU is on has been offlined, cpu_to_node() + * will return -1. There is no CPU on the node, and we should + * select the CPU on the other node. + */ + if (nid != -1) { + nodemask = cpumask_of_node(nid); + + /* Look for allowed, online CPU in same node. */ + for_each_cpu(dest_cpu, nodemask) { + if (is_cpu_allowed(p, dest_cpu)) + return dest_cpu; + } + } + + for (;;) { + /* Any allowed, online CPU? */ + for_each_cpu(dest_cpu, p->cpus_ptr) { + if (!is_cpu_allowed(p, dest_cpu)) + continue; + goto out; + } + + /* No more Mr. Nice Guy. */ + switch (state) { + case cpuset: + if (cpuset_cpus_allowed_fallback(p)) { + state = possible; + break; + } + fallthrough; + case possible: + set_cpus_allowed_force(p, task_cpu_fallback_mask(p)); + state = fail; + break; + + case fail: + BUG(); + break; + } + } + +out: + if (state != cpuset) { + /* + * Don't tell them about moving exiting tasks or + * kernel threads (both mm NULL), since they never + * leave kernel. + */ + if (p->mm && printk_ratelimit()) { + printk_deferred("process %d (%s) no longer affine to cpu%d\n", + task_pid_nr(p), p->comm, cpu); + } + } + + return dest_cpu; +} + +static inline void +sched_preempt_mask_flush(cpumask_t *mask, int prio, int ref) +{ + int cpu; + + cpumask_copy(mask, sched_preempt_mask + ref); + if (prio < ref) { + for_each_clear_bit(cpu, cpumask_bits(mask), nr_cpumask_bits) { + if (prio < cpu_rq(cpu)->prio) + cpumask_set_cpu(cpu, mask); + } + } else { + for_each_cpu_andnot(cpu, mask, sched_idle_mask) { + if (prio >= cpu_rq(cpu)->prio) + cpumask_clear_cpu(cpu, mask); + } + } +} + +static inline int +preempt_mask_check(cpumask_t *preempt_mask, const cpumask_t *allow_mask, int prio) +{ + cpumask_t *mask = sched_preempt_mask + prio; + int pr = atomic_read(&sched_prio_record); + + if (pr != prio && SCHED_QUEUE_BITS - 1 != prio) { + sched_preempt_mask_flush(mask, prio, pr); + atomic_set(&sched_prio_record, prio); + } + + return cpumask_and(preempt_mask, allow_mask, mask); +} + +DEFINE_STATIC_CALL(sched_idle_select_func, cpumask_and); + +static inline int select_task_rq(struct task_struct *p) +{ + cpumask_t allow_mask, mask; + + if (unlikely(!cpumask_and(&allow_mask, p->cpus_ptr, cpu_active_mask))) + return select_fallback_rq(task_cpu(p), p); + + if (static_call(sched_idle_select_func)(&mask, &allow_mask, sched_idle_mask) || + preempt_mask_check(&mask, &allow_mask, task_sched_prio(p))) + return best_mask_cpu(task_cpu(p), &mask); + + return best_mask_cpu(task_cpu(p), &allow_mask); +} + +void sched_set_stop_task(int cpu, struct task_struct *stop) +{ + static struct lock_class_key stop_pi_lock; + struct sched_param stop_param = { .sched_priority = STOP_PRIO }; + struct sched_param start_param = { .sched_priority = 0 }; + struct task_struct *old_stop = cpu_rq(cpu)->stop; + + if (stop) { + /* + * Make it appear like a SCHED_FIFO task, its something + * userspace knows about and won't get confused about. + * + * Also, it will make PI more or less work without too + * much confusion -- but then, stop work should not + * rely on PI working anyway. + */ + sched_setscheduler_nocheck(stop, SCHED_FIFO, &stop_param); + + /* + * The PI code calls rt_mutex_setprio() with ->pi_lock held to + * adjust the effective priority of a task. As a result, + * rt_mutex_setprio() can trigger (RT) balancing operations, + * which can then trigger wakeups of the stop thread to push + * around the current task. + * + * The stop task itself will never be part of the PI-chain, it + * never blocks, therefore that ->pi_lock recursion is safe. + * Tell lockdep about this by placing the stop->pi_lock in its + * own class. + */ + lockdep_set_class(&stop->pi_lock, &stop_pi_lock); + } + + cpu_rq(cpu)->stop = stop; + + if (old_stop) { + /* + * Reset it back to a normal scheduling policy so that + * it can die in pieces. + */ + sched_setscheduler_nocheck(old_stop, SCHED_NORMAL, &start_param); + } +} + +static int affine_move_task(struct rq *rq, struct task_struct *p, int dest_cpu, + raw_spinlock_t *lock, unsigned long irq_flags) + __releases(rq->lock) + __releases(p->pi_lock) +{ + /* Can the task run on the task's current CPU? If so, we're done */ + if (!cpumask_test_cpu(task_cpu(p), &p->cpus_mask)) { + if (is_migration_disabled(p)) + __migrate_force_enable(p, rq); + + if (task_on_cpu(p) || READ_ONCE(p->__state) == TASK_WAKING) { + struct migration_arg arg = { p, dest_cpu }; + + /* Need help from migration thread: drop lock and wait. */ + __task_access_unlock(p, lock); + raw_spin_unlock_irqrestore(&p->pi_lock, irq_flags); + stop_one_cpu(cpu_of(rq), migration_cpu_stop, &arg); + return 0; + } + if (task_on_rq_queued(p)) { + /* + * OK, since we're going to drop the lock immediately + * afterwards anyway. + */ + update_rq_clock(rq); + rq = move_queued_task(rq, p, dest_cpu); + lock = &rq->lock; + } + } + __task_access_unlock(p, lock); + raw_spin_unlock_irqrestore(&p->pi_lock, irq_flags); + return 0; +} + +static int __set_cpus_allowed_ptr_locked(struct task_struct *p, + struct affinity_context *ctx, + struct rq *rq, + raw_spinlock_t *lock, + unsigned long irq_flags) +{ + const struct cpumask *cpu_allowed_mask = task_cpu_possible_mask(p); + const struct cpumask *cpu_valid_mask = cpu_active_mask; + bool kthread = p->flags & PF_KTHREAD; + int dest_cpu; + int ret = 0; + + if (kthread || is_migration_disabled(p)) { + /* + * Kernel threads are allowed on online && !active CPUs, + * however, during cpu-hot-unplug, even these might get pushed + * away if not KTHREAD_IS_PER_CPU. + * + * Specifically, migration_disabled() tasks must not fail the + * cpumask_any_and_distribute() pick below, esp. so on + * SCA_MIGRATE_ENABLE, otherwise we'll not call + * set_cpus_allowed_common() and actually reset p->cpus_ptr. + */ + cpu_valid_mask = cpu_online_mask; + } + + if (!kthread && !cpumask_subset(ctx->new_mask, cpu_allowed_mask)) { + ret = -EINVAL; + goto out; + } + + /* + * Must re-check here, to close a race against __kthread_bind(), + * sched_setaffinity() is not guaranteed to observe the flag. + */ + if ((ctx->flags & SCA_CHECK) && (p->flags & PF_NO_SETAFFINITY)) { + ret = -EINVAL; + goto out; + } + + if (cpumask_equal(&p->cpus_mask, ctx->new_mask)) + goto out; + + dest_cpu = cpumask_any_and(cpu_valid_mask, ctx->new_mask); + if (dest_cpu >= nr_cpu_ids) { + ret = -EINVAL; + goto out; + } + + do_set_cpus_allowed(p, ctx); + + return affine_move_task(rq, p, dest_cpu, lock, irq_flags); + +out: + __task_access_unlock(p, lock); + raw_spin_unlock_irqrestore(&p->pi_lock, irq_flags); + + return ret; +} + +/* + * Change a given task's CPU affinity. Migrate the thread to a + * is removed from the allowed bitmask. + * + * NOTE: the caller must have a valid reference to the task, the + * task must not exit() & deallocate itself prematurely. The + * call is not atomic; no spinlocks may be held. + */ +int __set_cpus_allowed_ptr(struct task_struct *p, + struct affinity_context *ctx) +{ + unsigned long irq_flags; + struct rq *rq; + raw_spinlock_t *lock; + + raw_spin_lock_irqsave(&p->pi_lock, irq_flags); + rq = __task_access_lock(p, &lock); + /* + * Masking should be skipped if SCA_USER or any of the SCA_MIGRATE_* + * flags are set. + */ + if (p->user_cpus_ptr && + !(ctx->flags & SCA_USER) && + cpumask_and(rq->scratch_mask, ctx->new_mask, p->user_cpus_ptr)) + ctx->new_mask = rq->scratch_mask; + + + return __set_cpus_allowed_ptr_locked(p, ctx, rq, lock, irq_flags); +} + +int set_cpus_allowed_ptr(struct task_struct *p, const struct cpumask *new_mask) +{ + struct affinity_context ac = { + .new_mask = new_mask, + .flags = 0, + }; + + return __set_cpus_allowed_ptr(p, &ac); +} +EXPORT_SYMBOL_GPL(set_cpus_allowed_ptr); + +/* + * Change a given task's CPU affinity to the intersection of its current + * affinity mask and @subset_mask, writing the resulting mask to @new_mask. + * If user_cpus_ptr is defined, use it as the basis for restricting CPU + * affinity or use cpu_online_mask instead. + * + * If the resulting mask is empty, leave the affinity unchanged and return + * -EINVAL. + */ +static int restrict_cpus_allowed_ptr(struct task_struct *p, + struct cpumask *new_mask, + const struct cpumask *subset_mask) +{ + struct affinity_context ac = { + .new_mask = new_mask, + .flags = 0, + }; + unsigned long irq_flags; + raw_spinlock_t *lock; + struct rq *rq; + int err; + + raw_spin_lock_irqsave(&p->pi_lock, irq_flags); + rq = __task_access_lock(p, &lock); + + if (!cpumask_and(new_mask, task_user_cpus(p), subset_mask)) { + err = -EINVAL; + goto err_unlock; + } + + return __set_cpus_allowed_ptr_locked(p, &ac, rq, lock, irq_flags); + +err_unlock: + __task_access_unlock(p, lock); + raw_spin_unlock_irqrestore(&p->pi_lock, irq_flags); + return err; +} + +/* + * Restrict the CPU affinity of task @p so that it is a subset of + * task_cpu_possible_mask() and point @p->user_cpus_ptr to a copy of the + * old affinity mask. If the resulting mask is empty, we warn and walk + * up the cpuset hierarchy until we find a suitable mask. + */ +void force_compatible_cpus_allowed_ptr(struct task_struct *p) +{ + cpumask_var_t new_mask; + const struct cpumask *override_mask = task_cpu_possible_mask(p); + + alloc_cpumask_var(&new_mask, GFP_KERNEL); + + /* + * __migrate_task() can fail silently in the face of concurrent + * offlining of the chosen destination CPU, so take the hotplug + * lock to ensure that the migration succeeds. + */ + cpus_read_lock(); + if (!cpumask_available(new_mask)) + goto out_set_mask; + + if (!restrict_cpus_allowed_ptr(p, new_mask, override_mask)) + goto out_free_mask; + + /* + * We failed to find a valid subset of the affinity mask for the + * task, so override it based on its cpuset hierarchy. + */ + cpuset_cpus_allowed(p, new_mask); + override_mask = new_mask; + +out_set_mask: + if (printk_ratelimit()) { + printk_deferred("Overriding affinity for process %d (%s) to CPUs %*pbl\n", + task_pid_nr(p), p->comm, + cpumask_pr_args(override_mask)); + } + + WARN_ON(set_cpus_allowed_ptr(p, override_mask)); +out_free_mask: + cpus_read_unlock(); + free_cpumask_var(new_mask); +} + +/* + * Restore the affinity of a task @p which was previously restricted by a + * call to force_compatible_cpus_allowed_ptr(). + * + * It is the caller's responsibility to serialise this with any calls to + * force_compatible_cpus_allowed_ptr(@p). + */ +void relax_compatible_cpus_allowed_ptr(struct task_struct *p) +{ + struct affinity_context ac = { + .new_mask = task_user_cpus(p), + .flags = 0, + }; + int ret; + + /* + * Try to restore the old affinity mask with __sched_setaffinity(). + * Cpuset masking will be done there too. + */ + ret = __sched_setaffinity(p, &ac); + WARN_ON_ONCE(ret); +} + +static void +ttwu_stat(struct task_struct *p, int cpu, int wake_flags) +{ + struct rq *rq; + + if (!schedstat_enabled()) + return; + + rq = this_rq(); + + if (cpu == rq->cpu) { + __schedstat_inc(rq->ttwu_local); + __schedstat_inc(p->stats.nr_wakeups_local); + } else { + /** Alt schedule FW ToDo: + * How to do ttwu_wake_remote + */ + } + + __schedstat_inc(rq->ttwu_count); + __schedstat_inc(p->stats.nr_wakeups); +} + +/* + * Mark the task runnable. + */ +static inline void ttwu_do_wakeup(struct task_struct *p) +{ + p->is_blocked = 0; + WRITE_ONCE(p->__state, TASK_RUNNING); + trace_sched_wakeup(p); +} + +static inline void +ttwu_do_activate(struct rq *rq, struct task_struct *p, int wake_flags) +{ + if (p->sched_contributes_to_load) + rq->nr_uninterruptible--; + + if (!(wake_flags & WF_MIGRATED) && p->in_iowait) { + delayacct_blkio_end(p); + atomic_dec(&task_rq(p)->nr_iowait); + } + + activate_task(p, rq); + wakeup_preempt(rq); + + ttwu_do_wakeup(p); +} + +/* + * Consider @p being inside a wait loop: + * + * for (;;) { + * set_current_state(TASK_UNINTERRUPTIBLE); + * + * if (CONDITION) + * break; + * + * schedule(); + * } + * __set_current_state(TASK_RUNNING); + * + * between set_current_state() and schedule(). In this case @p is still + * runnable, so all that needs doing is change p->state back to TASK_RUNNING in + * an atomic manner. + * + * By taking task_rq(p)->lock we serialize against schedule(), if @p->on_rq + * then schedule() must still happen and p->state can be changed to + * TASK_RUNNING. Otherwise we lost the race, schedule() has happened, and we + * need to do a full wakeup with enqueue. + * + * Returns: %true when the wakeup is done, + * %false otherwise. + */ +static int ttwu_runnable(struct task_struct *p, int wake_flags) +{ + struct rq *rq; + raw_spinlock_t *lock; + int ret = 0; + + rq = __task_access_lock(p, &lock); + if (task_on_rq_queued(p)) { + if (!task_on_cpu(p)) { + /* + * When on_rq && !on_cpu the task is preempted, see if + * it should preempt the task that is current now. + */ + update_rq_clock(rq); + wakeup_preempt(rq); + } + ttwu_do_wakeup(p); + ret = 1; + } + __task_access_unlock(p, lock); + + return ret; +} + +void sched_ttwu_pending(void *arg) +{ + struct llist_node *llist = arg; + struct rq *rq = this_rq(); + struct task_struct *p, *t; + struct rq_flags rf; + + if (!llist) + return; + + rq_lock_irqsave(rq, &rf); + update_rq_clock(rq); + + llist_for_each_entry_safe(p, t, llist, wake_entry.llist) { + if (WARN_ON_ONCE(p->on_cpu)) + smp_cond_load_acquire(&p->on_cpu, !VAL); + + if (WARN_ON_ONCE(task_cpu(p) != cpu_of(rq))) + set_task_cpu(p, cpu_of(rq)); + + ttwu_do_activate(rq, p, p->sched_remote_wakeup ? WF_MIGRATED : 0); + } + + /* + * Must be after enqueueing at least once task such that + * idle_cpu() does not observe a false-negative -- if it does, + * it is possible for select_idle_siblings() to stack a number + * of tasks on this CPU during that window. + * + * It is OK to clear ttwu_pending when another task pending. + * We will receive IPI after local IRQ enabled and then enqueue it. + * Since now nr_running > 0, idle_cpu() will always get correct result. + */ + WRITE_ONCE(rq->ttwu_pending, 0); + rq_unlock_irqrestore(rq, &rf); +} + +/* + * Prepare the scene for sending an IPI for a remote smp_call + * + * Returns true if the caller can proceed with sending the IPI. + * Returns false otherwise. + */ +bool call_function_single_prep_ipi(int cpu) +{ + if (set_nr_if_polling(cpu_rq(cpu)->idle)) { + trace_sched_wake_idle_without_ipi(cpu); + return false; + } + + return true; +} + +/* + * Queue a task on the target CPUs wake_list and wake the CPU via IPI if + * necessary. The wakee CPU on receipt of the IPI will queue the task + * via sched_ttwu_wakeup() for activation so the wakee incurs the cost + * of the wakeup instead of the waker. + */ +static void __ttwu_queue_wakelist(struct task_struct *p, int cpu, int wake_flags) +{ + struct rq *rq = cpu_rq(cpu); + + p->sched_remote_wakeup = !!(wake_flags & WF_MIGRATED); + + WRITE_ONCE(rq->ttwu_pending, 1); + __smp_call_single_queue(cpu, &p->wake_entry.llist); +} + +static inline bool ttwu_queue_cond(struct task_struct *p, int cpu) +{ + int this_cpu = smp_processor_id(); + + /* + * Do not complicate things with the async wake_list while the CPU is + * in hotplug state. + */ + if (!cpu_active(cpu)) + return false; + + /* Ensure the task will still be allowed to run on the CPU. */ + if (!cpumask_test_cpu(cpu, p->cpus_ptr)) + return false; + + /* + * If the CPU does not share cache, then queue the task on the + * remote rqs wakelist to avoid accessing remote data. + */ + if (!cpus_share_cache(this_cpu, cpu)) + return true; + + if (cpu == this_cpu) + return false; + + /* + * If the wakee cpu is idle, or the task is descheduling and the + * only running task on the CPU, then use the wakelist to offload + * the task activation to the idle (or soon-to-be-idle) CPU as + * the current CPU is likely busy. nr_running is checked to + * avoid unnecessary task stacking. + * + * Note that we can only get here with (wakee) p->on_rq=0, + * p->on_cpu can be whatever, we've done the dequeue, so + * the wakee has been accounted out of ->nr_running. + */ + if (!cpu_rq(cpu)->nr_running) + return true; + + return false; +} + +static bool ttwu_queue_wakelist(struct task_struct *p, int cpu, int wake_flags) +{ + if (__is_defined(ALT_SCHED_TTWU_QUEUE) && ttwu_queue_cond(p, cpu)) { + sched_clock_cpu(cpu); /* Sync clocks across CPUs */ + __ttwu_queue_wakelist(p, cpu, wake_flags); + return true; + } + + return false; +} + +void wake_up_if_idle(int cpu) +{ + struct rq *rq = cpu_rq(cpu); + + guard(rcu)(); + if (is_idle_task(rcu_dereference(rq->curr))) { + guard(raw_spinlock_irqsave)(&rq->lock); + if (is_idle_task(rq->curr)) + resched_curr(rq); + } +} + +extern struct static_key_false sched_asym_cpucapacity; + +static __always_inline bool sched_asym_cpucap_active(void) +{ + return static_branch_unlikely(&sched_asym_cpucapacity); +} + +bool cpus_equal_capacity(int this_cpu, int that_cpu) +{ + if (!sched_asym_cpucap_active()) + return true; + + if (this_cpu == that_cpu) + return true; + + return arch_scale_cpu_capacity(this_cpu) == arch_scale_cpu_capacity(that_cpu); +} + +bool cpus_share_cache(int this_cpu, int that_cpu) +{ + if (this_cpu == that_cpu) + return true; + + return per_cpu(sd_llc_id, this_cpu) == per_cpu(sd_llc_id, that_cpu); +} + +static inline void ttwu_queue(struct task_struct *p, int cpu, int wake_flags) +{ + struct rq *rq = cpu_rq(cpu); + + if (ttwu_queue_wakelist(p, cpu, wake_flags)) + return; + + raw_spin_lock(&rq->lock); + update_rq_clock(rq); + ttwu_do_activate(rq, p, wake_flags); + raw_spin_unlock(&rq->lock); +} + +/* + * Invoked from try_to_wake_up() to check whether the task can be woken up. + * + * The caller holds p::pi_lock if p != current or has preemption + * disabled when p == current. + * + * The rules of saved_state: + * + * The related locking code always holds p::pi_lock when updating + * p::saved_state, which means the code is fully serialized in both cases. + * + * For PREEMPT_RT, the lock wait and lock wakeups happen via TASK_RTLOCK_WAIT. + * No other bits set. This allows to distinguish all wakeup scenarios. + * + * For FREEZER, the wakeup happens via TASK_FROZEN. No other bits set. This + * allows us to prevent early wakeup of tasks before they can be run on + * asymmetric ISA architectures (eg ARMv9). + */ +static __always_inline +bool ttwu_state_match(struct task_struct *p, unsigned int state, int *success) +{ + int match; + + if (IS_ENABLED(CONFIG_DEBUG_PREEMPT)) { + WARN_ON_ONCE((state & TASK_RTLOCK_WAIT) && + state != TASK_RTLOCK_WAIT); + } + + *success = !!(match = __task_state_match(p, state)); + + /* + * Saved state preserves the task state across blocking on + * an RT lock or TASK_FREEZABLE tasks. If the state matches, + * set p::saved_state to TASK_RUNNING, but do not wake the task + * because it waits for a lock wakeup or __thaw_task(). Also + * indicate success because from the regular waker's point of + * view this has succeeded. + * + * After acquiring the lock the task will restore p::__state + * from p::saved_state which ensures that the regular + * wakeup is not lost. The restore will also set + * p::saved_state to TASK_RUNNING so any further tests will + * not result in false positives vs. @success + */ + if (match < 0) + p->saved_state = TASK_RUNNING; + + return match > 0; +} + +/* + * Notes on Program-Order guarantees on SMP systems. + * + * MIGRATION + * + * The basic program-order guarantee on SMP systems is that when a task [t] + * migrates, all its activity on its old CPU [c0] happens-before any subsequent + * execution on its new CPU [c1]. + * + * For migration (of runnable tasks) this is provided by the following means: + * + * A) UNLOCK of the rq(c0)->lock scheduling out task t + * B) migration for t is required to synchronize *both* rq(c0)->lock and + * rq(c1)->lock (if not at the same time, then in that order). + * C) LOCK of the rq(c1)->lock scheduling in task + * + * Transitivity guarantees that B happens after A and C after B. + * Note: we only require RCpc transitivity. + * Note: the CPU doing B need not be c0 or c1 + * + * Example: + * + * CPU0 CPU1 CPU2 + * + * LOCK rq(0)->lock + * sched-out X + * sched-in Y + * UNLOCK rq(0)->lock + * + * LOCK rq(0)->lock // orders against CPU0 + * dequeue X + * UNLOCK rq(0)->lock + * + * LOCK rq(1)->lock + * enqueue X + * UNLOCK rq(1)->lock + * + * LOCK rq(1)->lock // orders against CPU2 + * sched-out Z + * sched-in X + * UNLOCK rq(1)->lock + * + * + * BLOCKING -- aka. SLEEP + WAKEUP + * + * For blocking we (obviously) need to provide the same guarantee as for + * migration. However the means are completely different as there is no lock + * chain to provide order. Instead we do: + * + * 1) smp_store_release(X->on_cpu, 0) -- finish_task() + * 2) smp_cond_load_acquire(!X->on_cpu) -- try_to_wake_up() + * + * Example: + * + * CPU0 (schedule) CPU1 (try_to_wake_up) CPU2 (schedule) + * + * LOCK rq(0)->lock LOCK X->pi_lock + * dequeue X + * sched-out X + * smp_store_release(X->on_cpu, 0); + * + * smp_cond_load_acquire(&X->on_cpu, !VAL); + * X->state = WAKING + * set_task_cpu(X,2) + * + * LOCK rq(2)->lock + * enqueue X + * X->state = RUNNING + * UNLOCK rq(2)->lock + * + * LOCK rq(2)->lock // orders against CPU1 + * sched-out Z + * sched-in X + * UNLOCK rq(2)->lock + * + * UNLOCK X->pi_lock + * UNLOCK rq(0)->lock + * + * + * However; for wakeups there is a second guarantee we must provide, namely we + * must observe the state that lead to our wakeup. That is, not only must our + * task observe its own prior state, it must also observe the stores prior to + * its wakeup. + * + * This means that any means of doing remote wakeups must order the CPU doing + * the wakeup against the CPU the task is going to end up running on. This, + * however, is already required for the regular Program-Order guarantee above, + * since the waking CPU is the one issueing the ACQUIRE (smp_cond_load_acquire). + * + */ + +/** + * try_to_wake_up - wake up a thread + * @p: the thread to be awakened + * @state: the mask of task states that can be woken + * @wake_flags: wake modifier flags (WF_*) + * + * Conceptually does: + * + * If (@state & @p->state) @p->state = TASK_RUNNING. + * + * If the task was not queued/runnable, also place it back on a runqueue. + * + * This function is atomic against schedule() which would dequeue the task. + * + * It issues a full memory barrier before accessing @p->state, see the comment + * with set_current_state(). + * + * Uses p->pi_lock to serialize against concurrent wake-ups. + * + * Relies on p->pi_lock stabilizing: + * - p->sched_class + * - p->cpus_ptr + * - p->sched_task_group + * in order to do migration, see its use of select_task_rq()/set_task_cpu(). + * + * Tries really hard to only take one task_rq(p)->lock for performance. + * Takes rq->lock in: + * - ttwu_runnable() -- old rq, unavoidable, see comment there; + * - ttwu_queue() -- new rq, for enqueue of the task; + * - psi_ttwu_dequeue() -- much sadness :-( accounting will kill us. + * + * As a consequence we race really badly with just about everything. See the + * many memory barriers and their comments for details. + * + * Return: %true if @p->state changes (an actual wakeup was done), + * %false otherwise. + */ +int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags) +{ + guard(preempt)(); + int cpu, success = 0; + + if (p == current) { + /* + * We're waking current, this means 'p->on_rq' and 'task_cpu(p) + * == smp_processor_id()'. Together this means we can special + * case the whole 'p->on_rq && ttwu_runnable()' case below + * without taking any locks. + * + * In particular: + * - we rely on Program-Order guarantees for all the ordering, + * - we're serialized against set_special_state() by virtue of + * it disabling IRQs (this allows not taking ->pi_lock). + */ + WARN_ON_ONCE(p->is_blocked); + if (!ttwu_state_match(p, state, &success)) + goto out; + + trace_sched_waking(p); + ttwu_do_wakeup(p); + goto out; + } + + /* + * If we are going to wake up a thread waiting for CONDITION we + * need to ensure that CONDITION=1 done by the caller can not be + * reordered with p->state check below. This pairs with smp_store_mb() + * in set_current_state() that the waiting thread does. + */ + scoped_guard (raw_spinlock_irqsave, &p->pi_lock) { + smp_mb__after_spinlock(); + if (!ttwu_state_match(p, state, &success)) + break; + + trace_sched_waking(p); + + /* + * Ensure we load p->on_rq _after_ p->state, otherwise it would + * be possible to, falsely, observe p->on_rq == 0 and get stuck + * in smp_cond_load_acquire() below. + * + * sched_ttwu_pending() try_to_wake_up() + * STORE p->on_rq = 1 LOAD p->state + * UNLOCK rq->lock + * + * __schedule() (switch to task 'p') + * LOCK rq->lock smp_rmb(); + * smp_mb__after_spinlock(); + * UNLOCK rq->lock + * + * [task p] + * STORE p->state = UNINTERRUPTIBLE LOAD p->on_rq + * + * Pairs with the LOCK+smp_mb__after_spinlock() on rq->lock in + * __schedule(). See the comment for smp_mb__after_spinlock(). + * + * A similar smp_rmb() lives in __task_needs_rq_lock(). + */ + smp_rmb(); + if (READ_ONCE(p->on_rq) && ttwu_runnable(p, wake_flags)) + break; + + /* + * Ensure we load p->on_cpu _after_ p->on_rq, otherwise it would be + * possible to, falsely, observe p->on_cpu == 0. + * + * One must be running (->on_cpu == 1) in order to remove oneself + * from the runqueue. + * + * __schedule() (switch to task 'p') try_to_wake_up() + * STORE p->on_cpu = 1 LOAD p->on_rq + * UNLOCK rq->lock + * + * __schedule() (put 'p' to sleep) + * LOCK rq->lock smp_rmb(); + * smp_mb__after_spinlock(); + * STORE p->on_rq = 0 LOAD p->on_cpu + * + * Pairs with the LOCK+smp_mb__after_spinlock() on rq->lock in + * __schedule(). See the comment for smp_mb__after_spinlock(). + * + * Form a control-dep-acquire with p->on_rq == 0 above, to ensure + * schedule()'s block_task() has 'happened' and p will no longer + * care about it's own p->state. See the comment in __schedule(). + */ + smp_acquire__after_ctrl_dep(); + + /* + * We're doing the wakeup (@success == 1), they did a dequeue (p->on_rq + * == 0), which means we need to do an enqueue, change p->state to + * TASK_WAKING such that we can unlock p->pi_lock before doing the + * enqueue, such as ttwu_queue_wakelist(). + */ + WRITE_ONCE(p->__state, TASK_WAKING); + + /* + * If the owning (remote) CPU is still in the middle of schedule() with + * this task as prev, considering queueing p on the remote CPUs wake_list + * which potentially sends an IPI instead of spinning on p->on_cpu to + * let the waker make forward progress. This is safe because IRQs are + * disabled and the IPI will deliver after on_cpu is cleared. + * + * Ensure we load task_cpu(p) after p->on_cpu: + * + * set_task_cpu(p, cpu); + * STORE p->cpu = @cpu + * __schedule() (switch to task 'p') + * LOCK rq->lock + * smp_mb__after_spin_lock() smp_cond_load_acquire(&p->on_cpu) + * STORE p->on_cpu = 1 LOAD p->cpu + * + * to ensure we observe the correct CPU on which the task is currently + * scheduling. + */ + if (smp_load_acquire(&p->on_cpu) && + ttwu_queue_wakelist(p, task_cpu(p), wake_flags)) + break; + + /* + * If the owning (remote) CPU is still in the middle of schedule() with + * this task as prev, wait until it's done referencing the task. + * + * Pairs with the smp_store_release() in finish_task(). + * + * This ensures that tasks getting woken will be fully ordered against + * their previous state and preserve Program Order. + */ + smp_cond_load_acquire(&p->on_cpu, !VAL); + + sched_task_ttwu(p); + + if ((wake_flags & WF_CURRENT_CPU) && + cpumask_test_cpu(smp_processor_id(), p->cpus_ptr)) + cpu = smp_processor_id(); + else + cpu = select_task_rq(p); + + if (cpu != task_cpu(p)) { + if (p->in_iowait) { + delayacct_blkio_end(p); + atomic_dec(&task_rq(p)->nr_iowait); + } + + wake_flags |= WF_MIGRATED; + set_task_cpu(p, cpu); + } + + ttwu_queue(p, cpu, wake_flags); + } +out: + if (success) + ttwu_stat(p, task_cpu(p), wake_flags); + + return success; +} + +static bool __task_needs_rq_lock(struct task_struct *p) +{ + unsigned int state = READ_ONCE(p->__state); + + /* + * Since pi->lock blocks try_to_wake_up(), we don't need rq->lock when + * the task is blocked. Make sure to check @state since ttwu() can drop + * locks at the end, see ttwu_queue_wakelist(). + */ + if (state == TASK_RUNNING || state == TASK_WAKING) + return true; + + /* + * Ensure we load p->on_rq after p->__state, otherwise it would be + * possible to, falsely, observe p->on_rq == 0. + * + * See try_to_wake_up() for a longer comment. + */ + smp_rmb(); + if (p->on_rq) + return true; + + /* + * Ensure the task has finished __schedule() and will not be referenced + * anymore. Again, see try_to_wake_up() for a longer comment. + */ + smp_rmb(); + smp_cond_load_acquire(&p->on_cpu, !VAL); + + return false; +} + +/** + * task_call_func - Invoke a function on task in fixed state + * @p: Process for which the function is to be invoked, can be @current. + * @func: Function to invoke. + * @arg: Argument to function. + * + * Fix the task in it's current state by avoiding wakeups and or rq operations + * and call @func(@arg) on it. This function can use task_is_runnable() and + * task_curr() to work out what the state is, if required. Given that @func + * can be invoked with a runqueue lock held, it had better be quite + * lightweight. + * + * Returns: + * Whatever @func returns + */ +int task_call_func(struct task_struct *p, task_call_f func, void *arg) +{ + struct rq *rq = NULL; + struct rq_flags rf; + int ret; + + raw_spin_lock_irqsave(&p->pi_lock, rf.flags); + + if (__task_needs_rq_lock(p)) + rq = __task_rq_lock(p, &rf); + + /* + * At this point the task is pinned; either: + * - blocked and we're holding off wakeups (pi->lock) + * - woken, and we're holding off enqueue (rq->lock) + * - queued, and we're holding off schedule (rq->lock) + * - running, and we're holding off de-schedule (rq->lock) + * + * The called function (@func) can use: task_curr(), p->on_rq and + * p->__state to differentiate between these states. + */ + ret = func(p, arg); + + if (rq) + __task_rq_unlock(rq, &rf); + + raw_spin_unlock_irqrestore(&p->pi_lock, rf.flags); + return ret; +} + +/** + * cpu_curr_snapshot - Return a snapshot of the currently running task + * @cpu: The CPU on which to snapshot the task. + * + * Returns the task_struct pointer of the task "currently" running on + * the specified CPU. If the same task is running on that CPU throughout, + * the return value will be a pointer to that task's task_struct structure. + * If the CPU did any context switches even vaguely concurrently with the + * execution of this function, the return value will be a pointer to the + * task_struct structure of a randomly chosen task that was running on + * that CPU somewhere around the time that this function was executing. + * + * If the specified CPU was offline, the return value is whatever it + * is, perhaps a pointer to the task_struct structure of that CPU's idle + * task, but there is no guarantee. Callers wishing a useful return + * value must take some action to ensure that the specified CPU remains + * online throughout. + * + * This function executes full memory barriers before and after fetching + * the pointer, which permits the caller to confine this function's fetch + * with respect to the caller's accesses to other shared variables. + */ +struct task_struct *cpu_curr_snapshot(int cpu) +{ + struct task_struct *t; + + smp_mb(); /* Pairing determined by caller's synchronization design. */ + t = rcu_dereference(cpu_curr(cpu)); + smp_mb(); /* Pairing determined by caller's synchronization design. */ + return t; +} + +/** + * wake_up_process - Wake up a specific process + * @p: The process to be woken up. + * + * Attempt to wake up the nominated process and move it to the set of runnable + * processes. + * + * Return: 1 if the process was woken up, 0 if it was already running. + * + * This function executes a full memory barrier before accessing the task state. + */ +int wake_up_process(struct task_struct *p) +{ + return try_to_wake_up(p, TASK_NORMAL, 0); +} +EXPORT_SYMBOL(wake_up_process); + +int wake_up_state(struct task_struct *p, unsigned int state) +{ + return try_to_wake_up(p, state, 0); +} + +/* + * Perform scheduler related setup for a newly forked process p. + * p is forked by current. + * + * __sched_fork() is basic setup which is also used by sched_init() to + * initialize the boot CPU's idle task. + */ +static inline void __sched_fork(u64 clone_flags, struct task_struct *p) +{ + p->on_rq = 0; + p->on_cpu = 0; + p->utime = 0; + p->stime = 0; + p->sched_time = 0; + + WARN_ON_ONCE(p->is_blocked); + +#ifdef CONFIG_SCHEDSTATS + /* Even if schedstat is disabled, there should not be garbage */ + memset(&p->stats, 0, sizeof(p->stats)); +#endif + +#ifdef CONFIG_PREEMPT_NOTIFIERS + INIT_HLIST_HEAD(&p->preempt_notifiers); +#endif + +#ifdef CONFIG_COMPACTION + p->capture_control = NULL; +#endif + p->wake_entry.u_flags = CSD_TYPE_TTWU; +} + +/* + * fork()/clone()-time setup: + */ +int sched_fork(u64 clone_flags, struct task_struct *p) +{ + __sched_fork(clone_flags, p); + /* + * We mark the process as NEW here. This guarantees that + * nobody will actually run it, and a signal or other external + * event cannot wake it up and insert it on the runqueue either. + */ + p->__state = TASK_NEW; + + /* + * Make sure we do not leak PI boosting priority to the child. + */ + p->prio = current->normal_prio; + + /* + * Revert to default priority/policy on fork if requested. + */ + if (unlikely(p->sched_reset_on_fork)) { + if (task_has_rt_policy(p)) { + p->policy = SCHED_NORMAL; + p->static_prio = NICE_TO_PRIO(0); + p->rt_priority = 0; + p->timer_slack_ns = p->default_timer_slack_ns; + } else if (PRIO_TO_NICE(p->static_prio) < 0) + p->static_prio = NICE_TO_PRIO(0); + + p->prio = p->normal_prio = p->static_prio; + + /* + * We don't need the reset flag anymore after the fork. It has + * fulfilled its duty: + */ + p->sched_reset_on_fork = 0; + } + +#ifdef CONFIG_SCHED_INFO + if (unlikely(sched_info_on())) + memset(&p->sched_info, 0, sizeof(p->sched_info)); +#endif + init_task_preempt_count(p); + + return 0; +} + +int sched_cgroup_fork(struct task_struct *p, struct kernel_clone_args *kargs) +{ + unsigned long flags; + struct rq *rq; + + /* + * Because we're not yet on the pid-hash, p->pi_lock isn't strictly + * required yet, but lockdep gets upset if rules are violated. + */ + raw_spin_lock_irqsave(&p->pi_lock, flags); + /* + * Share the timeslice between parent and child, thus the + * total amount of pending timeslices in the system doesn't change, + * resulting in more scheduling fairness. + */ + rq = this_rq(); + raw_spin_lock(&rq->lock); + + rq->curr->time_slice /= 2; + p->time_slice = rq->curr->time_slice; +#ifdef CONFIG_SCHED_HRTICK + hrtick_start(rq, rq->curr->time_slice); +#endif + + if (p->time_slice < RESCHED_NS) { + p->time_slice = sysctl_sched_base_slice; + resched_curr(rq); + } + sched_task_fork(p, rq); + raw_spin_unlock(&rq->lock); + + /* + * We're setting the CPU for the first time, we don't migrate, + * so use __set_task_cpu(). + */ + __set_task_cpu(p, smp_processor_id()); + raw_spin_unlock_irqrestore(&p->pi_lock, flags); + + return 0; +} + +void sched_cancel_fork(struct task_struct *p) +{ +} + +static void sched_mm_cid_fork(struct task_struct *t); + +void sched_post_fork(struct task_struct *p) +{ + sched_mm_cid_fork(p); +} + +#ifdef CONFIG_SCHEDSTATS + +DEFINE_STATIC_KEY_FALSE(sched_schedstats); + +static void set_schedstats(bool enabled) +{ + if (enabled) + static_branch_enable(&sched_schedstats); + else + static_branch_disable(&sched_schedstats); +} + +void force_schedstat_enabled(void) +{ + if (!schedstat_enabled()) { + pr_info("kernel profiling enabled schedstats, disable via kernel.sched_schedstats.\n"); + static_branch_enable(&sched_schedstats); + } +} + +static int __init setup_schedstats(char *str) +{ + int ret = 0; + if (!str) + goto out; + + if (!strcmp(str, "enable")) { + set_schedstats(true); + ret = 1; + } else if (!strcmp(str, "disable")) { + set_schedstats(false); + ret = 1; + } +out: + if (!ret) + pr_warn("Unable to parse schedstats=\n"); + + return ret; +} +__setup("schedstats=", setup_schedstats); + +#ifdef CONFIG_PROC_SYSCTL +static int sysctl_schedstats(const struct ctl_table *table, int write, void *buffer, + size_t *lenp, loff_t *ppos) +{ + struct ctl_table t; + int err; + int state = static_branch_likely(&sched_schedstats); + + if (write && !capable(CAP_SYS_ADMIN)) + return -EPERM; + + t = *table; + t.data = &state; + err = proc_dointvec_minmax(&t, write, buffer, lenp, ppos); + if (err < 0) + return err; + if (write) + set_schedstats(state); + return err; +} +#endif /* CONFIG_PROC_SYSCTL */ +#endif /* CONFIG_SCHEDSTATS */ + +#ifdef CONFIG_SYSCTL +static const struct ctl_table sched_core_sysctls[] = { +#ifdef CONFIG_SCHEDSTATS + { + .procname = "sched_schedstats", + .data = NULL, + .maxlen = sizeof(unsigned int), + .mode = 0644, + .proc_handler = sysctl_schedstats, + .extra1 = SYSCTL_ZERO, + .extra2 = SYSCTL_ONE, + }, +#endif /* CONFIG_SCHEDSTATS */ +}; +static int __init sched_core_sysctl_init(void) +{ + register_sysctl_init("kernel", sched_core_sysctls); + return 0; +} +late_initcall(sched_core_sysctl_init); +#endif /* CONFIG_SYSCTL */ + +/* + * wake_up_new_task - wake up a newly created task for the first time. + * + * This function will do some initial scheduler statistics housekeeping + * that must be done for every newly created context, then puts the task + * on the runqueue and wakes it. + */ +void wake_up_new_task(struct task_struct *p) +{ + unsigned long flags; + struct rq *rq; + + raw_spin_lock_irqsave(&p->pi_lock, flags); + WRITE_ONCE(p->__state, TASK_RUNNING); + rq = cpu_rq(select_task_rq(p)); + /* + * Fork balancing, do it here and not earlier because: + * - cpus_ptr can change in the fork path + * - any previously selected CPU might disappear through hotplug + * + * Use __set_task_cpu() to avoid calling sched_class::migrate_task_rq, + * as we're not fully set-up yet. + */ + __set_task_cpu(p, cpu_of(rq)); + + raw_spin_lock(&rq->lock); + update_rq_clock(rq); + + activate_task(p, rq); + trace_sched_wakeup_new(p); + wakeup_preempt(rq); + + raw_spin_unlock(&rq->lock); + raw_spin_unlock_irqrestore(&p->pi_lock, flags); +} + +#ifdef CONFIG_PREEMPT_NOTIFIERS + +static DEFINE_STATIC_KEY_FALSE(preempt_notifier_key); + +void preempt_notifier_inc(void) +{ + static_branch_inc(&preempt_notifier_key); +} +EXPORT_SYMBOL_GPL(preempt_notifier_inc); + +void preempt_notifier_dec(void) +{ + static_branch_dec(&preempt_notifier_key); +} +EXPORT_SYMBOL_GPL(preempt_notifier_dec); + +/** + * preempt_notifier_register - tell me when current is being preempted & rescheduled + * @notifier: notifier struct to register + */ +void preempt_notifier_register(struct preempt_notifier *notifier) +{ + if (!static_branch_unlikely(&preempt_notifier_key)) + WARN(1, "registering preempt_notifier while notifiers disabled\n"); + + hlist_add_head(¬ifier->link, ¤t->preempt_notifiers); +} +EXPORT_SYMBOL_GPL(preempt_notifier_register); + +/** + * preempt_notifier_unregister - no longer interested in preemption notifications + * @notifier: notifier struct to unregister + * + * This is *not* safe to call from within a preemption notifier. + */ +void preempt_notifier_unregister(struct preempt_notifier *notifier) +{ + hlist_del(¬ifier->link); +} +EXPORT_SYMBOL_GPL(preempt_notifier_unregister); + +static void __fire_sched_in_preempt_notifiers(struct task_struct *curr) +{ + struct preempt_notifier *notifier; + + hlist_for_each_entry(notifier, &curr->preempt_notifiers, link) + notifier->ops->sched_in(notifier, raw_smp_processor_id()); +} + +static __always_inline void fire_sched_in_preempt_notifiers(struct task_struct *curr) +{ + if (static_branch_unlikely(&preempt_notifier_key)) + __fire_sched_in_preempt_notifiers(curr); +} + +static void +__fire_sched_out_preempt_notifiers(struct task_struct *curr, + struct task_struct *next) +{ + struct preempt_notifier *notifier; + + hlist_for_each_entry(notifier, &curr->preempt_notifiers, link) + notifier->ops->sched_out(notifier, next); +} + +static __always_inline void +fire_sched_out_preempt_notifiers(struct task_struct *curr, + struct task_struct *next) +{ + if (static_branch_unlikely(&preempt_notifier_key)) + __fire_sched_out_preempt_notifiers(curr, next); +} + +#else /* !CONFIG_PREEMPT_NOTIFIERS: */ + +static inline void fire_sched_in_preempt_notifiers(struct task_struct *curr) +{ +} + +static inline void +fire_sched_out_preempt_notifiers(struct task_struct *curr, + struct task_struct *next) +{ +} + +#endif /* !CONFIG_PREEMPT_NOTIFIERS */ + +static inline void prepare_task(struct task_struct *next) +{ + /* + * Claim the task as running, we do this before switching to it + * such that any running task will have this set. + * + * See the smp_load_acquire(&p->on_cpu) case in ttwu() and + * its ordering comment. + */ + WRITE_ONCE(next->on_cpu, 1); +} + +static inline void finish_task(struct task_struct *prev) +{ + /* + * This must be the very last reference to @prev from this CPU. After + * p->on_cpu is cleared, the task can be moved to a different CPU. We + * must ensure this doesn't happen until the switch is completely + * finished. + * + * In particular, the load of prev->state in finish_task_switch() must + * happen before this. + * + * Pairs with the smp_cond_load_acquire() in try_to_wake_up(). + */ + smp_store_release(&prev->on_cpu, 0); +} + +static void do_balance_callbacks(struct rq *rq, struct balance_callback *head) +{ + void (*func)(struct rq *rq); + struct balance_callback *next; + + lockdep_assert_held(&rq->lock); + + while (head) { + func = (void (*)(struct rq *))head->func; + next = head->next; + head->next = NULL; + head = next; + + func(rq); + } +} + +static void balance_push(struct rq *rq); + +/* + * balance_push_callback is a right abuse of the callback interface and plays + * by significantly different rules. + * + * Where the normal balance_callback's purpose is to be ran in the same context + * that queued it (only later, when it's safe to drop rq->lock again), + * balance_push_callback is specifically targeted at __schedule(). + * + * This abuse is tolerated because it places all the unlikely/odd cases behind + * a single test, namely: rq->balance_callback == NULL. + */ +struct balance_callback balance_push_callback = { + .next = NULL, + .func = balance_push, +}; + +static inline struct balance_callback * +__splice_balance_callbacks(struct rq *rq, bool split) +{ + struct balance_callback *head = rq->balance_callback; + + if (likely(!head)) + return NULL; + + lockdep_assert_rq_held(rq); + /* + * Must not take balance_push_callback off the list when + * splice_balance_callbacks() and balance_callbacks() are not + * in the same rq->lock section. + * + * In that case it would be possible for __schedule() to interleave + * and observe the list empty. + */ + if (split && head == &balance_push_callback) + head = NULL; + else + rq->balance_callback = NULL; + + return head; +} + +struct balance_callback *splice_balance_callbacks(struct rq *rq) +{ + return __splice_balance_callbacks(rq, true); +} + +static void __balance_callbacks(struct rq *rq) +{ + do_balance_callbacks(rq, __splice_balance_callbacks(rq, false)); +} + +void balance_callbacks(struct rq *rq, struct balance_callback *head) +{ + unsigned long flags; + + if (unlikely(head)) { + raw_spin_lock_irqsave(&rq->lock, flags); + do_balance_callbacks(rq, head); + raw_spin_unlock_irqrestore(&rq->lock, flags); + } +} + +static inline void +prepare_lock_switch(struct rq *rq, struct task_struct *next) +{ + /* + * Since the runqueue lock will be released by the next + * task (which is an invalid locking op but in the case + * of the scheduler it's an obvious special-case), so we + * do an early lockdep release here: + */ + spin_release(&rq->lock.dep_map, _THIS_IP_); +#ifdef CONFIG_DEBUG_SPINLOCK + /* this is a valid case when another task releases the spinlock */ + rq->lock.owner = next; +#endif +} + +static inline void finish_lock_switch(struct rq *rq) +{ + /* + * If we are tracking spinlock dependencies then we have to + * fix up the runqueue lock - which gets 'carried over' from + * prev into current: + */ + spin_acquire(&rq->lock.dep_map, 0, 0, _THIS_IP_); + __balance_callbacks(rq); + hrtick_schedule_exit(rq); + raw_spin_unlock_irq(&rq->lock); +} + +/* + * NOP if the arch has not defined these: + */ + +#ifndef prepare_arch_switch +# define prepare_arch_switch(next) do { } while (0) +#endif + +#ifndef finish_arch_post_lock_switch +# define finish_arch_post_lock_switch() do { } while (0) +#endif + +static inline void kmap_local_sched_out(void) +{ +#ifdef CONFIG_KMAP_LOCAL + if (unlikely(current->kmap_ctrl.idx)) + __kmap_local_sched_out(); +#endif +} + +static inline void kmap_local_sched_in(void) +{ +#ifdef CONFIG_KMAP_LOCAL + if (unlikely(current->kmap_ctrl.idx)) + __kmap_local_sched_in(); +#endif +} + +/** + * prepare_task_switch - prepare to switch tasks + * @rq: the runqueue preparing to switch + * @next: the task we are going to switch to. + * + * This is called with the rq lock held and interrupts off. It must + * be paired with a subsequent finish_task_switch after the context + * switch. + * + * prepare_task_switch sets up locking and calls architecture specific + * hooks. + */ +static inline void +prepare_task_switch(struct rq *rq, struct task_struct *prev, + struct task_struct *next) +{ + kcov_prepare_switch(prev); + sched_info_switch(rq, prev, next); + perf_event_task_sched_out(prev, next); + fire_sched_out_preempt_notifiers(prev, next); + kmap_local_sched_out(); + prepare_task(next); + prepare_arch_switch(next); +} + +/** + * finish_task_switch - clean up after a task-switch + * @rq: runqueue associated with task-switch + * @prev: the thread we just switched away from. + * + * finish_task_switch must be called after the context switch, paired + * with a prepare_task_switch call before the context switch. + * finish_task_switch will reconcile locking set up by prepare_task_switch, + * and do any other architecture-specific cleanup actions. + * + * Note that we may have delayed dropping an mm in context_switch(). If + * so, we finish that here outside of the runqueue lock. (Doing it + * with the lock held can cause deadlocks; see schedule() for + * details.) + * + * The context switch have flipped the stack from under us and restored the + * local variables which were saved when this task called schedule() in the + * past. 'prev == current' is still correct but we need to recalculate this_rq + * because prev may have moved to another CPU. + */ +static struct rq *finish_task_switch(struct task_struct *prev) + __releases(rq->lock) +{ + struct rq *rq = this_rq(); + struct mm_struct *mm = rq->prev_mm; + unsigned int prev_state; + + /* + * The previous task will have left us with a preempt_count of 2 + * because it left us after: + * + * schedule() + * preempt_disable(); // 1 + * __schedule() + * raw_spin_lock_irq(&rq->lock) // 2 + * + * Also, see FORK_PREEMPT_COUNT. + */ + if (WARN_ONCE(preempt_count() != 2*PREEMPT_DISABLE_OFFSET, + "corrupted preempt_count: %s/%d/0x%x\n", + current->comm, current->pid, preempt_count())) + preempt_count_set(FORK_PREEMPT_COUNT); + + rq->prev_mm = NULL; + + /* + * A task struct has one reference for the use as "current". + * If a task dies, then it sets TASK_DEAD in tsk->state and calls + * schedule one last time. The schedule call will never return, and + * the scheduled task must drop that reference. + * + * We must observe prev->state before clearing prev->on_cpu (in + * finish_task), otherwise a concurrent wakeup can get prev + * running on another CPU and we could rave with its RUNNING -> DEAD + * transition, resulting in a double drop. + */ + prev_state = READ_ONCE(prev->__state); + vtime_task_switch(prev); + perf_event_task_sched_in(prev, current); + finish_task(prev); + tick_nohz_task_switch(); + finish_lock_switch(rq); + finish_arch_post_lock_switch(); + kcov_finish_switch(current); + /* + * kmap_local_sched_out() is invoked with rq::lock held and + * interrupts disabled. There is no requirement for that, but the + * sched out code does not have an interrupt enabled section. + * Restoring the maps on sched in does not require interrupts being + * disabled either. + */ + kmap_local_sched_in(); + + /* + * Any cached block-layer timestamp (plug->cur_ktime) is stale now, + * invalidate it. + */ + blk_plug_invalidate_ts(); + + fire_sched_in_preempt_notifiers(current); + /* + * When switching through a kernel thread, the loop in + * membarrier_{private,global}_expedited() may have observed that + * kernel thread and not issued an IPI. It is therefore possible to + * schedule between user->kernel->user threads without passing though + * switch_mm(). Membarrier requires a barrier after storing to + * rq->curr, before returning to userspace, so provide them here: + * + * - a full memory barrier for {PRIVATE,GLOBAL}_EXPEDITED, implicitly + * provided by mmdrop_lazy_tlb(), + * - a sync_core for SYNC_CORE. + */ + if (mm) { + membarrier_mm_sync_core_before_usermode(mm); + mmdrop_lazy_tlb_sched(mm); + } + if (unlikely(prev_state == TASK_DEAD)) { + cgroup_task_dead(prev); + + /* Task is done with its stack. */ + put_task_stack(prev); + + put_task_struct_rcu_user(prev); + } + + return rq; +} + +/** + * schedule_tail - first thing a freshly forked thread must call. + * @prev: the thread we just switched away from. + */ +asmlinkage __visible void schedule_tail(struct task_struct *prev) + __releases(rq->lock) +{ + /* + * New tasks start with FORK_PREEMPT_COUNT, see there and + * finish_task_switch() for details. + * + * finish_task_switch() will drop rq->lock() and lower preempt_count + * and the preempt_enable() will end up enabling preemption (on + * PREEMPT_COUNT kernels). + */ + + finish_task_switch(prev); + /* + * This is a special case: the newly created task has just + * switched the context for the first time. It is returning from + * schedule for the first time in this path. + */ + trace_sched_exit_tp(true); + preempt_enable(); + + if (current->set_child_tid) + put_user(task_pid_vnr(current), current->set_child_tid); + + calculate_sigpending(); +} + +/* + * context_switch - switch to the new MM and the new thread's register state. + */ +static __always_inline struct rq * +context_switch(struct rq *rq, struct task_struct *prev, + struct task_struct *next) +{ + prepare_task_switch(rq, prev, next); + + /* + * For paravirt, this is coupled with an exit in switch_to to + * combine the page table reload and the switch backend into + * one hypercall. + */ + arch_start_context_switch(prev); + + /* + * kernel -> kernel lazy + transfer active + * user -> kernel lazy + mmgrab_lazy_tlb() active + * + * kernel -> user switch + mmdrop_lazy_tlb() active + * user -> user switch + */ + if (!next->mm) { // to kernel + enter_lazy_tlb(prev->active_mm, next); + + next->active_mm = prev->active_mm; + if (prev->mm) // from user + mmgrab_lazy_tlb(prev->active_mm); + else + prev->active_mm = NULL; + } else { // to user + membarrier_switch_mm(rq, prev->active_mm, next->mm); + /* + * sys_membarrier() requires an smp_mb() between setting + * rq->curr / membarrier_switch_mm() and returning to userspace. + * + * The below provides this either through switch_mm(), or in + * case 'prev->active_mm == next->mm' through + * finish_task_switch()'s mmdrop(). + */ + switch_mm_irqs_off(prev->active_mm, next->mm, next); + lru_gen_use_mm(next->mm); + + if (!prev->mm) { // from kernel + /* will mmdrop_lazy_tlb() in finish_task_switch(). */ + rq->prev_mm = prev->active_mm; + prev->active_mm = NULL; + } + } + + mm_cid_switch_to(prev, next); + + /* + * Tell rseq that the task was scheduled in. Must be after + * switch_mm_cid() to get the TIF flag set. + */ + rseq_sched_switch_event(prev); + + prepare_lock_switch(rq, next); + + /* Here we just switch the register state and the stack. */ + switch_to(prev, next, prev); + barrier(); + + return finish_task_switch(prev); +} + +/* + * nr_running, nr_uninterruptible and nr_context_switches: + * + * externally visible scheduler statistics: current number of runnable + * threads, total number of context switches performed since bootup. + */ +unsigned int nr_running(void) +{ + unsigned int i, sum = 0; + + for_each_online_cpu(i) + sum += cpu_rq(i)->nr_running; + + return sum; +} + +/* + * Check if only the current task is running on the CPU. + * + * Caution: this function does not check that the caller has disabled + * preemption, thus the result might have a time-of-check-to-time-of-use + * race. The caller is responsible to use it correctly, for example: + * + * - from a non-preemptible section (of course) + * + * - from a thread that is bound to a single CPU + * + * - in a loop with very short iterations (e.g. a polling loop) + */ +bool single_task_running(void) +{ + return raw_rq()->nr_running == 1; +} +EXPORT_SYMBOL(single_task_running); + +unsigned long long nr_context_switches_cpu(int cpu) +{ + return cpu_rq(cpu)->nr_switches; +} + +unsigned long long nr_context_switches(void) +{ + int i; + unsigned long long sum = 0; + + for_each_possible_cpu(i) + sum += cpu_rq(i)->nr_switches; + + return sum; +} + +/* + * Consumers of these two interfaces, like for example the cpuidle menu + * governor, are using nonsensical data. Preferring shallow idle state selection + * for a CPU that has IO-wait which might not even end up running the task when + * it does become runnable. + */ + +unsigned int nr_iowait_cpu(int cpu) +{ + return atomic_read(&cpu_rq(cpu)->nr_iowait); +} + +/* + * IO-wait accounting, and how it's mostly bollocks (on SMP). + * + * The idea behind IO-wait account is to account the idle time that we could + * have spend running if it were not for IO. That is, if we were to improve the + * storage performance, we'd have a proportional reduction in IO-wait time. + * + * This all works nicely on UP, where, when a task blocks on IO, we account + * idle time as IO-wait, because if the storage were faster, it could've been + * running and we'd not be idle. + * + * This has been extended to SMP, by doing the same for each CPU. This however + * is broken. + * + * Imagine for instance the case where two tasks block on one CPU, only the one + * CPU will have IO-wait accounted, while the other has regular idle. Even + * though, if the storage were faster, both could've ran at the same time, + * utilising both CPUs. + * + * This means, that when looking globally, the current IO-wait accounting on + * SMP is a lower bound, by reason of under accounting. + * + * Worse, since the numbers are provided per CPU, they are sometimes + * interpreted per CPU, and that is nonsensical. A blocked task isn't strictly + * associated with any one particular CPU, it can wake to another CPU than it + * blocked on. This means the per CPU IO-wait number is meaningless. + * + * Task CPU affinities can make all that even more 'interesting'. + */ + +unsigned int nr_iowait(void) +{ + unsigned int i, sum = 0; + + for_each_possible_cpu(i) + sum += nr_iowait_cpu(i); + + return sum; +} + +/* + * sched_exec - execve() is a valuable balancing opportunity, because at + * this point the task has the smallest effective memory and cache + * footprint. + */ +void sched_exec(void) +{ +} + +DEFINE_PER_CPU(struct kernel_stat, kstat); +DEFINE_PER_CPU(struct kernel_cpustat, kernel_cpustat) = { +#ifdef CONFIG_NO_HZ_COMMON + .idle_sleeptime_seq = SEQCNT_ZERO(kernel_cpustat.idle_sleeptime_seq) +#endif +}; + +EXPORT_PER_CPU_SYMBOL(kstat); +EXPORT_PER_CPU_SYMBOL(kernel_cpustat); + +static inline void update_curr(struct rq *rq, struct task_struct *p) +{ + s64 ns = rq->clock_task - p->last_ran; + + p->sched_time += ns; + cgroup_account_cputime(p, ns); + account_group_exec_runtime(p, ns); + + p->time_slice -= ns; + p->last_ran = rq->clock_task; +} + +/* + * Return accounted runtime for the task. + * Return separately the current's pending runtime that have not been + * accounted yet. + */ +unsigned long long task_sched_runtime(struct task_struct *p) +{ + unsigned long flags; + struct rq *rq; + raw_spinlock_t *lock; + u64 ns; + +#ifdef CONFIG_64BIT + /* + * 64-bit doesn't need locks to atomically read a 64-bit value. + * So we have a optimization chance when the task's delta_exec is 0. + * Reading ->on_cpu is racy, but this is OK. + * + * If we race with it leaving CPU, we'll take a lock. So we're correct. + * If we race with it entering CPU, unaccounted time is 0. This is + * indistinguishable from the read occurring a few cycles earlier. + * If we see ->on_cpu without ->on_rq, the task is leaving, and has + * been accounted, so we're correct here as well. + */ + if (!p->on_cpu || !task_on_rq_queued(p)) + return tsk_seruntime(p); +#endif + + rq = task_access_lock_irqsave(p, &lock, &flags); + /* + * Must be ->curr _and_ ->on_rq. If dequeued, we would + * project cycles that may never be accounted to this + * thread, breaking clock_gettime(). + */ + if (p == rq->curr && task_on_rq_queued(p)) { + update_rq_clock(rq); + update_curr(rq, p); + } + ns = tsk_seruntime(p); + task_access_unlock_irqrestore(p, lock, &flags); + + return ns; +} + +/* This manages tasks that have run out of timeslice during a scheduler_tick */ +static inline void scheduler_task_tick(struct rq *rq) +{ + struct task_struct *p = rq->curr; + + if (is_idle_task(p)) + return; + + update_curr(rq, p); + cpufreq_update_util(rq, 0); + + /* + * Tasks have less than RESCHED_NS of time slice left they will be + * rescheduled. + */ + if (p->time_slice >= RESCHED_NS) + return; + set_tsk_need_resched(p); + set_preempt_need_resched(); +} + +static u64 cpu_resched_latency(struct rq *rq) +{ + int latency_warn_ms = READ_ONCE(sysctl_resched_latency_warn_ms); + u64 resched_latency, now = rq_clock(rq); + static bool warned_once; + + if (sysctl_resched_latency_warn_once && warned_once) + return 0; + + if (!need_resched() || !latency_warn_ms) + return 0; + + if (system_state == SYSTEM_BOOTING) + return 0; + + if (!rq->last_seen_need_resched_ns) { + rq->last_seen_need_resched_ns = now; + rq->ticks_without_resched = 0; + return 0; + } + + rq->ticks_without_resched++; + resched_latency = now - rq->last_seen_need_resched_ns; + if (resched_latency <= latency_warn_ms * NSEC_PER_MSEC) + return 0; + + warned_once = true; + + return resched_latency; +} + +static int __init setup_resched_latency_warn_ms(char *str) +{ + long val; + + if ((kstrtol(str, 0, &val))) { + pr_warn("Unable to set resched_latency_warn_ms\n"); + return 1; + } + + sysctl_resched_latency_warn_ms = val; + return 1; +} +__setup("resched_latency_warn_ms=", setup_resched_latency_warn_ms); + +/* + * This function gets called by the timer code, with HZ frequency. + * We call it with interrupts disabled. + */ +void sched_tick(void) +{ + int cpu __maybe_unused = smp_processor_id(); + struct rq *rq = cpu_rq(cpu); + struct task_struct *curr = rq->curr; + u64 resched_latency; + + if (housekeeping_cpu(cpu, HK_TYPE_KERNEL_NOISE)) + arch_scale_freq_tick(); + + sched_clock_tick(); + + raw_spin_lock(&rq->lock); + update_rq_clock(rq); + + if (dynamic_preempt_lazy() && tif_test_bit(TIF_NEED_RESCHED_LAZY)) + resched_curr(rq); + + scheduler_task_tick(rq); + if (sched_feat(LATENCY_WARN)) + resched_latency = cpu_resched_latency(rq); + calc_global_load_tick(rq); + + raw_spin_unlock(&rq->lock); + + if (sched_feat(LATENCY_WARN) && resched_latency) + resched_latency_warn(cpu, resched_latency); + + perf_event_task_tick(); + + if (curr->flags & PF_WQ_WORKER) + wq_worker_tick(curr); +} + +#ifdef CONFIG_NO_HZ_FULL + +struct tick_work { + int cpu; + atomic_t state; + struct delayed_work work; +}; +/* Values for ->state, see diagram below. */ +#define TICK_SCHED_REMOTE_OFFLINE 0 +#define TICK_SCHED_REMOTE_OFFLINING 1 +#define TICK_SCHED_REMOTE_RUNNING 2 + +/* + * State diagram for ->state: + * + * + * TICK_SCHED_REMOTE_OFFLINE + * | ^ + * | | + * | | sched_tick_remote() + * | | + * | | + * +--TICK_SCHED_REMOTE_OFFLINING + * | ^ + * | | + * sched_tick_start() | | sched_tick_stop() + * | | + * V | + * TICK_SCHED_REMOTE_RUNNING + * + * + * Other transitions get WARN_ON_ONCE(), except that sched_tick_remote() + * and sched_tick_start() are happy to leave the state in RUNNING. + */ + +static struct tick_work __percpu *tick_work_cpu; + +static void sched_tick_remote(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct tick_work *twork = container_of(dwork, struct tick_work, work); + int cpu = twork->cpu; + struct rq *rq = cpu_rq(cpu); + int os; + + /* + * Handle the tick only if it appears the remote CPU is running in full + * dynticks mode. The check is racy by nature, but missing a tick or + * having one too much is no big deal because the scheduler tick updates + * statistics and checks timeslices in a time-independent way, regardless + * of when exactly it is running. + */ + if (tick_nohz_tick_stopped_cpu(cpu)) { + guard(raw_spinlock_irqsave)(&rq->lock); + struct task_struct *curr = rq->curr; + + if (cpu_online(cpu)) { + update_rq_clock(rq); + + if (!is_idle_task(curr)) { + /* + * Make sure the next tick runs within a + * reasonable amount of time. + */ + u64 delta = rq_clock_task(rq) - curr->last_ran; + WARN_ON_ONCE(delta > (u64)NSEC_PER_SEC * 30); + } + scheduler_task_tick(rq); + + calc_load_nohz_remote(rq); + } + } + + /* + * Run the remote tick once per second (1Hz). This arbitrary + * frequency is large enough to avoid overload but short enough + * to keep scheduler internal stats reasonably up to date. But + * first update state to reflect hotplug activity if required. + */ + os = atomic_fetch_add_unless(&twork->state, -1, TICK_SCHED_REMOTE_RUNNING); + WARN_ON_ONCE(os == TICK_SCHED_REMOTE_OFFLINE); + if (os == TICK_SCHED_REMOTE_RUNNING) + queue_delayed_work(system_dfl_wq, dwork, HZ); +} + +static void sched_tick_start(int cpu) +{ + int os; + struct tick_work *twork; + + if (housekeeping_cpu(cpu, HK_TYPE_KERNEL_NOISE)) + return; + + WARN_ON_ONCE(!tick_work_cpu); + + twork = per_cpu_ptr(tick_work_cpu, cpu); + os = atomic_xchg(&twork->state, TICK_SCHED_REMOTE_RUNNING); + WARN_ON_ONCE(os == TICK_SCHED_REMOTE_RUNNING); + if (os == TICK_SCHED_REMOTE_OFFLINE) { + twork->cpu = cpu; + INIT_DELAYED_WORK(&twork->work, sched_tick_remote); + queue_delayed_work(system_dfl_wq, &twork->work, HZ); + } +} + +#ifdef CONFIG_HOTPLUG_CPU +static void sched_tick_stop(int cpu) +{ + struct tick_work *twork; + int os; + + if (housekeeping_cpu(cpu, HK_TYPE_KERNEL_NOISE)) + return; + + WARN_ON_ONCE(!tick_work_cpu); + + twork = per_cpu_ptr(tick_work_cpu, cpu); + /* There cannot be competing actions, but don't rely on stop-machine. */ + os = atomic_xchg(&twork->state, TICK_SCHED_REMOTE_OFFLINING); + WARN_ON_ONCE(os != TICK_SCHED_REMOTE_RUNNING); + /* Don't cancel, as this would mess up the state machine. */ +} +#endif /* CONFIG_HOTPLUG_CPU */ + +int __init sched_tick_offload_init(void) +{ + tick_work_cpu = alloc_percpu(struct tick_work); + BUG_ON(!tick_work_cpu); + return 0; +} + +#else /* !CONFIG_NO_HZ_FULL: */ +static inline void sched_tick_start(int cpu) { } +static inline void sched_tick_stop(int cpu) { } +#endif /* !CONFIG_NO_HZ_FULL */ + +#if defined(CONFIG_PREEMPTION) && (defined(CONFIG_DEBUG_PREEMPT) || \ + defined(CONFIG_PREEMPT_TRACER)) +/* + * If the value passed in is equal to the current preempt count + * then we just disabled preemption. Start timing the latency. + */ +static inline void preempt_latency_start(int val) +{ + if (preempt_count() == val) { + unsigned long ip = get_lock_parent_ip(); +#ifdef CONFIG_DEBUG_PREEMPT + current->preempt_disable_ip = ip; +#endif + trace_preempt_off(CALLER_ADDR0, ip); + } +} + +void preempt_count_add(int val) +{ +#ifdef CONFIG_DEBUG_PREEMPT + /* + * Underflow? + */ + if (DEBUG_LOCKS_WARN_ON((preempt_count() < 0))) + return; +#endif + __preempt_count_add(val); +#ifdef CONFIG_DEBUG_PREEMPT + /* + * Spinlock count overflowing soon? + */ + DEBUG_LOCKS_WARN_ON((preempt_count() & PREEMPT_MASK) >= + PREEMPT_MASK - 10); +#endif + preempt_latency_start(val); +} +EXPORT_SYMBOL(preempt_count_add); +NOKPROBE_SYMBOL(preempt_count_add); + +/* + * If the value passed in equals to the current preempt count + * then we just enabled preemption. Stop timing the latency. + */ +static inline void preempt_latency_stop(int val) +{ + if (preempt_count() == val) + trace_preempt_on(CALLER_ADDR0, get_lock_parent_ip()); +} + +void preempt_count_sub(int val) +{ +#ifdef CONFIG_DEBUG_PREEMPT + /* + * Underflow? + */ + if (DEBUG_LOCKS_WARN_ON(val > preempt_count())) + return; + /* + * Is the spinlock portion underflowing? + */ + if (DEBUG_LOCKS_WARN_ON((val < PREEMPT_MASK) && + !(preempt_count() & PREEMPT_MASK))) + return; +#endif + + preempt_latency_stop(val); + __preempt_count_sub(val); +} +EXPORT_SYMBOL(preempt_count_sub); +NOKPROBE_SYMBOL(preempt_count_sub); + +#else +static inline void preempt_latency_start(int val) { } +static inline void preempt_latency_stop(int val) { } +#endif + +static inline unsigned long get_preempt_disable_ip(struct task_struct *p) +{ +#ifdef CONFIG_DEBUG_PREEMPT + return p->preempt_disable_ip; +#else + return 0; +#endif +} + +/* + * Print scheduling while atomic bug: + */ +static noinline void __schedule_bug(struct task_struct *prev) +{ + /* Save this before calling printk(), since that will clobber it */ + unsigned long preempt_disable_ip = get_preempt_disable_ip(current); + + if (oops_in_progress) + return; + + printk(KERN_ERR "BUG: scheduling while atomic: %s/%d/0x%08x\n", + prev->comm, prev->pid, preempt_count()); + + debug_show_held_locks(prev); + print_modules(); + if (irqs_disabled()) + print_irqtrace_events(prev); + if (IS_ENABLED(CONFIG_DEBUG_PREEMPT)) { + pr_err("Preemption disabled at:"); + print_ip_sym(KERN_ERR, preempt_disable_ip); + } + check_panic_on_warn("scheduling while atomic"); + + dump_stack(); + add_taint(TAINT_WARN, LOCKDEP_STILL_OK); +} + +/* + * Various schedule()-time debugging checks and statistics: + */ +static inline void schedule_debug(struct task_struct *prev, bool preempt) +{ +#ifdef CONFIG_SCHED_STACK_END_CHECK + if (task_stack_end_corrupted(prev)) + panic("corrupted stack end detected inside scheduler\n"); + + if (task_scs_end_corrupted(prev)) + panic("corrupted shadow stack detected inside scheduler\n"); +#endif + +#ifdef CONFIG_DEBUG_ATOMIC_SLEEP + if (!preempt && READ_ONCE(prev->__state) && prev->non_block_count) { + printk(KERN_ERR "BUG: scheduling in a non-blocking section: %s/%d/%i\n", + prev->comm, prev->pid, prev->non_block_count); + dump_stack(); + add_taint(TAINT_WARN, LOCKDEP_STILL_OK); + } +#endif + + if (unlikely(in_atomic_preempt_off())) { + __schedule_bug(prev); + preempt_count_set(PREEMPT_DISABLED); + } + rcu_sleep_check(); + WARN_ON_ONCE(ct_state() == CT_STATE_USER); + + profile_hit(SCHED_PROFILING, __builtin_return_address(0)); + + schedstat_inc(this_rq()->sched_count); +} + +#ifdef ALT_SCHED_DEBUG +void alt_sched_debug(void) +{ + printk(KERN_INFO "sched: pending: 0x%04lx, idle: 0x%04lx, sg_idle: 0x%04lx," + " ecore_idle: 0x%04lx\n", + sched_rq_pending_mask.bits[0], + sched_idle_mask->bits[0], + sched_pcore_idle_mask->bits[0], + sched_ecore_idle_mask->bits[0]); +} +#endif + + +#ifdef CONFIG_PREEMPT_RT +#define SCHED_NR_MIGRATE_BREAK 8 +#else /* !CONFIG_PREEMPT_RT: */ +#define SCHED_NR_MIGRATE_BREAK 32 +#endif /* !CONFIG_PREEMPT_RT */ + +__read_mostly unsigned int sysctl_sched_nr_migrate = SCHED_NR_MIGRATE_BREAK; + +/* + * Migrate pending tasks in @rq to @dest_cpu + */ +static inline int +migrate_pending_tasks(struct rq *rq, struct rq *dest_rq, const int dest_cpu) +{ + struct task_struct *p, *skip = rq->curr; + int nr_migrated = 0; + int nr_tries = min(rq->nr_running / 2, sysctl_sched_nr_migrate); + + /* WA to check rq->curr is still on rq */ + if (!task_on_rq_queued(skip)) + return 0; + + while (skip != rq->idle && nr_tries && + (p = sched_rq_next_task(skip, rq)) != rq->idle) { + skip = sched_rq_next_task(p, rq); + if (cpumask_test_cpu(dest_cpu, p->cpus_ptr)) { + __SCHED_DEQUEUE_TASK(p, rq, 0, ); + set_task_cpu(p, dest_cpu); + sched_task_sanity_check(p, dest_rq); + __SCHED_ENQUEUE_TASK(p, dest_rq, 0, ); + nr_migrated++; + } + nr_tries--; + } + + return nr_migrated; +} + +static inline int take_other_rq_tasks(struct rq *rq, int cpu) +{ + cpumask_t *topo_mask, *end_mask, chk; + + if (unlikely(!rq->online)) + return 0; + + if (cpumask_empty(&sched_rq_pending_mask)) + return 0; + + topo_mask = per_cpu(sched_cpu_topo_masks, cpu); + end_mask = per_cpu(sched_cpu_topo_end_mask, cpu); + do { + int i; + + if (!cpumask_and(&chk, &sched_rq_pending_mask, topo_mask)) + continue; + + for_each_cpu_wrap(i, &chk, cpu) { + int nr_migrated; + struct rq *src_rq; + + src_rq = cpu_rq(i); + if (!do_raw_spin_trylock(&src_rq->lock)) + continue; + spin_acquire(&src_rq->lock.dep_map, + SINGLE_DEPTH_NESTING, 1, _RET_IP_); + + if ((nr_migrated = migrate_pending_tasks(src_rq, rq, cpu))) { + sub_nr_running(src_rq, nr_migrated); + + spin_release(&src_rq->lock.dep_map, _RET_IP_); + do_raw_spin_unlock(&src_rq->lock); + + add_nr_running(rq, nr_migrated); + + update_sched_preempt_mask(rq); + cpufreq_update_util(rq, 0); + + return 1; + } + + spin_release(&src_rq->lock.dep_map, _RET_IP_); + do_raw_spin_unlock(&src_rq->lock); + } + } while (++topo_mask < end_mask); + + return 0; +} + +static inline void time_slice_expired(struct task_struct *p, struct rq *rq) +{ + p->time_slice = sysctl_sched_base_slice; + + sched_task_renew(p, rq); + + if (SCHED_FIFO != p->policy && task_on_rq_queued(p)) + requeue_task(p, rq); +} + +static inline int balance_select_task_rq(struct task_struct *p, cpumask_t *avail_mask) +{ + cpumask_t mask; + + if (!preempt_mask_check(&mask, avail_mask, task_sched_prio(p))) + return -1; + + if (cpumask_and(&mask, &mask, p->cpus_ptr)) + return best_mask_cpu(task_cpu(p), &mask); + + return task_cpu(p); +} + +static inline void +__move_queued_task(struct rq *rq, struct task_struct *p, struct rq *dest_rq, int dest_cpu) +{ + WRITE_ONCE(p->on_rq, TASK_ON_RQ_MIGRATING); + dequeue_task(p, rq, 0); + set_task_cpu(p, dest_cpu); + + sched_task_sanity_check(p, dest_rq); + enqueue_task(p, dest_rq, 0); + WRITE_ONCE(p->on_rq, TASK_ON_RQ_QUEUED); + wakeup_preempt(dest_rq); +} + +static inline void prio_balance(struct rq *rq, const int cpu) +{ + struct task_struct *p, *next; + cpumask_t mask; + + if (!rq->online) + return; + + if (!cpumask_empty(sched_idle_mask)) + return; + + if (0 == rq->prio_balance_time) + return; + + if (rq->clock - rq->prio_balance_time < sysctl_sched_base_slice << 1) + return; + + rq->prio_balance_time = rq->clock; + + cpumask_copy(&mask, cpu_active_mask); + cpumask_clear_cpu(cpu, &mask); + + p = sched_rq_next_task(rq->curr, rq); + while (p != rq->idle) { + next = sched_rq_next_task(p, rq); + if (!is_migration_disabled(p)) { + int dest_cpu; + + dest_cpu = balance_select_task_rq(p, &mask); + if (dest_cpu < 0) + return; + + if (cpu != dest_cpu) { + struct rq *dest_rq = cpu_rq(dest_cpu); + + if (do_raw_spin_trylock(&dest_rq->lock)) { + cpumask_clear_cpu(dest_cpu, &mask); + + spin_acquire(&dest_rq->lock.dep_map, + SINGLE_DEPTH_NESTING, 1, _RET_IP_); + + __move_queued_task(rq, p, dest_rq, dest_cpu); + + spin_release(&dest_rq->lock.dep_map, _RET_IP_); + do_raw_spin_unlock(&dest_rq->lock); + } + } + } + p = next; + } +} + +/* + * Timeslices below RESCHED_NS are considered as good as expired as there's no + * point rescheduling when there's so little time left. + */ +static inline void check_curr(struct task_struct *p, struct rq *rq) +{ + if (unlikely(rq->idle == p)) + return; + + update_curr(rq, p); + + if (p->time_slice < RESCHED_NS) + time_slice_expired(p, rq); +} + +static inline struct task_struct * +choose_next_task(struct rq *rq, int cpu) +{ + struct task_struct *next = sched_rq_first_task(rq); + + if (next == rq->idle) { + if (!take_other_rq_tasks(rq, cpu)) { + sched_cpu_topology_balance(cpu, rq); + + schedstat_inc(rq->sched_goidle); + /*printk(KERN_INFO "sched: choose_next_task(%d) idle %px\n", cpu, next);*/ + return next; + } + next = sched_rq_first_task(rq); + } +#ifdef CONFIG_SCHED_HRTICK + hrtick_start(rq, next->time_slice); +#endif + /*printk(KERN_INFO "sched: choose_next_task(%d) next %px\n", cpu, next);*/ + return next; +} + +/* + * Constants for the sched_mode argument of __schedule(). + * + * The mode argument allows RT enabled kernels to differentiate a + * preemption from blocking on an 'sleeping' spin/rwlock. + */ + #define SM_IDLE (-1) + #define SM_NONE 0 + #define SM_PREEMPT 1 + #define SM_RTLOCK_WAIT 2 + +/* + * Helper function for __schedule() + * + * If a task does not have signals pending, deactivate it + * Otherwise marks the task's __state as RUNNING + */ +static bool try_to_block_task(struct rq *rq, struct task_struct *p, + unsigned long *task_state_p) +{ + unsigned long task_state = *task_state_p; + + WARN_ON_ONCE(p->is_blocked); + + if (signal_pending_state(task_state, p)) { + WRITE_ONCE(p->__state, TASK_RUNNING); + *task_state_p = TASK_RUNNING; + return false; + } + + p->is_blocked = 1; + + sched_task_deactivate(p, rq); + + block_task(rq, p, task_state); + + return true; +} + +/* + * schedule() is the main scheduler function. + * + * The main means of driving the scheduler and thus entering this function are: + * + * 1. Explicit blocking: mutex, semaphore, waitqueue, etc. + * + * 2. TIF_NEED_RESCHED flag is checked on interrupt and userspace return + * paths. For example, see arch/x86/entry_64.S. + * + * To drive preemption between tasks, the scheduler sets the flag in timer + * interrupt handler sched_tick(). + * + * 3. Wakeups don't really cause entry into schedule(). They add a + * task to the run-queue and that's it. + * + * Now, if the new task added to the run-queue preempts the current + * task, then the wakeup sets TIF_NEED_RESCHED and schedule() gets + * called on the nearest possible occasion: + * + * - If the kernel is preemptible (CONFIG_PREEMPTION=y): + * + * - in syscall or exception context, at the next outmost + * preempt_enable(). (this might be as soon as the wake_up()'s + * spin_unlock()!) + * + * - in IRQ context, return from interrupt-handler to + * preemptible context + * + * - If the kernel is not preemptible (CONFIG_PREEMPTION is not set) + * then at the next: + * + * - cond_resched() call + * - explicit schedule() call + * - return from syscall or exception to user-space + * - return from interrupt-handler to user-space + * + * WARNING: must be called with preemption disabled! + */ +static void __sched notrace __schedule(int sched_mode) +{ + struct task_struct *prev, *next; + /* + * On PREEMPT_RT kernel, SM_RTLOCK_WAIT is noted + * as a preemption by schedule_debug() and RCU. + */ + bool preempt = sched_mode > SM_NONE; + bool is_switch = false; + unsigned long *switch_count; + unsigned long prev_state; + struct rq *rq; + int cpu; + + /* Trace preemptions consistently with task switches */ + trace_sched_entry_tp(preempt); + + cpu = smp_processor_id(); + rq = cpu_rq(cpu); + prev = rq->curr; + + schedule_debug(prev, preempt); + + klp_sched_try_switch(prev); + + local_irq_disable(); + rcu_note_context_switch(preempt); + + /* + * Make sure that signal_pending_state()->signal_pending() below + * can't be reordered with __set_current_state(TASK_INTERRUPTIBLE) + * done by the caller to avoid the race with signal_wake_up(): + * + * __set_current_state(@state) signal_wake_up() + * schedule() set_tsk_thread_flag(p, TIF_SIGPENDING) + * wake_up_state(p, state) + * LOCK rq->lock LOCK p->pi_state + * smp_mb__after_spinlock() smp_mb__after_spinlock() + * if (signal_pending_state()) if (p->state & @state) + * + * Also, the membarrier system call requires a full memory barrier + * after coming from user-space, before storing to rq->curr; this + * barrier matches a full barrier in the proximity of the membarrier + * system call exit. + */ + raw_spin_lock(&rq->lock); + smp_mb__after_spinlock(); + + hrtick_schedule_enter(rq); + + update_rq_clock(rq); + + switch_count = &prev->nivcsw; + + /* Task state changes only considers SM_PREEMPT as preemption */ + preempt = sched_mode == SM_PREEMPT; + + /* + * We must load prev->state once (task_struct::state is volatile), such + * that we form a control dependency vs deactivate_task() below. + */ + prev_state = READ_ONCE(prev->__state); + if (sched_mode == SM_IDLE) { + if (!rq->nr_running) { + next = prev; + goto picked; + } + } else if (!preempt && prev_state) { + try_to_block_task(rq, prev, &prev_state); + switch_count = &prev->nvcsw; + } + + check_curr(prev, rq); + + next = choose_next_task(rq, cpu); +picked: + clear_tsk_need_resched(prev); + clear_preempt_need_resched(); + rq->last_seen_need_resched_ns = 0; + + is_switch = prev != next; + if (likely(is_switch)) { + next->last_ran = rq->clock_task; + + /*printk(KERN_INFO "sched: %px -> %px\n", prev, next);*/ + rq->nr_switches++; + /* + * RCU users of rcu_dereference(rq->curr) may not see + * changes to task_struct made by pick_next_task(). + */ + RCU_INIT_POINTER(rq->curr, next); + /* + * The membarrier system call requires each architecture + * to have a full memory barrier after updating + * rq->curr, before returning to user-space. + * + * Here are the schemes providing that barrier on the + * various architectures: + * - mm ? switch_mm() : mmdrop() for x86, s390, sparc, PowerPC, + * RISC-V. switch_mm() relies on membarrier_arch_switch_mm() + * on PowerPC and on RISC-V. + * - finish_lock_switch() for weakly-ordered + * architectures where spin_unlock is a full barrier, + * - switch_to() for arm64 (weakly-ordered, spin_unlock + * is a RELEASE barrier), + * + * The barrier matches a full barrier in the proximity of + * the membarrier system call entry. + * + * On RISC-V, this barrier pairing is also needed for the + * SYNC_CORE command when switching between processes, cf. + * the inline comments in membarrier_arch_switch_mm(). + */ + ++*switch_count; + + trace_sched_switch(preempt, prev, next, prev_state); + + /* Also unlocks the rq: */ + rq = context_switch(rq, prev, next); + + cpu = cpu_of(rq); + } else { + __balance_callbacks(rq); + prio_balance(rq, cpu); + hrtick_schedule_exit(rq); + raw_spin_unlock_irq(&rq->lock); + } + trace_sched_exit_tp(is_switch); +} + +void __noreturn do_task_dead(void) +{ + /* Causes final put_task_struct in finish_task_switch(): */ + set_special_state(TASK_DEAD); + + /* Tell freezer to ignore us: */ + current->flags |= PF_NOFREEZE; + + __schedule(SM_NONE); + BUG(); + + /* Avoid "noreturn function does return" - but don't continue if BUG() is a NOP: */ + for (;;) + cpu_relax(); +} + +static inline void sched_submit_work(struct task_struct *tsk) +{ + static DEFINE_WAIT_OVERRIDE_MAP(sched_map, LD_WAIT_CONFIG); + unsigned int task_flags; + + /* + * Establish LD_WAIT_CONFIG context to ensure none of the code called + * will use a blocking primitive -- which would lead to recursion. + */ + lock_map_acquire_try(&sched_map); + + task_flags = tsk->flags; + /* + * If a worker goes to sleep, notify and ask workqueue whether it + * wants to wake up a task to maintain concurrency. + */ + if (task_flags & PF_WQ_WORKER) + wq_worker_sleeping(tsk); + else if (task_flags & PF_IO_WORKER) + io_wq_worker_sleeping(tsk); + + /* + * spinlock and rwlock must not flush block requests. This will + * deadlock if the callback attempts to acquire a lock which is + * already acquired. + */ + WARN_ON_ONCE(current->__state & TASK_RTLOCK_WAIT); + + /* + * If we are going to sleep and we have plugged IO queued, + * make sure to submit it to avoid deadlocks. + */ + blk_flush_plug(tsk->plug, true); + + lock_map_release(&sched_map); +} + +static void sched_update_worker(struct task_struct *tsk) +{ + if (tsk->flags & (PF_WQ_WORKER | PF_IO_WORKER)) { + if (tsk->flags & PF_WQ_WORKER) + wq_worker_running(tsk); + else + io_wq_worker_running(tsk); + } +} + +static __always_inline void __schedule_loop(int sched_mode) +{ + do { + preempt_disable(); + __schedule(sched_mode); + sched_preempt_enable_no_resched(); + } while (need_resched()); +} + +asmlinkage __visible void __sched schedule(void) +{ + struct task_struct *tsk = current; + +#ifdef CONFIG_RT_MUTEXES + lockdep_assert(!tsk->sched_rt_mutex); +#endif + + if (!task_is_running(tsk)) + sched_submit_work(tsk); + __schedule_loop(SM_NONE); + sched_update_worker(tsk); +} +EXPORT_SYMBOL(schedule); + +/* + * synchronize_rcu_tasks() makes sure that no task is stuck in preempted + * state (have scheduled out non-voluntarily) by making sure that all + * tasks have either left the run queue or have gone into user space. + * As idle tasks do not do either, they must not ever be preempted + * (schedule out non-voluntarily). + * + * schedule_idle() is similar to schedule_preempt_disable() except that it + * never enables preemption because it does not call sched_submit_work(). + */ +void __sched schedule_idle(void) +{ + /* + * As this skips calling sched_submit_work(), which the idle task does + * regardless because that function is a NOP when the task is in a + * TASK_RUNNING state, make sure this isn't used someplace that the + * current task can be in any other state. Note, idle is always in the + * TASK_RUNNING state. + */ + WARN_ON_ONCE(current->__state); + do { + __schedule(SM_IDLE); + } while (need_resched()); +} + +#if defined(CONFIG_CONTEXT_TRACKING_USER) && !defined(CONFIG_HAVE_CONTEXT_TRACKING_USER_OFFSTACK) +asmlinkage __visible void __sched schedule_user(void) +{ + /* + * If we come here after a random call to set_need_resched(), + * or we have been woken up remotely but the IPI has not yet arrived, + * we haven't yet exited the RCU idle mode. Do it here manually until + * we find a better solution. + * + * NB: There are buggy callers of this function. Ideally we + * should warn if prev_state != CT_STATE_USER, but that will trigger + * too frequently to make sense yet. + */ + enum ctx_state prev_state = exception_enter(); + schedule(); + exception_exit(prev_state); +} +#endif + +/** + * schedule_preempt_disabled - called with preemption disabled + * + * Returns with preemption disabled. Note: preempt_count must be 1 + */ +void __sched schedule_preempt_disabled(void) +{ + sched_preempt_enable_no_resched(); + schedule(); + preempt_disable(); +} + +#ifdef CONFIG_PREEMPT_RT +void __sched notrace schedule_rtlock(void) +{ + __schedule_loop(SM_RTLOCK_WAIT); +} +NOKPROBE_SYMBOL(schedule_rtlock); +#endif + +static void __sched notrace preempt_schedule_common(void) +{ + do { + /* + * Because the function tracer can trace preempt_count_sub() + * and it also uses preempt_enable/disable_notrace(), if + * NEED_RESCHED is set, the preempt_enable_notrace() called + * by the function tracer will call this function again and + * cause infinite recursion. + * + * Preemption must be disabled here before the function + * tracer can trace. Break up preempt_disable() into two + * calls. One to disable preemption without fear of being + * traced. The other to still record the preemption latency, + * which can also be traced by the function tracer. + */ + preempt_disable_notrace(); + preempt_latency_start(1); + __schedule(SM_PREEMPT); + preempt_latency_stop(1); + preempt_enable_no_resched_notrace(); + + /* + * Check again in case we missed a preemption opportunity + * between schedule and now. + */ + } while (need_resched()); +} + +#ifdef CONFIG_PREEMPTION +/* + * This is the entry point to schedule() from in-kernel preemption + * off of preempt_enable. + */ +asmlinkage __visible void __sched notrace preempt_schedule(void) +{ + /* + * If there is a non-zero preempt_count or interrupts are disabled, + * we do not want to preempt the current task. Just return.. + */ + if (likely(!preemptible())) + return; + + preempt_schedule_common(); +} +NOKPROBE_SYMBOL(preempt_schedule); +EXPORT_SYMBOL(preempt_schedule); + +#ifdef CONFIG_PREEMPT_DYNAMIC +# ifdef CONFIG_HAVE_PREEMPT_DYNAMIC_CALL +# ifndef preempt_schedule_dynamic_enabled +# define preempt_schedule_dynamic_enabled preempt_schedule +# define preempt_schedule_dynamic_disabled NULL +# endif +DEFINE_STATIC_CALL(preempt_schedule, preempt_schedule_dynamic_enabled); +EXPORT_STATIC_CALL_TRAMP(preempt_schedule); +# elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) +static DEFINE_STATIC_KEY_TRUE(sk_dynamic_preempt_schedule); +void __sched notrace dynamic_preempt_schedule(void) +{ + if (!static_branch_unlikely(&sk_dynamic_preempt_schedule)) + return; + preempt_schedule(); +} +NOKPROBE_SYMBOL(dynamic_preempt_schedule); +EXPORT_SYMBOL(dynamic_preempt_schedule); +# endif +#endif /* CONFIG_PREEMPT_DYNAMIC */ + +/** + * preempt_schedule_notrace - preempt_schedule called by tracing + * + * The tracing infrastructure uses preempt_enable_notrace to prevent + * recursion and tracing preempt enabling caused by the tracing + * infrastructure itself. But as tracing can happen in areas coming + * from userspace or just about to enter userspace, a preempt enable + * can occur before user_exit() is called. This will cause the scheduler + * to be called when the system is still in usermode. + * + * To prevent this, the preempt_enable_notrace will use this function + * instead of preempt_schedule() to exit user context if needed before + * calling the scheduler. + */ +asmlinkage __visible void __sched notrace preempt_schedule_notrace(void) +{ + enum ctx_state prev_ctx; + + if (likely(!preemptible())) + return; + + do { + /* + * Because the function tracer can trace preempt_count_sub() + * and it also uses preempt_enable/disable_notrace(), if + * NEED_RESCHED is set, the preempt_enable_notrace() called + * by the function tracer will call this function again and + * cause infinite recursion. + * + * Preemption must be disabled here before the function + * tracer can trace. Break up preempt_disable() into two + * calls. One to disable preemption without fear of being + * traced. The other to still record the preemption latency, + * which can also be traced by the function tracer. + */ + preempt_disable_notrace(); + preempt_latency_start(1); + /* + * Needs preempt disabled in case user_exit() is traced + * and the tracer calls preempt_enable_notrace() causing + * an infinite recursion. + */ + prev_ctx = exception_enter(); + __schedule(SM_PREEMPT); + exception_exit(prev_ctx); + + preempt_latency_stop(1); + preempt_enable_no_resched_notrace(); + } while (need_resched()); +} +EXPORT_SYMBOL_GPL(preempt_schedule_notrace); + +#ifdef CONFIG_PREEMPT_DYNAMIC +# ifdef CONFIG_HAVE_PREEMPT_DYNAMIC_CALL +# ifndef preempt_schedule_notrace_dynamic_enabled +# define preempt_schedule_notrace_dynamic_enabled preempt_schedule_notrace +# define preempt_schedule_notrace_dynamic_disabled NULL +# endif +DEFINE_STATIC_CALL(preempt_schedule_notrace, preempt_schedule_notrace_dynamic_enabled); +EXPORT_STATIC_CALL_TRAMP(preempt_schedule_notrace); +# elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) +static DEFINE_STATIC_KEY_TRUE(sk_dynamic_preempt_schedule_notrace); +void __sched notrace dynamic_preempt_schedule_notrace(void) +{ + if (!static_branch_unlikely(&sk_dynamic_preempt_schedule_notrace)) + return; + preempt_schedule_notrace(); +} +NOKPROBE_SYMBOL(dynamic_preempt_schedule_notrace); +EXPORT_SYMBOL(dynamic_preempt_schedule_notrace); +# endif +#endif /* CONFIG_PREEMPT_DYNAMIC */ + +#endif /* CONFIG_PREEMPTION */ + +/* + * This is the entry point to schedule() from kernel preemption + * off of IRQ context. + * Note, that this is called and return with IRQs disabled. This will + * protect us against recursive calling from IRQ contexts. + */ +asmlinkage __visible void __sched preempt_schedule_irq(void) +{ + enum ctx_state prev_state; + + /* Catch callers which need to be fixed */ + BUG_ON(preempt_count() || !irqs_disabled()); + + prev_state = exception_enter(); + + do { + preempt_disable(); + local_irq_enable(); + __schedule(SM_PREEMPT); + local_irq_disable(); + sched_preempt_enable_no_resched(); + } while (need_resched()); + + exception_exit(prev_state); +} + +int default_wake_function(wait_queue_entry_t *curr, unsigned mode, int wake_flags, + void *key) +{ + WARN_ON_ONCE(wake_flags & ~(WF_SYNC|WF_CURRENT_CPU)); + return try_to_wake_up(curr->private, mode, wake_flags); +} +EXPORT_SYMBOL(default_wake_function); + +#ifdef CONFIG_RT_MUTEXES + +/* + * Would be more useful with typeof()/auto_type but they don't mix with + * bit-fields. Since it's a local thing, use int. Keep the generic sounding + * name such that if someone were to implement this function we get to compare + * notes. + */ +#define fetch_and_set(x, v) ({ int _x = (x); (x) = (v); _x; }) + +void rt_mutex_pre_schedule(void) +{ + lockdep_assert(!fetch_and_set(current->sched_rt_mutex, 1)); + sched_submit_work(current); +} + +void rt_mutex_schedule(void) +{ + lockdep_assert(current->sched_rt_mutex); + __schedule_loop(SM_NONE); +} + +void rt_mutex_post_schedule(void) +{ + sched_update_worker(current); + lockdep_assert(fetch_and_set(current->sched_rt_mutex, 0)); +} + +/* + * rt_mutex_setprio - set the current priority of a task + * @p: task to boost + * @pi_task: donor task + * + * This function changes the 'effective' priority of a task. It does + * not touch ->normal_prio like __setscheduler(). + * + * Used by the rt_mutex code to implement priority inheritance + * logic. Call site only calls if the priority of the task changed. + */ +void rt_mutex_setprio(struct task_struct *p, struct task_struct *pi_task) +{ + int prio, queue_flag = DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK; + struct rq *rq; + raw_spinlock_t *lock; + + /* XXX used to be waiter->prio, not waiter->task->prio */ + prio = __rt_effective_prio(pi_task, p->normal_prio); + + /* + * If nothing changed; bail early. + */ + if (p->pi_top_task == pi_task && prio == p->prio) + return; + + rq = __task_access_lock(p, &lock); + /* + * Set under pi_lock && rq->lock, such that the value can be used under + * either lock. + * + * Note that there is loads of tricky to make this pointer cache work + * right. rt_mutex_slowunlock()+rt_mutex_postunlock() work together to + * ensure a task is de-boosted (pi_task is set to NULL) before the + * task is allowed to run again (and can exit). This ensures the pointer + * points to a blocked task -- which guarantees the task is present. + */ + p->pi_top_task = pi_task; + + /* + * For FIFO/RR we only need to set prio, if that matches we're done. + */ + if (prio == p->prio) + goto out_unlock; + + /* + * Idle task boosting is a no-no in general. There is one + * exception, when PREEMPT_RT and NOHZ is active: + * + * The idle task calls get_next_timer_interrupt() and holds + * the timer wheel base->lock on the CPU and another CPU wants + * to access the timer (probably to cancel it). We can safely + * ignore the boosting request, as the idle CPU runs this code + * with interrupts disabled and will complete the lock + * protected section without being interrupted. So there is no + * real need to boost. + */ + if (unlikely(p == rq->idle)) { + WARN_ON(p != rq->curr); + WARN_ON(p->pi_blocked_on); + goto out_unlock; + } + + trace_sched_pi_setprio(p, pi_task); + + scoped_guard (sched_change, p, queue_flag) { + p->prio = prio; + } + +out_unlock: + /* Caller holds task_struct::pi_lock, IRQs are still disabled */ + + __balance_callbacks(rq); + __task_access_unlock(p, lock); +} +#endif /* CONFIG_RT_MUTEXES */ + +#if !defined(CONFIG_PREEMPTION) || defined(CONFIG_PREEMPT_DYNAMIC) +int __sched __cond_resched(void) +{ + if (should_resched(0) && !irqs_disabled()) { + preempt_schedule_common(); + return 1; + } + /* + * In PREEMPT_RCU kernels, ->rcu_read_lock_nesting tells the tick + * whether the current CPU is in an RCU read-side critical section, + * so the tick can report quiescent states even for CPUs looping + * in kernel context. In contrast, in non-preemptible kernels, + * RCU readers leave no in-memory hints, which means that CPU-bound + * processes executing in kernel context might never report an + * RCU quiescent state. Therefore, the following code causes + * cond_resched() to report a quiescent state, but only when RCU + * is in urgent need of one. + * A third case, preemptible, but non-PREEMPT_RCU provides for + * urgently needed quiescent states via rcu_flavor_sched_clock_irq(). + */ +#ifndef CONFIG_PREEMPT_RCU + rcu_all_qs(); +#endif + return 0; +} +EXPORT_SYMBOL(__cond_resched); +#endif + +#ifdef CONFIG_PREEMPT_DYNAMIC +# ifdef CONFIG_HAVE_PREEMPT_DYNAMIC_CALL +# define cond_resched_dynamic_enabled __cond_resched +# define cond_resched_dynamic_disabled ((void *)&__static_call_return0) +DEFINE_STATIC_CALL_RET0(cond_resched, __cond_resched); +EXPORT_STATIC_CALL_TRAMP(cond_resched); + +# define might_resched_dynamic_enabled __cond_resched +# define might_resched_dynamic_disabled ((void *)&__static_call_return0) +DEFINE_STATIC_CALL_RET0(might_resched, __cond_resched); +EXPORT_STATIC_CALL_TRAMP(might_resched); +# elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) +static DEFINE_STATIC_KEY_FALSE(sk_dynamic_cond_resched); +int __sched dynamic_cond_resched(void) +{ + if (!static_branch_unlikely(&sk_dynamic_cond_resched)) + return 0; + return __cond_resched(); +} +EXPORT_SYMBOL(dynamic_cond_resched); + +static DEFINE_STATIC_KEY_FALSE(sk_dynamic_might_resched); +int __sched dynamic_might_resched(void) +{ + if (!static_branch_unlikely(&sk_dynamic_might_resched)) + return 0; + return __cond_resched(); +} +EXPORT_SYMBOL(dynamic_might_resched); +# endif +#endif /* CONFIG_PREEMPT_DYNAMIC */ + +/* + * __cond_resched_lock() - if a reschedule is pending, drop the given lock, + * call schedule, and on return reacquire the lock. + * + * This works OK both with and without CONFIG_PREEMPTION. We do strange low-level + * operations here to prevent schedule() from being called twice (once via + * spin_unlock(), once by hand). + */ +int __cond_resched_lock(spinlock_t *lock) +{ + int resched = should_resched(PREEMPT_LOCK_OFFSET); + int ret = 0; + + lockdep_assert_held(lock); + + if (spin_needbreak(lock) || resched) { + spin_unlock(lock); + if (!_cond_resched()) + cpu_relax(); + ret = 1; + spin_lock(lock); + } + return ret; +} +EXPORT_SYMBOL(__cond_resched_lock); + +int __cond_resched_rwlock_read(rwlock_t *lock) +{ + int resched = should_resched(PREEMPT_LOCK_OFFSET); + int ret = 0; + + lockdep_assert_held_read(lock); + + if (rwlock_needbreak(lock) || resched) { + read_unlock(lock); + if (!_cond_resched()) + cpu_relax(); + ret = 1; + read_lock(lock); + } + return ret; +} +EXPORT_SYMBOL(__cond_resched_rwlock_read); + +int __cond_resched_rwlock_write(rwlock_t *lock) +{ + int resched = should_resched(PREEMPT_LOCK_OFFSET); + int ret = 0; + + lockdep_assert_held_write(lock); + + if (rwlock_needbreak(lock) || resched) { + write_unlock(lock); + if (!_cond_resched()) + cpu_relax(); + ret = 1; + write_lock(lock); + } + return ret; +} +EXPORT_SYMBOL(__cond_resched_rwlock_write); + +#ifdef CONFIG_PREEMPT_DYNAMIC + +# ifdef CONFIG_GENERIC_ENTRY +# include +# endif + +/* + * SC:cond_resched + * SC:might_resched + * SC:preempt_schedule + * SC:preempt_schedule_notrace + * SC:irqentry_exit_cond_resched + * + * + * NONE: + * cond_resched <- __cond_resched + * might_resched <- RET0 + * preempt_schedule <- NOP + * preempt_schedule_notrace <- NOP + * irqentry_exit_cond_resched <- NOP + * dynamic_preempt_lazy <- false + * + * VOLUNTARY: + * cond_resched <- __cond_resched + * might_resched <- __cond_resched + * preempt_schedule <- NOP + * preempt_schedule_notrace <- NOP + * irqentry_exit_cond_resched <- NOP + * dynamic_preempt_lazy <- false + * + * FULL: + * cond_resched <- RET0 + * might_resched <- RET0 + * preempt_schedule <- preempt_schedule + * preempt_schedule_notrace <- preempt_schedule_notrace + * irqentry_exit_cond_resched <- irqentry_exit_cond_resched + * dynamic_preempt_lazy <- false + * + * LAZY: + * cond_resched <- RET0 + * might_resched <- RET0 + * preempt_schedule <- preempt_schedule + * preempt_schedule_notrace <- preempt_schedule_notrace + * irqentry_exit_cond_resched <- irqentry_exit_cond_resched + * dynamic_preempt_lazy <- true + */ + +enum { + preempt_dynamic_undefined = -1, + preempt_dynamic_none, + preempt_dynamic_voluntary, + preempt_dynamic_full, + preempt_dynamic_lazy, +}; + +int preempt_dynamic_mode = preempt_dynamic_undefined; + +int sched_dynamic_mode(const char *str) +{ +# if !(defined(CONFIG_PREEMPT_RT) || defined(CONFIG_ARCH_HAS_PREEMPT_LAZY)) + if (!strcmp(str, "none")) + return preempt_dynamic_none; + + if (!strcmp(str, "voluntary")) + return preempt_dynamic_voluntary; +# endif + + if (!strcmp(str, "full")) + return preempt_dynamic_full; + +# ifdef CONFIG_ARCH_HAS_PREEMPT_LAZY + if (!strcmp(str, "lazy")) + return preempt_dynamic_lazy; +# endif + + return -EINVAL; +} + +# define preempt_dynamic_key_enable(f) static_key_enable(&sk_dynamic_##f.key) +# define preempt_dynamic_key_disable(f) static_key_disable(&sk_dynamic_##f.key) + +# if defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL) +# define preempt_dynamic_enable(f) static_call_update(f, f##_dynamic_enabled) +# define preempt_dynamic_disable(f) static_call_update(f, f##_dynamic_disabled) +# elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) +# define preempt_dynamic_enable(f) preempt_dynamic_key_enable(f) +# define preempt_dynamic_disable(f) preempt_dynamic_key_disable(f) +# else +# error "Unsupported PREEMPT_DYNAMIC mechanism" +# endif + +static DEFINE_MUTEX(sched_dynamic_mutex); + +static void __sched_dynamic_update(int mode) +{ + /* + * Avoid {NONE,VOLUNTARY} -> FULL transitions from ever ending up in + * the ZERO state, which is invalid. + */ + preempt_dynamic_enable(cond_resched); + preempt_dynamic_enable(cond_resched); + preempt_dynamic_enable(might_resched); + preempt_dynamic_enable(preempt_schedule); + preempt_dynamic_enable(preempt_schedule_notrace); + preempt_dynamic_enable(irqentry_exit_cond_resched); + preempt_dynamic_key_disable(preempt_lazy); + + switch (mode) { + case preempt_dynamic_none: + preempt_dynamic_enable(cond_resched); + preempt_dynamic_disable(might_resched); + preempt_dynamic_disable(preempt_schedule); + preempt_dynamic_disable(preempt_schedule_notrace); + preempt_dynamic_disable(irqentry_exit_cond_resched); + preempt_dynamic_key_disable(preempt_lazy); + if (mode != preempt_dynamic_mode) + pr_info("Dynamic Preempt: none\n"); + break; + + case preempt_dynamic_voluntary: + preempt_dynamic_enable(cond_resched); + preempt_dynamic_enable(might_resched); + preempt_dynamic_disable(preempt_schedule); + preempt_dynamic_disable(preempt_schedule_notrace); + preempt_dynamic_disable(irqentry_exit_cond_resched); + preempt_dynamic_key_disable(preempt_lazy); + if (mode != preempt_dynamic_mode) + pr_info("Dynamic Preempt: voluntary\n"); + break; + + case preempt_dynamic_full: + preempt_dynamic_enable(cond_resched); + preempt_dynamic_disable(might_resched); + preempt_dynamic_enable(preempt_schedule); + preempt_dynamic_enable(preempt_schedule_notrace); + preempt_dynamic_enable(irqentry_exit_cond_resched); + preempt_dynamic_key_disable(preempt_lazy); + if (mode != preempt_dynamic_mode) + pr_info("Dynamic Preempt: full\n"); + break; + + case preempt_dynamic_lazy: + preempt_dynamic_disable(cond_resched); + preempt_dynamic_disable(might_resched); + preempt_dynamic_enable(preempt_schedule); + preempt_dynamic_enable(preempt_schedule_notrace); + preempt_dynamic_enable(irqentry_exit_cond_resched); + preempt_dynamic_key_enable(preempt_lazy); + if (mode != preempt_dynamic_mode) + pr_info("Dynamic Preempt: lazy\n"); + break; + } + + WRITE_ONCE(preempt_dynamic_mode, mode); +} + +void sched_dynamic_update(int mode) +{ + mutex_lock(&sched_dynamic_mutex); + __sched_dynamic_update(mode); + mutex_unlock(&sched_dynamic_mutex); +} + +static int __init setup_preempt_mode(char *str) +{ + int mode = sched_dynamic_mode(str); + if (mode < 0) { + pr_warn("Dynamic Preempt: unsupported mode: %s\n", str); + return 0; + } + + sched_dynamic_update(mode); + return 1; +} +__setup("preempt=", setup_preempt_mode); + +static void __init preempt_dynamic_init(void) +{ + if (preempt_dynamic_mode == preempt_dynamic_undefined) { + if (IS_ENABLED(CONFIG_PREEMPT_NONE)) { + sched_dynamic_update(preempt_dynamic_none); + } else if (IS_ENABLED(CONFIG_PREEMPT_VOLUNTARY)) { + sched_dynamic_update(preempt_dynamic_voluntary); + } else if (IS_ENABLED(CONFIG_PREEMPT_LAZY)) { + sched_dynamic_update(preempt_dynamic_lazy); + } else { + /* Default static call setting, nothing to do */ + WARN_ON_ONCE(!IS_ENABLED(CONFIG_PREEMPT)); + preempt_dynamic_mode = preempt_dynamic_full; + pr_info("Dynamic Preempt: full\n"); + } + } +} + +# define PREEMPT_MODEL_ACCESSOR(mode) \ + bool preempt_model_##mode(void) \ + { \ + int mode = READ_ONCE(preempt_dynamic_mode); \ + WARN_ON_ONCE(mode == preempt_dynamic_undefined); \ + return mode == preempt_dynamic_##mode; \ + } \ + EXPORT_SYMBOL_GPL(preempt_model_##mode) + +PREEMPT_MODEL_ACCESSOR(none); +PREEMPT_MODEL_ACCESSOR(voluntary); +PREEMPT_MODEL_ACCESSOR(full); +PREEMPT_MODEL_ACCESSOR(lazy); + +#else /* !CONFIG_PREEMPT_DYNAMIC: */ + +#define preempt_dynamic_mode -1 + +static inline void preempt_dynamic_init(void) { } + +#endif /* CONFIG_PREEMPT_DYNAMIC */ + +const char *preempt_modes[] = { + "none", "voluntary", "full", "lazy", NULL, +}; + +const char *preempt_model_str(void) +{ + bool brace = IS_ENABLED(CONFIG_PREEMPT_RT) && + (IS_ENABLED(CONFIG_PREEMPT_DYNAMIC) || + IS_ENABLED(CONFIG_PREEMPT_LAZY)); + static char buf[128]; + + if (IS_ENABLED(CONFIG_PREEMPT_BUILD)) { + struct seq_buf s; + + seq_buf_init(&s, buf, sizeof(buf)); + seq_buf_puts(&s, "PREEMPT"); + + if (IS_ENABLED(CONFIG_PREEMPT_RT)) + seq_buf_printf(&s, "%sRT%s", + brace ? "_{" : "_", + brace ? "," : ""); + + if (IS_ENABLED(CONFIG_PREEMPT_DYNAMIC)) { + seq_buf_printf(&s, "(%s)%s", + preempt_dynamic_mode >= 0 ? + preempt_modes[preempt_dynamic_mode] : "undef", + brace ? "}" : ""); + return seq_buf_str(&s); + } + + if (IS_ENABLED(CONFIG_PREEMPT_LAZY)) { + seq_buf_printf(&s, "LAZY%s", + brace ? "}" : ""); + return seq_buf_str(&s); + } + + return seq_buf_str(&s); + } + + if (IS_ENABLED(CONFIG_PREEMPT_VOLUNTARY_BUILD)) + return "VOLUNTARY"; + + return "NONE"; +} + +int io_schedule_prepare(void) +{ + int old_iowait = current->in_iowait; + + current->in_iowait = 1; + blk_flush_plug(current->plug, true); + return old_iowait; +} + +void io_schedule_finish(int token) +{ + current->in_iowait = token; +} + +/* + * This task is about to go to sleep on IO. Increment rq->nr_iowait so + * that process accounting knows that this is a task in IO wait state. + * + * But don't do that if it is a deliberate, throttling IO wait (this task + * has set its backing_dev_info: the queue against which it should throttle) + */ + +long __sched io_schedule_timeout(long timeout) +{ + int token; + long ret; + + token = io_schedule_prepare(); + ret = schedule_timeout(timeout); + io_schedule_finish(token); + + return ret; +} +EXPORT_SYMBOL(io_schedule_timeout); + +void __sched io_schedule(void) +{ + int token; + + token = io_schedule_prepare(); + schedule(); + io_schedule_finish(token); +} +EXPORT_SYMBOL(io_schedule); + +void sched_show_task(struct task_struct *p) +{ + unsigned long free; + int ppid; + + if (!try_get_task_stack(p)) + return; + + pr_info("task:%-15.15s state:%c", p->comm, task_state_to_char(p)); + + if (task_is_running(p)) + pr_cont(" running task "); + free = stack_not_used(p); + ppid = 0; + rcu_read_lock(); + if (pid_alive(p)) + ppid = task_pid_nr(rcu_dereference(p->real_parent)); + rcu_read_unlock(); + pr_cont(" stack:%-5lu pid:%-5d tgid:%-5d ppid:%-6d task_flags:0x%04x flags:0x%08lx\n", + free, task_pid_nr(p), task_tgid_nr(p), + ppid, p->flags, read_task_thread_flags(p)); + + print_worker_info(KERN_INFO, p); + print_stop_info(KERN_INFO, p); + show_stack(p, NULL, KERN_INFO); + put_task_stack(p); +} +EXPORT_SYMBOL_GPL(sched_show_task); + +static inline bool +state_filter_match(unsigned long state_filter, struct task_struct *p) +{ + unsigned int state = READ_ONCE(p->__state); + + /* no filter, everything matches */ + if (!state_filter) + return true; + + /* filter, but doesn't match */ + if (!(state & state_filter)) + return false; + + /* + * When looking for TASK_UNINTERRUPTIBLE skip TASK_IDLE (allows + * TASK_KILLABLE). + */ + if (state_filter == TASK_UNINTERRUPTIBLE && (state & TASK_NOLOAD)) + return false; + + return true; +} + + +void show_state_filter(unsigned int state_filter) +{ + struct task_struct *g, *p; + + rcu_read_lock(); + for_each_process_thread(g, p) { + /* + * reset the NMI-timeout, listing all files on a slow + * console might take a lot of time: + * Also, reset softlockup watchdogs on all CPUs, because + * another CPU might be blocked waiting for us to process + * an IPI. + */ + touch_nmi_watchdog(); + touch_all_softlockup_watchdogs(); + if (state_filter_match(state_filter, p)) + sched_show_task(p); + } + + /* TODO: Alt schedule FW should support this + if (!state_filter) + sysrq_sched_debug_show(); + */ + rcu_read_unlock(); + /* + * Only show locks if all tasks are dumped: + */ + if (!state_filter) + debug_show_all_locks(); +} + +void dump_cpu_task(int cpu) +{ + if (in_hardirq() && cpu == smp_processor_id()) { + struct pt_regs *regs; + + regs = get_irq_regs(); + if (regs) { + show_regs(regs); + return; + } + } + + if (trigger_single_cpu_backtrace(cpu)) + return; + + pr_info("Task dump for CPU %d:\n", cpu); + sched_show_task(cpu_curr(cpu)); +} + +/** + * init_idle - set up an idle thread for a given CPU + * @idle: task in question + * @cpu: CPU the idle task belongs to + * + * NOTE: this function does not set the idle thread's NEED_RESCHED + * flag, to make booting more robust. + */ +void __init init_idle(struct task_struct *idle, int cpu) +{ + struct affinity_context ac = (struct affinity_context) { + .new_mask = cpumask_of(cpu), + .flags = 0, + }; + struct rq *rq = cpu_rq(cpu); + unsigned long flags; + + raw_spin_lock_irqsave(&idle->pi_lock, flags); + raw_spin_lock(&rq->lock); + + idle->last_ran = rq->clock_task; + idle->__state = TASK_RUNNING; + /* + * PF_KTHREAD should already be set at this point; regardless, make it + * look like a proper per-CPU kthread. + */ + idle->flags |= PF_KTHREAD | PF_NO_SETAFFINITY; + kthread_set_per_cpu(idle, cpu); + + sched_queue_init_idle(&rq->queue, idle); + + /* + * No validation and serialization required at boot time and for + * setting up the idle tasks of not yet online CPUs. + */ + set_cpus_allowed_common(idle, &ac); + + /* Silence PROVE_RCU */ + rcu_read_lock(); + __set_task_cpu(idle, cpu); + rcu_read_unlock(); + + rq->idle = idle; + rcu_assign_pointer(rq->curr, idle); + idle->on_cpu = 1; + + raw_spin_unlock(&rq->lock); + raw_spin_unlock_irqrestore(&idle->pi_lock, flags); + + /* Set the preempt count _outside_ the spinlocks! */ + init_idle_preempt_count(idle, cpu); + + ftrace_graph_init_idle_task(idle, cpu); + vtime_init_idle(idle, cpu); + sprintf(idle->comm, "%s/%d", INIT_TASK_COMM, cpu); +} + +int cpuset_cpumask_can_shrink(const struct cpumask __maybe_unused *cur, + const struct cpumask __maybe_unused *trial) +{ + return 1; +} + +int task_can_attach(struct task_struct *p) +{ + int ret = 0; + + /* + * Kthreads which disallow setaffinity shouldn't be moved + * to a new cpuset; we don't want to change their CPU + * affinity and isolating such threads by their set of + * allowed nodes is unnecessary. Thus, cpusets are not + * applicable for such threads. This prevents checking for + * success of set_cpus_allowed_ptr() on all attached tasks + * before cpus_mask may be changed. + */ + if (p->flags & PF_NO_SETAFFINITY) + ret = -EINVAL; + + return ret; +} + +bool sched_smp_initialized __read_mostly; + +#ifdef CONFIG_HOTPLUG_CPU +/* + * Invoked on the outgoing CPU in context of the CPU hotplug thread + * after ensuring that there are no user space tasks left on the CPU. + * + * If there is a lazy mm in use on the hotplug thread, drop it and + * switch to init_mm. + * + * The reference count on init_mm is dropped in finish_cpu(). + */ +static void sched_force_init_mm(void) +{ + struct mm_struct *mm = current->active_mm; + + if (mm != &init_mm) { + mmgrab_lazy_tlb(&init_mm); + local_irq_disable(); + current->active_mm = &init_mm; + switch_mm_irqs_off(mm, &init_mm, current); + local_irq_enable(); + finish_arch_post_lock_switch(); + mmdrop_lazy_tlb(mm); + } + + /* finish_cpu(), as ran on the BP, will clean up the active_mm state */ +} + +static int __balance_push_cpu_stop(void *arg) +{ + struct task_struct *p = arg; + struct rq *rq = this_rq(); + struct rq_flags rf; + int cpu; + + scoped_guard (raw_spinlock_irq, &p->pi_lock) { + cpu = select_fallback_rq(rq->cpu, p); + rq_lock(rq, &rf); + update_rq_clock(rq); + if (task_rq(p) == rq && task_on_rq_queued(p)) + rq = __migrate_task(rq, p, cpu); + rq_unlock(rq, &rf); + } + + put_task_struct(p); + + return 0; +} + +static DEFINE_PER_CPU(struct cpu_stop_work, push_work); + +/* + * This is enabled below SCHED_AP_ACTIVE; when !cpu_active(), but only + * effective when the hotplug motion is down. + */ +static void balance_push(struct rq *rq) +{ + struct task_struct *push_task = rq->curr; + + lockdep_assert_held(&rq->lock); + + /* + * Ensure the thing is persistent until balance_push_set(.on = false); + */ + rq->balance_callback = &balance_push_callback; + + /* + * Only active while going offline and when invoked on the outgoing + * CPU. + */ + if (!cpu_dying(rq->cpu) || rq != this_rq()) + return; + + /* + * Both the cpu-hotplug and stop task are in this case and are + * required to complete the hotplug process. + */ + if (kthread_is_per_cpu(push_task) || + is_migration_disabled(push_task)) { + + /* + * If this is the idle task on the outgoing CPU try to wake + * up the hotplug control thread which might wait for the + * last task to vanish. The rcuwait_active() check is + * accurate here because the waiter is pinned on this CPU + * and can't obviously be running in parallel. + * + * On RT kernels this also has to check whether there are + * pinned and scheduled out tasks on the runqueue. They + * need to leave the migrate disabled section first. + */ + if (!rq->nr_running && !rq_has_pinned_tasks(rq) && + rcuwait_active(&rq->hotplug_wait)) { + raw_spin_unlock(&rq->lock); + rcuwait_wake_up(&rq->hotplug_wait); + raw_spin_lock(&rq->lock); + } + return; + } + + get_task_struct(push_task); + /* + * Temporarily drop rq->lock such that we can wake-up the stop task. + * Both preemption and IRQs are still disabled. + */ + preempt_disable(); + raw_spin_unlock(&rq->lock); + stop_one_cpu_nowait(rq->cpu, __balance_push_cpu_stop, push_task, + this_cpu_ptr(&push_work)); + preempt_enable(); + /* + * At this point need_resched() is true and we'll take the loop in + * schedule(). The next pick is obviously going to be the stop task + * which kthread_is_per_cpu() and will push this task away. + */ + raw_spin_lock(&rq->lock); +} + +static void balance_push_set(int cpu, bool on) +{ + struct rq *rq = cpu_rq(cpu); + struct rq_flags rf; + + rq_lock_irqsave(rq, &rf); + if (on) { + WARN_ON_ONCE(rq->balance_callback); + rq->balance_callback = &balance_push_callback; + } else if (rq->balance_callback == &balance_push_callback) { + rq->balance_callback = NULL; + } + rq_unlock_irqrestore(rq, &rf); +} + +/* + * Invoked from a CPUs hotplug control thread after the CPU has been marked + * inactive. All tasks which are not per CPU kernel threads are either + * pushed off this CPU now via balance_push() or placed on a different CPU + * during wakeup. Wait until the CPU is quiescent. + */ +static void balance_hotplug_wait(void) +{ + struct rq *rq = this_rq(); + + rcuwait_wait_event(&rq->hotplug_wait, + rq->nr_running == 1 && !rq_has_pinned_tasks(rq), + TASK_UNINTERRUPTIBLE); +} + +#else /* !CONFIG_HOTPLUG_CPU: */ + +static void balance_push(struct rq *rq) +{ +} + +static void balance_push_set(int cpu, bool on) +{ +} + +static inline void balance_hotplug_wait(void) +{ +} +#endif /* !CONFIG_HOTPLUG_CPU */ + +static void set_rq_offline(struct rq *rq) +{ + if (rq->online) { + update_rq_clock(rq); + rq->online = false; + } +} + +static void set_rq_online(struct rq *rq) +{ + if (!rq->online) + rq->online = true; +} + +static inline void sched_set_rq_online(struct rq *rq, int cpu) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&rq->lock, flags); + set_rq_online(rq); + raw_spin_unlock_irqrestore(&rq->lock, flags); +} + +static inline void sched_set_rq_offline(struct rq *rq, int cpu) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&rq->lock, flags); + set_rq_offline(rq); + raw_spin_unlock_irqrestore(&rq->lock, flags); +} + +/* + * used to mark begin/end of suspend/resume: + */ +static int num_cpus_frozen; + +/* + * Update cpusets according to cpu_active mask. If cpusets are + * disabled, cpuset_update_active_cpus() becomes a simple wrapper + * around partition_sched_domains(). + * + * If we come here as part of a suspend/resume, don't touch cpusets because we + * want to restore it back to its original state upon resume anyway. + */ +static void cpuset_cpu_active(void) +{ + if (cpuhp_tasks_frozen) { + /* + * num_cpus_frozen tracks how many CPUs are involved in suspend + * resume sequence. As long as this is not the last online + * operation in the resume sequence, just build a single sched + * domain, ignoring cpusets. + */ + cpuset_reset_sched_domains(); + if (--num_cpus_frozen) + return; + /* + * This is the last CPU online operation. So fall through and + * restore the original sched domains by considering the + * cpuset configurations. + */ + cpuset_force_rebuild(); + } + + cpuset_update_active_cpus(); +} + +static void cpuset_cpu_inactive(unsigned int cpu) +{ + if (!cpuhp_tasks_frozen) { + cpuset_update_active_cpus(); + } else { + num_cpus_frozen++; + cpuset_reset_sched_domains(); + } +} + +static inline void sched_smt_present_inc(int cpu) +{ + if (cpumask_weight(cpu_smt_mask(cpu)) == 2) { + static_branch_inc_cpuslocked(&sched_smt_present); + cpumask_or(&sched_smt_mask, &sched_smt_mask, cpu_smt_mask(cpu)); + } +} + +static inline void sched_smt_present_dec(int cpu) +{ + if (cpumask_weight(cpu_smt_mask(cpu)) == 2) { + static_branch_dec_cpuslocked(&sched_smt_present); + if (!static_branch_likely(&sched_smt_present)) + cpumask_clear(sched_pcore_idle_mask); + cpumask_andnot(&sched_smt_mask, &sched_smt_mask, cpu_smt_mask(cpu)); + } +} + +int sched_cpu_activate(unsigned int cpu) +{ + struct rq *rq = cpu_rq(cpu); + + /* + * Clear the balance_push callback and prepare to schedule + * regular tasks. + */ + balance_push_set(cpu, false); + + set_cpu_active(cpu, true); + + if (sched_smp_initialized) + cpuset_cpu_active(); + + /* + * Put the rq online, if not already. This happens: + * + * 1) In the early boot process, because we build the real domains + * after all cpus have been brought up. + * + * 2) At runtime, if cpuset_cpu_active() fails to rebuild the + * domains. + */ + sched_set_rq_online(rq, cpu); + + /* + * When going up, increment the number of cores with SMT present. + */ + sched_smt_present_inc(cpu); + + return 0; +} + +int sched_cpu_deactivate(unsigned int cpu) +{ + struct rq *rq = cpu_rq(cpu); + + set_cpu_active(cpu, false); + + /* + * From this point forward, this CPU will refuse to run any task that + * is not: migrate_disable() or KTHREAD_IS_PER_CPU, and will actively + * push those tasks away until this gets cleared, see + * sched_cpu_dying(). + */ + balance_push_set(cpu, true); + + /* + * We've cleared cpu_active_mask, wait for all preempt-disabled and RCU + * users of this state to go away such that all new such users will + * observe it. + * + * Specifically, we rely on ttwu to no longer target this CPU, see + * ttwu_queue_cond() and is_cpu_allowed(). + * + * Do sync before park smpboot threads to take care the RCU boost case. + */ + synchronize_rcu(); + + sched_set_rq_offline(rq, cpu); + + /* + * When going down, decrement the number of cores with SMT present. + */ + sched_smt_present_dec(cpu); + + if (!sched_smp_initialized) + return 0; + + cpuset_cpu_inactive(cpu); + + return 0; +} + +static void sched_rq_cpu_starting(unsigned int cpu) +{ + struct rq *rq = cpu_rq(cpu); + + rq->calc_load_update = calc_load_update; +} + +int sched_cpu_starting(unsigned int cpu) +{ + sched_rq_cpu_starting(cpu); + sched_tick_start(cpu); + return 0; +} + +#ifdef CONFIG_HOTPLUG_CPU + +/* + * Invoked immediately before the stopper thread is invoked to bring the + * CPU down completely. At this point all per CPU kthreads except the + * hotplug thread (current) and the stopper thread (inactive) have been + * either parked or have been unbound from the outgoing CPU. Ensure that + * any of those which might be on the way out are gone. + * + * If after this point a bound task is being woken on this CPU then the + * responsible hotplug callback has failed to do it's job. + * sched_cpu_dying() will catch it with the appropriate fireworks. + */ +int sched_cpu_wait_empty(unsigned int cpu) +{ + balance_hotplug_wait(); + sched_force_init_mm(); + return 0; +} + +/* + * Since this CPU is going 'away' for a while, fold any nr_active delta we + * might have. Called from the CPU stopper task after ensuring that the + * stopper is the last running task on the CPU, so nr_active count is + * stable. We need to take the tear-down thread which is calling this into + * account, so we hand in adjust = 1 to the load calculation. + * + * Also see the comment "Global load-average calculations". + */ +static void calc_load_migrate(struct rq *rq) +{ + long delta = calc_load_fold_active(rq, 1); + + if (delta) + atomic_long_add(delta, &calc_load_tasks); +} + +static void dump_rq_tasks(struct rq *rq, const char *loglvl) +{ + struct task_struct *g, *p; + int cpu = cpu_of(rq); + + lockdep_assert_held(&rq->lock); + + printk("%sCPU%d enqueued tasks (%u total):\n", loglvl, cpu, rq->nr_running); + for_each_process_thread(g, p) { + if (task_cpu(p) != cpu) + continue; + + if (!task_on_rq_queued(p)) + continue; + + printk("%s\tpid: %d, name: %s\n", loglvl, p->pid, p->comm); + } +} + +int sched_cpu_dying(unsigned int cpu) +{ + struct rq *rq = cpu_rq(cpu); + unsigned long flags; + + /* Handle pending wakeups and then migrate everything off */ + sched_tick_stop(cpu); + + raw_spin_lock_irqsave(&rq->lock, flags); + if (rq->nr_running != 1 || rq_has_pinned_tasks(rq)) { + WARN(true, "Dying CPU not properly vacated!"); + dump_rq_tasks(rq, KERN_WARNING); + } + raw_spin_unlock_irqrestore(&rq->lock, flags); + + calc_load_migrate(rq); + hrtick_clear(rq); + return 0; +} +#endif /* CONFIG_HOTPLUG_CPU */ + +static void sched_init_topology_cpumask_early(void) +{ + int cpu; + cpumask_t *tmp; + + for_each_possible_cpu(cpu) { + /* init topo masks */ + tmp = per_cpu(sched_cpu_topo_masks, cpu); + + cpumask_copy(tmp, cpu_possible_mask); + per_cpu(sched_cpu_llc_mask, cpu) = tmp; + per_cpu(sched_cpu_topo_end_mask, cpu) = ++tmp; + } +} + +#define TOPOLOGY_CPUMASK(name, mask, last)\ + if (cpumask_and(topo, topo, mask)) { \ + cpumask_copy(topo, mask); \ + printk(KERN_INFO "sched: cpu#%02d topo: 0x%08lx - "#name, \ + cpu, (topo++)->bits[0]); \ + } \ + if (!last) \ + bitmap_complement(cpumask_bits(topo), cpumask_bits(mask), \ + nr_cpumask_bits); + +static void sched_init_topology_cpumask(void) +{ + int cpu; + cpumask_t *topo; + + for_each_online_cpu(cpu) { + topo = per_cpu(sched_cpu_topo_masks, cpu); + + bitmap_complement(cpumask_bits(topo), cpumask_bits(cpumask_of(cpu)), + nr_cpumask_bits); + TOPOLOGY_CPUMASK(smt, topology_sibling_cpumask(cpu), false); + TOPOLOGY_CPUMASK(cluster, topology_cluster_cpumask(cpu), false); + + per_cpu(sd_llc_id, cpu) = cpumask_first(cpu_coregroup_mask(cpu)); + per_cpu(sched_cpu_llc_mask, cpu) = topo; + TOPOLOGY_CPUMASK(coregroup, cpu_coregroup_mask(cpu), false); + + TOPOLOGY_CPUMASK(core, topology_core_cpumask(cpu), false); + + TOPOLOGY_CPUMASK(others, cpu_online_mask, true); + + per_cpu(sched_cpu_topo_end_mask, cpu) = topo; + printk(KERN_INFO "sched: cpu#%02d llc_id = %d, llc_mask idx = %d\n", + cpu, per_cpu(sd_llc_id, cpu), + (int) (per_cpu(sched_cpu_llc_mask, cpu) - + per_cpu(sched_cpu_topo_masks, cpu))); + } +} + +void __init sched_init_smp(void) +{ + /* Move init over to a non-isolated CPU */ + if (set_cpus_allowed_ptr(current, housekeeping_cpumask(HK_TYPE_DOMAIN)) < 0) + BUG(); + current->flags &= ~PF_NO_SETAFFINITY; + + sched_init_topology(); + sched_init_topology_cpumask(); + + sched_smp_initialized = true; +} + +static int __init migration_init(void) +{ + sched_cpu_starting(smp_processor_id()); + return 0; +} +early_initcall(migration_init); + +int in_sched_functions(unsigned long addr) +{ + return in_lock_functions(addr) || + (addr >= (unsigned long)__sched_text_start + && addr < (unsigned long)__sched_text_end); +} + +#ifdef CONFIG_CGROUP_SCHED +/* + * Default task group. + * Every task in system belongs to this group at bootup. + */ +struct task_group root_task_group; +LIST_HEAD(task_groups); + +/* Cacheline aligned slab cache for task_group */ +static struct kmem_cache *task_group_cache __ro_after_init; +#endif /* CONFIG_CGROUP_SCHED */ + +void __init sched_init(void) +{ + int i; + struct rq *rq; + + printk(KERN_INFO "sched/alt: "ALT_SCHED_NAME" CPU Scheduler "ALT_SCHED_VERSION\ + " by Alfred Chen.\n"); + + wait_bit_init(); + + for (i = 0; i < SCHED_QUEUE_BITS; i++) + cpumask_copy(sched_preempt_mask + i, cpu_present_mask); + +#ifdef CONFIG_CGROUP_SCHED + task_group_cache = KMEM_CACHE(task_group, 0); + + list_add(&root_task_group.list, &task_groups); + INIT_LIST_HEAD(&root_task_group.children); + INIT_LIST_HEAD(&root_task_group.siblings); +#endif /* CONFIG_CGROUP_SCHED */ + for_each_possible_cpu(i) { + rq = cpu_rq(i); + + sched_queue_init(&rq->queue); + rq->prio = IDLE_TASK_SCHED_PRIO; + rq->prio_balance_time = 0; +#ifdef CONFIG_SCHED_PDS + rq->prio_idx = rq->prio; +#endif + + raw_spin_lock_init(&rq->lock); + rq->nr_running = rq->nr_uninterruptible = 0; + rq->calc_load_active = 0; + rq->calc_load_update = jiffies + LOAD_FREQ; + rq->online = false; + rq->cpu = i; + + rq->balance_func = NULL; + rq->active_balance_arg.active = 0; + +#ifdef CONFIG_NO_HZ_COMMON + INIT_CSD(&rq->nohz_csd, nohz_csd_func, rq); +#endif + rq->balance_callback = &balance_push_callback; +#ifdef CONFIG_HOTPLUG_CPU + rcuwait_init(&rq->hotplug_wait); +#endif + rq->nr_switches = 0; + + hrtick_rq_init(rq); + atomic_set(&rq->nr_iowait, 0); + + zalloc_cpumask_var_node(&rq->scratch_mask, GFP_KERNEL, cpu_to_node(i)); + } + /* Set rq->online for cpu 0 */ + cpu_rq(0)->online = true; + /* + * The boot idle thread does lazy MMU switching as well: + */ + mmgrab_lazy_tlb(&init_mm); + enter_lazy_tlb(&init_mm, current); + + /* + * The idle task doesn't need the kthread struct to function, but it + * is dressed up as a per-CPU kthread and thus needs to play the part + * if we want to avoid special-casing it in code that deals with per-CPU + * kthreads. + */ + WARN_ON(!set_kthread_struct(current)); + + /* + * Make us the idle thread. Technically, schedule() should not be + * called from this thread, however somewhere below it might be, + * but because we are the idle thread, we just pick up running again + * when this runqueue becomes "idle". + */ + __sched_fork(0, current); + init_idle(current, smp_processor_id()); + + calc_load_update = jiffies + LOAD_FREQ; + + idle_thread_set_boot_cpu(); + balance_push_set(smp_processor_id(), false); + + sched_init_topology_cpumask_early(); + + preempt_dynamic_init(); +} + +#ifdef CONFIG_DEBUG_ATOMIC_SLEEP + +void __might_sleep(const char *file, int line) +{ + unsigned int state = get_current_state(); + /* + * Blocking primitives will set (and therefore destroy) current->state, + * since we will exit with TASK_RUNNING make sure we enter with it, + * otherwise we will destroy state. + */ + WARN_ONCE(state != TASK_RUNNING && current->task_state_change, + "do not call blocking ops when !TASK_RUNNING; " + "state=%x set at [<%p>] %pS\n", state, + (void *)current->task_state_change, + (void *)current->task_state_change); + + __might_resched(file, line, 0); +} +EXPORT_SYMBOL(__might_sleep); + +static void print_preempt_disable_ip(int preempt_offset, unsigned long ip) +{ + if (!IS_ENABLED(CONFIG_DEBUG_PREEMPT)) + return; + + if (preempt_count() == preempt_offset) + return; + + pr_err("Preemption disabled at:"); + print_ip_sym(KERN_ERR, ip); +} + +static inline bool resched_offsets_ok(unsigned int offsets) +{ + unsigned int nested = preempt_count(); + + nested += rcu_preempt_depth() << MIGHT_RESCHED_RCU_SHIFT; + + return nested == offsets; +} + +void __might_resched(const char *file, int line, unsigned int offsets) +{ + /* Ratelimiting timestamp: */ + static unsigned long prev_jiffy; + + unsigned long preempt_disable_ip; + + /* WARN_ON_ONCE() by default, no rate limit required: */ + rcu_sleep_check(); + + if ((resched_offsets_ok(offsets) && !irqs_disabled() && + !is_idle_task(current) && !current->non_block_count) || + system_state == SYSTEM_BOOTING || system_state > SYSTEM_RUNNING || + oops_in_progress) + return; + if (time_before(jiffies, prev_jiffy + HZ) && prev_jiffy) + return; + prev_jiffy = jiffies; + + /* Save this before calling printk(), since that will clobber it: */ + preempt_disable_ip = get_preempt_disable_ip(current); + + pr_err("BUG: sleeping function called from invalid context at %s:%d\n", + file, line); + pr_err("in_atomic(): %d, irqs_disabled(): %d, non_block: %d, pid: %d, name: %s\n", + in_atomic(), irqs_disabled(), current->non_block_count, + current->pid, current->comm); + pr_err("preempt_count: %x, expected: %x\n", preempt_count(), + offsets & MIGHT_RESCHED_PREEMPT_MASK); + + if (IS_ENABLED(CONFIG_PREEMPT_RCU)) { + pr_err("RCU nest depth: %d, expected: %u\n", + rcu_preempt_depth(), offsets >> MIGHT_RESCHED_RCU_SHIFT); + } + + if (task_stack_end_corrupted(current)) + pr_emerg("Thread overran stack, or stack corrupted\n"); + + debug_show_held_locks(current); + if (irqs_disabled()) + print_irqtrace_events(current); + + print_preempt_disable_ip(offsets & MIGHT_RESCHED_PREEMPT_MASK, + preempt_disable_ip); + + dump_stack(); + add_taint(TAINT_WARN, LOCKDEP_STILL_OK); +} +EXPORT_SYMBOL(__might_resched); + +void __cant_sleep(const char *file, int line, int preempt_offset) +{ + static unsigned long prev_jiffy; + + if (irqs_disabled()) + return; + + if (!IS_ENABLED(CONFIG_PREEMPT_COUNT)) + return; + + if (preempt_count() > preempt_offset) + return; + + if (time_before(jiffies, prev_jiffy + HZ) && prev_jiffy) + return; + prev_jiffy = jiffies; + + printk(KERN_ERR "BUG: assuming atomic context at %s:%d\n", file, line); + printk(KERN_ERR "in_atomic(): %d, irqs_disabled(): %d, pid: %d, name: %s\n", + in_atomic(), irqs_disabled(), + current->pid, current->comm); + + debug_show_held_locks(current); + dump_stack(); + add_taint(TAINT_WARN, LOCKDEP_STILL_OK); +} +EXPORT_SYMBOL_GPL(__cant_sleep); + +void __cant_migrate(const char *file, int line) +{ + static unsigned long prev_jiffy; + + if (irqs_disabled()) + return; + + if (is_migration_disabled(current)) + return; + + if (!IS_ENABLED(CONFIG_PREEMPT_COUNT)) + return; + + if (preempt_count() > 0) + return; + + if (time_before(jiffies, prev_jiffy + HZ) && prev_jiffy) + return; + prev_jiffy = jiffies; + + pr_err("BUG: assuming non migratable context at %s:%d\n", file, line); + pr_err("in_atomic(): %d, irqs_disabled(): %d, migration_disabled() %u pid: %d, name: %s\n", + in_atomic(), irqs_disabled(), is_migration_disabled(current), + current->pid, current->comm); + + debug_show_held_locks(current); + dump_stack(); + add_taint(TAINT_WARN, LOCKDEP_STILL_OK); +} +EXPORT_SYMBOL_GPL(__cant_migrate); +#endif /* CONFIG_DEBUG_ATOMIC_SLEEP */ + +#ifdef CONFIG_MAGIC_SYSRQ +void normalize_rt_tasks(void) +{ + struct task_struct *g, *p; + struct sched_attr attr = { + .sched_policy = SCHED_NORMAL, + }; + + read_lock(&tasklist_lock); + for_each_process_thread(g, p) { + /* + * Only normalize user tasks: + */ + if (p->flags & PF_KTHREAD) + continue; + + schedstat_set(p->stats.wait_start, 0); + schedstat_set(p->stats.sleep_start, 0); + schedstat_set(p->stats.block_start, 0); + + if (!rt_or_dl_task(p)) { + /* + * Renice negative nice level userspace + * tasks back to 0: + */ + if (task_nice(p) < 0) + set_user_nice(p, 0); + continue; + } + + __sched_setscheduler(p, &attr, false, false); + } + read_unlock(&tasklist_lock); +} +#endif /* CONFIG_MAGIC_SYSRQ */ + +#ifdef CONFIG_KGDB_KDB +/* + * These functions are only useful for KDB. + * + * They can only be called when the whole system has been + * stopped - every CPU needs to be quiescent, and no scheduling + * activity can take place. Using them for anything else would + * be a serious bug, and as a result, they aren't even visible + * under any other configuration. + */ + +/** + * curr_task - return the current task for a given CPU. + * @cpu: the processor in question. + * + * ONLY VALID WHEN THE WHOLE SYSTEM IS STOPPED! + * + * Return: The current task for @cpu. + */ +struct task_struct *curr_task(int cpu) +{ + return cpu_curr(cpu); +} + +#endif /* CONFIG_KGDB_KDB */ + +#ifdef CONFIG_CGROUP_SCHED +static void sched_free_group(struct task_group *tg) +{ + kmem_cache_free(task_group_cache, tg); +} + +static void sched_free_group_rcu(struct rcu_head *rhp) +{ + sched_free_group(container_of(rhp, struct task_group, rcu)); +} + +static void sched_unregister_group(struct task_group *tg) +{ + /* + * We have to wait for yet another RCU grace period to expire, as + * print_cfs_stats() might run concurrently. + */ + call_rcu(&tg->rcu, sched_free_group_rcu); +} + +/* allocate runqueue etc for a new task group */ +struct task_group *sched_create_group(struct task_group *parent) +{ + struct task_group *tg; + + tg = kmem_cache_alloc(task_group_cache, GFP_KERNEL | __GFP_ZERO); + if (!tg) + return ERR_PTR(-ENOMEM); + + return tg; +} + +void sched_online_group(struct task_group *tg, struct task_group *parent) +{ +} + +/* RCU callback to free various structures associated with a task group */ +static void sched_unregister_group_rcu(struct rcu_head *rhp) +{ + /* Now it should be safe to free those cfs_rqs: */ + sched_unregister_group(container_of(rhp, struct task_group, rcu)); +} + +void sched_destroy_group(struct task_group *tg) +{ + /* Wait for possible concurrent references to cfs_rqs complete: */ + call_rcu(&tg->rcu, sched_unregister_group_rcu); +} + +void sched_release_group(struct task_group *tg) +{ +} + +static inline struct task_group *css_tg(struct cgroup_subsys_state *css) +{ + return css ? container_of(css, struct task_group, css) : NULL; +} + +static struct cgroup_subsys_state * +cpu_cgroup_css_alloc(struct cgroup_subsys_state *parent_css) +{ + struct task_group *parent = css_tg(parent_css); + struct task_group *tg; + + if (!parent) { + /* This is early initialization for the top cgroup */ + return &root_task_group.css; + } + + tg = sched_create_group(parent); + if (IS_ERR(tg)) + return ERR_PTR(-ENOMEM); + return &tg->css; +} + +/* Expose task group only after completing cgroup initialization */ +static int cpu_cgroup_css_online(struct cgroup_subsys_state *css) +{ + struct task_group *tg = css_tg(css); + struct task_group *parent = css_tg(css->parent); + + if (parent) + sched_online_group(tg, parent); + return 0; +} + +static void cpu_cgroup_css_released(struct cgroup_subsys_state *css) +{ + struct task_group *tg = css_tg(css); + + sched_release_group(tg); +} + +static void cpu_cgroup_css_free(struct cgroup_subsys_state *css) +{ + struct task_group *tg = css_tg(css); + + /* + * Relies on the RCU grace period between css_released() and this. + */ + sched_unregister_group(tg); +} + +#ifdef CONFIG_RT_GROUP_SCHED +static int cpu_cgroup_can_attach(struct cgroup_taskset *tset) +{ + return 0; +} +#endif /* CONFIG_RT_GROUP_SCHED */ + +static void cpu_cgroup_attach(struct cgroup_taskset *tset) +{ +} + +#ifdef CONFIG_GROUP_SCHED_WEIGHT +static int sched_group_set_shares(struct task_group *tg, unsigned long shares) +{ + return 0; +} + +static int sched_group_set_idle(struct task_group *tg, long idle) +{ + return 0; +} + +static int cpu_shares_write_u64(struct cgroup_subsys_state *css, + struct cftype *cftype, u64 shareval) +{ + return sched_group_set_shares(css_tg(css), shareval); +} + +static u64 cpu_shares_read_u64(struct cgroup_subsys_state *css, + struct cftype *cft) +{ + return 1024; +} + +static s64 cpu_idle_read_s64(struct cgroup_subsys_state *css, + struct cftype *cft) +{ + return 0; +} + +static int cpu_idle_write_s64(struct cgroup_subsys_state *css, + struct cftype *cft, s64 idle) +{ + return sched_group_set_idle(css_tg(css), idle); +} +#endif /* CONFIG_GROUP_SCHED_WEIGHT */ + +#ifdef CONFIG_CFS_BANDWIDTH +static s64 cpu_cfs_quota_read_s64(struct cgroup_subsys_state *css, + struct cftype *cft) +{ + return -1; +} + +static int cpu_cfs_quota_write_s64(struct cgroup_subsys_state *css, + struct cftype *cftype, s64 cfs_quota_us) +{ + return 0; +} + +static u64 cpu_cfs_period_read_u64(struct cgroup_subsys_state *css, + struct cftype *cft) +{ + return 100000ULL; +} + +static int cpu_cfs_period_write_u64(struct cgroup_subsys_state *css, + struct cftype *cftype, u64 cfs_period_us) +{ + return 0; +} + +static u64 cpu_cfs_burst_read_u64(struct cgroup_subsys_state *css, + struct cftype *cft) +{ + return 0; +} + +static int cpu_cfs_burst_write_u64(struct cgroup_subsys_state *css, + struct cftype *cftype, u64 cfs_burst_us) +{ + return 0; +} + +static int cpu_cfs_stat_show(struct seq_file *sf, void *v) +{ + return 0; +} + +static int cpu_cfs_local_stat_show(struct seq_file *sf, void *v) +{ + return 0; +} +#endif /* CONFIG_CFS_BANDWIDTH */ + +#ifdef CONFIG_RT_GROUP_SCHED +static int cpu_rt_runtime_write(struct cgroup_subsys_state *css, + struct cftype *cft, s64 val) +{ + return 0; +} + +static s64 cpu_rt_runtime_read(struct cgroup_subsys_state *css, + struct cftype *cft) +{ + return 0; +} + +static int cpu_rt_period_write_uint(struct cgroup_subsys_state *css, + struct cftype *cftype, u64 rt_period_us) +{ + return 0; +} + +static u64 cpu_rt_period_read_uint(struct cgroup_subsys_state *css, + struct cftype *cft) +{ + return 0; +} +#endif /* CONFIG_RT_GROUP_SCHED */ + +#ifdef CONFIG_UCLAMP_TASK_GROUP +static int cpu_uclamp_min_show(struct seq_file *sf, void *v) +{ + seq_puts(sf, "0\n"); + return 0; +} + +static int cpu_uclamp_max_show(struct seq_file *sf, void *v) +{ + seq_puts(sf, "max\n"); + return 0; +} + +static ssize_t cpu_uclamp_min_write(struct kernfs_open_file *of, + char *buf, size_t nbytes, + loff_t off) +{ + return nbytes; +} + +static ssize_t cpu_uclamp_max_write(struct kernfs_open_file *of, + char *buf, size_t nbytes, + loff_t off) +{ + return nbytes; +} +#endif /* CONFIG_UCLAMP_TASK_GROUP */ + +static struct cftype cpu_legacy_files[] = { +#ifdef CONFIG_GROUP_SCHED_WEIGHT + { + .name = "shares", + .read_u64 = cpu_shares_read_u64, + .write_u64 = cpu_shares_write_u64, + }, + { + .name = "idle", + .read_s64 = cpu_idle_read_s64, + .write_s64 = cpu_idle_write_s64, + }, +#endif /* CONFIG_GROUP_SCHED_WEIGHT */ +#ifdef CONFIG_CFS_BANDWIDTH + { + .name = "cfs_quota_us", + .read_s64 = cpu_cfs_quota_read_s64, + .write_s64 = cpu_cfs_quota_write_s64, + }, + { + .name = "cfs_period_us", + .read_u64 = cpu_cfs_period_read_u64, + .write_u64 = cpu_cfs_period_write_u64, + }, + { + .name = "cfs_burst_us", + .read_u64 = cpu_cfs_burst_read_u64, + .write_u64 = cpu_cfs_burst_write_u64, + }, + { + .name = "stat", + .seq_show = cpu_cfs_stat_show, + }, + { + .name = "stat.local", + .seq_show = cpu_cfs_local_stat_show, + }, +#endif /* CONFIG_CFS_BANDWIDTH */ +#ifdef CONFIG_RT_GROUP_SCHED + { + .name = "rt_runtime_us", + .read_s64 = cpu_rt_runtime_read, + .write_s64 = cpu_rt_runtime_write, + }, + { + .name = "rt_period_us", + .read_u64 = cpu_rt_period_read_uint, + .write_u64 = cpu_rt_period_write_uint, + }, +#endif /* CONFIG_RT_GROUP_SCHED */ +#ifdef CONFIG_UCLAMP_TASK_GROUP + { + .name = "uclamp.min", + .flags = CFTYPE_NOT_ON_ROOT, + .seq_show = cpu_uclamp_min_show, + .write = cpu_uclamp_min_write, + }, + { + .name = "uclamp.max", + .flags = CFTYPE_NOT_ON_ROOT, + .seq_show = cpu_uclamp_max_show, + .write = cpu_uclamp_max_write, + }, +#endif /* CONFIG_UCLAMP_TASK_GROUP */ + { } /* Terminate */ +}; + +#ifdef CONFIG_GROUP_SCHED_WEIGHT +static u64 cpu_weight_read_u64(struct cgroup_subsys_state *css, + struct cftype *cft) +{ + return 100; +} + +static int cpu_weight_write_u64(struct cgroup_subsys_state *css, + struct cftype *cft, u64 weight) +{ + return 0; +} + +static s64 cpu_weight_nice_read_s64(struct cgroup_subsys_state *css, + struct cftype *cft) +{ + return 0; +} + +static int cpu_weight_nice_write_s64(struct cgroup_subsys_state *css, + struct cftype *cft, s64 nice) +{ + return 0; +} +#endif /* CONFIG_GROUP_SCHED_WEIGHT */ + +#ifdef CONFIG_CFS_BANDWIDTH +static int cpu_max_show(struct seq_file *sf, void *v) +{ + seq_puts(sf, "max 100000\n"); + return 0; +} + +static ssize_t cpu_max_write(struct kernfs_open_file *of, + char *buf, size_t nbytes, loff_t off) +{ + return nbytes; +} +#endif /* CONFIG_CFS_BANDWIDTH */ + +static struct cftype cpu_files[] = { +#ifdef CONFIG_GROUP_SCHED_WEIGHT + { + .name = "weight", + .flags = CFTYPE_NOT_ON_ROOT, + .read_u64 = cpu_weight_read_u64, + .write_u64 = cpu_weight_write_u64, + }, + { + .name = "weight.nice", + .flags = CFTYPE_NOT_ON_ROOT, + .read_s64 = cpu_weight_nice_read_s64, + .write_s64 = cpu_weight_nice_write_s64, + }, + { + .name = "idle", + .flags = CFTYPE_NOT_ON_ROOT, + .read_s64 = cpu_idle_read_s64, + .write_s64 = cpu_idle_write_s64, + }, +#endif /* CONFIG_GROUP_SCHED_WEIGHT */ +#ifdef CONFIG_CFS_BANDWIDTH + { + .name = "max", + .flags = CFTYPE_NOT_ON_ROOT, + .seq_show = cpu_max_show, + .write = cpu_max_write, + }, + { + .name = "max.burst", + .flags = CFTYPE_NOT_ON_ROOT, + .read_u64 = cpu_cfs_burst_read_u64, + .write_u64 = cpu_cfs_burst_write_u64, + }, +#endif /* CONFIG_CFS_BANDWIDTH */ +#ifdef CONFIG_UCLAMP_TASK_GROUP + { + .name = "uclamp.min", + .flags = CFTYPE_NOT_ON_ROOT, + .seq_show = cpu_uclamp_min_show, + .write = cpu_uclamp_min_write, + }, + { + .name = "uclamp.max", + .flags = CFTYPE_NOT_ON_ROOT, + .seq_show = cpu_uclamp_max_show, + .write = cpu_uclamp_max_write, + }, +#endif /* CONFIG_UCLAMP_TASK_GROUP */ + { } /* terminate */ +}; + +static int cpu_extra_stat_show(struct seq_file *sf, + struct cgroup_subsys_state *css) +{ + return 0; +} + +static int cpu_local_stat_show(struct seq_file *sf, + struct cgroup_subsys_state *css) +{ + return 0; +} + +struct cgroup_subsys cpu_cgrp_subsys = { + .css_alloc = cpu_cgroup_css_alloc, + .css_online = cpu_cgroup_css_online, + .css_released = cpu_cgroup_css_released, + .css_free = cpu_cgroup_css_free, + .css_extra_stat_show = cpu_extra_stat_show, + .css_local_stat_show = cpu_local_stat_show, +#ifdef CONFIG_RT_GROUP_SCHED + .can_attach = cpu_cgroup_can_attach, +#endif /* CONFIG_RT_GROUP_SCHED */ + .attach = cpu_cgroup_attach, + .legacy_cftypes = cpu_legacy_files, + .dfl_cftypes = cpu_files, + .early_init = true, + .threaded = true, +}; +#endif /* CONFIG_CGROUP_SCHED */ + +#undef CREATE_TRACE_POINTS + +#ifdef CONFIG_SCHED_MM_CID +/* + * Concurrency IDentifier management + * + * Serialization rules: + * + * mm::mm_cid::mutex: Serializes fork() and exit() and therefore + * protects mm::mm_cid::users and mode switch + * transitions + * + * mm::mm_cid::lock: Serializes mm_update_max_cids() and + * mm_update_cpus_allowed(). Nests in mm_cid::mutex + * and runqueue lock. + * + * The mm_cidmask bitmap is not protected by any of the mm::mm_cid locks + * and can only be modified with atomic operations. + * + * The mm::mm_cid:pcpu per CPU storage is protected by the CPUs runqueue + * lock. + * + * CID ownership: + * + * A CID is either owned by a task (stored in task_struct::mm_cid.cid) or + * by a CPU (stored in mm::mm_cid.pcpu::cid). CIDs owned by CPUs have the + * MM_CID_ONCPU bit set. + * + * During the transition of ownership mode, the MM_CID_TRANSIT bit is set + * on the CIDs. When this bit is set the tasks drop the CID back into the + * pool when scheduling out. + * + * Both bits (ONCPU and TRANSIT) are filtered out by task_cid() when the + * CID is actually handed over to user space in the RSEQ memory. + * + * Mode switching: + * + * The ownership mode is per process and stored in mm:mm_cid::mode with the + * following possible states: + * + * 0: Per task ownership + * 0 | MM_CID_TRANSIT: Transition from per CPU to per task + * MM_CID_ONCPU: Per CPU ownership + * MM_CID_ONCPU | MM_CID_TRANSIT: Transition from per task to per CPU + * + * All transitions of ownership mode happen in two phases: + * + * 1) mm:mm_cid::mode has the MM_CID_TRANSIT bit set. This is OR'ed on the + * CIDs and denotes that the CID is only temporarily owned by a + * task. When the task schedules out it drops the CID back into the + * pool if this bit is set. + * + * 2) The initiating context walks the per CPU space or the tasks to fixup + * or drop the CIDs and after completion it clears MM_CID_TRANSIT in + * mm:mm_cid::mode. After that point the CIDs are strictly task or CPU + * owned again. + * + * This two phase transition is required to prevent CID space exhaustion + * during the transition as a direct transfer of ownership would fail: + * + * - On task to CPU mode switch if a task is scheduled in on one CPU and + * then migrated to another CPU before the fixup freed enough per task + * CIDs. + * + * - On CPU to task mode switch if two tasks are scheduled in on the same + * CPU before the fixup freed per CPU CIDs. + * + * Both scenarios can result in a live lock because sched_in() is invoked + * with runqueue lock held and loops in search of a CID and the fixup + * thread can't make progress freeing them up because it is stuck on the + * same runqueue lock. + * + * While MM_CID_TRANSIT is active during the transition phase the MM_CID + * bitmap can be contended, but that's a temporary contention bound to the + * transition period. After that everything goes back into steady state and + * nothing except fork() and exit() will touch the bitmap. This is an + * acceptable tradeoff as it completely avoids complex serialization, + * memory barriers and atomic operations for the common case. + * + * Aside of that this mechanism also ensures RT compability: + * + * - The task which runs the fixup is fully preemptible except for the + * short runqueue lock held sections. + * + * - The transient impact of the bitmap contention is only problematic + * when there is a thundering herd scenario of tasks scheduling in and + * out concurrently. There is not much which can be done about that + * except for avoiding mode switching by a proper overall system + * configuration. + * + * Switching to per CPU mode happens when the user count becomes greater + * than the maximum number of CIDs, which is calculated by: + * + * opt_cids = min(mm_cid::nr_cpus_allowed, mm_cid::users); + * max_cids = min(1.25 * opt_cids, num_possible_cpus()); + * + * The +25% allowance is useful for tight CPU masks in scenarios where only + * a few threads are created and destroyed to avoid frequent mode + * switches. Though this allowance shrinks, the closer opt_cids becomes to + * num_possible_cpus(), which is the (unfortunate) hard ABI limit. + * + * At the point of switching to per CPU mode the new user is not yet + * visible in the system, so the task which initiated the fork() runs the + * fixup function. mm_cid_fixup_tasks_to_cpu() walks the thread list and + * either marks each task owned CID with MM_CID_TRANSIT if the task is + * running on a CPU or drops it into the CID pool if a task is not on a + * CPU. Tasks which schedule in before the task walk reaches them do the + * handover in mm_cid_schedin(). When mm_cid_fixup_tasks_to_cpus() + * completes it is guaranteed that no task related to that MM owns a CID + * anymore. + * + * Switching back to task mode happens when the user count goes below the + * threshold which was recorded on the per CPU mode switch: + * + * pcpu_thrs = min(opt_cids - (opt_cids / 4), num_possible_cpus() / 2); + * + * This threshold is updated when a affinity change increases the number of + * allowed CPUs for the MM, which might cause a switch back to per task + * mode. + * + * If the switch back was initiated by a exiting task, then that task runs + * the fixup function. If it was initiated by a affinity change, then it's + * run either in the deferred update function in context of a workqueue or + * by a task which forks a new one or by a task which exits. Whatever + * happens first. mm_cid_fixup_cpus_to_task() walks through the possible + * CPUs and either transfers the CPU owned CIDs to a related task which + * runs on the CPU or drops it into the pool. Tasks which schedule in on a + * CPU which the walk did not cover yet do the handover themself. + * + * This transition from CPU to per task ownership happens in two phases: + * + * 1) mm:mm_cid.transit contains MM_CID_TRANSIT This is OR'ed on the task + * CID and denotes that the CID is only temporarily owned by the + * task. When it schedules out the task drops the CID back into the + * pool if this bit is set. + * + * 2) The initiating context walks the per CPU space and after completion + * clears mm:mm_cid.transit. So after that point the CIDs are strictly + * task owned again. + * + * This two phase transition is required to prevent CID space exhaustion + * during the transition as a direct transfer of ownership would fail if + * two tasks are scheduled in on the same CPU before the fixup freed per + * CPU CIDs. + * + * When mm_cid_fixup_cpus_to_tasks() completes it's guaranteed that no CID + * related to that MM is owned by a CPU anymore. + */ + +/* + * Update the CID range properties when the constraints change. Invoked via + * fork(), exit() and affinity changes + */ +static void __mm_update_max_cids(struct mm_mm_cid *mc) +{ + unsigned int opt_cids, max_cids; + + /* Calculate the new optimal constraint */ + opt_cids = min(mc->nr_cpus_allowed, mc->users); + + /* Adjust the maximum CIDs to +25% limited by the number of possible CPUs */ + max_cids = min(opt_cids + (opt_cids / 4), num_possible_cpus()); + WRITE_ONCE(mc->max_cids, max_cids); +} + +static inline unsigned int mm_cid_calc_pcpu_thrs(struct mm_mm_cid *mc) +{ + unsigned int opt_cids; + + opt_cids = min(mc->nr_cpus_allowed, mc->users); + /* Has to be at least 1 because 0 indicates PCPU mode off */ + return max(min(opt_cids - opt_cids / 4, num_possible_cpus() / 2), 1); +} + +static bool mm_update_max_cids(struct mm_struct *mm) +{ + struct mm_mm_cid *mc = &mm->mm_cid; + bool percpu = cid_on_cpu(mc->mode); + + lockdep_assert_held(&mm->mm_cid.lock); + + /* Clear deferred mode switch flag. A change is handled by the caller */ + mc->update_deferred = false; + __mm_update_max_cids(mc); + + /* Check whether owner mode must be changed */ + if (!percpu) { + /* Enable per CPU mode when the number of users is above max_cids */ + if (mc->users > mc->max_cids) + mc->pcpu_thrs = mm_cid_calc_pcpu_thrs(mc); + } else { + /* Switch back to per task if user count under threshold */ + if (mc->users < mc->pcpu_thrs) + mc->pcpu_thrs = 0; + } + + /* Mode change required? */ + if (percpu == !!mc->pcpu_thrs) + return false; + + /* Flip the mode and set the transition flag to bridge the transfer */ + WRITE_ONCE(mc->mode, mc->mode ^ (MM_CID_TRANSIT | MM_CID_ONCPU)); + /* + * Order the store against the subsequent fixups so that + * acquire(rq::lock) cannot be reordered by the CPU before the + * store. + */ + smp_mb(); + return true; +} + +static inline void mm_update_cpus_allowed(struct mm_struct *mm, const struct cpumask *affmsk) +{ + struct cpumask *mm_allowed; + struct mm_mm_cid *mc; + unsigned int weight; + + if (!mm || !READ_ONCE(mm->mm_cid.users)) + return; + /* + * mm::mm_cid::mm_cpus_allowed is the superset of each threads + * allowed CPUs mask which means it can only grow. + */ + mc = &mm->mm_cid; + guard(raw_spinlock)(&mc->lock); + mm_allowed = mm_cpus_allowed(mm); + weight = cpumask_weighted_or(mm_allowed, mm_allowed, affmsk); + if (weight == mc->nr_cpus_allowed) + return; + + WRITE_ONCE(mc->nr_cpus_allowed, weight); + __mm_update_max_cids(mc); + if (!cid_on_cpu(mc->mode)) + return; + + /* Adjust the threshold to the wider set */ + mc->pcpu_thrs = mm_cid_calc_pcpu_thrs(mc); + /* Switch back to per task mode? */ + if (mc->users >= mc->pcpu_thrs) + return; + + /* Don't queue twice */ + if (mc->update_deferred) + return; + + /* Queue the irq work, which schedules the real work */ + mc->update_deferred = true; + irq_work_queue(&mc->irq_work); +} + +static inline void mm_cid_complete_transit(struct mm_struct *mm, unsigned int mode) +{ + /* + * Ensure that the store removing the TRANSIT bit cannot be + * reordered by the CPU before the fixups have been completed. + */ + smp_mb(); + WRITE_ONCE(mm->mm_cid.mode, mode); +} + +static inline void mm_cid_transit_to_task(struct task_struct *t, struct mm_cid_pcpu *pcp) +{ + if (cid_on_cpu(t->mm_cid.cid)) { + unsigned int cid = cpu_cid_to_cid(t->mm_cid.cid); + + t->mm_cid.cid = cid_to_transit_cid(cid); + pcp->cid = t->mm_cid.cid; + } +} + +static void mm_cid_fixup_cpus_to_tasks(struct mm_struct *mm) +{ + unsigned int cpu; + + /* Walk the CPUs and fixup all stale CIDs */ + for_each_possible_cpu(cpu) { + struct mm_cid_pcpu *pcp = per_cpu_ptr(mm->mm_cid.pcpu, cpu); + struct rq *rq = cpu_rq(cpu); + + /* Remote access to mm::mm_cid::pcpu requires rq_lock */ + guard(rq_lock_irq)(rq); + /* Is the CID still owned by the CPU? */ + if (cid_on_cpu(pcp->cid)) { + /* + * If rq->curr has @mm, transfer it with the + * transition bit set. Otherwise drop it. + */ + if (rq->curr->mm == mm && rq->curr->mm_cid.active) + mm_cid_transit_to_task(rq->curr, pcp); + else + mm_drop_cid_on_cpu(mm, pcp); + + } else if (rq->curr->mm == mm && rq->curr->mm_cid.active) { + unsigned int cid = rq->curr->mm_cid.cid; + + /* + * Set the transition bit only on a genuine task-owned + * CID. A running active task can legitimately have + * MM_CID_UNSET here: in per-CPU mode CIDs are assigned + * lazily on schedule-in, so the fork()/execve() window + * leaves the task active with no owned CID. Setting the + * transition bit on MM_CID_UNSET would later feed + * clear_bit() an out-of-bounds bit number via + * mm_cid_schedout(), so exclude it. A CPU-owned + * (MM_CID_ONCPU) CID is handled by the cid_on_cpu() + * branch above and never reaches here. + */ + if (cid != MM_CID_UNSET && !cid_in_transit(cid)) { + cid = cid_to_transit_cid(cid); + rq->curr->mm_cid.cid = cid; + pcp->cid = cid; + } + } + } + mm_cid_complete_transit(mm, 0); +} + +static inline void mm_cid_transit_to_cpu(struct task_struct *t, struct mm_cid_pcpu *pcp) +{ + if (cid_on_task(t->mm_cid.cid)) { + t->mm_cid.cid = cid_to_transit_cid(t->mm_cid.cid); + pcp->cid = t->mm_cid.cid; + } +} + +static void mm_cid_fixup_task_to_cpu(struct task_struct *t, struct mm_struct *mm) +{ + /* Remote access to mm::mm_cid::pcpu requires rq_lock */ + guard(task_rq_lock)(t); + if (cid_on_task(t->mm_cid.cid)) { + /* If running on the CPU, put the CID in transit mode, otherwise drop it */ + if (task_rq(t)->curr == t) + mm_cid_transit_to_cpu(t, per_cpu_ptr(mm->mm_cid.pcpu, task_cpu(t))); + else + mm_unset_cid_on_task(t); + } +} + +static void mm_cid_fixup_tasks_to_cpus(void) +{ + struct mm_struct *mm = current->mm; + struct task_struct *t; + + lockdep_assert_held(&mm->mm_cid.mutex); + + hlist_for_each_entry(t, &mm->mm_cid.user_list, mm_cid.node) { + /* Current has already transferred before invoking the fixup. */ + if (t != current) + mm_cid_fixup_task_to_cpu(t, mm); + } + + mm_cid_complete_transit(mm, MM_CID_ONCPU); +} + +static bool sched_mm_cid_add_user(struct task_struct *t, struct mm_struct *mm) +{ + lockdep_assert_held(&mm->mm_cid.lock); + + t->mm_cid.active = 1; + hlist_add_head(&t->mm_cid.node, &mm->mm_cid.user_list); + mm->mm_cid.users++; + return mm_update_max_cids(mm); +} + +static void sched_mm_cid_fork(struct task_struct *t) +{ + struct mm_struct *mm = t->mm; + bool percpu; + + if (!mm) + return; + + WARN_ON_ONCE(t->mm_cid.cid != MM_CID_UNSET); + + guard(mutex)(&mm->mm_cid.mutex); + scoped_guard(raw_spinlock_irq, &mm->mm_cid.lock) { + struct mm_cid_pcpu *pcp = this_cpu_ptr(mm->mm_cid.pcpu); + + /* First user ? */ + if (!mm->mm_cid.users) { + sched_mm_cid_add_user(t, mm); + t->mm_cid.cid = mm_get_cid(mm); + /* Required for execve() */ + pcp->cid = t->mm_cid.cid; + return; + } + + if (!sched_mm_cid_add_user(t, mm)) { + if (!cid_on_cpu(mm->mm_cid.mode)) + t->mm_cid.cid = mm_get_cid(mm); + return; + } + + /* Handle the mode change and transfer current's CID */ + percpu = cid_on_cpu(mm->mm_cid.mode); + if (!percpu) + mm_cid_transit_to_task(current, pcp); + else + mm_cid_transit_to_cpu(current, pcp); + } + + if (percpu) { + mm_cid_fixup_tasks_to_cpus(); + } else { + mm_cid_fixup_cpus_to_tasks(mm); + t->mm_cid.cid = mm_get_cid(mm); + } +} + +static bool sched_mm_cid_remove_user(struct task_struct *t) +{ + lockdep_assert_held(&t->mm->mm_cid.lock); + + t->mm_cid.active = 0; + /* Clear the transition bit */ + t->mm_cid.cid = cid_from_transit_cid(t->mm_cid.cid); + mm_unset_cid_on_task(t); + hlist_del_init(&t->mm_cid.node); + t->mm->mm_cid.users--; + return mm_update_max_cids(t->mm); +} + +static bool __sched_mm_cid_exit(struct task_struct *t) +{ + struct mm_struct *mm = t->mm; + + if (!sched_mm_cid_remove_user(t)) + return false; + /* + * Contrary to fork() this only deals with a switch back to per + * task mode either because the above decreased users or an + * affinity change increased the number of allowed CPUs and the + * deferred fixup did not run yet. + */ + if (WARN_ON_ONCE(cid_on_cpu(mm->mm_cid.mode))) + return false; + /* + * A failed fork(2) cleanup never gets here, so @current must have + * the same MM as @t. That's true for exit() and the failed + * pthread_create() cleanup case. + */ + if (WARN_ON_ONCE(current->mm != mm)) + return false; + return true; +} + +/* + * When a task exits, the MM CID held by the task is not longer required as + * the task cannot return to user space. + */ +void sched_mm_cid_exit(struct task_struct *t) +{ + struct mm_struct *mm = t->mm; + + if (!mm || !t->mm_cid.active) + return; + /* + * Ensure that only one instance is doing MM CID operations within + * a MM. The common case is uncontended. The rare fixup case adds + * some overhead. + */ + scoped_guard(mutex, &mm->mm_cid.mutex) { + /* mm_cid::mutex is sufficient to protect mm_cid::users */ + if (likely(mm->mm_cid.users > 1)) { + scoped_guard(raw_spinlock_irq, &mm->mm_cid.lock) { + if (!__sched_mm_cid_exit(t)) + return; + /* + * Mode change. The task has the CID unset + * already and dealt with an eventually set + * TRANSIT bit. If the CID is owned by the CPU + * then drop it. + */ + mm_drop_cid_on_cpu(mm, this_cpu_ptr(mm->mm_cid.pcpu)); + } + mm_cid_fixup_cpus_to_tasks(mm); + return; + } + /* Last user */ + scoped_guard(raw_spinlock_irq, &mm->mm_cid.lock) { + /* Required across execve() */ + if (t == current) + mm_cid_transit_to_task(t, this_cpu_ptr(mm->mm_cid.pcpu)); + /* Ignore mode change. There is nothing to do. */ + sched_mm_cid_remove_user(t); + } + } + + /* + * As this is the last user (execve(), process exit or failed + * fork(2)) there is no concurrency anymore. + * + * Synchronize eventually pending work to ensure that there are no + * dangling references left. @t->mm_cid.users is zero so nothing + * can queue this work anymore. + */ + irq_work_sync(&mm->mm_cid.irq_work); + cancel_work_sync(&mm->mm_cid.work); +} + +/* Deactivate MM CID allocation across execve() */ +void sched_mm_cid_before_execve(struct task_struct *t) +{ + sched_mm_cid_exit(t); +} + +/* Reactivate MM CID after execve() */ +void sched_mm_cid_after_execve(struct task_struct *t) +{ + if (t->mm) + sched_mm_cid_fork(t); +} + +static void mm_cid_work_fn(struct work_struct *work) +{ + struct mm_struct *mm = container_of(work, struct mm_struct, mm_cid.work); + + guard(mutex)(&mm->mm_cid.mutex); + /* Did the last user task exit already? */ + if (!mm->mm_cid.users) + return; + + scoped_guard(raw_spinlock_irq, &mm->mm_cid.lock) { + /* Have fork() or exit() handled it already? */ + if (!mm->mm_cid.update_deferred) + return; + /* This clears mm_cid::update_deferred */ + if (!mm_update_max_cids(mm)) + return; + /* Affinity changes can only switch back to task mode */ + if (WARN_ON_ONCE(cid_on_cpu(mm->mm_cid.mode))) + return; + } + mm_cid_fixup_cpus_to_tasks(mm); +} + +static void mm_cid_irq_work(struct irq_work *work) +{ + struct mm_struct *mm = container_of(work, struct mm_struct, mm_cid.irq_work); + + /* + * Needs to be unconditional because mm_cid::lock cannot be held + * when scheduling work as mm_update_cpus_allowed() nests inside + * rq::lock and schedule_work() might end up in wakeup... + */ + schedule_work(&mm->mm_cid.work); +} + +void mm_init_cid(struct mm_struct *mm, struct task_struct *p) +{ + mm->mm_cid.max_cids = 0; + mm->mm_cid.mode = 0; + mm->mm_cid.nr_cpus_allowed = p->nr_cpus_allowed; + mm->mm_cid.users = 0; + mm->mm_cid.pcpu_thrs = 0; + mm->mm_cid.update_deferred = 0; + raw_spin_lock_init(&mm->mm_cid.lock); + mutex_init(&mm->mm_cid.mutex); + mm->mm_cid.irq_work = IRQ_WORK_INIT_HARD(mm_cid_irq_work); + INIT_WORK(&mm->mm_cid.work, mm_cid_work_fn); + INIT_HLIST_HEAD(&mm->mm_cid.user_list); + cpumask_copy(mm_cpus_allowed(mm), &p->cpus_mask); + bitmap_zero(mm_cidmask(mm), num_possible_cpus()); +} +#else /* CONFIG_SCHED_MM_CID */ +static inline void mm_update_cpus_allowed(struct mm_struct *mm, const struct cpumask *affmsk) { } +static inline void sched_mm_cid_fork(struct task_struct *t) { } +#endif /* !CONFIG_SCHED_MM_CID */ + +static DEFINE_PER_CPU(struct sched_change_ctx, sched_change_ctx); + +struct sched_change_ctx *sched_change_begin(struct task_struct *p, unsigned int flags) +{ + struct sched_change_ctx *ctx = this_cpu_ptr(&sched_change_ctx); + + /* + * Must exclusively use matched flags since this is both dequeue and + * enqueue. + */ + WARN_ON_ONCE(flags & 0xFFFF0000); + + *ctx = (struct sched_change_ctx){ + .p = p, + .flags = flags, + .queued = task_on_rq_queued(p), + }; + + return ctx; +} + +void sched_change_end(struct sched_change_ctx *ctx) +{ + struct task_struct *p = ctx->p; + struct rq *rq = task_rq(ctx->p); + + /* Trigger resched if task sched_prio has been modified. */ + if (ctx->queued) { + update_rq_clock(rq); + requeue_task(p, rq); + wakeup_preempt(rq); + } +} diff --git a/kernel/sched/alt_core.h b/kernel/sched/alt_core.h new file mode 100644 index 000000000..0c25b4590 --- /dev/null +++ b/kernel/sched/alt_core.h @@ -0,0 +1,197 @@ +#ifndef _KERNEL_SCHED_ALT_CORE_H +#define _KERNEL_SCHED_ALT_CORE_H + +/* + * Compile time debug macro + * #define ALT_SCHED_DEBUG + */ + +/* + * Task related inlined functions + */ +static inline bool is_migration_disabled(struct task_struct *p) +{ + return p->migration_disabled; +} + +/* rt_prio(prio) defined in include/linux/sched/rt.h */ +#define rt_task(p) rt_prio((p)->prio) +#define rt_policy(policy) ((policy) == SCHED_FIFO || (policy) == SCHED_RR) +#define task_has_rt_policy(p) (rt_policy((p)->policy)) + +#define fair_policy(policy) ((policy) == SCHED_NORMAL || (policy) == SCHED_BATCH) + +#define valid_policy(policy) ((policy) <= SCHED_IDLE) + +#define task_has_dl_policy(p) (false) +#define dl_prio(prio) (false) + +struct affinity_context { + const struct cpumask *new_mask; + struct cpumask *user_mask; + unsigned int flags; +}; + +/* CONFIG_SCHED_CLASS_EXT is not supported */ +#define scx_switched_all() false + +#define SCA_CHECK 0x01 +#define SCA_MIGRATE_DISABLE 0x02 +#define SCA_MIGRATE_ENABLE 0x04 +#define SCA_USER 0x08 + +extern int __set_cpus_allowed_ptr(struct task_struct *p, struct affinity_context *ctx); + +static inline cpumask_t *alloc_user_cpus_ptr(int node) +{ + /* + * See set_cpus_allowed_force() above for the rcu_head usage. + */ + int size = max_t(int, cpumask_size(), sizeof(struct rcu_head)); + + return kmalloc_node(size, GFP_KERNEL, node); +} + +#ifdef CONFIG_RT_MUTEXES + +static inline int __rt_effective_prio(struct task_struct *pi_task, int prio) +{ + if (pi_task) + prio = min(prio, pi_task->prio); + + return prio; +} + +static inline int rt_effective_prio(struct task_struct *p, int prio) +{ + struct task_struct *pi_task = rt_mutex_get_top_task(p); + + return __rt_effective_prio(pi_task, prio); +} + +#else /* !CONFIG_RT_MUTEXES: */ + +static inline int rt_effective_prio(struct task_struct *p, int prio) +{ + return prio; +} + +#endif /* !CONFIG_RT_MUTEXES */ + +extern int __sched_setscheduler(struct task_struct *p, const struct sched_attr *attr, bool user, bool pi); +extern int __sched_setaffinity(struct task_struct *p, struct affinity_context *ctx); + +/* + * Context API + */ +static inline struct rq *__task_access_lock(struct task_struct *p, raw_spinlock_t **plock) +{ + struct rq *rq; + for (;;) { + rq = task_rq(p); + if (p->on_cpu || task_on_rq_queued(p)) { + raw_spin_lock(&rq->lock); + if (likely((p->on_cpu || task_on_rq_queued(p)) && rq == task_rq(p))) { + *plock = &rq->lock; + return rq; + } + raw_spin_unlock(&rq->lock); + } else if (task_on_rq_migrating(p)) { + do { + cpu_relax(); + } while (unlikely(task_on_rq_migrating(p))); + } else { + *plock = NULL; + return rq; + } + } +} + +static inline void __task_access_unlock(struct task_struct *p, raw_spinlock_t *lock) +{ + if (NULL != lock) + raw_spin_unlock(lock); +} + +static inline struct rq *task_access_lock(struct task_struct *p, struct rq_flags *rf) +{ + raw_spin_lock_irqsave(&p->pi_lock, rf->flags); + return __task_access_lock(p, &rf->lock); +} + +static inline void task_access_unlock(struct task_struct *p, struct rq_flags *rf) +{ + __task_access_unlock(p, rf->lock); + raw_spin_unlock_irqrestore(&p->pi_lock, rf->flags); +} + +DEFINE_LOCK_GUARD_1(task_access_lock, struct task_struct, + _T->rq = task_access_lock(_T->lock, &_T->rf), + task_access_unlock(_T->lock, &_T->rf), + struct rq *rq; struct rq_flags rf) + +void check_task_changed(struct task_struct *p, struct rq *rq); + +/* + * RQ related inlined functions + */ + +/* + * This routine assume that the idle task always in queue + */ +static inline struct task_struct *sched_rq_first_task(struct rq *rq) +{ + const struct list_head *head = &rq->queue.heads[sched_rq_prio_idx(rq)]; + + return list_first_entry(head, struct task_struct, sq_node); +} + +static inline struct task_struct * sched_rq_next_task(struct task_struct *p, struct rq *rq) +{ + struct list_head *next = p->sq_node.next; + + if (&rq->queue.heads[0] <= next && next < &rq->queue.heads[SCHED_LEVELS]) { + struct list_head *head; + unsigned long idx = next - &rq->queue.heads[0]; + + idx = find_next_bit(rq->queue.bitmap, SCHED_QUEUE_BITS, + sched_idx2prio(idx, rq) + 1); + head = &rq->queue.heads[sched_prio2idx(idx, rq)]; + + return list_first_entry(head, struct task_struct, sq_node); + } + + return list_next_entry(p, sq_node); +} + +extern void requeue_task(struct task_struct *p, struct rq *rq); + +#ifdef ALT_SCHED_DEBUG +extern void alt_sched_debug(void); +#else +static inline void alt_sched_debug(void) {} +#endif + +extern int sched_yield_type; + +extern cpumask_t sched_rq_pending_mask ____cacheline_aligned_in_smp; + +DECLARE_STATIC_KEY_FALSE(sched_smt_present); +DECLARE_PER_CPU_ALIGNED(cpumask_t *, sched_cpu_llc_mask); + +extern cpumask_t sched_smt_mask ____cacheline_aligned_in_smp; + +extern cpumask_t *const sched_idle_mask; +extern cpumask_t *const sched_sg_idle_mask; +extern cpumask_t *const sched_pcore_idle_mask; +extern cpumask_t *const sched_ecore_idle_mask; + +extern struct rq *move_queued_task(struct rq *rq, struct task_struct *p, int new_cpu); + +DECLARE_STATIC_CALL(sched_idle_select_func, cpumask_and); + +/* balance callback */ +extern struct balance_callback *splice_balance_callbacks(struct rq *rq); +extern void balance_callbacks(struct rq *rq, struct balance_callback *head); + +#endif /* _KERNEL_SCHED_ALT_CORE_H */ diff --git a/kernel/sched/alt_debug.c b/kernel/sched/alt_debug.c new file mode 100644 index 000000000..1dbd7eb6a --- /dev/null +++ b/kernel/sched/alt_debug.c @@ -0,0 +1,32 @@ +/* + * kernel/sched/alt_debug.c + * + * Print the alt scheduler debugging details + * + * Author: Alfred Chen + * Date : 2020 + */ +#include "sched.h" +#include "linux/sched/debug.h" + +/* + * This allows printing both to /proc/sched_debug and + * to the console + */ +#define SEQ_printf(m, x...) \ + do { \ + if (m) \ + seq_printf(m, x); \ + else \ + pr_cont(x); \ + } while (0) + +void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns, + struct seq_file *m) +{ + SEQ_printf(m, "%s (%d, #threads: %d)\n", p->comm, task_pid_nr_ns(p, ns), + get_nr_threads(p)); +} + +void proc_sched_set_task(struct task_struct *p) +{} diff --git a/kernel/sched/alt_sched.h b/kernel/sched/alt_sched.h new file mode 100644 index 000000000..e494d6401 --- /dev/null +++ b/kernel/sched/alt_sched.h @@ -0,0 +1,1067 @@ +#ifndef _KERNEL_SCHED_ALT_SCHED_H +#define _KERNEL_SCHED_ALT_SCHED_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../workqueue_internal.h" + +#include "cpupri.h" + +#ifdef CONFIG_CGROUP_SCHED +/* task group related information */ +struct task_group { + struct cgroup_subsys_state css; + + struct rcu_head rcu; + struct list_head list; + + struct task_group *parent; + struct list_head siblings; + struct list_head children; +}; + +extern struct task_group *sched_create_group(struct task_group *parent); +extern void sched_online_group(struct task_group *tg, + struct task_group *parent); +extern void sched_destroy_group(struct task_group *tg); +extern void sched_release_group(struct task_group *tg); +#endif /* CONFIG_CGROUP_SCHED */ + +#define MIN_SCHED_NORMAL_PRIO (32) +/* + * levels: RT(0-24), reserved(25-31), NORMAL(32-63), cpu idle task(64) + * + * -- BMQ -- + * NORMAL: (lower boost range 12, NICE_WIDTH 40, higher boost range 12) / 2 + * -- PDS -- + * NORMAL: SCHED_EDGE_DELTA + ((NICE_WIDTH 40) / 2) + */ +#define SCHED_LEVELS (64 + 1) + +#define IDLE_TASK_SCHED_PRIO (SCHED_LEVELS - 1) + +/* + * Increase resolution of nice-level calculations for 64-bit architectures. + * The extra resolution improves shares distribution and load balancing of + * low-weight task groups (eg. nice +19 on an autogroup), deeper taskgroup + * hierarchies, especially on larger systems. This is not a user-visible change + * and does not change the user-interface for setting shares/weights. + * + * We increase resolution only if we have enough bits to allow this increased + * resolution (i.e. 64-bit). The costs for increasing resolution when 32-bit + * are pretty high and the returns do not justify the increased costs. + * + * Really only required when CONFIG_FAIR_GROUP_SCHED=y is also set, but to + * increase coverage and consistency always enable it on 64-bit platforms. + */ +#ifdef CONFIG_64BIT +# define NICE_0_LOAD_SHIFT (SCHED_FIXEDPOINT_SHIFT + SCHED_FIXEDPOINT_SHIFT) +# define scale_load(w) ((w) << SCHED_FIXEDPOINT_SHIFT) +# define scale_load_down(w) \ +({ \ + unsigned long __w = (w); \ + if (__w) \ + __w = max(2UL, __w >> SCHED_FIXEDPOINT_SHIFT); \ + __w; \ +}) +#else +# define NICE_0_LOAD_SHIFT (SCHED_FIXEDPOINT_SHIFT) +# define scale_load(w) (w) +# define scale_load_down(w) (w) +#endif + +/* task_struct::on_rq states: */ +#define TASK_ON_RQ_QUEUED 1 +#define TASK_ON_RQ_MIGRATING 2 + +static inline int task_on_rq_queued(struct task_struct *p) +{ + return READ_ONCE(p->on_rq) == TASK_ON_RQ_QUEUED; +} + +static inline int task_on_rq_migrating(struct task_struct *p) +{ + return READ_ONCE(p->on_rq) == TASK_ON_RQ_MIGRATING; +} + +/* Wake flags. The first three directly map to some SD flag value */ +#define WF_EXEC 0x02 /* Wakeup after exec; maps to SD_BALANCE_EXEC */ +#define WF_FORK 0x04 /* Wakeup after fork; maps to SD_BALANCE_FORK */ +#define WF_TTWU 0x08 /* Wakeup; maps to SD_BALANCE_WAKE */ + +#define WF_SYNC 0x10 /* Waker goes to sleep after wakeup */ +#define WF_MIGRATED 0x20 /* Internal use, task got migrated */ +#define WF_CURRENT_CPU 0x40 /* Prefer to move the wakee to the current CPU. */ + +static_assert(WF_EXEC == SD_BALANCE_EXEC); +static_assert(WF_FORK == SD_BALANCE_FORK); +static_assert(WF_TTWU == SD_BALANCE_WAKE); + +/* + * {de,en}queue flags: + * + * SLEEP/WAKEUP - task is no-longer/just-became runnable + * + * SAVE/RESTORE - an otherwise spurious dequeue/enqueue, done to ensure tasks + * are in a known state which allows modification. Such pairs + * should preserve as much state as possible. + * + * MOVE - paired with SAVE/RESTORE, explicitly does not preserve the location + * in the runqueue. + * + * NOCLOCK - skip the update_rq_clock() (avoids double updates) + * + * MIGRATION - p->on_rq == TASK_ON_RQ_MIGRATING (used for DEADLINE) + * + * DELAYED - de/re-queue a sched_delayed task + * + * CLASS - going to update p->sched_class; makes sched_change call the + * various switch methods. + * + * ENQUEUE_HEAD - place at front of runqueue (tail if not specified) + * ENQUEUE_REPLENISH - CBS (replenish runtime and postpone deadline) + * ENQUEUE_MIGRATED - the task was migrated during wakeup + * ENQUEUE_RQ_SELECTED - ->select_task_rq() was called + * + * XXX SAVE/RESTORE in combination with CLASS doesn't really make sense, but + * SCHED_DEADLINE seems to rely on this for now. + */ + +#define DEQUEUE_SLEEP 0x0001 /* Matches ENQUEUE_WAKEUP */ +#define DEQUEUE_SAVE 0x0002 /* Matches ENQUEUE_RESTORE */ +#define DEQUEUE_MOVE 0x0004 /* Matches ENQUEUE_MOVE */ +#define DEQUEUE_NOCLOCK 0x0008 /* Matches ENQUEUE_NOCLOCK */ + +#define DEQUEUE_MIGRATING 0x0010 /* Matches ENQUEUE_MIGRATING */ +#define DEQUEUE_DELAYED 0x0020 /* Matches ENQUEUE_DELAYED */ +#define DEQUEUE_CLASS 0x0040 /* Matches ENQUEUE_CLASS */ + +#define DEQUEUE_SPECIAL 0x00010000 +#define DEQUEUE_THROTTLE 0x00020000 + +#define ENQUEUE_WAKEUP 0x0001 +#define ENQUEUE_RESTORE 0x0002 +#define ENQUEUE_MOVE 0x0004 +#define ENQUEUE_NOCLOCK 0x0008 + +#define ENQUEUE_MIGRATING 0x0010 +#define ENQUEUE_DELAYED 0x0020 +#define ENQUEUE_CLASS 0x0040 + +#define ENQUEUE_HEAD 0x00010000 +#define ENQUEUE_REPLENISH 0x00020000 +#define ENQUEUE_MIGRATED 0x00040000 +#define ENQUEUE_INITIAL 0x00080000 +#define ENQUEUE_RQ_SELECTED 0x00100000 + + +#define SCHED_QUEUE_BITS (SCHED_LEVELS - 1) + +struct sched_queue { + DECLARE_BITMAP(bitmap, SCHED_QUEUE_BITS); + struct list_head heads[SCHED_LEVELS]; +}; + +struct rq; +struct cpuidle_state; + +struct balance_callback { + struct balance_callback *next; + void (*func)(struct rq *rq); +}; + +typedef void (*balance_func_t)(struct rq *rq, int cpu); + +struct balance_arg { + struct task_struct *task; + int active; + cpumask_t *cpumask; +}; + +/* + * This is the main, per-CPU runqueue data structure. + * This data should only be modified by the local cpu. + */ +struct rq { + /* runqueue lock: */ + raw_spinlock_t lock; + + struct task_struct __rcu *curr; + struct task_struct *idle; + struct task_struct *stop; + struct mm_struct *prev_mm; + + struct sched_queue queue ____cacheline_aligned; + + int prio; +#ifdef CONFIG_SCHED_PDS + int prio_idx; + u64 time_edge; +#endif + + /* switch count */ + u64 nr_switches; + + atomic_t nr_iowait; + + u64 last_seen_need_resched_ns; + int ticks_without_resched; + +#ifdef CONFIG_MEMBARRIER + int membarrier_state; +#endif + + int cpu; /* cpu of this runqueue */ + bool online; + + unsigned int ttwu_pending; + unsigned char nohz_idle_balance; + unsigned char idle_balance; + +#ifdef CONFIG_HAVE_SCHED_AVG_IRQ + struct sched_avg avg_irq; +#endif + + balance_func_t balance_func; + struct balance_arg active_balance_arg ____cacheline_aligned; + struct cpu_stop_work active_balance_work; + + struct balance_callback *balance_callback; + +#ifdef CONFIG_HOTPLUG_CPU + struct rcuwait hotplug_wait; +#endif + unsigned int nr_pinned; + +#ifdef CONFIG_IRQ_TIME_ACCOUNTING + u64 prev_irq_time; +#endif /* CONFIG_IRQ_TIME_ACCOUNTING */ +#ifdef CONFIG_PARAVIRT + u64 prev_steal_time; +#endif /* CONFIG_PARAVIRT */ +#ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING + u64 prev_steal_time_rq; +#endif /* CONFIG_PARAVIRT_TIME_ACCOUNTING */ + + /* For genenal cpu load util */ + s32 load_history; + u64 load_block; + u64 load_stamp; + + /* calc_load related fields */ + unsigned long calc_load_update; + long calc_load_active; + + /* Ensure that all clocks are in the same cache line */ + u64 clock ____cacheline_aligned; + u64 clock_task; + u64 prio_balance_time; + + unsigned int nr_running; + unsigned long nr_uninterruptible; + +#ifdef CONFIG_SCHED_HRTICK + call_single_data_t hrtick_csd; + struct hrtimer hrtick_timer; + ktime_t hrtick_time; + ktime_t hrtick_delay; + unsigned int hrtick_sched; +#endif + +#ifdef CONFIG_SCHEDSTATS + + /* latency stats */ + struct sched_info rq_sched_info; + unsigned long long rq_cpu_time; + /* could above be rq->cfs_rq.exec_clock + rq->rt_rq.rt_runtime ? */ + + /* sys_sched_yield() stats */ + unsigned int yld_count; + + /* schedule() stats */ + unsigned int sched_switch; + unsigned int sched_count; + unsigned int sched_goidle; + + /* try_to_wake_up() stats */ + unsigned int ttwu_count; + unsigned int ttwu_local; +#endif /* CONFIG_SCHEDSTATS */ + +#ifdef CONFIG_CPU_IDLE + /* Must be inspected within a rcu lock section */ + struct cpuidle_state *idle_state; +#endif + +#ifdef CONFIG_NO_HZ_COMMON + call_single_data_t nohz_csd; + atomic_t nohz_flags; +#endif /* CONFIG_NO_HZ_COMMON */ + + /* Scratch cpumask to be temporarily used under rq_lock */ + cpumask_var_t scratch_mask; +}; + +extern unsigned int sysctl_sched_base_slice; + +extern unsigned long rq_load_util(struct rq *rq, unsigned long max); + +extern unsigned long calc_load_update; +extern atomic_long_t calc_load_tasks; + +extern void calc_global_load_tick(struct rq *this_rq); +extern long calc_load_fold_active(struct rq *this_rq, long adjust); + +DECLARE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues); +#define cpu_rq(cpu) (&per_cpu(runqueues, (cpu))) +#define this_rq() this_cpu_ptr(&runqueues) +#define task_rq(p) cpu_rq(task_cpu(p)) +#define cpu_curr(cpu) (cpu_rq(cpu)->curr) +#define raw_rq() raw_cpu_ptr(&runqueues) + +static inline bool idle_rq(struct rq *rq) +{ + return rq->curr == rq->idle && !rq->nr_running && !rq->ttwu_pending; +} + +/** + * available_idle_cpu - is a given CPU idle for enqueuing work. + * @cpu: the CPU in question. + * + * Return: 1 if the CPU is currently idle. 0 otherwise. + */ +static inline bool available_idle_cpu(int cpu) +{ + if (!idle_rq(cpu_rq(cpu))) + return 0; + + if (vcpu_is_preempted(cpu)) + return 0; + + return 1; +} + +#ifdef CONFIG_SYSCTL +void register_sched_domain_sysctl(void); +void unregister_sched_domain_sysctl(void); +#else +static inline void register_sched_domain_sysctl(void) +{ +} +static inline void unregister_sched_domain_sysctl(void) +{ +} +#endif + +extern bool sched_smp_initialized; + +enum { +#ifdef CONFIG_SCHED_SMT + SMT_LEVEL_SPACE_HOLDER, +#endif + CLUSTER_LEVEL_SPACE_HOLDER, + COREGROUP_LEVEL_SPACE_HOLDER, + CORE_LEVEL_SPACE_HOLDER, + OTHER_LEVEL_SPACE_HOLDER, + NR_CPU_AFFINITY_LEVELS +}; + +DECLARE_PER_CPU_ALIGNED(cpumask_t [NR_CPU_AFFINITY_LEVELS], sched_cpu_topo_masks); + +static inline int +__best_mask_cpu(const cpumask_t *cpumask, const cpumask_t *mask) +{ + int cpu; + + while ((cpu = cpumask_any_and(cpumask, mask)) >= nr_cpu_ids) + mask++; + + return cpu; +} + +static inline int best_mask_cpu(int cpu, const cpumask_t *mask) +{ + return __best_mask_cpu(mask, per_cpu(sched_cpu_topo_masks, cpu)); +} + +extern void resched_latency_warn(int cpu, u64 latency); + +#ifndef arch_scale_freq_tick +static __always_inline +void arch_scale_freq_tick(void) +{ +} +#endif + +#ifndef arch_scale_freq_capacity +static __always_inline +unsigned long arch_scale_freq_capacity(int cpu) +{ + return SCHED_CAPACITY_SCALE; +} +#endif + +static inline u64 __rq_clock_broken(struct rq *rq) +{ + return READ_ONCE(rq->clock); +} + +static inline u64 rq_clock(struct rq *rq) +{ + /* + * Relax lockdep_assert_held() checking as in VRQ, call to + * sched_info_xxxx() may not held rq->lock + * lockdep_assert_held(&rq->lock); + */ + return rq->clock; +} + +static inline u64 rq_clock_task(struct rq *rq) +{ + /* + * Relax lockdep_assert_held() checking as in VRQ, call to + * sched_info_xxxx() may not held rq->lock + * lockdep_assert_held(&rq->lock); + */ + return rq->clock_task; +} + +/* + * Below are scheduler API which using in other kernel code + * It use the dummy rq_flags + * ToDo : BMQ need to support these APIs for compatibility with mainline + * scheduler code. + */ +struct rq_flags { + unsigned long flags; + raw_spinlock_t *lock; +}; + +struct rq *__task_rq_lock(struct task_struct *p, struct rq_flags *rf) + __acquires(rq->lock); + +struct rq *task_rq_lock(struct task_struct *p, struct rq_flags *rf) + __acquires(p->pi_lock) + __acquires(rq->lock); + +static inline void __task_rq_unlock(struct rq *rq, struct rq_flags *rf) + __releases(rq->lock) +{ + raw_spin_unlock(&rq->lock); +} + +static inline void +task_rq_unlock(struct rq *rq, struct task_struct *p, struct rq_flags *rf) + __releases(rq->lock) + __releases(p->pi_lock) +{ + raw_spin_unlock(&rq->lock); + raw_spin_unlock_irqrestore(&p->pi_lock, rf->flags); +} + +DEFINE_LOCK_GUARD_1(task_rq_lock, struct task_struct, + _T->rq = task_rq_lock(_T->lock, &_T->rf), + task_rq_unlock(_T->rq, _T->lock, &_T->rf), + struct rq *rq; struct rq_flags rf) + +static inline void +rq_lock(struct rq *rq, struct rq_flags *rf) + __acquires(rq->lock) +{ + raw_spin_lock(&rq->lock); +} + +static inline void +rq_unlock(struct rq *rq, struct rq_flags *rf) + __releases(rq->lock) +{ + raw_spin_unlock(&rq->lock); +} + +static inline void +rq_lock_irq(struct rq *rq, struct rq_flags *rf) + __acquires(rq->lock) +{ + raw_spin_lock_irq(&rq->lock); +} + +static inline void +rq_unlock_irq(struct rq *rq, struct rq_flags *rf) + __releases(rq->lock) +{ + raw_spin_unlock_irq(&rq->lock); +} + +DEFINE_LOCK_GUARD_1(rq_lock_irq, struct rq, + rq_lock_irq(_T->lock, &_T->rf), + rq_unlock_irq(_T->lock, &_T->rf), + struct rq_flags rf) + +static inline struct rq * +this_rq_lock_irq(struct rq_flags *rf) + __acquires(rq->lock) +{ + struct rq *rq; + + local_irq_disable(); + rq = this_rq(); + raw_spin_lock(&rq->lock); + + return rq; +} + +static inline raw_spinlock_t *__rq_lockp(struct rq *rq) +{ + return &rq->lock; +} + +static inline raw_spinlock_t *rq_lockp(struct rq *rq) +{ + return __rq_lockp(rq); +} + +static inline void lockdep_assert_rq_held(struct rq *rq) +{ + lockdep_assert_held(__rq_lockp(rq)); +} + +extern void raw_spin_rq_lock_nested(struct rq *rq, int subclass); + +static inline void raw_spin_rq_lock(struct rq *rq) +{ + raw_spin_rq_lock_nested(rq, 0); +} + +static inline void raw_spin_rq_unlock(struct rq *rq) +{ + raw_spin_unlock(rq_lockp(rq)); +} + +static inline void raw_spin_rq_lock_irq(struct rq *rq) +{ + local_irq_disable(); + raw_spin_rq_lock(rq); +} + +static inline void raw_spin_rq_unlock_irq(struct rq *rq) +{ + raw_spin_rq_unlock(rq); + local_irq_enable(); +} + +static inline int task_current(struct rq *rq, struct task_struct *p) +{ + return rq->curr == p; +} + +static inline bool task_on_cpu(struct task_struct *p) +{ + return p->on_cpu; +} + +extern struct static_key_false sched_schedstats; + +#ifdef CONFIG_CPU_IDLE +static inline void idle_set_state(struct rq *rq, + struct cpuidle_state *idle_state) +{ + rq->idle_state = idle_state; +} +#else +static inline void idle_set_state(struct rq *rq, + struct cpuidle_state *idle_state) +{ +} +#endif + +static inline int cpu_of(const struct rq *rq) +{ + return rq->cpu; +} + +extern void resched_cpu(int cpu); + +#include "stats.h" + +#ifdef CONFIG_NO_HZ_COMMON +#define NOHZ_BALANCE_KICK_BIT 0 +#define NOHZ_STATS_KICK_BIT 1 + +#define NOHZ_BALANCE_KICK BIT(NOHZ_BALANCE_KICK_BIT) +#define NOHZ_STATS_KICK BIT(NOHZ_STATS_KICK_BIT) + +#define NOHZ_KICK_MASK (NOHZ_BALANCE_KICK | NOHZ_STATS_KICK) + +#define nohz_flags(cpu) (&cpu_rq(cpu)->nohz_flags) + +/* TODO: needed? +extern void nohz_balance_exit_idle(struct rq *rq); +#else +static inline void nohz_balance_exit_idle(struct rq *rq) { } +*/ +#endif + +#ifdef CONFIG_IRQ_TIME_ACCOUNTING +struct irqtime { + u64 total; + u64 tick_delta; + u64 irq_start_time; + struct u64_stats_sync sync; +}; + +DECLARE_PER_CPU(struct irqtime, cpu_irqtime); +DECLARE_STATIC_KEY_FALSE(sched_clock_irqtime); + +static inline int irqtime_enabled(void) +{ + return static_branch_likely(&sched_clock_irqtime); +} + +/* + * Returns the irqtime minus the softirq time computed by ksoftirqd. + * Otherwise ksoftirqd's sum_exec_runtime is substracted its own runtime + * and never move forward. + */ +static inline u64 irq_time_read(int cpu) +{ + struct irqtime *irqtime = &per_cpu(cpu_irqtime, cpu); + unsigned int seq; + u64 total; + + do { + seq = __u64_stats_fetch_begin(&irqtime->sync); + total = irqtime->total; + } while (__u64_stats_fetch_retry(&irqtime->sync, seq)); + + return total; +} +#else + +static inline int irqtime_enabled(void) +{ + return 0; +} + +#endif /* CONFIG_IRQ_TIME_ACCOUNTING */ + +#ifdef CONFIG_CPU_FREQ +DECLARE_PER_CPU(struct update_util_data __rcu *, cpufreq_update_util_data); +#endif /* CONFIG_CPU_FREQ */ + +#ifdef CONFIG_NO_HZ_FULL +extern int __init sched_tick_offload_init(void); +#else +static inline int sched_tick_offload_init(void) { return 0; } +#endif + +#ifdef arch_scale_freq_capacity +#ifndef arch_scale_freq_invariant +#define arch_scale_freq_invariant() (true) +#endif +#else /* arch_scale_freq_capacity */ +#define arch_scale_freq_invariant() (false) +#endif + +unsigned long sugov_effective_cpu_perf(int cpu, unsigned long actual, + unsigned long min, + unsigned long max); + +extern void schedule_idle(void); + +#define cap_scale(v, s) ((v)*(s) >> SCHED_CAPACITY_SHIFT) + +/* + * !! For sched_setattr_nocheck() (kernel) only !! + * + * This is actually gross. :( + * + * It is used to make schedutil kworker(s) higher priority than SCHED_DEADLINE + * tasks, but still be able to sleep. We need this on platforms that cannot + * atomically change clock frequency. Remove once fast switching will be + * available on such platforms. + * + * SUGOV stands for SchedUtil GOVernor. + */ +#define SCHED_FLAG_SUGOV 0x10000000 + +#ifdef CONFIG_MEMBARRIER +/* + * The scheduler provides memory barriers required by membarrier between: + * - prior user-space memory accesses and store to rq->membarrier_state, + * - store to rq->membarrier_state and following user-space memory accesses. + * In the same way it provides those guarantees around store to rq->curr. + */ +static inline void membarrier_switch_mm(struct rq *rq, + struct mm_struct *prev_mm, + struct mm_struct *next_mm) +{ + int membarrier_state; + + if (prev_mm == next_mm) + return; + + membarrier_state = atomic_read(&next_mm->membarrier_state); + if (READ_ONCE(rq->membarrier_state) == membarrier_state) + return; + + WRITE_ONCE(rq->membarrier_state, membarrier_state); +} +#else +static inline void membarrier_switch_mm(struct rq *rq, + struct mm_struct *prev_mm, + struct mm_struct *next_mm) +{ +} +#endif + +#ifdef CONFIG_NUMA +extern int sched_numa_find_closest(const struct cpumask *cpus, int cpu); +#else +static inline int sched_numa_find_closest(const struct cpumask *cpus, int cpu) +{ + return nr_cpu_ids; +} +#endif + +extern void swake_up_all_locked(struct swait_queue_head *q); +extern void __prepare_to_swait(struct swait_queue_head *q, struct swait_queue *wait); + +extern int try_to_wake_up(struct task_struct *tsk, unsigned int state, int wake_flags); + +#ifdef CONFIG_PREEMPT_DYNAMIC +extern int preempt_dynamic_mode; +extern int sched_dynamic_mode(const char *str); +extern void sched_dynamic_update(int mode); +#endif +extern const char *preempt_modes[]; + +static inline void nohz_run_idle_balance(int cpu) { } + +static inline unsigned long +uclamp_eff_value(struct task_struct *p, enum uclamp_id clamp_id) +{ + if (clamp_id == UCLAMP_MIN) + return 0; + + return SCHED_CAPACITY_SCALE; +} + +static inline bool uclamp_rq_is_capped(struct rq *rq) { return false; } + +static inline bool uclamp_is_used(void) +{ + return false; +} + +static inline unsigned long +uclamp_rq_get(struct rq *rq, enum uclamp_id clamp_id) +{ + if (clamp_id == UCLAMP_MIN) + return 0; + + return SCHED_CAPACITY_SCALE; +} + +static inline void +uclamp_rq_set(struct rq *rq, enum uclamp_id clamp_id, unsigned int value) +{ +} + +static inline bool uclamp_rq_is_idle(struct rq *rq) +{ + return false; +} + +#ifdef CONFIG_SCHED_MM_CID + +static __always_inline bool cid_on_cpu(unsigned int cid) +{ + return cid & MM_CID_ONCPU; +} + +static __always_inline bool cid_in_transit(unsigned int cid) +{ + return cid & MM_CID_TRANSIT; +} + +static __always_inline unsigned int cpu_cid_to_cid(unsigned int cid) +{ + return cid & ~MM_CID_ONCPU; +} + +static __always_inline unsigned int cid_to_cpu_cid(unsigned int cid) +{ + return cid | MM_CID_ONCPU; +} + +static __always_inline unsigned int cid_to_transit_cid(unsigned int cid) +{ + return cid | MM_CID_TRANSIT; +} + +static __always_inline unsigned int cid_from_transit_cid(unsigned int cid) +{ + return cid & ~MM_CID_TRANSIT; +} + +static __always_inline bool cid_on_task(unsigned int cid) +{ + /* True if none of the MM_CID_ONCPU, MM_CID_TRANSIT, MM_CID_UNSET bits is set */ + return cid < MM_CID_TRANSIT; +} + +static __always_inline void mm_drop_cid(struct mm_struct *mm, unsigned int cid) +{ + clear_bit(cid, mm_cidmask(mm)); +} + +static __always_inline void mm_unset_cid_on_task(struct task_struct *t) +{ + unsigned int cid = t->mm_cid.cid; + + t->mm_cid.cid = MM_CID_UNSET; + if (cid_on_task(cid)) + mm_drop_cid(t->mm, cid); +} + +static __always_inline void mm_drop_cid_on_cpu(struct mm_struct *mm, struct mm_cid_pcpu *pcp) +{ + /* Clear the ONCPU bit, but do not set UNSET in the per CPU storage */ + if (cid_on_cpu(pcp->cid)) { + pcp->cid = cpu_cid_to_cid(pcp->cid); + mm_drop_cid(mm, pcp->cid); + } +} + +static inline unsigned int __mm_get_cid(struct mm_struct *mm, unsigned int max_cids) +{ + unsigned int cid = find_first_zero_bit(mm_cidmask(mm), max_cids); + + if (cid >= max_cids) + return MM_CID_UNSET; + if (test_and_set_bit(cid, mm_cidmask(mm))) + return MM_CID_UNSET; + return cid; +} + +static inline unsigned int mm_get_cid(struct mm_struct *mm) +{ + unsigned int cid = __mm_get_cid(mm, READ_ONCE(mm->mm_cid.max_cids)); + + while (cid == MM_CID_UNSET) { + cpu_relax(); + cid = __mm_get_cid(mm, num_possible_cpus()); + } + return cid; +} + +static inline unsigned int mm_cid_converge(struct mm_struct *mm, unsigned int orig_cid, + unsigned int max_cids) +{ + unsigned int new_cid, cid = cpu_cid_to_cid(orig_cid); + + /* Is it in the optimal CID space? */ + if (likely(cid < max_cids)) + return orig_cid; + + /* Try to find one in the optimal space. Otherwise keep the provided. */ + new_cid = __mm_get_cid(mm, max_cids); + if (new_cid != MM_CID_UNSET) { + mm_drop_cid(mm, cid); + /* Preserve the ONCPU mode of the original CID */ + return new_cid | (orig_cid & MM_CID_ONCPU); + } + return orig_cid; +} + +static __always_inline void mm_cid_update_task_cid(struct task_struct *t, unsigned int cid) +{ + if (t->mm_cid.cid != cid) { + t->mm_cid.cid = cid; + rseq_sched_set_ids_changed(t); + } +} + +static __always_inline void mm_cid_update_pcpu_cid(struct mm_struct *mm, unsigned int cid) +{ + __this_cpu_write(mm->mm_cid.pcpu->cid, cid); +} + +static __always_inline void mm_cid_from_cpu(struct task_struct *t, unsigned int cpu_cid, + unsigned int mode) +{ + unsigned int max_cids, tcid = t->mm_cid.cid; + struct mm_struct *mm = t->mm; + + max_cids = READ_ONCE(mm->mm_cid.max_cids); + /* Optimize for the common case where both have the ONCPU bit set */ + if (likely(cid_on_cpu(cpu_cid & tcid))) { + if (likely(cpu_cid_to_cid(cpu_cid) < max_cids)) { + mm_cid_update_task_cid(t, cpu_cid); + return; + } + /* Try to converge into the optimal CID space */ + cpu_cid = mm_cid_converge(mm, cpu_cid, max_cids); + } else { + /* Hand over or drop the task owned CID */ + if (cid_on_task(tcid)) { + if (cid_on_cpu(cpu_cid)) + mm_unset_cid_on_task(t); + else + cpu_cid = cid_to_cpu_cid(tcid); + } + /* Still nothing, allocate a new one */ + if (!cid_on_cpu(cpu_cid)) + cpu_cid = cid_to_cpu_cid(mm_get_cid(mm)); + + /* Handle the transition mode flag if required */ + if (mode & MM_CID_TRANSIT) + cpu_cid = cpu_cid_to_cid(cpu_cid) | MM_CID_TRANSIT; + } + mm_cid_update_pcpu_cid(mm, cpu_cid); + mm_cid_update_task_cid(t, cpu_cid); +} + +static __always_inline void mm_cid_from_task(struct task_struct *t, unsigned int cpu_cid, + unsigned int mode) +{ + unsigned int max_cids, tcid = t->mm_cid.cid; + struct mm_struct *mm = t->mm; + + max_cids = READ_ONCE(mm->mm_cid.max_cids); + /* Optimize for the common case, where both have the ONCPU bit clear */ + if (likely(cid_on_task(tcid | cpu_cid))) { + if (likely(tcid < max_cids)) { + mm_cid_update_pcpu_cid(mm, tcid); + return; + } + /* Try to converge into the optimal CID space */ + tcid = mm_cid_converge(mm, tcid, max_cids); + } else { + /* Hand over or drop the CPU owned CID */ + if (cid_on_cpu(cpu_cid)) { + if (cid_on_task(tcid)) + mm_drop_cid_on_cpu(mm, this_cpu_ptr(mm->mm_cid.pcpu)); + else + tcid = cpu_cid_to_cid(cpu_cid); + } + /* Still nothing, allocate a new one */ + if (!cid_on_task(tcid)) + tcid = mm_get_cid(mm); + /* Set the transition mode flag if required */ + tcid |= mode & MM_CID_TRANSIT; + } + mm_cid_update_pcpu_cid(mm, tcid); + mm_cid_update_task_cid(t, tcid); +} + +static __always_inline void mm_cid_schedin(struct task_struct *next) +{ + struct mm_struct *mm = next->mm; + unsigned int cpu_cid, mode; + + if (!next->mm_cid.active) + return; + + cpu_cid = __this_cpu_read(mm->mm_cid.pcpu->cid); + mode = READ_ONCE(mm->mm_cid.mode); + if (likely(!cid_on_cpu(mode))) + mm_cid_from_task(next, cpu_cid, mode); + else + mm_cid_from_cpu(next, cpu_cid, mode); +} + +static __always_inline void mm_cid_schedout(struct task_struct *prev) +{ + /* During mode transitions CIDs are temporary and need to be dropped */ + if (likely(!cid_in_transit(prev->mm_cid.cid))) + return; + + mm_drop_cid(prev->mm, cid_from_transit_cid(prev->mm_cid.cid)); + prev->mm_cid.cid = MM_CID_UNSET; +} + +static inline void mm_cid_switch_to(struct task_struct *prev, struct task_struct *next) +{ + mm_cid_schedout(prev); + mm_cid_schedin(next); +} + +#else /* !CONFIG_SCHED_MM_CID: */ +static inline void mm_cid_switch_to(struct task_struct *prev, struct task_struct *next) { } +#endif /* !CONFIG_SCHED_MM_CID */ + +extern struct balance_callback balance_push_callback; + +static inline void +queue_balance_callback(struct rq *rq, + struct balance_callback *head, + void (*func)(struct rq *rq)) +{ + lockdep_assert_rq_held(rq); + + /* + * Don't (re)queue an already queued item; nor queue anything when + * balance_push() is active, see the comment with + * balance_push_callback. + */ + if (unlikely(head->next || rq->balance_callback == &balance_push_callback)) + return; + + head->func = func; + head->next = rq->balance_callback; + rq->balance_callback = head; +} + +/* + * The 'sched_change' pattern is the safe, easy and slow way of changing a + * task's scheduling properties. It dequeues a task, such that the scheduler + * is fully unaware of it; at which point its properties can be modified; + * after which it is enqueued again. + * + * Typically this must be called while holding task_rq_lock, since most/all + * properties are serialized under those locks. There is currently one + * exception to this rule in sched/ext which only holds rq->lock. + */ + +/* + * This structure is a temporary, used to preserve/convey the queueing state + * of the task between sched_change_begin() and sched_change_end(). Ensuring + * the task's queueing state is idempotent across the operation. + */ +struct sched_change_ctx { + u64 prio; + struct task_struct *p; + int flags; + bool queued; + bool running; +}; + +struct sched_change_ctx *sched_change_begin(struct task_struct *p, unsigned int flags); +void sched_change_end(struct sched_change_ctx *ctx); + +DEFINE_CLASS(sched_change, struct sched_change_ctx *, + sched_change_end(_T), + sched_change_begin(p, flags), + struct task_struct *p, unsigned int flags) + +DEFINE_CLASS_IS_UNCONDITIONAL(sched_change) + +#ifdef CONFIG_SCHED_BMQ +#include "bmq.h" +#endif +#ifdef CONFIG_SCHED_PDS +#include "pds.h" +#endif + +#endif /* _KERNEL_SCHED_ALT_SCHED_H */ diff --git a/kernel/sched/alt_topology.c b/kernel/sched/alt_topology.c new file mode 100644 index 000000000..cf5921e82 --- /dev/null +++ b/kernel/sched/alt_topology.c @@ -0,0 +1,287 @@ +#include "alt_core.h" +#include "alt_topology.h" + +static cpumask_t sched_pcore_mask ____cacheline_aligned_in_smp; + +static int __init pcore_cpus_setup(char *str) +{ + if (cpulist_parse(str, &sched_pcore_mask)) + pr_warn("sched/alt: pcore_cpus= incorrect CPU range\n"); + + return 1; +} +__setup("pcore_cpus=", pcore_cpus_setup); + +DEFINE_PER_CPU_READ_MOSTLY(enum cpu_topo_type, sched_cpu_topo); +DEFINE_PER_CPU_READ_MOSTLY(enum cpu_topo_balance_type, sched_cpu_topo_balance); +DEFINE_PER_CPU(struct balance_callback, active_balance_head); + +/* + * Idle cpu/rq selection functions + */ +#ifdef CONFIG_SCHED_SMT +static bool p1_idle_select_func(struct cpumask *dstp, const struct cpumask *src1p, + const struct cpumask *src2p) +{ + return cpumask_and(dstp, src1p, src2p + 1) || + cpumask_and(dstp, src1p, src2p); +} +#endif + +static bool p1p2_idle_select_func(struct cpumask *dstp, const struct cpumask *src1p, + const struct cpumask *src2p) +{ + return cpumask_and(dstp, src1p, src2p + 1) || + cpumask_and(dstp, src1p, src2p + 2) || + cpumask_and(dstp, src1p, src2p); +} + +/* common balance functions */ +static int active_balance_cpu_stop(void *data) +{ + struct balance_arg *arg = data; + struct task_struct *p = arg->task; + struct rq *rq = this_rq(); + unsigned long flags; + cpumask_t tmp; + + local_irq_save(flags); + + raw_spin_lock(&p->pi_lock); + raw_spin_lock(&rq->lock); + + arg->active = 0; + + if (task_on_rq_queued(p) && task_rq(p) == rq && + cpumask_and(&tmp, p->cpus_ptr, arg->cpumask) && + !is_migration_disabled(p)) { + int dcpu = __best_mask_cpu(&tmp, per_cpu(sched_cpu_llc_mask, cpu_of(rq))); + rq = move_queued_task(rq, p, dcpu); + } + + raw_spin_unlock(&rq->lock); + raw_spin_unlock_irqrestore(&p->pi_lock, flags); + + return 0; +} + +/* trigger_active_balance - for @rq */ +static inline int +trigger_active_balance(struct rq *src_rq, struct rq *rq, cpumask_t *target_mask) +{ + struct balance_arg *arg; + unsigned long flags; + struct task_struct *p; + int res; + + if (!raw_spin_trylock_irqsave(&rq->lock, flags)) + return 0; + + arg = &rq->active_balance_arg; + res = (1 == rq->nr_running) && \ + !is_migration_disabled((p = sched_rq_first_task(rq))) && \ + cpumask_intersects(p->cpus_ptr, target_mask) && \ + !arg->active; + if (res) { + arg->task = p; + arg->cpumask = target_mask; + + arg->active = 1; + } + + raw_spin_unlock_irqrestore(&rq->lock, flags); + + if (res) { + preempt_disable(); + raw_spin_unlock(&src_rq->lock); + + stop_one_cpu_nowait(cpu_of(rq), active_balance_cpu_stop, arg, + &rq->active_balance_work); + + preempt_enable(); + raw_spin_lock(&src_rq->lock); + } + + return res; +} + +static inline int +ecore_source_balance(struct rq *rq, cpumask_t *single_task_mask, cpumask_t *target_mask) +{ + if (cpumask_andnot(single_task_mask, single_task_mask, &sched_pcore_mask)) { + int i, cpu = cpu_of(rq); + + for_each_cpu_wrap(i, single_task_mask, cpu) + if (trigger_active_balance(rq, cpu_rq(i), target_mask)) + return 1; + } + + return 0; +} + +#ifdef CONFIG_SCHED_SMT +static inline int +smt_pcore_source_balance(struct rq *rq, cpumask_t *single_task_mask, cpumask_t *target_mask) +{ + cpumask_t smt_single_mask; + + if (cpumask_and(&smt_single_mask, single_task_mask, &sched_smt_mask)) { + int i, cpu = cpu_of(rq); + + for_each_cpu_wrap(i, &smt_single_mask, cpu) { + if (cpumask_subset(cpu_smt_mask(i), &smt_single_mask) && + trigger_active_balance(rq, cpu_rq(i), target_mask)) + return 1; + } + } + + return 0; +} + +/* smt p core balance functions */ +void smt_pcore_balance(struct rq *rq) +{ + cpumask_t single_task_mask; + + if (cpumask_andnot(&single_task_mask, cpu_active_mask, sched_idle_mask) && + cpumask_andnot(&single_task_mask, &single_task_mask, &sched_rq_pending_mask) && + (/* smt core group balance */ + (static_key_count(&sched_smt_present.key) > 1 && + smt_pcore_source_balance(rq, &single_task_mask, sched_sg_idle_mask) + ) || + /* e core to idle smt core balance */ + ecore_source_balance(rq, &single_task_mask, sched_sg_idle_mask))) + return; +} + +/* smt balance functions */ +void smt_balance(struct rq *rq) +{ + cpumask_t single_task_mask; + + if (cpumask_andnot(&single_task_mask, cpu_active_mask, sched_idle_mask) && + cpumask_andnot(&single_task_mask, &single_task_mask, &sched_rq_pending_mask) && + static_key_count(&sched_smt_present.key) > 1 && + smt_pcore_source_balance(rq, &single_task_mask, sched_sg_idle_mask)) + return; +} + +/* e core balance functions */ +void ecore_balance(struct rq *rq) +{ + cpumask_t single_task_mask; + + if (cpumask_andnot(&single_task_mask, cpu_active_mask, sched_idle_mask) && + cpumask_andnot(&single_task_mask, &single_task_mask, &sched_rq_pending_mask) && + cpumask_empty(sched_pcore_idle_mask) && + /* smt occupied p core to idle e core balance */ + smt_pcore_source_balance(rq, &single_task_mask, sched_ecore_idle_mask)) + return; +} +#endif /* CONFIG_SCHED_SMT */ + +/* p core balance functions */ +void pcore_balance(struct rq *rq) +{ + cpumask_t single_task_mask; + + if (cpumask_andnot(&single_task_mask, cpu_active_mask, sched_idle_mask) && + cpumask_andnot(&single_task_mask, &single_task_mask, &sched_rq_pending_mask) && + /* idle e core to p core balance */ + ecore_source_balance(rq, &single_task_mask, sched_pcore_idle_mask)) + return; +} + +#ifdef ALT_SCHED_DEBUG +#define SCHED_DEBUG_INFO(...) printk(KERN_INFO __VA_ARGS__) +#else +#define SCHED_DEBUG_INFO(...) do { } while(0) +#endif + +#define IDLE_SELECT_FUNC_UPDATE(func) \ +{ \ + static_call_update(sched_idle_select_func, &func); \ + printk(KERN_INFO "sched: idle select func -> "#func); \ +} + +#define SET_SCHED_CPU_TOPOLOGY(cpu, topo) \ +{ \ + per_cpu(sched_cpu_topo, (cpu)) = topo; \ + SCHED_DEBUG_INFO("sched: cpu#%02d -> "#topo, cpu); \ +} + +#define SET_SCHED_CPU_TOPOLOGY_BALANCE(cpu, balance) \ +{ \ + per_cpu(sched_cpu_topo_balance, (cpu)) = balance; \ + SCHED_DEBUG_INFO("sched: cpu#%02d -> "#balance, cpu); \ +} + +void sched_init_topology(void) +{ + int cpu; + struct rq *rq; + cpumask_t sched_ecore_mask = { CPU_BITS_NONE }; + int ecore_present = 0; + +#ifdef CONFIG_SCHED_SMT + if (!cpumask_empty(&sched_smt_mask)) + printk(KERN_INFO "sched: smt mask: 0x%08lx\n", sched_smt_mask.bits[0]); +#endif + + if (!cpumask_empty(&sched_pcore_mask)) { + cpumask_andnot(&sched_ecore_mask, cpu_online_mask, &sched_pcore_mask); + printk(KERN_INFO "sched: pcore mask: 0x%08lx, ecore mask: 0x%08lx\n", + sched_pcore_mask.bits[0], sched_ecore_mask.bits[0]); + + ecore_present = !cpumask_empty(&sched_ecore_mask); + } + + /* idle select function */ +#ifdef CONFIG_SCHED_SMT + if (cpumask_equal(&sched_smt_mask, cpu_online_mask)) { + IDLE_SELECT_FUNC_UPDATE(p1_idle_select_func); + } else +#endif + if (!cpumask_empty(&sched_pcore_mask)) { + IDLE_SELECT_FUNC_UPDATE(p1p2_idle_select_func); + } + + /* CPU topology setup */ + for_each_online_cpu(cpu) { + rq = cpu_rq(cpu); + /* take chance to reset time slice for idle tasks */ + rq->idle->time_slice = sysctl_sched_base_slice; + +#ifdef CONFIG_SCHED_SMT + if (cpumask_weight(cpu_smt_mask(cpu)) > 1) { + SET_SCHED_CPU_TOPOLOGY(cpu, CPU_TOPOLOGY_SMT); + + if (cpumask_test_cpu(cpu, &sched_pcore_mask) && + !cpumask_intersects(&sched_ecore_mask, &sched_smt_mask)) { + SET_SCHED_CPU_TOPOLOGY_BALANCE(cpu, CPU_TOPOLOGY_BALANCE_SMT_PCORE); + } else { + SET_SCHED_CPU_TOPOLOGY_BALANCE(cpu, CPU_TOPOLOGY_BALANCE_SMT); + } + + continue; + } +#endif + /* !SMT or only one cpu in sg */ + if (cpumask_test_cpu(cpu, &sched_pcore_mask)) { + SET_SCHED_CPU_TOPOLOGY(cpu, CPU_TOPOLOGY_PCORE); + + if (ecore_present) + SET_SCHED_CPU_TOPOLOGY_BALANCE(cpu, CPU_TOPOLOGY_BALANCE_PCORE); + + continue; + } + + if (cpumask_test_cpu(cpu, &sched_ecore_mask)) { + SET_SCHED_CPU_TOPOLOGY(cpu, CPU_TOPOLOGY_ECORE); +#ifdef CONFIG_SCHED_SMT + if (cpumask_intersects(&sched_pcore_mask, &sched_smt_mask)) + SET_SCHED_CPU_TOPOLOGY_BALANCE(cpu, CPU_TOPOLOGY_BALANCE_ECORE); +#endif + } + } +} diff --git a/kernel/sched/alt_topology.h b/kernel/sched/alt_topology.h new file mode 100644 index 000000000..14591a303 --- /dev/null +++ b/kernel/sched/alt_topology.h @@ -0,0 +1,113 @@ +#ifndef _KERNEL_SCHED_ALT_TOPOLOGY_H +#define _KERNEL_SCHED_ALT_TOPOLOGY_H + +/* + * CPU topology type + */ +enum cpu_topo_type { + CPU_TOPOLOGY_DEFAULT = 0, + CPU_TOPOLOGY_PCORE, + CPU_TOPOLOGY_ECORE, +#ifdef CONFIG_SCHED_SMT + CPU_TOPOLOGY_SMT, +#endif +}; + +DECLARE_PER_CPU_READ_MOSTLY(enum cpu_topo_type, sched_cpu_topo); + +static inline void sched_set_idle_mask(const unsigned int cpu) +{ + cpumask_set_cpu(cpu, sched_idle_mask); + + switch (per_cpu(sched_cpu_topo, cpu)) { + case CPU_TOPOLOGY_DEFAULT: + break; + case CPU_TOPOLOGY_PCORE: + cpumask_set_cpu(cpu, sched_pcore_idle_mask); + break; + case CPU_TOPOLOGY_ECORE: + cpumask_set_cpu(cpu, sched_ecore_idle_mask); + break; +#ifdef CONFIG_SCHED_SMT + case CPU_TOPOLOGY_SMT: + if (cpumask_subset(cpu_smt_mask(cpu), sched_idle_mask)) + cpumask_or(sched_sg_idle_mask, sched_sg_idle_mask, cpu_smt_mask(cpu)); + break; +#endif + } +} + +static inline void sched_clear_idle_mask(const unsigned int cpu) +{ + cpumask_clear_cpu(cpu, sched_idle_mask); + + switch (per_cpu(sched_cpu_topo, cpu)) { + case CPU_TOPOLOGY_DEFAULT: + break; + case CPU_TOPOLOGY_PCORE: + cpumask_clear_cpu(cpu, sched_pcore_idle_mask); + break; + case CPU_TOPOLOGY_ECORE: + cpumask_clear_cpu(cpu, sched_ecore_idle_mask); + break; +#ifdef CONFIG_SCHED_SMT + case CPU_TOPOLOGY_SMT: + cpumask_andnot(sched_sg_idle_mask, sched_sg_idle_mask, cpu_smt_mask(cpu)); + break; +#endif + } +} + +/* + * CPU topology balance type + */ +enum cpu_topo_balance_type { + CPU_TOPOLOGY_BALANCE_NONE = 0, + CPU_TOPOLOGY_BALANCE_PCORE, +#ifdef CONFIG_SCHED_SMT + CPU_TOPOLOGY_BALANCE_ECORE, + CPU_TOPOLOGY_BALANCE_SMT, + CPU_TOPOLOGY_BALANCE_SMT_PCORE, +#endif +}; + +DECLARE_PER_CPU_READ_MOSTLY(enum cpu_topo_balance_type, sched_cpu_topo_balance); +DECLARE_PER_CPU(struct balance_callback, active_balance_head); + +extern void pcore_balance(struct rq *rq); +#ifdef CONFIG_SCHED_SMT +extern void ecore_balance(struct rq *rq); +extern void smt_balance(struct rq *rq); +extern void smt_pcore_balance(struct rq *rq); +#endif + +static inline void sched_cpu_topology_balance(const unsigned int cpu, struct rq *rq) +{ + if (!rq->online) + return; + + switch (per_cpu(sched_cpu_topo_balance, cpu)) { + case CPU_TOPOLOGY_BALANCE_NONE: + break; + case CPU_TOPOLOGY_BALANCE_PCORE: + queue_balance_callback(rq, &per_cpu(active_balance_head, cpu), pcore_balance); + break; +#ifdef CONFIG_SCHED_SMT + case CPU_TOPOLOGY_BALANCE_ECORE: + queue_balance_callback(rq, &per_cpu(active_balance_head, cpu), ecore_balance); + break; + case CPU_TOPOLOGY_BALANCE_SMT: + if (cpumask_test_cpu(cpu, sched_sg_idle_mask)) + queue_balance_callback(rq, &per_cpu(active_balance_head, cpu), smt_balance); + break; + case CPU_TOPOLOGY_BALANCE_SMT_PCORE: + if (cpumask_test_cpu(cpu, sched_sg_idle_mask)) + queue_balance_callback(rq, &per_cpu(active_balance_head, cpu), smt_pcore_balance); + break; +#endif + } +} + +extern void sched_init_topology(void); + +#endif /* _KERNEL_SCHED_ALT_TOPOLOGY_H */ diff --git a/kernel/sched/bmq.h b/kernel/sched/bmq.h new file mode 100644 index 000000000..44698be03 --- /dev/null +++ b/kernel/sched/bmq.h @@ -0,0 +1,104 @@ +#ifndef _KERNEL_SCHED_BMQ_H +#define _KERNEL_SCHED_BMQ_H + +#define ALT_SCHED_NAME "BMQ" + +/* + * BMQ only routines + */ +static inline void boost_task(struct task_struct *p, int n) +{ + int limit; + + switch (p->policy) { + case SCHED_NORMAL: + limit = -MAX_PRIORITY_ADJ; + break; + case SCHED_BATCH: + limit = 0; + break; + default: + return; + } + + p->boost_prio = max(limit, p->boost_prio - n); +} + +static inline void deboost_task(struct task_struct *p) +{ + if ((SCHED_NORMAL == p->policy || SCHED_BATCH == p->policy) && + p->boost_prio < MAX_PRIORITY_ADJ) + p->boost_prio++; +} + +/* + * Common interfaces + */ +static inline void sched_timeslice_imp(const int timeslice_ms) {} + +/* This API is used in task_prio(), return value readed by human users */ +static inline int +task_sched_prio_normal(const struct task_struct *p, const struct rq *rq) +{ + return p->prio + p->boost_prio - MIN_NORMAL_PRIO; +} + +static inline int task_sched_prio(const struct task_struct *p) +{ + return (p->prio < MIN_NORMAL_PRIO)? (p->prio >> 2) : + MIN_SCHED_NORMAL_PRIO + (p->prio + p->boost_prio - MIN_NORMAL_PRIO) / 2; +} + +#define TASK_SCHED_PRIO_IDX(p, rq, idx, prio) \ + prio = task_sched_prio(p); \ + idx = prio; + +static inline int sched_prio2idx(int prio, struct rq *rq) +{ + return prio; +} + +static inline int sched_idx2prio(int idx, struct rq *rq) +{ + return idx; +} + +static inline int sched_rq_prio_idx(struct rq *rq) +{ + return rq->prio; +} + +static inline int task_running_nice(struct task_struct *p) +{ + return (p->prio + p->boost_prio > DEFAULT_PRIO); +} + +static inline void sched_update_rq_clock(struct rq *rq) {} + +static inline void sched_task_renew(struct task_struct *p, const struct rq *rq) +{ + deboost_task(p); +} + +static inline void sched_task_sanity_check(struct task_struct *p, struct rq *rq) {} +static inline void sched_task_fork(struct task_struct *p, struct rq *rq) {} + +static inline void do_sched_yield_type_1(struct task_struct *p, struct rq *rq) +{ + p->boost_prio = MAX_PRIORITY_ADJ; +} + +static inline void sched_task_ttwu(struct task_struct *p) +{ + s64 delta = this_rq()->clock_task > p->last_ran; + + if (likely(delta > 0)) + boost_task(p, delta >> 22); +} + +static inline void sched_task_deactivate(struct task_struct *p, struct rq *rq) +{ + boost_task(p, 1); +} + +#endif /* _KERNEL_SCHED_BMQ_H */ diff --git a/kernel/sched/build_policy.c b/kernel/sched/build_policy.c index d74b54f81..0a5d9e97a 100644 --- a/kernel/sched/build_policy.c +++ b/kernel/sched/build_policy.c @@ -49,13 +49,17 @@ #include "idle.c" -#include "rt.c" -#include "cpudeadline.c" +#ifndef CONFIG_SCHED_ALT +# include "rt.c" +# include "cpudeadline.c" +#endif #include "pelt.c" #include "cputime.c" +#ifndef CONFIG_SCHED_ALT #include "deadline.c" +#endif #ifdef CONFIG_SCHED_CLASS_EXT # include diff --git a/kernel/sched/build_utility.c b/kernel/sched/build_utility.c index e2cf3b08d..a64bf71a6 100644 --- a/kernel/sched/build_utility.c +++ b/kernel/sched/build_utility.c @@ -56,6 +56,10 @@ #include "clock.c" +#ifdef CONFIG_SCHED_ALT +# include "alt_topology.c" +#endif + #ifdef CONFIG_CGROUP_CPUACCT # include "cpuacct.c" #endif @@ -68,7 +72,7 @@ # include "cpufreq_schedutil.c" #endif -#include "debug.c" +# include "debug.c" #ifdef CONFIG_SCHEDSTATS # include "stats.c" @@ -81,7 +85,9 @@ #include "wait.c" #include "cpupri.c" -#include "stop_task.c" +#ifndef CONFIG_SCHED_ALT +# include "stop_task.c" +#endif #include "topology.c" diff --git a/kernel/sched/cpufreq_schedutil.c b/kernel/sched/cpufreq_schedutil.c index dff4ee046..2fb2f9048 100644 --- a/kernel/sched/cpufreq_schedutil.c +++ b/kernel/sched/cpufreq_schedutil.c @@ -225,6 +225,7 @@ unsigned long sugov_effective_cpu_perf(int cpu, unsigned long actual, static void sugov_get_util(struct sugov_cpu *sg_cpu, unsigned long boost) { +#ifndef CONFIG_SCHED_ALT unsigned long min, max, util = scx_cpuperf_target(sg_cpu->cpu); if (!scx_switched_all()) @@ -233,6 +234,10 @@ static void sugov_get_util(struct sugov_cpu *sg_cpu, unsigned long boost) util = max(util, boost); sg_cpu->bw_min = min; sg_cpu->util = sugov_effective_cpu_perf(sg_cpu->cpu, util, min, max); +#else /* CONFIG_SCHED_ALT */ + sg_cpu->bw_min = 0; + sg_cpu->util = rq_load_util(cpu_rq(sg_cpu->cpu), arch_scale_cpu_capacity(sg_cpu->cpu)); +#endif /* CONFIG_SCHED_ALT */ } /** @@ -360,6 +365,7 @@ static bool sugov_hold_freq(struct sugov_cpu *sg_cpu) unsigned long idle_calls; bool ret; +#ifndef CONFIG_SCHED_ALT /* * The heuristics in this function is for the fair class. For SCX, the * performance target comes directly from the BPF scheduler. Let's just @@ -367,6 +373,7 @@ static bool sugov_hold_freq(struct sugov_cpu *sg_cpu) */ if (scx_switched_all()) return false; +#endif /* !CONFIG_SCHED_ALT */ /* if capped by uclamp_max, always update to be in compliance */ if (uclamp_rq_is_capped(cpu_rq(sg_cpu->cpu))) @@ -392,8 +399,10 @@ static inline bool sugov_hold_freq(struct sugov_cpu *sg_cpu) { return false; } */ static inline void ignore_dl_rate_limit(struct sugov_cpu *sg_cpu) { +#ifndef CONFIG_SCHED_ALT if (cpu_bw_dl(cpu_rq(sg_cpu->cpu)) > sg_cpu->bw_min) - sg_cpu->sg_policy->need_freq_update = true; + sg_cpu->sg_policy->limits_changed = true; +#endif } static inline bool sugov_update_single_common(struct sugov_cpu *sg_cpu, @@ -689,6 +698,7 @@ static int sugov_kthread_create(struct sugov_policy *sg_policy) } ret = sched_setattr_nocheck(thread, &attr); + if (ret) { kthread_stop(thread); pr_warn("%s: failed to set SCHED_DEADLINE\n", __func__); diff --git a/kernel/sched/cputime.c b/kernel/sched/cputime.c index 06bddaa73..0bc9d146f 100644 --- a/kernel/sched/cputime.c +++ b/kernel/sched/cputime.c @@ -130,7 +130,7 @@ void account_user_time(struct task_struct *p, u64 cputime) p->utime += cputime; account_group_user_time(p, cputime); - index = (task_nice(p) > 0) ? CPUTIME_NICE : CPUTIME_USER; + index = task_running_nice(p) ? CPUTIME_NICE : CPUTIME_USER; /* Add user time to cpustat. */ task_group_account_field(p, index, cputime); @@ -154,7 +154,7 @@ void account_guest_time(struct task_struct *p, u64 cputime) p->gtime += cputime; /* Add guest time to cpustat. */ - if (task_nice(p) > 0) { + if (task_running_nice(p)) { task_group_account_field(p, CPUTIME_NICE, cputime); cpustat[CPUTIME_GUEST_NICE] += cputime; } else { @@ -305,7 +305,7 @@ static inline u64 account_other_time(u64 max) #ifdef CONFIG_64BIT static inline u64 read_sum_exec_runtime(struct task_struct *t) { - return t->se.sum_exec_runtime; + return tsk_seruntime(t); } #else /* !CONFIG_64BIT: */ static u64 read_sum_exec_runtime(struct task_struct *t) @@ -315,7 +315,7 @@ static u64 read_sum_exec_runtime(struct task_struct *t) struct rq *rq; rq = task_rq_lock(t, &rf); - ns = t->se.sum_exec_runtime; + ns = tsk_seruntime(t); task_rq_unlock(rq, t, &rf); return ns; @@ -811,7 +811,7 @@ void cputime_adjust(struct task_cputime *curr, struct prev_cputime *prev, void task_cputime_adjusted(struct task_struct *p, u64 *ut, u64 *st) { struct task_cputime cputime = { - .sum_exec_runtime = p->se.sum_exec_runtime, + .sum_exec_runtime = tsk_seruntime(p), }; if (task_cputime(p, &cputime.utime, &cputime.stime)) diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c index 40584b27e..8cf443fcf 100644 --- a/kernel/sched/debug.c +++ b/kernel/sched/debug.c @@ -11,6 +11,7 @@ #include #include "sched.h" +#ifndef CONFIG_SCHED_ALT /* * This allows printing both to /sys/kernel/debug/sched/debug and * to the console @@ -251,6 +252,7 @@ static const struct file_operations sched_cache_enable_fops = { .release = single_release, }; #endif +#endif /* !CONFIG_SCHED_ALT */ #ifdef CONFIG_PREEMPT_DYNAMIC @@ -318,6 +320,7 @@ static const struct file_operations sched_dynamic_fops = { #endif /* CONFIG_PREEMPT_DYNAMIC */ +#ifndef CONFIG_SCHED_ALT __read_mostly bool sched_debug_verbose; static struct dentry *sd_dentry; @@ -611,7 +614,11 @@ static const struct file_operations fair_server_period_fops = { .llseek = seq_lseek, .release = single_release, }; +#endif /* !CONFIG_SCHED_ALT */ +static struct dentry *debugfs_sched; + +#ifndef CONFIG_SCHED_ALT static void debugfs_fair_server_init(void) { struct dentry *d_fair; @@ -632,6 +639,7 @@ static void debugfs_fair_server_init(void) debugfs_create_file("period", 0644, d_cpu, (void *) cpu, &fair_server_period_fops); } } +#endif /* !CONFIG_SCHED_ALT */ static __init int sched_init_debug(void) { @@ -639,14 +647,17 @@ static __init int sched_init_debug(void) debugfs_sched = debugfs_create_dir("sched", NULL); +#ifndef CONFIG_SCHED_ALT debugfs_create_file("features", 0644, debugfs_sched, NULL, &sched_feat_fops); debugfs_create_file_unsafe("verbose", 0644, debugfs_sched, &sched_debug_verbose, &sched_verbose_fops); +#endif /* !CONFIG_SCHED_ALT */ #ifdef CONFIG_PREEMPT_DYNAMIC debugfs_create_file("preempt", 0644, debugfs_sched, NULL, &sched_dynamic_fops); #endif debugfs_create_u32("base_slice_ns", 0644, debugfs_sched, &sysctl_sched_base_slice); +#ifndef CONFIG_SCHED_ALT debugfs_create_u32("latency_warn_ms", 0644, debugfs_sched, &sysctl_resched_latency_warn_ms); debugfs_create_u32("latency_warn_once", 0644, debugfs_sched, &sysctl_resched_latency_warn_once); @@ -685,16 +696,21 @@ static __init int sched_init_debug(void) #endif debugfs_create_file("debug", 0444, debugfs_sched, NULL, &sched_debug_fops); +#endif /* !CONFIG_SCHED_ALT */ +#ifndef CONFIG_SCHED_ALT debugfs_fair_server_init(); #ifdef CONFIG_SCHED_CLASS_EXT debugfs_ext_server_init(); #endif +#endif /* !CONFIG_SCHED_ALT */ return 0; } late_initcall(sched_init_debug); +#ifndef CONFIG_SCHED_ALT + static cpumask_var_t sd_sysctl_cpus; static int sd_flags_show(struct seq_file *m, void *v) @@ -1440,6 +1456,11 @@ void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns, sched_show_numa(p, m); } +#else +void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns, + struct seq_file *m) +{ } +#endif /* !CONFIG_SCHED_ALT */ void proc_sched_set_task(struct task_struct *p) { diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c index 052435f4d..2c436e70a 100644 --- a/kernel/sched/idle.c +++ b/kernel/sched/idle.c @@ -454,6 +454,7 @@ void cpu_startup_entry(enum cpuhp_state state) do_idle(); } +#ifndef CONFIG_SCHED_ALT /* * idle-task scheduling class. */ @@ -591,3 +592,4 @@ DEFINE_SCHED_CLASS(idle) = { .switching_to = switching_to_idle, .update_curr = update_curr_idle, }; +#endif diff --git a/kernel/sched/pds.h b/kernel/sched/pds.h new file mode 100644 index 000000000..fe3099071 --- /dev/null +++ b/kernel/sched/pds.h @@ -0,0 +1,139 @@ +#ifndef _KERNEL_SCHED_PDS_H +#define _KERNEL_SCHED_PDS_H + +#define ALT_SCHED_NAME "PDS" + +static const u64 RT_MASK = ((1ULL << MIN_SCHED_NORMAL_PRIO) - 1); + +#define SCHED_NORMAL_PRIO_NUM (32) +#define SCHED_EDGE_DELTA (SCHED_NORMAL_PRIO_NUM - NICE_WIDTH / 2) + +/* PDS assume SCHED_NORMAL_PRIO_NUM is power of 2 */ +#define SCHED_NORMAL_PRIO_MOD(x) ((x) & (SCHED_NORMAL_PRIO_NUM - 1)) + +/* default time slice 4ms -> shift 22, 2 time slice slots -> shift 23 */ +static __read_mostly int sched_timeslice_shift = 23; + +/* + * Common interfaces + */ +static inline int +task_sched_prio_normal(const struct task_struct *p, const struct rq *rq) +{ + u64 sched_dl = max(p->deadline, rq->time_edge); + +#ifdef ALT_SCHED_DEBUG + if (WARN_ONCE(sched_dl - rq->time_edge > NORMAL_PRIO_NUM - 1, + "pds: task_sched_prio_normal() delta %lld\n", sched_dl - rq->time_edge)) + return SCHED_NORMAL_PRIO_NUM - 1; +#endif + + return sched_dl - rq->time_edge; +} + +static inline int task_sched_prio(const struct task_struct *p) +{ + return (p->prio < MIN_NORMAL_PRIO) ? (p->prio >> 2) : + MIN_SCHED_NORMAL_PRIO + task_sched_prio_normal(p, task_rq(p)); +} + +#define TASK_SCHED_PRIO_IDX(p, rq, idx, prio) \ + if (p->prio < MIN_NORMAL_PRIO) { \ + prio = p->prio >> 2; \ + idx = prio; \ + } else { \ + u64 sched_dl = max(p->deadline, rq->time_edge); \ + prio = MIN_SCHED_NORMAL_PRIO + sched_dl - rq->time_edge; \ + idx = MIN_SCHED_NORMAL_PRIO + SCHED_NORMAL_PRIO_MOD(sched_dl); \ + } + +static inline int sched_prio2idx(int sched_prio, struct rq *rq) +{ + return (IDLE_TASK_SCHED_PRIO == sched_prio || sched_prio < MIN_SCHED_NORMAL_PRIO) ? + sched_prio : + MIN_SCHED_NORMAL_PRIO + SCHED_NORMAL_PRIO_MOD(sched_prio + rq->time_edge); +} + +static inline int sched_idx2prio(int sched_idx, struct rq *rq) +{ + return (sched_idx < MIN_SCHED_NORMAL_PRIO) ? + sched_idx : + MIN_SCHED_NORMAL_PRIO + SCHED_NORMAL_PRIO_MOD(sched_idx - rq->time_edge); +} + +static inline int sched_rq_prio_idx(struct rq *rq) +{ + return rq->prio_idx; +} + +static inline int task_running_nice(struct task_struct *p) +{ + return (p->prio > DEFAULT_PRIO); +} + +static inline void sched_update_rq_clock(struct rq *rq) +{ + struct list_head head; + u64 old = rq->time_edge; + u64 now = rq->clock >> sched_timeslice_shift; + u64 prio, delta; + DECLARE_BITMAP(normal, SCHED_QUEUE_BITS); + + if (now == old) + return; + + rq->time_edge = now; + delta = min_t(u64, SCHED_NORMAL_PRIO_NUM, now - old); + INIT_LIST_HEAD(&head); + + prio = MIN_SCHED_NORMAL_PRIO; + for_each_set_bit_from(prio, rq->queue.bitmap, MIN_SCHED_NORMAL_PRIO + delta) + list_splice_tail_init(rq->queue.heads + MIN_SCHED_NORMAL_PRIO + + SCHED_NORMAL_PRIO_MOD(prio + old), &head); + + bitmap_shift_right(normal, rq->queue.bitmap, delta, SCHED_QUEUE_BITS); + if (!list_empty(&head)) { + u64 idx = MIN_SCHED_NORMAL_PRIO + SCHED_NORMAL_PRIO_MOD(now); + + __list_splice(&head, rq->queue.heads + idx, rq->queue.heads[idx].next); + set_bit(MIN_SCHED_NORMAL_PRIO, normal); + } + bitmap_replace(rq->queue.bitmap, normal, rq->queue.bitmap, + (const unsigned long *)&RT_MASK, SCHED_QUEUE_BITS); + + if (rq->prio < MIN_SCHED_NORMAL_PRIO || IDLE_TASK_SCHED_PRIO == rq->prio) + return; + + rq->prio = max_t(u64, MIN_SCHED_NORMAL_PRIO, rq->prio - delta); + rq->prio_idx = sched_prio2idx(rq->prio, rq); +} + +static inline void sched_task_renew(struct task_struct *p, const struct rq *rq) +{ + if (p->prio >= MIN_NORMAL_PRIO) + p->deadline = rq->time_edge + SCHED_EDGE_DELTA + + (p->static_prio - (MAX_PRIO - NICE_WIDTH)) / 2; +} + +static inline void sched_task_sanity_check(struct task_struct *p, struct rq *rq) +{ + u64 max_dl = rq->time_edge + SCHED_EDGE_DELTA + NICE_WIDTH / 2 - 1; + if (unlikely(p->deadline > max_dl)) + p->deadline = max_dl; +} + +static inline void sched_task_fork(struct task_struct *p, struct rq *rq) +{ + sched_task_renew(p, rq); +} + +static inline void do_sched_yield_type_1(struct task_struct *p, struct rq *rq) +{ + p->time_slice = sysctl_sched_base_slice; + sched_task_renew(p, rq); +} + +static inline void sched_task_ttwu(struct task_struct *p) {} +static inline void sched_task_deactivate(struct task_struct *p, struct rq *rq) {} + +#endif /* _KERNEL_SCHED_PDS_H */ diff --git a/kernel/sched/pelt.c b/kernel/sched/pelt.c index 897790889..376f039d2 100644 --- a/kernel/sched/pelt.c +++ b/kernel/sched/pelt.c @@ -267,6 +267,7 @@ ___update_load_avg(struct sched_avg *sa, unsigned long load) WRITE_ONCE(sa->util_avg, sa->util_sum / divider); } +#ifndef CONFIG_SCHED_ALT /* * sched_entity: * @@ -384,8 +385,9 @@ int update_dl_rq_load_avg(u64 now, struct rq *rq, int running) return 0; } +#endif -#ifdef CONFIG_SCHED_HW_PRESSURE +#if defined(CONFIG_SCHED_HW_PRESSURE) && !defined(CONFIG_SCHED_ALT) /* * hardware: * @@ -469,6 +471,7 @@ int update_irq_load_avg(struct rq *rq, u64 running) } #endif /* CONFIG_HAVE_SCHED_AVG_IRQ */ +#ifndef CONFIG_SCHED_ALT /* * Load avg and utiliztion metrics need to be updated periodically and before * consumption. This function updates the metrics for all subsystems except for @@ -488,3 +491,4 @@ bool update_other_load_avgs(struct rq *rq) update_hw_load_avg(rq_clock_task(rq), rq, hw_pressure) | update_irq_load_avg(rq, 0); } +#endif /* !CONFIG_SCHED_ALT */ diff --git a/kernel/sched/pelt.h b/kernel/sched/pelt.h index f921302dc..2fade7dab 100644 --- a/kernel/sched/pelt.h +++ b/kernel/sched/pelt.h @@ -5,14 +5,16 @@ #include "sched-pelt.h" +#ifndef CONFIG_SCHED_ALT int __update_load_avg_blocked_se(u64 now, struct sched_entity *se); int __update_load_avg_se(u64 now, struct cfs_rq *cfs_rq, struct sched_entity *se); int __update_load_avg_cfs_rq(u64 now, struct cfs_rq *cfs_rq); int update_rt_rq_load_avg(u64 now, struct rq *rq, int running); int update_dl_rq_load_avg(u64 now, struct rq *rq, int running); bool update_other_load_avgs(struct rq *rq); +#endif -#ifdef CONFIG_SCHED_HW_PRESSURE +#if defined(CONFIG_SCHED_HW_PRESSURE) && !defined(CONFIG_SCHED_ALT) int update_hw_load_avg(u64 now, struct rq *rq, u64 capacity); static inline u64 hw_load_avg(struct rq *rq) @@ -49,6 +51,7 @@ static inline u32 get_pelt_divider(struct sched_avg *avg) return PELT_MIN_DIVIDER + avg->period_contrib; } +#ifndef CONFIG_SCHED_ALT static inline void cfs_se_util_change(struct sched_avg *avg) { unsigned int enqueued; @@ -185,5 +188,6 @@ static inline u64 cfs_rq_clock_pelt(struct cfs_rq *cfs_rq) return rq_clock_pelt(rq_of(cfs_rq)); } #endif /* !CONFIG_CFS_BANDWIDTH */ +#endif /* CONFIG_SCHED_ALT */ #endif /* _KERNEL_SCHED_PELT_H */ diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index a4a1eb57a..aa0aaa9fd 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -5,6 +5,10 @@ #ifndef _KERNEL_SCHED_SCHED_H #define _KERNEL_SCHED_SCHED_H +#ifdef CONFIG_SCHED_ALT +#include "alt_sched.h" +#else + #include #include #include @@ -4213,4 +4217,9 @@ DEFINE_CLASS_IS_UNCONDITIONAL(sched_change) #include "ext/ext.h" +static inline int task_running_nice(struct task_struct *p) +{ + return (task_nice(p) > 0); +} +#endif /* !CONFIG_SCHED_ALT */ #endif /* _KERNEL_SCHED_SCHED_H */ diff --git a/kernel/sched/stats.c b/kernel/sched/stats.c index d1c9429a4..cc3764073 100644 --- a/kernel/sched/stats.c +++ b/kernel/sched/stats.c @@ -115,8 +115,10 @@ static int show_schedstat(struct seq_file *seq, void *v) seq_printf(seq, "timestamp %lu\n", jiffies); } else { struct rq *rq; +#ifndef CONFIG_SCHED_ALT struct sched_domain *sd; int dcount = 0; +#endif cpu = (unsigned long)(v - 2); rq = cpu_rq(cpu); @@ -131,6 +133,7 @@ static int show_schedstat(struct seq_file *seq, void *v) seq_printf(seq, "\n"); +#ifndef CONFIG_SCHED_ALT /* domain-specific stats */ rcu_read_lock(); for_each_domain(cpu, sd) { @@ -161,6 +164,7 @@ static int show_schedstat(struct seq_file *seq, void *v) sd->ttwu_move_balance); } rcu_read_unlock(); +#endif } return 0; } diff --git a/kernel/sched/stats.h b/kernel/sched/stats.h index ebe0a7765..90a7947c6 100644 --- a/kernel/sched/stats.h +++ b/kernel/sched/stats.h @@ -89,6 +89,7 @@ static inline void rq_sched_info_depart (struct rq *rq, unsigned long long delt #endif /* CONFIG_SCHEDSTATS */ +#ifndef CONFIG_SCHED_ALT static inline struct sched_statistics * __schedstats_from_se(struct sched_entity *se) { @@ -98,6 +99,7 @@ __schedstats_from_se(struct sched_entity *se) #endif return &task_of(se)->stats; } +#endif /* CONFIG_SCHED_ALT */ #ifdef CONFIG_PSI void psi_task_change(struct task_struct *task, int clear, int set); diff --git a/kernel/sched/syscalls.c b/kernel/sched/syscalls.c index b215b0ead..1e736f2b0 100644 --- a/kernel/sched/syscalls.c +++ b/kernel/sched/syscalls.c @@ -16,6 +16,14 @@ #include "sched.h" #include "autogroup.h" +#ifdef CONFIG_SCHED_ALT +#include "alt_core.h" + +static inline int __normal_prio(int policy, int rt_prio, int static_prio) +{ + return rt_policy(policy) ? (MAX_RT_PRIO - 1 - rt_prio) : static_prio; +} +#else /* !CONFIG_SCHED_ALT */ static inline int __normal_prio(int policy, int rt_prio, int nice) { int prio; @@ -29,6 +37,7 @@ static inline int __normal_prio(int policy, int rt_prio, int nice) return prio; } +#endif /* !CONFIG_SCHED_ALT */ /* * Calculate the expected normal priority: i.e. priority @@ -39,7 +48,11 @@ static inline int __normal_prio(int policy, int rt_prio, int nice) */ static inline int normal_prio(struct task_struct *p) { +#ifdef CONFIG_SCHED_ALT + return __normal_prio(p->policy, p->rt_priority, p->static_prio); +#else /* !CONFIG_SCHED_ALT */ return __normal_prio(p->policy, p->rt_priority, PRIO_TO_NICE(p->static_prio)); +#endif /* !CONFIG_SCHED_ALT */ } /* @@ -64,7 +77,9 @@ static int effective_prio(struct task_struct *p) void set_user_nice(struct task_struct *p, long nice) { +#ifndef CONFIG_SCHED_ALT int old_prio; +#endif if (task_nice(p) == nice || nice < MIN_NICE || nice > MAX_NICE) return; @@ -72,7 +87,11 @@ void set_user_nice(struct task_struct *p, long nice) * We have to be careful, if called from sys_setpriority(), * the task might be in the middle of scheduling on another CPU. */ +#ifdef CONFIG_SCHED_ALT + guard(task_access_lock)(p); +#else guard(task_rq_lock)(p); +#endif /* * The RT priorities are set via sched_setscheduler(), but we still @@ -87,8 +106,10 @@ void set_user_nice(struct task_struct *p, long nice) scoped_guard (sched_change, p, DEQUEUE_SAVE) { p->static_prio = NICE_TO_PRIO(nice); +#ifndef CONFIG_SCHED_ALT set_load_weight(p, true); old_prio = p->prio; +#endif p->prio = effective_prio(p); } } @@ -169,7 +190,19 @@ SYSCALL_DEFINE1(nice, int, increment) */ int task_prio(const struct task_struct *p) { +#ifdef CONFIG_SCHED_ALT +/* + * sched policy return value kernel prio user prio/nice + * + * (BMQ)normal, batch, idle[0 ... 53] [100 ... 139] 0/[-20 ... 19]/[-7 ... 7] + * (PDS)normal, batch, idle[0 ... 39] 100 0/[-20 ... 19] + * fifo, rr [-1 ... -100] [99 ... 0] [0 ... 99] + */ + return (p->prio < MAX_RT_PRIO) ? p->prio - MAX_RT_PRIO : + task_sched_prio_normal(p, task_rq(p)); +#else return p->prio - MAX_RT_PRIO; +#endif /* !CONFIG_SCHED_ALT */ } /** @@ -248,11 +281,16 @@ static void __setscheduler_params(struct task_struct *p, p->policy = policy; +#ifndef CONFIG_SCHED_ALT if (dl_policy(policy)) __setparam_dl(p, attr); else if (fair_policy(policy)) __setparam_fair(p, attr); +#else /* !CONFIG_SCHED_ALT */ + p->static_prio = NICE_TO_PRIO(attr->sched_nice); +#endif /* CONFIG_SCHED_ALT */ +#ifndef CONFIG_SCHED_ALT /* rt-policy tasks do not have a timerslack */ if (rt_or_dl_task_policy(p)) { p->timer_slack_ns = 0; @@ -260,6 +298,7 @@ static void __setscheduler_params(struct task_struct *p, /* when switching back to non-rt policy, restore timerslack */ p->timer_slack_ns = p->default_timer_slack_ns; } +#endif /* !CONFIG_SCHED_ALT */ /* * __sched_setscheduler() ensures attr->sched_priority == 0 when @@ -268,7 +307,9 @@ static void __setscheduler_params(struct task_struct *p, */ p->rt_priority = attr->sched_priority; p->normal_prio = normal_prio(p); +#ifndef CONFIG_SCHED_ALT set_load_weight(p, true); +#endif /* !CONFIG_SCHED_ALT */ } /* @@ -284,7 +325,7 @@ static bool check_same_owner(struct task_struct *p) uid_eq(cred->euid, pcred->uid)); } -#ifdef CONFIG_RT_MUTEXES +#if defined(CONFIG_RT_MUTEXES) && !defined(CONFIG_SCHED_ALT) static inline void __setscheduler_dl_pi(int newprio, int policy, struct task_struct *p, struct sched_change_ctx *scope) @@ -313,6 +354,7 @@ static inline void __setscheduler_dl_pi(int newprio, int policy, } #endif /* !CONFIG_RT_MUTEXES */ +#ifndef CONFIG_SCHED_ALT #ifdef CONFIG_UCLAMP_TASK static int uclamp_validate(struct task_struct *p, @@ -426,6 +468,7 @@ static inline int uclamp_validate(struct task_struct *p, static void __setscheduler_uclamp(struct task_struct *p, const struct sched_attr *attr) { } #endif /* !CONFIG_UCLAMP_TASK */ +#endif /* !CONFIG_SCHED_ALT */ /* * Allow unprivileged RT tasks to decrease priority. @@ -436,11 +479,13 @@ static int user_check_sched_setscheduler(struct task_struct *p, const struct sched_attr *attr, int policy, int reset_on_fork) { +#ifndef CONFIG_SCHED_ALT if (fair_policy(policy)) { if (attr->sched_nice < task_nice(p) && !is_nice_reduction(p, attr->sched_nice)) goto req_priv; } +#endif /* !CONFIG_SCHED_ALT */ if (rt_policy(policy)) { unsigned long rlim_rtprio = task_rlimit(p, RLIMIT_RTPRIO); @@ -455,6 +500,7 @@ static int user_check_sched_setscheduler(struct task_struct *p, goto req_priv; } +#ifndef CONFIG_SCHED_ALT /* * Can't set/change SCHED_DEADLINE policy at all for now * (safest behavior); in the future we would like to allow @@ -472,6 +518,7 @@ static int user_check_sched_setscheduler(struct task_struct *p, if (!is_nice_reduction(p, task_nice(p))) goto req_priv; } +#endif /* !CONFIG_SCHED_ALT */ /* Can't change other user's priorities: */ if (!check_same_owner(p)) @@ -491,12 +538,22 @@ static int user_check_sched_setscheduler(struct task_struct *p, } int __sched_setscheduler(struct task_struct *p, - const struct sched_attr *attr, - bool user, bool pi) + const struct sched_attr *attr, + bool user, bool pi) { +#ifdef CONFIG_SCHED_ALT + const struct sched_attr dl_squash_attr = { + .size = sizeof(struct sched_attr), + .sched_policy = SCHED_FIFO, + .sched_nice = 0, + .sched_priority = 99, + }; +#endif /* CONFIG_SCHED_ALT */ int oldpolicy = -1, policy = attr->sched_policy; int retval, oldprio, newprio; +#ifndef CONFIG_SCHED_ALT const struct sched_class *prev_class, *next_class; +#endif /* !CONFIG_SCHED_ALT */ struct balance_callback *head; struct rq_flags rf; int reset_on_fork; @@ -506,6 +563,15 @@ int __sched_setscheduler(struct task_struct *p, /* The pi code expects interrupts enabled */ BUG_ON(pi && in_interrupt()); +#ifdef CONFIG_SCHED_ALT + /* + * Alt schedule FW supports SCHED_DEADLINE by squash it as prio 0 SCHED_FIFO + */ + if (unlikely(SCHED_DEADLINE == policy)) { + attr = &dl_squash_attr; + policy = attr->sched_policy; + } +#endif /* CONFIG_SCHED_ALT */ recheck: /* Double check policy once rq lock held: */ if (policy < 0) { @@ -528,8 +594,12 @@ int __sched_setscheduler(struct task_struct *p, */ if (attr->sched_priority > MAX_RT_PRIO-1) return -EINVAL; +#ifdef CONFIG_SCHED_ALT + if ((rt_policy(policy) != (attr->sched_priority != 0))) +#else if ((dl_policy(policy) && !__checkparam_dl(attr)) || (rt_policy(policy) != (attr->sched_priority != 0))) +#endif /* !CONFIG_SCHED_ALT */ return -EINVAL; if (user) { @@ -545,6 +615,7 @@ int __sched_setscheduler(struct task_struct *p, return retval; } +#ifndef CONFIG_SCHED_ALT /* Update task specific "requested" clamps */ if (attr->sched_flags & SCHED_FLAG_UTIL_CLAMP) { retval = uclamp_validate(p, attr); @@ -560,6 +631,7 @@ int __sched_setscheduler(struct task_struct *p, cpuset_locked = true; cpuset_lock(); } +#endif /* !CONFIG_SCHED_ALT */ /* * Make sure no PI-waiters arrive (or leave) while we are @@ -568,8 +640,12 @@ int __sched_setscheduler(struct task_struct *p, * To be able to change p->policy safely, the appropriate * runqueue lock must be held. */ +#ifdef CONFIG_SCHED_ALT + rq = task_access_lock(p, &rf); +#else rq = task_rq_lock(p, &rf); update_rq_clock(rq); +#endif /* !CONFIG_SCHED_ALT */ /* * Changing the policy of the stop threads its a very bad idea: @@ -579,23 +655,31 @@ int __sched_setscheduler(struct task_struct *p, goto unlock; } +#ifndef CONFIG_SCHED_ALT retval = scx_check_setscheduler(p, policy); if (retval) goto unlock; +#endif /* !CONFIG_SCHED_ALT */ /* * If not changing anything there's no need to proceed further, * but store a possible modification of reset_on_fork. */ if (unlikely(policy == p->policy)) { +#ifdef CONFIG_SCHED_ALT + if (!rt_policy(policy) && NICE_TO_PRIO(attr->sched_nice) != p->static_prio) +#else if (fair_policy(policy) && (attr->sched_nice != task_nice(p) || (attr->sched_runtime != p->se.slice))) +#endif /* !CONFIG_SCHED_ALT */ goto change; if (rt_policy(policy) && attr->sched_priority != p->rt_priority) goto change; +#ifndef CONFIG_SCHED_ALT if (dl_policy(policy) && dl_param_changed(p, attr)) goto change; +#endif /* !CONFIG_SCHED_ALT */ if (attr->sched_flags & SCHED_FLAG_UTIL_CLAMP) goto change; @@ -605,6 +689,7 @@ int __sched_setscheduler(struct task_struct *p, } change: +#ifndef CONFIG_SCHED_ALT if (user) { #ifdef CONFIG_RT_GROUP_SCHED /* @@ -635,16 +720,22 @@ int __sched_setscheduler(struct task_struct *p, } } } +#endif /* !CONFIG_SCHED_ALT */ /* Re-check policy now with rq lock held: */ if (unlikely(oldpolicy != -1 && oldpolicy != p->policy)) { policy = oldpolicy = -1; +#ifdef CONFIG_SCHED_ALT + task_access_unlock(p, &rf); +#else task_rq_unlock(rq, p, &rf); +#endif /* !CONFIG_SCHED_ALT */ if (cpuset_locked) cpuset_unlock(); goto recheck; } +#ifndef CONFIG_SCHED_ALT /* * If setscheduling to SCHED_DEADLINE (or changing the parameters * of a SCHED_DEADLINE task) we need to check if enough bandwidth @@ -654,11 +745,16 @@ int __sched_setscheduler(struct task_struct *p, retval = -EBUSY; goto unlock; } +#endif /* !CONFIG_SCHED_ALT */ p->sched_reset_on_fork = reset_on_fork; oldprio = p->prio; +#ifdef CONFIG_SCHED_ALT + newprio = __normal_prio(policy, attr->sched_priority, NICE_TO_PRIO(attr->sched_nice)); +#else newprio = __normal_prio(policy, attr->sched_priority, attr->sched_nice); +#endif /* !CONFIG_SCHED_ALT */ if (pi) { /* * Take priority boosted tasks into account. If the new @@ -672,21 +768,27 @@ int __sched_setscheduler(struct task_struct *p, queue_flags &= ~DEQUEUE_MOVE; } +#ifndef CONFIG_SCHED_ALT prev_class = p->sched_class; next_class = __setscheduler_class(policy, newprio); if (prev_class != next_class) queue_flags |= DEQUEUE_CLASS; +#endif /* !CONFIG_SCHED_ALT */ scoped_guard (sched_change, p, queue_flags) { if (!(attr->sched_flags & SCHED_FLAG_KEEP_PARAMS)) { __setscheduler_params(p, attr); +#ifndef CONFIG_SCHED_ALT p->sched_class = next_class; +#endif /* !CONFIG_SCHED_ALT */ p->prio = newprio; __setscheduler_dl_pi(newprio, policy, p, scope); } +#ifndef CONFIG_SCHED_ALT __setscheduler_uclamp(p, attr); +#endif /* !CONFIG_SCHED_ALT */ if (scope->queued) { /* @@ -701,7 +803,11 @@ int __sched_setscheduler(struct task_struct *p, /* Avoid rq from going away on us: */ preempt_disable(); head = splice_balance_callbacks(rq); +#ifdef CONFIG_SCHED_ALT + task_access_unlock(p, &rf); +#else task_rq_unlock(rq, p, &rf); +#endif /* !CONFIG_SCHED_ALT */ if (pi) { if (cpuset_locked) @@ -716,7 +822,11 @@ int __sched_setscheduler(struct task_struct *p, return 0; unlock: +#ifdef CONFIG_SCHED_ALT + task_access_unlock(p, &rf); +#else task_rq_unlock(rq, p, &rf); +#endif /* !CONFIG_SCHED_ALT */ if (cpuset_locked) cpuset_unlock(); return retval; @@ -731,8 +841,10 @@ static int _sched_setscheduler(struct task_struct *p, int policy, .sched_nice = PRIO_TO_NICE(p->static_prio), }; +#ifndef CONFIG_SCHED_ALT if (p->se.custom_slice) attr.sched_runtime = p->se.slice; +#endif /* !CONFIG_SCHED_ALT */ /* Fixup the legacy SCHED_RESET_ON_FORK hack. */ if ((policy != SETPARAM_POLICY) && (policy & SCHED_RESET_ON_FORK)) { @@ -913,13 +1025,18 @@ static int sched_copy_attr(struct sched_attr __user *uattr, struct sched_attr *a static void get_params(struct task_struct *p, struct sched_attr *attr, unsigned int flags) { +#ifndef CONFIG_SCHED_ALT if (task_has_dl_policy(p)) { __getparam_dl(p, attr, flags); - } else if (task_has_rt_policy(p)) { + } else +#endif + if (task_has_rt_policy(p)) { attr->sched_priority = p->rt_priority; } else { attr->sched_nice = task_nice(p); +#ifndef CONFIG_SCHED_ALT attr->sched_runtime = p->se.slice; +#endif } } @@ -1106,6 +1223,7 @@ SYSCALL_DEFINE4(sched_getattr, pid_t, pid, struct sched_attr __user *, uattr, int dl_task_check_affinity(struct task_struct *p, const struct cpumask *mask) { +#ifndef CONFIG_SCHED_ALT /* * If the task isn't a deadline task or admission control is * disabled then we don't care about affinity changes. @@ -1129,6 +1247,7 @@ int dl_task_check_affinity(struct task_struct *p, const struct cpumask *mask) guard(rcu)(); if (!cpumask_subset(task_rq(p)->rd->span, mask)) return -EBUSY; +#endif return 0; } @@ -1152,9 +1271,11 @@ int __sched_setaffinity(struct task_struct *p, struct affinity_context *ctx) ctx->new_mask = new_mask; ctx->flags |= SCA_CHECK; +#ifndef CONFIG_SCHED_ALT retval = dl_task_check_affinity(p, new_mask); if (retval) goto out_free_new_mask; +#endif retval = __set_cpus_allowed_ptr(p, ctx); if (retval) @@ -1334,13 +1455,34 @@ SYSCALL_DEFINE3(sched_getaffinity, pid_t, pid, unsigned int, len, static void do_sched_yield(void) { - struct rq_flags rf; struct rq *rq; + struct rq_flags rf; + +#ifdef CONFIG_SCHED_ALT + struct task_struct *p; + + if (!sched_yield_type) + return; rq = this_rq_lock_irq(&rf); + schedstat_inc(rq->yld_count); + + p = current; + if (rt_task(p)) { + if (task_on_rq_queued(p)) + requeue_task(p, rq); + } else if (rq->nr_running > 1) { + do_sched_yield_type_1(p, rq); + if (task_on_rq_queued(p)) + requeue_task(p, rq); + } +#else /* !CONFIG_SCHED_ALT */ + rq = this_rq_lock_irq(&rf); + schedstat_inc(rq->yld_count); rq->donor->sched_class->yield_task(rq); +#endif /* !CONFIG_SCHED_ALT */ preempt_disable(); rq_unlock_irq(rq, &rf); @@ -1409,6 +1551,9 @@ EXPORT_SYMBOL(yield); */ int __sched yield_to(struct task_struct *p, bool preempt) { +#ifdef CONFIG_SCHED_ALT + return 0; +#else /* !CONFIG_SCHED_ALT */ struct task_struct *curr; struct rq *rq, *p_rq; int yielded = 0; @@ -1455,6 +1600,7 @@ int __sched yield_to(struct task_struct *p, bool preempt) schedule(); return yielded; +#endif /* !CONFIG_SCHED_ALT */ } EXPORT_SYMBOL_GPL(yield_to); @@ -1475,7 +1621,9 @@ SYSCALL_DEFINE1(sched_get_priority_max, int, policy) case SCHED_RR: ret = MAX_RT_PRIO-1; break; +#ifndef CONFIG_SCHED_ALT case SCHED_DEADLINE: +#endif case SCHED_NORMAL: case SCHED_BATCH: case SCHED_IDLE: @@ -1503,7 +1651,9 @@ SYSCALL_DEFINE1(sched_get_priority_min, int, policy) case SCHED_RR: ret = 1; break; +#ifndef CONFIG_SCHED_ALT case SCHED_DEADLINE: +#endif case SCHED_NORMAL: case SCHED_BATCH: case SCHED_IDLE: @@ -1515,7 +1665,9 @@ SYSCALL_DEFINE1(sched_get_priority_min, int, policy) static int sched_rr_get_interval(pid_t pid, struct timespec64 *t) { +#ifndef CONFIG_SCHED_ALT unsigned int time_slice = 0; +#endif int retval; if (pid < 0) @@ -1530,6 +1682,7 @@ static int sched_rr_get_interval(pid_t pid, struct timespec64 *t) if (retval) return retval; +#ifndef CONFIG_SCHED_ALT scoped_guard (task_rq_lock, p) { struct rq *rq = scope.rq; if (p->sched_class->get_rr_interval) @@ -1538,6 +1691,13 @@ static int sched_rr_get_interval(pid_t pid, struct timespec64 *t) } jiffies_to_timespec64(time_slice, t); +#else + } + + alt_sched_debug(); + + *t = ns_to_timespec64(sysctl_sched_base_slice); +#endif /* !CONFIG_SCHED_ALT */ return 0; } diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c index 622e2e019..525d630eb 100644 --- a/kernel/sched/topology.c +++ b/kernel/sched/topology.c @@ -3,6 +3,7 @@ * Scheduler topology setup/handling methods */ +#ifndef CONFIG_SCHED_ALT #include #include #include @@ -1786,8 +1787,10 @@ static void asym_cpu_capacity_scan(void) */ static int default_relax_domain_level = -1; +#endif /* CONFIG_SCHED_ALT */ int sched_domain_level_max; +#ifndef CONFIG_SCHED_ALT static int __init setup_relax_domain_level(char *str) { if (kstrtoint(str, 0, &default_relax_domain_level)) @@ -2027,6 +2030,7 @@ sd_init(struct sched_domain_topology_level *tl, return sd; } +#endif /* CONFIG_SCHED_ALT */ #ifdef CONFIG_SCHED_SMT int cpu_smt_flags(void) @@ -2118,6 +2122,7 @@ void __init set_sched_topology(struct sched_domain_topology_level *tl) sched_domain_topology_saved = NULL; } +#ifndef CONFIG_SCHED_ALT #ifdef CONFIG_NUMA static int cpu_numa_flags(void) { @@ -3502,3 +3507,31 @@ void partition_sched_domains(int ndoms_new, cpumask_var_t doms_new[], partition_sched_domains_locked(ndoms_new, doms_new, dattr_new); sched_domains_mutex_unlock(); } +#else /* CONFIG_SCHED_ALT */ +DEFINE_STATIC_KEY_FALSE(sched_asym_cpucapacity); + +void partition_sched_domains(int ndoms_new, cpumask_var_t doms_new[], + struct sched_domain_attr *dattr_new) +{} + +#ifdef CONFIG_NUMA +int sched_numa_find_closest(const struct cpumask *cpus, int cpu) +{ + return best_mask_cpu(cpu, cpus); +} + +int sched_numa_find_nth_cpu(const struct cpumask *cpus, int cpu, int node) +{ + return cpumask_nth(cpu, cpus); +} + +const struct cpumask *sched_numa_hop_mask(unsigned int node, unsigned int hops) +{ + return ERR_PTR(-EOPNOTSUPP); +} +EXPORT_SYMBOL_GPL(sched_numa_hop_mask); +#endif /* CONFIG_NUMA */ + +void sched_update_asym_prefer_cpu(int cpu, int old_prio, int new_prio) +{} +#endif diff --git a/kernel/sysctl.c b/kernel/sysctl.c index c9efb17cc..efe0de047 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -35,6 +35,10 @@ EXPORT_SYMBOL_GPL(sysctl_long_vals); static const int ngroups_max = NGROUPS_MAX; static const int cap_last_cap = CAP_LAST_CAP; +#ifdef CONFIG_SCHED_ALT +extern int sched_yield_type; +#endif + #ifdef CONFIG_PROC_SYSCTL /** @@ -1406,6 +1410,17 @@ static const struct ctl_table sysctl_subsys_table[] = { .proc_handler = proc_dointvec, }, #endif +#ifdef CONFIG_SCHED_ALT + { + .procname = "yield_type", + .data = &sched_yield_type, + .maxlen = sizeof (int), + .mode = 0644, + .proc_handler = &proc_dointvec_minmax, + .extra1 = SYSCTL_ZERO, + .extra2 = SYSCTL_TWO, + }, +#endif #ifdef CONFIG_SYSCTL_ARCH_UNALIGN_NO_WARN { .procname = "ignore-unaligned-usertrap", diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c index a7d3e8229..de863940e 100644 --- a/kernel/time/posix-cpu-timers.c +++ b/kernel/time/posix-cpu-timers.c @@ -223,7 +223,7 @@ static void task_sample_cputime(struct task_struct *p, u64 *samples) u64 stime, utime; task_cputime(p, &utime, &stime); - store_samples(samples, stime, utime, p->se.sum_exec_runtime); + store_samples(samples, stime, utime, tsk_seruntime(p)); } static void proc_sample_cputime_atomic(struct task_cputime_atomic *at, @@ -899,6 +899,7 @@ static void collect_posix_cputimers(struct posix_cputimers *pct, u64 *samples, } } +#ifndef CONFIG_SCHED_ALT static inline void check_dl_overrun(struct task_struct *tsk) { if (tsk->dl.dl_overrun) { @@ -906,6 +907,7 @@ static inline void check_dl_overrun(struct task_struct *tsk) send_signal_locked(SIGXCPU, SEND_SIG_PRIV, tsk, PIDTYPE_TGID); } } +#endif static bool check_rlimit(u64 time, u64 limit, int signo, bool rt, bool hard) { @@ -933,8 +935,10 @@ static void check_thread_timers(struct task_struct *tsk, u64 samples[CPUCLOCK_MAX]; unsigned long soft; +#ifndef CONFIG_SCHED_ALT if (dl_task(tsk)) check_dl_overrun(tsk); +#endif if (expiry_cache_is_inactive(pct)) return; @@ -948,7 +952,7 @@ static void check_thread_timers(struct task_struct *tsk, soft = task_rlimit(tsk, RLIMIT_RTTIME); if (soft != RLIM_INFINITY) { /* Task RT timeout is accounted in jiffies. RTTIME is usec */ - unsigned long rttime = tsk->rt.timeout * (USEC_PER_SEC / HZ); + unsigned long rttime = tsk_rttimeout(tsk) * (USEC_PER_SEC / HZ); unsigned long hard = task_rlimit_max(tsk, RLIMIT_RTTIME); /* At the hard limit, send SIGKILL. No further action. */ @@ -1179,8 +1183,10 @@ static inline bool fastpath_timer_check(struct task_struct *tsk) return true; } +#ifndef CONFIG_SCHED_ALT if (dl_task(tsk) && tsk->dl.dl_overrun) return true; +#endif return false; } diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c index 0e1265acd..b2111e675 100644 --- a/kernel/trace/trace_osnoise.c +++ b/kernel/trace/trace_osnoise.c @@ -1673,6 +1673,9 @@ static void osnoise_sleep(bool skip_period) */ static inline int osnoise_migration_pending(void) { +#ifdef CONFIG_SCHED_ALT + return 0; +#else if (!current->migration_pending) return 0; @@ -1694,6 +1697,7 @@ static inline int osnoise_migration_pending(void) mutex_unlock(&interface_lock); return 1; +#endif } /* diff --git a/kernel/trace/trace_selftest.c b/kernel/trace/trace_selftest.c index 929c84075..0cba8b036 100644 --- a/kernel/trace/trace_selftest.c +++ b/kernel/trace/trace_selftest.c @@ -1423,10 +1423,15 @@ static int trace_wakeup_test_thread(void *data) { /* Make this a -deadline thread */ static const struct sched_attr attr = { +#ifdef CONFIG_SCHED_ALT + /* No deadline on BMQ/PDS, use RR */ + .sched_policy = SCHED_RR, +#else .sched_policy = SCHED_DEADLINE, .sched_runtime = 100000ULL, .sched_deadline = 10000000ULL, .sched_period = 10000000ULL +#endif }; struct wakeup_test_data *x = data; diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 78068ae8f..46fe4a20b 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -1281,6 +1281,7 @@ static bool kick_pool(struct worker_pool *pool) p = worker->task; +#ifndef CONFIG_SCHED_ALT #ifdef CONFIG_SMP /* * Idle @worker is about to execute @work and waking up provides an @@ -1310,6 +1311,8 @@ static bool kick_pool(struct worker_pool *pool) } } #endif +#endif /* !CONFIG_SCHED_ALT */ + wake_up_process(p); return true; } @@ -1438,7 +1441,11 @@ void wq_worker_running(struct task_struct *task) * CPU intensive auto-detection cares about how long a work item hogged * CPU without sleeping. Reset the starting timestamp on wakeup. */ +#ifdef CONFIG_SCHED_ALT + worker->current_at = worker->task->sched_time; +#else worker->current_at = worker->task->se.sum_exec_runtime; +#endif WRITE_ONCE(worker->sleeping, 0); } @@ -1523,7 +1530,11 @@ void wq_worker_tick(struct task_struct *task) * We probably want to make this prettier in the future. */ if ((worker->flags & WORKER_NOT_RUNNING) || READ_ONCE(worker->sleeping) || +#ifdef CONFIG_SCHED_ALT + worker->task->sched_time - worker->current_at < +#else worker->task->se.sum_exec_runtime - worker->current_at < +#endif wq_cpu_intensive_thresh_us * NSEC_PER_USEC) return; @@ -3249,7 +3260,11 @@ __acquires(&pool->lock) worker->current_func = work->func; worker->current_pwq = pwq; if (worker->task) +#ifdef CONFIG_SCHED_ALT + worker->current_at = worker->task->sched_time; +#else worker->current_at = worker->task->se.sum_exec_runtime; +#endif worker->current_start = jiffies; work_data = *work_data_bits(work); worker->current_color = get_work_color(work_data); -- 2.55.0