git: 0b6b1c285920 - main - Add rangelock_may_recurse(9)

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Wed, 28 Aug 2024 14:49:57 UTC
The branch main has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=0b6b1c285920563ba0c119d3190ac25af4731d02

commit 0b6b1c285920563ba0c119d3190ac25af4731d02
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2024-08-27 21:33:38 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2024-08-28 14:33:58 +0000

    Add rangelock_may_recurse(9)
    
    Reviewed by:    markj
    Tested by:      lwhsu
    Sponsored by:   The FreeBSD Foundation
    Differential revision:  https://reviews.freebsd.org/D46465
---
 sys/kern/kern_rangelock.c | 41 +++++++++++++++++++++++++++++++++++++++++
 sys/sys/rangelock.h       |  1 +
 2 files changed, 42 insertions(+)

diff --git a/sys/kern/kern_rangelock.c b/sys/kern/kern_rangelock.c
index 0e62b91b4ee7..4d74c02302e7 100644
--- a/sys/kern/kern_rangelock.c
+++ b/sys/kern/kern_rangelock.c
@@ -752,6 +752,47 @@ rangelock_trywlock(struct rangelock *lock, vm_ooffset_t start, vm_ooffset_t end)
 	return (rangelock_lock_int(lock, true, start, end, RL_LOCK_WRITE));
 }
 
+/*
+ * If the caller asserts that it can obtain the range locks on the
+ * same lock simultaneously, switch to the non-cheat mode.  Cheat mode
+ * cannot handle it, hanging in drain or trylock retries.
+ */
+void
+rangelock_may_recurse(struct rangelock *lock)
+{
+	uintptr_t v, x;
+
+	v = atomic_load_ptr(&lock->head);
+	if ((v & RL_CHEAT_CHEATING) == 0)
+		return;
+
+	sleepq_lock(&lock->head);
+	for (;;) {
+		if ((v & RL_CHEAT_CHEATING) == 0) {
+			sleepq_release(&lock->head);
+			return;
+		}
+
+		/* Cheating and locked, drain. */
+		if ((v & RL_CHEAT_WLOCKED) != 0 ||
+		    (v & ~RL_CHEAT_MASK) >= RL_CHEAT_READER) {
+			x = v | RL_CHEAT_DRAINING;
+			if (atomic_fcmpset_ptr(&lock->head, &v, x) != 0) {
+				rangelock_cheat_drain(lock);
+				return;
+			}
+			continue;
+		}
+
+		/* Cheating and unlocked, clear RL_CHEAT_CHEATING. */
+		x = 0;
+		if (atomic_fcmpset_ptr(&lock->head, &v, x) != 0) {
+			sleepq_release(&lock->head);
+			return;
+		}
+	}
+}
+
 #ifdef INVARIANT_SUPPORT
 void
 _rangelock_cookie_assert(void *cookie, int what, const char *file, int line)
diff --git a/sys/sys/rangelock.h b/sys/sys/rangelock.h
index 127f101ddc2e..accf33d7296b 100644
--- a/sys/sys/rangelock.h
+++ b/sys/sys/rangelock.h
@@ -66,6 +66,7 @@ void	*rangelock_wlock(struct rangelock *lock, vm_ooffset_t start,
 void	*rangelock_trywlock(struct rangelock *lock, vm_ooffset_t start,
     vm_ooffset_t end);
 void	 rangelock_entry_free(struct rl_q_entry *e);
+void	rangelock_may_recurse(struct rangelock *lock);
 #if defined(INVARIANTS) || defined(INVARIANT_SUPPORT)
 void	_rangelock_cookie_assert(void *cookie, int what, const char *file,
     int line);