From: Eli Billauer <eli.billauer@gmail.com>
To: gregkh@linuxfoundation.org
Cc: arnd@arndb.de, linux-kernel@vger.kernel.org,
Eli Billauer <eli.billauer@gmail.com>
Subject: [PATCH 7/8] char: xillybus: Avoid indefinite sleep of work item
Date: Mon, 9 Nov 2026 12:27:59 +0100 [thread overview]
Message-ID: <20261109112800.48663-8-eli.billauer@gmail.com> (raw)
In-Reply-To: <20261109112800.48663-1-eli.billauer@gmail.com>
xillybus_write() may sleep indefinitely with rd_mutex taken while
waiting for a DMA buffer to become available for writing. As the work
item implementing automatic flush also takes this mutex, it may sleep
indefinitely as well.
Thanks to the Concurrency Managed Workqueue (cmwq), there has been no
visible problem with work items sleeping forever, but this shouldn't be
allowed by design.
This commit makes the autoflush work item return immediately if
xillybus_write() is invoked in parallel, and record that fact with the
help of a new struct member, autoflush_skipped. This way, the work item
doesn't attempt to take rd_mutex, avoiding sleeping indefinitely.
When an autoflush has been skipped this way, xillybus_write() compensates
for this with a direct call to xillybus_myflush().
Assisted-by: Sashiko-0.2.5:gemini-3.1-pro-preview
Link: https://sashiko.dev/#/patchset/20260724094302.50761-1-eli.billauer@gmail.com
Signed-off-by: Eli Billauer <eli.billauer@gmail.com>
---
drivers/char/xillybus/xillybus.h | 3 +
drivers/char/xillybus/xillybus_core.c | 88 +++++++++++++++++++++------
2 files changed, 72 insertions(+), 19 deletions(-)
diff --git a/drivers/char/xillybus/xillybus.h b/drivers/char/xillybus/xillybus.h
index 98c7ac4dd1f9..cd3cde833ce3 100644
--- a/drivers/char/xillybus/xillybus.h
+++ b/drivers/char/xillybus/xillybus.h
@@ -77,6 +77,9 @@ struct xilly_channel {
int rd_full;
spinlock_t rd_spinlock;
struct mutex rd_mutex;
+ struct mutex autoflush_mutex;
+ int dont_autoflush;
+ bool autoflush_skipped;
wait_queue_head_t rd_wait;
int rd_ref_count;
int rd_allow_partial;
diff --git a/drivers/char/xillybus/xillybus_core.c b/drivers/char/xillybus/xillybus_core.c
index 7189c6d97e7d..ab8ac78b28dc 100644
--- a/drivers/char/xillybus/xillybus_core.c
+++ b/drivers/char/xillybus/xillybus_core.c
@@ -84,10 +84,11 @@ static struct workqueue_struct *xillybus_wq;
* Blocking on the wait queues is allowed with mutexes held, but not with
* spinlocks.
*
- * Only interruptible blocking is allowed on mutexes and wait queues.
+ * Only interruptible blocking is allowed on mutexes and wait queues,
+ * except for autoflush_mutex.
*
* All in all, the locking order goes (with skips allowed, of course):
- * wr_mutex -> rd_mutex -> register_mutex ->
+ * autoflush_mutex -> wr_mutex -> rd_mutex -> register_mutex ->
* allow_isr_lock -> wr_spinlock -> rd_spinlock
*/
@@ -522,6 +523,9 @@ static int xilly_setupchannels(struct xilly_endpoint *ep,
spin_lock_init(&channel->rd_spinlock);
mutex_init(&channel->wr_mutex);
mutex_init(&channel->rd_mutex);
+ mutex_init(&channel->autoflush_mutex);
+ channel->dont_autoflush = 0;
+ channel->autoflush_skipped = false;
init_waitqueue_head(&channel->rd_wait);
init_waitqueue_head(&channel->wr_wait);
init_waitqueue_head(&channel->wr_ready_wait);
@@ -1194,7 +1198,10 @@ static int xillybus_myflush(struct xilly_channel *channel, long timeout)
/*
* Indefinite sleep with mutex taken. With data waiting for
* flushing user should not be surprised if open() for write
- * sleeps.
+ * sleeps. Note that timeout == 0 is used only with
+ * synchronous channels (with rd_synchronous true), and
+ * the autoflush work item is never queued for such, so
+ * no risk of work item sleeping indefinitely.
*/
if (timeout == 0)
wait_event_interruptible(channel->rd_wait,
@@ -1241,6 +1248,13 @@ static void xillybus_autoflush(struct work_struct *work)
workitem, struct xilly_channel, rd_workitem);
int rc;
+ guard(mutex)(&channel->autoflush_mutex);
+
+ if (channel->dont_autoflush) {
+ channel->autoflush_skipped = true;
+ return;
+ }
+
rc = xillybus_myflush(channel, -1);
if (rc == -EINTR)
dev_warn(channel->endpoint->dev,
@@ -1250,8 +1264,8 @@ static void xillybus_autoflush(struct work_struct *work)
"Autoflush failed under weird circumstances.\n");
}
-static ssize_t xillybus_write(struct file *filp, const char __user *userbuf,
- size_t count, loff_t *f_pos)
+static ssize_t __xillybus_write(struct file *filp, const char __user *userbuf,
+ size_t count)
{
ssize_t rc;
unsigned long flags;
@@ -1264,9 +1278,6 @@ static ssize_t xillybus_write(struct file *filp, const char __user *userbuf,
int howmany = 0, bufpos = 0, bufidx = 0, bufferdone = 0;
int end_offset_plus1 = 0;
- if (channel->endpoint->fatal_error)
- return -EIO;
-
rc = mutex_lock_interruptible(&channel->rd_mutex);
if (rc)
return rc;
@@ -1409,12 +1420,6 @@ static ssize_t xillybus_write(struct file *filp, const char __user *userbuf,
if (channel->endpoint->fatal_error)
return -EIO;
- if (!channel->rd_synchronous)
- queue_delayed_work(
- xillybus_wq,
- &channel->rd_workitem,
- XILLY_RX_TIMEOUT);
-
return rc;
}
}
@@ -1454,11 +1459,6 @@ static ssize_t xillybus_write(struct file *filp, const char __user *userbuf,
mutex_unlock(&channel->rd_mutex);
- if (!channel->rd_synchronous)
- queue_delayed_work(xillybus_wq,
- &channel->rd_workitem,
- XILLY_RX_TIMEOUT);
-
if (channel->endpoint->fatal_error)
return -EIO;
@@ -1475,6 +1475,56 @@ static ssize_t xillybus_write(struct file *filp, const char __user *userbuf,
return bytes_done;
}
+/*
+ * Prevent the autoflush work item from sleeping indefinitely by having
+ * dont_autoflush non-zero while __xillybus_write() is invoked, as this
+ * function may sleep indefinitely with rd_mutex taken.
+ * The work item terminates immediately when dont_autoflush is non-zero,
+ * and announces this by setting autoflush_skipped. Perform autoflush before
+ * returning to compensate for a skipped autoflush if necessary.
+ */
+
+static ssize_t xillybus_write(struct file *filp, const char __user *userbuf,
+ size_t count, loff_t *f_pos)
+{
+ struct xilly_channel *channel = filp->private_data;
+
+ ssize_t retval;
+ bool compensate = false;
+
+ if (channel->endpoint->fatal_error)
+ return -EIO;
+
+ /*
+ * On synchronous channels, flush is performed after every
+ * write operation. Autoflush is therefore irrelevant.
+ */
+ if (channel->rd_synchronous)
+ return __xillybus_write(filp, userbuf, count);
+
+ scoped_guard(mutex, &channel->autoflush_mutex)
+ channel->dont_autoflush++;
+
+ retval = __xillybus_write(filp, userbuf, count);
+
+ scoped_guard(mutex, &channel->autoflush_mutex) {
+ channel->dont_autoflush--;
+
+ if (channel->autoflush_skipped) {
+ compensate = true;
+ channel->autoflush_skipped = false;
+ }
+ }
+
+ if (compensate)
+ xillybus_myflush(channel, -1);
+
+ queue_delayed_work(xillybus_wq, &channel->rd_workitem,
+ XILLY_RX_TIMEOUT);
+
+ return retval;
+}
+
static int xillybus_open(struct inode *inode, struct file *filp)
{
int rc;
--
2.17.1
next prev parent reply other threads:[~2026-11-09 11:28 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-09 11:27 [PATCH 0/8] char: xillybus: Fix issues reported by Sashiko Eli Billauer
2026-11-09 11:27 ` [PATCH 1/8] char: xillybus: Use unsigned int literal Eli Billauer
2026-11-09 11:27 ` [PATCH 2/8] char: xillybus: Safer access of unaligned pointers Eli Billauer
2026-11-09 11:27 ` [PATCH 3/8] char: xillybus: Return error in fops read method handler Eli Billauer
2026-11-09 11:27 ` [PATCH 4/8] char: xillybus: Rely on FMODE flags in poll method Eli Billauer
2026-11-09 11:27 ` [PATCH 5/8] char: xillybus: Refactor flush function Eli Billauer
2026-11-09 11:27 ` [PATCH 6/8] char: xillybus: Correct handling of wait queue Eli Billauer
2026-11-09 11:27 ` Eli Billauer [this message]
2026-11-09 11:28 ` [PATCH 8/8] char: xillybus: Cancel delayed work items before teardown Eli Billauer
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=20261109112800.48663-8-eli.billauer@gmail.com \
--to=eli.billauer@gmail.com \
--cc=arnd@arndb.de \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
/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
all inboxes | Powered by JetHome®