Module Name: src
Committed By: riastradh
Date: Sun Dec 19 11:03:18 UTC 2021
Modified Files:
src/sys/external/bsd/common/include/linux: tasklet.h
src/sys/external/bsd/common/linux: linux_tasklet.c
Log Message:
Rework linux_tasklet.c a little.
Provide some more hacks for i915's grubby paws.
To generate a diff of this commit:
cvs rdiff -u -r1.4 -r1.5 src/sys/external/bsd/common/include/linux/tasklet.h
cvs rdiff -u -r1.4 -r1.5 src/sys/external/bsd/common/linux/linux_tasklet.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/sys/external/bsd/common/include/linux/tasklet.h
diff -u src/sys/external/bsd/common/include/linux/tasklet.h:1.4 src/sys/external/bsd/common/include/linux/tasklet.h:1.5
--- src/sys/external/bsd/common/include/linux/tasklet.h:1.4 Sun Dec 19 01:17:46 2021
+++ src/sys/external/bsd/common/include/linux/tasklet.h Sun Dec 19 11:03:17 2021
@@ -1,7 +1,7 @@
-/* $NetBSD: tasklet.h,v 1.4 2021/12/19 01:17:46 riastradh Exp $ */
+/* $NetBSD: tasklet.h,v 1.5 2021/12/19 11:03:17 riastradh Exp $ */
/*-
- * Copyright (c) 2018 The NetBSD Foundation, Inc.
+ * Copyright (c) 2018, 2020 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
@@ -33,16 +33,22 @@
#define _LINUX_TASKLET_H_
/* namespace */
+#define __tasklet_disable_sync_once linux___tasklet_disable_sync_once
+#define __tasklet_enable linux___tasklet_enable
+#define __tasklet_enable_sync_once linux___tasklet_enable_sync_once
+#define __tasklet_is_enabled linux___tasklet_is_enabled
+#define __tasklet_is_scheduled linux___tasklet_is_scheduled
#define tasklet_disable linux_tasklet_disable
-#define tasklet_disable_sync_once linux_tasklet_disable_sync_once
#define tasklet_enable linux_tasklet_enable
-#define tasklet_enable_sync_once linux_tasklet_enable_sync_once
#define tasklet_hi_schedule linux_tasklet_hi_schedule
#define tasklet_init linux_tasklet_init
-#define tasklet_is_enabled linux_tasklet_is_enabled
+#define tasklet_is_locked linux_tasklet_is_locked
#define tasklet_kill linux_tasklet_kill
#define tasklet_schedule linux_tasklet_schedule
#define tasklet_struct linux_tasklet_struct
+#define tasklet_trylock linux_tasklet_trylock
+#define tasklet_unlock linux_tasklet_unlock
+#define tasklet_unlock_wait linux_tasklet_unlock_wait
struct tasklet_struct {
SIMPLEQ_ENTRY(tasklet_struct) tl_entry;
@@ -81,9 +87,16 @@ void tasklet_schedule(struct tasklet_str
void tasklet_hi_schedule(struct tasklet_struct *);
void tasklet_kill(struct tasklet_struct *);
-/* i915drmkms hack */
-void tasklet_disable_sync_once(struct tasklet_struct *);
-void tasklet_enable_sync_once(struct tasklet_struct *);
-bool tasklet_is_enabled(const struct tasklet_struct *);
+bool tasklet_is_locked(const struct tasklet_struct *);
+bool tasklet_trylock(struct tasklet_struct *);
+void tasklet_unlock(struct tasklet_struct *);
+void tasklet_unlock_wait(const struct tasklet_struct *);
+
+/* i915 hacks */
+void __tasklet_disable_sync_once(struct tasklet_struct *);
+void __tasklet_enable_sync_once(struct tasklet_struct *);
+bool __tasklet_is_enabled(const struct tasklet_struct *);
+bool __tasklet_is_scheduled(const struct tasklet_struct *);
+bool __tasklet_enable(struct tasklet_struct *);
#endif /* _LINUX_TASKLET_H_ */
Index: src/sys/external/bsd/common/linux/linux_tasklet.c
diff -u src/sys/external/bsd/common/linux/linux_tasklet.c:1.4 src/sys/external/bsd/common/linux/linux_tasklet.c:1.5
--- src/sys/external/bsd/common/linux/linux_tasklet.c:1.4 Sun Dec 19 01:46:01 2021
+++ src/sys/external/bsd/common/linux/linux_tasklet.c Sun Dec 19 11:03:18 2021
@@ -1,7 +1,7 @@
-/* $NetBSD: linux_tasklet.c,v 1.4 2021/12/19 01:46:01 riastradh Exp $ */
+/* $NetBSD: linux_tasklet.c,v 1.5 2021/12/19 11:03:18 riastradh Exp $ */
/*-
- * Copyright (c) 2018 The NetBSD Foundation, Inc.
+ * Copyright (c) 2018, 2020 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
@@ -30,7 +30,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: linux_tasklet.c,v 1.4 2021/12/19 01:46:01 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: linux_tasklet.c,v 1.5 2021/12/19 11:03:18 riastradh Exp $");
#include <sys/types.h>
#include <sys/atomic.h>
@@ -195,27 +195,20 @@ tasklet_softintr(void *cookie)
/* Go through the queue of tasklets we grabbed. */
while (!SIMPLEQ_EMPTY(&th)) {
struct tasklet_struct *tasklet;
- unsigned state;
/* Remove the first tasklet from the queue. */
tasklet = SIMPLEQ_FIRST(&th);
SIMPLEQ_REMOVE_HEAD(&th, tl_entry);
+ KASSERT(atomic_load_relaxed(&tasklet->tl_state) &
+ TASKLET_SCHEDULED);
+
/*
* Test and set RUNNING, in case it is already running
* on another CPU and got scheduled again on this one
* before it completed.
*/
- do {
- state = tasklet->tl_state;
- /* It had better be scheduled. */
- KASSERT(state & TASKLET_SCHEDULED);
- if (state & TASKLET_RUNNING)
- break;
- } while (atomic_cas_uint(&tasklet->tl_state, state,
- state | TASKLET_RUNNING) != state);
-
- if (state & TASKLET_RUNNING) {
+ if (!tasklet_trylock(tasklet)) {
/*
* Put it back on the queue to run it again in
* a sort of busy-wait, and move on to the next
@@ -225,35 +218,30 @@ tasklet_softintr(void *cookie)
continue;
}
- /* Wait for last runner's side effects. */
- membar_enter();
-
- /* Check whether it's currently disabled. */
- if (tasklet->tl_disablecount) {
+ /*
+ * Check whether it's currently disabled.
+ *
+ * Pairs with membar_exit in __tasklet_enable.
+ */
+ if (atomic_load_acquire(&tasklet->tl_disablecount)) {
/*
* Disabled: clear the RUNNING bit and, requeue
* it, but keep it SCHEDULED.
*/
- KASSERT(tasklet->tl_state & TASKLET_RUNNING);
- atomic_and_uint(&tasklet->tl_state, ~TASKLET_RUNNING);
+ tasklet_unlock(tasklet);
tasklet_queue_enqueue(tq, tasklet);
continue;
}
/* Not disabled. Clear SCHEDULED and call func. */
- KASSERT(tasklet->tl_state & TASKLET_SCHEDULED);
+ KASSERT(atomic_load_relaxed(&tasklet->tl_state) &
+ TASKLET_SCHEDULED);
atomic_and_uint(&tasklet->tl_state, ~TASKLET_SCHEDULED);
(*tasklet->func)(tasklet->data);
- /*
- * Guarantee all caller-relevant reads or writes in
- * func have completed before clearing RUNNING bit.
- */
- membar_exit();
-
/* Clear RUNNING to notify tasklet_disable. */
- atomic_and_uint(&tasklet->tl_state, ~TASKLET_RUNNING);
+ tasklet_unlock(tasklet);
}
}
@@ -271,7 +259,7 @@ tasklet_queue_schedule(struct tasklet_qu
/* Test and set the SCHEDULED bit. If already set, we're done. */
do {
- ostate = tasklet->tl_state;
+ ostate = atomic_load_relaxed(&tasklet->tl_state);
if (ostate & TASKLET_SCHEDULED)
return;
nstate = ostate | TASKLET_SCHEDULED;
@@ -297,7 +285,7 @@ tasklet_queue_enqueue(struct tasklet_que
struct tasklet_cpu *tc;
int s;
- KASSERT(tasklet->tl_state & TASKLET_SCHEDULED);
+ KASSERT(atomic_load_relaxed(&tasklet->tl_state) & TASKLET_SCHEDULED);
/*
* Insert on the current CPU's queue while all interrupts are
@@ -326,8 +314,8 @@ tasklet_init(struct tasklet_struct *task
unsigned long data)
{
- tasklet->tl_state = 0;
- tasklet->tl_disablecount = 0;
+ atomic_store_relaxed(&tasklet->tl_state, 0);
+ atomic_store_relaxed(&tasklet->tl_disablecount, 0);
tasklet->func = func;
tasklet->data = data;
}
@@ -372,6 +360,8 @@ tasklet_hi_schedule(struct tasklet_struc
* If tasklet is guaranteed not to be scheduled, e.g. if you have
* just invoked tasklet_kill, then tasklet_disable serves to wait
* for it to complete in case it might already be running.
+ *
+ * Load-acquire semantics.
*/
void
tasklet_disable(struct tasklet_struct *tasklet)
@@ -384,21 +374,7 @@ tasklet_disable(struct tasklet_struct *t
KASSERT(disablecount != 0);
/* Wait for it to finish running, if it was running. */
- while (tasklet->tl_state & TASKLET_RUNNING)
- SPINLOCK_BACKOFF_HOOK;
-
- /*
- * Guarantee any side effects of running are visible to us
- * before we return.
- *
- * XXX membar_sync is overkill here. It is tempting to issue
- * membar_enter, but it only orders stores | loads, stores;
- * what we really want here is load_acquire(&tasklet->tl_state)
- * above, i.e. to witness all side effects preceding the store
- * whose value we loaded. Absent that, membar_sync is the best
- * we can do.
- */
- membar_sync();
+ tasklet_unlock_wait(tasklet);
}
/*
@@ -406,22 +382,14 @@ tasklet_disable(struct tasklet_struct *t
*
* Decrement tasklet's disable count. If it was previously
* scheduled to run, it may now run.
+ *
+ * Store-release semantics.
*/
void
tasklet_enable(struct tasklet_struct *tasklet)
{
- unsigned int disablecount __diagused;
- /*
- * Guarantee all caller-relevant reads or writes have completed
- * before potentially allowing tasklet to run again by
- * decrementing the disable count.
- */
- membar_exit();
-
- /* Decrement the disable count. */
- disablecount = atomic_dec_uint_nv(&tasklet->tl_disablecount);
- KASSERT(disablecount != UINT_MAX);
+ (void)__tasklet_enable(tasklet);
}
/*
@@ -439,7 +407,7 @@ tasklet_kill(struct tasklet_struct *task
"deadlock: soft interrupts are blocked in interrupt context");
/* Wait for it to be removed from the queue. */
- while (tasklet->tl_state & TASKLET_SCHEDULED)
+ while (atomic_load_relaxed(&tasklet->tl_state) & TASKLET_SCHEDULED)
SPINLOCK_BACKOFF_HOOK;
/*
@@ -459,19 +427,94 @@ tasklet_kill(struct tasklet_struct *task
*/
/* Wait for it to finish running. */
- while (tasklet->tl_state & TASKLET_RUNNING)
- SPINLOCK_BACKOFF_HOOK;
+ tasklet_unlock_wait(tasklet);
+}
+
+/*
+ * tasklet_is_scheduled(tasklet)
+ *
+ * True if tasklet is currently locked. Caller must use it only
+ * for positive assertions.
+ */
+bool
+tasklet_is_locked(const struct tasklet_struct *tasklet)
+{
+
+ return atomic_load_relaxed(&tasklet->tl_state) & TASKLET_RUNNING;
+}
+
+/*
+ * tasklet_trylock(tasklet)
+ *
+ * Try to lock tasklet, i.e., set TASKLET_RUNNING. Return true if
+ * we locked it, false if already locked.
+ *
+ * Load-acquire semantics.
+ */
+bool
+tasklet_trylock(struct tasklet_struct *tasklet)
+{
+ unsigned state;
+
+ do {
+ /* Pairs with membar_exit in tasklet_unlock. */
+ state = atomic_load_acquire(&tasklet->tl_state);
+ if (state & TASKLET_RUNNING)
+ return false;
+ } while (atomic_cas_uint(&tasklet->tl_state, state,
+ state | TASKLET_RUNNING) != state);
+
+ return true;
+}
+
+/*
+ * tasklet_unlock(tasklet)
+ *
+ * Unlock tasklet, i.e., clear TASKLET_RUNNING.
+ *
+ * Store-release semantics.
+ */
+void
+tasklet_unlock(struct tasklet_struct *tasklet)
+{
+
+ KASSERT(atomic_load_relaxed(&tasklet->tl_state) & TASKLET_RUNNING);
/*
- * Wait for any side effects running. Again, membar_sync is
- * overkill; we really want load_acquire(&tasklet->tl_state)
- * here.
+ * Pairs with atomic_load_acquire in tasklet_trylock and
+ * tasklet_unlock.
*/
- membar_sync();
+#ifndef __HAVE_ATOMIC_AS_MEMBAR
+ membar_exit();
+#endif
+ atomic_and_uint(&tasklet->tl_state, ~TASKLET_RUNNING);
+}
+
+/*
+ * tasklet_unlock_wait(tasklet)
+ *
+ * Busy-wait until tasklet is not running.
+ *
+ * Load-acquire semantics.
+ */
+void
+tasklet_unlock_wait(const struct tasklet_struct *tasklet)
+{
+
+ /* Pairs with membar_exit in tasklet_unlock. */
+ while (atomic_load_acquire(&tasklet->tl_state) & TASKLET_RUNNING)
+ SPINLOCK_BACKOFF_HOOK;
}
/*
- * tasklet_disable_sync_once(tasklet)
+ * BEGIN I915 HACKS
+ *
+ * The i915 driver abuses the tasklet abstraction like a cop abuses his
+ * wife.
+ */
+
+/*
+ * __tasklet_disable_sync_once(tasklet)
*
* Increment the disable count of tasklet, and if this is the
* first time it was disabled and it was already running,
@@ -482,7 +525,7 @@ tasklet_kill(struct tasklet_struct *task
* if this was not the first time it was disabled.
*/
void
-tasklet_disable_sync_once(struct tasklet_struct *tasklet)
+__tasklet_disable_sync_once(struct tasklet_struct *tasklet)
{
unsigned int disablecount;
@@ -495,21 +538,18 @@ tasklet_disable_sync_once(struct tasklet
* If it was zero, wait for it to finish running. If it was
* not zero, caller must not care whether it was running.
*/
- if (disablecount == 1) {
- while (tasklet->tl_state & TASKLET_RUNNING)
- SPINLOCK_BACKOFF_HOOK;
- membar_sync();
- }
+ if (disablecount == 1)
+ tasklet_unlock_wait(tasklet);
}
/*
- * tasklet_enable_sync_once(tasklet)
+ * __tasklet_enable_sync_once(tasklet)
*
* Decrement the disable count of tasklet, and if it goes to zero,
* kill tasklet.
*/
void
-tasklet_enable_sync_once(struct tasklet_struct *tasklet)
+__tasklet_enable_sync_once(struct tasklet_struct *tasklet)
{
unsigned int disablecount;
@@ -526,18 +566,65 @@ tasklet_enable_sync_once(struct tasklet_
}
/*
- * tasklet_is_enabled(tasklet)
+ * __tasklet_is_enabled(tasklet)
*
* True if tasklet is not currently disabled. Answer may be stale
* as soon as it is returned -- caller must use it only as a hint,
* or must arrange synchronization externally.
*/
bool
-tasklet_is_enabled(const struct tasklet_struct *tasklet)
+__tasklet_is_enabled(const struct tasklet_struct *tasklet)
{
unsigned int disablecount;
- disablecount = tasklet->tl_disablecount;
+ disablecount = atomic_load_relaxed(&tasklet->tl_disablecount);
+
+ return (disablecount == 0);
+}
+
+/*
+ * __tasklet_is_scheduled(tasklet)
+ *
+ * True if tasklet is currently scheduled. Answer may be stale as
+ * soon as it is returned -- caller must use it only as a hint, or
+ * must arrange synchronization externally.
+ */
+bool
+__tasklet_is_scheduled(const struct tasklet_struct *tasklet)
+{
+
+ return atomic_load_relaxed(&tasklet->tl_state) & TASKLET_SCHEDULED;
+}
+
+/*
+ * __tasklet_enable(tasklet)
+ *
+ * Decrement tasklet's disable count. If it was previously
+ * scheduled to run, it may now run. Return true if the disable
+ * count went down to zero; otherwise return false.
+ *
+ * Store-release semantics.
+ */
+bool
+__tasklet_enable(struct tasklet_struct *tasklet)
+{
+ unsigned int disablecount;
+
+ /*
+ * Guarantee all caller-relevant reads or writes have completed
+ * before potentially allowing tasklet to run again by
+ * decrementing the disable count.
+ *
+ * Pairs with atomic_load_acquire(&tasklet->tl_disablecount) in
+ * tasklet_softintr.
+ */
+#ifndef __HAVE_ATOMIC_AS_MEMBAR
+ membar_exit();
+#endif
+
+ /* Decrement the disable count. */
+ disablecount = atomic_dec_uint_nv(&tasklet->tl_disablecount);
+ KASSERT(disablecount != UINT_MAX);
return (disablecount == 0);
}