virtsnd_pcm_msg_send() can fail while queueing a transfer. Restore the
failed message's length and notify the device about earlier messages
queued by the same call, so they do not remain stranded.

Propagate that failure through the indirect PCM transfer callback instead
of discarding it. Commit the updated indirect state only after successful
submission and stop further transfers when a send fails. Return an XRUN
error to ALSA so the stream can recover.

Keep the polling feature path unchanged.

Fixes: fe981e67568c ("ALSA: virtio: use ack callback")
Assisted-by: LLM
Signed-off-by: Joonho Lee <[email protected]>
---
Changes in v2:
- Propagate send errors through ACK, retain indirect state only on
  success, and stop the stream before an out-of-sync retry.

 sound/virtio/virtio_pcm_msg.c | 12 +++++
 sound/virtio/virtio_pcm_ops.c | 82 +++++++++++++++++++++++++++++++----
 2 files changed, 85 insertions(+), 9 deletions(-)

diff --git a/sound/virtio/virtio_pcm_msg.c b/sound/virtio/virtio_pcm_msg.c
index 72103f606716..5371f4985fd0 100644
--- a/sound/virtio/virtio_pcm_msg.c
+++ b/sound/virtio/virtio_pcm_msg.c
@@ -260,6 +260,18 @@ int virtsnd_pcm_msg_send(struct virtio_pcm_substream *vss, 
unsigned long offset,
                                dev_err(&vdev->dev,
                                        "SID %u: failed to send I/O message\n",
                                        vss->sid);
+                               msg->length -= n;
+
+                               /*
+                                * Do not leave earlier messages queued without 
notifying
+                                * the device when a later message could not be 
added.
+                                */
+                               if (msg_count != vss->msg_count &&
+                                   !(vss->features &
+                                     (1U << VIRTIO_SND_PCM_F_MSG_POLLING)) &&
+                                   virtqueue_kick_prepare(vqueue))
+                                       virtqueue_notify(vqueue);
+
                                return rc;
                        }
 
diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c
index 1091100a4c98..a40ea394bf17 100644
--- a/sound/virtio/virtio_pcm_ops.c
+++ b/sound/virtio/virtio_pcm_ops.c
@@ -466,36 +466,100 @@ virtsnd_pcm_cp_pointer(struct snd_pcm_substream 
*substream)
                vss->hw_ptr);
 }
 
+struct virtsnd_pcm_transfer {
+       struct snd_pcm_indirect indirect;
+       int error;
+};
+
 static void virtsnd_pcm_trans_copy(struct snd_pcm_substream *substream,
                                   struct snd_pcm_indirect *rec, size_t bytes)
 {
        struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
+       struct virtsnd_pcm_transfer *transfer =
+               container_of(rec, struct virtsnd_pcm_transfer, indirect);
+
+       if (transfer->error)
+               return;
 
-       virtsnd_pcm_msg_send(vss, rec->sw_data, bytes);
+       transfer->error = virtsnd_pcm_msg_send(vss, rec->sw_data, bytes);
+}
+
+/* Caller holds vss->lock. */
+static void virtsnd_pcm_transfer_failed(struct virtio_pcm_substream *vss)
+{
+       vss->xfer_enabled = false;
+       vss->stopped = true;
+       vss->substream->runtime->stop_operating = true;
 }
 
 static int virtsnd_pcm_pb_ack(struct snd_pcm_substream *substream)
 {
        struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
        struct virtio_snd_queue *queue = virtsnd_pcm_queue(vss);
+       struct virtsnd_pcm_transfer transfer = {};
+       int rc;
 
-       guard(spinlock_irqsave)(&queue->lock);
-       guard(spinlock)(&vss->lock);
+       scoped_guard(spinlock_irqsave, &queue->lock) {
+               guard(spinlock)(&vss->lock);
+               transfer.indirect = vss->pcm_indirect;
+               if (vss->stopped) {
+                       rc = -EPIPE;
+               } else {
+                       rc = snd_pcm_indirect_playback_transfer(substream,
+                                                               
&transfer.indirect,
+                                                               
virtsnd_pcm_trans_copy);
+               }
+               if (!rc)
+                       rc = transfer.error;
+               if (rc)
+                       virtsnd_pcm_transfer_failed(vss);
+               else
+                       vss->pcm_indirect = transfer.indirect;
+       }
+       if (rc) {
+               dev_err(&vss->snd->vdev->dev,
+                       "SID %u: playback ACK transfer failed: %d\n",
+                       vss->sid, rc);
+               /* ALSA treats -EPIPE as an XRUN in ACK and pointer paths. */
+               return -EPIPE;
+       }
 
-       return snd_pcm_indirect_playback_transfer(substream, &vss->pcm_indirect,
-                                                 virtsnd_pcm_trans_copy);
+       return 0;
 }
 
 static int virtsnd_pcm_cp_ack(struct snd_pcm_substream *substream)
 {
        struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
        struct virtio_snd_queue *queue = virtsnd_pcm_queue(vss);
+       struct virtsnd_pcm_transfer transfer = {};
+       int rc;
 
-       guard(spinlock_irqsave)(&queue->lock);
-       guard(spinlock)(&vss->lock);
+       scoped_guard(spinlock_irqsave, &queue->lock) {
+               guard(spinlock)(&vss->lock);
+               transfer.indirect = vss->pcm_indirect;
+               if (vss->stopped) {
+                       rc = -EPIPE;
+               } else {
+                       rc = snd_pcm_indirect_capture_transfer(substream,
+                                                              
&transfer.indirect,
+                                                              
virtsnd_pcm_trans_copy);
+               }
+               if (!rc)
+                       rc = transfer.error;
+               if (rc)
+                       virtsnd_pcm_transfer_failed(vss);
+               else
+                       vss->pcm_indirect = transfer.indirect;
+       }
+       if (rc) {
+               dev_err(&vss->snd->vdev->dev,
+                       "SID %u: capture ACK transfer failed: %d\n",
+                       vss->sid, rc);
+               /* ALSA treats -EPIPE as an XRUN in ACK and pointer paths. */
+               return -EPIPE;
+       }
 
-       return snd_pcm_indirect_capture_transfer(substream, &vss->pcm_indirect,
-                                                virtsnd_pcm_trans_copy);
+       return 0;
 }
 
 /* PCM substream operators map. */
-- 
2.54.0 (Apple Git-157)


Reply via email to