1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * virtio-snd: Virtio sound device |
4 | * Copyright (C) 2021 OpenSynergy GmbH |
5 | */ |
6 | #include <sound/pcm_params.h> |
7 | |
8 | #include "virtio_card.h" |
9 | |
10 | /* |
11 | * I/O messages lifetime |
12 | * --------------------- |
13 | * |
14 | * Allocation: |
15 | * Messages are initially allocated in the ops->hw_params() after the size and |
16 | * number of periods have been successfully negotiated. |
17 | * |
18 | * Freeing: |
19 | * Messages can be safely freed after the queue has been successfully flushed |
20 | * (RELEASE command in the ops->sync_stop()) and the ops->hw_free() has been |
21 | * called. |
22 | * |
23 | * When the substream stops, the ops->sync_stop() waits until the device has |
24 | * completed all pending messages. This wait can be interrupted either by a |
25 | * signal or due to a timeout. In this case, the device can still access |
26 | * messages even after calling ops->hw_free(). It can also issue an interrupt, |
27 | * and the interrupt handler will also try to access message structures. |
28 | * |
29 | * Therefore, freeing of already allocated messages occurs: |
30 | * |
31 | * - in ops->hw_params(), if this operator was called several times in a row, |
32 | * or if ops->hw_free() failed to free messages previously; |
33 | * |
34 | * - in ops->hw_free(), if the queue has been successfully flushed; |
35 | * |
36 | * - in dev->release(). |
37 | */ |
38 | |
39 | /* Map for converting ALSA format to VirtIO format. */ |
40 | struct virtsnd_a2v_format { |
41 | snd_pcm_format_t alsa_bit; |
42 | unsigned int vio_bit; |
43 | }; |
44 | |
45 | static const struct virtsnd_a2v_format g_a2v_format_map[] = { |
46 | { SNDRV_PCM_FORMAT_IMA_ADPCM, VIRTIO_SND_PCM_FMT_IMA_ADPCM }, |
47 | { SNDRV_PCM_FORMAT_MU_LAW, VIRTIO_SND_PCM_FMT_MU_LAW }, |
48 | { SNDRV_PCM_FORMAT_A_LAW, VIRTIO_SND_PCM_FMT_A_LAW }, |
49 | { SNDRV_PCM_FORMAT_S8, VIRTIO_SND_PCM_FMT_S8 }, |
50 | { SNDRV_PCM_FORMAT_U8, VIRTIO_SND_PCM_FMT_U8 }, |
51 | { SNDRV_PCM_FORMAT_S16_LE, VIRTIO_SND_PCM_FMT_S16 }, |
52 | { SNDRV_PCM_FORMAT_U16_LE, VIRTIO_SND_PCM_FMT_U16 }, |
53 | { SNDRV_PCM_FORMAT_S18_3LE, VIRTIO_SND_PCM_FMT_S18_3 }, |
54 | { SNDRV_PCM_FORMAT_U18_3LE, VIRTIO_SND_PCM_FMT_U18_3 }, |
55 | { SNDRV_PCM_FORMAT_S20_3LE, VIRTIO_SND_PCM_FMT_S20_3 }, |
56 | { SNDRV_PCM_FORMAT_U20_3LE, VIRTIO_SND_PCM_FMT_U20_3 }, |
57 | { SNDRV_PCM_FORMAT_S24_3LE, VIRTIO_SND_PCM_FMT_S24_3 }, |
58 | { SNDRV_PCM_FORMAT_U24_3LE, VIRTIO_SND_PCM_FMT_U24_3 }, |
59 | { SNDRV_PCM_FORMAT_S20_LE, VIRTIO_SND_PCM_FMT_S20 }, |
60 | { SNDRV_PCM_FORMAT_U20_LE, VIRTIO_SND_PCM_FMT_U20 }, |
61 | { SNDRV_PCM_FORMAT_S24_LE, VIRTIO_SND_PCM_FMT_S24 }, |
62 | { SNDRV_PCM_FORMAT_U24_LE, VIRTIO_SND_PCM_FMT_U24 }, |
63 | { SNDRV_PCM_FORMAT_S32_LE, VIRTIO_SND_PCM_FMT_S32 }, |
64 | { SNDRV_PCM_FORMAT_U32_LE, VIRTIO_SND_PCM_FMT_U32 }, |
65 | { SNDRV_PCM_FORMAT_FLOAT_LE, VIRTIO_SND_PCM_FMT_FLOAT }, |
66 | { SNDRV_PCM_FORMAT_FLOAT64_LE, VIRTIO_SND_PCM_FMT_FLOAT64 }, |
67 | { SNDRV_PCM_FORMAT_DSD_U8, VIRTIO_SND_PCM_FMT_DSD_U8 }, |
68 | { SNDRV_PCM_FORMAT_DSD_U16_LE, VIRTIO_SND_PCM_FMT_DSD_U16 }, |
69 | { SNDRV_PCM_FORMAT_DSD_U32_LE, VIRTIO_SND_PCM_FMT_DSD_U32 }, |
70 | { SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE, |
71 | VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME } |
72 | }; |
73 | |
74 | /* Map for converting ALSA frame rate to VirtIO frame rate. */ |
75 | struct virtsnd_a2v_rate { |
76 | unsigned int rate; |
77 | unsigned int vio_bit; |
78 | }; |
79 | |
80 | static const struct virtsnd_a2v_rate g_a2v_rate_map[] = { |
81 | { 5512, VIRTIO_SND_PCM_RATE_5512 }, |
82 | { 8000, VIRTIO_SND_PCM_RATE_8000 }, |
83 | { 11025, VIRTIO_SND_PCM_RATE_11025 }, |
84 | { 16000, VIRTIO_SND_PCM_RATE_16000 }, |
85 | { 22050, VIRTIO_SND_PCM_RATE_22050 }, |
86 | { 32000, VIRTIO_SND_PCM_RATE_32000 }, |
87 | { 44100, VIRTIO_SND_PCM_RATE_44100 }, |
88 | { 48000, VIRTIO_SND_PCM_RATE_48000 }, |
89 | { 64000, VIRTIO_SND_PCM_RATE_64000 }, |
90 | { 88200, VIRTIO_SND_PCM_RATE_88200 }, |
91 | { 96000, VIRTIO_SND_PCM_RATE_96000 }, |
92 | { 176400, VIRTIO_SND_PCM_RATE_176400 }, |
93 | { 192000, VIRTIO_SND_PCM_RATE_192000 } |
94 | }; |
95 | |
96 | static int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream); |
97 | |
98 | /** |
99 | * virtsnd_pcm_open() - Open the PCM substream. |
100 | * @substream: Kernel ALSA substream. |
101 | * |
102 | * Context: Process context. |
103 | * Return: 0 on success, -errno on failure. |
104 | */ |
105 | static int virtsnd_pcm_open(struct snd_pcm_substream *substream) |
106 | { |
107 | struct virtio_pcm *vpcm = snd_pcm_substream_chip(substream); |
108 | struct virtio_pcm_stream *vs = &vpcm->streams[substream->stream]; |
109 | struct virtio_pcm_substream *vss = vs->substreams[substream->number]; |
110 | |
111 | substream->runtime->hw = vss->hw; |
112 | substream->private_data = vss; |
113 | |
114 | snd_pcm_hw_constraint_integer(runtime: substream->runtime, |
115 | SNDRV_PCM_HW_PARAM_PERIODS); |
116 | |
117 | vss->stopped = !!virtsnd_pcm_msg_pending_num(vss); |
118 | vss->suspended = false; |
119 | |
120 | /* |
121 | * If the substream has already been used, then the I/O queue may be in |
122 | * an invalid state. Just in case, we do a check and try to return the |
123 | * queue to its original state, if necessary. |
124 | */ |
125 | return virtsnd_pcm_sync_stop(substream); |
126 | } |
127 | |
128 | /** |
129 | * virtsnd_pcm_close() - Close the PCM substream. |
130 | * @substream: Kernel ALSA substream. |
131 | * |
132 | * Context: Process context. |
133 | * Return: 0. |
134 | */ |
135 | static int virtsnd_pcm_close(struct snd_pcm_substream *substream) |
136 | { |
137 | return 0; |
138 | } |
139 | |
140 | /** |
141 | * virtsnd_pcm_dev_set_params() - Set the parameters of the PCM substream on |
142 | * the device side. |
143 | * @vss: VirtIO PCM substream. |
144 | * @buffer_bytes: Size of the hardware buffer. |
145 | * @period_bytes: Size of the hardware period. |
146 | * @channels: Selected number of channels. |
147 | * @format: Selected sample format (SNDRV_PCM_FORMAT_XXX). |
148 | * @rate: Selected frame rate. |
149 | * |
150 | * Context: Any context that permits to sleep. |
151 | * Return: 0 on success, -errno on failure. |
152 | */ |
153 | static int virtsnd_pcm_dev_set_params(struct virtio_pcm_substream *vss, |
154 | unsigned int buffer_bytes, |
155 | unsigned int period_bytes, |
156 | unsigned int channels, |
157 | snd_pcm_format_t format, |
158 | unsigned int rate) |
159 | { |
160 | struct virtio_snd_msg *msg; |
161 | struct virtio_snd_pcm_set_params *request; |
162 | unsigned int i; |
163 | int vformat = -1; |
164 | int vrate = -1; |
165 | |
166 | for (i = 0; i < ARRAY_SIZE(g_a2v_format_map); ++i) |
167 | if (g_a2v_format_map[i].alsa_bit == format) { |
168 | vformat = g_a2v_format_map[i].vio_bit; |
169 | |
170 | break; |
171 | } |
172 | |
173 | for (i = 0; i < ARRAY_SIZE(g_a2v_rate_map); ++i) |
174 | if (g_a2v_rate_map[i].rate == rate) { |
175 | vrate = g_a2v_rate_map[i].vio_bit; |
176 | |
177 | break; |
178 | } |
179 | |
180 | if (vformat == -1 || vrate == -1) |
181 | return -EINVAL; |
182 | |
183 | msg = virtsnd_pcm_ctl_msg_alloc(vss, command: VIRTIO_SND_R_PCM_SET_PARAMS, |
184 | GFP_KERNEL); |
185 | if (!msg) |
186 | return -ENOMEM; |
187 | |
188 | request = virtsnd_ctl_msg_request(msg); |
189 | request->buffer_bytes = cpu_to_le32(buffer_bytes); |
190 | request->period_bytes = cpu_to_le32(period_bytes); |
191 | request->channels = channels; |
192 | request->format = vformat; |
193 | request->rate = vrate; |
194 | |
195 | if (vss->features & (1U << VIRTIO_SND_PCM_F_MSG_POLLING)) |
196 | request->features |= |
197 | cpu_to_le32(1U << VIRTIO_SND_PCM_F_MSG_POLLING); |
198 | |
199 | if (vss->features & (1U << VIRTIO_SND_PCM_F_EVT_XRUNS)) |
200 | request->features |= |
201 | cpu_to_le32(1U << VIRTIO_SND_PCM_F_EVT_XRUNS); |
202 | |
203 | return virtsnd_ctl_msg_send_sync(snd: vss->snd, msg); |
204 | } |
205 | |
206 | /** |
207 | * virtsnd_pcm_hw_params() - Set the parameters of the PCM substream. |
208 | * @substream: Kernel ALSA substream. |
209 | * @hw_params: Hardware parameters. |
210 | * |
211 | * Context: Process context. |
212 | * Return: 0 on success, -errno on failure. |
213 | */ |
214 | static int virtsnd_pcm_hw_params(struct snd_pcm_substream *substream, |
215 | struct snd_pcm_hw_params *hw_params) |
216 | { |
217 | struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream); |
218 | struct virtio_device *vdev = vss->snd->vdev; |
219 | int rc; |
220 | |
221 | if (virtsnd_pcm_msg_pending_num(vss)) { |
222 | dev_err(&vdev->dev, "SID %u: invalid I/O queue state\n" , |
223 | vss->sid); |
224 | return -EBADFD; |
225 | } |
226 | |
227 | rc = virtsnd_pcm_dev_set_params(vss, buffer_bytes: params_buffer_bytes(p: hw_params), |
228 | period_bytes: params_period_bytes(p: hw_params), |
229 | channels: params_channels(p: hw_params), |
230 | format: params_format(p: hw_params), |
231 | rate: params_rate(p: hw_params)); |
232 | if (rc) |
233 | return rc; |
234 | |
235 | /* |
236 | * Free previously allocated messages if ops->hw_params() is called |
237 | * several times in a row, or if ops->hw_free() failed to free messages. |
238 | */ |
239 | virtsnd_pcm_msg_free(vss); |
240 | |
241 | return virtsnd_pcm_msg_alloc(vss, periods: params_periods(p: hw_params), |
242 | period_bytes: params_period_bytes(p: hw_params)); |
243 | } |
244 | |
245 | /** |
246 | * virtsnd_pcm_hw_free() - Reset the parameters of the PCM substream. |
247 | * @substream: Kernel ALSA substream. |
248 | * |
249 | * Context: Process context. |
250 | * Return: 0 |
251 | */ |
252 | static int virtsnd_pcm_hw_free(struct snd_pcm_substream *substream) |
253 | { |
254 | struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream); |
255 | |
256 | /* If the queue is flushed, we can safely free the messages here. */ |
257 | if (!virtsnd_pcm_msg_pending_num(vss)) |
258 | virtsnd_pcm_msg_free(vss); |
259 | |
260 | return 0; |
261 | } |
262 | |
263 | /** |
264 | * virtsnd_pcm_prepare() - Prepare the PCM substream. |
265 | * @substream: Kernel ALSA substream. |
266 | * |
267 | * Context: Process context. |
268 | * Return: 0 on success, -errno on failure. |
269 | */ |
270 | static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream) |
271 | { |
272 | struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream); |
273 | struct virtio_device *vdev = vss->snd->vdev; |
274 | struct virtio_snd_msg *msg; |
275 | |
276 | if (!vss->suspended) { |
277 | if (virtsnd_pcm_msg_pending_num(vss)) { |
278 | dev_err(&vdev->dev, "SID %u: invalid I/O queue state\n" , |
279 | vss->sid); |
280 | return -EBADFD; |
281 | } |
282 | |
283 | vss->buffer_bytes = snd_pcm_lib_buffer_bytes(substream); |
284 | vss->hw_ptr = 0; |
285 | } else { |
286 | struct snd_pcm_runtime *runtime = substream->runtime; |
287 | unsigned int buffer_bytes = snd_pcm_lib_buffer_bytes(substream); |
288 | unsigned int period_bytes = snd_pcm_lib_period_bytes(substream); |
289 | int rc; |
290 | |
291 | rc = virtsnd_pcm_dev_set_params(vss, buffer_bytes, period_bytes, |
292 | channels: runtime->channels, |
293 | format: runtime->format, rate: runtime->rate); |
294 | if (rc) |
295 | return rc; |
296 | } |
297 | |
298 | vss->xfer_xrun = false; |
299 | vss->suspended = false; |
300 | vss->msg_count = 0; |
301 | |
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 | |
307 | msg = virtsnd_pcm_ctl_msg_alloc(vss, command: VIRTIO_SND_R_PCM_PREPARE, |
308 | GFP_KERNEL); |
309 | if (!msg) |
310 | return -ENOMEM; |
311 | |
312 | return virtsnd_ctl_msg_send_sync(snd: vss->snd, msg); |
313 | } |
314 | |
315 | /** |
316 | * virtsnd_pcm_trigger() - Process command for the PCM substream. |
317 | * @substream: Kernel ALSA substream. |
318 | * @command: Substream command (SNDRV_PCM_TRIGGER_XXX). |
319 | * |
320 | * Context: Any context. Takes and releases the VirtIO substream spinlock. |
321 | * May take and release the tx/rx queue spinlock. |
322 | * Return: 0 on success, -errno on failure. |
323 | */ |
324 | static int virtsnd_pcm_trigger(struct snd_pcm_substream *substream, int command) |
325 | { |
326 | struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream); |
327 | struct virtio_snd *snd = vss->snd; |
328 | struct virtio_snd_queue *queue; |
329 | struct virtio_snd_msg *msg; |
330 | unsigned long flags; |
331 | int rc = 0; |
332 | |
333 | switch (command) { |
334 | case SNDRV_PCM_TRIGGER_START: |
335 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
336 | queue = virtsnd_pcm_queue(vss); |
337 | |
338 | spin_lock_irqsave(&queue->lock, flags); |
339 | spin_lock(lock: &vss->lock); |
340 | if (vss->direction == SNDRV_PCM_STREAM_CAPTURE) |
341 | rc = virtsnd_pcm_msg_send(vss, offset: 0, bytes: vss->buffer_bytes); |
342 | if (!rc) |
343 | vss->xfer_enabled = true; |
344 | spin_unlock(lock: &vss->lock); |
345 | spin_unlock_irqrestore(lock: &queue->lock, flags); |
346 | if (rc) |
347 | return rc; |
348 | |
349 | msg = virtsnd_pcm_ctl_msg_alloc(vss, command: VIRTIO_SND_R_PCM_START, |
350 | GFP_KERNEL); |
351 | if (!msg) { |
352 | spin_lock_irqsave(&vss->lock, flags); |
353 | vss->xfer_enabled = false; |
354 | spin_unlock_irqrestore(lock: &vss->lock, flags); |
355 | |
356 | return -ENOMEM; |
357 | } |
358 | |
359 | return virtsnd_ctl_msg_send_sync(snd, msg); |
360 | case SNDRV_PCM_TRIGGER_SUSPEND: |
361 | vss->suspended = true; |
362 | fallthrough; |
363 | case SNDRV_PCM_TRIGGER_STOP: |
364 | vss->stopped = true; |
365 | fallthrough; |
366 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
367 | spin_lock_irqsave(&vss->lock, flags); |
368 | vss->xfer_enabled = false; |
369 | spin_unlock_irqrestore(lock: &vss->lock, flags); |
370 | |
371 | msg = virtsnd_pcm_ctl_msg_alloc(vss, command: VIRTIO_SND_R_PCM_STOP, |
372 | GFP_KERNEL); |
373 | if (!msg) |
374 | return -ENOMEM; |
375 | |
376 | return virtsnd_ctl_msg_send_sync(snd, msg); |
377 | default: |
378 | return -EINVAL; |
379 | } |
380 | } |
381 | |
382 | /** |
383 | * virtsnd_pcm_sync_stop() - Synchronous PCM substream stop. |
384 | * @substream: Kernel ALSA substream. |
385 | * |
386 | * The function can be called both from the upper level or from the driver |
387 | * itself. |
388 | * |
389 | * Context: Process context. Takes and releases the VirtIO substream spinlock. |
390 | * Return: 0 on success, -errno on failure. |
391 | */ |
392 | static int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream) |
393 | { |
394 | struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream); |
395 | struct virtio_snd *snd = vss->snd; |
396 | struct virtio_snd_msg *msg; |
397 | unsigned int js = msecs_to_jiffies(m: virtsnd_msg_timeout_ms); |
398 | int rc; |
399 | |
400 | cancel_work_sync(work: &vss->elapsed_period); |
401 | |
402 | if (!vss->stopped) |
403 | return 0; |
404 | |
405 | msg = virtsnd_pcm_ctl_msg_alloc(vss, command: VIRTIO_SND_R_PCM_RELEASE, |
406 | GFP_KERNEL); |
407 | if (!msg) |
408 | return -ENOMEM; |
409 | |
410 | rc = virtsnd_ctl_msg_send_sync(snd, msg); |
411 | if (rc) |
412 | return rc; |
413 | |
414 | /* |
415 | * The spec states that upon receipt of the RELEASE command "the device |
416 | * MUST complete all pending I/O messages for the specified stream ID". |
417 | * Thus, we consider the absence of I/O messages in the queue as an |
418 | * indication that the substream has been released. |
419 | */ |
420 | rc = wait_event_interruptible_timeout(vss->msg_empty, |
421 | !virtsnd_pcm_msg_pending_num(vss), |
422 | js); |
423 | if (rc <= 0) { |
424 | dev_warn(&snd->vdev->dev, "SID %u: failed to flush I/O queue\n" , |
425 | vss->sid); |
426 | |
427 | return !rc ? -ETIMEDOUT : rc; |
428 | } |
429 | |
430 | vss->stopped = false; |
431 | |
432 | return 0; |
433 | } |
434 | |
435 | /** |
436 | * virtsnd_pcm_pb_pointer() - Get the current hardware position for the PCM |
437 | * substream for playback. |
438 | * @substream: Kernel ALSA substream. |
439 | * |
440 | * Context: Any context. |
441 | * Return: Hardware position in frames inside [0 ... buffer_size) range. |
442 | */ |
443 | static snd_pcm_uframes_t |
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 | rec: &vss->pcm_indirect, |
450 | ptr: 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 | rec: &vss->pcm_indirect, |
468 | ptr: 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) |
473 | { |
474 | struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream); |
475 | |
476 | virtsnd_pcm_msg_send(vss, offset: 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(lock: &vss->lock); |
488 | |
489 | rc = snd_pcm_indirect_playback_transfer(substream, rec: &vss->pcm_indirect, |
490 | copy: virtsnd_pcm_trans_copy); |
491 | |
492 | spin_unlock(lock: &vss->lock); |
493 | spin_unlock_irqrestore(lock: &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); |
502 | unsigned long flags; |
503 | int rc; |
504 | |
505 | spin_lock_irqsave(&queue->lock, flags); |
506 | spin_lock(lock: &vss->lock); |
507 | |
508 | rc = snd_pcm_indirect_capture_transfer(substream, rec: &vss->pcm_indirect, |
509 | copy: virtsnd_pcm_trans_copy); |
510 | |
511 | spin_unlock(lock: &vss->lock); |
512 | spin_unlock_irqrestore(lock: &queue->lock, flags); |
513 | |
514 | return rc; |
515 | } |
516 | |
517 | /* PCM substream operators map. */ |
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 | }, |
543 | }; |
544 | |