xref: /linux-6.15/lib/dynamic_queue_limits.c (revision 3a17f23f)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
275957ba3STom Herbert /*
375957ba3STom Herbert  * Dynamic byte queue limits.  See include/linux/dynamic_queue_limits.h
475957ba3STom Herbert  *
575957ba3STom Herbert  * Copyright (c) 2011, Tom Herbert <[email protected]>
675957ba3STom Herbert  */
775957ba3STom Herbert #include <linux/types.h>
875957ba3STom Herbert #include <linux/kernel.h>
9930c514fSTom Herbert #include <linux/jiffies.h>
1075957ba3STom Herbert #include <linux/dynamic_queue_limits.h>
11565ac23bSRasmus Villemoes #include <linux/compiler.h>
12565ac23bSRasmus Villemoes #include <linux/export.h>
136025b913SJakub Kicinski #include <trace/events/napi.h>
1475957ba3STom Herbert 
150cfd32b7SHiroaki SHIMODA #define POSDIFF(A, B) ((int)((A) - (B)) > 0 ? (A) - (B) : 0)
1625426b79SHiroaki SHIMODA #define AFTER_EQ(A, B) ((int)((A) - (B)) >= 0)
1775957ba3STom Herbert 
dql_check_stall(struct dql * dql,unsigned short stall_thrs)184ba67ef3SBreno Leitao static void dql_check_stall(struct dql *dql, unsigned short stall_thrs)
196025b913SJakub Kicinski {
206025b913SJakub Kicinski 	unsigned long now;
216025b913SJakub Kicinski 
226025b913SJakub Kicinski 	if (!stall_thrs)
236025b913SJakub Kicinski 		return;
246025b913SJakub Kicinski 
256025b913SJakub Kicinski 	now = jiffies;
266025b913SJakub Kicinski 	/* Check for a potential stall */
276025b913SJakub Kicinski 	if (time_after_eq(now, dql->last_reap + stall_thrs)) {
286025b913SJakub Kicinski 		unsigned long hist_head, t, start, end;
296025b913SJakub Kicinski 
306025b913SJakub Kicinski 		/* We are trying to detect a period of at least @stall_thrs
316025b913SJakub Kicinski 		 * jiffies without any Tx completions, but during first half
326025b913SJakub Kicinski 		 * of which some Tx was posted.
336025b913SJakub Kicinski 		 */
346025b913SJakub Kicinski dqs_again:
356025b913SJakub Kicinski 		hist_head = READ_ONCE(dql->history_head);
366025b913SJakub Kicinski 		/* pairs with smp_wmb() in dql_queued() */
376025b913SJakub Kicinski 		smp_rmb();
386025b913SJakub Kicinski 
396025b913SJakub Kicinski 		/* Get the previous entry in the ring buffer, which is the
406025b913SJakub Kicinski 		 * oldest sample.
416025b913SJakub Kicinski 		 */
426025b913SJakub Kicinski 		start = (hist_head - DQL_HIST_LEN + 1) * BITS_PER_LONG;
436025b913SJakub Kicinski 
446025b913SJakub Kicinski 		/* Advance start to continue from the last reap time */
456025b913SJakub Kicinski 		if (time_before(start, dql->last_reap + 1))
466025b913SJakub Kicinski 			start = dql->last_reap + 1;
476025b913SJakub Kicinski 
486025b913SJakub Kicinski 		/* Newest sample we should have already seen a completion for */
496025b913SJakub Kicinski 		end = hist_head * BITS_PER_LONG + (BITS_PER_LONG - 1);
506025b913SJakub Kicinski 
516025b913SJakub Kicinski 		/* Shrink the search space to [start, (now - start_thrs/2)] if
526025b913SJakub Kicinski 		 * `end` is beyond the stall zone
536025b913SJakub Kicinski 		 */
546025b913SJakub Kicinski 		if (time_before(now, end + stall_thrs / 2))
556025b913SJakub Kicinski 			end = now - stall_thrs / 2;
566025b913SJakub Kicinski 
576025b913SJakub Kicinski 		/* Search for the queued time in [t, end] */
586025b913SJakub Kicinski 		for (t = start; time_before_eq(t, end); t++)
596025b913SJakub Kicinski 			if (test_bit(t % (DQL_HIST_LEN * BITS_PER_LONG),
606025b913SJakub Kicinski 				     dql->history))
616025b913SJakub Kicinski 				break;
626025b913SJakub Kicinski 
636025b913SJakub Kicinski 		/* Variable t contains the time of the queue */
646025b913SJakub Kicinski 		if (!time_before_eq(t, end))
656025b913SJakub Kicinski 			goto no_stall;
666025b913SJakub Kicinski 
676025b913SJakub Kicinski 		/* The ring buffer was modified in the meantime, retry */
686025b913SJakub Kicinski 		if (hist_head != READ_ONCE(dql->history_head))
696025b913SJakub Kicinski 			goto dqs_again;
706025b913SJakub Kicinski 
716025b913SJakub Kicinski 		dql->stall_cnt++;
726025b913SJakub Kicinski 		dql->stall_max = max_t(unsigned short, dql->stall_max, now - t);
736025b913SJakub Kicinski 
746025b913SJakub Kicinski 		trace_dql_stall_detected(dql->stall_thrs, now - t,
756025b913SJakub Kicinski 					 dql->last_reap, dql->history_head,
766025b913SJakub Kicinski 					 now, dql->history);
776025b913SJakub Kicinski 	}
786025b913SJakub Kicinski no_stall:
796025b913SJakub Kicinski 	dql->last_reap = now;
806025b913SJakub Kicinski }
816025b913SJakub Kicinski 
8275957ba3STom Herbert /* Records completed count and recalculates the queue limit */
dql_completed(struct dql * dql,unsigned int count)8375957ba3STom Herbert void dql_completed(struct dql *dql, unsigned int count)
8475957ba3STom Herbert {
8575957ba3STom Herbert 	unsigned int inprogress, prev_inprogress, limit;
86914bec10SHiroaki SHIMODA 	unsigned int ovlimit, completed, num_queued;
874ba67ef3SBreno Leitao 	unsigned short stall_thrs;
8825426b79SHiroaki SHIMODA 	bool all_prev_completed;
8975957ba3STom Herbert 
906aa7de05SMark Rutland 	num_queued = READ_ONCE(dql->num_queued);
914ba67ef3SBreno Leitao 	/* Read stall_thrs in advance since it belongs to the same (first)
924ba67ef3SBreno Leitao 	 * cache line as ->num_queued. This way, dql_check_stall() does not
934ba67ef3SBreno Leitao 	 * need to touch the first cache line again later, reducing the window
944ba67ef3SBreno Leitao 	 * of possible false sharing.
954ba67ef3SBreno Leitao 	 */
964ba67ef3SBreno Leitao 	stall_thrs = READ_ONCE(dql->stall_thrs);
97914bec10SHiroaki SHIMODA 
9875957ba3STom Herbert 	/* Can't complete more than what's in queue */
99914bec10SHiroaki SHIMODA 	BUG_ON(count > num_queued - dql->num_completed);
10075957ba3STom Herbert 
10175957ba3STom Herbert 	completed = dql->num_completed + count;
10275957ba3STom Herbert 	limit = dql->limit;
103914bec10SHiroaki SHIMODA 	ovlimit = POSDIFF(num_queued - dql->num_completed, limit);
104914bec10SHiroaki SHIMODA 	inprogress = num_queued - completed;
10575957ba3STom Herbert 	prev_inprogress = dql->prev_num_queued - dql->num_completed;
10625426b79SHiroaki SHIMODA 	all_prev_completed = AFTER_EQ(completed, dql->prev_num_queued);
10775957ba3STom Herbert 
10875957ba3STom Herbert 	if ((ovlimit && !inprogress) ||
10975957ba3STom Herbert 	    (dql->prev_ovlimit && all_prev_completed)) {
11075957ba3STom Herbert 		/*
11175957ba3STom Herbert 		 * Queue considered starved if:
11275957ba3STom Herbert 		 *   - The queue was over-limit in the last interval,
11375957ba3STom Herbert 		 *     and there is no more data in the queue.
11475957ba3STom Herbert 		 *  OR
11575957ba3STom Herbert 		 *   - The queue was over-limit in the previous interval and
11675957ba3STom Herbert 		 *     when enqueuing it was possible that all queued data
11775957ba3STom Herbert 		 *     had been consumed.  This covers the case when queue
11875957ba3STom Herbert 		 *     may have becomes starved between completion processing
11975957ba3STom Herbert 		 *     running and next time enqueue was scheduled.
12075957ba3STom Herbert 		 *
12175957ba3STom Herbert 		 *     When queue is starved increase the limit by the amount
12275957ba3STom Herbert 		 *     of bytes both sent and completed in the last interval,
12375957ba3STom Herbert 		 *     plus any previous over-limit.
12475957ba3STom Herbert 		 */
12575957ba3STom Herbert 		limit += POSDIFF(completed, dql->prev_num_queued) +
12675957ba3STom Herbert 		     dql->prev_ovlimit;
12775957ba3STom Herbert 		dql->slack_start_time = jiffies;
12875957ba3STom Herbert 		dql->lowest_slack = UINT_MAX;
12975957ba3STom Herbert 	} else if (inprogress && prev_inprogress && !all_prev_completed) {
13075957ba3STom Herbert 		/*
13175957ba3STom Herbert 		 * Queue was not starved, check if the limit can be decreased.
13275957ba3STom Herbert 		 * A decrease is only considered if the queue has been busy in
13375957ba3STom Herbert 		 * the whole interval (the check above).
13475957ba3STom Herbert 		 *
135dde57fe0SRandy Dunlap 		 * If there is slack, the amount of excess data queued above
136dde57fe0SRandy Dunlap 		 * the amount needed to prevent starvation, the queue limit
13775957ba3STom Herbert 		 * can be decreased.  To avoid hysteresis we consider the
13875957ba3STom Herbert 		 * minimum amount of slack found over several iterations of the
13975957ba3STom Herbert 		 * completion routine.
14075957ba3STom Herbert 		 */
14175957ba3STom Herbert 		unsigned int slack, slack_last_objs;
14275957ba3STom Herbert 
14375957ba3STom Herbert 		/*
14475957ba3STom Herbert 		 * Slack is the maximum of
14575957ba3STom Herbert 		 *   - The queue limit plus previous over-limit minus twice
14675957ba3STom Herbert 		 *     the number of objects completed.  Note that two times
14775957ba3STom Herbert 		 *     number of completed bytes is a basis for an upper bound
14875957ba3STom Herbert 		 *     of the limit.
14975957ba3STom Herbert 		 *   - Portion of objects in the last queuing operation that
15075957ba3STom Herbert 		 *     was not part of non-zero previous over-limit.  That is
15175957ba3STom Herbert 		 *     "round down" by non-overlimit portion of the last
15275957ba3STom Herbert 		 *     queueing operation.
15375957ba3STom Herbert 		 */
15475957ba3STom Herbert 		slack = POSDIFF(limit + dql->prev_ovlimit,
15575957ba3STom Herbert 		    2 * (completed - dql->num_completed));
15675957ba3STom Herbert 		slack_last_objs = dql->prev_ovlimit ?
15775957ba3STom Herbert 		    POSDIFF(dql->prev_last_obj_cnt, dql->prev_ovlimit) : 0;
15875957ba3STom Herbert 
15975957ba3STom Herbert 		slack = max(slack, slack_last_objs);
16075957ba3STom Herbert 
16175957ba3STom Herbert 		if (slack < dql->lowest_slack)
16275957ba3STom Herbert 			dql->lowest_slack = slack;
16375957ba3STom Herbert 
16475957ba3STom Herbert 		if (time_after(jiffies,
16575957ba3STom Herbert 			       dql->slack_start_time + dql->slack_hold_time)) {
16675957ba3STom Herbert 			limit = POSDIFF(limit, dql->lowest_slack);
16775957ba3STom Herbert 			dql->slack_start_time = jiffies;
16875957ba3STom Herbert 			dql->lowest_slack = UINT_MAX;
16975957ba3STom Herbert 		}
17075957ba3STom Herbert 	}
17175957ba3STom Herbert 
17275957ba3STom Herbert 	/* Enforce bounds on limit */
17375957ba3STom Herbert 	limit = clamp(limit, dql->min_limit, dql->max_limit);
17475957ba3STom Herbert 
17575957ba3STom Herbert 	if (limit != dql->limit) {
17675957ba3STom Herbert 		dql->limit = limit;
17775957ba3STom Herbert 		ovlimit = 0;
17875957ba3STom Herbert 	}
17975957ba3STom Herbert 
18075957ba3STom Herbert 	dql->adj_limit = limit + completed;
18175957ba3STom Herbert 	dql->prev_ovlimit = ovlimit;
182a911bad0SEric Dumazet 	dql->prev_last_obj_cnt = READ_ONCE(dql->last_obj_cnt);
18375957ba3STom Herbert 	dql->num_completed = completed;
184914bec10SHiroaki SHIMODA 	dql->prev_num_queued = num_queued;
1856025b913SJakub Kicinski 
1864ba67ef3SBreno Leitao 	dql_check_stall(dql, stall_thrs);
18775957ba3STom Herbert }
18875957ba3STom Herbert EXPORT_SYMBOL(dql_completed);
18975957ba3STom Herbert 
dql_reset(struct dql * dql)19075957ba3STom Herbert void dql_reset(struct dql *dql)
19175957ba3STom Herbert {
19275957ba3STom Herbert 	/* Reset all dynamic values */
193*3a17f23fSJing Su 	dql->limit = dql->min_limit;
19475957ba3STom Herbert 	dql->num_queued = 0;
19575957ba3STom Herbert 	dql->num_completed = 0;
19675957ba3STom Herbert 	dql->last_obj_cnt = 0;
19775957ba3STom Herbert 	dql->prev_num_queued = 0;
19875957ba3STom Herbert 	dql->prev_last_obj_cnt = 0;
19975957ba3STom Herbert 	dql->prev_ovlimit = 0;
20075957ba3STom Herbert 	dql->lowest_slack = UINT_MAX;
20175957ba3STom Herbert 	dql->slack_start_time = jiffies;
2026025b913SJakub Kicinski 
2036025b913SJakub Kicinski 	dql->last_reap = jiffies;
2046025b913SJakub Kicinski 	dql->history_head = jiffies / BITS_PER_LONG;
2056025b913SJakub Kicinski 	memset(dql->history, 0, sizeof(dql->history));
20675957ba3STom Herbert }
20775957ba3STom Herbert EXPORT_SYMBOL(dql_reset);
20875957ba3STom Herbert 
dql_init(struct dql * dql,unsigned int hold_time)2097a0947e7SStephen Hemminger void dql_init(struct dql *dql, unsigned int hold_time)
21075957ba3STom Herbert {
21175957ba3STom Herbert 	dql->max_limit = DQL_MAX_LIMIT;
21275957ba3STom Herbert 	dql->min_limit = 0;
21375957ba3STom Herbert 	dql->slack_hold_time = hold_time;
2146025b913SJakub Kicinski 	dql->stall_thrs = 0;
21575957ba3STom Herbert 	dql_reset(dql);
21675957ba3STom Herbert }
21775957ba3STom Herbert EXPORT_SYMBOL(dql_init);
218