From: Alexander Egorenkov <egorenar@linux.ibm.com>
To: linux-s390@vger.kernel.org
Cc: gor@linux.ibm.com, hca@linux.ibm.com, agordeev@linux.ibm.com,
oberpar@linux.ibm.com
Subject: [PATCH v2 2/2] s390/sclp: Ensure no callback gets called after sclp_unregister() returns
Date: Tue, 4 Aug 2026 09:45:11 +0200 [thread overview]
Message-ID: <20260804074511.236153-3-egorenar@linux.ibm.com> (raw)
In-Reply-To: <20260804074511.236153-1-egorenar@linux.ibm.com>
There is a potential race condition between sclp_unregister()
and sclp_dispatch_evbufs()/sclp_dispatch_state_change(). As a result,
it is not guaranteed that the callbacks registered with sclp_register()
will not get called one more time (and no more than one) after
sclp_unregister() returns.
Example of a race situation
===========================
CPU A (SCLP) CPU B (driver using SCLP API)
--> sclp_dispatch_evbufs()
--> spin_unlock(&sclp_lock)
--> sclp_unregister()
--> spin_lock(&sclp_lock)
--> remove struct sclp_register from list
--> spin_unlock(&sclp_lock)
<-- sclp_unregister()
--> free struct sclp_register
--> receiver_fn()
--> illegal memory access
--> spin_lock(&sclp_lock)
<-- sclp_dispatch_evbufs()
To guarantee that this race situation never occurs, it must be ensured
that sclp_unregister() waits for the last callback invocation by
sclp_dispatch_evbufs()/sclp_dispatch_state_change() to return.
The basic idea is to use a single completion per struct sclp_register
to block in sclp_unregister() until the last callback completes.
It is sufficient to use only one completion per struct sclp_register
because the callbacks state_change_fn() and receiver_fn() never get called
in parallel.
Initially, the completion is marked as done in sclp_register() and every
time when sclp_dispatch_evbufs()/sclp_dispatch_state_change() calls
one of the callbacks, the completion is marked as incomplete before
its invocation and as done afterwards. The call to wait_for_completion()
in sclp_unregister() shall block until the last invocation of the callback
finishes. And if no callback is pending or being invoked by
sclp_dispatch_evbufs()/sclp_dispatch_state_change() then the completion
is always marked as done and sclp_unregister() shall never block in
wait_for_completion() and return quickly.
To mark the completion as incomplete, reinit_completion() is needed and it
must be guaranteed that no racy wait_for_completion() calls are going on in
parallel. This is achieved by calling reinit_completion() with the SCLP
spinlock held. The following situations must be considered:
1. No callback invocation is pending or in progress when
wait_for_completion() is called from sclp_unregister(). This is
a regular situation occurring in most of the cases. wait_for_completion()
will return quickly because the completion is marked as done. When
wait_for_completion() gets called, the callbacks no longer exist in
the list sclp_reg_list, and, therefore,
sclp_dispatch_evbufs()/sclp_dispatch_state_change() would not be able
to obtain one of them and invoke reinit_completion() while
a wait_for_completion() call is going on in parallel.
2. One of the callbacks is pending or in progress when
sclp_unregister() gets called. This is a rare situation.
The SCLP spinlock is either being held by
sclp_dispatch_evbufs()/sclp_dispatch_state_change() or not. In the former
case, sclp_unregister() would block in the SCLP spinlock trying to
remove the callbacks from the list and not enter wait_for_completion().
The SCLP spinlock is released after a reinit_complete() call, therefore,
it will not race with a wait_for_complete() call in sclp_unregister().
If the SCLP spinlock is not held by
sclp_dispatch_evbufs()/sclp_dispatch_state_change(), then
a reinit_complete() call must have been completed already and
it will not race with a wait_for_complete() call in sclp_unregister().
With this change sclp_unregister() may no longer be invoked
from atomic context or registered callbacks. But this should not be a
problem because this is no regular use case and no driver using
sclp_register()/sclp_unregister() requires this at the moment and
likely should not require it in the future.
Signed-off-by: Alexander Egorenkov <egorenar@linux.ibm.com>
---
drivers/s390/char/sclp.c | 19 +++++++++++++++----
drivers/s390/char/sclp.h | 2 ++
2 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/drivers/s390/char/sclp.c b/drivers/s390/char/sclp.c
index 98e334724a62..8954f4260401 100644
--- a/drivers/s390/char/sclp.c
+++ b/drivers/s390/char/sclp.c
@@ -565,9 +565,11 @@ sclp_dispatch_evbufs(struct sccb_header *sccb)
evbuf, !reg);
if (reg && reg->receiver_fn) {
+ reinit_completion(®->done);
spin_unlock_irqrestore(&sclp_lock, flags);
reg->receiver_fn(evbuf);
spin_lock_irqsave(&sclp_lock, flags);
+ complete(®->done);
} else if (reg == NULL)
rc = -EOPNOTSUPP;
}
@@ -773,8 +775,8 @@ sclp_dispatch_state_change(void)
sccb_mask_t receive_mask;
sccb_mask_t send_mask;
+ spin_lock_irqsave(&sclp_lock, flags);
do {
- spin_lock_irqsave(&sclp_lock, flags);
reg = NULL;
list_for_each(l, &sclp_reg_list) {
reg = list_entry(l, struct sclp_register, list);
@@ -788,15 +790,18 @@ sclp_dispatch_state_change(void)
} else
reg = NULL;
}
- spin_unlock_irqrestore(&sclp_lock, flags);
if (reg && reg->state_change_fn) {
+ reinit_completion(®->done);
+ spin_unlock_irqrestore(&sclp_lock, flags);
/* STCG: State-change callback (b=callback) */
sclp_trace(2, "STCG", 0, (u64)reg->state_change_fn,
false);
-
reg->state_change_fn(reg);
+ spin_lock_irqsave(&sclp_lock, flags);
+ complete(®->done);
}
} while (reg);
+ spin_unlock_irqrestore(&sclp_lock, flags);
}
struct sclp_statechangebuf {
@@ -885,6 +890,8 @@ sclp_register(struct sclp_register *reg)
/* Trigger initial state change callback */
reg->sclp_receive_mask = 0;
reg->sclp_send_mask = 0;
+ init_completion(®->done);
+ complete(®->done);
list_add(®->list, &sclp_reg_list);
spin_unlock_irqrestore(&sclp_lock, flags);
rc = sclp_init_mask(1);
@@ -898,7 +905,10 @@ sclp_register(struct sclp_register *reg)
EXPORT_SYMBOL(sclp_register);
-/* Unregister event listener. */
+/* Unregister event listener.
+ * It can sleep and, therefore, may not get called from atomic context.
+ * And neither it can get called from a receive_fn() callback because
+ * struct sclp_register must remain valid after the call. */
void
sclp_unregister(struct sclp_register *reg)
{
@@ -911,6 +921,7 @@ sclp_unregister(struct sclp_register *reg)
list_del(®->list);
spin_unlock_irqrestore(&sclp_lock, flags);
sclp_init_mask(1);
+ wait_for_completion(®->done);
}
EXPORT_SYMBOL(sclp_unregister);
diff --git a/drivers/s390/char/sclp.h b/drivers/s390/char/sclp.h
index b31a680e0871..b04f6287ccfc 100644
--- a/drivers/s390/char/sclp.h
+++ b/drivers/s390/char/sclp.h
@@ -11,6 +11,7 @@
#include <linux/types.h>
#include <linux/list.h>
+#include <linux/completion.h>
#include <asm/asm-extable.h>
#include <asm/machine.h>
#include <asm/sclp.h>
@@ -275,6 +276,7 @@ struct sclp_register {
void (*state_change_fn)(struct sclp_register *);
/* called for events in cp_receive_mask/sclp_receive_mask */
void (*receiver_fn)(struct evbuf_header *);
+ struct completion done;
};
/* externals from sclp.c */
--
2.53.0
next prev parent reply other threads:[~2026-08-04 7:45 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-04 7:45 [PATCH v2 0/2] s390/sclp: Misc fixes Alexander Egorenkov
2026-08-04 7:45 ` [PATCH v2 1/2] s390/sclp_ocf: Fix computation of length of GDS values Alexander Egorenkov
2026-08-04 7:55 ` sashiko-bot
2026-08-04 7:45 ` Alexander Egorenkov [this message]
2026-08-04 8:01 ` [PATCH v2 2/2] s390/sclp: Ensure no callback gets called after sclp_unregister() returns sashiko-bot
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=20260804074511.236153-3-egorenar@linux.ibm.com \
--to=egorenar@linux.ibm.com \
--cc=agordeev@linux.ibm.com \
--cc=gor@linux.ibm.com \
--cc=hca@linux.ibm.com \
--cc=linux-s390@vger.kernel.org \
--cc=oberpar@linux.ibm.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