diff --git a/kernel/unwind/deferred.c b/kernel/unwind/deferred.c
index 716393dff810..5f98ac5e3a1b 100644
--- a/kernel/unwind/deferred.c
+++ b/kernel/unwind/deferred.c
@@ -23,10 +23,11 @@
*/
static DEFINE_PER_CPU(u64, unwind_ctx_ctr);
-/* Guards adding to and reading the list of callbacks */
+/* Guards adding to or removing from the list of callbacks */
static DEFINE_MUTEX(callback_mutex);
static LIST_HEAD(callbacks);
static unsigned long unwind_mask;
+DEFINE_STATIC_SRCU(unwind_srcu);
/*
* The context cookie is a unique identifier that is assigned to a user
@@ -137,6 +138,7 @@ static void unwind_deferred_task_work(struct callback_head *head)
struct unwind_work *work;
struct task_struct *task = current;
u64 cookie;
+ int idx;
if (WARN_ON_ONCE(!info->pending))
return;
@@ -155,14 +157,16 @@ static void unwind_deferred_task_work(struct callback_head *head)
cookie = get_cookie(info);
- guard(mutex)(&callback_mutex);
- list_for_each_entry(work, &callbacks, list) {
+ idx = srcu_read_lock(&unwind_srcu);
+ list_for_each_entry_srcu(work, &callbacks, list,
+ srcu_read_lock_held(&unwind_srcu)) {
if (task->unwind_mask & (1UL << work->bit)) {
work->func(work, &trace, cookie);
clear_bit(work->bit, ¤t->unwind_mask);
}
}
- barrier();
+ srcu_read_unlock(&unwind_srcu, idx);
+
/* If another task work is pending, reuse the cookie and stack trace */
if (!READ_ONCE(info->pending))
WRITE_ONCE(info->cookie, 0);@@ -238,6 +242,7 @@ int unwind_deferred_request(struct unwind_work *work, u64 *cookie)
{
struct unwind_task_info *info = ¤t->unwind_info;
int pending;
+ int bit;
int ret;
*cookie = 0;@@ -249,12 +254,17 @@ int unwind_deferred_request(struct unwind_work *work, u64 *cookie)
if (in_nmi())
return unwind_deferred_request_nmi(work, cookie);
+ /* Do not allow cancelled works to request again */
+ bit = READ_ONCE(work->bit);
+ if (WARN_ON_ONCE(bit < 0))
+ return -EINVAL;
+
guard(irqsave)();
*cookie = get_cookie(info);
/* This is already queued */
- if (current->unwind_mask & (1UL << work->bit))
+ if (current->unwind_mask & (1UL << bit))
return 1;
/* callback already pending? */
@@ -280,19 +290,26 @@ int unwind_deferred_request(struct unwind_work *work, u64 *cookie)
void unwind_deferred_cancel(struct unwind_work *work)
{
struct task_struct *g, *t;
+ int bit;
if (!work)
return;
guard(mutex)(&callback_mutex);
- list_del(&work->list);
+ list_del_rcu(&work->list);
+ bit = work->bit;
+
+ /* Do not allow any more requests and prevent callbacks */
+ work->bit = -1;
+
+ clear_bit(bit, &unwind_mask);
- clear_bit(work->bit, &unwind_mask);
+ synchronize_srcu(&unwind_srcu);
guard(rcu)();
/* Clear this bit from all threads */
for_each_process_thread(g, t) {
- clear_bit(work->bit, &t->unwind_mask);
+ clear_bit(bit, &t->unwind_mask);
}
}
@@ -309,7 +326,7 @@ int unwind_deferred_init(struct unwind_work *work, unwind_callback_t func)
work->bit = ffz(unwind_mask);
unwind_mask |= 1UL << work->bit;
- list_add(&work->list, &callbacks);
+ list_add_rcu(&work->list, &callbacks);
work->func = func;
return 0;
}
--
2.47.2