Skip to content

Commit fe981e6

Browse files
MatiasVaratiwai
authored andcommitted
ALSA: virtio: use ack callback
This commit uses the ack() callback to determine when a buffer has been updated, then exposes it to guest. The current mechanism splits a dma buffer into descriptors that are exposed to the device. This dma buffer is shared with the user application. When the device consumes a buffer, the driver moves the request from the used ring to available ring. The driver exposes the buffer to the device without knowing if the content has been updated from the user. The section 2.8.21.1 of the virtio spec states that: "The device MAY access the descriptor chains the driver created and the memory they refer to immediately". If the device picks up buffers from the available ring just after it is notified, it happens that the content may be old. When the ack() callback is invoked, the driver exposes only the buffers that have already been updated, i.e., enqueued in the available ring. Thus, the device always picks up a buffer that is updated. For capturing, the driver starts by exposing all the available buffers to device. After device updates the content of a buffer, it enqueues it in the used ring. It is only after the ack() for capturing is issued that the driver re-enqueues the buffer in the available ring. Co-developed-by: Anton Yakovlev <[email protected]> Signed-off-by: Anton Yakovlev <[email protected]> Signed-off-by: Matias Ezequiel Vara Larsen <[email protected]> Link: https://lore.kernel.org/r/ZTjkn1YAFz67yfqx@fedora Signed-off-by: Takashi Iwai <[email protected]>
1 parent 3473185 commit fe981e6

File tree

4 files changed

+158
-61
lines changed

4 files changed

+158
-61
lines changed

sound/virtio/virtio_pcm.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,9 @@ static int virtsnd_pcm_build_hw(struct virtio_pcm_substream *vss,
109109
SNDRV_PCM_INFO_BATCH |
110110
SNDRV_PCM_INFO_BLOCK_TRANSFER |
111111
SNDRV_PCM_INFO_INTERLEAVED |
112-
SNDRV_PCM_INFO_PAUSE;
112+
SNDRV_PCM_INFO_PAUSE |
113+
SNDRV_PCM_INFO_NO_REWINDS |
114+
SNDRV_PCM_INFO_SYNC_APPLPTR;
113115

114116
if (!info->channels_min || info->channels_min > info->channels_max) {
115117
dev_err(&vdev->dev,
@@ -471,7 +473,7 @@ int virtsnd_pcm_build_devs(struct virtio_snd *snd)
471473
for (kss = ks->substream; kss; kss = kss->next)
472474
vs->substreams[kss->number]->substream = kss;
473475

474-
snd_pcm_set_ops(vpcm->pcm, i, &virtsnd_pcm_ops);
476+
snd_pcm_set_ops(vpcm->pcm, i, &virtsnd_pcm_ops[i]);
475477
}
476478

477479
snd_pcm_set_managed_buffer_all(vpcm->pcm,

sound/virtio/virtio_pcm.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <linux/atomic.h>
1010
#include <linux/virtio_config.h>
1111
#include <sound/pcm.h>
12+
#include <sound/pcm-indirect.h>
1213

1314
struct virtio_pcm;
1415
struct virtio_pcm_msg;
@@ -21,6 +22,7 @@ struct virtio_pcm_msg;
2122
* @direction: Stream data flow direction (SNDRV_PCM_STREAM_XXX).
2223
* @features: Stream VirtIO feature bit map (1 << VIRTIO_SND_PCM_F_XXX).
2324
* @substream: Kernel ALSA substream.
25+
* @pcm_indirect: Kernel indirect pcm structure.
2426
* @hw: Kernel ALSA substream hardware descriptor.
2527
* @elapsed_period: Kernel work to handle the elapsed period state.
2628
* @lock: Spinlock that protects fields shared by interrupt handlers and
@@ -46,6 +48,7 @@ struct virtio_pcm_substream {
4648
u32 direction;
4749
u32 features;
4850
struct snd_pcm_substream *substream;
51+
struct snd_pcm_indirect pcm_indirect;
4952
struct snd_pcm_hardware hw;
5053
struct work_struct elapsed_period;
5154
spinlock_t lock;
@@ -57,7 +60,6 @@ struct virtio_pcm_substream {
5760
bool suspended;
5861
struct virtio_pcm_msg **msgs;
5962
unsigned int nmsgs;
60-
int msg_last_enqueued;
6163
unsigned int msg_count;
6264
wait_queue_head_t msg_empty;
6365
};
@@ -90,7 +92,7 @@ struct virtio_pcm {
9092
struct virtio_pcm_stream streams[SNDRV_PCM_STREAM_LAST + 1];
9193
};
9294

93-
extern const struct snd_pcm_ops virtsnd_pcm_ops;
95+
extern const struct snd_pcm_ops virtsnd_pcm_ops[];
9496

9597
int virtsnd_pcm_validate(struct virtio_device *vdev);
9698

@@ -117,7 +119,8 @@ int virtsnd_pcm_msg_alloc(struct virtio_pcm_substream *vss,
117119

118120
void virtsnd_pcm_msg_free(struct virtio_pcm_substream *vss);
119121

120-
int virtsnd_pcm_msg_send(struct virtio_pcm_substream *vss);
122+
int virtsnd_pcm_msg_send(struct virtio_pcm_substream *vss, unsigned long offset,
123+
unsigned long bytes);
121124

122125
unsigned int virtsnd_pcm_msg_pending_num(struct virtio_pcm_substream *vss);
123126

sound/virtio/virtio_pcm_msg.c

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,6 @@ int virtsnd_pcm_msg_alloc(struct virtio_pcm_substream *vss,
155155
sizeof(msg->xfer));
156156
sg_init_one(&msg->sgs[PCM_MSG_SG_STATUS], &msg->status,
157157
sizeof(msg->status));
158-
msg->length = period_bytes;
159158
virtsnd_pcm_sg_from(&msg->sgs[PCM_MSG_SG_DATA], sg_num, data,
160159
period_bytes);
161160

@@ -186,61 +185,75 @@ void virtsnd_pcm_msg_free(struct virtio_pcm_substream *vss)
186185
/**
187186
* virtsnd_pcm_msg_send() - Send asynchronous I/O messages.
188187
* @vss: VirtIO PCM substream.
188+
* @offset: starting position that has been updated
189+
* @bytes: number of bytes that has been updated
189190
*
190191
* All messages are organized in an ordered circular list. Each time the
191192
* function is called, all currently non-enqueued messages are added to the
192-
* virtqueue. For this, the function keeps track of two values:
193-
*
194-
* msg_last_enqueued = index of the last enqueued message,
195-
* msg_count = # of pending messages in the virtqueue.
193+
* virtqueue. For this, the function uses offset and bytes to calculate the
194+
* messages that need to be added.
196195
*
197196
* Context: Any context. Expects the tx/rx queue and the VirtIO substream
198197
* spinlocks to be held by caller.
199198
* Return: 0 on success, -errno on failure.
200199
*/
201-
int virtsnd_pcm_msg_send(struct virtio_pcm_substream *vss)
200+
int virtsnd_pcm_msg_send(struct virtio_pcm_substream *vss, unsigned long offset,
201+
unsigned long bytes)
202202
{
203-
struct snd_pcm_runtime *runtime = vss->substream->runtime;
204203
struct virtio_snd *snd = vss->snd;
205204
struct virtio_device *vdev = snd->vdev;
206205
struct virtqueue *vqueue = virtsnd_pcm_queue(vss)->vqueue;
207-
int i;
208-
int n;
206+
unsigned long period_bytes = snd_pcm_lib_period_bytes(vss->substream);
207+
unsigned long start, end, i;
208+
unsigned int msg_count = vss->msg_count;
209209
bool notify = false;
210+
int rc;
210211

211-
i = (vss->msg_last_enqueued + 1) % runtime->periods;
212-
n = runtime->periods - vss->msg_count;
212+
start = offset / period_bytes;
213+
end = (offset + bytes - 1) / period_bytes;
213214

214-
for (; n; --n, i = (i + 1) % runtime->periods) {
215+
for (i = start; i <= end; i++) {
215216
struct virtio_pcm_msg *msg = vss->msgs[i];
216217
struct scatterlist *psgs[] = {
217218
&msg->sgs[PCM_MSG_SG_XFER],
218219
&msg->sgs[PCM_MSG_SG_DATA],
219220
&msg->sgs[PCM_MSG_SG_STATUS]
220221
};
221-
int rc;
222-
223-
msg->xfer.stream_id = cpu_to_le32(vss->sid);
224-
memset(&msg->status, 0, sizeof(msg->status));
225-
226-
if (vss->direction == SNDRV_PCM_STREAM_PLAYBACK)
227-
rc = virtqueue_add_sgs(vqueue, psgs, 2, 1, msg,
228-
GFP_ATOMIC);
229-
else
230-
rc = virtqueue_add_sgs(vqueue, psgs, 1, 2, msg,
231-
GFP_ATOMIC);
232-
233-
if (rc) {
234-
dev_err(&vdev->dev,
235-
"SID %u: failed to send I/O message\n",
236-
vss->sid);
237-
return rc;
222+
unsigned long n;
223+
224+
n = period_bytes - (offset % period_bytes);
225+
if (n > bytes)
226+
n = bytes;
227+
228+
msg->length += n;
229+
if (msg->length == period_bytes) {
230+
msg->xfer.stream_id = cpu_to_le32(vss->sid);
231+
memset(&msg->status, 0, sizeof(msg->status));
232+
233+
if (vss->direction == SNDRV_PCM_STREAM_PLAYBACK)
234+
rc = virtqueue_add_sgs(vqueue, psgs, 2, 1, msg,
235+
GFP_ATOMIC);
236+
else
237+
rc = virtqueue_add_sgs(vqueue, psgs, 1, 2, msg,
238+
GFP_ATOMIC);
239+
240+
if (rc) {
241+
dev_err(&vdev->dev,
242+
"SID %u: failed to send I/O message\n",
243+
vss->sid);
244+
return rc;
245+
}
246+
247+
vss->msg_count++;
238248
}
239249

240-
vss->msg_last_enqueued = i;
241-
vss->msg_count++;
250+
offset = 0;
251+
bytes -= n;
242252
}
243253

254+
if (msg_count == vss->msg_count)
255+
return 0;
256+
244257
if (!(vss->features & (1U << VIRTIO_SND_PCM_F_MSG_POLLING)))
245258
notify = virtqueue_kick_prepare(vqueue);
246259

@@ -309,6 +322,8 @@ static void virtsnd_pcm_msg_complete(struct virtio_pcm_msg *msg,
309322
if (vss->hw_ptr >= vss->buffer_bytes)
310323
vss->hw_ptr -= vss->buffer_bytes;
311324

325+
msg->length = 0;
326+
312327
vss->xfer_xrun = false;
313328
vss->msg_count--;
314329

@@ -320,8 +335,6 @@ static void virtsnd_pcm_msg_complete(struct virtio_pcm_msg *msg,
320335
le32_to_cpu(msg->status.latency_bytes));
321336

322337
schedule_work(&vss->elapsed_period);
323-
324-
virtsnd_pcm_msg_send(vss);
325338
} else if (!vss->msg_count) {
326339
wake_up_all(&vss->msg_empty);
327340
}

sound/virtio/virtio_pcm_ops.c

Lines changed: 102 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,6 @@ static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)
282282

283283
vss->buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
284284
vss->hw_ptr = 0;
285-
vss->msg_last_enqueued = -1;
286285
} else {
287286
struct snd_pcm_runtime *runtime = substream->runtime;
288287
unsigned int buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
@@ -300,6 +299,11 @@ static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)
300299
vss->suspended = false;
301300
vss->msg_count = 0;
302301

302+
memset(&vss->pcm_indirect, 0, sizeof(vss->pcm_indirect));
303+
vss->pcm_indirect.sw_buffer_size =
304+
vss->pcm_indirect.hw_buffer_size =
305+
snd_pcm_lib_buffer_bytes(substream);
306+
303307
msg = virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_PREPARE,
304308
GFP_KERNEL);
305309
if (!msg)
@@ -324,7 +328,7 @@ static int virtsnd_pcm_trigger(struct snd_pcm_substream *substream, int command)
324328
struct virtio_snd_queue *queue;
325329
struct virtio_snd_msg *msg;
326330
unsigned long flags;
327-
int rc;
331+
int rc = 0;
328332

329333
switch (command) {
330334
case SNDRV_PCM_TRIGGER_START:
@@ -333,7 +337,8 @@ static int virtsnd_pcm_trigger(struct snd_pcm_substream *substream, int command)
333337

334338
spin_lock_irqsave(&queue->lock, flags);
335339
spin_lock(&vss->lock);
336-
rc = virtsnd_pcm_msg_send(vss);
340+
if (vss->direction == SNDRV_PCM_STREAM_CAPTURE)
341+
rc = virtsnd_pcm_msg_send(vss, 0, vss->buffer_bytes);
337342
if (!rc)
338343
vss->xfer_enabled = true;
339344
spin_unlock(&vss->lock);
@@ -428,37 +433,111 @@ static int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream)
428433
}
429434

430435
/**
431-
* virtsnd_pcm_pointer() - Get the current hardware position for the PCM
432-
* substream.
436+
* virtsnd_pcm_pb_pointer() - Get the current hardware position for the PCM
437+
* substream for playback.
433438
* @substream: Kernel ALSA substream.
434439
*
435-
* Context: Any context. Takes and releases the VirtIO substream spinlock.
440+
* Context: Any context.
436441
* Return: Hardware position in frames inside [0 ... buffer_size) range.
437442
*/
438443
static snd_pcm_uframes_t
439-
virtsnd_pcm_pointer(struct snd_pcm_substream *substream)
444+
virtsnd_pcm_pb_pointer(struct snd_pcm_substream *substream)
445+
{
446+
struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
447+
448+
return snd_pcm_indirect_playback_pointer(substream,
449+
&vss->pcm_indirect,
450+
vss->hw_ptr);
451+
}
452+
453+
/**
454+
* virtsnd_pcm_cp_pointer() - Get the current hardware position for the PCM
455+
* substream for capture.
456+
* @substream: Kernel ALSA substream.
457+
*
458+
* Context: Any context.
459+
* Return: Hardware position in frames inside [0 ... buffer_size) range.
460+
*/
461+
static snd_pcm_uframes_t
462+
virtsnd_pcm_cp_pointer(struct snd_pcm_substream *substream)
463+
{
464+
struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
465+
466+
return snd_pcm_indirect_capture_pointer(substream,
467+
&vss->pcm_indirect,
468+
vss->hw_ptr);
469+
}
470+
471+
static void virtsnd_pcm_trans_copy(struct snd_pcm_substream *substream,
472+
struct snd_pcm_indirect *rec, size_t bytes)
440473
{
441474
struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
442-
snd_pcm_uframes_t hw_ptr = SNDRV_PCM_POS_XRUN;
475+
476+
virtsnd_pcm_msg_send(vss, rec->sw_data, bytes);
477+
}
478+
479+
static int virtsnd_pcm_pb_ack(struct snd_pcm_substream *substream)
480+
{
481+
struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
482+
struct virtio_snd_queue *queue = virtsnd_pcm_queue(vss);
483+
unsigned long flags;
484+
int rc;
485+
486+
spin_lock_irqsave(&queue->lock, flags);
487+
spin_lock(&vss->lock);
488+
489+
rc = snd_pcm_indirect_playback_transfer(substream, &vss->pcm_indirect,
490+
virtsnd_pcm_trans_copy);
491+
492+
spin_unlock(&vss->lock);
493+
spin_unlock_irqrestore(&queue->lock, flags);
494+
495+
return rc;
496+
}
497+
498+
static int virtsnd_pcm_cp_ack(struct snd_pcm_substream *substream)
499+
{
500+
struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
501+
struct virtio_snd_queue *queue = virtsnd_pcm_queue(vss);
443502
unsigned long flags;
503+
int rc;
504+
505+
spin_lock_irqsave(&queue->lock, flags);
506+
spin_lock(&vss->lock);
507+
508+
rc = snd_pcm_indirect_capture_transfer(substream, &vss->pcm_indirect,
509+
virtsnd_pcm_trans_copy);
444510

445-
spin_lock_irqsave(&vss->lock, flags);
446-
if (!vss->xfer_xrun)
447-
hw_ptr = bytes_to_frames(substream->runtime, vss->hw_ptr);
448-
spin_unlock_irqrestore(&vss->lock, flags);
511+
spin_unlock(&vss->lock);
512+
spin_unlock_irqrestore(&queue->lock, flags);
449513

450-
return hw_ptr;
514+
return rc;
451515
}
452516

453517
/* PCM substream operators map. */
454-
const struct snd_pcm_ops virtsnd_pcm_ops = {
455-
.open = virtsnd_pcm_open,
456-
.close = virtsnd_pcm_close,
457-
.ioctl = snd_pcm_lib_ioctl,
458-
.hw_params = virtsnd_pcm_hw_params,
459-
.hw_free = virtsnd_pcm_hw_free,
460-
.prepare = virtsnd_pcm_prepare,
461-
.trigger = virtsnd_pcm_trigger,
462-
.sync_stop = virtsnd_pcm_sync_stop,
463-
.pointer = virtsnd_pcm_pointer,
518+
const struct snd_pcm_ops virtsnd_pcm_ops[] = {
519+
{
520+
.open = virtsnd_pcm_open,
521+
.close = virtsnd_pcm_close,
522+
.ioctl = snd_pcm_lib_ioctl,
523+
.hw_params = virtsnd_pcm_hw_params,
524+
.hw_free = virtsnd_pcm_hw_free,
525+
.prepare = virtsnd_pcm_prepare,
526+
.trigger = virtsnd_pcm_trigger,
527+
.sync_stop = virtsnd_pcm_sync_stop,
528+
.pointer = virtsnd_pcm_pb_pointer,
529+
.ack = virtsnd_pcm_pb_ack,
530+
},
531+
{
532+
.open = virtsnd_pcm_open,
533+
.close = virtsnd_pcm_close,
534+
.ioctl = snd_pcm_lib_ioctl,
535+
.hw_params = virtsnd_pcm_hw_params,
536+
.hw_free = virtsnd_pcm_hw_free,
537+
.prepare = virtsnd_pcm_prepare,
538+
.trigger = virtsnd_pcm_trigger,
539+
.sync_stop = virtsnd_pcm_sync_stop,
540+
.pointer = virtsnd_pcm_cp_pointer,
541+
.ack = virtsnd_pcm_cp_ack,
542+
},
464543
};

0 commit comments

Comments
 (0)