summaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2026-10-07 08:01:02 -1000
committerTejun Heo <tj@kernel.org>2026-10-07 08:01:02 -1000
commit92b542db38bf7cb4de583b2ca37febc549db0ce4 (patch)
tree30dcfdb338f7fdba597e1ec9314c2006df86f525 /kernel
parentfee1265a725725dac0ff7459f912d69558798b36 (diff)
parentd1a4549a3645e55cc9ade09e360280590b3c9559 (diff)
downloadlinux-next-92b542db38bf7cb4de583b2ca37febc549db0ce4.tar.gz
linux-next-92b542db38bf7cb4de583b2ca37febc549db0ce4.zip
Merge branch 'for-7.3-fixes' into for-next
Diffstat (limited to 'kernel')
-rw-r--r--kernel/Kconfig.locks20
-rw-r--r--kernel/Kconfig.preempt5
-rw-r--r--kernel/audit.c32
-rw-r--r--kernel/audit_tree.c24
-rw-r--r--kernel/audit_watch.c2
-rw-r--r--kernel/auditfilter.c12
-rw-r--r--kernel/auditsc.c3
-rw-r--r--kernel/bpf/Makefile2
-rw-r--r--kernel/bpf/arena.c43
-rw-r--r--kernel/bpf/arraymap.c77
-rw-r--r--kernel/bpf/backtrack.c67
-rw-r--r--kernel/bpf/bloom_filter.c19
-rw-r--r--kernel/bpf/bpf_inode_storage.c9
-rw-r--r--kernel/bpf/bpf_iter.c19
-rw-r--r--kernel/bpf/bpf_lsm.c8
-rw-r--r--kernel/bpf/bpf_struct_ops.c68
-rw-r--r--kernel/bpf/btf.c512
-rw-r--r--kernel/bpf/cfg.c41
-rw-r--r--kernel/bpf/cgroup.c143
-rw-r--r--kernel/bpf/check_btf.c26
-rw-r--r--kernel/bpf/const_fold.c12
-rw-r--r--kernel/bpf/core.c189
-rw-r--r--kernel/bpf/cpumask.c6
-rw-r--r--kernel/bpf/crypto.c5
-rw-r--r--kernel/bpf/diagnostics.c2359
-rw-r--r--kernel/bpf/diagnostics.h111
-rw-r--r--kernel/bpf/disasm.c111
-rw-r--r--kernel/bpf/dispatcher.c7
-rw-r--r--kernel/bpf/fixups.c285
-rw-r--r--kernel/bpf/hashtab.c95
-rw-r--r--kernel/bpf/helpers.c172
-rw-r--r--kernel/bpf/liveness.c122
-rw-r--r--kernel/bpf/local_storage.c5
-rw-r--r--kernel/bpf/log.c13
-rw-r--r--kernel/bpf/memalloc.c103
-rw-r--r--kernel/bpf/mmap_unlock_work.h51
-rw-r--r--kernel/bpf/net_namespace.c17
-rw-r--r--kernel/bpf/offload.c2
-rw-r--r--kernel/bpf/percpu_freelist.c35
-rw-r--r--kernel/bpf/percpu_freelist.h1
-rw-r--r--kernel/bpf/queue_stack_maps.c9
-rw-r--r--kernel/bpf/ringbuf.c6
-rw-r--r--kernel/bpf/rqspinlock.c13
-rw-r--r--kernel/bpf/stackmap.c536
-rw-r--r--kernel/bpf/states.c41
-rw-r--r--kernel/bpf/syscall.c180
-rw-r--r--kernel/bpf/task_iter.c48
-rw-r--r--kernel/bpf/trampoline.c158
-rw-r--r--kernel/bpf/verifier.c4118
-rw-r--r--kernel/capability.c19
-rw-r--r--kernel/cgroup/cgroup.c131
-rw-r--r--kernel/cgroup/cpuset-internal.h12
-rw-r--r--kernel/cgroup/cpuset-v1.c13
-rw-r--r--kernel/cgroup/cpuset.c695
-rw-r--r--kernel/cgroup/dmem.c276
-rw-r--r--kernel/cgroup/pids.c5
-rw-r--r--kernel/cgroup/rdma.c71
-rw-r--r--kernel/crash_core.c89
-rw-r--r--kernel/crash_dump_dm_crypt.c15
-rw-r--r--kernel/delayacct.c2
-rw-r--r--kernel/dma/Kconfig22
-rw-r--r--kernel/dma/coherent.c20
-rw-r--r--kernel/dma/contiguous.c16
-rw-r--r--kernel/dma/direct.c335
-rw-r--r--kernel/dma/direct.h56
-rw-r--r--kernel/dma/map_benchmark.c3
-rw-r--r--kernel/dma/mapping.c36
-rw-r--r--kernel/dma/ops_helpers.c4
-rw-r--r--kernel/dma/pool.c237
-rw-r--r--kernel/dma/swiotlb.c449
-rw-r--r--kernel/entry/Makefile3
-rw-r--r--kernel/entry/syscall-common.c21
-rw-r--r--kernel/entry/syscall_user_dispatch.c38
-rw-r--r--kernel/events/core.c536
-rw-r--r--kernel/events/ring_buffer.c16
-rw-r--r--kernel/events/uprobes.c56
-rw-r--r--kernel/exit.c60
-rw-r--r--kernel/fork.c98
-rw-r--r--kernel/futex/core.c271
-rw-r--r--kernel/futex/pi.c137
-rw-r--r--kernel/futex/requeue.c18
-rw-r--r--kernel/irq/Kconfig12
-rw-r--r--kernel/irq/Makefile1
-rw-r--r--kernel/irq/irqdesc.c11
-rw-r--r--kernel/irq/irqdomain.c8
-rw-r--r--kernel/irq/manage.c9
-rw-r--r--kernel/irq/msi.c2
-rw-r--r--kernel/irq/refcount_interrupt_test.c109
-rw-r--r--kernel/jump_label.c5
-rw-r--r--kernel/kcmp.c2
-rw-r--r--kernel/kcov.c92
-rw-r--r--kernel/kcsan/core.c12
-rw-r--r--kernel/kexec_file.c27
-rw-r--r--kernel/kprobes.c44
-rw-r--r--kernel/kthread.c2
-rw-r--r--kernel/livepatch/core.c34
-rw-r--r--kernel/liveupdate/Makefile1
-rw-r--r--kernel/liveupdate/kexec_handover.c552
-rw-r--r--kernel/liveupdate/kexec_handover_debug.c25
-rw-r--r--kernel/liveupdate/kexec_handover_internal.h9
-rw-r--r--kernel/liveupdate/luo_flb.c20
-rw-r--r--kernel/liveupdate/luo_session.c9
-rw-r--r--kernel/locking/lockdep.c144
-rw-r--r--kernel/locking/mutex.c4
-rw-r--r--kernel/locking/percpu-rwsem.c2
-rw-r--r--kernel/locking/qspinlock.c22
-rw-r--r--kernel/locking/rtmutex_api.c2
-rw-r--r--kernel/locking/spinlock.c31
-rw-r--r--kernel/module/dups.c169
-rw-r--r--kernel/module/kallsyms.c24
-rw-r--r--kernel/module/main.c37
-rw-r--r--kernel/module/procfs.c2
-rw-r--r--kernel/module/stats.c2
-rw-r--r--kernel/nstree.c10
-rw-r--r--kernel/params.c20
-rw-r--r--kernel/pid.c37
-rw-r--r--kernel/power/Kconfig10
-rw-r--r--kernel/power/hibernate.c26
-rw-r--r--kernel/power/snapshot.c47
-rw-r--r--kernel/power/swap.c34
-rw-r--r--kernel/power/wakelock.c2
-rw-r--r--kernel/printk/nbcon.c85
-rw-r--r--kernel/printk/printk.c32
-rw-r--r--kernel/rcu/Kconfig1
-rw-r--r--kernel/rcu/rcu.h20
-rw-r--r--kernel/rcu/rcu_segcblist.c139
-rw-r--r--kernel/rcu/rcu_segcblist.h8
-rw-r--r--kernel/rcu/rcuscale.c84
-rw-r--r--kernel/rcu/rcutorture.c374
-rw-r--r--kernel/rcu/srcutiny.c14
-rw-r--r--kernel/rcu/srcutree.c57
-rw-r--r--kernel/rcu/tasks.h62
-rw-r--r--kernel/rcu/tiny.c4
-rw-r--r--kernel/rcu/tree.c198
-rw-r--r--kernel/rcu/tree.h14
-rw-r--r--kernel/rcu/tree_exp.h29
-rw-r--r--kernel/rcu/tree_nocb.h126
-rw-r--r--kernel/rcu/tree_plugin.h93
-rw-r--r--kernel/rcu/tree_stall.h77
-rw-r--r--kernel/reboot.c44
-rw-r--r--kernel/resource.c4
-rw-r--r--kernel/scftorture.c16
-rw-r--r--kernel/sched/build_policy.c3
-rw-r--r--kernel/sched/core.c139
-rw-r--r--kernel/sched/cpufreq_schedutil.c35
-rw-r--r--kernel/sched/deadline.c13
-rw-r--r--kernel/sched/debug.c92
-rw-r--r--kernel/sched/ext/arena.c16
-rw-r--r--kernel/sched/ext/cid.c744
-rw-r--r--kernel/sched/ext/cid.h72
-rw-r--r--kernel/sched/ext/ext.c4474
-rw-r--r--kernel/sched/ext/idle.c132
-rw-r--r--kernel/sched/ext/inlines.h146
-rw-r--r--kernel/sched/ext/internal.h903
-rw-r--r--kernel/sched/ext/sub.c2682
-rw-r--r--kernel/sched/ext/sub.h232
-rw-r--r--kernel/sched/ext/types.h92
-rw-r--r--kernel/sched/fair.c1844
-rw-r--r--kernel/sched/idle.c8
-rw-r--r--kernel/sched/isolation.c16
-rw-r--r--kernel/sched/pelt.c6
-rw-r--r--kernel/sched/psi.c77
-rw-r--r--kernel/sched/rt.c7
-rw-r--r--kernel/sched/sched.h95
-rw-r--r--kernel/sched/topology.c30
-rw-r--r--kernel/sched/wait.c15
-rw-r--r--kernel/sched/wait_bit.c14
-rw-r--r--kernel/seccomp.c36
-rw-r--r--kernel/signal.c213
-rw-r--r--kernel/smp.c253
-rw-r--r--kernel/softirq.c25
-rw-r--r--kernel/stop_machine.c14
-rw-r--r--kernel/sys.c2
-rw-r--r--kernel/sys_ni.c4
-rw-r--r--kernel/sysctl.c530
-rw-r--r--kernel/taskstats.c45
-rw-r--r--kernel/time/Kconfig1
-rw-r--r--kernel/time/clockevents.c33
-rw-r--r--kernel/time/clocksource.c9
-rw-r--r--kernel/time/hrtimer.c79
-rw-r--r--kernel/time/itimer.c2
-rw-r--r--kernel/time/jiffies.c37
-rw-r--r--kernel/time/namespace.c6
-rw-r--r--kernel/time/namespace_vdso.c7
-rw-r--r--kernel/time/ntp.c310
-rw-r--r--kernel/time/ntp_internal.h4
-rw-r--r--kernel/time/posix-cpu-timers.c222
-rw-r--r--kernel/time/tick-broadcast.c36
-rw-r--r--kernel/time/tick-internal.h3
-rw-r--r--kernel/time/tick-sched.c7
-rw-r--r--kernel/time/time.c25
-rw-r--r--kernel/time/timekeeping.c204
-rw-r--r--kernel/time/timekeeping.h2
-rw-r--r--kernel/time/timekeeping_internal.h21
-rw-r--r--kernel/time/timer.c1
-rw-r--r--kernel/time/timer_list.c78
-rw-r--r--kernel/time/timer_migration.c4
-rw-r--r--kernel/time/timer_migration.h2
-rw-r--r--kernel/torture.c7
-rw-r--r--kernel/trace/Kconfig12
-rw-r--r--kernel/trace/bpf_trace.c130
-rw-r--r--kernel/trace/fgraph.c3
-rw-r--r--kernel/trace/fprobe.c72
-rw-r--r--kernel/trace/ftrace.c161
-rw-r--r--kernel/trace/preemptirq_delay_test.c9
-rw-r--r--kernel/trace/rethook.c2
-rw-r--r--kernel/trace/ring_buffer.c551
-rw-r--r--kernel/trace/ring_buffer_benchmark.c6
-rw-r--r--kernel/trace/rv/Kconfig15
-rw-r--r--kernel/trace/rv/Makefile2
-rw-r--r--kernel/trace/rv/monitors/nomiss/nomiss.c36
-rw-r--r--kernel/trace/rv/monitors/nomiss/nomiss_kunit.c38
-rw-r--r--kernel/trace/rv/monitors/nomiss/nomiss_kunit.h35
-rw-r--r--kernel/trace/rv/monitors/opid/opid.c12
-rw-r--r--kernel/trace/rv/monitors/opid/opid_kunit.c33
-rw-r--r--kernel/trace/rv/monitors/opid/opid_kunit.h23
-rw-r--r--kernel/trace/rv/monitors/pagefault/pagefault.c20
-rw-r--r--kernel/trace/rv/monitors/pagefault/pagefault_kunit.c34
-rw-r--r--kernel/trace/rv/monitors/pagefault/pagefault_kunit.h24
-rw-r--r--kernel/trace/rv/monitors/rtapp/Kconfig2
-rw-r--r--kernel/trace/rv/monitors/sco/sco.c13
-rw-r--r--kernel/trace/rv/monitors/sco/sco_kunit.c29
-rw-r--r--kernel/trace/rv/monitors/sco/sco_kunit.h24
-rw-r--r--kernel/trace/rv/monitors/sleep/Kconfig1
-rw-r--r--kernel/trace/rv/monitors/sleep/sleep.c107
-rw-r--r--kernel/trace/rv/monitors/sleep/sleep.h142
-rw-r--r--kernel/trace/rv/monitors/sleep/sleep_kunit.c59
-rw-r--r--kernel/trace/rv/monitors/sleep/sleep_kunit.h29
-rw-r--r--kernel/trace/rv/monitors/sssw/sssw.c14
-rw-r--r--kernel/trace/rv/monitors/sssw/sssw_kunit.c33
-rw-r--r--kernel/trace/rv/monitors/sssw/sssw_kunit.h30
-rw-r--r--kernel/trace/rv/monitors/stall/stall.c2
-rw-r--r--kernel/trace/rv/monitors/sts/sts.c19
-rw-r--r--kernel/trace/rv/monitors/sts/sts_kunit.c39
-rw-r--r--kernel/trace/rv/monitors/sts/sts_kunit.h33
-rw-r--r--kernel/trace/rv/monitors/wakeup/Kconfig16
-rw-r--r--kernel/trace/rv/monitors/wakeup/wakeup.c153
-rw-r--r--kernel/trace/rv/monitors/wakeup/wakeup.h92
-rw-r--r--kernel/trace/rv/monitors/wakeup/wakeup_trace.h14
-rw-r--r--kernel/trace/rv/rv.c86
-rw-r--r--kernel/trace/rv/rv_monitors_test.c182
-rw-r--r--kernel/trace/rv/rv_reactors.c1
-rw-r--r--kernel/trace/rv/rv_trace.h1
-rw-r--r--kernel/trace/simple_ring_buffer.c4
-rw-r--r--kernel/trace/trace.c240
-rw-r--r--kernel/trace/trace.h21
-rw-r--r--kernel/trace/trace_boot.c97
-rw-r--r--kernel/trace/trace_btf.c31
-rw-r--r--kernel/trace/trace_btf.h3
-rw-r--r--kernel/trace/trace_dynevent.c5
-rw-r--r--kernel/trace/trace_eprobe.c9
-rw-r--r--kernel/trace/trace_events.c196
-rw-r--r--kernel/trace/trace_events_filter.c9
-rw-r--r--kernel/trace/trace_events_hist.c188
-rw-r--r--kernel/trace/trace_events_synth.c10
-rw-r--r--kernel/trace/trace_events_trigger.c40
-rw-r--r--kernel/trace/trace_events_user.c78
-rw-r--r--kernel/trace/trace_fprobe.c24
-rw-r--r--kernel/trace/trace_functions.c10
-rw-r--r--kernel/trace/trace_functions_graph.c5
-rw-r--r--kernel/trace/trace_kprobe.c27
-rw-r--r--kernel/trace/trace_mmiotrace.c24
-rw-r--r--kernel/trace/trace_osnoise.c4
-rw-r--r--kernel/trace/trace_preemptirq.c2
-rw-r--r--kernel/trace/trace_printk.c3
-rw-r--r--kernel/trace/trace_probe.c1248
-rw-r--r--kernel/trace/trace_probe.h280
-rw-r--r--kernel/trace/trace_probe_kernel.h2
-rw-r--r--kernel/trace/trace_probe_tmpl.h27
-rw-r--r--kernel/trace/trace_remote.c50
-rw-r--r--kernel/trace/trace_sched_switch.c6
-rw-r--r--kernel/trace/trace_stack.c2
-rw-r--r--kernel/trace/trace_syscalls.c23
-rw-r--r--kernel/trace/trace_uprobe.c9
-rw-r--r--kernel/ucount.c6
-rw-r--r--kernel/umh.c6
-rw-r--r--kernel/up.c3
-rw-r--r--kernel/user.c4
-rw-r--r--kernel/utsname_sysctl.c4
-rw-r--r--kernel/watchdog.c16
-rw-r--r--kernel/workqueue.c24
281 files changed, 26227 insertions, 9755 deletions
diff --git a/kernel/Kconfig.locks b/kernel/Kconfig.locks
index 4198f0273ecd..1c6423aafcd4 100644
--- a/kernel/Kconfig.locks
+++ b/kernel/Kconfig.locks
@@ -243,6 +243,26 @@ config QUEUED_SPINLOCKS
def_bool y if ARCH_USE_QUEUED_SPINLOCKS
depends on SMP
+config QUEUED_SPINLOCKS_TRACE_CONTENDED_RELEASE
+ bool "Trace contended_release on queued spinlocks"
+ depends on QUEUED_SPINLOCKS && TRACEPOINTS
+ help
+ Fire the lock:contended_release tracepoint when a contended queued
+ spinlock is released, so it is possible to attribute a contended
+ spinlock to its holder.
+
+ Architectures that can patch the unlock site do this at no cost and
+ do not need this option.
+
+ Everywhere else the check is compiled into queued_spin_unlock() and
+ a small cost is paid on every unlock even when the tracepoint is
+ disabled: a static-branch NOP and possibly a few more instructions
+ to manage a stack frame.
+
+ Sleeping locks fire lock:contended_release regardless of this option.
+
+ If unsure, say N.
+
config BPF_ARCH_SPINLOCK
bool
diff --git a/kernel/Kconfig.preempt b/kernel/Kconfig.preempt
index 88c594c6d7fc..f294dad43bd7 100644
--- a/kernel/Kconfig.preempt
+++ b/kernel/Kconfig.preempt
@@ -122,6 +122,10 @@ config PREEMPT_RT_NEEDS_BH_LOCK
config PREEMPT_COUNT
bool
+config HAS_SEPARATE_PREEMPT_RESCHED_BITS
+ bool
+ depends on PREEMPT_COUNT && 64BIT
+
config PREEMPTION
bool
select PREEMPT_COUNT
@@ -169,6 +173,7 @@ config SCHED_CORE
config SCHED_CLASS_EXT
bool "Extensible Scheduling Class"
depends on BPF_SYSCALL && BPF_JIT && DEBUG_INFO_BTF
+ select GENERIC_ALLOCATOR
select STACKTRACE if STACKTRACE_SUPPORT
help
This option enables a new scheduler class sched_ext (SCX), which
diff --git a/kernel/audit.c b/kernel/audit.c
index dcc657d35776..9412af9144bc 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -62,6 +62,7 @@
#include <net/ip.h>
#include <net/ipv6.h>
#include <linux/sctp.h>
+#include <linux/overflow.h>
#include "audit.h"
@@ -950,7 +951,7 @@ main_queue:
* do the multicast send and rotate records from the
* main queue to the retry/hold queues */
wait_event_freezable(kauditd_wait,
- (skb_queue_len(&audit_queue) ? 1 : 0));
+ (skb_queue_len_lockless(&audit_queue) ? 1 : 0));
}
return 0;
@@ -1283,7 +1284,7 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh,
s.rate_limit = audit_rate_limit;
s.backlog_limit = audit_backlog_limit;
s.lost = atomic_read(&audit_lost);
- s.backlog = skb_queue_len(&audit_queue);
+ s.backlog = skb_queue_len_lockless(&audit_queue);
s.feature_bitmap = AUDIT_FEATURE_BITMAP_ALL;
s.backlog_wait_time = audit_backlog_wait_time;
s.backlog_wait_time_actual = atomic_read(&audit_backlog_wait_time_actual);
@@ -1627,7 +1628,7 @@ static void audit_receive(struct sk_buff *skb)
/* can't block with the ctrl lock, so penalize the sender now */
if (audit_backlog_limit &&
- (skb_queue_len(&audit_queue) > audit_backlog_limit)) {
+ (skb_queue_len_lockless(&audit_queue) > audit_backlog_limit)) {
DECLARE_WAITQUEUE(wait, current);
/* wake kauditd to try and flush the queue */
@@ -1933,7 +1934,7 @@ struct audit_buffer *audit_log_start(struct audit_context *ctx, gfp_t gfp_mask,
long stime = audit_backlog_wait_time;
while (audit_backlog_limit &&
- (skb_queue_len(&audit_queue) > audit_backlog_limit)) {
+ (skb_queue_len_lockless(&audit_queue) > audit_backlog_limit)) {
/* wake kauditd to try and flush the queue */
wake_up_interruptible(&kauditd_wait);
@@ -1953,7 +1954,7 @@ struct audit_buffer *audit_log_start(struct audit_context *ctx, gfp_t gfp_mask,
} else {
if (audit_rate_check() && printk_ratelimit())
pr_warn("audit_backlog=%d > audit_backlog_limit=%d\n",
- skb_queue_len(&audit_queue),
+ skb_queue_len_lockless(&audit_queue),
audit_backlog_limit);
audit_log_lost("backlog limit exceeded");
return NULL;
@@ -2080,7 +2081,8 @@ void audit_log_format(struct audit_buffer *ab, const char *fmt, ...)
void audit_log_n_hex(struct audit_buffer *ab, const unsigned char *buf,
size_t len)
{
- int i, avail, new_len;
+ int avail;
+ size_t i, new_len;
unsigned char *ptr;
struct sk_buff *skb;
@@ -2090,7 +2092,12 @@ void audit_log_n_hex(struct audit_buffer *ab, const unsigned char *buf,
BUG_ON(!ab->skb);
skb = ab->skb;
avail = skb_tailroom(skb);
- new_len = len<<1;
+
+ if (check_shl_overflow(len, 1, &new_len)) {
+ audit_log_format(ab, "?");
+ return;
+ }
+
if (new_len >= avail) {
/* Round the buffer request up to the next multiple */
new_len = AUDIT_BUFSIZ*(((new_len-avail)/AUDIT_BUFSIZ) + 1);
@@ -2113,7 +2120,8 @@ void audit_log_n_hex(struct audit_buffer *ab, const unsigned char *buf,
void audit_log_n_string(struct audit_buffer *ab, const char *string,
size_t slen)
{
- int avail, new_len;
+ int avail;
+ size_t new_len;
unsigned char *ptr;
struct sk_buff *skb;
@@ -2123,7 +2131,13 @@ void audit_log_n_string(struct audit_buffer *ab, const char *string,
BUG_ON(!ab->skb);
skb = ab->skb;
avail = skb_tailroom(skb);
- new_len = slen + 3; /* enclosing quotes + null terminator */
+
+ /* enclosing quotes + null terminator */
+ if (check_add_overflow(slen, 3, &new_len)) {
+ audit_log_format(ab, "?");
+ return;
+ }
+
if (new_len > avail) {
avail = audit_expand(ab, new_len);
if (!avail)
diff --git a/kernel/audit_tree.c b/kernel/audit_tree.c
index 1ed19b775912..f2e81be8265e 100644
--- a/kernel/audit_tree.c
+++ b/kernel/audit_tree.c
@@ -545,22 +545,38 @@ static void kill_rules(struct audit_context *context, struct audit_tree *tree)
{
struct audit_krule *rule, *next;
struct audit_entry *entry;
+ bool need_sync = false;
list_for_each_entry_safe(rule, next, &tree->rules, rlist) {
entry = container_of(rule, struct audit_entry, rule);
- list_del_init(&rule->rlist);
if (rule->tree) {
/* not a half-baked one */
audit_tree_log_remove_rule(context, rule);
- if (entry->rule.exe)
- audit_remove_mark(entry->rule.exe);
rule->tree = NULL;
list_del_rcu(&entry->list);
list_del(&entry->rule.list);
- call_rcu(&entry->rcu, audit_free_rule_rcu);
+ if (entry->rule.exe)
+ need_sync = true;
+ } else {
+ list_del_init(&rule->rlist);
}
}
+
+ if (list_empty(&tree->rules))
+ return;
+
+ if (need_sync)
+ synchronize_rcu();
+
+ list_for_each_entry_safe(rule, next, &tree->rules, rlist) {
+ entry = container_of(rule, struct audit_entry, rule);
+
+ list_del_init(&rule->rlist);
+ if (entry->rule.exe)
+ audit_remove_mark(entry->rule.exe);
+ call_rcu(&entry->rcu, audit_free_rule_rcu);
+ }
}
/*
diff --git a/kernel/audit_watch.c b/kernel/audit_watch.c
index 06dd0ebe73e2..4ac8a91e9ba8 100644
--- a/kernel/audit_watch.c
+++ b/kernel/audit_watch.c
@@ -372,7 +372,7 @@ static void audit_add_to_parent(struct audit_krule *krule,
struct audit_watch *w, *watch = krule->watch;
int watch_found = 0;
- BUG_ON(!mutex_is_locked(&audit_filter_mutex));
+ lockdep_assert_held(&audit_filter_mutex);
list_for_each_entry(w, &parent->watches, wlist) {
if (strcmp(watch->path, w->path))
diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
index 4401119b5275..f52645625214 100644
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -1023,7 +1023,6 @@ static inline int audit_add_rule(struct audit_entry *entry)
int audit_del_rule(struct audit_entry *entry)
{
struct audit_entry *e;
- struct audit_tree *tree = entry->rule.tree;
struct list_head *list;
int ret = 0;
#ifdef CONFIG_AUDITSYSCALL
@@ -1045,6 +1044,10 @@ int audit_del_rule(struct audit_entry *entry)
goto out;
}
+ list_del_rcu(&e->list);
+ list_del(&e->rule.list);
+ synchronize_rcu();
+
if (e->rule.watch)
audit_remove_watch_rule(&e->rule);
@@ -1062,16 +1065,11 @@ int audit_del_rule(struct audit_entry *entry)
audit_signals--;
#endif
- list_del_rcu(&e->list);
- list_del(&e->rule.list);
call_rcu(&e->rcu, audit_free_rule_rcu);
out:
mutex_unlock(&audit_filter_mutex);
- if (tree)
- audit_put_tree(tree); /* that's the temporary one */
-
return ret;
}
@@ -1156,6 +1154,8 @@ int audit_rule_change(int type, int seq, void *data, size_t datasz)
}
if (err || type == AUDIT_DEL_RULE) {
+ if (type == AUDIT_DEL_RULE && entry->rule.tree)
+ audit_put_tree(entry->rule.tree);
if (entry->rule.exe)
audit_remove_mark(entry->rule.exe);
audit_free_rule(entry);
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index 6610e667c728..2b9ce0b52511 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -2712,7 +2712,8 @@ int audit_signal_info_syscall(struct task_struct *t)
axp->d.next = ctx->aux_pids;
ctx->aux_pids = (void *)axp;
}
- BUG_ON(axp->pid_count >= AUDIT_AUX_PIDS);
+ if (WARN_ON_ONCE(axp->pid_count >= AUDIT_AUX_PIDS))
+ return -EINVAL;
axp->target_pid[axp->pid_count] = task_tgid_nr(t);
axp->target_auid[axp->pid_count] = audit_get_loginuid(t);
diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile
index 4dc41bf5780c..90255d80e5be 100644
--- a/kernel/bpf/Makefile
+++ b/kernel/bpf/Makefile
@@ -6,7 +6,7 @@ cflags-nogcse-$(CONFIG_X86)$(CONFIG_CC_IS_GCC) := -fno-gcse
endif
CFLAGS_core.o += -Wno-override-init $(cflags-nogcse-yy)
-obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o tnum.o cnum.o log.o token.o liveness.o const_fold.o
+obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o tnum.o cnum.o log.o token.o liveness.o const_fold.o diagnostics.o
obj-$(CONFIG_BPF_SYSCALL) += bpf_iter.o map_iter.o task_iter.o prog_iter.o link_iter.o
obj-$(CONFIG_BPF_SYSCALL) += hashtab.o arraymap.o percpu_freelist.o bpf_lru_list.o lpm_trie.o map_in_map.o bloom_filter.o
obj-$(CONFIG_BPF_SYSCALL) += local_storage.o queue_stack_maps.o ringbuf.o bpf_insn_array.o
diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
index 80b7b8a69446..7b6847200b43 100644
--- a/kernel/bpf/arena.c
+++ b/kernel/bpf/arena.c
@@ -55,8 +55,10 @@ struct bpf_arena {
struct vm_struct *kern_vm;
struct page *scratch_page;
struct range_tree rt;
- /* protects rt */
+ /* protects rt and nr_pages */
rqspinlock_t spinlock;
+ /* number of pages currently populated in the arena */
+ u64 nr_pages;
struct list_head vma_list;
/* protects vma_list */
struct mutex lock;
@@ -143,14 +145,14 @@ static long compute_pgoff(struct bpf_arena *arena, long uaddr)
}
struct apply_range_data {
+ struct bpf_arena *arena;
struct page **pages;
- struct page *scratch_page;
int i;
};
struct clear_range_data {
+ struct bpf_arena *arena;
struct llist_head *free_pages;
- struct page *scratch_page;
};
static int apply_range_set_cb(pte_t *pte, unsigned long addr, void *data)
@@ -180,7 +182,7 @@ static int apply_range_set_cb(pte_t *pte, unsigned long addr, void *data)
if (pte_none(old))
continue;
- if (WARN_ON_ONCE(pte_page(old) != d->scratch_page))
+ if (WARN_ON_ONCE(pte_page(old) != d->arena->scratch_page))
return -EBUSY;
ptep_get_and_clear(&init_mm, addr, pte);
flush_tlb_before_set(addr);
@@ -196,6 +198,7 @@ static int apply_range_set_cb(pte_t *pte, unsigned long addr, void *data)
set_pte_at(&init_mm, addr, pte, pteval);
#endif
d->i++;
+ WRITE_ONCE(d->arena->nr_pages, d->arena->nr_pages + 1);
return 0;
}
@@ -227,10 +230,11 @@ static int apply_range_clear_cb(pte_t *pte, unsigned long addr, void *data)
* scratches its PTE. A later bpf_arena_free_pages() over that range walks
* here. Without the skip, scratch_page would be freed.
*/
- if (page == d->scratch_page)
+ if (page == d->arena->scratch_page)
return 0;
__llist_add(&page->pcp_llist, d->free_pages);
+ WRITE_ONCE(d->arena->nr_pages, d->arena->nr_pages - 1);
return 0;
}
@@ -413,7 +417,9 @@ static int arena_map_check_btf(struct bpf_map *map, const struct btf *btf,
static u64 arena_map_mem_usage(const struct bpf_map *map)
{
- return 0;
+ struct bpf_arena *arena = container_of(map, struct bpf_arena, map);
+
+ return (u64)READ_ONCE(arena->nr_pages) << PAGE_SHIFT;
}
struct vma_list {
@@ -484,8 +490,12 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
kaddr = kbase + (u32)(vmf->address);
if (raw_res_spin_lock_irqsave(&arena->spinlock, flags))
- /* Make a reasonable effort to address impossible case */
- return VM_FAULT_RETRY;
+ /*
+ * A failed lock means a possible deadlock was detected. Don't
+ * return VM_FAULT_RETRY: this handler never took mmap_lock, but
+ * the fault path would re-take it on retry and deadlock. Fail.
+ */
+ return VM_FAULT_SIGBUS;
page = vmalloc_to_page((void *)kaddr);
if (page) {
@@ -506,8 +516,7 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
if (ret)
goto out_sigsegv_memcg;
- struct apply_range_data data = { .pages = &page, .i = 0,
- .scratch_page = arena->scratch_page };
+ struct apply_range_data data = { .arena = arena, .pages = &page, .i = 0 };
/* Account into memcg of the process that created bpf_arena */
ret = bpf_map_alloc_pages(map, NUMA_NO_NODE, 1, &page);
if (ret) {
@@ -696,8 +705,8 @@ static long arena_alloc_pages(struct bpf_arena *arena, long uaddr, long page_cnt
bpf_map_memcg_exit(old_memcg, new_memcg);
return 0;
}
+ data.arena = arena;
data.pages = pages;
- data.scratch_page = arena->scratch_page;
if (raw_res_spin_lock_irqsave(&arena->spinlock, flags))
goto out_free_pages;
@@ -853,6 +862,8 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
uaddr &= PAGE_MASK;
kaddr = bpf_arena_get_kern_vm_start(arena) + uaddr;
full_uaddr = clear_lo32(arena->user_vm_start) + uaddr;
+ if (full_uaddr < arena->user_vm_start)
+ return;
uaddr_end = min(arena->user_vm_end, full_uaddr + (page_cnt << PAGE_SHIFT));
if (full_uaddr >= uaddr_end)
return;
@@ -873,8 +884,8 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
range_tree_set(&arena->rt, pgoff, page_cnt);
init_llist_head(&free_pages);
+ cdata.arena = arena;
cdata.free_pages = &free_pages;
- cdata.scratch_page = arena->scratch_page;
/* clear ptes and collect struct pages */
apply_to_existing_page_range(&init_mm, kaddr, page_cnt << PAGE_SHIFT,
apply_range_clear_cb, &cdata);
@@ -981,8 +992,8 @@ static void arena_free_worker(struct work_struct *work)
bpf_map_memcg_enter(&arena->map, &old_memcg, &new_memcg);
init_llist_head(&free_pages);
+ cdata.arena = arena;
cdata.free_pages = &free_pages;
- cdata.scratch_page = arena->scratch_page;
arena_vm_start = bpf_arena_get_kern_vm_start(arena);
user_vm_start = bpf_arena_get_user_vm_start(arena);
@@ -1107,9 +1118,9 @@ __bpf_kfunc int bpf_arena_reserve_pages(void *p__map, void *ptr__ign, u32 page_c
__bpf_kfunc_end_defs();
BTF_KFUNCS_START(arena_kfuncs)
-BTF_ID_FLAGS(func, bpf_arena_alloc_pages, KF_ARENA_RET | KF_ARENA_ARG2)
-BTF_ID_FLAGS(func, bpf_arena_free_pages, KF_ARENA_ARG2)
-BTF_ID_FLAGS(func, bpf_arena_reserve_pages, KF_ARENA_ARG2)
+BTF_ID_FLAGS(func, bpf_arena_alloc_pages, KF_ARENA_RET | KF_ARENA_ARG2 | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_arena_free_pages, KF_ARENA_ARG2 | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_arena_reserve_pages, KF_ARENA_ARG2 | KF_SPINLOCK_SAFE)
BTF_KFUNCS_END(arena_kfuncs)
static const struct btf_kfunc_id_set common_kfunc_set = {
diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c
index 248b4818178c..0ce26b538075 100644
--- a/kernel/bpf/arraymap.c
+++ b/kernel/bpf/arraymap.c
@@ -259,6 +259,37 @@ static void *percpu_array_map_lookup_elem(struct bpf_map *map, void *key)
return this_cpu_ptr(array->pptrs[index & array->index_mask]);
}
+static int percpu_array_map_direct_value_addr(const struct bpf_map *map, u64 *imm, u32 off)
+{
+ struct bpf_array *array = container_of(map, struct bpf_array, map);
+
+ if (!bpf_jit_supports_percpu_insn())
+ return -EOPNOTSUPP;
+ if (map->max_entries != 1)
+ return -EOPNOTSUPP;
+ if (off >= map->value_size)
+ return -EINVAL;
+
+ *imm = (u64)(__force unsigned long) array->pptrs[0];
+ return 0;
+}
+
+static int percpu_array_map_direct_value_meta(const struct bpf_map *map, u64 imm, u32 *off)
+{
+ struct bpf_array *array = container_of(map, struct bpf_array, map);
+ u64 base = (u64)(__force unsigned long) array->pptrs[0];
+
+ if (!bpf_jit_supports_percpu_insn())
+ return -EOPNOTSUPP;
+ if (map->max_entries != 1)
+ return -EOPNOTSUPP;
+ if (imm < base || imm >= base + array->elem_size)
+ return -ENOENT;
+
+ *off = imm - base;
+ return 0;
+}
+
/* emit BPF instructions equivalent to C code of percpu_array_map_lookup_elem() */
static int percpu_array_map_gen_lookup(struct bpf_map *map, struct bpf_insn *insn_buf)
{
@@ -405,7 +436,7 @@ int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value,
void __percpu *pptr;
void *ptr, *val;
u32 size;
- int cpu;
+ int cpu, off = 0;
if (unlikely((map_flags & BPF_F_LOCK) || (u32)map_flags > BPF_F_ALL_CPUS))
/* unknown flags */
@@ -437,9 +468,10 @@ int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value,
}
for_each_possible_cpu(cpu) {
ptr = per_cpu_ptr(pptr, cpu);
- val = (map_flags & BPF_F_ALL_CPUS) ? value : value + size * cpu;
+ val = (map_flags & BPF_F_ALL_CPUS) ? value : value + off;
copy_map_value(map, ptr, val);
bpf_obj_cancel_fields(map, ptr);
+ off += size;
}
unlock:
rcu_read_unlock();
@@ -551,9 +583,10 @@ static int array_map_check_btf(struct bpf_map *map,
const struct btf_type *key_type,
const struct btf_type *value_type)
{
- /* One exception for keyless BTF: .bss/.data/.rodata map */
+ /* One exception for keyless BTF: .bss/.data/.rodata/.percpu map */
if (btf_type_is_void(key_type)) {
- if (map->map_type != BPF_MAP_TYPE_ARRAY ||
+ if ((map->map_type != BPF_MAP_TYPE_ARRAY &&
+ map->map_type != BPF_MAP_TYPE_PERCPU_ARRAY) ||
map->max_entries != 1)
return -EINVAL;
@@ -576,17 +609,42 @@ static int array_map_check_btf(struct bpf_map *map,
static int array_map_mmap(struct bpf_map *map, struct vm_area_struct *vma)
{
struct bpf_array *array = container_of(map, struct bpf_array, map);
- pgoff_t pgoff = PAGE_ALIGN(sizeof(*array)) >> PAGE_SHIFT;
if (!(map->map_flags & BPF_F_MMAPABLE))
return -EINVAL;
- if (vma->vm_pgoff * PAGE_SIZE + (vma->vm_end - vma->vm_start) >
+ /* use u64 math so the offset cannot overflow on 32-bit archs */
+ if ((u64)vma->vm_pgoff * PAGE_SIZE + (vma->vm_end - vma->vm_start) >
PAGE_ALIGN((u64)array->map.max_entries * array->elem_size))
return -EINVAL;
- return remap_vmalloc_range(vma, array_map_vmalloc_addr(array),
- vma->vm_pgoff + pgoff);
+ /*
+ * Pages are faulted in on demand by array_map_mmap_fault(). Set the
+ * same flags that the eager remap_vmalloc_range() path used to set
+ * via vm_insert_page(), so that e.g. NUMA balancing keeps skipping
+ * these VMAs.
+ */
+ vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP | VM_MIXEDMAP);
+
+ return 0;
+}
+
+static vm_fault_t array_map_mmap_fault(struct bpf_map *map,
+ struct vm_fault *vmf)
+{
+ struct bpf_array *array = container_of(map, struct bpf_array, map);
+ struct page *page;
+
+ page = vmalloc_to_page(array->value + ((u64)vmf->pgoff << PAGE_SHIFT));
+ if (!page)
+ return VM_FAULT_SIGBUS;
+
+ /* the eager remap_vmalloc_range() flushed via vm_insert_page() */
+ flush_dcache_folio(page_folio(page));
+ get_page(page);
+ vmf->page = page;
+
+ return 0;
}
static bool array_map_meta_equal(const struct bpf_map *meta0,
@@ -812,6 +870,7 @@ const struct bpf_map_ops array_map_ops = {
.map_direct_value_addr = array_map_direct_value_addr,
.map_direct_value_meta = array_map_direct_value_meta,
.map_mmap = array_map_mmap,
+ .map_mmap_fault = array_map_mmap_fault,
.map_seq_show_elem = array_map_seq_show_elem,
.map_check_btf = array_map_check_btf,
.map_lookup_batch = generic_map_lookup_batch,
@@ -832,6 +891,8 @@ const struct bpf_map_ops percpu_array_map_ops = {
.map_get_next_key = bpf_array_get_next_key,
.map_lookup_elem = percpu_array_map_lookup_elem,
.map_gen_lookup = percpu_array_map_gen_lookup,
+ .map_direct_value_addr = percpu_array_map_direct_value_addr,
+ .map_direct_value_meta = percpu_array_map_direct_value_meta,
.map_update_elem = array_map_update_elem,
.map_delete_elem = array_map_delete_elem,
.map_lookup_percpu_elem = percpu_array_map_lookup_percpu_elem,
diff --git a/kernel/bpf/backtrack.c b/kernel/bpf/backtrack.c
index 2e4ae0ef0860..47282ffeeaf9 100644
--- a/kernel/bpf/backtrack.c
+++ b/kernel/bpf/backtrack.c
@@ -214,7 +214,6 @@ static inline bool bt_is_reg_set(struct backtrack_state *bt, u32 reg)
return bt->reg_masks[bt->frame] & (1 << reg);
}
-
/* format registers bitmask, e.g., "r0,r2,r4" for 0x15 mask */
static void fmt_reg_mask(char *buf, ssize_t buf_sz, u32 reg_mask)
{
@@ -254,7 +253,6 @@ void bpf_fmt_stack_mask(char *buf, ssize_t buf_sz, u64 stack_mask)
}
}
-
/* For given verifier state backtrack_insn() is called from the last insn to
* the first insn. Its purpose is to compute a bitmask of registers and
* stack slots that needs precision in the parent verifier state.
@@ -285,6 +283,7 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx,
verbose(env, "stack=%s before ", env->tmp_str_buf);
verbose(env, "%d: ", idx);
bpf_verbose_insn(env, insn);
+ verbose(env, "\n");
}
/* If there is a history record that some registers gained range at this insn,
@@ -521,37 +520,34 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx,
return -EFAULT;
}
} else if (opcode == BPF_EXIT) {
- bool r0_precise;
+ bool from_subprog_call, r0_precise;
+
+ /* BPF_EXIT in subprog or callback always returns
+ * right after the call instruction, so by checking
+ * whether the instruction at subseq_idx-1 is subprog
+ * call or not we can distinguish actual exit from
+ * *subprog* from exit from *callback*. In the former
+ * case, we need to propagate r0 precision, if
+ * necessary. In the former we never do that.
+ */
+ from_subprog_call = subseq_idx - 1 >= 0 &&
+ bpf_pseudo_call(&env->prog->insnsi[subseq_idx - 1]);
+
+ r0_precise = from_subprog_call && bt_is_reg_set(bt, BPF_REG_0);
/* Backtracking to a nested function call, 'idx' is a part of
* the inner frame 'subseq_idx' is a part of the outer frame.
* In case of a regular function call, instructions giving
* precision to registers R1-R5 should have been found already.
- * In case of a callback, it is ok to have R1-R5 marked for
- * backtracking, as these registers are set by the function
- * invoking callback.
+ * In case of a callback from bpf_loop(), R{1,4} in the calling
+ * frame would be set as precise and that is correct.
*/
- if (subseq_idx >= 0 && bpf_calls_callback(env, subseq_idx))
- for (i = BPF_REG_1; i <= BPF_REG_5; i++)
- bt_clear_reg(bt, i);
- if (bt_reg_mask(bt) & BPF_REGMASK_ARGS) {
+ if (from_subprog_call && (bt_reg_mask(bt) & BPF_REGMASK_ARGS)) {
verifier_bug(env, "backtracking exit unexpected regs %x",
bt_reg_mask(bt));
return -EFAULT;
}
- /* BPF_EXIT in subprog or callback always returns
- * right after the call instruction, so by checking
- * whether the instruction at subseq_idx-1 is subprog
- * call or not we can distinguish actual exit from
- * *subprog* from exit from *callback*. In the former
- * case, we need to propagate r0 precision, if
- * necessary. In the former we never do that.
- */
- r0_precise = subseq_idx - 1 >= 0 &&
- bpf_pseudo_call(&env->prog->insnsi[subseq_idx - 1]) &&
- bt_is_reg_set(bt, BPF_REG_0);
-
bt_clear_reg(bt, BPF_REG_0);
if (bt_subprog_enter(bt))
return -EFAULT;
@@ -583,16 +579,29 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx,
*/
}
} else if (class == BPF_LD) {
- if (!bt_is_reg_set(bt, dreg))
- return 0;
- bt_clear_reg(bt, dreg);
/* It's ld_imm64 or ld_abs or ld_ind.
* For ld_imm64 no further tracking of precision
* into parent is necessary
*/
- if (mode == BPF_IND || mode == BPF_ABS)
- /* to be analyzed */
- return -ENOTSUPP;
+ if (mode == BPF_IMM) {
+ bt_clear_reg(bt, dreg);
+ return 0;
+ }
+ /*
+ * BPF_{IND,ABS} are modelled as two branches:
+ * - fallthrough;
+ * - implicit subprogram exit.
+ * It is necessary to switch current frame if
+ * implicit subprogram exit branch is backtracked.
+ */
+ if (mode == BPF_IND || mode == BPF_ABS) {
+ if (bt_is_reg_set(bt, dreg))
+ return -ENOTSUPP;
+ if (subseq_idx != idx + 1)
+ if (bt_subprog_enter(bt))
+ return -EFAULT;
+ return 0;
+ }
}
/* Propagate precision marks to linked registers, to account for
* registers marked as precise in this function.
@@ -636,7 +645,7 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx,
* r5 += 1
* ...
* call bpf_perf_event_output#25
- * where .arg5_type = ARG_CONST_SIZE_OR_ZERO
+ * where .arg5_type = ARG_MEM_SIZE_OR_ZERO
*
* and this case:
* r6 = 1
diff --git a/kernel/bpf/bloom_filter.c b/kernel/bpf/bloom_filter.c
index b73336c976b7..c6e7559b07de 100644
--- a/kernel/bpf/bloom_filter.c
+++ b/kernel/bpf/bloom_filter.c
@@ -41,7 +41,7 @@ static long bloom_map_peek_elem(struct bpf_map *map, void *value)
for (i = 0; i < bloom->nr_hash_funcs; i++) {
h = hash(bloom, value, map->value_size, i);
- if (!test_bit(h, bloom->bitset))
+ if (!test_bit(h % BITS_PER_LONG, bloom->bitset + BIT_WORD(h)))
return -ENOENT;
}
@@ -57,9 +57,13 @@ static long bloom_map_push_elem(struct bpf_map *map, void *value, u64 flags)
if (flags != BPF_ANY)
return -EINVAL;
+ /*
+ * On 32-bit architectures, hashes larger than INT_MAX would be
+ * treated as negative by set_bit().
+ */
for (i = 0; i < bloom->nr_hash_funcs; i++) {
h = hash(bloom, value, map->value_size, i);
- set_bit(h, bloom->bitset);
+ set_bit(h % BITS_PER_LONG, bloom->bitset + BIT_WORD(h));
}
return 0;
@@ -94,9 +98,10 @@ static int bloom_map_alloc_check(union bpf_attr *attr)
static struct bpf_map *bloom_map_alloc(union bpf_attr *attr)
{
- u32 bitset_bytes, bitset_mask, nr_hash_funcs, nr_bits;
+ u32 bitset_mask, nr_hash_funcs, nr_bits;
int numa_node = bpf_map_attr_numa_node(attr);
struct bpf_bloom_filter *bloom;
+ u64 bitset_bytes;
if (attr->key_size != 0 || attr->value_size == 0 ||
attr->max_entries == 0 ||
@@ -127,22 +132,16 @@ static struct bpf_map *bloom_map_alloc(union bpf_attr *attr)
if (check_mul_overflow(attr->max_entries, nr_hash_funcs, &nr_bits) ||
check_mul_overflow(nr_bits / 5, (u32)7, &nr_bits) ||
nr_bits > (1UL << 31)) {
- /* The bit array size is 2^32 bits but to avoid overflowing the
- * u32, we use U32_MAX, which will round up to the equivalent
- * number of bytes
- */
- bitset_bytes = BITS_TO_BYTES(U32_MAX);
bitset_mask = U32_MAX;
} else {
if (nr_bits <= BITS_PER_LONG)
nr_bits = BITS_PER_LONG;
else
nr_bits = roundup_pow_of_two(nr_bits);
- bitset_bytes = BITS_TO_BYTES(nr_bits);
bitset_mask = nr_bits - 1;
}
- bitset_bytes = roundup(bitset_bytes, sizeof(unsigned long));
+ bitset_bytes = BITS_TO_LONGS((u64)bitset_mask + 1) * sizeof(unsigned long);
bloom = bpf_map_area_alloc(sizeof(*bloom) + bitset_bytes, numa_node);
if (!bloom)
diff --git a/kernel/bpf/bpf_inode_storage.c b/kernel/bpf/bpf_inode_storage.c
index 0da8d923e39d..f9e81060c1f4 100644
--- a/kernel/bpf/bpf_inode_storage.c
+++ b/kernel/bpf/bpf_inode_storage.c
@@ -178,6 +178,15 @@ static int notsupp_get_next_key(struct bpf_map *map, void *key,
static struct bpf_map *inode_storage_map_alloc(union bpf_attr *attr)
{
+ /*
+ * Do not allow allocation of BPF_MAP_TYPE_INODE_STORAGE if the BPF LSM
+ * was not initialized by the LSM framework at boot. Without proper
+ * initialization, the BPF inode security blob offset remains unprepared,
+ * causing bpf_inode() to calculate an invalid memory offset and corrupt
+ * inode->i_security.
+ */
+ if (!bpf_lsm_initialized)
+ return ERR_PTR(-EOPNOTSUPP);
return bpf_local_storage_map_alloc(attr, &inode_cache);
}
diff --git a/kernel/bpf/bpf_iter.c b/kernel/bpf/bpf_iter.c
index f5eaeb2493d4..b40eb404adab 100644
--- a/kernel/bpf/bpf_iter.c
+++ b/kernel/bpf/bpf_iter.c
@@ -754,7 +754,7 @@ const struct bpf_func_proto bpf_loop_proto = {
.func = bpf_loop,
.gpl_only = false,
.ret_type = RET_INTEGER,
- .arg1_type = ARG_ANYTHING,
+ .arg1_type = ARG_SCALAR,
.arg2_type = ARG_PTR_TO_FUNC,
.arg3_type = ARG_PTR_TO_STACK_OR_NULL,
.arg4_type = ARG_ANYTHING,
@@ -782,8 +782,8 @@ __bpf_kfunc int bpf_iter_num_new(struct bpf_iter_num *it, int start, int end)
return -EINVAL;
}
- /* avoid overflows, e.g., if start == INT_MIN and end == INT_MAX */
- if ((s64)end - (s64)start > BPF_MAX_LOOPS) {
+ /* start <= end here, so end - start fits in a u32 without overflow */
+ if ((u32)(end - start) > BPF_MAX_LOOPS) {
s->cur = s->end = 0;
return -E2BIG;
}
@@ -802,12 +802,11 @@ __bpf_kfunc int *bpf_iter_num_next(struct bpf_iter_num* it)
{
struct bpf_iter_num_kern *s = (void *)it;
- /* check failed initialization or if we are done (same behavior);
- * need to be careful about overflow, so convert to s64 for checks,
- * e.g., if s->cur == s->end == INT_MAX, we can't just do
- * s->cur + 1 >= s->end
+ /*
+ * s->cur < s->end while iterating, else s->cur == s->end == 0; the signed
+ * s->cur + 1 >= s->end holds even when s->cur + 1 wraps (start == INT_MIN).
*/
- if ((s64)(s->cur + 1) >= s->end) {
+ if (s->cur + 1 >= s->end) {
s->cur = s->end = 0;
return NULL;
}
@@ -819,9 +818,7 @@ __bpf_kfunc int *bpf_iter_num_next(struct bpf_iter_num* it)
__bpf_kfunc void bpf_iter_num_destroy(struct bpf_iter_num *it)
{
- struct bpf_iter_num_kern *s = (void *)it;
-
- s->cur = s->end = 0;
+ /* no-op */
}
__bpf_kfunc_end_defs();
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 564071a92d7d..82c5988417a0 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -51,6 +51,9 @@ BTF_ID(func, bpf_lsm_key_getsecurity)
#ifdef CONFIG_AUDIT
BTF_ID(func, bpf_lsm_audit_rule_match)
#endif
+#ifdef CONFIG_SECURITY_NETWORK_XFRM
+BTF_ID(func, bpf_lsm_xfrm_decode_session)
+#endif
BTF_ID(func, bpf_lsm_ismaclabel)
BTF_ID(func, bpf_lsm_file_alloc_security)
BTF_SET_END(bpf_lsm_disabled_hooks)
@@ -183,7 +186,7 @@ static const struct bpf_func_proto bpf_ima_inode_hash_proto = {
.arg1_type = ARG_PTR_TO_BTF_ID,
.arg1_btf_id = &bpf_ima_inode_hash_btf_ids[0],
.arg2_type = ARG_PTR_TO_UNINIT_MEM,
- .arg3_type = ARG_CONST_SIZE,
+ .arg3_type = ARG_MEM_SIZE,
.allowed = bpf_ima_inode_hash_allowed,
};
@@ -202,7 +205,7 @@ static const struct bpf_func_proto bpf_ima_file_hash_proto = {
.arg1_type = ARG_PTR_TO_BTF_ID,
.arg1_btf_id = &bpf_ima_file_hash_btf_ids[0],
.arg2_type = ARG_PTR_TO_UNINIT_MEM,
- .arg3_type = ARG_CONST_SIZE,
+ .arg3_type = ARG_MEM_SIZE,
.allowed = bpf_ima_inode_hash_allowed,
};
@@ -292,7 +295,6 @@ BTF_ID(func, bpf_lsm_bpf_map_create)
BTF_ID(func, bpf_lsm_bpf_map_free)
BTF_ID(func, bpf_lsm_bpf_prog)
BTF_ID(func, bpf_lsm_bpf_prog_load)
-BTF_ID(func, bpf_lsm_bpf_prog_free)
BTF_ID(func, bpf_lsm_bpf_token_create)
BTF_ID(func, bpf_lsm_bpf_token_free)
BTF_ID(func, bpf_lsm_bpf_token_cmd)
diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
index 51b16e5f5534..d7c3030bc63b 100644
--- a/kernel/bpf/bpf_struct_ops.c
+++ b/kernel/bpf/bpf_struct_ops.c
@@ -147,6 +147,8 @@ void bpf_struct_ops_image_free(void *image)
#define MAYBE_NULL_SUFFIX "__nullable"
#define REFCOUNTED_SUFFIX "__ref"
+#define ARENA_SUFFIX "__arena"
+#define ARENA_MAYBE_NULL_SUFFIX "__arena__nullable"
/* Prepare argument info for every nullable argument of a member of a
* struct_ops type.
@@ -159,7 +161,7 @@ void bpf_struct_ops_image_free(void *image)
* to provide an array of struct bpf_ctx_arg_aux, which in turn provides
* the information that used by the verifier to check the arguments of the
* BPF struct_ops program assigned to the member. Here, we only care about
- * the arguments that are marked as __nullable.
+ * the arguments that are marked as __nullable, __ref or __arena.
*
* The array of struct bpf_ctx_arg_aux is eventually assigned to
* prog->aux->ctx_arg_info of BPF struct_ops programs and passed to the
@@ -172,10 +174,12 @@ static int prepare_arg_info(struct btf *btf,
const char *st_ops_name,
const char *member_name,
const struct btf_type *func_proto, void *stub_func_addr,
+ struct btf_func_model *model,
struct bpf_struct_ops_arg_info *arg_info)
{
const struct btf_type *stub_func_proto, *pointed_type;
- bool is_nullable = false, is_refcounted = false;
+ bool is_nullable = false, is_refcounted = false, is_arena = false;
+ bool is_arena_nullable = false;
const struct btf_param *stub_args, *args;
struct bpf_ctx_arg_aux *info, *info_buf;
u32 nargs, arg_no, info_cnt = 0;
@@ -225,27 +229,39 @@ static int prepare_arg_info(struct btf *btf,
/* Prepare info for every nullable argument */
info = info_buf;
for (arg_no = 0; arg_no < nargs; arg_no++) {
- /* Skip arguments that is not suffixed with
- * "__nullable or __ref".
+ bool ptr_to_arena, ptr_to_struct;
+
+ /*
+ * Skip arguments that are not suffixed with "__arena__nullable",
+ * "__arena", "__nullable", or "__ref".
*/
- is_nullable = btf_param_match_suffix(btf, &stub_args[arg_no],
- MAYBE_NULL_SUFFIX);
+ is_arena_nullable = btf_param_match_suffix(btf, &stub_args[arg_no],
+ ARENA_MAYBE_NULL_SUFFIX);
+ is_arena = btf_param_match_suffix(btf, &stub_args[arg_no], ARENA_SUFFIX);
+ is_nullable = !is_arena_nullable &&
+ btf_param_match_suffix(btf, &stub_args[arg_no], MAYBE_NULL_SUFFIX);
is_refcounted = btf_param_match_suffix(btf, &stub_args[arg_no],
REFCOUNTED_SUFFIX);
- if (is_nullable)
+ if (is_arena_nullable)
+ suffix = ARENA_MAYBE_NULL_SUFFIX;
+ else if (is_arena)
+ suffix = ARENA_SUFFIX;
+ else if (is_nullable)
suffix = MAYBE_NULL_SUFFIX;
else if (is_refcounted)
suffix = REFCOUNTED_SUFFIX;
else
continue;
- /* Should be a pointer to struct */
- pointed_type = btf_type_resolve_ptr(btf,
- args[arg_no].type,
- &arg_btf_id);
- if (!pointed_type ||
- !btf_type_is_struct(pointed_type)) {
+ /*
+ * Should be a pointer to struct, or any pointer for __arena or
+ * __arena__nullable.
+ */
+ pointed_type = btf_type_resolve_ptr(btf, args[arg_no].type, &arg_btf_id);
+ ptr_to_arena = pointed_type && (is_arena || is_arena_nullable);
+ ptr_to_struct = pointed_type && btf_type_is_struct(pointed_type);
+ if (!ptr_to_arena && !ptr_to_struct) {
pr_warn("stub function %s has %s tagging to an unsupported type\n",
stub_fname, suffix);
goto err_out;
@@ -268,7 +284,18 @@ static int prepare_arg_info(struct btf *btf,
info->btf_id = arg_btf_id;
info->btf = btf;
info->offset = offset;
- if (is_nullable) {
+ if (is_arena || is_arena_nullable) {
+ /*
+ * Both types get PTR_TO_ARENA. In verifier state,
+ * PTR_TO_ARENA encompasses potential NULL values, but
+ * we do not force the program to check it, or maintain
+ * precision around it, since it has no safety implication.
+ */
+ info->reg_type = PTR_TO_ARENA;
+ model->arg_flags[arg_no] |= BTF_FMODEL_ARENA_ARG;
+ if (is_arena_nullable)
+ model->arg_flags[arg_no] |= BTF_FMODEL_NULLABLE_ARG;
+ } else if (is_nullable) {
info->reg_type = PTR_TRUSTED | PTR_TO_BTF_ID | PTR_MAYBE_NULL;
} else if (is_refcounted) {
info->reg_type = PTR_TRUSTED | PTR_TO_BTF_ID;
@@ -445,9 +472,22 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
goto errout;
}
+ /*
+ * A >8 byte return value is passed back in a register pair,
+ * which the struct_ops trampoline does not preserve (only
+ * 8 bytes of the return value are saved and restored).
+ */
+ if (st_ops->func_models[i].ret_size > 8) {
+ pr_warn("func ptr %s in struct %s has a >8 byte return value, which is not supported\n",
+ mname, st_ops->name);
+ err = -EOPNOTSUPP;
+ goto errout;
+ }
+
stub_func_addr = *(void **)(st_ops->cfi_stubs + moff);
err = prepare_arg_info(btf, st_ops->name, mname,
func_proto, stub_func_addr,
+ &st_ops->func_models[i],
arg_info + i);
if (err)
goto errout;
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 15ae7c43f594..d870bc5e50bc 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -28,6 +28,7 @@
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/overflow.h>
+#include <linux/bitops.h>
#include <net/netfilter/nf_bpf_link.h>
@@ -1168,19 +1169,19 @@ static const char *btf_show_name(struct btf_show *show)
id = t->type;
break;
default:
- id = 0;
- break;
+ goto resolved;
}
+ t = btf_type_skip_qualifiers(show->btf, id);
if (!id)
break;
- t = btf_type_skip_qualifiers(show->btf, id);
}
/* We may not be able to represent this type; bail to be safe */
if (i == BTF_SHOW_MAX_ITER)
return "";
+resolved:
if (!name)
- name = btf_name_by_offset(show->btf, t->name_off);
+ name = btf_type_is_void(t) ? "void" : btf_name_by_offset(show->btf, t->name_off);
switch (BTF_INFO_KIND(t->info)) {
case BTF_KIND_STRUCT:
@@ -2533,7 +2534,6 @@ static void btf_bitfield_show(void *data, u8 bits_offset,
btf_int128_print(show, print_num);
}
-
static void btf_int_bits_show(const struct btf *btf,
const struct btf_type *t,
void *data, u8 bits_offset,
@@ -2911,14 +2911,29 @@ static void btf_modifier_show(const struct btf *btf,
else
t = btf_type_skip_modifiers(btf, type_id, NULL);
- btf_type_ops(t)->show(btf, t, type_id, data, bits_offset, show);
+ /*
+ * A modifier can resolve to void, which has no show op; print a
+ * placeholder rather than dereferencing NULL.
+ */
+ if (!btf_type_ops(t))
+ btf_df_show(btf, t, type_id, data, bits_offset, show);
+ else
+ btf_type_ops(t)->show(btf, t, type_id, data, bits_offset, show);
}
static void btf_var_show(const struct btf *btf, const struct btf_type *t,
u32 type_id, void *data, u8 bits_offset,
struct btf_show *show)
{
- t = btf_type_id_resolve(btf, &type_id);
+ /*
+ * btf_type_id_resolve() dereferences btf->resolved_ids, which is NULL
+ * for a base BTF (e.g. the vmlinux BTF that bpf_snprintf_btf() uses).
+ * Resolve the var's type directly in that case.
+ */
+ if (btf->resolved_ids)
+ t = btf_type_id_resolve(btf, &type_id);
+ else
+ t = btf_type_skip_modifiers(btf, t->type, &type_id);
btf_type_ops(t)->show(btf, t, type_id, data, bits_offset, show);
}
@@ -3472,12 +3487,69 @@ static int btf_find_struct(const struct btf *btf, const struct btf_type *t,
return BTF_FIELD_FOUND;
}
+struct btf_type_tag_match {
+ const char *name;
+ u32 flag;
+};
+
+struct btf_type_tag_walk_ctx {
+ const struct btf_type *t; /* Input/Output */
+ u32 id; /* Output */
+ u32 res; /* Output */
+};
+
+static int btf_type_tag_walk(const struct btf *btf,
+ struct btf_type_tag_walk_ctx *ctx,
+ const struct btf_type_tag_match *matches,
+ u32 match_cnt)
+{
+ const struct btf_type *t = ctx->t;
+ u32 res = 0;
+ const char *tag;
+ u32 id, i;
+
+ do {
+ id = t->type;
+ t = btf_type_by_id(btf, id);
+
+ if (!btf_type_is_modifier(t))
+ break;
+
+ if (!btf_type_is_type_tag(t) || btf_type_kflag(t))
+ continue;
+
+ tag = __btf_name_by_offset(btf, t->name_off);
+ for (i = 0; i < match_cnt; i++) {
+ if (strcmp(tag, matches[i].name))
+ continue;
+ res |= matches[i].flag;
+ break;
+ }
+ } while (true);
+
+ /* We only support a single tag. */
+ if (hweight32(res) > 1)
+ return -EINVAL;
+
+ ctx->t = t;
+ ctx->id = id;
+ ctx->res = res;
+
+ return 0;
+}
+
static int btf_find_kptr(const struct btf *btf, const struct btf_type *t,
u32 off, int sz, struct btf_field_info *info, u32 field_mask)
{
- enum btf_field_type type;
- const char *tag_value;
- bool is_type_tag;
+ static const struct btf_type_tag_match kptr_type_tags[] = {
+ { "kptr_untrusted", BPF_KPTR_UNREF },
+ { "kptr", BPF_KPTR_REF },
+ { "percpu_kptr", BPF_KPTR_PERCPU },
+ { "uptr", BPF_UPTR },
+ };
+ struct btf_type_tag_walk_ctx ctx;
+ enum btf_field_type type = 0;
+ int err;
u32 res_id;
/* Permit modifiers on the pointer itself */
@@ -3486,30 +3558,20 @@ static int btf_find_kptr(const struct btf *btf, const struct btf_type *t,
/* For PTR, sz is always == 8 */
if (!btf_type_is_ptr(t))
return BTF_FIELD_IGNORE;
- t = btf_type_by_id(btf, t->type);
- is_type_tag = btf_type_is_type_tag(t) && !btf_type_kflag(t);
- if (!is_type_tag)
- return BTF_FIELD_IGNORE;
- /* Reject extra tags */
- if (btf_type_is_type_tag(btf_type_by_id(btf, t->type)))
- return -EINVAL;
- tag_value = __btf_name_by_offset(btf, t->name_off);
- if (!strcmp("kptr_untrusted", tag_value))
- type = BPF_KPTR_UNREF;
- else if (!strcmp("kptr", tag_value))
- type = BPF_KPTR_REF;
- else if (!strcmp("percpu_kptr", tag_value))
- type = BPF_KPTR_PERCPU;
- else if (!strcmp("uptr", tag_value))
- type = BPF_UPTR;
- else
- return -EINVAL;
+
+ ctx.t = t;
+ err = btf_type_tag_walk(btf, &ctx, kptr_type_tags,
+ ARRAY_SIZE(kptr_type_tags));
+ if (err)
+ return err;
+
+ t = ctx.t;
+ res_id = ctx.id;
+ type = ctx.res;
if (!(type & field_mask))
return BTF_FIELD_IGNORE;
- /* Get the base type */
- t = btf_type_skip_modifiers(btf, t->type, &res_id);
/* Only pointer to struct is allowed */
if (!__btf_type_is_struct(t))
return -EINVAL;
@@ -3621,7 +3683,7 @@ static int btf_get_field_type(const struct btf *btf, const struct btf_type *var_
{ BPF_LIST_NODE, "bpf_list_node", false },
{ BPF_RB_ROOT, "bpf_rb_root", false },
{ BPF_RB_NODE, "bpf_rb_node", false },
- { BPF_REFCOUNT, "bpf_refcount", false },
+ { BPF_REFCOUNT, "bpf_refcount", true },
};
int type = 0, i;
const char *name = __btf_name_by_offset(btf, var_type->name_off);
@@ -3703,7 +3765,7 @@ static int btf_repeat_fields(struct btf_field_info *info, int info_cnt,
static int btf_find_struct_field(const struct btf *btf,
const struct btf_type *t, u32 field_mask,
struct btf_field_info *info, int info_cnt,
- u32 level);
+ u32 level, u32 *seen_mask);
/* Find special fields in the struct type of a field.
*
@@ -3714,7 +3776,7 @@ static int btf_find_struct_field(const struct btf *btf,
static int btf_find_nested_struct(const struct btf *btf, const struct btf_type *t,
u32 off, u32 nelems,
u32 field_mask, struct btf_field_info *info,
- int info_cnt, u32 level)
+ int info_cnt, u32 level, u32 *seen_mask)
{
int ret, err, i;
@@ -3722,7 +3784,7 @@ static int btf_find_nested_struct(const struct btf *btf, const struct btf_type *
if (level >= MAX_RESOLVE_DEPTH)
return -E2BIG;
- ret = btf_find_struct_field(btf, t, field_mask, info, info_cnt, level);
+ ret = btf_find_struct_field(btf, t, field_mask, info, info_cnt, level, seen_mask);
if (ret <= 0)
return ret;
@@ -3779,7 +3841,7 @@ static int btf_find_field_one(const struct btf *btf,
if (expected_size && expected_size != sz * nelems)
return 0;
ret = btf_find_nested_struct(btf, var_type, off, nelems, field_mask,
- &info[0], info_cnt, level);
+ &info[0], info_cnt, level, seen_mask);
return ret;
}
@@ -3844,11 +3906,11 @@ static int btf_find_field_one(const struct btf *btf,
static int btf_find_struct_field(const struct btf *btf,
const struct btf_type *t, u32 field_mask,
struct btf_field_info *info, int info_cnt,
- u32 level)
+ u32 level, u32 *seen_mask)
{
int ret, idx = 0;
const struct btf_member *member;
- u32 i, off, seen_mask = 0;
+ u32 i, off;
for_each_member(i, t, member) {
const struct btf_type *member_type = btf_type_by_id(btf,
@@ -3862,7 +3924,7 @@ static int btf_find_struct_field(const struct btf *btf,
ret = btf_find_field_one(btf, t, member_type, i,
off, 0,
- field_mask, &seen_mask,
+ field_mask, seen_mask,
&info[idx], info_cnt - idx, level);
if (ret < 0)
return ret;
@@ -3873,11 +3935,11 @@ static int btf_find_struct_field(const struct btf *btf,
static int btf_find_datasec_var(const struct btf *btf, const struct btf_type *t,
u32 field_mask, struct btf_field_info *info,
- int info_cnt, u32 level)
+ int info_cnt, u32 level, u32 *seen_mask)
{
int ret, idx = 0;
const struct btf_var_secinfo *vsi;
- u32 i, off, seen_mask = 0;
+ u32 i, off;
for_each_vsi(i, t, vsi) {
const struct btf_type *var = btf_type_by_id(btf, vsi->type);
@@ -3885,7 +3947,7 @@ static int btf_find_datasec_var(const struct btf *btf, const struct btf_type *t,
off = vsi->offset;
ret = btf_find_field_one(btf, var, var_type, -1, off, vsi->size,
- field_mask, &seen_mask,
+ field_mask, seen_mask,
&info[idx], info_cnt - idx,
level);
if (ret < 0)
@@ -3899,10 +3961,12 @@ static int btf_find_field(const struct btf *btf, const struct btf_type *t,
u32 field_mask, struct btf_field_info *info,
int info_cnt)
{
+ u32 seen_mask = 0;
+
if (__btf_type_is_struct(t))
- return btf_find_struct_field(btf, t, field_mask, info, info_cnt, 0);
+ return btf_find_struct_field(btf, t, field_mask, info, info_cnt, 0, &seen_mask);
else if (btf_type_is_datasec(t))
- return btf_find_datasec_var(btf, t, field_mask, info, info_cnt, 0);
+ return btf_find_datasec_var(btf, t, field_mask, info, info_cnt, 0, &seen_mask);
return -EINVAL;
}
@@ -4120,7 +4184,7 @@ struct btf_record *btf_parse_fields(const struct btf *btf, const struct btf_type
rec->spin_lock_off = rec->fields[i].offset;
break;
case BPF_RES_SPIN_LOCK:
- WARN_ON_ONCE(rec->spin_lock_off >= 0);
+ WARN_ON_ONCE(rec->res_spin_lock_off >= 0);
/* Cache offset for faster lookup at runtime */
rec->res_spin_lock_off = rec->fields[i].offset;
break;
@@ -4204,13 +4268,10 @@ int btf_check_and_fixup_fields(const struct btf *btf, struct btf_record *rec)
{
int i;
- /* There are three types that signify ownership of some other type:
- * kptr_ref, bpf_list_head, bpf_rb_root.
- * kptr_ref only supports storing kernel types, which can't store
- * references to program allocated local types.
- *
- * Hence we only need to ensure that bpf_{list_head,rb_root} ownership
- * does not form cycles.
+ /*
+ * Check fields which require the complete BTF and initialize runtime
+ * metadata. Ownership relationships are validated after every record has
+ * been fixed up.
*/
if (IS_ERR_OR_NULL(rec) || !(rec->field_mask & (BPF_GRAPH_ROOT | BPF_UPTR)))
return 0;
@@ -4241,51 +4302,88 @@ int btf_check_and_fixup_fields(const struct btf *btf, struct btf_record *rec)
if (!meta)
return -EFAULT;
rec->fields[i].graph_root.value_rec = meta->record;
+ }
+ return 0;
+}
- /* We need to set value_rec for all root types, but no need
- * to check ownership cycle for a type unless it's also a
- * node type.
- */
- if (!(rec->field_mask & BPF_GRAPH_NODE))
+static int btf_owned_type_idx(const struct btf *btf, struct btf_struct_metas *tab,
+ const struct btf_field *field)
+{
+ struct btf_struct_meta *meta;
+ u32 btf_id;
+
+ if (field->type & BPF_GRAPH_ROOT) {
+ btf_id = field->graph_root.value_btf_id;
+ } else if (field->type == BPF_KPTR_REF || field->type == BPF_KPTR_PERCPU) {
+ if (btf_is_kernel(field->kptr.btf))
+ return -ENOENT;
+ btf_id = field->kptr.btf_id;
+ } else {
+ return -ENOENT;
+ }
+
+ meta = btf_find_struct_meta(btf, btf_id);
+ if (!meta)
+ return field->type & BPF_GRAPH_ROOT ? -EFAULT : -ENOENT;
+ return meta - tab->types;
+}
+
+/*
+ * Each ownership edge adds kernel frames through bpf_obj_free_fields() and
+ * __bpf_obj_drop_impl(). Keep the bound deliberately small because object
+ * destruction can itself run below a BPF call chain. A final pointee without
+ * special fields is not present in the struct metadata table and adds only a
+ * non-recursing drop.
+ */
+#define BTF_MAX_OWNERSHIP_DEPTH 8
+
+static int btf_ownership_depth(const struct btf *btf,
+ struct btf_struct_metas *tab, u8 *depth,
+ int idx, int depth_left)
+{
+ const struct btf_record *rec = tab->types[idx].record;
+ int i, ret, max_depth = 0;
+
+ if (!depth_left)
+ return -ELOOP;
+ if (depth[idx])
+ goto done;
+
+ for (i = 0; i < rec->cnt; i++) {
+ ret = btf_owned_type_idx(btf, tab, &rec->fields[i]);
+ if (ret == -ENOENT)
continue;
+ if (ret < 0)
+ return ret;
+ ret = btf_ownership_depth(btf, tab, depth, ret, depth_left - 1);
+ if (ret < 0)
+ return ret;
+ max_depth = max(max_depth, ret);
+ }
+ depth[idx] = max_depth + 1;
+done:
+ return depth[idx] > depth_left ? -ELOOP : depth[idx];
+}
- /* We need to ensure ownership acyclicity among all types. The
- * proper way to do it would be to topologically sort all BTF
- * IDs based on the ownership edges, since there can be multiple
- * bpf_{list_head,rb_node} in a type. Instead, we use the
- * following resaoning:
- *
- * - A type can only be owned by another type in user BTF if it
- * has a bpf_{list,rb}_node. Let's call these node types.
- * - A type can only _own_ another type in user BTF if it has a
- * bpf_{list_head,rb_root}. Let's call these root types.
- *
- * We ensure that if a type is both a root and node, its
- * element types cannot be root types.
- *
- * To ensure acyclicity:
- *
- * When A is an root type but not a node, its ownership
- * chain can be:
- * A -> B -> C
- * Where:
- * - A is an root, e.g. has bpf_rb_root.
- * - B is both a root and node, e.g. has bpf_rb_node and
- * bpf_list_head.
- * - C is only an root, e.g. has bpf_list_node
- *
- * When A is both a root and node, some other type already
- * owns it in the BTF domain, hence it can not own
- * another root type through any of the ownership edges.
- * A -> B
- * Where:
- * - A is both an root and node.
- * - B is only an node.
- */
- if (meta->record->field_mask & BPF_GRAPH_ROOT)
- return -ELOOP;
+static int btf_check_ownership_depth(const struct btf *btf,
+ struct btf_struct_metas *tab)
+{
+ u8 *depth;
+ int i, ret = 0;
+
+ depth = kvcalloc(tab->cnt, sizeof(*depth), GFP_KERNEL | __GFP_NOWARN);
+ if (!depth)
+ return -ENOMEM;
+
+ for (i = 0; i < tab->cnt; i++) {
+ ret = btf_ownership_depth(btf, tab, depth, i,
+ BTF_MAX_OWNERSHIP_DEPTH);
+ if (ret < 0)
+ break;
+ ret = 0;
}
- return 0;
+ kvfree(depth);
+ return ret;
}
static void __btf_struct_show(const struct btf *btf, const struct btf_type *t,
@@ -5859,11 +5957,10 @@ struct btf_struct_meta *btf_find_struct_meta(const struct btf *btf, u32 btf_id)
return bsearch(&btf_id, tab->types, tab->cnt, sizeof(tab->types[0]), btf_id_cmp_func);
}
-static int btf_check_type_tags(struct btf_verifier_env *env,
- struct btf *btf, int start_id)
+static int btf_check_modifier_chain_length(struct btf_verifier_env *env,
+ struct btf *btf, int start_id)
{
int i, n, good_id = start_id - 1;
- bool in_tags;
n = btf_nr_types(btf);
for (i = start_id; i < n; i++) {
@@ -5879,20 +5976,12 @@ static int btf_check_type_tags(struct btf_verifier_env *env,
cond_resched();
- in_tags = btf_type_is_type_tag(t);
while (btf_type_is_modifier(t)) {
if (!chain_limit--) {
btf_verifier_log(env, "Max chain length or cycle detected");
return -ELOOP;
}
- if (btf_type_is_type_tag(t)) {
- if (!in_tags) {
- btf_verifier_log(env, "Type tags don't precede modifiers");
- return -EINVAL;
- }
- } else if (in_tags) {
- in_tags = false;
- }
+
if (cur_id <= good_id)
break;
/* Move to next type */
@@ -5970,7 +6059,7 @@ static struct btf *btf_parse(const union bpf_attr *attr, bpfptr_t uattr,
if (err)
goto errout;
- err = btf_check_type_tags(env, btf, 1);
+ err = btf_check_modifier_chain_length(env, btf, 1);
if (err)
goto errout;
@@ -5989,6 +6078,10 @@ static struct btf *btf_parse(const union bpf_attr *attr, bpfptr_t uattr,
if (err < 0)
goto errout_meta;
}
+
+ err = btf_check_ownership_depth(btf, struct_meta_tab);
+ if (err < 0)
+ goto errout_meta;
}
err = bpf_log_attr_finalize(attr_log, &env->log);
@@ -6378,7 +6471,7 @@ static struct btf *btf_parse_base(struct btf_verifier_env *env, const char *name
if (err)
goto errout;
- err = btf_check_type_tags(env, btf, 1);
+ err = btf_check_modifier_chain_length(env, btf, 1);
if (err)
goto errout;
@@ -6412,7 +6505,7 @@ struct btf *btf_parse_vmlinux(void)
if (IS_ERR(btf))
goto err_out;
- /* btf_parse_vmlinux() runs under bpf_verifier_lock */
+ /* btf_parse_vmlinux() runs under btf_vmlinux_lock */
bpf_ctx_convert.t = btf_type_by_id(btf, bpf_ctx_convert_btf_id[0]);
err = btf_alloc_id(btf);
if (err) {
@@ -6428,7 +6521,7 @@ err_out:
* split BTF ids will need to be mapped to actual base/split ids for
* BTF now that it has been relocated.
*/
-static __u32 btf_relocate_id(const struct btf *btf, __u32 id)
+__u32 btf_relocate_id(const struct btf *btf, __u32 id)
{
if (!btf->base_btf || !btf->base_id_map)
return id;
@@ -6504,7 +6597,7 @@ static struct btf *btf_parse_module(const char *module_name, const void *data,
if (err)
goto errout;
- err = btf_check_type_tags(env, btf, btf_nr_types(base_btf));
+ err = btf_check_modifier_chain_length(env, btf, btf_nr_types(base_btf));
if (err)
goto errout;
@@ -6617,6 +6710,10 @@ struct bpf_raw_tp_null_args {
static const struct bpf_raw_tp_null_args raw_tp_null_args[] = {
/* sched */
{ "sched_pi_setprio", 0x10 },
+ /*
+ * do_wait() passes NULL for wait4(-1) and waitid(P_ALL).
+ */
+ { "sched_process_wait", 0x1 },
/* ... from sched_numa_pair_template event class */
{ "sched_stick_numa", 0x100 },
{ "sched_swap_numa", 0x100 },
@@ -6637,13 +6734,6 @@ static const struct bpf_raw_tp_null_args raw_tp_null_args[] = {
{ "cachefiles_mark_inactive", 0x1 },
{ "cachefiles_vfs_error", 0x1 },
{ "cachefiles_io_error", 0x1 },
- { "cachefiles_ondemand_open", 0x1 },
- { "cachefiles_ondemand_copen", 0x1 },
- { "cachefiles_ondemand_close", 0x1 },
- { "cachefiles_ondemand_read", 0x1 },
- { "cachefiles_ondemand_cread", 0x1 },
- { "cachefiles_ondemand_fd_write", 0x1 },
- { "cachefiles_ondemand_fd_release", 0x1 },
/* ext4, from ext4__mballoc event class */
{ "ext4_mballoc_discard", 0x10 },
{ "ext4_mballoc_free", 0x10 },
@@ -6684,6 +6774,9 @@ static const struct bpf_raw_tp_null_args raw_tp_null_args[] = {
{ "rxrpc_resend", 0x10 },
{ "rxrpc_tq", 0x10 },
{ "rxrpc_client", 0x1 },
+ /* signal */
+ { "signal_generate", 0x20 },
+ { "signal_deliver", 0x20 },
/* skb */
{"kfree_skb", 0x1000},
/* sunrpc */
@@ -6810,14 +6903,18 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
const struct bpf_prog *prog,
struct bpf_insn_access_aux *info)
{
+ static const struct btf_type_tag_match ctx_type_tags[] = {
+ { "user", MEM_USER },
+ { "percpu", MEM_PERCPU },
+ };
const struct btf_type *t = prog->aux->attach_func_proto;
struct bpf_prog *tgt_prog = prog->aux->dst_prog;
struct btf *btf = bpf_prog_get_target_btf(prog);
const char *tname = prog->aux->attach_func_name;
struct bpf_verifier_log *log = info->log;
+ struct btf_type_tag_walk_ctx ctx;
const struct btf_param *args;
bool ptr_err_raw_tp = false;
- const char *tag_value;
u32 nr_args, arg;
int i, ret;
@@ -6918,15 +7015,19 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
return false;
}
- /* check for PTR_TO_RDONLY_BUF_OR_NULL or PTR_TO_RDWR_BUF_OR_NULL */
+ /*
+ * Check for PTR_TO_RDONLY_BUF_OR_NULL, PTR_TO_RDWR_BUF_OR_NULL or
+ * PTR_TO_ARENA (both nullable and non-nullable cases).
+ */
for (i = 0; i < prog->aux->ctx_arg_info_size; i++) {
const struct bpf_ctx_arg_aux *ctx_arg_info = &prog->aux->ctx_arg_info[i];
u32 type, flag;
type = base_type(ctx_arg_info->reg_type);
flag = type_flag(ctx_arg_info->reg_type);
- if (ctx_arg_info->offset == off && type == PTR_TO_BUF &&
- (flag & PTR_MAYBE_NULL)) {
+ if (ctx_arg_info->offset == off &&
+ (type == PTR_TO_ARENA ||
+ (type == PTR_TO_BUF && (flag & PTR_MAYBE_NULL)))) {
info->reg_type = ctx_arg_info->reg_type;
return true;
}
@@ -7020,22 +7121,18 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
}
info->btf = btf;
- info->btf_id = t->type;
- t = btf_type_by_id(btf, t->type);
-
- if (btf_type_is_type_tag(t) && !btf_type_kflag(t)) {
- tag_value = __btf_name_by_offset(btf, t->name_off);
- if (strcmp(tag_value, "user") == 0)
- info->reg_type |= MEM_USER;
- if (strcmp(tag_value, "percpu") == 0)
- info->reg_type |= MEM_PERCPU;
+ ctx.t = t;
+ ret = btf_type_tag_walk(btf, &ctx, ctx_type_tags,
+ ARRAY_SIZE(ctx_type_tags));
+ if (ret) {
+ bpf_log(log, "func '%s' arg%d type %s has multiple type tags\n",
+ tname, arg, btf_type_str(t));
+ return false;
}
+ info->reg_type |= ctx.res;
+ info->btf_id = ctx.id;
+ t = ctx.t;
- /* skip modifiers */
- while (btf_type_is_modifier(t)) {
- info->btf_id = t->type;
- t = btf_type_by_id(btf, t->type);
- }
if (!btf_type_is_struct(t)) {
bpf_log(log,
"func '%s' arg%d type %s is not a struct\n",
@@ -7069,12 +7166,12 @@ enum bpf_struct_walk_result {
static int btf_struct_walk(struct bpf_verifier_log *log, const struct btf *btf,
const struct btf_type *t, int off, int size,
u32 *next_btf_id, enum bpf_type_flag *flag,
- const char **field_name)
+ const char **field_name, bool walk_flex_arrays)
{
u32 i, moff, mtrue_end, msize = 0, total_nelems = 0;
const struct btf_type *mtype, *elem_type = NULL;
const struct btf_member *member;
- const char *tname, *mname, *tag_value;
+ const char *tname, *mname;
u32 vlen, elem_id, mid;
again:
@@ -7096,11 +7193,14 @@ again:
*flag |= PTR_UNTRUSTED;
if (off + size > t->size) {
+ struct btf_array *array_elem;
+
+ if (!walk_flex_arrays)
+ goto error;
+
/* If the last element is a variable size array, we may
* need to relax the rule.
*/
- struct btf_array *array_elem;
-
if (vlen == 0)
goto error;
@@ -7125,7 +7225,7 @@ again:
if (btf_type_is_int(t))
return WALK_SCALAR;
- if (!btf_type_is_struct(t))
+ if (!btf_type_is_struct(t) || !t->size)
goto error;
off = (off - moff) % t->size;
@@ -7270,8 +7370,15 @@ error:
}
if (btf_type_is_ptr(mtype)) {
- const struct btf_type *stype, *t;
+ static const struct btf_type_tag_match walk_type_tags[] = {
+ { "user", MEM_USER },
+ { "percpu", MEM_PERCPU },
+ { "rcu", MEM_RCU },
+ };
enum bpf_type_flag tmp_flag = 0;
+ struct btf_type_tag_walk_ctx ctx = { .t = mtype };
+ const struct btf_type *stype;
+ int err;
u32 id;
if (msize != size || off != moff) {
@@ -7281,22 +7388,17 @@ error:
return -EACCES;
}
- /* check type tag */
- t = btf_type_by_id(btf, mtype->type);
- if (btf_type_is_type_tag(t) && !btf_type_kflag(t)) {
- tag_value = __btf_name_by_offset(btf, t->name_off);
- /* check __user tag */
- if (strcmp(tag_value, "user") == 0)
- tmp_flag = MEM_USER;
- /* check __percpu tag */
- if (strcmp(tag_value, "percpu") == 0)
- tmp_flag = MEM_PERCPU;
- /* check __rcu tag */
- if (strcmp(tag_value, "rcu") == 0)
- tmp_flag = MEM_RCU;
+ err = btf_type_tag_walk(btf, &ctx, walk_type_tags,
+ ARRAY_SIZE(walk_type_tags));
+ if (err) {
+ bpf_log(log, "type '%s' has multiple type tags\n",
+ btf_type_str(mtype));
+ return err;
}
+ tmp_flag = ctx.res;
+ id = ctx.id;
+ stype = ctx.t;
- stype = btf_type_skip_modifiers(btf, mtype->type, &id);
if (btf_type_is_struct(stype)) {
*next_btf_id = id;
*flag |= tmp_flag;
@@ -7363,7 +7465,8 @@ int btf_struct_access(struct bpf_verifier_log *log,
t = btf_type_by_id(btf, id);
do {
- err = btf_struct_walk(log, btf, t, off, size, &id, &tmp_flag, field_name);
+ err = btf_struct_walk(log, btf, t, off, size, &id, &tmp_flag,
+ field_name, !type_is_alloc(reg->type));
switch (err) {
case WALK_PTR:
@@ -7422,7 +7525,7 @@ bool btf_types_are_same(const struct btf *btf1, u32 id1,
bool btf_struct_ids_match(struct bpf_verifier_log *log,
const struct btf *btf, u32 id, int off,
const struct btf *need_btf, u32 need_type_id,
- bool strict)
+ bool strict, bool walk_flex_arrays)
{
const struct btf_type *type;
enum bpf_type_flag flag = 0;
@@ -7441,7 +7544,8 @@ again:
type = btf_type_by_id(btf, id);
if (!type)
return false;
- err = btf_struct_walk(log, btf, type, off, 1, &id, &flag, NULL);
+ err = btf_struct_walk(log, btf, type, off, 1, &id, &flag, NULL,
+ walk_flex_arrays);
if (err != WALK_STRUCT)
return false;
@@ -7485,14 +7589,28 @@ static u8 __get_type_fmodel_flags(const struct btf_type *t)
{
u8 flags = 0;
- if (btf_type_is_struct(t))
- flags |= BTF_FMODEL_STRUCT_ARG;
if (btf_type_is_signed_int(t))
flags |= BTF_FMODEL_SIGNED_ARG;
return flags;
}
+static u8 __get_arg_fmodel_flags(const struct btf *btf,
+ const struct btf_param *arg,
+ const struct btf_type *t)
+{
+ u8 flags = __get_type_fmodel_flags(t);
+
+ if (btf_param_match_suffix(btf, arg, "__arena__nullable"))
+ flags |= BTF_FMODEL_ARENA_ARG | BTF_FMODEL_NULLABLE_ARG;
+ else if (btf_param_match_suffix(btf, arg, "__arena"))
+ flags |= BTF_FMODEL_ARENA_ARG;
+ else if (btf_param_match_suffix(btf, arg, "__nullable"))
+ flags |= BTF_FMODEL_NULLABLE_ARG;
+
+ return flags;
+}
+
int btf_distill_func_proto(struct bpf_verifier_log *log,
struct btf *btf,
const struct btf_type *func,
@@ -7558,7 +7676,7 @@ int btf_distill_func_proto(struct bpf_verifier_log *log,
return -EINVAL;
}
m->arg_size[i] = ret;
- m->arg_flags[i] = __get_type_fmodel_flags(t);
+ m->arg_flags[i] = __get_arg_fmodel_flags(btf, &args[i], t);
}
m->nr_args = nargs;
return 0;
@@ -7867,7 +7985,12 @@ static int btf_scan_type_tags(struct bpf_verifier_env *env,
const struct btf *btf, u32 type_id,
u32 *tags)
{
+ static const struct btf_type_tag_match func_type_tags[] = {
+ { "arena", ARG_TAG_ARENA },
+ };
+ struct btf_type_tag_walk_ctx ctx;
const struct btf_type *t;
+ int err;
/* Find the first pointer type in the chain. */
t = btf_type_skip_modifiers(btf, type_id, NULL);
@@ -7879,24 +8002,15 @@ static int btf_scan_type_tags(struct bpf_verifier_env *env,
if (!t || !btf_type_is_ptr(t))
return 0;
- /* We got a pointer, get all associated type tags. */
- for (t = btf_type_by_id(btf, t->type); t && btf_type_is_modifier(t);
- t = btf_type_by_id(btf, t->type)) {
-
- /* Skip non-type tag modifiers. */
- if (!btf_type_is_type_tag(t))
- continue;
-
- const char *tag = __btf_name_by_offset(btf, t->name_off);
-
- if (strcmp(tag, "arena") == 0) {
- *tags |= ARG_TAG_ARENA;
- } else {
- bpf_log(&env->log, "function signature member has unsupported type tag '%s'\n",
- tag);
- return -EOPNOTSUPP;
- }
+ ctx.t = t;
+ err = btf_type_tag_walk(btf, &ctx, func_type_tags,
+ ARRAY_SIZE(func_type_tags));
+ if (err) {
+ bpf_log(&env->log,
+ "function signature member has multiple type tags\n");
+ return err;
}
+ *tags |= ctx.res;
return 0;
}
@@ -8255,6 +8369,16 @@ int btf_type_snprintf_show(const struct btf *btf, u32 type_id, void *obj,
return ssnprintf.len;
}
+int btf_type_name_to_buf(const struct btf *btf, u32 type_id, char *buf, int len)
+{
+ struct btf_show show = {
+ .btf = btf,
+ .state.type_id = type_id,
+ };
+
+ return snprintf(buf, len, "%s", btf_show_name(&show));
+}
+
#ifdef CONFIG_PROC_FS
static void bpf_btf_show_fdinfo(struct seq_file *m, struct file *filp)
{
@@ -8593,7 +8717,7 @@ struct module *btf_try_get_module(const struct btf *btf)
/* Returns struct btf corresponding to the struct module.
* This function can return NULL or ERR_PTR.
*/
-static struct btf *btf_get_module_btf(const struct module *module)
+struct btf *btf_get_module_btf(const struct module *module)
{
#ifdef CONFIG_DEBUG_INFO_BTF_MODULES
struct btf_module *btf_mod, *tmp;
@@ -8663,9 +8787,10 @@ BPF_CALL_4(bpf_btf_find_by_name_kind, char *, name, int, name_sz, u32, kind, int
const struct bpf_func_proto bpf_btf_find_by_name_kind_proto = {
.func = bpf_btf_find_by_name_kind,
.gpl_only = false,
+ .might_sleep = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg2_type = ARG_CONST_SIZE,
+ .arg2_type = ARG_MEM_SIZE,
.arg3_type = ARG_ANYTHING,
.arg4_type = ARG_ANYTHING,
};
@@ -9077,6 +9202,35 @@ u32 *btf_kfunc_flags(const struct btf *btf, u32 kfunc_btf_id, const struct bpf_p
return btf_kfunc_id_set_contains(btf, hook, kfunc_btf_id);
}
+/*
+ * Check a single KF_* @flag on a kfunc across all of its hook sets.
+ * Returns:
+ * * 1 if @flag is set
+ * * 0 if @flag is not set
+ * * -EINVAL if @flag is set inconsistently across the sets
+ * * -ENOENT if kfunc_btf_id is not a registered kfunc
+ */
+int btf_kfunc_check_flag(const struct btf *btf, u32 kfunc_btf_id, u32 flag)
+{
+ enum btf_kfunc_hook hook;
+ int res = -ENOENT;
+ bool is_set;
+ u32 *flags;
+
+ for (hook = 0; hook < BTF_KFUNC_HOOK_MAX; hook++) {
+ flags = btf_kfunc_id_set_contains(btf, hook, kfunc_btf_id);
+ if (!flags)
+ continue;
+ is_set = *flags & flag;
+ if (res < 0)
+ res = is_set;
+ else if (res != is_set)
+ return -EINVAL;
+ }
+
+ return res;
+}
+
u32 *btf_kfunc_is_modify_return(const struct btf *btf, u32 kfunc_btf_id,
const struct bpf_prog *prog)
{
diff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c
index 26d37066465f..842c7d1eabcc 100644
--- a/kernel/bpf/cfg.c
+++ b/kernel/bpf/cfg.c
@@ -5,6 +5,8 @@
#include <linux/filter.h>
#include <linux/sort.h>
+#include "diagnostics.h"
+
#define verbose(env, fmt, args...) bpf_verifier_log_write(env, fmt, ##args)
/* non-recursive DFS pseudo code
@@ -47,7 +49,6 @@ enum {
BRANCH = 2,
};
-
static void mark_subprog_changes_pkt_data(struct bpf_verifier_env *env, int off)
{
struct bpf_subprog_info *subprog;
@@ -113,6 +114,10 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)
if (w < 0 || w >= env->prog->len) {
verbose_linfo(env, t, "%d: ", t);
verbose(env, "jump out of range from insn %d to %d\n", t, w);
+ bpf_diag_program_structure(
+ env, t, "jump out of range", "Keep branch targets inside the program.",
+ "Instruction %d jumps to instruction %d, but the program only contains instructions 0 through %d.",
+ t, w, env->prog->len - 1);
return -EINVAL;
}
@@ -120,6 +125,7 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)
/* mark branch target for state pruning */
mark_prune_point(env, w);
mark_jmp_point(env, w);
+ mark_jump_target(env, w);
}
if (insn_state[w] == 0) {
@@ -136,6 +142,11 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)
verbose_linfo(env, t, "%d: ", t);
verbose_linfo(env, w, "%d: ", w);
verbose(env, "back-edge from insn %d to %d\n", t, w);
+ bpf_diag_program_structure(
+ env, t, "back-edge is not allowed",
+ "Load with privileges that allow this back-edge, or rewrite the control flow so it does not branch backward.",
+ "Instruction %d branches back to instruction %d. This program is being rejected without the privilege needed for this back-edge.",
+ t, w);
return -EINVAL;
} else if (insn_state[w] == EXPLORED) {
/* forward- or cross-edge */
@@ -316,6 +327,11 @@ static struct bpf_iarray *jt_from_subprog(struct bpf_verifier_env *env,
if (!jt) {
verbose(env, "no jump tables found for subprog starting at %u\n", subprog_start);
+ bpf_diag_program_structure(
+ env, subprog_start, "missing jump table",
+ "Make sure subprograms containing gotox instructions are accompanied by jump tables referencing these subprograms.",
+ "No jump table was found for the subprogram that starts at instruction %u.",
+ subprog_start);
return ERR_PTR(-EINVAL);
}
@@ -343,6 +359,11 @@ create_jt(int t, struct bpf_verifier_env *env)
if (jt->items[i] < subprog_start || jt->items[i] >= subprog_end) {
verbose(env, "jump table for insn %d points outside of the subprog [%u,%u]\n",
t, subprog_start, subprog_end);
+ bpf_diag_program_structure(
+ env, t, "jump table target out of range",
+ "Keep every jump-table target inside the same subprogram.",
+ "The jump table for instruction %d points outside subprogram range [%u,%u).",
+ t, subprog_start, subprog_end);
kvfree(jt);
return ERR_PTR(-EINVAL);
}
@@ -374,10 +395,16 @@ static int visit_gotox_insn(int t, struct bpf_verifier_env *env)
w = jt->items[i];
if (w < 0 || w >= env->prog->len) {
verbose(env, "indirect jump out of range from insn %d to %d\n", t, w);
+ bpf_diag_program_structure(
+ env, t, "indirect jump out of range",
+ "Keep indirect jump targets inside the program.",
+ "Instruction %d can jump indirectly to instruction %d, but the program only contains instructions 0 through %d.",
+ t, w, env->prog->len - 1);
return -EINVAL;
}
mark_jmp_point(env, w);
+ mark_jump_target(env, w);
/* EXPLORED || DISCOVERED */
if (insn_state[w])
@@ -491,7 +518,7 @@ static int visit_insn(int t, struct bpf_verifier_env *env)
return ret;
}
} else if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) {
- struct bpf_kfunc_call_arg_meta meta;
+ struct bpf_call_arg_meta meta;
ret = bpf_fetch_kfunc_arg_meta(env, insn->imm, insn->off, &meta);
if (ret == 0 && bpf_is_iter_next_kfunc(&meta)) {
@@ -539,6 +566,7 @@ static int visit_insn(int t, struct bpf_verifier_env *env)
mark_prune_point(env, t + off + 1);
mark_jmp_point(env, t + off + 1);
+ mark_jump_target(env, t + off + 1);
return ret;
@@ -624,12 +652,21 @@ walk_cfg:
if (insn_state[i] != EXPLORED) {
verbose(env, "unreachable insn %d\n", i);
+ bpf_diag_program_structure(
+ env, i, "unreachable instruction",
+ "Remove the unreachable instruction or add valid control flow that reaches it.",
+ "Instruction %d is not reachable from the program entry point.", i);
ret = -EINVAL;
goto err_free;
}
if (bpf_is_ldimm64(insn)) {
if (insn_state[i + 1] != 0) {
verbose(env, "jump into the middle of ldimm64 insn %d\n", i);
+ bpf_diag_program_structure(
+ env, i, "jump into ldimm64 immediate",
+ "Target the first instruction of the ldimm64 pair, or restructure the jump target.",
+ "Control flow reaches the second half of the ldimm64 instruction pair that starts at instruction %d.",
+ i);
ret = -EINVAL;
goto err_free;
}
diff --git a/kernel/bpf/cgroup.c b/kernel/bpf/cgroup.c
index 83ce66296ac1..149672c76c49 100644
--- a/kernel/bpf/cgroup.c
+++ b/kernel/bpf/cgroup.c
@@ -813,8 +813,10 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
struct bpf_prog *old_prog = NULL;
struct bpf_cgroup_storage *storage[MAX_BPF_CGROUP_STORAGE_TYPE] = {};
struct bpf_cgroup_storage *new_storage[MAX_BPF_CGROUP_STORAGE_TYPE] = {};
+ struct bpf_cgroup_storage *old_storage[MAX_BPF_CGROUP_STORAGE_TYPE] = {};
struct bpf_prog *new_prog = prog ? : link->link.prog;
enum cgroup_bpf_attach_type atype;
+ u32 old_flags, old_pl_flags;
struct bpf_prog_list *pl;
struct hlist_head *progs;
int err;
@@ -865,6 +867,8 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
if (pl) {
old_prog = pl->prog;
+ old_pl_flags = pl->flags;
+ bpf_cgroup_storages_assign(old_storage, pl->storage);
} else {
pl = kmalloc_obj(*pl);
if (!pl) {
@@ -884,6 +888,7 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
pl->link = link;
pl->flags = flags;
bpf_cgroup_storages_assign(pl->storage, storage);
+ old_flags = cgrp->bpf.flags[atype];
cgrp->bpf.flags[atype] = saved_flags;
if (type == BPF_LSM_CGROUP) {
@@ -915,12 +920,15 @@ cleanup:
if (old_prog) {
pl->prog = old_prog;
pl->link = NULL;
+ pl->flags = old_pl_flags;
+ bpf_cgroup_storages_assign(pl->storage, old_storage);
}
bpf_cgroup_storages_free(new_storage);
if (!old_prog) {
hlist_del(&pl->node);
kfree(pl);
}
+ cgrp->bpf.flags[atype] = old_flags;
return err;
}
@@ -939,19 +947,65 @@ static int cgroup_bpf_attach(struct cgroup *cgrp,
return ret;
}
+static int effective_prog_pos(struct cgroup *cgrp,
+ enum cgroup_bpf_attach_type atype,
+ struct bpf_prog_list *target_pl)
+{
+ int cnt = 0, preorder_cnt = 0, fstart, bstart, init_bstart, pos = -1;
+ struct bpf_prog_list *pl;
+ struct cgroup *p = cgrp;
+
+ /* count effective programs to find where the preorder region ends */
+ do {
+ if (cnt == 0 || (p->bpf.flags[atype] & BPF_F_ALLOW_MULTI))
+ cnt += prog_list_length(&p->bpf.progs[atype], &preorder_cnt);
+ p = cgroup_parent(p);
+ } while (p);
+
+ /* replay compute_effective_progs() placement and record target's slot */
+ cnt = 0;
+ p = cgrp;
+ fstart = preorder_cnt;
+ bstart = preorder_cnt - 1;
+ do {
+ if (cnt > 0 && !(p->bpf.flags[atype] & BPF_F_ALLOW_MULTI))
+ continue;
+
+ init_bstart = bstart;
+ hlist_for_each_entry(pl, &p->bpf.progs[atype], node) {
+ if (!prog_list_prog(pl))
+ continue;
+
+ if (pl->flags & BPF_F_PREORDER) {
+ if (pl == target_pl)
+ pos = bstart;
+ bstart--;
+ } else {
+ if (pl == target_pl)
+ pos = fstart;
+ fstart++;
+ }
+ cnt++;
+ }
+
+ /* reverse pre-ordering progs at this cgroup level */
+ if (pos >= bstart + 1 && pos <= init_bstart)
+ pos = bstart + 1 + init_bstart - pos;
+ } while ((p = cgroup_parent(p)));
+
+ return pos;
+}
+
/* Swap updated BPF program for given link in effective program arrays across
* all descendant cgroups. This function is guaranteed to succeed.
*/
static void replace_effective_prog(struct cgroup *cgrp,
enum cgroup_bpf_attach_type atype,
- struct bpf_cgroup_link *link)
+ struct bpf_prog_list *pl)
{
struct bpf_prog_array_item *item;
struct cgroup_subsys_state *css;
struct bpf_prog_array *progs;
- struct bpf_prog_list *pl;
- struct hlist_head *head;
- struct cgroup *cg;
int pos;
css_for_each_descendant_pre(css, &cgrp->self) {
@@ -960,28 +1014,30 @@ static void replace_effective_prog(struct cgroup *cgrp,
if (percpu_ref_is_zero(&desc->bpf.refcnt))
continue;
- /* find position of link in effective progs array */
- for (pos = 0, cg = desc; cg; cg = cgroup_parent(cg)) {
- if (pos && !(cg->bpf.flags[atype] & BPF_F_ALLOW_MULTI))
- continue;
+ pos = effective_prog_pos(desc, atype, pl);
+ if (WARN_ON_ONCE(pos < 0))
+ continue;
- head = &cg->bpf.progs[atype];
- hlist_for_each_entry(pl, head, node) {
- if (!prog_list_prog(pl))
- continue;
- if (pl->link == link)
- goto found;
- pos++;
- }
- }
-found:
- BUG_ON(!cg);
progs = rcu_dereference_protected(
desc->bpf.effective[atype],
lockdep_is_held(&cgroup_mutex));
item = &progs->items[pos];
- WRITE_ONCE(item->prog, link->link.prog);
+ WRITE_ONCE(item->prog, pl->link->link.prog);
+ }
+}
+
+static bool cgroup_bpf_storages_compatible(struct bpf_prog *old_prog,
+ struct bpf_prog *new_prog)
+{
+ enum bpf_cgroup_storage_type stype;
+
+ for_each_cgroup_storage_type(stype) {
+ if (old_prog->aux->cgroup_storage[stype] !=
+ new_prog->aux->cgroup_storage[stype])
+ return false;
}
+
+ return true;
}
/**
@@ -1022,9 +1078,12 @@ static int __cgroup_bpf_replace(struct cgroup *cgrp,
if (!found)
return -ENOENT;
+ if (!cgroup_bpf_storages_compatible(link->link.prog, new_prog))
+ return -EINVAL;
+
cgrp->bpf.revisions[atype] += 1;
old_prog = xchg(&link->link.prog, new_prog);
- replace_effective_prog(cgrp, atype, link);
+ replace_effective_prog(cgrp, atype, pl);
bpf_prog_put(old_prog);
return 0;
}
@@ -1091,19 +1150,14 @@ static struct bpf_prog_list *find_detach_entry(struct hlist_head *progs,
* recomputing the array in place.
*
* @cgrp: The cgroup which descendants to travers
- * @prog: A program to detach or NULL
- * @link: A link to detach or NULL
+ * @pl: The prog_list entry being detached
* @atype: Type of detach operation
*/
-static void purge_effective_progs(struct cgroup *cgrp, struct bpf_prog *prog,
- struct bpf_cgroup_link *link,
+static void purge_effective_progs(struct cgroup *cgrp, struct bpf_prog_list *pl,
enum cgroup_bpf_attach_type atype)
{
struct cgroup_subsys_state *css;
struct bpf_prog_array *progs;
- struct bpf_prog_list *pl;
- struct hlist_head *head;
- struct cgroup *cg;
int pos;
/* recompute effective prog array in place */
@@ -1113,24 +1167,11 @@ static void purge_effective_progs(struct cgroup *cgrp, struct bpf_prog *prog,
if (percpu_ref_is_zero(&desc->bpf.refcnt))
continue;
- /* find position of link or prog in effective progs array */
- for (pos = 0, cg = desc; cg; cg = cgroup_parent(cg)) {
- if (pos && !(cg->bpf.flags[atype] & BPF_F_ALLOW_MULTI))
- continue;
-
- head = &cg->bpf.progs[atype];
- hlist_for_each_entry(pl, head, node) {
- if (!prog_list_prog(pl))
- continue;
- if (pl->prog == prog && pl->link == link)
- goto found;
- pos++;
- }
- }
-
+ pos = effective_prog_pos(desc, atype, pl);
/* no link or prog match, skip the cgroup of this layer */
- continue;
-found:
+ if (pos < 0)
+ continue;
+
progs = rcu_dereference_protected(
desc->bpf.effective[atype],
lockdep_is_held(&cgroup_mutex));
@@ -1196,7 +1237,7 @@ static int __cgroup_bpf_detach(struct cgroup *cgrp, struct bpf_prog *prog,
/* if update effective array failed replace the prog with a dummy prog*/
pl->prog = old_prog;
pl->link = link;
- purge_effective_progs(cgrp, old_prog, link, atype);
+ purge_effective_progs(cgrp, pl, atype);
}
/* now can actually delete it from this cgroup list */
@@ -2219,7 +2260,7 @@ int __cgroup_bpf_run_filter_getsockopt_kern(struct sock *sk, int level,
if (ret < 0)
return ret;
- if (ctx.optlen > *optlen)
+ if (ctx.optlen > *optlen || ctx.optlen < 0)
return -EFAULT;
/* BPF programs can shrink the buffer, export the modifications.
@@ -2289,7 +2330,7 @@ static const struct bpf_func_proto bpf_sysctl_get_name_proto = {
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_PTR_TO_MEM | MEM_WRITE,
- .arg3_type = ARG_CONST_SIZE,
+ .arg3_type = ARG_MEM_SIZE,
.arg4_type = ARG_ANYTHING,
};
@@ -2331,7 +2372,7 @@ static const struct bpf_func_proto bpf_sysctl_get_current_value_proto = {
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_PTR_TO_UNINIT_MEM,
- .arg3_type = ARG_CONST_SIZE,
+ .arg3_type = ARG_MEM_SIZE,
};
BPF_CALL_3(bpf_sysctl_get_new_value, struct bpf_sysctl_kern *, ctx, char *, buf,
@@ -2351,7 +2392,7 @@ static const struct bpf_func_proto bpf_sysctl_get_new_value_proto = {
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_PTR_TO_UNINIT_MEM,
- .arg3_type = ARG_CONST_SIZE,
+ .arg3_type = ARG_MEM_SIZE,
};
BPF_CALL_3(bpf_sysctl_set_new_value, struct bpf_sysctl_kern *, ctx,
@@ -2377,7 +2418,7 @@ static const struct bpf_func_proto bpf_sysctl_set_new_value_proto = {
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg3_type = ARG_CONST_SIZE,
+ .arg3_type = ARG_MEM_SIZE,
};
static const struct bpf_func_proto *
diff --git a/kernel/bpf/check_btf.c b/kernel/bpf/check_btf.c
index 93bebe6fe12e..4c1ed842f661 100644
--- a/kernel/bpf/check_btf.c
+++ b/kernel/bpf/check_btf.c
@@ -28,9 +28,9 @@ static int check_abnormal_return(struct bpf_verifier_env *env)
#define MIN_BPF_FUNCINFO_SIZE 8
#define MAX_FUNCINFO_REC_SIZE 252
-static int check_btf_func_early(struct bpf_verifier_env *env,
- const union bpf_attr *attr,
- bpfptr_t uattr)
+static int prepare_btf_func(struct bpf_verifier_env *env,
+ const union bpf_attr *attr,
+ bpfptr_t uattr)
{
u32 krec_size = sizeof(struct bpf_func_info);
const struct btf_type *type, *func_proto;
@@ -338,9 +338,9 @@ err_free:
#define MIN_CORE_RELO_SIZE sizeof(struct bpf_core_relo)
#define MAX_CORE_RELO_SIZE MAX_FUNCINFO_REC_SIZE
-static int check_core_relo(struct bpf_verifier_env *env,
- const union bpf_attr *attr,
- bpfptr_t uattr)
+int bpf_check_core_relo(struct bpf_verifier_env *env,
+ const union bpf_attr *attr,
+ bpfptr_t uattr)
{
u32 i, nr_core_relo, ncopy, expected_size, rec_size;
struct bpf_core_relo core_relo = {};
@@ -407,14 +407,14 @@ static int check_core_relo(struct bpf_verifier_env *env,
return err;
}
-int bpf_check_btf_info_early(struct bpf_verifier_env *env,
- const union bpf_attr *attr,
- bpfptr_t uattr)
+int bpf_prepare_btf_info(struct bpf_verifier_env *env,
+ const union bpf_attr *attr,
+ bpfptr_t uattr)
{
struct btf *btf;
int err;
- if (!attr->func_info_cnt && !attr->line_info_cnt) {
+ if (!attr->func_info_cnt && !attr->line_info_cnt && !attr->core_relo_cnt) {
if (check_abnormal_return(env))
return -EINVAL;
return 0;
@@ -429,7 +429,7 @@ int bpf_check_btf_info_early(struct bpf_verifier_env *env,
}
env->prog->aux->btf = btf;
- err = check_btf_func_early(env, attr, uattr);
+ err = prepare_btf_func(env, attr, uattr);
if (err)
return err;
return 0;
@@ -455,9 +455,5 @@ int bpf_check_btf_info(struct bpf_verifier_env *env,
if (err)
return err;
- err = check_core_relo(env, attr, uattr);
- if (err)
- return err;
-
return 0;
}
diff --git a/kernel/bpf/const_fold.c b/kernel/bpf/const_fold.c
index b2a19acadb91..7f1b30059cc8 100644
--- a/kernel/bpf/const_fold.c
+++ b/kernel/bpf/const_fold.c
@@ -182,7 +182,6 @@ static void const_reg_xfer(struct bpf_verifier_env *env, struct const_arg_info *
u64 val = 0;
if (!bpf_map_is_rdonly(map) || !map->ops->map_direct_value_addr ||
- map->map_type == BPF_MAP_TYPE_INSN_ARRAY ||
off < 0 || off + size > map->value_size ||
bpf_map_direct_read(map, off, size, &val, is_ldsx)) {
*dst = unknown;
@@ -199,14 +198,9 @@ process_call:
ci_out[r] = unknown;
break;
case BPF_STX:
- if (mode != BPF_ATOMIC)
- break;
- if (insn->imm == BPF_CMPXCHG)
- ci_out[BPF_REG_0] = unknown;
- else if (insn->imm == BPF_LOAD_ACQ)
- *dst = unknown;
- else if (insn->imm & BPF_FETCH)
- *src = unknown;
+ r = bpf_atomic_load_reg(insn);
+ if (r >= 0)
+ ci_out[r] = unknown;
break;
}
}
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 649cce41e13f..7f11555a5070 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -19,7 +19,9 @@
#include <uapi/linux/btf.h>
#include <linux/filter.h>
+#include <linux/sched/signal.h>
#include <linux/skbuff.h>
+#include <linux/static_call.h>
#include <linux/vmalloc.h>
#include <linux/prandom.h>
#include <linux/bpf.h>
@@ -125,6 +127,7 @@ struct bpf_prog *bpf_prog_alloc_no_stats(unsigned int size, gfp_t gfp_extra_flag
fp->aux->main_prog_aux = aux;
fp->aux->prog = fp;
fp->jit_requested = ebpf_jit_enabled();
+ fp->jit_required = IS_ENABLED(CONFIG_BPF_JIT_ALWAYS_ON);
fp->blinding_requested = bpf_jit_blinding_enabled(fp);
#ifdef CONFIG_CGROUP_BPF
aux->cgroup_atype = CGROUP_BPF_ATTACH_TYPE_INVALID;
@@ -304,7 +307,7 @@ int bpf_prog_calc_tag(struct bpf_prog *fp)
bool was_ld_map;
u32 i;
- dst = vmalloc(size);
+ dst = __vmalloc(size, GFP_KERNEL_ACCOUNT);
if (!dst)
return -ENOMEM;
@@ -875,6 +878,7 @@ int bpf_jit_add_poke_descriptor(struct bpf_prog *prog,
struct bpf_prog_pack {
struct list_head list;
void *ptr;
+ bool arch_flush_needed;
unsigned long bitmap[];
};
@@ -883,6 +887,15 @@ void bpf_jit_fill_hole_with_zero(void *area, unsigned int size)
memset(area, 0, size);
}
+DEFINE_STATIC_CALL_NULL(bpf_arch_pred_flush, bpf_arch_pred_flush);
+
+/*
+ * Enabled once bpf_arch_pred_flush points at a real flush routine. Lets the
+ * pack allocator test "is a predictor flush wired up at all" with a cheap
+ * static branch instead of repeatedly querying the static call target.
+ */
+DEFINE_STATIC_KEY_FALSE(bpf_pred_flush_enabled);
+
#define BPF_PROG_SIZE_TO_NBITS(size) (round_up(size, BPF_PROG_CHUNK_SIZE) / BPF_PROG_CHUNK_SIZE)
static DEFINE_MUTEX(pack_mutex);
@@ -904,6 +917,11 @@ static LIST_HEAD(pack_list);
#define BPF_PROG_CHUNK_COUNT (BPF_PROG_PACK_SIZE / BPF_PROG_CHUNK_SIZE)
+static bool bpf_jit_mem_is_rox(void)
+{
+ return execmem_is_rox(EXECMEM_BPF);
+}
+
static struct bpf_prog_pack *alloc_new_pack(bpf_jit_fill_hole_t bpf_fill_ill_insns)
{
struct bpf_prog_pack *pack;
@@ -915,14 +933,18 @@ static struct bpf_prog_pack *alloc_new_pack(bpf_jit_fill_hole_t bpf_fill_ill_ins
pack->ptr = bpf_jit_alloc_exec(BPF_PROG_PACK_SIZE);
if (!pack->ptr)
goto out;
- bpf_fill_ill_insns(pack->ptr, BPF_PROG_PACK_SIZE);
bitmap_zero(pack->bitmap, BPF_PROG_PACK_SIZE / BPF_PROG_CHUNK_SIZE);
- set_vm_flush_reset_perms(pack->ptr);
- err = set_memory_rox((unsigned long)pack->ptr,
- BPF_PROG_PACK_SIZE / PAGE_SIZE);
- if (err)
- goto out;
+ if (static_branch_unlikely(&bpf_pred_flush_enabled))
+ pack->arch_flush_needed = true;
+ if (!bpf_jit_mem_is_rox()) {
+ bpf_fill_ill_insns(pack->ptr, BPF_PROG_PACK_SIZE);
+ set_vm_flush_reset_perms(pack->ptr);
+ err = set_memory_rox((unsigned long)pack->ptr,
+ BPF_PROG_PACK_SIZE / PAGE_SIZE);
+ if (err)
+ goto out;
+ }
list_add_tail(&pack->list, &pack_list);
return pack;
@@ -932,18 +954,26 @@ out:
return NULL;
}
-void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns)
+void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool was_classic)
{
unsigned int nbits = BPF_PROG_SIZE_TO_NBITS(size);
- struct bpf_prog_pack *pack;
- unsigned long pos;
+ struct bpf_prog_pack *pack, *fallback_pack = NULL;
+ unsigned long pos, fallback_pos = 0;
void *ptr = NULL;
mutex_lock(&pack_mutex);
if (size > BPF_PROG_PACK_SIZE) {
+ /*
+ * Allocations larger than a pack get their own pages, and
+ * predictors are not flushed for such allocation. This is only
+ * safe because cBPF programs (the unprivileged attack surface)
+ * are bounded well below a pack size.
+ */
+ if (was_classic && static_branch_unlikely(&bpf_pred_flush_enabled))
+ pr_warn_once("BPF: Predictors not flushed for allocations greater than BPF_PROG_PACK_SIZE\n");
size = round_up(size, PAGE_SIZE);
ptr = bpf_jit_alloc_exec(size);
- if (ptr) {
+ if (ptr && !bpf_jit_mem_is_rox()) {
int err;
bpf_fill_ill_insns(ptr, size);
@@ -960,8 +990,29 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns)
list_for_each_entry(pack, &pack_list, list) {
pos = bitmap_find_next_zero_area(pack->bitmap, BPF_PROG_CHUNK_COUNT, 0,
nbits, 0);
- if (pos < BPF_PROG_CHUNK_COUNT)
+ if (pos >= BPF_PROG_CHUNK_COUNT)
+ continue;
+ /* Flush not enabled, use any pack */
+ if (!static_branch_unlikely(&bpf_pred_flush_enabled))
+ goto found_free_area;
+ /*
+ * cBPF reuse of a dirty pack triggers a flush, so prefer a
+ * clean pack for cBPF. eBPF never flushes, so steer it to a
+ * dirty pack and keep clean packs free for cBPF.
+ */
+ if (was_classic ^ pack->arch_flush_needed)
goto found_free_area;
+ if (!fallback_pack) {
+ fallback_pack = pack;
+ fallback_pos = pos;
+ }
+ }
+
+ /* No preferred pack found */
+ if (fallback_pack) {
+ pack = fallback_pack;
+ pos = fallback_pos;
+ goto found_free_area;
}
pack = alloc_new_pack(bpf_fill_ill_insns);
@@ -971,6 +1022,16 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns)
pos = 0;
found_free_area:
+ /* Flush only for cBPF as it may contain a crafted gadget */
+ if (static_branch_unlikely(&bpf_pred_flush_enabled) &&
+ pack->arch_flush_needed &&
+ was_classic) {
+ struct bpf_prog_pack *p;
+
+ static_call_cond(bpf_arch_pred_flush)();
+ list_for_each_entry(p, &pack_list, list)
+ p->arch_flush_needed = false;
+ }
bitmap_set(pack->bitmap, pos, nbits);
ptr = (void *)(pack->ptr) + (pos << BPF_PROG_CHUNK_SHIFT);
@@ -1008,6 +1069,9 @@ void bpf_prog_pack_free(void *ptr, u32 size)
"bpf_prog_pack bug: missing bpf_arch_text_invalidate?\n");
bitmap_clear(pack->bitmap, pos, nbits);
+
+ if (static_branch_unlikely(&bpf_pred_flush_enabled))
+ pack->arch_flush_needed = true;
if (bitmap_find_next_zero_area(pack->bitmap, BPF_PROG_CHUNK_COUNT, 0,
BPF_PROG_CHUNK_COUNT, 0) == 0) {
list_del(&pack->list);
@@ -1060,12 +1124,12 @@ void bpf_jit_uncharge_modmem(u32 size)
atomic_long_sub(size, &bpf_jit_current);
}
-void *__weak bpf_jit_alloc_exec(unsigned long size)
+void *bpf_jit_alloc_exec(unsigned long size)
{
return execmem_alloc(EXECMEM_BPF, size);
}
-void __weak bpf_jit_free_exec(void *addr)
+void bpf_jit_free_exec(void *addr)
{
execmem_free(addr);
}
@@ -1130,7 +1194,8 @@ bpf_jit_binary_pack_alloc(unsigned int proglen, u8 **image_ptr,
unsigned int alignment,
struct bpf_binary_header **rw_header,
u8 **rw_image,
- bpf_jit_fill_hole_t bpf_fill_ill_insns)
+ bpf_jit_fill_hole_t bpf_fill_ill_insns,
+ bool was_classic)
{
struct bpf_binary_header *ro_header;
u32 size, hole, start;
@@ -1143,7 +1208,7 @@ bpf_jit_binary_pack_alloc(unsigned int proglen, u8 **image_ptr,
if (bpf_jit_charge_modmem(size))
return NULL;
- ro_header = bpf_prog_pack_alloc(size, bpf_fill_ill_insns);
+ ro_header = bpf_prog_pack_alloc(size, bpf_fill_ill_insns, was_classic);
if (!ro_header) {
bpf_jit_uncharge_modmem(size);
return NULL;
@@ -1297,7 +1362,7 @@ static int bpf_jit_blind_insn(const struct bpf_insn *from,
{
struct bpf_insn *to = to_buff;
u32 imm_rnd = get_random_u32();
- s16 off;
+ int off;
BUILD_BUG_ON(BPF_REG_PARAMS + 2 != MAX_BPF_JIT_REG);
BUILD_BUG_ON(BPF_REG_AX + 1 != MAX_BPF_JIT_REG);
@@ -1373,6 +1438,8 @@ static int bpf_jit_blind_insn(const struct bpf_insn *from,
off = from->off;
if (off < 0)
off -= 2;
+ if (off < S16_MIN)
+ return -ERANGE;
*to++ = BPF_ALU64_IMM(BPF_MOV, BPF_REG_AX, imm_rnd ^ from->imm);
*to++ = BPF_ALU64_IMM(BPF_XOR, BPF_REG_AX, imm_rnd);
*to++ = BPF_JMP_REG(from->code, from->dst_reg, BPF_REG_AX, off);
@@ -1393,6 +1460,8 @@ static int bpf_jit_blind_insn(const struct bpf_insn *from,
off = from->off;
if (off < 0)
off -= 2;
+ if (off < S16_MIN)
+ return -ERANGE;
*to++ = BPF_ALU32_IMM(BPF_MOV, BPF_REG_AX, imm_rnd ^ from->imm);
*to++ = BPF_ALU32_IMM(BPF_XOR, BPF_REG_AX, imm_rnd);
*to++ = BPF_JMP32_REG(from->code, from->dst_reg, BPF_REG_AX,
@@ -1541,7 +1610,9 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp
if (!rewritten)
continue;
- if (env)
+ if (rewritten < 0)
+ tmp = ERR_PTR(rewritten);
+ else if (env)
tmp = bpf_patch_insn_data(env, i, insn_buff, rewritten);
else
tmp = bpf_patch_insn_single(clone, i, insn_buff, rewritten);
@@ -1555,6 +1626,8 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp
* fix it up here on error.
*/
bpf_jit_prog_release_other(prog, clone);
+ if (env && fatal_signal_pending(current))
+ return ERR_PTR(-EINTR);
return IS_ERR(tmp) ? tmp : ERR_PTR(-ENOMEM);
}
@@ -2565,36 +2638,25 @@ static struct bpf_prog *bpf_prog_jit_compile(struct bpf_verifier_env *env, struc
{
#ifdef CONFIG_BPF_JIT
struct bpf_prog *orig_prog;
- struct bpf_insn_aux_data *orig_insn_aux;
if (!bpf_prog_need_blind(prog))
return bpf_int_jit_compile(env, prog);
- if (env) {
- /*
- * If env is not NULL, we are called from the end of bpf_check(), at this
- * point, only insn_aux_data is used after failure, so it should be restored
- * on failure.
- */
- orig_insn_aux = bpf_dup_insn_aux_data(env);
- if (!orig_insn_aux)
- return prog;
- }
-
orig_prog = prog;
prog = bpf_jit_blind_constants(env, prog);
/*
- * If blinding was requested and we failed during blinding, we must fall
- * back to the interpreter.
+ * Fall back to the interpreter after blinding failures, except when
+ * the loader was killed.
*/
- if (IS_ERR(prog))
+ if (IS_ERR(prog)) {
+ if (PTR_ERR(prog) == -EINTR)
+ return prog;
goto out_restore;
+ }
prog = bpf_int_jit_compile(env, prog);
if (prog->jited) {
bpf_jit_prog_release_other(prog, orig_prog);
- if (env)
- vfree(orig_insn_aux);
return prog;
}
@@ -2602,8 +2664,6 @@ static struct bpf_prog *bpf_prog_jit_compile(struct bpf_verifier_env *env, struc
out_restore:
prog = orig_prog;
- if (env)
- bpf_restore_insn_aux_data(env, orig_insn_aux);
#endif
return prog;
}
@@ -2611,18 +2671,16 @@ out_restore:
struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct bpf_prog *fp,
int *err)
{
+ struct bpf_prog *jit_prog;
+
/* In case of BPF to BPF calls, verifier did all the prep
* work with regards to JITing, etc.
*/
- bool jit_needed = false;
+ bool jit_needed = fp->jit_required;
if (fp->bpf_func)
goto finalize;
- if (IS_ENABLED(CONFIG_BPF_JIT_ALWAYS_ON) ||
- bpf_prog_has_kfunc_call(fp))
- jit_needed = true;
-
if (!bpf_prog_select_interpreter(fp))
jit_needed = true;
@@ -2637,7 +2695,12 @@ struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct
if (*err)
return fp;
- fp = bpf_prog_jit_compile(env, fp);
+ jit_prog = bpf_prog_jit_compile(env, fp);
+ if (IS_ERR(jit_prog)) {
+ *err = PTR_ERR(jit_prog);
+ return fp;
+ }
+ fp = jit_prog;
bpf_prog_jit_attempt_done(fp);
if (!fp->jited && jit_needed) {
*err = -ENOTSUPP;
@@ -3243,6 +3306,11 @@ bool __weak bpf_jit_supports_stack_args(void)
return false;
}
+bool __weak bpf_jit_supports_arena_args(void)
+{
+ return false;
+}
+
bool __weak bpf_jit_supports_far_kfunc_call(void)
{
return false;
@@ -3407,24 +3475,14 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(xdp_bulk_tx);
#ifdef CONFIG_BPF_SYSCALL
-void bpf_get_linfo_file_line(struct btf *btf, const struct bpf_line_info *linfo,
- const char **filep, const char **linep, int *nump)
+void bpf_get_linfo_source(struct btf *btf, const struct bpf_line_info *linfo,
+ struct bpf_linfo_source *src)
{
- /* Get base component of the file path. */
- if (filep) {
- *filep = btf_name_by_offset(btf, linfo->file_name_off);
- *filep = kbasename(*filep);
- }
-
- /* Obtain the source line, and strip whitespace in prefix. */
- if (linep) {
- *linep = btf_name_by_offset(btf, linfo->line_off);
- while (isspace(**linep))
- *linep += 1;
- }
-
- if (nump)
- *nump = BPF_LINE_INFO_LINE_NUM(linfo->line_col);
+ src->file = kbasename(btf_name_by_offset(btf, linfo->file_name_off));
+ src->line = btf_name_by_offset(btf, linfo->line_off);
+ src->file_name_off = linfo->file_name_off;
+ src->line_num = BPF_LINE_INFO_LINE_NUM(linfo->line_col);
+ src->line_col = BPF_LINE_INFO_LINE_COL(linfo->line_col);
}
const struct bpf_line_info *bpf_find_linfo(const struct bpf_prog *prog, u32 insn_off)
@@ -3467,6 +3525,7 @@ const struct bpf_line_info *bpf_find_linfo(const struct bpf_prog *prog, u32 insn
int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char **filep,
const char **linep, int *nump)
{
+ struct bpf_linfo_source src;
int idx = -1, insn_start, insn_end, len;
struct bpf_line_info *linfo;
void **jited_linfo;
@@ -3498,7 +3557,15 @@ int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char *
if (idx == -1)
return -ENOENT;
- bpf_get_linfo_file_line(btf, &linfo[idx], filep, linep, nump);
+ bpf_get_linfo_source(btf, &linfo[idx], &src);
+ while (isspace(*src.line))
+ src.line++;
+ if (filep)
+ *filep = src.file;
+ if (linep)
+ *linep = src.line;
+ if (nump)
+ *nump = src.line_num;
return 0;
}
diff --git a/kernel/bpf/cpumask.c b/kernel/bpf/cpumask.c
index b8c805b4b06a..1336a4efa755 100644
--- a/kernel/bpf/cpumask.c
+++ b/kernel/bpf/cpumask.c
@@ -449,12 +449,12 @@ __bpf_kfunc u32 bpf_cpumask_weight(const struct cpumask *cpumask)
* @src__sz: Length of the BPF memory region in bytes.
*
* Return:
- * * 0 if the struct cpumask * instance was populated successfully.
+ * * 0 if the struct bpf_cpumask * instance was populated successfully.
* * -EACCES if the memory region is too small to populate the cpumask.
* * -EINVAL if the memory region is not aligned to the size of a long
* and the architecture does not support efficient unaligned accesses.
*/
-__bpf_kfunc int bpf_cpumask_populate(struct cpumask *cpumask, void *src, size_t src__sz)
+__bpf_kfunc int bpf_cpumask_populate(struct bpf_cpumask *cpumask, void *src, size_t src__sz)
{
unsigned long source = (unsigned long)src;
@@ -467,7 +467,7 @@ __bpf_kfunc int bpf_cpumask_populate(struct cpumask *cpumask, void *src, size_t
!IS_ALIGNED(source, sizeof(long)))
return -EINVAL;
- bitmap_copy(cpumask_bits(cpumask), src, nr_cpu_ids);
+ bitmap_copy(cpumask_bits(&cpumask->cpumask), src, nr_cpu_ids);
return 0;
}
diff --git a/kernel/bpf/crypto.c b/kernel/bpf/crypto.c
index 51f89cecefb4..3f3fe2450fc6 100644
--- a/kernel/bpf/crypto.c
+++ b/kernel/bpf/crypto.c
@@ -149,8 +149,9 @@ bpf_crypto_ctx_create(const struct bpf_crypto_params *params, u32 params__sz,
const struct bpf_crypto_type *type;
struct bpf_crypto_ctx *ctx;
- if (!params || params->reserved[0] || params->reserved[1] ||
- params__sz != sizeof(struct bpf_crypto_params)) {
+ if (!params ||
+ params__sz != sizeof(struct bpf_crypto_params) ||
+ params->reserved[0] || params->reserved[1]) {
*err = -EINVAL;
return NULL;
}
diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
new file mode 100644
index 000000000000..0abbbe177e31
--- /dev/null
+++ b/kernel/bpf/diagnostics.c
@@ -0,0 +1,2359 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+
+#include <linux/bpf.h>
+#include <linux/bpf_verifier.h>
+#include <linux/btf.h>
+#include <linux/ctype.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/seq_buf.h>
+#include <linux/overflow.h>
+#include <linux/slab.h>
+#include <linux/stdarg.h>
+#include <linux/string.h>
+
+#include "disasm.h"
+#include "diagnostics.h"
+
+#define REGISTER_TYPE_SAFETY "Register Type Safety"
+#define MEMORY_SAFETY "Memory Safety"
+#define RESOURCE_LIFETIME_SAFETY "Resource Lifetime Safety"
+#define CALL_TYPE_SAFETY "Call Type Safety"
+#define EXECUTION_CONTEXT_SAFETY "Execution Context Safety"
+#define PROGRAM_STRUCTURE "Program Structure"
+#define POLICY "Policy"
+
+#define BPF_DIAG_TEXT_WIDTH 100
+#define BPF_DIAG_TEXT_INDENT " "
+#define BPF_DIAG_CONTEXT 2
+#define BPF_DIAG_CONTEXT_CNT (1 + BPF_DIAG_CONTEXT * 2)
+#define BPF_DIAG_HISTORY_RENDER_MAX 64
+#define BPF_DIAG_SOURCE_LANE_WIDTH 88
+#define BPF_DIAG_TAB_WIDTH 8
+#define BPF_DIAG_FMT_CHUNK_SIZE (PAGE_SIZE - sizeof(struct diag_fmt_chunk))
+#define BPF_DIAG_FMT_BUF_SIZE 256
+#define BPF_DIAG_EVENT_LOG_MAX_SIZE (64U << 20)
+#define DISASM_LINE_LEN 160
+
+enum bpf_diag_mod_target_kind {
+ BPF_DIAG_MOD_TARGET_NONE,
+ BPF_DIAG_MOD_TARGET_REG,
+ BPF_DIAG_MOD_TARGET_STACK_ARG,
+ BPF_DIAG_MOD_TARGET_STACK_SLOT,
+ BPF_DIAG_MOD_TARGET_STACK_RANGE,
+};
+
+struct bpf_diag_mod_target {
+ u32 frame_id;
+ union {
+ struct {
+ s16 min_off;
+ s16 max_off;
+ } range;
+ u16 spi;
+ u8 regno;
+ u8 stack_arg;
+ };
+ u8 frameno;
+ u8 kind;
+};
+
+static struct bpf_diag_mod_target diag_reg_target(u32 frame_id, u8 frameno, u8 regno)
+{
+ return (struct bpf_diag_mod_target){
+ .frame_id = frame_id,
+ .frameno = frameno,
+ .kind = BPF_DIAG_MOD_TARGET_REG,
+ .regno = regno,
+ };
+}
+
+static struct bpf_diag_mod_target diag_stack_arg_target(u32 frame_id, u8 frameno, u8 slot)
+{
+ return (struct bpf_diag_mod_target){
+ .frame_id = frame_id,
+ .frameno = frameno,
+ .kind = BPF_DIAG_MOD_TARGET_STACK_ARG,
+ .stack_arg = slot,
+ };
+}
+
+static struct bpf_diag_mod_target diag_stack_slot_target(u32 frame_id, u8 frameno, u16 spi)
+{
+ return (struct bpf_diag_mod_target){
+ .frame_id = frame_id,
+ .frameno = frameno,
+ .kind = BPF_DIAG_MOD_TARGET_STACK_SLOT,
+ .spi = spi,
+ };
+}
+
+static struct bpf_diag_mod_target diag_stack_range_target(u32 frame_id, u8 frameno,
+ s16 min_off, s16 max_off)
+{
+ return (struct bpf_diag_mod_target){
+ .frame_id = frame_id,
+ .frameno = frameno,
+ .kind = BPF_DIAG_MOD_TARGET_STACK_RANGE,
+ .range.min_off = min_off,
+ .range.max_off = max_off,
+ };
+}
+
+struct bpf_diag_reg_snapshot {
+ u32 type;
+ u32 btf_id;
+ const struct bpf_map *map_ptr;
+ const struct btf *btf;
+ struct tnum var_off;
+ struct cnum64 r64;
+};
+
+enum bpf_diag_history_kind {
+ BPF_DIAG_HISTORY_BRANCH,
+ BPF_DIAG_HISTORY_MOD,
+ BPF_DIAG_HISTORY_REF_ACQUIRE,
+ BPF_DIAG_HISTORY_REF_RELEASE,
+ BPF_DIAG_HISTORY_CONTEXT,
+};
+
+struct bpf_diag_history_event {
+ u32 insn_idx : 24;
+ u32 kind : 8;
+ u8 in_lineage : 1;
+ union {
+ struct {
+ bool cond_true;
+ } branch;
+ struct {
+ struct bpf_diag_mod_target target;
+ struct bpf_diag_mod_target origin;
+ struct bpf_diag_reg_snapshot old, new;
+ u8 reason;
+ bool origin_valid;
+ } mod;
+ struct {
+ u32 ref_id;
+ } ref;
+ struct {
+ u32 depth;
+ u8 kind;
+ bool enter;
+ } ctx;
+ };
+};
+
+enum bpf_diag_history_scope {
+ BPF_DIAG_HISTORY_SCOPE_REG,
+ BPF_DIAG_HISTORY_SCOPE_STACK_ARG,
+ BPF_DIAG_HISTORY_SCOPE_REF,
+ BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+};
+
+struct bpf_diag_history_opts {
+ enum bpf_diag_history_scope scope;
+ u32 frame_id;
+ u32 frameno;
+ int regno;
+ int stack_arg_slot;
+ u32 ref_id;
+ enum bpf_diag_context_kind ctx_kind;
+ u32 ctx_depth;
+};
+
+static void diag_print_history(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_opts *opts);
+static bool diag_target_matches(const struct bpf_diag_mod_target *event_target,
+ const struct bpf_diag_mod_target *target);
+static const char *diag_context_name(enum bpf_diag_context_kind kind);
+struct disasm_line {
+ char text[DISASM_LINE_LEN];
+ int idx;
+ bool valid;
+};
+
+struct disasm_ctx {
+ struct bpf_verifier_env *env;
+ struct seq_buf seq;
+};
+
+struct diag_fmt_chunk {
+ struct list_head node;
+ struct seq_buf seq;
+ char data[];
+};
+
+struct diag_fmt_mark {
+ struct diag_fmt_chunk *chunk;
+ size_t len;
+};
+
+struct bpf_diag_log {
+ struct bpf_diag_history_event *events;
+ /* Sequence number of the oldest retained event on the active path. */
+ u64 first_seq;
+ u32 cnt;
+ u32 cap;
+ u32 head;
+ bool growth_failed;
+};
+
+struct bpf_diag_scratch {
+ struct bpf_linfo_source source_lines[BPF_DIAG_CONTEXT_CNT];
+ struct disasm_line disasm_lines[BPF_DIAG_CONTEXT_CNT];
+};
+
+struct bpf_diag_mod_scope {
+ struct bpf_reg_state target_reg_snapshot;
+ struct bpf_diag_mod_target target;
+ struct bpf_diag_mod_target origin;
+ enum bpf_diag_mod_reason reason;
+ u32 insn_idx;
+ bool active;
+ bool origin_valid;
+};
+
+struct bpf_diag {
+ struct bpf_diag_log log;
+ struct bpf_diag_scratch scratch;
+ struct list_head fmt_chunks;
+ struct bpf_diag_mod_scope mod;
+ u32 frame_id_gen;
+};
+
+bool bpf_diag_enabled(const struct bpf_verifier_env *env)
+{
+ return env->log.level & BPF_LOG_LEVEL;
+}
+
+static void diag_write(struct bpf_verifier_env *env, const char *fmt, ...) __printf(2, 3);
+
+int bpf_diag_init(struct bpf_verifier_env *env)
+{
+ if (!bpf_diag_enabled(env))
+ return 0;
+
+ env->diag = kzalloc_obj(struct bpf_diag, GFP_KERNEL_ACCOUNT);
+ if (!env->diag)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&env->diag->fmt_chunks);
+ return 0;
+}
+
+void bpf_diag_init_frame(struct bpf_verifier_env *env, struct bpf_func_state *state)
+{
+ if (env->diag)
+ state->diag_frame_id = ++env->diag->frame_id_gen;
+}
+
+static char *diag_fmt_alloc(struct bpf_verifier_env *env, size_t size)
+{
+ struct bpf_diag *diag = env->diag;
+ struct diag_fmt_chunk *chunk;
+ size_t capacity, available;
+ char *buf;
+
+ if (!diag || !size || size > INT_MAX)
+ return NULL;
+
+ if (!list_empty(&diag->fmt_chunks)) {
+ chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);
+ available = seq_buf_get_buf(&chunk->seq, &buf);
+ if (available >= size)
+ goto commit;
+ }
+
+ capacity = max_t(size_t, BPF_DIAG_FMT_CHUNK_SIZE, size);
+ chunk = kmalloc_flex(*chunk, data, capacity, GFP_KERNEL_ACCOUNT);
+ if (!chunk)
+ return NULL;
+
+ seq_buf_init(&chunk->seq, chunk->data, capacity);
+ list_add_tail(&chunk->node, &diag->fmt_chunks);
+ available = seq_buf_get_buf(&chunk->seq, &buf);
+ if (WARN_ON_ONCE(available < size))
+ return NULL;
+
+commit:
+ seq_buf_commit(&chunk->seq, size);
+ return buf;
+}
+
+char *bpf_diag_fmt_buf(struct bpf_verifier_env *env, size_t size)
+{
+ char *buf;
+
+ buf = diag_fmt_alloc(env, size);
+ if (buf)
+ buf[0] = '\0';
+ return buf;
+}
+
+const char *bpf_diag_vfmt(struct bpf_verifier_env *env, const char *fmt, va_list args)
+{
+ va_list copy;
+ char *buf;
+ int len;
+
+ va_copy(copy, args);
+ len = vsnprintf(NULL, 0, fmt, copy);
+ va_end(copy);
+ if (len < 0 || len == INT_MAX)
+ return "";
+
+ buf = diag_fmt_alloc(env, len + 1);
+ if (buf)
+ vsnprintf(buf, len + 1, fmt, args);
+ return buf ?: "";
+}
+
+const char *bpf_diag_fmt(struct bpf_verifier_env *env, const char *fmt, ...)
+{
+ const char *buf;
+ va_list args;
+
+ va_start(args, fmt);
+ buf = bpf_diag_vfmt(env, fmt, args);
+ va_end(args);
+ return buf;
+}
+
+static struct diag_fmt_mark diag_fmt_save(struct bpf_verifier_env *env)
+{
+ struct bpf_diag *diag = env->diag;
+ struct diag_fmt_mark mark = {};
+
+ if (!diag || list_empty(&diag->fmt_chunks))
+ return mark;
+
+ mark.chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);
+ mark.len = mark.chunk->seq.len;
+ return mark;
+}
+
+static void diag_fmt_restore(struct bpf_verifier_env *env, struct diag_fmt_mark mark)
+{
+ struct bpf_diag *diag = env->diag;
+ struct diag_fmt_chunk *chunk;
+
+ if (!diag)
+ return;
+
+ while (!list_empty(&diag->fmt_chunks)) {
+ chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);
+ if (chunk == mark.chunk)
+ break;
+ list_del(&chunk->node);
+ kfree(chunk);
+ }
+
+ if (mark.chunk) {
+ mark.chunk->seq.len = mark.len;
+ seq_buf_str(&mark.chunk->seq);
+ }
+}
+
+void bpf_diag_free(struct bpf_verifier_env *env)
+{
+ struct bpf_diag *diag = env->diag;
+
+ if (!diag)
+ return;
+
+ diag_fmt_restore(env, (struct diag_fmt_mark){});
+ kvfree(diag->log.events);
+ kfree(diag);
+ env->diag = NULL;
+}
+
+static void diag_write(struct bpf_verifier_env *env, const char *fmt, ...)
+{
+ va_list args;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ va_start(args, fmt);
+ bpf_verifier_vlog(&env->log, fmt, args);
+ va_end(args);
+}
+
+static u64 log_end(const struct bpf_diag_log *log)
+{
+ return log->first_seq + log->cnt;
+}
+
+static u32 log_pos(const struct bpf_diag_log *log, u32 idx)
+{
+ u32 pos = log->head + idx;
+
+ return pos < log->cap ? pos : pos - log->cap;
+}
+
+u64 bpf_diag_event_log_save(struct bpf_verifier_env *env)
+{
+ struct bpf_diag *diag = env->diag;
+
+ return diag ? log_end(&diag->log) : 0;
+}
+
+void bpf_diag_event_log_restore(struct bpf_verifier_env *env, u64 log_pos)
+{
+ struct bpf_diag *diag = env->diag;
+ struct bpf_diag_log *log;
+ u64 end_seq;
+
+ if (!diag)
+ return;
+
+ log = &diag->log;
+ end_seq = log_end(log);
+ if (WARN_ON_ONCE(log_pos > end_seq))
+ log_pos = end_seq;
+
+ /*
+ * A deep abandoned path may have rotated away the shared prefix. In
+ * that case, restart with an empty retained suffix and remember that
+ * every event before the restored mark is unavailable.
+ */
+ if (log_pos <= log->first_seq) {
+ log->first_seq = log_pos;
+ log->head = 0;
+ log->cnt = 0;
+ return;
+ }
+
+ log->cnt = log_pos - log->first_seq;
+}
+
+u32 bpf_diag_irq_depth(const struct bpf_verifier_state *state)
+{
+ u32 depth = 0;
+ int i;
+
+ for (i = 0; i < state->acquired_refs; i++) {
+ if (state->refs[i].type == REF_TYPE_IRQ)
+ depth++;
+ }
+
+ return depth;
+}
+
+static void diag_append_history(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_event *event)
+{
+ struct bpf_diag_history_event *events;
+ struct bpf_diag *diag = env->diag;
+ struct bpf_diag_log *log;
+ u32 cap, max_events;
+
+ if (!diag)
+ return;
+ log = &diag->log;
+
+ if (log->cnt < log->cap) {
+ log->events[log_pos(log, log->cnt++)] = *event;
+ return;
+ }
+
+ max_events = BPF_DIAG_EVENT_LOG_MAX_SIZE / sizeof(*events);
+ if (log->growth_failed || log->cap == max_events)
+ goto rotate;
+
+ cap = min(log->cap ? log->cap * 2 : 64, max_events);
+ events = kvrealloc(log->events, array_size(cap, sizeof(*events)), GFP_KERNEL_ACCOUNT);
+ if (!events) {
+ log->growth_failed = true;
+ goto rotate;
+ }
+ log->events = events;
+ log->cap = cap;
+ log->events[log->cnt++] = *event;
+ return;
+
+rotate:
+ if (log->cap) {
+ log->events[log->head++] = *event;
+ if (log->head == log->cap)
+ log->head = 0;
+ }
+ log->first_seq++;
+}
+
+static void diag_print_wrapped_prefixed(struct bpf_verifier_env *env, const char *first_prefix,
+ const char *next_prefix, const char *text)
+{
+ const char *prefix = first_prefix;
+
+ while (*text) {
+ const char *line = text;
+ int prefix_len = strlen(prefix);
+ int text_width = BPF_DIAG_TEXT_WIDTH - prefix_len;
+ int len = 0, last_space = -1;
+
+ if (text_width < 1)
+ text_width = 1;
+
+ while (line[len] && line[len] != '\n' && len < text_width) {
+ if (line[len] == ' ')
+ last_space = len;
+ len++;
+ }
+
+ if (line[len] && line[len] != '\n' && line[len] != ' ' && last_space > 0)
+ len = last_space;
+
+ diag_write(env, "%s%.*s\n", prefix, len, line);
+
+ text = line + len;
+ while (*text == ' ')
+ text++;
+ if (*text == '\n')
+ text++;
+
+ prefix = next_prefix;
+ }
+}
+
+const char *bpf_diag_fmt_btf_type(struct bpf_verifier_env *env, const struct btf *btf, u32 type_id)
+{
+ char *buf = bpf_diag_fmt_buf(env, BPF_DIAG_FMT_BUF_SIZE);
+ size_t len;
+ int ret;
+
+ if (!buf)
+ return "";
+
+ buf[0] = '\0';
+ ret = btf_type_name_to_buf(btf, type_id, buf, BPF_DIAG_FMT_BUF_SIZE);
+ if (ret < 0 || !buf[0]) {
+ scnprintf(buf, BPF_DIAG_FMT_BUF_SIZE, "BTF type ID %u", type_id);
+ return buf;
+ }
+
+ len = strlen(buf);
+ if (len && buf[len - 1] == '{')
+ buf[len - 1] = '\0';
+ return buf;
+}
+
+static void diag_vprint_indented(struct bpf_verifier_env *env, const char *fmt, va_list args)
+ __printf(2, 0);
+
+static void diag_vprint_indented(struct bpf_verifier_env *env, const char *fmt, va_list args)
+{
+ char *buf;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ buf = kvasprintf(GFP_KERNEL_ACCOUNT, fmt, args);
+ if (!buf) {
+ diag_write(env, "%s<failed to allocate diagnostic text>\n", BPF_DIAG_TEXT_INDENT);
+ return;
+ }
+
+ diag_print_wrapped_prefixed(env, BPF_DIAG_TEXT_INDENT, BPF_DIAG_TEXT_INDENT, buf);
+ kfree(buf);
+}
+
+static int diag_line_width(unsigned int line)
+{
+ int width = 1;
+
+ while (line >= 10) {
+ line /= 10;
+ width++;
+ }
+
+ return width;
+}
+
+static int diag_line_indent(const char *line)
+{
+ int indent = 0;
+
+ while (*line == ' ' || *line == '\t') {
+ if (*line == '\t')
+ indent = round_up(indent + 1, BPF_DIAG_TAB_WIDTH);
+ else
+ indent++;
+ line++;
+ }
+
+ return indent;
+}
+
+static void disasm_print(void *private_data, const char *fmt, ...) __printf(2, 3);
+
+static void disasm_print(void *private_data, const char *fmt, ...)
+{
+ struct disasm_ctx *ctx = private_data;
+ va_list args;
+
+ va_start(args, fmt);
+ seq_buf_vprintf(&ctx->seq, fmt, args);
+ va_end(args);
+}
+
+static const char *disasm_kfunc_name(void *private_data, const struct bpf_insn *insn)
+{
+ struct disasm_ctx *ctx = private_data;
+
+ return bpf_disasm_kfunc_name(ctx->env, insn);
+}
+
+static void format_disasm_line(struct bpf_verifier_env *env, int insn_idx,
+ struct disasm_line *line)
+{
+ struct disasm_ctx ctx = { .env = env };
+ struct bpf_insn *insn;
+ const struct bpf_insn_cbs cbs = {
+ .cb_call = disasm_kfunc_name,
+ .cb_print = disasm_print,
+ .private_data = &ctx,
+ };
+
+ line->idx = insn_idx;
+ line->valid = false;
+ seq_buf_init(&ctx.seq, line->text, sizeof(line->text));
+
+ if (insn_idx < 0 || insn_idx >= env->prog->len)
+ return;
+
+ if (insn_idx > 0 && bpf_is_ldimm64(&env->prog->insnsi[insn_idx - 1]))
+ return;
+
+ insn = &env->prog->insnsi[insn_idx];
+ if (bpf_is_ldimm64(insn) && insn_idx + 1 >= env->prog->len)
+ return;
+
+ print_bpf_insn(&cbs, insn, env->allow_ptr_leaks);
+ seq_buf_str(&ctx.seq);
+ ctx.seq.len = strnlen(line->text, sizeof(line->text));
+ while (ctx.seq.len && line->text[ctx.seq.len - 1] == '\n')
+ seq_buf_pop(&ctx.seq);
+ seq_buf_str(&ctx.seq);
+
+ line->valid = true;
+}
+
+static void diag_format_source_text(char *buf, size_t size, const char *line, int width)
+{
+ int col = 0, len = 0;
+
+ if (!size)
+ return;
+ if (width <= 0) {
+ buf[0] = '\0';
+ return;
+ }
+
+ line = line ?: "...";
+ while (*line && col < width && len + 1 < size) {
+ if (*line == '\t') {
+ int next = round_up(col + 1, BPF_DIAG_TAB_WIDTH);
+
+ while (col < next && col < width && len + 1 < size) {
+ buf[len++] = ' ';
+ col++;
+ }
+ line++;
+ continue;
+ }
+
+ buf[len++] = *line++;
+ col++;
+ }
+
+ if (*line) {
+ int ellipsis_len = min(3, width);
+
+ while (len > 0 && col > width - ellipsis_len) {
+ len--;
+ col--;
+ }
+ while (ellipsis_len-- && len + 1 < size)
+ buf[len++] = '.';
+ }
+
+ buf[len] = '\0';
+}
+
+static void diag_format_source_lane(char *buf, size_t size, const char *source_prefix,
+ int source_line_width, int line_num, const char *line)
+{
+ int len, text_width;
+
+ if (line_num <= 0) {
+ buf[0] = '\0';
+ return;
+ }
+
+ len = scnprintf(buf, size, "%s%*d | ", source_prefix, source_line_width, line_num);
+ text_width = BPF_DIAG_SOURCE_LANE_WIDTH - len;
+ diag_format_source_text(buf + len, size - len, line, text_width);
+}
+
+static void bpf_diag_header(struct bpf_verifier_env *env, const char *category,
+ const char *problem)
+{
+ char first;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ category = category ?: "Verifier Error";
+ problem = problem ?: "";
+
+ if (!problem[0]) {
+ diag_write(env, "\nVerification failed: %s\n", category);
+ return;
+ }
+
+ first = toupper(problem[0]);
+ diag_write(env, "\nVerification failed: %s: %c%s\n", category, first, problem + 1);
+}
+
+static void diag_reason(struct bpf_verifier_env *env, const char *fmt, ...) __printf(2, 3);
+static void diag_suggestion(struct bpf_verifier_env *env, const char *fmt, ...)
+ __printf(2, 3);
+
+static void diag_section(struct bpf_verifier_env *env, const char *title)
+{
+ if (!bpf_diag_enabled(env))
+ return;
+
+ diag_write(env, "\n%s:\n", title);
+}
+
+static void diag_reason(struct bpf_verifier_env *env, const char *fmt, ...)
+{
+ va_list args;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ diag_section(env, "Reason");
+
+ va_start(args, fmt);
+ diag_vprint_indented(env, fmt, args);
+ va_end(args);
+}
+
+static void diag_suggestion(struct bpf_verifier_env *env, const char *fmt, ...)
+{
+ va_list args;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ diag_section(env, "Suggestion");
+
+ va_start(args, fmt);
+ diag_vprint_indented(env, fmt, args);
+ va_end(args);
+ diag_write(env, "\n");
+}
+
+static void diag_print_source_annotation(struct bpf_verifier_env *env, int line_width, int indent,
+ const char *label, const char *msg)
+{
+ const char *first_prefix, *next_prefix, *text;
+
+ indent = min_t(int, indent, max_t(int, 0, BPF_DIAG_SOURCE_LANE_WIDTH - line_width - 8));
+ text = bpf_diag_fmt(env, "%s: %s", label, msg);
+ first_prefix = bpf_diag_fmt(env, " %*s | %*s^-- ", line_width + 4, "", indent, "");
+ next_prefix = bpf_diag_fmt(env, " %*s | %*s ", line_width + 4, "", indent, "");
+
+ diag_print_wrapped_prefixed(env, first_prefix, next_prefix, text);
+}
+
+static void diag_print_insn_context(struct bpf_verifier_env *env, u32 insn_idx,
+ struct disasm_line *disasm_lines)
+{
+ int insn_width = diag_line_width(env->prog->len ? env->prog->len - 1 : 0);
+ int i;
+
+ for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++) {
+ int row = i - BPF_DIAG_CONTEXT;
+
+ format_disasm_line(env, insn_idx + row, &disasm_lines[i]);
+ }
+
+ diag_write(env, " Instruction context:\n");
+ for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++) {
+ struct disasm_line *line = &disasm_lines[i];
+
+ if (line->valid)
+ diag_write(env, " %s%*d | %s\n",
+ line->idx == insn_idx ? ">>> " : " ",
+ insn_width, line->idx, line->text);
+ }
+}
+
+static void bpf_diag_source(struct bpf_verifier_env *env, u32 insn_idx, const char *label,
+ const char *fmt, ...)
+{
+ struct bpf_diag_scratch *scratch;
+ struct bpf_linfo_source *source_lines;
+ struct disasm_line *disasm_lines;
+ struct bpf_linfo_source src = {};
+ struct diag_fmt_mark mark;
+ const struct bpf_line_info *linfo;
+ const struct bpf_subprog_info *subprog;
+ struct btf *btf = env->prog->aux->btf;
+ char *source_lane;
+ const char *msg;
+ const char *func;
+ int start_line, end_line, width, indent, subprogno, linfo_start, linfo_end, i;
+ va_list args;
+
+ if (!bpf_diag_enabled(env))
+ return;
+ if (!env->diag)
+ return;
+
+ mark = diag_fmt_save(env);
+ label = label ?: "note";
+ scratch = &env->diag->scratch;
+ source_lines = scratch->source_lines;
+ disasm_lines = scratch->disasm_lines;
+ memset(source_lines, 0, sizeof(scratch->source_lines));
+ memset(disasm_lines, 0, sizeof(scratch->disasm_lines));
+
+ va_start(args, fmt);
+ msg = bpf_diag_vfmt(env, fmt, args);
+ va_end(args);
+ if (!*msg)
+ msg = "<failed to allocate diagnostic text>";
+
+ linfo = bpf_find_linfo(env->prog, insn_idx);
+ if (btf && linfo)
+ bpf_get_linfo_source(btf, linfo, &src);
+ if (!src.file || !*src.file) {
+ diag_write(env, " insn %u\n", insn_idx);
+ goto out_annotation;
+ }
+
+ subprog = bpf_find_containing_subprog(env, insn_idx);
+ subprogno = subprog ? subprog - env->subprog_info : -ENOENT;
+ func = subprogno >= 0 ? bpf_subprog_name(env, subprogno) : NULL;
+ if (func && *func)
+ diag_write(env, " %s @ %s:%d:%d\n", func, src.file, src.line_num, src.line_col);
+ else
+ diag_write(env, " %s:%d:%d\n", src.file, src.line_num, src.line_col);
+ if (!src.line || !*src.line)
+ goto out_annotation;
+
+ start_line = src.line_num - BPF_DIAG_CONTEXT;
+ end_line = src.line_num + BPF_DIAG_CONTEXT;
+ width = diag_line_width(end_line);
+ indent = diag_line_indent(src.line);
+ for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++)
+ source_lines[i].line_num = start_line + i;
+
+ linfo = env->prog->aux->linfo;
+ linfo_start = subprog ? subprog->linfo_idx : 0;
+ linfo_end = subprogno >= 0 && subprogno + 1 < env->subprog_cnt ?
+ env->subprog_info[subprogno + 1].linfo_idx : env->prog->aux->nr_linfo;
+ for (i = linfo_start; i < linfo_end; i++) {
+ struct bpf_linfo_source line_src;
+ int idx;
+
+ bpf_get_linfo_source(btf, &linfo[i], &line_src);
+ if (line_src.file_name_off != src.file_name_off ||
+ line_src.line_num < start_line || line_src.line_num > end_line ||
+ !line_src.line || !*line_src.line)
+ continue;
+
+ idx = line_src.line_num - start_line;
+ if (!source_lines[idx].line)
+ source_lines[idx] = line_src;
+ }
+
+ diag_write(env, " Source context:\n");
+ source_lane = bpf_diag_fmt_buf(env, BPF_DIAG_FMT_BUF_SIZE);
+ if (!source_lane)
+ goto out_restore;
+ for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++) {
+ const char *source_prefix;
+
+ source_prefix = source_lines[i].line_num == src.line_num ? ">>> " : " ";
+ diag_format_source_lane(source_lane, BPF_DIAG_FMT_BUF_SIZE, source_prefix, width,
+ source_lines[i].line_num, source_lines[i].line);
+ diag_write(env, " %s\n", source_lane);
+ if (source_lines[i].line_num == src.line_num)
+ diag_print_source_annotation(env, width, indent, label, msg);
+ }
+ diag_print_insn_context(env, insn_idx, disasm_lines);
+ goto out_restore;
+
+out_annotation:
+ diag_print_source_annotation(env, 0, 0, label, msg);
+ diag_print_insn_context(env, insn_idx, disasm_lines);
+out_restore:
+ diag_fmt_restore(env, mark);
+}
+
+static const struct bpf_func_state *diag_current_frame(const struct bpf_verifier_env *env)
+{
+ return env->cur_state->frame[env->cur_state->curframe];
+}
+
+void bpf_diag_register_type(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *problem, const char *reason, const char *suggestion)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REG,
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ .regno = regno,
+ };
+
+ bpf_diag_header(env, REGISTER_TYPE_SAFETY, problem);
+ diag_reason(env, "%s", reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s", problem);
+
+ if (regno >= 0)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+const char *bpf_diag_reg_type_plain(struct bpf_verifier_env *env, enum bpf_reg_type type)
+{
+ switch (base_type(type)) {
+ case NOT_INIT:
+ return "an uninitialized value";
+ case SCALAR_VALUE:
+ return "an integer scalar";
+ case PTR_TO_CTX:
+ return "a context pointer";
+ case PTR_TO_STACK:
+ return "a stack pointer";
+ case PTR_TO_MAP_VALUE:
+ if (type_may_be_null(type))
+ return "a nullable map value pointer";
+ return "a map value pointer";
+ case PTR_TO_MEM:
+ if (type_may_be_null(type))
+ return "a nullable memory pointer";
+ return "a memory pointer";
+ case PTR_TO_BTF_ID:
+ if (type_may_be_null(type))
+ return "a nullable kernel object pointer";
+ if (type_is_non_owning_ref(type))
+ return "a borrowed allocated object pointer";
+ if (type_is_ptr_alloc_obj(type))
+ return "an owned allocated object pointer";
+ if (type_flag(type) & PTR_UNTRUSTED)
+ return "an untrusted kernel object pointer";
+ return "a kernel object pointer";
+ default:
+ return reg_type_str(env, type);
+ }
+}
+
+static const char *diag_arg_ordinal(int argno)
+{
+ switch (argno) {
+ case 1:
+ return "first";
+ case 2:
+ return "second";
+ case 3:
+ return "third";
+ case 4:
+ return "fourth";
+ case 5:
+ return "fifth";
+ case 6:
+ return "sixth";
+ case 7:
+ return "seventh";
+ case 8:
+ return "eighth";
+ case 9:
+ return "ninth";
+ case 10:
+ return "tenth";
+ case 11:
+ return "eleventh";
+ case 12:
+ return "twelfth";
+ default:
+ return NULL;
+ }
+}
+
+void bpf_diag_call_type(struct bpf_verifier_env *env, u32 insn_idx, int argno, int regno,
+ int stack_arg_slot, const char *call_name, const char *arg_name,
+ const char *reason, const char *suggestion)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ };
+ const char *ordinal = diag_arg_ordinal(argno);
+ const char *arg_desc;
+ bool print_history = true;
+
+ if (regno >= 0) {
+ opts.scope = BPF_DIAG_HISTORY_SCOPE_REG;
+ opts.regno = regno;
+ } else if (stack_arg_slot >= 0) {
+ opts.scope = BPF_DIAG_HISTORY_SCOPE_STACK_ARG;
+ opts.stack_arg_slot = stack_arg_slot;
+ } else {
+ print_history = false;
+ }
+
+ if (ordinal && arg_name)
+ arg_desc = bpf_diag_fmt(env, "%s argument (%s)", ordinal, arg_name);
+ else if (ordinal)
+ arg_desc = bpf_diag_fmt(env, "%s argument", ordinal);
+ else if (arg_name)
+ arg_desc = bpf_diag_fmt(env, "argument %s", arg_name);
+ else
+ arg_desc = "argument";
+
+ bpf_diag_header(env, CALL_TYPE_SAFETY, "invalid call argument");
+ diag_reason(env, "The %s to %s does not satisfy the verifier contract: %s.",
+ arg_desc, call_name, reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "invalid %s for %s", arg_desc, call_name);
+
+ if (print_history)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+static const char *diag_context_constraint(enum bpf_diag_context_kind kind)
+{
+ switch (kind) {
+ case BPF_DIAG_CONTEXT_RCU:
+ return "RCU read-side critical sections cannot call operations that may sleep";
+ case BPF_DIAG_CONTEXT_PREEMPT:
+ return "preemption-disabled code cannot call operations that may sleep";
+ case BPF_DIAG_CONTEXT_IRQ:
+ return "IRQ-disabled code cannot call operations that may sleep";
+ case BPF_DIAG_CONTEXT_LOCK:
+ return "code holding a BPF spin lock cannot call operations that may sleep";
+ case BPF_DIAG_CONTEXT_NONE:
+ default:
+ return NULL;
+ }
+}
+
+static const char *diag_active_context(struct bpf_verifier_env *env, u32 depth,
+ const char *context)
+{
+ if (depth == 1)
+ return bpf_diag_fmt(env, "an active %s (depth 1)", context);
+ return bpf_diag_fmt(env, "%u active %ss (depth %u)", depth, context, depth);
+}
+
+static u32 diag_context_depth(struct bpf_verifier_env *env, enum bpf_diag_context_kind kind)
+{
+ switch (kind) {
+ case BPF_DIAG_CONTEXT_RCU:
+ return env->cur_state->active_rcu_locks;
+ case BPF_DIAG_CONTEXT_PREEMPT:
+ return env->cur_state->active_preempt_locks;
+ case BPF_DIAG_CONTEXT_IRQ:
+ return bpf_diag_irq_depth(env->cur_state);
+ case BPF_DIAG_CONTEXT_LOCK:
+ return env->cur_state->active_locks;
+ case BPF_DIAG_CONTEXT_NONE:
+ default:
+ return 0;
+ }
+}
+
+void bpf_diag_ctx_forbidden(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *operation, const char *suggestion)
+{
+ struct bpf_diag_history_opts opts;
+ enum bpf_diag_context_kind ctx_kind;
+ const char *constraint, *context;
+ u32 depth;
+
+ if (env->cur_state->active_rcu_locks)
+ ctx_kind = BPF_DIAG_CONTEXT_RCU;
+ else if (env->cur_state->active_preempt_locks)
+ ctx_kind = BPF_DIAG_CONTEXT_PREEMPT;
+ else if (env->cur_state->active_irq_id)
+ ctx_kind = BPF_DIAG_CONTEXT_IRQ;
+ else if (env->cur_state->active_locks)
+ ctx_kind = BPF_DIAG_CONTEXT_LOCK;
+ else
+ ctx_kind = BPF_DIAG_CONTEXT_NONE;
+
+ depth = diag_context_depth(env, ctx_kind);
+ opts = (struct bpf_diag_history_opts) {
+ .scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+ .ctx_kind = ctx_kind,
+ .ctx_depth = depth,
+ };
+ constraint = diag_context_constraint(ctx_kind);
+ context = diag_context_name(ctx_kind);
+
+ bpf_diag_header(env, EXECUTION_CONTEXT_SAFETY,
+ "operation is not allowed in this context");
+ if (constraint) {
+ if (depth) {
+ diag_reason(
+ env, "The operation %s cannot be used in %s because %s. This path is still inside %s.",
+ operation, context, constraint, diag_active_context(env, depth, context));
+ } else {
+ diag_reason(env, "The operation %s cannot be used in %s because %s.",
+ operation, context, constraint);
+ }
+ } else {
+ diag_reason(env, "The operation %s cannot be used in %s.", operation,
+ context);
+ }
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s is not allowed in %s", operation,
+ context);
+
+ if (ctx_kind != BPF_DIAG_CONTEXT_NONE)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_ctx_active(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ enum bpf_diag_context_kind ctx_kind, const char *suggestion)
+{
+ u32 depth = diag_context_depth(env, ctx_kind);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+ .ctx_kind = ctx_kind,
+ .ctx_depth = depth,
+ };
+ const char *context = diag_context_name(ctx_kind);
+
+ bpf_diag_header(env, EXECUTION_CONTEXT_SAFETY,
+ "operation is not allowed in this context");
+ diag_reason(
+ env, "The operation %s cannot be used while this path is still inside %s. Leave the region before this operation.",
+ operation, diag_active_context(env, depth, context));
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s is not allowed before leaving %s",
+ operation, context);
+
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_ctx_required(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ enum bpf_diag_context_kind ctx_kind, const char *suggestion)
+{
+ const char *context = diag_context_name(ctx_kind);
+
+ bpf_diag_header(env, EXECUTION_CONTEXT_SAFETY, "required context is not active");
+ diag_reason(env, "The operation %s requires an active %s, but this path is outside one.",
+ operation, context);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s requires %s", operation, context);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_ctx_underflow(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *operation, enum bpf_diag_context_kind ctx_kind,
+ const char *suggestion)
+{
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+ .ctx_kind = ctx_kind,
+ };
+ const char *context = diag_context_name(ctx_kind);
+
+ bpf_diag_header(env, EXECUTION_CONTEXT_SAFETY, "unmatched context exit");
+ diag_reason(
+ env, "The operation %s tries to leave %s, but this path has no active %s to leave. The current depth is 0.",
+ operation, context, context);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s has no matching enter on this path",
+ operation);
+
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_program_structure(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *problem, const char *suggestion,
+ const char *reason_fmt, ...)
+{
+ va_list args;
+
+ bpf_diag_header(env, PROGRAM_STRUCTURE, problem);
+ diag_section(env, "Reason");
+
+ va_start(args, reason_fmt);
+ diag_vprint_indented(env, reason_fmt, args);
+ va_end(args);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s", problem);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_policy(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ const char *reason, const char *suggestion)
+{
+ bpf_diag_header(env, POLICY, "operation is not allowed");
+ diag_reason(env, "The %s is not allowed: %s.", operation, reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "policy check failed for %s", operation);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *reg_name, const struct bpf_reg_state *reg,
+ enum bpf_diag_invalid_deref_kind kind, s64 offset)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REG,
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ .regno = regno,
+ };
+ const char *type_name = bpf_diag_reg_type_plain(env, reg->type);
+
+ bpf_diag_header(env, REGISTER_TYPE_SAFETY, "invalid dereference");
+
+ switch (kind) {
+ case BPF_DIAG_DEREF_SCALAR:
+ diag_reason(env, "%s is an integer scalar here, not a pointer to memory.",
+ reg_name);
+ break;
+ case BPF_DIAG_DEREF_NULLABLE_PTR:
+ diag_reason(
+ env, "%s may be NULL here (%s). The program could dereference NULL on this path, so the verifier cannot prove this access is safe.",
+ reg_name, type_name);
+ break;
+ case BPF_DIAG_DEREF_MODIFIED_PTR:
+ diag_reason(
+ env, "%s has offset %lld here, but this pointer type must be dereferenced in its original form.",
+ reg_name, offset);
+ break;
+ case BPF_DIAG_DEREF_INVALID_PTR:
+ default:
+ diag_reason(
+ env, "%s has type %s here, which is not valid for this memory access.",
+ reg_name, type_name);
+ break;
+ }
+
+ diag_section(env, "At");
+ if (kind == BPF_DIAG_DEREF_MODIFIED_PTR)
+ bpf_diag_source(env, insn_idx, "error",
+ "dereference requires the original %s pointer", type_name);
+ else
+ bpf_diag_source(env, insn_idx, "error", "invalid dereference of %s (%s)",
+ reg_name, type_name);
+
+ if (regno >= 0)
+ diag_print_history(env, &opts);
+
+ switch (kind) {
+ case BPF_DIAG_DEREF_NULLABLE_PTR:
+ diag_suggestion(
+ env, "Add a NULL check before the access and dereference the pointer only on the non-NULL path.");
+ break;
+ case BPF_DIAG_DEREF_MODIFIED_PTR:
+ diag_suggestion(
+ env, "Preserve the original pointer in another register, or use only offsets this pointer type permits before dereferencing it.");
+ break;
+ case BPF_DIAG_DEREF_SCALAR:
+ case BPF_DIAG_DEREF_INVALID_PTR:
+ default:
+ diag_suggestion(
+ env, "Preserve a pointer-valued register where needed, or reload and revalidate the pointer after scalar arithmetic, helper calls, or other operations that can invalidate it.");
+ break;
+ }
+}
+
+void bpf_diag_unreadable_reg(struct bpf_verifier_env *env, u32 insn_idx, int regno)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REG,
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ .regno = regno,
+ };
+ const struct bpf_diag_log *log = env->diag ? &env->diag->log : NULL;
+ struct bpf_diag_mod_target target;
+ bool invalidated = false;
+ int i;
+
+ target = diag_reg_target(opts.frame_id, opts.frameno, regno);
+ for (i = log ? log->cnt : 0; i > 0; i--) {
+ const struct bpf_diag_history_event *event;
+
+ event = &log->events[log_pos(log, i - 1)];
+
+ if (event->kind != BPF_DIAG_HISTORY_MOD ||
+ !diag_target_matches(&event->mod.target, &target))
+ continue;
+ invalidated = event->mod.new.type == NOT_INIT;
+ break;
+ }
+
+ bpf_diag_header(env, REGISTER_TYPE_SAFETY, "unreadable register");
+ if (invalidated)
+ diag_reason(
+ env, "R%d is not readable here. A previous operation invalidated this register, so the verifier cannot use it as an input.",
+ regno);
+ else if (log && !log->first_seq)
+ diag_reason(env,
+ "R%d has never been initialized on this path, so the verifier cannot use it as an input.",
+ regno);
+ else
+ diag_reason(
+ env, "R%d is not readable here. It may never have been initialized, or an earlier operation may have invalidated it.",
+ regno);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "R%d is not readable", regno);
+
+ if (regno >= 0)
+ diag_print_history(env, &opts);
+
+ if (invalidated)
+ diag_suggestion(
+ env, "Avoid using the register after it is invalidated, or initialize it again before this instruction.");
+ else if (log && !log->first_seq)
+ diag_suggestion(env, "Initialize R%d on every path before this instruction.", regno);
+ else
+ diag_suggestion(
+ env, "Initialize the register on every path, or initialize it again after any operation that invalidates it.");
+}
+
+static int diag_stack_argno(u8 slot)
+{
+ return MAX_BPF_FUNC_REG_ARGS + slot + 1;
+}
+
+static void diag_format_stack_arg(char *buf, size_t size, u8 slot, const char *arg_name)
+{
+ int argno = diag_stack_argno(slot);
+ const char *ordinal = diag_arg_ordinal(argno);
+
+ if (ordinal && arg_name)
+ scnprintf(buf, size, "outgoing stack argument %u (%s argument, %s)", slot + 1,
+ ordinal, arg_name);
+ else if (ordinal)
+ scnprintf(buf, size, "outgoing stack argument %u (%s argument)", slot + 1, ordinal);
+ else if (arg_name)
+ scnprintf(buf, size, "outgoing stack argument %u (%s)", slot + 1, arg_name);
+ else
+ scnprintf(buf, size, "outgoing stack argument %u", slot + 1);
+}
+
+void bpf_diag_stack_arg_uninit(struct bpf_verifier_env *env, u32 insn_idx, int nargs,
+ int stack_arg_slot, const char *callee_name,
+ const char *arg_name)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_STACK_ARG,
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ .stack_arg_slot = stack_arg_slot,
+ };
+ const char *arg_buf;
+
+ arg_buf = bpf_diag_fmt_buf(env, BPF_DIAG_FMT_BUF_SIZE);
+ if (arg_buf)
+ diag_format_stack_arg((char *)arg_buf, BPF_DIAG_FMT_BUF_SIZE, stack_arg_slot,
+ arg_name);
+ else
+ arg_buf = "";
+ bpf_diag_header(env, REGISTER_TYPE_SAFETY, "missing stack argument");
+ if (callee_name && *callee_name)
+ diag_reason(
+ env, "Function %s expects %d arguments, but %s is not initialized at this call.",
+ callee_name, nargs, arg_buf);
+ else
+ diag_reason(
+ env, "The callee expects %d arguments, but %s is not initialized at this call.",
+ nargs, arg_buf);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s is not initialized", arg_buf);
+
+ if (stack_arg_slot >= 0)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(
+ env, "Write the outgoing stack argument after any operation that may invalidate stored pointer values, and before making this call.");
+}
+
+void bpf_diag_memory(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion)
+{
+ bpf_diag_header(env, MEMORY_SAFETY, problem);
+ diag_reason(env, "%s", reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s", problem);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx, bool cond_true)
+{
+ struct bpf_diag_history_event event = {
+ .insn_idx = insn_idx,
+ .kind = BPF_DIAG_HISTORY_BRANCH,
+ .branch = {
+ .cond_true = cond_true,
+ },
+ };
+
+ diag_append_history(env, &event);
+}
+
+static void diag_snapshot_reg(struct bpf_diag_reg_snapshot *snapshot,
+ const struct bpf_reg_state *reg)
+{
+ snapshot->type = reg->type;
+ if (type_is_map_ptr(reg->type))
+ snapshot->map_ptr = reg->map_ptr;
+ if (base_type(reg->type) == PTR_TO_BTF_ID && reg->btf && reg->btf_id) {
+ snapshot->btf_id = reg->btf_id;
+ snapshot->btf = reg->btf;
+ }
+ snapshot->var_off = reg->var_off;
+ snapshot->r64 = reg->r64;
+}
+
+static bool diag_mod_insn_origin(struct bpf_verifier_env *env, u32 insn_idx,
+ const struct bpf_diag_mod_target *target,
+ struct bpf_diag_mod_target *origin)
+{
+ const struct bpf_insn *insn = &env->prog->insnsi[insn_idx];
+ u8 class = BPF_CLASS(insn->code);
+ const struct bpf_func_state *state;
+
+ if (target->kind == BPF_DIAG_MOD_TARGET_REG && (class == BPF_ALU || class == BPF_ALU64) &&
+ BPF_OP(insn->code) == BPF_MOV && BPF_SRC(insn->code) == BPF_X) {
+ *origin = diag_reg_target(target->frame_id, target->frameno, insn->src_reg);
+ return true;
+ }
+
+ if ((target->kind != BPF_DIAG_MOD_TARGET_STACK_ARG &&
+ target->kind != BPF_DIAG_MOD_TARGET_STACK_SLOT) ||
+ class != BPF_STX)
+ return false;
+
+ state = env->cur_state->frame[env->cur_state->curframe];
+ *origin = diag_reg_target(state->diag_frame_id, state->frameno, insn->src_reg);
+ return true;
+}
+
+static bool diag_mod_keeps_lineage(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_event *event)
+{
+ const struct bpf_insn *insn;
+ u8 class;
+
+ if (event->mod.reason != BPF_DIAG_MOD_WRITE ||
+ event->mod.target.kind != BPF_DIAG_MOD_TARGET_REG)
+ return false;
+
+ insn = &env->prog->insnsi[event->insn_idx];
+ class = BPF_CLASS(insn->code);
+ if (class != BPF_ALU && class != BPF_ALU64)
+ return false;
+
+ switch (BPF_OP(insn->code)) {
+ case BPF_ADD:
+ case BPF_SUB:
+ case BPF_MUL:
+ case BPF_OR:
+ case BPF_AND:
+ case BPF_LSH:
+ case BPF_RSH:
+ case BPF_ARSH:
+ case BPF_XOR:
+ case BPF_NEG:
+ case BPF_END:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void diag_record_mod(struct bpf_verifier_env *env, u32 insn_idx,
+ struct bpf_diag_mod_target target,
+ enum bpf_diag_mod_reason reason,
+ const struct bpf_reg_state *old_reg,
+ const struct bpf_reg_state *new_reg,
+ const struct bpf_diag_mod_target *origin)
+{
+ struct bpf_diag_history_event event = {
+ .insn_idx = insn_idx,
+ .kind = BPF_DIAG_HISTORY_MOD,
+ .mod = {
+ .target = target,
+ .reason = reason,
+ },
+ };
+
+ if (old_reg)
+ diag_snapshot_reg(&event.mod.old, old_reg);
+ if (new_reg)
+ diag_snapshot_reg(&event.mod.new, new_reg);
+ if (origin) {
+ event.mod.origin = *origin;
+ event.mod.origin_valid = true;
+ } else if (diag_mod_insn_origin(env, insn_idx, &target, &event.mod.origin)) {
+ event.mod.origin_valid = true;
+ }
+ if (old_reg && new_reg &&
+ (reason == BPF_DIAG_MOD_WRITE || reason == BPF_DIAG_MOD_SPILL) &&
+ !memcmp(&event.mod.old, &event.mod.new, sizeof(event.mod.old)) &&
+ !event.mod.origin_valid &&
+ diag_mod_keeps_lineage(env, &event))
+ return;
+
+ diag_append_history(env, &event);
+}
+
+static struct bpf_reg_state *target_to_reg(struct bpf_verifier_env *env,
+ const struct bpf_diag_mod_target *target)
+{
+ struct bpf_verifier_state *vstate = env->cur_state;
+ struct bpf_func_state *state;
+
+ state = target->frameno <= vstate->curframe ? vstate->frame[target->frameno] : NULL;
+
+ if (!state)
+ return NULL;
+ if (state->diag_frame_id != target->frame_id)
+ return NULL;
+
+ switch (target->kind) {
+ case BPF_DIAG_MOD_TARGET_REG:
+ if (target->regno >= MAX_BPF_REG)
+ return NULL;
+ return &state->regs[target->regno];
+ case BPF_DIAG_MOD_TARGET_STACK_ARG:
+ if (target->stack_arg >= state->out_stack_arg_cnt)
+ return NULL;
+ return &state->stack_arg_regs[target->stack_arg];
+ case BPF_DIAG_MOD_TARGET_STACK_SLOT:
+ if (target->spi >= state->allocated_stack / BPF_REG_SIZE)
+ return NULL;
+ return &state->stack[target->spi].spilled_ptr;
+ default:
+ return NULL;
+ }
+}
+
+static bool reg_to_target(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,
+ struct bpf_diag_mod_target *target)
+{
+ struct bpf_verifier_state *vstate = env->cur_state;
+ unsigned long addr = (unsigned long)reg;
+ int frame;
+
+ for (frame = 0; frame <= vstate->curframe; frame++) {
+ struct bpf_func_state *state = vstate->frame[frame];
+ unsigned long start, end;
+ u32 nslots = state->allocated_stack / BPF_REG_SIZE;
+ int spi;
+
+ start = (unsigned long)state->regs;
+ end = (unsigned long)(state->regs + MAX_BPF_REG);
+ if (addr >= start && addr < end) {
+ *target = diag_reg_target(state->diag_frame_id, state->frameno,
+ reg - state->regs);
+ return true;
+ }
+
+ start = (unsigned long)state->stack_arg_regs;
+ end = (unsigned long)(state->stack_arg_regs + state->out_stack_arg_cnt);
+ if (state->out_stack_arg_cnt && addr >= start && addr < end) {
+ *target = diag_stack_arg_target(state->diag_frame_id, state->frameno,
+ reg - state->stack_arg_regs);
+ return true;
+ }
+
+ start = (unsigned long)state->stack;
+ end = (unsigned long)(state->stack + nslots);
+ if (nslots && addr >= start && addr < end) {
+ spi = ((const char *)reg - (const char *)state->stack) /
+ sizeof(*state->stack);
+ *target = diag_stack_slot_target(state->diag_frame_id, state->frameno, spi);
+ return true;
+ }
+ }
+ return false;
+}
+
+void bpf_diag_mod_begin(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,
+ const struct bpf_reg_state *origin, enum bpf_diag_mod_reason reason)
+{
+ struct bpf_diag *diag = env->diag;
+
+ if (!diag)
+ return;
+ diag->mod.active = reg_to_target(env, reg, &diag->mod.target);
+ if (!diag->mod.active)
+ return;
+ diag->mod.target_reg_snapshot = *reg;
+ diag->mod.insn_idx = env->insn_idx;
+ diag->mod.reason = reason;
+ diag->mod.origin_valid = origin && reg_to_target(env, origin, &diag->mod.origin);
+}
+
+void bpf_diag_mod_end(struct bpf_verifier_env *env)
+{
+ struct bpf_diag *diag = env->diag;
+ const struct bpf_reg_state *new_reg;
+
+ if (!diag || !diag->mod.active)
+ return;
+ diag->mod.active = false;
+ /*
+ * Resolve the target again because the enclosing function state's stack
+ * may have been reallocated while the modification was in progress.
+ */
+ new_reg = target_to_reg(env, &diag->mod.target);
+ if (!new_reg)
+ return;
+ diag_record_mod(env, diag->mod.insn_idx, diag->mod.target, diag->mod.reason,
+ &diag->mod.target_reg_snapshot, new_reg,
+ diag->mod.origin_valid ? &diag->mod.origin : NULL);
+}
+
+void bpf_diag_record_scrub(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,
+ enum bpf_diag_mod_reason reason)
+{
+ struct bpf_diag_mod_target target;
+
+ if (!env->diag || reg->type == NOT_INIT || !reg_to_target(env, reg, &target))
+ return;
+ diag_record_mod(env, env->insn_idx, target, reason, reg, NULL, NULL);
+}
+
+void bpf_diag_record_scrub_stack(struct bpf_verifier_env *env,
+ const struct bpf_func_state *state, s16 min_off, s16 max_off,
+ enum bpf_diag_mod_reason reason)
+{
+ diag_record_mod(env, env->insn_idx,
+ diag_stack_range_target(state->diag_frame_id, state->frameno, min_off, max_off),
+ reason, NULL, NULL, NULL);
+}
+
+static void diag_record_ref(struct bpf_verifier_env *env, u32 insn_idx, u8 kind, u32 ref_id)
+{
+ struct bpf_diag_history_event event = {
+ .insn_idx = insn_idx,
+ .kind = kind,
+ .ref = {
+ .ref_id = ref_id,
+ },
+ };
+
+ diag_append_history(env, &event);
+}
+
+void bpf_diag_record_ref_acquire(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id)
+{
+ diag_record_ref(env, insn_idx, BPF_DIAG_HISTORY_REF_ACQUIRE, ref_id);
+}
+
+void bpf_diag_record_ref_release(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id)
+{
+ diag_record_ref(env, insn_idx, BPF_DIAG_HISTORY_REF_RELEASE, ref_id);
+}
+
+void bpf_diag_record_context(struct bpf_verifier_env *env, u32 insn_idx,
+ enum bpf_diag_context_kind ctx_kind, bool enter, u32 depth)
+{
+ /*
+ * Keep leave events so context rendering can stop at a depth-zero exit
+ * and show nested-region depth accurately for the active path.
+ */
+ struct bpf_diag_history_event event = {
+ .insn_idx = insn_idx,
+ .kind = BPF_DIAG_HISTORY_CONTEXT,
+ .ctx = {
+ .kind = ctx_kind,
+ .enter = enter,
+ .depth = depth,
+ },
+ };
+
+ diag_append_history(env, &event);
+}
+
+static int diag_history_context_start_idx(const struct bpf_diag_log *log,
+ const struct bpf_diag_history_opts *opts)
+{
+ int i;
+
+ if (!opts->ctx_depth)
+ return 0;
+
+ /* Find the most recent outermost entry, or a depth-zero exit. */
+ for (i = log->cnt; i > 0; i--) {
+ const struct bpf_diag_history_event *event;
+
+ event = &log->events[log_pos(log, i - 1)];
+
+ if (event->kind != BPF_DIAG_HISTORY_CONTEXT || event->ctx.kind != opts->ctx_kind)
+ continue;
+
+ if (event->ctx.enter && event->ctx.depth == 1)
+ return i - 1;
+ if (!event->ctx.enter && event->ctx.depth == 0)
+ return 0;
+ }
+
+ return 0;
+}
+
+struct bpf_diag_history_filter {
+ const struct bpf_diag_history_opts *opts;
+ u32 lineage_start;
+ bool lineage_valid;
+};
+
+static bool diag_target_matches(const struct bpf_diag_mod_target *event_target,
+ const struct bpf_diag_mod_target *target)
+{
+ int slot_off;
+
+ if (event_target->frame_id != target->frame_id || event_target->frameno != target->frameno)
+ return false;
+
+ if (event_target->kind == BPF_DIAG_MOD_TARGET_STACK_RANGE &&
+ target->kind == BPF_DIAG_MOD_TARGET_STACK_SLOT) {
+ slot_off = -(target->spi + 1) * BPF_REG_SIZE;
+ return event_target->range.min_off < slot_off + BPF_REG_SIZE &&
+ event_target->range.max_off > slot_off;
+ }
+
+ if (event_target->kind != target->kind)
+ return false;
+
+ switch (target->kind) {
+ case BPF_DIAG_MOD_TARGET_REG:
+ return event_target->regno == target->regno;
+ case BPF_DIAG_MOD_TARGET_STACK_ARG:
+ return event_target->stack_arg == target->stack_arg;
+ case BPF_DIAG_MOD_TARGET_STACK_SLOT:
+ return event_target->spi == target->spi;
+ default:
+ return false;
+ }
+}
+
+static void diag_build_lineage(struct bpf_verifier_env *env, struct bpf_diag_log *log,
+ struct bpf_diag_history_filter *filter)
+{
+ const struct bpf_diag_history_opts *opts = filter->opts;
+ struct bpf_diag_mod_target target;
+ int i;
+
+ for (i = 0; i < log->cnt; i++)
+ log->events[log_pos(log, i)].in_lineage = false;
+
+ if (opts->scope == BPF_DIAG_HISTORY_SCOPE_REG)
+ target = diag_reg_target(opts->frame_id, opts->frameno, opts->regno);
+ else if (opts->scope == BPF_DIAG_HISTORY_SCOPE_STACK_ARG)
+ target = diag_stack_arg_target(opts->frame_id, opts->frameno,
+ opts->stack_arg_slot);
+ else
+ return;
+
+ /*
+ * Find the nearest mutation of the active target. A fill or spill changes
+ * the target to its origin, so the same walk follows register/stack
+ * lineage recursively until it reaches the write that created the value.
+ */
+ for (i = log->cnt; i > 0; i--) {
+ struct bpf_diag_history_event *event;
+
+ event = &log->events[log_pos(log, i - 1)];
+ if (event->kind != BPF_DIAG_HISTORY_MOD ||
+ !diag_target_matches(&event->mod.target, &target))
+ continue;
+
+ event->in_lineage = true;
+ filter->lineage_start = i - 1;
+ filter->lineage_valid = true;
+
+ if (event->mod.origin_valid) {
+ target = event->mod.origin;
+ continue;
+ }
+ if (event->mod.reason != BPF_DIAG_MOD_WRITE &&
+ event->mod.reason != BPF_DIAG_MOD_SPILL)
+ continue;
+ if (diag_mod_keeps_lineage(env, event))
+ continue;
+ break;
+ }
+}
+
+static int diag_history_start_idx(const struct bpf_diag_log *log,
+ const struct bpf_diag_history_filter *filter)
+{
+ const struct bpf_diag_history_opts *opts = filter->opts;
+ int i;
+
+ if (opts->scope == BPF_DIAG_HISTORY_SCOPE_CONTEXT)
+ return diag_history_context_start_idx(log, opts);
+ if (filter->lineage_valid)
+ return filter->lineage_start;
+ if (opts->scope != BPF_DIAG_HISTORY_SCOPE_REF)
+ return 0;
+
+ for (i = log->cnt; i > 0; i--) {
+ const struct bpf_diag_history_event *event;
+
+ event = &log->events[log_pos(log, i - 1)];
+ if (event->kind == BPF_DIAG_HISTORY_REF_ACQUIRE &&
+ event->ref.ref_id == opts->ref_id)
+ return i - 1;
+ }
+
+ return 0;
+}
+
+static bool diag_history_event_visible(const struct bpf_diag_history_event *event,
+ const struct bpf_diag_history_filter *filter)
+{
+ const struct bpf_diag_history_opts *opts = filter->opts;
+
+ switch (event->kind) {
+ case BPF_DIAG_HISTORY_BRANCH:
+ return true;
+ case BPF_DIAG_HISTORY_MOD:
+ return filter->lineage_valid && event->in_lineage;
+ case BPF_DIAG_HISTORY_REF_ACQUIRE:
+ case BPF_DIAG_HISTORY_REF_RELEASE:
+ return opts->scope == BPF_DIAG_HISTORY_SCOPE_REF &&
+ event->ref.ref_id == opts->ref_id;
+ case BPF_DIAG_HISTORY_CONTEXT:
+ return opts->scope == BPF_DIAG_HISTORY_SCOPE_CONTEXT &&
+ event->ctx.kind == opts->ctx_kind;
+ default:
+ return false;
+ }
+}
+
+static const char *diag_s64_bound_name(s64 value)
+{
+ if (value == S64_MIN)
+ return "S64_MIN";
+ if (value == S64_MAX)
+ return "S64_MAX";
+ return NULL;
+}
+
+static const char *diag_u64_bound_name(u64 value)
+{
+ if (value == U64_MAX)
+ return "U64_MAX";
+ return NULL;
+}
+
+static const char *diag_s64_str(struct bpf_verifier_env *env, s64 value)
+{
+ return diag_s64_bound_name(value) ?: bpf_diag_fmt(env, "%lld", value);
+}
+
+static const char *diag_u64_str(struct bpf_verifier_env *env, u64 value)
+{
+ return diag_u64_bound_name(value) ?: bpf_diag_fmt(env, "%llu", value);
+}
+
+static bool diag_cnum64_unknown(struct cnum64 range)
+{
+ return cnum64_smin(range) == S64_MIN && cnum64_smax(range) == S64_MAX &&
+ cnum64_umin(range) == 0 && cnum64_umax(range) == U64_MAX;
+}
+
+static bool diag_snapshot_unknown(const struct bpf_diag_reg_snapshot *snapshot)
+{
+ return tnum_is_unknown(snapshot->var_off) && diag_cnum64_unknown(snapshot->r64);
+}
+
+static const char *diag_scalar_range(struct bpf_verifier_env *env, struct cnum64 range)
+{
+ return bpf_diag_fmt(env, "signed range [%s, %s], unsigned range [%s, %s]",
+ diag_s64_str(env, cnum64_smin(range)),
+ diag_s64_str(env, cnum64_smax(range)),
+ diag_u64_str(env, cnum64_umin(range)),
+ diag_u64_str(env, cnum64_umax(range)));
+}
+
+const char *bpf_diag_fmt_s64_sum(struct bpf_verifier_env *env, s64 value, int addend)
+{
+ s64 sum;
+
+ if (check_add_overflow(value, (s64)addend, &sum))
+ return bpf_diag_fmt(env, "%lld plus %d (%s)", value, addend,
+ addend < 0 ? "below S64_MIN" : "above S64_MAX");
+
+ return bpf_diag_fmt(env, "%lld", sum);
+}
+
+static const char *diag_access_offset(struct bpf_verifier_env *env, int off,
+ const struct bpf_reg_state *reg)
+{
+ if (tnum_is_const(reg->var_off))
+ return bpf_diag_fmt(env, "constant %s",
+ bpf_diag_fmt_s64_sum(env, (s64)reg->var_off.value, off));
+
+ if (tnum_is_unknown(reg->var_off) && diag_cnum64_unknown(reg->r64))
+ return bpf_diag_fmt(env, "unbounded");
+
+ if (off)
+ return bpf_diag_fmt(env,
+ "variable: known bits %#llx, unknown mask %#llx, plus fixed offset %d; %s",
+ (u64)reg->var_off.value, reg->var_off.mask, off,
+ diag_scalar_range(env, reg->r64));
+ return bpf_diag_fmt(env, "variable: known bits %#llx, unknown mask %#llx; %s",
+ (u64)reg->var_off.value, reg->var_off.mask,
+ diag_scalar_range(env, reg->r64));
+}
+
+void bpf_diag_mem_bounds(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *reg_name, const char *type_name, const char *proof,
+ int off, int size, u32 mem_size, const struct bpf_reg_state *reg)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REG,
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ .regno = regno,
+ };
+ const char *offset_desc;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ offset_desc = diag_access_offset(env, off, reg);
+
+ bpf_diag_header(env, MEMORY_SAFETY, "access outside bounds");
+ diag_reason(
+ env, "The verifier cannot prove offset + access_size <= object_size. Here, %s. %s is %s; offset is %s; access_size is %d; object_size is %u.",
+ proof, reg_name, type_name, offset_desc, size, mem_size);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "access may be outside object bounds");
+
+ if (regno >= 0)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(
+ env, "Add or adjust a bounds check that proves offset + access_size stays within the object.");
+}
+
+static const char *diag_lock_name(const struct bpf_reference_state *lock)
+{
+ switch (lock->type) {
+ case REF_TYPE_LOCK:
+ return "bpf_spin_lock";
+ case REF_TYPE_RES_LOCK:
+ return "resource spin lock";
+ case REF_TYPE_RES_LOCK_IRQ:
+ return "IRQ-saving resource spin lock";
+ default:
+ return "lock";
+ }
+}
+
+static void diag_res_report(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason)
+{
+ bpf_diag_header(env, RESOURCE_LIFETIME_SAFETY, problem);
+ diag_reason(env, "%s", reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s", problem);
+}
+
+void bpf_diag_res(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion)
+{
+ diag_res_report(env, insn_idx, problem, reason);
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_lock(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion,
+ const struct bpf_reference_state *active_lock)
+{
+ diag_res_report(env, insn_idx, problem, reason);
+
+ if (active_lock) {
+ diag_section(env, "Active lock");
+ bpf_diag_source(env, active_lock->insn_idx, "acquired",
+ "active %s has verifier identity %d",
+ diag_lock_name(active_lock), active_lock->id);
+ }
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_irq(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion, u32 depth)
+{
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+ .ctx_kind = BPF_DIAG_CONTEXT_IRQ,
+ .ctx_depth = depth,
+ };
+
+ bpf_diag_header(env, RESOURCE_LIFETIME_SAFETY, problem);
+ diag_reason(env, "%s", reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s", problem);
+
+ if (depth)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_leak(struct bpf_verifier_env *env, u32 ref_id, u32 alloc_insn, u32 fail_insn)
+{
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REF,
+ .ref_id = ref_id,
+ };
+
+ bpf_diag_header(env, RESOURCE_LIFETIME_SAFETY, "unreleased resource");
+ diag_reason(
+ env, "Owned resource (id=%u) was acquired at instruction %u and still needs to be released before this exit path.",
+ ref_id, alloc_insn);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, fail_insn, "error",
+ "owned resource (id=%u) still needs release", ref_id);
+
+ diag_print_history(env, &opts);
+
+ diag_suggestion(
+ env, "Release or transfer ownership of the acquired resource on every path before the program exits.");
+}
+
+static const char *diag_var_offset(struct bpf_verifier_env *env,
+ const struct bpf_diag_reg_snapshot *snapshot)
+{
+ if (tnum_is_const(snapshot->var_off))
+ return bpf_diag_fmt(env, "at offset %lld", (s64)snapshot->var_off.value);
+
+ if (diag_snapshot_unknown(snapshot))
+ return bpf_diag_fmt(env, "with unknown offset");
+
+ return bpf_diag_fmt(env,
+ "with variable offset: known bits %#llx, unknown mask %#llx, %s",
+ snapshot->var_off.value, snapshot->var_off.mask,
+ diag_scalar_range(env, snapshot->r64));
+}
+
+static const char *diag_reg_map_name(const struct bpf_map *map)
+{
+ if (!map || !map->name[0])
+ return NULL;
+
+ return map->name;
+}
+
+static const char *diag_reg_snapshot(struct bpf_verifier_env *env,
+ const struct bpf_diag_reg_snapshot *snapshot)
+{
+ const char *type_name = reg_type_str(env, snapshot->type);
+ const char *offset = diag_var_offset(env, snapshot);
+ const char *btf = snapshot->btf && snapshot->btf_id ?
+ bpf_diag_fmt_btf_type(env, snapshot->btf, snapshot->btf_id) : NULL;
+ const char *map_name;
+
+ if (snapshot->type == SCALAR_VALUE) {
+ if (tnum_is_const(snapshot->var_off))
+ return bpf_diag_fmt(env, "integer scalar value %lld",
+ (s64)snapshot->var_off.value);
+ if (diag_snapshot_unknown(snapshot))
+ return bpf_diag_fmt(env, "integer scalar with unknown value");
+ if (cnum64_is_const(snapshot->r64))
+ return bpf_diag_fmt(env, "integer scalar value %lld",
+ cnum64_smin(snapshot->r64));
+ return bpf_diag_fmt(env, "integer scalar with %s",
+ diag_scalar_range(env, snapshot->r64));
+ }
+
+ if (snapshot->type == NOT_INIT)
+ return bpf_diag_fmt(env, "uninitialized value");
+
+ if (base_type(snapshot->type) == PTR_TO_CTX)
+ return bpf_diag_fmt(env, "context pointer %s", offset);
+
+ if (base_type(snapshot->type) == PTR_TO_STACK)
+ return bpf_diag_fmt(env, "stack pointer %s", offset);
+
+ if (base_type(snapshot->type) == PTR_TO_MAP_VALUE) {
+ const char *kind = type_may_be_null(snapshot->type) ? "nullable map value" :
+ "map value";
+
+ map_name = diag_reg_map_name(snapshot->map_ptr);
+ if (map_name)
+ return bpf_diag_fmt(env, "%s from %s %s", kind, map_name, offset);
+ return bpf_diag_fmt(env, "%s %s", kind, offset);
+ }
+
+ if (base_type(snapshot->type) == CONST_PTR_TO_MAP) {
+ map_name = diag_reg_map_name(snapshot->map_ptr);
+ if (map_name)
+ return bpf_diag_fmt(env, "map pointer for map %s", map_name);
+ return bpf_diag_fmt(env, "map pointer");
+ }
+
+ if (type_is_non_owning_ref(snapshot->type)) {
+ if (btf)
+ return bpf_diag_fmt(env, "borrowed allocated object pointer type=%s", btf);
+ return bpf_diag_fmt(env, "borrowed allocated object pointer");
+ }
+
+ if (type_is_ptr_alloc_obj(snapshot->type)) {
+ if (btf)
+ return bpf_diag_fmt(env, "owned allocated object pointer type=%s", btf);
+ return bpf_diag_fmt(env, "owned allocated object pointer");
+ }
+
+ if (base_type(snapshot->type) == PTR_TO_BTF_ID && btf)
+ return bpf_diag_fmt(env, "%s type=%s %s", type_name, btf, offset);
+
+ return bpf_diag_fmt(env, "%s %s", type_name, offset);
+}
+
+static const char *diag_mod_target_desc(struct bpf_verifier_env *env,
+ const struct bpf_diag_mod_target *target)
+{
+ switch (target->kind) {
+ case BPF_DIAG_MOD_TARGET_REG:
+ return bpf_diag_fmt(env, "R%u", target->regno);
+ case BPF_DIAG_MOD_TARGET_STACK_ARG:
+ return bpf_diag_fmt(env, "*(R11-%u)", (target->stack_arg + 1) * BPF_REG_SIZE);
+ case BPF_DIAG_MOD_TARGET_STACK_SLOT:
+ return bpf_diag_fmt(env, "stack slot fp%d", -(target->spi + 1) * BPF_REG_SIZE);
+ default:
+ return "value";
+ }
+}
+
+static void diag_print_mod(struct bpf_verifier_env *env, const struct bpf_diag_history_event *event)
+{
+ const struct bpf_diag_mod_target *target = &event->mod.target;
+ const char *target_desc, *reason = NULL, *old, *new;
+ const char *label = "update";
+
+ if (target->kind == BPF_DIAG_MOD_TARGET_STACK_RANGE) {
+ bpf_diag_source(
+ env, event->insn_idx, "invalidated",
+ "variable-offset stack write may affect bytes fp%d through fp%d",
+ target->range.min_off, target->range.max_off - 1);
+ return;
+ }
+
+ old = diag_reg_snapshot(env, &event->mod.old);
+ new = diag_reg_snapshot(env, &event->mod.new);
+ target_desc = diag_mod_target_desc(env, target);
+
+ switch (event->mod.reason) {
+ case BPF_DIAG_MOD_REF_RELEASE:
+ reason = target->kind == BPF_DIAG_MOD_TARGET_REG ? "resource release invalidated "
+ "this pointer" :
+ "resource release invalidated "
+ "this value";
+ break;
+ case BPF_DIAG_MOD_PKT_DATA_CHANGE:
+ reason = "packet data may have moved";
+ break;
+ case BPF_DIAG_MOD_NON_OWN_REF:
+ reason = "leaving the protected region invalidated this borrowed pointer";
+ break;
+ case BPF_DIAG_MOD_CALLER_SAVED:
+ reason = target->kind == BPF_DIAG_MOD_TARGET_STACK_ARG ?
+ "call invalidated this outgoing stack argument" :
+ "call invalidated this caller-saved register";
+ break;
+ case BPF_DIAG_MOD_WRITE:
+ if (target->kind == BPF_DIAG_MOD_TARGET_STACK_SLOT)
+ reason = "a later stack write overwrote this spilled value";
+ break;
+ case BPF_DIAG_MOD_SPILL:
+ label = "spilled";
+ break;
+ case BPF_DIAG_MOD_VAR_WRITE:
+ default:
+ break;
+ }
+
+ if (reason) {
+ bpf_diag_source(env, event->insn_idx, "invalidated",
+ "%s: %s; previous value was %s", target_desc, reason, old);
+ return;
+ }
+
+ bpf_diag_source(env, event->insn_idx, label, "%s changed from %s to %s", target_desc,
+ old, new);
+}
+
+static void diag_print_ref_event(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_event *event)
+{
+ const char *label;
+
+ label = event->kind == BPF_DIAG_HISTORY_REF_ACQUIRE ? "acquired" : "released";
+ bpf_diag_source(env, event->insn_idx, label, "owned resource (id=%u)",
+ event->ref.ref_id);
+}
+
+static const char *diag_context_name(enum bpf_diag_context_kind kind)
+{
+ switch (kind) {
+ case BPF_DIAG_CONTEXT_RCU:
+ return "RCU read lock region";
+ case BPF_DIAG_CONTEXT_PREEMPT:
+ return "non-preemptible region";
+ case BPF_DIAG_CONTEXT_IRQ:
+ return "IRQ-disabled region";
+ case BPF_DIAG_CONTEXT_LOCK:
+ return "lock region";
+ case BPF_DIAG_CONTEXT_NONE:
+ default:
+ return "non-sleepable program";
+ }
+}
+
+static void diag_print_context_event(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_event *event)
+{
+ bpf_diag_source(env, event->insn_idx, "context", "%s %s; depth is now %u",
+ event->ctx.enter ? "entered" : "left",
+ diag_context_name(event->ctx.kind), event->ctx.depth);
+}
+
+static void diag_print_history(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_opts *opts)
+{
+ const struct bpf_diag_history_event *event;
+ struct bpf_diag_history_filter filter = {
+ .opts = opts,
+ };
+ struct bpf_diag_log *log;
+ struct diag_fmt_mark mark;
+ bool first = true;
+ int start_idx;
+ u32 i, visible_cnt = 0, visible_idx = 0;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ if (!env->diag)
+ return;
+ log = &env->diag->log;
+
+ diag_build_lineage(env, log, &filter);
+
+ start_idx = diag_history_start_idx(log, &filter);
+ for (i = start_idx; i < log->cnt; i++) {
+ event = &log->events[log_pos(log, i)];
+ if (diag_history_event_visible(event, &filter))
+ visible_cnt++;
+ }
+
+ if (!visible_cnt && !log->first_seq && opts->scope == BPF_DIAG_HISTORY_SCOPE_STACK_ARG)
+ return;
+
+ diag_section(env, "Causal path");
+ mark = diag_fmt_save(env);
+ for (i = start_idx; i < log->cnt; i++) {
+ event = &log->events[log_pos(log, i)];
+ if (!diag_history_event_visible(event, &filter))
+ continue;
+
+ diag_fmt_restore(env, mark);
+ if (visible_cnt > BPF_DIAG_HISTORY_RENDER_MAX &&
+ visible_idx >= BPF_DIAG_HISTORY_RENDER_MAX / 2 &&
+ visible_idx < visible_cnt - BPF_DIAG_HISTORY_RENDER_MAX / 2) {
+ if (visible_idx++ != BPF_DIAG_HISTORY_RENDER_MAX / 2)
+ continue;
+ if (!first)
+ diag_write(env, "\n");
+ first = false;
+ diag_write(env, " %u intermediate causal-history events omitted\n",
+ visible_cnt - BPF_DIAG_HISTORY_RENDER_MAX);
+ continue;
+ }
+ visible_idx++;
+
+ if (!first)
+ diag_write(env, "\n");
+ first = false;
+
+ switch (event->kind) {
+ case BPF_DIAG_HISTORY_BRANCH:
+ bpf_diag_source(env, event->insn_idx, "branch",
+ "took the %s branch of this conditional, goto %s",
+ event->branch.cond_true ? "true" : "false",
+ event->branch.cond_true ? "followed" : "not followed");
+ break;
+ case BPF_DIAG_HISTORY_MOD:
+ diag_print_mod(env, event);
+ break;
+ case BPF_DIAG_HISTORY_REF_ACQUIRE:
+ case BPF_DIAG_HISTORY_REF_RELEASE:
+ diag_print_ref_event(env, event);
+ break;
+ case BPF_DIAG_HISTORY_CONTEXT:
+ diag_print_context_event(env, event);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!visible_cnt)
+ diag_write(env, " no retained diagnostic events on this path\n");
+ if (log->first_seq)
+ diag_write(env, " %llu older causal-history event%s not retained because diagnostic "
+ "event storage reached capacity\n",
+ log->first_seq, log->first_seq == 1 ? "" : "s");
+ diag_fmt_restore(env, mark);
+}
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
new file mode 100644
index 000000000000..d1b79945008a
--- /dev/null
+++ b/kernel/bpf/diagnostics.h
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#ifndef __BPF_DIAGNOSTICS_H
+#define __BPF_DIAGNOSTICS_H
+
+#include <linux/bpf.h>
+#include <linux/compiler_attributes.h>
+#include <linux/stdarg.h>
+#include <linux/types.h>
+
+struct bpf_reference_state;
+struct bpf_func_state;
+struct bpf_reg_state;
+struct bpf_verifier_env;
+struct bpf_verifier_state;
+struct btf;
+
+const char *bpf_diag_fmt_s64_sum(struct bpf_verifier_env *env, s64 value, int addend);
+enum bpf_diag_mod_reason {
+ BPF_DIAG_MOD_WRITE,
+ BPF_DIAG_MOD_SPILL,
+ BPF_DIAG_MOD_VAR_WRITE,
+ BPF_DIAG_MOD_REF_RELEASE,
+ BPF_DIAG_MOD_PKT_DATA_CHANGE,
+ BPF_DIAG_MOD_NON_OWN_REF,
+ BPF_DIAG_MOD_CALLER_SAVED,
+};
+
+enum bpf_diag_context_kind {
+ BPF_DIAG_CONTEXT_NONE,
+ BPF_DIAG_CONTEXT_RCU,
+ BPF_DIAG_CONTEXT_PREEMPT,
+ BPF_DIAG_CONTEXT_IRQ,
+ BPF_DIAG_CONTEXT_LOCK,
+};
+
+enum bpf_diag_invalid_deref_kind {
+ BPF_DIAG_DEREF_SCALAR,
+ BPF_DIAG_DEREF_NULLABLE_PTR,
+ BPF_DIAG_DEREF_MODIFIED_PTR,
+ BPF_DIAG_DEREF_INVALID_PTR,
+};
+
+bool bpf_diag_enabled(const struct bpf_verifier_env *env);
+int bpf_diag_init(struct bpf_verifier_env *env);
+void bpf_diag_init_frame(struct bpf_verifier_env *env, struct bpf_func_state *state);
+char *bpf_diag_fmt_buf(struct bpf_verifier_env *env, size_t size);
+const char *bpf_diag_vfmt(struct bpf_verifier_env *env, const char *fmt, va_list args)
+ __printf(2, 0);
+const char *bpf_diag_fmt(struct bpf_verifier_env *env, const char *fmt, ...) __printf(2, 3);
+const char *bpf_diag_fmt_btf_type(struct bpf_verifier_env *env, const struct btf *btf, u32 type_id);
+const char *bpf_diag_reg_type_plain(struct bpf_verifier_env *env, enum bpf_reg_type type);
+u64 bpf_diag_event_log_save(struct bpf_verifier_env *env);
+void bpf_diag_event_log_restore(struct bpf_verifier_env *env, u64 log_pos);
+u32 bpf_diag_irq_depth(const struct bpf_verifier_state *state);
+void bpf_diag_free(struct bpf_verifier_env *env);
+void bpf_diag_register_type(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *problem, const char *reason, const char *suggestion);
+void bpf_diag_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *reg_name, const struct bpf_reg_state *reg,
+ enum bpf_diag_invalid_deref_kind kind, s64 offset);
+void bpf_diag_unreadable_reg(struct bpf_verifier_env *env, u32 insn_idx, int regno);
+void bpf_diag_stack_arg_uninit(struct bpf_verifier_env *env, u32 insn_idx, int nargs,
+ int stack_arg_slot, const char *callee_name,
+ const char *arg_name);
+void bpf_diag_memory(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion);
+void bpf_diag_mem_bounds(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *reg_name, const char *type_name, const char *proof,
+ int off, int size, u32 mem_size, const struct bpf_reg_state *reg);
+void bpf_diag_res(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion);
+void bpf_diag_lock(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion,
+ const struct bpf_reference_state *active_lock);
+void bpf_diag_irq(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion, u32 depth);
+void bpf_diag_leak(struct bpf_verifier_env *env, u32 ref_id, u32 alloc_insn, u32 fail_insn);
+void bpf_diag_call_type(struct bpf_verifier_env *env, u32 insn_idx, int argno, int regno,
+ int stack_arg_slot, const char *call_name, const char *arg_name,
+ const char *reason, const char *suggestion);
+void bpf_diag_ctx_forbidden(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *operation, const char *suggestion);
+void bpf_diag_ctx_active(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ enum bpf_diag_context_kind ctx_kind, const char *suggestion);
+void bpf_diag_ctx_required(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ enum bpf_diag_context_kind ctx_kind, const char *suggestion);
+void bpf_diag_ctx_underflow(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *operation, enum bpf_diag_context_kind ctx_kind,
+ const char *suggestion);
+void bpf_diag_program_structure(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *problem, const char *suggestion,
+ const char *reason_fmt, ...) __printf(5, 6);
+void bpf_diag_policy(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ const char *reason, const char *suggestion);
+void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx, bool cond_true);
+void bpf_diag_mod_begin(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,
+ const struct bpf_reg_state *origin, enum bpf_diag_mod_reason reason);
+void bpf_diag_mod_end(struct bpf_verifier_env *env);
+void bpf_diag_record_scrub(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,
+ enum bpf_diag_mod_reason reason);
+void bpf_diag_record_scrub_stack(struct bpf_verifier_env *env,
+ const struct bpf_func_state *state, s16 min_off, s16 max_off,
+ enum bpf_diag_mod_reason reason);
+void bpf_diag_record_ref_acquire(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id);
+void bpf_diag_record_ref_release(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id);
+void bpf_diag_record_context(struct bpf_verifier_env *env, u32 insn_idx,
+ enum bpf_diag_context_kind ctx_kind, bool enter, u32 depth);
+
+#endif /* __BPF_DIAGNOSTICS_H */
diff --git a/kernel/bpf/disasm.c b/kernel/bpf/disasm.c
index f8a3c7eb451e..3ce8d74b0e40 100644
--- a/kernel/bpf/disasm.c
+++ b/kernel/bpf/disasm.c
@@ -7,6 +7,9 @@
#include "disasm.h"
+/* Only defined by the non-UAPI linux/filter.h, which this file cannot use. */
+#define BPF_PROBE_ATOMIC 0xe0
+
#define __BPF_FUNC_STR_FN(x) [BPF_FUNC_ ## x] = __stringify(bpf_ ## x)
static const char * const func_id_str[] = {
__BPF_FUNC_MAPPER(__BPF_FUNC_STR_FN)
@@ -139,7 +142,7 @@ static void print_bpf_end_insn(bpf_insn_print_t verbose,
void *private_data,
const struct bpf_insn *insn)
{
- verbose(private_data, "(%02x) r%d = %s%d r%d\n",
+ verbose(private_data, "(%02x) r%d = %s%d r%d",
insn->code, insn->dst_reg,
BPF_SRC(insn->code) == BPF_TO_BE ? "be" : "le",
insn->imm, insn->dst_reg);
@@ -149,7 +152,7 @@ static void print_bpf_bswap_insn(bpf_insn_print_t verbose,
void *private_data,
const struct bpf_insn *insn)
{
- verbose(private_data, "(%02x) r%d = bswap%d r%d\n",
+ verbose(private_data, "(%02x) r%d = bswap%d r%d",
insn->code, insn->dst_reg,
insn->imm, insn->dst_reg);
}
@@ -197,19 +200,19 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs,
else
print_bpf_end_insn(verbose, cbs->private_data, insn);
} else if (BPF_OP(insn->code) == BPF_NEG) {
- verbose(cbs->private_data, "(%02x) %c%d = -%c%d\n",
+ verbose(cbs->private_data, "(%02x) %c%d = -%c%d",
insn->code, class == BPF_ALU ? 'w' : 'r',
insn->dst_reg, class == BPF_ALU ? 'w' : 'r',
insn->dst_reg);
} else if (is_addr_space_cast(insn)) {
- verbose(cbs->private_data, "(%02x) r%d = addr_space_cast(r%d, %u, %u)\n",
+ verbose(cbs->private_data, "(%02x) r%d = addr_space_cast(r%d, %u, %u)",
insn->code, insn->dst_reg,
insn->src_reg, ((u32)insn->imm) >> 16, (u16)insn->imm);
} else if (is_mov_percpu_addr(insn)) {
- verbose(cbs->private_data, "(%02x) r%d = &(void __percpu *)(r%d)\n",
+ verbose(cbs->private_data, "(%02x) r%d = &(void __percpu *)(r%d)",
insn->code, insn->dst_reg, insn->src_reg);
} else if (BPF_SRC(insn->code) == BPF_X) {
- verbose(cbs->private_data, "(%02x) %c%d %s %s%c%d\n",
+ verbose(cbs->private_data, "(%02x) %c%d %s %s%c%d",
insn->code, class == BPF_ALU ? 'w' : 'r',
insn->dst_reg,
is_sdiv_smod(insn) ? bpf_alu_sign_string[BPF_OP(insn->code) >> 4]
@@ -218,7 +221,7 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs,
class == BPF_ALU ? 'w' : 'r',
insn->src_reg);
} else {
- verbose(cbs->private_data, "(%02x) %c%d %s %d\n",
+ verbose(cbs->private_data, "(%02x) %c%d %s %d",
insn->code, class == BPF_ALU ? 'w' : 'r',
insn->dst_reg,
is_sdiv_smod(insn) ? bpf_alu_sign_string[BPF_OP(insn->code) >> 4]
@@ -226,80 +229,81 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs,
insn->imm);
}
} else if (class == BPF_STX) {
+ const char *probe_pfx = BPF_MODE(insn->code) == BPF_PROBE_ATOMIC ? "probe " : "";
+ bool atomic = BPF_MODE(insn->code) == BPF_ATOMIC ||
+ BPF_MODE(insn->code) == BPF_PROBE_ATOMIC;
+
if (BPF_MODE(insn->code) == BPF_MEM)
- verbose(cbs->private_data, "(%02x) *(%s *)(r%d %+d) = r%d\n",
+ verbose(cbs->private_data, "(%02x) *(%s *)(r%d %+d) = r%d",
insn->code,
bpf_ldst_string[BPF_SIZE(insn->code) >> 3],
insn->dst_reg,
insn->off, insn->src_reg);
- else if (BPF_MODE(insn->code) == BPF_ATOMIC &&
+ else if (atomic &&
(insn->imm == BPF_ADD || insn->imm == BPF_AND ||
insn->imm == BPF_OR || insn->imm == BPF_XOR)) {
- verbose(cbs->private_data, "(%02x) lock *(%s *)(r%d %+d) %s r%d\n",
- insn->code,
+ verbose(cbs->private_data, "(%02x) %slock *(%s *)(r%d %+d) %s r%d",
+ insn->code, probe_pfx,
bpf_ldst_string[BPF_SIZE(insn->code) >> 3],
insn->dst_reg, insn->off,
bpf_alu_string[BPF_OP(insn->imm) >> 4],
insn->src_reg);
- } else if (BPF_MODE(insn->code) == BPF_ATOMIC &&
+ } else if (atomic &&
(insn->imm == (BPF_ADD | BPF_FETCH) ||
insn->imm == (BPF_AND | BPF_FETCH) ||
insn->imm == (BPF_OR | BPF_FETCH) ||
insn->imm == (BPF_XOR | BPF_FETCH))) {
- verbose(cbs->private_data, "(%02x) r%d = atomic%s_fetch_%s((%s *)(r%d %+d), r%d)\n",
- insn->code, insn->src_reg,
+ verbose(cbs->private_data, "(%02x) %sr%d = atomic%s_fetch_%s((%s *)(r%d %+d), r%d)",
+ insn->code, probe_pfx, insn->src_reg,
BPF_SIZE(insn->code) == BPF_DW ? "64" : "",
bpf_atomic_alu_string[BPF_OP(insn->imm) >> 4],
bpf_ldst_string[BPF_SIZE(insn->code) >> 3],
insn->dst_reg, insn->off, insn->src_reg);
- } else if (BPF_MODE(insn->code) == BPF_ATOMIC &&
- insn->imm == BPF_CMPXCHG) {
- verbose(cbs->private_data, "(%02x) r0 = atomic%s_cmpxchg((%s *)(r%d %+d), r0, r%d)\n",
- insn->code,
+ } else if (atomic && insn->imm == BPF_CMPXCHG) {
+ verbose(cbs->private_data, "(%02x) %sr0 = atomic%s_cmpxchg((%s *)(r%d %+d), r0, r%d)",
+ insn->code, probe_pfx,
BPF_SIZE(insn->code) == BPF_DW ? "64" : "",
bpf_ldst_string[BPF_SIZE(insn->code) >> 3],
insn->dst_reg, insn->off,
insn->src_reg);
- } else if (BPF_MODE(insn->code) == BPF_ATOMIC &&
- insn->imm == BPF_XCHG) {
- verbose(cbs->private_data, "(%02x) r%d = atomic%s_xchg((%s *)(r%d %+d), r%d)\n",
- insn->code, insn->src_reg,
+ } else if (atomic && insn->imm == BPF_XCHG) {
+ verbose(cbs->private_data, "(%02x) %sr%d = atomic%s_xchg((%s *)(r%d %+d), r%d)",
+ insn->code, probe_pfx, insn->src_reg,
BPF_SIZE(insn->code) == BPF_DW ? "64" : "",
bpf_ldst_string[BPF_SIZE(insn->code) >> 3],
insn->dst_reg, insn->off, insn->src_reg);
- } else if (BPF_MODE(insn->code) == BPF_ATOMIC &&
- insn->imm == BPF_LOAD_ACQ) {
- verbose(cbs->private_data, "(%02x) r%d = load_acquire((%s *)(r%d %+d))\n",
- insn->code, insn->dst_reg,
+ } else if (atomic && insn->imm == BPF_LOAD_ACQ) {
+ verbose(cbs->private_data, "(%02x) %sr%d = load_acquire((%s *)(r%d %+d))",
+ insn->code, probe_pfx, insn->dst_reg,
bpf_ldst_string[BPF_SIZE(insn->code) >> 3],
insn->src_reg, insn->off);
- } else if (BPF_MODE(insn->code) == BPF_ATOMIC &&
- insn->imm == BPF_STORE_REL) {
- verbose(cbs->private_data, "(%02x) store_release((%s *)(r%d %+d), r%d)\n",
- insn->code,
+ } else if (atomic && insn->imm == BPF_STORE_REL) {
+ verbose(cbs->private_data, "(%02x) %sstore_release((%s *)(r%d %+d), r%d)",
+ insn->code, probe_pfx,
bpf_ldst_string[BPF_SIZE(insn->code) >> 3],
insn->dst_reg, insn->off, insn->src_reg);
} else {
- verbose(cbs->private_data, "BUG_%02x\n", insn->code);
+ verbose(cbs->private_data, "BUG_%02x", insn->code);
}
} else if (class == BPF_ST) {
if (BPF_MODE(insn->code) == BPF_MEM) {
- verbose(cbs->private_data, "(%02x) *(%s *)(r%d %+d) = %d\n",
+ verbose(cbs->private_data, "(%02x) *(%s *)(r%d %+d) = %d",
insn->code,
bpf_ldst_string[BPF_SIZE(insn->code) >> 3],
insn->dst_reg,
insn->off, insn->imm);
} else if (BPF_MODE(insn->code) == 0xc0 /* BPF_NOSPEC, no UAPI */) {
- verbose(cbs->private_data, "(%02x) nospec\n", insn->code);
+ verbose(cbs->private_data, "(%02x) nospec", insn->code);
} else {
- verbose(cbs->private_data, "BUG_st_%02x\n", insn->code);
+ verbose(cbs->private_data, "BUG_st_%02x", insn->code);
}
} else if (class == BPF_LDX) {
- if (BPF_MODE(insn->code) != BPF_MEM && BPF_MODE(insn->code) != BPF_MEMSX) {
- verbose(cbs->private_data, "BUG_ldx_%02x\n", insn->code);
+ if ((BPF_MODE(insn->code) != BPF_MEM && BPF_MODE(insn->code) != BPF_MEMSX) ||
+ (BPF_MODE(insn->code) == BPF_MEMSX && BPF_SIZE(insn->code) == BPF_DW)) {
+ verbose(cbs->private_data, "BUG_ldx_%02x", insn->code);
return;
}
- verbose(cbs->private_data, "(%02x) r%d = *(%s *)(r%d %+d)\n",
+ verbose(cbs->private_data, "(%02x) r%d = *(%s *)(r%d %+d)",
insn->code, insn->dst_reg,
BPF_MODE(insn->code) == BPF_MEM ?
bpf_ldst_string[BPF_SIZE(insn->code) >> 3] :
@@ -307,12 +311,12 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs,
insn->src_reg, insn->off);
} else if (class == BPF_LD) {
if (BPF_MODE(insn->code) == BPF_ABS) {
- verbose(cbs->private_data, "(%02x) r0 = *(%s *)skb[%d]\n",
+ verbose(cbs->private_data, "(%02x) r0 = *(%s *)skb[%d]",
insn->code,
bpf_ldst_string[BPF_SIZE(insn->code) >> 3],
insn->imm);
} else if (BPF_MODE(insn->code) == BPF_IND) {
- verbose(cbs->private_data, "(%02x) r0 = *(%s *)skb[r%d + %d]\n",
+ verbose(cbs->private_data, "(%02x) r0 = *(%s *)skb[r%d + %d]",
insn->code,
bpf_ldst_string[BPF_SIZE(insn->code) >> 3],
insn->src_reg, insn->imm);
@@ -323,18 +327,21 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs,
*/
u64 imm = ((u64)(insn + 1)->imm << 32) | (u32)insn->imm;
bool is_ptr = insn->src_reg == BPF_PSEUDO_MAP_FD ||
- insn->src_reg == BPF_PSEUDO_MAP_VALUE;
+ insn->src_reg == BPF_PSEUDO_MAP_VALUE ||
+ insn->src_reg == BPF_PSEUDO_MAP_IDX ||
+ insn->src_reg == BPF_PSEUDO_MAP_IDX_VALUE ||
+ insn->src_reg == BPF_PSEUDO_BTF_ID;
char tmp[64];
if (is_ptr && !allow_ptr_leaks)
imm = 0;
- verbose(cbs->private_data, "(%02x) r%d = %s\n",
+ verbose(cbs->private_data, "(%02x) r%d = %s",
insn->code, insn->dst_reg,
__func_imm_name(cbs, insn, imm,
tmp, sizeof(tmp)));
} else {
- verbose(cbs->private_data, "BUG_ld_%02x\n", insn->code);
+ verbose(cbs->private_data, "BUG_ld_%02x", insn->code);
return;
}
} else if (class == BPF_JMP32 || class == BPF_JMP) {
@@ -344,35 +351,35 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs,
char tmp[64];
if (insn->src_reg == BPF_PSEUDO_CALL) {
- verbose(cbs->private_data, "(%02x) call pc%s\n",
+ verbose(cbs->private_data, "(%02x) call pc%s",
insn->code,
__func_get_name(cbs, insn,
tmp, sizeof(tmp)));
} else {
strcpy(tmp, "unknown");
- verbose(cbs->private_data, "(%02x) call %s#%d\n", insn->code,
+ verbose(cbs->private_data, "(%02x) call %s#%d", insn->code,
__func_get_name(cbs, insn,
tmp, sizeof(tmp)),
insn->imm);
}
} else if (insn->code == (BPF_JMP | BPF_JA)) {
- verbose(cbs->private_data, "(%02x) goto pc%+d\n",
+ verbose(cbs->private_data, "(%02x) goto pc%+d",
insn->code, insn->off);
} else if (insn->code == (BPF_JMP | BPF_JA | BPF_X)) {
- verbose(cbs->private_data, "(%02x) gotox r%d\n",
+ verbose(cbs->private_data, "(%02x) gotox r%d",
insn->code, insn->dst_reg);
} else if (insn->code == (BPF_JMP | BPF_JCOND) &&
insn->src_reg == BPF_MAY_GOTO) {
- verbose(cbs->private_data, "(%02x) may_goto pc%+d\n",
+ verbose(cbs->private_data, "(%02x) may_goto pc%+d",
insn->code, insn->off);
} else if (insn->code == (BPF_JMP32 | BPF_JA)) {
- verbose(cbs->private_data, "(%02x) gotol pc%+d\n",
+ verbose(cbs->private_data, "(%02x) gotol pc%+d",
insn->code, insn->imm);
} else if (insn->code == (BPF_JMP | BPF_EXIT)) {
- verbose(cbs->private_data, "(%02x) exit\n", insn->code);
+ verbose(cbs->private_data, "(%02x) exit", insn->code);
} else if (BPF_SRC(insn->code) == BPF_X) {
verbose(cbs->private_data,
- "(%02x) if %c%d %s %c%d goto pc%+d\n",
+ "(%02x) if %c%d %s %c%d goto pc%+d",
insn->code, class == BPF_JMP32 ? 'w' : 'r',
insn->dst_reg,
bpf_jmp_string[BPF_OP(insn->code) >> 4],
@@ -380,14 +387,14 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs,
insn->src_reg, insn->off);
} else {
verbose(cbs->private_data,
- "(%02x) if %c%d %s 0x%x goto pc%+d\n",
+ "(%02x) if %c%d %s 0x%x goto pc%+d",
insn->code, class == BPF_JMP32 ? 'w' : 'r',
insn->dst_reg,
bpf_jmp_string[BPF_OP(insn->code) >> 4],
(u32)insn->imm, insn->off);
}
} else {
- verbose(cbs->private_data, "(%02x) %s\n",
+ verbose(cbs->private_data, "(%02x) %s",
insn->code, bpf_class_string[class]);
}
}
diff --git a/kernel/bpf/dispatcher.c b/kernel/bpf/dispatcher.c
index b77db7413f8c..79f0c222c583 100644
--- a/kernel/bpf/dispatcher.c
+++ b/kernel/bpf/dispatcher.c
@@ -145,10 +145,13 @@ void bpf_dispatcher_change_prog(struct bpf_dispatcher *d, struct bpf_prog *from,
mutex_lock(&d->mutex);
if (!d->image) {
- d->image = bpf_prog_pack_alloc(PAGE_SIZE, bpf_jit_fill_hole_with_zero);
+ d->image = bpf_prog_pack_alloc(PAGE_SIZE, bpf_jit_fill_hole_with_zero, false);
if (!d->image)
goto out;
- d->rw_image = bpf_jit_alloc_exec(PAGE_SIZE);
+ /* d->rw_image doesn't need to be in module memory range, so we
+ * can use vzalloc.
+ */
+ d->rw_image = vzalloc(PAGE_SIZE);
if (!d->rw_image) {
bpf_prog_pack_free(d->image, PAGE_SIZE);
d->image = NULL;
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index 3cf2cc6e3ab6..d6f83521fc78 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -8,18 +8,44 @@
#include <linux/bsearch.h>
#include <linux/sort.h>
#include <linux/perf_event.h>
+#include <linux/sched/signal.h>
#include <net/xdp.h>
#include "disasm.h"
#define verbose(env, fmt, args...) bpf_verifier_log_write(env, fmt, ##args)
+/*
+ * Matches BPF_PROBE_ATOMIC too: bpf_convert_ctx_accesses() rewrites arena
+ * atomics before bpf_opt_subreg_zext_lo32_rnd_hi32() runs.
+ */
static bool is_cmpxchg_insn(const struct bpf_insn *insn)
{
return BPF_CLASS(insn->code) == BPF_STX &&
- BPF_MODE(insn->code) == BPF_ATOMIC &&
+ (BPF_MODE(insn->code) == BPF_ATOMIC ||
+ BPF_MODE(insn->code) == BPF_PROBE_ATOMIC) &&
insn->imm == BPF_CMPXCHG;
}
+/* Returns true if 'insn' is an address space cast instruction translated as BPF_ALU op */
+static bool is_addr_space_cast32(struct bpf_prog *prog, const struct bpf_insn *insn)
+{
+ struct bpf_map *arena = (struct bpf_map *)prog->aux->arena;
+
+ if (insn->code != (BPF_ALU64 | BPF_MOV | BPF_X) || insn->off != BPF_ADDR_SPACE_CAST)
+ return false;
+
+ /* cast from as(1) to as(0) */
+ if (insn->imm == 1)
+ return true;
+
+ /* cast from as(0) to as(1) */
+ if (insn->imm == 1 << 16)
+ return arena && arena->map_flags & BPF_F_NO_USER_CONV;
+
+ /* non-BPF_F_NO_USER_CONV cast from as(0) to as(1) should be handled by JIT */
+ return false;
+}
+
/* Return the regno defined by the insn, or -1. */
static int insn_def_regno(const struct bpf_insn *insn)
{
@@ -29,30 +55,66 @@ static int insn_def_regno(const struct bpf_insn *insn)
case BPF_ST:
return -1;
case BPF_STX:
- if (BPF_MODE(insn->code) == BPF_ATOMIC ||
- BPF_MODE(insn->code) == BPF_PROBE_ATOMIC) {
- if (insn->imm == BPF_CMPXCHG)
- return BPF_REG_0;
- else if (insn->imm == BPF_LOAD_ACQ)
- return insn->dst_reg;
- else if (insn->imm & BPF_FETCH)
- return insn->src_reg;
- }
- return -1;
+ return bpf_atomic_load_reg(insn);
default:
return insn->dst_reg;
}
}
-/* Return TRUE if INSN has defined any 32-bit value explicitly. */
-static bool insn_has_def32(struct bpf_insn *insn)
+/*
+ * For use only in combination with insn_def_regno() >= 0.
+ * Returns TRUE if the destination register operates on 64-bit,
+ * otherwise return FALSE.
+ */
+static bool bpf_is_reg64(struct bpf_prog *prog, struct bpf_insn *insn)
{
- int dst_reg = insn_def_regno(insn);
+ u8 class = BPF_CLASS(insn->code);
+ u8 mode = BPF_MODE(insn->code);
+ u8 size = BPF_SIZE(insn->code);
+ u8 op = BPF_OP(insn->code);
+ bool mode_mem;
+
+ /* subregister endiness swap */
+ if ((class == BPF_ALU || class == BPF_ALU64) && op == BPF_END && insn->imm != 64)
+ return false;
+
+ /* w0 += 1 */
+ if (class == BPF_ALU && op != BPF_END)
+ return false;
+
+ /* address space casts converted to BPF_ALU, see bpf_do_misc_fixups() */
+ if (is_addr_space_cast32(prog, insn))
+ return false;
+
+ /* non 64-bit, non signed extended loads */
+ mode_mem = mode == BPF_MEM || mode == BPF_PROBE_MEM || mode == BPF_PROBE_MEM32;
+ if (class == BPF_LDX && mode_mem && size != BPF_DW)
+ return false;
- if (dst_reg == -1)
+ /* atomics, see insn_def_regno() */
+ if (class == BPF_STX && size != BPF_DW)
return false;
- return !bpf_is_reg64(insn, dst_reg, NULL, DST_OP);
+ /* both LD_IND and LD_ABS return 32-bit data. */
+ if (class == BPF_LD && (mode == BPF_IND || mode == BPF_ABS))
+ return false;
+
+ /* Conservatively return true at default. */
+ return true;
+}
+
+/*
+ * Return the 32-bit subregister defined by INSN, or -1 if INSN does not
+ * explicitly define a 32-bit value.
+ */
+int bpf_insn_def32(struct bpf_prog *prog, struct bpf_insn *insn)
+{
+ int dst_reg = insn_def_regno(insn);
+
+ if (dst_reg < 0 || bpf_is_reg64(prog, insn))
+ return -1;
+
+ return dst_reg;
}
static int kfunc_desc_cmp_by_imm_off(const void *a, const void *b)
@@ -169,11 +231,12 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env,
* (cnt == 1) is taken or not. There is no guarantee INSN at OFF is the
* original insn at old prog.
*/
- data[off].zext_dst = insn_has_def32(insn + off + cnt - 1);
+ data[off].zext_dst = bpf_insn_def32(new_prog, insn + off + cnt - 1) >= 0;
if (cnt == 1)
return;
prog_len = new_prog->len;
+ env->insn_aux_data_len = prog_len;
memmove(data + off + cnt - 1, data + off,
sizeof(struct bpf_insn_aux_data) * (prog_len - off - cnt + 1));
@@ -181,7 +244,7 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env,
for (i = off; i < off + cnt - 1; i++) {
/* Expand insni[off]'s seen count to the patched range. */
data[i].seen = old_seen;
- data[i].zext_dst = insn_has_def32(insn + i);
+ data[i].zext_dst = bpf_insn_def32(new_prog, insn + i) >= 0;
}
/*
@@ -244,12 +307,28 @@ static void adjust_poke_descs(struct bpf_prog *prog, u32 off, u32 len)
}
}
+/*
+ * Some post-verification instruction rewriting passes require an
+ * O(prog->len) operation per instruction. Keep their shared primitives
+ * killable and preemptible.
+ */
+static bool bpf_rewrite_must_abort(void)
+{
+ if (fatal_signal_pending(current))
+ return true;
+ cond_resched();
+ return false;
+}
+
struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
const struct bpf_insn *patch, u32 len)
{
struct bpf_prog *new_prog;
struct bpf_insn_aux_data *new_data = NULL;
+ if (bpf_rewrite_must_abort())
+ return NULL;
+
if (len > 1) {
new_data = vrealloc(env->insn_aux_data,
array_size(env->prog->len + len - 1,
@@ -345,13 +424,17 @@ static int adjust_subprog_starts_after_remove(struct bpf_verifier_env *env,
sizeof(*env->subprog_info) * move);
env->subprog_cnt -= j - i;
- /* remove func_info */
+ /* remove func_info and its aux */
if (aux->func_info) {
move = aux->func_info_cnt - j;
memmove(aux->func_info + i,
aux->func_info + j,
sizeof(*aux->func_info) * move);
+ if (aux->func_info_aux)
+ memmove(aux->func_info_aux + i,
+ aux->func_info_aux + j,
+ sizeof(*aux->func_info_aux) * move);
aux->func_info_cnt -= j - i;
/* func_info->insn_off is set after all code rewrites,
* in adjust_btf_func() - no need to adjust
@@ -440,7 +523,6 @@ static int bpf_adj_linfo_after_remove(struct bpf_verifier_env *env, u32 off,
void bpf_clear_insn_aux_data(struct bpf_verifier_env *env, int start, int len)
{
struct bpf_insn_aux_data *aux_data = env->insn_aux_data;
- struct bpf_insn *insns = env->prog->insnsi;
int end = start + len;
int i;
@@ -449,9 +531,6 @@ void bpf_clear_insn_aux_data(struct bpf_verifier_env *env, int start, int len)
kvfree(aux_data[i].jt);
aux_data[i].jt = NULL;
}
-
- if (bpf_is_ldimm64(&insns[i]))
- i++;
}
}
@@ -461,10 +540,12 @@ static int verifier_remove_insns(struct bpf_verifier_env *env, u32 off, u32 cnt)
unsigned int orig_prog_len = env->prog->len;
int err;
+ if (bpf_rewrite_must_abort())
+ return -EINTR;
+
if (bpf_prog_is_offloaded(env->prog->aux))
bpf_prog_offload_remove_insns(env, off, cnt);
- /* Should be called before bpf_remove_insns, as it uses prog->insnsi */
bpf_clear_insn_aux_data(env, off, cnt);
err = bpf_remove_insns(env->prog, off, cnt);
@@ -483,6 +564,7 @@ static int verifier_remove_insns(struct bpf_verifier_env *env, u32 off, u32 cnt)
memmove(aux_data + off, aux_data + off + cnt,
sizeof(*aux_data) * (orig_prog_len - off - cnt));
+ env->insn_aux_data_len -= cnt;
return 0;
}
@@ -616,11 +698,7 @@ int bpf_opt_subreg_zext_lo32_rnd_hi32(struct bpf_verifier_env *env,
if (load_reg == -1)
continue;
- /* NOTE: arg "reg" (the fourth one) is only used for
- * BPF_STX + SRC_OP, so it is safe to pass NULL
- * here.
- */
- if (bpf_is_reg64(&insn, load_reg, NULL, DST_OP)) {
+ if (bpf_is_reg64(env->prog, &insn)) {
if (class == BPF_LD &&
BPF_MODE(code) == BPF_IMM)
i++;
@@ -759,6 +837,7 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
for (i = 0; i < insn_cnt; i++, insn++) {
bpf_convert_ctx_access_t convert_ctx_access;
+ enum bpf_reg_type ptr_type;
u8 mode;
if (env->insn_aux_data[i + delta].nospec) {
@@ -851,7 +930,8 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
continue;
}
- switch ((int)env->insn_aux_data[i + delta].ptr_type) {
+ ptr_type = env->insn_aux_data[i + delta].ptr_type;
+ switch ((int)ptr_type) {
case PTR_TO_CTX:
if (!ops->convert_ctx_access)
continue;
@@ -867,26 +947,6 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
case PTR_TO_XDP_SOCK:
convert_ctx_access = bpf_xdp_sock_convert_ctx_access;
break;
- case PTR_TO_BTF_ID:
- case PTR_TO_BTF_ID | PTR_UNTRUSTED:
- /* PTR_TO_BTF_ID | MEM_ALLOC always has a valid lifetime, unlike
- * PTR_TO_BTF_ID, and an active referenced id, but the same cannot
- * be said once it is marked PTR_UNTRUSTED, hence we must handle
- * any faults for loads into such types. BPF_WRITE is disallowed
- * for this case.
- */
- case PTR_TO_BTF_ID | MEM_ALLOC | PTR_UNTRUSTED:
- case PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED:
- if (type == BPF_READ) {
- if (BPF_MODE(insn->code) == BPF_MEM)
- insn->code = BPF_LDX | BPF_PROBE_MEM |
- BPF_SIZE((insn)->code);
- else
- insn->code = BPF_LDX | BPF_PROBE_MEMSX |
- BPF_SIZE((insn)->code);
- env->prog->aux->num_exentries++;
- }
- continue;
case PTR_TO_ARENA:
if (BPF_MODE(insn->code) == BPF_MEMSX) {
if (!bpf_jit_supports_insn(insn, true)) {
@@ -900,6 +960,29 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
env->prog->aux->num_exentries++;
continue;
default:
+ /*
+ * A pointer which may fault on a dereference must not
+ * be loaded from without fault protection, hence turn
+ * the BPF_LDX into a BPF_PROBE_MEM one so that a bad
+ * address is handled rather than panicking the kernel.
+ * A store through one is rejected earlier, there is no
+ * probed counterpart to rewrite it into.
+ */
+ if (bpf_is_ptr_to_mem_or_btf_id(ptr_type) &&
+ bpf_may_fault_on_deref(ptr_type) &&
+ type == BPF_READ) {
+ if (BPF_MODE(insn->code) == BPF_MEM)
+ insn->code = BPF_LDX | BPF_PROBE_MEM |
+ BPF_SIZE(insn->code);
+ else
+ insn->code = BPF_LDX | BPF_PROBE_MEMSX |
+ BPF_SIZE(insn->code);
+ env->prog->aux->num_exentries++;
+ continue;
+ }
+ if (verifier_bug_if(bpf_may_fault_on_deref(ptr_type), env,
+ "access to a fault prone pointer is not rewritten as a probed one"))
+ return -EFAULT;
continue;
}
@@ -1005,26 +1088,6 @@ static void bpf_restore_subprog_starts(struct bpf_verifier_env *env, u32 *orig_s
env->subprog_info[env->subprog_cnt].start = env->prog->len;
}
-struct bpf_insn_aux_data *bpf_dup_insn_aux_data(struct bpf_verifier_env *env)
-{
- size_t size;
- void *new_aux;
-
- size = array_size(sizeof(struct bpf_insn_aux_data), env->prog->len);
- new_aux = __vmalloc(size, GFP_KERNEL_ACCOUNT);
- if (new_aux)
- memcpy(new_aux, env->insn_aux_data, size);
- return new_aux;
-}
-
-void bpf_restore_insn_aux_data(struct bpf_verifier_env *env,
- struct bpf_insn_aux_data *orig_insn_aux)
-{
- /* the expanded elements are zero-filled, so no special handling is required */
- vfree(env->insn_aux_data);
- env->insn_aux_data = orig_insn_aux;
-}
-
static int jit_subprogs(struct bpf_verifier_env *env)
{
struct bpf_prog *prog = env->prog, **func, *tmp;
@@ -1299,7 +1362,6 @@ int bpf_jit_subprogs(struct bpf_verifier_env *env)
bool blinded = false;
struct bpf_insn *insn;
struct bpf_prog *prog, *orig_prog;
- struct bpf_insn_aux_data *orig_insn_aux;
u32 *orig_subprog_starts;
if (env->subprog_cnt <= 1)
@@ -1307,20 +1369,14 @@ int bpf_jit_subprogs(struct bpf_verifier_env *env)
prog = orig_prog = env->prog;
if (bpf_prog_need_blind(prog)) {
- orig_insn_aux = bpf_dup_insn_aux_data(env);
- if (!orig_insn_aux) {
- err = -ENOMEM;
- goto out_cleanup;
- }
orig_subprog_starts = bpf_dup_subprog_starts(env);
if (!orig_subprog_starts) {
- vfree(orig_insn_aux);
err = -ENOMEM;
goto out_cleanup;
}
prog = bpf_jit_blind_constants(env, prog);
if (IS_ERR(prog)) {
- err = -ENOMEM;
+ err = PTR_ERR(prog);
prog = orig_prog;
goto out_restore;
}
@@ -1334,7 +1390,6 @@ int bpf_jit_subprogs(struct bpf_verifier_env *env)
if (blinded) {
bpf_jit_prog_release_other(prog, orig_prog);
kvfree(orig_subprog_starts);
- vfree(orig_insn_aux);
}
return 0;
@@ -1364,7 +1419,6 @@ out_jit_err:
out_restore:
bpf_restore_subprog_starts(env, orig_subprog_starts);
- bpf_restore_insn_aux_data(env, orig_insn_aux);
kvfree(orig_subprog_starts);
out_cleanup:
/* cleanup main prog to be interpreted */
@@ -1378,7 +1432,6 @@ int bpf_fixup_call_args(struct bpf_verifier_env *env)
#ifndef CONFIG_BPF_JIT_ALWAYS_ON
struct bpf_prog *prog = env->prog;
struct bpf_insn *insn = prog->insnsi;
- bool has_kfunc_call = bpf_prog_has_kfunc_call(prog);
int depth;
#endif
int i, err = 0;
@@ -1400,12 +1453,12 @@ int bpf_fixup_call_args(struct bpf_verifier_env *env)
err = bpf_jit_subprogs(env);
if (err == 0)
return 0;
- if (err == -EFAULT)
+ if (err == -EFAULT || err == -EINTR)
return err;
}
#ifndef CONFIG_BPF_JIT_ALWAYS_ON
- if (has_kfunc_call) {
- verbose(env, "calling kernel functions are not allowed in non-JITed programs\n");
+ if (prog->jit_required) {
+ verbose(env, "program requires BPF JIT compiler but it is not available\n");
return -EINVAL;
}
for (i = 0; i < env->subprog_cnt; i++) {
@@ -1447,7 +1500,6 @@ int bpf_fixup_call_args(struct bpf_verifier_env *env)
return err;
}
-
/* The function requires that first instruction in 'patch' is insnsi[prog->len - 1] */
static int add_hidden_subprog(struct bpf_verifier_env *env, struct bpf_insn *patch, int len)
{
@@ -1514,15 +1566,12 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
}
for (i = 0; i < insn_cnt;) {
- if (insn->code == (BPF_ALU64 | BPF_MOV | BPF_X) && insn->imm) {
- if ((insn->off == BPF_ADDR_SPACE_CAST && insn->imm == 1) ||
- (((struct bpf_map *)env->prog->aux->arena)->map_flags & BPF_F_NO_USER_CONV)) {
- /* convert to 32-bit mov that clears upper 32-bit */
- insn->code = BPF_ALU | BPF_MOV | BPF_X;
- /* clear off and imm, so it's a normal 'wX = wY' from JIT pov */
- insn->off = 0;
- insn->imm = 0;
- } /* cast from as(0) to as(1) should be handled by JIT */
+ if (is_addr_space_cast32(env->prog, insn)) {
+ /* convert to 32-bit mov that clears upper 32-bit */
+ insn->code = BPF_ALU | BPF_MOV | BPF_X;
+ /* clear off and imm, so it's a normal 'wX = wY' from JIT pov */
+ insn->off = 0;
+ insn->imm = 0;
goto next_insn;
}
@@ -1819,6 +1868,43 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
goto next_insn;
}
+ if (bpf_jit_supports_percpu_insn() &&
+ insn->code == (BPF_LD | BPF_IMM | BPF_DW) &&
+ (insn->src_reg == BPF_PSEUDO_MAP_VALUE ||
+ insn->src_reg == BPF_PSEUDO_MAP_IDX_VALUE)) {
+ struct bpf_map *map;
+
+ aux = &env->insn_aux_data[i + delta];
+ map = env->used_maps[aux->map_index];
+ if (map->map_type != BPF_MAP_TYPE_PERCPU_ARRAY)
+ goto next_insn;
+
+ prog->jit_required = true;
+
+ /*
+ * We are *skipping* first half of ld_imm64 insn
+ * with 'i++;', patching over second half of it
+ * with that same half + mov64_percpu_reg insn.
+ * All because bpf_patch_insn_data() can only
+ * replace one 8-byte insn, which does not work
+ * well for ld_imm64 insn.
+ */
+
+ insn_buf[0] = insn[1];
+ insn_buf[1] = BPF_MOV64_PERCPU_REG(insn->dst_reg, insn->dst_reg);
+ cnt = 2;
+
+ i++;
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ if (!new_prog)
+ return -ENOMEM;
+
+ delta += cnt - 1;
+ env->prog = prog = new_prog;
+ insn = new_prog->insnsi + i + delta;
+ goto next_insn;
+ }
+
if (insn->code != (BPF_JMP | BPF_CALL))
goto next_insn;
if (insn->src_reg == BPF_PSEUDO_CALL)
@@ -1841,8 +1927,10 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
}
/* Skip inlining the helper call if the JIT does it. */
- if (bpf_jit_inlines_helper_call(insn->imm))
+ if (bpf_jit_inlines_helper_call(insn->imm)) {
+ prog->jit_required = 1;
goto next_insn;
+ }
if (insn->imm == BPF_FUNC_get_route_realm)
prog->dst_needed = 1;
@@ -2007,6 +2095,9 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
return -EFAULT;
}
+ if (bpf_map_is_percpu_map(map_ptr->map_type))
+ prog->jit_required = true;
+
new_prog = bpf_patch_insn_data(env, i + delta,
insn_buf, cnt);
if (!new_prog)
@@ -2111,6 +2202,7 @@ patch_map_ops_generic:
* way, it's fine to back out this inlining logic
*/
#ifdef CONFIG_SMP
+ prog->jit_required = true;
insn_buf[0] = BPF_MOV64_IMM(BPF_REG_0, (u32)(unsigned long)&cpu_number);
insn_buf[1] = BPF_MOV64_PERCPU_REG(BPF_REG_0, BPF_REG_0);
insn_buf[2] = BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_0, 0);
@@ -2132,6 +2224,7 @@ patch_map_ops_generic:
/* Implement bpf_get_current_task() and bpf_get_current_task_btf() inline. */
if ((insn->imm == BPF_FUNC_get_current_task || insn->imm == BPF_FUNC_get_current_task_btf) &&
bpf_verifier_inlines_helper_call(env, insn->imm)) {
+ prog->jit_required = true;
insn_buf[0] = BPF_MOV64_IMM(BPF_REG_0, (u32)(unsigned long)&current_task);
insn_buf[1] = BPF_MOV64_PERCPU_REG(BPF_REG_0, BPF_REG_0);
insn_buf[2] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_0, 0);
@@ -2338,7 +2431,7 @@ patch_call_imm:
func_id_name(insn->imm), insn->imm);
return -EFAULT;
}
- insn->imm = fn->func - __bpf_call_base;
+ insn->imm = BPF_CALL_IMM(fn->func);
next_insn:
if (subprogs[cur_subprog + 1].start == i + delta + 1) {
subprogs[cur_subprog].stack_depth += stack_depth_extra;
diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c
index 9f394e1aa2e8..13a2356c84cf 100644
--- a/kernel/bpf/hashtab.c
+++ b/kernel/bpf/hashtab.c
@@ -128,6 +128,7 @@ struct htab_elem {
struct htab_btf_record {
struct btf_record *record;
+ struct btf *btf;
u32 key_size;
};
@@ -497,8 +498,13 @@ static void htab_dtor_ctx_free(void *ctx)
{
struct htab_btf_record *hrec = ctx;
+ /*
+ * The duplicated record still points into the map BTF, so free it
+ * before dropping the reference that keeps that BTF alive.
+ */
btf_record_free(hrec->record);
- kfree(ctx);
+ btf_put(hrec->btf);
+ kfree(hrec);
}
static int bpf_ma_set_dtor(struct bpf_map *map, struct bpf_mem_alloc *ma,
@@ -511,7 +517,7 @@ static int bpf_ma_set_dtor(struct bpf_map *map, struct bpf_mem_alloc *ma,
if (IS_ERR_OR_NULL(map->record))
return 0;
- hrec = kzalloc(sizeof(*hrec), GFP_KERNEL);
+ hrec = kzalloc_obj(*hrec);
if (!hrec)
return -ENOMEM;
hrec->key_size = map->key_size;
@@ -521,6 +527,15 @@ static int bpf_ma_set_dtor(struct bpf_map *map, struct bpf_mem_alloc *ma,
kfree(hrec);
return err;
}
+ /*
+ * btf_record_dup() only acquires kernel and module BTF. Fields whose
+ * types live in the map BTF keep pointing into it: kptrs to local
+ * types refer to map->btf, and graph roots carry a value record owned
+ * by its struct meta table. The context can outlive the map when the
+ * allocator defers its teardown, so hold a reference of our own.
+ */
+ hrec->btf = map->btf;
+ btf_get(hrec->btf);
bpf_mem_alloc_set_dtor(ma, dtor, htab_dtor_ctx_free, hrec);
return 0;
}
@@ -530,6 +545,9 @@ static int htab_map_check_btf(struct bpf_map *map, const struct btf *btf,
{
struct bpf_htab *htab = container_of(map, struct bpf_htab, map);
+ if (btf_type_is_void(key_type))
+ return -EINVAL;
+
if (htab_is_prealloc(htab))
return 0;
/*
@@ -998,7 +1016,6 @@ static void dec_elem_count(struct bpf_htab *htab)
atomic_dec(&htab->count);
}
-
static void free_htab_elem(struct bpf_htab *htab, struct htab_elem *l)
{
htab_put_fd_value(htab, l);
@@ -1026,7 +1043,7 @@ static void pcpu_copy_value(struct bpf_htab *htab, void __percpu *pptr,
} else {
u32 size = round_up(htab->map.value_size, 8);
void *val;
- int cpu;
+ int cpu, off = 0;
if (map_flags & BPF_F_CPU) {
cpu = map_flags >> 32;
@@ -1038,9 +1055,10 @@ static void pcpu_copy_value(struct bpf_htab *htab, void __percpu *pptr,
for_each_possible_cpu(cpu) {
ptr = per_cpu_ptr(pptr, cpu);
- val = (map_flags & BPF_F_ALL_CPUS) ? value : value + size * cpu;
+ val = (map_flags & BPF_F_ALL_CPUS) ? value : value + off;
copy_map_value(&htab->map, ptr, val);
bpf_obj_cancel_fields(&htab->map, ptr);
+ off += size;
}
}
}
@@ -1051,14 +1069,17 @@ static void pcpu_init_value(struct bpf_htab *htab, void __percpu *pptr,
/* When not setting the initial value on all cpus, zero-fill element
* values for other cpus. Otherwise, bpf program has no way to ensure
* known initial values for cpus other than current one
- * (onallcpus=false always when coming from bpf prog).
+ * (onallcpus=false always when coming from bpf prog,
+ * map_flags & BPF_F_CPU when coming from syscall but setting
+ * only one cpu).
*/
- if (!onallcpus) {
- int current_cpu = raw_smp_processor_id();
+ if (!onallcpus || (map_flags & BPF_F_CPU)) {
+ int init_cpu = (map_flags & BPF_F_CPU) ? map_flags >> 32 :
+ raw_smp_processor_id();
int cpu;
for_each_possible_cpu(cpu) {
- if (cpu == current_cpu)
+ if (cpu == init_cpu)
copy_map_value(&htab->map, per_cpu_ptr(pptr, cpu), value);
else /* Since elem is preallocated, we cannot touch special fields */
zero_map_value(&htab->map, per_cpu_ptr(pptr, cpu));
@@ -1769,6 +1790,12 @@ static int htab_lru_percpu_map_lookup_and_delete_elem(struct bpf_map *map,
flags);
}
+/*
+ * Max consecutive empty buckets to walk in one RCU +
+ * instrumentation-disabled section before rescheduling.
+ */
+#define HTAB_BATCH_EMPTY_RESCHED 64
+
static int
__htab_map_lookup_and_delete_batch(struct bpf_map *map,
const union bpf_attr *attr,
@@ -1790,6 +1817,7 @@ __htab_map_lookup_and_delete_batch(struct bpf_map *map,
unsigned long flags = 0;
bool locked = false;
struct htab_elem *l;
+ u32 empty_cnt = 0;
struct bucket *b;
int ret = 0;
@@ -1968,30 +1996,41 @@ again_nocopy:
}
next_batch:
- /* If we are not copying data, we can go to next bucket and avoid
- * unlocking the rcu.
+ /*
+ * If we are not copying data, we can go to next bucket and avoid
+ * unlocking the rcu. Bound the walk though: after
+ * HTAB_BATCH_EMPTY_RESCHED consecutive empty buckets, fully exit
+ * the critical section (no locks are held here) and reschedule.
*/
if (!bucket_cnt && (batch + 1 < htab->n_buckets)) {
batch++;
- goto again_nocopy;
+ if (++empty_cnt < HTAB_BATCH_EMPTY_RESCHED)
+ goto again_nocopy;
+ empty_cnt = 0;
+ rcu_read_unlock();
+ bpf_enable_instrumentation();
+ cond_resched_tasks_rcu_qs();
+ goto again;
}
rcu_read_unlock();
bpf_enable_instrumentation();
- if (bucket_cnt && (copy_to_user(ukeys + total * key_size, keys,
- key_size * bucket_cnt) ||
- copy_to_user(uvalues + total * value_size, values,
- value_size * bucket_cnt))) {
+ if (bucket_cnt && (copy_to_user(ukeys + (size_t)total * key_size, keys,
+ (size_t)key_size * bucket_cnt) ||
+ copy_to_user(uvalues + (size_t)total * value_size, values,
+ (size_t)value_size * bucket_cnt))) {
ret = -EFAULT;
goto after_loop;
}
total += bucket_cnt;
+ empty_cnt = 0;
batch++;
if (batch >= htab->n_buckets) {
ret = -ENOENT;
goto after_loop;
}
+ cond_resched_tasks_rcu_qs();
goto again;
after_loop:
@@ -2865,16 +2904,6 @@ static int rhtab_map_alloc_check(union bpf_attr *attr)
return htab_map_alloc_check(attr);
}
-static void rhtab_check_and_free_fields(struct bpf_rhtab *rhtab,
- struct rhtab_elem *elem)
-{
- if (IS_ERR_OR_NULL(rhtab->map.record))
- return;
-
- bpf_obj_free_fields(rhtab->map.record,
- rhtab_elem_value(elem, rhtab->map.key_size));
-}
-
static void rhtab_mem_dtor(void *obj, void *ctx)
{
struct htab_btf_record *hrec = ctx;
@@ -2964,13 +2993,12 @@ static int rhtab_delete_elem(struct bpf_rhtab *rhtab, struct rhtab_elem *elem, v
rhtab_read_elem_value(&rhtab->map, copy, elem, flags);
check_and_init_map_value(&rhtab->map, copy);
}
- /* Release internal structs: kptr, bpf_timer, task_work, wq */
- rhtab_check_and_free_fields(rhtab, elem);
+ bpf_obj_cancel_fields(&rhtab->map,
+ rhtab_elem_value(elem, rhtab->map.key_size));
bpf_mem_cache_free_rcu(&rhtab->ma, elem);
return 0;
}
-
static long rhtab_map_delete_elem(struct bpf_map *map, void *key)
{
struct bpf_rhtab *rhtab = container_of(map, struct bpf_rhtab, map);
@@ -3007,7 +3035,6 @@ static int rhtab_map_lookup_and_delete_elem(struct bpf_map *map, void *key, void
static long rhtab_map_update_existing(struct bpf_map *map, struct rhtab_elem *elem, void *value,
u64 map_flags)
{
- struct bpf_rhtab *rhtab = container_of(map, struct bpf_rhtab, map);
void *old_val = rhtab_elem_value(elem, map->key_size);
if (map_flags & BPF_NOEXIST)
@@ -3027,7 +3054,7 @@ static long rhtab_map_update_existing(struct bpf_map *map, struct rhtab_elem *el
* kptrs/etc. still sit in the slot. Cancel them after the copy
* to match arraymap's update semantics.
*/
- rhtab_check_and_free_fields(rhtab, elem);
+ bpf_obj_cancel_fields(map, old_val);
return 0;
}
@@ -3068,7 +3095,6 @@ static long rhtab_map_update_elem(struct bpf_map *map, void *key, void *value, u
memcpy(elem->data, key, map->key_size);
copy_map_value(map, rhtab_elem_value(elem, map->key_size), value);
- check_and_init_map_value(map, rhtab_elem_value(elem, map->key_size));
/* Prevent deadlock for NMI programs attempting to take bucket lock */
bpf_disable_instrumentation();
@@ -3112,6 +3138,9 @@ static int rhtab_map_check_btf(struct bpf_map *map, const struct btf *btf,
{
struct bpf_rhtab *rhtab = container_of(map, struct bpf_rhtab, map);
+ if (btf_type_is_void(key_type))
+ return -EINVAL;
+
return bpf_ma_set_dtor(map, &rhtab->ma, rhtab_mem_dtor);
}
@@ -3345,8 +3374,10 @@ static int __rhtab_map_lookup_and_delete_batch(struct bpf_map *map,
}
if (do_delete) {
+ migrate_disable();
for (i = 0; i < total; i++)
rhtab_delete_elem(rhtab, del_elems[i], NULL, 0);
+ migrate_enable();
}
rcu_read_unlock();
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index c18f1e16edee..712dca5a2c5b 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -278,7 +278,7 @@ const struct bpf_func_proto bpf_get_current_comm_proto = {
.gpl_only = false,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
- .arg2_type = ARG_CONST_SIZE,
+ .arg2_type = ARG_MEM_SIZE,
};
#if defined(CONFIG_QUEUED_SPINLOCKS) || defined(CONFIG_BPF_ARCH_SPINLOCK)
@@ -539,7 +539,7 @@ const struct bpf_func_proto bpf_strtol_proto = {
.gpl_only = false,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg2_type = ARG_CONST_SIZE,
+ .arg2_type = ARG_MEM_SIZE,
.arg3_type = ARG_ANYTHING,
.arg4_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_UNINIT | MEM_WRITE | MEM_ALIGNED,
.arg4_size = sizeof(s64),
@@ -567,7 +567,7 @@ const struct bpf_func_proto bpf_strtoul_proto = {
.gpl_only = false,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg2_type = ARG_CONST_SIZE,
+ .arg2_type = ARG_MEM_SIZE,
.arg3_type = ARG_ANYTHING,
.arg4_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_UNINIT | MEM_WRITE | MEM_ALIGNED,
.arg4_size = sizeof(u64),
@@ -583,7 +583,7 @@ static const struct bpf_func_proto bpf_strncmp_proto = {
.gpl_only = false,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg2_type = ARG_CONST_SIZE,
+ .arg2_type = ARG_MEM_SIZE,
.arg3_type = ARG_PTR_TO_CONST_STR,
};
@@ -627,7 +627,7 @@ const struct bpf_func_proto bpf_get_ns_current_pid_tgid_proto = {
.arg1_type = ARG_ANYTHING,
.arg2_type = ARG_ANYTHING,
.arg3_type = ARG_PTR_TO_UNINIT_MEM,
- .arg4_type = ARG_CONST_SIZE,
+ .arg4_type = ARG_MEM_SIZE,
};
static const struct bpf_func_proto bpf_get_raw_smp_processor_id_proto = {
@@ -653,7 +653,7 @@ const struct bpf_func_proto bpf_event_output_data_proto = {
.arg2_type = ARG_CONST_MAP_PTR,
.arg3_type = ARG_ANYTHING,
.arg4_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg5_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg5_type = ARG_MEM_SIZE_OR_ZERO,
};
BPF_CALL_3(bpf_copy_from_user, void *, dst, u32, size,
@@ -675,7 +675,7 @@ const struct bpf_func_proto bpf_copy_from_user_proto = {
.might_sleep = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
- .arg2_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg2_type = ARG_MEM_SIZE_OR_ZERO,
.arg3_type = ARG_ANYTHING,
};
@@ -706,7 +706,7 @@ const struct bpf_func_proto bpf_copy_from_user_task_proto = {
.might_sleep = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
- .arg2_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg2_type = ARG_MEM_SIZE_OR_ZERO,
.arg3_type = ARG_ANYTHING,
.arg4_type = ARG_PTR_TO_BTF_ID,
.arg4_btf_id = &btf_tracing_ids[BTF_TRACING_TYPE_TASK],
@@ -1093,10 +1093,10 @@ const struct bpf_func_proto bpf_snprintf_proto = {
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_MEM_OR_NULL | MEM_WRITE,
- .arg2_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg2_type = ARG_MEM_SIZE_OR_ZERO,
.arg3_type = ARG_PTR_TO_CONST_STR,
.arg4_type = ARG_PTR_TO_MEM | PTR_MAYBE_NULL | MEM_RDONLY,
- .arg5_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg5_type = ARG_MEM_SIZE_OR_ZERO,
};
static void *map_key_from_value(struct bpf_map *map, void *value, u32 *arr_idx)
@@ -1888,7 +1888,7 @@ static const struct bpf_func_proto bpf_dynptr_from_mem_proto = {
.gpl_only = false,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
- .arg2_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg2_type = ARG_MEM_SIZE_OR_ZERO,
.arg3_type = ARG_ANYTHING,
.arg4_type = ARG_PTR_TO_DYNPTR | DYNPTR_TYPE_LOCAL | MEM_UNINIT | MEM_WRITE,
};
@@ -1943,7 +1943,7 @@ static const struct bpf_func_proto bpf_dynptr_read_proto = {
.gpl_only = false,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
- .arg2_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg2_type = ARG_MEM_SIZE_OR_ZERO,
.arg3_type = ARG_PTR_TO_DYNPTR,
.arg4_type = ARG_ANYTHING,
.arg5_type = ARG_ANYTHING,
@@ -2004,7 +2004,7 @@ static const struct bpf_func_proto bpf_dynptr_write_proto = {
.arg1_type = ARG_PTR_TO_DYNPTR,
.arg2_type = ARG_ANYTHING,
.arg3_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg4_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg4_type = ARG_MEM_SIZE_OR_ZERO,
.arg5_type = ARG_ANYTHING,
};
@@ -3395,19 +3395,21 @@ __bpf_kfunc void bpf_throw(u64 cookie)
WARN_ON_ONCE(!ctx.aux->exception_boundary);
WARN_ON_ONCE(!ctx.bp);
WARN_ON_ONCE(!ctx.cnt);
- /* Prevent KASAN false positives for CONFIG_KASAN_STACK by unpoisoning
+ /*
+ * Prevent KASAN false positives for CONFIG_KASAN_STACK by unpoisoning
* deeper stack depths than ctx.sp as we do not return from bpf_throw,
- * which skips compiler generated instrumentation to do the same.
+ * which skips compiler generated instrumentation to do the same. Some
+ * architectures cannot recover sp while unwinding, so fall back to bp.
*/
- kasan_unpoison_task_stack_below((void *)(long)ctx.sp);
+ kasan_unpoison_task_stack_below((void *)(long)(ctx.sp ?: ctx.bp));
ctx.aux->bpf_exception_cb(cookie, ctx.sp + ctx.aux->stack_arg_sp_adjust, ctx.bp, 0, 0);
WARN(1, "A call to BPF exception callback should never return\n");
}
-__bpf_kfunc int bpf_wq_init(struct bpf_wq *wq, void *p__map, unsigned int flags)
+__bpf_kfunc int bpf_wq_init(struct bpf_wq *wq, void *p__const_map, unsigned int flags)
{
struct bpf_async_kern *async = (struct bpf_async_kern *)wq;
- struct bpf_map *map = p__map;
+ struct bpf_map *map = p__const_map;
BUILD_BUG_ON(sizeof(struct bpf_async_kern) > sizeof(struct bpf_wq));
BUILD_BUG_ON(__alignof__(struct bpf_async_kern) != __alignof__(struct bpf_wq));
@@ -4388,7 +4390,7 @@ struct bpf_task_work_ctx {
struct bpf_map *map;
void *map_val;
enum task_work_notify_mode mode;
- bpf_task_work_callback_t callback_fn;
+ bpf_callback_t callback_fn;
struct rcu_head rcu;
} __aligned(8);
@@ -4471,7 +4473,8 @@ static void bpf_task_work_callback(struct callback_head *cb)
key = (void *)map_key_from_value(ctx->map, ctx->map_val, &idx);
migrate_disable();
- ctx->callback_fn(ctx->map, key, ctx->map_val);
+ ctx->callback_fn((u64)(long)ctx->map, (u64)(long)key,
+ (u64)(long)ctx->map_val, 0, 0);
migrate_enable();
bpf_task_work_ctx_reset(ctx);
@@ -4594,7 +4597,7 @@ static struct bpf_task_work_ctx *bpf_task_work_acquire_ctx(struct bpf_task_work
}
static int bpf_task_work_schedule(struct task_struct *task, struct bpf_task_work *tw,
- struct bpf_map *map, bpf_task_work_callback_t callback_fn,
+ struct bpf_map *map, void *callback_fn,
struct bpf_prog_aux *aux, enum task_work_notify_mode mode)
{
struct bpf_prog *prog;
@@ -4619,7 +4622,7 @@ static int bpf_task_work_schedule(struct task_struct *task, struct bpf_task_work
}
ctx->task = task;
- ctx->callback_fn = callback_fn;
+ ctx->callback_fn = (bpf_callback_t)callback_fn;
ctx->prog = prog;
ctx->mode = mode;
ctx->map = map;
@@ -4642,17 +4645,17 @@ release_prog:
* mode
* @task: Task struct for which callback should be scheduled
* @tw: Pointer to struct bpf_task_work in BPF map value for internal bookkeeping
- * @map__map: bpf_map that embeds struct bpf_task_work in the values
+ * @map__const_map: bpf_map that embeds struct bpf_task_work in the values
* @callback: pointer to BPF subprogram to call
* @aux: pointer to bpf_prog_aux of the caller BPF program, implicitly set by the verifier
*
* Return: 0 if task work has been scheduled successfully, negative error code otherwise
*/
__bpf_kfunc int bpf_task_work_schedule_signal(struct task_struct *task, struct bpf_task_work *tw,
- void *map__map, bpf_task_work_callback_t callback,
+ void *map__const_map, bpf_task_work_callback_t callback,
struct bpf_prog_aux *aux)
{
- return bpf_task_work_schedule(task, tw, map__map, callback, aux, TWA_SIGNAL);
+ return bpf_task_work_schedule(task, tw, map__const_map, callback, aux, TWA_SIGNAL);
}
/**
@@ -4660,17 +4663,17 @@ __bpf_kfunc int bpf_task_work_schedule_signal(struct task_struct *task, struct b
* mode
* @task: Task struct for which callback should be scheduled
* @tw: Pointer to struct bpf_task_work in BPF map value for internal bookkeeping
- * @map__map: bpf_map that embeds struct bpf_task_work in the values
+ * @map__const_map: bpf_map that embeds struct bpf_task_work in the values
* @callback: pointer to BPF subprogram to call
* @aux: pointer to bpf_prog_aux of the caller BPF program, implicitly set by the verifier
*
* Return: 0 if task work has been scheduled successfully, negative error code otherwise
*/
__bpf_kfunc int bpf_task_work_schedule_resume(struct task_struct *task, struct bpf_task_work *tw,
- void *map__map, bpf_task_work_callback_t callback,
+ void *map__const_map, bpf_task_work_callback_t callback,
struct bpf_prog_aux *aux)
{
- return bpf_task_work_schedule(task, tw, map__map, callback, aux, TWA_RESUME);
+ return bpf_task_work_schedule(task, tw, map__const_map, callback, aux, TWA_RESUME);
}
static int make_file_dynptr(struct file *file, u32 flags, bool may_sleep,
@@ -4811,30 +4814,32 @@ BTF_ID_FLAGS(func, bpf_obj_drop, KF_RELEASE | KF_IMPLICIT_ARGS)
BTF_ID_FLAGS(func, bpf_obj_drop_impl, KF_RELEASE)
BTF_ID_FLAGS(func, bpf_percpu_obj_drop, KF_RELEASE | KF_IMPLICIT_ARGS)
BTF_ID_FLAGS(func, bpf_percpu_obj_drop_impl, KF_RELEASE)
-BTF_ID_FLAGS(func, bpf_refcount_acquire, KF_ACQUIRE | KF_RET_NULL | KF_RCU | KF_IMPLICIT_ARGS)
-BTF_ID_FLAGS(func, bpf_refcount_acquire_impl, KF_ACQUIRE | KF_RET_NULL | KF_RCU)
-BTF_ID_FLAGS(func, bpf_list_push_front, KF_IMPLICIT_ARGS)
-BTF_ID_FLAGS(func, bpf_list_push_front_impl)
-BTF_ID_FLAGS(func, bpf_list_push_back, KF_IMPLICIT_ARGS)
-BTF_ID_FLAGS(func, bpf_list_push_back_impl)
-BTF_ID_FLAGS(func, bpf_list_add, KF_IMPLICIT_ARGS)
-BTF_ID_FLAGS(func, bpf_list_pop_front, KF_ACQUIRE | KF_RET_NULL)
-BTF_ID_FLAGS(func, bpf_list_pop_back, KF_ACQUIRE | KF_RET_NULL)
-BTF_ID_FLAGS(func, bpf_list_del, KF_ACQUIRE | KF_RET_NULL)
-BTF_ID_FLAGS(func, bpf_list_front, KF_RET_NULL)
-BTF_ID_FLAGS(func, bpf_list_back, KF_RET_NULL)
-BTF_ID_FLAGS(func, bpf_list_is_first)
-BTF_ID_FLAGS(func, bpf_list_is_last)
-BTF_ID_FLAGS(func, bpf_list_empty)
+BTF_ID_FLAGS(func, bpf_refcount_acquire,
+ KF_ACQUIRE | KF_RET_NULL | KF_RCU | KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_refcount_acquire_impl,
+ KF_ACQUIRE | KF_RET_NULL | KF_RCU | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_list_push_front, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_list_push_front_impl, KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_list_push_back, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_list_push_back_impl, KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_list_add, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_list_pop_front, KF_ACQUIRE | KF_RET_NULL | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_list_pop_back, KF_ACQUIRE | KF_RET_NULL | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_list_del, KF_ACQUIRE | KF_RET_NULL | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_list_front, KF_RET_NULL | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_list_back, KF_RET_NULL | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_list_is_first, KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_list_is_last, KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_list_empty, KF_SPINLOCK_SAFE)
BTF_ID_FLAGS(func, bpf_task_acquire, KF_ACQUIRE | KF_RCU | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_task_release, KF_RELEASE)
-BTF_ID_FLAGS(func, bpf_rbtree_remove, KF_ACQUIRE | KF_RET_NULL)
-BTF_ID_FLAGS(func, bpf_rbtree_add, KF_IMPLICIT_ARGS)
-BTF_ID_FLAGS(func, bpf_rbtree_add_impl)
-BTF_ID_FLAGS(func, bpf_rbtree_first, KF_RET_NULL)
-BTF_ID_FLAGS(func, bpf_rbtree_root, KF_RET_NULL)
-BTF_ID_FLAGS(func, bpf_rbtree_left, KF_RET_NULL)
-BTF_ID_FLAGS(func, bpf_rbtree_right, KF_RET_NULL)
+BTF_ID_FLAGS(func, bpf_rbtree_remove, KF_ACQUIRE | KF_RET_NULL | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_rbtree_add, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_rbtree_add_impl, KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_rbtree_first, KF_RET_NULL | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_rbtree_root, KF_RET_NULL | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_rbtree_left, KF_RET_NULL | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_rbtree_right, KF_RET_NULL | KF_SPINLOCK_SAFE)
#ifdef CONFIG_CGROUPS
BTF_ID_FLAGS(func, bpf_cgroup_acquire, KF_ACQUIRE | KF_RCU | KF_RET_NULL)
@@ -4868,7 +4873,6 @@ static const struct btf_kfunc_id_set generic_kfunc_set = {
.set = &generic_btf_ids,
};
-
BTF_ID_LIST(generic_dtor_ids)
BTF_ID(struct, task_struct)
BTF_ID(func, bpf_task_release_dtor)
@@ -4879,14 +4883,14 @@ BTF_ID(func, bpf_cgroup_release_dtor)
BTF_KFUNCS_START(common_btf_ids)
BTF_ID_FLAGS(func, bpf_cast_to_kern_ctx, KF_FASTCALL)
-BTF_ID_FLAGS(func, bpf_rdonly_cast, KF_FASTCALL)
+BTF_ID_FLAGS(func, bpf_rdonly_cast, KF_FASTCALL | KF_PERFMON)
BTF_ID_FLAGS(func, bpf_rcu_read_lock)
BTF_ID_FLAGS(func, bpf_rcu_read_unlock)
BTF_ID_FLAGS(func, bpf_dynptr_slice, KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_dynptr_slice_rdwr, KF_RET_NULL)
-BTF_ID_FLAGS(func, bpf_iter_num_new, KF_ITER_NEW)
-BTF_ID_FLAGS(func, bpf_iter_num_next, KF_ITER_NEXT | KF_RET_NULL)
-BTF_ID_FLAGS(func, bpf_iter_num_destroy, KF_ITER_DESTROY)
+BTF_ID_FLAGS(func, bpf_iter_num_new, KF_ITER_NEW | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_iter_num_next, KF_ITER_NEXT | KF_RET_NULL | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_iter_num_destroy, KF_ITER_DESTROY | KF_SPINLOCK_SAFE)
BTF_ID_FLAGS(func, bpf_iter_task_vma_new, KF_ITER_NEW | KF_RCU)
BTF_ID_FLAGS(func, bpf_iter_task_vma_next, KF_ITER_NEXT | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_iter_task_vma_destroy, KF_ITER_DESTROY)
@@ -4916,26 +4920,26 @@ BTF_ID_FLAGS(func, bpf_wq_set_callback, KF_IMPLICIT_ARGS)
BTF_ID_FLAGS(func, bpf_wq_start)
BTF_ID_FLAGS(func, bpf_preempt_disable)
BTF_ID_FLAGS(func, bpf_preempt_enable)
-BTF_ID_FLAGS(func, bpf_iter_bits_new, KF_ITER_NEW)
+BTF_ID_FLAGS(func, bpf_iter_bits_new, KF_ITER_NEW | KF_PERFMON)
BTF_ID_FLAGS(func, bpf_iter_bits_next, KF_ITER_NEXT | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_iter_bits_destroy, KF_ITER_DESTROY)
-BTF_ID_FLAGS(func, bpf_copy_from_user_str, KF_SLEEPABLE)
-BTF_ID_FLAGS(func, bpf_copy_from_user_task_str, KF_SLEEPABLE)
-BTF_ID_FLAGS(func, bpf_get_kmem_cache)
+BTF_ID_FLAGS(func, bpf_copy_from_user_str, KF_SLEEPABLE | KF_PERFMON)
+BTF_ID_FLAGS(func, bpf_copy_from_user_task_str, KF_SLEEPABLE | KF_PERFMON)
+BTF_ID_FLAGS(func, bpf_get_kmem_cache, KF_PERFMON)
BTF_ID_FLAGS(func, bpf_iter_kmem_cache_new, KF_ITER_NEW | KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_iter_kmem_cache_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_iter_kmem_cache_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_local_irq_save)
BTF_ID_FLAGS(func, bpf_local_irq_restore)
#ifdef CONFIG_BPF_EVENTS
-BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr)
-BTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr)
-BTF_ID_FLAGS(func, bpf_probe_read_user_str_dynptr)
-BTF_ID_FLAGS(func, bpf_probe_read_kernel_str_dynptr)
-BTF_ID_FLAGS(func, bpf_copy_from_user_dynptr, KF_SLEEPABLE)
-BTF_ID_FLAGS(func, bpf_copy_from_user_str_dynptr, KF_SLEEPABLE)
-BTF_ID_FLAGS(func, bpf_copy_from_user_task_dynptr, KF_SLEEPABLE)
-BTF_ID_FLAGS(func, bpf_copy_from_user_task_str_dynptr, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr, KF_PERFMON)
+BTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr, KF_PERFMON)
+BTF_ID_FLAGS(func, bpf_probe_read_user_str_dynptr, KF_PERFMON)
+BTF_ID_FLAGS(func, bpf_probe_read_kernel_str_dynptr, KF_PERFMON)
+BTF_ID_FLAGS(func, bpf_copy_from_user_dynptr, KF_SLEEPABLE | KF_PERFMON)
+BTF_ID_FLAGS(func, bpf_copy_from_user_str_dynptr, KF_SLEEPABLE | KF_PERFMON)
+BTF_ID_FLAGS(func, bpf_copy_from_user_task_dynptr, KF_SLEEPABLE | KF_PERFMON)
+BTF_ID_FLAGS(func, bpf_copy_from_user_task_str_dynptr, KF_SLEEPABLE | KF_PERFMON)
#endif
#ifdef CONFIG_DMA_SHARED_BUFFER
BTF_ID_FLAGS(func, bpf_iter_dmabuf_new, KF_ITER_NEW | KF_SLEEPABLE)
@@ -4943,26 +4947,26 @@ BTF_ID_FLAGS(func, bpf_iter_dmabuf_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPAB
BTF_ID_FLAGS(func, bpf_iter_dmabuf_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)
#endif
BTF_ID_FLAGS(func, __bpf_trap)
-BTF_ID_FLAGS(func, bpf_strcmp);
-BTF_ID_FLAGS(func, bpf_strcasecmp);
-BTF_ID_FLAGS(func, bpf_strncasecmp);
-BTF_ID_FLAGS(func, bpf_strchr);
-BTF_ID_FLAGS(func, bpf_strchrnul);
-BTF_ID_FLAGS(func, bpf_strnchr);
-BTF_ID_FLAGS(func, bpf_strrchr);
-BTF_ID_FLAGS(func, bpf_strlen);
-BTF_ID_FLAGS(func, bpf_strnlen);
-BTF_ID_FLAGS(func, bpf_strspn);
-BTF_ID_FLAGS(func, bpf_strcspn);
-BTF_ID_FLAGS(func, bpf_strstr);
-BTF_ID_FLAGS(func, bpf_strcasestr);
-BTF_ID_FLAGS(func, bpf_strnstr);
-BTF_ID_FLAGS(func, bpf_strncasestr);
+BTF_ID_FLAGS(func, bpf_strcmp, KF_PERFMON);
+BTF_ID_FLAGS(func, bpf_strcasecmp, KF_PERFMON);
+BTF_ID_FLAGS(func, bpf_strncasecmp, KF_PERFMON);
+BTF_ID_FLAGS(func, bpf_strchr, KF_PERFMON);
+BTF_ID_FLAGS(func, bpf_strchrnul, KF_PERFMON);
+BTF_ID_FLAGS(func, bpf_strnchr, KF_PERFMON);
+BTF_ID_FLAGS(func, bpf_strrchr, KF_PERFMON);
+BTF_ID_FLAGS(func, bpf_strlen, KF_PERFMON);
+BTF_ID_FLAGS(func, bpf_strnlen, KF_PERFMON);
+BTF_ID_FLAGS(func, bpf_strspn, KF_PERFMON);
+BTF_ID_FLAGS(func, bpf_strcspn, KF_PERFMON);
+BTF_ID_FLAGS(func, bpf_strstr, KF_PERFMON);
+BTF_ID_FLAGS(func, bpf_strcasestr, KF_PERFMON);
+BTF_ID_FLAGS(func, bpf_strnstr, KF_PERFMON);
+BTF_ID_FLAGS(func, bpf_strncasestr, KF_PERFMON);
#if defined(CONFIG_BPF_LSM) && defined(CONFIG_CGROUPS)
BTF_ID_FLAGS(func, bpf_cgroup_read_xattr, KF_RCU)
#endif
-BTF_ID_FLAGS(func, bpf_stream_vprintk, KF_IMPLICIT_ARGS)
-BTF_ID_FLAGS(func, bpf_stream_print_stack, KF_IMPLICIT_ARGS)
+BTF_ID_FLAGS(func, bpf_stream_vprintk, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE | KF_PERFMON)
+BTF_ID_FLAGS(func, bpf_stream_print_stack, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE | KF_PERFMON)
BTF_ID_FLAGS(func, bpf_task_work_schedule_signal, KF_IMPLICIT_ARGS)
BTF_ID_FLAGS(func, bpf_task_work_schedule_resume, KF_IMPLICIT_ARGS)
BTF_ID_FLAGS(func, bpf_dynptr_from_file)
diff --git a/kernel/bpf/liveness.c b/kernel/bpf/liveness.c
index 0aadfbae0acc..301fc60bddc4 100644
--- a/kernel/bpf/liveness.c
+++ b/kernel/bpf/liveness.c
@@ -85,7 +85,7 @@ static struct func_instance *call_instance(struct bpf_verifier_env *env,
if (f)
return f;
- f = kvzalloc(sizeof(*f), GFP_KERNEL_ACCOUNT);
+ f = kvzalloc_obj(*f, GFP_KERNEL_ACCOUNT);
if (!f)
return ERR_PTR(-ENOMEM);
f->callsite = lookup_key;
@@ -269,7 +269,6 @@ bpf_insn_successors(struct bpf_verifier_env *env, u32 idx)
__diag_pop();
-
static inline bool update_insn(struct bpf_verifier_env *env,
struct func_instance *instance, u32 frame, u32 insn_idx)
{
@@ -497,7 +496,6 @@ static void print_instance(struct bpf_verifier_env *env, struct func_instance *i
pos = env->log.end_pos;
verbose(env, "%3d: ", insn_idx);
bpf_verbose_insn(env, &insns[insn_idx]);
- bpf_vlog_reset(&env->log, env->log.end_pos - 1); /* remove \n */
insn_pos = env->log.end_pos;
verbose(env, "%*c;", bpf_vlog_alignment(insn_pos - pos), ' ');
pos = env->log.end_pos;
@@ -1043,7 +1041,6 @@ static void arg_track_log(struct bpf_verifier_env *env, struct bpf_insn *insn, i
if (!printed) {
verbose(env, "%3d: ", idx);
bpf_verbose_insn(env, insn);
- bpf_vlog_reset(&env->log, env->log.end_pos - 1);
printed = true;
}
verbose(env, "\tr%d: ", i); verbose_arg_track(env, &at_in[i]);
@@ -1058,7 +1055,6 @@ static void arg_track_log(struct bpf_verifier_env *env, struct bpf_insn *insn, i
if (!printed) {
verbose(env, "%3d: ", idx);
bpf_verbose_insn(env, insn);
- bpf_vlog_reset(&env->log, env->log.end_pos - 1);
printed = true;
}
verbose(env, "\tsa%d: ", i); verbose_arg_track(env, &at_in[ai]);
@@ -1070,7 +1066,6 @@ static void arg_track_log(struct bpf_verifier_env *env, struct bpf_insn *insn, i
if (!printed) {
verbose(env, "%3d: ", idx);
bpf_verbose_insn(env, insn);
- bpf_vlog_reset(&env->log, env->log.end_pos - 1);
printed = true;
}
verbose(env, "\tfp%+d: ", -(i + 1) * 8); verbose_arg_track(env, &at_stack_in[i]);
@@ -1213,12 +1208,9 @@ static void arg_track_xfer(struct bpf_verifier_env *env, struct bpf_insn *insn,
clear_stack_for_all_offs(insn, at_out, insn->dst_reg,
at_stack_out, sz);
- if (insn->imm == BPF_CMPXCHG)
- at_out[BPF_REG_0] = none;
- else if (insn->imm == BPF_LOAD_ACQ)
- *dst = none;
- else if (insn->imm & BPF_FETCH)
- *src = none;
+ r = bpf_atomic_load_reg(insn);
+ if (r >= 0)
+ at_out[r] = none;
}
} else if (class == BPF_ST && BPF_MODE(insn->code) == BPF_MEM) {
u32 sz = bpf_size_to_bytes(BPF_SIZE(insn->code));
@@ -1545,6 +1537,7 @@ static void print_subprog_arg_access(struct bpf_verifier_env *env,
verbose(env, "%3d: ", idx);
bpf_verbose_insn(env, &insns[idx]);
+ verbose(env, "\n");
/* Collect what needs printing */
if (is_ldx_stx_call &&
@@ -1868,7 +1861,6 @@ static int analyze_subprog(struct bpf_verifier_env *env,
if (need_resched())
cond_resched();
-
/*
* When an instance is reused (must_write_initialized == true),
* record into a fresh instance and merge afterward. This avoids
@@ -2050,29 +2042,38 @@ out:
/* Each field is a register bitmask */
struct insn_live_regs {
- u16 use; /* registers read by instruction */
- u16 def; /* registers written by instruction */
- u16 in; /* registers that may be alive before instruction */
- u16 out; /* registers that may be alive after instruction */
+ u32 use; /* registers read by instruction */
+ u32 def; /* registers written by instruction */
+ u32 in; /* registers that may be alive before instruction */
+ u32 out; /* registers that may be alive after instruction */
};
/* Bitmask with 1s for all caller saved registers */
#define ALL_CALLER_SAVED_REGS ((1u << CALLER_SAVED_REGS) - 1)
+static inline u32 reg32_mask(u32 n) { return BIT(n); }
+static inline u32 reg64_mask(u32 n) { return BIT(n) | BIT(n + 16); }
+static inline u32 mask_widen(u32 m) { return m | (m << 16); }
+static inline u16 mask_lo(u32 m) { return (u16)m; }
+static inline u16 mask_hi(u32 m) { return (u16)(m >> 16); }
+
/* Compute info->{use,def} fields for the instruction */
static void compute_insn_live_regs(struct bpf_verifier_env *env,
struct bpf_insn *insn,
struct insn_live_regs *info)
{
struct bpf_call_summary cs;
- u8 class = BPF_CLASS(insn->code);
- u8 code = BPF_OP(insn->code);
- u8 mode = BPF_MODE(insn->code);
- u16 src = BIT(insn->src_reg);
- u16 dst = BIT(insn->dst_reg);
- u16 r0 = BIT(0);
- u16 def = 0;
- u16 use = 0xffff;
+ const u8 class = BPF_CLASS(insn->code);
+ const u8 code = BPF_OP(insn->code);
+ const u8 mode = BPF_MODE(insn->code);
+ const u8 size = BPF_SIZE(insn->code);
+ const u32 src = reg64_mask(insn->src_reg);
+ const u32 dst = reg64_mask(insn->dst_reg);
+ const u32 src32 = mask_lo(src);
+ const u32 dst32 = mask_lo(dst);
+ const u32 r0 = reg64_mask(0);
+ u32 def = 0;
+ u32 use = U32_MAX;
switch (class) {
case BPF_LD:
@@ -2083,8 +2084,8 @@ static void compute_insn_live_regs(struct bpf_verifier_env *env,
use = 0;
}
break;
- case BPF_LD | BPF_ABS:
- case BPF_LD | BPF_IND:
+ case BPF_ABS:
+ case BPF_IND:
/* stick with defaults */
break;
}
@@ -2092,7 +2093,15 @@ static void compute_insn_live_regs(struct bpf_verifier_env *env,
case BPF_LDX:
switch (mode) {
case BPF_MEM:
+ /* a narrow load still redefines the whole register */
+ def = dst;
+ use = src;
+ break;
case BPF_MEMSX:
+ /*
+ * sign extension defines the whole register;
+ * src holds a pointer, hence is used as 64-bit.
+ */
def = dst;
use = src;
break;
@@ -2110,12 +2119,19 @@ static void compute_insn_live_regs(struct bpf_verifier_env *env,
switch (mode) {
case BPF_MEM:
def = 0;
- use = dst | src;
+ use = dst | (size == BPF_DW ? src : src32);
break;
- case BPF_ATOMIC:
+ case BPF_ATOMIC: {
+ /*
+ * dst holds a pointer and is always used as 64-bit;
+ * the value operand and r0 are read as 32-bit for BPF_W atomics.
+ */
+ u32 srcv = size == BPF_DW ? src : src32;
+ u32 r0v = size == BPF_DW ? r0 : mask_lo(r0);
+
switch (insn->imm) {
case BPF_CMPXCHG:
- use = r0 | dst | src;
+ use = r0v | dst | srcv;
def = r0;
break;
case BPF_LOAD_ACQ:
@@ -2124,10 +2140,10 @@ static void compute_insn_live_regs(struct bpf_verifier_env *env,
break;
case BPF_STORE_REL:
def = 0;
- use = dst | src;
+ use = dst | srcv;
break;
default:
- use = dst | src;
+ use = dst | srcv;
if (insn->imm & BPF_FETCH)
def = src;
else
@@ -2135,6 +2151,7 @@ static void compute_insn_live_regs(struct bpf_verifier_env *env,
}
break;
}
+ }
break;
case BPF_ALU:
case BPF_ALU64:
@@ -2148,14 +2165,14 @@ static void compute_insn_live_regs(struct bpf_verifier_env *env,
if (BPF_SRC(insn->code) == BPF_K)
use = 0;
else
- use = src;
+ use = class == BPF_ALU64 ? src : src32;
break;
default:
def = dst;
if (BPF_SRC(insn->code) == BPF_K)
- use = dst;
+ use = class == BPF_ALU64 ? dst : dst32;
else
- use = dst | src;
+ use = class == BPF_ALU64 ? (dst | src) : (dst32 | src32);
}
break;
case BPF_JMP:
@@ -2181,13 +2198,14 @@ static void compute_insn_live_regs(struct bpf_verifier_env *env,
use = def & ~BIT(BPF_REG_0);
if (bpf_get_call_summary(env, insn, &cs))
use = GENMASK(min_t(u8, cs.num_params, MAX_BPF_FUNC_REG_ARGS), 1);
+ def = mask_widen(def);
+ use = mask_widen(use);
break;
default:
def = 0;
- if (BPF_SRC(insn->code) == BPF_K)
- use = dst;
- else
- use = dst | src;
+ use = class == BPF_JMP ? dst : dst32;
+ if (BPF_SRC(insn->code) == BPF_X)
+ use |= class == BPF_JMP ? src : src32;
}
break;
}
@@ -2209,6 +2227,7 @@ int bpf_compute_live_registers(struct bpf_verifier_env *env)
struct bpf_insn *insns = env->prog->insnsi;
struct insn_live_regs *state;
int insn_cnt = env->prog->len;
+ u64 pos, insn_pos;
int err = 0, i, j;
bool changed;
@@ -2252,8 +2271,8 @@ int bpf_compute_live_registers(struct bpf_verifier_env *env)
int insn_idx = env->cfg.insn_postorder[i];
struct insn_live_regs *live = &state[insn_idx];
struct bpf_iarray *succ;
- u16 new_out = 0;
- u16 new_in = 0;
+ u32 new_out = 0;
+ u32 new_in = 0;
succ = bpf_insn_successors(env, insn_idx);
for (int s = 0; s < succ->cnt; ++s)
@@ -2267,8 +2286,20 @@ int bpf_compute_live_registers(struct bpf_verifier_env *env)
}
}
- for (i = 0; i < insn_cnt; ++i)
- insn_aux[i].live_regs_before = state[i].in;
+ for (i = 0; i < insn_cnt; ++i) {
+ int def32 = bpf_insn_def32(env->prog, &insns[i]);
+ u32 out = state[i].out;
+ u32 in = state[i].in;
+
+ insn_aux[i].live_regs_before = mask_lo(in) | mask_hi(in);
+ /*
+ * On architectures where 32-bit operations do not reset upper halves
+ * of the registers, the verifier needs to zero extend a destination
+ * register if an instruction defines a 32-bit subregister and the
+ * upper half of that register is alive after the instruction.
+ */
+ insn_aux[i].zext_dst = def32 >= 0 && (mask_hi(out) & BIT(def32));
+ }
if (env->log.level & BPF_LOG_LEVEL2) {
verbose(env, "Live regs before insn:\n");
@@ -2284,7 +2315,12 @@ int bpf_compute_live_registers(struct bpf_verifier_env *env)
else
verbose(env, ".");
verbose(env, " ");
+ pos = env->log.end_pos;
bpf_verbose_insn(env, &insns[i]);
+ insn_pos = env->log.end_pos;
+ if (insn_aux[i].zext_dst)
+ verbose(env, "%*c; zext", bpf_vlog_alignment(insn_pos - pos), ' ');
+ verbose(env, "\n");
if (bpf_is_ldimm64(&insns[i]))
i++;
}
diff --git a/kernel/bpf/local_storage.c b/kernel/bpf/local_storage.c
index 23267213a17f..83cd527a2542 100644
--- a/kernel/bpf/local_storage.c
+++ b/kernel/bpf/local_storage.c
@@ -220,7 +220,7 @@ int bpf_percpu_cgroup_storage_update(struct bpf_map *_map, void *key,
struct bpf_cgroup_storage *storage;
void *val;
u32 size;
- int cpu;
+ int cpu, off = 0;
if ((u32)map_flags & ~(BPF_ANY | BPF_EXIST | BPF_F_CPU | BPF_F_ALL_CPUS))
return -EINVAL;
@@ -245,8 +245,9 @@ int bpf_percpu_cgroup_storage_update(struct bpf_map *_map, void *key,
}
size = round_up(_map->value_size, 8);
for_each_possible_cpu(cpu) {
- val = (map_flags & BPF_F_ALL_CPUS) ? value : value + size * cpu;
+ val = (map_flags & BPF_F_ALL_CPUS) ? value : value + off;
copy_map_value(_map, per_cpu_ptr(storage->percpu_buf, cpu), val);
+ off += size;
}
unlock:
rcu_read_unlock();
diff --git a/kernel/bpf/log.c b/kernel/bpf/log.c
index b740fa73ee26..fb032dfdc0de 100644
--- a/kernel/bpf/log.c
+++ b/kernel/bpf/log.c
@@ -615,17 +615,6 @@ static void print_scalar_ranges(struct bpf_verifier_env *env,
}
}
-static bool type_is_map_ptr(enum bpf_reg_type t) {
- switch (base_type(t)) {
- case CONST_PTR_TO_MAP:
- case PTR_TO_MAP_KEY:
- case PTR_TO_MAP_VALUE:
- return true;
- default:
- return false;
- }
-}
-
/*
* _a stands for append, was shortened to avoid multiline statements below.
* This macro is used to output a comma separated list of attributes.
@@ -873,7 +862,7 @@ struct bpf_verifier_log *bpf_log_attr_create_vlog(struct bpf_log_attr *attr_log,
if (!size)
return NULL;
- log = kzalloc_obj(*log, GFP_KERNEL);
+ log = kzalloc_obj(*log);
if (!log)
return ERR_PTR(-ENOMEM);
diff --git a/kernel/bpf/memalloc.c b/kernel/bpf/memalloc.c
index e9662db7198f..15684d0fc883 100644
--- a/kernel/bpf/memalloc.c
+++ b/kernel/bpf/memalloc.c
@@ -118,7 +118,13 @@ struct bpf_mem_cache {
struct llist_head free_by_rcu_ttrace;
struct llist_head waiting_for_gp_ttrace;
struct rcu_head rcu_ttrace;
+ /*
+ * 0 - idle
+ * 1 - __free_rcu() is queued
+ * 2 - __free_rcu() is queued and free_by_rcu_ttrace got more objects since
+ */
atomic_t call_rcu_ttrace_in_progress;
+ raw_spinlock_t lock;
};
struct bpf_mem_caches {
@@ -214,25 +220,24 @@ static void alloc_bulk(struct bpf_mem_cache *c, int cnt, int node, bool atomic)
gfp = __GFP_NOWARN | __GFP_ACCOUNT;
gfp |= atomic ? GFP_NOWAIT : GFP_KERNEL;
- for (i = 0; i < cnt; i++) {
- /*
- * For every 'c' llist_del_first(&c->free_by_rcu_ttrace); is
- * done only by one CPU == current CPU. Other CPUs might
- * llist_add() and llist_del_all() in parallel.
- */
- obj = llist_del_first(&c->free_by_rcu_ttrace);
- if (!obj)
- break;
- add_obj_to_free_list(c, obj);
- }
- if (i >= cnt)
- return;
+ /*
+ * c->lock serializes concurrent llist_del_first() against
+ * llist_del_all() in __free_rcu() and do_call_rcu_ttrace().
+ */
+ scoped_guard(raw_spinlock_irqsave, &c->lock) {
+ for (i = 0; i < cnt; i++) {
+ obj = llist_del_first(&c->free_by_rcu_ttrace);
+ if (!obj)
+ break;
+ add_obj_to_free_list(c, obj);
+ }
- for (; i < cnt; i++) {
- obj = llist_del_first(&c->waiting_for_gp_ttrace);
- if (!obj)
- break;
- add_obj_to_free_list(c, obj);
+ for (; i < cnt; i++) {
+ obj = llist_del_first(&c->waiting_for_gp_ttrace);
+ if (!obj)
+ break;
+ add_obj_to_free_list(c, obj);
+ }
}
if (i >= cnt)
return;
@@ -276,12 +281,30 @@ static int free_all(struct bpf_mem_cache *c, struct llist_node *llnode, bool per
return cnt;
}
+static void __do_call_rcu_ttrace(struct bpf_mem_cache *c);
+
static void __free_rcu(struct rcu_head *head)
{
struct bpf_mem_cache *c = container_of(head, struct bpf_mem_cache, rcu_ttrace);
+ struct llist_node *llnode;
+
+ scoped_guard(raw_spinlock_irqsave, &c->lock)
+ llnode = llist_del_all(&c->waiting_for_gp_ttrace);
+
+ free_all(c, llnode, !!c->percpu_size);
- free_all(c, llist_del_all(&c->waiting_for_gp_ttrace), !!c->percpu_size);
- atomic_set(&c->call_rcu_ttrace_in_progress, 0);
+ /*
+ * do_call_rcu_ttrace() that ran while GP was in flight left its objects
+ * in free_by_rcu_ttrace. This cache may never free or alloc in bulk
+ * again, so start the next GP from here.
+ * 'c' can be freed as soon as call_rcu_ttrace_in_progress is zero.
+ */
+ if (atomic_cmpxchg(&c->call_rcu_ttrace_in_progress, 1, 0) == 1)
+ return;
+
+ /* Pairs with synchronize_rcu() in free_mem_alloc() */
+ guard(rcu)();
+ __do_call_rcu_ttrace(c);
}
static void enque_to_free(struct bpf_mem_cache *c, void *obj)
@@ -294,17 +317,15 @@ static void enque_to_free(struct bpf_mem_cache *c, void *obj)
llist_add(llnode, &c->free_by_rcu_ttrace);
}
-static void do_call_rcu_ttrace(struct bpf_mem_cache *c)
+static void __do_call_rcu_ttrace(struct bpf_mem_cache *c)
{
struct llist_node *llnode, *t;
- if (atomic_xchg(&c->call_rcu_ttrace_in_progress, 1)) {
- if (unlikely(READ_ONCE(c->draining))) {
- llnode = llist_del_all(&c->free_by_rcu_ttrace);
- free_all(c, llnode, !!c->percpu_size);
- }
- return;
- }
+ /*
+ * Must be done before llist_del_all(). Objects that it misses were
+ * added by do_call_rcu_ttrace() that will set 2 after this store.
+ */
+ atomic_set(&c->call_rcu_ttrace_in_progress, 1);
WARN_ON_ONCE(!llist_empty(&c->waiting_for_gp_ttrace));
llist_for_each_safe(llnode, t, llist_del_all(&c->free_by_rcu_ttrace))
@@ -323,6 +344,22 @@ static void do_call_rcu_ttrace(struct bpf_mem_cache *c)
call_rcu_tasks_trace(&c->rcu_ttrace, __free_rcu);
}
+static void do_call_rcu_ttrace(struct bpf_mem_cache *c)
+{
+ struct llist_node *llnode;
+
+ if (atomic_xchg(&c->call_rcu_ttrace_in_progress, 2)) {
+ if (unlikely(READ_ONCE(c->draining))) {
+ scoped_guard(raw_spinlock_irqsave, &c->lock)
+ llnode = llist_del_all(&c->free_by_rcu_ttrace);
+ free_all(c, llnode, !!c->percpu_size);
+ }
+ return;
+ }
+
+ __do_call_rcu_ttrace(c);
+}
+
static void free_bulk(struct bpf_mem_cache *c)
{
struct bpf_mem_cache *tgt = c->tgt;
@@ -535,6 +572,7 @@ int bpf_mem_alloc_init(struct bpf_mem_alloc *ma, int size, bool percpu)
c->objcg = objcg;
c->percpu_size = percpu_size;
c->tgt = c;
+ raw_spin_lock_init(&c->lock);
init_refill_work(c);
prefill_mem_cache(c, cpu);
}
@@ -557,7 +595,7 @@ int bpf_mem_alloc_init(struct bpf_mem_alloc *ma, int size, bool percpu)
c->objcg = objcg;
c->percpu_size = percpu_size;
c->tgt = c;
-
+ raw_spin_lock_init(&c->lock);
init_refill_work(c);
prefill_mem_cache(c, cpu);
}
@@ -609,7 +647,7 @@ int bpf_mem_alloc_percpu_unit_init(struct bpf_mem_alloc *ma, int size)
c->objcg = objcg;
c->percpu_size = percpu_size;
c->tgt = c;
-
+ raw_spin_lock_init(&c->lock);
init_refill_work(c);
prefill_mem_cache(c, cpu);
}
@@ -694,7 +732,12 @@ static void free_mem_alloc(struct bpf_mem_alloc *ma)
* to wait for the pending __free_by_rcu(), and __free_rcu(). RCU Tasks
* Trace grace period implies RCU grace period, so all __free_rcu don't
* need extra call_rcu() (and thus extra rcu_barrier() here).
+ *
+ * __free_rcu() queues itself again unless it sees 'draining'. After
+ * synchronize_rcu() it either did that already or will not do it, so
+ * rcu_barrier_tasks_trace() cannot miss it.
*/
+ synchronize_rcu();
rcu_barrier(); /* wait for __free_by_rcu */
rcu_barrier_tasks_trace(); /* wait for __free_rcu */
free_mem_alloc_no_barrier(ma);
diff --git a/kernel/bpf/mmap_unlock_work.h b/kernel/bpf/mmap_unlock_work.h
index 5d18d7d85bef..1834db20b861 100644
--- a/kernel/bpf/mmap_unlock_work.h
+++ b/kernel/bpf/mmap_unlock_work.h
@@ -4,12 +4,15 @@
#ifndef __MMAP_UNLOCK_WORK_H__
#define __MMAP_UNLOCK_WORK_H__
+#include <linux/atomic.h>
+#include <linux/err.h>
#include <linux/irq_work.h>
/* irq_work to run mmap_read_unlock() in irq_work */
struct mmap_unlock_irq_work {
struct irq_work irq_work;
struct mm_struct *mm;
+ atomic_t active;
};
DECLARE_PER_CPU(struct mmap_unlock_irq_work, mmap_unlock_work);
@@ -18,32 +21,36 @@ DECLARE_PER_CPU(struct mmap_unlock_irq_work, mmap_unlock_work);
* We cannot do mmap_read_unlock() when the irq is disabled, because of
* risk to deadlock with rq_lock. To look up vma when the irqs are
* disabled, we need to run mmap_read_unlock() in irq_work. We use a
- * percpu variable to do the irq_work. If the irq_work is already used
- * by another lookup, we fall over.
+ * percpu variable to do the irq_work. The active flag reserves the slot
+ * before mmap_read_trylock() and until the irq_work callback consumes mm.
*/
-static inline bool bpf_mmap_unlock_get_irq_work(struct mmap_unlock_irq_work **work_ptr)
+static inline struct mmap_unlock_irq_work *bpf_mmap_unlock_guard_get(void)
{
- struct mmap_unlock_irq_work *work = NULL;
- bool irq_work_busy = false;
+ struct mmap_unlock_irq_work *work;
- if (irqs_disabled()) {
- if (!IS_ENABLED(CONFIG_PREEMPT_RT)) {
- work = this_cpu_ptr(&mmap_unlock_work);
- if (irq_work_is_busy(&work->irq_work)) {
- /* cannot queue more up_read, fallback */
- irq_work_busy = true;
- }
- } else {
- /*
- * PREEMPT_RT does not allow to trylock mmap sem in
- * interrupt disabled context. Force the fallback code.
- */
- irq_work_busy = true;
- }
- }
+ if (!irqs_disabled())
+ return NULL;
+
+ /*
+ * PREEMPT_RT does not allow to trylock mmap sem in interrupt
+ * disabled context. Force the fallback code.
+ */
+ if (IS_ENABLED(CONFIG_PREEMPT_RT))
+ return ERR_PTR(-EBUSY);
+
+ work = this_cpu_ptr(&mmap_unlock_work);
+ if (irq_work_is_busy(&work->irq_work) ||
+ atomic_cmpxchg_acquire(&work->active, 0, 1))
+ return ERR_PTR(-EBUSY);
- *work_ptr = work;
- return irq_work_busy;
+ return work;
+}
+
+static inline void
+bpf_mmap_unlock_guard_put(struct mmap_unlock_irq_work *work)
+{
+ if (work)
+ atomic_set_release(&work->active, 0);
}
static inline void bpf_mmap_unlock_mm(struct mmap_unlock_irq_work *work, struct mm_struct *mm)
diff --git a/kernel/bpf/net_namespace.c b/kernel/bpf/net_namespace.c
index 25f30f9edaef..81006a242618 100644
--- a/kernel/bpf/net_namespace.c
+++ b/kernel/bpf/net_namespace.c
@@ -171,33 +171,28 @@ static int bpf_netns_link_update_prog(struct bpf_link *link,
struct net *net;
int idx, ret;
+ guard(mutex)(&netns_bpf_mutex);
+
if (old_prog && old_prog != link->prog)
return -EPERM;
if (new_prog->type != link->prog->type)
return -EINVAL;
- mutex_lock(&netns_bpf_mutex);
-
net = net_link->net;
- if (!net || !check_net(net)) {
+ if (!net || !check_net(net))
/* Link auto-detached or netns dying */
- ret = -ENOLINK;
- goto out_unlock;
- }
+ return -ENOLINK;
run_array = rcu_dereference_protected(net->bpf.run_array[type],
lockdep_is_held(&netns_bpf_mutex));
idx = link_index(net, type, net_link);
ret = bpf_prog_array_update_at(run_array, idx, new_prog);
if (ret)
- goto out_unlock;
+ return ret;
old_prog = xchg(&link->prog, new_prog);
bpf_prog_put(old_prog);
-
-out_unlock:
- mutex_unlock(&netns_bpf_mutex);
- return ret;
+ return 0;
}
static int bpf_netns_link_fill_info(const struct bpf_link *link,
diff --git a/kernel/bpf/offload.c b/kernel/bpf/offload.c
index 0d6f5569588c..d855399812ee 100644
--- a/kernel/bpf/offload.c
+++ b/kernel/bpf/offload.c
@@ -698,6 +698,8 @@ static bool __bpf_offload_dev_match(struct bpf_prog *prog,
return false;
if (offload->netdev == netdev)
return true;
+ if (!bpf_prog_is_offloaded(prog->aux))
+ return false;
ondev1 = bpf_offload_find_netdev(offload->netdev);
ondev2 = bpf_offload_find_netdev(netdev);
diff --git a/kernel/bpf/percpu_freelist.c b/kernel/bpf/percpu_freelist.c
index 632762b57299..06ce588d13a3 100644
--- a/kernel/bpf/percpu_freelist.c
+++ b/kernel/bpf/percpu_freelist.c
@@ -17,6 +17,8 @@ int pcpu_freelist_init(struct pcpu_freelist *s)
raw_res_spin_lock_init(&head->lock);
head->first = NULL;
}
+ raw_res_spin_lock_init(&s->extralist.lock);
+ s->extralist.first = NULL;
return 0;
}
@@ -46,22 +48,28 @@ void __pcpu_freelist_push(struct pcpu_freelist *s,
struct pcpu_freelist_node *node)
{
struct pcpu_freelist_head *head;
- int cpu;
+ int cpu, this_cpu;
if (___pcpu_freelist_push(this_cpu_ptr(s->freelist), node))
return;
+ this_cpu = raw_smp_processor_id();
while (true) {
- for_each_cpu_wrap(cpu, cpu_possible_mask, raw_smp_processor_id()) {
- if (cpu == raw_smp_processor_id())
+ for_each_cpu_wrap(cpu, cpu_possible_mask, this_cpu) {
+ if (cpu == this_cpu)
continue;
+
head = per_cpu_ptr(s->freelist, cpu);
- if (raw_res_spin_lock(&head->lock))
- continue;
- pcpu_freelist_push_node(head, node);
- raw_res_spin_unlock(&head->lock);
- return;
+ if (___pcpu_freelist_push(head, node))
+ return;
}
+
+ /*
+ * Push cannot fail. Use the extra list when none of the
+ * per-CPU freelists can accept the node.
+ */
+ if (___pcpu_freelist_push(&s->extralist, node))
+ return;
}
}
@@ -117,6 +125,17 @@ static struct pcpu_freelist_node *___pcpu_freelist_pop(struct pcpu_freelist *s)
}
raw_res_spin_unlock(&head->lock);
}
+
+ /* Per-CPU lists are empty or unavailable, try the extra list. */
+ head = &s->extralist;
+ if (!READ_ONCE(head->first))
+ return NULL;
+ if (raw_res_spin_lock(&head->lock))
+ return NULL;
+ node = head->first;
+ if (node)
+ WRITE_ONCE(head->first, node->next);
+ raw_res_spin_unlock(&head->lock);
return node;
}
diff --git a/kernel/bpf/percpu_freelist.h b/kernel/bpf/percpu_freelist.h
index 914798b74967..980cf2884fd2 100644
--- a/kernel/bpf/percpu_freelist.h
+++ b/kernel/bpf/percpu_freelist.h
@@ -14,6 +14,7 @@ struct pcpu_freelist_head {
struct pcpu_freelist {
struct pcpu_freelist_head __percpu *freelist;
+ struct pcpu_freelist_head extralist;
};
struct pcpu_freelist_node {
diff --git a/kernel/bpf/queue_stack_maps.c b/kernel/bpf/queue_stack_maps.c
index 9a5f94371e50..6e8b18c32a10 100644
--- a/kernel/bpf/queue_stack_maps.c
+++ b/kernel/bpf/queue_stack_maps.c
@@ -99,8 +99,10 @@ static long __queue_map_get(struct bpf_map *map, void *value, bool delete)
int err = 0;
void *ptr;
- if (raw_res_spin_lock_irqsave(&qs->lock, flags))
+ if (raw_res_spin_lock_irqsave(&qs->lock, flags)) {
+ memset(value, 0, qs->map.value_size);
return -EBUSY;
+ }
if (queue_stack_map_is_empty(qs)) {
memset(value, 0, qs->map.value_size);
@@ -121,7 +123,6 @@ out:
return err;
}
-
static long __stack_map_get(struct bpf_map *map, void *value, bool delete)
{
struct bpf_queue_stack *qs = bpf_queue_stack(map);
@@ -130,8 +131,10 @@ static long __stack_map_get(struct bpf_map *map, void *value, bool delete)
void *ptr;
u32 index;
- if (raw_res_spin_lock_irqsave(&qs->lock, flags))
+ if (raw_res_spin_lock_irqsave(&qs->lock, flags)) {
+ memset(value, 0, qs->map.value_size);
return -EBUSY;
+ }
if (queue_stack_map_is_empty(qs)) {
memset(value, 0, qs->map.value_size);
diff --git a/kernel/bpf/ringbuf.c b/kernel/bpf/ringbuf.c
index 35ae64ade36b..3f1013d80544 100644
--- a/kernel/bpf/ringbuf.c
+++ b/kernel/bpf/ringbuf.c
@@ -321,7 +321,7 @@ static unsigned long ringbuf_avail_data_sz(struct bpf_ringbuf *rb)
if (unlikely(rb->overwrite_mode)) {
over_pos = smp_load_acquire(&rb->overwrite_pos);
prod_pos = smp_load_acquire(&rb->producer_pos);
- return prod_pos - max(cons_pos, over_pos);
+ return min(prod_pos - cons_pos, prod_pos - over_pos);
} else {
prod_pos = smp_load_acquire(&rb->producer_pos);
return prod_pos - cons_pos;
@@ -482,7 +482,7 @@ static void *__bpf_ringbuf_reserve(struct bpf_ringbuf *rb, u64 size)
prod_pos = rb->producer_pos;
new_prod_pos = prod_pos + len;
- while (pend_pos < prod_pos) {
+ while (prod_pos - pend_pos > 0) {
hdr = (void *)rb->data + (pend_pos & rb->mask);
hdr_len = READ_ONCE(hdr->len);
if (hdr_len & BPF_RINGBUF_BUSY_BIT)
@@ -634,7 +634,7 @@ const struct bpf_func_proto bpf_ringbuf_output_proto = {
.ret_type = RET_INTEGER,
.arg1_type = ARG_CONST_MAP_PTR,
.arg2_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg3_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg3_type = ARG_MEM_SIZE_OR_ZERO,
.arg4_type = ARG_ANYTHING,
};
diff --git a/kernel/bpf/rqspinlock.c b/kernel/bpf/rqspinlock.c
index e4e338cdb437..111ec80ea958 100644
--- a/kernel/bpf/rqspinlock.c
+++ b/kernel/bpf/rqspinlock.c
@@ -572,9 +572,10 @@ queue:
/* Disable queue destruction when we detect deadlocks. */
if (ret == -EDEADLK) {
- if (!next)
+ if (!try_cmpxchg_tail(lock, tail, 0)) {
next = smp_cond_load_relaxed(&node->next, (VAL));
- arch_mcs_spin_unlock_contended(&next->locked);
+ arch_mcs_spin_unlock_contended(&next->locked);
+ }
goto err_release_node;
}
@@ -744,10 +745,10 @@ __bpf_kfunc void bpf_res_spin_unlock_irqrestore(struct bpf_res_spin_lock *lock,
__bpf_kfunc_end_defs();
BTF_KFUNCS_START(rqspinlock_kfunc_ids)
-BTF_ID_FLAGS(func, bpf_res_spin_lock, KF_RET_NULL)
-BTF_ID_FLAGS(func, bpf_res_spin_unlock)
-BTF_ID_FLAGS(func, bpf_res_spin_lock_irqsave, KF_RET_NULL)
-BTF_ID_FLAGS(func, bpf_res_spin_unlock_irqrestore)
+BTF_ID_FLAGS(func, bpf_res_spin_lock, KF_RET_NULL | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_res_spin_unlock, KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_res_spin_lock_irqsave, KF_RET_NULL | KF_SPINLOCK_SAFE)
+BTF_ID_FLAGS(func, bpf_res_spin_unlock_irqrestore, KF_SPINLOCK_SAFE)
BTF_KFUNCS_END(rqspinlock_kfunc_ids)
static const struct btf_kfunc_id_set rqspinlock_kfunc_set = {
diff --git a/kernel/bpf/stackmap.c b/kernel/bpf/stackmap.c
index 77ba03216c09..d09d4c3fe547 100644
--- a/kernel/bpf/stackmap.c
+++ b/kernel/bpf/stackmap.c
@@ -175,6 +175,95 @@ static inline void stack_map_build_id_set_valid(struct bpf_stack_build_id *id,
memcpy(id->build_id, build_id, BUILD_ID_SIZE_MAX);
}
+/*
+ * A cached VMA lookup result. The range [vm_start, vm_end) is always set.
+ * vm_pgoff, file, build_id are set only when the build ID was resolved.
+ * Zero vm_end marks the slot empty. build_id aliases the id_offs[] entry.
+ */
+struct stack_map_cached_vma {
+ unsigned long vm_start;
+ unsigned long vm_end;
+ unsigned long vm_pgoff;
+ struct file *file; /* pinned in the sleepable path; NULL otherwise */
+ const unsigned char *build_id;
+};
+
+/*
+ * Per stack_map_get_build_id_offset() call cache of the last VMA with a build ID
+ * resolved and the last VMA with no usable build ID. Adjacent stack frames tend
+ * to land in the same VMA or the same backing file, so caching the last result
+ * of each kind lets us skip unnecessary VMA lookups and build ID parse calls.
+ * Keeping the two slots independent means a build-ID-less VMA doesn't evict the
+ * last resolved build ID.
+ */
+struct stack_map_build_id_cache {
+ struct stack_map_cached_vma resolved;
+ struct stack_map_cached_vma unresolved;
+};
+
+/*
+ * Fill @id from a cached range covering @ip. On a hit this writes @id (resolved
+ * range -> build ID + offset, unresolved range -> raw ip) and returns 0; on a
+ * miss it leaves @id untouched and returns -ENOENT.
+ */
+static int stack_map_build_id_set_from_cache(struct stack_map_build_id_cache *cache,
+ struct bpf_stack_build_id *id, u64 ip)
+{
+ unsigned long vm_start, vm_end, vm_pgoff;
+ u64 offset;
+
+ vm_start = cache->resolved.vm_start;
+ vm_end = cache->resolved.vm_end;
+ if (vm_end && ip >= vm_start && ip < vm_end) {
+ vm_pgoff = cache->resolved.vm_pgoff;
+ offset = stack_map_build_id_offset(vm_pgoff, vm_start, ip);
+ stack_map_build_id_set_valid(id, offset, cache->resolved.build_id);
+ return 0;
+ }
+
+ vm_start = cache->unresolved.vm_start;
+ vm_end = cache->unresolved.vm_end;
+ if (vm_end && ip >= vm_start && ip < vm_end) {
+ stack_map_build_id_set_ip(id);
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+/*
+ * Record @vma's build ID as the last resolved one. @file is the pinned backing
+ * file in the sleepable path (released when evicted), or NULL otherwise.
+ */
+static void stack_map_build_id_cache_set_resolved(struct stack_map_build_id_cache *cache,
+ struct file *file,
+ const unsigned char *build_id,
+ unsigned long vm_start,
+ unsigned long vm_end,
+ unsigned long vm_pgoff)
+{
+ if (cache->resolved.file)
+ fput(cache->resolved.file);
+ cache->resolved = (struct stack_map_cached_vma){
+ .vm_start = vm_start,
+ .vm_end = vm_end,
+ .vm_pgoff = vm_pgoff,
+ .file = file,
+ .build_id = build_id,
+ };
+}
+
+/* Record [vm_start, vm_end) as a range with no usable build ID. */
+static void stack_map_build_id_cache_set_unresolved(struct stack_map_build_id_cache *cache,
+ unsigned long vm_start,
+ unsigned long vm_end)
+{
+ cache->unresolved = (struct stack_map_cached_vma){
+ .vm_start = vm_start,
+ .vm_end = vm_end,
+ };
+}
+
struct stack_map_vma_lock {
struct vm_area_struct *vma;
struct mm_struct *mm;
@@ -244,15 +333,9 @@ static void stack_map_unlock_vma(struct stack_map_vma_lock *lock)
static void stack_map_get_build_id_offset_sleepable(struct bpf_stack_build_id *id_offs,
u32 trace_nr)
{
- struct mm_struct *mm = current->mm;
- struct stack_map_vma_lock lock = { .mm = mm };
- struct {
- struct file *file;
- const unsigned char *build_id;
- unsigned long vm_start;
- unsigned long vm_end;
- unsigned long vm_pgoff;
- } cache = {};
+ struct stack_map_vma_lock lock = { .mm = current->mm };
+ struct stack_map_build_id_cache cache = {};
+ struct stack_map_cached_vma *res = &cache.resolved;
unsigned long vm_pgoff, vm_start, vm_end;
struct vm_area_struct *vma;
struct file *file;
@@ -262,44 +345,39 @@ static void stack_map_get_build_id_offset_sleepable(struct bpf_stack_build_id *i
for (u32 i = 0; i < trace_nr; i++) {
ip = READ_ONCE(id_offs[i].ip);
- /*
- * Range cache fast path: if ip falls within the previously
- * resolved VMA range, reuse the cache build_id without
- * re-acquiring the VMA lock.
- */
- if (cache.build_id && ip >= cache.vm_start && ip < cache.vm_end) {
- offset = stack_map_build_id_offset(cache.vm_pgoff, cache.vm_start, ip);
- stack_map_build_id_set_valid(&id_offs[i], offset, cache.build_id);
+ if (!stack_map_build_id_set_from_cache(&cache, &id_offs[i], ip))
continue;
- }
vma = stack_map_lock_vma(&lock, ip);
if (!vma) {
stack_map_build_id_set_ip(&id_offs[i]);
continue;
}
+
+ vm_pgoff = vma->vm_pgoff;
+ vm_start = vma->vm_start;
+ vm_end = vma->vm_end;
+
if (vma_is_anonymous(vma) || !vma->vm_file) {
- stack_map_build_id_set_ip(&id_offs[i]);
stack_map_unlock_vma(&lock);
+ stack_map_build_id_set_ip(&id_offs[i]);
+ stack_map_build_id_cache_set_unresolved(&cache, vm_start, vm_end);
continue;
}
file = vma->vm_file;
- vm_pgoff = vma->vm_pgoff;
- vm_start = vma->vm_start;
- vm_end = vma->vm_end;
offset = stack_map_build_id_offset(vm_pgoff, vm_start, ip);
/*
- * Same backing file as previous (e.g. different VMAs
- * of the same ELF binary). Reuse the cache build_id.
+ * Same backing file as the last resolved VMA (another mapping
+ * of the same ELF binary): reuse its build_id without re-parsing.
*/
- if (file == cache.file) {
+ if (file == res->file) {
stack_map_unlock_vma(&lock);
- stack_map_build_id_set_valid(&id_offs[i], offset, cache.build_id);
- cache.vm_start = vm_start;
- cache.vm_end = vm_end;
- cache.vm_pgoff = vm_pgoff;
+ stack_map_build_id_set_valid(&id_offs[i], offset, res->build_id);
+ res->vm_start = vm_start;
+ res->vm_end = vm_end;
+ res->vm_pgoff = vm_pgoff;
continue;
}
@@ -310,21 +388,17 @@ static void stack_map_get_build_id_offset_sleepable(struct bpf_stack_build_id *i
if (build_id_parse_file(file, id_offs[i].build_id, NULL)) {
stack_map_build_id_set_ip(&id_offs[i]);
fput(file);
+ stack_map_build_id_cache_set_unresolved(&cache, vm_start, vm_end);
continue;
}
stack_map_build_id_set_valid(&id_offs[i], offset, id_offs[i].build_id);
- if (cache.file)
- fput(cache.file);
- cache.file = file;
- cache.build_id = id_offs[i].build_id;
- cache.vm_start = vm_start;
- cache.vm_end = vm_end;
- cache.vm_pgoff = vm_pgoff;
+ stack_map_build_id_cache_set_resolved(&cache, file, id_offs[i].build_id,
+ vm_start, vm_end, vm_pgoff);
}
- if (cache.file)
- fput(cache.file);
+ if (res->file)
+ fput(res->file);
}
/*
@@ -340,11 +414,10 @@ static void stack_map_get_build_id_offset_sleepable(struct bpf_stack_build_id *i
static void stack_map_get_build_id_offset(struct bpf_stack_build_id *id_offs,
u32 trace_nr, bool user, bool may_fault)
{
- struct mmap_unlock_irq_work *work = NULL;
- bool irq_work_busy = bpf_mmap_unlock_get_irq_work(&work);
+ struct mmap_unlock_irq_work *work;
bool has_user_ctx = user && current && current->mm;
- struct vm_area_struct *vma, *prev_vma = NULL;
- const unsigned char *prev_build_id = NULL;
+ struct stack_map_build_id_cache cache = {};
+ struct vm_area_struct *vma;
int i;
if (may_fault && has_user_ctx) {
@@ -352,42 +425,52 @@ static void stack_map_get_build_id_offset(struct bpf_stack_build_id *id_offs,
return;
}
- /* If the irq_work is in use, fall back to report ips. Same
- * fallback is used for kernel stack (!user) on a stackmap with
- * build_id.
- */
- if (!has_user_ctx || irq_work_busy || !mmap_read_trylock(current->mm)) {
- /* cannot access current->mm, fall back to ips */
- for (i = 0; i < trace_nr; i++)
- stack_map_build_id_set_ip(&id_offs[i]);
- return;
+ if (!has_user_ctx)
+ goto fallback;
+
+ work = bpf_mmap_unlock_guard_get();
+ if (IS_ERR(work))
+ goto fallback;
+
+ if (!mmap_read_trylock(current->mm)) {
+ bpf_mmap_unlock_guard_put(work);
+ goto fallback;
}
for (i = 0; i < trace_nr; i++) {
u64 ip = READ_ONCE(id_offs[i].ip);
- u64 offset;
- if (prev_build_id && range_in_vma(prev_vma, ip, ip)) {
- vma = prev_vma;
- offset = stack_map_build_id_offset(vma->vm_pgoff, vma->vm_start, ip);
- stack_map_build_id_set_valid(&id_offs[i], offset, prev_build_id);
+ if (!stack_map_build_id_set_from_cache(&cache, &id_offs[i], ip))
continue;
- }
+
vma = find_vma(current->mm, ip);
if (!vma || vma_is_anonymous(vma) ||
fetch_build_id(vma, id_offs[i].build_id, may_fault)) {
- /* per entry fall back to ips */
+ /* per entry fall back to ips; cache build-ID-less range */
stack_map_build_id_set_ip(&id_offs[i]);
- prev_vma = vma;
- prev_build_id = NULL;
+ if (vma)
+ stack_map_build_id_cache_set_unresolved(&cache,
+ vma->vm_start, vma->vm_end);
continue;
}
- offset = stack_map_build_id_offset(vma->vm_pgoff, vma->vm_start, ip);
- stack_map_build_id_set_valid(&id_offs[i], offset, id_offs[i].build_id);
- prev_vma = vma;
- prev_build_id = id_offs[i].build_id;
+ /*
+ * mmap_lock is held for the whole loop, so the cached VMA
+ * fields stay valid; no file pinning is needed here.
+ */
+ stack_map_build_id_set_valid(&id_offs[i],
+ stack_map_build_id_offset(vma->vm_pgoff, vma->vm_start, ip),
+ id_offs[i].build_id);
+ stack_map_build_id_cache_set_resolved(&cache, NULL, id_offs[i].build_id,
+ vma->vm_start, vma->vm_end,
+ vma->vm_pgoff);
}
bpf_mmap_unlock_mm(work, current->mm);
+ return;
+
+fallback:
+ /* cannot access current->mm, fall back to ips */
+ for (i = 0; i < trace_nr; i++)
+ stack_map_build_id_set_ip(&id_offs[i]);
}
static struct perf_callchain_entry *
@@ -427,78 +510,116 @@ get_callchain_entry_for_task(struct task_struct *task, u32 max_depth)
#endif
}
-static long __bpf_get_stackid(struct bpf_map *map,
- struct perf_callchain_entry *trace, u64 flags)
+struct stackid {
+ struct stack_map_bucket *bucket;
+ const u64 *ips;
+ u32 nr;
+ u32 len;
+ u32 hash;
+ u32 id;
+ bool hash_matches;
+};
+
+static int stackid_init(struct stackid *stackid, struct bpf_map *map,
+ const struct perf_callchain_entry *trace, u32 trace_nr, u64 flags)
{
struct bpf_stack_map *smap = container_of(map, struct bpf_stack_map, map);
- struct stack_map_bucket *bucket, *new_bucket, *old_bucket;
- u32 hash, id, trace_nr, trace_len, i, max_depth;
u32 skip = flags & BPF_F_SKIP_FIELD_MASK;
- bool user = flags & BPF_F_USER_STACK;
- u64 *ips;
- bool hash_matches;
+ u32 max_depth;
- if (trace->nr <= skip)
+ if (trace_nr <= skip)
/* skipping more than usable stack trace */
return -EFAULT;
max_depth = stack_map_calculate_max_depth(map->value_size, stack_map_data_size(map), flags);
- trace_nr = min_t(u32, trace->nr - skip, max_depth - skip);
- trace_len = trace_nr * sizeof(u64);
- ips = trace->ip + skip;
- hash = jhash2((u32 *)ips, trace_len / sizeof(u32), 0);
- id = hash & (smap->n_buckets - 1);
- bucket = READ_ONCE(smap->buckets[id]);
+ stackid->nr = min_t(u32, trace_nr - skip, max_depth - skip);
+ stackid->len = stackid->nr * sizeof(u64);
+ stackid->ips = trace->ip + skip;
+ stackid->hash = jhash2((const u32 *)stackid->ips, stackid->len / sizeof(u32), 0);
+ stackid->id = stackid->hash & (smap->n_buckets - 1);
+ stackid->bucket = READ_ONCE(smap->buckets[stackid->id]);
+ stackid->hash_matches = stackid->bucket && stackid->bucket->hash == stackid->hash;
+ return 0;
+}
+
+static int stackid_fastpath(struct stackid *stackid, struct bpf_map *map,
+ const struct perf_callchain_entry *trace, u32 trace_nr,
+ u64 flags)
+{
+ int err;
+
+ err = stackid_init(stackid, map, trace, trace_nr, flags);
+ if (err)
+ return err;
- hash_matches = bucket && bucket->hash == hash;
/* fast cmp */
- if (hash_matches && flags & BPF_F_FAST_STACK_CMP)
- return id;
+ if (stackid->hash_matches && flags & BPF_F_FAST_STACK_CMP)
+ return stackid->id;
+
+ if (stack_map_use_build_id(map))
+ return -ENOENT;
+ if (stackid->hash_matches && stackid->bucket->nr == stackid->nr &&
+ memcmp(stackid->bucket->data, stackid->ips, stackid->len) == 0)
+ return stackid->id;
+ if (stackid->bucket && !(flags & BPF_F_REUSE_STACKID))
+ return -EEXIST;
+ return -ENOENT;
+}
+
+static struct stack_map_bucket *
+stackid_new_bucket(struct stackid *stackid, struct bpf_map *map)
+{
+ struct bpf_stack_map *smap = container_of(map, struct bpf_stack_map, map);
+ struct bpf_stack_build_id *id_offs;
+ struct stack_map_bucket *bucket;
+ u32 i;
+
+ bucket = (struct stack_map_bucket *) pcpu_freelist_pop(&smap->freelist);
+ if (unlikely(!bucket))
+ return NULL;
+
+ if (stack_map_use_build_id(map)) {
+ id_offs = (struct bpf_stack_build_id *)bucket->data;
+ for (i = 0; i < stackid->nr; i++)
+ id_offs[i].ip = stackid->ips[i];
+ } else {
+ memcpy(bucket->data, stackid->ips, stackid->len);
+ }
+
+ bucket->hash = stackid->hash;
+ bucket->nr = stackid->nr;
+ return bucket;
+}
+
+static long stackid_install(struct stackid *stackid, struct bpf_map *map,
+ struct stack_map_bucket *new_bucket, u64 flags)
+{
+ struct bpf_stack_map *smap = container_of(map, struct bpf_stack_map, map);
+ bool user = flags & BPF_F_USER_STACK;
+ struct stack_map_bucket *old_bucket;
+ u32 trace_len;
if (stack_map_use_build_id(map)) {
struct bpf_stack_build_id *id_offs;
- /* for build_id+offset, pop a bucket before slow cmp */
- new_bucket = (struct stack_map_bucket *)
- pcpu_freelist_pop(&smap->freelist);
- if (unlikely(!new_bucket))
- return -ENOMEM;
- new_bucket->nr = trace_nr;
id_offs = (struct bpf_stack_build_id *)new_bucket->data;
- for (i = 0; i < trace_nr; i++)
- id_offs[i].ip = ips[i];
- stack_map_get_build_id_offset(id_offs, trace_nr, user, false /* !may_fault */);
- trace_len = trace_nr * sizeof(struct bpf_stack_build_id);
- if (hash_matches && bucket->nr == trace_nr &&
- memcmp(bucket->data, new_bucket->data, trace_len) == 0) {
+ stack_map_get_build_id_offset(id_offs, stackid->nr, user, false /* !may_fault */);
+ trace_len = stackid->nr * sizeof(struct bpf_stack_build_id);
+ if (stackid->hash_matches && stackid->bucket->nr == stackid->nr &&
+ memcmp(stackid->bucket->data, new_bucket->data, trace_len) == 0) {
pcpu_freelist_push(&smap->freelist, &new_bucket->fnode);
- return id;
+ return stackid->id;
}
- if (bucket && !(flags & BPF_F_REUSE_STACKID)) {
+ if (stackid->bucket && !(flags & BPF_F_REUSE_STACKID)) {
pcpu_freelist_push(&smap->freelist, &new_bucket->fnode);
return -EEXIST;
}
- } else {
- if (hash_matches && bucket->nr == trace_nr &&
- memcmp(bucket->data, ips, trace_len) == 0)
- return id;
- if (bucket && !(flags & BPF_F_REUSE_STACKID))
- return -EEXIST;
-
- new_bucket = (struct stack_map_bucket *)
- pcpu_freelist_pop(&smap->freelist);
- if (unlikely(!new_bucket))
- return -ENOMEM;
- memcpy(new_bucket->data, ips, trace_len);
}
- new_bucket->hash = hash;
- new_bucket->nr = trace_nr;
-
- old_bucket = xchg(&smap->buckets[id], new_bucket);
+ old_bucket = xchg(&smap->buckets[stackid->id], new_bucket);
if (old_bucket)
pcpu_freelist_push(&smap->freelist, &old_bucket->fnode);
- return id;
+ return stackid->id;
}
BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map,
@@ -506,23 +627,36 @@ BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map,
{
u32 elem_size = stack_map_data_size(map);
bool user = flags & BPF_F_USER_STACK;
+ struct stack_map_bucket *new_bucket;
struct perf_callchain_entry *trace;
+ struct stackid stackid;
bool kernel = !user;
u32 max_depth;
+ int err;
if (unlikely(flags & ~(BPF_F_SKIP_FIELD_MASK | BPF_F_USER_STACK |
BPF_F_FAST_STACK_CMP | BPF_F_REUSE_STACKID)))
return -EINVAL;
max_depth = stack_map_calculate_max_depth(map->value_size, elem_size, flags);
- trace = get_perf_callchain(regs, kernel, user, max_depth,
- false, false, 0);
- if (unlikely(!trace))
- /* couldn't fetch the stack trace */
- return -EFAULT;
+ scoped_guard(preempt) {
+ trace = get_perf_callchain(regs, kernel, user, max_depth,
+ false, false, 0);
+ if (unlikely(!trace))
+ /* couldn't fetch the stack trace */
+ return -EFAULT;
+
+ err = stackid_fastpath(&stackid, map, trace, trace->nr, flags);
+ if (err != -ENOENT)
+ return err;
+
+ new_bucket = stackid_new_bucket(&stackid, map);
+ if (!new_bucket)
+ return -ENOMEM;
+ }
- return __bpf_get_stackid(map, trace, flags);
+ return stackid_install(&stackid, map, new_bucket, flags);
}
const struct bpf_func_proto bpf_get_stackid_proto = {
@@ -534,7 +668,7 @@ const struct bpf_func_proto bpf_get_stackid_proto = {
.arg3_type = ARG_ANYTHING,
};
-static __u64 count_kernel_ip(struct perf_callchain_entry *trace)
+static __u64 count_kernel_ip(const struct perf_callchain_entry *trace)
{
__u64 nr_kernel = 0;
@@ -549,10 +683,13 @@ static __u64 count_kernel_ip(struct perf_callchain_entry *trace)
BPF_CALL_3(bpf_get_stackid_pe, struct bpf_perf_event_data_kern *, ctx,
struct bpf_map *, map, u64, flags)
{
+ const struct perf_callchain_entry *trace;
struct perf_event *event = ctx->event;
- struct perf_callchain_entry *trace;
+ struct stack_map_bucket *new_bucket;
+ struct stackid stackid;
bool kernel, user;
__u64 nr_kernel;
+ u32 trace_nr;
int ret;
/* perf_sample_data doesn't have callchain, use bpf_get_stackid */
@@ -572,26 +709,28 @@ BPF_CALL_3(bpf_get_stackid_pe, struct bpf_perf_event_data_kern *, ctx,
return -EFAULT;
nr_kernel = count_kernel_ip(trace);
- __u64 nr = trace->nr; /* save original */
if (kernel) {
- trace->nr = nr_kernel;
- ret = __bpf_get_stackid(map, trace, flags);
+ trace_nr = nr_kernel;
} else { /* user */
u64 skip = flags & BPF_F_SKIP_FIELD_MASK;
+ trace_nr = trace->nr;
skip += nr_kernel;
if (skip > BPF_F_SKIP_FIELD_MASK)
return -EFAULT;
flags = (flags & ~BPF_F_SKIP_FIELD_MASK) | skip;
- ret = __bpf_get_stackid(map, trace, flags);
}
- /* restore nr */
- trace->nr = nr;
+ ret = stackid_fastpath(&stackid, map, trace, trace_nr, flags);
+ if (ret != -ENOENT)
+ return ret;
- return ret;
+ new_bucket = stackid_new_bucket(&stackid, map);
+ if (new_bucket)
+ return stackid_install(&stackid, map, new_bucket, flags);
+ return -ENOMEM;
}
const struct bpf_func_proto bpf_get_stackid_proto_pe = {
@@ -603,19 +742,55 @@ const struct bpf_func_proto bpf_get_stackid_proto_pe = {
.arg3_type = ARG_ANYTHING,
};
+static u32 callchain_store(const struct perf_callchain_entry *trace, u32 trace_nr,
+ void *buf, u32 elem_size, u64 flags)
+{
+ bool user_build_id = flags & BPF_F_USER_BUILD_ID;
+ u32 skip = flags & BPF_F_SKIP_FIELD_MASK;
+ const u64 *ips;
+ u32 copy_len;
+
+ trace_nr = trace_nr - skip;
+ copy_len = trace_nr * elem_size;
+
+ ips = trace->ip + skip;
+ if (user_build_id) {
+ struct bpf_stack_build_id *id_offs = buf;
+
+ for (u32 i = 0; i < trace_nr; i++)
+ id_offs[i].ip = ips[i];
+ } else {
+ memcpy(buf, ips, copy_len);
+ }
+ return trace_nr;
+}
+
+static long callchain_finalize(void *buf, u32 size, u32 trace_nr, u32 elem_size,
+ u64 flags, bool may_fault)
+{
+ bool user_build_id = flags & BPF_F_USER_BUILD_ID;
+ bool user = flags & BPF_F_USER_STACK;
+ u32 copy_len = trace_nr * elem_size;
+
+ if (user_build_id)
+ stack_map_get_build_id_offset(buf, trace_nr, user, may_fault);
+
+ if (size > copy_len)
+ memset(buf + copy_len, 0, size - copy_len);
+ return copy_len;
+}
+
static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task,
- struct perf_callchain_entry *trace_in,
void *buf, u32 size, u64 flags, bool may_fault)
{
- u32 trace_nr, copy_len, elem_size, max_depth;
bool user_build_id = flags & BPF_F_USER_BUILD_ID;
bool crosstask = task && task != current;
u32 skip = flags & BPF_F_SKIP_FIELD_MASK;
bool user = flags & BPF_F_USER_STACK;
struct perf_callchain_entry *trace;
+ u32 trace_nr, elem_size, max_depth;
bool kernel = !user;
int err = -EINVAL;
- u64 *ips;
if (unlikely(flags & ~(BPF_F_SKIP_FIELD_MASK | BPF_F_USER_STACK |
BPF_F_USER_BUILD_ID)))
@@ -641,13 +816,11 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task,
max_depth = stack_map_calculate_max_depth(size, elem_size, flags);
+ preempt_disable();
if (may_fault)
rcu_read_lock(); /* need RCU for perf's callchain below */
- if (trace_in) {
- trace = trace_in;
- trace->nr = min_t(u32, trace->nr, max_depth);
- } else if (kernel && task) {
+ if (kernel && task) {
trace = get_callchain_entry_for_task(task, max_depth);
} else {
trace = get_perf_callchain(regs, kernel, user, max_depth,
@@ -657,33 +830,18 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task,
if (unlikely(!trace) || trace->nr < skip) {
if (may_fault)
rcu_read_unlock();
+ preempt_enable();
goto err_fault;
}
- trace_nr = trace->nr - skip;
- copy_len = trace_nr * elem_size;
-
- ips = trace->ip + skip;
- if (user_build_id) {
- struct bpf_stack_build_id *id_offs = buf;
- u32 i;
-
- for (i = 0; i < trace_nr; i++)
- id_offs[i].ip = ips[i];
- } else {
- memcpy(buf, ips, copy_len);
- }
+ trace_nr = callchain_store(trace, trace->nr, buf, elem_size, flags);
- /* trace/ips should not be dereferenced after this point */
+ /* trace should not be dereferenced after this point */
if (may_fault)
rcu_read_unlock();
+ preempt_enable();
- if (user_build_id)
- stack_map_get_build_id_offset(buf, trace_nr, user, may_fault);
-
- if (size > copy_len)
- memset(buf + copy_len, 0, size - copy_len);
- return copy_len;
+ return callchain_finalize(buf, size, trace_nr, elem_size, flags, may_fault);
err_fault:
err = -EFAULT;
@@ -695,7 +853,7 @@ clear:
BPF_CALL_4(bpf_get_stack, struct pt_regs *, regs, void *, buf, u32, size,
u64, flags)
{
- return __bpf_get_stack(regs, NULL, NULL, buf, size, flags, false /* !may_fault */);
+ return __bpf_get_stack(regs, NULL, buf, size, flags, false /* !may_fault */);
}
const struct bpf_func_proto bpf_get_stack_proto = {
@@ -704,23 +862,24 @@ const struct bpf_func_proto bpf_get_stack_proto = {
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_PTR_TO_UNINIT_MEM,
- .arg3_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg3_type = ARG_MEM_SIZE_OR_ZERO,
.arg4_type = ARG_ANYTHING,
};
BPF_CALL_4(bpf_get_stack_sleepable, struct pt_regs *, regs, void *, buf, u32, size,
u64, flags)
{
- return __bpf_get_stack(regs, NULL, NULL, buf, size, flags, true /* may_fault */);
+ return __bpf_get_stack(regs, NULL, buf, size, flags, true /* may_fault */);
}
const struct bpf_func_proto bpf_get_stack_sleepable_proto = {
.func = bpf_get_stack_sleepable,
.gpl_only = true,
+ .might_sleep = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_PTR_TO_UNINIT_MEM,
- .arg3_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg3_type = ARG_MEM_SIZE_OR_ZERO,
.arg4_type = ARG_ANYTHING,
};
@@ -730,14 +889,17 @@ static long __bpf_get_task_stack(struct task_struct *task, void *buf, u32 size,
struct pt_regs *regs;
long res = -EINVAL;
- if (!try_get_task_stack(task))
+ if (!try_get_task_stack(task)) {
+ memset(buf, 0, size);
return -EFAULT;
+ }
regs = task_pt_regs(task);
if (regs)
- res = __bpf_get_stack(regs, task, NULL, buf, size, flags, may_fault);
+ res = __bpf_get_stack(regs, task, buf, size, flags, may_fault);
+ else
+ memset(buf, 0, size);
put_task_stack(task);
-
return res;
}
@@ -754,7 +916,7 @@ const struct bpf_func_proto bpf_get_task_stack_proto = {
.arg1_type = ARG_PTR_TO_BTF_ID,
.arg1_btf_id = &btf_tracing_ids[BTF_TRACING_TYPE_TASK],
.arg2_type = ARG_PTR_TO_UNINIT_MEM,
- .arg3_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg3_type = ARG_MEM_SIZE_OR_ZERO,
.arg4_type = ARG_ANYTHING,
};
@@ -767,26 +929,53 @@ BPF_CALL_4(bpf_get_task_stack_sleepable, struct task_struct *, task, void *, buf
const struct bpf_func_proto bpf_get_task_stack_sleepable_proto = {
.func = bpf_get_task_stack_sleepable,
.gpl_only = false,
+ .might_sleep = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_BTF_ID,
.arg1_btf_id = &btf_tracing_ids[BTF_TRACING_TYPE_TASK],
.arg2_type = ARG_PTR_TO_UNINIT_MEM,
- .arg3_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg3_type = ARG_MEM_SIZE_OR_ZERO,
.arg4_type = ARG_ANYTHING,
};
+static int __bpf_get_stack_pe(const struct perf_callchain_entry *trace, u32 trace_nr,
+ void *buf, u32 size, u64 flags)
+{
+ bool user_build_id = flags & BPF_F_USER_BUILD_ID;
+ u64 skip = flags & BPF_F_SKIP_FIELD_MASK;
+ bool user = flags & BPF_F_USER_STACK;
+ u32 elem_size, max_depth, nr_trace;
+ bool kernel = !user;
+
+ if (kernel && user_build_id)
+ return -EINVAL;
+
+ elem_size = user_build_id ? sizeof(struct bpf_stack_build_id) : sizeof(u64);
+ if (unlikely(size % elem_size))
+ return -EINVAL;
+
+ max_depth = stack_map_calculate_max_depth(size, elem_size, flags);
+ trace_nr = min_t(u32, trace_nr, max_depth);
+
+ if (trace_nr < skip)
+ return -EFAULT;
+
+ nr_trace = callchain_store(trace, trace_nr, buf, elem_size, flags);
+ return callchain_finalize(buf, size, nr_trace, elem_size, flags, false /* !may_fault */);
+}
+
BPF_CALL_4(bpf_get_stack_pe, struct bpf_perf_event_data_kern *, ctx,
void *, buf, u32, size, u64, flags)
{
struct pt_regs *regs = (struct pt_regs *)(ctx->regs);
+ const struct perf_callchain_entry *trace;
struct perf_event *event = ctx->event;
- struct perf_callchain_entry *trace;
bool kernel, user;
int err = -EINVAL;
__u64 nr_kernel;
if (!(event->attr.sample_type & PERF_SAMPLE_CALLCHAIN))
- return __bpf_get_stack(regs, NULL, NULL, buf, size, flags, false /* !may_fault */);
+ return __bpf_get_stack(regs, NULL, buf, size, flags, false /* !may_fault */);
if (unlikely(flags & ~(BPF_F_SKIP_FIELD_MASK | BPF_F_USER_STACK |
BPF_F_USER_BUILD_ID)))
@@ -803,27 +992,20 @@ BPF_CALL_4(bpf_get_stack_pe, struct bpf_perf_event_data_kern *, ctx,
nr_kernel = count_kernel_ip(trace);
if (kernel) {
- __u64 nr = trace->nr;
-
- trace->nr = nr_kernel;
- err = __bpf_get_stack(regs, NULL, trace, buf, size, flags, false /* !may_fault */);
-
- /* restore nr */
- trace->nr = nr;
+ err = __bpf_get_stack_pe(trace, nr_kernel, buf, size, flags);
} else { /* user */
u64 skip = flags & BPF_F_SKIP_FIELD_MASK;
skip += nr_kernel;
if (skip > BPF_F_SKIP_FIELD_MASK)
goto clear;
-
flags = (flags & ~BPF_F_SKIP_FIELD_MASK) | skip;
- err = __bpf_get_stack(regs, NULL, trace, buf, size, flags, false /* !may_fault */);
+ err = __bpf_get_stack_pe(trace, trace->nr, buf, size, flags);
}
- return err;
clear:
- memset(buf, 0, size);
+ if (err < 0)
+ memset(buf, 0, size);
return err;
}
@@ -834,7 +1016,7 @@ const struct bpf_func_proto bpf_get_stack_proto_pe = {
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_PTR_TO_UNINIT_MEM,
- .arg3_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg3_type = ARG_MEM_SIZE_OR_ZERO,
.arg4_type = ARG_ANYTHING,
};
diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c
index 32f346ce3ffc..012b82513a3b 100644
--- a/kernel/bpf/states.c
+++ b/kernel/bpf/states.c
@@ -436,30 +436,28 @@ static void __clean_func_state(struct bpf_verifier_env *env,
continue;
/*
- * Only destroy spilled_ptr when hi half is dead.
- * If hi half is still live with STACK_SPILL, the
- * spilled_ptr metadata is needed for correct state
- * comparison in stacksafe().
- * is_spilled_reg() is using slot_type[7], but
- * is_spilled_scalar_after() check either slot_type[0] or [4]
+ * Only scalar spills can be degraded to raw stack bytes
+ * when their high half is dead. Pointer spills need the
+ * saved spilled_ptr metadata so partial fills keep
+ * rejecting as non-scalar register fills.
*/
if (!hi_live) {
struct bpf_reg_state *spill = &st->stack[i].spilled_ptr;
if (lo_live && stype == STACK_SPILL) {
- u8 val = STACK_MISC;
-
+ if (spill->type != SCALAR_VALUE)
+ continue;
/*
- * 8 byte spill of scalar 0 where half slot is dead
- * should become STACK_ZERO in lo 4 bytes.
+ * Can't replace with STACK_ZERO, because
+ * that requires bpf_mark_chain_precision().
*/
if (bpf_register_is_null(spill))
- val = STACK_ZERO;
+ continue;
for (j = 0; j < 4; j++) {
u8 *t = &st->stack[i].slot_type[j];
if (*t == STACK_SPILL)
- *t = val;
+ *t = STACK_MISC;
}
}
bpf_mark_reg_not_init(env, spill);
@@ -493,7 +491,8 @@ static bool regs_exact(const struct bpf_reg_state *rold,
{
return memcmp(rold, rcur, offsetof(struct bpf_reg_state, id)) == 0 &&
check_ids(rold->id, rcur->id, idmap) &&
- check_ids(rold->parent_id, rcur->parent_id, idmap);
+ check_ids(rold->parent_id, rcur->parent_id, idmap) &&
+ check_ids(rold->map_uid, rcur->map_uid, idmap);
}
enum exact_level {
@@ -618,7 +617,8 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
range_within(rold, rcur) &&
tnum_in(rold->var_off, rcur->var_off) &&
check_ids(rold->id, rcur->id, idmap) &&
- check_ids(rold->parent_id, rcur->parent_id, idmap);
+ check_ids(rold->parent_id, rcur->parent_id, idmap) &&
+ check_ids(rold->map_uid, rcur->map_uid, idmap);
case PTR_TO_PACKET_META:
case PTR_TO_PACKET:
/* We must have at least as much range as the old ptr
@@ -637,14 +637,14 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
/* id relations must be preserved */
if (!check_ids(rold->id, rcur->id, idmap))
return false;
+ /* Preserve displacements between pointers sharing an ID. */
+ if (rold->id && rold->r64.base != rcur->r64.base)
+ return false;
/* new val must satisfy old val knowledge */
return range_within(rold, rcur) &&
tnum_in(rold->var_off, rcur->var_off);
case PTR_TO_STACK:
- /* two stack pointers are equal only if they're pointing to
- * the same stack frame, since fp-8 in foo != fp-8 in bar
- */
- return regs_exact(rold, rcur, idmap) && rold->frameno == rcur->frameno;
+ return regs_exact(rold, rcur, idmap);
case PTR_TO_ARENA:
return true;
case PTR_TO_INSN:
@@ -811,7 +811,8 @@ static bool stacksafe(struct bpf_verifier_env *env, struct bpf_func_state *old,
* infinite loop check triggering, see
* iter_active_depths_differ()
*/
- if (old_reg->iter.btf != cur_reg->iter.btf ||
+ if (old_reg->type != cur_reg->type ||
+ old_reg->iter.btf != cur_reg->iter.btf ||
old_reg->iter.btf_id != cur_reg->iter.btf_id ||
old_reg->iter.state != cur_reg->iter.state ||
/* ignore {old_reg,cur_reg}->iter.depth, see above */
@@ -1122,7 +1123,7 @@ static bool states_maybe_looping(struct bpf_verifier_state *old,
fcur = cur->frame[fr];
for (i = 0; i < MAX_BPF_REG; i++)
if (memcmp(&fold->regs[i], &fcur->regs[i],
- offsetof(struct bpf_reg_state, frameno)))
+ offsetof(struct bpf_reg_state, precise)))
return false;
return true;
}
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index b44106c8ea75..96217b99399d 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -40,7 +40,6 @@
#include <linux/tracepoint.h>
#include <linux/overflow.h>
#include <linux/cookie.h>
-#include <linux/verification.h>
#include <linux/btf_ids.h>
#include <net/netfilter/nf_bpf_link.h>
@@ -637,7 +636,6 @@ int bpf_map_alloc_pages(const struct bpf_map *map, int nid,
return ret;
}
-
static int btf_field_cmp(const void *a, const void *b)
{
const struct btf_field *f1 = a, *f2 = b;
@@ -1078,11 +1076,24 @@ static void bpf_map_mmap_close(struct vm_area_struct *vma)
bpf_map_write_active_dec(map);
}
+static vm_fault_t bpf_map_mmap_fault(struct vm_fault *vmf)
+{
+ struct bpf_map *map = vmf->vma->vm_private_data;
+
+ return map->ops->map_mmap_fault(map, vmf);
+}
+
static const struct vm_operations_struct bpf_map_default_vmops = {
.open = bpf_map_mmap_open,
.close = bpf_map_mmap_close,
};
+static const struct vm_operations_struct bpf_map_lazy_vmops = {
+ .open = bpf_map_mmap_open,
+ .close = bpf_map_mmap_close,
+ .fault = bpf_map_mmap_fault,
+};
+
static int bpf_map_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct bpf_map *map = filp->private_data;
@@ -1118,7 +1129,7 @@ out:
return err;
/* set default open/close callbacks */
- vma->vm_ops = &bpf_map_default_vmops;
+ vma->vm_ops = map->ops->map_mmap_fault ? &bpf_map_lazy_vmops : &bpf_map_default_vmops;
vma->vm_private_data = map;
vm_flags_clear(vma, VM_MAYEXEC);
/* If mapping is read-only, then disallow potentially re-mapping with
@@ -1599,13 +1610,6 @@ static int map_create_alloc(union bpf_attr *attr, bpfptr_t uattr, struct bpf_ver
err = -EFAULT;
goto free_map;
}
-
- /* See libbpf: emit_signature_match() */
- BUILD_BUG_ON(offsetof(struct bpf_map, excl) != SHA256_DIGEST_SIZE);
- BUILD_BUG_ON(!__same_type(map->excl, u32));
- BUILD_BUG_ON(offsetof(struct bpf_map, sha) != 0);
- BUILD_BUG_ON(!__same_type(map->sha, u8[SHA256_DIGEST_SIZE]));
- map->excl = 1;
} else if (attr->excl_prog_hash_size) {
bpf_log(log, "Invalid excl_prog_hash_size.\n");
err = -EINVAL;
@@ -1657,7 +1661,7 @@ static int map_create(union bpf_attr *attr, bpfptr_t uattr, struct bpf_common_at
err = security_bpf_map_create(map, attr, token, uattr.is_kernel);
if (err)
- goto free_map_sec;
+ goto free_map;
err = bpf_map_alloc_id(map);
if (err)
@@ -1838,7 +1842,6 @@ free_key:
return err;
}
-
#define BPF_MAP_UPDATE_ELEM_LAST_FIELD flags
static int map_update_elem(union bpf_attr *attr, bpfptr_t uattr)
@@ -2033,7 +2036,7 @@ int generic_map_delete_batch(struct bpf_map *map,
for (cp = 0; cp < max_count; cp++) {
err = -EFAULT;
- if (copy_from_user(key, keys + cp * map->key_size,
+ if (copy_from_user(key, keys + (size_t)cp * map->key_size,
map->key_size))
break;
@@ -2095,9 +2098,9 @@ int generic_map_update_batch(struct bpf_map *map, struct file *map_file,
for (cp = 0; cp < max_count; cp++) {
err = -EFAULT;
- if (copy_from_user(key, keys + cp * map->key_size,
+ if (copy_from_user(key, keys + (size_t)cp * map->key_size,
map->key_size) ||
- copy_from_user(value, values + cp * value_size, value_size))
+ copy_from_user(value, values + (size_t)cp * value_size, value_size))
break;
err = bpf_map_update_value(map, map_file, key, value,
@@ -2176,12 +2179,12 @@ int generic_map_lookup_batch(struct bpf_map *map,
if (err)
goto free_buf;
- if (copy_to_user(keys + cp * map->key_size, key,
+ if (copy_to_user(keys + (size_t)cp * map->key_size, key,
map->key_size)) {
err = -EFAULT;
goto free_buf;
}
- if (copy_to_user(values + cp * value_size, value, value_size)) {
+ if (copy_to_user(values + (size_t)cp * value_size, value, value_size)) {
err = -EFAULT;
goto free_buf;
}
@@ -2445,6 +2448,21 @@ static void __bpf_prog_put_rcu(struct rcu_head *rcu)
bpf_prog_free(aux->prog);
}
+/*
+ * Progs called from a trampoline can also be reached by a task that was
+ * preempted in the trampoline before the prog's enter helper took its RCU
+ * read lock, wait for those first.
+ */
+static void __bpf_prog_put_rcu_tasks(struct rcu_head *rcu)
+{
+ struct bpf_prog *prog = container_of(rcu, struct bpf_prog_aux, rcu)->prog;
+
+ if (prog->sleepable)
+ call_rcu_tasks_trace(rcu, __bpf_prog_put_rcu);
+ else
+ call_rcu(rcu, __bpf_prog_put_rcu);
+}
+
static void __bpf_prog_put_noref(struct bpf_prog *prog, bool deferred)
{
bpf_prog_kallsyms_del_all(prog);
@@ -2458,7 +2476,9 @@ static void __bpf_prog_put_noref(struct bpf_prog *prog, bool deferred)
btf_put(prog->aux->attach_btf);
if (deferred) {
- if (prog->sleepable)
+ if (IS_ENABLED(CONFIG_TASKS_RCU) && prog->aux->tramp_linked)
+ call_rcu_tasks(&prog->aux->rcu, __bpf_prog_put_rcu_tasks);
+ else if (prog->sleepable)
call_rcu_tasks_trace(&prog->aux->rcu, __bpf_prog_put_rcu);
else
call_rcu(&prog->aux->rcu, __bpf_prog_put_rcu);
@@ -2886,64 +2906,6 @@ static bool is_perfmon_prog_type(enum bpf_prog_type prog_type)
}
}
-static enum bpf_sig_keyring bpf_classify_keyring(s32 keyring_id)
-{
- switch (keyring_id) {
- case 0:
- return BPF_SIG_KEYRING_BUILTIN;
- case (s32)(unsigned long)VERIFY_USE_SECONDARY_KEYRING:
- return BPF_SIG_KEYRING_SECONDARY;
- case (s32)(unsigned long)VERIFY_USE_PLATFORM_KEYRING:
- return BPF_SIG_KEYRING_PLATFORM;
- default:
- return BPF_SIG_KEYRING_USER;
- }
-}
-
-static int bpf_prog_verify_signature(struct bpf_prog *prog, union bpf_attr *attr,
- bool is_kernel, s32 *keyring_serial)
-{
- bpfptr_t usig = make_bpfptr(attr->signature, is_kernel);
- struct bpf_dynptr_kern sig_ptr, insns_ptr;
- struct bpf_key *key = NULL;
- void *sig;
- int err = 0;
-
- /*
- * Don't attempt to use kmalloc_large or vmalloc for signatures.
- * Practical signature for BPF program should be below this limit.
- */
- if (attr->signature_size > KMALLOC_MAX_CACHE_SIZE)
- return -EINVAL;
-
- if (system_keyring_id_check(attr->keyring_id) == 0)
- key = bpf_lookup_system_key(attr->keyring_id);
- else
- key = bpf_lookup_user_key(attr->keyring_id, 0);
-
- if (!key)
- return -EINVAL;
-
- sig = kvmemdup_bpfptr(usig, attr->signature_size);
- if (IS_ERR(sig)) {
- bpf_key_put(key);
- return PTR_ERR(sig);
- }
-
- bpf_dynptr_init(&sig_ptr, sig, BPF_DYNPTR_TYPE_LOCAL, 0,
- attr->signature_size);
- bpf_dynptr_init(&insns_ptr, prog->insnsi, BPF_DYNPTR_TYPE_LOCAL, 0,
- prog->len * sizeof(struct bpf_insn));
-
- err = bpf_verify_pkcs7_signature((struct bpf_dynptr *)&insns_ptr,
- (struct bpf_dynptr *)&sig_ptr, key);
- if (!err)
- *keyring_serial = bpf_key_serial(key);
- bpf_key_put(key);
- kvfree(sig);
- return err;
-}
-
static int bpf_prog_mark_insn_arrays_ready(struct bpf_prog *prog)
{
int err;
@@ -3109,6 +3071,10 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, struct bpf_log_at
prog->aux->attach_btf = attach_btf;
prog->aux->attach_btf_id = multi_func ? bpf_multi_func_btf_id[0] : attr->attach_btf_id;
prog->aux->dst_prog = dst_prog;
+ if (dst_prog) {
+ prog->aux->saved_dst_prog_type = dst_prog->type;
+ prog->aux->saved_dst_attach_type = dst_prog->expected_attach_type;
+ }
prog->aux->dev_bound = !!attr->prog_ifindex;
prog->aux->xdp_has_frags = attr->prog_flags & BPF_F_XDP_HAS_FRAGS;
@@ -3133,17 +3099,8 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, struct bpf_log_at
/* eBPF programs must be GPL compatible to use GPL-ed functions */
prog->gpl_compatible = license_is_gpl_compatible(license) ? 1 : 0;
- if (attr->signature) {
- err = bpf_prog_verify_signature(prog, attr, uattr.is_kernel,
- &prog->aux->sig.keyring_serial);
- if (err)
- goto free_prog;
- prog->aux->sig.keyring_type = bpf_classify_keyring(attr->keyring_id);
- prog->aux->sig.verdict = BPF_SIG_VERIFIED;
- } else {
- prog->aux->sig.keyring_type = BPF_SIG_KEYRING_NONE;
- prog->aux->sig.verdict = BPF_SIG_UNSIGNED;
- }
+ prog->aux->sig.keyring_type = BPF_SIG_KEYRING_NONE;
+ prog->aux->sig.verdict = BPF_SIG_UNSIGNED;
prog->orig_prog = NULL;
prog->jited = 0;
@@ -3189,10 +3146,6 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, struct bpf_log_at
if (err < 0)
goto free_prog;
- err = security_bpf_prog_load(prog, attr, token, uattr.is_kernel);
- if (err)
- goto free_prog;
-
/* run eBPF verifier */
err = bpf_check(&prog, attr, uattr, attr_log);
if (err < 0)
@@ -3471,9 +3424,10 @@ static const char *bpf_link_type_strs[] = {
static void bpf_link_show_fdinfo(struct seq_file *m, struct file *filp)
{
const struct bpf_link *link = filp->private_data;
- const struct bpf_prog *prog = link->prog;
+ const struct bpf_prog *prog;
enum bpf_link_type type = link->type;
char prog_tag[sizeof(prog->tag) * 2 + 1] = { };
+ u32 prog_id = 0;
if (type < ARRAY_SIZE(bpf_link_type_strs) && bpf_link_type_strs[type]) {
if (link->type == BPF_LINK_TYPE_KPROBE_MULTI)
@@ -3490,13 +3444,20 @@ static void bpf_link_show_fdinfo(struct seq_file *m, struct file *filp)
}
seq_printf(m, "link_id:\t%u\n", link->id);
+ rcu_read_lock();
+ prog = READ_ONCE(link->prog);
if (prog) {
bin2hex(prog_tag, prog->tag, sizeof(prog->tag));
+ prog_id = prog->aux->id;
+ }
+ rcu_read_unlock();
+
+ if (prog) {
seq_printf(m,
"prog_tag:\t%s\n"
"prog_id:\t%u\n",
prog_tag,
- prog->aux->id);
+ prog_id);
}
if (link->ops->show_fdinfo)
link->ops->show_fdinfo(link, m);
@@ -3564,7 +3525,6 @@ int bpf_link_prime(struct bpf_link *link, struct bpf_link_primer *primer)
if (fd < 0)
return fd;
-
id = bpf_link_alloc_id(link);
if (id < 0) {
put_unused_fd(fd);
@@ -3626,10 +3586,12 @@ static void bpf_tracing_link_release(struct bpf_link *link)
{
struct bpf_tracing_link *tr_link =
container_of(link, struct bpf_tracing_link, link.link);
+ int err;
- WARN_ON_ONCE(bpf_trampoline_unlink_prog(&tr_link->link.node,
- tr_link->trampoline,
- tr_link->tgt_prog));
+ err = bpf_trampoline_unlink_prog(&tr_link->link.node,
+ tr_link->trampoline,
+ tr_link->tgt_prog);
+ WARN_ONCE(err, "bpf_trampoline_unlink_prog failed: %d\n", err);
bpf_trampoline_put(tr_link->trampoline);
@@ -5535,6 +5497,7 @@ static int bpf_link_get_info_by_fd(struct file *file,
{
struct bpf_link_info __user *uinfo = u64_to_user_ptr(attr->info.info);
struct bpf_link_info info;
+ const struct bpf_prog *prog;
u32 info_len = attr->info.info_len;
int err;
@@ -5549,8 +5512,12 @@ static int bpf_link_get_info_by_fd(struct file *file,
info.type = link->type;
info.id = link->id;
- if (link->prog)
- info.prog_id = link->prog->aux->id;
+
+ rcu_read_lock();
+ prog = READ_ONCE(link->prog);
+ if (prog)
+ info.prog_id = prog->aux->id;
+ rcu_read_unlock();
if (link->ops->fill_link_info) {
err = link->ops->fill_link_info(link, &info);
@@ -5565,7 +5532,6 @@ static int bpf_link_get_info_by_fd(struct file *file,
return 0;
}
-
static int token_get_info_by_fd(struct file *file,
struct bpf_token *token,
const union bpf_attr *attr,
@@ -6093,7 +6059,10 @@ struct bpf_link *bpf_link_get_curr_or_next(u32 *id)
again:
link = idr_get_next(&link_idr, id);
if (link) {
- link = bpf_link_inc_not_zero(link);
+ if (link->id)
+ link = bpf_link_inc_not_zero(link);
+ else
+ link = ERR_PTR(-EAGAIN);
if (IS_ERR(link)) {
(*id)++;
goto again;
@@ -6308,7 +6277,7 @@ static int prog_stream_read(union bpf_attr *attr)
return ret;
}
-#define BPF_PROG_ASSOC_STRUCT_OPS_LAST_FIELD prog_assoc_struct_ops.prog_fd
+#define BPF_PROG_ASSOC_STRUCT_OPS_LAST_FIELD prog_assoc_struct_ops.flags
static int prog_assoc_struct_ops(union bpf_attr *attr)
{
@@ -6567,7 +6536,6 @@ BPF_CALL_3(bpf_sys_bpf, int, cmd, union bpf_attr *, attr, u32, attr_size)
return __sys_bpf(cmd, KERNEL_BPFPTR(attr), attr_size, KERNEL_BPFPTR(NULL), 0);
}
-
/* To shut up -Wmissing-prototypes.
* This function is used by the kernel light skeleton
* to load bpf programs when modules are loaded or during kernel boot.
@@ -6620,10 +6588,11 @@ EXPORT_SYMBOL_NS(kern_sys_bpf, "BPF_INTERNAL");
static const struct bpf_func_proto bpf_sys_bpf_proto = {
.func = bpf_sys_bpf,
.gpl_only = false,
+ .might_sleep = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_ANYTHING,
.arg2_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg3_type = ARG_CONST_SIZE,
+ .arg3_type = ARG_MEM_SIZE,
};
const struct bpf_func_proto * __weak
@@ -6645,6 +6614,7 @@ BPF_CALL_1(bpf_sys_close, u32, fd)
static const struct bpf_func_proto bpf_sys_close_proto = {
.func = bpf_sys_close,
.gpl_only = false,
+ .might_sleep = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_ANYTHING,
};
@@ -6670,7 +6640,7 @@ static const struct bpf_func_proto bpf_kallsyms_lookup_name_proto = {
.gpl_only = false,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg2_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg2_type = ARG_MEM_SIZE_OR_ZERO,
.arg3_type = ARG_ANYTHING,
.arg4_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_UNINIT | MEM_WRITE | MEM_ALIGNED,
.arg4_size = sizeof(u64),
diff --git a/kernel/bpf/task_iter.c b/kernel/bpf/task_iter.c
index e791ae065c39..13e1aabe6f88 100644
--- a/kernel/bpf/task_iter.c
+++ b/kernel/bpf/task_iter.c
@@ -753,9 +753,9 @@ static struct bpf_iter_reg task_vma_reg_info = {
BPF_CALL_5(bpf_find_vma, struct task_struct *, task, u64, start,
bpf_callback_t, callback_fn, void *, callback_ctx, u64, flags)
{
- struct mmap_unlock_irq_work *work = NULL;
+ struct mmap_unlock_irq_work *work;
struct vm_area_struct *vma;
- bool irq_work_busy = false;
+ bool __maybe_unused mmput_needed = false;
struct mm_struct *mm;
int ret = -ENOENT;
@@ -765,14 +765,43 @@ BPF_CALL_5(bpf_find_vma, struct task_struct *, task, u64, start,
if (!task)
return -ENOENT;
- mm = task->mm;
+ if (task == current) {
+ mm = task->mm;
+ } else {
+ /*
+ * Foreign task: pin task->mm against a concurrent exit_mm().
+ * Use trylock on alloc_lock instead of get_task_mm()'s
+ * blocking task_lock() to avoid deadlocking the target task.
+ */
+ if (!IS_ENABLED(CONFIG_MMU))
+ return -EOPNOTSUPP;
+ if (irqs_disabled())
+ return -EBUSY;
+ if (!spin_trylock(&task->alloc_lock))
+ return -EBUSY;
+ mm = task->mm;
+ if (mm && !(task->flags & PF_KTHREAD)) {
+ mmget(mm);
+ mmput_needed = true;
+ } else {
+ mm = NULL;
+ }
+ spin_unlock(&task->alloc_lock);
+ }
if (!mm)
return -ENOENT;
- irq_work_busy = bpf_mmap_unlock_get_irq_work(&work);
+ work = bpf_mmap_unlock_guard_get();
+ if (IS_ERR(work)) {
+ ret = PTR_ERR(work);
+ goto out;
+ }
- if (irq_work_busy || !mmap_read_trylock(mm))
- return -EBUSY;
+ if (!mmap_read_trylock(mm)) {
+ bpf_mmap_unlock_guard_put(work);
+ ret = -EBUSY;
+ goto out;
+ }
vma = find_vma(mm, start);
@@ -782,6 +811,11 @@ BPF_CALL_5(bpf_find_vma, struct task_struct *, task, u64, start,
ret = 0;
}
bpf_mmap_unlock_mm(work, mm);
+out:
+#ifdef CONFIG_MMU
+ if (mmput_needed)
+ mmput_async(mm);
+#endif
return ret;
}
@@ -1161,6 +1195,8 @@ static void do_mmap_read_unlock(struct irq_work *entry)
work = container_of(entry, struct mmap_unlock_irq_work, irq_work);
mmap_read_unlock_non_owner(work->mm);
+ work->mm = NULL;
+ bpf_mmap_unlock_guard_put(work);
}
static int __init task_iter_init(void)
diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
index 1a721fc4bef5..bf4cb3dd444d 100644
--- a/kernel/bpf/trampoline.c
+++ b/kernel/bpf/trampoline.c
@@ -401,6 +401,7 @@ static struct bpf_trampoline *bpf_trampoline_lookup(u64 key, unsigned long ip)
head = &trampoline_ip_table[hash_64(tr->ip, TRAMPOLINE_HASH_BITS)];
hlist_add_head(&tr->hlist_ip, head);
refcount_set(&tr->refcnt, 1);
+ INIT_LIST_HEAD(&tr->images);
for (i = 0; i < BPF_TRAMP_MAX; i++)
INIT_HLIST_HEAD(&tr->progs_hlist[i]);
out:
@@ -529,21 +530,58 @@ bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_a
return tnodes;
}
+/*
+ * The arena base against which save_args() converts the arguments marked
+ * with BTF_FMODEL_ARENA_ARG. Only the struct_ops indirect trampoline
+ * converts: it dispatches to a single prog whose arena is known at
+ * generation time. Return 0 when there is nothing to convert.
+ */
+u64 bpf_tramp_arena_base(const struct btf_func_model *m,
+ struct bpf_tramp_nodes *tnodes, u32 flags)
+{
+ const struct bpf_prog *prog;
+ int i;
+
+ if (!(flags & BPF_TRAMP_F_INDIRECT) ||
+ tnodes[BPF_TRAMP_FENTRY].nr_nodes != 1)
+ return 0;
+
+ for (i = 0; i < m->nr_args; i++)
+ if (m->arg_flags[i] & BTF_FMODEL_ARENA_ARG)
+ break;
+ if (i == m->nr_args)
+ return 0;
+
+ /* Verification rejects an arena argument without an arena. */
+ prog = tnodes[BPF_TRAMP_FENTRY].nodes[0]->link->prog;
+ if (WARN_ON_ONCE(!prog->aux->arena))
+ return 0;
+
+ return bpf_arena_get_kern_vm_start(prog->aux->arena);
+}
+
static void bpf_tramp_image_free(struct bpf_tramp_image *im)
{
bpf_image_ksym_del(&im->ksym);
arch_free_bpf_trampoline(im->image, im->size);
bpf_jit_uncharge_modmem(im->size);
percpu_ref_exit(&im->pcref);
+ kfree(im->skips);
kfree_rcu(im, rcu);
}
static void __bpf_tramp_image_put_deferred(struct work_struct *work)
{
struct bpf_tramp_image *im;
+ struct bpf_trampoline *tr;
im = container_of(work, struct bpf_tramp_image, work);
+ tr = im->tr;
+ trampoline_lock(tr);
+ list_del(&im->list);
+ trampoline_unlock(tr);
bpf_tramp_image_free(im);
+ bpf_trampoline_put(tr);
}
/* callback, fexit step 3 or fentry step 2 */
@@ -571,7 +609,7 @@ static void __bpf_tramp_image_put_rcu_tasks(struct rcu_head *rcu)
struct bpf_tramp_image *im;
im = container_of(rcu, struct bpf_tramp_image, rcu);
- if (im->ip_after_call)
+ if (im->call_orig)
/* the case of fmod_ret/fexit trampoline and CONFIG_PREEMPTION=y */
percpu_ref_kill(&im->pcref);
else
@@ -591,9 +629,9 @@ static void bpf_tramp_image_put(struct bpf_tramp_image *im)
*
* The trampoline is unreachable before bpf_tramp_image_put().
*
- * First, patch the trampoline to avoid calling into fexit progs.
- * The progs will be freed even if the original function is still
- * executing or sleeping.
+ * Progs are patched out of the image when they are detached, see
+ * bpf_trampoline_skip_prog(), so they can be freed even if a task is
+ * still in the image.
* In case of CONFIG_PREEMPT=y use call_rcu_tasks() to wait on
* first few asm instructions to execute and call into
* __bpf_tramp_enter->percpu_ref_get.
@@ -607,11 +645,7 @@ static void bpf_tramp_image_put(struct bpf_tramp_image *im)
* percpu_ref_kill will be waiting for. Hence the first
* call_rcu_tasks() is not necessary.
*/
- if (im->ip_after_call) {
- int err = bpf_arch_text_poke(im->ip_after_call, BPF_MOD_NOP,
- BPF_MOD_JUMP, NULL,
- im->ip_epilogue);
- WARN_ON(err);
+ if (im->call_orig) {
if (IS_ENABLED(CONFIG_TASKS_RCU))
call_rcu_tasks(&im->rcu, __bpf_tramp_image_put_rcu_tasks);
else
@@ -628,7 +662,7 @@ static void bpf_tramp_image_put(struct bpf_tramp_image *im)
call_rcu_tasks_trace(&im->rcu, __bpf_tramp_image_put_rcu_tasks);
}
-static struct bpf_tramp_image *bpf_tramp_image_alloc(u64 key, int size)
+static struct bpf_tramp_image *bpf_tramp_image_alloc(u64 key, int size, int nr_progs)
{
struct bpf_tramp_image *im;
struct bpf_ksym *ksym;
@@ -639,6 +673,10 @@ static struct bpf_tramp_image *bpf_tramp_image_alloc(u64 key, int size)
if (!im)
goto out;
+ im->skips = kzalloc_objs(*im->skips, nr_progs);
+ if (!im->skips)
+ goto out_free_im;
+
err = bpf_jit_charge_modmem(size);
if (err)
goto out_free_im;
@@ -665,11 +703,19 @@ out_free_image:
out_uncharge:
bpf_jit_uncharge_modmem(size);
out_free_im:
+ kfree(im->skips);
kfree(im);
out:
return ERR_PTR(err);
}
+void bpf_trampoline_set_flags(struct bpf_trampoline *tr, u32 flags)
+{
+ trampoline_lock(tr);
+ tr->flags |= flags;
+ trampoline_unlock(tr);
+}
+
static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mutex,
const struct bpf_trampoline_ops *ops, void *data)
{
@@ -734,11 +780,12 @@ again:
goto out;
}
- im = bpf_tramp_image_alloc(tr->key, size);
+ im = bpf_tramp_image_alloc(tr->key, size, total);
if (IS_ERR(im)) {
err = PTR_ERR(im);
goto out;
}
+ im->call_orig = tr->flags & BPF_TRAMP_F_CALL_ORIG;
err = arch_prepare_bpf_trampoline(im, im->image, im->image + size,
&tr->func.model, tr->flags, tnodes,
@@ -769,8 +816,14 @@ again:
#endif
out_free:
- if (err)
+ if (err) {
bpf_tramp_image_free(im);
+ } else {
+ /* track the image until it is freed, for bpf_trampoline_skip_prog() */
+ refcount_inc(&tr->refcnt);
+ im->tr = tr;
+ list_add(&im->list, &tr->images);
+ }
out:
/* If any error happens, restore previous flags */
if (err)
@@ -870,6 +923,7 @@ static int bpf_trampoline_add_prog(struct bpf_trampoline *tr,
}
hlist_add_head(&node->tramp_hlist, prog_list);
+ node->link->prog->aux->tramp_linked = true;
if (kind == BPF_TRAMP_FSESSION) {
tr->progs_cnt[BPF_TRAMP_FENTRY]++;
fexit = fsession_exit(node);
@@ -883,6 +937,41 @@ static int bpf_trampoline_add_prog(struct bpf_trampoline *tr,
return 0;
}
+/*
+ * Patch the nop in front of a prog call to a jump over it. A task can be
+ * preempted anywhere in the image, so archs that need several instructions for
+ * a jump of any range patch a single near branch here instead.
+ */
+int __weak arch_bpf_trampoline_skip(void *nop, void *target)
+{
+ return bpf_arch_text_poke(nop, BPF_MOD_NOP, BPF_MOD_JUMP, NULL, target);
+}
+
+/*
+ * prog was detached and can be freed, but tasks may still be running in images
+ * that call it, sleeping in an earlier prog for example. They can be in any
+ * image that is not freed yet, not only in cur_image, so patch all of them to
+ * jump over prog.
+ */
+static void bpf_trampoline_skip_prog(struct bpf_trampoline *tr, struct bpf_prog *prog)
+{
+ struct bpf_tramp_image *im;
+ int i, err;
+
+ list_for_each_entry(im, &tr->images, list) {
+ for (i = 0; i < im->nr_skips; i++) {
+ struct bpf_tramp_skip *skip = &im->skips[i];
+
+ if (skip->prog != prog)
+ continue;
+ err = arch_bpf_trampoline_skip(skip->nop, skip->target);
+ WARN_ON_ONCE(err);
+ /* not a nop anymore, and prog's address can be reused */
+ skip->prog = NULL;
+ }
+ }
+}
+
static void bpf_trampoline_remove_prog(struct bpf_trampoline *tr,
struct bpf_tramp_node *node)
{
@@ -900,6 +989,7 @@ static void bpf_trampoline_remove_prog(struct bpf_trampoline *tr,
}
hlist_del_init(&node->tramp_hlist);
tr->progs_cnt[kind]--;
+ bpf_trampoline_skip_prog(tr, node->link->prog);
}
static int __bpf_trampoline_link_prog(struct bpf_tramp_node *node,
@@ -913,6 +1003,13 @@ static int __bpf_trampoline_link_prog(struct bpf_tramp_node *node,
int cnt = 0, i;
kind = bpf_attach_type_to_tramp(node->link->prog);
+ /*
+ * Arena ctx args are converted only by struct_ops indirect
+ * trampolines. They must never be attached to a generic trampoline.
+ */
+ if (WARN_ON_ONCE(bpf_prog_has_arena_ctx_arg(node->link->prog)))
+ return -ENOTSUPP;
+
if (tr->extension_prog)
/* cannot attach fentry/fexit if extension prog is attached.
* cannot overwrite extension prog either.
@@ -997,12 +1094,15 @@ static void bpf_shim_tramp_link_release(struct bpf_link *link)
{
struct bpf_shim_tramp_link *shim_link =
container_of(link, struct bpf_shim_tramp_link, link.link);
+ int err;
/* paired with 'shim_link->trampoline = tr' in bpf_trampoline_link_cgroup_shim */
if (!shim_link->trampoline)
return;
- WARN_ON_ONCE(bpf_trampoline_unlink_prog(&shim_link->link.node, shim_link->trampoline, NULL));
+ err = bpf_trampoline_unlink_prog(&shim_link->link.node, shim_link->trampoline, NULL);
+ WARN_ONCE(err, "bpf_trampoline_unlink_prog failed: %d\n", err);
+
bpf_trampoline_put(shim_link->trampoline);
}
@@ -1198,11 +1298,9 @@ void bpf_trampoline_put(struct bpf_trampoline *tr)
if (WARN_ON_ONCE(!hlist_empty(&tr->progs_hlist[i])))
goto out;
- /* This code will be executed even when the last bpf_tramp_image
- * is alive. All progs are detached from the trampoline and the
- * trampoline image is patched with jmp into epilogue to skip
- * fexit progs. The fentry-only trampoline will be freed via
- * multiple rcu callbacks.
+ /*
+ * All progs are detached and the last image has been freed, images
+ * hold a reference on the trampoline until then.
*/
hlist_del(&tr->hlist_key);
hlist_del(&tr->hlist_ip);
@@ -1536,6 +1634,7 @@ static int register_fentry_multi(struct bpf_trampoline *tr, struct bpf_tramp_ima
if (bpf_trampoline_use_jmp(tr->flags))
addr = ftrace_jmp_set(addr);
+ tr->func.ftrace_managed = true;
ftrace_hash_add(data->reg, data->entry, ip, addr);
tr->cur_image = im;
return 0;
@@ -1584,7 +1683,17 @@ static void bpf_trampoline_multi_attach_init(struct bpf_trampoline *tr)
static void bpf_trampoline_multi_attach_free(struct bpf_trampoline *tr)
{
- if (tr->multi_attach.old_image)
+ /*
+ * Only free old_image if it is no longer the active image.
+ * When bpf_trampoline_update() fails before modify_fentry_multi()/
+ * unregister_fentry_multi() is called, cur_image is unchanged
+ * (cur_image == old_image) and ftrace still points to it. Freeing
+ * it would cause a UAF when ftrace calls into the freed memory.
+ * On success, cur_image is either a new image or NULL, so
+ * old_image != cur_image means the image is stale.
+ */
+ if (tr->multi_attach.old_image &&
+ tr->multi_attach.old_image != tr->cur_image)
bpf_tramp_image_put(tr->multi_attach.old_image);
tr->multi_attach.old_image = NULL;
@@ -1708,19 +1817,21 @@ rollback_put:
return err;
}
-int bpf_trampoline_multi_detach(struct bpf_prog *prog, struct bpf_tracing_multi_link *link)
+void bpf_trampoline_multi_detach(struct bpf_prog *prog,
+ struct bpf_tracing_multi_link *link)
{
struct bpf_tracing_multi_data *data = &link->data;
struct bpf_tracing_multi_node *mnode;
- int i;
+ int i, err;
trampoline_lock_all();
for_each_mnode(mnode, link) {
data->entry = &mnode->entry;
bpf_trampoline_multi_attach_init(mnode->trampoline);
- WARN_ON_ONCE(__bpf_trampoline_unlink_prog(&mnode->node, mnode->trampoline,
- NULL, &trampoline_multi_ops, data));
+ err = __bpf_trampoline_unlink_prog(&mnode->node, mnode->trampoline, NULL,
+ &trampoline_multi_ops, data);
+ WARN_ONCE(err, "__bpf_trampoline_unlink_prog failed: %d\n", err);
}
if (ftrace_hash_count(data->unreg))
@@ -1737,7 +1848,6 @@ int bpf_trampoline_multi_detach(struct bpf_prog *prog, struct bpf_tracing_multi_
bpf_trampoline_put(mnode->trampoline);
clear_tracing_multi_data(data);
- return 0;
}
#undef for_each_mnode_cnt
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 2abc79dbf281..5f874979b8d7 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -22,6 +22,8 @@
#include <linux/ctype.h>
#include <linux/error-injection.h>
#include <linux/bpf_lsm.h>
+#include <linux/security.h>
+#include <linux/verification.h>
#include <linux/btf_ids.h>
#include <linux/poison.h>
#include <linux/module.h>
@@ -32,6 +34,7 @@
#include <linux/trace_events.h>
#include <linux/kallsyms.h>
+#include "diagnostics.h"
#include "disasm.h"
static const struct bpf_verifier_ops * const bpf_verifier_ops[] = {
@@ -191,6 +194,7 @@ struct bpf_verifier_stack_elem {
struct bpf_verifier_stack_elem *next;
/* length of verifier log at the time this state was pushed on stack */
u32 log_pos;
+ u64 diag_log_pos;
};
#define BPF_COMPLEXITY_LIMIT_JMP_SEQ 8192
@@ -201,9 +205,11 @@ struct bpf_verifier_stack_elem {
#define BPF_PRIV_STACK_MIN_SIZE 64
static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int parent_id);
-static int release_reference_nomark(struct bpf_verifier_state *state, int id);
+static int __release_reference_nomark(struct bpf_verifier_state *state, int id);
+static int release_reference_nomark(struct bpf_verifier_env *env, int id);
static int release_reference(struct bpf_verifier_env *env, int id);
static void invalidate_non_owning_refs(struct bpf_verifier_env *env);
+static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env);
static bool in_rbtree_lock_required_cb(struct bpf_verifier_env *env);
static bool is_tracing_prog_type(enum bpf_prog_type type);
static int ref_set_non_owning(struct bpf_verifier_env *env,
@@ -250,27 +256,6 @@ static int validate_ref_obj(struct bpf_verifier_env *env, struct ref_obj_desc *r
return 0;
}
-struct bpf_call_arg_meta {
- struct bpf_map_desc map;
- struct bpf_dynptr_desc dynptr;
- struct ref_obj_desc ref_obj;
- bool raw_mode;
- bool pkt_access;
- u8 release_regno;
- int regno;
- int access_size;
- int mem_size;
- u64 msize_max_value;
- int func_id;
- struct btf *btf;
- u32 btf_id;
- struct btf *ret_btf;
- u32 ret_btf_id;
- u32 subprogno;
- struct btf_field *kptr_field;
- s64 const_map_key;
-};
-
struct bpf_kfunc_meta {
struct btf *btf;
const struct btf_type *proto;
@@ -322,6 +307,7 @@ static const char *btf_type_name(const struct btf *btf, u32 id)
}
static DEFINE_MUTEX(bpf_verifier_lock);
+static DEFINE_MUTEX(btf_vmlinux_lock);
static DEFINE_MUTEX(bpf_percpu_ma_lock);
__printf(2, 3) static void verbose(void *private_data, const char *fmt, ...)
@@ -366,9 +352,18 @@ static bool reg_not_null(struct bpf_verifier_env *env, const struct bpf_reg_stat
if (type_may_be_null(type))
return false;
+ /*
+ * The types below guarantee a non-NULL base, an unbounded offset can
+ * still wrap base + offset to zero.
+ */
+ if (reg_smin(reg) <= -BPF_MAX_VAR_OFF || reg_smax(reg) >= BPF_MAX_VAR_OFF)
+ return false;
+
type = base_type(type);
return type == PTR_TO_SOCKET ||
type == PTR_TO_TCP_SOCK ||
+ type == PTR_TO_XDP_SOCK ||
+ type == PTR_TO_BUF ||
type == PTR_TO_MAP_VALUE ||
type == PTR_TO_MAP_KEY ||
type == PTR_TO_SOCK_COMMON ||
@@ -422,7 +417,7 @@ static bool subprog_returns_void(struct bpf_verifier_env *env, int subprog)
return btf_type_is_void(type);
}
-static const char *subprog_name(const struct bpf_verifier_env *env, int subprog)
+const char *bpf_subprog_name(const struct bpf_verifier_env *env, int subprog)
{
struct bpf_func_info *info;
@@ -572,7 +567,7 @@ static int stack_slot_obj_get_spi(struct bpf_verifier_env *env, struct bpf_reg_s
}
off = reg->var_off.value;
- if (off % BPF_REG_SIZE) {
+ if (off >= 0 || off % BPF_REG_SIZE) {
verbose(env, "cannot pass in %s at an offset=%d\n", obj_kind, off);
return -EINVAL;
}
@@ -652,7 +647,6 @@ static void __mark_dynptr_reg(struct bpf_reg_state *reg,
enum bpf_dynptr_type type,
bool first_slot, int id, int parent_id);
-
static void mark_dynptr_stack_regs(struct bpf_verifier_env *env,
struct bpf_reg_state *sreg1,
struct bpf_reg_state *sreg2,
@@ -831,6 +825,10 @@ static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,
if (dynptr_type_referenced(state->stack[spi].spilled_ptr.dynptr.type) &&
dynptr_ref_cnt(env, state->stack[spi].spilled_ptr.parent_id) <= 1) {
verbose(env, "cannot overwrite referenced dynptr\n");
+ bpf_diag_res(
+ env, env->insn_idx, "referenced dynptr overwrite",
+ "This stack slot contains a dynptr that owns or protects a referenced resource. Overwriting the last dynptr for that resource would lose the verifier-tracked release path.",
+ "Release or clone the dynptr so another live dynptr still tracks the referenced resource before overwriting this stack slot.");
return -EINVAL;
}
@@ -901,36 +899,39 @@ static bool is_dynptr_reg_valid_init(struct bpf_verifier_env *env, struct bpf_re
return true;
}
-static bool is_dynptr_type_expected(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
- enum bpf_arg_type arg_type)
+static enum bpf_dynptr_type dynptr_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *reg)
{
- struct bpf_func_state *state = bpf_func(env, reg);
- enum bpf_dynptr_type dynptr_type;
+ struct bpf_func_state *state;
int spi;
+ if (reg->type == CONST_PTR_TO_DYNPTR)
+ return reg->dynptr.type;
+
+ spi = dynptr_get_spi(env, reg);
+ if (spi < 0)
+ return BPF_DYNPTR_TYPE_INVALID;
+ state = bpf_func(env, reg);
+ return state->stack[spi].spilled_ptr.dynptr.type;
+}
+
+static bool is_dynptr_type_expected(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
+ enum bpf_arg_type arg_type)
+{
/* ARG_PTR_TO_DYNPTR takes any type of dynptr */
if (arg_type == ARG_PTR_TO_DYNPTR)
return true;
- dynptr_type = arg_to_dynptr_type(arg_type);
- if (reg->type == CONST_PTR_TO_DYNPTR) {
- return reg->dynptr.type == dynptr_type;
- } else {
- spi = dynptr_get_spi(env, reg);
- if (spi < 0)
- return false;
- return state->stack[spi].spilled_ptr.dynptr.type == dynptr_type;
- }
+ return dynptr_reg_type(env, reg) == arg_to_dynptr_type(arg_type);
}
static void __mark_reg_known_zero(struct bpf_reg_state *reg);
static bool in_rcu_cs(struct bpf_verifier_env *env);
-static bool is_kfunc_rcu_protected(struct bpf_kfunc_call_arg_meta *meta);
+static bool is_kfunc_rcu_protected(struct bpf_call_arg_meta *meta);
static int mark_stack_slots_iter(struct bpf_verifier_env *env,
- struct bpf_kfunc_call_arg_meta *meta,
+ struct bpf_call_arg_meta *meta,
struct bpf_reg_state *reg, int insn_idx,
struct btf *btf, u32 btf_id, int nr_slots)
{
@@ -1060,10 +1061,10 @@ static int is_iter_reg_valid_init(struct bpf_verifier_env *env, struct bpf_reg_s
}
static int acquire_irq_state(struct bpf_verifier_env *env, int insn_idx);
-static int release_irq_state(struct bpf_verifier_state *state, int id);
+static int release_irq_state(struct bpf_verifier_env *env, int id);
static int mark_stack_slot_irq_flag(struct bpf_verifier_env *env,
- struct bpf_kfunc_call_arg_meta *meta,
+ struct bpf_call_arg_meta *meta,
struct bpf_reg_state *reg, int insn_idx,
int kfunc_class)
{
@@ -1113,13 +1114,23 @@ static int unmark_stack_slot_irq_flag(struct bpf_verifier_env *env, struct bpf_r
if (st->irq.kfunc_class != kfunc_class) {
const char *flag_kfunc = st->irq.kfunc_class == IRQ_NATIVE_KFUNC ? "native" : "lock";
const char *used_kfunc = kfunc_class == IRQ_NATIVE_KFUNC ? "native" : "lock";
+ const char *reason;
verbose(env, "irq flag acquired by %s kfuncs cannot be restored with %s kfuncs\n",
flag_kfunc, used_kfunc);
+ reason = bpf_diag_fmt(env,
+ "This IRQ flag was saved by %s IRQ kfuncs, but the restore call "
+ "belongs to the %s IRQ kfunc family. Save and restore operations "
+ "must use the same family.",
+ flag_kfunc, used_kfunc);
+ bpf_diag_irq(env, env->insn_idx, "IRQ flag restore mismatch", reason,
+ "Restore the flag with the matching IRQ restore kfunc for the save "
+ "operation that created it.",
+ bpf_diag_irq_depth(env->cur_state));
return -EINVAL;
}
- err = release_irq_state(env->cur_state, st->id);
+ err = release_irq_state(env, st->id);
WARN_ON_ONCE(err && err != -EACCES);
if (err) {
int insn_idx = 0;
@@ -1133,6 +1144,11 @@ static int unmark_stack_slot_irq_flag(struct bpf_verifier_env *env, struct bpf_r
verbose(env, "cannot restore irq state out of order, expected id=%d acquired at insn_idx=%d\n",
env->cur_state->active_irq_id, insn_idx);
+ bpf_diag_irq(env, env->insn_idx, "IRQ flag restore out of order",
+ "IRQ-disabled regions must be restored in last-in, first-out order, "
+ "but this restore does not match the currently active IRQ flag.",
+ "Restore nested IRQ flags in the reverse order they were saved.",
+ bpf_diag_irq_depth(env->cur_state));
return err;
}
@@ -1434,6 +1450,7 @@ static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int par
s->type = REF_TYPE_PTR;
s->id = ++env->id_gen;
s->parent_id = parent_id;
+ bpf_diag_record_ref_acquire(env, insn_idx, s->id);
return s->id;
}
@@ -1453,6 +1470,8 @@ static int acquire_lock_state(struct bpf_verifier_env *env, int insn_idx, enum r
state->active_locks++;
state->active_lock_id = id;
state->active_lock_ptr = ptr;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_LOCK, true,
+ state->active_locks);
return 0;
}
@@ -1468,6 +1487,8 @@ static int acquire_irq_state(struct bpf_verifier_env *env, int insn_idx)
s->id = ++env->id_gen;
state->active_irq_id = s->id;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_IRQ, true,
+ bpf_diag_irq_depth(state));
return s->id;
}
@@ -1509,8 +1530,9 @@ static bool reg_is_referenced(struct bpf_verifier_env *env, const struct bpf_reg
return find_reference_state(env->cur_state, reg->id);
}
-static int release_lock_state(struct bpf_verifier_state *state, int type, int id, void *ptr)
+static int release_lock_state(struct bpf_verifier_env *env, int type, int id, void *ptr)
{
+ struct bpf_verifier_state *state = env->cur_state;
void *prev_ptr = NULL;
u32 prev_id = 0;
int i;
@@ -1523,6 +1545,8 @@ static int release_lock_state(struct bpf_verifier_state *state, int type, int id
/* Reassign active lock (id, ptr). */
state->active_lock_id = prev_id;
state->active_lock_ptr = prev_ptr;
+ bpf_diag_record_context(env, env->insn_idx, BPF_DIAG_CONTEXT_LOCK,
+ false, state->active_locks);
return 0;
}
if (state->refs[i].type & REF_TYPE_LOCK_MASK) {
@@ -1533,8 +1557,9 @@ static int release_lock_state(struct bpf_verifier_state *state, int type, int id
return -EINVAL;
}
-static int release_irq_state(struct bpf_verifier_state *state, int id)
+static int release_irq_state(struct bpf_verifier_env *env, int id)
{
+ struct bpf_verifier_state *state = env->cur_state;
u32 prev_id = 0;
int i;
@@ -1547,6 +1572,8 @@ static int release_irq_state(struct bpf_verifier_state *state, int id)
if (state->refs[i].id == id) {
release_reference_state(state, i);
state->active_irq_id = prev_id;
+ bpf_diag_record_context(env, env->insn_idx, BPF_DIAG_CONTEXT_IRQ,
+ false, bpf_diag_irq_depth(state));
return 0;
} else {
prev_id = state->refs[i].id;
@@ -1610,6 +1637,8 @@ static int copy_func_state(struct bpf_func_state *dst,
const struct bpf_func_state *src)
{
memcpy(dst, src, offsetof(struct bpf_func_state, stack));
+ /* Instruction accounting is path-local, not part of verifier state. */
+ dst->insns_subtotal = 0;
return copy_stack_state(dst, src);
}
@@ -1689,7 +1718,6 @@ static bool same_callsites(struct bpf_verifier_state *a, struct bpf_verifier_sta
return true;
}
-
void bpf_free_backedges(struct bpf_scc_visit *visit)
{
struct bpf_scc_backedge *backedge, *next;
@@ -1716,6 +1744,7 @@ static int pop_stack(struct bpf_verifier_env *env, int *prev_insn_idx,
err = bpf_copy_verifier_state(cur, &head->st);
if (err)
return err;
+ bpf_diag_event_log_restore(env, head->diag_log_pos);
}
if (pop_log)
bpf_vlog_reset(&env->log, head->log_pos);
@@ -1759,6 +1788,7 @@ static struct bpf_verifier_state *push_stack(struct bpf_verifier_env *env,
elem->prev_insn_idx = prev_insn_idx;
elem->next = env->head;
elem->log_pos = env->log.end_pos;
+ elem->diag_log_pos = bpf_diag_event_log_save(env);
env->head = elem;
env->stack_size++;
err = bpf_copy_verifier_state(&elem->st, cur);
@@ -1805,6 +1835,17 @@ static const int caller_saved[CALLER_SAVED_REGS] = {
BPF_REG_0, BPF_REG_1, BPF_REG_2, BPF_REG_3, BPF_REG_4, BPF_REG_5
};
+static void bpf_diag_record_caller_saved(struct bpf_verifier_env *env,
+ struct bpf_reg_state *regs)
+{
+ int i;
+
+ for (i = 1; i < CALLER_SAVED_REGS; i++) {
+ bpf_diag_record_scrub(env, &regs[caller_saved[i]],
+ BPF_DIAG_MOD_CALLER_SAVED);
+ }
+}
+
/* This helper doesn't clear reg->id */
static void ___mark_reg_known(struct bpf_reg_state *reg, u64 imm)
{
@@ -1823,6 +1864,7 @@ static void __mark_reg_known(struct bpf_reg_state *reg, u64 imm)
offsetof(struct bpf_reg_state, var_off) - sizeof(reg->type));
reg->id = 0;
reg->parent_id = 0;
+ reg->map_uid = 0;
___mark_reg_known(reg, imm);
}
@@ -1872,32 +1914,35 @@ static void __mark_dynptr_reg(struct bpf_reg_state *reg, enum bpf_dynptr_type ty
reg->dynptr.first_slot = first_slot;
}
-static void mark_ptr_not_null_reg(struct bpf_reg_state *reg)
+/*
+ * Refine the return type of the bpf_map_lookup_elem() for special map types:
+ * map-in-map, xskmap, sockmap and sockhash.
+ */
+static void refine_map_lookup_value(struct bpf_reg_state *reg)
{
- if (base_type(reg->type) == PTR_TO_MAP_VALUE) {
- const struct bpf_map *map = reg->map_ptr;
+ enum bpf_type_flag maybe_null = reg->type & PTR_MAYBE_NULL;
+ const struct bpf_map *map = reg->map_ptr;
- if (map->inner_map_meta) {
- reg->type = CONST_PTR_TO_MAP;
- reg->map_ptr = map->inner_map_meta;
- /* transfer reg's id which is unique for every map_lookup_elem
- * as UID of the inner map.
- */
- if (btf_record_has_field(map->inner_map_meta->record,
- BPF_TIMER | BPF_WORKQUEUE | BPF_TASK_WORK)) {
- reg->map_uid = reg->id;
- }
- } else if (map->map_type == BPF_MAP_TYPE_XSKMAP) {
- reg->type = PTR_TO_XDP_SOCK;
- } else if (map->map_type == BPF_MAP_TYPE_SOCKMAP ||
- map->map_type == BPF_MAP_TYPE_SOCKHASH) {
- reg->type = PTR_TO_SOCKET;
- } else {
- reg->type = PTR_TO_MAP_VALUE;
- }
- return;
+ if (map->inner_map_meta) {
+ reg->type = CONST_PTR_TO_MAP | maybe_null;
+ reg->map_ptr = map->inner_map_meta;
+ /*
+ * transfer reg's id which is unique for every map_lookup_elem
+ * as UID of the inner map.
+ */
+ reg->map_uid = reg->id;
+ } else if (map->map_type == BPF_MAP_TYPE_XSKMAP) {
+ reg->type = PTR_TO_XDP_SOCK | maybe_null;
+ reg->map_uid = 0;
+ } else if (map->map_type == BPF_MAP_TYPE_SOCKMAP ||
+ map->map_type == BPF_MAP_TYPE_SOCKHASH) {
+ reg->type = PTR_TO_SOCKET | maybe_null;
+ reg->map_uid = 0;
}
+}
+static void mark_ptr_not_null_reg(struct bpf_reg_state *reg)
+{
reg->type &= ~PTR_MAYBE_NULL;
}
@@ -2147,12 +2192,9 @@ out:
/* Mark a register as having a completely unknown (scalar) value. */
void bpf_mark_reg_unknown_imprecise(struct bpf_reg_state *reg)
{
- s32 subreg_def = reg->subreg_def;
-
memset(reg, 0, sizeof(*reg));
reg->type = SCALAR_VALUE;
reg->var_off = tnum_unknown;
- reg->subreg_def = subreg_def;
__mark_reg_unbounded(reg);
}
@@ -2228,7 +2270,6 @@ static int mark_btf_ld_reg(struct bpf_verifier_env *env,
}
}
-#define DEF_NOT_SUBREG (0)
static void init_reg_state(struct bpf_verifier_env *env,
struct bpf_func_state *state)
{
@@ -2237,7 +2278,6 @@ static void init_reg_state(struct bpf_verifier_env *env,
for (i = 0; i < MAX_BPF_REG; i++) {
bpf_mark_reg_not_init(env, &regs[i]);
- regs[i].subreg_def = DEF_NOT_SUBREG;
}
/* frame pointer */
@@ -2261,6 +2301,7 @@ static void init_func_state(struct bpf_verifier_env *env,
{
state->callsite = callsite;
state->frameno = frameno;
+ bpf_diag_init_frame(env, state);
state->subprogno = subprogno;
state->callback_ret_range = retval_range(0, 0);
init_reg_state(env, state);
@@ -2283,6 +2324,7 @@ static struct bpf_verifier_state *push_async_cb(struct bpf_verifier_env *env,
elem->prev_insn_idx = prev_insn_idx;
elem->next = env->head;
elem->log_pos = env->log.end_pos;
+ elem->diag_log_pos = bpf_diag_event_log_save(env);
env->head = elem;
env->stack_size++;
if (env->stack_size > BPF_COMPLEXITY_LIMIT_JMP_SEQ) {
@@ -2309,7 +2351,6 @@ static struct bpf_verifier_state *push_async_cb(struct bpf_verifier_env *env,
return &elem->st;
}
-
static int cmp_subprogs(const void *a, const void *b)
{
return ((struct bpf_subprog_info *)a)->start -
@@ -2490,6 +2531,83 @@ int bpf_get_kfunc_addr(const struct bpf_prog *prog, u32 func_id,
return 0;
}
+#define BPF_FD_SLOT_BTF 1UL
+
+static void fd_slot_set_map(struct bpf_fd_array *slot, struct bpf_map *map)
+{
+ slot->val = (unsigned long)map;
+}
+
+static void fd_slot_set_btf(struct bpf_fd_array *slot, struct btf *btf)
+{
+ slot->val = (unsigned long)btf | BPF_FD_SLOT_BTF;
+}
+
+static struct bpf_map *fd_slot_map(struct bpf_fd_array slot)
+{
+ if (slot.val & BPF_FD_SLOT_BTF)
+ return NULL;
+ return (struct bpf_map *)slot.val;
+}
+
+static struct btf *fd_slot_btf(struct bpf_fd_array slot)
+{
+ if (!(slot.val & BPF_FD_SLOT_BTF))
+ return NULL;
+ return (struct btf *)(slot.val & ~BPF_FD_SLOT_BTF);
+}
+
+static struct btf *
+fd_array_get_btf_continuous(struct bpf_verifier_env *env, u32 idx)
+{
+ struct btf *btf;
+
+ if (idx >= env->fd_array_cnt) {
+ verbose(env, "kfunc fd_idx %u out of bounds, fd_array_cnt %u\n",
+ idx, env->fd_array_cnt);
+ return ERR_PTR(-EINVAL);
+ }
+ btf = fd_slot_btf(env->fd_array[idx]);
+ if (!btf) {
+ verbose(env, "kfunc fd_idx %u is not a module BTF\n", idx);
+ return ERR_PTR(-EINVAL);
+ }
+ btf_get(btf);
+ return btf;
+}
+
+static struct btf *
+fd_array_get_btf_sparse(struct bpf_verifier_env *env, u32 idx)
+{
+ struct btf *btf;
+ int btf_fd;
+
+ if (copy_from_bpfptr_offset(&btf_fd, env->fd_array_raw,
+ (size_t)idx * sizeof(btf_fd), sizeof(btf_fd)))
+ return ERR_PTR(-EFAULT);
+ btf = btf_get_by_fd(btf_fd);
+ if (IS_ERR(btf)) {
+ verbose(env, "invalid module BTF fd specified\n");
+ return btf;
+ }
+ return btf;
+}
+
+static struct btf *fd_array_get_btf(struct bpf_verifier_env *env, u32 idx)
+{
+ if (env->signature) {
+ verbose(env, "signed program cannot bind any BTF\n");
+ return ERR_PTR(-EACCES);
+ }
+ if (env->fd_array)
+ return fd_array_get_btf_continuous(env, idx);
+ if (!bpfptr_is_null(env->fd_array_raw))
+ return fd_array_get_btf_sparse(env, idx);
+
+ verbose(env, "kfunc offset > 0 without fd_array is invalid\n");
+ return ERR_PTR(-EPROTO);
+}
+
static struct btf *__find_kfunc_desc_btf(struct bpf_verifier_env *env,
s16 offset)
{
@@ -2498,7 +2616,6 @@ static struct btf *__find_kfunc_desc_btf(struct bpf_verifier_env *env,
struct bpf_kfunc_btf *b;
struct module *mod;
struct btf *btf;
- int btf_fd;
tab = env->prog->aux->kfunc_btf_tab;
b = bsearch(&kf_btf, tab->descs, tab->nr_descs,
@@ -2509,22 +2626,9 @@ static struct btf *__find_kfunc_desc_btf(struct bpf_verifier_env *env,
return ERR_PTR(-E2BIG);
}
- if (bpfptr_is_null(env->fd_array)) {
- verbose(env, "kfunc offset > 0 without fd_array is invalid\n");
- return ERR_PTR(-EPROTO);
- }
-
- if (copy_from_bpfptr_offset(&btf_fd, env->fd_array,
- offset * sizeof(btf_fd),
- sizeof(btf_fd)))
- return ERR_PTR(-EFAULT);
-
- btf = btf_get_by_fd(btf_fd);
- if (IS_ERR(btf)) {
- verbose(env, "invalid module BTF fd specified\n");
+ btf = fd_array_get_btf(env, offset);
+ if (IS_ERR(btf))
return btf;
- }
-
if (!btf_is_module(btf)) {
verbose(env, "BTF fd for kfunc is not a module BTF\n");
btf_put(btf);
@@ -2582,26 +2686,47 @@ static struct btf *find_kfunc_desc_btf(struct bpf_verifier_env *env, s16 offset)
return btf_vmlinux ?: ERR_PTR(-ENOENT);
}
+static struct btf *find_kfunc_desc_btf_cached(struct bpf_verifier_env *env, s16 offset)
+{
+ struct bpf_kfunc_btf kf_btf = { .offset = offset };
+ struct bpf_kfunc_btf_tab *tab;
+ struct bpf_kfunc_btf *b;
+
+ if (!offset)
+ return btf_vmlinux ?: ERR_PTR(-ENOENT);
+ if (offset < 0)
+ return ERR_PTR(-EINVAL);
+
+ tab = env->prog->aux->kfunc_btf_tab;
+ if (!tab)
+ return ERR_PTR(-ENOENT);
+
+ b = bsearch(&kf_btf, tab->descs, tab->nr_descs,
+ sizeof(tab->descs[0]), kfunc_btf_cmp_by_off);
+ return b ? b->btf : ERR_PTR(-ENOENT);
+}
+
#define KF_IMPL_SUFFIX "_impl"
-static const struct btf_type *find_kfunc_impl_proto(struct bpf_verifier_env *env,
+static const struct btf_type *find_kfunc_impl_proto(struct bpf_verifier_log *log,
struct btf *btf,
const char *func_name)
{
- char *buf = env->tmp_str_buf;
const struct btf_type *func;
+ char buf[KSYM_NAME_LEN];
s32 impl_id;
int len;
- len = snprintf(buf, TMP_STR_BUF_LEN, "%s%s", func_name, KF_IMPL_SUFFIX);
- if (len < 0 || len >= TMP_STR_BUF_LEN) {
- verbose(env, "function name %s%s is too long\n", func_name, KF_IMPL_SUFFIX);
+ len = snprintf(buf, sizeof(buf), "%s%s", func_name, KF_IMPL_SUFFIX);
+ if (len < 0 || len >= sizeof(buf)) {
+ bpf_log(log, "function name %s%s is too long\n",
+ func_name, KF_IMPL_SUFFIX);
return NULL;
}
impl_id = btf_find_by_name_kind(btf, buf, BTF_KIND_FUNC);
if (impl_id <= 0) {
- verbose(env, "cannot find function %s in BTF\n", buf);
+ bpf_log(log, "cannot find function %s in BTF\n", buf);
return NULL;
}
@@ -2653,7 +2778,7 @@ static int fetch_kfunc_meta(struct bpf_verifier_env *env,
* can be found through the counterpart _impl kfunc.
*/
if (kfunc_flags && (*kfunc_flags & KF_IMPLICIT_ARGS))
- func_proto = find_kfunc_impl_proto(env, btf, func_name);
+ func_proto = find_kfunc_impl_proto(&env->log, btf, func_name);
else
func_proto = btf_type_by_id(btf, func->type);
@@ -2673,8 +2798,12 @@ static int fetch_kfunc_meta(struct bpf_verifier_env *env,
return 0;
}
+static int gen_kfunc_arg_proto(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
+ struct bpf_func_proto *proto);
+
int bpf_add_kfunc_call(struct bpf_verifier_env *env, u32 func_id, u16 offset)
{
+ struct bpf_call_arg_meta meta;
struct bpf_kfunc_btf_tab *btf_tab;
struct btf_func_model func_model;
struct bpf_kfunc_desc_tab *tab;
@@ -2714,6 +2843,8 @@ int bpf_add_kfunc_call(struct bpf_verifier_env *env, u32 func_id, u16 offset)
prog_aux->kfunc_tab = tab;
}
+ env->prog->jit_required = 1;
+
/* func_id == 0 is always invalid, but instead of returning an error, be
* conservative and wait until the code elimination pass before returning
* error, so that invalid calls that get pruned out can be in BPF programs
@@ -2758,26 +2889,41 @@ int bpf_add_kfunc_call(struct bpf_verifier_env *env, u32 func_id, u16 offset)
if (err)
return err;
- desc = &tab->descs[tab->nr_descs++];
+ memset(&meta, 0, sizeof(meta));
+ meta.btf = kfunc.btf;
+ meta.func_id = kfunc.id;
+ meta.func_proto = kfunc.proto;
+ meta.func_name = kfunc.name;
+ meta.kfunc_flags = kfunc.flags ? *kfunc.flags : 0;
+
+ tab = krealloc(tab, struct_size(tab, descs, tab->nr_descs + 1), GFP_KERNEL_ACCOUNT);
+ if (!tab)
+ return -ENOMEM;
+ prog_aux->kfunc_tab = tab;
+
+ desc = &tab->descs[tab->nr_descs];
+ memset(desc, 0, sizeof(*desc));
+
+ err = gen_kfunc_arg_proto(env, &meta, &desc->proto);
+ if (err)
+ return err;
+
desc->func_id = func_id;
desc->offset = offset;
desc->addr = addr;
desc->func_model = func_model;
+ tab->nr_descs++;
sort(tab->descs, tab->nr_descs, sizeof(tab->descs[0]),
kfunc_desc_cmp_by_id_off, NULL);
return 0;
}
-bool bpf_prog_has_kfunc_call(const struct bpf_prog *prog)
-{
- return !!prog->aux->kfunc_tab;
-}
-
-static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
+static int add_subprogs(struct bpf_verifier_env *env)
{
struct bpf_subprog_info *subprog = env->subprog_info;
int i, ret, insn_cnt = env->prog->len, ex_cb_insn;
struct bpf_insn *insn = env->prog->insnsi;
+ const char *operation, *suggestion;
/* Add entry function. */
ret = add_subprog(env, 0);
@@ -2785,20 +2931,26 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
return ret;
for (i = 0; i < insn_cnt; i++, insn++) {
- if (!bpf_pseudo_func(insn) && !bpf_pseudo_call(insn) &&
- !bpf_pseudo_kfunc_call(insn))
+ if (!bpf_pseudo_func(insn) && !bpf_pseudo_call(insn))
continue;
if (!env->bpf_capable) {
+ if (bpf_pseudo_func(insn)) {
+ operation = "BPF function reference";
+ suggestion = "Load this program with the required capability, or avoid BPF function references in unprivileged programs.";
+ } else {
+ operation = "BPF-to-BPF function call";
+ suggestion = "Load this program with the required capability, or avoid BPF-to-BPF function calls in unprivileged programs.";
+ }
verbose(env, "loading/calling other bpf or kernel functions are allowed for CAP_BPF and CAP_SYS_ADMIN\n");
+ bpf_diag_policy(
+ env, i, operation,
+ "loading or calling other BPF functions requires CAP_BPF or CAP_SYS_ADMIN",
+ suggestion);
return -EPERM;
}
- if (bpf_pseudo_func(insn) || bpf_pseudo_call(insn))
- ret = add_subprog(env, i + insn->imm + 1);
- else
- ret = bpf_add_kfunc_call(env, insn->imm, insn->off);
-
+ ret = add_subprog(env, i + insn->imm + 1);
if (ret < 0)
return ret;
}
@@ -2836,6 +2988,32 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
return 0;
}
+static int add_kfuncs(struct bpf_verifier_env *env)
+{
+ struct bpf_insn *insn = env->prog->insnsi;
+ int i, ret, insn_cnt = env->prog->len;
+
+ for (i = 0; i < insn_cnt; i++, insn++) {
+ if (!bpf_pseudo_kfunc_call(insn))
+ continue;
+
+ if (!env->bpf_capable) {
+ verbose(env, "loading/calling other bpf or kernel functions are allowed for CAP_BPF and CAP_SYS_ADMIN\n");
+ bpf_diag_policy(
+ env, i, "kernel function call",
+ "calling kernel functions requires CAP_BPF or CAP_SYS_ADMIN",
+ "Load this program with the required capability, or avoid kernel function calls in unprivileged programs.");
+ return -EPERM;
+ }
+
+ ret = bpf_add_kfunc_call(env, insn->imm, insn->off);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
static int check_subprogs(struct bpf_verifier_env *env)
{
int i, subprog_start, subprog_end, off, cur_subprog = 0;
@@ -2866,9 +3044,17 @@ static int check_subprogs(struct bpf_verifier_env *env)
subprog[cur_subprog].exit_idx = i;
goto next;
}
+ if (insn_is_gotox(&insn[i]))
+ goto next;
off = i + bpf_jmp_offset(&insn[i]) + 1;
if (off < subprog_start || off >= subprog_end) {
verbose(env, "jump out of range from insn %d to %d\n", i, off);
+ bpf_diag_program_structure(
+ env, i, "jump out of range",
+ "Keep branch targets within the same subprogram, or use an explicit subprogram call.",
+ "Instruction %d jumps to instruction %d, but subprogram %d only contains instructions %d through %d. "
+ "A branch target must stay inside the same subprogram.",
+ i, off, cur_subprog, subprog_start, subprog_end - 1);
return -EINVAL;
}
next:
@@ -2879,8 +3065,14 @@ next:
*/
if (code != (BPF_JMP | BPF_EXIT) &&
code != (BPF_JMP32 | BPF_JA) &&
- code != (BPF_JMP | BPF_JA)) {
+ code != (BPF_JMP | BPF_JA) &&
+ !insn_is_gotox(&insn[i])) {
verbose(env, "last insn is not an exit or jmp\n");
+ bpf_diag_program_structure(
+ env, i, "subprogram can fall through",
+ "End each subprogram with an exit or an explicit jump that keeps control flow inside the subprogram.",
+ "Subprogram %d reaches its last instruction %d without an exit or jump, so control could continue into the next subprogram.",
+ cur_subprog, i);
return -EINVAL;
}
subprog_start = subprog_end;
@@ -2951,8 +3143,13 @@ static int sort_subprogs_topo(struct bpf_verifier_env *env)
if (bpf_pseudo_func(&insn[idx]))
continue;
verbose(env, "recursive call from %s() to %s()\n",
- subprog_name(env, cur),
- subprog_name(env, callee));
+ bpf_subprog_name(env, cur),
+ bpf_subprog_name(env, callee));
+ bpf_diag_program_structure(
+ env, idx, "recursive subprogram call",
+ "Rewrite the recursion as an explicit bounded loop, or split the logic so subprogram calls do not form a cycle.",
+ "This bpf2bpf call would make the subprogram call graph recursive. "
+ "The verifier requires a finite, acyclic call graph so it can bound stack depth and analysis.");
ret = -EINVAL;
goto out;
}
@@ -2973,7 +3170,7 @@ static int sort_subprogs_topo(struct bpf_verifier_env *env)
if (env->log.level & BPF_LOG_LEVEL2)
for (i = 0; i < cnt; i++)
verbose(env, "topo_order[%d] = %s\n",
- i, subprog_name(env, env->subprog_topo_order[i]));
+ i, bpf_subprog_name(env, env->subprog_topo_order[i]));
out:
kvfree(dfs_stack);
kvfree(color);
@@ -2989,133 +3186,25 @@ static void mark_stack_slots_scratched(struct bpf_verifier_env *env,
mark_stack_slot_scratched(env, spi - i);
}
-/* This function is supposed to be used by the following 32-bit optimization
- * code only. It returns TRUE if the source or destination register operates
- * on 64-bit, otherwise return FALSE.
- */
-bool bpf_is_reg64(struct bpf_insn *insn,
- u32 regno, struct bpf_reg_state *reg, enum bpf_reg_arg_type t)
-{
- u8 code, class, op;
-
- code = insn->code;
- class = BPF_CLASS(code);
- op = BPF_OP(code);
- if (class == BPF_JMP) {
- /* BPF_EXIT for "main" will reach here. Return TRUE
- * conservatively.
- */
- if (op == BPF_EXIT)
- return true;
- if (op == BPF_CALL) {
- /* BPF to BPF call will reach here because of marking
- * caller saved clobber with DST_OP_NO_MARK for which we
- * don't care the register def because they are anyway
- * marked as NOT_INIT already.
- */
- if (insn->src_reg == BPF_PSEUDO_CALL)
- return false;
- /* Helper call will reach here because of arg type
- * check, conservatively return TRUE.
- */
- if (t == SRC_OP)
- return true;
-
- return false;
- }
- }
-
- if (class == BPF_ALU64 && op == BPF_END && (insn->imm == 16 || insn->imm == 32))
- return false;
-
- if (class == BPF_ALU64 || class == BPF_JMP ||
- (class == BPF_ALU && op == BPF_END && insn->imm == 64))
- return true;
-
- if (class == BPF_ALU || class == BPF_JMP32)
- return false;
-
- if (class == BPF_LDX) {
- if (t != SRC_OP)
- return BPF_SIZE(code) == BPF_DW || BPF_MODE(code) == BPF_MEMSX;
- /* LDX source must be ptr. */
- return true;
- }
-
- if (class == BPF_STX) {
- /* BPF_STX (including atomic variants) has one or more source
- * operands, one of which is a ptr. Check whether the caller is
- * asking about it.
- */
- if (t == SRC_OP && reg->type != SCALAR_VALUE)
- return true;
- return BPF_SIZE(code) == BPF_DW;
- }
-
- if (class == BPF_LD) {
- u8 mode = BPF_MODE(code);
-
- /* LD_IMM64 */
- if (mode == BPF_IMM)
- return true;
-
- /* Both LD_IND and LD_ABS return 32-bit data. */
- if (t != SRC_OP)
- return false;
-
- /* Implicit ctx ptr. */
- if (regno == BPF_REG_6)
- return true;
-
- /* Explicit source could be any width. */
- return true;
- }
-
- if (class == BPF_ST)
- /* The only source register for BPF_ST is a ptr. */
- return true;
-
- /* Conservatively return true at default. */
- return true;
-}
-
-static void mark_insn_zext(struct bpf_verifier_env *env,
- struct bpf_reg_state *reg)
-{
- s32 def_idx = reg->subreg_def;
-
- if (def_idx == DEF_NOT_SUBREG)
- return;
-
- env->insn_aux_data[def_idx - 1].zext_dst = true;
- /* The dst will be zero extended, so won't be sub-register anymore. */
- reg->subreg_def = DEF_NOT_SUBREG;
-}
-
static int __check_reg_arg(struct bpf_verifier_env *env, struct bpf_reg_state *regs, u32 regno,
enum bpf_reg_arg_type t)
{
- struct bpf_insn *insn = env->prog->insnsi + env->insn_idx;
struct bpf_reg_state *reg;
- bool rw64;
mark_reg_scratched(env, regno);
reg = &regs[regno];
- rw64 = bpf_is_reg64(insn, regno, reg, t);
if (t == SRC_OP) {
/* check whether register used as source operand can be read */
if (reg->type == NOT_INIT) {
verbose(env, "R%d !read_ok\n", regno);
+ bpf_diag_unreadable_reg(env, env->insn_idx, regno);
return -EACCES;
}
/* We don't need to worry about FP liveness because it's read-only */
if (regno == BPF_REG_FP)
return 0;
- if (rw64)
- mark_insn_zext(env, reg);
-
return 0;
} else {
/* check whether register used as dest operand can be written to */
@@ -3123,7 +3212,6 @@ static int __check_reg_arg(struct bpf_verifier_env *env, struct bpf_reg_state *r
verbose(env, "frame pointer is read only\n");
return -EACCES;
}
- reg->subreg_def = rw64 ? DEF_NOT_SUBREG : env->insn_idx + 1;
if (t == DST_OP)
mark_reg_unknown(env, regs, regno);
}
@@ -3227,7 +3315,7 @@ static void linked_regs_unpack(u64 val, struct linked_regs *s)
}
}
-static const char *disasm_kfunc_name(void *data, const struct bpf_insn *insn)
+const char *bpf_disasm_kfunc_name(void *data, const struct bpf_insn *insn)
{
const struct btf_type *func;
struct btf *desc_btf;
@@ -3235,18 +3323,20 @@ static const char *disasm_kfunc_name(void *data, const struct bpf_insn *insn)
if (insn->src_reg != BPF_PSEUDO_KFUNC_CALL)
return NULL;
- desc_btf = find_kfunc_desc_btf(data, insn->off);
+ desc_btf = find_kfunc_desc_btf_cached(data, insn->off);
if (IS_ERR(desc_btf))
return "<error>";
func = btf_type_by_id(desc_btf, insn->imm);
+ if (!func || !btf_type_is_func(func))
+ return "<error>";
return btf_name_by_offset(desc_btf, func->name_off);
}
void bpf_verbose_insn(struct bpf_verifier_env *env, struct bpf_insn *insn)
{
const struct bpf_insn_cbs cbs = {
- .cb_call = disasm_kfunc_name,
+ .cb_call = bpf_disasm_kfunc_name,
.cb_print = verbose,
.private_data = env,
};
@@ -3304,34 +3394,6 @@ static int mark_chain_precision_batch(struct bpf_verifier_env *env,
return bpf_mark_chain_precision(env, starting_state, -1, NULL);
}
-static bool is_spillable_regtype(enum bpf_reg_type type)
-{
- switch (base_type(type)) {
- case PTR_TO_MAP_VALUE:
- case PTR_TO_STACK:
- case PTR_TO_CTX:
- case PTR_TO_PACKET:
- case PTR_TO_PACKET_META:
- case PTR_TO_PACKET_END:
- case PTR_TO_FLOW_KEYS:
- case CONST_PTR_TO_MAP:
- case PTR_TO_SOCKET:
- case PTR_TO_SOCK_COMMON:
- case PTR_TO_TCP_SOCK:
- case PTR_TO_XDP_SOCK:
- case PTR_TO_BTF_ID:
- case PTR_TO_BUF:
- case PTR_TO_MEM:
- case PTR_TO_FUNC:
- case PTR_TO_MAP_KEY:
- case PTR_TO_ARENA:
- return true;
- default:
- return false;
- }
-}
-
-
/* check if register is a constant scalar value */
static bool is_reg_const(struct bpf_reg_state *reg, bool subreg32)
{
@@ -3345,13 +3407,18 @@ static u64 reg_const_value(struct bpf_reg_state *reg, bool subreg32)
return subreg32 ? tnum_subreg(reg->var_off).value : reg->var_off.value;
}
+static bool is_pointer_regtype(enum bpf_reg_type type)
+{
+ return type != SCALAR_VALUE && type != NOT_INIT;
+}
+
static bool __is_pointer_value(bool allow_ptr_leaks,
const struct bpf_reg_state *reg)
{
if (allow_ptr_leaks)
return false;
- return reg->type != SCALAR_VALUE;
+ return is_pointer_regtype(reg->type);
}
static void clear_scalar_id(struct bpf_reg_state *reg)
@@ -3388,6 +3455,7 @@ static void save_register_state(struct bpf_verifier_env *env,
{
int i;
+ bpf_diag_mod_begin(env, &state->stack[spi].spilled_ptr, reg, BPF_DIAG_MOD_SPILL);
state->stack[spi].spilled_ptr = *reg;
for (i = BPF_REG_SIZE; i > BPF_REG_SIZE - size; i--)
@@ -3396,6 +3464,8 @@ static void save_register_state(struct bpf_verifier_env *env,
/* size < 8 bytes spill */
for (; i; i--)
mark_stack_slot_misc(env, &state->stack[spi].slot_type[i - 1]);
+
+ bpf_diag_mod_end(env);
}
static bool is_bpf_st_mem(struct bpf_insn *insn)
@@ -3468,7 +3538,16 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
bpf_is_spilled_reg(&state->stack[spi]) &&
!bpf_is_spilled_scalar_reg(&state->stack[spi]) &&
size != BPF_REG_SIZE) {
+ const char *reason;
+
verbose(env, "attempt to corrupt spilled pointer on stack\n");
+ reason = bpf_diag_fmt(env,
+ "This store writes %d bytes at stack offset %d into a stack slot that currently holds a spilled pointer. "
+ "Partial writes to spilled pointers are rejected because they can corrupt pointer metadata and leak kernel pointers.",
+ size, off);
+ bpf_diag_memory(
+ env, insn_idx, "stack spill corruption", reason,
+ "Write the full 8-byte spilled pointer slot, or use a separate stack slot for scalar data before overwriting only part of it.");
return -EACCES;
}
@@ -3476,10 +3555,11 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
if (value_regno >= 0)
reg = &cur->regs[value_regno];
if (!env->bypass_spec_v4) {
- bool sanitize = reg && is_spillable_regtype(reg->type);
+ bool sanitize = reg && is_pointer_regtype(reg->type);
for (i = 0; i < size; i++) {
- u8 type = state->stack[spi].slot_type[i];
+ u8 type = state->stack[spi].slot_type[(slot - i) %
+ BPF_REG_SIZE];
if (type != STACK_MISC && type != STACK_ZERO) {
sanitize = true;
@@ -3507,7 +3587,7 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
save_register_state(env, state, spi, reg, size);
/* Break the relation on a narrowing spill. */
if (!reg_value_fits)
- state->stack[spi].spilled_ptr.id = 0;
+ clear_scalar_id(&state->stack[spi].spilled_ptr);
} else if (!reg && !(off % BPF_REG_SIZE) && is_bpf_st_mem(insn) &&
env->bpf_capable) {
struct bpf_reg_state *tmp_reg = &env->fake_reg[0];
@@ -3516,7 +3596,7 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
__mark_reg_known(tmp_reg, insn->imm);
tmp_reg->type = SCALAR_VALUE;
save_register_state(env, state, spi, tmp_reg, size);
- } else if (reg && is_spillable_regtype(reg->type)) {
+ } else if (reg && is_pointer_regtype(reg->type)) {
/* register containing pointer is being spilled into stack */
if (size != BPF_REG_SIZE) {
verbose_linfo(env, insn_idx, "; ");
@@ -3531,6 +3611,9 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
} else {
u8 type = STACK_MISC;
+ if (bpf_is_spilled_reg(&state->stack[spi]))
+ bpf_diag_record_scrub(env, &state->stack[spi].spilled_ptr,
+ BPF_DIAG_MOD_WRITE);
scrub_special_slot(state, spi);
/* when we zero initialize stack slots mark them as such */
@@ -3691,6 +3774,8 @@ static int check_stack_write_var_off(struct bpf_verifier_env *env,
if (err)
return err;
}
+ bpf_diag_record_scrub_stack(env, state, min_off, max_off,
+ BPF_DIAG_MOD_VAR_WRITE);
return 0;
}
@@ -3701,14 +3786,21 @@ static int check_stack_write_var_off(struct bpf_verifier_env *env,
* SCALAR. This function does not deal with register filling; the caller must
* ensure that all spilled registers in the stack range have been marked as
* read.
+ *
+ * STACK_SPILL bytes backed by spilled scalar const zeroes are also considered
+ * zero bytes. In that case, mark the contributing stack slots precise so
+ * pruning cannot reuse a zero-spill state for a later non-zero spill state.
+ *
+ * Returns an error if precision backtracking fails.
*/
-static void mark_reg_stack_read(struct bpf_verifier_env *env,
- /* func where src register points to */
- struct bpf_func_state *ptr_state,
- int min_off, int max_off, int dst_regno)
+static int mark_reg_stack_read(struct bpf_verifier_env *env,
+ /* func where src register points to */
+ struct bpf_func_state *ptr_state,
+ int min_off, int max_off, int dst_regno)
{
struct bpf_verifier_state *vstate = env->cur_state;
struct bpf_func_state *state = vstate->frame[vstate->curframe];
+ u64 zero_spill_mask = 0;
int i, slot, spi;
u8 *stype;
int zeros = 0;
@@ -3718,19 +3810,48 @@ static void mark_reg_stack_read(struct bpf_verifier_env *env,
spi = slot / BPF_REG_SIZE;
mark_stack_slot_scratched(env, spi);
stype = ptr_state->stack[spi].slot_type;
- if (stype[slot % BPF_REG_SIZE] != STACK_ZERO)
- break;
- zeros++;
+ if (stype[slot % BPF_REG_SIZE] == STACK_ZERO) {
+ zeros++;
+ continue;
+ }
+ if (stype[slot % BPF_REG_SIZE] == STACK_SPILL &&
+ bpf_register_is_null(&ptr_state->stack[spi].spilled_ptr)) {
+ zero_spill_mask |= 1ull << spi;
+ zeros++;
+ continue;
+ }
+ break;
}
if (zeros == max_off - min_off) {
/* Any access_size read into register is zero extended,
* so the whole register == const_zero.
*/
__mark_reg_const_zero(env, &state->regs[dst_regno]);
+ if (zero_spill_mask) {
+ bpf_bt_set_frame_slot_mask(&env->bt, ptr_state->frameno, zero_spill_mask);
+ return mark_chain_precision_batch(env, env->cur_state);
+ }
} else {
/* have read misc data from the stack */
mark_reg_unknown(env, state->regs, dst_regno);
}
+
+ return 0;
+}
+
+static void bpf_diag_stack_read_uninit(struct bpf_verifier_env *env, int off, int i,
+ int size)
+{
+ const char *reason;
+
+ reason = bpf_diag_fmt(env,
+ "This rejected read uses %d bytes at stack offset %d, but byte %d in that range is uninitialized on this path. "
+ "Programs loaded with CAP_PERFMON can be allowed to read uninitialized stack bytes, but this program is being rejected without that allowance.",
+ size, off, i);
+ bpf_diag_memory(
+ env, env->insn_idx, "uninitialized stack read", reason,
+ "Initialize every byte in the stack range before reading it, adjust the offset and size so the read covers only initialized bytes, "
+ "or load with CAP_PERFMON if uninitialized stack reads are intended.");
}
/* Read the stack at 'off' and put the results into the register indicated by
@@ -3752,6 +3873,7 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
int i, slot = -off - 1, spi = slot / BPF_REG_SIZE;
struct bpf_reg_state *reg;
u8 *stype, type;
+ int err;
int insn_flags = INSN_F_STACK_ACCESS;
int hist_spi = spi, hist_frame = reg_state->frameno;
@@ -3761,6 +3883,12 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
mark_stack_slot_scratched(env, spi);
check_fastcall_stack_contract(env, state, env->insn_idx, off);
+ /*
+ * Refine the in-progress load record's origin to the source stack slot.
+ */
+ if (dst_regno >= 0)
+ bpf_diag_mod_begin(env, &state->regs[dst_regno], reg, BPF_DIAG_MOD_WRITE);
+
if (bpf_is_spilled_reg(&reg_state->stack[spi])) {
u8 spill_size = 1;
@@ -3779,11 +3907,6 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
if (size <= spill_size &&
bpf_stack_narrow_access_ok(off, size, spill_size)) {
- /* The earlier check_reg_arg() has decided the
- * subreg_def for this insn. Save it first.
- */
- s32 subreg_def = state->regs[dst_regno].subreg_def;
-
if (env->bpf_capable && size == 4 && spill_size == 4 &&
get_reg_width(reg) <= 32)
/* Ensure stack slot has an ID to build a relation
@@ -3791,7 +3914,6 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
*/
assign_scalar_id_before_mov(env, reg);
state->regs[dst_regno] = *reg;
- state->regs[dst_regno].subreg_def = subreg_def;
/* Break the relation on a narrowing fill.
* coerce_reg_to_size will adjust the boundaries.
@@ -3821,6 +3943,7 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
} else {
verbose(env, "invalid read from stack off %d+%d size %d\n",
off, i, size);
+ bpf_diag_stack_read_uninit(env, off, i, size);
}
return -EACCES;
}
@@ -3834,7 +3957,10 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
__mark_reg_const_zero(env, &state->regs[dst_regno]);
insn_flags = 0; /* not restoring original register state */
} else {
- mark_reg_unknown(env, state->regs, dst_regno);
+ err = mark_reg_stack_read(env, reg_state, off, off + size,
+ dst_regno);
+ if (err)
+ return err;
insn_flags = 0; /* not restoring original register state */
}
}
@@ -3876,11 +4002,15 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
} else {
verbose(env, "invalid read from stack off %d+%d size %d\n",
off, i, size);
+ bpf_diag_stack_read_uninit(env, off, i, size);
}
return -EACCES;
}
- if (dst_regno >= 0)
- mark_reg_stack_read(env, reg_state, off, off + size, dst_regno);
+ if (dst_regno >= 0) {
+ err = mark_reg_stack_read(env, reg_state, off, off + size, dst_regno);
+ if (err)
+ return err;
+ }
insn_flags = 0; /* we are not restoring spilled register */
}
if (insn_flags)
@@ -3934,7 +4064,10 @@ static int check_stack_read_var_off(struct bpf_verifier_env *env, struct bpf_reg
min_off = reg_smin(reg) + off;
max_off = reg_smax(reg) + off;
- mark_reg_stack_read(env, ptr_state, min_off, max_off + size, dst_regno);
+ err = mark_reg_stack_read(env, ptr_state, min_off, max_off + size,
+ dst_regno);
+ if (err)
+ return err;
check_fastcall_stack_contract(env, ptr_state, env->insn_idx, min_off);
return 0;
}
@@ -3962,11 +4095,19 @@ static int check_stack_read(struct bpf_verifier_env *env,
* check_stack_read_fixed_off).
*/
if (dst_regno < 0 && var_off) {
+ const char *reason;
char tn_buf[48];
tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off);
verbose(env, "variable offset stack pointer cannot be passed into helper function; var_off=%s off=%d size=%d\n",
tn_buf, off, size);
+ reason = bpf_diag_fmt(env,
+ "The helper would access the stack through variable offset %s plus fixed offset %d and size %d. "
+ "Helper stack memory arguments require a constant stack offset and a precise initialized range.",
+ tn_buf, off, size);
+ bpf_diag_memory(
+ env, env->insn_idx, "variable stack access", reason,
+ "Use a fixed stack offset for helper memory arguments, or copy the needed bytes into a fixed stack slot first.");
return -EACCES;
}
/* Variable offset is prohibited for unprivileged mode for simplicity
@@ -3993,7 +4134,6 @@ static int check_stack_read(struct bpf_verifier_env *env,
return err;
}
-
/* check_stack_write dispatches to check_stack_write_fixed_off or
* check_stack_write_var_off.
*
@@ -4052,14 +4192,17 @@ static int check_stack_arg_write(struct bpf_verifier_env *env, struct bpf_func_s
if (spi + 1 > subprog->max_out_stack_arg_cnt)
subprog->max_out_stack_arg_cnt = spi + 1;
+ arg = &state->stack_arg_regs[spi];
+ bpf_diag_mod_begin(env, arg, value_reg, BPF_DIAG_MOD_WRITE);
+
if (value_reg) {
state->stack_arg_regs[spi] = *value_reg;
} else {
/* BPF_ST: store immediate, treat as scalar */
- arg = &state->stack_arg_regs[spi];
arg->type = SCALAR_VALUE;
__mark_reg_known(arg, env->prog->insnsi[env->insn_idx].imm);
}
+ bpf_diag_mod_end(env);
state->no_stack_arg_load = true;
return bpf_push_jmp_history(env, env->cur_state,
INSN_F_STACK_ARG_ACCESS, spi, 0, 0);
@@ -4092,7 +4235,9 @@ static int check_stack_arg_read(struct bpf_verifier_env *env, struct bpf_func_st
caller = vstate->frame[vstate->curframe - 1];
arg = &caller->stack_arg_regs[spi];
cur = vstate->frame[vstate->curframe];
+ bpf_diag_mod_begin(env, &cur->regs[dst_regno], arg, BPF_DIAG_MOD_WRITE);
cur->regs[dst_regno] = *arg;
+ bpf_diag_mod_end(env);
return bpf_push_jmp_history(env, env->cur_state,
INSN_F_STACK_ARG_ACCESS, spi, 0, 0);
}
@@ -4106,8 +4251,18 @@ static int mark_stack_arg_precision(struct bpf_verifier_env *env, int arg_idx)
return mark_chain_precision_batch(env, env->cur_state);
}
+static int mark_arg_precision(struct bpf_verifier_env *env, argno_t argno)
+{
+ int regno = reg_from_argno(argno);
+
+ if (regno >= 0)
+ return mark_chain_precision(env, regno);
+ return mark_stack_arg_precision(env, arg_idx_from_argno(argno));
+}
+
static int check_outgoing_stack_args(struct bpf_verifier_env *env, struct bpf_func_state *caller,
- int nargs)
+ int nargs, const char *callee_name, const struct btf *btf,
+ const struct btf_param *args)
{
int i, spi;
@@ -4115,8 +4270,14 @@ static int check_outgoing_stack_args(struct bpf_verifier_env *env, struct bpf_fu
spi = i - MAX_BPF_FUNC_REG_ARGS;
if (spi >= caller->out_stack_arg_cnt ||
caller->stack_arg_regs[spi].type == NOT_INIT) {
+ const char *arg_name = NULL;
+
+ if (args && args[i].name_off)
+ arg_name = btf_name_by_offset(btf, args[i].name_off);
verbose(env, "callee expects %d args, stack arg%d is not initialized\n",
nargs, spi + 1);
+ bpf_diag_stack_arg_uninit(env, env->insn_idx, nargs, spi,
+ callee_name, arg_name);
return -EFAULT;
}
}
@@ -4197,6 +4358,9 @@ static int check_mem_region_access(struct bpf_verifier_env *env, struct bpf_reg_
int off, int size, u32 mem_size,
bool zero_size_allowed)
{
+ const char *proof = "";
+ const char *start;
+ s64 max_start, max_end;
int err;
/* We may have adjusted the register pointing to memory region, so we
@@ -4215,14 +4379,28 @@ static int check_mem_region_access(struct bpf_verifier_env *env, struct bpf_reg_
reg_smin(reg) + off < 0)) {
verbose(env, "%s min value is negative, either use unsigned index or do a if (index >=0) check.\n",
reg_arg_name(env, argno));
- return -EACCES;
+ err = -EACCES;
+ if (bpf_diag_enabled(env)) {
+ start = bpf_diag_fmt_s64_sum(env, reg_smin(reg), off);
+ proof = bpf_diag_fmt(
+ env, "the minimal bound for a memory access is a negative value: %s",
+ start);
+ }
+ goto report_error;
}
+
err = __check_mem_access(env, reg, argno, reg_smin(reg) + off, size,
mem_size, zero_size_allowed);
if (err) {
verbose(env, "%s min value is outside of the allowed memory range\n",
reg_arg_name(env, argno));
- return err;
+ if (bpf_diag_enabled(env)) {
+ start = bpf_diag_fmt_s64_sum(env, reg_smin(reg), off);
+ proof = bpf_diag_fmt(
+ env, "the minimal bound for a memory access is %s and is outside of the object of size %u",
+ start, mem_size);
+ }
+ goto report_error;
}
/* If we haven't set a max value then we need to bail since we can't be
@@ -4232,17 +4410,36 @@ static int check_mem_region_access(struct bpf_verifier_env *env, struct bpf_reg_
if (reg_umax(reg) >= BPF_MAX_VAR_OFF) {
verbose(env, "%s unbounded memory access, make sure to bounds check any such access\n",
reg_arg_name(env, argno));
- return -EACCES;
+ err = -EACCES;
+ if (bpf_diag_enabled(env))
+ proof = bpf_diag_fmt(
+ env, "the maximal bound for a memory access is %llu and exceeds maximum allowed offset of %u",
+ reg_umax(reg), BPF_MAX_VAR_OFF);
+ goto report_error;
}
+
err = __check_mem_access(env, reg, argno, reg_umax(reg) + off, size,
mem_size, zero_size_allowed);
if (err) {
verbose(env, "%s max value is outside of the allowed memory range\n",
reg_arg_name(env, argno));
- return err;
+ if (bpf_diag_enabled(env)) {
+ max_start = (s64)reg_umax(reg) + off;
+ max_end = max_start + size;
+ proof = bpf_diag_fmt(
+ env, "the maximal bound for a memory access is %lld: start %lld + access_size %d, beyond object_size %u",
+ max_end, max_start, size, mem_size);
+ }
+ goto report_error;
}
return 0;
+
+report_error:
+ bpf_diag_mem_bounds(env, env->insn_idx, reg_from_argno(argno),
+ reg_arg_name(env, argno), reg_type_str(env, reg->type), proof,
+ off, size, mem_size, reg);
+ return err;
}
static int __check_ptr_off_reg(struct bpf_verifier_env *env,
@@ -4271,6 +4468,9 @@ static int __check_ptr_off_reg(struct bpf_verifier_env *env,
if (!fixed_off_ok && reg->var_off.value != 0) {
verbose(env, "dereference of modified %s ptr %s off=%lld disallowed\n",
reg_type_str(env, reg->type), reg_arg_name(env, argno), reg->var_off.value);
+ bpf_diag_invalid_deref(env, env->insn_idx, reg_from_argno(argno),
+ reg_arg_name(env, argno), reg,
+ BPF_DIAG_DEREF_MODIFIED_PTR, reg->var_off.value);
return -EACCES;
}
@@ -4309,6 +4509,13 @@ static int map_kptr_match_type(struct bpf_verifier_env *env,
if (type_flag(reg->type) & ~perm_flags)
goto bad_type;
+ /*
+ * A BPF_KPTR_PERCPU field is read back as MEM_PERCPU, so the value
+ * stored in it must carry the same flag.
+ */
+ if ((kptr_field->type == BPF_KPTR_PERCPU) != !!(reg->type & MEM_PERCPU))
+ goto bad_type;
+
/* We need to verify reg->type and reg->btf, before accessing reg->btf */
reg_name = btf_type_name(reg->btf, reg->btf_id);
@@ -4347,7 +4554,8 @@ static int map_kptr_match_type(struct bpf_verifier_env *env,
*/
if (!btf_struct_ids_match(&env->log, reg->btf, reg->btf_id, reg->var_off.value,
kptr_field->kptr.btf, kptr_field->kptr.btf_id,
- kptr_field->type != BPF_KPTR_UNREF))
+ kptr_field->type != BPF_KPTR_UNREF,
+ !type_is_alloc(reg->type)))
goto bad_type;
return 0;
bad_type:
@@ -4373,7 +4581,9 @@ static bool in_sleepable(struct bpf_verifier_env *env)
static bool in_rcu_cs(struct bpf_verifier_env *env)
{
return env->cur_state->active_rcu_locks ||
+ env->cur_state->active_preempt_locks ||
env->cur_state->active_locks ||
+ env->cur_state->active_irq_id ||
!in_sleepable(env);
}
@@ -4392,6 +4602,9 @@ BTF_ID(struct, task_struct)
#ifdef CONFIG_CRYPTO
BTF_ID(struct, bpf_crypto_ctx)
#endif
+#ifdef CONFIG_INET
+BTF_ID(struct, bpf_ksock)
+#endif
BTF_SET_END(rcu_protected_types)
static bool rcu_protected_object(const struct btf *btf, u32 btf_id)
@@ -4509,8 +4722,15 @@ static int check_map_kptr_access(struct bpf_verifier_env *env,
return ret;
} else if (class == BPF_STX) {
val_reg = reg_state(env, value_regno);
- if (!bpf_register_is_null(val_reg) &&
- map_kptr_match_type(env, kptr_field, val_reg, value_regno))
+ if (bpf_register_is_null(val_reg)) {
+ /*
+ * This store is valid only because the scalar is known to be
+ * zero. Mark it precise so another scalar cannot be pruned
+ * against this state.
+ */
+ return mark_chain_precision(env, value_regno);
+ }
+ if (map_kptr_match_type(env, kptr_field, val_reg, value_regno))
return -EACCES;
} else if (class == BPF_ST) {
if (insn->imm) {
@@ -4602,7 +4822,7 @@ static int check_map_access(struct bpf_verifier_env *env, struct bpf_reg_state *
}
static bool may_access_direct_pkt_data(struct bpf_verifier_env *env,
- const struct bpf_call_arg_meta *meta,
+ const struct bpf_func_proto *fn,
enum bpf_access_type t)
{
enum bpf_prog_type prog_type = resolve_prog_type(env->prog);
@@ -4626,8 +4846,8 @@ static bool may_access_direct_pkt_data(struct bpf_verifier_env *env,
case BPF_PROG_TYPE_LWT_XMIT:
case BPF_PROG_TYPE_SK_SKB:
case BPF_PROG_TYPE_SK_MSG:
- if (meta)
- return meta->pkt_access;
+ if (fn)
+ return fn->pkt_access;
env->seen_direct_write = true;
return true;
@@ -4788,7 +5008,6 @@ static int check_sock_access(struct bpf_verifier_env *env, int insn_idx,
valid = false;
}
-
if (valid) {
env->insn_aux_data[insn_idx].ctx_field_size =
info.ctx_field_size;
@@ -4842,6 +5061,22 @@ static bool is_arena_reg(struct bpf_verifier_env *env, int regno)
return reg->type == PTR_TO_ARENA;
}
+static bool is_load_acq_unsafe(struct bpf_verifier_env *env, int regno,
+ struct bpf_insn *insn)
+{
+ const struct bpf_reg_state *reg = reg_state(env, regno);
+
+ /*
+ * A BPF_LOAD_ACQ is not rewritten to a BPF_PROBE_MEM load by the
+ * verifier, unlike a regular BPF_LDX. The JIT would emit a plain load
+ * with no exception table entry, so a fault (e.g. NULL deref) crashes
+ * the kernel instead of being handled. Reject the source pointer types
+ * that would have needed that protection, the remaining ones stay
+ * allowed.
+ */
+ return insn->imm == BPF_LOAD_ACQ && bpf_may_fault_on_deref(reg->type);
+}
+
/* Return false if @regno contains a pointer whose type isn't supported for
* atomic instruction @insn.
*/
@@ -4858,7 +5093,8 @@ static bool atomic_ptr_type_ok(struct bpf_verifier_env *env, int regno,
return false;
if (is_arena_reg(env, regno))
return bpf_jit_supports_insn(insn, true);
-
+ if (is_load_acq_unsafe(env, regno, insn))
+ return false;
return true;
}
@@ -4871,6 +5107,18 @@ static u32 *reg2btf_ids[__BPF_REG_TYPE_MAX] = {
[CONST_PTR_TO_MAP] = btf_bpf_map_id,
};
+static enum bpf_reg_type lookup_reg2btf_ids(u32 ref_id)
+{
+ enum bpf_reg_type type;
+
+ for (type = 0; type < __BPF_REG_TYPE_MAX; type++) {
+ if (reg2btf_ids[type] && *reg2btf_ids[type] == ref_id)
+ return type;
+ }
+
+ return NOT_INIT;
+}
+
static bool is_trusted_reg(struct bpf_verifier_env *env, const struct bpf_reg_state *reg)
{
/* A referenced register is always trusted. */
@@ -5089,6 +5337,15 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
if (!priv_stack_supported)
subprog[idx].priv_stack_mode = NO_PRIV_STACK;
process_func:
+ if (subprog[idx].has_ld_abs) {
+ for (tmp = idx; tmp >= 0; tmp = dinfo[tmp].caller) {
+ if (subprog[tmp].is_cb) {
+ verbose(env, "cannot use BPF_LD_[ABS|IND] within callback\n");
+ return -EINVAL;
+ }
+ }
+ }
+
/* protect against potential stack overflow that might happen when
* bpf2bpf calls get combined with tailcalls. Limit the caller's stack
* depth for such case down to 256 so that the worst case scenario
@@ -5185,10 +5442,6 @@ continue_func:
if (verifier_bug_if(sidx < 0, env, "callee not found at insn %d", next_insn))
return -EFAULT;
if (subprog[sidx].is_async_cb) {
- if (subprog[sidx].has_tail_call) {
- verifier_bug(env, "subprog has tail_call and async cb");
- return -EFAULT;
- }
/* async callbacks don't increase bpf prog stack size unless called directly */
if (!bpf_pseudo_call(insn + i))
continue;
@@ -5211,8 +5464,8 @@ continue_func:
if (!priv_stack_supported)
subprog[idx].priv_stack_mode = NO_PRIV_STACK;
- if (subprog[idx].has_tail_call)
- tail_call_reachable = true;
+ /* sync tail_call_reachable with callee state on entry */
+ tail_call_reachable = subprog[idx].has_tail_call;
frame = bpf_subprog_is_global(env, idx) ? 0 : frame + 1;
if (frame >= MAX_CALL_FRAMES) {
@@ -5229,8 +5482,8 @@ continue_func:
*/
if (tail_call_reachable) {
for (tmp = idx; tmp >= 0; tmp = dinfo[tmp].caller) {
- if (subprog[tmp].is_exception_cb) {
- verbose(env, "cannot tail call within exception cb\n");
+ if (subprog[tmp].is_cb) {
+ verbose(env, "cannot tail call within callback\n");
return -EINVAL;
}
if (subprog[tmp].stack_arg_cnt) {
@@ -5276,7 +5529,7 @@ static int check_max_stack_depth(struct bpf_verifier_env *env)
bool priv_stack_supported;
int ret;
- dinfo = kvcalloc(env->subprog_cnt, sizeof(*dinfo), GFP_KERNEL_ACCOUNT);
+ dinfo = kvzalloc_objs(*dinfo, env->subprog_cnt, GFP_KERNEL_ACCOUNT);
if (!dinfo)
return -ENOMEM;
@@ -5325,14 +5578,11 @@ static int check_max_stack_depth(struct bpf_verifier_env *env)
static int __check_buffer_access(struct bpf_verifier_env *env,
const char *buf_info,
const struct bpf_reg_state *reg,
- argno_t argno, int off, int size)
+ argno_t argno, int off, int size,
+ u32 *access_end)
{
- if (off < 0) {
- verbose(env,
- "%s invalid %s buffer access: off=%d, size=%d\n",
- reg_arg_name(env, argno), buf_info, off, size);
- return -EACCES;
- }
+ s64 start;
+
if (!tnum_is_const(reg->var_off)) {
char tn_buf[48];
@@ -5343,6 +5593,15 @@ static int __check_buffer_access(struct bpf_verifier_env *env,
return -EACCES;
}
+ start = (s64)reg->var_off.value + off;
+ if (start < 0) {
+ verbose(env,
+ "%s invalid negative %s buffer offset: off=%d, var_off=%lld\n",
+ reg_arg_name(env, argno), buf_info, off, (s64)reg->var_off.value);
+ return -EACCES;
+ }
+
+ *access_end = start + size;
return 0;
}
@@ -5350,14 +5609,14 @@ static int check_tp_buffer_access(struct bpf_verifier_env *env,
const struct bpf_reg_state *reg,
argno_t argno, int off, int size)
{
+ u32 access_end;
int err;
- err = __check_buffer_access(env, "tracepoint", reg, argno, off, size);
+ err = __check_buffer_access(env, "tracepoint", reg, argno, off, size, &access_end);
if (err)
return err;
- env->prog->aux->max_tp_access = max(reg->var_off.value + off + size,
- env->prog->aux->max_tp_access);
+ env->prog->aux->max_tp_access = max(access_end, env->prog->aux->max_tp_access);
return 0;
}
@@ -5369,13 +5628,14 @@ static int check_buffer_access(struct bpf_verifier_env *env,
u32 *max_access)
{
const char *buf_info = type_is_rdonly_mem(reg->type) ? "rdonly" : "rdwr";
+ u32 access_end;
int err;
- err = __check_buffer_access(env, buf_info, reg, argno, off, size);
+ err = __check_buffer_access(env, buf_info, reg, argno, off, size, &access_end);
if (err)
return err;
- *max_access = max(reg->var_off.value + off + size, *max_access);
+ *max_access = max(access_end, *max_access);
return 0;
}
@@ -5568,6 +5828,8 @@ int bpf_map_direct_read(struct bpf_map *map, int off, int size, u64 *val,
u64 addr;
int err;
+ if (map->map_type == BPF_MAP_TYPE_INSN_ARRAY || map->map_type == BPF_MAP_TYPE_PERCPU_ARRAY)
+ return -EINVAL;
err = map->ops->map_direct_value_addr(map, &addr, off);
if (err)
return err;
@@ -5780,23 +6042,37 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
return -EACCES;
}
+ if (atype != BPF_READ && bpf_may_fault_on_deref(reg->type)) {
+ verbose(env, "only read is supported\n");
+ return -EACCES;
+ }
+
if (env->ops->btf_struct_access && !type_is_alloc(reg->type) && atype == BPF_WRITE) {
if (!btf_is_kernel(reg->btf)) {
verifier_bug(env, "reg->btf must be kernel btf");
return -EFAULT;
}
ret = env->ops->btf_struct_access(&env->log, reg, off, size);
+ if (ret < 0)
+ verbose(env,
+ "%s cannot write into ptr_%s at off=%d size=%d\n",
+ reg_arg_name(env, argno), tname, off, size);
} else {
/* Writes are permitted with default btf_struct_access for
- * program allocated objects (which always have id > 0),
- * but not for untrusted PTR_TO_BTF_ID | MEM_ALLOC.
+ * program allocated objects (which always have id > 0).
*/
if (atype != BPF_READ && !type_is_ptr_alloc_obj(reg->type)) {
verbose(env, "only read is supported\n");
return -EACCES;
}
- if (type_is_alloc(reg->type) && !type_is_non_owning_ref(reg->type) &&
+ /*
+ * A fault-prone allocated object may still be read through a
+ * BPF_PROBE_MEM load after its lifetime protection ends. Writes
+ * through such pointers were rejected above.
+ */
+ if (type_is_alloc(reg->type) && !bpf_may_fault_on_deref(reg->type) &&
+ !type_is_non_owning_ref(reg->type) &&
!(reg->type & MEM_RCU) && !reg_is_referenced(env, reg)) {
verifier_bug(env, "allocated object must have a referenced id");
return -EFAULT;
@@ -6056,6 +6332,51 @@ static void add_scalar_to_reg(struct bpf_reg_state *dst_reg, s64 val)
reg_bounds_sync(dst_reg);
}
+static int check_map_mem_read(struct bpf_verifier_env *env, struct bpf_reg_state *reg, int off,
+ int bpf_size, int value_regno, bool is_ldsx)
+{
+ struct bpf_reg_state *regs = cur_regs(env);
+ int size = bpf_size_to_bytes(bpf_size);
+ struct bpf_map *map = reg->map_ptr;
+
+ switch (map->map_type) {
+ case BPF_MAP_TYPE_INSN_ARRAY:
+ if (bpf_size != BPF_DW) {
+ verbose(env, "Invalid read of %d bytes from insn_array\n", size);
+ return -EACCES;
+ }
+ regs[value_regno] = *reg;
+ add_scalar_to_reg(&regs[value_regno], off);
+ regs[value_regno].type = PTR_TO_INSN;
+ return 0;
+ case BPF_MAP_TYPE_PERCPU_ARRAY:
+ goto reg_unknown;
+ default:
+ break;
+ }
+
+ /* If map is read-only, track its contents as scalars. */
+ if (tnum_is_const(reg->var_off) &&
+ bpf_map_is_rdonly(map) &&
+ map->ops->map_direct_value_addr) {
+ int map_off = off + reg->var_off.value;
+ u64 val = 0;
+ int err;
+
+ err = bpf_map_direct_read(map, map_off, size, &val, is_ldsx);
+ if (err)
+ return err;
+
+ regs[value_regno].type = SCALAR_VALUE;
+ __mark_reg_known(&regs[value_regno], val);
+ return 0;
+ }
+
+reg_unknown:
+ mark_reg_unknown(env, regs, value_regno);
+ return 0;
+}
+
/* check whether memory at (regno + off) is accessible for t = (read | write)
* if t==write, value_regno is a register which value is stored into memory
* if t==read, value_regno is a register which will receive the value from memory
@@ -6110,38 +6431,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
if (kptr_field) {
err = check_map_kptr_access(env, value_regno, insn_idx, kptr_field);
} else if (t == BPF_READ && value_regno >= 0) {
- struct bpf_map *map = reg->map_ptr;
-
- /*
- * If map is read-only, track its contents as scalars,
- * unless it is an insn array (see the special case below)
- */
- if (tnum_is_const(reg->var_off) &&
- bpf_map_is_rdonly(map) &&
- map->ops->map_direct_value_addr &&
- map->map_type != BPF_MAP_TYPE_INSN_ARRAY) {
- int map_off = off + reg->var_off.value;
- u64 val = 0;
-
- err = bpf_map_direct_read(map, map_off, size,
- &val, is_ldsx);
- if (err)
- return err;
-
- regs[value_regno].type = SCALAR_VALUE;
- __mark_reg_known(&regs[value_regno], val);
- } else if (map->map_type == BPF_MAP_TYPE_INSN_ARRAY) {
- if (bpf_size != BPF_DW) {
- verbose(env, "Invalid read of %d bytes from insn_array\n",
- size);
- return -EACCES;
- }
- regs[value_regno] = *reg;
- add_scalar_to_reg(&regs[value_regno], off);
- regs[value_regno].type = PTR_TO_INSN;
- } else {
- mark_reg_unknown(env, regs, value_regno);
- }
+ err = check_map_mem_read(env, reg, off, bpf_size, value_regno, is_ldsx);
}
} else if (base_type(reg->type) == PTR_TO_MEM) {
bool rdonly_mem = type_is_rdonly_mem(reg->type);
@@ -6150,6 +6440,9 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
if (type_may_be_null(reg->type)) {
verbose(env, "%s invalid mem access '%s'\n", reg_arg_name(env, argno),
reg_type_str(env, reg->type));
+ bpf_diag_invalid_deref(env, insn_idx, reg_from_argno(argno),
+ reg_arg_name(env, argno), reg,
+ BPF_DIAG_DEREF_NULLABLE_PTR, 0);
return -EACCES;
}
@@ -6165,6 +6458,15 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
return -EACCES;
}
+ if (rdonly_untrusted && !env->allow_ptr_leaks) {
+ verbose(env, "%s access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN\n",
+ reg_type_str(env, reg->type));
+ bpf_diag_policy(env, insn_idx, "read from untrusted read-only memory",
+ "the access requires CAP_PERFMON",
+ "Load the program with CAP_PERFMON, or avoid dereferencing untrusted pointers.");
+ return -EPERM;
+ }
+
/*
* Accesses to untrusted PTR_TO_MEM are done through probe
* instructions, hence no need to check bounds in that case.
@@ -6196,6 +6498,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
*/
if (info.reg_type == SCALAR_VALUE) {
if (info.is_retval && get_func_retval_range(env->prog, &range)) {
+ mark_reg_unknown(env, regs, value_regno);
err = __mark_reg_s32_range(env, regs, value_regno,
range.minval, range.maxval);
if (err)
@@ -6206,12 +6509,6 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
} else {
mark_reg_known_zero(env, regs,
value_regno);
- /* A load of ctx field could have different
- * actual load size with the one encoded in the
- * insn. When the dst is PTR, it is for sure not
- * a sub-register.
- */
- regs[value_regno].subreg_def = DEF_NOT_SUBREG;
if (base_type(info.reg_type) == PTR_TO_BTF_ID) {
regs[value_regno].btf = info.btf;
regs[value_regno].btf_id = info.btf_id;
@@ -6305,18 +6602,38 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
if (t == BPF_READ && value_regno >= 0)
mark_reg_unknown(env, regs, value_regno);
} else {
+ enum bpf_diag_invalid_deref_kind kind = BPF_DIAG_DEREF_INVALID_PTR;
+
verbose(env, "%s invalid mem access '%s'\n", reg_arg_name(env, argno),
reg_type_str(env, reg->type));
+ if (reg->type == SCALAR_VALUE)
+ kind = BPF_DIAG_DEREF_SCALAR;
+ else if (type_may_be_null(reg->type))
+ kind = BPF_DIAG_DEREF_NULLABLE_PTR;
+ bpf_diag_invalid_deref(env, insn_idx, reg_from_argno(argno),
+ reg_arg_name(env, argno), reg, kind, 0);
return -EACCES;
}
if (!err && size < BPF_REG_SIZE && value_regno >= 0 && t == BPF_READ &&
regs[value_regno].type == SCALAR_VALUE) {
- if (!is_ldsx)
+ if (!is_ldsx) {
/* b/h/w load zero-extends, mark upper bits as known 0 */
coerce_reg_to_size(&regs[value_regno], size);
- else
+ } else {
+ /*
+ * Sign-extension can change the register value relative
+ * to a scalar it is linked with by id (e.g. a zero-
+ * extending fill of the same spilled stack slot), thus
+ * drop the shared id in that case.
+ */
+ bool no_sext = reg_umax(&regs[value_regno]) <
+ (1ULL << (size * BITS_PER_BYTE - 1));
+
coerce_reg_to_size_sx(&regs[value_regno], size);
+ if (!no_sext)
+ clear_scalar_id(&regs[value_regno]);
+ }
}
return err;
}
@@ -6354,15 +6671,19 @@ static int check_load_mem(struct bpf_verifier_env *env, struct bpf_insn *insn,
src_reg_type = regs[insn->src_reg].type;
- /* Check if (src_reg + off) is readable. The state of dst_reg will be
- * updated by this call.
+ /*
+ * check_stack_read_fixed_off() may refine the modification's origin to
+ * the source stack slot.
*/
+ bpf_diag_mod_begin(env, &regs[insn->dst_reg], NULL, BPF_DIAG_MOD_WRITE);
err = check_mem_access(env, env->insn_idx, regs + insn->src_reg, argno_from_reg(insn->src_reg), insn->off,
BPF_SIZE(insn->code), BPF_READ, insn->dst_reg,
strict_alignment_once, is_ldsx);
err = err ?: save_aux_ptr_type(env, src_reg_type,
allow_trust_mismatch);
err = err ?: reg_bounds_sanity_check(env, &regs[insn->dst_reg], ctx);
+ if (!err)
+ bpf_diag_mod_end(env);
return err;
}
@@ -6453,21 +6774,12 @@ static int check_atomic_rmw(struct bpf_verifier_env *env,
return -EACCES;
}
- if (insn->imm & BPF_FETCH) {
- if (insn->imm == BPF_CMPXCHG)
- load_reg = BPF_REG_0;
- else
- load_reg = insn->src_reg;
-
+ load_reg = bpf_atomic_load_reg(insn);
+ if (load_reg >= 0) {
/* check and record load of old value */
err = check_reg_arg(env, load_reg, DST_OP);
if (err)
return err;
- } else {
- /* This instruction accesses a memory location but doesn't
- * actually load it into a register.
- */
- load_reg = -1;
}
dst_reg = cur_regs(env) + insn->dst_reg;
@@ -6477,18 +6789,20 @@ static int check_atomic_rmw(struct bpf_verifier_env *env,
*/
err = check_mem_access(env, env->insn_idx, dst_reg, argno_from_reg(insn->dst_reg), insn->off,
BPF_SIZE(insn->code), BPF_READ, -1, true, false);
- if (!err && load_reg >= 0)
+ if (!err && load_reg >= 0) {
+ bpf_diag_mod_begin(env, cur_regs(env) + load_reg, NULL, BPF_DIAG_MOD_WRITE);
err = check_mem_access(env, env->insn_idx, dst_reg, argno_from_reg(insn->dst_reg),
insn->off, BPF_SIZE(insn->code),
BPF_READ, load_reg, true, false);
+ if (!err)
+ bpf_diag_mod_end(env);
+ }
if (err)
return err;
- if (is_arena_reg(env, insn->dst_reg)) {
- err = save_aux_ptr_type(env, PTR_TO_ARENA, false);
- if (err)
- return err;
- }
+ err = save_aux_ptr_type(env, dst_reg->type, false);
+ if (err)
+ return err;
/* Check whether we can write into the same memory. */
err = check_mem_access(env, env->insn_idx, dst_reg, argno_from_reg(insn->dst_reg), insn->off,
BPF_SIZE(insn->code), BPF_WRITE, -1, true, false);
@@ -6502,7 +6816,7 @@ static int check_atomic_load(struct bpf_verifier_env *env,
{
int err;
- err = check_load_mem(env, insn, true, false, false, "atomic_load");
+ err = check_reg_arg(env, insn->src_reg, SRC_OP);
if (err)
return err;
@@ -6513,7 +6827,7 @@ static int check_atomic_load(struct bpf_verifier_env *env,
return -EACCES;
}
- return 0;
+ return check_load_mem(env, insn, true, false, false, "atomic_load");
}
static int check_atomic_store(struct bpf_verifier_env *env,
@@ -6590,12 +6904,14 @@ static int check_stack_range_initialized(
*/
bool clobber = type == BPF_WRITE;
/*
- * Negative access_size signals global subprog/kfunc arg check where
+ * Negative access_size signals global subprog arg check where
* STACK_POISON slots are acceptable. static stack liveness
* might have determined that subprog doesn't read them,
* but BTF based global subprog validation isn't accurate enough.
*/
bool allow_poison = access_size < 0 || clobber;
+ /* The call will initialize the memory; uninitialized stack allowed */
+ bool raw_mode = meta && meta->arg_raw_mem.regno == reg_from_argno(argno);
access_size = abs(access_size);
@@ -6608,7 +6924,6 @@ static int check_stack_range_initialized(
if (err)
return err;
-
if (tnum_is_const(reg->var_off)) {
min_off = max_off = reg->var_off.value + off;
} else {
@@ -6631,41 +6946,14 @@ static int check_stack_range_initialized(
* helper return since specific bounds are unknown what may
* cause uninitialized stack leaking.
*/
- if (meta && meta->raw_mode)
- meta = NULL;
+ raw_mode = false;
min_off = reg_smin(reg) + off;
max_off = reg_smax(reg) + off;
}
- if (meta && meta->raw_mode) {
- /* Ensure we won't be overwriting dynptrs when simulating byte
- * by byte access in check_helper_call using meta.access_size.
- * This would be a problem if we have a helper in the future
- * which takes:
- *
- * helper(uninit_mem, len, dynptr)
- *
- * Now, uninint_mem may overlap with dynptr pointer. Hence, it
- * may end up writing to dynptr itself when touching memory from
- * arg 1. This can be relaxed on a case by case basis for known
- * safe cases, but reject due to the possibilitiy of aliasing by
- * default.
- */
- for (i = min_off; i < max_off + access_size; i++) {
- int stack_off = -i - 1;
-
- spi = bpf_get_spi(i);
- /* raw_mode may write past allocated_stack */
- if (state->allocated_stack <= stack_off)
- continue;
- if (state->stack[spi].slot_type[stack_off % BPF_REG_SIZE] == STACK_DYNPTR) {
- verbose(env, "potential write to dynptr at off=%d disallowed\n", i);
- return -EACCES;
- }
- }
- meta->access_size = access_size;
- meta->regno = reg_from_argno(argno);
+ if (raw_mode) {
+ meta->arg_raw_mem.size = access_size;
return 0;
}
@@ -6724,14 +7012,17 @@ mark:
return 0;
}
-static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,
- int access_size, enum bpf_access_type access_type,
- bool zero_size_allowed,
- struct bpf_call_arg_meta *meta)
+static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
+ argno_t argno, int access_size,
+ enum bpf_access_type access_type, bool zero_size_allowed,
+ struct bpf_call_arg_meta *meta, bool *known_memory)
{
struct bpf_reg_state *regs = cur_regs(env);
u32 *max_access;
+ if (known_memory)
+ *known_memory = true;
+
switch (base_type(reg->type)) {
case PTR_TO_PACKET:
case PTR_TO_PACKET_META:
@@ -6801,6 +7092,8 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_
if (zero_size_allowed && access_size == 0 &&
bpf_register_is_null(reg))
return 0;
+ if (known_memory && base_type(reg->type) != PTR_TO_CTX)
+ *known_memory = false;
verbose(env, "%s type=%s ", reg_arg_name(env, argno),
reg_type_str(env, reg->type));
@@ -6809,6 +7102,12 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_
}
}
+enum bpf_mem_size_failure {
+ BPF_MEM_SIZE_FAIL_NONE,
+ BPF_MEM_SIZE_FAIL_MEMORY,
+ BPF_MEM_SIZE_FAIL_SIZE,
+};
+
/* verify arguments to helpers or kfuncs consisting of a pointer and an access
* size.
*
@@ -6817,11 +7116,15 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_
static int check_mem_size_reg(struct bpf_verifier_env *env,
struct bpf_reg_state *mem_reg,
struct bpf_reg_state *size_reg, argno_t mem_argno,
- argno_t size_argno, enum bpf_access_type access_type,
+ argno_t size_argno, u32 access_type,
bool zero_size_allowed,
- struct bpf_call_arg_meta *meta)
+ struct bpf_call_arg_meta *meta,
+ enum bpf_mem_size_failure *failure)
{
- int err;
+ int err = 0;
+
+ if (failure)
+ *failure = BPF_MEM_SIZE_FAIL_NONE;
/* This is used to refine r0 return value bounds for helpers
* that enforce this value as an upper bound on return values.
@@ -6844,42 +7147,54 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,
if (reg_smin(size_reg) < 0) {
verbose(env, "%s min value is negative, either use unsigned or 'var &= const'\n",
reg_arg_name(env, size_argno));
- return -EACCES;
+ err = -EACCES;
+ goto size_error;
}
if (reg_umin(size_reg) == 0 && !zero_size_allowed) {
verbose(env, "%s invalid zero-sized read: u64=[%lld,%lld]\n",
reg_arg_name(env, size_argno), reg_umin(size_reg), reg_umax(size_reg));
- return -EACCES;
+ err = -EACCES;
+ goto size_error;
}
if (reg_umax(size_reg) >= BPF_MAX_VAR_SIZ) {
verbose(env, "%s unbounded memory access, use 'var &= const' or 'if (var < const)'\n",
reg_arg_name(env, size_argno));
- return -EACCES;
+ err = -EACCES;
+ goto size_error;
}
- err = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),
- access_type, zero_size_allowed, meta);
- if (!err) {
- int regno = reg_from_argno(size_argno);
- if (regno >= 0)
- err = mark_chain_precision(env, regno);
- else
- err = mark_stack_arg_precision(env, arg_idx_from_argno(size_argno));
- }
+ if (access_type & BPF_READ)
+ err = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),
+ BPF_READ, zero_size_allowed, meta, NULL);
+ if (!err && access_type & BPF_WRITE)
+ err = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),
+ BPF_WRITE, zero_size_allowed, meta, NULL);
+ if (err && failure)
+ *failure = BPF_MEM_SIZE_FAIL_MEMORY;
+
+ if (!err)
+ err = mark_arg_precision(env, size_argno);
+
+ return err;
+
+size_error:
+ if (failure)
+ *failure = BPF_MEM_SIZE_FAIL_SIZE;
return err;
}
static int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
- argno_t argno, u32 mem_size)
+ argno_t argno, u32 mem_size, enum bpf_access_type access_type,
+ struct bpf_call_arg_meta *meta, bool *known_memory)
{
- bool may_be_null = type_may_be_null(reg->type);
- struct bpf_reg_state saved_reg;
- int err;
+ int size, err = 0;
if (bpf_register_is_null(reg))
- return 0;
+ return mark_arg_precision(env, argno);
+ if (known_memory)
+ *known_memory = true;
if (mem_size > S32_MAX) {
verbose(env, "%s memory size %u is too large\n",
@@ -6887,48 +7202,83 @@ static int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg
return -EACCES;
}
- /* Assuming that the register contains a value check if the memory
- * access is safe. Temporarily save and restore the register's state as
- * the conversion shouldn't be visible to a caller.
+ /*
+ * Only a global subprog (meta == NULL) may read poisoned stack slots:
+ * its static stack liveness proved the callee body skips them.
*/
- if (may_be_null) {
- saved_reg = *reg;
- mark_ptr_not_null_reg(reg);
+ size = (!meta && base_type(reg->type) == PTR_TO_STACK) ? -(int)mem_size : mem_size;
+
+ if (access_type & BPF_READ)
+ err = check_helper_mem_access(env, reg, argno, size, BPF_READ, true, meta,
+ known_memory);
+ if (!err && (access_type & BPF_WRITE))
+ err = check_helper_mem_access(env, reg, argno, size, BPF_WRITE, true, meta,
+ known_memory);
+
+ return err;
+}
+
+static int process_const_alloc_mem_size(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
+ argno_t argno, struct ret_mem_desc *ret_mem)
+{
+ int regno = reg_from_argno(argno);
+ int err;
+
+ if (ret_mem->found) {
+ verifier_bug(env, "only one allocation size argument permitted");
+ return -EFAULT;
}
- int size = base_type(reg->type) == PTR_TO_STACK ? -(int)mem_size : mem_size;
+ if (!tnum_is_const(reg->var_off)) {
+ verbose(env, "%s is not a const\n", reg_arg_name(env, argno));
+ return -EINVAL;
+ }
- err = check_helper_mem_access(env, reg, argno, size, BPF_READ, true, NULL);
- err = err ?: check_helper_mem_access(env, reg, argno, size, BPF_WRITE, true, NULL);
+ if (reg->var_off.value > U32_MAX) {
+ verbose(env, "%s allocation size exceeds u32 max\n", reg_arg_name(env, argno));
+ return -EINVAL;
+ }
- if (may_be_null)
- *reg = saved_reg;
+ if (regno >= 0)
+ err = mark_chain_precision(env, regno);
+ else
+ err = mark_stack_arg_precision(env, arg_idx_from_argno(argno));
+ if (err)
+ return err;
- return err;
+ ret_mem->size = reg->var_off.value;
+ ret_mem->found = true;
+
+ return 0;
}
-static int check_kfunc_mem_size_reg(struct bpf_verifier_env *env, struct bpf_reg_state *mem_reg,
- struct bpf_reg_state *size_reg, argno_t mem_argno, argno_t size_argno)
+static int process_const_arg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
+ argno_t argno, struct bpf_call_arg_meta *meta)
{
- bool may_be_null = type_may_be_null(mem_reg->type);
- struct bpf_reg_state saved_reg;
- struct bpf_call_arg_meta meta;
+ int regno = reg_from_argno(argno);
int err;
- memset(&meta, 0, sizeof(meta));
+ if (meta->arg_constant.found) {
+ verifier_bug(env, "only one constant argument permitted");
+ return -EFAULT;
+ }
- if (may_be_null) {
- saved_reg = *mem_reg;
- mark_ptr_not_null_reg(mem_reg);
+ if (!tnum_is_const(reg->var_off)) {
+ verbose(env, "%s must be a known constant\n", reg_arg_name(env, argno));
+ return -EINVAL;
}
- err = check_mem_size_reg(env, mem_reg, size_reg, mem_argno, size_argno, BPF_READ, true, &meta);
- err = err ?: check_mem_size_reg(env, mem_reg, size_reg, mem_argno, size_argno, BPF_WRITE, true, &meta);
+ if (regno >= 0)
+ err = mark_chain_precision(env, regno);
+ else
+ err = mark_stack_arg_precision(env, arg_idx_from_argno(argno));
+ if (err < 0)
+ return err;
- if (may_be_null)
- *mem_reg = saved_reg;
+ meta->arg_constant.found = true;
+ meta->arg_constant.value = reg->var_off.value;
- return err;
+ return 0;
}
enum {
@@ -6964,6 +7314,7 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
bool is_lock = flags & PROCESS_SPIN_LOCK, is_res_lock = flags & PROCESS_RES_LOCK;
const char *lock_str = is_res_lock ? "bpf_res_spin" : "bpf_spin";
struct bpf_verifier_state *cur = env->cur_state;
+ struct bpf_reference_state *lock;
bool is_const = tnum_is_const(reg->var_off);
bool is_irq = flags & PROCESS_LOCK_IRQ;
u64 val = reg->var_off.value;
@@ -7013,14 +7364,25 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
ptr = btf;
if (!is_res_lock && cur->active_locks) {
- if (find_lock_state(env->cur_state, REF_TYPE_LOCK, 0, NULL)) {
+ lock = find_lock_state(cur, REF_TYPE_LOCK, 0, NULL);
+ if (lock) {
verbose(env,
"Locking two bpf_spin_locks are not allowed\n");
+ bpf_diag_lock(
+ env, env->insn_idx, "nested spin lock",
+ "This path already holds a bpf_spin_lock. The verifier allows only one regular BPF spin lock at a time.",
+ "Unlock the current bpf_spin_lock before taking another one.", lock);
return -EINVAL;
}
} else if (is_res_lock && cur->active_locks) {
- if (find_lock_state(env->cur_state, REF_TYPE_RES_LOCK | REF_TYPE_RES_LOCK_IRQ, reg->id, ptr)) {
+ lock = find_lock_state(cur, REF_TYPE_RES_LOCK | REF_TYPE_RES_LOCK_IRQ,
+ reg->id, ptr);
+ if (lock) {
verbose(env, "Acquiring the same lock again, AA deadlock detected\n");
+ bpf_diag_lock(
+ env, env->insn_idx, "recursive resource spin lock",
+ "This path already holds the same resource spin lock. Taking it again would deadlock.",
+ "Avoid reacquiring the same resource spin lock before it is unlocked.", lock);
return -EINVAL;
}
}
@@ -7047,6 +7409,10 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
if (!cur->active_locks) {
verbose(env, "%s_unlock without taking a lock\n", lock_str);
+ bpf_diag_res(
+ env, env->insn_idx, "unlock without lock",
+ "This unlock operation has no matching active lock on the current path.",
+ "Take the matching lock before this unlock, or remove the unmatched unlock path.");
return -EINVAL;
}
@@ -7056,20 +7422,45 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
type = REF_TYPE_RES_LOCK;
else
type = REF_TYPE_LOCK;
- if (!find_lock_state(cur, type, reg->id, ptr)) {
+
+ lock = find_lock_state(cur, type, reg->id, ptr);
+ if (!lock) {
verbose(env, "%s_unlock of different lock\n", lock_str);
+ lock = find_lock_state(cur, REF_TYPE_LOCK_MASK, cur->active_lock_id,
+ cur->active_lock_ptr);
+ bpf_diag_lock(
+ env, env->insn_idx, "unlock of a different lock",
+ "This unlock does not match any active lock with the same tracked identity on the current path.",
+ "Unlock the same lock object that was most recently acquired.", lock);
return -EINVAL;
}
if (reg->id != cur->active_lock_id || ptr != cur->active_lock_ptr) {
verbose(env, "%s_unlock cannot be out of order\n", lock_str);
+ lock = find_lock_state(cur, REF_TYPE_LOCK_MASK, cur->active_lock_id,
+ cur->active_lock_ptr);
+ bpf_diag_lock(
+ env, env->insn_idx, "unlock out of order",
+ "Locks must be released in last-in, first-out order, but this unlock does not match the currently active lock.",
+ "Release nested locks in the reverse order they were acquired.", lock);
return -EINVAL;
}
- if (release_lock_state(cur, type, reg->id, ptr)) {
+ if (release_lock_state(env, type, reg->id, ptr)) {
verbose(env, "%s_unlock of different lock\n", lock_str);
+ bpf_diag_lock(
+ env, env->insn_idx, "unlock of a different lock",
+ "The verifier could not release a lock state matching this unlock operation.",
+ "Pass the same lock object and lock kind that were used for the matching lock operation.",
+ lock);
return -EINVAL;
}
-
+ /*
+ * Invalidate non-owning refs before RCU demotion clears their
+ * NON_OWN_REF flag.
+ */
invalidate_non_owning_refs(env);
+
+ if (!in_rcu_cs(env))
+ invalidate_rcu_protected_refs(env);
}
return 0;
}
@@ -7138,18 +7529,6 @@ static int process_timer_func(struct bpf_verifier_env *env, struct bpf_reg_state
return check_map_field_pointer(env, reg, argno, BPF_TIMER, map);
}
-static int process_timer_helper(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,
- struct bpf_call_arg_meta *meta)
-{
- return process_timer_func(env, reg, argno, &meta->map);
-}
-
-static int process_timer_kfunc(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,
- struct bpf_kfunc_call_arg_meta *meta)
-{
- return process_timer_func(env, reg, argno, &meta->map);
-}
-
static int process_kptr_func(struct bpf_verifier_env *env, int regno,
struct bpf_call_arg_meta *meta)
{
@@ -7198,6 +7577,12 @@ static int process_kptr_func(struct bpf_verifier_env *env, int regno,
return 0;
}
+static void bpf_diag_call_arg(struct bpf_verifier_env *env, u32 insn_idx, argno_t argno,
+ const char *call_name, const char *reason, const char *suggestion);
+__printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u32 insn_idx,
+ argno_t argno, const char *call_name,
+ const char *suggestion, const char *fmt, ...);
+
/*
* Validate dynptr arguments for helper, kfunc and subprog.
*
@@ -7222,7 +7607,8 @@ static int process_kptr_func(struct bpf_verifier_env *env, int regno,
* and checked dynamically during runtime.
*/
static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
- argno_t argno, int insn_idx, enum bpf_arg_type arg_type,
+ argno_t argno, int insn_idx, const char *call_name,
+ enum bpf_arg_type arg_type,
struct ref_obj_desc *ref_obj, struct bpf_dynptr_desc *dynptr)
{
int spi, err = 0;
@@ -7231,6 +7617,11 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
verbose(env,
"%s expected pointer to stack or const struct bpf_dynptr\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, call_name,
+ "Pass the address of a stack dynptr object, or use a const dynptr pointer returned by the verifier-supported path.",
+ "a dynptr argument must be a pointer to a dynptr stack slot or a verifier-provided const struct bpf_dynptr, but %s is %s",
+ reg_arg_name(env, argno), bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
@@ -7253,6 +7644,10 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
if (!is_dynptr_reg_valid_uninit(env, reg)) {
verbose(env, "Dynptr has to be an uninitialized dynptr\n");
+ bpf_diag_res(
+ env, insn_idx, "dynptr is already initialized",
+ "This kfunc constructs a dynptr and requires an uninitialized dynptr stack slot, but the selected slot already holds dynptr state.",
+ "Use a fresh stack dynptr slot, or release/destroy the existing dynptr before reusing the slot.");
return -EINVAL;
}
@@ -7269,21 +7664,35 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
/* For the reg->type == PTR_TO_STACK case, bpf_dynptr is never const */
if (reg->type == CONST_PTR_TO_DYNPTR && (arg_type & OBJ_RELEASE)) {
verbose(env, "CONST_PTR_TO_DYNPTR cannot be released\n");
+ bpf_diag_res(
+ env, insn_idx, "const dynptr release",
+ "This release operation was given a const dynptr. Const dynptr values are verifier-provided views and cannot be released by the program.",
+ "Release only mutable dynptrs that the program initialized or reserved.");
return -EINVAL;
}
if (!is_dynptr_reg_valid_init(env, reg)) {
verbose(env, "Expected an initialized dynptr as %s\n",
reg_arg_name(env, argno));
+ bpf_diag_res(
+ env, insn_idx, "uninitialized dynptr use",
+ "This operation requires an initialized dynptr, but the stack slot does not currently hold a valid dynptr on this path.",
+ "Initialize the dynptr on every path before this call, and avoid overwriting or releasing it before this use.");
return -EINVAL;
}
/* Fold modifiers (in this case, OBJ_RELEASE) when checking expected type */
if (!is_dynptr_type_expected(env, reg, arg_type & ~OBJ_RELEASE)) {
- verbose(env,
- "Expected a dynptr of type %s as %s\n",
- dynptr_type_str(arg_to_dynptr_type(arg_type)),
- reg_arg_name(env, argno));
+ enum bpf_dynptr_type expected_type = arg_to_dynptr_type(arg_type);
+ enum bpf_dynptr_type actual_type = dynptr_reg_type(env, reg);
+
+ verbose(env, "Expected a dynptr of type %s as %s\n",
+ dynptr_type_str(expected_type), reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, call_name,
+ "Use a dynptr constructor that matches this operation, or call an operation that accepts the dynptr's current type.",
+ "the dynptr is initialized with backing object type %s, but this operation expects dynptr type %s",
+ dynptr_type_str(actual_type), dynptr_type_str(expected_type));
return -EINVAL;
}
@@ -7294,10 +7703,6 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
if (spi < 0)
return spi;
- /*
- * For CONST_PTR_TO_DYNPTR, reg is already scratched by check_reg_arg
- * in check_helper_call and mark_btf_func_reg_size in check_kfunc_call.
- */
mark_stack_slots_scratched(env, spi, BPF_DYNPTR_NR_SLOTS);
reg = &state->stack[spi].spilled_ptr;
@@ -7312,23 +7717,22 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
return err;
}
-static bool is_iter_kfunc(struct bpf_kfunc_call_arg_meta *meta)
+static bool is_iter_kfunc(struct bpf_call_arg_meta *meta)
{
return meta->kfunc_flags & (KF_ITER_NEW | KF_ITER_NEXT | KF_ITER_DESTROY);
}
-static bool is_iter_new_kfunc(struct bpf_kfunc_call_arg_meta *meta)
+static bool is_iter_new_kfunc(struct bpf_call_arg_meta *meta)
{
return meta->kfunc_flags & KF_ITER_NEW;
}
-
-static bool is_iter_destroy_kfunc(struct bpf_kfunc_call_arg_meta *meta)
+static bool is_iter_destroy_kfunc(struct bpf_call_arg_meta *meta)
{
return meta->kfunc_flags & KF_ITER_DESTROY;
}
-static bool is_kfunc_arg_iter(struct bpf_kfunc_call_arg_meta *meta, int arg_idx,
+static bool is_kfunc_arg_iter(struct bpf_call_arg_meta *meta, int arg_idx,
const struct btf_param *arg)
{
/* btf_check_iter_kfuncs() guarantees that first argument of any iter
@@ -7342,7 +7746,7 @@ static bool is_kfunc_arg_iter(struct bpf_kfunc_call_arg_meta *meta, int arg_idx,
}
static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno, int insn_idx,
- struct bpf_kfunc_call_arg_meta *meta)
+ struct bpf_call_arg_meta *meta)
{
struct bpf_func_state *state = bpf_func(env, reg);
const struct btf_type *t;
@@ -7352,6 +7756,11 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
if (reg->type != PTR_TO_STACK) {
verbose(env, "%s expected pointer to an iterator on stack\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, meta->func_name,
+ "Pass the address of a stack iterator object for iterator new, next, and destroy calls.",
+ "iterator state must live in verifier-tracked stack memory, but %s is %s",
+ reg_arg_name(env, argno), bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
@@ -7365,6 +7774,10 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
if (btf_id < 0) {
verbose(env, "expected valid iter pointer as %s\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg(
+ env, insn_idx, argno, meta->func_name,
+ "the kfunc expects a recognized iterator state pointer, but this argument does not match a valid iterator type",
+ "Pass the exact iterator state type expected by this kfunc.");
return -EINVAL;
}
t = btf_type_by_id(meta->btf, btf_id);
@@ -7375,6 +7788,10 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
if (!is_iter_reg_valid_uninit(env, reg, nr_slots)) {
verbose(env, "expected uninitialized iter_%s as %s\n",
iter_type_str(meta->btf, btf_id), reg_arg_name(env, argno));
+ bpf_diag_res(
+ env, insn_idx, "iterator is already initialized",
+ "Iterator creation requires an uninitialized iterator stack object, but this stack range already contains iterator state.",
+ "Use a fresh iterator stack slot, or destroy the existing iterator before reusing the slot.");
return -EINVAL;
}
@@ -7399,9 +7816,16 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
case -EINVAL:
verbose(env, "expected an initialized iter_%s as %s\n",
iter_type_str(meta->btf, btf_id), reg_arg_name(env, argno));
+ bpf_diag_res(
+ env, insn_idx, "uninitialized iterator use",
+ "This iterator operation requires an initialized iterator state object, but the stack range does not contain a live iterator on this path.",
+ "Call the matching iterator new kfunc on every path before calling next or destroy, and do not destroy the iterator before this use.");
return err;
case -EPROTO:
verbose(env, "expected an RCU CS when using %s\n", meta->func_name);
+ bpf_diag_ctx_required(
+ env, insn_idx, meta->func_name, BPF_DIAG_CONTEXT_RCU,
+ "Wrap iterator use in bpf_rcu_read_lock() and bpf_rcu_read_unlock(), keeping all exit paths balanced.");
return err;
default:
return err;
@@ -7509,7 +7933,7 @@ static int widen_imprecise_scalars(struct bpf_verifier_env *env,
}
static struct bpf_reg_state *get_iter_from_state(struct bpf_verifier_state *cur_st,
- struct bpf_kfunc_call_arg_meta *meta)
+ struct bpf_call_arg_meta *meta)
{
int iter_frameno = meta->iter.frameno;
int iter_spi = meta->iter.spi;
@@ -7596,7 +8020,7 @@ static struct bpf_reg_state *get_iter_from_state(struct bpf_verifier_state *cur_
* bpf_iter_num_destroy(&it);
*/
static int process_iter_next_call(struct bpf_verifier_env *env, int insn_idx,
- struct bpf_kfunc_call_arg_meta *meta)
+ struct bpf_call_arg_meta *meta)
{
struct bpf_verifier_state *cur_st = env->cur_state, *queued_st, *prev_st;
struct bpf_func_state *cur_fr = cur_st->frame[cur_st->curframe], *queued_fr;
@@ -7652,13 +8076,18 @@ static int process_iter_next_call(struct bpf_verifier_env *env, int insn_idx,
static bool arg_type_is_mem_size(enum bpf_arg_type type)
{
- return type == ARG_CONST_SIZE ||
- type == ARG_CONST_SIZE_OR_ZERO;
+ return type == ARG_MEM_SIZE || type == ARG_MEM_SIZE_OR_ZERO;
}
static bool arg_type_is_raw_mem(enum bpf_arg_type type)
{
- return base_type(type) == ARG_PTR_TO_MEM &&
+ /*
+ * A map value output buffer (e.g. bpf_map_pop_elem) is also a raw
+ * (uninitialized) memory argument, and like ARG_PTR_TO_MEM it may be
+ * passed as a PTR_TO_STACK that reaches check_stack_range_initialized().
+ */
+ return (base_type(type) == ARG_PTR_TO_MEM ||
+ base_type(type) == ARG_PTR_TO_MAP_VALUE) &&
type & MEM_UNINIT;
}
@@ -7793,9 +8222,10 @@ static const struct bpf_reg_types dynptr_types = {
static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
[ARG_PTR_TO_MAP_KEY] = &mem_types,
[ARG_PTR_TO_MAP_VALUE] = &mem_types,
- [ARG_CONST_SIZE] = &scalar_types,
- [ARG_CONST_SIZE_OR_ZERO] = &scalar_types,
+ [ARG_MEM_SIZE] = &scalar_types,
+ [ARG_MEM_SIZE_OR_ZERO] = &scalar_types,
[ARG_CONST_ALLOC_SIZE_OR_ZERO] = &scalar_types,
+ [ARG_SCALAR] = &scalar_types,
[ARG_CONST_MAP_PTR] = &const_map_ptr_types,
[ARG_PTR_TO_CTX] = &context_types,
[ARG_PTR_TO_SOCK_COMMON] = &sock_types,
@@ -7816,13 +8246,70 @@ static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
[ARG_PTR_TO_DYNPTR] = &dynptr_types,
};
+static void bpf_diag_call_arg(struct bpf_verifier_env *env, u32 insn_idx, argno_t argno,
+ const char *call_name, const char *reason,
+ const char *suggestion)
+{
+ int arg = arg_from_argno(argno);
+ int regno = reg_from_argno(argno);
+ int stack_slot = -1;
+
+ if (arg < 0 && regno >= BPF_REG_1 && regno <= BPF_REG_5)
+ arg = regno;
+ if (arg > MAX_BPF_FUNC_REG_ARGS)
+ stack_slot = arg - MAX_BPF_FUNC_REG_ARGS - 1;
+
+ bpf_diag_call_type(env, insn_idx, arg, regno, stack_slot,
+ call_name && *call_name ? call_name : "call",
+ reg_arg_name(env, argno), reason, suggestion);
+}
+
+static const char *bpf_diag_arg_name(struct bpf_verifier_env *env, argno_t argno)
+{
+ return bpf_diag_fmt(env, "%s", reg_arg_name(env, argno));
+}
+
+__printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u32 insn_idx,
+ argno_t argno, const char *call_name,
+ const char *suggestion, const char *fmt, ...)
+{
+ const char *reason;
+ va_list args;
+
+ va_start(args, fmt);
+ reason = bpf_diag_vfmt(env, fmt, args);
+ va_end(args);
+
+ bpf_diag_call_arg(env, insn_idx, argno, call_name, reason, suggestion);
+}
+
+static const char *bpf_diag_expected_reg_types(struct bpf_verifier_env *env,
+ const enum bpf_reg_type *types, int count)
+{
+ size_t len = 0, size = 1;
+ char *buf;
+ int i;
+
+ for (i = 0; i < count; i++)
+ size += strlen(reg_type_str(env, types[i])) + (i ? 2 : 0);
+
+ buf = bpf_diag_fmt_buf(env, size);
+ if (!buf)
+ return "";
+
+ for (i = 0; i < count; i++)
+ len += scnprintf(buf + len, size - len, "%s%s", i ? ", " : "",
+ reg_type_str(env, types[i]));
+ return buf;
+}
+
static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,
- enum bpf_arg_type arg_type,
- const u32 *arg_btf_id,
- struct bpf_call_arg_meta *meta)
+ enum bpf_arg_type arg_type, const u32 *arg_btf_id,
+ struct bpf_call_arg_meta *meta, const char *call_name)
{
enum bpf_reg_type expected, type = reg->type;
const struct bpf_reg_types *compatible;
+ const char *actual, *accepted;
int i, j, err;
compatible = compatible_reg_types[base_type(arg_type)];
@@ -7869,6 +8356,12 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re
for (j = 0; j + 1 < i; j++)
verbose(env, "%s, ", reg_type_str(env, compatible->types[j]));
verbose(env, "%s\n", reg_type_str(env, compatible->types[j]));
+ actual = bpf_diag_fmt(env, "%s", reg_type_str(env, reg->type));
+ accepted = bpf_diag_expected_reg_types(env, compatible->types, i);
+ bpf_diag_call_arg_fmt(env, env->insn_idx, argno, call_name,
+ "Pass a value with one of the accepted pointer or scalar types for this call.",
+ "it has type %s, but this argument accepts %s",
+ actual, accepted);
return -EACCES;
found:
@@ -7905,6 +8398,10 @@ found:
(!type_may_be_null(arg_type) || arg_type_is_release(arg_type))) {
verbose(env, "Possibly NULL pointer passed to helper %s\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg(
+ env, env->insn_idx, argno, call_name,
+ "the pointer may be NULL, but this call requires a non-NULL pointer",
+ "Add a NULL check and make the call only on the non-NULL path.");
return -EACCES;
}
@@ -7933,7 +8430,7 @@ found:
if (!btf_struct_ids_match(&env->log, reg->btf, reg->btf_id,
reg->var_off.value, btf_vmlinux, *arg_btf_id,
- strict_type_match)) {
+ strict_type_match, !type_is_alloc(reg->type))) {
verbose(env, "%s is of type %s but %s is expected\n",
reg_arg_name(env, argno),
btf_type_name(reg->btf, reg->btf_id),
@@ -7990,9 +8487,10 @@ reg_find_field_offset(const struct bpf_reg_state *reg, s32 off, u32 fields)
return field;
}
-static int check_func_arg_reg_off(struct bpf_verifier_env *env,
- const struct bpf_reg_state *reg, argno_t argno,
- enum bpf_arg_type arg_type)
+static int __check_func_arg_reg_off(struct bpf_verifier_env *env,
+ const struct bpf_reg_state *reg, argno_t argno,
+ enum bpf_arg_type arg_type,
+ bool btf_id_fixed_off_ok)
{
u32 type = reg->type;
@@ -8049,12 +8547,11 @@ static int check_func_arg_reg_off(struct bpf_verifier_env *env,
case PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF | MEM_RCU:
/* When referenced PTR_TO_BTF_ID is passed to release function,
* its fixed offset must be 0. In the other cases, fixed offset
- * can be non-zero. This was already checked above. So pass
- * fixed_off_ok as true to allow fixed offset for all other
- * cases. var_off always must be 0 for PTR_TO_BTF_ID, hence we
- * still need to do checks instead of returning.
+ * can be non-zero unless the caller requires otherwise.
+ * var_off always must be 0 for PTR_TO_BTF_ID, hence we still
+ * need to do checks instead of returning.
*/
- return __check_ptr_off_reg(env, reg, argno, true);
+ return __check_ptr_off_reg(env, reg, argno, btf_id_fixed_off_ok);
case PTR_TO_CTX:
/*
* Allow fixed and variable offsets for syscall context, but
@@ -8070,6 +8567,13 @@ static int check_func_arg_reg_off(struct bpf_verifier_env *env,
}
}
+static int check_func_arg_reg_off(struct bpf_verifier_env *env,
+ const struct bpf_reg_state *reg, argno_t argno,
+ enum bpf_arg_type arg_type)
+{
+ return __check_func_arg_reg_off(env, reg, argno, arg_type, true);
+}
+
static int check_arg_const_str(struct bpf_verifier_env *env,
struct bpf_reg_state *reg, argno_t argno)
{
@@ -8088,6 +8592,12 @@ static int check_arg_const_str(struct bpf_verifier_env *env,
return -EACCES;
}
+ if (map->map_type == BPF_MAP_TYPE_PERCPU_ARRAY) {
+ verbose(env, "%s points to percpu_array map which cannot be used as const string\n",
+ reg_arg_name(env, argno));
+ return -EACCES;
+ }
+
if (!bpf_map_is_rdonly(map)) {
verbose(env, "%s does not point to a readonly map'\n", reg_arg_name(env, argno));
return -EACCES;
@@ -8187,15 +8697,53 @@ static int get_constant_map_key(struct bpf_verifier_env *env,
static bool can_elide_value_nullness(const struct bpf_map *map);
+static int process_map_ptr_arg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
+ argno_t argno, struct bpf_call_arg_meta *meta)
+{
+ /* Use map_uid (which is unique id of inner map) to reject:
+ * inner_map1 = bpf_map_lookup_elem(outer_map, key1)
+ * inner_map2 = bpf_map_lookup_elem(outer_map, key2)
+ * if (inner_map1 && inner_map2) {
+ * timer = bpf_map_lookup_elem(inner_map1);
+ * if (timer)
+ * // mismatch would have been allowed
+ * bpf_timer_init(timer, inner_map2);
+ * }
+ *
+ * Comparing map_ptr is enough to distinguish normal and outer maps.
+ */
+ if (meta->map.ptr &&
+ (meta->map.ptr != reg->map_ptr || meta->map.uid != reg->map_uid)) {
+ argno_t obj_argno = argno_from_reg(reg_from_argno(argno) - 1);
+ struct btf_record *rec = meta->map.ptr->record;
+ const char *obj_name = "workqueue";
+
+ if (rec->timer_off >= 0)
+ obj_name = "timer";
+ else if (rec->task_work_off >= 0)
+ obj_name = "bpf_task_work";
+
+ verbose(env, "%s pointer in %s map_uid=%d ",
+ obj_name, reg_arg_name(env, obj_argno), meta->map.uid);
+ verbose(env, "doesn't match map pointer in %s map_uid=%d\n",
+ reg_arg_name(env, argno), reg->map_uid);
+ return -EINVAL;
+ }
+
+ meta->map.ptr = reg->map_ptr;
+ meta->map.uid = reg->map_uid;
+ return 0;
+}
+
static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
struct bpf_call_arg_meta *meta,
- const struct bpf_func_proto *fn,
int insn_idx)
{
+ const struct bpf_func_proto *fn = meta->fn;
u32 regno = BPF_REG_1 + arg;
struct bpf_reg_state *reg = reg_state(env, regno);
enum bpf_arg_type arg_type = fn->arg_type[arg];
- argno_t argno = argno_from_arg(arg + 1);
+ argno_t argno = argno_from_reg(regno);
enum bpf_reg_type type = reg->type;
u32 *arg_btf_id = NULL;
u32 key_size;
@@ -8218,7 +8766,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
}
if (type_is_pkt_pointer(type) &&
- !may_access_direct_pkt_data(env, meta, BPF_READ)) {
+ !may_access_direct_pkt_data(env, fn, BPF_READ)) {
verbose(env, "helper access to the packet is not allowed\n");
return -EACCES;
}
@@ -8229,22 +8777,27 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
return err;
}
- if (bpf_register_is_null(reg) && type_may_be_null(arg_type))
+ if (bpf_register_is_null(reg) && type_may_be_null(arg_type)) {
/* A NULL register has a SCALAR_VALUE type, so skip
* type checking.
*/
+ err = mark_chain_precision(env, regno);
+ if (err)
+ return err;
goto skip_type_check;
+ }
/* arg_btf_id and arg_size are in a union. */
if (base_type(arg_type) == ARG_PTR_TO_BTF_ID ||
base_type(arg_type) == ARG_PTR_TO_SPIN_LOCK)
arg_btf_id = fn->arg_btf_id[arg];
- err = check_reg_type(env, reg, argno_from_reg(regno), arg_type, arg_btf_id, meta);
+ err = check_reg_type(env, reg, argno, arg_type, arg_btf_id, meta,
+ func_id_name(meta->func_id));
if (err)
return err;
- err = check_func_arg_reg_off(env, reg, argno_from_reg(regno), arg_type);
+ err = check_func_arg_reg_off(env, reg, argno, arg_type);
if (err)
return err;
@@ -8253,6 +8806,10 @@ skip_type_check:
!reg_is_referenced(env, reg) && !bpf_register_is_null(reg)) {
verbose(env, "release helper %s expects referenced PTR_TO_BTF_ID passed to %s\n",
func_id_name(meta->func_id), reg_arg_name(env, argno));
+ bpf_diag_call_arg(
+ env, insn_idx, argno, func_id_name(meta->func_id),
+ "release helpers require a value that owns a live resource returned by a matching acquire helper",
+ "Pass the resource-owning pointer returned by the matching acquire helper, and avoid calling the release helper after ownership has already been transferred or released.");
return -EINVAL;
}
@@ -8262,29 +8819,9 @@ skip_type_check:
switch (base_type(arg_type)) {
case ARG_CONST_MAP_PTR:
/* bpf_map_xxx(map_ptr) call: remember that map_ptr */
- if (meta->map.ptr) {
- /* Use map_uid (which is unique id of inner map) to reject:
- * inner_map1 = bpf_map_lookup_elem(outer_map, key1)
- * inner_map2 = bpf_map_lookup_elem(outer_map, key2)
- * if (inner_map1 && inner_map2) {
- * timer = bpf_map_lookup_elem(inner_map1);
- * if (timer)
- * // mismatch would have been allowed
- * bpf_timer_init(timer, inner_map2);
- * }
- *
- * Comparing map_ptr is enough to distinguish normal and outer maps.
- */
- if (meta->map.ptr != reg->map_ptr ||
- meta->map.uid != reg->map_uid) {
- verbose(env,
- "timer pointer in R1 map_uid=%d doesn't match map pointer in R2 map_uid=%d\n",
- meta->map.uid, reg->map_uid);
- return -EINVAL;
- }
- }
- meta->map.ptr = reg->map_ptr;
- meta->map.uid = reg->map_uid;
+ err = process_map_ptr_arg(env, reg, argno, meta);
+ if (err)
+ return err;
break;
case ARG_PTR_TO_MAP_KEY:
/* bpf_map_xxx(..., map_ptr, ..., key) call:
@@ -8301,7 +8838,8 @@ skip_type_check:
return -EFAULT;
}
key_size = meta->map.ptr->key_size;
- err = check_helper_mem_access(env, reg, argno_from_reg(regno), key_size, BPF_READ, false, NULL);
+ err = check_helper_mem_access(env, reg, argno, key_size, BPF_READ, false, NULL,
+ NULL);
if (err)
return err;
if (can_elide_value_nullness(meta->map.ptr)) {
@@ -8327,10 +8865,18 @@ skip_type_check:
verifier_bug(env, "invalid map_ptr to access map->value");
return -EFAULT;
}
- meta->raw_mode = arg_type & MEM_UNINIT;
- err = check_helper_mem_access(env, reg, argno_from_reg(regno), meta->map.ptr->value_size,
+
+ /*
+ * Disable raw mode for bpf_map_peek_elem() on a bloom filter. The helper reads
+ * the value buffer as an input rather than filling it.
+ */
+ if (meta->func_id == BPF_FUNC_map_peek_elem &&
+ meta->map.ptr->map_type == BPF_MAP_TYPE_BLOOM_FILTER)
+ meta->arg_raw_mem.regno = 0;
+
+ err = check_helper_mem_access(env, reg, argno, meta->map.ptr->value_size,
arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ,
- false, meta);
+ false, meta, NULL);
break;
case ARG_PTR_TO_PERCPU_BTF_ID:
if (!reg->btf_id) {
@@ -8346,11 +8892,11 @@ skip_type_check:
return -EACCES;
}
if (meta->func_id == BPF_FUNC_spin_lock) {
- err = process_spin_lock(env, reg, argno_from_reg(regno), PROCESS_SPIN_LOCK);
+ err = process_spin_lock(env, reg, argno, PROCESS_SPIN_LOCK);
if (err)
return err;
} else if (meta->func_id == BPF_FUNC_spin_unlock) {
- err = process_spin_lock(env, reg, argno_from_reg(regno), 0);
+ err = process_spin_lock(env, reg, argno, 0);
if (err)
return err;
} else {
@@ -8359,7 +8905,7 @@ skip_type_check:
}
break;
case ARG_PTR_TO_TIMER:
- err = process_timer_helper(env, reg, argno_from_reg(regno), meta);
+ err = process_timer_func(env, reg, argno, &meta->map);
if (err)
return err;
break;
@@ -8370,51 +8916,41 @@ skip_type_check:
/* The access to this pointer is only checked when we hit the
* next is_mem_size argument below.
*/
- meta->raw_mode = arg_type & MEM_UNINIT;
if (arg_type & MEM_FIXED_SIZE) {
- err = check_helper_mem_access(env, reg, argno_from_reg(regno), fn->arg_size[arg],
- arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ,
- false, meta);
+ err = check_mem_reg(env, reg, argno_from_reg(regno), fn->arg_size[arg],
+ arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ, meta, NULL);
if (err)
return err;
if (arg_type & MEM_ALIGNED)
err = check_ptr_alignment(env, reg, 0, fn->arg_size[arg], true);
}
break;
- case ARG_CONST_SIZE:
- err = check_mem_size_reg(env, reg_state(env, regno - 1), reg, argno_from_reg(regno - 1),
- argno_from_reg(regno),
- fn->arg_type[arg - 1] & MEM_WRITE ?
- BPF_WRITE : BPF_READ,
- false, meta);
+ case ARG_MEM_SIZE:
+ err = check_mem_size_reg(env, reg_state(env, regno - 1), reg,
+ argno_from_reg(regno - 1), argno,
+ fn->arg_type[arg - 1] & MEM_WRITE ? BPF_WRITE : BPF_READ,
+ false, meta, NULL);
break;
- case ARG_CONST_SIZE_OR_ZERO:
- err = check_mem_size_reg(env, reg_state(env, regno - 1), reg, argno_from_reg(regno - 1),
- argno_from_reg(regno),
- fn->arg_type[arg - 1] & MEM_WRITE ?
- BPF_WRITE : BPF_READ,
- true, meta);
+ case ARG_MEM_SIZE_OR_ZERO:
+ err = check_mem_size_reg(env, reg_state(env, regno - 1), reg,
+ argno_from_reg(regno - 1), argno,
+ fn->arg_type[arg - 1] & MEM_WRITE ? BPF_WRITE : BPF_READ,
+ true, meta, NULL);
break;
case ARG_PTR_TO_DYNPTR:
- err = process_dynptr_func(env, reg, argno_from_reg(regno), insn_idx, arg_type, &meta->ref_obj,
- &meta->dynptr);
+ err = process_dynptr_func(env, reg, argno, insn_idx, func_id_name(meta->func_id),
+ arg_type, &meta->ref_obj, &meta->dynptr);
if (err)
return err;
break;
case ARG_CONST_ALLOC_SIZE_OR_ZERO:
- if (!tnum_is_const(reg->var_off)) {
- verbose(env, "R%d is not a known constant'\n",
- regno);
- return -EACCES;
- }
- meta->mem_size = reg->var_off.value;
- err = mark_chain_precision(env, regno);
+ err = process_const_alloc_mem_size(env, reg, argno, &meta->ret_mem);
if (err)
return err;
break;
case ARG_PTR_TO_CONST_STR:
{
- err = check_arg_const_str(env, reg, argno_from_reg(regno));
+ err = check_arg_const_str(env, reg, argno);
if (err)
return err;
break;
@@ -8451,12 +8987,7 @@ static bool may_update_sockmap(struct bpf_verifier_env *env, int func_id)
if (func_id == BPF_FUNC_map_delete_elem)
return true;
break;
- case BPF_PROG_TYPE_SOCKET_FILTER:
- case BPF_PROG_TYPE_SCHED_CLS:
- case BPF_PROG_TYPE_SCHED_ACT:
- case BPF_PROG_TYPE_XDP:
case BPF_PROG_TYPE_SK_REUSEPORT:
- case BPF_PROG_TYPE_FLOW_DISSECTOR:
case BPF_PROG_TYPE_SK_LOOKUP:
return true;
default:
@@ -8726,26 +9257,21 @@ error:
return -EINVAL;
}
-static bool check_raw_mode_ok(const struct bpf_func_proto *fn)
+static bool check_raw_mode_ok(const struct bpf_func_proto *fn, struct bpf_call_arg_meta *meta)
{
- int count = 0;
+ int i;
- if (arg_type_is_raw_mem(fn->arg1_type))
- count++;
- if (arg_type_is_raw_mem(fn->arg2_type))
- count++;
- if (arg_type_is_raw_mem(fn->arg3_type))
- count++;
- if (arg_type_is_raw_mem(fn->arg4_type))
- count++;
- if (arg_type_is_raw_mem(fn->arg5_type))
- count++;
+ for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
+ if (fn->arg_type[i] == ARG_DONTCARE)
+ break;
+ if (!arg_type_is_raw_mem(fn->arg_type[i]))
+ continue;
+ if (meta->arg_raw_mem.regno)
+ return false;
+ meta->arg_raw_mem.regno = i + 1;
+ }
- /* We only support one arg being in raw mode at the moment,
- * which is sufficient for the helper functions we have
- * right now.
- */
- return count <= 1;
+ return true;
}
static bool check_args_pair_invalid(const struct bpf_func_proto *fn, int arg)
@@ -8786,6 +9312,8 @@ static bool check_btf_id_ok(const struct bpf_func_proto *fn)
int i;
for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
+ if (fn->arg_type[i] == ARG_DONTCARE)
+ break;
if (base_type(fn->arg_type[i]) == ARG_PTR_TO_BTF_ID)
return !!fn->arg_btf_id[i];
if (base_type(fn->arg_type[i]) == ARG_PTR_TO_SPIN_LOCK)
@@ -8807,6 +9335,8 @@ static bool check_mem_arg_rw_flag_ok(const struct bpf_func_proto *fn)
for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
enum bpf_arg_type arg_type = fn->arg_type[i];
+ if (arg_type == ARG_DONTCARE)
+ break;
if (base_type(arg_type) != ARG_PTR_TO_MEM)
continue;
if (!(arg_type & (MEM_WRITE | MEM_RDONLY)))
@@ -8823,6 +9353,8 @@ static bool check_proto_release_reg(const struct bpf_func_proto *fn, struct bpf_
for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
enum bpf_arg_type arg_type = fn->arg_type[i];
+ if (arg_type == ARG_DONTCARE)
+ break;
if (arg_type_is_release(arg_type)) {
if (meta->release_regno)
return false;
@@ -8835,7 +9367,7 @@ static bool check_proto_release_reg(const struct bpf_func_proto *fn, struct bpf_
static int check_func_proto(const struct bpf_func_proto *fn, struct bpf_call_arg_meta *meta)
{
- return check_raw_mode_ok(fn) &&
+ return check_raw_mode_ok(fn, meta) &&
check_arg_pair_ok(fn) &&
check_mem_arg_rw_flag_ok(fn) &&
check_proto_release_reg(fn, meta) &&
@@ -8854,8 +9386,10 @@ static void clear_all_pkt_pointers(struct bpf_verifier_env *env)
struct bpf_reg_state *reg;
bpf_for_each_reg_in_vstate(env->cur_state, state, reg, ({
- if (reg_is_pkt_pointer_any(reg) || reg_is_dynptr_slice_pkt(reg))
+ if (reg_is_pkt_pointer_any(reg) || reg_is_dynptr_slice_pkt(reg)) {
+ bpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_PKT_DATA_CHANGE);
mark_reg_invalid(env, reg);
+ }
}));
}
@@ -8885,7 +9419,7 @@ static void mark_pkt_end(struct bpf_verifier_state *vstate, int regn, bool range
reg->range = AT_PKT_END;
}
-static int release_reference_nomark(struct bpf_verifier_state *state, int id)
+static int __release_reference_nomark(struct bpf_verifier_state *state, int id)
{
int i;
@@ -8900,6 +9434,16 @@ static int release_reference_nomark(struct bpf_verifier_state *state, int id)
return -EINVAL;
}
+static int release_reference_nomark(struct bpf_verifier_env *env, int id)
+{
+ int err;
+
+ err = __release_reference_nomark(env->cur_state, id);
+ if (!err)
+ bpf_diag_record_ref_release(env, env->insn_idx, id);
+ return err;
+}
+
static int idstack_push(struct bpf_idmap *idmap, u32 id)
{
int i;
@@ -8942,8 +9486,10 @@ static int release_reference(struct bpf_verifier_env *env, int id)
if (err)
return err;
- if (find_reference_state(vstate, id))
- WARN_ON_ONCE(release_reference_nomark(vstate, id));
+ if (find_reference_state(vstate, id)) {
+ err = release_reference_nomark(env, id);
+ WARN_ON_ONCE(err);
+ }
while ((id = idstack_pop(idstack))) {
/*
@@ -8971,10 +9517,25 @@ static int release_reference(struct bpf_verifier_env *env, int id)
return err;
}
+ /*
+ * A dynptr occupies two stack slots that invalidate_dynptr()
+ * clears together. Record both scrubs before invalidating it.
+ */
+ if (stack && stack->slot_type[BPF_REG_SIZE - 1] == STACK_DYNPTR) {
+ struct bpf_stack_state *dyn_stack = stack;
+
+ if (reg->dynptr.first_slot)
+ dyn_stack--;
+ bpf_diag_record_scrub(env, &dyn_stack[0].spilled_ptr,
+ BPF_DIAG_MOD_REF_RELEASE);
+ bpf_diag_record_scrub(env, &dyn_stack[1].spilled_ptr,
+ BPF_DIAG_MOD_REF_RELEASE);
+ invalidate_dynptr(env, dyn_stack);
+ continue;
+ }
+ bpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_REF_RELEASE);
if (!stack || stack->slot_type[BPF_REG_SIZE - 1] == STACK_SPILL)
mark_reg_invalid(env, reg);
- else if (stack->slot_type[BPF_REG_SIZE - 1] == STACK_DYNPTR)
- invalidate_dynptr(env, stack);
}));
}
@@ -8987,8 +9548,10 @@ static void invalidate_non_owning_refs(struct bpf_verifier_env *env)
struct bpf_reg_state *reg;
bpf_for_each_reg_in_vstate(env->cur_state, unused, reg, ({
- if (type_is_non_owning_ref(reg->type))
+ if (type_is_non_owning_ref(reg->type)) {
+ bpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_NON_OWN_REF);
mark_reg_invalid(env, reg);
+ }
}));
}
@@ -9001,8 +9564,10 @@ static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env)
bpf_for_each_reg_in_vstate_mask(env->cur_state, state, reg, stack, clear_mask, ({
if (reg->type & MEM_RCU) {
- reg->type &= ~(MEM_RCU | PTR_MAYBE_NULL);
+ bpf_diag_mod_begin(env, reg, NULL, BPF_DIAG_MOD_WRITE);
+ reg->type &= ~(MEM_RCU | PTR_MAYBE_NULL | NON_OWN_REF);
reg->type |= PTR_UNTRUSTED;
+ bpf_diag_mod_end(env);
}
}));
}
@@ -9013,15 +9578,19 @@ static int ref_convert_alloc_rcu_protected(struct bpf_verifier_env *env, u32 id)
struct bpf_reg_state *reg;
int err;
- err = release_reference_nomark(env->cur_state, id);
+ err = release_reference_nomark(env, id);
+ if (err)
+ return err;
bpf_for_each_reg_in_vstate(env->cur_state, state, reg, ({
if (reg->id != id)
continue;
if ((reg->type & MEM_ALLOC) && (reg->type & MEM_PERCPU)) {
+ bpf_diag_mod_begin(env, reg, NULL, BPF_DIAG_MOD_WRITE);
reg->id = 0;
reg->type &= ~MEM_ALLOC;
reg->type |= MEM_RCU;
+ bpf_diag_mod_end(env);
}
}));
@@ -9033,6 +9602,8 @@ static void clear_caller_saved_regs(struct bpf_verifier_env *env,
{
int i;
+ bpf_diag_record_caller_saved(env, regs);
+
/* after the call registers r0 - r5 were scratched */
for (i = 0; i < CALLER_SAVED_REGS; i++) {
bpf_mark_reg_not_init(env, &regs[caller_saved[i]]);
@@ -9040,13 +9611,15 @@ static void clear_caller_saved_regs(struct bpf_verifier_env *env,
}
}
-static void invalidate_outgoing_stack_args(const struct bpf_verifier_env *env,
+static void invalidate_outgoing_stack_args(struct bpf_verifier_env *env,
struct bpf_func_state *state)
{
int i, nslots = state->out_stack_arg_cnt;
- for (i = 0; i < nslots; i++)
+ for (i = 0; i < nslots; i++) {
+ bpf_diag_record_scrub(env, &state->stack_arg_regs[i], BPF_DIAG_MOD_CALLER_SAVED);
bpf_mark_reg_not_init(env, &state->stack_arg_regs[i]);
+ }
}
typedef int (*set_callee_state_fn)(struct bpf_verifier_env *env,
@@ -9114,20 +9687,28 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
struct bpf_func_state *caller = cur_func(env);
struct bpf_verifier_log *log = &env->log;
struct ref_obj_desc ref_obj = {};
+ const struct btf_param *args;
+ const struct btf_type *func, *func_proto;
u32 i;
int ret, err;
ret = btf_prepare_func_args(env, subprog);
if (ret) {
if (bpf_in_stack_arg_cnt(sub) > 0) {
- err = check_outgoing_stack_args(env, caller, sub->arg_cnt);
+ err = check_outgoing_stack_args(env, caller, sub->arg_cnt,
+ bpf_subprog_name(env, subprog),
+ NULL, NULL);
if (err)
return err;
}
return ret;
}
- ret = check_outgoing_stack_args(env, caller, sub->arg_cnt);
+ func = btf_type_by_id(btf, env->prog->aux->func_info[subprog].type_id);
+ func_proto = btf_type_by_id(btf, func->type);
+ args = btf_params(func_proto);
+ ret = check_outgoing_stack_args(env, caller, sub->arg_cnt,
+ bpf_subprog_name(env, subprog), btf, args);
if (ret)
return ret;
@@ -9166,9 +9747,21 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
ret = check_func_arg_reg_off(env, reg, argno, ARG_DONTCARE);
if (ret < 0)
return ret;
- if (check_mem_reg(env, reg, argno, arg->mem_size))
+ if (check_mem_reg(env, reg, argno, arg->mem_size, BPF_READ | BPF_WRITE, NULL,
+ NULL))
+ return -EINVAL;
+ /*
+ * PTR_TO_PACKET get passed as PTR_TO_MEM, preventing
+ * us from adjusting bounds tracking info.
+ */
+ if ((reg_is_pkt_pointer_any(reg) || reg_is_dynptr_slice_pkt(reg)) &&
+ sub->changes_pkt_data) {
+ bpf_log(log, "%s is a packet pointer, but func#%d may change packet data\n",
+ reg_arg_name(env, argno), subprog);
return -EINVAL;
- if (!(arg->arg_type & PTR_MAYBE_NULL) && (reg->type & PTR_MAYBE_NULL)) {
+ }
+ if (!(arg->arg_type & PTR_MAYBE_NULL) &&
+ (type_may_be_null(reg->type) || bpf_register_is_null(reg))) {
bpf_log(log, "%s is expected to be non-NULL\n",
reg_arg_name(env, argno));
return -EINVAL;
@@ -9191,18 +9784,25 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
if (ret)
return ret;
- ret = process_dynptr_func(env, reg, argno, -1, arg->arg_type, &ref_obj, NULL);
+ ret = process_dynptr_func(env, reg, argno, env->insn_idx,
+ bpf_subprog_name(env, subprog), arg->arg_type,
+ &ref_obj, NULL);
if (ret)
return ret;
} else if (base_type(arg->arg_type) == ARG_PTR_TO_BTF_ID) {
struct bpf_call_arg_meta meta;
int err;
- if (bpf_register_is_null(reg) && type_may_be_null(arg->arg_type))
+ if (bpf_register_is_null(reg) && type_may_be_null(arg->arg_type)) {
+ err = mark_arg_precision(env, argno);
+ if (err)
+ return err;
continue;
+ }
memset(&meta, 0, sizeof(meta)); /* leave func_id as zero */
- err = check_reg_type(env, reg, argno, arg->arg_type, &arg->btf_id, &meta);
+ err = check_reg_type(env, reg, argno, arg->arg_type, &arg->btf_id, &meta,
+ bpf_subprog_name(env, subprog));
err = err ?: check_func_arg_reg_off(env, reg, argno, arg->arg_type);
if (err)
return err;
@@ -9343,17 +9943,26 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (err == -EFAULT)
return err;
if (bpf_subprog_is_global(env, subprog)) {
- const char *sub_name = subprog_name(env, subprog);
+ struct bpf_func_info_aux *sub_aux = subprog_aux(env, subprog);
+ const char *sub_name = bpf_subprog_name(env, subprog);
+ const char *operation;
+ bool returns_void;
if (env->cur_state->active_locks) {
verbose(env, "global function calls are not allowed while holding a lock,\n"
"use static function instead\n");
+ operation = bpf_diag_fmt(env, "global function %s()", sub_name);
+ bpf_diag_ctx_active(env, *insn_idx, operation, BPF_DIAG_CONTEXT_LOCK,
+ "Release the lock before calling the global function, or use a static function instead.");
return -EINVAL;
}
if (env->subprog_info[subprog].might_sleep && !in_sleepable_context(env)) {
verbose(env, "sleepable global function %s() called in %s\n",
sub_name, non_sleepable_context_description(env));
+ operation = bpf_diag_fmt(env, "sleepable global function %s()", sub_name);
+ bpf_diag_ctx_forbidden(env, *insn_idx, operation,
+ "Move the call outside the critical section, or use a non-sleepable function.");
return -EINVAL;
}
@@ -9366,17 +9975,21 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (env->log.level & BPF_LOG_LEVEL)
verbose(env, "Func#%d ('%s') is global and assumed valid.\n",
subprog, sub_name);
+ sub_aux->called[in_sleepable_context(env)] = true;
+ returns_void = subprog_returns_void(env, subprog);
if (env->subprog_info[subprog].changes_pkt_data)
clear_all_pkt_pointers(env);
- /* mark global subprog for verifying after main prog */
- subprog_aux(env, subprog)->called = true;
+ if (returns_void)
+ bpf_diag_record_scrub(env, &caller->regs[BPF_REG_0], BPF_DIAG_MOD_CALLER_SAVED);
+ else
+ bpf_diag_mod_begin(env, &caller->regs[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
clear_caller_saved_regs(env, caller->regs);
invalidate_outgoing_stack_args(env, cur_func(env));
/* All non-void global functions return a 64-bit SCALAR_VALUE. */
- if (!subprog_returns_void(env, subprog)) {
+ if (!returns_void) {
mark_reg_unknown(env, caller->regs, BPF_REG_0);
- caller->regs[BPF_REG_0].subreg_def = DEF_NOT_SUBREG;
+ bpf_diag_mod_end(env);
}
if (env->subprog_info[subprog].might_throw) {
@@ -9411,6 +10024,7 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (err)
return err;
+ bpf_diag_record_scrub(env, &caller->regs[BPF_REG_0], BPF_DIAG_MOD_CALLER_SAVED);
clear_caller_saved_regs(env, caller->regs);
/* and go analyze first insn of the callee */
@@ -9440,10 +10054,13 @@ int map_set_for_each_callback_args(struct bpf_verifier_env *env,
callee->regs[BPF_REG_2].type = PTR_TO_MAP_KEY;
__mark_reg_known_zero(&callee->regs[BPF_REG_2]);
callee->regs[BPF_REG_2].map_ptr = caller->regs[BPF_REG_1].map_ptr;
+ callee->regs[BPF_REG_2].map_uid = caller->regs[BPF_REG_1].map_uid;
callee->regs[BPF_REG_3].type = PTR_TO_MAP_VALUE;
__mark_reg_known_zero(&callee->regs[BPF_REG_3]);
callee->regs[BPF_REG_3].map_ptr = caller->regs[BPF_REG_1].map_ptr;
+ callee->regs[BPF_REG_3].map_uid = caller->regs[BPF_REG_1].map_uid;
+ callee->regs[BPF_REG_3].id = ++env->id_gen;
/* pointer to stack or null */
callee->regs[BPF_REG_4] = caller->regs[BPF_REG_3];
@@ -9521,6 +10138,7 @@ static int set_timer_callback_state(struct bpf_verifier_env *env,
int insn_idx)
{
struct bpf_map *map_ptr = caller->regs[BPF_REG_1].map_ptr;
+ u32 map_uid = caller->regs[BPF_REG_1].map_uid;
/* bpf_timer_set_callback(struct bpf_timer *timer, void *callback_fn);
* callback_fn(struct bpf_map *map, void *key, void *value);
@@ -9528,14 +10146,18 @@ static int set_timer_callback_state(struct bpf_verifier_env *env,
callee->regs[BPF_REG_1].type = CONST_PTR_TO_MAP;
__mark_reg_known_zero(&callee->regs[BPF_REG_1]);
callee->regs[BPF_REG_1].map_ptr = map_ptr;
+ callee->regs[BPF_REG_1].map_uid = map_uid;
callee->regs[BPF_REG_2].type = PTR_TO_MAP_KEY;
__mark_reg_known_zero(&callee->regs[BPF_REG_2]);
callee->regs[BPF_REG_2].map_ptr = map_ptr;
+ callee->regs[BPF_REG_2].map_uid = map_uid;
callee->regs[BPF_REG_3].type = PTR_TO_MAP_VALUE;
__mark_reg_known_zero(&callee->regs[BPF_REG_3]);
callee->regs[BPF_REG_3].map_ptr = map_ptr;
+ callee->regs[BPF_REG_3].map_uid = map_uid;
+ callee->regs[BPF_REG_3].id = ++env->id_gen;
/* unused */
bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]);
@@ -9635,6 +10257,7 @@ static int set_task_work_schedule_callback_state(struct bpf_verifier_env *env,
int insn_idx)
{
struct bpf_map *map_ptr = caller->regs[BPF_REG_3].map_ptr;
+ u32 map_uid = caller->regs[BPF_REG_3].map_uid;
/*
* callback_fn(struct bpf_map *map, void *key, void *value);
@@ -9642,14 +10265,18 @@ static int set_task_work_schedule_callback_state(struct bpf_verifier_env *env,
callee->regs[BPF_REG_1].type = CONST_PTR_TO_MAP;
__mark_reg_known_zero(&callee->regs[BPF_REG_1]);
callee->regs[BPF_REG_1].map_ptr = map_ptr;
+ callee->regs[BPF_REG_1].map_uid = map_uid;
callee->regs[BPF_REG_2].type = PTR_TO_MAP_KEY;
__mark_reg_known_zero(&callee->regs[BPF_REG_2]);
callee->regs[BPF_REG_2].map_ptr = map_ptr;
+ callee->regs[BPF_REG_2].map_uid = map_uid;
callee->regs[BPF_REG_3].type = PTR_TO_MAP_VALUE;
__mark_reg_known_zero(&callee->regs[BPF_REG_3]);
callee->regs[BPF_REG_3].map_ptr = map_ptr;
+ callee->regs[BPF_REG_3].map_uid = map_uid;
+ callee->regs[BPF_REG_3].id = ++env->id_gen;
/* unused */
bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]);
@@ -9661,9 +10288,46 @@ static int set_task_work_schedule_callback_state(struct bpf_verifier_env *env,
static bool is_rbtree_lock_required_kfunc(u32 btf_id);
-/* Are we currently verifying the callback for a rbtree helper that must
- * be called with lock held? If so, no need to complain about unreleased
- * lock
+static void account_processed_insn(struct bpf_verifier_env *env)
+{
+ struct bpf_func_state *frame = cur_func(env);
+
+ env->insn_processed++;
+ frame->insns_subtotal++;
+ env->subprog_info[frame->subprogno].insns_self++;
+}
+
+static void account_processed_insns(struct bpf_verifier_env *env,
+ struct bpf_func_state *callee,
+ struct bpf_func_state *caller)
+{
+ u32 insns;
+
+ if (!callee)
+ return;
+
+ insns = callee->insns_subtotal;
+
+ env->subprog_info[callee->subprogno].insns_total += insns;
+ if (caller)
+ caller->insns_subtotal += insns;
+ callee->insns_subtotal = 0;
+}
+
+static void account_current_path(struct bpf_verifier_env *env)
+{
+ struct bpf_verifier_state *state = env->cur_state;
+ int frame;
+
+ for (frame = state->curframe; frame >= 0; frame--)
+ account_processed_insns(env, state->frame[frame],
+ frame ? state->frame[frame - 1] : NULL);
+}
+
+/*
+ * Are we currently verifying the callback for an rbtree kfunc that must
+ * be called with a lock held, or one of that callback's subprogs? If so,
+ * no need to complain about an unreleased lock.
*/
static bool in_rbtree_lock_required_cb(struct bpf_verifier_env *env)
{
@@ -9671,17 +10335,19 @@ static bool in_rbtree_lock_required_cb(struct bpf_verifier_env *env)
struct bpf_insn *insn = env->prog->insnsi;
struct bpf_func_state *callee;
int kfunc_btf_id;
+ u32 frame;
- if (!state->curframe)
- return false;
-
- callee = state->frame[state->curframe];
+ for (frame = state->curframe; frame; frame--) {
+ callee = state->frame[frame];
+ if (!callee->in_callback_fn)
+ continue;
- if (!callee->in_callback_fn)
- return false;
+ kfunc_btf_id = insn[callee->callsite].imm;
+ if (is_rbtree_lock_required_kfunc(kfunc_btf_id))
+ return true;
+ }
- kfunc_btf_id = insn[callee->callsite].imm;
- return is_rbtree_lock_required_kfunc(kfunc_btf_id);
+ return false;
}
static bool retval_range_within(struct bpf_retval_range range, const struct bpf_reg_state *reg)
@@ -9738,7 +10404,9 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
}
} else {
/* return to the caller whatever r0 had in the callee */
+ bpf_diag_mod_begin(env, &caller->regs[BPF_REG_0], r0, BPF_DIAG_MOD_WRITE);
caller->regs[BPF_REG_0] = *r0;
+ bpf_diag_mod_end(env);
}
/* for callbacks like bpf_loop or bpf_for_each_map_elem go back to callsite,
@@ -9757,6 +10425,7 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
verbose(env, "to caller at %d:\n", *insn_idx);
print_verifier_state(env, state, caller->frameno, true);
}
+ account_processed_insns(env, callee, caller);
/* clear everything in the callee. In case of exceptional exits using
* bpf_throw, this will be done by copy_verifier_state for extra frames. */
free_func_state(callee);
@@ -9943,6 +10612,7 @@ static int check_reference_leak(struct bpf_verifier_env *env, bool exception_exi
continue;
verbose(env, "Unreleased reference id=%d alloc_insn=%d\n",
state->refs[i].id, state->refs[i].insn_idx);
+ bpf_diag_leak(env, state->refs[i].id, state->refs[i].insn_idx, env->insn_idx);
refs_lingering = true;
}
return refs_lingering ? -EINVAL : 0;
@@ -9954,6 +10624,8 @@ static int check_resource_leak(struct bpf_verifier_env *env, bool exception_exit
if (check_lock && env->cur_state->active_locks) {
verbose(env, "%s cannot be used inside bpf_spin_lock-ed region\n", prefix);
+ bpf_diag_ctx_active(env, env->insn_idx, prefix, BPF_DIAG_CONTEXT_LOCK,
+ "Release the BPF spin lock before this operation on every path.");
return -EINVAL;
}
@@ -9965,16 +10637,23 @@ static int check_resource_leak(struct bpf_verifier_env *env, bool exception_exit
if (check_lock && env->cur_state->active_irq_id) {
verbose(env, "%s cannot be used inside bpf_local_irq_save-ed region\n", prefix);
+ bpf_diag_ctx_active(env, env->insn_idx, prefix, BPF_DIAG_CONTEXT_IRQ,
+ "Restore the saved IRQ state before this operation on every path.");
return -EINVAL;
}
if (check_lock && env->cur_state->active_rcu_locks) {
verbose(env, "%s cannot be used inside bpf_rcu_read_lock-ed region\n", prefix);
+ bpf_diag_ctx_active(env, env->insn_idx, prefix, BPF_DIAG_CONTEXT_RCU,
+ "Call bpf_rcu_read_unlock() before this operation on every path.");
return -EINVAL;
}
if (check_lock && env->cur_state->active_preempt_locks) {
verbose(env, "%s cannot be used inside bpf_preempt_disable-ed region\n", prefix);
+ bpf_diag_ctx_active(
+ env, env->insn_idx, prefix, BPF_DIAG_CONTEXT_PREEMPT,
+ "Call bpf_preempt_enable() before this operation on every path.");
return -EINVAL;
}
@@ -10045,33 +10724,45 @@ static struct bpf_insn_aux_data *cur_aux(const struct bpf_verifier_env *env)
return &env->insn_aux_data[env->insn_idx];
}
-static bool loop_flag_is_zero(struct bpf_verifier_env *env)
+/* Returns 1 if R4 is a known zero, 0 if it is not, a negative errno on error. */
+static int loop_flag_is_zero(struct bpf_verifier_env *env)
{
struct bpf_reg_state *reg = reg_state(env, BPF_REG_4);
- bool reg_is_null = bpf_register_is_null(reg);
+ int err;
- if (reg_is_null)
- mark_chain_precision(env, BPF_REG_4);
+ if (!bpf_register_is_null(reg))
+ return 0;
- return reg_is_null;
+ err = mark_chain_precision(env, BPF_REG_4);
+ if (err)
+ return err;
+ return 1;
}
-static void update_loop_inline_state(struct bpf_verifier_env *env, u32 subprogno)
+static int update_loop_inline_state(struct bpf_verifier_env *env, u32 subprogno)
{
struct bpf_loop_inline_state *state = &cur_aux(env)->loop_inline_state;
+ int flag_is_zero;
if (!state->initialized) {
+ flag_is_zero = loop_flag_is_zero(env);
+ if (flag_is_zero < 0)
+ return flag_is_zero;
state->initialized = 1;
- state->fit_for_inline = loop_flag_is_zero(env);
+ state->fit_for_inline = flag_is_zero;
state->callback_subprogno = subprogno;
- return;
+ return 0;
}
if (!state->fit_for_inline)
- return;
+ return 0;
- state->fit_for_inline = (loop_flag_is_zero(env) &&
+ flag_is_zero = loop_flag_is_zero(env);
+ if (flag_is_zero < 0)
+ return flag_is_zero;
+ state->fit_for_inline = (flag_is_zero &&
state->callback_subprogno == subprogno);
+ return 0;
}
/* Returns whether or not the given map can potentially elide
@@ -10108,11 +10799,7 @@ int bpf_get_helper_proto(struct bpf_verifier_env *env, int func_id,
/* Check if we're in a sleepable context. */
static inline bool in_sleepable_context(struct bpf_verifier_env *env)
{
- return !env->cur_state->active_rcu_locks &&
- !env->cur_state->active_preempt_locks &&
- !env->cur_state->active_locks &&
- !env->cur_state->active_irq_id &&
- in_sleepable(env);
+ return !in_rcu_cs(env);
}
static const char *non_sleepable_context_description(struct bpf_verifier_env *env)
@@ -10156,6 +10843,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
enum bpf_type_flag ret_flag;
struct bpf_reg_state *regs;
struct bpf_call_arg_meta meta;
+ const char *operation;
int insn_idx = *insn_idx_p;
bool changes_data;
int i, err, func_id;
@@ -10171,17 +10859,31 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
if (err) {
verbose(env, "program of this type cannot use helper %s#%d\n",
func_id_name(func_id), func_id);
+ operation = bpf_diag_fmt(env, "helper %s#%d", func_id_name(func_id), func_id);
+ bpf_diag_policy(
+ env, insn_idx, operation, "this program type does not allow the helper",
+ "Use a helper allowed for this program type, or move the logic to a compatible program type.");
return err;
}
/* eBPF programs must be GPL compatible to use GPL-ed functions */
if (!env->prog->gpl_compatible && fn->gpl_only) {
verbose(env, "cannot call GPL-restricted function from non-GPL compatible program\n");
+ operation = bpf_diag_fmt(env, "helper %s#%d", func_id_name(func_id), func_id);
+ bpf_diag_policy(
+ env, insn_idx, operation,
+ "this helper is restricted to GPL-compatible programs",
+ "Use a GPL-compatible license, or replace the helper with one that is available to non-GPL programs.");
return -EINVAL;
}
if (fn->allowed && !fn->allowed(env->prog)) {
verbose(env, "helper call is not allowed in probe\n");
+ operation = bpf_diag_fmt(env, "helper %s#%d", func_id_name(func_id), func_id);
+ bpf_diag_policy(
+ env, insn_idx, operation,
+ "the helper-specific policy callback rejected this program",
+ "Use the helper only from an allowed attach point or program configuration.");
return -EINVAL;
}
@@ -10193,7 +10895,6 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
}
memset(&meta, 0, sizeof(meta));
- meta.pkt_access = fn->pkt_access;
err = check_func_proto(fn, &meta);
if (err) {
@@ -10204,6 +10905,10 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
if (fn->might_sleep && !in_sleepable_context(env)) {
verbose(env, "sleepable helper %s#%d in %s\n", func_id_name(func_id), func_id,
non_sleepable_context_description(env));
+ operation = bpf_diag_fmt(env, "sleepable helper %s#%d",
+ func_id_name(func_id), func_id);
+ bpf_diag_ctx_forbidden(env, insn_idx, operation,
+ "Move the helper call outside the critical section, or use a non-sleepable helper.");
return -EINVAL;
}
@@ -10212,9 +10917,10 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
env->insn_aux_data[insn_idx].non_sleepable = true;
meta.func_id = func_id;
+ meta.fn = fn;
/* check args */
for (i = 0; i < MAX_BPF_FUNC_REG_ARGS; i++) {
- err = check_func_arg(env, i, &meta, fn, insn_idx);
+ err = check_func_arg(env, i, &meta, insn_idx);
if (err)
return err;
}
@@ -10232,8 +10938,9 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
/* Mark slots with STACK_MISC in case of raw mode, stack offset
* is inferred from register state.
*/
- for (i = 0; i < meta.access_size; i++) {
- err = check_mem_access(env, insn_idx, regs + meta.regno, argno_from_reg(meta.regno), i, BPF_B,
+ for (i = 0; i < meta.arg_raw_mem.size; i++) {
+ err = check_mem_access(env, insn_idx, regs + meta.arg_raw_mem.regno,
+ argno_from_reg(meta.arg_raw_mem.regno), i, BPF_B,
BPF_WRITE, -1, false, false);
if (err)
return err;
@@ -10263,6 +10970,9 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
verbose(env, "get_local_storage() doesn't support non-zero flags\n");
return -EINVAL;
}
+ err = mark_chain_precision(env, BPF_REG_2);
+ if (err)
+ return err;
break;
case BPF_FUNC_for_each_map_elem:
err = push_callback_call(env, insn, insn_idx, meta.subprogno,
@@ -10280,7 +10990,9 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
err = check_bpf_snprintf_call(env, regs);
break;
case BPF_FUNC_loop:
- update_loop_inline_state(env, meta.subprogno);
+ err = update_loop_inline_state(env, meta.subprogno);
+ if (err)
+ return err;
/* Verifier relies on R1 value to determine if bpf_loop() iteration
* is finished, thus mark it precise.
*/
@@ -10389,15 +11101,14 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
return err;
/* reset caller saved regs */
+ bpf_diag_record_caller_saved(env, regs);
+ bpf_diag_mod_begin(env, &regs[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
for (i = 0; i < CALLER_SAVED_REGS; i++) {
bpf_mark_reg_not_init(env, &regs[caller_saved[i]]);
check_reg_arg(env, caller_saved[i], DST_OP_NO_MARK);
}
invalidate_outgoing_stack_args(env, cur_func(env));
- /* helper call returns 64-bit value. */
- regs[BPF_REG_0].subreg_def = DEF_NOT_SUBREG;
-
/* update return register (already marked as written above) */
ret_type = fn->ret_type;
ret_flag = type_flag(ret_type);
@@ -10431,10 +11142,12 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
regs[BPF_REG_0].map_ptr = meta.map.ptr;
regs[BPF_REG_0].map_uid = meta.map.uid;
regs[BPF_REG_0].type = PTR_TO_MAP_VALUE | ret_flag;
- if (!type_may_be_null(ret_flag) &&
+ if (type_may_be_null(ret_flag) ||
btf_record_has_field(meta.map.ptr->record, BPF_SPIN_LOCK | BPF_RES_SPIN_LOCK)) {
regs[BPF_REG_0].id = ++env->id_gen;
}
+ /* requires regs[BPF_REG_0].id to be set because of the map-in-map case */
+ refine_map_lookup_value(&regs[BPF_REG_0]);
break;
case RET_PTR_TO_SOCKET:
mark_reg_known_zero(env, regs, BPF_REG_0);
@@ -10451,7 +11164,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
case RET_PTR_TO_MEM:
mark_reg_known_zero(env, regs, BPF_REG_0);
regs[BPF_REG_0].type = PTR_TO_MEM | ret_flag;
- regs[BPF_REG_0].mem_size = meta.mem_size;
+ regs[BPF_REG_0].mem_size = meta.ret_mem.size;
break;
case RET_PTR_TO_MEM_OR_BTF_ID:
{
@@ -10532,7 +11245,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
return -EINVAL;
}
- if (type_may_be_null(regs[BPF_REG_0].type))
+ if (type_may_be_null(regs[BPF_REG_0].type) && !regs[BPF_REG_0].id)
regs[BPF_REG_0].id = ++env->id_gen;
if (is_ptr_cast_function(func_id) &&
@@ -10544,6 +11257,8 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
if (err)
return err;
+ bpf_diag_mod_end(env);
+
/*
* In order for a release of any of the original or cast pointers
* to invalidate all other pointers, reuse the same reference id for
@@ -10560,6 +11275,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
__mark_reg_known_zero(r0);
r0->type = SCALAR_VALUE;
+ bpf_diag_mod_begin(env, &regs[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
regs[BPF_REG_0].type &= ~PTR_MAYBE_NULL;
regs[BPF_REG_0].id = meta.ref_obj.id;
} else if (is_acquire_function(func_id, meta.map.ptr)) {
@@ -10578,6 +11294,8 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
if (err)
return err;
+ bpf_diag_mod_end(env);
+
err = check_map_func_compatibility(env, meta.map.ptr, func_id);
if (err)
return err;
@@ -10615,6 +11333,17 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
if (env->cur_state->curframe) {
struct bpf_verifier_state *branch;
+ /*
+ * A taken tail call is modeled as a return from the current
+ * frame. A callback frame cannot be left that way because
+ * prepare_func_exit() would apply its return contract to the
+ * unknown R0 synthesized below. Stack-depth validation rejects
+ * this construct anyway.
+ */
+ if (cur_func(env)->in_callback_fn) {
+ verbose(env, "cannot tail call within callback\n");
+ return -EINVAL;
+ }
mark_reg_scratched(env, BPF_REG_0);
branch = push_stack(env, env->insn_idx + 1, env->insn_idx, false);
if (IS_ERR(branch))
@@ -10635,76 +11364,55 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
return 0;
}
-/* mark_btf_func_reg_size() is used when the reg size is determined by
- * the BTF func_proto's return value size and argument.
- */
-static void __mark_btf_func_reg_size(struct bpf_verifier_env *env, struct bpf_reg_state *regs,
- u32 regno, size_t reg_size)
-{
- struct bpf_reg_state *reg = &regs[regno];
-
- if (regno == BPF_REG_0) {
- /* Function return value */
- reg->subreg_def = reg_size == sizeof(u64) ?
- DEF_NOT_SUBREG : env->insn_idx + 1;
- } else if (reg_size == sizeof(u64)) {
- /* Function argument */
- mark_insn_zext(env, reg);
- }
-}
-
-static void mark_btf_func_reg_size(struct bpf_verifier_env *env, u32 regno,
- size_t reg_size)
-{
- return __mark_btf_func_reg_size(env, cur_regs(env), regno, reg_size);
-}
-
-static bool is_kfunc_acquire(struct bpf_kfunc_call_arg_meta *meta)
+static bool is_kfunc_acquire(struct bpf_call_arg_meta *meta)
{
return meta->kfunc_flags & KF_ACQUIRE;
}
-static bool is_kfunc_release(struct bpf_kfunc_call_arg_meta *meta)
+static bool is_kfunc_release(struct bpf_call_arg_meta *meta)
{
return meta->kfunc_flags & KF_RELEASE;
}
-static bool is_kfunc_destructive(struct bpf_kfunc_call_arg_meta *meta)
+static bool is_kfunc_destructive(struct bpf_call_arg_meta *meta)
{
return meta->kfunc_flags & KF_DESTRUCTIVE;
}
-static bool is_kfunc_rcu(struct bpf_kfunc_call_arg_meta *meta)
+static bool is_kfunc_perfmon(struct bpf_call_arg_meta *meta)
+{
+ return meta->kfunc_flags & KF_PERFMON;
+}
+
+static bool is_kfunc_rcu(struct bpf_call_arg_meta *meta)
{
return meta->kfunc_flags & KF_RCU;
}
-static bool is_kfunc_rcu_protected(struct bpf_kfunc_call_arg_meta *meta)
+static bool is_kfunc_rcu_protected(struct bpf_call_arg_meta *meta)
{
return meta->kfunc_flags & KF_RCU_PROTECTED;
}
static bool is_kfunc_arg_mem_size(const struct btf *btf,
- const struct btf_param *arg,
- const struct bpf_reg_state *reg)
+ const struct btf_param *arg)
{
const struct btf_type *t;
t = btf_type_skip_modifiers(btf, arg->type, NULL);
- if (!btf_type_is_scalar(t) || reg->type != SCALAR_VALUE)
+ if (!btf_type_is_scalar(t))
return false;
return btf_param_match_suffix(btf, arg, "__sz");
}
static bool is_kfunc_arg_const_mem_size(const struct btf *btf,
- const struct btf_param *arg,
- const struct bpf_reg_state *reg)
+ const struct btf_param *arg)
{
const struct btf_type *t;
t = btf_type_skip_modifiers(btf, arg->type, NULL);
- if (!btf_type_is_scalar(t) || reg->type != SCALAR_VALUE)
+ if (!btf_type_is_scalar(t))
return false;
return btf_param_match_suffix(btf, arg, "__szk");
@@ -10725,6 +11433,11 @@ static bool is_kfunc_arg_map(const struct btf *btf, const struct btf_param *arg)
return btf_param_match_suffix(btf, arg, "__map");
}
+static bool is_kfunc_arg_const_map(const struct btf *btf, const struct btf_param *arg)
+{
+ return btf_param_match_suffix(btf, arg, "__const_map");
+}
+
static bool is_kfunc_arg_alloc_obj(const struct btf *btf, const struct btf_param *arg)
{
return btf_param_match_suffix(btf, arg, "__alloc");
@@ -10742,7 +11455,8 @@ static bool is_kfunc_arg_refcounted_kptr(const struct btf *btf, const struct btf
static bool is_kfunc_arg_nullable(const struct btf *btf, const struct btf_param *arg)
{
- return btf_param_match_suffix(btf, arg, "__nullable");
+ return btf_param_match_suffix(btf, arg, "__nullable") ||
+ btf_param_match_suffix(btf, arg, "__arena");
}
static bool is_kfunc_arg_nonown_allowed(const struct btf *btf, const struct btf_param *arg)
@@ -10760,6 +11474,12 @@ static bool is_kfunc_arg_irq_flag(const struct btf *btf, const struct btf_param
return btf_param_match_suffix(btf, arg, "__irq_flag");
}
+static bool is_kfunc_arg_arena(const struct btf *btf, const struct btf_param *arg)
+{
+ return btf_param_match_suffix(btf, arg, "__arena__nullable") ||
+ btf_param_match_suffix(btf, arg, "__arena");
+}
+
static bool is_kfunc_arg_scalar_with_name(const struct btf *btf,
const struct btf_param *arg,
const char *name)
@@ -10900,7 +11620,7 @@ static bool is_kfunc_arg_prog_aux(const struct btf *btf, const struct btf_param
* To determine whether an argument is implicit, we compare its position
* against the number of arguments in the prototype w/o implicit args.
*/
-static bool is_kfunc_arg_implicit(const struct bpf_kfunc_call_arg_meta *meta, u32 arg_idx)
+static bool is_kfunc_arg_implicit(const struct bpf_call_arg_meta *meta, u32 arg_idx)
{
const struct btf_type *func, *func_proto;
u32 argn;
@@ -10956,6 +11676,11 @@ static bool __btf_type_is_scalar_struct(struct bpf_verifier_env *env,
}
enum kfunc_ptr_arg_type {
+ KF_ARG_CONST_MEM_SIZE,
+ KF_ARG_MEM_SIZE,
+ KF_ARG_CONST,
+ KF_ARG_CONST_ALLOC_SIZE_OR_ZERO,
+ KF_ARG_ANYTHING,
KF_ARG_PTR_TO_CTX,
KF_ARG_PTR_TO_ALLOC_BTF_ID, /* Allocated object */
KF_ARG_PTR_TO_REFCOUNTED_KPTR, /* Refcounted local kptr */
@@ -10965,18 +11690,17 @@ enum kfunc_ptr_arg_type {
KF_ARG_PTR_TO_LIST_NODE,
KF_ARG_PTR_TO_BTF_ID, /* Also covers reg2btf_ids conversions */
KF_ARG_PTR_TO_MEM,
- KF_ARG_PTR_TO_MEM_SIZE, /* Size derived from next argument, skip it */
KF_ARG_PTR_TO_CALLBACK,
KF_ARG_PTR_TO_RB_ROOT,
KF_ARG_PTR_TO_RB_NODE,
- KF_ARG_PTR_TO_NULL,
KF_ARG_PTR_TO_CONST_STR,
- KF_ARG_PTR_TO_MAP,
+ KF_ARG_CONST_MAP_PTR,
KF_ARG_PTR_TO_TIMER,
KF_ARG_PTR_TO_WORKQUEUE,
KF_ARG_PTR_TO_IRQ_FLAG,
KF_ARG_PTR_TO_RES_SPIN_LOCK,
KF_ARG_PTR_TO_TASK_WORK,
+ KF_ARG_PTR_TO_ARENA,
};
enum special_kfunc_type {
@@ -11046,10 +11770,7 @@ enum special_kfunc_type {
KF_bpf_task_work_schedule_resume,
KF_bpf_arena_alloc_pages,
KF_bpf_arena_free_pages,
- KF_bpf_arena_reserve_pages,
KF_bpf_session_is_return,
- KF_bpf_stream_vprintk,
- KF_bpf_stream_print_stack,
};
BTF_ID_LIST(special_kfunc_list)
@@ -11139,14 +11860,11 @@ BTF_ID(func, bpf_task_work_schedule_signal)
BTF_ID(func, bpf_task_work_schedule_resume)
BTF_ID(func, bpf_arena_alloc_pages)
BTF_ID(func, bpf_arena_free_pages)
-BTF_ID(func, bpf_arena_reserve_pages)
#ifdef CONFIG_BPF_EVENTS
BTF_ID(func, bpf_session_is_return)
#else
BTF_ID_UNUSED
#endif
-BTF_ID(func, bpf_stream_vprintk)
-BTF_ID(func, bpf_stream_print_stack)
static bool is_bpf_obj_new_kfunc(u32 func_id)
{
@@ -11199,7 +11917,7 @@ static bool is_task_work_add_kfunc(u32 func_id)
func_id == special_kfunc_list[KF_bpf_task_work_schedule_resume];
}
-static bool is_kfunc_ret_null(struct bpf_kfunc_call_arg_meta *meta)
+static bool is_kfunc_ret_null(struct bpf_call_arg_meta *meta)
{
if (is_bpf_refcount_acquire_kfunc(meta->func_id) && meta->arg_owning_ref)
return false;
@@ -11207,140 +11925,208 @@ static bool is_kfunc_ret_null(struct bpf_kfunc_call_arg_meta *meta)
return meta->kfunc_flags & KF_RET_NULL;
}
-static bool is_kfunc_bpf_rcu_read_lock(struct bpf_kfunc_call_arg_meta *meta)
+static bool is_kfunc_bpf_rcu_read_lock(struct bpf_call_arg_meta *meta)
{
return meta->func_id == special_kfunc_list[KF_bpf_rcu_read_lock];
}
-static bool is_kfunc_bpf_rcu_read_unlock(struct bpf_kfunc_call_arg_meta *meta)
+static bool is_kfunc_bpf_rcu_read_unlock(struct bpf_call_arg_meta *meta)
{
return meta->func_id == special_kfunc_list[KF_bpf_rcu_read_unlock];
}
-static bool is_kfunc_bpf_preempt_disable(struct bpf_kfunc_call_arg_meta *meta)
+static bool is_kfunc_bpf_preempt_disable(struct bpf_call_arg_meta *meta)
{
return meta->func_id == special_kfunc_list[KF_bpf_preempt_disable];
}
-static bool is_kfunc_bpf_preempt_enable(struct bpf_kfunc_call_arg_meta *meta)
+static bool is_kfunc_bpf_preempt_enable(struct bpf_call_arg_meta *meta)
{
return meta->func_id == special_kfunc_list[KF_bpf_preempt_enable];
}
-bool bpf_is_kfunc_pkt_changing(struct bpf_kfunc_call_arg_meta *meta)
+bool bpf_is_kfunc_pkt_changing(struct bpf_call_arg_meta *meta)
{
return meta->func_id == special_kfunc_list[KF_bpf_xdp_pull_data];
}
-static enum kfunc_ptr_arg_type
-get_kfunc_ptr_arg_type(struct bpf_verifier_env *env, struct bpf_func_state *caller,
- struct bpf_reg_state *regs, struct bpf_kfunc_call_arg_meta *meta,
- const struct btf_type *t, const struct btf_type *ref_t,
- const char *ref_tname, const struct btf_param *args,
- int arg, int nargs, argno_t argno, struct bpf_reg_state *reg)
+static int
+get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
+ const struct btf_param *args, int arg, int nargs)
{
- bool arg_mem_size = false;
+ const struct btf_type *t, *ref_t = NULL;
+ argno_t argno = argno_from_arg(arg + 1);
+ const char *ref_tname = NULL;
+ int arg_type;
- if (meta->func_id == special_kfunc_list[KF_bpf_cast_to_kern_ctx] ||
- meta->func_id == special_kfunc_list[KF_bpf_session_is_return] ||
- meta->func_id == special_kfunc_list[KF_bpf_session_cookie])
- return KF_ARG_PTR_TO_CTX;
+ t = btf_type_skip_modifiers(meta->btf, args[arg].type, NULL);
- if (arg + 1 < nargs &&
- (is_kfunc_arg_mem_size(meta->btf, &args[arg + 1], get_func_arg_reg(caller, regs, arg + 1)) ||
- is_kfunc_arg_const_mem_size(meta->btf, &args[arg + 1], get_func_arg_reg(caller, regs, arg + 1))))
- arg_mem_size = true;
+ /* Scalar arguments are classified from their BTF suffix/name alone. */
+ if (btf_type_is_scalar(t)) {
+ if (is_kfunc_arg_constant(meta->btf, &args[arg]))
+ return KF_ARG_CONST;
+ if (is_kfunc_arg_const_mem_size(meta->btf, &args[arg]))
+ return KF_ARG_CONST_MEM_SIZE;
+ if (is_kfunc_arg_mem_size(meta->btf, &args[arg]))
+ return KF_ARG_MEM_SIZE;
+ if (is_kfunc_arg_scalar_with_name(meta->btf, &args[arg], "rdonly_buf_size") ||
+ is_kfunc_arg_scalar_with_name(meta->btf, &args[arg], "rdwr_buf_size"))
+ return KF_ARG_CONST_ALLOC_SIZE_OR_ZERO;
+ return KF_ARG_ANYTHING;
+ }
+
+ if (!btf_type_is_ptr(t)) {
+ verbose(env, "Unrecognized %s type %s\n",
+ reg_arg_name(env, argno), btf_type_str(t));
+ return -EINVAL;
+ }
+ ref_t = btf_type_skip_modifiers(meta->btf, t->type, NULL);
+ ref_tname = btf_name_by_offset(meta->btf, ref_t->name_off);
/* In this function, we verify the kfunc's BTF as per the argument type,
* leaving the rest of the verification with respect to the register
* type to our caller. When a set of conditions hold in the BTF type of
* arguments, we resolve it to a known kfunc_ptr_arg_type.
*/
- if (btf_is_prog_ctx_type(&env->log, meta->btf, t, resolve_prog_type(env->prog), arg))
- return KF_ARG_PTR_TO_CTX;
-
- if (is_kfunc_arg_nullable(meta->btf, &args[arg]) && bpf_register_is_null(reg) &&
- !arg_mem_size)
- return KF_ARG_PTR_TO_NULL;
-
- if (is_kfunc_arg_alloc_obj(meta->btf, &args[arg]))
- return KF_ARG_PTR_TO_ALLOC_BTF_ID;
-
- if (is_kfunc_arg_refcounted_kptr(meta->btf, &args[arg]))
- return KF_ARG_PTR_TO_REFCOUNTED_KPTR;
-
- if (is_kfunc_arg_dynptr(meta->btf, &args[arg]))
- return KF_ARG_PTR_TO_DYNPTR;
-
- if (is_kfunc_arg_iter(meta, arg, &args[arg]))
- return KF_ARG_PTR_TO_ITER;
-
- if (is_kfunc_arg_list_head(meta->btf, &args[arg]))
- return KF_ARG_PTR_TO_LIST_HEAD;
-
- if (is_kfunc_arg_list_node(meta->btf, &args[arg]))
- return KF_ARG_PTR_TO_LIST_NODE;
-
- if (is_kfunc_arg_rbtree_root(meta->btf, &args[arg]))
- return KF_ARG_PTR_TO_RB_ROOT;
-
- if (is_kfunc_arg_rbtree_node(meta->btf, &args[arg]))
- return KF_ARG_PTR_TO_RB_NODE;
-
- if (is_kfunc_arg_const_str(meta->btf, &args[arg]))
- return KF_ARG_PTR_TO_CONST_STR;
+ if (meta->func_id == special_kfunc_list[KF_bpf_cast_to_kern_ctx] ||
+ meta->func_id == special_kfunc_list[KF_bpf_session_is_return] ||
+ meta->func_id == special_kfunc_list[KF_bpf_session_cookie])
+ arg_type = KF_ARG_PTR_TO_CTX;
+ else if (btf_is_prog_ctx_type(&env->log, meta->btf, t, resolve_prog_type(env->prog), arg))
+ arg_type = KF_ARG_PTR_TO_CTX;
+ else if (is_kfunc_arg_alloc_obj(meta->btf, &args[arg]))
+ arg_type = KF_ARG_PTR_TO_ALLOC_BTF_ID;
+ else if (is_kfunc_arg_refcounted_kptr(meta->btf, &args[arg]))
+ arg_type = KF_ARG_PTR_TO_REFCOUNTED_KPTR;
+ else if (is_kfunc_arg_dynptr(meta->btf, &args[arg]))
+ arg_type = KF_ARG_PTR_TO_DYNPTR;
+ else if (is_kfunc_arg_iter(meta, arg, &args[arg]))
+ arg_type = KF_ARG_PTR_TO_ITER;
+ else if (is_kfunc_arg_list_head(meta->btf, &args[arg]))
+ arg_type = KF_ARG_PTR_TO_LIST_HEAD;
+ else if (is_kfunc_arg_list_node(meta->btf, &args[arg]))
+ arg_type = KF_ARG_PTR_TO_LIST_NODE;
+ else if (is_kfunc_arg_rbtree_root(meta->btf, &args[arg]))
+ arg_type = KF_ARG_PTR_TO_RB_ROOT;
+ else if (is_kfunc_arg_rbtree_node(meta->btf, &args[arg]))
+ arg_type = KF_ARG_PTR_TO_RB_NODE;
+ else if (is_kfunc_arg_const_str(meta->btf, &args[arg]))
+ arg_type = KF_ARG_PTR_TO_CONST_STR;
+ else if (is_kfunc_arg_const_map(meta->btf, &args[arg]))
+ arg_type = KF_ARG_CONST_MAP_PTR;
+ else if (is_kfunc_arg_map(meta->btf, &args[arg]))
+ arg_type = KF_ARG_PTR_TO_BTF_ID;
+ else if (is_kfunc_arg_wq(meta->btf, &args[arg]))
+ arg_type = KF_ARG_PTR_TO_WORKQUEUE;
+ else if (is_kfunc_arg_timer(meta->btf, &args[arg]))
+ arg_type = KF_ARG_PTR_TO_TIMER;
+ else if (is_kfunc_arg_task_work(meta->btf, &args[arg]))
+ arg_type = KF_ARG_PTR_TO_TASK_WORK;
+ else if (is_kfunc_arg_irq_flag(meta->btf, &args[arg]))
+ arg_type = KF_ARG_PTR_TO_IRQ_FLAG;
+ else if (is_kfunc_arg_res_spin_lock(meta->btf, &args[arg]))
+ arg_type = KF_ARG_PTR_TO_RES_SPIN_LOCK;
+ else if (is_kfunc_arg_callback(env, meta->btf, &args[arg]))
+ arg_type = KF_ARG_PTR_TO_CALLBACK;
+ else if (is_kfunc_arg_arena(meta->btf, &args[arg])) {
+ if (!bpf_jit_supports_arena_args()) {
+ verbose(env, "JIT does not support kfunc %s() with arena pointer arguments\n",
+ meta->func_name);
+ return -ENOTSUPP;
+ }
+ if (!env->prog->aux->arena) {
+ verbose(env,
+ "%s arena pointer requires a program with an associated arena\n",
+ reg_arg_name(env, argno));
+ return -EINVAL;
+ }
+ if (reg_from_argno(argno) < 0) {
+ verbose(env, "%s arena pointer cannot be a stack argument\n",
+ reg_arg_name(env, argno));
+ return -EINVAL;
+ }
+ /*
+ * Both suffixes accept a constant zero. The function model determines
+ * whether the JIT rebases it to the arena base or preserves NULL.
+ * The common nullable path below records that verifier property.
+ */
+ arg_type = KF_ARG_PTR_TO_ARENA;
+ } else if (arg + 1 < nargs &&
+ (is_kfunc_arg_mem_size(meta->btf, &args[arg + 1]) ||
+ is_kfunc_arg_const_mem_size(meta->btf, &args[arg + 1]))) {
+ if (!btf_type_is_void(ref_t) && !btf_type_is_scalar(ref_t) &&
+ !__btf_type_is_scalar_struct(env, meta->btf, ref_t, 0)) {
+ verbose(env, "%s pointer type %s %s must point to void, scalar, or struct with scalar\n",
+ reg_arg_name(env, argno), btf_type_str(ref_t), ref_tname);
+ return -EINVAL;
+ }
+ arg_type = KF_ARG_PTR_TO_MEM;
+ } else if (btf_type_is_struct(ref_t))
+ /* A pointer to a struct without a size argument is classified as KF_ARG_PTR_TO_BTF_ID */
+ arg_type = KF_ARG_PTR_TO_BTF_ID;
+ else {
+ /*
+ * Otherwise this is a fixed-size memory buffer supported by
+ * check_helper_mem_access(): a pointer to a scalar or a struct of
+ * scalars. The access size is derived from the pointed-to BTF type.
+ */
+ if (!btf_type_is_scalar(ref_t) &&
+ !__btf_type_is_scalar_struct(env, meta->btf, ref_t, 0)) {
+ verbose(env, "%s pointer type %s %s must point to scalar, or struct with scalar\n",
+ reg_arg_name(env, argno), btf_type_str(ref_t), ref_tname);
+ return -EINVAL;
+ }
+ arg_type = KF_ARG_PTR_TO_MEM | MEM_FIXED_SIZE;
+ }
- if (is_kfunc_arg_map(meta->btf, &args[arg]))
- return KF_ARG_PTR_TO_MAP;
+ if (is_kfunc_arg_nullable(meta->btf, &args[arg]))
+ arg_type |= PTR_MAYBE_NULL;
- if (is_kfunc_arg_wq(meta->btf, &args[arg]))
- return KF_ARG_PTR_TO_WORKQUEUE;
+ return arg_type;
+}
- if (is_kfunc_arg_timer(meta->btf, &args[arg]))
- return KF_ARG_PTR_TO_TIMER;
+static int gen_kfunc_arg_proto(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
+ struct bpf_func_proto *proto)
+{
+ const struct btf *btf = meta->btf;
+ const struct btf_param *args;
+ u32 i, nargs;
+ int arg_type;
- if (is_kfunc_arg_task_work(meta->btf, &args[arg]))
- return KF_ARG_PTR_TO_TASK_WORK;
+ args = (const struct btf_param *)(meta->func_proto + 1);
+ nargs = btf_type_vlen(meta->func_proto);
+ if (nargs > MAX_BPF_FUNC_ARGS) {
+ verbose(env, "Function %s has %d > %d args\n", meta->func_name,
+ nargs, MAX_BPF_FUNC_ARGS);
+ return -EINVAL;
+ }
+ if (nargs > MAX_BPF_FUNC_REG_ARGS && !bpf_jit_supports_stack_args()) {
+ verbose(env, "JIT does not support kfunc %s() with %d args\n",
+ meta->func_name, nargs);
+ return -ENOTSUPP;
+ }
- if (is_kfunc_arg_irq_flag(meta->btf, &args[arg]))
- return KF_ARG_PTR_TO_IRQ_FLAG;
+ for (i = 0; i < nargs; i++) {
+ if (is_kfunc_arg_prog_aux(btf, &args[i]) ||
+ is_kfunc_arg_ignore(btf, &args[i]) ||
+ is_kfunc_arg_implicit(meta, i))
+ continue;
- if (is_kfunc_arg_res_spin_lock(meta->btf, &args[arg]))
- return KF_ARG_PTR_TO_RES_SPIN_LOCK;
+ arg_type = get_kfunc_arg_type(env, meta, args, i, nargs);
+ if (arg_type < 0)
+ return arg_type;
- if ((base_type(reg->type) == PTR_TO_BTF_ID || reg2btf_ids[base_type(reg->type)])) {
- if (!btf_type_is_struct(ref_t)) {
- verbose(env, "kernel function %s %s pointer type %s %s is not supported\n",
- meta->func_name, reg_arg_name(env, argno),
- btf_type_str(ref_t), ref_tname);
- return -EINVAL;
- }
- return KF_ARG_PTR_TO_BTF_ID;
+ proto->arg_type[i] = arg_type;
}
- if (is_kfunc_arg_callback(env, meta->btf, &args[arg]))
- return KF_ARG_PTR_TO_CALLBACK;
-
- /* This is the catch all argument type of register types supported by
- * check_helper_mem_access. However, we only allow when argument type is
- * pointer to scalar, or struct composed (recursively) of scalars. When
- * arg_mem_size is true, the pointer can be void *.
- */
- if (!btf_type_is_scalar(ref_t) && !__btf_type_is_scalar_struct(env, meta->btf, ref_t, 0) &&
- (arg_mem_size ? !btf_type_is_void(ref_t) : 1)) {
- verbose(env, "%s pointer type %s %s must point to %sscalar, or struct with scalar\n",
- reg_arg_name(env, argno),
- btf_type_str(ref_t), ref_tname, arg_mem_size ? "void, " : "");
- return -EINVAL;
- }
- return arg_mem_size ? KF_ARG_PTR_TO_MEM_SIZE : KF_ARG_PTR_TO_MEM;
+ return 0;
}
static int process_kf_arg_ptr_to_btf_id(struct bpf_verifier_env *env,
struct bpf_reg_state *reg,
const struct btf_type *ref_t,
const char *ref_tname, u32 ref_id,
- struct bpf_kfunc_call_arg_meta *meta,
+ struct bpf_call_arg_meta *meta,
int arg, argno_t argno)
{
const struct btf_type *reg_ref_t;
@@ -11392,7 +12178,8 @@ static int process_kf_arg_ptr_to_btf_id(struct bpf_verifier_env *env,
reg_ref_t = btf_type_skip_modifiers(reg_btf, reg_ref_id, &reg_ref_id);
reg_ref_tname = btf_name_by_offset(reg_btf, reg_ref_t->name_off);
struct_same = btf_struct_ids_match(&env->log, reg_btf, reg_ref_id, reg->var_off.value,
- meta->btf, ref_id, strict_type_match);
+ meta->btf, ref_id, strict_type_match,
+ !type_is_alloc(reg->type));
/* If kfunc is accepting a projection type (ie. __sk_buff), it cannot
* actually use it -- it must cast to the underlying type. So we allow
* caller to pass in the underlying type.
@@ -11409,7 +12196,7 @@ static int process_kf_arg_ptr_to_btf_id(struct bpf_verifier_env *env,
}
static int process_irq_flag(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,
- struct bpf_kfunc_call_arg_meta *meta)
+ struct bpf_call_arg_meta *meta)
{
int err, spi, kfunc_class = IRQ_NATIVE_KFUNC;
bool irq_save;
@@ -11433,6 +12220,12 @@ static int process_irq_flag(struct bpf_verifier_env *env, struct bpf_reg_state *
if (!is_irq_flag_reg_valid_uninit(env, reg)) {
verbose(env, "expected uninitialized irq flag as %s\n",
reg_arg_name(env, argno));
+ bpf_diag_res(env, env->insn_idx, "IRQ flag is already initialized",
+ "Saving IRQ state requires an uninitialized stack slot for "
+ "the IRQ flag, but this slot already contains tracked IRQ "
+ "flag state.",
+ "Use a fresh stack slot for this save operation, or restore "
+ "the existing IRQ flag before reusing the slot.");
return -EINVAL;
}
@@ -11449,6 +12242,11 @@ static int process_irq_flag(struct bpf_verifier_env *env, struct bpf_reg_state *
if (err) {
verbose(env, "expected an initialized irq flag as %s\n",
reg_arg_name(env, argno));
+ bpf_diag_res(env, env->insn_idx, "uninitialized IRQ flag restore",
+ "Restoring IRQ state requires a stack slot that was "
+ "initialized by a matching IRQ save operation on this path.",
+ "Pass the same stack slot that was previously initialized by "
+ "the matching IRQ save kfunc.");
return err;
}
@@ -11461,11 +12259,13 @@ static int process_irq_flag(struct bpf_verifier_env *env, struct bpf_reg_state *
err = unmark_stack_slot_irq_flag(env, reg, kfunc_class);
if (err)
return err;
+
+ if (!in_rcu_cs(env))
+ invalidate_rcu_protected_refs(env);
}
return 0;
}
-
static int ref_set_non_owning(struct bpf_verifier_env *env, struct bpf_reg_state *reg)
{
struct btf_record *rec = reg_btf_record(reg);
@@ -11491,8 +12291,10 @@ static void ref_convert_owning_non_owning(struct bpf_verifier_env *env, u32 id)
{
struct bpf_func_state *unused;
struct bpf_reg_state *reg;
+ int err;
- WARN_ON_ONCE(release_reference_nomark(env->cur_state, id));
+ err = release_reference_nomark(env, id);
+ WARN_ON_ONCE(err);
bpf_for_each_reg_in_vstate(env->cur_state, unused, reg, ({
if (reg->id == id) {
@@ -11600,20 +12402,6 @@ static bool is_bpf_rbtree_api_kfunc(u32 btf_id)
btf_id == special_kfunc_list[KF_bpf_rbtree_right];
}
-static bool is_bpf_iter_num_api_kfunc(u32 btf_id)
-{
- return btf_id == special_kfunc_list[KF_bpf_iter_num_new] ||
- btf_id == special_kfunc_list[KF_bpf_iter_num_next] ||
- btf_id == special_kfunc_list[KF_bpf_iter_num_destroy];
-}
-
-static bool is_bpf_graph_api_kfunc(u32 btf_id)
-{
- return is_bpf_list_api_kfunc(btf_id) ||
- is_bpf_rbtree_api_kfunc(btf_id) ||
- is_bpf_refcount_acquire_kfunc(btf_id);
-}
-
static bool is_bpf_res_spin_lock_kfunc(u32 btf_id)
{
return btf_id == special_kfunc_list[KF_bpf_res_spin_lock] ||
@@ -11622,24 +12410,16 @@ static bool is_bpf_res_spin_lock_kfunc(u32 btf_id)
btf_id == special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore];
}
-static bool is_bpf_arena_kfunc(u32 btf_id)
+static bool kfunc_spin_allowed(struct bpf_verifier_env *env, s32 func_id, s16 offset)
{
- return btf_id == special_kfunc_list[KF_bpf_arena_alloc_pages] ||
- btf_id == special_kfunc_list[KF_bpf_arena_free_pages] ||
- btf_id == special_kfunc_list[KF_bpf_arena_reserve_pages];
-}
+ struct bpf_kfunc_meta kfunc;
+ int err;
-static bool is_bpf_stream_kfunc(u32 btf_id)
-{
- return btf_id == special_kfunc_list[KF_bpf_stream_vprintk] ||
- btf_id == special_kfunc_list[KF_bpf_stream_print_stack];
-}
+ err = fetch_kfunc_meta(env, func_id, offset, &kfunc);
+ if (err || !kfunc.flags)
+ return false;
-static bool kfunc_spin_allowed(u32 btf_id)
-{
- return is_bpf_graph_api_kfunc(btf_id) || is_bpf_iter_num_api_kfunc(btf_id) ||
- is_bpf_res_spin_lock_kfunc(btf_id) || is_bpf_arena_kfunc(btf_id) ||
- is_bpf_stream_kfunc(btf_id);
+ return *kfunc.flags & KF_SPINLOCK_SAFE;
}
static bool is_sync_callback_calling_kfunc(u32 btf_id)
@@ -11734,7 +12514,7 @@ static bool check_kfunc_is_graph_node_api(struct bpf_verifier_env *env,
static int
__process_kf_arg_ptr_to_graph_root(struct bpf_verifier_env *env,
struct bpf_reg_state *reg, argno_t argno,
- struct bpf_kfunc_call_arg_meta *meta,
+ struct bpf_call_arg_meta *meta,
enum btf_field_type head_field_type,
struct btf_field **head_field)
{
@@ -11784,7 +12564,7 @@ __process_kf_arg_ptr_to_graph_root(struct bpf_verifier_env *env,
static int process_kf_arg_ptr_to_list_head(struct bpf_verifier_env *env,
struct bpf_reg_state *reg, argno_t argno,
- struct bpf_kfunc_call_arg_meta *meta)
+ struct bpf_call_arg_meta *meta)
{
return __process_kf_arg_ptr_to_graph_root(env, reg, argno, meta, BPF_LIST_HEAD,
&meta->arg_list_head.field);
@@ -11792,7 +12572,7 @@ static int process_kf_arg_ptr_to_list_head(struct bpf_verifier_env *env,
static int process_kf_arg_ptr_to_rbtree_root(struct bpf_verifier_env *env,
struct bpf_reg_state *reg, argno_t argno,
- struct bpf_kfunc_call_arg_meta *meta)
+ struct bpf_call_arg_meta *meta)
{
return __process_kf_arg_ptr_to_graph_root(env, reg, argno, meta, BPF_RB_ROOT,
&meta->arg_rbtree_root.field);
@@ -11801,7 +12581,7 @@ static int process_kf_arg_ptr_to_rbtree_root(struct bpf_verifier_env *env,
static int
__process_kf_arg_ptr_to_graph_node(struct bpf_verifier_env *env,
struct bpf_reg_state *reg, argno_t argno,
- struct bpf_kfunc_call_arg_meta *meta,
+ struct bpf_call_arg_meta *meta,
enum btf_field_type head_field_type,
enum btf_field_type node_field_type,
struct btf_field **node_field)
@@ -11839,7 +12619,8 @@ __process_kf_arg_ptr_to_graph_node(struct bpf_verifier_env *env,
et = btf_type_by_id(field->graph_root.btf, field->graph_root.value_btf_id);
t = btf_type_by_id(reg->btf, reg->btf_id);
if (!btf_struct_ids_match(&env->log, reg->btf, reg->btf_id, 0, field->graph_root.btf,
- field->graph_root.value_btf_id, true)) {
+ field->graph_root.value_btf_id, true,
+ !type_is_alloc(reg->type))) {
verbose(env, "operation on %s expects arg#1 %s at offset=%d "
"in struct %s, but arg is at offset=%d in struct %s\n",
btf_field_type_name(head_field_type),
@@ -11865,7 +12646,7 @@ __process_kf_arg_ptr_to_graph_node(struct bpf_verifier_env *env,
static int process_kf_arg_ptr_to_list_node(struct bpf_verifier_env *env,
struct bpf_reg_state *reg, argno_t argno,
- struct bpf_kfunc_call_arg_meta *meta)
+ struct bpf_call_arg_meta *meta)
{
return __process_kf_arg_ptr_to_graph_node(env, reg, argno, meta,
BPF_LIST_HEAD, BPF_LIST_NODE,
@@ -11874,7 +12655,7 @@ static int process_kf_arg_ptr_to_list_node(struct bpf_verifier_env *env,
static int process_kf_arg_ptr_to_rbtree_node(struct bpf_verifier_env *env,
struct bpf_reg_state *reg, argno_t argno,
- struct bpf_kfunc_call_arg_meta *meta)
+ struct bpf_call_arg_meta *meta)
{
return __process_kf_arg_ptr_to_graph_node(env, reg, argno, meta,
BPF_RB_ROOT, BPF_RB_NODE,
@@ -11903,7 +12684,7 @@ static bool check_css_task_iter_allowlist(struct bpf_verifier_env *env)
}
}
-static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_arg_meta *meta,
+static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
int insn_idx)
{
const char *func_name = meta->func_name, *ref_tname;
@@ -11917,18 +12698,8 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
args = (const struct btf_param *)(meta->func_proto + 1);
nargs = btf_type_vlen(meta->func_proto);
- if (nargs > MAX_BPF_FUNC_ARGS) {
- verbose(env, "Function %s has %d > %d args\n", func_name, nargs,
- MAX_BPF_FUNC_ARGS);
- return -EINVAL;
- }
- if (nargs > MAX_BPF_FUNC_REG_ARGS && !bpf_jit_supports_stack_args()) {
- verbose(env, "JIT does not support kfunc %s() with %d args\n",
- func_name, nargs);
- return -ENOTSUPP;
- }
- ret = check_outgoing_stack_args(env, caller, nargs);
+ ret = check_outgoing_stack_args(env, caller, nargs, func_name, btf, args);
if (ret)
return ret;
@@ -11941,9 +12712,9 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
enum bpf_arg_type arg_type = ARG_DONTCARE;
argno_t argno = argno_from_arg(i + 1);
int regno = reg_from_argno(argno);
- u32 ref_id, type_size;
- bool is_ret_buf_sz = false;
- int kf_arg_type;
+ bool btf_id_fixed_off_ok = true;
+ u32 ref_id = args[i].type, type_size;
+ int kf_arg_type = meta->fn->arg_type[i];
if (is_kfunc_arg_prog_aux(btf, &args[i])) {
/* Reject repeated use bpf_prog_aux */
@@ -11966,161 +12737,79 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
t = btf_type_skip_modifiers(btf, args[i].type, NULL);
- if (btf_type_is_scalar(t)) {
- if (reg->type != SCALAR_VALUE) {
- verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
- return -EINVAL;
- }
-
- if (is_kfunc_arg_constant(meta->btf, &args[i])) {
- if (meta->arg_constant.found) {
- verifier_bug(env, "only one constant argument permitted");
- return -EFAULT;
- }
- if (!tnum_is_const(reg->var_off)) {
- verbose(env, "%s must be a known constant\n",
- reg_arg_name(env, argno));
- return -EINVAL;
- }
- if (regno >= 0)
- ret = mark_chain_precision(env, regno);
- else
- ret = mark_stack_arg_precision(env, i);
- if (ret < 0)
- return ret;
- meta->arg_constant.found = true;
- meta->arg_constant.value = reg->var_off.value;
- } else if (is_kfunc_arg_scalar_with_name(btf, &args[i], "rdonly_buf_size")) {
- meta->r0_rdonly = true;
- is_ret_buf_sz = true;
- } else if (is_kfunc_arg_scalar_with_name(btf, &args[i], "rdwr_buf_size")) {
- is_ret_buf_sz = true;
- }
-
- if (is_ret_buf_sz) {
- if (meta->r0_size) {
- verbose(env, "2 or more rdonly/rdwr_buf_size parameters for kfunc");
- return -EINVAL;
- }
-
- if (!tnum_is_const(reg->var_off)) {
- verbose(env, "%s is not a const\n",
- reg_arg_name(env, argno));
- return -EINVAL;
- }
-
- meta->r0_size = reg->var_off.value;
- if (regno >= 0)
- ret = mark_chain_precision(env, regno);
- else
- ret = mark_stack_arg_precision(env, i);
- if (ret)
- return ret;
- }
- continue;
+ if (btf_type_is_ptr(t)) {
+ ref_t = btf_type_skip_modifiers(btf, t->type, &ref_id);
+ ref_tname = btf_name_by_offset(btf, ref_t->name_off);
}
- if (!btf_type_is_ptr(t)) {
- verbose(env, "Unrecognized %s type %s\n",
- reg_arg_name(env, argno), btf_type_str(t));
- return -EINVAL;
- }
+ if (btf_type_is_ptr(t) &&
+ (bpf_register_is_null(reg) || type_may_be_null(reg->type)) &&
+ !type_may_be_null(kf_arg_type)) {
+ const char *expected_type;
- if ((bpf_register_is_null(reg) || type_may_be_null(reg->type)) &&
- !is_kfunc_arg_nullable(meta->btf, &args[i])) {
+ expected_type = bpf_diag_fmt_btf_type(env, btf, args[i].type);
verbose(env, "Possibly NULL pointer passed to trusted %s\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Add a NULL check and call the kfunc only on the non-NULL path.",
+ "the pointer may be NULL, but this kfunc requires a non-NULL value of type %s",
+ expected_type);
return -EACCES;
}
if (regno == meta->release_regno && !is_kfunc_arg_dynptr(meta->btf, &args[i]) &&
!reg_is_referenced(env, reg) && !bpf_register_is_null(reg)) {
+ const char *expected_type;
+
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
verbose(env, "release kfunc %s expects referenced PTR_TO_BTF_ID passed to %s\n",
func_name, reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass the resource-owning pointer returned by the matching acquire kfunc, and avoid calling the release kfunc after ownership has already been transferred or released.",
+ "release kfuncs require a resource-owning value of type %s returned by a matching acquire kfunc",
+ expected_type);
return -EINVAL;
}
if (reg_is_referenced(env, reg))
update_ref_obj(&meta->ref_obj, reg);
- ref_t = btf_type_skip_modifiers(btf, t->type, &ref_id);
- ref_tname = btf_name_by_offset(btf, ref_t->name_off);
+ if (bpf_register_is_null(reg) && type_may_be_null(kf_arg_type)) {
+ ret = mark_arg_precision(env, argno);
+ if (ret)
+ return ret;
+ continue;
+ }
- kf_arg_type = get_kfunc_ptr_arg_type(env, caller, regs, meta, t, ref_t, ref_tname,
- args, i, nargs, argno, reg);
- if (kf_arg_type < 0)
- return kf_arg_type;
+ if (is_kfunc_arg_map(btf, &args[i])) {
+ ref_id = *reg2btf_ids[CONST_PTR_TO_MAP];
+ ref_t = btf_type_by_id(btf_vmlinux, ref_id);
+ ref_tname = btf_name_by_offset(btf, ref_t->name_off);
+ }
- switch (kf_arg_type) {
- case KF_ARG_PTR_TO_NULL:
- continue;
- case KF_ARG_PTR_TO_MAP:
- if (!reg->map_ptr) {
- verbose(env, "pointer in %s isn't map pointer\n",
- reg_arg_name(env, argno));
- return -EINVAL;
- }
- if (meta->map.ptr && (reg->map_ptr->record->wq_off >= 0 ||
- reg->map_ptr->record->task_work_off >= 0)) {
- /* Use map_uid (which is unique id of inner map) to reject:
- * inner_map1 = bpf_map_lookup_elem(outer_map, key1)
- * inner_map2 = bpf_map_lookup_elem(outer_map, key2)
- * if (inner_map1 && inner_map2) {
- * wq = bpf_map_lookup_elem(inner_map1);
- * if (wq)
- * // mismatch would have been allowed
- * bpf_wq_init(wq, inner_map2);
- * }
- *
- * Comparing map_ptr is enough to distinguish normal and outer maps.
- */
- if (meta->map.ptr != reg->map_ptr ||
- meta->map.uid != reg->map_uid) {
- if (reg->map_ptr->record->task_work_off >= 0) {
- verbose(env,
- "bpf_task_work pointer in R2 map_uid=%d doesn't match map pointer in R3 map_uid=%d\n",
- meta->map.uid, reg->map_uid);
- return -EINVAL;
- }
- verbose(env,
- "workqueue pointer in R1 map_uid=%d doesn't match map pointer in R2 map_uid=%d\n",
- meta->map.uid, reg->map_uid);
- return -EINVAL;
- }
- }
- meta->map.ptr = reg->map_ptr;
- meta->map.uid = reg->map_uid;
- fallthrough;
+ switch (base_type(kf_arg_type)) {
+ case KF_ARG_CONST:
+ case KF_ARG_CONST_MEM_SIZE:
+ case KF_ARG_MEM_SIZE:
+ case KF_ARG_ANYTHING:
+ case KF_ARG_CONST_ALLOC_SIZE_OR_ZERO:
case KF_ARG_PTR_TO_ALLOC_BTF_ID:
case KF_ARG_PTR_TO_BTF_ID:
- if (!is_trusted_reg(env, reg)) {
- if (!is_kfunc_rcu(meta)) {
- verbose(env, "%s must be referenced or trusted\n",
- reg_arg_name(env, argno));
- return -EINVAL;
- }
- if (!is_rcu_reg(reg)) {
- verbose(env, "%s must be a rcu pointer\n",
- reg_arg_name(env, argno));
- return -EINVAL;
- }
- }
- fallthrough;
+ case KF_ARG_CONST_MAP_PTR:
case KF_ARG_PTR_TO_ITER:
case KF_ARG_PTR_TO_LIST_HEAD:
case KF_ARG_PTR_TO_LIST_NODE:
case KF_ARG_PTR_TO_RB_ROOT:
case KF_ARG_PTR_TO_RB_NODE:
case KF_ARG_PTR_TO_MEM:
- case KF_ARG_PTR_TO_MEM_SIZE:
case KF_ARG_PTR_TO_CALLBACK:
- case KF_ARG_PTR_TO_REFCOUNTED_KPTR:
case KF_ARG_PTR_TO_CONST_STR:
case KF_ARG_PTR_TO_WORKQUEUE:
case KF_ARG_PTR_TO_TIMER:
case KF_ARG_PTR_TO_TASK_WORK:
case KF_ARG_PTR_TO_IRQ_FLAG:
case KF_ARG_PTR_TO_RES_SPIN_LOCK:
+ case KF_ARG_PTR_TO_ARENA:
break;
case KF_ARG_PTR_TO_DYNPTR:
arg_type = ARG_PTR_TO_DYNPTR;
@@ -12128,6 +12817,10 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
case KF_ARG_PTR_TO_CTX:
arg_type = ARG_PTR_TO_CTX;
break;
+ case KF_ARG_PTR_TO_REFCOUNTED_KPTR:
+ arg_type = ARG_PTR_TO_BTF_ID;
+ btf_id_fixed_off_ok = false;
+ break;
default:
verifier_bug(env, "unknown kfunc arg type %d", kf_arg_type);
return -EFAULT;
@@ -12135,15 +12828,76 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
if (regno == meta->release_regno)
arg_type |= OBJ_RELEASE;
- ret = check_func_arg_reg_off(env, reg, argno, arg_type);
+ ret = __check_func_arg_reg_off(env, reg, argno, arg_type,
+ btf_id_fixed_off_ok);
if (ret < 0)
return ret;
- switch (kf_arg_type) {
+ switch (base_type(kf_arg_type)) {
+ case KF_ARG_CONST:
+ if (reg->type != SCALAR_VALUE) {
+ verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass an integer scalar value for this argument, not a pointer or resource object.",
+ "the kfunc expects an integer scalar, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
+ return -EINVAL;
+ }
+
+ ret = process_const_arg(env, reg, argno, meta);
+ if (ret < 0) {
+ if (ret == -EINVAL)
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a compile-time constant or a value the verifier can prove is constant at this call.",
+ "the kfunc requires this scalar argument to be a verifier-known constant, but %s is variable on this path",
+ reg_arg_name(env, argno));
+ return ret;
+ }
+ break;
+ case KF_ARG_ANYTHING:
+ if (reg->type != SCALAR_VALUE) {
+ verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass an integer scalar value for this argument, not a pointer or resource object.",
+ "the kfunc expects an integer scalar, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
+ return -EINVAL;
+ }
+ break;
+ case KF_ARG_CONST_ALLOC_SIZE_OR_ZERO:
+ if (reg->type != SCALAR_VALUE) {
+ verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass an integer scalar value for this argument, not a pointer or resource object.",
+ "the kfunc expects an integer scalar, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
+ return -EINVAL;
+ }
+
+ if (is_kfunc_arg_scalar_with_name(btf, &args[i], "rdonly_buf_size"))
+ meta->r0_rdonly = true;
+ ret = process_const_alloc_mem_size(env, reg, argno, &meta->ret_mem);
+ if (ret < 0) {
+ if (ret == -EINVAL)
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a verifier-known constant size for this kfunc buffer argument.",
+ "the kfunc uses this argument as a return-buffer size, but %s is invalid or variable on this path",
+ reg_arg_name(env, argno));
+ return ret;
+ }
+ break;
case KF_ARG_PTR_TO_CTX:
if (reg->type != PTR_TO_CTX) {
verbose(env, "%s expected pointer to ctx, but got %s\n",
reg_arg_name(env, argno), reg_type_str(env, reg->type));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass the original program context pointer or preserve it before modifying registers.",
+ "the kfunc expects a context pointer, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
@@ -12154,6 +12908,13 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
meta->ret_btf_id = ret;
}
break;
+ case KF_ARG_PTR_TO_ARENA:
+ if (reg->type != PTR_TO_ARENA && reg->type != SCALAR_VALUE) {
+ verbose(env, "%s is not a pointer to arena or scalar\n",
+ reg_arg_name(env, argno));
+ return -EINVAL;
+ }
+ break;
case KF_ARG_PTR_TO_ALLOC_BTF_ID:
if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC)) {
if (!is_bpf_obj_drop_kfunc(meta->func_id)) {
@@ -12170,10 +12931,19 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
} else {
verbose(env, "%s expected pointer to allocated object\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a pointer returned by the matching BPF object allocation path.",
+ "the kfunc expects an allocated object pointer, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
if (!reg_is_referenced(env, reg)) {
verbose(env, "allocated object must be referenced\n");
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass the owned object pointer before it is released or transferred.",
+ "the allocated object pointer in %s must still carry verifier-tracked ownership, but this pointer no longer owns a live resource",
+ reg_arg_name(env, argno));
return -EINVAL;
}
if (meta->btf == btf_vmlinux) {
@@ -12210,8 +12980,8 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
dynptr_arg_type |= (unsigned int)get_dynptr_type_flag(parent_type);
}
- ret = process_dynptr_func(env, reg, argno, insn_idx, dynptr_arg_type,
- &meta->ref_obj, &meta->dynptr);
+ ret = process_dynptr_func(env, reg, argno, insn_idx, func_name,
+ dynptr_arg_type, &meta->ref_obj, &meta->dynptr);
if (ret < 0)
return ret;
break;
@@ -12308,75 +13078,181 @@ check_ok:
if (ret < 0)
return ret;
break;
- case KF_ARG_PTR_TO_MAP:
- /* If argument has '__map' suffix expect 'struct bpf_map *' */
- ref_id = *reg2btf_ids[CONST_PTR_TO_MAP];
- ref_t = btf_type_by_id(btf_vmlinux, ref_id);
- ref_tname = btf_name_by_offset(btf, ref_t->name_off);
- fallthrough;
- case KF_ARG_PTR_TO_BTF_ID:
- /* Only base_type is checked, further checks are done here */
- if ((base_type(reg->type) != PTR_TO_BTF_ID ||
- (bpf_type_has_unsafe_modifiers(reg->type) && !is_rcu_reg(reg))) &&
- !reg2btf_ids[base_type(reg->type)]) {
- verbose(env, "%s is %s ", reg_arg_name(env, argno),
- reg_type_str(env, reg->type));
- verbose(env, "expected %s or socket\n",
- reg_type_str(env, base_type(reg->type) |
- (type_flag(reg->type) & BPF_REG_TRUSTED_MODIFIERS)));
+ case KF_ARG_CONST_MAP_PTR:
+ if (base_type(reg->type) != CONST_PTR_TO_MAP ||
+ type_may_be_null(reg->type)) {
+ verbose(env, "pointer in %s isn't map pointer\n",
+ reg_arg_name(env, argno));
return -EINVAL;
}
- ret = process_kf_arg_ptr_to_btf_id(env, reg, ref_t, ref_tname, ref_id, meta, i, argno);
+ ret = process_map_ptr_arg(env, reg, argno, meta);
if (ret < 0)
return ret;
break;
- case KF_ARG_PTR_TO_MEM:
- resolve_ret = btf_resolve_size(btf, ref_t, &type_size);
- if (IS_ERR(resolve_ret)) {
- verbose(env, "%s reference type('%s %s') size cannot be determined: %ld\n",
- reg_arg_name(env, argno), btf_type_str(ref_t),
- ref_tname, PTR_ERR(resolve_ret));
+ case KF_ARG_PTR_TO_BTF_ID:
+ /* Only base_type is checked, further checks are done here */
+ if (base_type(reg->type) == PTR_TO_BTF_ID ||
+ reg2btf_ids[base_type(reg->type)]) {
+ if (!is_trusted_reg(env, reg) ||
+ bpf_type_has_unsafe_modifiers(reg->type)) {
+ if (!is_kfunc_rcu(meta)) {
+ const char *expected_type;
+
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
+ verbose(env, "%s must be referenced or trusted\n",
+ reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a pointer acquired from a verifier-tracked source, or call this kfunc only inside the required protection if it accepts RCU pointers.",
+ "the kfunc requires a trusted or resource-owning pointer to %s, but %s is %s",
+ expected_type,
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
+ return -EINVAL;
+ }
+ if (!is_rcu_reg(reg)) {
+ const char *expected_type;
+
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
+ verbose(env, "%s must be a rcu pointer\n",
+ reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Use this kfunc with a pointer that is valid in an RCU read lock region.",
+ "the kfunc requires an RCU-protected pointer to %s, but %s is %s",
+ expected_type,
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
+ return -EINVAL;
+ }
+ }
+
+ ret = process_kf_arg_ptr_to_btf_id(env, reg, ref_t, ref_tname, ref_id, meta, i, argno);
+ if (ret < 0)
+ return ret;
+ break;
+ }
+
+ if (!__btf_type_is_scalar_struct(env, meta->btf, ref_t, 0)) {
+ enum bpf_reg_type reg2btf_type = lookup_reg2btf_ids(ref_id);
+ const char *expected_type;
+
+ verbose(env, "%s is %s expected %s %s",
+ reg_arg_name(env, argno), reg_type_str(env, reg->type),
+ btf_type_str(ref_t), ref_tname);
+ if (reg2btf_type != NOT_INIT)
+ verbose(env, " or %s", reg_type_str(env, reg2btf_type));
+ verbose(env, "\n");
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a verifier-tracked pointer to the expected kernel object type, not a pointer to stack storage or another memory buffer.",
+ "the kfunc expects a pointer to %s, but this argument is %s and cannot be used as that kernel object pointer",
+ expected_type,
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
- ret = check_mem_reg(env, reg, argno, type_size);
- if (ret < 0)
- return ret;
- break;
- case KF_ARG_PTR_TO_MEM_SIZE:
- {
- struct bpf_reg_state *buff_reg = reg;
- const struct btf_param *buff_arg = &args[i];
- struct bpf_reg_state *size_reg = get_func_arg_reg(caller, regs, i + 1);
- const struct btf_param *size_arg = &args[i + 1];
- argno_t next_argno = argno_from_arg(i + 2);
-
- if (!bpf_register_is_null(buff_reg) || !is_kfunc_arg_nullable(meta->btf, buff_arg)) {
- ret = check_kfunc_mem_size_reg(env, buff_reg, size_reg,
- argno, next_argno);
+
+ /*
+ * If the register does not contain btf id but the argument type is a pointer to
+ * scalar-only struct, allow verifying it as a fixed size memory.
+ */
+ kf_arg_type = KF_ARG_PTR_TO_MEM | MEM_FIXED_SIZE;
+ fallthrough;
+ case KF_ARG_PTR_TO_MEM:
+ if (kf_arg_type & MEM_FIXED_SIZE) {
+ bool known_memory;
+
+ resolve_ret = btf_resolve_size(btf, ref_t, &type_size);
+ if (IS_ERR(resolve_ret)) {
+ verbose(env, "%s reference type('%s %s') size cannot be determined: %ld\n",
+ reg_arg_name(env, argno), btf_type_str(ref_t),
+ ref_tname, PTR_ERR(resolve_ret));
+ return -EINVAL;
+ }
+ ret = check_mem_reg(env, reg, argno, type_size, BPF_READ | BPF_WRITE,
+ meta, &known_memory);
if (ret < 0) {
- verbose(env, "%s and ", reg_arg_name(env, argno));
- verbose(env, "%s memory, len pair leads to invalid memory access\n",
- reg_arg_name(env, next_argno));
+ const char *expected_type;
+
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
+ if (known_memory)
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, func_name,
+ "Pass memory with at least the required number of accessible bytes and suitable read and write access.",
+ "the kfunc expects %u bytes of memory for %s, but the verifier cannot prove that %s provides a readable and writable range of that size",
+ type_size, expected_type,
+ bpf_diag_reg_type_plain(env, reg->type));
+ else
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, func_name,
+ "Pass stack, map, context, or other verifier-known memory of the expected type and size, not an integer cast to a pointer.",
+ "the kfunc expects %u bytes of memory for %s, but it is %s and not verifier-known memory",
+ type_size, expected_type,
+ bpf_diag_reg_type_plain(env, reg->type));
return ret;
}
}
+ break;
+ case KF_ARG_CONST_MEM_SIZE:
+ ret = process_const_arg(env, reg, argno, meta);
+ if (ret < 0) {
+ if (ret == -EINVAL)
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a compile-time constant or a value the verifier can prove is constant at this call.",
+ "the kfunc requires this memory size to be a verifier-known constant, but %s is variable on this path",
+ reg_arg_name(env, argno));
+ return ret;
+ }
+ fallthrough;
+ case KF_ARG_MEM_SIZE:
+ {
+ struct bpf_reg_state *buff_reg = get_func_arg_reg(caller, regs, i - 1);
+ struct bpf_reg_state *size_reg = reg;
+ argno_t buff_argno = argno_from_arg(i);
+ enum bpf_mem_size_failure failure;
- if (is_kfunc_arg_const_mem_size(meta->btf, size_arg, size_reg)) {
- if (meta->arg_constant.found) {
- verifier_bug(env, "only one constant argument permitted");
- return -EFAULT;
- }
- if (!tnum_is_const(size_reg->var_off)) {
- verbose(env, "%s must be a known constant\n",
- reg_arg_name(env, next_argno));
- return -EINVAL;
- }
- meta->arg_constant.found = true;
- meta->arg_constant.value = size_reg->var_off.value;
+ if (reg->type != SCALAR_VALUE) {
+ verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass an integer scalar length for this memory argument.",
+ "the kfunc expects a scalar memory size, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
+ return -EINVAL;
}
- /* Skip next '__sz' or '__szk' argument */
- i++;
+ if (bpf_register_is_null(buff_reg))
+ break;
+
+ ret = check_mem_size_reg(env, buff_reg, size_reg, buff_argno, argno,
+ BPF_READ | BPF_WRITE, true, meta, &failure);
+ if (ret < 0) {
+ const char *buff_arg, *size_arg;
+
+ buff_arg = bpf_diag_arg_name(env, buff_argno);
+ size_arg = bpf_diag_arg_name(env, argno);
+ verbose(env, "%s and ", reg_arg_name(env, buff_argno));
+ verbose(env, "%s memory, len pair leads to invalid memory access\n",
+ reg_arg_name(env, argno));
+ if (failure == BPF_MEM_SIZE_FAIL_MEMORY) {
+ bpf_diag_call_arg_fmt(env, insn_idx, buff_argno, func_name,
+ "Pass a stack, map, context, or other verifier-known memory pointer, and keep the paired length within that object.",
+ "it is the memory pointer in a memory/length pair with %s, but %s does not describe verifier-readable memory for the requested length",
+ size_arg, buff_arg);
+ } else if (failure == BPF_MEM_SIZE_FAIL_SIZE) {
+ if (reg_smin(size_reg) < 0)
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, func_name,
+ "Constrain the memory size to a non-negative value smaller than BPF_MAX_VAR_SIZ before this call.",
+ "the memory size in %s may be negative because its signed minimum is %lld",
+ size_arg, reg_smin(size_reg));
+ else
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, func_name,
+ "Constrain the memory size to a non-negative value smaller than BPF_MAX_VAR_SIZ before this call.",
+ "the memory size in %s may reach %llu bytes, but variable memory accesses must stay below %u bytes",
+ size_arg, reg_umax(size_reg), BPF_MAX_VAR_SIZ);
+ }
+ return ret;
+ }
break;
}
case KF_ARG_PTR_TO_CALLBACK:
@@ -12390,9 +13266,14 @@ check_ok:
if (!type_is_ptr_alloc_obj(reg->type)) {
verbose(env, "%s is neither owning or non-owning ref\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass an owning or non-owning pointer to a BPF-managed object containing a bpf_refcount field.",
+ "the kfunc expects a pointer to a BPF-managed refcounted object, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
- if (!type_is_non_owning_ref(reg->type))
+ if (!type_is_non_owning_ref(reg->type) && reg_is_referenced(env, reg))
meta->arg_owning_ref = true;
rec = reg_btf_record(reg);
@@ -12414,6 +13295,11 @@ check_ok:
if (reg->type != PTR_TO_MAP_VALUE) {
verbose(env, "%s doesn't point to a const string\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a constant string pointer that the verifier recognizes, such as a string stored in a read-only map value.",
+ "the kfunc expects a pointer to a constant string stored in verifier-known memory, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
ret = check_arg_const_str(env, reg, argno);
@@ -12436,7 +13322,7 @@ check_ok:
reg_arg_name(env, argno));
return -EINVAL;
}
- ret = process_timer_kfunc(env, reg, argno, meta);
+ ret = process_timer_func(env, reg, argno, &meta->map);
if (ret < 0)
return ret;
break;
@@ -12454,6 +13340,11 @@ check_ok:
if (reg->type != PTR_TO_STACK) {
verbose(env, "%s doesn't point to an irq flag on stack\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass the same stack slot used by bpf_local_irq_save() or bpf_res_spin_lock_irqsave().",
+ "the kfunc expects a stack pointer to an IRQ flag slot, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
ret = process_irq_flag(env, reg, argno, meta);
@@ -12464,6 +13355,11 @@ check_ok:
{
int flags = PROCESS_RES_LOCK;
+ if (in_rbtree_lock_required_cb(env)) {
+ verbose(env, "can't res_spin_{lock,unlock} in rbtree cb\n");
+ return -EACCES;
+ }
+
if (reg->type != PTR_TO_MAP_VALUE && reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
verbose(env, "%s doesn't point to map value or allocated object\n",
reg_arg_name(env, argno));
@@ -12492,16 +13388,17 @@ check_ok:
int bpf_fetch_kfunc_arg_meta(struct bpf_verifier_env *env,
s32 func_id,
s16 offset,
- struct bpf_kfunc_call_arg_meta *meta)
+ struct bpf_call_arg_meta *meta)
{
struct bpf_kfunc_meta kfunc;
int err;
+ memset(meta, 0, sizeof(*meta));
+
err = fetch_kfunc_meta(env, func_id, offset, &kfunc);
if (err)
return err;
- memset(meta, 0, sizeof(*meta));
meta->btf = kfunc.btf;
meta->func_id = kfunc.id;
meta->func_proto = kfunc.proto;
@@ -12651,7 +13548,7 @@ s64 bpf_kfunc_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn *
int arg, int insn_idx)
{
struct bpf_insn_aux_data *aux = &env->insn_aux_data[insn_idx];
- struct bpf_kfunc_call_arg_meta meta;
+ struct bpf_call_arg_meta meta;
const struct btf_param *args;
const struct btf_type *t, *ref_t;
const struct btf *btf;
@@ -12712,7 +13609,7 @@ out:
* 0 - fall-through to 'else' branch
* < 0 - not fall-through to 'else' branch, return error
*/
-static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_kfunc_call_arg_meta *meta,
+static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
struct bpf_reg_state *regs, struct bpf_insn_aux_data *insn_aux,
const struct btf_type *ptr_type, struct btf *desc_btf)
{
@@ -12891,11 +13788,12 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
struct bpf_reg_state *regs = cur_regs(env);
const char *func_name, *ptr_type_name;
const struct btf_type *t, *ptr_type;
- struct bpf_kfunc_call_arg_meta meta;
+ struct bpf_call_arg_meta meta;
struct bpf_insn_aux_data *insn_aux;
+ const char *operation;
int err, insn_idx = *insn_idx_p;
- const struct btf_param *args;
u32 i, nargs, ptr_type_id;
+ struct bpf_kfunc_desc *desc;
struct btf *desc_btf;
int id;
@@ -12904,14 +13802,26 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
return 0;
err = bpf_fetch_kfunc_arg_meta(env, insn->imm, insn->off, &meta);
- if (err == -EACCES && meta.func_name)
+ if (err == -EACCES && meta.func_name) {
verbose(env, "calling kernel function %s is not allowed\n", meta.func_name);
+ operation = bpf_diag_fmt(env, "kfunc %s", meta.func_name);
+ bpf_diag_policy(
+ env, insn_idx, operation, "this program cannot call the kfunc",
+ "Use a kfunc allowed for this program type and attach point, or change the program context.");
+ }
if (err)
return err;
desc_btf = meta.btf;
func_name = meta.func_name;
insn_aux = &env->insn_aux_data[insn_idx];
+ desc = find_kfunc_desc(env->prog, insn->imm, insn->off);
+ if (!desc) {
+ verifier_bug(env, "kfunc descriptor not found for func_id %u", insn->imm);
+ return -EFAULT;
+ }
+ meta.fn = &desc->proto;
+
insn_aux->is_iter_next = bpf_is_iter_next_kfunc(&meta);
if (!insn->off &&
@@ -12938,7 +13848,6 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
verbose(env, "failed to mark s32 range for retval in forked state for lock\n");
return err;
}
- __mark_btf_func_reg_size(env, regs, BPF_REG_0, sizeof(u32));
} else if (!insn->off && insn->imm == special_kfunc_list[KF___bpf_trap]) {
verbose(env, "unexpected __bpf_trap() due to uninitialized variable?\n");
return -EFAULT;
@@ -12946,12 +13855,28 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (is_kfunc_destructive(&meta) && !capable(CAP_SYS_BOOT)) {
verbose(env, "destructive kfunc calls require CAP_SYS_BOOT capability\n");
+ operation = bpf_diag_fmt(env, "destructive kfunc %s", meta.func_name);
+ bpf_diag_policy(
+ env, insn_idx, operation, "destructive kfuncs require CAP_SYS_BOOT",
+ "Load the program with CAP_SYS_BOOT, or avoid destructive kfuncs.");
return -EACCES;
}
+ if (is_kfunc_perfmon(&meta) && !env->allow_ptr_leaks) {
+ verbose(env, "%s is allowed only to CAP_PERFMON and CAP_SYS_ADMIN\n",
+ func_name);
+ operation = bpf_diag_fmt(env, "kfunc %s", func_name);
+ bpf_diag_policy(env, insn_idx, operation, "the kfunc requires CAP_PERFMON",
+ "Load the program with CAP_PERFMON, or avoid the kfunc.");
+ return -EPERM;
+ }
+
sleepable = bpf_is_kfunc_sleepable(&meta);
if (sleepable && !in_sleepable(env)) {
verbose(env, "program must be sleepable to call sleepable kfunc %s\n", func_name);
+ operation = bpf_diag_fmt(env, "sleepable kfunc %s", func_name);
+ bpf_diag_ctx_forbidden(env, insn_idx, operation,
+ "Mark the program sleepable if the program type allows it, or use a non-sleepable kfunc.");
return -EACCES;
}
@@ -12989,11 +13914,6 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
}
}
- if (meta.func_id == special_kfunc_list[KF_bpf_session_cookie]) {
- meta.r0_size = sizeof(u64);
- meta.r0_rdonly = false;
- }
-
if (is_bpf_wq_set_callback_kfunc(meta.func_id)) {
err = push_callback_call(env, insn, insn_idx, meta.subprogno,
set_timer_callback_state);
@@ -13022,26 +13942,46 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (rcu_lock) {
env->cur_state->active_rcu_locks++;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_RCU, true,
+ env->cur_state->active_rcu_locks);
} else if (rcu_unlock) {
if (env->cur_state->active_rcu_locks == 0) {
verbose(env, "unmatched rcu read unlock (kernel function %s)\n", func_name);
+ bpf_diag_ctx_underflow(
+ env, insn_idx, func_name, BPF_DIAG_CONTEXT_RCU,
+ "Remove the extra bpf_rcu_read_unlock() call, or ensure this path first enters an RCU read lock region.");
return -EINVAL;
}
- if (--env->cur_state->active_rcu_locks == 0)
+ env->cur_state->active_rcu_locks--;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_RCU, false,
+ env->cur_state->active_rcu_locks);
+ if (!in_rcu_cs(env))
invalidate_rcu_protected_refs(env);
} else if (preempt_disable) {
env->cur_state->active_preempt_locks++;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_PREEMPT, true,
+ env->cur_state->active_preempt_locks);
} else if (preempt_enable) {
if (env->cur_state->active_preempt_locks == 0) {
verbose(env, "unmatched attempt to enable preemption (kernel function %s)\n", func_name);
+ bpf_diag_ctx_underflow(
+ env, insn_idx, func_name, BPF_DIAG_CONTEXT_PREEMPT,
+ "Remove the extra bpf_preempt_enable() call, or ensure this path first disables preemption.");
return -EINVAL;
}
env->cur_state->active_preempt_locks--;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_PREEMPT, false,
+ env->cur_state->active_preempt_locks);
+ if (!in_rcu_cs(env))
+ invalidate_rcu_protected_refs(env);
}
if (sleepable && !in_sleepable_context(env)) {
verbose(env, "kernel func %s is sleepable within %s\n",
func_name, non_sleepable_context_description(env));
+ operation = bpf_diag_fmt(env, "sleepable kfunc %s", func_name);
+ bpf_diag_ctx_forbidden(env, insn_idx, operation,
+ "Move the kfunc call outside the critical section, or use a non-sleepable kfunc.");
return -EACCES;
}
@@ -13052,6 +13992,9 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (is_kfunc_rcu_protected(&meta) && !in_rcu_cs(env)) {
verbose(env, "kernel func %s requires RCU critical section protection\n", func_name);
+ bpf_diag_ctx_required(
+ env, insn_idx, func_name, BPF_DIAG_CONTEXT_RCU,
+ "Call this kfunc between bpf_rcu_read_lock() and bpf_rcu_read_unlock(), keeping all exit paths balanced.");
return -EACCES;
}
@@ -13089,11 +14032,12 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
}
}
+ bpf_diag_record_caller_saved(env, regs);
+ bpf_diag_mod_begin(env, &regs[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
for (i = 0; i < CALLER_SAVED_REGS; i++) {
u32 regno = caller_saved[i];
bpf_mark_reg_not_init(env, &regs[regno]);
- regs[regno].subreg_def = DEF_NOT_SUBREG;
}
invalidate_outgoing_stack_args(env, cur_func(env));
@@ -13115,7 +14059,6 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (meta.btf == btf_vmlinux && (meta.func_id == special_kfunc_list[KF_bpf_res_spin_lock] ||
meta.func_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave]))
__mark_reg_const_zero(env, &regs[BPF_REG_0]);
- mark_btf_func_reg_size(env, BPF_REG_0, t->size);
} else if (btf_type_is_ptr(t)) {
ptr_type = btf_type_skip_modifiers(desc_btf, t->type, &ptr_type_id);
err = check_special_kfunc(env, &meta, regs, insn_aux, ptr_type, desc_btf);
@@ -13126,15 +14069,19 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
/* kfunc returning 'void *' is equivalent to returning scalar */
mark_reg_unknown(env, regs, BPF_REG_0);
} else if (!__btf_type_is_struct(ptr_type)) {
- if (!meta.r0_size) {
+ if (!meta.ret_mem.found) {
__u32 sz;
if (!IS_ERR(btf_resolve_size(desc_btf, ptr_type, &sz))) {
- meta.r0_size = sz;
+ meta.ret_mem.found = true;
+ meta.ret_mem.size = sz;
meta.r0_rdonly = true;
}
+
+ if (meta.func_id == special_kfunc_list[KF_bpf_session_cookie])
+ meta.r0_rdonly = false;
}
- if (!meta.r0_size) {
+ if (!meta.ret_mem.found) {
ptr_type_name = btf_name_by_offset(desc_btf,
ptr_type->name_off);
verbose(env,
@@ -13147,7 +14094,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
mark_reg_known_zero(env, regs, BPF_REG_0);
regs[BPF_REG_0].type = PTR_TO_MEM;
- regs[BPF_REG_0].mem_size = meta.r0_size;
+ regs[BPF_REG_0].mem_size = meta.ret_mem.size;
if (meta.r0_rdonly)
regs[BPF_REG_0].type |= MEM_RDONLY;
@@ -13202,7 +14149,6 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
/* For mark_ptr_or_null_reg, see 93c230e3f5bd6 */
regs[BPF_REG_0].id = ++env->id_gen;
}
- mark_btf_func_reg_size(env, BPF_REG_0, sizeof(void *));
if (is_kfunc_acquire(&meta)) {
id = acquire_reference(env, insn_idx, 0);
if (id < 0)
@@ -13239,17 +14185,11 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
caller_info->stack_arg_cnt = stack_arg_cnt;
}
- args = (const struct btf_param *)(meta.func_proto + 1);
- for (i = 0; i < min_t(int, nargs, MAX_BPF_FUNC_REG_ARGS); i++) {
- u32 regno = i + 1;
-
- t = btf_type_skip_modifiers(desc_btf, args[i].type, NULL);
- if (btf_type_is_ptr(t))
- mark_btf_func_reg_size(env, regno, sizeof(void *));
- else
- /* scalar. ensured by check_kfunc_args() */
- mark_btf_func_reg_size(env, regno, t->size);
- }
+ /*
+ * Record R0 before process_iter_next_call() snapshots the alternate
+ * iterator path's diagnostic position.
+ */
+ bpf_diag_mod_end(env);
if (bpf_is_iter_next_kfunc(&meta)) {
err = process_iter_next_call(env, insn_idx, &meta);
@@ -13530,23 +14470,21 @@ static void sanitize_mark_insn_seen(struct bpf_verifier_env *env)
env->insn_aux_data[env->insn_idx].seen = env->pass_cnt;
}
-static int sanitize_err(struct bpf_verifier_env *env,
- const struct bpf_insn *insn, int reason,
- const struct bpf_reg_state *off_reg,
- const struct bpf_reg_state *dst_reg)
+static int sanitize_err(struct bpf_verifier_env *env, const struct bpf_insn *insn, int reason)
{
static const char *err = "pointer arithmetic with it prohibited for !root";
const char *op = BPF_OP(insn->code) == BPF_ADD ? "add" : "sub";
u32 dst = insn->dst_reg, src = insn->src_reg;
+ struct bpf_reg_state *regs = cur_regs(env);
switch (reason) {
case REASON_BOUNDS:
verbose(env, "R%d has unknown scalar with mixed signed bounds, %s\n",
- off_reg == dst_reg ? dst : src, err);
+ regs[src].type == SCALAR_VALUE ? src : dst, err);
break;
case REASON_TYPE:
verbose(env, "R%d has pointer with unsupported alu operation, %s\n",
- off_reg == dst_reg ? src : dst, err);
+ regs[src].type == SCALAR_VALUE ? dst : src, err);
break;
case REASON_PATHS:
verbose(env, "R%d tried to %s from different maps, paths or scalars, %s\n",
@@ -13636,9 +14574,8 @@ static int sanitize_check_bounds(struct bpf_verifier_env *env,
* If we return -EACCES, caller may want to try again treating pointer as a
* scalar. So we only emit a diagnostic if !env->allow_ptr_leaks.
*/
-static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
- struct bpf_insn *insn,
- const struct bpf_reg_state *ptr_reg,
+static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, struct bpf_insn *insn,
+ u32 ptr_regno, const struct bpf_reg_state *ptr_reg,
const struct bpf_reg_state *off_reg)
{
struct bpf_verifier_state *vstate = env->cur_state;
@@ -13650,6 +14587,7 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
struct bpf_sanitize_info info = {};
u8 opcode = BPF_OP(insn->code);
u32 dst = insn->dst_reg;
+ const char *reason;
int ret, bounds_ret;
dst_reg = &regs[dst];
@@ -13673,22 +14611,27 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
verbose(env,
"R%d 32-bit pointer arithmetic prohibited\n",
dst);
+ reason = bpf_diag_fmt(
+ env, "R%d holds %s. 32-bit ALU operations on pointers discard pointer tracking, so the verifier cannot keep the result as a safe pointer.",
+ ptr_regno, bpf_diag_reg_type_plain(env, ptr_reg->type));
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "32-bit pointer arithmetic", reason,
+ "Use a 64-bit ALU instruction with an allowed, bounded scalar offset.");
return -EACCES;
}
if (ptr_reg->type & PTR_MAYBE_NULL) {
verbose(env, "R%d pointer arithmetic on %s prohibited, null-check it first\n",
dst, reg_type_str(env, ptr_reg->type));
+ reason = bpf_diag_fmt(
+ env, "R%d may be NULL (%s). Pointer arithmetic is allowed only after the program proves the pointer is non-NULL on this path.",
+ ptr_regno, reg_type_str(env, ptr_reg->type));
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer arithmetic before NULL check", reason,
+ "Make sure that a NULL check precedes any arithmetic performed on the pointer.");
return -EACCES;
}
- /*
- * Accesses to untrusted PTR_TO_MEM are done through probe
- * instructions, hence no need to track offsets.
- */
- if (base_type(ptr_reg->type) == PTR_TO_MEM && (ptr_reg->type & PTR_UNTRUSTED))
- return 0;
-
switch (base_type(ptr_reg->type)) {
case PTR_TO_CTX:
case PTR_TO_MAP_VALUE:
@@ -13715,29 +14658,64 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
default:
verbose(env, "R%d pointer arithmetic on %s prohibited\n",
dst, reg_type_str(env, ptr_reg->type));
+ reason = bpf_diag_fmt(
+ env, "R%d holds %s. This pointer kind does not allow offset arithmetic.",
+ ptr_regno, bpf_diag_reg_type_plain(env, ptr_reg->type));
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer arithmetic is not allowed", reason,
+ "Do not change this pointer's offset; use it only in operations accepted for its kind.");
return -EACCES;
}
- /* In case of 'scalar += pointer', dst_reg inherits pointer type and id.
- * The id may be overwritten later if we create a new variable offset.
+ /* For 'scalar += pointer', dst_reg inherits the complete pointer
+ * register state. Individual fields may be adjusted later by pointer
+ * arithmetic. Callers guarantee that below does not overwrite off_reg.
*/
- dst_reg->type = ptr_reg->type;
- dst_reg->id = ptr_reg->id;
+ if (dst_reg != ptr_reg)
+ *dst_reg = *ptr_reg;
- if (!check_reg_sane_offset_scalar(env, off_reg, ptr_reg->type) ||
- !check_reg_sane_offset_ptr(env, ptr_reg, ptr_reg->type))
- return -EINVAL;
+ /*
+ * Accesses to untrusted PTR_TO_MEM are done through probe
+ * instructions, hence no need to track offsets.
+ */
+ if (base_type(ptr_reg->type) == PTR_TO_MEM && (ptr_reg->type & PTR_UNTRUSTED))
+ return 0;
- /* pointer types do not carry 32-bit bounds at the moment. */
- __mark_reg32_unbounded(dst_reg);
+ if (!check_reg_sane_offset_scalar(env, off_reg, ptr_reg->type)) {
+ reason = bpf_diag_fmt(
+ env, "The scalar offset used with R%d is unbounded or outside the verifier's safe pointer-offset range [-%u, %u].",
+ ptr_regno, BPF_MAX_VAR_OFF, BPF_MAX_VAR_OFF);
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer offset is not safe", reason,
+ "Clamp or bounds-check the scalar offset before applying it to the pointer.");
+ return -EINVAL;
+ }
+ if (!check_reg_sane_offset_ptr(env, ptr_reg, ptr_reg->type)) {
+ reason = bpf_diag_fmt(
+ env, "R%d already has an offset outside the verifier's safe range [-%u, %u] for %s.",
+ ptr_regno, BPF_MAX_VAR_OFF, BPF_MAX_VAR_OFF,
+ bpf_diag_reg_type_plain(env, ptr_reg->type));
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer offset is not safe", reason,
+ "Keep the base pointer within the verifier's allowed offset range before applying more arithmetic.");
+ return -EINVAL;
+ }
if (sanitize_needed(opcode)) {
ret = sanitize_ptr_alu(env, insn, ptr_reg, off_reg, dst_reg,
&info, false);
if (ret < 0)
- return sanitize_err(env, insn, ret, off_reg, dst_reg);
+ return sanitize_err(env, insn, ret);
}
+ /*
+ * Pointer types do not carry 32-bit bounds at the moment. Blank r32
+ * only after sanitize_ptr_alu() may have snapshotted dst_reg into a
+ * speculative path: otherwise reg_bounds_sanity_check() might hit some
+ * constraints violations.
+ */
+ __mark_reg32_unbounded(dst_reg);
+
switch (opcode) {
case BPF_ADD:
/*
@@ -13765,10 +14743,17 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
}
break;
case BPF_SUB:
- if (dst_reg == off_reg) {
+ if (dst_reg != ptr_reg) {
/* scalar -= pointer. Creates an unknown scalar */
verbose(env, "R%d tried to subtract pointer from scalar\n",
dst);
+ reason = bpf_diag_fmt(
+ env, "This operation subtracts pointer register R%d from scalar register R%d. "
+ "The verifier only tracks pointer-minus-scalar arithmetic for allowed pointer types.",
+ ptr_regno, dst);
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer subtracted from scalar", reason,
+ "Keep the pointer as the base; only add or subtract bounded scalars when permitted.");
return -EACCES;
}
/* We don't allow subtraction from FP, because (according to
@@ -13778,6 +14763,12 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
if (ptr_reg->type == PTR_TO_STACK) {
verbose(env, "R%d subtraction from stack pointer prohibited\n",
dst);
+ reason = bpf_diag_fmt(
+ env, "R%d is a stack pointer. The verifier does not allow BPF_SUB to move stack pointers.",
+ ptr_regno);
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "subtraction from stack pointer", reason,
+ "Use addition from R10 to form stack addresses within the tracked stack frame.");
return -EACCES;
}
dst_reg->r64 = cnum64_add(ptr_reg->r64, cnum64_negate(off_reg->r64));
@@ -13803,17 +14794,47 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
/* bitwise ops on pointers are troublesome, prohibit. */
verbose(env, "R%d bitwise operator %s on pointer prohibited\n",
dst, bpf_alu_string[opcode >> 4]);
+ reason = bpf_diag_fmt(
+ env, "R%d holds %s. Bitwise operator %s would destroy the pointer value the verifier is tracking.",
+ ptr_regno, bpf_diag_reg_type_plain(env, ptr_reg->type),
+ bpf_alu_string[opcode >> 4]);
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "bitwise operation on pointer", reason,
+ "Do bitwise operations on scalar values, not on pointer-valued registers.");
return -EACCES;
default:
/* other operators (e.g. MUL,LSH) produce non-pointer results */
verbose(env, "R%d pointer arithmetic with %s operator prohibited\n",
dst, bpf_alu_string[opcode >> 4]);
+ reason = bpf_diag_fmt(
+ env, "R%d holds %s. Operator %s is not one of the limited pointer arithmetic operations the verifier can track.",
+ ptr_regno, bpf_diag_reg_type_plain(env, ptr_reg->type),
+ bpf_alu_string[opcode >> 4]);
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "invalid pointer arithmetic operator", reason,
+ "Use only verifier-supported addition or subtraction with a bounded scalar offset, or perform this operation on a scalar value.");
return -EACCES;
}
- if (!check_reg_sane_offset_ptr(env, dst_reg, ptr_reg->type))
+ if (!check_reg_sane_offset_ptr(env, dst_reg, ptr_reg->type)) {
+ reason = bpf_diag_fmt(
+ env, "After this arithmetic, R%d would be outside the verifier's safe offset range [-%u, %u] for %s.",
+ dst, BPF_MAX_VAR_OFF, BPF_MAX_VAR_OFF,
+ bpf_diag_reg_type_plain(env, ptr_reg->type));
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer offset is not safe", reason,
+ "Tighten the scalar bounds before the arithmetic so the resulting pointer remains within the allowed range.");
return -EINVAL;
- reg_bounds_sync(dst_reg);
+ }
+ /*
+ * A packet pointer that keeps its id or range is checked against a
+ * range set from the checked pointer's umax, so var_off must not tighten
+ * its umax. r32 must still match var_off for reg_bounds_sanity_check().
+ */
+ if (reg_is_pkt_pointer(dst_reg) && (known || dst_reg->range > 0))
+ __update_reg32_bounds(dst_reg);
+ else
+ reg_bounds_sync(dst_reg);
bounds_ret = sanitize_check_bounds(env, insn, dst_reg);
if (bounds_ret == -EACCES)
return bounds_ret;
@@ -13828,7 +14849,7 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
return -EFAULT;
}
if (ret < 0)
- return sanitize_err(env, insn, ret, off_reg, dst_reg);
+ return sanitize_err(env, insn, ret);
}
return 0;
@@ -14580,7 +15601,7 @@ static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
if (sanitize_needed(opcode)) {
ret = sanitize_val_alu(env, insn);
if (ret < 0)
- return sanitize_err(env, insn, ret, NULL, NULL);
+ return sanitize_err(env, insn, ret);
}
/* Calculate sign/unsigned bounds and tnum for alu32 and alu64 bit ops.
@@ -14728,6 +15749,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
struct bpf_reg_state *regs = state->regs, *dst_reg, *src_reg;
struct bpf_reg_state *ptr_reg = NULL, off_reg = {0};
bool alu32 = (BPF_CLASS(insn->code) != BPF_ALU64);
+ struct bpf_insn_aux_data *aux = cur_aux(env);
u8 opcode = BPF_OP(insn->code);
int err;
@@ -14739,24 +15761,45 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
/* Case where at least one operand is an arena. */
if (dst_reg->type == PTR_TO_ARENA || (src_reg && src_reg->type == PTR_TO_ARENA)) {
- struct bpf_insn_aux_data *aux = cur_aux(env);
if (dst_reg->type != PTR_TO_ARENA)
*dst_reg = *src_reg;
- dst_reg->subreg_def = env->insn_idx + 1;
+ if (BPF_CLASS(insn->code) == BPF_ALU64) {
+ /*
+ * Only arena pointers set needs_zext, but doing so
+ * modifies the instruction at fixup time to an ALU32
+ * and makes it unsuitable for 64-bit scalar args. We
+ * prevent zext from being set if the instruction has
+ * been previously called with non-arena registers.
+ */
+ if (aux->prevent_zext) {
+ verbose(env, "same insn cannot be used with and without arena pointer\n");
+ return -EINVAL;
+ }
- if (BPF_CLASS(insn->code) == BPF_ALU64)
/*
* 32-bit operations zero upper bits automatically.
* 64-bit operations need to be converted to 32.
*/
aux->needs_zext = true;
+ aux->zext_dst = true;
+ }
/* Any arithmetic operations are allowed on arena pointers */
return 0;
}
+ /* Prevent the instruction from being used with arena pointers (see above). */
+ if (env->prog->aux->arena && BPF_CLASS(insn->code) == BPF_ALU64) {
+ if (aux->needs_zext) {
+ verbose(env, "same insn cannot be used with and without arena pointer\n");
+ return -EINVAL;
+ }
+
+ aux->prevent_zext = true;
+ }
+
if (dst_reg->type != SCALAR_VALUE)
ptr_reg = dst_reg;
@@ -14783,16 +15826,16 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
err = mark_chain_precision(env, insn->dst_reg);
if (err)
return err;
- return adjust_ptr_min_max_vals(env, insn,
- src_reg, dst_reg);
+ off_reg = *dst_reg;
+ return adjust_ptr_min_max_vals(env, insn, insn->src_reg, src_reg,
+ &off_reg);
}
} else if (ptr_reg) {
/* pointer += scalar */
err = mark_chain_precision(env, insn->src_reg);
if (err)
return err;
- return adjust_ptr_min_max_vals(env, insn,
- dst_reg, src_reg);
+ return adjust_ptr_min_max_vals(env, insn, insn->dst_reg, dst_reg, src_reg);
} else if (dst_reg->precise) {
/* if dst_reg is precise, src_reg should be precise as well */
err = mark_chain_precision(env, insn->src_reg);
@@ -14807,8 +15850,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
__mark_reg_known(&off_reg, insn->imm);
src_reg = &off_reg;
if (ptr_reg) /* pointer += K */
- return adjust_ptr_min_max_vals(env, insn,
- ptr_reg, src_reg);
+ return adjust_ptr_min_max_vals(env, insn, insn->dst_reg, ptr_reg, src_reg);
}
/* Got here implies adding two SCALAR_VALUEs */
@@ -14894,6 +15936,8 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
u8 opcode = BPF_OP(insn->code);
int err;
+ bpf_diag_mod_begin(env, &regs[insn->dst_reg], NULL, BPF_DIAG_MOD_WRITE);
+
if (opcode == BPF_END || opcode == BPF_NEG) {
/* check src operand */
err = check_reg_arg(env, insn->dst_reg, SRC_OP);
@@ -14947,18 +15991,14 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
if (insn->imm) {
/* off == BPF_ADDR_SPACE_CAST */
mark_reg_unknown(env, regs, insn->dst_reg);
- if (insn->imm == 1) { /* cast from as(1) to as(0) */
+ if (insn->imm == 1) /* cast from as(1) to as(0) */
dst_reg->type = PTR_TO_ARENA;
- /* PTR_TO_ARENA is 32-bit */
- dst_reg->subreg_def = env->insn_idx + 1;
- }
} else if (insn->off == 0) {
/* case: R1 = R2
* copy register state to dest reg
*/
assign_scalar_id_before_mov(env, src_reg);
*dst_reg = *src_reg;
- dst_reg->subreg_def = DEF_NOT_SUBREG;
} else {
/* case: R1 = (s8, s16 s32)R2 */
if (is_pointer_value(env, insn->src_reg)) {
@@ -14976,7 +16016,6 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
if (!no_sext)
clear_scalar_id(dst_reg);
coerce_reg_to_size_sx(dst_reg, insn->off >> 3);
- dst_reg->subreg_def = DEF_NOT_SUBREG;
} else {
mark_reg_unknown(env, regs, insn->dst_reg);
}
@@ -15001,7 +16040,6 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
*/
if (!is_src_reg_u32)
clear_scalar_id(dst_reg);
- dst_reg->subreg_def = env->insn_idx + 1;
} else {
/* case: W1 = (s8, s16)W2 */
bool no_sext = reg_umax(src_reg) < (1ULL << (insn->off - 1));
@@ -15011,7 +16049,6 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
*dst_reg = *src_reg;
if (!no_sext)
clear_scalar_id(dst_reg);
- dst_reg->subreg_def = env->insn_idx + 1;
coerce_subreg_to_size_sx(dst_reg, insn->off >> 3);
}
} else {
@@ -15074,7 +16111,12 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
return err;
}
- return reg_bounds_sanity_check(env, &regs[insn->dst_reg], "alu");
+ err = reg_bounds_sanity_check(env, &regs[insn->dst_reg], "alu");
+ if (err)
+ return err;
+
+ bpf_diag_mod_end(env);
+ return 0;
}
static void find_good_pkt_pointers(struct bpf_verifier_state *vstate,
@@ -15429,6 +16471,13 @@ static int is_branch_taken(struct bpf_verifier_env *env, struct bpf_reg_state *r
if (__is_pointer_value(false, reg1) || __is_pointer_value(false, reg2)) {
u64 val;
+ /*
+ * The low 32 bits of a valid pointer may well be zero, hence
+ * nothing below applies to a 32-bit comparison.
+ */
+ if (is_jmp32)
+ return -1;
+
/* arrange that reg2 is a scalar, and reg1 is a pointer */
if (!is_reg_const(reg2, is_jmp32)) {
opcode = flip_opcode(opcode);
@@ -15689,7 +16738,7 @@ static void mark_ptr_or_null_regs(struct bpf_verifier_state *vstate, u32 regno,
* No one could have freed the reference state before
* doing the NULL check.
*/
- WARN_ON_ONCE(release_reference_nomark(vstate, id));
+ WARN_ON_ONCE(__release_reference_nomark(vstate, id));
bpf_for_each_reg_in_vstate(vstate, state, reg, ({
mark_ptr_or_null_reg(state, reg, id, is_null);
@@ -15880,12 +16929,8 @@ static void sync_linked_regs(struct bpf_verifier_env *env, struct bpf_verifier_s
continue;
if ((!(reg->id & BPF_ADD_CONST) && !(known_reg->id & BPF_ADD_CONST)) ||
reg->delta == known_reg->delta) {
- s32 saved_subreg_def = reg->subreg_def;
-
*reg = *known_reg;
- reg->subreg_def = saved_subreg_def;
} else {
- s32 saved_subreg_def = reg->subreg_def;
s32 saved_off = reg->delta;
u32 saved_id = reg->id;
@@ -15895,12 +16940,11 @@ static void sync_linked_regs(struct bpf_verifier_env *env, struct bpf_verifier_s
/* reg = known_reg; reg += delta */
*reg = *known_reg;
/*
- * Must preserve off, id and subreg_def flag,
- * otherwise another sync_linked_regs() will be incorrect.
+ * Must preserve off and id, otherwise another sync_linked_regs()
+ * will be incorrect.
*/
reg->delta = saved_off;
reg->id = saved_id;
- reg->subreg_def = saved_subreg_def;
scalar32_min_max_add(reg, &fake_reg);
scalar_min_max_add(reg, &fake_reg);
@@ -15995,6 +17039,16 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
return err;
}
+ /*
+ * Collect the linked registers before env->{true,false}_reg{1,2} setup,
+ * otherwise ids dropped by collect_linked_regs() would be resurrected
+ * when env->{true,false}_reg{1,2} are copied back.
+ */
+ if (BPF_SRC(insn->code) == BPF_X && src_reg->type == SCALAR_VALUE && src_reg->id)
+ collect_linked_regs(env, this_branch, src_reg->id, &linked_regs);
+ if (dst_reg->type == SCALAR_VALUE && dst_reg->id)
+ collect_linked_regs(env, this_branch, dst_reg->id, &linked_regs);
+
is_jmp32 = BPF_CLASS(insn->code) == BPF_JMP32;
env->false_reg1 = *dst_reg;
env->false_reg2 = *src_reg;
@@ -16049,10 +17103,6 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
* 'this_branch' and 'other_branch' share this history
* if parent state is created.
*/
- if (BPF_SRC(insn->code) == BPF_X && src_reg->type == SCALAR_VALUE && src_reg->id)
- collect_linked_regs(env, this_branch, src_reg->id, &linked_regs);
- if (dst_reg->type == SCALAR_VALUE && dst_reg->id)
- collect_linked_regs(env, this_branch, dst_reg->id, &linked_regs);
if (linked_regs.cnt > 1) {
err = bpf_push_jmp_history(env, this_branch, 0, 0, 0, linked_regs_pack(&linked_regs));
if (err)
@@ -16102,7 +17152,6 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
*/
if (!is_jmp32 && BPF_SRC(insn->code) == BPF_X &&
__is_pointer_value(false, src_reg) && __is_pointer_value(false, dst_reg) &&
- type_may_be_null(src_reg->type) != type_may_be_null(dst_reg->type) &&
base_type(src_reg->type) != PTR_TO_BTF_ID &&
base_type(dst_reg->type) != PTR_TO_BTF_ID) {
eq_branch_regs = NULL;
@@ -16118,9 +17167,11 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
break;
}
if (eq_branch_regs) {
- if (type_may_be_null(src_reg->type))
+ /* src == dst && dst != NULL => src != NULL */
+ if (reg_not_null(env, dst_reg) && type_may_be_null(src_reg->type))
mark_ptr_not_null_reg(&eq_branch_regs[insn->src_reg]);
- else
+ /* src == dst && src != NULL => dst != NULL */
+ if (reg_not_null(env, src_reg) && type_may_be_null(dst_reg->type))
mark_ptr_not_null_reg(&eq_branch_regs[insn->dst_reg]);
}
}
@@ -16135,6 +17186,15 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
type_may_be_null(dst_reg->type) &&
((BPF_SRC(insn->code) == BPF_K && insn->imm == 0) ||
(BPF_SRC(insn->code) == BPF_X && bpf_register_is_null(src_reg)))) {
+ /*
+ * For BPF_X the zero is a property of this execution path,
+ * hence src_reg has to be precise.
+ */
+ if (BPF_SRC(insn->code) == BPF_X) {
+ err = mark_chain_precision(env, insn->src_reg);
+ if (err)
+ return err;
+ }
/* Mark all identical registers in each branch as either
* safe or unknown depending R == 0 or R != 0 conditional.
*/
@@ -16173,11 +17233,13 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
return err;
dst_reg = &regs[insn->dst_reg];
+ bpf_diag_mod_begin(env, dst_reg, NULL, BPF_DIAG_MOD_WRITE);
if (insn->src_reg == 0) {
u64 imm = ((u64)(insn + 1)->imm << 32) | (u32)insn->imm;
dst_reg->type = SCALAR_VALUE;
__mark_reg_known(&regs[insn->dst_reg], imm);
+ bpf_diag_mod_end(env);
return 0;
}
@@ -16201,6 +17263,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
verifier_bug(env, "pseudo btf id: unexpected dst reg type");
return -EFAULT;
}
+ bpf_diag_mod_end(env);
return 0;
}
@@ -16217,9 +17280,19 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
verbose(env, "callback function not static\n");
return -EINVAL;
}
+ /*
+ * When env->subprog_cnt == 1 this instruction won't be rewritten
+ * to hold a real function address. Assume that no usable program
+ * combines e.g. main and timer callback and just reject here.
+ */
+ if (subprogno == 0) {
+ verbose(env, "callback function cannot be the main program\n");
+ return -EINVAL;
+ }
dst_reg->type = PTR_TO_FUNC;
dst_reg->subprogno = subprogno;
+ bpf_diag_mod_end(env);
return 0;
}
@@ -16230,6 +17303,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
if (map->map_type == BPF_MAP_TYPE_ARENA) {
__mark_reg_unknown(env, dst_reg);
dst_reg->map_ptr = map;
+ bpf_diag_mod_end(env);
return 0;
}
__mark_reg_known(dst_reg, aux->map_off);
@@ -16247,6 +17321,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
return -EFAULT;
}
+ bpf_diag_mod_end(env);
return 0;
}
@@ -16279,6 +17354,7 @@ static bool may_access_skb(enum bpf_prog_type type)
*/
static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn)
{
+ struct bpf_verifier_state *state = env->cur_state;
struct bpf_reg_state *regs = cur_regs(env);
static const int ctx_reg = BPF_REG_6;
u8 mode = BPF_MODE(insn->code);
@@ -16289,6 +17365,13 @@ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn)
return -EINVAL;
}
+ for (i = state->curframe; i; i--) {
+ if (state->frame[i]->in_callback_fn) {
+ verbose(env, "cannot use BPF_LD_[ABS|IND] within callback\n");
+ return -EINVAL;
+ }
+ }
+
if (!env->ops->gen_ld_abs) {
verifier_bug(env, "gen_ld_abs is null");
return -EFAULT;
@@ -16325,6 +17408,8 @@ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn)
return err;
/* reset caller saved regs to unreadable */
+ bpf_diag_record_caller_saved(env, regs);
+ bpf_diag_mod_begin(env, &regs[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
for (i = 0; i < CALLER_SAVED_REGS; i++) {
bpf_mark_reg_not_init(env, &regs[caller_saved[i]]);
check_reg_arg(env, caller_saved[i], DST_OP_NO_MARK);
@@ -16335,8 +17420,7 @@ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn)
* Already marked as written above.
*/
mark_reg_unknown(env, regs, BPF_REG_0);
- /* ld_abs load up to 32-bit skb data. */
- regs[BPF_REG_0].subreg_def = env->insn_idx + 1;
+ bpf_diag_mod_end(env);
/*
* See bpf_gen_ld_abs() which emits a hidden BPF_EXIT with r0=0
* which must be explored by the verifier when in a subprog.
@@ -16357,7 +17441,6 @@ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn)
return 0;
}
-
static bool return_retval_range(struct bpf_verifier_env *env, struct bpf_retval_range *range)
{
enum bpf_prog_type prog_type = resolve_prog_type(env->prog);
@@ -16646,12 +17729,11 @@ bool bpf_verifier_inlines_helper_call(struct bpf_verifier_env *env, s32 imm)
bool bpf_get_call_summary(struct bpf_verifier_env *env, struct bpf_insn *call,
struct bpf_call_summary *cs)
{
- struct bpf_kfunc_call_arg_meta meta;
+ struct bpf_call_arg_meta meta;
const struct bpf_func_proto *fn;
int i;
if (bpf_helper_call(call)) {
-
if (bpf_get_helper_proto(env, call->imm, &fn) < 0)
/* error would be reported later */
return false;
@@ -16757,6 +17839,10 @@ bool bpf_get_call_summary(struct bpf_verifier_env *env, struct bpf_insn *call,
* r0 = *(u64 *)(r10 - 8); r0 += r1;
* r0 += r1; exit;
* exit;
+ *
+ * Both uses of the marks assume that a pattern is entered at its first
+ * spill and thus executes as a unit, hence a pattern is not grown past
+ * an instruction targeted by a jump.
*/
static void mark_fastcall_pattern_for_call(struct bpf_verifier_env *env,
struct bpf_subprog_info *subprog,
@@ -16795,6 +17881,10 @@ static void mark_fastcall_pattern_for_call(struct bpf_verifier_env *env,
for (i = 1, off = lowest_off; i <= ARRAY_SIZE(caller_saved); ++i, off += BPF_REG_SIZE) {
if (insn_idx - i < 0 || insn_idx + i >= env->prog->len)
break;
+ /* stx/ldx/call must not be a jump targets, a jump to the first stx is fine */
+ if (bpf_is_jump_target(env, insn_idx - i + 1) ||
+ bpf_is_jump_target(env, insn_idx + i))
+ break;
stx = &insns[insn_idx - i];
ldx = &insns[insn_idx + i];
/* must be a stack spill/fill pair */
@@ -16961,6 +18051,8 @@ static bool reg_type_mismatch_ok(enum bpf_reg_type type)
case PTR_TO_BTF_ID:
case PTR_TO_ARENA:
return false;
+ case PTR_TO_MEM:
+ return !bpf_may_fault_on_deref(type);
default:
return true;
}
@@ -16984,27 +18076,29 @@ static bool reg_type_mismatch(enum bpf_reg_type src, enum bpf_reg_type prev)
!reg_type_mismatch_ok(prev));
}
-static bool is_ptr_to_mem_or_btf_id(enum bpf_reg_type type)
+static bool is_ptr_to_mem(enum bpf_reg_type type)
{
- switch (base_type(type)) {
- case PTR_TO_MEM:
- case PTR_TO_BTF_ID:
- return true;
- default:
- return false;
- }
+ return base_type(type) == PTR_TO_MEM;
}
-static bool is_ptr_to_mem(enum bpf_reg_type type)
+static enum bpf_reg_type merge_ptr_types(enum bpf_reg_type type_a,
+ enum bpf_reg_type type_b)
{
- return base_type(type) == PTR_TO_MEM;
+ bool to_mem = is_ptr_to_mem(type_a) || is_ptr_to_mem(type_b);
+ enum bpf_reg_type type_merged = to_mem ? PTR_TO_MEM : PTR_TO_BTF_ID;
+
+ if (bpf_may_fault_on_deref(type_a) || bpf_may_fault_on_deref(type_b))
+ type_merged |= to_mem ? MEM_RDONLY | PTR_UNTRUSTED :
+ PTR_UNTRUSTED;
+ else
+ type_merged |= ((type_a | type_b) & MEM_RDONLY);
+ return type_merged;
}
static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type type,
bool allow_trust_mismatch)
{
enum bpf_reg_type *prev_type = &env->insn_aux_data[env->insn_idx].ptr_type;
- enum bpf_reg_type merged_type;
if (*prev_type == NOT_INIT) {
/* Saw a valid insn
@@ -17021,24 +18115,16 @@ static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type typ
* Reject it.
*/
if (allow_trust_mismatch &&
- is_ptr_to_mem_or_btf_id(type) &&
- is_ptr_to_mem_or_btf_id(*prev_type)) {
+ bpf_is_ptr_to_mem_or_btf_id(type) &&
+ bpf_is_ptr_to_mem_or_btf_id(*prev_type)) {
/*
* Have to support a use case when one path through
- * the program yields TRUSTED pointer while another
- * is UNTRUSTED. Fallback to UNTRUSTED to generate
- * BPF_PROBE_MEM/BPF_PROBE_MEMSX.
- * Same behavior of MEM_RDONLY flag.
+ * the program yields a TRUSTED pointer while another
+ * is UNTRUSTED. Merge them into a type which keeps
+ * the BPF_PROBE_MEM/BPF_PROBE_MEMSX rewrite when
+ * either side needs it.
*/
- if (is_ptr_to_mem(type) || is_ptr_to_mem(*prev_type))
- merged_type = PTR_TO_MEM;
- else
- merged_type = PTR_TO_BTF_ID;
- if ((type & PTR_UNTRUSTED) || (*prev_type & PTR_UNTRUSTED))
- merged_type |= PTR_UNTRUSTED;
- if ((type & MEM_RDONLY) || (*prev_type & MEM_RDONLY))
- merged_type |= MEM_RDONLY;
- *prev_type = merged_type;
+ *prev_type = merge_ptr_types(type, *prev_type);
} else {
verbose(env, "same insn cannot be used with different pointers\n");
return -EINVAL;
@@ -17262,9 +18348,13 @@ static int do_check_insn(struct bpf_verifier_env *env, bool *do_print_state)
insn->imm != BPF_FUNC_spin_unlock &&
insn->imm != BPF_FUNC_kptr_xchg) ||
(insn->src_reg == BPF_PSEUDO_KFUNC_CALL &&
- (insn->off != 0 || !kfunc_spin_allowed(insn->imm)))) {
+ !kfunc_spin_allowed(env, insn->imm, insn->off))) {
verbose(env,
"function calls are not allowed while holding a lock\n");
+ bpf_diag_ctx_active(
+ env, env->insn_idx,
+ "function call", BPF_DIAG_CONTEXT_LOCK,
+ "Release the BPF spin lock before making this call, or move the call outside the locked region.");
return -EINVAL;
}
}
@@ -17338,7 +18428,9 @@ static int do_check(struct bpf_verifier_env *env)
insn = &insns[env->insn_idx];
insn_aux = &env->insn_aux_data[env->insn_idx];
- if (++env->insn_processed > BPF_COMPLEXITY_LIMIT_INSNS) {
+ account_processed_insn(env);
+
+ if (env->insn_processed > BPF_COMPLEXITY_LIMIT_INSNS) {
verbose(env,
"BPF program is too large. Processed %d insn\n",
env->insn_processed);
@@ -17347,6 +18439,27 @@ static int do_check(struct bpf_verifier_env *env)
state->last_insn_idx = env->prev_insn_idx;
state->insn_idx = env->insn_idx;
+ /*
+ * Record the incoming edge so active and queued paths use the same
+ * branch-recording path. A zero-offset conditional has identical
+ * successors, so its outcome cannot be reconstructed from the edge.
+ */
+ if (!state->speculative && prev_insn_idx >= 0 && prev_insn_idx < insn_cnt) {
+ struct bpf_insn *prev_insn = &insns[prev_insn_idx];
+ int fallthrough_idx = prev_insn_idx + 1;
+ int branch_idx = prev_insn_idx + bpf_jmp_offset(prev_insn) + 1;
+ u8 class = BPF_CLASS(prev_insn->code);
+ u8 opcode = BPF_OP(prev_insn->code);
+
+ if ((class == BPF_JMP || class == BPF_JMP32) &&
+ opcode != BPF_JA && opcode != BPF_CALL && opcode != BPF_EXIT &&
+ opcode <= BPF_JCOND && branch_idx != fallthrough_idx) {
+ if (env->insn_idx == branch_idx)
+ bpf_diag_record_branch(env, prev_insn_idx, true);
+ else if (env->insn_idx == fallthrough_idx)
+ bpf_diag_record_branch(env, prev_insn_idx, false);
+ }
+ }
if (bpf_is_prune_point(env, env->insn_idx)) {
err = bpf_is_state_visited(env, env->insn_idx);
@@ -17396,6 +18509,7 @@ static int do_check(struct bpf_verifier_env *env)
env->prev_log_pos = env->log.end_pos;
verbose(env, "%d: ", env->insn_idx);
bpf_verbose_insn(env, insn);
+ verbose(env, "\n");
env->prev_insn_print_pos = env->log.end_pos - env->prev_log_pos;
env->prev_log_pos = env->log.end_pos;
}
@@ -17478,6 +18592,7 @@ static int do_check(struct bpf_verifier_env *env)
"speculation barrier after jump instruction may not have the desired effect"))
return -EFAULT;
process_bpf_exit:
+ account_current_path(env);
mark_verifier_state_scratched(env);
err = bpf_update_branch_counts(env, env->cur_state);
if (err)
@@ -17540,6 +18655,11 @@ static int __add_used_btf(struct bpf_verifier_env *env, struct btf *btf)
if (env->used_btfs[i].btf == btf)
goto ret_put;
+ if (env->signature) {
+ verbose(env, "signed program cannot bind any BTF\n");
+ ret = -EACCES;
+ goto ret_put;
+ }
if (env->used_btf_cnt >= MAX_USED_BTFS) {
verbose(env, "The total number of btfs per program has reached the limit of %u\n",
MAX_USED_BTFS);
@@ -17732,7 +18852,9 @@ static int check_map_prog_compatibility(struct bpf_verifier_env *env,
verbose(env, "socket filter progs cannot use bpf_spin_lock yet\n");
return -EINVAL;
}
+ }
+ if (btf_record_has_field(map->record, BPF_SPIN_LOCK)) {
if (is_tracing_prog_type(prog_type)) {
verbose(env, "tracing progs cannot use bpf_spin_lock yet\n");
return -EINVAL;
@@ -17804,6 +18926,7 @@ static int check_map_prog_compatibility(struct bpf_verifier_env *env,
return -EOPNOTSUPP;
}
env->prog->aux->arena = (void *)map;
+ env->prog->jit_required = true;
if (!bpf_arena_get_user_vm_start(env->prog->aux->arena)) {
verbose(env, "arena's user address must be set via map_extra or mmap()\n");
return -EINVAL;
@@ -17822,6 +18945,12 @@ static int __add_used_map(struct bpf_verifier_env *env, struct bpf_map *map)
if (env->used_maps[i] == map)
return i;
+ if (env->signature &&
+ env->prog->aux->sig.verdict == BPF_SIG_VERIFIED) {
+ verbose(env, "signed program cannot bind map '%s' not covered by the signature\n",
+ map->name);
+ return -EACCES;
+ }
if (env->used_map_cnt >= MAX_USED_MAPS) {
verbose(env, "The total number of maps per program has reached the limit of %u\n",
MAX_USED_MAPS);
@@ -17851,6 +18980,7 @@ static int __add_used_map(struct bpf_verifier_env *env, struct bpf_map *map)
return err;
}
env->insn_array_maps[env->insn_array_map_cnt++] = map;
+ env->prog->jit_required = true;
}
return env->used_map_cnt - 1;
@@ -17874,6 +19004,48 @@ static int add_used_map(struct bpf_verifier_env *env, int fd)
return __add_used_map(env, map);
}
+static int fd_array_get_map_idx_continuous(struct bpf_verifier_env *env, u32 idx)
+{
+ struct bpf_map *map;
+
+ if (idx >= env->fd_array_cnt) {
+ verbose(env, "fd_idx %u out of bounds, fd_array_cnt %u\n",
+ idx, env->fd_array_cnt);
+ return -EINVAL;
+ }
+ map = fd_slot_map(env->fd_array[idx]);
+ if (!map) {
+ verbose(env, "fd_idx %u is not a map\n", idx);
+ return -EINVAL;
+ }
+ return __add_used_map(env, map);
+}
+
+static int fd_array_get_map_idx_sparse(struct bpf_verifier_env *env, u32 idx)
+{
+ int fd;
+
+ if (copy_from_bpfptr_offset(&fd, env->fd_array_raw,
+ (size_t)idx * sizeof(fd), sizeof(fd)))
+ return -EFAULT;
+ return add_used_map(env, fd);
+}
+
+static int fd_array_get_map_idx(struct bpf_verifier_env *env, u32 idx)
+{
+ if (env->fd_array)
+ return fd_array_get_map_idx_continuous(env, idx);
+ if (env->signature) {
+ verbose(env, "signed program must bind maps via a continuous fd_array (fd_array_cnt)\n");
+ return -EACCES;
+ }
+ if (!bpfptr_is_null(env->fd_array_raw))
+ return fd_array_get_map_idx_sparse(env, idx);
+
+ verbose(env, "fd_idx without fd_array is invalid\n");
+ return -EPROTO;
+}
+
static int check_alu_fields(struct bpf_verifier_env *env, struct bpf_insn *insn)
{
u8 class = BPF_CLASS(insn->code);
@@ -18091,7 +19263,6 @@ static int check_and_resolve_insns(struct bpf_verifier_env *env)
struct bpf_map *map;
int map_idx;
u64 addr;
- u32 fd;
if (i == insn_cnt - 1 || insn[1].code != 0 ||
insn[1].dst_reg != 0 || insn[1].src_reg != 0 ||
@@ -18143,21 +19314,17 @@ static int check_and_resolve_insns(struct bpf_verifier_env *env)
switch (insn[0].src_reg) {
case BPF_PSEUDO_MAP_IDX_VALUE:
case BPF_PSEUDO_MAP_IDX:
- if (bpfptr_is_null(env->fd_array)) {
- verbose(env, "fd_idx without fd_array is invalid\n");
- return -EPROTO;
- }
- if (copy_from_bpfptr_offset(&fd, env->fd_array,
- insn[0].imm * sizeof(fd),
- sizeof(fd)))
- return -EFAULT;
+ map_idx = fd_array_get_map_idx(env, insn[0].imm);
break;
default:
- fd = insn[0].imm;
+ if (env->signature) {
+ verbose(env, "signed program cannot reference a map by fd, only via fd_array index\n");
+ return -EINVAL;
+ }
+ map_idx = add_used_map(env, insn[0].imm);
break;
}
- map_idx = add_used_map(env, fd);
if (map_idx < 0)
return map_idx;
map = env->used_maps[map_idx];
@@ -18251,8 +19418,6 @@ static void release_insn_arrays(struct bpf_verifier_env *env)
bpf_insn_array_release(env->insn_array_maps[i]);
}
-
-
/* The verifier does more data flow analysis than llvm and will not
* explore branches that are dead at run time. Malicious programs can
* have dead code too. Therefore replace all dead at-run-time code
@@ -18280,8 +19445,6 @@ static void sanitize_dead_code(struct bpf_verifier_env *env)
}
}
-
-
static void free_states(struct bpf_verifier_env *env)
{
struct bpf_verifier_state_list *sl;
@@ -18325,13 +19488,15 @@ static void free_states(struct bpf_verifier_env *env)
}
}
-static int do_check_common(struct bpf_verifier_env *env, int subprog)
+static int do_check_common(struct bpf_verifier_env *env, int subprog, bool is_sleepable)
{
bool pop_log = !(env->log.level & BPF_LOG_LEVEL2);
struct bpf_subprog_info *sub = subprog_info(env, subprog);
struct bpf_prog_aux *aux = env->prog->aux;
struct bpf_verifier_state *state;
struct bpf_reg_state *regs;
+ u32 old_insns_total = sub->insns_total;
+ u32 insn_processed = env->insn_processed;
int ret, i;
env->prev_linfo = NULL;
@@ -18343,7 +19508,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
state->curframe = 0;
state->speculative = false;
state->branches = 1;
- state->in_sleepable = env->prog->sleepable;
+ state->in_sleepable = is_sleepable;
state->frame[0] = kzalloc_obj(struct bpf_func_state, GFP_KERNEL_ACCOUNT);
if (!state->frame[0]) {
kfree(state);
@@ -18359,7 +19524,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
regs = state->frame[state->curframe]->regs;
if (subprog || env->prog->type == BPF_PROG_TYPE_EXT) {
- const char *sub_name = subprog_name(env, subprog);
+ const char *sub_name = bpf_subprog_name(env, subprog);
struct bpf_subprog_arg_info *arg;
struct bpf_reg_state *reg;
@@ -18470,9 +19635,24 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
ret = do_check(env);
out:
- if (!ret && pop_log)
- bpf_vlog_reset(&env->log, 0);
+ account_current_path(env);
+ if (!ret) {
+ if (pop_log)
+ bpf_vlog_reset(&env->log, 0);
+ bpf_diag_event_log_restore(env, 0);
+ }
free_states(env);
+
+ /*
+ * The override is needed to account for async subprograms, which
+ * are verified with their own set of stack frames and thus are
+ * not accounted as callees by account_current_path().
+ * Accumulate their total counts as total counts of the main or
+ * global subprog hosting the async call.
+ * Start from the saved total of earlier contexts: adding to the current
+ * total would count this pass's synchronous paths twice.
+ */
+ sub->insns_total = old_insns_total + (env->insn_processed - insn_processed);
return ret;
}
@@ -18500,15 +19680,19 @@ static int do_check_subprogs(struct bpf_verifier_env *env)
{
struct bpf_prog_aux *aux = env->prog->aux;
struct bpf_func_info_aux *sub_aux;
- int i, ret, new_cnt;
- u32 insn_processed;
+ int context, i, ret, new_cnt;
if (!aux->func_info)
return 0;
- /* exception callback is presumed to be always called */
- if (env->exception_callback_subprog)
- subprog_aux(env, env->exception_callback_subprog)->called = true;
+ /*
+ * Callbacks cannot throw, so the exception callback always runs in the
+ * main program's context. It is presumed to be always called.
+ */
+ if (env->exception_callback_subprog) {
+ sub_aux = subprog_aux(env, env->exception_callback_subprog);
+ sub_aux->called[env->prog->sleepable] = true;
+ }
again:
new_cnt = 0;
@@ -18516,33 +19700,29 @@ again:
if (!bpf_subprog_is_global(env, i))
continue;
- insn_processed = env->insn_processed;
-
sub_aux = subprog_aux(env, i);
- if (!sub_aux->called || sub_aux->verified)
- continue;
+ for (context = 0; context < ARRAY_SIZE(sub_aux->called); context++) {
+ if (!sub_aux->called[context] || sub_aux->verified[context])
+ continue;
- env->insn_idx = env->subprog_info[i].start;
- WARN_ON_ONCE(env->insn_idx == 0);
- ret = do_check_common(env, i);
- env->subprog_info[i].insn_processed = env->insn_processed - insn_processed;
- if (ret) {
- return ret;
- } else if (env->log.level & BPF_LOG_LEVEL) {
- verbose(env, "Func#%d ('%s') is safe for any args that match its prototype\n",
- i, subprog_name(env, i));
- }
+ env->insn_idx = env->subprog_info[i].start;
+ WARN_ON_ONCE(env->insn_idx == 0);
+ ret = do_check_common(env, i, context);
+ if (ret)
+ return ret;
+ if (env->log.level & BPF_LOG_LEVEL)
+ verbose(env, "Func#%d ('%s') is safe for any args "
+ "that match its prototype\n",
+ i, bpf_subprog_name(env, i));
- /* We verified new global subprog, it might have called some
- * more global subprogs that we haven't verified yet, so we
- * need to do another pass over subprogs to verify those.
- */
- sub_aux->verified = true;
- new_cnt++;
+ sub_aux->verified[context] = true;
+ new_cnt++;
+ }
}
- /* We can't loop forever as we verify at least one global subprog on
- * each pass.
+ /*
+ * We can't loop forever as each pass verifies at least one new context,
+ * and there are only two contexts per global subprog.
*/
if (new_cnt)
goto again;
@@ -18552,18 +19732,15 @@ again:
static int do_check_main(struct bpf_verifier_env *env)
{
- u32 insn_processed = env->insn_processed;
int ret;
env->insn_idx = 0;
- ret = do_check_common(env, 0);
- env->subprog_info[0].insn_processed = env->insn_processed - insn_processed;
+ ret = do_check_common(env, 0, env->prog->sleepable);
if (!ret)
env->prog->aux->stack_depth = env->subprog_info[0].stack_depth;
return ret;
}
-
static void print_verification_stats(struct bpf_verifier_env *env)
{
/* Skip over hidden subprogs which are not verified. */
@@ -18572,15 +19749,20 @@ static void print_verification_stats(struct bpf_verifier_env *env)
if (env->log.level & BPF_LOG_STATS) {
verbose(env, "verification time %lld usec\n",
div_u64(env->verification_time, 1000));
- verbose(env, "stack depth %d", env->subprog_info[0].stack_depth);
- for (i = 1; i < subprog_cnt; i++)
- verbose(env, "+%d", env->subprog_info[i].stack_depth);
- verbose(env, " max %d\n", env->max_stack_depth);
- verbose(env, "insns processed %d", env->subprog_info[0].insn_processed);
- for (i = 1; i < subprog_cnt; i++)
- if (bpf_subprog_is_global(env, i))
- verbose(env, "+%d", env->subprog_info[i].insn_processed);
- verbose(env, "\n");
+ verbose(env, "stack depth max %d\n", env->max_stack_depth);
+ for (i = 0; i < subprog_cnt; i++) {
+ const char *name = env->subprog_info[i].name;
+ const char *kind;
+
+ if (!name || !name[0])
+ name = "<unknown>";
+ kind = i == 0 ? "main" :
+ bpf_subprog_is_global(env, i) ? "global" : "static";
+ verbose(env, "subprog %d (%s) %s insns_self %d insns_total %d stack %d\n",
+ i, name, kind, env->subprog_info[i].insns_self,
+ env->subprog_info[i].insns_total,
+ env->subprog_info[i].stack_depth);
+ }
}
verbose(env, "processed %d insns (limit %d) max_states_per_insn %d "
"total_states %d peak_states %d mark_read %d\n",
@@ -18602,6 +19784,7 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
{
const struct btf_type *t, *func_proto;
const struct bpf_struct_ops_desc *st_ops_desc;
+ const struct bpf_struct_ops_arg_info *arg_info;
const struct bpf_struct_ops *st_ops;
const struct btf_member *member;
struct bpf_prog *prog = env->prog;
@@ -18680,10 +19863,23 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
return -EACCES;
}
- for (i = 0; i < st_ops_desc->arg_info[member_idx].cnt; i++) {
- if (st_ops_desc->arg_info[member_idx].info[i].refcounted) {
+ arg_info = &st_ops_desc->arg_info[member_idx];
+ for (i = 0; i < arg_info->cnt; i++) {
+ const struct bpf_ctx_arg_aux *info = &arg_info->info[i];
+
+ if (info->refcounted)
has_refcounted_arg = true;
- break;
+ if (base_type(info->reg_type) == PTR_TO_ARENA) {
+ if (!bpf_jit_supports_arena_args()) {
+ verbose(env, "JIT does not support arena arguments\n");
+ return -ENOTSUPP;
+ }
+ if (!prog->aux->arena) {
+ verbose(env,
+ "arena argument of %s requires a program with an associated arena\n",
+ mname);
+ return -EINVAL;
+ }
}
}
@@ -18704,8 +19900,7 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
prog->aux->attach_func_name = mname;
env->ops = st_ops->verifier_ops;
- return bpf_prog_ctx_arg_info_init(prog, st_ops_desc->arg_info[member_idx].info,
- st_ops_desc->arg_info[member_idx].cnt);
+ return bpf_prog_ctx_arg_info_init(prog, arg_info->info, arg_info->cnt);
}
#define SECURITY_PREFIX "security_"
@@ -18812,6 +20007,9 @@ static int btf_id_allow_sleepable(u32 btf_id, unsigned long addr, const struct b
const struct btf_type *t;
const char *tname;
+ if (!btf_is_kernel(btf))
+ return -EINVAL;
+
switch (prog->type) {
case BPF_PROG_TYPE_TRACING:
t = btf_type_by_id(btf, btf_id);
@@ -18855,6 +20053,61 @@ static int btf_id_allow_sleepable(u32 btf_id, unsigned long addr, const struct b
return -EINVAL;
}
+/*
+ * Resolve the prototype describing a trace target's real ABI. A
+ * KF_IMPLICIT_ARGS kfunc has its injected args stripped from the public
+ * prototype, so use the _impl prototype; other targets use their own.
+ */
+static const struct btf_type *
+btf_attach_func_proto(struct bpf_verifier_log *log, struct btf *btf, u32 func_id)
+{
+ const struct btf_type *func;
+ struct module *mod = NULL;
+ const char *name;
+ int implicit;
+
+ func = btf_type_by_id(btf, func_id);
+ if (!func || !btf_type_is_func(func))
+ return NULL;
+ name = btf_name_by_offset(btf, func->name_off);
+
+ /*
+ * btf_kfunc_check_flag() reads kfunc_set_tab, which for a module is
+ * stable only once it is live; hold a module ref across the read to
+ * exclude a concurrent module load.
+ */
+ if (btf_is_module(btf)) {
+ mod = btf_try_get_module(btf);
+ if (!mod)
+ return NULL;
+ }
+ implicit = btf_kfunc_check_flag(btf, func_id, KF_IMPLICIT_ARGS);
+ module_put(mod);
+
+ if (implicit == -EINVAL) {
+ bpf_log(log, "kfunc %s has inconsistent KF_IMPLICIT_ARGS\n", name);
+ return NULL;
+ }
+ if (implicit > 0)
+ return find_kfunc_impl_proto(log, btf, name);
+
+ return btf_type_by_id(btf, func->type);
+}
+
+static bool attach_uses_trampoline_retval(enum bpf_attach_type type)
+{
+ switch (type) {
+ case BPF_MODIFY_RETURN:
+ case BPF_TRACE_FEXIT:
+ case BPF_TRACE_FEXIT_MULTI:
+ case BPF_TRACE_FSESSION:
+ case BPF_TRACE_FSESSION_MULTI:
+ return true;
+ default:
+ return false;
+ }
+}
+
int bpf_check_attach_target(struct bpf_verifier_log *log,
const struct bpf_prog *prog,
const struct bpf_prog *tgt_prog,
@@ -18914,6 +20167,16 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
bpf_log(log, "Subprog %s doesn't exist\n", tname);
return -EINVAL;
}
+ /*
+ * A struct_ops indirect trampoline converts arena arguments
+ * before invoking its program. A tracing or extension program
+ * attached to the main program would see the converted offset as a
+ * regular BTF pointer.
+ */
+ if (subprog == 0 && bpf_prog_has_arena_ctx_arg(tgt_prog)) {
+ bpf_log(log, "Cannot attach to a target with arena context arguments\n");
+ return -EOPNOTSUPP;
+ }
if (aux->func && aux->func[subprog]->aux->exception_cb) {
bpf_log(log,
"%s programs cannot attach to exception callback\n",
@@ -19103,8 +20366,8 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
if (prog_extension &&
btf_check_type_match(log, prog, btf, t))
return -EINVAL;
- t = btf_type_by_id(btf, t->type);
- if (!btf_type_is_func_proto(t))
+ t = btf_attach_func_proto(log, btf, btf_id);
+ if (!t || !btf_type_is_func_proto(t))
return -EINVAL;
if ((prog->aux->saved_dst_prog_type || prog->aux->saved_dst_attach_type) &&
@@ -19119,6 +20382,14 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
if (ret < 0)
return ret;
+ if (tgt_info->fmodel.ret_size > 8 &&
+ attach_uses_trampoline_retval(prog->expected_attach_type)) {
+ bpf_log(log,
+ "Attach to function %s with a >8 byte return value is not supported for this attach type\n",
+ tname);
+ return -EOPNOTSUPP;
+ }
+
/*
* *.multi programs don't need an address during program
* verification, we just take the module ref if needed.
@@ -19354,7 +20625,7 @@ static int check_attach_btf_id(struct bpf_verifier_env *env)
return -ENOMEM;
if (tgt_prog && tgt_prog->aux->tail_call_reachable)
- tr->flags = BPF_TRAMP_F_TAIL_CALL_CTX;
+ bpf_trampoline_set_flags(tr, BPF_TRAMP_F_TAIL_CALL_CTX);
prog->aux->dst_trampoline = tr;
return 0;
@@ -19387,14 +20658,15 @@ int bpf_check_attach_btf_id_multi(struct btf *btf, struct bpf_prog *prog, u32 bt
tname = btf_name_by_offset(btf, t->name_off);
if (!tname)
return -EINVAL;
- if (!btf_type_is_func(t))
- return -EINVAL;
- t = btf_type_by_id(btf, t->type);
- if (!btf_type_is_func_proto(t))
+ t = btf_attach_func_proto(NULL, btf, btf_id);
+ if (!t || !btf_type_is_func_proto(t))
return -EINVAL;
err = btf_distill_func_proto(NULL, btf, t, tname, &tgt_info->fmodel);
if (err < 0)
return err;
+ if (tgt_info->fmodel.ret_size > 8 &&
+ attach_uses_trampoline_retval(prog->expected_attach_type))
+ return -EOPNOTSUPP;
if (btf_is_module(btf)) {
/* The bpf program already holds reference to module. */
if (WARN_ON_ONCE(!prog->aux->mod))
@@ -19418,13 +20690,25 @@ int bpf_check_attach_btf_id_multi(struct btf *btf, struct bpf_prog *prog, u32 bt
struct btf *bpf_get_btf_vmlinux(void)
{
- if (!btf_vmlinux && IS_ENABLED(CONFIG_DEBUG_INFO_BTF)) {
- mutex_lock(&bpf_verifier_lock);
- if (!btf_vmlinux)
- btf_vmlinux = btf_parse_vmlinux();
- mutex_unlock(&bpf_verifier_lock);
+ /* Pairs with the smp_store_release() on the parse path below. */
+ struct btf *btf = smp_load_acquire(&btf_vmlinux);
+
+ if (!btf && IS_ENABLED(CONFIG_DEBUG_INFO_BTF)) {
+ mutex_lock(&btf_vmlinux_lock);
+ btf = btf_vmlinux;
+ if (!btf) {
+ btf = btf_parse_vmlinux();
+ /*
+ * Order the parsed BTF contents and the globals the
+ * parse populated (e.g. bpf_ctx_convert.t) before
+ * the pointer publication. Pairs with the acquire
+ * on the lockless fast path above.
+ */
+ smp_store_release(&btf_vmlinux, btf);
+ }
+ mutex_unlock(&btf_vmlinux_lock);
}
- return btf_vmlinux;
+ return btf;
}
/*
@@ -19432,7 +20716,7 @@ struct btf *bpf_get_btf_vmlinux(void)
* this case expect that every file descriptor in the array is either a map or
* a BTF. Everything else is considered to be trash.
*/
-static int add_fd_from_fd_array(struct bpf_verifier_env *env, int fd)
+static int add_fd_from_fd_array(struct bpf_verifier_env *env, u32 idx, int fd)
{
struct bpf_map *map;
struct btf *btf;
@@ -19444,51 +20728,82 @@ static int add_fd_from_fd_array(struct bpf_verifier_env *env, int fd)
err = __add_used_map(env, map);
if (err < 0)
return err;
+ fd_slot_set_map(&env->fd_array[idx], map);
return 0;
}
btf = __btf_get_by_fd(f);
if (!IS_ERR(btf)) {
btf_get(btf);
- return __add_used_btf(env, btf);
+ err = __add_used_btf(env, btf);
+ if (err < 0)
+ return err;
+ fd_slot_set_btf(&env->fd_array[idx], btf);
+ return 0;
}
verbose(env, "fd %d is not pointing to valid bpf_map or btf\n", fd);
return PTR_ERR(map);
}
-static int process_fd_array(struct bpf_verifier_env *env, union bpf_attr *attr, bpfptr_t uattr)
+/*
+ * A continuous fd_array is resolved into an in-memory cache with one slot
+ * per entry. The bound here is deliberately generous and not derived from
+ * the per-program object limits: Duplicate entries /are/ permitted, and
+ * the number of distinct maps and BTFs a program can bind is enforced when
+ * each entry is resolved by __add_used_map() and __add_used_btf().
+ */
+#define MAX_FD_ARRAY_CNT 4096
+
+static int process_fd_array_continuous(struct bpf_verifier_env *env,
+ bpfptr_t fd_array, u32 cnt)
{
- size_t size = sizeof(int);
- int ret;
- int fd;
+ int fd, ret;
u32 i;
- env->fd_array = make_bpfptr(attr->fd_array, uattr.is_kernel);
-
- /*
- * The only difference between old (no fd_array_cnt is given) and new
- * APIs is that in the latter case the fd_array is expected to be
- * continuous and is scanned for map fds right away
- */
- if (!attr->fd_array_cnt)
- return 0;
-
- /* Check for integer overflow */
- if (attr->fd_array_cnt >= (U32_MAX / size)) {
- verbose(env, "fd_array_cnt is too big (%u)\n", attr->fd_array_cnt);
- return -EINVAL;
+ if (cnt > MAX_FD_ARRAY_CNT) {
+ verbose(env, "fd_array has too many entries (%u, max %u)\n",
+ cnt, MAX_FD_ARRAY_CNT);
+ return -E2BIG;
}
- for (i = 0; i < attr->fd_array_cnt; i++) {
- if (copy_from_bpfptr_offset(&fd, env->fd_array, i * size, size))
+ env->fd_array = kvzalloc_objs(*env->fd_array, cnt, GFP_KERNEL_ACCOUNT);
+ if (!env->fd_array)
+ return -ENOMEM;
+ env->fd_array_cnt = cnt;
+ for (i = 0; i < cnt; i++) {
+ if (copy_from_bpfptr_offset(&fd, fd_array,
+ (size_t)i * sizeof(fd), sizeof(fd)))
return -EFAULT;
-
- ret = add_fd_from_fd_array(env, fd);
+ ret = add_fd_from_fd_array(env, i, fd);
if (ret)
return ret;
}
+ return 0;
+}
+
+static int process_fd_array(struct bpf_verifier_env *env,
+ union bpf_attr *attr, bpfptr_t uattr)
+{
+ bpfptr_t fd_array = make_bpfptr(attr->fd_array, uattr.is_kernel);
+ if (bpfptr_is_null(fd_array)) {
+ if (attr->fd_array_cnt) {
+ verbose(env, "fd_array_cnt %u without fd_array is invalid\n",
+ attr->fd_array_cnt);
+ return -EINVAL;
+ }
+ return 0;
+ }
+ /*
+ * New API: the caller passes fd_array_cnt and a continuous array that
+ * is resolved and bound up front. Legacy API (no fd_array_cnt): keep
+ * the caller's array and resolve entries on the spot at each reference.
+ */
+ if (attr->fd_array_cnt)
+ return process_fd_array_continuous(env, fd_array,
+ attr->fd_array_cnt);
+ env->fd_array_raw = fd_array;
return 0;
}
@@ -19688,6 +21003,51 @@ int bpf_fixup_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
insn_buf[4] = BPF_ALU64_REG(BPF_SUB, BPF_REG_0, BPF_REG_1);
insn_buf[5] = BPF_ALU64_IMM(BPF_NEG, BPF_REG_0, 0);
*cnt = 6;
+ } else if (desc->func_id == special_kfunc_list[KF_bpf_iter_num_new]) {
+ /* inline bpf_iter_num_new(&it, start, end); R1=&it, R2=start, R3=end */
+ int i = 0;
+
+ /* if (start > end) goto einval; */
+ insn_buf[i++] = BPF_JMP32_REG(BPF_JSGT, BPF_REG_2, BPF_REG_3, 8);
+ /* r0 = (u32)end - (u32)start; if (r0 > BPF_MAX_LOOPS) goto e2big; */
+ insn_buf[i++] = BPF_MOV32_REG(BPF_REG_0, BPF_REG_3);
+ insn_buf[i++] = BPF_ALU32_REG(BPF_SUB, BPF_REG_0, BPF_REG_2);
+ insn_buf[i++] = BPF_JMP_IMM(BPF_JGT, BPF_REG_0, BPF_MAX_LOOPS, 8);
+ /* s->cur = start - 1; s->end = end; return 0; */
+ insn_buf[i++] = BPF_ALU32_IMM(BPF_ADD, BPF_REG_2, -1);
+ insn_buf[i++] = BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_2, 0);
+ insn_buf[i++] = BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_3, 4);
+ insn_buf[i++] = BPF_MOV64_IMM(BPF_REG_0, 0);
+ insn_buf[i++] = BPF_JMP_A(5);
+ /* einval: s->cur = s->end = 0; return -EINVAL; */
+ insn_buf[i++] = BPF_ST_MEM(BPF_DW, BPF_REG_1, 0, 0);
+ insn_buf[i++] = BPF_MOV64_IMM(BPF_REG_0, -EINVAL);
+ insn_buf[i++] = BPF_JMP_A(2);
+ /* e2big: s->cur = s->end = 0; return -E2BIG; */
+ insn_buf[i++] = BPF_ST_MEM(BPF_DW, BPF_REG_1, 0, 0);
+ insn_buf[i++] = BPF_MOV64_IMM(BPF_REG_0, -E2BIG);
+ *cnt = i;
+ } else if (desc->func_id == special_kfunc_list[KF_bpf_iter_num_next]) {
+ /* inline bpf_iter_num_next(&it); R1=&it, returns &s->cur or NULL */
+ int i = 0;
+
+ /* r0 = s->cur + 1; if ((s32)r0 >= s->end) goto done; */
+ insn_buf[i++] = BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_1, 0);
+ insn_buf[i++] = BPF_ALU32_IMM(BPF_ADD, BPF_REG_0, 1);
+ insn_buf[i++] = BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_1, 4);
+ insn_buf[i++] = BPF_JMP32_REG(BPF_JSGE, BPF_REG_0, BPF_REG_2, 3);
+ /* s->cur = r0; return &s->cur; */
+ insn_buf[i++] = BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0, 0);
+ insn_buf[i++] = BPF_MOV64_REG(BPF_REG_0, BPF_REG_1);
+ insn_buf[i++] = BPF_JMP_A(2);
+ /* done: s->cur = s->end = 0; return NULL; */
+ insn_buf[i++] = BPF_ST_MEM(BPF_DW, BPF_REG_1, 0, 0);
+ insn_buf[i++] = BPF_MOV64_IMM(BPF_REG_0, 0);
+ *cnt = i;
+ } else if (desc->func_id == special_kfunc_list[KF_bpf_iter_num_destroy]) {
+ /* bpf_iter_num_destroy() is a no-op; emit a nop to drop the call */
+ insn_buf[0] = BPF_JMP_A(0);
+ *cnt = 1;
}
if (env->insn_aux_data[insn_idx].arg_prog) {
@@ -19703,6 +21063,146 @@ int bpf_fixup_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
return 0;
}
+static enum bpf_sig_keyring bpf_classify_keyring(s32 keyring_id)
+{
+ switch (keyring_id) {
+ case 0:
+ return BPF_SIG_KEYRING_BUILTIN;
+ case (s32)(unsigned long)VERIFY_USE_SECONDARY_KEYRING:
+ return BPF_SIG_KEYRING_SECONDARY;
+ case (s32)(unsigned long)VERIFY_USE_PLATFORM_KEYRING:
+ return BPF_SIG_KEYRING_PLATFORM;
+ default:
+ return BPF_SIG_KEYRING_USER;
+ }
+}
+
+/*
+ * Verify the PKCS#7 signature of a loaded program. Called from bpf_check()
+ * once the program's metadata maps have been resolved into used_maps, so
+ * the exact maps folded into the signature are the ones the program binds.
+ *
+ * The signature covers the instructions followed by the frozen contents of
+ * each map, in @maps order: insns || map_0 || map_1 || [...]. On success the
+ * verdict and keyring info are recorded on prog->aux.
+ */
+static int bpf_prog_verify_signature(struct bpf_verifier_env *env,
+ union bpf_attr *attr, bool is_kernel)
+{
+ bpfptr_t usig = make_bpfptr(attr->signature, is_kernel);
+ struct bpf_dynptr_kern sig_ptr, data_ptr;
+ struct bpf_prog *prog = env->prog;
+ struct bpf_map **maps = env->used_maps;
+ struct bpf_key *key = NULL;
+ void *sig, *data = NULL;
+ u32 map_cnt = env->used_map_cnt;
+ u32 i, off, insns_sz;
+ u64 data_sz;
+ int err = 0;
+
+ /*
+ * Don't attempt to use kmalloc_large or vmalloc for signatures.
+ * Practical signature for BPF program should be below this limit.
+ */
+ if (!attr->signature_size ||
+ attr->signature_size > KMALLOC_MAX_CACHE_SIZE)
+ return -EINVAL;
+ if (system_keyring_id_check(attr->keyring_id) == 0)
+ key = bpf_lookup_system_key(attr->keyring_id);
+ else
+ key = bpf_lookup_user_key(attr->keyring_id, 0);
+ if (!key) {
+ verbose(env, "cannot resolve signing keyring with keyring_id %d\n",
+ attr->keyring_id);
+ return -EINVAL;
+ }
+
+ sig = kvmemdup_bpfptr(usig, attr->signature_size);
+ if (IS_ERR(sig)) {
+ bpf_key_put(key);
+ return PTR_ERR(sig);
+ }
+
+ insns_sz = prog->len * sizeof(struct bpf_insn);
+ data_sz = insns_sz;
+ for (i = 0; i < map_cnt; i++) {
+ struct bpf_map *map = maps[i];
+
+ if (map->map_type != BPF_MAP_TYPE_ARRAY ||
+ !map->ops->map_direct_value_addr) {
+ verbose(env, "signed program metadata map '%s' must be an array\n",
+ map->name);
+ err = -EINVAL;
+ goto out;
+ }
+ if (!READ_ONCE(map->frozen)) {
+ verbose(env, "signed program metadata map '%s' must be frozen\n",
+ map->name);
+ err = -EPERM;
+ goto out;
+ }
+ if (bpf_map_write_active(map)) {
+ verbose(env, "signed program metadata map '%s' has active writers\n",
+ map->name);
+ err = -EBUSY;
+ goto out;
+ }
+ if (!map->excl_prog_sha) {
+ verbose(env, "signed program metadata map '%s' must be exclusive\n",
+ map->name);
+ err = -EPERM;
+ goto out;
+ }
+ data_sz += map->value_size;
+ }
+ if (bpf_dynptr_check_size(data_sz)) {
+ verbose(env, "signed payload too large: %llu bytes\n", data_sz);
+ err = -E2BIG;
+ goto out;
+ }
+ data = kvmalloc(data_sz, GFP_KERNEL_ACCOUNT | __GFP_ZERO);
+ if (!data) {
+ err = -ENOMEM;
+ goto out;
+ }
+ memcpy(data, prog->insnsi, insns_sz);
+ off = insns_sz;
+ for (i = 0; i < map_cnt; i++) {
+ struct bpf_map *map = maps[i];
+ u64 addr;
+
+ err = map->ops->map_direct_value_addr(map, &addr, 0);
+ if (err) {
+ verbose(env, "failed to read signed metadata map '%s': %d\n",
+ map->name, err);
+ goto out;
+ }
+ memcpy(data + off, (void *)(unsigned long)addr,
+ map->value_size);
+ off += map->value_size;
+ }
+
+ bpf_dynptr_init(&data_ptr, data, BPF_DYNPTR_TYPE_LOCAL, 0, data_sz);
+ bpf_dynptr_init(&sig_ptr, sig, BPF_DYNPTR_TYPE_LOCAL, 0,
+ attr->signature_size);
+
+ err = bpf_verify_pkcs7_signature((struct bpf_dynptr *)&data_ptr,
+ (struct bpf_dynptr *)&sig_ptr, key);
+ if (err) {
+ verbose(env, "signature verification failed: %d\n", err);
+ } else {
+ verbose(env, "signature verification passed\n");
+ prog->aux->sig.keyring_serial = bpf_key_serial(key);
+ prog->aux->sig.keyring_type = bpf_classify_keyring(attr->keyring_id);
+ prog->aux->sig.verdict = BPF_SIG_VERIFIED;
+ }
+out:
+ kvfree(data);
+ bpf_key_put(key);
+ kvfree(sig);
+ return err;
+}
+
int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
struct bpf_log_attr *attr_log)
{
@@ -19725,18 +21225,6 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
return -ENOMEM;
env->bt.env = env;
-
- len = (*prog)->len;
- env->insn_aux_data =
- vzalloc(array_size(sizeof(struct bpf_insn_aux_data), len));
- ret = -ENOMEM;
- if (!env->insn_aux_data)
- goto err_free_env;
- for (i = 0; i < len; i++)
- env->insn_aux_data[i].orig_idx = i;
- env->succ = bpf_iarray_realloc(NULL, 2);
- if (!env->succ)
- goto err_free_env;
env->prog = *prog;
env->ops = bpf_verifier_ops[env->prog->type];
@@ -19745,22 +21233,60 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
env->bypass_spec_v1 = bpf_bypass_spec_v1(env->prog->aux->token);
env->bypass_spec_v4 = bpf_bypass_spec_v4(env->prog->aux->token);
env->bpf_capable = is_priv = bpf_token_capable(env->prog->aux->token, CAP_BPF);
-
- bpf_get_btf_vmlinux();
-
- /* grab the mutex to protect few globals used by verifier */
- if (!is_priv)
- mutex_lock(&bpf_verifier_lock);
+ env->signature = attr->signature;
/* user could have requested verbose verifier output
* and supplied buffer to store the verification trace
*/
ret = bpf_vlog_init(&env->log, attr_log->level, attr_log->ubuf, attr_log->size);
if (ret)
- goto err_unlock;
+ goto err_free_env;
+ ret = bpf_diag_init(env);
+ if (ret)
+ goto err_prep;
+ if (env->prog->insnsi[env->prog->len - 1].code == (BPF_LD | BPF_IMM | BPF_DW)) {
+ verbose(env, "invalid bpf_ld_imm64 insn\n");
+ ret = -EINVAL;
+ goto err_prep;
+ }
+ if (env->signature) {
+ ret = bpf_prog_calc_tag(env->prog);
+ if (ret < 0)
+ goto err_prep;
+ }
ret = process_fd_array(env, attr, uattr);
if (ret)
+ goto err_prep;
+
+ if (env->signature) {
+ ret = bpf_prog_verify_signature(env, attr, uattr.is_kernel);
+ if (ret)
+ goto err_prep;
+ }
+
+ ret = security_bpf_prog_load(env->prog, attr, env->prog->aux->token,
+ uattr.is_kernel);
+ if (ret)
+ goto err_prep;
+
+ bpf_get_btf_vmlinux();
+
+ /* Serialize verification of unprivileged programs. */
+ if (!is_priv)
+ mutex_lock(&bpf_verifier_lock);
+
+ len = env->insn_aux_data_len = env->prog->len;
+ env->insn_aux_data =
+ __vmalloc(array_size(sizeof(struct bpf_insn_aux_data), len),
+ GFP_KERNEL_ACCOUNT | __GFP_ZERO);
+ ret = -ENOMEM;
+ if (!env->insn_aux_data)
+ goto skip_full_check;
+ for (i = 0; i < len; i++)
+ env->insn_aux_data[i].orig_idx = i;
+ env->succ = bpf_iarray_realloc(NULL, 2);
+ if (!env->succ)
goto skip_full_check;
mark_verifier_state_clean(env);
@@ -19793,11 +21319,18 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
INIT_LIST_HEAD(&env->explored_states[i]);
INIT_LIST_HEAD(&env->free_list);
- ret = bpf_check_btf_info_early(env, attr, uattr);
+ /* Prepare BTF and func_info needed to discover all subprograms. */
+ ret = bpf_prepare_btf_info(env, attr, uattr);
+ if (ret < 0)
+ goto skip_full_check;
+
+ /* Apply CO-RE before validating the program's instruction layout. */
+ ret = bpf_check_core_relo(env, attr, uattr);
if (ret < 0)
goto skip_full_check;
- ret = add_subprog_and_kfunc(env);
+ /* Discover all subprograms before validating their layout and BTF. */
+ ret = add_subprogs(env);
if (ret < 0)
goto skip_full_check;
@@ -19805,14 +21338,21 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
if (ret < 0)
goto skip_full_check;
+ /* Validate BTF against the complete subprogram layout. */
ret = bpf_check_btf_info(env, attr, uattr);
if (ret < 0)
goto skip_full_check;
+ /* Validate instructions and resolve the program's referenced resources. */
ret = check_and_resolve_insns(env);
if (ret < 0)
goto skip_full_check;
+ /* Build kfunc prototypes after resolving program resources. */
+ ret = add_kfuncs(env);
+ if (ret < 0)
+ goto skip_full_check;
+
if (bpf_prog_is_offloaded(env->prog->aux)) {
ret = bpf_prog_offload_verifier_prep(env->prog);
if (ret)
@@ -19984,17 +21524,27 @@ err_release_maps:
*prog = env->prog;
module_put(env->attach_btf_mod);
-err_unlock:
if (!is_priv)
mutex_unlock(&bpf_verifier_lock);
- bpf_clear_insn_aux_data(env, 0, env->prog->len);
- vfree(env->insn_aux_data);
+ goto err_free_env;
+err_prep:
+ err = bpf_log_attr_finalize(attr_log, &env->log);
+ if (err)
+ ret = err;
+ release_insn_arrays(env);
+ release_maps(env);
+ release_btfs(env);
err_free_env:
+ if (env->insn_aux_data)
+ bpf_clear_insn_aux_data(env, 0, env->insn_aux_data_len);
+ vfree(env->insn_aux_data);
+ kvfree(env->fd_array);
bpf_stack_liveness_free(env);
kvfree(env->cfg.insn_postorder);
kvfree(env->scc_info);
kvfree(env->succ);
kvfree(env->gotox_tmp_buf);
+ bpf_diag_free(env);
kvfree(env);
return ret;
}
diff --git a/kernel/capability.c b/kernel/capability.c
index 829f49ae07b9..90e6ab62f6db 100644
--- a/kernel/capability.c
+++ b/kernel/capability.c
@@ -326,7 +326,6 @@ bool has_capability_noaudit(struct task_struct *t, int cap)
{
return has_ns_capability_noaudit(t, &init_user_ns, cap);
}
-EXPORT_SYMBOL(has_capability_noaudit);
static bool ns_capable_common(struct user_namespace *ns,
int cap,
@@ -416,6 +415,24 @@ bool capable(int cap)
return ns_capable(&init_user_ns, cap);
}
EXPORT_SYMBOL(capable);
+
+/**
+ * capable_noaudit - Determine if the current task has a superior
+ * capability in effect by checking the process's effective
+ * capabilities (unaudited).
+ * @cap: The capability to be tested for
+ *
+ * This is the same as capable(), except it uses CAP_OPT_NOAUDIT as to prevent
+ * issuing spurious audit messages.
+ *
+ * This sets PF_SUPERPRIV on the task if the capability is available on the
+ * assumption that it's about to be used.
+ */
+bool capable_noaudit(int cap)
+{
+ return ns_capable_noaudit(&init_user_ns, cap);
+}
+EXPORT_SYMBOL(capable_noaudit);
#endif /* CONFIG_MULTIUSER */
/**
diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
index 38f8d9df8fbc..227d09704ca5 100644
--- a/kernel/cgroup/cgroup.c
+++ b/kernel/cgroup/cgroup.c
@@ -88,6 +88,8 @@ EXPORT_SYMBOL_GPL(css_set_lock);
struct blocking_notifier_head cgroup_lifetime_notifier =
BLOCKING_NOTIFIER_INIT(cgroup_lifetime_notifier);
+struct blocking_notifier_head cgroup_task_notifier =
+ BLOCKING_NOTIFIER_INIT(cgroup_task_notifier);
DEFINE_SPINLOCK(trace_cgroup_path_lock);
char trace_cgroup_path[TRACE_CGROUP_PATH_LEN];
@@ -104,7 +106,7 @@ DEFINE_PERCPU_RWSEM(cgroup_threadgroup_rwsem);
#define cgroup_assert_mutex_or_rcu_locked() \
RCU_LOCKDEP_WARN(!rcu_read_lock_held() && \
!lockdep_is_held(&cgroup_mutex), \
- "cgroup_mutex or RCU read lock required");
+ "cgroup_mutex or RCU read lock required")
/*
* cgroup destruction makes heavy use of work items and there can be a lot
@@ -2676,14 +2678,27 @@ struct task_struct *cgroup_taskset_next(struct cgroup_taskset *tset,
return NULL;
}
+static void cgroup_migrate_notify_canceled(struct css_set *src_cset,
+ struct task_struct *task)
+{
+ struct cgroup_task_migrate_ctx ctx = {
+ .task = task,
+ .src_dcgrp = src_cset->dfl_cgrp,
+ .dst_dcgrp = src_cset->mg_dst_cset->dfl_cgrp,
+ };
+
+ blocking_notifier_call_chain(&cgroup_task_notifier,
+ CGROUP_TASK_MIGRATE_CANCELED, &ctx);
+}
+
/**
* cgroup_migrate_execute - migrate a taskset
* @mgctx: migration context
*
- * Migrate tasks in @mgctx as setup by migration preparation functions.
- * This function fails iff one of the ->can_attach callbacks fails and
- * guarantees that either all or none of the tasks in @mgctx are migrated.
- * @mgctx is consumed regardless of success.
+ * Migrate tasks in @mgctx as setup by migration preparation functions. This
+ * function fails iff one of the ->can_attach callbacks or CGROUP_TASK_MIGRATING
+ * notifications fails and guarantees that either all or none of the tasks in
+ * @mgctx are migrated. @mgctx is consumed regardless of success.
*/
static int cgroup_migrate_execute(struct cgroup_mgctx *mgctx)
{
@@ -2691,6 +2706,7 @@ static int cgroup_migrate_execute(struct cgroup_mgctx *mgctx)
struct cgroup_subsys *ss;
struct task_struct *task, *tmp_task;
struct css_set *cset, *tmp_cset;
+ bool dfl_migration = false;
int ssid, failed_ssid, ret;
/* check that we can legitimately attach to the cgroup */
@@ -2708,6 +2724,33 @@ static int cgroup_migrate_execute(struct cgroup_mgctx *mgctx)
}
/*
+ * Notify each task about the impending migration. An error return fails
+ * the migration. Only migrations on the default hierarchy are reported:
+ * a migration modifies either every moved task's dfl cgroup or, on
+ * cgroup1 or for subtree_control writes, none.
+ */
+ list_for_each_entry(cset, &tset->src_csets, mg_node) {
+ if (cset->dfl_cgrp == cset->mg_dst_cset->dfl_cgrp)
+ continue;
+ dfl_migration = true;
+ list_for_each_entry(task, &cset->mg_tasks, cg_list) {
+ struct cgroup_task_migrate_ctx ctx = {
+ .task = task,
+ .src_dcgrp = cset->dfl_cgrp,
+ .dst_dcgrp = cset->mg_dst_cset->dfl_cgrp,
+ };
+
+ ret = blocking_notifier_call_chain_robust(&cgroup_task_notifier,
+ CGROUP_TASK_MIGRATING,
+ CGROUP_TASK_MIGRATE_CANCELED,
+ &ctx);
+ ret = notifier_to_errno(ret);
+ if (ret)
+ goto out_cancel_migrating;
+ }
+ }
+
+ /*
* Now that we're guaranteed success, proceed to move all tasks to
* the new cgroup. There are no failure cases after here, so this
* is the commit point.
@@ -2750,9 +2793,41 @@ static int cgroup_migrate_execute(struct cgroup_mgctx *mgctx)
} while_each_subsys_mask();
}
+ /*
+ * Notify each task after successful migration. The operation can no
+ * longer fail and the return value is ignored. The MIGRATING loop
+ * above explains why only dfl migrations are reported. Per-task
+ * sources are not tracked past the commit point, so src_dcgrp is
+ * NULL.
+ */
+ if (dfl_migration) {
+ list_for_each_entry(cset, &tset->dst_csets, mg_node) {
+ list_for_each_entry(task, &cset->mg_tasks, cg_list) {
+ struct cgroup_task_migrate_ctx ctx = {
+ .task = task,
+ .dst_dcgrp = cset->dfl_cgrp,
+ };
+
+ blocking_notifier_call_chain(
+ &cgroup_task_notifier,
+ CGROUP_TASK_MIGRATED, &ctx);
+ }
+ }
+ }
+
ret = 0;
goto out_release_tset;
+out_cancel_migrating:
+ list_for_each_entry_continue_reverse(task, &cset->mg_tasks, cg_list)
+ cgroup_migrate_notify_canceled(cset, task);
+ list_for_each_entry_continue_reverse(cset, &tset->src_csets, mg_node) {
+ if (cset->dfl_cgrp == cset->mg_dst_cset->dfl_cgrp)
+ continue;
+ list_for_each_entry_reverse(task, &cset->mg_tasks, cg_list)
+ cgroup_migrate_notify_canceled(cset, task);
+ }
+ failed_ssid = CGROUP_SUBSYS_COUNT;
out_cancel_attach:
if (tset->nr_tasks) {
do_each_subsys_mask(ss, ssid, mgctx->ss_mask) {
@@ -2976,11 +3051,11 @@ int cgroup_migrate_prepare_dst(struct cgroup_mgctx *mgctx)
* cgroup_migrate_prepare_dst() on the targets before invoking this
* function and following up with cgroup_migrate_finish().
*
- * As long as a controller's ->can_attach() doesn't fail, this function is
- * guaranteed to succeed. This means that, excluding ->can_attach()
- * failure, when migrating multiple targets, the success or failure can be
- * decided for all targets by invoking group_migrate_prepare_dst() before
- * actually starting migrating.
+ * As long as a controller's ->can_attach() or a CGROUP_TASK_MIGRATING
+ * notification doesn't fail, this function is guaranteed to succeed. This
+ * means that, excluding those failures, when migrating multiple targets,
+ * the success or failure can be decided for all targets by invoking
+ * group_migrate_prepare_dst() before actually starting migrating.
*/
int cgroup_migrate(struct task_struct *leader, bool threadgroup,
struct cgroup_mgctx *mgctx)
@@ -3996,6 +4071,7 @@ static ssize_t pressure_write(struct kernfs_open_file *of, char *buf,
struct psi_trigger *new;
struct cgroup *cgrp;
struct psi_group *psi;
+ bool need_rtpoll_worker;
ssize_t ret = 0;
cgrp = cgroup_kn_lock_live(of->kn, false);
@@ -4015,12 +4091,32 @@ static ssize_t pressure_write(struct kernfs_open_file *of, char *buf,
}
psi = cgroup_psi(cgrp);
- new = psi_trigger_create(psi, buf, res, of->file, of);
+ new = psi_trigger_create(psi, buf, res, of->file, of,
+ &need_rtpoll_worker);
if (IS_ERR(new)) {
ret = PTR_ERR(new);
goto out_unlock;
}
+ /*
+ * The worker fork must run with neither cgroup_mutex nor the file's
+ * kernfs active reference held. The latter is broken since
+ * cgroup_kn_lock_live(). @of->priv may be released while unlocked, so
+ * recheck before publishing @new.
+ */
+ if (need_rtpoll_worker) {
+ cgroup_unlock();
+ ret = psi_trigger_create_rtpoll_worker(psi);
+ cgroup_lock();
+
+ if (!ret && !of->priv)
+ ret = -ENODEV;
+ if (ret) {
+ psi_trigger_destroy(new);
+ goto out_unlock;
+ }
+ }
+
smp_store_release(&ctx->psi.trigger, new);
out_unlock:
@@ -5207,10 +5303,13 @@ struct task_struct *css_task_iter_next(struct css_task_iter *it)
if (it->flags & CSS_TASK_ITER_SKIPPED)
css_task_iter_advance(it);
- if (it->task_pos) {
+ while (it->task_pos && !it->cur_task) {
it->cur_task = list_entry(it->task_pos, struct task_struct,
cg_list);
- get_task_struct(it->cur_task);
+ /* a task on dying_tasks with zero refcount is only valid for
+ * RCU readers, not even interesting for
+ * CSS_TASK_ITER_WITH_DEAD, find another one */
+ it->cur_task = tryget_task_struct(it->cur_task);
css_task_iter_advance(it);
}
@@ -6777,10 +6876,7 @@ static int cgroup_css_set_fork(struct kernel_clone_args *kargs)
spin_lock_irq(&css_set_lock);
cset = task_css_set(current);
get_css_set(cset);
- if (kargs->cgrp)
- kargs->kill_seq = kargs->cgrp->kill_seq;
- else
- kargs->kill_seq = cset->dfl_cgrp->kill_seq;
+ kargs->kill_seq = cset->dfl_cgrp->kill_seq;
spin_unlock_irq(&css_set_lock);
if (!(kargs->flags & CLONE_INTO_CGROUP)) {
@@ -6844,6 +6940,7 @@ static int cgroup_css_set_fork(struct kernel_clone_args *kargs)
put_css_set(cset);
kargs->cgrp = dst_cgrp;
+ kargs->kill_seq = dst_cgrp->kill_seq;
return ret;
err:
diff --git a/kernel/cgroup/cpuset-internal.h b/kernel/cgroup/cpuset-internal.h
index f7aaf01f7cd5..e7d010661fd3 100644
--- a/kernel/cgroup/cpuset-internal.h
+++ b/kernel/cgroup/cpuset-internal.h
@@ -146,10 +146,9 @@ struct cpuset {
nodemask_t old_mems_allowed;
/*
- * Tasks are being attached to this cpuset. Used to prevent
- * zeroing cpus/mems_allowed between ->can_attach() and ->attach().
+ * For linking impacted cpusets during an attach operation.
*/
- int attach_in_progress;
+ struct llist_node attach_node;
/* partition root state */
int partition_root_state;
@@ -165,7 +164,7 @@ struct cpuset {
* number of SCHED_DEADLINE tasks attached to this cpuset, so that we
* know when to rebuild associated root domain bandwidth information.
*/
- int nr_deadline_tasks;
+ atomic_t nr_deadline_tasks;
int nr_migrate_dl_tasks;
/* DL bandwidth that needs destination reservation for this attach. */
u64 sum_migrate_dl_bw;
@@ -269,10 +268,7 @@ static inline int nr_cpusets(void)
static inline bool cpuset_is_populated(struct cpuset *cs)
{
lockdep_assert_cpuset_lock_held();
-
- /* Cpusets in the process of attaching should be considered as populated */
- return cgroup_is_populated(cs->css.cgroup) ||
- cs->attach_in_progress;
+ return cgroup_is_populated(cs->css.cgroup);
}
/**
diff --git a/kernel/cgroup/cpuset-v1.c b/kernel/cgroup/cpuset-v1.c
index 3e9968dd91e9..562ad35f00d0 100644
--- a/kernel/cgroup/cpuset-v1.c
+++ b/kernel/cgroup/cpuset-v1.c
@@ -204,7 +204,7 @@ static s64 cpuset_read_s64(struct cgroup_subsys_state *css, struct cftype *cft)
}
/*
- * update task's spread flag if cpuset's page/slab spread flag is set
+ * Update a task's spread flag if the cpuset's page spread flag is set.
*
* Call with callback_lock or cpuset_mutex held. The check can be skipped
* if on default hierarchy.
@@ -219,18 +219,13 @@ void cpuset1_update_task_spread_flags(struct cpuset *cs,
task_set_spread_page(tsk);
else
task_clear_spread_page(tsk);
-
- if (is_spread_slab(cs))
- task_set_spread_slab(tsk);
- else
- task_clear_spread_slab(tsk);
}
/**
- * cpuset1_update_tasks_flags - update the spread flags of tasks in the cpuset.
- * @cs: the cpuset in which each task's spread flags needs to be changed
+ * cpuset1_update_tasks_flags - update the page spread flag of cpuset tasks
+ * @cs: the cpuset whose tasks need their page spread flag updated
*
- * Iterate through each task of @cs updating its spread flags. As this
+ * Iterate through each task of @cs updating its page spread flag. As this
* function is called with cpuset_mutex held, cpuset membership stays
* stable.
*/
diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
index 591e3aa487fc..8b220e9df91e 100644
--- a/kernel/cgroup/cpuset.c
+++ b/kernel/cgroup/cpuset.c
@@ -37,6 +37,7 @@
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/task_work.h>
+#include <linux/llist.h>
DEFINE_STATIC_KEY_FALSE(cpusets_pre_enable_key);
DEFINE_STATIC_KEY_FALSE(cpusets_enabled_key);
@@ -152,6 +153,12 @@ static cpumask_var_t isolated_cpus; /* CSCB */
static bool update_housekeeping; /* RWCS */
/*
+ * Set if "cpuset_v2_mode" mount option is used
+ * Cached at bind time and not lock protected; accessed via {READ,WRITE}_ONCE
+ */
+static bool cpuset_v2_mode;
+
+/*
* Copy of isolated_cpus to be passed to housekeeping_update()
*/
static cpumask_var_t isolated_hk_cpus; /* T */
@@ -222,14 +229,14 @@ void inc_dl_tasks_cs(struct task_struct *p)
{
struct cpuset *cs = task_cs(p);
- cs->nr_deadline_tasks++;
+ atomic_inc(&cs->nr_deadline_tasks);
}
void dec_dl_tasks_cs(struct task_struct *p)
{
struct cpuset *cs = task_cs(p);
- cs->nr_deadline_tasks--;
+ atomic_dec(&cs->nr_deadline_tasks);
}
static inline bool is_partition_valid(const struct cpuset *cs)
@@ -356,6 +363,41 @@ static struct workqueue_struct *cpuset_migrate_mm_wq;
static DECLARE_WAIT_QUEUE_HEAD(cpuset_attach_wq);
+/*
+ * Cpuset task attach context
+ * Protected by cpuset_mutex
+ */
+static struct {
+ int in_progress;
+ bool cpus_updated;
+ bool mems_updated;
+ bool task_work_queued;
+ bool many_dest_cs; /* Have many destination cpusets */
+ struct cpuset *old_cs; /* Source cpuset */
+ nodemask_t nodemask_to;
+} attach_ctx;
+static LLIST_HEAD(src_cs_head);
+static LLIST_HEAD(dst_cs_head);
+
+/*
+ * Wait if task attach is in progress until it is done and then acquire
+ * cpuset_mutex before returning.
+ */
+static void wait_attach_done_lock(void)
+ __acquires(&cpuset_mutex)
+{
+ for (;;) {
+ mutex_lock(&cpuset_mutex);
+ if (!attach_ctx.in_progress)
+ return;
+
+ mutex_unlock(&cpuset_mutex);
+
+ /* Wait until attach operation is done to prevent racing */
+ wait_event(cpuset_attach_wq, attach_ctx.in_progress == 0);
+ }
+}
+
static inline void check_insane_mems_config(nodemask_t *nodes)
{
if (!cpusets_insane_config() &&
@@ -368,22 +410,22 @@ static inline void check_insane_mems_config(nodemask_t *nodes)
}
/*
- * decrease cs->attach_in_progress.
- * wake_up cpuset_attach_wq if cs->attach_in_progress==0.
+ * decrease attach_ctx.in_progress.
+ * wake_up cpuset_attach_wq if attach_ctx.in_progress==0.
*/
-static inline void dec_attach_in_progress_locked(struct cpuset *cs)
+static inline void dec_attach_in_progress_locked(void)
{
lockdep_assert_cpuset_lock_held();
- cs->attach_in_progress--;
- if (!cs->attach_in_progress)
+ attach_ctx.in_progress--;
+ if (!attach_ctx.in_progress)
wake_up(&cpuset_attach_wq);
}
-static inline void dec_attach_in_progress(struct cpuset *cs)
+static inline void dec_attach_in_progress(void)
{
mutex_lock(&cpuset_mutex);
- dec_attach_in_progress_locked(cs);
+ dec_attach_in_progress_locked();
mutex_unlock(&cpuset_mutex);
}
@@ -403,8 +445,7 @@ static inline bool cpuset_v2(void)
*/
static inline bool is_in_v2_mode(void)
{
- return cpuset_v2() ||
- (cpuset_cgrp_subsys.root->flags & CGRP_ROOT_CPUSET_V2_MODE);
+ return cpuset_v2() || READ_ONCE(cpuset_v2_mode);
}
/**
@@ -432,8 +473,7 @@ static inline bool partition_is_populated(struct cpuset *cs,
* nr_populated_domain_children may include populated
* csets from descendants that are partitions.
*/
- if (cgroup_has_tasks(cs->css.cgroup) ||
- cs->attach_in_progress)
+ if (cgroup_has_tasks(cs->css.cgroup))
return true;
rcu_read_lock();
@@ -478,10 +518,26 @@ static void guarantee_active_cpus(struct task_struct *tsk,
rcu_read_lock();
cs = task_cs(tsk);
- while (!cpumask_intersects(cs->effective_cpus, pmask))
+ while (!cpumask_intersects(cs->effective_cpus, pmask)) {
cs = parent_cs(cs);
-
+ if (unlikely(!cs)) {
+ /*
+ * The top cpuset doesn't have any active cpu as a
+ * consequence of a race between its caller and the cpu
+ * hotplug operation where cpu_active_mask is updated
+ * asynchronously before cpuset_handle_hotplug() is
+ * being called to adjust the effective_cpus of the
+ * affected cpusets. But we know the top cpuset's
+ * effective_cpus is on its way to be identical to
+ * cpu_active_mask minus the exclusive CPUs dedicated
+ * to other valid cpuset partitions. Just pass back
+ * the filtered cpu_active_mask in this case.
+ */
+ goto out_unlock;
+ }
+ }
cpumask_and(pmask, pmask, cs->effective_cpus);
+out_unlock:
rcu_read_unlock();
}
@@ -489,7 +545,10 @@ static void guarantee_active_cpus(struct task_struct *tsk,
* Return in *pmask the portion of a cpusets's mems_allowed that
* are online, with memory. If none are online with memory, walk
* up the cpuset hierarchy until we find one that does have some
- * online mems. The top cpuset always has some mems online.
+ * online mems. The top cpuset always has some mems online. With v2,
+ * effective_mems should always contain online memory nodes except
+ * during the transition period where a memory node hotunplug operation
+ * is in progress.
*
* One way or another, we guarantee to return some non-empty subset
* of node_states[N_MEMORY].
@@ -581,6 +640,7 @@ static struct cpuset *dup_or_alloc_cpuset(struct cpuset *cs)
return NULL;
trial->dl_bw_cpu = -1;
+ init_llist_node(&trial->attach_node);
/* Setup cpumask pointer array */
cpumask_var_t *pmask[4] = {
@@ -918,7 +978,7 @@ static void dl_update_tasks_root_domain(struct cpuset *cs)
struct css_task_iter it;
struct task_struct *task;
- if (cs->nr_deadline_tasks == 0)
+ if (atomic_read(&cs->nr_deadline_tasks) == 0)
return;
css_task_iter_start(&cs->css, 0, &it);
@@ -1089,12 +1149,35 @@ void cpuset_update_tasks_cpumask(struct cpuset *cs, struct cpumask *new_cpus)
* @cs: the cpuset the need to recompute the new effective_cpus mask
* @parent: the parent cpuset
*
+ * For v2, the parent's effective_cpus is inherited if cpumask is empty.
* The result is valid only if the given cpuset isn't a partition root.
*/
static void compute_effective_cpumask(struct cpumask *new_cpus,
struct cpuset *cs, struct cpuset *parent)
{
- cpumask_and(new_cpus, cs->cpus_allowed, parent->effective_cpus);
+ bool has_cpus;
+
+ has_cpus = cpumask_and(new_cpus, cs->cpus_allowed, parent->effective_cpus);
+ if (!has_cpus && is_in_v2_mode())
+ cpumask_copy(new_cpus, parent->effective_cpus);
+}
+
+/**
+ * compute_effective_nodemask - Compute the effective nodemask of the cpuset
+ * @new_mems: the temp variable for the new effective_mems mask
+ * @cs: the cpuset the need to recompute the new effective_mems mask
+ * @parent: the parent cpuset
+ *
+ * For v2, the parent's effective_mems is inherited if nodemask is empty.
+ */
+static void compute_effective_nodemask(nodemask_t *new_mems,
+ struct cpuset *cs, struct cpuset *parent)
+{
+ bool has_mems;
+
+ has_mems = nodes_and(*new_mems, cs->mems_allowed, parent->effective_mems);
+ if (!has_mems && is_in_v2_mode())
+ nodes_copy(*new_mems, parent->effective_mems);
}
/*
@@ -1197,6 +1280,28 @@ static void reset_partition_data(struct cpuset *cs)
cpumask_copy(cs->effective_cpus, parent->effective_cpus);
}
+/* Return true if isolated_cpus changes. */
+static bool isolated_cpu_update(int new_prs, int cpu)
+{
+ lockdep_assert_held(&callback_lock);
+ lockdep_assert_held(&cpuset_mutex);
+
+ if (new_prs == PRS_ISOLATED) {
+ if (cpumask_test_cpu(cpu, isolated_cpus))
+ return false;
+ cpumask_set_cpu(cpu, isolated_cpus);
+ return true;
+ }
+
+ /* CPUs isolated at boot must remain isolated. */
+ if (!cpumask_test_cpu(cpu,
+ housekeeping_cpumask(HK_TYPE_DOMAIN_BOOT)) ||
+ !cpumask_test_cpu(cpu, isolated_cpus))
+ return false;
+ cpumask_clear_cpu(cpu, isolated_cpus);
+ return true;
+}
+
/*
* isolated_cpus_update - Update the isolated_cpus mask
* @old_prs: old partition_root_state
@@ -1205,19 +1310,16 @@ static void reset_partition_data(struct cpuset *cs)
*/
static void isolated_cpus_update(int old_prs, int new_prs, struct cpumask *xcpus)
{
+ bool updated = false;
+ int cpu;
+
WARN_ON_ONCE(old_prs == new_prs);
lockdep_assert_held(&callback_lock);
lockdep_assert_held(&cpuset_mutex);
- if (new_prs == PRS_ISOLATED) {
- if (cpumask_subset(xcpus, isolated_cpus))
- return;
- cpumask_or(isolated_cpus, isolated_cpus, xcpus);
- } else {
- if (!cpumask_intersects(xcpus, isolated_cpus))
- return;
- cpumask_andnot(isolated_cpus, isolated_cpus, xcpus);
- }
- update_housekeeping = true;
+ for_each_cpu(cpu, xcpus)
+ updated |= isolated_cpu_update(new_prs, cpu);
+ if (updated)
+ update_housekeeping = true;
}
/*
@@ -1510,10 +1612,11 @@ static int remote_partition_enable(struct cpuset *cs, int new_prs,
* above it or remote partition root underneath it is not allowed.
*/
compute_excpus(cs, tmp->new_cpus);
- WARN_ON_ONCE(cpumask_intersects(tmp->new_cpus, subpartitions_cpus));
if (!cpumask_intersects(tmp->new_cpus, cpu_active_mask) ||
cpumask_subset(top_cpuset.effective_cpus, tmp->new_cpus))
return PERR_INVCPUS;
+ if (cpumask_intersects(tmp->new_cpus, subpartitions_cpus))
+ return PERR_NOCPUS;
if (((new_prs == PRS_ISOLATED) &&
!isolated_cpus_can_update(tmp->new_cpus, NULL)) ||
prstate_housekeeping_conflict(new_prs, tmp->new_cpus))
@@ -1525,7 +1628,7 @@ static int remote_partition_enable(struct cpuset *cs, int new_prs,
cpumask_copy(cs->effective_xcpus, tmp->new_cpus);
spin_unlock_irq(&callback_lock);
cpuset_force_rebuild();
- cs->prs_err = 0;
+ WRITE_ONCE(cs->prs_err, 0);
/*
* Propagate changes in top_cpuset's effective_cpus down the hierarchy.
@@ -1599,7 +1702,7 @@ static void remote_cpus_update(struct cpuset *cs, struct cpumask *xcpus,
WARN_ON_ONCE(!cpumask_subset(cs->effective_xcpus, subpartitions_cpus));
if (cpumask_empty(excpus)) {
- cs->prs_err = PERR_CPUSEMPTY;
+ WRITE_ONCE(cs->prs_err, PERR_CPUSEMPTY);
goto invalidate;
}
@@ -1614,13 +1717,13 @@ static void remote_cpus_update(struct cpuset *cs, struct cpumask *xcpus,
if (adding) {
WARN_ON_ONCE(cpumask_intersects(tmp->addmask, subpartitions_cpus));
if (!capable(CAP_SYS_ADMIN))
- cs->prs_err = PERR_ACCESS;
+ WRITE_ONCE(cs->prs_err, PERR_ACCESS);
else if (cpumask_intersects(tmp->addmask, subpartitions_cpus) ||
cpumask_subset(top_cpuset.effective_cpus, tmp->addmask))
- cs->prs_err = PERR_NOCPUS;
+ WRITE_ONCE(cs->prs_err, PERR_NOCPUS);
else if ((prs == PRS_ISOLATED) &&
!isolated_cpus_can_update(tmp->addmask, tmp->delmask))
- cs->prs_err = PERR_HKEEPING;
+ WRITE_ONCE(cs->prs_err, PERR_HKEEPING);
if (cs->prs_err)
goto invalidate;
}
@@ -2048,13 +2151,13 @@ static void compute_partition_effective_cpumask(struct cpuset *cs,
* partition root.
*/
WARN_ON_ONCE(is_remote_partition(child));
- child->prs_err = 0;
+ WRITE_ONCE(child->prs_err, 0);
if (!cpumask_subset(child->effective_xcpus,
cs->effective_xcpus))
- child->prs_err = PERR_INVCPUS;
+ WRITE_ONCE(child->prs_err, PERR_INVCPUS);
else if (populated &&
cpumask_subset(new_ecpus, child->effective_xcpus))
- child->prs_err = PERR_NOCPUS;
+ WRITE_ONCE(child->prs_err, PERR_NOCPUS);
if (child->prs_err) {
int old_prs = child->partition_root_state;
@@ -2144,15 +2247,6 @@ static void update_cpumasks_hier(struct cpuset *cs, struct tmpmasks *tmp,
}
/*
- * If it becomes empty, inherit the effective mask of the
- * parent, which is guaranteed to have some CPUs unless
- * it is a partition root that has explicitly distributed
- * out all its CPUs.
- */
- if (is_in_v2_mode() && !remote && cpumask_empty(tmp->new_cpus))
- cpumask_copy(tmp->new_cpus, parent->effective_cpus);
-
- /*
* Skip the whole subtree if
* 1) the cpumask remains the same,
* 2) has no partition root state,
@@ -2367,8 +2461,10 @@ static void partition_cpus_change(struct cpuset *cs, struct cpuset *trialcs,
return;
prs_err = validate_partition(cs, trialcs);
- if (prs_err)
- trialcs->prs_err = cs->prs_err = prs_err;
+ if (prs_err) {
+ WRITE_ONCE(cs->prs_err, prs_err);
+ trialcs->prs_err = prs_err;
+ }
if (is_remote_partition(cs)) {
if (trialcs->prs_err)
@@ -2619,6 +2715,14 @@ static void *cpuset_being_rebound;
* Iterate through each task of @cs updating its mems_allowed to the
* effective cpuset's. As this function is called with cpuset_mutex held,
* cpuset membership stays stable.
+ *
+ * - cpuset_change_task_nodemask(): guarantee_online_mems()
+ * - mpol_rebind_mm(): effective_mems
+ * - cpuset_migrate_mm(): guarantee_online_mems()
+ * - old_mems_allowed: guarantee_online_mems()
+ *
+ * For v2, guarantee_online_mems() should return a node mask that is the same
+ * as the effective_mems of current cpuset.
*/
void cpuset_update_tasks_nodemask(struct cpuset *cs)
{
@@ -2627,7 +2731,6 @@ void cpuset_update_tasks_nodemask(struct cpuset *cs)
struct task_struct *task;
cpuset_being_rebound = cs; /* causes mpol_dup() rebind */
-
guarantee_online_mems(cs, &newmems);
/*
@@ -2647,13 +2750,22 @@ void cpuset_update_tasks_nodemask(struct cpuset *cs)
cpuset_change_task_nodemask(task, &newmems);
+ /* Rebind and migrate mm only for thread group leader */
+ if (!thread_group_leader(task))
+ continue;
+
mm = get_task_mm(task);
if (!mm)
continue;
migrate = is_memory_migrate(cs);
- mpol_rebind_mm(mm, &cs->mems_allowed);
+ /*
+ * For v1 we can have empty effective_mems, but we cannot
+ * attach any tasks (see cpuset_can_attach_check()). For v2,
+ * effective_mems is guaranteed to not be empty.
+ */
+ mpol_rebind_mm(mm, &cs->effective_mems);
if (migrate)
cpuset_migrate_mm(mm, &cs->old_mems_allowed, &newmems);
else
@@ -2692,14 +2804,7 @@ static void update_nodemasks_hier(struct cpuset *cs, nodemask_t *new_mems)
cpuset_for_each_descendant_pre(cp, pos_css, cs) {
struct cpuset *parent = parent_cs(cp);
- bool has_mems = nodes_and(*new_mems, cp->mems_allowed, parent->effective_mems);
-
- /*
- * If it becomes empty, inherit the effective mask of the
- * parent, which is guaranteed to have some MEMs.
- */
- if (is_in_v2_mode() && !has_mems)
- *new_mems = parent->effective_mems;
+ compute_effective_nodemask(new_mems, cp, parent);
/* Skip the whole subtree if the nodemask remains the same. */
if (nodes_equal(*new_mems, cp->effective_mems)) {
@@ -2800,7 +2905,7 @@ int cpuset_update_flag(cpuset_flagbits_t bit, struct cpuset *cs,
{
struct cpuset *trialcs;
int balance_flag_changed;
- int spread_flag_changed;
+ int spread_page_changed;
int err;
trialcs = dup_or_alloc_cpuset(cs);
@@ -2819,8 +2924,7 @@ int cpuset_update_flag(cpuset_flagbits_t bit, struct cpuset *cs,
balance_flag_changed = (is_sched_load_balance(cs) !=
is_sched_load_balance(trialcs));
- spread_flag_changed = ((is_spread_slab(cs) != is_spread_slab(trialcs))
- || (is_spread_page(cs) != is_spread_page(trialcs)));
+ spread_page_changed = is_spread_page(cs) != is_spread_page(trialcs);
spin_lock_irq(&callback_lock);
cs->flags = trialcs->flags;
@@ -2833,7 +2937,7 @@ int cpuset_update_flag(cpuset_flagbits_t bit, struct cpuset *cs,
rebuild_sched_domains_locked();
}
- if (spread_flag_changed)
+ if (spread_page_changed)
cpuset1_update_tasks_flags(cs);
out:
free_cpuset(trialcs);
@@ -2968,27 +3072,104 @@ out:
return 0;
}
-static struct cpuset *cpuset_attach_old_cs;
-
/*
* Check to see if a cpuset can accept a new task
* For v1, cpus_allowed and mems_allowed can't be empty.
* For v2, effective_cpus can't be empty.
* Note that in v1, effective_cpus = cpus_allowed.
+ *
+ * Also set the boolean flag passed in by @psetsched depending on if
+ * security_task_setscheduler() call is needed and @oldcs is not NULL.
*/
-static int cpuset_can_attach_check(struct cpuset *cs)
+static int cpuset_can_attach_check(struct cpuset *cs, struct cpuset *oldcs,
+ bool *psetsched)
{
+ bool cpus_updated, mems_updated;
+
if (cpumask_empty(cs->effective_cpus) ||
(!is_in_v2_mode() && nodes_empty(cs->mems_allowed)))
return -ENOSPC;
+
+ if (!oldcs)
+ return 0;
+
+ if (!llist_on_list(&oldcs->attach_node))
+ llist_add(&oldcs->attach_node, &src_cs_head);
+
+ if (!llist_on_list(&cs->attach_node))
+ llist_add(&cs->attach_node, &dst_cs_head);
+
+ cpus_updated = !cpumask_equal(cs->effective_cpus, oldcs->effective_cpus);
+ mems_updated = !nodes_equal(cs->effective_mems, oldcs->effective_mems);
+
+ if (cpus_updated)
+ attach_ctx.cpus_updated = true;
+ if (mems_updated)
+ attach_ctx.mems_updated = true;
+
+ /*
+ * Skip rights over task setsched check in v2 when nothing changes for
+ * the current oldcs/cs pair, migration permission derives from
+ * hierarchy ownership in cgroup_procs_write_permission()).
+ */
+ *psetsched = !cpuset_v2() || cpus_updated || mems_updated;
+
+ /*
+ * A v1 cpuset with tasks will have no CPU left only when CPU hotplug
+ * brings the last online CPU offline as users are not allowed to empty
+ * cpuset.cpus when there are active tasks inside. When that happens,
+ * we should allow tasks to migrate out without security check to make
+ * sure they will be able to run after migration.
+ */
+ if (!is_in_v2_mode() && cpumask_empty(oldcs->effective_cpus))
+ *psetsched = false;
+
+ return 0;
+}
+
+static int cpuset_reserve_dl_bw(void)
+{
+ struct cpuset *cs;
+ int cpu, ret;
+
+ llist_for_each_entry(cs, dst_cs_head.first, attach_node) {
+ if (!cs->sum_migrate_dl_bw)
+ continue;
+
+ cpu = cpumask_any_and(cpu_active_mask, cs->effective_cpus);
+ if (unlikely(cpu >= nr_cpu_ids))
+ return -EINVAL;
+
+ ret = dl_bw_alloc(cpu, cs->sum_migrate_dl_bw);
+ if (ret)
+ return ret;
+
+ cs->dl_bw_cpu = cpu;
+ }
return 0;
}
-static void reset_migrate_dl_data(struct cpuset *cs)
+/*
+ * Clear and optionally apply (@cancel is false) the attach related data in the
+ * source or destination cpuset.
+ */
+static void clear_attach_data(struct llist_head *head, bool cancel)
{
- cs->nr_migrate_dl_tasks = 0;
- cs->sum_migrate_dl_bw = 0;
- cs->dl_bw_cpu = -1;
+ struct cpuset *cs, *next;
+ struct llist_node *lnode = __llist_del_all(head);
+
+ llist_for_each_entry_safe(cs, next, lnode, attach_node) {
+ init_llist_node(&cs->attach_node);
+ if (cs->nr_migrate_dl_tasks) {
+ if (!cancel)
+ atomic_add(cs->nr_migrate_dl_tasks, &cs->nr_deadline_tasks);
+ else if (cs->dl_bw_cpu >= 0) /* && cancel */
+ dl_bw_free(cs->dl_bw_cpu, cs->sum_migrate_dl_bw);
+ cs->nr_migrate_dl_tasks = 0;
+ cs->sum_migrate_dl_bw = 0;
+ cs->dl_bw_cpu = -1;
+ }
+ }
}
/* Called by cgroups to determine if a cpuset is usable; cpuset_mutex held */
@@ -2998,44 +3179,66 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
struct cpuset *cs, *oldcs;
struct task_struct *task;
bool setsched_check;
- int cpu, ret;
-
- /* used later by cpuset_attach() */
- cpuset_attach_old_cs = task_cs(cgroup_taskset_first(tset, &css));
- oldcs = cpuset_attach_old_cs;
- cs = css_cs(css);
+ int ret;
+ cs = oldcs = NULL;
mutex_lock(&cpuset_mutex);
-
- /* Check to see if task is allowed in the cpuset */
- ret = cpuset_can_attach_check(cs);
- if (ret)
- goto out_unlock;
+ attach_ctx.old_cs = NULL; /* Used later in cpuset_attach_task() */
+ attach_ctx.cpus_updated = false;
+ attach_ctx.mems_updated = false;
+ attach_ctx.many_dest_cs = false;
/*
- * Skip rights over task setsched check in v2 when nothing changes,
- * migration permission derives from hierarchy ownership in
- * cgroup_procs_write_permission()).
+ * The attach_ctx.old_cs is used mainly by cpuset_migrate_mm() to get
+ * the old_mems_allowed value. There are two ways that many-to-one
+ * cpuset migration can happen:
+ * 1) A multithread application with threads in different cpusets is
+ * wholely migrated to a new cpuset.
+ * 2) Disabling v2 cpuset controller will move all the tasks in child
+ * cpusets to the parent cpuset.
+ *
+ * In the former case, it is the mm setting of the group leader that
+ * really matters. So attach_ctx.old_cs should track the oldcs of the
+ * group leader. It falls back to the oldcs of the first task if there
+ * is no group leader in the taskset. In the latter case, effective_mems
+ * of child cpusets must always be a subset of the parent. So no real
+ * page migration will be necessary no matter which child cpuset is
+ * selected as attach_ctx.old_cs.
+ *
+ * For a v2 threaded subtree where cpuset isn't enabled in some of the
+ * cgroups, it is possible that oldcs == cs for some of the tasks.
+ * In this case, we can skip checking on those tasks as there is no
+ * actual migration wrt cpuset.
*/
- setsched_check = !cpuset_v2() ||
- !cpumask_equal(cs->effective_cpus, oldcs->effective_cpus) ||
- !nodes_equal(cs->effective_mems, oldcs->effective_mems);
+ cgroup_taskset_for_each(task, css, tset) {
+ struct cpuset *new_cs = css_cs(css);
+ struct cpuset *new_oldcs = task_cs(task);
+
+ if ((new_oldcs != oldcs) || (new_cs != cs)) {
+ if (cs && (new_cs != cs))
+ attach_ctx.many_dest_cs = true;
+ cs = new_cs;
+ oldcs = new_oldcs;
+ if (oldcs == cs)
+ continue;
+ if (!attach_ctx.old_cs)
+ attach_ctx.old_cs = oldcs;
+ ret = cpuset_can_attach_check(cs, oldcs, &setsched_check);
+ if (ret)
+ goto out_unlock;
+ }
- /*
- * A v1 cpuset with tasks will have no CPU left only when CPU hotplug
- * brings the last online CPU offline as users are not allowed to empty
- * cpuset.cpus when there are active tasks inside. When that happens,
- * we should allow tasks to migrate out without security check to make
- * sure they will be able to run after migration.
- */
- if (!is_in_v2_mode() && cpumask_empty(oldcs->effective_cpus))
- setsched_check = false;
+ if (oldcs == cs)
+ continue;
- cgroup_taskset_for_each(task, css, tset) {
ret = task_can_attach(task);
if (ret)
goto out_unlock;
+ /* Update attach_ctx.old_cs to the latest group leader */
+ if (task == task->group_leader)
+ attach_ctx.old_cs = task_cs(task);
+
if (setsched_check) {
ret = security_task_setscheduler(task);
if (ret)
@@ -3049,57 +3252,48 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
* contribute to sum_migrate_dl_bw.
*/
cs->nr_migrate_dl_tasks++;
+ oldcs->nr_migrate_dl_tasks--;
if (dl_task_needs_bw_move(task, cs->effective_cpus))
cs->sum_migrate_dl_bw += task->dl.dl_bw;
}
}
- if (!cs->sum_migrate_dl_bw)
- goto out_success;
-
- cpu = cpumask_any_and(cpu_active_mask, cs->effective_cpus);
- if (unlikely(cpu >= nr_cpu_ids)) {
+ /*
+ * The only case where there are multiple destination cpusets for
+ * task migration is when enabling a v2 cpuset controllers where
+ * tasks will be migrated to multiple child cpusets from a parent
+ * cpuset with the same effective CPUs and memory nodes. IOW,
+ * both attach_cpus_updated and attach_mems_updated should be false.
+ * If not, it is a condition that the current code cannot handle.
+ * Print a warning and abort the attach operation as further code
+ * change may be needed.
+ */
+ if (WARN_ON_ONCE(attach_ctx.many_dest_cs && (!cpuset_v2() ||
+ attach_ctx.cpus_updated || attach_ctx.mems_updated))) {
ret = -EINVAL;
goto out_unlock;
}
- ret = dl_bw_alloc(cpu, cs->sum_migrate_dl_bw);
- if (ret)
- goto out_unlock;
-
- cs->dl_bw_cpu = cpu;
-
-out_success:
- /*
- * Mark attach is in progress. This makes validate_change() fail
- * changes which zero cpus/mems_allowed.
- */
- cs->attach_in_progress++;
+ ret = cpuset_reserve_dl_bw();
out_unlock:
- if (ret)
- reset_migrate_dl_data(cs);
+ if (ret) {
+ clear_attach_data(&src_cs_head, true);
+ clear_attach_data(&dst_cs_head, true);
+ } else {
+ attach_ctx.in_progress++;
+ }
+
mutex_unlock(&cpuset_mutex);
return ret;
}
static void cpuset_cancel_attach(struct cgroup_taskset *tset)
{
- struct cgroup_subsys_state *css;
- struct cpuset *cs;
-
- cgroup_taskset_first(tset, &css);
- cs = css_cs(css);
-
mutex_lock(&cpuset_mutex);
- dec_attach_in_progress_locked(cs);
-
- 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);
-
+ dec_attach_in_progress_locked();
+ clear_attach_data(&src_cs_head, true);
+ clear_attach_data(&dst_cs_head, true);
mutex_unlock(&cpuset_mutex);
}
@@ -3109,10 +3303,11 @@ static void cpuset_cancel_attach(struct cgroup_taskset *tset)
* allocate from cpuset_init().
*/
static cpumask_var_t cpus_attach;
-static nodemask_t cpuset_attach_nodemask_to;
static void cpuset_attach_task(struct cpuset *cs, struct task_struct *task)
{
+ struct mm_struct *mm;
+
lockdep_assert_cpuset_lock_held();
if (cs != &top_cpuset)
@@ -3126,90 +3321,88 @@ static void cpuset_attach_task(struct cpuset *cs, struct task_struct *task)
*/
WARN_ON_ONCE(set_cpus_allowed_ptr(task, cpus_attach));
- cpuset_change_task_nodemask(task, &cpuset_attach_nodemask_to);
+ if (cpuset_v2() && !attach_ctx.mems_updated)
+ return;
+
+ cpuset_change_task_nodemask(task, &attach_ctx.nodemask_to);
cpuset1_update_task_spread_flags(cs, task);
+
+ if ((task != task->group_leader) || !attach_ctx.mems_updated)
+ return;
+
+ /*
+ * Change mm for threadgroup leader. This is expensive and may
+ * sleep and should be moved outside migration path proper.
+ */
+ mm = get_task_mm(task);
+ if (mm) {
+ struct cpuset *oldcs = attach_ctx.old_cs;
+
+ mpol_rebind_mm(mm, &cs->effective_mems);
+
+ /*
+ * old_mems_allowed is the same with mems_allowed
+ * here, except if this task is being moved
+ * automatically due to hotplug. In that case
+ * @mems_allowed has been updated and is empty, so
+ * @old_mems_allowed is the right nodesets that we
+ * migrate mm from.
+ */
+ if (is_memory_migrate(cs)) {
+ cpuset_migrate_mm(mm, &oldcs->old_mems_allowed,
+ &attach_ctx.nodemask_to);
+ attach_ctx.task_work_queued = true;
+ } else {
+ mmput(mm);
+ }
+ }
}
static void cpuset_attach(struct cgroup_taskset *tset)
{
struct task_struct *task;
- struct task_struct *leader;
struct cgroup_subsys_state *css;
struct cpuset *cs;
- struct cpuset *oldcs = cpuset_attach_old_cs;
- bool cpus_updated, mems_updated;
- bool queue_task_work = false;
cgroup_taskset_first(tset, &css);
cs = css_cs(css);
lockdep_assert_cpus_held(); /* see cgroup_attach_lock() */
mutex_lock(&cpuset_mutex);
- cpus_updated = !cpumask_equal(cs->effective_cpus,
- oldcs->effective_cpus);
- mems_updated = !nodes_equal(cs->effective_mems, oldcs->effective_mems);
+ attach_ctx.task_work_queued = false;
+ guarantee_online_mems(cs, &attach_ctx.nodemask_to);
/*
- * In the default hierarchy, enabling cpuset in the child cgroups
- * will trigger a number of cpuset_attach() calls with no change
- * in effective cpus and mems. In that case, we can optimize out
- * by skipping the task iteration and update.
+ * attach_ctx.old_cs can only be NULL if no task is actually migrating.
+ * This is highly unlikely. If it happens at all, we can skip task
+ * iteration and setting old_mems_allowed.
*/
- if (cpuset_v2() && !cpus_updated && !mems_updated) {
- cpuset_attach_nodemask_to = cs->effective_mems;
+ if (unlikely(!attach_ctx.old_cs))
goto out;
- }
-
- guarantee_online_mems(cs, &cpuset_attach_nodemask_to);
-
- cgroup_taskset_for_each(task, css, tset)
- cpuset_attach_task(cs, task);
/*
- * Change mm for all threadgroup leaders. This is expensive and may
- * sleep and should be moved outside migration path proper. Skip it
- * if there is no change in effective_mems and CS_MEMORY_MIGRATE is
- * not set.
+ * In the default hierarchy, enabling cpuset in the child cgroups
+ * will trigger a cpuset_attach() call with no change in effective cpus
+ * and mems. In that case, we can optimize out by skipping the task
+ * iteration and the destination cpuset list is iterated to set
+ * old_mems_allowed.
*/
- cpuset_attach_nodemask_to = cs->effective_mems;
- if (!is_memory_migrate(cs) && !mems_updated)
+ if (cpuset_v2() && !attach_ctx.cpus_updated && !attach_ctx.mems_updated) {
+ llist_for_each_entry(cs, dst_cs_head.first, attach_node)
+ cs->old_mems_allowed = attach_ctx.nodemask_to;
goto out;
-
- cgroup_taskset_for_each_leader(leader, css, tset) {
- struct mm_struct *mm = get_task_mm(leader);
-
- if (mm) {
- mpol_rebind_mm(mm, &cpuset_attach_nodemask_to);
-
- /*
- * old_mems_allowed is the same with mems_allowed
- * here, except if this task is being moved
- * automatically due to hotplug. In that case
- * @mems_allowed has been updated and is empty, so
- * @old_mems_allowed is the right nodesets that we
- * migrate mm from.
- */
- if (is_memory_migrate(cs)) {
- cpuset_migrate_mm(mm, &oldcs->old_mems_allowed,
- &cpuset_attach_nodemask_to);
- queue_task_work = true;
- } else
- mmput(mm);
- }
}
-out:
- if (queue_task_work)
- schedule_flush_migrate_mm();
- cs->old_mems_allowed = cpuset_attach_nodemask_to;
-
- if (cs->nr_migrate_dl_tasks) {
- cs->nr_deadline_tasks += cs->nr_migrate_dl_tasks;
- oldcs->nr_deadline_tasks -= cs->nr_migrate_dl_tasks;
- reset_migrate_dl_data(cs);
- }
+ cgroup_taskset_for_each(task, css, tset)
+ cpuset_attach_task(cs, task);
- dec_attach_in_progress_locked(cs);
+ if (attach_ctx.task_work_queued)
+ schedule_flush_migrate_mm();
+ cs->old_mems_allowed = attach_ctx.nodemask_to;
+out:
+ clear_attach_data(&src_cs_head, false);
+ clear_attach_data(&dst_cs_head, false);
+ dec_attach_in_progress_locked();
mutex_unlock(&cpuset_mutex);
}
@@ -3229,7 +3422,12 @@ ssize_t cpuset_write_resmask(struct kernfs_open_file *of,
return -EACCES;
buf = strstrip(buf);
- cpuset_full_lock();
+
+ /* cpuset_mutex acquired in wait_attach_done_lock() */
+ mutex_lock(&cpuset_top_mutex);
+ cpus_read_lock();
+ wait_attach_done_lock();
+
if (!is_cpuset_online(cs))
goto out_unlock;
@@ -3360,7 +3558,10 @@ static ssize_t cpuset_partition_write(struct kernfs_open_file *of, char *buf,
else
return -EINVAL;
- cpuset_full_lock();
+ mutex_lock(&cpuset_top_mutex);
+ cpus_read_lock();
+ wait_attach_done_lock();
+
if (is_cpuset_online(cs))
retval = update_prstate(cs, val);
cpuset_update_sd_hk_unlock();
@@ -3552,6 +3753,8 @@ static void cpuset_bind(struct cgroup_subsys_state *root_css)
mutex_lock(&cpuset_mutex);
spin_lock_irq(&callback_lock);
+ WRITE_ONCE(cpuset_v2_mode,
+ !!(cpuset_cgrp_subsys.root->flags & CGRP_ROOT_CPUSET_V2_MODE));
if (is_in_v2_mode()) {
cpumask_copy(top_cpuset.cpus_allowed, cpu_possible_mask);
cpumask_copy(top_cpuset.effective_xcpus, cpu_possible_mask);
@@ -3587,7 +3790,7 @@ static int cpuset_can_fork(struct task_struct *task, struct css_set *cset)
mutex_lock(&cpuset_mutex);
/* Check to see if task is allowed in the cpuset */
- ret = cpuset_can_attach_check(cs);
+ ret = cpuset_can_attach_check(cs, NULL, NULL);
if (ret)
goto out_unlock;
@@ -3599,11 +3802,7 @@ static int cpuset_can_fork(struct task_struct *task, struct css_set *cset)
if (ret)
goto out_unlock;
- /*
- * Mark attach is in progress. This makes validate_change() fail
- * changes which zero cpus/mems_allowed.
- */
- cs->attach_in_progress++;
+ attach_ctx.in_progress++;
out_unlock:
mutex_unlock(&cpuset_mutex);
return ret;
@@ -3621,7 +3820,7 @@ static void cpuset_cancel_fork(struct task_struct *task, struct css_set *cset)
if (same_cs)
return;
- dec_attach_in_progress(cs);
+ dec_attach_in_progress();
}
/*
@@ -3631,15 +3830,14 @@ static void cpuset_cancel_fork(struct task_struct *task, struct css_set *cset)
*/
static void cpuset_fork(struct task_struct *task)
{
- struct cpuset *cs;
- bool same_cs;
+ struct cpuset *cs, *oldcs;
rcu_read_lock();
cs = task_cs(task);
- same_cs = (cs == task_cs(current));
+ oldcs = task_cs(current);
rcu_read_unlock();
- if (same_cs) {
+ if (cs == oldcs) {
if (cs == &top_cpuset)
return;
@@ -3650,10 +3848,22 @@ static void cpuset_fork(struct task_struct *task)
/* CLONE_INTO_CGROUP */
mutex_lock(&cpuset_mutex);
- guarantee_online_mems(cs, &cpuset_attach_nodemask_to);
+ guarantee_online_mems(cs, &attach_ctx.nodemask_to);
+ cs->old_mems_allowed = attach_ctx.nodemask_to;
+
+ /*
+ * Assume CPUs and memory nodes are updated
+ * A CLONE_INTO_CGROUP operation should have taken the cgroup mutex
+ * and so there shouldn't be a competing cpuset_attach() operation.
+ */
+ attach_ctx.cpus_updated = attach_ctx.mems_updated = true;
+ attach_ctx.task_work_queued = false;
+ attach_ctx.old_cs = oldcs;
cpuset_attach_task(cs, task);
+ if (attach_ctx.task_work_queued)
+ schedule_flush_migrate_mm();
- dec_attach_in_progress_locked(cs);
+ dec_attach_in_progress_locked();
mutex_unlock(&cpuset_mutex);
}
@@ -3700,6 +3910,7 @@ int __init cpuset_init(void)
cpumask_setall(top_cpuset.effective_xcpus);
cpumask_setall(top_cpuset.exclusive_cpus);
nodes_setall(top_cpuset.effective_mems);
+ init_llist_node(&top_cpuset.attach_node);
cpuset1_init(&top_cpuset);
@@ -3757,23 +3968,11 @@ static void cpuset_hotplug_update_tasks(struct cpuset *cs, struct tmpmasks *tmp)
bool remote;
int partcmd = -1;
struct cpuset *parent;
-retry:
- wait_event(cpuset_attach_wq, cs->attach_in_progress == 0);
-
- mutex_lock(&cpuset_mutex);
-
- /*
- * We have raced with task attaching. We wait until attaching
- * is finished, so we won't attach a task to an empty cpuset.
- */
- if (cs->attach_in_progress) {
- mutex_unlock(&cpuset_mutex);
- goto retry;
- }
+ wait_attach_done_lock();
parent = parent_cs(cs);
compute_effective_cpumask(&new_cpus, cs, parent);
- nodes_and(new_mems, cs->mems_allowed, parent->effective_mems);
+ compute_effective_nodemask(&new_mems, cs, parent);
if (!tmp || !cs->partition_root_state)
goto update_tasks;
@@ -3789,7 +3988,7 @@ retry:
if (remote && (cpumask_empty(subpartitions_cpus) ||
(cpumask_empty(&new_cpus) &&
partition_is_populated(cs, NULL)))) {
- cs->prs_err = PERR_HOTPLUG;
+ WRITE_ONCE(cs->prs_err, PERR_HOTPLUG);
remote_partition_disable(cs, tmp);
compute_effective_cpumask(&new_cpus, cs, parent);
remote = false;
@@ -3868,15 +4067,16 @@ static void cpuset_handle_hotplug(void)
static cpumask_t new_cpus;
static nodemask_t new_mems;
bool cpus_updated, mems_updated;
- bool on_dfl = is_in_v2_mode();
+ bool on_dfl;
struct tmpmasks tmp, *ptmp = NULL;
- if (on_dfl && !alloc_tmpmasks(&tmp))
- ptmp = &tmp;
-
lockdep_assert_cpus_held();
mutex_lock(&cpuset_mutex);
+ on_dfl = is_in_v2_mode();
+ if (on_dfl && !alloc_tmpmasks(&tmp))
+ ptmp = &tmp;
+
/* fetch the available cpus/mems and find out which changed how */
cpumask_copy(&new_cpus, cpu_active_mask);
new_mems = node_states[N_MEMORY];
@@ -4124,6 +4324,28 @@ 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);
@@ -4157,7 +4379,7 @@ nodemask_t cpuset_mems_allowed(struct task_struct *tsk)
*
* Are any of the nodes in the nodemask allowed in current->mems_allowed?
*/
-int cpuset_nodemask_valid_mems_allowed(nodemask_t *nodemask)
+int cpuset_nodemask_valid_mems_allowed(const nodemask_t *nodemask)
{
return nodes_intersects(*nodemask, current->mems_allowed);
}
@@ -4193,7 +4415,7 @@ static struct cpuset *nearest_hardwall_ancestor(struct cpuset *cs)
* nearest enclosing hardwalled ancestor cpuset.
*
* Scanning up parent cpusets requires callback_lock. The
- * __alloc_pages() routine only calls here with __GFP_HARDWALL bit
+ * page allocator only calls here with __GFP_HARDWALL bit
* _not_ set if it's a GFP_KERNEL allocation, and all nodes in the
* current tasks mems_allowed came up empty on the first pass over
* the zonelist. So only GFP_KERNEL allocations, if all nodes in the
@@ -4206,11 +4428,8 @@ static struct cpuset *nearest_hardwall_ancestor(struct cpuset *cs)
* come before the __GFP_HARDWALL check, otherwise a dying task
* would be blocked on the fast path.
*
- * The second pass through get_page_from_freelist() doesn't even call
- * here for GFP_ATOMIC calls. For those calls, the __alloc_pages()
- * variable 'wait' is not set, and the bit ALLOC_CPUSET is not set
- * in alloc_flags. That logic and the checks below have the combined
- * affect that:
+ * The second pass through get_page_from_freelist() doesn't even call here for
+ * GFP_ATOMIC calls. That, and the checks below have the combined affect that:
* in_interrupt - any node ok (current task context irrelevant)
* GFP_ATOMIC - any node ok
* tsk_is_oom_victim - any node ok
@@ -4310,14 +4529,10 @@ void cpuset_nodes_allowed(struct cgroup *cgroup, nodemask_t *mask)
* cpuset_spread_node() - On which node to begin search for a page
* @rotor: round robin rotor
*
- * If a task is marked PF_SPREAD_PAGE or PF_SPREAD_SLAB (as for
- * tasks in a cpuset with is_spread_page or is_spread_slab set),
- * and if the memory allocation used cpuset_mem_spread_node()
- * to determine on which node to start looking, as it will for
- * certain page cache or slab cache pages such as used for file
- * system buffers and inode caches, then instead of starting on the
- * local node to look for a free page, rather spread the starting
- * node around the tasks mems_allowed nodes.
+ * If a task is marked PFA_SPREAD_PAGE and a page cache allocation uses
+ * cpuset_mem_spread_node() to determine where to start looking, spread the
+ * starting node around the task's mems_allowed nodes instead of starting on
+ * the local node.
*
* We don't have to worry about the returned node being offline
* because "it can't happen", and even if it did, it would be ok.
@@ -4327,8 +4542,8 @@ void cpuset_nodes_allowed(struct cgroup *cgroup, nodemask_t *mask)
* should not be possible for the following code to return an
* offline node. But if it did, that would be ok, as this routine
* is not returning the node where the allocation must be, only
- * the node where the search should start. The zonelist passed to
- * __alloc_pages() will include all nodes. If the slab allocator
+ * the node where the search should start. The zonelist used by
+ * the allocator will include all nodes. If the slab allocator
* is passed an offline node, it will fall back to the local node.
* See kmem_cache_alloc_node().
*/
diff --git a/kernel/cgroup/dmem.c b/kernel/cgroup/dmem.c
index 4753a67d0f0f..4683f3d68022 100644
--- a/kernel/cgroup/dmem.c
+++ b/kernel/cgroup/dmem.c
@@ -17,6 +17,13 @@
#include <linux/refcount.h>
#include <linux/rculist.h>
#include <linux/slab.h>
+#include <linux/srcu.h>
+
+/* Maximum reclaim attempts before giving up when lowering dmem.max. */
+#define DMEM_MAX_RECLAIM_RETRIES 16
+
+/* SRCU domain serialising reclaim callbacks against region unregistration. */
+DEFINE_STATIC_SRCU(dmemcg_srcu);
struct dmem_cgroup_region {
/**
@@ -48,9 +55,18 @@ struct dmem_cgroup_region {
/**
* @unregistered: Whether the region is unregistered by its caller.
- * No new pools should be added to the region afterwards.
+ * No new pools should be added to the region afterwards, and no new
+ * reclaim callbacks should be invoked.
*/
bool unregistered;
+
+ /**
+ * @ops: Optional driver operations for this region.
+ */
+ const struct dmem_cgroup_ops *ops;
+
+ /** @reclaim_priv: Private data passed to @ops->reclaim. */
+ void *reclaim_priv;
};
struct dmemcg_state {
@@ -145,21 +161,52 @@ static void free_cg_pool(struct dmem_cgroup_pool_state *pool)
}
static void
-set_resource_min(struct dmem_cgroup_pool_state *pool, u64 val)
+set_resource_min(struct dmem_cgroup_pool_state *pool, u64 val, bool nonblock)
{
page_counter_set_min(&pool->cnt, val);
}
static void
-set_resource_low(struct dmem_cgroup_pool_state *pool, u64 val)
+set_resource_low(struct dmem_cgroup_pool_state *pool, u64 val, bool nonblock)
{
page_counter_set_low(&pool->cnt, val);
}
static void
-set_resource_max(struct dmem_cgroup_pool_state *pool, u64 val)
+set_resource_max(struct dmem_cgroup_pool_state *pool, u64 val, bool nonblock)
{
- page_counter_set_max(&pool->cnt, val);
+ struct dmem_cgroup_region *region = pool->region;
+ unsigned long limit = (unsigned long)val;
+
+ /* Apply the new limit immediately so concurrent allocations are throttled. */
+ xchg(&pool->cnt.max, limit);
+
+ if (nonblock)
+ return;
+
+ int srcu_idx = srcu_read_lock(&dmemcg_srcu);
+
+ if (!READ_ONCE(region->unregistered) && region->ops && region->ops->reclaim) {
+ for (int retries = DMEM_MAX_RECLAIM_RETRIES; ; ) {
+ u64 usage = page_counter_read(&pool->cnt);
+ int ret;
+
+ if (usage <= limit)
+ break;
+
+ if (signal_pending(current))
+ break;
+
+ ret = region->ops->reclaim(pool, usage - limit, region->reclaim_priv);
+
+ /* -ENOSPC means no progress; other errors are fatal. */
+ if (ret && (ret != -ENOSPC || !retries--))
+ break;
+
+ cond_resched();
+ }
+ }
+ srcu_read_unlock(&dmemcg_srcu, srcu_idx);
}
static u64 get_resource_low(struct dmem_cgroup_pool_state *pool)
@@ -182,11 +229,17 @@ static u64 get_resource_current(struct dmem_cgroup_pool_state *pool)
return pool ? page_counter_read(&pool->cnt) : 0;
}
+static u64 get_resource_peak(struct dmem_cgroup_pool_state *pool)
+{
+ return pool ? READ_ONCE(pool->cnt.watermark) : 0;
+}
+
static void reset_all_resource_limits(struct dmem_cgroup_pool_state *rpool)
{
- set_resource_min(rpool, 0);
- set_resource_low(rpool, 0);
- set_resource_max(rpool, PAGE_COUNTER_MAX);
+ set_resource_min(rpool, 0, false);
+ set_resource_low(rpool, 0, false);
+ /* nonblock: raising to max makes reclaim a no-op; sleeping is forbidden here. */
+ set_resource_max(rpool, PAGE_COUNTER_MAX, true);
}
static void dmemcs_offline(struct cgroup_subsys_state *css)
@@ -463,7 +516,10 @@ static void dmemcg_free_region(struct kref *ref)
* dmem_cgroup_unregister_region() - Unregister a previously registered region.
* @region: The region to unregister.
*
- * This function undoes dmem_cgroup_register_region.
+ * This function undoes dmem_cgroup_register_region. It drains any
+ * in-flight reclaim callbacks before returning, so the caller may safely
+ * free the resources pointed to by the @reclaim_priv that was passed at
+ * registration time.
*/
void dmem_cgroup_unregister_region(struct dmem_cgroup_region *region)
{
@@ -488,16 +544,18 @@ void dmem_cgroup_unregister_region(struct dmem_cgroup_region *region)
* no new pools should be added to the dead region
* by get_cg_pool_unlocked.
*/
- region->unregistered = true;
+ WRITE_ONCE(region->unregistered, true);
spin_unlock(&dmemcg_lock);
+ synchronize_srcu(&dmemcg_srcu);
+
kref_put(&region->ref, dmemcg_free_region);
}
EXPORT_SYMBOL_GPL(dmem_cgroup_unregister_region);
/**
* dmem_cgroup_register_region() - Register a regions for dev cgroup.
- * @size: Size of region to register, in bytes.
+ * @init: Initialization parameters for the region.
* @fmt: Region parameters to register
*
* This function registers a node in the dmem cgroup with the
@@ -506,13 +564,15 @@ EXPORT_SYMBOL_GPL(dmem_cgroup_unregister_region);
*
* Return: NULL or a struct on success, PTR_ERR on failure.
*/
-struct dmem_cgroup_region *dmem_cgroup_register_region(u64 size, const char *fmt, ...)
+struct dmem_cgroup_region *
+dmem_cgroup_register_region(const struct dmem_cgroup_init *init,
+ const char *fmt, ...)
{
struct dmem_cgroup_region *ret;
char *region_name;
va_list ap;
- if (!size)
+ if (!init || !init->size)
return NULL;
va_start(ap, fmt);
@@ -529,7 +589,9 @@ struct dmem_cgroup_region *dmem_cgroup_register_region(u64 size, const char *fmt
INIT_LIST_HEAD(&ret->pools);
ret->name = region_name;
- ret->size = size;
+ ret->size = init->size;
+ ret->ops = init->ops;
+ ret->reclaim_priv = init->reclaim_priv;
kref_init(&ret->ref);
spin_lock(&dmemcg_lock);
@@ -695,6 +757,110 @@ err:
}
EXPORT_SYMBOL_GPL(dmem_cgroup_try_charge);
+/**
+ * dmem_cgroup_below_min() - Tests whether current usage is within min limit.
+ *
+ * @root: Root of the subtree to calculate protection for, or NULL to calculate global protection.
+ * @test: The pool to test the usage/min limit of.
+ *
+ * Return: true if usage is below min and the cgroup is protected, false otherwise.
+ */
+bool dmem_cgroup_below_min(struct dmem_cgroup_pool_state *root,
+ struct dmem_cgroup_pool_state *test)
+{
+ if (root == test || !pool_parent(test))
+ return false;
+
+ if (!root) {
+ for (root = test; pool_parent(root); root = pool_parent(root))
+ {}
+ }
+
+ /*
+ * In mem_cgroup_below_min(), the memcg pendant, this call is missing.
+ * mem_cgroup_below_min() gets called during traversal of the cgroup tree, where
+ * protection is already calculated as part of the traversal. dmem cgroup eviction
+ * does not traverse the cgroup tree, so we need to recalculate effective protection
+ * here.
+ */
+ dmem_cgroup_calculate_protection(root, test);
+ return page_counter_read(&test->cnt) <= READ_ONCE(test->cnt.emin);
+}
+EXPORT_SYMBOL_GPL(dmem_cgroup_below_min);
+
+/**
+ * dmem_cgroup_below_low() - Tests whether current usage is within low limit.
+ *
+ * @root: Root of the subtree to calculate protection for, or NULL to calculate global protection.
+ * @test: The pool to test the usage/low limit of.
+ *
+ * Return: true if usage is below low and the cgroup is protected, false otherwise.
+ */
+bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root,
+ struct dmem_cgroup_pool_state *test)
+{
+ if (root == test || !pool_parent(test))
+ return false;
+
+ if (!root) {
+ for (root = test; pool_parent(root); root = pool_parent(root))
+ {}
+ }
+
+ /*
+ * In mem_cgroup_below_low(), the memcg pendant, this call is missing.
+ * mem_cgroup_below_low() gets called during traversal of the cgroup tree, where
+ * protection is already calculated as part of the traversal. dmem cgroup eviction
+ * does not traverse the cgroup tree, so we need to recalculate effective protection
+ * here.
+ */
+ dmem_cgroup_calculate_protection(root, test);
+ return page_counter_read(&test->cnt) <= READ_ONCE(test->cnt.elow);
+}
+EXPORT_SYMBOL_GPL(dmem_cgroup_below_low);
+
+/**
+ * dmem_cgroup_get_common_ancestor(): Find the first common ancestor of two pools.
+ * @a: First pool to find the common ancestor of.
+ * @b: First pool to find the common ancestor of.
+ *
+ * Return: The first pool that is a parent of both @a and @b, or NULL if either @a or @b are NULL,
+ * or if such a pool does not exist. A reference to the returned pool is grabbed and must be
+ * released by the caller when it is done using the pool.
+ */
+struct dmem_cgroup_pool_state *dmem_cgroup_get_common_ancestor(struct dmem_cgroup_pool_state *a,
+ struct dmem_cgroup_pool_state *b)
+{
+ struct cgroup *ancestor_cgroup;
+ struct cgroup_subsys_state *ancestor_css;
+ struct dmemcg_state *ancestor_dmemcs = NULL;
+ struct dmem_cgroup_pool_state *pool = NULL;
+
+ if (!a || !b)
+ return NULL;
+
+ ancestor_cgroup = cgroup_common_ancestor(a->cs->css.cgroup, b->cs->css.cgroup);
+ if (!ancestor_cgroup)
+ return NULL;
+
+ rcu_read_lock();
+ ancestor_css = cgroup_e_css(ancestor_cgroup, &dmem_cgrp_subsys);
+ if (css_tryget(ancestor_css))
+ ancestor_dmemcs = css_to_dmemcs(ancestor_css);
+ rcu_read_unlock();
+
+ if (ancestor_dmemcs) {
+ pool = get_cg_pool_unlocked(css_to_dmemcs(ancestor_css),
+ a->region);
+ if (WARN_ON(IS_ERR(pool))) {
+ pool = NULL;
+ css_put(ancestor_css);
+ }
+ }
+ return pool;
+}
+EXPORT_SYMBOL_GPL(dmem_cgroup_get_common_ancestor);
+
static int dmem_cgroup_region_capacity_show(struct seq_file *sf, void *v)
{
struct dmem_cgroup_region *region;
@@ -726,60 +892,42 @@ static int dmemcg_parse_limit(char *options, u64 *new_limit)
static ssize_t dmemcg_limit_write(struct kernfs_open_file *of,
char *buf, size_t nbytes, loff_t off,
- void (*apply)(struct dmem_cgroup_pool_state *, u64))
+ void (*apply)(struct dmem_cgroup_pool_state *, u64, bool))
{
struct dmemcg_state *dmemcs = css_to_dmemcs(of_css(of));
- int err = 0;
-
- while (buf && !err) {
- struct dmem_cgroup_pool_state *pool = NULL;
- char *options, *region_name;
- struct dmem_cgroup_region *region;
- u64 new_limit;
-
- options = buf;
- buf = strchr(buf, '\n');
- if (buf)
- *buf++ = '\0';
-
- options = strstrip(options);
-
- /* eat empty lines */
- if (!options[0])
- continue;
-
- region_name = strsep(&options, " \t");
- if (!region_name[0])
- continue;
-
- if (!options || !*options)
- return -EINVAL;
+ struct dmem_cgroup_pool_state *pool;
+ struct dmem_cgroup_region *region;
+ bool nonblock = of->file->f_flags & O_NONBLOCK;
+ char *region_name;
+ u64 new_limit;
+ int err;
- rcu_read_lock();
- region = dmemcg_get_region_by_name(region_name);
- rcu_read_unlock();
+ buf = strstrip(buf);
+ region_name = strsep(&buf, " \t");
+ if (!buf || !region_name[0])
+ return -EINVAL;
- if (!region)
- return -EINVAL;
+ rcu_read_lock();
+ region = dmemcg_get_region_by_name(region_name);
+ rcu_read_unlock();
+ if (!region)
+ return -EINVAL;
- err = dmemcg_parse_limit(options, &new_limit);
- if (err < 0)
- goto out_put;
+ err = dmemcg_parse_limit(buf, &new_limit);
+ if (err < 0)
+ goto out_put;
- pool = get_cg_pool_unlocked(dmemcs, region);
- if (IS_ERR(pool)) {
- err = PTR_ERR(pool);
- goto out_put;
- }
+ pool = get_cg_pool_unlocked(dmemcs, region);
+ if (IS_ERR(pool)) {
+ err = PTR_ERR(pool);
+ goto out_put;
+ }
- /* And commit */
- apply(pool, new_limit);
- dmemcg_pool_put(pool);
+ apply(pool, new_limit, nonblock);
+ dmemcg_pool_put(pool);
out_put:
- kref_put(&region->ref, dmemcg_free_region);
- }
-
+ kref_put(&region->ref, dmemcg_free_region);
return err ?: nbytes;
}
@@ -808,6 +956,11 @@ static int dmemcg_limit_show(struct seq_file *sf, void *v,
return 0;
}
+static int dmem_cgroup_region_peak_show(struct seq_file *sf, void *v)
+{
+ return dmemcg_limit_show(sf, v, get_resource_peak);
+}
+
static int dmem_cgroup_region_current_show(struct seq_file *sf, void *v)
{
return dmemcg_limit_show(sf, v, get_resource_current);
@@ -857,6 +1010,11 @@ static struct cftype files[] = {
.seq_show = dmem_cgroup_region_current_show,
},
{
+ .name = "peak",
+ .seq_show = dmem_cgroup_region_peak_show,
+ .flags = CFTYPE_NOT_ON_ROOT,
+ },
+ {
.name = "min",
.write = dmem_cgroup_region_min_write,
.seq_show = dmem_cgroup_region_min_show,
diff --git a/kernel/cgroup/pids.c b/kernel/cgroup/pids.c
index ecbb839d2acb..78cdc0558d0c 100644
--- a/kernel/cgroup/pids.c
+++ b/kernel/cgroup/pids.c
@@ -253,6 +253,11 @@ static void pids_event(struct pids_cgroup *pids_forking,
}
if (!cgroup_subsys_on_dfl(pids_cgrp_subsys) ||
cgrp_dfl_root.flags & CGRP_ROOT_PIDS_LOCAL_EVENTS) {
+ /*
+ * pids.events reports the local counter on legacy hierarchies
+ * and when pids_localevents is enabled.
+ */
+ cgroup_file_notify(&p->events_file);
cgroup_file_notify(&p->events_local_file);
return;
}
diff --git a/kernel/cgroup/rdma.c b/kernel/cgroup/rdma.c
index 5e82a03b3270..9489f3df0bf3 100644
--- a/kernel/cgroup/rdma.c
+++ b/kernel/cgroup/rdma.c
@@ -19,6 +19,7 @@
#define RDMACG_MAX_STR "max"
enum rdmacg_limit_tokens {
+ RDMACG_DEVICE_INDEX,
RDMACG_HCA_HANDLE_VAL,
RDMACG_HCA_HANDLE_MAX,
RDMACG_HCA_OBJECT_VAL,
@@ -27,6 +28,7 @@ enum rdmacg_limit_tokens {
};
static const match_table_t rdmacg_limit_tokens = {
+ { RDMACG_DEVICE_INDEX, "index=%u" },
{ RDMACG_HCA_HANDLE_VAL, "hca_handle=%d" },
{ RDMACG_HCA_HANDLE_MAX, "hca_handle=max" },
{ RDMACG_HCA_OBJECT_VAL, "hca_object=%d" },
@@ -464,17 +466,53 @@ void rdmacg_unregister_device(struct rdmacg_device *device)
}
EXPORT_SYMBOL(rdmacg_unregister_device);
-static struct rdmacg_device *rdmacg_get_device_locked(const char *name)
+static struct rdmacg_device *
+rdmacg_get_device_locked(const char *name, bool has_index, u32 index)
{
+ struct rdmacg_device *match = NULL;
struct rdmacg_device *device;
lockdep_assert_held(&rdmacg_mutex);
- list_for_each_entry(device, &rdmacg_devices, dev_node)
- if (!strcmp(name, device->name))
- return device;
+ list_for_each_entry(device, &rdmacg_devices, dev_node) {
+ if (strcmp(name, device->name))
+ continue;
- return NULL;
+ if (has_index) {
+ if (device->index == index)
+ return device;
+ continue;
+ }
+
+ if (match)
+ return ERR_PTR(-ENOTUNIQ);
+ match = device;
+ }
+
+ return match ?: ERR_PTR(-ENODEV);
+}
+
+static bool
+rdmacg_device_name_unique_locked(const struct rdmacg_device *device)
+{
+ struct rdmacg_device *other;
+
+ lockdep_assert_held(&rdmacg_mutex);
+
+ list_for_each_entry(other, &rdmacg_devices, dev_node)
+ if (other != device && !strcmp(other->name, device->name))
+ return false;
+
+ return true;
+}
+
+static void rdmacg_print_device_key(struct seq_file *sf,
+ const struct rdmacg_device *device)
+{
+ seq_puts(sf, device->name);
+ if (!rdmacg_device_name_unique_locked(device))
+ seq_printf(sf, " index=%u", device->index);
+ seq_putc(sf, ' ');
}
static ssize_t rdmacg_resource_set_max(struct kernfs_open_file *of,
@@ -488,6 +526,8 @@ static ssize_t rdmacg_resource_set_max(struct kernfs_open_file *of,
char *p;
int *new_limits;
unsigned long enables = 0;
+ u32 dev_index = 0;
+ bool has_index = false;
int i = 0, ret = 0;
/* extract the device name first */
@@ -503,7 +543,7 @@ static ssize_t rdmacg_resource_set_max(struct kernfs_open_file *of,
goto err;
}
- /* parse resource limit tokens */
+ /* parse the optional device index and resource limit tokens */
while ((p = strsep(&options, " \t\n"))) {
substring_t args[MAX_OPT_ARGS];
int tok, intval;
@@ -513,6 +553,13 @@ static ssize_t rdmacg_resource_set_max(struct kernfs_open_file *of,
tok = match_token(p, rdmacg_limit_tokens, args);
switch (tok) {
+ case RDMACG_DEVICE_INDEX:
+ if (has_index || match_uint(&args[0], &dev_index)) {
+ ret = -EINVAL;
+ goto parse_err;
+ }
+ has_index = true;
+ break;
case RDMACG_HCA_HANDLE_VAL:
if (match_int(&args[0], &intval) || intval < 0) {
ret = -EINVAL;
@@ -546,9 +593,9 @@ static ssize_t rdmacg_resource_set_max(struct kernfs_open_file *of,
/* acquire lock to synchronize with hot plug devices */
mutex_lock(&rdmacg_mutex);
- device = rdmacg_get_device_locked(dev_name);
- if (!device) {
- ret = -ENODEV;
+ device = rdmacg_get_device_locked(dev_name, has_index, dev_index);
+ if (IS_ERR(device)) {
+ ret = PTR_ERR(device);
goto dev_err;
}
@@ -626,7 +673,7 @@ static int rdmacg_resource_read(struct seq_file *sf, void *v)
mutex_lock(&rdmacg_mutex);
list_for_each_entry(device, &rdmacg_devices, dev_node) {
- seq_printf(sf, "%s ", device->name);
+ rdmacg_print_device_key(sf, device);
rpool = find_cg_rpool_locked(cg, device);
print_rpool_values(sf, rpool);
@@ -650,7 +697,7 @@ static int rdmacg_events_show(struct seq_file *sf, void *v)
list_for_each_entry(device, &rdmacg_devices, dev_node) {
rpool = find_cg_rpool_locked(cg, device);
- seq_printf(sf, "%s ", device->name);
+ rdmacg_print_device_key(sf, device);
for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
seq_printf(sf, "%s.max=%llu %s.alloc_fail=%llu",
rdmacg_resource_names[i],
@@ -679,7 +726,7 @@ static int rdmacg_events_local_show(struct seq_file *sf, void *v)
list_for_each_entry(device, &rdmacg_devices, dev_node) {
rpool = find_cg_rpool_locked(cg, device);
- seq_printf(sf, "%s ", device->name);
+ rdmacg_print_device_key(sf, device);
for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
seq_printf(sf, "%s.max=%llu %s.alloc_fail=%llu",
rdmacg_resource_names[i],
diff --git a/kernel/crash_core.c b/kernel/crash_core.c
index 4f21fc3b108b..2b36aa9fade0 100644
--- a/kernel/crash_core.c
+++ b/kernel/crash_core.c
@@ -168,9 +168,6 @@ static inline resource_size_t crash_resource_size(const struct resource *res)
return !res->end ? 0 : resource_size(res);
}
-
-
-
int crash_prepare_elf64_headers(struct crash_mem *mem, int need_kernel_map,
void **addr, unsigned long *sz)
{
@@ -272,6 +269,92 @@ int crash_prepare_elf64_headers(struct crash_mem *mem, int need_kernel_map,
return 0;
}
+static struct crash_mem *alloc_cmem(unsigned int nr_ranges)
+{
+ struct crash_mem *cmem;
+
+ cmem = kvzalloc_flex(*cmem, ranges, nr_ranges);
+ if (!cmem)
+ return NULL;
+
+ cmem->max_nr_ranges = nr_ranges;
+ return cmem;
+}
+
+unsigned int __weak arch_get_system_nr_ranges(void) { return 0; }
+int __weak arch_crash_populate_cmem(struct crash_mem *cmem) { return -1; }
+int __weak arch_crash_exclude_ranges(struct crash_mem *cmem) { return 0; }
+
+int __weak arch_crash_exclude_mem_range(struct crash_mem **mem,
+ unsigned long long mstart,
+ unsigned long long mend)
+{
+ return crash_exclude_mem_range(*mem, mstart, mend);
+}
+
+int crash_exclude_core_ranges(struct crash_mem **cmem)
+{
+ int ret, i;
+
+ /* Exclude crashkernel region */
+ ret = arch_crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
+ if (ret)
+ return ret;
+
+ if (crashk_low_res.end) {
+ ret = arch_crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0; i < crashk_cma_cnt; ++i) {
+ ret = arch_crash_exclude_mem_range(cmem, crashk_cma_ranges[i].start,
+ crashk_cma_ranges[i].end);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int crash_prepare_headers(int need_kernel_map, void **addr, unsigned long *sz,
+ unsigned long *nr_mem_ranges)
+{
+ unsigned int max_nr_ranges;
+ struct crash_mem *cmem;
+ int ret;
+
+ max_nr_ranges = arch_get_system_nr_ranges();
+ if (!max_nr_ranges)
+ return -ENOMEM;
+
+ cmem = alloc_cmem(max_nr_ranges);
+ if (!cmem)
+ return -ENOMEM;
+
+ ret = arch_crash_populate_cmem(cmem);
+ if (ret)
+ goto out;
+
+ ret = crash_exclude_core_ranges(&cmem);
+ if (ret)
+ goto out;
+
+ ret = arch_crash_exclude_ranges(cmem);
+ if (ret)
+ goto out;
+
+ /* Return the computed number of memory ranges, for hotplug usage */
+ if (nr_mem_ranges)
+ *nr_mem_ranges = cmem->nr_ranges;
+
+ ret = crash_prepare_elf64_headers(cmem, need_kernel_map, addr, sz);
+
+out:
+ kvfree(cmem);
+ return ret;
+}
+
/**
* crash_exclude_mem_range - exclude a mem range for existing ranges
* @mem: mem->range contains an array of ranges sorted in ascending order
diff --git a/kernel/crash_dump_dm_crypt.c b/kernel/crash_dump_dm_crypt.c
index cb875ddb6ba6..c685497cd470 100644
--- a/kernel/crash_dump_dm_crypt.c
+++ b/kernel/crash_dump_dm_crypt.c
@@ -81,7 +81,6 @@ static int add_key_to_keyring(struct dm_crypt_key *dm_key,
kexec_dprintk("Error when adding key");
}
- key_ref_put(keyring_ref);
return r;
}
@@ -104,6 +103,7 @@ static int restore_dm_crypt_keys_to_thread_keyring(void)
struct dm_crypt_key *key;
size_t keys_header_size;
key_ref_t keyring_ref;
+ int ret = 0;
u64 addr;
/* find the target keyring (which must be writable) */
@@ -118,7 +118,8 @@ static int restore_dm_crypt_keys_to_thread_keyring(void)
dm_crypt_keys_read((char *)&key_count, sizeof(key_count), &addr);
if (key_count > KEY_NUM_MAX) {
kexec_dprintk("Failed to read the number of dm-crypt keys\n");
- return -1;
+ ret = -1;
+ goto out;
}
kexec_dprintk("There are %u keys\n", key_count);
@@ -126,8 +127,10 @@ static int restore_dm_crypt_keys_to_thread_keyring(void)
keys_header_size = get_keys_header_size(key_count);
keys_header = kzalloc(keys_header_size, GFP_KERNEL);
- if (!keys_header)
- return -ENOMEM;
+ if (!keys_header) {
+ ret = -ENOMEM;
+ goto out;
+ }
dm_crypt_keys_read((char *)keys_header, keys_header_size, &addr);
@@ -137,7 +140,9 @@ static int restore_dm_crypt_keys_to_thread_keyring(void)
add_key_to_keyring(key, keyring_ref);
}
- return 0;
+out:
+ key_ref_put(keyring_ref);
+ return ret;
}
static int read_key_from_user_keyring(struct dm_crypt_key *dm_key)
diff --git a/kernel/delayacct.c b/kernel/delayacct.c
index 2e55c493c98b..479e860aff70 100644
--- a/kernel/delayacct.c
+++ b/kernel/delayacct.c
@@ -54,7 +54,7 @@ void delayacct_init(void)
set_delayacct(delayacct_on);
}
-#ifdef CONFIG_PROC_SYSCTL
+#ifdef CONFIG_SYSCTL
static int sysctl_delayacct(const struct ctl_table *table, int write, void *buffer,
size_t *lenp, loff_t *ppos)
{
diff --git a/kernel/dma/Kconfig b/kernel/dma/Kconfig
index 0a4ba21a57a7..3830a63ae032 100644
--- a/kernel/dma/Kconfig
+++ b/kernel/dma/Kconfig
@@ -86,6 +86,28 @@ config SWIOTLB
bool
select NEED_DMA_MAP_STATE
+config SWIOTLB_DEFAULT_SIZE_MB
+ int "Default SWIOTLB bounce buffer size in MB"
+ depends on SWIOTLB
+ range 1 64
+ default 64
+ help
+ Sets the default size of the software IO TLB (SWIOTLB) bounce buffer
+ pool allocated at boot time. The default is 64 MB.
+
+ On memory-constrained embedded or mobile platforms (e.g., those with
+ a hardware IOMMU such as ARM SMMU covering most DMA-capable devices),
+ a smaller value such as 4 or 8 MB may be sufficient. The SWIOTLB is
+ then only needed for devices that bypass the IOMMU or have restricted
+ DMA address ranges.
+
+ The minimum allowed value is 1 MB. This compile-time default can be
+ overridden at runtime using the "swiotlb=<nslabs>" kernel command line
+ parameter. Refer to Documentation/admin-guide/kernel-parameters.txt
+ for details.
+
+ If unsure, leave at the default value of 64.
+
config SWIOTLB_DYNAMIC
bool "Dynamic allocation of DMA bounce buffers"
default n
diff --git a/kernel/dma/coherent.c b/kernel/dma/coherent.c
index bcdc0f76d2e8..4d0266893bcc 100644
--- a/kernel/dma/coherent.c
+++ b/kernel/dma/coherent.c
@@ -28,7 +28,7 @@ static inline struct dma_coherent_mem *dev_get_coherent_memory(struct device *de
}
static inline dma_addr_t dma_get_device_base(struct device *dev,
- struct dma_coherent_mem * mem)
+ struct dma_coherent_mem *mem)
{
if (mem->use_dev_dma_pfn_offset)
return phys_to_dma(dev, PFN_PHYS(mem->pfn_base));
@@ -69,8 +69,8 @@ out_free_dma_mem:
kfree(dma_mem);
out_unmap_membase:
memunmap(mem_base);
- pr_err("Reserved memory: failed to init DMA memory pool at %pa, size %zd MiB\n",
- &phys_addr, size / SZ_1M);
+ pr_err("Reserved memory: failed to init DMA memory pool at %pa, size %zu KiB\n",
+ &phys_addr, size / SZ_1K);
return ERR_PTR(-ENOMEM);
}
@@ -236,14 +236,15 @@ static int __dma_mmap_from_coherent(struct dma_coherent_mem *mem,
{
if (mem && vaddr >= mem->virt_base && vaddr + size <=
(mem->virt_base + ((dma_addr_t)mem->size << PAGE_SHIFT))) {
- unsigned long off = vma->vm_pgoff;
+ const pgoff_t pgoff_start = vma_start_pgoff(vma);
+ const pgoff_t pgoff_end = vma_end_pgoff(vma);
int start = (vaddr - mem->virt_base) >> PAGE_SHIFT;
unsigned long user_count = vma_pages(vma);
int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
*ret = -ENXIO;
- if (off < count && user_count <= count - off) {
- unsigned long pfn = mem->pfn_base + start + off;
+ if (pgoff_start < count && pgoff_end <= count) {
+ unsigned long pfn = mem->pfn_base + start + pgoff_start;
*ret = remap_pfn_range(vma, vma->vm_start, pfn,
user_count << PAGE_SHIFT,
vma->vm_page_prot);
@@ -351,8 +352,7 @@ static int rmem_dma_device_init(struct reserved_mem *rmem, struct device *dev)
min_not_zero(dev->coherent_dma_mask, dev->bus_dma_limit))
dev_warn(dev, "reserved memory is beyond device's set DMA address range\n");
- dma_assign_coherent_memory(dev, mem);
- return 0;
+ return dma_assign_coherent_memory(dev, mem);
}
static void rmem_dma_device_release(struct reserved_mem *rmem,
@@ -384,8 +384,8 @@ static int __init rmem_dma_setup(unsigned long node, struct reserved_mem *rmem)
}
#endif
- pr_info("Reserved memory: created DMA memory pool at %pa, size %ld MiB\n",
- &rmem->base, (unsigned long)rmem->size / SZ_1M);
+ pr_info("Reserved memory: created DMA memory pool at %pa, size %llu KiB\n",
+ &rmem->base, (unsigned long long)(rmem->size / SZ_1K));
return 0;
}
diff --git a/kernel/dma/contiguous.c b/kernel/dma/contiguous.c
index f754079a287d..66093460584e 100644
--- a/kernel/dma/contiguous.c
+++ b/kernel/dma/contiguous.c
@@ -140,21 +140,22 @@ static bool numa_cma_configured __initdata;
static int __init early_numa_cma(char *p)
{
int nid, count = 0;
- unsigned long tmp;
+ unsigned long node;
+ phys_addr_t size;
char *s = p;
while (*s) {
- if (sscanf(s, "%lu%n", &tmp, &count) != 1)
+ if (sscanf(s, "%lu%n", &node, &count) != 1)
break;
if (s[count] == ':') {
- if (tmp >= MAX_NUMNODES)
+ if (node >= MAX_NUMNODES)
break;
- nid = array_index_nospec(tmp, MAX_NUMNODES);
+ nid = array_index_nospec(node, MAX_NUMNODES);
s += count + 1;
- tmp = memparse(s, &s);
- numa_cma_size[nid] = tmp;
+ size = memparse(s, &s);
+ numa_cma_size[nid] = size;
if (*s == ',')
s++;
@@ -207,9 +208,10 @@ static void __init dma_numa_cma_reserve(void)
pernuma_size_bytes = cma_get_size(dma_contiguous_default_area);
for_each_node(nid) {
- int size, ret;
+ phys_addr_t size;
char name[CMA_MAX_NAME];
struct cma **cma;
+ int ret;
if (!node_online(nid)) {
if (pernuma_size_bytes || numa_cma_size[nid])
diff --git a/kernel/dma/direct.c b/kernel/dma/direct.c
index 4391b797d4db..da665ca22d5c 100644
--- a/kernel/dma/direct.c
+++ b/kernel/dma/direct.c
@@ -14,6 +14,8 @@
#include <linux/set_memory.h>
#include <linux/slab.h>
#include <linux/pci-p2pdma.h>
+#include <linux/cc_platform.h>
+
#include "direct.h"
/*
@@ -24,11 +26,11 @@
u64 zone_dma_limit __ro_after_init = DMA_BIT_MASK(24);
static inline dma_addr_t phys_to_dma_direct(struct device *dev,
- phys_addr_t phys)
+ phys_addr_t phys, bool unencrypted)
{
- if (force_dma_unencrypted(dev))
+ if (unencrypted)
return phys_to_dma_unencrypted(dev, phys);
- return phys_to_dma(dev, phys);
+ return phys_to_dma_encrypted(dev, phys);
}
static inline struct page *dma_direct_to_page(struct device *dev,
@@ -39,8 +41,9 @@ static inline struct page *dma_direct_to_page(struct device *dev,
u64 dma_direct_get_required_mask(struct device *dev)
{
+ bool require_decrypted = force_dma_unencrypted(dev);
phys_addr_t phys = ((phys_addr_t)max_pfn << PAGE_SHIFT) - 1;
- u64 max_dma = phys_to_dma_direct(dev, phys);
+ u64 max_dma = phys_to_dma_direct(dev, phys, require_decrypted);
return (1ULL << (fls64(max_dma) - 1)) * 2 - 1;
}
@@ -69,7 +72,8 @@ static gfp_t dma_direct_optimal_gfp_mask(struct device *dev, u64 *phys_limit)
bool dma_coherent_ok(struct device *dev, phys_addr_t phys, size_t size)
{
- dma_addr_t dma_addr = phys_to_dma_direct(dev, phys);
+ bool require_decrypted = force_dma_unencrypted(dev);
+ dma_addr_t dma_addr = phys_to_dma_direct(dev, phys, require_decrypted);
if (dma_addr == DMA_MAPPING_ERROR)
return false;
@@ -79,34 +83,28 @@ bool dma_coherent_ok(struct device *dev, phys_addr_t phys, size_t size)
static int dma_set_decrypted(struct device *dev, void *vaddr, size_t size)
{
- if (!force_dma_unencrypted(dev))
- return 0;
- return set_memory_decrypted((unsigned long)vaddr, PFN_UP(size));
+ int ret;
+
+ ret = set_memory_decrypted((unsigned long)vaddr, PFN_UP(size));
+ if (ret)
+ pr_warn_ratelimited("leaking DMA memory that can't be decrypted\n");
+ return ret;
}
static int dma_set_encrypted(struct device *dev, void *vaddr, size_t size)
{
int ret;
- if (!force_dma_unencrypted(dev))
- return 0;
ret = set_memory_encrypted((unsigned long)vaddr, PFN_UP(size));
if (ret)
pr_warn_ratelimited("leaking DMA memory that can't be re-encrypted\n");
return ret;
}
-static void __dma_direct_free_pages(struct device *dev, struct page *page,
- size_t size)
-{
- if (swiotlb_free(dev, page, size))
- return;
- dma_free_contiguous(dev, page, size);
-}
-
-static struct page *dma_direct_alloc_swiotlb(struct device *dev, size_t size)
+static struct page *dma_direct_alloc_swiotlb(struct device *dev, size_t size,
+ unsigned long attrs)
{
- struct page *page = swiotlb_alloc(dev, size);
+ struct page *page = swiotlb_alloc(dev, size, attrs);
if (page && !dma_coherent_ok(dev, page_to_phys(page), size)) {
swiotlb_free(dev, page, size);
@@ -125,9 +123,6 @@ static struct page *__dma_direct_alloc_pages(struct device *dev, size_t size,
WARN_ON_ONCE(!PAGE_ALIGNED(size));
- if (is_swiotlb_for_alloc(dev))
- return dma_direct_alloc_swiotlb(dev, size);
-
gfp |= dma_direct_optimal_gfp_mask(dev, &phys_limit);
page = dma_alloc_contiguous(dev, size, gfp);
if (page) {
@@ -164,22 +159,24 @@ static bool dma_direct_use_pool(struct device *dev, gfp_t gfp)
return !gfpflags_allow_blocking(gfp) && !is_swiotlb_for_alloc(dev);
}
-static void *dma_direct_alloc_from_pool(struct device *dev, size_t size,
- dma_addr_t *dma_handle, gfp_t gfp)
+static struct page *dma_direct_alloc_from_pool(struct device *dev, size_t size,
+ dma_addr_t *dma_handle, void **cpu_addr, gfp_t gfp,
+ unsigned long attrs)
{
struct page *page;
u64 phys_limit;
- void *ret;
if (WARN_ON_ONCE(!IS_ENABLED(CONFIG_DMA_COHERENT_POOL)))
return NULL;
gfp |= dma_direct_optimal_gfp_mask(dev, &phys_limit);
- page = dma_alloc_from_pool(dev, size, &ret, gfp, dma_coherent_ok);
+ page = dma_alloc_from_pool(dev, size, cpu_addr, gfp, attrs,
+ dma_coherent_ok);
if (!page)
return NULL;
- *dma_handle = phys_to_dma_direct(dev, page_to_phys(page));
- return ret;
+ *dma_handle = phys_to_dma_direct(dev, page_to_phys(page),
+ attrs & __DMA_ATTR_ALLOC_CC_SHARED);
+ return page;
}
static void *dma_direct_alloc_no_mapping(struct device *dev, size_t size,
@@ -194,9 +191,11 @@ static void *dma_direct_alloc_no_mapping(struct device *dev, size_t size,
/* remove any dirty cache lines on the kernel alias */
if (!PageHighMem(page))
arch_dma_prep_coherent(page, size);
-
- /* return the page pointer as the opaque cookie */
- *dma_handle = phys_to_dma_direct(dev, page_to_phys(page));
+ /*
+ * return the page pointer as the opaque cookie.
+ * Never used for unencrypted allocation
+ */
+ *dma_handle = phys_to_dma_encrypted(dev, page_to_phys(page));
return page;
}
@@ -204,15 +203,31 @@ void *dma_direct_alloc(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t gfp, unsigned long attrs)
{
bool remap = false, set_uncached = false;
+ bool mark_mem_decrypt = false;
+ bool allow_highmem = true;
struct page *page;
- void *ret;
+ void *cpu_addr;
+
+ if (force_dma_unencrypted(dev))
+ attrs |= __DMA_ATTR_ALLOC_CC_SHARED;
+
+ if (attrs & __DMA_ATTR_ALLOC_CC_SHARED) {
+ /*
+ * Unencrypted/shared DMA requires a linear-mapped buffer
+ * address to look up the PFN and set architecture-required PFN
+ * attributes. This is not possible with HighMem. Avoid HighMem
+ * allocation.
+ */
+ allow_highmem = false;
+ mark_mem_decrypt = true;
+ }
size = PAGE_ALIGN(size);
if (attrs & DMA_ATTR_NO_WARN)
gfp |= __GFP_NOWARN;
- if ((attrs & DMA_ATTR_NO_KERNEL_MAPPING) &&
- !force_dma_unencrypted(dev) && !is_swiotlb_for_alloc(dev))
+ if (((attrs & (DMA_ATTR_NO_KERNEL_MAPPING | __DMA_ATTR_ALLOC_CC_SHARED)) ==
+ DMA_ATTR_NO_KERNEL_MAPPING) && !is_swiotlb_for_alloc(dev))
return dma_direct_alloc_no_mapping(dev, size, dma_handle, gfp);
if (!dev_is_dma_coherent(dev)) {
@@ -245,16 +260,37 @@ void *dma_direct_alloc(struct device *dev, size_t size,
/*
* Remapping or decrypting memory may block, allocate the memory from
* the atomic pools instead if we aren't allowed block.
+ * FIXME: With CONFIG_DMA_DIRECT_REMAP, the pool is also mapped as
+ * DMA-coherent (non-cacheable). We may want to create a separate pool
+ * dedicated to CC_SHARED atomic allocations.
*/
- if ((remap || force_dma_unencrypted(dev)) &&
- dma_direct_use_pool(dev, gfp))
- return dma_direct_alloc_from_pool(dev, size, dma_handle, gfp);
+ if ((remap || (attrs & __DMA_ATTR_ALLOC_CC_SHARED)) &&
+ dma_direct_use_pool(dev, gfp)) {
+ page = dma_direct_alloc_from_pool(dev, size,
+ dma_handle, &cpu_addr,
+ gfp, attrs);
+ return page ? cpu_addr : NULL;
+ }
+
+ if (is_swiotlb_for_alloc(dev)) {
+ page = dma_direct_alloc_swiotlb(dev, size, attrs);
+ if (page) {
+ /*
+ * swiotlb allocations comes from pool already marked
+ * decrypted
+ */
+ mark_mem_decrypt = false;
+ goto setup_page;
+ }
+ return NULL;
+ }
/* we always manually zero the memory once we are done */
- page = __dma_direct_alloc_pages(dev, size, gfp & ~__GFP_ZERO, true);
+ page = __dma_direct_alloc_pages(dev, size, gfp & ~__GFP_ZERO, allow_highmem);
if (!page)
return NULL;
+setup_page:
/*
* dma_alloc_contiguous can return highmem pages depending on a
* combination the cma= arguments and per-arch setup. These need to be
@@ -265,43 +301,56 @@ void *dma_direct_alloc(struct device *dev, size_t size,
set_uncached = false;
}
+ if (mark_mem_decrypt) {
+ void *lm_addr;
+
+ lm_addr = page_address(page);
+ if (set_memory_decrypted((unsigned long)lm_addr, PFN_UP(size)))
+ goto out_leak_pages;
+ }
+
if (remap) {
pgprot_t prot = dma_pgprot(dev, PAGE_KERNEL, attrs);
- if (force_dma_unencrypted(dev))
- prot = pgprot_decrypted(prot);
-
/* remove any dirty cache lines on the kernel alias */
arch_dma_prep_coherent(page, size);
/* create a coherent mapping */
- ret = dma_common_contiguous_remap(page, size, prot,
- __builtin_return_address(0));
- if (!ret)
- goto out_free_pages;
+ cpu_addr = dma_common_contiguous_remap(page, size, prot,
+ __builtin_return_address(0));
+ if (!cpu_addr)
+ goto out_encrypt_pages;
} else {
- ret = page_address(page);
- if (dma_set_decrypted(dev, ret, size))
- goto out_leak_pages;
+ cpu_addr = page_address(page);
}
- memset(ret, 0, size);
+ memset(cpu_addr, 0, size);
if (set_uncached) {
+ void *uncached_cpu_addr;
+
arch_dma_prep_coherent(page, size);
- ret = arch_dma_set_uncached(ret, size);
- if (IS_ERR(ret))
- goto out_encrypt_pages;
+ uncached_cpu_addr = arch_dma_set_uncached(cpu_addr, size);
+ if (IS_ERR(uncached_cpu_addr))
+ goto out_free_remap_pages;
+ cpu_addr = uncached_cpu_addr;
}
- *dma_handle = phys_to_dma_direct(dev, page_to_phys(page));
- return ret;
+ *dma_handle = phys_to_dma_direct(dev, page_to_phys(page),
+ attrs & __DMA_ATTR_ALLOC_CC_SHARED);
+ return cpu_addr;
+
+out_free_remap_pages:
+ if (remap)
+ dma_common_free_remap(cpu_addr, size);
out_encrypt_pages:
- if (dma_set_encrypted(dev, page_address(page), size))
- return NULL;
-out_free_pages:
- __dma_direct_free_pages(dev, page, size);
+ if (mark_mem_decrypt &&
+ dma_set_encrypted(dev, page_address(page), size))
+ goto out_leak_pages;
+
+ if (!swiotlb_free(dev, page, size))
+ dma_free_contiguous(dev, page, size);
return NULL;
out_leak_pages:
return NULL;
@@ -310,10 +359,23 @@ out_leak_pages:
void dma_direct_free(struct device *dev, size_t size,
void *cpu_addr, dma_addr_t dma_addr, unsigned long attrs)
{
+ phys_addr_t phys;
+ bool mark_mem_encrypted = false;
+ struct io_tlb_pool *swiotlb_pool;
unsigned int page_order = get_order(size);
- if ((attrs & DMA_ATTR_NO_KERNEL_MAPPING) &&
- !force_dma_unencrypted(dev) && !is_swiotlb_for_alloc(dev)) {
+ /*
+ * If the allocation used decrypted/shared backing pages, restore
+ * the encryption state on free.
+ */
+ if (force_dma_unencrypted(dev))
+ attrs |= __DMA_ATTR_ALLOC_CC_SHARED;
+
+ if (attrs & __DMA_ATTR_ALLOC_CC_SHARED)
+ mark_mem_encrypted = true;
+
+ if (((attrs & (DMA_ATTR_NO_KERNEL_MAPPING | __DMA_ATTR_ALLOC_CC_SHARED)) ==
+ DMA_ATTR_NO_KERNEL_MAPPING) && !is_swiotlb_for_alloc(dev)) {
/* cpu_addr is a struct page cookie, not a kernel address */
dma_free_contiguous(dev, cpu_addr, size);
return;
@@ -338,36 +400,70 @@ void dma_direct_free(struct device *dev, size_t size,
dma_free_from_pool(dev, cpu_addr, PAGE_ALIGN(size)))
return;
+ phys = dma_to_phys(dev, dma_addr);
+ swiotlb_pool = swiotlb_find_pool(dev, phys);
+ if (swiotlb_pool)
+ /* Swiotlb doesn't need a page attribute update on free */
+ mark_mem_encrypted = false;
+
if (is_vmalloc_addr(cpu_addr)) {
vunmap(cpu_addr);
} else {
if (IS_ENABLED(CONFIG_ARCH_HAS_DMA_CLEAR_UNCACHED))
arch_dma_clear_uncached(cpu_addr, size);
- if (dma_set_encrypted(dev, cpu_addr, size))
+ }
+
+ if (mark_mem_encrypted) {
+ void *lm_addr;
+
+ lm_addr = phys_to_virt(phys);
+ if (set_memory_encrypted((unsigned long)lm_addr, PFN_UP(size))) {
+ pr_warn_ratelimited("leaking DMA memory that can't be re-encrypted\n");
return;
+ }
}
- __dma_direct_free_pages(dev, dma_direct_to_page(dev, dma_addr), size);
+ if (swiotlb_pool)
+ swiotlb_free_from_pool(dev, phys, swiotlb_pool);
+ else
+ dma_free_contiguous(dev, dma_direct_to_page(dev, dma_addr), size);
}
struct page *dma_direct_alloc_pages(struct device *dev, size_t size,
dma_addr_t *dma_handle, enum dma_data_direction dir, gfp_t gfp)
{
+ unsigned long attrs = 0;
struct page *page;
- void *ret;
+ void *cpu_addr;
+
+ if (force_dma_unencrypted(dev))
+ attrs |= __DMA_ATTR_ALLOC_CC_SHARED;
+
+ if ((attrs & __DMA_ATTR_ALLOC_CC_SHARED) && dma_direct_use_pool(dev, gfp))
+ return dma_direct_alloc_from_pool(dev, size, dma_handle,
+ &cpu_addr, gfp, attrs);
+
+ if (is_swiotlb_for_alloc(dev)) {
+ page = dma_direct_alloc_swiotlb(dev, size, attrs);
+ if (!page)
+ return NULL;
- if (force_dma_unencrypted(dev) && dma_direct_use_pool(dev, gfp))
- return dma_direct_alloc_from_pool(dev, size, dma_handle, gfp);
+ cpu_addr = page_address(page);
+ goto setup_page;
+ }
page = __dma_direct_alloc_pages(dev, size, gfp, false);
if (!page)
return NULL;
- ret = page_address(page);
- if (dma_set_decrypted(dev, ret, size))
+ cpu_addr = page_address(page);
+ if ((attrs & __DMA_ATTR_ALLOC_CC_SHARED) &&
+ dma_set_decrypted(dev, cpu_addr, size))
goto out_leak_pages;
- memset(ret, 0, size);
- *dma_handle = phys_to_dma_direct(dev, page_to_phys(page));
+setup_page:
+ memset(cpu_addr, 0, size);
+ *dma_handle = phys_to_dma_direct(dev, page_to_phys(page),
+ attrs & __DMA_ATTR_ALLOC_CC_SHARED);
return page;
out_leak_pages:
return NULL;
@@ -377,16 +473,32 @@ void dma_direct_free_pages(struct device *dev, size_t size,
struct page *page, dma_addr_t dma_addr,
enum dma_data_direction dir)
{
+ phys_addr_t phys;
void *vaddr = page_address(page);
+ struct io_tlb_pool *swiotlb_pool;
+ /*
+ * if the device had requested for an unencrypted buffer,
+ * convert it to encrypted on free
+ */
+ bool mark_mem_encrypted = force_dma_unencrypted(dev);
- /* If cpu_addr is not from an atomic pool, dma_free_from_pool() fails */
+ /* If page is not from an atomic pool, dma_free_from_pool_page() fails */
if (IS_ENABLED(CONFIG_DMA_COHERENT_POOL) &&
- dma_free_from_pool(dev, vaddr, size))
+ dma_free_from_pool_page(dev, page, size))
return;
- if (dma_set_encrypted(dev, vaddr, size))
+ phys = page_to_phys(page);
+ swiotlb_pool = swiotlb_find_pool(dev, phys);
+ if (swiotlb_pool)
+ mark_mem_encrypted = false;
+
+ if (mark_mem_encrypted && dma_set_encrypted(dev, vaddr, size))
return;
- __dma_direct_free_pages(dev, page, size);
+
+ if (swiotlb_pool)
+ swiotlb_free_from_pool(dev, phys, swiotlb_pool);
+ else
+ dma_free_contiguous(dev, page, size);
}
#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \
@@ -489,9 +601,8 @@ int dma_direct_map_sg(struct device *dev, struct scatterlist *sgl, int nents,
case PCI_P2PDMA_MAP_BUS_ADDR:
sg->dma_address = pci_p2pdma_bus_addr_map(
p2pdma_state.mem, sg_phys(sg));
- sg_dma_len(sg) = sg->length;
sg_dma_mark_bus_address(sg);
- continue;
+ break;
default:
ret = -EREMOTEIO;
goto out_unmap;
@@ -534,23 +645,83 @@ int dma_direct_mmap(struct device *dev, struct vm_area_struct *vma,
unsigned long user_count = vma_pages(vma);
unsigned long count = PAGE_ALIGN(size) >> PAGE_SHIFT;
unsigned long pfn = PHYS_PFN(dma_to_phys(dev, dma_addr));
+ const pgoff_t pgoff_start = vma_start_pgoff(vma);
+ const pgoff_t pgoff_end = vma_end_pgoff(vma);
int ret = -ENXIO;
- vma->vm_page_prot = dma_pgprot(dev, vma->vm_page_prot, attrs);
if (force_dma_unencrypted(dev))
- vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
+ attrs |= DMA_ATTR_CC_SHARED;
+
+ vma->vm_page_prot = dma_pgprot(dev, vma->vm_page_prot, attrs);
if (dma_mmap_from_dev_coherent(dev, vma, cpu_addr, size, &ret))
return ret;
if (dma_mmap_from_global_coherent(vma, cpu_addr, size, &ret))
return ret;
- if (vma->vm_pgoff >= count || user_count > count - vma->vm_pgoff)
+ if (pgoff_start >= count || pgoff_end > count)
return -ENXIO;
- return remap_pfn_range(vma, vma->vm_start, pfn + vma->vm_pgoff,
+ return remap_pfn_range(vma, vma->vm_start, pfn + pgoff_start,
user_count << PAGE_SHIFT, vma->vm_page_prot);
}
+dma_addr_t dma_direct_map_phys(struct device *dev, phys_addr_t phys,
+ size_t size, enum dma_data_direction dir,
+ unsigned long attrs, bool flush)
+{
+ dma_addr_t dma_addr;
+
+ if (attrs & DMA_ATTR_MMIO) {
+ /*
+ * For host memory encryption treat MMIO memory as shared
+ */
+ if (cc_platform_has(CC_ATTR_HOST_MEM_ENCRYPT))
+ attrs |= DMA_ATTR_CC_SHARED;
+ }
+
+ if (is_swiotlb_force_bounce(dev)) {
+ if (attrs & (DMA_ATTR_MMIO | DMA_ATTR_REQUIRE_COHERENT))
+ return DMA_MAPPING_ERROR;
+
+ return swiotlb_map(dev, phys, size, dir, attrs);
+ }
+
+ if (attrs & DMA_ATTR_CC_SHARED)
+ dma_addr = phys_to_dma_unencrypted(dev, phys);
+ else
+ dma_addr = phys_to_dma_encrypted(dev, phys);
+
+ if (attrs & DMA_ATTR_MMIO) {
+ if (unlikely(!dma_capable(dev, dma_addr, size, false, attrs)))
+ goto err_overflow;
+ goto dma_mapped;
+ }
+
+ if (unlikely(!dma_capable(dev, dma_addr, size, true, attrs)) ||
+ dma_kmalloc_needs_bounce(dev, size, dir)) {
+ if (is_swiotlb_active(dev) &&
+ !(attrs & DMA_ATTR_REQUIRE_COHERENT))
+ return swiotlb_map(dev, phys, size, dir, attrs);
+ goto err_overflow;
+ }
+
+dma_mapped:
+ if (!dev_is_dma_coherent(dev) &&
+ !(attrs & (DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_MMIO))) {
+ arch_sync_dma_for_device(phys, size, dir);
+ if (flush)
+ arch_sync_dma_flush();
+ }
+ return dma_addr;
+
+err_overflow:
+ dev_WARN_ONCE(
+ dev, 1,
+ "DMA addr %pad+%zu overflow (mask %llx, bus limit %llx).\n",
+ &dma_addr, size, *dev->dma_mask, dev->bus_dma_limit);
+ return DMA_MAPPING_ERROR;
+}
+
int dma_direct_supported(struct device *dev, u64 mask)
{
u64 min_mask = ((u64)max_pfn << PAGE_SHIFT) - 1;
@@ -626,8 +797,10 @@ size_t dma_direct_max_mapping_size(struct device *dev)
{
/* If SWIOTLB is active, use its maximum mapping size */
if (is_swiotlb_active(dev) &&
- (dma_addressing_limited(dev) || is_swiotlb_force_bounce(dev)))
+ (dma_addressing_limited(dev) || is_swiotlb_force_bounce(dev) ||
+ force_dma_unencrypted(dev)))
return swiotlb_max_mapping_size(dev);
+
return SIZE_MAX;
}
diff --git a/kernel/dma/direct.h b/kernel/dma/direct.h
index 7140c208c123..a7adadb1b2a5 100644
--- a/kernel/dma/direct.h
+++ b/kernel/dma/direct.h
@@ -17,6 +17,9 @@ bool dma_direct_can_mmap(struct device *dev);
int dma_direct_mmap(struct device *dev, struct vm_area_struct *vma,
void *cpu_addr, dma_addr_t dma_addr, size_t size,
unsigned long attrs);
+dma_addr_t dma_direct_map_phys(struct device *dev, phys_addr_t phys,
+ size_t size, enum dma_data_direction dir,
+ unsigned long attrs, bool flush);
bool dma_direct_need_sync(struct device *dev, dma_addr_t dma_addr);
int dma_direct_map_sg(struct device *dev, struct scatterlist *sgl, int nents,
enum dma_data_direction dir, unsigned long attrs);
@@ -82,59 +85,6 @@ static inline void dma_direct_sync_single_for_cpu(struct device *dev,
swiotlb_sync_single_for_cpu(dev, paddr, size, dir);
}
-static inline dma_addr_t dma_direct_map_phys(struct device *dev,
- phys_addr_t phys, size_t size, enum dma_data_direction dir,
- unsigned long attrs, bool flush)
-{
- dma_addr_t dma_addr;
-
- if (is_swiotlb_force_bounce(dev)) {
- if (!(attrs & DMA_ATTR_CC_SHARED)) {
- if (attrs & (DMA_ATTR_MMIO | DMA_ATTR_REQUIRE_COHERENT))
- return DMA_MAPPING_ERROR;
-
- return swiotlb_map(dev, phys, size, dir, attrs);
- }
- } else if (attrs & DMA_ATTR_CC_SHARED) {
- return DMA_MAPPING_ERROR;
- }
-
- if (attrs & DMA_ATTR_MMIO) {
- dma_addr = phys;
- if (unlikely(!dma_capable(dev, dma_addr, size, false)))
- goto err_overflow;
- } else if (attrs & DMA_ATTR_CC_SHARED) {
- dma_addr = phys_to_dma_unencrypted(dev, phys);
- if (unlikely(!dma_capable(dev, dma_addr, size, false)))
- goto err_overflow;
- } else {
- dma_addr = phys_to_dma(dev, phys);
- if (unlikely(!dma_capable(dev, dma_addr, size, true)) ||
- dma_kmalloc_needs_bounce(dev, size, dir)) {
- if (is_swiotlb_active(dev) &&
- !(attrs & DMA_ATTR_REQUIRE_COHERENT))
- return swiotlb_map(dev, phys, size, dir, attrs);
-
- goto err_overflow;
- }
- }
-
- if (!dev_is_dma_coherent(dev) &&
- !(attrs & (DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_MMIO))) {
- arch_sync_dma_for_device(phys, size, dir);
- if (flush)
- arch_sync_dma_flush();
- }
- return dma_addr;
-
-err_overflow:
- dev_WARN_ONCE(
- dev, 1,
- "DMA addr %pad+%zu overflow (mask %llx, bus limit %llx).\n",
- &dma_addr, size, *dev->dma_mask, dev->bus_dma_limit);
- return DMA_MAPPING_ERROR;
-}
-
static inline void dma_direct_unmap_phys(struct device *dev, dma_addr_t addr,
size_t size, enum dma_data_direction dir, unsigned long attrs,
bool flush)
diff --git a/kernel/dma/map_benchmark.c b/kernel/dma/map_benchmark.c
index fdc070f419f6..957707158ff6 100644
--- a/kernel/dma/map_benchmark.c
+++ b/kernel/dma/map_benchmark.c
@@ -51,8 +51,7 @@ struct dma_single_map_param {
static void *dma_single_map_benchmark_prepare(struct map_benchmark_data *map)
{
- struct dma_single_map_param *params __free(kfree) = kzalloc(sizeof(*params),
- GFP_KERNEL);
+ struct dma_single_map_param *params __free(kfree) = kzalloc_obj(*params);
if (!params)
return NULL;
diff --git a/kernel/dma/mapping.c b/kernel/dma/mapping.c
index 4fe04669e5e6..bf2651a70b7c 100644
--- a/kernel/dma/mapping.c
+++ b/kernel/dma/mapping.c
@@ -537,13 +537,21 @@ EXPORT_SYMBOL(dma_get_sgtable_attrs);
*/
pgprot_t dma_pgprot(struct device *dev, pgprot_t prot, unsigned long attrs)
{
+ pgprot_t dma_prot;
+
if (dev_is_dma_coherent(dev))
- return prot;
+ dma_prot = prot;
#ifdef CONFIG_ARCH_HAS_DMA_WRITE_COMBINE
- if (attrs & DMA_ATTR_WRITE_COMBINE)
- return pgprot_writecombine(prot);
+ else if (attrs & DMA_ATTR_WRITE_COMBINE)
+ dma_prot = pgprot_writecombine(prot);
#endif
- return pgprot_dmacoherent(prot);
+ else
+ dma_prot = pgprot_dmacoherent(prot);
+
+ if (attrs & (DMA_ATTR_CC_SHARED | __DMA_ATTR_ALLOC_CC_SHARED))
+ return pgprot_decrypted(dma_prot);
+ else
+ return pgprot_encrypted(dma_prot);
}
#endif /* CONFIG_MMU */
@@ -638,6 +646,15 @@ void *dma_alloc_attrs(struct device *dev, size_t size, dma_addr_t *dma_handle,
if (WARN_ON_ONCE(flag & __GFP_COMP))
return NULL;
+ if (attrs & (DMA_ATTR_CC_SHARED | __DMA_ATTR_ALLOC_CC_SHARED)) {
+ trace_dma_alloc(dev, NULL, 0, size, DMA_BIDIRECTIONAL, flag,
+ attrs);
+ return NULL;
+ }
+
+ if (force_dma_unencrypted(dev))
+ attrs |= __DMA_ATTR_ALLOC_CC_SHARED;
+
if (dma_alloc_from_dev_coherent(dev, size, dma_handle, &cpu_addr)) {
trace_dma_alloc(dev, cpu_addr, *dma_handle, size,
DMA_BIDIRECTIONAL, flag, attrs);
@@ -761,12 +778,14 @@ EXPORT_SYMBOL_GPL(dma_free_pages);
int dma_mmap_pages(struct device *dev, struct vm_area_struct *vma,
size_t size, struct page *page)
{
- unsigned long count = PAGE_ALIGN(size) >> PAGE_SHIFT;
+ const pgoff_t pgoff_start = vma_start_pgoff(vma);
+ const pgoff_t pgoff_end = vma_end_pgoff(vma);
+ const unsigned long count = PAGE_ALIGN(size) >> PAGE_SHIFT;
- if (vma->vm_pgoff >= count || vma_pages(vma) > count - vma->vm_pgoff)
+ if (pgoff_start >= count || pgoff_end > count)
return -ENXIO;
return remap_pfn_range(vma, vma->vm_start,
- page_to_pfn(page) + vma->vm_pgoff,
+ page_to_pfn(page) + pgoff_start,
vma_pages(vma) << PAGE_SHIFT, vma->vm_page_prot);
}
EXPORT_SYMBOL_GPL(dma_mmap_pages);
@@ -979,6 +998,9 @@ size_t dma_max_mapping_size(struct device *dev)
const struct dma_map_ops *ops = get_dma_ops(dev);
size_t size = SIZE_MAX;
+ if (!dev->dma_mask)
+ return 0;
+
if (dma_map_direct(dev, ops))
size = dma_direct_max_mapping_size(dev);
else if (use_dma_iommu(dev))
diff --git a/kernel/dma/ops_helpers.c b/kernel/dma/ops_helpers.c
index 20caf9cabf69..6b5f9208d31c 100644
--- a/kernel/dma/ops_helpers.c
+++ b/kernel/dma/ops_helpers.c
@@ -39,7 +39,7 @@ int dma_common_mmap(struct device *dev, struct vm_area_struct *vma,
#ifdef CONFIG_MMU
unsigned long user_count = vma_pages(vma);
unsigned long count = PAGE_ALIGN(size) >> PAGE_SHIFT;
- unsigned long off = vma->vm_pgoff;
+ unsigned long off = vma_start_pgoff(vma);
struct page *page = dma_common_vaddr_to_page(cpu_addr);
int ret = -ENXIO;
@@ -52,7 +52,7 @@ int dma_common_mmap(struct device *dev, struct vm_area_struct *vma,
return -ENXIO;
return remap_pfn_range(vma, vma->vm_start,
- page_to_pfn(page) + vma->vm_pgoff,
+ page_to_pfn(page) + vma_start_pgoff(vma),
user_count << PAGE_SHIFT, vma->vm_page_prot);
#else
return -ENXIO;
diff --git a/kernel/dma/pool.c b/kernel/dma/pool.c
index 2b2fbb709242..00f422a1e896 100644
--- a/kernel/dma/pool.c
+++ b/kernel/dma/pool.c
@@ -12,12 +12,18 @@
#include <linux/set_memory.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
+#include <linux/cc_platform.h>
-static struct gen_pool *atomic_pool_dma __ro_after_init;
+struct dma_gen_pool {
+ bool cc_shared;
+ struct gen_pool *pool;
+};
+
+static struct dma_gen_pool atomic_pool_dma __ro_after_init;
static unsigned long pool_size_dma;
-static struct gen_pool *atomic_pool_dma32 __ro_after_init;
+static struct dma_gen_pool atomic_pool_dma32 __ro_after_init;
static unsigned long pool_size_dma32;
-static struct gen_pool *atomic_pool_kernel __ro_after_init;
+static struct dma_gen_pool atomic_pool_kernel __ro_after_init;
static unsigned long pool_size_kernel;
/* Size can be defined by the coherent_pool command line */
@@ -76,13 +82,15 @@ static bool cma_in_zone(gfp_t gfp)
return true;
}
-static int atomic_pool_expand(struct gen_pool *pool, size_t pool_size,
+static int atomic_pool_expand(struct dma_gen_pool *dma_pool, size_t pool_size,
gfp_t gfp)
{
unsigned int order;
struct page *page = NULL;
+ bool leak_pages = false;
void *addr;
int ret = -ENOMEM;
+ pgprot_t prot __maybe_unused;
/* Cannot allocate larger than MAX_PAGE_ORDER */
order = min(get_order(pool_size), MAX_PAGE_ORDER);
@@ -101,8 +109,12 @@ static int atomic_pool_expand(struct gen_pool *pool, size_t pool_size,
arch_dma_prep_coherent(page, pool_size);
#ifdef CONFIG_DMA_DIRECT_REMAP
- addr = dma_common_contiguous_remap(page, pool_size,
- pgprot_decrypted(pgprot_dmacoherent(PAGE_KERNEL)),
+ if (dma_pool->cc_shared)
+ prot = pgprot_decrypted(pgprot_dmacoherent(PAGE_KERNEL));
+ else
+ prot = pgprot_dmacoherent(PAGE_KERNEL);
+
+ addr = dma_common_contiguous_remap(page, pool_size, prot,
__builtin_return_address(0));
if (!addr)
goto free_page;
@@ -113,12 +125,17 @@ static int atomic_pool_expand(struct gen_pool *pool, size_t pool_size,
* Memory in the atomic DMA pools must be unencrypted, the pools do not
* shrink so no re-encryption occurs in dma_direct_free().
*/
- ret = set_memory_decrypted((unsigned long)page_to_virt(page),
- 1 << order);
- if (ret)
- goto remove_mapping;
- ret = gen_pool_add_virt(pool, (unsigned long)addr, page_to_phys(page),
- pool_size, NUMA_NO_NODE);
+ if (dma_pool->cc_shared) {
+ ret = set_memory_decrypted((unsigned long)page_to_virt(page),
+ 1 << order);
+ if (ret) {
+ leak_pages = true;
+ goto remove_mapping;
+ }
+ }
+
+ ret = gen_pool_add_virt(dma_pool->pool, (unsigned long)addr,
+ page_to_phys(page), pool_size, NUMA_NO_NODE);
if (ret)
goto encrypt_mapping;
@@ -126,62 +143,67 @@ static int atomic_pool_expand(struct gen_pool *pool, size_t pool_size,
return 0;
encrypt_mapping:
- ret = set_memory_encrypted((unsigned long)page_to_virt(page),
- 1 << order);
- if (WARN_ON_ONCE(ret)) {
- /* Decrypt succeeded but encrypt failed, purposely leak */
- goto out;
- }
+ if (dma_pool->cc_shared &&
+ set_memory_encrypted((unsigned long)page_to_virt(page), 1 << order))
+ leak_pages = true;
+
remove_mapping:
#ifdef CONFIG_DMA_DIRECT_REMAP
dma_common_free_remap(addr, pool_size);
free_page:
- __free_pages(page, order);
#endif
+ if (!leak_pages)
+ __free_pages(page, order);
out:
return ret;
}
-static void atomic_pool_resize(struct gen_pool *pool, gfp_t gfp)
+static void atomic_pool_resize(struct dma_gen_pool *dma_pool, gfp_t gfp)
{
- if (pool && gen_pool_avail(pool) < atomic_pool_size)
- atomic_pool_expand(pool, gen_pool_size(pool), gfp);
+ if (dma_pool->pool && gen_pool_avail(dma_pool->pool) < atomic_pool_size)
+ atomic_pool_expand(dma_pool, gen_pool_size(dma_pool->pool), gfp);
}
static void atomic_pool_work_fn(struct work_struct *work)
{
if (IS_ENABLED(CONFIG_ZONE_DMA))
- atomic_pool_resize(atomic_pool_dma,
+ atomic_pool_resize(&atomic_pool_dma,
GFP_KERNEL | GFP_DMA);
if (IS_ENABLED(CONFIG_ZONE_DMA32))
- atomic_pool_resize(atomic_pool_dma32,
+ atomic_pool_resize(&atomic_pool_dma32,
GFP_KERNEL | GFP_DMA32);
- atomic_pool_resize(atomic_pool_kernel, GFP_KERNEL);
+ atomic_pool_resize(&atomic_pool_kernel, GFP_KERNEL);
}
-static __init struct gen_pool *__dma_atomic_pool_init(size_t pool_size,
- gfp_t gfp)
+static __init struct dma_gen_pool *__dma_atomic_pool_init(struct dma_gen_pool *dma_pool,
+ size_t pool_size, gfp_t gfp)
{
- struct gen_pool *pool;
int ret;
- pool = gen_pool_create(PAGE_SHIFT, NUMA_NO_NODE);
- if (!pool)
+ dma_pool->pool = gen_pool_create(PAGE_SHIFT, NUMA_NO_NODE);
+ if (!dma_pool->pool)
return NULL;
- gen_pool_set_algo(pool, gen_pool_first_fit_order_align, NULL);
+ gen_pool_set_algo(dma_pool->pool, gen_pool_first_fit_order_align, NULL);
- ret = atomic_pool_expand(pool, pool_size, gfp);
+ /* if platform is using memory encryption atomic pools are by default shared. */
+ if (cc_platform_has(CC_ATTR_MEM_ENCRYPT))
+ dma_pool->cc_shared = true;
+ else
+ dma_pool->cc_shared = false;
+
+ ret = atomic_pool_expand(dma_pool, pool_size, gfp);
if (ret) {
- gen_pool_destroy(pool);
+ gen_pool_destroy(dma_pool->pool);
+ dma_pool->pool = NULL;
pr_err("DMA: failed to allocate %zu KiB %pGg pool for atomic allocation\n",
pool_size >> 10, &gfp);
return NULL;
}
pr_info("DMA: preallocated %zu KiB %pGg pool for atomic allocations\n",
- gen_pool_size(pool) >> 10, &gfp);
- return pool;
+ gen_pool_size(dma_pool->pool) >> 10, &gfp);
+ return dma_pool;
}
#ifdef CONFIG_ZONE_DMA32
@@ -207,21 +229,22 @@ static int __init dma_atomic_pool_init(void)
/* All memory might be in the DMA zone(s) to begin with */
if (has_managed_zone(ZONE_NORMAL)) {
- atomic_pool_kernel = __dma_atomic_pool_init(atomic_pool_size,
- GFP_KERNEL);
- if (!atomic_pool_kernel)
+ __dma_atomic_pool_init(&atomic_pool_kernel, atomic_pool_size, GFP_KERNEL);
+ if (!atomic_pool_kernel.pool)
ret = -ENOMEM;
}
+
if (has_managed_dma()) {
- atomic_pool_dma = __dma_atomic_pool_init(atomic_pool_size,
- GFP_KERNEL | GFP_DMA);
- if (!atomic_pool_dma)
+ __dma_atomic_pool_init(&atomic_pool_dma, atomic_pool_size,
+ GFP_KERNEL | GFP_DMA);
+ if (!atomic_pool_dma.pool)
ret = -ENOMEM;
}
+
if (has_managed_dma32) {
- atomic_pool_dma32 = __dma_atomic_pool_init(atomic_pool_size,
- GFP_KERNEL | GFP_DMA32);
- if (!atomic_pool_dma32)
+ __dma_atomic_pool_init(&atomic_pool_dma32, atomic_pool_size,
+ GFP_KERNEL | GFP_DMA32);
+ if (!atomic_pool_dma32.pool)
ret = -ENOMEM;
}
@@ -230,19 +253,44 @@ static int __init dma_atomic_pool_init(void)
}
postcore_initcall(dma_atomic_pool_init);
-static inline struct gen_pool *dma_guess_pool(struct gen_pool *prev, gfp_t gfp)
+static inline struct dma_gen_pool *__dma_guess_pool(struct dma_gen_pool *first,
+ struct dma_gen_pool *second, struct dma_gen_pool *third)
{
- if (prev == NULL) {
+ if (first->pool)
+ return first;
+ if (second && second->pool)
+ return second;
+ if (third && third->pool)
+ return third;
+ return NULL;
+}
+
+static inline struct dma_gen_pool *dma_guess_pool(struct dma_gen_pool *prev,
+ gfp_t gfp)
+{
+ if (!prev) {
if (gfp & GFP_DMA)
- return atomic_pool_dma ?: atomic_pool_dma32 ?: atomic_pool_kernel;
+ return __dma_guess_pool(&atomic_pool_dma,
+ &atomic_pool_dma32,
+ &atomic_pool_kernel);
+
if (gfp & GFP_DMA32)
- return atomic_pool_dma32 ?: atomic_pool_dma ?: atomic_pool_kernel;
- return atomic_pool_kernel ?: atomic_pool_dma32 ?: atomic_pool_dma;
+ return __dma_guess_pool(&atomic_pool_dma32,
+ &atomic_pool_dma,
+ &atomic_pool_kernel);
+
+ return __dma_guess_pool(&atomic_pool_kernel,
+ &atomic_pool_dma32,
+ &atomic_pool_dma);
}
- if (prev == atomic_pool_kernel)
- return atomic_pool_dma32 ? atomic_pool_dma32 : atomic_pool_dma;
- if (prev == atomic_pool_dma32)
- return atomic_pool_dma;
+
+ if (prev == &atomic_pool_kernel)
+ return __dma_guess_pool(&atomic_pool_dma32,
+ &atomic_pool_dma, NULL);
+
+ if (prev == &atomic_pool_dma32)
+ return __dma_guess_pool(&atomic_pool_dma, NULL, NULL);
+
return NULL;
}
@@ -272,16 +320,20 @@ static struct page *__dma_alloc_from_pool(struct device *dev, size_t size,
}
struct page *dma_alloc_from_pool(struct device *dev, size_t size,
- void **cpu_addr, gfp_t gfp,
+ void **cpu_addr, gfp_t gfp, unsigned long attrs,
bool (*phys_addr_ok)(struct device *, phys_addr_t, size_t))
{
- struct gen_pool *pool = NULL;
+ struct dma_gen_pool *dma_pool = NULL;
struct page *page;
bool pool_found = false;
- while ((pool = dma_guess_pool(pool, gfp))) {
+ while ((dma_pool = dma_guess_pool(dma_pool, gfp))) {
+
+ if (dma_pool->cc_shared != !!(attrs & __DMA_ATTR_ALLOC_CC_SHARED))
+ continue;
+
pool_found = true;
- page = __dma_alloc_from_pool(dev, size, pool, cpu_addr,
+ page = __dma_alloc_from_pool(dev, size, dma_pool->pool, cpu_addr,
phys_addr_ok);
if (page)
return page;
@@ -296,14 +348,77 @@ struct page *dma_alloc_from_pool(struct device *dev, size_t size,
bool dma_free_from_pool(struct device *dev, void *start, size_t size)
{
- struct gen_pool *pool = NULL;
+ struct dma_gen_pool *dma_pool = NULL;
+
+ while ((dma_pool = dma_guess_pool(dma_pool, 0))) {
- while ((pool = dma_guess_pool(pool, 0))) {
- if (!gen_pool_has_addr(pool, (unsigned long)start, size))
+ if (!gen_pool_has_addr(dma_pool->pool, (unsigned long)start, size))
continue;
- gen_pool_free(pool, (unsigned long)start, size);
+
+ gen_pool_free(dma_pool->pool, (unsigned long)start, size);
return true;
}
return false;
}
+
+struct dma_pool_phys_match {
+ phys_addr_t phys;
+ size_t size;
+ unsigned long addr;
+ bool found;
+};
+
+static void dma_pool_find_phys(struct gen_pool *pool, struct gen_pool_chunk *chunk,
+ void *data)
+{
+ struct dma_pool_phys_match *match = data;
+ phys_addr_t end = match->phys + match->size - 1;
+ phys_addr_t chunk_end;
+
+ if (match->found)
+ return;
+
+ chunk_end = chunk->phys_addr + (chunk->end_addr - chunk->start_addr);
+ if (match->phys < chunk->phys_addr || end > chunk_end)
+ return;
+
+ match->addr = chunk->start_addr + (match->phys - chunk->phys_addr);
+ match->found = true;
+}
+
+static bool dma_free_from_pool_phys(struct dma_gen_pool *dma_pool, phys_addr_t phys,
+ size_t size)
+{
+ struct dma_pool_phys_match match = {
+ .phys = phys,
+ .size = size,
+ };
+
+ gen_pool_for_each_chunk(dma_pool->pool, dma_pool_find_phys, &match);
+ if (!match.found)
+ return false;
+
+ gen_pool_free(dma_pool->pool, match.addr, size);
+ return true;
+}
+
+/*
+ * FIXME: We could avoid this by storing the remapped virtual address in
+ * struct page and using that for lookup.
+ */
+bool dma_free_from_pool_page(struct device *dev, struct page *page, size_t size)
+{
+ struct dma_gen_pool *dma_pool = NULL;
+ phys_addr_t phys = page_to_phys(page);
+
+ if (!IS_ENABLED(CONFIG_DMA_DIRECT_REMAP))
+ return dma_free_from_pool(dev, page_address(page), size);
+
+ while ((dma_pool = dma_guess_pool(dma_pool, 0))) {
+ if (dma_free_from_pool_phys(dma_pool, phys, size))
+ return true;
+ }
+
+ return false;
+}
diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c
index 1abd3e6146f4..aa2f1c4588b9 100644
--- a/kernel/dma/swiotlb.c
+++ b/kernel/dma/swiotlb.c
@@ -180,6 +180,74 @@ static unsigned int limit_nareas(unsigned int nareas, unsigned long nslots)
return nareas;
}
+#ifdef CONFIG_DEBUG_FS
+/*
+ * Track the total used slots with a global atomic value in order to have
+ * correct information to determine the high water mark.
+ */
+static void inc_used_and_hiwater_real(struct io_tlb_mem *mem,
+ unsigned int nslots)
+{
+ unsigned long old_hiwater, new_used;
+
+ new_used = atomic_long_add_return(nslots, &mem->total_used);
+ old_hiwater = atomic_long_read(&mem->used_hiwater);
+ do {
+ if (new_used <= old_hiwater)
+ break;
+ } while (!atomic_long_try_cmpxchg(&mem->used_hiwater,
+ &old_hiwater, new_used));
+}
+
+static void dec_used_real(struct io_tlb_mem *mem, unsigned int nslots)
+{
+ atomic_long_sub(nslots, &mem->total_used);
+}
+
+static void inc_used_and_hiwater_nop(struct io_tlb_mem *mem,
+ unsigned int nslots)
+{
+}
+static void dec_used_nop(struct io_tlb_mem *mem, unsigned int nslots)
+{
+}
+
+DEFINE_STATIC_CALL(swiotlb_inc_used, inc_used_and_hiwater_nop);
+DEFINE_STATIC_CALL(swiotlb_dec_used, dec_used_nop);
+
+static __always_inline void inc_used_and_hiwater(struct io_tlb_mem *mem,
+ unsigned int nslots)
+{
+ static_call(swiotlb_inc_used)(mem, nslots);
+}
+
+static __always_inline void dec_used(struct io_tlb_mem *mem,
+ unsigned int nslots)
+{
+ static_call(swiotlb_dec_used)(mem, nslots);
+}
+
+static bool track_hiwater_enabled __read_mostly;
+
+#else
+
+static __always_inline void inc_used_and_hiwater(struct io_tlb_mem *mem,
+ unsigned int nslots)
+{
+}
+
+static __always_inline void dec_used(struct io_tlb_mem *mem,
+ unsigned int nslots)
+{
+}
+#endif
+
+/*
+ * The tracking of used slots high watermark can be enabled
+ * by appending "track_hiwater" to the swiotlb= boot parameter.
+ * When disabled the tracking functions are no-ops with near-zero
+ * overhead via static_call.
+ */
static int __init
setup_io_tlb_npages(char *str)
{
@@ -194,10 +262,24 @@ setup_io_tlb_npages(char *str)
swiotlb_adjust_nareas(simple_strtoul(str, &str, 0));
if (*str == ',')
++str;
- if (!strcmp(str, "force"))
+ if (!strncmp(str, "force", 5)) {
swiotlb_force_bounce = true;
- else if (!strcmp(str, "noforce"))
+ str += 5;
+ } else if (!strncmp(str, "noforce", 7)) {
swiotlb_force_disable = true;
+ str += 7;
+ }
+
+#ifdef CONFIG_DEBUG_FS
+ if (*str == ',')
+ ++str;
+ if (!strncmp(str, "track_hiwater", 13)) {
+ track_hiwater_enabled = true;
+ static_call_update(swiotlb_inc_used,
+ inc_used_and_hiwater_real);
+ static_call_update(swiotlb_dec_used, dec_used_real);
+ }
+#endif
return 0;
}
@@ -248,6 +330,23 @@ static inline unsigned long nr_slots(u64 val)
return DIV_ROUND_UP(val, IO_TLB_SIZE);
}
+static void swiotlb_mark_pool_used(struct io_tlb_pool *pool)
+{
+ unsigned long i;
+
+ for (i = 0; i < pool->nareas; i++) {
+ pool->areas[i].index = 0;
+ pool->areas[i].used = pool->area_nslabs;
+ }
+
+ for (i = 0; i < pool->nslabs; i++) {
+ pool->slots[i].list = 0;
+ pool->slots[i].orig_addr = INVALID_PHYS_ADDR;
+ pool->slots[i].alloc_size = 0;
+ pool->slots[i].pad_slots = 0;
+ }
+}
+
/*
* Early SWIOTLB allocation may be too early to allow an architecture to
* perform the desired operations. This function allows the architecture to
@@ -259,16 +358,35 @@ void __init swiotlb_update_mem_attributes(void)
struct io_tlb_pool *mem = &io_tlb_default_mem.defpool;
unsigned long bytes;
+ /*
+ * if platform support memory encryption, swiotlb buffers are
+ * shared by default.
+ */
+ if (cc_platform_has(CC_ATTR_MEM_ENCRYPT))
+ io_tlb_default_mem.cc_shared = true;
+ else
+ io_tlb_default_mem.cc_shared = false;
+
if (!mem->nslabs || mem->late_alloc)
return;
bytes = PAGE_ALIGN(mem->nslabs << IO_TLB_SHIFT);
- set_memory_decrypted((unsigned long)mem->vaddr, bytes >> PAGE_SHIFT);
+
+ if (io_tlb_default_mem.cc_shared) {
+ int ret;
+
+ ret = set_memory_decrypted((unsigned long)mem->vaddr,
+ bytes >> PAGE_SHIFT);
+ if (ret) {
+ pr_warn("Failed to decrypt default memory pool, disabling it\n");
+ swiotlb_mark_pool_used(mem);
+ }
+ }
}
static void swiotlb_init_io_tlb_pool(struct io_tlb_pool *mem, phys_addr_t start,
- unsigned long nslabs, bool late_alloc, unsigned int nareas)
+ void *vaddr, unsigned long nslabs, bool late_alloc,
+ unsigned int nareas)
{
- void *vaddr = phys_to_virt(start);
unsigned long bytes = nslabs << IO_TLB_SHIFT, i;
mem->nslabs = nslabs;
@@ -364,8 +482,7 @@ void __init swiotlb_init_remap(bool addressing_limit, unsigned int flags,
if (swiotlb_force_disable)
return;
- io_tlb_default_mem.force_bounce =
- swiotlb_force_bounce || (flags & SWIOTLB_FORCE);
+ io_tlb_default_mem.force_bounce = swiotlb_force_bounce;
#ifdef CONFIG_SWIOTLB_DYNAMIC
if (!remap)
@@ -409,7 +526,7 @@ void __init swiotlb_init_remap(bool addressing_limit, unsigned int flags,
return;
}
- swiotlb_init_io_tlb_pool(mem, __pa(tlb), nslabs, false, nareas);
+ swiotlb_init_io_tlb_pool(mem, __pa(tlb), tlb, nslabs, false, nareas);
add_mem_pool(&io_tlb_default_mem, mem);
if (flags & SWIOTLB_VERBOSE)
@@ -431,9 +548,10 @@ int swiotlb_init_late(size_t size, gfp_t gfp_mask,
{
struct io_tlb_pool *mem = &io_tlb_default_mem.defpool;
unsigned long nslabs = ALIGN(size >> IO_TLB_SHIFT, IO_TLB_SEGSIZE);
+ unsigned int order, area_order, slot_order;
+ bool leak_pages = false;
unsigned int nareas;
unsigned char *vstart = NULL;
- unsigned int order, area_order;
bool retried = false;
int rc = 0;
@@ -493,6 +611,7 @@ retry:
(PAGE_SIZE << order) >> 20);
}
+ rc = -ENOMEM;
nareas = limit_nareas(default_nareas, nslabs);
area_order = get_order(array_size(sizeof(*mem->areas), nareas));
mem->areas = (struct io_tlb_area *)
@@ -500,30 +619,42 @@ retry:
if (!mem->areas)
goto error_area;
+ slot_order = get_order(array_size(sizeof(*mem->slots), nslabs));
mem->slots = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO,
- get_order(array_size(sizeof(*mem->slots), nslabs)));
+ slot_order);
if (!mem->slots)
goto error_slots;
- set_memory_decrypted((unsigned long)vstart,
- (nslabs << IO_TLB_SHIFT) >> PAGE_SHIFT);
- swiotlb_init_io_tlb_pool(mem, virt_to_phys(vstart), nslabs, true,
+ if (io_tlb_default_mem.cc_shared) {
+ rc = set_memory_decrypted((unsigned long)vstart,
+ (nslabs << IO_TLB_SHIFT) >> PAGE_SHIFT);
+ if (rc) {
+ leak_pages = true;
+ goto error_decrypt;
+ }
+ }
+
+ swiotlb_init_io_tlb_pool(mem, virt_to_phys(vstart), vstart, nslabs, true,
nareas);
add_mem_pool(&io_tlb_default_mem, mem);
swiotlb_print_info();
return 0;
+error_decrypt:
+ free_pages((unsigned long)mem->slots, slot_order);
error_slots:
free_pages((unsigned long)mem->areas, area_order);
error_area:
- free_pages((unsigned long)vstart, order);
- return -ENOMEM;
+ if (!leak_pages)
+ free_pages((unsigned long)vstart, order);
+ return rc;
}
void __init swiotlb_exit(void)
{
struct io_tlb_pool *mem = &io_tlb_default_mem.defpool;
+ bool leak_pages = false;
unsigned long tbl_vaddr;
size_t tbl_size, slots_size;
unsigned int area_order;
@@ -539,17 +670,23 @@ void __init swiotlb_exit(void)
tbl_size = PAGE_ALIGN(mem->end - mem->start);
slots_size = PAGE_ALIGN(array_size(sizeof(*mem->slots), mem->nslabs));
- set_memory_encrypted(tbl_vaddr, tbl_size >> PAGE_SHIFT);
+ if (io_tlb_default_mem.cc_shared) {
+ if (set_memory_encrypted(tbl_vaddr, tbl_size >> PAGE_SHIFT))
+ leak_pages = true;
+ }
+
if (mem->late_alloc) {
area_order = get_order(array_size(sizeof(*mem->areas),
mem->nareas));
free_pages((unsigned long)mem->areas, area_order);
- free_pages(tbl_vaddr, get_order(tbl_size));
+ if (!leak_pages)
+ free_pages(tbl_vaddr, get_order(tbl_size));
free_pages((unsigned long)mem->slots, get_order(slots_size));
} else {
memblock_free(mem->areas,
array_size(sizeof(*mem->areas), mem->nareas));
- memblock_phys_free(mem->start, tbl_size);
+ if (!leak_pages)
+ memblock_phys_free(mem->start, tbl_size);
memblock_free(mem->slots, slots_size);
}
@@ -563,6 +700,7 @@ void __init swiotlb_exit(void)
* @gfp: GFP flags for the allocation.
* @bytes: Size of the buffer.
* @phys_limit: Maximum allowed physical address of the buffer.
+ * @attrs: DMA attributes for the allocation.
*
* Allocate pages from the buddy allocator. If successful, make the allocated
* pages decrypted that they can be used for DMA.
@@ -570,9 +708,11 @@ void __init swiotlb_exit(void)
* Return: Decrypted pages, %NULL on allocation failure, or ERR_PTR(-EAGAIN)
* if the allocated physical address was above @phys_limit.
*/
-static struct page *alloc_dma_pages(gfp_t gfp, size_t bytes, u64 phys_limit)
+static struct page *alloc_dma_pages(gfp_t gfp, size_t bytes,
+ u64 phys_limit, unsigned long attrs)
{
unsigned int order = get_order(bytes);
+ bool cc_shared = attrs & __DMA_ATTR_ALLOC_CC_SHARED;
struct page *page;
phys_addr_t paddr;
void *vaddr;
@@ -588,13 +728,13 @@ static struct page *alloc_dma_pages(gfp_t gfp, size_t bytes, u64 phys_limit)
}
vaddr = phys_to_virt(paddr);
- if (set_memory_decrypted((unsigned long)vaddr, PFN_UP(bytes)))
+ if (cc_shared && set_memory_decrypted((unsigned long)vaddr, PFN_UP(bytes)))
goto error;
return page;
error:
/* Intentional leak if pages cannot be encrypted again. */
- if (!set_memory_encrypted((unsigned long)vaddr, PFN_UP(bytes)))
+ if (cc_shared && !set_memory_encrypted((unsigned long)vaddr, PFN_UP(bytes)))
__free_pages(page, order);
return NULL;
}
@@ -602,29 +742,33 @@ error:
/**
* swiotlb_alloc_tlb() - allocate a dynamic IO TLB buffer
* @dev: Device for which a memory pool is allocated.
+ * @mem: SWIOTLB allocator for the pool.
* @bytes: Size of the buffer.
* @phys_limit: Maximum allowed physical address of the buffer.
* @gfp: GFP flags for the allocation.
+ * @vaddr: Receives the virtual address for the allocated buffer.
*
* Return: Allocated pages, or %NULL on allocation failure.
*/
-static struct page *swiotlb_alloc_tlb(struct device *dev, size_t bytes,
- u64 phys_limit, gfp_t gfp)
+static struct page *swiotlb_alloc_tlb(struct device *dev,
+ struct io_tlb_mem *mem, size_t bytes,
+ u64 phys_limit, gfp_t gfp, void **vaddr)
{
struct page *page;
+ unsigned long attrs = mem->cc_shared ? __DMA_ATTR_ALLOC_CC_SHARED : 0;
+ *vaddr = NULL;
/*
* Allocate from the atomic pools if memory is encrypted and
* the allocation is atomic, because decrypting may block.
*/
- if (!gfpflags_allow_blocking(gfp) && dev && force_dma_unencrypted(dev)) {
- void *vaddr;
+ if (!gfpflags_allow_blocking(gfp) && dev && mem->cc_shared) {
if (!IS_ENABLED(CONFIG_DMA_COHERENT_POOL))
return NULL;
- return dma_alloc_from_pool(dev, bytes, &vaddr, gfp,
- dma_coherent_ok);
+ return dma_alloc_from_pool(dev, bytes, vaddr, gfp,
+ attrs, dma_coherent_ok);
}
gfp &= ~GFP_ZONEMASK;
@@ -633,7 +777,7 @@ static struct page *swiotlb_alloc_tlb(struct device *dev, size_t bytes,
else if (phys_limit <= DMA_BIT_MASK(32))
gfp |= __GFP_DMA32;
- while (IS_ERR(page = alloc_dma_pages(gfp, bytes, phys_limit))) {
+ while (IS_ERR(page = alloc_dma_pages(gfp, bytes, phys_limit, attrs))) {
if (IS_ENABLED(CONFIG_ZONE_DMA32) &&
phys_limit < DMA_BIT_MASK(64) &&
!(gfp & (__GFP_DMA32 | __GFP_DMA)))
@@ -645,6 +789,8 @@ static struct page *swiotlb_alloc_tlb(struct device *dev, size_t bytes,
return NULL;
}
+ if (page)
+ *vaddr = phys_to_virt(page_to_phys(page));
return page;
}
@@ -652,21 +798,25 @@ static struct page *swiotlb_alloc_tlb(struct device *dev, size_t bytes,
* swiotlb_free_tlb() - free a dynamically allocated IO TLB buffer
* @vaddr: Virtual address of the buffer.
* @bytes: Size of the buffer.
+ * @cc_shared: true if @vaddr was allocated decrypted and must be
+ * re-encrypted before being freed
*/
-static void swiotlb_free_tlb(void *vaddr, size_t bytes)
+static void swiotlb_free_tlb(void *vaddr, size_t bytes, bool cc_shared)
{
if (IS_ENABLED(CONFIG_DMA_COHERENT_POOL) &&
dma_free_from_pool(NULL, vaddr, bytes))
return;
/* Intentional leak if pages cannot be encrypted again. */
- if (!set_memory_encrypted((unsigned long)vaddr, PFN_UP(bytes)))
+ if (!cc_shared ||
+ !set_memory_encrypted((unsigned long)vaddr, PFN_UP(bytes)))
__free_pages(virt_to_page(vaddr), get_order(bytes));
}
/**
* swiotlb_alloc_pool() - allocate a new IO TLB memory pool
* @dev: Device for which a memory pool is allocated.
+ * @mem: SWIOTLB allocator for the pool.
* @minslabs: Minimum number of slabs.
* @nslabs: Desired (maximum) number of slabs.
* @nareas: Number of areas.
@@ -680,11 +830,13 @@ static void swiotlb_free_tlb(void *vaddr, size_t bytes)
* Return: New memory pool, or %NULL on allocation failure.
*/
static struct io_tlb_pool *swiotlb_alloc_pool(struct device *dev,
- unsigned long minslabs, unsigned long nslabs,
- unsigned int nareas, u64 phys_limit, gfp_t gfp)
+ struct io_tlb_mem *mem, unsigned long minslabs,
+ unsigned long nslabs, unsigned int nareas, u64 phys_limit,
+ gfp_t gfp)
{
struct io_tlb_pool *pool;
unsigned int slot_order;
+ void *tlb_vaddr;
struct page *tlb;
size_t pool_size;
size_t tlb_size;
@@ -699,9 +851,11 @@ static struct io_tlb_pool *swiotlb_alloc_pool(struct device *dev,
if (!pool)
goto error;
pool->areas = (void *)pool + sizeof(*pool);
+ pool->cc_shared = mem->cc_shared;
tlb_size = nslabs << IO_TLB_SHIFT;
- while (!(tlb = swiotlb_alloc_tlb(dev, tlb_size, phys_limit, gfp))) {
+ while (!(tlb = swiotlb_alloc_tlb(dev, mem, tlb_size,
+ phys_limit, gfp, &tlb_vaddr))) {
if (nslabs <= minslabs)
goto error_tlb;
nslabs = ALIGN(nslabs >> 1, IO_TLB_SEGSIZE);
@@ -715,11 +869,12 @@ static struct io_tlb_pool *swiotlb_alloc_pool(struct device *dev,
if (!pool->slots)
goto error_slots;
- swiotlb_init_io_tlb_pool(pool, page_to_phys(tlb), nslabs, true, nareas);
+ swiotlb_init_io_tlb_pool(pool, page_to_phys(tlb), tlb_vaddr, nslabs,
+ true, nareas);
return pool;
error_slots:
- swiotlb_free_tlb(page_address(tlb), tlb_size);
+ swiotlb_free_tlb(tlb_vaddr, tlb_size, mem->cc_shared);
error_tlb:
kfree(pool);
error:
@@ -736,7 +891,7 @@ static void swiotlb_dyn_alloc(struct work_struct *work)
container_of(work, struct io_tlb_mem, dyn_alloc);
struct io_tlb_pool *pool;
- pool = swiotlb_alloc_pool(NULL, IO_TLB_MIN_SLABS, default_nslabs,
+ pool = swiotlb_alloc_pool(NULL, mem, IO_TLB_MIN_SLABS, default_nslabs,
default_nareas, mem->phys_limit, GFP_KERNEL);
if (!pool) {
pr_warn_ratelimited("Failed to allocate new pool");
@@ -746,21 +901,24 @@ static void swiotlb_dyn_alloc(struct work_struct *work)
add_mem_pool(mem, pool);
}
-/**
- * swiotlb_dyn_free() - RCU callback to free a memory pool
- * @rcu: RCU head in the corresponding struct io_tlb_pool.
- */
-static void swiotlb_dyn_free(struct rcu_head *rcu)
+static void swiotlb_dyn_free_work(struct work_struct *work)
{
- struct io_tlb_pool *pool = container_of(rcu, struct io_tlb_pool, rcu);
+ struct io_tlb_pool *pool =
+ container_of(to_rcu_work(work), struct io_tlb_pool, dyn_free);
size_t slots_size = array_size(sizeof(*pool->slots), pool->nslabs);
size_t tlb_size = pool->end - pool->start;
free_pages((unsigned long)pool->slots, get_order(slots_size));
- swiotlb_free_tlb(pool->vaddr, tlb_size);
+ swiotlb_free_tlb(pool->vaddr, tlb_size, pool->cc_shared);
kfree(pool);
}
+static void swiotlb_schedule_dyn_free(struct io_tlb_pool *pool)
+{
+ INIT_RCU_WORK(&pool->dyn_free, swiotlb_dyn_free_work);
+ queue_rcu_work(system_wq, &pool->dyn_free);
+}
+
/**
* __swiotlb_find_pool() - find the IO TLB pool for a physical address
* @dev: Device which has mapped the DMA buffer.
@@ -807,7 +965,7 @@ static void swiotlb_del_pool(struct device *dev, struct io_tlb_pool *pool)
list_del_rcu(&pool->node);
spin_unlock_irqrestore(&dev->dma_io_tlb_lock, flags);
- call_rcu(&pool->rcu, swiotlb_dyn_free);
+ swiotlb_schedule_dyn_free(pool);
}
#endif /* CONFIG_SWIOTLB_DYNAMIC */
@@ -861,7 +1019,6 @@ static void swiotlb_bounce(struct device *dev, phys_addr_t tlb_addr, size_t size
int index = (tlb_addr - mem->start) >> IO_TLB_SHIFT;
phys_addr_t orig_addr = mem->slots[index].orig_addr;
size_t alloc_size = mem->slots[index].alloc_size;
- unsigned long pfn = PFN_DOWN(orig_addr);
unsigned char *vaddr = mem->vaddr + tlb_addr - mem->start;
int tlb_offset;
@@ -894,7 +1051,8 @@ static void swiotlb_bounce(struct device *dev, phys_addr_t tlb_addr, size_t size
size = alloc_size;
}
- if (PageHighMem(pfn_to_page(pfn))) {
+ if (PhysHighMem(orig_addr)) {
+ unsigned long pfn = PFN_DOWN(orig_addr);
unsigned int offset = orig_addr & ~PAGE_MASK;
struct page *page;
unsigned int sz = 0;
@@ -959,40 +1117,6 @@ static unsigned int wrap_area_index(struct io_tlb_pool *mem, unsigned int index)
return index;
}
-/*
- * Track the total used slots with a global atomic value in order to have
- * correct information to determine the high water mark. The mem_used()
- * function gives imprecise results because there's no locking across
- * multiple areas.
- */
-#ifdef CONFIG_DEBUG_FS
-static void inc_used_and_hiwater(struct io_tlb_mem *mem, unsigned int nslots)
-{
- unsigned long old_hiwater, new_used;
-
- new_used = atomic_long_add_return(nslots, &mem->total_used);
- old_hiwater = atomic_long_read(&mem->used_hiwater);
- do {
- if (new_used <= old_hiwater)
- break;
- } while (!atomic_long_try_cmpxchg(&mem->used_hiwater,
- &old_hiwater, new_used));
-}
-
-static void dec_used(struct io_tlb_mem *mem, unsigned int nslots)
-{
- atomic_long_sub(nslots, &mem->total_used);
-}
-
-#else /* !CONFIG_DEBUG_FS */
-static void inc_used_and_hiwater(struct io_tlb_mem *mem, unsigned int nslots)
-{
-}
-static void dec_used(struct io_tlb_mem *mem, unsigned int nslots)
-{
-}
-#endif /* CONFIG_DEBUG_FS */
-
#ifdef CONFIG_SWIOTLB_DYNAMIC
#ifdef CONFIG_DEBUG_FS
static void inc_transient_used(struct io_tlb_mem *mem, unsigned int nslots)
@@ -1021,6 +1145,7 @@ static void dec_transient_used(struct io_tlb_mem *mem, unsigned int nslots)
* @pool: Memory pool to be searched.
* @area_index: Index of the IO TLB memory area to be searched.
* @orig_addr: Original (non-bounced) IO buffer address.
+ * @tbl_dma_addr: DMA address of the bounce buffer.
* @alloc_size: Total requested size of the bounce buffer,
* including initial alignment padding.
* @alloc_align_mask: Required alignment of the allocated buffer.
@@ -1032,13 +1157,11 @@ static void dec_transient_used(struct io_tlb_mem *mem, unsigned int nslots)
* Return: Index of the first allocated slot, or -1 on error.
*/
static int swiotlb_search_pool_area(struct device *dev, struct io_tlb_pool *pool,
- int area_index, phys_addr_t orig_addr, size_t alloc_size,
- unsigned int alloc_align_mask)
+ int area_index, phys_addr_t orig_addr, dma_addr_t tbl_dma_addr,
+ size_t alloc_size, unsigned int alloc_align_mask)
{
struct io_tlb_area *area = pool->areas + area_index;
unsigned long boundary_mask = dma_get_seg_boundary(dev);
- dma_addr_t tbl_dma_addr =
- phys_to_dma_unencrypted(dev, pool->start) & boundary_mask;
unsigned long max_slots = get_max_slots(boundary_mask);
unsigned int iotlb_align_mask = dma_get_min_align_mask(dev);
unsigned int nslots = nr_slots(alloc_size), stride;
@@ -1051,6 +1174,8 @@ static int swiotlb_search_pool_area(struct device *dev, struct io_tlb_pool *pool
BUG_ON(!nslots);
BUG_ON(area_index >= pool->nareas);
+ tbl_dma_addr &= boundary_mask;
+
/*
* Historically, swiotlb allocations >= PAGE_SIZE were guaranteed to be
* page-aligned in the absence of any other alignment requirements.
@@ -1162,6 +1287,7 @@ static int swiotlb_search_area(struct device *dev, int start_cpu,
{
struct io_tlb_mem *mem = dev->dma_io_tlb_mem;
struct io_tlb_pool *pool;
+ dma_addr_t tbl_dma_addr;
int area_index;
int index = -1;
@@ -1170,9 +1296,15 @@ static int swiotlb_search_area(struct device *dev, int start_cpu,
if (cpu_offset >= pool->nareas)
continue;
area_index = (start_cpu + cpu_offset) & (pool->nareas - 1);
+
+ if (mem->cc_shared)
+ tbl_dma_addr = phys_to_dma_unencrypted(dev, pool->start);
+ else
+ tbl_dma_addr = phys_to_dma_encrypted(dev, pool->start);
+
index = swiotlb_search_pool_area(dev, pool, area_index,
- orig_addr, alloc_size,
- alloc_align_mask);
+ orig_addr, tbl_dma_addr,
+ alloc_size, alloc_align_mask);
if (index >= 0) {
*retpool = pool;
break;
@@ -1202,6 +1334,7 @@ static int swiotlb_find_slots(struct device *dev, phys_addr_t orig_addr,
{
struct io_tlb_mem *mem = dev->dma_io_tlb_mem;
struct io_tlb_pool *pool;
+ dma_addr_t tbl_dma_addr;
unsigned long nslabs;
unsigned long flags;
u64 phys_limit;
@@ -1226,15 +1359,20 @@ static int swiotlb_find_slots(struct device *dev, phys_addr_t orig_addr,
nslabs = nr_slots(alloc_size);
phys_limit = min_not_zero(*dev->dma_mask, dev->bus_dma_limit);
- pool = swiotlb_alloc_pool(dev, nslabs, nslabs, 1, phys_limit,
+ pool = swiotlb_alloc_pool(dev, mem, nslabs, nslabs, 1, phys_limit,
GFP_NOWAIT);
if (!pool)
return -1;
- index = swiotlb_search_pool_area(dev, pool, 0, orig_addr,
+ if (mem->cc_shared)
+ tbl_dma_addr = phys_to_dma_unencrypted(dev, pool->start);
+ else
+ tbl_dma_addr = phys_to_dma_encrypted(dev, pool->start);
+
+ index = swiotlb_search_pool_area(dev, pool, 0, orig_addr, tbl_dma_addr,
alloc_size, alloc_align_mask);
if (index < 0) {
- swiotlb_dyn_free(&pool->rcu);
+ swiotlb_schedule_dyn_free(pool);
return -1;
}
@@ -1276,15 +1414,23 @@ static int swiotlb_find_slots(struct device *dev, phys_addr_t orig_addr,
size_t alloc_size, unsigned int alloc_align_mask,
struct io_tlb_pool **retpool)
{
+ struct io_tlb_mem *mem = dev->dma_io_tlb_mem;
struct io_tlb_pool *pool;
+ dma_addr_t tbl_dma_addr;
int start, i;
int index;
- *retpool = pool = &dev->dma_io_tlb_mem->defpool;
+ *retpool = pool = &mem->defpool;
+ if (mem->cc_shared)
+ tbl_dma_addr = phys_to_dma_unencrypted(dev, pool->start);
+ else
+ tbl_dma_addr = phys_to_dma_encrypted(dev, pool->start);
+
i = start = raw_smp_processor_id() & (pool->nareas - 1);
do {
index = swiotlb_search_pool_area(dev, pool, i, orig_addr,
- alloc_size, alloc_align_mask);
+ tbl_dma_addr, alloc_size,
+ alloc_align_mask);
if (index >= 0)
return index;
if (++i >= pool->nareas)
@@ -1295,24 +1441,6 @@ static int swiotlb_find_slots(struct device *dev, phys_addr_t orig_addr,
#endif /* CONFIG_SWIOTLB_DYNAMIC */
-#ifdef CONFIG_DEBUG_FS
-
-/**
- * mem_used() - get number of used slots in an allocator
- * @mem: Software IO TLB allocator.
- *
- * The result is accurate in this version of the function, because an atomic
- * counter is available if CONFIG_DEBUG_FS is set.
- *
- * Return: Number of used slots.
- */
-static unsigned long mem_used(struct io_tlb_mem *mem)
-{
- return atomic_long_read(&mem->total_used);
-}
-
-#else /* !CONFIG_DEBUG_FS */
-
/**
* mem_pool_used() - get number of used slots in a memory pool
* @pool: Software IO TLB memory pool.
@@ -1335,13 +1463,20 @@ static unsigned long mem_pool_used(struct io_tlb_pool *pool)
* mem_used() - get number of used slots in an allocator
* @mem: Software IO TLB allocator.
*
- * The result is not accurate, because there is no locking of individual
- * areas.
+ * When trace_hiwater and CONFIG_DEBUG_FS is enabled, the result is accurate
+ * because the total number of used slots is tracked in mem->total_used.
+ * Otherwise, the result is an approximation, because there is no locking of
+ * individual areas.
*
- * Return: Approximate number of used slots.
+ * Return: Number of used slots.
*/
static unsigned long mem_used(struct io_tlb_mem *mem)
{
+#ifdef CONFIG_DEBUG_FS
+ if (track_hiwater_enabled)
+ return atomic_long_read(&mem->total_used);
+#endif
+
#ifdef CONFIG_SWIOTLB_DYNAMIC
struct io_tlb_pool *pool;
unsigned long used = 0;
@@ -1357,8 +1492,6 @@ static unsigned long mem_used(struct io_tlb_mem *mem)
#endif
}
-#endif /* CONFIG_DEBUG_FS */
-
/**
* swiotlb_tbl_map_single() - bounce buffer map a single contiguous physical area
* @dev: Device which maps the buffer.
@@ -1367,9 +1500,19 @@ static unsigned long mem_used(struct io_tlb_mem *mem)
* any pre- or post-padding for alignment
* @alloc_align_mask: Required start and end alignment of the allocated buffer
* @dir: DMA direction
- * @attrs: Optional DMA attributes for the map operation
+ * @attrs: Optional DMA attributes for the map operation, updated
+ * to match the selected SWIOTLB pool
*
* Find and allocate a suitable sequence of IO TLB slots for the request.
+ * The device's SWIOTLB pool must match the device's current DMA encryption
+ * requirements. If the device requires decrypted DMA, bouncing is done through
+ * an unencrypted pool and the mapping is marked shared. If the device can DMA
+ * to encrypted memory, bouncing is done through an encrypted pool even when the
+ * original DMA address was unencrypted. Enabling encrypted DMA for a device is
+ * therefore expected to update its default io_tlb_mem to an encrypted pool, so
+ * later bounce mappings for both encrypted and decrypted original memory use
+ * that encrypted pool.
+ *
* The allocated space starts at an alignment specified by alloc_align_mask,
* and the size of the allocated space is rounded up so that the total amount
* of allocated space is a multiple of (alloc_align_mask + 1). If
@@ -1386,7 +1529,7 @@ static unsigned long mem_used(struct io_tlb_mem *mem)
*/
phys_addr_t swiotlb_tbl_map_single(struct device *dev, phys_addr_t orig_addr,
size_t mapping_size, unsigned int alloc_align_mask,
- enum dma_data_direction dir, unsigned long attrs)
+ enum dma_data_direction dir, unsigned long *attrs)
{
struct io_tlb_mem *mem = dev->dma_io_tlb_mem;
unsigned int offset;
@@ -1406,6 +1549,30 @@ phys_addr_t swiotlb_tbl_map_single(struct device *dev, phys_addr_t orig_addr,
if (cc_platform_has(CC_ATTR_MEM_ENCRYPT))
pr_warn_once("Memory encryption is active and system is using DMA bounce buffers\n");
+ if (cc_platform_has(CC_ATTR_GUEST_MEM_ENCRYPT)) {
+
+ /* swiotlb pool is incorrect for this device */
+ if (unlikely(mem->cc_shared != force_dma_unencrypted(dev)))
+ return (phys_addr_t)DMA_MAPPING_ERROR;
+
+ } else if (cc_platform_has(CC_ATTR_HOST_MEM_ENCRYPT)) {
+ /*
+ * On hosts with memory encryption, SWIOTLB-backed memory is
+ * unencrypted. DMA addresses returned for bounce buffers must
+ * therefore be marked unencrypted, even for devices that can
+ * address encrypted memory. This also preserves swiotlb=force
+ * behavior for those devices.
+ */
+ if (unlikely(!mem->cc_shared))
+ return (phys_addr_t)DMA_MAPPING_ERROR;
+ }
+
+ /* Force attrs to match the kind of memory in the pool */
+ if (mem->cc_shared)
+ *attrs |= DMA_ATTR_CC_SHARED;
+ else
+ *attrs &= ~DMA_ATTR_CC_SHARED;
+
/*
* The default swiotlb memory pool is allocated with PAGE_SIZE
* alignment. If a mapping is requested with larger alignment,
@@ -1420,7 +1587,7 @@ phys_addr_t swiotlb_tbl_map_single(struct device *dev, phys_addr_t orig_addr,
size = ALIGN(mapping_size + offset, alloc_align_mask + 1);
index = swiotlb_find_slots(dev, orig_addr, size, alloc_align_mask, &pool);
if (index == -1) {
- if (!(attrs & DMA_ATTR_NO_WARN))
+ if (!(*attrs & DMA_ATTR_NO_WARN))
dev_warn_ratelimited(dev,
"swiotlb buffer is full (sz: %zd bytes), total %lu (slots), used %lu (slots)\n",
size, mem->nslabs, mem_used(mem));
@@ -1599,13 +1766,16 @@ dma_addr_t swiotlb_map(struct device *dev, phys_addr_t paddr, size_t size,
trace_swiotlb_bounced(dev, phys_to_dma(dev, paddr), size);
- swiotlb_addr = swiotlb_tbl_map_single(dev, paddr, size, 0, dir, attrs);
+ swiotlb_addr = swiotlb_tbl_map_single(dev, paddr, size, 0, dir, &attrs);
if (swiotlb_addr == (phys_addr_t)DMA_MAPPING_ERROR)
return DMA_MAPPING_ERROR;
- /* Ensure that the address returned is DMA'ble */
- dma_addr = phys_to_dma_unencrypted(dev, swiotlb_addr);
- if (unlikely(!dma_capable(dev, dma_addr, size, true))) {
+ if (attrs & DMA_ATTR_CC_SHARED)
+ dma_addr = phys_to_dma_unencrypted(dev, swiotlb_addr);
+ else
+ dma_addr = phys_to_dma_encrypted(dev, swiotlb_addr);
+
+ if (unlikely(!dma_capable(dev, dma_addr, size, true, attrs))) {
__swiotlb_tbl_unmap_single(dev, swiotlb_addr, size, dir,
attrs | DMA_ATTR_SKIP_CPU_SYNC,
swiotlb_find_pool(dev, swiotlb_addr));
@@ -1768,7 +1938,7 @@ static inline void swiotlb_create_debugfs_files(struct io_tlb_mem *mem,
#ifdef CONFIG_DMA_RESTRICTED_POOL
-struct page *swiotlb_alloc(struct device *dev, size_t size)
+struct page *swiotlb_alloc(struct device *dev, size_t size, unsigned long attrs)
{
struct io_tlb_mem *mem = dev->dma_io_tlb_mem;
struct io_tlb_pool *pool;
@@ -1779,6 +1949,9 @@ struct page *swiotlb_alloc(struct device *dev, size_t size)
if (!mem)
return NULL;
+ if (mem->cc_shared != !!(attrs & __DMA_ATTR_ALLOC_CC_SHARED))
+ return NULL;
+
align = (1 << (get_order(size) + PAGE_SHIFT)) - 1;
index = swiotlb_find_slots(dev, 0, size, align, &pool);
if (index == -1)
@@ -1809,6 +1982,12 @@ bool swiotlb_free(struct device *dev, struct page *page, size_t size)
return true;
}
+void swiotlb_free_from_pool(struct device *dev,
+ phys_addr_t tlb_addr, struct io_tlb_pool *pool)
+{
+ swiotlb_release_slots(dev, tlb_addr, pool);
+}
+
static int rmem_swiotlb_device_init(struct reserved_mem *rmem,
struct device *dev)
{
@@ -1848,11 +2027,29 @@ static int rmem_swiotlb_device_init(struct reserved_mem *rmem,
kfree(mem);
return -ENOMEM;
}
+ /*
+ * if platform supports memory encryption,
+ * restricted mem pool is shared by default
+ */
+ if (cc_platform_has(CC_ATTR_MEM_ENCRYPT)) {
+ int ret;
+
+ mem->cc_shared = true;
+ ret = set_memory_decrypted((unsigned long)phys_to_virt(rmem->base),
+ rmem->size >> PAGE_SHIFT);
+ if (ret) {
+ dev_err(dev, "Failed to decrypt restricted DMA pool\n");
+ kfree(pool->areas);
+ kfree(pool->slots);
+ kfree(mem);
+ return ret;
+ }
+ } else {
+ mem->cc_shared = false;
+ }
- set_memory_decrypted((unsigned long)phys_to_virt(rmem->base),
- rmem->size >> PAGE_SHIFT);
- swiotlb_init_io_tlb_pool(pool, rmem->base, nslabs,
- false, nareas);
+ swiotlb_init_io_tlb_pool(pool, rmem->base, phys_to_virt(rmem->base),
+ nslabs, false, nareas);
mem->force_bounce = true;
mem->for_alloc = true;
#ifdef CONFIG_SWIOTLB_DYNAMIC
diff --git a/kernel/entry/Makefile b/kernel/entry/Makefile
index 2333d70802e4..f220bae86b12 100644
--- a/kernel/entry/Makefile
+++ b/kernel/entry/Makefile
@@ -13,5 +13,6 @@ CFLAGS_REMOVE_common.o = -fstack-protector -fstack-protector-strong
CFLAGS_common.o += -fno-stack-protector
obj-$(CONFIG_GENERIC_IRQ_ENTRY) += common.o
-obj-$(CONFIG_GENERIC_SYSCALL) += syscall-common.o syscall_user_dispatch.o
+obj-$(CONFIG_GENERIC_SYSCALL) += syscall-common.o
+obj-$(CONFIG_SYSCALL_USER_DISPATCH) += syscall_user_dispatch.o
obj-$(CONFIG_VIRT_XFER_TO_GUEST_WORK) += virt.o
diff --git a/kernel/entry/syscall-common.c b/kernel/entry/syscall-common.c
index cd4967a9c53e..b8eac9efb6fd 100644
--- a/kernel/entry/syscall-common.c
+++ b/kernel/entry/syscall-common.c
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
+#include <linux/audit.h>
#include <linux/entry-common.h>
#define CREATE_TRACE_POINTS
@@ -7,17 +8,23 @@
/* Out of line to prevent tracepoint code duplication */
-long trace_syscall_enter(struct pt_regs *regs, long syscall)
+void trace_syscall_enter(struct pt_regs *regs)
{
- trace_sys_enter(regs, syscall);
- /*
- * Probes or BPF hooks in the tracepoint may have changed the
- * system call number. Reread it.
- */
- return syscall_get_nr(current, regs);
+ trace_sys_enter(regs, syscall_get_nr(current, regs));
}
void trace_syscall_exit(struct pt_regs *regs, long ret)
{
trace_sys_exit(regs, ret);
}
+
+#ifdef CONFIG_AUDITSYSCALL
+void syscall_enter_audit(struct pt_regs *regs)
+{
+ long syscall = syscall_get_nr(current, regs);
+ unsigned long args[6];
+
+ syscall_get_arguments(current, regs, args);
+ __audit_syscall_entry(syscall, args[0], args[1], args[2], args[3]);
+}
+#endif
diff --git a/kernel/entry/syscall_user_dispatch.c b/kernel/entry/syscall_user_dispatch.c
index d89dffcc2d64..59c861866941 100644
--- a/kernel/entry/syscall_user_dispatch.c
+++ b/kernel/entry/syscall_user_dispatch.c
@@ -2,21 +2,22 @@
/*
* Copyright (C) 2020 Collabora Ltd.
*/
-
+#include <linux/elf.h>
#include <linux/entry-common.h>
-#include <linux/sched.h>
#include <linux/prctl.h>
#include <linux/ptrace.h>
-#include <linux/syscall_user_dispatch.h>
-#include <linux/uaccess.h>
-#include <linux/signal.h>
-#include <linux/elf.h>
-
+#include <linux/sched.h>
#include <linux/sched/signal.h>
#include <linux/sched/task_stack.h>
+#include <linux/signal.h>
+#include <linux/syscall_user_dispatch.h>
+#include <linux/sysctl.h>
+#include <linux/uaccess.h>
#include <asm/syscall.h>
+static bool syscall_user_dispatch_allowed __read_mostly = true;
+
static void trigger_sigsys(struct pt_regs *regs)
{
struct kernel_siginfo info;
@@ -102,6 +103,10 @@ static int task_set_syscall_user_dispatch(struct task_struct *task, unsigned lon
return -EINVAL;
}
+ /* Arming can be denied at runtime via sysctl, disarming is allowed */
+ if (mode != PR_SYS_DISPATCH_OFF && !syscall_user_dispatch_allowed)
+ return -EPERM;
+
/*
* access_ok() will clear memory tags for tagged addresses
* if current has memory tagging enabled.
@@ -172,3 +177,22 @@ int syscall_user_dispatch_set_config(struct task_struct *task, unsigned long siz
return task_set_syscall_user_dispatch(task, cfg.mode, cfg.offset, cfg.len,
(char __user *)(uintptr_t)cfg.selector);
}
+
+#ifdef CONFIG_SYSCTL
+static const struct ctl_table syscall_user_dispatch_sysctls[] = {
+ {
+ .procname = "syscall_user_dispatch",
+ .data = &syscall_user_dispatch_allowed,
+ .maxlen = sizeof(syscall_user_dispatch_allowed),
+ .mode = 0644,
+ .proc_handler = proc_dobool,
+ },
+};
+
+static int __init syscall_user_dispatch_sysctl_init(void)
+{
+ register_sysctl_init("kernel", syscall_user_dispatch_sysctls);
+ return 0;
+}
+late_initcall(syscall_user_dispatch_sysctl_init);
+#endif /* CONFIG_SYSCTL */
diff --git a/kernel/events/core.c b/kernel/events/core.c
index 954c36e28101..7846d70be57f 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -2343,6 +2343,34 @@ static inline struct list_head *get_event_list(struct perf_event *event)
&event->pmu_ctx->flexible_active;
}
+/* @sibling must already be unlinked from its old leader's sibling_list. */
+static void perf_promote_sibling_to_leader(struct perf_event *sibling,
+ struct perf_event_context *ctx,
+ int group_caps)
+{
+ /*
+ * Events that have PERF_EV_CAP_SIBLING require being part of
+ * a group and cannot exist on their own, schedule them out
+ * and move them into the ERROR state. Also see
+ * _perf_event_enable(), it will not be able to recover this
+ * ERROR state.
+ */
+ if (sibling->event_caps & PERF_EV_CAP_SIBLING)
+ __event_disable(sibling, ctx, PERF_EVENT_STATE_ERROR);
+
+ sibling->group_leader = sibling;
+ sibling->group_caps = group_caps;
+
+ if (sibling->attach_state & PERF_ATTACH_CONTEXT) {
+ add_event_to_groups(sibling, ctx);
+
+ if (sibling->state == PERF_EVENT_STATE_ACTIVE)
+ list_add_tail(&sibling->active_list, get_event_list(sibling));
+ }
+
+ perf_event__header_size(sibling);
+}
+
static void perf_group_detach(struct perf_event *event)
{
struct perf_event *leader = event->group_leader;
@@ -2366,8 +2394,9 @@ static void perf_group_detach(struct perf_event *event)
*/
if (leader != event) {
list_del_init(&event->sibling_list);
- event->group_leader->nr_siblings--;
- event->group_leader->group_generation++;
+ leader->nr_siblings--;
+ leader->group_generation++;
+ perf_promote_sibling_to_leader(event, ctx, event->event_caps);
goto out;
}
@@ -2377,32 +2406,14 @@ static void perf_group_detach(struct perf_event *event)
* to whatever list we are on.
*/
list_for_each_entry_safe(sibling, tmp, &event->sibling_list, sibling_list) {
-
- /*
- * Events that have PERF_EV_CAP_SIBLING require being part of
- * a group and cannot exist on their own, schedule them out
- * and move them into the ERROR state. Also see
- * _perf_event_enable(), it will not be able to recover this
- * ERROR state.
- */
- if (sibling->event_caps & PERF_EV_CAP_SIBLING)
- __event_disable(sibling, ctx, PERF_EVENT_STATE_ERROR);
-
- sibling->group_leader = sibling;
list_del_init(&sibling->sibling_list);
/* Inherit group flags from the previous leader */
- sibling->group_caps = event->group_caps;
-
- if (sibling->attach_state & PERF_ATTACH_CONTEXT) {
- add_event_to_groups(sibling, event->ctx);
-
- if (sibling->state == PERF_EVENT_STATE_ACTIVE)
- list_add_tail(&sibling->active_list, get_event_list(sibling));
- }
+ perf_promote_sibling_to_leader(sibling, ctx, event->group_caps);
WARN_ON_ONCE(sibling->ctx != event->ctx);
}
+ event->nr_siblings = 0;
out:
for_each_sibling_event(tmp, leader)
@@ -2592,12 +2603,7 @@ __perf_remove_from_context(struct perf_event *event,
if (flags & DETACH_DEAD)
state = PERF_EVENT_STATE_DEAD;
- event_sched_out(event, ctx);
-
- if (event->state > PERF_EVENT_STATE_OFF)
- perf_cgroup_event_disable(event, ctx);
-
- perf_event_set_state(event, min(event->state, state));
+ __event_disable(event, ctx, state);
if (flags & DETACH_GROUP)
perf_group_detach(event);
@@ -2666,8 +2672,9 @@ static void __event_disable(struct perf_event *event,
enum perf_event_state state)
{
event_sched_out(event, ctx);
- perf_cgroup_event_disable(event, ctx);
- perf_event_set_state(event, state);
+ if (event->state > PERF_EVENT_STATE_OFF)
+ perf_cgroup_event_disable(event, ctx);
+ perf_event_set_state(event, min(event->state, state));
}
/*
@@ -3757,6 +3764,9 @@ static void perf_ctx_sched_task_cb(struct perf_event_context *ctx,
list_for_each_entry(pmu_ctx, &ctx->pmu_ctx_list, pmu_ctx_entry) {
cpc = this_cpc(pmu_ctx->pmu);
+ if (cpc->task_epc != pmu_ctx)
+ continue;
+
if (cpc->sched_cb_usage && pmu_ctx->pmu->sched_task)
pmu_ctx->pmu->sched_task(pmu_ctx, task, sched_in);
}
@@ -3907,7 +3917,7 @@ static void __perf_pmu_sched_task(struct perf_cpu_pmu_context *cpc,
perf_ctx_lock(cpuctx, cpuctx->task_ctx);
perf_pmu_disable(pmu);
- pmu->sched_task(cpc->task_epc, task, sched_in);
+ pmu->sched_task(&cpc->epc, task, sched_in);
perf_pmu_enable(pmu);
perf_ctx_unlock(cpuctx, cpuctx->task_ctx);
@@ -3917,15 +3927,17 @@ static void perf_pmu_sched_task(struct task_struct *prev,
struct task_struct *next,
bool sched_in)
{
- struct perf_cpu_context *cpuctx = this_cpu_ptr(&perf_cpu_context);
- struct perf_cpu_pmu_context *cpc;
+ struct perf_cpu_pmu_context *cpc, *cpc2;
- /* cpuctx->task_ctx will be handled in perf_event_context_sched_in/out */
- if (prev == next || cpuctx->task_ctx)
+ if (prev == next)
return;
- list_for_each_entry(cpc, this_cpu_ptr(&sched_cb_list), sched_cb_entry)
+ list_for_each_entry_safe(cpc, cpc2, this_cpu_ptr(&sched_cb_list), sched_cb_entry) {
+ if (cpc->task_epc)
+ continue;
+
__perf_pmu_sched_task(cpc, sched_in ? next : prev, sched_in);
+ }
}
static void perf_event_switch(struct task_struct *task,
@@ -4729,7 +4741,7 @@ static void perf_remove_from_owner(struct perf_event *event);
static void perf_event_exit_event(struct perf_event *event,
struct perf_event_context *ctx,
struct task_struct *task,
- bool revoke);
+ unsigned long detach_flags);
/*
* Removes all events from the current task that have been marked
@@ -4756,7 +4768,7 @@ static void perf_event_remove_on_exec(struct perf_event_context *ctx)
modified = true;
- perf_event_exit_event(event, ctx, ctx->task, false);
+ perf_event_exit_event(event, ctx, ctx->task, DETACH_GROUP);
}
raw_spin_lock_irqsave(&ctx->lock, flags);
@@ -5447,6 +5459,8 @@ attach_task_ctx_data(struct task_struct *task, struct kmem_cache *ctx_cache,
}
if (refcount_inc_not_zero(&old->refcount)) {
+ if (global)
+ old->global = true;
free_perf_ctx_data(cd); /* unused */
return 0;
}
@@ -6343,6 +6357,9 @@ static DEFINE_MUTEX(perf_mediated_pmu_mutex);
/* !exclude_guest event of PMU with PERF_PMU_CAP_MEDIATED_VPMU */
static inline bool is_include_guest_event(struct perf_event *event)
{
+ if (!event->pmu)
+ return false;
+
if ((event->pmu->capabilities & PERF_PMU_CAP_MEDIATED_VPMU) &&
!event->attr.exclude_guest)
return true;
@@ -6998,7 +7015,7 @@ static void perf_mmap_open(struct vm_area_struct *vma)
refcount_inc(&event->mmap_count);
refcount_inc(&event->rb->mmap_count);
- if (vma->vm_pgoff)
+ if (vma_start_pgoff(vma))
refcount_inc(&event->rb->aux_mmap_count);
if (mapped)
@@ -7022,7 +7039,6 @@ static void perf_mmap_close(struct vm_area_struct *vma)
mapped_f unmapped = get_mapped(event, event_unmapped);
struct perf_buffer *rb = ring_buffer_get(event);
struct user_struct *mmap_user = rb->mmap_user;
- bool detach_rest = false;
/* FIXIES vs perf_pmu_unregister() */
if (unmapped)
@@ -7032,7 +7048,7 @@ static void perf_mmap_close(struct vm_area_struct *vma)
* The AUX buffer is strictly a sub-buffer, serialize using aux_mutex
* to avoid complications.
*/
- if (rb_has_aux(rb) && vma->vm_pgoff == rb->aux_pgoff &&
+ if (rb_has_aux(rb) && vma_start_pgoff(vma) == rb->aux_pgoff &&
refcount_dec_and_mutex_lock(&rb->aux_mmap_count, &rb->aux_mutex)) {
/*
* Stop all AUX events that are writing to this buffer,
@@ -7053,17 +7069,18 @@ static void perf_mmap_close(struct vm_area_struct *vma)
mutex_unlock(&rb->aux_mutex);
}
- if (refcount_dec_and_test(&rb->mmap_count))
- detach_rest = true;
-
- if (!refcount_dec_and_mutex_lock(&event->mmap_count, &event->mmap_mutex))
- goto out_put;
-
- ring_buffer_attach(event, NULL);
- mutex_unlock(&event->mmap_mutex);
+ /*
+ * Drop references in reverse order of perf_mmap() to prevent
+ * rb revival after rb->mmap_count reaches zero.
+ */
+ if (refcount_dec_and_mutex_lock(&event->mmap_count,
+ &event->mmap_mutex)) {
+ ring_buffer_attach(event, NULL);
+ mutex_unlock(&event->mmap_mutex);
+ }
/* If there's still other mmap()s of this buffer, we're done. */
- if (!detach_rest)
+ if (!refcount_dec_and_test(&rb->mmap_count))
goto out_put;
/*
@@ -7150,6 +7167,8 @@ static int map_range(struct perf_buffer *rb, struct vm_area_struct *vma)
int err = 0;
unsigned long pagenum;
+ guard(mutex)(&rb->aux_mutex);
+
/*
* We map this as a VM_PFNMAP VMA.
*
@@ -7190,7 +7209,8 @@ static int map_range(struct perf_buffer *rb, struct vm_area_struct *vma)
*/
for (pagenum = 0; pagenum < nr_pages; pagenum++) {
unsigned long va = vma->vm_start + PAGE_SIZE * pagenum;
- struct page *page = perf_mmap_to_page(rb, vma->vm_pgoff + pagenum);
+ struct page *page = perf_mmap_to_page(rb,
+ vma_start_pgoff(vma) + pagenum);
if (page == NULL) {
err = -EINVAL;
@@ -7344,6 +7364,7 @@ static int perf_mmap_rb(struct vm_area_struct *vma, struct perf_event *event,
static int perf_mmap_aux(struct vm_area_struct *vma, struct perf_event *event,
unsigned long nr_pages)
{
+ const pgoff_t pgoff_start = vma_start_pgoff(vma);
long extra = 0, user_extra = nr_pages;
u64 aux_offset, aux_size;
struct perf_buffer *rb;
@@ -7366,11 +7387,11 @@ static int perf_mmap_aux(struct vm_area_struct *vma, struct perf_event *event,
if (aux_offset < perf_data_size(rb) + PAGE_SIZE)
return -EINVAL;
- if (aux_offset != vma->vm_pgoff << PAGE_SHIFT)
+ if (aux_offset != pgoff_start << PAGE_SHIFT)
return -EINVAL;
/* already mapped with a different offset */
- if (rb_has_aux(rb) && rb->aux_pgoff != vma->vm_pgoff)
+ if (rb_has_aux(rb) && rb->aux_pgoff != pgoff_start)
return -EINVAL;
if (aux_size != nr_pages * PAGE_SIZE)
@@ -7400,7 +7421,7 @@ static int perf_mmap_aux(struct vm_area_struct *vma, struct perf_event *event,
if (vma->vm_flags & VM_WRITE)
rb_flags |= RING_BUFFER_WRITABLE;
- ret = rb_alloc_aux(rb, event, vma->vm_pgoff, nr_pages,
+ ret = rb_alloc_aux(rb, event, pgoff_start, nr_pages,
event->attr.aux_watermark, rb_flags);
if (ret) {
refcount_dec(&rb->mmap_count);
@@ -7457,7 +7478,7 @@ static int perf_mmap(struct file *file, struct vm_area_struct *vma)
if (event->state <= PERF_EVENT_STATE_REVOKED)
return -ENODEV;
- if (vma->vm_pgoff == 0)
+ if (!vma_start_pgoff(vma))
ret = perf_mmap_rb(vma, event, nr_pages);
else
ret = perf_mmap_aux(vma, event, nr_pages);
@@ -7605,9 +7626,11 @@ static void perf_sigtrap(struct perf_event *event)
{
/*
* Both perf_pending_task() and perf_pending_irq() can race with the
- * task exiting.
+ * task exiting or exec-ing. We can determine if such a race has
+ * occurred by checking if perf_event_exit_task(), which will set
+ * ctx->task to TASK_TOMBSTONE, has already been called.
*/
- if (current->flags & PF_EXITING)
+ if (event->ctx->task == TASK_TOMBSTONE)
return;
/*
@@ -7791,10 +7814,20 @@ unsigned long perf_misc_flags(struct perf_event *event,
unsigned long perf_instruction_pointer(struct perf_event *event,
struct pt_regs *regs)
{
- if (should_sample_guest(event))
- return perf_guest_get_ip();
+ /*
+ * Hardware skid can lead to a scenario where a PMI is
+ * delivered after the CPU has already entered kernel mode.
+ * In that case, user-space sampling must not expose kernel
+ * register state.
+ */
+ if (should_sample_guest(event)) {
+ return event->attr.exclude_kernel &&
+ !(perf_guest_state() & PERF_GUEST_USER) ?
+ 0 : perf_guest_get_ip();
+ }
- return perf_arch_instruction_pointer(regs);
+ return event->attr.exclude_kernel && !user_mode(regs) ?
+ 0 : perf_arch_instruction_pointer(regs);
}
static void
@@ -7828,10 +7861,22 @@ static void perf_sample_regs_user(struct perf_regs *regs_user,
}
static void perf_sample_regs_intr(struct perf_regs *regs_intr,
- struct pt_regs *regs)
+ struct pt_regs *regs,
+ bool exclude_kernel)
{
- regs_intr->regs = regs;
- regs_intr->abi = perf_reg_abi(current);
+ /*
+ * Hardware skid can lead to a scenario where a PMI is
+ * delivered after the CPU has already entered kernel mode.
+ * In that case, user-space sampling must not expose kernel
+ * register state.
+ */
+ if (exclude_kernel && !user_mode(regs)) {
+ regs_intr->abi = PERF_SAMPLE_REGS_ABI_NONE;
+ regs_intr->regs = NULL;
+ } else {
+ regs_intr->regs = regs;
+ regs_intr->abi = perf_reg_abi(current);
+ }
}
@@ -8086,10 +8131,15 @@ static void __perf_event_header__init_id(struct perf_sample_data *data,
}
}
-void perf_event_header__init_id(struct perf_event_header *header,
- struct perf_sample_data *data,
- struct perf_event *event)
+void perf_event_header__init(struct perf_event_header *header,
+ struct perf_sample_data *data,
+ u32 type, u16 misc, u16 size,
+ struct perf_event *event)
{
+ header->type = type;
+ header->misc = misc;
+ header->size = size;
+
if (event->attr.sample_id_all) {
header->size += event->id_header_size;
__perf_event_header__init_id(data, event, event->attr.sample_type);
@@ -8722,7 +8772,8 @@ void perf_prepare_sample(struct perf_sample_data *data,
/* regs dump ABI info */
int size = sizeof(u64);
- perf_sample_regs_intr(&data->regs_intr, regs);
+ perf_sample_regs_intr(&data->regs_intr, regs,
+ event->attr.exclude_kernel);
if (data->regs_intr.regs) {
u64 mask = event->attr.sample_regs_intr;
@@ -8925,17 +8976,16 @@ perf_event_read_event(struct perf_event *event,
struct perf_output_handle handle;
struct perf_sample_data sample;
struct perf_read_event read_event = {
- .header = {
- .type = PERF_RECORD_READ,
- .misc = 0,
- .size = sizeof(read_event) + event->read_size,
- },
.pid = perf_event_pid(event, task),
.tid = perf_event_tid(event, task),
};
int ret;
- perf_event_header__init_id(&read_event.header, &sample, event);
+ perf_event_header__init(&read_event.header, &sample,
+ PERF_RECORD_READ,
+ /* misc= */ 0,
+ sizeof(read_event) + event->read_size,
+ event);
ret = perf_output_begin(&handle, &sample, event, read_event.header.size);
if (ret)
return;
@@ -9176,6 +9226,7 @@ struct perf_task_event {
u32 ptid;
u64 time;
} event_id;
+ int new;
};
static int perf_event_task_match(struct perf_event *event)
@@ -9192,17 +9243,21 @@ static void perf_event_task_output(struct perf_event *event,
struct perf_output_handle handle;
struct perf_sample_data sample;
struct task_struct *task = task_event->task;
- int ret, size = task_event->event_id.header.size;
+ int ret;
if (!perf_event_task_match(event))
return;
- perf_event_header__init_id(&task_event->event_id.header, &sample, event);
+ perf_event_header__init(&task_event->event_id.header, &sample,
+ task_event->new ? PERF_RECORD_FORK : PERF_RECORD_EXIT,
+ /* misc= */ 0,
+ sizeof(task_event->event_id),
+ event);
ret = perf_output_begin(&handle, &sample, event,
task_event->event_id.header.size);
if (ret)
- goto out;
+ return;
task_event->event_id.pid = perf_event_pid(event, task);
task_event->event_id.tid = perf_event_tid(event, task);
@@ -9224,8 +9279,6 @@ static void perf_event_task_output(struct perf_event *event,
perf_event__output_id_sample(event, &handle, &sample);
perf_output_end(&handle);
-out:
- task_event->event_id.header.size = size;
}
static void perf_event_task(struct task_struct *task,
@@ -9242,18 +9295,7 @@ static void perf_event_task(struct task_struct *task,
task_event = (struct perf_task_event){
.task = task,
.task_ctx = task_ctx,
- .event_id = {
- .header = {
- .type = new ? PERF_RECORD_FORK : PERF_RECORD_EXIT,
- .misc = 0,
- .size = sizeof(task_event.event_id),
- },
- /* .pid */
- /* .ppid */
- /* .tid */
- /* .ptid */
- /* .time */
- },
+ .new = new,
};
perf_iterate_sb(perf_event_task_output,
@@ -9330,6 +9372,7 @@ struct perf_comm_event {
u32 pid;
u32 tid;
} event_id;
+ bool exec;
};
static int perf_event_comm_match(struct perf_event *event)
@@ -9343,18 +9386,21 @@ static void perf_event_comm_output(struct perf_event *event,
struct perf_comm_event *comm_event = data;
struct perf_output_handle handle;
struct perf_sample_data sample;
- int size = comm_event->event_id.header.size;
int ret;
if (!perf_event_comm_match(event))
return;
- perf_event_header__init_id(&comm_event->event_id.header, &sample, event);
+ perf_event_header__init(&comm_event->event_id.header, &sample,
+ PERF_RECORD_COMM,
+ comm_event->exec ? PERF_RECORD_MISC_COMM_EXEC : 0,
+ sizeof(comm_event->event_id) + comm_event->comm_size,
+ event);
ret = perf_output_begin(&handle, &sample, event,
comm_event->event_id.header.size);
if (ret)
- goto out;
+ return;
comm_event->event_id.pid = perf_event_pid(event, comm_event->task);
comm_event->event_id.tid = perf_event_tid(event, comm_event->task);
@@ -9366,8 +9412,6 @@ static void perf_event_comm_output(struct perf_event *event,
perf_event__output_id_sample(event, &handle, &sample);
perf_output_end(&handle);
-out:
- comm_event->event_id.header.size = size;
}
static void perf_event_comm_event(struct perf_comm_event *comm_event)
@@ -9382,8 +9426,6 @@ static void perf_event_comm_event(struct perf_comm_event *comm_event)
comm_event->comm = comm;
comm_event->comm_size = size;
- comm_event->event_id.header.size = sizeof(comm_event->event_id) + size;
-
perf_iterate_sb(perf_event_comm_output,
comm_event,
NULL);
@@ -9400,15 +9442,8 @@ void perf_event_comm(struct task_struct *task, bool exec)
.task = task,
/* .comm */
/* .comm_size */
- .event_id = {
- .header = {
- .type = PERF_RECORD_COMM,
- .misc = exec ? PERF_RECORD_MISC_COMM_EXEC : 0,
- /* .size */
- },
- /* .pid */
- /* .tid */
- },
+ /* .event_id */
+ .exec = exec,
};
perf_event_comm_event(&comm_event);
@@ -9442,18 +9477,20 @@ static void perf_event_namespaces_output(struct perf_event *event,
struct perf_namespaces_event *namespaces_event = data;
struct perf_output_handle handle;
struct perf_sample_data sample;
- u16 header_size = namespaces_event->event_id.header.size;
int ret;
if (!perf_event_namespaces_match(event))
return;
- perf_event_header__init_id(&namespaces_event->event_id.header,
- &sample, event);
+ perf_event_header__init(&namespaces_event->event_id.header, &sample,
+ PERF_RECORD_NAMESPACES,
+ /* misc= */ 0,
+ sizeof(namespaces_event->event_id),
+ event);
ret = perf_output_begin(&handle, &sample, event,
namespaces_event->event_id.header.size);
if (ret)
- goto out;
+ return;
namespaces_event->event_id.pid = perf_event_pid(event,
namespaces_event->task);
@@ -9465,8 +9502,6 @@ static void perf_event_namespaces_output(struct perf_event *event,
perf_event__output_id_sample(event, &handle, &sample);
perf_output_end(&handle);
-out:
- namespaces_event->event_id.header.size = header_size;
}
static void perf_fill_ns_link_info(struct perf_ns_link_info *ns_link_info,
@@ -9497,11 +9532,7 @@ void perf_event_namespaces(struct task_struct *task)
namespaces_event = (struct perf_namespaces_event){
.task = task,
.event_id = {
- .header = {
- .type = PERF_RECORD_NAMESPACES,
- .misc = 0,
- .size = sizeof(namespaces_event.event_id),
- },
+ /* .header */
/* .pid */
/* .tid */
.nr_namespaces = NR_NAMESPACES,
@@ -9569,18 +9600,19 @@ static void perf_event_cgroup_output(struct perf_event *event, void *data)
struct perf_cgroup_event *cgroup_event = data;
struct perf_output_handle handle;
struct perf_sample_data sample;
- u16 header_size = cgroup_event->event_id.header.size;
int ret;
+ u16 size = sizeof(cgroup_event->event_id) + cgroup_event->path_size;
if (!perf_event_cgroup_match(event))
return;
- perf_event_header__init_id(&cgroup_event->event_id.header,
- &sample, event);
+ perf_event_header__init(&cgroup_event->event_id.header, &sample,
+ PERF_RECORD_CGROUP, /* misc= */ 0, size,
+ event);
ret = perf_output_begin(&handle, &sample, event,
cgroup_event->event_id.header.size);
if (ret)
- goto out;
+ return;
perf_output_put(&handle, cgroup_event->event_id);
__output_copy(&handle, cgroup_event->path, cgroup_event->path_size);
@@ -9588,8 +9620,6 @@ static void perf_event_cgroup_output(struct perf_event *event, void *data)
perf_event__output_id_sample(event, &handle, &sample);
perf_output_end(&handle);
-out:
- cgroup_event->event_id.header.size = header_size;
}
static void perf_event_cgroup(struct cgroup *cgrp)
@@ -9604,11 +9634,6 @@ static void perf_event_cgroup(struct cgroup *cgrp)
cgroup_event = (struct perf_cgroup_event){
.event_id = {
- .header = {
- .type = PERF_RECORD_CGROUP,
- .misc = 0,
- .size = sizeof(cgroup_event.event_id),
- },
.id = cgroup_id(cgrp),
},
};
@@ -9631,7 +9656,6 @@ static void perf_event_cgroup(struct cgroup *cgrp)
while (!IS_ALIGNED(size, sizeof(u64)))
cgroup_event.path[size++] = '\0';
- cgroup_event.event_id.header.size += size;
cgroup_event.path_size = size;
perf_iterate_sb(perf_event_cgroup_output,
@@ -9687,38 +9711,40 @@ static void perf_event_mmap_output(struct perf_event *event,
struct perf_mmap_event *mmap_event = data;
struct perf_output_handle handle;
struct perf_sample_data sample;
- int size = mmap_event->event_id.header.size;
- u32 type = mmap_event->event_id.header.type;
- bool use_build_id;
+ int size = sizeof(mmap_event->event_id) + mmap_event->file_size;
+ u32 type = PERF_RECORD_MMAP;
+ u16 misc = PERF_RECORD_MISC_USER;
+ bool use_build_id = false;
int ret;
if (!perf_event_mmap_match(event, data))
return;
if (event->attr.mmap2) {
- mmap_event->event_id.header.type = PERF_RECORD_MMAP2;
- mmap_event->event_id.header.size += sizeof(mmap_event->maj);
- mmap_event->event_id.header.size += sizeof(mmap_event->min);
- mmap_event->event_id.header.size += sizeof(mmap_event->ino);
- mmap_event->event_id.header.size += sizeof(mmap_event->ino_generation);
- mmap_event->event_id.header.size += sizeof(mmap_event->prot);
- mmap_event->event_id.header.size += sizeof(mmap_event->flags);
- }
-
- perf_event_header__init_id(&mmap_event->event_id.header, &sample, event);
+ type = PERF_RECORD_MMAP2;
+ size += sizeof(mmap_event->maj);
+ size += sizeof(mmap_event->min);
+ size += sizeof(mmap_event->ino);
+ size += sizeof(mmap_event->ino_generation);
+ size += sizeof(mmap_event->prot);
+ size += sizeof(mmap_event->flags);
+ use_build_id = event->attr.build_id && mmap_event->build_id_size;
+ if (use_build_id)
+ misc |= PERF_RECORD_MISC_MMAP_BUILD_ID;
+ }
+ if (!(mmap_event->vma->vm_flags & VM_EXEC))
+ misc |= PERF_RECORD_MISC_MMAP_DATA;
+
+ perf_event_header__init(&mmap_event->event_id.header, &sample,
+ type, misc, size, event);
ret = perf_output_begin(&handle, &sample, event,
mmap_event->event_id.header.size);
if (ret)
- goto out;
+ return;
mmap_event->event_id.pid = perf_event_pid(event, current);
mmap_event->event_id.tid = perf_event_tid(event, current);
- use_build_id = event->attr.build_id && mmap_event->build_id_size;
-
- if (event->attr.mmap2 && use_build_id)
- mmap_event->event_id.header.misc |= PERF_RECORD_MISC_MMAP_BUILD_ID;
-
perf_output_put(&handle, mmap_event->event_id);
if (event->attr.mmap2) {
@@ -9743,9 +9769,6 @@ static void perf_event_mmap_output(struct perf_event *event,
perf_event__output_id_sample(event, &handle, &sample);
perf_output_end(&handle);
-out:
- mmap_event->event_id.header.size = size;
- mmap_event->event_id.header.type = type;
}
static void perf_event_mmap_event(struct perf_mmap_event *mmap_event)
@@ -9841,11 +9864,6 @@ got_name:
mmap_event->prot = prot;
mmap_event->flags = flags;
- if (!(vma->vm_flags & VM_EXEC))
- mmap_event->event_id.header.misc |= PERF_RECORD_MISC_MMAP_DATA;
-
- mmap_event->event_id.header.size = sizeof(mmap_event->event_id) + size;
-
if (atomic_read(&nr_build_id_events))
build_id_parse_nofault(vma, mmap_event->build_id, &mmap_event->build_id_size);
@@ -9884,7 +9902,7 @@ static bool perf_addr_filter_vma_adjust(struct perf_addr_filter *filter,
struct perf_addr_filter_range *fr)
{
unsigned long vma_size = vma->vm_end - vma->vm_start;
- unsigned long off = vma->vm_pgoff << PAGE_SHIFT;
+ unsigned long off = vma_start_pgoff(vma) << PAGE_SHIFT;
struct file *file = vma->vm_file;
if (!perf_addr_filter_match(filter, file, off, vma_size))
@@ -9965,16 +9983,12 @@ void perf_event_mmap(struct vm_area_struct *vma)
/* .file_name */
/* .file_size */
.event_id = {
- .header = {
- .type = PERF_RECORD_MMAP,
- .misc = PERF_RECORD_MISC_USER,
- /* .size */
- },
+ /* .header */
/* .pid */
/* .tid */
.start = vma->vm_start,
.len = vma->vm_end - vma->vm_start,
- .pgoff = (u64)vma->vm_pgoff << PAGE_SHIFT,
+ .pgoff = (u64)vma_start_pgoff(vma) << PAGE_SHIFT,
},
/* .maj (attr_mmap2 only) */
/* .min (attr_mmap2 only) */
@@ -9999,18 +10013,15 @@ void perf_event_aux_event(struct perf_event *event, unsigned long head,
u64 size;
u64 flags;
} rec = {
- .header = {
- .type = PERF_RECORD_AUX,
- .misc = 0,
- .size = sizeof(rec),
- },
.offset = head,
.size = size,
.flags = flags,
};
int ret;
- perf_event_header__init_id(&rec.header, &sample, event);
+ perf_event_header__init(&rec.header, &sample,
+ PERF_RECORD_AUX, /* misc= */ 0, sizeof(rec),
+ event);
ret = perf_output_begin(&handle, &sample, event, rec.header.size);
if (ret)
@@ -10035,15 +10046,14 @@ void perf_log_lost_samples(struct perf_event *event, u64 lost)
struct perf_event_header header;
u64 lost;
} lost_samples_event = {
- .header = {
- .type = PERF_RECORD_LOST_SAMPLES,
- .misc = 0,
- .size = sizeof(lost_samples_event),
- },
.lost = lost,
};
- perf_event_header__init_id(&lost_samples_event.header, &sample, event);
+ perf_event_header__init(&lost_samples_event.header, &sample,
+ PERF_RECORD_LOST_SAMPLES,
+ /* misc= */ 0,
+ sizeof(lost_samples_event),
+ event);
ret = perf_output_begin(&handle, &sample, event,
lost_samples_event.header.size);
@@ -10068,6 +10078,8 @@ struct perf_switch_event {
u32 next_prev_pid;
u32 next_prev_tid;
} event_id;
+ bool sched_in;
+ bool preempt;
};
static int perf_event_switch_match(struct perf_event *event)
@@ -10080,6 +10092,9 @@ static void perf_event_switch_output(struct perf_event *event, void *data)
struct perf_switch_event *se = data;
struct perf_output_handle handle;
struct perf_sample_data sample;
+ u32 type;
+ u16 misc;
+ u16 size;
int ret;
if (!perf_event_switch_match(event))
@@ -10087,18 +10102,22 @@ static void perf_event_switch_output(struct perf_event *event, void *data)
/* Only CPU-wide events are allowed to see next/prev pid/tid */
if (event->ctx->task) {
- se->event_id.header.type = PERF_RECORD_SWITCH;
- se->event_id.header.size = sizeof(se->event_id.header);
+ type = PERF_RECORD_SWITCH;
+ size = sizeof(se->event_id.header);
} else {
- se->event_id.header.type = PERF_RECORD_SWITCH_CPU_WIDE;
- se->event_id.header.size = sizeof(se->event_id);
+ type = PERF_RECORD_SWITCH_CPU_WIDE;
+ size = sizeof(se->event_id);
se->event_id.next_prev_pid =
perf_event_pid(event, se->next_prev);
se->event_id.next_prev_tid =
perf_event_tid(event, se->next_prev);
}
+ misc = se->sched_in ? 0 : PERF_RECORD_MISC_SWITCH_OUT;
+ if (se->preempt)
+ misc |= PERF_RECORD_MISC_SWITCH_OUT_PREEMPT;
- perf_event_header__init_id(&se->event_id.header, &sample, event);
+ perf_event_header__init(&se->event_id.header, &sample,
+ type, misc, size, event);
ret = perf_output_begin(&handle, &sample, event, se->event_id.header.size);
if (ret)
@@ -10124,22 +10143,11 @@ static void perf_event_switch(struct task_struct *task,
switch_event = (struct perf_switch_event){
.task = task,
.next_prev = next_prev,
- .event_id = {
- .header = {
- /* .type */
- .misc = sched_in ? 0 : PERF_RECORD_MISC_SWITCH_OUT,
- /* .size */
- },
- /* .next_prev_pid */
- /* .next_prev_tid */
- },
+ /* .event_id */
+ .sched_in = sched_in,
+ .preempt = !sched_in && task_is_runnable(task),
};
- if (!sched_in && task_is_runnable(task)) {
- switch_event.event_id.header.misc |=
- PERF_RECORD_MISC_SWITCH_OUT_PREEMPT;
- }
-
perf_iterate_sb(perf_event_switch_output, &switch_event, NULL);
}
@@ -10159,20 +10167,17 @@ static void perf_log_throttle(struct perf_event *event, int enable)
u64 id;
u64 stream_id;
} throttle_event = {
- .header = {
- .type = PERF_RECORD_THROTTLE,
- .misc = 0,
- .size = sizeof(throttle_event),
- },
.time = perf_event_clock(event),
.id = primary_event_id(event),
.stream_id = event->id,
};
- if (enable)
- throttle_event.header.type = PERF_RECORD_UNTHROTTLE;
-
- perf_event_header__init_id(&throttle_event.header, &sample, event);
+ perf_event_header__init(&throttle_event.header, &sample,
+ enable ? PERF_RECORD_UNTHROTTLE
+ : PERF_RECORD_THROTTLE,
+ /* misc= */ 0,
+ sizeof(throttle_event),
+ event);
ret = perf_output_begin(&handle, &sample, event,
throttle_event.header.size);
@@ -10211,12 +10216,14 @@ static void perf_event_ksymbol_output(struct perf_event *event, void *data)
struct perf_output_handle handle;
struct perf_sample_data sample;
int ret;
+ u16 size = sizeof(ksymbol_event->event_id) + ksymbol_event->name_len;
if (!perf_event_ksymbol_match(event))
return;
- perf_event_header__init_id(&ksymbol_event->event_id.header,
- &sample, event);
+ perf_event_header__init(&ksymbol_event->event_id.header, &sample,
+ PERF_RECORD_KSYMBOL, /* misc= */ 0, size,
+ event);
ret = perf_output_begin(&handle, &sample, event,
ksymbol_event->event_id.header.size);
if (ret)
@@ -10257,11 +10264,6 @@ void perf_event_ksymbol(u16 ksym_type, u64 addr, u32 len, bool unregister,
.name = name,
.name_len = name_len,
.event_id = {
- .header = {
- .type = PERF_RECORD_KSYMBOL,
- .size = sizeof(ksymbol_event.event_id) +
- name_len,
- },
.addr = addr,
.len = len,
.ksym_type = ksym_type,
@@ -10305,8 +10307,11 @@ static void perf_event_bpf_output(struct perf_event *event, void *data)
if (!perf_event_bpf_match(event))
return;
- perf_event_header__init_id(&bpf_event->event_id.header,
- &sample, event);
+ perf_event_header__init(&bpf_event->event_id.header, &sample,
+ PERF_RECORD_BPF_EVENT,
+ /* misc= */ 0,
+ sizeof(bpf_event->event_id),
+ event);
ret = perf_output_begin(&handle, &sample, event,
bpf_event->event_id.header.size);
if (ret)
@@ -10362,10 +10367,6 @@ void perf_event_bpf_event(struct bpf_prog *prog,
bpf_event = (struct perf_bpf_event){
.prog = prog,
.event_id = {
- .header = {
- .type = PERF_RECORD_BPF_EVENT,
- .size = sizeof(bpf_event.event_id),
- },
.type = type,
.flags = flags,
.id = prog->aux->id,
@@ -10393,18 +10394,23 @@ static void perf_callchain_deferred_output(struct perf_event *event, void *data)
struct perf_callchain_deferred_event *deferred_event = data;
struct perf_output_handle handle;
struct perf_sample_data sample;
- int ret, size = deferred_event->event.header.size;
+ int ret;
+ u16 size = sizeof(deferred_event->event) + (deferred_event->trace->nr * sizeof(u64));
if (!event->attr.defer_output)
return;
/* XXX do we really need sample_id_all for this ??? */
- perf_event_header__init_id(&deferred_event->event.header, &sample, event);
+ perf_event_header__init(&deferred_event->event.header, &sample,
+ PERF_RECORD_CALLCHAIN_DEFERRED,
+ PERF_RECORD_MISC_USER,
+ size,
+ event);
ret = perf_output_begin(&handle, &sample, event,
deferred_event->event.header.size);
if (ret)
- goto out;
+ return;
perf_output_put(&handle, deferred_event->event);
for (int i = 0; i < deferred_event->trace->nr; i++) {
@@ -10414,8 +10420,6 @@ static void perf_callchain_deferred_output(struct perf_event *event, void *data)
perf_event__output_id_sample(event, &handle, &sample);
perf_output_end(&handle);
-out:
- deferred_event->event.header.size = size;
}
static void perf_unwind_deferred_callback(struct unwind_work *work,
@@ -10424,12 +10428,6 @@ static void perf_unwind_deferred_callback(struct unwind_work *work,
struct perf_callchain_deferred_event deferred_event = {
.trace = trace,
.event = {
- .header = {
- .type = PERF_RECORD_CALLCHAIN_DEFERRED,
- .misc = PERF_RECORD_MISC_USER,
- .size = sizeof(deferred_event.event) +
- (trace->nr * sizeof(u64)),
- },
.cookie = cookie,
.nr = trace->nr,
},
@@ -10441,7 +10439,8 @@ static void perf_unwind_deferred_callback(struct unwind_work *work,
struct perf_text_poke_event {
const void *old_bytes;
const void *new_bytes;
- size_t pad;
+ u16 tot;
+ u16 pad;
u16 old_len;
u16 new_len;
@@ -10462,13 +10461,18 @@ static void perf_event_text_poke_output(struct perf_event *event, void *data)
struct perf_text_poke_event *text_poke_event = data;
struct perf_output_handle handle;
struct perf_sample_data sample;
+ u16 size = sizeof(text_poke_event->event_id) + text_poke_event->tot + text_poke_event->pad;
u64 padding = 0;
int ret;
if (!perf_event_text_poke_match(event))
return;
- perf_event_header__init_id(&text_poke_event->event_id.header, &sample, event);
+ perf_event_header__init(&text_poke_event->event_id.header, &sample,
+ PERF_RECORD_TEXT_POKE,
+ PERF_RECORD_MISC_KERNEL,
+ size,
+ event);
ret = perf_output_begin(&handle, &sample, event,
text_poke_event->event_id.header.size);
@@ -10506,15 +10510,11 @@ void perf_event_text_poke(const void *addr, const void *old_bytes,
text_poke_event = (struct perf_text_poke_event){
.old_bytes = old_bytes,
.new_bytes = new_bytes,
+ .tot = tot,
.pad = pad,
.old_len = old_len,
.new_len = new_len,
.event_id = {
- .header = {
- .type = PERF_RECORD_TEXT_POKE,
- .misc = PERF_RECORD_MISC_KERNEL,
- .size = sizeof(text_poke_event.event_id) + tot + pad,
- },
.addr = (unsigned long)addr,
},
};
@@ -10545,13 +10545,12 @@ static void perf_log_itrace_start(struct perf_event *event)
event->attach_state & PERF_ATTACH_ITRACE)
return;
- rec.header.type = PERF_RECORD_ITRACE_START;
- rec.header.misc = 0;
- rec.header.size = sizeof(rec);
rec.pid = perf_event_pid(event, current);
rec.tid = perf_event_tid(event, current);
- perf_event_header__init_id(&rec.header, &sample, event);
+ perf_event_header__init(&rec.header, &sample,
+ PERF_RECORD_ITRACE_START, /* misc= */ 0, sizeof(rec),
+ event);
ret = perf_output_begin(&handle, &sample, event, rec.header.size);
if (ret)
@@ -10576,12 +10575,10 @@ void perf_report_aux_output_id(struct perf_event *event, u64 hw_id)
if (event->parent)
event = event->parent;
- rec.header.type = PERF_RECORD_AUX_OUTPUT_HW_ID;
- rec.header.misc = 0;
- rec.header.size = sizeof(rec);
- rec.hw_id = hw_id;
-
- perf_event_header__init_id(&rec.header, &sample, event);
+ rec.hw_id = hw_id;
+ perf_event_header__init(&rec.header, &sample,
+ PERF_RECORD_AUX_OUTPUT_HW_ID, /* misc= */ 0,
+ sizeof(rec), event);
ret = perf_output_begin(&handle, &sample, event, rec.header.size);
if (ret)
@@ -12694,7 +12691,7 @@ static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr,
struct cpumask *mask = perf_scope_cpumask(pmu->scope);
if (mask)
- return cpumap_print_to_pagebuf(true, buf, mask);
+ return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(mask));
return 0;
}
@@ -12937,7 +12934,7 @@ static void __pmu_detach_event(struct pmu *pmu, struct perf_event *event,
/*
* De-schedule the event and mark it REVOKED.
*/
- perf_event_exit_event(event, ctx, ctx->task, true);
+ perf_event_exit_event(event, ctx, ctx->task, DETACH_REVOKE);
/*
* All _free_event() bits that rely on event->pmu:
@@ -12968,6 +12965,7 @@ static void __pmu_detach_event(struct pmu *pmu, struct perf_event *event,
exclusive_event_destroy(event);
module_put(pmu->module);
+ mediated_pmu_unaccount_event(event);
event->pmu = NULL; /* force fault instead of UAF */
}
@@ -13524,9 +13522,8 @@ perf_event_alloc(struct perf_event_attr *attr, int cpu,
return ERR_PTR(err);
if (has_addr_filter(event)) {
- event->addr_filter_ranges = kcalloc(pmu->nr_addr_filters,
- sizeof(struct perf_addr_filter_range),
- GFP_KERNEL);
+ event->addr_filter_ranges = kzalloc_objs(struct perf_addr_filter_range,
+ pmu->nr_addr_filters);
if (!event->addr_filter_ranges)
return ERR_PTR(-ENOMEM);
@@ -13909,7 +13906,9 @@ SYSCALL_DEFINE5(perf_event_open,
if (err)
return err;
- if (!attr.exclude_kernel) {
+ if (!attr.exclude_kernel || attr.text_poke ||
+ ((attr.sample_type & PERF_SAMPLE_CALLCHAIN) &&
+ !attr.exclude_callchain_kernel)) {
err = perf_allow_kernel();
if (err)
return err;
@@ -13970,7 +13969,7 @@ SYSCALL_DEFINE5(perf_event_open,
goto err_fd;
}
group_leader = fd_file(group)->private_data;
- if (group_leader->state <= PERF_EVENT_STATE_REVOKED) {
+ if (group_leader->state <= PERF_EVENT_STATE_EXIT) {
err = -ENODEV;
goto err_fd;
}
@@ -14101,6 +14100,12 @@ SYSCALL_DEFINE5(perf_event_open,
if (group_leader->ctx != ctx)
goto err_locked;
+ /* Recheck under ctx::mutex to serialize against remove-on-exec. */
+ if (group_leader->state <= PERF_EVENT_STATE_EXIT) {
+ err = -ENODEV;
+ goto err_locked;
+ }
+
/*
* Only a group leader can be exclusive or pinned
*/
@@ -14525,12 +14530,13 @@ static void
perf_event_exit_event(struct perf_event *event,
struct perf_event_context *ctx,
struct task_struct *task,
- bool revoke)
+ unsigned long detach_flags)
{
struct perf_event *parent_event = event->parent;
- unsigned long detach_flags = DETACH_EXIT;
unsigned int attach_state;
+ detach_flags |= DETACH_EXIT;
+
if (parent_event) {
/*
* Do not destroy the 'original' grouping; because of the
@@ -14553,8 +14559,8 @@ perf_event_exit_event(struct perf_event *event,
sync_child_event(event, task);
}
- if (revoke)
- detach_flags |= DETACH_GROUP | DETACH_REVOKE;
+ if (detach_flags & DETACH_REVOKE)
+ detach_flags |= DETACH_GROUP;
perf_remove_from_context(event, detach_flags);
/*
@@ -14642,7 +14648,7 @@ static void perf_event_exit_task_context(struct task_struct *task, bool exit)
perf_event_task(task, ctx, 0);
list_for_each_entry_safe(child_event, next, &ctx->event_list, event_entry)
- perf_event_exit_event(child_event, ctx, exit ? task : NULL, false);
+ perf_event_exit_event(child_event, ctx, exit ? task : NULL, 0);
mutex_unlock(&ctx->mutex);
@@ -14768,6 +14774,24 @@ int perf_allow_kernel(void)
}
EXPORT_SYMBOL_GPL(perf_allow_kernel);
+int perf_allow_cpu(void)
+{
+ if (sysctl_perf_event_paranoid > 0 && !perfmon_capable())
+ return -EACCES;
+
+ return security_perf_event_open(PERF_SECURITY_CPU);
+}
+EXPORT_SYMBOL_GPL(perf_allow_cpu);
+
+int perf_allow_tracepoint(void)
+{
+ if (sysctl_perf_event_paranoid > -1 && !perfmon_capable())
+ return -EPERM;
+
+ return security_perf_event_open(PERF_SECURITY_TRACEPOINT);
+}
+EXPORT_SYMBOL_GPL(perf_allow_tracepoint);
+
/*
* Inherit an event from parent task to child task.
*
diff --git a/kernel/events/ring_buffer.c b/kernel/events/ring_buffer.c
index 9fe92161715e..13bd42e5493d 100644
--- a/kernel/events/ring_buffer.c
+++ b/kernel/events/ring_buffer.c
@@ -246,14 +246,13 @@ __perf_output_begin(struct perf_output_handle *handle,
handle->size = (1UL << page_shift) - offset;
if (unlikely(have_lost)) {
- lost_event.header.size = sizeof(lost_event);
- lost_event.header.type = PERF_RECORD_LOST;
- lost_event.header.misc = 0;
lost_event.id = event->id;
lost_event.lost = local_xchg(&rb->lost, 0);
/* XXX mostly redundant; @data is already fully initializes */
- perf_event_header__init_id(&lost_event.header, data, event);
+ perf_event_header__init(&lost_event.header, data,
+ PERF_RECORD_LOST, /* misc= */ 0,
+ sizeof(lost_event), event);
perf_output_put(handle, lost_event);
perf_event__output_id_sample(event, handle, data);
}
@@ -509,7 +508,10 @@ void perf_aux_output_end(struct perf_output_handle *handle, unsigned long size)
/*
* Only send RECORD_AUX if we have something useful to communicate
*
- * Note: the OVERWRITE records by themselves are not considered
+ * PMU_FORMAT bits identify the PMU type rather than an AUX event
+ * has occurred, so ignore them for zero-sized records.
+ *
+ * The OVERWRITE records by themselves are not considered
* useful, as they don't communicate any *new* information,
* aside from the short-lived offset, that becomes history at
* the next event sched-in and therefore isn't useful.
@@ -518,7 +520,9 @@ void perf_aux_output_end(struct perf_output_handle *handle, unsigned long size)
* offset. So, from now on we don't output AUX records that
* have *only* OVERWRITE flag set.
*/
- if (size || (handle->aux_flags & ~(u64)PERF_AUX_FLAG_OVERWRITE))
+ if (size ||
+ (handle->aux_flags & ~(u64)(PERF_AUX_FLAG_PMU_FORMAT_TYPE_MASK |
+ PERF_AUX_FLAG_OVERWRITE)))
perf_event_aux_event(handle->event, aux_head, size,
handle->aux_flags);
diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c
index 4084e926e284..7709ea882477 100644
--- a/kernel/events/uprobes.c
+++ b/kernel/events/uprobes.c
@@ -54,7 +54,7 @@ static struct mutex uprobes_mmap_mutex[UPROBES_HASH_SZ];
DEFINE_STATIC_PERCPU_RWSEM(dup_mmap_sem);
/* Covers return_instance's uprobe lifetime. */
-DEFINE_STATIC_SRCU(uretprobes_srcu);
+DEFINE_STATIC_SRCU_FAST_UPDOWN(uretprobes_srcu);
/* Have a copy of original instruction */
#define UPROBE_COPY_INSN 0
@@ -144,12 +144,14 @@ static bool valid_vma(struct vm_area_struct *vma, bool is_register)
static unsigned long offset_to_vaddr(struct vm_area_struct *vma, loff_t offset)
{
- return vma->vm_start + offset - ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
+ return vma->vm_start + offset -
+ ((loff_t)vma_start_pgoff(vma) << PAGE_SHIFT);
}
static loff_t vaddr_to_offset(struct vm_area_struct *vma, unsigned long vaddr)
{
- return ((loff_t)vma->vm_pgoff << PAGE_SHIFT) + (vaddr - vma->vm_start);
+ return ((loff_t)vma_start_pgoff(vma) << PAGE_SHIFT) +
+ (vaddr - vma->vm_start);
}
/**
@@ -511,7 +513,7 @@ int uprobe_write(struct arch_uprobe *auprobe, struct vm_area_struct *vma,
uprobe = container_of(auprobe, struct uprobe, arch);
- if (WARN_ON_ONCE(!is_cow_mapping(vma->vm_flags)))
+ if (WARN_ON_ONCE(!vma_is_cow_mapping(vma)))
return -EINVAL;
/*
@@ -707,12 +709,13 @@ static void put_uprobe(struct uprobe *uprobe)
}
/* Initialize hprobe as SRCU-protected "leased" uprobe */
-static void hprobe_init_leased(struct hprobe *hprobe, struct uprobe *uprobe, int srcu_idx)
+static void hprobe_init_leased(struct hprobe *hprobe, struct uprobe *uprobe,
+ struct srcu_ctr __percpu *srcu_scp)
{
WARN_ON(!uprobe);
hprobe->state = HPROBE_LEASED;
hprobe->uprobe = uprobe;
- hprobe->srcu_idx = srcu_idx;
+ hprobe->srcu_scp = srcu_scp;
}
/* Initialize hprobe as refcounted ("stable") uprobe (uprobe can be NULL). */
@@ -720,7 +723,7 @@ static void hprobe_init_stable(struct hprobe *hprobe, struct uprobe *uprobe)
{
hprobe->state = uprobe ? HPROBE_STABLE : HPROBE_GONE;
hprobe->uprobe = uprobe;
- hprobe->srcu_idx = -1;
+ hprobe->srcu_scp = NULL;
}
/*
@@ -757,7 +760,7 @@ static void hprobe_finalize(struct hprobe *hprobe, enum hprobe_state hstate)
{
switch (hstate) {
case HPROBE_LEASED:
- __srcu_read_unlock(&uretprobes_srcu, hprobe->srcu_idx);
+ srcu_up_read_fast(&uretprobes_srcu, hprobe->srcu_scp);
break;
case HPROBE_STABLE:
put_uprobe(hprobe->uprobe);
@@ -829,8 +832,8 @@ static struct uprobe *hprobe_expire(struct hprobe *hprobe, bool get)
*/
if (try_cmpxchg(&hprobe->state, &hstate, uprobe ? HPROBE_STABLE : HPROBE_GONE)) {
/* We won the race, we are the ones to unlock SRCU */
- __srcu_read_unlock(&uretprobes_srcu, hprobe->srcu_idx);
- return get ? get_uprobe(uprobe) : uprobe;
+ srcu_up_read_fast(&uretprobes_srcu, hprobe->srcu_scp);
+ return get && uprobe ? get_uprobe(uprobe) : uprobe;
}
/*
@@ -1210,7 +1213,7 @@ build_map_info(struct address_space *mapping, loff_t offset, bool is_register)
again:
i_mmap_lock_read(mapping);
- vma_interval_tree_foreach(vma, &mapping->i_mmap, pgoff, pgoff) {
+ mapping_rmap_tree_foreach(vma, mapping, pgoff, pgoff) {
if (!valid_vma(vma, is_register))
continue;
@@ -1482,7 +1485,7 @@ static int unapply_uprobe(struct uprobe *uprobe, struct mm_struct *mm)
file_inode(vma->vm_file) != uprobe->inode)
continue;
- offset = (loff_t)vma->vm_pgoff << PAGE_SHIFT;
+ offset = (loff_t)vma_start_pgoff(vma) << PAGE_SHIFT;
if (uprobe->offset < offset ||
uprobe->offset >= offset + vma->vm_end - vma->vm_start)
continue;
@@ -1806,14 +1809,6 @@ static struct xol_area *get_xol_area(void)
return area;
}
-void __weak arch_uprobe_clear_state(struct mm_struct *mm)
-{
-}
-
-void __weak arch_uprobe_init_state(struct mm_struct *mm)
-{
-}
-
/*
* uprobe_clear_state - Free the area allocated for slots.
*/
@@ -1825,8 +1820,6 @@ void uprobe_clear_state(struct mm_struct *mm)
delayed_uprobe_remove(NULL, mm);
mutex_unlock(&delayed_uprobe_lock);
- arch_uprobe_clear_state(mm);
-
if (!area)
return;
@@ -2045,7 +2038,7 @@ static void ri_timer(struct timer_list *timer)
struct return_instance *ri;
/* SRCU protects uprobe from reuse for the cmpxchg() inside hprobe_expire(). */
- guard(srcu)(&uretprobes_srcu);
+ guard(srcu_fast_updown)(&uretprobes_srcu);
/* RCU protects return_instance from freeing. */
guard(rcu)();
@@ -2142,7 +2135,7 @@ static int dup_utask(struct task_struct *t, struct uprobe_task *o_utask)
t->utask = n_utask;
/* protect uprobes from freeing, we'll need try_get_uprobe() them */
- guard(srcu)(&uretprobes_srcu);
+ guard(srcu_fast_updown)(&uretprobes_srcu);
p = &n_utask->return_instances;
for (o = o_utask->return_instances; o; o = o->next) {
@@ -2254,8 +2247,8 @@ static void prepare_uretprobe(struct uprobe *uprobe, struct pt_regs *regs,
{
struct uprobe_task *utask = current->utask;
unsigned long orig_ret_vaddr, trampoline_vaddr;
+ struct srcu_ctr __percpu *srcu_scp;
bool chained;
- int srcu_idx;
if (!get_xol_area())
goto free;
@@ -2293,8 +2286,12 @@ static void prepare_uretprobe(struct uprobe *uprobe, struct pt_regs *regs,
orig_ret_vaddr = utask->return_instances->orig_ret_vaddr;
}
- /* __srcu_read_lock() because SRCU lock survives switch to user space */
- srcu_idx = __srcu_read_lock(&uretprobes_srcu);
+ /*
+ * Use srcu_down_read_fast() because the SRCU lock survives a switch to
+ * user space and can be unlocked from a different context by ri_timer()
+ * or dup_utask().
+ */
+ srcu_scp = srcu_down_read_fast(&uretprobes_srcu);
ri->func = instruction_pointer(regs);
ri->stack = user_stack_pointer(regs);
@@ -2303,7 +2300,7 @@ static void prepare_uretprobe(struct uprobe *uprobe, struct pt_regs *regs,
utask->depth++;
- hprobe_init_leased(&ri->hprobe, uprobe, srcu_idx);
+ hprobe_init_leased(&ri->hprobe, uprobe, srcu_scp);
ri->next = utask->return_instances;
rcu_assign_pointer(utask->return_instances, ri);
@@ -2453,7 +2450,8 @@ static struct uprobe *find_active_uprobe_speculative(unsigned long bp_vaddr)
if (!vm_file)
return NULL;
- offset = (loff_t)(vma->vm_pgoff << PAGE_SHIFT) + (bp_vaddr - vma->vm_start);
+ offset = (loff_t)(vma_start_pgoff(vma) << PAGE_SHIFT) +
+ (bp_vaddr - vma->vm_start);
uprobe = find_uprobe_rcu(vm_file->f_inode, offset);
if (!uprobe)
return NULL;
diff --git a/kernel/exit.c b/kernel/exit.c
index 1056422bc101..282328d2b4cf 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -48,7 +48,6 @@
#include <linux/audit.h> /* for audit_free() */
#include <linux/resource.h>
#include <linux/task_io_accounting_ops.h>
-#include <linux/blkdev.h>
#include <linux/task_work.h>
#include <linux/fs_struct.h>
#include <linux/init_task.h>
@@ -212,7 +211,12 @@ static void __exit_signal(struct release_task_post *post, struct task_struct *ts
__unhash_process(post, tsk, group_dead);
write_sequnlock(&sig->stats_lock);
- tsk->sighand = NULL;
+ /*
+ * Ensure that all preceeding state is visible. Pairs with
+ * the smp_acquire__after_ctrl_dep() in the sighand == NULL
+ * path of lock_task_sighand().
+ */
+ smp_store_release(&tsk->sighand, NULL);
spin_unlock(&sighand->siglock);
__cleanup_sighand(sighand);
@@ -257,8 +261,11 @@ repeat:
pidfs_exit(p);
cgroup_task_release(p);
- /* Retrieve @thread_pid before __unhash_process() may set it to NULL. */
- thread_pid = task_pid(p);
+ /*
+ * Pin @thread_pid before __unhash_process() clears it. The last
+ * PIDTYPE detach can otherwise free it before proc_flush_pid().
+ */
+ thread_pid = get_pid(task_pid(p));
write_lock_irq(&tasklist_lock);
ptrace_release_task(p);
@@ -287,20 +294,21 @@ repeat:
}
write_unlock_irq(&tasklist_lock);
- /* @thread_pid can't go away until free_pids() below */
proc_flush_pid(thread_pid);
+ put_pid(thread_pid);
exit_cred_namespaces(p);
add_device_randomness(&p->se.sum_exec_runtime,
sizeof(p->se.sum_exec_runtime));
free_pids(post.pids);
release_thread(p);
/*
- * This task was already removed from the process/thread/pid lists
- * and lock_task_sighand(p) can't succeed. Nobody else can touch
- * ->pending or, if group dead, signal->shared_pending. We can call
- * flush_sigqueue() lockless.
+ * This task was already removed from the process/thread/pid lists and
+ * lock_task_sighand(p) can't succeed. If it's the group leader then
+ * flush tsk->signal->shared_pending. tsk->pending has been flushed
+ * already in exit_signals(). Nothing else can touch
+ * signal->shared_pending anymore, so flush_sigqueue() can be invoked
+ * lockless.
*/
- flush_sigqueue(&p->pending);
if (thread_group_leader(p))
flush_sigqueue(&p->signal->shared_pending);
@@ -543,32 +551,6 @@ void mm_update_next_owner(struct mm_struct *mm)
}
#endif /* CONFIG_MEMCG */
-#if defined(CONFIG_SCHED_CACHE) && defined(CONFIG_NUMA_BALANCING)
-/*
- * Subtract the memory footprint of the current task from
- * mm.
- */
-static void exit_mm_sched_cache(struct mm_struct *mm)
-{
- unsigned long fp, sub;
-
- if (!current->total_numa_faults)
- return;
- /*
- * No lock protection due to performance considerations.
- * Make sure mm->sc_stat.footprint does not become
- * negative.
- */
- fp = READ_ONCE(mm->sc_stat.footprint);
- sub = min(fp, current->total_numa_faults);
- WRITE_ONCE(mm->sc_stat.footprint, fp - sub);
-}
-#else
-static inline void exit_mm_sched_cache(struct mm_struct *mm)
-{
-}
-#endif /* CONFIG_SCHED_CACHE CONFIG_NUMA_BALANCING */
-
/*
* Turn us into a lazy TLB process if we
* aren't already..
@@ -577,11 +559,11 @@ static void exit_mm(void)
{
struct mm_struct *mm = current->mm;
- exit_mm_release(current, mm);
+ mm_exit_exec_release(current, mm);
if (!mm)
return;
- exit_mm_sched_cache(mm);
+ sched_cache_exit_mm(current);
mmap_read_lock(mm);
mmgrab_lazy_tlb(mm);
@@ -1111,7 +1093,7 @@ void __noreturn make_task_dead(int signr)
SYSCALL_DEFINE1(exit, int, error_code)
{
- do_exit((error_code&0xff)<<8);
+ do_exit((error_code & 0xff) << 8);
}
/*
diff --git a/kernel/fork.c b/kernel/fork.c
index 892a95214c54..10f2d05d816a 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -143,7 +143,7 @@
unsigned long total_forks; /* Handle normal Linux uptimes. */
int nr_threads; /* The idle threads do not count.. */
-static int max_threads; /* tunable limit on nr_threads */
+static int max_threads __read_mostly; /* tunable limit on nr_threads */
#define NAMED_ARRAY_INDEX(x) [x] = __stringify(x)
@@ -537,6 +537,7 @@ void free_task(struct task_struct *tsk)
#endif
release_user_cpus_ptr(tsk);
scs_release(tsk);
+ smp_task_ipi_mask_free(tsk);
#ifndef CONFIG_THREAD_INFO_IN_TASK
/*
@@ -935,10 +936,14 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
#endif
account_kernel_stack(tsk, 1);
- err = scs_prepare(tsk, node);
+ err = smp_task_ipi_mask_alloc(tsk);
if (err)
goto free_stack;
+ err = scs_prepare(tsk, node);
+ if (err)
+ goto free_ipi_mask;
+
#ifdef CONFIG_SECCOMP
/*
* We must handle setting up seccomp filters once we're under
@@ -1009,8 +1014,15 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
tsk->mm_cid.active = 0;
INIT_HLIST_NODE(&tsk->mm_cid.node);
#endif
+
+#ifdef CONFIG_BPF_SYSCALL
+ RCU_INIT_POINTER(tsk->bpf_storage, NULL);
+ tsk->bpf_ctx = NULL;
+#endif
return tsk;
+free_ipi_mask:
+ smp_task_ipi_mask_free(tsk);
free_stack:
exit_task_stack_account(tsk);
free_thread_stack(tsk);
@@ -1064,7 +1076,6 @@ static void mm_init_uprobes_state(struct mm_struct *mm)
{
#ifdef CONFIG_UPROBES
mm->uprobes_state.xol_area = NULL;
- arch_uprobe_init_state(mm);
#endif
}
@@ -1497,15 +1508,9 @@ static void mm_release(struct task_struct *tsk, struct mm_struct *mm)
complete_vfork_done(tsk);
}
-void exit_mm_release(struct task_struct *tsk, struct mm_struct *mm)
+void mm_exit_exec_release(struct task_struct *tsk, struct mm_struct *mm)
{
- futex_exit_release(tsk);
- mm_release(tsk, mm);
-}
-
-void exec_mm_release(struct task_struct *tsk, struct mm_struct *mm)
-{
- futex_exec_release(tsk);
+ futex_exit_exec_release(tsk);
mm_release(tsk, mm);
}
@@ -1594,6 +1599,7 @@ static int copy_mm(u64 clone_flags, struct task_struct *tsk)
tsk->mm = mm;
tsk->active_mm = mm;
+ sched_cache_fork(tsk);
return 0;
}
@@ -1613,9 +1619,27 @@ static int copy_exec_state(u64 clone_flags, struct task_struct *tsk)
return task_exec_state_copy(tsk);
}
-static int copy_fs(u64 clone_flags, struct task_struct *tsk)
+static int copy_fs(u64 clone_flags, struct task_struct *tsk, bool umh)
{
- struct fs_struct *fs = current->fs;
+ struct fs_struct *fs;
+
+ /*
+ * Usermodehelper may copy userspace_init_fs filesystem state but
+ * they don't get to create mount namespaces, share the
+ * filesystem state, or be started from a non-initial mount
+ * namespace.
+ */
+ if (umh) {
+ if (clone_flags & (CLONE_NEWNS | CLONE_FS))
+ return -EINVAL;
+ if (current->nsproxy->mnt_ns != &init_mnt_ns)
+ return -EINVAL;
+ fs = userspace_init_fs;
+ } else {
+ fs = current->fs;
+ VFS_WARN_ON_ONCE(current->fs != current->real_fs);
+ }
+
if (clone_flags & CLONE_FS) {
/* tsk->fs is already what we want */
read_seqlock_excl(&fs->seq);
@@ -1628,7 +1652,7 @@ static int copy_fs(u64 clone_flags, struct task_struct *tsk)
read_sequnlock_excl(&fs->seq);
return 0;
}
- tsk->fs = copy_fs_struct(fs);
+ tsk->real_fs = tsk->fs = copy_fs_struct(fs);
if (!tsk->fs)
return -ENOMEM;
return 0;
@@ -1973,9 +1997,9 @@ static bool need_futex_hash_allocate_default(u64 clone_flags)
{
/*
* Allocate a default futex hash for any sibling that will
- * share the parent's mm, except vfork.
+ * share the parent's mm.
*/
- return (clone_flags & (CLONE_VM | CLONE_VFORK)) == CLONE_VM;
+ return clone_flags & CLONE_VM;
}
/*
@@ -2110,6 +2134,11 @@ __latent_entropy struct task_struct *copy_process(
p = dup_task_struct(current, node);
if (!p)
goto fork_out;
+ /*
+ * Must run before the first fallible op, so error paths never
+ * free the parent's ret_stack.
+ */
+ ftrace_graph_init_task(p);
retval = copy_exec_state(clone_flags, p);
if (retval)
goto bad_fork_free;
@@ -2136,8 +2165,6 @@ __latent_entropy struct task_struct *copy_process(
*/
p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? args->child_tid : NULL;
- ftrace_graph_init_task(p);
-
rt_mutex_init_task(p);
raw_spin_lock_init(&p->blocked_lock);
@@ -2247,10 +2274,6 @@ __latent_entropy struct task_struct *copy_process(
p->sequential_io = 0;
p->sequential_io_avg = 0;
#endif
-#ifdef CONFIG_BPF_SYSCALL
- RCU_INIT_POINTER(p->bpf_storage, NULL);
- p->bpf_ctx = NULL;
-#endif
unwind_task_init(p);
@@ -2276,7 +2299,7 @@ __latent_entropy struct task_struct *copy_process(
retval = copy_files(clone_flags, p, args->no_files);
if (retval)
goto bad_fork_cleanup_semundo;
- retval = copy_fs(clone_flags, p);
+ retval = copy_fs(clone_flags, p, args->umh);
if (retval)
goto bad_fork_cleanup_files;
retval = copy_sighand(clone_flags, p);
@@ -2338,6 +2361,7 @@ __latent_entropy struct task_struct *copy_process(
#ifdef CONFIG_BLOCK
p->plug = NULL;
+ p->flags &= ~PF_BLOCK_TS;
#endif
futex_init_task(p);
@@ -2579,6 +2603,7 @@ bad_fork_cleanup_io:
bad_fork_cleanup_namespaces:
exit_nsproxy_namespaces(p);
bad_fork_cleanup_mm:
+ sched_cache_fork_cleanup(p);
if (p->mm) {
mm_clear_owner(p->mm, p);
mmput(p->mm);
@@ -2817,6 +2842,7 @@ pid_t user_mode_thread(int (*fn)(void *), void *arg, unsigned long flags)
.exit_signal = (flags & CSIGNAL),
.fn = fn,
.fn_arg = arg,
+ .umh = 1,
};
return kernel_clone(&args);
@@ -3214,7 +3240,7 @@ static int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp
*/
int ksys_unshare(unsigned long unshare_flags)
{
- struct fs_struct *fs, *new_fs = NULL;
+ struct fs_struct *new_fs = NULL;
struct files_struct *new_fd = NULL;
struct cred *new_cred = NULL;
struct nsproxy *new_nsproxy = NULL;
@@ -3245,6 +3271,10 @@ int ksys_unshare(unsigned long unshare_flags)
if (unshare_flags & CLONE_NEWNS)
unshare_flags |= CLONE_FS;
+ /* No unsharing with overriden fs state */
+ VFS_WARN_ON_ONCE(unshare_flags & (CLONE_NEWNS | CLONE_FS) &&
+ current->fs != current->real_fs);
+
err = check_unshare_flags(unshare_flags);
if (err)
goto bad_unshare_out;
@@ -3292,23 +3322,13 @@ int ksys_unshare(unsigned long unshare_flags)
new_nsproxy = NULL;
}
- task_lock(current);
-
- if (new_fs) {
- fs = current->fs;
- read_seqlock_excl(&fs->seq);
- current->fs = new_fs;
- if (--fs->users)
- new_fs = NULL;
- else
- new_fs = fs;
- read_sequnlock_excl(&fs->seq);
- }
+ if (new_fs)
+ new_fs = switch_fs_struct(new_fs);
- if (new_fd)
+ if (new_fd) {
+ guard(task_lock)(current);
swap(current->files, new_fd);
-
- task_unlock(current);
+ }
if (new_cred) {
/* Install the new user namespace */
diff --git a/kernel/futex/core.c b/kernel/futex/core.c
index 179b26e9c934..095f9fe440e3 100644
--- a/kernel/futex/core.c
+++ b/kernel/futex/core.c
@@ -45,26 +45,24 @@
#include <linux/rseq.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
+#include <linux/kmemleak.h>
+#include <linux/wait_bit.h>
#include <vdso/futex.h>
+#include <asm/runtime-const.h>
+
#include "futex.h"
#include "../locking/rtmutex_common.h"
-/*
- * The base of the bucket array and its size are always used together
- * (after initialization only in futex_hash()), so ensure that they
- * reside in the same cacheline.
- */
-static struct {
- unsigned long hashmask;
- unsigned int hashshift;
- struct futex_hash_bucket *queues[MAX_NUMNODES];
-} __futex_data __read_mostly __aligned(2*sizeof(long));
+static u32 __futex_mask __ro_after_init;
+static u32 __futex_shift __ro_after_init;
+static struct futex_hash_bucket **__futex_queues __ro_after_init;
-#define futex_hashmask (__futex_data.hashmask)
-#define futex_hashshift (__futex_data.hashshift)
-#define futex_queues (__futex_data.queues)
+static __always_inline struct futex_hash_bucket **futex_queues(void)
+{
+ return runtime_const_ptr(__futex_queues);
+}
struct futex_private_hash {
int state;
@@ -143,8 +141,14 @@ static bool futex_private_hash_get(struct futex_private_hash *fph)
void futex_private_hash_put(struct futex_private_hash *fph)
{
- if (fph && futex_ref_put(fph))
- wake_up_var(fph->mm);
+ struct mm_struct *mm;
+
+ if (!fph)
+ return;
+
+ mm = fph->mm;
+ if (futex_ref_put(fph))
+ wake_up_var(mm);
}
static struct futex_hash_bucket *
@@ -209,10 +213,12 @@ static bool __futex_pivot_hash(struct mm_struct *mm, struct futex_private_hash *
futex_rehash_private(fph, new);
}
new->state = FR_PERCPU;
- scoped_guard(rcu) {
- mmph->batches = get_state_synchronize_rcu();
- rcu_assign_pointer(mmph->hash, new);
- }
+ rcu_assign_pointer(mmph->hash, new);
+ /*
+ * mmph->batches must reference a grace period which started after
+ * mmph->hash was assigned. See futex_ref_drop().
+ */
+ mmph->batches = get_state_synchronize_rcu();
kvfree_rcu(fph, rcu);
return true;
}
@@ -395,13 +401,13 @@ __futex_hash(union futex_key *key, struct futex_private_hash *fph, struct futex_
* NOTE: this isn't perfectly uniform, but it is fast and
* handles sparse node masks.
*/
- node = (hash >> futex_hashshift) % nr_node_ids;
+ node = runtime_const_shift_right_32(hash, __futex_shift) % nr_node_ids;
if (!node_possible(node)) {
node = find_next_bit_wrap(node_possible_map.bits, nr_node_ids, node);
}
}
- return &futex_queues[node][hash & futex_hashmask];
+ return &futex_queues()[node][runtime_const_mask_32(hash, __futex_mask)];
}
/**
@@ -520,7 +526,7 @@ int get_futex_key(u32 __user *uaddr, unsigned int flags, union futex_key *key,
* The futex address must be "naturally" aligned.
*/
key->both.offset = address % PAGE_SIZE;
- if (unlikely((address % size) != 0))
+ if (unlikely((address & (size-1)) != 0))
return -EINVAL;
address -= key->both.offset;
@@ -982,8 +988,11 @@ retry:
return -1;
/*
- * Special case for regular (non PI) futexes. The unlock path in
- * user space has two race scenarios:
+ * Special case for regular (non PI) futexes. Ordinarily, we do
+ * not perform any processing here unless the current thread was
+ * the owner of the futex (by the TID check below).
+ *
+ * However, the unlock path has three race scenarios:
*
* 1. The unlock path releases the user space futex value and
* before it can execute the futex() syscall to wake up
@@ -992,42 +1001,70 @@ retry:
* 2. A woken up waiter is killed before it can acquire the
* futex in user space.
*
- * In the second case, the wake up notification could be generated
- * by the unlock path in user space after setting the futex value
- * to zero or by the kernel after setting the OWNER_DIED bit below.
+ * 3. A woken up waiter is killed in user space after another
+ * thread has acquired the futex, but before it can set
+ * FUTEX_WAITERS.
*
- * In both cases the TID validation below prevents a wakeup of
- * potential waiters which can cause these waiters to block
- * forever.
+ * Note that, if userspace uses the FUTEX_ROBUST_UNLOCK flag, we
+ * will not see case 1 here.
*
- * In both cases the following conditions are met:
+ * In the second and third case, the wake up notification could
+ * be generated from any of:
*
- * 1) task->futex.robust_list->list_op_pending != NULL
- * @pending_op == true
- * 2) The owner part of user space futex value == 0
+ * i. An ordinary futex wakeup after unlock (with or
+ * without FUTEX_ROBUST_UNLOCK)
+ * ii. A robust wakeup from another thread's death
+ * iii. A previous round through this special case
+ *
+ * As a result, the futex world will be in one of four states:
+ *
+ * A. The futex word is 0 (unlocked)
+ * B. The futex word is owned by another thread
+ * (FUTEX_WAITERS is not set)
+ * C. The futex word is owned by another thread
+ * (FUTEX_WAITERS set)
+ * D. The futex's owner died and OWNER_DIED is set
+ * (the owner part of the word is 0)
+ *
+ * The key issue is that the kernel usually (at least from
+ * sources ii. and iii. or when so requested by userspace from
+ * source i.) only ever wakes *one* waiter at a time. If this
+ * waiter dies before acquiring the futex (or setting the
+ * FUTEX_WAITERS bit), the kernel *must* still wake the next
+ * waiter down the line to uphold the futex invariants and
+ * avoid lost wakeups. Note we do not need to handle state C,
+ * as it does not matter to us whether *we* successfully set
+ * the bit or a third thread did so in the meantime.
+ *
+ * Therefore, in these cases we must issue an additional
+ * futex_wake(). Note however that we *must not* set OWNER_DIED
+ * here. Our thread is *not* the owner of the futex.
+ *
+ * Thus to summarize, the conditions for needing the additional
+ * futex_wake() are:
+ *
+ * 1) @pending_op == true (the thread has not finished the
+ * mutex operation)
+ * 2) The futex word is in one of the states A, B or D
* 3) Regular futex: @pi == false
*
- * If these conditions are met, it is safe to attempt waking up a
- * potential waiter without touching the user space futex value and
- * trying to set the OWNER_DIED bit. If the futex value is zero,
- * the rest of the user space mutex state is consistent, so a woken
- * waiter will just take over the uncontended futex. Setting the
- * OWNER_DIED bit would create inconsistent state and malfunction
- * of the user space owner died handling. Otherwise, the OWNER_DIED
- * bit is already set, and the woken waiter is expected to deal with
- * this.
+ * Note in particular that in all of the states A-D the owner
+ * portion of the futex word differs from our thread's TID
+ * (unless the actual owner has the same TID in another PID
+ * namespace, but we cannot currently distinguish that
+ * scenario), so this can be a special-case wakeup in the bail
+ * path of the ordinary TID check.
*/
owner = uval & FUTEX_TID_MASK;
- if (pending_op && !pi && !owner) {
- futex_wake(uaddr, FLAGS_SIZE_32 | FLAGS_SHARED, NULL, 1,
- FUTEX_BITSET_MATCH_ANY);
+ if (owner != task_pid_vnr(curr)) {
+ if (pending_op && !pi && (!owner || !(uval & FUTEX_WAITERS))) {
+ futex_wake(uaddr, FLAGS_SIZE_32 | FLAGS_SHARED, NULL, 1,
+ FUTEX_BITSET_MATCH_ANY);
+ }
return 0;
}
- if (owner != task_pid_vnr(curr))
- return 0;
-
/*
* Ok, this dying thread is truly holding a futex
* of interest. Set the OWNER_DIED bit atomically
@@ -1493,14 +1530,12 @@ static void futex_cleanup_begin(struct task_struct *tsk)
raw_spin_unlock_irq(&tsk->pi_lock);
}
-static void futex_cleanup_end(struct task_struct *tsk, int state)
+static void futex_cleanup_end(struct task_struct *tsk)
__releases(&tsk->futex.exit_mutex)
{
- /*
- * Lockless store. The only side effect is that an observer might
- * take another loop until it becomes visible.
- */
- tsk->futex.state = state;
+ scoped_guard(raw_spinlock_irq, &tsk->pi_lock)
+ tsk->futex.state = FUTEX_STATE_DEAD;
+
/*
* Drop the exit protection. This unblocks waiters which observed
* FUTEX_STATE_EXITING to reevaluate the state.
@@ -1508,29 +1543,46 @@ static void futex_cleanup_end(struct task_struct *tsk, int state)
mutex_unlock(&tsk->futex.exit_mutex);
}
-void futex_exec_release(struct task_struct *tsk)
+/*
+ * Invoked from mm_exit_exec_release() to cleanup the robust lists and pi state
+ * of the outgoing task.
+ *
+ * exec() makes it interesting for futexes because the TID of the task stays the
+ * same, but from a futex perspective the task has to be treated like an exiting
+ * task. This is especially important for the sanity check for private futexes
+ * in attach_to_pi_owner() which compares the owner's mm with the waiter's mm.
+ *
+ * That check would give the wrong answer if futex_cleanup_end() would
+ * set the state to FUTEX_STATE_OK as long as the task still has the old
+ * mm.
+ *
+ * After the task has switched to the new mm it sets it to
+ * FUTEX_STATE_OK again in futex_exec_done().
+ */
+void futex_exit_exec_release(struct task_struct *tsk)
{
- /*
- * The state handling is done for consistency, but in the case of
- * exec() there is no way to prevent further damage as the PID stays
- * the same. But for the unlikely and arguably buggy case that a
- * futex is held on exec(), this provides at least as much state
- * consistency protection which is possible.
- */
futex_cleanup_begin(tsk);
futex_cleanup(tsk);
- /*
- * Reset the state to FUTEX_STATE_OK. The task is alive and about
- * exec a new binary.
- */
- futex_cleanup_end(tsk, FUTEX_STATE_OK);
+ futex_cleanup_end(tsk);
}
-void futex_exit_release(struct task_struct *tsk)
+/*
+ * exec() has switched to the new mm. Futex operations are safe again.
+ */
+void futex_exec_done(struct task_struct *tsk)
{
- futex_cleanup_begin(tsk);
- futex_cleanup(tsk);
- futex_cleanup_end(tsk, FUTEX_STATE_DEAD);
+ /*
+ * This store does not have to take tsk::futex::exit_mutex because the
+ * phase where waiters block on it during state FUTEX_STATE_EXITING has
+ * been finished when futex_cleanup_end() set the state to
+ * FUTEX_STATE_DEAD.
+ *
+ * This transitions back from FUTEX_STATE_DEAD to FUTEX_STATE_OK. The
+ * ordering guarantee required here is that the previous store to
+ * tsk::mm in the calling code cannot be reordered against this store.
+ */
+ guard(raw_spinlock_irq)(&tsk->pi_lock);
+ tsk->futex.state = FUTEX_STATE_OK;
}
static void futex_hash_bucket_init(struct futex_hash_bucket *fhb)
@@ -1746,20 +1798,20 @@ void futex_hash_free(struct mm_struct *mm)
free_percpu(mm->futex.phash.ref);
kvfree(mm->futex.phash.hash_new);
fph = rcu_dereference_raw(mm->futex.phash.hash);
- if (fph)
- kvfree(fph);
+ kvfree(fph);
}
static bool futex_pivot_pending(struct mm_struct *mm)
{
+ struct futex_mm_phash *mmph = &mm->futex.phash;
struct futex_private_hash *fph;
- guard(rcu)();
+ guard(mutex)(&mmph->lock);
- if (!mm->futex.phash.hash_new)
+ if (!mmph->hash_new)
return true;
- fph = rcu_dereference(mm->futex.phash.hash);
+ fph = rcu_dereference_raw(mmph->hash);
return futex_ref_is_dead(fph);
}
@@ -1810,18 +1862,22 @@ static int futex_hash_allocate(unsigned int hash_slots, unsigned int flags)
}
if (!mm->futex.phash.ref) {
+ unsigned int __percpu *ref = alloc_percpu(unsigned int);
+
+ if (!ref)
+ return -ENOMEM;
+
/*
- * This will always be allocated by the first thread and
- * therefore requires no locking.
+ * Tasks sharing the mm can run this concurrently, so take the
+ * initial reference before publishing the counter.
*/
- mm->futex.phash.ref = alloc_percpu(unsigned int);
- if (!mm->futex.phash.ref)
- return -ENOMEM;
- this_cpu_inc(*mm->futex.phash.ref); /* 0 -> 1 */
+ this_cpu_inc(*ref); /* 0 -> 1 */
+ if (cmpxchg(&mm->futex.phash.ref, NULL, ref))
+ free_percpu(ref);
}
- fph = kvzalloc(struct_size(fph, queues, hash_slots),
- GFP_KERNEL_ACCOUNT | __GFP_NOWARN);
+ fph = kvzalloc_flex(*fph, queues, hash_slots,
+ GFP_KERNEL_ACCOUNT | __GFP_NOWARN);
if (!fph)
return -ENOMEM;
@@ -1833,11 +1889,35 @@ static int futex_hash_allocate(unsigned int hash_slots, unsigned int flags)
futex_hash_bucket_init(&fph->queues[i]);
if (custom) {
+ struct wait_bit_queue_entry __wbq_entry;
+ struct wait_queue_head *__wq_head;
+
/*
* Only let prctl() wait / retry; don't unduly delay clone().
*/
again:
- wait_var_event(mm, futex_pivot_pending(mm));
+ __wq_head = __var_waitqueue(mm);
+ init_wait_var_entry(&__wbq_entry, mm, 0);
+ __wbq_entry.wq_entry.func = woken_wake_bit_function;
+ add_wait_queue(__wq_head, &__wbq_entry.wq_entry);
+
+ /*
+ * add_wait_queue() futex_ref_put()
+ * MB (this) MB (implied)
+ * futex_pivot_pending() wake_up_var()
+ * waitqueue_active()
+ *
+ * Notably, it must not be possible to see
+ * !futex_pivot_pending() && !waitqueue_active().
+ */
+ smp_mb();
+
+ while (!futex_pivot_pending(mm) &&
+ wait_woken(&__wbq_entry.wq_entry, TASK_UNINTERRUPTIBLE,
+ MAX_SCHEDULE_TIMEOUT))
+ /* empty */;
+
+ remove_wait_queue(__wq_head, &__wbq_entry.wq_entry);
}
scoped_guard(mutex, &mm->futex.phash.lock) {
@@ -1922,7 +2002,7 @@ int futex_hash_allocate_default(void)
* 16 <= threads * 4 <= global hash size
*/
buckets = roundup_pow_of_two(4 * threads);
- buckets = clamp(buckets, 16, futex_hashmask + 1);
+ buckets = clamp(buckets, 16, __futex_mask + 1);
if (current_buckets >= buckets)
return 0;
@@ -2020,10 +2100,22 @@ static int __init futex_init(void)
hashsize = max(4, hashsize);
hashsize = roundup_pow_of_two(hashsize);
#endif
- futex_hashshift = ilog2(hashsize);
+ __futex_mask = hashsize - 1;
+ __futex_shift = ilog2(hashsize);
size = sizeof(struct futex_hash_bucket) * hashsize;
order = get_order(size);
+ __futex_queues = kzalloc_objs(*__futex_queues, nr_node_ids);
+ kmemleak_not_leak(__futex_queues);
+
+ runtime_const_init(shift, __futex_shift);
+ runtime_const_init(mask, __futex_mask);
+ runtime_const_init(ptr, __futex_queues);
+
+ barrier();
+
+ BUG_ON(!futex_queues());
+
for_each_node(n) {
struct futex_hash_bucket *table;
@@ -2037,10 +2129,9 @@ static int __init futex_init(void)
for (i = 0; i < hashsize; i++)
futex_hash_bucket_init(&table[i]);
- futex_queues[n] = table;
+ futex_queues()[n] = table;
}
- futex_hashmask = hashsize - 1;
pr_info("futex hash table entries: %lu (%lu bytes on %d NUMA nodes, total %lu KiB, %s).\n",
hashsize, size, num_possible_nodes(), size * num_possible_nodes() / 1024,
order > MAX_PAGE_ORDER ? "vmalloc" : "linear");
diff --git a/kernel/futex/pi.c b/kernel/futex/pi.c
index 795011ea1202..98f1b962e59a 100644
--- a/kernel/futex/pi.c
+++ b/kernel/futex/pi.c
@@ -193,6 +193,58 @@ void put_pi_state(struct futex_pi_state *pi_state)
* pi_mutex->wait_lock
* p->pi_lock
*
+ * Futex kernel state:
+ *
+ * The kernel tracks the task state in p::futex::state to protect against exit()
+ * and exec(). The states are:
+ *
+ * - FUTEX_STATE_OK when the task is alive and waiters can be attached
+ *
+ * - FUTEX_STATE_EXITING when the task cleans up the robust list and PI
+ * state. Concurrent waiters cannot attach anymore and have to wait until the
+ * cleanup is finished to re-evaluate the potential changes caused by the
+ * robust list and PI state cleanups.
+ *
+ * - FUTEX_STATE_DEAD when the task has cleaned up the robust list. This state
+ * is set independent of exit() or exec(). In the exit() case the task is
+ * gone. In the exec() case this ensures that nothing can attach to the task
+ * after cleaning up the robust list and PI state before it has switched to
+ * the new mm. From a futex point of view the task is dead until it sets the
+ * state to FUTEX_STATE_OK again after switching to the new mm.
+ *
+ * The valid state transitions for exit():
+ *
+ * FUTEX_STATE_OK -> FUTEX_STATE_EXITING -> FUTEX_STATE_DEAD
+ *
+ * The valid state transitions for exec():
+ *
+ * FUTEX_STATE_OK -> FUTEX_STATE_EXITING -> FUTEX_STATE_DEAD -> FUTEX_STATE_OK
+ *
+ * The state has two related locks:
+ *
+ * 1) p::pi_lock
+ *
+ * p::pi_lock has to be taken by the waiter when evaluating the state to
+ * protect against a concurrent exit/exec cleanup by the owner. If the state
+ * is OK then the waiter can be attached to the owner while still holding
+ * pi_lock.
+ *
+ * The cleanup code has to hold it for all state transitions to ensure that
+ * the stores to the state cannot be reordered against previous stores on
+ * which the waiter correctness depends on.
+ *
+ * 2) p::futex::exit_mutex
+ *
+ * The mutex is acquired when the cleanup starts and released at the end. It
+ * obviously is not serializing the owner's cleanup against itself. It is
+ * used to avoid a live lock caused by a waiter preempting the owner's
+ * cleanup. Such a waiter would busy loop forever waiting for the owner to
+ * finish the cleanup.
+ *
+ * To prevent this, waiters have to drop all locks when observing
+ * FUTEX_STATE_EXITING and block on the mutex. When the owner releases the
+ * mutex after finishing the cleanup the waiters make progress and
+ * re-evaluate the situation.
*/
/*
@@ -318,19 +370,11 @@ out_error:
return ret;
}
-static int handle_exit_race(u32 __user *uaddr, u32 uval,
- struct task_struct *tsk)
+static int handle_exit_race(u32 __user *uaddr, u32 uval)
{
u32 uval2;
/*
- * If the futex exit state is not yet FUTEX_STATE_DEAD, tell the
- * caller that the alleged owner is busy.
- */
- if (tsk && tsk->futex.state != FUTEX_STATE_DEAD)
- return -EBUSY;
-
- /*
* Reread the user space value to handle the following situation:
*
* CPU0 CPU1
@@ -427,7 +471,7 @@ static int attach_to_pi_owner(u32 __user *uaddr, u32 uval, union futex_key *key,
return -EAGAIN;
p = find_get_task_by_vpid(pid);
if (!p)
- return handle_exit_race(uaddr, uval, NULL);
+ return handle_exit_race(uaddr, uval);
if (unlikely(p->flags & PF_KTHREAD)) {
put_task_struct(p);
@@ -435,34 +479,55 @@ static int attach_to_pi_owner(u32 __user *uaddr, u32 uval, union futex_key *key,
}
/*
- * We need to look at the task state to figure out, whether the
- * task is exiting. To protect against the change of the task state
- * in futex_exit_release(), we do this protected by p->pi_lock:
+ * We need to look at the task state to figure out whether the task is
+ * exiting. To protect against the change of the task state from
+ * FUTEX_STATE_OK to FUTEX_STATE_EXISTING in futex_cleanup_begin() it is
+ * required to do this protected by p->pi_lock, which prevents the owner
+ * from concurrently starting the exit cleanup.
+ *
+ * If the state is FUTEX_STATE_OK pi_lock must be held until the waiter
+ * is attached to protect against a concurrent exit()/exec().
*/
raw_spin_lock_irq(&p->pi_lock);
+
+ /* Validate that the task is ready for futex operations. */
if (unlikely(p->futex.state != FUTEX_STATE_OK)) {
/*
- * The task is on the way out. When the futex state is
- * FUTEX_STATE_DEAD, we know that the task has finished
- * the cleanup:
+ * The task is on the way out. When state is FUTEX_STATE_EXITING
+ * the cleanup is in progress. To avoid a live lock when the
+ * waiter preempted the owner, store the task pointer in
+ * @exiting and keep the reference on the task. The calling code
+ * will drop all locks, block on @p::futex::exit_mutex and wait
+ * for the owner to finish the cleanup. Once the owner released
+ * the mutex the waiter drops the reference count and
+ * re-evaluates the situation.
*/
- int ret = handle_exit_race(uaddr, uval, p);
+ if (p->futex.state == FUTEX_STATE_EXITING) {
+ raw_spin_unlock_irq(&p->pi_lock);
+ *exiting = p;
+ return -EBUSY;
+ }
+
+ int ret = handle_exit_race(uaddr, uval);
raw_spin_unlock_irq(&p->pi_lock);
+ put_task_struct(p);
+ return ret;
+ }
+
+ if (IS_ENABLED(CONFIG_MMU) && futex_key_is_private(key)) {
/*
- * If the owner task is between FUTEX_STATE_EXITING and
- * FUTEX_STATE_DEAD then store the task pointer and keep
- * the reference on the task struct. The calling code will
- * drop all locks, wait for the task to reach
- * FUTEX_STATE_DEAD and then drop the refcount. This is
- * required to prevent a live lock when the current task
- * preempted the exiting task between the two states.
+ * A private futex key holds a pointer to the waiter's mm
+ * without holding a reference on it. So it must not be attached
+ * to an owner in a different address space. Otherwise that
+ * owner's exit cleanup could access the private hash after the
+ * key's mm is freed.
*/
- if (ret == -EBUSY)
- *exiting = p;
- else
+ if (unlikely(p->mm != key->private.mm)) {
+ raw_spin_unlock_irq(&p->pi_lock);
put_task_struct(p);
- return ret;
+ return -EPERM;
+ }
}
__attach_to_pi_owner(p, key, ps);
@@ -1005,17 +1070,11 @@ retry_private:
* Caution; releasing @hb in-scope. The hb->lock is still locked
* while the reference is dropped. The reference can not be dropped
* after the unlock because if a user initiated resize is in progress
- * then we might need to wake him. This can not be done after the
- * rt_mutex_pre_schedule() invocation. The hb will remain valid because
- * the thread, performing resize, will block on hb->lock during
- * the requeue.
+ * then we might need to wake him. The hb will remain valid
+ * because the thread, performing resize, will block on
+ * hb->lock during the requeue.
*/
futex_private_hash_put(no_free_ptr(hbr.fph));
- /*
- * Must be done before we enqueue the waiter, here is unfortunately
- * under the hb lock, but that *should* work because it does nothing.
- */
- rt_mutex_pre_schedule();
rt_mutex_init_waiter(&rt_waiter);
@@ -1081,10 +1140,6 @@ cleanup:
* the
*/
futex_q_lockptr_lock(&q);
- /*
- * Waiter is unqueued.
- */
- rt_mutex_post_schedule();
no_block:
/*
* Fixup the pi_state owner and possibly acquire the lock if we
diff --git a/kernel/futex/requeue.c b/kernel/futex/requeue.c
index 7384672916fb..b3f4a4bccb12 100644
--- a/kernel/futex/requeue.c
+++ b/kernel/futex/requeue.c
@@ -154,8 +154,16 @@ static inline void futex_requeue_pi_complete(struct futex_q *q, int locked)
} while (!atomic_try_cmpxchg(&q->requeue_state, &old, new));
#ifdef CONFIG_PREEMPT_RT
- /* If the waiter interleaved with the requeue let it know */
- if (unlikely(old == Q_REQUEUE_PI_WAIT))
+ /*
+ * The waiter in futex_requeue_pi_wakeup_sync() can interleave with the
+ * wake below: It will assign Q_REQUEUE_PI_IN_PROGRESS and here it will
+ * be updated to Q_REQUEUE_PI_LOCKED (locked = 1). The rcuwait_wait_event()
+ * will already read Q_REQUEUE_PI_LOCKED and skip the schedule() invocation,
+ * leading to an access of futex_q::requeue_wait after the waiter returned.
+ * In this case only we skip the wake here and rely on following wake in
+ * requeue_pi_wake_futex() to perform the wake if needed.
+ */
+ if (unlikely(old == Q_REQUEUE_PI_WAIT) && new != Q_REQUEUE_PI_LOCKED)
rcuwait_wake_up(&q->requeue_wait);
#endif
}
@@ -645,12 +653,6 @@ retry_private:
continue;
}
- /* Self-deadlock: non-top waiter already owns the PI futex. */
- if (rt_mutex_owner(&pi_state->pi_mutex) == this->task) {
- ret = -EDEADLK;
- break;
- }
-
ret = rt_mutex_start_proxy_lock(&pi_state->pi_mutex,
this->rt_waiter,
this->task);
diff --git a/kernel/irq/Kconfig b/kernel/irq/Kconfig
index 05cba4e16dad..6923f37eaab4 100644
--- a/kernel/irq/Kconfig
+++ b/kernel/irq/Kconfig
@@ -150,6 +150,18 @@ config IRQ_KUNIT_TEST
If unsure, say N.
+config REFCOUNT_INTERRUPT_KUNIT_TEST
+ tristate "Test refcounted interrupt enable/disable" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ This builds the kunit tests for the refcounted interrupt
+ infrastructure. It verifies the correctness of single, nested,
+ and multiple interrupt enable/disable state changes and ensures
+ that the underlying reference counting mechanisms work as expected.
+
+ If unsure, say N.
+
endmenu
config GENERIC_IRQ_MULTI_HANDLER
diff --git a/kernel/irq/Makefile b/kernel/irq/Makefile
index 86a2e5ae08f9..0e5df962a149 100644
--- a/kernel/irq/Makefile
+++ b/kernel/irq/Makefile
@@ -16,3 +16,4 @@ obj-$(CONFIG_SMP) += affinity.o
obj-$(CONFIG_GENERIC_IRQ_DEBUGFS) += debugfs.o
obj-$(CONFIG_GENERIC_IRQ_MATRIX_ALLOCATOR) += matrix.o
obj-$(CONFIG_IRQ_KUNIT_TEST) += irq_test.o
+obj-$(CONFIG_REFCOUNT_INTERRUPT_KUNIT_TEST) += refcount_interrupt_test.o
diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c
index 80ef4e27dcf4..3a818f07a101 100644
--- a/kernel/irq/irqdesc.c
+++ b/kernel/irq/irqdesc.c
@@ -1004,7 +1004,7 @@ unsigned int kstat_irqs_cpu(unsigned int irq, int cpu)
{
struct irq_desc *desc = irq_to_desc(irq);
- return desc && desc->kstat_irqs ? per_cpu(desc->kstat_irqs->cnt, cpu) : 0;
+ return desc ? irq_desc_kstat_cpu(desc, cpu) : 0;
}
static unsigned int kstat_irqs_desc(struct irq_desc *desc, const struct cpumask *cpumask)
@@ -1026,7 +1026,7 @@ static unsigned int kstat_irqs(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);
- if (!desc || !desc->kstat_irqs)
+ if (!desc)
return 0;
return kstat_irqs_desc(desc, cpu_possible_mask);
}
@@ -1038,18 +1038,15 @@ void kstat_snapshot_irqs(void)
struct irq_desc *desc;
unsigned int irq;
- for_each_irq_desc(irq, desc) {
- if (!desc->kstat_irqs)
- continue;
+ for_each_irq_desc(irq, desc)
this_cpu_write(desc->kstat_irqs->ref, this_cpu_read(desc->kstat_irqs->cnt));
- }
}
unsigned int kstat_get_irq_since_snapshot(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);
- if (!desc || !desc->kstat_irqs)
+ if (!desc)
return 0;
return this_cpu_read(desc->kstat_irqs->cnt) - this_cpu_read(desc->kstat_irqs->ref);
}
diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c
index f15c9f1223bb..57c819da30c2 100644
--- a/kernel/irq/irqdomain.c
+++ b/kernel/irq/irqdomain.c
@@ -1658,7 +1658,7 @@ static int irq_domain_alloc_irqs_locked(struct irq_domain *domain, int irq_base,
for (i = 0; i < nr_irqs; i++) {
ret = irq_domain_trim_hierarchy(virq + i);
if (ret)
- goto out_free_irq_data;
+ goto out_free_irqs;
}
for (i = 0; i < nr_irqs; i++)
@@ -1666,6 +1666,8 @@ static int irq_domain_alloc_irqs_locked(struct irq_domain *domain, int irq_base,
return virq;
+out_free_irqs:
+ irq_domain_free_irqs_hierarchy(domain, virq, nr_irqs);
out_free_irq_data:
irq_domain_free_irq_data(virq, nr_irqs);
out_free_desc:
@@ -1963,7 +1965,7 @@ EXPORT_SYMBOL_GPL(irq_domain_free_irqs_parent);
static void __irq_domain_deactivate_irq(struct irq_data *irq_data)
{
- if (irq_data && irq_data->domain) {
+ if (irq_data->domain) {
struct irq_domain *domain = irq_data->domain;
if (domain->ops->deactivate)
@@ -1977,7 +1979,7 @@ static int __irq_domain_activate_irq(struct irq_data *irqd, bool reserve)
{
int ret = 0;
- if (irqd && irqd->domain) {
+ if (irqd->domain) {
struct irq_domain *domain = irqd->domain;
if (irqd->parent_data)
diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
index 7eb07e3bdb4c..57eff26fa646 100644
--- a/kernel/irq/manage.c
+++ b/kernel/irq/manage.c
@@ -171,6 +171,7 @@ int irq_can_set_affinity(unsigned int irq)
{
return __irq_can_set_affinity(irq_to_desc(irq));
}
+EXPORT_SYMBOL_GPL(irq_can_set_affinity);
/**
* irq_can_set_affinity_usr - Check if affinity of a irq can be set from user space
@@ -2305,7 +2306,7 @@ int request_nmi(unsigned int irq, irq_handler_t handler,
!irq_supports_nmi(desc))
return -EINVAL;
- action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
+ action = kzalloc_obj(struct irqaction);
if (!action)
return -ENOMEM;
@@ -2703,11 +2704,7 @@ static int __irq_get_irqchip_state(struct irq_data *data, enum irqchip_irq_state
return -ENODEV;
if (chip->irq_get_irqchip_state)
break;
-#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
- data = data->parent_data;
-#else
- data = NULL;
-#endif
+ data = irqd_get_parent_data(data);
} while (data);
if (data)
diff --git a/kernel/irq/msi.c b/kernel/irq/msi.c
index 903be7289c53..fb5f372215bf 100644
--- a/kernel/irq/msi.c
+++ b/kernel/irq/msi.c
@@ -1683,7 +1683,6 @@ void msi_domain_free_irqs_range(struct device *dev, unsigned int domid,
guard(msi_descs_lock)(dev);
msi_domain_free_irqs_range_locked(dev, domid, first, last);
}
-EXPORT_SYMBOL_GPL(msi_domain_free_irqs_all);
/**
* msi_domain_free_irqs_all_locked - Free all interrupts from a MSI interrupt domain
@@ -1714,6 +1713,7 @@ void msi_domain_free_irqs_all(struct device *dev, unsigned int domid)
guard(msi_descs_lock)(dev);
msi_domain_free_irqs_all_locked(dev, domid);
}
+EXPORT_SYMBOL_GPL(msi_domain_free_irqs_all);
/**
* msi_device_domain_free_wired - Free a wired interrupt in @domain
diff --git a/kernel/irq/refcount_interrupt_test.c b/kernel/irq/refcount_interrupt_test.c
new file mode 100644
index 000000000000..ca904dba24b9
--- /dev/null
+++ b/kernel/irq/refcount_interrupt_test.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit test for refcounted interrupt enable/disables.
+ */
+
+#include <kunit/test.h>
+#include <linux/interrupt_rc.h>
+
+#define TEST_IRQ_ON() KUNIT_EXPECT_FALSE(test, irqs_disabled())
+#define TEST_IRQ_OFF() KUNIT_EXPECT_TRUE(test, irqs_disabled())
+
+/* ===== Test cases ===== */
+static void test_single_irq_change(struct kunit *test)
+{
+ local_interrupt_disable();
+ TEST_IRQ_OFF();
+ local_interrupt_enable();
+}
+
+static void test_nested_irq_change(struct kunit *test)
+{
+ local_interrupt_disable();
+ TEST_IRQ_OFF();
+ local_interrupt_disable();
+ TEST_IRQ_OFF();
+ local_interrupt_disable();
+ TEST_IRQ_OFF();
+
+ local_interrupt_enable();
+ TEST_IRQ_OFF();
+ local_interrupt_enable();
+ TEST_IRQ_OFF();
+ local_interrupt_enable();
+ TEST_IRQ_ON();
+}
+
+static void test_multiple_irq_change(struct kunit *test)
+{
+ local_interrupt_disable();
+ TEST_IRQ_OFF();
+ local_interrupt_disable();
+ TEST_IRQ_OFF();
+
+ local_interrupt_enable();
+ TEST_IRQ_OFF();
+ local_interrupt_enable();
+ TEST_IRQ_ON();
+
+ local_interrupt_disable();
+ TEST_IRQ_OFF();
+ local_interrupt_enable();
+ TEST_IRQ_ON();
+}
+
+static void test_irq_save(struct kunit *test)
+{
+ unsigned long flags;
+
+ local_irq_save(flags);
+ TEST_IRQ_OFF();
+ local_interrupt_disable();
+ TEST_IRQ_OFF();
+ local_interrupt_enable();
+ TEST_IRQ_OFF();
+ local_irq_restore(flags);
+ TEST_IRQ_ON();
+
+ local_interrupt_disable();
+ TEST_IRQ_OFF();
+ local_irq_save(flags);
+ TEST_IRQ_OFF();
+ local_irq_restore(flags);
+ TEST_IRQ_OFF();
+ local_interrupt_enable();
+ TEST_IRQ_ON();
+}
+
+static struct kunit_case test_cases[] = {
+ KUNIT_CASE(test_single_irq_change),
+ KUNIT_CASE(test_nested_irq_change),
+ KUNIT_CASE(test_multiple_irq_change),
+ KUNIT_CASE(test_irq_save),
+ {},
+};
+
+/* init and exit are the same. */
+static int test_init(struct kunit *test)
+{
+ TEST_IRQ_ON();
+
+ return 0;
+}
+
+static void test_exit(struct kunit *test)
+{
+ TEST_IRQ_ON();
+}
+
+static struct kunit_suite refcount_interrupt_test_suite = {
+ .name = "refcount_interrupt",
+ .test_cases = test_cases,
+ .init = test_init,
+ .exit = test_exit,
+};
+
+kunit_test_suite(refcount_interrupt_test_suite);
+MODULE_AUTHOR("Lyude Paul <lyude@redhat.com>");
+MODULE_DESCRIPTION("Refcounted interrupt unit test suite");
+MODULE_LICENSE("GPL");
diff --git a/kernel/jump_label.c b/kernel/jump_label.c
index e851e4b37d0e..ab6b774bcfd7 100644
--- a/kernel/jump_label.c
+++ b/kernel/jump_label.c
@@ -726,12 +726,11 @@ static int jump_label_add_module(struct module *mod)
if (static_key_sealed(key))
goto do_poke;
- jlm = kzalloc(sizeof(struct static_key_mod), GFP_KERNEL);
+ jlm = kzalloc_obj(struct static_key_mod);
if (!jlm)
return -ENOMEM;
if (!static_key_linked(key)) {
- jlm2 = kzalloc(sizeof(struct static_key_mod),
- GFP_KERNEL);
+ jlm2 = kzalloc_obj(struct static_key_mod);
if (!jlm2) {
kfree(jlm);
return -ENOMEM;
diff --git a/kernel/kcmp.c b/kernel/kcmp.c
index 7c1a65bd5f8d..76476aeee067 100644
--- a/kernel/kcmp.c
+++ b/kernel/kcmp.c
@@ -186,7 +186,7 @@ SYSCALL_DEFINE5(kcmp, pid_t, pid1, pid_t, pid2, int, type,
ret = kcmp_ptr(task1->files, task2->files, KCMP_FILES);
break;
case KCMP_FS:
- ret = kcmp_ptr(task1->fs, task2->fs, KCMP_FS);
+ ret = kcmp_ptr(task1->real_fs, task2->real_fs, KCMP_FS);
break;
case KCMP_SIGHAND:
ret = kcmp_ptr(task1->sighand, task2->sighand, KCMP_SIGHAND);
diff --git a/kernel/kcov.c b/kernel/kcov.c
index 1df373fb562b..35420f0ac524 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -86,17 +86,12 @@ struct kcov_remote {
static DEFINE_SPINLOCK(kcov_remote_lock);
static DEFINE_HASHTABLE(kcov_remote_map, 4);
-static struct list_head kcov_remote_areas = LIST_HEAD_INIT(kcov_remote_areas);
+static struct list_head kcov_remote_areas[2] = {
+ LIST_HEAD_INIT(kcov_remote_areas[0]), LIST_HEAD_INIT(kcov_remote_areas[1])
+};
struct kcov_percpu_data {
- void *irq_area;
local_lock_t lock;
-
- unsigned int saved_mode;
- unsigned int saved_size;
- void *saved_area;
- struct kcov *saved_kcov;
- int saved_sequence;
};
static DEFINE_PER_CPU(struct kcov_percpu_data, kcov_percpu_data) = {
@@ -132,12 +127,13 @@ static struct kcov_remote *kcov_remote_add(struct kcov *kcov, u64 handle)
}
/* Must be called with kcov_remote_lock locked. */
-static struct kcov_remote_area *kcov_remote_area_get(unsigned int size)
+static struct kcov_remote_area *kcov_remote_area_get(unsigned int size, bool irq)
{
struct kcov_remote_area *area;
struct list_head *pos;
+ struct list_head *list = &kcov_remote_areas[irq];
- list_for_each(pos, &kcov_remote_areas) {
+ list_for_each(pos, list) {
area = list_entry(pos, struct kcov_remote_area, list);
if (area->size == size) {
list_del(&area->list);
@@ -149,11 +145,11 @@ static struct kcov_remote_area *kcov_remote_area_get(unsigned int size)
/* Must be called with kcov_remote_lock locked. */
static void kcov_remote_area_put(struct kcov_remote_area *area,
- unsigned int size)
+ unsigned int size, bool irq)
{
INIT_LIST_HEAD(&area->list);
area->size = size;
- list_add(&area->list, &kcov_remote_areas);
+ list_add(&area->list, &kcov_remote_areas[irq]);
/*
* KMSAN doesn't instrument this file, so it may not know area->list
* is initialized. Unpoison it explicitly to avoid reports in
@@ -390,6 +386,12 @@ void kcov_task_init(struct task_struct *t)
kcov_task_reset(t);
t->kcov_remote = NULL;
t->kcov_handle = current->kcov_handle;
+ t->kcov_softirq = 0;
+ t->kcov_saved_mode = 0;
+ t->kcov_saved_size = 0;
+ t->kcov_saved_area = NULL;
+ t->kcov_saved_kcov = NULL;
+ t->kcov_saved_sequence = 0;
}
static void kcov_reset(struct kcov *kcov)
@@ -512,7 +514,7 @@ static int kcov_mmap(struct file *filep, struct vm_area_struct *vma)
spin_lock_irqsave(&kcov->lock, flags);
size = kcov->size * sizeof(unsigned long);
- if (kcov->area == NULL || vma->vm_pgoff != 0 ||
+ if (kcov->area == NULL || vma_start_pgoff(vma) ||
vma->vm_end - vma->vm_start != size) {
res = -EINVAL;
goto exit;
@@ -836,17 +838,16 @@ static inline bool kcov_mode_enabled(unsigned int mode)
static void kcov_remote_softirq_start(struct task_struct *t)
__must_hold(&kcov_percpu_data.lock)
{
- struct kcov_percpu_data *data = this_cpu_ptr(&kcov_percpu_data);
unsigned int mode;
mode = READ_ONCE(t->kcov_mode);
barrier();
if (kcov_mode_enabled(mode)) {
- data->saved_mode = mode;
- data->saved_size = t->kcov_size;
- data->saved_area = t->kcov_area;
- data->saved_sequence = t->kcov_sequence;
- data->saved_kcov = t->kcov;
+ t->kcov_saved_mode = mode;
+ t->kcov_saved_size = t->kcov_size;
+ t->kcov_saved_area = t->kcov_area;
+ t->kcov_saved_sequence = t->kcov_sequence;
+ t->kcov_saved_kcov = t->kcov;
kcov_stop(t);
}
}
@@ -854,17 +855,15 @@ static void kcov_remote_softirq_start(struct task_struct *t)
static void kcov_remote_softirq_stop(struct task_struct *t)
__must_hold(&kcov_percpu_data.lock)
{
- struct kcov_percpu_data *data = this_cpu_ptr(&kcov_percpu_data);
-
- if (data->saved_kcov) {
- kcov_start(t, data->saved_kcov, data->saved_size,
- data->saved_area, data->saved_mode,
- data->saved_sequence);
- data->saved_mode = 0;
- data->saved_size = 0;
- data->saved_area = NULL;
- data->saved_sequence = 0;
- data->saved_kcov = NULL;
+ if (t->kcov_saved_kcov) {
+ kcov_start(t, t->kcov_saved_kcov, t->kcov_saved_size,
+ t->kcov_saved_area, t->kcov_saved_mode,
+ t->kcov_saved_sequence);
+ t->kcov_saved_mode = 0;
+ t->kcov_saved_size = 0;
+ t->kcov_saved_area = NULL;
+ t->kcov_saved_sequence = 0;
+ t->kcov_saved_kcov = NULL;
}
}
@@ -927,17 +926,17 @@ void kcov_remote_start(u64 handle)
sequence = kcov->sequence;
if (in_task()) {
size = kcov->remote_size;
- area = kcov_remote_area_get(size);
+ area = kcov_remote_area_get(size, false);
} else {
size = CONFIG_KCOV_IRQ_AREA_SIZE;
- area = this_cpu_ptr(&kcov_percpu_data)->irq_area;
+ area = kcov_remote_area_get(size, true);
}
spin_unlock(&kcov_remote_lock);
- /* Can only happen when in_task(). */
+ /* Allocate new buffer if we can sleep. */
if (!area) {
local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
- area = vmalloc(size * sizeof(unsigned long));
+ area = in_task() ? vmalloc(size * sizeof(unsigned long)) : NULL;
if (!area) {
kcov_put(kcov);
return;
@@ -1079,11 +1078,9 @@ void kcov_remote_stop(void)
kcov_move_area(kcov->mode, kcov->area, kcov->size, area);
spin_unlock(&kcov->lock);
- if (in_task()) {
- spin_lock(&kcov_remote_lock);
- kcov_remote_area_put(area, size);
- spin_unlock(&kcov_remote_lock);
- }
+ spin_lock(&kcov_remote_lock);
+ kcov_remote_area_put(area, size, !in_task());
+ spin_unlock(&kcov_remote_lock);
local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
@@ -1129,14 +1126,21 @@ static void __init selftest(void)
static int __init kcov_init(void)
{
- int cpu;
+ int cpu = num_possible_cpus();
+
+#ifdef CONFIG_PREEMPT_RT
+ /* Allocate some extra buffers in order to prepare for softirq preemption. */
+ cpu = cpu >= 4 ? cpu * 2 : cpu + 4;
+#endif
+ while (cpu--) {
+ void *area = vmalloc(CONFIG_KCOV_IRQ_AREA_SIZE * sizeof(unsigned long));
+ unsigned long flags;
- for_each_possible_cpu(cpu) {
- void *area = vmalloc_node(CONFIG_KCOV_IRQ_AREA_SIZE *
- sizeof(unsigned long), cpu_to_node(cpu));
if (!area)
return -ENOMEM;
- per_cpu_ptr(&kcov_percpu_data, cpu)->irq_area = area;
+ spin_lock_irqsave(&kcov_remote_lock, flags);
+ kcov_remote_area_put(area, CONFIG_KCOV_IRQ_AREA_SIZE, true);
+ spin_unlock_irqrestore(&kcov_remote_lock, flags);
}
/*
diff --git a/kernel/kcsan/core.c b/kernel/kcsan/core.c
index 8a7baf4e332e..2db82661cd60 100644
--- a/kernel/kcsan/core.c
+++ b/kernel/kcsan/core.c
@@ -585,8 +585,14 @@ kcsan_setup_watchpoint(const volatile void *ptr, size_t size, int type, unsigned
* information is lost if dirtied by KCSAN.
*/
kcsan_save_irqtrace(current);
- if (!interrupt_watcher)
+ if (!interrupt_watcher) {
local_irq_save(irq_flags);
+ /*
+ * NMIs can still fire, disable checking for all interrupt
+ * contexts.
+ */
+ raw_cpu_ptr(&kcsan_cpu_ctx)->disable_count++;
+ }
watchpoint = insert_watchpoint((unsigned long)ptr, size, is_write);
if (watchpoint == NULL) {
@@ -699,8 +705,10 @@ kcsan_setup_watchpoint(const volatile void *ptr, size_t size, int type, unsigned
atomic_long_dec(&kcsan_counters[KCSAN_COUNTER_USED_WATCHPOINTS]);
out_unlock:
- if (!interrupt_watcher)
+ if (!interrupt_watcher) {
+ raw_cpu_ptr(&kcsan_cpu_ctx)->disable_count--;
local_irq_restore(irq_flags);
+ }
kcsan_restore_irqtrace(current);
ctx->disable_scoped--;
diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c
index 2bfbb2d144e6..59fb9d71e9d8 100644
--- a/kernel/kexec_file.c
+++ b/kernel/kexec_file.c
@@ -27,6 +27,7 @@
#include <linux/syscalls.h>
#include <linux/vmalloc.h>
#include <linux/dma-map-ops.h>
+#include <linux/kexec_handover.h>
#include "kexec_internal.h"
#ifdef CONFIG_KEXEC_SIG
@@ -798,6 +799,16 @@ int kexec_add_buffer(struct kexec_buf *kbuf)
return 0;
}
+static bool kexec_only_cma_segments(struct kimage *image)
+{
+ for (int i = 0; i < image->nr_segments; i++) {
+ if (!image->segment_cma[i])
+ return false;
+ }
+
+ return true;
+}
+
/* Calculate and store the digest of segments */
static int kexec_calculate_store_digests(struct kimage *image)
{
@@ -822,6 +833,21 @@ static int kexec_calculate_store_digests(struct kimage *image)
sha256_init(&sctx);
+ /*
+ * If KHO is enabled, the destinations are located in KHO scratch.
+ * KHO scratch can only contain early boot allocations and movable
+ * allocations. That means there is no risk of memory corruption by
+ * uncancelled DMA.
+ *
+ * If all segments were loaded into contiguous memory, there will be no
+ * relocations at all, so also no risk of corruption.
+ */
+ if (image->type != KEXEC_TYPE_CRASH &&
+ (kho_is_enabled() || kexec_only_cma_segments(image))) {
+ pr_debug("disabling checksum verification in purgatory\n");
+ goto skip_checksum;
+ }
+
for (j = i = 0; i < image->nr_segments; i++) {
struct kexec_segment *ksegment;
@@ -867,6 +893,7 @@ static int kexec_calculate_store_digests(struct kimage *image)
j++;
}
+skip_checksum:
sha256_final(&sctx, digest);
ret = kexec_purgatory_get_set_symbol(image, "purgatory_sha_regions",
diff --git a/kernel/kprobes.c b/kernel/kprobes.c
index bfc89083daa9..e787e4948c8b 100644
--- a/kernel/kprobes.c
+++ b/kernel/kprobes.c
@@ -42,6 +42,7 @@
#include <linux/execmem.h>
#include <linux/cleanup.h>
#include <linux/wait.h>
+#include <linux/wait_bit.h>
#include <asm/sections.h>
#include <asm/cacheflush.h>
@@ -495,14 +496,16 @@ static bool kprobe_queued(struct kprobe *p)
static struct kprobe *get_optimized_kprobe(kprobe_opcode_t *addr)
{
int i;
- struct kprobe *p = NULL;
+ struct kprobe *p;
struct optimized_kprobe *op;
/* Don't check i == 0, since that is a breakpoint case. */
- for (i = 1; !p && i < MAX_OPTIMIZED_LENGTH / sizeof(kprobe_opcode_t); i++)
+ for (i = 1; i < MAX_OPTIMIZED_LENGTH / sizeof(kprobe_opcode_t); i++) {
p = get_kprobe(addr - i);
+ /* A disabled probe can have prepared, but inactive, optinsns. */
+ if (!p || !kprobe_optready(p) || kprobe_disarmed(p))
+ continue;
- if (p && kprobe_optready(p)) {
op = container_of(p, struct optimized_kprobe, kp);
if (arch_within_optimized_kprobe(op, addr))
return p;
@@ -526,7 +529,8 @@ enum {
OPTIMIZER_ST_FLUSHING = 2,
};
-static DECLARE_COMPLETION(optimizer_completion);
+/* Bumped at the end of each kprobe_optimizer() pass, under 'kprobe_mutex' */
+static unsigned long optimizer_passes;
#define OPTIMIZE_DELAY 5
@@ -654,9 +658,9 @@ static void kprobe_optimizer(void)
do_free_cleaned_kprobes();
}
- /* Step 5: Kick optimizer again if needed. But if there is a flush requested, */
- if (completion_done(&optimizer_completion))
- complete(&optimizer_completion);
+ /* Step 5: Wake up flushers, and kick optimizer again if needed. */
+ optimizer_passes++;
+ wake_up_var_locked(&optimizer_passes, &kprobe_mutex);
if (!list_empty(&optimizing_list) || !list_empty(&unoptimizing_list))
kick_kprobe_optimizer(); /*normal kick*/
@@ -708,7 +712,8 @@ static void wait_for_kprobe_optimizer_locked(void)
lockdep_assert_held(&kprobe_mutex);
while (!list_empty(&optimizing_list) || !list_empty(&unoptimizing_list)) {
- init_completion(&optimizer_completion);
+ unsigned long passes = optimizer_passes;
+
/*
* Set state to OPTIMIZER_ST_FLUSHING and wake up the thread if it's
* idle. If it's already kicked, it will see the state change.
@@ -717,9 +722,12 @@ static void wait_for_kprobe_optimizer_locked(void)
OPTIMIZER_ST_FLUSHING) != OPTIMIZER_ST_FLUSHING)
wake_up(&kprobe_optimizer_wait);
- mutex_unlock(&kprobe_mutex);
- wait_for_completion(&optimizer_completion);
- mutex_lock(&kprobe_mutex);
+ /*
+ * kprobe_optimizer() holds 'kprobe_mutex' for a whole pass, which
+ * this drops while sleeping, so a new count means a full pass ran.
+ */
+ wait_var_event_mutex(&optimizer_passes,
+ optimizer_passes != passes, &kprobe_mutex);
}
}
@@ -1447,8 +1455,14 @@ static bool __within_kprobe_blacklist(unsigned long addr)
/*
* If 'kprobe_blacklist' is defined, check the address and
* reject any probe registration in the prohibited area.
+ * Note: this can return true during transition period where
+ * (start_addr, end_addr) in the black list is shrinking
+ * but old entry has not been removed yet. This is acceptable
+ * because the worst case is that we reject more probes than
+ * we should.
*/
- list_for_each_entry(ent, &kprobe_blacklist, list) {
+ guard(rcu)();
+ list_for_each_entry_rcu(ent, &kprobe_blacklist, list) {
if (addr >= ent->start_addr && addr < ent->end_addr)
return true;
}
@@ -2509,7 +2523,7 @@ int kprobe_add_ksym_blacklist(unsigned long entry)
ent->start_addr = entry;
ent->end_addr = entry + size;
INIT_LIST_HEAD(&ent->list);
- list_add_tail(&ent->list, &kprobe_blacklist);
+ list_add_tail_rcu(&ent->list, &kprobe_blacklist);
return (int)size;
}
@@ -2603,8 +2617,8 @@ static void kprobe_remove_area_blacklist(unsigned long start, unsigned long end)
list_for_each_entry_safe(ent, n, &kprobe_blacklist, list) {
if (ent->start_addr < start || ent->start_addr >= end)
continue;
- list_del(&ent->list);
- kfree(ent);
+ list_del_rcu(&ent->list);
+ kfree_rcu(ent, rcu);
}
}
diff --git a/kernel/kthread.c b/kernel/kthread.c
index 63beb59b7a3d..a3f95c90456b 100644
--- a/kernel/kthread.c
+++ b/kernel/kthread.c
@@ -852,7 +852,7 @@ int kthread_affine_preferred(struct task_struct *p, const struct cpumask *mask)
if (!zalloc_cpumask_var(&affinity, GFP_KERNEL))
return -ENOMEM;
- kthread->preferred_affinity = kzalloc(sizeof(struct cpumask), GFP_KERNEL);
+ kthread->preferred_affinity = kzalloc_obj(struct cpumask);
if (!kthread->preferred_affinity) {
ret = -ENOMEM;
goto out;
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index 28d15ba58a26..a240d1144e89 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -799,9 +799,6 @@ void klp_free_replaced_patches_async(struct klp_patch *new_patch)
static int klp_init_func(struct klp_object *obj, struct klp_func *func)
{
- if (!func->old_name)
- return -EINVAL;
-
/*
* NOPs get the address later. The patched module must be loaded,
* see klp_init_object_loaded().
@@ -1092,6 +1089,25 @@ err:
return ret;
}
+static int klp_check_patch(struct klp_patch *patch)
+{
+ struct klp_object *obj;
+ struct klp_func *func;
+
+ if (!patch || !patch->mod || !patch->objs)
+ return -EINVAL;
+
+ klp_for_each_object_static(patch, obj) {
+ if (!obj->funcs)
+ return -EINVAL;
+ klp_for_each_func_static(obj, func) {
+ if (!func->old_name)
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
/**
* klp_enable_patch() - enable the livepatch
* @patch: patch to be enabled
@@ -1108,16 +1124,10 @@ err:
int klp_enable_patch(struct klp_patch *patch)
{
int ret;
- struct klp_object *obj;
-
- if (!patch || !patch->mod || !patch->objs)
- return -EINVAL;
-
- klp_for_each_object_static(patch, obj) {
- if (!obj->funcs)
- return -EINVAL;
- }
+ ret = klp_check_patch(patch);
+ if (ret)
+ return ret;
if (!is_livepatch_module(patch->mod)) {
pr_err("module %s is not marked as a livepatch module\n",
diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
index eec9d3ae07eb..d9f469462556 100644
--- a/kernel/liveupdate/Makefile
+++ b/kernel/liveupdate/Makefile
@@ -8,7 +8,6 @@ luo-y := \
luo_session.o
obj-$(CONFIG_KEXEC_HANDOVER) += kexec_handover.o
-obj-$(CONFIG_KEXEC_HANDOVER_DEBUG) += kexec_handover_debug.o
obj-$(CONFIG_KEXEC_HANDOVER_DEBUGFS) += kexec_handover_debugfs.o
obj-$(CONFIG_LIVEUPDATE) += luo.o
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 4834a809985a..7c4d86daf86d 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -34,10 +34,21 @@
* KHO is tightly coupled with mm init and needs access to some of mm
* internal APIs.
*/
-#include "../../mm/internal.h"
+#include "../../mm/mm_init.h"
+#include "../../mm/vmalloc.h"
#include "../kexec_internal.h"
#include "kexec_handover_internal.h"
+/*
+ * This is the minimal alignment required by deferred struct page init.
+ * deferred_init_memmap_chunk frees memory to the buddy allocator, which looks
+ * at the neighboring pages (up to MAX_PAGE_ORDER) to merge them.
+ * If KHO scratch is not aligned to that value, buddy can access uninitialized
+ * struct pages, which can cause a crash.
+ */
+#define SCRATCH_ALIGNMENT_BYTES (PAGE_SIZE * MAX_ORDER_NR_PAGES)
+static_assert(SCRATCH_ALIGNMENT_BYTES >= CMA_MIN_ALIGNMENT_BYTES);
+
/* The magic token for preserved pages */
#define KHO_PAGE_MAGIC 0x4b484f50U /* ASCII for 'KHOP' */
@@ -84,8 +95,25 @@ static struct kho_out kho_out = {
},
};
+struct kho_in {
+ phys_addr_t fdt_phys;
+ phys_addr_t scratch_phys;
+ char previous_release[__NEW_UTS_LEN + 1];
+ u32 kexec_count;
+ struct kho_debugfs dbg;
+ struct kho_radix_tree radix_tree;
+};
+
+static struct kho_in kho_in = {
+};
+
+static const void *kho_get_fdt(void)
+{
+ return kho_in.fdt_phys ? phys_to_virt(kho_in.fdt_phys) : NULL;
+}
+
/**
- * kho_radix_encode_key - Encodes a physical address and order into a radix key.
+ * kho_encode_radix_key - Encodes a physical address and order into a radix key.
* @phys: The physical address of the page.
* @order: The order of the page.
*
@@ -95,35 +123,38 @@ static struct kho_out kho_out = {
*
* Return: The encoded unsigned long radix key.
*/
-static unsigned long kho_radix_encode_key(phys_addr_t phys, unsigned int order)
+static unsigned long kho_encode_radix_key(phys_addr_t phys, unsigned int order)
{
- /* Order bits part */
- unsigned long h = 1UL << (KHO_ORDER_0_LOG2 - order);
- /* Shifted physical address part */
- unsigned long l = phys >> (PAGE_SHIFT + order);
+ /* The physical address is encoded by shifting the PFN by its order. */
+ unsigned long shift = PAGE_SHIFT + order;
+ /* Order bit goes right before the shifted PFN. */
+ unsigned long h = 1UL << (64 - shift);
+ /* Shifted PFN. */
+ unsigned long l = phys >> shift;
return h | l;
}
/**
- * kho_radix_decode_key - Decodes a radix key back into a physical address and order.
+ * kho_decode_radix_key - Decodes a radix key back into a physical address and order.
* @key: The unsigned long key to decode.
* @order: An output parameter, a pointer to an unsigned int where the decoded
* page order will be stored.
*
- * This function reverses the encoding performed by kho_radix_encode_key(),
+ * This function reverses the encoding performed by kho_encode_radix_key(),
* extracting the original physical address and page order from a given key.
*
* Return: The decoded physical address.
*/
-static phys_addr_t kho_radix_decode_key(unsigned long key, unsigned int *order)
+static phys_addr_t kho_decode_radix_key(unsigned long key, unsigned int *order)
{
- unsigned int order_bit = fls64(key);
+ /* fls64() indexes starting from 1. */
+ unsigned int order_bit = fls64(key) - 1;
phys_addr_t phys;
- /* order_bit is numbered starting at 1 from fls64 */
- *order = KHO_ORDER_0_LOG2 - order_bit + 1;
- /* The order is discarded by the shift */
+ /* order bit goes right before the shifted PFN. */
+ *order = 64 - (PAGE_SHIFT + order_bit);
+ /* The order bit is discarded by the shift */
phys = key << (PAGE_SHIFT + *order);
return phys;
@@ -143,25 +174,47 @@ static unsigned long kho_radix_get_table_index(unsigned long key,
return (key >> s) % (1 << KHO_TABLE_SIZE_LOG2);
}
+static void __ref *kho_radix_alloc_node(void)
+{
+ struct kho_radix_node *node;
+
+ if (slab_is_available())
+ node = (struct kho_radix_node *)get_zeroed_page(GFP_KERNEL);
+ else
+ node = memblock_alloc(PAGE_SIZE, PAGE_SIZE);
+
+ return node;
+}
+
+static void __ref kho_radix_free_node(struct kho_radix_node *node)
+{
+ if (slab_is_available())
+ free_page((unsigned long)node);
+ else
+ memblock_free(node, PAGE_SIZE);
+}
+
/**
- * kho_radix_add_page - Marks a page as preserved in the radix tree.
+ * kho_radix_add_key - Add a key to the radix tree.
* @tree: The KHO radix tree.
- * @pfn: The page frame number of the page to preserve.
- * @order: The order of the page.
+ * @key: The key to add.
+ *
+ * This function traverses the radix tree based on the @key provided. It sets the
+ * corresponding bit in the leaf bitmap to mark the @key as present. If
+ * intermediate nodes do not exist along the path, they are allocated and added
+ * to the tree.
*
- * This function traverses the radix tree based on the key derived from @pfn
- * and @order. It sets the corresponding bit in the leaf bitmap to mark the
- * page for preservation. If intermediate nodes do not exist along the path,
- * they are allocated and added to the tree.
+ * NOTE: Currently only keys of width up to %KHO_RADIX_KEY_WIDTH are supported.
+ * This limit only exists because current users of the radix tree don't use more
+ * than that. Changing the maximum width requires changing the tree depth, which
+ * needs bumping the ABI version.
*
* Return: 0 on success, or a negative error code on failure.
*/
-int kho_radix_add_page(struct kho_radix_tree *tree,
- unsigned long pfn, unsigned int order)
+int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)
{
/* Newly allocated nodes for error cleanup */
struct kho_radix_node *intermediate_nodes[KHO_TREE_MAX_DEPTH] = { 0 };
- unsigned long key = kho_radix_encode_key(PFN_PHYS(pfn), order);
struct kho_radix_node *anchor_node = NULL;
struct kho_radix_node *node = tree->root;
struct kho_radix_node *new_node;
@@ -172,6 +225,9 @@ int kho_radix_add_page(struct kho_radix_tree *tree,
if (WARN_ON_ONCE(!tree->root))
return -EINVAL;
+ if (unlikely(fls64(key) > KHO_RADIX_KEY_WIDTH))
+ return -ERANGE;
+
might_sleep();
guard(mutex)(&tree->lock);
@@ -186,7 +242,7 @@ int kho_radix_add_page(struct kho_radix_tree *tree,
}
/* Next node is empty, create a new node for it */
- new_node = (struct kho_radix_node *)get_zeroed_page(GFP_KERNEL);
+ new_node = kho_radix_alloc_node();
if (!new_node) {
err = -ENOMEM;
goto err_free_nodes;
@@ -217,29 +273,26 @@ int kho_radix_add_page(struct kho_radix_tree *tree,
err_free_nodes:
for (i = KHO_TREE_MAX_DEPTH - 1; i > 0; i--) {
if (intermediate_nodes[i])
- free_page((unsigned long)intermediate_nodes[i]);
+ kho_radix_free_node(intermediate_nodes[i]);
}
if (anchor_node)
anchor_node->table[anchor_idx] = 0;
return err;
}
-EXPORT_SYMBOL_GPL(kho_radix_add_page);
+EXPORT_SYMBOL_GPL(kho_radix_add_key);
/**
- * kho_radix_del_page - Removes a page's preservation status from the radix tree.
+ * kho_radix_del_key - Removes the key from the radix tree.
* @tree: The KHO radix tree.
- * @pfn: The page frame number of the page to unpreserve.
- * @order: The order of the page.
+ * @key: The key to remove.
*
* This function traverses the radix tree and clears the bit corresponding to
- * the page, effectively removing its "preserved" status. It does not free
- * the tree's intermediate nodes, even if they become empty.
+ * the @key, effectively removing it from the tree. It does not free the tree's
+ * intermediate nodes, even if they become empty.
*/
-void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,
- unsigned int order)
+void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
{
- unsigned long key = kho_radix_encode_key(PFN_PHYS(pfn), order);
struct kho_radix_node *node = tree->root;
struct kho_radix_leaf *leaf;
unsigned int i, idx;
@@ -247,6 +300,10 @@ void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,
if (WARN_ON_ONCE(!tree->root))
return;
+ /* Keys wider than KHO_RADIX_KEY_WIDTH are not allowed to be added. */
+ if (unlikely(fls64(key) > KHO_RADIX_KEY_WIDTH))
+ return;
+
might_sleep();
guard(mutex)(&tree->lock);
@@ -270,21 +327,85 @@ void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,
idx = kho_radix_get_bitmap_index(key);
__clear_bit(idx, leaf->bitmap);
}
-EXPORT_SYMBOL_GPL(kho_radix_del_page);
+EXPORT_SYMBOL_GPL(kho_radix_del_key);
+
+static void __kho_radix_destroy_tree(struct kho_radix_node *root,
+ unsigned int level)
+{
+ unsigned long i;
-static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf,
- unsigned long key,
- kho_radix_tree_walk_callback_t cb)
+ if (level == 0) {
+ kho_radix_free_node(root);
+ return;
+ }
+
+ for (i = 0; i < PAGE_SIZE / sizeof(phys_addr_t); i++) {
+ if (root->table[i])
+ __kho_radix_destroy_tree(phys_to_virt(root->table[i]),
+ level - 1);
+ }
+
+ kho_radix_free_node(root);
+}
+
+/**
+ * kho_radix_init_tree - initialize the radix tree.
+ * @tree: the tree to initialize.
+ * @root: root table of the radix tree.
+ *
+ * Initialize the radix tree with the given root node. If root is %NULL, an
+ * empty root table is allocated. If root is not %NULL, it is the caller's
+ * responsibility to make sure the root is valid and in the correct format.
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root)
+{
+ if (!root)
+ root = kho_radix_alloc_node();
+ if (!root)
+ return -ENOMEM;
+
+ tree->root = root;
+ mutex_init(&tree->lock);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(kho_radix_init_tree);
+
+/**
+ * kho_radix_destroy_tree - Destroy the radix tree
+ * @tree: The radix tree to destroy
+ *
+ * Walk @tree and free all its nodes.
+ */
+void kho_radix_destroy_tree(struct kho_radix_tree *tree)
+{
+ if (!tree->root)
+ return;
+
+ __kho_radix_destroy_tree(tree->root, KHO_TREE_MAX_DEPTH - 1);
+ tree->root = NULL;
+}
+EXPORT_SYMBOL_GPL(kho_radix_destroy_tree);
+
+static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
+ const struct kho_radix_walk_cb *cb, void *data)
{
unsigned long *bitmap = (unsigned long *)leaf;
- unsigned int order;
- phys_addr_t phys;
unsigned int i;
int err;
+ if (cb->node) {
+ err = cb->node(virt_to_phys(leaf), data);
+ if (err)
+ return err;
+ }
+
+ if (!cb->leaf)
+ return 0;
+
for_each_set_bit(i, bitmap, PAGE_SIZE * BITS_PER_BYTE) {
- phys = kho_radix_decode_key(key | i, &order);
- err = cb(phys, order);
+ err = cb->leaf(key | i, data);
if (err)
return err;
}
@@ -294,7 +415,7 @@ static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf,
static int __kho_radix_walk_tree(struct kho_radix_node *root,
unsigned int level, unsigned long start,
- kho_radix_tree_walk_callback_t cb)
+ const struct kho_radix_walk_cb *cb, void *data)
{
struct kho_radix_node *node;
struct kho_radix_leaf *leaf;
@@ -302,6 +423,12 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
unsigned int shift;
int err;
+ if (cb->node) {
+ err = cb->node(virt_to_phys(root), data);
+ if (err)
+ return err;
+ }
+
for (i = 0; i < PAGE_SIZE / sizeof(phys_addr_t); i++) {
if (!root->table[i])
continue;
@@ -318,10 +445,10 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
* node is pointing to the level 0 bitmap.
*/
leaf = (struct kho_radix_leaf *)node;
- err = kho_radix_walk_leaf(leaf, key, cb);
+ err = kho_radix_walk_leaf(leaf, key, cb, data);
} else {
err = __kho_radix_walk_tree(node, level - 1,
- key, cb);
+ key, cb, data);
}
if (err)
@@ -332,28 +459,27 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
}
/**
- * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each preserved page.
+ * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each key.
* @tree: A pointer to the KHO radix tree to walk.
- * @cb: A callback function of type kho_radix_tree_walk_callback_t that will be
- * invoked for each preserved page found in the tree. The callback receives
- * the physical address and order of the preserved page.
+ * @cb: Set of callbacks to be invoked during the tree walk.
+ * @data: Opaque data pointer passed to each callback in @cb.
*
- * This function walks the radix tree, searching from the specified top level
- * down to the lowest level (level 0). For each preserved page found, it invokes
- * the provided callback, passing the page's physical address and order.
+ * This function walks the radix tree, searching from the top level down to the
+ * lowest level (level 0), invoking the appropriate callbacks.
*
* Return: 0 if the walk completed the specified tree, or the non-zero return
* value from the callback that stopped the walk.
*/
int kho_radix_walk_tree(struct kho_radix_tree *tree,
- kho_radix_tree_walk_callback_t cb)
+ const struct kho_radix_walk_cb *cb, void *data)
{
if (WARN_ON_ONCE(!tree->root))
return -EINVAL;
guard(mutex)(&tree->lock);
- return __kho_radix_walk_tree(tree->root, KHO_TREE_MAX_DEPTH - 1, 0, cb);
+ return __kho_radix_walk_tree(tree->root, KHO_TREE_MAX_DEPTH - 1, 0, cb,
+ data);
}
EXPORT_SYMBOL_GPL(kho_radix_walk_tree);
@@ -484,14 +610,17 @@ static struct page *__init kho_get_preserved_page(phys_addr_t phys,
return pfn_to_page(pfn);
}
-static int __init kho_preserved_memory_reserve(phys_addr_t phys,
- unsigned int order)
+static int __init kho_preserved_memory_reserve(unsigned long key, void *data)
{
union kho_page_info info;
struct page *page;
+ unsigned int order;
+ phys_addr_t phys;
u64 sz;
- sz = 1 << (order + PAGE_SHIFT);
+ phys = kho_decode_radix_key(key, &order);
+
+ sz = 1UL << (order + PAGE_SHIFT);
page = kho_get_preserved_page(phys, order);
/* Reserve the memory preserved in KHO in memblock */
@@ -519,6 +648,13 @@ static phys_addr_t __init kho_get_mem_map_phys(const void *fdt)
return get_unaligned((const u64 *)mem_ptr);
}
+static void __init *kho_get_mem_map(const void *fdt)
+{
+ phys_addr_t phys = kho_get_mem_map_phys(fdt);
+
+ return phys ? phys_to_virt(phys) : NULL;
+}
+
/*
* With KHO enabled, memory can become fragmented because KHO regions may
* be anywhere in physical address space. The scratch regions give us a
@@ -619,19 +755,24 @@ early_param("kho_scratch", kho_parse_scratch_size);
static void __init scratch_size_update(void)
{
/*
- * If fixed sizes are not provided via command line, calculate them
- * now.
+ * If fixed sizes are not provided via command line, calculate them now.
+ * Remove HugeTLB allocations from it because they never get allocated
+ * from scratch.
*/
if (scratch_scale) {
phys_addr_t size;
size = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT,
NUMA_NO_NODE);
+ size -= memblock_reserved_hugetlb_size(ARCH_LOW_ADDRESS_LIMIT,
+ NUMA_NO_NODE);
size = size * scratch_scale / 100;
scratch_size_lowmem = size;
size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,
NUMA_NO_NODE);
+ size -= memblock_reserved_hugetlb_size(MEMBLOCK_ALLOC_ANYWHERE,
+ NUMA_NO_NODE);
size = size * scratch_scale / 100 - scratch_size_lowmem;
scratch_size_global = size;
}
@@ -640,8 +781,8 @@ static void __init scratch_size_update(void)
* Scratch areas are released as MIGRATE_CMA. Round them up to the right
* size.
*/
- scratch_size_lowmem = round_up(scratch_size_lowmem, CMA_MIN_ALIGNMENT_BYTES);
- scratch_size_global = round_up(scratch_size_global, CMA_MIN_ALIGNMENT_BYTES);
+ scratch_size_lowmem = round_up(scratch_size_lowmem, SCRATCH_ALIGNMENT_BYTES);
+ scratch_size_global = round_up(scratch_size_global, SCRATCH_ALIGNMENT_BYTES);
}
static phys_addr_t __init scratch_size_node(int nid)
@@ -651,12 +792,31 @@ static phys_addr_t __init scratch_size_node(int nid)
if (scratch_scale) {
size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,
nid);
+ /* Do not count HugeTLB pages. */
+ size -= memblock_reserved_hugetlb_size(MEMBLOCK_ALLOC_ANYWHERE,
+ nid);
size = size * scratch_scale / 100;
} else {
size = scratch_size_pernode;
}
- return round_up(size, CMA_MIN_ALIGNMENT_BYTES);
+ return round_up(size, SCRATCH_ALIGNMENT_BYTES);
+}
+
+bool kho_scratch_overlap(phys_addr_t phys, size_t size)
+{
+ phys_addr_t scratch_start, scratch_end;
+ unsigned int i;
+
+ for (i = 0; i < kho_scratch_cnt; i++) {
+ scratch_start = kho_scratch[i].addr;
+ scratch_end = kho_scratch[i].addr + kho_scratch[i].size;
+
+ if (phys < scratch_end && (phys + size) > scratch_start)
+ return true;
+ }
+
+ return false;
}
/**
@@ -692,7 +852,7 @@ static void __init kho_reserve_scratch(void)
* next kernel
*/
size = scratch_size_lowmem;
- addr = memblock_phys_alloc_range(size, CMA_MIN_ALIGNMENT_BYTES, 0,
+ addr = memblock_phys_alloc_range(size, SCRATCH_ALIGNMENT_BYTES, 0,
ARCH_LOW_ADDRESS_LIMIT);
if (!addr) {
pr_err("Failed to reserve lowmem scratch buffer\n");
@@ -705,7 +865,7 @@ static void __init kho_reserve_scratch(void)
/* reserve large contiguous area for allocations without nid */
size = scratch_size_global;
- addr = memblock_phys_alloc(size, CMA_MIN_ALIGNMENT_BYTES);
+ addr = memblock_phys_alloc(size, SCRATCH_ALIGNMENT_BYTES);
if (!addr) {
pr_err("Failed to reserve global scratch buffer\n");
goto err_free_scratch_areas;
@@ -721,7 +881,7 @@ static void __init kho_reserve_scratch(void)
*/
for_each_node_state(nid, N_MEMORY) {
size = scratch_size_node(nid);
- addr = memblock_alloc_range_nid(size, CMA_MIN_ALIGNMENT_BYTES,
+ addr = memblock_alloc_range_nid(size, SCRATCH_ALIGNMENT_BYTES,
0, MEMBLOCK_ALLOC_ACCESSIBLE,
nid, true);
if (!addr) {
@@ -746,6 +906,140 @@ err_disable_kho:
kho_enable = false;
}
+/*
+ * Look for free blocks of 1G. This is a heuristic chosen to work efficiently
+ * with large systems with hundreds of gigabytes of memory. It will work poorly
+ * on smaller systems. The algorithm itself doesn't depend on the actual value,
+ * so it can be changed to a different heuristic later if needed.
+ */
+#define KHO_SCRATCH_EXT_BLKSIZE SZ_1G
+#define KHO_SCRATCH_EXT_BLKSHIFT const_ilog2(KHO_SCRATCH_EXT_BLKSIZE)
+
+/* Called for the KHO preserved memory radix tree. */
+static int __init kho_ext_walk_leaf(unsigned long key, void *data)
+{
+ struct kho_radix_tree *busy_blocks = data;
+ phys_addr_t start, end;
+ unsigned int order;
+ int err;
+
+ /*
+ * The key is from the KHO preserved memory radix tree. It is decoded to
+ * a physical address of a preservation and its order.
+ */
+ start = kho_decode_radix_key(key, &order);
+ end = start + (1UL << (order + PAGE_SHIFT));
+
+ while (start < end) {
+ err = kho_radix_add_key(busy_blocks, start >> KHO_SCRATCH_EXT_BLKSHIFT);
+ if (err)
+ return err;
+
+ start += (1UL << KHO_SCRATCH_EXT_BLKSHIFT);
+ }
+
+ return 0;
+}
+
+/* Called for the KHO preserved memory radix tree. */
+static int __init kho_ext_walk_node(phys_addr_t phys, void *data)
+{
+ struct kho_radix_tree *busy_blocks = data;
+
+ return kho_radix_add_key(busy_blocks, phys >> KHO_SCRATCH_EXT_BLKSHIFT);
+}
+
+/* Called for the busy block radix tree. */
+static int __init kho_ext_mark_scratch(unsigned long key, void *data)
+{
+ phys_addr_t *prev_end = data;
+ phys_addr_t start = key << KHO_SCRATCH_EXT_BLKSHIFT;
+ int err;
+
+ if (start > *prev_end) {
+ err = memblock_mark_kho_scratch(*prev_end, start - *prev_end);
+ if (err)
+ return err;
+ }
+
+ *prev_end = start + (1UL << KHO_SCRATCH_EXT_BLKSHIFT);
+ return 0;
+}
+
+/*
+ * kho_extend_scratch - Extend the scratch regions
+ *
+ * The KHO preserved memory radix tree mixes both physical address and order
+ * into a single key. This makes it hard to look for free ranges directly. This
+ * function first walks the radix tree and digests it down into another radix
+ * tree, whose keys identify blocks of size KHO_SCRATCH_EXT_BLKSIZE which
+ * contain preserved memory.
+ *
+ * Then it walks the digested radix tree and marks everything that doesn't have
+ * preserved memory as scratch.
+ *
+ * NOTE: This function allocates memory so it should be called when scratch has
+ * available space.
+ *
+ * NOTE: The pages of the KHO preserved memory radix tree tables are not marked
+ * as preserved in the preserved memory tree. But they are expected to remain
+ * untouched until the tree is fully parsed. So this function also considers
+ * them to be "preserved memory" and marks their blocks as busy.
+ *
+ * NOTE: efi_init()::reserve_regions() removes all regions except
+ * MEMBLOCK_KHO_SCRATCH. This function adds such regions but they are not KHO
+ * scratch memory, so they should not be removed. This function should always be
+ * called after reserve_regions().
+ */
+static void __init kho_extend_scratch(void)
+{
+ const struct kho_radix_walk_cb kho_cb = {
+ .leaf = kho_ext_walk_leaf,
+ .node = kho_ext_walk_node,
+ };
+ const struct kho_radix_walk_cb ext_cb = {
+ .leaf = kho_ext_mark_scratch,
+ };
+ static struct lock_class_key busy_radix_class;
+ struct kho_radix_tree busy_blocks;
+ phys_addr_t prev_end = 0;
+ int err = 0;
+
+ err = kho_radix_init_tree(&busy_blocks, NULL);
+ if (err)
+ goto print;
+
+ /*
+ * The walk of kho_in.radix_tree adds keys to busy_blocks. The walk
+ * takes the kho_in radix tree lock and adding the key takes busy_blocks
+ * lock. Since both are struct kho_radix_tree and share the same lock
+ * class, lockdep gets confused. Set a different class for
+ * busy_blocks.lock to make lockdep happy.
+ */
+ lockdep_set_class(&busy_blocks.lock, &busy_radix_class);
+
+ /* Walk the KHO radix tree to find busy blocks. */
+ err = kho_radix_walk_tree(&kho_in.radix_tree, &kho_cb, &busy_blocks);
+ if (err)
+ goto out;
+
+ /* Walk the busy blocks and mark everything between keys as scratch. */
+ err = kho_radix_walk_tree(&busy_blocks, &ext_cb, &prev_end);
+ if (err)
+ goto out;
+
+ /* Mark everything from last busy block to end of DRAM. */
+ if (prev_end < memblock_end_of_DRAM())
+ err = memblock_mark_kho_scratch(prev_end, memblock_end_of_DRAM() - prev_end);
+
+ /* fallthrough */
+out:
+ kho_radix_destroy_tree(&busy_blocks);
+print:
+ if (err)
+ pr_err("Failed to extend scratch: %pe\n", ERR_PTR(err));
+}
+
/**
* kho_add_subtree - record the physical address of a sub blob in KHO root tree.
* @name: name of the sub tree.
@@ -856,10 +1150,12 @@ int kho_preserve_folio(struct folio *folio)
const unsigned long pfn = folio_pfn(folio);
const unsigned int order = folio_order(folio);
- if (WARN_ON(kho_scratch_overlap(pfn << PAGE_SHIFT, PAGE_SIZE << order)))
+ if (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) &&
+ WARN_ON(kho_scratch_overlap(pfn << PAGE_SHIFT, PAGE_SIZE << order)))
return -EINVAL;
- return kho_radix_add_page(tree, pfn, order);
+ return kho_radix_add_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),
+ order));
}
EXPORT_SYMBOL_GPL(kho_preserve_folio);
@@ -877,7 +1173,7 @@ void kho_unpreserve_folio(struct folio *folio)
const unsigned long pfn = folio_pfn(folio);
const unsigned int order = folio_order(folio);
- kho_radix_del_page(tree, pfn, order);
+ kho_radix_del_key(tree, kho_encode_radix_key(PFN_PHYS(pfn), order));
}
EXPORT_SYMBOL_GPL(kho_unpreserve_folio);
@@ -906,7 +1202,8 @@ static void __kho_unpreserve(struct kho_radix_tree *tree,
while (pfn < end_pfn) {
order = __kho_preserve_pages_order(pfn, end_pfn);
- kho_radix_del_page(tree, pfn, order);
+ kho_radix_del_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),
+ order));
pfn += 1 << order;
}
@@ -931,7 +1228,8 @@ int kho_preserve_pages(struct page *page, unsigned long nr_pages)
unsigned long failed_pfn = 0;
int err = 0;
- if (WARN_ON(kho_scratch_overlap(start_pfn << PAGE_SHIFT,
+ if (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) &&
+ WARN_ON(kho_scratch_overlap(start_pfn << PAGE_SHIFT,
nr_pages << PAGE_SHIFT))) {
return -EINVAL;
}
@@ -939,7 +1237,8 @@ int kho_preserve_pages(struct page *page, unsigned long nr_pages)
while (pfn < end_pfn) {
unsigned int order = __kho_preserve_pages_order(pfn, end_pfn);
- err = kho_radix_add_page(tree, pfn, order);
+ err = kho_radix_add_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),
+ order));
if (err) {
failed_pfn = pfn;
break;
@@ -1315,22 +1614,6 @@ void kho_restore_free(void *mem)
}
EXPORT_SYMBOL_GPL(kho_restore_free);
-struct kho_in {
- phys_addr_t fdt_phys;
- phys_addr_t scratch_phys;
- char previous_release[__NEW_UTS_LEN + 1];
- u32 kexec_count;
- struct kho_debugfs dbg;
-};
-
-static struct kho_in kho_in = {
-};
-
-static const void *kho_get_fdt(void)
-{
- return kho_in.fdt_phys ? phys_to_virt(kho_in.fdt_phys) : NULL;
-}
-
/**
* is_kho_boot - check if current kernel was booted via KHO-enabled
* kexec
@@ -1398,26 +1681,24 @@ int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size)
}
EXPORT_SYMBOL_GPL(kho_retrieve_subtree);
-static int __init kho_mem_retrieve(const void *fdt)
+static void __init kho_mem_retrieve(void)
{
- struct kho_radix_tree tree;
- const phys_addr_t *mem;
- int len;
-
- /* Retrieve the KHO radix tree from passed-in FDT. */
- mem = fdt_getprop(fdt, 0, KHO_FDT_MEMORY_MAP_PROP_NAME, &len);
+ const struct kho_radix_walk_cb cb = {
+ .leaf = kho_preserved_memory_reserve,
+ };
- if (!mem || len != sizeof(*mem)) {
- pr_err("failed to get preserved KHO memory tree\n");
- return -ENOENT;
- }
+ if (kho_radix_walk_tree(&kho_in.radix_tree, &cb, NULL))
+ goto err;
- if (!*mem)
- return -EINVAL;
+ return;
- tree.root = phys_to_virt(*mem);
- mutex_init(&tree.lock);
- return kho_radix_walk_tree(&tree, kho_preserved_memory_reserve);
+err:
+ /*
+ * Failed to initialize preserved memory. Clear FDT and radix so KHO
+ * users don't treat it as a KHO boot.
+ */
+ kho_in.fdt_phys = 0;
+ kho_in.radix_tree.root = NULL;
}
static __init int kho_out_fdt_setup(void)
@@ -1543,16 +1824,14 @@ static __init int kho_init(void)
if (!kho_enable)
return 0;
- tree->root = kzalloc(PAGE_SIZE, GFP_KERNEL);
- if (!tree->root) {
- err = -ENOMEM;
+ err = kho_radix_init_tree(tree, NULL);
+ if (err)
goto err_free_scratch;
- }
kho_out.fdt = kho_alloc_preserve(PAGE_SIZE);
if (IS_ERR(kho_out.fdt)) {
err = PTR_ERR(kho_out.fdt);
- goto err_free_kho_radix_tree_root;
+ goto err_free_kho_radix_tree;
}
err = kho_debugfs_init();
@@ -1603,9 +1882,8 @@ static __init int kho_init(void)
err_free_fdt:
kho_unpreserve_free(kho_out.fdt);
-err_free_kho_radix_tree_root:
- kfree(tree->root);
- tree->root = NULL;
+err_free_kho_radix_tree:
+ kho_radix_destroy_tree(tree);
err_free_scratch:
kho_out.fdt = NULL;
for (int i = 0; i < kho_scratch_cnt; i++) {
@@ -1619,16 +1897,52 @@ err_free_scratch:
}
fs_initcall(kho_init);
-void __init kho_memory_init(void)
+void __init kho_memory_init_early(void)
{
- if (kho_in.scratch_phys) {
- kho_scratch = phys_to_virt(kho_in.scratch_phys);
+ const void *fdt = kho_get_fdt();
+ void *mem_map;
- if (kho_mem_retrieve(kho_get_fdt()))
- kho_in.fdt_phys = 0;
- } else {
+ if (!is_kho_boot())
+ return;
+
+ /*
+ * kho_get_mem_map() should always succeed. If it fails, kho_populate()
+ * catches that and never sets kho_in.scratch_phys, which stops memory
+ * retrieval.
+ */
+ mem_map = kho_get_mem_map(fdt);
+ if (WARN_ON(!mem_map))
+ goto err;
+
+ /*
+ * kho_scratch_overlap() needs kho_scratch to be initialized. It
+ * is used by free_area_init() on KHO boots, so initialize it
+ * early.
+ */
+ kho_scratch = phys_to_virt(kho_in.scratch_phys);
+
+ if (kho_radix_init_tree(&kho_in.radix_tree, mem_map))
+ goto err;
+
+ kho_extend_scratch();
+
+ return;
+
+err:
+ /*
+ * Failed to initialize preserved memory radix tree. Clear FDT
+ * and scratch so KHO users don't treat it as a KHO boot.
+ */
+ kho_in.fdt_phys = 0;
+ kho_in.scratch_phys = 0;
+}
+
+void __init kho_memory_init(void)
+{
+ if (kho_in.scratch_phys)
+ kho_mem_retrieve();
+ else
kho_reserve_scratch();
- }
}
void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len,
diff --git a/kernel/liveupdate/kexec_handover_debug.c b/kernel/liveupdate/kexec_handover_debug.c
deleted file mode 100644
index 6efb696f5426..000000000000
--- a/kernel/liveupdate/kexec_handover_debug.c
+++ /dev/null
@@ -1,25 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * kexec_handover_debug.c - kexec handover optional debug functionality
- * Copyright (C) 2025 Google LLC, Pasha Tatashin <pasha.tatashin@soleen.com>
- */
-
-#define pr_fmt(fmt) "KHO: " fmt
-
-#include "kexec_handover_internal.h"
-
-bool kho_scratch_overlap(phys_addr_t phys, size_t size)
-{
- phys_addr_t scratch_start, scratch_end;
- unsigned int i;
-
- for (i = 0; i < kho_scratch_cnt; i++) {
- scratch_start = kho_scratch[i].addr;
- scratch_end = kho_scratch[i].addr + kho_scratch[i].size;
-
- if (phys < scratch_end && (phys + size) > scratch_start)
- return true;
- }
-
- return false;
-}
diff --git a/kernel/liveupdate/kexec_handover_internal.h b/kernel/liveupdate/kexec_handover_internal.h
index 0399ff107775..805d2a76c388 100644
--- a/kernel/liveupdate/kexec_handover_internal.h
+++ b/kernel/liveupdate/kexec_handover_internal.h
@@ -41,13 +41,4 @@ static inline void kho_debugfs_blob_remove(struct kho_debugfs *dbg,
void *blob) { }
#endif /* CONFIG_KEXEC_HANDOVER_DEBUGFS */
-#ifdef CONFIG_KEXEC_HANDOVER_DEBUG
-bool kho_scratch_overlap(phys_addr_t phys, size_t size);
-#else
-static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)
-{
- return false;
-}
-#endif /* CONFIG_KEXEC_HANDOVER_DEBUG */
-
#endif /* LINUX_KEXEC_HANDOVER_INTERNAL_H */
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index 5c27134ce7ba..cd715a7c1d99 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -133,7 +133,7 @@ static int luo_flb_file_preserve_one(struct liveupdate_flb *flb)
return 0;
}
-static void luo_flb_file_unpreserve_one(struct liveupdate_flb *flb)
+void liveupdate_flb_put_outgoing(struct liveupdate_flb *flb)
{
struct luo_flb_private *private = luo_flb_get_private(flb);
@@ -168,7 +168,10 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
if (private->incoming.finished)
return -ENODATA;
- if (private->incoming.retrieved)
+ if (private->incoming.retrieve_status < 0)
+ return private->incoming.retrieve_status;
+
+ if (private->incoming.retrieve_status > 0)
return 0;
if (!fh->active)
@@ -194,12 +197,13 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
err = flb->ops->retrieve(&args);
if (err) {
+ private->incoming.retrieve_status = err;
module_put(flb->ops->owner);
return err;
}
private->incoming.obj = args.obj;
- private->incoming.retrieved = true;
+ private->incoming.retrieve_status = 1;
return 0;
}
@@ -213,7 +217,7 @@ void liveupdate_flb_put_incoming(struct liveupdate_flb *flb)
if (!refcount_dec_and_test(&private->incoming.count))
return;
- if (!private->incoming.retrieved) {
+ if (private->incoming.retrieve_status <= 0) {
int err = luo_flb_retrieve_one(flb);
if (WARN_ON(err))
@@ -264,7 +268,7 @@ int luo_flb_file_preserve(struct liveupdate_file_handler *fh)
exit_err:
list_for_each_entry_continue_reverse(iter, flb_list, list)
- luo_flb_file_unpreserve_one(iter->flb);
+ liveupdate_flb_put_outgoing(iter->flb);
up_read(&luo_register_rwlock);
return err;
@@ -289,7 +293,7 @@ void luo_flb_file_unpreserve(struct liveupdate_file_handler *fh)
guard(rwsem_read)(&luo_register_rwlock);
list_for_each_entry_reverse(iter, flb_list, list)
- luo_flb_file_unpreserve_one(iter->flb);
+ liveupdate_flb_put_outgoing(iter->flb);
}
/**
@@ -544,6 +548,10 @@ int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb, void **objp)
return -EOPNOTSUPP;
guard(mutex)(&private->outgoing.lock);
+ if (!private->outgoing.obj)
+ return -ENOENT;
+
+ refcount_inc(&private->outgoing.count);
*objp = private->outgoing.obj;
return 0;
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index b79b2a488974..f48e9a4185f9 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -154,7 +154,6 @@ static struct luo_session *luo_session_alloc(const char *name)
return ERR_PTR(-ENOMEM);
strscpy(session->name, name, sizeof(session->name));
- INIT_LIST_HEAD(&session->file_set.files_list);
luo_file_set_init(&session->file_set);
INIT_LIST_HEAD(&session->list);
mutex_init(&session->mutex);
@@ -316,8 +315,12 @@ static int luo_session_finish(struct luo_session *session,
struct luo_ucmd *ucmd)
{
struct liveupdate_session_finish *argp = ucmd->cmd;
- int err = luo_session_finish_one(session);
+ int err;
+
+ if (argp->reserved)
+ return -EINVAL;
+ err = luo_session_finish_one(session);
if (err)
return err;
@@ -378,7 +381,7 @@ static const struct luo_ioctl_op luo_session_ioctl_ops[] = {
IOCTL_OP(LIVEUPDATE_SESSION_RETRIEVE_FD, luo_session_retrieve_fd,
struct liveupdate_session_retrieve_fd, token, LUO_IOCTL_INCOMING),
IOCTL_OP(LIVEUPDATE_SESSION_GET_NAME, luo_session_get_name,
- struct liveupdate_session_retrieve_fd, token, LUO_IOCTL_ALL),
+ struct liveupdate_session_get_name, name, LUO_IOCTL_ALL),
};
static bool luo_ioctl_type_valid(struct luo_session *session,
diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c
index 2d4c5bab5af8..c3dc84a7cef2 100644
--- a/kernel/locking/lockdep.c
+++ b/kernel/locking/lockdep.c
@@ -787,17 +787,33 @@ static void lockdep_print_held_locks(struct task_struct *p)
{
int i, depth = READ_ONCE(p->lockdep_depth);
- if (!depth)
- printk("no locks held by %s/%d.\n", p->comm, task_pid_nr(p));
- else
- printk("%d lock%s held by %s/%d:\n", depth,
- str_plural(depth), p->comm, task_pid_nr(p));
/*
- * It's not reliable to print a task's held locks if it's not sleeping
- * and it's not the current task.
+ * Note that it's always somewhat unreliable to print held locks
+ * of a task that is running on another CPU, but we cannot guarantee
+ * the stability of ->held_locks without actually stopping all active
+ * remote CPUs, which we absolutely do not want to do because it's
+ * very intrusive and thus slow.
+ *
+ * So we do the next best thing here: we print out the held lock
+ * array on a best-effort basis, without crashing even if the
+ * fields are being modified on another CPU. Note the careful
+ * construction of print_lock() so that it never crashes.
+ *
+ * We also print out the CPU the task is or was last running on, with
+ * the message saying 'on CPU...' if the task is running, and
+ * 'last CPU' if it's not.
+ *
+ * Also note that the task_is_running(p) information is fundamentally
+ * racy: even if the message says the task is 'on CPU', the task may
+ * have scheduled out already, or if it says 'last CPU', it may just
+ * have scheduled in on another CPU. But even with these limitations
+ * it's still useful debuggining information.
*/
- if (p != current && task_is_running(p))
- return;
+ printk("locks held by %s/%d: %d, %s CPU#%d%s\n",
+ p->comm, task_pid_nr(p), depth,
+ task_is_running(p) ? "last" : "on", task_cpu(p),
+ depth > 0 ? ":" : "");
+
for (i = 0; i < depth; i++) {
printk(" #%d: ", i);
print_lock(p->held_locks + i);
@@ -947,6 +963,34 @@ look_up_lock_class(const struct lockdep_map *lock, unsigned int subclass)
return NULL;
}
+static __always_inline bool lock_class_cache_is_valid(const struct lockdep_map *lock,
+ const struct lock_class *class,
+ unsigned int subclass)
+{
+ unsigned int class_subclass;
+
+ if (!class)
+ return false;
+
+ if (unlikely(class < lock_classes || class >= lock_classes + MAX_LOCKDEP_KEYS))
+ return false;
+
+ if (unlikely(!arch_test_bit(class - lock_classes, lock_classes_in_use)))
+ return false;
+
+ if (unlikely(!lock->key))
+ return false;
+
+ class_subclass = subclass ? subclass : class->subclass;
+ if (unlikely(class_subclass >= MAX_LOCKDEP_SUBCLASSES))
+ return false;
+
+ if (unlikely(READ_ONCE(class->key) != lock->key->subkeys + class_subclass))
+ return false;
+
+ return true;
+}
+
/*
* Static locks do not have their class-keys yet - for them the key is
* the lock object itself. If the lock is in the per cpu area, the
@@ -1379,9 +1423,9 @@ out_unlock_set:
out_set_class_cache:
if (!subclass || force)
- lock->class_cache[0] = class;
+ WRITE_ONCE(lock->class_cache[0], class);
else if (subclass < NR_LOCKDEP_CACHING_CLASSES)
- lock->class_cache[subclass] = class;
+ WRITE_ONCE(lock->class_cache[subclass], class);
/*
* Hash collision, did we smoke some? We found a class with a matching
@@ -4941,7 +4985,7 @@ void lockdep_init_map_type(struct lockdep_map *lock, const char *name,
int i;
for (i = 0; i < NR_LOCKDEP_CACHING_CLASSES; i++)
- lock->class_cache[i] = NULL;
+ WRITE_ONCE(lock->class_cache[i], NULL);
#ifdef CONFIG_LOCK_STAT
lock->cpu = raw_smp_processor_id();
@@ -5006,12 +5050,15 @@ EXPORT_SYMBOL_GPL(__lockdep_no_track__);
void lockdep_set_lock_cmp_fn(struct lockdep_map *lock, lock_cmp_fn cmp_fn,
lock_print_fn print_fn)
{
- struct lock_class *class = lock->class_cache[0];
+ struct lock_class *class = READ_ONCE(lock->class_cache[0]);
unsigned long flags;
raw_local_irq_save(flags);
lockdep_recursion_inc();
+ if (!lock_class_cache_is_valid(lock, class, 0))
+ class = NULL;
+
if (!class)
class = register_lock_class(lock, 0, 0);
@@ -5077,7 +5124,7 @@ static int __lock_is_held(const struct lockdep_map *lock, int read);
static int __lock_acquire(struct lockdep_map *lock, unsigned int subclass,
int trylock, int read, int check, int hardirqs_off,
struct lockdep_map *nest_lock, unsigned long ip,
- int references, int pin_count, int sync)
+ int references, int pin_count, int sync, int seq)
{
struct task_struct *curr = current;
struct lock_class *class = NULL;
@@ -5103,8 +5150,11 @@ static int __lock_acquire(struct lockdep_map *lock, unsigned int subclass,
if (DEBUG_LOCKS_WARN_ON(subclass >= MAX_LOCKDEP_SUBCLASSES))
return 0;
- if (subclass < NR_LOCKDEP_CACHING_CLASSES)
- class = lock->class_cache[subclass];
+ if (subclass < NR_LOCKDEP_CACHING_CLASSES) {
+ class = READ_ONCE(lock->class_cache[subclass]);
+ if (!lock_class_cache_is_valid(lock, class, subclass))
+ class = NULL;
+ }
/*
* Not cached?
*/
@@ -5183,6 +5233,7 @@ static int __lock_acquire(struct lockdep_map *lock, unsigned int subclass,
hlock->holdtime_stamp = lockstat_clock();
#endif
hlock->pin_count = pin_count;
+ hlock->seq_count = seq;
if (check_wait_context(curr, hlock))
return 0;
@@ -5307,9 +5358,9 @@ static noinstr int match_held_lock(const struct held_lock *hlock,
return 1;
if (hlock->references) {
- const struct lock_class *class = lock->class_cache[0];
+ const struct lock_class *class = READ_ONCE(lock->class_cache[0]);
- if (!class)
+ if (!lock_class_cache_is_valid(lock, class, 0))
class = look_up_lock_class(lock, 0);
/*
@@ -5388,7 +5439,7 @@ static int reacquire_held_locks(struct task_struct *curr, unsigned int depth,
hlock->read, hlock->check,
hlock->hardirqs_off,
hlock->nest_lock, hlock->acquire_ip,
- hlock->references, hlock->pin_count, 0)) {
+ hlock->references, hlock->pin_count, 0, hlock->seq_count)) {
case 0:
return 1;
case 1:
@@ -5437,6 +5488,8 @@ __lock_set_class(struct lockdep_map *lock, const char *name,
lock->wait_type_outer,
lock->lock_type);
class = register_lock_class(lock, subclass, 0);
+ if (!class)
+ return 0;
hlock->class_idx = class - lock_classes;
curr->lockdep_depth = i;
@@ -5669,14 +5722,17 @@ static void __lock_unpin_lock(struct lockdep_map *lock, struct pin_cookie cookie
struct held_lock *hlock = curr->held_locks + i;
if (match_held_lock(hlock, lock)) {
+ int pin_count;
+
if (WARN(!hlock->pin_count, "unpinning an unpinned lock\n"))
return;
- hlock->pin_count -= cookie.val;
+ pin_count = hlock->pin_count - cookie.val;
- if (WARN((int)hlock->pin_count < 0, "pin count corrupted\n"))
- hlock->pin_count = 0;
+ if (WARN(pin_count < 0, "pin count corrupted\n"))
+ pin_count = 0;
+ hlock->pin_count = pin_count;
return;
}
}
@@ -5684,6 +5740,24 @@ static void __lock_unpin_lock(struct lockdep_map *lock, struct pin_cookie cookie
WARN(1, "unpinning an unheld lock\n");
}
+static u32 __lock_sequence(struct lockdep_map *lock)
+{
+ struct task_struct *curr = current;
+ int i;
+
+ if (unlikely(!debug_locks))
+ return ~0;
+
+ for (i = 0; i < curr->lockdep_depth; i++) {
+ struct held_lock *hlock = curr->held_locks + i;
+
+ if (match_held_lock(hlock, lock))
+ return hlock->seq_count;
+ }
+
+ return ~0;
+}
+
/*
* Check whether we follow the irq-flags state precisely:
*/
@@ -5866,7 +5940,8 @@ void lock_acquire(struct lockdep_map *lock, unsigned int subclass,
lockdep_recursion_inc();
__lock_acquire(lock, subclass, trylock, read, check,
- irqs_disabled_flags(flags), nest_lock, ip, 0, 0, 0);
+ irqs_disabled_flags(flags), nest_lock, ip, 0, 0, 0,
+ ++current->lockdep_seq);
lockdep_recursion_finish();
raw_local_irq_restore(flags);
}
@@ -5914,7 +5989,8 @@ void lock_sync(struct lockdep_map *lock, unsigned subclass, int read,
lockdep_recursion_inc();
__lock_acquire(lock, subclass, 0, read, check,
- irqs_disabled_flags(flags), nest_lock, ip, 0, 0, 1);
+ irqs_disabled_flags(flags), nest_lock, ip, 0, 0, 1,
+ ++current->lockdep_seq);
check_chain_key(current);
lockdep_recursion_finish();
raw_local_irq_restore(flags);
@@ -6000,6 +6076,26 @@ void lock_unpin_lock(struct lockdep_map *lock, struct pin_cookie cookie)
}
EXPORT_SYMBOL_GPL(lock_unpin_lock);
+u32 lock_sequence(struct lockdep_map *lock)
+{
+ unsigned long flags;
+ u32 seq = ~0;
+
+ if (unlikely(!lockdep_enabled()))
+ return seq;
+
+ raw_local_irq_save(flags);
+ check_flags(flags);
+
+ lockdep_recursion_inc();
+ seq = __lock_sequence(lock);
+ lockdep_recursion_finish();
+ raw_local_irq_restore(flags);
+
+ return seq;
+}
+EXPORT_SYMBOL_GPL(lock_sequence);
+
#ifdef CONFIG_LOCK_STAT
static void print_lock_contention_bug(struct task_struct *curr,
struct lockdep_map *lock,
diff --git a/kernel/locking/mutex.c b/kernel/locking/mutex.c
index 8a85912d7ee6..942a939cee95 100644
--- a/kernel/locking/mutex.c
+++ b/kernel/locking/mutex.c
@@ -1272,6 +1272,10 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(contention_begin);
EXPORT_TRACEPOINT_SYMBOL_GPL(contention_end);
EXPORT_TRACEPOINT_SYMBOL_GPL(contended_release);
+__weak int arch_contended_release_trace_reg(void) { return 0; }
+
+__weak void arch_contended_release_trace_unreg(void) { }
+
/**
* atomic_dec_and_mutex_lock - return holding mutex if we dec to 0
* @cnt: the atomic which we are to dec
diff --git a/kernel/locking/percpu-rwsem.c b/kernel/locking/percpu-rwsem.c
index f7e152c40d6d..6c78961fe753 100644
--- a/kernel/locking/percpu-rwsem.c
+++ b/kernel/locking/percpu-rwsem.c
@@ -211,7 +211,7 @@ EXPORT_SYMBOL_GPL(percpu_is_read_locked);
*/
static bool readers_active_check(struct percpu_rw_semaphore *sem)
{
- if (per_cpu_sum(*sem->read_count) != 0)
+ if (data_race(per_cpu_sum(*sem->read_count)) != 0)
return false;
/*
diff --git a/kernel/locking/qspinlock.c b/kernel/locking/qspinlock.c
index af8d122bb649..33fe6d437c8f 100644
--- a/kernel/locking/qspinlock.c
+++ b/kernel/locking/qspinlock.c
@@ -104,6 +104,28 @@ static __always_inline u32 __pv_wait_head_or_lock(struct qspinlock *lock,
#define queued_spin_lock_slowpath native_queued_spin_lock_slowpath
#endif
+#if !defined(queued_spin_unlock) && \
+ IS_ENABLED(CONFIG_QUEUED_SPINLOCKS_TRACE_CONTENDED_RELEASE)
+/*
+ * Out-of-line trace-and-release path for queued_spin_unlock(), used when
+ * the contended_release tracepoint is enabled.
+ *
+ * queued_spin_release() is duplicated here on purpose: doing the release
+ * in this function (rather than tracing here and releasing in the caller)
+ * lets queued_spin_unlock() return right after the call, so the
+ * tracepoint-disabled hot path never has to keep lock live across a call
+ * in a callee-saved register. Keep this release in sync with the one in
+ * queued_spin_unlock().
+ */
+void __lockfunc queued_spin_release_traced(struct qspinlock *lock)
+{
+ if (queued_spin_is_contended(lock))
+ trace_call__contended_release(lock);
+ queued_spin_release(lock);
+}
+EXPORT_SYMBOL(queued_spin_release_traced);
+#endif
+
#endif /* _GEN_PV_LOCK_SLOWPATH */
/**
diff --git a/kernel/locking/rtmutex_api.c b/kernel/locking/rtmutex_api.c
index 5d48d64725b1..eb18b094473c 100644
--- a/kernel/locking/rtmutex_api.c
+++ b/kernel/locking/rtmutex_api.c
@@ -423,6 +423,7 @@ int __sched rt_mutex_wait_proxy_lock(struct rt_mutex_base *lock,
{
int ret;
+ rt_mutex_futex_pre_schedule();
raw_spin_lock_irq(&lock->wait_lock);
/* sleep on the mutex */
set_current_state(TASK_INTERRUPTIBLE);
@@ -433,6 +434,7 @@ int __sched rt_mutex_wait_proxy_lock(struct rt_mutex_base *lock,
*/
fixup_rt_mutex_waiters(lock, true);
raw_spin_unlock_irq(&lock->wait_lock);
+ rt_mutex_futex_post_schedule();
return ret;
}
diff --git a/kernel/locking/spinlock.c b/kernel/locking/spinlock.c
index b42d293da38b..83a17eaf5717 100644
--- a/kernel/locking/spinlock.c
+++ b/kernel/locking/spinlock.c
@@ -129,6 +129,21 @@ static void __lockfunc __raw_##op##_lock_bh(locktype##_t *lock) \
*/
BUILD_LOCK_OPS(spin, raw_spinlock, __acquires);
+/* No rwlock_t variants for now, so just build this function by hand */
+static void __lockfunc __raw_spin_lock_irq_disable(raw_spinlock_t *lock)
+{
+ for (;;) {
+ preempt_disable();
+ local_interrupt_disable();
+ if (likely(do_raw_spin_trylock(lock)))
+ break;
+ local_interrupt_enable();
+ preempt_enable();
+
+ arch_spin_relax(&lock->raw_lock);
+ }
+}
+
#ifndef CONFIG_PREEMPT_RT
BUILD_LOCK_OPS(read, rwlock, __acquires_shared);
BUILD_LOCK_OPS(write, rwlock, __acquires);
@@ -176,6 +191,14 @@ noinline void __lockfunc _raw_spin_lock_irq(raw_spinlock_t *lock)
EXPORT_SYMBOL(_raw_spin_lock_irq);
#endif
+#ifndef CONFIG_INLINE_SPIN_LOCK_IRQ
+noinline void __lockfunc _raw_spin_lock_irq_disable(raw_spinlock_t *lock)
+{
+ __raw_spin_lock_irq_disable(lock);
+}
+EXPORT_SYMBOL_GPL(_raw_spin_lock_irq_disable);
+#endif
+
#ifndef CONFIG_INLINE_SPIN_LOCK_BH
noinline void __lockfunc _raw_spin_lock_bh(raw_spinlock_t *lock)
{
@@ -208,6 +231,14 @@ noinline void __lockfunc _raw_spin_unlock_irq(raw_spinlock_t *lock)
EXPORT_SYMBOL(_raw_spin_unlock_irq);
#endif
+#ifndef CONFIG_INLINE_SPIN_UNLOCK_IRQ
+noinline void __lockfunc _raw_spin_unlock_irq_enable(raw_spinlock_t *lock)
+{
+ __raw_spin_unlock_irq_enable(lock);
+}
+EXPORT_SYMBOL_GPL(_raw_spin_unlock_irq_enable);
+#endif
+
#ifndef CONFIG_INLINE_SPIN_UNLOCK_BH
noinline void __lockfunc _raw_spin_unlock_bh(raw_spinlock_t *lock)
{
diff --git a/kernel/module/dups.c b/kernel/module/dups.c
index 1d720a5311ba..c9be9df6a65d 100644
--- a/kernel/module/dups.c
+++ b/kernel/module/dups.c
@@ -7,29 +7,22 @@
#define pr_fmt(fmt) "module: " fmt
+#include <linux/bug.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/container_of.h>
+#include <linux/list.h>
+#include <linux/lockdep.h>
#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/param.h>
+#include <linux/printk.h>
+#include <linux/refcount.h>
#include <linux/sched.h>
-#include <linux/sched/task.h>
-#include <linux/binfmts.h>
-#include <linux/syscalls.h>
-#include <linux/unistd.h>
-#include <linux/kmod.h>
#include <linux/slab.h>
-#include <linux/completion.h>
-#include <linux/cred.h>
-#include <linux/file.h>
+#include <linux/string.h>
#include <linux/workqueue.h>
-#include <linux/security.h>
-#include <linux/mount.h>
-#include <linux/kernel.h>
-#include <linux/init.h>
-#include <linux/resource.h>
-#include <linux/notifier.h>
-#include <linux/suspend.h>
-#include <linux/rwsem.h>
-#include <linux/ptrace.h>
-#include <linux/async.h>
-#include <linux/uaccess.h>
#include "internal.h"
@@ -38,32 +31,42 @@
static bool enable_dups_trace = IS_ENABLED(CONFIG_MODULE_DEBUG_AUTOLOAD_DUPS_TRACE);
module_param(enable_dups_trace, bool_enable_only, 0644);
-/*
- * Protects dup_kmod_reqs list, adds / removals with RCU.
- */
+/* A mutex-protected list of active kmod requests. */
static DEFINE_MUTEX(kmod_dup_mutex);
static LIST_HEAD(dup_kmod_reqs);
struct kmod_dup_req {
+ refcount_t refcount;
struct list_head list;
char name[MODULE_NAME_LEN];
struct completion first_req_done;
- struct work_struct complete_work;
struct delayed_work delete_work;
int dup_ret;
};
+static void get_kmod_req(struct kmod_dup_req *kmod_req)
+{
+ refcount_inc(&kmod_req->refcount);
+}
+
+static void put_kmod_req(struct kmod_dup_req *kmod_req)
+{
+ if (refcount_dec_and_test(&kmod_req->refcount))
+ kfree(kmod_req);
+}
+
+DEFINE_FREE(put_kmod_req, struct kmod_dup_req *, if (_T) put_kmod_req(_T))
+
static struct kmod_dup_req *kmod_dup_request_lookup(char *module_name)
{
struct kmod_dup_req *kmod_req;
- list_for_each_entry_rcu(kmod_req, &dup_kmod_reqs, list,
- lockdep_is_held(&kmod_dup_mutex)) {
- if (strlen(kmod_req->name) == strlen(module_name) &&
- !memcmp(kmod_req->name, module_name, strlen(module_name))) {
+ lockdep_assert_held(&kmod_dup_mutex);
+
+ list_for_each_entry(kmod_req, &dup_kmod_reqs, list) {
+ if (!strcmp(kmod_req->name, module_name))
return kmod_req;
- }
- }
+ }
return NULL;
}
@@ -86,58 +89,40 @@ static void kmod_dup_request_delete(struct work_struct *work)
* kmod. The inneficies there are a call to modprobe and modprobe
* just returning 0.
*/
- mutex_lock(&kmod_dup_mutex);
- list_del_rcu(&kmod_req->list);
- synchronize_rcu();
- mutex_unlock(&kmod_dup_mutex);
- kfree(kmod_req);
+ scoped_guard(mutex, &kmod_dup_mutex)
+ list_del(&kmod_req->list);
+
+ put_kmod_req(kmod_req);
}
-static void kmod_dup_request_complete(struct work_struct *work)
+static struct kmod_dup_req *alloc_kmod_req(const char *module_name)
{
- struct kmod_dup_req *kmod_req;
+ struct kmod_dup_req *kmod_req = kzalloc_obj(*kmod_req);
- kmod_req = container_of(work, struct kmod_dup_req, complete_work);
-
- /*
- * This will ensure that the kernel will let all the waiters get
- * informed its time to check the return value. It's time to
- * go home.
- */
- complete_all(&kmod_req->first_req_done);
+ if (!kmod_req)
+ return NULL;
- /*
- * Now that we have allowed prior request_module() calls to go on
- * with life, let's schedule deleting this entry. We don't have
- * to do it right away, but we *eventually* want to do it so to not
- * let this linger forever as this is just a boot optimization for
- * possible abuses of vmalloc() incurred by finit_module() thrashing.
- */
- queue_delayed_work(system_dfl_wq, &kmod_req->delete_work, 60 * HZ);
+ refcount_set(&kmod_req->refcount, 1);
+ strscpy(kmod_req->name, module_name);
+ INIT_DELAYED_WORK(&kmod_req->delete_work, kmod_dup_request_delete);
+ init_completion(&kmod_req->first_req_done);
+ return kmod_req;
}
bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret)
{
- struct kmod_dup_req *kmod_req, *new_kmod_req;
+ struct kmod_dup_req *kmod_req __free(put_kmod_req) = NULL;
int ret;
- /*
- * Pre-allocate the entry in case we have to use it later
- * to avoid contention with the mutex.
- */
- new_kmod_req = kzalloc_obj(*new_kmod_req);
- if (!new_kmod_req)
- return false;
-
- memcpy(new_kmod_req->name, module_name, strlen(module_name));
- INIT_WORK(&new_kmod_req->complete_work, kmod_dup_request_complete);
- INIT_DELAYED_WORK(&new_kmod_req->delete_work, kmod_dup_request_delete);
- init_completion(&new_kmod_req->first_req_done);
+ scoped_guard(mutex, &kmod_dup_mutex) {
+ struct kmod_dup_req *new_kmod_req;
- mutex_lock(&kmod_dup_mutex);
+ kmod_req = kmod_dup_request_lookup(module_name);
+ if (kmod_req) {
+ get_kmod_req(kmod_req);
+ break;
+ }
- kmod_req = kmod_dup_request_lookup(module_name);
- if (!kmod_req) {
/*
* If the first request that came through for a module
* was with request_module_nowait() we cannot wait for it
@@ -150,9 +135,7 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret)
* would benefit from duplicate detection.
*/
if (!wait) {
- kfree(new_kmod_req);
pr_debug("New request_module_nowait() for %s -- cannot track duplicates for this request\n", module_name);
- mutex_unlock(&kmod_dup_mutex);
return false;
}
@@ -161,14 +144,14 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret)
* keep tab on duplicates later.
*/
pr_debug("New request_module() for %s\n", module_name);
- list_add_rcu(&new_kmod_req->list, &dup_kmod_reqs);
- mutex_unlock(&kmod_dup_mutex);
+ new_kmod_req = alloc_kmod_req(module_name);
+ if (!new_kmod_req)
+ return false;
+ list_add(&new_kmod_req->list, &dup_kmod_reqs);
return false;
}
- mutex_unlock(&kmod_dup_mutex);
/* We are dealing with a duplicate request now */
- kfree(new_kmod_req);
/*
* To fix these try to use try_then_request_module() instead as that
@@ -214,7 +197,6 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret)
/* Now the duplicate request has the same exact return value as the first request */
*dup_ret = kmod_req->dup_ret;
-
return true;
}
@@ -222,26 +204,29 @@ void kmod_dup_request_announce(char *module_name, int ret)
{
struct kmod_dup_req *kmod_req;
- mutex_lock(&kmod_dup_mutex);
+ /*
+ * Look for a kmod_dup_req previously added in
+ * kmod_dup_request_exists_wait(). Note that a request_module_nowait()
+ * without its own kmod_dup_req entry can announce a result of
+ * a concurrent request_module() call.
+ */
+ scoped_guard(mutex, &kmod_dup_mutex) {
+ kmod_req = kmod_dup_request_lookup(module_name);
+ if (!kmod_req || completion_done(&kmod_req->first_req_done))
+ return;
- kmod_req = kmod_dup_request_lookup(module_name);
- if (!kmod_req)
- goto out;
+ kmod_req->dup_ret = ret;
- kmod_req->dup_ret = ret;
+ /* Inform all duplicate waiters to check the return value. */
+ complete_all(&kmod_req->first_req_done);
+ }
/*
- * If we complete() here we may allow duplicate threads
- * to continue before the first one that submitted the
- * request. We're in no rush also, given that each and
- * every bounce back to userspace is slow we avoid that
- * with a slight delay here. So queueue up the completion
- * and let duplicates suffer, just wait a tad bit longer.
- * There is no rush. But we also don't want to hold the
- * caller up forever or introduce any boot delays.
+ * Now that we have allowed prior request_module() calls to go on
+ * with life, let's schedule deleting this entry. We don't have
+ * to do it right away, but we *eventually* want to do it so to not
+ * let this linger forever as this is just a boot optimization for
+ * possible abuses of vmalloc() incurred by finit_module() thrashing.
*/
- queue_work(system_dfl_wq, &kmod_req->complete_work);
-
-out:
- mutex_unlock(&kmod_dup_mutex);
+ queue_delayed_work(system_dfl_wq, &kmod_req->delete_work, 60 * HZ);
}
diff --git a/kernel/module/kallsyms.c b/kernel/module/kallsyms.c
index 0fc11e45df9b..f23126d804b2 100644
--- a/kernel/module/kallsyms.c
+++ b/kernel/module/kallsyms.c
@@ -258,17 +258,25 @@ static const char *find_kallsyms_symbol(struct module *mod,
unsigned int i, best = 0;
unsigned long nextval, bestval;
struct mod_kallsyms *kallsyms = rcu_dereference(mod->kallsyms);
- struct module_memory *mod_mem;
+ struct module_memory *mod_mem = NULL;
- /* At worse, next value is at end of module */
- if (within_module_init(addr, mod))
- mod_mem = &mod->mem[MOD_INIT_TEXT];
- else
- mod_mem = &mod->mem[MOD_TEXT];
+ for_each_mod_mem_type(type) {
+#ifndef CONFIG_KALLSYMS_ALL
+ if (!mod_mem_type_is_text(type))
+ continue;
+#endif
+ if (within_module_mem_type(addr, mod, type)) {
+ mod_mem = &mod->mem[type];
+ break;
+ }
+ }
- nextval = (unsigned long)mod_mem->base + mod_mem->size;
+ if (!mod_mem)
+ return NULL;
- bestval = kallsyms_symbol_value(&kallsyms->symtab[best]);
+ /* Initialize bounds within memory region the address belongs to. */
+ nextval = (unsigned long)mod_mem->base + mod_mem->size;
+ bestval = (unsigned long)mod_mem->base - 1;
/*
* Scan for closest preceding symbol, and next symbol. (ELF
diff --git a/kernel/module/main.c b/kernel/module/main.c
index 46dd8d25a605..c1b34dc1e89a 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -1458,7 +1458,6 @@ static void free_module(struct module *mod)
/* This may be empty, but that's OK */
module_arch_freeing_init(mod);
- kfree(mod->args);
percpu_modfree(mod);
free_mod_mem(mod);
@@ -2011,6 +2010,7 @@ static int elf_validity_cache_sechdrs(struct load_info *info)
* Specifically checks:
*
* * Section name table index is inbounds of section headers
+ * * Section name table type is SHT_STRTAB
* * Section name table is not empty
* * Section name table is NUL terminated
* * All section name offsets are inbounds of the section
@@ -2038,6 +2038,11 @@ static int elf_validity_cache_secstrings(struct load_info *info)
strhdr = &info->sechdrs[info->hdr->e_shstrndx];
+ if (strhdr->sh_type != SHT_STRTAB) {
+ pr_err("Invalid ELF section name table type: %u\n", strhdr->sh_type);
+ return -ENOEXEC;
+ }
+
/*
* The section name table must be NUL-terminated, as required
* by the spec. This makes strcmp and pr_* calls that access
@@ -2204,7 +2209,7 @@ static int elf_validity_cache_index_sym(struct load_info *info)
* Must have &load_info->index.sym populated.
*
* Looks at the symbol table's associated string table, makes sure it is
- * in-bounds, and caches it.
+ * in-bounds and of type SHT_STRTAB, and caches it.
*
* Return: %0 if valid, %-ENOEXEC on failure.
*/
@@ -2218,6 +2223,12 @@ static int elf_validity_cache_index_str(struct load_info *info)
return -ENOEXEC;
}
+ if (info->sechdrs[str_idx].sh_type != SHT_STRTAB) {
+ pr_err("Invalid ELF symbol string table type: %u\n",
+ info->sechdrs[str_idx].sh_type);
+ return -ENOEXEC;
+ }
+
info->index.str = str_idx;
return 0;
}
@@ -3425,7 +3436,7 @@ static int load_module(struct load_info *info, const char __user *uargs,
struct module *mod;
bool module_allocated = false;
long err = 0;
- char *after_dashes;
+ char *args = NULL, *after_dashes;
/*
* Do the signature check (if any) first. All that
@@ -3523,9 +3534,9 @@ static int load_module(struct load_info *info, const char __user *uargs,
flush_module_icache(mod);
/* Now copy in args */
- mod->args = strndup_user(uargs, ~0UL >> 1);
- if (IS_ERR(mod->args)) {
- err = PTR_ERR(mod->args);
+ args = strndup_user(uargs, ~0UL >> 1);
+ if (IS_ERR(args)) {
+ err = PTR_ERR(args);
goto free_arch_cleanup;
}
@@ -3546,7 +3557,7 @@ static int load_module(struct load_info *info, const char __user *uargs,
mod->async_probe_requested = async_probe;
/* Module is ready to execute: parsing args may do that. */
- after_dashes = parse_args(mod->name, mod->args, mod->kp, mod->num_kp,
+ after_dashes = parse_args(mod->name, args, mod->kp, mod->num_kp,
-32768, 32767, mod,
unknown_module_param_cb);
if (IS_ERR(after_dashes)) {
@@ -3556,6 +3567,8 @@ static int load_module(struct load_info *info, const char __user *uargs,
pr_warn("%s: parameters '%s' after `--' ignored\n",
mod->name, after_dashes);
}
+ kfree(args);
+ args = NULL;
/* Link in to sysfs. */
err = mod_sysfs_setup(mod, info, mod->kp, mod->num_kp);
@@ -3568,8 +3581,9 @@ static int load_module(struct load_info *info, const char __user *uargs,
goto sysfs_cleanup;
}
- if (codetag_load_module(mod))
- goto sysfs_cleanup;
+ err = codetag_load_module(mod);
+ if (err)
+ goto livepatch_cleanup;
/* Get rid of temporary copy. */
free_copy(info, flags);
@@ -3579,6 +3593,9 @@ static int load_module(struct load_info *info, const char __user *uargs,
return do_init_module(mod);
+ livepatch_cleanup:
+ if (is_livepatch_module(mod))
+ free_module_elf(mod);
sysfs_cleanup:
mod_sysfs_teardown(mod);
coming_cleanup:
@@ -3597,7 +3614,7 @@ static int load_module(struct load_info *info, const char __user *uargs,
ddebug_cleanup:
ftrace_release_mod(mod);
synchronize_rcu();
- kfree(mod->args);
+ kfree(args);
free_arch_cleanup:
module_arch_cleanup(mod);
free_modinfo:
diff --git a/kernel/module/procfs.c b/kernel/module/procfs.c
index 0a4841e88adb..90712aa9dd13 100644
--- a/kernel/module/procfs.c
+++ b/kernel/module/procfs.c
@@ -64,7 +64,7 @@ static void m_stop(struct seq_file *m, void *p)
static unsigned int module_total_size(struct module *mod)
{
- int size = 0;
+ unsigned int size = 0;
for_each_mod_mem_type(type)
size += mod->mem[type].size;
diff --git a/kernel/module/stats.c b/kernel/module/stats.c
index 3a9672f93a8e..78b59bc05387 100644
--- a/kernel/module/stats.c
+++ b/kernel/module/stats.c
@@ -253,7 +253,7 @@ int try_add_failed_module(const char *name, enum fail_dup_mod_reason reason)
mod_fail = kzalloc_obj(*mod_fail);
if (!mod_fail)
return -ENOMEM;
- memcpy(mod_fail->name, name, strlen(name));
+ strscpy(mod_fail->name, name);
__set_bit(reason, &mod_fail->dup_fail_mask);
atomic_long_inc(&mod_fail->count);
list_add_rcu(&mod_fail->list, &dup_failed_modules);
diff --git a/kernel/nstree.c b/kernel/nstree.c
index 6d12e5900ac0..831f279d174a 100644
--- a/kernel/nstree.c
+++ b/kernel/nstree.c
@@ -533,19 +533,13 @@ DEFINE_FREE(ns_put, struct ns_common *, if (!IS_ERR_OR_NULL(_T)) ns_put(_T))
static inline struct ns_common *__must_check legitimize_ns(const struct klistns *kls,
struct ns_common *candidate)
{
- struct ns_common *ns __free(ns_put) = NULL;
-
if (!ns_requested(kls, candidate))
return NULL;
- ns = ns_get_unless_inactive(candidate);
- if (!ns)
- return NULL;
-
- if (!may_list_ns(kls, ns))
+ if (!may_list_ns(kls, candidate))
return NULL;
- return no_free_ptr(ns);
+ return ns_get_unless_inactive(candidate);
}
static ssize_t do_listns_userns(struct klistns *kls)
diff --git a/kernel/params.c b/kernel/params.c
index a668863a4bb6..8b25133fed24 100644
--- a/kernel/params.c
+++ b/kernel/params.c
@@ -136,8 +136,8 @@ static int parse_one(char *param,
if (!val &&
!(params[i].ops->flags & KERNEL_PARAM_OPS_FL_NOARG))
return -EINVAL;
- pr_debug("handling %s with %p\n", param,
- params[i].ops->set);
+ pr_debug("handling %s with value '%s'\n", param,
+ val ? val : "no-arg");
kernel_param_lock(params[i].mod);
if (param_check_unsafe(&params[i]))
err = params[i].ops->set(val, &params[i]);
@@ -261,6 +261,7 @@ EXPORT_SYMBOL_GPL(param_set_uint_minmax);
int param_set_charp(const char *val, const struct kernel_param *kp)
{
+ char *tmp;
size_t len, maxlen = 1024;
len = strnlen(val, maxlen + 1);
@@ -269,19 +270,20 @@ int param_set_charp(const char *val, const struct kernel_param *kp)
return -ENOSPC;
}
- maybe_kfree_parameter(*(char **)kp->arg);
-
/*
* This is a hack. We can't kmalloc() in early boot, and we
* don't need to; this mangled commandline is preserved.
*/
if (slab_is_available()) {
- *(char **)kp->arg = kmalloc_parameter(len + 1);
- if (!*(char **)kp->arg)
+ tmp = kmalloc_parameter(len + 1);
+ if (!tmp)
return -ENOMEM;
- strcpy(*(char **)kp->arg, val);
+ memcpy(tmp, val, len + 1);
} else
- *(const char **)kp->arg = val;
+ tmp = (char *)val;
+
+ maybe_kfree_parameter(*(char **)kp->arg);
+ *(char **)kp->arg = tmp;
return 0;
}
@@ -538,7 +540,7 @@ const struct kernel_param_ops param_ops_string = {
};
EXPORT_SYMBOL(param_ops_string);
-/* sysfs output in /sys/modules/XYZ/parameters/ */
+/* sysfs output in /sys/module/XYZ/parameters/ */
#define to_module_attr(n) container_of_const(n, struct module_attribute, attr)
#define to_module_kobject(n) container_of(n, struct module_kobject, kobj)
diff --git a/kernel/pid.c b/kernel/pid.c
index f55189a3d07d..95b8ccfa8269 100644
--- a/kernel/pid.c
+++ b/kernel/pid.c
@@ -324,8 +324,10 @@ struct pid *alloc_pid(struct pid_namespace *ns, pid_t *arg_set_tid,
* error path may try to wakeup the possibly freed ns->child_reaper.
*/
retval = -ENOMEM;
- if (unlikely(!(ns->pid_allocated & PIDNS_ADDING)))
- goto out_free;
+ for (upid = pid->numbers + ns->level; upid >= pid->numbers; --upid)
+ if (unlikely(!(upid->ns->pid_allocated & PIDNS_ADDING)))
+ goto out_free;
+
for (upid = pid->numbers + ns->level; upid >= pid->numbers; --upid) {
/* Make the PID visible to find_pid_ns. */
idr_replace(&upid->ns->idr, pid, upid->nr);
@@ -764,29 +766,6 @@ static struct ctl_table_root pid_table_root = {
.set_ownership = pid_table_root_set_ownership,
};
-static int proc_do_cad_pid(const struct ctl_table *table, int write, void *buffer,
- size_t *lenp, loff_t *ppos)
-{
- struct pid *new_pid;
- pid_t tmp_pid;
- int r;
- struct ctl_table tmp_table = *table;
-
- tmp_pid = pid_vnr(cad_pid);
- tmp_table.data = &tmp_pid;
-
- r = proc_dointvec(&tmp_table, write, buffer, lenp, ppos);
- if (r || !write)
- return r;
-
- new_pid = find_get_pid(tmp_pid);
- if (!new_pid)
- return -ESRCH;
-
- put_pid(xchg(&cad_pid, new_pid));
- return 0;
-}
-
static const struct ctl_table pid_table[] = {
{
.procname = "pid_max",
@@ -797,14 +776,6 @@ static const struct ctl_table pid_table[] = {
.extra1 = &pid_max_min,
.extra2 = &pid_max_max,
},
-#ifdef CONFIG_PROC_SYSCTL
- {
- .procname = "cad_pid",
- .maxlen = sizeof(int),
- .mode = 0600,
- .proc_handler = proc_do_cad_pid,
- },
-#endif
};
#endif
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index 530c897311d4..71165e7f04f4 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -268,6 +268,16 @@ config DPM_WATCHDOG
captured in pstore device for inspection in subsequent
boot session.
+config DPM_WATCHDOG_ENABLED
+ bool "Enable DPM watchdog by default"
+ depends on DPM_WATCHDOG
+ default y
+ help
+ If you say Y here, the DPM watchdog will be enabled by default.
+ If you say N, it will be compiled in but disabled. It can be
+ enabled at boot time via the "pm.dpm_watchdog_enabled" kernel
+ parameter or at runtime via sysfs.
+
config DPM_WATCHDOG_TIMEOUT
int "Watchdog timeout to panic in seconds"
range 1 120
diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c
index d2479c69d71a..c13f68ab7f6e 100644
--- a/kernel/power/hibernate.c
+++ b/kernel/power/hibernate.c
@@ -408,9 +408,18 @@ int hibernation_snapshot(int platform_mode)
if (error)
goto Close;
+ error = dpm_prepare(PMSG_FREEZE);
+ if (error)
+ goto Complete;
+
+ /* Preallocate image memory before freezing kernel threads and shutting down devices. */
+ error = hibernate_preallocate_memory();
+ if (error)
+ goto Complete;
+
error = freeze_kernel_threads();
if (error)
- goto Close;
+ goto Cleanup;
if (hibernation_test(TEST_FREEZER)) {
@@ -422,15 +431,6 @@ int hibernation_snapshot(int platform_mode)
goto Thaw;
}
- error = dpm_prepare(PMSG_FREEZE);
- if (error)
- goto Complete;
-
- /* Preallocate image memory before shutting down devices. */
- error = hibernate_preallocate_memory();
- if (error)
- goto Complete;
-
console_suspend_all();
pm_restrict_gfp_mask();
@@ -464,10 +464,12 @@ int hibernation_snapshot(int platform_mode)
platform_end(platform_mode);
return error;
- Complete:
- dpm_complete(PMSG_RECOVER);
Thaw:
thaw_kernel_threads();
+ Cleanup:
+ swsusp_free();
+ Complete:
+ dpm_complete(PMSG_RECOVER);
goto Close;
}
diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c
index d933b5b2c05d..b209712cb2c3 100644
--- a/kernel/power/snapshot.c
+++ b/kernel/power/snapshot.c
@@ -458,7 +458,7 @@ static struct rtree_node *alloc_rtree_node(gfp_t gfp_mask, int safe_needed,
return node;
}
-/**
+/*
* add_rtree_block - Add a new leave node to the radix tree.
*
* The leave nodes need to be allocated in order to keep the leaves
@@ -528,7 +528,7 @@ static int add_rtree_block(struct mem_zone_bm_rtree *zone, gfp_t gfp_mask,
static void free_zone_bm_rtree(struct mem_zone_bm_rtree *zone,
int clear_nosave_free);
-/**
+/*
* create_zone_bm_rtree - Create a radix tree for one zone.
*
* Allocated the mem_zone_bm_rtree structure and initializes it.
@@ -566,7 +566,7 @@ static struct mem_zone_bm_rtree *create_zone_bm_rtree(gfp_t gfp_mask,
return zone;
}
-/**
+/*
* free_zone_bm_rtree - Free the memory of the radix tree.
*
* Free all node pages of the radix tree. The mem_zone_bm_rtree
@@ -678,7 +678,7 @@ static int create_mem_extents(struct list_head *list, gfp_t gfp_mask)
return 0;
}
-/**
+/*
* memory_bm_create - Allocate memory for a memory bitmap.
*/
static int memory_bm_create(struct memory_bitmap *bm, gfp_t gfp_mask,
@@ -720,7 +720,7 @@ static int memory_bm_create(struct memory_bitmap *bm, gfp_t gfp_mask,
goto Exit;
}
-/**
+/*
* memory_bm_free - Free memory occupied by the memory bitmap.
* @bm: Memory bitmap.
*/
@@ -736,7 +736,7 @@ static void memory_bm_free(struct memory_bitmap *bm, int clear_nosave_free)
INIT_LIST_HEAD(&bm->zones);
}
-/**
+/*
* memory_bm_find_bit - Find the bit for a given PFN in a memory bitmap.
*
* Find the bit in memory bitmap @bm that corresponds to the given PFN.
@@ -988,7 +988,7 @@ static void memory_bm_recycle(struct memory_bitmap *bm)
}
}
-/**
+/*
* register_nosave_region - Register a region of unsaveable memory.
*
* Register a range of page frames the contents of which should not be saved
@@ -1305,7 +1305,7 @@ static unsigned int count_free_highmem_pages(void)
return cnt;
}
-/**
+/*
* saveable_highmem_page - Check if a highmem page is saveable.
*
* Determine whether a highmem page should be included in a hibernation image.
@@ -1362,7 +1362,7 @@ static unsigned int count_highmem_pages(void)
}
#endif /* CONFIG_HIGHMEM */
-/**
+/*
* saveable_page - Check if the given page is saveable.
*
* Determine whether a non-highmem page should be included in a hibernation
@@ -1440,7 +1440,7 @@ static inline bool do_copy_page(long *dst, long *src)
return !z;
}
-/**
+/*
* safe_copy_page - Copy a page in a safe way.
*
* Check if the page we are going to copy is marked as present in the kernel
@@ -1687,7 +1687,7 @@ static unsigned long preallocate_image_highmem(unsigned long nr_pages)
return preallocate_image_pages(nr_pages, GFP_IMAGE | __GFP_HIGHMEM);
}
-/**
+/*
* __fraction - Compute (an approximation of) x * (multiplier / base).
*/
static unsigned long __fraction(u64 x, u64 multiplier, u64 base)
@@ -1982,7 +1982,7 @@ int hibernate_preallocate_memory(void)
}
#ifdef CONFIG_HIGHMEM
-/**
+/*
* count_pages_for_highmem - Count non-highmem pages needed for copying highmem.
*
* Compute the number of non-highmem pages that will be necessary for creating
@@ -2003,7 +2003,7 @@ static unsigned int count_pages_for_highmem(unsigned int nr_highmem)
static unsigned int count_pages_for_highmem(unsigned int nr_highmem) { return 0; }
#endif /* CONFIG_HIGHMEM */
-/**
+/*
* enough_free_mem - Check if there is enough free memory for the image.
*/
static int enough_free_mem(unsigned int nr_pages, unsigned int nr_highmem)
@@ -2023,7 +2023,7 @@ static int enough_free_mem(unsigned int nr_pages, unsigned int nr_highmem)
}
#ifdef CONFIG_HIGHMEM
-/**
+/*
* get_highmem_buffer - Allocate a buffer for highmem pages.
*
* If there are some highmem pages in the hibernation image, we may need a
@@ -2035,7 +2035,7 @@ static inline int get_highmem_buffer(int safe_needed)
return buffer ? 0 : -ENOMEM;
}
-/**
+/*
* alloc_highmem_pages - Allocate some highmem pages for the image.
*
* Try to allocate as many pages as needed, but if the number of free highmem
@@ -2065,7 +2065,7 @@ static inline unsigned int alloc_highmem_pages(struct memory_bitmap *bm,
unsigned int n) { return 0; }
#endif /* CONFIG_HIGHMEM */
-/**
+/*
* swsusp_alloc - Allocate memory for hibernation image.
*
* We first try to allocate as many highmem pages as there are
@@ -2292,7 +2292,7 @@ static void duplicate_memory_bitmap(struct memory_bitmap *dst,
}
}
-/**
+/*
* mark_unsafe_pages - Mark pages that were used before hibernation.
*
* Mark the pages that cannot be used for storing the image during restoration,
@@ -2330,7 +2330,7 @@ static int check_header(struct swsusp_info *info)
return 0;
}
-/**
+/*
* load_header - Check the image header and copy the data from it.
*/
static int load_header(struct swsusp_info *info)
@@ -2483,7 +2483,7 @@ static int prepare_highmem_image(struct memory_bitmap *bm,
static struct page *last_highmem_page;
-/**
+/*
* get_highmem_page_buffer - Prepare a buffer to store a highmem image page.
*
* For a given highmem image page get a buffer that suspend_write_next() should
@@ -2706,7 +2706,7 @@ static int prepare_image(struct memory_bitmap *new_bm, struct memory_bitmap *bm,
return error;
}
-/**
+/*
* get_buffer - Get the address to store the next image data page.
*
* Get the address that snapshot_write_next() should return to its caller to
@@ -2797,9 +2797,10 @@ next:
return error;
error = memory_bm_create(&zero_bm, GFP_ATOMIC, PG_ANY);
- if (error)
+ if (error) {
+ memory_bm_free(&copy_bm, PG_UNSAFE_CLEAR);
return error;
-
+ }
nr_zero_pages = 0;
hibernate_restore_protection_begin();
@@ -2843,7 +2844,7 @@ next:
return PAGE_SIZE;
}
-/**
+/*
* snapshot_write_finalize - Complete the loading of a hibernation image.
*
* Must be called after the last call to snapshot_write_next() in case the last
diff --git a/kernel/power/swap.c b/kernel/power/swap.c
index c626e9dc3c1c..c78f1593600b 100644
--- a/kernel/power/swap.c
+++ b/kernel/power/swap.c
@@ -430,19 +430,22 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf,
if (!handle->cur)
return -EINVAL;
- offset = alloc_swapdev_block(root_swap);
- error = write_page(buf, offset, hb);
- if (error)
- return error;
- handle->cur->entries[handle->k++] = offset;
+
+ /*
+ * If the current map page is full, allocate and link next one first.
+ * Delaying this until here avoids writing an empty swap map page when
+ * the image size is an exact MAP_PAGE_ENTRIES multiple.
+ */
if (handle->k >= MAP_PAGE_ENTRIES) {
offset = alloc_swapdev_block(root_swap);
if (!offset)
return -ENOSPC;
+
handle->cur->next_swap = offset;
error = write_page(handle->cur, handle->cur_swap, hb);
if (error)
- goto out;
+ return error;
+
clear_page(handle->cur);
handle->cur_swap = offset;
handle->k = 0;
@@ -450,7 +453,7 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf,
if (hb && low_free_pages() <= handle->reqd_free_pages) {
error = hib_wait_io(hb);
if (error)
- goto out;
+ return error;
/*
* Recalculate the number of required free pages, to
* make sure we never take more than half.
@@ -458,14 +461,21 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf,
handle->reqd_free_pages = reqd_free_pages();
}
}
- out:
- return error;
+
+ offset = alloc_swapdev_block(root_swap);
+ error = write_page(buf, offset, hb);
+ if (error)
+ return error;
+ handle->cur->entries[handle->k++] = offset;
+ return 0;
}
static int flush_swap_writer(struct swap_map_handle *handle)
{
- if (handle->cur && handle->cur_swap)
+ if (handle->cur && handle->cur_swap && handle->k)
return write_page(handle->cur, handle->cur_swap, NULL);
+ else if (handle->cur && handle->cur_swap)
+ return 0;
else
return -EINVAL;
}
@@ -739,7 +749,7 @@ static int save_compressed_image(struct swap_map_handle *handle,
data[thr].cc = crypto_alloc_acomp(hib_comp_algo, 0, CRYPTO_ALG_ASYNC);
if (IS_ERR_OR_NULL(data[thr].cc)) {
- pr_err("Could not allocate comp stream %ld\n", PTR_ERR(data[thr].cc));
+ pr_err("Could not allocate comp stream %pe\n", data[thr].cc);
ret = -EFAULT;
goto out_clean;
}
@@ -1243,7 +1253,7 @@ static int load_compressed_image(struct swap_map_handle *handle,
data[thr].cc = crypto_alloc_acomp(hib_comp_algo, 0, CRYPTO_ALG_ASYNC);
if (IS_ERR_OR_NULL(data[thr].cc)) {
- pr_err("Could not allocate comp stream %ld\n", PTR_ERR(data[thr].cc));
+ pr_err("Could not allocate comp stream %pe\n", data[thr].cc);
ret = -EFAULT;
goto out_clean;
}
diff --git a/kernel/power/wakelock.c b/kernel/power/wakelock.c
index fd763da06a87..a8b6bd5ec46b 100644
--- a/kernel/power/wakelock.c
+++ b/kernel/power/wakelock.c
@@ -63,7 +63,7 @@ static unsigned int number_of_wakelocks;
static inline bool wakelocks_limit_exceeded(void)
{
- return number_of_wakelocks > CONFIG_PM_WAKELOCKS_LIMIT;
+ return number_of_wakelocks >= CONFIG_PM_WAKELOCKS_LIMIT;
}
static inline void increment_wakelocks_number(void)
diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c
index 4b03b019cd5e..fb1b37741952 100644
--- a/kernel/printk/nbcon.c
+++ b/kernel/printk/nbcon.c
@@ -1382,7 +1382,7 @@ bool nbcon_kthread_create(struct console *con)
return true;
kt = kthread_run(nbcon_kthread_func, con, "pr/%s%d", con->name, con->index);
- if (WARN_ON(IS_ERR(kt))) {
+ if (IS_ERR(kt)) {
con_printk(KERN_ERR, con, "failed to start printing thread\n");
return false;
}
@@ -1782,7 +1782,7 @@ bool nbcon_alloc(struct console *con)
}
rcuwait_init(&con->rcuwait);
- init_irq_work(&con->irq_work, nbcon_irq_work);
+ con->irq_work = IRQ_WORK_INIT_LAZY(nbcon_irq_work);
atomic_long_set(&ACCESS_PRIVATE(con, nbcon_prev_seq), -1UL);
nbcon_state_set(con, &state);
@@ -1837,6 +1837,8 @@ void nbcon_free(struct console *con)
/* Synchronize the kthread stop. */
lockdep_assert_console_list_lock_held();
+ irq_work_sync(&con->irq_work);
+
if (printk_kthreads_running) {
nbcon_kthread_stop(con);
@@ -1885,6 +1887,7 @@ bool nbcon_device_try_acquire(struct console *con)
memset(ctxt, 0, sizeof(*ctxt));
ctxt->console = con;
+ /* Keep in sync with nbcon_braille_try_acquire(). */
ctxt->prio = NBCON_PRIO_NORMAL;
if (!nbcon_context_try_acquire(ctxt, false))
@@ -2000,3 +2003,81 @@ void nbcon_kdb_release(struct nbcon_write_context *wctxt)
*/
__nbcon_atomic_flush_pending_con(ctxt->console, prb_next_reserve_seq(prb));
}
+
+/**
+ * nbcon_is_braille - Checks whether the nbcon write context is using Braille console
+ *
+ * @wctxt: checked nbcon write context
+ *
+ * Return: True when the write context is associated with a Braille console.
+ * Otherwise, return false.
+ *
+ * Context: Can be called in any context but only when Braille console is
+ * registered and the struct console could not disappear.
+ */
+bool nbcon_write_context_is_braille(struct nbcon_write_context *wctxt)
+{
+ struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt);
+ struct console *con = ctxt->console;
+
+ return con && con->flags & CON_BRL;
+}
+EXPORT_SYMBOL_GPL(nbcon_write_context_is_braille);
+
+/**
+ * nbcon_braille_try_acquire - Try to acquire nbcon console for braille_write()
+ *
+ * @con: The nbcon console to acquire
+ * @wctxt: The nbcon write context to be used on success
+ *
+ * Context: braille_write() for emitting a single buffer on Braille console.
+ *
+ * Return: True if the console was acquired. False otherwise.
+ *
+ * Braille console is not registered as a proper printk consoles. Instead,
+ * it is integrated with the graphical virtual terminal.
+ *
+ * This function is going to synchronize the Braille write against other
+ * operations on the used serial port. The port can be used also for a user
+ * input but printk() won't emit the messages there directly. It means
+ * the other operations will get synchronized using nbcon_device_try_acquire().
+ */
+bool nbcon_braille_try_acquire(struct console *con,
+ struct nbcon_write_context *wctxt)
+{
+ struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt);
+ bool success;
+
+ memset(ctxt, 0, sizeof(*ctxt));
+ ctxt->console = con;
+ /* Keep in sync with nbcon_device_try_acquire(). */
+ if (panic_on_this_cpu())
+ ctxt->prio = NBCON_PRIO_PANIC;
+ else
+ ctxt->prio = NBCON_PRIO_NORMAL;
+
+ success = nbcon_context_try_acquire(ctxt, false);
+ /*
+ * Try hard in panic, otherwise the Braille console would
+ * miss a text on the related VT.
+ */
+ if (panic_on_this_cpu() && !success) {
+ ctxt->allow_unsafe_takeover = true;
+ success = nbcon_context_try_acquire(ctxt, false);
+ }
+
+ return success;
+}
+
+/**
+ * nbcon_braille_release - Release the nbcon console
+ *
+ * @wctxt: The nbcon write context initialized by a successful
+ * nbcon_braille_try_acquire()
+ */
+void nbcon_braille_release(struct nbcon_write_context *wctxt)
+{
+ struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt);
+
+ nbcon_context_release(ctxt);
+}
diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index 2fe9a963c823..6d3d18a50da7 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -3264,10 +3264,8 @@ static bool console_flush_one_record(bool do_cond_resched, u64 *next_seq, bool *
if (flags & CON_NBCON) {
progress = nbcon_legacy_emit_next_record(con, handover, cookie,
!do_cond_resched);
- printk_seq = nbcon_seq_read(con);
} else {
progress = console_emit_next_record(con, handover, cookie);
- printk_seq = con->seq;
}
/*
@@ -3277,6 +3275,15 @@ static bool console_flush_one_record(bool do_cond_resched, u64 *next_seq, bool *
if (*handover)
goto fail;
+ /*
+ * @con can be used here now that it is certain that this
+ * context is still holding the SRCU read lock.
+ */
+ if (flags & CON_NBCON)
+ printk_seq = nbcon_seq_read(con);
+ else
+ printk_seq = con->seq;
+
/* Track the next of the highest seq flushed. */
if (printk_seq > *next_seq)
*next_seq = printk_seq;
@@ -3725,7 +3732,7 @@ static bool legacy_kthread_create(void)
lockdep_assert_console_list_lock_held();
kt = kthread_run(legacy_kthread_func, NULL, "pr/legacy");
- if (WARN_ON(IS_ERR(kt))) {
+ if (IS_ERR(kt)) {
pr_err("failed to start legacy printing thread\n");
return false;
}
@@ -3890,9 +3897,6 @@ static int console_call_setup(struct console *newcon, char *options)
* the newly registered console with any of the ones selected
* by either the command line or add_preferred_console() and
* setup/enable it.
- *
- * Care need to be taken with consoles that are statically
- * enabled such as netconsole
*/
static int try_enable_preferred_console(struct console *newcon,
bool user_specified)
@@ -3933,14 +3937,6 @@ static int try_enable_preferred_console(struct console *newcon,
return 0;
}
- /*
- * Some consoles, such as pstore and netconsole, can be enabled even
- * without matching. Accept the pre-enabled consoles only when match()
- * and setup() had a chance to be called.
- */
- if (newcon->flags & CON_ENABLED && c->user_specified == user_specified)
- return 0;
-
return -ENOENT;
}
@@ -4123,6 +4119,14 @@ void register_console(struct console *newcon)
if (err == -ENOENT)
err = try_enable_preferred_console(newcon, false);
+ /*
+ * Some consoles, such as pstore and netconsole, can be enabled even
+ * without matching. Accept them at this stage when they had a chance
+ * to match() and call setup().
+ */
+ if (err == -ENOENT && (newcon->flags & CON_ENABLED))
+ err = 0;
+
/* printk() messages are not printed to the Braille console. */
if (err || newcon->flags & CON_BRL) {
if (newcon->flags & CON_NBCON)
diff --git a/kernel/rcu/Kconfig b/kernel/rcu/Kconfig
index 762299291e09..332df7a7a634 100644
--- a/kernel/rcu/Kconfig
+++ b/kernel/rcu/Kconfig
@@ -140,7 +140,6 @@ config FORCE_TASKS_TRACE_RCU
config TASKS_TRACE_RCU
bool
default n
- select IRQ_WORK
config TASKS_TRACE_RCU_NO_MB
bool "Override RCU Tasks Trace inclusion of read-side memory barriers"
diff --git a/kernel/rcu/rcu.h b/kernel/rcu/rcu.h
index fa6d30ce73d1..39a9f6fa9a7b 100644
--- a/kernel/rcu/rcu.h
+++ b/kernel/rcu/rcu.h
@@ -46,16 +46,25 @@
* the number of pending readers that will use
* this inactive index is bounded).
*
- * RCU polled GP special control value:
+ * RCU polled GP special control values:
*
* RCU_GET_STATE_COMPLETED : State value indicating an already-completed
* polled GP has completed. This value covers
* both the state and the counter of the
* grace-period sequence number.
+ *
+ * RCU_GET_STATE_NOT_TRACKED : State value indicating that a GP component
+ * is not tracked by this subsystem and should
+ * not be checked. Used by SRCU and RCU Tasks
+ * which do not track expedited GPs, to prevent
+ * false-positive completion when their
+ * gp_seq entries are checked via
+ * poll_state_synchronize_rcu_full().
*/
-/* Low-order bit definition for polled grace-period APIs. */
+/* Low-order bit definitions for polled grace-period APIs. */
#define RCU_GET_STATE_COMPLETED 0x1
+#define RCU_GET_STATE_NOT_TRACKED 0x2
/* A complete grace period count */
#define RCU_SEQ_GP (RCU_SEQ_STATE_MASK + 1)
@@ -695,4 +704,11 @@ static inline int rcu_stall_notifier_call_chain(unsigned long val, void *v) { re
void synchronize_rcu_trivial_preempt(void);
#endif // #ifdef CONFIG_TRIVIAL_PREEMPT_RCU
+#if defined(CONFIG_RCU_TORTURE_TEST) && defined(CONFIG_RCU_BOOST)
+bool rcu_is_task_rcu_boosted(void);
+#else // #if defined(CONFIG_RCU_TORTURE_TEST) && defined(CONFIG_RCU_BOOST)
+static inline bool rcu_is_task_rcu_boosted(void) { return false; }
+#endif // #else // #if defined(CONFIG_RCU_TORTURE_TEST) && defined(CONFIG_RCU_BOOST)
+
+
#endif /* __LINUX_RCU_H */
diff --git a/kernel/rcu/rcu_segcblist.c b/kernel/rcu/rcu_segcblist.c
index 298a2c573f02..dd770006e7f8 100644
--- a/kernel/rcu/rcu_segcblist.c
+++ b/kernel/rcu/rcu_segcblist.c
@@ -12,6 +12,7 @@
#include <linux/kernel.h>
#include <linux/types.h>
+#include "rcu.h"
#include "rcu_segcblist.h"
/* Initialize simple callback list. */
@@ -307,13 +308,13 @@ struct rcu_head *rcu_segcblist_first_pend_cb(struct rcu_segcblist *rsclp)
/*
* Return false if there are no CBs awaiting grace periods, otherwise,
- * return true and store the nearest waited-upon grace period into *lp.
+ * return true and store the nearest waited-upon grace period state into *gsp.
*/
-bool rcu_segcblist_nextgp(struct rcu_segcblist *rsclp, unsigned long *lp)
+bool rcu_segcblist_nextgp(struct rcu_segcblist *rsclp, struct rcu_gp_seq *gsp)
{
if (!rcu_segcblist_pend_cbs(rsclp))
return false;
- *lp = rsclp->gp_seq[RCU_WAIT_TAIL];
+ *gsp = rsclp->gp_seq[RCU_WAIT_TAIL];
return true;
}
@@ -463,31 +464,15 @@ void rcu_segcblist_insert_pend_cbs(struct rcu_segcblist *rsclp,
}
/*
- * Advance the callbacks in the specified rcu_segcblist structure based
- * on the current value passed in for the grace-period counter.
+ * Clean up and compact the segmented callback list after callbacks have been
+ * advanced to the RCU_DONE_TAIL segment. The @i parameter is the index of the
+ * first segment that was NOT advanced (i.e., the segment after the last one
+ * moved to RCU_DONE_TAIL). This function fixes up tail pointers and compacts
+ * any gaps left by the moved segments.
*/
-void rcu_segcblist_advance(struct rcu_segcblist *rsclp, unsigned long seq)
+static void rcu_segcblist_advance_compact(struct rcu_segcblist *rsclp, int i)
{
- int i, j;
-
- WARN_ON_ONCE(!rcu_segcblist_is_enabled(rsclp));
- if (rcu_segcblist_restempty(rsclp, RCU_DONE_TAIL))
- return;
-
- /*
- * Find all callbacks whose ->gp_seq numbers indicate that they
- * are ready to invoke, and put them into the RCU_DONE_TAIL segment.
- */
- for (i = RCU_WAIT_TAIL; i < RCU_NEXT_TAIL; i++) {
- if (ULONG_CMP_LT(seq, rsclp->gp_seq[i]))
- break;
- WRITE_ONCE(rsclp->tails[RCU_DONE_TAIL], rsclp->tails[i]);
- rcu_segcblist_move_seglen(rsclp, i, RCU_DONE_TAIL);
- }
-
- /* If no callbacks moved, nothing more need be done. */
- if (i == RCU_WAIT_TAIL)
- return;
+ int j;
/* Clean up tail pointers that might have been misordered above. */
for (j = RCU_WAIT_TAIL; j < i; j++)
@@ -509,6 +494,39 @@ void rcu_segcblist_advance(struct rcu_segcblist *rsclp, unsigned long seq)
}
/*
+ * Advance the callbacks in the specified rcu_segcblist structure based
+ * on the current grace-period state. Checks both normal and expedited
+ * grace periods, advancing callbacks when either GP type completes.
+ */
+void rcu_segcblist_advance(struct rcu_segcblist *rsclp)
+{
+ int i;
+
+ WARN_ON_ONCE(!rcu_segcblist_is_enabled(rsclp));
+ if (rcu_segcblist_restempty(rsclp, RCU_DONE_TAIL))
+ return;
+
+ /*
+ * Find all callbacks whose grace periods have completed (either
+ * normal or expedited) and put them into the RCU_DONE_TAIL segment.
+ * We check against the current global GP state, which includes
+ * proper memory barriers and handles special completion values.
+ */
+ for (i = RCU_WAIT_TAIL; i < RCU_NEXT_TAIL; i++) {
+ if (!poll_state_synchronize_rcu_full(&rsclp->gp_seq[i]))
+ break;
+ WRITE_ONCE(rsclp->tails[RCU_DONE_TAIL], rsclp->tails[i]);
+ rcu_segcblist_move_seglen(rsclp, i, RCU_DONE_TAIL);
+ }
+
+ /* If no callbacks moved, nothing more need be done. */
+ if (i == RCU_WAIT_TAIL)
+ return;
+
+ rcu_segcblist_advance_compact(rsclp, i);
+}
+
+/*
* "Accelerate" callbacks based on more-accurate grace-period information.
* The reason for this is that RCU does not synchronize the beginnings and
* ends of grace periods, and that callbacks are posted locally. This in
@@ -519,11 +537,11 @@ void rcu_segcblist_advance(struct rcu_segcblist *rsclp, unsigned long seq)
* them to complete at the end of the earlier grace period.
*
* This function operates on an rcu_segcblist structure, and also the
- * grace-period sequence number seq at which new callbacks would become
+ * grace-period state gsp at which new callbacks would become
* ready to invoke. Returns true if there are callbacks that won't be
- * ready to invoke until seq, false otherwise.
+ * ready to invoke until the grace period represented by gsp, false otherwise.
*/
-bool rcu_segcblist_accelerate(struct rcu_segcblist *rsclp, unsigned long seq)
+bool rcu_segcblist_accelerate(struct rcu_segcblist *rsclp, struct rcu_gp_seq *gsp)
{
int i, j;
@@ -533,20 +551,20 @@ bool rcu_segcblist_accelerate(struct rcu_segcblist *rsclp, unsigned long seq)
/*
* Find the segment preceding the oldest segment of callbacks
- * whose ->gp_seq[] completion is at or after that passed in via
- * "seq", skipping any empty segments. This oldest segment, along
+ * whose grace period completion is at or after that passed in via
+ * "gsp", skipping any empty segments. This oldest segment, along
* with any later segments, can be merged in with any newly arrived
- * callbacks in the RCU_NEXT_TAIL segment, and assigned "seq"
- * as their ->gp_seq[] grace-period completion sequence number.
+ * callbacks in the RCU_NEXT_TAIL segment, and assigned "gsp"
+ * as their grace-period completion state.
*/
for (i = RCU_NEXT_READY_TAIL; i > RCU_DONE_TAIL; i--)
if (!rcu_segcblist_segempty(rsclp, i) &&
- ULONG_CMP_LT(rsclp->gp_seq[i], seq))
+ ULONG_CMP_LT(rsclp->gp_seq[i].norm, gsp->norm))
break;
/*
* If all the segments contain callbacks that correspond to
- * earlier grace-period sequence numbers than "seq", leave.
+ * earlier grace-period sequence numbers than "gsp", leave.
* Assuming that the rcu_segcblist structure has enough
* segments in its arrays, this can only happen if some of
* the non-done segments contain callbacks that really are
@@ -554,15 +572,15 @@ bool rcu_segcblist_accelerate(struct rcu_segcblist *rsclp, unsigned long seq)
* out by the next call to rcu_segcblist_advance().
*
* Also advance to the oldest segment of callbacks whose
- * ->gp_seq[] completion is at or after that passed in via "seq",
+ * ->gp_seq[] completion is at or after that passed in via "gsp",
* skipping any empty segments.
*
* Note that segment "i" (and any lower-numbered segments
* containing older callbacks) will be unaffected, and their
- * grace-period numbers remain unchanged. For example, if i ==
+ * grace-period states remain unchanged. For example, if i ==
* WAIT_TAIL, then neither WAIT_TAIL nor DONE_TAIL will be touched.
* Instead, the CBs in NEXT_TAIL will be merged with those in
- * NEXT_READY_TAIL and the grace-period number of NEXT_READY_TAIL
+ * NEXT_READY_TAIL and the grace-period state of NEXT_READY_TAIL
* would be updated. NEXT_TAIL would then be empty.
*/
if (rcu_segcblist_restempty(rsclp, i) || ++i >= RCU_NEXT_TAIL)
@@ -574,14 +592,14 @@ bool rcu_segcblist_accelerate(struct rcu_segcblist *rsclp, unsigned long seq)
/*
* Merge all later callbacks, including newly arrived callbacks,
- * into the segment located by the for-loop above. Assign "seq"
- * as the ->gp_seq[] value in order to correctly handle the case
+ * into the segment located by the for-loop above. Assign "gsp"
+ * as the grace-period state in order to correctly handle the case
* where there were no pending callbacks in the rcu_segcblist
* structure other than in the RCU_NEXT_TAIL segment.
*/
for (; i < RCU_NEXT_TAIL; i++) {
WRITE_ONCE(rsclp->tails[i], rsclp->tails[RCU_NEXT_TAIL]);
- rsclp->gp_seq[i] = seq;
+ rsclp->gp_seq[i] = *gsp;
}
return true;
}
@@ -620,3 +638,42 @@ void rcu_segcblist_merge(struct rcu_segcblist *dst_rsclp,
rcu_segcblist_init(src_rsclp);
}
+
+void srcu_segcblist_advance(struct rcu_segcblist *rsclp, unsigned long seq)
+{
+ int i;
+
+ WARN_ON_ONCE(!rcu_segcblist_is_enabled(rsclp));
+ if (rcu_segcblist_restempty(rsclp, RCU_DONE_TAIL))
+ return;
+
+ /*
+ * Find all callbacks whose normal GP sequence numbers indicate
+ * that they are ready to invoke. For SRCU, we only check norm.
+ */
+ for (i = RCU_WAIT_TAIL; i < RCU_NEXT_TAIL; i++) {
+ if (ULONG_CMP_LT(seq, rsclp->gp_seq[i].norm))
+ break;
+ WRITE_ONCE(rsclp->tails[RCU_DONE_TAIL], rsclp->tails[i]);
+ rcu_segcblist_move_seglen(rsclp, i, RCU_DONE_TAIL);
+ }
+
+ /* If no callbacks moved, nothing more need be done. */
+ if (i == RCU_WAIT_TAIL)
+ return;
+
+ rcu_segcblist_advance_compact(rsclp, i);
+}
+
+/*
+ * SRCU wrapper for rcu_segcblist_accelerate() - converts SRCU's unsigned
+ * long GP sequence to rcu_gp_seq format with exp set to
+ * RCU_GET_STATE_NOT_TRACKED (since SRCU does not use expedited GPs)
+ * and calls the core rcu_segcblist_accelerate().
+ */
+bool srcu_segcblist_accelerate(struct rcu_segcblist *rsclp, unsigned long seq)
+{
+ struct rcu_gp_seq gs = { .norm = seq, .exp = RCU_GET_STATE_NOT_TRACKED };
+
+ return rcu_segcblist_accelerate(rsclp, &gs);
+}
diff --git a/kernel/rcu/rcu_segcblist.h b/kernel/rcu/rcu_segcblist.h
index fadc08ad4b7b..431c4466b889 100644
--- a/kernel/rcu/rcu_segcblist.h
+++ b/kernel/rcu/rcu_segcblist.h
@@ -124,7 +124,7 @@ bool rcu_segcblist_ready_cbs(struct rcu_segcblist *rsclp);
bool rcu_segcblist_pend_cbs(struct rcu_segcblist *rsclp);
struct rcu_head *rcu_segcblist_first_cb(struct rcu_segcblist *rsclp);
struct rcu_head *rcu_segcblist_first_pend_cb(struct rcu_segcblist *rsclp);
-bool rcu_segcblist_nextgp(struct rcu_segcblist *rsclp, unsigned long *lp);
+bool rcu_segcblist_nextgp(struct rcu_segcblist *rsclp, struct rcu_gp_seq *gsp);
void rcu_segcblist_enqueue(struct rcu_segcblist *rsclp,
struct rcu_head *rhp);
bool rcu_segcblist_entrain(struct rcu_segcblist *rsclp,
@@ -139,7 +139,9 @@ void rcu_segcblist_insert_done_cbs(struct rcu_segcblist *rsclp,
struct rcu_cblist *rclp);
void rcu_segcblist_insert_pend_cbs(struct rcu_segcblist *rsclp,
struct rcu_cblist *rclp);
-void rcu_segcblist_advance(struct rcu_segcblist *rsclp, unsigned long seq);
-bool rcu_segcblist_accelerate(struct rcu_segcblist *rsclp, unsigned long seq);
+void rcu_segcblist_advance(struct rcu_segcblist *rsclp);
+bool rcu_segcblist_accelerate(struct rcu_segcblist *rsclp, struct rcu_gp_seq *gsp);
void rcu_segcblist_merge(struct rcu_segcblist *dst_rsclp,
struct rcu_segcblist *src_rsclp);
+void srcu_segcblist_advance(struct rcu_segcblist *rsclp, unsigned long seq);
+bool srcu_segcblist_accelerate(struct rcu_segcblist *rsclp, unsigned long seq);
diff --git a/kernel/rcu/rcuscale.c b/kernel/rcu/rcuscale.c
index ac0b1c6b7dae..1097ec15879c 100644
--- a/kernel/rcu/rcuscale.c
+++ b/kernel/rcu/rcuscale.c
@@ -91,6 +91,8 @@ torture_param(int, shutdown_secs, !IS_MODULE(CONFIG_RCU_SCALE_TEST) * 300,
torture_param(int, verbose, 1, "Enable verbose debugging printk()s");
torture_param(int, writer_holdoff, 0, "Holdoff (us) between GPs, zero to disable");
torture_param(int, writer_holdoff_jiffies, 0, "Holdoff (jiffies) between GPs, zero to disable");
+torture_param(int, nexp, 0, "Number of expedited GP threads to run concurrently");
+torture_param(int, exp_interval, 0, "Interval (us) between expedited GPs, zero to disable");
torture_param(int, kfree_rcu_test, 0, "Do we run a kfree_rcu() scale test?");
torture_param(int, kfree_mult, 1, "Multiple of kfree_obj size to allocate.");
torture_param(int, kfree_by_call_rcu, 0, "Use call_rcu() to emulate kfree_rcu()?");
@@ -115,8 +117,10 @@ struct writer_freelist {
static int nrealreaders;
static int nrealwriters;
+static int nrealexp;
static struct task_struct **writer_tasks;
static struct task_struct **reader_tasks;
+static struct task_struct **exp_tasks;
static u64 **writer_durations;
static bool *writer_done;
@@ -463,6 +467,34 @@ rcu_scale_reader(void *arg)
}
/*
+ * RCU expedited GP kthread. Repeatedly invokes expedited grace periods
+ * to generate concurrent expedited GP load while the normal-GP writers
+ * are being measured. This allows measuring the benefit of callbacks
+ * that can piggyback on expedited grace periods.
+ */
+static int
+rcu_scale_exp(void *arg)
+{
+ long me = (long)arg;
+
+ VERBOSE_SCALEOUT_STRING("rcu_scale_exp task started");
+ set_cpus_allowed_ptr(current, cpumask_of(me % nr_cpu_ids));
+ set_user_nice(current, MIN_NICE);
+
+ if (holdoff)
+ schedule_timeout_idle(holdoff * HZ);
+
+ do {
+ if (exp_interval)
+ udelay(exp_interval);
+ cur_ops->exp_sync();
+ rcu_scale_wait_shutdown();
+ } while (!torture_must_stop());
+ torture_kthread_stopping("rcu_scale_exp");
+ return 0;
+}
+
+/*
* Allocate a writer_mblock structure for the specified rcu_scale_writer
* task.
*/
@@ -664,8 +696,10 @@ static void
rcu_scale_print_module_parms(struct rcu_scale_ops *cur_ops, const char *tag)
{
pr_alert("%s" SCALE_FLAG
- "--- %s: gp_async=%d gp_async_max=%d gp_exp=%d holdoff=%d minruntime=%d nreaders=%d nwriters=%d writer_holdoff=%d writer_holdoff_jiffies=%d verbose=%d shutdown_secs=%d\n",
- scale_type, tag, gp_async, gp_async_max, gp_exp, holdoff, minruntime, nrealreaders, nrealwriters, writer_holdoff, writer_holdoff_jiffies, verbose, shutdown_secs);
+ "--- %s: gp_async=%d gp_async_max=%d gp_exp=%d holdoff=%d minruntime=%d nreaders=%d nwriters=%d nexp=%d exp_interval=%d writer_holdoff=%d writer_holdoff_jiffies=%d verbose=%d shutdown_secs=%d\n",
+ scale_type, tag, gp_async, gp_async_max, gp_exp, holdoff,
+ minruntime, nrealreaders, nrealwriters, nrealexp, exp_interval,
+ writer_holdoff, writer_holdoff_jiffies, verbose, shutdown_secs);
}
/*
@@ -809,6 +843,13 @@ kfree_scale_cleanup(void)
if (torture_cleanup_begin())
return;
+ if (exp_tasks) {
+ for (i = 0; i < nrealexp; i++)
+ torture_stop_kthread(rcu_scale_exp, exp_tasks[i]);
+ kfree(exp_tasks);
+ exp_tasks = NULL;
+ }
+
if (kfree_reader_tasks) {
for (i = 0; i < kfree_nrealthreads; i++)
torture_stop_kthread(kfree_scale_thread,
@@ -903,6 +944,22 @@ kfree_scale_init(void)
goto unwind;
}
+ if (nrealexp > 0 && cur_ops->exp_sync) {
+ exp_tasks = kzalloc_objs(exp_tasks[0], nrealexp);
+ if (!exp_tasks) {
+ SCALEOUT_ERRSTRING("out of memory");
+ firsterr = -ENOMEM;
+ goto unwind;
+ }
+ for (i = 0; i < nrealexp; i++) {
+ firsterr = torture_create_kthread(rcu_scale_exp,
+ (void *)i,
+ exp_tasks[i]);
+ if (torture_init_error(firsterr))
+ goto unwind;
+ }
+ }
+
while (atomic_read(&n_kfree_scale_thread_started) < kfree_nrealthreads)
schedule_timeout_uninterruptible(1);
@@ -959,6 +1016,13 @@ rcu_scale_cleanup(void)
return;
}
+ if (exp_tasks) {
+ for (i = 0; i < nrealexp; i++)
+ torture_stop_kthread(rcu_scale_exp, exp_tasks[i]);
+ kfree(exp_tasks);
+ exp_tasks = NULL;
+ }
+
if (reader_tasks) {
for (i = 0; i < nrealreaders; i++)
torture_stop_kthread(rcu_scale_reader,
@@ -1076,6 +1140,7 @@ rcu_scale_init(void)
if (kthread_tp)
kthread_stime = kthread_tp->stime;
}
+ nrealexp = nexp;
if (kfree_rcu_test)
return kfree_scale_init();
@@ -1107,6 +1172,21 @@ rcu_scale_init(void)
}
while (atomic_read(&n_rcu_scale_reader_started) < nrealreaders)
schedule_timeout_uninterruptible(1);
+ if (nrealexp > 0 && cur_ops->exp_sync) {
+ exp_tasks = kzalloc_objs(exp_tasks[0], nrealexp);
+ if (!exp_tasks) {
+ SCALEOUT_ERRSTRING("out of memory");
+ firsterr = -ENOMEM;
+ goto unwind;
+ }
+ for (i = 0; i < nrealexp; i++) {
+ firsterr = torture_create_kthread(rcu_scale_exp,
+ (void *)i,
+ exp_tasks[i]);
+ if (torture_init_error(firsterr))
+ goto unwind;
+ }
+ }
writer_tasks = kzalloc_objs(writer_tasks[0], nrealwriters);
writer_durations = kcalloc(nrealwriters, sizeof(*writer_durations), GFP_KERNEL);
writer_n_durations = kzalloc_objs(*writer_n_durations, nrealwriters);
diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
index 882a158ada7b..794937e13e7c 100644
--- a/kernel/rcu/rcutorture.c
+++ b/kernel/rcu/rcutorture.c
@@ -80,6 +80,7 @@ MODULE_AUTHOR("Paul E. McKenney <paulmck@linux.ibm.com> and Josh Triplett <josh@
/* Must be power of two minus one. */
#define RCUTORTURE_RDR_MAX_SEGS (RCUTORTURE_RDR_MAX_LOOPS + 3)
+torture_param(bool, deboost_timeliness_check, 0, "Enable checks for immediate deboosting");
torture_param(int, extendables, RCUTORTURE_MAX_EXTEND,
"Extend readers by disabling bh (1), irqs (2), or preempt (4)");
torture_param(int, fqs_duration, 0, "Duration of fqs bursts (us), 0 to disable");
@@ -115,6 +116,7 @@ torture_param(int, n_barrier_cbs, 0, "# of callbacks/kthreads for barrier testin
torture_param(int, n_up_down, 32, "# of concurrent up/down hrtimer-based RCU readers");
torture_param(int, nfakewriters, 4, "Number of RCU fake writer threads");
torture_param(int, nreaders, -1, "Number of RCU reader threads");
+torture_param(bool, nwriters, 1, "Number of RCU writer threads (0 or 1)");
torture_param(int, object_debug, 0, "Enable debug-object double call_rcu() testing");
torture_param(int, onoff_holdoff, 0, "Time after boot before CPU hotplugs (s)");
torture_param(int, onoff_interval, 0, "Time between CPU hotplugs (jiffies), 0=disable");
@@ -138,6 +140,7 @@ torture_param(int, stall_cpu_irqsoff, 0, "Disable interrupts while stalling.");
torture_param(int, stall_cpu_block, 0, "Sleep while stalling.");
torture_param(int, stall_cpu_repeat, 0, "Number of additional stalls after the first one.");
torture_param(int, stall_gp_kthread, 0, "Grace-period kthread stall duration (s).");
+torture_param(bool, stall_only, 0, "Suppress all non-CPU-stall kthreads.");
torture_param(int, stat_interval, 60, "Number of seconds between stats printk()s");
torture_param(int, stutter, 5, "Number of seconds to run/halt test");
torture_param(int, test_boost, 1, "Test RCU prio boost: 0=no, 1=maybe, 2=yes.");
@@ -212,6 +215,7 @@ static long n_rcu_torture_boost_ktrerror;
static long n_rcu_torture_boost_failure;
static long n_rcu_torture_boosts;
static atomic_long_t n_rcu_torture_timers;
+static atomic_long_t n_rcu_torture_irqs;
static long n_barrier_attempts;
static long n_barrier_successes; /* did rcu_barrier test succeed? */
static unsigned long n_read_exits;
@@ -392,23 +396,23 @@ struct rcu_torture_ops {
void (*exp_current)(void);
unsigned long (*get_gp_state_exp)(void);
unsigned long (*start_gp_poll_exp)(void);
- void (*start_gp_poll_exp_full)(struct rcu_gp_oldstate *rgosp);
+ void (*start_gp_poll_exp_full)(struct rcu_gp_seq *gsp);
bool (*poll_gp_state_exp)(unsigned long oldstate);
void (*cond_sync_exp)(unsigned long oldstate);
- void (*cond_sync_exp_full)(struct rcu_gp_oldstate *rgosp);
+ void (*cond_sync_exp_full)(struct rcu_gp_seq *gsp);
unsigned long (*get_comp_state)(void);
- void (*get_comp_state_full)(struct rcu_gp_oldstate *rgosp);
+ void (*get_comp_state_full)(struct rcu_gp_seq *gsp);
bool (*same_gp_state)(unsigned long oldstate1, unsigned long oldstate2);
- bool (*same_gp_state_full)(struct rcu_gp_oldstate *rgosp1, struct rcu_gp_oldstate *rgosp2);
+ bool (*same_gp_state_full)(struct rcu_gp_seq *rgosp1, struct rcu_gp_seq *rgosp2);
unsigned long (*get_gp_state)(void);
- void (*get_gp_state_full)(struct rcu_gp_oldstate *rgosp);
+ void (*get_gp_state_full)(struct rcu_gp_seq *gsp);
unsigned long (*start_gp_poll)(void);
- void (*start_gp_poll_full)(struct rcu_gp_oldstate *rgosp);
+ void (*start_gp_poll_full)(struct rcu_gp_seq *gsp);
bool (*poll_gp_state)(unsigned long oldstate);
- bool (*poll_gp_state_full)(struct rcu_gp_oldstate *rgosp);
+ bool (*poll_gp_state_full)(struct rcu_gp_seq *gsp);
bool (*poll_need_2gp)(bool poll, bool poll_full);
void (*cond_sync)(unsigned long oldstate);
- void (*cond_sync_full)(struct rcu_gp_oldstate *rgosp);
+ void (*cond_sync_full)(struct rcu_gp_seq *gsp);
int poll_active;
int poll_active_full;
call_rcu_func_t call;
@@ -426,6 +430,7 @@ struct rcu_torture_ops {
void (*format_gp_seqs)(unsigned long long seqs, char *cp, size_t len);
void (*set_gpwrap_lag)(unsigned long lag);
int (*get_gpwrap_count)(int cpu);
+ bool (*is_task_rcu_boosted)(void);
long cbflood_max;
int irq_capable;
int can_boost;
@@ -464,12 +469,14 @@ rcu_read_delay(struct torture_random_state *rrsp, struct rt_read_seg *rtrsp)
unsigned long longdelay_ms = 300;
unsigned long long ts;
- /* We want a short delay sometimes to make a reader delay the grace
- * period, and we want a long delay occasionally to trigger
- * force_quiescent_state. */
+ // If there is a forward-progress test in flight, don't delay.
+ if (atomic_read(&rcu_fwd_cb_nodelay))
+ return;
- if (!atomic_read(&rcu_fwd_cb_nodelay) &&
- !(torture_random(rrsp) % (nrealreaders * 2000 * longdelay_ms))) {
+ // We want a short delay sometimes to make a reader delay the grace
+ // period, and we want a long delay occasionally to trigger
+ // force_quiescent_state.
+ if (!(torture_random(rrsp) % (nrealreaders * 2000 * longdelay_ms))) {
started = cur_ops->get_gp_seq();
ts = rcu_trace_clock_local();
if ((preempt_count() & HARDIRQ_MASK) || softirq_count())
@@ -635,6 +642,7 @@ static struct rcu_torture_ops rcu_ops = {
.format_gp_seqs = rcutorture_format_gp_seqs,
.set_gpwrap_lag = rcu_set_gpwrap_lag,
.get_gpwrap_count = rcu_get_gpwrap_count,
+ .is_task_rcu_boosted = rcu_is_task_rcu_boosted,
.irq_capable = 1,
.can_boost = IS_ENABLED(CONFIG_RCU_BOOST),
.extendables = RCUTORTURE_MAX_EXTEND,
@@ -760,11 +768,16 @@ srcu_read_delay(struct torture_random_state *rrsp, struct rt_read_seg *rtrsp)
const long uspertick = 1000000 / HZ;
const long longdelay = 10;
- /* We want there to be long-running readers, but not all the time. */
+ // If there is a forward-progress test in flight, don't delay.
+ if (atomic_read(&rcu_fwd_cb_nodelay))
+ return;
+
+ // We want there to be long-running readers, but not all the time.
+ // The !rcu_preempt_depth() is for RCU Tasks Trace.
delay = torture_random(rrsp) %
(nrealreaders * 2 * longdelay * uspertick);
- if (!delay && in_task()) {
+ if (!delay && !in_atomic() && !rcu_preempt_depth() && !irqs_disabled()) {
schedule_timeout_interruptible(longdelay);
rtrsp->rt_delay_jiffies = longdelay;
} else {
@@ -1213,15 +1226,24 @@ static struct rcu_torture_ops tasks_rude_ops = {
* Definitions for tracing RCU-tasks torture testing.
*/
+// Note that an RCU Tasks Trace GP must imply an RCU GP.
static int tasks_tracing_torture_read_lock(void)
{
- rcu_read_lock_trace();
- return 0;
+ int use_rcu = !(jiffies & 0xff);
+
+ if (use_rcu)
+ rcu_read_lock();
+ else
+ rcu_read_lock_trace();
+ return use_rcu;
}
-static void tasks_tracing_torture_read_unlock(int idx)
+static void tasks_tracing_torture_read_unlock(int use_rcu)
{
- rcu_read_unlock_trace();
+ if (use_rcu)
+ rcu_read_unlock();
+ else
+ rcu_read_unlock_trace();
}
static void rcu_tasks_tracing_torture_deferred_free(struct rcu_torture *p)
@@ -1236,7 +1258,8 @@ static struct rcu_torture_ops tasks_tracing_ops = {
.read_delay = srcu_read_delay, /* just reuse srcu's version. */
.readunlock = tasks_tracing_torture_read_unlock,
.readlock_held = rcu_read_lock_trace_held,
- .get_gp_seq = rcu_no_completed,
+ .get_gp_seq = rcu_tasks_trace_batches_completed,
+ .gp_diff = rcu_seq_diff,
.deferred_free = rcu_tasks_tracing_torture_deferred_free,
.sync = synchronize_rcu_tasks_trace,
.exp_sync = synchronize_rcu_tasks_trace,
@@ -1605,7 +1628,7 @@ static void rcu_torture_write_types(void)
static void do_rtws_sync(struct torture_random_state *trsp, void (*sync)(void))
{
unsigned long cookie;
- struct rcu_gp_oldstate cookie_full;
+ struct rcu_gp_seq cookie_full;
bool dopoll;
bool dopoll_full;
unsigned long r = torture_random(trsp);
@@ -1653,18 +1676,18 @@ rcu_torture_writer(void *arg)
bool booting_still = false;
bool can_expedite = !rcu_gp_is_expedited() && !rcu_gp_is_normal();
unsigned long cookie;
- struct rcu_gp_oldstate cookie_full;
+ struct rcu_gp_seq cookie_full;
int expediting = 0;
unsigned long gp_snap;
unsigned long gp_snap1;
- struct rcu_gp_oldstate gp_snap_full;
- struct rcu_gp_oldstate gp_snap1_full;
+ struct rcu_gp_seq gp_snap_full;
+ struct rcu_gp_seq gp_snap1_full;
int i;
int idx;
unsigned long j;
struct work_struct lazy_work;
int oldnice = task_nice(current);
- struct rcu_gp_oldstate *rgo = NULL;
+ struct rcu_gp_seq *rgo = NULL;
int rgo_size = 0;
struct rcu_torture *rp;
struct rcu_torture *old_rp;
@@ -1963,7 +1986,7 @@ static int
rcu_torture_fakewriter(void *arg)
{
unsigned long gp_snap;
- struct rcu_gp_oldstate gp_snap_full;
+ struct rcu_gp_seq gp_snap_full;
DEFINE_TORTURE_RANDOM(rand);
VERBOSE_TOROUT_STRING("rcu_torture_fakewriter task started");
@@ -2124,6 +2147,8 @@ static void rcu_torture_reader_do_mbchk(long myid, struct rcu_torture *rtp,
smp_store_release(&rtrcp_assigner->rtc_chkrdr, -1); // Assigner can again assign.
}
+static DEFINE_PER_CPU(bool, torture_in_scf_handler);
+
// Verify the specified RCUTORTURE_RDR* state.
#define ROEC_ARGS "%s %s: Current %#x To add %#x To remove %#x preempt_count() %#x\n", __func__, s, curstate, new, old, preempt_count()
static void rcutorture_one_extend_check(char *s, int curstate, int new, int old)
@@ -2133,7 +2158,7 @@ static void rcutorture_one_extend_check(char *s, int curstate, int new, int old)
if (!IS_ENABLED(CONFIG_RCU_TORTURE_TEST_CHK_RDR_STATE) || in_nmi())
return;
- WARN_ONCE(!(curstate & RCUTORTURE_RDR_IRQ) && irqs_disabled() && !in_hardirq(), ROEC_ARGS);
+ WARN_ONCE(!(curstate & RCUTORTURE_RDR_IRQ) && irqs_disabled() && !in_hardirq() && !this_cpu_read(torture_in_scf_handler), ROEC_ARGS);
WARN_ONCE((curstate & RCUTORTURE_RDR_IRQ) && !irqs_disabled(), ROEC_ARGS);
// If CONFIG_PREEMPT_COUNT=n, further checks are unreliable.
@@ -2150,7 +2175,7 @@ static void rcutorture_one_extend_check(char *s, int curstate, int new, int old)
// Interrupt handlers have all sorts of stuff disabled, so ignore
// unintended disabling.
- if (in_serving_softirq() || in_hardirq())
+ if (in_serving_softirq() || in_hardirq() || this_cpu_read(torture_in_scf_handler))
return;
WARN_ONCE(cur_ops->extendables &&
@@ -2343,11 +2368,18 @@ rcutorture_extend_mask(int oldmask, struct torture_random_state *trsp)
}
/*
+ * Don't mess with interrupt masking in interrupt handlers.
+ */
+ if (in_hardirq() || this_cpu_read(torture_in_scf_handler))
+ mask &= ~(preempts_irq | bhs);
+
+ /*
* Can't enable bh w/irq disabled.
*/
if (mask & RCUTORTURE_RDR_IRQ)
mask |= oldmask & bhs;
+
/*
* Ideally these sequences would be detected in debug builds
* (regardless of RT), but until then don't stop testing
@@ -2392,7 +2424,7 @@ rcutorture_loop_extend(int *readstate, struct torture_random_state *trsp, struct
struct rcu_torture_one_read_state {
bool checkpolling;
unsigned long cookie;
- struct rcu_gp_oldstate cookie_full;
+ struct rcu_gp_seq cookie_full;
unsigned long started;
struct rcu_torture *p;
int readstate;
@@ -2401,6 +2433,80 @@ struct rcu_torture_one_read_state {
unsigned long long ts;
};
+static void rcu_torture_dump_read_segs(struct rt_read_seg *rrsp, int nsegs)
+{
+ bool firsttime;
+ int i;
+ int j;
+
+ firsttime = 1;
+ for (i = 0; i < nsegs; i++) {
+ if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_GP))
+ pr_alert("\t%lluus ", div64_u64(rrsp[i].rt_ts, 1000ULL));
+ else
+ pr_alert("\t");
+ pr_cont("%d: %#4x", i, rrsp[i].rt_readstate);
+ if (rrsp[i].rt_delay_jiffies != 0) {
+ pr_cont("%s%ldjiffies", firsttime ? "" : "+",
+ rrsp[i].rt_delay_jiffies);
+ firsttime = 0;
+ }
+ if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_CPU)) {
+ pr_cont(" CPU %2d", rrsp[i].rt_cpu);
+ if (rrsp[i].rt_cpu != rrsp[i].rt_end_cpu)
+ pr_cont("->%-2d", rrsp[i].rt_end_cpu);
+ else
+ pr_cont(" ...");
+ }
+ if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_GP) &&
+ cur_ops->gather_gp_seqs && cur_ops->format_gp_seqs) {
+ char buf1[20+1];
+ char buf2[20+1];
+ char sepchar = '-';
+
+ cur_ops->format_gp_seqs(rrsp[i].rt_gp_seq, buf1, ARRAY_SIZE(buf1));
+ cur_ops->format_gp_seqs(rrsp[i].rt_gp_seq_end, buf2, ARRAY_SIZE(buf2));
+ if (rrsp[i].rt_gp_seq == rrsp[i].rt_gp_seq_end) {
+ if (buf2[0]) {
+ for (j = 0; buf2[j]; j++)
+ buf2[j] = '.';
+ if (j)
+ buf2[j - 1] = ' ';
+ }
+ sepchar = ' ';
+ }
+ pr_cont(" %s%c%s", buf1, sepchar, buf2);
+ }
+ if (rrsp[i].rt_delay_ms != 0) {
+ pr_cont(" %s%ldms", firsttime ? "" : "+", rrsp[i].rt_delay_ms);
+ firsttime = 0;
+ }
+ if (rrsp[i].rt_delay_us != 0) {
+ pr_cont(" %s%ldus", firsttime ? "" : "+", rrsp[i].rt_delay_us);
+ firsttime = 0;
+ }
+ pr_cont("%s", rrsp[i].rt_preempted ? " preempted" : "");
+ if (rrsp[i].rt_readstate & RCUTORTURE_RDR_BH)
+ pr_cont(" BH");
+ if (rrsp[i].rt_readstate & RCUTORTURE_RDR_IRQ)
+ pr_cont(" IRQ");
+ if (rrsp[i].rt_readstate & RCUTORTURE_RDR_PREEMPT)
+ pr_cont(" PREEMPT");
+ if (rrsp[i].rt_readstate & RCUTORTURE_RDR_RBH)
+ pr_cont(" RBH");
+ if (rrsp[i].rt_readstate & RCUTORTURE_RDR_SCHED)
+ pr_cont(" SCHED");
+ if (rrsp[i].rt_readstate & RCUTORTURE_RDR_RCU_1)
+ pr_cont(" RCU_1");
+ if (rrsp[i].rt_readstate & RCUTORTURE_RDR_RCU_2)
+ pr_cont(" RCU_2");
+ pr_cont("\n");
+
+ }
+ if (rt_read_preempted)
+ pr_alert("\tReader was preempted.\n");
+}
+
static void init_rcu_torture_one_read_state(struct rcu_torture_one_read_state *rtorsp,
struct torture_random_state *trsp)
{
@@ -2465,13 +2571,13 @@ static void rcu_torture_one_read_end(struct rcu_torture_one_read_state *rtorsp,
rtorsp->ts, rtorsp->started, completed);
rcu_ftrace_dump(DUMP_ALL);
}
- __this_cpu_inc(rcu_torture_count[pipe_count]);
+ this_cpu_inc(rcu_torture_count[pipe_count]);
completed = rcutorture_seq_diff(completed, rtorsp->started);
if (completed > RCU_TORTURE_PIPE_LEN) {
/* Should not happen, but... */
completed = RCU_TORTURE_PIPE_LEN;
}
- __this_cpu_inc(rcu_torture_batch[completed]);
+ this_cpu_inc(rcu_torture_batch[completed]);
preempt_enable();
if (rtorsp->checkpolling) {
if (cur_ops->get_gp_state && cur_ops->poll_gp_state)
@@ -2514,7 +2620,9 @@ static void rcu_torture_one_read_end(struct rcu_torture_one_read_state *rtorsp,
*/
static bool rcu_torture_one_read(struct torture_random_state *trsp, long myid)
{
+ static int firsttime = 1;
int newstate;
+ unsigned int nsegs;
struct rcu_torture_one_read_state rtors;
WARN_ON_ONCE(!rcu_is_watching());
@@ -2526,6 +2634,26 @@ static bool rcu_torture_one_read(struct torture_random_state *trsp, long myid)
return false;
rtors.rtrsp = rcutorture_loop_extend(&rtors.readstate, trsp, rtors.rtrsp);
rcu_torture_one_read_end(&rtors, trsp);
+
+ // This splat will happen on systems built with CONFIG_IRQ_WORK=n
+ // and on systems where arch_irq_work_has_interrupt() returns false.
+ // It might also happen on systems using a short-duration clock
+ // interrupt instead of a self-IPI (powerpc, s390) or that use
+ // neither a self-IPI nor a short-duration clock interrupts
+ // (all architectures using the generic implementation
+ // of arch_irq_work_raise()). On such systems, RCU cannot
+ // guarantee to immediately deboost RCU readers when the outermost
+ // rcu_read_unlock() does not end the full segmented RCU read-side
+ // critical section.
+ if (cur_ops->is_task_rcu_boosted && cur_ops->is_task_rcu_boosted() &&
+ !in_serving_softirq() && !in_hardirq() && !in_nmi() &&
+ READ_ONCE(firsttime) && xchg(&firsttime, 0)) {
+ WARN_ON_ONCE(deboost_timeliness_check);
+ nsegs = rtors.rtrsp - rtors.rtseg;
+ nsegs = clamp_val(nsegs, 0, RCUTORTURE_RDR_MAX_SEGS);
+ pr_alert("Slow-deboost rcutorture reader segments:\n");
+ rcu_torture_dump_read_segs(rtors.rtseg, nsegs);
+ }
return true;
}
@@ -2545,7 +2673,7 @@ static void rcu_torture_timer(struct timer_list *unused)
atomic_long_inc(&n_rcu_torture_timers);
(void)rcu_torture_one_read(this_cpu_ptr(&rcu_torture_timer_rand), -1);
- /* Test call_rcu() invocation from interrupt handler. */
+ /* Test call_rcu() invocation from softirq handler. */
if (cur_ops->call) {
struct rcu_head *rhp = kmalloc_obj(*rhp, GFP_NOWAIT);
@@ -2554,6 +2682,41 @@ static void rcu_torture_timer(struct timer_list *unused)
}
}
+static DEFINE_TORTURE_RANDOM_PERCPU(rcu_torture_irq_rand);
+
+/*
+ * RCU torture reader from timer handler. Dereferences rcu_torture_current,
+ * incrementing the corresponding element of the pipeline array. The
+ * counter in the element should never be greater than 1, otherwise, the
+ * RCU implementation is broken.
+ *
+ * Note that on some systems, "interrupts" from idle are direct calls
+ * rather than interrupts. The torture_in_scf_handler per-CPU variable
+ * accounts for this case.
+ */
+static void rcu_torture_irq(void *unused)
+{
+ WARN_ON_ONCE(in_nmi());
+ lockdep_assert_irqs_disabled();
+ atomic_long_inc(&n_rcu_torture_irqs);
+ this_cpu_write(torture_in_scf_handler, true);
+ (void)rcu_torture_one_read(this_cpu_ptr(&rcu_torture_irq_rand), -1);
+ this_cpu_write(torture_in_scf_handler, false);
+
+ // Test call_rcu() invocation from interrupt handler. Interrupts
+ // will always be disabled here, even in CONFIG_PREEMPT_RT=y kernels.
+ // The "right" thing to do would be to create a special-purpose
+ // lockless or raw-spinlock-protected allocator, but in the meantime,
+ // skip testing call_rcu() from interrupt handlers in kernels built
+ // with either CONFIG_PREEMPT_RT=y or CONFIG_PROVE_LOCKING=y.
+ if (cur_ops->call && !IS_ENABLED(CONFIG_PROVE_LOCKING) && !IS_ENABLED(CONFIG_PREEMPT_RT)) {
+ struct rcu_head *rhp = kmalloc_obj(*rhp, GFP_NOWAIT);
+
+ if (rhp)
+ cur_ops->call(rhp, rcu_torture_timer_cb);
+ }
+}
+
/*
* RCU torture reader kthread. Repeatedly dereferences rcu_torture_current,
* incrementing the corresponding element of the pipeline array. The
@@ -2563,6 +2726,7 @@ static void rcu_torture_timer(struct timer_list *unused)
static int
rcu_torture_reader(void *arg)
{
+ unsigned long lastscf = jiffies;
unsigned long lastsleep = jiffies;
long myid = (long)arg;
int mynumonline = myid;
@@ -2576,8 +2740,25 @@ rcu_torture_reader(void *arg)
tick_dep_set_task(current, TICK_DEP_BIT_RCU); // CPU bound, so need tick.
do {
if (irqreader && cur_ops->irq_capable) {
- if (!timer_pending(&t))
+ if (!timer_pending(&t)) {
+ int cpu;
+
mod_timer(&t, jiffies + 1);
+ preempt_disable();
+ cpu = torture_random(&rand) % nr_cpu_ids;
+ if (!cpu_online(cpu)) {
+ cpu = cpumask_next(cpu, cpu_online_mask);
+ if (cpu >= nr_cpu_ids)
+ cpu = cpumask_next(-1, cpu_online_mask);
+ }
+ // An smp_call_function_single() to self is not an interrupt!
+ if (cpu != smp_processor_id() &&
+ time_after(jiffies, lastscf + HZ * nrealreaders / 50)) {
+ smp_call_function_single(cpu, rcu_torture_irq, NULL, 0);
+ lastscf = jiffies;
+ }
+ preempt_enable();
+ }
}
if (!rcu_torture_one_read(&rand, myid) && !torture_must_stop())
schedule_timeout_interruptible(HZ);
@@ -2853,10 +3034,11 @@ rcu_torture_stats_print(void)
atomic_read(&n_rcu_torture_mbchk_fail), atomic_read(&n_rcu_torture_mbchk_tries),
n_rcu_torture_barrier_error,
n_rcu_torture_boost_ktrerror);
- pr_cont("rtbf: %ld rtb: %ld nt: %ld ",
+ pr_cont("rtbf: %ld rtb: %ld nt: %ld ni: %ld ",
n_rcu_torture_boost_failure,
n_rcu_torture_boosts,
- atomic_long_read(&n_rcu_torture_timers));
+ atomic_long_read(&n_rcu_torture_timers),
+ atomic_long_read(&n_rcu_torture_irqs));
if (updownreaders)
pr_cont("ndowns: %lu nups: %lu nhrt: %lu nmigrates: %lu ", ndowns, nups, nunexpired, nmigrates);
torture_onoff_stats();
@@ -2913,10 +3095,10 @@ rcu_torture_stats_print(void)
if (cur_ops->get_gp_data)
cur_ops->get_gp_data(&flags, &gp_seq);
wtp = READ_ONCE(writer_task);
- pr_alert("??? Writer stall state %s(%d) g%lu f%#x ->state %#x cpu %d\n",
+ pr_alert("??? Writer stall state %s(%d) g%lu f%#x ->state %c cpu %d\n",
rcu_torture_writer_state_getname(),
rcu_torture_writer_state, gp_seq, flags,
- wtp == NULL ? ~0U : wtp->__state,
+ wtp == NULL ? '?' : task_state_to_char(wtp),
wtp == NULL ? -1 : (int)task_cpu(wtp));
if (!splatted && wtp) {
sched_show_task(wtp);
@@ -3000,7 +3182,7 @@ static void
rcu_torture_print_module_parms(struct rcu_torture_ops *cur_ops, const char *tag)
{
pr_alert("%s" TORTURE_FLAG
- "--- %s: nreaders=%d nfakewriters=%d "
+ "--- %s: nreaders=%d nwriters=%d nfakewriters=%d "
"stat_interval=%d verbose=%d test_no_idle_hz=%d "
"shuffle_interval=%d stutter=%d irqreader=%d "
"fqs_duration=%d fqs_holdoff=%d fqs_stutter=%d "
@@ -3015,7 +3197,7 @@ rcu_torture_print_module_parms(struct rcu_torture_ops *cur_ops, const char *tag)
"nocbs_nthreads=%d nocbs_toggle=%d "
"test_nmis=%d "
"preempt_duration=%d preempt_interval=%d n_up_down=%d\n",
- torture_type, tag, nrealreaders, nrealfakewriters,
+ torture_type, tag, nrealreaders, nwriters, nrealfakewriters,
stat_interval, verbose, test_no_idle_hz, shuffle_interval,
stutter, irqreader, fqs_duration, fqs_holdoff, fqs_stutter,
test_boost, cur_ops->can_boost,
@@ -3447,13 +3629,17 @@ static void rcu_torture_fwd_prog_cr(struct rcu_fwd *rfp)
unsigned long stopat;
unsigned long stoppedat;
- pr_alert("%s: Starting forward-progress test %d\n", __func__, rfp->rcu_fwd_id);
- if (READ_ONCE(rcu_fwd_emergency_stop))
+ if (READ_ONCE(rcu_fwd_emergency_stop)) {
+ pr_alert("%s: Emergency stop, so no forward-progress test %d\n", __func__, rfp->rcu_fwd_id);
return; /* Get out of the way quickly, no GP wait! */
- if (!cur_ops->call)
+ }
+ if (!cur_ops->call) {
+ pr_alert("%s: No ->call(), so no forward-progress test %d\n", __func__, rfp->rcu_fwd_id);
return; /* Can't do call_rcu() fwd prog without ->call. */
+ }
/* Loop continuously posting RCU callbacks. */
+ pr_alert("%s: Starting forward-progress test %d\n", __func__, rfp->rcu_fwd_id);
atomic_inc(&rcu_fwd_cb_nodelay);
cur_ops->sync(); /* Later readers see above write. */
WRITE_ONCE(rfp->rcu_fwd_startat, jiffies);
@@ -4020,9 +4206,7 @@ static int rcu_torture_preempt(void *unused)
// Wait for preempt_interval ms with up to 100us fuzz.
torture_hrtimeout_ms(preempt_interval, 100, &rand);
// Select online CPU.
- cpu = cpumask_next(cpu, cpu_online_mask);
- if (cpu >= nr_cpu_ids)
- cpu = cpumask_next(-1, cpu_online_mask);
+ cpu = cpumask_next_wrap(cpu, cpu_online_mask);
WARN_ON_ONCE(cpu >= nr_cpu_ids);
// Move to that CPU, if can't do so, retry later.
if (torture_sched_setaffinity(current->pid, cpumask_of(cpu), false))
@@ -4094,11 +4278,9 @@ static void rcu_gpwrap_lag_cleanup(void)
static void
rcu_torture_cleanup(void)
{
- int firsttime;
int flags = 0;
unsigned long gp_seq = 0;
int i;
- int j;
if (torture_cleanup_begin()) {
if (cur_ops->cb_barrier != NULL) {
@@ -4183,76 +4365,8 @@ rcu_torture_cleanup(void)
pr_alert("Failure/close-call rcutorture reader segments:\n");
if (rt_read_nsegs == 0)
pr_alert("\t: No segments recorded!!!\n");
- firsttime = 1;
- for (i = 0; i < rt_read_nsegs; i++) {
- if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_GP))
- pr_alert("\t%lluus ", div64_u64(err_segs[i].rt_ts, 1000ULL));
- else
- pr_alert("\t");
- pr_cont("%d: %#4x", i, err_segs[i].rt_readstate);
- if (err_segs[i].rt_delay_jiffies != 0) {
- pr_cont("%s%ldjiffies", firsttime ? "" : "+",
- err_segs[i].rt_delay_jiffies);
- firsttime = 0;
- }
- if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_CPU)) {
- pr_cont(" CPU %2d", err_segs[i].rt_cpu);
- if (err_segs[i].rt_cpu != err_segs[i].rt_end_cpu)
- pr_cont("->%-2d", err_segs[i].rt_end_cpu);
- else
- pr_cont(" ...");
- }
- if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_GP) &&
- cur_ops->gather_gp_seqs && cur_ops->format_gp_seqs) {
- char buf1[20+1];
- char buf2[20+1];
- char sepchar = '-';
-
- cur_ops->format_gp_seqs(err_segs[i].rt_gp_seq,
- buf1, ARRAY_SIZE(buf1));
- cur_ops->format_gp_seqs(err_segs[i].rt_gp_seq_end,
- buf2, ARRAY_SIZE(buf2));
- if (err_segs[i].rt_gp_seq == err_segs[i].rt_gp_seq_end) {
- if (buf2[0]) {
- for (j = 0; buf2[j]; j++)
- buf2[j] = '.';
- if (j)
- buf2[j - 1] = ' ';
- }
- sepchar = ' ';
- }
- pr_cont(" %s%c%s", buf1, sepchar, buf2);
- }
- if (err_segs[i].rt_delay_ms != 0) {
- pr_cont(" %s%ldms", firsttime ? "" : "+",
- err_segs[i].rt_delay_ms);
- firsttime = 0;
- }
- if (err_segs[i].rt_delay_us != 0) {
- pr_cont(" %s%ldus", firsttime ? "" : "+",
- err_segs[i].rt_delay_us);
- firsttime = 0;
- }
- pr_cont("%s", err_segs[i].rt_preempted ? " preempted" : "");
- if (err_segs[i].rt_readstate & RCUTORTURE_RDR_BH)
- pr_cont(" BH");
- if (err_segs[i].rt_readstate & RCUTORTURE_RDR_IRQ)
- pr_cont(" IRQ");
- if (err_segs[i].rt_readstate & RCUTORTURE_RDR_PREEMPT)
- pr_cont(" PREEMPT");
- if (err_segs[i].rt_readstate & RCUTORTURE_RDR_RBH)
- pr_cont(" RBH");
- if (err_segs[i].rt_readstate & RCUTORTURE_RDR_SCHED)
- pr_cont(" SCHED");
- if (err_segs[i].rt_readstate & RCUTORTURE_RDR_RCU_1)
- pr_cont(" RCU_1");
- if (err_segs[i].rt_readstate & RCUTORTURE_RDR_RCU_2)
- pr_cont(" RCU_2");
- pr_cont("\n");
-
- }
- if (rt_read_preempted)
- pr_alert("\tReader was preempted.\n");
+ else
+ rcu_torture_dump_read_segs(err_segs, rt_read_nsegs);
}
if (atomic_read(&n_rcu_torture_error) || n_rcu_torture_barrier_error)
rcu_torture_print_module_parms(cur_ops, "End of test: FAILURE");
@@ -4557,6 +4671,23 @@ rcu_torture_init(void)
cur_ops = NULL;
goto unwind;
}
+ if (stall_only) {
+ pr_alert("rcu-torture: stall_only specified, suppressing all else.\n");
+ fqs_stutter = 0;
+ fwd_progress = 0;
+ n_barrier_cbs = 0;
+ nfakewriters = 0;
+ nocbs_nthreads = 0;
+ nreaders = 0;
+ n_up_down = 0;
+ nwriters = 0;
+ onoff_interval = 0;
+ preempt_duration = 0;
+ read_exit_burst = 0;
+ shuffle_interval = 0;
+ stutter = 0;
+ test_boost = 0;
+ }
if (cur_ops->fqs == NULL && fqs_duration != 0) {
pr_alert("rcu-torture: ->fqs NULL and non-zero fqs_duration, fqs disabled.\n");
fqs_duration = 0;
@@ -4663,10 +4794,11 @@ rcu_torture_init(void)
goto unwind;
}
- firsterr = torture_create_kthread(rcu_torture_writer, NULL,
- writer_task);
- if (torture_init_error(firsterr))
- goto unwind;
+ if (nwriters) {
+ firsterr = torture_create_kthread(rcu_torture_writer, NULL, writer_task);
+ if (torture_init_error(firsterr))
+ goto unwind;
+ }
firsterr = rcu_torture_updown_init();
if (torture_init_error(firsterr))
diff --git a/kernel/rcu/srcutiny.c b/kernel/rcu/srcutiny.c
index a2e2d516e51b..558ba8d316db 100644
--- a/kernel/rcu/srcutiny.c
+++ b/kernel/rcu/srcutiny.c
@@ -48,31 +48,31 @@ static int init_srcu_struct_fields(struct srcu_struct *ssp)
#ifdef CONFIG_DEBUG_LOCK_ALLOC
-int __init_srcu_struct(struct srcu_struct *ssp, const char *name,
- struct lock_class_key *key)
+int init_srcu_struct_lockdep(struct srcu_struct *ssp, const char *name,
+ struct lock_class_key *key)
{
/* Don't re-initialize a lock while it is held. */
debug_check_no_locks_freed((void *)ssp, sizeof(*ssp));
lockdep_init_map(&ssp->dep_map, name, key, 0);
return init_srcu_struct_fields(ssp);
}
-EXPORT_SYMBOL_GPL(__init_srcu_struct);
+EXPORT_SYMBOL_GPL(init_srcu_struct_lockdep);
#else /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */
/*
- * init_srcu_struct - initialize a sleep-RCU structure
+ * init_srcu_struct_generic - initialize a sleep-RCU structure
* @ssp: structure to initialize.
*
* Must invoke this on a given srcu_struct before passing that srcu_struct
* to any other function. Each srcu_struct represents a separate domain
* of SRCU protection.
*/
-int init_srcu_struct(struct srcu_struct *ssp)
+int init_srcu_struct_generic(struct srcu_struct *ssp)
{
return init_srcu_struct_fields(ssp);
}
-EXPORT_SYMBOL_GPL(init_srcu_struct);
+EXPORT_SYMBOL_GPL(init_srcu_struct_generic);
#endif /* #else #ifdef CONFIG_DEBUG_LOCK_ALLOC */
@@ -85,7 +85,7 @@ EXPORT_SYMBOL_GPL(init_srcu_struct);
*/
void cleanup_srcu_struct(struct srcu_struct *ssp)
{
- WARN_ON(ssp->srcu_lock_nesting[0] || ssp->srcu_lock_nesting[1]);
+ WARN_ON(srcu_readers_active(ssp));
irq_work_sync(&ssp->srcu_irq_work);
flush_work(&ssp->srcu_work);
WARN_ON(ssp->srcu_gp_running);
diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c
index 7c2f7cc131f7..ed204b3f4b84 100644
--- a/kernel/rcu/srcutree.c
+++ b/kernel/rcu/srcutree.c
@@ -266,12 +266,13 @@ __init_srcu_struct_common(struct srcu_struct *ssp, const char *name, struct lock
return init_srcu_struct_fields(ssp, false);
}
-int __init_srcu_struct(struct srcu_struct *ssp, const char *name, struct lock_class_key *key)
+int init_srcu_struct_lockdep(struct srcu_struct *ssp, const char *name,
+ struct lock_class_key *key)
{
ssp->srcu_reader_flavor = 0;
return __init_srcu_struct_common(ssp, name, key);
}
-EXPORT_SYMBOL_GPL(__init_srcu_struct);
+EXPORT_SYMBOL_GPL(init_srcu_struct_lockdep);
int __init_srcu_struct_fast(struct srcu_struct *ssp, const char *name, struct lock_class_key *key)
{
@@ -291,7 +292,7 @@ EXPORT_SYMBOL_GPL(__init_srcu_struct_fast_updown);
#else /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */
/**
- * init_srcu_struct - initialize a sleep-RCU structure
+ * init_srcu_struct_generic - initialize a sleep-RCU structure
* @ssp: structure to initialize.
*
* Use this in place of DEFINE_SRCU() and DEFINE_STATIC_SRCU()
@@ -301,12 +302,12 @@ EXPORT_SYMBOL_GPL(__init_srcu_struct_fast_updown);
* to any other function. Each srcu_struct represents a separate domain
* of SRCU protection.
*/
-int init_srcu_struct(struct srcu_struct *ssp)
+int init_srcu_struct_generic(struct srcu_struct *ssp)
{
ssp->srcu_reader_flavor = 0;
return init_srcu_struct_fields(ssp, false);
}
-EXPORT_SYMBOL_GPL(init_srcu_struct);
+EXPORT_SYMBOL_GPL(init_srcu_struct_generic);
/**
* init_srcu_struct_fast - initialize a fast-reader sleep-RCU structure
@@ -598,31 +599,6 @@ static bool srcu_readers_active_idx_check(struct srcu_struct *ssp, int idx)
return srcu_readers_lock_idx(ssp, idx, did_gp, unlocks);
}
-/**
- * srcu_readers_active - returns true if there are readers. and false
- * otherwise
- * @ssp: which srcu_struct to count active readers (holding srcu_read_lock).
- *
- * Note that this is not an atomic primitive, and can therefore suffer
- * severe errors when invoked on an active srcu_struct. That said, it
- * can be useful as an error check at cleanup time.
- */
-static bool srcu_readers_active(struct srcu_struct *ssp)
-{
- int cpu;
- unsigned long sum = 0;
-
- for_each_possible_cpu(cpu) {
- struct srcu_data *sdp = per_cpu_ptr(ssp->sda, cpu);
-
- sum += atomic_long_read(&sdp->srcu_ctrs[0].srcu_locks);
- sum += atomic_long_read(&sdp->srcu_ctrs[1].srcu_locks);
- sum -= atomic_long_read(&sdp->srcu_ctrs[0].srcu_unlocks);
- sum -= atomic_long_read(&sdp->srcu_ctrs[1].srcu_unlocks);
- }
- return sum;
-}
-
/*
* We use an adaptive strategy for synchronize_srcu() and especially for
* synchronize_srcu_expedited(). We spin for a fixed time period
@@ -725,7 +701,12 @@ void cleanup_srcu_struct(struct srcu_struct *ssp)
for_each_possible_cpu(cpu) {
struct srcu_data *sdp = per_cpu_ptr(ssp->sda, cpu);
- timer_delete_sync(&sdp->delay_work);
+ // Call srcu_barrier() before this cleanup_srcu_struct()
+ // to avoid triggering this WARN_ON().
+ if (WARN_ON(timer_delete_sync(&sdp->delay_work) &&
+ rcu_segcblist_n_cbs(&sdp->srcu_cblist)) &&
+ rcu_cpu_beenfullyonline(sdp->cpu))
+ queue_work_on(sdp->cpu, rcu_gp_wq, &sdp->work);
flush_work(&sdp->work);
if (WARN_ON(rcu_segcblist_n_cbs(&sdp->srcu_cblist)))
return; /* Forgot srcu_barrier(), so just leak it! */
@@ -1351,7 +1332,7 @@ static unsigned long srcu_gp_start_if_needed(struct srcu_struct *ssp,
* 2) The grace period for RCU_WAIT_TAIL is seen as started but not
* completed so rcu_seq_current() returns X + SRCU_STATE_SCAN1.
*
- * 3) This value is passed to rcu_segcblist_advance() which can't move
+ * 3) This value is passed to srcu_segcblist_advance() which can't move
* any segment forward and fails.
*
* 4) srcu_gp_start_if_needed() still proceeds with callback acceleration.
@@ -1360,15 +1341,15 @@ static unsigned long srcu_gp_start_if_needed(struct srcu_struct *ssp,
* RCU_NEXT_READY_TAIL segment as started (ie: X + 4 + SRCU_STATE_SCAN1)
* so it returns a snapshot of the next grace period, which is X + 12.
*
- * 5) The value of X + 12 is passed to rcu_segcblist_accelerate() but the
+ * 5) The value of X + 12 is passed to srcu_segcblist_accelerate() but the
* freshly enqueued callback in RCU_NEXT_TAIL can't move to
* RCU_NEXT_READY_TAIL which already has callbacks for a previous grace
* period (gp_num = X + 8). So acceleration fails.
*/
s = rcu_seq_snap(&ssp->srcu_sup->srcu_gp_seq);
if (rhp) {
- rcu_segcblist_advance(&sdp->srcu_cblist,
- rcu_seq_current(&ssp->srcu_sup->srcu_gp_seq));
+ srcu_segcblist_advance(&sdp->srcu_cblist,
+ rcu_seq_current(&ssp->srcu_sup->srcu_gp_seq));
/*
* Acceleration can never fail because the base current gp_seq
* used for acceleration is <= the value of gp_seq used for
@@ -1376,7 +1357,7 @@ static unsigned long srcu_gp_start_if_needed(struct srcu_struct *ssp,
* always be able to be emptied by the acceleration into the
* RCU_NEXT_READY_TAIL or RCU_WAIT_TAIL segments.
*/
- WARN_ON_ONCE(!rcu_segcblist_accelerate(&sdp->srcu_cblist, s));
+ WARN_ON_ONCE(!srcu_segcblist_accelerate(&sdp->srcu_cblist, s));
}
if (ULONG_CMP_LT(sdp->srcu_gp_seq_needed, s)) {
sdp->srcu_gp_seq_needed = s;
@@ -1891,8 +1872,8 @@ static void srcu_invoke_callbacks(struct work_struct *work)
rcu_cblist_init(&ready_cbs);
raw_spin_lock_irq_rcu_node(sdp);
WARN_ON_ONCE(!rcu_segcblist_segempty(&sdp->srcu_cblist, RCU_NEXT_TAIL));
- rcu_segcblist_advance(&sdp->srcu_cblist,
- rcu_seq_current(&ssp->srcu_sup->srcu_gp_seq));
+ srcu_segcblist_advance(&sdp->srcu_cblist,
+ rcu_seq_current(&ssp->srcu_sup->srcu_gp_seq));
/*
* Although this function is theoretically re-entrant, concurrent
* callbacks invocation is disallowed to avoid executing an SRCU barrier
diff --git a/kernel/rcu/tasks.h b/kernel/rcu/tasks.h
index f4da5fad70f5..627295396cd9 100644
--- a/kernel/rcu/tasks.h
+++ b/kernel/rcu/tasks.h
@@ -67,7 +67,6 @@ struct rcu_tasks_percpu {
* @gp_start: Most recent grace-period start in jiffies.
* @tasks_gp_seq: Number of grace periods completed since boot in upper bits.
* @n_ipis: Number of IPIs sent to encourage grace periods to end.
- * @n_ipis_fails: Number of IPI-send failures.
* @kthread_ptr: This flavor's grace-period/callback-invocation kthread.
* @lazy_jiffies: Number of jiffies to allow callbacks to be lazy.
* @pregp_func: This flavor's pre-grace-period function (optional).
@@ -102,7 +101,6 @@ struct rcu_tasks {
unsigned long gp_start;
unsigned long tasks_gp_seq;
unsigned long n_ipis;
- unsigned long n_ipis_fails;
struct task_struct *kthread_ptr;
unsigned long lazy_jiffies;
rcu_tasks_gp_func_t gp_func;
@@ -157,8 +155,8 @@ static struct rcu_tasks rt_name = \
#ifdef CONFIG_TASKS_RCU
/* Report delay of scan exiting tasklist in rcu_tasks_postscan(). */
-static void tasks_rcu_exit_srcu_stall(struct timer_list *unused);
-static DEFINE_TIMER(tasks_rcu_exit_srcu_stall_timer, tasks_rcu_exit_srcu_stall);
+static void tasks_rcu_exit_stall(struct timer_list *unused);
+static DEFINE_TIMER(tasks_rcu_exit_stall_timer, tasks_rcu_exit_stall);
#endif
/* Control stall timeouts. Disable with <= 0, otherwise jiffies till stall. */
@@ -397,7 +395,11 @@ static void call_rcu_tasks_generic(struct rcu_head *rhp, rcu_callback_t func,
raw_spin_unlock_irqrestore(&rtp->cbs_gbl_lock, flags);
}
rcu_read_unlock();
- /* We can't create the thread unless interrupts are enabled. */
+ // We can't create the kthread with interrupts disabled because a
+ // scheduler spinlock might be held, so kthread creation is deferred
+ // until core_initcall() time. Similarly, wakeups are deferred using
+ // irq_work in order to avoid potential scheduler-lock-deadlock
+ // lockdep splats.
if (needwake && READ_ONCE(rtp->kthread_ptr))
irq_work_queue(&rtpcp->rtp_irq_work);
}
@@ -481,8 +483,8 @@ static int rcu_tasks_need_gpcb(struct rcu_tasks *rtp)
if (cpu > 0)
ncbsnz += n;
}
- rcu_segcblist_advance(&rtpcp->cblist, rcu_seq_current(&rtp->tasks_gp_seq));
- (void)rcu_segcblist_accelerate(&rtpcp->cblist, rcu_seq_snap(&rtp->tasks_gp_seq));
+ srcu_segcblist_advance(&rtpcp->cblist, rcu_seq_current(&rtp->tasks_gp_seq));
+ (void)srcu_segcblist_accelerate(&rtpcp->cblist, rcu_seq_snap(&rtp->tasks_gp_seq));
if (rtpcp->urgent_gp > 0 && rcu_segcblist_pend_cbs(&rtpcp->cblist)) {
if (rtp->lazy_jiffies)
rtpcp->urgent_gp--;
@@ -565,7 +567,7 @@ static void rcu_tasks_invoke_cbs(struct rcu_tasks *rtp, struct rcu_tasks_percpu
if (rcu_segcblist_empty(&rtpcp->cblist))
return;
raw_spin_lock_irqsave_rcu_node(rtpcp, flags);
- rcu_segcblist_advance(&rtpcp->cblist, rcu_seq_current(&rtp->tasks_gp_seq));
+ srcu_segcblist_advance(&rtpcp->cblist, rcu_seq_current(&rtp->tasks_gp_seq));
rcu_segcblist_extract_done_cbs(&rtpcp->cblist, &rcl);
raw_spin_unlock_irqrestore_rcu_node(rtpcp, flags);
len = rcl.len;
@@ -578,7 +580,7 @@ static void rcu_tasks_invoke_cbs(struct rcu_tasks *rtp, struct rcu_tasks_percpu
}
raw_spin_lock_irqsave_rcu_node(rtpcp, flags);
rcu_segcblist_add_len(&rtpcp->cblist, -len);
- (void)rcu_segcblist_accelerate(&rtpcp->cblist, rcu_seq_snap(&rtp->tasks_gp_seq));
+ (void)srcu_segcblist_accelerate(&rtpcp->cblist, rcu_seq_snap(&rtp->tasks_gp_seq));
raw_spin_unlock_irqrestore_rcu_node(rtpcp, flags);
}
@@ -683,7 +685,6 @@ static void __init rcu_spawn_tasks_kthread_generic(struct rcu_tasks *rtp)
t = kthread_run(rcu_tasks_kthread, rtp, "%s_kthread", rtp->kname);
if (WARN_ONCE(IS_ERR(t), "%s: Could not start %s grace-period kthread, OOM is now expected behavior\n", __func__, rtp->name))
return;
- smp_mb(); /* Ensure others see full kthread. */
}
#ifndef CONFIG_TINY_RCU
@@ -722,6 +723,7 @@ static void show_rcu_tasks_generic_gp_kthread(struct rcu_tasks *rtp, char *s)
bool havecbs = false;
bool haveurgent = false;
bool haveurgentcbs = false;
+ bool havependtimer = false;
for_each_possible_cpu(cpu) {
struct rcu_tasks_percpu *rtpcp = per_cpu_ptr(rtp->rtpcpu, cpu);
@@ -732,19 +734,22 @@ static void show_rcu_tasks_generic_gp_kthread(struct rcu_tasks *rtp, char *s)
haveurgent = true;
if (!data_race(rcu_segcblist_empty(&rtpcp->cblist)) && data_race(rtpcp->urgent_gp))
haveurgentcbs = true;
- if (havecbs && haveurgent && haveurgentcbs)
+ if (data_race(timer_pending(&rtpcp->lazy_timer)))
+ havependtimer = true;
+ if (havecbs && haveurgent && haveurgentcbs && havependtimer)
break;
}
- pr_info("%s: %s(%d) since %lu g:%lu i:%lu/%lu %c%c%c%c l:%lu %s\n",
+ pr_info("%s: %s(%d) since %lu g:%lu i:%lu %c%c%c%c%c l:%lu %s\n",
rtp->kname,
tasks_gp_state_getname(rtp), data_race(rtp->gp_state),
jiffies - data_race(rtp->gp_jiffies),
data_race(rcu_seq_current(&rtp->tasks_gp_seq)),
- data_race(rtp->n_ipis_fails), data_race(rtp->n_ipis),
+ data_race(rtp->n_ipis),
".k"[!!data_race(rtp->kthread_ptr)],
".C"[havecbs],
".u"[haveurgent],
".U"[haveurgentcbs],
+ ".P"[havependtimer],
rtp->lazy_jiffies,
s);
}
@@ -1027,8 +1032,8 @@ static void rcu_tasks_postscan(struct list_head *hop)
int rtsi = READ_ONCE(rcu_task_stall_info);
if (!IS_ENABLED(CONFIG_TINY_RCU)) {
- tasks_rcu_exit_srcu_stall_timer.expires = jiffies + rtsi;
- add_timer(&tasks_rcu_exit_srcu_stall_timer);
+ tasks_rcu_exit_stall_timer.expires = jiffies + rtsi;
+ add_timer(&tasks_rcu_exit_stall_timer);
}
/*
@@ -1081,7 +1086,7 @@ static void rcu_tasks_postscan(struct list_head *hop)
}
if (!IS_ENABLED(CONFIG_TINY_RCU))
- timer_delete_sync(&tasks_rcu_exit_srcu_stall_timer);
+ timer_delete_sync(&tasks_rcu_exit_stall_timer);
}
/* See if tasks are still holding out, complain if so. */
@@ -1153,7 +1158,7 @@ static void rcu_tasks_postgp(struct rcu_tasks *rtp)
synchronize_rcu();
}
-static void tasks_rcu_exit_srcu_stall(struct timer_list *unused)
+static void tasks_rcu_exit_stall(struct timer_list *unused)
{
#ifndef CONFIG_TINY_RCU
int rtsi;
@@ -1163,8 +1168,8 @@ static void tasks_rcu_exit_srcu_stall(struct timer_list *unused)
__func__, rcu_tasks.kname, rcu_tasks.tasks_gp_seq,
tasks_gp_state_getname(&rcu_tasks), jiffies - rcu_tasks.gp_jiffies);
pr_info("Please check any exiting tasks stuck between calls to exit_tasks_rcu_start() and exit_tasks_rcu_finish()\n");
- tasks_rcu_exit_srcu_stall_timer.expires = jiffies + rtsi;
- add_timer(&tasks_rcu_exit_srcu_stall_timer);
+ tasks_rcu_exit_stall_timer.expires = jiffies + rtsi;
+ add_timer(&tasks_rcu_exit_stall_timer);
#endif // #ifndef CONFIG_TINY_RCU
}
@@ -1174,7 +1179,7 @@ static void tasks_rcu_exit_srcu_stall(struct timer_list *unused)
* @func: actual callback function to be invoked after the grace period
*
* The callback function will be invoked some time after a full grace
- * period elapses, in other words after all currently executing RCU
+ * period elapses, in other words after all currently executing rcu-tasks
* read-side critical sections have completed. call_rcu_tasks() assumes
* that the read-side critical sections end at a voluntary context
* switch (not a preemption!), cond_resched_tasks_rcu_qs(), entry into idle,
@@ -1360,8 +1365,8 @@ DEFINE_RCU_TASKS(rcu_tasks_rude, rcu_tasks_rude_wait_gp, call_rcu_tasks_rude,
* @func: actual callback function to be invoked after the grace period
*
* The callback function will be invoked some time after a full grace
- * period elapses, in other words after all currently executing RCU
- * read-side critical sections have completed. call_rcu_tasks_rude()
+ * period elapses, in other words after all currently executing rude
+ * rcu-tasks read-side critical sections have completed. call_rcu_tasks_rude()
* assumes that the read-side critical sections end at context switch,
* cond_resched_tasks_rcu_qs(), or transition to usermode execution (as
* usermode execution is schedulable). As such, there are no read-side
@@ -1385,7 +1390,7 @@ static void call_rcu_tasks_rude(struct rcu_head *rhp, rcu_callback_t func)
*
* Control will return to the caller some time after a rude rcu-tasks
* grace period has elapsed, in other words after all currently
- * executing rcu-tasks read-side critical sections have elapsed. These
+ * executing rude rcu-tasks read-side critical sections have elapsed. These
* read-side critical sections are delimited by calls to schedule(),
* cond_resched_tasks_rcu_qs(), userspace execution (which is a schedulable
* context), and (in theory, anyway) cond_resched().
@@ -1455,6 +1460,7 @@ struct rcu_tasks_test_desc {
const char *name;
bool notrun;
unsigned long runstart;
+ void (*gp_dbg)(void);
};
static struct rcu_tasks_test_desc tests[] = {
@@ -1462,6 +1468,8 @@ static struct rcu_tasks_test_desc tests[] = {
.name = "call_rcu_tasks()",
/* If not defined, the test is skipped. */
.notrun = IS_ENABLED(CONFIG_TASKS_RCU),
+ /* Dump rcu tasks status, if test failed. */
+ .gp_dbg = show_rcu_tasks_classic_gp_kthread
},
{
.name = "call_rcu_tasks_trace()",
@@ -1521,6 +1529,8 @@ static int rcu_tasks_verify_self_tests(void)
while (tests[i].notrun) { // still hanging.
if (time_after(jiffies, tests[i].runstart + bst)) {
pr_err("%s has failed boot-time tests.\n", tests[i].name);
+ if (tests[i].gp_dbg)
+ tests[i].gp_dbg();
ret = -1;
break;
}
@@ -1606,4 +1616,10 @@ static inline void rcu_tasks_bootup_oddness(void) {}
DEFINE_SRCU_FAST(rcu_tasks_trace_srcu_struct);
EXPORT_SYMBOL_GPL(rcu_tasks_trace_srcu_struct);
+unsigned long rcu_tasks_trace_batches_completed(void)
+{
+ return srcu_batches_completed(&rcu_tasks_trace_srcu_struct);
+}
+EXPORT_SYMBOL_GPL(rcu_tasks_trace_batches_completed);
+
#endif /* #else #ifdef CONFIG_TASKS_TRACE_RCU */
diff --git a/kernel/rcu/tiny.c b/kernel/rcu/tiny.c
index 585cade21010..dccccd6be941 100644
--- a/kernel/rcu/tiny.c
+++ b/kernel/rcu/tiny.c
@@ -187,9 +187,9 @@ EXPORT_SYMBOL_GPL(call_rcu);
* Store a grace-period-counter "cookie". For more information,
* see the Tree RCU header comment.
*/
-void get_completed_synchronize_rcu_full(struct rcu_gp_oldstate *rgosp)
+void get_completed_synchronize_rcu_full(struct rcu_gp_seq *gsp)
{
- rgosp->rgos_norm = RCU_GET_STATE_COMPLETED;
+ gsp->norm = RCU_GET_STATE_COMPLETED;
}
EXPORT_SYMBOL_GPL(get_completed_synchronize_rcu_full);
diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c
index 03a43d3d2616..96848fc1f02b 100644
--- a/kernel/rcu/tree.c
+++ b/kernel/rcu/tree.c
@@ -357,9 +357,10 @@ bool rcu_watching_zero_in_eqs(int cpu, int *vp)
*/
notrace void rcu_momentary_eqs(void)
{
+ struct rcu_data *rdp = this_cpu_ptr(&rcu_data);
int seq;
- raw_cpu_write(rcu_data.rcu_need_heavy_qs, false);
+ WRITE_ONCE(rdp->rcu_need_heavy_qs, false);
seq = ct_state_inc(2 * CT_RCU_WATCHING);
/* It is illegal to call this from idle state. */
WARN_ON_ONCE(!(seq & CT_RCU_WATCHING));
@@ -986,8 +987,8 @@ static int rcu_watching_snap_recheck(struct rcu_data *rdp)
}
/* Trace-event wrapper function for trace_rcu_future_grace_period. */
-static void trace_rcu_this_gp(struct rcu_node *rnp, struct rcu_data *rdp,
- unsigned long gp_seq_req, const char *s)
+static void trace_rcu_this_gp(struct rcu_node *rnp, unsigned long gp_seq_req,
+ const char *s)
{
trace_rcu_future_grace_period(rcu_state.name, READ_ONCE(rnp->gp_seq),
gp_seq_req, rnp->level,
@@ -1026,7 +1027,7 @@ static bool rcu_start_this_gp(struct rcu_node *rnp_start, struct rcu_data *rdp,
* Note that rnp_start->lock must not be released.
*/
raw_lockdep_assert_held_rcu_node(rnp_start);
- trace_rcu_this_gp(rnp_start, rdp, gp_seq_req, TPS("Startleaf"));
+ trace_rcu_this_gp(rnp_start, gp_seq_req, TPS("Startleaf"));
for (rnp = rnp_start; 1; rnp = rnp->parent) {
if (rnp != rnp_start)
raw_spin_lock_rcu_node(rnp);
@@ -1034,8 +1035,7 @@ static bool rcu_start_this_gp(struct rcu_node *rnp_start, struct rcu_data *rdp,
rcu_seq_started(&rnp->gp_seq, gp_seq_req) ||
(rnp != rnp_start &&
rcu_seq_state(rcu_seq_current(&rnp->gp_seq)))) {
- trace_rcu_this_gp(rnp, rdp, gp_seq_req,
- TPS("Prestarted"));
+ trace_rcu_this_gp(rnp, gp_seq_req, TPS("Prestarted"));
goto unlock_out;
}
WRITE_ONCE(rnp->gp_seq_needed, gp_seq_req);
@@ -1046,7 +1046,7 @@ static bool rcu_start_this_gp(struct rcu_node *rnp_start, struct rcu_data *rdp,
* rcu_gp_cleanup() will see the marking. Bail to
* reduce contention.
*/
- trace_rcu_this_gp(rnp_start, rdp, gp_seq_req,
+ trace_rcu_this_gp(rnp_start, gp_seq_req,
TPS("Startedleaf"));
goto unlock_out;
}
@@ -1058,14 +1058,14 @@ static bool rcu_start_this_gp(struct rcu_node *rnp_start, struct rcu_data *rdp,
/* If GP already in progress, just leave, otherwise start one. */
if (rcu_gp_in_progress()) {
- trace_rcu_this_gp(rnp, rdp, gp_seq_req, TPS("Startedleafroot"));
+ trace_rcu_this_gp(rnp, gp_seq_req, TPS("Startedleafroot"));
goto unlock_out;
}
- trace_rcu_this_gp(rnp, rdp, gp_seq_req, TPS("Startedroot"));
+ trace_rcu_this_gp(rnp, gp_seq_req, TPS("Startedroot"));
WRITE_ONCE(rcu_state.gp_flags, rcu_state.gp_flags | RCU_GP_FLAG_INIT);
WRITE_ONCE(rcu_state.gp_req_activity, jiffies);
if (!READ_ONCE(rcu_state.gp_kthread)) {
- trace_rcu_this_gp(rnp, rdp, gp_seq_req, TPS("NoGPkthread"));
+ trace_rcu_this_gp(rnp, gp_seq_req, TPS("NoGPkthread"));
goto unlock_out;
}
trace_rcu_grace_period(rcu_state.name, data_race(rcu_state.gp_seq), TPS("newreq"));
@@ -1088,12 +1088,11 @@ unlock_out:
static bool rcu_future_gp_cleanup(struct rcu_node *rnp)
{
bool needmore;
- struct rcu_data *rdp = this_cpu_ptr(&rcu_data);
needmore = ULONG_CMP_LT(rnp->gp_seq, rnp->gp_seq_needed);
if (!needmore)
rnp->gp_seq_needed = rnp->gp_seq; /* Avoid counter wrap. */
- trace_rcu_this_gp(rnp, rdp, rnp->gp_seq,
+ trace_rcu_this_gp(rnp, rnp->gp_seq,
needmore ? TPS("CleanupMore") : TPS("Cleanup"));
return needmore;
}
@@ -1139,7 +1138,7 @@ static void rcu_gp_kthread_wake(void)
*/
static bool rcu_accelerate_cbs(struct rcu_node *rnp, struct rcu_data *rdp)
{
- unsigned long gp_seq_req;
+ struct rcu_gp_seq gs;
bool ret = false;
rcu_lockdep_assert_cblist_protected(rdp);
@@ -1161,15 +1160,15 @@ static bool rcu_accelerate_cbs(struct rcu_node *rnp, struct rcu_data *rdp)
* accelerating callback invocation to an earlier grace-period
* number.
*/
- gp_seq_req = rcu_seq_snap(&rcu_state.gp_seq);
- if (rcu_segcblist_accelerate(&rdp->cblist, gp_seq_req))
- ret = rcu_start_this_gp(rnp, rdp, gp_seq_req);
+ get_state_synchronize_rcu_full(&gs);
+ if (rcu_segcblist_accelerate(&rdp->cblist, &gs))
+ ret = rcu_start_this_gp(rnp, rdp, gs.norm);
/* Trace depending on how much we were able to accelerate. */
if (rcu_segcblist_restempty(&rdp->cblist, RCU_WAIT_TAIL))
- trace_rcu_grace_period(rcu_state.name, gp_seq_req, TPS("AccWaitCB"));
+ trace_rcu_grace_period(rcu_state.name, gs.norm, TPS("AccWaitCB"));
else
- trace_rcu_grace_period(rcu_state.name, gp_seq_req, TPS("AccReadyCB"));
+ trace_rcu_grace_period(rcu_state.name, gs.norm, TPS("AccReadyCB"));
trace_rcu_segcb_stats(&rdp->cblist, TPS("SegCbPostAcc"));
@@ -1186,14 +1185,14 @@ static bool rcu_accelerate_cbs(struct rcu_node *rnp, struct rcu_data *rdp)
static void rcu_accelerate_cbs_unlocked(struct rcu_node *rnp,
struct rcu_data *rdp)
{
- unsigned long c;
+ struct rcu_gp_seq gs;
bool needwake;
rcu_lockdep_assert_cblist_protected(rdp);
- c = rcu_seq_snap(&rcu_state.gp_seq);
- if (!READ_ONCE(rdp->gpwrap) && ULONG_CMP_GE(rdp->gp_seq_needed, c)) {
+ get_state_synchronize_rcu_full(&gs);
+ if (!READ_ONCE(rdp->gpwrap) && ULONG_CMP_GE(rdp->gp_seq_needed, gs.norm)) {
/* Old request still live, so mark recent callbacks. */
- (void)rcu_segcblist_accelerate(&rdp->cblist, c);
+ (void)rcu_segcblist_accelerate(&rdp->cblist, &gs);
return;
}
raw_spin_lock_rcu_node(rnp); /* irqs already disabled. */
@@ -1206,7 +1205,7 @@ static void rcu_accelerate_cbs_unlocked(struct rcu_node *rnp,
/*
* Move any callbacks whose grace period has completed to the
* RCU_DONE_TAIL sublist, then compact the remaining sublists and
- * assign ->gp_seq numbers to any callbacks in the RCU_NEXT_TAIL
+ * assign ->gp_seq[] state to any callbacks in the RCU_NEXT_TAIL
* sublist. This function is idempotent, so it does not hurt to
* invoke it repeatedly. As long as it is not invoked -too- often...
* Returns true if the RCU grace-period kthread needs to be awakened.
@@ -1223,10 +1222,10 @@ static bool rcu_advance_cbs(struct rcu_node *rnp, struct rcu_data *rdp)
return false;
/*
- * Find all callbacks whose ->gp_seq numbers indicate that they
- * are ready to invoke, and put them into the RCU_DONE_TAIL sublist.
+ * Find all callbacks whose grace periods have completed (either
+ * normal or expedited) and put them into the RCU_DONE_TAIL sublist.
*/
- rcu_segcblist_advance(&rdp->cblist, rnp->gp_seq);
+ rcu_segcblist_advance(&rdp->cblist);
/* Classify any remaining callbacks. */
return rcu_accelerate_cbs(rnp, rdp);
@@ -2221,8 +2220,15 @@ static noinline void rcu_gp_cleanup(void)
dump_blkd_tasks(rnp, 10);
WARN_ON_ONCE(rnp->qsmask);
WRITE_ONCE(rnp->gp_seq, new_gp_seq);
- if (!rnp->parent)
- smp_mb(); // Order against failing poll_state_synchronize_rcu_full().
+ if (!rnp->parent) {
+ /*
+ * Order against failing poll_state_synchronize_rcu_full(),
+ * and also against rcu_nocb_gp_cleanup() -> swait_active(),
+ * which relies on this barrier to observe a waiter that
+ * enqueued before re-checking the grace-period state.
+ */
+ smp_mb();
+ }
rdp = this_cpu_ptr(&rcu_data);
if (rnp == rdp->mynode)
needgp = __note_gp_changes(rnp, rdp) || needgp;
@@ -2252,8 +2258,7 @@ static noinline void rcu_gp_cleanup(void)
/* Check for GP requests since above loop. */
rdp = this_cpu_ptr(&rcu_data);
if (!needgp && ULONG_CMP_LT(rnp->gp_seq, rnp->gp_seq_needed)) {
- trace_rcu_this_gp(rnp, rdp, rnp->gp_seq_needed,
- TPS("CleanupMore"));
+ trace_rcu_this_gp(rnp, rnp->gp_seq_needed, TPS("CleanupMore"));
needgp = true;
}
/* Advance CBs to reduce false positives below. */
@@ -2539,7 +2544,7 @@ rcu_check_quiescent_state(struct rcu_data *rdp)
* Was there a quiescent state since the beginning of the grace
* period? If no, then exit and wait for the next call.
*/
- if (rdp->cpu_no_qs.b.norm)
+ if (READ_ONCE(rdp->cpu_no_qs.b.norm))
return;
/*
@@ -2670,7 +2675,7 @@ static void rcu_do_batch(struct rcu_data *rdp)
// reporting, so check time limits for them.
if (rdp->rcu_cpu_kthread_status == RCU_KTHREAD_RUNNING &&
rcu_do_batch_check_time(count, tlimit, jlimit_check, jlimit)) {
- rdp->rcu_cpu_has_work = 1;
+ WRITE_ONCE(rdp->rcu_cpu_has_work, 1);
break;
}
}
@@ -2881,6 +2886,23 @@ static __latent_entropy void rcu_core(void)
/* Update RCU state based on any recent quiescent states. */
rcu_check_quiescent_state(rdp);
+ /* Advance callbacks if an expedited GP has completed. */
+ if (!rcu_rdp_is_offloaded(rdp) && rcu_segcblist_is_enabled(&rdp->cblist)) {
+ struct rcu_gp_seq gp_state;
+
+ if (rcu_segcblist_nextgp(&rdp->cblist, &gp_state) &&
+ poll_state_synchronize_rcu_full(&gp_state)) {
+ guard(irqsave)();
+ if (raw_spin_trylock_rcu_node(rnp)) {
+ bool needwake = rcu_advance_cbs(rnp, rdp);
+
+ raw_spin_unlock_rcu_node(rnp);
+ if (needwake)
+ rcu_gp_kthread_wake();
+ }
+ }
+ }
+
/* No grace period and unregistered callbacks? */
if (!rcu_gp_in_progress() &&
rcu_segcblist_is_enabled(&rdp->cblist) && !rcu_rdp_is_offloaded(rdp)) {
@@ -2889,7 +2911,7 @@ static __latent_entropy void rcu_core(void)
rcu_accelerate_cbs_unlocked(rnp, rdp);
}
- rcu_check_gp_start_stall(rnp, rdp, rcu_jiffies_till_stall_check());
+ rcu_check_gp_start_stall(rnp, rcu_jiffies_till_stall_check());
/* If there are callbacks ready, invoke them. */
if (!rcu_rdp_is_offloaded(rdp) && rcu_segcblist_ready_cbs(&rdp->cblist) &&
@@ -2930,7 +2952,7 @@ static void invoke_rcu_core_kthread(void)
unsigned long flags;
local_irq_save(flags);
- __this_cpu_write(rcu_data.rcu_cpu_has_work, 1);
+ this_cpu_write(rcu_data.rcu_cpu_has_work, 1);
t = __this_cpu_read(rcu_data.rcu_cpu_kthread_task);
if (t != NULL && t != current)
rcu_wake_cond(t, __this_cpu_read(rcu_data.rcu_cpu_kthread_status));
@@ -2957,7 +2979,7 @@ static void rcu_cpu_kthread_park(unsigned int cpu)
static int rcu_cpu_kthread_should_run(unsigned int cpu)
{
- return __this_cpu_read(rcu_data.rcu_cpu_has_work);
+ return this_cpu_read(rcu_data.rcu_cpu_has_work);
}
/*
@@ -2978,7 +3000,7 @@ static void rcu_cpu_kthread(unsigned int cpu)
local_bh_disable();
*statusp = RCU_KTHREAD_RUNNING;
local_irq_disable();
- work = *workp;
+ work = READ_ONCE(*workp);
WRITE_ONCE(*workp, 0);
local_irq_enable();
if (work)
@@ -3023,7 +3045,7 @@ static int __init rcu_spawn_core_kthreads(void)
return 0;
}
-static void rcutree_enqueue(struct rcu_data *rdp, struct rcu_head *head, rcu_callback_t func)
+static void rcutree_enqueue(struct rcu_data *rdp, struct rcu_head *head)
{
rcu_segcblist_enqueue(&rdp->cblist, head);
trace_rcu_callback(rcu_state.name, head,
@@ -3035,9 +3057,9 @@ static void rcutree_enqueue(struct rcu_data *rdp, struct rcu_head *head, rcu_cal
* Handle any core-RCU processing required by a call_rcu() invocation.
*/
static void call_rcu_core(struct rcu_data *rdp, struct rcu_head *head,
- rcu_callback_t func, unsigned long flags)
+ unsigned long flags)
{
- rcutree_enqueue(rdp, head, func);
+ rcutree_enqueue(rdp, head);
/*
* If called from an extended quiescent state, invoke the RCU
* core in order to force a re-evaluation of RCU's idleness.
@@ -3178,9 +3200,9 @@ __call_rcu_common(struct rcu_head *head, rcu_callback_t func, bool lazy_in)
check_cb_ovld(rdp);
if (unlikely(rcu_rdp_is_offloaded(rdp)))
- call_rcu_nocb(rdp, head, func, flags, lazy);
+ call_rcu_nocb(rdp, head, flags, lazy);
else
- call_rcu_core(rdp, head, func, flags);
+ call_rcu_core(rdp, head, flags);
local_irq_restore(flags);
}
@@ -3287,7 +3309,7 @@ EXPORT_SYMBOL_GPL(call_rcu);
* Later on, this could in theory be the case for kernels built with
* CONFIG_SMP=y && CONFIG_PREEMPTION=y running on a single CPU, but this
* is not a common case. Furthermore, this optimization would cause
- * the rcu_gp_oldstate structure to expand by 50%, so this potential
+ * the rcu_gp_seq structure to expand by 50%, so this potential
* grace-period optimization is ignored once the scheduler is running.
*/
static int rcu_blocking_is_gp(void)
@@ -3416,16 +3438,16 @@ EXPORT_SYMBOL_GPL(synchronize_rcu);
/**
* get_completed_synchronize_rcu_full - Return a full pre-completed polled state cookie
- * @rgosp: Place to put state cookie
+ * @gsp: Place to put state cookie
*
- * Stores into @rgosp a value that will always be treated by functions
+ * Stores into @gsp a value that will always be treated by functions
* like poll_state_synchronize_rcu_full() as a cookie whose grace period
* has already completed.
*/
-void get_completed_synchronize_rcu_full(struct rcu_gp_oldstate *rgosp)
+void get_completed_synchronize_rcu_full(struct rcu_gp_seq *gsp)
{
- rgosp->rgos_norm = RCU_GET_STATE_COMPLETED;
- rgosp->rgos_exp = RCU_GET_STATE_COMPLETED;
+ gsp->norm = RCU_GET_STATE_COMPLETED;
+ gsp->exp = RCU_GET_STATE_COMPLETED;
}
EXPORT_SYMBOL_GPL(get_completed_synchronize_rcu_full);
@@ -3449,13 +3471,13 @@ EXPORT_SYMBOL_GPL(get_state_synchronize_rcu);
/**
* get_state_synchronize_rcu_full - Snapshot RCU state, both normal and expedited
- * @rgosp: location to place combined normal/expedited grace-period state
+ * @gsp: location to place combined normal/expedited grace-period state
*
- * Places the normal and expedited grace-period states in @rgosp. This
+ * Places the normal and expedited grace-period states in @gsp. This
* state value can be passed to a later call to cond_synchronize_rcu_full()
* or poll_state_synchronize_rcu_full() to determine whether or not a
* grace period (whether normal or expedited) has elapsed in the meantime.
- * The rcu_gp_oldstate structure takes up twice the memory of an unsigned
+ * The rcu_gp_seq structure takes up twice the memory of an unsigned
* long, but is guaranteed to see all grace periods. In contrast, the
* combined state occupies less memory, but can sometimes fail to take
* grace periods into account.
@@ -3463,7 +3485,7 @@ EXPORT_SYMBOL_GPL(get_state_synchronize_rcu);
* This does not guarantee that the needed grace period will actually
* start.
*/
-void get_state_synchronize_rcu_full(struct rcu_gp_oldstate *rgosp)
+void get_state_synchronize_rcu_full(struct rcu_gp_seq *gsp)
{
/*
* Any prior manipulation of RCU-protected data must happen
@@ -3475,8 +3497,8 @@ void get_state_synchronize_rcu_full(struct rcu_gp_oldstate *rgosp)
// in poll_state_synchronize_rcu_full() notwithstanding. Use of
// the latter here would result in too-short grace periods due to
// interactions with newly onlined CPUs.
- rgosp->rgos_norm = rcu_seq_snap(&rcu_state.gp_seq);
- rgosp->rgos_exp = rcu_seq_snap(&rcu_state.expedited_sequence);
+ gsp->norm = rcu_seq_snap(&rcu_state.gp_seq);
+ gsp->exp = rcu_seq_snap(&rcu_state.expedited_sequence);
}
EXPORT_SYMBOL_GPL(get_state_synchronize_rcu_full);
@@ -3527,18 +3549,18 @@ EXPORT_SYMBOL_GPL(start_poll_synchronize_rcu);
/**
* start_poll_synchronize_rcu_full - Take a full snapshot and start RCU grace period
- * @rgosp: value from get_state_synchronize_rcu_full() or start_poll_synchronize_rcu_full()
+ * @gsp: value from get_state_synchronize_rcu_full() or start_poll_synchronize_rcu_full()
*
- * Places the normal and expedited grace-period states in *@rgos. This
+ * Places the normal and expedited grace-period states in *@gs. This
* state value can be passed to a later call to cond_synchronize_rcu_full()
* or poll_state_synchronize_rcu_full() to determine whether or not a
* grace period (whether normal or expedited) has elapsed in the meantime.
* If the needed grace period is not already slated to start, notifies
* RCU core of the need for that grace period.
*/
-void start_poll_synchronize_rcu_full(struct rcu_gp_oldstate *rgosp)
+void start_poll_synchronize_rcu_full(struct rcu_gp_seq *gsp)
{
- get_state_synchronize_rcu_full(rgosp);
+ get_state_synchronize_rcu_full(gsp);
start_poll_synchronize_rcu_common();
}
@@ -3588,21 +3610,39 @@ bool poll_state_synchronize_rcu(unsigned long oldstate)
}
EXPORT_SYMBOL_GPL(poll_state_synchronize_rcu);
+/*
+ * Racy, memory-ordering-free test of whether the normal or expedited grace
+ * period recorded in *gsp has completed. Callers that need the full
+ * memory-ordering guarantees must use poll_state_synchronize_rcu_full();
+ * this variant is only a hint (e.g. for rcu_pending()) and leaves any
+ * required ordering to a subsequent ordered check.
+ */
+static bool poll_state_synchronize_rcu_full_unordered(struct rcu_gp_seq *gsp)
+{
+ struct rcu_node *rnp = rcu_get_root();
+
+ return gsp->norm == RCU_GET_STATE_COMPLETED ||
+ rcu_seq_done_exact(&rnp->gp_seq, gsp->norm) ||
+ gsp->exp == RCU_GET_STATE_COMPLETED ||
+ (gsp->exp != RCU_GET_STATE_NOT_TRACKED &&
+ rcu_seq_done_exact(&rcu_state.expedited_sequence, gsp->exp));
+}
+
/**
* poll_state_synchronize_rcu_full - Has the specified RCU grace period completed?
- * @rgosp: value from get_state_synchronize_rcu_full() or start_poll_synchronize_rcu_full()
+ * @gsp: value from get_state_synchronize_rcu_full() or start_poll_synchronize_rcu_full()
*
* If a full RCU grace period has elapsed since the earlier call from
- * which *rgosp was obtained, return @true, otherwise return @false.
+ * which *gsp was obtained, return @true, otherwise return @false.
* If @false is returned, it is the caller's responsibility to invoke this
* function later on until it does return @true. Alternatively, the caller
- * can explicitly wait for a grace period, for example, by passing @rgosp
+ * can explicitly wait for a grace period, for example, by passing @gsp
* to cond_synchronize_rcu() or by directly invoking synchronize_rcu().
*
* Yes, this function does not take counter wrap into account.
* But counter wrap is harmless. If the counter wraps, we have waited
* for more than a billion grace periods (and way more on a 64-bit
- * system!). Those needing to keep rcu_gp_oldstate values for very
+ * system!). Those needing to keep rcu_gp_seq values for very
* long time periods (many hours even on 32-bit systems) should check
* them occasionally and either refresh them or set a flag indicating
* that the grace period has completed. Alternatively, they can use
@@ -3611,7 +3651,7 @@ EXPORT_SYMBOL_GPL(poll_state_synchronize_rcu);
*
* This function provides the same memory-ordering guarantees that would
* be provided by a synchronize_rcu() that was invoked at the call to
- * the function that provided @rgosp, and that returned at the end of this
+ * the function that provided @gsp, and that returned at the end of this
* function. And this guarantee requires that the root rcu_node structure's
* ->gp_seq field be checked instead of that of the rcu_state structure.
* The problem is that the just-ending grace-period's callbacks can be
@@ -3621,15 +3661,10 @@ EXPORT_SYMBOL_GPL(poll_state_synchronize_rcu);
* cause a subsequent poll_state_synchronize_rcu_full() to return @true,
* then the root rcu_node structure is the one that needs to be polled.
*/
-bool poll_state_synchronize_rcu_full(struct rcu_gp_oldstate *rgosp)
+bool poll_state_synchronize_rcu_full(struct rcu_gp_seq *gsp)
{
- struct rcu_node *rnp = rcu_get_root();
-
smp_mb(); // Order against root rcu_node structure grace-period cleanup.
- if (rgosp->rgos_norm == RCU_GET_STATE_COMPLETED ||
- rcu_seq_done_exact(&rnp->gp_seq, rgosp->rgos_norm) ||
- rgosp->rgos_exp == RCU_GET_STATE_COMPLETED ||
- rcu_seq_done_exact(&rcu_state.expedited_sequence, rgosp->rgos_exp)) {
+ if (poll_state_synchronize_rcu_full_unordered(gsp)) {
smp_mb(); /* Ensure GP ends before subsequent accesses. */
return true;
}
@@ -3664,11 +3699,11 @@ EXPORT_SYMBOL_GPL(cond_synchronize_rcu);
/**
* cond_synchronize_rcu_full - Conditionally wait for an RCU grace period
- * @rgosp: value from get_state_synchronize_rcu_full(), start_poll_synchronize_rcu_full(), or start_poll_synchronize_rcu_expedited_full()
+ * @gsp: value from get_state_synchronize_rcu_full(), start_poll_synchronize_rcu_full(), or start_poll_synchronize_rcu_expedited_full()
*
* If a full RCU grace period has elapsed since the call to
* get_state_synchronize_rcu_full(), start_poll_synchronize_rcu_full(),
- * or start_poll_synchronize_rcu_expedited_full() from which @rgosp was
+ * or start_poll_synchronize_rcu_expedited_full() from which @gsp was
* obtained, just return. Otherwise, invoke synchronize_rcu() to wait
* for a full grace period.
*
@@ -3679,12 +3714,12 @@ EXPORT_SYMBOL_GPL(cond_synchronize_rcu);
*
* This function provides the same memory-ordering guarantees that
* would be provided by a synchronize_rcu() that was invoked at the call
- * to the function that provided @rgosp and that returned at the end of
+ * to the function that provided @gsp and that returned at the end of
* this function.
*/
-void cond_synchronize_rcu_full(struct rcu_gp_oldstate *rgosp)
+void cond_synchronize_rcu_full(struct rcu_gp_seq *gsp)
{
- if (!poll_state_synchronize_rcu_full(rgosp))
+ if (!poll_state_synchronize_rcu_full(gsp))
synchronize_rcu();
}
EXPORT_SYMBOL_GPL(cond_synchronize_rcu_full);
@@ -3699,6 +3734,7 @@ EXPORT_SYMBOL_GPL(cond_synchronize_rcu_full);
static int rcu_pending(int user)
{
bool gp_in_progress;
+ struct rcu_gp_seq gp_state;
struct rcu_data *rdp = this_cpu_ptr(&rcu_data);
struct rcu_node *rnp = rdp->mynode;
@@ -3729,6 +3765,17 @@ static int rcu_pending(int user)
rcu_segcblist_ready_cbs(&rdp->cblist))
return 1;
+ /*
+ * Has a GP (normal or expedited) completed for pending callbacks?
+ * This is only a racy hint to decide whether to run rcu_core(); the
+ * ordered re-check and callback advancement happen there, so the
+ * unordered test avoids paying for memory barriers on every tick.
+ */
+ if (!rcu_rdp_is_offloaded(rdp) &&
+ rcu_segcblist_nextgp(&rdp->cblist, &gp_state) &&
+ poll_state_synchronize_rcu_full_unordered(&gp_state))
+ return 1;
+
/* Has RCU gone idle with this CPU needing another grace period? */
if (!gp_in_progress && rcu_segcblist_is_enabled(&rdp->cblist) &&
!rcu_rdp_is_offloaded(rdp) &&
@@ -4900,6 +4947,9 @@ static void __init rcu_dump_rcu_node_tree(void)
struct workqueue_struct *rcu_gp_wq;
+static struct cpumask rcu_stall_cpumask;
+static struct cpumask rcu_exp_stall_cpumask;
+
void __init rcu_init(void)
{
int cpu = smp_processor_id();
diff --git a/kernel/rcu/tree.h b/kernel/rcu/tree.h
index 7dfc57e9adb1..eedfa43059e8 100644
--- a/kernel/rcu/tree.h
+++ b/kernel/rcu/tree.h
@@ -253,7 +253,7 @@ struct rcu_data {
u8 nocb_gp_sleep; /* Is the nocb GP thread asleep? */
u8 nocb_gp_bypass; /* Found a bypass on last scan? */
u8 nocb_gp_gp; /* GP to wait for on last scan? */
- unsigned long nocb_gp_seq; /* If so, ->gp_seq to wait for. */
+ struct rcu_gp_seq nocb_gp_seq; /* If so, GP state to wait for. */
unsigned long nocb_gp_loops; /* # passes through wait code. */
struct swait_queue_head nocb_gp_wq; /* For nocb kthreads to sleep on. */
bool nocb_cb_sleep; /* Is the nocb CB thread asleep? */
@@ -296,6 +296,11 @@ struct rcu_data {
int cpu;
};
+static inline void rcu_defer_qs_clear(struct rcu_data *rdp)
+{
+ WRITE_ONCE(rdp->defer_qs_pending, DEFER_QS_IDLE);
+}
+
/* Values for nocb_defer_wakeup field in struct rcu_data. */
#define RCU_NOCB_WAKE_NOT 0
#define RCU_NOCB_WAKE_BYPASS 1
@@ -386,7 +391,6 @@ struct rcu_state {
struct mutex exp_mutex; /* Serialize expedited GP. */
struct mutex exp_wake_mutex; /* Serialize wakeup. */
unsigned long expedited_sequence; /* Take a ticket. */
- atomic_t expedited_need_qs; /* # CPUs left to check in. */
struct swait_queue_head expedited_wq; /* Wait for check-ins. */
int ncpus_snap; /* # CPUs seen last time. */
u8 cbovld; /* Callback overload now? */
@@ -498,12 +502,13 @@ static bool rcu_preempt_need_deferred_qs(struct task_struct *t);
static void zero_cpu_stall_ticks(struct rcu_data *rdp);
static struct swait_queue_head *rcu_nocb_gp_get(struct rcu_node *rnp);
static void rcu_nocb_gp_cleanup(struct swait_queue_head *sq);
+static void rcu_nocb_exp_cleanup(struct rcu_node *rnp);
static void rcu_init_one_nocb(struct rcu_node *rnp);
static bool wake_nocb_gp(struct rcu_data *rdp);
static bool rcu_nocb_flush_bypass(struct rcu_data *rdp, struct rcu_head *rhp,
unsigned long j, bool lazy);
static void call_rcu_nocb(struct rcu_data *rdp, struct rcu_head *head,
- rcu_callback_t func, unsigned long flags, bool lazy);
+ unsigned long flags, bool lazy);
static void __maybe_unused __call_rcu_nocb_wake(struct rcu_data *rdp, bool was_empty,
unsigned long flags);
static int rcu_nocb_need_deferred_wakeup(struct rcu_data *rdp, int level);
@@ -540,8 +545,7 @@ static bool rcu_nohz_full_cpu(void);
static void record_gp_stall_check_time(void);
static void rcu_iw_handler(struct irq_work *iwp);
static void check_cpu_stall(struct rcu_data *rdp);
-static void rcu_check_gp_start_stall(struct rcu_node *rnp, struct rcu_data *rdp,
- const unsigned long gpssdelay);
+static void rcu_check_gp_start_stall(struct rcu_node *rnp, const unsigned long gpssdelay);
/* Forward declarations for tree_exp.h. */
static void sync_rcu_do_polled_gp(struct work_struct *wp);
diff --git a/kernel/rcu/tree_exp.h b/kernel/rcu/tree_exp.h
index 82cada459e5d..81d42b6e2e31 100644
--- a/kernel/rcu/tree_exp.h
+++ b/kernel/rcu/tree_exp.h
@@ -578,6 +578,7 @@ static void synchronize_rcu_expedited_stall(unsigned long jiffies_start, unsigne
if (!(READ_ONCE(rnp->expmask) & mask))
continue;
ndetected++;
+ cpumask_set_cpu(cpu, &rcu_exp_stall_cpumask);
rdp = per_cpu_ptr(&rcu_data, cpu);
pr_cont(" %d-%c%c%c%c", cpu,
"O."[!!cpu_online(cpu)],
@@ -665,6 +666,8 @@ static void synchronize_rcu_expedited_wait(void)
if (rcu_stall_is_suppressed())
continue;
+ cpumask_clear(&rcu_exp_stall_cpumask);
+
nbcon_cpu_emergency_enter();
j = jiffies;
@@ -675,7 +678,7 @@ static void synchronize_rcu_expedited_wait(void)
nbcon_cpu_emergency_exit();
- panic_on_rcu_stall();
+ panic_on_rcu_stall(&rcu_exp_stall_cpumask);
}
}
@@ -708,6 +711,8 @@ static void rcu_exp_wait_wake(unsigned long s)
}
smp_mb(); /* All above changes before wakeup. */
wake_up_all(&rnp->exp_wq[rcu_seq_ctr(s) & 0x3]);
+ if (rcu_is_leaf_node(rnp))
+ rcu_nocb_exp_cleanup(rnp);
}
trace_rcu_exp_grace_period(rcu_state.name, s, TPS("endwake"));
mutex_unlock(&rcu_state.exp_wake_mutex);
@@ -731,7 +736,7 @@ static void rcu_exp_need_qs(void)
{
lockdep_assert_irqs_disabled();
ASSERT_EXCLUSIVE_WRITER_SCOPED(*this_cpu_ptr(&rcu_data.cpu_no_qs.b.exp));
- __this_cpu_write(rcu_data.cpu_no_qs.b.exp, true);
+ this_cpu_write(rcu_data.cpu_no_qs.b.exp, true);
/* Store .exp before .rcu_urgent_qs. */
smp_store_release(this_cpu_ptr(&rcu_data.rcu_urgent_qs), true);
set_need_resched_current();
@@ -870,7 +875,7 @@ static void rcu_exp_handler(void *unused)
ASSERT_EXCLUSIVE_WRITER_SCOPED(rdp->cpu_no_qs.b.exp);
if (!(READ_ONCE(rnp->expmask) & rdp->grpmask) ||
- __this_cpu_read(rcu_data.cpu_no_qs.b.exp))
+ this_cpu_read(rcu_data.cpu_no_qs.b.exp))
return;
if (rcu_is_cpu_rrupt_from_idle() ||
(IS_ENABLED(CONFIG_PREEMPT_COUNT) && preempt_bh_enabled)) {
@@ -1047,18 +1052,18 @@ EXPORT_SYMBOL_GPL(start_poll_synchronize_rcu_expedited);
/**
* start_poll_synchronize_rcu_expedited_full - Take a full snapshot and start expedited grace period
- * @rgosp: Place to put snapshot of grace-period state
+ * @gsp: Place to put snapshot of grace-period state
*
- * Places the normal and expedited grace-period states in rgosp. This
+ * Places the normal and expedited grace-period states in gsp. This
* state value can be passed to a later call to cond_synchronize_rcu_full()
* or poll_state_synchronize_rcu_full() to determine whether or not a
* grace period (whether normal or expedited) has elapsed in the meantime.
* If the needed expedited grace period is not already slated to start,
* initiates that grace period.
*/
-void start_poll_synchronize_rcu_expedited_full(struct rcu_gp_oldstate *rgosp)
+void start_poll_synchronize_rcu_expedited_full(struct rcu_gp_seq *gsp)
{
- get_state_synchronize_rcu_full(rgosp);
+ get_state_synchronize_rcu_full(gsp);
(void)start_poll_synchronize_rcu_expedited();
}
EXPORT_SYMBOL_GPL(start_poll_synchronize_rcu_expedited_full);
@@ -1092,11 +1097,11 @@ EXPORT_SYMBOL_GPL(cond_synchronize_rcu_expedited);
/**
* cond_synchronize_rcu_expedited_full - Conditionally wait for an expedited RCU grace period
- * @rgosp: value from get_state_synchronize_rcu_full(), start_poll_synchronize_rcu_full(), or start_poll_synchronize_rcu_expedited_full()
+ * @gsp: value from get_state_synchronize_rcu_full(), start_poll_synchronize_rcu_full(), or start_poll_synchronize_rcu_expedited_full()
*
* If a full RCU grace period has elapsed since the call to
* get_state_synchronize_rcu_full(), start_poll_synchronize_rcu_full(),
- * or start_poll_synchronize_rcu_expedited_full() from which @rgosp was
+ * or start_poll_synchronize_rcu_expedited_full() from which @gsp was
* obtained, just return. Otherwise, invoke synchronize_rcu_expedited()
* to wait for a full grace period.
*
@@ -1107,12 +1112,12 @@ EXPORT_SYMBOL_GPL(cond_synchronize_rcu_expedited);
*
* This function provides the same memory-ordering guarantees that
* would be provided by a synchronize_rcu() that was invoked at the call
- * to the function that provided @rgosp and that returned at the end of
+ * to the function that provided @gsp and that returned at the end of
* this function.
*/
-void cond_synchronize_rcu_expedited_full(struct rcu_gp_oldstate *rgosp)
+void cond_synchronize_rcu_expedited_full(struct rcu_gp_seq *gsp)
{
- if (!poll_state_synchronize_rcu_full(rgosp))
+ if (!poll_state_synchronize_rcu_full(gsp))
synchronize_rcu_expedited();
}
EXPORT_SYMBOL_GPL(cond_synchronize_rcu_expedited_full);
diff --git a/kernel/rcu/tree_nocb.h b/kernel/rcu/tree_nocb.h
index 373b877cf171..19bb42672baf 100644
--- a/kernel/rcu/tree_nocb.h
+++ b/kernel/rcu/tree_nocb.h
@@ -170,13 +170,35 @@ static void rcu_lockdep_assert_cblist_protected(struct rcu_data *rdp)
lockdep_assert_held(&rdp->nocb_lock);
}
+static void rcu_nocb_cleanup_wake(struct swait_queue_head *sq)
+{
+ if (swait_active(sq))
+ swake_up_all(sq);
+}
+
/*
* Wake up any no-CBs CPUs' kthreads that were waiting on the just-ended
* grace period.
*/
static void rcu_nocb_gp_cleanup(struct swait_queue_head *sq)
{
- swake_up_all(sq);
+ /*
+ * swait_active() can be checked first because of the following
+ * ordering, which pairs the smp_mb() in rcu_gp_cleanup() against
+ * the implicit barrier in prepare_to_swait()/set_current_state()
+ * on the nocb_gp_wait() side:
+ *
+ * rcu_gp_cleanup() nocb_gp_wait()
+ * --------------- --------------
+ * WRITE_ONCE(root->gp_seq, new_gp_seq); swait_event_interruptible_exclusive(sq)
+ * smp_mb() prepare_to_swait()
+ * if swait_active(sq) list_add_tail(...)
+ * swake_up_all(sq) set_current_state()
+ * smp_mb()
+ * if (poll_state_synchronize_rcu_full())
+ * ...
+ */
+ rcu_nocb_cleanup_wake(sq);
}
static struct swait_queue_head *rcu_nocb_gp_get(struct rcu_node *rnp)
@@ -190,6 +212,38 @@ static void rcu_init_one_nocb(struct rcu_node *rnp)
init_swait_queue_head(&rnp->nocb_gp_wq[1]);
}
+/*
+ * Wake NOCB rcuog kthreads on a leaf node so that they can advance
+ * callbacks that were waiting for the just-completed expedited GP.
+ *
+ * The rcuog kthread waiting for a grace period sleeps on the per-leaf-node
+ * ->nocb_gp_wq[] (not on its rdp_gp's ->nocb_gp_wq, which only signals that
+ * new callbacks have shown up), so this is the queue that must be woken.
+ * Both the even and odd waitqueues are woken because the expedited sequence
+ * does not share parity with the normal ->gp_seq the waiter indexed with.
+ */
+static void rcu_nocb_exp_cleanup(struct rcu_node *rnp)
+{
+ /*
+ * swait_active() can be checked first because of the following
+ * ordering, which pairs the smp_mb() in rcu_exp_wait_wake() against
+ * the implicit barrier in prepare_to_swait()/set_current_state()
+ * on the nocb_gp_wait() side:
+ *
+ * rcu_exp_wait_wake() nocb_gp_wait()
+ * --------------- --------------
+ * rcu_seq_end(&rcu_state.expedited_sequence); swait_event_interruptible_exclusive(sq)
+ * smp_mb() prepare_to_swait()
+ * if swait_active(sq) list_add_tail(...)
+ * swake_up_all(sq) set_current_state()
+ * smp_mb()
+ * if (poll_state_synchronize_rcu_full())
+ * ...
+ */
+ rcu_nocb_cleanup_wake(&rnp->nocb_gp_wq[0]);
+ rcu_nocb_cleanup_wake(&rnp->nocb_gp_wq[1]);
+}
+
/* Clear any pending deferred wakeup timer (nocb_gp_lock must be held). */
static void nocb_defer_wakeup_cancel(struct rcu_data *rdp_gp)
{
@@ -433,7 +487,7 @@ static bool rcu_nocb_try_bypass(struct rcu_data *rdp, struct rcu_head *rhp,
bool lazy)
{
unsigned long c;
- unsigned long cur_gp_seq;
+ struct rcu_gp_seq cur_gp_seq;
unsigned long j = jiffies;
long ncbs = rcu_cblist_n_cbs(&rdp->nocb_bypass);
long lazy_len = READ_ONCE(rdp->lazy_len);
@@ -502,7 +556,7 @@ static bool rcu_nocb_try_bypass(struct rcu_data *rdp, struct rcu_head *rhp,
}
if (j != rdp->nocb_gp_adv_time &&
rcu_segcblist_nextgp(&rdp->cblist, &cur_gp_seq) &&
- rcu_seq_done(&rdp->mynode->gp_seq, cur_gp_seq)) {
+ poll_state_synchronize_rcu_full(&cur_gp_seq)) {
rcu_advance_cbs_nowake(rdp->mynode, rdp);
rdp->nocb_gp_adv_time = j;
}
@@ -603,13 +657,13 @@ static void __call_rcu_nocb_wake(struct rcu_data *rdp, bool was_alldone,
}
static void call_rcu_nocb(struct rcu_data *rdp, struct rcu_head *head,
- rcu_callback_t func, unsigned long flags, bool lazy)
+ unsigned long flags, bool lazy)
{
bool was_alldone;
if (!rcu_nocb_try_bypass(rdp, head, &was_alldone, flags, lazy)) {
/* Not enqueued on bypass but locked, do regular enqueue */
- rcutree_enqueue(rdp, head, func);
+ rcutree_enqueue(rdp, head);
__call_rcu_nocb_wake(rdp, was_alldone, flags); /* unlocks */
}
}
@@ -659,7 +713,6 @@ static noinline_for_stack void nocb_gp_wait(struct rcu_data *my_rdp)
{
bool bypass = false;
int __maybe_unused cpu = my_rdp->cpu;
- unsigned long cur_gp_seq;
unsigned long flags;
bool gotcbs = false;
unsigned long j = jiffies;
@@ -669,7 +722,7 @@ static noinline_for_stack void nocb_gp_wait(struct rcu_data *my_rdp)
bool needwake_gp;
struct rcu_data *rdp, *rdp_toggling = NULL;
struct rcu_node *rnp;
- unsigned long wait_gp_seq = 0; // Suppress "use uninitialized" warning.
+ struct rcu_gp_seq wait_gp_seq = {0}; // Suppress "use uninitialized" warning.
bool wasempty = false;
/*
@@ -693,6 +746,7 @@ static noinline_for_stack void nocb_gp_wait(struct rcu_data *my_rdp)
* won't be ignored for long.
*/
list_for_each_entry(rdp, &my_rdp->nocb_head_rdp, nocb_entry_rdp) {
+ struct rcu_gp_seq cur_gp_seq;
long bypass_ncbs;
bool flush_bypass = false;
long lazy_ncbs;
@@ -731,21 +785,27 @@ static noinline_for_stack void nocb_gp_wait(struct rcu_data *my_rdp)
if (!rcu_segcblist_restempty(&rdp->cblist,
RCU_NEXT_READY_TAIL) ||
(rcu_segcblist_nextgp(&rdp->cblist, &cur_gp_seq) &&
- rcu_seq_done(&rnp->gp_seq, cur_gp_seq))) {
+ poll_state_synchronize_rcu_full(&cur_gp_seq))) {
raw_spin_lock_rcu_node(rnp); /* irqs disabled. */
needwake_gp = rcu_advance_cbs(rnp, rdp);
wasempty = rcu_segcblist_restempty(&rdp->cblist,
RCU_NEXT_READY_TAIL);
raw_spin_unlock_rcu_node(rnp); /* irqs disabled. */
}
- // Need to wait on some grace period?
WARN_ON_ONCE(wasempty &&
!rcu_segcblist_restempty(&rdp->cblist,
RCU_NEXT_READY_TAIL));
+ // Need to wait on some grace period?
if (rcu_segcblist_nextgp(&rdp->cblist, &cur_gp_seq)) {
- if (!needwait_gp ||
- ULONG_CMP_LT(cur_gp_seq, wait_gp_seq))
- wait_gp_seq = cur_gp_seq;
+ /*
+ * Track the earliest pending normal and expedited GP
+ * across the group so the wait below can be released by
+ * whichever completes first.
+ */
+ if (!needwait_gp || ULONG_CMP_LT(cur_gp_seq.norm, wait_gp_seq.norm))
+ wait_gp_seq.norm = cur_gp_seq.norm;
+ if (!needwait_gp || ULONG_CMP_LT(cur_gp_seq.exp, wait_gp_seq.exp))
+ wait_gp_seq.exp = cur_gp_seq.exp;
needwait_gp = true;
trace_rcu_nocb_wake(rcu_state.name, rdp->cpu,
TPS("NeedWaitGP"));
@@ -767,7 +827,8 @@ static noinline_for_stack void nocb_gp_wait(struct rcu_data *my_rdp)
my_rdp->nocb_gp_bypass = bypass;
my_rdp->nocb_gp_gp = needwait_gp;
- my_rdp->nocb_gp_seq = needwait_gp ? wait_gp_seq : 0;
+ if (needwait_gp)
+ my_rdp->nocb_gp_seq = wait_gp_seq;
// At least one child with non-empty ->nocb_bypass, so set
// timer in order to avoid stranding its callbacks.
@@ -802,12 +863,12 @@ static noinline_for_stack void nocb_gp_wait(struct rcu_data *my_rdp)
nocb_gp_sleep(my_rdp, cpu);
} else {
rnp = my_rdp->mynode;
- trace_rcu_this_gp(rnp, my_rdp, wait_gp_seq, TPS("StartWait"));
+ trace_rcu_this_gp(rnp, wait_gp_seq.norm, TPS("StartWait"));
swait_event_interruptible_exclusive(
- rnp->nocb_gp_wq[rcu_seq_ctr(wait_gp_seq) & 0x1],
- rcu_seq_done(&rnp->gp_seq, wait_gp_seq) ||
+ rnp->nocb_gp_wq[rcu_seq_ctr(wait_gp_seq.norm) & 0x1],
+ poll_state_synchronize_rcu_full(&wait_gp_seq) ||
!READ_ONCE(my_rdp->nocb_gp_sleep));
- trace_rcu_this_gp(rnp, my_rdp, wait_gp_seq, TPS("EndWait"));
+ trace_rcu_this_gp(rnp, wait_gp_seq.norm, TPS("EndWait"));
}
if (!rcu_nocb_poll) {
@@ -841,7 +902,8 @@ static noinline_for_stack void nocb_gp_wait(struct rcu_data *my_rdp)
swake_up_one(&rdp_toggling->nocb_state_wq);
}
- my_rdp->nocb_gp_seq = -1;
+ my_rdp->nocb_gp_seq.norm = -1;
+ my_rdp->nocb_gp_seq.exp = -1;
WARN_ON(signal_pending(current));
}
@@ -877,7 +939,7 @@ static inline bool nocb_cb_wait_cond(struct rcu_data *rdp)
static void nocb_cb_wait(struct rcu_data *rdp)
{
struct rcu_segcblist *cblist = &rdp->cblist;
- unsigned long cur_gp_seq;
+ struct rcu_gp_seq cur_gp_seq;
unsigned long flags;
bool needwake_gp = false;
struct rcu_node *rnp = rdp->mynode;
@@ -919,7 +981,7 @@ static void nocb_cb_wait(struct rcu_data *rdp)
lockdep_assert_irqs_enabled();
rcu_nocb_lock_irqsave(rdp, flags);
if (rcu_segcblist_nextgp(cblist, &cur_gp_seq) &&
- rcu_seq_done(&rnp->gp_seq, cur_gp_seq) &&
+ poll_state_synchronize_rcu_full(&cur_gp_seq) &&
raw_spin_trylock_rcu_node(rnp)) { /* irqs already disabled. */
needwake_gp = rcu_advance_cbs(rdp->mynode, rdp);
raw_spin_unlock_rcu_node(rnp); /* irqs remain disabled. */
@@ -1525,7 +1587,7 @@ static void show_rcu_nocb_gp_state(struct rcu_data *rdp)
{
struct rcu_node *rnp = rdp->mynode;
- pr_info("nocb GP %d %c%c%c%c%c %c[%c%c] %c%c:%ld rnp %d:%d %lu %c CPU %d%s\n",
+ pr_info("nocb GP %d %c%c%c%c%c %c[%c%c] %c%c:%ld/%ld rnp %d:%d %lu %c CPU %d%s\n",
rdp->cpu,
"kK"[!!rdp->nocb_gp_kthread],
"lL"[raw_spin_is_locked(&rdp->nocb_gp_lock)],
@@ -1537,7 +1599,8 @@ static void show_rcu_nocb_gp_state(struct rcu_data *rdp)
".W"[swait_active(&rnp->nocb_gp_wq[1])],
".B"[!!rdp->nocb_gp_bypass],
".G"[!!rdp->nocb_gp_gp],
- (long)rdp->nocb_gp_seq,
+ (long)rdp->nocb_gp_seq.norm,
+ (long)rdp->nocb_gp_seq.exp,
rnp->grplo, rnp->grphi, READ_ONCE(rdp->nocb_gp_loops),
rdp->nocb_gp_kthread ? task_state_to_char(rdp->nocb_gp_kthread) : '.',
rdp->nocb_gp_kthread ? (int)task_cpu(rdp->nocb_gp_kthread) : -1,
@@ -1548,8 +1611,8 @@ static void show_rcu_nocb_gp_state(struct rcu_data *rdp)
static void show_rcu_nocb_state(struct rcu_data *rdp)
{
char bufd[22];
- char bufw[45];
- char bufr[45];
+ char bufw[64];
+ char bufr[64];
char bufn[22];
char bufb[22];
struct rcu_data *nocb_next_rdp;
@@ -1569,9 +1632,12 @@ static void show_rcu_nocb_state(struct rcu_data *rdp)
nocb_entry_rdp);
sprintf(bufd, "%ld", rsclp->seglen[RCU_DONE_TAIL]);
- sprintf(bufw, "%ld(%ld)", rsclp->seglen[RCU_WAIT_TAIL], rsclp->gp_seq[RCU_WAIT_TAIL]);
- sprintf(bufr, "%ld(%ld)", rsclp->seglen[RCU_NEXT_READY_TAIL],
- rsclp->gp_seq[RCU_NEXT_READY_TAIL]);
+ sprintf(bufw, "%ld(%ld/%ld)", rsclp->seglen[RCU_WAIT_TAIL],
+ rsclp->gp_seq[RCU_WAIT_TAIL].norm,
+ rsclp->gp_seq[RCU_WAIT_TAIL].exp);
+ sprintf(bufr, "%ld(%ld/%ld)", rsclp->seglen[RCU_NEXT_READY_TAIL],
+ rsclp->gp_seq[RCU_NEXT_READY_TAIL].norm,
+ rsclp->gp_seq[RCU_NEXT_READY_TAIL].exp);
sprintf(bufn, "%ld", rsclp->seglen[RCU_NEXT_TAIL]);
sprintf(bufb, "%ld", rcu_cblist_n_cbs(&rdp->nocb_bypass));
pr_info(" CB %d^%d->%d %c%c%c%c%c F%ld L%ld C%d %c%s%c%s%c%s%c%s%c%s q%ld %c CPU %d%s\n",
@@ -1654,6 +1720,10 @@ static void rcu_init_one_nocb(struct rcu_node *rnp)
{
}
+static void rcu_nocb_exp_cleanup(struct rcu_node *rnp)
+{
+}
+
static bool wake_nocb_gp(struct rcu_data *rdp)
{
return false;
@@ -1666,7 +1736,7 @@ static bool rcu_nocb_flush_bypass(struct rcu_data *rdp, struct rcu_head *rhp,
}
static void call_rcu_nocb(struct rcu_data *rdp, struct rcu_head *head,
- rcu_callback_t func, unsigned long flags, bool lazy)
+ unsigned long flags, bool lazy)
{
WARN_ON_ONCE(1); /* Should be dead code! */
}
diff --git a/kernel/rcu/tree_plugin.h b/kernel/rcu/tree_plugin.h
index 95ad967adcf3..743c16247fc0 100644
--- a/kernel/rcu/tree_plugin.h
+++ b/kernel/rcu/tree_plugin.h
@@ -298,11 +298,11 @@ static void rcu_preempt_ctxt_queue(struct rcu_node *rnp, struct rcu_data *rdp)
static void rcu_qs(void)
{
RCU_LOCKDEP_WARN(preemptible(), "rcu_qs() invoked with preemption enabled!!!\n");
- if (__this_cpu_read(rcu_data.cpu_no_qs.b.norm)) {
+ if (this_cpu_read(rcu_data.cpu_no_qs.b.norm)) {
trace_rcu_grace_period(TPS("rcu_preempt"),
__this_cpu_read(rcu_data.gp_seq),
TPS("cpuqs"));
- __this_cpu_write(rcu_data.cpu_no_qs.b.norm, false);
+ this_cpu_write(rcu_data.cpu_no_qs.b.norm, false);
barrier(); /* Coordinate with rcu_flavor_sched_clock_irq(). */
WRITE_ONCE(current->rcu_read_unlock_special.b.need_qs, false);
}
@@ -488,7 +488,7 @@ rcu_preempt_deferred_qs_irqrestore(struct task_struct *t, unsigned long flags)
rdp = this_cpu_ptr(&rcu_data);
if (rdp->defer_qs_pending == DEFER_QS_PENDING)
- rdp->defer_qs_pending = DEFER_QS_IDLE;
+ rcu_defer_qs_clear(rdp);
/*
* If RCU core is waiting for this CPU to exit its critical section,
@@ -599,7 +599,7 @@ rcu_preempt_deferred_qs_irqrestore(struct task_struct *t, unsigned long flags)
*/
static notrace bool rcu_preempt_need_deferred_qs(struct task_struct *t)
{
- return (__this_cpu_read(rcu_data.cpu_no_qs.b.exp) ||
+ return (this_cpu_read(rcu_data.cpu_no_qs.b.exp) ||
READ_ONCE(t->rcu_read_unlock_special.s)) &&
rcu_preempt_depth() == 0;
}
@@ -614,9 +614,35 @@ static notrace bool rcu_preempt_need_deferred_qs(struct task_struct *t)
notrace void rcu_preempt_deferred_qs(struct task_struct *t)
{
unsigned long flags;
+ struct rcu_data *rdp;
- if (!rcu_preempt_need_deferred_qs(t))
+ if (!rcu_preempt_need_deferred_qs(t)) {
+ /*
+ * If we got here from a softirq/irq_work that fired while
+ * rcu_preempt_depth() > 0, the deferred-QS mechanism has been
+ * consumed without doing any work: rcu_preempt_need_deferred_qs()
+ * just returned false because the task is still in a reader, so
+ * the actual QS report has to wait for the next
+ * rcu_read_unlock().
+ *
+ * Clear ->defer_qs_pending here so the next outer
+ * rcu_read_unlock_special() can re-arm a fresh mechanism (in
+ * particular the irq_work path, which the local_irq_enable()
+ * recovery boundary cannot itself reschedule from).
+ *
+ * Recursion safety: rcu_preempt_depth() > 0 means we are inside
+ * an outer reader, so any inner rcu_read_unlock() reached via
+ * tracing (bpf programs attached to trace points) brings
+ * nesting to outer (> 0), never to 0, so no recursive
+ * raise_softirq_irqoff()/irq_work_queue_on() can be triggered
+ * by this clear.
+ */
+ if (rcu_preempt_depth() > 0) {
+ rdp = this_cpu_ptr(&rcu_data);
+ rcu_defer_qs_clear(rdp);
+ }
return;
+ }
local_irq_save(flags);
rcu_preempt_deferred_qs_irqrestore(t, flags);
}
@@ -645,7 +671,7 @@ static void rcu_preempt_deferred_qs_handler(struct irq_work *iwp)
* 5. Deferred QS reporting does not happen.
*/
if (rcu_preempt_depth() > 0)
- WRITE_ONCE(rdp->defer_qs_pending, DEFER_QS_IDLE);
+ rcu_defer_qs_clear(rdp);
}
/*
@@ -923,10 +949,10 @@ void rcu_read_unlock_strict(void)
*
* The in_atomic_preempt_off() check ensures that we come here holding
* the last preempt_count (which will get dropped once we return to
- * __rcu_read_unlock().
+ * __rcu_read_unlock()).
*/
rdp = this_cpu_ptr(&rcu_data);
- rdp->cpu_no_qs.b.norm = false;
+ WRITE_ONCE(rdp->cpu_no_qs.b.norm, false);
rcu_report_qs_rdp(rdp);
udelay(rcu_unlock_delay);
}
@@ -950,12 +976,12 @@ static void __init rcu_bootup_announce(void)
static void rcu_qs(void)
{
RCU_LOCKDEP_WARN(preemptible(), "rcu_qs() invoked with preemption enabled!!!");
- if (!__this_cpu_read(rcu_data.cpu_no_qs.s))
+ if (!this_cpu_read(rcu_data.cpu_no_qs.s))
return;
trace_rcu_grace_period(TPS("rcu_sched"),
__this_cpu_read(rcu_data.gp_seq), TPS("cpuqs"));
- __this_cpu_write(rcu_data.cpu_no_qs.b.norm, false);
- if (__this_cpu_read(rcu_data.cpu_no_qs.b.exp))
+ this_cpu_write(rcu_data.cpu_no_qs.b.norm, false);
+ if (this_cpu_read(rcu_data.cpu_no_qs.b.exp))
rcu_report_exp_rdp(this_cpu_ptr(&rcu_data));
}
@@ -970,7 +996,7 @@ void rcu_all_qs(void)
{
unsigned long flags;
- if (!raw_cpu_read(rcu_data.rcu_urgent_qs))
+ if (!READ_ONCE(*raw_cpu_ptr(&rcu_data.rcu_urgent_qs)))
return;
preempt_disable(); // For CONFIG_PREEMPT_COUNT=y kernels
/* Load rcu_urgent_qs before other flags. */
@@ -978,8 +1004,8 @@ void rcu_all_qs(void)
preempt_enable();
return;
}
- this_cpu_write(rcu_data.rcu_urgent_qs, false);
- if (unlikely(raw_cpu_read(rcu_data.rcu_need_heavy_qs))) {
+ WRITE_ONCE(*this_cpu_ptr(&rcu_data.rcu_urgent_qs), false);
+ if (unlikely(READ_ONCE(*this_cpu_ptr(&rcu_data.rcu_need_heavy_qs)))) {
local_irq_save(flags);
rcu_momentary_eqs();
local_irq_restore(flags);
@@ -999,8 +1025,8 @@ void rcu_note_context_switch(bool preempt)
/* Load rcu_urgent_qs before other flags. */
if (!smp_load_acquire(this_cpu_ptr(&rcu_data.rcu_urgent_qs)))
goto out;
- this_cpu_write(rcu_data.rcu_urgent_qs, false);
- if (unlikely(raw_cpu_read(rcu_data.rcu_need_heavy_qs)))
+ WRITE_ONCE(*this_cpu_ptr(&rcu_data.rcu_urgent_qs), false);
+ if (unlikely(READ_ONCE(*this_cpu_ptr(&rcu_data.rcu_need_heavy_qs))))
rcu_momentary_eqs();
out:
rcu_tasks_qs(current, preempt);
@@ -1320,6 +1346,41 @@ static void rcu_spawn_one_boost_kthread(struct rcu_node *rnp)
wake_up_process(t); /* get to TASK_INTERRUPTIBLE quickly. */
}
+#ifdef CONFIG_RCU_TORTURE_TEST
+
+/*
+ * Is the current task RCU priority boosted? This is used by
+ * rcutorture to check that tasks are always deboosted once then exit
+ * an RCU read-side critical section, no matter how many overlapping
+ * segments of rcu_read_lock(), preempt_disable(), local_bh_disable(),
+ * or local_irq_disable() made up that reader.
+ *
+ * The lockless accesses in rt_mutex_owner(&rnp->boost_mtx.rtmutex)
+ * are safe because tasks release ->boost_mtx when they own it, they
+ * cannot be boosted unless current->rcu_blocked_node is non-NULL,
+ * current->rcu_blocked_node is modified only by the current task,
+ * rt_mutex_owner() uses READ_ONCE() on the ->owner field, and the owner
+ * switching among other tasks cannot force an equality comparison.
+ */
+bool rcu_is_task_rcu_boosted(void)
+{
+ bool ret;
+ struct rcu_node *rnp;
+ struct task_struct *t = current;
+
+ preempt_disable(); // Stabilize ->rcu_blocked_node
+ rnp = t->rcu_blocked_node;
+ if (!rnp)
+ ret = false;
+ else
+ ret = (rt_mutex_owner(&rnp->boost_mtx.rtmutex) == t);
+ preempt_enable();
+ return ret;
+}
+EXPORT_SYMBOL_GPL(rcu_is_task_rcu_boosted);
+
+#endif // #ifdef CONFIG_RCU_TORTURE_TEST
+
#else /* #ifdef CONFIG_RCU_BOOST */
static void rcu_initiate_boost(struct rcu_node *rnp, unsigned long flags)
diff --git a/kernel/rcu/tree_stall.h b/kernel/rcu/tree_stall.h
index cf7ae51cba40..091e7850ab6e 100644
--- a/kernel/rcu/tree_stall.h
+++ b/kernel/rcu/tree_stall.h
@@ -159,7 +159,7 @@ static int __init check_cpu_stall_init(void)
early_initcall(check_cpu_stall_init);
/* If so specified via sysctl, panic, yielding cleaner stall-warning output. */
-static void panic_on_rcu_stall(void)
+static void panic_on_rcu_stall(const struct cpumask *stalled_mask)
{
static int cpu_stall;
@@ -167,7 +167,7 @@ static void panic_on_rcu_stall(void)
* Attempt to kick out the BPF scheduler if it's installed and defer
* the panic to give the system a chance to recover.
*/
- if (scx_rcu_cpu_stall())
+ if (scx_rcu_cpu_stall(stalled_mask))
return;
if (++cpu_stall < sysctl_max_rcu_stall_to_panic)
@@ -573,13 +573,13 @@ static void rcu_check_gp_kthread_starvation(void)
if (rcu_is_gp_kthread_starving(&j)) {
cpu = gpk ? task_cpu(gpk) : -1;
- pr_err("%s kthread starved for %ld jiffies! g%ld f%#x %s(%d) ->state=%#x ->cpu=%d\n",
+ pr_err("%s kthread starved for %ld jiffies! g%ld f%#x %s(%d) ->state=%c ->cpu=%d\n",
rcu_state.name, j,
(long)rcu_seq_current(&rcu_state.gp_seq),
data_race(READ_ONCE(rcu_state.gp_flags)),
gp_state_getname(rcu_state.gp_state),
data_race(READ_ONCE(rcu_state.gp_state)),
- gpk ? data_race(READ_ONCE(gpk->__state)) : ~0, cpu);
+ gpk ? task_state_to_char(gpk) : '?', cpu);
if (gpk) {
struct rcu_data *rdp = per_cpu_ptr(&rcu_data, cpu);
@@ -616,12 +616,12 @@ static void rcu_check_gp_kthread_expired_fqs_timer(void)
time_after(jiffies, jiffies_fqs + RCU_STALL_MIGHT_MIN) &&
gpk && !READ_ONCE(gpk->on_rq)) {
cpu = task_cpu(gpk);
- pr_err("%s kthread timer wakeup didn't happen for %ld jiffies! g%ld f%#x %s(%d) ->state=%#x\n",
+ pr_err("%s kthread timer wakeup didn't happen for %ld jiffies! g%ld f%#x %s(%d) ->state=%c\n",
rcu_state.name, (jiffies - jiffies_fqs),
(long)rcu_seq_current(&rcu_state.gp_seq),
data_race(READ_ONCE(rcu_state.gp_flags)), // Diagnostic read
gp_state_getname(RCU_GP_WAIT_FQS), RCU_GP_WAIT_FQS,
- data_race(READ_ONCE(gpk->__state)));
+ task_state_to_char(gpk));
pr_err("\tPossible timer handling issue on cpu=%d timer-softirq=%u\n",
cpu, kstat_softirqs_cpu(TIMER_SOFTIRQ, cpu));
}
@@ -644,6 +644,8 @@ static void print_other_cpu_stall(unsigned long gp_seq, unsigned long gps)
if (rcu_stall_is_suppressed())
return;
+ cpumask_clear(&rcu_stall_cpumask);
+
nbcon_cpu_emergency_enter();
/*
@@ -659,6 +661,7 @@ static void print_other_cpu_stall(unsigned long gp_seq, unsigned long gps)
for_each_leaf_node_possible_cpu(rnp, cpu)
if (rnp->qsmask & leaf_node_cpu_bit(rnp, cpu)) {
print_cpu_stall_info(cpu);
+ cpumask_set_cpu(cpu, &rcu_stall_cpumask);
ndetected++;
}
}
@@ -700,7 +703,7 @@ static void print_other_cpu_stall(unsigned long gp_seq, unsigned long gps)
nbcon_cpu_emergency_exit();
- panic_on_rcu_stall();
+ panic_on_rcu_stall(&rcu_stall_cpumask);
rcu_force_quiescent_state(); /* Kick them all. */
}
@@ -753,7 +756,9 @@ static void print_cpu_stall(unsigned long gp_seq, unsigned long gps)
nbcon_cpu_emergency_exit();
- panic_on_rcu_stall();
+ cpumask_clear(&rcu_stall_cpumask);
+ cpumask_set_cpu(smp_processor_id(), &rcu_stall_cpumask);
+ panic_on_rcu_stall(&rcu_stall_cpumask);
/*
* Attempt to revive the RCU machinery by forcing a context switch.
@@ -927,20 +932,13 @@ bool rcu_check_boost_fail(unsigned long gp_state, int *cpup)
}
EXPORT_SYMBOL_GPL(rcu_check_boost_fail);
-/*
- * Show the state of the grace-period kthreads.
- */
-void show_rcu_gp_kthreads(void)
+static noinline_for_stack void show_rcu_state(void)
{
- unsigned long cbs = 0;
- int cpu;
unsigned long j;
unsigned long ja;
unsigned long jr;
unsigned long js;
unsigned long jw;
- struct rcu_data *rdp;
- struct rcu_node *rnp;
struct task_struct *t = READ_ONCE(rcu_state.gp_kthread);
j = jiffies;
@@ -948,30 +946,48 @@ void show_rcu_gp_kthreads(void)
jr = j - data_race(READ_ONCE(rcu_state.gp_req_activity));
js = j - data_race(READ_ONCE(rcu_state.gp_start));
jw = j - data_race(READ_ONCE(rcu_state.gp_wake_time));
- pr_info("%s: wait state: %s(%d) ->state: %#x ->rt_priority %u delta ->gp_start %lu ->gp_activity %lu ->gp_req_activity %lu ->gp_wake_time %lu ->gp_wake_seq %ld ->gp_seq %ld ->gp_seq_needed %ld ->gp_max %lu ->gp_flags %#x\n",
+ pr_info("%s: wait state: %s(%d) ->state: %c ->rt_priority %u delta ->gp_start %lu ->gp_activity %lu ->gp_req_activity %lu ->gp_wake_time %lu ->gp_wake_seq %ld ->gp_seq %ld ->gp_seq_needed %ld ->gp_max %lu ->gp_flags %#x\n",
rcu_state.name, gp_state_getname(rcu_state.gp_state),
data_race(READ_ONCE(rcu_state.gp_state)),
- t ? data_race(READ_ONCE(t->__state)) : 0x1ffff, t ? t->rt_priority : 0xffU,
+ t ? task_state_to_char(t) : '?', t ? t->rt_priority : 0xffU,
js, ja, jr, jw, (long)data_race(READ_ONCE(rcu_state.gp_wake_seq)),
(long)data_race(READ_ONCE(rcu_state.gp_seq)),
(long)data_race(READ_ONCE(rcu_get_root()->gp_seq_needed)),
data_race(READ_ONCE(rcu_state.gp_max)),
data_race(READ_ONCE(rcu_state.gp_flags)));
+}
+
+static noinline_for_stack void show_rcu_node(struct rcu_node *rnp)
+{
+ pr_info("\trcu_node %d:%d ->gp_seq %ld ->gp_seq_needed %ld ->qsmask %#lx %c%c%c%c ->n_boosts %ld\n",
+ rnp->grplo, rnp->grphi,
+ (long)data_race(READ_ONCE(rnp->gp_seq)),
+ (long)data_race(READ_ONCE(rnp->gp_seq_needed)),
+ data_race(READ_ONCE(rnp->qsmask)),
+ ".b"[!!data_race(READ_ONCE(rnp->boost_kthread_task))],
+ ".B"[!!data_race(READ_ONCE(rnp->boost_tasks))],
+ ".E"[!!data_race(READ_ONCE(rnp->exp_tasks))],
+ ".G"[!!data_race(READ_ONCE(rnp->gp_tasks))],
+ data_race(READ_ONCE(rnp->n_boosts)));
+}
+
+/*
+ * Show the state of the grace-period kthreads.
+ */
+void show_rcu_gp_kthreads(void)
+{
+ unsigned long cbs = 0;
+ int cpu;
+ struct rcu_data *rdp;
+ struct rcu_node *rnp;
+
+ show_rcu_state();
rcu_for_each_node_breadth_first(rnp) {
if (ULONG_CMP_GE(READ_ONCE(rcu_state.gp_seq), READ_ONCE(rnp->gp_seq_needed)) &&
!data_race(READ_ONCE(rnp->qsmask)) && !data_race(READ_ONCE(rnp->boost_tasks)) &&
!data_race(READ_ONCE(rnp->exp_tasks)) && !data_race(READ_ONCE(rnp->gp_tasks)))
continue;
- pr_info("\trcu_node %d:%d ->gp_seq %ld ->gp_seq_needed %ld ->qsmask %#lx %c%c%c%c ->n_boosts %ld\n",
- rnp->grplo, rnp->grphi,
- (long)data_race(READ_ONCE(rnp->gp_seq)),
- (long)data_race(READ_ONCE(rnp->gp_seq_needed)),
- data_race(READ_ONCE(rnp->qsmask)),
- ".b"[!!data_race(READ_ONCE(rnp->boost_kthread_task))],
- ".B"[!!data_race(READ_ONCE(rnp->boost_tasks))],
- ".E"[!!data_race(READ_ONCE(rnp->exp_tasks))],
- ".G"[!!data_race(READ_ONCE(rnp->gp_tasks))],
- data_race(READ_ONCE(rnp->n_boosts)));
+ show_rcu_node(rnp);
if (!rcu_is_leaf_node(rnp))
continue;
for_each_leaf_node_possible_cpu(rnp, cpu) {
@@ -998,8 +1014,7 @@ EXPORT_SYMBOL_GPL(show_rcu_gp_kthreads);
* This function checks for grace-period requests that fail to motivate
* RCU to come out of its idle mode.
*/
-static void rcu_check_gp_start_stall(struct rcu_node *rnp, struct rcu_data *rdp,
- const unsigned long gpssdelay)
+static void rcu_check_gp_start_stall(struct rcu_node *rnp, const unsigned long gpssdelay)
{
unsigned long flags;
unsigned long j;
@@ -1074,7 +1089,7 @@ void rcu_fwd_progress_check(unsigned long j)
__func__, jiffies - data_race(READ_ONCE(rcu_state.gp_end)));
preempt_disable();
rdp = this_cpu_ptr(&rcu_data);
- rcu_check_gp_start_stall(rdp->mynode, rdp, j);
+ rcu_check_gp_start_stall(rdp->mynode, j);
preempt_enable();
}
for_each_possible_cpu(cpu) {
diff --git a/kernel/reboot.c b/kernel/reboot.c
index 695c33e75efd..d177d89fcc33 100644
--- a/kernel/reboot.c
+++ b/kernel/reboot.c
@@ -13,7 +13,9 @@
#include <linux/kexec.h>
#include <linux/kmod.h>
#include <linux/kmsg_dump.h>
+#include <linux/rcupdate.h>
#include <linux/reboot.h>
+#include <linux/sched/signal.h>
#include <linux/suspend.h>
#include <linux/syscalls.h>
#include <linux/syscore_ops.h>
@@ -24,8 +26,7 @@
*/
static int C_A_D = 1;
-struct pid *cad_pid;
-EXPORT_SYMBOL(cad_pid);
+struct pid __rcu *cad_pid;
#if defined(CONFIG_ARM)
#define DEFAULT_REBOOT_MODE = REBOOT_HARD
@@ -1366,6 +1367,39 @@ static struct attribute *reboot_attrs[] = {
};
#ifdef CONFIG_SYSCTL
+static int proc_do_cad_pid(const struct ctl_table *table, int write, void *buffer,
+ size_t *lenp, loff_t *ppos)
+{
+ struct ctl_table tmp_table = *table;
+ struct pid *new_pid;
+ struct pid *old_pid;
+ pid_t tmp_pid;
+ int r;
+
+ rcu_read_lock();
+ tmp_pid = pid_vnr(rcu_dereference(cad_pid));
+ rcu_read_unlock();
+
+ tmp_table.data = &tmp_pid;
+
+ r = proc_dointvec(&tmp_table, write, buffer, lenp, ppos);
+ if (r || !write)
+ return r;
+
+ new_pid = find_get_pid(tmp_pid);
+ if (!new_pid)
+ return -ESRCH;
+
+ old_pid = unrcu_pointer(xchg(&cad_pid, RCU_INITIALIZER(new_pid)));
+ /*
+ * Wait for cad_pid readers before put_pid(). We cannot use
+ * call_rcu() here because free_pid() already owns pid->rcu.
+ */
+ synchronize_rcu();
+ put_pid(old_pid);
+ return 0;
+}
+
static const struct ctl_table kern_reboot_table[] = {
{
.procname = "poweroff_cmd",
@@ -1381,6 +1415,12 @@ static const struct ctl_table kern_reboot_table[] = {
.mode = 0644,
.proc_handler = proc_dointvec,
},
+ {
+ .procname = "cad_pid",
+ .maxlen = sizeof(int),
+ .mode = 0600,
+ .proc_handler = proc_do_cad_pid,
+ },
};
static void __init kernel_reboot_sysctls_init(void)
diff --git a/kernel/resource.c b/kernel/resource.c
index d02a53fb95d8..e60539a55541 100644
--- a/kernel/resource.c
+++ b/kernel/resource.c
@@ -1238,7 +1238,7 @@ reserve_region_with_split(struct resource *root, resource_size_t start,
*
* Returns alignment on success, 0 (invalid alignment) on failure.
*/
-resource_size_t resource_alignment(struct resource *res)
+resource_size_t resource_alignment(const struct resource *res)
{
switch (res->flags & (IORESOURCE_SIZEALIGN | IORESOURCE_STARTALIGN)) {
case IORESOURCE_SIZEALIGN:
@@ -1859,7 +1859,7 @@ int iomem_map_sanity_check(resource_size_t addr, unsigned long size)
if (p->flags & IORESOURCE_BUSY)
continue;
- pr_warn("resource sanity check: requesting [mem %pa-%pa], which spans more than %s %pR\n",
+ pr_debug("resource sanity check: requesting [mem %pa-%pa], which spans more than %s %pR\n",
&addr, &end, p->name, p);
err = -1;
break;
diff --git a/kernel/scftorture.c b/kernel/scftorture.c
index 327c315f411c..603b2dba1536 100644
--- a/kernel/scftorture.c
+++ b/kernel/scftorture.c
@@ -193,6 +193,7 @@ static void scf_torture_stats_print(void)
scfs.n_single += scf_stats_p[i].n_single;
scfs.n_single_ofl += scf_stats_p[i].n_single_ofl;
scfs.n_single_rpc += scf_stats_p[i].n_single_rpc;
+ scfs.n_single_rpc_ofl += scf_stats_p[i].n_single_rpc_ofl;
scfs.n_single_wait += scf_stats_p[i].n_single_wait;
scfs.n_single_wait_ofl += scf_stats_p[i].n_single_wait_ofl;
scfs.n_many += scf_stats_p[i].n_many;
@@ -348,6 +349,8 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra
int ret = 0;
struct scf_check *scfcp = NULL;
struct scf_selector *scfsp = scf_sel_rand(trsp);
+ bool is_single = (scfsp->scfs_prim == SCF_PRIM_SINGLE ||
+ scfsp->scfs_prim == SCF_PRIM_SINGLE_RPC);
if (scfsp->scfs_prim == SCF_PRIM_SINGLE || scfsp->scfs_wait) {
scfcp = kmalloc_obj(*scfcp, GFP_ATOMIC);
@@ -364,8 +367,6 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra
}
if (use_cpus_read_lock)
cpus_read_lock();
- else
- preempt_disable();
switch (scfsp->scfs_prim) {
case SCF_PRIM_RESCHED:
if (IS_BUILTIN(CONFIG_SCF_TORTURE_TEST)) {
@@ -411,13 +412,10 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra
if (!ret) {
if (use_cpus_read_lock)
cpus_read_unlock();
- else
- preempt_enable();
+
wait_for_completion(&scfcp->scfc_completion);
if (use_cpus_read_lock)
cpus_read_lock();
- else
- preempt_disable();
} else {
scfp->n_single_rpc_ofl++;
scf_add_to_free_list(scfcp);
@@ -452,7 +450,7 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra
scfcp->scfc_out = true;
}
if (scfcp && scfsp->scfs_wait) {
- if (WARN_ON_ONCE((num_online_cpus() > 1 || scfsp->scfs_prim == SCF_PRIM_SINGLE) &&
+ if (WARN_ON_ONCE(((use_cpus_read_lock && num_online_cpus() > 1) || is_single) &&
!scfcp->scfc_out)) {
pr_warn("%s: Memory-ordering failure, scfs_prim: %d.\n", __func__, scfsp->scfs_prim);
atomic_inc(&n_mb_out_errs); // Leak rather than trash!
@@ -463,8 +461,6 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra
}
if (use_cpus_read_lock)
cpus_read_unlock();
- else
- preempt_enable();
if (allocfail)
schedule_timeout_idle((1 + longwait) * HZ); // Let no-wait handlers complete.
else if (!(torture_random(trsp) & 0xfff))
@@ -496,7 +492,7 @@ static int scftorture_invoker(void *arg)
"%s: Wanted CPU %d, running on %d, nr_cpu_ids = %d\n",
__func__, scfp->cpu, curcpu, nr_cpu_ids);
- if (!atomic_dec_return(&n_started))
+ if (atomic_dec_return(&n_started))
while (atomic_read_acquire(&n_started)) {
if (torture_must_stop()) {
VERBOSE_SCFTORTOUT("scftorture_invoker %d ended before starting", scfp->cpu);
diff --git a/kernel/sched/build_policy.c b/kernel/sched/build_policy.c
index d74b54f81992..2a828725a7f9 100644
--- a/kernel/sched/build_policy.c
+++ b/kernel/sched/build_policy.c
@@ -66,10 +66,13 @@
# include "ext/cid.h"
# include "ext/arena.h"
# include "ext/idle.h"
+# include "ext/sub.h"
+# include "ext/inlines.h"
# include "ext/ext.c"
# include "ext/cid.c"
# include "ext/arena.c"
# include "ext/idle.c"
+# include "ext/sub.c"
#endif
#include "syscalls.c"
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 3cc6fb1d2054..1fe40de6ebe3 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -40,7 +40,6 @@
#include <linux/sched/rseq_api.h>
#include <linux/sched/rt.h>
-#include <linux/blkdev.h>
#include <linux/context_tracking.h>
#include <linux/cpuset.h>
#include <linux/delayacct.h>
@@ -443,6 +442,17 @@ static void __sched_core_flip(bool enabled)
sched_core_lock(cpu, &flags);
+ /*
+ * A core-wide selection may have the shared rq lock temporarily
+ * released by a lock-dropping ->pick_task(). Flipping would
+ * rebind rq_lockp() under it. Wait it out.
+ */
+ while (cpu_rq(cpu)->core->core_pick_in_flight) {
+ sched_core_unlock(cpu, &flags);
+ cpu_relax();
+ sched_core_lock(cpu, &flags);
+ }
+
for_each_cpu(t, smt_mask)
cpu_rq(t)->core_enabled = enabled;
@@ -3341,6 +3351,8 @@ void relax_compatible_cpus_allowed_ptr(struct task_struct *p)
void set_task_cpu(struct task_struct *p, unsigned int new_cpu)
{
unsigned int state = READ_ONCE(p->__state);
+ bool proxy_migrated = sched_proxy_exec() && p->is_blocked &&
+ task_cpu(p) != p->wake_cpu;
/*
* We should never call set_task_cpu() on a blocked task,
@@ -3376,7 +3388,12 @@ void set_task_cpu(struct task_struct *p, unsigned int new_cpu)
*/
WARN_ON_ONCE(!cpu_online(new_cpu));
- WARN_ON_ONCE(is_migration_disabled(p));
+ /*
+ * Proxy execution can move a blocked task's scheduling context to any
+ * CPU without moving its migration-disabled execution context. The
+ * wakeup path will return the task to a CPU where it can execute.
+ */
+ WARN_ON_ONCE(is_migration_disabled(p) && !proxy_migrated);
trace_sched_migrate_task(p, new_cpu);
@@ -3732,11 +3749,17 @@ static inline void ttwu_do_wakeup(struct task_struct *p)
void update_rq_avg_idle(struct rq *rq)
{
- u64 delta = rq_clock(rq) - rq->idle_stamp;
- u64 max = 2*rq->max_idle_balance_cost;
+ u64 idle_stamp = rq->idle_stamp;
+ u64 delta, max;
+
+ if (!idle_stamp)
+ return;
+
+ delta = rq_clock(rq) - idle_stamp;
update_avg(&rq->avg_idle, delta);
+ max = 2 * rq->max_idle_balance_cost;
if (rq->avg_idle > max)
rq->avg_idle = max;
rq->idle_stamp = 0;
@@ -4638,7 +4661,7 @@ void set_numabalancing_state(bool enabled)
__set_numabalancing_state(enabled);
}
-#ifdef CONFIG_PROC_SYSCTL
+#ifdef CONFIG_SYSCTL
static void reset_memory_tiering(void)
{
struct pglist_data *pgdat;
@@ -4674,7 +4697,7 @@ static int sysctl_numa_balancing(const struct ctl_table *table, int write,
}
return err;
}
-#endif /* CONFIG_PROC_SYSCTL */
+#endif /* CONFIG_SYSCTL */
#endif /* CONFIG_NUMA_BALANCING */
#ifdef CONFIG_SCHEDSTATS
@@ -4718,7 +4741,7 @@ out:
}
__setup("schedstats=", setup_schedstats);
-#ifdef CONFIG_PROC_SYSCTL
+#ifdef CONFIG_SYSCTL
static int sysctl_schedstats(const struct ctl_table *table, int write, void *buffer,
size_t *lenp, loff_t *ppos)
{
@@ -4738,7 +4761,7 @@ static int sysctl_schedstats(const struct ctl_table *table, int write, void *buf
set_schedstats(state);
return err;
}
-#endif /* CONFIG_PROC_SYSCTL */
+#endif /* CONFIG_SYSCTL */
#endif /* CONFIG_SCHEDSTATS */
#ifdef CONFIG_SYSCTL
@@ -5368,6 +5391,12 @@ static struct rq *finish_task_switch(struct task_struct *prev)
*/
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
@@ -5651,11 +5680,8 @@ 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);
}
@@ -5757,8 +5783,8 @@ void sched_tick(void)
{
int cpu = smp_processor_id();
struct rq *rq = cpu_rq(cpu);
- /* accounting goes to the donor task */
- struct task_struct *donor;
+ /* scheduler accounting goes to the donor task */
+ struct task_struct *curr, *donor;
struct rq_flags rf;
unsigned long hw_pressure;
u64 resched_latency;
@@ -5769,9 +5795,10 @@ void sched_tick(void)
sched_clock_tick();
rq_lock(rq, &rf);
+ curr = rq->curr;
donor = rq->donor;
- psi_account_irqtime(rq, donor, NULL);
+ psi_account_irqtime(rq, curr, NULL);
update_rq_clock(rq);
hw_pressure = arch_scale_hw_pressure(cpu_of(rq));
@@ -5794,8 +5821,8 @@ void sched_tick(void)
perf_event_task_tick();
- if (donor->flags & PF_WQ_WORKER)
- wq_worker_tick(donor);
+ if (curr->flags & PF_WQ_WORKER)
+ wq_worker_tick(curr);
if (!scx_switched_all()) {
rq->idle_balance = idle_cpu(cpu);
@@ -5967,8 +5994,13 @@ void preempt_count_add(int val)
#ifdef CONFIG_DEBUG_PREEMPT
/*
* Underflow?
+ *
+ * Cannot detect underflow based on the current preempt_count() value
+ * if using HAS_SEPARATE_PREEMPT_RESCHED_BITS because preempt count takes all 32
+ * bits.
*/
- if (DEBUG_LOCKS_WARN_ON((preempt_count() < 0)))
+ if (!IS_ENABLED(CONFIG_HAS_SEPARATE_PREEMPT_RESCHED_BITS) &&
+ DEBUG_LOCKS_WARN_ON((preempt_count() < 0)))
return;
#endif
__preempt_count_add(val);
@@ -6000,7 +6032,10 @@ void preempt_count_sub(int val)
/*
* Underflow?
*/
- if (DEBUG_LOCKS_WARN_ON(val > preempt_count()))
+ unsigned int uval = val;
+ unsigned int pc = preempt_count();
+
+ if (DEBUG_LOCKS_WARN_ON(pc - uval > pc))
return;
/*
* Is the spinlock portion underflowing?
@@ -6217,7 +6252,7 @@ pick_next_task(struct rq *rq, struct rq_flags *rf)
unsigned long cookie;
int i, cpu, occ = 0;
struct rq *rq_i;
- bool need_sync;
+ bool need_sync = false;
if (!sched_core_enabled(rq))
return __pick_next_task(rq, rf);
@@ -6236,6 +6271,8 @@ pick_next_task(struct rq *rq, struct rq_flags *rf)
return __pick_next_task(rq, rf);
}
+ rq->core->core_pick_in_flight++;
+
/*
* If there were no {en,de}queues since we picked (IOW, the task
* pointers are all still valid), and we haven't scheduled the last
@@ -6260,7 +6297,9 @@ pick_next_task(struct rq *rq, struct rq_flags *rf)
prev_balance(rq, rf);
smt_mask = cpu_smt_mask(cpu);
- need_sync = !!rq->core->core_cookie;
+
+restart:
+ need_sync |= !!rq->core->core_cookie;
/* reset state */
rq->core->core_cookie = 0UL;
@@ -6295,10 +6334,15 @@ pick_next_task(struct rq *rq, struct rq_flags *rf)
* and there are no cookied tasks running on siblings.
*/
if (!need_sync) {
-restart_single:
next = pick_task(rq, rf);
- if (unlikely(next == RETRY_TASK))
- goto restart_single;
+ if (unlikely(next == RETRY_TASK)) {
+ /* rq lock may have been dropped, clocks invalidated */
+ core_clock_updated = false;
+ if (!(rq->clock_update_flags & RQCF_UPDATED))
+ update_rq_clock(rq);
+ goto restart;
+ }
+
if (!next->core_cookie) {
rq->core_pick = NULL;
rq->core_dl_server = NULL;
@@ -6318,7 +6362,6 @@ restart_single:
*
* Tie-break prio towards the current CPU
*/
-restart_multi:
max = NULL;
for_each_cpu_wrap(i, smt_mask, cpu) {
rq_i = cpu_rq(i);
@@ -6332,8 +6375,13 @@ restart_multi:
update_rq_clock(rq_i);
p = pick_task(rq_i, rf);
- if (unlikely(p == RETRY_TASK))
- goto restart_multi;
+ if (unlikely(p == RETRY_TASK)) {
+ /* rq lock may have been dropped, clocks invalidated */
+ core_clock_updated = false;
+ if (!(rq->clock_update_flags & RQCF_UPDATED))
+ update_rq_clock(rq);
+ goto restart;
+ }
rq_i->core_pick = p;
rq_i->core_dl_server = rq_i->dl_server;
@@ -6439,6 +6487,7 @@ restart_multi:
}
out_set_next:
+ rq->core->core_pick_in_flight--;
put_prev_set_next_task(rq, rq->donor, next);
if (rq->core->core_forceidle_count && next == rq->idle)
queue_core_balance(rq);
@@ -6634,6 +6683,13 @@ static void sched_core_cpu_deactivate(unsigned int cpu)
core_rq->core_forceidle_occupation = rq->core_forceidle_occupation;
/*
+ * A stale leftover would bias the count forever if this CPU later
+ * returns as its own leader. Move, don't copy.
+ */
+ core_rq->core_pick_in_flight = rq->core_pick_in_flight;
+ rq->core_pick_in_flight = 0;
+
+ /*
* Accounting edge for forced idle is handled in pick_next_task().
* Don't need another one here, since the hotplug thread shouldn't
* have a cookie.
@@ -7290,12 +7346,10 @@ static inline void sched_submit_work(struct task_struct *tsk)
static void sched_update_worker(struct task_struct *tsk)
{
- if (tsk->flags & (PF_WQ_WORKER | PF_IO_WORKER | PF_BLOCK_TS)) {
- if (tsk->flags & PF_BLOCK_TS)
- blk_plug_invalidate_ts(tsk);
+ if (tsk->flags & (PF_WQ_WORKER | PF_IO_WORKER)) {
if (tsk->flags & PF_WQ_WORKER)
wq_worker_running(tsk);
- else if (tsk->flags & PF_IO_WORKER)
+ else
io_wq_worker_running(tsk);
}
}
@@ -7597,6 +7651,17 @@ void rt_mutex_pre_schedule(void)
sched_submit_work(current);
}
+/*
+ * Used within the futex syscall context, skips sched_submit_work() because none
+ * its work will be done. Asserts ensure that it is indeed the case.
+ */
+void rt_mutex_futex_pre_schedule(void)
+{
+ lockdep_assert(!(current->flags & (PF_WQ_WORKER | PF_IO_WORKER)));
+ lockdep_assert(!current->plug);
+ lockdep_assert(!fetch_and_set(current->sched_rt_mutex, 1));
+}
+
void rt_mutex_schedule(void)
{
lockdep_assert(current->sched_rt_mutex);
@@ -7609,6 +7674,11 @@ void rt_mutex_post_schedule(void)
lockdep_assert(fetch_and_set(current->sched_rt_mutex, 0));
}
+void rt_mutex_futex_post_schedule(void)
+{
+ lockdep_assert(fetch_and_set(current->sched_rt_mutex, 0));
+}
+
/*
* rt_mutex_setprio - set the current priority of a task
* @p: task to boost
@@ -9049,6 +9119,7 @@ void __init sched_init(void)
rq->core_forceidle_count = 0;
rq->core_forceidle_occupation = 0;
rq->core_forceidle_start = 0;
+ rq->core_pick_in_flight = 0;
rq->core_cookie = 0UL;
#endif
@@ -9195,7 +9266,7 @@ void __might_resched(const char *file, int line, unsigned int offsets)
}
EXPORT_SYMBOL(__might_resched);
-void __cant_sleep(const char *file, int line, int preempt_offset)
+void __cant_sleep(const char *file, int line)
{
static unsigned long prev_jiffy;
@@ -9205,7 +9276,7 @@ void __cant_sleep(const char *file, int line, int preempt_offset)
if (!IS_ENABLED(CONFIG_PREEMPT_COUNT))
return;
- if (preempt_count() > preempt_offset)
+ if (preempt_count())
return;
if (time_before(jiffies, prev_jiffy + HZ) && prev_jiffy)
@@ -9237,7 +9308,7 @@ void __cant_migrate(const char *file, int line)
if (!IS_ENABLED(CONFIG_PREEMPT_COUNT))
return;
- if (preempt_count() > 0)
+ if (preempt_count())
return;
if (time_before(jiffies, prev_jiffy + HZ) && prev_jiffy)
diff --git a/kernel/sched/cpufreq_schedutil.c b/kernel/sched/cpufreq_schedutil.c
index ae9fd211cec1..49ccd6f1c185 100644
--- a/kernel/sched/cpufreq_schedutil.c
+++ b/kernel/sched/cpufreq_schedutil.c
@@ -50,6 +50,7 @@ struct sugov_cpu {
unsigned long util;
unsigned long bw_min;
+ unsigned long bw_max;
/* The field below is for single-CPU policies only: */
#ifdef CONFIG_NO_HZ_COMMON
@@ -61,6 +62,17 @@ static DEFINE_PER_CPU(struct sugov_cpu, sugov_cpu);
/************************ Governor internals ***********************/
+static void sugov_update_rate_limit_us(struct sugov_policy *sg_policy)
+{
+ /*
+ * Cast rate_limit_us before multiplication to force 64-bit arithmetic.
+ * Otherwise, on 32-bit platforms, both operands are converted to
+ * 32-bit unsigned long and the multiplication may overflow.
+ */
+ sg_policy->freq_update_delay_ns =
+ (s64)sg_policy->tunables->rate_limit_us * NSEC_PER_USEC;
+}
+
static bool sugov_should_update_freq(struct sugov_policy *sg_policy, u64 time)
{
s64 delta_ns;
@@ -232,6 +244,7 @@ static void sugov_get_util(struct sugov_cpu *sg_cpu, unsigned long boost)
util = effective_cpu_util(sg_cpu->cpu, util, &min, &max);
util = max(util, boost);
sg_cpu->bw_min = min;
+ sg_cpu->bw_max = max;
sg_cpu->util = sugov_effective_cpu_perf(sg_cpu->cpu, util, min, max);
}
@@ -314,7 +327,7 @@ static void sugov_iowait_boost(struct sugov_cpu *sg_cpu, u64 time,
* A CPU running a task which woken up after an IO operation can have its
* utilization boosted to speed up the completion of those IO operations.
* The IO boost value is increased each time a task wakes up from IO, in
- * sugov_iowait_apply(), and it's instead decreased by this function,
+ * sugov_iowait_boost(), and it's instead decreased by this function,
* each time an increase has not been requested (!iowait_boost_pending).
*
* A CPU which also appears to have been idle for at least one tick has also
@@ -484,8 +497,9 @@ static void sugov_update_single_perf(struct update_util_data *hook, u64 time,
sg_cpu->util = prev_util;
cpufreq_driver_adjust_perf(sg_policy->policy, sg_cpu->bw_min,
- sg_cpu->util, max_cap);
+ sg_cpu->util, sg_cpu->bw_max, max_cap);
+ sg_policy->need_freq_update = false;
sg_policy->last_freq_update_time = time;
}
@@ -589,7 +603,7 @@ static ssize_t rate_limit_us_show(struct gov_attr_set *attr_set, char *buf)
{
struct sugov_tunables *tunables = to_sugov_tunables(attr_set);
- return sprintf(buf, "%u\n", tunables->rate_limit_us);
+ return sysfs_emit(buf, "%u\n", tunables->rate_limit_us);
}
static ssize_t
@@ -605,7 +619,7 @@ rate_limit_us_store(struct gov_attr_set *attr_set, const char *buf, size_t count
tunables->rate_limit_us = rate_limit_us;
list_for_each_entry(sg_policy, &attr_set->policy_list, tunables_hook)
- sg_policy->freq_update_delay_ns = rate_limit_us * NSEC_PER_USEC;
+ sugov_update_rate_limit_us(sg_policy);
return count;
}
@@ -847,7 +861,7 @@ static int sugov_start(struct cpufreq_policy *policy)
void (*uu)(struct update_util_data *data, u64 time, unsigned int flags);
unsigned int cpu;
- sg_policy->freq_update_delay_ns = sg_policy->tunables->rate_limit_us * NSEC_PER_USEC;
+ sugov_update_rate_limit_us(sg_policy);
sg_policy->last_freq_update_time = 0;
sg_policy->next_freq = 0;
sg_policy->work_in_progress = false;
@@ -869,8 +883,19 @@ static int sugov_start(struct cpufreq_policy *policy)
memset(sg_cpu, 0, sizeof(*sg_cpu));
sg_cpu->cpu = cpu;
sg_cpu->sg_policy = sg_policy;
+ }
+
+ /*
+ * Publish the hooks only after all per-CPU data is initialized, so a
+ * shared policy's sugov_update_shared() never reads an uninitialized
+ * sibling sugov_cpu.
+ */
+ for_each_cpu(cpu, policy->cpus) {
+ struct sugov_cpu *sg_cpu = &per_cpu(sugov_cpu, cpu);
+
cpufreq_add_update_util_hook(cpu, &sg_cpu->update_util, uu);
}
+
return 0;
}
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index 0f858b98c9aa..0663c00c41c0 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -1017,7 +1017,8 @@ static void update_dl_entity(struct sched_dl_entity *dl_se)
if (dl_time_before(dl_se->deadline, rq_clock(rq)) ||
dl_entity_overflow(dl_se, rq_clock(rq))) {
- if (unlikely((!dl_is_implicit(dl_se) || dl_se->dl_defer) &&
+ if (unlikely((!dl_is_implicit(dl_se) ||
+ (dl_se->dl_defer && dl_se->dl_defer_running)) &&
!dl_time_before(dl_se->deadline, rq_clock(rq)) &&
!is_dl_boosted(dl_se))) {
update_dl_revised_wakeup(dl_se, rq);
@@ -2733,15 +2734,17 @@ static int balance_dl(struct rq *rq, struct rq_flags *rf)
*/
static void wakeup_preempt_dl(struct rq *rq, struct task_struct *p, int flags)
{
+ struct task_struct *donor = rq->donor;
/*
* Can only get preempted by stop-class, and those should be
* few and short lived, doesn't really make sense to push
* anything away for that.
*/
- if (p->sched_class != &dl_sched_class)
+ if (p->sched_class != &dl_sched_class ||
+ donor->sched_class != &dl_sched_class)
return;
- if (dl_entity_preempt(&p->dl, &rq->donor->dl)) {
+ if (dl_entity_preempt(&p->dl, &donor->dl)) {
resched_curr(rq);
return;
}
@@ -3025,8 +3028,8 @@ static struct task_struct *pick_next_pushable_dl_task(struct rq *rq)
next_node = rb_first_cached(&rq->dl.pushable_dl_tasks_root);
while (next_node) {
i = __node_2_pdl(next_node);
- /* make sure task isn't on_cpu (possible with proxy-exec) */
- if (!task_on_cpu(rq, i)) {
+ /* skip tasks that cannot be migrated */
+ if (!task_on_cpu(rq, i) && !is_migration_disabled(i)) {
p = i;
break;
}
diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c
index 40584b27ea0c..72236db67983 100644
--- a/kernel/sched/debug.c
+++ b/kernel/sched/debug.c
@@ -633,6 +633,82 @@ 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;
@@ -686,6 +762,10 @@ 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();
@@ -895,10 +975,11 @@ 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 %9Ld.%06ld %c %9Ld.%06ld %c %9Ld.%06ld %9Ld.%06ld %9Ld %5d ",
+ SEQ_printf(m, " %15s %5d %10ld %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(cfs_rq_of(&p->se), &p->se) ? 'E' : 'N',
+ entity_eligible(&rq->cfs, &p->se) ? 'E' : 'N',
SPLIT_NS(p->se.deadline),
p->se.custom_slice ? 'S' : ' ',
SPLIT_NS(p->se.slice),
@@ -927,7 +1008,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 vruntime eligible "
+ SEQ_printf(m, " S task PID weight vruntime eligible "
"deadline slice sum-exec switches "
"prio wait-time sum-sleep sum-block"
#ifdef CONFIG_NUMA_BALANCING
@@ -1035,6 +1116,8 @@ 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",
@@ -1359,7 +1442,6 @@ void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns,
P_SCHEDSTAT(wait_count);
PN_SCHEDSTAT(iowait_sum);
P_SCHEDSTAT(iowait_count);
- P_SCHEDSTAT(nr_migrations_cold);
P_SCHEDSTAT(nr_failed_migrations_affine);
P_SCHEDSTAT(nr_failed_migrations_running);
P_SCHEDSTAT(nr_failed_migrations_hot);
@@ -1371,8 +1453,6 @@ void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns,
P_SCHEDSTAT(nr_wakeups_remote);
P_SCHEDSTAT(nr_wakeups_affine);
P_SCHEDSTAT(nr_wakeups_affine_attempts);
- P_SCHEDSTAT(nr_wakeups_passive);
- P_SCHEDSTAT(nr_wakeups_idle);
avg_atom = p->se.sum_exec_runtime;
if (nr_switches)
diff --git a/kernel/sched/ext/arena.c b/kernel/sched/ext/arena.c
index 5783694ec21d..f7a9f633f435 100644
--- a/kernel/sched/ext/arena.c
+++ b/kernel/sched/ext/arena.c
@@ -6,8 +6,7 @@
*
* Each chunk added to @sch->arena_pool comes from one
* bpf_arena_alloc_pages_sleepable() call and is registered at the
- * kernel-side mapping address. Callers translate to the BPF-arena form
- * themselves if needed.
+ * kernel-side mapping address.
*
* Allocations grow the pool on demand. Underlying arena pages are released
* when the arena map itself is torn down.
@@ -70,8 +69,6 @@ void scx_arena_pool_destroy(struct scx_sched *sch)
*/
static int scx_arena_grow(struct scx_sched *sch, u32 page_cnt)
{
- u64 kern_vm_start;
- u32 uaddr32;
void *p;
int ret;
@@ -83,15 +80,8 @@ static int scx_arena_grow(struct scx_sched *sch, u32 page_cnt)
if (!p)
return -ENOMEM;
- uaddr32 = (u32)(unsigned long)p;
- /* arena.o, which defines these, is built only on MMU && 64BIT */
-#if defined(CONFIG_MMU) && defined(CONFIG_64BIT)
- kern_vm_start = bpf_arena_map_kern_vm_start(sch->arena_map);
-#else
- kern_vm_start = 0;
-#endif
-
- ret = gen_pool_add(sch->arena_pool, kern_vm_start + uaddr32,
+ ret = gen_pool_add(sch->arena_pool,
+ (unsigned long)scx_arena_to_kaddr(sch, p),
page_cnt * PAGE_SIZE, NUMA_NO_NODE);
if (ret) {
bpf_arena_free_pages_non_sleepable(sch->arena_map, p, page_cnt);
diff --git a/kernel/sched/ext/cid.c b/kernel/sched/ext/cid.c
index af83084ec740..4b08866d75f6 100644
--- a/kernel/sched/ext/cid.c
+++ b/kernel/sched/ext/cid.c
@@ -11,20 +11,26 @@
#include "cid.h"
/*
- * cid tables.
- *
- * Pointers are published once on first enable and never revoked. The default
- * mapping is populated before ops.init() runs; scx_bpf_cid_override() commits
- * before it returns. As long as the BPF scheduler only uses the tables from
- * those points onward, it sees a consistent view.
+ * cid tables. The cid kfuncs are available whether the root scheduler is
+ * cid-form or cpu-form, the latter to allow gradual migration to cids, so every
+ * root builds a default mapping. Each root enable allocates a fresh set, builds
+ * it privately and publishes the __rcu globals below once the layout is final.
+ * Root disable unpublishes and RCU-frees the set. kfuncs may run before the
+ * tables are published and must check for NULL.
*/
-s16 *scx_cid_to_cpu_tbl;
-s16 *scx_cpu_to_cid_tbl;
-struct scx_cid_topo *scx_cid_topo;
+u32 scx_nr_cid_shards;
+s16 __rcu *scx_cid_to_cpu_tbl;
+s16 __rcu *scx_cpu_to_cid_tbl;
+s32 __rcu *scx_cid_to_shard;
+s32 __rcu *scx_shard_node;
+struct scx_cid_shard __rcu *scx_cid_shard_ranges;
+struct scx_cid_topo __rcu *scx_cid_topo;
+
+static struct scx_cid_tables *scx_cid_tables; /* used only during alloc/free */
#define SCX_CID_TOPO_NEG (struct scx_cid_topo) { \
.core_cid = -1, .core_idx = -1, .llc_cid = -1, .llc_idx = -1, \
- .node_cid = -1, .node_idx = -1, \
+ .node_cid = -1, .node_idx = -1, .shard_cid = -1, .shard_idx = -1, \
}
/*
@@ -43,37 +49,128 @@ static const struct cpumask *cpu_llc_mask(int cpu, struct cpumask *fallbacks)
return &ci->info_list[ci->num_leaves - 1].shared_cpu_map;
}
-/* Allocate the cid tables once on first enable; never freed. */
-static s32 scx_cid_arrays_alloc(void)
+/*
+ * Compute per-LLC shard layout. Each shard holds at most @shard_size cids, and
+ * in any case no more than SCX_CID_SHARD_MAX_CPUS. Cores are spread as evenly
+ * as possible across shards so cpu count is balanced: the first *@nr_large_p
+ * shards get (*@cores_per_shard_p + 1) cores, the rest get *@cores_per_shard_p.
+ */
+static void calc_shard_layout(const struct cpumask *llc_cpus, u32 shard_size,
+ u32 *cores_per_shard_p, u32 *nr_large_p)
{
- u32 npossible = num_possible_cpus();
- s16 *cid_to_cpu, *cpu_to_cid;
- struct scx_cid_topo *cid_topo;
+ u32 nr_cores = 0, nr_cpus = 0, nr_shards;
+ int cpu;
+
+ for_each_cpu(cpu, llc_cpus) {
+ nr_cpus++;
+ if (cpumask_first(topology_sibling_cpumask(cpu)) == cpu)
+ nr_cores++;
+ }
- if (scx_cid_to_cpu_tbl)
- return 0;
+ nr_shards = max_t(u32, 1, DIV_ROUND_UP(nr_cpus, shard_size));
+ nr_shards = max_t(u32, nr_shards,
+ DIV_ROUND_UP(nr_cpus, SCX_CID_SHARD_MAX_CPUS));
- cid_to_cpu = kzalloc_objs(*scx_cid_to_cpu_tbl, npossible, GFP_KERNEL);
- cpu_to_cid = kzalloc_objs(*scx_cpu_to_cid_tbl, nr_cpu_ids, GFP_KERNEL);
- cid_topo = kmalloc_objs(*scx_cid_topo, npossible, GFP_KERNEL);
+ *cores_per_shard_p = nr_cores / nr_shards;
+ *nr_large_p = nr_cores % nr_shards;
+}
- if (!cid_to_cpu || !cpu_to_cid || !cid_topo) {
- kfree(cid_to_cpu);
- kfree(cpu_to_cid);
- kfree(cid_topo);
- return -ENOMEM;
+static void scx_cid_tables_free(struct scx_cid_tables *tbls)
+{
+ if (!tbls)
+ return;
+ kvfree(tbls->cid_to_cpu);
+ kvfree(tbls->cpu_to_cid);
+ kvfree(tbls->cid_to_shard);
+ kvfree(tbls->shard_node);
+ kvfree(tbls->shard_ranges);
+ kvfree(tbls->topo);
+ kfree(tbls);
+}
+
+static void scx_cid_tables_free_rcufn(struct rcu_head *rcu)
+{
+ scx_cid_tables_free(container_of(rcu, struct scx_cid_tables, rcu));
+}
+
+static struct scx_cid_tables *scx_cid_alloc_tables(void)
+{
+ u32 npossible = num_possible_cpus();
+ struct scx_cid_tables *tbls;
+
+ tbls = kzalloc_obj(*tbls);
+ if (!tbls)
+ return NULL;
+
+ tbls->cid_to_cpu = kvzalloc_objs(*tbls->cid_to_cpu, npossible);
+ tbls->cpu_to_cid = kvzalloc_objs(*tbls->cpu_to_cid, nr_cpu_ids);
+ tbls->cid_to_shard = kvzalloc_objs(*tbls->cid_to_shard, npossible);
+ tbls->shard_node = kvzalloc_objs(*tbls->shard_node, npossible);
+ tbls->shard_ranges = kvzalloc_objs(*tbls->shard_ranges, npossible);
+ tbls->topo = kvzalloc_objs(*tbls->topo, npossible);
+
+ if (!tbls->cid_to_cpu || !tbls->cpu_to_cid || !tbls->cid_to_shard ||
+ !tbls->shard_node || !tbls->shard_ranges || !tbls->topo) {
+ scx_cid_tables_free(tbls);
+ return NULL;
}
- WRITE_ONCE(scx_cid_to_cpu_tbl, cid_to_cpu);
- WRITE_ONCE(scx_cpu_to_cid_tbl, cpu_to_cid);
- WRITE_ONCE(scx_cid_topo, cid_topo);
- return 0;
+ return tbls;
+}
+
+/**
+ * scx_cid_publish_tables - Publish the tables scx_cid_init() built
+ *
+ * Called after ops.init_cids() where the layout is final.
+ */
+void scx_cid_publish_tables(void)
+{
+ struct scx_cid_tables *tbls = scx_cid_tables;
+
+ lockdep_assert_held(&scx_enable_mutex);
+
+ scx_nr_cid_shards = tbls->nr_shards;
+ rcu_assign_pointer(scx_cid_to_cpu_tbl, tbls->cid_to_cpu);
+ rcu_assign_pointer(scx_cpu_to_cid_tbl, tbls->cpu_to_cid);
+ rcu_assign_pointer(scx_cid_to_shard, tbls->cid_to_shard);
+ rcu_assign_pointer(scx_shard_node, tbls->shard_node);
+ rcu_assign_pointer(scx_cid_shard_ranges, tbls->shard_ranges);
+ rcu_assign_pointer(scx_cid_topo, tbls->topo);
+}
+
+/**
+ * scx_cid_retire_tables - Unpublish and retire the cid tables
+ *
+ * Called by root disable after the readers which dereference without NULL
+ * checks are drained, inside cpus_read_lock() to exclude the hotplug path.
+ */
+void scx_cid_retire_tables(void)
+{
+ struct scx_cid_tables *tbls = scx_cid_tables;
+
+ lockdep_assert_held(&scx_enable_mutex);
+ lockdep_assert_cpus_held();
+
+ if (!tbls)
+ return;
+
+ scx_cid_tables = NULL;
+ RCU_INIT_POINTER(scx_cid_to_cpu_tbl, NULL);
+ RCU_INIT_POINTER(scx_cpu_to_cid_tbl, NULL);
+ RCU_INIT_POINTER(scx_cid_to_shard, NULL);
+ RCU_INIT_POINTER(scx_shard_node, NULL);
+ RCU_INIT_POINTER(scx_cid_shard_ranges, NULL);
+ RCU_INIT_POINTER(scx_cid_topo, NULL);
+ call_rcu(&tbls->rcu, scx_cid_tables_free_rcufn);
}
/**
* scx_cid_init - build the cid mapping
* @sch: the scx_sched being initialized; used as the scx_error() target
*
+ * Build a fresh table set. It becomes visible through scx_cid_publish_tables()
+ * and is retired by scx_cid_retire_tables() at disable.
+ *
* See "Topological CPU IDs" in cid.h for the model. Walk online cpus by
* intersection at each level (parent_scratch & this_level_mask), which keeps
* containment correct by construction and naturally splits a physical LLC
@@ -88,18 +185,32 @@ s32 scx_cid_init(struct scx_sched *sch)
cpumask_var_t core_scratch __free(free_cpumask_var) = CPUMASK_VAR_NULL;
cpumask_var_t llc_fallback __free(free_cpumask_var) = CPUMASK_VAR_NULL;
cpumask_var_t online_no_topo __free(free_cpumask_var) = CPUMASK_VAR_NULL;
+ struct scx_cid_tables *tbls;
u32 next_cid = 0;
s32 next_node_idx = 0, next_llc_idx = 0, next_core_idx = 0;
- s32 cpu, ret;
+ s32 next_shard_idx = 0;
+ u32 shard_size, max_cids;
+ u32 notopo_in_shard;
+ s32 notopo_shard_cid, notopo_shard_idx;
+ s32 cpu, cid, si;
/* CMASK_MAX_WORDS in cid.bpf.h covers NR_CPUS up to 8192 */
BUILD_BUG_ON(NR_CPUS > 8192);
lockdep_assert_cpus_held();
+ lockdep_assert_held(&scx_enable_mutex);
- ret = scx_cid_arrays_alloc();
- if (ret)
- return ret;
+ shard_size = sch->ops.cid_shard_size ?: SCX_CID_SHARD_SIZE_DFL;
+ max_cids = min_t(u32, shard_size, SCX_CID_SHARD_MAX_CPUS);
+
+ tbls = scx_cid_alloc_tables();
+ if (!tbls)
+ return -ENOMEM;
+
+ scx_cid_tables = tbls;
+
+ for (si = 0; si < num_possible_cpus(); si++)
+ tbls->shard_node[si] = NUMA_NO_NODE;
if (!zalloc_cpumask_var(&to_walk, GFP_KERNEL) ||
!zalloc_cpumask_var(&node_scratch, GFP_KERNEL) ||
@@ -111,7 +222,7 @@ s32 scx_cid_init(struct scx_sched *sch)
/* -1 sentinels for sparse-possible cpu id holes (0 is a valid cid) */
for (cpu = 0; cpu < nr_cpu_ids; cpu++)
- scx_cpu_to_cid_tbl[cpu] = -1;
+ tbls->cpu_to_cid[cpu] = -1;
cpumask_copy(to_walk, cpu_online_mask);
@@ -142,36 +253,69 @@ s32 scx_cid_init(struct scx_sched *sch)
const struct cpumask *llc_mask = cpu_llc_mask(ncpu, llc_fallback);
s32 llc_cid = next_cid;
s32 llc_idx = next_llc_idx++;
+ u32 cores_per_shard, nr_large;
+ u32 shard_local = 0, cores_in_shard = 0, cids_in_shard = 0;
+ s32 shard_cid, shard_idx;
/* llc_scratch = node_scratch & this llc */
cpumask_and(llc_scratch, node_scratch, llc_mask);
if (WARN_ON_ONCE(!cpumask_test_cpu(ncpu, llc_scratch)))
return -EINVAL;
+ calc_shard_layout(llc_scratch, shard_size, &cores_per_shard, &nr_large);
+ shard_cid = next_cid;
+ shard_idx = next_shard_idx++;
+ tbls->shard_node[shard_idx] = nid;
+
while (!cpumask_empty(llc_scratch)) {
s32 lcpu = cpumask_first(llc_scratch);
const struct cpumask *sib = topology_sibling_cpumask(lcpu);
s32 core_cid = next_cid;
s32 core_idx = next_core_idx++;
s32 ccpu;
+ u32 max_cores, cids_in_core;
/* core_scratch = llc_scratch & this core */
cpumask_and(core_scratch, llc_scratch, sib);
if (WARN_ON_ONCE(!cpumask_test_cpu(lcpu, core_scratch)))
return -EINVAL;
+ /*
+ * Advance to a new shard when either core or
+ * cid count reaches max. The latter bounds
+ * shard sizes under uneven SMT. Never start an
+ * empty shard.
+ */
+ cids_in_core = cpumask_weight(core_scratch);
+ max_cores = cores_per_shard + (shard_local < nr_large ? 1 : 0);
+ if (cores_in_shard &&
+ (cores_in_shard >= max_cores ||
+ cids_in_shard + cids_in_core > max_cids)) {
+ shard_local++;
+ cores_in_shard = 0;
+ cids_in_shard = 0;
+ shard_cid = next_cid;
+ shard_idx = next_shard_idx++;
+ tbls->shard_node[shard_idx] = nid;
+ }
+ cores_in_shard++;
+ cids_in_shard += cids_in_core;
+
for_each_cpu(ccpu, core_scratch) {
s32 cid = next_cid++;
- scx_cid_to_cpu_tbl[cid] = ccpu;
- scx_cpu_to_cid_tbl[ccpu] = cid;
- scx_cid_topo[cid] = (struct scx_cid_topo){
+ tbls->cid_to_cpu[cid] = ccpu;
+ tbls->cpu_to_cid[ccpu] = cid;
+ tbls->cid_to_shard[cid] = shard_idx;
+ tbls->topo[cid] = (struct scx_cid_topo){
.core_cid = core_cid,
.core_idx = core_idx,
.llc_cid = llc_cid,
.llc_idx = llc_idx,
.node_cid = node_cid,
.node_idx = node_idx,
+ .shard_cid = shard_cid,
+ .shard_idx = shard_idx,
};
cpumask_clear_cpu(ccpu, llc_scratch);
@@ -184,21 +328,37 @@ s32 scx_cid_init(struct scx_sched *sch)
/*
* No-topo section: any possible cpu without a cid - normally just the
- * not-online ones. Collect any currently-online cpus that land here in
- * @online_no_topo so we can warn about them at the end.
+ * not-online ones. Pack into shards of up to min(@shard_size,
+ * SCX_CID_SHARD_MAX_CPUS) cids so that every cid has a valid shard
+ * assignment and the hard cap holds even with a large @shard_size.
+ * Collect any currently-online cpus that land here in @online_no_topo
+ * so we can warn about them at the end.
*/
- for_each_cpu(cpu, cpu_possible_mask) {
- s32 cid;
+ notopo_in_shard = min_t(u32, shard_size, SCX_CID_SHARD_MAX_CPUS);
+ notopo_shard_cid = -1;
+ notopo_shard_idx = -1;
- if (__scx_cpu_to_cid(cpu) != -1)
+ for_each_cpu(cpu, cpu_possible_mask) {
+ if (tbls->cpu_to_cid[cpu] != -1)
continue;
if (cpu_online(cpu))
cpumask_set_cpu(cpu, online_no_topo);
cid = next_cid++;
- scx_cid_to_cpu_tbl[cid] = cpu;
- scx_cpu_to_cid_tbl[cpu] = cid;
- scx_cid_topo[cid] = SCX_CID_TOPO_NEG;
+ tbls->cid_to_cpu[cid] = cpu;
+ tbls->cpu_to_cid[cpu] = cid;
+
+ if (notopo_in_shard >= min_t(u32, shard_size, SCX_CID_SHARD_MAX_CPUS)) {
+ notopo_shard_cid = cid;
+ notopo_shard_idx = next_shard_idx++;
+ notopo_in_shard = 0;
+ }
+ notopo_in_shard++;
+
+ tbls->cid_to_shard[cid] = notopo_shard_idx;
+ tbls->topo[cid] = SCX_CID_TOPO_NEG;
+ tbls->topo[cid].shard_cid = notopo_shard_cid;
+ tbls->topo[cid].shard_idx = notopo_shard_idx;
}
if (!cpumask_empty(llc_fallback))
@@ -208,6 +368,20 @@ s32 scx_cid_init(struct scx_sched *sch)
pr_warn("scx_cid: online cpus with no usable topology: %*pbl\n",
cpumask_pr_args(online_no_topo));
+ /*
+ * Fill cid_shard_ranges[] from cid_to_shard[]. Shards are contiguous
+ * cid ranges by construction: base_cid is the first cid landing in a
+ * shard, nr_cids is the count.
+ */
+ for (cid = 0; cid < next_cid; cid++) {
+ s32 sidx = tbls->cid_to_shard[cid];
+
+ if (tbls->shard_ranges[sidx].nr_cids == 0)
+ tbls->shard_ranges[sidx].base_cid = cid;
+ tbls->shard_ranges[sidx].nr_cids++;
+ }
+
+ tbls->nr_shards = next_shard_idx;
return 0;
}
@@ -253,50 +427,76 @@ void scx_cmask_fill(struct scx_cmask *m)
m->bits[nr_words - 1] &= (1ULL << tail_bits) - 1;
}
-/**
- * scx_cpumask_to_cmask - Translate a kernel cpumask into a cmask
- * @src: source cpumask
- * @dst: cmask to write
- *
- * Clear @dst's active range and set the bit for each cid whose cpu is in
- * @src and lies within that range. Out-of-range cids are silently ignored.
+/*
+ * Return the index of the largest entry in @counts, or NUMA_NO_NODE if all
+ * entries are zero. Ties resolve to the lowest index.
*/
-void scx_cpumask_to_cmask(const struct cpumask *src, struct scx_cmask *dst)
+static s32 pick_max_node(const u32 *counts, u32 n)
{
- s32 cpu;
+ s32 best = NUMA_NO_NODE;
+ u32 best_count = 0, i;
- scx_cmask_clear(dst);
- for_each_cpu(cpu, src) {
- s32 cid = __scx_cpu_to_cid(cpu);
-
- if (cid >= 0)
- __scx_cmask_set(cid, dst);
+ for (i = 0; i < n; i++) {
+ if (counts[i] > best_count) {
+ best_count = counts[i];
+ best = i;
+ }
}
+ return best;
}
__bpf_kfunc_start_defs();
/**
- * scx_bpf_cid_override - Install an explicit cpu->cid mapping
- * @cpu_to_cid: array of nr_cpu_ids s32 entries (cid for each cpu)
- * @cpu_to_cid__sz: must be nr_cpu_ids * sizeof(s32) bytes
+ * scx_bpf_cid_override - Install an explicit cpu->cid mapping with shard info
+ * @cpu_to_cid__arena: array of nr_cpu_ids s32 entries (cid for each cpu)
+ * @cpu_to_cid_cnt: number of entries, must be nr_cpu_ids
+ * @shard_start__arena: array of first-cid-of-each-shard, one entry per shard
+ * @shard_start_cnt: number of shards
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
*
- * May only be called from ops.init() of the root scheduler. Replace the
- * topology-probed cid mapping with the caller-provided one. Each possible cpu
- * must map to a unique cid in [0, num_possible_cpus()). Topo info is cleared.
- * On invalid input, trigger scx_error() to abort the scheduler.
+ * May only be called from ops.init_cids() of the root scheduler. Replace the
+ * topology-probed cid mapping and shard layout with caller-provided ones. Each
+ * possible cpu must map to a unique cid in [0, num_possible_cpus()). The shard
+ * starts must be strictly increasing with the first entry 0 and all values <
+ * num_possible_cpus(). The last shard extends to num_possible_cpus() and no
+ * shard may span more than SCX_CID_SHARD_MAX_CPUS cids. Topo info
+ * (core/LLC/node) is cleared and the shard layout is set from the input. On
+ * invalid input, abort the scheduler.
*/
-__bpf_kfunc void scx_bpf_cid_override(const s32 *cpu_to_cid, u32 cpu_to_cid__sz,
+__bpf_kfunc void scx_bpf_cid_override(const s32 *cpu_to_cid__arena, u32 cpu_to_cid_cnt,
+ const s32 *shard_start__arena, u32 shard_start_cnt,
const struct bpf_prog_aux *aux)
{
cpumask_var_t seen __free(free_cpumask_var) = CPUMASK_VAR_NULL;
+ u32 *node_counts __free(kfree) = NULL;
+ s32 *cpu_to_cid __free(kfree) = NULL;
+ s32 *shard_start __free(kfree) = NULL;
+ u32 npossible = num_possible_cpus();
+ struct scx_cid_tables *tbls;
struct scx_sched *sch;
+ u32 nr_shards = shard_start_cnt;
bool alloced;
- s32 cpu, cid;
+ s32 cpu, cid, si;
- /* GFP_KERNEL alloc must happen before the rcu read section */
+ /*
+ * GFP_KERNEL allocs must happen before the rcu read section. Snapshot
+ * the BPF-supplied arrays so a concurrent arena write can't change
+ * them between validation and use.
+ *
+ * The BPF-supplied counts size the snapshots and thus the arena reads.
+ * Gate the copies on the count bounds, reported below once @sch is
+ * available. The bounded reads, at most 32KB, stay within the guard
+ * region that arena fault recovery covers.
+ */
alloced = zalloc_cpumask_var(&seen, GFP_KERNEL);
+ node_counts = kzalloc_objs(*node_counts, nr_node_ids);
+ if (cpu_to_cid_cnt == nr_cpu_ids)
+ cpu_to_cid = kmemdup(cpu_to_cid__arena, cpu_to_cid_cnt * sizeof(s32),
+ GFP_KERNEL);
+ if (nr_shards && nr_shards <= npossible)
+ shard_start = kmemdup(shard_start__arena, nr_shards * sizeof(s32),
+ GFP_KERNEL);
guard(rcu)();
@@ -304,22 +504,59 @@ __bpf_kfunc void scx_bpf_cid_override(const s32 *cpu_to_cid, u32 cpu_to_cid__sz,
if (unlikely(!sch))
return;
- if (!alloced) {
- scx_error(sch, "scx_bpf_cid_override: failed to allocate cpumask");
+ /* called from ops.init_cids(), so the tables exist and are unpublished */
+ lockdep_assert_held(&scx_enable_mutex);
+ tbls = scx_cid_tables;
+
+ if (cpu_to_cid_cnt != nr_cpu_ids) {
+ scx_error(sch, "scx_bpf_cid_override: cpu_to_cid expected %u entries, got %u",
+ nr_cpu_ids, cpu_to_cid_cnt);
+ return;
+ }
+
+ if (!nr_shards || nr_shards > npossible) {
+ scx_error(sch, "scx_bpf_cid_override: invalid shard_start count %u",
+ nr_shards);
return;
}
- if (scx_parent(sch)) {
- scx_error(sch, "scx_bpf_cid_override() only allowed from root sched");
+ if (!alloced || !node_counts || !cpu_to_cid || !shard_start) {
+ scx_error(sch, "scx_bpf_cid_override: allocation failed");
return;
}
- if (cpu_to_cid__sz != nr_cpu_ids * sizeof(s32)) {
- scx_error(sch, "scx_bpf_cid_override: expected %zu bytes, got %u",
- nr_cpu_ids * sizeof(s32), cpu_to_cid__sz);
+ /* validate shard_start[]: starts at 0, strictly increasing, in range */
+ if (shard_start[0] != 0) {
+ scx_error(sch, "scx_bpf_cid_override: shard_start[0] must be 0, got %d",
+ shard_start[0]);
+ return;
+ }
+ for (si = 1; si < nr_shards; si++) {
+ if (shard_start[si] <= shard_start[si - 1]) {
+ scx_error(sch, "scx_bpf_cid_override: shard_start not increasing at [%d]",
+ si);
+ return;
+ }
+ if (shard_start[si] >= npossible) {
+ scx_error(sch, "scx_bpf_cid_override: shard_start[%d]=%d >= %u",
+ si, shard_start[si], npossible);
+ return;
+ }
+ if (shard_start[si] - shard_start[si - 1] > SCX_CID_SHARD_MAX_CPUS) {
+ scx_error(sch, "scx_bpf_cid_override: shard[%d] span %d exceeds max %d",
+ si - 1, shard_start[si] - shard_start[si - 1],
+ SCX_CID_SHARD_MAX_CPUS);
+ return;
+ }
+ }
+ if (npossible - shard_start[nr_shards - 1] > SCX_CID_SHARD_MAX_CPUS) {
+ scx_error(sch, "scx_bpf_cid_override: shard[%d] span %d exceeds max %d",
+ nr_shards - 1, npossible - shard_start[nr_shards - 1],
+ SCX_CID_SHARD_MAX_CPUS);
return;
}
+ /* validate first so that invalid input leaves the tables untouched */
for_each_possible_cpu(cpu) {
s32 c = cpu_to_cid[cpu];
@@ -329,13 +566,56 @@ __bpf_kfunc void scx_bpf_cid_override(const s32 *cpu_to_cid, u32 cpu_to_cid__sz,
scx_error(sch, "cid %d assigned to multiple cpus", c);
return;
}
- scx_cpu_to_cid_tbl[cpu] = c;
- scx_cid_to_cpu_tbl[c] = cpu;
}
- /* Invalidate stale topo info - the override carries no topology. */
- for (cid = 0; cid < num_possible_cpus(); cid++)
- scx_cid_topo[cid] = SCX_CID_TOPO_NEG;
+ for_each_possible_cpu(cpu) {
+ s32 c = cpu_to_cid[cpu];
+
+ tbls->cpu_to_cid[cpu] = c;
+ tbls->cid_to_cpu[c] = cpu;
+ }
+
+ /*
+ * Derive shard_node[] by majority count: an overridden shard may
+ * span NUMA nodes, so assign each to the node that owns the most cpus.
+ */
+ for (si = 0; si < nr_shards; si++) {
+ u32 end = (si + 1 < nr_shards) ? shard_start[si + 1] : npossible;
+
+ memset(node_counts, 0, nr_node_ids * sizeof(*node_counts));
+ for (cid = shard_start[si]; cid < end; cid++) {
+ s32 node = cpu_to_node(tbls->cid_to_cpu[cid]);
+
+ if (numa_valid_node(node))
+ node_counts[node]++;
+ }
+ tbls->shard_node[si] = pick_max_node(node_counts, nr_node_ids);
+ }
+
+ /*
+ * Invalidate stale topo info and install shard layout from
+ * @shard_start. Walk shards to derive shard_cid/shard_idx for each cid.
+ */
+ si = 0;
+ for (cid = 0; cid < npossible; cid++) {
+ if (si + 1 < nr_shards && cid >= shard_start[si + 1])
+ si++;
+ tbls->cid_to_shard[cid] = si;
+ tbls->topo[cid] = SCX_CID_TOPO_NEG;
+ tbls->topo[cid].shard_cid = shard_start[si];
+ tbls->topo[cid].shard_idx = si;
+ }
+
+ /* Rebuild shard_ranges[] for the new layout. */
+ memset(tbls->shard_ranges, 0, npossible * sizeof(*tbls->shard_ranges));
+ for (si = 0; si < nr_shards; si++) {
+ u32 end = (si + 1 < nr_shards) ? shard_start[si + 1] : npossible;
+
+ tbls->shard_ranges[si].base_cid = shard_start[si];
+ tbls->shard_ranges[si].nr_cids = end - shard_start[si];
+ }
+
+ tbls->nr_shards = nr_shards;
}
/**
@@ -395,21 +675,25 @@ __bpf_kfunc s32 scx_bpf_cpu_to_cid(s32 cpu, const struct bpf_prog_aux *aux)
* bits outside stay untouched. In particular, scx_cmask_copy() does NOT zero
* @dst bits that lie outside @src's range.
*
- * The _RACY variants are otherwise identical to their non-racy counterpart but
- * read @src word-by-word via data_race(). Memory ordering with concurrent
- * writers is the caller's responsibility.
+ * Word accesses use READ_ONCE/WRITE_ONCE so a caller may read @src
+ * locklessly. Memory ordering against concurrent writers is the caller's
+ * responsibility.
*/
enum cmask_op2 {
/* mutating */
CMASK_OP2_AND,
CMASK_OP2_OR,
- CMASK_OP2_OR_RACY,
CMASK_OP2_COPY,
- CMASK_OP2_COPY_RACY,
CMASK_OP2_ANDNOT,
/* predicates - short-circuit when the per-word result is true */
CMASK_OP2_SUBSET,
CMASK_OP2_INTERSECTS,
+ /*
+ * @a is a BPF-arena cmask. Words on @a use READ_ONCE/WRITE_ONCE since
+ * BPF may read/write concurrently. See scx_cmask_ref_or() / _copy().
+ */
+ CMASK_OP2_REF_OR,
+ CMASK_OP2_REF_COPY,
};
static __always_inline bool cmask_op2_is_pred(const enum cmask_op2 op)
@@ -422,28 +706,28 @@ static __always_inline bool cmask_word_op2(u64 *av, const u64 *bp, u64 mask,
{
switch (op) {
case CMASK_OP2_AND:
- *av &= ~mask | *bp;
+ WRITE_ONCE(*av, *av & (~mask | READ_ONCE(*bp)));
return false;
case CMASK_OP2_OR:
- *av |= *bp & mask;
- return false;
- case CMASK_OP2_OR_RACY:
- *av |= data_race(*bp) & mask;
+ WRITE_ONCE(*av, *av | (READ_ONCE(*bp) & mask));
return false;
case CMASK_OP2_COPY:
- *av = (*av & ~mask) | (*bp & mask);
- return false;
- case CMASK_OP2_COPY_RACY:
- *av = (*av & ~mask) | (data_race(*bp) & mask);
+ WRITE_ONCE(*av, (*av & ~mask) | (READ_ONCE(*bp) & mask));
return false;
case CMASK_OP2_ANDNOT:
- *av &= ~(*bp & mask);
+ WRITE_ONCE(*av, *av & ~(READ_ONCE(*bp) & mask));
return false;
case CMASK_OP2_SUBSET:
/* stop on the first bit in @sub not set in @super */
- return (*bp & ~*av) & mask;
+ return (READ_ONCE(*bp) & ~READ_ONCE(*av)) & mask;
case CMASK_OP2_INTERSECTS:
- return (*av & *bp) & mask;
+ return (READ_ONCE(*av) & READ_ONCE(*bp)) & mask;
+ case CMASK_OP2_REF_OR:
+ WRITE_ONCE(*av, READ_ONCE(*av) | (READ_ONCE(*bp) & mask));
+ return false;
+ case CMASK_OP2_REF_COPY:
+ WRITE_ONCE(*av, (READ_ONCE(*av) & ~mask) | (READ_ONCE(*bp) & mask));
+ return false;
}
unreachable();
}
@@ -504,7 +788,7 @@ static __always_inline bool cmask_word_op1(const u64 *ap, u64 mask,
{
switch (op) {
case CMASK_OP1_ANY_SET:
- return *ap & mask;
+ return READ_ONCE(*ap) & mask;
}
unreachable();
}
@@ -556,39 +840,12 @@ void scx_cmask_or(struct scx_cmask *dst, const struct scx_cmask *src)
src->bits, src->base, src->nr_cids, CMASK_OP2_OR);
}
-/**
- * scx_cmask_or_racy - OR @src into @dst, reading @src without locking
- *
- * @src is read word-by-word through data_race(). Same per-bit independence
- * rationale as scx_cmask_copy_racy(). Memory ordering with writers is the
- * caller's responsibility.
- */
-void scx_cmask_or_racy(struct scx_cmask *dst, const struct scx_cmask *src)
-{
- cmask_walk_op2(dst->bits, dst->base, dst->nr_cids,
- src->bits, src->base, src->nr_cids, CMASK_OP2_OR_RACY);
-}
-
void scx_cmask_copy(struct scx_cmask *dst, const struct scx_cmask *src)
{
cmask_walk_op2(dst->bits, dst->base, dst->nr_cids,
src->bits, src->base, src->nr_cids, CMASK_OP2_COPY);
}
-/**
- * scx_cmask_copy_racy - Snapshot @src into @dst without locking
- *
- * @src is read word-by-word through data_race(). Head/tail masking matches
- * scx_cmask_copy(). Each bit in a cmask is independent, so partial updates
- * just leave some bits fresher than others. Memory ordering with writers is
- * the caller's responsibility.
- */
-void scx_cmask_copy_racy(struct scx_cmask *dst, const struct scx_cmask *src)
-{
- cmask_walk_op2(dst->bits, dst->base, dst->nr_cids,
- src->bits, src->base, src->nr_cids, CMASK_OP2_COPY_RACY);
-}
-
void scx_cmask_andnot(struct scx_cmask *dst, const struct scx_cmask *src)
{
cmask_walk_op2(dst->bits, dst->base, dst->nr_cids,
@@ -655,38 +912,47 @@ bool scx_cmask_empty(const struct scx_cmask *m)
/**
* scx_bpf_cid_topo - Copy out per-cid topology info
* @cid: cid to look up
- * @out__uninit: where to copy the topology info; fully written by this call
+ * @out: where to copy the topology info
+ * @out__sz: size of @out, the program's sizeof(struct scx_cid_topo)
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
*
- * Fill @out__uninit with the topology info for @cid. Trigger scx_error() if
- * @cid is out of range. If @cid is valid but in the no-topo section, all fields
- * are set to -1.
+ * Fill @out with the topology info for @cid. Trigger scx_error() if @cid is out
+ * of range. If @cid is valid but in the no-topo section, all fields are set to
+ * -1. All fields are also set to -1 when no cid tables have been published yet,
+ * which a program may observe while racing the root enable.
+ *
+ * The program's struct may be older or newer than the kernel's. The smaller of
+ * @out__sz and the kernel's size is copied and the rest of @out is set to -1.
*/
-__bpf_kfunc void scx_bpf_cid_topo(s32 cid, struct scx_cid_topo *out__uninit,
+__bpf_kfunc void scx_bpf_cid_topo(s32 cid, struct scx_cid_topo *out, size_t out__sz,
const struct bpf_prog_aux *aux)
{
+ size_t len = min(out__sz, sizeof(*out));
+ struct scx_cid_topo *topo;
struct scx_sched *sch;
+ /* the error cases and fields the kernel lacks read as -1 */
+ memset(out, 0xff, out__sz);
+
guard(rcu)();
sch = scx_prog_sched(aux);
- if (unlikely(!sch) || !cid_valid(sch, cid)) {
- *out__uninit = SCX_CID_TOPO_NEG;
+ topo = rcu_dereference(scx_cid_topo);
+ if (unlikely(!sch) || !cid_valid(sch, cid) || unlikely(!topo))
return;
- }
- *out__uninit = READ_ONCE(scx_cid_topo)[cid];
+ memcpy(out, &topo[cid], len);
}
__bpf_kfunc_end_defs();
-BTF_KFUNCS_START(scx_kfunc_ids_init)
+BTF_KFUNCS_START(scx_kfunc_ids_init_cids)
BTF_ID_FLAGS(func, scx_bpf_cid_override, KF_IMPLICIT_ARGS | KF_SLEEPABLE)
-BTF_KFUNCS_END(scx_kfunc_ids_init)
+BTF_KFUNCS_END(scx_kfunc_ids_init_cids)
-static const struct btf_kfunc_id_set scx_kfunc_set_init = {
+static const struct btf_kfunc_id_set scx_kfunc_set_init_cids = {
.owner = THIS_MODULE,
- .set = &scx_kfunc_ids_init,
+ .set = &scx_kfunc_ids_init_cids,
.filter = scx_kfunc_context_filter,
};
@@ -701,9 +967,205 @@ static const struct btf_kfunc_id_set scx_kfunc_set_cid = {
.set = &scx_kfunc_ids_cid,
};
+/**
+ * scx_cmask_ref_init - Bind a scx_cmask_ref to a BPF-arena cmask
+ * @sch: scheduler whose arena hosts @src
+ * @src: BPF-supplied cmask, rebased to its kernel address
+ * @ref: output ref
+ *
+ * Snapshot @src's @base, @nr_cids and @alloc_words. The snapshot is necessary
+ * because BPF may mutate the live header asynchronously.
+ *
+ * Return 0 on success, -EINVAL if the range is out of bounds or @alloc_words
+ * doesn't cover it.
+ */
+int scx_cmask_ref_init(struct scx_sched *sch, const struct scx_cmask *src,
+ struct scx_cmask_ref *ref)
+{
+ u32 base, nr_cids, alloc_words, npossible = num_possible_cpus();
+ s32 *cid_to_shard;
+
+ base = READ_ONCE(src->base);
+ nr_cids = READ_ONCE(src->nr_cids);
+ alloc_words = READ_ONCE(src->alloc_words);
+
+ if (unlikely(base >= npossible || nr_cids > npossible - base ||
+ SCX_CMASK_NR_WORDS(nr_cids) > alloc_words))
+ return -EINVAL;
+
+ ref->sch = sch;
+ ref->src = (struct scx_cmask *)src;
+ ref->base = base;
+ ref->nr_cids = nr_cids;
+
+ cid_to_shard = rcu_dereference_all(scx_cid_to_shard);
+ ref->shard_first = cid_to_shard[base];
+ if (likely(nr_cids))
+ ref->shard_end = cid_to_shard[base + nr_cids - 1] + 1;
+ else
+ ref->shard_end = ref->shard_first;
+
+ return 0;
+}
+
+/**
+ * scx_cmask_ref_init_kern - Bind a scx_cmask_ref to a kernel-owned cmask
+ * @sch: scheduler the cmask belongs to
+ * @m: kernel address of the target cmask, storage sized for @nr_cids at @base
+ * @base: first cid of the active range
+ * @nr_cids: active range length
+ * @ref: output ref
+ *
+ * Like scx_cmask_ref_init() but the geometry is supplied by the caller, not
+ * read from @m's header, so a concurrent BPF write to the header can't steer
+ * later sizing or offsets. Rewrite the header from the trusted geometry and
+ * bind @ref to it.
+ */
+void scx_cmask_ref_init_kern(struct scx_sched *sch, struct scx_cmask *m,
+ u32 base, u32 nr_cids, struct scx_cmask_ref *ref)
+{
+ s32 *cid_to_shard;
+
+ WRITE_ONCE(m->base, base);
+ WRITE_ONCE(m->nr_cids, nr_cids);
+ WRITE_ONCE(m->alloc_words, SCX_CMASK_NR_WORDS(nr_cids));
+
+ ref->sch = sch;
+ ref->src = m;
+ ref->base = base;
+ ref->nr_cids = nr_cids;
+
+ cid_to_shard = rcu_dereference_all(scx_cid_to_shard);
+ ref->shard_first = cid_to_shard[base];
+ if (likely(nr_cids))
+ ref->shard_end = cid_to_shard[base + nr_cids - 1] + 1;
+ else
+ ref->shard_end = ref->shard_first;
+}
+
+/**
+ * scx_cmask_ref_shard - Read one shard from @ref into @out
+ * @ref: validated ref
+ * @shard_idx: target shard, in [@ref->shard_first, @ref->shard_end)
+ * @out: output cmask whose @out->alloc_words must hold the shard
+ *
+ * Set @out to the intersection of @ref's range with @shard_idx's cid range,
+ * with bits[] read from @ref->src via READ_ONCE. Empty intersection sets
+ * @out->nr_cids to 0. scx_error()s on @ref's sched if @out can't hold the
+ * shard.
+ */
+void scx_cmask_ref_shard(const struct scx_cmask_ref *ref, s32 shard_idx,
+ struct scx_cmask *out)
+{
+ const struct scx_cid_shard *shard =
+ &rcu_dereference_all(scx_cid_shard_ranges)[shard_idx];
+ u32 shard_base = shard->base_cid;
+ u32 shard_end = shard_base + shard->nr_cids;
+ u32 isect_base, isect_end, nr_words, src_off, wi;
+ u64 head_mask, tail_mask;
+
+ isect_base = max(ref->base, shard_base);
+ isect_end = min(ref->base + ref->nr_cids, shard_end);
+
+ if (isect_base >= isect_end) {
+ out->base = shard_base;
+ out->nr_cids = 0;
+ return;
+ }
+
+ nr_words = ((isect_end - 1) / 64) - (isect_base / 64) + 1;
+ if (nr_words > out->alloc_words) {
+ scx_error(ref->sch, "scx_cmask_ref_shard: out alloc_words=%u < %u for shard %d",
+ out->alloc_words, nr_words, shard_idx);
+ out->base = shard_base;
+ out->nr_cids = 0;
+ return;
+ }
+
+ out->base = isect_base;
+ out->nr_cids = isect_end - isect_base;
+ src_off = (isect_base / 64) - (ref->base / 64);
+
+ for (wi = 0; wi < nr_words; wi++)
+ out->bits[wi] = READ_ONCE(ref->src->bits[src_off + wi]);
+
+ head_mask = GENMASK_U64(63, isect_base & 63);
+ out->bits[0] &= head_mask;
+ tail_mask = GENMASK_U64((isect_end - 1) & 63, 0);
+ out->bits[nr_words - 1] &= tail_mask;
+}
+
+/**
+ * scx_cmask_ref_or - OR @src into the arena cmask referenced by @ref
+ * @ref: validated ref
+ * @src: stable kernel cmask
+ *
+ * Bits inside the intersection of @ref's snapshotted range with @src's range
+ * are OR'd into @ref->src and bits outside are left unchanged. Stores on
+ * @ref->src use WRITE_ONCE since BPF may read/write concurrently.
+ */
+void scx_cmask_ref_or(const struct scx_cmask_ref *ref, const struct scx_cmask *src)
+{
+ cmask_walk_op2(ref->src->bits, ref->base, ref->nr_cids,
+ src->bits, src->base, src->nr_cids, CMASK_OP2_REF_OR);
+}
+
+/**
+ * scx_cmask_ref_copy - Copy @src into the arena cmask referenced by @ref
+ * @ref: validated ref
+ * @src: stable kernel cmask
+ *
+ * Bits inside the intersection of @ref's snapshotted range with @src's range
+ * take @src's values and bits outside are left unchanged. Stores on @ref->src
+ * use WRITE_ONCE since BPF may read/write concurrently.
+ */
+void scx_cmask_ref_copy(const struct scx_cmask_ref *ref, const struct scx_cmask *src)
+{
+ cmask_walk_op2(ref->src->bits, ref->base, ref->nr_cids,
+ src->bits, src->base, src->nr_cids, CMASK_OP2_REF_COPY);
+}
+
+/**
+ * scx_cmask_ref_from_cpumask - Populate @ref's arena cmask from a cpumask
+ * @ref: kern-bound ref, see scx_cmask_ref_init_kern()
+ * @cpumask: cpus to translate into cids
+ *
+ * Write @ref's active range one word at a time, setting each cid's bit when
+ * its cpu is in @cpumask. Offsets and length come from @ref's trusted geometry
+ * and stores use WRITE_ONCE since BPF may read concurrently, so the arena
+ * header is never read.
+ */
+void scx_cmask_ref_from_cpumask(const struct scx_cmask_ref *ref,
+ const struct cpumask *cpumask)
+{
+ struct scx_cmask *m = ref->src;
+ u32 base = ref->base, nr_cids = ref->nr_cids;
+ u32 wi, nr_words;
+
+ if (!nr_cids)
+ return;
+
+ nr_words = (base + nr_cids - 1) / 64 - base / 64 + 1;
+ for (wi = 0; wi < nr_words; wi++) {
+ u32 word_first_cid = (base / 64 + wi) * 64;
+ u64 word = 0;
+ u32 bit;
+
+ for (bit = 0; bit < 64; bit++) {
+ u32 cid = word_first_cid + bit;
+
+ if (cid < base || cid >= base + nr_cids)
+ continue;
+ if (cpumask_test_cpu(__scx_cid_to_cpu(cid), cpumask))
+ word |= BIT_U64(bit);
+ }
+ WRITE_ONCE(m->bits[wi], word);
+ }
+}
+
int scx_cid_kfunc_init(void)
{
- return register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, &scx_kfunc_set_init) ?:
+ return register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, &scx_kfunc_set_init_cids) ?:
register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, &scx_kfunc_set_cid) ?:
register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &scx_kfunc_set_cid) ?:
register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, &scx_kfunc_set_cid);
diff --git a/kernel/sched/ext/cid.h b/kernel/sched/ext/cid.h
index 9c4f4b907f12..2fe2311a0f99 100644
--- a/kernel/sched/ext/cid.h
+++ b/kernel/sched/ext/cid.h
@@ -48,25 +48,40 @@ struct scx_sched;
* See the comment above the table definitions in cid.c for the
* memory-ordering and visibility contract.
*/
-extern s16 *scx_cid_to_cpu_tbl;
-extern s16 *scx_cpu_to_cid_tbl;
-extern struct scx_cid_topo *scx_cid_topo;
-extern struct btf_id_set8 scx_kfunc_ids_init;
+struct scx_cid_tables {
+ u32 nr_shards;
+ s16 *cid_to_cpu; /* [num_possible_cpus()] */
+ s16 *cpu_to_cid; /* [nr_cpu_ids] */
+ s32 *cid_to_shard; /* [num_possible_cpus()] */
+ s32 *shard_node; /* [num_possible_cpus()] */
+ struct scx_cid_shard *shard_ranges; /* [num_possible_cpus()] */
+ struct scx_cid_topo *topo; /* [num_possible_cpus()] */
+ struct rcu_head rcu;
+};
+
+extern u32 scx_nr_cid_shards;
+extern s16 __rcu *scx_cid_to_cpu_tbl;
+extern s16 __rcu *scx_cpu_to_cid_tbl;
+extern s32 __rcu *scx_cid_to_shard;
+extern s32 __rcu *scx_shard_node;
+extern struct scx_cid_shard __rcu *scx_cid_shard_ranges;
+extern struct scx_cid_topo __rcu *scx_cid_topo;
+extern struct btf_id_set8 scx_kfunc_ids_init_cids;
+extern struct btf_id_set8 scx_kfunc_ids_cid;
void scx_cmask_clear(struct scx_cmask *m);
void scx_cmask_fill(struct scx_cmask *m);
void scx_cmask_and(struct scx_cmask *dst, const struct scx_cmask *src);
void scx_cmask_or(struct scx_cmask *dst, const struct scx_cmask *src);
-void scx_cmask_or_racy(struct scx_cmask *dst, const struct scx_cmask *src);
void scx_cmask_copy(struct scx_cmask *dst, const struct scx_cmask *src);
-void scx_cmask_copy_racy(struct scx_cmask *dst, const struct scx_cmask *src);
void scx_cmask_andnot(struct scx_cmask *dst, const struct scx_cmask *src);
bool scx_cmask_subset(const struct scx_cmask *sub, const struct scx_cmask *super);
bool scx_cmask_intersects(const struct scx_cmask *a, const struct scx_cmask *b);
bool scx_cmask_empty(const struct scx_cmask *m);
s32 scx_cid_init(struct scx_sched *sch);
+void scx_cid_publish_tables(void);
+void scx_cid_retire_tables(void);
int scx_cid_kfunc_init(void);
-void scx_cpumask_to_cmask(const struct cpumask *src, struct scx_cmask *dst);
/**
* cid_valid - Verify a cid value, to be used on ops input args
@@ -88,14 +103,12 @@ static inline bool cid_valid(struct scx_sched *sch, s32 cid)
* __scx_cid_to_cpu - Unchecked cid->cpu table lookup
* @cid: cid to look up. Must be in [0, num_possible_cpus()).
*
- * Intended for callsites that have already validated @cid and that hold a
- * non-NULL @sch from scx_prog_sched() - a live sched implies the table has
- * been allocated, so no NULL check is needed here.
+ * Intended for callsites that have already validated @cid and that run on a
+ * live scheduler, which guarantees the tables are published and stable.
*/
static inline s32 __scx_cid_to_cpu(s32 cid)
{
- /* READ_ONCE pairs with WRITE_ONCE in scx_cid_arrays_alloc() */
- return READ_ONCE(scx_cid_to_cpu_tbl)[cid];
+ return rcu_dereference_all(scx_cid_to_cpu_tbl)[cid];
}
/**
@@ -106,7 +119,7 @@ static inline s32 __scx_cid_to_cpu(s32 cid)
*/
static inline s32 __scx_cpu_to_cid(s32 cpu)
{
- return READ_ONCE(scx_cpu_to_cid_tbl)[cpu];
+ return rcu_dereference_all(scx_cpu_to_cid_tbl)[cpu];
}
/**
@@ -115,15 +128,19 @@ static inline s32 __scx_cpu_to_cid(s32 cpu)
* @cid: cid to look up
*
* Return the cpu for @cid or a negative errno on failure. Invalid cid triggers
- * scx_error() on @sch. The cid arrays are allocated on first scheduler enable
- * and never freed, so the returned cpu is stable for the lifetime of the loaded
- * scheduler.
+ * scx_error() on @sch. The mapping is stable while the scheduler is live.
+ *
+ * Return -EINVAL without triggering scx_error() if no tables have been
+ * published yet, which a prog-facing kfunc can observe while racing the root
+ * scheduler enable.
*/
static inline s32 scx_cid_to_cpu(struct scx_sched *sch, s32 cid)
{
- if (!cid_valid(sch, cid))
+ s16 *tbl = rcu_dereference_all(scx_cid_to_cpu_tbl);
+
+ if (!cid_valid(sch, cid) || unlikely(!tbl))
return -EINVAL;
- return __scx_cid_to_cpu(cid);
+ return tbl[cid];
}
/**
@@ -132,13 +149,15 @@ static inline s32 scx_cid_to_cpu(struct scx_sched *sch, s32 cid)
* @cpu: cpu to look up
*
* Return the cid for @cpu or a negative errno on failure. Invalid cpu triggers
- * scx_error() on @sch. Same lifetime guarantee as scx_cid_to_cpu().
+ * scx_error() on @sch. Same usage rules as scx_cid_to_cpu().
*/
static inline s32 scx_cpu_to_cid(struct scx_sched *sch, s32 cpu)
{
- if (!scx_cpu_valid(sch, cpu, NULL))
+ s16 *tbl = rcu_dereference_all(scx_cpu_to_cid_tbl);
+
+ if (!scx_cpu_valid(sch, cpu, NULL) || unlikely(!tbl))
return -EINVAL;
- return __scx_cpu_to_cid(cpu);
+ return tbl[cpu];
}
/**
@@ -291,4 +310,15 @@ static inline s32 scx_cpu_ret(struct scx_sched *sch, s32 cpu_or_cid)
return scx_cid_to_cpu(sch, cpu_or_cid);
}
+int scx_cmask_ref_init(struct scx_sched *sch, const struct scx_cmask *src,
+ struct scx_cmask_ref *ref);
+void scx_cmask_ref_init_kern(struct scx_sched *sch, struct scx_cmask *m,
+ u32 base, u32 nr_cids, struct scx_cmask_ref *ref);
+void scx_cmask_ref_shard(const struct scx_cmask_ref *ref, s32 shard_idx,
+ struct scx_cmask *out);
+void scx_cmask_ref_from_cpumask(const struct scx_cmask_ref *ref,
+ const struct cpumask *cpumask);
+void scx_cmask_ref_or(const struct scx_cmask_ref *ref, const struct scx_cmask *src);
+void scx_cmask_ref_copy(const struct scx_cmask_ref *ref, const struct scx_cmask *src);
+
#endif /* _KERNEL_SCHED_EXT_CID_H */
diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
index 691d53fe0f64..5fe980da545d 100644
--- a/kernel/sched/ext/ext.c
+++ b/kernel/sched/ext/ext.c
@@ -19,8 +19,10 @@
#include "cid.h"
#include "arena.h"
#include "idle.h"
+#include "sub.h"
+#include "inlines.h"
-static DEFINE_RAW_SPINLOCK(scx_sched_lock);
+DEFINE_RAW_SPINLOCK(scx_sched_lock);
/*
* NOTE: sched_ext is in the process of growing multiple scheduler support and
@@ -36,17 +38,17 @@ struct scx_sched __rcu *scx_root;
* All scheds, writers must hold both scx_enable_mutex and scx_sched_lock.
* Readers can hold either or rcu_read_lock().
*/
-static LIST_HEAD(scx_sched_all);
+LIST_HEAD(scx_sched_all);
#ifdef CONFIG_EXT_SUB_SCHED
-static const struct rhashtable_params scx_sched_hash_params = {
+const struct rhashtable_params scx_sched_hash_params = {
.key_len = sizeof_field(struct scx_sched, ops.sub_cgroup_id),
.key_offset = offsetof(struct scx_sched, ops.sub_cgroup_id),
.head_offset = offsetof(struct scx_sched, hash_node),
.insecure_elasticity = true, /* inserted under scx_sched_lock */
};
-static struct rhashtable scx_sched_hash;
+struct rhashtable scx_sched_hash;
#endif
/* see SCX_OPS_TID_TO_TASK */
@@ -68,9 +70,9 @@ static DEFINE_RAW_SPINLOCK(scx_tasks_lock);
static LIST_HEAD(scx_tasks);
/* ops enable/disable */
-static DEFINE_MUTEX(scx_enable_mutex);
+DEFINE_MUTEX(scx_enable_mutex);
DEFINE_STATIC_KEY_FALSE(__scx_enabled);
-DEFINE_STATIC_PERCPU_RWSEM(scx_fork_rwsem);
+DEFINE_PERCPU_RWSEM(scx_fork_rwsem);
static atomic_t scx_enable_state_var = ATOMIC_INIT(SCX_DISABLED);
static DEFINE_RAW_SPINLOCK(scx_bypass_lock);
static bool scx_init_task_enabled;
@@ -79,6 +81,14 @@ DEFINE_STATIC_KEY_FALSE(__scx_switched_all);
static DEFINE_STATIC_KEY_FALSE(__scx_tid_to_task_enabled);
/*
+ * Gates cgroup ops delivery. Set at the end of the cgroup init phase of root
+ * enable and cleared before root disable starts tearing down tasks, both under
+ * scx_cgroup_lock(). Holding cgroup_lock() and seeing %true guarantees no race
+ * against root tearing down tasks.
+ */
+bool scx_cgroup_enabled;
+
+/*
* True once SCX_OPS_TID_TO_TASK has been negotiated with the root scheduler
* and the tid->task table is live. Wraps the static key so callers don't
* take the address, and hints "likely enabled" for the common case where
@@ -95,13 +105,29 @@ static atomic_long_t scx_hotplug_seq = ATOMIC_LONG_INIT(0);
/* Global cursor for the per-CPU tid allocator. Starts at 1; tid 0 is reserved. */
static atomic64_t scx_tid_cursor = ATOMIC64_INIT(1);
+/* is @dsq synchronized by the containing rq lock instead of dsq->lock? */
+static bool dsq_is_rq_owned(struct scx_dispatch_q *dsq)
+{
+ switch (dsq->id) {
+ case SCX_DSQ_LOCAL:
+ case SCX_DSQ_REJECT:
+ case SCX_DSQ_RESCUE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* Cursor for unique scx_sched instance ids. id 0 is reserved. */
+static atomic64_t scx_sched_id_cursor = ATOMIC64_INIT(0);
+
#ifdef CONFIG_EXT_SUB_SCHED
/*
* The sub sched being enabled. Used by scx_disable_and_exit_task() to exit
* tasks for the sub-sched being enabled. Use a global variable instead of a
* per-task field as all enables are serialized.
*/
-static struct scx_sched *scx_enabling_sub_sched;
+struct scx_sched *scx_enabling_sub_sched;
#else
#define scx_enabling_sub_sched (struct scx_sched *)NULL
#endif /* CONFIG_EXT_SUB_SCHED */
@@ -171,15 +197,6 @@ static const struct rhashtable_params dsq_hash_params = {
static LLIST_HEAD(dsqs_to_free);
-/* string formatting from BPF */
-struct scx_bstr_buf {
- u64 data[MAX_BPRINTF_VARARGS];
- char line[SCX_EXIT_MSG_LEN];
-};
-
-static DEFINE_RAW_SPINLOCK(scx_exit_bstr_buf_lock);
-static struct scx_bstr_buf scx_exit_bstr_buf;
-
/* ops debug dump */
static DEFINE_RAW_SPINLOCK(scx_dump_lock);
@@ -242,7 +259,6 @@ MODULE_PARM_DESC(bypass_lb_intv_us, "bypass load balance interval in microsecond
static void run_deferred(struct rq *rq);
static bool task_dead_and_done(struct task_struct *p);
-static void scx_kick_cpu(struct scx_sched *sch, s32 cpu, u64 flags);
static void scx_disable(struct scx_sched *sch, enum scx_exit_kind kind);
__printf(5, 6) bool __scx_exit(struct scx_sched *sch,
@@ -272,58 +288,6 @@ static bool u32_before(u32 a, u32 b)
return (s32)(a - b) < 0;
}
-#ifdef CONFIG_EXT_SUB_SCHED
-/**
- * scx_next_descendant_pre - find the next descendant for pre-order walk
- * @pos: the current position (%NULL to initiate traversal)
- * @root: sched whose descendants to walk
- *
- * To be used by scx_for_each_descendant_pre(). Find the next descendant to
- * visit for pre-order traversal of @root's descendants. @root is included in
- * the iteration and the first node to be visited.
- */
-static struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos,
- struct scx_sched *root)
-{
- struct scx_sched *next;
-
- lockdep_assert(lockdep_is_held(&scx_enable_mutex) ||
- lockdep_is_held(&scx_sched_lock));
-
- /* if first iteration, visit @root */
- if (!pos)
- return root;
-
- /* visit the first child if exists */
- next = list_first_entry_or_null(&pos->children, struct scx_sched, sibling);
- if (next)
- return next;
-
- /* no child, visit my or the closest ancestor's next sibling */
- while (pos != root) {
- if (!list_is_last(&pos->sibling, &scx_parent(pos)->children))
- return list_next_entry(pos, sibling);
- pos = scx_parent(pos);
- }
-
- return NULL;
-}
-
-static struct scx_sched *scx_find_sub_sched(u64 cgroup_id)
-{
- return rhashtable_lookup(&scx_sched_hash, &cgroup_id,
- scx_sched_hash_params);
-}
-
-static void scx_set_task_sched(struct task_struct *p, struct scx_sched *sch)
-{
- rcu_assign_pointer(p->scx.sched, sch);
-}
-#else /* CONFIG_EXT_SUB_SCHED */
-static inline struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos, struct scx_sched *root) { return pos ? NULL : root; }
-static inline void scx_set_task_sched(struct task_struct *p, struct scx_sched *sch) {}
-#endif /* CONFIG_EXT_SUB_SCHED */
-
/**
* scx_is_descendant - Test whether sched is a descendant
* @sch: sched to test
@@ -331,26 +295,13 @@ static inline void scx_set_task_sched(struct task_struct *p, struct scx_sched *s
*
* Test whether @sch is a descendant of @ancestor.
*/
-static bool scx_is_descendant(struct scx_sched *sch, struct scx_sched *ancestor)
+bool scx_is_descendant(struct scx_sched *sch, struct scx_sched *ancestor)
{
if (sch->level < ancestor->level)
return false;
return sch->ancestors[ancestor->level] == ancestor;
}
-/**
- * scx_for_each_descendant_pre - pre-order walk of a sched's descendants
- * @pos: iteration cursor
- * @root: sched to walk the descendants of
- *
- * Walk @root's descendants. @root is included in the iteration and the first
- * node to be visited. Must be called with either scx_enable_mutex or
- * scx_sched_lock held.
- */
-#define scx_for_each_descendant_pre(pos, root) \
- for ((pos) = scx_next_descendant_pre(NULL, (root)); (pos); \
- (pos) = scx_next_descendant_pre((pos), (root)))
-
static struct scx_dispatch_q *find_global_dsq(struct scx_sched *sch, s32 cpu)
{
return &sch->pnode[cpu_to_node(cpu)]->global_dsq;
@@ -369,11 +320,6 @@ static const struct sched_class *scx_setscheduler_class(struct task_struct *p)
return __setscheduler_class(p->policy, p->prio);
}
-static struct scx_dispatch_q *bypass_dsq(struct scx_sched *sch, s32 cpu)
-{
- return &per_cpu_ptr(sch->pcpu, cpu)->bypass_dsq;
-}
-
static struct scx_dispatch_q *bypass_enq_target_dsq(struct scx_sched *sch, s32 cpu)
{
#ifdef CONFIG_EXT_SUB_SCHED
@@ -392,27 +338,7 @@ static struct scx_dispatch_q *bypass_enq_target_dsq(struct scx_sched *sch, s32 c
sch = scx_parent(sch);
#endif /* CONFIG_EXT_SUB_SCHED */
- return bypass_dsq(sch, cpu);
-}
-
-/**
- * bypass_dsp_enabled - Check if bypass dispatch path is enabled
- * @sch: scheduler to check
- *
- * When a descendant scheduler enters bypass mode, bypassed tasks are scheduled
- * by the nearest non-bypassing ancestor, or the root scheduler if all ancestors
- * are bypassing. In the former case, the ancestor is not itself bypassing but
- * its bypass DSQs will be populated with bypassed tasks from descendants. Thus,
- * the ancestor's bypass dispatch path must be active even though its own
- * bypass_depth remains zero.
- *
- * This function checks bypass_dsp_enable_depth which is managed separately from
- * bypass_depth to enable this decoupling. See enable_bypass_dsp() and
- * disable_bypass_dsp().
- */
-static bool bypass_dsp_enabled(struct scx_sched *sch)
-{
- return unlikely(atomic_read(&sch->bypass_dsp_enable_depth));
+ return scx_bypass_dsq(sch, cpu);
}
/**
@@ -451,9 +377,9 @@ static bool rq_is_open(struct rq *rq, u64 enq_flags)
* If we're in the dispatch path holding rq lock, $curr may or may not
* be ready depending on whether the on-going dispatch decides to extend
* $curr's slice. We say yes here and resolve it at the end of dispatch.
- * See balance_one().
+ * See dispatch_one().
*/
- if (rq->scx.flags & SCX_RQ_IN_BALANCE)
+ if (rq->scx.flags & SCX_RQ_IN_DISPATCH)
return true;
/*
@@ -461,8 +387,16 @@ static bool rq_is_open(struct rq *rq, u64 enq_flags)
* so allow it to avoid spuriously triggering reenq on a combined
* PREEMPT|IMMED insertion.
*/
- if (enq_flags & SCX_ENQ_PREEMPT)
- return true;
+ if (enq_flags & SCX_ENQ_PREEMPT) {
+ struct task_struct *curr = rq->curr;
+
+ /*
+ * A protected slice refuses the preemption and the cpu stays
+ * occupied. See rq_owned_post_enq().
+ */
+ return curr->sched_class != &ext_sched_class ||
+ likely(!(curr->scx.flags & SCX_TASK_PROTECTED));
+ }
/*
* @rq is either in transition to or running an SCX task and can't go
@@ -480,47 +414,78 @@ static bool rq_is_open(struct rq *rq, u64 enq_flags)
DEFINE_PER_CPU(struct rq *, scx_locked_rq_state);
/*
+ * Under core scheduling, a pick that releases the rq lock invalidates the
+ * core-wide selection it is part of. Count the releases so that the core-sched
+ * pick can tell whether one happened across dispatch.
+ */
+static void scx_rq_lock_drop(struct rq *rq)
+{
+ lockdep_assert_rq_held(rq);
+#ifdef CONFIG_SCHED_CORE
+ if (sched_core_enabled(rq))
+ rq->scx.lock_drop_seq++;
+#endif
+}
+
+static void switch_rq_lock(struct rq *from, struct rq *to)
+{
+ bool tracked = scx_locked_rq() == from;
+
+ if (tracked)
+ update_locked_rq(NULL);
+ scx_rq_lock_drop(from);
+ raw_spin_rq_unlock(from);
+ raw_spin_rq_lock(to);
+ if (tracked)
+ update_locked_rq(to);
+}
+
+/*
* Flipped on enable per sch->is_cid_type. Declared in internal.h so
* subsystem inlines can read it.
*/
DEFINE_STATIC_KEY_FALSE(__scx_is_cid_type);
/**
- * scx_call_op_set_cpumask - invoke ops.set_cpumask / ops_cid.set_cmask for @task
+ * scx_fill_cmask_scratch - Build this cpu's arena cmask from @cpumask
+ * @sch: scx_sched whose scratch to fill
+ * @cpumask: cpus to translate into cids
+ *
+ * The scratch lives in BPF-writable arena memory and its header can't be
+ * trusted, so it is rewritten from kernel geometry rather than read. Caller
+ * must hold an rq lock so this cpu is the sole kernel writer for as long as the
+ * returned address is in use.
+ */
+static struct scx_cmask *scx_fill_cmask_scratch(struct scx_sched *sch,
+ const struct cpumask *cpumask)
+{
+ struct scx_cmask *kern_va = *this_cpu_ptr(sch->set_cmask_scratch);
+ struct scx_cmask_ref ref;
+
+ scx_cmask_ref_init_kern(sch, kern_va, 0, num_possible_cpus(), &ref);
+ scx_cmask_ref_from_cpumask(&ref, cpumask);
+ return kern_va;
+}
+
+/**
+ * scx_call_op_set_cpumask - Invoke the set_cpumask or set_cmask op for @task
* @sch: scx_sched being invoked
* @rq: rq to update as the currently-locked rq, or NULL
* @task: task whose affinity is changing
* @cpumask: new cpumask
*
- * For cid-form schedulers, translate @cpumask to a cmask via the per-cpu
- * scratch in cid.c and dispatch through the ops_cid union view. Caller
- * must hold @rq's rq lock so this_cpu_ptr is stable across the call.
+ * For cid-form schedulers, translate @cpumask to a cmask in the per-cpu scratch
+ * and dispatch through the ops_cid union view. Caller must hold @rq's rq lock.
*/
static inline void scx_call_op_set_cpumask(struct scx_sched *sch, struct rq *rq,
struct task_struct *task,
const struct cpumask *cpumask)
{
- WARN_ON_ONCE(current->scx.kf_tasks[0]);
- current->scx.kf_tasks[0] = task;
- if (rq)
- update_locked_rq(rq);
-
- if (scx_is_cid_type()) {
- struct scx_cmask *kern_va = *this_cpu_ptr(sch->set_cmask_scratch);
- /*
- * Build the per-CPU arena cmask and hand BPF its arena address.
- * Caller holds the rq lock with IRQs disabled, which makes us
- * the sole user of the scratch area.
- */
- scx_cpumask_to_cmask(cpumask, kern_va);
- sch->ops_cid.set_cmask(task, scx_kaddr_to_arena(sch, kern_va));
- } else {
- sch->ops.set_cpumask(task, cpumask);
- }
-
- if (rq)
- update_locked_rq(NULL);
- current->scx.kf_tasks[0] = NULL;
+ if (scx_is_cid_type())
+ SCX_CALL_CID_OP_TASK(sch, set_cmask, rq, task,
+ scx_fill_cmask_scratch(sch, cpumask));
+ else
+ SCX_CALL_OP_TASK(sch, set_cpumask, rq, task, cpumask);
}
enum scx_dsq_iter_flags {
@@ -676,12 +641,12 @@ struct bpf_iter_scx_dsq {
} __attribute__((aligned(8)));
-static u32 scx_get_task_state(const struct task_struct *p)
+u32 scx_get_task_state(const struct task_struct *p)
{
return p->scx.flags & SCX_TASK_STATE_MASK;
}
-static void scx_set_task_state(struct task_struct *p, u32 state)
+void scx_set_task_state(struct task_struct *p, u32 state)
{
u32 prev_state = scx_get_task_state(p);
bool warn = false;
@@ -721,23 +686,6 @@ static void scx_set_task_state(struct task_struct *p, u32 state)
p->scx.flags |= state;
}
-/*
- * SCX task iterator.
- */
-struct scx_task_iter {
- struct sched_ext_entity cursor;
- struct task_struct *locked_task;
- struct rq *rq;
- struct rq_flags rf;
- u32 cnt;
- bool list_locked;
-#ifdef CONFIG_EXT_SUB_SCHED
- struct cgroup *cgrp;
- struct cgroup_subsys_state *css_pos;
- struct css_task_iter css_iter;
-#endif
-};
-
/**
* scx_task_iter_start - Lock scx_tasks_lock and start a task iteration
* @iter: iterator to init
@@ -766,7 +714,7 @@ struct scx_task_iter {
* All tasks which existed when the iteration started are guaranteed to be
* visited as long as they are not dead.
*/
-static void scx_task_iter_start(struct scx_task_iter *iter, struct cgroup *cgrp)
+void scx_task_iter_start(struct scx_task_iter *iter, struct cgroup *cgrp)
{
memset(iter, 0, sizeof(*iter));
@@ -805,7 +753,7 @@ static void __scx_task_iter_rq_unlock(struct scx_task_iter *iter)
* This function can be safely called anytime during an iteration. The next
* iterator operation will automatically restore the necessary locking.
*/
-static void scx_task_iter_unlock(struct scx_task_iter *iter)
+void scx_task_iter_unlock(struct scx_task_iter *iter)
{
__scx_task_iter_rq_unlock(iter);
if (iter->list_locked) {
@@ -848,7 +796,7 @@ static void scx_task_iter_relock(struct scx_task_iter *iter,
* which is released on return. If the iterator holds a task's rq lock, that rq
* lock is also released. See scx_task_iter_start() for details.
*/
-static void scx_task_iter_stop(struct scx_task_iter *iter)
+void scx_task_iter_stop(struct scx_task_iter *iter)
{
#ifdef CONFIG_EXT_SUB_SCHED
if (iter->cgrp) {
@@ -923,7 +871,7 @@ static struct task_struct *scx_task_iter_next(struct scx_task_iter *iter)
* whether they would like to filter out dead tasks. See scx_task_iter_start()
* for details.
*/
-static struct task_struct *scx_task_iter_next_locked(struct scx_task_iter *iter)
+struct task_struct *scx_task_iter_next_locked(struct scx_task_iter *iter)
{
struct task_struct *p;
@@ -936,9 +884,9 @@ static struct task_struct *scx_task_iter_next_locked(struct scx_task_iter *iter)
* unloading. The init_tasks ("swappers") should be excluded
* from the iteration because:
*
- * - It's unsafe to use __setschduler_prio() on an init_task to
- * determine the sched_class to use as it won't preserve its
- * idle_sched_class.
+ * - It's unsafe to use __setscheduler_class() on an init_task
+ * to determine the sched_class to use as it won't preserve
+ * its idle_sched_class.
*
* - ops.init/exit_task() can easily be confused if called with
* init_tasks as they, e.g., share PID 0.
@@ -979,49 +927,13 @@ static struct task_struct *scx_task_iter_next_locked(struct scx_task_iter *iter)
}
/**
- * scx_add_event - Increase an event counter for 'name' by 'cnt'
- * @sch: scx_sched to account events for
- * @name: an event name defined in struct scx_event_stats
- * @cnt: the number of the event occurred
- *
- * This can be used when preemption is not disabled.
- */
-#define scx_add_event(sch, name, cnt) do { \
- this_cpu_add((sch)->pcpu->event_stats.name, (cnt)); \
- trace_sched_ext_event(#name, (cnt)); \
-} while(0)
-
-/**
- * __scx_add_event - Increase an event counter for 'name' by 'cnt'
- * @sch: scx_sched to account events for
- * @name: an event name defined in struct scx_event_stats
- * @cnt: the number of the event occurred
- *
- * This should be used only when preemption is disabled.
- */
-#define __scx_add_event(sch, name, cnt) do { \
- __this_cpu_add((sch)->pcpu->event_stats.name, (cnt)); \
- trace_sched_ext_event(#name, cnt); \
-} while(0)
-
-/**
- * scx_agg_event - Aggregate an event counter 'kind' from 'src_e' to 'dst_e'
- * @dst_e: destination event stats
- * @src_e: source event stats
- * @kind: a kind of event to be aggregated
- */
-#define scx_agg_event(dst_e, src_e, kind) do { \
- (dst_e)->kind += READ_ONCE((src_e)->kind); \
-} while(0)
-
-/**
* scx_dump_event - Dump an event 'kind' in 'events' to 's'
* @s: output seq_buf
* @events: event stats
* @kind: a kind of event to dump
*/
#define scx_dump_event(s, events, kind) do { \
- dump_line(&(s), "%40s: %16lld", #kind, (events)->kind); \
+ scx_dump_line(&(s), "%40s: %16lld", #kind, (events)->kind); \
} while (0)
@@ -1088,28 +1000,6 @@ bool scx_cpu_valid(struct scx_sched *sch, s32 cpu, const char *where)
}
}
-/**
- * ops_sanitize_err - Sanitize a -errno value
- * @sch: scx_sched to error out on error
- * @ops_name: operation to blame on failure
- * @err: -errno value to sanitize
- *
- * Verify @err is a valid -errno. If not, trigger scx_error() and return
- * -%EPROTO. This is necessary because returning a rogue -errno up the chain can
- * cause misbehaviors. For an example, a large negative return from
- * ops.init_task() triggers an oops when passed up the call chain because the
- * value fails IS_ERR() test after being encoded with ERR_PTR() and then is
- * handled as a pointer.
- */
-static int ops_sanitize_err(struct scx_sched *sch, const char *ops_name, s32 err)
-{
- if (err < 0 && err >= -MAX_ERRNO)
- return err;
-
- scx_error(sch, "ops.%s() returned an invalid errno %d", ops_name, err);
- return -EPROTO;
-}
-
static void deferred_bal_cb_workfn(struct rq *rq)
{
run_deferred(rq);
@@ -1121,6 +1011,7 @@ static void deferred_irq_workfn(struct irq_work *irq_work)
raw_spin_rq_lock(rq);
run_deferred(rq);
+ scx_rq_lock_drop(rq);
raw_spin_rq_unlock(rq);
}
@@ -1137,7 +1028,7 @@ static void schedule_deferred(struct rq *rq)
/*
* This is the fallback when schedule_deferred_locked() can't use
* the cheaper balance callback or wakeup hook paths (the target
- * CPU is not in balance or wakeup). Currently, this is primarily
+ * CPU is not in dispatch or wakeup). Currently, this is primarily
* hit by reenqueue operations targeting a remote CPU.
*
* Queue on the target CPU. The deferred work can run from any CPU
@@ -1173,31 +1064,31 @@ static void schedule_deferred_locked(struct rq *rq)
return;
/*
- * If in balance, the balance callbacks will be called before rq lock is
- * released. Schedule one.
+ * If in dispatch, the balance callbacks will be called before rq lock
+ * is released. Schedule one.
*
*
* We can't directly insert the callback into the
* rq's list: The call can drop its lock and make the pending balance
* callback visible to unrelated code paths that call rq_pin_lock().
*
- * Just let balance_one() know that it must do it itself.
+ * Just let dispatch_one() know that it must do it itself.
*/
- if (rq->scx.flags & SCX_RQ_IN_BALANCE) {
+ if (rq->scx.flags & SCX_RQ_IN_DISPATCH) {
rq->scx.flags |= SCX_RQ_BAL_CB_PENDING;
return;
}
/*
* No scheduler hooks available. Use the generic irq_work path. The
- * above WAKEUP and BALANCE paths should cover most of the cases and the
- * time to IRQ re-enable shouldn't be long.
+ * above WAKEUP and DISPATCH paths should cover most of the cases and
+ * the time to IRQ re-enable shouldn't be long.
*/
schedule_deferred(rq);
}
-static void schedule_dsq_reenq(struct scx_sched *sch, struct scx_dispatch_q *dsq,
- u64 reenq_flags, struct rq *locked_rq)
+void schedule_dsq_reenq(struct scx_sched *sch, struct scx_dispatch_q *dsq,
+ u64 reenq_flags, struct rq *locked_rq)
{
struct rq *rq;
@@ -1211,6 +1102,18 @@ static void schedule_dsq_reenq(struct scx_sched *sch, struct scx_dispatch_q *dsq
if (dsq->id == SCX_DSQ_LOCAL) {
rq = container_of(dsq, struct rq, scx.local_dsq);
+ /*
+ * A sub-sched lacking baseline access on the target cid has no
+ * business triggering IPIs. The lockless test is fine: slipping
+ * through right after a revoke is harmless and a wrong denial
+ * can't happen - if the caller has seen its ownership, so does
+ * this test.
+ */
+ if (unlikely(scx_missing_caps(sch, cpu_of(rq), SCX_CAP_BASE))) {
+ __scx_add_event(sch, SCX_EV_SUB_REENQ_DENIED, 1);
+ return;
+ }
+
struct scx_sched_pcpu *sch_pcpu = per_cpu_ptr(sch->pcpu, cpu_of(rq));
struct scx_deferred_reenq_local *drl = &sch_pcpu->deferred_reenq_local;
@@ -1261,61 +1164,236 @@ static void schedule_dsq_reenq(struct scx_sched *sch, struct scx_dispatch_q *dsq
schedule_deferred(rq);
}
-static void schedule_reenq_local(struct rq *rq, u64 reenq_flags)
-{
- struct scx_sched *root = rcu_dereference_sched(scx_root);
+/*
+ * p->scx.slice_oob packs an out-of-band slice request into one atomic64. A zero
+ * word means no request. Otherwise the fields are:
+ *
+ * 63 SCX_SLICE_OOB_PENDING, set on every request
+ * 62-43 lower bits of issuing scheduler's id
+ * 42-0 requested slice duration in nsecs
+ *
+ * A duration of SCX_SLICE_OOB_DUR_MASK means SCX_SLICE_INF. A finite dur
+ * saturates at SCX_SLICE_OOB_DUR_MASK - 1. The id is used to detect and ignore
+ * a request that outlived a task ownership change.
+ *
+ * Only the low 20 bits of sch->id are packed, which is enough to make
+ * collisions practically impossible. A theoretical collision just lets a stale
+ * request through once.
+ */
+enum scx_slice_oob_consts {
+ SCX_SLICE_OOB_DUR_BITS = 43,
+ SCX_SLICE_OOB_ID_BITS = 64 - SCX_SLICE_OOB_DUR_BITS - 1,
- if (WARN_ON_ONCE(!root))
- return;
+ SCX_SLICE_OOB_DUR_MASK = (1LLU << SCX_SLICE_OOB_DUR_BITS) - 1,
+ SCX_SLICE_OOB_ID_SHIFT = SCX_SLICE_OOB_DUR_BITS,
+ SCX_SLICE_OOB_ID_MASK = (1LLU << SCX_SLICE_OOB_ID_BITS) - 1,
+ SCX_SLICE_OOB_PENDING = 1LLU << 63,
+};
+
+/*
+ * Slice and dsq_vtime write rules
+ *
+ * While @p is running, sleeping or queued on an rq-owned DSQ, both fields are
+ * protected by the rq lock. While running, the rq lock is required because
+ * update_curr_scx() RMWs the slice and the cap check for slice extension is
+ * only reliable under the rq lock.
+ *
+ * While @p is queued on a user DSQ or on the BPF side, the kernel neither
+ * consumes nor decides on the fields. Synchronizing the writers is the BPF
+ * scheduler's responsibility. An rq-locked scx_bpf_task_set_slice() write and a
+ * concurrent DSQ insertion commit can race each other and whichever lands last
+ * wins.
+ *
+ * A DSQ insert kfunc doesn't update the fields directly. The verdict carries
+ * the values and apply_slice_vtime() commits them at the insertion.
+ *
+ * scx_bpf_task_set_slice() may be called from any context and writes directly
+ * only if @p's rq lock is already held, otherwise it bounces through
+ * p->scx.slice_oob, applied under @p's rq lock at the next slice consideration.
+ *
+ * While %SCX_TASK_PROTECTED is set, every scheduler-reachable slice update is
+ * refused. See set_task_slice_keep_oob().
+ *
+ * dsq_vtime orders the next PRIQ insertion and has no running-side consumer, so
+ * scx_bpf_task_set_dsq_vtime() writes it directly. Fork-time init and direct
+ * BPF stores from non-cid-form schedulers are outside these rules.
+ */
- schedule_dsq_reenq(root, &rq->scx.local_dsq, reenq_flags, rq);
+/* clear a pending slice request */
+static void clear_task_slice_oob(struct task_struct *p)
+{
+ if (unlikely(atomic64_read(&p->scx.slice_oob)))
+ atomic64_set(&p->scx.slice_oob, 0);
}
/**
- * touch_core_sched - Update timestamp used for core-sched task ordering
- * @rq: rq to read clock from, must be locked
- * @p: task to update the timestamp for
+ * dsq_insert_head - FIFO head insertion honoring %SCX_TASK_PROTECTED
+ * @dsq: DSQ to insert into
+ * @p: task being inserted
*
- * Update @p->scx.core_sched_at timestamp. This is used by scx_prio_less() to
- * implement global or local-DSQ FIFO ordering for core-sched. Should be called
- * when a task becomes runnable and its turn on the CPU ends (e.g. slice
- * exhaustion).
+ * A HEAD insert should land behind any leading protected tasks. Return %true
+ * indicates whether @p became the first entry.
*/
-static void touch_core_sched(struct rq *rq, struct task_struct *p)
+static bool dsq_insert_head(struct scx_dispatch_q *dsq, struct task_struct *p)
{
- lockdep_assert_rq_held(rq);
+ struct list_head *pos = &dsq->list;
+ struct scx_dsq_list_node *node;
-#ifdef CONFIG_SCHED_CORE
/*
- * It's okay to update the timestamp spuriously. Use
- * sched_core_disabled() which is cheaper than enabled().
- *
- * As this is used to determine ordering between tasks of sibling CPUs,
- * it may be better to use per-core dispatch sequence instead.
+ * Only rq-owned DSQs can hold protected tasks and the associated rq
+ * lock keeps their flags stable.
*/
- if (!sched_core_disabled())
- p->scx.core_sched_at = sched_clock_cpu(cpu_of(rq));
-#endif
+ if (!dsq_is_rq_owned(dsq)) {
+ list_add(&p->scx.dsq_list.node, &dsq->list);
+ return true;
+ }
+
+ list_for_each_entry(node, &dsq->list, node) {
+ struct task_struct *q;
+
+ if (WARN_ON_ONCE(node->flags & SCX_DSQ_LNODE_ITER_CURSOR))
+ continue;
+
+ q = container_of(node, struct task_struct, scx.dsq_list);
+ if (!(q->scx.flags & SCX_TASK_PROTECTED))
+ break;
+
+ pos = &node->node;
+ }
+
+ list_add(&p->scx.dsq_list.node, pos);
+
+ return pos == &dsq->list;
+}
+
+/**
+ * set_task_slice_keep_oob - Set @p's slice, leaving any pending oob request
+ * @p: task of interest
+ * @slice: slice to set
+ *
+ * While %SCX_TASK_PROTECTED is set, BPF schedulers may not modify the slice.
+ * Refuse and return %false.
+ */
+static bool set_task_slice_keep_oob(struct task_struct *p, u64 slice)
+{
+ lockdep_assert_rq_held(task_rq(p));
+
+ if (unlikely(p->scx.flags & SCX_TASK_PROTECTED))
+ return false;
+
+ p->scx.slice = slice;
+ return true;
+}
+
+/* set @p's slice, superseding any pending out-of-band request */
+bool scx_set_task_slice(struct task_struct *p, u64 slice)
+{
+ if (!set_task_slice_keep_oob(p, slice))
+ return false;
+ clear_task_slice_oob(p);
+ return true;
}
/**
- * touch_core_sched_dispatch - Update core-sched timestamp on dispatch
- * @rq: rq to read clock from, must be locked
- * @p: task being dispatched
+ * scx_task_slice_ended - @p's slice is consumed or given up
+ * @rq: rq @p is on
+ * @p: task of interest
*
- * If the BPF scheduler implements custom core-sched ordering via
- * ops.core_sched_before(), @p->scx.core_sched_at is used to implement FIFO
- * ordering within each local DSQ. This function is called from dispatch paths
- * and updates @p->scx.core_sched_at if custom core-sched ordering is in effect.
+ * End what rides on the slice - the protection, and the rescue if @p is being
+ * rescued.
+ *
+ * A dequeue normally ends the slice too. The exception is a save/restore pair
+ * on the running task. Attribute changes like renice cycle the task through
+ * dequeue and enqueue while it keeps executing, so the slice continues. A
+ * queued task instead loses its DSQ position on any dequeue and the slice ends
+ * with it.
*/
-static void touch_core_sched_dispatch(struct rq *rq, struct task_struct *p)
+void scx_task_slice_ended(struct rq *rq, struct task_struct *p)
{
lockdep_assert_rq_held(rq);
-#ifdef CONFIG_SCHED_CORE
- if (unlikely(SCX_HAS_OP(scx_root, core_sched_before)))
- touch_core_sched(rq, p);
-#endif
+ p->scx.flags &= ~SCX_TASK_PROTECTED;
+ if (unlikely(p == scx_rescuee(rq)))
+ scx_rescue_end(rq);
+}
+
+/* request @p's slice to be set to @slice, see the write rules above */
+static void set_task_slice_oob(struct scx_sched *sch, struct task_struct *p, u64 slice)
+{
+ u64 dur;
+
+ if (slice == SCX_SLICE_INF) {
+ dur = SCX_SLICE_OOB_DUR_MASK;
+ } else if (unlikely(slice >= SCX_SLICE_OOB_DUR_MASK)) {
+ dur = SCX_SLICE_OOB_DUR_MASK - 1;
+ scx_add_event(sch, SCX_EV_SLICE_CLAMPED, 1);
+ } else {
+ dur = slice;
+ }
+
+ atomic64_set(&p->scx.slice_oob, SCX_SLICE_OOB_PENDING |
+ ((sch->id & SCX_SLICE_OOB_ID_MASK) << SCX_SLICE_OOB_ID_SHIFT) | dur);
+}
+
+/*
+ * Apply a pending out-of-band slice request under @rq's lock. A request whose
+ * packed id no longer matches @p's current owner is dropped. An extension needs
+ * baseline cpu access on @p's cid, shortening is always allowed, and a
+ * protected slice refuses both. %SCX_EV_SLICE_DENIED counts the denials. See
+ * the write rules above.
+ */
+static void apply_task_slice_oob(struct rq *rq, struct task_struct *p)
+{
+ u64 oob, dur, slice;
+
+ lockdep_assert_rq_held(rq);
+
+ if (likely(!atomic64_read(&p->scx.slice_oob)))
+ return;
+
+ oob = atomic64_xchg(&p->scx.slice_oob, 0);
+ if (unlikely(!oob))
+ return;
+
+ /* the issuing scheduler no longer owns @p, drop the request */
+ if (unlikely(((oob >> SCX_SLICE_OOB_ID_SHIFT) & SCX_SLICE_OOB_ID_MASK) !=
+ (scx_task_sched(p)->id & SCX_SLICE_OOB_ID_MASK)))
+ return;
+
+ dur = oob & SCX_SLICE_OOB_DUR_MASK;
+ slice = dur == SCX_SLICE_OOB_DUR_MASK ? SCX_SLICE_INF : dur;
+
+ if (slice > p->scx.slice &&
+ unlikely(scx_missing_caps(scx_task_sched(p), cpu_of(rq), SCX_CAP_BASE))) {
+ __scx_add_event(scx_task_sched(p), SCX_EV_SLICE_DENIED, 1);
+ return;
+ }
+
+ if (unlikely(!set_task_slice_keep_oob(p, slice)))
+ __scx_add_event(scx_task_sched(p), SCX_EV_SLICE_DENIED, 1);
+}
+
+/*
+ * A dsq insert kfunc doesn't write slice or dsq_vtime. The verdict carries them
+ * and they are committed here, at the insertion. A zero @slice keeps the
+ * current value, floored at 1 so the task isn't treated as expired.
+ */
+static void apply_slice_vtime(struct task_struct *p, u64 slice, u64 vtime, u64 enq_flags)
+{
+ if (slice) {
+ p->scx.slice = slice;
+ /*
+ * An explicit slice supersedes a pending oob request. A carried
+ * default refill is not an explicit request and must keep it.
+ */
+ if (!(enq_flags & SCX_ENQ_SLICE_DFL))
+ clear_task_slice_oob(p);
+ } else if (!p->scx.slice) {
+ p->scx.slice = 1;
+ }
+
+ if (enq_flags & SCX_ENQ_DSQ_PRIQ)
+ p->scx.dsq_vtime = vtime;
}
static void update_curr_scx(struct rq *rq)
@@ -1323,15 +1401,18 @@ static void update_curr_scx(struct rq *rq)
struct task_struct *curr = rq->curr;
s64 delta_exec;
+ /* apply even on 0 delta_exec, callers may still act on the slice */
+ apply_task_slice_oob(rq, curr);
+
delta_exec = update_curr_common(rq);
if (unlikely(delta_exec <= 0))
return;
- if (curr->scx.slice != SCX_SLICE_INF) {
+ if (curr->scx.slice != SCX_SLICE_INF)
curr->scx.slice -= min_t(u64, curr->scx.slice, delta_exec);
- if (!curr->scx.slice)
- touch_core_sched(rq, curr);
- }
+
+ if (unlikely(curr == scx_rescuee(rq)))
+ scx_rescue_charge(rq, delta_exec);
dl_server_update(&rq->ext_server, delta_exec);
}
@@ -1357,8 +1438,8 @@ static void dsq_inc_nr(struct scx_dispatch_q *dsq, struct task_struct *p, u64 en
* to the CPU or dequeued. In both cases, the only way @p can go back to
* the BPF sched is through enqueueing. If being inserted into a local
* DSQ with IMMED, persist the state until the next enqueueing event in
- * do_enqueue_task() so that we can maintain IMMED protection through
- * e.g. SAVE/RESTORE cycles and slice extensions.
+ * scx_do_enqueue_task() so that we can maintain IMMED protection
+ * through e.g. SAVE/RESTORE cycles and slice extensions.
*/
if (enq_flags & SCX_ENQ_IMMED) {
if (unlikely(dsq->id != SCX_DSQ_LOCAL)) {
@@ -1381,7 +1462,7 @@ static void dsq_inc_nr(struct scx_dispatch_q *dsq, struct task_struct *p, u64 en
* done yet, @p can't go on the CPU immediately. Re-enqueue.
*/
if (unlikely(dsq->nr > 1 || !rq_is_open(rq, enq_flags)))
- schedule_reenq_local(rq, 0);
+ scx_schedule_reenq_local(rq, 0);
}
}
@@ -1403,7 +1484,11 @@ static void dsq_dec_nr(struct scx_dispatch_q *dsq, struct task_struct *p)
static void refill_task_slice_dfl(struct scx_sched *sch, struct task_struct *p)
{
- p->scx.slice = READ_ONCE(sch->slice_dfl);
+ /*
+ * A default refill is not an explicit request, so it must not drop a
+ * pending out-of-band one, which is applied when @p next runs.
+ */
+ set_task_slice_keep_oob(p, READ_ONCE(sch->slice_dfl));
__scx_add_event(sch, SCX_EV_REFILL_SLICE_DFL, 1);
}
@@ -1422,28 +1507,32 @@ static inline bool task_scx_migrating(struct task_struct *p)
return p->scx.sticky_cpu >= 0;
}
-/*
- * Call ops.dequeue() if the task is in BPF custody and not migrating.
- * Clears %SCX_TASK_IN_CUSTODY when the callback is invoked.
- */
-static void call_task_dequeue(struct scx_sched *sch, struct rq *rq,
- struct task_struct *p, u64 deq_flags)
+/* Must be called under the lock serializing @p's custody transfers. */
+static bool task_leave_custody(struct task_struct *p)
{
if (!(p->scx.flags & SCX_TASK_IN_CUSTODY) || task_scx_migrating(p))
- return;
-
- if (SCX_HAS_OP(sch, dequeue))
- SCX_CALL_OP_TASK(sch, dequeue, rq, p, deq_flags);
+ return false;
p->scx.flags &= ~SCX_TASK_IN_CUSTODY;
+ return true;
}
-static void local_dsq_post_enq(struct scx_sched *sch, struct scx_dispatch_q *dsq,
- struct task_struct *p, u64 enq_flags)
+static void rq_owned_post_enq(struct scx_sched *sch, struct rq *rq,
+ struct scx_dispatch_q *dsq, struct task_struct *p,
+ u64 enq_flags)
{
- struct rq *rq = container_of(dsq, struct rq, scx.local_dsq);
+ if (task_leave_custody(p) && SCX_HAS_OP(sch, dequeue))
+ SCX_CALL_OP_TASK(sch, dequeue, rq, p, 0);
- call_task_dequeue(sch, rq, p, 0);
+ /*
+ * Only local inserts get the wakeup treatment below. Rejects kick the
+ * deferred reenq and rescue parks are paced by the rescue timer.
+ */
+ if (unlikely(dsq->id != SCX_DSQ_LOCAL)) {
+ if (dsq->id == SCX_DSQ_REJECT)
+ schedule_deferred_locked(rq);
+ return;
+ }
/*
* Note that @rq's lock may be dropped between this enqueue and @p
@@ -1481,34 +1570,41 @@ static void local_dsq_post_enq(struct scx_sched *sch, struct scx_dispatch_q *dsq
wakeup_preempt(rq, p, 0);
/*
- * If @rq is in balance, the CPU is already vacant and looking for the
+ * If @rq is in dispatch, the CPU is already vacant and looking for the
* next task to run. No need to preempt or trigger resched after moving
* @p into its local DSQ.
* Note that the wakeup_preempt() above may have already triggered
* a resched if @rq->next_class was idle. It's harmless, since
* need_resched is cleared immediately after task pick.
*/
- if (rq->scx.flags & SCX_RQ_IN_BALANCE)
+ if (rq->scx.flags & SCX_RQ_IN_DISPATCH)
return;
if ((enq_flags & SCX_ENQ_PREEMPT) && p != rq->curr &&
rq->curr->sched_class == &ext_sched_class) {
- rq->curr->scx.slice = 0;
- resched_curr(rq);
+ if (likely(scx_set_task_slice(rq->curr, 0)))
+ resched_curr(rq);
+ else
+ __scx_add_event(sch, SCX_EV_SLICE_DENIED, 1);
}
}
-static void dispatch_enqueue(struct scx_sched *sch, struct rq *rq,
- struct scx_dispatch_q *dsq, struct task_struct *p,
- u64 enq_flags)
+static void scx_dispatch_enqueue(struct scx_sched *sch, struct rq *rq,
+ struct scx_dispatch_q *dsq, struct task_struct *p,
+ u64 slice, u64 vtime, u64 enq_flags)
{
- bool is_local = dsq->id == SCX_DSQ_LOCAL;
+ bool is_rq_owned = false;
+
+ if (dsq->id == SCX_DSQ_LOCAL) {
+ dsq = scx_resolve_local_dsq(sch, rq, p, &enq_flags);
+ is_rq_owned = true;
+ }
WARN_ON_ONCE(p->scx.dsq || !list_empty(&p->scx.dsq_list.node));
WARN_ON_ONCE((p->scx.dsq_flags & SCX_TASK_DSQ_ON_PRIQ) ||
!RB_EMPTY_NODE(&p->scx.dsq_priq));
- if (!is_local) {
+ if (!is_rq_owned) {
raw_spin_lock_nested(&dsq->lock,
(enq_flags & SCX_ENQ_NESTED) ? SINGLE_DEPTH_NESTING : 0);
@@ -1534,6 +1630,13 @@ static void dispatch_enqueue(struct scx_sched *sch, struct rq *rq,
enq_flags &= ~SCX_ENQ_DSQ_PRIQ;
}
+ /*
+ * @dsq is locked and @enq_flags is sanitized. Commit the carried slice
+ * and vtime before the PRIQ insertion below reads the new dsq_vtime.
+ */
+ if (enq_flags & SCX_ENQ_APPLY_SLICE)
+ apply_slice_vtime(p, slice, vtime, enq_flags);
+
if (enq_flags & SCX_ENQ_DSQ_PRIQ) {
struct rb_node *rbp;
@@ -1573,9 +1676,8 @@ static void dispatch_enqueue(struct scx_sched *sch, struct rq *rq,
dsq->id);
if (enq_flags & (SCX_ENQ_HEAD | SCX_ENQ_PREEMPT)) {
- list_add(&p->scx.dsq_list.node, &dsq->list);
/* new task inserted at head - use fastpath */
- if (!(dsq->id & SCX_DSQ_FLAG_BUILTIN))
+ if (dsq_insert_head(dsq, p) && !(dsq->id & SCX_DSQ_FLAG_BUILTIN))
rcu_assign_pointer(dsq->first_task, p);
} else {
/*
@@ -1603,19 +1705,31 @@ static void dispatch_enqueue(struct scx_sched *sch, struct rq *rq,
* ops_state first, both sides would modify p->scx.flags
* concurrently in a non-atomic way.
*/
- if (is_local) {
- local_dsq_post_enq(sch, dsq, p, enq_flags);
+ if (is_rq_owned) {
+ rq_owned_post_enq(sch, rq, dsq, p, enq_flags);
} else {
+ bool call_dequeue = false;
+
/*
- * Task on global/bypass DSQ: leave custody, task on
- * non-terminal DSQ: enter custody.
+ * Global and bypass DSQs are terminal - the task leaves the
+ * scheduler's custody, so ops.dequeue() fires. It can run
+ * without @p's rq lock (finish_dispatch() passes the dispatch
+ * rq); that's safe because dequeue_task_scx() waits on
+ * SCX_OPSS_DISPATCHING (see the ops_state note above) and so
+ * can't race it. A non-terminal DSQ keeps the task in custody.
+ * The custody transfer happens under @dsq->lock so that later
+ * consumers see the flag clear; the callback runs after
+ * @dsq->lock is dropped because it may lock a DSQ itself.
*/
if (dsq->id == SCX_DSQ_GLOBAL || dsq->id == SCX_DSQ_BYPASS)
- call_task_dequeue(sch, rq, p, 0);
+ call_dequeue = task_leave_custody(p);
else
p->scx.flags |= SCX_TASK_IN_CUSTODY;
raw_spin_unlock(&dsq->lock);
+
+ if (call_dequeue && SCX_HAS_OP(sch, dequeue))
+ SCX_CALL_OP_TASK(sch, dequeue, rq, p, 0);
}
/*
@@ -1626,8 +1740,7 @@ static void dispatch_enqueue(struct scx_sched *sch, struct rq *rq,
atomic_long_set_release(&p->scx.ops_state, SCX_OPSS_NONE);
}
-static void task_unlink_from_dsq(struct task_struct *p,
- struct scx_dispatch_q *dsq)
+void scx_task_unlink_from_dsq(struct task_struct *p, struct scx_dispatch_q *dsq)
{
WARN_ON_ONCE(list_empty(&p->scx.dsq_list.node));
@@ -1640,7 +1753,7 @@ static void task_unlink_from_dsq(struct task_struct *p,
list_del_init(&p->scx.dsq_list.node);
dsq_dec_nr(dsq, p);
- if (!(dsq->id & SCX_DSQ_FLAG_BUILTIN) && dsq->first_task == p) {
+ if (!(dsq->id & SCX_DSQ_FLAG_BUILTIN) && rcu_access_pointer(dsq->first_task) == p) {
struct task_struct *first_task;
first_task = nldsq_next_task(dsq, NULL, false);
@@ -1648,10 +1761,10 @@ static void task_unlink_from_dsq(struct task_struct *p,
}
}
-static void dispatch_dequeue(struct rq *rq, struct task_struct *p)
+void scx_dispatch_dequeue(struct rq *rq, struct task_struct *p)
{
struct scx_dispatch_q *dsq = p->scx.dsq;
- bool is_local = dsq == &rq->scx.local_dsq;
+ bool is_rq_owned = dsq && dsq_is_rq_owned(dsq);
lockdep_assert_rq_held(rq);
@@ -1675,7 +1788,7 @@ static void dispatch_dequeue(struct rq *rq, struct task_struct *p)
return;
}
- if (!is_local)
+ if (!is_rq_owned)
raw_spin_lock(&dsq->lock);
/*
@@ -1684,7 +1797,7 @@ static void dispatch_dequeue(struct rq *rq, struct task_struct *p)
*/
if (p->scx.holding_cpu < 0) {
/* @p must still be on @dsq, dequeue */
- task_unlink_from_dsq(p, dsq);
+ scx_task_unlink_from_dsq(p, dsq);
} else {
/*
* We're racing against dispatch_to_local_dsq() which already
@@ -1697,13 +1810,13 @@ static void dispatch_dequeue(struct rq *rq, struct task_struct *p)
}
p->scx.dsq = NULL;
- if (!is_local)
+ if (!is_rq_owned)
raw_spin_unlock(&dsq->lock);
}
/*
- * Abbreviated version of dispatch_dequeue() that can be used when both @p's rq
- * and dsq are locked.
+ * Abbreviated version of scx_dispatch_dequeue() that can be used when both
+ * @p's rq and dsq are locked.
*/
static void dispatch_dequeue_locked(struct task_struct *p,
struct scx_dispatch_q *dsq)
@@ -1711,7 +1824,7 @@ static void dispatch_dequeue_locked(struct task_struct *p,
lockdep_assert_rq_held(task_rq(p));
lockdep_assert_held(&dsq->lock);
- task_unlink_from_dsq(p, dsq);
+ scx_task_unlink_from_dsq(p, dsq);
p->scx.dsq = NULL;
}
@@ -1738,6 +1851,10 @@ static struct scx_dispatch_q *find_dsq_for_dispatch(struct scx_sched *sch,
else
dsq = find_user_dsq(sch, dsq_id);
+ /*
+ * Built-in DSQs are never inserted into dsq_hash, so REJECT and RESCUE
+ * hit the error below. They cannot be reached with an ID.
+ */
if (unlikely(!dsq)) {
scx_error(sch, "non-existent DSQ 0x%llx", dsq_id);
return find_global_dsq(sch, tcpu);
@@ -1749,7 +1866,7 @@ static struct scx_dispatch_q *find_dsq_for_dispatch(struct scx_sched *sch,
static void mark_direct_dispatch(struct scx_sched *sch,
struct task_struct *ddsp_task,
struct task_struct *p, u64 dsq_id,
- u64 enq_flags)
+ u64 slice, u64 vtime, u64 enq_flags)
{
/*
* Mark that dispatch already happened from ops.select_cpu() or
@@ -1773,6 +1890,8 @@ static void mark_direct_dispatch(struct scx_sched *sch,
WARN_ON_ONCE(p->scx.ddsp_dsq_id != SCX_DSQ_INVALID);
WARN_ON_ONCE(p->scx.ddsp_enq_flags);
+ p->scx.ddsp_slice = slice;
+ p->scx.ddsp_vtime = vtime;
p->scx.ddsp_dsq_id = dsq_id;
p->scx.ddsp_enq_flags = enq_flags;
}
@@ -1784,10 +1903,10 @@ static void mark_direct_dispatch(struct scx_sched *sch,
* - direct_dispatch(): cleared on the synchronous enqueue path, deferred
* dispatch keeps the state until consumed
* - process_ddsp_deferred_locals(): cleared after consuming deferred state,
- * - do_enqueue_task(): cleared on enqueue fallbacks where the dispatch
+ * - scx_do_enqueue_task(): cleared on enqueue fallbacks where the dispatch
* verdict is ignored (local/global/bypass)
- * - dequeue_task_scx(): cleared after dispatch_dequeue(), covering deferred
- * cancellation and holding_cpu races
+ * - dequeue_task_scx(): cleared after scx_dispatch_dequeue(), covering
+ * deferred cancellation and holding_cpu races
* - scx_disable_task(): cleared for queued wakeup tasks, which are excluded by
* the scx_bypass() loop, so that stale state is not reused by a subsequent
* scheduler instance
@@ -1804,9 +1923,7 @@ static void direct_dispatch(struct scx_sched *sch, struct task_struct *p,
struct rq *rq = task_rq(p);
struct scx_dispatch_q *dsq =
find_dsq_for_dispatch(sch, rq, p->scx.ddsp_dsq_id, task_cpu(p));
- u64 ddsp_enq_flags;
-
- touch_core_sched_dispatch(rq, p);
+ u64 ddsp_enq_flags, slice, vtime;
p->scx.ddsp_enq_flags |= enq_flags;
@@ -1846,12 +1963,15 @@ static void direct_dispatch(struct scx_sched *sch, struct task_struct *p,
}
ddsp_enq_flags = p->scx.ddsp_enq_flags;
+ slice = p->scx.ddsp_slice;
+ vtime = p->scx.ddsp_vtime;
clear_direct_dispatch(p);
- dispatch_enqueue(sch, rq, dsq, p, ddsp_enq_flags | SCX_ENQ_CLEAR_OPSS);
+ scx_dispatch_enqueue(sch, rq, dsq, p, slice, vtime,
+ ddsp_enq_flags | SCX_ENQ_APPLY_SLICE | SCX_ENQ_CLEAR_OPSS);
}
-static bool scx_rq_online(struct rq *rq)
+bool scx_rq_online(struct rq *rq)
{
/*
* Test both cpu_active() and %SCX_RQ_ONLINE. %SCX_RQ_ONLINE indicates
@@ -1863,8 +1983,8 @@ static bool scx_rq_online(struct rq *rq)
return likely((rq->scx.flags & SCX_RQ_ONLINE) && cpu_active(cpu_of(rq)));
}
-static void do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags,
- int sticky_cpu)
+void scx_do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags,
+ int sticky_cpu)
{
struct scx_sched *sch = scx_task_sched(p);
struct task_struct **ddsp_taskp;
@@ -1886,6 +2006,24 @@ static void do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags,
p->scx.flags &= ~SCX_TASK_IMMED;
/*
+ * A task reenqueued too many times without running means the scheduler
+ * keeps re-deciding a placement it can't honor, e.g. re-inserting to a
+ * cid it lacks caps on. Eject the owning scheduler and strand the task
+ * to be picked up during sched exit.
+ */
+ if (enq_flags & SCX_ENQ_REENQ) {
+ if (++p->scx.reenq_cnt > 1)
+ __scx_add_event(sch, SCX_EV_REENQ_REPEAT, 1);
+
+ if (unlikely(p->scx.reenq_cnt > SCX_REENQ_MAX_REPEAT)) {
+ __scx_exit(sch, SCX_EXIT_ERROR_REENQ, 0, cpu_of(rq),
+ "%s[%d] reenqueued %u times without running",
+ p->comm, p->pid, p->scx.reenq_cnt);
+ return;
+ }
+ }
+
+ /*
* If !scx_rq_online(), we already told the BPF scheduler that the CPU
* is offline and are just running the hotplug path. Don't bother the
* BPF scheduler.
@@ -1905,6 +2043,7 @@ static void do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags,
if (!(sch->ops.flags & SCX_OPS_ENQ_EXITING) &&
unlikely(p->flags & PF_EXITING)) {
__scx_add_event(sch, SCX_EV_ENQ_SKIP_EXITING, 1);
+ enq_flags |= SCX_ENQ_RESCUE; /* avoid looping on cap rejection */
goto local;
}
@@ -1918,8 +2057,14 @@ static void do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags,
if (unlikely(!SCX_HAS_OP(sch, enqueue)))
goto global;
- /* DSQ bypass didn't trigger, enqueue on the BPF scheduler */
- qseq = rq->scx.ops_qseq++ << SCX_OPSS_QSEQ_SHIFT;
+ /*
+ * DSQ bypass didn't trigger, enqueue on the BPF scheduler. Wrap the
+ * per-task qseq counter where the QSEQ field wraps and skip 0, which is
+ * what scx_bpf_dsq_insert() records for a task in NONE or DISPATCHING.
+ */
+ p->scx.ops_qseq = ((p->scx.ops_qseq + 1) &
+ (SCX_OPSS_QSEQ_MASK >> SCX_OPSS_QSEQ_SHIFT)) ?: 1;
+ qseq = (unsigned long)p->scx.ops_qseq << SCX_OPSS_QSEQ_SHIFT;
WARN_ON_ONCE(atomic_long_read(&p->scx.ops_state) != SCX_OPSS_NONE);
atomic_long_set(&p->scx.ops_state, SCX_OPSS_QUEUEING | qseq);
@@ -1951,7 +2096,7 @@ direct:
direct_dispatch(sch, p, enq_flags);
return;
local_norefill:
- dispatch_enqueue(sch, rq, &rq->scx.local_dsq, p, enq_flags);
+ scx_dispatch_enqueue(sch, rq, &rq->scx.local_dsq, p, 0, 0, enq_flags);
return;
local:
dsq = &rq->scx.local_dsq;
@@ -1964,15 +2109,9 @@ bypass:
goto enqueue;
enqueue:
- /*
- * For task-ordering, slice refill must be treated as implying the end
- * of the current slice. Otherwise, the longer @p stays on the CPU, the
- * higher priority it becomes from scx_prio_less()'s POV.
- */
- touch_core_sched(rq, p);
refill_task_slice_dfl(sch, p);
clear_direct_dispatch(p);
- dispatch_enqueue(sch, rq, dsq, p, enq_flags);
+ scx_dispatch_enqueue(sch, rq, dsq, p, 0, 0, enq_flags);
}
static bool task_runnable(const struct task_struct *p)
@@ -1990,36 +2129,61 @@ static void set_task_runnable(struct rq *rq, struct task_struct *p)
}
/*
- * list_add_tail() must be used. scx_bypass() depends on tasks being
- * appended to the runnable_list.
+ * list_add_tail() must be used. scx_bypass() and rq_offline_scx()
+ * depend on tasks being appended to the runnable_list.
*/
list_add_tail(&p->scx.runnable_node, &rq->scx.runnable_list);
+
+ /*
+ * Record the rq @p is runnable on, maintained under the rq lock so it
+ * stays valid unlike task_cpu(), which a remote wakeup can move under
+ * pi_lock alone.
+ */
+ WRITE_ONCE(p->scx.runnable_cpu, cpu_of(rq));
}
static void clr_task_runnable(struct task_struct *p, bool reset_runnable_at)
{
list_del_init(&p->scx.runnable_node);
- if (reset_runnable_at)
+ WRITE_ONCE(p->scx.runnable_cpu, -1);
+ if (reset_runnable_at) {
p->scx.flags |= SCX_TASK_RESET_RUNNABLE_AT;
+ p->scx.reenq_cnt = 0;
+ }
}
static void enqueue_task_scx(struct rq *rq, struct task_struct *p, int core_enq_flags)
{
struct scx_sched *sch = scx_task_sched(p);
int sticky_cpu = p->scx.sticky_cpu;
- u64 enq_flags = core_enq_flags | rq->scx.extra_enq_flags;
+ u64 enq_flags = core_enq_flags | rq->scx.remote_activate_enq_flags;
+
+ /*
+ * An SCX-internal migration ends on arrival. Clear sticky_cpu so @p can
+ * leave custody when inserted into the destination DSQ.
+ */
+ if (sticky_cpu >= 0)
+ p->scx.sticky_cpu = -1;
- if (enq_flags & ENQUEUE_WAKEUP)
+ /*
+ * SCX_RQ_IN_WAKEUP promises a task_woken_scx() call once this enqueue
+ * returns. Only the core's wakeup path delivers one. The flags stashed
+ * for a remote activation may carry the wakeup bit without it.
+ */
+ if (core_enq_flags & ENQUEUE_WAKEUP)
rq->scx.flags |= SCX_RQ_IN_WAKEUP;
/*
* Restoring a running task will be immediately followed by
* set_next_task_scx() which expects the task to not be on the BPF
* scheduler as tasks can only start running through local DSQs. Force
- * direct-dispatch into the local DSQ by setting the sticky_cpu.
+ * direct-dispatch into the local DSQ by setting the sticky_cpu. Mark
+ * IGNORE_CAPS to force entry into the local DSQ.
*/
- if (unlikely(enq_flags & ENQUEUE_RESTORE) && task_current(rq, p))
+ if (unlikely(enq_flags & ENQUEUE_RESTORE) && task_current(rq, p)) {
sticky_cpu = cpu_of(rq);
+ enq_flags |= SCX_ENQ_IGNORE_CAPS;
+ }
if (p->scx.flags & SCX_TASK_QUEUED) {
WARN_ON_ONCE(!task_runnable(p));
@@ -2034,17 +2198,11 @@ static void enqueue_task_scx(struct rq *rq, struct task_struct *p, int core_enq_
if (SCX_HAS_OP(sch, runnable) && !task_on_rq_migrating(p))
SCX_CALL_OP_TASK(sch, runnable, rq, p, enq_flags);
- if (enq_flags & SCX_ENQ_WAKEUP)
- touch_core_sched(rq, p);
-
/* Start dl_server if this is the first task being enqueued */
if (rq->scx.nr_running == 1)
dl_server_start(&rq->ext_server);
- do_enqueue_task(rq, p, enq_flags, sticky_cpu);
-
- if (sticky_cpu >= 0)
- p->scx.sticky_cpu = -1;
+ scx_do_enqueue_task(rq, p, enq_flags, sticky_cpu);
out:
rq->scx.flags &= ~SCX_RQ_IN_WAKEUP;
@@ -2078,7 +2236,7 @@ retry:
/*
* A queued task must always be in BPF scheduler's custody. If
* SCX_TASK_IN_CUSTODY is clear, finish_dispatch() on another
- * CPU has already passed call_task_dequeue() (which clears the
+ * CPU has already passed task_leave_custody() (which clears the
* flag), but has not yet written SCX_OPSS_NONE. That final
* store does not require this rq's lock, so retrying with
* cpu_relax() is bounded: we will observe NONE (or DISPATCHING,
@@ -2126,7 +2284,8 @@ retry:
* NONE but the task may still have %SCX_TASK_IN_CUSTODY set until
* it is enqueued on the destination.
*/
- call_task_dequeue(sch, rq, p, deq_flags);
+ if (task_leave_custody(p) && SCX_HAS_OP(sch, dequeue))
+ SCX_CALL_OP_TASK(sch, dequeue, rq, p, deq_flags);
}
static bool dequeue_task_scx(struct rq *rq, struct task_struct *p, int core_deq_flags)
@@ -2136,9 +2295,9 @@ static bool dequeue_task_scx(struct rq *rq, struct task_struct *p, int core_deq_
/*
* Set %SCX_DEQ_SCHED_CHANGE when the dequeue is due to a property
- * change (not sleep or core-sched pick).
+ * change (not sleep).
*/
- if (!(deq_flags & (DEQUEUE_SLEEP | SCX_DEQ_CORE_SCHED_EXEC)))
+ if (!(deq_flags & DEQUEUE_SLEEP))
deq_flags |= SCX_DEQ_SCHED_CHANGE;
if (!(p->scx.flags & SCX_TASK_QUEUED)) {
@@ -2156,13 +2315,15 @@ static bool dequeue_task_scx(struct rq *rq, struct task_struct *p, int core_deq_
*
* @p may go through multiple stopping <-> running transitions between
* here and put_prev_task_scx() if task attribute changes occur while
- * balance_one() leaves @rq unlocked. However, they don't contain any
+ * dispatch_one() leaves @rq unlocked. However, they don't contain any
* information meaningful to the BPF scheduler and can be suppressed by
* skipping the callbacks if the task is !QUEUED.
*/
- if (SCX_HAS_OP(sch, stopping) && task_current(rq, p)) {
+ if (task_current(rq, p) &&
+ (SCX_HAS_OP(sch, stopping) || unlikely(p == scx_rescuee(rq)))) {
update_curr_scx(rq);
- SCX_CALL_OP_TASK(sch, stopping, rq, p, false);
+ if (SCX_HAS_OP(sch, stopping))
+ SCX_CALL_OP_TASK(sch, stopping, rq, p, false);
}
if (SCX_HAS_OP(sch, quiescent) && !task_on_rq_migrating(p))
@@ -2177,7 +2338,12 @@ static bool dequeue_task_scx(struct rq *rq, struct task_struct *p, int core_deq_
rq->scx.nr_running--;
sub_nr_running(rq, 1);
- dispatch_dequeue(rq, p);
+ scx_dispatch_dequeue(rq, p);
+
+ /* see scx_task_slice_ended() for the save/restore exception */
+ if (!((deq_flags & DEQUEUE_SAVE) && task_current(rq, p)))
+ scx_task_slice_ended(rq, p);
+
clear_direct_dispatch(p);
return true;
}
@@ -2187,10 +2353,13 @@ static void yield_task_scx(struct rq *rq)
struct task_struct *p = rq->donor;
struct scx_sched *sch = scx_task_sched(p);
+ /* a yield gives the slice up */
+ scx_task_slice_ended(rq, p);
+
if (SCX_HAS_OP(sch, yield))
SCX_CALL_OP_2TASKS_RET(sch, yield, rq, p, NULL);
else
- p->scx.slice = 0;
+ scx_set_task_slice(p, 0);
}
static bool yield_to_task_scx(struct rq *rq, struct task_struct *to)
@@ -2198,6 +2367,9 @@ static bool yield_to_task_scx(struct rq *rq, struct task_struct *to)
struct task_struct *from = rq->donor;
struct scx_sched *sch = scx_task_sched(from);
+ /* like a plain yield, giving the slice up ends the protection */
+ scx_task_slice_ended(rq, from);
+
if (SCX_HAS_OP(sch, yield) && sch == scx_task_sched(to))
return SCX_CALL_OP_2TASKS_RET(sch, yield, rq, from, to);
else
@@ -2225,35 +2397,32 @@ static void wakeup_preempt_scx(struct rq *rq, struct task_struct *p, int wake_fl
* - A higher-priority wakes up while SCX dispatch is in progress.
*/
if (rq->scx.nr_immed)
- schedule_reenq_local(rq, 0);
+ scx_schedule_reenq_local(rq, 0);
}
-static void move_local_task_to_local_dsq(struct scx_sched *sch,
- struct task_struct *p, u64 enq_flags,
- struct scx_dispatch_q *src_dsq,
- struct rq *dst_rq)
+void scx_move_local_task_to_local_dsq(struct scx_sched *sch, struct task_struct *p,
+ u64 enq_flags, struct rq *dst_rq)
{
- struct scx_dispatch_q *dst_dsq = &dst_rq->scx.local_dsq;
+ struct scx_dispatch_q *dst_dsq = scx_resolve_local_dsq(sch, dst_rq, p, &enq_flags);
- /* @dsq is locked and @p is on @dst_rq */
- lockdep_assert_held(&src_dsq->lock);
lockdep_assert_rq_held(dst_rq);
WARN_ON_ONCE(p->scx.holding_cpu >= 0);
if (enq_flags & (SCX_ENQ_HEAD | SCX_ENQ_PREEMPT))
- list_add(&p->scx.dsq_list.node, &dst_dsq->list);
+ dsq_insert_head(dst_dsq, p);
else
list_add_tail(&p->scx.dsq_list.node, &dst_dsq->list);
dsq_inc_nr(dst_dsq, p, enq_flags);
p->scx.dsq = dst_dsq;
- local_dsq_post_enq(sch, dst_dsq, p, enq_flags);
+ rq_owned_post_enq(sch, dst_rq, dst_dsq, p, enq_flags);
}
/**
* move_remote_task_to_local_dsq - Move a task from a foreign rq to a local DSQ
+ * @sch: scheduler placing @p
* @p: task to move
* @enq_flags: %SCX_ENQ_*
* @src_rq: rq to move the task from, locked on entry, released on return
@@ -2261,7 +2430,8 @@ static void move_local_task_to_local_dsq(struct scx_sched *sch,
*
* Move @p which is currently on @src_rq to @dst_rq's local DSQ.
*/
-static void move_remote_task_to_local_dsq(struct task_struct *p, u64 enq_flags,
+static void move_remote_task_to_local_dsq(struct scx_sched *sch,
+ struct task_struct *p, u64 enq_flags,
struct rq *src_rq, struct rq *dst_rq)
{
lockdep_assert_rq_held(src_rq);
@@ -2274,19 +2444,22 @@ static void move_remote_task_to_local_dsq(struct task_struct *p, u64 enq_flags,
deactivate_task(src_rq, p, 0);
set_task_cpu(p, cpu_of(dst_rq));
- raw_spin_rq_unlock(src_rq);
- raw_spin_rq_lock(dst_rq);
+ switch_rq_lock(src_rq, dst_rq);
/*
- * We want to pass scx-specific enq_flags but activate_task() will
- * truncate the upper 32 bit. As we own @rq, we can pass them through
- * @rq->scx.extra_enq_flags instead.
+ * activate_task() below truncates enq_flags to 32 bits and re-derives
+ * @p's owner, dropping our scx flags and the placing @sch. We own @rq,
+ * so stash both across the call. The enqueue reads them back, keeping
+ * the scx flags and checking caps against the placer, not the owner.
*/
WARN_ON_ONCE(!cpumask_test_cpu(cpu_of(dst_rq), p->cpus_ptr));
- WARN_ON_ONCE(dst_rq->scx.extra_enq_flags);
- dst_rq->scx.extra_enq_flags = enq_flags;
+ WARN_ON_ONCE(dst_rq->scx.remote_activate_enq_flags ||
+ dst_rq->scx.remote_activate_sch);
+ dst_rq->scx.remote_activate_enq_flags = enq_flags;
+ dst_rq->scx.remote_activate_sch = sch;
activate_task(dst_rq, p, 0);
- dst_rq->scx.extra_enq_flags = 0;
+ dst_rq->scx.remote_activate_enq_flags = 0;
+ dst_rq->scx.remote_activate_sch = NULL;
}
/*
@@ -2307,6 +2480,7 @@ static void move_remote_task_to_local_dsq(struct task_struct *p, u64 enq_flags,
* no to the BPF scheduler initiated migrations while offline.
*
* The caller must ensure that @p and @rq are on different CPUs.
+ * If enforce == true, caller must hold @p's rq lock.
*/
static bool task_can_run_on_remote_rq(struct scx_sched *sch,
struct task_struct *p, struct rq *rq,
@@ -2314,6 +2488,14 @@ static bool task_can_run_on_remote_rq(struct scx_sched *sch,
{
s32 cpu = cpu_of(rq);
+ /*
+ * To prevent races with @p still running on its old CPU while switching
+ * out, make sure we're holding @p's rq lock so as not to risk
+ * erroneously killing the BPF scheduler.
+ */
+ if (enforce)
+ lockdep_assert_rq_held(task_rq(p));
+
WARN_ON_ONCE(task_cpu(p) == cpu);
/*
@@ -2358,13 +2540,14 @@ static bool task_can_run_on_remote_rq(struct scx_sched *sch,
}
/**
- * unlink_dsq_and_lock_src_rq() - Unlink task from its DSQ and lock its task_rq
+ * unlink_dsq_and_switch_rq_lock() - Unlink task and switch to its rq lock
* @p: target task
* @dsq: locked DSQ @p is currently on
+ * @locked_rq: currently locked rq
* @src_rq: rq @p is currently on, stable with @dsq locked
*
- * Called with @dsq locked but no rq's locked. We want to move @p to a different
- * DSQ, including any local DSQ, but are not locking @src_rq. Locking @src_rq is
+ * Called with @dsq and @locked_rq locked. We want to move @p to a different DSQ,
+ * including any local DSQ, but are not locking @src_rq. Locking @src_rq is
* required when transferring into a local DSQ. Even when transferring into a
* non-local DSQ, it's better to use the same mechanism to protect against
* dequeues and maintain the invariant that @p->scx.dsq can only change while
@@ -2381,43 +2564,42 @@ static bool task_can_run_on_remote_rq(struct scx_sched *sch,
* values afterwards, as this operation can't be preempted or recurse, the
* holding_cpu can never become this CPU again before we're done. Thus, we can
* tell whether we lost to dequeue by testing whether the holding_cpu still
- * points to this CPU. See dispatch_dequeue() for the counterpart.
+ * points to this CPU. See scx_dispatch_dequeue() for the counterpart.
*
* On return, @dsq is unlocked and @src_rq is locked. Returns %true if @p is
* still valid. %false if lost to dequeue.
*/
-static bool unlink_dsq_and_lock_src_rq(struct task_struct *p,
- struct scx_dispatch_q *dsq,
- struct rq *src_rq)
+static bool unlink_dsq_and_switch_rq_lock(struct task_struct *p,
+ struct scx_dispatch_q *dsq,
+ struct rq *locked_rq,
+ struct rq *src_rq)
{
s32 cpu = raw_smp_processor_id();
lockdep_assert_held(&dsq->lock);
+ lockdep_assert_rq_held(locked_rq);
WARN_ON_ONCE(p->scx.holding_cpu >= 0);
- task_unlink_from_dsq(p, dsq);
+ scx_task_unlink_from_dsq(p, dsq);
p->scx.holding_cpu = cpu;
raw_spin_unlock(&dsq->lock);
- raw_spin_rq_lock(src_rq);
+ switch_rq_lock(locked_rq, src_rq);
/* task_rq couldn't have changed if we're still the holding cpu */
return likely(p->scx.holding_cpu == cpu) &&
!WARN_ON_ONCE(src_rq != task_rq(p));
}
-static bool consume_remote_task(struct rq *this_rq,
+static bool consume_remote_task(struct scx_sched *sch, struct rq *this_rq,
struct task_struct *p, u64 enq_flags,
struct scx_dispatch_q *dsq, struct rq *src_rq)
{
- raw_spin_rq_unlock(this_rq);
-
- if (unlink_dsq_and_lock_src_rq(p, dsq, src_rq)) {
- move_remote_task_to_local_dsq(p, enq_flags, src_rq, this_rq);
+ if (unlink_dsq_and_switch_rq_lock(p, dsq, this_rq, src_rq)) {
+ move_remote_task_to_local_dsq(sch, p, enq_flags, src_rq, this_rq);
return true;
} else {
- raw_spin_rq_unlock(src_rq);
- raw_spin_rq_lock(this_rq);
+ switch_rq_lock(src_rq, this_rq);
return false;
}
}
@@ -2469,14 +2651,12 @@ static struct rq *move_task_between_dsqs(struct scx_sched *sch,
if (dst_dsq->id == SCX_DSQ_LOCAL) {
/* @p is going from a non-local DSQ to a local DSQ */
if (src_rq == dst_rq) {
- task_unlink_from_dsq(p, src_dsq);
- move_local_task_to_local_dsq(sch, p, enq_flags,
- src_dsq, dst_rq);
+ scx_task_unlink_from_dsq(p, src_dsq);
raw_spin_unlock(&src_dsq->lock);
+ scx_move_local_task_to_local_dsq(sch, p, enq_flags, dst_rq);
} else {
raw_spin_unlock(&src_dsq->lock);
- move_remote_task_to_local_dsq(p, enq_flags,
- src_rq, dst_rq);
+ move_remote_task_to_local_dsq(sch, p, enq_flags, src_rq, dst_rq);
}
} else {
/*
@@ -2486,14 +2666,14 @@ static struct rq *move_task_between_dsqs(struct scx_sched *sch,
dispatch_dequeue_locked(p, src_dsq);
raw_spin_unlock(&src_dsq->lock);
- dispatch_enqueue(sch, dst_rq, dst_dsq, p, enq_flags);
+ scx_dispatch_enqueue(sch, dst_rq, dst_dsq, p, 0, 0, enq_flags);
}
return dst_rq;
}
-static bool consume_dispatch_q(struct scx_sched *sch, struct rq *rq,
- struct scx_dispatch_q *dsq, u64 enq_flags)
+bool scx_consume_dispatch_q(struct scx_sched *sch, struct rq *rq,
+ struct scx_dispatch_q *dsq, u64 enq_flags)
{
struct task_struct *p;
retry:
@@ -2522,14 +2702,14 @@ retry:
break;
if (rq == task_rq) {
- task_unlink_from_dsq(p, dsq);
- move_local_task_to_local_dsq(sch, p, enq_flags, dsq, rq);
+ scx_task_unlink_from_dsq(p, dsq);
raw_spin_unlock(&dsq->lock);
+ scx_move_local_task_to_local_dsq(sch, p, enq_flags, rq);
return true;
}
if (task_can_run_on_remote_rq(sch, p, rq, false)) {
- if (likely(consume_remote_task(rq, p, enq_flags, dsq, task_rq)))
+ if (likely(consume_remote_task(sch, rq, p, enq_flags, dsq, task_rq)))
return true;
goto retry;
}
@@ -2539,11 +2719,11 @@ retry:
return false;
}
-static bool consume_global_dsq(struct scx_sched *sch, struct rq *rq)
+bool scx_consume_global_dsq(struct scx_sched *sch, struct rq *rq)
{
int node = cpu_to_node(cpu_of(rq));
- return consume_dispatch_q(sch, rq, &sch->pnode[node]->global_dsq, 0);
+ return scx_consume_dispatch_q(sch, rq, &sch->pnode[node]->global_dsq, 0);
}
/**
@@ -2552,6 +2732,8 @@ static bool consume_global_dsq(struct scx_sched *sch, struct rq *rq)
* @rq: current rq which is locked
* @dst_dsq: destination DSQ
* @p: task to dispatch
+ * @slice: slice carried by the insert verdict, 0 keeps the current value
+ * @vtime: vtime carried by the insert verdict, committed on PRIQ inserts
* @enq_flags: %SCX_ENQ_*
*
* We're holding @rq lock and want to dispatch @p to @dst_dsq which is a local
@@ -2562,8 +2744,8 @@ static bool consume_global_dsq(struct scx_sched *sch, struct rq *rq)
* %SCX_OPSS_DISPATCHING).
*/
static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq,
- struct scx_dispatch_q *dst_dsq,
- struct task_struct *p, u64 enq_flags)
+ struct scx_dispatch_q *dst_dsq, struct task_struct *p,
+ u64 slice, u64 vtime, u64 enq_flags)
{
struct rq *src_rq = task_rq(p);
struct rq *dst_rq = container_of(dst_dsq, struct rq, scx.local_dsq);
@@ -2576,15 +2758,8 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq,
* If dispatching to @rq that @p is already on, no lock dancing needed.
*/
if (rq == src_rq && rq == dst_rq) {
- dispatch_enqueue(sch, rq, dst_dsq, p,
- enq_flags | SCX_ENQ_CLEAR_OPSS);
- return;
- }
-
- if (src_rq != dst_rq &&
- unlikely(!task_can_run_on_remote_rq(sch, p, dst_rq, true))) {
- dispatch_enqueue(sch, rq, find_global_dsq(sch, task_cpu(p)), p,
- enq_flags | SCX_ENQ_CLEAR_OPSS | SCX_ENQ_GDSQ_FALLBACK);
+ scx_dispatch_enqueue(sch, rq, dst_dsq, p, slice, vtime,
+ enq_flags | SCX_ENQ_APPLY_SLICE | SCX_ENQ_CLEAR_OPSS);
return;
}
@@ -2597,7 +2772,7 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq,
* As DISPATCHING guarantees that @p is wholly ours, we can pretend that
* we're moving from a DSQ and use the same mechanism - mark the task
* under transfer with holding_cpu, release DISPATCHING and then follow
- * the same protocol. See unlink_dsq_and_lock_src_rq().
+ * the same protocol. See unlink_dsq_and_switch_rq_lock().
*/
p->scx.holding_cpu = raw_smp_processor_id();
@@ -2606,14 +2781,14 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq,
/* switch to @src_rq lock */
if (locked_rq != src_rq) {
- raw_spin_rq_unlock(locked_rq);
+ switch_rq_lock(locked_rq, src_rq);
locked_rq = src_rq;
- raw_spin_rq_lock(src_rq);
}
/* task_rq couldn't have changed if we're still the holding cpu */
if (likely(p->scx.holding_cpu == raw_smp_processor_id()) &&
!WARN_ON_ONCE(src_rq != task_rq(p))) {
+ bool fallback = false;
/*
* If @p is staying on the same rq, there's no need to go
* through the full deactivate/activate cycle. Optimize by
@@ -2621,33 +2796,41 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq,
*/
if (src_rq == dst_rq) {
p->scx.holding_cpu = -1;
- dispatch_enqueue(sch, dst_rq, &dst_rq->scx.local_dsq, p,
- enq_flags);
+ scx_dispatch_enqueue(sch, dst_rq, &dst_rq->scx.local_dsq, p,
+ slice, vtime, enq_flags | SCX_ENQ_APPLY_SLICE);
+ } else if (unlikely(!task_can_run_on_remote_rq(sch, p, dst_rq, true))) {
+ p->scx.holding_cpu = -1;
+ fallback = true;
+ scx_dispatch_enqueue(sch, src_rq, find_global_dsq(sch, task_cpu(p)),
+ p, slice, vtime,
+ enq_flags | SCX_ENQ_APPLY_SLICE |
+ SCX_ENQ_GDSQ_FALLBACK);
} else {
- move_remote_task_to_local_dsq(p, enq_flags,
- src_rq, dst_rq);
+ apply_slice_vtime(p, slice, vtime, enq_flags);
+ move_remote_task_to_local_dsq(sch, p, enq_flags, src_rq, dst_rq);
/* task has been moved to dst_rq, which is now locked */
locked_rq = dst_rq;
}
/* if the destination CPU is idle, wake it up */
- if (sched_class_above(p->sched_class, dst_rq->curr->sched_class))
+ if (!fallback && sched_class_above(p->sched_class, dst_rq->curr->sched_class))
resched_curr(dst_rq);
}
/* switch back to @rq lock */
- if (locked_rq != rq) {
- raw_spin_rq_unlock(locked_rq);
- raw_spin_rq_lock(rq);
- }
+ if (locked_rq != rq)
+ switch_rq_lock(locked_rq, rq);
}
/**
* finish_dispatch - Asynchronously finish dispatching a task
+ * @sch: the scheduler
* @rq: current rq which is locked
* @p: task to finish dispatching
* @qseq_at_dispatch: qseq when @p started getting dispatched
* @dsq_id: destination DSQ ID
+ * @slice: slice carried by the insert verdict, 0 keeps the current value
+ * @vtime: vtime carried by the insert verdict, committed on PRIQ inserts
* @enq_flags: %SCX_ENQ_*
*
* Dispatching to local DSQs may need to wait for queueing to complete or
@@ -2661,15 +2844,13 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq,
* was valid in the first place. Make sure that the task is still owned by the
* BPF scheduler and claim the ownership before dispatching.
*/
-static void finish_dispatch(struct scx_sched *sch, struct rq *rq,
- struct task_struct *p,
- unsigned long qseq_at_dispatch,
- u64 dsq_id, u64 enq_flags)
+static void finish_dispatch(struct scx_sched *sch, struct rq *rq, struct task_struct *p,
+ unsigned long qseq_at_dispatch, u64 dsq_id,
+ u64 slice, u64 vtime, u64 enq_flags)
{
struct scx_dispatch_q *dsq;
unsigned long opss;
- touch_core_sched_dispatch(rq, p);
retry:
/*
* No need for _acquire here. @p is accessed only after a successful
@@ -2710,10 +2891,10 @@ retry:
goto retry;
case SCX_OPSS_QUEUEING:
/*
- * do_enqueue_task() is in the process of transferring the task
- * to the BPF scheduler while holding @p's rq lock. As we aren't
- * holding any kernel or BPF resource that the enqueue path may
- * depend upon, it's safe to wait.
+ * scx_do_enqueue_task() is in the process of transferring the
+ * task to the BPF scheduler while holding @p's rq lock. As we
+ * aren't holding any kernel or BPF resource that the enqueue
+ * path may depend upon, it's safe to wait.
*/
wait_ops_state(p, opss);
goto retry;
@@ -2721,15 +2902,16 @@ retry:
BUG_ON(!(p->scx.flags & SCX_TASK_QUEUED));
- dsq = find_dsq_for_dispatch(sch, this_rq(), dsq_id, task_cpu(p));
+ dsq = find_dsq_for_dispatch(sch, rq, dsq_id, task_cpu(p));
if (dsq->id == SCX_DSQ_LOCAL)
- dispatch_to_local_dsq(sch, rq, dsq, p, enq_flags);
+ dispatch_to_local_dsq(sch, rq, dsq, p, slice, vtime, enq_flags);
else
- dispatch_enqueue(sch, rq, dsq, p, enq_flags | SCX_ENQ_CLEAR_OPSS);
+ scx_dispatch_enqueue(sch, rq, dsq, p, slice, vtime,
+ enq_flags | SCX_ENQ_APPLY_SLICE | SCX_ENQ_CLEAR_OPSS);
}
-static void flush_dispatch_buf(struct scx_sched *sch, struct rq *rq)
+void scx_flush_dispatch_buf(struct scx_sched *sch, struct rq *rq)
{
struct scx_dsp_ctx *dspc = &this_cpu_ptr(sch->pcpu)->dsp_ctx;
u32 u;
@@ -2738,7 +2920,7 @@ static void flush_dispatch_buf(struct scx_sched *sch, struct rq *rq)
struct scx_dsp_buf_ent *ent = &dspc->buf[u];
finish_dispatch(sch, rq, ent->task, ent->qseq, ent->dsq_id,
- ent->enq_flags);
+ ent->slice, ent->vtime, ent->enq_flags);
}
dspc->nr_tasks += dspc->cursor;
@@ -2758,125 +2940,18 @@ static inline void maybe_queue_balance_callback(struct rq *rq)
rq->scx.flags &= ~SCX_RQ_BAL_CB_PENDING;
}
-/*
- * One user of this function is scx_bpf_dispatch() which can be called
- * recursively as sub-sched dispatches nest. Always inline to reduce stack usage
- * from the call frame.
- */
-static __always_inline bool
-scx_dispatch_sched(struct scx_sched *sch, struct rq *rq,
- struct task_struct *prev, bool nested)
+static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev)
{
- struct scx_dsp_ctx *dspc = &this_cpu_ptr(sch->pcpu)->dsp_ctx;
- int nr_loops = SCX_DSP_MAX_LOOPS;
- s32 cpu = cpu_of(rq);
- bool prev_on_sch = (prev->sched_class == &ext_sched_class) &&
- scx_task_on_sched(sch, prev);
-
- if (consume_global_dsq(sch, rq))
- return true;
-
- if (bypass_dsp_enabled(sch)) {
- /* if @sch is bypassing, only the bypass DSQs are active */
- if (scx_bypassing(sch, cpu))
- return consume_dispatch_q(sch, rq, bypass_dsq(sch, cpu), 0);
-
-#ifdef CONFIG_EXT_SUB_SCHED
- /*
- * If @sch isn't bypassing but its children are, @sch is
- * responsible for making forward progress for both its own
- * tasks that aren't bypassing and the bypassing descendants'
- * tasks. The following implements a simple built-in behavior -
- * let each CPU try to run the bypass DSQ every Nth time.
- *
- * Later, if necessary, we can add an ops flag to suppress the
- * auto-consumption and a kfunc to consume the bypass DSQ and,
- * so that the BPF scheduler can fully control scheduling of
- * bypassed tasks.
- */
- struct scx_sched_pcpu *pcpu = per_cpu_ptr(sch->pcpu, cpu);
-
- if (!(pcpu->bypass_host_seq++ % SCX_BYPASS_HOST_NTH) &&
- consume_dispatch_q(sch, rq, bypass_dsq(sch, cpu), 0)) {
- __scx_add_event(sch, SCX_EV_SUB_BYPASS_DISPATCH, 1);
- return true;
- }
-#endif /* CONFIG_EXT_SUB_SCHED */
- }
-
- if (unlikely(!SCX_HAS_OP(sch, dispatch)) || !scx_rq_online(rq))
- return false;
-
- dspc->rq = rq;
-
- /*
- * The dispatch loop. Because flush_dispatch_buf() may drop the rq lock,
- * the local DSQ might still end up empty after a successful
- * ops.dispatch(). If the local DSQ is empty even after ops.dispatch()
- * produced some tasks, retry. The BPF scheduler may depend on this
- * looping behavior to simplify its implementation.
- */
- do {
- dspc->nr_tasks = 0;
-
- if (nested) {
- SCX_CALL_OP(sch, dispatch, rq, scx_cpu_arg(cpu),
- prev_on_sch ? prev : NULL);
- } else {
- /* stash @prev so that nested invocations can access it */
- rq->scx.sub_dispatch_prev = prev;
- SCX_CALL_OP(sch, dispatch, rq, scx_cpu_arg(cpu),
- prev_on_sch ? prev : NULL);
- rq->scx.sub_dispatch_prev = NULL;
- }
-
- flush_dispatch_buf(sch, rq);
-
- if ((prev->scx.flags & SCX_TASK_QUEUED) && prev->scx.slice) {
- rq->scx.flags |= SCX_RQ_BAL_KEEP;
- return true;
- }
- if (rq->scx.local_dsq.nr)
- return true;
- if (consume_global_dsq(sch, rq))
- return true;
-
- /*
- * ops.dispatch() can trap us in this loop by repeatedly
- * dispatching ineligible tasks. Break out once in a while to
- * allow the watchdog to run. As IRQ can't be enabled in
- * balance(), we want to complete this scheduling cycle and then
- * start a new one. IOW, we want to call resched_curr() on the
- * next, most likely idle, task, not the current one. Use
- * __scx_bpf_kick_cpu() for deferred kicking.
- */
- if (unlikely(!--nr_loops)) {
- scx_kick_cpu(sch, cpu, 0);
- break;
- }
- } while (dspc->nr_tasks);
-
- /*
- * Prevent the CPU from going idle while bypassed descendants have tasks
- * queued. Without this fallback, bypassed tasks could stall if the host
- * scheduler's ops.dispatch() doesn't yield any tasks.
- */
- if (bypass_dsp_enabled(sch))
- return consume_dispatch_q(sch, rq, bypass_dsq(sch, cpu), 0);
-
- return false;
-}
-
-static int balance_one(struct rq *rq, struct task_struct *prev)
-{
- struct scx_sched *sch = scx_root;
+ struct scx_sched *root_sch = scx_root_protected_live();
+ enum scx_dsp_verdict verdict;
s32 cpu = cpu_of(rq);
lockdep_assert_rq_held(rq);
- rq->scx.flags |= SCX_RQ_IN_BALANCE;
- rq->scx.flags &= ~SCX_RQ_BAL_KEEP;
+ rq->scx.flags |= SCX_RQ_IN_DISPATCH;
+
+ scx_process_sync_ecaps(rq, prev);
- if ((sch->ops.flags & SCX_OPS_HAS_CPU_PREEMPT) &&
+ if ((root_sch->ops.flags & SCX_OPS_HAS_CPU_PREEMPT) &&
unlikely(rq->scx.cpu_released)) {
/*
* If the previous sched_class for the current CPU was not SCX,
@@ -2884,8 +2959,8 @@ static int balance_one(struct rq *rq, struct task_struct *prev)
* core. This callback complements ->cpu_release(), which is
* emitted in switch_class().
*/
- if (sch->ops.cpu_acquire)
- SCX_CALL_OP(sch, cpu_acquire, rq, cpu, NULL);
+ if (root_sch->ops.cpu_acquire)
+ SCX_CALL_OP(root_sch, cpu_acquire, rq, cpu, NULL);
rq->scx.cpu_released = false;
}
@@ -2903,31 +2978,40 @@ static int balance_one(struct rq *rq, struct task_struct *prev)
* test.
*/
if ((prev->scx.flags & SCX_TASK_QUEUED) && prev->scx.slice &&
- !scx_bypassing(sch, cpu)) {
- rq->scx.flags |= SCX_RQ_BAL_KEEP;
+ !scx_bypassing(scx_task_sched(prev), cpu)) {
+ verdict = SCX_DSP_PREV;
goto has_tasks;
}
}
/* if there already are tasks to run, nothing to do */
- if (rq->scx.local_dsq.nr)
+ if (rq->scx.local_dsq.nr) {
+ verdict = SCX_DSP_LOCAL;
goto has_tasks;
+ }
- if (scx_dispatch_sched(sch, rq, prev, false))
+ verdict = scx_dispatch_sched(root_sch, rq, prev, false);
+ if (verdict != SCX_DSP_NONE)
goto has_tasks;
/*
- * Didn't find another task to run. Keep running @prev unless
- * %SCX_OPS_ENQ_LAST is in effect.
+ * Didn't find another task to run. Keep running @prev unless its own
+ * scheduler set %SCX_OPS_ENQ_LAST and takes the enqueue instead, see
+ * put_prev_task_scx(). Read the scheduler here as the dispatch above
+ * may have dropped the rq lock while @prev changed class or scheduler.
*/
- if ((prev->scx.flags & SCX_TASK_QUEUED) &&
- (!(sch->ops.flags & SCX_OPS_ENQ_LAST) || scx_bypassing(sch, cpu))) {
- rq->scx.flags |= SCX_RQ_BAL_KEEP;
- __scx_add_event(sch, SCX_EV_DISPATCH_KEEP_LAST, 1);
- goto has_tasks;
+ if (prev->scx.flags & SCX_TASK_QUEUED) {
+ struct scx_sched *prev_sch = scx_task_sched(prev);
+
+ if ((!(prev_sch->ops.flags & SCX_OPS_ENQ_LAST) ||
+ scx_bypassing(prev_sch, cpu)) && scx_task_can_stay_on_cpu(rq, prev)) {
+ __scx_add_event(prev_sch, SCX_EV_DISPATCH_KEEP_LAST, 1);
+ verdict = SCX_DSP_PREV;
+ goto has_tasks;
+ }
}
- rq->scx.flags &= ~SCX_RQ_IN_BALANCE;
- return false;
+ rq->scx.flags &= ~SCX_RQ_IN_DISPATCH;
+ return SCX_DSP_NONE;
has_tasks:
/*
@@ -2941,10 +3025,10 @@ has_tasks:
* between the IMMED queueing and the subsequent scheduling event.
*/
if (unlikely(rq->scx.local_dsq.nr > 1 && rq->scx.nr_immed))
- schedule_reenq_local(rq, 0);
+ scx_schedule_reenq_local(rq, 0);
- rq->scx.flags &= ~SCX_RQ_IN_BALANCE;
- return true;
+ rq->scx.flags &= ~SCX_RQ_IN_DISPATCH;
+ return verdict;
}
static void set_next_task_scx(struct rq *rq, struct task_struct *p, bool first)
@@ -2957,7 +3041,7 @@ static void set_next_task_scx(struct rq *rq, struct task_struct *p, bool first)
* dispatched. Call ops_dequeue() to notify the BPF scheduler.
*/
ops_dequeue(rq, p, SCX_DEQ_CORE_SCHED_EXEC);
- dispatch_dequeue(rq, p);
+ scx_dispatch_dequeue(rq, p);
}
p->se.exec_start = rq_clock_task(rq);
@@ -2968,26 +3052,43 @@ static void set_next_task_scx(struct rq *rq, struct task_struct *p, bool first)
clr_task_runnable(p, true);
+ /* apply any pending out-of-band slice request before the tick decision */
+ apply_task_slice_oob(rq, p);
+
/*
* @p is getting newly scheduled or got kicked after someone updated its
- * slice. Refresh whether tick can be stopped. See scx_can_stop_tick().
+ * slice. Update SCX_RQ_CAN_STOP_TICK to reflect whether the tick can be
+ * stopped. See scx_can_stop_tick().
+ *
+ * Moreover, refresh the load_avgs just when transitioning in and out of
+ * nohz. In the future, we might want to add a mechanism to update
+ * load_avgs periodically on tick-stopped CPUs.
*/
- if ((p->scx.slice == SCX_SLICE_INF) !=
- (bool)(rq->scx.flags & SCX_RQ_CAN_STOP_TICK)) {
- if (p->scx.slice == SCX_SLICE_INF)
+ if (p->scx.slice == SCX_SLICE_INF) {
+ if (!(rq->scx.flags & SCX_RQ_CAN_STOP_TICK)) {
+ /*
+ * Bypass mode always assigns finite slices, so @p
+ * can't have an infinite slice while bypassing.
+ * Therefore, sched_update_tick_dependency() can safely
+ * evaluate the outgoing task.
+ */
rq->scx.flags |= SCX_RQ_CAN_STOP_TICK;
- else
- rq->scx.flags &= ~SCX_RQ_CAN_STOP_TICK;
+ sched_update_tick_dependency(rq);
- sched_update_tick_dependency(rq);
+ update_other_load_avgs(rq);
+ }
+ } else {
+ if (rq->scx.flags & SCX_RQ_CAN_STOP_TICK) {
+ rq->scx.flags &= ~SCX_RQ_CAN_STOP_TICK;
+ update_other_load_avgs(rq);
+ }
/*
- * For now, let's refresh the load_avgs just when transitioning
- * in and out of nohz. In the future, we might want to add a
- * mechanism which calls the following periodically on
- * tick-stopped CPUs.
+ * @rq still references the outgoing scheduling context. A finite
+ * slice is sufficient by itself to require the tick.
*/
- update_other_load_avgs(rq);
+ if (tick_nohz_full_cpu(cpu_of(rq)))
+ tick_nohz_dep_set_cpu(cpu_of(rq), TICK_DEP_BIT_SCHED);
}
}
@@ -3005,7 +3106,7 @@ preempt_reason_from_class(const struct sched_class *class)
static void switch_class(struct rq *rq, struct task_struct *next)
{
- struct scx_sched *sch = scx_root;
+ struct scx_sched *sch = scx_root_protected_live();
const struct sched_class *next_class = next->sched_class;
if (!(sch->ops.flags & SCX_OPS_HAS_CPU_PREEMPT))
@@ -3027,7 +3128,7 @@ static void switch_class(struct rq *rq, struct task_struct *next)
* preempted, and it regaining control of the CPU.
*
* ->cpu_release() complements ->cpu_acquire(), which is emitted the
- * next time that balance_one() is invoked.
+ * next time that dispatch_one() is invoked.
*/
if (!rq->scx.cpu_released) {
if (sch->ops.cpu_release) {
@@ -3046,12 +3147,24 @@ static void put_prev_task_scx(struct rq *rq, struct task_struct *p,
struct task_struct *next)
{
struct scx_sched *sch = scx_task_sched(p);
+ bool rescue_keep = false;
/* see kick_sync_wait_bal_cb() */
smp_store_release(&rq->scx.kick_sync, rq->scx.kick_sync + 1);
update_curr_scx(rq);
+ /*
+ * If the slice is consumed, protection ends with it. A rescuee
+ * preempted beforehand keeps going, see scx_rescue_keep().
+ */
+ if (!p->scx.slice) {
+ if (unlikely(p == scx_rescuee(rq)))
+ rescue_keep = scx_rescue_keep(rq, p);
+ if (!rescue_keep)
+ scx_task_slice_ended(rq, p);
+ }
+
/* see dequeue_task_scx() on why we skip when !QUEUED */
if (SCX_HAS_OP(sch, stopping) && (p->scx.flags & SCX_TASK_QUEUED))
SCX_CALL_OP_TASK(sch, stopping, rq, p, true);
@@ -3065,14 +3178,34 @@ static void put_prev_task_scx(struct rq *rq, struct task_struct *p,
* forcing a different task. Leave it at the head of the local
* DSQ unless it was an IMMED task. IMMED tasks should not
* linger on a busy CPU, reenqueue them to the BPF scheduler.
+ *
+ * An open rescue must keep @p on the local DSQ even if the
+ * scheduler zeroed the slice in ops.stopping() above.
*/
- if (p->scx.slice && !scx_bypassing(sch, cpu_of(rq))) {
+ if ((p->scx.slice || unlikely(p == scx_rescuee(rq))) &&
+ !scx_bypassing(sch, cpu_of(rq))) {
if (p->scx.flags & SCX_TASK_IMMED) {
p->scx.flags |= SCX_TASK_REENQ_PREEMPTED;
- do_enqueue_task(rq, p, SCX_ENQ_REENQ, -1);
+ scx_do_enqueue_task(rq, p, SCX_ENQ_REENQ, -1);
p->scx.flags &= ~SCX_TASK_REENQ_REASON_MASK;
} else {
- dispatch_enqueue(sch, rq, &rq->scx.local_dsq, p, SCX_ENQ_HEAD);
+ u64 enq_flags = 0;
+
+ /*
+ * Keep a preempted rescue going. If preempted
+ * by another SCX task, append to the local DSQ,
+ * see scx_rescue_keep().
+ */
+ if (unlikely(p == scx_rescuee(rq))) {
+ enq_flags |= SCX_ENQ_IGNORE_CAPS;
+ if (!rescue_keep)
+ enq_flags |= SCX_ENQ_HEAD;
+ } else {
+ enq_flags |= SCX_ENQ_HEAD;
+ }
+
+ scx_dispatch_enqueue(sch, rq, &rq->scx.local_dsq, p, 0, 0,
+ enq_flags);
}
goto switch_class;
}
@@ -3082,12 +3215,19 @@ static void put_prev_task_scx(struct rq *rq, struct task_struct *p,
* sched_class, %SCX_OPS_ENQ_LAST must be set. Tell
* ops.enqueue() that @p is the only one available for this cpu,
* which should trigger an explicit follow-up scheduling event.
+ * This doesn't apply if the baseline access on the CPU is lost.
+ *
+ * Under core scheduling, a pick dispatches only when nothing is
+ * locally runnable and can legitimately go idle with @p still
+ * runnable (see do_pick_task_scx()).
*/
- if (next && sched_class_above(&ext_sched_class, next->sched_class)) {
- WARN_ON_ONCE(!(sch->ops.flags & SCX_OPS_ENQ_LAST));
- do_enqueue_task(rq, p, SCX_ENQ_LAST, -1);
+ if (next && sched_class_above(&ext_sched_class, next->sched_class) &&
+ scx_task_can_stay_on_cpu(rq, p)) {
+ WARN_ON_ONCE(!sched_core_enabled(rq) &&
+ !(sch->ops.flags & SCX_OPS_ENQ_LAST));
+ scx_do_enqueue_task(rq, p, SCX_ENQ_LAST, -1);
} else {
- do_enqueue_task(rq, p, 0, -1);
+ scx_do_enqueue_task(rq, p, 0, -1);
}
}
@@ -3098,12 +3238,27 @@ switch_class:
static void kick_sync_wait_bal_cb(struct rq *rq)
{
- struct scx_kick_syncs __rcu *ks = __this_cpu_read(scx_kick_syncs);
- unsigned long *ksyncs = rcu_dereference_sched(ks)->syncs;
+ struct scx_kick_syncs __rcu *ks;
+ unsigned long *ksyncs;
bool waited;
s32 cpu;
/*
+ * This callback is queued and normally flushed within @rq's own
+ * scheduling pass. However, dispatch can drop the rq lock while it sits
+ * queued, and lock takers in that window (the sched class change paths,
+ * the scx task iterator) flush pending balance callbacks on release,
+ * running this one on a foreign CPU whose snapshots are unrelated. The
+ * kicked CPUs are already on their way to advance the kick_syncs being
+ * waited on. Don't get in the way.
+ */
+ if (unlikely(cpu_of(rq) != smp_processor_id()))
+ return;
+
+ ks = __this_cpu_read(scx_kick_syncs);
+ ksyncs = rcu_dereference_sched(ks)->syncs;
+
+ /*
* Drop rq lock and enable IRQs while waiting. IRQs must be enabled
* — a target CPU may be waiting for us to process an IPI (e.g. TLB
* flush) while we wait for its kick_sync to advance.
@@ -3125,6 +3280,7 @@ retry:
continue;
}
+ scx_rq_lock_drop(rq);
raw_spin_rq_unlock_irq(rq);
while (READ_ONCE(cpu_rq(cpu)->scx.kick_sync) == ksyncs[cpu]) {
smp_store_release(&rq->scx.kick_sync, rq->scx.kick_sync + 1);
@@ -3144,27 +3300,23 @@ static struct task_struct *first_local_task(struct rq *rq)
struct task_struct, scx.dsq_list.node);
}
-static struct task_struct *
-do_pick_task_scx(struct rq *rq, struct rq_flags *rf, bool force_scx)
+/*
+ * Run dispatch and queue the follow-up work for a pick.
+ */
+static enum scx_dsp_verdict dispatch_pick(struct rq *rq, struct rq_flags *rf,
+ struct task_struct *prev)
{
- struct task_struct *prev = rq->curr;
- bool keep_prev;
- struct task_struct *p;
-
- /* see kick_sync_wait_bal_cb() */
- smp_store_release(&rq->scx.kick_sync, rq->scx.kick_sync + 1);
-
- rq_modified_begin(rq, &ext_sched_class);
+ enum scx_dsp_verdict verdict;
rq_unpin_lock(rq, rf);
- balance_one(rq, prev);
+ verdict = dispatch_one(rq, prev);
rq_repin_lock(rq, rf);
maybe_queue_balance_callback(rq);
/*
- * Defer to a balance callback which can drop rq lock and enable
- * IRQs. Waiting directly in the pick path would deadlock against
- * CPUs sending us IPIs (e.g. TLB flushes) while we wait for them.
+ * Defer to a balance callback which can drop rq lock and enable IRQs.
+ * Waiting directly in the pick path would deadlock against CPUs sending
+ * us IPIs (e.g. TLB flushes) while we wait for them.
*/
if (unlikely(rq->scx.kick_sync_pending)) {
rq->scx.kick_sync_pending = false;
@@ -3172,10 +3324,90 @@ do_pick_task_scx(struct rq *rq, struct rq_flags *rf, bool force_scx)
kick_sync_wait_bal_cb);
}
+ return verdict;
+}
+
+#ifdef CONFIG_SCHED_CORE
+/*
+ * Dispatch for a pick when core scheduling is enabled. The selection picks for
+ * all SMT siblings and the rq_i->core_pick state it builds must stay atomic
+ * throughout. If the dispatch released the rq lock, anything can have happened
+ * in between - return %SCX_DSP_RETRY to restart the selection against current
+ * state.
+ */
+static enum scx_dsp_verdict dispatch_core_pick(struct rq *rq, struct rq_flags *rf,
+ struct task_struct *prev)
+{
+ enum scx_dsp_verdict verdict;
+ u32 seq = rq->scx.lock_drop_seq;
+
+ /* another dispatch is in flight on @rq, let that handle it */
+ if (rq->scx.flags & SCX_RQ_IN_DISPATCH)
+ return SCX_DSP_NONE;
+
+ rq_unpin_lock(rq, rf);
+
+ verdict = dispatch_one(rq, prev);
+
+ if (cpu_of(rq) == smp_processor_id()) {
+ maybe_queue_balance_callback(rq);
+
+ /* see dispatch_pick() */
+ if (unlikely(rq->scx.kick_sync_pending)) {
+ rq->scx.kick_sync_pending = false;
+ queue_balance_callback(rq, &rq->scx.kick_sync_bal_cb,
+ kick_sync_wait_bal_cb);
+ }
+ } else if (unlikely(rq->scx.flags & SCX_RQ_BAL_CB_PENDING)) {
+ /*
+ * Balance callbacks must run in the context that queued them,
+ * so they can't be queued on another CPU's rq. Run the deferred
+ * work directly instead.
+ */
+ rq->scx.flags &= ~SCX_RQ_BAL_CB_PENDING;
+ run_deferred(rq);
+ }
+
+ rq_repin_lock(rq, rf);
+
+ /* if dispatch_one() released the rq lock, restart the selection */
+ if (rq->scx.lock_drop_seq != seq)
+ return SCX_DSP_RETRY;
+
+ return verdict;
+}
+#else /* CONFIG_SCHED_CORE */
+static enum scx_dsp_verdict dispatch_core_pick(struct rq *rq, struct rq_flags *rf,
+ struct task_struct *prev)
+{
+ return SCX_DSP_NONE;
+}
+#endif /* CONFIG_SCHED_CORE */
+
+static struct task_struct *
+do_pick_task_scx(struct rq *rq, struct rq_flags *rf, bool force_scx)
+{
+ struct task_struct *prev = rq->curr;
+ enum scx_dsp_verdict verdict;
+ struct task_struct *p;
+
+ /* see kick_sync_wait_bal_cb() */
+ smp_store_release(&rq->scx.kick_sync, rq->scx.kick_sync + 1);
+
+ rq_modified_begin(rq, &ext_sched_class);
+
+ if (sched_core_enabled(rq))
+ verdict = dispatch_core_pick(rq, rf, prev);
+ else
+ verdict = dispatch_pick(rq, rf, prev);
+
+ if (verdict == SCX_DSP_RETRY)
+ return RETRY_TASK;
+
/*
- * If any higher-priority sched class enqueued a runnable task on
- * this rq during balance_one(), abort and return RETRY_TASK, so
- * that the scheduler loop can restart.
+ * If any higher-priority sched class enqueued a runnable task on this
+ * rq during dispatch_one(), abort and return RETRY_TASK, so that the
+ * scheduler loop can restart.
*
* If @force_scx is true, always try to pick a SCHED_EXT task,
* regardless of any higher-priority sched classes activity.
@@ -3183,28 +3415,23 @@ do_pick_task_scx(struct rq *rq, struct rq_flags *rf, bool force_scx)
if (!force_scx && rq_modified_above(rq, &ext_sched_class))
return RETRY_TASK;
- keep_prev = rq->scx.flags & SCX_RQ_BAL_KEEP;
- if (unlikely(keep_prev &&
- prev->sched_class != &ext_sched_class)) {
- WARN_ON_ONCE(scx_enable_state() == SCX_ENABLED);
- keep_prev = false;
- }
-
/*
- * If balance_one() is telling us to keep running @prev, replenish slice
- * if necessary and keep running @prev. Otherwise, pop the first one
- * from the local DSQ.
+ * If we're keeping @prev, replenish slice if necessary and keep running
+ * @prev. Otherwise, pop the first one from the local DSQ.
*/
- if (keep_prev) {
+ if (verdict == SCX_DSP_PREV) {
p = prev;
- if (!p->scx.slice)
+ if (!p->scx.slice) {
+ /* the slice is consumed, protection ends */
+ scx_task_slice_ended(rq, p);
refill_task_slice_dfl(scx_task_sched(p), p);
+ }
} else {
p = first_local_task(rq);
if (!p)
return NULL;
- if (unlikely(!p->scx.slice)) {
+ if (unlikely(!p->scx.slice) && scx_task_can_stay_on_cpu(rq, p)) {
struct scx_sched *sch = scx_task_sched(p);
if (!scx_bypassing(sch, cpu_of(rq)) &&
@@ -3263,33 +3490,70 @@ void ext_server_init(struct rq *rq)
* usual sched_class'es and needs to find out the expected task ordering. For
* SCX, core-sched calls this function to interrogate the task ordering.
*
- * Unless overridden by ops.core_sched_before(), @p->scx.core_sched_at is used
- * to implement the default task ordering. The older the timestamp, the higher
- * priority the task - the global FIFO ordering matching the default scheduling
- * behavior.
+ * A pair of tasks owned by one scheduler is ordered by the owner's
+ * ops.core_sched_before(). A pair spanning two schedulers is ordered by their
+ * nearest common ancestor which implements the op - the one case where the op
+ * is called on tasks that the scheduler delegated to its sub-schedulers and may
+ * not be scheduling anymore.
*
- * When ops.core_sched_before() is enabled, @p->scx.core_sched_at is used to
- * implement FIFO ordering within each local DSQ. See pick_task_scx().
+ * When neither applies, or the deciding scheduler is bypassing on either task's
+ * CPU, the default ordering runs the task which has been waiting longer first.
+ * A running task counts as the most recently serviced and orders after every
+ * waiting task. Waiting tasks are compared by @p->scx.runnable_at.
+ *
+ * Return: %true if @a should run after @b.
*/
bool scx_prio_less(const struct task_struct *a, const struct task_struct *b,
bool in_fi)
{
struct scx_sched *sch_a = scx_task_sched(a);
struct scx_sched *sch_b = scx_task_sched(b);
+ struct scx_sched *sch = NULL;
+ bool a_running, b_running;
+
+ if (sch_a == sch_b) {
+ if (SCX_HAS_OP(sch_a, core_sched_before))
+ sch = sch_a;
+ } else {
+ s32 level;
+
+ for (level = min(sch_a->level, sch_b->level); level >= 0; level--) {
+ struct scx_sched *anc = sch_a->ancestors[level];
+
+ if (anc == sch_b->ancestors[level] &&
+ SCX_HAS_OP(anc, core_sched_before)) {
+ sch = anc;
+ break;
+ }
+ }
+ }
/*
+ * scx_prio_less() returns whether @a should run after @b while
+ * ops.core_sched_before() returns whether its first argument should run
+ * before the second. Swap the arguments.
+ *
* The const qualifiers are dropped from task_struct pointers when
* calling ops.core_sched_before(). Accesses are controlled by the
* verifier.
*/
- if (sch_a == sch_b && SCX_HAS_OP(sch_a, core_sched_before) &&
- !scx_bypassing(sch_a, task_cpu(a)))
- return SCX_CALL_OP_2TASKS_RET(sch_a, core_sched_before,
- task_rq(a),
- (struct task_struct *)a,
- (struct task_struct *)b);
- else
- return time_after64(a->scx.core_sched_at, b->scx.core_sched_at);
+ if (sch && !scx_bypassing(sch, task_cpu(a)) && !scx_bypassing(sch, task_cpu(b)))
+ return SCX_CALL_OP_2TASKS_RET(sch, core_sched_before, task_rq(a),
+ (struct task_struct *)b,
+ (struct task_struct *)a);
+
+ /*
+ * runnable_at is refreshed only on enqueue, so a task which keeps
+ * occupying its CPU carries a stale stamp. A running task is the most
+ * recently serviced whatever its stamp says. Order it after every
+ * waiting task.
+ */
+ a_running = a->on_cpu;
+ b_running = b->on_cpu;
+ if (a_running != b_running)
+ return a_running;
+
+ return time_after(a->scx.runnable_at, b->scx.runnable_at);
}
#endif /* CONFIG_SCHED_CORE */
@@ -3334,17 +3598,34 @@ static int select_task_rq_scx(struct task_struct *p, int prev_cpu, int wake_flag
} else {
s32 cpu;
+ /*
+ * While bypassing, the enqueue path routes @p to a bypass DSQ
+ * without consulting the direct-dispatch target, making the
+ * default selection pointless. It doesn't work anyway when the
+ * scheduler does its own idle tracking and the built-in idle
+ * cpumasks are not updated. Leave @p on @prev_cpu.
+ */
+ if (bypassing) {
+ __scx_add_event(sch, SCX_EV_BYPASS_DISPATCH, 1);
+ p->scx.selected_cpu = prev_cpu;
+ return prev_cpu;
+ }
+
cpu = scx_select_cpu_dfl(p, prev_cpu, wake_flags, NULL, 0);
if (cpu >= 0) {
- refill_task_slice_dfl(sch, p);
+ /*
+ * Carry the slice refill and let the insertion commit
+ * it under rq lock. See the write rules.
+ */
+ __scx_add_event(sch, SCX_EV_REFILL_SLICE_DFL, 1);
+ p->scx.ddsp_slice = READ_ONCE(sch->slice_dfl);
+ p->scx.ddsp_enq_flags = SCX_ENQ_SLICE_DFL;
p->scx.ddsp_dsq_id = SCX_DSQ_LOCAL;
} else {
cpu = prev_cpu;
}
p->scx.selected_cpu = cpu;
- if (bypassing)
- __scx_add_event(sch, SCX_EV_BYPASS_DISPATCH, 1);
return cpu;
}
}
@@ -3371,15 +3652,20 @@ static void set_cpus_allowed_scx(struct task_struct *p,
*
* Fine-grained memory write control is enforced by BPF making the const
* designation pointless. Cast it away when calling the operation.
+ *
+ * The cid form receives the initial mask when the task is enabled and
+ * hears about changes only afterwards, see struct scx_enable_args.
*/
- if (SCX_HAS_OP(sch, set_cpumask))
+ if (SCX_HAS_OP(sch, set_cpumask) &&
+ (!scx_is_cid_type() || scx_get_task_state(p) == SCX_TASK_ENABLED))
scx_call_op_set_cpumask(sch, task_rq(p), p, (struct cpumask *)p->cpus_ptr);
}
static void handle_hotplug(struct rq *rq, bool online)
{
- struct scx_sched *sch = scx_root;
+ struct scx_sched *sch = scx_root_protected();
s32 cpu = cpu_of(rq);
+ s32 cpu_or_cid = cpu;
atomic_long_inc(&scx_hotplug_seq);
@@ -3394,10 +3680,43 @@ static void handle_hotplug(struct rq *rq, bool online)
if (scx_enabled())
scx_idle_update_selcpu_topology(&sch->ops);
+ if (online)
+ scx_online_ecaps(rq);
+ else
+ scx_offline_ecaps(rq);
+
+ /*
+ * The tables can't be retired while this function is running as the
+ * retirement is inside cpus_read_lock. However, scx_cpu_arg() is
+ * awkward here as the tables can be NULL after root enable failure and
+ * lockdep would trigger without surrounding rcu_read_lock(). Open code
+ * the translation. If the table is NULL, the ops are also cleared and
+ * @cpu_or_cid goes unused.
+ */
+ if (scx_is_cid_type()) {
+ s16 *tbl = rcu_dereference_check(scx_cpu_to_cid_tbl,
+ lockdep_is_cpus_held());
+
+ if (tbl) {
+ struct scx_sched *pos;
+
+ cpu_or_cid = tbl[cpu];
+
+ guard(raw_spinlock_irqsave)(&scx_sched_lock);
+ list_for_each_entry(pos, &scx_sched_all, all) {
+ struct scx_cmask *mask = pos->online_cmask;
+
+ if (mask)
+ __assign_bit(cpu_or_cid, (unsigned long *)mask->bits,
+ online);
+ }
+ }
+ }
+
if (online && SCX_HAS_OP(sch, cpu_online))
- SCX_CALL_OP(sch, cpu_online, NULL, scx_cpu_arg(cpu));
+ SCX_CALL_OP(sch, cpu_online, NULL, cpu_or_cid);
else if (!online && SCX_HAS_OP(sch, cpu_offline))
- SCX_CALL_OP(sch, cpu_offline, NULL, scx_cpu_arg(cpu));
+ SCX_CALL_OP(sch, cpu_offline, NULL, cpu_or_cid);
else
scx_exit(sch, SCX_EXIT_UNREG_KERN,
SCX_ECODE_ACT_RESTART | SCX_ECODE_RSN_HOTPLUG,
@@ -3422,7 +3741,26 @@ static void rq_online_scx(struct rq *rq)
static void rq_offline_scx(struct rq *rq)
{
+ struct task_struct *p, *n;
+
rq->scx.flags &= ~SCX_RQ_ONLINE;
+
+ /* sched domain rebuilds call rq_offline with the CPU staying alive */
+ if (cpu_active(cpu_of(rq)))
+ return;
+
+ scx_rescue_flush(rq);
+
+ /*
+ * An offline CPU no longer calls ops.dispatch(). Re-enqueue its tasks
+ * onto the local DSQ so that they run here and balance_push() moves
+ * them off.
+ */
+ list_for_each_entry_safe_reverse(p, n, &rq->scx.runnable_list, scx.runnable_node) {
+ if (p->scx.dsq == &rq->scx.local_dsq)
+ continue;
+ guard(sched_change)(p, DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK);
+ }
}
static bool check_rq_for_timeouts(struct rq *rq)
@@ -3443,8 +3781,19 @@ static bool check_rq_for_timeouts(struct rq *rq)
if (unlikely(time_after(jiffies,
last_runnable + READ_ONCE(sch->watchdog_timeout)))) {
+ struct scx_dispatch_q *dsq = READ_ONCE(p->scx.dsq);
u32 dur_ms = jiffies_to_msecs(jiffies - last_runnable);
+ /*
+ * A task can be stuck on a DSQ that a sched other than
+ * its owner is responsible for draining, e.g. an
+ * ancestor's bypass DSQ while the owner is bypassing.
+ * Blame the drainer. The local DSQ is consumed by the
+ * cpu itself and keeps blame on the owner.
+ */
+ if (dsq && dsq->sched && dsq->id != SCX_DSQ_LOCAL)
+ sch = dsq->sched;
+
__scx_exit(sch, SCX_EXIT_ERROR_STALL, 0, cpu_of(rq),
"%s[%d] failed to run for %u.%03us",
p->comm, p->pid, dur_ms / 1000,
@@ -3509,15 +3858,13 @@ static void task_tick_scx(struct rq *rq, struct task_struct *curr, int queued)
update_curr_scx(rq);
/*
- * While disabling, always resched and refresh core-sched timestamp as
- * we can't trust the slice management or ops.core_sched_before().
+ * While disabling, always resched as we can't trust the slice
+ * management.
*/
- if (scx_bypassing(sch, cpu_of(rq))) {
- curr->scx.slice = 0;
- touch_core_sched(rq, curr);
- } else if (SCX_HAS_OP(sch, tick)) {
+ if (scx_bypassing(sch, cpu_of(rq)))
+ scx_set_task_slice(curr, 0);
+ else if (SCX_HAS_OP(sch, tick))
SCX_CALL_OP_TASK(sch, tick, rq, curr);
- }
if (!curr->scx.slice)
resched_curr(rq);
@@ -3537,15 +3884,28 @@ static struct cgroup *tg_cgrp(struct task_group *tg)
return &cgrp_dfl_root.cgrp;
}
-#define SCX_INIT_TASK_ARGS_CGROUP(tg) .cgroup = tg_cgrp(tg),
+#define SCX_INIT_TASK_ARGS_CGROUP(cgrp) .cgroup = (cgrp),
#else /* CONFIG_EXT_GROUP_SCHED */
-#define SCX_INIT_TASK_ARGS_CGROUP(tg)
+#define SCX_INIT_TASK_ARGS_CGROUP(cgrp)
#endif /* CONFIG_EXT_GROUP_SCHED */
-static int __scx_init_task(struct scx_sched *sch, struct task_struct *p, bool fork)
+/**
+ * __scx_init_task - Initialize a task for a sched
+ * @sch: sched to initialize @p for
+ * @p: task of interest
+ * @cgrp: cgroup @p is joining, %NULL for @p's current task_group's cgroup
+ * @fork: %true if @p is being forked
+ *
+ * Pre-commit cgroup migration passes @cgrp explicitly as @p's task_group
+ * still reflects the source.
+ *
+ * Return 0 on success, -errno on failure.
+ */
+int __scx_init_task(struct scx_sched *sch, struct task_struct *p,
+ struct cgroup *cgrp, bool fork)
{
int ret;
@@ -3553,13 +3913,13 @@ static int __scx_init_task(struct scx_sched *sch, struct task_struct *p, bool fo
if (SCX_HAS_OP(sch, init_task)) {
struct scx_init_task_args args = {
- SCX_INIT_TASK_ARGS_CGROUP(task_group(p))
+ SCX_INIT_TASK_ARGS_CGROUP(cgrp ?: tg_cgrp(task_group(p)))
.fork = fork,
};
ret = SCX_CALL_OP_RET(sch, init_task, NULL, p, &args);
if (unlikely(ret)) {
- ret = ops_sanitize_err(sch, "init_task", ret);
+ ret = scx_ops_sanitize_err(sch, "init_task", ret);
return ret;
}
}
@@ -3571,6 +3931,9 @@ static int __scx_init_task(struct scx_sched *sch, struct task_struct *p, bool fo
} else if (unlikely(fork)) {
scx_error(sch, "ops.init_task() set task->scx.disallow for %s[%d] during fork",
p->comm, p->pid);
+ } else if (unlikely(scx_enable_state() != SCX_ENABLING)) {
+ scx_error(sch, "ops.init_task() set task->scx.disallow for %s[%d] outside the enable path",
+ p->comm, p->pid);
} else {
struct rq *rq;
struct rq_flags rf;
@@ -3621,14 +3984,33 @@ static void __scx_enable_task(struct scx_sched *sch, struct task_struct *p)
p->scx.weight = sched_weight_to_cgroup(weight);
- if (SCX_HAS_OP(sch, enable))
- SCX_CALL_OP_TASK(sch, enable, rq, p);
+ if (SCX_HAS_OP(sch, enable)) {
+ if (scx_is_cid_type()) {
+ struct scx_cmask *cmask = scx_fill_cmask_scratch(sch, p->cpus_ptr);
+ struct scx_enable_args args = {
+ .cmask_arena_addr = scx_kaddr_to_arena(sch, cmask),
+ };
+
+ SCX_CALL_CID_OP_TASK(sch, enable, rq, p, &args);
+ } else {
+ SCX_CALL_OP_TASK(sch, enable, rq, p);
+ }
+ }
+
+ /*
+ * The initial mask also goes out through set_cmask() so a scheduler can
+ * track affinity there alone, and before set_weight() so that the mask
+ * is in place when weight-dependent state is derived, see struct
+ * scx_enable_args.
+ */
+ if (scx_is_cid_type() && SCX_HAS_OP(sch, set_cpumask))
+ scx_call_op_set_cpumask(sch, rq, p, p->cpus_ptr);
if (SCX_HAS_OP(sch, set_weight))
SCX_CALL_OP_TASK(sch, set_weight, rq, p, p->scx.weight);
}
-static void scx_enable_task(struct scx_sched *sch, struct task_struct *p)
+void scx_enable_task(struct scx_sched *sch, struct task_struct *p)
{
__scx_enable_task(sch, p);
scx_set_task_state(p, SCX_TASK_ENABLED);
@@ -3648,6 +4030,15 @@ static void scx_disable_task(struct scx_sched *sch, struct task_struct *p)
scx_set_task_state(p, SCX_TASK_READY);
/*
+ * Reset the SCX-managed fields when @p leaves the BPF scheduler's
+ * control, after ops.disable() has observed their final values.
+ */
+ p->scx.dsq_vtime = 0;
+ scx_task_slice_ended(rq, p);
+ scx_set_task_slice(p, 0);
+ p->scx.reenq_cnt = 0;
+
+ /*
* Verify the task is not in BPF scheduler's custody. If flag
* transitions are consistent, the flag should always be clear
* here.
@@ -3655,8 +4046,7 @@ static void scx_disable_task(struct scx_sched *sch, struct task_struct *p)
WARN_ON_ONCE(p->scx.flags & SCX_TASK_IN_CUSTODY);
}
-static void __scx_disable_and_exit_task(struct scx_sched *sch,
- struct task_struct *p)
+void __scx_disable_and_exit_task(struct scx_sched *sch, struct task_struct *p)
{
struct scx_exit_task_args args = {
.cancelled = false,
@@ -3690,19 +4080,19 @@ static void __scx_disable_and_exit_task(struct scx_sched *sch,
* ran. The task state has not been transitioned, so this mirrors the
* SCX_TASK_INIT branch in __scx_disable_and_exit_task().
*/
-static void scx_sub_init_cancel_task(struct scx_sched *sch, struct task_struct *p)
+void scx_sub_init_cancel_task(struct scx_sched *sch, struct task_struct *p)
{
struct scx_exit_task_args args = { .cancelled = true };
lockdep_assert_held(&p->pi_lock);
lockdep_assert_rq_held(task_rq(p));
+ /* @p was never associated with @sch, dispatch on the explicit @sch */
if (SCX_HAS_OP(sch, exit_task))
- SCX_CALL_OP_TASK(sch, exit_task, task_rq(p), p, &args);
+ __SCX_CALL_OP_TASK(sch, ops, exit_task, task_rq(p), p, &args);
}
-static void scx_disable_and_exit_task(struct scx_sched *sch,
- struct task_struct *p)
+void scx_disable_and_exit_task(struct scx_sched *sch, struct task_struct *p)
{
__scx_disable_and_exit_task(sch, p);
@@ -3730,6 +4120,7 @@ void init_scx_entity(struct sched_ext_entity *scx)
RB_CLEAR_NODE(&scx->dsq_priq);
scx->sticky_cpu = -1;
scx->holding_cpu = -1;
+ scx->runnable_cpu = -1;
INIT_LIST_HEAD(&scx->runnable_node);
scx->runnable_at = jiffies;
scx->ddsp_dsq_id = SCX_DSQ_INVALID;
@@ -3784,12 +4175,12 @@ int scx_fork(struct task_struct *p, struct kernel_clone_args *kargs)
if (scx_init_task_enabled) {
#ifdef CONFIG_EXT_SUB_SCHED
- struct scx_sched *sch = kargs->cset->dfl_cgrp->scx_sched;
+ struct scx_sched *sch = scx_cgroup_sched(kargs->cset->dfl_cgrp);
#else
- struct scx_sched *sch = scx_root;
+ struct scx_sched *sch = scx_root_protected_live();
#endif
scx_set_task_state(p, SCX_TASK_INIT_BEGIN);
- ret = __scx_init_task(sch, p, true);
+ ret = __scx_init_task(sch, p, NULL, true);
if (unlikely(ret)) {
scx_set_task_state(p, SCX_TASK_NONE);
return ret;
@@ -3832,7 +4223,7 @@ void scx_post_fork(struct task_struct *p)
void scx_cancel_fork(struct task_struct *p)
{
- if (scx_enabled()) {
+ if (scx_init_task_enabled) {
struct rq *rq;
struct rq_flags rf;
@@ -3925,6 +4316,17 @@ static void reweight_task_scx(struct rq *rq, struct task_struct *p,
if (task_dead_and_done(p))
return;
+ /*
+ * When switching sched_class away from SCX, reweight_task_scx()
+ * is called _after_ scx_disable_task(). Skip calling ops.set_weight()
+ * since the BPF scheduler may have already forgotten the task in
+ * ops.disable().
+ * p->scx.weight will be recalculated in scx_enable_task() if the task
+ * ever returns to SCX class.
+ */
+ if (scx_get_task_state(p) != SCX_TASK_ENABLED)
+ return;
+
p->scx.weight = sched_weight_to_cgroup(scale_load_down(lw->weight));
if (SCX_HAS_OP(sch, set_weight))
SCX_CALL_OP_TASK(sch, set_weight, rq, p, p->scx.weight);
@@ -3945,9 +4347,10 @@ static void switching_to_scx(struct rq *rq, struct task_struct *p)
/*
* set_cpus_allowed_scx() is not called while @p is associated with a
- * different scheduler class. Keep the BPF scheduler up-to-date.
+ * different scheduler class. Keep the BPF scheduler up-to-date. The cid
+ * form gets its mask from scx_enable_task().
*/
- if (SCX_HAS_OP(sch, set_cpumask))
+ if (!scx_is_cid_type() && SCX_HAS_OP(sch, set_cpumask))
scx_call_op_set_cpumask(sch, rq, p, (struct cpumask *)p->cpus_ptr);
}
@@ -4002,13 +4405,15 @@ static void process_ddsp_deferred_locals(struct rq *rq)
struct scx_dispatch_q *dsq;
u64 dsq_id = p->scx.ddsp_dsq_id;
u64 enq_flags = p->scx.ddsp_enq_flags;
+ u64 slice = p->scx.ddsp_slice;
+ u64 vtime = p->scx.ddsp_vtime;
list_del_init(&p->scx.dsq_list.node);
clear_direct_dispatch(p);
dsq = find_dsq_for_dispatch(sch, rq, dsq_id, task_cpu(p));
if (!WARN_ON_ONCE(dsq->id != SCX_DSQ_LOCAL))
- dispatch_to_local_dsq(sch, rq, dsq, p, enq_flags);
+ dispatch_to_local_dsq(sch, rq, dsq, p, slice, vtime, enq_flags);
}
}
@@ -4032,16 +4437,20 @@ static void process_ddsp_deferred_locals(struct rq *rq)
* Reenqueued tasks go through ops.enqueue() with %SCX_ENQ_REENQ |
* %SCX_TASK_REENQ_IMMED. If the BPF scheduler dispatches back to the same local
* DSQ with %SCX_ENQ_IMMED while the CPU is still unavailable, this triggers
- * another reenq cycle. Repetitions are bounded by %SCX_REENQ_LOCAL_MAX_REPEAT
- * in process_deferred_reenq_locals().
+ * another reenq cycle. Repetitions are bounded by %SCX_REENQ_MAX_REPEAT in
+ * scx_do_enqueue_task(), which ejects the task's owning scheduler.
*/
-static bool local_task_should_reenq(struct task_struct *p, u64 *reenq_flags, u32 *reason)
+static bool local_task_should_reenq(struct rq *rq, struct task_struct *p,
+ u64 *reenq_flags, u32 *reason)
{
bool first;
first = !(*reenq_flags & SCX_REENQ_TSR_NOT_FIRST);
*reenq_flags |= SCX_REENQ_TSR_NOT_FIRST;
+ if (unlikely((p->scx.flags & SCX_TASK_PROTECTED) || p == scx_rescuee(rq)))
+ return false;
+
*reason = SCX_TASK_REENQ_KFUNC;
if ((p->scx.flags & SCX_TASK_IMMED) &&
@@ -4051,9 +4460,26 @@ static bool local_task_should_reenq(struct task_struct *p, u64 *reenq_flags, u32
return true;
}
+ if ((*reenq_flags & SCX_REENQ_CAP_REVOKE) &&
+ scx_task_reenq_on_cap_revoke(rq, p)) {
+ *reason = SCX_TASK_REENQ_CAP;
+ return true;
+ }
+
return *reenq_flags & SCX_REENQ_ANY;
}
+/*
+ * The dispatcher stores the final ops_state after dropping the DSQ lock, so @p
+ * can be found on a DSQ while still %SCX_OPSS_DISPATCHING. Reenqueueing @p
+ * before that store lands would have it clobber the new %SCX_OPSS_QUEUED.
+ */
+void scx_reenq_wait_dispatching(struct task_struct *p)
+{
+ if (unlikely(atomic_long_read_acquire(&p->scx.ops_state) == SCX_OPSS_DISPATCHING))
+ wait_ops_state(p, SCX_OPSS_DISPATCHING);
+}
+
static u32 reenq_local(struct scx_sched *sch, struct rq *rq, u64 reenq_flags)
{
LIST_HEAD(tasks);
@@ -4094,10 +4520,11 @@ static u32 reenq_local(struct scx_sched *sch, struct rq *rq, u64 reenq_flags)
if (!scx_is_descendant(task_sch, sch))
continue;
- if (!local_task_should_reenq(p, &reenq_flags, &reason))
+ if (!local_task_should_reenq(rq, p, &reenq_flags, &reason))
continue;
- dispatch_dequeue(rq, p);
+ scx_reenq_wait_dispatching(p);
+ scx_dispatch_dequeue(rq, p);
if (WARN_ON_ONCE(p->scx.flags & SCX_TASK_REENQ_REASON_MASK))
p->scx.flags &= ~SCX_TASK_REENQ_REASON_MASK;
@@ -4109,25 +4536,41 @@ static u32 reenq_local(struct scx_sched *sch, struct rq *rq, u64 reenq_flags)
list_for_each_entry_safe(p, n, &tasks, scx.dsq_list.node) {
list_del_init(&p->scx.dsq_list.node);
- do_enqueue_task(rq, p, SCX_ENQ_REENQ, -1);
+ scx_do_enqueue_task(rq, p, SCX_ENQ_REENQ, -1);
p->scx.flags &= ~SCX_TASK_REENQ_REASON_MASK;
nr_enqueued++;
}
+ /*
+ * The revoke that scheduled this scan may have raced the pick: curr
+ * may be a now-capless task, either one that kept running or one
+ * promoted off the local DSQ between the ecaps sync and this scan.
+ * Zero the slice to evict it. The enqueue gate blocks new capless
+ * inserts, so no later pick can slip through after the scan.
+ */
+ if ((reenq_flags & SCX_REENQ_CAP_REVOKE) &&
+ rq->curr->sched_class == &ext_sched_class &&
+ scx_task_reenq_on_cap_revoke(rq, rq->curr)) {
+ scx_set_task_slice(rq->curr, 0);
+ resched_curr(rq);
+ }
+
return nr_enqueued;
}
static void process_deferred_reenq_locals(struct rq *rq)
{
- u64 seq = ++rq->scx.deferred_reenq_locals_seq;
-
lockdep_assert_rq_held(rq);
+ /*
+ * A task can be re-queued within this loop when a reenqueued task
+ * bounces straight back to the local DSQ. That recursion is bounded by
+ * the per-task reenqueue cap in scx_do_enqueue_task().
+ */
while (true) {
struct scx_sched *sch;
u64 reenq_flags;
- bool skip = false;
scoped_guard (raw_spinlock, &rq->scx.deferred_reenq_lock) {
struct scx_deferred_reenq_local *drl =
@@ -4146,27 +4589,12 @@ static void process_deferred_reenq_locals(struct rq *rq)
reenq_flags = drl->flags;
WRITE_ONCE(drl->flags, 0);
list_del_init(&drl->node);
-
- if (likely(drl->seq != seq)) {
- drl->seq = seq;
- drl->cnt = 0;
- } else {
- if (unlikely(++drl->cnt > SCX_REENQ_LOCAL_MAX_REPEAT)) {
- scx_error(sch, "SCX_ENQ_REENQ on SCX_DSQ_LOCAL repeated %u times",
- drl->cnt);
- skip = true;
- }
-
- __scx_add_event(sch, SCX_EV_REENQ_LOCAL_REPEAT, 1);
- }
}
- if (!skip) {
- /* see schedule_dsq_reenq() */
- smp_mb();
+ /* see schedule_dsq_reenq() */
+ smp_mb();
- reenq_local(sch, rq, reenq_flags);
- }
+ reenq_local(sch, rq, reenq_flags);
}
}
@@ -4202,8 +4630,10 @@ static void reenq_user(struct rq *rq, struct scx_dispatch_q *dsq, u64 reenq_flag
task_rq = task_rq(p);
if (locked_rq != task_rq) {
- if (locked_rq)
+ if (locked_rq) {
+ scx_rq_lock_drop(locked_rq);
raw_spin_rq_unlock(locked_rq);
+ }
if (unlikely(!raw_spin_rq_trylock(task_rq))) {
raw_spin_unlock(&dsq->lock);
raw_spin_rq_lock(task_rq);
@@ -4217,6 +4647,7 @@ static void reenq_user(struct rq *rq, struct scx_dispatch_q *dsq, u64 reenq_flag
}
/* @p is on @dsq, its rq and @dsq are locked */
+ scx_reenq_wait_dispatching(p);
dispatch_dequeue_locked(p, dsq);
raw_spin_unlock(&dsq->lock);
@@ -4224,11 +4655,12 @@ static void reenq_user(struct rq *rq, struct scx_dispatch_q *dsq, u64 reenq_flag
p->scx.flags &= ~SCX_TASK_REENQ_REASON_MASK;
p->scx.flags |= reason;
- do_enqueue_task(task_rq, p, SCX_ENQ_REENQ, -1);
+ scx_do_enqueue_task(task_rq, p, SCX_ENQ_REENQ, -1);
p->scx.flags &= ~SCX_TASK_REENQ_REASON_MASK;
if (!(++nr_enqueued % SCX_TASK_ITER_BATCH)) {
+ scx_rq_lock_drop(locked_rq);
raw_spin_rq_unlock(locked_rq);
locked_rq = NULL;
cpu_relax();
@@ -4241,8 +4673,10 @@ static void reenq_user(struct rq *rq, struct scx_dispatch_q *dsq, u64 reenq_flag
raw_spin_unlock(&dsq->lock);
if (locked_rq != rq) {
- if (locked_rq)
+ if (locked_rq) {
+ scx_rq_lock_drop(locked_rq);
raw_spin_rq_unlock(locked_rq);
+ }
raw_spin_rq_lock(rq);
}
}
@@ -4253,7 +4687,7 @@ static void process_deferred_reenq_users(struct rq *rq)
while (true) {
struct scx_dispatch_q *dsq;
- u64 reenq_flags;
+ u64 dsq_id, reenq_flags;
scoped_guard (raw_spinlock, &rq->scx.deferred_reenq_lock) {
struct scx_deferred_reenq_user *dru =
@@ -4276,7 +4710,12 @@ static void process_deferred_reenq_users(struct rq *rq)
/* see schedule_dsq_reenq() */
smp_mb();
- BUG_ON(dsq->id & SCX_DSQ_FLAG_BUILTIN);
+ /* destroy_dsq() may have raced and invalidated @dsq, nothing to reenq */
+ dsq_id = READ_ONCE(dsq->id);
+ if (unlikely(dsq_id == SCX_DSQ_INVALID))
+ continue;
+
+ BUG_ON(dsq_id & SCX_DSQ_FLAG_BUILTIN);
reenq_user(rq, dsq, reenq_flags);
}
}
@@ -4290,6 +4729,8 @@ static void run_deferred(struct rq *rq)
if (!list_empty(&rq->scx.deferred_reenq_users))
process_deferred_reenq_users(rq);
+
+ scx_reenq_reject(rq);
}
#ifdef CONFIG_NO_HZ_FULL
@@ -4301,10 +4742,26 @@ bool scx_can_stop_tick(struct rq *rq)
if (p->sched_class != &ext_sched_class)
return true;
+ /*
+ * @rq->curr may still reference an outgoing EXT task after it has been
+ * dequeued. If no EXT tasks are accounted on @rq, ignore its stale
+ * slice state. If another task is dispatched from a DSQ,
+ * set_next_task_scx() will update the dependency for the incoming task.
+ */
+ if (!rq->scx.nr_running)
+ return true;
+
if (scx_bypassing(sch, cpu_of(rq)))
return false;
/*
+ * A running rescuee's charging and expiry are tick-driven, see
+ * scx_rescue_charge(). Keep the tick while rescue is in progress.
+ */
+ if (unlikely(p == scx_rescuee(rq)))
+ return false;
+
+ /*
* @rq can dispatch from different DSQs, so we can't tell whether it
* needs the tick or not by looking at nr_running. Allow stopping ticks
* iff the BPF scheduler indicated so. See set_next_task_scx().
@@ -4316,7 +4773,6 @@ bool scx_can_stop_tick(struct rq *rq)
#ifdef CONFIG_EXT_GROUP_SCHED
DEFINE_STATIC_PERCPU_RWSEM(scx_cgroup_ops_rwsem);
-static bool scx_cgroup_enabled;
void scx_tg_init(struct task_group *tg)
{
@@ -4326,28 +4782,97 @@ void scx_tg_init(struct task_group *tg)
tg->scx.idle = false;
}
+/**
+ * scx_tg_sched - Resolve a task_group's sched
+ * @tg: task_group of interest
+ *
+ * Return the sched that @tg's ops.cgroup_init() succeeded on, %NULL if @tg
+ * isn't inited. An autogroup tg has no cgroup of its own and resolves to the
+ * root sched.
+ *
+ * When a child sched exits, its task_groups are moved to the parent and
+ * re-inited on it. A failed re-init fails the parent in turn and leaves the
+ * task_group without a sched it's inited on, resolving to %NULL. See
+ * scx_cgroup_return_subtree().
+ *
+ * Safe for callers read-locking the ops rwsem. tg->scx.sched rewrites
+ * write-lock it, and tg on/offline can't overlap such callers as a css's files
+ * are created after online and drained before offline.
+ */
+static struct scx_sched *scx_tg_sched(struct task_group *tg)
+{
+ lockdep_assert(lockdep_is_held(&cgroup_mutex) ||
+ lockdep_is_held(&scx_cgroup_ops_rwsem));
+
+ if (!tg->css.cgroup)
+ tg = &root_task_group;
+ /* INITED means ops.cgroup_init() succeeded on @tg->scx.sched */
+ return (tg->scx.flags & SCX_TG_INITED) ? tg->scx.sched : NULL;
+}
+
+/**
+ * scx_tg_knob_sched - Resolve the sched receiving a task_group's knob updates
+ * @tg: task_group of interest
+ *
+ * Knobs of a cgroup belong to the parent. Deliver the set_* ops to the
+ * parent task_group's sched, which equals @tg's own sched everywhere except
+ * at a sub-scheduler attach point, where the sub's parent sched receives
+ * them.
+ *
+ * Return %NULL if the parent task_group has no sched. That can happen when the
+ * parent's ops.cgroup_init() fails while a sub-scheduler is being disabled.
+ *
+ * The callers sit in @tg's cgroup file writes holding the ops rwsem read
+ * side. That extends scx_tg_sched()'s file-write argument to the parent's
+ * sched read: a parent css outlives its children's files.
+ */
+static struct scx_sched *scx_tg_knob_sched(struct task_group *tg)
+{
+ lockdep_assert(lockdep_is_held(&cgroup_mutex) ||
+ lockdep_is_held(&scx_cgroup_ops_rwsem));
+
+ if (!tg->css.cgroup || !tg->css.parent)
+ return scx_tg_sched(&root_task_group);
+ return scx_tg_sched(css_tg(tg->css.parent));
+}
+
int scx_tg_online(struct task_group *tg)
{
- struct scx_sched *sch = scx_root;
int ret = 0;
WARN_ON_ONCE(tg->scx.flags & (SCX_TG_ONLINE | SCX_TG_INITED));
if (scx_cgroup_enabled) {
+ struct scx_sched *sch;
+
+ /*
+ * The cgroup lifetime notifier populates cgrp->scx_sched before
+ * css_online, but only on the default hierarchy. Sub-scheds are
+ * attached to the cgroup2 hierarchy, so a cgroup1 task_group
+ * always belongs to the root sched.
+ */
+ if (cgroup_on_dfl(tg->css.cgroup))
+ sch = scx_cgroup_sched(tg->css.cgroup);
+ else
+ sch = scx_tg_sched(&root_task_group);
+
if (SCX_HAS_OP(sch, cgroup_init)) {
struct scx_cgroup_init_args args =
{ .weight = tg->scx.weight,
.bw_period_us = tg->scx.bw_period_us,
.bw_quota_us = tg->scx.bw_quota_us,
- .bw_burst_us = tg->scx.bw_burst_us };
+ .bw_burst_us = tg->scx.bw_burst_us,
+ .sched_idle = tg->scx.idle };
ret = SCX_CALL_OP_RET(sch, cgroup_init,
NULL, tg->css.cgroup, &args);
if (ret)
- ret = ops_sanitize_err(sch, "cgroup_init", ret);
+ ret = scx_ops_sanitize_err(sch, "cgroup_init", ret);
}
- if (ret == 0)
+ if (ret == 0) {
+ tg->scx.sched = sch;
tg->scx.flags |= SCX_TG_ONLINE | SCX_TG_INITED;
+ }
} else {
tg->scx.flags |= SCX_TG_ONLINE;
}
@@ -4357,19 +4882,30 @@ int scx_tg_online(struct task_group *tg)
void scx_tg_offline(struct task_group *tg)
{
- struct scx_sched *sch = scx_root;
+ struct scx_sched *sch = tg->scx.sched;
WARN_ON_ONCE(!(tg->scx.flags & SCX_TG_ONLINE));
- if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_exit) &&
- (tg->scx.flags & SCX_TG_INITED))
+ /* INITED implies non-NULL @sch, test before SCX_HAS_OP() derefs */
+ if (scx_cgroup_enabled && (tg->scx.flags & SCX_TG_INITED) &&
+ SCX_HAS_OP(sch, cgroup_exit))
SCX_CALL_OP(sch, cgroup_exit, NULL, tg->css.cgroup);
+ tg->scx.sched = NULL;
tg->scx.flags &= ~(SCX_TG_ONLINE | SCX_TG_INITED);
}
+/*
+ * @p's sched for the cgroup migration paths. Stable as re-homes happen either
+ * at CGROUP_TASK_MIGRATED of the same migration or under scx_cgroup_lock(),
+ * both while holding cgroup_mutex.
+ */
+static struct scx_sched *scx_cgroup_task_sched(struct task_struct *p)
+{
+ return rcu_dereference_protected(p->scx.sched, lockdep_is_held(&cgroup_mutex));
+}
+
int scx_cgroup_can_attach(struct cgroup_taskset *tset)
{
- struct scx_sched *sch = scx_root;
struct cgroup_subsys_state *css;
struct task_struct *p;
int ret;
@@ -4378,6 +4914,7 @@ int scx_cgroup_can_attach(struct cgroup_taskset *tset)
return 0;
cgroup_taskset_for_each(p, css, tset) {
+ struct scx_sched *sch = scx_cgroup_task_sched(p);
struct cgroup *from = tg_cgrp(task_group(p));
struct cgroup *to = tg_cgrp(css_tg(css));
@@ -4391,11 +4928,22 @@ int scx_cgroup_can_attach(struct cgroup_taskset *tset)
if (from == to)
continue;
+ /*
+ * The cgroup_move ops are delivered to @p's sched, and only for
+ * moves that don't re-home @p. A re-homing move changes the dfl
+ * cgroup's sched and is reported through the
+ * exit_task/init_task pair that the re-homing generates.
+ */
+ if (!sch || sch != scx_cgroup_sched(task_css_set(p)->mg_dst_cset->dfl_cgrp))
+ continue;
+
if (SCX_HAS_OP(sch, cgroup_prep_move)) {
ret = SCX_CALL_OP_RET(sch, cgroup_prep_move, NULL,
p, from, css->cgroup);
- if (ret)
+ if (ret) {
+ ret = scx_ops_sanitize_err(sch, "cgroup_prep_move", ret);
goto err;
+ }
}
p->scx.cgrp_moving_from = from;
@@ -4405,31 +4953,33 @@ int scx_cgroup_can_attach(struct cgroup_taskset *tset)
err:
cgroup_taskset_for_each(p, css, tset) {
- if (SCX_HAS_OP(sch, cgroup_cancel_move) &&
- p->scx.cgrp_moving_from)
+ struct scx_sched *sch = scx_cgroup_task_sched(p);
+
+ /* cgrp_moving_from implies non-NULL @sch, test it first */
+ if (p->scx.cgrp_moving_from && SCX_HAS_OP(sch, cgroup_cancel_move))
SCX_CALL_OP(sch, cgroup_cancel_move, NULL,
p, p->scx.cgrp_moving_from, css->cgroup);
p->scx.cgrp_moving_from = NULL;
}
- return ops_sanitize_err(sch, "cgroup_prep_move", ret);
+ return ret;
}
void scx_cgroup_move_task(struct task_struct *p)
{
- struct scx_sched *sch = scx_root;
+ struct scx_sched *sch;
if (!scx_cgroup_enabled)
return;
/*
- * scx_cgroup_can_attach() sets cgrp_moving_from only when the task's
- * cgroup changes. Migration keys off css rather than cgroup identity,
- * so it can hand an unchanged-cgroup task here with cgrp_moving_from
- * NULL. Nothing to report to the BPF scheduler then, so skip it and
- * keep prep_move and move paired.
+ * Migration keys off css rather than cgroup identity, so it can hand an
+ * unchanged-cgroup task here with cgrp_moving_from NULL. Nothing to
+ * report to the BPF scheduler then, so skip it and keep prep_move and
+ * move paired.
*/
- if (SCX_HAS_OP(sch, cgroup_move) && p->scx.cgrp_moving_from)
+ sch = scx_cgroup_task_sched(p);
+ if (p->scx.cgrp_moving_from && SCX_HAS_OP(sch, cgroup_move))
SCX_CALL_OP_TASK(sch, cgroup_move, task_rq(p),
p, p->scx.cgrp_moving_from,
tg_cgrp(task_group(p)));
@@ -4438,7 +4988,6 @@ void scx_cgroup_move_task(struct task_struct *p)
void scx_cgroup_cancel_attach(struct cgroup_taskset *tset)
{
- struct scx_sched *sch = scx_root;
struct cgroup_subsys_state *css;
struct task_struct *p;
@@ -4446,8 +4995,10 @@ void scx_cgroup_cancel_attach(struct cgroup_taskset *tset)
return;
cgroup_taskset_for_each(p, css, tset) {
- if (SCX_HAS_OP(sch, cgroup_cancel_move) &&
- p->scx.cgrp_moving_from)
+ struct scx_sched *sch = scx_cgroup_task_sched(p);
+
+ /* cgrp_moving_from implies non-NULL @sch, test it first */
+ if (p->scx.cgrp_moving_from && SCX_HAS_OP(sch, cgroup_cancel_move))
SCX_CALL_OP(sch, cgroup_cancel_move, NULL,
p, p->scx.cgrp_moving_from, css->cgroup);
p->scx.cgrp_moving_from = NULL;
@@ -4459,9 +5010,9 @@ void scx_group_set_weight(struct task_group *tg, unsigned long weight)
struct scx_sched *sch;
percpu_down_read(&scx_cgroup_ops_rwsem);
- sch = scx_root;
+ sch = scx_tg_knob_sched(tg);
- if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_weight) &&
+ if (scx_cgroup_enabled && sch && SCX_HAS_OP(sch, cgroup_set_weight) &&
tg->scx.weight != weight)
SCX_CALL_OP(sch, cgroup_set_weight, NULL, tg_cgrp(tg), weight);
@@ -4475,9 +5026,10 @@ void scx_group_set_idle(struct task_group *tg, bool idle)
struct scx_sched *sch;
percpu_down_read(&scx_cgroup_ops_rwsem);
- sch = scx_root;
+ sch = scx_tg_knob_sched(tg);
- if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_idle))
+ if (scx_cgroup_enabled && sch && SCX_HAS_OP(sch, cgroup_set_idle) &&
+ tg->scx.idle != idle)
SCX_CALL_OP(sch, cgroup_set_idle, NULL, tg_cgrp(tg), idle);
/* Update the task group's idle state */
@@ -4492,9 +5044,9 @@ void scx_group_set_bandwidth(struct task_group *tg,
struct scx_sched *sch;
percpu_down_read(&scx_cgroup_ops_rwsem);
- sch = scx_root;
+ sch = scx_tg_knob_sched(tg);
- if (scx_cgroup_enabled && SCX_HAS_OP(sch, cgroup_set_bandwidth) &&
+ if (scx_cgroup_enabled && sch && SCX_HAS_OP(sch, cgroup_set_bandwidth) &&
(tg->scx.bw_period_us != period_us ||
tg->scx.bw_quota_us != quota_us ||
tg->scx.bw_burst_us != burst_us))
@@ -4515,20 +5067,25 @@ static struct cgroup *root_cgroup(void)
return &cgrp_dfl_root.cgrp;
}
-static void scx_cgroup_lock(void)
+/*
+ * cgroup_lock() must nest outside the rwsem write side: a writer waiting
+ * for cgroup_mutex deadlocks with cgroup teardown, which holds it while
+ * draining a set_* file write blocked on the rwsem behind the writer.
+ */
+void scx_cgroup_lock(void)
{
+ cgroup_lock();
#ifdef CONFIG_EXT_GROUP_SCHED
percpu_down_write(&scx_cgroup_ops_rwsem);
#endif
- cgroup_lock();
}
-static void scx_cgroup_unlock(void)
+void scx_cgroup_unlock(void)
{
- cgroup_unlock();
#ifdef CONFIG_EXT_GROUP_SCHED
percpu_up_write(&scx_cgroup_ops_rwsem);
#endif
+ cgroup_unlock();
}
#else /* CONFIG_EXT_GROUP_SCHED || CONFIG_EXT_SUB_SCHED */
static inline struct cgroup *root_cgroup(void) { return NULL; }
@@ -4536,26 +5093,6 @@ static inline void scx_cgroup_lock(void) {}
static inline void scx_cgroup_unlock(void) {}
#endif /* CONFIG_EXT_GROUP_SCHED || CONFIG_EXT_SUB_SCHED */
-#ifdef CONFIG_EXT_SUB_SCHED
-static struct cgroup *sch_cgroup(struct scx_sched *sch)
-{
- return sch->cgrp;
-}
-
-/* for each descendant of @cgrp including self, set ->scx_sched to @sch */
-static void set_cgroup_sched(struct cgroup *cgrp, struct scx_sched *sch)
-{
- struct cgroup *pos;
- struct cgroup_subsys_state *css;
-
- cgroup_for_each_live_descendant_pre(pos, css, cgrp)
- rcu_assign_pointer(pos->scx_sched, sch);
-}
-#else /* CONFIG_EXT_SUB_SCHED */
-static inline struct cgroup *sch_cgroup(struct scx_sched *sch) { return NULL; }
-static inline void set_cgroup_sched(struct cgroup *cgrp, struct scx_sched *sch) {}
-#endif /* CONFIG_EXT_SUB_SCHED */
-
/*
* Omitted operations:
*
@@ -4599,8 +5136,7 @@ DEFINE_SCHED_CLASS(ext) = {
#endif
};
-static s32 init_dsq(struct scx_dispatch_q *dsq, u64 dsq_id,
- struct scx_sched *sch)
+s32 scx_init_dsq(struct scx_dispatch_q *dsq, u64 dsq_id, struct scx_sched *sch)
{
s32 cpu;
@@ -4690,7 +5226,7 @@ static void destroy_dsq(struct scx_sched *sch, u64 dsq_id)
goto out_unlock_dsq;
/*
- * Mark dead by invalidating ->id to prevent dispatch_enqueue() from
+ * Mark dead by invalidating ->id to prevent scx_dispatch_enqueue() from
* queueing more tasks. As this function can be called from anywhere,
* freeing is bounced through an irq work to avoid nesting RCU
* operations inside scheduler locks.
@@ -4710,8 +5246,6 @@ static void scx_cgroup_exit(struct scx_sched *sch)
{
struct cgroup_subsys_state *css;
- scx_cgroup_enabled = false;
-
/*
* scx_tg_on/offline() are excluded through cgroup_lock(). If we walk
* cgroups and exit all the inited ones, all online cgroups are exited.
@@ -4719,14 +5253,13 @@ static void scx_cgroup_exit(struct scx_sched *sch)
css_for_each_descendant_post(css, &root_task_group.css) {
struct task_group *tg = css_tg(css);
- if (!(tg->scx.flags & SCX_TG_INITED))
- continue;
- tg->scx.flags &= ~SCX_TG_INITED;
-
- if (!sch->ops.cgroup_exit)
- continue;
-
- SCX_CALL_OP(sch, cgroup_exit, NULL, css->cgroup);
+ /* also clear the sched of tgs whose ops.cgroup_init() failed */
+ tg->scx.sched = NULL;
+ if (tg->scx.flags & SCX_TG_INITED) {
+ tg->scx.flags &= ~SCX_TG_INITED;
+ if (sch->ops.cgroup_exit)
+ SCX_CALL_OP(sch, cgroup_exit, NULL, css->cgroup);
+ }
}
}
@@ -4741,34 +5274,30 @@ static int scx_cgroup_init(struct scx_sched *sch)
*/
css_for_each_descendant_pre(css, &root_task_group.css) {
struct task_group *tg = css_tg(css);
- struct scx_cgroup_init_args args = {
- .weight = tg->scx.weight,
- .bw_period_us = tg->scx.bw_period_us,
- .bw_quota_us = tg->scx.bw_quota_us,
- .bw_burst_us = tg->scx.bw_burst_us,
- };
- if ((tg->scx.flags &
- (SCX_TG_ONLINE | SCX_TG_INITED)) != SCX_TG_ONLINE)
+ if ((tg->scx.flags & (SCX_TG_ONLINE | SCX_TG_INITED)) != SCX_TG_ONLINE)
continue;
- if (!sch->ops.cgroup_init) {
- tg->scx.flags |= SCX_TG_INITED;
- continue;
- }
+ if (sch->ops.cgroup_init) {
+ struct scx_cgroup_init_args args = {
+ .weight = tg->scx.weight,
+ .bw_period_us = tg->scx.bw_period_us,
+ .bw_quota_us = tg->scx.bw_quota_us,
+ .bw_burst_us = tg->scx.bw_burst_us,
+ .sched_idle = tg->scx.idle,
+ };
- ret = SCX_CALL_OP_RET(sch, cgroup_init, NULL,
- css->cgroup, &args);
- if (ret) {
- scx_error(sch, "ops.cgroup_init() failed (%d)", ret);
- return ret;
+ ret = SCX_CALL_OP_RET(sch, cgroup_init, NULL, css->cgroup, &args);
+ if (ret) {
+ scx_error(sch, "ops.cgroup_init() failed (%d)", ret);
+ return ret;
+ }
}
+
+ tg->scx.sched = sch;
tg->scx.flags |= SCX_TG_INITED;
}
- WARN_ON_ONCE(scx_cgroup_enabled);
- scx_cgroup_enabled = true;
-
return 0;
}
@@ -4838,13 +5367,20 @@ static const struct attribute_group scx_global_attr_group = {
static void free_pnode(struct scx_sched_pnode *pnode);
static void free_exit_info(struct scx_exit_info *ei);
+static const char *scx_exit_reason(enum scx_exit_kind kind);
+static bool scx_claim_exit(struct scx_sched *sch, enum scx_exit_kind kind);
-static s32 scx_set_cmask_scratch_alloc(struct scx_sched *sch)
+s32 scx_alloc_kern_arena_objs(struct scx_sched *sch)
{
size_t size = struct_size_t(struct scx_cmask, bits,
SCX_CMASK_NR_WORDS(num_possible_cpus()));
+ struct scx_cmask *online;
+ struct scx_cmask_ref ref;
int cpu;
+ /* hotplug stays excluded until the online mask is published */
+ lockdep_assert_cpus_held();
+
if (!sch->is_cid_type || !sch->arena_pool)
return 0;
@@ -4860,15 +5396,28 @@ static s32 scx_set_cmask_scratch_alloc(struct scx_sched *sch)
return -ENOMEM;
scx_cmask_init(*slot, 0, num_possible_cpus());
}
+
+ /* pack the online mask alongside the scratch masks */
+ online = scx_arena_alloc(sch, size);
+ if (!online)
+ return -ENOMEM;
+
+ scoped_guard(rcu) {
+ scx_cmask_ref_init_kern(sch, online, 0, num_possible_cpus(), &ref);
+ scx_cmask_ref_from_cpumask(&ref, cpu_active_mask);
+ }
+ sch->online_cmask = online;
+
return 0;
}
-static void scx_set_cmask_scratch_free(struct scx_sched *sch)
+static void scx_free_kern_arena_objs(struct scx_sched *sch)
{
size_t size = struct_size_t(struct scx_cmask, bits,
SCX_CMASK_NR_WORDS(num_possible_cpus()));
int cpu;
+ scx_arena_free(sch, sch->online_cmask, size);
if (!sch->set_cmask_scratch)
return;
@@ -4889,11 +5438,13 @@ static void scx_sched_free_rcu_work(struct work_struct *work)
struct scx_dispatch_q *dsq;
int cpu, node;
+ irq_work_sync(&sch->propagate_exit_irq_work);
irq_work_sync(&sch->disable_irq_work);
kthread_destroy_worker(sch->helper);
timer_shutdown_sync(&sch->bypass_lb_timer);
free_cpumask_var(sch->bypass_lb_donee_cpumask);
free_cpumask_var(sch->bypass_lb_resched_cpumask);
+ free_cpumask_var(sch->stall_cpus);
#ifdef CONFIG_EXT_SUB_SCHED
kfree(sch->cgrp_path);
@@ -4901,6 +5452,8 @@ static void scx_sched_free_rcu_work(struct work_struct *work)
cgroup_put(sch_cgroup(sch));
if (sch->sub_kset)
kobject_put(&sch->sub_kset->kobj);
+ if (scx_parent(sch))
+ kobject_put(&scx_parent(sch)->kobj);
#endif /* CONFIG_EXT_SUB_SCHED */
for_each_possible_cpu(cpu) {
@@ -4913,7 +5466,21 @@ static void scx_sched_free_rcu_work(struct work_struct *work)
*/
WARN_ON_ONCE(!list_empty(&pcpu->deferred_reenq_local.node));
- exit_dsq(bypass_dsq(sch, cpu));
+ /* remove the queued ecaps sync so the pcpu can be freed */
+ scx_discard_ecaps_to_sync(cpu, pcpu);
+
+ /*
+ * Bypass blocks new kicks. Flush the kick irq_work so this
+ * pcpu's to_kick_node is off the list before it is freed.
+ */
+ irq_work_sync(&cpu_rq(cpu)->scx.kick_cpus_irq_work);
+ WARN_ON_ONCE(!list_empty(&pcpu->to_kick_node));
+ free_cpumask_var(pcpu->cpus_to_kick);
+ free_cpumask_var(pcpu->cpus_to_kick_if_idle);
+ free_cpumask_var(pcpu->cpus_to_preempt);
+ free_cpumask_var(pcpu->cpus_to_wait);
+
+ exit_dsq(scx_bypass_dsq(sch, cpu));
}
free_percpu(sch->pcpu);
@@ -4922,6 +5489,8 @@ static void scx_sched_free_rcu_work(struct work_struct *work)
free_pnode(sch->pnode[node]);
kfree(sch->pnode);
+ scx_free_pshards(sch);
+
rhashtable_walk_enter(&sch->dsq_hash, &rht_iter);
do {
rhashtable_walk_start(&rht_iter);
@@ -4935,10 +5504,14 @@ static void scx_sched_free_rcu_work(struct work_struct *work)
rhashtable_free_and_destroy(&sch->dsq_hash, NULL, NULL);
free_exit_info(sch->exit_info);
- scx_set_cmask_scratch_free(sch);
+ scx_free_kern_arena_objs(sch);
scx_arena_pool_destroy(sch);
if (sch->arena_map)
bpf_map_put(sch->arena_map);
+
+ /* @sch is completely inactive by now */
+ scx_dec_has_subs(sch);
+
kfree(sch);
}
@@ -4971,26 +5544,62 @@ static ssize_t scx_attr_events_show(struct kobject *kobj,
int at = 0;
scx_read_events(sch, &events);
- at += scx_attr_event_show(buf, at, &events, SCX_EV_SELECT_CPU_FALLBACK);
- at += scx_attr_event_show(buf, at, &events, SCX_EV_DISPATCH_LOCAL_DSQ_OFFLINE);
- at += scx_attr_event_show(buf, at, &events, SCX_EV_DISPATCH_KEEP_LAST);
- at += scx_attr_event_show(buf, at, &events, SCX_EV_ENQ_SKIP_EXITING);
- at += scx_attr_event_show(buf, at, &events, SCX_EV_ENQ_SKIP_MIGRATION_DISABLED);
- at += scx_attr_event_show(buf, at, &events, SCX_EV_REENQ_IMMED);
- at += scx_attr_event_show(buf, at, &events, SCX_EV_REENQ_LOCAL_REPEAT);
- at += scx_attr_event_show(buf, at, &events, SCX_EV_REFILL_SLICE_DFL);
- at += scx_attr_event_show(buf, at, &events, SCX_EV_BYPASS_DURATION);
- at += scx_attr_event_show(buf, at, &events, SCX_EV_BYPASS_DISPATCH);
- at += scx_attr_event_show(buf, at, &events, SCX_EV_BYPASS_ACTIVATE);
- at += scx_attr_event_show(buf, at, &events, SCX_EV_INSERT_NOT_OWNED);
- at += scx_attr_event_show(buf, at, &events, SCX_EV_SUB_BYPASS_DISPATCH);
+#define SCX_EVENT(name) (at += scx_attr_event_show(buf, at, &events, name))
+ SCX_EVENTS_LIST(SCX_EVENT);
+#undef SCX_EVENT
return at;
}
SCX_ATTR(events);
+#ifdef CONFIG_EXT_SUB_SCHED
+static const char *scx_cap_names[__SCX_NR_CAPS] = {
+ [__SCX_CAP_ENQ_IMMED] = "enq_immed",
+ [__SCX_CAP_ENQ] = "enq",
+ [__SCX_CAP_PREEMPT] = "preempt",
+ [__SCX_CAP_PERF] = "perf",
+};
+
+static ssize_t scx_attr_caps_show(struct kobject *kobj,
+ struct kobj_attribute *ka, char *buf)
+{
+ struct scx_sched *sch = container_of(kobj, struct scx_sched, kobj);
+ u32 npossible = num_possible_cpus();
+ struct scx_cmask *agg __free(kfree) =
+ kzalloc_flex(*agg, bits, SCX_CMASK_NR_WORDS(npossible));
+ unsigned long *agg_bm __free(bitmap) = bitmap_zalloc(npossible, GFP_KERNEL);
+ ssize_t count = 0;
+ s32 cap, si;
+
+ if (!agg || !agg_bm)
+ return -ENOMEM;
+
+ for (cap = 0; cap < __SCX_NR_CAPS; cap++) {
+ SCX_CMASK_DEFINE(snap, 0, SCX_CID_SHARD_MAX_CPUS);
+
+ scx_cmask_init(agg, 0, npossible);
+ for (si = 0; si < sch->nr_pshards; si++) {
+ struct scx_cmask *cm = &sch->pshard[si]->caps[cap].cmask;
+
+ scx_cmask_reframe(snap, cm->base, cm->nr_cids);
+ scx_cmask_copy(snap, cm);
+ scx_cmask_or(agg, snap);
+ }
+ /* %*pbl takes unsigned long bitmap layout, convert from u64 */
+ bitmap_from_arr64(agg_bm, agg->bits, npossible);
+ count += sysfs_emit_at(buf, count, "%s: %*pbl\n",
+ scx_cap_names[cap], npossible, agg_bm);
+ }
+ return count;
+}
+SCX_ATTR(caps);
+#endif /* CONFIG_EXT_SUB_SCHED */
+
static struct attribute *scx_sched_attrs[] = {
&scx_attr_ops.attr,
&scx_attr_events.attr,
+#ifdef CONFIG_EXT_SUB_SCHED
+ &scx_attr_caps.attr,
+#endif
NULL,
};
ATTRIBUTE_GROUPS(scx_sched);
@@ -5023,7 +5632,7 @@ static const struct kset_uevent_ops scx_uevent_ops = {
};
/*
- * Used by sched_fork() and __setscheduler_prio() to pick the matching
+ * Used by sched_fork() and __setscheduler_class() to pick the matching
* sched_class. dl/rt are already handled.
*/
bool task_should_scx(int policy)
@@ -5077,6 +5686,7 @@ bool scx_allow_ttwu_queue(const struct task_struct *p)
/**
* handle_lockup - sched_ext common lockup handler
+ * @exit_cpu: CPU to record in exit_info. Pass the stalled/hung CPU, not current.
* @fmt: format string
*
* Called on system stall or lockup condition and initiates abort of sched_ext
@@ -5086,7 +5696,7 @@ bool scx_allow_ttwu_queue(const struct task_struct *p)
* resolve the lockup. %false if sched_ext is not enabled or abort was already
* initiated by someone else.
*/
-static __printf(1, 2) bool handle_lockup(const char *fmt, ...)
+static __printf(2, 3) bool handle_lockup(int exit_cpu, const char *fmt, ...)
{
struct scx_sched *sch;
va_list args;
@@ -5102,7 +5712,7 @@ static __printf(1, 2) bool handle_lockup(const char *fmt, ...)
case SCX_ENABLING:
case SCX_ENABLED:
va_start(args, fmt);
- ret = scx_verror(sch, fmt, args);
+ ret = scx_vexit(sch, SCX_EXIT_ERROR, 0, exit_cpu, fmt, args);
va_end(args);
return ret;
default:
@@ -5112,6 +5722,7 @@ static __printf(1, 2) bool handle_lockup(const char *fmt, ...)
/**
* scx_rcu_cpu_stall - sched_ext RCU CPU stall handler
+ * @stalled_mask: bit mask of stalled CPUs
*
* While there are various reasons why RCU CPU stalls can occur on a system
* that may not be caused by the current BPF scheduler, try kicking out the
@@ -5122,9 +5733,46 @@ static __printf(1, 2) bool handle_lockup(const char *fmt, ...)
* resolve the reported RCU stall. %false if sched_ext is not enabled or someone
* else already initiated abort.
*/
-bool scx_rcu_cpu_stall(void)
+bool scx_rcu_cpu_stall(const struct cpumask *stalled_mask)
{
- return handle_lockup("RCU CPU stall detected!");
+ struct scx_sched *sch;
+ struct scx_exit_info *ei;
+ int exit_cpu;
+
+ guard(rcu)();
+
+ sch = rcu_dereference(scx_root);
+ if (unlikely(!sch))
+ return false;
+
+ switch (scx_enable_state()) {
+ case SCX_ENABLING:
+ case SCX_ENABLED:
+ break;
+ default:
+ return false;
+ }
+
+ exit_cpu = cpumask_empty(stalled_mask) ? -1 : (int)cpumask_first(stalled_mask);
+ ei = sch->exit_info;
+
+ guard(preempt)();
+
+ if (!scx_claim_exit(sch, SCX_EXIT_ERROR))
+ return false;
+
+#ifdef CONFIG_STACKTRACE
+ ei->bt_len = stack_trace_save(ei->bt, SCX_EXIT_BT_LEN, 1);
+#endif
+ scnprintf(ei->msg, SCX_EXIT_MSG_LEN, "RCU CPU stall on CPUs (%*pbl)",
+ cpumask_pr_args(stalled_mask));
+ ei->kind = SCX_EXIT_ERROR;
+ ei->reason = scx_exit_reason(SCX_EXIT_ERROR);
+ ei->exit_cpu = exit_cpu;
+ cpumask_copy(sch->stall_cpus, stalled_mask);
+
+ irq_work_queue(&sch->disable_irq_work);
+ return true;
}
/**
@@ -5139,53 +5787,39 @@ bool scx_rcu_cpu_stall(void)
*/
void scx_softlockup(u32 dur_s)
{
- if (!handle_lockup("soft lockup - CPU %d stuck for %us", smp_processor_id(), dur_s))
+ int cpu = smp_processor_id();
+
+ if (!handle_lockup(cpu, "soft lockup - CPU %d stuck for %us", cpu, dur_s))
return;
printk_deferred(KERN_ERR "sched_ext: Soft lockup - CPU %d stuck for %us, disabling BPF scheduler\n",
- smp_processor_id(), dur_s);
+ cpu, dur_s);
}
-/*
- * scx_hardlockup() runs from NMI and eventually calls scx_claim_exit(),
- * which takes scx_sched_lock. scx_sched_lock isn't NMI-safe and grabbing
- * it from NMI context can lead to deadlocks. Defer via irq_work; the
- * disable path runs off irq_work anyway.
- */
-static atomic_t scx_hardlockup_cpu = ATOMIC_INIT(-1);
-
-static void scx_hardlockup_irq_workfn(struct irq_work *work)
-{
- int cpu = atomic_xchg(&scx_hardlockup_cpu, -1);
-
- if (cpu >= 0 && handle_lockup("hard lockup - CPU %d", cpu))
- printk_deferred(KERN_ERR "sched_ext: Hard lockup - CPU %d, disabling BPF scheduler\n",
- cpu);
-}
-
-static DEFINE_IRQ_WORK(scx_hardlockup_irq_work, scx_hardlockup_irq_workfn);
-
/**
* scx_hardlockup - sched_ext hardlockup handler
+ * @cpu: the target CPU
*
* A poorly behaving BPF scheduler can trigger hard lockup by e.g. putting
* numerous affinitized tasks in a single queue and directing all CPUs at it.
* Try kicking out the current scheduler in an attempt to recover the system to
* a good state before taking more drastic actions.
*
- * Queues an irq_work; the handle_lockup() call happens in IRQ context (see
- * scx_hardlockup_irq_workfn).
+ * Called from NMI. Aborting the scheduler sets ->aborting throughout the
+ * hierarchy before returning, which is what breaks the dispatch-path live-locks
+ * that can hard-lock CPUs.
*
- * Returns %true if sched_ext is enabled and the work was queued, %false
- * otherwise.
+ * Returns %true if sched_ext is enabled and abort was initiated, which may
+ * resolve the lockup. %false if sched_ext is not enabled or abort was already
+ * initiated by someone else.
*/
bool scx_hardlockup(int cpu)
{
- if (!rcu_access_pointer(scx_root))
+ if (!handle_lockup(cpu, "hard lockup - CPU %d", cpu))
return false;
- atomic_cmpxchg(&scx_hardlockup_cpu, -1, cpu);
- irq_work_queue(&scx_hardlockup_irq_work);
+ printk_deferred(KERN_ERR "sched_ext: Hard lockup - CPU %d, disabling BPF scheduler\n",
+ cpu);
return true;
}
@@ -5194,7 +5828,7 @@ static u32 bypass_lb_cpu(struct scx_sched *sch, s32 donor,
u32 nr_donor_target, u32 nr_donee_target)
{
struct rq *donor_rq = cpu_rq(donor);
- struct scx_dispatch_q *donor_dsq = bypass_dsq(sch, donor);
+ struct scx_dispatch_q *donor_dsq = scx_bypass_dsq(sch, donor);
struct task_struct *p, *n;
struct scx_dsq_list_node cursor = INIT_DSQ_LIST_CURSOR(cursor, donor_dsq, 0);
s32 delta = READ_ONCE(donor_dsq->nr) - nr_donor_target;
@@ -5242,7 +5876,7 @@ resume:
if (donee >= nr_cpu_ids)
continue;
- donee_dsq = bypass_dsq(sch, donee);
+ donee_dsq = scx_bypass_dsq(sch, donee);
/*
* $p's rq is not locked but $p's DSQ lock protects its
@@ -5263,7 +5897,7 @@ resume:
* between bypass DSQs.
*/
dispatch_dequeue_locked(p, donor_dsq);
- dispatch_enqueue(sch, cpu_rq(donee), donee_dsq, p, SCX_ENQ_NESTED);
+ scx_dispatch_enqueue(sch, cpu_rq(donee), donee_dsq, p, 0, 0, SCX_ENQ_NESTED);
/*
* $donee might have been idle and need to be woken up. No need
@@ -5278,6 +5912,7 @@ resume:
if (!(nr_balanced % SCX_BYPASS_LB_BATCH) && n) {
list_move_tail(&cursor.node, &n->scx.dsq_list.node);
raw_spin_unlock(&donor_dsq->lock);
+ scx_rq_lock_drop(donor_rq);
raw_spin_rq_unlock_irq(donor_rq);
cpu_relax();
raw_spin_rq_lock_irq(donor_rq);
@@ -5288,6 +5923,7 @@ resume:
list_del_init(&cursor.node);
raw_spin_unlock(&donor_dsq->lock);
+ scx_rq_lock_drop(donor_rq);
raw_spin_rq_unlock_irq(donor_rq);
return nr_balanced;
@@ -5306,7 +5942,7 @@ static void bypass_lb_node(struct scx_sched *sch, int node)
/* count the target tasks and CPUs */
for_each_cpu_and(cpu, cpu_online_mask, node_mask) {
- u32 nr = READ_ONCE(bypass_dsq(sch, cpu)->nr);
+ u32 nr = READ_ONCE(scx_bypass_dsq(sch, cpu)->nr);
nr_tasks += nr;
nr_cpus++;
@@ -5328,7 +5964,7 @@ static void bypass_lb_node(struct scx_sched *sch, int node)
cpumask_clear(donee_mask);
for_each_cpu_and(cpu, cpu_online_mask, node_mask) {
- if (READ_ONCE(bypass_dsq(sch, cpu)->nr) < nr_target)
+ if (READ_ONCE(scx_bypass_dsq(sch, cpu)->nr) < nr_target)
cpumask_set_cpu(cpu, donee_mask);
}
@@ -5339,7 +5975,7 @@ static void bypass_lb_node(struct scx_sched *sch, int node)
break;
if (cpumask_test_cpu(cpu, donee_mask))
continue;
- if (READ_ONCE(bypass_dsq(sch, cpu)->nr) <= nr_donor_target)
+ if (READ_ONCE(scx_bypass_dsq(sch, cpu)->nr) <= nr_donor_target)
continue;
nr_balanced += bypass_lb_cpu(sch, cpu, donee_mask, resched_mask,
@@ -5350,7 +5986,7 @@ static void bypass_lb_node(struct scx_sched *sch, int node)
resched_cpu(cpu);
for_each_cpu_and(cpu, cpu_online_mask, node_mask) {
- u32 nr = READ_ONCE(bypass_dsq(sch, cpu)->nr);
+ u32 nr = READ_ONCE(scx_bypass_dsq(sch, cpu)->nr);
after_min = min(nr, after_min);
after_max = max(nr, after_max);
@@ -5376,7 +6012,7 @@ static void scx_bypass_lb_timerfn(struct timer_list *timer)
int node;
u32 intv_us;
- if (!bypass_dsp_enabled(sch))
+ if (!scx_bypass_dsp_enabled(sch))
return;
for_each_node_with_cpus(node)
@@ -5442,9 +6078,9 @@ static void enable_bypass_dsp(struct scx_sched *sch)
* dispatch enabled while a descendant is bypassing, which is all that's
* required.
*
- * bypass_dsp_enabled() test is used to determine whether to enter the
- * bypass dispatch handling path from both bypassing and hosting scheds.
- * Bump enable depth on both @sch and bypass dispatch host.
+ * scx_bypass_dsp_enabled() test is used to determine whether to enter
+ * the bypass dispatch handling path from both bypassing and hosting
+ * scheds. Bump enable depth on both @sch and bypass dispatch host.
*/
ret = atomic_inc_return(&sch->bypass_dsp_enable_depth);
WARN_ON_ONCE(ret <= 0);
@@ -5464,7 +6100,7 @@ static void enable_bypass_dsp(struct scx_sched *sch)
}
/* may be called without holding scx_bypass_lock */
-static void disable_bypass_dsp(struct scx_sched *sch)
+void scx_disable_bypass_dsp(struct scx_sched *sch)
{
s32 ret;
@@ -5481,6 +6117,38 @@ static void disable_bypass_dsp(struct scx_sched *sch)
}
/**
+ * unbypass_renotify_idle - Arm an idle re-notify for a sched leaving bypass
+ * @rq: rq of the cpu leaving bypass
+ * @pos: scheduler that just left bypass on @rq's cpu
+ * @pcpu: @pos's per-cpu state for @rq's cpu
+ *
+ * A sched leaving bypass is owed the ops.update_idle() calls suppressed while
+ * bypassing. A cpu that goes idle during the bypass window and stays idle won't
+ * produce a notification. Arm a re-notify that scx_bypass()'s resched flushes
+ * on the next idle pick.
+ *
+ * An acute case is ops.sub_attach(). If the parent grants the child cids while
+ * attaching, when attach is complete and bypass is lifted, the child may hold
+ * idle cids it never saw go idle.
+ *
+ * The root is no exception as bypass suppresses its notifications the same way.
+ * However, the root uses a separate per-rq flag so its re-notify keeps working
+ * even when !CONFIG_EXT_SUB_SCHED.
+ */
+static void unbypass_renotify_idle(struct rq *rq, struct scx_sched *pos,
+ struct scx_sched_pcpu *pcpu)
+{
+ if (!pos->level) {
+ rq->scx.flags |= SCX_RQ_ROOT_IDLE_RENOTIFY;
+ return;
+ }
+#ifdef CONFIG_EXT_SUB_SCHED
+ pcpu->idle_renotify = true;
+ rq->scx.flags |= SCX_RQ_SUB_IDLE_RENOTIFY;
+#endif
+}
+
+/**
* scx_bypass - [Un]bypass scx_ops and guarantee forward progress
* @sch: sched to bypass
* @bypass: true for bypass, false for unbypass
@@ -5501,18 +6169,18 @@ static void disable_bypass_dsp(struct scx_sched *sch)
*
* - ops.dispatch() is ignored.
*
- * - balance_one() does not set %SCX_RQ_BAL_KEEP on non-zero slice as slice
+ * - dispatch_one() does not report %SCX_DSP_PREV on non-zero slice as slice
* can't be trusted. Whenever a tick triggers, the running task is rotated to
- * the tail of the queue with core_sched_at touched.
+ * the tail of the queue.
*
* - pick_next_task() suppresses zero slice warning.
*
* - scx_kick_cpu() is disabled to avoid irq_work malfunction during PM
* operations.
*
- * - scx_prio_less() reverts to the default core_sched_at order.
+ * - scx_prio_less() reverts to the default runnable_at order.
*/
-static void scx_bypass(struct scx_sched *sch, bool bypass)
+void scx_bypass(struct scx_sched *sch, bool bypass)
{
struct scx_sched *pos;
unsigned long flags;
@@ -5563,11 +6231,17 @@ static void scx_bypass(struct scx_sched *sch, bool bypass)
scx_for_each_descendant_pre(pos, sch) {
struct scx_sched_pcpu *pcpu = per_cpu_ptr(pos->pcpu, cpu);
+ bool was_bypassing = pcpu->flags & SCX_SCHED_PCPU_BYPASSING;
- if (pos->bypass_depth)
+ if (pos->bypass_depth) {
pcpu->flags |= SCX_SCHED_PCPU_BYPASSING;
- else
+ } else {
pcpu->flags &= ~SCX_SCHED_PCPU_BYPASSING;
+ if (was_bypassing) {
+ unbypass_renotify_idle(rq, pos, pcpu);
+ scx_unbypass_replay_ecaps(rq, pos);
+ }
+ }
}
raw_spin_unlock(&scx_sched_lock);
@@ -5578,6 +6252,7 @@ static void scx_bypass(struct scx_sched *sch, bool bypass)
* sees scx_bypassing() before moving tasks to SCX.
*/
if (!scx_enabled()) {
+ scx_rq_lock_drop(rq);
raw_spin_rq_unlock(rq);
continue;
}
@@ -5594,6 +6269,13 @@ static void scx_bypass(struct scx_sched *sch, bool bypass)
if (!scx_is_descendant(scx_task_sched(p), sch))
continue;
+ /*
+ * Bypass trumps protection. Cycling clears for queued
+ * tasks but current task needs explicit stripping.
+ */
+ if (bypass && task_current(rq, p))
+ scx_task_slice_ended(rq, p);
+
/* cycling deq/enq is enough, see the function comment */
scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
/* nothing */ ;
@@ -5604,12 +6286,13 @@ static void scx_bypass(struct scx_sched *sch, bool bypass)
if (cpu_online(cpu) || cpu == smp_processor_id())
resched_curr(rq);
+ scx_rq_lock_drop(rq);
raw_spin_rq_unlock(rq);
}
/* disarming must come after moving all tasks out of the bypass DSQs */
if (!bypass)
- disable_bypass_dsp(sch);
+ scx_disable_bypass_dsp(sch);
unlock:
raw_spin_unlock_irqrestore(&scx_bypass_lock, flags);
}
@@ -5656,12 +6339,18 @@ static const char *scx_exit_reason(enum scx_exit_kind kind)
return "disabled by sysrq-S";
case SCX_EXIT_PARENT:
return "parent exiting";
+ case SCX_EXIT_PARENT_KILL:
+ return "killed by parent scheduler";
case SCX_EXIT_ERROR:
return "runtime error";
case SCX_EXIT_ERROR_BPF:
return "scx_bpf_error";
case SCX_EXIT_ERROR_STALL:
return "runnable task stall";
+ case SCX_EXIT_ERROR_REENQ:
+ return "reenqueue limit";
+ case SCX_EXIT_ERROR_RESCUE:
+ return "rescue bandwidth overload";
default:
return "<UNKNOWN>";
}
@@ -5672,9 +6361,11 @@ static void free_kick_syncs(void)
int cpu;
for_each_possible_cpu(cpu) {
- struct scx_kick_syncs **ksyncs = per_cpu_ptr(&scx_kick_syncs, cpu);
+ struct scx_kick_syncs __rcu **ksyncs = per_cpu_ptr(&scx_kick_syncs, cpu);
struct scx_kick_syncs *to_free;
+ /* flush the pending kick before freeing @ksyncs */
+ irq_work_sync(&cpu_rq(cpu)->scx.kick_cpus_irq_work);
to_free = rcu_replace_pointer(*ksyncs, NULL, true);
if (to_free)
kvfree_rcu(to_free, rcu);
@@ -5701,63 +6392,70 @@ static void refresh_watchdog(void)
cancel_delayed_work_sync(&scx_watchdog_work);
}
-static s32 scx_link_sched(struct scx_sched *sch)
+s32 scx_link_sched(struct scx_sched *sch)
{
- const char *err_msg = "";
- s32 ret = 0;
-
- scoped_guard(raw_spinlock_irq, &scx_sched_lock) {
+ scoped_guard(raw_spinlock_irqsave, &scx_bypass_lock) /* for the parent bypass check */
+ scoped_guard(raw_spinlock, &scx_sched_lock) {
#ifdef CONFIG_EXT_SUB_SCHED
struct scx_sched *parent = scx_parent(sch);
if (parent) {
+ s32 ret;
+
/*
- * scx_claim_exit() propagates exit_kind transition to
- * its sub-scheds while holding scx_sched_lock - either
- * we can see the parent's non-NONE exit_kind or the
- * parent can shoot us down.
+ * Bypass state is spread across per-cpu flags and a
+ * depth count, so inheriting it is tricky and has no
+ * valid use case. Refuse it.
*/
- if (atomic_read(&parent->exit_kind) != SCX_EXIT_NONE) {
- err_msg = "parent disabled";
- ret = -ENOENT;
- break;
+ if (READ_ONCE(parent->bypass_depth)) {
+ scx_error(sch, "parent bypassing (%d)", -EBUSY);
+ return -EBUSY;
}
ret = rhashtable_lookup_insert_fast(&scx_sched_hash,
&sch->hash_node, scx_sched_hash_params);
if (ret) {
- err_msg = "failed to insert into scx_sched_hash";
- break;
+ scx_error(sch, "failed to insert into scx_sched_hash (%d)",
+ ret);
+ return ret;
+ }
+
+ list_add_tail_rcu(&sch->sibling, &parent->children);
+
+ /*
+ * Pairs with the mb after the ->aborting assertion in
+ * scx_claim_exit(). Either we see ->aborting and back
+ * out, or the exit path sees us and exits us.
+ */
+ smp_mb();
+ if (unlikely(READ_ONCE(parent->aborting))) {
+ rhashtable_remove_fast(&scx_sched_hash, &sch->hash_node,
+ scx_sched_hash_params);
+ list_del_rcu(&sch->sibling);
+ scx_error(sch, "parent disabled (%d)", -ENOENT);
+ return -ENOENT;
}
- list_add_tail(&sch->sibling, &parent->children);
+ sch->linked = true;
}
#endif /* CONFIG_EXT_SUB_SCHED */
list_add_tail_rcu(&sch->all, &scx_sched_all);
}
- /*
- * scx_error() takes scx_sched_lock via scx_claim_exit(), so it must run after
- * the guard above is released.
- */
- if (ret) {
- scx_error(sch, "%s (%d)", err_msg, ret);
- return ret;
- }
-
refresh_watchdog();
return 0;
}
-static void scx_unlink_sched(struct scx_sched *sch)
+void scx_unlink_sched(struct scx_sched *sch)
{
scoped_guard(raw_spinlock_irq, &scx_sched_lock) {
#ifdef CONFIG_EXT_SUB_SCHED
- if (scx_parent(sch)) {
+ if (sch->linked) {
rhashtable_remove_fast(&scx_sched_hash, &sch->hash_node,
scx_sched_hash_params);
- list_del_init(&sch->sibling);
+ list_del_rcu(&sch->sibling);
+ sch->linked = false;
}
#endif /* CONFIG_EXT_SUB_SCHED */
list_del_rcu(&sch->all);
@@ -5771,13 +6469,13 @@ static void scx_unlink_sched(struct scx_sched *sch)
* @sch. Once @sch becomes empty during disable, there's no point in dumping it.
* This prevents calling dump ops on a dead sch.
*/
-static void scx_disable_dump(struct scx_sched *sch)
+void scx_disable_dump(struct scx_sched *sch)
{
guard(raw_spinlock_irqsave)(&scx_dump_lock);
sch->dump_disabled = true;
}
-static void scx_log_sched_disable(struct scx_sched *sch)
+void scx_log_sched_disable(struct scx_sched *sch)
{
struct scx_exit_info *ei = sch->exit_info;
const char *type = scx_parent(sch) ? "sub-scheduler" : "scheduler";
@@ -5797,202 +6495,6 @@ static void scx_log_sched_disable(struct scx_sched *sch)
}
}
-#ifdef CONFIG_EXT_SUB_SCHED
-static DECLARE_WAIT_QUEUE_HEAD(scx_unlink_waitq);
-
-static void drain_descendants(struct scx_sched *sch)
-{
- /*
- * Child scheds that finished the critical part of disabling will take
- * themselves off @sch->children. Wait for it to drain. As propagation
- * is recursive, empty @sch->children means that all proper descendant
- * scheds reached unlinking stage.
- */
- wait_event(scx_unlink_waitq, list_empty(&sch->children));
-}
-
-static void scx_fail_parent(struct scx_sched *sch,
- struct task_struct *failed, s32 fail_code)
-{
- struct scx_sched *parent = scx_parent(sch);
- struct scx_task_iter sti;
- struct task_struct *p;
-
- scx_error(parent, "ops.init_task() failed (%d) for %s[%d] while disabling a sub-scheduler",
- fail_code, failed->comm, failed->pid);
-
- /*
- * Once $parent is bypassed, it's safe to put SCX_TASK_NONE tasks into
- * it. This may cause downstream failures on the BPF side but $parent is
- * dying anyway.
- */
- scx_bypass(parent, true);
-
- scx_task_iter_start(&sti, sch->cgrp);
- while ((p = scx_task_iter_next_locked(&sti))) {
- if (scx_task_on_sched(parent, p))
- continue;
-
- scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
- scx_disable_and_exit_task(sch, p);
- scx_set_task_sched(p, parent);
- }
- }
- scx_task_iter_stop(&sti);
-}
-
-static void scx_sub_disable(struct scx_sched *sch)
-{
- struct scx_sched *parent = scx_parent(sch);
- struct scx_task_iter sti;
- struct task_struct *p;
- int ret;
-
- /*
- * Guarantee forward progress and wait for descendants to be disabled.
- * To limit disruptions, $parent is not bypassed. Tasks are fully
- * prepped and then inserted back into $parent.
- */
- scx_bypass(sch, true);
- drain_descendants(sch);
-
- /*
- * Here, every runnable task is guaranteed to make forward progress and
- * we can safely use blocking synchronization constructs. Actually
- * disable ops.
- */
- mutex_lock(&scx_enable_mutex);
- percpu_down_write(&scx_fork_rwsem);
- scx_cgroup_lock();
-
- set_cgroup_sched(sch_cgroup(sch), parent);
-
- scx_task_iter_start(&sti, sch->cgrp);
- while ((p = scx_task_iter_next_locked(&sti))) {
- struct rq *rq;
- struct rq_flags rf;
-
- /* filter out duplicate visits */
- if (scx_task_on_sched(parent, p))
- continue;
-
- /*
- * By the time control reaches here, all descendant schedulers
- * should already have been disabled.
- */
- WARN_ON_ONCE(!scx_task_on_sched(sch, p));
-
- /*
- * @p is pinned by the iter: css_task_iter_next() takes a
- * reference and holds it until the next iter_next() call, so
- * @p->usage is guaranteed > 0.
- */
- get_task_struct(p);
-
- scx_task_iter_unlock(&sti);
-
- /*
- * $p is READY or ENABLED on @sch. Initialize for $parent,
- * disable and exit from @sch, and then switch over to $parent.
- *
- * If a task fails to initialize for $parent, the only available
- * action is disabling $parent too. While this allows disabling
- * of a child sched to cause the parent scheduler to fail, the
- * failure can only originate from ops.init_task() of the
- * parent. A child can't directly affect the parent through its
- * own failures.
- */
- ret = __scx_init_task(parent, p, false);
- if (ret) {
- scx_fail_parent(sch, p, ret);
- put_task_struct(p);
- break;
- }
-
- rq = task_rq_lock(p, &rf);
-
- if (scx_get_task_state(p) == SCX_TASK_DEAD) {
- /*
- * sched_ext_dead() raced us between __scx_init_task()
- * and this rq lock and ran exit_task() on @sch (the
- * sched @p was on at that point), not on $parent.
- * $parent's just-completed init is owed an exit_task()
- * and we issue it here.
- */
- scx_sub_init_cancel_task(parent, p);
- task_rq_unlock(rq, p, &rf);
- put_task_struct(p);
- continue;
- }
-
- scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
- /*
- * $p is initialized for $parent and still attached to
- * @sch. Disable and exit for @sch, switch over to
- * $parent, override the state to READY to account for
- * $p having already been initialized, and then enable.
- */
- scx_disable_and_exit_task(sch, p);
- scx_set_task_state(p, SCX_TASK_INIT_BEGIN);
- scx_set_task_state(p, SCX_TASK_INIT);
- scx_set_task_sched(p, parent);
- scx_set_task_state(p, SCX_TASK_READY);
- scx_enable_task(parent, p);
- }
-
- task_rq_unlock(rq, p, &rf);
- put_task_struct(p);
- }
- scx_task_iter_stop(&sti);
-
- scx_disable_dump(sch);
-
- scx_cgroup_unlock();
- percpu_up_write(&scx_fork_rwsem);
-
- /*
- * All tasks are moved off of @sch but there may still be on-going
- * operations (e.g. ops.select_cpu()). Drain them by flushing RCU. Use
- * the expedited version as ancestors may be waiting in bypass mode.
- * Also, tell the parent that there is no need to keep running bypass
- * DSQs for us.
- */
- synchronize_rcu_expedited();
- disable_bypass_dsp(sch);
-
- scx_unlink_sched(sch);
-
- mutex_unlock(&scx_enable_mutex);
-
- /*
- * @sch is now unlinked from the parent's children list. Notify and call
- * ops.sub_detach/exit(). Note that ops.sub_detach/exit() must be called
- * after unlinking and releasing all locks. See scx_claim_exit().
- */
- wake_up_all(&scx_unlink_waitq);
-
- if (parent->ops.sub_detach && sch->sub_attached) {
- struct scx_sub_detach_args sub_detach_args = {
- .ops = &sch->ops,
- .cgroup_path = sch->cgrp_path,
- };
- SCX_CALL_OP(parent, sub_detach, NULL,
- &sub_detach_args);
- }
-
- scx_log_sched_disable(sch);
-
- if (sch->ops.exit)
- SCX_CALL_OP(sch, exit, NULL, sch->exit_info);
- if (sch->sub_kset)
- kobject_del(&sch->sub_kset->kobj);
- kobject_del(&sch->kobj);
-}
-#else /* CONFIG_EXT_SUB_SCHED */
-static inline void drain_descendants(struct scx_sched *sch) { }
-static inline void scx_sub_disable(struct scx_sched *sch) { }
-#endif /* CONFIG_EXT_SUB_SCHED */
-
static void scx_root_disable(struct scx_sched *sch)
{
struct scx_task_iter sti;
@@ -6030,10 +6532,11 @@ static void scx_root_disable(struct scx_sched *sch)
WRITE_ONCE(scx_switching_all, false);
/*
- * Shut down cgroup support before tasks so that the cgroup attach path
- * doesn't race against scx_disable_and_exit_task().
+ * Shut down cgroup support before tasks so that the cgroup attach and
+ * migration paths don't race against scx_disable_and_exit_task().
*/
scx_cgroup_lock();
+ scx_cgroup_enabled = false;
scx_cgroup_exit(sch);
scx_cgroup_unlock();
@@ -6073,12 +6576,9 @@ static void scx_root_disable(struct scx_sched *sch)
percpu_up_write(&scx_fork_rwsem);
/*
- * Invalidate all the rq clocks to prevent getting outdated
- * rq clocks from a previous scx scheduler.
- *
- * Also re-balance the dl_server bandwidth reservations: detach
- * ext_server (no more sched_ext tasks) and reinstate fair_server if it
- * was previously detached because we were running in full mode.
+ * Re-balance the dl_server bandwidth reservations: detach ext_server
+ * (no more sched_ext tasks) and reinstate fair_server if it was
+ * previously detached because we were running in full mode.
*
* Unlike the enable path, this runs on a recovery path that cannot
* fail, so we use dl_server_swap_bw() to atomically free ext_server's
@@ -6091,8 +6591,6 @@ static void scx_root_disable(struct scx_sched *sch)
for_each_possible_cpu(cpu) {
struct rq *rq = cpu_rq(cpu);
- scx_rq_clock_invalidate(rq);
-
scoped_guard(rq_lock_irqsave, rq) {
update_rq_clock(rq);
if (was_switched_all) {
@@ -6121,14 +6619,23 @@ static void scx_root_disable(struct scx_sched *sch)
if (sch->ops.exit)
SCX_CALL_OP(sch, exit, NULL, sch->exit_info);
+ /*
+ * @sch's non-ops programs such as timers and tracers can fire after
+ * ops.exit(). Now that exit is complete, stop scx_prog_sched() from
+ * resolving to @sch and drain in-flight resolvers.
+ */
+ WRITE_ONCE(sch->dead, true);
+ synchronize_rcu();
+
scx_unlink_sched(sch);
/*
- * scx_root clearing must be inside cpus_read_lock(). See
- * handle_hotplug().
+ * scx_root clearing and cid table retirement must be inside
+ * cpus_read_lock(). See handle_hotplug().
*/
cpus_read_lock();
RCU_INIT_POINTER(scx_root, NULL);
+ scx_cid_retire_tables();
cpus_read_unlock();
/*
@@ -6140,7 +6647,9 @@ static void scx_root_disable(struct scx_sched *sch)
if (sch->sub_kset)
kobject_del(&sch->sub_kset->kobj);
#endif
- kobject_del(&sch->kobj);
+ /* not added if enable failed before scx_sched_sysfs_add() */
+ if (sch->kobj.state_in_sysfs)
+ kobject_del(&sch->kobj);
free_kick_syncs();
@@ -6151,12 +6660,36 @@ done:
scx_bypass(sch, false);
}
+/**
+ * scx_propagate_exit_irq_workfn - Claim SCX_EXIT_PARENT on the exiting subtree
+ * @irq_work: &scx_sched.propagate_exit_irq_work
+ *
+ * Queued by scx_claim_exit() after a non-PARENT claim. Claims SCX_EXIT_PARENT
+ * on each descendant, giving every one its own disable work - most of disabling
+ * is serialized but ops.exit() can take arbitrarily long and running them in
+ * separate helper kthreads parallelizes it. No recursion as only non-PARENT
+ * claims propagate.
+ */
+static void scx_propagate_exit_irq_workfn(struct irq_work *irq_work)
+{
+ struct scx_sched *sch = container_of(irq_work, struct scx_sched,
+ propagate_exit_irq_work);
+ struct scx_sched *pos;
+
+ scoped_guard (raw_spinlock_irqsave, &scx_sched_lock) {
+ scx_for_each_descendant_pre(pos, sch)
+ scx_disable(pos, SCX_EXIT_PARENT);
+ }
+}
+
/*
* Claim the exit on @sch. The caller must ensure that the helper kthread work
* is kicked before the current task can be preempted. Once exit_kind is
* claimed, scx_error() can no longer trigger, so if the current task gets
* preempted and the BPF scheduler fails to schedule it back, the helper work
* will never be kicked and the whole system can wedge.
+ *
+ * Lock-free and safe to call from any context including NMI.
*/
static bool scx_claim_exit(struct scx_sched *sch, enum scx_exit_kind kind)
{
@@ -6170,35 +6703,31 @@ static bool scx_claim_exit(struct scx_sched *sch, enum scx_exit_kind kind)
if (!atomic_try_cmpxchg(&sch->exit_kind, &none, kind))
return false;
- /*
- * Some CPUs may be trapped in the dispatch paths. Set the aborting
- * flag to break potential live-lock scenarios, ensuring we can
- * successfully reach scx_bypass().
- */
- WRITE_ONCE(sch->aborting, true);
+ if (kind == SCX_EXIT_PARENT) {
+ /* an ancestor is already sweeping the subtree */
+ WRITE_ONCE(sch->aborting, true);
+ } else {
+ struct scx_sched *pos;
- /*
- * Propagate exits to descendants immediately. Each has a dedicated
- * helper kthread and can run in parallel. While most of disabling is
- * serialized, running them in separate threads allows parallelizing
- * ops.exit(), which can take arbitrarily long prolonging bypass mode.
- *
- * To guarantee forward progress, this propagation must be in-line so
- * that ->aborting is synchronously asserted for all sub-scheds. The
- * propagation is also the interlocking point against sub-sched
- * attachment. See scx_link_sched().
- *
- * This doesn't cause recursions as propagation only takes place for
- * non-propagation exits.
- */
- if (kind != SCX_EXIT_PARENT) {
- scoped_guard (raw_spinlock_irqsave, &scx_sched_lock) {
- struct scx_sched *pos;
+ /*
+ * CPUs may be live-locked in the dispatch paths of @sch or its
+ * descendants, which ->aborting breaks. Sweep the subtree
+ * locklessly so that this works from NMI. smp_store_mb() orders
+ * each node's ->aborting store before its children are walked -
+ * either we see a racing scx_link_sched() on ->children or it
+ * sees ->aborting.
+ */
+ scoped_guard (rcu) {
scx_for_each_descendant_pre(pos, sch)
- scx_disable(pos, SCX_EXIT_PARENT);
+ smp_store_mb(pos->aborting, true);
}
+
+ irq_work_queue(&sch->propagate_exit_irq_work);
}
+ /* fired after ->aborting is set so callbacks can't delay recovery */
+ trace_sched_ext_exit(sch, kind);
+
return true;
}
@@ -6240,7 +6769,7 @@ static void scx_disable(struct scx_sched *sch, enum scx_exit_kind kind)
* as a noop. Syncing the irq_work first is required to guarantee the
* kthread work has been queued before waiting for it.
*/
-static void scx_flush_disable_work(struct scx_sched *sch)
+void scx_flush_disable_work(struct scx_sched *sch)
{
int kind;
@@ -6260,7 +6789,7 @@ static void dump_newline(struct seq_buf *s)
seq_buf_putc(s, '\n');
}
-static __printf(2, 3) void dump_line(struct seq_buf *s, const char *fmt, ...)
+__printf(2, 3) void scx_dump_line(struct seq_buf *s, const char *fmt, ...)
{
va_list args;
@@ -6292,7 +6821,7 @@ static void dump_stack_trace(struct seq_buf *s, const char *prefix,
unsigned int i;
for (i = 0; i < len; i++)
- dump_line(s, "%s%pS", prefix, (void *)bt[i]);
+ scx_dump_line(s, "%s%pS", prefix, (void *)bt[i]);
}
static void ops_dump_init(struct seq_buf *s, const char *prefix)
@@ -6342,7 +6871,7 @@ static void ops_dump_flush(void)
*/
c = *end;
*end = '\0';
- dump_line(dd->s, "%s%s", dd->prefix, line);
+ scx_dump_line(dd->s, "%s%s", dd->prefix, line);
if (c == '\0')
break;
@@ -6386,21 +6915,19 @@ static void scx_dump_task(struct scx_sched *sch, struct seq_buf *s, struct scx_d
(unsigned long long)p->scx.dsq->id);
dump_newline(s);
- dump_line(s, " %c%c %s[%d] %s%s %+ldms",
- marker, task_state_to_char(p), p->comm, p->pid,
- own_marker, sch_id_buf,
- jiffies_delta_msecs(p->scx.runnable_at, dctx->at_jiffies));
- dump_line(s, " scx_state/flags=%u/0x%x dsq_flags=0x%x ops_state/qseq=%lu/%lu",
- scx_get_task_state(p) >> SCX_TASK_STATE_SHIFT,
- p->scx.flags & ~SCX_TASK_STATE_MASK,
- p->scx.dsq_flags, ops_state & SCX_OPSS_STATE_MASK,
- ops_state >> SCX_OPSS_QSEQ_SHIFT);
- dump_line(s, " sticky/holding_cpu=%d/%d dsq_id=%s",
- p->scx.sticky_cpu, p->scx.holding_cpu, dsq_id_buf);
- dump_line(s, " dsq_vtime=%llu slice=%llu weight=%u",
- p->scx.dsq_vtime, p->scx.slice, p->scx.weight);
- dump_line(s, " cpus=%*pb no_mig=%u", cpumask_pr_args(p->cpus_ptr),
- p->migration_disabled);
+ scx_dump_line(s, " %c%c %s[%d] %s%s %+ldms",
+ marker, task_state_to_char(p), p->comm, p->pid, own_marker, sch_id_buf,
+ jiffies_delta_msecs(p->scx.runnable_at, dctx->at_jiffies));
+ scx_dump_line(s, " scx_state/flags=%u/0x%x dsq_flags=0x%x ops_state/qseq=%lu/%lu",
+ scx_get_task_state(p) >> SCX_TASK_STATE_SHIFT,
+ p->scx.flags & ~SCX_TASK_STATE_MASK, p->scx.dsq_flags,
+ ops_state & SCX_OPSS_STATE_MASK, ops_state >> SCX_OPSS_QSEQ_SHIFT);
+ scx_dump_line(s, " sticky/holding_cpu=%d/%d dsq_id=%s",
+ p->scx.sticky_cpu, p->scx.holding_cpu, dsq_id_buf);
+ scx_dump_line(s, " dsq_vtime=%llu slice=%llu weight=%u",
+ p->scx.dsq_vtime, p->scx.slice, p->scx.weight);
+ scx_dump_line(s, " cpus=%*pb no_mig=%u", cpumask_pr_args(p->cpus_ptr),
+ p->migration_disabled);
if (SCX_HAS_OP(sch, dump_task)) {
ops_dump_init(s, " ");
@@ -6422,6 +6949,7 @@ static void scx_dump_cpu(struct scx_sched *sch, struct seq_buf *s,
bool dump_all_tasks)
{
struct rq *rq = cpu_rq(cpu);
+ struct scx_sched_pcpu *pcpu = per_cpu_ptr(sch->pcpu, cpu);
struct rq_flags rf;
struct task_struct *p;
struct seq_buf ns;
@@ -6447,28 +6975,27 @@ static void scx_dump_cpu(struct scx_sched *sch, struct seq_buf *s,
seq_buf_init(&ns, buf, avail);
dump_newline(&ns);
- dump_line(&ns, "CPU %-4d: nr_run=%u flags=0x%x cpu_rel=%d ops_qseq=%lu ksync=%lu",
- cpu, rq->scx.nr_running, rq->scx.flags,
- rq->scx.cpu_released, rq->scx.ops_qseq,
- rq->scx.kick_sync);
- dump_line(&ns, " curr=%s[%d] class=%ps",
- rq->curr->comm, rq->curr->pid,
- rq->curr->sched_class);
- if (!cpumask_empty(rq->scx.cpus_to_kick))
- dump_line(&ns, " cpus_to_kick : %*pb",
- cpumask_pr_args(rq->scx.cpus_to_kick));
- if (!cpumask_empty(rq->scx.cpus_to_kick_if_idle))
- dump_line(&ns, " idle_to_kick : %*pb",
- cpumask_pr_args(rq->scx.cpus_to_kick_if_idle));
- if (!cpumask_empty(rq->scx.cpus_to_preempt))
- dump_line(&ns, " cpus_to_preempt: %*pb",
- cpumask_pr_args(rq->scx.cpus_to_preempt));
- if (!cpumask_empty(rq->scx.cpus_to_wait))
- dump_line(&ns, " cpus_to_wait : %*pb",
- cpumask_pr_args(rq->scx.cpus_to_wait));
+ scx_dump_line(&ns, "CPU %-4d: nr_run=%u flags=0x%x cpu_rel=%d ksync=%lu",
+ cpu, rq->scx.nr_running, rq->scx.flags, rq->scx.cpu_released,
+ rq->scx.kick_sync);
+ scx_rescue_dump(&ns, rq);
+ scx_dump_line(&ns, " curr=%s[%d] class=%ps",
+ rq->curr->comm, rq->curr->pid, rq->curr->sched_class);
+ if (!cpumask_empty(pcpu->cpus_to_kick))
+ scx_dump_line(&ns, " cpus_to_kick : %*pb",
+ cpumask_pr_args(pcpu->cpus_to_kick));
+ if (!cpumask_empty(pcpu->cpus_to_kick_if_idle))
+ scx_dump_line(&ns, " idle_to_kick : %*pb",
+ cpumask_pr_args(pcpu->cpus_to_kick_if_idle));
+ if (!cpumask_empty(pcpu->cpus_to_preempt))
+ scx_dump_line(&ns, " cpus_to_preempt: %*pb",
+ cpumask_pr_args(pcpu->cpus_to_preempt));
+ if (!cpumask_empty(pcpu->cpus_to_wait))
+ scx_dump_line(&ns, " cpus_to_wait : %*pb",
+ cpumask_pr_args(pcpu->cpus_to_wait));
if (!cpumask_empty(rq->scx.cpus_to_sync))
- dump_line(&ns, " cpus_to_sync : %*pb",
- cpumask_pr_args(rq->scx.cpus_to_sync));
+ scx_dump_line(&ns, " cpus_to_sync : %*pb",
+ cpumask_pr_args(rq->scx.cpus_to_sync));
used = seq_buf_used(&ns);
if (SCX_HAS_OP(sch, dump_cpu)) {
@@ -6536,25 +7063,25 @@ static void scx_dump_state(struct scx_sched *sch, struct scx_exit_info *ei,
#ifdef CONFIG_EXT_SUB_SCHED
if (sch->level == 0)
- dump_line(&s, "%s: root", sch->ops.name);
+ scx_dump_line(&s, "%s: root", sch->ops.name);
else
- dump_line(&s, "%s: sub%d-%llu %s",
- sch->ops.name, sch->level, sch->ops.sub_cgroup_id,
- sch->cgrp_path);
+ scx_dump_line(&s, "%s: sub%d-%llu %s",
+ sch->ops.name, sch->level, sch->ops.sub_cgroup_id,
+ sch->cgrp_path);
#endif
if (ei->kind == SCX_EXIT_NONE) {
- dump_line(&s, "Debug dump triggered by %s", ei->reason);
+ scx_dump_line(&s, "Debug dump triggered by %s", ei->reason);
} else {
if (ei->exit_cpu >= 0)
- dump_line(&s, "%s[%d] triggered exit kind %d on CPU %d:",
- current->comm, current->pid, ei->kind,
- ei->exit_cpu);
+ scx_dump_line(&s, "%s[%d] triggered exit kind %d on CPU %d:",
+ current->comm, current->pid, ei->kind,
+ ei->exit_cpu);
else
- dump_line(&s, "%s[%d] triggered exit kind %d:",
- current->comm, current->pid, ei->kind);
- dump_line(&s, " %s (%s)", ei->reason, ei->msg);
+ scx_dump_line(&s, "%s[%d] triggered exit kind %d:",
+ current->comm, current->pid, ei->kind);
+ scx_dump_line(&s, " %s (%s)", ei->reason, ei->msg);
dump_newline(&s);
- dump_line(&s, "Backtrace:");
+ scx_dump_line(&s, "Backtrace:");
dump_stack_trace(&s, " ", ei->bt, ei->bt_len);
}
@@ -6565,38 +7092,37 @@ static void scx_dump_state(struct scx_sched *sch, struct scx_exit_info *ei,
}
dump_newline(&s);
- dump_line(&s, "CPU states");
- dump_line(&s, "----------");
+ scx_dump_line(&s, "CPU states");
+ scx_dump_line(&s, "----------");
/*
- * Dump the exit CPU first so it isn't lost to dump truncation, then
- * walk the rest in order, skipping the one already dumped.
+ * Dump stalled CPUs first so they aren't lost to dump truncation, then
+ * walk the rest in order. Fall back to exit_cpu if no stall mask set.
*/
- if (ei->exit_cpu >= 0)
- scx_dump_cpu(sch, &s, &dctx, ei->exit_cpu, dump_all_tasks);
- for_each_possible_cpu(cpu) {
- if (cpu != ei->exit_cpu)
+ if (!cpumask_empty(sch->stall_cpus)) {
+ for_each_cpu(cpu, sch->stall_cpus)
scx_dump_cpu(sch, &s, &dctx, cpu, dump_all_tasks);
+ for_each_possible_cpu(cpu) {
+ if (!cpumask_test_cpu(cpu, sch->stall_cpus))
+ scx_dump_cpu(sch, &s, &dctx, cpu, dump_all_tasks);
+ }
+ } else {
+ if (ei->exit_cpu >= 0)
+ scx_dump_cpu(sch, &s, &dctx, ei->exit_cpu, dump_all_tasks);
+ for_each_possible_cpu(cpu) {
+ if (cpu != ei->exit_cpu)
+ scx_dump_cpu(sch, &s, &dctx, cpu, dump_all_tasks);
+ }
}
dump_newline(&s);
- dump_line(&s, "Event counters");
- dump_line(&s, "--------------");
+ scx_dump_line(&s, "Event counters");
+ scx_dump_line(&s, "--------------");
scx_read_events(sch, &events);
- scx_dump_event(s, &events, SCX_EV_SELECT_CPU_FALLBACK);
- scx_dump_event(s, &events, SCX_EV_DISPATCH_LOCAL_DSQ_OFFLINE);
- scx_dump_event(s, &events, SCX_EV_DISPATCH_KEEP_LAST);
- scx_dump_event(s, &events, SCX_EV_ENQ_SKIP_EXITING);
- scx_dump_event(s, &events, SCX_EV_ENQ_SKIP_MIGRATION_DISABLED);
- scx_dump_event(s, &events, SCX_EV_REENQ_IMMED);
- scx_dump_event(s, &events, SCX_EV_REENQ_LOCAL_REPEAT);
- scx_dump_event(s, &events, SCX_EV_REFILL_SLICE_DFL);
- scx_dump_event(s, &events, SCX_EV_BYPASS_DURATION);
- scx_dump_event(s, &events, SCX_EV_BYPASS_DISPATCH);
- scx_dump_event(s, &events, SCX_EV_BYPASS_ACTIVATE);
- scx_dump_event(s, &events, SCX_EV_INSERT_NOT_OWNED);
- scx_dump_event(s, &events, SCX_EV_SUB_BYPASS_DISPATCH);
+#define SCX_EVENT(name) scx_dump_event(s, &events, name)
+ SCX_EVENTS_LIST(SCX_EVENT);
+#undef SCX_EVENT
if (seq_buf_has_overflowed(&s) && dump_len >= sizeof(trunc_marker))
memcpy(ei->dump + dump_len - sizeof(trunc_marker),
@@ -6614,24 +7140,21 @@ static void scx_disable_irq_workfn(struct irq_work *irq_work)
kthread_queue_work(sch->helper, &sch->disable_work);
}
-bool scx_vexit(struct scx_sched *sch,
- enum scx_exit_kind kind, s64 exit_code, s32 exit_cpu,
- const char *fmt, va_list args)
+/* finish exit_info and kick the disable work, ei->msg must already be set */
+static void scx_finish_exit(struct scx_sched *sch, enum scx_exit_kind kind,
+ s64 exit_code, s32 exit_cpu)
{
struct scx_exit_info *ei = sch->exit_info;
- guard(preempt)();
-
- if (!scx_claim_exit(sch, kind))
- return false;
-
ei->exit_code = exit_code;
#ifdef CONFIG_STACKTRACE
- if (kind >= SCX_EXIT_ERROR)
+ /*
+ * stack_trace_save()'s NMI-safety is arch-dependent and undocumented.
+ * Skip the backtrace when exiting from NMI.
+ */
+ if (kind >= SCX_EXIT_ERROR && !in_nmi())
ei->bt_len = stack_trace_save(ei->bt, SCX_EXIT_BT_LEN, 1);
#endif
- vscnprintf(ei->msg, SCX_EXIT_MSG_LEN, fmt, args);
-
/*
* Set ei->kind and ->reason for scx_dump_state(). They'll be set again
* in scx_disable_workfn().
@@ -6641,6 +7164,22 @@ bool scx_vexit(struct scx_sched *sch,
ei->exit_cpu = exit_cpu;
irq_work_queue(&sch->disable_irq_work);
+}
+
+bool scx_vexit(struct scx_sched *sch,
+ enum scx_exit_kind kind, s64 exit_code, s32 exit_cpu,
+ const char *fmt, va_list args)
+{
+ struct scx_exit_info *ei = sch->exit_info;
+
+ guard(preempt)();
+
+ if (!scx_claim_exit(sch, kind))
+ return false;
+
+ vscnprintf(ei->msg, SCX_EXIT_MSG_LEN, fmt, args);
+
+ scx_finish_exit(sch, kind, exit_code, exit_cpu);
return true;
}
@@ -6653,7 +7192,7 @@ static int alloc_kick_syncs(void)
* can exceed percpu allocator limits on large machines.
*/
for_each_possible_cpu(cpu) {
- struct scx_kick_syncs **ksyncs = per_cpu_ptr(&scx_kick_syncs, cpu);
+ struct scx_kick_syncs __rcu **ksyncs = per_cpu_ptr(&scx_kick_syncs, cpu);
struct scx_kick_syncs *new_ksyncs;
WARN_ON_ONCE(rcu_access_pointer(*ksyncs));
@@ -6687,7 +7226,7 @@ static struct scx_sched_pnode *alloc_pnode(struct scx_sched *sch, int node)
if (!pnode)
return NULL;
- if (init_dsq(&pnode->global_dsq, SCX_DSQ_GLOBAL, sch)) {
+ if (scx_init_dsq(&pnode->global_dsq, SCX_DSQ_GLOBAL, sch)) {
kfree(pnode);
return NULL;
}
@@ -6696,30 +7235,12 @@ static struct scx_sched_pnode *alloc_pnode(struct scx_sched *sch, int node)
}
/*
- * scx_enable() is offloaded to a dedicated system-wide RT kthread to avoid
- * starvation. During the READY -> ENABLED task switching loop, the calling
- * thread's sched_class gets switched from fair to ext. As fair has higher
- * priority than ext, the calling thread can be indefinitely starved under
- * fair-class saturation, leading to a system hang.
- */
-struct scx_enable_cmd {
- struct kthread_work work;
- union {
- struct sched_ext_ops *ops;
- struct sched_ext_ops_cid *ops_cid;
- };
- bool is_cid_type;
- struct bpf_map *arena_map; /* arena ref to transfer to sch */
- int ret;
-};
-
-/*
* Allocate and initialize a new scx_sched. @cgrp's reference is always
* consumed whether the function succeeds or fails.
*/
-static struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd,
- struct cgroup *cgrp,
- struct scx_sched *parent)
+struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd,
+ struct cgroup *cgrp,
+ struct scx_sched *parent)
{
struct sched_ext_ops *ops = cmd->ops;
struct scx_sched *sch;
@@ -6766,7 +7287,7 @@ static struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd,
}
for_each_possible_cpu(cpu) {
- ret = init_dsq(bypass_dsq(sch, cpu), SCX_DSQ_BYPASS, sch);
+ ret = scx_init_dsq(scx_bypass_dsq(sch, cpu), SCX_DSQ_BYPASS, sch);
if (ret) {
bypass_fail_cpu = cpu;
goto err_free_pcpu;
@@ -6776,8 +7297,20 @@ static struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd,
for_each_possible_cpu(cpu) {
struct scx_sched_pcpu *pcpu = per_cpu_ptr(sch->pcpu, cpu);
+ node = cpu_to_node(cpu);
pcpu->sch = sch;
INIT_LIST_HEAD(&pcpu->deferred_reenq_local.node);
+#ifdef CONFIG_EXT_SUB_SCHED
+ init_llist_node(&pcpu->ecaps_to_sync_node);
+#endif
+ INIT_LIST_HEAD(&pcpu->to_kick_node);
+ if (!zalloc_cpumask_var_node(&pcpu->cpus_to_kick, GFP_KERNEL, node) ||
+ !zalloc_cpumask_var_node(&pcpu->cpus_to_kick_if_idle, GFP_KERNEL, node) ||
+ !zalloc_cpumask_var_node(&pcpu->cpus_to_preempt, GFP_KERNEL, node) ||
+ !zalloc_cpumask_var_node(&pcpu->cpus_to_wait, GFP_KERNEL, node)) {
+ ret = -ENOMEM;
+ goto err_free_pcpu;
+ }
}
sch->helper = kthread_run_worker(0, "sched_ext_helper");
@@ -6793,6 +7326,7 @@ static struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd,
level * sizeof(parent->ancestors[0]));
sch->ancestors[level] = sch;
sch->level = level;
+ sch->id = atomic64_inc_return(&scx_sched_id_cursor);
if (ops->timeout_ms)
sch->watchdog_timeout = msecs_to_jiffies(ops->timeout_ms);
@@ -6802,6 +7336,7 @@ static struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd,
sch->slice_dfl = SCX_SLICE_DFL;
atomic_set(&sch->exit_kind, SCX_EXIT_NONE);
sch->disable_irq_work = IRQ_WORK_INIT_HARD(scx_disable_irq_workfn);
+ sch->propagate_exit_irq_work = IRQ_WORK_INIT_HARD(scx_propagate_exit_irq_workfn);
kthread_init_work(&sch->disable_work, scx_disable_workfn);
timer_setup(&sch->bypass_lb_timer, scx_bypass_lb_timerfn, 0);
@@ -6813,6 +7348,10 @@ static struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd,
ret = -ENOMEM;
goto err_free_lb_cpumask;
}
+ if (!zalloc_cpumask_var(&sch->stall_cpus, GFP_KERNEL)) {
+ ret = -ENOMEM;
+ goto err_free_lb_resched_cpumask;
+ }
/*
* Copy ops through the right union view. For cid-form the source is
* struct sched_ext_ops_cid which lacks the trailing cpu_acquire/
@@ -6825,11 +7364,6 @@ static struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd,
sch->ops = *cmd->ops;
}
- rcu_assign_pointer(ops->priv, sch);
-
- sch->kobj.kset = scx_kset;
- INIT_LIST_HEAD(&sch->all);
-
#ifdef CONFIG_EXT_SUB_SCHED
char *buf = kzalloc(PATH_MAX, GFP_KERNEL);
if (!buf) {
@@ -6847,38 +7381,36 @@ static struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd,
sch->cgrp = cgrp;
INIT_LIST_HEAD(&sch->children);
INIT_LIST_HEAD(&sch->sibling);
+#endif /* CONFIG_EXT_SUB_SCHED */
- if (parent)
- ret = kobject_init_and_add(&sch->kobj, &scx_ktype,
- &parent->sub_kset->kobj,
- "sub-%llu", cgroup_id(cgrp));
- else
- ret = kobject_init_and_add(&sch->kobj, &scx_ktype, NULL, "root");
+ /*
+ * Publishing makes @sch visible to scx_prog_sched() readers. Failure
+ * paths after this point must free @sch through kobject_put() whose
+ * release path defers the actual freeing by an RCU grace period.
+ */
+ rcu_assign_pointer(ops->priv, sch);
- if (ret < 0) {
- RCU_INIT_POINTER(ops->priv, NULL);
- kobject_put(&sch->kobj);
- return ERR_PTR(ret);
- }
+ sch->kobj.kset = scx_kset;
+ INIT_LIST_HEAD(&sch->all);
- if (ops->sub_attach) {
- sch->sub_kset = kset_create_and_add("sub", NULL, &sch->kobj);
- if (!sch->sub_kset) {
- RCU_INIT_POINTER(ops->priv, NULL);
- kobject_put(&sch->kobj);
- return ERR_PTR(-ENOMEM);
- }
- }
-#else /* CONFIG_EXT_SUB_SCHED */
- ret = kobject_init_and_add(&sch->kobj, &scx_ktype, NULL, "root");
- if (ret < 0) {
- RCU_INIT_POINTER(ops->priv, NULL);
- kobject_put(&sch->kobj);
- return ERR_PTR(ret);
+#ifdef CONFIG_EXT_SUB_SCHED
+ if (parent) {
+ /*
+ * Pin @parent for @sch's lifetime. The kobject hierarchy pins
+ * it only via @parent->sub_kset, which is dropped during
+ * disable. Released in scx_sched_free_rcu_work().
+ */
+ kobject_get(&parent->kobj);
}
#endif /* CONFIG_EXT_SUB_SCHED */
/*
+ * Init the kobj but don't add to sysfs yet. The enable path calls
+ * scx_sched_sysfs_add() once @sch's sysfs-visible state is initialized.
+ */
+ kobject_init(&sch->kobj, &scx_ktype);
+
+ /*
* Consume the arena_map ref bpf_scx_reg_cid() took. Defer to here so
* earlier failure paths leave cmd->arena_map set and bpf_scx_reg_cid
* drops the ref. After this point, sch owns the ref and any cleanup
@@ -6895,18 +7427,27 @@ static struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd,
#ifdef CONFIG_EXT_SUB_SCHED
err_free_lb_resched:
- RCU_INIT_POINTER(ops->priv, NULL);
- free_cpumask_var(sch->bypass_lb_resched_cpumask);
+ free_cpumask_var(sch->stall_cpus);
#endif
+err_free_lb_resched_cpumask:
+ free_cpumask_var(sch->bypass_lb_resched_cpumask);
err_free_lb_cpumask:
free_cpumask_var(sch->bypass_lb_donee_cpumask);
err_stop_helper:
kthread_destroy_worker(sch->helper);
err_free_pcpu:
for_each_possible_cpu(cpu) {
+ struct scx_sched_pcpu *pcpu = per_cpu_ptr(sch->pcpu, cpu);
+
+ free_cpumask_var(pcpu->cpus_to_kick);
+ free_cpumask_var(pcpu->cpus_to_kick_if_idle);
+ free_cpumask_var(pcpu->cpus_to_preempt);
+ free_cpumask_var(pcpu->cpus_to_wait);
+ }
+ for_each_possible_cpu(cpu) {
if (cpu == bypass_fail_cpu)
break;
- exit_dsq(bypass_dsq(sch, cpu));
+ exit_dsq(scx_bypass_dsq(sch, cpu));
}
free_percpu(sch->pcpu);
err_free_pnode:
@@ -6926,6 +7467,36 @@ err_put_cgrp:
return ERR_PTR(ret);
}
+/*
+ * Add @sch's kobject to sysfs, and create its sub_kset if the scheduler
+ * implements ops.sub_attach. Called by the enable workfns once @sch's
+ * sysfs-visible state is initialized.
+ */
+int scx_sched_sysfs_add(struct scx_sched *sch)
+{
+#ifdef CONFIG_EXT_SUB_SCHED
+ struct scx_sched *parent = scx_parent(sch);
+ int ret;
+
+ if (parent)
+ ret = kobject_add(&sch->kobj, &parent->sub_kset->kobj,
+ "sub-%llu", cgroup_id(sch_cgroup(sch)));
+ else
+ ret = kobject_add(&sch->kobj, NULL, "root");
+ if (ret < 0)
+ return ret;
+
+ if (sch->ops.sub_attach) {
+ sch->sub_kset = kset_create_and_add("sub", NULL, &sch->kobj);
+ if (!sch->sub_kset)
+ return -ENOMEM;
+ }
+ return 0;
+#else
+ return kobject_add(&sch->kobj, NULL, "root");
+#endif
+}
+
static int check_hotplug_seq(struct scx_sched *sch,
const struct sched_ext_ops *ops)
{
@@ -6950,7 +7521,7 @@ static int check_hotplug_seq(struct scx_sched *sch,
return 0;
}
-static int validate_ops(struct scx_sched *sch, const struct sched_ext_ops *ops)
+int scx_validate_ops(struct scx_sched *sch, const struct sched_ext_ops *ops)
{
/*
* It doesn't make sense to specify the SCX_OPS_ENQ_LAST flag if the
@@ -6967,7 +7538,7 @@ static int validate_ops(struct scx_sched *sch, const struct sched_ext_ops *ops)
* enabled it.
*/
if ((ops->flags & SCX_OPS_TID_TO_TASK) && scx_parent(sch) &&
- !(scx_root->ops.flags & SCX_OPS_TID_TO_TASK)) {
+ !(sch->ancestors[0]->ops.flags & SCX_OPS_TID_TO_TASK)) {
scx_error(sch, "SCX_OPS_TID_TO_TASK requires root scheduler to enable it");
return -EINVAL;
}
@@ -6988,7 +7559,7 @@ static int validate_ops(struct scx_sched *sch, const struct sched_ext_ops *ops)
* run past the BPF allocation. Skip for cid-form.
*/
if (!sch->is_cid_type && (ops->cpu_acquire || ops->cpu_release))
- pr_warn("ops->cpu_acquire/release() are deprecated, use sched_switch TP instead\n");
+ pr_warn_ratelimited("ops->cpu_acquire/release() are deprecated, use sched_switch TP instead\n");
/*
* Sub-scheduler support is tied to the cid-form struct_ops. A sub-sched
@@ -7053,22 +7624,24 @@ static void scx_root_enable_workfn(struct kthread_work *work)
#ifdef CONFIG_EXT_SUB_SCHED
cgroup_get(cgrp);
#endif
+ /*
+ * Transition to ENABLING to arm the disable path. Allocation failure
+ * still unwinds locally. Full disabling on failure applies only after
+ * scx_alloc_and_add_sched() succeeds.
+ */
+ WARN_ON_ONCE(scx_set_enable_state(SCX_ENABLING) != SCX_DISABLED);
+ WARN_ON_ONCE(scx_root);
+
sch = scx_alloc_and_add_sched(cmd, cgrp, NULL);
if (IS_ERR(sch)) {
ret = PTR_ERR(sch);
+ WARN_ON_ONCE(scx_set_enable_state(SCX_DISABLED) != SCX_ENABLING);
goto err_free_tid_hash;
}
if (sch->is_cid_type)
static_branch_enable(&__scx_is_cid_type);
- /*
- * Transition to ENABLING and clear exit info to arm the disable path.
- * Failure triggers full disabling from here on.
- */
- WARN_ON_ONCE(scx_set_enable_state(SCX_ENABLING) != SCX_DISABLED);
- WARN_ON_ONCE(scx_root);
-
atomic_long_set(&scx_nr_rejected, 0);
for_each_possible_cpu(cpu) {
@@ -7078,6 +7651,9 @@ static void scx_root_enable_workfn(struct kthread_work *work)
rq->scx.cpuperf_target = SCX_CPUPERF_ONE;
}
+ scx_discard_stale_ecaps_syncs();
+ scx_rescue_set_knobs(sch);
+
/*
* Keep CPUs stable during enable so that the BPF scheduler can track
* online CPUs by watching ->on/offline_cpu() after ->init().
@@ -7085,10 +7661,9 @@ static void scx_root_enable_workfn(struct kthread_work *work)
cpus_read_lock();
/*
- * Build the cid mapping before publishing scx_root. The cid kfuncs
- * dereference the cid arrays unconditionally once scx_prog_sched()
- * returns non-NULL; the rcu_assign_pointer() below pairs with their
- * rcu_dereference() to make the populated arrays visible.
+ * Build the cid mapping into a private under-construction set. It
+ * becomes visible to readers only through scx_cid_publish_tables() once
+ * ops.init_cids() has finalized the layout.
*/
ret = scx_cid_init(sch);
if (ret) {
@@ -7110,24 +7685,57 @@ static void scx_root_enable_workfn(struct kthread_work *work)
scx_idle_enable(ops);
- if (sch->ops.init) {
- ret = SCX_CALL_OP_RET(sch, init, NULL);
+ /*
+ * A cid-form scheduler finalizes its cid layout in ops.init_cids(),
+ * which may call scx_bpf_cid_override(). Run it before the caps and
+ * shard state are built so the final layout is in effect.
+ */
+ if (sch->is_cid_type && sch->ops_cid.init_cids) {
+ ret = SCX_CALL_OP_RET(sch, init_cids, NULL);
if (ret) {
- ret = ops_sanitize_err(sch, "init", ret);
+ ret = scx_ops_sanitize_err(sch, "init_cids", ret);
cpus_read_unlock();
- scx_error(sch, "ops.init() failed (%d)", ret);
+ scx_error(sch, "ops.init_cids() failed (%d)", ret);
goto err_disable;
}
- sch->exit_info->flags |= SCX_EFLAG_INITIALIZED;
}
+ /* the cid layout is final, expose it to readers */
+ scx_cid_publish_tables();
+
ret = scx_arena_pool_init(sch);
if (ret) {
cpus_read_unlock();
goto err_disable;
}
- ret = scx_set_cmask_scratch_alloc(sch);
+ ret = scx_alloc_kern_arena_objs(sch);
+ if (ret) {
+ cpus_read_unlock();
+ goto err_disable;
+ }
+
+ ret = scx_alloc_pshards(sch);
+ if (ret) {
+ cpus_read_unlock();
+ goto err_disable;
+ }
+
+ scx_init_root_caps(sch);
+
+ /* the cid caps and shards are live now, so ops.init() can query them */
+ if (sch->ops.init) {
+ ret = SCX_CALL_OP_RET(sch, init, NULL);
+ if (ret) {
+ ret = scx_ops_sanitize_err(sch, "init", ret);
+ cpus_read_unlock();
+ scx_error(sch, "ops.init() failed (%d)", ret);
+ goto err_disable;
+ }
+ sch->exit_info->flags |= SCX_EFLAG_INITIALIZED;
+ }
+
+ ret = scx_sched_sysfs_add(sch);
if (ret) {
cpus_read_unlock();
goto err_disable;
@@ -7146,7 +7754,7 @@ static void scx_root_enable_workfn(struct kthread_work *work)
cpus_read_unlock();
- ret = validate_ops(sch, ops);
+ ret = scx_validate_ops(sch, ops);
if (ret)
goto err_disable;
@@ -7206,7 +7814,7 @@ static void scx_root_enable_workfn(struct kthread_work *work)
/*
* Enable ops for every task. Fork is excluded by scx_fork_rwsem
* preventing new tasks from being added. No need to exclude tasks
- * leaving as sched_ext_free() can handle both prepped and enabled
+ * leaving as sched_ext_dead() can handle both prepped and enabled
* tasks. Prep all tasks first and then enable them with preemption
* disabled.
*
@@ -7222,6 +7830,9 @@ static void scx_root_enable_workfn(struct kthread_work *work)
if (ret)
goto err_disable_unlock_all;
+ WARN_ON_ONCE(scx_cgroup_enabled);
+ scx_cgroup_enabled = true;
+
scx_task_iter_start(&sti, NULL);
while ((p = scx_task_iter_next_locked(&sti))) {
/*
@@ -7245,7 +7856,7 @@ static void scx_root_enable_workfn(struct kthread_work *work)
scx_set_task_state(p, SCX_TASK_INIT_BEGIN);
scx_task_iter_unlock(&sti);
- ret = __scx_init_task(sch, p, false);
+ ret = __scx_init_task(sch, p, NULL, false);
scx_task_iter_relock(&sti, p);
@@ -7295,7 +7906,7 @@ static void scx_root_enable_workfn(struct kthread_work *work)
/*
* We're fully committed and can't fail. The task READY -> ENABLED
- * transitions here are synchronized against sched_ext_free() through
+ * transitions here are synchronized against sched_ext_dead() through
* scx_tasks_lock.
*/
percpu_down_write(&scx_fork_rwsem);
@@ -7312,7 +7923,7 @@ static void scx_root_enable_workfn(struct kthread_work *work)
queue_flags |= DEQUEUE_CLASS;
scoped_guard (sched_change, p, queue_flags) {
- p->scx.slice = READ_ONCE(sch->slice_dfl);
+ scx_set_task_slice(p, READ_ONCE(sch->slice_dfl));
p->sched_class = new_class;
}
}
@@ -7323,6 +7934,7 @@ static void scx_root_enable_workfn(struct kthread_work *work)
if (!scx_tryset_enable_state(SCX_ENABLED, SCX_ENABLING)) {
WARN_ON_ONCE(atomic_read(&sch->exit_kind) == SCX_EXIT_NONE);
+ ret = -EBUSY;
goto err_disable;
}
@@ -7388,347 +8000,6 @@ err_disable:
cmd->ret = 0;
}
-#ifdef CONFIG_EXT_SUB_SCHED
-/* verify that a scheduler can be attached to @cgrp and return the parent */
-static struct scx_sched *find_parent_sched(struct cgroup *cgrp)
-{
- struct scx_sched *parent = cgrp->scx_sched;
- struct scx_sched *pos;
-
- lockdep_assert_held(&scx_sched_lock);
-
- /* can't attach twice to the same cgroup */
- if (parent->cgrp == cgrp)
- return ERR_PTR(-EBUSY);
-
- /* does $parent allow sub-scheds? */
- if (!parent->ops.sub_attach)
- return ERR_PTR(-EOPNOTSUPP);
-
- /* can't insert between $parent and its exiting children */
- list_for_each_entry(pos, &parent->children, sibling)
- if (cgroup_is_descendant(pos->cgrp, cgrp))
- return ERR_PTR(-EBUSY);
-
- return parent;
-}
-
-static bool assert_task_ready_or_enabled(struct task_struct *p)
-{
- u32 state = scx_get_task_state(p);
-
- switch (state) {
- case SCX_TASK_READY:
- case SCX_TASK_ENABLED:
- return true;
- default:
- WARN_ONCE(true, "sched_ext: Invalid task state %d for %s[%d] during enabling sub sched",
- state, p->comm, p->pid);
- return false;
- }
-}
-
-static void scx_sub_enable_workfn(struct kthread_work *work)
-{
- struct scx_enable_cmd *cmd = container_of(work, struct scx_enable_cmd, work);
- struct sched_ext_ops *ops = cmd->ops;
- struct cgroup *cgrp;
- struct scx_sched *parent, *sch;
- struct scx_task_iter sti;
- struct task_struct *p;
- s32 i, ret;
-
- mutex_lock(&scx_enable_mutex);
-
- if (!scx_enabled()) {
- ret = -ENODEV;
- goto out_unlock;
- }
-
- /* See scx_root_enable_workfn() for the @ops->priv check. */
- if (rcu_access_pointer(ops->priv)) {
- ret = -EBUSY;
- goto out_unlock;
- }
-
- cgrp = cgroup_get_from_id(ops->sub_cgroup_id);
- if (IS_ERR(cgrp)) {
- ret = PTR_ERR(cgrp);
- goto out_unlock;
- }
-
- raw_spin_lock_irq(&scx_sched_lock);
- parent = find_parent_sched(cgrp);
- if (IS_ERR(parent)) {
- raw_spin_unlock_irq(&scx_sched_lock);
- ret = PTR_ERR(parent);
- goto out_put_cgrp;
- }
- kobject_get(&parent->kobj);
- raw_spin_unlock_irq(&scx_sched_lock);
-
- /* scx_alloc_and_add_sched() consumes @cgrp whether it succeeds or not */
- sch = scx_alloc_and_add_sched(cmd, cgrp, parent);
- kobject_put(&parent->kobj);
- if (IS_ERR(sch)) {
- ret = PTR_ERR(sch);
- goto out_unlock;
- }
-
- ret = scx_link_sched(sch);
- if (ret)
- goto err_disable;
-
- if (sch->level >= SCX_SUB_MAX_DEPTH) {
- scx_error(sch, "max nesting depth %d violated",
- SCX_SUB_MAX_DEPTH);
- goto err_disable;
- }
-
- if (sch->ops.init) {
- ret = SCX_CALL_OP_RET(sch, init, NULL);
- if (ret) {
- ret = ops_sanitize_err(sch, "init", ret);
- scx_error(sch, "ops.init() failed (%d)", ret);
- goto err_disable;
- }
- sch->exit_info->flags |= SCX_EFLAG_INITIALIZED;
- }
-
- ret = scx_arena_pool_init(sch);
- if (ret)
- goto err_disable;
-
- ret = scx_set_cmask_scratch_alloc(sch);
- if (ret)
- goto err_disable;
-
- if (validate_ops(sch, ops))
- goto err_disable;
-
- struct scx_sub_attach_args sub_attach_args = {
- .ops = &sch->ops,
- .cgroup_path = sch->cgrp_path,
- };
-
- ret = SCX_CALL_OP_RET(parent, sub_attach, NULL,
- &sub_attach_args);
- if (ret) {
- ret = ops_sanitize_err(sch, "sub_attach", ret);
- scx_error(sch, "parent rejected (%d)", ret);
- goto err_disable;
- }
- sch->sub_attached = true;
-
- scx_bypass(sch, true);
-
- for (i = SCX_OPI_BEGIN; i < SCX_OPI_END; i++)
- if (((void (**)(void))ops)[i])
- set_bit(i, sch->has_op);
-
- percpu_down_write(&scx_fork_rwsem);
- scx_cgroup_lock();
-
- /*
- * Set cgroup->scx_sched's and check CSS_ONLINE. Either we see
- * !CSS_ONLINE or scx_cgroup_lifetime_notify() sees and shoots us down.
- */
- set_cgroup_sched(sch_cgroup(sch), sch);
- if (!(cgrp->self.flags & CSS_ONLINE)) {
- scx_error(sch, "cgroup is not online");
- goto err_unlock_and_disable;
- }
-
- /*
- * Initialize tasks for the new child $sch without exiting them for
- * $parent so that the tasks can always be reverted back to $parent
- * sched on child init failure.
- */
- WARN_ON_ONCE(scx_enabling_sub_sched);
- scx_enabling_sub_sched = sch;
-
- scx_task_iter_start(&sti, sch->cgrp);
- while ((p = scx_task_iter_next_locked(&sti))) {
- struct rq *rq;
- struct rq_flags rf;
-
- /*
- * Task iteration may visit the same task twice when racing
- * against exiting. Use %SCX_TASK_SUB_INIT to mark tasks which
- * finished __scx_init_task() and skip if set.
- *
- * A task may exit and get freed between __scx_init_task()
- * completion and scx_enable_task(). In such cases,
- * scx_disable_and_exit_task() must exit the task for both the
- * parent and child scheds.
- */
- if (p->scx.flags & SCX_TASK_SUB_INIT)
- continue;
-
- /* @p is pinned by the iter; see scx_sub_disable() */
- get_task_struct(p);
-
- if (!assert_task_ready_or_enabled(p)) {
- ret = -EINVAL;
- goto abort;
- }
-
- scx_task_iter_unlock(&sti);
-
- /*
- * As $p is still on $parent, it can't be transitioned to INIT.
- * Let's worry about task state later. Use __scx_init_task().
- */
- ret = __scx_init_task(sch, p, false);
- if (ret)
- goto abort;
-
- rq = task_rq_lock(p, &rf);
-
- if (scx_get_task_state(p) == SCX_TASK_DEAD) {
- /*
- * sched_ext_dead() raced us between __scx_init_task()
- * and this rq lock and ran exit_task() on $parent (the
- * sched @p was on at that point), not on @sch. @sch's
- * just-completed init is owed an exit_task() and we
- * issue it here.
- */
- scx_sub_init_cancel_task(sch, p);
- task_rq_unlock(rq, p, &rf);
- put_task_struct(p);
- continue;
- }
-
- p->scx.flags |= SCX_TASK_SUB_INIT;
- task_rq_unlock(rq, p, &rf);
-
- put_task_struct(p);
- }
- scx_task_iter_stop(&sti);
-
- /*
- * All tasks are prepped. Disable/exit tasks for $parent and enable for
- * the new @sch.
- */
- scx_task_iter_start(&sti, sch->cgrp);
- while ((p = scx_task_iter_next_locked(&sti))) {
- /*
- * Use clearing of %SCX_TASK_SUB_INIT to detect and skip
- * duplicate iterations.
- */
- if (!(p->scx.flags & SCX_TASK_SUB_INIT))
- continue;
-
- scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
- /*
- * $p must be either READY or ENABLED. If ENABLED,
- * __scx_disabled_and_exit_task() first disables and
- * makes it READY. However, after exiting $p, it will
- * leave $p as READY.
- */
- assert_task_ready_or_enabled(p);
- __scx_disable_and_exit_task(parent, p);
-
- /*
- * $p is now only initialized for @sch and READY, which
- * is what we want. Assign it to @sch and enable.
- */
- scx_set_task_sched(p, sch);
- scx_enable_task(sch, p);
-
- p->scx.flags &= ~SCX_TASK_SUB_INIT;
- }
- }
- scx_task_iter_stop(&sti);
-
- scx_enabling_sub_sched = NULL;
-
- scx_cgroup_unlock();
- percpu_up_write(&scx_fork_rwsem);
-
- scx_bypass(sch, false);
-
- pr_info("sched_ext: BPF sub-scheduler \"%s\" enabled\n", sch->ops.name);
- kobject_uevent(&sch->kobj, KOBJ_ADD);
- ret = 0;
- goto out_unlock;
-
-out_put_cgrp:
- cgroup_put(cgrp);
-out_unlock:
- mutex_unlock(&scx_enable_mutex);
- cmd->ret = ret;
- return;
-
-abort:
- put_task_struct(p);
- scx_task_iter_stop(&sti);
-
- /*
- * Undo __scx_init_task() for tasks we marked. scx_enable_task() never
- * ran for @sch on them, so calling scx_disable_task() here would invoke
- * ops.disable() without a matching ops.enable(). scx_enabling_sub_sched
- * must stay set until SUB_INIT is cleared from every marked task -
- * scx_disable_and_exit_task() reads it when a task exits concurrently.
- */
- scx_task_iter_start(&sti, sch->cgrp);
- while ((p = scx_task_iter_next_locked(&sti))) {
- if (p->scx.flags & SCX_TASK_SUB_INIT) {
- scx_sub_init_cancel_task(sch, p);
- p->scx.flags &= ~SCX_TASK_SUB_INIT;
- }
- }
- scx_task_iter_stop(&sti);
- scx_enabling_sub_sched = NULL;
-err_unlock_and_disable:
- /* we'll soon enter disable path, keep bypass on */
- scx_cgroup_unlock();
- percpu_up_write(&scx_fork_rwsem);
-err_disable:
- mutex_unlock(&scx_enable_mutex);
- scx_flush_disable_work(sch);
- cmd->ret = 0;
-}
-
-static s32 scx_cgroup_lifetime_notify(struct notifier_block *nb,
- unsigned long action, void *data)
-{
- struct cgroup *cgrp = data;
- struct cgroup *parent = cgroup_parent(cgrp);
-
- if (!cgroup_on_dfl(cgrp))
- return NOTIFY_OK;
-
- switch (action) {
- case CGROUP_LIFETIME_ONLINE:
- /* inherit ->scx_sched from $parent */
- if (parent)
- rcu_assign_pointer(cgrp->scx_sched, parent->scx_sched);
- break;
- case CGROUP_LIFETIME_OFFLINE:
- /* if there is a sched attached, shoot it down */
- if (cgrp->scx_sched && cgrp->scx_sched->cgrp == cgrp)
- scx_exit(cgrp->scx_sched, SCX_EXIT_UNREG_KERN,
- SCX_ECODE_RSN_CGROUP_OFFLINE,
- "cgroup %llu going offline", cgroup_id(cgrp));
- break;
- }
-
- return NOTIFY_OK;
-}
-
-static struct notifier_block scx_cgroup_lifetime_nb = {
- .notifier_call = scx_cgroup_lifetime_notify,
-};
-
-static s32 __init scx_cgroup_lifetime_notifier_init(void)
-{
- return blocking_notifier_chain_register(&cgroup_lifetime_notifier,
- &scx_cgroup_lifetime_nb);
-}
-core_initcall(scx_cgroup_lifetime_notifier_init);
-#endif /* CONFIG_EXT_SUB_SCHED */
-
static s32 scx_enable(struct scx_enable_cmd *cmd, struct bpf_link *link)
{
static struct kthread_worker *helper;
@@ -7791,6 +8062,21 @@ static bool bpf_scx_is_valid_access(int off, int size,
return btf_ctx_access(off, size, type, prog, info);
}
+/* common to both forms: only scx.disallow is writable */
+static int bpf_scx_btf_struct_access_common(const struct bpf_reg_state *reg,
+ int off, int size)
+{
+ const struct btf_type *t;
+
+ t = btf_type_by_id(reg->btf, reg->btf_id);
+ if (t == task_struct_type &&
+ off >= offsetof(struct task_struct, scx.disallow) &&
+ off + size <= offsetofend(struct task_struct, scx.disallow))
+ return SCALAR_VALUE;
+
+ return -EACCES;
+}
+
static int bpf_scx_btf_struct_access(struct bpf_verifier_log *log,
const struct bpf_reg_state *reg, int off,
int size)
@@ -7799,23 +8085,22 @@ static int bpf_scx_btf_struct_access(struct bpf_verifier_log *log,
t = btf_type_by_id(reg->btf, reg->btf_id);
if (t == task_struct_type) {
- /*
- * COMPAT: Will be removed in v6.23.
- */
if ((off >= offsetof(struct task_struct, scx.slice) &&
off + size <= offsetofend(struct task_struct, scx.slice)) ||
(off >= offsetof(struct task_struct, scx.dsq_vtime) &&
- off + size <= offsetofend(struct task_struct, scx.dsq_vtime))) {
- pr_warn("sched_ext: Writing directly to p->scx.slice/dsq_vtime is deprecated, use scx_bpf_task_set_slice/dsq_vtime()");
- return SCALAR_VALUE;
- }
-
- if (off >= offsetof(struct task_struct, scx.disallow) &&
- off + size <= offsetofend(struct task_struct, scx.disallow))
+ off + size <= offsetofend(struct task_struct, scx.dsq_vtime)))
return SCALAR_VALUE;
}
- return -EACCES;
+ return bpf_scx_btf_struct_access_common(reg, off, size);
+}
+
+/* cid-form rejects direct slice and dsq_vtime writes in favor of the kfuncs */
+static int bpf_scx_cid_btf_struct_access(struct bpf_verifier_log *log,
+ const struct bpf_reg_state *reg, int off,
+ int size)
+{
+ return bpf_scx_btf_struct_access_common(reg, off, size);
}
static const struct bpf_verifier_ops bpf_scx_verifier_ops = {
@@ -7824,6 +8109,12 @@ static const struct bpf_verifier_ops bpf_scx_verifier_ops = {
.btf_struct_access = bpf_scx_btf_struct_access,
};
+static const struct bpf_verifier_ops bpf_scx_cid_verifier_ops = {
+ .get_func_proto = bpf_base_func_proto,
+ .is_valid_access = bpf_scx_is_valid_access,
+ .btf_struct_access = bpf_scx_cid_btf_struct_access,
+};
+
static int bpf_scx_init_member(const struct btf_type *t,
const struct btf_member *member,
void *kdata, const void *udata)
@@ -7865,6 +8156,27 @@ static int bpf_scx_init_member(const struct btf_type *t,
case offsetof(struct sched_ext_ops, hotplug_seq):
ops->hotplug_seq = *(u64 *)(udata + moff);
return 1;
+ case offsetof(struct sched_ext_ops, cid_shard_size):
+ ops->cid_shard_size = *(u32 *)(udata + moff);
+ return 1;
+ case offsetof(struct sched_ext_ops, rescue_bandwidth_ppt): {
+ u32 bw_ppt = *(u32 *)(udata + moff);
+
+ if (bw_ppt > SCX_RESCUE_MAX_BW_PPT && bw_ppt != SCX_RESCUE_DISABLE)
+ return -E2BIG;
+ ops->rescue_bandwidth_ppt = bw_ppt;
+ return 1;
+ }
+ case offsetof(struct sched_ext_ops, rescue_quantum_us): {
+ u32 quantum_us = *(u32 *)(udata + moff);
+
+ if (quantum_us > SCX_RESCUE_MAX_QUANTUM_US)
+ return -E2BIG;
+ if (quantum_us && quantum_us < SCX_RESCUE_MIN_QUANTUM_US)
+ return -EINVAL;
+ ops->rescue_quantum_us = quantum_us;
+ return 1;
+ }
#ifdef CONFIG_EXT_SUB_SCHED
case offsetof(struct sched_ext_ops, sub_cgroup_id):
ops->sub_cgroup_id = *(u64 *)(udata + moff);
@@ -7875,20 +8187,6 @@ static int bpf_scx_init_member(const struct btf_type *t,
return 0;
}
-#ifdef CONFIG_EXT_SUB_SCHED
-static void scx_pstack_recursion_on_dispatch(struct bpf_prog *prog)
-{
- struct scx_sched *sch;
-
- guard(rcu)();
- sch = scx_prog_sched(prog->aux);
- if (unlikely(!sch))
- return;
-
- scx_error(sch, "dispatch recursion detected");
-}
-#endif /* CONFIG_EXT_SUB_SCHED */
-
static int bpf_scx_check_member(const struct btf_type *t,
const struct btf_member *member,
const struct bpf_prog *prog)
@@ -7901,9 +8199,11 @@ static int bpf_scx_check_member(const struct btf_type *t,
case offsetof(struct sched_ext_ops, cgroup_init):
case offsetof(struct sched_ext_ops, cgroup_exit):
case offsetof(struct sched_ext_ops, cgroup_prep_move):
+ case offsetof(struct sched_ext_ops, cgroup_set_bandwidth):
#endif
case offsetof(struct sched_ext_ops, cpu_online):
case offsetof(struct sched_ext_ops, cpu_offline):
+ case offsetof(struct sched_ext_ops, init_cids):
case offsetof(struct sched_ext_ops, init):
case offsetof(struct sched_ext_ops, exit):
case offsetof(struct sched_ext_ops, sub_attach):
@@ -7927,6 +8227,11 @@ static int bpf_scx_check_member(const struct btf_type *t,
case offsetof(struct sched_ext_ops, dispatch):
prog->aux->priv_stack_requested = true;
prog->aux->recursion_detected = scx_pstack_recursion_on_dispatch;
+ break;
+ case offsetof(struct sched_ext_ops, sub_caps_updated):
+ prog->aux->priv_stack_requested = true;
+ prog->aux->recursion_detected = scx_pstack_recursion_on_caps_updated;
+ break;
}
#endif /* CONFIG_EXT_SUB_SCHED */
@@ -8062,6 +8367,7 @@ static s32 sched_ext_ops__sub_attach(struct scx_sub_attach_args *args) { return
static void sched_ext_ops__sub_detach(struct scx_sub_detach_args *args) {}
static void sched_ext_ops__cpu_online(s32 cpu) {}
static void sched_ext_ops__cpu_offline(s32 cpu) {}
+static s32 sched_ext_ops__init_cids(void) { return -EINVAL; }
static s32 sched_ext_ops__init(void) { return -EINVAL; }
static void sched_ext_ops__exit(struct scx_exit_info *info) {}
static void sched_ext_ops__dump(struct scx_dump_ctx *ctx) {}
@@ -8103,6 +8409,7 @@ static struct sched_ext_ops __bpf_ops_sched_ext_ops = {
.sub_detach = sched_ext_ops__sub_detach,
.cpu_online = sched_ext_ops__cpu_online,
.cpu_offline = sched_ext_ops__cpu_offline,
+ .init_cids = sched_ext_ops__init_cids,
.init = sched_ext_ops__init,
.exit = sched_ext_ops__exit,
.dump = sched_ext_ops__dump,
@@ -8126,11 +8433,14 @@ static struct bpf_struct_ops bpf_sched_ext_ops = {
/*
* cid-form cfi stubs. Stubs whose signatures match the cpu-form (param types
- * identical, only param names differ across structs) are reused; only
- * set_cmask needs a fresh stub since the second argument type differs.
+ * identical, only param names differ across structs) are reused. Some need
+ * fresh stubs, set_cmask and enable due to argument differences and the
+ * sub-sched notifiers because no cpu-form stub exists to reuse.
*/
-static void sched_ext_ops_cid__set_cmask(struct task_struct *p,
- const struct scx_cmask *cmask) {}
+static void sched_ext_ops_cid__set_cmask(struct task_struct *p, const struct scx_cmask *cmask__arena) {}
+static void sched_ext_ops_cid__enable(struct task_struct *p, struct scx_enable_args *args) {}
+static void sched_ext_ops__sub_caps_updated(const struct scx_cmask *cmask__arena, u64 caps) {}
+static void sched_ext_ops__sub_ecaps_updated(s32 cid, u64 before, u64 after) {}
static struct sched_ext_ops_cid __bpf_ops_sched_ext_ops_cid = {
.select_cid = sched_ext_ops__select_cpu,
@@ -8149,22 +8459,25 @@ static struct sched_ext_ops_cid __bpf_ops_sched_ext_ops_cid = {
.update_idle = sched_ext_ops__update_idle,
.init_task = sched_ext_ops__init_task,
.exit_task = sched_ext_ops__exit_task,
- .enable = sched_ext_ops__enable,
+ .enable = sched_ext_ops_cid__enable,
.disable = sched_ext_ops__disable,
#ifdef CONFIG_EXT_GROUP_SCHED
- .cgroup_init = sched_ext_ops__cgroup_init,
- .cgroup_exit = sched_ext_ops__cgroup_exit,
- .cgroup_prep_move = sched_ext_ops__cgroup_prep_move,
- .cgroup_move = sched_ext_ops__cgroup_move,
- .cgroup_cancel_move = sched_ext_ops__cgroup_cancel_move,
- .cgroup_set_weight = sched_ext_ops__cgroup_set_weight,
- .cgroup_set_bandwidth = sched_ext_ops__cgroup_set_bandwidth,
- .cgroup_set_idle = sched_ext_ops__cgroup_set_idle,
+ .cpuctl_init = sched_ext_ops__cgroup_init,
+ .cpuctl_exit = sched_ext_ops__cgroup_exit,
+ .cpuctl_prep_move = sched_ext_ops__cgroup_prep_move,
+ .cpuctl_move = sched_ext_ops__cgroup_move,
+ .cpuctl_cancel_move = sched_ext_ops__cgroup_cancel_move,
+ .cpuctl_set_weight = sched_ext_ops__cgroup_set_weight,
+ .cpuctl_set_bandwidth = sched_ext_ops__cgroup_set_bandwidth,
+ .cpuctl_set_idle = sched_ext_ops__cgroup_set_idle,
#endif
.sub_attach = sched_ext_ops__sub_attach,
.sub_detach = sched_ext_ops__sub_detach,
+ .sub_caps_updated = sched_ext_ops__sub_caps_updated,
+ .sub_ecaps_updated = sched_ext_ops__sub_ecaps_updated,
.cid_online = sched_ext_ops__cpu_online,
.cid_offline = sched_ext_ops__cpu_offline,
+ .init_cids = sched_ext_ops__init_cids,
.init = sched_ext_ops__init,
.exit = sched_ext_ops__exit,
.dump = sched_ext_ops__dump,
@@ -8178,7 +8491,7 @@ static struct sched_ext_ops_cid __bpf_ops_sched_ext_ops_cid = {
* verified to match by the BUILD_BUG_ON checks in scx_init().
*/
static struct bpf_struct_ops bpf_sched_ext_ops_cid = {
- .verifier_ops = &bpf_scx_verifier_ops,
+ .verifier_ops = &bpf_scx_cid_verifier_ops,
.reg = bpf_scx_reg_cid,
.unreg = bpf_scx_unreg,
.check_member = bpf_scx_check_member,
@@ -8242,21 +8555,23 @@ static bool can_skip_idle_kick(struct rq *rq)
* We can skip idle kicking if @rq is going to go through at least one
* full SCX scheduling cycle before going idle. Just checking whether
* curr is not idle is insufficient because we could be racing
- * balance_one() trying to pull the next task from a remote rq, which
+ * dispatch_one() trying to pull the next task from a remote rq, which
* may fail, and @rq may become idle afterwards.
*
* The race window is small and we don't and can't guarantee that @rq is
* only kicked while idle anyway. Skip only when sure.
*/
- return !is_idle_task(rq->curr) && !(rq->scx.flags & SCX_RQ_IN_BALANCE);
+ return !is_idle_task(rq->curr) && !(rq->scx.flags & SCX_RQ_IN_DISPATCH);
}
-static bool kick_one_cpu(s32 cpu, struct rq *this_rq, unsigned long *ksyncs)
+static bool kick_one_cpu(s32 cpu, struct scx_sched_pcpu *pcpu, struct rq *this_rq,
+ unsigned long *ksyncs)
{
struct rq *rq = cpu_rq(cpu);
struct scx_rq *this_scx = &this_rq->scx;
const struct sched_class *cur_class;
bool should_wait = false;
+ bool kickable;
unsigned long flags;
raw_spin_rq_lock_irqsave(rq, flags);
@@ -8266,47 +8581,68 @@ static bool kick_one_cpu(s32 cpu, struct rq *this_rq, unsigned long *ksyncs)
* During CPU hotplug, a CPU may depend on kicking itself to make
* forward progress. Allow kicking self regardless of online state. If
* @cpu is running a higher class task, we have no control over @cpu.
- * Skip kicking.
- */
- if ((cpu_online(cpu) || cpu == cpu_of(this_rq)) &&
- !sched_class_above(cur_class, &ext_sched_class)) {
- if (cpumask_test_cpu(cpu, this_scx->cpus_to_preempt)) {
- if (cur_class == &ext_sched_class)
- rq->curr->scx.slice = 0;
- cpumask_clear_cpu(cpu, this_scx->cpus_to_preempt);
+ * Skip kicking. A sub-sched lacking baseline access on @cid has no
+ * business forcing a reschedule there - skip. This is the authoritative
+ * cap check: ecaps is read here under @rq's lock.
+ */
+ kickable = (cpu_online(cpu) || cpu == cpu_of(this_rq)) &&
+ !sched_class_above(cur_class, &ext_sched_class);
+
+ if (kickable && !scx_missing_caps(pcpu->sch, cpu, SCX_CAP_BASE)) {
+ if (cpumask_test_cpu(cpu, pcpu->cpus_to_preempt)) {
+ if (cur_class == &ext_sched_class) {
+ u64 caps = scx_caps_for_preempt(pcpu->sch, rq, 0);
+
+ if (unlikely(scx_missing_caps(pcpu->sch, cpu, caps)))
+ __scx_add_event(pcpu->sch, SCX_EV_SUB_PREEMPT_DENIED, 1);
+ else if (unlikely(!scx_set_task_slice(rq->curr, 0)))
+ __scx_add_event(pcpu->sch, SCX_EV_SLICE_DENIED, 1);
+ }
+ cpumask_clear_cpu(cpu, pcpu->cpus_to_preempt);
}
- if (cpumask_test_cpu(cpu, this_scx->cpus_to_wait)) {
+ if (cpumask_test_cpu(cpu, pcpu->cpus_to_wait)) {
if (cur_class == &ext_sched_class) {
cpumask_set_cpu(cpu, this_scx->cpus_to_sync);
ksyncs[cpu] = rq->scx.kick_sync;
should_wait = true;
}
- cpumask_clear_cpu(cpu, this_scx->cpus_to_wait);
+ cpumask_clear_cpu(cpu, pcpu->cpus_to_wait);
}
resched_curr(rq);
} else {
- cpumask_clear_cpu(cpu, this_scx->cpus_to_preempt);
- cpumask_clear_cpu(cpu, this_scx->cpus_to_wait);
+ /* a kickable cpu was skipped solely for the missing caps */
+ if (kickable)
+ __scx_add_event(pcpu->sch, SCX_EV_SUB_KICK_DENIED, 1);
+ cpumask_clear_cpu(cpu, pcpu->cpus_to_preempt);
+ cpumask_clear_cpu(cpu, pcpu->cpus_to_wait);
}
+ scx_rq_lock_drop(rq);
raw_spin_rq_unlock_irqrestore(rq, flags);
return should_wait;
}
-static void kick_one_cpu_if_idle(s32 cpu, struct rq *this_rq)
+static void kick_one_cpu_if_idle(s32 cpu, struct scx_sched_pcpu *pcpu,
+ struct rq *this_rq)
{
struct rq *rq = cpu_rq(cpu);
unsigned long flags;
raw_spin_rq_lock_irqsave(rq, flags);
+ /* idle kicks need baseline access too, see kick_one_cpu() */
if (!can_skip_idle_kick(rq) &&
- (cpu_online(cpu) || cpu == cpu_of(this_rq)))
- resched_curr(rq);
+ (cpu_online(cpu) || cpu == cpu_of(this_rq))) {
+ if (likely(!scx_missing_caps(pcpu->sch, cpu, SCX_CAP_BASE)))
+ resched_curr(rq);
+ else
+ __scx_add_event(pcpu->sch, SCX_EV_SUB_KICK_DENIED, 1);
+ }
+ scx_rq_lock_drop(rq);
raw_spin_rq_unlock_irqrestore(rq, flags);
}
@@ -8315,6 +8651,7 @@ static void kick_cpus_irq_workfn(struct irq_work *irq_work)
struct rq *this_rq = this_rq();
struct scx_rq *this_scx = &this_rq->scx;
struct scx_kick_syncs __rcu *ksyncs_pcpu = __this_cpu_read(scx_kick_syncs);
+ struct scx_sched_pcpu *pcpu, *tmp;
bool should_wait = false;
unsigned long *ksyncs;
s32 cpu;
@@ -8325,15 +8662,24 @@ static void kick_cpus_irq_workfn(struct irq_work *irq_work)
ksyncs = rcu_dereference_bh(ksyncs_pcpu)->syncs;
- for_each_cpu(cpu, this_scx->cpus_to_kick) {
- should_wait |= kick_one_cpu(cpu, this_rq, ksyncs);
- cpumask_clear_cpu(cpu, this_scx->cpus_to_kick);
- cpumask_clear_cpu(cpu, this_scx->cpus_to_kick_if_idle);
- }
+ /*
+ * Walk scheds with pending kicks on this cpu. scx_kick_cpu() adds to
+ * the list under local_irq_save() and only this irq_work consumes it.
+ * A plain list without locking is sufficient.
+ */
+ list_for_each_entry_safe(pcpu, tmp, &this_scx->sched_pcpus_to_kick, to_kick_node) {
+ list_del_init(&pcpu->to_kick_node);
+
+ for_each_cpu(cpu, pcpu->cpus_to_kick) {
+ should_wait |= kick_one_cpu(cpu, pcpu, this_rq, ksyncs);
+ cpumask_clear_cpu(cpu, pcpu->cpus_to_kick);
+ cpumask_clear_cpu(cpu, pcpu->cpus_to_kick_if_idle);
+ }
- for_each_cpu(cpu, this_scx->cpus_to_kick_if_idle) {
- kick_one_cpu_if_idle(cpu, this_rq);
- cpumask_clear_cpu(cpu, this_scx->cpus_to_kick_if_idle);
+ for_each_cpu(cpu, pcpu->cpus_to_kick_if_idle) {
+ kick_one_cpu_if_idle(cpu, pcpu, this_rq);
+ cpumask_clear_cpu(cpu, pcpu->cpus_to_kick_if_idle);
+ }
}
/*
@@ -8344,6 +8690,7 @@ static void kick_cpus_irq_workfn(struct irq_work *irq_work)
raw_spin_rq_lock(this_rq);
this_scx->kick_sync_pending = true;
resched_curr(this_rq);
+ scx_rq_lock_drop(this_rq);
raw_spin_rq_unlock(this_rq);
}
}
@@ -8453,16 +8800,17 @@ void __init init_sched_ext_class(void)
int n = cpu_to_node(cpu);
/* local_dsq's sch will be set during scx_root_enable() */
- BUG_ON(init_dsq(&rq->scx.local_dsq, SCX_DSQ_LOCAL, NULL));
+ BUG_ON(scx_init_dsq(&rq->scx.local_dsq, SCX_DSQ_LOCAL, NULL));
+#ifdef CONFIG_EXT_SUB_SCHED
+ BUG_ON(scx_init_dsq(&rq->scx.reject_dsq, SCX_DSQ_REJECT, NULL));
+ scx_rescue_init(rq);
+#endif
INIT_LIST_HEAD(&rq->scx.runnable_list);
INIT_LIST_HEAD(&rq->scx.ddsp_deferred_locals);
- BUG_ON(!zalloc_cpumask_var_node(&rq->scx.cpus_to_kick, GFP_KERNEL, n));
- BUG_ON(!zalloc_cpumask_var_node(&rq->scx.cpus_to_kick_if_idle, GFP_KERNEL, n));
- BUG_ON(!zalloc_cpumask_var_node(&rq->scx.cpus_to_preempt, GFP_KERNEL, n));
- BUG_ON(!zalloc_cpumask_var_node(&rq->scx.cpus_to_wait, GFP_KERNEL, n));
BUG_ON(!zalloc_cpumask_var_node(&rq->scx.cpus_to_sync, GFP_KERNEL, n));
+ INIT_LIST_HEAD(&rq->scx.sched_pcpus_to_kick);
raw_spin_lock_init(&rq->scx.deferred_reenq_lock);
INIT_LIST_HEAD(&rq->scx.deferred_reenq_locals);
INIT_LIST_HEAD(&rq->scx.deferred_reenq_users);
@@ -8491,6 +8839,11 @@ static bool scx_vet_enq_flags(struct scx_sched *sch, u64 dsq_id, u64 *enq_flags)
bool is_local = dsq_id == SCX_DSQ_LOCAL ||
(dsq_id & SCX_DSQ_LOCAL_ON) == SCX_DSQ_LOCAL_ON;
+ if (unlikely(*enq_flags & __SCX_ENQ_INTERNAL_MASK)) {
+ scx_error(sch, "invalid enq_flags 0x%llx", *enq_flags);
+ return false;
+ }
+
if (*enq_flags & SCX_ENQ_IMMED) {
if (unlikely(!is_local)) {
scx_error(sch, "SCX_ENQ_IMMED on a non-local DSQ 0x%llx", dsq_id);
@@ -8500,6 +8853,11 @@ static bool scx_vet_enq_flags(struct scx_sched *sch, u64 dsq_id, u64 *enq_flags)
*enq_flags |= SCX_ENQ_IMMED;
}
+ if (unlikely((*enq_flags & SCX_ENQ_RESCUE) && !is_local)) {
+ scx_error(sch, "SCX_ENQ_RESCUE on a non-local DSQ 0x%llx", dsq_id);
+ return false;
+ }
+
return true;
}
@@ -8513,11 +8871,6 @@ static bool scx_dsq_insert_preamble(struct scx_sched *sch, struct task_struct *p
return false;
}
- if (unlikely(*enq_flags & __SCX_ENQ_INTERNAL_MASK)) {
- scx_error(sch, "invalid enq_flags 0x%llx", *enq_flags);
- return false;
- }
-
/* see SCX_EV_INSERT_NOT_OWNED definition */
if (unlikely(!scx_task_on_sched(sch, p))) {
__scx_add_event(sch, SCX_EV_INSERT_NOT_OWNED, 1);
@@ -8531,14 +8884,14 @@ static bool scx_dsq_insert_preamble(struct scx_sched *sch, struct task_struct *p
}
static void scx_dsq_insert_commit(struct scx_sched *sch, struct task_struct *p,
- u64 dsq_id, u64 enq_flags)
+ u64 dsq_id, u64 slice, u64 vtime, u64 enq_flags)
{
struct scx_dsp_ctx *dspc = &this_cpu_ptr(sch->pcpu)->dsp_ctx;
struct task_struct *ddsp_task;
ddsp_task = __this_cpu_read(direct_dispatch_task);
if (ddsp_task) {
- mark_direct_dispatch(sch, ddsp_task, p, dsq_id, enq_flags);
+ mark_direct_dispatch(sch, ddsp_task, p, dsq_id, slice, vtime, enq_flags);
return;
}
@@ -8551,6 +8904,8 @@ static void scx_dsq_insert_commit(struct scx_sched *sch, struct task_struct *p,
.task = p,
.qseq = atomic_long_read(&p->scx.ops_state) & SCX_OPSS_QSEQ_MASK,
.dsq_id = dsq_id,
+ .slice = slice,
+ .vtime = vtime,
.enq_flags = enq_flags,
};
}
@@ -8558,7 +8913,7 @@ static void scx_dsq_insert_commit(struct scx_sched *sch, struct task_struct *p,
__bpf_kfunc_start_defs();
/**
- * scx_bpf_dsq_insert - Insert a task into the FIFO queue of a DSQ
+ * scx_bpf_dsq_insert___v2 - Insert a task into the FIFO queue of a DSQ
* @p: task_struct to insert
* @dsq_id: DSQ to insert into
* @slice: duration @p can run for in nsecs, 0 to keep the current value
@@ -8572,7 +8927,7 @@ __bpf_kfunc_start_defs();
* When called from ops.select_cpu() or ops.enqueue(), it's for direct dispatch
* and @p must match the task being enqueued.
*
- * When called from ops.select_cpu(), @enq_flags and @dsp_id are stored, and @p
+ * When called from ops.select_cpu(), @enq_flags and @dsq_id are stored, and @p
* will be directly inserted into the corresponding dispatch queue after
* ops.select_cpu() returns. If @p is inserted into SCX_DSQ_LOCAL, it will be
* inserted into the local DSQ of the CPU returned by ops.select_cpu().
@@ -8611,12 +8966,7 @@ __bpf_kfunc bool scx_bpf_dsq_insert___v2(struct task_struct *p, u64 dsq_id,
if (!scx_dsq_insert_preamble(sch, p, dsq_id, &enq_flags))
return false;
- if (slice)
- p->scx.slice = slice;
- else
- p->scx.slice = p->scx.slice ?: 1;
-
- scx_dsq_insert_commit(sch, p, dsq_id, enq_flags);
+ scx_dsq_insert_commit(sch, p, dsq_id, slice, 0, enq_flags);
return true;
}
@@ -8637,14 +8987,7 @@ static bool scx_dsq_insert_vtime(struct scx_sched *sch, struct task_struct *p,
if (!scx_dsq_insert_preamble(sch, p, dsq_id, &enq_flags))
return false;
- if (slice)
- p->scx.slice = slice;
- else
- p->scx.slice = p->scx.slice ?: 1;
-
- p->scx.dsq_vtime = vtime;
-
- scx_dsq_insert_commit(sch, p, dsq_id, enq_flags | SCX_ENQ_DSQ_PRIQ);
+ scx_dsq_insert_commit(sch, p, dsq_id, slice, vtime, enq_flags | SCX_ENQ_DSQ_PRIQ);
return true;
}
@@ -8722,10 +9065,17 @@ __bpf_kfunc void scx_bpf_dsq_insert_vtime(struct task_struct *p, u64 dsq_id,
#ifdef CONFIG_EXT_SUB_SCHED
/*
* Disallow if any sub-scheds are attached. There is no way to tell
- * which scheduler called us, just error out @p's scheduler.
+ * which scheduler called us, so error out @p's scheduler -- read it
+ * under RCU as @p's locks aren't necessarily held here. @p may be a
+ * task past sched_ext_dead() or an idle task, in which case its
+ * scheduler can't be determined and there is nothing obviously wrong
+ * to report; just refuse the call.
*/
if (unlikely(!list_empty(&sch->children))) {
- scx_error(scx_task_sched(p), "__scx_bpf_dsq_insert_vtime() must be used");
+ struct scx_sched *tsch = scx_task_sched_rcu(p);
+
+ if (tsch)
+ scx_error(tsch, "__scx_bpf_dsq_insert_vtime() must be used");
return;
}
#endif
@@ -8749,13 +9099,13 @@ static const struct btf_kfunc_id_set scx_kfunc_set_enqueue_dispatch = {
};
static bool scx_dsq_move(struct bpf_iter_scx_dsq_kern *kit,
- struct task_struct *p, u64 dsq_id, u64 enq_flags)
+ struct task_struct *p, u64 dsq_id, u64 enq_flags,
+ bool priq)
{
struct scx_dispatch_q *src_dsq = kit->dsq, *dst_dsq;
struct scx_sched *sch;
- struct rq *this_rq, *src_rq, *locked_rq;
+ struct rq *p_rq, *src_rq, *locked_rq;
bool dispatched = false;
- bool in_balance;
unsigned long flags;
/*
@@ -8771,40 +9121,40 @@ static bool scx_dsq_move(struct bpf_iter_scx_dsq_kern *kit,
if (!scx_vet_enq_flags(sch, dsq_id, &enq_flags))
return false;
+ /* internal bit, can only go in after @enq_flags is vetted */
+ if (priq)
+ enq_flags |= SCX_ENQ_DSQ_PRIQ;
+
/*
* If the BPF scheduler keeps calling this function repeatedly, it can
- * cause similar live-lock conditions as consume_dispatch_q().
+ * cause similar live-lock conditions as scx_consume_dispatch_q().
*/
if (unlikely(READ_ONCE(sch->aborting)))
return false;
- if (unlikely(!scx_task_on_sched(sch, p))) {
- scx_error(sch, "scx_bpf_dsq_move[_vtime]() on %s[%d] but the task belongs to a different scheduler",
- p->comm, p->pid);
- return false;
- }
-
/*
- * Can be called from either ops.dispatch() locking this_rq() or any
- * context where no rq lock is held. If latter, lock @p's task_rq which
- * we'll likely need anyway.
+ * Can be called from either ops.dispatch() holding the dispatched rq's
+ * lock or any context where no rq lock is held. If latter, lock @p's
+ * task_rq which we'll likely need anyway.
*/
src_rq = task_rq(p);
local_irq_save(flags);
- this_rq = this_rq();
- in_balance = this_rq->scx.flags & SCX_RQ_IN_BALANCE;
- if (in_balance) {
- if (this_rq != src_rq) {
- raw_spin_rq_unlock(this_rq);
- raw_spin_rq_lock(src_rq);
- }
+ /*
+ * Under core scheduling, dispatch can run for a sibling rq, so the
+ * locked rq is not necessarily this CPU's.
+ */
+ locked_rq = scx_locked_rq();
+
+ if (locked_rq) {
+ if (locked_rq != src_rq)
+ switch_rq_lock(locked_rq, src_rq);
} else {
raw_spin_rq_lock(src_rq);
}
- locked_rq = src_rq;
+ p_rq = src_rq;
raw_spin_lock(&src_dsq->lock);
/* did someone else get to it while we dropped the locks? */
@@ -8813,30 +9163,40 @@ static bool scx_dsq_move(struct bpf_iter_scx_dsq_kern *kit,
goto out;
}
+ /*
+ * @p has been on $src_dsq and can't move anymore. If @p is not on @sch,
+ * the caller didn't have authority over @p at the time of the call.
+ */
+ if (unlikely(!scx_task_on_sched(sch, p))) {
+ scx_error(sch, "scx_bpf_dsq_move[_vtime]() on %s[%d] but the task belongs to a different scheduler",
+ p->comm, p->pid);
+ raw_spin_unlock(&src_dsq->lock);
+ goto out;
+ }
+
/* @p is still on $src_dsq and stable, determine the destination */
- dst_dsq = find_dsq_for_dispatch(sch, this_rq, dsq_id, task_cpu(p));
+ dst_dsq = find_dsq_for_dispatch(sch, locked_rq ?: this_rq(), dsq_id, task_cpu(p));
/*
- * Apply vtime and slice updates before moving so that the new time is
- * visible before inserting into $dst_dsq. @p is still on $src_dsq but
- * this is safe as we're locking it.
+ * Apply vtime and slice updates before moving. @p is still on $src_dsq
+ * with both $src_dsq and its task_rq locked, satisfying the write
+ * rules, and the PRIQ insertion into $dst_dsq reads the new vtime.
*/
if (kit->cursor.flags & __SCX_DSQ_ITER_HAS_VTIME)
p->scx.dsq_vtime = kit->vtime;
if (kit->cursor.flags & __SCX_DSQ_ITER_HAS_SLICE)
- p->scx.slice = kit->slice;
+ scx_set_task_slice(p, kit->slice);
/* execute move */
- locked_rq = move_task_between_dsqs(sch, p, enq_flags, src_dsq, dst_dsq);
+ p_rq = move_task_between_dsqs(sch, p, enq_flags, src_dsq, dst_dsq);
dispatched = true;
out:
- if (in_balance) {
- if (this_rq != locked_rq) {
- raw_spin_rq_unlock(locked_rq);
- raw_spin_rq_lock(this_rq);
- }
+ if (locked_rq) {
+ if (locked_rq != p_rq)
+ switch_rq_lock(p_rq, locked_rq);
} else {
- raw_spin_rq_unlock_irqrestore(locked_rq, flags);
+ scx_rq_lock_drop(p_rq);
+ raw_spin_rq_unlock_irqrestore(p_rq, flags);
}
kit->cursor.flags &= ~(__SCX_DSQ_ITER_HAS_SLICE |
@@ -8892,7 +9252,7 @@ __bpf_kfunc void scx_bpf_dispatch_cancel(const struct bpf_prog_aux *aux)
}
/**
- * scx_bpf_dsq_move_to_local - move a task from a DSQ to the current CPU's local DSQ
+ * scx_bpf_dsq_move_to_local___v2 - move a task from a DSQ to the current CPU's local DSQ
* @dsq_id: DSQ to move task from. Must be a user-created DSQ
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
* @enq_flags: %SCX_ENQ_*
@@ -8934,7 +9294,7 @@ __bpf_kfunc bool scx_bpf_dsq_move_to_local___v2(u64 dsq_id, u64 enq_flags,
dspc = &this_cpu_ptr(sch->pcpu)->dsp_ctx;
- flush_dispatch_buf(sch, dspc->rq);
+ scx_flush_dispatch_buf(sch, dspc->rq);
dsq = find_user_dsq(sch, dsq_id);
if (unlikely(!dsq)) {
@@ -8942,11 +9302,11 @@ __bpf_kfunc bool scx_bpf_dsq_move_to_local___v2(u64 dsq_id, u64 enq_flags,
return false;
}
- if (consume_dispatch_q(sch, dspc->rq, dsq, enq_flags)) {
+ if (scx_consume_dispatch_q(sch, dspc->rq, dsq, enq_flags)) {
/*
* A successfully consumed task can be dequeued before it starts
* running while the CPU is trying to migrate other dispatched
- * tasks. Bump nr_tasks to tell balance_one() to retry on empty
+ * tasks. Bump nr_tasks to tell dispatch_one() to retry on empty
* local DSQ.
*/
dspc->nr_tasks++;
@@ -9031,7 +9391,7 @@ __bpf_kfunc bool scx_bpf_dsq_move(struct bpf_iter_scx_dsq *it__iter,
u64 enq_flags)
{
return scx_dsq_move((struct bpf_iter_scx_dsq_kern *)it__iter,
- p, dsq_id, enq_flags);
+ p, dsq_id, enq_flags, false);
}
/**
@@ -9056,47 +9416,8 @@ __bpf_kfunc bool scx_bpf_dsq_move_vtime(struct bpf_iter_scx_dsq *it__iter,
u64 enq_flags)
{
return scx_dsq_move((struct bpf_iter_scx_dsq_kern *)it__iter,
- p, dsq_id, enq_flags | SCX_ENQ_DSQ_PRIQ);
-}
-
-#ifdef CONFIG_EXT_SUB_SCHED
-/**
- * scx_bpf_sub_dispatch - Trigger dispatching on a child scheduler
- * @cgroup_id: cgroup ID of the child scheduler to dispatch
- * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
- *
- * Allows a parent scheduler to trigger dispatching on one of its direct
- * child schedulers. The child scheduler runs its dispatch operation to
- * move tasks from dispatch queues to the local runqueue.
- *
- * Returns: true on success, false if cgroup_id is invalid, not a direct
- * child, or caller lacks dispatch permission.
- */
-__bpf_kfunc bool scx_bpf_sub_dispatch(u64 cgroup_id, const struct bpf_prog_aux *aux)
-{
- struct rq *this_rq = this_rq();
- struct scx_sched *parent, *child;
-
- guard(rcu)();
- parent = scx_prog_sched(aux);
- if (unlikely(!parent))
- return false;
-
- child = scx_find_sub_sched(cgroup_id);
-
- if (unlikely(!child))
- return false;
-
- if (unlikely(scx_parent(child) != parent)) {
- scx_error(parent, "trying to dispatch a distant sub-sched on cgroup %llu",
- cgroup_id);
- return false;
- }
-
- return scx_dispatch_sched(child, this_rq, this_rq->scx.sub_dispatch_prev,
- true);
+ p, dsq_id, enq_flags, true);
}
-#endif /* CONFIG_EXT_SUB_SCHED */
__bpf_kfunc_end_defs();
@@ -9188,10 +9509,10 @@ __bpf_kfunc s32 scx_bpf_create_dsq(u64 dsq_id, s32 node, const struct bpf_prog_a
return -ENOMEM;
/*
- * init_dsq() must be called in GFP_KERNEL context. Init it with NULL
- * @sch and update afterwards.
+ * scx_init_dsq() must be called in GFP_KERNEL context. Init it with
+ * NULL @sch and update afterwards.
*/
- ret = init_dsq(dsq, dsq_id, NULL);
+ ret = scx_init_dsq(dsq, dsq_id, NULL);
if (ret) {
kfree(dsq);
return ret;
@@ -9245,20 +9566,60 @@ __bpf_kfunc_start_defs();
* @slice: time slice to set in nsecs
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
*
- * Set @p's time slice to @slice. Returns %true on success, %false if the
- * calling scheduler doesn't have authority over @p.
+ * Set @p's time slice. @p must be on the calling scheduler. The value is
+ * applied whether or not the caller holds @p's rq lock - see the slice write
+ * rules above for the ownership model.
+ *
+ * Raising the slice is honored only while the scheduler holds %SCX_CAP_BASE on
+ * @p's cpu, otherwise it is counted in %SCX_EV_SLICE_DENIED. Shortening is
+ * always allowed. On the stashed path the slice is packed into an atomic64_t
+ * with the scheduler id and a flag bit, so a slice too large to fit is clamped
+ * and counted in %SCX_EV_SLICE_CLAMPED. %SCX_SLICE_INF is preserved.
+ *
+ * Return %true on success, %false if @p is not on the calling scheduler.
*/
__bpf_kfunc bool scx_bpf_task_set_slice(struct task_struct *p, u64 slice,
const struct bpf_prog_aux *aux)
{
struct scx_sched *sch;
+ struct rq *locked_rq;
guard(rcu)();
sch = scx_prog_sched(aux);
if (unlikely(!sch || !scx_task_on_sched(sch, p)))
return false;
- p->scx.slice = slice;
+ /*
+ * Directly write only when we hold the lock of the rq @p is queued or
+ * running on. See the write rules above.
+ *
+ * While @p is queued on a user DSQ or in the BPF scheduler,
+ * synchronization is the scheduler's responsibility. This write can
+ * race a concurrent dispatch's commit, see apply_slice_vtime().
+ *
+ * Making this kfunc always go through the oob stash would leave the
+ * commit as the only direct writer and close the race, but that would
+ * require two more oob application points - the dispatch keep-prev test
+ * and the tick-time expiry check.
+ */
+ locked_rq = scx_locked_rq();
+ if (!locked_rq ||
+ (READ_ONCE(p->scx.runnable_cpu) != cpu_of(locked_rq) &&
+ !task_current(locked_rq, p))) {
+ set_task_slice_oob(sch, p, slice);
+ return true;
+ }
+
+ /* under the rq lock: apply now, extensions gated on baseline access */
+ if (slice > p->scx.slice &&
+ unlikely(scx_missing_caps(sch, cpu_of(locked_rq), SCX_CAP_BASE))) {
+ __scx_add_event(sch, SCX_EV_SLICE_DENIED, 1);
+ return true;
+ }
+
+ if (unlikely(!scx_set_task_slice(p, slice)))
+ __scx_add_event(sch, SCX_EV_SLICE_DENIED, 1);
+
return true;
}
@@ -9285,14 +9646,25 @@ __bpf_kfunc bool scx_bpf_task_set_dsq_vtime(struct task_struct *p, u64 vtime,
return true;
}
-static void scx_kick_cpu(struct scx_sched *sch, s32 cpu, u64 flags)
+void scx_kick_cpu(struct scx_sched *sch, s32 cpu, u64 flags)
{
+ struct scx_sched_pcpu *pcpu;
struct rq *this_rq;
unsigned long irq_flags;
+ /*
+ * The per-cpu kick list is guarded only by local_irq_save(), which does
+ * not mask NMIs, so kicking from NMI could corrupt it and is unsupported.
+ */
+ if (unlikely(in_nmi())) {
+ scx_error(sch, "scx_bpf_kick_cpu() called from NMI");
+ return;
+ }
+
local_irq_save(irq_flags);
this_rq = this_rq();
+ pcpu = this_cpu_ptr(sch->pcpu);
/*
* While bypassing for PM ops, IRQ handling may not be online which can
@@ -9306,6 +9678,9 @@ static void scx_kick_cpu(struct scx_sched *sch, s32 cpu, u64 flags)
* Actual kicking is bounced to kick_cpus_irq_workfn() to avoid nesting
* rq locks. We can probably be smarter and avoid bouncing if called
* from ops which don't hold a rq lock.
+ *
+ * The kick masks are owned by @sch->pcpu, so that a preempt kick can be
+ * attributed to @sch.
*/
if (flags & SCX_KICK_IDLE) {
struct rq *target_rq = cpu_rq(cpu);
@@ -9315,21 +9690,25 @@ static void scx_kick_cpu(struct scx_sched *sch, s32 cpu, u64 flags)
if (raw_spin_rq_trylock(target_rq)) {
if (can_skip_idle_kick(target_rq)) {
+ scx_rq_lock_drop(target_rq);
raw_spin_rq_unlock(target_rq);
goto out;
}
+ scx_rq_lock_drop(target_rq);
raw_spin_rq_unlock(target_rq);
}
- cpumask_set_cpu(cpu, this_rq->scx.cpus_to_kick_if_idle);
+ cpumask_set_cpu(cpu, pcpu->cpus_to_kick_if_idle);
} else {
- cpumask_set_cpu(cpu, this_rq->scx.cpus_to_kick);
+ cpumask_set_cpu(cpu, pcpu->cpus_to_kick);
if (flags & SCX_KICK_PREEMPT)
- cpumask_set_cpu(cpu, this_rq->scx.cpus_to_preempt);
+ cpumask_set_cpu(cpu, pcpu->cpus_to_preempt);
if (flags & SCX_KICK_WAIT)
- cpumask_set_cpu(cpu, this_rq->scx.cpus_to_wait);
+ cpumask_set_cpu(cpu, pcpu->cpus_to_wait);
}
+ if (list_empty(&pcpu->to_kick_node))
+ list_add_tail(&pcpu->to_kick_node, &this_rq->scx.sched_pcpus_to_kick);
irq_work_queue(&this_rq->scx.kick_cpus_irq_work);
out:
local_irq_restore(irq_flags);
@@ -9362,10 +9741,13 @@ __bpf_kfunc void scx_bpf_kick_cpu(s32 cpu, u64 flags, const struct bpf_prog_aux
* @flags: %SCX_KICK_* flags
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
*
- * cid-addressed equivalent of scx_bpf_kick_cpu(). Return 0 on success,
- * -errno otherwise.
+ * cid-addressed equivalent of scx_bpf_kick_cpu(). An invalid @cid aborts the
+ * scheduler via scx_cid_to_cpu(). Caps are enforced on the delivery path: a
+ * kick is dropped if the caller lacks baseline access on @cid, and a
+ * %SCX_KICK_PREEMPT degrades to a plain reschedule if the caller lacks
+ * %SCX_CAP_PREEMPT for a task outside its subtree.
*/
-__bpf_kfunc s32 scx_bpf_kick_cid(s32 cid, u64 flags, const struct bpf_prog_aux *aux)
+__bpf_kfunc void scx_bpf_kick_cid(s32 cid, u64 flags, const struct bpf_prog_aux *aux)
{
struct scx_sched *sch;
s32 cpu;
@@ -9373,12 +9755,11 @@ __bpf_kfunc s32 scx_bpf_kick_cid(s32 cid, u64 flags, const struct bpf_prog_aux *
guard(rcu)();
sch = scx_prog_sched(aux);
if (unlikely(!sch))
- return -ENODEV;
+ return;
cpu = scx_cid_to_cpu(sch, cid);
if (cpu < 0)
- return cpu;
+ return;
scx_kick_cpu(sch, cpu, flags);
- return 0;
}
/**
@@ -9388,6 +9769,10 @@ __bpf_kfunc s32 scx_bpf_kick_cid(s32 cid, u64 flags, const struct bpf_prog_aux *
*
* Return the number of tasks in the DSQ matching @dsq_id. If not found,
* -%ENOENT is returned.
+ *
+ * %SCX_DSQ_LOCAL resolves to the local DSQ of the rq the current scheduler
+ * operation is locked to - e.g. the rq being dispatched for in ops.dispatch() -
+ * or the calling CPU's when no rq is locked.
*/
__bpf_kfunc s32 scx_bpf_dsq_nr_queued(u64 dsq_id, const struct bpf_prog_aux *aux)
{
@@ -9404,7 +9789,7 @@ __bpf_kfunc s32 scx_bpf_dsq_nr_queued(u64 dsq_id, const struct bpf_prog_aux *aux
}
if (dsq_id == SCX_DSQ_LOCAL) {
- ret = READ_ONCE(this_rq()->scx.local_dsq.nr);
+ ret = READ_ONCE((scx_locked_rq() ?: this_rq())->scx.local_dsq.nr);
goto out;
} else if ((dsq_id & SCX_DSQ_LOCAL_ON) == SCX_DSQ_LOCAL_ON) {
s32 cpu = scx_cpu_ret(sch, dsq_id & SCX_DSQ_LOCAL_CPU_MASK);
@@ -9514,7 +9899,7 @@ __bpf_kfunc struct task_struct *bpf_iter_scx_dsq_next(struct bpf_iter_scx_dsq *i
* bpf_iter_scx_dsq_destroy - Destroy a DSQ iterator
* @it: iterator to destroy
*
- * Undo scx_iter_scx_dsq_new().
+ * Undo bpf_iter_scx_dsq_new().
*/
__bpf_kfunc void bpf_iter_scx_dsq_destroy(struct bpf_iter_scx_dsq *it)
{
@@ -9583,10 +9968,15 @@ __bpf_kfunc struct task_struct *scx_bpf_dsq_peek(u64 dsq_id,
* - User DSQs
*
* Re-enqueues are performed asynchronously. Can be called from anywhere.
+ *
+ * %SCX_DSQ_LOCAL resolves to the local DSQ of the rq the current scheduler
+ * operation is locked to - e.g. the rq being dispatched for in ops.dispatch() -
+ * or the calling CPU's when no rq is locked.
*/
__bpf_kfunc void scx_bpf_dsq_reenq(u64 dsq_id, u64 reenq_flags,
const struct bpf_prog_aux *aux)
{
+ struct rq *locked_rq = scx_locked_rq();
struct scx_sched *sch;
struct scx_dispatch_q *dsq;
@@ -9605,12 +9995,12 @@ __bpf_kfunc void scx_bpf_dsq_reenq(u64 dsq_id, u64 reenq_flags,
if (!(reenq_flags & __SCX_REENQ_FILTER_MASK))
reenq_flags |= SCX_REENQ_ANY;
- dsq = find_dsq_for_dispatch(sch, this_rq(), dsq_id, smp_processor_id());
- schedule_dsq_reenq(sch, dsq, reenq_flags, scx_locked_rq());
+ dsq = find_dsq_for_dispatch(sch, locked_rq ?: this_rq(), dsq_id, smp_processor_id());
+ schedule_dsq_reenq(sch, dsq, reenq_flags, locked_rq);
}
/**
- * scx_bpf_reenqueue_local - Re-enqueue tasks on a local DSQ
+ * scx_bpf_reenqueue_local___v2 - Re-enqueue tasks on a local DSQ
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
*
* Iterate over all of the tasks currently enqueued on the local DSQ of the
@@ -9665,12 +10055,38 @@ static s32 __bstr_format(struct scx_sched *sch, u64 *data_buf, char *line_buf,
return ret;
}
-__printf(3, 0)
-static s32 bstr_format(struct scx_sched *sch, struct scx_bstr_buf *buf,
- char *fmt, unsigned long long *data, u32 data__sz)
+/*
+ * Exit @sch with the reason formatted from a BPF-supplied bstr format. The exit
+ * is claimed first and the reason is formatted directly into the winner-owned
+ * exit_info buffer, which allows use from any context including NMI.
+ *
+ * @fmt_blame is the sched blamed for formatting failures through the
+ * scx_error() calls in __bstr_format() and differs from @sch when a parent
+ * supplies the kill reason for a child. A formatting failure doesn't revert the
+ * claim - @sch still exits with the claimed kind and a fallback message.
+ */
+__printf(5, 0)
+bool scx_exit_bstr(struct scx_sched *sch, enum scx_exit_kind kind,
+ s64 exit_code, struct scx_sched *fmt_blame, char *fmt,
+ unsigned long long *data, u32 data__sz)
{
- return __bstr_format(sch, buf->data, buf->line, sizeof(buf->line),
- fmt, data, data__sz);
+ struct scx_exit_info *ei = sch->exit_info;
+ u64 data_buf[MAX_BPRINTF_VARARGS];
+ s32 ret;
+
+ guard(preempt)();
+
+ if (!scx_claim_exit(sch, kind))
+ return false;
+
+ ret = __bstr_format(fmt_blame, data_buf, ei->msg, SCX_EXIT_MSG_LEN,
+ fmt, data, data__sz);
+ if (ret < 0)
+ scnprintf(ei->msg, SCX_EXIT_MSG_LEN,
+ "exit message formatting failed (%d)", ret);
+
+ scx_finish_exit(sch, kind, exit_code, raw_smp_processor_id());
+ return true;
}
__bpf_kfunc_start_defs();
@@ -9692,14 +10108,13 @@ __bpf_kfunc void scx_bpf_exit_bstr(s64 exit_code, char *fmt,
const struct bpf_prog_aux *aux)
{
struct scx_sched *sch;
- unsigned long flags;
- raw_spin_lock_irqsave(&scx_exit_bstr_buf_lock, flags);
+ guard(rcu)();
+
sch = scx_prog_sched(aux);
- if (likely(sch) &&
- bstr_format(sch, &scx_exit_bstr_buf, fmt, data, data__sz) >= 0)
- scx_exit(sch, SCX_EXIT_UNREG_BPF, exit_code, "%s", scx_exit_bstr_buf.line);
- raw_spin_unlock_irqrestore(&scx_exit_bstr_buf_lock, flags);
+ if (likely(sch))
+ scx_exit_bstr(sch, SCX_EXIT_UNREG_BPF, exit_code, sch, fmt,
+ data, data__sz);
}
/**
@@ -9717,14 +10132,13 @@ __bpf_kfunc void scx_bpf_error_bstr(char *fmt, unsigned long long *data,
u32 data__sz, const struct bpf_prog_aux *aux)
{
struct scx_sched *sch;
- unsigned long flags;
- raw_spin_lock_irqsave(&scx_exit_bstr_buf_lock, flags);
+ guard(rcu)();
+
sch = scx_prog_sched(aux);
- if (likely(sch) &&
- bstr_format(sch, &scx_exit_bstr_buf, fmt, data, data__sz) >= 0)
- scx_exit(sch, SCX_EXIT_ERROR_BPF, 0, "%s", scx_exit_bstr_buf.line);
- raw_spin_unlock_irqrestore(&scx_exit_bstr_buf_lock, flags);
+ if (likely(sch))
+ scx_exit_bstr(sch, SCX_EXIT_ERROR_BPF, 0, sch, fmt, data,
+ data__sz);
}
/**
@@ -9764,8 +10178,8 @@ __bpf_kfunc void scx_bpf_dump_bstr(char *fmt, unsigned long long *data,
ret = __bstr_format(sch, buf->data, buf->line + dd->cursor,
sizeof(buf->line) - dd->cursor, fmt, data, data__sz);
if (ret < 0) {
- dump_line(dd->s, "%s[!] (\"%s\", %p, %u) failed to format (%d)",
- dd->prefix, fmt, data, data__sz, ret);
+ scx_dump_line(dd->s, "%s[!] (\"%s\", %p, %u) failed to format (%d)",
+ dd->prefix, fmt, data, data__sz, ret);
return;
}
@@ -9883,6 +10297,62 @@ __bpf_kfunc u32 scx_bpf_cidperf_cur(s32 cid, const struct bpf_prog_aux *aux)
return arch_scale_freq_capacity(cpu);
}
+/* validate and apply a cpuperf target, see scx_bpf_cpuperf_set() */
+static s32 scx_cpuperf_set(struct scx_sched *sch, s32 cpu, u32 perf)
+{
+ struct rq *rq, *locked_rq;
+ struct rq_flags rf;
+ s32 ret;
+
+ if (unlikely(perf > SCX_CPUPERF_ONE)) {
+ scx_error(sch, "Invalid cpuperf target %u for CPU %d", perf, cpu);
+ return -EINVAL;
+ }
+
+ if (!scx_cpu_valid(sch, cpu, NULL))
+ return -EINVAL;
+
+ rq = cpu_rq(cpu);
+ locked_rq = scx_locked_rq();
+
+ /*
+ * When called with an rq lock held, restrict the operation to the
+ * corresponding CPU to prevent ABBA deadlocks.
+ */
+ if (locked_rq && rq != locked_rq) {
+ scx_error(sch, "Invalid target CPU %d", cpu);
+ return -EINVAL;
+ }
+
+ /*
+ * If no rq lock is held, allow to operate on any CPU by acquiring
+ * the corresponding rq lock.
+ */
+ if (!locked_rq) {
+ rq_lock_irqsave(rq, &rf);
+ update_rq_clock(rq);
+ }
+
+ /*
+ * ecaps updates are folded under the rq lock, making this test
+ * authoritative: a write can never land after a revoke has taken
+ * effect on @cpu.
+ */
+ if (likely(!scx_missing_caps(sch, cpu, SCX_CAP_PERF))) {
+ rq->scx.cpuperf_target = perf;
+ cpufreq_update_util(rq, 0);
+ ret = 0;
+ } else {
+ __scx_add_event(sch, SCX_EV_SUB_CIDPERF_DENIED, 1);
+ ret = -EACCES;
+ }
+
+ if (!locked_rq)
+ rq_unlock_irqrestore(rq, &rf);
+
+ return ret;
+}
+
/**
* scx_bpf_cpuperf_set - Set the relative performance target of a CPU
* @cpu: CPU of interest
@@ -9908,39 +10378,7 @@ __bpf_kfunc void scx_bpf_cpuperf_set(s32 cpu, u32 perf, const struct bpf_prog_au
if (unlikely(!sch))
return;
- if (unlikely(perf > SCX_CPUPERF_ONE)) {
- scx_error(sch, "Invalid cpuperf target %u for CPU %d", perf, cpu);
- return;
- }
-
- if (scx_cpu_valid(sch, cpu, NULL)) {
- struct rq *rq = cpu_rq(cpu), *locked_rq = scx_locked_rq();
- struct rq_flags rf;
-
- /*
- * When called with an rq lock held, restrict the operation
- * to the corresponding CPU to prevent ABBA deadlocks.
- */
- if (locked_rq && rq != locked_rq) {
- scx_error(sch, "Invalid target CPU %d", cpu);
- return;
- }
-
- /*
- * If no rq lock is held, allow to operate on any CPU by
- * acquiring the corresponding rq lock.
- */
- if (!locked_rq) {
- rq_lock_irqsave(rq, &rf);
- update_rq_clock(rq);
- }
-
- rq->scx.cpuperf_target = perf;
- cpufreq_update_util(rq, 0);
-
- if (!locked_rq)
- rq_unlock_irqrestore(rq, &rf);
- }
+ scx_cpuperf_set(sch, cpu, perf);
}
/**
@@ -9949,10 +10387,13 @@ __bpf_kfunc void scx_bpf_cpuperf_set(s32 cpu, u32 perf, const struct bpf_prog_au
* @perf: target performance level [0, %SCX_CPUPERF_ONE]
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
*
- * cid-addressed equivalent of scx_bpf_cpuperf_set().
+ * cid-addressed equivalent of scx_bpf_cpuperf_set(). A sub-sched needs
+ * SCX_CAP_PERF on @cid. Returns 0 if the target was applied, -%EACCES if
+ * the write was denied for missing caps, other -errnos if @cid didn't
+ * resolve.
*/
-__bpf_kfunc void scx_bpf_cidperf_set(s32 cid, u32 perf,
- const struct bpf_prog_aux *aux)
+__bpf_kfunc s32 scx_bpf_cidperf_set(s32 cid, u32 perf,
+ const struct bpf_prog_aux *aux)
{
struct scx_sched *sch;
s32 cpu;
@@ -9961,11 +10402,12 @@ __bpf_kfunc void scx_bpf_cidperf_set(s32 cid, u32 perf,
sch = scx_prog_sched(aux);
if (unlikely(!sch))
- return;
+ return -ENODEV;
cpu = scx_cid_to_cpu(sch, cid);
if (cpu < 0)
- return;
- scx_bpf_cpuperf_set(cpu, perf, aux);
+ return cpu;
+
+ return scx_cpuperf_set(sch, cpu, perf);
}
/**
@@ -10005,7 +10447,8 @@ __bpf_kfunc u32 scx_bpf_nr_cids(void)
* hotplug, which lets schedulers treat [0, nr_online_cids) as the online
* range. Schedulers that prefer to handle hotplug without a restart should
* install a custom mapping via scx_bpf_cid_override() and track onlining
- * through the ops.cid_online / ops.cid_offline callbacks.
+ * through the ops.cid_online / ops.cid_offline callbacks, starting from the
+ * mask scx_bpf_online_cmask() returns.
*/
__bpf_kfunc u32 scx_bpf_nr_online_cids(void)
{
@@ -10013,17 +10456,49 @@ __bpf_kfunc u32 scx_bpf_nr_online_cids(void)
}
/**
+ * scx_bpf_online_cmask - Return the online cid mask in the scheduler arena
+ * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
+ *
+ * Return a kernel-maintained cmask covering [0, scx_bpf_nr_cids()), or NULL if
+ * the calling program is not associated with a live cid-form scheduler or the
+ * mask is not allocated yet, as in ops.init_cids(). Treat the mask as read-only
+ * even though arena memory stays writable by the BPF scheduler. The mask
+ * follows the SCX hotplug notifications: a cid's bit is updated before
+ * ops.cid_online/offline() runs for it. The pointer is valid from ops.init()
+ * through ops.exit(). Root ops.init() runs with hotplug excluded. Other
+ * contexts can observe concurrent updates.
+ */
+__bpf_kfunc const void *scx_bpf_online_cmask(const struct bpf_prog_aux *aux)
+{
+ struct scx_sched *sch;
+ struct scx_cmask *online;
+
+ guard(rcu)();
+
+ sch = scx_prog_sched(aux);
+ if (unlikely(!sch))
+ return NULL;
+ online = sch->online_cmask;
+ if (unlikely(!online))
+ return NULL;
+
+ return (void *)scx_kaddr_to_arena(sch, online);
+}
+
+/**
* scx_bpf_this_cid - Return the cid of the CPU this program is running on
*
* cid-addressed equivalent of bpf_get_smp_processor_id() for scx programs.
* The current cpu is trivially valid, so this is just a table lookup. Return
- * -EINVAL if called from a non-SCX program before any scheduler has ever
- * been enabled (the cid table is still unallocated at that point).
+ * -EINVAL if called before any scheduler has ever published its cid tables.
*/
__bpf_kfunc s32 scx_bpf_this_cid(void)
{
- s16 *tbl = READ_ONCE(scx_cpu_to_cid_tbl);
+ s16 *tbl;
+
+ guard(rcu)();
+ tbl = rcu_dereference(scx_cpu_to_cid_tbl);
if (!tbl)
return -EINVAL;
return tbl[raw_smp_processor_id()];
@@ -10082,47 +10557,23 @@ __bpf_kfunc s32 scx_bpf_task_cpu(const struct task_struct *p)
* @p: task of interest
*
* cid-addressed equivalent of scx_bpf_task_cpu(). task_cpu(p) is always a
- * valid cpu, so this is just a table lookup. Return -EINVAL if called from
- * a non-SCX program before any scheduler has ever been enabled.
+ * valid cpu, so this is just a table lookup. Return -EINVAL if called before
+ * any scheduler has ever published its cid tables.
*/
__bpf_kfunc s32 scx_bpf_task_cid(const struct task_struct *p)
{
- s16 *tbl = READ_ONCE(scx_cpu_to_cid_tbl);
+ s16 *tbl;
+
+ /* KF_RCU covers only @p - a sleepable program holds no RCU lock */
+ guard(rcu)();
+ tbl = rcu_dereference(scx_cpu_to_cid_tbl);
if (!tbl)
return -EINVAL;
return tbl[task_cpu(p)];
}
/**
- * scx_bpf_cpu_rq - Fetch the rq of a CPU
- * @cpu: CPU of the rq
- * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
- */
-__bpf_kfunc struct rq *scx_bpf_cpu_rq(s32 cpu, const struct bpf_prog_aux *aux)
-{
- struct scx_sched *sch;
-
- guard(rcu)();
-
- sch = scx_prog_sched(aux);
- if (unlikely(!sch))
- return NULL;
-
- if (!scx_cpu_valid(sch, cpu, NULL))
- return NULL;
-
- if (!sch->warned_deprecated_rq) {
- printk_deferred(KERN_WARNING "sched_ext: %s() is deprecated; "
- "use scx_bpf_locked_rq() when holding rq lock "
- "or scx_bpf_cpu_curr() to read remote curr safely.\n", __func__);
- sch->warned_deprecated_rq = true;
- }
-
- return cpu_rq(cpu);
-}
-
-/**
* scx_bpf_locked_rq - Return the rq currently locked by SCX
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
*
@@ -10224,6 +10675,27 @@ __bpf_kfunc struct task_struct *scx_bpf_tid_to_task(u64 tid)
return container_of(scx, struct task_struct, scx);
}
+u64 __scx_bpf_now(struct rq *rq)
+{
+ /* the caller must be on @rq's cpu or hold its lock */
+ lockdep_assert((rq == this_rq() && !preemptible()) ||
+ lockdep_is_held(__rq_lockp(rq)));
+
+ if (smp_load_acquire(&rq->scx.flags) & SCX_RQ_CLK_VALID) {
+ /* if the rq clock is valid, use the cached rq clock */
+ return READ_ONCE(rq->scx.clock);
+ } else {
+ /*
+ * Otherwise, return a fresh rq clock.
+ *
+ * The rq clock is updated outside of the rq lock.
+ * In this case, keep the updated rq clock invalid so the next
+ * read outside the rq lock gets a fresh rq clock.
+ */
+ return sched_clock_cpu(cpu_of(rq));
+ }
+}
+
/**
* scx_bpf_now - Returns a high-performance monotonically non-decreasing
* clock for the current CPU. The clock returned is in nanoseconds.
@@ -10254,76 +10726,47 @@ __bpf_kfunc struct task_struct *scx_bpf_tid_to_task(u64 tid)
*/
__bpf_kfunc u64 scx_bpf_now(void)
{
- struct rq *rq;
- u64 clock;
-
- preempt_disable();
-
- rq = this_rq();
- if (smp_load_acquire(&rq->scx.flags) & SCX_RQ_CLK_VALID) {
- /*
- * If the rq clock is valid, use the cached rq clock.
- *
- * Note that scx_bpf_now() is re-entrant between a process
- * context and an interrupt context (e.g., timer interrupt).
- * However, we don't need to consider the race between them
- * because such race is not observable from a caller.
- */
- clock = READ_ONCE(rq->scx.clock);
- } else {
- /*
- * Otherwise, return a fresh rq clock.
- *
- * The rq clock is updated outside of the rq lock.
- * In this case, keep the updated rq clock invalid so the next
- * kfunc call outside the rq lock gets a fresh rq clock.
- */
- clock = sched_clock_cpu(cpu_of(rq));
- }
-
- preempt_enable();
-
- return clock;
+ /*
+ * Note that scx_bpf_now() is re-entrant between a process context and
+ * an interrupt context (e.g., timer interrupt). However, we don't need
+ * to consider the race between them because such race is not observable
+ * from a caller.
+ */
+ guard(preempt)();
+ return __scx_bpf_now(this_rq());
}
static void scx_read_events(struct scx_sched *sch, struct scx_event_stats *events)
{
- struct scx_event_stats *e_cpu;
int cpu;
/* Aggregate per-CPU event counters into @events. */
memset(events, 0, sizeof(*events));
for_each_possible_cpu(cpu) {
- e_cpu = &per_cpu_ptr(sch->pcpu, cpu)->event_stats;
- scx_agg_event(events, e_cpu, SCX_EV_SELECT_CPU_FALLBACK);
- scx_agg_event(events, e_cpu, SCX_EV_DISPATCH_LOCAL_DSQ_OFFLINE);
- scx_agg_event(events, e_cpu, SCX_EV_DISPATCH_KEEP_LAST);
- scx_agg_event(events, e_cpu, SCX_EV_ENQ_SKIP_EXITING);
- scx_agg_event(events, e_cpu, SCX_EV_ENQ_SKIP_MIGRATION_DISABLED);
- scx_agg_event(events, e_cpu, SCX_EV_REENQ_IMMED);
- scx_agg_event(events, e_cpu, SCX_EV_REENQ_LOCAL_REPEAT);
- scx_agg_event(events, e_cpu, SCX_EV_REFILL_SLICE_DFL);
- scx_agg_event(events, e_cpu, SCX_EV_BYPASS_DURATION);
- scx_agg_event(events, e_cpu, SCX_EV_BYPASS_DISPATCH);
- scx_agg_event(events, e_cpu, SCX_EV_BYPASS_ACTIVATE);
- scx_agg_event(events, e_cpu, SCX_EV_INSERT_NOT_OWNED);
- scx_agg_event(events, e_cpu, SCX_EV_SUB_BYPASS_DISPATCH);
+ struct scx_event_stats *e_cpu = &per_cpu_ptr(sch->pcpu, cpu)->event_stats;
+#define SCX_EVENT(name) (events->name += READ_ONCE(e_cpu->name))
+ SCX_EVENTS_LIST(SCX_EVENT);
+#undef SCX_EVENT
}
}
-/*
- * scx_bpf_events - Get a system-wide event counter to
+/**
+ * scx_bpf_events - Read the event counters of the calling scheduler
* @events: output buffer from a BPF program
- * @events__sz: @events len, must end in '__sz'' for the verifier
+ * @events__sz: @events len, must end in '__sz' for the verifier
+ * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
+ *
+ * Read the event counters of the scheduler associated with the calling program.
+ * @events is zeroed when no scheduler can be resolved.
*/
-__bpf_kfunc void scx_bpf_events(struct scx_event_stats *events,
- size_t events__sz)
+__bpf_kfunc void scx_bpf_events(struct scx_event_stats *events, size_t events__sz,
+ const struct bpf_prog_aux *aux)
{
struct scx_sched *sch;
struct scx_event_stats e_sys;
rcu_read_lock();
- sch = rcu_dereference(scx_root);
+ sch = scx_prog_sched(aux);
if (sch)
scx_read_events(sch, &e_sys);
else
@@ -10405,6 +10848,7 @@ BTF_ID_FLAGS(func, scx_bpf_nr_node_ids)
BTF_ID_FLAGS(func, scx_bpf_nr_cpu_ids)
BTF_ID_FLAGS(func, scx_bpf_nr_cids)
BTF_ID_FLAGS(func, scx_bpf_nr_online_cids)
+BTF_ID_FLAGS(func, scx_bpf_online_cmask, KF_IMPLICIT_ARGS | KF_ARENA_RET)
BTF_ID_FLAGS(func, scx_bpf_this_cid)
BTF_ID_FLAGS(func, scx_bpf_get_possible_cpumask, KF_ACQUIRE)
BTF_ID_FLAGS(func, scx_bpf_get_online_cpumask, KF_ACQUIRE)
@@ -10412,16 +10856,19 @@ BTF_ID_FLAGS(func, scx_bpf_put_cpumask, KF_RELEASE)
BTF_ID_FLAGS(func, scx_bpf_task_running, KF_RCU)
BTF_ID_FLAGS(func, scx_bpf_task_cpu, KF_RCU)
BTF_ID_FLAGS(func, scx_bpf_task_cid, KF_RCU)
-BTF_ID_FLAGS(func, scx_bpf_cpu_rq, KF_IMPLICIT_ARGS)
BTF_ID_FLAGS(func, scx_bpf_locked_rq, KF_IMPLICIT_ARGS | KF_RET_NULL)
BTF_ID_FLAGS(func, scx_bpf_cpu_curr, KF_IMPLICIT_ARGS | KF_RET_NULL | KF_RCU_PROTECTED)
BTF_ID_FLAGS(func, scx_bpf_cid_curr, KF_IMPLICIT_ARGS | KF_RET_NULL | KF_RCU_PROTECTED)
BTF_ID_FLAGS(func, scx_bpf_tid_to_task, KF_RET_NULL | KF_RCU_PROTECTED)
BTF_ID_FLAGS(func, scx_bpf_now)
-BTF_ID_FLAGS(func, scx_bpf_events)
+BTF_ID_FLAGS(func, scx_bpf_events, KF_IMPLICIT_ARGS)
#ifdef CONFIG_CGROUP_SCHED
BTF_ID_FLAGS(func, scx_bpf_task_cgroup, KF_IMPLICIT_ARGS | KF_RCU | KF_ACQUIRE)
#endif
+BTF_ID_FLAGS(func, scx_bpf_sub_grant, KF_IMPLICIT_ARGS)
+BTF_ID_FLAGS(func, scx_bpf_sub_revoke, KF_IMPLICIT_ARGS)
+BTF_ID_FLAGS(func, scx_bpf_sub_caps, KF_IMPLICIT_ARGS)
+BTF_ID_FLAGS(func, scx_bpf_sub_kill_bstr, KF_IMPLICIT_ARGS)
BTF_KFUNCS_END(scx_kfunc_ids_any)
static const struct btf_kfunc_id_set scx_kfunc_set_any = {
@@ -10447,7 +10894,6 @@ static const struct btf_kfunc_id_set scx_kfunc_set_any = {
BTF_KFUNCS_START(scx_kfunc_ids_cpu_only)
BTF_ID_FLAGS(func, scx_bpf_kick_cpu, KF_IMPLICIT_ARGS)
BTF_ID_FLAGS(func, scx_bpf_task_cpu, KF_RCU)
-BTF_ID_FLAGS(func, scx_bpf_cpu_rq, KF_IMPLICIT_ARGS)
BTF_ID_FLAGS(func, scx_bpf_cpu_curr, KF_IMPLICIT_ARGS | KF_RET_NULL | KF_RCU_PROTECTED)
BTF_ID_FLAGS(func, scx_bpf_cpu_node, KF_IMPLICIT_ARGS)
BTF_ID_FLAGS(func, scx_bpf_cpuperf_cap, KF_IMPLICIT_ARGS)
@@ -10480,7 +10926,7 @@ BTF_KFUNCS_END(scx_kfunc_ids_cpu_only)
*/
enum scx_kf_allow_flags {
SCX_KF_ALLOW_UNLOCKED = 1 << 0,
- SCX_KF_ALLOW_INIT = 1 << 1,
+ SCX_KF_ALLOW_INIT_CIDS = 1 << 1,
SCX_KF_ALLOW_CPU_RELEASE = 1 << 2,
SCX_KF_ALLOW_DISPATCH = 1 << 3,
SCX_KF_ALLOW_ENQUEUE = 1 << 4,
@@ -10510,9 +10956,11 @@ static const u32 scx_kf_allow_flags[] = {
#endif /* CONFIG_EXT_GROUP_SCHED */
[SCX_OP_IDX(sub_attach)] = SCX_KF_ALLOW_UNLOCKED,
[SCX_OP_IDX(sub_detach)] = SCX_KF_ALLOW_UNLOCKED,
+ [SCX_OP_IDX(sub_ecaps_updated)] = SCX_KF_ALLOW_ENQUEUE | SCX_KF_ALLOW_DISPATCH,
[SCX_OP_IDX(cpu_online)] = SCX_KF_ALLOW_UNLOCKED,
[SCX_OP_IDX(cpu_offline)] = SCX_KF_ALLOW_UNLOCKED,
- [SCX_OP_IDX(init)] = SCX_KF_ALLOW_UNLOCKED | SCX_KF_ALLOW_INIT,
+ [SCX_OP_IDX(init_cids)] = SCX_KF_ALLOW_UNLOCKED | SCX_KF_ALLOW_INIT_CIDS,
+ [SCX_OP_IDX(init)] = SCX_KF_ALLOW_UNLOCKED,
[SCX_OP_IDX(exit)] = SCX_KF_ALLOW_UNLOCKED,
};
@@ -10527,7 +10975,7 @@ static const u32 scx_kf_allow_flags[] = {
int scx_kfunc_context_filter(const struct bpf_prog *prog, u32 kfunc_id)
{
bool in_unlocked = btf_id_set8_contains(&scx_kfunc_ids_unlocked, kfunc_id);
- bool in_init = btf_id_set8_contains(&scx_kfunc_ids_init, kfunc_id);
+ bool in_init_cids = btf_id_set8_contains(&scx_kfunc_ids_init_cids, kfunc_id);
bool in_select_cpu = btf_id_set8_contains(&scx_kfunc_ids_select_cpu, kfunc_id);
bool in_enqueue = btf_id_set8_contains(&scx_kfunc_ids_enqueue_dispatch, kfunc_id);
bool in_dispatch = btf_id_set8_contains(&scx_kfunc_ids_dispatch, kfunc_id);
@@ -10535,19 +10983,20 @@ int scx_kfunc_context_filter(const struct bpf_prog *prog, u32 kfunc_id)
bool in_idle = btf_id_set8_contains(&scx_kfunc_ids_idle, kfunc_id);
bool in_any = btf_id_set8_contains(&scx_kfunc_ids_any, kfunc_id);
bool in_cpu_only = btf_id_set8_contains(&scx_kfunc_ids_cpu_only, kfunc_id);
+ bool in_cid = btf_id_set8_contains(&scx_kfunc_ids_cid, kfunc_id);
u32 moff, flags;
/* Not an SCX kfunc - allow. */
- if (!(in_unlocked || in_init || in_select_cpu || in_enqueue || in_dispatch ||
- in_cpu_release || in_idle || in_any))
+ if (!(in_unlocked || in_init_cids || in_select_cpu || in_enqueue || in_dispatch ||
+ in_cpu_release || in_idle || in_any || in_cid))
return 0;
/* SYSCALL progs (e.g. BPF test_run()) may call unlocked and select_cpu kfuncs. */
if (prog->type == BPF_PROG_TYPE_SYSCALL)
- return (in_unlocked || in_select_cpu || in_idle || in_any) ? 0 : -EACCES;
+ return (in_unlocked || in_select_cpu || in_idle || in_any || in_cid) ? 0 : -EACCES;
if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
- return (in_any || in_idle) ? 0 : -EACCES;
+ return (in_any || in_idle || in_cid) ? 0 : -EACCES;
/*
* add_subprog_and_kfunc() collects all kfunc calls, including dead code
@@ -10582,7 +11031,7 @@ int scx_kfunc_context_filter(const struct bpf_prog *prog, u32 kfunc_id)
return -EACCES;
/* SCX struct_ops: check the per-op allow list. */
- if (in_any || in_idle)
+ if (in_any || in_idle || in_cid)
return 0;
moff = prog->aux->attach_st_ops_member_off;
@@ -10590,7 +11039,7 @@ int scx_kfunc_context_filter(const struct bpf_prog *prog, u32 kfunc_id)
if ((flags & SCX_KF_ALLOW_UNLOCKED) && in_unlocked)
return 0;
- if ((flags & SCX_KF_ALLOW_INIT) && in_init)
+ if ((flags & SCX_KF_ALLOW_INIT_CIDS) && in_init_cids)
return 0;
if ((flags & SCX_KF_ALLOW_CPU_RELEASE) && in_cpu_release)
return 0;
@@ -10624,6 +11073,9 @@ static int __init scx_init(void)
CID_OFFSET_MATCH(timeout_ms, timeout_ms);
CID_OFFSET_MATCH(exit_dump_len, exit_dump_len);
CID_OFFSET_MATCH(hotplug_seq, hotplug_seq);
+ CID_OFFSET_MATCH(cid_shard_size, cid_shard_size);
+ CID_OFFSET_MATCH(rescue_bandwidth_ppt, rescue_bandwidth_ppt);
+ CID_OFFSET_MATCH(rescue_quantum_us, rescue_quantum_us);
CID_OFFSET_MATCH(sub_cgroup_id, sub_cgroup_id);
/* shared callbacks: the union view requires byte-for-byte offset match */
CID_OFFSET_MATCH(enqueue, enqueue);
@@ -10646,28 +11098,31 @@ static int __init scx_init(void)
CID_OFFSET_MATCH(dump_task, dump_task);
CID_OFFSET_MATCH(sub_attach, sub_attach);
CID_OFFSET_MATCH(sub_detach, sub_detach);
+ CID_OFFSET_MATCH(sub_caps_updated, sub_caps_updated);
+ CID_OFFSET_MATCH(sub_ecaps_updated, sub_ecaps_updated);
+ CID_OFFSET_MATCH(init_cids, init_cids);
CID_OFFSET_MATCH(init, init);
CID_OFFSET_MATCH(exit, exit);
-#ifdef CONFIG_EXT_GROUP_SCHED
- CID_OFFSET_MATCH(cgroup_init, cgroup_init);
- CID_OFFSET_MATCH(cgroup_exit, cgroup_exit);
- CID_OFFSET_MATCH(cgroup_prep_move, cgroup_prep_move);
- CID_OFFSET_MATCH(cgroup_move, cgroup_move);
- CID_OFFSET_MATCH(cgroup_cancel_move, cgroup_cancel_move);
- CID_OFFSET_MATCH(cgroup_set_weight, cgroup_set_weight);
- CID_OFFSET_MATCH(cgroup_set_bandwidth, cgroup_set_bandwidth);
- CID_OFFSET_MATCH(cgroup_set_idle, cgroup_set_idle);
-#endif
/* renamed callbacks must occupy the same slot as their cpu-form sibling */
CID_OFFSET_MATCH(select_cpu, select_cid);
CID_OFFSET_MATCH(set_cpumask, set_cmask);
CID_OFFSET_MATCH(cpu_online, cid_online);
CID_OFFSET_MATCH(cpu_offline, cid_offline);
CID_OFFSET_MATCH(dump_cpu, dump_cid);
+#ifdef CONFIG_EXT_GROUP_SCHED
+ CID_OFFSET_MATCH(cgroup_init, cpuctl_init);
+ CID_OFFSET_MATCH(cgroup_exit, cpuctl_exit);
+ CID_OFFSET_MATCH(cgroup_prep_move, cpuctl_prep_move);
+ CID_OFFSET_MATCH(cgroup_move, cpuctl_move);
+ CID_OFFSET_MATCH(cgroup_cancel_move, cpuctl_cancel_move);
+ CID_OFFSET_MATCH(cgroup_set_weight, cpuctl_set_weight);
+ CID_OFFSET_MATCH(cgroup_set_bandwidth, cpuctl_set_bandwidth);
+ CID_OFFSET_MATCH(cgroup_set_idle, cpuctl_set_idle);
+#endif
/* @priv tail must align since both share the same data block */
CID_OFFSET_MATCH(priv, priv);
/*
- * cid-form must end exactly at @priv - validate_ops() skips
+ * cid-form must end exactly at @priv - scx_validate_ops() skips
* cpu_acquire/cpu_release for cid-form because reading those fields
* past the BPF allocation would be UB.
*/
@@ -10752,3 +11207,16 @@ static int __init scx_init(void)
return 0;
}
__initcall(scx_init);
+
+/*
+ * Compatibility markers for userspace. Existence of a marker function
+ * represents that the kernel supports that sched-ext feature.
+ */
+
+/*
+ * scx_compat_marker_cgroup_set_bandwidth_may_sleep: advertises that
+ * ops.cgroup_set_bandwidth() may be implemented as a sleepable callback.
+ */
+#ifdef CONFIG_EXT_GROUP_SCHED
+DEFINE_SCX_COMPAT_MARKER(cgroup_set_bandwidth_may_sleep);
+#endif /* CONFIG_EXT_GROUP_SCHED */
diff --git a/kernel/sched/ext/idle.c b/kernel/sched/ext/idle.c
index 8e8c6201b7df..aa9fb6de0ad6 100644
--- a/kernel/sched/ext/idle.c
+++ b/kernel/sched/ext/idle.c
@@ -12,6 +12,7 @@
#include "internal.h"
#include "cid.h"
#include "idle.h"
+#include "sub.h"
/* Enable/disable built-in idle CPU selection policy */
static DEFINE_STATIC_KEY_FALSE(scx_builtin_idle_enabled);
@@ -554,8 +555,10 @@ s32 scx_select_cpu_dfl(struct task_struct *p, s32 prev_cpu, u64 wake_flags,
cpu_rq(cpu)->scx.local_dsq.nr == 0 &&
(!(flags & SCX_PICK_IDLE_IN_NODE) || (waker_node == node)) &&
!cpumask_empty(idle_cpumask(waker_node)->cpu)) {
- if (cpumask_test_cpu(cpu, allowed))
+ if (cpumask_test_cpu(cpu, allowed)) {
+ scx_idle_test_and_clear_cpu(cpu);
goto out_unlock;
+ }
}
}
@@ -731,6 +734,55 @@ static void update_builtin_idle(int cpu, bool idle)
}
/*
+ * Notify schedulers of an idle transition on @cpu's cid, delivering to every
+ * sched that holds %SCX_CAP_BASE on the cid (the root holds every cap). A real
+ * transition (@do_notify) reaches all holders. A forced one (@root_renotify for
+ * the root, a sub-sched's idle_renotify marker for a sub) reaches only the owed
+ * scheds.
+ */
+static void scx_idle_notify(struct rq *rq, bool idle, bool do_notify, bool root_renotify)
+{
+ s32 cpu = cpu_of(rq);
+ s32 cid = scx_cpu_arg(cpu);
+ struct scx_sched *root = scx_root_protected_live();
+ struct scx_sched *pos;
+
+ lockdep_assert_rq_held(rq);
+
+ /* with no sub-sched, only the root can be owed a notification */
+ if (!scx_has_subs()) {
+ if ((do_notify || root_renotify) &&
+ SCX_HAS_OP(root, update_idle) && !scx_bypassing(root, cpu))
+ SCX_CALL_OP(root, update_idle, rq, cid, idle);
+ return;
+ }
+
+ pos = scx_next_descendant_pre(NULL, root);
+ while (pos) {
+ bool forced = false;
+
+ if (unlikely(scx_missing_caps(pos, cpu, SCX_CAP_BASE))) {
+ pos = scx_skip_subtree_pre(pos, root);
+ continue;
+ }
+
+ if (!pos->level) {
+ forced = root_renotify;
+ }
+#ifdef CONFIG_EXT_SUB_SCHED
+ else if (per_cpu_ptr(pos->pcpu, cpu)->idle_renotify) {
+ per_cpu_ptr(pos->pcpu, cpu)->idle_renotify = false;
+ forced = true;
+ }
+#endif
+ if ((do_notify || forced) && SCX_HAS_OP(pos, update_idle) &&
+ !scx_bypassing(pos, cpu))
+ SCX_CALL_OP(pos, update_idle, rq, cid, idle);
+ pos = scx_next_descendant_pre(pos, root);
+ }
+}
+
+/*
* Update the idle state of a CPU to @idle.
*
* If @do_notify is true, ops.update_idle() is invoked to notify the scx
@@ -748,44 +800,39 @@ static void update_builtin_idle(int cpu, bool idle)
*/
void __scx_update_idle(struct rq *rq, bool idle, bool do_notify)
{
- struct scx_sched *sch = scx_root;
int cpu = cpu_of(rq);
lockdep_assert_rq_held(rq);
/*
- * Update the idle masks:
- * - for real idle transitions (do_notify == true)
- * - for idle-to-idle transitions (indicated by the previous task
- * being the idle thread, managed by pick_task_idle())
- *
- * Skip updating idle masks if the previous task is not the idle
- * thread, since set_next_task_idle() has already handled it when
- * transitioning from a task to the idle thread (calling this
- * function with do_notify == true).
- *
- * In this way we can avoid updating the idle masks twice,
- * unnecessarily.
+ * pick_task_idle() calls here only on an idle-to-idle re-pick and the
+ * transitions call with @do_notify, so every reaching call updates the
+ * masks.
*/
if (static_branch_likely(&scx_builtin_idle_enabled))
- if (do_notify || is_idle_task(rq->curr))
- update_builtin_idle(cpu, idle);
+ update_builtin_idle(cpu, idle);
/*
- * Trigger ops.update_idle() only when transitioning from a task to
- * the idle thread and vice versa.
- *
- * Idle transitions are indicated by do_notify being set to true,
- * managed by put_prev_task_idle()/set_next_task_idle().
+ * ops.update_idle() fires on real idle transitions, indicated by
+ * @do_notify and managed by put_prev_task_idle()/set_next_task_idle().
+ * An idle pick also fires it to flush a forced notify owed to a sched
+ * that missed transitions while bypassed or on a cid it just gained.
+ * unbypass_renotify_idle() and scx_process_sync_ecaps() arm the per-rq
+ * gates, and scx_idle_notify() targets the owed scheds.
*
- * This must come after builtin idle update so that BPF schedulers can
- * create interlocking between ops.update_idle() and ops.enqueue() -
+ * This must come after the builtin idle update so that BPF schedulers
+ * can create interlocking between ops.update_idle() and ops.enqueue() -
* either enqueue() sees the idle bit or update_idle() sees the task
* that enqueue() queued.
*/
- if (SCX_HAS_OP(sch, update_idle) && do_notify &&
- !scx_bypassing(sch, cpu_of(rq)))
- SCX_CALL_OP(sch, update_idle, rq, scx_cpu_arg(cpu_of(rq)), idle);
+ if (do_notify ||
+ (idle && (rq->scx.flags &
+ (SCX_RQ_SUB_IDLE_RENOTIFY | SCX_RQ_ROOT_IDLE_RENOTIFY)))) {
+ bool root_renotify = rq->scx.flags & SCX_RQ_ROOT_IDLE_RENOTIFY;
+
+ rq->scx.flags &= ~(SCX_RQ_SUB_IDLE_RENOTIFY | SCX_RQ_ROOT_IDLE_RENOTIFY);
+ scx_idle_notify(rq, idle, do_notify, root_renotify);
+ }
}
static void reset_idle_masks(struct sched_ext_ops *ops)
@@ -793,20 +840,20 @@ static void reset_idle_masks(struct sched_ext_ops *ops)
int node;
/*
- * Consider all online cpus idle. Should converge to the actual state
- * quickly.
+ * Start with all CPUs marked busy. The idle masks are populated when
+ * bypass is lifted and each idle CPU is forced through an idle re-pick.
+ * This may temporarily omit idle CPUs but never advertises a busy CPU as
+ * idle.
*/
if (!(ops->flags & SCX_OPS_BUILTIN_IDLE_PER_NODE)) {
- cpumask_copy(idle_cpumask(NUMA_NO_NODE)->cpu, cpu_online_mask);
- cpumask_copy(idle_cpumask(NUMA_NO_NODE)->smt, cpu_online_mask);
+ cpumask_clear(idle_cpumask(NUMA_NO_NODE)->cpu);
+ cpumask_clear(idle_cpumask(NUMA_NO_NODE)->smt);
return;
}
for_each_node(node) {
- const struct cpumask *node_mask = cpumask_of_node(node);
-
- cpumask_and(idle_cpumask(node)->cpu, cpu_online_mask, node_mask);
- cpumask_and(idle_cpumask(node)->smt, cpu_online_mask, node_mask);
+ cpumask_clear(idle_cpumask(node)->cpu);
+ cpumask_clear(idle_cpumask(node)->smt);
}
}
@@ -1095,10 +1142,17 @@ __bpf_kfunc s32 scx_bpf_select_cpu_and(struct task_struct *p, s32 prev_cpu, u64
#ifdef CONFIG_EXT_SUB_SCHED
/*
* Disallow if any sub-scheds are attached. There is no way to tell
- * which scheduler called us, just error out @p's scheduler.
+ * which scheduler called us, so error out @p's scheduler -- read it
+ * under RCU as @p's locks aren't necessarily held here. @p may be a
+ * task past sched_ext_dead() or an idle task, in which case its
+ * scheduler can't be determined and there is nothing obviously wrong
+ * to report; just refuse the call.
*/
if (unlikely(!list_empty(&sch->children))) {
- scx_error(scx_task_sched(p), "__scx_bpf_select_cpu_and() must be used");
+ struct scx_sched *tsch = scx_task_sched_rcu(p);
+
+ if (tsch)
+ scx_error(tsch, "__scx_bpf_select_cpu_and() must be used");
return -EINVAL;
}
#endif
@@ -1316,7 +1370,7 @@ __bpf_kfunc s32 scx_bpf_pick_idle_cpu_node(const struct cpumask *cpus_allowed,
/**
* scx_bpf_pick_idle_cpu - Pick and claim an idle cpu
* @cpus_allowed: Allowed cpumask
- * @flags: %SCX_PICK_IDLE_CPU_* flags
+ * @flags: %SCX_PICK_IDLE_* flags
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
*
* Pick and claim an idle cpu in @cpus_allowed. Returns the picked idle cpu
@@ -1363,7 +1417,7 @@ __bpf_kfunc s32 scx_bpf_pick_idle_cpu(const struct cpumask *cpus_allowed,
* or pick any CPU from @node
* @cpus_allowed: Allowed cpumask
* @node: target NUMA node
- * @flags: %SCX_PICK_IDLE_CPU_* flags
+ * @flags: %SCX_PICK_IDLE_* flags
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
*
* Pick and claim an idle cpu in @cpus_allowed. If none is available, pick any
@@ -1414,7 +1468,7 @@ __bpf_kfunc s32 scx_bpf_pick_any_cpu_node(const struct cpumask *cpus_allowed,
/**
* scx_bpf_pick_any_cpu - Pick and claim an idle cpu if available or pick any CPU
* @cpus_allowed: Allowed cpumask
- * @flags: %SCX_PICK_IDLE_CPU_* flags
+ * @flags: %SCX_PICK_IDLE_* flags
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
*
* Pick and claim an idle cpu in @cpus_allowed. If none is available, pick any
diff --git a/kernel/sched/ext/inlines.h b/kernel/sched/ext/inlines.h
new file mode 100644
index 000000000000..8de9d8fb4b25
--- /dev/null
+++ b/kernel/sched/ext/inlines.h
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * BPF extensible scheduler class: Documentation/scheduler/sched-ext.rst
+ *
+ * Inline definitions layered on top of internal.h and cid.h.
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ * Copyright (c) 2026 Tejun Heo <tj@kernel.org>
+ */
+#ifndef _KERNEL_SCHED_EXT_INLINES_H
+#define _KERNEL_SCHED_EXT_INLINES_H
+
+#include "internal.h"
+#include "cid.h"
+
+/* what dispatch concluded, consumed by the pick that follows */
+enum scx_dsp_verdict {
+ SCX_DSP_NONE, /* nothing to run */
+ SCX_DSP_LOCAL, /* local DSQ has tasks */
+ SCX_DSP_PREV, /* keep running @prev */
+ SCX_DSP_RETRY, /* pick helpers only: restart the pick */
+};
+
+/*
+ * One user of this function is scx_bpf_sub_dispatch() which can be called
+ * recursively as sub-sched dispatches nest. Always inline to reduce stack usage
+ * from the call frame.
+ */
+static __always_inline enum scx_dsp_verdict
+scx_dispatch_sched(struct scx_sched *sch, struct rq *rq,
+ struct task_struct *prev, bool nested)
+{
+ struct scx_dsp_ctx *dspc = &this_cpu_ptr(sch->pcpu)->dsp_ctx;
+ int nr_loops = SCX_DSP_MAX_LOOPS;
+ s32 cpu = cpu_of(rq);
+ bool prev_on_sch = (prev->sched_class == &ext_sched_class) &&
+ scx_task_on_sched(sch, prev);
+
+ if (scx_consume_global_dsq(sch, rq))
+ return SCX_DSP_LOCAL;
+
+ if (scx_bypass_dsp_enabled(sch)) {
+ /* if @sch is bypassing, only the bypass DSQs are active */
+ if (scx_bypassing(sch, cpu)) {
+ if (scx_consume_dispatch_q(sch, rq, scx_bypass_dsq(sch, cpu), 0))
+ return SCX_DSP_LOCAL;
+ return SCX_DSP_NONE;
+ }
+
+#ifdef CONFIG_EXT_SUB_SCHED
+ /*
+ * If @sch isn't bypassing but its children are, @sch is
+ * responsible for making forward progress for both its own
+ * tasks that aren't bypassing and the bypassing descendants'
+ * tasks. The following implements a simple built-in behavior -
+ * let each CPU try to run the bypass DSQ every Nth time.
+ *
+ * Later, if necessary, we can add an ops flag to suppress the
+ * auto-consumption and a kfunc to consume the bypass DSQ and,
+ * so that the BPF scheduler can fully control scheduling of
+ * bypassed tasks.
+ */
+ struct scx_sched_pcpu *pcpu = per_cpu_ptr(sch->pcpu, cpu);
+
+ if (!(pcpu->bypass_host_seq++ % SCX_BYPASS_HOST_NTH) &&
+ scx_consume_dispatch_q(sch, rq, scx_bypass_dsq(sch, cpu), 0)) {
+ __scx_add_event(sch, SCX_EV_SUB_BYPASS_DISPATCH, 1);
+ return SCX_DSP_LOCAL;
+ }
+#endif /* CONFIG_EXT_SUB_SCHED */
+ }
+
+ /*
+ * scx_rq_online() can't be used. Its cpu_active() test goes false
+ * before CPU hotplug waits for an RCU grace period, and
+ * rq_offline_scx() moves this CPU's tasks to the local DSQ only after
+ * the wait. The grace period can depend on those tasks running.
+ */
+ if (unlikely(!SCX_HAS_OP(sch, dispatch)) || !(rq->scx.flags & SCX_RQ_ONLINE))
+ return SCX_DSP_NONE;
+
+ dspc->rq = rq;
+
+ /*
+ * The dispatch loop. Because scx_flush_dispatch_buf() may drop the rq
+ * lock, the local DSQ might still end up empty after a successful
+ * ops.dispatch(). If the local DSQ is empty even after ops.dispatch()
+ * produced some tasks, retry. The BPF scheduler may depend on this
+ * looping behavior to simplify its implementation.
+ */
+ do {
+ dspc->nr_tasks = 0;
+
+#ifdef CONFIG_EXT_SUB_SCHED
+ /* stash @prev so that nested invocations can access it */
+ if (!nested)
+ rq->scx.sub_dispatch_prev = prev;
+#endif
+
+ SCX_CALL_OP(sch, dispatch, rq, scx_cpu_arg(cpu),
+ prev_on_sch ? prev : NULL);
+
+#ifdef CONFIG_EXT_SUB_SCHED
+ if (!nested)
+ rq->scx.sub_dispatch_prev = NULL;
+#endif
+
+ scx_flush_dispatch_buf(sch, rq);
+
+ if ((prev->scx.flags & SCX_TASK_QUEUED) && prev->scx.slice)
+ return SCX_DSP_PREV;
+ if (rq->scx.local_dsq.nr)
+ return SCX_DSP_LOCAL;
+ if (scx_consume_global_dsq(sch, rq))
+ return SCX_DSP_LOCAL;
+
+ /*
+ * ops.dispatch() can trap us in this loop by repeatedly
+ * dispatching ineligible tasks. Break out once in a while to
+ * allow the watchdog to run. As IRQ can't be enabled in
+ * dispatch, we want to complete this scheduling cycle and then
+ * start a new one. IOW, we want to call resched_curr() on the
+ * next, most likely idle, task, not the current one. Use
+ * __scx_bpf_kick_cpu() for deferred kicking.
+ */
+ if (unlikely(!--nr_loops)) {
+ scx_kick_cpu(sch, cpu, 0);
+ break;
+ }
+ } while (dspc->nr_tasks);
+
+ /*
+ * Prevent the CPU from going idle while bypassed descendants have tasks
+ * queued. Without this fallback, bypassed tasks could stall if the host
+ * scheduler's ops.dispatch() doesn't yield any tasks.
+ */
+ if (scx_bypass_dsp_enabled(sch) &&
+ scx_consume_dispatch_q(sch, rq, scx_bypass_dsq(sch, cpu), 0)) {
+ __scx_add_event(sch, SCX_EV_SUB_BYPASS_DISPATCH, 1);
+ return SCX_DSP_LOCAL;
+ }
+
+ return SCX_DSP_NONE;
+}
+
+#endif /* _KERNEL_SCHED_EXT_INLINES_H */
diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h
index 145272cb4d8a..5b37faa532d6 100644
--- a/kernel/sched/ext/internal.h
+++ b/kernel/sched/ext/internal.h
@@ -11,6 +11,34 @@
#include "../sched.h"
#include "types.h"
+#include <trace/events/sched_ext.h>
+
+/**
+ * scx_add_event - Increase an event counter for 'name' by 'cnt'
+ * @sch: scx_sched to account events for
+ * @name: an event name defined in struct scx_event_stats
+ * @cnt: the number of the event occurred
+ *
+ * This can be used when preemption is not disabled.
+ */
+#define scx_add_event(sch, name, cnt) do { \
+ this_cpu_add((sch)->pcpu->event_stats.name, (cnt)); \
+ trace_sched_ext_event(#name, (cnt)); \
+} while(0)
+
+/**
+ * __scx_add_event - Increase an event counter for 'name' by 'cnt'
+ * @sch: scx_sched to account events for
+ * @name: an event name defined in struct scx_event_stats
+ * @cnt: the number of the event occurred
+ *
+ * This should be used only when preemption is disabled.
+ */
+#define __scx_add_event(sch, name, cnt) do { \
+ __this_cpu_add((sch)->pcpu->event_stats.name, (cnt)); \
+ trace_sched_ext_event(#name, cnt); \
+} while(0)
+
#define SCX_OP_IDX(op) (offsetof(struct sched_ext_ops, op) / sizeof(void (*)(void)))
#define SCX_MOFF_IDX(moff) ((moff) / sizeof(void (*)(void)))
@@ -23,10 +51,13 @@ enum scx_exit_kind {
SCX_EXIT_UNREG_KERN, /* kernel-initiated unregistration */
SCX_EXIT_SYSRQ, /* requested by 'S' sysrq */
SCX_EXIT_PARENT, /* parent exiting */
+ SCX_EXIT_PARENT_KILL, /* killed by parent scheduler */
SCX_EXIT_ERROR = 1024, /* runtime error, error msg contains details */
SCX_EXIT_ERROR_BPF, /* ERROR but triggered through scx_bpf_error() */
SCX_EXIT_ERROR_STALL, /* watchdog detected stalled runnable tasks */
+ SCX_EXIT_ERROR_REENQ, /* task hit reenqueue limit without running */
+ SCX_EXIT_ERROR_RESCUE, /* ejected for overloading rescue execution */
};
/*
@@ -219,6 +250,31 @@ struct scx_exit_task_args {
bool cancelled;
};
+/**
+ * struct scx_enable_args - Argument container for cid-form ops.enable()
+ * @cmask_arena_addr: BPF arena address of the cmask of cids the task may run on
+ *
+ * @cmask_arena_addr is the task's affinity as it enters the scheduler.
+ * set_cmask() delivers the same mask right after enable(), before set_weight()
+ * and the first enqueue, then every affinity change afterwards, and is never
+ * called before enable(). A scheduler may therefore track affinity in
+ * set_cmask() alone.
+ *
+ * The kernel builds the mask in the scheduler arena from its own geometry, so
+ * the header is valid regardless of what the scheduler last wrote there. The
+ * memory is per-cpu scratch reused once the callback returns: copy the bits
+ * out, don't keep the address. The set_cmask() argument follows the same rules.
+ *
+ * The address is a plain value rather than a typed pointer because BTF can't
+ * mark a struct member as an arena pointer yet and a pointer member would reach
+ * the program typed as a kernel pointer. Cast it to struct scx_cmask __arena *
+ * before use. Once arena members can be typed, a typed alias will join this
+ * field in an anonymous union at the same offset.
+ */
+struct scx_enable_args {
+ u64 cmask_arena_addr;
+};
+
/* argument container for ops.cgroup_init() */
struct scx_cgroup_init_args {
/* the weight of the cgroup [1..10000] */
@@ -228,6 +284,9 @@ struct scx_cgroup_init_args {
u64 bw_period_us;
u64 bw_quota_us;
u64 bw_burst_us;
+
+ /* whether the cgroup is configured SCHED_IDLE via cpu.idle */
+ bool sched_idle;
};
enum scx_cpu_preempt_reason {
@@ -372,8 +431,9 @@ struct sched_ext_ops {
* @p: task running currently
*
* This operation is called every 1/HZ seconds on CPUs which are
- * executing an SCX task. Setting @p->scx.slice to 0 will trigger an
- * immediate dispatch cycle on the CPU.
+ * executing an SCX task. Setting a slice of 0 for @p with
+ * scx_bpf_task_set_slice() will trigger an immediate dispatch cycle on
+ * the CPU.
*/
void (*tick)(struct task_struct *p);
@@ -410,7 +470,7 @@ struct sched_ext_ops {
*
* Note that this callback may be called from a CPU other than the
* one the task is going to run on. This can happen when a task
- * property is changed (i.e., affinity), since scx_next_task_scx(),
+ * property is changed (i.e., affinity), since set_next_task_scx(),
* which triggers this callback, may run on a CPU different from
* the task's assigned CPU.
*
@@ -453,7 +513,7 @@ struct sched_ext_ops {
* - sleeping (%SCX_DEQ_SLEEP)
* - being moved to another CPU
* - being temporarily taken off the queue for an attribute change
- * (%SCX_DEQ_SAVE)
+ * (%SCX_DEQ_SCHED_CHANGE)
*
* This and ->dequeue() are related but not coupled. This operation
* notifies @p's state transition and may not be preceded by ->dequeue()
@@ -489,6 +549,11 @@ struct sched_ext_ops {
* the BPF scheduler. Should return %true if @a should run before @b.
* %false if there's no required ordering or @b should run before @a.
*
+ * In a scheduler hierarchy, a pair spanning two schedulers is ordered
+ * by the nearest common ancestor implementing this op, so the op may be
+ * called on tasks that the scheduler delegated to its sub-schedulers
+ * and is not scheduling anymore. See scx_prio_less().
+ *
* If not specified, the default is ordering them according to when they
* became runnable.
*/
@@ -532,6 +597,12 @@ struct sched_ext_ops {
*
* Specify the %SCX_OPS_KEEP_BUILTIN_IDLE flag to keep the built-in idle
* tracking.
+ *
+ * Only actual transitions are reported. A CPU that is claimed with an
+ * idle pick and kicked but dispatches no task returns to idle without a
+ * transition. A scheduler tracking idle CPUs itself must restore the
+ * idle state from ops.dispatch() when it returns without the next task
+ * to run.
*/
void (*update_idle)(s32 cpu, bool idle);
@@ -615,8 +686,19 @@ struct sched_ext_ops {
* @cgrp: cgroup being initialized
* @args: init arguments, see the struct definition
*
- * Either the BPF scheduler is being loaded or @cgrp created, initialize
- * @cgrp for sched_ext. This operation may block.
+ * Initialize @cgrp for sched_ext, delivered to @cgrp's sched either
+ * when the BPF scheduler is being loaded or when @cgrp is created. This
+ * operation may block.
+ *
+ * Cgroup handovers also generate these ops: an enabling sub-scheduler
+ * receives ops.cgroup_init() for every cgroup in its subtree while the
+ * previous sched receives ops.cgroup_exit(), and disabling reverses the
+ * two.
+ *
+ * When the BPF scheduler is being loaded or cgroups are being handed
+ * over, @cgrp may already have been removed by userspace: a removed
+ * cgroup stays schedulable until its dying tasks finish their final
+ * context switches.
*
* Return 0 for success, -errno for failure. An error return while
* loading will abort loading of the BPF scheduler. During cgroup
@@ -629,8 +711,13 @@ struct sched_ext_ops {
* @cgroup_exit: Exit a cgroup
* @cgrp: cgroup being exited
*
- * Either the BPF scheduler is being unloaded or @cgrp destroyed, exit
- * @cgrp for sched_ext. This operation my block.
+ * Exit @cgrp for sched_ext, delivered to the sched whose
+ * ops.cgroup_init() it pairs with, either when the BPF scheduler is
+ * being unloaded or when @cgrp is destroyed. This operation may block.
+ *
+ * For a destroyed @cgrp, delivery follows the last scheduling event on
+ * it: a removed cgroup stays schedulable until its dying tasks finish
+ * their final context switches.
*/
void (*cgroup_exit)(struct cgroup *cgrp);
@@ -643,6 +730,12 @@ struct sched_ext_ops {
* Prepare @p for move from cgroup @from to @to. This operation may
* block and can be used for allocations.
*
+ * The cgroup_move ops are delivered to @p's sched, and only for moves
+ * that don't re-home @p. A re-homing move is reported through
+ * ops.exit_task() and ops.init_task() instead. @from and @to can
+ * reference cgroups the sched never received ops.cgroup_init() for, as
+ * the cpu controller can be coarser than the sub-scheduler topology.
+ *
* Return 0 for success, -errno for failure. An error return aborts the
* migration.
*/
@@ -678,6 +771,11 @@ struct sched_ext_ops {
* @weight: new weight [1..10000]
*
* Update @cgrp's weight to @weight.
+ *
+ * Knobs of a cgroup belong to the parent, so the set_* ops are
+ * delivered to @cgrp's parent's sched. That sched may never have seen
+ * ops.cgroup_init() for @cgrp - at a sub-scheduler attach point, the
+ * parent sched tracks @cgrp through ops.sub_attach() instead.
*/
void (*cgroup_set_weight)(struct cgroup *cgrp, u32 weight);
@@ -689,7 +787,7 @@ struct sched_ext_ops {
* @burst_us: bandwidth control burst
*
* Update @cgrp's bandwidth control parameters. This is from the cpu.max
- * cgroup interface.
+ * cgroup interface. This operation may block.
*
* @quota_us / @period_us determines the CPU bandwidth @cgrp is entitled
* to. For example, if @period_us is 1_000_000 and @quota_us is
@@ -698,6 +796,8 @@ struct sched_ext_ops {
* burst temporarily. The specific control mechanism and thus the
* interpretation of @period_us and burstiness is up to the BPF
* scheduler.
+ *
+ * Delivery follows the same rule as cgroup_set_weight().
*/
void (*cgroup_set_bandwidth)(struct cgroup *cgrp,
u64 period_us, u64 quota_us, u64 burst_us);
@@ -710,6 +810,8 @@ struct sched_ext_ops {
* Update @cgrp's idle state to @idle. This callback is invoked when
* a cgroup transitions between idle and non-idle states, allowing the
* BPF scheduler to adjust its behavior accordingly.
+ *
+ * Delivery follows the same rule as cgroup_set_weight().
*/
void (*cgroup_set_idle)(struct cgroup *cgrp, bool idle);
@@ -729,6 +831,39 @@ struct sched_ext_ops {
*/
void (*sub_detach)(struct scx_sub_detach_args *args);
+ /**
+ * @sub_caps_updated: Caps on this sub-sched's shard changed
+ * @cmask: cids whose caps changed (cmask->base identifies the shard)
+ * @caps: SCX_CAP_* that changed
+ *
+ * Invoked after grant or revoke modifies caps on a shard. There can be
+ * only one in-flight invocation per shard. @cmask and @caps coalesce
+ * all changes since the last delivery. Direction (set vs cleared) isn't
+ * encoded. Query current state with scx_bpf_sub_caps().
+ *
+ * Delivered asynchronously after the change is recorded, and may run
+ * before it takes effect on any given cpu. Use it to track which caps
+ * the sub-sched holds and propagate to its own children, not to decide
+ * if a task can run on a cpu now. sub_ecaps_updated() reports that per
+ * cpu, once it is in effect.
+ *
+ * May call scx_bpf_sub_grant() / scx_bpf_sub_revoke() on children.
+ */
+ void (*sub_caps_updated)(const struct scx_cmask *cmask, u64 caps);
+
+ /**
+ * @sub_ecaps_updated: This sub-sched's effective caps on a cid changed
+ * @cid: the cid whose effective caps changed
+ * @before: effective caps as of the last delivery
+ * @after: effective caps now
+ *
+ * Invoked when this sub-sched's effective caps on @cid change, once the
+ * change is in effect on the cpu. Runs in dispatch context with rq lock
+ * held, and can perform all operations allowed in ops.dispatch()
+ * including inserting/moving tasks.
+ */
+ void (*sub_ecaps_updated)(s32 cid, u64 before, u64 after);
+
/*
* All online ops must come before ops.cpu_online().
*/
@@ -752,10 +887,19 @@ struct sched_ext_ops {
void (*cpu_offline)(s32 cpu);
/*
- * All CPU hotplug ops must come before ops.init().
+ * All CPU hotplug ops must come before ops.init_cids().
*/
/**
+ * @init_cids: Finalize the cid layout (cid-form only)
+ *
+ * Runs after the default cid layout is built, before caps and shards
+ * are finalized. A cid-form scheduler may call scx_bpf_cid_override()
+ * here for a custom layout. Ignored for cpu-form schedulers.
+ */
+ s32 (*init_cids)(void);
+
+ /**
* @init: Initialize the BPF scheduler
*/
s32 (*init)(void);
@@ -809,8 +953,51 @@ struct sched_ext_ops {
u64 hotplug_seq;
/**
- * @cgroup_id: When >1, attach the scheduler as a sub-scheduler on the
- * specified cgroup.
+ * @cid_shard_size: Target number of CIDs per shard
+ *
+ * Shards are contiguous CID ranges used as operation and locking
+ * domains for sub-scheduling. Each LLC is divided into ceil(nr_cpus /
+ * @cid_shard_size) shards, then cores are distributed across them
+ * evenly. If one core has more logical CPUs than @cid_shard_size, its
+ * shard will become larger than @cid_shard_size. Values above
+ * SCX_CID_SHARD_MAX_CPUS are capped. 0 means use the default (24).
+ */
+ u32 cid_shard_size;
+
+ /**
+ * @rescue_bandwidth_ppt: Rescue execution bandwidth in parts per thousand
+ *
+ * The fraction of each CPU's time that may be consumed running tasks
+ * from its rescue DSQ. A higher bandwidth admits and escalates rescues
+ * faster, see @rescue_quantum_us.
+ *
+ * Only the root scheduler's value is used. 0 means the default of 20
+ * (2%). May not exceed 250 (25%). %SCX_RESCUE_DISABLE disables rescue -
+ * %SCX_ENQ_RESCUE inserts are then rejected like any other insert
+ * lacking the caps.
+ */
+ u32 rescue_bandwidth_ppt;
+
+ /**
+ * @rescue_quantum_us: Rescue execution quantum in microseconds
+ *
+ * How much CPU time each rescue gets. Rescues run one at a time per CPU
+ * and admissions are paced to keep rescue execution within
+ * @rescue_bandwidth_ppt - with the defaults, one 5ms rescue every
+ * 250ms. A crowded queue round-robins on the quantum divided across the
+ * waiters, floored at 1ms. A stuck rescue eventually escalates to
+ * forced execution. A larger quantum interrupts the CPU less often but
+ * for longer and spaces rescues further apart.
+ *
+ * Only the root scheduler's value is used. 0 means the default (5000).
+ * Non-zero values must be within [1000, 100000]. Values too short for
+ * the kernel to meter are lifted silently.
+ */
+ u32 rescue_quantum_us;
+
+ /**
+ * @sub_cgroup_id: When >1, attach the scheduler as a sub-scheduler
+ * on the specified cgroup.
*/
u64 sub_cgroup_id;
@@ -818,8 +1005,9 @@ struct sched_ext_ops {
* @name: BPF scheduler's name
*
* Must be a non-zero valid BPF object name including only isalnum(),
- * '_' and '.' chars. Shows up in kernel.sched_ext_ops sysctl while the
- * BPF scheduler is enabled.
+ * '_' and '.' chars. Exposed via the ops file in the scheduler's sysfs
+ * directory, /sys/kernel/sched_ext/root/ops for the root scheduler,
+ * while the BPF scheduler is enabled.
*/
char name[SCX_OPS_NAME_LEN];
@@ -874,9 +1062,11 @@ struct sched_ext_ops {
* - dispatch -> dispatch (cpu arg is now cid)
* - update_idle -> update_idle (cpu arg is now cid)
* - set_cpumask -> set_cmask (cmask instead of cpumask)
+ * - enable -> enable (takes struct scx_enable_args)
* - cpu_online -> cid_online
* - cpu_offline -> cid_offline
* - dump_cpu -> dump_cid
+ * - cgroup_* -> cpuctl_* (they track the cgroup cpu controller)
* - cpu_acquire/cpu_release -> not present (deprecated in sched_ext_ops)
*
* BPF schedulers using this type cannot call cpu-form scx_bpf_* kfuncs;
@@ -900,36 +1090,37 @@ struct sched_ext_ops_cid {
struct task_struct *b);
void (*set_weight)(struct task_struct *p, u32 weight);
void (*set_cmask)(struct task_struct *p,
- const struct scx_cmask *cmask);
+ const struct scx_cmask *cmask__arena);
void (*update_idle)(s32 cid, bool idle);
s32 (*init_task)(struct task_struct *p,
struct scx_init_task_args *args);
void (*exit_task)(struct task_struct *p,
struct scx_exit_task_args *args);
- void (*enable)(struct task_struct *p);
+ void (*enable)(struct task_struct *p, struct scx_enable_args *args);
void (*disable)(struct task_struct *p);
void (*dump)(struct scx_dump_ctx *ctx);
void (*dump_cid)(struct scx_dump_ctx *ctx, s32 cid, bool idle);
void (*dump_task)(struct scx_dump_ctx *ctx, struct task_struct *p);
#ifdef CONFIG_EXT_GROUP_SCHED
- s32 (*cgroup_init)(struct cgroup *cgrp,
- struct scx_cgroup_init_args *args);
- void (*cgroup_exit)(struct cgroup *cgrp);
- s32 (*cgroup_prep_move)(struct task_struct *p,
- struct cgroup *from, struct cgroup *to);
- void (*cgroup_move)(struct task_struct *p,
- struct cgroup *from, struct cgroup *to);
- void (*cgroup_cancel_move)(struct task_struct *p,
- struct cgroup *from, struct cgroup *to);
- void (*cgroup_set_weight)(struct cgroup *cgrp, u32 weight);
- void (*cgroup_set_bandwidth)(struct cgroup *cgrp,
- u64 period_us, u64 quota_us, u64 burst_us);
- void (*cgroup_set_idle)(struct cgroup *cgrp, bool idle);
+ s32 (*cpuctl_init)(struct cgroup *cgrp, struct scx_cgroup_init_args *args);
+ void (*cpuctl_exit)(struct cgroup *cgrp);
+ s32 (*cpuctl_prep_move)(struct task_struct *p, struct cgroup *from,
+ struct cgroup *to);
+ void (*cpuctl_move)(struct task_struct *p, struct cgroup *from, struct cgroup *to);
+ void (*cpuctl_cancel_move)(struct task_struct *p, struct cgroup *from,
+ struct cgroup *to);
+ void (*cpuctl_set_weight)(struct cgroup *cgrp, u32 weight);
+ void (*cpuctl_set_bandwidth)(struct cgroup *cgrp, u64 period_us, u64 quota_us,
+ u64 burst_us);
+ void (*cpuctl_set_idle)(struct cgroup *cgrp, bool idle);
#endif /* CONFIG_EXT_GROUP_SCHED */
s32 (*sub_attach)(struct scx_sub_attach_args *args);
void (*sub_detach)(struct scx_sub_detach_args *args);
+ void (*sub_caps_updated)(const struct scx_cmask *cmask__arena, u64 caps);
+ void (*sub_ecaps_updated)(s32 cid, u64 before, u64 after);
void (*cid_online)(s32 cid);
void (*cid_offline)(s32 cid);
+ s32 (*init_cids)(void);
s32 (*init)(void);
void (*exit)(struct scx_exit_info *info);
@@ -939,6 +1130,9 @@ struct sched_ext_ops_cid {
u32 timeout_ms;
u32 exit_dump_len;
u64 hotplug_seq;
+ u32 cid_shard_size;
+ u32 rescue_bandwidth_ppt;
+ u32 rescue_quantum_us;
u64 sub_cgroup_id;
char name[SCX_OPS_NAME_LEN];
@@ -954,8 +1148,8 @@ enum scx_opi {
SCX_OPI_NORMAL_BEGIN = 0,
SCX_OPI_NORMAL_END = SCX_OP_IDX(cpu_online),
SCX_OPI_CPU_HOTPLUG_BEGIN = SCX_OP_IDX(cpu_online),
- SCX_OPI_CPU_HOTPLUG_END = SCX_OP_IDX(init),
- SCX_OPI_END = SCX_OP_IDX(init),
+ SCX_OPI_CPU_HOTPLUG_END = SCX_OP_IDX(init_cids),
+ SCX_OPI_END = SCX_OP_IDX(init_cids),
};
/*
@@ -1001,15 +1195,13 @@ struct scx_event_stats {
s64 SCX_EV_REENQ_IMMED;
/*
- * The number of times a reenq of local DSQ caused another reenq of
- * local DSQ. This can happen when %SCX_ENQ_IMMED races against a higher
- * priority class task even if the BPF scheduler always satisfies the
- * prerequisites for %SCX_ENQ_IMMED at the time of enqueue. However,
- * that scenario is very unlikely and this count going up regularly
- * indicates that the BPF scheduler is handling %SCX_ENQ_REENQ
- * incorrectly causing recursive reenqueues.
+ * The number of times a reenqueue (%SCX_ENQ_REENQ) led to another
+ * reenqueue without the task running in between. This count climbing
+ * rapidly indicates that the BPF scheduler keeps re-deciding placements
+ * it can't honor. A single task reenqueued more than
+ * %SCX_REENQ_MAX_REPEAT times gets its owning scheduler ejected.
*/
- s64 SCX_EV_REENQ_LOCAL_REPEAT;
+ s64 SCX_EV_REENQ_REPEAT;
/*
* Total number of times a task's time slice was refilled with the
@@ -1018,6 +1210,18 @@ struct scx_event_stats {
s64 SCX_EV_REFILL_SLICE_DFL;
/*
+ * The number of times an out-of-band slice request exceeded the maximum
+ * representable value and was clamped.
+ */
+ s64 SCX_EV_SLICE_CLAMPED;
+
+ /*
+ * The number of times a slice extension was denied because the
+ * scheduler lacked baseline cpu access on the task's cpu.
+ */
+ s64 SCX_EV_SLICE_DENIED;
+
+ /*
* The total duration of bypass modes in nanoseconds.
*/
s64 SCX_EV_BYPASS_DURATION;
@@ -1049,8 +1253,70 @@ struct scx_event_stats {
* from sub_bypass_dsq's.
*/
s64 SCX_EV_SUB_BYPASS_DISPATCH;
+
+ /*
+ * The number of times a migration-disabled task lacking the cap for its
+ * cid was allowed onto the local DSQ. It must run on its pinned CPU, so
+ * it can't be rejected. The violation is counted here.
+ */
+ s64 SCX_EV_SUB_FORCED_ADMIT;
+
+ /*
+ * The number of times a preempting kick was refused because the
+ * sub-sched lacked SCX_CAP_PREEMPT for a task outside its subtree. The
+ * kick degrades to a plain reschedule.
+ */
+ s64 SCX_EV_SUB_PREEMPT_DENIED;
+
+ /*
+ * The number of times a kick was skipped because the sub-sched lacked
+ * baseline access on the target cid. The preempt-part degradation of a
+ * delivered kick is counted in SCX_EV_SUB_PREEMPT_DENIED instead.
+ */
+ s64 SCX_EV_SUB_KICK_DENIED;
+
+ /*
+ * The number of times a local DSQ reenq was dropped because the
+ * sub-sched lacked baseline access on the target cid.
+ */
+ s64 SCX_EV_SUB_REENQ_DENIED;
+
+ /*
+ * The number of times scx_bpf_cidperf_set() was denied because the
+ * sub-sched lacked SCX_CAP_PERF on the target cid.
+ */
+ s64 SCX_EV_SUB_CIDPERF_DENIED;
+
+ /*
+ * The number of times an insert carrying %SCX_ENQ_RESCUE lacked the
+ * caps for its cid and the task entered the rescue path.
+ */
+ s64 SCX_EV_SUB_RESCUE;
};
+#define SCX_EVENTS_LIST(SCX_EVENT) \
+ SCX_EVENT(SCX_EV_SELECT_CPU_FALLBACK); \
+ SCX_EVENT(SCX_EV_DISPATCH_LOCAL_DSQ_OFFLINE); \
+ SCX_EVENT(SCX_EV_DISPATCH_KEEP_LAST); \
+ SCX_EVENT(SCX_EV_ENQ_SKIP_EXITING); \
+ SCX_EVENT(SCX_EV_ENQ_SKIP_MIGRATION_DISABLED); \
+ SCX_EVENT(SCX_EV_REENQ_IMMED); \
+ SCX_EVENT(SCX_EV_REENQ_REPEAT); \
+ SCX_EVENT(SCX_EV_REFILL_SLICE_DFL); \
+ SCX_EVENT(SCX_EV_SLICE_CLAMPED); \
+ SCX_EVENT(SCX_EV_SLICE_DENIED); \
+ SCX_EVENT(SCX_EV_BYPASS_DURATION); \
+ SCX_EVENT(SCX_EV_BYPASS_DISPATCH); \
+ SCX_EVENT(SCX_EV_BYPASS_ACTIVATE); \
+ SCX_EVENT(SCX_EV_INSERT_NOT_OWNED); \
+ SCX_EVENT(SCX_EV_SUB_BYPASS_DISPATCH); \
+ SCX_EVENT(SCX_EV_SUB_FORCED_ADMIT); \
+ SCX_EVENT(SCX_EV_SUB_PREEMPT_DENIED); \
+ SCX_EVENT(SCX_EV_SUB_KICK_DENIED); \
+ SCX_EVENT(SCX_EV_SUB_REENQ_DENIED); \
+ SCX_EVENT(SCX_EV_SUB_CIDPERF_DENIED); \
+ SCX_EVENT(SCX_EV_SUB_RESCUE)
+
struct scx_sched;
enum scx_sched_pcpu_flags {
@@ -1062,6 +1328,8 @@ struct scx_dsp_buf_ent {
struct task_struct *task;
unsigned long qseq;
u64 dsq_id;
+ u64 slice;
+ u64 vtime;
u64 enq_flags;
};
@@ -1075,8 +1343,6 @@ struct scx_dsp_ctx {
struct scx_deferred_reenq_local {
struct list_head node;
u64 flags;
- u64 seq;
- u32 cnt;
};
struct scx_sched_pcpu {
@@ -1084,6 +1350,49 @@ struct scx_sched_pcpu {
u64 flags; /* protected by rq lock */
/*
+ * Kick state owned by this cpu for this sched. scx_kick_cpu() records
+ * targets here and links @to_kick_node onto the cpu's
+ * rq->scx.sched_pcpus_to_kick. The cpu's single kick irq_work walks
+ * that list and kicks each sched's targets on its behalf. Per-sched so
+ * a kick stays attributed to its scheduler.
+ */
+ cpumask_var_t cpus_to_kick;
+ cpumask_var_t cpus_to_kick_if_idle;
+ cpumask_var_t cpus_to_preempt;
+ cpumask_var_t cpus_to_wait;
+ struct list_head to_kick_node;
+
+#ifdef CONFIG_EXT_SUB_SCHED
+ /*
+ * pshard->caps[cap_bit] is the set of cids the sched holds that one
+ * cap on. ecaps is its transpose: the set of SCX_CAP_* bits the sched
+ * effectively holds on this cpu, with implied caps folded in, so that
+ * the hot-path check is a single read.
+ *
+ * While pshard->caps[] under pshard->lock is the target configuration,
+ * ecaps is the effective copy owned by the cpu. It is written under the
+ * rq lock while processing rq->ecaps_to_sync. Can also be read with
+ * READ_ONCE() outside rq lock.
+ *
+ * See queue_sync_ecaps() and scx_process_sync_ecaps().
+ */
+ u64 ecaps;
+ struct llist_node ecaps_to_sync_node;
+ /* owed a forced update_idle() re-notify on this cpu */
+ bool idle_renotify;
+ /* effective caps as of the last sub_ecaps_updated() delivery */
+ u64 reported_ecaps;
+
+ /*
+ * Decaying rescue runtime consumed on this cpu, see
+ * scx_rescue_decay_avg(). Overload on this cpu ejects the sub with the
+ * largest value. Accessed only under this cpu's rq lock.
+ */
+ u64 rescue_avg;
+ u64 rescue_avg_at; /* last decay, jiffies_64 */
+#endif
+
+ /*
* The event counters are in a per-CPU variable to minimize the
* accounting overhead. A system-wide view on the event counter is
* constructed when requested by scx_bpf_events().
@@ -1104,19 +1413,161 @@ struct scx_sched_pnode {
struct scx_dispatch_q global_dsq;
};
+/*
+ * Sub-sched capability delegation.
+ *
+ * Caps are per-cid permissions parents delegate to direct children via
+ * scx_bpf_sub_grant() / scx_bpf_sub_revoke(). A child's cap set is always a
+ * subset of its parent's. A sub-sched checks its caps locally, and cross-sched
+ * communication is needed only when the delegation set itself changes.
+ *
+ * Caps are used to implement sub-sched scheduling on the enqueue path. Picking
+ * a cid for a task at a leaf depends on which cids the leaf is allowed to use.
+ * Resolving that programmatically on every enqueue would mean a cross-sched
+ * round-trip call chain, possibly retrying if the request can't be granted
+ * as-is.
+ *
+ * The dispatch path is different - it runs as top-down recursion via
+ * scx_bpf_sub_dispatch(): a sched's dispatch op invokes a child's dispatch op
+ * on the local rq, and the subtree dispatches in a single pass.
+ *
+ * Locking is per shard. cid space is split into shards, and each sub-sched has
+ * its own pshard->lock for each shard. Operations are broken up on shard
+ * boundaries. Different shards never contend. Shards are expected to be
+ * topology-aligned and likely to serve as the locality unit when cids are
+ * allocated to schedulers, so per-shard lock granularity scales naturally with
+ * the allocation pattern.
+ *
+ * ENQ_IMMED insert an IMMED task onto the cid's local DSQ
+ * - kick the cid's cpu (except SCX_KICK_PREEMPT)
+ *
+ * ENQ insert any task onto the cid's local DSQ (implies ENQ_IMMED)
+ *
+ * PREEMPT preempt any task running on the cid regardless of the owning
+ * sched (implies ENQ). Preempting a task in the sched's own subtree
+ * doesn't require any cap.
+ * - SCX_ENQ_PREEMPT inserts
+ * - SCX_KICK_PREEMPT kicks
+ *
+ * PERF control the cid's cpu power/perf management state, currently the
+ * cpufreq target set through scx_bpf_cidperf_set(). Hardware
+ * control is a separate axis from queue access: PERF neither
+ * implies nor is implied by the caps above.
+ *
+ * Implied caps apply to the holder's own use of a cid, not to delegation.
+ * scx_bpf_sub_grant() delegates literally-held caps, so a cap held only through
+ * implication is usable but cannot be re-delegated to a child. When granting a
+ * cap, it usually makes sense to delegate its implied caps explicitly alongside
+ * it.
+ */
+enum scx_cap_flags {
+ __SCX_CAP_ENQ_IMMED = 0,
+ __SCX_CAP_ENQ = 1,
+ __SCX_CAP_PREEMPT = 2,
+ __SCX_CAP_PERF = 3,
+
+ __SCX_NR_CAPS,
+ __SCX_CAP_ALL = BIT_U64(__SCX_NR_CAPS) - 1,
+
+ SCX_CAP_ENQ_IMMED = BIT_U64(__SCX_CAP_ENQ_IMMED),
+ SCX_CAP_ENQ = BIT_U64(__SCX_CAP_ENQ),
+ SCX_CAP_PREEMPT = BIT_U64(__SCX_CAP_PREEMPT),
+ SCX_CAP_PERF = BIT_U64(__SCX_CAP_PERF),
+
+ /* alias for minimal cap to make any use of a cpu */
+ SCX_CAP_BASE = SCX_CAP_ENQ_IMMED,
+
+ /* caps whose loss strands queued tasks, see scx_process_sync_ecaps() */
+ SCX_CAPS_REENQ_ON_LOSS = SCX_CAP_ENQ_IMMED | SCX_CAP_ENQ,
+};
+
+#ifdef CONFIG_EXT_SUB_SCHED
+/* iterate set bits in a u64 cap mask */
+#define scx_for_each_cap_bit(cap_bit, caps) \
+ for (u64 __caps = (caps); \
+ __caps && ((cap_bit) = __ffs64(__caps), true); \
+ __caps &= __caps - 1)
+
+/*
+ * Sub-cap update notifier.
+ *
+ * ops_cid.sub_caps_updated() notifies sub-scheds when their cap state changes
+ * so they can refresh internal state without polling scx_bpf_sub_caps() per
+ * enqueue.
+ *
+ * Three constraints shape the design:
+ *
+ * 1. Static memory. Deliveries use a fixed-size buffer, both for runtime
+ * efficiency and so notifications can't be lost under memory pressure.
+ *
+ * 2. High-frequency updates. Grant/revoke can mutate caps in bursts, and the
+ * notifier path must absorb that without amplifying it.
+ *
+ * 3. Recursive grant/revoke from the callback. A child receiving a
+ * notification can call grant/revoke on its own children, which can
+ * cascade recursively down its subtree.
+ *
+ * (1) and (2) lead to coalescing into a fixed payload. Each delivery carries a
+ * single (cmask, caps) pair covering every change since the previous one.
+ * Direction (set vs cleared) isn't encoded as it doesn't fit in the fixed-size
+ * summary. The callback queries scx_bpf_sub_caps() for current state. Only one
+ * delivery is in flight per shard. Further changes fold into the same buffer
+ * and ship as the next callback, so a shard's callbacks fire in order.
+ *
+ * (3) leads to deferred delivery. Events accumulate during grant/revoke and are
+ * delivered after the shard lock is released.
+ */
+struct scx_caps_updated {
+ raw_spinlock_t lock;
+ u64 caps;
+ struct scx_cmask *cmask_arena_out;
+ struct list_head node_in_flight;
+ /* Kernel-side accumulator. Access as &cu->cmask. */
+ TRAILING_OVERLAP(struct scx_cmask, cmask, bits,
+ u64 _bits[SCX_CMASK_NR_WORDS(SCX_CID_SHARD_MAX_CPUS)];
+ );
+};
+
+struct scx_pshard {
+ raw_spinlock_t lock; /* serializes caps */
+ struct scx_sched *sch; /* backpointer */
+ struct scx_caps_updated caps_updated;
+
+ /*
+ * Per-cap cmask, inline via TRAILING_OVERLAP so cmask.bits[] overlaps
+ * the trailing _bits[] storage. Access as &caps[i].cmask. See
+ * scx_sched_pcpu->ecaps.
+ */
+ TRAILING_OVERLAP(struct scx_cmask, cmask, bits,
+ u64 _bits[SCX_CMASK_NR_WORDS(SCX_CID_SHARD_MAX_CPUS)];
+ ) caps[__SCX_NR_CAPS];
+
+ /*
+ * Shard geometry captured at alloc. cmask_arena_out's own header is
+ * bpf-writable and the live shard range can change before the
+ * rcu-deferred free, so re-init and size cmask_arena_out from these
+ * trusted copies instead.
+ */
+ u32 base;
+ u32 nr_cids;
+};
+#endif
+
struct scx_sched {
/*
* cpu-form and cid-form ops share field offsets up to .priv (verified
* by BUILD_BUG_ON in scx_init()). The anonymous union lets the kernel
* access either view of the same storage without function-pointer
* casts: use .ops for cpu-form and shared fields, .ops_cid for the
- * cid-renamed callbacks (set_cmask, select_cid, cid_online, ...).
+ * callbacks whose cid-form signature differs (set_cmask, enable,
+ * select_cid, cid_online, ...).
*/
union {
struct sched_ext_ops ops;
struct sched_ext_ops_cid ops_cid;
};
bool is_cid_type; /* true if registered via bpf_sched_ext_ops_cid */
+ bool dead; /* set after ops.exit(), gates scx_prog_sched() */
/*
* Arena map auto-discovered from member progs at struct_ops attach.
@@ -1125,19 +1576,19 @@ struct scx_sched {
*
* @arena_pool sub-allocates @arena_map. Each gen_pool chunk is added
* at the kernel-side mapping address. @arena_kern_base is the start
- * of the arena's kern_vm range. See scx_arena_to_kaddr() and
- * scx_kaddr_to_arena().
+ * of the arena's kern_vm range. See scx_arena_to_kaddr().
*/
struct bpf_map *arena_map;
struct gen_pool *arena_pool;
uintptr_t arena_kern_base;
/*
- * Per-CPU arena cmask used by scx_call_op_set_cpumask() to hand a cmask
- * to ops_cid.set_cmask(). The kernel writes through the stored kern_va
- * and hands BPF its arena pointer via scx_kaddr_to_arena().
+ * Per-CPU arena cmask the kernel fills from a task's cpumask and hands
+ * to ops_cid.enable() and ops_cid.set_cmask(). The stored pointers are
+ * the kernel addresses.
*/
struct scx_cmask * __percpu *set_cmask_scratch;
+ struct scx_cmask *online_cmask;
DECLARE_BITMAP(has_op, SCX_OPI_END);
@@ -1151,13 +1602,16 @@ struct scx_sched {
*/
struct rhashtable dsq_hash;
struct scx_sched_pnode **pnode;
+#ifdef CONFIG_EXT_SUB_SCHED
+ struct scx_pshard **pshard; /* indexed by shard_idx */
+#endif
struct scx_sched_pcpu __percpu *pcpu;
u64 slice_dfl;
u64 bypass_timestamp;
s32 bypass_depth;
- /* bypass dispatch path enable state, see bypass_dsp_enabled() */
+ /* bypass dispatch path enable state, see scx_bypass_dsp_enabled() */
unsigned long bypass_dsp_claim;
atomic_t bypass_dsp_enable_depth;
@@ -1166,16 +1620,28 @@ struct scx_sched {
u32 dsp_max_batch;
s32 level;
+#ifdef CONFIG_EXT_SUB_SCHED
+ /*
+ * pshard[] size captured at enable for the async RCU free path -
+ * scx_nr_cid_shards may be rewritten by a later enable's
+ * scx_cid_publish_tables() before free runs. While sch is active, use
+ * the global.
+ */
+ u32 nr_pshards;
+#endif
+
/*
* Updates to the following warned bitfields can race causing RMW issues
* but it doesn't really matter.
*/
bool warned_zero_slice:1;
- bool warned_deprecated_rq:1;
bool warned_unassoc_progs:1;
struct list_head all;
+ /* unique instance id, monotonic and never reused */
+ u64 id;
+
#ifdef CONFIG_EXT_SUB_SCHED
struct rhash_head hash_node;
@@ -1185,6 +1651,7 @@ struct scx_sched {
char *cgrp_path;
struct kset *sub_kset;
+ bool linked; /* on ->children, see scx_link_sched() */
bool sub_attached;
#endif /* CONFIG_EXT_SUB_SCHED */
@@ -1203,9 +1670,11 @@ struct scx_sched {
struct kthread_worker *helper;
struct irq_work disable_irq_work;
struct kthread_work disable_work;
+ struct irq_work propagate_exit_irq_work; /* see scx_claim_exit() */
struct timer_list bypass_lb_timer;
cpumask_var_t bypass_lb_donee_cpumask;
cpumask_var_t bypass_lb_resched_cpumask;
+ cpumask_var_t stall_cpus;
struct rcu_work rcu_work;
/* all ancestors including self */
@@ -1228,13 +1697,16 @@ static inline void *scx_arena_to_kaddr(struct scx_sched *sch, const void *bpf_pt
}
/**
- * scx_kaddr_to_arena - Translate a kernel arena address to its BPF form
+ * scx_kaddr_to_arena - Translate a kernel arena address to the BPF form
* @sch: scheduler whose arena hosts @kaddr
- * @kaddr: kernel-side arena address, supplied by trusted kernel code
+ * @kaddr: kernel address inside @sch's arena
+ *
+ * __arena callback arguments need no translation. Addresses handed to BPF any
+ * other way, such as struct fields and kfunc return values, go through this.
*/
-static inline void *scx_kaddr_to_arena(struct scx_sched *sch, const void *kaddr)
+static inline uintptr_t scx_kaddr_to_arena(struct scx_sched *sch, const void *kaddr)
{
- return (void *)((uintptr_t)kaddr - sch->arena_kern_base);
+ return (uintptr_t)kaddr - sch->arena_kern_base;
}
enum scx_wake_flags {
@@ -1279,6 +1751,17 @@ enum scx_enq_flags {
SCX_ENQ_IMMED = 1LLU << 33,
/*
+ * Only allowed on local DSQs. If the insert lacks the caps for the
+ * target cid, divert the task to the CPU's rescue path instead of
+ * rejecting and reenqueueing, e.g. when the task's affinity is
+ * restricted to cids the scheduler doesn't hold. The kernel runs
+ * rescued tasks on the target CPU. Rescue execution is guaranteed to
+ * make forward progress and is bandwidth-limited, see the
+ * rescue_bandwidth_ppt and rescue_quantum_us ops fields.
+ */
+ SCX_ENQ_RESCUE = 1LLU << 34,
+
+ /*
* The task being enqueued was previously enqueued on a DSQ, but was
* removed and is being re-enqueued. See SCX_TASK_REENQ_* flags to find
* out why a given task is being reenqueued.
@@ -1303,6 +1786,9 @@ enum scx_enq_flags {
SCX_ENQ_DSQ_PRIQ = 1LLU << 57,
SCX_ENQ_NESTED = 1LLU << 58,
SCX_ENQ_GDSQ_FALLBACK = 1LLU << 59, /* fell back to global DSQ */
+ SCX_ENQ_IGNORE_CAPS = 1LLU << 60, /* admit to local DSQ ignoring caps */
+ SCX_ENQ_APPLY_SLICE = 1LLU << 61, /* apply carried slice/vtime at insertion */
+ SCX_ENQ_SLICE_DFL = 1LLU << 62, /* carried slice is a default refill */
};
enum scx_deq_flags {
@@ -1329,6 +1815,9 @@ enum scx_reenq_flags {
/* low 16bits determine which tasks should be reenqueued */
SCX_REENQ_ANY = 1LLU << 0, /* all tasks */
+ /* internal: kernel-issued on cap revoke, not accepted from BPF */
+ SCX_REENQ_CAP_REVOKE = 1LLU << 1,
+
__SCX_REENQ_FILTER_MASK = 0xffffLLU,
__SCX_REENQ_USER_MASK = SCX_REENQ_ANY,
@@ -1374,6 +1863,7 @@ enum scx_kick_flags {
enum scx_tg_flags {
SCX_TG_ONLINE = 1U << 0,
SCX_TG_INITED = 1U << 1,
+ SCX_TG_SUB_INIT = 1U << 2, /* see scx_cgroup_claim_subtree() */
};
enum scx_enable_state {
@@ -1469,21 +1959,24 @@ static const char *scx_enable_state_str[] = {
* The sched_ext core uses a "lock dancing" protocol coordinated by
* p->scx.holding_cpu. When moving a task to a different rq:
*
- * 1. Verify task can be moved (CPU affinity, migration_disabled, etc.)
- * 2. Set p->scx.holding_cpu to the current CPU
- * 3. Set task state to %SCX_OPSS_NONE; dequeue waits while DISPATCHING
+ * 1. Set p->scx.holding_cpu to the current CPU
+ * 2. Set task state to %SCX_OPSS_NONE; dequeue waits while DISPATCHING
* is set, so clearing DISPATCHING first prevents the circular wait
* (safe to lock the rq we need)
- * 4. Unlock the current CPU's rq
- * 5. Lock src_rq (where the task currently lives)
- * 6. Verify p->scx.holding_cpu == current CPU, if not, dequeue won the
+ * 3. Unlock the current CPU's rq
+ * 4. Lock src_rq (where the task currently lives)
+ * 5. Verify p->scx.holding_cpu == current CPU, if not, dequeue won the
* race (dequeue clears holding_cpu to -1 when it takes the task), in
* this case migration is aborted
- * 7. If src_rq == dst_rq: clear holding_cpu and enqueue directly
+ * 6. If src_rq == dst_rq: clear holding_cpu and enqueue directly
* into dst_rq's local DSQ (no lock swap needed)
- * 8. Otherwise: call move_remote_task_to_local_dsq(), which releases
- * src_rq, locks dst_rq, and performs the deactivate/activate
- * migration cycle (dst_rq is held on return)
+ * 7. Otherwise, verify under src_rq lock that the task can be moved to dst_rq
+ * (CPU affinity, migration_disabled, etc.). If not, clear holding_cpu,
+ * leave the task on src_rq, and enqueue it on the fallback DSQ.
+ * 8. Otherwise (i.e. if the task can be moved to dst_rq), call
+ * move_remote_task_to_local_dsq(), which releases src_rq, locks dst_rq,
+ * and performs the deactivate/activate migration cycle
+ * (dst_rq is held on return)
* 9. Unlock dst_rq and re-lock the current CPU's rq to restore
* the lock state expected by the caller
*
@@ -1505,6 +1998,11 @@ enum scx_ops_state {
* dequeue/requeue, the dispatcher can tell whether it still has a claim
* on the task being dispatched.
*
+ * QSEQ is generated from the per-task p->scx.ops_qseq counter so that
+ * it doesn't repeat across QUEUED instances of the same task even if
+ * the task moves between rqs. 0 is never used as a valid QSEQ since
+ * NONE and DISPATCHING map to this value.
+ *
* As some 32bit archs can't do 64bit store_release/load_acquire,
* p->scx.ops_state is atomic_long_t which leaves 30 bits for QSEQ on
* 32bit machines. The dispatch race window QSEQ protects is very narrow
@@ -1517,6 +2015,68 @@ enum scx_ops_state {
#define SCX_OPSS_STATE_MASK ((1LU << SCX_OPSS_QSEQ_SHIFT) - 1)
#define SCX_OPSS_QSEQ_MASK (~SCX_OPSS_STATE_MASK)
+/*
+ * SCX task iterator.
+ */
+struct scx_task_iter {
+ struct sched_ext_entity cursor;
+ struct task_struct *locked_task;
+ struct rq *rq;
+ struct rq_flags rf;
+ u32 cnt;
+ bool list_locked;
+#ifdef CONFIG_EXT_SUB_SCHED
+ struct cgroup *cgrp;
+ struct cgroup_subsys_state *css_pos;
+ struct css_task_iter css_iter;
+#endif
+};
+
+/*
+ * scx_enable() is offloaded to a dedicated system-wide RT kthread to avoid
+ * starvation. During the READY -> ENABLED task switching loop, the calling
+ * thread's sched_class gets switched from fair to ext. As fair has higher
+ * priority than ext, the calling thread can be indefinitely starved under
+ * fair-class saturation, leading to a system hang.
+ */
+struct scx_enable_cmd {
+ struct kthread_work work;
+ union {
+ struct sched_ext_ops *ops;
+ struct sched_ext_ops_cid *ops_cid;
+ };
+ bool is_cid_type;
+ struct bpf_map *arena_map; /* arena ref to transfer to sch */
+ int ret;
+};
+
+/* string formatting from BPF */
+struct scx_bstr_buf {
+ u64 data[MAX_BPRINTF_VARARGS];
+ char line[SCX_EXIT_MSG_LEN];
+};
+
+/* Internal helper for DEFINE_SCX_COMPAT_MARKER(). */
+#define DECLARE_SCX_COMPAT_MARKER(func) \
+ extern void scx_compat_marker_##func(void)
+
+/**
+ * DEFINE_SCX_COMPAT_MARKER() - define a userspace capability marker
+ * @func: marker suffix; the defined symbol is scx_compat_marker_@func
+ *
+ * Emit an empty, callerless function that is retained in the kernel's BTF.
+ * Its presence is part of the kernel<->userspace contract: userspace probes
+ * scx_compat_marker_@func (e.g. via BTF) to detect that this kernel supports
+ * the corresponding feature.
+ *
+ * The leading declaration suppresses the missing-prototype warning; the
+ * trailing declaration consumes the semicolon at the use site.
+ */
+#define DEFINE_SCX_COMPAT_MARKER(func) \
+ DECLARE_SCX_COMPAT_MARKER(func); \
+ __used __retain void scx_compat_marker_##func(void) {} \
+ DECLARE_SCX_COMPAT_MARKER(func)
+
extern struct scx_sched __rcu *scx_root;
DECLARE_PER_CPU(struct rq *, scx_locked_rq_state);
@@ -1537,12 +2097,159 @@ __printf(5, 0) bool scx_vexit(struct scx_sched *sch, enum scx_exit_kind kind,
__printf(5, 6) bool __scx_exit(struct scx_sched *sch, enum scx_exit_kind kind,
s64 exit_code, s32 exit_cpu, const char *fmt, ...);
+u32 scx_get_task_state(const struct task_struct *p);
+void scx_set_task_state(struct task_struct *p, u32 state);
+void scx_task_iter_start(struct scx_task_iter *iter, struct cgroup *cgrp);
+void scx_task_iter_unlock(struct scx_task_iter *iter);
+void scx_task_iter_stop(struct scx_task_iter *iter);
+struct task_struct *scx_task_iter_next_locked(struct scx_task_iter *iter);
+bool scx_set_task_slice(struct task_struct *p, u64 slice);
+void scx_task_slice_ended(struct rq *rq, struct task_struct *p);
+void scx_task_unlink_from_dsq(struct task_struct *p, struct scx_dispatch_q *dsq);
+void scx_dispatch_dequeue(struct rq *rq, struct task_struct *p);
+void scx_do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags,
+ int sticky_cpu);
+void scx_move_local_task_to_local_dsq(struct scx_sched *sch, struct task_struct *p,
+ u64 enq_flags, struct rq *dst_rq);
+bool scx_consume_dispatch_q(struct scx_sched *sch, struct rq *rq,
+ struct scx_dispatch_q *dsq, u64 enq_flags);
+bool scx_consume_global_dsq(struct scx_sched *sch, struct rq *rq);
+bool scx_rq_online(struct rq *rq);
+void scx_flush_dispatch_buf(struct scx_sched *sch, struct rq *rq);
+s32 scx_init_dsq(struct scx_dispatch_q *dsq, u64 dsq_id, struct scx_sched *sch);
+__printf(2, 3) void scx_dump_line(struct seq_buf *s, const char *fmt, ...);
+void scx_kick_cpu(struct scx_sched *sch, s32 cpu, u64 flags);
+u64 __scx_bpf_now(struct rq *rq);
+void schedule_dsq_reenq(struct scx_sched *sch, struct scx_dispatch_q *dsq,
+ u64 reenq_flags, struct rq *locked_rq);
+void scx_reenq_wait_dispatching(struct task_struct *p);
+int __scx_init_task(struct scx_sched *sch, struct task_struct *p,
+ struct cgroup *cgrp, bool fork);
+void scx_enable_task(struct scx_sched *sch, struct task_struct *p);
+void __scx_disable_and_exit_task(struct scx_sched *sch, struct task_struct *p);
+void scx_sub_init_cancel_task(struct scx_sched *sch, struct task_struct *p);
+void scx_disable_and_exit_task(struct scx_sched *sch, struct task_struct *p);
+#if defined(CONFIG_EXT_GROUP_SCHED) || defined(CONFIG_EXT_SUB_SCHED)
+void scx_cgroup_lock(void);
+void scx_cgroup_unlock(void);
+#endif
+s32 scx_alloc_kern_arena_objs(struct scx_sched *sch);
+void scx_disable_bypass_dsp(struct scx_sched *sch);
+void scx_bypass(struct scx_sched *sch, bool bypass);
+s32 scx_link_sched(struct scx_sched *sch);
+void scx_unlink_sched(struct scx_sched *sch);
+void scx_disable_dump(struct scx_sched *sch);
+void scx_log_sched_disable(struct scx_sched *sch);
+void scx_flush_disable_work(struct scx_sched *sch);
+struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd,
+ struct cgroup *cgrp,
+ struct scx_sched *parent);
+int scx_validate_ops(struct scx_sched *sch, const struct sched_ext_ops *ops);
+int scx_sched_sysfs_add(struct scx_sched *sch);
+bool scx_is_descendant(struct scx_sched *sch, struct scx_sched *ancestor);
+__printf(5, 0) bool scx_exit_bstr(struct scx_sched *sch, enum scx_exit_kind kind,
+ s64 exit_code, struct scx_sched *fmt_blame,
+ char *fmt, unsigned long long *data, u32 data__sz);
+
+extern raw_spinlock_t scx_sched_lock;
+extern struct mutex scx_enable_mutex;
+extern struct percpu_rw_semaphore scx_fork_rwsem;
+extern bool scx_cgroup_enabled;
+extern struct list_head scx_sched_all;
+#ifdef CONFIG_EXT_SUB_SCHED
+extern const struct rhashtable_params scx_sched_hash_params;
+extern struct rhashtable scx_sched_hash;
+extern struct scx_sched *scx_enabling_sub_sched;
+#endif
+
#define scx_exit(sch, kind, exit_code, fmt, args...) \
__scx_exit(sch, kind, exit_code, raw_smp_processor_id(), fmt, ##args)
#define scx_error(sch, fmt, args...) \
scx_exit((sch), SCX_EXIT_ERROR, 0, fmt, ##args)
-#define scx_verror(sch, fmt, args) \
- scx_vexit((sch), SCX_EXIT_ERROR, 0, raw_smp_processor_id(), fmt, args)
+
+/**
+ * scx_root_protected_live - Root sched for paths that only run while live
+ *
+ * scx_root is published before the scheduler goes live and cleared only after
+ * it is fully drained, so a path that only executes while the scheduler is live
+ * can never race an update. Return the root sched with a plain load, never
+ * %NULL.
+ */
+static inline struct scx_sched *scx_root_protected_live(void)
+{
+ return rcu_dereference_protected(scx_root, true);
+}
+
+/**
+ * scx_root_protected - Root sched for contexts that exclude its updates
+ *
+ * Both scx_root updates run under the locks checked below, so holding one
+ * excludes them. Return the root sched with a plain load, %NULL if no scheduler
+ * is loaded.
+ */
+static inline struct scx_sched *scx_root_protected(void)
+{
+ return rcu_dereference_protected(scx_root,
+ lockdep_is_cpus_held() ||
+ lockdep_is_held(&scx_enable_mutex));
+}
+
+static inline struct scx_dispatch_q *scx_bypass_dsq(struct scx_sched *sch, s32 cpu)
+{
+ return &per_cpu_ptr(sch->pcpu, cpu)->bypass_dsq;
+}
+
+/**
+ * scx_bypass_dsp_enabled - Check if bypass dispatch path is enabled
+ * @sch: scheduler to check
+ *
+ * When a descendant scheduler enters bypass mode, bypassed tasks are scheduled
+ * by the nearest non-bypassing ancestor, or the root scheduler if all ancestors
+ * are bypassing. In the former case, the ancestor is not itself bypassing but
+ * its bypass DSQs will be populated with bypassed tasks from descendants. Thus,
+ * the ancestor's bypass dispatch path must be active even though its own
+ * bypass_depth remains zero.
+ *
+ * This function checks bypass_dsp_enable_depth which is managed separately from
+ * bypass_depth to enable this decoupling. See enable_bypass_dsp() and
+ * scx_disable_bypass_dsp().
+ */
+static inline bool scx_bypass_dsp_enabled(struct scx_sched *sch)
+{
+ return unlikely(atomic_read(&sch->bypass_dsp_enable_depth));
+}
+
+/**
+ * scx_ops_sanitize_err - Sanitize a -errno value
+ * @sch: scx_sched to error out on error
+ * @ops_name: operation to blame on failure
+ * @err: -errno value to sanitize
+ *
+ * Verify @err is a valid -errno. If not, trigger scx_error() and return
+ * -%EPROTO. This is necessary because returning a rogue -errno up the chain can
+ * cause misbehaviors. For an example, a large negative return from
+ * ops.init_task() triggers an oops when passed up the call chain because the
+ * value fails IS_ERR() test after being encoded with ERR_PTR() and then is
+ * handled as a pointer.
+ */
+static inline int scx_ops_sanitize_err(struct scx_sched *sch, const char *ops_name, s32 err)
+{
+ if (err < 0 && err >= -MAX_ERRNO)
+ return err;
+
+ scx_error(sch, "ops.%s() returned an invalid errno %d", ops_name, err);
+ return -EPROTO;
+}
+
+static inline void scx_schedule_reenq_local(struct rq *rq, u64 reenq_flags)
+{
+ struct scx_sched *root = rcu_dereference_sched(scx_root);
+
+ if (WARN_ON_ONCE(!root))
+ return;
+
+ schedule_dsq_reenq(root, &rq->scx.local_dsq, reenq_flags, rq);
+}
/*
* Return the rq currently locked from an scx callback, or NULL if no rq is
@@ -1570,8 +2277,11 @@ static inline void update_locked_rq(struct rq *rq)
/*
* SCX ops can recurse via scx_bpf_sub_dispatch() - the inner call must not
* clobber the outer's scx_locked_rq_state. Save it on entry, restore on exit.
+ *
+ * @ops is the ops table to dispatch through: ops for the cpu form, ops_cid
+ * for the cid form.
*/
-#define SCX_CALL_OP(sch, op, locked_rq, args...) \
+#define __SCX_CALL_OP(sch, ops, op, locked_rq, args...) \
do { \
struct rq *__prev_locked_rq; \
\
@@ -1584,6 +2294,9 @@ do { \
update_locked_rq(__prev_locked_rq); \
} while (0)
+#define SCX_CALL_OP(sch, op, locked_rq, args...) \
+ __SCX_CALL_OP(sch, ops, op, locked_rq, ##args)
+
#define SCX_CALL_OP_RET(sch, op, locked_rq, args...) \
({ \
struct rq *__prev_locked_rq; \
@@ -1615,17 +2328,36 @@ do { \
* WARN_ON_ONCE() in each macro catches a re-entry of any of the three variants
* while a previous one is still in progress.
*/
-#define SCX_CALL_OP_TASK(sch, op, locked_rq, task, args...) \
+#define __SCX_CALL_OP_TASK(sch, ops, op, locked_rq, task, args...) \
do { \
WARN_ON_ONCE(current->scx.kf_tasks[0]); \
current->scx.kf_tasks[0] = task; \
- SCX_CALL_OP((sch), op, locked_rq, task, ##args); \
+ __SCX_CALL_OP((sch), ops, op, locked_rq, task, ##args); \
current->scx.kf_tasks[0] = NULL; \
} while (0)
+/*
+ * A per-task op runs on @task's owner - WARN if @sch isn't it. Sites that must
+ * target a different scheduler call __SCX_CALL_OP_TASK() directly.
+ */
+#define SCX_CALL_OP_TASK(sch, op, locked_rq, task, args...) \
+do { \
+ WARN_ON_ONCE(scx_has_subs() && (sch) != scx_task_sched_rcu(task)); \
+ __SCX_CALL_OP_TASK((sch), ops, op, locked_rq, task, ##args); \
+} while (0)
+
+/*
+ * Dispatch a task op through the cid-form ops_cid table, for the ops whose
+ * cid-form signature differs from the cpu-form slot: set_cmask() takes an arena
+ * cmask instead of a cpumask and enable() takes scx_enable_args.
+ */
+#define SCX_CALL_CID_OP_TASK(sch, op, locked_rq, task, args...) \
+ __SCX_CALL_OP_TASK(sch, ops_cid, op, locked_rq, task, ##args)
+
#define SCX_CALL_OP_TASK_RET(sch, op, locked_rq, task, args...) \
({ \
__typeof__((sch)->ops.op(task, ##args)) __ret; \
+ WARN_ON_ONCE(scx_has_subs() && (sch) != scx_task_sched_rcu(task)); \
WARN_ON_ONCE(current->scx.kf_tasks[0]); \
current->scx.kf_tasks[0] = task; \
__ret = SCX_CALL_OP_RET((sch), op, locked_rq, task, ##args); \
@@ -1665,6 +2397,19 @@ static inline bool scx_bypassing(struct scx_sched *sch, s32 cpu)
}
#ifdef CONFIG_EXT_SUB_SCHED
+DECLARE_STATIC_KEY_FALSE(__scx_has_subs);
+
+/**
+ * scx_has_subs - Whether any sub-scheduler exists
+ *
+ * Gates the sub-sched portions of hot paths so that a root-only system doesn't
+ * pay for them. See scx_sub_enable_workfn() and scx_sched_free_rcu_work().
+ */
+static inline bool scx_has_subs(void)
+{
+ return static_branch_unlikely(&__scx_has_subs);
+}
+
/**
* scx_task_sched - Find scx_sched scheduling a task
* @p: task of interest
@@ -1714,14 +2459,20 @@ static inline bool scx_task_on_sched(struct scx_sched *sch,
static inline struct scx_sched *scx_prog_sched(const struct bpf_prog_aux *aux)
{
struct sched_ext_ops *ops;
- struct scx_sched *root;
+ struct scx_sched *sch, *root;
ops = bpf_prog_get_assoc_struct_ops(aux);
- if (likely(ops))
- return rcu_dereference_all(ops->priv);
+ if (likely(ops)) {
+ sch = rcu_dereference_all(ops->priv);
+ if (sch && unlikely(READ_ONCE(sch->dead)))
+ return NULL;
+ return sch;
+ }
root = rcu_dereference_all(scx_root);
if (root) {
+ if (unlikely(READ_ONCE(root->dead)))
+ return NULL;
/*
* COMPAT-v6.19: Schedulers built before sub-sched support was
* introduced may have unassociated non-struct_ops programs.
@@ -1752,7 +2503,10 @@ static inline struct scx_sched *scx_parent(struct scx_sched *sch)
else
return NULL;
}
+
#else /* CONFIG_EXT_SUB_SCHED */
+static inline bool scx_has_subs(void) { return false; }
+
static inline struct scx_sched *scx_task_sched(const struct task_struct *p)
{
return rcu_dereference_protected(scx_root,
@@ -1773,10 +2527,15 @@ static inline bool scx_task_on_sched(struct scx_sched *sch,
static inline struct scx_sched *scx_prog_sched(const struct bpf_prog_aux *aux)
{
- return rcu_dereference_all(scx_root);
+ struct scx_sched *root = rcu_dereference_all(scx_root);
+
+ if (root && unlikely(READ_ONCE(root->dead)))
+ return NULL;
+ return root;
}
static inline struct scx_sched *scx_parent(struct scx_sched *sch) { return NULL; }
+
#endif /* CONFIG_EXT_SUB_SCHED */
#endif /* _KERNEL_SCHED_EXT_INTERNAL_H */
diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c
new file mode 100644
index 000000000000..90441206d3b9
--- /dev/null
+++ b/kernel/sched/ext/sub.c
@@ -0,0 +1,2682 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BPF extensible scheduler class: Documentation/scheduler/sched-ext.rst
+ *
+ * Sub-scheduler hierarchy support.
+ *
+ * A sub-scheduler is an scx_sched attached to a cgroup subtree under another
+ * scx_sched. This file holds the sub-scheduler implementation: the scheduler
+ * tree walk, capability delegation, per-shard cap state and its sync, and the
+ * sub-scheduler enable/disable paths. The core dispatch/enqueue machinery it
+ * builds on lives in ext.c.
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ * Copyright (c) 2026 Tejun Heo <tj@kernel.org>
+ */
+#include <linux/rhashtable.h>
+#include "internal.h"
+#include "cid.h"
+#include "arena.h"
+#include "sub.h"
+#include "inlines.h"
+
+#ifdef CONFIG_EXT_SUB_SCHED
+
+/*
+ * On while any sub-scheduler exists so that a root-only system doesn't pay for
+ * the sub-sched portions of hot paths. See scx_has_subs().
+ */
+DEFINE_STATIC_KEY_FALSE(__scx_has_subs);
+
+/* latched at root enable before any rescue runs */
+static s32 scx_rescue_bw_1024;
+static s64 scx_rescue_quantum_ns;
+static s64 scx_rescue_sat_delta_ns;
+static unsigned long scx_rescue_decay_halflife;
+static unsigned long scx_rescue_overload_after;
+
+/**
+ * scx_skip_subtree_pre - Skip @pos's subtree in a pre-order walk
+ * @pos: current position
+ * @root: walk root
+ *
+ * In a walk started by scx_next_descendant_pre(), continue past @pos's subtree:
+ * return @pos's next sibling, or the closest ancestor's next sibling, or NULL
+ * if @pos's subtree is the last under @root. Same locking rules.
+ */
+struct scx_sched *scx_skip_subtree_pre(struct scx_sched *pos, struct scx_sched *root)
+{
+ struct scx_sched *next;
+
+ lockdep_assert(lockdep_is_held(&scx_enable_mutex) ||
+ lockdep_is_held(&scx_sched_lock) ||
+ rcu_read_lock_any_held());
+
+ while (pos != root) {
+ next = list_next_or_null_rcu(&scx_parent(pos)->children, &pos->sibling,
+ struct scx_sched, sibling);
+ if (next)
+ return next;
+ pos = scx_parent(pos);
+ }
+ return NULL;
+}
+
+/**
+ * scx_next_descendant_pre - find the next descendant for pre-order walk
+ * @pos: the current position (%NULL to initiate traversal)
+ * @root: sched whose descendants to walk
+ *
+ * To be used by scx_for_each_descendant_pre(). Find the next descendant to
+ * visit for pre-order traversal of @root's descendants. @root is included in
+ * the iteration and the first node to be visited.
+ */
+struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos, struct scx_sched *root)
+{
+ struct scx_sched *next;
+
+ lockdep_assert(lockdep_is_held(&scx_enable_mutex) ||
+ lockdep_is_held(&scx_sched_lock) ||
+ rcu_read_lock_any_held());
+
+ /* if first iteration, visit @root */
+ if (!pos)
+ return root;
+
+ /* visit the first child if exists */
+ next = list_first_or_null_rcu(&pos->children, struct scx_sched, sibling);
+ if (next)
+ return next;
+
+ /* no child, visit my or the closest ancestor's next sibling */
+ return scx_skip_subtree_pre(pos, root);
+}
+
+static struct scx_sched *scx_find_sub_sched(u64 cgroup_id)
+{
+ return rhashtable_lookup(&scx_sched_hash, &cgroup_id,
+ scx_sched_hash_params);
+}
+
+void scx_set_task_sched(struct task_struct *p, struct scx_sched *sch)
+{
+ rcu_assign_pointer(p->scx.sched, sch);
+}
+
+struct cgroup *sch_cgroup(struct scx_sched *sch)
+{
+ return sch->cgrp;
+}
+
+/* for each descendant of @cgrp including self, set ->scx_sched to @sch */
+void set_cgroup_sched(struct cgroup *cgrp, struct scx_sched *sch)
+{
+ struct cgroup *pos;
+ struct cgroup_subsys_state *css;
+
+ cgroup_for_each_live_descendant_pre(pos, css, cgrp)
+ rcu_assign_pointer(pos->scx_sched, sch);
+}
+
+static void free_pshard(struct scx_pshard *pshard)
+{
+ struct scx_caps_updated *cu;
+
+ if (!pshard)
+ return;
+ cu = &pshard->caps_updated;
+ if (cu->cmask_arena_out)
+ scx_arena_free(pshard->sch, cu->cmask_arena_out,
+ struct_size_t(struct scx_cmask, bits,
+ SCX_CMASK_NR_WORDS(pshard->nr_cids)));
+ kfree(pshard);
+}
+
+void scx_free_pshards(struct scx_sched *sch)
+{
+ s32 si;
+
+ if (!sch->pshard)
+ return;
+ for (si = 0; si < sch->nr_pshards; si++)
+ free_pshard(sch->pshard[si]);
+ kfree(sch->pshard);
+}
+
+static struct scx_pshard *alloc_pshard(struct scx_sched *sch, s32 shard_idx, s32 node)
+{
+ const struct scx_cid_shard *shard =
+ &rcu_dereference_protected(scx_cid_shard_ranges,
+ lockdep_is_held(&scx_enable_mutex))[shard_idx];
+ size_t cmask_size = struct_size_t(struct scx_cmask, bits,
+ SCX_CMASK_NR_WORDS(shard->nr_cids));
+ struct scx_pshard *pshard;
+ struct scx_caps_updated *cu;
+ s32 i;
+
+ pshard = kzalloc_node(sizeof(*pshard), GFP_KERNEL, node);
+ if (!pshard)
+ return NULL;
+
+ raw_spin_lock_init(&pshard->lock);
+ pshard->sch = sch;
+ pshard->base = shard->base_cid;
+ pshard->nr_cids = shard->nr_cids;
+
+ for (i = 0; i < __SCX_NR_CAPS; i++)
+ scx_cmask_init(&pshard->caps[i].cmask, shard->base_cid, shard->nr_cids);
+
+ cu = &pshard->caps_updated;
+ raw_spin_lock_init(&cu->lock);
+ INIT_LIST_HEAD(&cu->node_in_flight);
+ __scx_cmask_init(&cu->cmask, shard->base_cid, shard->nr_cids, SCX_CID_SHARD_MAX_CPUS);
+
+ cu->cmask_arena_out = scx_arena_alloc(sch, cmask_size);
+ if (!cu->cmask_arena_out) {
+ free_pshard(pshard);
+ return NULL;
+ }
+
+ scx_cmask_init(cu->cmask_arena_out, shard->base_cid, shard->nr_cids);
+
+ return pshard;
+}
+
+s32 scx_alloc_pshards(struct scx_sched *sch)
+{
+ struct scx_pshard **pshard;
+ s32 *shard_node;
+ s32 si;
+
+ if (!sch->is_cid_type || !sch->arena_pool)
+ return 0;
+
+ shard_node = rcu_dereference_protected(scx_shard_node,
+ lockdep_is_held(&scx_enable_mutex));
+
+ pshard = kzalloc_objs(pshard[0], scx_nr_cid_shards);
+ if (!pshard)
+ return -ENOMEM;
+
+ for (si = 0; si < scx_nr_cid_shards; si++) {
+ pshard[si] = alloc_pshard(sch, si, shard_node[si]);
+ if (!pshard[si]) {
+ while (--si >= 0)
+ free_pshard(pshard[si]);
+ kfree(pshard);
+ return -ENOMEM;
+ }
+ }
+
+ sch->nr_pshards = scx_nr_cid_shards;
+ /*
+ * Publish only after every entry is built so a reader observing
+ * @sch->pshard never sees a partially-filled array or unpublished cid
+ * tables. Pair the store with a barrier and an acquire load on the
+ * read side.
+ */
+ smp_wmb();
+ WRITE_ONCE(sch->pshard, pshard);
+ return 0;
+}
+
+/*
+ * Seed the root's caps fully. Root owns all cids on all caps at enable time.
+ * Children acquire caps via scx_bpf_sub_grant().
+ */
+void scx_init_root_caps(struct scx_sched *sch)
+{
+ s32 si, i;
+
+ for (si = 0; si < sch->nr_pshards; si++) {
+ struct scx_pshard *ps = sch->pshard[si];
+
+ for (i = 0; i < __SCX_NR_CAPS; i++)
+ scx_cmask_fill(&ps->caps[i].cmask);
+ }
+}
+
+/* unserved remainder of @rq's rescuee's admitted slice, 0 once fully served */
+static s64 scx_rescue_slice_remaining(struct rq *rq)
+{
+ s64 served = rq->scx.rescue.curr->se.sum_exec_runtime - rq->scx.rescue.exec_snap;
+
+ return max(rq->scx.rescue.slice - served, 0);
+}
+
+/*
+ * Decay @pcpu's rescue usage average in place, halving per the knob-derived
+ * halflife, see scx_rescue_set_knobs(). The timestamp advances only by whole
+ * halflives.
+ */
+static u64 scx_rescue_decay_avg(struct scx_sched_pcpu *pcpu)
+{
+ unsigned long halflife = scx_rescue_decay_halflife;
+ u64 n = div_u64(get_jiffies_64() - pcpu->rescue_avg_at, halflife);
+
+ if (n) {
+ pcpu->rescue_avg = n < 64 ? pcpu->rescue_avg >> n : 0;
+ pcpu->rescue_avg_at += n * halflife;
+ }
+ return pcpu->rescue_avg;
+}
+
+/**
+ * scx_rescue_charge - Charge the rescuee's runtime
+ * @rq: rq the rescuee is running on
+ * @delta_exec: runtime being charged
+ *
+ * Also ends the rescue once the admitted slice has been served in full. Ending
+ * on served time rather than slice exhaustion bounds both the rescue and the
+ * charging when a scheduler extends the rescuee's slice.
+ */
+void scx_rescue_charge(struct rq *rq, s64 delta_exec)
+{
+ struct scx_sched_pcpu *pcpu;
+
+ lockdep_assert_rq_held(rq);
+
+ /*
+ * A rescue slice is bounded by one quantum and tick-driven expiry can
+ * overshoot by up to a tick. Clamp to avoid wild over-charges on VMs.
+ */
+ delta_exec = min_t(s64, delta_exec, scx_rescue_quantum_ns + TICK_NSEC);
+
+ rq->scx.rescue.budget -= delta_exec;
+
+ /* per-cpu usage average feeds the overload victim pick */
+ pcpu = per_cpu_ptr(scx_task_sched(rq->curr)->pcpu, cpu_of(rq));
+ pcpu->rescue_avg = scx_rescue_decay_avg(pcpu) + delta_exec;
+
+ if (!scx_rescue_slice_remaining(rq))
+ scx_task_slice_ended(rq, rq->scx.rescue.curr);
+}
+
+/**
+ * scx_rescue_end - End the rescue execution on @rq
+ * @rq: rq of interest
+ *
+ * When no rescuee is left pending, the session is over and the balance above
+ * one quantum dies with it - it would otherwise become a banked license to
+ * preempt the cid owner long after the starvation ended. While waiters remain,
+ * the accrued deficit belongs to the queue and carries into the next rescue.
+ */
+void scx_rescue_end(struct rq *rq)
+{
+ lockdep_assert_rq_held(rq);
+
+ rq->scx.rescue.curr = NULL;
+ if (list_empty(&rq->scx.rescue.dsq.list))
+ rq->scx.rescue.budget = min(rq->scx.rescue.budget, scx_rescue_quantum_ns);
+}
+
+/**
+ * scx_rescue_keep - Keep the rescue going for a preempted-out rescuee
+ * @rq: rq @p is running on
+ * @p: task under rescue whose slice is exhausted
+ *
+ * Called from put_prev_task_scx() to decide what an exhausted slice means for
+ * the rescuee. scx_rescue_charge() ends the rescue the moment the admitted
+ * slice is fully served, so arriving here with the rescue still open means @p
+ * was preempted. Restore the unserved remainder and return %true - @p stays the
+ * rescuee and the caller reinserts it at the tail of the local DSQ, behind
+ * whatever preempted the rescuee.
+ *
+ * Return %false to end the rescue instead - the slice is already fully served,
+ * @p is leaving the rq or bypass is dismantling rescues.
+ */
+bool scx_rescue_keep(struct rq *rq, struct task_struct *p)
+{
+ s64 remaining = scx_rescue_slice_remaining(rq);
+
+ lockdep_assert_rq_held(rq);
+
+ if (!remaining || !(p->scx.flags & SCX_TASK_QUEUED) ||
+ scx_bypassing(scx_task_sched(p), cpu_of(rq)))
+ return false;
+
+ scx_set_task_slice(p, remaining);
+ return true;
+}
+
+/**
+ * scx_rescue_accrue - Accrue budget at the configured fraction of elapsed time
+ * @rq: rq of interest
+ *
+ * A session spans from the first arrival until no rescuee is left, pending or
+ * admitted. While one is active the cap is three quanta and the balance drives
+ * escalation, see scx_rescue_timerfn(). Outside a session the cap is one
+ * quantum, so an idle gap funds the next arrival's admission but never an
+ * escalation.
+ */
+static void scx_rescue_accrue(struct rq *rq)
+{
+ bool in_session = rq->scx.rescue.curr || !list_empty(&rq->scx.rescue.dsq.list);
+ s64 cap = in_session ? 3 * scx_rescue_quantum_ns : scx_rescue_quantum_ns;
+ s64 delta;
+ u64 now;
+
+ lockdep_assert_rq_held(rq);
+
+ /* not every path here holds an updated rq clock, use __scx_bpf_now() */
+ now = __scx_bpf_now(rq);
+ delta = now - rq->scx.rescue.clock;
+ rq->scx.rescue.clock = now;
+
+ /*
+ * Avoid multiplication overflows by taking a shortcut when the gap is
+ * large enough to fill the budget.
+ */
+ if (delta >= scx_rescue_sat_delta_ns)
+ rq->scx.rescue.budget = cap;
+ else
+ rq->scx.rescue.budget =
+ min(cap, rq->scx.rescue.budget +
+ ((delta * scx_rescue_bw_1024) >> SCHED_CAPACITY_SHIFT));
+}
+
+/*
+ * The slice for the next admission - the quantum divided across the stranded
+ * tasks so that a crowded queue round-robins on shorter slices.
+ */
+static s64 scx_rescue_next_slice(struct rq *rq)
+{
+ s64 min_slice = max_t(s64, SCX_RESCUE_MIN_SLICE_US * NSEC_PER_USEC, TICK_NSEC);
+ u32 depth = rq->scx.rescue.dsq.nr ?: 1;
+
+ return clamp(div_s64(scx_rescue_quantum_ns, depth), min_slice, scx_rescue_quantum_ns);
+}
+
+static void scx_rescue_timer_arm(struct rq *rq)
+{
+ struct timer_list *timer = &rq->scx.rescue.timer;
+ s64 delay = scx_rescue_quantum_ns / 4; /* should be granular enough */
+
+ if (timer_pending(timer))
+ return;
+
+ /*
+ * While the head waiter can't be admitted because the bucket is short
+ * of a full quantum, stretch to the full funding delay.
+ */
+ if (!rq->scx.rescue.curr && rq->scx.rescue.budget < scx_rescue_quantum_ns) {
+ s64 deficit = scx_rescue_quantum_ns - rq->scx.rescue.budget;
+
+ delay = max(delay,
+ div_s64(deficit << SCHED_CAPACITY_SHIFT, scx_rescue_bw_1024));
+ }
+
+ /* +1 rounds up so the beat is due by the time the timer fires */
+ timer->expires = jiffies + nsecs_to_jiffies(delay) + 1;
+ add_timer_on(timer, cpu_of(rq));
+}
+
+/**
+ * scx_rescue_admit - Start rescuing @p on @rq
+ * @rq: rq @p is being admitted on
+ * @p: task being admitted, off any DSQ
+ * @slice: CPU time to grant
+ *
+ * The schedulers keep their normal control over @p and may preempt or reslice
+ * it. @slice is measured on served CPU time against the snapshot taken here, so
+ * neither shortens the rescue, see scx_rescue_charge() and scx_rescue_keep().
+ * Prolonged denial escalates into protected execution, see
+ * scx_rescue_timerfn().
+ */
+static void scx_rescue_admit(struct rq *rq, struct task_struct *p, s64 slice)
+{
+ lockdep_assert_rq_held(rq);
+ WARN_ON_ONCE(rq->scx.rescue.curr);
+
+ rq->scx.rescue.curr = p;
+ rq->scx.rescue.slice = slice;
+ rq->scx.rescue.exec_snap = p->se.sum_exec_runtime;
+ scx_set_task_slice(p, slice);
+ scx_rescue_timer_arm(rq);
+}
+
+/**
+ * scx_rescue_try_admit - Try to admit a freshly stranded task
+ * @rq: rq @p is being inserted on
+ * @p: stranded task being diverted to rescue
+ *
+ * One rescue at a time and earlier arrivals go first. Admission needs a full
+ * quantum of budget, spent as the rescue runs. Return %true if @p was admitted
+ * and should be inserted at the tail of @rq's local DSQ, %false if it has to
+ * park on the rescue DSQ, with the timer armed to admit it later.
+ */
+static bool scx_rescue_try_admit(struct rq *rq, struct task_struct *p)
+{
+ scx_rescue_accrue(rq);
+
+ if (!rq->scx.rescue.curr && list_empty(&rq->scx.rescue.dsq.list) &&
+ rq->scx.rescue.budget >= scx_rescue_quantum_ns) {
+ scx_rescue_admit(rq, p, scx_rescue_quantum_ns);
+ return true;
+ }
+
+ scx_rescue_timer_arm(rq);
+ return false;
+}
+
+/**
+ * scx_rescue_check_overload - Eject the top rescue consumer on a stuck rescue
+ * @rq: rq whose rescue timer fired
+ *
+ * If the oldest waiter on @rq's rescue DSQ has been queued for too long, rescue
+ * demand on this cpu persistently exceeds the configured bandwidth. Eject the
+ * sub with the highest recent rescue consumption instead of letting the
+ * scheduler stall path blame the waiter's owner, who may just be crowded out.
+ */
+static void scx_rescue_check_overload(struct rq *rq)
+{
+ struct scx_sched *victim = NULL, *pos;
+ struct task_struct *p;
+ int cpu = cpu_of(rq);
+ u64 max_avg = 0;
+ u32 dur_ms;
+
+ lockdep_assert_rq_held(rq);
+
+ p = list_first_entry_or_null(&rq->scx.rescue.dsq.list, struct task_struct,
+ scx.dsq_list.node);
+ if (!p)
+ return;
+
+ /* has the head waiter been queued for longer than the threshold? */
+ if (time_before(jiffies, p->scx.rescue_at + scx_rescue_overload_after))
+ return;
+
+ /*
+ * Grace period after the last ejection on this cpu - the freed
+ * bandwidth gets one threshold's worth of time to drain the backlog
+ * before another sub is judged.
+ */
+ if (time_before64(get_jiffies_64(), rq->scx.rescue.kill_at +
+ scx_rescue_overload_after))
+ return;
+
+ list_for_each_entry_rcu(pos, &scx_sched_all, all) {
+ u64 avg = scx_rescue_decay_avg(per_cpu_ptr(pos->pcpu, cpu));
+
+ /* skip an already-exiting sub, else the ejection is wasted */
+ if (pos->level && avg > max_avg &&
+ atomic_read(&pos->exit_kind) == SCX_EXIT_NONE) {
+ max_avg = avg;
+ victim = pos;
+ }
+ }
+ if (!victim)
+ return;
+
+ rq->scx.rescue.kill_at = get_jiffies_64();
+ dur_ms = jiffies_to_msecs(jiffies - p->scx.rescue_at);
+ __scx_exit(victim, SCX_EXIT_ERROR_RESCUE, 0, cpu,
+ "used too much rescue CPU time (%llums) while %s[%d] waited %u.%03us to be rescued",
+ div_u64(max_avg, NSEC_PER_MSEC), p->comm, p->pid, dur_ms / 1000,
+ dur_ms % 1000);
+}
+
+/**
+ * scx_rescue_timerfn - Drive and pace rescue execution
+ * @timer: rq->scx.rescue.timer
+ *
+ * Runs every quarter quantum while a rescuee exists, pending or admitted, see
+ * scx_rescue_timer_arm(). The head waiter is admitted once the bucket holds a
+ * full quantum and granted its slice, see scx_rescue_next_slice(). A session
+ * whose budget accumulates over two quanta with the admitted rescuee still
+ * waiting escalates - the rescuee's remaining slice turns into protected
+ * execution and it preempts the current task. An overloaded rescue queue ejects
+ * the top consumer, see scx_rescue_check_overload().
+ */
+static void scx_rescue_timerfn(struct timer_list *timer)
+{
+ struct rq *rq = timer_container_of(rq, timer, scx.rescue.timer);
+ struct task_struct *p;
+
+ guard(rq_lock_irqsave)(rq);
+
+ p = rq->scx.rescue.curr;
+ if (!p && list_empty(&rq->scx.rescue.dsq.list))
+ return;
+
+ scx_rescue_accrue(rq);
+ scx_rescue_check_overload(rq);
+
+ if (!p) {
+ s64 slice = scx_rescue_next_slice(rq);
+
+ /* no rescue in progress */
+ if (rq->scx.rescue.budget < scx_rescue_quantum_ns)
+ goto out_arm;
+
+ /* there's enough budget to start rescuing the next one */
+ p = list_first_entry(&rq->scx.rescue.dsq.list, struct task_struct,
+ scx.dsq_list.node);
+ scx_task_unlink_from_dsq(p, &rq->scx.rescue.dsq);
+ scx_rescue_admit(rq, p, slice);
+ scx_move_local_task_to_local_dsq(scx_task_sched(p), p,
+ SCX_ENQ_IGNORE_CAPS, rq);
+ if (sched_class_above(&ext_sched_class, rq->curr->sched_class))
+ resched_curr(rq);
+ } else if (p->scx.dsq && rq->scx.rescue.budget > 2 * scx_rescue_quantum_ns) {
+ /*
+ * The rescuee waited for the CPU for too long. Escalate - grant
+ * the unserved remainder, protect it from the schedulers and
+ * preempt the current task. The slice is set before the
+ * protection. Repeat beats only repeat the head move - the
+ * slice write is refused on a protected task.
+ */
+ scx_set_task_slice(p, scx_rescue_slice_remaining(rq));
+ p->scx.flags |= SCX_TASK_PROTECTED;
+ scx_task_unlink_from_dsq(p, &rq->scx.local_dsq);
+ scx_move_local_task_to_local_dsq(scx_task_sched(p), p,
+ SCX_ENQ_HEAD | SCX_ENQ_PREEMPT | SCX_ENQ_IGNORE_CAPS,
+ rq);
+ }
+out_arm:
+ scx_rescue_timer_arm(rq);
+}
+
+/* flush out tasks waiting for rescue before a CPU goes down */
+void scx_rescue_flush(struct rq *rq)
+{
+ struct task_struct *p, *n;
+
+ lockdep_assert_rq_held(rq);
+
+ /* end the current rescue */
+ if (rq->scx.rescue.curr)
+ scx_task_slice_ended(rq, rq->scx.rescue.curr);
+
+ /* and flush out all pending ones */
+ list_for_each_entry_safe(p, n, &rq->scx.rescue.dsq.list, scx.dsq_list.node) {
+ scx_task_unlink_from_dsq(p, &rq->scx.rescue.dsq);
+ scx_move_local_task_to_local_dsq(scx_task_sched(p), p,
+ SCX_ENQ_IGNORE_CAPS, rq);
+ }
+
+ timer_delete(&rq->scx.rescue.timer);
+}
+
+void scx_rescue_dump(struct seq_buf *s, struct rq *rq)
+{
+ struct task_struct *p = rq->scx.rescue.curr;
+
+ scx_dump_line(s, " rescue=%u budget=%lldus rescuing=%s[%d]",
+ rq->scx.rescue.dsq.nr,
+ div_s64(rq->scx.rescue.budget, NSEC_PER_USEC),
+ p ? p->comm : "none", p ? p->pid : -1);
+}
+
+/*
+ * A scheduler whose stall watchdog is shorter than the overload threshold gets
+ * stall-killed over its parked waiters before the overload check can eject the
+ * actual top consumer. The root's knobs set the threshold, warn on any
+ * scheduler that doesn't fit it.
+ */
+static void scx_rescue_check_timeout(struct scx_sched *sch)
+{
+ if (!scx_rescue_bw_1024 || sch->watchdog_timeout > scx_rescue_overload_after)
+ return;
+
+ pr_warn("sched_ext: %s: watchdog timeout %ums <= rescue overload threshold %ums\n",
+ sch->ops.name, jiffies_to_msecs(sch->watchdog_timeout),
+ jiffies_to_msecs(scx_rescue_overload_after));
+}
+
+/* latch the rescue parameters on root scheduler enable */
+void scx_rescue_set_knobs(struct scx_sched *sch)
+{
+ s32 bw_ppt = sch->ops.rescue_bandwidth_ppt ?: SCX_RESCUE_DFL_BW_PPT;
+ s64 quantum_us = sch->ops.rescue_quantum_us ?: SCX_RESCUE_DFL_QUANTUM_US;
+ s64 period_ns;
+
+ if (sch->ops.rescue_bandwidth_ppt == SCX_RESCUE_DISABLE) {
+ scx_rescue_bw_1024 = 0;
+ return;
+ }
+
+ scx_rescue_bw_1024 = bw_ppt * SCHED_CAPACITY_SCALE / 1000;
+ scx_rescue_quantum_ns = max(quantum_us * NSEC_PER_USEC, TICK_NSEC);
+ scx_rescue_sat_delta_ns =
+ div_s64((4 * scx_rescue_quantum_ns + TICK_NSEC) << SCHED_CAPACITY_SHIFT,
+ scx_rescue_bw_1024);
+
+ /*
+ * The overload threshold and the decay halflife scale with the funding
+ * period - the time the bucket takes to fund one full quantum.
+ */
+ period_ns = div_s64(scx_rescue_quantum_ns << SCHED_CAPACITY_SHIFT, scx_rescue_bw_1024);
+ scx_rescue_overload_after =
+ clamp(nsecs_to_jiffies(SCX_RESCUE_OVERLOAD_MULT * period_ns),
+ msecs_to_jiffies(SCX_RESCUE_MIN_OVERLOAD_MS),
+ msecs_to_jiffies(SCX_RESCUE_MAX_OVERLOAD_MS));
+ scx_rescue_decay_halflife = scx_rescue_overload_after / 4;
+
+ /* a single in-budget wait must not cross the overload trigger */
+ if (nsecs_to_jiffies(period_ns) > scx_rescue_overload_after / 2)
+ pr_warn("sched_ext: %s: rescue funding period %lldms > overload threshold %ums / 2\n",
+ sch->ops.name, div_s64(period_ns, NSEC_PER_MSEC),
+ jiffies_to_msecs(scx_rescue_overload_after));
+
+ scx_rescue_check_timeout(sch);
+}
+
+void scx_rescue_init(struct rq *rq)
+{
+ BUG_ON(scx_init_dsq(&rq->scx.rescue.dsq, SCX_DSQ_RESCUE, NULL));
+ timer_setup(&rq->scx.rescue.timer, scx_rescue_timerfn, TIMER_PINNED);
+ rq->scx.rescue.kill_at = get_jiffies_64();
+}
+
+/**
+ * scx_resolve_local_dsq - Pick the local, rescue or reject DSQ for an insert
+ * @sch: enqueuing sub-sched
+ * @rq: rq whose local DSQ @p targets
+ * @p: task being inserted
+ * @enq_flags: in/out, unhonored flags are cleared
+ *
+ * Return @rq's local DSQ if @sch holds the required caps on @rq's cid.
+ * Otherwise, return @rq's rescue DSQ if the insert carries %SCX_ENQ_RESCUE and
+ * rescue is enabled, or @rq's reject DSQ after recording the reenq reason on
+ * @p.
+ *
+ * %SCX_ENQ_IMMED, %SCX_ENQ_PREEMPT and %SCX_ENQ_HEAD are cleared when diverting
+ * to rescue or reject. %SCX_ENQ_PREEMPT is also cleared on a fallback
+ * migration-disabled admission.
+ *
+ * Bypass doesn't need special-casing as a bypassing sched's tasks are enqueued
+ * to and run by its nearest non-bypassing ancestor. If root is bypassing, it
+ * always holds all caps.
+ */
+struct scx_dispatch_q *scx_resolve_local_dsq(struct scx_sched *sch, struct rq *rq,
+ struct task_struct *p, u64 *enq_flags)
+{
+ if (!scx_has_subs())
+ return &rq->scx.local_dsq;
+
+ s32 cid = __scx_cpu_to_cid(cpu_of(rq));
+ struct scx_sched *asch = rq->scx.remote_activate_sch ?: sch;
+ u64 needed = scx_caps_for_enq(*enq_flags);
+ u64 missing;
+
+ /*
+ * On a remote activation the scheduling sched (@asch) differs from
+ * @p's owner (@sch). Check caps against the scheduling sched.
+ */
+ if (*enq_flags & SCX_ENQ_PREEMPT)
+ needed |= scx_caps_for_preempt(asch, rq, *enq_flags);
+ missing = scx_missing_caps(asch, cpu_of(rq), needed);
+
+ /* requirements met */
+ if (likely(!missing))
+ return &rq->scx.local_dsq;
+
+ /*
+ * The task must run on this CPU regardless of caps: the rq is draining
+ * offline (BPF scheduler bypassed), the task is migration-disabled, or a
+ * migration is pending. Admit despite the missing caps and count it.
+ * Refuse preemptions.
+ */
+ if (unlikely(!scx_rq_online(rq) || is_migration_disabled(p) ||
+ p->migration_pending)) {
+ __scx_add_event(sch, SCX_EV_SUB_FORCED_ADMIT, 1);
+ *enq_flags &= ~SCX_ENQ_PREEMPT;
+ return &rq->scx.local_dsq;
+ }
+
+ /*
+ * Diverting to rescue or reject, neither of which honors IMMED, PREEMPT
+ * or HEAD - a diversion has no priority and IMMED is not allowed on
+ * non-local DSQs. Strip the enq and task flags along with the slice.
+ */
+ *enq_flags &= ~(SCX_ENQ_IMMED | SCX_ENQ_PREEMPT | SCX_ENQ_HEAD |
+ SCX_ENQ_APPLY_SLICE | SCX_ENQ_SLICE_DFL);
+ p->scx.flags &= ~SCX_TASK_IMMED;
+
+ /* the enqueuer opted for rescue instead of rejection and reenqueue */
+ if ((*enq_flags & SCX_ENQ_RESCUE) && likely(scx_rescue_bw_1024)) {
+ __scx_add_event(sch, SCX_EV_SUB_RESCUE, 1);
+ if (scx_rescue_try_admit(rq, p))
+ return &rq->scx.local_dsq;
+
+ /* queueing, the overload trigger measures the wait from here */
+ p->scx.rescue_at = jiffies;
+ return &rq->scx.rescue.dsq;
+ }
+
+ p->scx.reenq_reason_caps = missing;
+ p->scx.reenq_reason_cid = cid;
+
+ return &rq->scx.reject_dsq;
+}
+
+/* @p lost the caps needed to stay on @rq's local DSQ? Record reason if so. */
+bool scx_task_reenq_on_cap_revoke(struct rq *rq, struct task_struct *p)
+{
+ u64 missing;
+
+ /* migration-disabled tasks and the rescuee are admitted capless */
+ if (is_migration_disabled(p) || p == scx_rescuee(rq))
+ return false;
+
+ missing = scx_missing_caps(scx_task_sched(p), cpu_of(rq), scx_caps_for_task(p));
+ if (likely(!missing))
+ return false;
+
+ p->scx.reenq_reason_caps = missing;
+ p->scx.reenq_reason_cid = __scx_cpu_to_cid(cpu_of(rq));
+ return true;
+}
+
+/*
+ * Drain @rq->scx.reject_dsq, reenqueueing each task so the BPF re-decides
+ * from p->scx.reenq_reason_*.
+ *
+ * A task can be re-rejected repeatedly. The reenqueue is bounded per task in
+ * scx_do_enqueue_task(), which ejects the owning sub past SCX_REENQ_MAX_REPEAT.
+ * Rejection can't happen for root.
+ */
+void scx_reenq_reject(struct rq *rq)
+{
+ LIST_HEAD(tasks);
+ struct task_struct *p, *n;
+
+ lockdep_assert_rq_held(rq);
+
+ if (!scx_has_subs() || list_empty(&rq->scx.reject_dsq.list))
+ return;
+
+ /*
+ * Move to a private list so a task re-rejected by the
+ * scx_do_enqueue_task() below isn't revisited this round.
+ */
+ list_for_each_entry_safe(p, n, &rq->scx.reject_dsq.list, scx.dsq_list.node) {
+ /* migration_pending tasks should have bypassed to local DSQ */
+ if (WARN_ON_ONCE(p->migration_pending))
+ continue;
+
+ scx_reenq_wait_dispatching(p);
+ scx_dispatch_dequeue(rq, p);
+
+ if (WARN_ON_ONCE(p->scx.flags & SCX_TASK_REENQ_REASON_MASK))
+ p->scx.flags &= ~SCX_TASK_REENQ_REASON_MASK;
+ p->scx.flags |= SCX_TASK_REENQ_CAP;
+
+ list_add_tail(&p->scx.dsq_list.node, &tasks);
+ }
+
+ list_for_each_entry_safe(p, n, &tasks, scx.dsq_list.node) {
+ list_del_init(&p->scx.dsq_list.node);
+
+ scx_do_enqueue_task(rq, p, SCX_ENQ_REENQ, -1);
+
+ p->scx.flags &= ~SCX_TASK_REENQ_REASON_MASK;
+ }
+}
+
+/* record a caps change, see struct scx_caps_updated */
+static void caps_updated_record(struct scx_pshard *ps, const struct scx_cmask *cids, u64 caps,
+ struct list_head *to_deliver)
+{
+ struct scx_caps_updated *cu = &ps->caps_updated;
+
+ guard(raw_spinlock)(&cu->lock);
+ scx_cmask_or(&cu->cmask, cids);
+ cu->caps |= caps;
+ if (list_empty(&cu->node_in_flight))
+ list_add_tail(&cu->node_in_flight, to_deliver);
+}
+
+/* deliver queued caps_updated callbacks, see struct scx_caps_updated */
+static void caps_updated_deliver(struct list_head *to_deliver)
+{
+ struct scx_caps_updated *cu, *tmp;
+
+ list_for_each_entry_safe(cu, tmp, to_deliver, node_in_flight) {
+ struct scx_pshard *ps = container_of(cu, struct scx_pshard, caps_updated);
+ struct scx_sched *sch = ps->sch;
+
+ while (true) {
+ u64 caps = 0;
+
+ /*
+ * During enable, has_op is set after ops.sub_attach(),
+ * so !has_op means the op is absent or the sched isn't
+ * live yet - e.g. caps grant from ops.sub_attach().
+ * Either way don't consume - leave for
+ * scx_sub_seed_caps() to deliver once live.
+ */
+ scoped_guard (raw_spinlock, &cu->lock) {
+ if (cu->caps && SCX_HAS_OP(sch, sub_caps_updated) &&
+ likely(!READ_ONCE(sch->aborting))) {
+ struct scx_cmask_ref ref;
+
+ caps = cu->caps;
+ scx_cmask_ref_init_kern(sch, cu->cmask_arena_out,
+ ps->base, ps->nr_cids, &ref);
+ scx_cmask_ref_copy(&ref, &cu->cmask);
+ scx_cmask_clear(&cu->cmask);
+ cu->caps = 0;
+ } else {
+ list_del_init(&cu->node_in_flight);
+ }
+ }
+ if (!caps)
+ break;
+
+ /* caps != 0 only when deliverable (has_op, above) */
+ SCX_CALL_OP(sch, sub_caps_updated, NULL, cu->cmask_arena_out, caps);
+ }
+ }
+}
+
+/*
+ * Deliver caps owed to @sch that couldn't be delivered earlier (e.g. a grant
+ * taken during its sub_attach(), before has_op was set). Called once @sch is
+ * enabled.
+ */
+static void scx_sub_seed_caps(struct scx_sched *sch)
+{
+ LIST_HEAD(to_deliver);
+ s32 si;
+
+ guard(irqsave)();
+
+ for (si = 0; si < sch->nr_pshards; si++) {
+ struct scx_pshard *ps = sch->pshard[si];
+ struct scx_caps_updated *cu = &ps->caps_updated;
+
+ scoped_guard (raw_spinlock, &cu->lock) {
+ if (cu->caps && list_empty(&cu->node_in_flight))
+ list_add_tail(&cu->node_in_flight, &to_deliver);
+ }
+ }
+ caps_updated_deliver(&to_deliver);
+}
+
+static u64 calc_effective_caps(struct scx_pshard *ps, s32 cid)
+{
+ u64 ecaps = 0;
+ u32 cap_bit;
+
+ for (cap_bit = 0; cap_bit < __SCX_NR_CAPS; cap_bit++)
+ if (scx_cmask_test(cid, &ps->caps[cap_bit].cmask))
+ ecaps |= BIT_U64(cap_bit) | scx_caps_implied(BIT_U64(cap_bit));
+ return ecaps;
+}
+
+/**
+ * queue_sync_ecaps - Queue ecaps update for a (sch, cid) pair
+ * @sch: sched to update
+ * @cid: cid to update
+ *
+ * Queue an ecaps update for @sch's @cid and kick the cpu so that it syncs in
+ * dispatch_one().
+ */
+static void queue_sync_ecaps(struct scx_sched *sch, s32 cid)
+{
+ s32 cpu = __scx_cid_to_cpu(cid);
+ struct scx_sched_pcpu *pcpu = per_cpu_ptr(sch->pcpu, cpu);
+
+ /*
+ * Pairs with smp_mb() in scx_process_sync_ecaps(). Either the check
+ * below sees the node off the list and queues it, or the in-flight sync
+ * sees the caps[] update made before this call.
+ */
+ smp_mb();
+
+ /* @cid's pshard->lock excludes concurrent queueing attempts */
+ if (llist_on_list(&pcpu->ecaps_to_sync_node))
+ return;
+ if (llist_add(&pcpu->ecaps_to_sync_node, &cpu_rq(cpu)->scx.ecaps_to_sync))
+ scx_kick_cpu(sch->ancestors[0], cpu, 0);
+}
+
+/* discard @rq's queued ecaps syncs */
+static void discard_queued_syncs(struct rq *rq)
+{
+ struct llist_node *pos, *tmp;
+
+ lockdep_assert_rq_held(rq);
+
+ llist_for_each_safe(pos, tmp, llist_del_all(&rq->scx.ecaps_to_sync))
+ init_llist_node(pos);
+}
+
+/**
+ * scx_process_sync_ecaps - Sync this cpu's ecaps to pshard->caps[]
+ * @rq: the cid's cpu rq
+ * @prev: @rq's previous task from the in-progress dispatch
+ *
+ * pshard->caps[] is the target configuration. pcpu->ecaps is the effective
+ * transposed copy owned by the cid's cpu and written only here under @rq's
+ * lock.
+ *
+ * A sched that newly gains baseline access here is owed an update_idle() so it
+ * learns the cid's idle state. Such a gain arms the per-rq
+ * %SCX_RQ_SUB_IDLE_RENOTIFY gate so the next idle pick delivers it.
+ */
+void scx_process_sync_ecaps(struct rq *rq, struct task_struct *prev)
+{
+ s32 cpu = cpu_of(rq);
+ s32 cid, shard;
+ struct llist_node *batch, *pos, *tmp;
+ u64 lost_all = 0;
+
+ lockdep_assert_rq_held(rq);
+
+ if (!scx_has_subs() || likely(llist_empty(&rq->scx.ecaps_to_sync)))
+ return;
+
+ /*
+ * ecaps are zeroed while the cpu is inactive and must stay zero.
+ * Discard queued syncs instead of processing them - the
+ * scx_online_ecaps() reseed re-syncs every sched on activation.
+ * cpu_active() clears before the offline zeroing and sets before the
+ * reseed is queued, so this test can neither miss a racing sync nor
+ * eat the reseed.
+ */
+ if (unlikely(!cpu_active(cpu))) {
+ discard_queued_syncs(rq);
+ return;
+ }
+
+ /* @cid is valid here: the cpu is active with queued syncs */
+ cid = __scx_cpu_to_cid(cpu);
+ shard = rcu_dereference_all(scx_cid_to_shard)[cid];
+
+ batch = llist_del_all(&rq->scx.ecaps_to_sync);
+ llist_for_each_safe(pos, tmp, batch) {
+ struct scx_sched_pcpu *pcpu =
+ container_of(pos, struct scx_sched_pcpu, ecaps_to_sync_node);
+ struct scx_pshard *ps = pcpu->sch->pshard[shard];
+ u64 old, ecaps, lost, gained;
+
+ init_llist_node(pos);
+
+ /* pairs with smp_mb() in queue_sync_ecaps(), see there */
+ smp_mb();
+
+ old = READ_ONCE(pcpu->ecaps);
+ ecaps = calc_effective_caps(ps, cid);
+ WRITE_ONCE(pcpu->ecaps, ecaps);
+
+ lost = old & ~ecaps;
+ gained = ecaps & ~old;
+ lost_all |= lost;
+
+ /*
+ * Tell the sched its effective caps on this cid changed. The
+ * invocation is equivalent to the dispatch path and may drop
+ * and re-acquire the rq lock temporarily while the rest of
+ * @batch is held privately, see scx_discard_ecaps_to_sync().
+ * The dispatch kfuncs resolve their context on the executing
+ * cpu, which under core scheduling can differ from @rq's cpu,
+ * so the context is set up there. The rq recorded in it keeps
+ * the dispatches targeting @rq.
+ */
+ if (ecaps != pcpu->reported_ecaps &&
+ SCX_HAS_OP(pcpu->sch, sub_ecaps_updated) &&
+ !scx_bypassing(pcpu->sch, cpu)) {
+ struct scx_dsp_ctx *dspc = &this_cpu_ptr(pcpu->sch->pcpu)->dsp_ctx;
+
+ dspc->rq = rq;
+ /* stash @prev so nested dispatches can access it */
+ rq->scx.sub_dispatch_prev = prev;
+ SCX_CALL_OP(pcpu->sch, sub_ecaps_updated, rq, scx_cpu_arg(cpu),
+ pcpu->reported_ecaps, ecaps);
+ rq->scx.sub_dispatch_prev = NULL;
+ scx_flush_dispatch_buf(pcpu->sch, rq);
+ pcpu->reported_ecaps = ecaps;
+ }
+
+ /*
+ * Gaining baseline access owes an update_idle() so the sched
+ * learns the cpu's idle state. Arm the per-rq gate so the next
+ * idle pick flushes it. Losing access drops any pending notify.
+ */
+ if (gained & SCX_CAP_BASE) {
+ pcpu->idle_renotify = true;
+ rq->scx.flags |= SCX_RQ_SUB_IDLE_RENOTIFY;
+ } else if (lost & SCX_CAP_BASE) {
+ pcpu->idle_renotify = false;
+ }
+ }
+
+ /*
+ * Losing a cap can strand already-queued tasks. Schedule a reenq scan
+ * to move the now-capless ones off the local DSQ. The scan tests
+ * against the effective caps and thus must come after the ecaps sync.
+ */
+ if (lost_all & SCX_CAPS_REENQ_ON_LOSS)
+ scx_schedule_reenq_local(rq, SCX_REENQ_CAP_REVOKE);
+}
+
+/**
+ * scx_unbypass_replay_ecaps - Replay a bypass-suppressed ecaps notification
+ * @rq: rq of the cpu leaving bypass
+ * @sch: scheduler that just left bypass on @rq's cpu
+ *
+ * scx_process_sync_ecaps() consumes syncs while bypassing without delivering
+ * ops.sub_ecaps_updated(), leaving reported_ecaps stale. Nothing re-queues a
+ * sync when bypass lifts, so without a replay a cid that never changes again
+ * would never be notified. The attach-time initial grants are the acute case
+ * as they are consumed during the enable bypass window. Re-queue a sync for
+ * any undelivered delta so the next dispatch delivers it.
+ */
+void scx_unbypass_replay_ecaps(struct rq *rq, struct scx_sched *sch)
+{
+ s32 cpu = cpu_of(rq);
+ struct scx_sched_pcpu *pcpu = per_cpu_ptr(sch->pcpu, cpu);
+ struct scx_pshard *ps;
+ s32 cid;
+
+ lockdep_assert_rq_held(rq);
+
+ /* root holds every cap and never uses ecaps */
+ if (!sch->level)
+ return;
+
+ if (READ_ONCE(pcpu->ecaps) == pcpu->reported_ecaps)
+ return;
+
+ cid = __scx_cpu_to_cid(cpu);
+ ps = sch->pshard[rcu_dereference_all(scx_cid_to_shard)[cid]];
+
+ guard(raw_spinlock)(&ps->lock);
+ queue_sync_ecaps(sch, cid);
+}
+
+/*
+ * A cpu came back. Re-seed each sub-sched's ecaps on the cpu's cid. The sync
+ * recomputes effective caps from the pshard and fires ops.sub_ecaps_updated()
+ * only on a real change since offline.
+ */
+void scx_online_ecaps(struct rq *rq)
+{
+ struct scx_sched *root, *pos;
+ s32 cid, shard;
+
+ /*
+ * Only a live hierarchy can have ecaps to reseed. This also keeps the
+ * table reads below away from an enable that failed before publishing
+ * the tables. A concurrent disable can't retire them, see
+ * handle_hotplug().
+ */
+ if (!scx_enabled())
+ return;
+
+ guard(rq_lock_irqsave)(rq);
+
+ root = scx_root_protected();
+ cid = __scx_cpu_to_cid(cpu_of(rq));
+ shard = rcu_dereference_all(scx_cid_to_shard)[cid];
+
+ scx_for_each_descendant_pre(pos, root) {
+ struct scx_pshard *ps;
+
+ /* root holds every cap and never uses ecaps */
+ if (!pos->level)
+ continue;
+
+ ps = pos->pshard[shard];
+ guard(raw_spinlock)(&ps->lock);
+ queue_sync_ecaps(pos, cid);
+ }
+}
+
+/*
+ * A cpu is going down. Zero each sub-sched's in-effect ecaps so cap checks
+ * treat the cpu as capless while offline. Pending and late-queued syncs are
+ * discarded at consumption by scx_process_sync_ecaps() while the cpu is
+ * inactive. Leave reported_ecaps. Ownership is unchanged, so the
+ * scx_online_ecaps() reseed reports only a genuine delta. No callback fires
+ * here.
+ */
+void scx_offline_ecaps(struct rq *rq)
+{
+ s32 cpu = cpu_of(rq);
+ struct scx_sched *root, *pos;
+
+ guard(rq_lock_irqsave)(rq);
+
+ root = scx_root_protected();
+
+ scx_for_each_descendant_pre(pos, root) {
+ /* root holds every cap and never uses ecaps */
+ if (!pos->level)
+ continue;
+
+ WRITE_ONCE(per_cpu_ptr(pos->pcpu, cpu)->ecaps, 0);
+ }
+}
+
+/*
+ * @pcpu's sched was unhashed before the grace period, so nothing re-queues its
+ * sync node. Remove the node from @rq's pending list so the pcpu can be freed.
+ */
+void scx_discard_ecaps_to_sync(s32 cpu, struct scx_sched_pcpu *pcpu)
+{
+ struct rq *rq = cpu_rq(cpu);
+ struct llist_node *head = NULL, *tail = NULL;
+ struct llist_node *pos, *tmp;
+
+ /*
+ * llist can't unlink a single node. Take all queued nodes, drop @pcpu's
+ * and resplice the rest. Nodes in the taken batch read as on-list
+ * throughout, so queue_sync_ecaps() stays correct.
+ */
+ if (llist_on_list(&pcpu->ecaps_to_sync_node)) {
+ scoped_guard (rq_lock_irqsave, rq) {
+ llist_for_each_safe(pos, tmp, llist_del_all(&rq->scx.ecaps_to_sync)) {
+ if (pos == &pcpu->ecaps_to_sync_node) {
+ init_llist_node(pos);
+ } else {
+ pos->next = head;
+ head = pos;
+ if (!tail)
+ tail = pos;
+ }
+ }
+ if (head)
+ llist_add_batch(head, tail, &rq->scx.ecaps_to_sync);
+ }
+ }
+
+ /*
+ * An in-flight scx_process_sync_ecaps() batch may still hold the node
+ * privately across dispatch-induced rq unlocks, reading as on-list.
+ *
+ * Because a bypassing sched gets no op call, init_llist_node() and all
+ * @pcpu accesses share one contiguous lock hold, off-list under the rq
+ * lock means @pcpu won't be accessed again.
+ */
+ while (true) {
+ scoped_guard (rq_lock_irqsave, rq) {
+ if (!llist_on_list(&pcpu->ecaps_to_sync_node))
+ return;
+ }
+ cpu_relax();
+ }
+}
+
+/**
+ * scx_discard_stale_ecaps_syncs - Discard ecaps syncs from earlier schedulers
+ *
+ * To be called during root enable before the scheduler goes live. An earlier
+ * root's sub-sched may not have gone through its RCU free path yet (e.g. a
+ * still-open link fd defers it) and can leave queued ecaps syncs behind.
+ * Processing them would decode the dead sched's pshards with the current cid
+ * layout. Discard them instead. The backing scx_sched_pcpu's are still
+ * allocated as the free path removes ecaps_to_sync_node before freeing.
+ */
+void scx_discard_stale_ecaps_syncs(void)
+{
+ s32 cpu;
+
+ for_each_possible_cpu(cpu) {
+ struct rq *rq = cpu_rq(cpu);
+
+ guard(rq_lock_irqsave)(rq);
+ discard_queued_syncs(rq);
+ }
+}
+
+static DECLARE_WAIT_QUEUE_HEAD(scx_unlink_waitq);
+
+void drain_descendants(struct scx_sched *sch)
+{
+ /*
+ * Child scheds that finished the critical part of disabling will take
+ * themselves off @sch->children. Wait for it to drain. As propagation
+ * is recursive, empty @sch->children means that all proper descendant
+ * scheds reached unlinking stage.
+ */
+ wait_event(scx_unlink_waitq, list_empty(&sch->children));
+}
+
+/**
+ * scx_rehome_task - Move a task to a sched it has been initialized for
+ * @to: sched taking over @p, @p's init on it already complete
+ * @p: task to re-home
+ *
+ * Exit @p from its current sched and switch it over to @to, overriding the
+ * state to %SCX_TASK_READY to account for the already completed init. A task
+ * on a non-ext class, possible under an %SCX_OPS_SWITCH_PARTIAL root, stays
+ * %READY and is enabled by switching_to_scx() if it switches over.
+ */
+static void scx_rehome_task(struct scx_sched *to, struct task_struct *p)
+{
+ lockdep_assert_held(&p->pi_lock);
+ lockdep_assert_rq_held(task_rq(p));
+
+ scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
+ scx_disable_and_exit_task(scx_task_sched(p), p);
+ scx_set_task_state(p, SCX_TASK_INIT_BEGIN);
+ scx_set_task_state(p, SCX_TASK_INIT);
+ scx_set_task_sched(p, to);
+ scx_set_task_state(p, SCX_TASK_READY);
+ if (p->sched_class == &ext_sched_class)
+ scx_enable_task(to, p);
+ }
+}
+
+/**
+ * scx_punt_task - Hand a task to a failed sched without initialization
+ * @to: failed and bypassed sched taking custody of @p
+ * @p: task to punt
+ *
+ * Take @p off its current sched and put it on @to at %SCX_TASK_NONE. @to is
+ * dying and its teardown will re-home @p properly.
+ *
+ * Used when @to must take over @p but failed to initialize it. Bypass keeps
+ * scheduling decisions away from @to but @p can still trigger its task ops,
+ * which may confuse the BPF side. @to is dying anyway. The exit paths skip
+ * %NONE tasks (see __scx_disable_and_exit_task() and switched_from_scx()).
+ */
+static void scx_punt_task(struct scx_sched *to, struct task_struct *p)
+{
+ lockdep_assert_held(&p->pi_lock);
+ lockdep_assert_rq_held(task_rq(p));
+ WARN_ON_ONCE(!READ_ONCE(to->bypass_depth));
+
+ scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
+ scx_disable_and_exit_task(scx_task_sched(p), p);
+ scx_set_task_sched(p, to);
+ }
+}
+
+static void scx_fail_parent(struct scx_sched *sch,
+ struct task_struct *failed, s32 fail_code)
+{
+ struct scx_sched *parent = scx_parent(sch);
+ struct scx_task_iter sti;
+ struct task_struct *p;
+
+ scx_error(parent, "ops.init_task() failed (%d) for %s[%d] while disabling a sub-scheduler",
+ fail_code, failed->comm, failed->pid);
+
+ /*
+ * Once $parent is bypassed, tasks can be punted into it. This may
+ * cause downstream failures on the BPF side but $parent is dying
+ * anyway.
+ */
+ scx_bypass(parent, true);
+
+ scx_task_iter_start(&sti, sch->cgrp);
+ while ((p = scx_task_iter_next_locked(&sti))) {
+ if (scx_task_on_sched(parent, p))
+ continue;
+
+ scx_punt_task(parent, p);
+ }
+ scx_task_iter_stop(&sti);
+}
+
+#ifdef CONFIG_EXT_GROUP_SCHED
+/**
+ * scx_cgroup_claim_subtree - Claim the subtree's cgroups for an enabling sub
+ * @sch: sub-scheduler being enabled
+ *
+ * Called while enabling @sch, after the subtree's cgrp->scx_sched's are pointed
+ * at @sch and before any task is claimed. This mirrors root enable's
+ * cgroups-before-tasks order. The ops.init_task() args are task_group-granular
+ * and can still reference a cgroup outside the handed-over set when the cpu
+ * controller is coarser than the sub topology or mounted on cgroup1.
+ *
+ * First init each of the parent sched's subtree cgroups on @sch, and only then
+ * exit them from the parent, so that a failed init can be unwound with the
+ * parent untouched. The both-inited transient is invisible outside
+ * scx_cgroup_lock(). %SCX_TG_SUB_INIT tracks the first pass's progress.
+ * %SCX_TG_INITED stays set throughout, except for a task_group whose
+ * ops.cgroup_init() failed on the parent (see scx_cgroup_return_subtree()):
+ * there is nothing to exit from the parent and %SCX_TG_INITED is set back with
+ * the transfer.
+ *
+ * Dying but not yet offlined task_groups are included: a removed cgroup keeps
+ * hosting scheduling events until its dying tasks finish their final context
+ * switches, so it still needs to be inited on a sched, and its offline-time
+ * ops.cgroup_exit() follows the last of those events.
+ *
+ * Return 0 on success, -errno on failure. On failure, @sch has been
+ * scx_error()'d and is left with no cgroups.
+ */
+static s32 scx_cgroup_claim_subtree(struct scx_sched *sch)
+{
+ struct cgroup *sub_cgrp = sch_cgroup(sch);
+ struct cgroup_subsys_state *ecss = cgroup_e_css(sub_cgrp, &cpu_cgrp_subsys);
+ struct scx_sched *parent = scx_parent(sch);
+ struct cgroup_subsys_state *css;
+ int ret;
+
+ css_for_each_descendant_pre(css, ecss) {
+ struct task_group *tg = css_tg(css);
+ struct scx_cgroup_init_args args = {
+ .weight = tg->scx.weight,
+ .bw_period_us = tg->scx.bw_period_us,
+ .bw_quota_us = tg->scx.bw_quota_us,
+ .bw_burst_us = tg->scx.bw_burst_us,
+ .sched_idle = tg->scx.idle,
+ };
+
+ if (tg->scx.sched != parent ||
+ !cgroup_is_descendant(css->cgroup, sub_cgrp))
+ continue;
+
+ if (SCX_HAS_OP(sch, cgroup_init)) {
+ ret = SCX_CALL_OP_RET(sch, cgroup_init, NULL, css->cgroup, &args);
+ if (ret) {
+ scx_error(sch, "ops.cgroup_init() failed (%d)", ret);
+ goto err;
+ }
+ }
+ tg->scx.flags |= SCX_TG_SUB_INIT;
+ }
+
+ css_for_each_descendant_post(css, ecss) {
+ struct task_group *tg = css_tg(css);
+
+ /*
+ * SUB_INIT is pass 1's progress mark: pass 2 and the err path
+ * must visit exactly the tgs pass 1 inited.
+ */
+ if (!(tg->scx.flags & SCX_TG_SUB_INIT))
+ continue;
+
+ /* skip the exit if the parent's ops.cgroup_init() failed */
+ if ((tg->scx.flags & SCX_TG_INITED) && SCX_HAS_OP(parent, cgroup_exit))
+ SCX_CALL_OP(parent, cgroup_exit, NULL, css->cgroup);
+ tg->scx.sched = sch;
+ tg->scx.flags |= SCX_TG_INITED;
+ tg->scx.flags &= ~SCX_TG_SUB_INIT;
+ }
+
+ return 0;
+
+err:
+ css_for_each_descendant_post(css, ecss) {
+ struct task_group *tg = css_tg(css);
+
+ if (!(tg->scx.flags & SCX_TG_SUB_INIT))
+ continue;
+
+ if (SCX_HAS_OP(sch, cgroup_exit))
+ SCX_CALL_OP(sch, cgroup_exit, NULL, css->cgroup);
+ tg->scx.flags &= ~SCX_TG_SUB_INIT;
+ }
+ return ret;
+}
+
+/**
+ * scx_cgroup_return_subtree - Return the subtree's cgroups to the parent sched
+ * @sch: sub-scheduler being disabled
+ *
+ * Called while disabling @sch, after the subtree's cgrp->scx_sched's are reset
+ * to the parent sched and before tasks are re-homed, mirroring root disable's
+ * cgroups-before-tasks teardown order. The reverse of
+ * scx_cgroup_claim_subtree(): exit @sch's cgroups from @sch, then init them on
+ * the parent with the current tg->scx.* values, resyncing settings that changed
+ * while @sch had them.
+ *
+ * When an init on the parent fails, the parent is failed - the same policy as
+ * task re-homing. The remaining task_groups are punted: they move to the parent
+ * anyway with %SCX_TG_INITED cleared, as ops.cgroup_init() failed or never ran
+ * for them. A punted task_group gets no cgroup ops. The dying parent's own
+ * disable moves it one sched up, initing it there. Root ends the chain: root
+ * teardown drops cgroup ops entirely and the next enable's bulk init re-inits
+ * every online task_group.
+ *
+ * The task re-home that follows still delivers ops.init_task() to the dying
+ * parent, including for tasks in punted cgroups it never inited - tolerated
+ * like the downstream failures of task punting (see scx_punt_task()).
+ */
+static void scx_cgroup_return_subtree(struct scx_sched *sch)
+{
+ struct cgroup *sub_cgrp = sch_cgroup(sch);
+ struct cgroup_subsys_state *ecss = cgroup_e_css(sub_cgrp, &cpu_cgrp_subsys);
+ struct scx_sched *parent = scx_parent(sch);
+ struct cgroup_subsys_state *css;
+ bool parent_failed = false;
+ int ret;
+
+ css_for_each_descendant_post(css, ecss) {
+ struct task_group *tg = css_tg(css);
+
+ if (tg->scx.sched != sch ||
+ !cgroup_is_descendant(css->cgroup, sub_cgrp))
+ continue;
+
+ /* skip the exit if @sch's ops.cgroup_init() failed for the tg */
+ if ((tg->scx.flags & SCX_TG_INITED) && SCX_HAS_OP(sch, cgroup_exit))
+ SCX_CALL_OP(sch, cgroup_exit, NULL, css->cgroup);
+ tg->scx.sched = parent;
+ tg->scx.flags |= SCX_TG_SUB_INIT;
+ }
+
+ css_for_each_descendant_pre(css, ecss) {
+ struct task_group *tg = css_tg(css);
+ struct scx_cgroup_init_args args = {
+ .weight = tg->scx.weight,
+ .bw_period_us = tg->scx.bw_period_us,
+ .bw_quota_us = tg->scx.bw_quota_us,
+ .bw_burst_us = tg->scx.bw_burst_us,
+ .sched_idle = tg->scx.idle,
+ };
+
+ /* the first pass must have transferred everything */
+ WARN_ON_ONCE(tg->scx.sched == sch);
+
+ /*
+ * SUB_INIT distinguishes the tgs pass 1 moved. The sched test
+ * can't: a tg punted to the parent by an earlier failure would
+ * also match.
+ */
+ if (!(tg->scx.flags & SCX_TG_SUB_INIT))
+ continue;
+ tg->scx.flags &= ~(SCX_TG_SUB_INIT | SCX_TG_INITED);
+
+ /*
+ * A re-init on $parent failed. The task_groups from here on are
+ * punted: they stay on the dying $parent with INITED clear and
+ * move onward when it disables.
+ */
+ if (parent_failed)
+ continue;
+
+ if (SCX_HAS_OP(parent, cgroup_init)) {
+ ret = SCX_CALL_OP_RET(parent, cgroup_init, NULL, css->cgroup, &args);
+ if (ret) {
+ scx_error(parent, "ops.cgroup_init() failed (%d) while disabling a sub-scheduler",
+ ret);
+ parent_failed = true;
+ continue;
+ }
+ }
+ tg->scx.flags |= SCX_TG_INITED;
+ }
+}
+#else
+static inline s32 scx_cgroup_claim_subtree(struct scx_sched *sch) { return 0; }
+static inline void scx_cgroup_return_subtree(struct scx_sched *sch) {}
+#endif
+
+void scx_sub_disable(struct scx_sched *sch)
+{
+ struct scx_sched *parent = scx_parent(sch);
+ struct scx_task_iter sti;
+ struct task_struct *p;
+ int ret;
+
+ /*
+ * Guarantee forward progress and wait for descendants to be disabled.
+ * To limit disruptions, $parent is not bypassed. Tasks are fully
+ * prepped and then inserted back into $parent.
+ */
+ scx_bypass(sch, true);
+ drain_descendants(sch);
+
+ /*
+ * Here, every runnable task is guaranteed to make forward progress and
+ * we can safely use blocking synchronization constructs. Actually
+ * disable ops.
+ */
+ mutex_lock(&scx_enable_mutex);
+ percpu_down_write(&scx_fork_rwsem);
+ scx_cgroup_lock();
+
+ /*
+ * An enable that failed before scx_link_sched() succeeded never owned a
+ * cgroup or task and won't be waited on by an ancestor's
+ * drain_descendants(). Nothing to reparent and walking the tasks can
+ * misbehave as the task ownership invariant (either owned by self or
+ * parent) does not hold. ->sibling can't identify this case - an undone
+ * link leaves it non-empty.
+ */
+ if (!sch->linked)
+ goto dump;
+
+ set_cgroup_sched(sch_cgroup(sch), parent);
+
+ /*
+ * Return the subtree's cgroups before re-homing tasks so that any
+ * ops.init_task() on $parent only sees cgroups it has initialized.
+ */
+ scx_cgroup_return_subtree(sch);
+
+ scx_task_iter_start(&sti, sch->cgrp);
+ while ((p = scx_task_iter_next_locked(&sti))) {
+ struct rq *rq;
+ struct rq_flags rf;
+
+ /* filter out duplicate visits */
+ if (scx_task_on_sched(parent, p))
+ continue;
+
+ /*
+ * By the time control reaches here, all linked descendant
+ * schedulers should have been disabled.
+ */
+ WARN_ON_ONCE(!scx_task_on_sched(sch, p));
+
+ /*
+ * @p is pinned by the iter: css_task_iter_next() takes a
+ * reference and holds it until the next iter_next() call, so
+ * @p->usage is guaranteed > 0.
+ */
+ get_task_struct(p);
+
+ scx_task_iter_unlock(&sti);
+
+ /*
+ * $p is READY or ENABLED on @sch. Initialize for $parent,
+ * disable and exit from @sch, and then switch over to $parent.
+ *
+ * If a task fails to initialize for $parent, the only available
+ * action is disabling $parent too. While this allows disabling
+ * of a child sched to cause the parent scheduler to fail, the
+ * failure can only originate from ops.init_task() of the
+ * parent. A child can't directly affect the parent through its
+ * own failures.
+ */
+ ret = __scx_init_task(parent, p, NULL, false);
+ if (ret) {
+ scx_fail_parent(sch, p, ret);
+ put_task_struct(p);
+ break;
+ }
+
+ rq = task_rq_lock(p, &rf);
+
+ if (scx_get_task_state(p) == SCX_TASK_DEAD) {
+ /*
+ * sched_ext_dead() raced us between __scx_init_task()
+ * and this rq lock and ran exit_task() on @sch (the
+ * sched @p was on at that point), not on $parent.
+ * $parent's just-completed init is owed an exit_task()
+ * and we issue it here.
+ */
+ scx_sub_init_cancel_task(parent, p);
+ task_rq_unlock(rq, p, &rf);
+ put_task_struct(p);
+ continue;
+ }
+
+ scx_rehome_task(parent, p);
+
+ task_rq_unlock(rq, p, &rf);
+ put_task_struct(p);
+ }
+ scx_task_iter_stop(&sti);
+
+dump:
+ scx_disable_dump(sch);
+
+ scx_cgroup_unlock();
+ percpu_up_write(&scx_fork_rwsem);
+
+ /*
+ * All tasks are moved off of @sch but there may still be on-going
+ * operations (e.g. ops.select_cpu()). Drain them by flushing RCU. Use
+ * the expedited version as ancestors may be waiting in bypass mode.
+ * Also, tell the parent that there is no need to keep running bypass
+ * DSQs for us.
+ */
+ synchronize_rcu_expedited();
+ scx_disable_bypass_dsp(sch);
+
+ scx_unlink_sched(sch);
+
+ mutex_unlock(&scx_enable_mutex);
+
+ /*
+ * @sch is now unlinked from the parent's children list. Notify and call
+ * ops.sub_detach/exit(). Note that ops.sub_detach/exit() must be called
+ * after unlinking and releasing all locks. See scx_claim_exit().
+ */
+ wake_up_all(&scx_unlink_waitq);
+
+ if (parent->ops.sub_detach && sch->sub_attached) {
+ struct scx_sub_detach_args sub_detach_args = {
+ .ops = &sch->ops,
+ .cgroup_path = sch->cgrp_path,
+ };
+ SCX_CALL_OP(parent, sub_detach, NULL,
+ &sub_detach_args);
+ }
+
+ scx_log_sched_disable(sch);
+
+ if (sch->ops.exit)
+ SCX_CALL_OP(sch, exit, NULL, sch->exit_info);
+
+ /*
+ * @sch's non-ops programs such as timers and tracers can fire after
+ * ops.exit(). Now that exit is complete, stop scx_prog_sched() from
+ * resolving to @sch and drain in-flight resolvers.
+ */
+ WRITE_ONCE(sch->dead, true);
+ synchronize_rcu();
+
+ if (sch->sub_kset)
+ kobject_del(&sch->sub_kset->kobj);
+ /* not added if enable failed before scx_sched_sysfs_add() */
+ if (sch->kobj.state_in_sysfs)
+ kobject_del(&sch->kobj);
+}
+
+/* verify that a scheduler can be attached to @cgrp and return the parent */
+static struct scx_sched *find_parent_sched(struct cgroup *cgrp)
+{
+ struct scx_sched *parent = scx_cgroup_sched(cgrp);
+ struct scx_sched *pos;
+
+ lockdep_assert_held(&scx_sched_lock);
+
+ /* can't attach twice to the same cgroup */
+ if (parent->cgrp == cgrp)
+ return ERR_PTR(-EBUSY);
+
+ /* does $parent allow sub-scheds? */
+ if (!parent->ops.sub_attach)
+ return ERR_PTR(-EOPNOTSUPP);
+
+ /* can't insert between $parent and its exiting children */
+ list_for_each_entry(pos, &parent->children, sibling)
+ if (cgroup_is_descendant(pos->cgrp, cgrp))
+ return ERR_PTR(-EBUSY);
+
+ return parent;
+}
+
+static bool assert_task_ready_or_enabled(struct task_struct *p)
+{
+ u32 state = scx_get_task_state(p);
+
+ switch (state) {
+ case SCX_TASK_READY:
+ case SCX_TASK_ENABLED:
+ return true;
+ default:
+ WARN_ONCE(true, "sched_ext: Invalid task state %d for %s[%d] during enabling sub sched",
+ state, p->comm, p->pid);
+ return false;
+ }
+}
+
+void scx_sub_enable_workfn(struct kthread_work *work)
+{
+ struct scx_enable_cmd *cmd = container_of(work, struct scx_enable_cmd, work);
+ struct sched_ext_ops *ops = cmd->ops;
+ struct cgroup *cgrp;
+ struct scx_sched *parent, *sch;
+ struct scx_task_iter sti;
+ struct task_struct *p;
+ s32 i, ret;
+
+ mutex_lock(&scx_enable_mutex);
+
+ if (!scx_enabled()) {
+ ret = -ENODEV;
+ goto out_unlock;
+ }
+
+ /* See scx_root_enable_workfn() for the @ops->priv check. */
+ if (rcu_access_pointer(ops->priv)) {
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ cgrp = cgroup_get_from_id(ops->sub_cgroup_id);
+ if (IS_ERR(cgrp)) {
+ ret = PTR_ERR(cgrp);
+ goto out_unlock;
+ }
+
+ raw_spin_lock_irq(&scx_sched_lock);
+ parent = find_parent_sched(cgrp);
+ if (IS_ERR(parent)) {
+ raw_spin_unlock_irq(&scx_sched_lock);
+ ret = PTR_ERR(parent);
+ goto out_put_cgrp;
+ }
+ kobject_get(&parent->kobj);
+ raw_spin_unlock_irq(&scx_sched_lock);
+
+ /*
+ * Flip the hot-path gates before ops->priv is published - the sub's
+ * programs can e.g. kick cpus from that point on. The matching dec is
+ * at the end of scx_sched_free_rcu_work().
+ */
+ static_branch_inc(&__scx_has_subs);
+
+ /* scx_alloc_and_add_sched() consumes @cgrp whether it succeeds or not */
+ sch = scx_alloc_and_add_sched(cmd, cgrp, parent);
+ kobject_put(&parent->kobj);
+ if (IS_ERR(sch)) {
+ static_branch_dec(&__scx_has_subs);
+ ret = PTR_ERR(sch);
+ goto out_unlock;
+ }
+
+ /*
+ * Validate before scx_link_sched() publishes @sch, so an invalid sub
+ * never becomes visible with an unallocated pshard.
+ */
+ ret = scx_validate_ops(sch, ops);
+ if (ret)
+ goto err_disable;
+
+ scx_rescue_check_timeout(sch);
+
+ /*
+ * Allocate pshard[] before scx_link_sched() publishes @sch into the
+ * parent's RCU children list. A concurrent revoke walking the tree
+ * would otherwise dereference sch->pshard[si] while it's still NULL.
+ * Unlike the root path, the cid shard layout is stable at this point.
+ *
+ * scx_alloc_pshards() skips allocation when @sch's arena pool isn't
+ * initialized, so scx_arena_pool_init() must run first.
+ */
+ ret = scx_arena_pool_init(sch);
+ if (ret)
+ goto err_disable;
+
+ ret = scx_alloc_pshards(sch);
+ if (ret)
+ goto err_disable;
+
+ ret = scx_link_sched(sch);
+ if (ret)
+ goto err_disable;
+
+ ret = scx_sched_sysfs_add(sch);
+ if (ret)
+ goto err_disable;
+
+ if (sch->level >= SCX_SUB_MAX_DEPTH) {
+ scx_error(sch, "max nesting depth %d violated",
+ SCX_SUB_MAX_DEPTH);
+ ret = -EINVAL;
+ goto err_disable;
+ }
+
+ scoped_guard(cpus_read_lock) {
+ ret = scx_alloc_kern_arena_objs(sch);
+ if (ret)
+ goto err_disable;
+ }
+
+ if (sch->ops.init) {
+ ret = SCX_CALL_OP_RET(sch, init, NULL);
+ if (ret) {
+ ret = scx_ops_sanitize_err(sch, "init", ret);
+ scx_error(sch, "ops.init() failed (%d)", ret);
+ goto err_disable;
+ }
+ sch->exit_info->flags |= SCX_EFLAG_INITIALIZED;
+ }
+
+ struct scx_sub_attach_args sub_attach_args = {
+ .ops = &sch->ops,
+ .cgroup_path = sch->cgrp_path,
+ };
+
+ ret = SCX_CALL_OP_RET(parent, sub_attach, NULL,
+ &sub_attach_args);
+ if (ret) {
+ ret = scx_ops_sanitize_err(sch, "sub_attach", ret);
+ scx_error(sch, "parent rejected (%d)", ret);
+ goto err_disable;
+ }
+ sch->sub_attached = true;
+
+ scx_bypass(sch, true);
+
+ for (i = SCX_OPI_BEGIN; i < SCX_OPI_END; i++)
+ if (((void (**)(void))ops)[i])
+ set_bit(i, sch->has_op);
+
+ percpu_down_write(&scx_fork_rwsem);
+ scx_cgroup_lock();
+
+ /*
+ * Set cgroup->scx_sched's and check CSS_ONLINE. Either we see
+ * !CSS_ONLINE or scx_cgroup_lifetime_notify() sees and shoots us down.
+ */
+ set_cgroup_sched(sch_cgroup(sch), sch);
+ if (!(cgrp->self.flags & CSS_ONLINE)) {
+ scx_error(sch, "cgroup is not online");
+ ret = -ENODEV;
+ goto err_unlock_and_disable;
+ }
+
+ /*
+ * Take over the subtree's cgroups before any task is claimed,
+ * mirroring root enable's cgroups-before-tasks order.
+ */
+ ret = scx_cgroup_claim_subtree(sch);
+ if (ret)
+ goto err_unlock_and_disable;
+
+ /*
+ * Initialize tasks for the new child $sch without exiting them for
+ * $parent so that the tasks can always be reverted back to $parent
+ * sched on child init failure.
+ */
+ WARN_ON_ONCE(scx_enabling_sub_sched);
+ scx_enabling_sub_sched = sch;
+
+ scx_task_iter_start(&sti, sch->cgrp);
+ while ((p = scx_task_iter_next_locked(&sti))) {
+ struct rq *rq;
+ struct rq_flags rf;
+
+ /*
+ * Task iteration may visit the same task twice when racing
+ * against exiting. Use %SCX_TASK_SUB_INIT to mark tasks which
+ * finished __scx_init_task() and skip if set.
+ *
+ * A task may exit and get freed between __scx_init_task()
+ * completion and scx_enable_task(). In such cases,
+ * scx_disable_and_exit_task() must exit the task for both the
+ * parent and child scheds.
+ */
+ if (p->scx.flags & SCX_TASK_SUB_INIT)
+ continue;
+
+ /* @p is pinned by the iter; see scx_sub_disable() */
+ get_task_struct(p);
+
+ if (!assert_task_ready_or_enabled(p)) {
+ ret = -EINVAL;
+ goto abort;
+ }
+
+ scx_task_iter_unlock(&sti);
+
+ /*
+ * As $p is still on $parent, it can't be transitioned to INIT.
+ * Let's worry about task state later. Use __scx_init_task().
+ */
+ ret = __scx_init_task(sch, p, NULL, false);
+ if (ret)
+ goto abort;
+
+ rq = task_rq_lock(p, &rf);
+
+ if (scx_get_task_state(p) == SCX_TASK_DEAD) {
+ /*
+ * sched_ext_dead() raced us between __scx_init_task()
+ * and this rq lock and ran exit_task() on $parent (the
+ * sched @p was on at that point), not on @sch. @sch's
+ * just-completed init is owed an exit_task() and we
+ * issue it here.
+ */
+ scx_sub_init_cancel_task(sch, p);
+ task_rq_unlock(rq, p, &rf);
+ put_task_struct(p);
+ continue;
+ }
+
+ p->scx.flags |= SCX_TASK_SUB_INIT;
+ task_rq_unlock(rq, p, &rf);
+
+ put_task_struct(p);
+ }
+ scx_task_iter_stop(&sti);
+
+ /*
+ * All tasks are prepped. Disable/exit tasks for $parent and enable for
+ * the new @sch.
+ */
+ scx_task_iter_start(&sti, sch->cgrp);
+ while ((p = scx_task_iter_next_locked(&sti))) {
+ /*
+ * Use clearing of %SCX_TASK_SUB_INIT to detect and skip
+ * duplicate iterations.
+ */
+ if (!(p->scx.flags & SCX_TASK_SUB_INIT))
+ continue;
+
+ scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
+ /*
+ * $p must be either READY or ENABLED. If ENABLED,
+ * __scx_disabled_and_exit_task() first disables and
+ * makes it READY. However, after exiting $p, it will
+ * leave $p as READY.
+ */
+ assert_task_ready_or_enabled(p);
+ __scx_disable_and_exit_task(parent, p);
+
+ /*
+ * $p is now only initialized for @sch and READY, which
+ * is what we want. Assign it to @sch and, if it's on
+ * the ext class, enable. A non-ext task, possible under
+ * an %SCX_OPS_SWITCH_PARTIAL root, stays READY and is
+ * enabled by switching_to_scx() if it switches over.
+ */
+ scx_set_task_sched(p, sch);
+ if (p->sched_class == &ext_sched_class)
+ scx_enable_task(sch, p);
+
+ p->scx.flags &= ~SCX_TASK_SUB_INIT;
+ }
+ }
+ scx_task_iter_stop(&sti);
+
+ scx_enabling_sub_sched = NULL;
+
+ scx_cgroup_unlock();
+ percpu_up_write(&scx_fork_rwsem);
+
+ scx_bypass(sch, false);
+
+ /* @sch is enabled; deliver any caps owed since its sub_attach() */
+ scx_sub_seed_caps(sch);
+
+ pr_info("sched_ext: BPF sub-scheduler \"%s\" enabled\n", sch->ops.name);
+ kobject_uevent(&sch->kobj, KOBJ_ADD);
+ ret = 0;
+ goto out_unlock;
+
+out_put_cgrp:
+ cgroup_put(cgrp);
+out_unlock:
+ mutex_unlock(&scx_enable_mutex);
+ cmd->ret = ret;
+ return;
+
+abort:
+ put_task_struct(p);
+ scx_task_iter_stop(&sti);
+
+ /*
+ * Undo __scx_init_task() for tasks we marked. scx_enable_task() never
+ * ran for @sch on them, so calling scx_disable_task() here would invoke
+ * ops.disable() without a matching ops.enable(). scx_enabling_sub_sched
+ * must stay set until SUB_INIT is cleared from every marked task -
+ * scx_disable_and_exit_task() reads it when a task exits concurrently.
+ */
+ scx_task_iter_start(&sti, sch->cgrp);
+ while ((p = scx_task_iter_next_locked(&sti))) {
+ if (p->scx.flags & SCX_TASK_SUB_INIT) {
+ scx_sub_init_cancel_task(sch, p);
+ p->scx.flags &= ~SCX_TASK_SUB_INIT;
+ }
+ }
+ scx_task_iter_stop(&sti);
+ scx_enabling_sub_sched = NULL;
+err_unlock_and_disable:
+ /* we'll soon enter disable path, keep bypass on */
+ scx_cgroup_unlock();
+ percpu_up_write(&scx_fork_rwsem);
+err_disable:
+ mutex_unlock(&scx_enable_mutex);
+ /*
+ * Some enable failures only return an errno (e.g. -ENOMEM from an
+ * allocation) without calling scx_error(). Record it so
+ * scx_flush_disable_work() runs the disable and ops.exit() fires.
+ */
+ scx_error(sch, "scx_sub_enable() failed (%d)", ret);
+ scx_flush_disable_work(sch);
+ cmd->ret = 0;
+}
+
+/**
+ * scx_cgroup_task_migrating - Prepare a task for a cgroup migration
+ * @ctx: migration being prepared
+ *
+ * A task's sched must match its cgroup's owner, so a migration that crosses a
+ * sched boundary re-homes the task once committed. Run the fallible part here,
+ * before the migration commits: initialize the task for the destination sched.
+ * A rejection fails the cgroup.procs write.
+ */
+static s32 scx_cgroup_task_migrating(struct cgroup_task_migrate_ctx *ctx)
+{
+ struct task_struct *p = ctx->task;
+ struct scx_sched *to;
+ int ret;
+
+ /*
+ * Cleared under scx_cgroup_lock() before root disable starts tearing
+ * down tasks. As cgroup_mutex is held, a set flag guarantees that the
+ * teardown loop is not running concurrently.
+ */
+ if (!scx_cgroup_enabled)
+ return NOTIFY_OK;
+
+ to = scx_cgroup_sched(ctx->dst_dcgrp);
+ if (scx_task_on_sched(to, p))
+ return NOTIFY_OK;
+
+ ret = __scx_init_task(to, p, ctx->dst_dcgrp, false);
+ if (ret)
+ return notifier_from_errno(ret);
+
+ return NOTIFY_OK;
+}
+
+/**
+ * scx_cgroup_task_migrated - Re-home a task that changed cgroups
+ * @ctx: committed migration
+ *
+ * Move the task to its new cgroup's sched, which scx_cgroup_task_migrating()
+ * already initialized it for. Can't fail.
+ *
+ * This is safe against all phases of the destination sched's destruction. A
+ * disable resets cgroup ownership to the parent and re-homes tasks in one
+ * scx_cgroup_lock() section. If that section already ran, the destination would
+ * be the parent. Otherwise, the re-home loop is still ahead and guaranteed to
+ * visit the task, now in the destination cgroup.
+ */
+static void scx_cgroup_task_migrated(struct cgroup_task_migrate_ctx *ctx)
+{
+ struct task_struct *p = ctx->task;
+ struct scx_sched *to;
+ struct rq *rq;
+ struct rq_flags rf;
+
+ if (!scx_cgroup_enabled)
+ return;
+
+ to = scx_cgroup_sched(ctx->dst_dcgrp);
+ if (scx_task_on_sched(to, p))
+ return;
+
+ rq = task_rq_lock(p, &rf);
+ scx_rehome_task(to, p);
+ task_rq_unlock(rq, p, &rf);
+}
+
+/**
+ * scx_cgroup_task_migrate_canceled - Undo migration preparation
+ * @ctx: canceled migration
+ *
+ * The migration failed after scx_cgroup_task_migrating() initialized the task
+ * for the destination sched. The task stays on its current sched in the source
+ * cgroup. Undo the destination's init.
+ */
+static void scx_cgroup_task_migrate_canceled(struct cgroup_task_migrate_ctx *ctx)
+{
+ struct task_struct *p = ctx->task;
+ struct scx_sched *to;
+ struct rq *rq;
+ struct rq_flags rf;
+
+ if (!scx_cgroup_enabled)
+ return;
+
+ to = scx_cgroup_sched(ctx->dst_dcgrp);
+ if (scx_task_on_sched(to, p))
+ return;
+
+ rq = task_rq_lock(p, &rf);
+ scx_sub_init_cancel_task(to, p);
+ task_rq_unlock(rq, p, &rf);
+}
+
+static s32 scx_cgroup_lifetime_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct cgroup *cgrp = data;
+ struct cgroup *parent = cgroup_parent(cgrp);
+ struct scx_sched *sch;
+
+ if (!cgroup_on_dfl(cgrp))
+ return NOTIFY_OK;
+
+ switch (action) {
+ case CGROUP_LIFETIME_ONLINE:
+ /* inherit ->scx_sched from $parent */
+ if (parent)
+ rcu_assign_pointer(cgrp->scx_sched, scx_cgroup_sched(parent));
+ break;
+ case CGROUP_LIFETIME_OFFLINE:
+ /* if there is a sched attached, shoot it down */
+ sch = scx_cgroup_sched(cgrp);
+ if (sch && sch->cgrp == cgrp)
+ scx_exit(sch, SCX_EXIT_UNREG_KERN,
+ SCX_ECODE_RSN_CGROUP_OFFLINE,
+ "cgroup %llu going offline", cgroup_id(cgrp));
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block scx_cgroup_lifetime_nb = {
+ .notifier_call = scx_cgroup_lifetime_notify,
+};
+
+static s32 scx_cgroup_task_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct cgroup_task_migrate_ctx *ctx = data;
+
+ switch (action) {
+ case CGROUP_TASK_MIGRATING:
+ return scx_cgroup_task_migrating(ctx);
+ case CGROUP_TASK_MIGRATED:
+ scx_cgroup_task_migrated(ctx);
+ break;
+ case CGROUP_TASK_MIGRATE_CANCELED:
+ scx_cgroup_task_migrate_canceled(ctx);
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block scx_cgroup_task_nb = {
+ .notifier_call = scx_cgroup_task_notify,
+};
+
+static s32 __init scx_cgroup_notifier_init(void)
+{
+ s32 ret;
+
+ ret = blocking_notifier_chain_register(&cgroup_lifetime_notifier,
+ &scx_cgroup_lifetime_nb);
+ if (ret)
+ return ret;
+
+ return blocking_notifier_chain_register(&cgroup_task_notifier,
+ &scx_cgroup_task_nb);
+}
+core_initcall(scx_cgroup_notifier_init);
+
+static void scx_pstack_recursion(struct bpf_prog *prog, const char *op)
+{
+ struct scx_sched *sch;
+
+ guard(rcu)();
+ sch = scx_prog_sched(prog->aux);
+ if (unlikely(!sch))
+ return;
+
+ scx_error(sch, "%s recursion detected", op);
+}
+
+void scx_pstack_recursion_on_dispatch(struct bpf_prog *prog)
+{
+ scx_pstack_recursion(prog, "dispatch");
+}
+
+void scx_pstack_recursion_on_caps_updated(struct bpf_prog *prog)
+{
+ scx_pstack_recursion(prog, "sub_caps_updated");
+}
+
+__bpf_kfunc_start_defs();
+
+/**
+ * scx_bpf_sub_dispatch - Trigger dispatching on a child scheduler
+ * @cgroup_id: cgroup ID of the child scheduler to dispatch
+ * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
+ *
+ * Allows a parent scheduler to trigger dispatching on one of its direct
+ * child schedulers. The child scheduler runs its dispatch operation to
+ * move tasks from dispatch queues to the local runqueue.
+ *
+ * Returns: true on success, false if cgroup_id is invalid, not a direct
+ * child, or caller lacks dispatch permission.
+ */
+__bpf_kfunc bool scx_bpf_sub_dispatch(u64 cgroup_id, const struct bpf_prog_aux *aux)
+{
+ struct rq *rq = scx_locked_rq();
+ struct scx_sched *parent, *child;
+
+ guard(rcu)();
+ parent = scx_prog_sched(aux);
+ if (unlikely(!parent))
+ return false;
+
+ child = scx_find_sub_sched(cgroup_id);
+
+ if (unlikely(!child))
+ return false;
+
+ if (unlikely(scx_parent(child) != parent)) {
+ scx_error(parent, "trying to dispatch a distant sub-sched on cgroup %llu",
+ cgroup_id);
+ return false;
+ }
+
+ /*
+ * Skip a child that does not effectively hold the base cap on this cpu:
+ * its inserts would only be rejected. ecaps are synced at the top of
+ * dispatch_one() before dispatch, so this reflects the in-effect state.
+ */
+ if (scx_missing_caps(child, cpu_of(rq), SCX_CAP_BASE))
+ return false;
+
+ return scx_dispatch_sched(child, rq, rq->scx.sub_dispatch_prev, true) !=
+ SCX_DSP_NONE;
+}
+
+/* Validate common inputs. On success, *parent_out and *child_out are set. */
+static s32 sub_cap_preamble(u64 cgroup_id, u64 caps, const struct bpf_prog_aux *aux,
+ struct scx_sched **parent_out, struct scx_sched **child_out)
+{
+ struct scx_sched *parent, *child;
+
+ parent = scx_prog_sched(aux);
+ if (unlikely(!parent))
+ return -ENODEV;
+
+ if (!scx_is_cid_type()) {
+ scx_error(parent, "sub-cap kfuncs require a cid-form scheduler");
+ return -EOPNOTSUPP;
+ }
+
+ child = scx_find_sub_sched(cgroup_id);
+ if (unlikely(!child))
+ return -ENODEV;
+
+ if (unlikely(scx_parent(child) != parent)) {
+ scx_error(parent, "%s: sub-%llu is not a direct child",
+ parent->cgrp_path, cgroup_id);
+ return -EINVAL;
+ }
+
+ if (unlikely(caps & ~__SCX_CAP_ALL)) {
+ scx_error(parent, "invalid caps 0x%llx", caps);
+ return -EINVAL;
+ }
+
+ *parent_out = parent;
+ *child_out = child;
+ return 0;
+}
+
+/**
+ * scx_bpf_sub_grant - Grant @caps on a cmask's cids to a direct child
+ * @cgroup_id: cgroup id of the direct child sub-sched
+ * @caps: bitmask of SCX_CAP_* to grant
+ * @cmask__arena: cid cmask to grant @caps on
+ * @denied_out__arena__nullable: optional cmask accumulating refused cids
+ * @aux: implicit BPF argument
+ *
+ * A cid in @cmask__arena is granted to the child only if the parent holds every
+ * requested cap on it. Refused cids are OR'd into the denied mask when
+ * provided. Refusals outside the denied mask's range are not recorded.
+ *
+ * All-or-nothing keeps the caller-visible result binary per cid, so the denied
+ * mask is one mask to interpret rather than a per-cap matrix.
+ *
+ * Return 0 on full success, -EPERM if any cid was refused, or a negative
+ * errno on other failures.
+ */
+__bpf_kfunc s32 scx_bpf_sub_grant(u64 cgroup_id, u64 caps,
+ const struct scx_cmask *cmask__arena,
+ struct scx_cmask *denied_out__arena__nullable,
+ const struct bpf_prog_aux *aux)
+{
+ struct scx_cmask_ref ref, denied_ref;
+ struct scx_sched *parent, *child;
+ bool any_denied = false;
+ LIST_HEAD(to_deliver);
+ s32 si, ret;
+
+ guard(irqsave)();
+
+ ret = sub_cap_preamble(cgroup_id, caps, aux, &parent, &child);
+ if (ret)
+ return ret;
+
+ ret = scx_cmask_ref_init(parent, cmask__arena, &ref);
+ if (ret) {
+ scx_error(parent, "invalid cmask (%d)", ret);
+ return ret;
+ }
+
+ if (denied_out__arena__nullable) {
+ ret = scx_cmask_ref_init(parent, denied_out__arena__nullable, &denied_ref);
+ if (ret) {
+ scx_error(parent, "invalid denied_out (%d)", ret);
+ return ret;
+ }
+ }
+
+ /* apply the grant one shard at a time */
+ for (si = ref.shard_first; si < ref.shard_end; si++) {
+ SCX_CMASK_DEFINE_SHARD(slice, 0, SCX_CID_SHARD_MAX_CPUS);
+ struct scx_pshard *pps = parent->pshard[si];
+ struct scx_pshard *cps = child->pshard[si];
+ u64 granted_caps = 0;
+ u32 cap_bit;
+
+ scx_cmask_ref_shard(&ref, si, slice);
+ if (scx_cmask_empty(slice))
+ continue;
+
+ SCX_CMASK_DEFINE_SHARD(granted_cids, slice->base, slice->nr_cids);
+ SCX_CMASK_DEFINE_SHARD(changed_cids, slice->base, slice->nr_cids);
+ SCX_CMASK_DEFINE_SHARD(delta, slice->base, slice->nr_cids);
+
+ scx_cmask_copy(granted_cids, slice);
+
+ scoped_guard (raw_spinlock, &pps->lock) {
+ guard(raw_spinlock_nested)(&cps->lock);
+
+ /*
+ * Narrow granted_cids to cids the parent holds every
+ * requested cap on. All-or-nothing per cid.
+ */
+ scx_for_each_cap_bit(cap_bit, caps)
+ scx_cmask_and(granted_cids, &pps->caps[cap_bit].cmask);
+
+ /*
+ * For each requested cap, fold the newly-set cids into
+ * the child and accumulate the delta.
+ */
+ scx_for_each_cap_bit(cap_bit, caps) {
+ struct scx_cmask *ccm = &cps->caps[cap_bit].cmask;
+
+ scx_cmask_copy(delta, granted_cids);
+ scx_cmask_andnot(delta, ccm);
+ if (scx_cmask_empty(delta))
+ continue;
+
+ scx_cmask_or(ccm, delta);
+ scx_cmask_or(changed_cids, delta);
+ granted_caps |= BIT_U64(cap_bit);
+ }
+
+ if (granted_caps) {
+ s32 cid;
+
+ caps_updated_record(cps, changed_cids, granted_caps,
+ &to_deliver);
+ /*
+ * The sync arms an update_idle() re-notify if
+ * the cid gains baseline access, so the holder
+ * learns of an already-idle cid.
+ */
+ scx_cmask_for_each_cid(cid, changed_cids)
+ queue_sync_ecaps(child, cid);
+ }
+ }
+
+ /* record cids that didn't make it into the denied mask */
+ if (!scx_cmask_subset(slice, granted_cids)) {
+ any_denied = true;
+ if (denied_out__arena__nullable) {
+ SCX_CMASK_DEFINE_SHARD(denied, slice->base, slice->nr_cids);
+
+ scx_cmask_copy(denied, slice);
+ scx_cmask_andnot(denied, granted_cids);
+ scx_cmask_ref_or(&denied_ref, denied);
+ }
+ }
+ }
+
+ caps_updated_deliver(&to_deliver);
+
+ return any_denied ? -EPERM : 0;
+}
+
+/**
+ * scx_bpf_sub_revoke - Revoke @caps on a cmask's cids from a direct child
+ * @cgroup_id: cgroup id of the direct child sub-sched
+ * @caps: bitmask of SCX_CAP_* to revoke
+ * @cmask__arena: cid cmask to revoke @caps on
+ * @aux: implicit BPF argument
+ *
+ * Clear @caps bits on @cmask__arena from the child named by @cgroup_id and all
+ * its descendants. The origin parent's pshard lock is held across the subtree
+ * walk so a concurrent grant from the origin parent observes the revoked state.
+ */
+__bpf_kfunc void scx_bpf_sub_revoke(u64 cgroup_id, u64 caps,
+ const struct scx_cmask *cmask__arena,
+ const struct bpf_prog_aux *aux)
+{
+ struct scx_cmask_ref ref;
+ struct scx_sched *parent, *child, *pos;
+ LIST_HEAD(to_deliver);
+ s32 si, ret;
+
+ guard(irqsave)();
+
+ if (sub_cap_preamble(cgroup_id, caps, aux, &parent, &child))
+ return;
+
+ ret = scx_cmask_ref_init(parent, cmask__arena, &ref);
+ if (ret) {
+ scx_error(parent, "invalid cmask (%d)", ret);
+ return;
+ }
+
+ /* per-shard, walk child's subtree and clear @caps */
+ for (si = ref.shard_first; si < ref.shard_end; si++) {
+ SCX_CMASK_DEFINE_SHARD(slice, 0, SCX_CID_SHARD_MAX_CPUS);
+
+ scx_cmask_ref_shard(&ref, si, slice);
+ if (scx_cmask_empty(slice))
+ continue;
+
+ /*
+ * Pre-order with subtree skip: a descendant that cleared
+ * nothing means no descendant of it can hold @caps on these
+ * cids either.
+ */
+ guard(raw_spinlock)(&parent->pshard[si]->lock);
+ pos = scx_next_descendant_pre(NULL, child);
+ while (pos) {
+ struct scx_pshard *ps = pos->pshard[si];
+ SCX_CMASK_DEFINE_SHARD(changed_cids, slice->base, slice->nr_cids);
+ SCX_CMASK_DEFINE_SHARD(delta, slice->base, slice->nr_cids);
+ u64 revoked_caps = 0;
+ u32 cap_bit;
+
+ scoped_guard (raw_spinlock_nested, &ps->lock) {
+ /*
+ * For each cap, clear lost cids and accumulate
+ * the per-cap diff for notification.
+ */
+ scx_for_each_cap_bit(cap_bit, caps) {
+ struct scx_cmask *cm = &ps->caps[cap_bit].cmask;
+
+ scx_cmask_copy(delta, cm);
+ scx_cmask_and(delta, slice);
+ if (scx_cmask_empty(delta))
+ continue;
+
+ scx_cmask_andnot(cm, delta);
+ scx_cmask_or(changed_cids, delta);
+ revoked_caps |= BIT_U64(cap_bit);
+ }
+
+ if (revoked_caps) {
+ s32 cid;
+
+ caps_updated_record(ps, changed_cids, revoked_caps,
+ &to_deliver);
+ scx_cmask_for_each_cid(cid, changed_cids)
+ queue_sync_ecaps(pos, cid);
+ }
+ }
+
+ if (revoked_caps)
+ pos = scx_next_descendant_pre(pos, child);
+ else
+ pos = scx_skip_subtree_pre(pos, child);
+ }
+ }
+
+ caps_updated_deliver(&to_deliver);
+}
+
+/**
+ * scx_bpf_sub_caps - Read self's or a direct child's cap cmasks
+ * @cgroup_id: 0 for self, or a direct child's cgroup id
+ * @caps: one or more SCX_CAP_* bits
+ * @out__arena: cmask to receive the union of @caps within its range
+ * @aux: implicit BPF argument
+ *
+ * Read the cap cmasks granted on each cid for self (@cgroup_id 0) or a direct
+ * child - the literal granted set. A sched can read only itself or a direct
+ * child.
+ *
+ * Return 0, -ENODEV if @cgroup_id names no direct child, or -EINVAL on bad
+ * inputs.
+ */
+__bpf_kfunc s32 scx_bpf_sub_caps(u64 cgroup_id, u64 caps, struct scx_cmask *out__arena,
+ const struct bpf_prog_aux *aux)
+{
+ struct scx_cmask_ref ref;
+ struct scx_sched *sch, *target;
+ struct scx_pshard **pshard;
+ s32 si, ret;
+
+ guard(irqsave)();
+
+ sch = scx_prog_sched(aux);
+ if (unlikely(!sch))
+ return -ENODEV;
+
+ if (!scx_is_cid_type()) {
+ scx_error(sch, "sub-cap kfuncs require a cid-form scheduler");
+ return -EOPNOTSUPP;
+ }
+
+ if (unlikely(caps & ~__SCX_CAP_ALL)) {
+ scx_error(sch, "invalid caps 0x%llx", caps);
+ return -EINVAL;
+ }
+
+ /* @cgroup_id 0 reads self, otherwise a direct child */
+ if (cgroup_id) {
+ target = scx_find_sub_sched(cgroup_id);
+ if (unlikely(!target))
+ return -ENODEV;
+ if (unlikely(scx_parent(target) != sch)) {
+ scx_error(sch, "%s: sub-%llu is not a direct child",
+ sch->cgrp_path, cgroup_id);
+ return -EINVAL;
+ }
+ } else {
+ target = sch;
+ }
+
+ /*
+ * The target's caps storage may not be set up yet (e.g. a self-read
+ * during ops.init_cids()). Pairs with the publish in
+ * scx_alloc_pshards(): a non-NULL pshard has every element set and the
+ * acquire also orders the cid table reads below against it.
+ */
+ pshard = smp_load_acquire(&target->pshard);
+ if (unlikely(!pshard)) {
+ scx_error(sch, "scx_bpf_sub_caps() called before caps storage is initialized");
+ return -ENODEV;
+ }
+
+ ret = scx_cmask_ref_init(sch, out__arena, &ref);
+ if (ret) {
+ scx_error(sch, "invalid out (%d)", ret);
+ return ret;
+ }
+
+ for (si = ref.shard_first; si < ref.shard_end; si++) {
+ const struct scx_cid_shard *shard =
+ &rcu_dereference_all(scx_cid_shard_ranges)[si];
+ SCX_CMASK_DEFINE_SHARD(local_out, shard->base_cid, shard->nr_cids);
+ u32 cap_bit;
+
+ scx_for_each_cap_bit(cap_bit, caps)
+ scx_cmask_or(local_out, &pshard[si]->caps[cap_bit].cmask);
+ scx_cmask_ref_copy(&ref, local_out);
+ }
+ return 0;
+}
+
+/**
+ * scx_bpf_sub_kill_bstr - Kill a direct child sub-scheduler
+ * @cgroup_id: cgroup id of the direct child to kill
+ * @fmt: reason message format string
+ * @data: format string parameters packaged using ___bpf_fill() macro
+ * @data__sz: @data len, must end in '__sz' for the verifier
+ * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
+ *
+ * Evict a direct child sub-scheduler, disabling it with the supplied reason.
+ * The child and its subtree are torn down asynchronously through the usual
+ * disable path.
+ *
+ * Unlike scx_bpf_exit(), no exit code is taken: the child is a separate
+ * scheduler with its own exit-code semantics, so a code chosen by the parent
+ * would have no defined meaning. The reason string carries the intent.
+ *
+ * Return 0 on success or -ENODEV if @cgroup_id names no sub-scheduler, which
+ * can race with the child detaching on its own and so is not a scheduler error.
+ * Naming a sched that exists but is not a direct child aborts the parent.
+ */
+__printf(2, 0)
+__bpf_kfunc s32 scx_bpf_sub_kill_bstr(u64 cgroup_id, char *fmt,
+ unsigned long long *data, u32 data__sz,
+ const struct bpf_prog_aux *aux)
+{
+ struct scx_sched *parent, *child;
+
+ guard(rcu)();
+
+ parent = scx_prog_sched(aux);
+ if (unlikely(!parent))
+ return -ENODEV;
+
+ if (!scx_is_cid_type()) {
+ scx_error(parent, "sub-cap kfuncs require a cid-form scheduler");
+ return -EOPNOTSUPP;
+ }
+
+ child = scx_find_sub_sched(cgroup_id);
+ if (unlikely(!child))
+ return -ENODEV;
+
+ if (unlikely(scx_parent(child) != parent)) {
+ scx_error(parent, "%s: sub-%llu is not a direct child",
+ parent->cgrp_path, cgroup_id);
+ return -EINVAL;
+ }
+
+ scx_exit_bstr(child, SCX_EXIT_PARENT_KILL, 0, parent, fmt, data, data__sz);
+ return 0;
+}
+
+__bpf_kfunc_end_defs();
+
+#else /* !CONFIG_EXT_SUB_SCHED */
+
+__bpf_kfunc_start_defs();
+
+__bpf_kfunc s32 scx_bpf_sub_grant(u64 cgroup_id, u64 caps,
+ const struct scx_cmask *cmask__arena,
+ struct scx_cmask *denied_out__arena__nullable,
+ const struct bpf_prog_aux *aux)
+{
+ return -EOPNOTSUPP;
+}
+
+__bpf_kfunc void scx_bpf_sub_revoke(u64 cgroup_id, u64 caps,
+ const struct scx_cmask *cmask__arena,
+ const struct bpf_prog_aux *aux)
+{
+}
+
+__bpf_kfunc s32 scx_bpf_sub_caps(u64 cgroup_id, u64 caps, struct scx_cmask *out__arena,
+ const struct bpf_prog_aux *aux)
+{
+ return -EOPNOTSUPP;
+}
+
+__bpf_kfunc s32 scx_bpf_sub_kill_bstr(u64 cgroup_id, char *fmt,
+ unsigned long long *data, u32 data__sz,
+ const struct bpf_prog_aux *aux)
+{
+ return -EOPNOTSUPP;
+}
+
+__bpf_kfunc_end_defs();
+
+#endif /* CONFIG_EXT_SUB_SCHED */
diff --git a/kernel/sched/ext/sub.h b/kernel/sched/ext/sub.h
new file mode 100644
index 000000000000..f7bcdfda8dd8
--- /dev/null
+++ b/kernel/sched/ext/sub.h
@@ -0,0 +1,232 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * BPF extensible scheduler class: Documentation/scheduler/sched-ext.rst
+ *
+ * Sub-scheduler hierarchy support.
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ * Copyright (c) 2026 Tejun Heo <tj@kernel.org>
+ */
+#ifndef _KERNEL_SCHED_EXT_SUB_H
+#define _KERNEL_SCHED_EXT_SUB_H
+
+#include "internal.h"
+
+#ifdef CONFIG_EXT_SUB_SCHED
+
+struct scx_sched *scx_skip_subtree_pre(struct scx_sched *pos, struct scx_sched *root);
+struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos, struct scx_sched *root);
+void scx_set_task_sched(struct task_struct *p, struct scx_sched *sch);
+struct cgroup *sch_cgroup(struct scx_sched *sch);
+void set_cgroup_sched(struct cgroup *cgrp, struct scx_sched *sch);
+void scx_pstack_recursion_on_dispatch(struct bpf_prog *prog);
+void scx_pstack_recursion_on_caps_updated(struct bpf_prog *prog);
+void drain_descendants(struct scx_sched *sch);
+void scx_sub_disable(struct scx_sched *sch);
+void scx_sub_enable_workfn(struct kthread_work *work);
+bool scx_bpf_sub_dispatch(u64 cgroup_id, const struct bpf_prog_aux *aux);
+void scx_free_pshards(struct scx_sched *sch);
+s32 scx_alloc_pshards(struct scx_sched *sch);
+void scx_init_root_caps(struct scx_sched *sch);
+void scx_process_sync_ecaps(struct rq *rq, struct task_struct *prev);
+void scx_unbypass_replay_ecaps(struct rq *rq, struct scx_sched *sch);
+void scx_online_ecaps(struct rq *rq);
+void scx_offline_ecaps(struct rq *rq);
+void scx_discard_ecaps_to_sync(s32 cpu, struct scx_sched_pcpu *pcpu);
+void scx_discard_stale_ecaps_syncs(void);
+struct scx_dispatch_q *scx_resolve_local_dsq(struct scx_sched *sch, struct rq *rq,
+ struct task_struct *p, u64 *enq_flags);
+bool scx_task_reenq_on_cap_revoke(struct rq *rq, struct task_struct *p);
+void scx_reenq_reject(struct rq *rq);
+void scx_rescue_charge(struct rq *rq, s64 delta_exec);
+void scx_rescue_end(struct rq *rq);
+bool scx_rescue_keep(struct rq *rq, struct task_struct *p);
+void scx_rescue_flush(struct rq *rq);
+void scx_rescue_dump(struct seq_buf *s, struct rq *rq);
+void scx_rescue_set_knobs(struct scx_sched *sch);
+void scx_rescue_init(struct rq *rq);
+
+/*
+ * cgrp->scx_sched is written by root/sub enable/disable under all of
+ * scx_enable_mutex, scx_fork_rwsem and cgroup_mutex. A new cgroup inherits the
+ * parent's sched under just cgroup_mutex but is not yet reachable by the other
+ * two lock holders. Any one of the three locks stabilizes the association.
+ */
+static inline struct scx_sched *scx_cgroup_sched(struct cgroup *cgrp)
+{
+ return rcu_dereference_check(cgrp->scx_sched,
+ lockdep_is_held(&cgroup_mutex) ||
+ percpu_rwsem_is_held(&scx_fork_rwsem) ||
+ lockdep_is_held(&scx_enable_mutex));
+}
+
+static inline const char *sch_cgrp_path(struct scx_sched *sch)
+{
+ return sch->cgrp_path;
+}
+
+/* a dying sub's hot-path influence ends in scx_sched_free_rcu_work() */
+static inline void scx_dec_has_subs(struct scx_sched *sch)
+{
+ if (sch->level)
+ static_branch_dec(&__scx_has_subs);
+}
+
+#else /* CONFIG_EXT_SUB_SCHED */
+
+static inline struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos, struct scx_sched *root) { return pos ? NULL : root; }
+static inline struct scx_sched *scx_skip_subtree_pre(struct scx_sched *pos, struct scx_sched *root) { return NULL; }
+static inline void scx_set_task_sched(struct task_struct *p, struct scx_sched *sch) {}
+static inline struct cgroup *sch_cgroup(struct scx_sched *sch) { return NULL; }
+static inline const char *sch_cgrp_path(struct scx_sched *sch) { return "/"; }
+static inline void set_cgroup_sched(struct cgroup *cgrp, struct scx_sched *sch) {}
+static inline void drain_descendants(struct scx_sched *sch) { }
+static inline void scx_sub_disable(struct scx_sched *sch) { }
+static inline void scx_free_pshards(struct scx_sched *sch) {}
+static inline s32 scx_alloc_pshards(struct scx_sched *sch) { return 0; }
+static inline void scx_init_root_caps(struct scx_sched *sch) {}
+static inline void scx_process_sync_ecaps(struct rq *rq, struct task_struct *prev) {}
+static inline void scx_unbypass_replay_ecaps(struct rq *rq, struct scx_sched *sch) {}
+static inline void scx_online_ecaps(struct rq *rq) {}
+static inline void scx_offline_ecaps(struct rq *rq) {}
+static inline void scx_discard_ecaps_to_sync(s32 cpu, struct scx_sched_pcpu *pcpu) {}
+static inline void scx_discard_stale_ecaps_syncs(void) {}
+static inline struct scx_dispatch_q *scx_resolve_local_dsq(struct scx_sched *sch, struct rq *rq, struct task_struct *p, u64 *enq_flags) { return &rq->scx.local_dsq; }
+static inline bool scx_task_reenq_on_cap_revoke(struct rq *rq, struct task_struct *p) { return false; }
+static inline void scx_reenq_reject(struct rq *rq) {}
+static inline void scx_rescue_charge(struct rq *rq, s64 delta_exec) {}
+static inline void scx_rescue_end(struct rq *rq) {}
+static inline bool scx_rescue_keep(struct rq *rq, struct task_struct *p) { return false; }
+static inline void scx_rescue_flush(struct rq *rq) {}
+static inline void scx_rescue_dump(struct seq_buf *s, struct rq *rq) {}
+static inline void scx_rescue_set_knobs(struct scx_sched *sch) {}
+static inline void scx_rescue_init(struct rq *rq) {}
+static inline void scx_dec_has_subs(struct scx_sched *sch) {}
+
+#endif /* CONFIG_EXT_SUB_SCHED */
+
+/**
+ * scx_for_each_descendant_pre - pre-order walk of a sched's descendants
+ * @pos: iteration cursor
+ * @root: sched to walk the descendants of
+ *
+ * Walk @root's descendants. @root is included in the iteration and the first
+ * node to be visited. Must be called with scx_enable_mutex, scx_sched_lock, or
+ * RCU read lock.
+ */
+#define scx_for_each_descendant_pre(pos, root) \
+ for ((pos) = scx_next_descendant_pre(NULL, (root)); (pos); \
+ (pos) = scx_next_descendant_pre((pos), (root)))
+
+#ifdef CONFIG_EXT_SUB_SCHED
+
+/**
+ * scx_missing_caps - The caps in @needed that @sch lacks on @cpu
+ * @sch: sched to test
+ * @cpu: cpu to test on
+ * @needed: bitmask of SCX_CAP_* values
+ *
+ * Return the caps in @needed that @sch lacks for @cpu, 0 if it holds them all.
+ */
+static inline u64 scx_missing_caps(struct scx_sched *sch, s32 cpu, u64 needed)
+{
+ u64 ecaps;
+
+ /* no sub-scheds, no missing caps */
+ if (!scx_has_subs())
+ return 0;
+
+ /* root holds every cap on every cpu */
+ if (!sch->level)
+ return 0;
+
+ ecaps = READ_ONCE(per_cpu_ptr(sch->pcpu, cpu)->ecaps);
+
+ return needed & ~ecaps;
+}
+
+/*
+ * Cap semantics: which caps an action requires, and which caps a cap implies.
+ * Keep all such mappings collected here.
+ */
+
+/* map @enq_flags to the SCX_CAP_* bit required for the local-DSQ insert */
+static inline u64 scx_caps_for_enq(u64 enq_flags)
+{
+ /* a restored task must be put into the local DSQ regardless of caps */
+ if (unlikely(enq_flags & SCX_ENQ_IGNORE_CAPS))
+ return 0;
+ if (enq_flags & SCX_ENQ_IMMED)
+ return SCX_CAP_ENQ_IMMED;
+ return SCX_CAP_ENQ;
+}
+
+/* map queued @p to the SCX_CAP_* bit required to stay on its local DSQ */
+static inline u64 scx_caps_for_task(struct task_struct *p)
+{
+ if (p->scx.flags & SCX_TASK_IMMED)
+ return SCX_CAP_ENQ_IMMED;
+ return SCX_CAP_ENQ;
+}
+
+/* the cap @sch needs to preempt @rq's current task, 0 if none */
+static inline u64 scx_caps_for_preempt(struct scx_sched *sch, struct rq *rq, u64 enq_flags)
+{
+ struct task_struct *curr = rq->curr;
+
+ /* a kernel-forced placement preempts regardless of caps */
+ if (unlikely(enq_flags & SCX_ENQ_IGNORE_CAPS))
+ return 0;
+ /* a non-ext task can't be preempted by ext, own-subtree needs no cap */
+ if (curr->sched_class != &ext_sched_class ||
+ scx_is_descendant(scx_task_sched(curr), sch))
+ return 0;
+ return SCX_CAP_PREEMPT;
+}
+
+/* caps implied by holding @cap */
+static inline u64 scx_caps_implied(u64 cap)
+{
+ switch (cap) {
+ case SCX_CAP_PREEMPT:
+ return SCX_CAP_ENQ | SCX_CAP_ENQ_IMMED;
+ case SCX_CAP_ENQ:
+ return SCX_CAP_ENQ_IMMED;
+ }
+ return 0;
+}
+
+/* may @p keep running on @rq's cpu? requires baseline cpu access */
+static inline bool scx_task_can_stay_on_cpu(struct rq *rq, struct task_struct *p)
+{
+ if (!scx_has_subs())
+ return true;
+
+ /* a migration-disabled task is let in without caps, keep it likewise */
+ if (unlikely(is_migration_disabled(p)))
+ return true;
+
+ return likely(!scx_missing_caps(scx_task_sched(p), cpu_of(rq), SCX_CAP_BASE));
+}
+
+/* the task admitted for rescue on @rq, NULL if none */
+static inline struct task_struct *scx_rescuee(struct rq *rq)
+{
+ lockdep_assert_rq_held(rq);
+
+ if (!scx_has_subs())
+ return NULL;
+
+ return rq->scx.rescue.curr;
+}
+
+#else /* CONFIG_EXT_SUB_SCHED */
+
+static inline u64 scx_missing_caps(struct scx_sched *sch, s32 cpu, u64 needed) { return 0; }
+static inline u64 scx_caps_for_preempt(struct scx_sched *sch, struct rq *rq, u64 enq_flags) { return 0; }
+static inline bool scx_task_can_stay_on_cpu(struct rq *rq, struct task_struct *p) { return true; }
+static inline struct task_struct *scx_rescuee(struct rq *rq) { return NULL; }
+
+#endif /* CONFIG_EXT_SUB_SCHED */
+
+#endif /* _KERNEL_SCHED_EXT_SUB_H */
diff --git a/kernel/sched/ext/types.h b/kernel/sched/ext/types.h
index bc74eafd43f1..139176cf9fc6 100644
--- a/kernel/sched/ext/types.h
+++ b/kernel/sched/ext/types.h
@@ -19,6 +19,18 @@ enum scx_consts {
SCX_DSP_MAX_LOOPS = 32,
SCX_WATCHDOG_MAX_TIMEOUT = 30 * HZ,
+ /* rescue knob defaults and limits, see scx_rescue_timerfn() */
+ SCX_RESCUE_DFL_BW_PPT = 20, /* parts per thousand, 2% */
+ SCX_RESCUE_MAX_BW_PPT = 250, /* 25% */
+ SCX_RESCUE_DISABLE = U32_MAX, /* disables rescue */
+ SCX_RESCUE_DFL_QUANTUM_US = 5000,
+ SCX_RESCUE_MIN_QUANTUM_US = 1000,
+ SCX_RESCUE_MAX_QUANTUM_US = 100000,
+ SCX_RESCUE_MIN_SLICE_US = 1000, /* floor of the divided slice */
+ SCX_RESCUE_OVERLOAD_MULT = 16, /* overload threshold in funding periods */
+ SCX_RESCUE_MIN_OVERLOAD_MS = 1000,
+ SCX_RESCUE_MAX_OVERLOAD_MS = 15000,
+
/* per-CPU chunk size for p->scx.tid allocation, see scx_alloc_tid() */
SCX_TID_CHUNK = 1024,
@@ -41,17 +53,26 @@ enum scx_consts {
SCX_BYPASS_LB_MIN_DELTA_DIV = 4,
SCX_BYPASS_LB_BATCH = 256,
- SCX_REENQ_LOCAL_MAX_REPEAT = 256,
+ SCX_REENQ_MAX_REPEAT = 256,
SCX_SUB_MAX_DEPTH = 4,
};
/*
- * Per-cid topology info. For each topology level (core, LLC, node), records
- * the first cid in the unit and its global index. Global indices are
- * consecutive integers assigned in cid-walk order, so e.g. core_idx ranges
- * over [0, nr_cores_at_init) with no gaps. No-topo cids have all fields set
- * to -1.
+ * Per-cid topology info. For each topology level (core, LLC, node) and shard,
+ * records the first cid in the unit and its global index. Global indices are
+ * consecutive integers assigned in cid-walk order, so e.g. core_idx ranges over
+ * [0, nr_cores_at_init) with no gaps. No-topo cids have core/LLC/node fields
+ * set to -1 but always have valid shard assignments.
+ *
+ * Shards are contiguous CID ranges used as scalable locking/work domains for
+ * sub-scheduler operations. By default each LLC becomes one shard, split into
+ * smaller shards if the LLC exceeds the target size. No-topo cids are packed
+ * into their own max-sized shards.
+ *
+ * New fields are appended, never inserted: scx_bpf_cid_topo() copies this
+ * struct out sized by the program's own layout, and an older program's copy
+ * must stay a prefix of the kernel's.
*
* @core_cid: first cid of this cid's core (smt-sibling group)
* @core_idx: global index of that core, in [0, nr_cores_at_init)
@@ -59,6 +80,8 @@ enum scx_consts {
* @llc_idx: global index of that LLC, in [0, nr_llcs_at_init)
* @node_cid: first cid of this cid's NUMA node
* @node_idx: global index of that node, in [0, nr_nodes_at_init)
+ * @shard_cid: first cid of this cid's shard
+ * @shard_idx: global index of that shard, in [0, scx_nr_cid_shards)
*/
struct scx_cid_topo {
s32 core_cid;
@@ -67,6 +90,24 @@ struct scx_cid_topo {
s32 llc_idx;
s32 node_cid;
s32 node_idx;
+ s32 shard_cid;
+ s32 shard_idx;
+};
+
+enum scx_cid_consts {
+ SCX_CID_SHARD_SIZE_DFL = 24,
+ SCX_CID_SHARD_MAX_CPUS = 512,
+};
+
+/*
+ * Per-shard metadata for O(1) shard->cid-range lookup.
+ *
+ * @base_cid: first cid of the shard
+ * @nr_cids: number of cids in the shard
+ */
+struct scx_cid_shard {
+ s32 base_cid;
+ s32 nr_cids;
};
/*
@@ -91,7 +132,7 @@ struct scx_cmask {
u32 base;
u32 nr_cids;
u32 alloc_words;
- u64 bits[] __counted_by(alloc_words);
+ u64 bits[];
};
/*
@@ -147,4 +188,41 @@ struct scx_cmask {
#define SCX_CMASK_DEFINE_SHARD(NAME, BASE, NR_CIDS) \
__SCX_CMASK_DEFINE(NAME, BASE, NR_CIDS, SCX_CID_SHARD_MAX_CPUS)
+/*
+ * scx_cmask_ref: validated reference to a BPF-arena cmask.
+ *
+ * scx_cmask_ref_init() snapshots @base/@nr_cids. The snapshot is what
+ * downstream code uses for sizing - the live header can be mutated concurrently
+ * by BPF.
+ *
+ * scx_cmask_ref_shard() reads one shard into a cmask. scx_cmask_ref_or() and
+ * scx_cmask_ref_copy() write back into the referenced arena cmask, bounded by
+ * the snapshot.
+ *
+ * Typical input use:
+ *
+ * struct scx_cmask_ref ref;
+ * SCX_CMASK_DEFINE(shard, 0, SCX_CID_SHARD_MAX_CPUS);
+ * s32 idx, ret;
+ *
+ * ret = scx_cmask_ref_init(sch, src, &ref);
+ * if (ret < 0)
+ * return ret;
+ *
+ * for (idx = ref.shard_first; idx < ref.shard_end; idx++) {
+ * scx_cmask_ref_shard(&ref, idx, shard);
+ * if (!shard->nr_cids)
+ * continue;
+ * ... use idx and shard ...
+ * }
+ */
+struct scx_cmask_ref {
+ struct scx_sched *sch;
+ struct scx_cmask *src;
+ u32 base;
+ u32 nr_cids;
+ s32 shard_first;
+ s32 shard_end;
+};
+
#endif /* _KERNEL_SCHED_EXT_TYPES_H */
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index d78467ec6ee1..57360f5cdde4 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -26,6 +26,8 @@
#include <linux/jiffies.h>
#include <linux/mm_api.h>
#include <linux/highmem.h>
+#include <linux/hrtimer.h>
+#include <linux/hrtimer_bases.h>
#include <linux/spinlock_api.h>
#include <linux/cpumask_api.h>
#include <linux/lockdep_api.h>
@@ -38,6 +40,7 @@
#include <linux/sched/isolation.h>
#include <linux/sched/nohz.h>
#include <linux/sched/prio.h>
+#include <linux/static_call.h>
#include <linux/cpuidle.h>
#include <linux/interrupt.h>
@@ -72,7 +75,10 @@
unsigned int sysctl_sched_tunable_scaling = SCHED_TUNABLESCALING_LOG;
/*
- * Minimal preemption granularity for CPU-bound tasks:
+ * Default base time slice (request size r_i) for SCHED_NORMAL/SCHED_BATCH:
+ *
+ * Under EEVDF this is the request size used to compute the virtual
+ * deadline; see update_deadline().
*
* (default: 0.70 msec * (1 + ilog(ncpus)), units: nanoseconds)
*/
@@ -296,8 +302,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 (unlikely(se->load.weight != NICE_0_LOAD))
- delta = __calc_delta(delta, NICE_0_LOAD, &se->load);
+ if (se->h_load.weight != NICE_0_LOAD)
+ delta = __calc_delta(delta, NICE_0_LOAD, &se->h_load);
return delta;
}
@@ -427,38 +433,6 @@ 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;
@@ -502,11 +476,6 @@ 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;
@@ -685,7 +654,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->load.weight);
+ unsigned long weight = avg_vruntime_weight(cfs_rq, se->h_load.weight);
s64 w_vruntime, key = entity_key(cfs_rq, se);
w_vruntime = key * weight;
@@ -702,7 +671,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->load.weight);
+ weight = avg_vruntime_weight(cfs_rq, se->h_load.weight);
key = entity_key(cfs_rq, se);
if (check_mul_overflow(key, weight, &key))
@@ -748,7 +717,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->load.weight);
+ unsigned long weight = avg_vruntime_weight(cfs_rq, se->h_load.weight);
s64 key = entity_key(cfs_rq, se);
cfs_rq->sum_w_vruntime -= key * weight;
@@ -790,7 +759,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->load.weight);
+ unsigned long w = avg_vruntime_weight(cfs_rq, curr->h_load.weight);
runtime += entity_key(cfs_rq, curr) * w;
weight += w;
@@ -813,6 +782,67 @@ u64 avg_vruntime(struct cfs_rq *cfs_rq)
return cfs_rq->zero_vruntime;
}
+/*
+ * \Sum (v_i - v0)*w_i
+ * V = ------------------- + v0
+ * \Sum w_i
+ *
+ * Let W = \Sum w_i, and move v_j such that 'v_j == V', thus:
+ *
+ * V = 1/W * {(v_j - v0)*w_j + \Sum_i!=j (v_i - v0)*w_i} + v0
+ *
+ * v_j = 1/W * {(v_j - v0)*w_j + \Sum_i!=j (v_i - v0)*w_i} + v0
+ *
+ * v_j = 1/W * (v_j - v0)*w_j + 1/W * \Sum_i!=j (v_i - v0)*w_i + v0
+ *
+ * v_j - 1/W * (v_j - v0)*w_j = 1/W * \Sum_i!=j (v_i - v0)*w_i + v0
+ *
+ * v_j*W - (v_j - v0)*w_j = \Sum_i!=j (v_i - v0)*w_i + v0*W
+ *
+ * v_j*(W - w_j) + v0*w_j = \Sum_i!=j (v_i - v0)*w_i + v0*W
+ *
+ * v_j*(W - w_j) = \Sum_i!=j (v_i - v0)*w_i + v0*(W - w_j)
+ *
+ * \Sum_i!=j (v_i - v0)*w_i
+ * v_j = ------------------------ + v0
+ * W - w_j
+ *
+ * When v_j happens to be curr, then '\Sum_i!=j (v_i - v0)*w_i'
+ * is cfs_rq->sum_w_runtime, and 'W - w_j' is cfs_rq->sum_weight, since curr
+ * is not included in the sum.
+ */
+static u64 ineligible_vruntime(struct cfs_rq *cfs_rq)
+{
+ struct sched_entity *curr = cfs_rq->curr;
+ long weight = cfs_rq->sum_weight;
+ s64 delta = 0;
+
+ if (curr && !curr->on_rq)
+ curr = NULL;
+
+ /*
+ * This is called from set_next_task_fair(.first=true) /
+ * set_protect_slice() so curr had better be set and on_rq.
+ */
+ WARN_ON_ONCE(!curr);
+
+ if (weight) {
+ s64 runtime = cfs_rq->sum_w_vruntime;
+
+ /*
+ * Do not add @curr to obtain the effective '- w_j' terms.
+ */
+
+ /* sign flips effective floor / ceiling */
+ if (runtime < 0)
+ runtime -= (weight - 1);
+
+ delta = div64_long(runtime, weight);
+ }
+
+ return cfs_rq->zero_vruntime + delta + 1;
+}
+
static inline u64 cfs_rq_max_slice(struct cfs_rq *cfs_rq);
/*
@@ -861,8 +891,6 @@ 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);
@@ -898,7 +926,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->load.weight);
+ unsigned long weight = avg_vruntime_weight(cfs_rq, curr->h_load.weight);
avg += entity_key(cfs_rq, curr) * weight;
load += weight;
@@ -1004,6 +1032,13 @@ static inline void __max_slice_update(struct sched_entity *se, struct rb_node *n
}
}
+static inline void min_vruntime_copy(struct sched_entity *new, struct sched_entity *old)
+{
+ new->min_vruntime = old->min_vruntime;
+ new->min_slice = old->min_slice;
+ new->max_slice = old->max_slice;
+}
+
/*
* se->min_vruntime = min(se->vruntime, {left,right}->min_vruntime)
*/
@@ -1031,23 +1066,32 @@ static inline bool min_vruntime_update(struct sched_entity *se, bool exit)
se->max_slice == old_max_slice;
}
-RB_DECLARE_CALLBACKS(static, min_vruntime_cb, struct sched_entity,
- run_node, min_vruntime, min_vruntime_update);
+
+RB_DECLARE_CALLBACKS_MULTI(static, min_vruntime_cb, struct sched_entity,
+ run_node, min_vruntime_copy, min_vruntime_update);
/*
* Enqueue an entity into the rb-tree:
*/
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;
+ se->max_slice = se->slice;
+
rb_add_augmented_cached(&se->run_node, &cfs_rq->tasks_timeline,
__entity_less, &min_vruntime_cb);
}
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);
@@ -1090,8 +1134,14 @@ static inline void set_protect_slice(struct cfs_rq *cfs_rq, struct sched_entity
slice = cfs_rq_min_slice(cfs_rq);
slice = min(slice, se->slice);
- if (slice != se->slice)
- vprot = min_vruntime(vprot, se->vruntime + calc_delta_fair(slice, se));
+
+ /* If there are shorter slices than se's one */
+ if (slice != se->slice) {
+ if (sched_feat(PREEMPT_SHORT))
+ vprot = min_vruntime(vprot, ineligible_vruntime(cfs_rq));
+ else
+ vprot = min_vruntime(vprot, se->vruntime + calc_delta_fair(slice, se));
+ }
se->vprot = vprot;
}
@@ -1099,8 +1149,9 @@ static inline void set_protect_slice(struct cfs_rq *cfs_rq, struct sched_entity
static inline void update_protect_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
u64 slice = cfs_rq_min_slice(cfs_rq);
+ u64 vruntime = min_vruntime(se->vruntime, avg_vruntime(cfs_rq));
- se->vprot = min_vruntime(se->vprot, se->vruntime + calc_delta_fair(slice, se));
+ se->vprot = min_vruntime(se->vprot, vruntime + calc_delta_fair(slice, se));
}
static inline bool protect_slice(struct sched_entity *se)
@@ -1144,7 +1195,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->nr_queued == 1)
+ if (cfs_rq->h_nr_queued == 1)
return curr && curr->on_rq ? curr : se;
/*
@@ -1363,7 +1414,6 @@ static s64 update_se(struct rq *rq, struct sched_entity *se)
se->exec_start = now;
if (entity_is_task(se)) {
- struct task_struct *donor = task_of(se);
struct task_struct *running = rq->curr;
/*
* If se is a task, we account the time against the running
@@ -1376,8 +1426,7 @@ static s64 update_se(struct rq *rq, struct sched_entity *se)
account_group_exec_runtime(running, delta_exec);
account_mm_sched(rq, running, delta_exec);
- /* cgroup time is always accounted against the donor */
- cgroup_account_cputime(donor, delta_exec);
+ cgroup_account_cputime(running, delta_exec);
} else {
/* If not task, account the time against donor se */
se->sum_exec_runtime += delta_exec;
@@ -1394,8 +1443,6 @@ 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
/*
@@ -1431,7 +1478,7 @@ static inline int get_sched_cache_scale(int mul)
return (1 + (tol - 1) * mul);
}
-static bool exceed_llc_capacity(struct mm_struct *mm, int cpu)
+static bool exceed_llc_capacity(struct sched_cache_group *grp, int cpu)
{
#ifdef CONFIG_NUMA_BALANCING
unsigned long llc, footprint;
@@ -1450,7 +1497,7 @@ static bool exceed_llc_capacity(struct mm_struct *mm, int cpu)
* excluded.
*/
llc = sd->llc_bytes;
- footprint = READ_ONCE(mm->sc_stat.footprint);
+ footprint = READ_ONCE(grp->footprint);
/*
* Scale the LLC size by 256*llc_aggr_tolerance
@@ -1479,7 +1526,7 @@ static bool exceed_llc_capacity(struct mm_struct *mm, int cpu)
return false;
}
-static bool invalid_llc_nr(struct mm_struct *mm, struct task_struct *p,
+static bool invalid_llc_nr(struct sched_cache_group *grp, struct task_struct *p,
int cpu)
{
int scale;
@@ -1495,10 +1542,32 @@ static bool invalid_llc_nr(struct mm_struct *mm, struct task_struct *p,
if (scale == INT_MAX)
return false;
- return !fits_capacity((mm->sc_stat.nr_running_avg * cpu_smt_num_threads),
+ return !fits_capacity((READ_ONCE(grp->nr_running_avg) * cpu_smt_num_threads),
(scale * per_cpu(sd_llc_size, cpu)));
}
+/*
+ * A task counts in nr_pref_llc_running while it is queued on its preferred
+ * LLC (pref_llc_queued) and runnable (!sched_delayed), keeping the counter in
+ * the runnable domain so alb_break_llc() can compare it with h_nr_runnable.
+ */
+static bool task_pref_llc_runnable(struct task_struct *p)
+{
+ return p->pref_llc_queued && !p->se.sched_delayed;
+}
+
+static void pref_llc_running_inc(struct rq *rq, struct task_struct *p)
+{
+ if (task_pref_llc_runnable(p))
+ rq->nr_pref_llc_running++;
+}
+
+static void pref_llc_running_dec(struct rq *rq, struct task_struct *p)
+{
+ if (task_pref_llc_runnable(p))
+ rq->nr_pref_llc_running--;
+}
+
static void account_llc_enqueue(struct rq *rq, struct task_struct *p)
{
int pref_llc, pref_llc_queued;
@@ -1510,7 +1579,6 @@ static void account_llc_enqueue(struct rq *rq, struct task_struct *p)
pref_llc_queued = (pref_llc == task_llc(p));
rq->nr_llc_running++;
- rq->nr_pref_llc_running += pref_llc_queued;
/*
* Record whether p is enqueued on its preferred
@@ -1528,6 +1596,9 @@ static void account_llc_enqueue(struct rq *rq, struct task_struct *p)
*/
p->pref_llc_queued = pref_llc_queued;
+ /* Skipped while delayed; clear_delayed() adds it back on wake. */
+ pref_llc_running_inc(rq, p);
+
sd = rcu_dereference_all(rq->sd);
if (sd && (unsigned int)pref_llc < sd->llc_max)
sd->llc_counts[pref_llc]++;
@@ -1544,7 +1615,12 @@ static void account_llc_dequeue(struct rq *rq, struct task_struct *p)
rq->nr_llc_running--;
if (p->pref_llc_queued) {
- rq->nr_pref_llc_running--;
+ /*
+ * Skipped if still delayed (set_delayed() already removed it);
+ * clearing pref_llc_queued below also stops clear_delayed()
+ * from re-adding it.
+ */
+ pref_llc_running_dec(rq, p);
/*
* Update the status in case
* other logic might query
@@ -1572,12 +1648,20 @@ static void account_llc_dequeue(struct rq *rq, struct task_struct *p)
}
}
-void mm_init_sched(struct mm_struct *mm,
- struct sched_cache_time __percpu *_pcpu_sched)
+int mm_init_sched(struct mm_struct *mm,
+ struct sched_cache_time __percpu *_pcpu_sched)
{
+ struct sched_cache_group *grp;
unsigned long epoch = 0;
int i;
+ grp = kzalloc_obj(*grp);
+ if (!grp) {
+ free_percpu(_pcpu_sched);
+ mm->sched_cache_grp = NULL;
+ return -ENOMEM;
+ }
+
for_each_possible_cpu(i) {
struct sched_cache_time *pcpu_sched = per_cpu_ptr(_pcpu_sched, i);
struct rq *rq = cpu_rq(i);
@@ -1588,18 +1672,141 @@ void mm_init_sched(struct mm_struct *mm,
epoch = rq->cpu_epoch;
}
- raw_spin_lock_init(&mm->sc_stat.lock);
- mm->sc_stat.epoch = epoch;
- mm->sc_stat.cpu = -1;
- mm->sc_stat.next_scan = jiffies;
- mm->sc_stat.nr_running_avg = 0;
- mm->sc_stat.footprint = 0;
+ raw_spin_lock_init(&grp->lock);
+ grp->epoch = epoch;
+ grp->cpu = -1;
+ grp->next_scan = jiffies;
+ grp->nr_running_avg = 0;
+ grp->footprint = 0;
+ refcount_set(&grp->refcnt, 1);
/*
- * The update to mm->sc_stat should not be reordered
- * before initialization to mm's other fields, in case
+ * The update to grp->pcpu_sched should not be reordered
+ * before initialization to grp's other fields, in case
* the readers may get invalid mm_sched_epoch, etc.
*/
- smp_store_release(&mm->sc_stat.pcpu_sched, _pcpu_sched);
+ smp_store_release(&grp->pcpu_sched, _pcpu_sched);
+ /*
+ * Publish the group last. Not every reader qualifies it by
+ * grp->pcpu_sched - can_migrate_llc_task() only checks that the
+ * pointer is non-NULL before reading grp->footprint and
+ * grp->nr_running_avg - so a reachable group must already be
+ * fully initialized.
+ */
+ smp_store_release(&mm->sched_cache_grp, grp);
+ return 0;
+}
+
+static void sched_cache_group_free_rcu(struct rcu_head *rcu)
+{
+ struct sched_cache_group *grp =
+ container_of(rcu, struct sched_cache_group, rcu);
+
+ free_percpu(grp->pcpu_sched);
+ kfree(grp);
+}
+
+static void sched_cache_group_put(struct sched_cache_group *grp)
+{
+ if (!grp || !refcount_dec_and_test(&grp->refcnt))
+ return;
+
+ call_rcu(&grp->rcu, sched_cache_group_free_rcu);
+}
+
+DEFINE_FREE(sched_cache_group_put, struct sched_cache_group *,
+ sched_cache_group_put(_T));
+
+#define rcu_deref_sched_cache_grp(tsk) \
+ rcu_dereference_check((tsk)->sched_cache_grp, (tsk) == current)
+
+static struct sched_cache_group *sched_cache_replace_grp(struct task_struct *p,
+ struct sched_cache_group *new)
+{
+ struct sched_cache_group *old;
+
+ old = rcu_deref_sched_cache_grp(p);
+ rcu_assign_pointer(p->sched_cache_grp, new);
+
+ return old;
+}
+
+struct sched_cache_group *sched_cache_group_get(struct sched_cache_group *grp)
+{
+ /*
+ * refcount_inc_not_zero() is the acquire primitive for lockless
+ * (RCU) lookups; plain refcount_inc() would scribble the count if
+ * it already reached zero. Return NULL in that case.
+ */
+ if (grp && !refcount_inc_not_zero(&grp->refcnt))
+ grp = NULL;
+
+ return grp;
+}
+
+struct sched_cache_group *task_cache_group_get(struct task_struct *p)
+{
+ guard(rcu)();
+ return sched_cache_group_get(rcu_dereference(p->sched_cache_grp));
+}
+
+void sched_cache_fork(struct task_struct *p)
+{
+ /*
+ * The child takes its own reference on the mm's cache group, separate
+ * from the reference held by the mm. @p is not yet visible to readers,
+ * so a plain initializing store is enough.
+ */
+ RCU_INIT_POINTER(p->sched_cache_grp,
+ sched_cache_group_get(p->mm->sched_cache_grp));
+}
+
+void sched_cache_fork_cleanup(struct task_struct *p)
+{
+ /*
+ * A fork that fails after sched_cache_fork() never reaches exit_mm(),
+ * so drop the reference here. @p never became visible, so there are no
+ * concurrent readers and the reference we hold keeps the group alive.
+ */
+ sched_cache_group_put(rcu_access_pointer(p->sched_cache_grp));
+ RCU_INIT_POINTER(p->sched_cache_grp, NULL);
+}
+
+void sched_cache_exec_mmap(struct task_struct *p, struct mm_struct *mm)
+{
+ struct sched_cache_group *old;
+
+ /*
+ * Acquire the new reference before publishing the pointer, then drop
+ * the old one. @p is current and the only writer of its own pointer.
+ */
+ old = sched_cache_replace_grp(p, sched_cache_group_get(mm->sched_cache_grp));
+ sched_cache_group_put(old);
+}
+
+void sched_cache_exit_mm(struct task_struct *p)
+{
+ struct sched_cache_group *grp = sched_cache_replace_grp(p, NULL);
+
+#ifdef CONFIG_NUMA_BALANCING
+ /*
+ * Subtract this task's footprint from the group before dropping the
+ * reference, so the group footprint converges as its threads exit.
+ * Unlocked for performance; clamp to avoid underflow.
+ */
+ if (grp && p->total_numa_faults) {
+ unsigned long fp = READ_ONCE(grp->footprint);
+ unsigned long sub = min(fp, p->total_numa_faults);
+
+ WRITE_ONCE(grp->footprint, fp - sub);
+ }
+#endif
+ sched_cache_group_put(grp);
+}
+
+void mm_destroy_sched(struct mm_struct *mm)
+{
+ sched_cache_group_put(mm->sched_cache_grp);
+ mm->sched_cache_grp = NULL;
}
/* because why would C be fully specified */
@@ -1650,14 +1857,14 @@ static unsigned long fraction_mm_sched(struct rq *rq,
return div64_u64(NICE_0_LOAD * pcpu_sched->runtime, rq->cpu_runtime + 1);
}
-static int get_pref_llc(struct task_struct *p, struct mm_struct *mm)
+static int get_pref_llc(struct task_struct *p, struct sched_cache_group *grp)
{
int mm_sched_llc = -1, mm_sched_cpu;
- if (!mm)
+ if (!grp)
return -1;
- mm_sched_cpu = READ_ONCE(mm->sc_stat.cpu);
+ mm_sched_cpu = READ_ONCE(grp->cpu);
if (mm_sched_cpu != -1) {
mm_sched_llc = llc_id(mm_sched_cpu);
@@ -1687,8 +1894,8 @@ static unsigned int task_running_on_cpu(int cpu, struct task_struct *p);
static inline
void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec)
{
+ struct sched_cache_group *grp = rcu_dereference_all(p->sched_cache_grp);
struct sched_cache_time *pcpu_sched;
- struct mm_struct *mm = p->mm;
int mm_sched_llc = -1;
unsigned long epoch;
@@ -1699,12 +1906,18 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec)
return;
/*
* init_task, kthreads and user thread created
- * by user_mode_thread() don't have mm.
+ * by user_mode_thread() don't have a cache group.
+ * In theory a kernel thread does not have any valid
+ * cache group, because sched_cache_fork() is not
+ * invoked for a kernel thread - !grp should gate the
+ * kernel thread. Use the PF_KTHREAD check explicitly
+ * here for safety reasons, to guard against future
+ * modifications and to pair with task_tick_cache().
*/
- if (!mm || !mm->sc_stat.pcpu_sched)
+ if (p->flags & PF_KTHREAD || !grp || !grp->pcpu_sched)
return;
- pcpu_sched = per_cpu_ptr(mm->sc_stat.pcpu_sched, cpu_of(rq));
+ pcpu_sched = per_cpu_ptr(grp->pcpu_sched, cpu_of(rq));
scoped_guard (raw_spinlock, &rq->cpu_epoch_lock) {
__update_mm_sched(rq, pcpu_sched);
@@ -1717,14 +1930,14 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec)
* If this process hasn't hit task_cache_work() for a while invalidate
* its preferred state.
*/
- if ((long)(epoch - READ_ONCE(mm->sc_stat.epoch)) > llc_epoch_affinity_timeout ||
- invalid_llc_nr(mm, p, cpu_of(rq)) ||
- exceed_llc_capacity(mm, cpu_of(rq))) {
- if (READ_ONCE(mm->sc_stat.cpu) != -1)
- WRITE_ONCE(mm->sc_stat.cpu, -1);
+ if ((long)(epoch - READ_ONCE(grp->epoch)) > llc_epoch_affinity_timeout ||
+ invalid_llc_nr(grp, p, cpu_of(rq)) ||
+ exceed_llc_capacity(grp, cpu_of(rq))) {
+ if (READ_ONCE(grp->cpu) != -1)
+ WRITE_ONCE(grp->cpu, -1);
}
- mm_sched_llc = get_pref_llc(p, mm);
+ mm_sched_llc = get_pref_llc(p, grp);
/* task not on rq accounted later in account_entity_enqueue() */
if (task_running_on_cpu(rq->cpu, p) &&
@@ -1737,31 +1950,32 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec)
static void task_tick_cache(struct rq *rq, struct task_struct *p)
{
+ struct sched_cache_group *grp = rcu_dereference_all(p->sched_cache_grp);
struct callback_head *work = &p->cache_work;
- struct mm_struct *mm = p->mm;
unsigned long epoch;
if (!sched_cache_enabled())
return;
- if (!mm || p->flags & PF_KTHREAD ||
- !mm->sc_stat.pcpu_sched)
+ if (!grp || p->flags & PF_KTHREAD ||
+ !grp->pcpu_sched)
return;
epoch = rq->cpu_epoch;
/* avoid moving backwards */
- if (time_after_eq(mm->sc_stat.epoch, epoch))
+ if (time_after_eq(grp->epoch, epoch))
return;
- guard(raw_spinlock)(&mm->sc_stat.lock);
+ guard(raw_spinlock)(&grp->lock);
if (work->next == work) {
task_work_add(p, work, TWA_RESUME);
- WRITE_ONCE(mm->sc_stat.epoch, epoch);
+ WRITE_ONCE(grp->epoch, epoch);
}
}
-static void get_scan_cpumasks(cpumask_var_t cpus, struct task_struct *p)
+static void get_scan_cpumasks(cpumask_var_t cpus, struct task_struct *p,
+ struct sched_cache_group *grp)
{
#ifdef CONFIG_NUMA_BALANCING
int cpu, curr_cpu, nid, pref_nid;
@@ -1769,7 +1983,7 @@ static void get_scan_cpumasks(cpumask_var_t cpus, struct task_struct *p)
if (!static_branch_likely(&sched_numa_balancing))
goto out;
- cpu = READ_ONCE(p->mm->sc_stat.cpu);
+ cpu = READ_ONCE(grp->cpu);
if (cpu != -1)
nid = cpu_to_node(cpu);
curr_cpu = task_cpu(p);
@@ -1826,13 +2040,13 @@ static inline void update_avg_scale(u64 *avg, u64 sample)
static void task_cache_work(struct callback_head *work)
{
+ struct sched_cache_group *grp __free(sched_cache_group_put) = NULL;
+ cpumask_var_t cpus __free(free_cpumask_var) = CPUMASK_VAR_NULL;
int cpu, m_a_cpu = -1, nr_running = 0, curr_cpu;
unsigned long next_scan, now = jiffies;
struct task_struct *p = current, *cur;
unsigned long curr_m_a_occ = 0;
- struct mm_struct *mm = p->mm;
unsigned long m_a_occ = 0;
- cpumask_var_t cpus;
WARN_ON_ONCE(work != &p->cache_work);
@@ -1841,21 +2055,30 @@ static void task_cache_work(struct callback_head *work)
if (p->flags & PF_EXITING)
return;
- next_scan = READ_ONCE(mm->sc_stat.next_scan);
+ /*
+ * A reference makes sure grp is not released by others. The rcu
+ * lock can not be held till after zalloc_cpumask_var() below,
+ * because the latter might sleep.
+ */
+ grp = task_cache_group_get(p);
+ if (!grp)
+ return;
+
+ next_scan = READ_ONCE(grp->next_scan);
if (time_before(now, next_scan))
return;
/* only 1 thread is allowed to scan */
- if (!try_cmpxchg(&mm->sc_stat.next_scan, &next_scan,
+ if (!try_cmpxchg(&grp->next_scan, &next_scan,
now + max_t(unsigned long,
READ_ONCE(llc_epoch_period), 1)))
return;
curr_cpu = task_cpu(p);
- if (invalid_llc_nr(mm, p, curr_cpu) ||
- exceed_llc_capacity(mm, curr_cpu)) {
- if (READ_ONCE(mm->sc_stat.cpu) != -1)
- WRITE_ONCE(mm->sc_stat.cpu, -1);
+ if (invalid_llc_nr(grp, p, curr_cpu) ||
+ exceed_llc_capacity(grp, curr_cpu)) {
+ if (READ_ONCE(grp->cpu) != -1)
+ WRITE_ONCE(grp->cpu, -1);
return;
}
@@ -1866,7 +2089,7 @@ static void task_cache_work(struct callback_head *work)
scoped_guard (cpus_read_lock) {
guard(rcu)();
- get_scan_cpumasks(cpus, p);
+ get_scan_cpumasks(cpus, p, grp);
for_each_cpu(cpu, cpus) {
/* XXX sched_cluster_active */
@@ -1879,16 +2102,20 @@ static void task_cache_work(struct callback_head *work)
for_each_cpu(i, sched_domain_span(sd)) {
occ = fraction_mm_sched(cpu_rq(i),
- per_cpu_ptr(mm->sc_stat.pcpu_sched, i));
+ per_cpu_ptr(grp->pcpu_sched, i));
a_occ += occ;
if (occ > m_occ) {
m_occ = occ;
m_cpu = i;
}
+ /*
+ * rcu_access_pointer() is used because the
+ * pointer is only compared, never dereferenced.
+ */
cur = rcu_dereference_all(cpu_rq(i)->curr);
if (cur && !(cur->flags & (PF_EXITING | PF_KTHREAD)) &&
- cur->mm == mm)
+ rcu_access_pointer(cur->sched_cache_grp) == grp)
nr_running++;
}
@@ -1912,7 +2139,7 @@ static void task_cache_work(struct callback_head *work)
m_a_cpu = m_cpu;
}
- if (llc_id(cpu) == llc_id(READ_ONCE(mm->sc_stat.cpu)))
+ if (llc_id(cpu) == llc_id(READ_ONCE(grp->cpu)))
curr_m_a_occ = a_occ;
cpumask_andnot(cpus, cpus, sched_domain_span(sd));
@@ -1921,7 +2148,7 @@ static void task_cache_work(struct callback_head *work)
if (m_a_occ > (2 * curr_m_a_occ)) {
/*
- * Avoid switching sc_stat.cpu too fast.
+ * Avoid switching sched_cache_grp->cpu too fast.
* The reason to choose 2X is because:
* 1. It is better to keep the preferred LLC stable,
* rather than changing it frequently and cause migrations
@@ -1930,11 +2157,10 @@ static void task_cache_work(struct callback_head *work)
* 3. 2X is chosen based on test results, as it delivers
* the optimal performance gain so far.
*/
- WRITE_ONCE(mm->sc_stat.cpu, m_a_cpu);
+ WRITE_ONCE(grp->cpu, m_a_cpu);
}
- update_avg_scale(&mm->sc_stat.nr_running_avg, nr_running);
- free_cpumask_var(cpus);
+ update_avg_scale(&grp->nr_running_avg, nr_running);
}
void init_sched_mm(struct task_struct *p)
@@ -1944,10 +2170,18 @@ void init_sched_mm(struct task_struct *p)
init_task_work(work, task_cache_work);
work->next = work;
/*
+ * dup_task_struct() copies the parent's task_struct, including its
+ * sched_cache_grp, for which the child holds no reference. Clear it
+ * here - before copy_mm() runs - so the child never carries a
+ * borrowed pointer that the fork error path would put.
+ */
+ RCU_INIT_POINTER(p->sched_cache_grp, NULL);
+ /*
* Reset new task's preference to avoid
* polluting account_llc_enqueue().
*/
p->preferred_llc = -1;
+ p->pref_llc_queued = 0;
}
#else /* CONFIG_SCHED_CACHE */
@@ -1969,6 +2203,10 @@ static void account_llc_enqueue(struct rq *rq, struct task_struct *p) {}
static void account_llc_dequeue(struct rq *rq, struct task_struct *p) {}
+static void pref_llc_running_inc(struct rq *rq, struct task_struct *p) {}
+
+static void pref_llc_running_dec(struct rq *rq, struct task_struct *p) {}
+
#endif /* CONFIG_SCHED_CACHE */
/*
@@ -1990,7 +2228,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->curr;
+ struct sched_entity *curr = cfs_rq->h_curr;
struct rq *rq = rq_of(cfs_rq);
s64 delta_exec;
bool resched;
@@ -2002,26 +2240,29 @@ 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 (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);
- }
-
- account_cfs_rq_runtime(cfs_rq, delta_exec);
+ /*
+ * 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->nr_queued == 1)
+ if (cfs_rq->h_nr_queued == 1)
return;
if (resched || !protect_slice(curr)) {
@@ -2032,7 +2273,10 @@ static void update_curr(struct cfs_rq *cfs_rq)
static void update_curr_fair(struct rq *rq)
{
- update_curr(cfs_rq_of(&rq->donor->se));
+ struct sched_entity *se = &rq->donor->se;
+
+ for_each_sched_entity(se)
+ update_curr(cfs_rq_of(se));
}
static inline void
@@ -2108,7 +2352,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->curr)
+ if (se != cfs_rq->h_curr)
update_stats_wait_start_fair(cfs_rq, se);
if (flags & ENQUEUE_WAKEUP)
@@ -2126,7 +2370,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->curr)
+ if (se != cfs_rq->h_curr)
update_stats_wait_end_fair(cfs_rq, se);
if ((flags & DEQUEUE_SLEEP) && entity_is_task(se)) {
@@ -3639,6 +3883,7 @@ static int preferred_group_nid(struct task_struct *p, int nid)
static void task_numa_placement(struct task_struct *p)
__context_unsafe(/* conditional locking */)
{
+ struct sched_cache_group __maybe_unused *grp;
int seq, nid, max_nid = NUMA_NO_NODE;
unsigned long max_faults = 0;
unsigned long fault_types[2] = { 0, 0 };
@@ -3731,19 +3976,24 @@ static void task_numa_placement(struct task_struct *p)
* heuristic and occasional lost updates are tolerable.
*
* If a task exits, its corresponding footprint must
- * be subtracted from the mm->sc_stat.footprint, otherwise
- * the mm->sc_stat.footprint will not converge:
- * the exiting thread's footprint remains unchanged/undecayed
- * in mm->sc_stat.footprint. See exit_mm().
+ * be subtracted from p->sched_cache_grp->footprint,
+ * otherwise the footprint will not converge: the
+ * exiting thread's footprint remains unchanged/undecayed.
+ * See exit_mm().
*
* Lost updates and unsynchronized subtraction
* in exit_mm() can cause footprint + diff to
* go negative. Clamp to zero to prevent the
* unsigned footprint from wrapping.
*/
- new_fp = (long)READ_ONCE(p->mm->sc_stat.footprint) + diff;
- WRITE_ONCE(p->mm->sc_stat.footprint,
- max(new_fp, 0L));
+ scoped_guard(rcu) {
+ grp = rcu_dereference(p->sched_cache_grp);
+
+ if (grp) {
+ new_fp = (long)READ_ONCE(grp->footprint) + diff;
+ WRITE_ONCE(grp->footprint, max(new_fp, 0L));
+ }
+ }
#endif
}
@@ -4467,6 +4717,7 @@ 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);
@@ -4482,6 +4733,7 @@ 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);
@@ -4563,7 +4815,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)
{
- unsigned long old_weight = se->load.weight;
+ long old_weight = se->h_load.weight;
/*
* VRUNTIME
@@ -4663,16 +4915,17 @@ rescale_entity(struct sched_entity *se, unsigned long weight, bool rel_vprot)
se->vprot = div64_long(se->vprot * old_weight, weight);
}
-static void reweight_entity(struct cfs_rq *cfs_rq, struct sched_entity *se,
- unsigned long weight)
+static void reweight_eevdf(struct cfs_rq *cfs_rq, struct sched_entity *se,
+ unsigned long weight, bool on_rq)
{
bool curr = cfs_rq->curr == se;
bool rel_vprot = false;
u64 avruntime = 0;
- if (se->on_rq) {
- /* commit outstanding execution time */
- update_curr(cfs_rq);
+ if (se->h_load.weight == weight)
+ return;
+
+ if (on_rq) {
avruntime = avg_vruntime(cfs_rq);
se->vlag = entity_lag(cfs_rq, se, avruntime);
se->deadline -= avruntime;
@@ -4682,46 +4935,90 @@ static void reweight_entity(struct cfs_rq *cfs_rq, struct sched_entity *se,
rel_vprot = true;
}
- cfs_rq->nr_queued--;
+ cfs_rq->h_nr_queued--;
if (!curr)
__dequeue_entity(cfs_rq, 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 {
- u32 divider = get_pelt_divider(&se->avg);
- se->avg.load_avg = div_u64(se_weight(se) * se->avg.load_sum, divider);
- } while (0);
+ update_load_set(&se->h_load, weight);
- enqueue_load_avg(cfs_rq, se);
- if (se->on_rq) {
+ if (on_rq) {
if (rel_vprot)
se->vprot += avruntime;
se->deadline += avruntime;
se->rel_deadline = 0;
se->vruntime = avruntime - se->vlag;
- update_load_add(&cfs_rq->load, se->load.weight);
if (!curr)
__enqueue_entity(cfs_rq, se);
- cfs_rq->nr_queued++;
+ 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);
+
+ update_load_set(&se->load, weight);
+
+ do {
+ u32 divider = get_pelt_divider(&se->avg);
+ se->avg.load_avg = div_u64(se_weight(se) * se->avg.load_sum, divider);
+ } while (0);
+
+ enqueue_load_avg(cfs_rq, se);
+
+ 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);
+}
+
static void reweight_task_fair(struct rq *rq, struct task_struct *p,
const struct load_weight *lw)
{
struct sched_entity *se = &p->se;
- struct cfs_rq *cfs_rq = cfs_rq_of(se);
- struct load_weight *load = &se->load;
+ unsigned long weight = NICE_0_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_entity(cfs_rq, se, lw->weight);
- load->inv_weight = lw->inv_weight;
+ reweight_eevdf(&rq->cfs, &p->se, weight, p->se.on_rq);
}
static inline int throttled_hierarchy(struct cfs_rq *cfs_rq);
@@ -4800,12 +5097,10 @@ static inline int throttled_hierarchy(struct cfs_rq *cfs_rq);
*
* hence icky!
*/
-static long calc_group_shares(struct cfs_rq *cfs_rq)
+static long __calc_smp_shares(struct cfs_rq *cfs_rq, long tg_shares, long shares_max)
{
- long tg_weight, tg_shares, load, shares;
struct task_group *tg = cfs_rq->tg;
-
- tg_shares = READ_ONCE(tg->shares);
+ long tg_weight, load, shares;
load = max(scale_load_down(cfs_rq->load.weight), cfs_rq->avg.load_avg);
@@ -4831,7 +5126,120 @@ static long calc_group_shares(struct cfs_rq *cfs_rq)
* case no task is runnable on a CPU MIN_SHARES=2 should be returned
* instead of 0.
*/
- return clamp_t(long, shares, MIN_SHARES, tg_shares);
+ 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);
+ }
+
+ /*
+ * An empty cpuset would propagate a 0 shares_max into
+ * __calc_smp_shares(), where clamp() yields hi when hi < lo and so
+ * defeats the MIN_SHARES floor. Match tg_tasks(), which floors at 1.
+ */
+ return max(nr, 1);
+}
+
+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);
}
/*
@@ -4850,9 +5258,8 @@ static void update_cfs_group(struct sched_entity *se)
if (!gcfs_rq || !gcfs_rq->load.weight)
return;
- shares = calc_group_shares(gcfs_rq);
- if (unlikely(se->load.weight != shares))
- reweight_entity(cfs_rq_of(se), se, shares);
+ shares = static_call(calc_group_shares)(gcfs_rq);
+ reweight_entity(cfs_rq_of(se), se, shares);
}
#else /* !CONFIG_FAIR_GROUP_SCHED: */
@@ -4970,11 +5377,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_share().
+ * Updating tg's load_avg is necessary before update_cfs_group().
*/
static inline void update_tg_load_avg(struct cfs_rq *cfs_rq)
{
- long delta;
+ long dl, dr;
u64 now;
/*
@@ -4995,17 +5402,21 @@ 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;
- 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);
+ 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);
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 delta;
+ long dl, dr;
u64 now;
/*
@@ -5015,9 +5426,12 @@ static inline void clear_tg_load_avg(struct cfs_rq *cfs_rq)
return;
now = rq_clock(rq_of(cfs_rq));
- delta = 0 - cfs_rq->tg_load_avg_contrib;
- atomic_long_add(delta, &cfs_rq->tg->load_avg);
+ 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);
cfs_rq->tg_load_avg_contrib = 0;
+ cfs_rq->tg_runnable_avg_contrib = 0;
cfs_rq->last_update_tg_load_avg = now;
}
@@ -5174,7 +5588,8 @@ static inline void
update_tg_cfs_runnable(struct cfs_rq *cfs_rq, struct sched_entity *se, struct cfs_rq *gcfs_rq)
{
long delta_sum, delta_avg = gcfs_rq->avg.runnable_avg - se->avg.runnable_avg;
- u32 new_sum, divider;
+ u64 new_sum;
+ u32 divider;
/* Nothing to update */
if (!delta_avg)
@@ -5188,7 +5603,7 @@ update_tg_cfs_runnable(struct cfs_rq *cfs_rq, struct sched_entity *se, struct cf
/* Set new sched_entity's runnable */
se->avg.runnable_avg = gcfs_rq->avg.runnable_avg;
- new_sum = se->avg.runnable_avg * divider;
+ new_sum = (u64)se->avg.runnable_avg * divider;
delta_sum = (long)new_sum - (long)se->avg.runnable_sum;
se->avg.runnable_sum = new_sum;
@@ -5430,7 +5845,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_share() for example.
+ * cfs_rq->avg is used for task_h_load() and update_cfs_group() for example.
*
* Return: true if the load decayed or we removed load.
*
@@ -5968,6 +6383,7 @@ 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;
@@ -5975,6 +6391,9 @@ 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
@@ -5983,7 +6402,7 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
*
* EEVDF: placement strategy #1 / #2
*/
- if (sched_feat(PLACE_LAG) && cfs_rq->nr_queued && se->vlag) {
+ if (sched_feat(PLACE_LAG) && nr_queued && se->vlag) {
struct sched_entity *curr = cfs_rq->curr;
long load, weight;
@@ -6043,9 +6462,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->load.weight);
+ load += avg_vruntime_weight(cfs_rq, curr->h_load.weight);
- weight = avg_vruntime_weight(cfs_rq, se->load.weight);
+ weight = avg_vruntime_weight(cfs_rq, se->h_load.weight);
lag *= load + weight;
if (WARN_ON_ONCE(!load))
load = 1;
@@ -6104,22 +6523,8 @@ 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.
@@ -6138,13 +6543,6 @@ 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 */
@@ -6153,8 +6551,6 @@ 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) {
@@ -6172,36 +6568,46 @@ enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
}
}
-static void __clear_buddies_next(struct sched_entity *se)
+static void set_next_buddy(struct cfs_rq *cfs_rq, struct sched_entity *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;
- }
+ if (WARN_ON_ONCE(!se->on_rq || se->sched_delayed))
+ return;
+ if (se_is_idle(se))
+ return;
+ cfs_rq->next = se;
}
static void clear_buddies(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
if (cfs_rq->next == se)
- __clear_buddies_next(se);
+ cfs_rq->next = NULL;
}
static __always_inline void return_cfs_rq_runtime(struct cfs_rq *cfs_rq);
static void set_delayed(struct sched_entity *se)
{
- se->sched_delayed = 1;
-
/*
* Delayed se of cfs_rq have no tasks queued on them.
- * Do not adjust h_nr_runnable since dequeue_entities()
+ * Do not adjust h_nr_runnable since __dequeue_task()
* will account it for blocked tasks.
+ *
+ * This check can be removed because when flat pick
+ * patches get merged as only task can get delayed,
+ * same for clear_delayed().
*/
- if (!entity_is_task(se))
+ if (!entity_is_task(se)) {
+ se->sched_delayed = 1;
return;
+ }
+
+ /*
+ * Drop a task leaving the runnable set.
+ * Needs to be called before sched_delayed is set.
+ * clear_delayed() mirrors this after clearing the flag.
+ */
+ pref_llc_running_dec(rq_of(cfs_rq_of(se)), task_of(se));
+ se->sched_delayed = 1;
for_each_sched_entity(se) {
struct cfs_rq *cfs_rq = cfs_rq_of(se);
@@ -6223,6 +6629,13 @@ static void clear_delayed(struct sched_entity *se)
if (!entity_is_task(se))
return;
+ /*
+ * Re-add on wake, after sched_delayed is cleared. On a final delayed
+ * dequeue account_llc_dequeue() already cleared pref_llc_queued, so
+ * this does nothing.
+ */
+ pref_llc_running_inc(rq_of(cfs_rq_of(se)), task_of(se));
+
for_each_sched_entity(se) {
struct cfs_rq *cfs_rq = cfs_rq_of(se);
@@ -6230,45 +6643,16 @@ static void clear_delayed(struct sched_entity *se)
}
}
-static bool
+static void
dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
- bool sleep = flags & DEQUEUE_SLEEP;
- int action = 0;
-
- update_curr(cfs_rq);
- clear_buddies(cfs_rq, se);
+ int action = UPDATE_TG;
- 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 (sleep && !(flags & DEQUEUE_DELAYED))
+ if ((flags & DEQUEUE_SLEEP) && !(flags & DEQUEUE_DELAYED))
action |= UPDATE_UTIL_EST;
}
@@ -6286,14 +6670,6 @@ 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);
@@ -6302,9 +6678,6 @@ 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
@@ -6317,15 +6690,11 @@ 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, bool first)
+set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
- clear_buddies(cfs_rq, se);
-
/* 'current' is not kept within the tree. */
if (se->on_rq) {
/*
@@ -6334,16 +6703,12 @@ set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, bool first)
* 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->curr);
- cfs_rq->curr = se;
+ WARN_ON_ONCE(cfs_rq->h_curr);
+ cfs_rq->h_curr = se;
/*
* Track our maximum slice length, if the CPU's load is at
@@ -6363,23 +6728,17 @@ set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, bool first)
se->prev_sum_exec_runtime = se->sum_exec_runtime;
}
-static int dequeue_entities(struct rq *rq, struct sched_entity *se, int flags);
+static bool __dequeue_task(struct rq *rq, struct task_struct *p, 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, struct cfs_rq *cfs_rq, bool protect)
+pick_next_entity(struct rq *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_entities(rq, se, DEQUEUE_SLEEP | DEQUEUE_DELAYED);
+ __dequeue_task(rq, task_of(se), DEQUEUE_SLEEP | DEQUEUE_DELAYED);
/*
* Must not reference @se again, see __block_task().
*/
@@ -6399,13 +6758,11 @@ 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->curr != prev);
- cfs_rq->curr = NULL;
+ WARN_ON_ONCE(cfs_rq->h_curr != prev);
+ cfs_rq->h_curr = NULL;
}
static void
@@ -6845,14 +7202,14 @@ static int tg_throttle_down(struct task_group *tg, void *data)
static bool throttle_cfs_rq(struct cfs_rq *cfs_rq)
{
struct cfs_bandwidth *cfs_b = tg_cfs_bandwidth(cfs_rq->tg);
- struct sched_entity *curr = cfs_rq->curr;
+ struct sched_entity *curr = cfs_rq->h_curr;
struct rq *rq = rq_of(cfs_rq);
scoped_guard(raw_spinlock, &cfs_b->lock) {
u64 target_runtime = 1;
/*
- * If cfs_rq->curr is still runnable, we are here from an
+ * If cfs_rq->h_curr is still runnable, we are here from an
* update_curr(). Request sysctl_sched_cfs_bandwidth_slice
* worth of bandwidth to continue running.
*
@@ -6960,7 +7317,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.nr_queued)
+ if (rq->curr == rq->idle && rq->cfs.h_nr_queued)
resched_curr(rq);
}
@@ -7059,7 +7416,7 @@ static bool distribute_cfs_runtime(struct cfs_bandwidth *cfs_b)
if (!list_empty(&cfs_rq->throttled_csd_list))
continue;
- if (cfs_rq->curr) {
+ if (cfs_rq->h_curr) {
update_rq_clock(rq);
update_curr(cfs_rq);
}
@@ -7295,7 +7652,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->curr)
+ if (!cfs_rq->runtime_enabled || cfs_rq->h_curr)
return;
/* ensure the group is not already throttled */
@@ -7667,7 +8024,7 @@ static void hrtick_start_fair(struct rq *rq, struct task_struct *p)
resched_curr(rq);
return;
}
- delta = (se->load.weight * vdelta) / NICE_0_LOAD;
+ delta = (se->h_load.weight * vdelta) / NICE_0_LOAD;
/*
* Correct for instantaneous load of other classes.
@@ -7767,10 +8124,8 @@ static int choose_idle_cpu(int cpu, struct task_struct *p)
}
static void
-requeue_delayed_entity(struct sched_entity *se)
+requeue_delayed_entity(struct cfs_rq *cfs_rq, 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
@@ -7780,19 +8135,67 @@ requeue_delayed_entity(struct sched_entity *se)
WARN_ON_ONCE(!se->on_rq);
if (update_entity_lag(cfs_rq, se)) {
- cfs_rq->nr_queued--;
+ cfs_rq->h_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->nr_queued++;
+ cfs_rq->h_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;
+}
+
+/* Update curr's vruntime before placing entity or updating lag */
+static inline void update_curr_eevdf(struct cfs_rq *cfs_rq)
+{
+ if (!cfs_rq->curr)
+ return;
+
+ update_curr(cfs_rq_of(cfs_rq->curr));
+}
+
/*
* The enqueue_task method is called before nr_running is
* increased. Here we update the fair scheduling stats and
@@ -7801,13 +8204,12 @@ requeue_delayed_entity(struct sched_entity *se)
static void
enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
- struct cfs_rq *cfs_rq;
- struct sched_entity *se = &p->se;
- 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;
+ int task_new = !(flags & ENQUEUE_WAKEUP);
+ struct sched_entity *se = &p->se;
+ struct cfs_rq *cfs_rq = &rq->cfs;
+ unsigned long weight;
+ bool curr;
if (task_is_throttled(p) && enqueue_throttled_task(p))
return;
@@ -7819,10 +8221,12 @@ 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(&rq->cfs, p);
+ util_est_enqueue(cfs_rq, p);
+
+ update_curr_eevdf(cfs_rq);
if (flags & ENQUEUE_DELAYED) {
- requeue_delayed_entity(se);
+ requeue_delayed_entity(cfs_rq, se);
return;
}
@@ -7834,57 +8238,22 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
if (p->in_iowait)
cpufreq_update_util(rq, SCHED_CPUFREQ_IOWAIT);
- if (task_new && se->sched_delayed)
- h_nr_runnable = 0;
-
- for_each_sched_entity(se) {
- if (se->on_rq) {
- if (se->sched_delayed)
- requeue_delayed_entity(se);
- break;
- }
- cfs_rq = cfs_rq_of(se);
-
- /*
- * 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);
-
- 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);
+ /*
+ * XXX comment on the curr thing
+ */
+ curr = (cfs_rq->curr == se);
+ if (curr)
+ place_entity(cfs_rq, se, flags);
- update_load_avg(cfs_rq, se, UPDATE_TG);
- se_update_runnable(se);
- update_cfs_group(se);
+ if (se->on_rq && se->sched_delayed)
+ requeue_delayed_entity(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);
+ weight = enqueue_hierarchy(p, flags);
- 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 (!curr) {
+ reweight_eevdf(cfs_rq, se, weight, false);
+ place_entity(cfs_rq, se, flags | ENQUEUE_QUEUED);
+ __enqueue_entity(cfs_rq, se);
}
if (!rq_h_nr_queued && rq->cfs.h_nr_queued)
@@ -7915,105 +8284,109 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
hrtick_update(rq);
}
-/*
- * 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)
+static void dequeue_hierarchy(struct task_struct *p, int flags)
{
- bool was_sched_idle = sched_idle_rq(rq);
+ struct sched_entity *se = &p->se;
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;
- struct cfs_rq *cfs_rq;
- u64 slice = 0;
+ int h_nr_idle = task_has_idle_policy(p);
+ bool dequeue = true;
- 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;
- }
+ if (task_sleep || task_delayed || !se->sched_delayed)
+ h_nr_runnable = 1;
for_each_sched_entity(se) {
- cfs_rq = cfs_rq_of(se);
+ struct cfs_rq *cfs_rq = cfs_rq_of(se);
- if (!dequeue_entity(cfs_rq, se, flags)) {
- if (p && &p->se == se)
- return -1;
+ update_curr(cfs_rq);
- slice = cfs_rq_min_slice(cfs_rq);
- break;
+ 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);
}
cfs_rq->h_nr_runnable -= h_nr_runnable;
- cfs_rq->h_nr_queued -= h_nr_queued;
+ cfs_rq->h_nr_queued--;
cfs_rq->h_nr_idle -= h_nr_idle;
if (cfs_rq_is_idle(cfs_rq))
- h_nr_idle = h_nr_queued;
+ h_nr_idle = 1;
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);
}
+}
- for_each_sched_entity(se) {
- cfs_rq = cfs_rq_of(se);
+/*
+ * 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;
- update_load_avg(cfs_rq, se, UPDATE_TG);
- se_update_runnable(se);
- update_cfs_group(se);
+ clear_buddies(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);
+ update_curr_eevdf(cfs_rq);
+ update_entity_lag(cfs_rq, se);
- cfs_rq->h_nr_runnable -= h_nr_runnable;
- cfs_rq->h_nr_queued -= h_nr_queued;
- cfs_rq->h_nr_idle -= h_nr_idle;
+ 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;
- if (cfs_rq_is_idle(cfs_rq))
- h_nr_idle = h_nr_queued;
+ WARN_ON_ONCE(delay && se->sched_delayed);
- if (throttled_hierarchy(cfs_rq) && task_throttled)
- record_throttle_clock(cfs_rq);
+ 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;
+ }
}
- sub_nr_running(rq, h_nr_queued);
+ 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);
/* balance early to pull high priority tasks */
if (unlikely(!was_sched_idle && sched_idle_rq(rq)))
rq->next_balance = jiffies;
- if (p && task_delayed) {
+ if (task_delayed) {
+ clear_delayed(se);
+
WARN_ON_ONCE(!task_sleep);
WARN_ON_ONCE(p->on_rq != 1);
@@ -8025,7 +8398,7 @@ static int dequeue_entities(struct rq *rq, struct sched_entity *se, int flags)
__block_task(rq, p);
}
- return 1;
+ return true;
}
/*
@@ -8043,11 +8416,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_entities(rq, &p->se, flags) < 0)
+ if (!__dequeue_task(rq, p, flags))
return false;
/*
- * Must not reference @p after dequeue_entities(DEQUEUE_DELAYED).
+ * Must not reference @p after __dequeue_task(DEQUEUE_DELAYED).
*/
return true;
}
@@ -9642,19 +10015,6 @@ 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);
}
@@ -9688,27 +10048,14 @@ 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. */
};
-static inline bool
-set_preempt_buddy(struct cfs_rq *cfs_rq, int wake_flags,
- struct sched_entity *pse, struct sched_entity *se)
+static inline bool set_preempt_buddy(struct cfs_rq *cfs_rq, struct sched_entity *pse)
{
/*
* Keep existing buddy if the deadline is sooner than pse.
@@ -9719,7 +10066,16 @@ 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(pse);
+ set_next_buddy(cfs_rq, pse);
+ return true;
+}
+
+static inline bool set_short_buddy(struct cfs_rq *cfs_rq, struct sched_entity *pse)
+{
+ if (cfs_rq->next && cfs_rq->next->slice < pse->slice)
+ return false;
+
+ set_next_buddy(cfs_rq, pse);
return true;
}
@@ -9772,13 +10128,14 @@ 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 = task_cfs_rq(donor);
+ struct cfs_rq *cfs_rq = &rq->cfs;
int cse_is_idle, pse_is_idle;
/*
* XXX Getting preempted by higher class, try and find idle CPU?
*/
- if (p->sched_class != &fair_sched_class)
+ if (p->sched_class != &fair_sched_class ||
+ donor->sched_class != &fair_sched_class)
return;
if (unlikely(se == pse))
@@ -9803,42 +10160,43 @@ static void wakeup_preempt_fair(struct rq *rq, struct task_struct *p, int wake_f
* prevents us from potentially nominating it as a false LAST_BUDDY
* below.
*/
- if (test_tsk_need_resched(rq->curr))
+ if (!sched_feat(PREEMPT_SHORT) && test_tsk_need_resched(rq->curr))
return;
if (!sched_feat(WAKEUP_PREEMPTION))
return;
- find_matching_se(&se, &pse);
WARN_ON_ONCE(!pse);
cse_is_idle = se_is_idle(se);
pse_is_idle = se_is_idle(pse);
+ nse = se;
/*
* Preempt an idle entity in favor of a non-idle entity (and don't preempt
* in the inverse case).
*/
- if (cse_is_idle && !pse_is_idle) {
- /*
- * When non-idle entity preempt an idle entity,
- * don't give idle entity slice protection.
- */
- preempt_action = PREEMPT_WAKEUP_SHORT;
+ if (cse_is_idle && !pse_is_idle)
goto preempt;
- }
+
+ update_curr_fair(rq);
if (cse_is_idle != pse_is_idle)
- return;
+ goto update;
/*
* BATCH and IDLE tasks do not preempt others.
*/
if (unlikely(!normal_policy(p->policy)))
- return;
+ goto update;
+
+ /*
+ * Do not preempt for tasks that are sched_delayed as it would violate
+ * EEVDF to forcibly queue an ineligible task.
+ */
+ if (pse->sched_delayed)
+ goto update;
- 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.
@@ -9850,17 +10208,13 @@ static void wakeup_preempt_fair(struct rq *rq, struct task_struct *p, int wake_f
/*
* Ignore wakee preemption on WF_FORK as it is less likely that
- * there is shared data as exec often follow fork. Do not
- * preempt for tasks that are sched_delayed as it would violate
- * EEVDF to forcibly queue an ineligible task.
+ * there is shared data as exec often follow fork.
*/
- if ((wake_flags & WF_FORK) || pse->sched_delayed)
- return;
+ if (wake_flags & WF_FORK)
+ goto update;
/* Prefer picking wakee soon if appropriate. */
- if (sched_feat(NEXT_BUDDY) &&
- set_preempt_buddy(cfs_rq, wake_flags, pse, se)) {
-
+ if (sched_feat(NEXT_BUDDY) && set_preempt_buddy(cfs_rq, pse)) {
/*
* Decide whether to obey WF_SYNC hint for a new buddy. Old
* buddies are ignored as they may not be relevant to the
@@ -9882,29 +10236,33 @@ static void wakeup_preempt_fair(struct rq *rq, struct task_struct *p, int wake_f
}
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 (cfs_rq->h_nr_queued) {
+ nse = pick_next_entity(rq, preempt_action != PREEMPT_WAKEUP_SHORT);
+ if (unlikely(!nse))
+ goto pick;
+
+ /* 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 @p is eligible but not the next task to run then cancel protection
+ * to prevent large scheduling latency
*/
- if (!nse && cfs_rq->nr_queued)
- goto pick;
-
+ if (preempt_action == PREEMPT_WAKEUP_SHORT && entity_eligible(cfs_rq, pse))
+ goto preempt;
+update:
if (sched_feat(RUN_TO_PARITY))
update_protect_slice(cfs_rq, se);
return;
preempt:
- if (preempt_action == PREEMPT_WAKEUP_SHORT) {
- cancel_protect_slice(se);
- clear_buddies(cfs_rq, se);
- }
+ cancel_protect_slice(se);
+
+ if (preempt_action == PREEMPT_WAKEUP_SHORT)
+ set_short_buddy(cfs_rq, pse);
resched_curr_lazy(rq);
}
@@ -9912,33 +10270,24 @@ preempt:
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:
- cfs_rq = &rq->cfs;
- if (!cfs_rq->nr_queued)
+ if (!cfs_rq->h_nr_queued)
goto idle;
- throttled = false;
+ /* Might not have done put_prev_entity() */
+ if (cfs_rq->curr && cfs_rq->curr->on_rq)
+ update_curr_eevdf(cfs_rq);
- 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);
+ se = pick_next_entity(rq, true);
+ if (!se)
+ goto again;
p = task_of(se);
- if (unlikely(throttled))
- task_throttle_setup_work(p);
return p;
idle:
@@ -9975,7 +10324,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;
+ struct cfs_rq *cfs_rq = &rq->cfs;
struct sched_entity *nse = NULL;
#ifdef CONFIG_FAIR_GROUP_SCHED
@@ -9985,7 +10334,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->curr)
+ if (!nse || cfs_rq->h_curr)
put_prev_entity(cfs_rq, se);
#ifdef CONFIG_FAIR_GROUP_SCHED
if (nse) {
@@ -10004,6 +10353,14 @@ 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);
}
/*
@@ -10012,8 +10369,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?
@@ -10027,7 +10384,7 @@ static void yield_task_fair(struct rq *rq)
/*
* Update run-time statistics of the 'current'.
*/
- update_curr(cfs_rq);
+ update_curr_eevdf(cfs_rq);
/*
* Tell update_rq_clock() that we've just updated,
* so we don't do microscopic update in schedule()
@@ -10054,11 +10411,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)
+ if (!se->on_rq || se->sched_delayed)
return false;
/* Tell the scheduler that we'd really like se to run next. */
- set_next_buddy(se);
+ set_next_buddy(&task_rq(p)->cfs, se);
yield_task_fair(rq);
@@ -10254,6 +10611,7 @@ enum migration_type {
#define LBF_SOME_PINNED 0x08
#define LBF_ACTIVE_LB 0x10
#define LBF_LLC_PINNED 0x20
+#define LBF_ACTIVE_LB_LLC 0x40
struct lb_env {
struct sched_domain *sd;
@@ -10397,15 +10755,10 @@ 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;
+ struct cfs_rq *dst_cfs_rq = &cpu_rq(dest_cpu)->cfs;
-#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))
+ if (sched_feat(PLACE_LAG) && dst_cfs_rq->h_nr_queued &&
+ !entity_eligible(&task_rq(p)->cfs, &p->se))
return 1;
return 0;
@@ -10563,30 +10916,53 @@ static enum llc_mig can_migrate_llc(int src_cpu, int dst_cpu,
return mig_llc;
}
+static inline bool task_misfits_asym_cpu(struct lb_env *env, struct task_struct *p)
+{
+ /*
+ * On asymmetric CPU capacity domains, do not let cache-aware
+ * balancing pull the task onto a destination CPU that cannot
+ * accommodate it. Doing so would turn the task into a misfit on
+ * the destination, trading a cache-locality gain for a capacity
+ * loss. If the task already does not fit its source CPU, the move
+ * cannot make things worse, so let the LLC preference decide.
+ */
+ if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && p &&
+ !task_fits_cpu(p, env->dst_cpu) &&
+ task_fits_cpu(p, env->src_cpu))
+ return true;
+
+ return false;
+}
+
/*
* Check if task p can migrate from source LLC to
* destination LLC in terms of cache aware load balance.
*/
-static enum llc_mig can_migrate_llc_task(int src_cpu, int dst_cpu,
+static enum llc_mig can_migrate_llc_task(struct lb_env *env,
struct task_struct *p)
{
- struct mm_struct *mm;
+ struct sched_cache_group *grp;
bool to_pref;
- int cpu;
+ int cpu, src_cpu, dst_cpu;
+
+ if (task_misfits_asym_cpu(env, p))
+ return mig_forbid;
- mm = p->mm;
- if (!mm)
+ src_cpu = env->src_cpu;
+ dst_cpu = env->dst_cpu;
+ grp = rcu_dereference_all(p->sched_cache_grp);
+ if (!grp)
return mig_unrestricted;
- cpu = READ_ONCE(mm->sc_stat.cpu);
+ cpu = READ_ONCE(grp->cpu);
if (cpu < 0 || cpus_share_cache(src_cpu, dst_cpu))
return mig_unrestricted;
/* skip cache aware load balance for too many threads */
- if (invalid_llc_nr(mm, p, dst_cpu) ||
- exceed_llc_capacity(mm, dst_cpu)) {
- if (READ_ONCE(mm->sc_stat.cpu) != -1)
- WRITE_ONCE(mm->sc_stat.cpu, -1);
+ if (invalid_llc_nr(grp, p, dst_cpu) ||
+ exceed_llc_capacity(grp, dst_cpu)) {
+ if (READ_ONCE(grp->cpu) != -1)
+ WRITE_ONCE(grp->cpu, -1);
return mig_unrestricted;
}
@@ -10630,6 +11006,14 @@ alb_break_llc(struct lb_env *env)
unsigned long util = 0;
struct task_struct *cur;
+ /*
+ * Migrating misfit tasks from current CPU
+ * to CPU with a better fit.
+ * Prioritize that over LLC preference.
+ */
+ if (env->migration_type == migrate_misfit)
+ return false;
+
if (env->src_rq->nr_running <= 1)
return true;
@@ -10637,7 +11021,8 @@ alb_break_llc(struct lb_env *env)
if (cur && cur->sched_class == &fair_sched_class)
util = task_util(cur);
- if (can_migrate_llc(env->src_cpu, env->dst_cpu,
+ if (task_misfits_asym_cpu(env, cur) ||
+ can_migrate_llc(env->src_cpu, env->dst_cpu,
util, false) == mig_forbid)
return true;
}
@@ -10646,6 +11031,21 @@ alb_break_llc(struct lb_env *env)
}
/*
+ * Returns true if p's preferred LLC does not match the destination CPU
+ * under migrate_llc_task semantics. Passive LB passes migrate_llc_task
+ * in env->migration_type, while active LB carries LBF_ACTIVE_LB_LLC in
+ * env->flags to avoid overwriting env->migration_type.
+ */
+static inline bool
+migrate_llc_task_wrong_dst(struct task_struct *p, struct lb_env *env)
+{
+ return sched_cache_enabled() &&
+ (env->migration_type == migrate_llc_task ||
+ env->flags & LBF_ACTIVE_LB_LLC) &&
+ READ_ONCE(p->preferred_llc) != llc_id(env->dst_cpu);
+}
+
+/*
* Check if migrating task p from env->src_cpu to
* env->dst_cpu breaks LLC localiy.
*/
@@ -10673,12 +11073,10 @@ static bool migrate_degrades_llc(struct task_struct *p, struct lb_env *env)
* run on env->dst_cpu, skip the tasks do not prefer
* env->dst_cpu, and find the one that prefers.
*/
- if (env->migration_type == migrate_llc_task &&
- READ_ONCE(p->preferred_llc) != llc_id(env->dst_cpu))
+ if (migrate_llc_task_wrong_dst(p, env))
return true;
- if (can_migrate_llc_task(env->src_cpu,
- env->dst_cpu, p) != mig_forbid)
+ if (can_migrate_llc_task(env, p) != mig_forbid)
return false;
return true;
@@ -10698,6 +11096,12 @@ alb_break_llc(struct lb_env *env)
}
static inline bool
+migrate_llc_task_wrong_dst(struct task_struct *p, struct lb_env *env)
+{
+ return false;
+}
+
+static inline bool
migrate_degrades_llc(struct task_struct *p, struct lb_env *env)
{
return false;
@@ -10796,7 +11200,7 @@ int can_migrate_task(struct task_struct *p, struct lb_env *env)
* 4) too many balance attempts have failed.
*/
if (env->flags & LBF_ACTIVE_LB)
- return 1;
+ return !migrate_llc_task_wrong_dst(p, env);
degrades = migrate_degrades_locality(p, env);
if (!degrades) {
@@ -11188,7 +11592,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;
@@ -11742,6 +12146,15 @@ static inline bool llc_balance(struct lb_env *env, struct sg_lb_stats *sgs,
return false;
/*
+ * On asymmetric domains, group_misfit_task_load
+ * should be prioritized to move tasks to CPU that fit them
+ * over aggregating tasks to their preferred LLC.
+ */
+ if ((env->sd->flags & SD_ASYM_CPUCAPACITY) &&
+ sgs->group_misfit_task_load)
+ return false;
+
+ /*
* Skip cache aware tagging if nr_balanced_failed is sufficiently high.
* Threshold of cache_nice_tries is set to 1 higher than nr_balance_failed
* to avoid excessive task migration at the same time.
@@ -11860,10 +12273,25 @@ static inline void update_sg_lb_stats(struct lb_env *env,
continue;
if (sd_flags & SD_ASYM_CPUCAPACITY) {
- /* Check for a misfit task on the cpu */
- if (sgs->group_misfit_task_load < rq->misfit_task_load) {
- sgs->group_misfit_task_load = rq->misfit_task_load;
- *sg_overloaded = 1;
+ if (rq->misfit_task_load) {
+ /*
+ * Always mark the root domain overloaded so big
+ * CPUs can pick up misfit tasks via newly idle
+ * balance.
+ */
+ if (balancing_at_rd)
+ *sg_overloaded = 1;
+
+ /*
+ * Only account misfit load if @dst_cpu can
+ * help; otherwise, the group may be classified
+ * as misfit_task and update_sd_pick_busiest()
+ * will skip it.
+ */
+ if (capacity_greater(capacity_of(env->dst_cpu),
+ group->sgc->max_capacity) &&
+ (sgs->group_misfit_task_load < rq->misfit_task_load))
+ sgs->group_misfit_task_load = rq->misfit_task_load;
}
} else if (env->idle && sched_reduced_capacity(rq, env->sd)) {
/* Check for a task running on a CPU with reduced capacity */
@@ -11942,6 +12370,17 @@ static bool update_sd_pick_busiest(struct lb_env *env,
sds->local_stat.group_type != group_has_spare))
return false;
+ /*
+ * Candidate sg has no more than one task per CPU and has higher
+ * per-CPU capacity. Migrating tasks to less capable CPUs may harm
+ * throughput. Maximize throughput, power/energy consequences are not
+ * considered.
+ */
+ if ((env->sd->flags & SD_ASYM_CPUCAPACITY) &&
+ (sgs->group_type <= group_fully_busy) &&
+ (capacity_greater(sg->sgc->min_capacity, capacity_of(env->dst_cpu))))
+ return false;
+
if (sgs->group_type > busiest->group_type)
return true;
@@ -12048,17 +12487,6 @@ has_spare:
break;
}
- /*
- * Candidate sg has no more than one task per CPU and has higher
- * per-CPU capacity. Migrating tasks to less capable CPUs may harm
- * throughput. Maximize throughput, power/energy consequences are not
- * considered.
- */
- if ((env->sd->flags & SD_ASYM_CPUCAPACITY) &&
- (sgs->group_type <= group_fully_busy) &&
- (capacity_greater(sg->sgc->min_capacity, capacity_of(env->dst_cpu))))
- return false;
-
return true;
}
@@ -12976,9 +13404,24 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env,
* average load.
*/
if (env->sd->flags & SD_ASYM_CPUCAPACITY &&
- !capacity_greater(capacity_of(env->dst_cpu), capacity) &&
- nr_running == 1)
- continue;
+ nr_running == 1) {
+ bool cluster_equal_cap = static_branch_unlikely(&sched_cluster_active) &&
+ (get_actual_cpu_capacity(env->dst_cpu) ==
+ get_actual_cpu_capacity(i));
+ bool smt_degraded_cap = sched_smt_active() && !is_core_idle(i);
+
+ /*
+ * Busy SMT siblings reduce the capacity of CPU @i. Do
+ * not skip it in this case.
+ *
+ * CONFIG_SCHED_CLUSTER requires balancing load across
+ * clusters of identical capacity, accounting for
+ * hardware and cpufreq pressure.
+ */
+ if (!smt_degraded_cap && !cluster_equal_cap &&
+ !capacity_greater(capacity_of(env->dst_cpu), capacity))
+ continue;
+ }
/*
* Make sure we only pull tasks from a CPU of lower priority
@@ -13156,6 +13599,20 @@ static int need_active_balance(struct lb_env *env)
}
static int active_load_balance_cpu_stop(void *data);
+static int active_load_balance_llc_cpu_stop(void *data);
+
+/*
+ * migration_type is checked elsewhere to decide migration policy, so
+ * it shouldn't be repurposed just to flag an LLC-directed active
+ * balance across the stopper. Pick the callback here instead.
+ */
+static inline cpu_stop_fn_t alb_stop_fn(struct lb_env *env)
+{
+ if (env->migration_type == migrate_llc_task)
+ return active_load_balance_llc_cpu_stop;
+
+ return active_load_balance_cpu_stop;
+}
static int should_we_balance(struct lb_env *env)
{
@@ -13437,75 +13894,78 @@ more_balance:
}
}
- if (!ld_moved) {
- schedstat_inc(sd->lb_failed[idle]);
- /*
- * Increment the failure counter only on periodic balance.
- * We do not want newidle balance, which can be very
- * frequent, pollute the failure counter causing
- * excessive cache_hot migrations and active balances.
- *
- * Similarly for migration_misfit which is not related to
- * load/util migration, don't pollute nr_balance_failed.
- *
- * The same for cache aware scheduling's allowance for
- * load imbalance. If regular load balance does not
- * migrate task due to LLC locality, it is a expected
- * behavior and don't pollute nr_balance_failed.
- * See can_migrate_task().
- */
- if (idle != CPU_NEWLY_IDLE &&
- env.migration_type != migrate_misfit &&
- !(env.flags & LBF_LLC_PINNED))
- sd->nr_balance_failed++;
+ if (ld_moved) {
+ sd->nr_balance_failed = 0;
+ goto out_unbalanced;
+ }
- if (need_active_balance(&env)) {
- unsigned long flags;
+ schedstat_inc(sd->lb_failed[idle]);
+ /*
+ * Increment the failure counter only on periodic balance.
+ * We do not want newidle balance, which can be very
+ * frequent, pollute the failure counter causing
+ * excessive cache_hot migrations and active balances.
+ *
+ * Similarly for migration_misfit which is not related to
+ * load/util migration, don't pollute nr_balance_failed.
+ *
+ * The same for cache aware scheduling's allowance for
+ * load imbalance. If regular load balance does not
+ * migrate task due to LLC locality, it is a expected
+ * behavior and don't pollute nr_balance_failed.
+ * See can_migrate_task().
+ */
+ if (idle != CPU_NEWLY_IDLE &&
+ env.migration_type != migrate_misfit &&
+ !(env.flags & LBF_LLC_PINNED))
+ sd->nr_balance_failed++;
- raw_spin_rq_lock_irqsave(busiest, flags);
+ if (!need_active_balance(&env))
+ goto out_unbalanced;
- /*
- * Don't kick the active_load_balance_cpu_stop,
- * if the curr task on busiest CPU can't be
- * moved to this_cpu:
- */
- if (!cpumask_test_cpu(this_cpu, busiest->curr->cpus_ptr)) {
- raw_spin_rq_unlock_irqrestore(busiest, flags);
- goto out_one_pinned;
- }
+ scoped_guard (raw_spin_rq_lock_irqsave, busiest) {
+ /*
+ * Don't kick the active_load_balance_cpu_stop,
+ * if the curr task on busiest CPU can't be
+ * moved to this_cpu:
+ */
+ if (!cpumask_test_cpu(this_cpu, busiest->curr->cpus_ptr))
+ goto out_one_pinned;
- /* Record that we found at least one task that could run on this_cpu */
- env.flags &= ~LBF_ALL_PINNED;
+ /* Record that we found at least one task that could run on this_cpu */
+ env.flags &= ~LBF_ALL_PINNED;
- /*
- * ->active_balance synchronizes accesses to
- * ->active_balance_work. Once set, it's cleared
- * only after active load balance is finished.
- */
- if (!busiest->active_balance) {
- busiest->active_balance = 1;
- busiest->push_cpu = this_cpu;
- active_balance = 1;
- }
+ /*
+ * ->active_balance synchronizes accesses to
+ * ->active_balance_work. Once set, it's cleared
+ * only after active load balance is finished.
+ */
+ if (busiest->active_balance)
+ goto out_unbalanced;
- preempt_disable();
- raw_spin_rq_unlock_irqrestore(busiest, flags);
- if (active_balance) {
- stop_one_cpu_nowait(cpu_of(busiest),
- active_load_balance_cpu_stop, busiest,
- &busiest->active_balance_work);
- }
- preempt_enable();
- }
- } else {
- sd->nr_balance_failed = 0;
- }
+ /*
+ * @busiest dropped its rq_lock in the middle of
+ * scheduling out its ->curr task (->on_rq := 0), no
+ * need to forcefully punt it away with active balance.
+ */
+ if (!busiest->curr->on_rq)
+ goto out_unbalanced;
- if (likely(!active_balance) || need_active_balance(&env)) {
- /* We were unbalanced, so reset the balancing interval */
- sd->balance_interval = sd->min_interval;
+ busiest->active_balance = 1;
+ busiest->push_cpu = this_cpu;
+ active_balance = 1;
+ preempt_disable();
+ }
+ if (active_balance) {
+ stop_one_cpu_nowait(cpu_of(busiest),
+ alb_stop_fn(&env), busiest,
+ &busiest->active_balance_work);
}
+ preempt_enable();
+out_unbalanced:
+ /* We were unbalanced, so reset the balancing interval */
+ sd->balance_interval = sd->min_interval;
goto out;
out_balanced:
@@ -13603,7 +14063,7 @@ update_next_balance(struct sched_domain *sd, unsigned long *next_balance)
* least 1 task to be running on each physical CPU where possible, and
* avoids physical / logical imbalances.
*/
-static int active_load_balance_cpu_stop(void *data)
+static int __active_load_balance_cpu_stop(void *data, unsigned int lb_flags)
{
struct rq *busiest_rq = data;
int busiest_cpu = cpu_of(busiest_rq);
@@ -13653,7 +14113,7 @@ static int active_load_balance_cpu_stop(void *data)
.src_cpu = busiest_rq->cpu,
.src_rq = busiest_rq,
.idle = CPU_IDLE,
- .flags = LBF_ACTIVE_LB,
+ .flags = LBF_ACTIVE_LB | lb_flags,
};
schedstat_inc(sd->alb_count);
@@ -13681,6 +14141,16 @@ out_unlock:
return 0;
}
+static int active_load_balance_cpu_stop(void *data)
+{
+ return __active_load_balance_cpu_stop(data, 0);
+}
+
+static int active_load_balance_llc_cpu_stop(void *data)
+{
+ return __active_load_balance_cpu_stop(data, LBF_ACTIVE_LB_LLC);
+}
+
/*
* Scale the max sched_balance_rq interval with the number of CPUs in the system.
* This trades load-balance latency on larger machines for less cross talk.
@@ -13849,29 +14319,62 @@ static inline int on_null_domain(struct rq *rq)
*/
static inline int find_new_ilb(void)
{
- int this_cpu = smp_processor_id();
- const struct cpumask *hk_mask;
- int ilb_cpu;
+ struct cpumask *ilb_cpus;
+ int ilb_cpu, fallback = -1;
- hk_mask = housekeeping_cpumask(HK_TYPE_KERNEL_NOISE);
+ lockdep_assert_irqs_disabled();
+
+ /*
+ * Reuse the per-CPU select_rq_mask, which is protected from concurrent
+ * use on this CPU by having interrupts disabled.
+ */
+ ilb_cpus = this_cpu_cpumask_var_ptr(select_rq_mask);
+ cpumask_and(ilb_cpus, nohz.idle_cpus_mask,
+ housekeeping_cpumask(HK_TYPE_KERNEL_NOISE));
- for_each_cpu_and(ilb_cpu, nohz.idle_cpus_mask, hk_mask) {
- if (ilb_cpu == this_cpu)
+ for_each_cpu(ilb_cpu, ilb_cpus) {
+ if (!idle_cpu(ilb_cpu)) {
+ /*
+ * Once an idle fallback exists, a busy CPU proves that
+ * this core cannot be fully idle. Skip its siblings.
+ */
+ if (sched_smt_active() && fallback >= 0)
+ cpumask_andnot(ilb_cpus, ilb_cpus, cpu_smt_mask(ilb_cpu));
continue;
+ }
+
+ /*
+ * Running the idle load balancer on an idle sibling of a busy
+ * SMT core can reduce the capacity available to its sibling. Prefer
+ * a CPU whose entire core is idle, but retain the first idle CPU as
+ * a fallback so idle balancing can still make progress when no fully
+ * idle core exists.
+ */
+ if (sched_smt_active() && !is_core_idle(ilb_cpu)) {
+ if (fallback < 0)
+ fallback = ilb_cpu;
+
+ /*
+ * The core is not idle, so there is no need to check
+ * any of its other SMT siblings.
+ */
+ cpumask_andnot(ilb_cpus, ilb_cpus,
+ cpu_smt_mask(ilb_cpu));
+ continue;
+ }
- if (idle_cpu(ilb_cpu))
- return ilb_cpu;
+ return ilb_cpu;
}
- return -1;
+ return fallback;
}
/*
* Kick a CPU to do the NOHZ balancing, if it is time for it, via a cross-CPU
* SMP function call (IPI).
*
- * We pick the first idle CPU in the HK_TYPE_KERNEL_NOISE housekeeping set
- * (if there is one).
+ * Prefer a CPU on a fully idle core in the HK_TYPE_KERNEL_NOISE housekeeping
+ * set. Fall back to the first idle CPU when no fully idle core exists.
*/
static void kick_ilb(unsigned int flags)
{
@@ -14571,7 +15074,7 @@ static inline void task_tick_core(struct rq *rq, struct task_struct *curr)
* if the sibling is forced idle, then trigger schedule to
* give forced idle task a chance.
*
- * sched_slice() considers only this active rq and it gets the
+ * __entity_slice_used() considers only this active rq and it gets the
* whole slice. But during force idle, we have siblings acting
* like a single runqueue and hence we need to consider runnable
* tasks on this CPU and the forced idle CPU. Ideally, we should
@@ -14580,7 +15083,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.nr_queued == 1 &&
+ if (rq->core->core_forceidle_count && rq->cfs.h_nr_queued == 1 &&
__entity_slice_used(&curr->se, MIN_NR_TASKS_DURING_FORCEIDLE))
resched_curr(rq);
}
@@ -14789,30 +15292,8 @@ 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
@@ -14851,11 +15332,20 @@ 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;
- for_each_sched_entity(se) {
- cfs_rq = cfs_rq_of(se);
- entity_tick(cfs_rq, se, queued);
+ 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);
}
if (queued)
@@ -14895,7 +15385,7 @@ prio_changed_fair(struct rq *rq, struct task_struct *p, u64 oldprio)
if (p->prio == oldprio)
return;
- if (rq->cfs.nr_queued == 1)
+ if (rq->cfs.h_nr_queued == 1)
return;
/*
@@ -15024,33 +15514,44 @@ 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) {
- struct cfs_rq *cfs_rq = cfs_rq_of(se);
+ cfs_rq = cfs_rq_of(se);
- if (IS_ENABLED(CONFIG_FAIR_GROUP_SCHED) &&
- first && cfs_rq->curr)
- break;
+ if (!IS_ENABLED(CONFIG_FAIR_GROUP_SCHED) ||
+ !first || !cfs_rq->h_curr)
+ set_next_entity(cfs_rq, se);
- 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)) {
/*
@@ -15163,17 +15664,8 @@ 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->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);
- }
+ if (se)
remove_entity_load_avg(se);
- }
/*
* Only empty task groups can be destroyed; so we can speculatively
diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c
index 052435f4d3e3..eb73b65ce6c4 100644
--- a/kernel/sched/idle.c
+++ b/kernel/sched/idle.c
@@ -503,7 +503,13 @@ static void set_next_task_idle(struct rq *rq, struct task_struct *next, bool fir
struct task_struct *pick_task_idle(struct rq *rq, struct rq_flags *rf)
{
- scx_update_idle(rq, true, false);
+ /*
+ * Notify scx only on an idle-to-idle re-pick (the cpu was already idle).
+ * A real task->idle transition is delivered by set_next_task_idle(), so
+ * calling here too would duplicate it.
+ */
+ if (scx_enabled() && is_idle_task(rq->curr))
+ scx_update_idle(rq, true, false);
return rq->idle;
}
diff --git a/kernel/sched/isolation.c b/kernel/sched/isolation.c
index ef152d401fe2..156025ef81b7 100644
--- a/kernel/sched/isolation.c
+++ b/kernel/sched/isolation.c
@@ -8,6 +8,7 @@
*
*/
#include <linux/sched/isolation.h>
+#include <linux/llist.h>
#include <linux/pci.h>
#include "sched.h"
@@ -27,6 +28,7 @@ struct housekeeping {
};
static struct housekeeping housekeeping;
+static __initdata LLIST_HEAD(memblock_freelist);
bool housekeeping_enabled(enum hk_type type)
{
@@ -189,10 +191,22 @@ void __init housekeeping_init(void)
WARN_ON_ONCE(cpumask_empty(omask));
cpumask_copy(nmask, omask);
RCU_INIT_POINTER(housekeeping.cpumasks[type], nmask);
- memblock_free(omask, cpumask_size());
+ __llist_add((struct llist_node *)omask, &memblock_freelist);
}
}
+static int __init housekeeping_late_init(void)
+{
+ struct llist_node *llnode, *pos, *t;
+
+ /* Free allocated memblock memory, if any */
+ llnode = __llist_del_all(&memblock_freelist);
+ llist_for_each_safe(pos, t, llnode)
+ memblock_free(pos, cpumask_size());
+ return 0;
+}
+pure_initcall(housekeeping_late_init);
+
static void __init housekeeping_setup_type(enum hk_type type,
cpumask_var_t housekeeping_staging)
{
diff --git a/kernel/sched/pelt.c b/kernel/sched/pelt.c
index 897790889ba3..779eb58a4261 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->curr still points to it.
+ * se has been already dequeued but cfs_rq->h_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->curr == se)) {
+ cfs_rq->h_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->curr != NULL)) {
+ cfs_rq->h_curr != NULL)) {
___update_load_avg(&cfs_rq->avg, 1);
trace_pelt_cfs_tp(cfs_rq);
diff --git a/kernel/sched/psi.c b/kernel/sched/psi.c
index d9c9d9480a45..4e152410653d 100644
--- a/kernel/sched/psi.c
+++ b/kernel/sched/psi.c
@@ -1023,7 +1023,7 @@ void psi_account_irqtime(struct rq *rq, struct task_struct *curr, struct task_st
irq = irq_time_read(cpu);
delta = (s64)(irq - rq->psi_irq_time);
- if (delta < 0)
+ if (delta <= 0)
return;
rq->psi_irq_time = irq;
@@ -1134,6 +1134,12 @@ void psi_cgroup_free(struct cgroup *cgroup)
return;
cancel_delayed_work_sync(&cgroup->psi->avgs_work);
+ /*
+ * A psi_schedule_rtpoll_work() call racing the last trigger's
+ * destruction may have re-armed the timer after psi_trigger_destroy()
+ * deleted it. Spurious firing while the group is alive is harmless.
+ */
+ timer_shutdown_sync(&cgroup->psi->rtpoll_timer);
free_percpu(cgroup->psi->pcpu);
/* All triggers must be removed by now */
WARN_ONCE(cgroup->psi->rtpoll_states, "psi: trigger leak\n");
@@ -1292,9 +1298,44 @@ int psi_show(struct seq_file *m, struct psi_group *group, enum psi_res res)
return 0;
}
+/*
+ * Create @group's rtpoll worker after psi_trigger_create() reported the need
+ * for one. kthread creation depends on the whole fork path and we don't want
+ * all of that nested inside cgroup_mutex, so the caller must drop it and any
+ * other lock that forks can wait behind. If two callers race, the loser stops
+ * its never-woken kthread.
+ */
+int psi_trigger_create_rtpoll_worker(struct psi_group *group)
+{
+ struct task_struct *task;
+
+ task = kthread_create(psi_rtpoll_worker, group, "psimon");
+ if (IS_ERR(task))
+ return PTR_ERR(task);
+
+ scoped_guard(mutex, &group->rtpoll_trigger_lock) {
+ if (!rcu_access_pointer(group->rtpoll_task)) {
+ atomic_set(&group->rtpoll_wakeup, 0);
+ wake_up_process(task);
+ rcu_assign_pointer(group->rtpoll_task, task);
+
+ /*
+ * Poll once to catch up on scheduling attempts dropped
+ * while there was no rtpoll worker.
+ */
+ psi_schedule_rtpoll_work(group, 1, true);
+ return 0;
+ }
+ }
+
+ kthread_stop(task);
+ return 0;
+}
+
struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf,
enum psi_res res, struct file *file,
- struct kernfs_open_file *of)
+ struct kernfs_open_file *of,
+ bool *need_rtpoll_worker)
{
struct psi_trigger *t;
enum psi_states state;
@@ -1302,6 +1343,8 @@ struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf,
bool privileged;
u32 window_us;
+ *need_rtpoll_worker = false;
+
if (static_branch_likely(&psi_disabled))
return ERR_PTR(-EOPNOTSUPP);
@@ -1362,26 +1405,14 @@ struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf,
if (privileged) {
mutex_lock(&group->rtpoll_trigger_lock);
- if (!rcu_access_pointer(group->rtpoll_task)) {
- struct task_struct *task;
-
- task = kthread_create(psi_rtpoll_worker, group, "psimon");
- if (IS_ERR(task)) {
- kfree(t);
- mutex_unlock(&group->rtpoll_trigger_lock);
- return ERR_CAST(task);
- }
- atomic_set(&group->rtpoll_wakeup, 0);
- wake_up_process(task);
- rcu_assign_pointer(group->rtpoll_task, task);
- }
-
list_add(&t->node, &group->rtpoll_triggers);
group->rtpoll_min_period = min(group->rtpoll_min_period,
div_u64(t->win.size, UPDATES_PER_WINDOW));
group->rtpoll_nr_triggers[t->state]++;
group->rtpoll_states |= (1 << t->state);
+ *need_rtpoll_worker = !rcu_access_pointer(group->rtpoll_task);
+
mutex_unlock(&group->rtpoll_trigger_lock);
} else {
mutex_lock(&group->avgs_lock);
@@ -1541,6 +1572,8 @@ static ssize_t psi_write(struct file *file, const char __user *user_buf,
size_t buf_size;
struct seq_file *seq;
struct psi_trigger *new;
+ bool need_rtpoll_worker;
+ int ret;
if (static_branch_likely(&psi_disabled))
return -EOPNOTSUPP;
@@ -1565,12 +1598,22 @@ static ssize_t psi_write(struct file *file, const char __user *user_buf,
return -EBUSY;
}
- new = psi_trigger_create(&psi_system, buf, res, file, NULL);
+ new = psi_trigger_create(&psi_system, buf, res, file, NULL,
+ &need_rtpoll_worker);
if (IS_ERR(new)) {
mutex_unlock(&seq->lock);
return PTR_ERR(new);
}
+ if (need_rtpoll_worker) {
+ ret = psi_trigger_create_rtpoll_worker(&psi_system);
+ if (ret) {
+ psi_trigger_destroy(new);
+ mutex_unlock(&seq->lock);
+ return ret;
+ }
+ }
+
smp_store_release(&seq->private, new);
mutex_unlock(&seq->lock);
diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c
index e474c31d8fe6..85303add726d 100644
--- a/kernel/sched/rt.c
+++ b/kernel/sched/rt.c
@@ -1629,7 +1629,8 @@ static void wakeup_preempt_rt(struct rq *rq, struct task_struct *p, int flags)
/*
* XXX If we're preempted by DL, queue a push?
*/
- if (p->sched_class != &rt_sched_class)
+ if (p->sched_class != &rt_sched_class ||
+ donor->sched_class != &rt_sched_class)
return;
if (p->prio < donor->prio) {
@@ -1871,8 +1872,8 @@ static struct task_struct *pick_next_pushable_task(struct rq *rq)
return NULL;
plist_for_each_entry(i, head, pushable_tasks) {
- /* make sure task isn't on_cpu (possible with proxy-exec) */
- if (!task_on_cpu(rq, i)) {
+ /* skip tasks that cannot be migrated */
+ if (!task_on_cpu(rq, i) && !is_migration_disabled(i)) {
p = i;
break;
}
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 56acf502ba26..4c25fbe84fb5 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -493,6 +493,8 @@ 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
@@ -528,21 +530,8 @@ 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,
@@ -571,6 +560,7 @@ 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)
@@ -628,6 +618,17 @@ 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);
@@ -706,6 +707,7 @@ struct cfs_rq {
/*
* CFS load tracking
*/
+ struct sched_entity *h_curr;
struct sched_avg avg;
#ifndef CONFIG_64BIT
u64 last_update_time_copy;
@@ -721,6 +723,7 @@ 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;
@@ -784,39 +787,59 @@ enum scx_rq_flags {
*/
SCX_RQ_ONLINE = 1 << 0,
SCX_RQ_CAN_STOP_TICK = 1 << 1,
- SCX_RQ_BAL_KEEP = 1 << 3, /* balance decided to keep current */
SCX_RQ_CLK_VALID = 1 << 5, /* RQ clock is fresh and valid */
SCX_RQ_BAL_CB_PENDING = 1 << 6, /* must queue a cb after dispatching */
+ SCX_RQ_SUB_IDLE_RENOTIFY = 1 << 7, /* sub-scheds are owed update_idle() */
+ SCX_RQ_ROOT_IDLE_RENOTIFY = 1 << 8, /* the root is owed update_idle() */
SCX_RQ_IN_WAKEUP = 1 << 16,
- SCX_RQ_IN_BALANCE = 1 << 17,
+ SCX_RQ_IN_DISPATCH = 1 << 17,
+};
+
+/* per-rq rescue execution state, see scx_rescue_timerfn() */
+struct scx_rq_rescue {
+ struct scx_dispatch_q dsq; /* stranded tasks awaiting rescue */
+ s64 budget; /* execution token bucket, ns */
+ u64 clock; /* last budget accrual timestamp */
+ struct task_struct *curr; /* task being rescued, one at a time */
+ s64 slice; /* curr's admitted slice */
+ u64 exec_snap; /* sum_exec_runtime at admission */
+ struct timer_list timer; /* paces admission and escalation */
+ u64 kill_at; /* last ejection, init before any */
};
struct scx_rq {
struct scx_dispatch_q local_dsq;
+#ifdef CONFIG_EXT_SUB_SCHED
+ struct scx_dispatch_q reject_dsq; /* staging for cap-rejected tasks */
+ struct scx_rq_rescue rescue;
+#endif
struct list_head runnable_list; /* runnable tasks on this rq */
struct list_head ddsp_deferred_locals; /* deferred ddsps from enq */
- unsigned long ops_qseq;
- u64 extra_enq_flags; /* see move_task_to_local_dsq() */
+ /* both stashed across the activate_task() in move_remote_task_to_local_dsq() */
+ u64 remote_activate_enq_flags;
+ struct scx_sched *remote_activate_sch;
u32 nr_running;
u32 cpuperf_target; /* [0, SCHED_CAPACITY_SCALE] */
bool in_select_cpu;
bool cpu_released;
u32 flags;
u32 nr_immed; /* ENQ_IMMED tasks on local_dsq */
+#ifdef CONFIG_SCHED_CORE
+ u32 lock_drop_seq; /* nr dispatch lock releases */
+#endif
u64 clock; /* current per-rq clock -- see scx_bpf_now() */
- cpumask_var_t cpus_to_kick;
- cpumask_var_t cpus_to_kick_if_idle;
- cpumask_var_t cpus_to_preempt;
- cpumask_var_t cpus_to_wait;
+#ifdef CONFIG_EXT_SUB_SCHED
+ struct llist_head ecaps_to_sync; /* pending ecaps syncs */
+ struct task_struct *sub_dispatch_prev;
+#endif
cpumask_var_t cpus_to_sync;
bool kick_sync_pending;
unsigned long kick_sync;
- struct task_struct *sub_dispatch_prev;
+ struct list_head sched_pcpus_to_kick; /* see kick_cpus_irq_workfn() */
raw_spinlock_t deferred_reenq_lock;
- u64 deferred_reenq_locals_seq;
struct list_head deferred_reenq_locals; /* scheds requesting reenq of local DSQ */
struct list_head deferred_reenq_users; /* user DSQs requesting reenq */
struct balance_callback deferred_bal_cb;
@@ -1358,6 +1381,7 @@ struct rq {
unsigned int core_forceidle_seq;
unsigned int core_forceidle_occupation;
u64 core_forceidle_start;
+ unsigned int core_pick_in_flight;
#endif /* CONFIG_SCHED_CORE */
/* Scratch cpumask to be temporarily used under rq_lock */
@@ -2018,7 +2042,8 @@ DEFINE_LOCK_GUARD_1(rq_lock, struct rq,
rq_unlock(_T->lock, &_T->rf),
struct rq_flags rf)
-DECLARE_LOCK_GUARD_1_ATTRS(rq_lock, __acquires(__rq_lockp(_T)), __releases(__rq_lockp(*(struct rq **)_T)));
+DECLARE_LOCK_GUARD_1_ATTRS(rq_lock, __acquires(__rq_lockp(_T)),
+ __releases(__rq_lockp(*(struct rq **)_T)));
#define class_rq_lock_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(rq_lock, _T)
DEFINE_LOCK_GUARD_1(rq_lock_irq, struct rq,
@@ -2026,7 +2051,8 @@ DEFINE_LOCK_GUARD_1(rq_lock_irq, struct rq,
rq_unlock_irq(_T->lock, &_T->rf),
struct rq_flags rf)
-DECLARE_LOCK_GUARD_1_ATTRS(rq_lock_irq, __acquires(__rq_lockp(_T)), __releases(__rq_lockp(*(struct rq **)_T)));
+DECLARE_LOCK_GUARD_1_ATTRS(rq_lock_irq, __acquires(__rq_lockp(_T)),
+ __releases(__rq_lockp(*(struct rq **)_T)));
#define class_rq_lock_irq_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(rq_lock_irq, _T)
DEFINE_LOCK_GUARD_1(rq_lock_irqsave, struct rq,
@@ -2034,9 +2060,20 @@ DEFINE_LOCK_GUARD_1(rq_lock_irqsave, struct rq,
rq_unlock_irqrestore(_T->lock, &_T->rf),
struct rq_flags rf)
-DECLARE_LOCK_GUARD_1_ATTRS(rq_lock_irqsave, __acquires(__rq_lockp(_T)), __releases(__rq_lockp(*(struct rq **)_T)));
+DECLARE_LOCK_GUARD_1_ATTRS(rq_lock_irqsave, __acquires(__rq_lockp(_T)),
+ __releases(__rq_lockp(*(struct rq **)_T)));
#define class_rq_lock_irqsave_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(rq_lock_irqsave, _T)
+DEFINE_LOCK_GUARD_1(raw_spin_rq_lock_irqsave, struct rq,
+ raw_spin_rq_lock_irqsave(_T->lock, _T->flags),
+ raw_spin_rq_unlock_irqrestore(_T->lock, _T->flags),
+ unsigned long flags)
+
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spin_rq_lock_irqsave, __acquires(__rq_lockp(_T)),
+ __releases(__rq_lockp(*(struct rq **)_T)));
+#define class_raw_spin_rq_lock_irqsave_constructor(_T) \
+ WITH_LOCK_GUARD_1_ATTRS(raw_spin_rq_lock_irqsave, _T)
+
#define this_rq_lock_irq(...) __acquire_ret(_this_rq_lock_irq(__VA_ARGS__), __rq_lockp(__ret))
static inline struct rq *_this_rq_lock_irq(struct rq_flags *rf) __acquires_ret
{
@@ -2571,6 +2608,7 @@ 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)
@@ -4183,8 +4221,9 @@ extern void balance_callbacks(struct rq *rq, struct balance_callback *head);
* 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.
+ * properties are serialized under those locks. There are currently two
+ * exceptions to this rule in sched/ext which only hold rq->lock: scx_bypass()
+ * and rq_offline_scx().
*/
/*
diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
index 622e2e01974c..3dab0253976f 100644
--- a/kernel/sched/topology.c
+++ b/kernel/sched/topology.c
@@ -271,7 +271,7 @@ void rebuild_sched_domains_energy(void)
mutex_unlock(&sched_energy_mutex);
}
-#ifdef CONFIG_PROC_SYSCTL
+#ifdef CONFIG_SYSCTL
static int sched_energy_aware_handler(const struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
{
@@ -317,7 +317,7 @@ static int __init sched_energy_aware_sysctl_init(void)
}
late_initcall(sched_energy_aware_sysctl_init);
-#endif /* CONFIG_PROC_SYSCTL */
+#endif /* CONFIG_SYSCTL */
static void free_pd(struct perf_domain *pd)
{
@@ -985,8 +985,8 @@ void sched_cache_active_set(void)
}
/*
- * Update the bottom sched_domain's llc_bytes for @cpu and all its
- * LLC siblings. Called from cacheinfo_cpu_online() or
+ * Update the bottom sched_domain's llc_bytes for @cpus sharing a physical
+ * LLC. Called from cacheinfo_cpu_online() or
* cacheinfo_cpu_pre_down() with cpu hotplug lock held.
*
* Note: get_effective_llc_bytes() returns 0 on PowerPC.
@@ -996,17 +996,13 @@ void sched_cache_active_set(void)
* and does not populates the per-CPU struct cpu_cacheinfo array
* that get_cpu_cacheinfo_llc() reads.
*/
-void sched_update_llc_bytes(unsigned int cpu)
+void sched_update_llc_bytes(const struct cpumask *cpus)
{
struct sched_domain *sd, *sdp;
unsigned int i;
sched_domains_mutex_lock();
- sdp = rcu_dereference_sched_domain(per_cpu(sd_llc, cpu));
- if (!sdp)
- goto unlock;
-
/*
* ci->shared_cpu_map is built incrementally as CPUs come
* online, so the first CPU in an LLC initially sees
@@ -1014,14 +1010,22 @@ void sched_update_llc_bytes(unsigned int cpu)
* get_effective_llc_bytes(). Re-evaluating every LLC
* sibling on each online event corrects this once the full
* shared_cpu_map is known.
+ *
+ * The departing CPU's domains have already been detached when
+ * cacheinfo removes it. Use the surviving cache siblings instead.
+ * They may belong to different cpuset partitions, so use each CPU's
+ * own LLC domain to scale its share of the physical cache.
*/
- for_each_cpu(i, sched_domain_span(sdp)) {
+ for_each_cpu(i, cpus) {
+ sdp = rcu_dereference_sched_domain(per_cpu(sd_llc, i));
+ if (!sdp)
+ continue;
+
sd = rcu_dereference_sched_domain(cpu_rq(i)->sd);
if (sd)
sd->llc_bytes = get_effective_llc_bytes(i, sdp);
}
-unlock:
sched_domains_mutex_unlock();
}
@@ -1995,10 +1999,6 @@ sd_init(struct sched_domain_topology_level *tl,
/*
* Convert topological properties into behaviour.
*/
- /* Don't attempt to spread across CPUs of different capacities. */
- if ((sd->flags & SD_ASYM_CPUCAPACITY) && sd->child)
- sd->child->flags &= ~SD_PREFER_SIBLING;
-
if (sd->flags & SD_SHARE_CPUCAPACITY) {
sd->imbalance_pct = 110;
diff --git a/kernel/sched/wait.c b/kernel/sched/wait.c
index 20f27e2cf7ae..d033f600f48c 100644
--- a/kernel/sched/wait.c
+++ b/kernel/sched/wait.c
@@ -5,6 +5,7 @@
* (C) 2004 Nadia Yvette Chambers, Oracle
*/
#include "sched.h"
+#include <linux/wait_bit.h>
void __init_waitqueue_head(struct wait_queue_head *wq_head, const char *name, struct lock_class_key *key)
{
@@ -463,3 +464,17 @@ int woken_wake_function(struct wait_queue_entry *wq_entry, unsigned mode, int sy
return default_wake_function(wq_entry, mode, sync, key);
}
EXPORT_SYMBOL(woken_wake_function);
+
+int woken_wake_bit_function(struct wait_queue_entry *wq_entry, unsigned mode, int sync, void *arg)
+{
+ struct wait_bit_key *key = __var_wake_key(wq_entry, arg);
+ if (!key)
+ return 0;
+
+ /* Pairs with the smp_store_mb() in wait_woken(). */
+ smp_mb(); /* C */
+ wq_entry->flags |= WQ_FLAG_WOKEN;
+
+ return default_wake_function(wq_entry, mode, sync, key);
+}
+EXPORT_SYMBOL(woken_wake_bit_function);
diff --git a/kernel/sched/wait_bit.c b/kernel/sched/wait_bit.c
index 1088d3b7012c..348f7211b4aa 100644
--- a/kernel/sched/wait_bit.c
+++ b/kernel/sched/wait_bit.c
@@ -167,9 +167,7 @@ wait_queue_head_t *__var_waitqueue(void *p)
}
EXPORT_SYMBOL(__var_waitqueue);
-static int
-var_wake_function(struct wait_queue_entry *wq_entry, unsigned int mode,
- int sync, void *arg)
+struct wait_bit_key *__var_wake_key(struct wait_queue_entry *wq_entry, void *arg)
{
struct wait_bit_key *key = arg;
struct wait_bit_queue_entry *wbq_entry =
@@ -177,6 +175,16 @@ var_wake_function(struct wait_queue_entry *wq_entry, unsigned int mode,
if (wbq_entry->key.flags != key->flags ||
wbq_entry->key.bit_nr != key->bit_nr)
+ return NULL;
+
+ return key;
+}
+
+static int var_wake_function(struct wait_queue_entry *wq_entry, unsigned int mode,
+ int sync, void *arg)
+{
+ struct wait_bit_key *key = __var_wake_key(wq_entry, arg);
+ if (!key)
return 0;
return autoremove_wake_function(wq_entry, mode, sync, key);
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index 066909393c38..86cf4460d69e 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -1100,12 +1100,13 @@ void secure_computing_strict(int this_syscall)
else
BUG();
}
-int __secure_computing(void)
+
+bool __seccomp_permit_syscall(void)
{
int this_syscall = syscall_get_nr(current, current_pt_regs());
secure_computing_strict(this_syscall);
- return 0;
+ return true;
}
#else
@@ -1256,7 +1257,7 @@ out:
return -1;
}
-static int __seccomp_filter(int this_syscall, const bool recheck_after_trace)
+static bool __seccomp_filter(int this_syscall, const bool recheck_after_trace)
{
u32 filter_ret, action;
struct seccomp_data sd;
@@ -1294,7 +1295,7 @@ static int __seccomp_filter(int this_syscall, const bool recheck_after_trace)
case SECCOMP_RET_TRACE:
/* We've been put in this state by the ptracer already. */
if (recheck_after_trace)
- return 0;
+ return true;
/* ENOSYS these calls if there is no tracer attached. */
if (!ptrace_event_enabled(current, PTRACE_EVENT_SECCOMP)) {
@@ -1329,20 +1330,17 @@ static int __seccomp_filter(int this_syscall, const bool recheck_after_trace)
* a reload of all registers. This does not goto skip since
* a skip would have already been reported.
*/
- if (__seccomp_filter(this_syscall, true))
- return -1;
-
- return 0;
+ return __seccomp_filter(this_syscall, true);
case SECCOMP_RET_USER_NOTIF:
if (seccomp_do_user_notification(this_syscall, match, &sd))
goto skip;
- return 0;
+ return true;
case SECCOMP_RET_LOG:
seccomp_log(this_syscall, 0, action, true);
- return 0;
+ return true;
case SECCOMP_RET_ALLOW:
/*
@@ -1350,7 +1348,7 @@ static int __seccomp_filter(int this_syscall, const bool recheck_after_trace)
* this action since SECCOMP_RET_ALLOW is the starting
* state in seccomp_run_filters().
*/
- return 0;
+ return true;
case SECCOMP_RET_KILL_THREAD:
case SECCOMP_RET_KILL_PROCESS:
@@ -1367,46 +1365,46 @@ static int __seccomp_filter(int this_syscall, const bool recheck_after_trace)
} else {
do_exit(SIGSYS);
}
- return -1; /* skip the syscall go directly to signal handling */
+ return false; /* skip the syscall go directly to signal handling */
}
unreachable();
skip:
seccomp_log(this_syscall, 0, action, match ? match->log : false);
- return -1;
+ return false;
}
#else
-static int __seccomp_filter(int this_syscall, const bool recheck_after_trace)
+static bool __seccomp_filter(int this_syscall, const bool recheck_after_trace)
{
BUG();
- return -1;
+ return false;
}
#endif
-int __secure_computing(void)
+bool __seccomp_permit_syscall(void)
{
int mode = current->seccomp.mode;
int this_syscall;
if (IS_ENABLED(CONFIG_CHECKPOINT_RESTORE) &&
unlikely(current->ptrace & PT_SUSPEND_SECCOMP))
- return 0;
+ return true;
this_syscall = syscall_get_nr(current, current_pt_regs());
switch (mode) {
case SECCOMP_MODE_STRICT:
__secure_computing_strict(this_syscall); /* may call do_exit */
- return 0;
+ return true;
case SECCOMP_MODE_FILTER:
return __seccomp_filter(this_syscall, false);
/* Surviving SECCOMP_RET_KILL_* must be proactively impossible. */
case SECCOMP_MODE_DEAD:
WARN_ON_ONCE(1);
do_exit(SIGKILL);
- return -1;
+ return false;
default:
BUG();
}
diff --git a/kernel/signal.c b/kernel/signal.c
index 9c2b32c4d755..d31ebcb6ed4d 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -130,28 +130,10 @@ static bool sig_ignored(struct task_struct *t, int sig, bool force)
*/
static inline bool has_pending_signals(sigset_t *signal, sigset_t *blocked)
{
- unsigned long ready;
- long i;
-
- switch (_NSIG_WORDS) {
- default:
- for (i = _NSIG_WORDS, ready = 0; --i >= 0 ;)
- ready |= signal->sig[i] &~ blocked->sig[i];
- break;
-
- case 4: ready = signal->sig[3] &~ blocked->sig[3];
- ready |= signal->sig[2] &~ blocked->sig[2];
- ready |= signal->sig[1] &~ blocked->sig[1];
- ready |= signal->sig[0] &~ blocked->sig[0];
- break;
-
- case 2: ready = signal->sig[1] &~ blocked->sig[1];
- ready |= signal->sig[0] &~ blocked->sig[0];
- break;
-
- case 1: ready = signal->sig[0] &~ blocked->sig[0];
- }
- return ready != 0;
+ unsigned long ready = 0;
+ for (long i = 0; i < _NSIG_WORDS; i++)
+ ready |= signal->sig[i] & ~blocked->sig[i];
+ return ready != 0;
}
#define PENDING(p,b) has_pending_signals(&(p)->signal, (b))
@@ -475,18 +457,42 @@ static void __sigqueue_free(struct sigqueue *q)
kmem_cache_free(sigqueue_cachep, q);
}
-void flush_sigqueue(struct sigpending *queue)
+/*
+ * flush_sigqueue_list() can only be invoked without holding sighand::siglock in
+ * the following cases:
+ *
+ * 1) When flushing task::pending _after_ setting task::flags PF_EXITING
+ *
+ * All functions which try to send a signal to @task will observe PF_EXITING
+ * and drop the signal.
+ *
+ * 2) When flushing task::signal::shared_pending _after_ the last task in a
+ * thread group was unhashed and task::sighand is NULL.
+ *
+ * Nothing can queue a signal anymore because sighand is NULL.
+ */
+static void flush_sigqueue_list(struct list_head *head)
{
- struct sigqueue *q;
+ struct sigqueue *q, *tmp;
- sigemptyset(&queue->signal);
- while (!list_empty(&queue->list)) {
- q = list_entry(queue->list.next, struct sigqueue , list);
+ list_for_each_entry_safe(q, tmp, head, list) {
list_del_init(&q->list);
__sigqueue_free(q);
}
}
+void flush_sigqueue(struct sigpending *queue)
+{
+ sigemptyset(&queue->signal);
+ flush_sigqueue_list(&queue->list);
+}
+
+static void sigqueue_dequeue_pending(struct sigpending *queue, struct list_head *head)
+{
+ sigemptyset(&queue->signal);
+ list_splice_init(&queue->list, head);
+}
+
/*
* Flush all pending signals for this kthread.
*/
@@ -1037,6 +1043,21 @@ static inline bool legacy_queue(struct sigpending *signals, int sig)
return (sig < SIGRTMIN) && sigismember(&signals->signal, sig);
}
+/*
+ * When PF_EXITING is set the task is on the way out and has t::pending
+ * flushed already. Prevent queueing of PIDTYPE_PID signals as they would
+ * be leaked.
+ */
+static inline bool task_can_queue_signal(struct task_struct *t, enum pid_type type)
+{
+ lockdep_assert_held(&t->sighand->siglock);
+
+ if (!(t->flags & PF_EXITING))
+ return true;
+
+ return type != PIDTYPE_PID;
+}
+
static int __send_signal_locked(int sig, struct kernel_siginfo *info,
struct task_struct *t, enum pid_type type, bool force)
{
@@ -1048,6 +1069,10 @@ static int __send_signal_locked(int sig, struct kernel_siginfo *info,
lockdep_assert_held(&t->sighand->siglock);
result = TRACE_SIGNAL_IGNORED;
+
+ if (!task_can_queue_signal(t, type))
+ goto ret;
+
if (!prepare_signal(sig, t, force))
goto ret;
@@ -1181,6 +1206,7 @@ static inline bool has_si_pid_and_uid(struct kernel_siginfo *info)
int send_signal_locked(int sig, struct kernel_siginfo *info,
struct task_struct *t, enum pid_type type)
{
+ struct kernel_siginfo __maybe_unused rewritten;
/* Should SIGKILL or SIGSTOP be received by a pid namespace init? */
bool force = false;
@@ -1192,24 +1218,34 @@ int send_signal_locked(int sig, struct kernel_siginfo *info,
force = true;
} else if (has_si_pid_and_uid(info)) {
/* SIGKILL and SIGSTOP is special or has ids */
+#ifdef CONFIG_USER_NS
struct user_namespace *t_user_ns;
+ kuid_t uid;
rcu_read_lock();
t_user_ns = task_cred_xxx(t, user_ns);
if (current_user_ns() != t_user_ns) {
- kuid_t uid = make_kuid(current_user_ns(), info->si_uid);
- info->si_uid = from_kuid_munged(t_user_ns, uid);
+ rewritten = *info;
+ info = &rewritten;
+ uid = make_kuid(current_user_ns(), info->si_uid);
+ rewritten.si_uid = from_kuid_munged(t_user_ns, uid);
}
rcu_read_unlock();
-
+#endif
/* A kernel generated signal? */
force = (info->si_code == SI_KERNEL);
+#ifdef CONFIG_PID_NS
/* From an ancestor pid namespace? */
if (!task_pid_nr_ns(current, task_active_pid_ns(t))) {
- info->si_pid = 0;
+ if (info != &rewritten) {
+ rewritten = *info;
+ info = &rewritten;
+ }
+ rewritten.si_pid = 0;
force = true;
}
+#endif
}
return __send_signal_locked(sig, info, t, type, force);
}
@@ -1362,8 +1398,16 @@ struct sighand_struct *lock_task_sighand(struct task_struct *tsk,
rcu_read_lock();
for (;;) {
sighand = rcu_dereference(tsk->sighand);
- if (unlikely(sighand == NULL))
+ if (unlikely(sighand == NULL)) {
+ /*
+ * Pairs with the smp_store_release() in
+ * __exit_signal(). It ensures that all state
+ * modifications to the task preceeding the store are
+ * visible to the callers of lock_task_sighand().
+ */
+ smp_acquire__after_ctrl_dep();
break;
+ }
/*
* This sighand can be already freed and even reused, but
@@ -1891,6 +1935,18 @@ int kill_pid(struct pid *pid, int sig, int priv)
}
EXPORT_SYMBOL(kill_pid);
+int kill_cad_pid(int sig, int priv)
+{
+ int ret;
+
+ rcu_read_lock();
+ ret = kill_pid(rcu_dereference(cad_pid), sig, priv);
+ rcu_read_unlock();
+
+ return ret;
+}
+EXPORT_SYMBOL(kill_cad_pid);
+
#ifdef CONFIG_POSIX_TIMERS
/*
* These functions handle POSIX timer signals. POSIX timers use
@@ -1967,11 +2023,25 @@ static inline struct task_struct *posixtimer_get_target(struct k_itimer *tmr)
struct task_struct *t = pid_task(tmr->it_pid, tmr->it_pid_type);
if (t && tmr->it_pid_type != PIDTYPE_PID &&
- same_thread_group(t, current) && !current->exit_state)
+ same_thread_group(t, current) && !(current->flags & PF_EXITING))
t = current;
return t;
}
+/*
+ * Find the target task for the POSIX timer signal and prevent that a
+ * PIDTYPE_PID signal is queued on a task which has PF_EXITING set.
+ */
+static inline struct task_struct *posixtimer_get_unignore_target(struct k_itimer *tmr)
+{
+ struct task_struct *t = posixtimer_get_target(tmr);
+
+ if (t && task_can_queue_signal(t, tmr->it_pid_type))
+ return t;
+
+ return NULL;
+}
+
void posixtimer_send_sigqueue(struct k_itimer *tmr)
{
struct sigqueue *q = &tmr->sigq;
@@ -1989,6 +2059,9 @@ void posixtimer_send_sigqueue(struct k_itimer *tmr)
if (!likely(lock_task_sighand(t, &flags)))
return;
+ if (!task_can_queue_signal(t, tmr->it_pid_type))
+ goto unlock;
+
/*
* Update @tmr::sigqueue_seq for posix timer signals with sighand
* locked to prevent a race against dequeue_signal().
@@ -2080,6 +2153,7 @@ void posixtimer_send_sigqueue(struct k_itimer *tmr)
result = TRACE_SIGNAL_DELIVERED;
out:
trace_signal_generate(sig, &q->info, t, tmr->it_pid_type != PIDTYPE_PID, result);
+unlock:
unlock_task_sighand(t, &flags);
}
@@ -2135,7 +2209,7 @@ static void posixtimer_sig_unignore(struct task_struct *tsk, int sig)
* has exited by now, drop the reference count.
*/
guard(rcu)();
- target = posixtimer_get_target(tmr);
+ target = posixtimer_get_unignore_target(tmr);
if (target)
posixtimer_queue_sigqueue(&tmr->sigq, target, tmr->it_pid_type);
else
@@ -3119,42 +3193,36 @@ static void retarget_shared_pending(struct task_struct *tsk, sigset_t *which)
void exit_signals(struct task_struct *tsk)
{
+ LIST_HEAD(sigq_list);
int group_stop = 0;
- sigset_t unblocked;
/*
* @tsk is about to have PF_EXITING set - lock out users which
- * expect stable threadgroup.
+ * expect a stable threadgroup.
*/
cgroup_threadgroup_change_begin(tsk);
- if (thread_group_empty(tsk) || (tsk->signal->flags & SIGNAL_GROUP_EXIT)) {
+ scoped_guard(spinlock_irq, &tsk->sighand->siglock) {
tsk->flags |= PF_EXITING;
- cgroup_threadgroup_change_end(tsk);
- return;
- }
- spin_lock_irq(&tsk->sighand->siglock);
- /*
- * From now this task is not visible for group-wide signals,
- * see wants_signal(), do_signal_stop().
- */
- tsk->flags |= PF_EXITING;
+ sigqueue_dequeue_pending(&tsk->pending, &sigq_list);
- cgroup_threadgroup_change_end(tsk);
+ if (task_sigpending(tsk) && !thread_group_empty(tsk) &&
+ !(tsk->signal->flags & SIGNAL_GROUP_EXIT)) {
+ sigset_t unblocked = tsk->blocked;
- if (!task_sigpending(tsk))
- goto out;
+ signotset(&unblocked);
+ retarget_shared_pending(tsk, &unblocked);
- unblocked = tsk->blocked;
- signotset(&unblocked);
- retarget_shared_pending(tsk, &unblocked);
+ if (unlikely(tsk->jobctl & JOBCTL_STOP_PENDING) &&
+ task_participate_group_stop(tsk))
+ group_stop = CLD_STOPPED;
+ }
+ }
- if (unlikely(tsk->jobctl & JOBCTL_STOP_PENDING) &&
- task_participate_group_stop(tsk))
- group_stop = CLD_STOPPED;
-out:
- spin_unlock_irq(&tsk->sighand->siglock);
+ cgroup_threadgroup_change_end(tsk);
+
+ flush_sigqueue_list(&sigq_list);
/*
* If group stop has completed, deliver the notification. This
@@ -3943,6 +4011,15 @@ static void prepare_kill_siginfo(int sig, struct kernel_siginfo *info,
info->si_uid = from_kuid_munged(current_user_ns(), current_uid());
}
+/*
+ * Not even root can pretend to send SI_FROMKERNEL() signals.
+ * Nor can they impersonate kill()/tgkill(), which have si_pid/uid
+ */
+static bool si_code_reserved_to_kernel(int si_code)
+{
+ return si_code >= 0 || si_code == SI_TKILL;
+}
+
/**
* sys_kill - send a signal to a process
* @pid: the PID of the process
@@ -3950,11 +4027,7 @@ static void prepare_kill_siginfo(int sig, struct kernel_siginfo *info,
*/
SYSCALL_DEFINE2(kill, pid_t, pid, int, sig)
{
- struct kernel_siginfo info;
-
- prepare_kill_siginfo(sig, &info, PIDTYPE_TGID);
-
- return kill_something_info(sig, &info, pid);
+ return kill_something_info(sig, SEND_SIG_NOINFO, pid);
}
/*
@@ -4038,7 +4111,7 @@ static int do_pidfd_send_signal(struct pid *pid, int sig, enum pid_type type,
/* Only allow sending arbitrary signals to yourself. */
if ((task_pid(current) != pid || type > PIDTYPE_TGID) &&
- (kinfo.si_code >= 0 || kinfo.si_code == SI_TKILL))
+ si_code_reserved_to_kernel(kinfo.si_code))
return -EPERM;
} else {
prepare_kill_siginfo(sig, &kinfo, type);
@@ -4193,11 +4266,8 @@ SYSCALL_DEFINE2(tkill, pid_t, pid, int, sig)
static int do_rt_sigqueueinfo(pid_t pid, int sig, kernel_siginfo_t *info)
{
- /* Not even root can pretend to send signals from the kernel.
- * Nor can they impersonate a kill()/tgkill(), which adds source info.
- */
- if ((info->si_code >= 0 || info->si_code == SI_TKILL) &&
- (task_pid_vnr(current) != pid))
+ if (si_code_reserved_to_kernel(info->si_code) &&
+ task_pid_vnr(current) != pid)
return -EPERM;
/* POSIX.1b doesn't mention process groups. */
@@ -4240,11 +4310,8 @@ static int do_rt_tgsigqueueinfo(pid_t tgid, pid_t pid, int sig, kernel_siginfo_t
if (pid <= 0 || tgid <= 0)
return -EINVAL;
- /* Not even root can pretend to send signals from the kernel.
- * Nor can they impersonate a kill()/tgkill(), which adds source info.
- */
- if ((info->si_code >= 0 || info->si_code == SI_TKILL) &&
- (task_pid_vnr(current) != pid))
+ if (si_code_reserved_to_kernel(info->si_code) &&
+ task_pid_vnr(current) != pid)
return -EPERM;
return do_send_specific(tgid, pid, sig, info);
diff --git a/kernel/smp.c b/kernel/smp.c
index a0bb56bd8dda..b696bcc60c08 100644
--- a/kernel/smp.c
+++ b/kernel/smp.c
@@ -16,6 +16,7 @@
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gfp.h>
+#include <linux/slab.h>
#include <linux/smp.h>
#include <linux/cpu.h>
#include <linux/sched.h>
@@ -63,7 +64,14 @@ int smpcfd_prepare_cpu(unsigned int cpu)
free_cpumask_var(cfd->cpumask);
return -ENOMEM;
}
- cfd->csd = alloc_percpu(call_single_data_t);
+
+ /*
+ * Allocate the per-CPU CSD the first time a CPU comes up. It is
+ * not freed when the CPU is offlined, so csd_lock_wait() can access
+ * it even when the CPU was offlined after preemption was re-enabled.
+ */
+ if (!cfd->csd)
+ cfd->csd = alloc_percpu(call_single_data_t);
if (!cfd->csd) {
free_cpumask_var(cfd->cpumask);
free_cpumask_var(cfd->cpumask_ipi);
@@ -79,7 +87,6 @@ int smpcfd_dead_cpu(unsigned int cpu)
free_cpumask_var(cfd->cpumask);
free_cpumask_var(cfd->cpumask_ipi);
- free_percpu(cfd->csd);
return 0;
}
@@ -137,10 +144,10 @@ csd_do_func(smp_call_func_t func, void *info, call_single_data_t *csd)
trace_csd_function_exit(func, csd);
}
-#ifdef CONFIG_CSD_LOCK_WAIT_DEBUG
-
static DEFINE_STATIC_KEY_MAYBE(CONFIG_CSD_LOCK_WAIT_DEBUG_DEFAULT, csdlock_debug_enabled);
+#ifdef CONFIG_CSD_LOCK_WAIT_DEBUG
+
/*
* Parse the csdlock_debug= kernel boot parameter.
*
@@ -182,16 +189,22 @@ static atomic_t csd_bug_count = ATOMIC_INIT(0);
static void __csd_lock_record(call_single_data_t *csd)
{
if (!csd) {
- smp_mb(); /* NULL cur_csd after unlock. */
- __this_cpu_write(cur_csd, NULL);
+ /*
+ * Pairs with smp_load_acquire() of cur_csd in
+ * csd_lock_wait_toolong(): orders any preceding CSD
+ * callback/unlock before a remote reader observes NULL.
+ */
+ smp_store_release(this_cpu_ptr(&cur_csd), NULL);
return;
}
__this_cpu_write(cur_csd_func, csd->func);
__this_cpu_write(cur_csd_info, csd->info);
- smp_wmb(); /* func and info before csd. */
- __this_cpu_write(cur_csd, csd);
- smp_mb(); /* Update cur_csd before function call. */
- /* Or before unlock, as the case may be. */
+ /*
+ * Pairs with smp_load_acquire() of cur_csd in
+ * csd_lock_wait_toolong(): publishes cur_csd_func and
+ * cur_csd_info before the non-NULL pointer becomes visible.
+ */
+ smp_store_release(this_cpu_ptr(&cur_csd), csd);
}
static __always_inline void csd_lock_record(call_single_data_t *csd)
@@ -272,7 +285,13 @@ static bool csd_lock_wait_toolong(call_single_data_t *csd, u64 ts0, u64 *ts1, in
cpux = 0;
else
cpux = cpu;
- cpu_cur_csd = smp_load_acquire(&per_cpu(cur_csd, cpux)); /* Before func and info. */
+ /*
+ * Pairs with smp_store_release() of cur_csd in __csd_lock_record():
+ * a non-NULL cur_csd here implies cur_csd_func and cur_csd_info
+ * are the matching publication; a NULL value is ordered after any
+ * preceding CSD callback/unlock on the remote CPU.
+ */
+ cpu_cur_csd = smp_load_acquire(&per_cpu(cur_csd, cpux));
/* How long since this CSD lock was stuck. */
ts_delta = ts2 - ts0;
pr_alert("csd: %s non-responsive CSD lock (#%d) on CPU#%d, waiting %lld ns for CPU#%02d %pS(%ps).\n",
@@ -323,6 +342,8 @@ static void __csd_lock_wait(call_single_data_t *csd)
int bug_id = 0;
u64 ts0, ts1;
+ guard(preempt)();
+
ts1 = ts0 = ktime_get_mono_fast_ns();
for (;;) {
if (csd_lock_wait_toolong(csd, ts0, &ts1, &bug_id, &nmessages))
@@ -342,6 +363,10 @@ static __always_inline void csd_lock_wait(call_single_data_t *csd)
smp_cond_load_acquire(&csd->node.u_flags, !(VAL & CSD_FLAG_LOCK));
}
#else
+static __always_inline void __csd_lock_wait(call_single_data_t *csd)
+{
+}
+
static void csd_lock_record(call_single_data_t *csd)
{
}
@@ -354,8 +379,23 @@ static __always_inline void csd_lock_wait(call_single_data_t *csd)
static __always_inline void csd_lock(call_single_data_t *csd)
{
- csd_lock_wait(csd);
- csd->node.u_flags |= CSD_FLAG_LOCK;
+ if (IS_ENABLED(CONFIG_CSD_LOCK_WAIT_DEBUG) &&
+ static_branch_unlikely(&csdlock_debug_enabled)) {
+
+ for (;;) {
+ unsigned int flags;
+
+ __csd_lock_wait(csd);
+ flags = READ_ONCE(csd->node.u_flags);
+
+ if (!(flags & CSD_FLAG_LOCK) &&
+ try_cmpxchg_acquire(&csd->node.u_flags, &flags, flags | CSD_FLAG_LOCK))
+ break;
+ }
+ } else {
+ csd_lock_wait(csd);
+ csd->node.u_flags |= CSD_FLAG_LOCK;
+ }
/*
* prevent CPU from reordering the above assignment
@@ -380,7 +420,8 @@ static DEFINE_PER_CPU_SHARED_ALIGNED(call_single_data_t, csd_data);
#ifdef CONFIG_CSD_LOCK_WAIT_DEBUG
static call_single_data_t *get_single_csd_data(int cpu)
{
- if (static_branch_unlikely(&csdlock_debug_enabled))
+ if (static_branch_unlikely(&csdlock_debug_enabled) &&
+ (unsigned int)cpu < nr_cpu_ids)
return per_cpu_ptr(&csd_data, cpu);
return this_cpu_ptr(&csd_data);
}
@@ -639,17 +680,9 @@ void flush_smp_call_function_queue(void)
local_irq_restore(flags);
}
-/**
- * smp_call_function_single - Run a function on a specific CPU
- * @cpu: Specific target CPU for this function.
- * @func: The function to run. This must be fast and non-blocking.
- * @info: An arbitrary pointer to pass to the function.
- * @wait: If true, wait until function has completed on other CPUs.
- *
- * Returns: %0 on success, else a negative status code.
- */
-int smp_call_function_single(int cpu, smp_call_func_t func, void *info,
- int wait)
+static int __smp_call_function_single(int cpu, smp_call_func_t func,
+ void *info, const struct cpumask *mask,
+ bool wait)
{
call_single_data_t *csd;
call_single_data_t csd_stack = {
@@ -666,6 +699,14 @@ int smp_call_function_single(int cpu, smp_call_func_t func, void *info,
*/
this_cpu = get_cpu();
+ if (mask) {
+ /* Try for same CPU (cheapest) */
+ if (!cpumask_test_cpu(this_cpu, mask))
+ cpu = sched_numa_find_nth_cpu(mask, 0, cpu_to_node(this_cpu));
+ else
+ cpu = this_cpu;
+ }
+
/*
* Can deadlock when called with interrupts disabled.
* We allow cpu's that are not yet online though, as no one else can
@@ -698,13 +739,32 @@ int smp_call_function_single(int cpu, smp_call_func_t func, void *info,
err = generic_exec_single(cpu, csd);
+ /*
+ * @csd is stack-allocated when @wait is true. No concurrent access
+ * except from the IPI completion path, so we can re-enable preemption
+ * early to reduce latency.
+ */
+ put_cpu();
+
if (wait)
csd_lock_wait(csd);
- put_cpu();
-
return err;
}
+
+/**
+ * smp_call_function_single - Run a function on a specific CPU
+ * @cpu: Specific target CPU for this function.
+ * @func: The function to run. This must be fast and non-blocking.
+ * @info: An arbitrary pointer to pass to the function.
+ * @wait: If true, wait until function has completed on other CPUs.
+ *
+ * Returns: %0 on success, else a negative status code.
+ */
+int smp_call_function_single(int cpu, smp_call_func_t func, void *info, bool wait)
+{
+ return __smp_call_function_single(cpu, func, info, NULL, wait);
+}
EXPORT_SYMBOL(smp_call_function_single);
/**
@@ -755,10 +815,10 @@ EXPORT_SYMBOL_GPL(smp_call_function_single_async);
/**
* smp_call_function_any - Run a function on any of the given cpus
- * @mask: The mask of cpus it can run on.
- * @func: The function to run. This must be fast and non-blocking.
- * @info: An arbitrary pointer to pass to the function.
- * @wait: If true, wait until function has completed.
+ * @mask: The mask of cpus it can run on.
+ * @func: The function to run. This must be fast and non-blocking.
+ * @info: An arbitrary pointer to pass to the function.
+ * @wait: If true, wait until function has completed.
*
* Selection preference:
* 1) current cpu if in @mask
@@ -769,19 +829,53 @@ EXPORT_SYMBOL_GPL(smp_call_function_single_async);
int smp_call_function_any(const struct cpumask *mask,
smp_call_func_t func, void *info, int wait)
{
- unsigned int cpu;
- int ret;
+ return __smp_call_function_single(-1, func, info, mask, wait);
+}
+EXPORT_SYMBOL_GPL(smp_call_function_any);
- /* Try for same CPU (cheapest) */
- cpu = get_cpu();
- if (!cpumask_test_cpu(cpu, mask))
- cpu = sched_numa_find_nth_cpu(mask, 0, cpu_to_node(cpu));
+static DEFINE_STATIC_KEY_FALSE(ipi_mask_inlined);
- ret = smp_call_function_single(cpu, func, info, wait);
- put_cpu();
- return ret;
+#ifdef CONFIG_PREEMPTION
+
+int smp_task_ipi_mask_alloc(struct task_struct *task)
+{
+ if (static_branch_unlikely(&ipi_mask_inlined))
+ return 0;
+
+ ACCESS_PRIVATE(task, ipi_mask).ipi_mask_ptr =
+ kmalloc(cpumask_size(), GFP_KERNEL);
+ if (!ACCESS_PRIVATE(task, ipi_mask).ipi_mask_ptr)
+ return -ENOMEM;
+
+ return 0;
}
-EXPORT_SYMBOL_GPL(smp_call_function_any);
+
+void smp_task_ipi_mask_free(struct task_struct *task)
+{
+ if (static_branch_unlikely(&ipi_mask_inlined))
+ return;
+
+ kfree(ACCESS_PRIVATE(task, ipi_mask).ipi_mask_ptr);
+}
+
+static cpumask_t *smp_task_ipi_mask(struct task_struct *cur)
+{
+ /*
+ * If cpumask_size() is smaller than or equal to the pointer
+ * size, it stashes the cpumask in the pointer itself to
+ * avoid extra memory allocations.
+ */
+ if (static_branch_unlikely(&ipi_mask_inlined))
+ return (cpumask_t *)&ACCESS_PRIVATE(cur, ipi_mask).ipi_mask_val;
+
+ return ACCESS_PRIVATE(cur, ipi_mask).ipi_mask_ptr;
+}
+#else
+static cpumask_t *smp_task_ipi_mask(struct task_struct *cur)
+{
+ return NULL;
+}
+#endif
/*
* Flags to be used as scf_flags argument of smp_call_function_many_cond().
@@ -797,13 +891,20 @@ static void smp_call_function_many_cond(const struct cpumask *mask,
unsigned int scf_flags,
smp_cond_func_t cond_func)
{
- int cpu, last_cpu, this_cpu = smp_processor_id();
- struct call_function_data *cfd;
+ struct cpumask *cpumask, *task_mask;
bool wait = scf_flags & SCF_WAIT;
- int nr_cpus = 0;
+ struct call_function_data *cfd;
+ int cpu, last_cpu, this_cpu;
bool run_remote = false;
+ int nr_cpus = 0;
- lockdep_assert_preemption_disabled();
+ this_cpu = get_cpu();
+ cfd = this_cpu_ptr(&cfd_data);
+ task_mask = smp_task_ipi_mask(current);
+ if (task_mask)
+ cpumask = task_mask;
+ else
+ cpumask = cfd->cpumask;
/*
* Can deadlock when called with interrupts disabled.
@@ -825,16 +926,15 @@ static void smp_call_function_many_cond(const struct cpumask *mask,
/* Check if we need remote execution, i.e., any CPU excluding this one. */
if (cpumask_any_and_but(mask, cpu_online_mask, this_cpu) < nr_cpu_ids) {
- cfd = this_cpu_ptr(&cfd_data);
- cpumask_and(cfd->cpumask, mask, cpu_online_mask);
- __cpumask_clear_cpu(this_cpu, cfd->cpumask);
+ cpumask_and(cpumask, mask, cpu_online_mask);
+ __cpumask_clear_cpu(this_cpu, cpumask);
cpumask_clear(cfd->cpumask_ipi);
- for_each_cpu(cpu, cfd->cpumask) {
+ for_each_cpu(cpu, cpumask) {
call_single_data_t *csd = per_cpu_ptr(cfd->csd, cpu);
if (cond_func && !cond_func(cpu, info)) {
- __cpumask_clear_cpu(cpu, cfd->cpumask);
+ __cpumask_clear_cpu(cpu, cpumask);
continue;
}
@@ -884,8 +984,18 @@ static void smp_call_function_many_cond(const struct cpumask *mask,
local_irq_restore(flags);
}
+ /*
+ * The IPI work has been queued and dispatched. On PREEMPT kernels,
+ * tasks created through dup_task_struct() have task-local wait masks.
+ * The boot init_task can fall back to cfd->cpumask when the mask is
+ * not inlined, but other tasks still use task-local masks and cannot
+ * overwrite it. On !PREEMPT kernels, preempt_enable() cannot schedule
+ * another task, so the per-CPU mask remains protected.
+ */
+ put_cpu();
+
if (run_remote && wait) {
- for_each_cpu(cpu, cfd->cpumask) {
+ for_each_cpu(cpu, cpumask) {
call_single_data_t *csd;
csd = per_cpu_ptr(cfd->csd, cpu);
@@ -896,15 +1006,14 @@ static void smp_call_function_many_cond(const struct cpumask *mask,
/**
* smp_call_function_many() - Run a function on a set of CPUs.
- * @mask: The set of cpus to run on (only runs on online subset).
- * @func: The function to run. This must be fast and non-blocking.
- * @info: An arbitrary pointer to pass to the function.
- * @wait: If true, wait (atomically) until function has completed
- * on other CPUs.
+ * @mask: The set of cpus to run on (only runs on online subset).
+ * @func: The function to run. This must be fast and non-blocking.
+ * @info: An arbitrary pointer to pass to the function.
+ * @wait: If true, wait (atomically) until function has completed
+ * on other CPUs.
*
* You must not call this function with disabled interrupts or from a
- * hardware interrupt handler or from a bottom half handler. Preemption
- * must be disabled when calling this function.
+ * hardware interrupt handler or from a bottom half handler.
*
* @func is not called on the local CPU even if @mask contains it. Consider
* using on_each_cpu_cond_mask() instead if this is not desirable.
@@ -918,10 +1027,10 @@ EXPORT_SYMBOL(smp_call_function_many);
/**
* smp_call_function() - Run a function on all other CPUs.
- * @func: The function to run. This must be fast and non-blocking.
- * @info: An arbitrary pointer to pass to the function.
- * @wait: If true, wait (atomically) until function has completed
- * on other CPUs.
+ * @func: The function to run. This must be fast and non-blocking.
+ * @info: An arbitrary pointer to pass to the function.
+ * @wait: If true, wait (atomically) until function has completed
+ * on other CPUs.
*
* If @wait is true, then returns once @func has returned; otherwise
* it returns just before the target cpu calls @func.
@@ -931,9 +1040,8 @@ EXPORT_SYMBOL(smp_call_function_many);
*/
void smp_call_function(smp_call_func_t func, void *info, int wait)
{
- preempt_disable();
- smp_call_function_many(cpu_online_mask, func, info, wait);
- preempt_enable();
+ smp_call_function_many_cond(cpu_online_mask, func, info,
+ wait ? SCF_WAIT : 0, NULL);
}
EXPORT_SYMBOL(smp_call_function);
@@ -999,6 +1107,9 @@ EXPORT_SYMBOL(nr_cpu_ids);
void __init setup_nr_cpu_ids(void)
{
set_nr_cpu_ids(find_last_bit(cpumask_bits(cpu_possible_mask), NR_CPUS) + 1);
+
+ if (IS_ENABLED(CONFIG_PREEMPTION) && cpumask_size() <= sizeof(unsigned long))
+ static_branch_enable(&ipi_mask_inlined);
}
/* Called by boot processor to activate the rest. */
@@ -1035,12 +1146,14 @@ void __init smp_init(void)
* @func: The function to run on all applicable CPUs.
* This must be fast and non-blocking.
* @info: An arbitrary pointer to pass to both functions.
- * @wait: If true, wait (atomically) until function has
- * completed on other CPUs.
+ * @wait: If true, wait until function has completed on other CPUs.
* @mask: The set of cpus to run on (only runs on online subset).
*
- * Preemption is disabled to protect against CPUs going offline but not online.
- * CPUs going online during the call will not be seen or sent an IPI.
+ * Target CPU selection and work queueing are done with preemption
+ * disabled. This protects against CPUs going offline, but not against
+ * CPUs coming online concurrently; newly online CPUs are not guaranteed
+ * to be seen or sent an IPI. If @wait is true, the final wait for remote
+ * completion happens after that preemption-disabled section.
*
* You must not call this function with disabled interrupts or
* from a hardware interrupt handler or from a bottom half handler.
@@ -1053,9 +1166,7 @@ void on_each_cpu_cond_mask(smp_cond_func_t cond_func, smp_call_func_t func,
if (wait)
scf_flags |= SCF_WAIT;
- preempt_disable();
smp_call_function_many_cond(mask, func, info, scf_flags, cond_func);
- preempt_enable();
}
EXPORT_SYMBOL(on_each_cpu_cond_mask);
diff --git a/kernel/softirq.c b/kernel/softirq.c
index 4425d8dce44b..5d02c36c40e3 100644
--- a/kernel/softirq.c
+++ b/kernel/softirq.c
@@ -9,6 +9,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#define INSTANTIATE_EXPORTED_INTERRUPT_DISABLE
#include <linux/export.h>
#include <linux/kernel_stat.h>
#include <linux/interrupt.h>
@@ -88,6 +89,28 @@ EXPORT_PER_CPU_SYMBOL_GPL(hardirqs_enabled);
EXPORT_PER_CPU_SYMBOL_GPL(hardirq_context);
#endif
+DEFINE_PER_CPU(unsigned long, local_interrupt_disable_state);
+
+void _local_interrupt_save_state(unsigned long flags)
+{
+ __local_interrupt_save_state(flags);
+}
+EXPORT_SYMBOL(_local_interrupt_save_state);
+
+void _local_interrupt_enable(void)
+{
+ __local_interrupt_enable();
+}
+EXPORT_SYMBOL(_local_interrupt_enable);
+
+#ifndef CONFIG_HAS_SEPARATE_PREEMPT_RESCHED_BITS
+/*
+ * Any 32bit architecture that still cares about performance should
+ * probably ensure this is near preempt_count.
+ */
+DEFINE_PER_CPU(unsigned int, nmi_nesting);
+#endif
+
/*
* SOFTIRQ_OFFSET usage:
*
@@ -729,7 +752,7 @@ static inline void __irq_exit_rcu(void)
if (!in_interrupt() && local_softirq_pending()) {
/*
* If we left hrtimers unarmed, make sure to arm them now,
- * before enabling interrupts to run SoftIRQ.
+ * before enabling interrupts to run softirq.
*/
hrtimer_rearm_deferred();
invoke_softirq();
diff --git a/kernel/stop_machine.c b/kernel/stop_machine.c
index 773d8e9ae30c..d085ba1f4b44 100644
--- a/kernel/stop_machine.c
+++ b/kernel/stop_machine.c
@@ -7,6 +7,7 @@
* Copyright (C) 2010 SUSE Linux Products GmbH
* Copyright (C) 2010 Tejun Heo <tj@kernel.org>
*/
+#include <linux/bug.h>
#include <linux/compiler.h>
#include <linux/completion.h>
#include <linux/cpu.h>
@@ -376,17 +377,14 @@ int stop_two_cpus(unsigned int cpu1, unsigned int cpu2, cpu_stop_fn_t fn, void *
* and will remain untouched until stopper starts executing @fn.
*
* CONTEXT:
- * Don't care.
- *
- * RETURNS:
- * true if cpu_stop_work was queued successfully and @fn will be called,
- * false otherwise.
+ * Don't care, but the caller must ensure @cpu's stopper stays enabled
+ * until the work is queued, e.g. by preempt_disable().
*/
-bool stop_one_cpu_nowait(unsigned int cpu, cpu_stop_fn_t fn, void *arg,
- struct cpu_stop_work *work_buf)
+void stop_one_cpu_nowait(unsigned int cpu, cpu_stop_fn_t fn, void *arg,
+ struct cpu_stop_work *work_buf)
{
*work_buf = (struct cpu_stop_work){ .fn = fn, .arg = arg, .caller = _RET_IP_, };
- return cpu_stop_queue_work(cpu, work_buf);
+ WARN_ON_ONCE(!cpu_stop_queue_work(cpu, work_buf));
}
static bool queue_stop_cpus_work(const struct cpumask *cpumask,
diff --git a/kernel/sys.c b/kernel/sys.c
index df69bd71de03..35b538ba843c 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -2189,7 +2189,7 @@ static int prctl_set_auxv(struct mm_struct *mm, unsigned long addr,
BUILD_BUG_ON(sizeof(user_auxv) != sizeof(mm->saved_auxv));
task_lock(current);
- memcpy(mm->saved_auxv, user_auxv, len);
+ memcpy(mm->saved_auxv, user_auxv, sizeof(user_auxv));
task_unlock(current);
return 0;
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index add3032da16f..c8be0abaa407 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -351,6 +351,10 @@ COND_SYSCALL(ppoll_time32);
COND_SYSCALL_COMPAT(ppoll_time32);
COND_SYSCALL(utimensat_time32);
COND_SYSCALL(clock_adjtime32);
+COND_SYSCALL(gettimeofday);
+COND_SYSCALL_COMPAT(gettimeofday);
+COND_SYSCALL(time);
+COND_SYSCALL(stime);
/*
* The syscalls below are not found in include/uapi/asm-generic/unistd.h
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index c9efb17cc255..38597f26b34c 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -35,7 +35,7 @@ EXPORT_SYMBOL_GPL(sysctl_long_vals);
static const int ngroups_max = NGROUPS_MAX;
static const int cap_last_cap = CAP_LAST_CAP;
-#ifdef CONFIG_PROC_SYSCTL
+#ifdef CONFIG_SYSCTL
/**
* enum sysctl_writes_mode - supported sysctl write modes
@@ -64,14 +64,14 @@ enum sysctl_writes_mode {
};
static enum sysctl_writes_mode sysctl_writes_strict = SYSCTL_WRITES_STRICT;
-#endif /* CONFIG_PROC_SYSCTL */
+#endif /* CONFIG_SYSCTL */
#endif /* CONFIG_SYSCTL */
/*
* /proc/sys support
*/
-#ifdef CONFIG_PROC_SYSCTL
+#ifdef CONFIG_SYSCTL
static int _proc_do_string(char *data, int maxlen, int dir,
char *buffer, size_t *lenp, loff_t *ppos)
@@ -148,7 +148,7 @@ static void warn_sysctl_write(const struct ctl_table *table)
* @ppos: file position
* @table: the sysctl table
*
- * Returns true if the first position is non-zero and the sysctl_writes_strict
+ * Returns: true if the first position is non-zero and the sysctl_writes_strict
* mode indicates this is not allowed for numeric input types. String proc
* handlers can ignore the return value.
*/
@@ -184,7 +184,7 @@ static bool proc_first_pos_non_zero_ignore(loff_t *ppos,
* and a newline '\n' is added. It is truncated if the buffer is
* not large enough.
*
- * Returns 0 on success.
+ * Returns: %0 on success.
*/
int proc_dostring(const struct ctl_table *table, int dir,
void *buffer, size_t *lenp, loff_t *ppos)
@@ -225,11 +225,14 @@ static void proc_skip_char(char **buf, size_t *size, const char v)
* @base: the base to use
* @res: where the parsed integer will be stored
*
- * In case of success 0 is returned and @res will contain the parsed integer,
- * @endp will hold any trailing characters.
* This function will fail the parse on overflow. If there wasn't an overflow
* the function will defer the decision what characters count as invalid to the
* caller.
+ *
+ * Returns:
+ * * %0 on success and @res will contain the parsed integer,
+ * @endp will hold any trailing characters.
+ * * %-ERANGE on overflow.
*/
static int strtoul_lenient(const char *cp, char **endp, unsigned int base,
unsigned long *res)
@@ -263,10 +266,12 @@ static int strtoul_lenient(const char *cp, char **endp, unsigned int base,
* @perm_tr_len: size of the perm_tr vector
* @tr: pointer to store the trailer character
*
- * In case of success %0 is returned and @buf and @size are updated with
- * the amount of bytes read. If @tr is non-NULL and a trailing
- * character exists (size is non-zero after returning from this
- * function), @tr is updated with the trailing character.
+ * Returns:
+ * * %0 on success and @buf and @size are updated with
+ * the amount of bytes read. If @tr is non-NULL and a trailing
+ * character exists (size is non-zero after returning from this
+ * function), @tr is updated with the trailing character.
+ * * %-EINVAL on failure.
*/
static int proc_get_long(char **buf, size_t *size,
unsigned long *val, bool *neg,
@@ -365,7 +370,7 @@ static void proc_put_char(void **buf, size_t *size, char c)
* not NULL. Check that the values are less than UINT_MAX to avoid
* having to support wrap around from userspace.
*
- * returns 0 on success.
+ * Returns: %0 on success.
*/
int proc_uint_u2k_conv_uop(const ulong *u_ptr, uint *k_ptr,
ulong (*u_ptr_op)(const ulong))
@@ -386,7 +391,7 @@ int proc_uint_u2k_conv_uop(const ulong *u_ptr, uint *k_ptr,
*
* Uses READ_ONCE to assign value to u_ptr.
*
- * returns 0 on success.
+ * Returns: %0 on success.
*/
int proc_uint_k2u_conv(ulong *u_ptr, const uint *k_ptr)
{
@@ -409,7 +414,7 @@ int proc_uint_k2u_conv(ulong *u_ptr, const uint *k_ptr)
* When direction is kernel to user, then the u_ptr is modified.
* When direction is user to kernel, then the k_ptr is modified.
*
- * Returns 0 on success
+ * Returns: %0 on success
*/
int proc_uint_conv(ulong *u_ptr, uint *k_ptr, int dir,
const struct ctl_table *tbl, bool k_ptr_range_check,
@@ -444,15 +449,15 @@ static int proc_uint_u2k_conv(const ulong *u_ptr, uint *k_ptr)
return proc_uint_u2k_conv_uop(u_ptr, k_ptr, NULL);
}
-static int do_proc_uint_conv(ulong *u_ptr, uint *k_ptr, int dir,
+static int do_proc_uint_conv(bool *negp, ulong *u_ptr, uint *k_ptr, int dir,
const struct ctl_table *tbl)
{
return proc_uint_conv(u_ptr, k_ptr, dir, tbl, false,
proc_uint_u2k_conv, proc_uint_k2u_conv);
}
-static int do_proc_uint_conv_minmax(ulong *u_ptr, uint *k_ptr, int dir,
- const struct ctl_table *tbl)
+static int do_proc_uint_conv_minmax(bool *negp, ulong *u_ptr, uint *k_ptr,
+ int dir, const struct ctl_table *tbl)
{
return proc_uint_conv(u_ptr, k_ptr, dir, tbl, true,
proc_uint_u2k_conv, proc_uint_k2u_conv);
@@ -478,7 +483,7 @@ int proc_int_k2u_conv_kop(ulong *u_ptr, const int *k_ptr, bool *negp,
if (val < 0) {
*negp = true;
- *u_ptr = k_ptr_op ? -k_ptr_op((ulong)val) : -(ulong)val;
+ *u_ptr = k_ptr_op ? k_ptr_op(-(ulong)val) : -(ulong)val;
} else {
*negp = false;
*u_ptr = k_ptr_op ? k_ptr_op((ulong)val) : (ulong) val;
@@ -515,6 +520,23 @@ int proc_int_u2k_conv_uop(const ulong *u_ptr, int *k_ptr, const bool *negp,
return 0;
}
+/**
+ * proc_int_conv - Change user or kernel pointer based on direction
+ *
+ * @negp: will be passed to uni-directional converters
+ * @u_ptr: pointer to user variable
+ * @k_ptr: pointer to kernel variable
+ * @dir: %TRUE if this is a write to the sysctl file
+ * @tbl: the sysctl table
+ * @k_ptr_range_check: Check range for k_ptr when %TRUE
+ * @user_to_kern: Callback used to assign value from user to kernel var
+ * @kern_to_user: Callback used to assign value from kernel to user var
+ *
+ * When direction is kernel to user, then the u_ptr is modified.
+ * When direction is user to kernel, then the k_ptr is modified.
+ *
+ * Returns: 0 on success
+ */
int proc_int_conv(bool *negp, ulong *u_ptr, int *k_ptr, int dir,
const struct ctl_table *tbl, bool k_ptr_range_check,
int (*user_to_kern)(const bool *negp, const ulong *u_ptr, int *k_ptr),
@@ -572,14 +594,80 @@ static int do_proc_int_conv_minmax(bool *negp, unsigned long *u_ptr, int *k_ptr,
static const char proc_wspace_sep[] = { ' ', '\t', '\n' };
-static int do_proc_dointvec(const struct ctl_table *table, int dir,
- void *buffer, size_t *lenp, loff_t *ppos,
- int (*conv)(bool *negp, unsigned long *u_ptr, int *k_ptr,
- int dir, const struct ctl_table *table))
+/*
+ * Element type processed by do_proc_vec(). The tag selects the element size
+ * and signedness, and it selects which member of union proc_vec_conv is live.
+ */
+enum proc_vec_type {
+ PROC_VEC_INT,
+ PROC_VEC_UINT,
+ PROC_VEC_ULONG,
+};
+
+/*
+ * Converter passed to do_proc_vec(). Only the member matching the
+ * enum proc_vec_type tag is ever read, so every dispatch stays fully typed and
+ * no void * converter pointer is needed.
+ */
+union proc_vec_conv {
+ int (*int_conv)(bool *negp, ulong *u_ptr, int *k_ptr,
+ int dir, const struct ctl_table *table);
+ int (*uint_conv)(bool *negp, ulong *u_ptr, uint *k_ptr,
+ int dir, const struct ctl_table *table);
+ int (*ulong_conv)(bool *negp, ulong *u_ptr, ulong *k_ptr,
+ int dir, const struct ctl_table *table);
+};
+
+/*
+ * Dispatch to the converter member selected by @type. @k_ptr walks
+ * table->data as raw bytes and is cast back to the element type here.
+ */
+static int proc_vec_conv(enum proc_vec_type type, union proc_vec_conv conv,
+ bool *negp, ulong *u_ptr, char *k_ptr, int dir,
+ const struct ctl_table *table)
+{
+ switch (type) {
+ case PROC_VEC_INT:
+ return conv.int_conv(negp, u_ptr, (int *)k_ptr, dir, table);
+ case PROC_VEC_UINT:
+ return conv.uint_conv(negp, u_ptr, (uint *)k_ptr, dir, table);
+ case PROC_VEC_ULONG:
+ return conv.ulong_conv(negp, u_ptr, (ulong *)k_ptr, dir, table);
+ }
+ return -EINVAL;
+}
+
+/*
+ * Read/write a vector of @type elements. The element size and signedness are
+ * derived from @type, so a single runtime function replaces the per-type
+ * variants. table->data is walked as raw bytes (@i) advanced by @size; the
+ * converter performs the actual typed load/store.
+ */
+static int do_proc_vec(const struct ctl_table *table, int dir,
+ void *buffer, size_t *lenp, loff_t *ppos,
+ enum proc_vec_type type, union proc_vec_conv conv)
{
- int *i, vleft, first = 1, err = 0;
- size_t left;
- char *p;
+ int vleft, first = 1, err = 0;
+ size_t left, size;
+ bool is_unsigned;
+ char *i, *p;
+
+ switch (type) {
+ case PROC_VEC_INT:
+ size = sizeof(int);
+ is_unsigned = false;
+ break;
+ case PROC_VEC_UINT:
+ size = sizeof(uint);
+ is_unsigned = true;
+ break;
+ case PROC_VEC_ULONG:
+ size = sizeof(ulong);
+ is_unsigned = true;
+ break;
+ default:
+ return -EINVAL;
+ }
if (!table->data || !table->maxlen || !*lenp ||
(*ppos && SYSCTL_KERN_TO_USER(dir))) {
@@ -587,12 +675,13 @@ static int do_proc_dointvec(const struct ctl_table *table, int dir,
return 0;
}
- i = (int *) table->data;
- vleft = table->maxlen / sizeof(*i);
+ i = table->data;
+ vleft = table->maxlen / size;
left = *lenp;
- if (!conv)
- conv = do_proc_int_conv;
+ /* uint arrays are not supported, *Do not* add support for them. */
+ if (type == PROC_VEC_UINT && vleft != 1)
+ return -EINVAL;
if (SYSCTL_USER_TO_KERN(dir)) {
if (proc_first_pos_non_zero_ignore(ppos, table))
@@ -603,9 +692,9 @@ static int do_proc_dointvec(const struct ctl_table *table, int dir,
p = buffer;
}
- for (; left && vleft--; i++, first=0) {
+ for (; left && vleft--; i += size, first = 0) {
unsigned long lval;
- bool neg;
+ bool neg = false;
if (SYSCTL_USER_TO_KERN(dir)) {
proc_skip_spaces(&p, &left);
@@ -613,16 +702,18 @@ static int do_proc_dointvec(const struct ctl_table *table, int dir,
if (!left)
break;
err = proc_get_long(&p, &left, &lval, &neg,
- proc_wspace_sep,
- sizeof(proc_wspace_sep), NULL);
+ proc_wspace_sep,
+ sizeof(proc_wspace_sep), NULL);
+ if (!err && neg && is_unsigned)
+ err = -EINVAL;
if (err)
break;
- if (conv(&neg, &lval, i, 1, table)) {
+ if (proc_vec_conv(type, conv, &neg, &lval, i, dir, table)) {
err = -EINVAL;
break;
}
} else {
- if (conv(&neg, &lval, i, 0, table)) {
+ if (proc_vec_conv(type, conv, &neg, &lval, i, dir, table)) {
err = -EINVAL;
break;
}
@@ -644,122 +735,6 @@ out:
return err;
}
-static int do_proc_douintvec_w(const struct ctl_table *table, void *buffer,
- size_t *lenp, loff_t *ppos,
- int (*conv)(unsigned long *u_ptr,
- unsigned int *k_ptr, int dir,
- const struct ctl_table *table))
-{
- unsigned long lval;
- int err = 0;
- size_t left;
- bool neg;
- char *p = buffer;
-
- left = *lenp;
-
- if (proc_first_pos_non_zero_ignore(ppos, table))
- goto bail_early;
-
- if (left > PAGE_SIZE - 1)
- left = PAGE_SIZE - 1;
-
- proc_skip_spaces(&p, &left);
- if (!left) {
- err = -EINVAL;
- goto out_free;
- }
-
- err = proc_get_long(&p, &left, &lval, &neg,
- proc_wspace_sep,
- sizeof(proc_wspace_sep), NULL);
- if (err || neg) {
- err = -EINVAL;
- goto out_free;
- }
-
- if (conv(&lval, (unsigned int *) table->data, 1, table)) {
- err = -EINVAL;
- goto out_free;
- }
-
- if (!err && left)
- proc_skip_spaces(&p, &left);
-
-out_free:
- if (err)
- return -EINVAL;
-
- return 0;
-
-bail_early:
- *ppos += *lenp;
- return err;
-}
-
-static int do_proc_douintvec_r(const struct ctl_table *table, void *buffer,
- size_t *lenp, loff_t *ppos,
- int (*conv)(unsigned long *u_ptr,
- unsigned int *k_ptr, int dir,
- const struct ctl_table *table))
-{
- unsigned long lval;
- int err = 0;
- size_t left;
-
- left = *lenp;
-
- if (conv(&lval, (unsigned int *) table->data, 0, table)) {
- err = -EINVAL;
- goto out;
- }
-
- proc_put_long(&buffer, &left, lval, false);
- if (!left)
- goto out;
-
- proc_put_char(&buffer, &left, '\n');
-
-out:
- *lenp -= left;
- *ppos += *lenp;
-
- return err;
-}
-
-static int do_proc_douintvec(const struct ctl_table *table, int dir,
- void *buffer, size_t *lenp, loff_t *ppos,
- int (*conv)(unsigned long *u_ptr,
- unsigned int *k_ptr, int dir,
- const struct ctl_table *table))
-{
- unsigned int vleft;
-
- if (!table->data || !table->maxlen || !*lenp ||
- (*ppos && SYSCTL_KERN_TO_USER(dir))) {
- *lenp = 0;
- return 0;
- }
-
- vleft = table->maxlen / sizeof(unsigned int);
-
- /*
- * Arrays are not supported, keep this simple. *Do not* add
- * support for them.
- */
- if (vleft != 1) {
- *lenp = 0;
- return -EINVAL;
- }
-
- if (!conv)
- conv = do_proc_uint_conv;
-
- if (SYSCTL_USER_TO_KERN(dir))
- return do_proc_douintvec_w(table, buffer, lenp, ppos, conv);
- return do_proc_douintvec_r(table, buffer, lenp, ppos, conv);
-}
-
/**
* proc_douintvec_conv - read a vector of unsigned ints with a custom converter
*
@@ -774,14 +749,19 @@ static int do_proc_douintvec(const struct ctl_table *table, int dir,
* values from/to the user buffer, treated as an ASCII string. Negative
* strings are not allowed.
*
- * Returns 0 on success
+ * Returns: %0 on success
*/
int proc_douintvec_conv(const struct ctl_table *table, int dir, void *buffer,
size_t *lenp, loff_t *ppos,
- int (*conv)(unsigned long *u_ptr, unsigned int *k_ptr,
+ int (*conv)(bool *negp, ulong *u_ptr, uint *k_ptr,
int dir, const struct ctl_table *table))
{
- return do_proc_douintvec(table, dir, buffer, lenp, ppos, conv);
+
+ if (!conv)
+ conv = do_proc_uint_conv;
+
+ return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_UINT,
+ (union proc_vec_conv){ .uint_conv = conv });
}
/**
@@ -798,7 +778,7 @@ int proc_douintvec_conv(const struct ctl_table *table, int dir, void *buffer,
* table->data must point to a bool variable and table->maxlen must
* be sizeof(bool).
*
- * Returns 0 on success.
+ * Returns: %0 on success.
*/
int proc_dobool(const struct ctl_table *table, int dir, void *buffer,
size_t *lenp, loff_t *ppos)
@@ -835,12 +815,13 @@ int proc_dobool(const struct ctl_table *table, int dir, void *buffer,
* Reads/writes up to table->maxlen/sizeof(unsigned int) integer
* values from/to the user buffer, treated as an ASCII string.
*
- * Returns 0 on success.
+ * Returns: %0 on success.
*/
int proc_dointvec(const struct ctl_table *table, int dir, void *buffer,
size_t *lenp, loff_t *ppos)
{
- return do_proc_dointvec(table, dir, buffer, lenp, ppos, NULL);
+ return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_INT,
+ (union proc_vec_conv){ .int_conv = do_proc_int_conv });
}
/**
@@ -854,13 +835,13 @@ int proc_dointvec(const struct ctl_table *table, int dir, void *buffer,
* Reads/writes up to table->maxlen/sizeof(unsigned int) unsigned integer
* values from/to the user buffer, treated as an ASCII string.
*
- * Returns 0 on success.
+ * Returns: %0 on success.
*/
int proc_douintvec(const struct ctl_table *table, int dir, void *buffer,
size_t *lenp, loff_t *ppos)
{
- return do_proc_douintvec(table, dir, buffer, lenp, ppos,
- do_proc_uint_conv);
+ return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_UINT,
+ (union proc_vec_conv){ .uint_conv = do_proc_uint_conv });
}
/**
@@ -877,14 +858,14 @@ int proc_douintvec(const struct ctl_table *table, int dir, void *buffer,
* This routine will ensure the values are within the range specified by
* table->extra1 (min) and table->extra2 (max).
*
- * Returns 0 on success or -EINVAL when the range check fails and
+ * Returns: %0 on success or -EINVAL when the range check fails and
* SYSCTL_USER_TO_KERN(dir) == true
*/
int proc_dointvec_minmax(const struct ctl_table *table, int dir,
void *buffer, size_t *lenp, loff_t *ppos)
{
- return do_proc_dointvec(table, dir, buffer, lenp, ppos,
- do_proc_int_conv_minmax);
+ return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_INT,
+ (union proc_vec_conv){ .int_conv = do_proc_int_conv_minmax });
}
/**
@@ -904,14 +885,14 @@ int proc_dointvec_minmax(const struct ctl_table *table, int dir,
* (max). And Check that the values are less than UINT_MAX to avoid having to
* support wrap around uses from userspace.
*
- * Returns 0 on success or -ERANGE when range check failes and
+ * Returns: %0 on success or -ERANGE when range check failes and
* SYSCTL_USER_TO_KERN(dir) == true
*/
int proc_douintvec_minmax(const struct ctl_table *table, int dir,
void *buffer, size_t *lenp, loff_t *ppos)
{
- return do_proc_douintvec(table, dir, buffer, lenp, ppos,
- do_proc_uint_conv_minmax);
+ return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_UINT,
+ (union proc_vec_conv){ .uint_conv = do_proc_uint_conv_minmax });
}
/**
@@ -929,7 +910,7 @@ int proc_douintvec_minmax(const struct ctl_table *table, int dir,
* This routine will ensure the values are within the range specified by
* table->extra1 (min) and table->extra2 (max).
*
- * Returns 0 on success or an error on SYSCTL_USER_TO_KERN(dir) == true
+ * Returns: %0 on success or an error on SYSCTL_USER_TO_KERN(dir) == true
* and the range check fails.
*/
int proc_dou8vec_minmax(const struct ctl_table *table, int dir,
@@ -954,8 +935,8 @@ int proc_dou8vec_minmax(const struct ctl_table *table, int dir,
tmp.extra2 = (unsigned int *) &max;
val = READ_ONCE(*data);
- res = do_proc_douintvec(&tmp, dir, buffer, lenp, ppos,
- do_proc_uint_conv_minmax);
+ res = do_proc_vec(&tmp, dir, buffer, lenp, ppos, PROC_VEC_UINT,
+ (union proc_vec_conv){ .uint_conv = do_proc_uint_conv_minmax });
if (res)
return res;
if (SYSCTL_USER_TO_KERN(dir))
@@ -964,87 +945,129 @@ int proc_dou8vec_minmax(const struct ctl_table *table, int dir,
}
EXPORT_SYMBOL_GPL(proc_dou8vec_minmax);
-static int do_proc_doulongvec_minmax(const struct ctl_table *table, int dir,
- void *buffer, size_t *lenp, loff_t *ppos,
- unsigned long convmul,
- unsigned long convdiv)
+/**
+ * proc_ulong_conv - Change user or kernel pointer based on direction
+ *
+ * @u_ptr: pointer to user variable
+ * @k_ptr: pointer to kernel variable
+ * @dir: %TRUE if this is a write to the sysctl file
+ * @tbl: the sysctl table
+ * @k_ptr_range_check: Check range for k_ptr when %TRUE
+ * @user_to_kern: Callback used to assign value from user to kernel var
+ * @kern_to_user: Callback used to assign value from kernel to user var
+ *
+ * When direction is kernel to user, then the u_ptr is modified.
+ * When direction is user to kernel, then the k_ptr is modified.
+ *
+ * Returns: 0 on success
+ */
+int proc_ulong_conv(ulong *u_ptr, ulong *k_ptr, int dir,
+ const struct ctl_table *tbl, bool k_ptr_range_check,
+ int (*user_to_kern)(const ulong *u_ptr, ulong *k_ptr),
+ int (*kern_to_user)(ulong *u_ptr, const ulong *k_ptr))
{
- unsigned long *i, *min, *max;
- int vleft, first = 1, err = 0;
- size_t left;
- char *p;
-
- if (!table->data || !table->maxlen || !*lenp ||
- (*ppos && SYSCTL_KERN_TO_USER(dir))) {
- *lenp = 0;
- return 0;
- }
-
- i = table->data;
- min = table->extra1;
- max = table->extra2;
- vleft = table->maxlen / sizeof(unsigned long);
- left = *lenp;
+ if (SYSCTL_KERN_TO_USER(dir))
+ return kern_to_user(u_ptr, k_ptr);
- if (SYSCTL_USER_TO_KERN(dir)) {
- if (proc_first_pos_non_zero_ignore(ppos, table))
- goto out;
+ if (k_ptr_range_check) {
+ ulong tmp_k;
+ int ret;
- if (left > PAGE_SIZE - 1)
- left = PAGE_SIZE - 1;
- p = buffer;
- }
+ if (!tbl)
+ return -EINVAL;
+ ret = user_to_kern(u_ptr, &tmp_k);
+ if (ret)
+ return ret;
+ if ((tbl->extra1 && *(ulong *)tbl->extra1 > tmp_k) ||
+ (tbl->extra2 && *(ulong *)tbl->extra2 < tmp_k))
+ return -ERANGE;
+ WRITE_ONCE(*k_ptr, tmp_k);
+ } else
+ return user_to_kern(u_ptr, k_ptr);
+ return 0;
+}
- for (; left && vleft--; i++, first = 0) {
- unsigned long val;
+/**
+ * proc_ulong_u2k_conv_uop - Assign user value to a kernel pointer
+ *
+ * @u_ptr: pointer to user space variable
+ * @k_ptr: pointer to kernel variable
+ * @u_ptr_op: execute this function before assigning to k_ptr
+ *
+ * Uses WRITE_ONCE to assign value to k_ptr. Executes u_ptr_op if
+ * not NULL.
+ *
+ * Returns: 0 on success.
+ */
+int proc_ulong_u2k_conv_uop(const ulong *u_ptr, ulong *k_ptr,
+ ulong (*u_ptr_op)(const ulong))
+{
+ ulong u = u_ptr_op ? u_ptr_op(*u_ptr) : *u_ptr;
- if (SYSCTL_USER_TO_KERN(dir)) {
- bool neg;
+ WRITE_ONCE(*k_ptr, u);
+ return 0;
+}
- proc_skip_spaces(&p, &left);
- if (!left)
- break;
+static int proc_ulong_u2k_conv(const ulong *u_ptr, ulong *k_ptr)
+{
+ return proc_ulong_u2k_conv_uop(u_ptr, k_ptr, NULL);
+}
- err = proc_get_long(&p, &left, &val, &neg,
- proc_wspace_sep,
- sizeof(proc_wspace_sep), NULL);
- if (err || neg) {
- err = -EINVAL;
- break;
- }
+/**
+ * proc_ulong_k2u_conv_kop - Assign kernel value to a user space pointer
+ *
+ * @u_ptr: pointer to user space variable
+ * @k_ptr: pointer to kernel variable
+ * @k_ptr_op: Operation applied to k_ptr before assignment
+ *
+ * Uses READ_ONCE to assign value to u_ptr. Executes k_ptr_op if
+ * not NULL.
+ *
+ * Returns: 0 on success.
+ */
+int proc_ulong_k2u_conv_kop(ulong *u_ptr, const ulong *k_ptr,
+ ulong (*k_ptr_op)(const ulong))
+{
+ ulong val = k_ptr_op ? k_ptr_op(READ_ONCE(*k_ptr)) : READ_ONCE(*k_ptr);
+ *u_ptr = (ulong)val;
+ return 0;
+}
- val = convmul * val / convdiv;
- if ((min && val < *min) || (max && val > *max)) {
- err = -EINVAL;
- break;
- }
- WRITE_ONCE(*i, val);
- } else {
- val = convdiv * READ_ONCE(*i) / convmul;
- if (!first)
- proc_put_char(&buffer, &left, '\t');
- proc_put_long(&buffer, &left, val, false);
- }
- }
+static int proc_ulong_k2u_conv(ulong *u_ptr, const ulong *k_ptr)
+{
+ return proc_ulong_k2u_conv_kop(u_ptr, k_ptr, NULL);
+}
- if (SYSCTL_KERN_TO_USER(dir) && !first && left && !err)
- proc_put_char(&buffer, &left, '\n');
- if (SYSCTL_USER_TO_KERN(dir) && !err)
- proc_skip_spaces(&p, &left);
- if (SYSCTL_USER_TO_KERN(dir) && first)
- return err ? : -EINVAL;
- *lenp -= left;
-out:
- *ppos += *lenp;
- return err;
+static int do_proc_ulong_conv(bool *negp, ulong *u_ptr, ulong *k_ptr, int dir,
+ const struct ctl_table *tbl)
+{
+ return proc_ulong_conv(u_ptr, k_ptr, dir, tbl, true,
+ proc_ulong_u2k_conv, proc_ulong_k2u_conv);
}
-int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir,
+/**
+ * proc_doulongvec_conv - read a vector of unsigned longs with a custom converter
+ *
+ * @table: the sysctl table
+ * @dir: %TRUE if this is a write to the sysctl file
+ * @buffer: the user buffer
+ * @lenp: the size of the user buffer
+ * @ppos: file position
+ * @conv: Custom converter call back
+ *
+ * Reads/writes up to table->maxlen/sizeof(unsigned long) unsigned long
+ * values from/to the user buffer, treated as an ASCII string. Negative
+ * strings are not allowed.
+ *
+ * Returns: 0 on success
+ */
+int proc_doulongvec_conv(const struct ctl_table *table, int dir,
void *buffer, size_t *lenp, loff_t *ppos,
- unsigned long convmul, unsigned long convdiv)
+ int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr,
+ int dir, const struct ctl_table *table))
{
- return do_proc_doulongvec_minmax(table, dir, buffer, lenp, ppos,
- convmul, convdiv);
+ return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_ULONG,
+ (union proc_vec_conv){ .ulong_conv = conv });
}
/**
@@ -1061,12 +1084,13 @@ int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir,
* This routine will ensure the values are within the range specified by
* table->extra1 (min) and table->extra2 (max).
*
- * Returns 0 on success.
+ * Returns: %0 on success.
*/
int proc_doulongvec_minmax(const struct ctl_table *table, int dir,
void *buffer, size_t *lenp, loff_t *ppos)
{
- return proc_doulongvec_minmax_conv(table, dir, buffer, lenp, ppos, 1l, 1l);
+ return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_ULONG,
+ (union proc_vec_conv){ .ulong_conv = do_proc_ulong_conv });
}
/**
@@ -1076,21 +1100,22 @@ int proc_doulongvec_minmax(const struct ctl_table *table, int dir,
* @buffer: the user buffer
* @lenp: the size of the user buffer
* @ppos: file position
- * @conv: Custom converter call back
+ * @conv: Custom converter call back. Defaults to do_proc_int_conv
*
- * Reads/writes up to table->maxlen/sizeof(unsigned int) unsigned integer
- * values from/to the user buffer, treated as an ASCII string. Negative
- * strings are not allowed.
+ * Reads/writes up to table->maxlen/sizeof(int) integer values from/to the
+ * user buffer, treated as an ASCII string.
*
* Returns: 0 on success
*/
-
int proc_dointvec_conv(const struct ctl_table *table, int dir, void *buffer,
size_t *lenp, loff_t *ppos,
int (*conv)(bool *negp, unsigned long *u_ptr, int *k_ptr,
int dir, const struct ctl_table *table))
{
- return do_proc_dointvec(table, dir, buffer, lenp, ppos, conv);
+ if (!conv)
+ conv = do_proc_int_conv;
+ return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_INT,
+ (union proc_vec_conv){ .int_conv = conv });
}
/**
@@ -1108,7 +1133,7 @@ int proc_dointvec_conv(const struct ctl_table *table, int dir, void *buffer,
* large bitmaps may be represented in a compact manner. Writing into
* the file will clear the bitmap then update it with the given input.
*
- * Returns 0 on success.
+ * Returns: %0 on success.
*/
int proc_do_large_bitmap(const struct ctl_table *table, int dir,
void *buffer, size_t *lenp, loff_t *ppos)
@@ -1240,7 +1265,7 @@ int proc_do_large_bitmap(const struct ctl_table *table, int dir,
return err;
}
-#else /* CONFIG_PROC_SYSCTL */
+#else /* CONFIG_SYSCTL */
int proc_dostring(const struct ctl_table *table, int dir,
void *buffer, size_t *lenp, loff_t *ppos)
@@ -1280,7 +1305,7 @@ int proc_douintvec_minmax(const struct ctl_table *table, int dir,
int proc_douintvec_conv(const struct ctl_table *table, int write, void *buffer,
size_t *lenp, loff_t *ppos,
- int (*conv)(unsigned long *lvalp, unsigned int *valp,
+ int (*conv)(bool *negp, ulong *lvalp, uint *valp,
int write, const struct ctl_table *table))
{
return -ENOSYS;
@@ -1317,9 +1342,10 @@ int proc_doulongvec_minmax(const struct ctl_table *table, int dir,
return -ENOSYS;
}
-int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir,
+int proc_doulongvec_conv(const struct ctl_table *table, int dir,
void *buffer, size_t *lenp, loff_t *ppos,
- unsigned long convmul, unsigned long convdiv)
+ int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr,
+ int dir, const struct ctl_table *table))
{
return -ENOSYS;
}
@@ -1338,7 +1364,7 @@ int proc_do_large_bitmap(const struct ctl_table *table, int dir,
return -ENOSYS;
}
-#endif /* CONFIG_PROC_SYSCTL */
+#endif /* CONFIG_SYSCTL */
#if defined(CONFIG_SYSCTL)
int proc_do_static_key(const struct ctl_table *table, int dir,
@@ -1372,7 +1398,7 @@ int proc_do_static_key(const struct ctl_table *table, int dir,
}
static const struct ctl_table sysctl_subsys_table[] = {
-#ifdef CONFIG_PROC_SYSCTL
+#ifdef CONFIG_SYSCTL
{
.procname = "sysctl_writes_strict",
.data = &sysctl_writes_strict,
diff --git a/kernel/taskstats.c b/kernel/taskstats.c
index 2cd0172d0516..f31df72f0e9d 100644
--- a/kernel/taskstats.c
+++ b/kernel/taskstats.c
@@ -361,17 +361,14 @@ static int parse(struct nlattr *na, struct cpumask *mask)
int len;
int ret;
- if (na == NULL)
- return 1;
len = nla_len(na);
if (len > TASKSTATS_CPUMASK_MAXLEN)
return -E2BIG;
if (len < 1)
return -EINVAL;
- data = kmalloc(len, GFP_KERNEL);
+ data = nla_strdup(na, GFP_KERNEL);
if (!data)
return -ENOMEM;
- nla_strscpy(data, na, len);
ret = cpulist_parse(data, mask);
kfree(data);
return ret;
@@ -423,7 +420,7 @@ static int cgroupstats_user_cmd(struct sk_buff *skb, struct genl_info *info)
fd = nla_get_u32(info->attrs[CGROUPSTATS_CMD_ATTR_FD]);
CLASS(fd, f)(fd);
if (fd_empty(f))
- return 0;
+ return -EBADF;
size = nla_total_size(sizeof(struct cgroupstats));
@@ -451,36 +448,18 @@ static int cgroupstats_user_cmd(struct sk_buff *skb, struct genl_info *info)
return send_reply(rep_skb, info);
}
-static int cmd_attr_register_cpumask(struct genl_info *info)
-{
- cpumask_var_t mask;
- int rc;
-
- if (!alloc_cpumask_var(&mask, GFP_KERNEL))
- return -ENOMEM;
- rc = parse(info->attrs[TASKSTATS_CMD_ATTR_REGISTER_CPUMASK], mask);
- if (rc < 0)
- goto out;
- rc = add_del_listener(info->snd_portid, mask, REGISTER);
-out:
- free_cpumask_var(mask);
- return rc;
-}
-
-static int cmd_attr_deregister_cpumask(struct genl_info *info)
+static int cmd_attr_cpumask(struct genl_info *info, int attr,
+ enum actions action)
{
- cpumask_var_t mask;
+ cpumask_var_t mask __free(free_cpumask_var) = CPUMASK_VAR_NULL;
int rc;
if (!alloc_cpumask_var(&mask, GFP_KERNEL))
return -ENOMEM;
- rc = parse(info->attrs[TASKSTATS_CMD_ATTR_DEREGISTER_CPUMASK], mask);
+ rc = parse(info->attrs[attr], mask);
if (rc < 0)
- goto out;
- rc = add_del_listener(info->snd_portid, mask, DEREGISTER);
-out:
- free_cpumask_var(mask);
- return rc;
+ return rc;
+ return add_del_listener(info->snd_portid, mask, action);
}
static size_t taskstats_packet_size(void)
@@ -555,9 +534,13 @@ err:
static int taskstats_user_cmd(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TASKSTATS_CMD_ATTR_REGISTER_CPUMASK])
- return cmd_attr_register_cpumask(info);
+ return cmd_attr_cpumask(info,
+ TASKSTATS_CMD_ATTR_REGISTER_CPUMASK,
+ REGISTER);
else if (info->attrs[TASKSTATS_CMD_ATTR_DEREGISTER_CPUMASK])
- return cmd_attr_deregister_cpumask(info);
+ return cmd_attr_cpumask(info,
+ TASKSTATS_CMD_ATTR_DEREGISTER_CPUMASK,
+ DEREGISTER);
else if (info->attrs[TASKSTATS_CMD_ATTR_PID])
return cmd_attr_pid(info);
else if (info->attrs[TASKSTATS_CMD_ATTR_TGID])
diff --git a/kernel/time/Kconfig b/kernel/time/Kconfig
index d098ac39bde4..ddfb6bee0745 100644
--- a/kernel/time/Kconfig
+++ b/kernel/time/Kconfig
@@ -133,7 +133,6 @@ config NO_HZ_FULL
depends on HAVE_VIRT_CPU_ACCOUNTING_GEN
select NO_HZ_COMMON
select RCU_NOCB_CPU
- select VIRT_CPU_ACCOUNTING_GEN
select IRQ_WORK
select CPU_ISOLATION
help
diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c
index 0014d163f989..62ad7c000386 100644
--- a/kernel/time/clockevents.c
+++ b/kernel/time/clockevents.c
@@ -615,34 +615,41 @@ void clockevents_handle_noop(struct clock_event_device *dev)
{
}
-/**
- * clockevents_exchange_device - release and request clock devices
- * @old: device to release (can be NULL)
- * @new: device to request (can be NULL)
- *
- * Called from various tick functions with clockevents_lock held and
- * interrupts disabled.
- */
-void clockevents_exchange_device(struct clock_event_device *old,
- struct clock_event_device *new)
+void __clockevents_exchange_device(struct clock_event_device *old,
+ struct clock_event_device *new)
{
/*
* Caller releases a clock event device. We queue it into the
* released list and do a notify add later.
*/
if (old) {
- module_put(old->owner);
clockevents_switch_state(old, CLOCK_EVT_STATE_DETACHED);
list_move(&old->list, &clockevents_released);
}
if (new) {
- BUG_ON(!clockevent_state_detached(new));
+ WARN_ON(!clockevent_state_detached(new));
clockevents_shutdown(new);
}
}
/**
+ * clockevents_exchange_device - release and request clock devices
+ * @old: device to release (can be NULL)
+ * @new: device to request (can be NULL)
+ *
+ * Called from various tick functions with clockevents_lock held and
+ * interrupts disabled.
+ */
+void clockevents_exchange_device(struct clock_event_device *old,
+ struct clock_event_device *new)
+{
+ __clockevents_exchange_device(old, new);
+ if (old)
+ module_put(old->owner);
+}
+
+/**
* clockevents_suspend - suspend clock devices
*/
void clockevents_suspend(void)
@@ -699,7 +706,7 @@ void tick_offline_cpu(unsigned int cpu)
if (cpumask_test_cpu(cpu, dev->cpumask) &&
cpumask_weight(dev->cpumask) == 1 &&
!tick_is_broadcast_device(dev)) {
- BUG_ON(!clockevent_state_detached(dev));
+ WARN_ON(!clockevent_state_detached(dev));
list_del(&dev->list);
}
}
diff --git a/kernel/time/clocksource.c b/kernel/time/clocksource.c
index e48c4d379a7c..f1253f5795c6 100644
--- a/kernel/time/clocksource.c
+++ b/kernel/time/clocksource.c
@@ -123,7 +123,6 @@ static atomic_t watchdog_reset_pending;
/* Watchdog interval: 0.5sec. */
#define WATCHDOG_INTERVAL (HZ >> 1)
-#define WATCHDOG_INTERVAL_NS (WATCHDOG_INTERVAL * (NSEC_PER_SEC / HZ))
/* Maximum time between two reference watchdog readouts */
#define WATCHDOG_READOUT_MAX_NS (50U * NSEC_PER_USEC)
@@ -1566,8 +1565,12 @@ static int __init init_clocksource_sysfs(void)
{
int error = subsys_system_register(&clocksource_subsys, NULL);
- if (!error)
- error = device_register(&device_clocksource);
+ if (error)
+ return error;
+
+ error = device_register(&device_clocksource);
+ if (error)
+ bus_unregister(&clocksource_subsys);
return error;
}
diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c
index 313dcea127fe..cbf1693c86b3 100644
--- a/kernel/time/hrtimer.c
+++ b/kernel/time/hrtimer.c
@@ -26,6 +26,7 @@
#include <linux/export.h>
#include <linux/percpu.h>
#include <linux/hrtimer.h>
+#include <linux/hrtimer_bases.h>
#include <linux/notifier.h>
#include <linux/syscalls.h>
#include <linux/interrupt.h>
@@ -676,6 +677,8 @@ static ktime_t hrtimer_update_next_event(struct hrtimer_cpu_base *cpu_base)
static inline ktime_t hrtimer_update_base(struct hrtimer_cpu_base *base)
{
+ lockdep_assert_held(&base->lock);
+
ktime_t *offs_real = &base->clock_base[HRTIMER_BASE_REALTIME].offset;
ktime_t *offs_boot = &base->clock_base[HRTIMER_BASE_BOOTTIME].offset;
ktime_t *offs_tai = &base->clock_base[HRTIMER_BASE_TAI].offset;
@@ -707,7 +710,7 @@ static inline void hrtimer_rearm_event(ktime_t expires_next, bool deferred)
tick_program_event(expires_next, 1);
}
-static void __hrtimer_reprogram(struct hrtimer_cpu_base *cpu_base, struct hrtimer *next_timer,
+static void __hrtimer_reprogram(struct hrtimer_cpu_base *cpu_base,
ktime_t expires_next)
{
cpu_base->expires_next = expires_next;
@@ -743,7 +746,7 @@ static void hrtimer_force_reprogram(struct hrtimer_cpu_base *cpu_base, bool skip
if (skip_equal && expires_next == cpu_base->expires_next)
return;
- __hrtimer_reprogram(cpu_base, cpu_base->next_timer, expires_next);
+ __hrtimer_reprogram(cpu_base, expires_next);
}
/* High resolution timer related functions */
@@ -896,14 +899,14 @@ static void hrtimer_reprogram(struct hrtimer *timer, bool reprogram)
cpu_base->next_timer = timer;
- __hrtimer_reprogram(cpu_base, timer, expires);
+ __hrtimer_reprogram(cpu_base, expires);
}
static bool update_needs_ipi(struct hrtimer_cpu_base *cpu_base, unsigned int active)
{
struct hrtimer_clock_base *base;
- unsigned int seq;
ktime_t expires;
+ u32 seq;
/*
* Update the base offsets unconditionally so the following
@@ -1040,6 +1043,30 @@ static inline void unlock_hrtimer_base(const struct hrtimer *timer, unsigned lon
}
/**
+ * hrtimer_update_function - Update the timer's callback function
+ * @timer: Timer to update
+ * @function: New callback function
+ *
+ * Only safe to call if the timer is not enqueued. Can be called in the callback function if the
+ * timer is not enqueued at the same time (see the comments above HRTIMER_STATE_ENQUEUED).
+ */
+void hrtimer_update_function(struct hrtimer *timer,
+ enum hrtimer_restart (*function)(struct hrtimer *))
+{
+#ifdef CONFIG_PROVE_LOCKING
+ guard(raw_spinlock_irqsave)(&timer->base->cpu_base->lock);
+
+ if (WARN_ON_ONCE(hrtimer_is_queued(timer)))
+ return;
+
+ if (WARN_ON_ONCE(!function))
+ return;
+#endif
+ ACCESS_PRIVATE(timer, function) = function;
+}
+EXPORT_SYMBOL_GPL(hrtimer_update_function);
+
+/**
* hrtimer_forward() - forward the timer expiry
* @timer: hrtimer to forward
* @now: forward past this time
@@ -1236,13 +1263,23 @@ remove_and_enqueue_same_base(struct hrtimer *timer, struct hrtimer_clock_base *b
{
bool was_first = false;
+ /*
+ * Updating the sort key while @timer is queued can temporarily
+ * make the tree inconsistent. This is safe under cpu_base->lock:
+ * no other queue operation can observe that state.
+ * hrtimer_can_update_in_place() either confirms that the new expiry
+ * fits between the neighbours or timerqueue_linked_del() removes the
+ * timer without consulting the expiry.
+ */
+ hrtimer_set_expires_range_ns(timer, expires, delta_ns);
+ expires = hrtimer_get_expires(timer);
+
/* Remove it from the timer queue if active */
if (timer->is_queued) {
was_first = !timerqueue_linked_prev(&timer->node);
/* Try to update in place to avoid the de/enqueue dance */
if (hrtimer_can_update_in_place(timer, base, expires)) {
- hrtimer_set_expires_range_ns(timer, expires, delta_ns);
trace_hrtimer_start(timer, mode, true);
if (was_first)
base->expires_next = expires;
@@ -1253,9 +1290,6 @@ remove_and_enqueue_same_base(struct hrtimer *timer, struct hrtimer_clock_base *b
timerqueue_linked_del(&base->active, &timer->node);
}
- /* Set the new expiry time */
- hrtimer_set_expires_range_ns(timer, expires, delta_ns);
-
debug_activate(timer, mode, timer->is_queued);
base->cpu_base->active_bases |= 1 << base->index;
@@ -1786,13 +1820,21 @@ EXPORT_SYMBOL_GPL(__hrtimer_get_remaining);
ktime_t hrtimer_get_next_event(void)
{
struct hrtimer_cpu_base *cpu_base = this_cpu_ptr(&hrtimer_bases);
- ktime_t expires = KTIME_MAX;
- guard(raw_spinlock_irqsave)(&cpu_base->lock);
- if (!hrtimer_hres_active(cpu_base))
- expires = __hrtimer_get_next_event(cpu_base, HRTIMER_ACTIVE_ALL);
+ /*
+ * When HRES is active cmp_next_hrtimer_event() expects KTIME_MAX.
+ *
+ * cpu_base->hres_active is written only by the local CPU in
+ * hrtimer_switch_to_hres() from hard interrupt context and in
+ * hrtimers_cpu_starting() during CPU bring-up, and all callers reach
+ * this with interrupts disabled on the same CPU, so an unlocked read is
+ * stable without holding the lock.
+ */
+ if (hrtimer_hres_active(cpu_base))
+ return KTIME_MAX;
- return expires;
+ guard(raw_spinlock_irqsave)(&cpu_base->lock);
+ return __hrtimer_get_next_event(cpu_base, HRTIMER_ACTIVE_ALL);
}
/**
@@ -2060,13 +2102,6 @@ static void __run_hrtimer(struct hrtimer_cpu_base *cpu_base, struct hrtimer_cloc
base->running = NULL;
}
-static __always_inline struct hrtimer *clock_base_next_timer_safe(struct hrtimer_clock_base *base)
-{
- struct timerqueue_linked_node *next = timerqueue_linked_first(&base->active);
-
- return next ? hrtimer_from_timerqueue_node(next) : NULL;
-}
-
static void __hrtimer_run_queues(struct hrtimer_cpu_base *cpu_base, ktime_t now,
unsigned long flags, unsigned int active_mask)
{
@@ -2228,8 +2263,10 @@ retry:
expires_next = hrtimer_update_next_event(cpu_base);
cpu_base->hang_detected = false;
if (expires_next < now) {
- if (++retries < 3)
+ if (++retries < 3) {
+ cpu_base->nr_retries++;
goto retry;
+ }
delta = ktime_sub(now, entry_time);
cpu_base->max_hang_time = max_t(unsigned int, cpu_base->max_hang_time, delta);
diff --git a/kernel/time/itimer.c b/kernel/time/itimer.c
index 7c6110e964e7..03a32dffc56c 100644
--- a/kernel/time/itimer.c
+++ b/kernel/time/itimer.c
@@ -100,7 +100,7 @@ static int do_getitimer(int which, struct itimerspec64 *value)
static int put_itimerval(struct __kernel_old_itimerval __user *o,
const struct itimerspec64 *i)
{
- struct __kernel_old_itimerval v;
+ struct __kernel_old_itimerval v = {};
v.it_interval.tv_sec = i->it_interval.tv_sec;
v.it_interval.tv_usec = i->it_interval.tv_nsec / NSEC_PER_USEC;
diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c
index d51428867a33..9b3487d40cd6 100644
--- a/kernel/time/jiffies.c
+++ b/kernel/time/jiffies.c
@@ -98,9 +98,11 @@ void __init register_refined_jiffies(long cycles_per_second)
__clocksource_register(&refined_jiffies);
}
-#ifdef CONFIG_PROC_SYSCTL
+#ifdef CONFIG_SYSCTL
static ulong mult_hz(const ulong val)
{
+ if (val >= ULONG_MAX / HZ)
+ return ULONG_MAX;
return val * HZ;
}
@@ -136,6 +138,8 @@ static int sysctl_k2u_int_conv_userhz(bool *negp, ulong *u_ptr, const int *k_ptr
static ulong sysctl_msecs_to_jiffies(const ulong val)
{
+ if (val > jiffies_to_msecs(MAX_JIFFY_OFFSET))
+ return MAX_JIFFY_OFFSET;
return msecs_to_jiffies(val);
}
@@ -181,11 +185,28 @@ static int do_proc_int_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr,
int *k_ptr, int dir,
const struct ctl_table *tbl)
{
- return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, false,
+ return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, true,
sysctl_u2k_int_conv_ms, sysctl_k2u_int_conv_ms);
}
-#else // CONFIG_PROC_SYSCTL
+static int sysctl_u2k_ulong_conv_ms(const ulong *u_ptr, ulong *k_ptr)
+{
+ return proc_ulong_u2k_conv_uop(u_ptr, k_ptr, sysctl_msecs_to_jiffies);
+}
+
+static int sysctl_k2u_ulong_conv_ms(ulong *u_ptr, const ulong *k_ptr)
+{
+ return proc_ulong_k2u_conv_kop(u_ptr, k_ptr, sysctl_jiffies_to_msecs);
+}
+
+static int do_proc_ulong_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, ulong *k_ptr,
+ int dir, const struct ctl_table *tbl)
+{
+ return proc_ulong_conv(u_ptr, k_ptr, dir, tbl, true,
+ sysctl_u2k_ulong_conv_ms, sysctl_k2u_ulong_conv_ms);
+}
+
+#else // CONFIG_SYSCTL
static int do_proc_int_conv_jiffies(bool *negp, ulong *u_ptr, int *k_ptr,
int dir, const struct ctl_table *tbl)
{
@@ -211,6 +232,12 @@ static int do_proc_int_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr,
{
return -ENOSYS;
}
+
+static int do_proc_ulong_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, ulong *k_ptr,
+ int dir, const struct ctl_table *tbl)
+{
+ return -ENOSYS;
+}
#endif
/**
@@ -309,8 +336,8 @@ int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int dir,
int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir,
void *buffer, size_t *lenp, loff_t *ppos)
{
- return proc_doulongvec_minmax_conv(table, dir, buffer, lenp, ppos,
- HZ, 1000l);
+ return proc_doulongvec_conv(table, dir, buffer, lenp, ppos,
+ do_proc_ulong_conv_ms_jiffies_minmax);
}
EXPORT_SYMBOL(proc_doulongvec_ms_jiffies_minmax);
diff --git a/kernel/time/namespace.c b/kernel/time/namespace.c
index 5fa0af66cf3f..3aff27bb0a15 100644
--- a/kernel/time/namespace.c
+++ b/kernel/time/namespace.c
@@ -293,10 +293,12 @@ int proc_timens_set_offset(struct file *file, struct task_struct *p,
return -EINVAL;
}
- if (off->val.tv_sec > KTIME_SEC_MAX ||
- off->val.tv_sec < -KTIME_SEC_MAX)
+ if (off->val.tv_sec > KTIME_SEC_MAX || off->val.tv_sec < -KTIME_SEC_MAX)
return -ERANGE;
+ if (off->val.tv_nsec < 0 || off->val.tv_nsec >= NSEC_PER_SEC)
+ return -EINVAL;
+
tp = timespec64_add(tp, off->val);
/*
* KTIME_SEC_MAX is divided by 2 to be sure that KTIME_MAX is
diff --git a/kernel/time/namespace_vdso.c b/kernel/time/namespace_vdso.c
index 0d74d160eec9..5ac7b6a6d3a8 100644
--- a/kernel/time/namespace_vdso.c
+++ b/kernel/time/namespace_vdso.c
@@ -74,11 +74,8 @@ struct page *find_timens_vvar_page(struct vm_area_struct *vma)
return current->nsproxy->time_ns->vvar_page;
/*
- * VM_PFNMAP | VM_IO protect .fault() handler from being called
- * through interfaces like /proc/$pid/mem or
- * process_vm_{readv,writev}() as long as there's no .access()
- * in special_mapping_vmops().
- * For more details check_vma_flags() and __access_remote_vm()
+ * vvar_fault() protects this from being called through remote interfaces like
+ * /proc/$pid/mem or process_vm_{readv,writev}().
*/
WARN(1, "vvar_page accessed remotely");
diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
index 97fa99b96dd0..d22b532ec536 100644
--- a/kernel/time/ntp.c
+++ b/kernel/time/ntp.c
@@ -26,11 +26,13 @@
/**
* struct ntp_data - Structure holding all NTP related state
* @tick_usec: USER_HZ period in microseconds
- * @tick_length: Adjusted tick length
- * @tick_length_base: Base value for @tick_length
+ * @tick_length: Tick length in ns << NTP_SCALE_SHIFT
* @time_state: State of the clock synchronization
* @time_status: Clock status bits
* @time_offset: Time adjustment in nanoseconds
+ * @skew_delta: Per-tick phase slew rate for the coming second, in
+ * @time_offset units (shifted-ns / HZ). Set by
+ * second_overflow().
* @time_constant: PLL time constant
* @time_maxerror: Maximum error in microseconds holding the NTP sync distance
* (NTP dispersion + delay / 2)
@@ -38,7 +40,13 @@
* @time_freq: Frequency offset scaled nsecs/secs
* @time_reftime: Time at last adjustment in seconds
* @time_adjust: Adjustment value
+ * @time_adjust_frac: Sub-microsecond remainder of @time_adjust being
+ * delivered, in ns << NTP_SCALE_SHIFT (not divided by HZ).
* @ntp_tick_adj: Constant boot-param configurable NTP tick adjustment (upscaled)
+ * @cs_tick_adj: Fixed per-second adjustment compensating for the difference
+ * between the nominal NTP interval and the real time taken
+ * by the clocksource's integer @cycle_interval (upscaled).
+ * Set by the timekeeping core via ntp_clear().
* @ntp_next_leap_sec: Second value of the next pending leapsecond, or TIME64_MAX if no leap
*
* @pps_valid: PPS signal watchdog counter
@@ -59,17 +67,19 @@
struct ntp_data {
unsigned long tick_usec;
u64 tick_length;
- u64 tick_length_base;
int time_state;
int time_status;
s64 time_offset;
+ s64 skew_delta;
long time_constant;
long time_maxerror;
long time_esterror;
s64 time_freq;
time64_t time_reftime;
long time_adjust;
+ s64 time_adjust_frac;
s64 ntp_tick_adj;
+ s64 cs_tick_adj;
time64_t ntp_next_leap_sec;
#ifdef CONFIG_NTP_PPS
int pps_valid;
@@ -101,6 +111,9 @@ static struct ntp_data tk_ntp_data[TIMEKEEPERS_MAX] = {
#define SECS_PER_DAY 86400
#define MAX_TICKADJ 500LL /* usecs */
+/* One microsecond of phase, in plain shifted-ns (ns << NTP_SCALE_SHIFT) */
+#define ONE_US_NS ((s64)NSEC_PER_USEC << NTP_SCALE_SHIFT)
+/* Per-tick MAX_TICKADJ slew, in plain shifted-ns */
#define MAX_TICKADJ_SCALED \
(((MAX_TICKADJ * NSEC_PER_USEC) << NTP_SCALE_SHIFT) / NTP_INTERVAL_FREQ)
#define MAX_TAI_OFFSET 100000
@@ -245,8 +258,7 @@ static inline void pps_fill_timex(struct ntp_data *ntpdata, struct __kernel_time
#endif /* CONFIG_NTP_PPS */
/*
- * Update tick_length and tick_length_base, based on tick_usec, ntp_tick_adj and
- * time_freq:
+ * Update tick_length based on tick_usec, ntp_tick_adj and time_freq:
*/
static void ntp_update_frequency(struct ntp_data *ntpdata)
{
@@ -255,6 +267,7 @@ static void ntp_update_frequency(struct ntp_data *ntpdata)
second_length = (u64)(tick_usec * NSEC_PER_USEC * USER_HZ) << NTP_SCALE_SHIFT;
second_length += ntpdata->ntp_tick_adj;
+ second_length += ntpdata->cs_tick_adj;
second_length += ntpdata->time_freq;
new_base = div_u64(second_length, NTP_INTERVAL_FREQ);
@@ -263,8 +276,7 @@ static void ntp_update_frequency(struct ntp_data *ntpdata)
* Don't wait for the next second_overflow, apply the change to the
* tick length immediately:
*/
- ntpdata->tick_length += new_base - ntpdata->tick_length_base;
- ntpdata->tick_length_base = new_base;
+ ntpdata->tick_length = new_base;
}
static inline s64 ntp_update_offset_fll(struct ntp_data *ntpdata, s64 offset64, long secs)
@@ -335,14 +347,15 @@ static void __ntp_clear(struct ntp_data *ntpdata)
{
/* Stop active adjtime() */
ntpdata->time_adjust = 0;
+ ntpdata->time_adjust_frac = 0;
ntpdata->time_status |= STA_UNSYNC;
ntpdata->time_maxerror = NTP_PHASE_LIMIT;
ntpdata->time_esterror = NTP_PHASE_LIMIT;
ntp_update_frequency(ntpdata);
- ntpdata->tick_length = ntpdata->tick_length_base;
ntpdata->time_offset = 0;
+ ntpdata->skew_delta = 0;
ntpdata->ntp_next_leap_sec = TIME64_MAX;
/* Clear PPS state variables */
@@ -350,11 +363,26 @@ static void __ntp_clear(struct ntp_data *ntpdata)
}
/**
- * ntp_clear - Clears the NTP state variables
- * @tkid: Timekeeper ID to be able to select proper ntp data array member
+ * ntp_clear - Clear NTP state and set the clocksource quantisation adjustment
+ * @tkid: Timekeeper ID
+ * @cs_tick_adj: Per-second adjustment in ns << NTP_SCALE_SHIFT
+ *
+ * The timekeeping core uses an integer number of cycles (@cycle_interval)
+ * per NTP interval, so the real time that interval represents differs from
+ * the nominal NTP_INTERVAL_LENGTH by up to half a counter period. Folding
+ * this fixed offset into @cs_tick_adj makes it an explicit part of the NTP
+ * tick_length computation in ntp.c, instead of being applied during
+ * timekeeping accumulation where the NTP code never saw it. Like
+ * @ntp_tick_adj it stays internal to the kernel; userspace still sees the
+ * nominal tick via adjtimex. NTP retains its full symmetric ±MAXFREQ range
+ * around the corrected base rate.
+ *
+ * Called whenever the clocksource is (re)configured, which is also when the
+ * rest of the NTP state must be cleared, so the two are done together.
*/
-void ntp_clear(unsigned int tkid)
+void ntp_clear(unsigned int tkid, s64 cs_tick_adj)
{
+ tk_ntp_data[tkid].cs_tick_adj = cs_tick_adj;
__ntp_clear(&tk_ntp_data[tkid]);
}
@@ -364,6 +392,186 @@ u64 ntp_tick_length(unsigned int tkid)
return tk_ntp_data[tkid].tick_length;
}
+s64 ntp_get_skew_delta(unsigned int tkid)
+{
+ return tk_ntp_data[tkid].skew_delta;
+}
+
+/* Sign of @x as +1 or -1 (zero counts as positive; callers pass nonzero). */
+static inline int signof(s64 x)
+{
+ return x < 0 ? -1 : 1;
+}
+
+static s64 ntp_drain_time_offset(unsigned int tkid, s64 amount)
+{
+ struct ntp_data *ntpdata = &tk_ntp_data[tkid];
+
+ /* Only drain if amount and time_offset have the same sign */
+ if (!amount || signof(amount) != signof(ntpdata->time_offset))
+ return amount;
+
+ /* Clamp: don't overshoot zero */
+ if (abs(amount) > abs(ntpdata->time_offset)) {
+ s64 undrained = amount - ntpdata->time_offset;
+
+ ntpdata->time_offset = 0;
+ return undrained;
+ }
+
+ ntpdata->time_offset -= amount;
+ return 0;
+}
+
+/*
+ * Drain the legacy adjtime() correction (time_adjust) as it is delivered.
+ *
+ * @amount is the total intentional per-tick skew for this accumulation
+ * (skew_delta << shift), in time_offset units (shifted_ns / HZ); it covers
+ * both the exponential time_offset slew and the linear adjtime slew. This
+ * function claims only the adjtime share — capped at the MAX_TICKADJ rate —
+ * and returns the remainder for ntp_drain_time_offset().
+ *
+ * time_adjust is in whole µs. The sub-µs remainder being delivered lives in
+ * time_adjust_frac (plain shifted-ns, i.e. ns << NTP_SCALE_SHIFT -- unlike
+ * time_offset these are NOT pre-divided by HZ); we top it up by borrowing
+ * whole microseconds from time_adjust as the drain consumes it.
+ */
+static s64 ntp_drain_time_adjust(unsigned int tkid, s64 amount, unsigned int shift)
+{
+ struct ntp_data *ntpdata = &tk_ntp_data[tkid];
+ /* Sign reference: time_adjust if any whole us remain, else the drawer */
+ s64 ref = ntpdata->time_adjust ? (s64)ntpdata->time_adjust
+ : ntpdata->time_adjust_frac;
+ s64 deliver, deficit, claimed;
+
+ if (!amount || !ref || signof(amount) != signof(ref))
+ return amount;
+
+ /*
+ * Phase to deliver this accumulation, in plain shifted-ns. The drain
+ * @amount is in ÷HZ units, so multiply by HZ first, then clamp to the
+ * MAX_TICKADJ rate (MAX_TICKADJ_SCALED is the per-tick slew in
+ * shifted-ns). Multiply-then-clamp avoids an s64 divide for the cap.
+ */
+ deliver = min(abs(amount) * NTP_INTERVAL_FREQ,
+ (s64)MAX_TICKADJ_SCALED << shift);
+
+ /* Top up the sub-µs drawer from whole-µs time_adjust as needed */
+ deficit = deliver - abs(ntpdata->time_adjust_frac);
+ if (deficit > 0 && ntpdata->time_adjust) {
+ long borrow = div64_u64(deficit + ONE_US_NS - 1, ONE_US_NS);
+
+ if (ntpdata->time_adjust > 0) {
+ borrow = min(borrow, ntpdata->time_adjust);
+ ntpdata->time_adjust -= borrow;
+ ntpdata->time_adjust_frac += (s64)borrow * ONE_US_NS;
+ } else {
+ /* Clamp without negating time_adjust (UB for LONG_MIN) */
+ if (ntpdata->time_adjust > -borrow)
+ borrow = -ntpdata->time_adjust;
+ ntpdata->time_adjust += borrow;
+ ntpdata->time_adjust_frac -= (s64)borrow * ONE_US_NS;
+ }
+ }
+
+ /* Never deliver more than the drawer holds */
+ deliver = min(deliver, abs(ntpdata->time_adjust_frac));
+ if (ntpdata->time_adjust_frac > 0)
+ ntpdata->time_adjust_frac -= deliver;
+ else
+ ntpdata->time_adjust_frac += deliver;
+
+ /* Return the unclaimed remainder in ÷HZ drain units for time_offset */
+ claimed = div_s64(deliver, NTP_INTERVAL_FREQ);
+ return amount - signof(amount) * claimed;
+}
+
+/*
+ * Drain one accumulation's worth of intentional skew as it is delivered.
+ *
+ * @amount is the total intentional per-tick skew for this accumulation
+ * (skew_delta << shift), in time_offset units (shifted_ns / HZ). The
+ * adjtime() linear share is taken from time_adjust first (capped at the
+ * MAX_TICKADJ rate, hence @shift), then the exponential remainder from
+ * time_offset. Returns the amount actually claimed (same ÷HZ units).
+ */
+s64 ntp_drain_skew(unsigned int tkid, s64 amount, unsigned int shift)
+{
+ s64 unclaimed = ntp_drain_time_adjust(tkid, amount, shift);
+
+ unclaimed = ntp_drain_time_offset(tkid, unclaimed);
+
+ /*
+ * Return the amount actually drained from the intentional
+ * phase offset in time_offset and/or time_adjust.
+ */
+ return amount - unclaimed;
+}
+
+/*
+ * time_offset (drained exponentially) and time_adjust (drained linearly at the
+ * MAX_TICKADJ rate) can be asked to slew the clock in opposite directions.
+ * second_overflow() only folds their *net* into skew_delta, so the cancelling
+ * part would never be drained from either tracker via the per-tick code -- and
+ * if they cancel exactly, skew_delta is zero and neither converges at all.
+ *
+ * Settle that cancelling phase directly between the two here. No clock motion
+ * results (the opposing slews annihilate), but both move toward zero so neither
+ * stalls. @amount is the phase to take off time_offset, in its (÷HZ) units and
+ * with its sign; the same real magnitude comes off time_adjust in the opposite
+ * direction. Clamped so neither tracker is driven past zero.
+ */
+static void ntp_transfer_offset_adjust(struct ntp_data *ntpdata, s64 amount)
+{
+ s64 frac_delta, carry;
+
+ /*
+ * Don't drain time_offset past zero. @amount shares its sign and is
+ * normally bounded below it by ntp_offset_chunk(), but the ±1 skew_delta
+ * floor for a tiny time_offset can exceed it, so clamp.
+ */
+ if (abs(amount) > abs(ntpdata->time_offset))
+ amount = ntpdata->time_offset;
+ if (!amount)
+ return;
+
+ /*
+ * Remove the matching phase from time_adjust, in plain shifted-ns. No
+ * clamp against time_adjust's zero is needed: @amount is bounded by the
+ * adjtime chunk, which second_overflow() never lets exceed time_adjust's
+ * own pending phase, so this cannot overshoot.
+ */
+ frac_delta = amount * NTP_INTERVAL_FREQ;
+
+ ntpdata->time_offset -= amount;
+
+ /* Add the matching phase to time_adjust, carrying whole µs (O(1)). */
+ ntpdata->time_adjust_frac += frac_delta;
+ if (ntpdata->time_adjust_frac >= ONE_US_NS ||
+ ntpdata->time_adjust_frac <= -ONE_US_NS) {
+ carry = div64_s64(ntpdata->time_adjust_frac, ONE_US_NS);
+ ntpdata->time_adjust += carry;
+ ntpdata->time_adjust_frac -= carry * ONE_US_NS;
+ }
+
+ /*
+ * Keep time_adjust and its sub-µs remainder the same sign. The
+ * truncating carry above can leave them opposed (e.g. +4 µs paired
+ * with -250 ns), and ntp_drain_time_adjust() treats abs(time_adjust_frac)
+ * as same-direction drawer capacity -- an opposing remainder there makes
+ * it over-deliver phase that was never removed from the pile. Borrow or
+ * repay a single whole µs to realign; the total phase is unchanged.
+ */
+ if (ntpdata->time_adjust > 0 && ntpdata->time_adjust_frac < 0) {
+ ntpdata->time_adjust--;
+ ntpdata->time_adjust_frac += ONE_US_NS;
+ } else if (ntpdata->time_adjust < 0 && ntpdata->time_adjust_frac > 0) {
+ ntpdata->time_adjust++;
+ ntpdata->time_adjust_frac -= ONE_US_NS;
+ }
+}
+
/**
* ntp_get_next_leap - Returns the next leapsecond in CLOCK_REALTIME ktime_t
* @tkid: Timekeeper ID
@@ -398,7 +606,6 @@ ktime_t ntp_get_next_leap(unsigned int tkid)
int second_overflow(unsigned int tkid, time64_t secs)
{
struct ntp_data *ntpdata = &tk_ntp_data[tkid];
- s64 delta;
int leap = 0;
s32 rem;
@@ -458,35 +665,70 @@ int second_overflow(unsigned int tkid, time64_t secs)
}
/* Compute the phase adjustment for the next second */
- ntpdata->tick_length = ntpdata->tick_length_base;
-
- delta = ntp_offset_chunk(ntpdata, ntpdata->time_offset);
- ntpdata->time_offset -= delta;
- ntpdata->tick_length += delta;
/* Check PPS signal */
pps_dec_valid(ntpdata);
- if (!ntpdata->time_adjust)
- goto out;
+ /*
+ * Set the per-tick skew rate for the next second. This is in
+ * the same units as time_offset: (ns << NTP_SCALE_SHIFT) / HZ.
+ * If the result is so low that the skew imparted would round
+ * to zero, pass the bare minimum ±1 to ensure that it *does*
+ * actually drain completely to zero. It won't overshoot because
+ * logarithmic_accumulation() only drains what it can from
+ * time_offset or time_adjust, and the rest ends up in ntp_error
+ * which drives the selection of 'mult' immediately each tick.
+ */
+ if (ntpdata->time_offset || ntpdata->time_adjust ||
+ ntpdata->time_adjust_frac) {
+ s64 off_chunk = ntp_offset_chunk(ntpdata, ntpdata->time_offset);
+ s64 adj_chunk = 0, net;
- if (ntpdata->time_adjust > MAX_TICKADJ) {
- ntpdata->time_adjust -= MAX_TICKADJ;
- ntpdata->tick_length += MAX_TICKADJ_SCALED;
- goto out;
- }
+ /*
+ * Once the exponential chunk rounds to zero, deliver the last
+ * remaining offset this second so it converges to zero instead
+ * of stalling just above it.
+ */
+ if (!off_chunk)
+ off_chunk = ntpdata->time_offset;
+
+ if (ntpdata->time_adjust || ntpdata->time_adjust_frac) {
+ s64 adj;
+
+ if (ntpdata->time_adjust >= MAX_TICKADJ)
+ adj = MAX_TICKADJ * ONE_US_NS;
+ else if (ntpdata->time_adjust <= -MAX_TICKADJ)
+ adj = -MAX_TICKADJ * ONE_US_NS;
+ else
+ adj = ntpdata->time_adjust * ONE_US_NS +
+ ntpdata->time_adjust_frac;
+
+ adj_chunk = div_s64(adj, NTP_INTERVAL_FREQ);
+ if (!adj_chunk)
+ adj_chunk = signof(ntpdata->time_adjust_frac);
+ }
- if (ntpdata->time_adjust < -MAX_TICKADJ) {
- ntpdata->time_adjust += MAX_TICKADJ;
- ntpdata->tick_length -= MAX_TICKADJ_SCALED;
- goto out;
- }
+ /*
+ * If the two slews oppose, only their net would drive the
+ * per-tick drain, so the cancelling part would never drain from
+ * either tracker and an exact cancellation would stall both.
+ * Settle that overlap directly between them (no clock motion).
+ */
+ if (off_chunk && adj_chunk && signof(off_chunk) != signof(adj_chunk)) {
+ s64 conflict = min(abs(off_chunk), abs(adj_chunk));
- ntpdata->tick_length += (s64)(ntpdata->time_adjust * NSEC_PER_USEC / NTP_INTERVAL_FREQ)
- << NTP_SCALE_SHIFT;
- ntpdata->time_adjust = 0;
+ ntp_transfer_offset_adjust(ntpdata, signof(off_chunk) * conflict);
+ }
+
+ /* Net is what the clock delivers; reduce to per-tick, then floor. */
+ net = off_chunk + adj_chunk;
+ ntpdata->skew_delta = div_s64(net, NTP_INTERVAL_FREQ);
+ if (!ntpdata->skew_delta && net)
+ ntpdata->skew_delta = signof(net);
+ } else {
+ ntpdata->skew_delta = 0;
+ }
-out:
return leap;
}
@@ -779,6 +1021,7 @@ int ntp_adjtimex(unsigned int tkid, struct __kernel_timex *txc, const struct tim
if (!(txc->modes & ADJ_OFFSET_READONLY)) {
/* adjtime() is independent from ntp_adjtime() */
ntpdata->time_adjust = txc->offset;
+ ntpdata->time_adjust_frac = 0;
ntp_update_frequency(ntpdata);
audit_ntp_set_old(ad, AUDIT_NTP_ADJUST, save_adjust);
@@ -1020,6 +1263,7 @@ static void hardpps_update_phase(struct ntp_data *ntpdata, long error)
NTP_INTERVAL_FREQ);
/* Cancel running adjtime() */
ntpdata->time_adjust = 0;
+ ntpdata->time_adjust_frac = 0;
}
/* Update jitter */
ntpdata->pps_jitter += (jitter - ntpdata->pps_jitter) >> PPS_INTMIN;
diff --git a/kernel/time/ntp_internal.h b/kernel/time/ntp_internal.h
index 7084d839c207..0474a761bafc 100644
--- a/kernel/time/ntp_internal.h
+++ b/kernel/time/ntp_internal.h
@@ -3,9 +3,11 @@
#define _LINUX_NTP_INTERNAL_H
extern void ntp_init(void);
-extern void ntp_clear(unsigned int tkid);
+extern void ntp_clear(unsigned int tkid, s64 cs_tick_adj);
/* Returns how long ticks are at present, in ns / 2^NTP_SCALE_SHIFT. */
extern u64 ntp_tick_length(unsigned int tkid);
+extern s64 ntp_get_skew_delta(unsigned int tkid);
+extern s64 ntp_drain_skew(unsigned int tkid, s64 amount, unsigned int shift);
extern ktime_t ntp_get_next_leap(unsigned int tkid);
extern int second_overflow(unsigned int tkid, time64_t secs);
extern int ntp_adjtimex(unsigned int tkid, struct __kernel_timex *txc, const struct timespec64 *ts,
diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c
index 5e633d8750d1..0bf4fcd969c8 100644
--- a/kernel/time/posix-cpu-timers.c
+++ b/kernel/time/posix-cpu-timers.c
@@ -408,6 +408,7 @@ static int posix_cpu_timer_create(struct k_itimer *new_timer)
new_timer->kclock = &clock_posix_cpu;
timerqueue_init(&new_timer->it.cpu.node);
+ INIT_LIST_HEAD(&new_timer->it.cpu.elist);
new_timer->it.cpu.pid = get_pid(pid);
rcu_read_unlock();
return 0;
@@ -461,6 +462,127 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p)
trigger_base_recalc_expires(timer, p);
}
+/*
+ * Lookup the task via timer->it.cpu.pid and attempt to lock the task's sighand.
+ *
+ * This can race with the reaping of the task:
+ *
+ * CPU0 CPU1
+ *
+ * // Finds task
+ * p = pid_task(pid, pid_type); __exit_signal(p)
+ * lock(p, sighand);
+ * posix_cpu_timers*_exit();
+ * sighand = lock_task_sighand(p); unhash_task(p);
+ * p->sighand = NULL;
+ * unlock(sighand);
+ *
+ * In this case sighand is NULL, which means the task and the associated timer
+ * queue cannot be longer accessed safely.
+ *
+ * __exit_signal() invokes posix_cpu_timers_exit() and if the thread group is
+ * dead it also invokes posix_cpu_timers_group_exit(). These functions delete
+ * all pending timers from the related timer queues. The POSIX timers (k_itimer)
+ * themself are still accessible, but not longer connected to the task.
+ *
+ * exec() works slightly differently. The task which exec()'s terminates all
+ * other threads in the thread group and runs __exit_signal() on them. As the
+ * thread group is not dead they only clean up the per task timers via
+ * posix_cpu_timers_exit().
+ *
+ * As the TGID on exec() stays the same per process timers stay queued, if they
+ * are armed. This works without a problem when exec() is done by the thread
+ * group leader. If a non-leader thread exec()'s this can end up in the
+ * following scenario:
+ *
+ * CPU0 CPU1
+ * // Returns old leader
+ * p = pid_task(pid, pid_type); de_thread()
+ * switch_leader()
+ * release_task(old leader)
+ * __exit_signal()
+ * old_leader->sighand = NULL;
+ * // Returns NULL
+ * sighand = lock_task_sighand(p)
+ *
+ * That's problematic for several functions:
+ *
+ * - posix_cpu_timer_del(): If the timer is still enqueued on the task the
+ * underlying k_itimer will be freed which results in a UAF in
+ * run_posix_cpu_timers() or on timerqueue related add/delete operations.
+ * If the timer is not enqueued, the failure is harmless
+ *
+ * - posix_cpu_timer_set(): Independent of the enqueued state that results in a
+ * transient failure which is user space visible (-ESRCH) for regular posix
+ * timers. But for the use case in do_cpu_nanosleep() it's the same UAF
+ * problem just that the timer is allocated on the stack.
+ *
+ * - posix_cpu_timer_rearm(): Timer is not enqueued at that point, but this
+ * silently ignores the rearm request, which is a functional problem as the
+ * timer wont expire anymore.
+ */
+static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned long *flags)
+{
+ enum pid_type type = clock_pid_type(timer->it_clock);
+ struct cpu_timer *ctmr = &timer->it.cpu;
+
+ guard(rcu)();
+
+ for (;;) {
+ struct task_struct *t = pid_task(timer->it.cpu.pid, type);
+
+ /* Fail if the task cannot be found. */
+ if (!t)
+ break;
+
+ /* Try to lock the task's sighand */
+ if (lock_task_sighand(t, flags))
+ return t;
+
+ /*
+ * The next PID lookup might either fail or return the new
+ * leader. This is correct for both exit() and exec().
+ */
+ }
+
+ /*
+ * If the timer is still enqueued, warn. There is nothing safe to do
+ * here as there might be two timers in there which are removed in
+ * parallel and that will cause more damage than good. This should never
+ * happen!
+ *
+ * Ensure that the stores to the timer and timerqueue are visible:
+ *
+ * __exit_signal()
+ * posix_cpu_timers*_exit()
+ * write_seqlock(seqlock)
+ * smp_wmb(); <-------
+ * __unhash_process() | !pid_task()
+ * ----> smp_rmb();
+ * WARN_ON_ONCE(...)
+ */
+ smp_rmb();
+ WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node));
+ return NULL;
+}
+
+/*
+ * If the timer is queued on the expiry list, then it cannot be dequeued because
+ * the firing list is not protected by sighand->lock. The delivery path is
+ * waiting for the timer lock. So go back, unlock and retry.
+ */
+static bool posix_cpu_timer_on_expiry_list(struct k_itimer *timer)
+{
+ if (list_empty(&timer->it.cpu.elist))
+ return false;
+
+ /*
+ * Prevent signal delivery as there is no point in delivering a signal
+ * which is made obsolete right away.
+ */
+ timer->it.cpu.firing = false;
+ return true;
+}
/*
* Clean up a CPU-clock timer that is about to be destroyed.
@@ -470,49 +592,22 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p)
*/
static int posix_cpu_timer_del(struct k_itimer *timer)
{
- struct cpu_timer *ctmr = &timer->it.cpu;
- struct sighand_struct *sighand;
struct task_struct *p;
unsigned long flags;
int ret = 0;
- rcu_read_lock();
- p = cpu_timer_task_rcu(timer);
- if (!p)
- goto out;
+ p = timer_lock_sighand(timer, &flags);
- /*
- * Protect against sighand release/switch in exit/exec and process/
- * thread timer list entry concurrent read/writes.
- */
- sighand = lock_task_sighand(p, &flags);
- if (unlikely(sighand == NULL)) {
- /*
- * This raced with the reaping of the task. The exit cleanup
- * should have removed this timer from the timer queue.
- */
- WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node));
- } else {
- if (timer->it.cpu.firing) {
- /*
- * Prevent signal delivery. The timer cannot be dequeued
- * because it is on the firing list which is not protected
- * by sighand->lock. The delivery path is waiting for
- * the timer lock. So go back, unlock and retry.
- */
- timer->it.cpu.firing = false;
+ if (likely(p)) {
+ if (posix_cpu_timer_on_expiry_list(timer))
ret = TIMER_RETRY;
- } else {
+ else
disarm_timer(timer, p);
- }
unlock_task_sighand(p, &flags);
}
-out:
- rcu_read_unlock();
-
if (!ret) {
- put_pid(ctmr->pid);
+ put_pid(timer->it.cpu.pid);
timer->it_status = POSIX_TIMER_DISARMED;
}
return ret;
@@ -626,21 +721,17 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags,
clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock);
struct cpu_timer *ctmr = &timer->it.cpu;
u64 old_expires, new_expires, now;
- struct sighand_struct *sighand;
struct task_struct *p;
unsigned long flags;
int ret = 0;
- rcu_read_lock();
- p = cpu_timer_task_rcu(timer);
- if (!p) {
- /*
- * If p has just been reaped, we can no
- * longer get any information about it at all.
- */
- rcu_read_unlock();
+ p = timer_lock_sighand(timer, &flags);
+ /*
+ * If p has just been reaped, we can no longer get any information about
+ * it at all.
+ */
+ if (!p)
return -ESRCH;
- }
/*
* Use the to_ktime conversion because that clamps the maximum
@@ -648,31 +739,10 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags,
*/
new_expires = ktime_to_ns(timespec64_to_ktime(new->it_value));
- /*
- * Protect against sighand release/switch in exit/exec and p->cpu_timers
- * and p->signal->cpu_timers read/write in arm_timer()
- */
- sighand = lock_task_sighand(p, &flags);
- /*
- * If p has just been reaped, we can no
- * longer get any information about it at all.
- */
- if (unlikely(sighand == NULL)) {
- rcu_read_unlock();
- return -ESRCH;
- }
-
/* Retrieve the current expiry time before disarming the timer */
old_expires = cpu_timer_getexpires(ctmr);
- if (unlikely(timer->it.cpu.firing)) {
- /*
- * Prevent signal delivery. The timer cannot be dequeued
- * because it is on the firing list which is not protected
- * by sighand->lock. The delivery path is waiting for
- * the timer lock. So go back, unlock and retry.
- */
- timer->it.cpu.firing = false;
+ if (posix_cpu_timer_on_expiry_list(timer)) {
ret = TIMER_RETRY;
} else {
cpu_timer_dequeue(ctmr);
@@ -698,7 +768,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags,
/* Retry if the timer expiry is running concurrently */
if (unlikely(ret)) {
unlock_task_sighand(p, &flags);
- goto out;
+ return ret;
}
/* Convert relative expiry time to absolute */
@@ -733,8 +803,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags,
*/
if (!sigev_none && new_expires && now >= new_expires)
cpu_timer_fire(timer);
-out:
- rcu_read_unlock();
return ret;
}
@@ -1018,19 +1086,12 @@ static void check_process_timers(struct task_struct *tsk,
static bool posix_cpu_timer_rearm(struct k_itimer *timer)
{
clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock);
- struct sighand_struct *sighand;
struct task_struct *p;
unsigned long flags;
u64 now;
- guard(rcu)();
- p = cpu_timer_task_rcu(timer);
- if (!p)
- return true;
-
- /* Protect timer list r/w in arm_timer() */
- sighand = lock_task_sighand(p, &flags);
- if (unlikely(sighand == NULL))
+ p = timer_lock_sighand(timer, &flags);
+ if (unlikely(!p))
return true;
/*
@@ -1300,8 +1361,11 @@ static void handle_posix_cpu_timers(struct task_struct *tsk)
unsigned long flags, start;
LIST_HEAD(firing);
- if (!lock_task_sighand(tsk, &flags))
- return;
+ /*
+ * tsk is current and ->sighand is stable, see the
+ * tsk->exit_state check in run_posix_cpu_timers()
+ */
+ spin_lock_irqsave(&tsk->sighand->siglock, flags);
do {
/*
@@ -1361,7 +1425,7 @@ static void handle_posix_cpu_timers(struct task_struct *tsk)
* that gets the timer lock before we do will give it up and
* spin until we've taken care of that timer below.
*/
- unlock_task_sighand(tsk, &flags);
+ spin_unlock_irqrestore(&tsk->sighand->siglock, flags);
/*
* Now that all the timers on our list have the firing flag,
diff --git a/kernel/time/tick-broadcast.c b/kernel/time/tick-broadcast.c
index 115e0bf01276..bda3d2391a60 100644
--- a/kernel/time/tick-broadcast.c
+++ b/kernel/time/tick-broadcast.c
@@ -165,23 +165,31 @@ static bool tick_set_oneshot_wakeup_device(struct clock_event_device *newdev,
*/
void tick_install_broadcast_device(struct clock_event_device *dev, int cpu)
{
- struct clock_event_device *cur = tick_broadcast_device.evtdev;
+ struct clock_event_device *cur;
- if (tick_set_oneshot_wakeup_device(dev, cpu))
- return;
+ scoped_guard(raw_spinlock_irqsave, &tick_broadcast_lock) {
- if (!tick_check_broadcast_device(cur, dev))
- return;
+ if (tick_set_oneshot_wakeup_device(dev, cpu))
+ return;
- if (!try_module_get(dev->owner))
- return;
+ cur = tick_broadcast_device.evtdev;
+ if (!tick_check_broadcast_device(cur, dev))
+ return;
- clockevents_exchange_device(cur, dev);
+ if (!try_module_get(dev->owner))
+ return;
+
+ __clockevents_exchange_device(cur, dev);
+ if (cur)
+ cur->event_handler = clockevents_handle_noop;
+ WRITE_ONCE(tick_broadcast_device.evtdev, dev);
+ if (!cpumask_empty(tick_broadcast_mask))
+ tick_broadcast_start_periodic(dev);
+ }
+
+ /* Module release must be outside of the lock */
if (cur)
- cur->event_handler = clockevents_handle_noop;
- tick_broadcast_device.evtdev = dev;
- if (!cpumask_empty(tick_broadcast_mask))
- tick_broadcast_start_periodic(dev);
+ module_put(cur->owner);
if (!(dev->features & CLOCK_EVT_FEAT_ONESHOT))
return;
@@ -1218,7 +1226,7 @@ int tick_broadcast_oneshot_active(void)
*/
bool tick_broadcast_oneshot_available(void)
{
- struct clock_event_device *bc = tick_broadcast_device.evtdev;
+ struct clock_event_device *bc = READ_ONCE(tick_broadcast_device.evtdev);
return bc ? bc->features & CLOCK_EVT_FEAT_ONESHOT : false;
}
@@ -1226,7 +1234,7 @@ bool tick_broadcast_oneshot_available(void)
#else
int __tick_broadcast_oneshot_control(enum tick_broadcast_state state)
{
- struct clock_event_device *bc = tick_broadcast_device.evtdev;
+ struct clock_event_device *bc = READ_ONCE(tick_broadcast_device.evtdev);
if (!bc || (bc->features & CLOCK_EVT_FEAT_HRTIMER))
return -EBUSY;
diff --git a/kernel/time/tick-internal.h b/kernel/time/tick-internal.h
index 597d816d22e8..65680db95053 100644
--- a/kernel/time/tick-internal.h
+++ b/kernel/time/tick-internal.h
@@ -3,6 +3,7 @@
* tick internal variable and functions used by low/high res code
*/
#include <linux/hrtimer.h>
+#include <linux/hrtimer_bases.h>
#include <linux/tick.h>
#include "timekeeping.h"
@@ -54,6 +55,8 @@ static inline void clockevent_set_state(struct clock_event_device *dev,
}
extern void clockevents_shutdown(struct clock_event_device *dev);
+extern void __clockevents_exchange_device(struct clock_event_device *old,
+ struct clock_event_device *new);
extern void clockevents_exchange_device(struct clock_event_device *old,
struct clock_event_device *new);
extern void clockevents_switch_state(struct clock_event_device *dev,
diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c
index 98a9cae915c0..6c3fea386713 100644
--- a/kernel/time/tick-sched.c
+++ b/kernel/time/tick-sched.c
@@ -828,8 +828,7 @@ static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu)
ts->timer_expires_base = basemono;
/*
- * Keep the periodic tick, when RCU, architecture or irq_work
- * requests it.
+ * Keep the periodic tick, when RCU or irq_work requests it.
* Aside of that, check whether the local timer softirq is
* pending. If so, its a bad idea to call get_next_timer_interrupt(),
* because there is an already expired timer, so it will request
@@ -837,8 +836,8 @@ static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu)
* minimal delta, which brings us back to this place
* immediately. Lather, rinse and repeat...
*/
- if (rcu_needs_cpu() || arch_needs_cpu() ||
- irq_work_needs_cpu() || local_timer_softirq_pending()) {
+ if (rcu_needs_cpu() || irq_work_needs_cpu() ||
+ local_timer_softirq_pending()) {
next_tick = basemono + TICK_NSEC;
} else {
/*
diff --git a/kernel/time/time.c b/kernel/time/time.c
index 0dd63a91e7c5..079ab34f61db 100644
--- a/kernel/time/time.c
+++ b/kernel/time/time.c
@@ -42,6 +42,13 @@
#include <generated/timeconst.h>
#include "timekeeping.h"
+#include "timekeeping_internal.h"
+
+#if defined(CONFIG_64BIT) || defined(CONFIG_COMPAT_32BIT_TIME)
+#define __WANT_OLD_TIME_TYPE_SYSCALL 1
+#endif
+
+static_assert(sizeof(__kernel_old_time_t) == 8 ? IS_ENABLED(__WANT_OLD_TIME_TYPE_SYSCALL) : true);
/*
* The timezone where the local system is located. Used as a default by some
@@ -51,7 +58,7 @@ struct timezone sys_tz;
EXPORT_SYMBOL(sys_tz);
-#ifdef __ARCH_WANT_SYS_TIME
+#if defined(__ARCH_WANT_SYS_TIME) && defined(__WANT_OLD_TIME_TYPE_SYSCALL)
/*
* sys_time() can be implemented in user-level using
@@ -96,7 +103,7 @@ SYSCALL_DEFINE1(stime, __kernel_old_time_t __user *, tptr)
return 0;
}
-#endif /* __ARCH_WANT_SYS_TIME */
+#endif /* __ARCH_WANT_SYS_TIME && __WANT_OLD_TIME_TYPE_SYSCALL */
#ifdef CONFIG_COMPAT_32BIT_TIME
#ifdef __ARCH_WANT_SYS_TIME32
@@ -137,6 +144,7 @@ SYSCALL_DEFINE1(stime32, old_time32_t __user *, tptr)
#endif /* __ARCH_WANT_SYS_TIME32 */
#endif
+#ifdef __WANT_OLD_TIME_TYPE_SYSCALL
SYSCALL_DEFINE2(gettimeofday, struct __kernel_old_timeval __user *, tv,
struct timezone __user *, tz)
{
@@ -154,6 +162,7 @@ SYSCALL_DEFINE2(gettimeofday, struct __kernel_old_timeval __user *, tv,
}
return 0;
}
+#endif /* __WANT_OLD_TIME_TYPE_SYSCALL */
/*
* In case for some reason the CMOS clock has not already been running
@@ -203,6 +212,9 @@ SYSCALL_DEFINE2(settimeofday, struct __kernel_old_timeval __user *, tv,
struct timezone new_tz;
if (tv) {
+ if (!IS_ENABLED(__WANT_OLD_TIME_TYPE_SYSCALL))
+ return -EINVAL;
+
if (get_user(new_ts.tv_sec, &tv->tv_sec) ||
get_user(new_ts.tv_nsec, &tv->tv_usec))
return -EFAULT;
@@ -220,7 +232,7 @@ SYSCALL_DEFINE2(settimeofday, struct __kernel_old_timeval __user *, tv,
return do_sys_settimeofday64(tv ? &new_ts : NULL, tz ? &new_tz : NULL);
}
-#ifdef CONFIG_COMPAT
+#ifdef CONFIG_COMPAT_32BIT_TIME
COMPAT_SYSCALL_DEFINE2(gettimeofday, struct old_timeval32 __user *, tv,
struct timezone __user *, tz)
{
@@ -239,7 +251,9 @@ COMPAT_SYSCALL_DEFINE2(gettimeofday, struct old_timeval32 __user *, tv,
return 0;
}
+#endif /* CONFIG_COMPAT_32BIT_TIME */
+#ifdef CONFIG_COMPAT
COMPAT_SYSCALL_DEFINE2(settimeofday, struct old_timeval32 __user *, tv,
struct timezone __user *, tz)
{
@@ -247,6 +261,9 @@ COMPAT_SYSCALL_DEFINE2(settimeofday, struct old_timeval32 __user *, tv,
struct timezone new_tz;
if (tv) {
+ if (!IS_ENABLED(CONFIG_COMPAT_32BIT_TIME))
+ return -EINVAL;
+
if (get_user(new_ts.tv_sec, &tv->tv_sec) ||
get_user(new_ts.tv_nsec, &tv->tv_usec))
return -EFAULT;
@@ -263,7 +280,7 @@ COMPAT_SYSCALL_DEFINE2(settimeofday, struct old_timeval32 __user *, tv,
return do_sys_settimeofday64(tv ? &new_ts : NULL, tz ? &new_tz : NULL);
}
-#endif
+#endif /* CONFIG_COMPAT */
#ifdef CONFIG_64BIT
SYSCALL_DEFINE1(adjtimex, struct __kernel_timex __user *, txc_p)
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index b1b5ec43c0f2..ea2e6e55f37b 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -339,7 +339,6 @@ static inline void clocksource_enable_inline_read(void) { }
static void tk_setup_internals(struct timekeeper *tk, struct clocksource *clock)
{
u64 interval;
- u64 tmp, ntpinterval;
struct clocksource *old_clock;
++tk->cs_was_changed_seq;
@@ -353,20 +352,16 @@ static void tk_setup_internals(struct timekeeper *tk, struct clocksource *clock)
tk->tkr_raw.cycle_last = tk->tkr_mono.cycle_last;
/* Do the ns -> cycle conversion first, using original mult */
- tmp = NTP_INTERVAL_LENGTH;
- tmp <<= clock->shift;
- ntpinterval = tmp;
- tmp += clock->mult/2;
- do_div(tmp, clock->mult);
- if (tmp == 0)
- tmp = 1;
-
- interval = (u64) tmp;
+ interval = (u64)NTP_INTERVAL_LENGTH << clock->shift;
+ interval += clock->mult / 2;
+ do_div(interval, clock->mult);
+ if (interval == 0)
+ interval = 1;
+
tk->cycle_interval = interval;
/* Go back from cycles -> shifted ns */
tk->xtime_interval = interval * clock->mult;
- tk->xtime_remainder = ntpinterval - tk->xtime_interval;
tk->raw_interval = interval * clock->mult;
/* if changing clocks, convert xtime_nsec shift units */
@@ -386,7 +381,38 @@ static void tk_setup_internals(struct timekeeper *tk, struct clocksource *clock)
tk->ntp_error = 0;
tk->ntp_error_shift = NTP_SCALE_SHIFT - clock->shift;
- tk->ntp_tick = ntpinterval << tk->ntp_error_shift;
+
+ /*
+ * ntp_tick is the tick length that NTP disciplines (its ±500 PPM
+ * scales only this part), in NTP-shifted ns: the real interval of
+ * a whole number of counter cycles. Because cycle_interval is
+ * rounded to an integer number of cycles, this ntp_tick differs
+ * from the true intended 1/HZ tick length by up to half a cycle
+ * period.
+ */
+ tk->ntp_tick = (u64)tk->xtime_interval << tk->ntp_error_shift;
+
+ /*
+ * cs_tick_adj is the constant difference between the disciplined
+ * ntp_tick above and the true 1/HZ tick, expressed per-second to
+ * match the ntp_update_frequency() addends and handed to NTP via
+ * ntp_clear() to be explicitly included in its tick_length.
+ *
+ * Worked example: HZ=1000, ACPI PM timer at 3.579545 MHz, which
+ * has 3579.545 cycles in 1ms, rounded to cycle_interval = 3580.
+ *
+ * So ntp_tick is actually 1.000127ms, as that is the amount of
+ * time that 3580 cycles will take at the nominal frequency. This
+ * is the part that NTP disciplines, causing each 3580 counts to
+ * advance the clock by up to NTP's ±500PPM of that amount.
+ *
+ * The "extra" 127ns/tick is what's stored in cs_tick_adj and
+ * applied as a constant correction by ntp_update_frequency() so
+ * that NTP *believes* it's disciplining a 1ms tick.
+ */
+ tk->cs_tick_adj = (s64)tk->ntp_tick -
+ ((s64)NTP_INTERVAL_LENGTH << NTP_SCALE_SHIFT);
+ tk->cs_tick_adj *= NTP_INTERVAL_FREQ;
/*
* The timekeeper keeps its own mult values for the currently
@@ -397,6 +423,7 @@ static void tk_setup_internals(struct timekeeper *tk, struct clocksource *clock)
tk->tkr_raw.mult = clock->mult;
tk->ntp_err_mult = 0;
tk->skip_second_overflow = 0;
+ tk->skew_delta = 0;
tk->cs_id = clock->id;
@@ -803,7 +830,7 @@ static void timekeeping_update_from_shadow(struct tk_data *tkd, unsigned int act
if (action & TK_CLEAR_NTP) {
tk->ntp_error = 0;
- ntp_clear(tk->id);
+ ntp_clear(tk->id, tk->cs_tick_adj);
}
tk_update_leap_state(tk);
@@ -831,7 +858,11 @@ static void timekeeping_update_from_shadow(struct tk_data *tkd, unsigned int act
* the downside that the reader side does not longer benefit from
* the cacheline optimized data layout of the timekeeper and requires
* another indirection.
+ *
+ * Write xtime_sec first so that even if the memcpy() tears the store
+ * data integrity is provided for ktime_get_real_seconds().
*/
+ WRITE_ONCE(tkd->timekeeper.xtime_sec, tk->xtime_sec);
memcpy(&tkd->timekeeper, tk, sizeof(*tk));
write_seqcount_end(&tkd->seq);
}
@@ -1159,11 +1190,11 @@ time64_t ktime_get_real_seconds(void)
unsigned int seq;
if (IS_ENABLED(CONFIG_64BIT))
- return tk->xtime_sec;
+ return READ_ONCE(tk->xtime_sec);
do {
seq = read_seqcount_begin(&tk_core.seq);
- seconds = tk->xtime_sec;
+ seconds = READ_ONCE(tk->xtime_sec);
} while (read_seqcount_retry(&tk_core.seq, seq));
@@ -1185,7 +1216,7 @@ noinstr time64_t __ktime_get_real_seconds(void)
{
struct timekeeper *tk = &tk_core.timekeeper;
- return tk->xtime_sec;
+ return READ_ONCE(tk->xtime_sec);
}
static inline u64 tk_clock_read_snapshot(const struct tk_read_base *tkr,
@@ -1202,10 +1233,21 @@ static inline u64 tk_clock_read_snapshot(const struct tk_read_base *tkr,
/**
* ktime_get_snapshot_id - Simultaneously snapshot a given clock ID with
- * CLOCK_MONOTONIC_RAW and the underlying
+ * the corresponding monotonic raw and the underlying
* clocksource counter value.
* @clock_id: The clock ID to snapshot
* @systime_snapshot: Pointer to struct receiving the system time snapshot
+ *
+ * For the system time keeping clocks (REALTIME, MONOTONIC and BOOTTIME) the
+ * monotonic raw clock is CLOCK_MONOTONIC_RAW. For AUX clocks this is the
+ * monotonic raw clock related to the AUX clock. These AUX clock related
+ * monotonic raw clocks have a strict linear offset to the system time
+ * CLOCK_MONOTONIC_RAW:
+ *
+ * MONOTONIC_RAW(AUX$N) = CLOCK_MONOTONIC_RAW(system) + offset(AUX$N)
+ *
+ * The offset is established when a AUX clock is initialized, but it is
+ * currently not accessible.
*/
void ktime_get_snapshot_id(clockid_t clock_id, struct system_time_snapshot *systime_snapshot)
{
@@ -1512,6 +1554,9 @@ EXPORT_SYMBOL_GPL(ktime_real_to_base_clock);
* @xtstamp: Receives simultaneously captured system and device time
*
* Reads a timestamp from a device and correlates it to system time
+ *
+ * See documentation for ktime_get_snapshot_id() for information about the raw
+ * monotonic time stamp which is used here.
*/
int get_device_system_crosststamp(int (*get_time_fn)
(ktime_t *device_time,
@@ -1522,10 +1567,11 @@ int get_device_system_crosststamp(int (*get_time_fn)
struct system_device_crosststamp *xtstamp)
{
u64 syscnt_cycles, cycles, now, interval_start;
- unsigned int seq, clock_was_set_seq = 0;
ktime_t base_sys, base_raw, *offs;
+ u32 clock_was_set_seq = 0;
u64 nsec_sys, nsec_raw;
u8 cs_was_changed_seq;
+ unsigned int seq;
bool do_interp;
struct timekeeper *tk;
struct tk_data *tkd;
@@ -1897,40 +1943,6 @@ void ktime_get_raw_ts64(struct timespec64 *ts)
EXPORT_SYMBOL(ktime_get_raw_ts64);
/**
- * ktime_get_clock_ts64 - Returns time of a clock in a timespec
- * @id: POSIX clock ID of the clock to read
- * @ts: Pointer to the timespec64 to be set
- *
- * The timestamp is invalidated (@ts->sec is set to -1) if the
- * clock @id is not available.
- */
-void ktime_get_clock_ts64(clockid_t id, struct timespec64 *ts)
-{
- /* Invalidate time stamp */
- ts->tv_sec = -1;
- ts->tv_nsec = 0;
-
- switch (id) {
- case CLOCK_REALTIME:
- ktime_get_real_ts64(ts);
- return;
- case CLOCK_MONOTONIC:
- ktime_get_ts64(ts);
- return;
- case CLOCK_MONOTONIC_RAW:
- ktime_get_raw_ts64(ts);
- return;
- case CLOCK_AUX ... CLOCK_AUX_LAST:
- if (IS_ENABLED(CONFIG_POSIX_AUX_CLOCKS))
- ktime_get_aux_ts64(id, ts);
- return;
- default:
- WARN_ON_ONCE(1);
- }
-}
-EXPORT_SYMBOL_GPL(ktime_get_clock_ts64);
-
-/**
* timekeeping_valid_for_hres - Check if timekeeping is suitable for hres
*/
int timekeeping_valid_for_hres(void)
@@ -2076,7 +2088,12 @@ void __init timekeeping_init(void)
tk_set_wall_to_mono(tks, wall_to_mono);
- timekeeping_update_from_shadow(&tk_core, TK_CLOCK_WAS_SET);
+ /*
+ * Use TK_UPDATE_ALL so the NTP layer picks up the clocksource's
+ * cs_tick_adj via ntp_clear(). Clearing NTP here is otherwise
+ * redundant as ntp_init() already initialised it above.
+ */
+ timekeeping_update_from_shadow(&tk_core, TK_UPDATE_ALL);
}
/* time in seconds when suspend began for persistent clock */
@@ -2390,6 +2407,11 @@ static __always_inline void timekeeping_apply_adjustment(struct timekeeper *tk,
* xtime_nsec_2 = xtime_nsec_1 - offset
* Which simplifies to:
* xtime_nsec -= offset
+ *
+ * When subtracting offset from xtime_nsec, the same amount
+ * (in appropriate units) has to be added to ntp_error, in
+ * order to correctly track the delta between the time
+ * reported in xtime_nsec, and the intended time.
*/
if ((mult_adj > 0) && (tk->tkr_mono.mult + mult_adj < mult_adj)) {
/* NTP adjustment caused clocksource mult overflow */
@@ -2400,6 +2422,7 @@ static __always_inline void timekeeping_apply_adjustment(struct timekeeper *tk,
tk->tkr_mono.mult += mult_adj;
tk->xtime_interval += interval;
tk->tkr_mono.xtime_nsec -= offset;
+ tk->ntp_error += offset << tk->ntp_error_shift;
}
/*
@@ -2409,18 +2432,27 @@ static __always_inline void timekeeping_apply_adjustment(struct timekeeper *tk,
static void timekeeping_adjust(struct timekeeper *tk, s64 offset)
{
u64 ntp_tl = ntp_tick_length(tk->id);
+ s64 skew = ntp_get_skew_delta(tk->id);
u32 mult;
/*
- * Determine the multiplier from the current NTP tick length.
- * Avoid expensive division when the tick length doesn't change.
+ * Determine the multiplier from the current NTP tick length plus
+ * skew_delta. The skew biases mult so that ±1 dithering can deliver
+ * the time_offset slew rate. Recompute when either changes.
*/
- if (likely(tk->ntp_tick == ntp_tl)) {
+ if (likely(tk->ntp_tick == ntp_tl && tk->skew_delta == skew)) {
+ /* Revert to the base mult rate. */
mult = tk->tkr_mono.mult - tk->ntp_err_mult;
} else {
tk->ntp_tick = ntp_tl;
- mult = div64_u64((tk->ntp_tick >> tk->ntp_error_shift) -
- tk->xtime_remainder, tk->cycle_interval);
+ tk->skew_delta = skew;
+ /*
+ * skew_delta is stored pre-divided by HZ (matching time_offset);
+ * scale it back up to the full per-tick rate for the mult bias.
+ */
+ skew *= NTP_INTERVAL_FREQ;
+ mult = div64_u64((tk->ntp_tick + skew) >> tk->ntp_error_shift,
+ tk->cycle_interval);
}
/*
@@ -2545,8 +2577,25 @@ static u64 logarithmic_accumulation(struct timekeeper *tk, u64 offset,
/* Accumulate error between NTP and clock interval */
tk->ntp_error += tk->ntp_tick << shift;
- tk->ntp_error -= (tk->xtime_interval + tk->xtime_remainder) <<
- (tk->ntp_error_shift + shift);
+ tk->ntp_error -= tk->xtime_interval << (tk->ntp_error_shift + shift);
+
+ /*
+ * When skewing, do so by adjusting ntp_error to impart an extra
+ * target delta into ntp_error per tick, limited to what can be
+ * drained from time_offset / time_adjust to avoid overshoot.
+ *
+ * The base 'mult' value was calculated with the skew taken into
+ * account, such that the per-tick choice of 'mult' vs. 'mult+1'
+ * allows for the desired effective rate and ntp_error does not
+ * grow unbounded.
+ *
+ * Once the full desired phase offset is delivered, any remaining
+ * skew imparted by the adjusted 'mult', accounted above, remains
+ * in ntp_error and will be compensated by the dithering over time.
+ */
+ if (tk->skew_delta)
+ tk->ntp_error += ntp_drain_skew(tk->id, tk->skew_delta << shift,
+ shift) * NTP_INTERVAL_FREQ;
return offset;
}
@@ -2795,7 +2844,7 @@ void do_timer(unsigned long ticks)
*
* Called from hrtimer_interrupt() or retrigger_next_event()
*/
-ktime_t ktime_get_update_offsets_now(unsigned int *cwsseq, ktime_t *offs_real,
+ktime_t ktime_get_update_offsets_now(u32 *cwsseq, ktime_t *offs_real,
ktime_t *offs_boot, ktime_t *offs_tai)
{
struct timekeeper *tk = &tk_core.timekeeper;
@@ -2943,10 +2992,12 @@ static int __do_adjtimex(struct tk_data *tkd, struct __kernel_timex *txc,
return ret;
add_device_randomness(txc, sizeof(*txc));
- if (!aux_clock)
+ if (!aux_clock) {
ktime_get_real_ts64(&ts);
- else
- tk_get_aux_ts64(tkd->timekeeper.id, &ts);
+ } else {
+ if (!tk_get_aux_ts64(tkd->timekeeper.id, &ts))
+ return -ENODEV;
+ }
add_device_randomness(&ts, sizeof(ts));
@@ -3051,7 +3102,7 @@ static inline unsigned int clockid_to_tkid(unsigned int id)
static inline struct tk_data *aux_get_tk_data(clockid_t id)
{
- if (!clockid_aux_valid(id))
+ if (!clockid_is_aux_clock(id))
return NULL;
return &timekeeper_data[clockid_to_tkid(id)];
}
@@ -3146,7 +3197,7 @@ EXPORT_SYMBOL_GPL(ktime_get_aux_ts64);
static int aux_get_res(clockid_t id, struct timespec64 *tp)
{
- if (!clockid_aux_valid(id))
+ if (!clockid_is_aux_clock(id))
return -ENODEV;
tp->tv_sec = aux_clock_resolution_ns() / NSEC_PER_SEC;
@@ -3313,7 +3364,9 @@ static const struct attribute_group aux_clock_enable_attr_group = {
static int __init tk_aux_sysfs_init(void)
{
struct kobject *auxo, *tko = kobject_create_and_add("time", kernel_kobj);
+ struct kobject *clks[MAX_AUX_CLOCKS];
int ret = -ENOMEM;
+ int i;
if (!tko)
return ret;
@@ -3322,21 +3375,28 @@ static int __init tk_aux_sysfs_init(void)
if (!auxo)
goto err_clean;
- for (int i = 0; i < MAX_AUX_CLOCKS; i++) {
+ for (i = 0; i < MAX_AUX_CLOCKS; i++) {
char id[2] = { [0] = '0' + i, };
- struct kobject *clk = kobject_create_and_add(id, auxo);
+ clks[i] = kobject_create_and_add(id, auxo);
- if (!clk) {
+ if (!clks[i]) {
ret = -ENOMEM;
- goto err_clean;
+ goto err_clks;
}
- ret = sysfs_create_group(clk, &aux_clock_enable_attr_group);
+ ret = sysfs_create_group(clks[i], &aux_clock_enable_attr_group);
if (ret)
- goto err_clean;
+ goto err_clk;
}
return 0;
+err_clk:
+ kobject_put(clks[i]);
+err_clks:
+ while (--i >= 0) {
+ sysfs_remove_group(clks[i], &aux_clock_enable_attr_group);
+ kobject_put(clks[i]);
+ }
err_clean:
kobject_put(auxo);
kobject_put(tko);
diff --git a/kernel/time/timekeeping.h b/kernel/time/timekeeping.h
index 198d0608db74..4201f9e90813 100644
--- a/kernel/time/timekeeping.h
+++ b/kernel/time/timekeeping.h
@@ -4,7 +4,7 @@
/*
* Internal interfaces for kernel/time/
*/
-extern ktime_t ktime_get_update_offsets_now(unsigned int *cwsseq,
+extern ktime_t ktime_get_update_offsets_now(u32 *cwsseq,
ktime_t *offs_real,
ktime_t *offs_boot,
ktime_t *offs_tai);
diff --git a/kernel/time/timekeeping_internal.h b/kernel/time/timekeeping_internal.h
index 973ede670a36..6d719b8e5ea2 100644
--- a/kernel/time/timekeeping_internal.h
+++ b/kernel/time/timekeeping_internal.h
@@ -6,6 +6,8 @@
#include <linux/spinlock.h>
#include <linux/time.h>
+struct timekeeper;
+
/*
* timekeeping debug functions
*/
@@ -48,4 +50,23 @@ void timekeeper_unlock_irqrestore(unsigned long flags);
/* NTP specific interface to access the current seconds value */
long ktime_get_ntp_seconds(unsigned int id);
+#ifdef CONFIG_GENERIC_GETTIMEOFDAY
+
+extern void update_vsyscall(struct timekeeper *tk);
+extern void update_vsyscall_tz(void);
+extern void vdso_time_update_aux(struct timekeeper *tk);
+
+#else
+
+static inline void update_vsyscall(struct timekeeper *tk)
+{
+}
+static inline void update_vsyscall_tz(void)
+{
+}
+static inline void vdso_time_update_aux(struct timekeeper *tk)
+{
+}
+#endif
+
#endif /* _TIMEKEEPING_INTERNAL_H */
diff --git a/kernel/time/timer.c b/kernel/time/timer.c
index 655a8c6cd84d..ae9abf14688e 100644
--- a/kernel/time/timer.c
+++ b/kernel/time/timer.c
@@ -2492,6 +2492,7 @@ static void migrate_timer_list(struct timer_base *new_base, struct hlist_head *h
timer = hlist_entry(head->first, struct timer_list, entry);
detach_timer(timer, false);
timer->flags = (timer->flags & ~TIMER_BASEMASK) | cpu;
+ debug_timer_activate(timer);
internal_add_timer(new_base, timer);
}
}
diff --git a/kernel/time/timer_list.c b/kernel/time/timer_list.c
index 514802def1e0..0406bf4488e9 100644
--- a/kernel/time/timer_list.c
+++ b/kernel/time/timer_list.c
@@ -20,7 +20,7 @@
struct timer_list_iter {
int cpu;
bool second_pass;
- u64 now;
+ ktime_t now;
};
/*
@@ -44,19 +44,19 @@ static void SEQ_printf(struct seq_file *m, const char *fmt, ...)
static void
print_timer(struct seq_file *m, struct hrtimer *taddr, struct hrtimer *timer,
- int idx, u64 now)
+ int idx, ktime_t now)
{
SEQ_printf(m, " #%d: <%p>, %ps", idx, taddr, ACCESS_PRIVATE(timer, function));
SEQ_printf(m, ", S:%02x", timer->is_queued);
SEQ_printf(m, "\n");
- SEQ_printf(m, " # expires at %Lu-%Lu nsecs [in %Ld to %Ld nsecs]\n",
- (unsigned long long)ktime_to_ns(hrtimer_get_softexpires(timer)),
- (unsigned long long)ktime_to_ns(hrtimer_get_expires(timer)),
- (long long)(ktime_to_ns(hrtimer_get_softexpires(timer)) - now),
- (long long)(ktime_to_ns(hrtimer_get_expires(timer)) - now));
+ SEQ_printf(m, " # expires at %lld-%lld nsecs [in %lld to %lld nsecs]\n",
+ (long long)hrtimer_get_softexpires(timer),
+ (long long)hrtimer_get_expires(timer),
+ (long long)ktime_sub(hrtimer_get_softexpires(timer), now),
+ (long long)ktime_sub(hrtimer_get_expires(timer), now));
}
-static void print_active_timers(struct seq_file *m, struct hrtimer_clock_base *base, u64 now)
+static void print_active_timers(struct seq_file *m, struct hrtimer_clock_base *base, ktime_t now)
{
struct timerqueue_linked_node *curr;
struct hrtimer *timer, tmp;
@@ -94,21 +94,21 @@ next_one:
}
static void
-print_base(struct seq_file *m, struct hrtimer_clock_base *base, u64 now)
+print_base(struct seq_file *m, struct hrtimer_clock_base *base, ktime_t now)
{
SEQ_printf(m, " .base: %p\n", base);
SEQ_printf(m, " .index: %d\n", base->index);
SEQ_printf(m, " .resolution: %u nsecs\n", hrtimer_resolution);
#ifdef CONFIG_HIGH_RES_TIMERS
- SEQ_printf(m, " .offset: %Ld nsecs\n",
+ SEQ_printf(m, " .offset: %lld nsecs\n",
(long long) base->offset);
#endif
SEQ_printf(m, "active timers:\n");
- print_active_timers(m, base, now + ktime_to_ns(base->offset));
+ print_active_timers(m, base, ktime_add(now, base->offset));
}
-static void print_cpu(struct seq_file *m, int cpu, u64 now)
+static void print_cpu(struct seq_file *m, int cpu, ktime_t now)
{
struct hrtimer_cpu_base *cpu_base = &per_cpu(hrtimer_bases, cpu);
int i;
@@ -118,15 +118,17 @@ static void print_cpu(struct seq_file *m, int cpu, u64 now)
SEQ_printf(m, " clock %d:\n", i);
print_base(m, cpu_base->clock_base + i, now);
}
-#define P(x) \
- SEQ_printf(m, " .%-15s: %Lu\n", #x, \
- (unsigned long long)(cpu_base->x))
-#define P_ns(x) \
- SEQ_printf(m, " .%-15s: %Lu nsecs\n", #x, \
- (unsigned long long)(ktime_to_ns(cpu_base->x)))
+
+#define DIAG_READ(x) data_race(READ_ONCE(x))
+
+#define P(x) \
+ SEQ_printf(m, " .%-15s: %llu\n", #x, \
+ (unsigned long long)DIAG_READ(cpu_base->x))
+#define P_ktime(x) \
+ SEQ_printf(m, " .%-15s: %lld nsecs\n", #x, (long long)DIAG_READ(cpu_base->x))
#ifdef CONFIG_HIGH_RES_TIMERS
- P_ns(expires_next);
+ P_ktime(expires_next);
P(hres_active);
P(nr_events);
P(nr_retries);
@@ -134,38 +136,39 @@ static void print_cpu(struct seq_file *m, int cpu, u64 now)
P(max_hang_time);
#endif
#undef P
-#undef P_ns
+#undef P_ktime
#ifdef CONFIG_TICK_ONESHOT
# define P(x) \
- SEQ_printf(m, " .%-15s: %Lu\n", #x, \
- (unsigned long long)(ts->x))
-# define P_ns(x) \
- SEQ_printf(m, " .%-15s: %Lu nsecs\n", #x, \
- (unsigned long long)(ktime_to_ns(ts->x)))
+ SEQ_printf(m, " .%-15s: %llu\n", #x, \
+ (unsigned long long)DIAG_READ(ts->x))
+# define P_ktime(x) \
+ SEQ_printf(m, " .%-15s: %lld nsecs\n", #x, (long long)DIAG_READ(ts->x))
# define P_flag(x, f) \
- SEQ_printf(m, " .%-15s: %d\n", #x, !!(ts->flags & (f)))
+ SEQ_printf(m, " .%-15s: %d\n", #x, !!(DIAG_READ(ts->flags) & (f)))
{
struct tick_sched *ts = tick_get_tick_sched(cpu);
P_flag(nohz, TS_FLAG_NOHZ);
P_flag(highres, TS_FLAG_HIGHRES);
- P_ns(last_tick);
+ P_ktime(last_tick);
P_flag(tick_stopped, TS_FLAG_STOPPED);
P(idle_calls);
P(idle_sleeps);
- P_ns(idle_entrytime);
- P_ns(idle_waketime);
+ P_ktime(idle_entrytime);
+ P_ktime(idle_waketime);
P(last_jiffies);
P(next_timer);
- P_ns(idle_expires);
- SEQ_printf(m, "jiffies: %Lu\n",
+ P_ktime(idle_expires);
+ SEQ_printf(m, "jiffies: %llu\n",
(unsigned long long)jiffies);
}
#endif
#undef P
-#undef P_ns
+#undef P_ktime
+#undef P_flag
+#undef DIAG_READ
SEQ_printf(m, "\n");
}
@@ -196,8 +199,7 @@ print_tickdevice(struct seq_file *m, struct tick_device *td, int cpu)
SEQ_printf(m, " mult: %u\n", dev->mult);
SEQ_printf(m, " shift: %u\n", dev->shift);
SEQ_printf(m, " mode: %d\n", clockevent_get_state(dev));
- SEQ_printf(m, " next_event: %Ld nsecs\n",
- (unsigned long long) ktime_to_ns(dev->next_event));
+ SEQ_printf(m, " next_event: %lld nsecs\n", (long long)dev->next_event);
SEQ_printf(m, " set_next_event: %ps\n", dev->set_next_event);
@@ -250,17 +252,17 @@ static void timer_list_show_tickdevices_header(struct seq_file *m)
}
#endif
-static inline void timer_list_header(struct seq_file *m, u64 now)
+static inline void timer_list_header(struct seq_file *m, ktime_t now)
{
SEQ_printf(m, "Timer List Version: v0.11\n");
SEQ_printf(m, "HRTIMER_MAX_CLOCK_BASES: %d\n", HRTIMER_MAX_CLOCK_BASES);
- SEQ_printf(m, "now at %Ld nsecs\n", (unsigned long long)now);
+ SEQ_printf(m, "now at %lld nsecs\n", (long long)now);
SEQ_printf(m, "\n");
}
void sysrq_timer_list_show(void)
{
- u64 now = ktime_to_ns(ktime_get());
+ ktime_t now = ktime_get();
int cpu;
timer_list_header(NULL, now);
@@ -318,7 +320,7 @@ static void *timer_list_start(struct seq_file *file, loff_t *offset)
struct timer_list_iter *iter = file->private;
if (!*offset)
- iter->now = ktime_to_ns(ktime_get());
+ iter->now = ktime_get();
iter->cpu = -1;
iter->second_pass = false;
return move_iter(iter, *offset);
diff --git a/kernel/time/timer_migration.c b/kernel/time/timer_migration.c
index 806c23cf71fc..059d43355e65 100644
--- a/kernel/time/timer_migration.c
+++ b/kernel/time/timer_migration.c
@@ -1847,8 +1847,10 @@ static int tmigr_setup_groups(struct tmigr_hierarchy *hier, unsigned int cpu,
}
/* Assert single root without parent */
- if (WARN_ON_ONCE(i >= tmigr_hierarchy_levels))
+ if (WARN_ON_ONCE(i >= tmigr_hierarchy_levels)) {
+ kfree(stack);
return -EINVAL;
+ }
for (; i >= start_lvl; i--) {
group = stack[i];
diff --git a/kernel/time/timer_migration.h b/kernel/time/timer_migration.h
index 31735dd52327..c9c1c29f011d 100644
--- a/kernel/time/timer_migration.h
+++ b/kernel/time/timer_migration.h
@@ -103,7 +103,7 @@ struct tmigr_group {
* before the timer migration hierarchy hotplug callback is
* reached. During this phase, the CPU has to handle the
* global timers on its own and must not act as a migrator.
-
+ *
* @idle: Indicates whether the CPU is idle in the timer migration
* hierarchy
* @remote: Is set when timers of the CPU are expired remotely
diff --git a/kernel/torture.c b/kernel/torture.c
index 77cb3589b19f..8c4e6b2fe8ba 100644
--- a/kernel/torture.c
+++ b/kernel/torture.c
@@ -577,6 +577,8 @@ static int torture_shuffle(void *arg)
*/
int torture_shuffle_init(long shuffint)
{
+ int ret;
+
shuffle_interval = shuffint;
shuffle_idle_cpu = -1;
@@ -587,7 +589,10 @@ int torture_shuffle_init(long shuffint)
}
/* Create the shuffler thread */
- return torture_create_kthread(torture_shuffle, NULL, shuffler_task);
+ ret = torture_create_kthread(torture_shuffle, NULL, shuffler_task);
+ if (ret)
+ free_cpumask_var(shuffle_tmp_mask);
+ return ret;
}
EXPORT_SYMBOL_GPL(torture_shuffle_init);
diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig
index 084f34dc6c9f..0ab5916575a9 100644
--- a/kernel/trace/Kconfig
+++ b/kernel/trace/Kconfig
@@ -779,6 +779,18 @@ config PROBE_EVENTS_BTF_ARGS
kernel function entry or a tracepoint.
This is available only if BTF (BPF Type Format) support is enabled.
+config PROBE_EVENTS_DUMP_FETCHARG
+ bool "Dump of dynamic probe event fetch-arguments"
+ depends on PROBE_EVENTS
+ default n
+ help
+ This shows the dump of fetch-arguments of dynamic probe events
+ alongside their event definitions in the dynamic_events file
+ as comment lines. This is useful to debug the probe events.
+ Since this exposes the raw values in the dynamic_events file,
+ it might be a security risk. Only enable it if you need to debug
+ probe events themselves.
+
config KPROBE_EVENTS
depends on KPROBES
depends on HAVE_REGS_AND_STACK_ACCESS_API
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 82f8feea6931..29260951aa87 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -221,7 +221,7 @@ const struct bpf_func_proto bpf_probe_read_user_proto = {
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
- .arg2_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg2_type = ARG_MEM_SIZE_OR_ZERO,
.arg3_type = ARG_ANYTHING,
};
@@ -258,7 +258,7 @@ const struct bpf_func_proto bpf_probe_read_user_str_proto = {
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
- .arg2_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg2_type = ARG_MEM_SIZE_OR_ZERO,
.arg3_type = ARG_ANYTHING,
};
@@ -273,7 +273,7 @@ const struct bpf_func_proto bpf_probe_read_kernel_proto = {
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
- .arg2_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg2_type = ARG_MEM_SIZE_OR_ZERO,
.arg3_type = ARG_ANYTHING,
};
@@ -308,7 +308,7 @@ const struct bpf_func_proto bpf_probe_read_kernel_str_proto = {
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
- .arg2_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg2_type = ARG_MEM_SIZE_OR_ZERO,
.arg3_type = ARG_ANYTHING,
};
@@ -328,7 +328,7 @@ static const struct bpf_func_proto bpf_probe_read_compat_proto = {
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
- .arg2_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg2_type = ARG_MEM_SIZE_OR_ZERO,
.arg3_type = ARG_ANYTHING,
};
@@ -347,7 +347,7 @@ static const struct bpf_func_proto bpf_probe_read_compat_str_proto = {
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
- .arg2_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg2_type = ARG_MEM_SIZE_OR_ZERO,
.arg3_type = ARG_ANYTHING,
};
#endif /* CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE */
@@ -383,7 +383,7 @@ static const struct bpf_func_proto bpf_probe_write_user_proto = {
.ret_type = RET_INTEGER,
.arg1_type = ARG_ANYTHING,
.arg2_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg3_type = ARG_CONST_SIZE,
+ .arg3_type = ARG_MEM_SIZE,
};
#define MAX_TRACE_PRINTK_VARARGS 3
@@ -418,7 +418,7 @@ static const struct bpf_func_proto bpf_trace_printk_proto = {
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg2_type = ARG_CONST_SIZE,
+ .arg2_type = ARG_MEM_SIZE,
};
static void __set_printk_clr_event(struct work_struct *work)
@@ -474,9 +474,9 @@ static const struct bpf_func_proto bpf_trace_vprintk_proto = {
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg2_type = ARG_CONST_SIZE,
+ .arg2_type = ARG_MEM_SIZE,
.arg3_type = ARG_PTR_TO_MEM | PTR_MAYBE_NULL | MEM_RDONLY,
- .arg4_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg4_type = ARG_MEM_SIZE_OR_ZERO,
};
const struct bpf_func_proto *bpf_get_trace_vprintk_proto(void)
@@ -518,9 +518,9 @@ static const struct bpf_func_proto bpf_seq_printf_proto = {
.arg1_type = ARG_PTR_TO_BTF_ID,
.arg1_btf_id = &btf_seq_file_ids[0],
.arg2_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg3_type = ARG_CONST_SIZE,
+ .arg3_type = ARG_MEM_SIZE,
.arg4_type = ARG_PTR_TO_MEM | PTR_MAYBE_NULL | MEM_RDONLY,
- .arg5_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg5_type = ARG_MEM_SIZE_OR_ZERO,
};
BPF_CALL_3(bpf_seq_write, struct seq_file *, m, const void *, data, u32, len)
@@ -535,7 +535,7 @@ static const struct bpf_func_proto bpf_seq_write_proto = {
.arg1_type = ARG_PTR_TO_BTF_ID,
.arg1_btf_id = &btf_seq_file_ids[0],
.arg2_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg3_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg3_type = ARG_MEM_SIZE_OR_ZERO,
};
BPF_CALL_4(bpf_seq_printf_btf, struct seq_file *, m, struct btf_ptr *, ptr,
@@ -559,7 +559,7 @@ static const struct bpf_func_proto bpf_seq_printf_btf_proto = {
.arg1_type = ARG_PTR_TO_BTF_ID,
.arg1_btf_id = &btf_seq_file_ids[0],
.arg2_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg3_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg3_type = ARG_MEM_SIZE_OR_ZERO,
.arg4_type = ARG_ANYTHING,
};
@@ -633,7 +633,7 @@ static const struct bpf_func_proto bpf_perf_event_read_value_proto = {
.arg1_type = ARG_CONST_MAP_PTR,
.arg2_type = ARG_ANYTHING,
.arg3_type = ARG_PTR_TO_UNINIT_MEM,
- .arg4_type = ARG_CONST_SIZE,
+ .arg4_type = ARG_MEM_SIZE,
};
const struct bpf_func_proto *bpf_get_perf_event_read_value_proto(void)
@@ -730,7 +730,7 @@ static const struct bpf_func_proto bpf_perf_event_output_proto = {
.arg2_type = ARG_CONST_MAP_PTR,
.arg3_type = ARG_ANYTHING,
.arg4_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg5_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg5_type = ARG_MEM_SIZE_OR_ZERO,
};
static DEFINE_PER_CPU(int, bpf_event_output_nest_level);
@@ -996,7 +996,7 @@ static const struct bpf_func_proto bpf_d_path_proto = {
.arg1_type = ARG_PTR_TO_BTF_ID,
.arg1_btf_id = &bpf_d_path_btf_ids[0],
.arg2_type = ARG_PTR_TO_MEM | MEM_WRITE,
- .arg3_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg3_type = ARG_MEM_SIZE_OR_ZERO,
.allowed = bpf_d_path_allowed,
};
@@ -1053,9 +1053,9 @@ const struct bpf_func_proto bpf_snprintf_btf_proto = {
.gpl_only = false,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_MEM | MEM_WRITE,
- .arg2_type = ARG_CONST_SIZE,
+ .arg2_type = ARG_MEM_SIZE,
.arg3_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg4_type = ARG_CONST_SIZE,
+ .arg4_type = ARG_MEM_SIZE,
.arg5_type = ARG_ANYTHING,
};
@@ -1218,7 +1218,7 @@ const struct bpf_func_proto bpf_get_branch_snapshot_proto = {
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
- .arg2_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg2_type = ARG_MEM_SIZE_OR_ZERO,
};
BPF_CALL_3(get_func_arg, void *, ctx, u32, n, u64 *, value)
@@ -1421,7 +1421,7 @@ static const struct bpf_func_proto bpf_perf_event_output_proto_tp = {
.arg2_type = ARG_CONST_MAP_PTR,
.arg3_type = ARG_ANYTHING,
.arg4_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg5_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg5_type = ARG_MEM_SIZE_OR_ZERO,
};
BPF_CALL_3(bpf_get_stackid_tp, void *, tp_buff, struct bpf_map *, map,
@@ -1462,7 +1462,7 @@ static const struct bpf_func_proto bpf_get_stack_proto_tp = {
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_PTR_TO_UNINIT_MEM,
- .arg3_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg3_type = ARG_MEM_SIZE_OR_ZERO,
.arg4_type = ARG_ANYTHING,
};
@@ -1524,12 +1524,12 @@ clear:
}
static const struct bpf_func_proto bpf_perf_prog_read_value_proto = {
- .func = bpf_perf_prog_read_value,
- .gpl_only = true,
- .ret_type = RET_INTEGER,
- .arg1_type = ARG_PTR_TO_CTX,
- .arg2_type = ARG_PTR_TO_UNINIT_MEM,
- .arg3_type = ARG_CONST_SIZE,
+ .func = bpf_perf_prog_read_value,
+ .gpl_only = true,
+ .ret_type = RET_INTEGER,
+ .arg1_type = ARG_PTR_TO_CTX,
+ .arg2_type = ARG_PTR_TO_UNINIT_MEM,
+ .arg3_type = ARG_MEM_SIZE,
};
BPF_CALL_4(bpf_read_branch_records, struct bpf_perf_event_data_kern *, ctx,
@@ -1566,7 +1566,7 @@ static const struct bpf_func_proto bpf_read_branch_records_proto = {
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_PTR_TO_MEM_OR_NULL | MEM_WRITE,
- .arg3_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg3_type = ARG_MEM_SIZE_OR_ZERO,
.arg4_type = ARG_ANYTHING,
};
@@ -1646,7 +1646,7 @@ static const struct bpf_func_proto bpf_perf_event_output_proto_raw_tp = {
.arg2_type = ARG_CONST_MAP_PTR,
.arg3_type = ARG_ANYTHING,
.arg4_type = ARG_PTR_TO_MEM | MEM_RDONLY,
- .arg5_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg5_type = ARG_MEM_SIZE_OR_ZERO,
};
extern const struct bpf_func_proto bpf_skb_output_proto;
@@ -1701,7 +1701,7 @@ static const struct bpf_func_proto bpf_get_stack_proto_raw_tp = {
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_PTR_TO_UNINIT_MEM,
- .arg3_type = ARG_CONST_SIZE_OR_ZERO,
+ .arg3_type = ARG_MEM_SIZE_OR_ZERO,
.arg4_type = ARG_ANYTHING,
};
@@ -2376,9 +2376,12 @@ static int copy_user_syms(struct user_syms *us, unsigned long __user *usyms, u32
int err = -ENOMEM;
unsigned int i;
+ if (!access_ok(usyms, cnt * sizeof(*usyms)))
+ return -EFAULT;
+
syms = kvmalloc_array(cnt, sizeof(*syms), GFP_KERNEL);
if (!syms)
- goto error;
+ return -ENOMEM;
buf = kvmalloc_array(cnt, KSYM_NAME_LEN, GFP_KERNEL);
if (!buf)
@@ -2403,10 +2406,8 @@ static int copy_user_syms(struct user_syms *us, unsigned long __user *usyms, u32
return 0;
error:
- if (err) {
- kvfree(syms);
- kvfree(buf);
- }
+ kvfree(syms);
+ kvfree(buf);
return err;
}
@@ -3686,7 +3687,7 @@ static void bpf_tracing_multi_link_release(struct bpf_link *link)
struct bpf_tracing_multi_link *tr_link =
container_of(link, struct bpf_tracing_multi_link, link);
- WARN_ON_ONCE(bpf_trampoline_multi_detach(link->prog, tr_link));
+ bpf_trampoline_multi_detach(link->prog, tr_link);
}
static void bpf_tracing_multi_link_dealloc(struct bpf_link *link)
@@ -3699,6 +3700,60 @@ static void bpf_tracing_multi_link_dealloc(struct bpf_link *link)
kvfree(tr_link);
}
+static int bpf_tracing_multi_link_fill_link_info(const struct bpf_link *link,
+ struct bpf_link_info *info)
+{
+ u64 __user *ucookies = u64_to_user_ptr(info->tracing_multi.cookies);
+ u64 __user *uaddrs = u64_to_user_ptr(info->tracing_multi.addrs);
+ u32 __user *uids = u64_to_user_ptr(info->tracing_multi.ids);
+ struct bpf_tracing_multi_link *tr_link;
+ u32 ucount = info->tracing_multi.count;
+ bool has_cookies, show_addrs;
+ int err = 0;
+
+ if ((uids || ucookies || uaddrs) && !ucount)
+ return -EINVAL;
+
+ tr_link = container_of(link, struct bpf_tracing_multi_link, link);
+
+ info->tracing_multi.attach_type = tr_link->link.attach_type;
+ info->tracing_multi.count = tr_link->nodes_cnt;
+ info->tracing_multi.btf_obj_id = btf_obj_id(tr_link->link.prog->aux->attach_btf);
+
+ if (!uids && !ucookies && !uaddrs)
+ return 0;
+
+ if (ucount < tr_link->nodes_cnt)
+ err = -ENOSPC;
+ else
+ ucount = tr_link->nodes_cnt;
+
+ has_cookies = !!tr_link->cookies;
+ show_addrs = kallsyms_show_value(current_cred());
+
+ for (int i = 0; i < ucount; i++) {
+ struct bpf_tracing_multi_node *mnode = &tr_link->nodes[i];
+ u64 addr, cookie;
+ u32 id;
+
+ bpf_trampoline_unpack_key(mnode->trampoline->key, NULL, &id);
+
+ addr = show_addrs ? mnode->trampoline->ip : 0;
+ cookie = has_cookies ? tr_link->cookies[i] : 0;
+
+ if (uids && put_user(id, uids + i))
+ return -EFAULT;
+ if (uaddrs && put_user(addr, uaddrs + i))
+ return -EFAULT;
+ if (ucookies && put_user(cookie, ucookies + i))
+ return -EFAULT;
+
+ cond_resched();
+ }
+
+ return err;
+}
+
#ifdef CONFIG_PROC_FS
static void bpf_tracing_multi_show_fdinfo(const struct bpf_link *link,
struct seq_file *seq)
@@ -3729,6 +3784,7 @@ static void bpf_tracing_multi_show_fdinfo(const struct bpf_link *link,
static const struct bpf_link_ops bpf_tracing_multi_link_lops = {
.release = bpf_tracing_multi_link_release,
.dealloc_deferred = bpf_tracing_multi_link_dealloc,
+ .fill_link_info = bpf_tracing_multi_link_fill_link_info,
#ifdef CONFIG_PROC_FS
.show_fdinfo = bpf_tracing_multi_show_fdinfo,
#endif
diff --git a/kernel/trace/fgraph.c b/kernel/trace/fgraph.c
index 40d373d65f9b..ed455b53513b 100644
--- a/kernel/trace/fgraph.c
+++ b/kernel/trace/fgraph.c
@@ -143,9 +143,6 @@ enum {
#define FGRAPH_DATA_INDEX_MASK GENMASK(FGRAPH_DATA_INDEX_BITS - 1, 0)
#define FGRAPH_DATA_INDEX_SHIFT (FGRAPH_DATA_SHIFT + FGRAPH_DATA_BITS)
-#define FGRAPH_MAX_INDEX \
- ((FGRAPH_INDEX_SIZE << FGRAPH_DATA_BITS) + FGRAPH_RET_INDEX)
-
#define FGRAPH_ARRAY_SIZE FGRAPH_INDEX_BITS
/*
diff --git a/kernel/trace/fprobe.c b/kernel/trace/fprobe.c
index f378613ad120..da286619c5d8 100644
--- a/kernel/trace/fprobe.c
+++ b/kernel/trace/fprobe.c
@@ -47,6 +47,10 @@ static struct rhltable fprobe_ip_table;
static DEFINE_MUTEX(fprobe_mutex);
static struct fgraph_ops fprobe_graph_ops;
+DEFINE_LOCK_GUARD_0(rcu_sched_notrace,
+ rcu_read_lock_sched_notrace(),
+ rcu_read_unlock_sched_notrace())
+
static u32 fprobe_node_hashfn(const void *data, u32 len, u32 seed)
{
return hash_ptr(*(unsigned long **)data, 32);
@@ -171,6 +175,11 @@ static inline bool write_fprobe_header(unsigned long *stack,
static inline void read_fprobe_header(unsigned long *stack,
struct fprobe **fp, unsigned int *size_words)
{
+ if (!*stack) {
+ *fp = NULL;
+ *size_words = 0;
+ return;
+ }
*fp = arch_decode_fprobe_header_fp(*stack);
*size_words = arch_decode_fprobe_header_size(*stack);
}
@@ -181,7 +190,7 @@ static inline void read_fprobe_header(unsigned long *stack,
struct __fprobe_header {
struct fprobe *fp;
unsigned long size_words;
-} __packed;
+};
#define FPROBE_HEADER_SIZE_IN_LONG SIZE_IN_LONG(sizeof(struct __fprobe_header))
@@ -203,6 +212,12 @@ static inline void read_fprobe_header(unsigned long *stack,
{
struct __fprobe_header *fph = (struct __fprobe_header *)stack;
+ if (!*stack) {
+ *fp = NULL;
+ *size_words = 0;
+ return;
+ }
+
*fp = fph->fp;
*size_words = fph->size_words;
}
@@ -318,16 +333,14 @@ static void fprobe_ftrace_entry(unsigned long ip, unsigned long parent_ip,
struct fprobe *fp;
int bit;
+ if (!rcu_is_watching())
+ return;
+
bit = ftrace_test_recursion_trylock(ip, parent_ip);
if (bit < 0)
return;
- /*
- * ftrace_test_recursion_trylock() disables preemption, but
- * rhltable_lookup() checks whether rcu_read_lcok is held.
- * So we take rcu_read_lock() here.
- */
- rcu_read_lock();
+ guard(rcu_sched_notrace)();
head = rhltable_lookup(&fprobe_ip_table, &ip, fprobe_rht_params);
rhl_for_each_entry_rcu(node, pos, head, hlist) {
@@ -342,7 +355,6 @@ static void fprobe_ftrace_entry(unsigned long ip, unsigned long parent_ip,
else
__fprobe_handler(ip, parent_ip, fp, fregs, NULL);
}
- rcu_read_unlock();
ftrace_test_recursion_unlock(bit);
}
NOKPROBE_SYMBOL(fprobe_ftrace_entry);
@@ -464,15 +476,8 @@ static bool fprobe_exists_on_hash(unsigned long ip, bool ftrace)
#ifdef CONFIG_MODULES
static void fprobe_remove_ips(unsigned long *ips, unsigned int cnt)
{
- if (!nr_fgraph_fprobes)
- __fprobe_graph_unregister();
- else if (cnt)
- ftrace_set_filter_ips(&fprobe_graph_ops.ops, ips, cnt, 1, 0);
-
- if (!nr_ftrace_fprobes)
- __fprobe_ftrace_unregister();
- else if (cnt)
- ftrace_set_filter_ips(&fprobe_ftrace_ops, ips, cnt, 1, 0);
+ fprobe_graph_remove_ips(ips, cnt);
+ fprobe_ftrace_remove_ips(ips, cnt);
}
#endif
#else
@@ -563,10 +568,13 @@ static int fprobe_fgraph_entry(struct ftrace_graph_ent *trace, struct fgraph_ops
struct fprobe *fp;
int used, ret;
+ if (!rcu_is_watching())
+ return 0;
+
if (WARN_ON_ONCE(!fregs))
return 0;
- guard(rcu)();
+ guard(rcu_sched_notrace)();
head = rhltable_lookup(&fprobe_ip_table, &func, fprobe_rht_params);
reserved_words = 0;
rhl_for_each_entry_rcu(node, pos, head, hlist) {
@@ -613,6 +621,16 @@ static int fprobe_fgraph_entry(struct ftrace_graph_ent *trace, struct fgraph_ops
continue;
data_size = fp->entry_data_size;
+ /*
+ * The list may have grown since it was sized, so this node
+ * may not fit. Skip it as missed rather than overrun the
+ * reservation.
+ */
+ if (fp->exit_handler &&
+ used + FPROBE_HEADER_SIZE_IN_LONG + SIZE_IN_LONG(data_size) > reserved_words) {
+ fp->nmissed++;
+ continue;
+ }
if (data_size && fp->exit_handler)
data = fgraph_data + used + FPROBE_HEADER_SIZE_IN_LONG;
else
@@ -632,6 +650,10 @@ static int fprobe_fgraph_entry(struct ftrace_graph_ent *trace, struct fgraph_ops
}
}
+ /* Terminate the list, fgraph_reserve_data() does not clear it. */
+ if (used && used < reserved_words)
+ fgraph_data[used] = 0;
+
/* If any exit_handler is set, data must be used. */
return used != 0;
}
@@ -647,13 +669,16 @@ static void fprobe_return(struct ftrace_graph_ret *trace,
int size, curr;
int size_words;
+ if (!rcu_is_watching())
+ return;
+
fgraph_data = (unsigned long *)fgraph_retrieve_data(gops->idx, &size);
if (WARN_ON_ONCE(!fgraph_data))
return;
size_words = SIZE_IN_LONG(size);
ret_ip = ftrace_regs_get_instruction_pointer(fregs);
- preempt_disable_notrace();
+ guard(rcu_sched_notrace)();
curr = 0;
while (size_words > curr) {
@@ -669,7 +694,6 @@ static void fprobe_return(struct ftrace_graph_ret *trace,
}
curr += size;
}
- preempt_enable_notrace();
}
NOKPROBE_SYMBOL(fprobe_return);
@@ -942,7 +966,7 @@ int register_fprobe(struct fprobe *fp, const char *filter, const char *notfilter
if (num < 0)
return num;
- addrs = kcalloc(num, sizeof(*addrs), GFP_KERNEL);
+ addrs = kzalloc_objs(*addrs, num);
if (!addrs)
return -ENOMEM;
@@ -951,10 +975,8 @@ int register_fprobe(struct fprobe *fp, const char *filter, const char *notfilter
return -ENOMEM;
ret = get_ips_from_filter(filter, notfilter, addrs, mods, num);
- if (ret < 0)
- return ret;
-
- ret = register_fprobe_ips(fp, addrs, ret);
+ if (ret >= 0)
+ ret = register_fprobe_ips(fp, addrs, ret);
for (int i = 0; i < num; i++) {
if (mods[i])
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
index f93e34dd2328..673a54fdf392 100644
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -75,6 +75,8 @@
.func_hash = &opsname.local_hash, \
.local_hash.regex_lock = __MUTEX_INITIALIZER(opsname.local_hash.regex_lock), \
.subop_list = LIST_HEAD_INIT(opsname.subop_list),
+/* Used only to synchronize the initialization of ftrace_ops */
+static DEFINE_MUTEX(ops_mutex);
#else
#define INIT_OPS_HASH(opsname)
#endif
@@ -159,11 +161,18 @@ const struct ftrace_ops ftrace_nop_ops = {
static inline void ftrace_ops_init(struct ftrace_ops *ops)
{
#ifdef CONFIG_DYNAMIC_FTRACE
- if (!(ops->flags & FTRACE_OPS_FL_INITIALIZED)) {
+ unsigned long flags = smp_load_acquire(&ops->flags);
+
+ if (!(flags & FTRACE_OPS_FL_INITIALIZED)) {
+ guard(mutex)(&ops_mutex);
+ /* Could have been initialized before lock taken */
+ if (unlikely(ops->flags & FTRACE_OPS_FL_INITIALIZED))
+ return;
mutex_init(&ops->local_hash.regex_lock);
INIT_LIST_HEAD(&ops->subop_list);
ops->func_hash = &ops->local_hash;
- ops->flags |= FTRACE_OPS_FL_INITIALIZED;
+ flags = ops->flags | FTRACE_OPS_FL_INITIALIZED;
+ smp_store_release(&ops->flags, flags);
}
#endif
}
@@ -1098,6 +1107,12 @@ struct ftrace_ops global_ops = {
};
/*
+ * parser_lock - Protects trace_parser state against concurrent operations.
+ * Held across trace_get_user() and subsequent buffer parsing to prevent races.
+ */
+static DEFINE_MUTEX(parser_lock);
+
+/*
* Used by the stack unwinder to know about dynamic ftrace trampolines.
*/
struct ftrace_ops *ftrace_ops_trampoline(unsigned long addr)
@@ -2639,7 +2654,8 @@ unsigned long ftrace_find_rec_direct(unsigned long ip)
{
struct ftrace_func_entry *entry;
- entry = __ftrace_lookup_ip(direct_functions, ip);
+ guard(preempt_notrace)();
+ entry = __ftrace_lookup_ip(rcu_dereference_sched(direct_functions), ip);
if (!entry)
return 0;
@@ -4670,7 +4686,8 @@ ftrace_avail_addrs_open(struct inode *inode, struct file *file)
/**
* ftrace_regex_open - initialize function tracer filter files
- * @ops: The ftrace_ops that hold the hash filters
+ * @tr: The trace_array that holds the ftrace_ops [optional]
+ * @ops: The ftrace_ops that hold the hash filters [optional]
* @flag: The type of filter to process
* @inode: The inode, usually passed in to your open routine
* @file: The file, usually passed in to your open routine
@@ -4684,26 +4701,45 @@ ftrace_avail_addrs_open(struct inode *inode, struct file *file)
* tracing_lseek() should be used as the lseek routine, and
* release must call ftrace_regex_release().
*
+ * Note, If @tr is not NULL, its reference has to be taken before
+ * @ops may be referenced.
+ * If @ops is NULL and @tr is not, then @tr->ops is used.
+ * If @tr is NULL and @ops is not then @ops->private is uesd for @tr.
+ * If both @tr and @ops are NULL, then the &global_ops is
+ * to be used, and @tr will be the global_ops.private pointer.
+ *
* Returns: 0 on success or a negative errno value on failure
*/
int
-ftrace_regex_open(struct ftrace_ops *ops, int flag,
+ftrace_regex_open(struct trace_array *tr, struct ftrace_ops *ops, int flag,
struct inode *inode, struct file *file)
{
- struct ftrace_iterator *iter;
+ struct ftrace_iterator *iter = NULL;
struct ftrace_hash *hash;
struct list_head *mod_head;
- struct trace_array *tr = ops->private;
- int ret = -ENOMEM;
-
- ftrace_ops_init(ops);
+ int ret = -ENODEV;
if (unlikely(ftrace_disabled))
return -ENODEV;
+ if (!tr) {
+ if (!ops)
+ ops = &global_ops;
+ tr = ops->private;
+ }
+
if (tracing_check_open_get_tr(tr))
return -ENODEV;
+ if (!ops)
+ ops = tr->ops;
+
+ if (WARN_ON_ONCE(!ops))
+ goto out;
+
+ ftrace_ops_init(ops);
+
+ ret = -ENOMEM;
iter = kzalloc_obj(*iter);
if (!iter)
goto out;
@@ -4781,21 +4817,19 @@ ftrace_regex_open(struct ftrace_ops *ops, int flag,
static int
ftrace_filter_open(struct inode *inode, struct file *file)
{
- struct ftrace_ops *ops = inode->i_private;
+ struct trace_array *tr = inode->i_private;
- /* Checks for tracefs lockdown */
- return ftrace_regex_open(ops,
- FTRACE_ITER_FILTER | FTRACE_ITER_DO_PROBES,
- inode, file);
+ return ftrace_regex_open(tr, NULL,
+ FTRACE_ITER_FILTER | FTRACE_ITER_DO_PROBES,
+ inode, file);
}
static int
ftrace_notrace_open(struct inode *inode, struct file *file)
{
- struct ftrace_ops *ops = inode->i_private;
+ struct trace_array *tr = inode->i_private;
- /* Checks for tracefs lockdown */
- return ftrace_regex_open(ops, FTRACE_ITER_NOTRACE,
+ return ftrace_regex_open(tr, NULL, FTRACE_ITER_NOTRACE,
inode, file);
}
@@ -5842,6 +5876,8 @@ ftrace_regex_write(struct file *file, const char __user *ubuf,
/* iter->hash is a local copy, so we don't need regex_lock */
parser = &iter->parser;
+
+ guard(mutex)(&parser_lock);
read = trace_get_user(parser, ubuf, cnt, ppos);
if (read >= 0 && trace_parser_loaded(parser) &&
@@ -6503,6 +6539,7 @@ int update_ftrace_direct_del(struct ftrace_ops *ops, struct ftrace_hash *hash)
struct ftrace_hash *new_direct_functions;
struct ftrace_hash *new_filter_hash = NULL;
struct ftrace_hash *old_filter_hash;
+ struct ftrace_hash *direct_hash;
struct ftrace_func_entry *entry;
struct ftrace_func_entry *del;
unsigned long size;
@@ -6514,11 +6551,13 @@ int update_ftrace_direct_del(struct ftrace_ops *ops, struct ftrace_hash *hash)
return -EINVAL;
if (!(ops->flags & FTRACE_OPS_FL_ENABLED))
return -EINVAL;
- if (direct_functions == EMPTY_HASH)
- return -EINVAL;
mutex_lock(&direct_mutex);
+ direct_hash = rcu_dereference_protected(direct_functions, lockdep_is_held(&direct_mutex));
+ if (direct_hash == EMPTY_HASH)
+ goto out_unlock;
+
old_filter_hash = ops->func_hash ? ops->func_hash->filter_hash : NULL;
if (!hash_count(old_filter_hash))
@@ -6528,7 +6567,7 @@ int update_ftrace_direct_del(struct ftrace_ops *ops, struct ftrace_hash *hash)
size = 1 << hash->size_bits;
for (int i = 0; i < size; i++) {
hlist_for_each_entry(entry, &hash->buckets[i], hlist) {
- del = __ftrace_lookup_ip(direct_functions, entry->ip);
+ del = __ftrace_lookup_ip(direct_hash, entry->ip);
if (!del || del->direct != entry->direct)
goto out_unlock;
}
@@ -6539,7 +6578,7 @@ int update_ftrace_direct_del(struct ftrace_ops *ops, struct ftrace_hash *hash)
if (!new_filter_hash)
goto out_unlock;
- new_direct_functions = hash_sub(direct_functions, hash);
+ new_direct_functions = hash_sub(direct_hash, hash);
if (!new_direct_functions)
goto out_unlock;
@@ -6566,7 +6605,7 @@ int update_ftrace_direct_del(struct ftrace_ops *ops, struct ftrace_hash *hash)
/* free the new_direct_functions */
old_direct_functions = new_direct_functions;
} else {
- old_direct_functions = direct_functions;
+ old_direct_functions = direct_hash;
rcu_assign_pointer(direct_functions, new_direct_functions);
}
@@ -6605,6 +6644,7 @@ int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, b
.func = ftrace_stub,
.flags = FTRACE_OPS_FL_STUB,
};
+ struct ftrace_hash *direct_hash;
struct ftrace_hash *orig_hash;
unsigned long size, i;
int err = -EINVAL;
@@ -6615,8 +6655,6 @@ int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, b
return -EINVAL;
if (!(ops->flags & FTRACE_OPS_FL_ENABLED))
return -EINVAL;
- if (direct_functions == EMPTY_HASH)
- return -EINVAL;
/*
* We can be called from within ops_func callback with direct_mutex
@@ -6624,6 +6662,12 @@ int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, b
*/
if (do_direct_lock)
mutex_lock(&direct_mutex);
+ else
+ lockdep_assert_held_once(&direct_mutex);
+
+ direct_hash = rcu_dereference_protected(direct_functions, lockdep_is_held(&direct_mutex));
+ if (direct_hash == EMPTY_HASH)
+ goto unlock;
orig_hash = ops->func_hash ? ops->func_hash->filter_hash : NULL;
if (!orig_hash)
@@ -6631,7 +6675,7 @@ int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, b
/* Enable the tmp_ops to have the same functions as the hash object. */
ftrace_ops_init(&tmp_ops);
- tmp_ops.func_hash->filter_hash = hash;
+ rcu_assign_pointer(tmp_ops.func_hash->filter_hash, hash);
err = register_ftrace_function_nolock(&tmp_ops);
if (err)
@@ -6655,7 +6699,7 @@ int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, b
size = 1 << hash->size_bits;
for (i = 0; i < size; i++) {
hlist_for_each_entry(entry, &hash->buckets[i], hlist) {
- tmp = __ftrace_lookup_ip(direct_functions, entry->ip);
+ tmp = __ftrace_lookup_ip(direct_hash, entry->ip);
if (!tmp)
continue;
tmp->direct = entry->direct;
@@ -6984,12 +7028,14 @@ int ftrace_regex_release(struct inode *inode, struct file *file)
iter = file->private_data;
parser = &iter->parser;
+ mutex_lock(&parser_lock);
if (trace_parser_loaded(parser)) {
int enable = !(iter->flags & FTRACE_ITER_NOTRACE);
ftrace_process_regex(iter, parser->buffer,
parser->idx, enable);
}
+ mutex_unlock(&parser_lock);
trace_parser_put(parser);
@@ -7321,10 +7367,12 @@ ftrace_graph_release(struct inode *inode, struct file *file)
parser = &fgd->parser;
+ mutex_lock(&parser_lock);
if (trace_parser_loaded((parser))) {
ret = ftrace_graph_set_hash(fgd->new_hash,
parser->buffer);
}
+ mutex_unlock(&parser_lock);
trace_parser_put(parser);
@@ -7437,6 +7485,7 @@ ftrace_graph_write(struct file *file, const char __user *ubuf,
parser = &fgd->parser;
+ guard(mutex)(&parser_lock);
read = trace_get_user(parser, ubuf, cnt, ppos);
if (read >= 0 && trace_parser_loaded(parser) &&
@@ -7470,15 +7519,15 @@ static const struct file_operations ftrace_graph_notrace_fops = {
};
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
-void ftrace_create_filter_files(struct ftrace_ops *ops,
+void ftrace_create_filter_files(struct trace_array *tr,
struct dentry *parent)
{
trace_create_file("set_ftrace_filter", TRACE_MODE_WRITE, parent,
- ops, &ftrace_filter_fops);
+ tr, &ftrace_filter_fops);
trace_create_file("set_ftrace_notrace", TRACE_MODE_WRITE, parent,
- ops, &ftrace_notrace_fops);
+ tr, &ftrace_notrace_fops);
}
/*
@@ -7503,7 +7552,6 @@ void ftrace_destroy_filter_files(struct ftrace_ops *ops)
static __init int ftrace_init_dyn_tracefs(struct dentry *d_tracer)
{
-
trace_create_file("available_filter_functions", TRACE_MODE_READ,
d_tracer, NULL, &ftrace_avail_fops);
@@ -7516,7 +7564,7 @@ static __init int ftrace_init_dyn_tracefs(struct dentry *d_tracer)
trace_create_file("touched_functions", TRACE_MODE_READ,
d_tracer, NULL, &ftrace_touched_fops);
- ftrace_create_filter_files(&global_ops, d_tracer);
+ ftrace_create_filter_files(NULL, d_tracer);
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
trace_create_file("set_graph_function", TRACE_MODE_WRITE, d_tracer,
@@ -8283,7 +8331,8 @@ static void add_to_clear_hash_list(struct list_head *clear_list,
void ftrace_free_mem(struct module *mod, void *start_ptr, void *end_ptr)
{
unsigned long start = (unsigned long)(start_ptr);
- unsigned long end = (unsigned long)(end_ptr);
+ /* end is inclusive and end_ptr is exclusive */
+ unsigned long end = (unsigned long)(end_ptr) - 1;
struct ftrace_page **last_pg = &ftrace_pages_start;
struct ftrace_page *tmp_page = NULL;
struct ftrace_page *pg;
@@ -8293,6 +8342,9 @@ void ftrace_free_mem(struct module *mod, void *start_ptr, void *end_ptr)
struct ftrace_init_func *func, *func_next;
LIST_HEAD(clear_hash);
+ if (start_ptr >= end_ptr)
+ return;
+
key.ip = start;
key.flags = end; /* overload flags, as it is unsigned long */
@@ -9357,38 +9409,10 @@ static void ftrace_startup_sysctl(void)
}
}
-static void ftrace_shutdown_sysctl(void)
-{
- int command;
-
- if (unlikely(ftrace_disabled))
- return;
-
- /* ftrace_start_up is true if ftrace is running */
- if (ftrace_start_up) {
- command = FTRACE_DISABLE_CALLS;
- if (ftrace_graph_active)
- command |= FTRACE_STOP_FUNC_RET;
- ftrace_run_update_code(command);
- }
-}
#else
# define ftrace_startup_sysctl() do { } while (0)
-# define ftrace_shutdown_sysctl() do { } while (0)
#endif /* CONFIG_DYNAMIC_FTRACE */
-static bool is_permanent_ops_registered(void)
-{
- struct ftrace_ops *op;
-
- do_for_each_ftrace_op(op, ftrace_ops_list) {
- if (op->flags & FTRACE_OPS_FL_PERMANENT)
- return true;
- } while_for_each_ftrace_op(op);
-
- return false;
-}
-
static int
ftrace_enable_sysctl(const struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
@@ -9415,15 +9439,12 @@ ftrace_enable_sysctl(const struct ctl_table *table, int write,
ftrace_startup_sysctl();
} else {
- if (is_permanent_ops_registered()) {
- ftrace_enabled = true;
- return -EBUSY;
- }
-
- /* stopping ftrace calls (just send to ftrace_stub) */
- ftrace_trace_function = ftrace_stub;
-
- ftrace_shutdown_sysctl();
+ /*
+ * Disabling ftrace at runtime via this knob is deprecated.
+ */
+ ftrace_enabled = true;
+ pr_warn_once("The ftrace_enabled file is deprecated and no longer disables ftrace\n");
+ return -EOPNOTSUPP;
}
last_ftrace_enabled = !!ftrace_enabled;
diff --git a/kernel/trace/preemptirq_delay_test.c b/kernel/trace/preemptirq_delay_test.c
index acb0c971a408..69e5238737ed 100644
--- a/kernel/trace/preemptirq_delay_test.c
+++ b/kernel/trace/preemptirq_delay_test.c
@@ -6,6 +6,7 @@
*/
#include <linux/trace_clock.h>
+#include <linux/cpumask.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
@@ -123,6 +124,13 @@ static int preemptirq_delay_run(void *data)
return -ENOMEM;
if (cpu_affinity > -1) {
+ unsigned int cpu = cpu_affinity;
+
+ if (cpu >= nr_cpu_ids || !cpu_possible(cpu)) {
+ pr_err("cpu_affinity:%d, invalid CPU\n", cpu_affinity);
+ goto out;
+ }
+
cpumask_clear(cpu_mask);
cpumask_set_cpu(cpu_affinity, cpu_mask);
if (set_cpus_allowed_ptr(current, cpu_mask))
@@ -132,6 +140,7 @@ static int preemptirq_delay_run(void *data)
for (i = 0; i < s; i++)
(testfuncs[i])(i);
+out:
complete(&done);
set_current_state(TASK_INTERRUPTIBLE);
diff --git a/kernel/trace/rethook.c b/kernel/trace/rethook.c
index 5a8bdf88999a..87a27f3aa4a6 100644
--- a/kernel/trace/rethook.c
+++ b/kernel/trace/rethook.c
@@ -171,7 +171,7 @@ struct rethook_node *rethook_try_get(struct rethook *rh)
* This expects the caller will set up a rethook on a function entry.
* When the function returns, the rethook will eventually be reclaimed
* or released in the rethook_recycle() with call_rcu().
- * This means the caller must be run in the RCU-availabe context.
+ * This means the caller must be run in the RCU-available context.
*/
if (unlikely(!rcu_is_watching()))
return NULL;
diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c
index 56a328e94395..04bb94c29f58 100644
--- a/kernel/trace/ring_buffer.c
+++ b/kernel/trace/ring_buffer.c
@@ -270,7 +270,8 @@ unsigned ring_buffer_event_length(struct ring_buffer_event *event)
if (event->type_len > RINGBUF_TYPE_DATA_TYPE_LEN_MAX)
return length;
length -= RB_EVNT_HDR_SIZE;
- if (length > RB_MAX_SMALL_DATA + sizeof(event->array[0]))
+ if (length > RB_MAX_SMALL_DATA + sizeof(event->array[0]) ||
+ RB_FORCE_8BYTE_ALIGNMENT)
length -= sizeof(event->array[0]);
return length;
}
@@ -329,6 +330,14 @@ struct buffer_data_read_page {
struct buffer_data_page *data; /* actual data, stored in this page */
};
+static __always_inline unsigned int rb_read_page_capacity(struct buffer_data_read_page *rpage)
+{
+ return (PAGE_SIZE << rpage->order) - BUF_PAGE_HDR_SIZE;
+}
+
+/* The number of bits for static buffer ids */
+#define RB_STATIC_BITS 30
+
/*
* Note, the buffer_page list must be first. The buffer pages
* are allocated in cache lines, which means that each buffer
@@ -344,7 +353,7 @@ struct buffer_page {
local_t entries; /* entries on this page */
unsigned long real_end; /* real end of data */
unsigned order; /* order of the page */
- u32 id:30; /* ID for external mapping */
+ u32 id:RB_STATIC_BITS; /* ID for external mapping */
u32 range:1; /* Mapped via a range */
struct buffer_data_page *page; /* Actual data page */
};
@@ -390,6 +399,17 @@ static __always_inline unsigned int rb_page_size(struct buffer_page *bpage)
return rb_data_page_size(bpage->page);
}
+/**
+ * rb_page_capacity - Get the capacity of a buffer page
+ * @bpage: The buffer page
+ *
+ * Return: The maximum size available for events in the given buffer page.
+ */
+static __always_inline unsigned int rb_page_capacity(struct buffer_page *bpage)
+{
+ return (PAGE_SIZE << bpage->order) - BUF_PAGE_HDR_SIZE;
+}
+
static void free_buffer_page(struct buffer_page *bpage)
{
/* Range pages are not to be freed */
@@ -404,11 +424,11 @@ static void free_buffer_page(struct buffer_page *bpage)
*/
#define alloc_cpu_buffer(cpu) (struct ring_buffer_per_cpu *) \
kzalloc_node(ALIGN(sizeof(struct ring_buffer_per_cpu), \
- cache_line_size()), GFP_KERNEL, cpu_to_node(cpu));
+ cache_line_size()), GFP_KERNEL, cpu_to_node(cpu))
#define alloc_cpu_page(cpu) (struct buffer_page *) \
kzalloc_node(ALIGN(sizeof(struct buffer_page), \
- cache_line_size()), GFP_KERNEL, cpu_to_node(cpu));
+ cache_line_size()), GFP_KERNEL, cpu_to_node(cpu))
static struct buffer_data_page *alloc_cpu_data(int cpu, int order)
{
@@ -502,11 +522,11 @@ struct ring_buffer_per_cpu {
int cpu;
atomic_t record_disabled;
atomic_t resize_disabled;
- struct trace_buffer *buffer;
+ struct trace_buffer *buffer;
raw_spinlock_t reader_lock; /* serialize readers */
arch_spinlock_t lock;
struct lock_class_key lock_key;
- struct buffer_data_page *free_page;
+ struct buffer_data_read_page free_page;
unsigned long nr_pages;
unsigned int current_context;
struct list_head *pages;
@@ -540,7 +560,6 @@ struct ring_buffer_per_cpu {
/* pages removed since last reset */
unsigned long pages_removed;
- unsigned int mapped;
unsigned int user_mapped; /* user space mapping */
struct mutex mapping_lock;
struct buffer_page **subbuf_ids; /* ID to subbuf VA */
@@ -560,7 +579,6 @@ struct ring_buffer_per_cpu {
struct trace_buffer {
unsigned flags;
- int cpus;
atomic_t record_disabled;
atomic_t resizing;
cpumask_var_t cpumask;
@@ -585,11 +603,72 @@ struct trace_buffer {
struct ring_buffer_meta *meta;
- unsigned int subbuf_size;
unsigned int subbuf_order;
- unsigned int max_data_size;
};
+static __always_inline unsigned int rb_subbuf_size(struct trace_buffer *buffer)
+{
+ return PAGE_SIZE << buffer->subbuf_order;
+}
+
+/**
+ * rb_subbuf_capacity - Get the capacity of a subbuffer
+ * @buffer: A trace buffer
+ *
+ * Unsafe to use without holding trace_buffer::mutex or with resizing enabled.
+ * Consider rb_page_capacity() instead.
+ *
+ * Return: The maximum size available for events in a trace buffer subbuffer.
+ */
+static __always_inline unsigned int rb_subbuf_capacity(struct trace_buffer *buffer)
+{
+ return rb_subbuf_size(buffer) - BUF_PAGE_HDR_SIZE;
+}
+
+/**
+ * rb_subbuf_max_data_size - Get the maximum payload size of a single event
+ * @buffer: A trace buffer
+ *
+ * Return: The maximum data payload size that can be stored in a single event.
+ */
+static __always_inline unsigned int rb_subbuf_max_data_size(struct trace_buffer *buffer)
+{
+ struct ring_buffer_event *event;
+
+ /*
+ * surely rb_subbuf_capacity() is bigger than
+ * RINGBUF_TYPE_DATA_TYPE_LEN_MAX (see ring_buffer_event_length).
+ */
+ return rb_subbuf_capacity(buffer) - RB_EVNT_HDR_SIZE - sizeof(event->array[0]);
+}
+
+/**
+ * rb_subbuf_start - Get the start address of a subbuffer
+ * @buffer: A trace buffer
+ * @addr: An address of an event on a subbuffer
+ *
+ * Return: The start of the subbuffer for where @addr sits
+ */
+static __always_inline
+unsigned long rb_subbuf_start(struct trace_buffer *buffer, unsigned long addr)
+{
+ return addr & ~((unsigned long)(rb_subbuf_size(buffer) - 1));
+}
+
+static bool rb_is_static(struct ring_buffer_per_cpu *cpu_buffer)
+{
+ return cpu_buffer->user_mapped || cpu_buffer->remote || cpu_buffer->ring_meta;
+}
+
+static unsigned long rb_static_max_pages(void)
+{
+ /*
+ * Static ring buffers are using bpage::id and must account for the
+ * reader page.
+ */
+ return (1UL << RB_STATIC_BITS) - 1;
+}
+
struct ring_buffer_iter {
struct ring_buffer_per_cpu *cpu_buffer;
unsigned long head;
@@ -629,7 +708,7 @@ int ring_buffer_print_page_header(struct trace_buffer *buffer, struct trace_seq
trace_seq_printf(s, "\tfield: char data;\t"
"offset:%u;\tsize:%u;\tsigned:%u;\n",
(unsigned int)offsetof(typeof(field), data),
- (unsigned int)(buffer ? buffer->subbuf_size :
+ (unsigned int)(buffer ? rb_subbuf_capacity(buffer) :
PAGE_SIZE - BUF_PAGE_HDR_SIZE),
(unsigned int)is_signed_type(char));
@@ -825,14 +904,13 @@ static void rb_wake_up_waiters(struct irq_work *work)
struct ring_buffer_per_cpu *cpu_buffer =
container_of(rbwork, struct ring_buffer_per_cpu, irq_work);
- /* Called from interrupt context */
- raw_spin_lock(&cpu_buffer->reader_lock);
- rbwork->wakeup_full = false;
- rbwork->full_waiters_pending = false;
+ scoped_guard(raw_spinlock_irqsave, &cpu_buffer->reader_lock) {
+ rbwork->wakeup_full = false;
+ rbwork->full_waiters_pending = false;
- /* Waking up all waiters, they will reset the shortest full */
- cpu_buffer->shortest_full = 0;
- raw_spin_unlock(&cpu_buffer->reader_lock);
+ /* Waking up all waiters, they will reset the shortest full */
+ cpu_buffer->shortest_full = 0;
+ }
wake_up_all(&rbwork->full_waiters);
}
@@ -1607,7 +1685,7 @@ out_locked:
* This is used to help find the next per cpu subbuffer within a mapped range.
*/
static unsigned long
-rb_range_align_subbuf(unsigned long addr, int subbuf_size, int nr_subbufs)
+rb_range_align_subbuf(unsigned long addr, unsigned int subbuf_size, unsigned long nr_subbufs)
{
addr += sizeof(struct ring_buffer_cpu_meta) +
sizeof(int) * nr_subbufs;
@@ -1617,13 +1695,12 @@ rb_range_align_subbuf(unsigned long addr, int subbuf_size, int nr_subbufs)
/*
* Return the ring_buffer_meta for a given @cpu.
*/
-static void *rb_range_meta(struct trace_buffer *buffer, int nr_pages, int cpu)
+static void *rb_range_meta(struct trace_buffer *buffer, unsigned long nr_pages, int cpu)
{
- int subbuf_size = buffer->subbuf_size + BUF_PAGE_HDR_SIZE;
+ unsigned int subbuf_size = rb_subbuf_size(buffer);
struct ring_buffer_cpu_meta *meta;
struct ring_buffer_meta *bmeta;
- unsigned long ptr;
- int nr_subbufs;
+ unsigned long ptr, nr_subbufs;
bmeta = buffer->meta;
if (!bmeta)
@@ -1669,7 +1746,7 @@ static void *rb_range_meta(struct trace_buffer *buffer, int nr_pages, int cpu)
/* Return the start of subbufs given the meta pointer */
static void *rb_subbufs_from_meta(struct ring_buffer_cpu_meta *meta)
{
- int subbuf_size = meta->subbuf_size;
+ unsigned int subbuf_size = meta->subbuf_size;
unsigned long ptr;
ptr = (unsigned long)meta;
@@ -1681,11 +1758,11 @@ static void *rb_subbufs_from_meta(struct ring_buffer_cpu_meta *meta)
/*
* Return a specific sub-buffer for a given @cpu defined by @idx.
*/
-static void *rb_range_buffer(struct ring_buffer_per_cpu *cpu_buffer, int idx)
+static void *rb_range_buffer(struct ring_buffer_per_cpu *cpu_buffer, unsigned long idx)
{
struct ring_buffer_cpu_meta *meta;
+ unsigned int subbuf_size;
unsigned long ptr;
- int subbuf_size;
meta = rb_range_meta(cpu_buffer->buffer, 0, cpu_buffer->cpu);
if (!meta)
@@ -1701,7 +1778,7 @@ static void *rb_range_buffer(struct ring_buffer_per_cpu *cpu_buffer, int idx)
ptr = (unsigned long)rb_subbufs_from_meta(meta);
- ptr += subbuf_size * idx;
+ ptr += (unsigned long)subbuf_size * idx;
if (ptr + subbuf_size > cpu_buffer->buffer->range_addr_end)
return NULL;
@@ -1778,13 +1855,12 @@ static bool rb_meta_init(struct trace_buffer *buffer, int scratch_size)
* must be the same.
*/
static bool rb_cpu_meta_valid(struct ring_buffer_cpu_meta *meta, int cpu,
- struct trace_buffer *buffer, int nr_pages,
+ struct trace_buffer *buffer, unsigned long nr_pages,
unsigned long *subbuf_mask)
{
- int subbuf_size = PAGE_SIZE;
unsigned long buffers_start;
unsigned long buffers_end;
- int i;
+ unsigned long i;
if (!subbuf_mask)
return false;
@@ -1794,8 +1870,13 @@ static bool rb_cpu_meta_valid(struct ring_buffer_cpu_meta *meta, int cpu,
return false;
}
+ if (meta->nr_subbufs != nr_pages + 1) {
+ pr_info("Ring buffer boot meta [%d] invalid nr_subbufs\n", cpu);
+ return false;
+ }
+
buffers_start = meta->first_buffer;
- buffers_end = meta->first_buffer + (subbuf_size * meta->nr_subbufs);
+ buffers_end = meta->first_buffer + (meta->nr_subbufs * PAGE_SIZE);
/* Is the head and commit buffers within the range of buffers? */
if (meta->head_buffer < buffers_start ||
@@ -2033,8 +2114,8 @@ static void rb_meta_validate_events(struct ring_buffer_per_cpu *cpu_buffer)
struct buffer_page *head_page, *orig_head, *orig_reader;
struct rb_validation_state state = { 0 };
bool skip = false;
+ unsigned long i;
int ret;
- int i;
if (!meta || !meta->head_buffer)
return;
@@ -2085,7 +2166,7 @@ static void rb_meta_validate_events(struct ring_buffer_per_cpu *cpu_buffer)
rb_validate_buffer(head_page, cpu_buffer, meta, &state, 0, state.ts);
}
if (i)
- pr_info("Ring buffer [%d] rewound %d pages\n", cpu_buffer->cpu, i);
+ pr_info("Ring buffer [%d] rewound %lu pages\n", cpu_buffer->cpu, i);
/* The last rewound page must be skipped. */
if (head_page != orig_head)
@@ -2169,7 +2250,8 @@ static void rb_meta_validate_events(struct ring_buffer_per_cpu *cpu_buffer)
}
}
-static void rb_range_meta_init(struct trace_buffer *buffer, int nr_pages, int scratch_size)
+static void rb_range_meta_init(struct trace_buffer *buffer, unsigned long nr_pages,
+ int scratch_size)
{
struct ring_buffer_cpu_meta *meta;
unsigned long *subbuf_mask;
@@ -2269,8 +2351,8 @@ static int rbm_show(struct seq_file *m, void *v)
rb_meta_subbuf_idx(meta, (void *)meta->head_buffer));
seq_printf(m, "commit_buffer: %d\n",
rb_meta_subbuf_idx(meta, (void *)meta->commit_buffer));
- seq_printf(m, "subbuf_size: %d\n", meta->subbuf_size);
- seq_printf(m, "nr_subbufs: %d\n", meta->nr_subbufs);
+ seq_printf(m, "subbuf_size: %u\n", meta->subbuf_size);
+ seq_printf(m, "nr_subbufs: %u\n", meta->nr_subbufs);
return 0;
}
@@ -2329,10 +2411,7 @@ static struct ring_buffer_desc *ring_buffer_desc(struct trace_buffer_desc *trace
size_t len;
int i;
- if (!trace_desc)
- return NULL;
-
- if (cpu >= trace_desc->nr_cpus)
+ if (!trace_desc || !trace_desc->nr_cpus)
return NULL;
end = (struct ring_buffer_desc *)((void *)trace_desc + trace_desc->struct_len);
@@ -2358,7 +2437,7 @@ static void *ring_buffer_desc_page(struct ring_buffer_desc *desc, unsigned int p
}
static int __rb_allocate_pages(struct ring_buffer_per_cpu *cpu_buffer,
- long nr_pages, struct list_head *pages)
+ unsigned long nr_pages, struct list_head *pages)
{
struct trace_buffer *buffer = cpu_buffer->buffer;
struct ring_buffer_cpu_meta *meta = NULL;
@@ -2434,8 +2513,8 @@ static int __rb_allocate_pages(struct ring_buffer_per_cpu *cpu_buffer,
bpage->id = i + 1;
cpu_buffer->subbuf_ids[i + 1] = bpage;
} else {
- int order = cpu_buffer->buffer->subbuf_order;
- bpage->page = alloc_cpu_data(cpu_buffer->cpu, order);
+ bpage->page = alloc_cpu_data(cpu_buffer->cpu,
+ cpu_buffer->buffer->subbuf_order);
if (!bpage->page)
goto free_pages;
}
@@ -2486,7 +2565,7 @@ static int rb_allocate_pages(struct ring_buffer_per_cpu *cpu_buffer,
}
static struct ring_buffer_per_cpu *
-rb_allocate_cpu_buffer(struct trace_buffer *buffer, long nr_pages, int cpu)
+rb_allocate_cpu_buffer(struct trace_buffer *buffer, unsigned long nr_pages, int cpu)
{
struct ring_buffer_per_cpu *cpu_buffer __free(kfree) =
alloc_cpu_buffer(cpu);
@@ -2512,6 +2591,7 @@ rb_allocate_cpu_buffer(struct trace_buffer *buffer, long nr_pages, int cpu)
bpage = alloc_cpu_page(cpu);
if (!bpage)
return NULL;
+ bpage->order = cpu_buffer->buffer->subbuf_order;
rb_check_bpage(cpu_buffer, bpage);
@@ -2522,7 +2602,6 @@ rb_allocate_cpu_buffer(struct trace_buffer *buffer, long nr_pages, int cpu)
* Range mapped buffers have the same restrictions as memory
* mapped ones do.
*/
- cpu_buffer->mapped = 1;
cpu_buffer->ring_meta = rb_range_meta(buffer, nr_pages, cpu);
bpage->page = rb_range_buffer(cpu_buffer, 0);
if (!bpage->page)
@@ -2530,6 +2609,8 @@ rb_allocate_cpu_buffer(struct trace_buffer *buffer, long nr_pages, int cpu)
if (cpu_buffer->ring_meta->head_buffer)
rb_meta_buffer_update(cpu_buffer, bpage);
bpage->range = 1;
+
+ atomic_inc(&cpu_buffer->resize_disabled);
} else if (buffer->remote) {
struct ring_buffer_desc *desc = ring_buffer_desc(buffer->remote->desc, cpu);
@@ -2539,8 +2620,8 @@ rb_allocate_cpu_buffer(struct trace_buffer *buffer, long nr_pages, int cpu)
cpu_buffer->remote = buffer->remote;
cpu_buffer->meta_page = (struct trace_buffer_meta *)(void *)desc->meta_va;
cpu_buffer->nr_pages = nr_pages;
- cpu_buffer->subbuf_ids = kcalloc(cpu_buffer->nr_pages + 1,
- sizeof(*cpu_buffer->subbuf_ids), GFP_KERNEL);
+ cpu_buffer->subbuf_ids = kzalloc_objs(*cpu_buffer->subbuf_ids,
+ cpu_buffer->nr_pages + 1);
if (!cpu_buffer->subbuf_ids)
goto fail_free_reader;
@@ -2555,8 +2636,7 @@ rb_allocate_cpu_buffer(struct trace_buffer *buffer, long nr_pages, int cpu)
bpage->range = 1;
cpu_buffer->subbuf_ids[0] = bpage;
} else {
- int order = cpu_buffer->buffer->subbuf_order;
- bpage->page = alloc_cpu_data(cpu, order);
+ bpage->page = alloc_cpu_data(cpu, bpage->order);
if (!bpage->page)
goto fail_free_reader;
}
@@ -2601,6 +2681,7 @@ rb_allocate_cpu_buffer(struct trace_buffer *buffer, long nr_pages, int cpu)
return_ptr(cpu_buffer);
fail_free_reader:
+ kfree(cpu_buffer->subbuf_ids);
free_buffer_page(cpu_buffer->reader_page);
return NULL;
@@ -2629,7 +2710,7 @@ static void rb_free_cpu_buffer(struct ring_buffer_per_cpu *cpu_buffer)
free_buffer_page(bpage);
}
- free_page((unsigned long)cpu_buffer->free_page);
+ free_pages((unsigned long)cpu_buffer->free_page.data, cpu_buffer->free_page.order);
kfree(cpu_buffer);
}
@@ -2641,8 +2722,8 @@ static void rb_test_inject_invalid_pages(struct trace_buffer *buffer)
struct ring_buffer_cpu_meta *meta;
struct buffer_data_page *dpage;
unsigned long entry_bytes = 0;
+ unsigned int subbuf_size;
unsigned long ptr;
- int subbuf_size;
int invalid = 0;
int cpu;
int i;
@@ -2712,8 +2793,8 @@ static struct trace_buffer *alloc_buffer(unsigned long size, unsigned flags,
struct ring_buffer_remote *remote)
{
struct trace_buffer *buffer __free(kfree) = NULL;
- long nr_pages;
- int subbuf_size;
+ unsigned int subbuf_size;
+ unsigned long nr_pages;
int bsize;
int cpu;
int ret;
@@ -2729,10 +2810,6 @@ static struct trace_buffer *alloc_buffer(unsigned long size, unsigned flags,
buffer->subbuf_order = order;
subbuf_size = (PAGE_SIZE << order);
- buffer->subbuf_size = subbuf_size - BUF_PAGE_HDR_SIZE;
-
- /* Max payload is buffer page size - header (8bytes) */
- buffer->max_data_size = buffer->subbuf_size - (sizeof(u32) * 2);
buffer->flags = flags;
buffer->clock = trace_clock_local;
@@ -2741,7 +2818,6 @@ static struct trace_buffer *alloc_buffer(unsigned long size, unsigned flags,
init_irq_work(&buffer->irq_work.work, rb_wake_up_waiters);
init_waitqueue_head(&buffer->irq_work.waiters);
- buffer->cpus = nr_cpu_ids;
bsize = sizeof(void *) * nr_cpu_ids;
buffer->buffers = kzalloc(ALIGN(bsize, cache_line_size()),
@@ -2772,6 +2848,8 @@ static struct trace_buffer *alloc_buffer(unsigned long size, unsigned flags,
size = end - buffers_start;
size = size / nr_cpu_ids;
+ if (size < sizeof(struct ring_buffer_cpu_meta))
+ goto fail_free_buffers;
/*
* The number of sub-buffers (nr_pages) is determined by the
* total size allocated minus the meta data size.
@@ -2781,6 +2859,10 @@ static struct trace_buffer *alloc_buffer(unsigned long size, unsigned flags,
*/
nr_pages = (size - sizeof(struct ring_buffer_cpu_meta)) /
(subbuf_size + sizeof(int));
+
+ if (nr_pages > rb_static_max_pages())
+ goto fail_free_buffers;
+
/* Need at least two pages plus the reader page */
if (nr_pages < 3)
goto fail_free_buffers;
@@ -2813,12 +2895,15 @@ static struct trace_buffer *alloc_buffer(unsigned long size, unsigned flags,
/* The writer is remote. This ring-buffer is read-only */
atomic_inc(&buffer->record_disabled);
nr_pages = desc->nr_page_va - 1;
+
+ if (nr_pages > rb_static_max_pages())
+ goto fail_free_buffers;
+
if (nr_pages < 2)
goto fail_free_buffers;
} else {
-
/* need at least two pages */
- nr_pages = DIV_ROUND_UP(size, buffer->subbuf_size);
+ nr_pages = DIV_ROUND_UP(size, rb_subbuf_capacity(buffer));
if (nr_pages < 2)
nr_pages = 2;
}
@@ -3201,7 +3286,7 @@ static void update_pages_handler(struct work_struct *work)
* @size: the new size.
* @cpu_id: the cpu buffer to resize
*
- * Minimum size is 2 * buffer->subbuf_size.
+ * Minimum size is 2 * rb_subbuf_capacity(buffer).
*
* Returns 0 on success and < 0 on failure.
*/
@@ -3223,12 +3308,6 @@ int ring_buffer_resize(struct trace_buffer *buffer, unsigned long size,
!cpumask_test_cpu(cpu_id, buffer->cpumask))
return 0;
- nr_pages = DIV_ROUND_UP(size, buffer->subbuf_size);
-
- /* we need a minimum of two pages */
- if (nr_pages < 2)
- nr_pages = 2;
-
/*
* Keep CPUs from coming online while resizing to synchronize
* with new per CPU buffers being created.
@@ -3239,6 +3318,12 @@ int ring_buffer_resize(struct trace_buffer *buffer, unsigned long size,
mutex_lock(&buffer->mutex);
atomic_inc(&buffer->resizing);
+ nr_pages = DIV_ROUND_UP(size, rb_subbuf_capacity(buffer));
+
+ /* we need a minimum of two pages */
+ if (nr_pages < 2)
+ nr_pages = 2;
+
if (cpu_id == RING_BUFFER_ALL_CPUS) {
/*
* Don't succeed if resizing is disabled, as a reader might be
@@ -3511,7 +3596,7 @@ rb_event_index(struct ring_buffer_per_cpu *cpu_buffer, struct ring_buffer_event
{
unsigned long addr = (unsigned long)event;
- addr &= (PAGE_SIZE << cpu_buffer->buffer->subbuf_order) - 1;
+ addr &= (unsigned long)rb_subbuf_size(cpu_buffer->buffer) - 1;
return addr - BUF_PAGE_HDR_SIZE;
}
@@ -3753,8 +3838,8 @@ static inline void
rb_reset_tail(struct ring_buffer_per_cpu *cpu_buffer,
unsigned long tail, struct rb_event_info *info)
{
- unsigned long bsize = READ_ONCE(cpu_buffer->buffer->subbuf_size);
struct buffer_page *tail_page = info->tail_page;
+ unsigned long bsize = rb_page_capacity(tail_page);
struct ring_buffer_event *event;
unsigned long length = info->length;
@@ -4099,8 +4184,7 @@ rb_try_to_discard(struct ring_buffer_per_cpu *cpu_buffer,
new_index = rb_event_index(cpu_buffer, event);
old_index = new_index + rb_event_ts_length(event);
- addr = (unsigned long)event;
- addr &= ~((PAGE_SIZE << cpu_buffer->buffer->subbuf_order) - 1);
+ addr = rb_subbuf_start(cpu_buffer->buffer, (unsigned long)event);
bpage = READ_ONCE(cpu_buffer->tail_page);
@@ -4765,7 +4849,7 @@ __rb_reserve_next(struct ring_buffer_per_cpu *cpu_buffer,
tail = write - info->length;
/* See if we shot pass the end of this buffer page */
- if (unlikely(write > cpu_buffer->buffer->subbuf_size)) {
+ if (unlikely(write > rb_page_capacity(tail_page))) {
check_buffer(cpu_buffer, info, CHECK_FULL_PAGE);
return rb_move_tail(cpu_buffer, tail, info);
}
@@ -4901,7 +4985,7 @@ rb_reserve_next_event(struct trace_buffer *buffer,
if (ring_buffer_time_stamp_abs(cpu_buffer->buffer)) {
add_ts_default = RB_ADD_STAMP_ABSOLUTE;
info.length += RB_LEN_TIME_EXTEND;
- if (info.length > cpu_buffer->buffer->max_data_size)
+ if (info.length > rb_subbuf_max_data_size(cpu_buffer->buffer))
goto out_fail;
} else {
add_ts_default = RB_ADD_STAMP_NONE;
@@ -4976,7 +5060,7 @@ ring_buffer_lock_reserve(struct trace_buffer *buffer, unsigned long length)
if (unlikely(atomic_read(&cpu_buffer->record_disabled)))
goto out;
- if (unlikely(length > buffer->max_data_size))
+ if (unlikely(length > rb_subbuf_max_data_size(buffer)))
goto out;
if (unlikely(trace_recursive_lock(cpu_buffer)))
@@ -5010,7 +5094,7 @@ rb_decrement_entry(struct ring_buffer_per_cpu *cpu_buffer,
struct buffer_page *bpage = cpu_buffer->commit_page;
struct buffer_page *start;
- addr &= ~((PAGE_SIZE << cpu_buffer->buffer->subbuf_order) - 1);
+ addr = rb_subbuf_start(cpu_buffer->buffer, addr);
/* Do the likely case first */
if (likely(bpage->page == (void *)addr)) {
@@ -5123,7 +5207,7 @@ int ring_buffer_write(struct trace_buffer *buffer,
if (atomic_read(&cpu_buffer->record_disabled))
return -EBUSY;
- if (length > buffer->max_data_size)
+ if (length > rb_subbuf_max_data_size(buffer))
return -EBUSY;
if (unlikely(trace_recursive_lock(cpu_buffer)))
@@ -5751,8 +5835,11 @@ __rb_get_reader_page_from_remote(struct ring_buffer_per_cpu *cpu_buffer)
prev_reader = cpu_buffer->subbuf_ids[cpu_buffer->meta_page->reader.id];
- WARN_ON_ONCE(cpu_buffer->remote->swap_reader_page(cpu_buffer->cpu,
- cpu_buffer->remote->priv));
+ if (cpu_buffer->remote->swap_reader_page(cpu_buffer->cpu,
+ cpu_buffer->remote->priv)) {
+ pr_warn_ratelimited("Remote reader page swap failed\n");
+ return NULL;
+ }
/* nr_pages doesn't include the reader page */
if (WARN_ON_ONCE(cpu_buffer->meta_page->reader.id > cpu_buffer->nr_pages))
return NULL;
@@ -5785,6 +5872,7 @@ __rb_get_reader_page_from_remote(struct ring_buffer_per_cpu *cpu_buffer)
cpu_buffer->head_page = new_head;
cpu_buffer->reader_page = new_reader;
+ cpu_buffer->reader_page->read = 0;
cpu_buffer->pages = &new_head->list;
cpu_buffer->read_stamp = new_reader->page->time_stamp;
cpu_buffer->lost_events = cpu_buffer->meta_page->reader.lost_events;
@@ -5795,13 +5883,12 @@ __rb_get_reader_page_from_remote(struct ring_buffer_per_cpu *cpu_buffer)
static struct buffer_page *
__rb_get_reader_page(struct ring_buffer_per_cpu *cpu_buffer)
{
- int max_loops = cpu_buffer->ring_meta ? cpu_buffer->nr_pages : 3;
- unsigned long bsize = READ_ONCE(cpu_buffer->buffer->subbuf_size);
+ unsigned long max_loops = cpu_buffer->ring_meta ? cpu_buffer->nr_pages : 3;
struct buffer_page *reader = NULL;
+ unsigned long nr_loops = 0;
unsigned long overwrite;
unsigned long flags;
int missed_events = 0;
- int nr_loops = 0;
bool ret;
local_irq_save(flags);
@@ -5944,7 +6031,7 @@ __rb_get_reader_page(struct ring_buffer_per_cpu *cpu_buffer)
#define USECS_WAIT 1000000
for (nr_loops = 0; nr_loops < USECS_WAIT; nr_loops++) {
/* If the write is past the end of page, a writer is still updating it */
- if (likely(!reader || rb_page_write(reader) <= bsize))
+ if (likely(!reader || rb_page_write(reader) <= rb_page_capacity(reader)))
break;
udelay(1);
@@ -6119,8 +6206,8 @@ rb_iter_peek(struct ring_buffer_iter *iter, u64 *ts)
struct trace_buffer *buffer;
struct ring_buffer_per_cpu *cpu_buffer;
struct ring_buffer_event *event;
- int nr_loops = 0;
- int max_loops;
+ unsigned long nr_loops = 0;
+ unsigned long max_loops;
if (ts)
*ts = 0;
@@ -6377,36 +6464,44 @@ EXPORT_SYMBOL_GPL(ring_buffer_consume);
struct ring_buffer_iter *
ring_buffer_read_start(struct trace_buffer *buffer, int cpu, gfp_t flags)
{
+ struct ring_buffer_iter *iter __free(kfree) = kzalloc_obj(*iter, flags);
struct ring_buffer_per_cpu *cpu_buffer;
- struct ring_buffer_iter *iter;
- if (!cpumask_test_cpu(cpu, buffer->cpumask))
- return NULL;
-
- iter = kzalloc_obj(*iter, flags);
if (!iter)
return NULL;
- /* Holds the entire event: data and meta data */
- iter->event_size = buffer->subbuf_size;
- iter->event = kmalloc(iter->event_size, flags);
- if (!iter->event) {
- kfree(iter);
+ if (!cpumask_test_cpu(cpu, buffer->cpumask))
return NULL;
- }
cpu_buffer = buffer->buffers[cpu];
- iter->cpu_buffer = cpu_buffer;
+ /*
+ * Only KDB is using GFP_ATOMIC, for the others, lock the buffer to
+ * prevent concurrent resizing.
+ */
+ if (gfpflags_allow_blocking(flags))
+ mutex_lock(&buffer->mutex);
atomic_inc(&cpu_buffer->resize_disabled);
+ if (gfpflags_allow_blocking(flags))
+ mutex_unlock(&buffer->mutex);
+
+ /* Holds the entire event: data and meta data. */
+ iter->event_size = rb_page_capacity(READ_ONCE(cpu_buffer->reader_page));
+ iter->event = kmalloc(iter->event_size, flags);
+ if (!iter->event) {
+ atomic_dec(&cpu_buffer->resize_disabled);
+ return NULL;
+ }
+ iter->cpu_buffer = cpu_buffer;
+
guard(raw_spinlock_irqsave)(&cpu_buffer->reader_lock);
arch_spin_lock(&cpu_buffer->lock);
rb_iter_reset(iter);
arch_spin_unlock(&cpu_buffer->lock);
- return iter;
+ return_ptr(iter);
}
EXPORT_SYMBOL_GPL(ring_buffer_read_start);
@@ -6460,7 +6555,7 @@ unsigned long ring_buffer_size(struct trace_buffer *buffer, int cpu)
if (!cpumask_test_cpu(cpu, buffer->cpumask))
return 0;
- return buffer->subbuf_size * buffer->buffers[cpu]->nr_pages;
+ return rb_subbuf_capacity(buffer) * buffer->buffers[cpu]->nr_pages;
}
EXPORT_SYMBOL_GPL(ring_buffer_size);
@@ -6474,8 +6569,9 @@ unsigned long ring_buffer_max_event_size(struct trace_buffer *buffer)
{
/* If abs timestamp is requested, events have a timestamp too */
if (ring_buffer_time_stamp_abs(buffer))
- return buffer->max_data_size - RB_LEN_TIME_EXTEND;
- return buffer->max_data_size;
+ return rb_subbuf_max_data_size(buffer) - RB_LEN_TIME_EXTEND;
+
+ return rb_subbuf_max_data_size(buffer);
}
EXPORT_SYMBOL_GPL(ring_buffer_max_event_size);
@@ -6607,12 +6703,11 @@ rb_reset_cpu(struct ring_buffer_per_cpu *cpu_buffer)
rb_head_page_activate(cpu_buffer);
cpu_buffer->pages_removed = 0;
- if (cpu_buffer->mapped) {
- rb_update_meta_page(cpu_buffer);
- if (cpu_buffer->ring_meta) {
- struct ring_buffer_cpu_meta *meta = cpu_buffer->ring_meta;
- meta->commit_buffer = meta->head_buffer;
- }
+ rb_update_meta_page(cpu_buffer);
+ if (cpu_buffer->ring_meta) {
+ struct ring_buffer_cpu_meta *meta = cpu_buffer->ring_meta;
+
+ meta->commit_buffer = meta->head_buffer;
}
}
@@ -6852,7 +6947,7 @@ int ring_buffer_swap_cpu(struct trace_buffer *buffer_a,
{
struct ring_buffer_per_cpu *cpu_buffer_a;
struct ring_buffer_per_cpu *cpu_buffer_b;
- int ret = -EINVAL;
+ int ret = -EBUSY;
if (!cpumask_test_cpu(cpu, buffer_a->cpumask) ||
!cpumask_test_cpu(cpu, buffer_b->cpumask))
@@ -6861,8 +6956,8 @@ int ring_buffer_swap_cpu(struct trace_buffer *buffer_a,
cpu_buffer_a = buffer_a->buffers[cpu];
cpu_buffer_b = buffer_b->buffers[cpu];
- /* It's up to the callers to not try to swap mapped buffers */
- if (WARN_ON_ONCE(cpu_buffer_a->mapped || cpu_buffer_b->mapped))
+ /* It's up to the callers to not try to swap static buffers */
+ if (WARN_ON_ONCE(rb_is_static(cpu_buffer_a) || rb_is_static(cpu_buffer_b)))
return -EBUSY;
/* At least make sure the two buffers are somewhat the same */
@@ -6893,10 +6988,10 @@ int ring_buffer_swap_cpu(struct trace_buffer *buffer_a,
atomic_inc(&cpu_buffer_a->record_disabled);
atomic_inc(&cpu_buffer_b->record_disabled);
- ret = -EBUSY;
- if (local_read(&cpu_buffer_a->committing))
+ /* Do not swap if either buffer is in the process of writing */
+ if (cpu_buffer_a->current_context)
goto out_dec;
- if (local_read(&cpu_buffer_b->committing))
+ if (cpu_buffer_b->current_context)
goto out_dec;
/*
@@ -6928,56 +7023,78 @@ EXPORT_SYMBOL_GPL(ring_buffer_swap_cpu);
* ring_buffer_alloc_read_page - allocate a page to read from buffer
* @buffer: the buffer to allocate for.
* @cpu: the cpu buffer to allocate.
+ * @rpage: pointer to pass in an already allocated page (can be NULL)
+ * and returns the allocated page.
*
- * This function is used in conjunction with ring_buffer_read_page.
+ * This function is used in conjunction with ring_buffer_read_page().
* When reading a full page from the ring buffer, these functions
* can be used to speed up the process. The calling function should
* allocate a few pages first with this function. Then when it
* needs to get pages from the ring buffer, it passes the result
- * of this function into ring_buffer_read_page, which will swap
+ * of this function into ring_buffer_read_page(), which will swap
* the page that was allocated, with the read page of the buffer.
*
+ * If @rpage is provided, and it has a different order than the current
+ * subbuffer order, its payload will be freed and re-allocated. If it
+ * already matches the order, it is simply returned.
+ *
* Returns:
- * The page allocated, or ERR_PTR
+ * 0 on success, < 0 on error
*/
-struct buffer_data_read_page *
-ring_buffer_alloc_read_page(struct trace_buffer *buffer, int cpu)
+int ring_buffer_alloc_read_page(struct trace_buffer *buffer, int cpu,
+ struct buffer_data_read_page **rpage)
{
struct ring_buffer_per_cpu *cpu_buffer;
- struct buffer_data_read_page *bpage = NULL;
unsigned long flags;
+ unsigned int order;
if (!cpumask_test_cpu(cpu, buffer->cpumask))
- return ERR_PTR(-ENODEV);
+ return -ENODEV;
- bpage = kzalloc_obj(*bpage);
- if (!bpage)
- return ERR_PTR(-ENOMEM);
+ if (!rpage)
+ return -EINVAL;
- bpage->order = buffer->subbuf_order;
+ order = READ_ONCE(buffer->subbuf_order);
+
+ if (*rpage) {
+ if ((*rpage)->order == order)
+ return 0;
+
+ /* We can reuse rpage, but we discard the payload */
+ free_pages((unsigned long)(*rpage)->data, (*rpage)->order);
+ (*rpage)->data = NULL;
+ } else {
+ *rpage = kzalloc_obj(**rpage);
+ if (!*rpage)
+ return -ENOMEM;
+ }
+
+ (*rpage)->order = order;
cpu_buffer = buffer->buffers[cpu];
+
local_irq_save(flags);
arch_spin_lock(&cpu_buffer->lock);
- if (cpu_buffer->free_page) {
- bpage->data = cpu_buffer->free_page;
- cpu_buffer->free_page = NULL;
+ if (cpu_buffer->free_page.data) {
+ **rpage = cpu_buffer->free_page;
+ cpu_buffer->free_page.data = NULL;
}
arch_spin_unlock(&cpu_buffer->lock);
local_irq_restore(flags);
- if (bpage->data) {
- rb_init_data_page(bpage->data);
+ if ((*rpage)->data) {
+ rb_init_data_page((*rpage)->data);
} else {
- bpage->data = alloc_cpu_data(cpu, cpu_buffer->buffer->subbuf_order);
- if (!bpage->data) {
- kfree(bpage);
- return ERR_PTR(-ENOMEM);
+ (*rpage)->data = alloc_cpu_data(cpu, (*rpage)->order);
+ if (!(*rpage)->data) {
+ kfree(*rpage);
+ *rpage = NULL;
+ return -ENOMEM;
}
}
- return bpage;
+ return 0;
}
EXPORT_SYMBOL_GPL(ring_buffer_alloc_read_page);
@@ -6985,21 +7102,30 @@ EXPORT_SYMBOL_GPL(ring_buffer_alloc_read_page);
* ring_buffer_free_read_page - free an allocated read page
* @buffer: the buffer the page was allocate for
* @cpu: the cpu buffer the page came from
- * @data_page: the page to free
+ * @rpage: the buffer_data_read_page to free
*
* Free a page allocated from ring_buffer_alloc_read_page.
*/
void ring_buffer_free_read_page(struct trace_buffer *buffer, int cpu,
- struct buffer_data_read_page *data_page)
+ struct buffer_data_read_page *rpage)
{
struct ring_buffer_per_cpu *cpu_buffer;
- struct buffer_data_page *dpage = data_page->data;
- struct page *page = virt_to_page(dpage);
+ struct buffer_data_page *dpage;
unsigned long flags;
+ struct page *page;
if (!buffer || !buffer->buffers || !buffer->buffers[cpu])
return;
+ if (!rpage)
+ return;
+
+ dpage = rpage->data;
+ if (!dpage)
+ goto out;
+
+ page = virt_to_page(dpage);
+
cpu_buffer = buffer->buffers[cpu];
/*
@@ -7007,14 +7133,14 @@ void ring_buffer_free_read_page(struct trace_buffer *buffer, int cpu,
* is different from the subbuffer order of the buffer -
* we can't reuse it
*/
- if (page_ref_count(page) > 1 || data_page->order != buffer->subbuf_order)
+ if (page_ref_count(page) > 1 || rpage->order != READ_ONCE(buffer->subbuf_order))
goto out;
local_irq_save(flags);
arch_spin_lock(&cpu_buffer->lock);
- if (!cpu_buffer->free_page) {
- cpu_buffer->free_page = dpage;
+ if (!cpu_buffer->free_page.data) {
+ cpu_buffer->free_page = *rpage;
dpage = NULL;
}
@@ -7022,8 +7148,8 @@ void ring_buffer_free_read_page(struct trace_buffer *buffer, int cpu,
local_irq_restore(flags);
out:
- free_pages((unsigned long)dpage, data_page->order);
- kfree(data_page);
+ free_pages((unsigned long)dpage, rpage->order);
+ kfree(rpage);
}
EXPORT_SYMBOL_GPL(ring_buffer_free_read_page);
@@ -7074,7 +7200,6 @@ int ring_buffer_read_page(struct trace_buffer *buffer,
unsigned int size;
unsigned int read;
u64 save_timestamp;
- bool force_memcpy;
if (!cpumask_test_cpu(cpu, buffer->cpumask))
return -1;
@@ -7091,13 +7216,12 @@ int ring_buffer_read_page(struct trace_buffer *buffer,
if (!data_page || !data_page->data)
return -1;
- if (data_page->order != buffer->subbuf_order)
- return -1;
-
dpage = data_page->data;
if (!dpage)
return -1;
+ len = min_t(size_t, len, rb_read_page_capacity(data_page));
+
guard(raw_spinlock_irqsave)(&cpu_buffer->reader_lock);
reader = rb_get_reader_page(cpu_buffer);
@@ -7113,33 +7237,26 @@ int ring_buffer_read_page(struct trace_buffer *buffer,
/* Check if any events were dropped */
missed_events = cpu_buffer->lost_events;
- force_memcpy = cpu_buffer->mapped || cpu_buffer->remote;
-
- /*
- * If this page has been partially read or
- * if len is not big enough to read the rest of the page or
- * a writer is still on the page, then
- * we must copy the data from the page to the buffer.
- * Otherwise, we can simply swap the page with the one passed in.
- */
+ /*
+ * It is not possible to swap the reader page if:
+ * - It has been partially read
+ * - len is not big enough to read it entirely
+ * - A writer is still on it
+ * - The ring buffer is static
+ * - The order doesn't match
+ */
if (read || (len < (size - read)) ||
cpu_buffer->reader_page == cpu_buffer->commit_page ||
- force_memcpy) {
+ rb_is_static(cpu_buffer) ||
+ data_page->order != reader->order) {
struct buffer_data_page *rpage = cpu_buffer->reader_page->page;
unsigned int rpos = read;
unsigned int pos = 0;
unsigned int event_size;
unsigned int flags = 0;
- /*
- * If a full page is expected, this can still be returned
- * if there's been a previous partial read and the
- * rest of the page can be read and the commit page is off
- * the reader page.
- */
- if (full &&
- (!read || (len < (size - read)) ||
- cpu_buffer->reader_page == cpu_buffer->commit_page))
+ /* If a full page is requested, it cannot be the commit page */
+ if (full && cpu_buffer->reader_page == cpu_buffer->commit_page)
return -1;
if (len > (size - read))
@@ -7174,7 +7291,7 @@ int ring_buffer_read_page(struct trace_buffer *buffer,
rpos = reader->read;
pos += event_size;
- if (rpos >= event_size)
+ if (rpos >= size)
break;
event = rb_reader_event(cpu_buffer);
@@ -7225,7 +7342,7 @@ int ring_buffer_read_page(struct trace_buffer *buffer,
* missed events, then record it there.
*/
if (missed_events > 0 &&
- buffer->subbuf_size - size >= sizeof(missed_events)) {
+ rb_read_page_capacity(data_page) - size >= sizeof(missed_events)) {
memcpy(&dpage->data[size], &missed_events,
sizeof(missed_events));
local_add(RB_MISSED_STORED, &dpage->commit);
@@ -7245,8 +7362,8 @@ int ring_buffer_read_page(struct trace_buffer *buffer,
/*
* This page may be off to user land. Zero it out here.
*/
- if (size < buffer->subbuf_size)
- memset(&dpage->data[size], 0, buffer->subbuf_size - size);
+ if (size < rb_read_page_capacity(data_page))
+ memset(&dpage->data[size], 0, rb_read_page_capacity(data_page) - size);
return read;
}
@@ -7265,6 +7382,18 @@ void *ring_buffer_read_page_data(struct buffer_data_read_page *page)
EXPORT_SYMBOL_GPL(ring_buffer_read_page_data);
/**
+ * ring_buffer_read_page_size - get size of the read page.
+ * @rpage: the page to get the size from
+ *
+ * Returns size of the page in bytes.
+ */
+unsigned int ring_buffer_read_page_size(struct buffer_data_read_page *rpage)
+{
+ return rpage ? PAGE_SIZE << rpage->order : 0;
+}
+EXPORT_SYMBOL_GPL(ring_buffer_read_page_size);
+
+/**
* ring_buffer_subbuf_size_get - get size of the sub buffer.
* @buffer: the buffer to get the sub buffer size from
*
@@ -7272,7 +7401,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_read_page_data);
*/
int ring_buffer_subbuf_size_get(struct trace_buffer *buffer)
{
- return buffer->subbuf_size + BUF_PAGE_HDR_SIZE;
+ return rb_subbuf_size(buffer);
}
EXPORT_SYMBOL_GPL(ring_buffer_subbuf_size_get);
@@ -7317,8 +7446,9 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order)
{
struct ring_buffer_per_cpu *cpu_buffer;
struct buffer_page *bpage, *tmp;
- int old_order, old_size;
- int nr_pages;
+ unsigned int old_capacity;
+ unsigned long nr_pages;
+ int old_order;
int psize;
int err;
int cpu;
@@ -7326,9 +7456,6 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order)
if (!buffer || order < 0)
return -EINVAL;
- if (buffer->subbuf_order == order)
- return 0;
-
psize = (1 << order) * PAGE_SIZE;
if (psize <= BUF_PAGE_HDR_SIZE)
return -EINVAL;
@@ -7337,18 +7464,29 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order)
if (psize > RB_WRITE_MASK + 1)
return -EINVAL;
- old_order = buffer->subbuf_order;
- old_size = buffer->subbuf_size;
-
/* prevent another thread from changing buffer sizes */
guard(mutex)(&buffer->mutex);
+
+ old_order = buffer->subbuf_order;
+ if (old_order == order)
+ return 0;
+
+ old_capacity = rb_subbuf_capacity(buffer);
+
+ /* The mmap fast path reads subbuf_order without buffer->mutex. */
+ for_each_buffer_cpu(buffer, cpu) {
+ if (!cpumask_test_cpu(cpu, buffer->cpumask))
+ continue;
+ if (atomic_read(&buffer->buffers[cpu]->resize_disabled))
+ return -EBUSY;
+ }
+
atomic_inc(&buffer->record_disabled);
/* Make sure all commits have finished */
synchronize_rcu();
- buffer->subbuf_order = order;
- buffer->subbuf_size = psize - BUF_PAGE_HDR_SIZE;
+ WRITE_ONCE(buffer->subbuf_order, order);
/* Make sure all new buffers are allocated, before deleting the old ones */
for_each_buffer_cpu(buffer, cpu) {
@@ -7358,14 +7496,14 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order)
cpu_buffer = buffer->buffers[cpu];
- if (cpu_buffer->mapped) {
+ if (atomic_read(&cpu_buffer->resize_disabled)) {
err = -EBUSY;
goto error;
}
/* Update the number of pages to match the new size */
- nr_pages = old_size * buffer->buffers[cpu]->nr_pages;
- nr_pages = DIV_ROUND_UP(nr_pages, buffer->subbuf_size);
+ nr_pages = old_capacity * buffer->buffers[cpu]->nr_pages;
+ nr_pages = DIV_ROUND_UP(nr_pages, rb_subbuf_capacity(buffer));
/* we need a minimum of two pages */
if (nr_pages < 2)
@@ -7387,7 +7525,7 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order)
}
for_each_buffer_cpu(buffer, cpu) {
- struct buffer_data_page *old_free_data_page;
+ struct buffer_data_read_page old_free_data_page;
struct list_head old_pages;
unsigned long flags;
@@ -7428,8 +7566,10 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order)
cpu_buffer->nr_pages = cpu_buffer->nr_pages_to_update;
cpu_buffer->nr_pages_to_update = 0;
+ arch_spin_lock(&cpu_buffer->lock);
old_free_data_page = cpu_buffer->free_page;
- cpu_buffer->free_page = NULL;
+ cpu_buffer->free_page.data = NULL;
+ arch_spin_unlock(&cpu_buffer->lock);
rb_head_page_activate(cpu_buffer);
@@ -7440,7 +7580,7 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order)
list_del_init(&bpage->list);
free_buffer_page(bpage);
}
- free_pages((unsigned long)old_free_data_page, old_order);
+ free_pages((unsigned long)old_free_data_page.data, old_free_data_page.order);
rb_check_pages(cpu_buffer);
}
@@ -7450,8 +7590,7 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order)
return 0;
error:
- buffer->subbuf_order = old_order;
- buffer->subbuf_size = old_size;
+ WRITE_ONCE(buffer->subbuf_order, old_order);
atomic_dec(&buffer->record_disabled);
@@ -7499,10 +7638,10 @@ static void rb_setup_ids_meta_page(struct ring_buffer_per_cpu *cpu_buffer,
struct buffer_page **subbuf_ids)
{
struct trace_buffer_meta *meta = cpu_buffer->meta_page;
- unsigned int nr_subbufs = cpu_buffer->nr_pages + 1;
+ unsigned long nr_subbufs = cpu_buffer->nr_pages + 1;
struct buffer_page *first_subbuf, *subbuf;
- int cnt = 0;
- int id = 0;
+ unsigned int cnt = 0;
+ unsigned int id = 0;
id = rb_page_id(cpu_buffer, cpu_buffer->reader_page, id);
subbuf_ids[id++] = cpu_buffer->reader_page;
@@ -7529,7 +7668,7 @@ static void rb_setup_ids_meta_page(struct ring_buffer_per_cpu *cpu_buffer,
meta->meta_struct_len = sizeof(*meta);
meta->nr_subbufs = nr_subbufs;
- meta->subbuf_size = cpu_buffer->buffer->subbuf_size + BUF_PAGE_HDR_SIZE;
+ meta->subbuf_size = rb_subbuf_size(cpu_buffer->buffer);
meta->meta_page_size = meta->subbuf_size;
rb_update_meta_page(cpu_buffer);
@@ -7571,11 +7710,7 @@ static int __rb_inc_dec_mapped(struct ring_buffer_per_cpu *cpu_buffer,
lockdep_assert_held(&cpu_buffer->mapping_lock);
- /* mapped is always greater or equal to user_mapped */
- if (WARN_ON(cpu_buffer->mapped < cpu_buffer->user_mapped))
- return -EINVAL;
-
- if (inc && cpu_buffer->mapped == UINT_MAX)
+ if (inc && cpu_buffer->user_mapped == UINT_MAX)
return -EBUSY;
if (WARN_ON(!inc && cpu_buffer->user_mapped == 0))
@@ -7584,13 +7719,10 @@ static int __rb_inc_dec_mapped(struct ring_buffer_per_cpu *cpu_buffer,
mutex_lock(&cpu_buffer->buffer->mutex);
raw_spin_lock_irqsave(&cpu_buffer->reader_lock, flags);
- if (inc) {
+ if (inc)
cpu_buffer->user_mapped++;
- cpu_buffer->mapped++;
- } else {
+ else
cpu_buffer->user_mapped--;
- cpu_buffer->mapped--;
- }
raw_spin_unlock_irqrestore(&cpu_buffer->reader_lock, flags);
mutex_unlock(&cpu_buffer->buffer->mutex);
@@ -7613,7 +7745,8 @@ static int __rb_inc_dec_mapped(struct ring_buffer_per_cpu *cpu_buffer,
static int __rb_map_vma(struct ring_buffer_per_cpu *cpu_buffer,
struct vm_area_struct *vma)
{
- unsigned long nr_subbufs, nr_pages, nr_vma_pages, pgoff = vma->vm_pgoff;
+ unsigned long nr_subbufs, nr_pages, nr_vma_pages;
+ pgoff_t pgoff = vma_start_pgoff(vma);
unsigned int subbuf_pages, subbuf_order;
struct page **pages __free(kfree) = NULL;
int p = 0, s = 0;
@@ -7736,6 +7869,9 @@ int ring_buffer_map(struct trace_buffer *buffer, int cpu,
/* prevent another thread from changing buffer/sub-buffer sizes */
guard(mutex)(&buffer->mutex);
+ if (cpu_buffer->nr_pages > rb_static_max_pages())
+ return -E2BIG;
+
err = rb_alloc_meta_page(cpu_buffer);
if (err)
return err;
@@ -7762,7 +7898,6 @@ int ring_buffer_map(struct trace_buffer *buffer, int cpu,
if (!err) {
raw_spin_lock_irqsave(&cpu_buffer->reader_lock, flags);
/* This is the first time it is mapped by user */
- cpu_buffer->mapped++;
cpu_buffer->user_mapped = 1;
raw_spin_unlock_irqrestore(&cpu_buffer->reader_lock, flags);
} else {
@@ -7819,8 +7954,6 @@ int ring_buffer_unmap(struct trace_buffer *buffer, int cpu)
raw_spin_lock_irqsave(&cpu_buffer->reader_lock, flags);
/* This is the last user space mapping */
- if (!WARN_ON_ONCE(cpu_buffer->mapped < cpu_buffer->user_mapped))
- cpu_buffer->mapped--;
cpu_buffer->user_mapped = 0;
raw_spin_unlock_irqrestore(&cpu_buffer->reader_lock, flags);
@@ -7891,7 +8024,7 @@ consume:
* missed events, then record it there.
*/
commit = rb_page_size(reader);
- if (buffer->subbuf_size - commit >= sizeof(missed_events)) {
+ if (rb_page_capacity(reader) - commit >= sizeof(missed_events)) {
memcpy(&dpage->data[commit], &missed_events,
sizeof(missed_events));
local_add(RB_MISSED_STORED, &dpage->commit);
@@ -7923,7 +8056,7 @@ consume:
out:
/* Some archs do not have data cache coherency between kernel and user-space */
flush_kernel_vmap_range(cpu_buffer->reader_page->page,
- buffer->subbuf_size + BUF_PAGE_HDR_SIZE);
+ rb_subbuf_size(buffer));
rb_update_meta_page(cpu_buffer);
@@ -8214,7 +8347,7 @@ static __init int test_ringbuffer(void)
out_free:
for_each_online_cpu(cpu) {
- if (!rb_threads[cpu])
+ if (IS_ERR_OR_NULL(rb_threads[cpu]))
break;
kthread_stop(rb_threads[cpu]);
}
diff --git a/kernel/trace/ring_buffer_benchmark.c b/kernel/trace/ring_buffer_benchmark.c
index 593e3b59e42e..c3d34c0e64e2 100644
--- a/kernel/trace/ring_buffer_benchmark.c
+++ b/kernel/trace/ring_buffer_benchmark.c
@@ -104,7 +104,7 @@ static enum event_status read_event(int cpu)
static enum event_status read_page(int cpu)
{
- struct buffer_data_read_page *bpage;
+ struct buffer_data_read_page *bpage = NULL;
struct ring_buffer_event *event;
struct rb_page *rpage;
unsigned long commit;
@@ -114,8 +114,8 @@ static enum event_status read_page(int cpu)
int inc;
int i;
- bpage = ring_buffer_alloc_read_page(buffer, cpu);
- if (IS_ERR(bpage))
+ ret = ring_buffer_alloc_read_page(buffer, cpu, &bpage);
+ if (ret < 0)
return EVENT_DROPPED;
page_size = ring_buffer_subbuf_size_get(buffer);
diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig
index 3884b14df375..efa930f94ea4 100644
--- a/kernel/trace/rv/Kconfig
+++ b/kernel/trace/rv/Kconfig
@@ -76,6 +76,7 @@ source "kernel/trace/rv/monitors/opid/Kconfig"
source "kernel/trace/rv/monitors/rtapp/Kconfig"
source "kernel/trace/rv/monitors/pagefault/Kconfig"
source "kernel/trace/rv/monitors/sleep/Kconfig"
+source "kernel/trace/rv/monitors/wakeup/Kconfig"
# Add new rtapp monitors here
source "kernel/trace/rv/monitors/stall/Kconfig"
@@ -111,3 +112,17 @@ config RV_REACT_PANIC
help
Enables the panic reactor. The panic reactor emits a printk()
message if an exception is found and panic()s the system.
+
+config RV_MONITORS_KUNIT_TEST
+ tristate "KUnit tests for RV monitors" if !KUNIT_ALL_TESTS
+ depends on KUNIT && RV && RV_REACTORS
+ default KUNIT_ALL_TESTS
+ help
+ Enable KUnit tests for the RV (Runtime Verification) monitors.
+ These tests verify that monitors correctly detect violations by
+ triggering fake events and validating the expected reactions.
+
+ Enabling this may slightly increase overhead of some monitors if any
+ unrelated KUnit test is running.
+
+ If unsure, say N.
diff --git a/kernel/trace/rv/Makefile b/kernel/trace/rv/Makefile
index 94498da35b37..cdbf68c84f5a 100644
--- a/kernel/trace/rv/Makefile
+++ b/kernel/trace/rv/Makefile
@@ -20,7 +20,9 @@ obj-$(CONFIG_RV_MON_OPID) += monitors/opid/opid.o
obj-$(CONFIG_RV_MON_STALL) += monitors/stall/stall.o
obj-$(CONFIG_RV_MON_DEADLINE) += monitors/deadline/deadline.o
obj-$(CONFIG_RV_MON_NOMISS) += monitors/nomiss/nomiss.o
+obj-$(CONFIG_RV_MON_WAKEUP) += monitors/wakeup/wakeup.o
# Add new monitors here
obj-$(CONFIG_RV_REACTORS) += rv_reactors.o
obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o
obj-$(CONFIG_RV_REACT_PANIC) += reactor_panic.o
+obj-$(CONFIG_RV_MONITORS_KUNIT_TEST) += rv_monitors_test.o
diff --git a/kernel/trace/rv/monitors/nomiss/nomiss.c b/kernel/trace/rv/monitors/nomiss/nomiss.c
index 8ead8783c29f..6e47d379f777 100644
--- a/kernel/trace/rv/monitors/nomiss/nomiss.c
+++ b/kernel/trace/rv/monitors/nomiss/nomiss.c
@@ -57,24 +57,12 @@ static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
enum states next_state, u64 time_ns)
{
if (curr_state == ready_nomiss)
- return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns);
+ return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns, DEADLINE_NS(ha_mon));
else if (curr_state == running_nomiss)
- return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns);
+ return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns, DEADLINE_NS(ha_mon));
return true;
}
-static inline void ha_convert_inv_guard(struct ha_monitor *ha_mon,
- enum states curr_state, enum events event,
- enum states next_state, u64 time_ns)
-{
- if (curr_state == next_state)
- return;
- if (curr_state == ready_nomiss)
- ha_inv_to_guard(ha_mon, clk_nomiss, DEADLINE_NS(ha_mon), time_ns);
- else if (curr_state == running_nomiss)
- ha_inv_to_guard(ha_mon, clk_nomiss, DEADLINE_NS(ha_mon), time_ns);
-}
-
static inline bool ha_verify_guards(struct ha_monitor *ha_mon,
enum states curr_state, enum events event,
enum states next_state, u64 time_ns)
@@ -122,8 +110,6 @@ static bool ha_verify_constraint(struct ha_monitor *ha_mon,
if (!ha_verify_invariants(ha_mon, curr_state, event, next_state, time_ns))
return false;
- ha_convert_inv_guard(ha_mon, curr_state, event, next_state, time_ns);
-
if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns))
return false;
@@ -291,3 +277,21 @@ module_exit(unregister_nomiss);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("nomiss: dl entities run to completion before their deadline.");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "nomiss_kunit.h"
+
+const struct rv_nomiss_ops rv_nomiss_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .deadline_thresh = &deadline_thresh,
+ .handle_dl_replenish = handle_dl_replenish,
+ .handle_dl_throttle = handle_dl_throttle,
+ .handle_dl_server_stop = handle_dl_server_stop,
+ .handle_sched_switch = handle_sched_switch,
+ .handle_sched_wakeup = handle_sched_wakeup,
+ .handle_sys_enter = handle_sys_enter,
+ .handle_newtask = handle_newtask,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_nomiss_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/nomiss/nomiss_kunit.c b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.c
new file mode 100644
index 000000000000..763129e2a990
--- /dev/null
+++ b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.c
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/sched.h>
+#include "nomiss_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_NOMISS)
+
+static void rv_test_nomiss(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct task_struct *other = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_nomiss_ops.mon);
+
+ target->pid = 99;
+ target->policy = SCHED_DEADLINE;
+ target->dl.runtime = 10000;
+ target->dl.dl_deadline = 20000;
+
+ rv_nomiss_ops.handle_newtask(NULL, target, 0);
+
+ /* Task gets preempted and can't terminate before deadline */
+ rv_nomiss_ops.handle_sched_switch(NULL, 0, other, target, TASK_RUNNING);
+ rv_nomiss_ops.handle_dl_replenish(NULL, &target->dl, 0, DL_TASK);
+ udelay(10);
+ rv_nomiss_ops.handle_sched_switch(NULL, 0, target, other, TASK_RUNNING);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) {
+ udelay(15 + div_u64(*rv_nomiss_ops.deadline_thresh, 1000));
+ rv_nomiss_ops.handle_sched_switch(NULL, 0, other, target, TASK_RUNNING);
+ }
+}
+
+#else
+#define rv_test_nomiss rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/nomiss/nomiss_kunit.h b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.h
new file mode 100644
index 000000000000..2be779c5dbaa
--- /dev/null
+++ b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __NOMISS_KUNIT_H
+#define __NOMISS_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_nomiss_ops {
+ struct rv_kunit_mon mon;
+ const u64 *deadline_thresh;
+ void (*handle_dl_replenish)(void *data, struct sched_dl_entity *dl_se,
+ int cpu, u8 type);
+ void (*handle_dl_throttle)(void *data, struct sched_dl_entity *dl_se,
+ int cpu, u8 type);
+ void (*handle_dl_server_stop)(void *data, struct sched_dl_entity *dl_se,
+ int cpu, u8 type);
+ void (*handle_sched_switch)(void *data, bool preempt,
+ struct task_struct *prev,
+ struct task_struct *next,
+ unsigned int prev_state);
+ void (*handle_sched_wakeup)(void *data, struct task_struct *tsk);
+ void (*handle_sys_enter)(void *data, struct pt_regs *regs, long id);
+ void (*handle_newtask)(void *data, struct task_struct *task, u64 flags);
+} rv_nomiss_ops;
+#endif
+
+#endif /* __NOMISS_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/opid/opid.c b/kernel/trace/rv/monitors/opid/opid.c
index 3b6a85e815b8..9ae619f176fa 100644
--- a/kernel/trace/rv/monitors/opid/opid.c
+++ b/kernel/trace/rv/monitors/opid/opid.c
@@ -115,3 +115,15 @@ module_exit(unregister_opid);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("opid: operations with preemption and irq disabled.");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "opid_kunit.h"
+
+const struct rv_opid_ops rv_opid_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_sched_need_resched = handle_sched_need_resched,
+ .handle_sched_waking = handle_sched_waking,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_opid_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/opid/opid_kunit.c b/kernel/trace/rv/monitors/opid/opid_kunit.c
new file mode 100644
index 000000000000..3cb087a74241
--- /dev/null
+++ b/kernel/trace/rv/monitors/opid/opid_kunit.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/sched.h>
+#include "opid_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_OPID)
+
+static void rv_test_opid(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_opid_ops.mon);
+
+ /* Ensure we keep the same per-cpu monitor */
+ guard(migrate)();
+ KUNIT_EXPECT_TRUE(test, preemptible());
+
+ /* Wakeup with preemption and interrupts enabled */
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_opid_ops.handle_sched_waking(NULL, NULL);
+
+ /* Need resched with interrupts enabled */
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) {
+ scoped_guard(preempt)
+ rv_opid_ops.handle_sched_need_resched(NULL, NULL, 0, TIF_NEED_RESCHED);
+ }
+}
+
+#else
+#define rv_test_opid rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/opid/opid_kunit.h b/kernel/trace/rv/monitors/opid/opid_kunit.h
new file mode 100644
index 000000000000..4969c6175957
--- /dev/null
+++ b/kernel/trace/rv/monitors/opid/opid_kunit.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __OPID_KUNIT_H
+#define __OPID_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_opid_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_sched_need_resched)(void *data, struct task_struct *tsk, int cpu, int tif);
+ void (*handle_sched_waking)(void *data, struct task_struct *p);
+} rv_opid_ops;
+#endif
+
+#endif /* __OPID_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/pagefault/pagefault.c b/kernel/trace/rv/monitors/pagefault/pagefault.c
index 9fe6123b2200..c599fc19fc88 100644
--- a/kernel/trace/rv/monitors/pagefault/pagefault.c
+++ b/kernel/trace/rv/monitors/pagefault/pagefault.c
@@ -38,7 +38,7 @@ static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bo
static void handle_page_fault(void *data, unsigned long address, struct pt_regs *regs,
unsigned long error_code)
{
- ltl_atom_pulse(current, LTL_PAGEFAULT, true);
+ ltl_atom_pulse(rv_get_current(), LTL_PAGEFAULT, true);
}
static int enable_pagefault(void)
@@ -63,7 +63,7 @@ static void disable_pagefault(void)
ltl_monitor_destroy();
}
-static struct rv_monitor rv_pagefault = {
+static struct rv_monitor rv_this = {
.name = "pagefault",
.description = "Monitor that RT tasks do not raise page faults",
.enable = enable_pagefault,
@@ -72,12 +72,12 @@ static struct rv_monitor rv_pagefault = {
static int __init register_pagefault(void)
{
- return rv_register_monitor(&rv_pagefault, &rv_rtapp);
+ return rv_register_monitor(&rv_this, &rv_rtapp);
}
static void __exit unregister_pagefault(void)
{
- rv_unregister_monitor(&rv_pagefault);
+ rv_unregister_monitor(&rv_this);
}
module_init(register_pagefault);
@@ -86,3 +86,15 @@ module_exit(unregister_pagefault);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nam Cao <namcao@linutronix.de>");
MODULE_DESCRIPTION("pagefault: Monitor that RT tasks do not raise page faults");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "pagefault_kunit.h"
+
+const struct rv_pagefault_ops rv_pagefault_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_page_fault = handle_page_fault,
+ .handle_task_newtask = handle_task_newtask,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_pagefault_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/pagefault/pagefault_kunit.c b/kernel/trace/rv/monitors/pagefault/pagefault_kunit.c
new file mode 100644
index 000000000000..06369960b008
--- /dev/null
+++ b/kernel/trace/rv/monitors/pagefault/pagefault_kunit.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <linux/sched/deadline.h>
+#include <linux/sched/rt.h>
+#include "pagefault_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_PAGEFAULT)
+
+static void rv_test_pagefault(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_pagefault_ops.mon);
+
+ /* Initial pagefault when non-RT to start the model without failure */
+ target->policy = SCHED_NORMAL;
+ target->prio = MAX_RT_PRIO + 20;
+ rv_pagefault_ops.handle_task_newtask(NULL, target, 0);
+ rv_mock_current(target);
+ rv_pagefault_ops.handle_page_fault(NULL, 0, NULL, 0);
+
+ /* RT task has a page fault */
+ target->policy = SCHED_FIFO;
+ target->prio = MAX_RT_PRIO - 1;
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_pagefault_ops.handle_page_fault(NULL, 0, NULL, 0);
+}
+
+#else
+#define rv_test_pagefault rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/pagefault/pagefault_kunit.h b/kernel/trace/rv/monitors/pagefault/pagefault_kunit.h
new file mode 100644
index 000000000000..2f9652f08b3f
--- /dev/null
+++ b/kernel/trace/rv/monitors/pagefault/pagefault_kunit.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __PAGEFAULT_KUNIT_H
+#define __PAGEFAULT_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_pagefault_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_page_fault)(void *data, unsigned long address, struct pt_regs *regs,
+ unsigned long error_code);
+ void (*handle_task_newtask)(void *data, struct task_struct *task, u64 flags);
+} rv_pagefault_ops;
+#endif
+
+#endif /* __PAGEFAULT_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/rtapp/Kconfig b/kernel/trace/rv/monitors/rtapp/Kconfig
index 1ce9370a9ba8..1fcd7a400ded 100644
--- a/kernel/trace/rv/monitors/rtapp/Kconfig
+++ b/kernel/trace/rv/monitors/rtapp/Kconfig
@@ -1,6 +1,6 @@
config RV_MON_RTAPP
depends on RV
- depends on RV_PER_TASK_MONITORS >= 2
+ depends on RV_PER_TASK_MONITORS >= 3
bool "rtapp monitor"
help
Collection of monitors to check for common problems with real-time
diff --git a/kernel/trace/rv/monitors/sco/sco.c b/kernel/trace/rv/monitors/sco/sco.c
index 5a3bd5e16e62..1ef1b96e859d 100644
--- a/kernel/trace/rv/monitors/sco/sco.c
+++ b/kernel/trace/rv/monitors/sco/sco.c
@@ -83,3 +83,16 @@ module_exit(unregister_sco);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("sco: scheduling context operations.");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "sco_kunit.h"
+
+const struct rv_sco_ops rv_sco_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_sched_set_state = handle_sched_set_state,
+ .handle_schedule_entry = handle_schedule_entry,
+ .handle_schedule_exit = handle_schedule_exit,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_sco_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/sco/sco_kunit.c b/kernel/trace/rv/monitors/sco/sco_kunit.c
new file mode 100644
index 000000000000..5e59bcbfcf0b
--- /dev/null
+++ b/kernel/trace/rv/monitors/sco/sco_kunit.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/sched.h>
+#include "sco_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_SCO)
+
+static void rv_test_sco(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_sco_ops.mon);
+
+ /* Ensure we keep the same per-cpu monitor */
+ guard(migrate)();
+
+ /* Set state while scheduling */
+ rv_sco_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+ rv_sco_ops.handle_schedule_entry(NULL, false);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sco_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+}
+
+#else
+#define rv_test_sco rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/sco/sco_kunit.h b/kernel/trace/rv/monitors/sco/sco_kunit.h
new file mode 100644
index 000000000000..567757df6b1d
--- /dev/null
+++ b/kernel/trace/rv/monitors/sco/sco_kunit.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __SCO_KUNIT_H
+#define __SCO_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_sco_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_sched_set_state)(void *data, struct task_struct *tsk, int state);
+ void (*handle_schedule_entry)(void *data, bool preempt);
+ void (*handle_schedule_exit)(void *data, bool is_switch);
+} rv_sco_ops;
+#endif
+
+#endif /* __SCO_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/sleep/Kconfig b/kernel/trace/rv/monitors/sleep/Kconfig
index 6b7a122e7b47..d6ec3e9a91b6 100644
--- a/kernel/trace/rv/monitors/sleep/Kconfig
+++ b/kernel/trace/rv/monitors/sleep/Kconfig
@@ -5,7 +5,6 @@ config RV_MON_SLEEP
select RV_LTL_MONITOR
depends on HAVE_SYSCALL_TRACEPOINTS
depends on RV_MON_RTAPP
- select TRACE_IRQFLAGS
default y
select LTL_MON_EVENTS_ID
bool "sleep monitor"
diff --git a/kernel/trace/rv/monitors/sleep/sleep.c b/kernel/trace/rv/monitors/sleep/sleep.c
index 8dfe5ec13e19..b82537251e09 100644
--- a/kernel/trace/rv/monitors/sleep/sleep.c
+++ b/kernel/trace/rv/monitors/sleep/sleep.c
@@ -36,16 +36,14 @@ static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
{
ltl_atom_set(mon, LTL_SLEEP, false);
- ltl_atom_set(mon, LTL_WAKE, false);
+ ltl_atom_set(mon, LTL_SCHEDULE_IN, false);
ltl_atom_set(mon, LTL_ABORT_SLEEP, false);
ltl_atom_set(mon, LTL_WOKEN_BY_HARDIRQ, false);
ltl_atom_set(mon, LTL_WOKEN_BY_NMI, false);
ltl_atom_set(mon, LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, false);
if (task_creation) {
- ltl_atom_set(mon, LTL_KTHREAD_SHOULD_STOP, false);
- ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, false);
- ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, false);
+ ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, false);
ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false);
ltl_atom_set(mon, LTL_CLOCK_NANOSLEEP, false);
ltl_atom_set(mon, LTL_FUTEX_WAIT, false);
@@ -54,34 +52,7 @@ static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bo
ltl_atom_set(mon, LTL_BLOCK_ON_RT_MUTEX, false);
}
- if (task->flags & PF_KTHREAD) {
- ltl_atom_set(mon, LTL_KERNEL_THREAD, true);
-
- /* kernel tasks do not do syscall */
- ltl_atom_set(mon, LTL_FUTEX_WAIT, false);
- ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false);
- ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, false);
- ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, false);
- ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false);
- ltl_atom_set(mon, LTL_CLOCK_NANOSLEEP, false);
- ltl_atom_set(mon, LTL_EPOLL_WAIT, false);
-
- if (strstarts(task->comm, "migration/"))
- ltl_atom_set(mon, LTL_TASK_IS_MIGRATION, true);
- else
- ltl_atom_set(mon, LTL_TASK_IS_MIGRATION, false);
-
- if (strstarts(task->comm, "rcu"))
- ltl_atom_set(mon, LTL_TASK_IS_RCU, true);
- else
- ltl_atom_set(mon, LTL_TASK_IS_RCU, false);
- } else {
- ltl_atom_set(mon, LTL_KTHREAD_SHOULD_STOP, false);
- ltl_atom_set(mon, LTL_KERNEL_THREAD, false);
- ltl_atom_set(mon, LTL_TASK_IS_RCU, false);
- ltl_atom_set(mon, LTL_TASK_IS_MIGRATION, false);
- }
-
+ ltl_atom_set(mon, LTL_USER_THREAD, !(task->flags & PF_KTHREAD));
}
static void handle_sched_set_state(void *data, struct task_struct *task, int state)
@@ -92,17 +63,17 @@ static void handle_sched_set_state(void *data, struct task_struct *task, int sta
ltl_atom_pulse(task, LTL_ABORT_SLEEP, true);
}
-static void handle_sched_wakeup(void *data, struct task_struct *task)
+static void handle_sched_exit(void *data, bool is_switch)
{
- ltl_atom_pulse(task, LTL_WAKE, true);
+ ltl_atom_pulse(rv_get_current(), LTL_SCHEDULE_IN, true);
}
static void handle_sched_waking(void *data, struct task_struct *task)
{
- if (this_cpu_read(hardirq_context)) {
+ if (in_hardirq()) {
ltl_atom_pulse(task, LTL_WOKEN_BY_HARDIRQ, true);
} else if (in_task()) {
- if (current->prio <= task->prio)
+ if (rv_get_current()->prio <= task->prio)
ltl_atom_pulse(task, LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, true);
} else if (in_nmi()) {
ltl_atom_pulse(task, LTL_WOKEN_BY_NMI, true);
@@ -112,12 +83,12 @@ static void handle_sched_waking(void *data, struct task_struct *task)
static void handle_contention_begin(void *data, void *lock, unsigned int flags)
{
if (flags & LCB_F_RT)
- ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, true);
+ ltl_atom_update(rv_get_current(), LTL_BLOCK_ON_RT_MUTEX, true);
}
static void handle_contention_end(void *data, void *lock, int ret)
{
- ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, false);
+ ltl_atom_update(rv_get_current(), LTL_BLOCK_ON_RT_MUTEX, false);
}
static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
@@ -126,7 +97,7 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
unsigned long args[6];
int op, cmd;
- mon = ltl_get_monitor(current);
+ mon = ltl_get_monitor(rv_get_current());
switch (id) {
#ifdef __NR_clock_nanosleep
@@ -135,11 +106,10 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
#ifdef __NR_clock_nanosleep_time64
case __NR_clock_nanosleep_time64:
#endif
- syscall_get_arguments(current, regs, args);
- ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, args[0] == CLOCK_MONOTONIC);
- ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, args[0] == CLOCK_TAI);
+ syscall_get_arguments(rv_get_current(), regs, args);
+ ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, args[0] == CLOCK_REALTIME);
ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, args[1] == TIMER_ABSTIME);
- ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, true);
+ ltl_atom_update(rv_get_current(), LTL_CLOCK_NANOSLEEP, true);
break;
#ifdef __NR_futex
@@ -148,25 +118,25 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
#ifdef __NR_futex_time64
case __NR_futex_time64:
#endif
- syscall_get_arguments(current, regs, args);
+ syscall_get_arguments(rv_get_current(), regs, args);
op = args[1];
cmd = op & FUTEX_CMD_MASK;
switch (cmd) {
case FUTEX_LOCK_PI:
case FUTEX_LOCK_PI2:
- ltl_atom_update(current, LTL_FUTEX_LOCK_PI, true);
+ ltl_atom_update(rv_get_current(), LTL_FUTEX_LOCK_PI, true);
break;
case FUTEX_WAIT:
case FUTEX_WAIT_BITSET:
case FUTEX_WAIT_REQUEUE_PI:
- ltl_atom_update(current, LTL_FUTEX_WAIT, true);
+ ltl_atom_update(rv_get_current(), LTL_FUTEX_WAIT, true);
break;
}
break;
#ifdef __NR_epoll_wait
case __NR_epoll_wait:
- ltl_atom_update(current, LTL_EPOLL_WAIT, true);
+ ltl_atom_update(rv_get_current(), LTL_EPOLL_WAIT, true);
break;
#endif
}
@@ -174,21 +144,14 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
static void handle_sys_exit(void *data, struct pt_regs *regs, long ret)
{
- struct ltl_monitor *mon = ltl_get_monitor(current);
+ struct ltl_monitor *mon = ltl_get_monitor(rv_get_current());
ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false);
ltl_atom_set(mon, LTL_FUTEX_WAIT, false);
- ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, false);
- ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, false);
+ ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, false);
ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false);
ltl_atom_set(mon, LTL_EPOLL_WAIT, false);
- ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, false);
-}
-
-static void handle_kthread_stop(void *data, struct task_struct *task)
-{
- /* FIXME: this could race with other tracepoint handlers */
- ltl_atom_update(task, LTL_KTHREAD_SHOULD_STOP, true);
+ ltl_atom_update(rv_get_current(), LTL_CLOCK_NANOSLEEP, false);
}
static int enable_sleep(void)
@@ -200,11 +163,10 @@ static int enable_sleep(void)
return retval;
rv_attach_trace_probe("rtapp_sleep", sched_waking, handle_sched_waking);
- rv_attach_trace_probe("rtapp_sleep", sched_wakeup, handle_sched_wakeup);
+ rv_attach_trace_probe("rtapp_sleep", sched_exit_tp, handle_sched_exit);
rv_attach_trace_probe("rtapp_sleep", sched_set_state_tp, handle_sched_set_state);
rv_attach_trace_probe("rtapp_sleep", contention_begin, handle_contention_begin);
rv_attach_trace_probe("rtapp_sleep", contention_end, handle_contention_end);
- rv_attach_trace_probe("rtapp_sleep", sched_kthread_stop, handle_kthread_stop);
rv_attach_trace_probe("rtapp_sleep", sys_enter, handle_sys_enter);
rv_attach_trace_probe("rtapp_sleep", sys_exit, handle_sys_exit);
return 0;
@@ -213,18 +175,17 @@ static int enable_sleep(void)
static void disable_sleep(void)
{
rv_detach_trace_probe("rtapp_sleep", sched_waking, handle_sched_waking);
- rv_detach_trace_probe("rtapp_sleep", sched_wakeup, handle_sched_wakeup);
+ rv_detach_trace_probe("rtapp_sleep", sched_exit_tp, handle_sched_exit);
rv_detach_trace_probe("rtapp_sleep", sched_set_state_tp, handle_sched_set_state);
rv_detach_trace_probe("rtapp_sleep", contention_begin, handle_contention_begin);
rv_detach_trace_probe("rtapp_sleep", contention_end, handle_contention_end);
- rv_detach_trace_probe("rtapp_sleep", sched_kthread_stop, handle_kthread_stop);
rv_detach_trace_probe("rtapp_sleep", sys_enter, handle_sys_enter);
rv_detach_trace_probe("rtapp_sleep", sys_exit, handle_sys_exit);
ltl_monitor_destroy();
}
-static struct rv_monitor rv_sleep = {
+static struct rv_monitor rv_this = {
.name = "sleep",
.description = "Monitor that RT tasks do not undesirably sleep",
.enable = enable_sleep,
@@ -233,12 +194,12 @@ static struct rv_monitor rv_sleep = {
static int __init register_sleep(void)
{
- return rv_register_monitor(&rv_sleep, &rv_rtapp);
+ return rv_register_monitor(&rv_this, &rv_rtapp);
}
static void __exit unregister_sleep(void)
{
- rv_unregister_monitor(&rv_sleep);
+ rv_unregister_monitor(&rv_this);
}
module_init(register_sleep);
@@ -247,3 +208,21 @@ module_exit(unregister_sleep);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nam Cao <namcao@linutronix.de>");
MODULE_DESCRIPTION("sleep: Monitor that RT tasks do not undesirably sleep");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "sleep_kunit.h"
+
+const struct rv_sleep_ops rv_sleep_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_sched_waking = handle_sched_waking,
+ .handle_sched_exit = handle_sched_exit,
+ .handle_sched_set_state = handle_sched_set_state,
+ .handle_contention_begin = handle_contention_begin,
+ .handle_contention_end = handle_contention_end,
+ .handle_sys_enter = handle_sys_enter,
+ .handle_sys_exit = handle_sys_exit,
+ .handle_task_newtask = handle_task_newtask,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_sleep_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/sleep/sleep.h b/kernel/trace/rv/monitors/sleep/sleep.h
index 95dc2727c059..44e593f41e6a 100644
--- a/kernel/trace/rv/monitors/sleep/sleep.h
+++ b/kernel/trace/rv/monitors/sleep/sleep.h
@@ -18,16 +18,12 @@ enum ltl_atom {
LTL_EPOLL_WAIT,
LTL_FUTEX_LOCK_PI,
LTL_FUTEX_WAIT,
- LTL_KERNEL_THREAD,
- LTL_KTHREAD_SHOULD_STOP,
- LTL_NANOSLEEP_CLOCK_MONOTONIC,
- LTL_NANOSLEEP_CLOCK_TAI,
+ LTL_NANOSLEEP_CLOCK_REALTIME,
LTL_NANOSLEEP_TIMER_ABSTIME,
LTL_RT,
+ LTL_SCHEDULE_IN,
LTL_SLEEP,
- LTL_TASK_IS_MIGRATION,
- LTL_TASK_IS_RCU,
- LTL_WAKE,
+ LTL_USER_THREAD,
LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO,
LTL_WOKEN_BY_HARDIRQ,
LTL_WOKEN_BY_NMI,
@@ -44,16 +40,12 @@ static const char *ltl_atom_str(enum ltl_atom atom)
"ep_wa",
"fu_lo_pi",
"fu_wa",
- "ker_th",
- "kth_sh_st",
- "na_cl_mo",
- "na_cl_ta",
+ "na_cl_re",
"na_ti_ab",
"rt",
- "sl",
- "ta_mi",
- "ta_rc",
- "wak",
+ "sch_in",
+ "sle",
+ "us_th",
"wo_eq_hi_pr",
"wo_ha",
"wo_nm",
@@ -81,47 +73,41 @@ static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)
bool woken_by_hardirq = test_bit(LTL_WOKEN_BY_HARDIRQ, mon->atoms);
bool woken_by_equal_or_higher_prio = test_bit(LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO,
mon->atoms);
- bool wake = test_bit(LTL_WAKE, mon->atoms);
- bool task_is_rcu = test_bit(LTL_TASK_IS_RCU, mon->atoms);
- bool task_is_migration = test_bit(LTL_TASK_IS_MIGRATION, mon->atoms);
+ bool user_thread = test_bit(LTL_USER_THREAD, mon->atoms);
bool sleep = test_bit(LTL_SLEEP, mon->atoms);
+ bool schedule_in = test_bit(LTL_SCHEDULE_IN, mon->atoms);
bool rt = test_bit(LTL_RT, mon->atoms);
bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms);
- bool nanosleep_clock_tai = test_bit(LTL_NANOSLEEP_CLOCK_TAI, mon->atoms);
- bool nanosleep_clock_monotonic = test_bit(LTL_NANOSLEEP_CLOCK_MONOTONIC, mon->atoms);
- bool kthread_should_stop = test_bit(LTL_KTHREAD_SHOULD_STOP, mon->atoms);
- bool kernel_thread = test_bit(LTL_KERNEL_THREAD, mon->atoms);
+ bool nanosleep_clock_realtime = test_bit(LTL_NANOSLEEP_CLOCK_REALTIME, mon->atoms);
bool futex_wait = test_bit(LTL_FUTEX_WAIT, mon->atoms);
bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms);
bool epoll_wait = test_bit(LTL_EPOLL_WAIT, mon->atoms);
bool clock_nanosleep = test_bit(LTL_CLOCK_NANOSLEEP, mon->atoms);
bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms);
bool abort_sleep = test_bit(LTL_ABORT_SLEEP, mon->atoms);
- bool val42 = task_is_rcu || task_is_migration;
- bool val43 = futex_lock_pi || val42;
- bool val5 = block_on_rt_mutex || val43;
- bool val34 = abort_sleep || kthread_should_stop;
- bool val35 = woken_by_nmi || val34;
- bool val36 = woken_by_hardirq || val35;
- bool val14 = woken_by_equal_or_higher_prio || val36;
- bool val13 = !wake;
- bool val26 = nanosleep_clock_monotonic || nanosleep_clock_tai;
- bool val27 = nanosleep_timer_abstime && val26;
- bool val18 = clock_nanosleep && val27;
+ bool val7 = block_on_rt_mutex || futex_lock_pi;
+ bool val32 = woken_by_nmi || abort_sleep;
+ bool val33 = woken_by_hardirq || val32;
+ bool val14 = woken_by_equal_or_higher_prio || val33;
+ bool val13 = !schedule_in;
+ bool val25 = !nanosleep_clock_realtime;
+ bool val26 = nanosleep_timer_abstime && val25;
+ bool val18 = clock_nanosleep && val26;
bool val20 = val18 || epoll_wait;
- bool val9 = futex_wait || val20;
- bool val11 = val9 || kernel_thread;
+ bool val11 = futex_wait || val20;
+ bool val3 = !user_thread;
bool val2 = !sleep;
+ bool val4 = val2 || val3;
bool val1 = !rt;
- bool val3 = val1 || val2;
+ bool val5 = val1 || val4;
- if (val3)
+ if (val5)
__set_bit(S0, mon->states);
if (val11 && val13)
__set_bit(S1, mon->states);
if (val11 && val14)
__set_bit(S4, mon->states);
- if (val5)
+ if (val7)
__set_bit(S5, mon->states);
}
@@ -132,131 +118,125 @@ ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned l
bool woken_by_hardirq = test_bit(LTL_WOKEN_BY_HARDIRQ, mon->atoms);
bool woken_by_equal_or_higher_prio = test_bit(LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO,
mon->atoms);
- bool wake = test_bit(LTL_WAKE, mon->atoms);
- bool task_is_rcu = test_bit(LTL_TASK_IS_RCU, mon->atoms);
- bool task_is_migration = test_bit(LTL_TASK_IS_MIGRATION, mon->atoms);
+ bool user_thread = test_bit(LTL_USER_THREAD, mon->atoms);
bool sleep = test_bit(LTL_SLEEP, mon->atoms);
+ bool schedule_in = test_bit(LTL_SCHEDULE_IN, mon->atoms);
bool rt = test_bit(LTL_RT, mon->atoms);
bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms);
- bool nanosleep_clock_tai = test_bit(LTL_NANOSLEEP_CLOCK_TAI, mon->atoms);
- bool nanosleep_clock_monotonic = test_bit(LTL_NANOSLEEP_CLOCK_MONOTONIC, mon->atoms);
- bool kthread_should_stop = test_bit(LTL_KTHREAD_SHOULD_STOP, mon->atoms);
- bool kernel_thread = test_bit(LTL_KERNEL_THREAD, mon->atoms);
+ bool nanosleep_clock_realtime = test_bit(LTL_NANOSLEEP_CLOCK_REALTIME, mon->atoms);
bool futex_wait = test_bit(LTL_FUTEX_WAIT, mon->atoms);
bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms);
bool epoll_wait = test_bit(LTL_EPOLL_WAIT, mon->atoms);
bool clock_nanosleep = test_bit(LTL_CLOCK_NANOSLEEP, mon->atoms);
bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms);
bool abort_sleep = test_bit(LTL_ABORT_SLEEP, mon->atoms);
- bool val42 = task_is_rcu || task_is_migration;
- bool val43 = futex_lock_pi || val42;
- bool val5 = block_on_rt_mutex || val43;
- bool val34 = abort_sleep || kthread_should_stop;
- bool val35 = woken_by_nmi || val34;
- bool val36 = woken_by_hardirq || val35;
- bool val14 = woken_by_equal_or_higher_prio || val36;
- bool val13 = !wake;
- bool val26 = nanosleep_clock_monotonic || nanosleep_clock_tai;
- bool val27 = nanosleep_timer_abstime && val26;
- bool val18 = clock_nanosleep && val27;
+ bool val7 = block_on_rt_mutex || futex_lock_pi;
+ bool val32 = woken_by_nmi || abort_sleep;
+ bool val33 = woken_by_hardirq || val32;
+ bool val14 = woken_by_equal_or_higher_prio || val33;
+ bool val13 = !schedule_in;
+ bool val25 = !nanosleep_clock_realtime;
+ bool val26 = nanosleep_timer_abstime && val25;
+ bool val18 = clock_nanosleep && val26;
bool val20 = val18 || epoll_wait;
- bool val9 = futex_wait || val20;
- bool val11 = val9 || kernel_thread;
+ bool val11 = futex_wait || val20;
+ bool val3 = !user_thread;
bool val2 = !sleep;
+ bool val4 = val2 || val3;
bool val1 = !rt;
- bool val3 = val1 || val2;
+ bool val5 = val1 || val4;
switch (state) {
case S0:
- if (val3)
+ if (val5)
__set_bit(S0, next);
if (val11 && val13)
__set_bit(S1, next);
if (val11 && val14)
__set_bit(S4, next);
- if (val5)
+ if (val7)
__set_bit(S5, next);
break;
case S1:
if (val11 && val13)
__set_bit(S1, next);
- if (val13 && val3)
+ if (val13 && val5)
__set_bit(S2, next);
- if (val14 && val3)
+ if (val14 && val5)
__set_bit(S3, next);
if (val11 && val14)
__set_bit(S4, next);
- if (val13 && val5)
+ if (val13 && val7)
__set_bit(S6, next);
- if (val14 && val5)
+ if (val14 && val7)
__set_bit(S7, next);
break;
case S2:
if (val11 && val13)
__set_bit(S1, next);
- if (val13 && val3)
+ if (val13 && val5)
__set_bit(S2, next);
- if (val14 && val3)
+ if (val14 && val5)
__set_bit(S3, next);
if (val11 && val14)
__set_bit(S4, next);
- if (val13 && val5)
+ if (val13 && val7)
__set_bit(S6, next);
- if (val14 && val5)
+ if (val14 && val7)
__set_bit(S7, next);
break;
case S3:
- if (val3)
+ if (val5)
__set_bit(S0, next);
if (val11 && val13)
__set_bit(S1, next);
if (val11 && val14)
__set_bit(S4, next);
- if (val5)
+ if (val7)
__set_bit(S5, next);
break;
case S4:
- if (val3)
+ if (val5)
__set_bit(S0, next);
if (val11 && val13)
__set_bit(S1, next);
if (val11 && val14)
__set_bit(S4, next);
- if (val5)
+ if (val7)
__set_bit(S5, next);
break;
case S5:
- if (val3)
+ if (val5)
__set_bit(S0, next);
if (val11 && val13)
__set_bit(S1, next);
if (val11 && val14)
__set_bit(S4, next);
- if (val5)
+ if (val7)
__set_bit(S5, next);
break;
case S6:
if (val11 && val13)
__set_bit(S1, next);
- if (val13 && val3)
+ if (val13 && val5)
__set_bit(S2, next);
- if (val14 && val3)
+ if (val14 && val5)
__set_bit(S3, next);
if (val11 && val14)
__set_bit(S4, next);
- if (val13 && val5)
+ if (val13 && val7)
__set_bit(S6, next);
- if (val14 && val5)
+ if (val14 && val7)
__set_bit(S7, next);
break;
case S7:
- if (val3)
+ if (val5)
__set_bit(S0, next);
if (val11 && val13)
__set_bit(S1, next);
if (val11 && val14)
__set_bit(S4, next);
- if (val5)
+ if (val7)
__set_bit(S5, next);
break;
}
diff --git a/kernel/trace/rv/monitors/sleep/sleep_kunit.c b/kernel/trace/rv/monitors/sleep/sleep_kunit.c
new file mode 100644
index 000000000000..17df5baf1ec2
--- /dev/null
+++ b/kernel/trace/rv/monitors/sleep/sleep_kunit.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/syscalls.h>
+#include <trace/events/sched.h>
+#include <uapi/linux/futex.h>
+#include "sleep_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_SLEEP)
+
+static void rv_test_sleep(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct task_struct *other = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+ unsigned long args[6] = {0};
+ struct pt_regs regs = {0};
+
+ prepare_test(test, &rv_sleep_ops.mon);
+ target->policy = SCHED_FIFO;
+ target->prio = MAX_RT_PRIO - 2;
+ other->policy = SCHED_FIFO;
+ other->prio = MAX_RT_PRIO - 1;
+ rv_sleep_ops.handle_task_newtask(NULL, target, 0);
+
+ /* RT task sleeps on a non RT-friendly nanosleep */
+ rv_mock_current(target);
+ args[0] = CLOCK_REALTIME;
+ syscall_set_arguments(target, &regs, args);
+#ifdef __NR_clock_nanosleep
+ rv_sleep_ops.handle_sys_enter(NULL, &regs, __NR_clock_nanosleep);
+#elif defined(__NR_clock_nanosleep_time64)
+ rv_sleep_ops.handle_sys_enter(NULL, &regs, __NR_clock_nanosleep_time64);
+#endif
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sleep_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+ rv_sleep_ops.handle_sys_exit(NULL, NULL, 0);
+
+ /* RT task woken up by lower priority task */
+ args[1] = FUTEX_WAIT;
+ syscall_set_arguments(target, &regs, args);
+ rv_mock_current(target);
+#ifdef __NR_futex
+ rv_sleep_ops.handle_sys_enter(NULL, &regs, __NR_futex);
+#elif defined(__NR_futex_time64)
+ rv_sleep_ops.handle_sys_enter(NULL, &regs, __NR_futex_time64);
+#endif
+ rv_sleep_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+ rv_mock_current(other);
+ rv_sleep_ops.handle_sched_waking(NULL, target);
+ rv_mock_current(target);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sleep_ops.handle_sched_exit(NULL, true);
+}
+
+#else
+#define rv_test_sleep rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/sleep/sleep_kunit.h b/kernel/trace/rv/monitors/sleep/sleep_kunit.h
new file mode 100644
index 000000000000..3ebf8d2699f2
--- /dev/null
+++ b/kernel/trace/rv/monitors/sleep/sleep_kunit.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __SLEEP_KUNIT_H
+#define __SLEEP_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_sleep_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_sched_waking)(void *data, struct task_struct *task);
+ void (*handle_sched_exit)(void *data, bool is_switch);
+ void (*handle_sched_set_state)(void *data, struct task_struct *task, int state);
+ void (*handle_contention_begin)(void *data, void *lock, unsigned int flags);
+ void (*handle_contention_end)(void *data, void *lock, int ret);
+ void (*handle_sys_enter)(void *data, struct pt_regs *regs, long id);
+ void (*handle_sys_exit)(void *data, struct pt_regs *regs, long ret);
+ void (*handle_task_newtask)(void *data, struct task_struct *task, u64 flags);
+} rv_sleep_ops;
+#endif
+
+#endif /* __SLEEP_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/sssw/sssw.c b/kernel/trace/rv/monitors/sssw/sssw.c
index a91321c890cd..fbfde32dc136 100644
--- a/kernel/trace/rv/monitors/sssw/sssw.c
+++ b/kernel/trace/rv/monitors/sssw/sssw.c
@@ -112,3 +112,17 @@ module_exit(unregister_sssw);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("sssw: set state sleep and wakeup.");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "sssw_kunit.h"
+
+const struct rv_sssw_ops rv_sssw_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_sched_set_state = handle_sched_set_state,
+ .handle_sched_switch = handle_sched_switch,
+ .handle_sched_wakeup = handle_sched_wakeup,
+ .handle_signal_deliver = handle_signal_deliver,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_sssw_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/sssw/sssw_kunit.c b/kernel/trace/rv/monitors/sssw/sssw_kunit.c
new file mode 100644
index 000000000000..a95faf859c60
--- /dev/null
+++ b/kernel/trace/rv/monitors/sssw/sssw_kunit.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/sched.h>
+#include "sssw_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_SSSW)
+
+static void rv_test_sssw(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct task_struct *other = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_sssw_ops.mon);
+
+ /* Suspend without setting to sleepable */
+ rv_sssw_ops.handle_sched_set_state(NULL, target, TASK_RUNNING);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sssw_ops.handle_sched_switch(NULL, 0, target, other, TASK_INTERRUPTIBLE);
+
+ /* Switch in after suspension without wakeup */
+ rv_sssw_ops.handle_sched_wakeup(NULL, target);
+ rv_sssw_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+ rv_sssw_ops.handle_sched_switch(NULL, 0, target, other, TASK_INTERRUPTIBLE);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sssw_ops.handle_sched_switch(NULL, 0, other, target, TASK_RUNNING);
+}
+
+#else
+#define rv_test_sssw rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/sssw/sssw_kunit.h b/kernel/trace/rv/monitors/sssw/sssw_kunit.h
new file mode 100644
index 000000000000..6513daa7afba
--- /dev/null
+++ b/kernel/trace/rv/monitors/sssw/sssw_kunit.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __SSSW_KUNIT_H
+#define __SSSW_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_sssw_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_sched_set_state)(void *data, struct task_struct *tsk, int state);
+ void (*handle_sched_switch)(void *data, bool preempt,
+ struct task_struct *prev,
+ struct task_struct *next,
+ unsigned int prev_state);
+ void (*handle_sched_wakeup)(void *data, struct task_struct *p);
+ void (*handle_signal_deliver)(void *data, int sig,
+ struct kernel_siginfo *info,
+ struct k_sigaction *ka);
+} rv_sssw_ops;
+#endif
+
+#endif /* __SSSW_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/stall/stall.c b/kernel/trace/rv/monitors/stall/stall.c
index 3c38fb1a0159..b265578f845c 100644
--- a/kernel/trace/rv/monitors/stall/stall.c
+++ b/kernel/trace/rv/monitors/stall/stall.c
@@ -38,7 +38,7 @@ static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
enum states next_state, u64 time_ns)
{
if (curr_state == enqueued_stall)
- return ha_check_invariant_jiffy(ha_mon, clk_stall, time_ns);
+ return ha_check_invariant_jiffy(ha_mon, clk_stall, time_ns, threshold_jiffies);
return true;
}
diff --git a/kernel/trace/rv/monitors/sts/sts.c b/kernel/trace/rv/monitors/sts/sts.c
index ce031cbf202a..2a044cf925b1 100644
--- a/kernel/trace/rv/monitors/sts/sts.c
+++ b/kernel/trace/rv/monitors/sts/sts.c
@@ -152,3 +152,22 @@ module_exit(unregister_sts);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("sts: schedule implies task switch.");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "sts_kunit.h"
+
+const struct rv_sts_ops rv_sts_ops = {
+ .mon = RV_MON_OPS_INIT(),
+#ifdef CONFIG_X86_LOCAL_APIC
+ .handle_vector_irq_entry = handle_vector_irq_entry,
+#endif
+ .handle_irq_disable = handle_irq_disable,
+ .handle_irq_enable = handle_irq_enable,
+ .handle_irq_entry = handle_irq_entry,
+ .handle_sched_switch = handle_sched_switch,
+ .handle_schedule_entry = handle_schedule_entry,
+ .handle_schedule_exit = handle_schedule_exit,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_sts_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/sts/sts_kunit.c b/kernel/trace/rv/monitors/sts/sts_kunit.c
new file mode 100644
index 000000000000..a07316fff091
--- /dev/null
+++ b/kernel/trace/rv/monitors/sts/sts_kunit.c
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/sched.h>
+#include "sts_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_STS)
+
+static void rv_test_sts(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct task_struct *other = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_sts_ops.mon);
+ /* Per-CPU monitor, make sure we don't change CPU mid-test */
+ guard(migrate)();
+
+ /* Switch without disabling interrupts */
+ rv_sts_ops.handle_schedule_exit(NULL, false);
+ rv_sts_ops.handle_schedule_entry(NULL, false);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sts_ops.handle_sched_switch(NULL, 0, target, other, TASK_RUNNING);
+
+ rv_sts_ops.handle_schedule_exit(NULL, false);
+
+ /* Schedule from interrupt context */
+ rv_sts_ops.handle_schedule_entry(NULL, false);
+ rv_sts_ops.handle_irq_disable(NULL, 0, 0);
+ rv_sts_ops.handle_irq_entry(NULL, 0, NULL);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sts_ops.handle_sched_switch(NULL, 0, target, other, TASK_RUNNING);
+ rv_sts_ops.handle_irq_enable(NULL, 0, 0);
+}
+
+#else
+#define rv_test_sts rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/sts/sts_kunit.h b/kernel/trace/rv/monitors/sts/sts_kunit.h
new file mode 100644
index 000000000000..dede4e098c1f
--- /dev/null
+++ b/kernel/trace/rv/monitors/sts/sts_kunit.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __STS_KUNIT_H
+#define __STS_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_sts_ops {
+ struct rv_kunit_mon mon;
+#ifdef CONFIG_X86_LOCAL_APIC
+ void (*handle_vector_irq_entry)(void *data, int vector);
+#endif
+ void (*handle_irq_disable)(void *data, unsigned long ip, unsigned long parent_ip);
+ void (*handle_irq_enable)(void *data, unsigned long ip, unsigned long parent_ip);
+ void (*handle_irq_entry)(void *data, int irq, struct irqaction *action);
+ void (*handle_sched_switch)(void *data, bool preempt,
+ struct task_struct *prev,
+ struct task_struct *next,
+ unsigned int prev_state);
+ void (*handle_schedule_entry)(void *data, bool preempt);
+ void (*handle_schedule_exit)(void *data, bool is_switch);
+} rv_sts_ops;
+#endif
+
+#endif /* __STS_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/wakeup/Kconfig b/kernel/trace/rv/monitors/wakeup/Kconfig
new file mode 100644
index 000000000000..98f618f0e01d
--- /dev/null
+++ b/kernel/trace/rv/monitors/wakeup/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_WAKEUP
+ depends on RV
+ depends on RV_MON_RTAPP
+ depends on HAVE_SYSCALL_TRACEPOINTS
+ default y
+ select LTL_MON_EVENTS_ID
+ bool "wakeup monitor"
+ help
+ This monitor detects a lower-priority task waking up a
+ higher-priority task. The RV_MON_SLEEP monitor already
+ detects this case, but this monitor detects in the context
+ of the waker task instead. This and RV_MON_SLEEP can be
+ enabled together to get the stacktrace of both the waker
+ task and the wakee task.
diff --git a/kernel/trace/rv/monitors/wakeup/wakeup.c b/kernel/trace/rv/monitors/wakeup/wakeup.c
new file mode 100644
index 000000000000..01b47416f24e
--- /dev/null
+++ b/kernel/trace/rv/monitors/wakeup/wakeup.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "wakeup"
+
+#include <trace/events/syscalls.h>
+#include <trace/events/sched.h>
+#include <trace/events/lock.h>
+#include <uapi/linux/futex.h>
+
+#include <rv_trace.h>
+#include <monitors/rtapp/rtapp.h>
+
+
+#ifndef __NR_futex
+#define __NR_futex (-__COUNTER__)
+#endif
+#ifndef __NR_futex_time64
+#define __NR_futex_time64 (-__COUNTER__)
+#endif
+
+#include "wakeup.h"
+#include <rv/ltl_monitor.h>
+
+static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
+{
+ /*
+ * This includes "actual" real-time tasks and also PI-boosted
+ * tasks. A task being PI-boosted means it is blocking an "actual"
+ * real-task, therefore it should also obey the monitor's rule,
+ * otherwise the "actual" real-task may be delayed.
+ */
+ ltl_atom_set(mon, LTL_RT, rt_or_dl_task(task));
+}
+
+static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
+{
+ ltl_atom_set(mon, LTL_WOKEN_BY_LOWER_PRIO, false);
+ ltl_atom_set(mon, LTL_WOKEN_BY_SOFTIRQ, false);
+
+ if (task_creation) {
+ ltl_atom_set(mon, LTL_BLOCK_ON_RT_MUTEX, false);
+ ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false);
+ }
+
+ ltl_atom_set(mon, LTL_USER_THREAD, !(task->flags & PF_KTHREAD));
+}
+
+static void handle_sched_waking(void *data, struct task_struct *task)
+{
+ if (in_task()) {
+ if (current->prio > task->prio)
+ ltl_atom_pulse(task, LTL_WOKEN_BY_LOWER_PRIO, true);
+ } else if (in_serving_softirq()) {
+ ltl_atom_pulse(task, LTL_WOKEN_BY_SOFTIRQ, true);
+ }
+}
+
+static void handle_contention_begin(void *data, void *lock, unsigned int flags)
+{
+ if (flags & LCB_F_RT)
+ ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, true);
+}
+
+static void handle_contention_end(void *data, void *lock, int ret)
+{
+ ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, false);
+}
+
+static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
+{
+ unsigned long args[6];
+ int op, cmd;
+
+ switch (id) {
+ case __NR_futex:
+ case __NR_futex_time64:
+ syscall_get_arguments(current, regs, args);
+ op = args[1];
+ cmd = op & FUTEX_CMD_MASK;
+
+ switch (cmd) {
+ case FUTEX_LOCK_PI:
+ case FUTEX_LOCK_PI2:
+ ltl_atom_update(current, LTL_FUTEX_LOCK_PI, true);
+ break;
+ }
+ break;
+ }
+}
+
+static void handle_sys_exit(void *data, struct pt_regs *regs, long ret)
+{
+ ltl_atom_update(current, LTL_FUTEX_LOCK_PI, false);
+}
+
+static int enable_wakeup(void)
+{
+ int retval;
+
+ retval = ltl_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("rtapp_wakeup", sched_waking, handle_sched_waking);
+ rv_attach_trace_probe("rtapp_wakeup", contention_begin, handle_contention_begin);
+ rv_attach_trace_probe("rtapp_wakeup", contention_end, handle_contention_end);
+ rv_attach_trace_probe("rtapp_wakeup", sys_enter, handle_sys_enter);
+ rv_attach_trace_probe("rtapp_wakeup", sys_exit, handle_sys_exit);
+
+ return 0;
+}
+
+static void disable_wakeup(void)
+{
+ rv_detach_trace_probe("rtapp_wakeup", sched_waking, handle_sched_waking);
+ rv_detach_trace_probe("rtapp_wakeup", contention_begin, handle_contention_begin);
+ rv_detach_trace_probe("rtapp_wakeup", contention_end, handle_contention_end);
+ rv_detach_trace_probe("rtapp_wakeup", sys_enter, handle_sys_enter);
+ rv_detach_trace_probe("rtapp_wakeup", sys_exit, handle_sys_exit);
+
+ ltl_monitor_destroy();
+}
+
+static struct rv_monitor rv_wakeup = {
+ .name = "wakeup",
+ .description = "Monitor that real-time tasks are not woken by lower-priority tasks",
+ .enable = enable_wakeup,
+ .disable = disable_wakeup,
+};
+
+static int __init register_wakeup(void)
+{
+ return rv_register_monitor(&rv_wakeup, &rv_rtapp);
+}
+
+static void __exit unregister_wakeup(void)
+{
+ rv_unregister_monitor(&rv_wakeup);
+}
+
+module_init(register_wakeup);
+module_exit(unregister_wakeup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nam Cao <namcao@linutronix.de>");
+MODULE_DESCRIPTION("Monitor that real-time tasks are not woken by lower-priority tasks");
diff --git a/kernel/trace/rv/monitors/wakeup/wakeup.h b/kernel/trace/rv/monitors/wakeup/wakeup.h
new file mode 100644
index 000000000000..6f80da64e0e1
--- /dev/null
+++ b/kernel/trace/rv/monitors/wakeup/wakeup.h
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * C implementation of Buchi automaton, automatically generated by
+ * tools/verification/rvgen from the linear temporal logic specification.
+ * For further information, see kernel documentation:
+ * Documentation/trace/rv/linear_temporal_logic.rst
+ */
+
+#include <linux/rv.h>
+
+#define MONITOR_NAME wakeup
+
+enum ltl_atom {
+ LTL_BLOCK_ON_RT_MUTEX,
+ LTL_FUTEX_LOCK_PI,
+ LTL_RT,
+ LTL_USER_THREAD,
+ LTL_WOKEN_BY_LOWER_PRIO,
+ LTL_WOKEN_BY_SOFTIRQ,
+ LTL_NUM_ATOM
+};
+static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM);
+
+static const char *ltl_atom_str(enum ltl_atom atom)
+{
+ static const char *const names[] = {
+ "bl_on_rt_mu",
+ "fu_lo_pi",
+ "rt",
+ "us_th",
+ "wo_lo_pr",
+ "wo_so",
+ };
+
+ return names[atom];
+}
+
+enum ltl_buchi_state {
+ S0,
+ RV_NUM_BA_STATES
+};
+static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES);
+
+static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)
+{
+ bool woken_by_softirq = test_bit(LTL_WOKEN_BY_SOFTIRQ, mon->atoms);
+ bool woken_by_lower_prio = test_bit(LTL_WOKEN_BY_LOWER_PRIO, mon->atoms);
+ bool user_thread = test_bit(LTL_USER_THREAD, mon->atoms);
+ bool rt = test_bit(LTL_RT, mon->atoms);
+ bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms);
+ bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms);
+ bool val9 = block_on_rt_mutex || futex_lock_pi;
+ bool val6 = !woken_by_softirq;
+ bool val5 = !woken_by_lower_prio;
+ bool val8 = val5 && val6;
+ bool val10 = val8 || val9;
+ bool val3 = !user_thread;
+ bool val2 = !rt;
+ bool val4 = val2 || val3;
+ bool val11 = val4 || val10;
+
+ if (val11)
+ __set_bit(S0, mon->states);
+}
+
+static void
+ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next)
+{
+ bool woken_by_softirq = test_bit(LTL_WOKEN_BY_SOFTIRQ, mon->atoms);
+ bool woken_by_lower_prio = test_bit(LTL_WOKEN_BY_LOWER_PRIO, mon->atoms);
+ bool user_thread = test_bit(LTL_USER_THREAD, mon->atoms);
+ bool rt = test_bit(LTL_RT, mon->atoms);
+ bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms);
+ bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms);
+ bool val9 = block_on_rt_mutex || futex_lock_pi;
+ bool val6 = !woken_by_softirq;
+ bool val5 = !woken_by_lower_prio;
+ bool val8 = val5 && val6;
+ bool val10 = val8 || val9;
+ bool val3 = !user_thread;
+ bool val2 = !rt;
+ bool val4 = val2 || val3;
+ bool val11 = val4 || val10;
+
+ switch (state) {
+ case S0:
+ if (val11)
+ __set_bit(S0, next);
+ break;
+ }
+}
diff --git a/kernel/trace/rv/monitors/wakeup/wakeup_trace.h b/kernel/trace/rv/monitors/wakeup/wakeup_trace.h
new file mode 100644
index 000000000000..7e056183f920
--- /dev/null
+++ b/kernel/trace/rv/monitors/wakeup/wakeup_trace.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_WAKEUP
+DEFINE_EVENT(event_ltl_monitor_id, event_wakeup,
+ TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next),
+ TP_ARGS(task, states, atoms, next));
+DEFINE_EVENT(error_ltl_monitor_id, error_wakeup,
+ TP_PROTO(struct task_struct *task),
+ TP_ARGS(task));
+#endif /* CONFIG_RV_MON_WAKEUP */
diff --git a/kernel/trace/rv/rv.c b/kernel/trace/rv/rv.c
index ee4e68102f17..29f155c6968b 100644
--- a/kernel/trace/rv/rv.c
+++ b/kernel/trace/rv/rv.c
@@ -164,7 +164,6 @@ struct dentry *get_monitors_root(void)
*/
LIST_HEAD(rv_monitors_list);
-static int task_monitor_count;
static bool task_monitor_slots[CONFIG_RV_PER_TASK_MONITORS];
int rv_get_task_monitor_slot(void)
@@ -173,22 +172,16 @@ int rv_get_task_monitor_slot(void)
lockdep_assert_held(&rv_interface_lock);
- if (task_monitor_count == CONFIG_RV_PER_TASK_MONITORS)
- return -EBUSY;
-
- task_monitor_count++;
-
for (i = 0; i < CONFIG_RV_PER_TASK_MONITORS; i++) {
- if (task_monitor_slots[i] == false) {
+ if (!task_monitor_slots[i]) {
task_monitor_slots[i] = true;
return i;
}
}
- WARN_ONCE(1, "RV task_monitor_count and slots are out of sync\n");
-
- return -EINVAL;
+ return -EBUSY;
}
+EXPORT_SYMBOL_GPL(rv_get_task_monitor_slot);
void rv_put_task_monitor_slot(int slot)
{
@@ -199,12 +192,13 @@ void rv_put_task_monitor_slot(int slot)
return;
}
- WARN_ONCE(!task_monitor_slots[slot], "RV releasing unused task_monitor_slots: %d\n",
- slot);
+ if (WARN_ONCE(!task_monitor_slots[slot],
+ "RV releasing unused task monitor slot: %d\n", slot))
+ return;
- task_monitor_count--;
task_monitor_slots[slot] = false;
}
+EXPORT_SYMBOL_GPL(rv_put_task_monitor_slot);
/*
* Monitors with a parent are nested,
@@ -852,3 +846,69 @@ int __init rv_init_interface(void)
return 0;
}
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <rv/kunit.h>
+#include <kunit/visibility.h>
+
+/*
+ * rv_set_testing - ensure mutual exclusion between KUnit tests and real monitors
+ *
+ * KUnit tests for RV monitors rely on stubs that are incompatible with
+ * the execution of real monitors. Ensure mutual exclusion by acquiring
+ * the rv_interface_lock for the duration of the suite.
+ *
+ * Returns 0 on success, -EBUSY if any real monitor is already enabled.
+ */
+int rv_set_testing(struct kunit_suite *suite)
+{
+ struct rv_monitor *mon;
+
+ mutex_lock(&rv_interface_lock);
+
+ list_for_each_entry(mon, &rv_monitors_list, list) {
+ if (mon->enabled) {
+ mutex_unlock(&rv_interface_lock);
+ return -EBUSY;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(rv_set_testing);
+
+/*
+ * rv_clear_testing - allow real monitors to run again after KUnit tests
+ */
+void rv_clear_testing(struct kunit_suite *suite)
+{
+ mutex_unlock(&rv_interface_lock);
+}
+EXPORT_SYMBOL_IF_KUNIT(rv_clear_testing);
+
+/*
+ * rv_get_mock_current() is called only if we are running from a KUnit test.
+ * This can occur from a legitimate RV test or any unrelated test running when
+ * a real RV monitor is active and triggering events.
+ * We assume the former case is the only one where mock_current is not NULL and
+ * can occur only sequentially (KUnit doesn't run tests in parallel).
+ * We cannot rely on the test's context because there is no way to safely
+ * understand from which test we are running and KUnit utilities require
+ * locking, which is unsafe from NMI or scheduling context.
+ * Note that it is not possible for a real RV monitor to run when the RV KUnit
+ * tests are running (see rv_set_testing()).
+ */
+static struct task_struct *mock_current;
+
+void rv_mock_current(struct task_struct *tsk)
+{
+ mock_current = tsk;
+}
+EXPORT_SYMBOL_IF_KUNIT(rv_mock_current);
+
+struct task_struct *rv_get_mock_current(void)
+{
+ return mock_current ?: current;
+}
+EXPORT_SYMBOL_GPL(rv_get_mock_current);
+#endif
diff --git a/kernel/trace/rv/rv_monitors_test.c b/kernel/trace/rv/rv_monitors_test.c
new file mode 100644
index 000000000000..3ad11195e664
--- /dev/null
+++ b/kernel/trace/rv/rv_monitors_test.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026-2029 Red Hat, Inc. Gabriele Monaco <gmonaco@redhat.com>
+ *
+ * RV monitor kunit tests:
+ * Tests the RV monitors by triggering fake events to verify monitor
+ * behavior and reactions. Tests start from the first defined event and
+ * trigger events in order to verify error detection.
+ */
+#include <rv/kunit.h>
+#include <kunit/test-bug.h>
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include "rv.h"
+
+/*
+ * An easy way to pass the context is to use kunit_get_current_test()->priv,
+ * but this doesn't always work (e.g. a reactor running from another context
+ * like softirq). Store the current value here whenever a test is running.
+ */
+static struct rv_kunit_ctx *active_ctx;
+
+__printf(1, 0)
+static void rv_kunit_mock_react(const char *msg, va_list args)
+{
+ if (active_ctx)
+ ++active_ctx->reactions;
+}
+
+/*
+ * teardown_test - Disable the monitor for a kunit test
+ *
+ * Since per-task monitors are special, make sure we reset all the ones we
+ * started manually here, if required.
+ */
+void teardown_test(void *arg)
+{
+ const struct rv_kunit_mon *mon = arg;
+ struct kunit *test = kunit_get_current_test();
+
+ if (test) {
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ RV_KUNIT_EXPECT_NO_REACTION(test, ctx);
+
+ if (mon->is_per_task && mon->task_reset) {
+ for (int i = 0; i < ctx->mock_task_count; i++)
+ mon->task_reset(ctx->mock_tasks[i]);
+ synchronize_rcu();
+ }
+ }
+
+ mon->rv_this->enabled = 0;
+
+ if (mon->rv_this->reactor)
+ mon->rv_this->react = mon->rv_this->reactor->react;
+ else
+ mon->rv_this->react = NULL;
+ active_ctx = NULL;
+ rv_mock_current(NULL);
+
+ if (mon->is_per_task)
+ *mon->task_slot = RV_PER_TASK_MONITOR_INIT;
+ else
+ mon->monitor_destroy();
+}
+
+/*
+ * prepare_test - Enable the monitor for a kunit test
+ *
+ * Do the bare minimum to set up the monitor, per-task monitors are special as
+ * "real" initialisation/destruction iterates over real tasks, and may register
+ * handlers. All we need is to select the right slot in the task_struct.
+ */
+void prepare_test(struct kunit *test, const struct rv_kunit_mon *mon)
+{
+ KUNIT_ASSERT_FALSE(test, mon->rv_this->enabled);
+
+ active_ctx = test->priv;
+ mon->rv_this->react = rv_kunit_mock_react;
+
+ if (mon->is_per_task)
+ *mon->task_slot = 0;
+ else
+ KUNIT_ASSERT_EQ(test, mon->monitor_init(), 0);
+
+ mon->rv_this->enabled = 1;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ kunit_add_action_or_reset(test, teardown_test, (void *)mon));
+}
+
+struct task_struct *rv_kunit_alloc_mock_task(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+ struct task_struct *tsk;
+
+ KUNIT_ASSERT_LT(test, ctx->mock_task_count, RV_KUNIT_MAX_MOCK_TASKS);
+
+ tsk = kunit_kzalloc(test, sizeof(struct task_struct), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, tsk);
+
+ if (!IS_ENABLED(CONFIG_THREAD_INFO_IN_TASK)) {
+ tsk->stack = kunit_kzalloc(test, sizeof(struct thread_info), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, tsk->stack);
+ }
+
+ ctx->mock_tasks[ctx->mock_task_count++] = tsk;
+ return tsk;
+}
+
+static int rv_mon_test_init(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx;
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
+
+ test->priv = ctx;
+
+ return 0;
+}
+
+static void __maybe_unused rv_test_stub(struct kunit *test)
+{
+ kunit_skip(test, "Monitor not enabled\n");
+}
+
+/*
+ * rv_test_dummy - test reactions work as expected
+ */
+static void rv_test_dummy(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+ static struct rv_monitor dummy_monitor = {
+ .name = "dummy",
+ .react = rv_kunit_mock_react,
+ };
+
+ active_ctx = ctx;
+
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_react(&dummy_monitor, "dummy");
+ RV_KUNIT_EXPECT_NO_REACTION(test, ctx);
+
+ active_ctx = NULL;
+}
+
+#include "monitors/sco/sco_kunit.c"
+#include "monitors/sssw/sssw_kunit.c"
+#include "monitors/sts/sts_kunit.c"
+#include "monitors/opid/opid_kunit.c"
+#include "monitors/nomiss/nomiss_kunit.c"
+#include "monitors/pagefault/pagefault_kunit.c"
+#include "monitors/sleep/sleep_kunit.c"
+
+static struct kunit_case rv_mon_test_cases[] = {
+ KUNIT_CASE(rv_test_dummy),
+ KUNIT_CASE(rv_test_sco),
+ KUNIT_CASE(rv_test_sssw),
+ KUNIT_CASE(rv_test_sts),
+ KUNIT_CASE(rv_test_opid),
+ KUNIT_CASE(rv_test_nomiss),
+ KUNIT_CASE(rv_test_pagefault),
+ KUNIT_CASE(rv_test_sleep),
+ {}
+};
+
+static struct kunit_suite rv_mon_test_suite = {
+ .name = "rv_mon",
+ .suite_init = rv_set_testing,
+ .suite_exit = rv_clear_testing,
+ .init = rv_mon_test_init,
+ .test_cases = rv_mon_test_cases,
+};
+
+kunit_test_suites(&rv_mon_test_suite);
+
+MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
+MODULE_DESCRIPTION("RV monitor kunit tests: test monitors by triggering reactions");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
diff --git a/kernel/trace/rv/rv_reactors.c b/kernel/trace/rv/rv_reactors.c
index 460af07f7aba..2f5fc8d18dea 100644
--- a/kernel/trace/rv/rv_reactors.c
+++ b/kernel/trace/rv/rv_reactors.c
@@ -479,3 +479,4 @@ void rv_react(struct rv_monitor *monitor, const char *msg, ...)
va_end(args);
}
+EXPORT_SYMBOL_GPL(rv_react);
diff --git a/kernel/trace/rv/rv_trace.h b/kernel/trace/rv/rv_trace.h
index 9622c269789c..2f8a932432c9 100644
--- a/kernel/trace/rv/rv_trace.h
+++ b/kernel/trace/rv/rv_trace.h
@@ -241,6 +241,7 @@ DECLARE_EVENT_CLASS(error_ltl_monitor_id,
);
#include <monitors/pagefault/pagefault_trace.h>
#include <monitors/sleep/sleep_trace.h>
+#include <monitors/wakeup/wakeup_trace.h>
// Add new monitors based on CONFIG_LTL_MON_EVENTS_ID here
#endif /* CONFIG_LTL_MON_EVENTS_ID */
diff --git a/kernel/trace/simple_ring_buffer.c b/kernel/trace/simple_ring_buffer.c
index f4642f5adda3..49913bb0057a 100644
--- a/kernel/trace/simple_ring_buffer.c
+++ b/kernel/trace/simple_ring_buffer.c
@@ -160,8 +160,8 @@ int simple_ring_buffer_swap_reader_page(struct simple_rb_per_cpu *cpu_buffer)
overrun = cpu_buffer->meta->overrun;
} while (!simple_bpage_unset_head_link(last, reader, SIMPLE_RB_LINK_NORMAL) && retry--);
- if (!retry)
- return -EINVAL;
+ if (retry < 0)
+ return -EBUSY;
cpu_buffer->head_page = simple_bpage_from_link(reader->link.next);
cpu_buffer->head_page->link.prev = &reader->link;
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 1146b83b711a..e4a490d3d08c 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -87,7 +87,7 @@ void __init disable_tracing_selftest(const char *reason)
/* Pipe tracepoints to printk */
static struct trace_iterator *tracepoint_print_iter;
-int tracepoint_printk;
+static int tracepoint_printk;
static bool tracepoint_printk_stop_on_boot __initdata;
static bool traceoff_after_boot __initdata;
static DEFINE_STATIC_KEY_FALSE(tracepoint_printk_key);
@@ -1788,7 +1788,7 @@ void trace_buffered_event_enable(void)
per_cpu(trace_buffered_event, cpu) = event;
- scoped_guard(preempt,) {
+ scoped_guard(preempt) {
if (cpu == smp_processor_id() &&
__this_cpu_read(trace_buffered_event) !=
per_cpu(trace_buffered_event, cpu))
@@ -4320,14 +4320,16 @@ static const char readme_msg[] =
"\t args: <name>=fetcharg[:type]\n"
"\t fetcharg: (%<register>|$<efield>), @<address>, @<symbol>[+|-<offset>],\n"
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
- "\t $stack<index>, $stack, $retval, $comm, $arg<N>,\n"
+ "\t $stack<index>, $stack, $retval, $comm, $arg<N>, $current\n"
#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
- "\t <argname>[->field[->field|.field...]],\n"
+ "\t [(structname[,field])]<argname>[->field[->field|.field...]],\n"
+ "\t [(structname[,field])](fetcharg)->field[->field|.field...],\n"
#endif
#else
- "\t $stack<index>, $stack, $retval, $comm,\n"
+ "\t $stack<index>, $stack, $retval, $comm, $current\n"
#endif
"\t +|-[u]<offset>(<fetcharg>), \\imm-value, \\\"imm-string\"\n"
+ "\t this_cpu_read(<fetcharg>), this_cpu_ptr(<fetcharg>)\n"
"\t kernel return probes support: $retval, $arg<N>, $comm\n"
"\t type: s8/16/32/64, u8/16/32/64, x8/16/32/64, char, string, symbol,\n"
"\t b<bit-width>@<bit-offset>/<container-size>, ustring,\n"
@@ -4669,7 +4671,7 @@ trace_event_update_with_eval_map(struct module *mod,
map = start;
- trace_event_update_all(map, len);
+ trace_event_update_all(map, len, mod);
if (len <= 0)
return;
@@ -5015,7 +5017,6 @@ int tracing_set_tracer(struct trace_array *tr, const char *buf)
RING_BUFFER_ALL_CPUS);
if (ret < 0)
return ret;
- ret = 0;
}
list_for_each_entry(t, &tr->tracers, list) {
@@ -6188,7 +6189,7 @@ char *trace_user_fault_read(struct trace_user_buf_info *tinfo,
{
int cpu = smp_processor_id();
char *buffer = per_cpu_ptr(tinfo->tbuf, cpu)->buf;
- unsigned int cnt;
+ unsigned long long cnt;
int trys = 0;
int ret;
@@ -7081,8 +7082,8 @@ ssize_t tracing_buffers_read(struct file *filp, char __user *ubuf,
{
struct ftrace_buffer_info *info = filp->private_data;
struct trace_iterator *iter = &info->iter;
+ unsigned int spare_size;
void *trace_data;
- int page_size;
ssize_t ret = 0;
ssize_t size;
@@ -7092,36 +7093,22 @@ ssize_t tracing_buffers_read(struct file *filp, char __user *ubuf,
if (iter->snapshot && tracer_uses_snapshot(iter->tr->current_trace))
return -EBUSY;
- page_size = ring_buffer_subbuf_size_get(iter->array_buffer->buffer);
+ spare_size = ring_buffer_read_page_size(info->spare);
- /* Make sure the spare matches the current sub buffer size */
- if (info->spare) {
- if (page_size != info->spare_size) {
- ring_buffer_free_read_page(iter->array_buffer->buffer,
- info->spare_cpu, info->spare);
- info->spare = NULL;
- }
- }
+again:
+ /* Do we have previous read data to read? */
+ if (info->read < spare_size)
+ goto read;
- if (!info->spare) {
- info->spare = ring_buffer_alloc_read_page(iter->array_buffer->buffer,
- iter->cpu_file);
- if (IS_ERR(info->spare)) {
- ret = PTR_ERR(info->spare);
- info->spare = NULL;
- } else {
- info->spare_cpu = iter->cpu_file;
- info->spare_size = page_size;
- }
- }
- if (!info->spare)
+ ret = ring_buffer_alloc_read_page(iter->array_buffer->buffer, iter->cpu_file,
+ &info->spare);
+ if (ret)
return ret;
- /* Do we have previous read data to read? */
- if (info->read < page_size)
- goto read;
+ spare_size = ring_buffer_read_page_size(info->spare);
+ info->read = spare_size;
+ info->spare_cpu = iter->cpu_file;
- again:
trace_access_lock(iter->cpu_file);
ret = ring_buffer_read_page(iter->array_buffer->buffer,
info->spare,
@@ -7147,8 +7134,9 @@ ssize_t tracing_buffers_read(struct file *filp, char __user *ubuf,
}
info->read = 0;
+
read:
- size = page_size - info->read;
+ size = spare_size - info->read;
if (size > count)
size = count;
trace_data = ring_buffer_read_page_data(info->spare);
@@ -7189,26 +7177,24 @@ int tracing_buffers_release(struct inode *inode, struct file *file)
__trace_array_put(iter->tr);
- if (info->spare)
- ring_buffer_free_read_page(iter->array_buffer->buffer,
- info->spare_cpu, info->spare);
+ ring_buffer_free_read_page(iter->array_buffer->buffer, info->spare_cpu, info->spare);
kvfree(info);
return 0;
}
struct buffer_ref {
- struct trace_buffer *buffer;
- void *page;
- int cpu;
- refcount_t refcount;
+ struct trace_buffer *buffer;
+ struct buffer_data_read_page *rpage;
+ int cpu;
+ refcount_t refcount;
};
static void buffer_ref_release(struct buffer_ref *ref)
{
if (!refcount_dec_and_test(&ref->refcount))
return;
- ring_buffer_free_read_page(ref->buffer, ref->cpu, ref->page);
+ ring_buffer_free_read_page(ref->buffer, ref->cpu, ref->rpage);
kfree(ref);
}
@@ -7267,25 +7253,15 @@ ssize_t tracing_buffers_splice_read(struct file *file, loff_t *ppos,
.ops = &buffer_pipe_buf_ops,
.spd_release = buffer_spd_release,
};
+ unsigned int page_size = 0;
struct buffer_ref *ref;
bool woken = false;
- int page_size;
int entries, i;
ssize_t ret = 0;
if (iter->snapshot && tracer_uses_snapshot(iter->tr->current_trace))
return -EBUSY;
- page_size = ring_buffer_subbuf_size_get(iter->array_buffer->buffer);
- if (*ppos & (page_size - 1))
- return -EINVAL;
-
- if (len & (page_size - 1)) {
- if (len < page_size)
- return -EINVAL;
- len &= (~(page_size - 1));
- }
-
if (splice_grow_spd(pipe, &spd))
return -ENOMEM;
@@ -7305,25 +7281,39 @@ ssize_t tracing_buffers_splice_read(struct file *file, loff_t *ppos,
refcount_set(&ref->refcount, 1);
ref->buffer = iter->array_buffer->buffer;
- ref->page = ring_buffer_alloc_read_page(ref->buffer, iter->cpu_file);
- if (IS_ERR(ref->page)) {
- ret = PTR_ERR(ref->page);
- ref->page = NULL;
+
+ ret = ring_buffer_alloc_read_page(ref->buffer, iter->cpu_file, &ref->rpage);
+ if (ret) {
kfree(ref);
break;
}
ref->cpu = iter->cpu_file;
- r = ring_buffer_read_page(ref->buffer, ref->page,
- len, iter->cpu_file, 1);
+ page_size = ring_buffer_read_page_size(ref->rpage);
+
+ r = -EINVAL;
+ if (IS_ALIGNED(*ppos, page_size) && len >= page_size) {
+ r = ring_buffer_read_page(ref->buffer, ref->rpage, len, iter->cpu_file, 1);
+ } else if (!i) {
+ /*
+ * If this fails to read on the first iteration, it
+ * means the length was too small and an error should
+ * be returned to user space. Otherwise, at least
+ * one sub-buffer was successfully read but this failed
+ * due to either the length was unaligned or the
+ * subbuf order changed. Either case, do not report
+ * an error.
+ */
+ ret = -EINVAL;
+ }
+
if (r < 0) {
- ring_buffer_free_read_page(ref->buffer, ref->cpu,
- ref->page);
+ ring_buffer_free_read_page(ref->buffer, ref->cpu, ref->rpage);
kfree(ref);
break;
}
- page = virt_to_page(ring_buffer_read_page_data(ref->page));
+ page = virt_to_page(ring_buffer_read_page_data(ref->rpage));
spd.pages[i] = page;
spd.partial[i].len = page_size;
@@ -7727,12 +7717,55 @@ trace_options_write(struct file *filp, const char __user *ubuf, size_t cnt,
return cnt;
}
+static bool tr_option_match(struct trace_array *tr, void *topt)
+{
+ for (int i = 0; i < tr->nr_topts; i++) {
+ struct trace_options *tr_topts = &tr->topts[i];
+
+ if (topt >= (void *)&tr_topts->topts[0] &&
+ topt < (void *)&tr_topts->topts[tr_topts->nr_topts])
+ return true;
+ }
+ return false;
+}
+
+/*
+ * The topt is the address of a trace_array->topts[] element that holds the
+ * the tracer options descriptor. But since the trace_array reference has not
+ * been taken yet, it cannot be dereferenced as it could have been freed by
+ * a rmdir of the instance the trace_array represents.
+ *
+ * Search the list of trace_arrays and compare the topt to the address of
+ * the entire trace_array topts array for each trace_array in the list.
+ * If one is matched, then take the reference and return it. If not, the
+ * trace_array no longer exits.
+ */
+static int trace_array_tracer_options_get(void *topt)
+{
+ struct trace_array *tr;
+ int ret;
+
+ ret = security_locked_down(LOCKDOWN_TRACEFS);
+ if (ret)
+ return ret;
+
+ if (tracing_disabled)
+ return -ENODEV;
+
+ guard(mutex)(&trace_types_lock);
+ list_for_each_entry(tr, &ftrace_trace_arrays, list) {
+ if (tr_option_match(tr, topt))
+ return __trace_array_get(tr);
+ }
+ return -ENODEV;
+}
+
static int tracing_open_options(struct inode *inode, struct file *filp)
{
struct trace_option_dentry *topt = inode->i_private;
int ret;
- ret = tracing_check_open_get_tr(topt->tr);
+ ret = trace_array_tracer_options_get(topt);
if (ret)
return ret;
@@ -7841,11 +7874,70 @@ trace_options_core_write(struct file *filp, const char __user *ubuf, size_t cnt,
return cnt;
}
+/*
+ * The tr_index is the address of a trace_array->trace_flags_index[]
+ * element that holds the index of the trace flag. But since the
+ * trace_array reference has not been taken yet, it cannot be referenced
+ * as it could have been freed by a rmdir of the instance the trace_array
+ * represents.
+ *
+ * Search the list of trace_arrays and compare the tr_index to the
+ * address of the entire trace_array trace_flags_index array for each
+ * trace_array in the list. If one is matched, then take the reference
+ * and return it. If not, the trace_array no longer exits.
+ */
+static int trace_array_options_get(void *tr_index)
+{
+ struct trace_array *tr;
+ int ret;
+
+ ret = security_locked_down(LOCKDOWN_TRACEFS);
+ if (ret)
+ return ret;
+
+ if (tracing_disabled)
+ return -ENODEV;
+
+ guard(mutex)(&trace_types_lock);
+ list_for_each_entry(tr, &ftrace_trace_arrays, list) {
+ if (tr_index >= (void *)&tr->trace_flags_index[0] &&
+ tr_index < (void *)&tr->trace_flags_index[TRACE_FLAGS_MAX_SIZE])
+ return __trace_array_get(tr);
+ }
+ return -ENODEV;
+}
+
+static int trace_options_open(struct inode *inode, struct file *filp)
+{
+ void *tr_index = inode->i_private;
+
+ if (trace_array_options_get(tr_index) < 0)
+ return -ENODEV;
+
+ filp->private_data = tr_index;
+
+ return 0;
+}
+
+static int trace_options_release(struct inode *inode, struct file *filp)
+{
+ void *tr_index = filp->private_data;
+ struct trace_array *tr;
+ unsigned int index;
+
+ get_tr_index(tr_index, &tr, &index);
+
+ trace_array_put(tr);
+
+ return 0;
+}
+
static const struct file_operations trace_options_core_fops = {
- .open = tracing_open_generic,
- .read = trace_options_core_read,
- .write = trace_options_core_write,
- .llseek = generic_file_llseek,
+ .open = trace_options_open,
+ .read = trace_options_core_read,
+ .write = trace_options_core_write,
+ .llseek = generic_file_llseek,
+ .release = trace_options_release,
};
struct dentry *trace_create_file(const char *name,
@@ -7935,6 +8027,7 @@ create_trace_option_files(struct trace_array *tr, struct tracer *tracer,
tr->topts = tr_topts;
tr->topts[tr->nr_topts].tracer = tracer;
tr->topts[tr->nr_topts].topts = topts;
+ tr->topts[tr->nr_topts].nr_topts = cnt;
tr->nr_topts++;
for (cnt = 0; opts[cnt].name; cnt++) {
@@ -8215,6 +8308,8 @@ buffer_subbuf_size_write(struct file *filp, const char __user *ubuf,
/* Do not allow tracing while changing the order of the ring buffer */
tracing_stop_tr(tr);
+ trace_access_lock(RING_BUFFER_ALL_CPUS);
+
old_order = ring_buffer_subbuf_order_get(tr->array_buffer.buffer);
if (old_order == order)
goto out;
@@ -8254,6 +8349,7 @@ buffer_subbuf_size_write(struct file *filp, const char __user *ubuf,
#endif
(*ppos)++;
out:
+ trace_access_unlock(RING_BUFFER_ALL_CPUS);
if (ret)
cnt = ret;
tracing_start_tr(tr);
@@ -9647,6 +9743,11 @@ __init static void enable_instances(void)
if (flag_delim)
*flag_delim++ = '\0';
+ if (trace_array_find(name)) {
+ pr_warn("Tracing: Instance %s already exists\n", name);
+ continue;
+ }
+
if (backup) {
if (backup_instance_area(backup, &addr, &size) < 0)
continue;
@@ -9727,7 +9828,8 @@ __init static void enable_instances(void)
tr = trace_array_create_systems(name, NULL, addr, size);
if (IS_ERR(tr)) {
- pr_warn("Tracing: Failed to create instance buffer %s\n", curr_str);
+ pr_warn("Tracing: Failed to create instance buffer '%s' (%ld)\n", name,
+ PTR_ERR(tr));
continue;
}
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index 80fe152af1dd..3749485a7d85 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -227,6 +227,7 @@ struct array_buffer {
struct trace_options {
struct tracer *tracer;
struct trace_option_dentry *topts;
+ int nr_topts;
};
struct trace_pid_list *trace_pid_list_alloc(void);
@@ -745,11 +746,10 @@ static inline int tracing_get_cpu(struct inode *inode)
void tracing_reset_cpu(struct array_buffer *buf, int cpu);
struct ftrace_buffer_info {
- struct trace_iterator iter;
- void *spare;
- unsigned int spare_cpu;
- unsigned int spare_size;
- unsigned int read;
+ struct trace_iterator iter;
+ struct buffer_data_read_page *spare;
+ unsigned int spare_cpu;
+ unsigned int read;
};
/**
@@ -1340,7 +1340,7 @@ extern void clear_ftrace_function_probes(struct trace_array *tr);
int register_ftrace_command(struct ftrace_func_command *cmd);
int unregister_ftrace_command(struct ftrace_func_command *cmd);
-void ftrace_create_filter_files(struct ftrace_ops *ops,
+void ftrace_create_filter_files(struct trace_array *tr,
struct dentry *parent);
void ftrace_destroy_filter_files(struct ftrace_ops *ops);
@@ -1363,11 +1363,12 @@ static inline void clear_ftrace_function_probes(struct trace_array *tr)
{
}
+static inline void ftrace_create_filter_files(struct trace_array *tr,
+ struct dentry *parent) { }
/*
* The ops parameter passed in is usually undefined.
* This must be a macro.
*/
-#define ftrace_create_filter_files(ops, parent) do { } while (0)
#define ftrace_destroy_filter_files(ops) do { } while (0)
#endif /* CONFIG_FUNCTION_TRACER && CONFIG_DYNAMIC_FTRACE */
@@ -1941,6 +1942,7 @@ struct event_trigger_data {
struct list_head named_list;
struct event_trigger_data *named_data;
struct llist_node llist;
+ void (*private_data_free)(struct event_trigger_data *data);
};
/* Avoid typos */
@@ -1951,6 +1953,7 @@ struct event_trigger_data {
struct enable_trigger_data {
struct trace_event_file *file;
+ struct trace_event_call *call;
bool enable;
bool hist;
};
@@ -2284,13 +2287,13 @@ static inline const char *get_syscall_name(int syscall)
#ifdef CONFIG_EVENT_TRACING
void trace_event_init(void);
-void trace_event_update_all(struct trace_eval_map **map, int len);
+void trace_event_update_all(struct trace_eval_map **map, int len, struct module *mod);
/* Used from boot time tracer */
extern int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set);
extern int trigger_process_regex(struct trace_event_file *file, char *buff);
#else
static inline void __init trace_event_init(void) { }
-static inline void trace_event_update_all(struct trace_eval_map **map, int len) { }
+static inline void trace_event_update_all(struct trace_eval_map **map, int len, struct module *mod) { }
#endif
#ifdef CONFIG_TRACER_SNAPSHOT
diff --git a/kernel/trace/trace_boot.c b/kernel/trace/trace_boot.c
index 2ca2541c8a58..5da0a8bbb110 100644
--- a/kernel/trace/trace_boot.c
+++ b/kernel/trace/trace_boot.c
@@ -18,6 +18,7 @@
#include <linux/trace_events.h>
#include "trace.h"
+#include "trace_dynevent.h"
#define MAX_BUF_LEN 256
@@ -172,6 +173,93 @@ trace_boot_add_synth_event(struct xbc_node *node, const char *event)
}
#endif
+#if defined(CONFIG_EPROBE_EVENTS) || defined(CONFIG_FPROBE_EVENTS)
+static int __init
+trace_boot_add_probe_event(struct xbc_node *node, const char *group,
+ const char *event, char type, const char *type_name)
+{
+ struct xbc_node *anode;
+ char buf[MAX_BUF_LEN];
+ const char *val;
+ int ret = 0;
+
+ xbc_node_for_each_array_value(node, "probes", anode, val) {
+ if (val[0] == type && (val[1] == ':' || isspace(val[1]))) {
+ ret = strscpy(buf, val, MAX_BUF_LEN);
+ if (ret < 0) {
+ pr_err("%s command is too long: %s\n", type_name, val);
+ break;
+ }
+ } else {
+ ret = snprintf(buf, MAX_BUF_LEN, "%c:%s/%s %s", type, group, event, val);
+ if (ret >= MAX_BUF_LEN || ret < 0) {
+ pr_err("%s command is too long: %c:%s/%s %s\n",
+ type_name, type, group, event, val);
+ ret = -E2BIG;
+ break;
+ }
+ }
+
+ ret = dyn_event_create(buf, NULL);
+ if (ret) {
+ pr_err("Failed to add %s: %s\n", type_name, buf);
+ break;
+ }
+ }
+
+ return ret;
+}
+#endif
+
+#ifdef CONFIG_EPROBE_EVENTS
+static inline int __init
+trace_boot_add_eprobe_event(struct xbc_node *node, const char *group,
+ const char *event)
+{
+ return trace_boot_add_probe_event(node, group, event, 'e', "eprobe");
+}
+#else
+static inline int __init
+trace_boot_add_eprobe_event(struct xbc_node *node, const char *group,
+ const char *event)
+{
+ pr_err("Event probe is not supported.\n");
+ return -EOPNOTSUPP;
+}
+#endif
+
+#ifdef CONFIG_FPROBE_EVENTS
+static inline int __init
+trace_boot_add_fprobe_event(struct xbc_node *node, const char *group,
+ const char *event)
+{
+ return trace_boot_add_probe_event(node, group, event, 'f', "fprobe");
+}
+
+static inline int __init
+trace_boot_add_tprobe_event(struct xbc_node *node, const char *group,
+ const char *event)
+{
+ return trace_boot_add_probe_event(node, group, event, 't', "tprobe");
+}
+#else
+static inline int __init
+trace_boot_add_fprobe_event(struct xbc_node *node, const char *group,
+ const char *event)
+{
+ pr_err("Fprobe event is not supported.\n");
+ return -EOPNOTSUPP;
+}
+
+static inline int __init
+trace_boot_add_tprobe_event(struct xbc_node *node, const char *group,
+ const char *event)
+{
+ pr_err("Tracepoint probe is not supported.\n");
+ return -EOPNOTSUPP;
+}
+#endif
+
#ifdef CONFIG_HIST_TRIGGERS
static int __init __printf(3, 4)
append_printf(char **bufp, char *end, const char *fmt, ...)
@@ -477,6 +565,15 @@ trace_boot_init_one_event(struct trace_array *tr, struct xbc_node *gnode,
if (!strcmp(group, "synthetic"))
if (trace_boot_add_synth_event(enode, event) < 0)
return;
+ if (!strcmp(group, "eprobes"))
+ if (trace_boot_add_eprobe_event(enode, group, event) < 0)
+ return;
+ if (!strcmp(group, "fprobes"))
+ if (trace_boot_add_fprobe_event(enode, group, event) < 0)
+ return;
+ if (!strcmp(group, "tracepoints") || !strcmp(group, "tprobes"))
+ if (trace_boot_add_tprobe_event(enode, group, event) < 0)
+ return;
mutex_lock(&event_mutex);
file = find_event_file(tr, group, event);
diff --git a/kernel/trace/trace_btf.c b/kernel/trace/trace_btf.c
index 00172f301f25..ee7a04886bf6 100644
--- a/kernel/trace/trace_btf.c
+++ b/kernel/trace/trace_btf.c
@@ -61,47 +61,50 @@ struct btf_anon_stack {
/*
* Find a member of data structure/union by name and return it.
- * Return NULL if not found, or -EINVAL if parameter is invalid.
- * If the member is an member of anonymous union/structure, the offset
- * of that anonymous union/structure is stored into @anon_offset. Caller
- * can calculate the correct offset from the root data structure by
- * adding anon_offset to the member's offset.
+ * Return NULL if not found, or ERR_PTR(-EINVAL) if parameter is invalid.
+ * If the member is a member of an anonymous union/structure, the bit offset
+ * of that anonymous union/structure is stored into @anon_offset.
+ * If @member_type is non-NULL, the actual containing structure/union type
+ * of the found member is stored into @member_type.
*/
const struct btf_member *btf_find_struct_member(struct btf *btf,
const struct btf_type *type,
const char *member_name,
- u32 *anon_offset)
+ u32 *anon_offset,
+ const struct btf_type **member_type)
{
struct btf_anon_stack *anon_stack;
const struct btf_member *member;
+ const struct btf_type *mtype;
u32 tid, cur_offset = 0;
const char *name;
int i, top = 0;
+ if (!btf_type_is_struct(type))
+ return ERR_PTR(-EINVAL);
+
anon_stack = kzalloc_objs(*anon_stack, BTF_ANON_STACK_MAX);
if (!anon_stack)
return ERR_PTR(-ENOMEM);
retry:
- if (!btf_type_is_struct(type)) {
- member = ERR_PTR(-EINVAL);
- goto out;
- }
-
for_each_member(i, type, member) {
if (!member->name_off) {
/* Anonymous union/struct: push it for later use */
- if (btf_type_skip_modifiers(btf, member->type, &tid) &&
+ mtype = btf_type_skip_modifiers(btf, member->type, &tid);
+ if (mtype && btf_type_is_struct(mtype) &&
top < BTF_ANON_STACK_MAX) {
anon_stack[top].tid = tid;
- anon_stack[top++].offset =
- cur_offset + member->offset;
+ anon_stack[top++].offset = cur_offset +
+ __btf_member_bit_offset(type, member);
}
} else {
name = btf_name_by_offset(btf, member->name_off);
if (name && !strcmp(member_name, name)) {
if (anon_offset)
*anon_offset = cur_offset;
+ if (member_type)
+ *member_type = type;
goto out;
}
}
diff --git a/kernel/trace/trace_btf.h b/kernel/trace/trace_btf.h
index 4bc44bc261e6..4bd26bceae23 100644
--- a/kernel/trace/trace_btf.h
+++ b/kernel/trace/trace_btf.h
@@ -8,4 +8,5 @@ const struct btf_param *btf_get_func_param(const struct btf_type *func_proto,
const struct btf_member *btf_find_struct_member(struct btf *btf,
const struct btf_type *type,
const char *member_name,
- u32 *anon_offset);
+ u32 *anon_offset,
+ const struct btf_type **member_type);
diff --git a/kernel/trace/trace_dynevent.c b/kernel/trace/trace_dynevent.c
index c4dfbc293bae..6f2e39b797e5 100644
--- a/kernel/trace/trace_dynevent.c
+++ b/kernel/trace/trace_dynevent.c
@@ -116,6 +116,8 @@ int dyn_event_release(const char *raw_command, struct dyn_event_operations *type
return ret;
}
+static int create_dyn_event(const char *raw_command);
+
/*
* Locked version of event creation. The event creation must be protected by
* dyn_event_ops_mutex because of protecting trace_probe_log.
@@ -124,6 +126,9 @@ int dyn_event_create(const char *raw_command, struct dyn_event_operations *type)
{
int ret;
+ if (!type)
+ return create_dyn_event(raw_command);
+
mutex_lock(&dyn_event_ops_mutex);
ret = type->create(raw_command);
mutex_unlock(&dyn_event_ops_mutex);
diff --git a/kernel/trace/trace_eprobe.c b/kernel/trace/trace_eprobe.c
index b66d6196338d..998e6390937a 100644
--- a/kernel/trace/trace_eprobe.c
+++ b/kernel/trace/trace_eprobe.c
@@ -87,6 +87,8 @@ static int eprobe_dyn_event_show(struct seq_file *m, struct dyn_event *ev)
seq_printf(m, " %s=%s", ep->tp.args[i].name, ep->tp.args[i].comm);
seq_putc(m, '\n');
+ trace_probe_dump_args(m, &ep->tp);
+
return 0;
}
@@ -172,7 +174,8 @@ static bool eprobe_dyn_event_match(const char *system, const char *event,
if (!slash)
return false;
- if (strncmp(ep->event_system, argv[0], slash - argv[0]))
+ if (strncmp(ep->event_system, argv[0], slash - argv[0]) ||
+ ep->event_system[slash - argv[0]] != '\0')
return false;
if (strcmp(ep->event_name, slash + 1))
return false;
@@ -315,7 +318,7 @@ get_event_field(struct fetch_insn *code, void *rec)
val = (unsigned long)addr;
break;
case FILTER_PTR_STRING:
- val = (unsigned long)(*(char *)addr);
+ val = *(unsigned long *)addr;
break;
default:
WARN_ON_ONCE(1);
@@ -927,7 +930,7 @@ static int __trace_eprobe_create(int argc, const char *argv[])
} else
ep->filter_str = NULL;
- ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ ctx = kzalloc_obj(*ctx);
if (!ctx)
return -ENOMEM;
ctx->event = ep->event;
diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c
index c46e623e7e0d..30c0ddf90887 100644
--- a/kernel/trace/trace_events.c
+++ b/kernel/trace/trace_events.c
@@ -14,6 +14,7 @@
#include <linux/workqueue.h>
#include <linux/security.h>
#include <linux/spinlock.h>
+#include <linux/seq_buf.h>
#include <linux/kthread.h>
#include <linux/tracefs.h>
#include <linux/uaccess.h>
@@ -22,6 +23,7 @@
#include <linux/sort.h>
#include <linux/slab.h>
#include <linux/delay.h>
+#include <linux/btf.h>
#include <trace/events/sched.h>
#include <trace/syscall.h>
@@ -400,6 +402,33 @@ static bool process_string(const char *fmt, int len, struct trace_event_call *ca
return true;
}
+static void test_double_dereference(const char *str, int len,
+ struct trace_event_call *call)
+{
+ const char *ptr;
+ const char *end = str + len;
+
+ ptr = strstr(str, "REC->");
+
+ while (ptr && ptr < end) {
+
+ ptr += 5;
+ for (; ptr < end; ptr++) {
+ if (ptr[0] == '-' && ptr[1] == '>') {
+ pr_warn("TRACE EVENT ERROR: Event %s has double dereference in TP_printk: %.*s\n",
+ trace_event_name(call), len, str);
+ WARN_ONCE(1, "Event %s has double dereference in TP_printk: %.*s\n",
+ trace_event_name(call), len, str);
+ return;
+ }
+ if (!isalnum(*ptr) && *ptr != '_')
+ break;
+ }
+
+ ptr = strstr(ptr, "REC->");
+ }
+}
+
static void handle_dereference_arg(const char *arg_str, u64 string_flags, int len,
u64 *dereference_flags, int arg,
struct trace_event_call *call)
@@ -459,12 +488,6 @@ static void test_event_printk(struct trace_event_call *call)
if (in_quote) {
arg = 0;
first = false;
- /*
- * If there was no %p* uses
- * the fmt is OK.
- */
- if (!dereference_flags)
- return;
}
}
if (in_quote) {
@@ -576,6 +599,8 @@ static void test_event_printk(struct trace_event_call *call)
continue;
}
+ test_double_dereference(fmt + start_arg, e - start_arg, call);
+
if (dereference_flags & (1ULL << arg)) {
handle_dereference_arg(fmt + start_arg, string_flags,
e - start_arg,
@@ -589,6 +614,8 @@ static void test_event_printk(struct trace_event_call *call)
}
}
+ test_double_dereference(fmt + start_arg, i - start_arg, call);
+
if (dereference_flags & (1ULL << arg)) {
handle_dereference_arg(fmt + start_arg, string_flags,
i - start_arg,
@@ -945,7 +972,7 @@ static int remove_cache_mod(struct trace_array *tr, const char *mod,
if (strcmp(event_mod->module, mod) != 0)
continue;
- if (match && strcmp(event_mod->match, match) != 0)
+ if (match && (!event_mod->match || strcmp(event_mod->match, match) != 0))
continue;
if (system &&
@@ -1350,7 +1377,9 @@ __ftrace_set_clr_event_nolock(struct trace_array *tr, const char *match,
call = file->event_call;
/* If a module is specified, skip events that are not that module */
- if (module && (!call->module || strcmp(module_name(call->module), module)))
+ if (module &&
+ ((call->flags & TRACE_EVENT_FL_DYNAMIC) ||
+ !call->module || strcmp(module_name(call->module), module)))
continue;
name = trace_event_name(call);
@@ -1460,6 +1489,8 @@ int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set)
/* Put back the colon to allow this to be called again */
if (buf)
*(buf - 1) = ':';
+ if (mod)
+ *(mod - 5) = ':';
return ret;
}
@@ -2200,6 +2231,61 @@ event_id_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos)
}
#endif
+#ifdef CONFIG_BPF_EVENTS
+static ssize_t
+event_btf_ids_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos)
+{
+ struct trace_event_file *file;
+ struct trace_event_call *call;
+ const struct btf_type *t;
+ struct module *mod = NULL;
+ u32 raw_id = 0, tp_id = 0, obj_id = 0;
+ const u32 *ids;
+ struct btf *btf;
+ char buf[128];
+ int len;
+
+ /* Module unload could free call->class and ids[] mid-read. */
+ scoped_guard(mutex, &event_mutex) {
+ file = event_file_file(filp);
+ if (!file)
+ return -ENODEV;
+
+ call = file->event_call;
+ ids = call->class->btf_ids;
+ if (!ids)
+ return -ENOENT;
+ if (!(call->flags & TRACE_EVENT_FL_DYNAMIC))
+ mod = (struct module *)call->module;
+
+ btf = btf_get_module_btf(mod);
+ if (IS_ERR_OR_NULL(btf))
+ return -ENOENT;
+
+ /* Module-local ids in ids[] need base+local relocation. */
+ tp_id = btf_relocate_id(btf, ids[1]);
+
+ /*
+ * Without FL_TRACEPOINT the dispatcher is shared (e.g. all
+ * per-syscall events fan out from __bpf_trace_sys_enter), so
+ * raw_btf_id has no per-event attach point — report 0.
+ */
+ if (call->flags & TRACE_EVENT_FL_TRACEPOINT) {
+ t = btf_type_by_id(btf, btf_relocate_id(btf, ids[0]));
+ raw_id = t ? t->type : 0;
+ }
+ obj_id = btf_obj_id(btf);
+ btf_put(btf);
+ }
+
+ len = scnprintf(buf, sizeof(buf),
+ "btf_obj_id: %u\nraw_btf_id: %u\ntp_btf_id: %u\n",
+ obj_id, raw_id, tp_id);
+
+ return simple_read_from_buffer(ubuf, cnt, ppos, buf, len);
+}
+#endif
+
static ssize_t
event_filter_read(struct file *filp, char __user *ubuf, size_t cnt,
loff_t *ppos)
@@ -2652,14 +2738,14 @@ static const struct file_operations ftrace_show_event_filters_fops = {
.open = ftrace_event_show_filters_open,
.read = seq_read,
.llseek = seq_lseek,
- .release = seq_release,
+ .release = ftrace_event_release,
};
static const struct file_operations ftrace_show_event_triggers_fops = {
.open = ftrace_event_show_triggers_open,
.read = seq_read,
.llseek = seq_lseek,
- .release = seq_release,
+ .release = ftrace_event_release,
};
static const struct file_operations ftrace_set_event_pid_fops = {
@@ -2700,6 +2786,13 @@ static const struct file_operations ftrace_event_id_fops = {
};
#endif
+#ifdef CONFIG_BPF_EVENTS
+static const struct file_operations ftrace_event_btf_ids_fops = {
+ .read = event_btf_ids_read,
+ .llseek = default_llseek,
+};
+#endif
+
static const struct file_operations ftrace_event_filter_fops = {
.open = tracing_open_file_tr,
.read = event_filter_read,
@@ -2817,7 +2910,17 @@ ftrace_event_set_open(struct inode *inode, struct file *file)
static int
ftrace_event_show_filters_open(struct inode *inode, struct file *file)
{
- return ftrace_event_open(inode, file, &show_show_event_filters_seq_ops);
+ struct trace_array *tr = inode->i_private;
+ int ret;
+
+ ret = tracing_check_open_get_tr(tr);
+ if (ret)
+ return ret;
+
+ ret = ftrace_event_open(inode, file, &show_show_event_filters_seq_ops);
+ if (ret < 0)
+ trace_array_put(tr);
+ return ret;
}
/**
@@ -2831,7 +2934,17 @@ ftrace_event_show_filters_open(struct inode *inode, struct file *file)
static int
ftrace_event_show_triggers_open(struct inode *inode, struct file *file)
{
- return ftrace_event_open(inode, file, &show_show_event_triggers_seq_ops);
+ struct trace_array *tr = inode->i_private;
+ int ret;
+
+ ret = tracing_check_open_get_tr(tr);
+ if (ret)
+ return ret;
+
+ ret = ftrace_event_open(inode, file, &show_show_event_triggers_seq_ops);
+ if (ret < 0)
+ trace_array_put(tr);
+ return ret;
}
static int
@@ -3093,6 +3206,14 @@ static int event_callback(const char *name, umode_t *mode, void **data,
}
#endif
+#ifdef CONFIG_BPF_EVENTS
+ if (call->class->btf_ids && strcmp(name, "btf_ids") == 0) {
+ *mode = TRACE_MODE_READ;
+ *fops = &ftrace_event_btf_ids_fops;
+ return 1;
+ }
+#endif
+
#ifdef CONFIG_HIST_TRIGGERS
if (strcmp(name, "hist") == 0) {
*mode = TRACE_MODE_READ;
@@ -3147,7 +3268,14 @@ event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file)
.callback = event_callback,
},
#endif
-#define NR_RO_EVENT_ENTRIES (1 + IS_ENABLED(CONFIG_PERF_EVENTS))
+#ifdef CONFIG_BPF_EVENTS
+ {
+ .name = "btf_ids",
+ .callback = event_callback,
+ },
+#endif
+#define NR_RO_EVENT_ENTRIES (1 + IS_ENABLED(CONFIG_PERF_EVENTS) + \
+ IS_ENABLED(CONFIG_BPF_EVENTS))
/* Readonly files must be above this line and counted by NR_RO_EVENT_ENTRIES. */
{
.name = "enable",
@@ -3190,6 +3318,13 @@ event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file)
if (WARN_ON_ONCE(strcmp(call->class->system, TRACE_SYSTEM) == 0))
return -ENODEV;
+ ret = event_define_fields(call);
+ if (ret < 0) {
+ pr_warn("Could not initialize trace point events/%s\n",
+ trace_event_name(call));
+ return ret;
+ }
+
e_events = event_subsystem_dir(tr, call->class->system, file, parent);
if (!e_events)
return -ENOMEM;
@@ -3208,12 +3343,6 @@ event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file)
file->ei = ei;
- ret = event_define_fields(call);
- if (ret < 0) {
- pr_warn("Could not initialize trace point events/%s\n", name);
- return ret;
- }
-
/* Gets decremented on freeing of the "enable" file */
event_file_get(file);
@@ -3555,7 +3684,7 @@ static void update_event_fields(struct trace_event_call *call,
}
/* Update all events for replacing eval and sanitizing */
-void trace_event_update_all(struct trace_eval_map **map, int len)
+void trace_event_update_all(struct trace_eval_map **map, int len, struct module *mod)
{
struct trace_event_call *call, *p;
const char *last_system = NULL;
@@ -3564,8 +3693,13 @@ void trace_event_update_all(struct trace_eval_map **map, int len)
int last_i;
int i;
+ mutex_lock(&event_mutex);
down_write(&trace_event_sem);
list_for_each_entry_safe(call, p, &ftrace_events, list) {
+
+ if (mod && call->module != mod)
+ continue;
+
/* events are usually grouped together with systems */
if (!last_system || call->class->system != last_system) {
first = true;
@@ -3602,6 +3736,7 @@ void trace_event_update_all(struct trace_eval_map **map, int len)
cond_resched();
}
up_write(&trace_event_sem);
+ mutex_unlock(&event_mutex);
}
static bool event_in_systems(struct trace_event_call *call,
@@ -3931,8 +4066,8 @@ static void trace_module_add_events(struct module *mod)
end = mod->trace_events + mod->num_trace_events;
for_each_event(call, start, end) {
- __register_event(*call, mod);
- __add_event_to_tracers(*call);
+ if (!__register_event(*call, mod))
+ __add_event_to_tracers(*call);
}
update_cache_events(mod);
@@ -4501,13 +4636,20 @@ extern struct trace_event_call *__start_ftrace_events[];
extern struct trace_event_call *__stop_ftrace_events[];
static char bootup_event_buf[COMMAND_LINE_SIZE] __initdata;
+static struct seq_buf bootup_event_seq __initdata = {
+ .buffer = bootup_event_buf,
+ .size = sizeof(bootup_event_buf),
+};
static __init int setup_trace_event(char *str)
{
- if (bootup_event_buf[0] != '\0')
- strlcat(bootup_event_buf, ",", COMMAND_LINE_SIZE);
+ if (seq_buf_used(&bootup_event_seq) > 0)
+ seq_buf_puts(&bootup_event_seq, ",");
- strlcat(bootup_event_buf, str, COMMAND_LINE_SIZE);
+ seq_buf_puts(&bootup_event_seq, str);
+
+ if (seq_buf_has_overflowed(&bootup_event_seq))
+ return -ENOMEM;
trace_set_ring_buffer_expanded(NULL);
disable_tracing_selftest("running event tracing");
@@ -4766,6 +4908,7 @@ static __init int event_trace_enable(void)
*/
__trace_early_add_events(tr);
+ seq_buf_str(&bootup_event_seq);
early_enable_events(tr, bootup_event_buf, false);
trace_printk_start_comm();
@@ -4794,6 +4937,7 @@ static __init int event_trace_enable_again(void)
if (!tr)
return -ENODEV;
+ seq_buf_str(&bootup_event_seq);
early_enable_events(tr, bootup_event_buf, true);
return 0;
@@ -4897,6 +5041,8 @@ static __init void event_test_stuff(void)
struct task_struct *test_thread;
test_thread = kthread_run(event_test_thread, NULL, "test-events");
+ if (WARN_ON(IS_ERR(test_thread)))
+ return;
msleep(1);
kthread_stop(test_thread);
}
diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c
index 609325f57942..2b46ca536045 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -1027,6 +1027,9 @@ static int regex_match_full(char *str, struct regex *r, int len)
if (!len)
return strcmp(str, r->pattern) == 0;
+ if (len < r->len)
+ return 0;
+
return strncmp(str, r->pattern, len) == 0;
}
@@ -1056,11 +1059,9 @@ static int regex_match_end(char *str, struct regex *r, int len)
return 0;
}
-static int regex_match_glob(char *str, struct regex *r, int len __maybe_unused)
+static int regex_match_glob(char *str, struct regex *r, int len)
{
- if (glob_match(r->pattern, str))
- return 1;
- return 0;
+ return glob_match_len(r->pattern, str, len) ? 1 : 0;
}
/**
diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c
index 82ce492ab268..8af97fd4ee2d 100644
--- a/kernel/trace/trace_events_hist.c
+++ b/kernel/trace/trace_events_hist.c
@@ -94,7 +94,6 @@ typedef u64 (*hist_field_fn_t) (struct hist_field *field,
#define HIST_FIELD_OPERANDS_MAX 2
#define HIST_FIELDS_MAX (TRACING_MAP_FIELDS_MAX + TRACING_MAP_VARS_MAX)
#define HIST_ACTIONS_MAX 8
-#define HIST_CONST_DIGITS_MAX 21
#define HIST_DIV_SHIFT 20 /* For optimizing division by constants */
enum field_op_id {
@@ -170,7 +169,6 @@ struct hist_field {
struct hist_field *operands[HIST_FIELD_OPERANDS_MAX];
struct hist_trigger_data *hist_data;
enum hist_field_fn fn_num;
- unsigned int ref;
unsigned int size;
unsigned int offset;
unsigned int is_signed;
@@ -1733,86 +1731,94 @@ static const char *get_hist_field_flags(struct hist_field *hist_field)
return flags_str;
}
-static void expr_field_str(struct hist_field *field, char *expr)
+static bool expr_field_str(struct hist_field *field, struct seq_buf *s)
{
+ const char *field_name;
+
if (field->flags & HIST_FIELD_FL_VAR_REF) {
if (!field->system)
- strcat(expr, "$");
- } else if (field->flags & HIST_FIELD_FL_CONST) {
- char str[HIST_CONST_DIGITS_MAX];
+ seq_buf_putc(s, '$');
+ } else if (field->flags & HIST_FIELD_FL_CONST)
+ seq_buf_printf(s, "%llu", field->constant);
- snprintf(str, HIST_CONST_DIGITS_MAX, "%llu", field->constant);
- strcat(expr, str);
- }
+ field_name = hist_field_name(field, 0);
+ if (!field_name)
+ return false;
- strcat(expr, hist_field_name(field, 0));
+ seq_buf_puts(s, field_name);
if (field->flags && !(field->flags & HIST_FIELD_FL_VAR_REF)) {
const char *flags_str = get_hist_field_flags(field);
- if (flags_str) {
- strcat(expr, ".");
- strcat(expr, flags_str);
- }
+ if (flags_str)
+ seq_buf_printf(s, ".%s", flags_str);
}
+
+ return !seq_buf_has_overflowed(s);
}
static char *expr_str(struct hist_field *field, unsigned int level)
{
- char *expr;
+ char *expr __free(kfree) = NULL;
+ struct seq_buf s;
if (level > 1)
- return NULL;
+ return ERR_PTR(-EINVAL);
expr = kzalloc(MAX_FILTER_STR_VAL, GFP_KERNEL);
if (!expr)
- return NULL;
+ return ERR_PTR(-ENOMEM);
+
+ seq_buf_init(&s, expr, MAX_FILTER_STR_VAL);
if (!field->operands[0]) {
- expr_field_str(field, expr);
- return expr;
+ if (!expr_field_str(field, &s))
+ return ERR_PTR(-E2BIG);
+
+ return_ptr(expr);
}
if (field->operator == FIELD_OP_UNARY_MINUS) {
char *subexpr;
- strcat(expr, "-(");
subexpr = expr_str(field->operands[0], ++level);
- if (!subexpr) {
- kfree(expr);
- return NULL;
- }
- strcat(expr, subexpr);
- strcat(expr, ")");
+ if (IS_ERR(subexpr))
+ return subexpr;
+ seq_buf_printf(&s, "-(%s)", subexpr);
kfree(subexpr);
- return expr;
+ if (seq_buf_has_overflowed(&s))
+ return ERR_PTR(-E2BIG);
+
+ return_ptr(expr);
}
- expr_field_str(field->operands[0], expr);
+ if (!expr_field_str(field->operands[0], &s))
+ return ERR_PTR(-E2BIG);
switch (field->operator) {
case FIELD_OP_MINUS:
- strcat(expr, "-");
+ seq_buf_putc(&s, '-');
break;
case FIELD_OP_PLUS:
- strcat(expr, "+");
+ seq_buf_putc(&s, '+');
break;
case FIELD_OP_DIV:
- strcat(expr, "/");
+ seq_buf_putc(&s, '/');
break;
case FIELD_OP_MULT:
- strcat(expr, "*");
+ seq_buf_putc(&s, '*');
break;
default:
- kfree(expr);
- return NULL;
+ return ERR_PTR(-EINVAL);
}
- expr_field_str(field->operands[1], expr);
+ if (seq_buf_has_overflowed(&s) ||
+ !expr_field_str(field->operands[1], &s))
+ return ERR_PTR(-E2BIG);
- return expr;
+ return_ptr(expr);
}
/*
@@ -1906,16 +1912,8 @@ out:
return field_op;
}
-static void get_hist_field(struct hist_field *hist_field)
-{
- hist_field->ref++;
-}
-
static void __destroy_hist_field(struct hist_field *hist_field)
{
- if (--hist_field->ref > 1)
- return;
-
kfree(hist_field->var.name);
kfree(hist_field->name);
@@ -1962,8 +1960,6 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data,
if (!hist_field)
return NULL;
- hist_field->ref = 1;
-
hist_field->hist_data = hist_data;
if (flags & HIST_FIELD_FL_EXPR || flags & HIST_FIELD_FL_ALIAS)
@@ -1984,9 +1980,7 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data,
if (flags & HIST_FIELD_FL_CONST) {
hist_field->fn_num = HIST_FIELD_FN_CONST;
hist_field->size = sizeof(u64);
- hist_field->type = kstrdup("u64", GFP_KERNEL);
- if (!hist_field->type)
- goto free;
+ hist_field->type = "u64";
goto out;
}
@@ -2218,10 +2212,8 @@ static struct hist_field *create_var_ref(struct hist_trigger_data *hist_data,
for (i = 0; i < hist_data->n_var_refs; i++) {
ref_field = hist_data->var_refs[i];
if (ref_field->var.idx == var_field->var.idx &&
- ref_field->var.hist_data == var_field->hist_data) {
- get_hist_field(ref_field);
+ ref_field->var.hist_data == var_field->hist_data)
return ref_field;
- }
}
/* Sanity check to avoid out-of-bound write on 'hist_data->var_refs' */
if (hist_data->n_var_refs >= TRACING_MAP_VARS_MAX)
@@ -2229,7 +2221,7 @@ static struct hist_field *create_var_ref(struct hist_trigger_data *hist_data,
ref_field = create_hist_field(var_field->hist_data, NULL, flags, NULL);
if (ref_field) {
if (init_var_ref(ref_field, var_field, system, event_name)) {
- destroy_hist_field(ref_field, 0);
+ __destroy_hist_field(ref_field);
return NULL;
}
@@ -2325,6 +2317,7 @@ parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file,
struct ftrace_event_field *field = NULL;
char *field_name, *modifier, *str;
struct trace_array *tr = file->tr;
+ bool stack_modifier = false;
modifier = str = kstrdup(field_str, GFP_KERNEL);
if (!modifier)
@@ -2347,9 +2340,10 @@ parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file,
*flags |= HIST_FIELD_FL_EXECNAME;
else if (strcmp(modifier, "syscall") == 0)
*flags |= HIST_FIELD_FL_SYSCALL;
- else if (strcmp(modifier, "stacktrace") == 0)
+ else if (strcmp(modifier, "stacktrace") == 0) {
*flags |= HIST_FIELD_FL_STACKTRACE;
- else if (strcmp(modifier, "log2") == 0)
+ stack_modifier = true;
+ } else if (strcmp(modifier, "log2") == 0)
*flags |= HIST_FIELD_FL_LOG2;
else if (strcmp(modifier, "usecs") == 0)
*flags |= HIST_FIELD_FL_TIMESTAMP_USECS;
@@ -2410,6 +2404,7 @@ parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file,
*flags |= HIST_FIELD_FL_CPU;
} else if (field && field->filter_type == FILTER_STACKTRACE) {
*flags |= HIST_FIELD_FL_STACKTRACE;
+ field = NULL;
} else if (field && field->filter_type == FILTER_COMM) {
*flags |= HIST_FIELD_FL_COMM | HIST_FIELD_FL_STRING;
} else {
@@ -2420,6 +2415,12 @@ parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file,
}
}
}
+
+ if (stack_modifier &&
+ (!field || field->filter_type != FILTER_STACKTRACE)) {
+ hist_err(tr, HIST_ERR_BAD_FIELD_MODIFIER, errpos(field_str));
+ field = ERR_PTR(-EINVAL);
+ }
out:
kfree(str);
@@ -2626,6 +2627,11 @@ static struct hist_field *parse_unary(struct hist_trigger_data *hist_data,
expr->is_signed = operand1->is_signed;
expr->operator = FIELD_OP_UNARY_MINUS;
expr->name = expr_str(expr, 0);
+ if (IS_ERR(expr->name)) {
+ ret = PTR_ERR(expr->name);
+ expr->name = NULL;
+ goto free;
+ }
expr->type = kstrdup_const(operand1->type, GFP_KERNEL);
if (!expr->type) {
ret = -ENOMEM;
@@ -2838,6 +2844,11 @@ static struct hist_field *parse_expr(struct hist_trigger_data *hist_data,
destroy_hist_field(operand1, 0);
expr->name = expr_str(expr, 0);
+ if (IS_ERR(expr->name)) {
+ ret = PTR_ERR(expr->name);
+ expr->name = NULL;
+ goto free_expr;
+ }
} else {
/* The operand sizes should be the same, so just pick one */
expr->size = operand1->size;
@@ -2851,6 +2862,11 @@ static struct hist_field *parse_expr(struct hist_trigger_data *hist_data,
}
expr->name = expr_str(expr, 0);
+ if (IS_ERR(expr->name)) {
+ ret = PTR_ERR(expr->name);
+ expr->name = NULL;
+ goto free_expr;
+ }
}
return expr;
@@ -3256,7 +3272,6 @@ static struct hist_field *create_var(struct hist_trigger_data *hist_data,
goto out;
}
- var->ref = 1;
var->flags = HIST_FIELD_FL_VAR;
var->var.idx = idx;
var->var.hist_data = var->hist_data = hist_data;
@@ -4293,8 +4308,7 @@ static int __create_val_field(struct hist_trigger_data *hist_data,
goto err;
} else {
/* Value */
- if (hist_field->flags & (HIST_FIELD_FL_GRAPH | HIST_FIELD_FL_PERCENT |
- HIST_FIELD_FL_BUCKET | HIST_FIELD_FL_LOG2 |
+ if (hist_field->flags & (HIST_FIELD_FL_BUCKET | HIST_FIELD_FL_LOG2 |
HIST_FIELD_FL_SYM | HIST_FIELD_FL_SYM_OFFSET |
HIST_FIELD_FL_SYSCALL | HIST_FIELD_FL_STACKTRACE))
goto err;
@@ -4311,6 +4325,7 @@ static int __create_val_field(struct hist_trigger_data *hist_data,
return ret;
err:
hist_err(file->tr, HIST_ERR_BAD_FIELD_MODIFIER, errpos(field_str));
+ destroy_hist_field(hist_field, 0);
return -EINVAL;
}
@@ -5670,7 +5685,7 @@ static int print_entries(struct seq_file *m,
{
struct tracing_map_sort_entry **sort_entries = NULL;
struct tracing_map *map = hist_data->map;
- int i, j, n_entries;
+ int i, j, n_entries, ret;
struct hist_val_stat *stats = NULL;
u64 val;
@@ -5680,6 +5695,8 @@ static int print_entries(struct seq_file *m,
if (n_entries < 0)
return n_entries;
+ ret = n_entries;
+
/* Calculate the max and the total for each field if needed. */
for (j = 0; j < hist_data->n_vals; j++) {
if (!(hist_data->fields[j]->flags &
@@ -5688,7 +5705,7 @@ static int print_entries(struct seq_file *m,
if (!stats) {
stats = kzalloc_objs(*stats, hist_data->n_vals);
if (!stats) {
- n_entries = -ENOMEM;
+ ret = -ENOMEM;
goto out;
}
}
@@ -5709,7 +5726,7 @@ static int print_entries(struct seq_file *m,
out:
tracing_map_destroy_sort_entries(sort_entries, n_entries);
- return n_entries;
+ return ret;
}
static void hist_trigger_show(struct seq_file *m,
@@ -6349,6 +6366,7 @@ static void event_hist_trigger_free(struct event_trigger_data *data)
trigger_data_free(data);
+ tracepoint_synchronize_unregister();
remove_hist_vars(hist_data);
unregister_field_var_hists(hist_data);
@@ -6362,17 +6380,18 @@ static int event_hist_trigger_named_init(struct event_trigger_data *data)
{
int ret;
- data->ref++;
-
- save_named_trigger(data->named_data->name, data);
-
ret = event_hist_trigger_init(data->named_data);
if (ret < 0) {
kfree(data->cmd_ops);
data->cmd_ops = &trigger_hist_cmd;
+ return ret;
}
- return ret;
+ data->ref++;
+
+ save_named_trigger(data->named_data->name, data);
+
+ return 0;
}
static void event_hist_trigger_named_free(struct event_trigger_data *data)
@@ -6388,6 +6407,7 @@ static void event_hist_trigger_named_free(struct event_trigger_data *data)
del_named_trigger(data);
trigger_data_free(data);
+ tracepoint_synchronize_unregister();
kfree(cmd_ops);
}
}
@@ -6621,12 +6641,6 @@ static int hist_register_trigger(char *glob,
data->cmd_ops = cmd_ops;
}
- if (data->cmd_ops->init) {
- ret = data->cmd_ops->init(data);
- if (ret < 0)
- goto out;
- }
-
if (hist_data->enable_timestamps) {
char *clock = hist_data->attrs->clock;
@@ -6639,8 +6653,19 @@ static int hist_register_trigger(char *glob,
tracing_set_filter_buffering(file->tr, true);
}
- if (named_data)
+ if (data->cmd_ops->init) {
+ ret = data->cmd_ops->init(data);
+ if (ret < 0) {
+ if (hist_data->enable_timestamps)
+ tracing_set_filter_buffering(file->tr, false);
+ goto out;
+ }
+ }
+
+ if (named_data) {
+ remove_hist_vars(hist_data);
destroy_hist_data(hist_data);
+ }
out:
return ret;
}
@@ -6654,11 +6679,12 @@ static int hist_trigger_enable(struct event_trigger_data *data,
update_cond_flag(file);
- if (trace_event_trigger_enable_disable(file, 1) < 0) {
- list_del_rcu(&data->list);
- update_cond_flag(file);
+ /*
+ * On failure the caller undoes the registration, and
+ * hist_unregister_trigger() can only find the trigger here.
+ */
+ if (trace_event_trigger_enable_disable(file, 1) < 0)
ret--;
- }
return ret;
}
@@ -6736,13 +6762,13 @@ static void hist_unregister_trigger(char *glob,
}
}
- if (test && test->cmd_ops->free)
- test->cmd_ops->free(test);
-
if (hist_data->enable_timestamps) {
if (!hist_data->remove || test)
tracing_set_filter_buffering(file->tr, false);
}
+
+ if (test && test->cmd_ops->free)
+ test->cmd_ops->free(test);
}
static bool hist_file_check_refs(struct trace_event_file *file)
@@ -6947,6 +6973,8 @@ static int event_hist_trigger_parse(struct event_command *cmd_ops,
return ret;
out_unreg:
event_trigger_unregister(cmd_ops, file, glob+1, trigger_data);
+ /* The unregister frees trigger_data, skip out_free */
+ goto out;
out_free:
remove_hist_vars(hist_data);
diff --git a/kernel/trace/trace_events_synth.c b/kernel/trace/trace_events_synth.c
index e6871230bde9..dc15658a887c 100644
--- a/kernel/trace/trace_events_synth.c
+++ b/kernel/trace/trace_events_synth.c
@@ -839,8 +839,10 @@ static struct synth_field *parse_synth_field(int argc, char **argv,
seq_buf_puts(&s, "__data_loc ");
seq_buf_puts(&s, field->type);
- if (WARN_ON_ONCE(!seq_buf_buffer_left(&s)))
+ if (WARN_ON_ONCE(!seq_buf_buffer_left(&s))) {
+ kfree(type);
goto free;
+ }
s.buffer[s.len] = '\0';
kfree(field->type);
@@ -1446,13 +1448,13 @@ static int __create_synth_event(const char *name, const char *raw_fields)
if (cmd_version > 1 && n_fields_this_loop >= 1) {
synth_err(SYNTH_ERR_INVALID_CMD, errpos(field_str));
ret = -EINVAL;
- goto err_free_arg;
+ goto err_free_field;
}
if (n_fields == SYNTH_FIELDS_MAX) {
synth_err(SYNTH_ERR_TOO_MANY_FIELDS, 0);
ret = -EINVAL;
- goto err_free_arg;
+ goto err_free_field;
}
fields[n_fields++] = field;
@@ -1491,6 +1493,8 @@ static int __create_synth_event(const char *name, const char *raw_fields)
kfree(saved_fields);
return ret;
+ err_free_field:
+ free_synth_field(field);
err_free_arg:
argv_free(argv);
err:
diff --git a/kernel/trace/trace_events_trigger.c b/kernel/trace/trace_events_trigger.c
index 655db2e82513..4d2fde996c0f 100644
--- a/kernel/trace/trace_events_trigger.c
+++ b/kernel/trace/trace_events_trigger.c
@@ -38,6 +38,13 @@ static void trigger_create_kthread_locked(void)
}
}
+static void trigger_data_free_one(struct event_trigger_data *data)
+{
+ if (data->private_data_free)
+ data->private_data_free(data);
+ kfree(data);
+}
+
static void trigger_data_free_queued_locked(void)
{
struct event_trigger_data *data, *tmp;
@@ -52,7 +59,7 @@ static void trigger_data_free_queued_locked(void)
tracepoint_synchronize_unregister();
llist_for_each_entry_safe(data, tmp, llnodes, llist)
- kfree(data);
+ trigger_data_free_one(data);
}
/* Bulk garbage collection of event_trigger_data elements */
@@ -75,7 +82,7 @@ static int trigger_kthread_fn(void *ignore)
tracepoint_synchronize_unregister();
llist_for_each_entry_safe(data, tmp, llnodes, llist)
- kfree(data);
+ trigger_data_free_one(data);
}
return 0;
@@ -1717,6 +1724,15 @@ int event_enable_trigger_print(struct seq_file *m,
return 0;
}
+static void enable_trigger_private_data_free(struct event_trigger_data *data)
+{
+ struct enable_trigger_data *enable_data = data->private_data;
+
+ /* The file may already be freed here, only the call is kept alive */
+ trace_event_put_ref(enable_data->call);
+ kfree(enable_data);
+}
+
void event_enable_trigger_free(struct event_trigger_data *data)
{
struct enable_trigger_data *enable_data = data->private_data;
@@ -1728,9 +1744,8 @@ void event_enable_trigger_free(struct event_trigger_data *data)
if (!data->ref) {
/* Remove the SOFT_MODE flag */
trace_event_enable_disable(enable_data->file, 0, 1);
- trace_event_put_ref(enable_data->file->event_call);
+ data->private_data_free = enable_trigger_private_data_free;
trigger_data_free(data);
- kfree(enable_data);
}
}
@@ -1739,7 +1754,7 @@ int event_enable_trigger_parse(struct event_command *cmd_ops,
char *glob, char *cmd, char *param_and_filter)
{
struct trace_event_file *event_enable_file;
- struct enable_trigger_data *enable_data;
+ struct enable_trigger_data *enable_data __free(kfree) = NULL;
struct event_trigger_data *trigger_data;
struct trace_array *tr = file->tr;
char *param, *filter;
@@ -1787,19 +1802,16 @@ int event_enable_trigger_parse(struct event_command *cmd_ops,
enable_data->hist = hist;
enable_data->enable = enable;
enable_data->file = event_enable_file;
+ enable_data->call = event_enable_file->event_call;
trigger_data = trigger_data_alloc(cmd_ops, cmd, param, enable_data);
- if (!trigger_data) {
- kfree(enable_data);
+ if (!trigger_data)
return ret;
- }
if (remove) {
event_trigger_unregister(cmd_ops, file, glob+1, trigger_data);
kfree(trigger_data);
- kfree(enable_data);
- ret = 0;
- return ret;
+ return 0;
}
/* Up the trigger_data count to make sure nothing frees it on failure */
@@ -1828,7 +1840,12 @@ int event_enable_trigger_parse(struct event_command *cmd_ops,
if (ret)
goto out_disable;
+ /* It's now safe to free the reference taken earlier */
event_trigger_free(trigger_data);
+
+ /* The enabled_data is assigned to trigger_data->private_data */
+ retain_and_null_ptr(enable_data);
+
return ret;
out_disable:
trace_event_enable_disable(event_enable_file, 0, 1);
@@ -1837,7 +1854,6 @@ int event_enable_trigger_parse(struct event_command *cmd_ops,
out_free:
event_trigger_reset_filter(cmd_ops, trigger_data);
event_trigger_free(trigger_data);
- kfree(enable_data);
return ret;
}
diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c
index c4ba484f7b38..f658c3a77aa7 100644
--- a/kernel/trace/trace_events_user.c
+++ b/kernel/trace/trace_events_user.c
@@ -109,6 +109,9 @@ struct user_event_enabler {
/* Track enable bit, flags, etc. Aligned for bitops. */
unsigned long values;
+
+ /* Defer the event put and enabler free past an RCU grace period. */
+ struct rcu_work put_rwork;
};
/* Bits 0-5 are for the bit to update upon enable/disable (0-63 allowed) */
@@ -396,17 +399,39 @@ error:
return NULL;
};
-static void user_event_enabler_destroy(struct user_event_enabler *enabler,
- bool locked)
+static void delayed_user_event_enabler_put(struct work_struct *work)
{
- list_del_rcu(&enabler->mm_enablers_link);
+ struct user_event_enabler *enabler = container_of(to_rcu_work(work),
+ struct user_event_enabler, put_rwork);
/* No longer tracking the event via the enabler */
- user_event_put(enabler->event, locked);
+ user_event_put(enabler->event, false);
+ /* Run from queue_rcu_work(), the RCU grace period has elapsed */
kfree(enabler);
}
+static void user_event_enabler_destroy(struct user_event_enabler *enabler)
+{
+ list_del_rcu(&enabler->mm_enablers_link);
+
+ /*
+ * The enabler is removed from an RCU-traversed list
+ * (user_event_mm_dup() walks mm->enablers under rcu_read_lock() only),
+ * and readers there dereference enabler->event and take a new ref on
+ * it. Both the put of that event reference and the free of the enabler
+ * therefore have to wait for a grace period so no reader can be looking
+ * at the enabler or racing the last put of its event.
+ *
+ * The put itself must not run in RCU context: when it drops the last
+ * reference user_event_put() takes event_mutex, which cannot be taken
+ * from a softirq/RCU callback. Defer both to a work item scheduled
+ * after a grace period via queue_rcu_work().
+ */
+ INIT_RCU_WORK(&enabler->put_rwork, delayed_user_event_enabler_put);
+ queue_rcu_work(system_percpu_wq, &enabler->put_rwork);
+}
+
static int user_event_mm_fault_in(struct user_event_mm *mm, unsigned long uaddr,
int attempt)
{
@@ -464,7 +489,7 @@ static void user_event_enabler_fault_fixup(struct work_struct *work)
/* User asked for enabler to be removed during fault */
if (test_bit(ENABLE_VAL_FREEING_BIT, ENABLE_BITOPS(enabler))) {
- user_event_enabler_destroy(enabler, true);
+ user_event_enabler_destroy(enabler);
goto out;
}
@@ -764,7 +789,7 @@ static void user_event_mm_destroy(struct user_event_mm *mm)
struct user_event_enabler *enabler, *next;
list_for_each_entry_safe(enabler, next, &mm->enablers, mm_enablers_link)
- user_event_enabler_destroy(enabler, false);
+ user_event_enabler_destroy(enabler);
mmdrop(mm->mm);
kfree(mm);
@@ -843,6 +868,9 @@ void user_event_mm_dup(struct task_struct *t, struct user_event_mm *old_mm)
struct user_event_mm *mm = user_event_mm_alloc(t);
struct user_event_enabler *enabler;
+ /* On failure, do not free parent's copy */
+ t->user_event_mm = NULL;
+
if (!mm)
return;
@@ -1094,10 +1122,9 @@ static void user_event_destroy_validators(struct user_event *user)
}
}
-static void user_event_destroy_fields(struct user_event *user)
+static void user_event_destroy_fields(struct list_head *head)
{
struct ftrace_event_field *field, *next;
- struct list_head *head = &user->fields;
list_for_each_entry_safe(field, next, head, link) {
list_del(&field->link);
@@ -1474,17 +1501,32 @@ static int user_event_set_call_visible(struct user_event *user, bool visible)
static int destroy_user_event(struct user_event *user)
{
+ LIST_HEAD(fields);
int ret = 0;
lockdep_assert_held(&event_mutex);
- /* Must destroy fields before call removal */
- user_event_destroy_fields(user);
+ /*
+ * Detach the fields before removing the call. Removing the event
+ * frees the field list memory (trace_destroy_fields() is run on
+ * successful removal and kmem_cache_free()s the fields), but the
+ * fields here are allocated and owned by user_events. Destroy
+ * them separately once removal has succeeded.
+ */
+ list_splice_init(&user->fields, &fields);
ret = user_event_set_call_visible(user, false);
- if (ret)
+ if (ret) {
+ /*
+ * Removal failed and the event stays registered, recover
+ * the fields so it is left in a consistent state.
+ */
+ list_splice(&fields, &user->fields);
return ret;
+ }
+
+ user_event_destroy_fields(&fields);
dyn_event_remove(&user->devent);
hash_del(&user->node);
@@ -1813,7 +1855,7 @@ static int user_event_show(struct seq_file *m, struct dyn_event *ev)
list_for_each_entry_reverse(field, head, link) {
if (depth == 0)
- seq_puts(m, " ");
+ seq_putc(m, ' ');
else
seq_puts(m, "; ");
@@ -1825,7 +1867,7 @@ static int user_event_show(struct seq_file *m, struct dyn_event *ev)
depth++;
}
- seq_puts(m, "\n");
+ seq_putc(m, '\n');
return 0;
}
@@ -2184,7 +2226,7 @@ static int user_event_parse(struct user_event_group *group, char *name,
put_user_lock:
mutex_unlock(&event_mutex);
put_user:
- user_event_destroy_fields(user);
+ user_event_destroy_fields(&user->fields);
user_event_destroy_validators(user);
kfree(user->call.print_fmt);
@@ -2645,7 +2687,7 @@ static long user_events_ioctl_unreg(unsigned long uarg)
flags |= enabler->values & ENABLE_VAL_COMPAT_MASK;
if (!test_bit(ENABLE_VAL_FAULTING_BIT, ENABLE_BITOPS(enabler)))
- user_event_enabler_destroy(enabler, true);
+ user_event_enabler_destroy(enabler);
/* Removed at least one */
ret = 0;
@@ -2781,7 +2823,7 @@ static int user_seq_show(struct seq_file *m, void *p)
hash_for_each(group->register_table, i, user, node) {
status = user->status;
- seq_printf(m, "%s", EVENT_TP_NAME(user));
+ seq_puts(m, EVENT_TP_NAME(user));
if (status != 0) {
seq_puts(m, " # Used by");
@@ -2794,13 +2836,13 @@ static int user_seq_show(struct seq_file *m, void *p)
busy++;
}
- seq_puts(m, "\n");
+ seq_putc(m, '\n');
active++;
}
mutex_unlock(&group->reg_mutex);
- seq_puts(m, "\n");
+ seq_putc(m, '\n');
seq_printf(m, "Active: %d\n", active);
seq_printf(m, "Busy: %d\n", busy);
diff --git a/kernel/trace/trace_fprobe.c b/kernel/trace/trace_fprobe.c
index 9f5f08c0e7c2..4e9034ebc3fb 100644
--- a/kernel/trace/trace_fprobe.c
+++ b/kernel/trace/trace_fprobe.c
@@ -238,13 +238,10 @@ static bool trace_fprobe_is_busy(struct dyn_event *ev)
static bool trace_fprobe_match_command_head(struct trace_fprobe *tf,
int argc, const char **argv)
{
- char buf[MAX_ARGSTR_LEN + 1];
-
if (!argc)
return true;
- snprintf(buf, sizeof(buf), "%s", trace_fprobe_symbol(tf));
- if (strcmp(buf, argv[0]))
+ if (strcmp(trace_fprobe_symbol(tf), argv[0]))
return false;
argc--; argv++;
@@ -474,7 +471,6 @@ static int fentry_perf_func(struct trace_fprobe *tf, unsigned long entry_ip,
regs = ftrace_fill_perf_regs(fregs, regs);
entry->ip = entry_ip;
- memset(&entry[1], 0, dsize);
store_trace_args(&entry[1], &tf->tp, fregs, NULL, sizeof(*entry), dsize);
perf_trace_buf_submit(entry, size, rctx, call->event.type, 1, regs,
head, NULL);
@@ -764,7 +760,7 @@ static int unregister_fprobe_event(struct trace_fprobe *tf)
return trace_probe_unregister_event_call(&tf->tp);
}
-static int __regsiter_tracepoint_fprobe(struct trace_fprobe *tf)
+static int __register_tracepoint_fprobe(struct trace_fprobe *tf)
{
struct tracepoint_user *tuser __free(tuser_put) = NULL;
struct module *mod __free(module_put) = NULL;
@@ -836,7 +832,7 @@ static int __register_trace_fprobe(struct trace_fprobe *tf)
tf->fp.flags &= ~FPROBE_FL_DISABLED;
if (trace_fprobe_is_tracepoint(tf))
- return __regsiter_tracepoint_fprobe(tf);
+ return __register_tracepoint_fprobe(tf);
/* TODO: handle filter, nofilter or symbol list */
return register_fprobe(&tf->fp, tf->symbol, NULL);
@@ -1449,6 +1445,8 @@ static int trace_fprobe_show(struct seq_file *m, struct dyn_event *ev)
seq_printf(m, " %s=%s", tf->tp.args[i].name, tf->tp.args[i].comm);
seq_putc(m, '\n');
+ trace_probe_dump_args(m, &tf->tp);
+
return 0;
}
@@ -1481,11 +1479,21 @@ static int enable_trace_fprobe(struct trace_event_call *call,
list_for_each_entry(tf, trace_probe_probe_list(tp), tp.list) {
ret = __register_trace_fprobe(tf);
if (ret < 0)
- return ret;
+ goto err;
}
}
return 0;
+
+err:
+ /* Failed to enable one of them. Roll back all */
+ list_for_each_entry(tf, trace_probe_probe_list(tp), tp.list)
+ __unregister_trace_fprobe(tf);
+ if (file)
+ trace_probe_remove_file(tp, file);
+ else
+ trace_probe_clear_flag(tp, TP_FLAG_PROFILE);
+ return ret;
}
/*
diff --git a/kernel/trace/trace_functions.c b/kernel/trace/trace_functions.c
index f283391a4dc8..c879d43a5fbb 100644
--- a/kernel/trace/trace_functions.c
+++ b/kernel/trace/trace_functions.c
@@ -101,7 +101,7 @@ int ftrace_create_function_files(struct trace_array *tr,
return ret;
}
- ftrace_create_filter_files(tr->ops, parent);
+ ftrace_create_filter_files(tr, parent);
return 0;
}
@@ -458,12 +458,12 @@ func_set_flag(struct trace_array *tr, u32 old_flags, u32 bit, int set)
ftrace_func_t func;
u32 new_flags;
- /* Do nothing if already set. */
- if (!!set == !!(tr->current_trace_flags->val & bit))
+ /* We can change this flag only when current tracer is function. */
+ if (tr->current_trace != &function_trace)
return 0;
- /* We can change this flag only when not running. */
- if (tr->current_trace != &function_trace)
+ /* Do nothing if already set. */
+ if (!!set == !!(tr->current_trace_flags->val & bit))
return 0;
new_flags = (tr->current_trace_flags->val & ~bit) | (set ? bit : 0);
diff --git a/kernel/trace/trace_functions_graph.c b/kernel/trace/trace_functions_graph.c
index 0d2d3a2ea7dd..c5befd0c5b9a 100644
--- a/kernel/trace/trace_functions_graph.c
+++ b/kernel/trace/trace_functions_graph.c
@@ -52,6 +52,7 @@ struct fgraph_data {
};
struct ftrace_graph_ret_entry ret;
int failed;
+ int ent_size;
int cpu;
};
@@ -1274,6 +1275,7 @@ print_graph_entry(struct ftrace_graph_ent_entry *field, struct trace_seq *s,
if (s->full) {
data->failed = 1;
data->cpu = cpu;
+ data->ent_size = iter->ent_size;
} else
data->failed = 0;
}
@@ -1349,7 +1351,7 @@ print_graph_return(struct ftrace_graph_ret_entry *retentry, struct trace_seq *s,
* that if the funcgraph-tail option is enabled.
*/
if (func_match && !(flags & TRACE_GRAPH_PRINT_TAIL))
- trace_seq_puts(s, "}");
+ trace_seq_putc(s, '}');
else
trace_seq_printf(s, "} /* %ps */", (void *)func);
}
@@ -1457,6 +1459,7 @@ print_graph_function_flags(struct trace_iterator *iter, u32 flags)
if (data && data->failed) {
field = &data->ent.ent;
iter->cpu = data->cpu;
+ iter->ent_size = data->ent_size;
ret = print_graph_entry(field, s, iter, flags);
if (ret == TRACE_TYPE_HANDLED && iter->cpu != cpu) {
per_cpu_ptr(data->cpu_data, iter->cpu)->ignore = 1;
diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c
index a8420e6abb56..e41a7c113646 100644
--- a/kernel/trace/trace_kprobe.c
+++ b/kernel/trace/trace_kprobe.c
@@ -149,20 +149,28 @@ static bool trace_kprobe_is_busy(struct dyn_event *ev)
static bool trace_kprobe_match_command_head(struct trace_kprobe *tk,
int argc, const char **argv)
{
- char buf[MAX_ARGSTR_LEN + 1];
+ char buf[32];
+ int len;
if (!argc)
return true;
- if (!tk->symbol)
+ if (!tk->symbol) {
snprintf(buf, sizeof(buf), "0x%p", tk->rp.kp.addr);
- else if (tk->rp.kp.offset)
- snprintf(buf, sizeof(buf), "%s+%u",
- trace_kprobe_symbol(tk), tk->rp.kp.offset);
- else
- snprintf(buf, sizeof(buf), "%s", trace_kprobe_symbol(tk));
- if (strcmp(buf, argv[0]))
+ if (strcmp(buf, argv[0]))
+ return false;
+ } else if (tk->rp.kp.offset) {
+ len = strlen(trace_kprobe_symbol(tk));
+ if (strncmp(trace_kprobe_symbol(tk), argv[0], len) ||
+ argv[0][len] != '+')
+ return false;
+
+ snprintf(buf, sizeof(buf), "%u", tk->rp.kp.offset);
+ if (strcmp(buf, &argv[0][len + 1]))
+ return false;
+ } else if (strcmp(trace_kprobe_symbol(tk), argv[0]))
return false;
+
argc--; argv++;
return trace_probe_match_command_args(&tk->tp, argc, argv);
@@ -1320,6 +1328,8 @@ static int trace_kprobe_show(struct seq_file *m, struct dyn_event *ev)
seq_printf(m, " %s=%s", tk->tp.args[i].name, tk->tp.args[i].comm);
seq_putc(m, '\n');
+ trace_probe_dump_args(m, &tk->tp);
+
return 0;
}
@@ -1719,7 +1729,6 @@ kprobe_perf_func(struct trace_kprobe *tk, struct pt_regs *regs)
return 0;
entry->ip = (unsigned long)tk->rp.kp.addr;
- memset(&entry[1], 0, dsize);
store_trace_args(&entry[1], &tk->tp, regs, NULL, sizeof(*entry), dsize);
perf_trace_buf_submit(entry, size, rctx, call->event.type, 1, regs,
head, NULL);
diff --git a/kernel/trace/trace_mmiotrace.c b/kernel/trace/trace_mmiotrace.c
index 226cf66e0d68..77120d467e11 100644
--- a/kernel/trace/trace_mmiotrace.c
+++ b/kernel/trace/trace_mmiotrace.c
@@ -29,6 +29,7 @@ static void mmio_reset_data(struct trace_array *tr)
{
overrun_detected = false;
prev_overruns = 0;
+ atomic_set(&dropped_count, 0);
tracing_reset_online_cpus(&tr->array_buffer);
}
@@ -109,7 +110,6 @@ static void mmio_pipe_open(struct trace_iterator *iter)
iter->private = hiter;
}
-/* XXX: This is not called when the pipe is closed! */
static void mmio_close(struct trace_iterator *iter)
{
struct header_iter *hiter = iter->private;
@@ -146,7 +146,7 @@ static ssize_t mmio_read(struct trace_iterator *iter, struct file *filp,
goto print_out;
}
- if (!hiter)
+ if (!hiter || !hiter->dev)
return 0;
mmio_print_pcidev(s, hiter->dev);
@@ -244,13 +244,16 @@ static enum print_line_t mmio_print_map(struct trace_iterator *iter)
static enum print_line_t mmio_print_mark(struct trace_iterator *iter)
{
struct trace_entry *entry = iter->ent;
- struct print_entry *print = (struct print_entry *)entry;
- const char *msg = print->buf;
+ struct print_entry *print;
+ const char *msg;
struct trace_seq *s = &iter->seq;
unsigned long long t = ns2usecs(iter->ts);
unsigned long usec_rem = do_div(t, USEC_PER_SEC);
unsigned secs = (unsigned long)t;
+ trace_assign_type(print, entry);
+ msg = print->buf;
+
/* The trailing newline must be in the message. */
trace_seq_printf(s, "MARK %u.%06lu %s", secs, usec_rem, msg);
@@ -279,6 +282,7 @@ static struct tracer mmio_tracer __read_mostly =
.start = mmio_trace_start,
.pipe_open = mmio_pipe_open,
.close = mmio_close,
+ .pipe_close = mmio_close,
.read = mmio_read,
.print_line = mmio_print_line,
.noboot = true,
@@ -293,11 +297,15 @@ device_initcall(init_mmio_trace);
static void __trace_mmiotrace_rw(struct trace_array *tr,
struct mmiotrace_rw *rw)
{
- struct trace_buffer *buffer = tr->array_buffer.buffer;
+ struct trace_buffer *buffer;
struct ring_buffer_event *event;
struct trace_mmiotrace_rw *entry;
unsigned int trace_ctx;
+ if (!tr)
+ return;
+
+ buffer = tr->array_buffer.buffer;
trace_ctx = tracing_gen_ctx_flags(0);
event = trace_buffer_lock_reserve(buffer, TRACE_MMIO_RW,
sizeof(*entry), trace_ctx);
@@ -320,11 +328,15 @@ void mmio_trace_rw(struct mmiotrace_rw *rw)
static void __trace_mmiotrace_map(struct trace_array *tr,
struct mmiotrace_map *map)
{
- struct trace_buffer *buffer = tr->array_buffer.buffer;
+ struct trace_buffer *buffer;
struct ring_buffer_event *event;
struct trace_mmiotrace_map *entry;
unsigned int trace_ctx;
+ if (!tr)
+ return;
+
+ buffer = tr->array_buffer.buffer;
trace_ctx = tracing_gen_ctx_flags(0);
event = trace_buffer_lock_reserve(buffer, TRACE_MMIO_MAP,
sizeof(*entry), trace_ctx);
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index 5e83c4f6f2b4..0e1265acd1cc 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -179,7 +179,9 @@ static void osnoise_unregister_instance(struct trace_array *tr)
if (!found)
return;
- kvfree_rcu_mightsleep(inst);
+ /* Do a full sync to ensure that tr remains valid, not just inst */
+ synchronize_rcu();
+ kvfree(inst);
}
/*
diff --git a/kernel/trace/trace_preemptirq.c b/kernel/trace/trace_preemptirq.c
index 0c42b15c3800..b63e3558948f 100644
--- a/kernel/trace/trace_preemptirq.c
+++ b/kernel/trace/trace_preemptirq.c
@@ -30,7 +30,7 @@
#else
#define trace(point, args) \
do { \
- if (trace_##point##_enabled()) { \
+ if (__trace_##point##_enabled()) { \
bool exit_rcu = false; \
if (in_nmi()) \
break; \
diff --git a/kernel/trace/trace_printk.c b/kernel/trace/trace_printk.c
index 3ea17af60169..98171a2398e4 100644
--- a/kernel/trace/trace_printk.c
+++ b/kernel/trace/trace_printk.c
@@ -71,10 +71,9 @@ void hold_module_trace_bprintk_format(const char **start, const char **end)
fmt = NULL;
tb_fmt = kmalloc_obj(*tb_fmt);
if (tb_fmt) {
- fmt = kmalloc(strlen(*iter) + 1, GFP_KERNEL);
+ fmt = kstrdup(*iter, GFP_KERNEL);
if (fmt) {
list_add_tail(&tb_fmt->list, &trace_bprintk_fmt_list);
- strcpy(fmt, *iter);
tb_fmt->fmt = fmt;
} else
kfree(tb_fmt);
diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index fd1caa1f9723..804442b2f7d2 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -188,7 +188,7 @@ void __trace_probe_log_err(int offset, int err_type)
lockdep_assert_held(&dyn_event_ops_mutex);
- if (!trace_probe_log.argv)
+ if (!trace_probe_log.argv || !trace_probe_log.argc)
return;
/* Recalculate the length and allocate buffer */
@@ -342,13 +342,63 @@ static int parse_trace_event(char *arg, struct fetch_insn *code,
ret = parse_trace_event_arg(arg, code, ctx);
if (!ret)
return 0;
- if (strcmp(arg, "comm") == 0 || strcmp(arg, "COMM") == 0) {
- code->op = FETCH_OP_COMM;
- return 0;
- }
return -EINVAL;
}
+/* this_cpu_* parser */
+#define THIS_CPU_PTR_PREFIX "this_cpu_ptr("
+#define THIS_CPU_READ_PREFIX "this_cpu_read("
+#define THIS_CPU_PTR_LEN (sizeof(THIS_CPU_PTR_PREFIX) - 1)
+#define THIS_CPU_READ_LEN (sizeof(THIS_CPU_READ_PREFIX) - 1)
+
+static int
+parse_probe_arg(char *arg, const struct fetch_type *type,
+ struct fetch_insn **pcode, struct fetch_insn *end,
+ struct traceprobe_parse_context *ctx);
+
+static int parse_this_cpu(char *arg, struct traceprobe_parse_context *ctx)
+{
+ bool is_read = false;
+ char *tmp;
+
+ /*
+ * This is only for kernel probes, excluding eprobe, because per-cpu
+ * pointer should not be recorded by events.
+ */
+ if (!(ctx->flags & TPARG_FL_KERNEL) ||
+ (ctx->flags & TPARG_FL_TEVENT)) {
+ trace_probe_log_err(ctx->offset, NOSUP_PERCPU);
+ return -EINVAL;
+ }
+ if (str_has_prefix(arg, THIS_CPU_PTR_PREFIX)) {
+ arg += THIS_CPU_PTR_LEN;
+ ctx->offset += THIS_CPU_PTR_LEN;
+ } else if (str_has_prefix(arg, THIS_CPU_READ_PREFIX)) {
+ arg += THIS_CPU_READ_LEN;
+ ctx->offset += THIS_CPU_READ_LEN;
+ is_read = true;
+ } else {
+ trace_probe_log_err(ctx->offset, BAD_FETCH_ARG);
+ return -EINVAL;
+ }
+
+ tmp = strrchr(arg, ')');
+ if (!tmp) {
+ trace_probe_log_err(ctx->offset + strlen(arg),
+ DEREF_OPEN_BRACE);
+ return -EINVAL;
+ }
+ *tmp = '\0';
+
+ ctx->stack[ctx->depth].type = STATE_DEREF;
+ ctx->stack[ctx->depth].deref.deref = FETCH_OP_CPU_PTR;
+ ctx->stack[ctx->depth].deref.offset = 0;
+ ctx->stack[ctx->depth].deref.cur_offs = ctx->offset;
+ ctx->stack[ctx->depth].deref.inner_arg = arg;
+ ctx->stack[ctx->depth].deref.is_cpu_read = is_read;
+ return 0;
+}
+
#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
static u32 btf_type_int(const struct btf_type *t)
@@ -360,9 +410,8 @@ static bool btf_type_is_char_ptr(struct btf *btf, const struct btf_type *type)
{
const struct btf_type *real_type;
u32 intdata;
- s32 tid;
- real_type = btf_type_skip_modifiers(btf, type->type, &tid);
+ real_type = btf_type_skip_modifiers(btf, type->type, NULL);
if (!real_type)
return false;
@@ -379,14 +428,13 @@ static bool btf_type_is_char_array(struct btf *btf, const struct btf_type *type)
const struct btf_type *real_type;
const struct btf_array *array;
u32 intdata;
- s32 tid;
if (BTF_INFO_KIND(type->info) != BTF_KIND_ARRAY)
return false;
array = (const struct btf_array *)(type + 1);
- real_type = btf_type_skip_modifiers(btf, array->type, &tid);
+ real_type = btf_type_skip_modifiers(btf, array->type, NULL);
intdata = btf_type_int(real_type);
return !(BTF_INT_ENCODING(intdata) & BTF_INT_SIGNED)
@@ -447,10 +495,7 @@ static const char *fetch_type_from_btf_type(struct btf *btf,
return "s64";
case BTF_KIND_PTR:
/* pointer will be converted to "x??" */
- if (IS_ENABLED(CONFIG_64BIT))
- return "x64";
- else
- return "x32";
+ return IS_ENABLED(CONFIG_64BIT) ? "x64" : "x32";
case BTF_KIND_INT:
intdata = btf_type_int(type);
if (BTF_INT_ENCODING(intdata) & BTF_INT_SIGNED) {
@@ -574,6 +619,65 @@ static int split_next_field(char *varname, char **next_field,
return ret;
}
+/* Inner loop for solving dot operator ('.'). Return bit-offset of the given field */
+static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **ptype,
+ struct traceprobe_parse_context *ctx)
+{
+ const struct btf_type *type = *ptype;
+ const struct btf_member *field;
+ const struct btf_type *mtype;
+ struct btf *btf = ctx_btf(ctx);
+ char *fieldname = *pfieldname;
+ int bitoffs = 0;
+ u32 anon_offs;
+ char *next;
+ int is_ptr;
+
+ do {
+ next = NULL;
+ is_ptr = split_next_field(fieldname, &next, ctx);
+ if (is_ptr < 0)
+ return is_ptr;
+
+ anon_offs = 0;
+ field = btf_find_struct_member(btf, type, fieldname,
+ &anon_offs, &mtype);
+ if (IS_ERR(field)) {
+ trace_probe_log_err(ctx->offset, BAD_BTF_TID);
+ return PTR_ERR(field);
+ }
+ if (!field) {
+ trace_probe_log_err(ctx->offset, NO_BTF_FIELD);
+ return -ENOENT;
+ }
+ /* Add anonymous structure/union offset */
+ bitoffs += anon_offs;
+
+ /* Accumulate the bit-offsets of the dot-connected fields */
+ if (btf_type_kflag(mtype)) {
+ bitoffs += BTF_MEMBER_BIT_OFFSET(field->offset);
+ ctx->last_bitsize = BTF_MEMBER_BITFIELD_SIZE(field->offset);
+ } else {
+ bitoffs += field->offset;
+ ctx->last_bitsize = 0;
+ }
+
+ type = btf_type_skip_modifiers(btf, field->type, NULL);
+ if (!type) {
+ trace_probe_log_err(ctx->offset, BAD_BTF_TID);
+ return -EINVAL;
+ }
+
+ if (next)
+ ctx->offset += next - fieldname;
+ fieldname = next;
+ } while (!is_ptr && fieldname);
+
+ *pfieldname = fieldname;
+ *ptype = type;
+
+ return bitoffs;
+}
/*
* Parse the field of data structure. The @type must be a pointer type
* pointing the target data structure type.
@@ -583,16 +687,13 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
struct traceprobe_parse_context *ctx)
{
struct fetch_insn *code = *pcode;
- const struct btf_member *field;
- u32 bitoffs, anon_offs;
- bool is_struct = ctx->struct_btf != NULL;
struct btf *btf = ctx_btf(ctx);
- char *next;
- int is_ptr;
- s32 tid;
+ bool is_first_field = true;
+ int bitoffs;
do {
- if (!is_struct) {
+ /* For the first field of typecast, @type will be the target structure type. */
+ if (!(is_first_field && ctx->struct_btf)) {
/* Outer loop for solving arrow operator ('->') */
if (BTF_INFO_KIND(type->info) != BTF_KIND_PTR) {
trace_probe_log_err(ctx->offset, NO_PTR_STRCT);
@@ -600,66 +701,31 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
}
/* Convert a struct pointer type to a struct type */
- type = btf_type_skip_modifiers(btf, type->type, &tid);
+ type = btf_type_skip_modifiers(btf, type->type, NULL);
if (!type) {
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
return -EINVAL;
}
}
- /* Only the first type can skip being a pointer */
- is_struct = false;
-
- bitoffs = 0;
- do {
- /* Inner loop for solving dot operator ('.') */
- next = NULL;
- is_ptr = split_next_field(fieldname, &next, ctx);
- if (is_ptr < 0)
- return is_ptr;
-
- anon_offs = 0;
- field = btf_find_struct_member(btf, type, fieldname,
- &anon_offs);
- if (IS_ERR(field)) {
- trace_probe_log_err(ctx->offset, BAD_BTF_TID);
- return PTR_ERR(field);
- }
- if (!field) {
- trace_probe_log_err(ctx->offset, NO_BTF_FIELD);
- return -ENOENT;
- }
- /* Add anonymous structure/union offset */
- bitoffs += anon_offs;
-
- /* Accumulate the bit-offsets of the dot-connected fields */
- if (btf_type_kflag(type)) {
- bitoffs += BTF_MEMBER_BIT_OFFSET(field->offset);
- ctx->last_bitsize = BTF_MEMBER_BITFIELD_SIZE(field->offset);
- } else {
- bitoffs += field->offset;
- ctx->last_bitsize = 0;
- }
-
- type = btf_type_skip_modifiers(btf, field->type, &tid);
- if (!type) {
- trace_probe_log_err(ctx->offset, BAD_BTF_TID);
- return -EINVAL;
- }
-
- ctx->offset += next - fieldname;
- fieldname = next;
- } while (!is_ptr && fieldname);
+ bitoffs = get_bitoffset_of_field(&fieldname, &type, ctx);
+ if (bitoffs < 0)
+ return bitoffs;
if (++code == end) {
trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
return -EINVAL;
}
code->op = FETCH_OP_DEREF; /* TODO: user deref support */
code->offset = bitoffs / 8;
+ if (is_first_field && ctx->struct_btf) {
+ /* The first field can be typecasted with field option. */
+ code->offset -= ctx->prefix_byteoffs;
+ }
*pcode = code;
ctx->last_bitoffs = bitoffs % 8;
ctx->last_type = type;
+ is_first_field = false;
} while (fieldname);
return 0;
@@ -678,7 +744,9 @@ static int parse_btf_arg(char *varname,
int i, is_ptr, ret;
u32 tid;
- if (WARN_ON_ONCE(!ctx->funcname && !(ctx->flags & TPARG_FL_TEVENT)))
+ /* Note: field is not separated at this point, so check prefix. */
+ if (!str_has_prefix(varname, "$current") &&
+ !ctx->funcname && !(ctx->flags & TPARG_FL_TEVENT))
return -EINVAL;
is_ptr = split_next_field(varname, &field, ctx);
@@ -691,22 +759,23 @@ static int parse_btf_arg(char *varname,
return -EOPNOTSUPP;
}
- if (ctx->flags & TPARG_FL_TEVENT) {
- ret = parse_trace_event(varname, code, ctx);
+ if (!strcmp(varname, "$current")) {
+ code->op = FETCH_OP_CURRENT;
+ /* If no typecast is specified for $current, use task_struct by default */
+ ret = bpf_find_btf_id("task_struct", BTF_KIND_STRUCT, &ctx->struct_btf);
if (ret < 0) {
- trace_probe_log_err(ctx->offset, BAD_ATTACH_ARG);
- return ret;
+ trace_probe_log_err(ctx->offset, NO_BTF_ENTRY);
+ return -ENOENT;
}
- /* TEVENT is only here via a typecast */
- if (WARN_ON_ONCE(ctx->struct_btf == NULL))
- return -EINVAL;
- type = ctx->last_struct;
+ tid = (u32)ret;
+ type = ctx->last_struct =
+ btf_type_skip_modifiers(ctx->struct_btf, tid, NULL);
goto found_type;
}
if (ctx->flags & TPARG_FL_RETURN && !strcmp(varname, "$retval")) {
code->op = FETCH_OP_RETVAL;
- /* Check whether the function return type is not void */
+ /* Check whether the function return type is not void, even with typecast. */
if (query_btf_context(ctx) == 0) {
if (ctx->proto->type == 0) {
trace_probe_log_err(ctx->offset, NO_RETVAL);
@@ -759,7 +828,7 @@ static int parse_btf_arg(char *varname,
return -ENOENT;
found:
- type = btf_type_skip_modifiers(ctx->btf, tid, &tid);
+ type = btf_type_skip_modifiers(ctx->btf, tid, NULL);
found_type:
if (!type) {
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
@@ -829,39 +898,165 @@ static int query_btf_struct(const char *sname, struct traceprobe_parse_context *
return 0;
}
-static int handle_typecast(char *arg, struct fetch_insn **pcode,
- struct fetch_insn *end,
- struct traceprobe_parse_context *ctx)
+static int parse_btf_casttype(char *casttype, struct traceprobe_parse_context *ctx)
{
- char *tmp;
+ char *field;
int ret;
- /* Currently this only works for eprobes */
- if (!(ctx->flags & TPARG_FL_TEVENT)) {
- trace_probe_log_err(ctx->offset, TYPECAST_NOT_EVENT);
+ /* Field option - evaluated later. */
+ field = strchr(casttype, ',');
+ if (field)
+ *field++ = '\0';
+
+ ret = query_btf_struct(casttype, ctx);
+ if (ret < 0) {
+ trace_probe_log_err(ctx->offset, NO_PTR_STRCT);
return -EINVAL;
}
+ if (field) {
+ struct btf_type *type = (struct btf_type *)ctx->last_struct;
+
+ ctx->offset += field - casttype;
+ ret = get_bitoffset_of_field(&field, &ctx->last_struct, ctx);
+ if (ret < 0)
+ return ret;
+ if (ret % 8) {
+ trace_probe_log_err(ctx->offset, TYPECAST_NOT_ALIGNED);
+ return -EINVAL;
+ }
+ if (field != NULL) {
+ /* this means @field skips an arrow operator ("->"). */
+ trace_probe_log_err(ctx->offset - 2, TYPECAST_BAD_ARROW);
+ return -EINVAL;
+ }
+ ctx->prefix_byteoffs = ret / 8;
+ /* Restore the original struct type (overwritten by get_bitoffset_of_field) */
+ ctx->last_struct = type;
+ }
+
+ return ret;
+}
+
+/* Find the matching closing parenthesis for a given opening parenthesis. */
+static char *find_matched_close_paren(char *s)
+{
+ char *p = s;
+ int count = 0;
+
+ while (*p) {
+ if (*p == '(')
+ count++;
+ else if (*p == ')') {
+ if (--count == 0)
+ return p;
+ }
+ p++;
+ }
+ return NULL;
+}
+
+static int handle_typecast(char *arg, struct traceprobe_parse_context *ctx)
+{
+ int orig_offset = ctx->offset;
+ char *close;
+ char *tmp;
+ char *fieldname;
+
+ if (!(tparg_is_event_probe(ctx->flags) ||
+ tparg_is_function_entry(ctx->flags) ||
+ tparg_is_function_return(ctx->flags))) {
+ trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
+ return -EOPNOTSUPP;
+ }
+
+ /*
+ * Always consider the token after typecast as a nested call
+ * For example: (STRUCT)VAR->FIELD and (STRUCT)(VAR)->FIELD are same.
+ * VAR is solved in the nested call.
+ */
tmp = strchr(arg, ')');
if (!tmp) {
trace_probe_log_err(ctx->offset + strlen(arg),
DEREF_OPEN_BRACE);
return -EINVAL;
}
- *tmp = '\0';
- ret = query_btf_struct(arg + 1, ctx);
- *tmp = ')';
+ *tmp++ = '\0';
- if (ret < 0) {
- trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
- return -EINVAL;
- }
+ ctx->offset += tmp - arg;
+ if (*tmp == '(') {
+ close = find_matched_close_paren(tmp);
- tmp++;
+ if (!close) {
+ trace_probe_log_err(ctx->offset, DEREF_OPEN_BRACE);
+ return -EINVAL;
+ }
+ /* We expect a field access for typecast */
+ if (close[1] != '-' || close[2] != '>') {
+ trace_probe_log_err(ctx->offset + close - tmp + 1,
+ TYPECAST_REQ_FIELD);
+ return -EINVAL;
+ }
+ /* Skip '(' */
+ ctx->offset += 1;
+ tmp++;
+ } else if (*tmp == '+' || *tmp == '-' ||
+ str_has_prefix(tmp, THIS_CPU_PTR_PREFIX) ||
+ str_has_prefix(tmp, THIS_CPU_READ_PREFIX)) {
+ /* Dereference can have another field access inside it. */
+ char *open = strchr(tmp + 1, '(');
+
+ if (!open) {
+ trace_probe_log_err(ctx->offset,
+ DEREF_NEED_BRACE);
+ return -EINVAL;
+ }
+ close = find_matched_close_paren(open);
+ if (!close) {
+ trace_probe_log_err(ctx->offset + strlen(tmp),
+ DEREF_OPEN_BRACE);
+ return -EINVAL;
+ }
+ close++;
+ /* We expect a field access for typecast */
+ if (close[0] != '-' || close[1] != '>') {
+ trace_probe_log_err(ctx->offset + close - tmp,
+ TYPECAST_REQ_FIELD);
+ return -EINVAL;
+ }
+ } else {
+ if (tmp[0] == '@') {
+ /* @sym+offset is not allowed without parenthesized */
+ close = strpbrk(tmp, "+-");
+ if (close && isdigit(close[1])) {
+ trace_probe_log_err(ctx->offset,
+ TYPECAST_SYM_OFFSET);
+ return -EINVAL;
+ }
+ }
+ /* Inner variable name */
+ close = strchr(tmp, '-');
+ if (!close || close[1] != '>') {
+ trace_probe_log_err(ctx->offset + strlen(tmp),
+ TYPECAST_REQ_FIELD);
+ return -EINVAL;
+ }
+ }
+ *close = '\0';
- ctx->offset += tmp - arg;
- ret = parse_btf_arg(tmp, pcode, end, ctx);
- return ret;
+ /* Let fieldname point the field name. */
+ if (close[1] == '-')
+ fieldname = close + 3; /* Skip "->" after closing parenthesis */
+ else
+ fieldname = close + 2; /* Skip ">" after inner variable name */
+
+ ctx->stack[ctx->depth].type = STATE_TYPECAST;
+ ctx->stack[ctx->depth].typecast.casttype = arg + 1;
+ ctx->stack[ctx->depth].typecast.fieldname = fieldname;
+ ctx->stack[ctx->depth].typecast.orig_offset = orig_offset;
+ ctx->stack[ctx->depth].typecast.field_offset_diff = fieldname - arg;
+ ctx->stack[ctx->depth].typecast.inner_arg = tmp;
+ return 0;
}
#else /* !CONFIG_PROBE_EVENTS_BTF_ARGS */
@@ -906,10 +1101,21 @@ static int check_prepare_btf_string_fetch(char *typename,
return 0;
}
-static int handle_typecast(char *arg, struct fetch_insn **pcode,
- struct fetch_insn *end,
+static int parse_btf_casttype(char *casttype,
+ struct traceprobe_parse_context *ctx)
+{
+ return -EOPNOTSUPP;
+}
+
+static int parse_btf_field(char *fieldname, const struct btf_type *type,
+ struct fetch_insn **pcode, struct fetch_insn *end,
struct traceprobe_parse_context *ctx)
{
+ return -EOPNOTSUPP;
+}
+
+static int handle_typecast(char *arg, struct traceprobe_parse_context *ctx)
+{
trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
return -EOPNOTSUPP;
}
@@ -1038,7 +1244,7 @@ void store_trace_entry_data(void *edata, struct trace_probe *tp, struct pt_regs
val = regs_get_kernel_argument(regs, code->param);
break;
case FETCH_OP_ST_EDATA:
- *(unsigned long *)((unsigned long)edata + code->offset) = val;
+ *(unsigned long *)((u8 *)edata + code->offset) = val;
break;
case FETCH_OP_END:
goto end;
@@ -1054,61 +1260,147 @@ NOKPROBE_SYMBOL(store_trace_entry_data)
#define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long))
-/* Parse $vars. @orig_arg points '$', which syncs to @ctx->offset */
-static int parse_probe_vars(char *orig_arg, const struct fetch_type *t,
- struct fetch_insn **pcode,
- struct fetch_insn *end,
- struct traceprobe_parse_context *ctx)
+static int parse_probe_var_retval(char *orig_arg,
+ struct fetch_insn **pcode,
+ struct fetch_insn *end,
+ struct traceprobe_parse_context *ctx)
{
struct fetch_insn *code = *pcode;
- int err = TP_ERR_BAD_VAR;
- char *arg = orig_arg + 1;
+
+ if (!(ctx->flags & TPARG_FL_RETURN)) {
+ trace_probe_log_err(ctx->offset, RETVAL_ON_PROBE);
+ return -EINVAL;
+ }
+ if (!(ctx->flags & TPARG_FL_KERNEL) ||
+ !IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS)) {
+ code->op = FETCH_OP_RETVAL;
+ return 0;
+ }
+ return parse_btf_arg(orig_arg, pcode, end, ctx);
+}
+
+static int parse_probe_var_stack(char *arg, int len, struct fetch_insn *code,
+ struct traceprobe_parse_context *ctx)
+{
unsigned long param;
- int ret = 0;
- int len;
+ int ret;
- if (ctx->flags & TPARG_FL_TEVENT) {
- if (parse_trace_event(arg, code, ctx) < 0)
- goto inval;
+ if (arg[len] == '\0') {
+ code->op = FETCH_OP_STACKP;
return 0;
}
- if (str_has_prefix(arg, "retval")) {
- if (!(ctx->flags & TPARG_FL_RETURN)) {
- err = TP_ERR_RETVAL_ON_PROBE;
- goto inval;
+ if (isdigit(arg[len])) {
+ ret = kstrtoul(arg + len, 10, &param);
+ if (ret) {
+ trace_probe_log_err(ctx->offset, BAD_VAR);
+ return ret;
}
- if (!(ctx->flags & TPARG_FL_KERNEL) ||
- !IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS)) {
- code->op = FETCH_OP_RETVAL;
- return 0;
+
+ if ((ctx->flags & TPARG_FL_KERNEL) &&
+ param > PARAM_MAX_STACK) {
+ trace_probe_log_err(ctx->offset, BAD_STACK_NUM);
+ return -EINVAL;
}
+ code->op = FETCH_OP_STACK;
+ code->param = (unsigned int)param;
+ return 0;
+ }
+
+ trace_probe_log_err(ctx->offset, BAD_VAR);
+ return -EINVAL;
+}
+
+static int parse_probe_var_current(char *orig_arg, char *arg,
+ struct fetch_insn **pcode,
+ struct fetch_insn *end,
+ struct traceprobe_parse_context *ctx)
+{
+ struct fetch_insn *code = *pcode;
+
+ /* $current is only supported by kernel probe. */
+ if (!(ctx->flags & TPARG_FL_KERNEL)) {
+ trace_probe_log_err(ctx->offset, BAD_VAR);
+ return -EINVAL;
+ }
+ arg += strlen("current");
+ if (*arg == '-' && IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS))
return parse_btf_arg(orig_arg, pcode, end, ctx);
+
+ if (*arg != '\0') {
+ trace_probe_log_err(ctx->offset, BAD_VAR);
+ return -EINVAL;
}
- len = str_has_prefix(arg, "stack");
- if (len) {
+ code->op = FETCH_OP_CURRENT;
+ return 0;
+}
- if (arg[len] == '\0') {
- code->op = FETCH_OP_STACKP;
- return 0;
- }
+#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
+static int parse_probe_var_arg(char *arg, int len, struct fetch_insn *code,
+ struct traceprobe_parse_context *ctx)
+{
+ unsigned long param;
+ int ret;
- if (isdigit(arg[len])) {
- ret = kstrtoul(arg + len, 10, &param);
- if (ret)
- goto inval;
+ ret = kstrtoul(arg + len, 10, &param);
+ if (ret) {
+ trace_probe_log_err(ctx->offset, BAD_VAR);
+ return ret;
+ }
- if ((ctx->flags & TPARG_FL_KERNEL) &&
- param > PARAM_MAX_STACK) {
- err = TP_ERR_BAD_STACK_NUM;
- goto inval;
- }
- code->op = FETCH_OP_STACK;
- code->param = (unsigned int)param;
+ if (!param || param > PARAM_MAX_STACK) {
+ trace_probe_log_err(ctx->offset, BAD_ARG_NUM);
+ return -EINVAL;
+ }
+ param--; /* argN starts from 1, but internal arg[N] starts from 0 */
+
+ if (tparg_is_function_entry(ctx->flags)) {
+ code->op = FETCH_OP_ARG;
+ code->param = (unsigned int)param;
+ /*
+ * The tracepoint probe will probe a stub function, and the
+ * first parameter of the stub is a dummy and should be ignored.
+ */
+ if (ctx->flags & TPARG_FL_TPOINT)
+ code->param++;
+ } else if (tparg_is_function_return(ctx->flags)) {
+ /* function entry argument access from return probe */
+ ret = __store_entry_arg(ctx->tp, param);
+ if (ret < 0) /* This error should be an internal error */
+ return ret;
+
+ code->op = FETCH_OP_EDATA;
+ code->offset = ret;
+ } else {
+ trace_probe_log_err(ctx->offset, NOFENTRY_ARGS);
+ return -EINVAL;
+ }
+ return 0;
+}
+#else
+static int parse_probe_var_arg(char *arg, int len, struct fetch_insn *code,
+ struct traceprobe_parse_context *ctx)
+{
+ trace_probe_log_err(ctx->offset, BAD_VAR);
+ return -EINVAL;
+}
+#endif
+
+/* Parse $vars. @orig_arg points '$', which syncs to @ctx->offset */
+static int parse_probe_vars(char *orig_arg, const struct fetch_type *t,
+ struct fetch_insn **pcode,
+ struct fetch_insn *end,
+ struct traceprobe_parse_context *ctx)
+{
+ struct fetch_insn *code = *pcode;
+ char *arg = orig_arg + 1;
+ int len, ret;
+
+ if (ctx->flags & TPARG_FL_TEVENT) {
+ ret = parse_trace_event(arg, code, ctx);
+ if (!ret)
return 0;
- }
- goto inval;
}
if (strcmp(arg, "comm") == 0 || strcmp(arg, "COMM") == 0) {
@@ -1116,46 +1408,27 @@ static int parse_probe_vars(char *orig_arg, const struct fetch_type *t,
return 0;
}
-#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
- len = str_has_prefix(arg, "arg");
- if (len) {
- ret = kstrtoul(arg + len, 10, &param);
- if (ret)
- goto inval;
+ /* eprobe only support event fields or '$comm'. */
+ if (ctx->flags & TPARG_FL_TEVENT)
+ goto inval;
- if (!param || param > PARAM_MAX_STACK) {
- err = TP_ERR_BAD_ARG_NUM;
- goto inval;
- }
- param--; /* argN starts from 1, but internal arg[N] starts from 0 */
+ if (str_has_prefix(arg, "retval"))
+ return parse_probe_var_retval(orig_arg, pcode, end, ctx);
- if (tparg_is_function_entry(ctx->flags)) {
- code->op = FETCH_OP_ARG;
- code->param = (unsigned int)param;
- /*
- * The tracepoint probe will probe a stub function, and the
- * first parameter of the stub is a dummy and should be ignored.
- */
- if (ctx->flags & TPARG_FL_TPOINT)
- code->param++;
- } else if (tparg_is_function_return(ctx->flags)) {
- /* function entry argument access from return probe */
- ret = __store_entry_arg(ctx->tp, param);
- if (ret < 0) /* This error should be an internal error */
- return ret;
+ len = str_has_prefix(arg, "stack");
+ if (len)
+ return parse_probe_var_stack(arg, len, code, ctx);
- code->op = FETCH_OP_EDATA;
- code->offset = ret;
- } else {
- err = TP_ERR_NOFENTRY_ARGS;
- goto inval;
- }
- return 0;
- }
-#endif
+ /* $current returns the address of the current task_struct. */
+ if (str_has_prefix(arg, "current"))
+ return parse_probe_var_current(orig_arg, arg, pcode, end, ctx);
+
+ len = str_has_prefix(arg, "arg");
+ if (len)
+ return parse_probe_var_arg(arg, len, code, ctx);
inval:
- __trace_probe_log_err(ctx->offset, err);
+ trace_probe_log_err(ctx->offset, BAD_VAR);
return -EINVAL;
}
@@ -1184,179 +1457,353 @@ static int __parse_imm_string(char *str, char **pbuf, int offs)
return 0;
}
-/* Recursive argument parser */
-static int
-parse_probe_arg(char *arg, const struct fetch_type *type,
- struct fetch_insn **pcode, struct fetch_insn *end,
- struct traceprobe_parse_context *ctx)
+static int parse_probe_arg_register(char *arg, struct fetch_insn *code,
+ struct traceprobe_parse_context *ctx)
+{
+ int ret;
+
+ if (ctx->flags & (TPARG_FL_TEVENT | TPARG_FL_FPROBE)) {
+ /* eprobe and fprobe do not handle registers */
+ trace_probe_log_err(ctx->offset, BAD_VAR);
+ return -EINVAL;
+ }
+ ret = regs_query_register_offset(arg + 1);
+ if (ret >= 0) {
+ code->op = FETCH_OP_REG;
+ code->param = (unsigned int)ret;
+ return 0;
+ }
+ trace_probe_log_err(ctx->offset, BAD_REG_NAME);
+ return -EINVAL;
+}
+
+static int parse_probe_arg_mem_symbol(char *arg, struct fetch_insn **pcode,
+ struct fetch_insn *end,
+ struct traceprobe_parse_context *ctx)
{
struct fetch_insn *code = *pcode;
unsigned long param;
+ long offset = 0;
+ int ret;
+
+ if (isdigit(arg[1])) {
+ ret = kstrtoul(arg + 1, 0, &param);
+ if (ret) {
+ trace_probe_log_err(ctx->offset, BAD_MEM_ADDR);
+ return ret;
+ }
+ /* load address */
+ code->op = FETCH_OP_IMM;
+ code->immediate = param;
+ } else if (arg[1] == '+') {
+ /* Kernel probes do not support file offsets */
+ if (ctx->flags & TPARG_FL_KERNEL) {
+ trace_probe_log_err(ctx->offset, FILE_ON_KPROBE);
+ return -EINVAL;
+ }
+ ret = kstrtol(arg + 2, 0, &offset);
+ if (ret) {
+ trace_probe_log_err(ctx->offset, BAD_FILE_OFFS);
+ return ret;
+ }
+
+ code->op = FETCH_OP_FOFFS;
+ code->immediate = (unsigned long)offset;
+ offset = 0;
+ } else {
+ /* uprobes don't support symbols */
+ if (!(ctx->flags & TPARG_FL_KERNEL)) {
+ trace_probe_log_err(ctx->offset, SYM_ON_UPROBE);
+ return -EINVAL;
+ }
+ /* Preserve symbol for updating */
+ code->op = FETCH_NOP_SYMBOL;
+ code->data = kstrdup(arg + 1, GFP_KERNEL);
+ if (!code->data)
+ return -ENOMEM;
+ if (++code == end) {
+ trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
+ return -EINVAL;
+ }
+ code->op = FETCH_OP_IMM;
+ code->immediate = 0;
+ }
+ /* These are fetching from memory */
+ if (++code == end) {
+ trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
+ return -EINVAL;
+ }
+ *pcode = code;
+ code->op = FETCH_OP_DEREF;
+ code->offset = offset;
+ return 0;
+}
+
+static int parse_probe_arg_deref(char *arg, struct traceprobe_parse_context *ctx)
+{
int deref = FETCH_OP_DEREF;
long offset = 0;
char *tmp;
- int ret = 0;
+ int ret;
- switch (arg[0]) {
- case '$':
- ret = parse_probe_vars(arg, type, pcode, end, ctx);
- break;
+ if (arg[1] == 'u') {
+ deref = FETCH_OP_UDEREF;
+ arg[1] = arg[0];
+ arg++;
+ }
+ if (arg[0] == '+')
+ arg++; /* Skip '+', because kstrtol() rejects it. */
+ tmp = strchr(arg, '(');
+ if (!tmp) {
+ trace_probe_log_err(ctx->offset, DEREF_NEED_BRACE);
+ return -EINVAL;
+ }
+ *tmp = '\0';
+ ret = kstrtol(arg, 0, &offset);
+ if (ret) {
+ trace_probe_log_err(ctx->offset, BAD_DEREF_OFFS);
+ return ret;
+ }
+ ctx->offset += (tmp + 1 - arg) + (arg[0] != '-' ? 1 : 0);
+ arg = tmp + 1;
- case '%': /* named register */
- if (ctx->flags & (TPARG_FL_TEVENT | TPARG_FL_FPROBE)) {
- /* eprobe and fprobe do not handle registers */
- trace_probe_log_err(ctx->offset, BAD_VAR);
- break;
+ tmp = strrchr(arg, ')');
+ if (!tmp) {
+ trace_probe_log_err(ctx->offset + strlen(arg),
+ DEREF_OPEN_BRACE);
+ return -EINVAL;
+ }
+ *tmp = '\0';
+
+ ctx->stack[ctx->depth].type = STATE_DEREF;
+ ctx->stack[ctx->depth].deref.deref = deref;
+ ctx->stack[ctx->depth].deref.offset = offset;
+ ctx->stack[ctx->depth].deref.cur_offs = ctx->offset;
+ ctx->stack[ctx->depth].deref.inner_arg = arg;
+ ctx->stack[ctx->depth].deref.is_cpu_read = false;
+ return 0;
+}
+
+static int parse_probe_arg_imm(char *arg, struct fetch_insn *code,
+ struct traceprobe_parse_context *ctx)
+{
+ char *tmp;
+ int ret;
+
+ if (arg[1] == '"') { /* Immediate string */
+ ret = __parse_imm_string(arg + 2, &tmp, ctx->offset + 2);
+ if (ret)
+ return ret;
+ code->op = FETCH_OP_IMMSTR;
+ code->data = tmp;
+ } else {
+ ret = str_to_immediate(arg + 1, &code->immediate);
+ if (ret) {
+ trace_probe_log_err(ctx->offset + 1, BAD_IMM);
+ return ret;
}
- ret = regs_query_register_offset(arg + 1);
- if (ret >= 0) {
- code->op = FETCH_OP_REG;
- code->param = (unsigned int)ret;
- ret = 0;
- } else
- trace_probe_log_err(ctx->offset, BAD_REG_NAME);
- break;
+ code->op = FETCH_OP_IMM;
+ }
+ return 0;
+}
- case '@': /* memory, file-offset or symbol */
- if (isdigit(arg[1])) {
- ret = kstrtoul(arg + 1, 0, &param);
- if (ret) {
- trace_probe_log_err(ctx->offset, BAD_MEM_ADDR);
- break;
- }
- /* load address */
- code->op = FETCH_OP_IMM;
- code->immediate = param;
- } else if (arg[1] == '+') {
- /* kprobes don't support file offsets */
- if (ctx->flags & TPARG_FL_KERNEL) {
- trace_probe_log_err(ctx->offset, FILE_ON_KPROBE);
- return -EINVAL;
- }
- ret = kstrtol(arg + 2, 0, &offset);
- if (ret) {
- trace_probe_log_err(ctx->offset, BAD_FILE_OFFS);
- break;
- }
+static int parse_probe_arg_default(char *arg, struct fetch_insn **pcode,
+ struct fetch_insn *end,
+ struct traceprobe_parse_context *ctx)
+{
+ int ret;
- code->op = FETCH_OP_FOFFS;
- code->immediate = (unsigned long)offset; // imm64?
- } else {
- /* uprobes don't support symbols */
- if (!(ctx->flags & TPARG_FL_KERNEL)) {
- trace_probe_log_err(ctx->offset, SYM_ON_UPROBE);
+ if (isalpha(arg[0]) || arg[0] == '_') {
+ /* BTF variable or event field */
+ if (ctx->flags & TPARG_FL_TEVENT) {
+ ret = parse_trace_event(arg, *pcode, ctx);
+ if (ret < 0) {
+ trace_probe_log_err(ctx->offset, NO_EVENT_FIELD);
return -EINVAL;
}
- /* Preserve symbol for updating */
- code->op = FETCH_NOP_SYMBOL;
- code->data = kstrdup(arg + 1, GFP_KERNEL);
- if (!code->data)
- return -ENOMEM;
- if (++code == end) {
- trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
- return -EINVAL;
- }
- code->op = FETCH_OP_IMM;
- code->immediate = 0;
+ return 0;
}
- /* These are fetching from memory */
- if (++code == end) {
- trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
+ if (!tparg_is_function_entry(ctx->flags) &&
+ !tparg_is_function_return(ctx->flags)) {
+ trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
return -EINVAL;
}
- *pcode = code;
- code->op = FETCH_OP_DEREF;
- code->offset = offset;
- break;
+ return parse_btf_arg(arg, pcode, end, ctx);
+ }
- case '+': /* deref memory */
- case '-':
- if (arg[1] == 'u') {
- deref = FETCH_OP_UDEREF;
- arg[1] = arg[0];
- arg++;
- }
- if (arg[0] == '+')
- arg++; /* Skip '+', because kstrtol() rejects it. */
- tmp = strchr(arg, '(');
- if (!tmp) {
- trace_probe_log_err(ctx->offset, DEREF_NEED_BRACE);
- return -EINVAL;
- }
- *tmp = '\0';
- ret = kstrtol(arg, 0, &offset);
- if (ret) {
- trace_probe_log_err(ctx->offset, BAD_DEREF_OFFS);
+ return 0;
+}
+
+static int parse_probe_arg_nested(char **parg, struct traceprobe_parse_context *ctx)
+{
+ char *arg = *parg;
+ int ret;
+
+ while (true) {
+ /* Determine if this is a nested argument */
+ if (arg[0] != '+' && arg[0] != '-' && arg[0] != '(' &&
+ !str_has_prefix(arg, THIS_CPU_PTR_PREFIX) &&
+ !str_has_prefix(arg, THIS_CPU_READ_PREFIX))
break;
+
+ /* If nested, check the maximum depth limit */
+ if (ctx->depth >= TRACEPROBE_MAX_NESTED_LEVEL) {
+ trace_probe_log_err(ctx->offset, TOO_MANY_NESTED);
+ return -E2BIG;
}
- ctx->offset += (tmp + 1 - arg) + (arg[0] != '-' ? 1 : 0);
- arg = tmp + 1;
- tmp = strrchr(arg, ')');
- if (!tmp) {
- trace_probe_log_err(ctx->offset + strlen(arg),
- DEREF_OPEN_BRACE);
- return -EINVAL;
- } else {
- const struct fetch_type *t2 = find_fetch_type(NULL, ctx->flags);
- int cur_offs = ctx->offset;
- *tmp = '\0';
- ret = parse_probe_arg(arg, t2, &code, end, ctx);
+ /* Perform the actual parsing subroutine calls */
+ switch (arg[0]) {
+ case '+':
+ case '-':
+ ret = parse_probe_arg_deref(arg, ctx);
if (ret)
- break;
- ctx->offset = cur_offs;
- if (code->op == FETCH_OP_COMM ||
- code->op == FETCH_OP_DATA) {
- trace_probe_log_err(ctx->offset, COMM_CANT_DEREF);
- return -EINVAL;
- }
- if (++code == end) {
- trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
- return -EINVAL;
- }
- *pcode = code;
-
- code->op = deref;
- code->offset = offset;
- /* Reset the last type if used */
- ctx->last_type = NULL;
- }
- break;
- case '\\': /* Immediate value */
- if (arg[1] == '"') { /* Immediate string */
- ret = __parse_imm_string(arg + 2, &tmp, ctx->offset + 2);
+ return ret;
+ arg = ctx->stack[ctx->depth].deref.inner_arg;
+ break;
+ case '(':
+ ret = handle_typecast(arg, ctx);
if (ret)
- break;
- code->op = FETCH_OP_DATA;
- code->data = tmp;
- } else {
- ret = str_to_immediate(arg + 1, &code->immediate);
+ return ret;
+ arg = ctx->stack[ctx->depth].typecast.inner_arg;
+ break;
+ default:
+ ret = parse_this_cpu(arg, ctx);
if (ret)
- trace_probe_log_err(ctx->offset + 1, BAD_IMM);
- else
- code->op = FETCH_OP_IMM;
+ return ret;
+ arg = ctx->stack[ctx->depth].deref.inner_arg;
+ break;
}
+ ctx->depth++;
+ }
+
+ *parg = arg;
+ return 0;
+}
+
+static int parse_probe_arg_leaf(char *arg, const struct fetch_type *type,
+ struct fetch_insn **pcode, struct fetch_insn *end,
+ struct traceprobe_parse_context *ctx)
+{
+ struct fetch_insn *code = *pcode;
+ int ret;
+
+ switch (arg[0]) {
+ case '$':
+ ret = parse_probe_vars(arg, type, pcode, end, ctx);
break;
- case '(':
- ret = handle_typecast(arg, pcode, end, ctx);
+ case '%': /* named register */
+ ret = parse_probe_arg_register(arg, code, ctx);
+ break;
+ case '@': /* memory, file-offset or symbol */
+ ret = parse_probe_arg_mem_symbol(arg, pcode, end, ctx);
+ break;
+ case '\\': /* Immediate value */
+ ret = parse_probe_arg_imm(arg, code, ctx);
break;
default:
- if (isalpha(arg[0]) || arg[0] == '_') { /* BTF variable */
- if (!tparg_is_function_entry(ctx->flags) &&
- !tparg_is_function_return(ctx->flags)) {
- trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
- return -EINVAL;
- }
- ret = parse_btf_arg(arg, pcode, end, ctx);
- break;
- }
+ ret = parse_probe_arg_default(arg, pcode, end, ctx);
+ break;
}
- if (!ret && code->op == FETCH_OP_NOP) {
+
+ if (ret)
+ return ret;
+
+ if (code->op == FETCH_OP_NOP) {
/* Parsed, but do not find fetch method */
trace_probe_log_err(ctx->offset, BAD_FETCH_ARG);
- ret = -EINVAL;
+ return -EINVAL;
}
- return ret;
+
+ return 0;
+}
+
+static int unwind_parse_states(struct fetch_insn **pcode, struct fetch_insn *end,
+ struct traceprobe_parse_context *ctx)
+{
+ struct parse_state *state;
+ struct fetch_insn *code;
+ int ret;
+
+ while (ctx->depth > 0) {
+ ctx->depth--;
+ state = &ctx->stack[ctx->depth];
+
+ if (state->type == STATE_DEREF) {
+ code = *pcode;
+ ctx->offset = state->deref.cur_offs;
+ if (code->op == FETCH_OP_COMM || code->op == FETCH_OP_IMMSTR) {
+ trace_probe_log_err(ctx->offset, COMM_CANT_DEREF);
+ return -EINVAL;
+ }
+
+ if (!(state->deref.deref == FETCH_OP_CPU_PTR &&
+ *state->deref.inner_arg == '@')) {
+ code++;
+ if (code == end) {
+ trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
+ return -EINVAL;
+ }
+ }
+ *pcode = code;
+
+ code->op = state->deref.deref;
+ code->offset = state->deref.offset;
+ ctx->last_type = NULL;
+
+ if (state->deref.is_cpu_read) {
+ code = *pcode;
+ code++;
+ if (code == end) {
+ trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
+ return -EINVAL;
+ }
+ code->op = FETCH_OP_DEREF;
+ code->offset = 0;
+ *pcode = code;
+ }
+ } else if (state->type == STATE_TYPECAST) {
+ clear_struct_btf(ctx);
+
+ /* resolve the typecast struct name */
+ ctx->offset = state->typecast.orig_offset + 1; /* for the '(' */
+ ret = parse_btf_casttype(state->typecast.casttype, ctx);
+ if (ret < 0)
+ return ret;
+
+ ctx->offset = state->typecast.orig_offset +
+ state->typecast.field_offset_diff;
+ ret = parse_btf_field(state->typecast.fieldname,
+ ctx->last_struct, pcode,
+ end, ctx);
+ ctx->prefix_byteoffs = 0;
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/* Loop-based (non-recursive) argument parser */
+static int
+parse_probe_arg(char *arg, const struct fetch_type *type,
+ struct fetch_insn **pcode, struct fetch_insn *end,
+ struct traceprobe_parse_context *ctx)
+{
+ int ret;
+
+ ctx->depth = 0;
+
+ ret = parse_probe_arg_nested(&arg, ctx);
+ if (ret)
+ return ret;
+
+ ret = parse_probe_arg_leaf(arg, type, pcode, end, ctx);
+ if (ret)
+ return ret;
+
+ return unwind_parse_states(pcode, end, ctx);
}
/* Bitfield type needs to be parsed into a fetch function */
@@ -1482,7 +1929,7 @@ static int finalize_fetch_insn(struct fetch_insn *code,
} else {
if (code->op != FETCH_OP_DEREF && code->op != FETCH_OP_UDEREF &&
code->op != FETCH_OP_IMM && code->op != FETCH_OP_COMM &&
- code->op != FETCH_OP_DATA && code->op != FETCH_OP_TP_ARG) {
+ code->op != FETCH_OP_IMMSTR && code->op != FETCH_OP_TP_ARG) {
trace_probe_log_err(ctx->offset + type_offset,
BAD_STRING);
return -EINVAL;
@@ -1491,7 +1938,7 @@ static int finalize_fetch_insn(struct fetch_insn *code,
if (!strcmp(parg->type->name, "symstr") ||
(code->op == FETCH_OP_IMM || code->op == FETCH_OP_COMM ||
- code->op == FETCH_OP_DATA) || code->op == FETCH_OP_TP_ARG ||
+ code->op == FETCH_OP_IMMSTR) || code->op == FETCH_OP_TP_ARG ||
parg->count) {
/*
* IMM, DATA and COMM is pointing actual address, those
@@ -1617,7 +2064,6 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
ctx);
if (ret < 0)
goto fail;
-
/* Update storing type if BTF is available */
if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) &&
ctx->last_type) {
@@ -1667,7 +2113,7 @@ fail:
if (ret < 0) {
for (code = tmp; code < tmp + FETCH_INSN_MAX; code++)
if (code->op == FETCH_NOP_SYMBOL ||
- code->op == FETCH_OP_DATA)
+ code->op == FETCH_OP_IMMSTR)
kfree(code->data);
}
kfree(tmp);
@@ -1766,7 +2212,7 @@ void traceprobe_free_probe_arg(struct probe_arg *arg)
while (code && code->op != FETCH_OP_END) {
if (code->op == FETCH_NOP_SYMBOL ||
- code->op == FETCH_OP_DATA)
+ code->op == FETCH_OP_IMMSTR)
kfree(code->data);
code++;
}
@@ -1898,7 +2344,11 @@ const char **traceprobe_expand_meta_args(int argc, const char *argv[],
trace_probe_log_err(0, BAD_VAR);
return ERR_PTR(-ENOENT);
}
- /* Note: $argN starts from $arg1 */
+ /* Note: $argN starts from $arg1, so $arg0 is invalid. */
+ if (n == 0) {
+ trace_probe_log_err(0, BAD_ARG_NUM);
+ return ERR_PTR(-EINVAL);
+ }
ret = sprint_nth_btf_arg(n - 1, type, buf + used,
bufsize - used, ctx);
if (ret < 0)
@@ -2010,7 +2460,7 @@ int traceprobe_update_arg(struct probe_arg *arg)
}
/* When len=0, we just calculate the needed length */
-#define LEN_OR_ZERO (len ? len - pos : 0)
+#define LEN_OR_ZERO (len > pos ? len - pos : 0)
static int __set_print_fmt(struct trace_probe *tp, char *buf, int len,
enum probe_print_type ptype)
{
@@ -2103,19 +2553,60 @@ int traceprobe_set_print_fmt(struct trace_probe *tp, enum probe_print_type ptype
int traceprobe_define_arg_fields(struct trace_event_call *event_call,
size_t offset, struct trace_probe *tp)
{
+ struct trace_probe_event *tpe = trace_probe_event_from_call(event_call);
int ret, i;
+ /*
+ * A field created by trace_define_field() only stores the name and
+ * type pointers, it does not copy the strings. Here they point into
+ * the probe_arg of @tp, which is freed when @tp is removed. For an
+ * event with multiple probes attached, the field list is defined
+ * once by the first probe but kept alive by the surviving siblings,
+ * so removing that first probe would leave the fields referencing
+ * freed memory. Duplicate the strings and anchor the copies on the
+ * trace_probe_event, which lives as long as the field list itself.
+ *
+ * event_define_fields() ignores the return value of this hook, so
+ * if a previous attempt failed before creating any field, it may
+ * call here again. Release duplicates left behind by such an
+ * attempt before starting over.
+ */
+ for (i = 0; i < tpe->nr_field_strings; i++)
+ kfree(tpe->field_strings[i]);
+ kfree(tpe->field_strings);
+ tpe->field_strings = NULL;
+ tpe->nr_field_strings = 0;
+
+ if (tp->nr_args) {
+ tpe->field_strings = kcalloc(tp->nr_args * 2, sizeof(char *),
+ GFP_KERNEL);
+ if (!tpe->field_strings)
+ return -ENOMEM;
+ }
+
/* Set argument names as fields */
for (i = 0; i < tp->nr_args; i++) {
struct probe_arg *parg = &tp->args[i];
const char *fmt = parg->type->fmttype;
int size = parg->type->size;
+ char *name, *type;
if (parg->fmt)
fmt = parg->fmt;
if (parg->count)
size *= parg->count;
- ret = trace_define_field(event_call, fmt, parg->name,
+
+ name = kstrdup(parg->name, GFP_KERNEL);
+ type = kstrdup(fmt, GFP_KERNEL);
+ if (!name || !type) {
+ kfree(name);
+ kfree(type);
+ return -ENOMEM;
+ }
+ tpe->field_strings[tpe->nr_field_strings++] = name;
+ tpe->field_strings[tpe->nr_field_strings++] = type;
+
+ ret = trace_define_field(event_call, type, name,
offset + parg->offset, size,
parg->type->is_signed,
FILTER_OTHER);
@@ -2127,6 +2618,11 @@ int traceprobe_define_arg_fields(struct trace_event_call *event_call,
static void trace_probe_event_free(struct trace_probe_event *tpe)
{
+ int i;
+
+ for (i = 0; i < tpe->nr_field_strings; i++)
+ kfree(tpe->field_strings[i]);
+ kfree(tpe->field_strings);
kfree(tpe->class.system);
kfree(tpe->call.name);
kfree(tpe->call.print_fmt);
@@ -2322,10 +2818,9 @@ int trace_probe_compare_arg_type(struct trace_probe *a, struct trace_probe *b)
return b->nr_args + 1;
for (i = 0; i < a->nr_args; i++) {
- if ((b->nr_args <= i) ||
- ((a->args[i].type != b->args[i].type) ||
- (a->args[i].count != b->args[i].count) ||
- strcmp(a->args[i].name, b->args[i].name)))
+ if ((a->args[i].type != b->args[i].type) ||
+ (a->args[i].count != b->args[i].count) ||
+ strcmp(a->args[i].name, b->args[i].name))
return i + 1;
}
@@ -2335,16 +2830,17 @@ int trace_probe_compare_arg_type(struct trace_probe *a, struct trace_probe *b)
bool trace_probe_match_command_args(struct trace_probe *tp,
int argc, const char **argv)
{
- char buf[MAX_ARGSTR_LEN + 1];
int i;
if (tp->nr_args < argc)
return false;
for (i = 0; i < argc; i++) {
- snprintf(buf, sizeof(buf), "%s=%s",
- tp->args[i].name, tp->args[i].comm);
- if (strcmp(buf, argv[i]))
+ int len = strlen(tp->args[i].name);
+
+ if (strncmp(argv[i], tp->args[i].name, len) ||
+ argv[i][len] != '=' ||
+ strcmp(argv[i] + len + 1, tp->args[i].comm))
return false;
}
return true;
@@ -2393,3 +2889,99 @@ int trace_probe_print_args(struct trace_seq *s, struct probe_arg *args, int nr_a
}
return 0;
}
+
+#ifdef CONFIG_PROBE_EVENTS_DUMP_FETCHARG
+
+struct fetch_op_decode {
+ const char *name;
+ void (*decode)(struct seq_file *m, struct fetch_insn *insn);
+};
+
+static const struct fetch_op_decode fetch_op_decode[];
+
+static void fetcharg_decode_none(struct seq_file *m, struct fetch_insn *insn)
+{
+ seq_puts(m, fetch_op_decode[insn->op].name);
+}
+
+static void fetcharg_decode_param(struct seq_file *m, struct fetch_insn *insn)
+{
+ seq_printf(m, "%s(%u)", fetch_op_decode[insn->op].name, insn->param);
+}
+
+static void fetcharg_decode_imm(struct seq_file *m, struct fetch_insn *insn)
+{
+ seq_printf(m, "%s(0x%lx)", fetch_op_decode[insn->op].name, insn->immediate);
+}
+
+static void fetcharg_decode_string(struct seq_file *m, struct fetch_insn *insn)
+{
+ seq_printf(m, "%s(%s)", fetch_op_decode[insn->op].name, (char *)insn->data);
+}
+
+static void fetcharg_decode_symbol(struct seq_file *m, struct fetch_insn *insn)
+{
+ seq_printf(m, "%s(%s)", fetch_op_decode[insn->op].name, (char *)insn->data);
+}
+
+static void fetcharg_decode_offset(struct seq_file *m, struct fetch_insn *insn)
+{
+ seq_printf(m, "%s(offset=%d)", fetch_op_decode[insn->op].name, insn->offset);
+}
+
+static void fetcharg_decode_store(struct seq_file *m, struct fetch_insn *insn)
+{
+ if (insn->op == FETCH_OP_ST_RAW)
+ seq_printf(m, "%s(size=%u)", fetch_op_decode[insn->op].name, insn->size);
+ else
+ seq_printf(m, "%s(offset=%d,size=%u)", fetch_op_decode[insn->op].name,
+ insn->offset, insn->size);
+}
+
+static void fetcharg_decode_bf(struct seq_file *m, struct fetch_insn *insn)
+{
+ seq_printf(m, "%s(basesize=%u,lshift=%u,rshift=%u)",
+ fetch_op_decode[insn->op].name, insn->basesize, insn->lshift, insn->rshift);
+}
+
+static void fetcharg_decode_tp_arg(struct seq_file *m, struct fetch_insn *insn)
+{
+ struct ftrace_event_field *field = insn->data;
+
+ seq_printf(m, "%s(%s)", fetch_op_decode[insn->op].name, field->name);
+}
+
+#define FETCH_OP(opname, decode_fn) \
+ [FETCH_OP_##opname] = { .name = #opname, .decode = fetcharg_decode_##decode_fn }
+
+static const struct fetch_op_decode fetch_op_decode[] = FETCH_OP_LIST;
+#undef FETCH_OP
+
+static void trace_probe_dump_arg(struct seq_file *m, struct probe_arg *parg)
+{
+ int i;
+
+ seq_printf(m, "# %s: ", parg->name);
+ for (i = 0; i < FETCH_INSN_MAX; i++) {
+ struct fetch_insn *insn = parg->code + i;
+
+ if (insn->op >= ARRAY_SIZE(fetch_op_decode) || !fetch_op_decode[insn->op].decode)
+ seq_printf(m, "unknown(%d)", insn->op);
+ else
+ fetch_op_decode[insn->op].decode(m, insn);
+
+ if (insn->op == FETCH_OP_END)
+ break;
+ seq_puts(m, " -> ");
+ }
+ seq_putc(m, '\n');
+}
+
+void trace_probe_dump_args(struct seq_file *m, struct trace_probe *tp)
+{
+ int i;
+
+ for (i = 0; i < tp->nr_args; i++)
+ trace_probe_dump_arg(m, &tp->args[i]);
+}
+#endif /* CONFIG_PROBE_EVENTS_DUMP_FETCHARG */
diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
index 15758cc11fc6..d1fb3520700f 100644
--- a/kernel/trace/trace_probe.h
+++ b/kernel/trace/trace_probe.h
@@ -32,7 +32,7 @@
#include "trace_output.h"
#define MAX_TRACE_ARGS 128
-#define MAX_ARGSTR_LEN 63
+#define MAX_ARGSTR_LEN 255
#define MAX_ARRAY_LEN 64
#define MAX_ARG_NAME_LEN 32
#define MAX_BTF_ARGS_LEN 128
@@ -83,38 +83,48 @@ static nokprobe_inline u32 update_data_loc(u32 loc, int consumed)
/* Printing function type */
typedef int (*print_type_func_t)(struct trace_seq *, void *, void *);
-enum fetch_op {
- FETCH_OP_NOP = 0,
- // Stage 1 (load) ops
- FETCH_OP_REG, /* Register : .param = offset */
- FETCH_OP_STACK, /* Stack : .param = index */
- FETCH_OP_STACKP, /* Stack pointer */
- FETCH_OP_RETVAL, /* Return value */
- FETCH_OP_IMM, /* Immediate : .immediate */
- FETCH_OP_COMM, /* Current comm */
- FETCH_OP_ARG, /* Function argument : .param */
- FETCH_OP_FOFFS, /* File offset: .immediate */
- FETCH_OP_DATA, /* Allocated data: .data */
- FETCH_OP_EDATA, /* Entry data: .offset */
- // Stage 2 (dereference) op
- FETCH_OP_DEREF, /* Dereference: .offset */
- FETCH_OP_UDEREF, /* User-space Dereference: .offset */
- // Stage 3 (store) ops
- FETCH_OP_ST_RAW, /* Raw: .size */
- FETCH_OP_ST_MEM, /* Mem: .offset, .size */
- FETCH_OP_ST_UMEM, /* Mem: .offset, .size */
- FETCH_OP_ST_STRING, /* String: .offset, .size */
- FETCH_OP_ST_USTRING, /* User String: .offset, .size */
- FETCH_OP_ST_SYMSTR, /* Kernel Symbol String: .offset, .size */
- FETCH_OP_ST_EDATA, /* Store Entry Data: .offset */
- // Stage 4 (modify) op
- FETCH_OP_MOD_BF, /* Bitfield: .basesize, .lshift, .rshift */
- // Stage 5 (loop) op
- FETCH_OP_LP_ARRAY, /* Array: .param = loop count */
- FETCH_OP_TP_ARG, /* Trace Point argument */
- FETCH_OP_END,
- FETCH_NOP_SYMBOL, /* Unresolved Symbol holder */
-};
+#define FETCH_OP_LIST { \
+ /* Stage 1 (load) ops */ \
+ FETCH_OP(NOP, none), /* NOP */ \
+ FETCH_OP(REG, param), /* Register: .param = offset */ \
+ FETCH_OP(STACK, param), /* Stack: .param = index */ \
+ FETCH_OP(STACKP, none), /* Stack pointer */ \
+ FETCH_OP(RETVAL, none), /* Return value */ \
+ FETCH_OP(IMM, imm), /* Immediate: .immediate */ \
+ FETCH_OP(COMM, none), /* Current comm */ \
+ FETCH_OP(CURRENT, none), /* Current task_struct address */\
+ FETCH_OP(ARG, param), /* Argument: .param = index */ \
+ FETCH_OP(FOFFS, imm), /* File offset: .immediate */ \
+ FETCH_OP(IMMSTR, string), /* Allocated string: .data */ \
+ FETCH_OP(EDATA, offset), /* Entry data: .offset */ \
+ FETCH_OP(TP_ARG, tp_arg), /* Tracepoint argument: .data */\
+ /* Stage 2 (dereference) ops */ \
+ FETCH_OP(DEREF, offset), /* Dereference: .offset */ \
+ FETCH_OP(UDEREF, offset), /* User-space dereference: .offset */\
+ FETCH_OP(CPU_PTR, none), /* Per-CPU pointer: .offset */ \
+ /* Stage 3 (store) ops */ \
+ FETCH_OP(ST_RAW, store), /* Raw value: .size */ \
+ FETCH_OP(ST_MEM, store), /* Memory: .offset, .size */ \
+ FETCH_OP(ST_UMEM, store), /* User memory: .offset, .size */\
+ FETCH_OP(ST_STRING, store), /* String: .offset, .size */ \
+ FETCH_OP(ST_USTRING, store), /* User string: .offset, .size */\
+ FETCH_OP(ST_SYMSTR, store), /* Symbol name: .offset, .size */\
+ FETCH_OP(ST_EDATA, offset), /* Entry data: .offset */ \
+ /* Stage 4 (modify) op */ \
+ FETCH_OP(MOD_BF, bf), /* Bitfield: .basesize, .lshift, .rshift*/\
+ /* Stage 5 (loop) op */ \
+ FETCH_OP(LP_ARRAY, param), /* Loop array: .param = count */\
+ /* End */ \
+ FETCH_OP(END, none), \
+ /* Unresolved Symbol holder */ \
+ FETCH_OP(NOP_SYMBOL, symbol), /* Non loaded symbol: .data = symbol name */\
+}
+
+#define FETCH_OP(opname, decode_fn) FETCH_OP_##opname
+enum fetch_op FETCH_OP_LIST;
+#undef FETCH_OP
+
+#define FETCH_NOP_SYMBOL FETCH_OP_NOP_SYMBOL
struct fetch_insn {
enum fetch_op op;
@@ -209,7 +219,6 @@ DECLARE_BASIC_PRINT_TYPE_FUNC(symbol);
_ASSIGN_FETCH_TYPE(#ptype, ptype, ftype, sizeof(ftype), sign, atype)
#define ASSIGN_FETCH_TYPE_END {}
-#define MAX_ARRAY_LEN 64
#ifdef CONFIG_KPROBE_EVENTS
bool trace_kprobe_on_func_entry(struct trace_event_call *call);
@@ -255,6 +264,8 @@ struct trace_probe_event {
struct trace_event_call call;
struct list_head files;
struct list_head probes;
+ char **field_strings;
+ int nr_field_strings;
struct trace_uprobe_filter filter[];
};
@@ -350,7 +361,7 @@ static inline int trace_probe_unregister_event_call(struct trace_probe *tp)
static inline bool trace_probe_has_single_file(struct trace_probe *tp)
{
- return !!list_is_singular(&tp->event->files);
+ return list_is_singular(&tp->event->files);
}
int trace_probe_init(struct trace_probe *tp, const char *event,
@@ -370,6 +381,13 @@ bool trace_probe_match_command_args(struct trace_probe *tp,
int trace_probe_create(const char *raw_command, int (*createfn)(int, const char **));
int trace_probe_print_args(struct trace_seq *s, struct probe_arg *args, int nr_args,
u8 *data, void *field);
+#ifdef CONFIG_PROBE_EVENTS_DUMP_FETCHARG
+void trace_probe_dump_args(struct seq_file *m, struct trace_probe *tp);
+#else
+static inline void trace_probe_dump_args(struct seq_file *m, struct trace_probe *tp)
+{
+}
+#endif
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
int traceprobe_get_entry_data_size(struct trace_probe *tp);
@@ -414,6 +432,39 @@ static inline bool tparg_is_function_return(unsigned int flags)
return (flags & TPARG_FL_LOC_MASK) == (TPARG_FL_KERNEL | TPARG_FL_RETURN);
}
+static inline bool tparg_is_event_probe(unsigned int flags)
+{
+ return !!(flags & TPARG_FL_TEVENT);
+}
+
+/* Each typecast consumes nested level. So the max number of typecast is 8. */
+#define TRACEPROBE_MAX_NESTED_LEVEL 8
+
+enum parse_state_type {
+ STATE_DEREF,
+ STATE_TYPECAST,
+};
+
+struct parse_state {
+ int type;
+ union {
+ struct {
+ int deref;
+ long offset;
+ int cur_offs;
+ char *inner_arg;
+ bool is_cpu_read;
+ } deref;
+ struct {
+ char *casttype;
+ char *fieldname;
+ int orig_offset;
+ int field_offset_diff;
+ char *inner_arg;
+ } typecast;
+ };
+};
+
struct traceprobe_parse_context {
struct trace_event_call *event;
/* BTF related parameters */
@@ -430,8 +481,12 @@ struct traceprobe_parse_context {
struct trace_probe *tp;
unsigned int flags;
int offset;
+ int prefix_byteoffs; /* The byte offset of the prefix field of typecast */
+ struct parse_state stack[TRACEPROBE_MAX_NESTED_LEVEL + 1];
+ int depth;
};
+
extern int traceprobe_parse_probe_arg(struct trace_probe *tp, int i,
const char *argv,
struct traceprobe_parse_context *ctx);
@@ -485,88 +540,95 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
#undef ERRORS
#define ERRORS \
- C(FILE_NOT_FOUND, "Failed to find the given file"), \
- C(NO_REGULAR_FILE, "Not a regular file"), \
- C(BAD_REFCNT, "Invalid reference counter offset"), \
- C(REFCNT_OPEN_BRACE, "Reference counter brace is not closed"), \
- C(BAD_REFCNT_SUFFIX, "Reference counter has wrong suffix"), \
- C(BAD_UPROBE_OFFS, "Invalid uprobe offset"), \
- C(BAD_MAXACT_TYPE, "Maxactive is only for function exit"), \
- C(BAD_MAXACT, "Invalid maxactive number"), \
- C(MAXACT_TOO_BIG, "Maxactive is too big"), \
- C(BAD_PROBE_ADDR, "Invalid probed address or symbol"), \
- C(NON_UNIQ_SYMBOL, "The symbol is not unique"), \
- C(BAD_RETPROBE, "Retprobe address must be an function entry"), \
- C(NO_TRACEPOINT, "Tracepoint is not found"), \
- C(BAD_TP_NAME, "Invalid character in tracepoint name"),\
- C(BAD_ADDR_SUFFIX, "Invalid probed address suffix"), \
- C(NO_GROUP_NAME, "Group name is not specified"), \
- C(GROUP_TOO_LONG, "Group name is too long"), \
- C(BAD_GROUP_NAME, "Group name must follow the same rules as C identifiers"), \
- C(NO_EVENT_NAME, "Event name is not specified"), \
- C(EVENT_TOO_LONG, "Event name is too long"), \
- C(BAD_EVENT_NAME, "Event name must follow the same rules as C identifiers"), \
- C(EVENT_EXIST, "Given group/event name is already used by another event"), \
- C(RETVAL_ON_PROBE, "$retval is not available on probe"), \
- C(NO_RETVAL, "This function returns 'void' type"), \
- C(BAD_STACK_NUM, "Invalid stack number"), \
+ C(ARGIDX_2BIG, "$argN index is too big"), \
+ C(ARGS_2LONG, "$arg* failed because the argument list is too long"), \
+ C(ARG_NAME_TOO_LONG, "Argument name is too long"), \
+ C(ARG_TOO_LONG, "Argument expression is too long"), \
+ C(ARRAY_NO_CLOSE, "Array is not closed"), \
+ C(ARRAY_TOO_BIG, "Array number is too big"), \
+ C(BAD_ADDR_SUFFIX, "Invalid probed address suffix"), \
+ C(BAD_ARG_NAME, "Argument name must follow the same rules as C identifiers"), \
C(BAD_ARG_NUM, "Invalid argument number"), \
- C(BAD_VAR, "Invalid $-valiable specified"), \
- C(BAD_REG_NAME, "Invalid register name"), \
- C(BAD_MEM_ADDR, "Invalid memory address"), \
- C(BAD_IMM, "Invalid immediate value"), \
- C(IMMSTR_NO_CLOSE, "String is not closed with '\"'"), \
- C(FILE_ON_KPROBE, "File offset is not available with kprobe"), \
- C(BAD_FILE_OFFS, "Invalid file offset value"), \
- C(SYM_ON_UPROBE, "Symbol is not available with uprobe"), \
- C(TOO_MANY_OPS, "Dereference is too much nested"), \
- C(DEREF_NEED_BRACE, "Dereference needs a brace"), \
+ C(BAD_ARRAY_NUM, "Invalid array size"), \
+ C(BAD_ARRAY_SUFFIX, "Array has wrong suffix"), \
+ C(BAD_ATTACH_ARG, "Attached event does not have this field"), \
+ C(BAD_ATTACH_EVENT, "Attached event does not exist"), \
+ C(BAD_BITFIELD, "Invalid bitfield"), \
+ C(BAD_BTF_TID, "Failed to get BTF type info."), \
C(BAD_DEREF_OFFS, "Invalid dereference offset"), \
- C(DEREF_OPEN_BRACE, "Dereference brace is not closed"), \
- C(COMM_CANT_DEREF, "$comm can not be dereferenced"), \
+ C(BAD_EVENT_NAME, "Event name must follow the same rules as C identifiers"), \
C(BAD_FETCH_ARG, "Invalid fetch argument"), \
- C(ARRAY_NO_CLOSE, "Array is not closed"), \
- C(BAD_ARRAY_SUFFIX, "Array has wrong suffix"), \
- C(BAD_ARRAY_NUM, "Invalid array size"), \
- C(ARRAY_TOO_BIG, "Array number is too big"), \
- C(BAD_TYPE, "Unknown type is specified"), \
- C(BAD_STRING, "String accepts only memory argument"), \
+ C(BAD_FILE_OFFS, "Invalid file offset value"), \
+ C(BAD_GROUP_NAME, "Group name must follow the same rules as C identifiers"), \
+ C(BAD_HYPHEN, "Failed to parse single hyphen. Forgot '>'?"), \
+ C(BAD_IMM, "Invalid immediate value"), \
+ C(BAD_INSN_BNDRY, "Probe point is not an instruction boundary"), \
+ C(BAD_MAXACT, "Invalid maxactive number"), \
+ C(BAD_MAXACT_TYPE, "Maxactive is only for function exit"), \
+ C(BAD_MEM_ADDR, "Invalid memory address"), \
+ C(BAD_PROBE_ADDR, "Invalid probed address or symbol"), \
+ C(BAD_REFCNT, "Invalid reference counter offset"), \
+ C(BAD_REFCNT_SUFFIX, "Reference counter has wrong suffix"), \
+ C(BAD_REG_NAME, "Invalid register name"), \
+ C(BAD_RETPROBE, "Retprobe address must be an function entry"), \
+ C(BAD_STACK_NUM, "Invalid stack number"), \
+ C(BAD_STRING, "String accepts only memory argument"), \
C(BAD_SYMSTRING, "Symbol String doesn't accept data/userdata"), \
- C(BAD_BITFIELD, "Invalid bitfield"), \
- C(ARG_NAME_TOO_LONG, "Argument name is too long"), \
- C(NO_ARG_NAME, "Argument name is not specified"), \
- C(BAD_ARG_NAME, "Argument name must follow the same rules as C identifiers"), \
- C(USED_ARG_NAME, "This argument name is already used"), \
- C(ARG_TOO_LONG, "Argument expression is too long"), \
+ C(BAD_TP_NAME, "Invalid character in tracepoint name"), \
+ C(BAD_TYPE, "Unknown type is specified"), \
+ C(BAD_TYPE4STR, "This type does not fit for string."), \
+ C(BAD_UPROBE_OFFS, "Invalid uprobe offset"), \
+ C(BAD_VAR, "Invalid $-variable specified"), \
+ C(BAD_VAR_ARGS, "$arg* must be an independent parameter without name etc."), \
+ C(COMM_CANT_DEREF, "$comm can not be dereferenced"), \
+ C(DEREF_NEED_BRACE, "Dereference needs a brace"), \
+ C(DEREF_OPEN_BRACE, "Dereference brace is not closed"), \
+ C(DIFF_ARG_TYPE, "Argument type or name is different from existing probe"), \
+ C(DIFF_PROBE_TYPE, "Probe type is different from existing probe"), \
+ C(DOUBLE_ARGS, "$arg* can be used only once in the parameters"), \
+ C(EVENT_EXIST, "Given group/event name is already used by another event"), \
+ C(EVENT_TOO_BIG, "Event too big (too many fields?)"), \
+ C(EVENT_TOO_LONG, "Event name is too long"), \
+ C(FAIL_REG_PROBE, "Failed to register probe event"), \
+ C(FILE_NOT_FOUND, "Failed to find the given file"), \
+ C(FILE_ON_KPROBE, "File offset is not available for kernel probes"), \
+ C(GROUP_TOO_LONG, "Group name is too long"), \
+ C(IMMSTR_NO_CLOSE, "String is not closed with '\"'"), \
+ C(MAXACT_TOO_BIG, "Maxactive is too big"), \
+ C(NEED_STRING_TYPE, "$comm and immediate-string only accepts string type"), \
+ C(NOFENTRY_ARGS, "$arg* can be used only on function entry or exit"), \
+ C(NON_UNIQ_SYMBOL, "The symbol is not unique"), \
+ C(NOSUP_BTFARG, "BTF is not available or not supported"), \
+ C(NOSUP_DAT_ARG, "Non pointer structure/union argument is not supported."), \
+ C(NOSUP_PERCPU, "Per-cpu variable access is only for kernel probes"), \
C(NO_ARG_BODY, "No argument expression"), \
- C(BAD_INSN_BNDRY, "Probe point is not an instruction boundary"),\
- C(FAIL_REG_PROBE, "Failed to register probe event"),\
- C(DIFF_PROBE_TYPE, "Probe type is different from existing probe"),\
- C(DIFF_ARG_TYPE, "Argument type or name is different from existing probe"),\
- C(SAME_PROBE, "There is already the exact same probe event"),\
- C(NO_EVENT_INFO, "This requires both group and event name to attach"),\
- C(BAD_ATTACH_EVENT, "Attached event does not exist"),\
- C(BAD_ATTACH_ARG, "Attached event does not have this field"),\
+ C(NO_ARG_NAME, "Argument name is not specified"), \
+ C(NO_BTFARG, "This variable is not found at this probe point"), \
+ C(NO_BTF_ENTRY, "No BTF entry for this probe point"), \
+ C(NO_BTF_FIELD, "This field is not found."), \
C(NO_EP_FILTER, "No filter rule after 'if'"), \
- C(NOSUP_BTFARG, "BTF is not available or not supported"), \
- C(NO_BTFARG, "This variable is not found at this probe point"),\
- C(NO_BTF_ENTRY, "No BTF entry for this probe point"), \
- C(BAD_VAR_ARGS, "$arg* must be an independent parameter without name etc."),\
- C(NOFENTRY_ARGS, "$arg* can be used only on function entry or exit"), \
- C(DOUBLE_ARGS, "$arg* can be used only once in the parameters"), \
- C(ARGS_2LONG, "$arg* failed because the argument list is too long"), \
- C(ARGIDX_2BIG, "$argN index is too big"), \
+ C(NO_EVENT_FIELD, "This event field is not found."), \
+ C(NO_EVENT_INFO, "This requires both group and event name to attach"), \
+ C(NO_EVENT_NAME, "Event name is not specified"), \
+ C(NO_GROUP_NAME, "Group name is not specified"), \
C(NO_PTR_STRCT, "This is not a pointer to union/structure."), \
- C(NOSUP_DAT_ARG, "Non pointer structure/union argument is not supported."),\
- C(BAD_HYPHEN, "Failed to parse single hyphen. Forgot '>'?"), \
- C(NO_BTF_FIELD, "This field is not found."), \
- C(BAD_BTF_TID, "Failed to get BTF type info."),\
- C(BAD_TYPE4STR, "This type does not fit for string."),\
- C(NEED_STRING_TYPE, "$comm and immediate-string only accepts string type"),\
- C(TOO_MANY_ARGS, "Too many arguments are specified"), \
+ C(NO_REGULAR_FILE, "Not a regular file"), \
+ C(NO_RETVAL, "This function returns 'void' type"), \
+ C(NO_TRACEPOINT, "Tracepoint is not found"), \
+ C(REFCNT_OPEN_BRACE, "Reference counter brace is not closed"), \
+ C(RETVAL_ON_PROBE, "$retval is not available on probe"), \
+ C(SAME_PROBE, "There is already the exact same probe event"), \
+ C(SYM_ON_UPROBE, "Symbol is not available with uprobe"), \
+ C(TOO_MANY_ARGS, "Too many arguments are specified"), \
C(TOO_MANY_EARGS, "Too many entry arguments specified"), \
- C(EVENT_TOO_BIG, "Event too big (too many fields?)"), \
- C(TYPECAST_NOT_EVENT, "Typecasts are only for eprobe fields"),
+ C(TOO_MANY_NESTED, "Too many nested typecasts/dereferences"), \
+ C(TOO_MANY_OPS, "Dereference is too much nested"), \
+ C(TYPECAST_BAD_ARROW, "Typecast field option does not support -> operator"), \
+ C(TYPECAST_NOT_ALIGNED, "Typecast field option is not byte-aligned"), \
+ C(TYPECAST_NOT_EVENT, "Typecasts are only for eprobe fields"), \
+ C(TYPECAST_REQ_FIELD, "Typecast requires a field access"), \
+ C(TYPECAST_SYM_OFFSET, "@SYM+/-OFFSET with typecast needs parentheses"), \
+ C(USED_ARG_NAME, "This argument name is already used"),
#undef C
#define C(a, b) TP_ERR_##a
diff --git a/kernel/trace/trace_probe_kernel.h b/kernel/trace/trace_probe_kernel.h
index bb723eefd7b7..4eb4acfa09ba 100644
--- a/kernel/trace/trace_probe_kernel.h
+++ b/kernel/trace/trace_probe_kernel.h
@@ -12,7 +12,7 @@
static nokprobe_inline int
fetch_store_strlen_user(unsigned long addr)
{
- const void __user *uaddr = (__force const void __user *)addr;
+ const void __user *uaddr = (__force const void __user *)addr;
return strnlen_user_nofault(uaddr, MAX_STRING_SIZE);
}
diff --git a/kernel/trace/trace_probe_tmpl.h b/kernel/trace/trace_probe_tmpl.h
index f39b37fcdb3b..8db12f758fda 100644
--- a/kernel/trace/trace_probe_tmpl.h
+++ b/kernel/trace/trace_probe_tmpl.h
@@ -109,9 +109,12 @@ process_common_fetch_insn(struct fetch_insn *code, unsigned long *val)
case FETCH_OP_COMM:
*val = (unsigned long)current->comm;
break;
- case FETCH_OP_DATA:
+ case FETCH_OP_IMMSTR:
*val = (unsigned long)code->data;
break;
+ case FETCH_OP_CURRENT:
+ *val = (unsigned long)current;
+ break;
default:
return -EILSEQ;
}
@@ -126,25 +129,35 @@ process_fetch_insn_bottom(struct fetch_insn *code, unsigned long val,
struct fetch_insn *s3 = NULL;
int total = 0, ret = 0, i = 0;
u32 loc = 0;
- unsigned long lval = val;
+ unsigned long lval, llval = val;
stage2:
/* 2nd stage: dereference memory if needed */
do {
- if (code->op == FETCH_OP_DEREF) {
- lval = val;
+ lval = val;
+ switch (code->op) {
+ case FETCH_OP_DEREF:
ret = probe_mem_read(&val, (void *)val + code->offset,
sizeof(val));
- } else if (code->op == FETCH_OP_UDEREF) {
- lval = val;
+ break;
+ case FETCH_OP_UDEREF:
ret = probe_mem_read_user(&val,
(void *)val + code->offset, sizeof(val));
- } else
break;
+ case FETCH_OP_CPU_PTR:
+ val = (unsigned long)this_cpu_ptr((void __percpu *)val);
+ ret = 0;
+ break;
+ default:
+ lval = llval;
+ goto out;
+ }
if (ret)
return ret;
+ llval = lval;
code++;
} while (1);
+out:
s3 = code;
stage3:
diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c
index 2a6cc000ec98..2d5bc423efca 100644
--- a/kernel/trace/trace_remote.c
+++ b/kernel/trace/trace_remote.c
@@ -251,8 +251,8 @@ static int trace_remote_get(struct trace_remote *remote, int cpu)
if (cpu != RING_BUFFER_ALL_CPUS && !remote->pcpu_reader_locks) {
int lock_cpu;
- remote->pcpu_reader_locks = kcalloc(nr_cpu_ids, sizeof(*remote->pcpu_reader_locks),
- GFP_KERNEL);
+ remote->pcpu_reader_locks = kzalloc_objs(*remote->pcpu_reader_locks,
+ nr_cpu_ids);
if (!remote->pcpu_reader_locks) {
trace_remote_try_unload(remote);
return -ENOMEM;
@@ -324,7 +324,7 @@ static int __alloc_ring_buffer_iter(struct trace_remote_iterator *iter, int cpu)
return iter->rb_iter ? 0 : -ENOMEM;
}
- iter->rb_iters = kcalloc(nr_cpu_ids, sizeof(*iter->rb_iters), GFP_KERNEL);
+ iter->rb_iters = kzalloc_objs(*iter->rb_iters, nr_cpu_ids);
if (!iter->rb_iters)
return -ENOMEM;
@@ -979,43 +979,40 @@ EXPORT_SYMBOL_GPL(trace_remote_free_buffer);
int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size, size_t buffer_size,
const struct cpumask *cpumask)
{
- unsigned int nr_pages = max(DIV_ROUND_UP(buffer_size, PAGE_SIZE), 2UL) + 1;
- void *desc_end = desc + desc_size;
+ size_t min_desc_size = trace_buffer_desc_size(buffer_size, cpumask_weight(cpumask));
struct ring_buffer_desc *rb_desc;
int cpu, ret = -ENOMEM;
+ unsigned int nr_pages;
- if (desc_size < struct_size(desc, __data, 0))
+ if (min_desc_size == SIZE_MAX)
+ return -E2BIG;
+
+ if (desc_size < min_desc_size)
return -EINVAL;
desc->nr_cpus = 0;
- desc->struct_len = struct_size(desc, __data, 0);
+ desc->struct_len = min_desc_size;
- rb_desc = (struct ring_buffer_desc *)&desc->__data[0];
+ rb_desc = __first_ring_buffer_desc(desc);
+ nr_pages = __calc_nr_pages_ring_buffer_desc(buffer_size);
for_each_cpu(cpu, cpumask) {
unsigned int id;
- if ((void *)rb_desc + struct_size(rb_desc, page_va, nr_pages) > desc_end) {
- ret = -EINVAL;
- goto err;
- }
-
rb_desc->cpu = cpu;
rb_desc->nr_page_va = 0;
rb_desc->meta_va = (unsigned long)__get_free_page(GFP_KERNEL);
if (!rb_desc->meta_va)
goto err;
+ desc->nr_cpus++;
+
for (id = 0; id < nr_pages; id++) {
+ rb_desc->nr_page_va++;
rb_desc->page_va[id] = (unsigned long)__get_free_page(GFP_KERNEL);
if (!rb_desc->page_va[id])
goto err;
-
- rb_desc->nr_page_va++;
}
- desc->nr_cpus++;
- desc->struct_len += offsetof(struct ring_buffer_desc, page_va);
- desc->struct_len += struct_size(rb_desc, page_va, rb_desc->nr_page_va);
rb_desc = __next_ring_buffer_desc(rb_desc);
}
@@ -1156,10 +1153,21 @@ static ssize_t remote_events_dir_enable_write(struct file *filp, const char __us
for (i = 0; i < remote->nr_events; i++) {
struct remote_event *evt = &remote->events[i];
+ int eret;
- trace_remote_enable_event(remote, evt, enable);
+ eret = trace_remote_enable_event(remote, evt, enable);
+ /*
+ * Save the first error and return that. Some events
+ * may still have been enabled, but let the user
+ * know that something went wrong.
+ */
+ if (!ret && eret)
+ ret = eret;
}
+ if (ret)
+ return ret;
+
return count;
}
@@ -1200,7 +1208,7 @@ remote_events_dir_header_page_read(struct file *filp, char __user *ubuf, size_t
struct trace_seq *s;
int ret;
- s = kmalloc(sizeof(*s), GFP_KERNEL);
+ s = kmalloc_obj(*s);
if (!s)
return -ENOMEM;
@@ -1223,7 +1231,7 @@ remote_events_dir_header_event_read(struct file *filp, char __user *ubuf, size_t
struct trace_seq *s;
int ret;
- s = kmalloc(sizeof(*s), GFP_KERNEL);
+ s = kmalloc_obj(*s);
if (!s)
return -ENOMEM;
diff --git a/kernel/trace/trace_sched_switch.c b/kernel/trace/trace_sched_switch.c
index e9f0ff962660..1bd351c1fbfb 100644
--- a/kernel/trace/trace_sched_switch.c
+++ b/kernel/trace/trace_sched_switch.c
@@ -286,12 +286,12 @@ static void __trace_find_cmdline(int pid, char comm[])
int tpid;
if (!pid) {
- strcpy(comm, "<idle>");
+ strscpy(comm, "<idle>", TASK_COMM_LEN);
return;
}
if (WARN_ON_ONCE(pid < 0)) {
- strcpy(comm, "<XXX>");
+ strscpy(comm, "<XXX>", TASK_COMM_LEN);
return;
}
@@ -304,7 +304,7 @@ static void __trace_find_cmdline(int pid, char comm[])
return;
}
}
- strcpy(comm, "<...>");
+ strscpy(comm, "<...>", TASK_COMM_LEN);
}
void trace_find_cmdline(int pid, char comm[])
diff --git a/kernel/trace/trace_stack.c b/kernel/trace/trace_stack.c
index 0aa2514a6593..e7f4e523587d 100644
--- a/kernel/trace/trace_stack.c
+++ b/kernel/trace/trace_stack.c
@@ -499,7 +499,7 @@ stack_trace_filter_open(struct inode *inode, struct file *file)
struct ftrace_ops *ops = inode->i_private;
/* Checks for tracefs lockdown */
- return ftrace_regex_open(ops, FTRACE_ITER_FILTER,
+ return ftrace_regex_open(NULL, ops, FTRACE_ITER_FILTER,
inode, file);
}
diff --git a/kernel/trace/trace_syscalls.c b/kernel/trace/trace_syscalls.c
index e98ee7e1e66f..e35744049e3f 100644
--- a/kernel/trace/trace_syscalls.c
+++ b/kernel/trace/trace_syscalls.c
@@ -9,6 +9,7 @@
#include <linux/ftrace.h>
#include <linux/perf_event.h>
#include <linux/xarray.h>
+#include <linux/btf_ids.h>
#include <asm/syscall.h>
#include "trace_output.h"
@@ -1303,12 +1304,26 @@ struct trace_event_functions exit_syscall_print_funcs = {
.trace = print_syscall_exit,
};
+#if defined(CONFIG_BPF_EVENTS) && defined(CONFIG_DEBUG_INFO_BTF)
+/* BTF id lists for the shared sys_enter/sys_exit dispatcher tracepoints. */
+BTF_ID_LIST(syscall_enter_btf_ids)
+BTF_ID(func, __bpf_trace_sys_enter)
+BTF_ID(struct, trace_event_raw_sys_enter)
+
+BTF_ID_LIST(syscall_exit_btf_ids)
+BTF_ID(func, __bpf_trace_sys_exit)
+BTF_ID(struct, trace_event_raw_sys_exit)
+#endif
+
struct trace_event_class __refdata event_class_syscall_enter = {
.system = "syscalls",
.reg = syscall_enter_register,
.fields_array = syscall_enter_fields_array,
.get_fields = syscall_get_enter_fields,
.raw_init = init_syscall_trace,
+#if defined(CONFIG_BPF_EVENTS) && defined(CONFIG_DEBUG_INFO_BTF)
+ .btf_ids = syscall_enter_btf_ids,
+#endif
};
struct trace_event_class __refdata event_class_syscall_exit = {
@@ -1321,6 +1336,9 @@ struct trace_event_class __refdata event_class_syscall_exit = {
},
.fields = LIST_HEAD_INIT(event_class_syscall_exit.fields),
.raw_init = init_syscall_trace,
+#if defined(CONFIG_BPF_EVENTS) && defined(CONFIG_DEBUG_INFO_BTF)
+ .btf_ids = syscall_exit_btf_ids,
+#endif
};
unsigned long __init __weak arch_syscall_addr(int nr)
@@ -1451,6 +1469,11 @@ static void perf_syscall_enter(void *ignore, struct pt_regs *regs, long id)
if (syscall_get_data(sys_data, args, &user_ptr,
&size, user_sizes, &uargs, buf_size) < 0)
return;
+
+ /* The above may have caused a migration */
+ head = this_cpu_ptr(sys_data->enter_event->perf_events);
+ if (hlist_empty(head))
+ return;
}
/* get the size after alignment with the u32 buffer size field */
diff --git a/kernel/trace/trace_uprobe.c b/kernel/trace/trace_uprobe.c
index c274346853d1..22cc3c8181b8 100644
--- a/kernel/trace/trace_uprobe.c
+++ b/kernel/trace/trace_uprobe.c
@@ -281,7 +281,7 @@ static bool trace_uprobe_is_busy(struct dyn_event *ev)
static bool trace_uprobe_match_command_head(struct trace_uprobe *tu,
int argc, const char **argv)
{
- char buf[MAX_ARGSTR_LEN + 1];
+ char buf[64];
int len;
if (!argc)
@@ -368,7 +368,7 @@ error:
static void free_trace_uprobe(struct trace_uprobe *tu)
{
- if (!tu)
+ if (IS_ERR_OR_NULL(tu))
return;
path_put(&tu->path);
@@ -533,7 +533,7 @@ static int register_trace_uprobe(struct trace_uprobe *tu)
return ret;
}
-DEFINE_FREE(free_trace_uprobe, struct trace_uprobe *, if (_T) free_trace_uprobe(_T))
+DEFINE_FREE(free_trace_uprobe, struct trace_uprobe *, free_trace_uprobe(_T))
/*
* Argument syntax:
@@ -765,6 +765,9 @@ static int trace_uprobe_show(struct seq_file *m, struct dyn_event *ev)
seq_printf(m, " %s=%s", tu->tp.args[i].name, tu->tp.args[i].comm);
seq_putc(m, '\n');
+
+ trace_probe_dump_args(m, &tu->tp);
+
return 0;
}
diff --git a/kernel/ucount.c b/kernel/ucount.c
index d6dc3e859f12..ec8b1445e287 100644
--- a/kernel/ucount.c
+++ b/kernel/ucount.c
@@ -4,6 +4,7 @@
#include <linux/sysctl.h>
#include <linux/slab.h>
#include <linux/cred.h>
+#include <linux/export.h>
#include <linux/hash.h>
#include <linux/kmemleak.h>
#include <linux/user_namespace.h>
@@ -89,6 +90,9 @@ static const struct ctl_table user_table[] = {
UCOUNT_ENTRY("max_fanotify_groups"),
UCOUNT_ENTRY("max_fanotify_marks"),
#endif
+#if IS_ENABLED(CONFIG_BINFMT_MISC)
+ UCOUNT_ENTRY("max_binfmt_misc_interpreters"),
+#endif
};
#endif /* CONFIG_SYSCTL */
@@ -233,6 +237,7 @@ fail:
put_ucounts(ucounts);
return NULL;
}
+EXPORT_SYMBOL_FOR_MODULES(inc_ucount, "binfmt_misc");
void dec_ucount(struct ucounts *ucounts, enum ucount_type type)
{
@@ -243,6 +248,7 @@ void dec_ucount(struct ucounts *ucounts, enum ucount_type type)
}
put_ucounts(ucounts);
}
+EXPORT_SYMBOL_FOR_MODULES(dec_ucount, "binfmt_misc");
long inc_rlimit_ucounts(struct ucounts *ucounts, enum rlimit_type type, long v)
{
diff --git a/kernel/umh.c b/kernel/umh.c
index 48117c569e1a..6e2c7bb315c6 100644
--- a/kernel/umh.c
+++ b/kernel/umh.c
@@ -71,10 +71,8 @@ static int call_usermodehelper_exec_async(void *data)
spin_unlock_irq(&current->sighand->siglock);
/*
- * Initial kernel threads share ther FS with init, in order to
- * get the init root directory. But we've now created a new
- * thread that is going to execve a user process and has its own
- * 'struct fs_struct'. Reset umask to the default.
+ * Usermodehelper threads get a copy of userspace init's
+ * fs_struct. Reset umask to the default.
*/
current->fs->umask = 0022;
diff --git a/kernel/up.c b/kernel/up.c
index df50828cc2f0..6d4ac9502e8b 100644
--- a/kernel/up.c
+++ b/kernel/up.c
@@ -9,8 +9,7 @@
#include <linux/smp.h>
#include <linux/hypervisor.h>
-int smp_call_function_single(int cpu, void (*func) (void *info), void *info,
- int wait)
+int smp_call_function_single(int cpu, void (*func)(void *info), void *info, bool wait)
{
unsigned long flags;
diff --git a/kernel/user.c b/kernel/user.c
index 7aef4e679a6a..21bafdc11379 100644
--- a/kernel/user.c
+++ b/kernel/user.c
@@ -23,9 +23,9 @@
#if IS_ENABLED(CONFIG_BINFMT_MISC)
struct binfmt_misc init_binfmt_misc = {
- .entries = LIST_HEAD_INIT(init_binfmt_misc.entries),
+ .entries = HLIST_HEAD_INIT,
.enabled = true,
- .entries_lock = __RW_LOCK_UNLOCKED(init_binfmt_misc.entries_lock),
+ .entries_lock = __SPIN_LOCK_UNLOCKED(init_binfmt_misc.entries_lock),
};
EXPORT_SYMBOL_GPL(init_binfmt_misc);
#endif
diff --git a/kernel/utsname_sysctl.c b/kernel/utsname_sysctl.c
index bfbaaecb1dd4..6c1522201e39 100644
--- a/kernel/utsname_sysctl.c
+++ b/kernel/utsname_sysctl.c
@@ -13,7 +13,7 @@
#include <linux/wait.h>
#include <linux/rwsem.h>
-#ifdef CONFIG_PROC_SYSCTL
+#ifdef CONFIG_SYSCTL
static void *get_uts(const struct ctl_table *table)
{
@@ -122,7 +122,7 @@ static const struct ctl_table uts_kern_table[] = {
},
};
-#ifdef CONFIG_PROC_SYSCTL
+#ifdef CONFIG_SYSCTL
/*
* Notify userspace about a change in a certain entry of uts_kern_table,
* identified by the parameter proc.
diff --git a/kernel/watchdog.c b/kernel/watchdog.c
index 87dd5e0f6968..e5134ad7b663 100644
--- a/kernel/watchdog.c
+++ b/kernel/watchdog.c
@@ -359,14 +359,14 @@ static void lockup_detector_update_enable(void)
if (watchdog_hardlockup_available && watchdog_hardlockup_user_enabled)
watchdog_enabled |= WATCHDOG_HARDLOCKUP_ENABLED;
if (watchdog_softlockup_user_enabled)
- watchdog_enabled |= WATCHDOG_SOFTOCKUP_ENABLED;
+ watchdog_enabled |= WATCHDOG_SOFTLOCKUP_ENABLED;
}
#ifdef CONFIG_SOFTLOCKUP_DETECTOR
/*
- * Delay the soflockup report when running a known slow code.
- * It does _not_ affect the timestamp of the last successdul reschedule.
+ * Delay the softlockup report when running a known slow code.
+ * It does _not_ affect the timestamp of the last successful reschedule.
*/
#define SOFTLOCKUP_DELAY_REPORT ULONG_MAX
@@ -742,7 +742,7 @@ static int is_softlockup(unsigned long touch_ts,
unsigned long period_ts,
unsigned long now)
{
- if ((watchdog_enabled & WATCHDOG_SOFTOCKUP_ENABLED) && watchdog_thresh) {
+ if ((watchdog_enabled & WATCHDOG_SOFTLOCKUP_ENABLED) && watchdog_thresh) {
/*
* If period_ts has not been updated during a sample_period, then
* in the subsequent few sample_periods, period_ts might also not
@@ -1098,11 +1098,11 @@ static void proc_watchdog_update(bool thresh_changed)
* caller | table->data points to | 'which'
* -------------------|----------------------------------|-------------------------------
* proc_watchdog | watchdog_user_enabled | WATCHDOG_HARDLOCKUP_ENABLED |
- * | | WATCHDOG_SOFTOCKUP_ENABLED
+ * | | WATCHDOG_SOFTLOCKUP_ENABLED
* -------------------|----------------------------------|-------------------------------
* proc_nmi_watchdog | watchdog_hardlockup_user_enabled | WATCHDOG_HARDLOCKUP_ENABLED
* -------------------|----------------------------------|-------------------------------
- * proc_soft_watchdog | watchdog_softlockup_user_enabled | WATCHDOG_SOFTOCKUP_ENABLED
+ * proc_soft_watchdog | watchdog_softlockup_user_enabled | WATCHDOG_SOFTLOCKUP_ENABLED
*/
static int proc_watchdog_common(int which, const struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
@@ -1136,7 +1136,7 @@ static int proc_watchdog(const struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
{
return proc_watchdog_common(WATCHDOG_HARDLOCKUP_ENABLED |
- WATCHDOG_SOFTOCKUP_ENABLED,
+ WATCHDOG_SOFTLOCKUP_ENABLED,
table, write, buffer, lenp, ppos);
}
@@ -1159,7 +1159,7 @@ static int proc_nmi_watchdog(const struct ctl_table *table, int write,
static int proc_soft_watchdog(const struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
{
- return proc_watchdog_common(WATCHDOG_SOFTOCKUP_ENABLED,
+ return proc_watchdog_common(WATCHDOG_SOFTLOCKUP_ENABLED,
table, write, buffer, lenp, ppos);
}
#endif
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index c06d131c2505..685e539dfe74 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -6520,6 +6520,30 @@ bool current_is_workqueue_rescuer(void)
}
/**
+ * current_is_workqueue_mem_reclaim - is %current a %WQ_MEM_RECLAIM worker?
+ *
+ * Determine whether %current is a workqueue worker executing on a workqueue
+ * created with %WQ_MEM_RECLAIM. This mirrors the condition that
+ * check_flush_dependency() warns on: flushing (or otherwise waiting on) a
+ * !WQ_MEM_RECLAIM workqueue from such a context breaks the forward-progress
+ * guarantee and can deadlock. Callers that may recurse into such a flush --
+ * e.g. NFS LOCALIO submitting into a stacked filesystem that flushes its own
+ * !WQ_MEM_RECLAIM workqueue -- can use this to decide whether they must defer
+ * the work to a !WQ_MEM_RECLAIM workqueue rather than run it inline.
+ *
+ * Return: %true if %current is a %WQ_MEM_RECLAIM worker. %false otherwise.
+ */
+bool current_is_workqueue_mem_reclaim(void)
+{
+ struct worker *worker = current_wq_worker();
+
+ return worker && worker->current_pwq &&
+ ((worker->current_pwq->wq->flags &
+ (WQ_MEM_RECLAIM | __WQ_LEGACY)) == WQ_MEM_RECLAIM);
+}
+EXPORT_SYMBOL_GPL(current_is_workqueue_mem_reclaim);
+
+/**
* workqueue_congested - test whether a workqueue is congested
* @cpu: CPU in question
* @wq: target workqueue