[PATCH 2/2] evl/sched/quota: Correct budget tracking for preempted threads

Jan Kiszka <[email protected]> Sun, 14 Jun 2026 21:35:55 +0200
Newsgroups dev.linux.lists.xenomai
Message-ID <[email protected]>
From: Jan Kiszka <[email protected]>

This was so far not possible due to a missing sched_class callback,
causing threads being credited for the preemption time of threads from
higher-weighted classes.

Signed-off-by: Jan Kiszka <[email protected]>
---
 kernel/evl/sched/quota.c | 56 +++++++++++++++++++++++++++++-----------
 1 file changed, 41 insertions(+), 15 deletions(-)

diff --git a/kernel/evl/sched/quota.c b/kernel/evl/sched/quota.c
index 0829da711a66a..d331dc2e8f7ac 100644
--- a/kernel/evl/sched/quota.c
+++ b/kernel/evl/sched/quota.c
@@ -187,11 +187,28 @@ static void quota_refill_handler(struct evl_timer *timer) /* oob stage stalled *
 	raw_spin_unlock(&rq->lock);
 }
 
+static void charge_usage(struct evl_quota_group *tg, ktime_t now)
+{
+	ktime_t elapsed;
+
+	elapsed =  ktime_sub(now, tg->run_start);
+	if (elapsed < tg->run_budget)
+		tg->run_budget = ktime_sub(tg->run_budget, elapsed);
+	else
+		tg->run_budget = 0;
+}
+
 static void quota_limit_handler(struct evl_timer *timer) /* oob stage stalled */
 {
+	struct evl_quota_group *tg;
 	struct evl_rq *rq;
 
 	rq = container_of(timer, struct evl_rq, quota.limit_timer);
+
+	tg = rq->curr->quota;
+	if (tg)
+		charge_usage(tg, evl_ktime_monotonic());
+
 	/*
 	 * Force a rescheduling on the return path of the current
 	 * interrupt, so that the budget is re-evaluated for the
@@ -253,6 +270,8 @@ static bool quota_setparam(struct evl_thread *thread,
 			/* Dequeued earlier by our caller. */
 			list_del(&thread->quota_next);
 			thread->quota->nr_threads--;
+		} else if (thread == evl_current()) {
+			tg->run_start = evl_ktime_monotonic();
 		}
 		thread->quota = tg;
 		list_add(&thread->quota_next, &tg->members);
@@ -400,21 +419,11 @@ static struct evl_thread *quota_pick(struct evl_rq *rq)
 	struct evl_thread *next, *curr = rq->curr;
 	struct evl_sched_quota *qs = &rq->quota;
 	struct evl_quota_group *otg, *tg;
-	ktime_t now, elapsed;
+	ktime_t now;
 
 	now = evl_ktime_monotonic();
 	otg = curr->quota;
-	if (otg == NULL)
-		goto pick;
-	/*
-	 * Charge the time consumed by the outgoing thread to the
-	 * group it belongs to.
-	 */
-	elapsed = ktime_sub(now, otg->run_start);
-	if (elapsed < otg->run_budget)
-		otg->run_budget = ktime_sub(otg->run_budget, elapsed);
-	else
-		otg->run_budget = 0;
+
 pick:
 	next = evl_get_schedq(&rq->fifo.runnable);
 	if (next == NULL) {
@@ -430,8 +439,6 @@ static struct evl_thread *quota_pick(struct evl_rq *rq)
 	if (tg == NULL)
 		return next;
 
-	tg->run_start = now;
-
 	/*
 	 * Don't consider budget if kicked, we have to allow this
 	 * thread to run until it eventually switches to in-band
@@ -448,9 +455,12 @@ static struct evl_thread *quota_pick(struct evl_rq *rq)
 		goto pick;
 	}
 
-	if (otg == tg && evl_timer_is_running(&qs->limit_timer))
+	if (otg != tg) {
+		tg->run_start = now;
+	} else if (evl_timer_is_running(&qs->limit_timer)) {
 		/* Same group, leave the running timer untouched. */
 		goto out;
+	}
 
 	/* Arm limit timer for the new running group. */
 	evl_start_timer(&qs->limit_timer,
@@ -462,6 +472,21 @@ static struct evl_thread *quota_pick(struct evl_rq *rq)
 	return next;
 }
 
+static void quota_out(struct evl_thread *thread, struct evl_thread *next)
+{
+	struct evl_quota_group *otg = thread->quota;
+	struct evl_quota_group *ntg = next->quota;
+	ktime_t now;
+
+	if (otg && otg != ntg) {
+		if (ntg)
+			now = ntg->run_start;
+		else
+			now = evl_ktime_monotonic();
+		charge_usage(otg, now);
+	}
+}
+
 static void quota_migrate(struct evl_thread *thread, struct evl_rq *rq)
 {
 	union evl_sched_param param;
@@ -786,6 +811,7 @@ struct evl_sched_class evl_sched_quota = {
 	.sched_dequeue		=	quota_dequeue,
 	.sched_requeue		=	quota_requeue,
 	.sched_pick		=	quota_pick,
+	.sched_out		=	quota_out,
 	.sched_migrate		=	quota_migrate,
 	.sched_chkparam		=	quota_chkparam,
 	.sched_setparam		=	quota_setparam,
-- 
2.47.3