A process issuing blocking writes to a virtio console may get stuck indefinitely if another thread polls the device. Here is how to trigger the bug:
- Thread A writes to the port until the virtqueue is full. - Thread A calls wait_port_writable() and goes to sleep, waiting on port->waitqueue. - The host processes some of the write, marks buffers as used and raises an interrupt. - Before the interrupt is serviced, thread B executes port_fops_poll(). This calls reclaim_consumed_buffers() via will_write_block() and consumes all used buffers. - The interrupt is serviced. vring_interrupt() finds no used buffers via more_used() and returns without waking port->waitqueue. - Thread A is still in wait_event(port->waitqueue), waiting for a wakeup that never arrives.
The crux is that invoking reclaim_consumed_buffers() may cause vring_interrupt() to omit wakeups.
Fix this by making reclaim_consumed_buffers() issue an additional wake up if it consumed any buffers.
Signed-off-by: Lorenz Bauer lmb@isovalent.com --- As far as I can tell all currently maintained stable series kernels need this commit. I've tested that it applies cleanly to 5.10.247, however wasn't able to build the kernel due to an unrelated link error. Instead I applied it to 5.15.197, which compiled and verified to be fixed. --- drivers/char/virtio_console.c | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index 088182e54debd6029ea2c2a5542d7a28500e67b8..7cd3ad9da9b53a7a570410f12501acc7fd7e3b9b 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -581,6 +581,7 @@ static ssize_t send_control_msg(struct port *port, unsigned int event, static void reclaim_consumed_buffers(struct port *port) { struct port_buffer *buf; + bool freed = false; unsigned int len;
if (!port->portdev) { @@ -589,7 +590,15 @@ static void reclaim_consumed_buffers(struct port *port) } while ((buf = virtqueue_get_buf(port->out_vq, &len))) { free_buf(buf, false); + freed = true; + } + if (freed) { + /* We freed all used buffers. Issue a wake up so that other pending + * tasks do not get stuck. This is necessary because vring_interrupt() + * will drop wakeups from the host if there are no used buffers. + */ port->outvq_full = false; + wake_up_interruptible(&port->waitqueue); } }
--- base-commit: d358e5254674b70f34c847715ca509e46eb81e6f change-id: 20251215-virtio-console-lost-wakeup-0f566c5cd35f
Best regards,
Hi,
Thanks for your patch.
FYI: kernel test robot notices the stable kernel rule is not satisfied.
The check is based on https://www.kernel.org/doc/html/latest/process/stable-kernel-rules.html#opti...
Rule: add the tag "Cc: stable@vger.kernel.org" in the sign-off area to have the patch automatically included in the stable tree. Subject: [PATCH] virtio: console: fix lost wakeup when device is written and polled Link: https://lore.kernel.org/stable/20251215-virtio-console-lost-wakeup-v1-1-79a5...
On Mon, Dec 15, 2025, at 11:40, Lorenz Bauer wrote:
- }
- if (freed) {
/* We freed all used buffers. Issue a wake up so that other pending* tasks do not get stuck. This is necessary because vring_interrupt()* will drop wakeups from the host if there are no used buffers. port->outvq_full = false;*/ }wake_up_interruptible(&port->waitqueue);
Is it always enough to wake up only one waiter? From your description it sounds like it might need wake_up_interruptible_all() instead, but I may be misunderstanding the issue.
Arnd
On Mon, Dec 15, 2025 at 10:49 AM Arnd Bergmann arnd@arndb.de wrote:
Is it always enough to wake up only one waiter? From your description it sounds like it might need wake_up_interruptible_all() instead, but I may be misunderstanding the issue.
Ah, I'm just not familiar with waitqueues, so that is very possible. I based it on out_intr(), which also only does a wake_up_interruptible(). So either this is enough, or we need a wholesale replacement of wake ups? :(
Nothing in the virtio console prevents a third thread from entering the fray and also getting stuck as far as I can tell.
- Thread A: write(): fill up vq, enter waitqueue - Thread B: write(): vq is full, enter waitqueue - Thread C: poll(): consume used buffers - vring_interrupt() dropped, both A and B stuck
Is there other locking going on in the virtio layer or somewhere else that would prevent concurrent write?
Best Lorenz
linux-stable-mirror@lists.linaro.org