Linux block layer
 help / color / mirror / Atom feed
From: Keith Busch <kbusch@meta.com>
To: <linux-block@vger.kernel.org>
Cc: <linux-scsi@vger.kernel.org>, <axboe@kernel.dk>, <hch@lst.de>,
	<bvanassche@acm.org>, <sumit.saxena@broadcom.com>,
	Keith Busch <kbusch@kernel.org>
Subject: [RFC PATCH 1/6] lib/sbitmap: add ranged allocation, bounded wakeup relay, and ranged weight
Date: Mon, 6 Jul 2026 10:34:33 -0700	[thread overview]
Message-ID: <20260706173438.3537347-2-kbusch@meta.com> (raw)
In-Reply-To: <20260706173438.3537347-1-kbusch@meta.com>

From: Keith Busch <kbusch@kernel.org>

Add some helpers that let a caller carve a shared bitmap into per-user
windows.

  * sbitmap_queue_get_range() allocates a free bit only within requested range.
  * sbitmap_queue_wake_up_relay() hands a wakeup to another waiter when the
    woken one cannot use the freed bit (it fell outside that waiter's window).
  * sbitmap_weight_range() counts set (and not cleared) bits in a sub-range.

No callers yet. The intention is for blk-mq users for shared tag
fairness without requiring per-io atomic accounting operations.

Signed-off-by: Keith Busch <kbusch@kernel.org>
---
 include/linux/sbitmap.h |  53 +++++++++++
 lib/sbitmap.c           | 200 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 253 insertions(+)

diff --git a/include/linux/sbitmap.h b/include/linux/sbitmap.h
index cc7ad189caa5c..9fceda5d9de51 100644
--- a/include/linux/sbitmap.h
+++ b/include/linux/sbitmap.h
@@ -86,6 +86,7 @@ struct sbitmap {
 
 #define SBQ_WAIT_QUEUES 8
 #define SBQ_WAKE_BATCH 8
+#define SBQ_RELAY_BUDGET SBQ_WAKE_BATCH
 
 /**
  * struct sbq_wait_state - Wait queue in a &struct sbitmap_queue.
@@ -149,6 +150,14 @@ struct sbitmap_queue {
 	 * @wakeup_cnt: Number of thread wake ups issued.
 	 */
 	atomic_t wakeup_cnt;
+
+	/**
+	 * @relay_credits: Remaining range-relay wakeups allowed before the next
+	 * genuine completion. Refilled by sbitmap_queue_wake_up() and consumed
+	 * by sbitmap_queue_wake_up_relay(); bounds the relay chain so it cannot
+	 * cycle when no waiter can use a freed bit.
+	 */
+	atomic_t relay_credits;
 };
 
 /**
@@ -375,6 +384,18 @@ void sbitmap_show(struct sbitmap *sb, struct seq_file *m);
  */
 unsigned int sbitmap_weight(const struct sbitmap *sb);
 
+/**
+ * sbitmap_weight_range() - Return how many set and not cleared bits in a
+ * sub-range of a &struct sbitmap.
+ * @sb: Bitmap to check.
+ * @off: First bit of the range.
+ * @len: Number of bits in the range.
+ *
+ * Return: How many bits in [@off, @off + @len) are set and not cleared.
+ */
+unsigned int sbitmap_weight_range(const struct sbitmap *sb, unsigned int off,
+				  unsigned int len);
+
 /**
  * sbitmap_bitmap_show() - Write a hex dump of a &struct sbitmap to a &struct
  * seq_file.
@@ -472,6 +493,22 @@ unsigned long __sbitmap_queue_get_batch(struct sbitmap_queue *sbq, int nr_tags,
 int sbitmap_queue_get_shallow(struct sbitmap_queue *sbq,
 			      unsigned int shallow_depth);
 
+/**
+ * sbitmap_queue_get_range() - Try to allocate a free bit from a restricted
+ * range of a &struct sbitmap_queue, with preemption already disabled.
+ * @sbq: Bitmap queue to allocate from.
+ * @min: First bit (inclusive) the caller is allowed to allocate.
+ * @max: Last bit (exclusive) the caller is allowed to allocate.
+ *
+ * Allocation is confined to the [@min, @max) window. This lets several users
+ * share one bitmap while each is bounded to its own (possibly overlapping)
+ * window, providing fairness without a separate per-user accounting counter.
+ *
+ * Return: Non-negative allocated bit number if successful, -1 otherwise.
+ */
+int sbitmap_queue_get_range(struct sbitmap_queue *sbq, unsigned int min,
+			    unsigned int max);
+
 /**
  * sbitmap_queue_get() - Try to allocate a free bit from a &struct
  * sbitmap_queue.
@@ -575,6 +612,22 @@ void sbitmap_queue_wake_all(struct sbitmap_queue *sbq);
  */
 void sbitmap_queue_wake_up(struct sbitmap_queue *sbq, int nr);
 
+/**
+ * sbitmap_queue_wake_up_relay() - Relay a wakeup to the next waiter.
+ * @sbq: Bitmap queue to wake up.
+ * @cur: The relaying waiter's own wait queue, skipped so it does not wake
+ *       itself (it is about to sleep again); may be NULL.
+ *
+ * Wake a single waiter on the next active wait queue without touching the
+ * wake_batch accounting. Intended for range-limited allocation: a waiter that
+ * was woken but could not use the freed bit (it fell outside the waiter's
+ * allowed range) calls this to pass the wakeup along to another waiter that
+ * may be able to use it. The relay is bounded by a credit budget refilled
+ * only by genuine completions, so it cannot cycle indefinitely.
+ */
+void sbitmap_queue_wake_up_relay(struct sbitmap_queue *sbq,
+				 struct sbq_wait_state *cur);
+
 /**
  * sbitmap_queue_show() - Dump &struct sbitmap_queue information to a &struct
  * seq_file.
diff --git a/lib/sbitmap.c b/lib/sbitmap.c
index 4d188d05db153..3264085b4d533 100644
--- a/lib/sbitmap.c
+++ b/lib/sbitmap.c
@@ -338,6 +338,130 @@ static int sbitmap_get_shallow(struct sbitmap *sb, unsigned long shallow_depth)
 	return nr;
 }
 
+/*
+ * Find and set a free bit in the [low, high) sub-range of a single word.
+ * Wraps back to @low once so a free bit below @hint is still found.
+ */
+static int __sbitmap_get_word_range(unsigned long *word, unsigned int low,
+				    unsigned int high, unsigned int hint)
+{
+	bool wrap;
+	int nr;
+
+	if (hint < low || hint >= high)
+		hint = low;
+	/* don't wrap if already starting from the bottom of the range */
+	wrap = hint > low;
+
+	while (1) {
+		nr = find_next_zero_bit(word, high, hint);
+		if (unlikely(nr >= high)) {
+			if (wrap) {
+				hint = low;
+				wrap = false;
+				continue;
+			}
+			return -1;
+		}
+		if (!test_and_set_bit_lock(nr, word))
+			break;
+		hint = nr + 1;
+		if (hint >= high) {
+			if (!wrap)
+				return -1;
+			hint = low;
+			wrap = false;
+		}
+	}
+
+	return nr;
+}
+
+static int sbitmap_find_bit_in_word_range(struct sbitmap_word *map,
+					  unsigned int low, unsigned int high,
+					  unsigned int hint)
+{
+	int nr;
+
+	do {
+		nr = __sbitmap_get_word_range(&map->word, low, high, hint);
+		if (nr != -1)
+			break;
+		if (!sbitmap_deferred_clear(map, high, low, false))
+			break;
+	} while (1);
+
+	return nr;
+}
+
+/*
+ * Allocate a bit restricted to the [min, max) range of the bitmap. Used to
+ * carve the shared depth into (possibly overlapping) per-user windows without
+ * a separate accounting counter: a user simply cannot allocate outside its
+ * window.
+ */
+static int __sbitmap_get_range(struct sbitmap *sb, unsigned int min,
+			       unsigned int max, unsigned int hint)
+{
+	unsigned int min_index, max_index, index;
+	unsigned int nwords, i;
+	int nr = -1;
+
+	if (max > sb->depth)
+		max = sb->depth;
+	if (min >= max)
+		return -1;
+
+	min_index = SB_NR_TO_INDEX(sb, min);
+	max_index = SB_NR_TO_INDEX(sb, max - 1);
+	nwords = max_index - min_index + 1;
+
+	if (hint < min || hint >= max)
+		hint = min;
+	index = SB_NR_TO_INDEX(sb, hint);
+
+	for (i = 0; i < nwords; i++) {
+		unsigned int word_base = index << sb->shift;
+		unsigned int word_depth = __map_depth(sb, index);
+		unsigned int low, high, h;
+
+		low = (index == min_index) ? SB_NR_TO_BIT(sb, min) : 0;
+		high = (index == max_index) ? SB_NR_TO_BIT(sb, max - 1) + 1 :
+					      word_depth;
+		h = (hint >= word_base && hint < word_base + word_depth) ?
+			SB_NR_TO_BIT(sb, hint) : low;
+
+		nr = sbitmap_find_bit_in_word_range(&sb->map[index], low,
+						    high, h);
+		if (nr != -1) {
+			nr += word_base;
+			break;
+		}
+
+		if (++index > max_index)
+			index = min_index;
+	}
+
+	return nr;
+}
+
+static int sbitmap_get_range(struct sbitmap *sb, unsigned int min,
+			     unsigned int max)
+{
+	int nr;
+	unsigned int hint, depth;
+
+	if (WARN_ON_ONCE(unlikely(!sb->alloc_hint)))
+		return -1;
+
+	depth = READ_ONCE(sb->depth);
+	hint = update_alloc_hint_before_get(sb, depth);
+	nr = __sbitmap_get_range(sb, min, max, hint);
+	update_alloc_hint_after_get(sb, depth, hint, nr);
+
+	return nr;
+}
+
 bool sbitmap_any_bit_set(const struct sbitmap *sb)
 {
 	unsigned int i;
@@ -377,6 +501,38 @@ unsigned int sbitmap_weight(const struct sbitmap *sb)
 }
 EXPORT_SYMBOL_GPL(sbitmap_weight);
 
+unsigned int sbitmap_weight_range(const struct sbitmap *sb, unsigned int off,
+				  unsigned int len)
+{
+	unsigned int weight = 0;
+	unsigned int end = off + len;
+	unsigned int index;
+
+	if (end > sb->depth)
+		end = sb->depth;
+
+	for (index = SB_NR_TO_INDEX(sb, off); index < sb->map_nr; index++) {
+		const struct sbitmap_word *word = &sb->map[index];
+		unsigned int base = index << sb->shift;
+		unsigned int wbits = __map_depth(sb, index);
+		unsigned int lo, hi;
+		unsigned long val;
+
+		if (base >= end)
+			break;
+		val = READ_ONCE(word->word) & ~READ_ONCE(word->cleared);
+		lo = (off > base) ? off - base : 0;
+		hi = (end - base < wbits) ? end - base : wbits;
+		if (lo)
+			val &= ~((1UL << lo) - 1);
+		if (hi < BITS_PER_LONG)
+			val &= (1UL << hi) - 1;
+		weight += hweight_long(val);
+	}
+	return weight;
+}
+EXPORT_SYMBOL_GPL(sbitmap_weight_range);
+
 void sbitmap_show(struct sbitmap *sb, struct seq_file *m)
 {
 	seq_printf(m, "depth=%u\n", sb->depth);
@@ -462,6 +618,7 @@ int sbitmap_queue_init_node(struct sbitmap_queue *sbq, unsigned int depth,
 	atomic_set(&sbq->ws_active, 0);
 	atomic_set(&sbq->completion_cnt, 0);
 	atomic_set(&sbq->wakeup_cnt, 0);
+	atomic_set(&sbq->relay_credits, 0);
 
 	sbq->ws = kzalloc_node(SBQ_WAIT_QUEUES * sizeof(*sbq->ws), flags, node);
 	if (!sbq->ws) {
@@ -573,6 +730,13 @@ int sbitmap_queue_get_shallow(struct sbitmap_queue *sbq,
 }
 EXPORT_SYMBOL_GPL(sbitmap_queue_get_shallow);
 
+int sbitmap_queue_get_range(struct sbitmap_queue *sbq, unsigned int min,
+			    unsigned int max)
+{
+	return sbitmap_get_range(&sbq->sb, min, max);
+}
+EXPORT_SYMBOL_GPL(sbitmap_queue_get_range);
+
 void sbitmap_queue_min_shallow_depth(struct sbitmap_queue *sbq,
 				     unsigned int min_shallow_depth)
 {
@@ -629,10 +793,46 @@ void sbitmap_queue_wake_up(struct sbitmap_queue *sbq, int nr)
 	} while (!atomic_try_cmpxchg(&sbq->wakeup_cnt,
 				     &wakeups, wakeups + wake_batch));
 
+	atomic_set(&sbq->relay_credits, SBQ_RELAY_BUDGET);
 	__sbitmap_queue_wake_up(sbq, wake_batch);
 }
 EXPORT_SYMBOL_GPL(sbitmap_queue_wake_up);
 
+/*
+ * Wake a single waiter on the next active wait queue, bypassing the
+ * wake_batch accounting (no bit was freed). This "relays" a wakeup that the
+ * current waiter consumed but could not use - e.g. the freed bit fell outside
+ * its allowed allocation range - so a waiter that *can* use a free bit gets a
+ * chance to run. Relaying continues, one hop per failed waiter, until a usable
+ * waiter is found or the chain credits drain.
+ */
+void sbitmap_queue_wake_up_relay(struct sbitmap_queue *sbq,
+				 struct sbq_wait_state *cur)
+{
+	int wake_index, i;
+
+	if (!atomic_read(&sbq->ws_active))
+		return;
+
+	if (atomic_dec_if_positive(&sbq->relay_credits) < 0)
+		return;
+
+	wake_index = atomic_read(&sbq->wake_index);
+	for (i = 0; i < SBQ_WAIT_QUEUES; i++) {
+		struct sbq_wait_state *ws = &sbq->ws[wake_index];
+
+		wake_index = sbq_index_inc(wake_index);
+		if (ws != cur && waitqueue_active(&ws->wait)) {
+			wake_up_nr(&ws->wait, 1);
+			break;
+		}
+	}
+
+	if (wake_index != atomic_read(&sbq->wake_index))
+		atomic_set(&sbq->wake_index, wake_index);
+}
+EXPORT_SYMBOL_GPL(sbitmap_queue_wake_up_relay);
+
 static inline void sbitmap_update_cpu_hint(struct sbitmap *sb, int cpu, int tag)
 {
 	if (likely(!sb->round_robin && tag < sb->depth))
-- 
2.52.0


  reply	other threads:[~2026-07-06 17:35 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-06 17:34 [RFC PATCH 0/6] sbitmap enforced fairness for blk-mq Keith Busch
2026-07-06 17:34 ` Keith Busch [this message]
2026-07-06 17:34 ` [RFC PATCH 2/6] blk-mq: replace shared-tag fairness counter with allocation windows Keith Busch
2026-07-06 17:34 ` [RFC PATCH 3/6] blk-mq: factor out a per-hctx tag busy iterator Keith Busch
2026-07-06 17:34 ` [RFC PATCH 4/6] blk-mq: add a shared zone to tag fairness Keith Busch
2026-07-06 17:34 ` [RFC PATCH 5/6] blk-mq: cache shared-tag fairness windows Keith Busch
2026-07-06 17:34 ` [RFC PATCH 6/6] scsi: add shared-tag fairness to host_tagset drivers Keith Busch
2026-07-06 17:56 ` [RFC PATCH 0/6] sbitmap enforced fairness for blk-mq Bart Van Assche
2026-07-06 18:26   ` Keith Busch

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260706173438.3537347-2-kbusch@meta.com \
    --to=kbusch@meta.com \
    --cc=axboe@kernel.dk \
    --cc=bvanassche@acm.org \
    --cc=hch@lst.de \
    --cc=kbusch@kernel.org \
    --cc=linux-block@vger.kernel.org \
    --cc=linux-scsi@vger.kernel.org \
    --cc=sumit.saxena@broadcom.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox