1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * virtio-snd: Virtio sound device |
4 | * Copyright (C) 2021 OpenSynergy GmbH |
5 | */ |
6 | #include <linux/moduleparam.h> |
7 | #include <linux/virtio_config.h> |
8 | |
9 | #include "virtio_card.h" |
10 | |
11 | static u32 pcm_buffer_ms = 160; |
12 | module_param(pcm_buffer_ms, uint, 0644); |
13 | MODULE_PARM_DESC(pcm_buffer_ms, "PCM substream buffer time in milliseconds" ); |
14 | |
15 | static u32 pcm_periods_min = 2; |
16 | module_param(pcm_periods_min, uint, 0644); |
17 | MODULE_PARM_DESC(pcm_periods_min, "Minimum number of PCM periods" ); |
18 | |
19 | static u32 pcm_periods_max = 16; |
20 | module_param(pcm_periods_max, uint, 0644); |
21 | MODULE_PARM_DESC(pcm_periods_max, "Maximum number of PCM periods" ); |
22 | |
23 | static u32 pcm_period_ms_min = 10; |
24 | module_param(pcm_period_ms_min, uint, 0644); |
25 | MODULE_PARM_DESC(pcm_period_ms_min, "Minimum PCM period time in milliseconds" ); |
26 | |
27 | static u32 pcm_period_ms_max = 80; |
28 | module_param(pcm_period_ms_max, uint, 0644); |
29 | MODULE_PARM_DESC(pcm_period_ms_max, "Maximum PCM period time in milliseconds" ); |
30 | |
31 | /* Map for converting VirtIO format to ALSA format. */ |
32 | static const snd_pcm_format_t g_v2a_format_map[] = { |
33 | [VIRTIO_SND_PCM_FMT_IMA_ADPCM] = SNDRV_PCM_FORMAT_IMA_ADPCM, |
34 | [VIRTIO_SND_PCM_FMT_MU_LAW] = SNDRV_PCM_FORMAT_MU_LAW, |
35 | [VIRTIO_SND_PCM_FMT_A_LAW] = SNDRV_PCM_FORMAT_A_LAW, |
36 | [VIRTIO_SND_PCM_FMT_S8] = SNDRV_PCM_FORMAT_S8, |
37 | [VIRTIO_SND_PCM_FMT_U8] = SNDRV_PCM_FORMAT_U8, |
38 | [VIRTIO_SND_PCM_FMT_S16] = SNDRV_PCM_FORMAT_S16_LE, |
39 | [VIRTIO_SND_PCM_FMT_U16] = SNDRV_PCM_FORMAT_U16_LE, |
40 | [VIRTIO_SND_PCM_FMT_S18_3] = SNDRV_PCM_FORMAT_S18_3LE, |
41 | [VIRTIO_SND_PCM_FMT_U18_3] = SNDRV_PCM_FORMAT_U18_3LE, |
42 | [VIRTIO_SND_PCM_FMT_S20_3] = SNDRV_PCM_FORMAT_S20_3LE, |
43 | [VIRTIO_SND_PCM_FMT_U20_3] = SNDRV_PCM_FORMAT_U20_3LE, |
44 | [VIRTIO_SND_PCM_FMT_S24_3] = SNDRV_PCM_FORMAT_S24_3LE, |
45 | [VIRTIO_SND_PCM_FMT_U24_3] = SNDRV_PCM_FORMAT_U24_3LE, |
46 | [VIRTIO_SND_PCM_FMT_S20] = SNDRV_PCM_FORMAT_S20_LE, |
47 | [VIRTIO_SND_PCM_FMT_U20] = SNDRV_PCM_FORMAT_U20_LE, |
48 | [VIRTIO_SND_PCM_FMT_S24] = SNDRV_PCM_FORMAT_S24_LE, |
49 | [VIRTIO_SND_PCM_FMT_U24] = SNDRV_PCM_FORMAT_U24_LE, |
50 | [VIRTIO_SND_PCM_FMT_S32] = SNDRV_PCM_FORMAT_S32_LE, |
51 | [VIRTIO_SND_PCM_FMT_U32] = SNDRV_PCM_FORMAT_U32_LE, |
52 | [VIRTIO_SND_PCM_FMT_FLOAT] = SNDRV_PCM_FORMAT_FLOAT_LE, |
53 | [VIRTIO_SND_PCM_FMT_FLOAT64] = SNDRV_PCM_FORMAT_FLOAT64_LE, |
54 | [VIRTIO_SND_PCM_FMT_DSD_U8] = SNDRV_PCM_FORMAT_DSD_U8, |
55 | [VIRTIO_SND_PCM_FMT_DSD_U16] = SNDRV_PCM_FORMAT_DSD_U16_LE, |
56 | [VIRTIO_SND_PCM_FMT_DSD_U32] = SNDRV_PCM_FORMAT_DSD_U32_LE, |
57 | [VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME] = |
58 | SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE |
59 | }; |
60 | |
61 | /* Map for converting VirtIO frame rate to ALSA frame rate. */ |
62 | struct virtsnd_v2a_rate { |
63 | unsigned int alsa_bit; |
64 | unsigned int rate; |
65 | }; |
66 | |
67 | static const struct virtsnd_v2a_rate g_v2a_rate_map[] = { |
68 | [VIRTIO_SND_PCM_RATE_5512] = { SNDRV_PCM_RATE_5512, .rate: 5512 }, |
69 | [VIRTIO_SND_PCM_RATE_8000] = { SNDRV_PCM_RATE_8000, .rate: 8000 }, |
70 | [VIRTIO_SND_PCM_RATE_11025] = { SNDRV_PCM_RATE_11025, .rate: 11025 }, |
71 | [VIRTIO_SND_PCM_RATE_16000] = { SNDRV_PCM_RATE_16000, .rate: 16000 }, |
72 | [VIRTIO_SND_PCM_RATE_22050] = { SNDRV_PCM_RATE_22050, .rate: 22050 }, |
73 | [VIRTIO_SND_PCM_RATE_32000] = { SNDRV_PCM_RATE_32000, .rate: 32000 }, |
74 | [VIRTIO_SND_PCM_RATE_44100] = { SNDRV_PCM_RATE_44100, .rate: 44100 }, |
75 | [VIRTIO_SND_PCM_RATE_48000] = { SNDRV_PCM_RATE_48000, .rate: 48000 }, |
76 | [VIRTIO_SND_PCM_RATE_64000] = { SNDRV_PCM_RATE_64000, .rate: 64000 }, |
77 | [VIRTIO_SND_PCM_RATE_88200] = { SNDRV_PCM_RATE_88200, .rate: 88200 }, |
78 | [VIRTIO_SND_PCM_RATE_96000] = { SNDRV_PCM_RATE_96000, .rate: 96000 }, |
79 | [VIRTIO_SND_PCM_RATE_176400] = { SNDRV_PCM_RATE_176400, .rate: 176400 }, |
80 | [VIRTIO_SND_PCM_RATE_192000] = { SNDRV_PCM_RATE_192000, .rate: 192000 } |
81 | }; |
82 | |
83 | /** |
84 | * virtsnd_pcm_build_hw() - Parse substream config and build HW descriptor. |
85 | * @vss: VirtIO substream. |
86 | * @info: VirtIO substream information entry. |
87 | * |
88 | * Context: Any context. |
89 | * Return: 0 on success, -EINVAL if configuration is invalid. |
90 | */ |
91 | static int virtsnd_pcm_build_hw(struct virtio_pcm_substream *vss, |
92 | struct virtio_snd_pcm_info *info) |
93 | { |
94 | struct virtio_device *vdev = vss->snd->vdev; |
95 | unsigned int i; |
96 | u64 values; |
97 | size_t sample_max = 0; |
98 | size_t sample_min = 0; |
99 | |
100 | vss->features = le32_to_cpu(info->features); |
101 | |
102 | /* |
103 | * TODO: set SNDRV_PCM_INFO_{BATCH,BLOCK_TRANSFER} if device supports |
104 | * only message-based transport. |
105 | */ |
106 | vss->hw.info = |
107 | SNDRV_PCM_INFO_MMAP | |
108 | SNDRV_PCM_INFO_MMAP_VALID | |
109 | SNDRV_PCM_INFO_BATCH | |
110 | SNDRV_PCM_INFO_BLOCK_TRANSFER | |
111 | SNDRV_PCM_INFO_INTERLEAVED | |
112 | SNDRV_PCM_INFO_PAUSE | |
113 | SNDRV_PCM_INFO_NO_REWINDS | |
114 | SNDRV_PCM_INFO_SYNC_APPLPTR; |
115 | |
116 | if (!info->channels_min || info->channels_min > info->channels_max) { |
117 | dev_err(&vdev->dev, |
118 | "SID %u: invalid channel range [%u %u]\n" , |
119 | vss->sid, info->channels_min, info->channels_max); |
120 | return -EINVAL; |
121 | } |
122 | |
123 | vss->hw.channels_min = info->channels_min; |
124 | vss->hw.channels_max = info->channels_max; |
125 | |
126 | values = le64_to_cpu(info->formats); |
127 | |
128 | vss->hw.formats = 0; |
129 | |
130 | for (i = 0; i < ARRAY_SIZE(g_v2a_format_map); ++i) |
131 | if (values & (1ULL << i)) { |
132 | snd_pcm_format_t alsa_fmt = g_v2a_format_map[i]; |
133 | int bytes = snd_pcm_format_physical_width(format: alsa_fmt) / 8; |
134 | |
135 | if (!sample_min || sample_min > bytes) |
136 | sample_min = bytes; |
137 | |
138 | if (sample_max < bytes) |
139 | sample_max = bytes; |
140 | |
141 | vss->hw.formats |= pcm_format_to_bits(pcm_format: alsa_fmt); |
142 | } |
143 | |
144 | if (!vss->hw.formats) { |
145 | dev_err(&vdev->dev, |
146 | "SID %u: no supported PCM sample formats found\n" , |
147 | vss->sid); |
148 | return -EINVAL; |
149 | } |
150 | |
151 | values = le64_to_cpu(info->rates); |
152 | |
153 | vss->hw.rates = 0; |
154 | |
155 | for (i = 0; i < ARRAY_SIZE(g_v2a_rate_map); ++i) |
156 | if (values & (1ULL << i)) { |
157 | if (!vss->hw.rate_min || |
158 | vss->hw.rate_min > g_v2a_rate_map[i].rate) |
159 | vss->hw.rate_min = g_v2a_rate_map[i].rate; |
160 | |
161 | if (vss->hw.rate_max < g_v2a_rate_map[i].rate) |
162 | vss->hw.rate_max = g_v2a_rate_map[i].rate; |
163 | |
164 | vss->hw.rates |= g_v2a_rate_map[i].alsa_bit; |
165 | } |
166 | |
167 | if (!vss->hw.rates) { |
168 | dev_err(&vdev->dev, |
169 | "SID %u: no supported PCM frame rates found\n" , |
170 | vss->sid); |
171 | return -EINVAL; |
172 | } |
173 | |
174 | vss->hw.periods_min = pcm_periods_min; |
175 | vss->hw.periods_max = pcm_periods_max; |
176 | |
177 | /* |
178 | * We must ensure that there is enough space in the buffer to store |
179 | * pcm_buffer_ms ms for the combination (Cmax, Smax, Rmax), where: |
180 | * Cmax = maximum supported number of channels, |
181 | * Smax = maximum supported sample size in bytes, |
182 | * Rmax = maximum supported frame rate. |
183 | */ |
184 | vss->hw.buffer_bytes_max = |
185 | PAGE_ALIGN(sample_max * vss->hw.channels_max * pcm_buffer_ms * |
186 | (vss->hw.rate_max / MSEC_PER_SEC)); |
187 | |
188 | /* |
189 | * We must ensure that the minimum period size is enough to store |
190 | * pcm_period_ms_min ms for the combination (Cmin, Smin, Rmin), where: |
191 | * Cmin = minimum supported number of channels, |
192 | * Smin = minimum supported sample size in bytes, |
193 | * Rmin = minimum supported frame rate. |
194 | */ |
195 | vss->hw.period_bytes_min = |
196 | sample_min * vss->hw.channels_min * pcm_period_ms_min * |
197 | (vss->hw.rate_min / MSEC_PER_SEC); |
198 | |
199 | /* |
200 | * We must ensure that the maximum period size is enough to store |
201 | * pcm_period_ms_max ms for the combination (Cmax, Smax, Rmax). |
202 | */ |
203 | vss->hw.period_bytes_max = |
204 | sample_max * vss->hw.channels_max * pcm_period_ms_max * |
205 | (vss->hw.rate_max / MSEC_PER_SEC); |
206 | |
207 | return 0; |
208 | } |
209 | |
210 | /** |
211 | * virtsnd_pcm_find() - Find the PCM device for the specified node ID. |
212 | * @snd: VirtIO sound device. |
213 | * @nid: Function node ID. |
214 | * |
215 | * Context: Any context. |
216 | * Return: a pointer to the PCM device or ERR_PTR(-ENOENT). |
217 | */ |
218 | struct virtio_pcm *virtsnd_pcm_find(struct virtio_snd *snd, u32 nid) |
219 | { |
220 | struct virtio_pcm *vpcm; |
221 | |
222 | list_for_each_entry(vpcm, &snd->pcm_list, list) |
223 | if (vpcm->nid == nid) |
224 | return vpcm; |
225 | |
226 | return ERR_PTR(error: -ENOENT); |
227 | } |
228 | |
229 | /** |
230 | * virtsnd_pcm_find_or_create() - Find or create the PCM device for the |
231 | * specified node ID. |
232 | * @snd: VirtIO sound device. |
233 | * @nid: Function node ID. |
234 | * |
235 | * Context: Any context that permits to sleep. |
236 | * Return: a pointer to the PCM device or ERR_PTR(-errno). |
237 | */ |
238 | struct virtio_pcm *virtsnd_pcm_find_or_create(struct virtio_snd *snd, u32 nid) |
239 | { |
240 | struct virtio_device *vdev = snd->vdev; |
241 | struct virtio_pcm *vpcm; |
242 | |
243 | vpcm = virtsnd_pcm_find(snd, nid); |
244 | if (!IS_ERR(ptr: vpcm)) |
245 | return vpcm; |
246 | |
247 | vpcm = devm_kzalloc(dev: &vdev->dev, size: sizeof(*vpcm), GFP_KERNEL); |
248 | if (!vpcm) |
249 | return ERR_PTR(error: -ENOMEM); |
250 | |
251 | vpcm->nid = nid; |
252 | list_add_tail(new: &vpcm->list, head: &snd->pcm_list); |
253 | |
254 | return vpcm; |
255 | } |
256 | |
257 | /** |
258 | * virtsnd_pcm_validate() - Validate if the device can be started. |
259 | * @vdev: VirtIO parent device. |
260 | * |
261 | * Context: Any context. |
262 | * Return: 0 on success, -EINVAL on failure. |
263 | */ |
264 | int virtsnd_pcm_validate(struct virtio_device *vdev) |
265 | { |
266 | if (pcm_periods_min < 2 || pcm_periods_min > pcm_periods_max) { |
267 | dev_err(&vdev->dev, |
268 | "invalid range [%u %u] of the number of PCM periods\n" , |
269 | pcm_periods_min, pcm_periods_max); |
270 | return -EINVAL; |
271 | } |
272 | |
273 | if (!pcm_period_ms_min || pcm_period_ms_min > pcm_period_ms_max) { |
274 | dev_err(&vdev->dev, |
275 | "invalid range [%u %u] of the size of the PCM period\n" , |
276 | pcm_period_ms_min, pcm_period_ms_max); |
277 | return -EINVAL; |
278 | } |
279 | |
280 | if (pcm_buffer_ms < pcm_periods_min * pcm_period_ms_min) { |
281 | dev_err(&vdev->dev, |
282 | "pcm_buffer_ms(=%u) value cannot be < %u ms\n" , |
283 | pcm_buffer_ms, pcm_periods_min * pcm_period_ms_min); |
284 | return -EINVAL; |
285 | } |
286 | |
287 | if (pcm_period_ms_max > pcm_buffer_ms / 2) { |
288 | dev_err(&vdev->dev, |
289 | "pcm_period_ms_max(=%u) value cannot be > %u ms\n" , |
290 | pcm_period_ms_max, pcm_buffer_ms / 2); |
291 | return -EINVAL; |
292 | } |
293 | |
294 | return 0; |
295 | } |
296 | |
297 | /** |
298 | * virtsnd_pcm_period_elapsed() - Kernel work function to handle the elapsed |
299 | * period state. |
300 | * @work: Elapsed period work. |
301 | * |
302 | * The main purpose of this function is to call snd_pcm_period_elapsed() in |
303 | * a process context, not in an interrupt context. This is necessary because PCM |
304 | * devices operate in non-atomic mode. |
305 | * |
306 | * Context: Process context. |
307 | */ |
308 | static void virtsnd_pcm_period_elapsed(struct work_struct *work) |
309 | { |
310 | struct virtio_pcm_substream *vss = |
311 | container_of(work, struct virtio_pcm_substream, elapsed_period); |
312 | |
313 | snd_pcm_period_elapsed(substream: vss->substream); |
314 | } |
315 | |
316 | /** |
317 | * virtsnd_pcm_parse_cfg() - Parse the stream configuration. |
318 | * @snd: VirtIO sound device. |
319 | * |
320 | * This function is called during initial device initialization. |
321 | * |
322 | * Context: Any context that permits to sleep. |
323 | * Return: 0 on success, -errno on failure. |
324 | */ |
325 | int virtsnd_pcm_parse_cfg(struct virtio_snd *snd) |
326 | { |
327 | struct virtio_device *vdev = snd->vdev; |
328 | struct virtio_snd_pcm_info *info; |
329 | u32 i; |
330 | int rc; |
331 | |
332 | virtio_cread_le(vdev, struct virtio_snd_config, streams, |
333 | &snd->nsubstreams); |
334 | if (!snd->nsubstreams) |
335 | return 0; |
336 | |
337 | snd->substreams = devm_kcalloc(dev: &vdev->dev, n: snd->nsubstreams, |
338 | size: sizeof(*snd->substreams), GFP_KERNEL); |
339 | if (!snd->substreams) |
340 | return -ENOMEM; |
341 | |
342 | info = kcalloc(n: snd->nsubstreams, size: sizeof(*info), GFP_KERNEL); |
343 | if (!info) |
344 | return -ENOMEM; |
345 | |
346 | rc = virtsnd_ctl_query_info(snd, command: VIRTIO_SND_R_PCM_INFO, start_id: 0, |
347 | count: snd->nsubstreams, size: sizeof(*info), info); |
348 | if (rc) |
349 | goto on_exit; |
350 | |
351 | for (i = 0; i < snd->nsubstreams; ++i) { |
352 | struct virtio_pcm_substream *vss = &snd->substreams[i]; |
353 | struct virtio_pcm *vpcm; |
354 | |
355 | vss->snd = snd; |
356 | vss->sid = i; |
357 | INIT_WORK(&vss->elapsed_period, virtsnd_pcm_period_elapsed); |
358 | init_waitqueue_head(&vss->msg_empty); |
359 | spin_lock_init(&vss->lock); |
360 | |
361 | rc = virtsnd_pcm_build_hw(vss, info: &info[i]); |
362 | if (rc) |
363 | goto on_exit; |
364 | |
365 | vss->nid = le32_to_cpu(info[i].hdr.hda_fn_nid); |
366 | |
367 | vpcm = virtsnd_pcm_find_or_create(snd, nid: vss->nid); |
368 | if (IS_ERR(ptr: vpcm)) { |
369 | rc = PTR_ERR(ptr: vpcm); |
370 | goto on_exit; |
371 | } |
372 | |
373 | switch (info[i].direction) { |
374 | case VIRTIO_SND_D_OUTPUT: |
375 | vss->direction = SNDRV_PCM_STREAM_PLAYBACK; |
376 | break; |
377 | case VIRTIO_SND_D_INPUT: |
378 | vss->direction = SNDRV_PCM_STREAM_CAPTURE; |
379 | break; |
380 | default: |
381 | dev_err(&vdev->dev, "SID %u: unknown direction (%u)\n" , |
382 | vss->sid, info[i].direction); |
383 | rc = -EINVAL; |
384 | goto on_exit; |
385 | } |
386 | |
387 | vpcm->streams[vss->direction].nsubstreams++; |
388 | } |
389 | |
390 | on_exit: |
391 | kfree(objp: info); |
392 | |
393 | return rc; |
394 | } |
395 | |
396 | /** |
397 | * virtsnd_pcm_build_devs() - Build ALSA PCM devices. |
398 | * @snd: VirtIO sound device. |
399 | * |
400 | * Context: Any context that permits to sleep. |
401 | * Return: 0 on success, -errno on failure. |
402 | */ |
403 | int virtsnd_pcm_build_devs(struct virtio_snd *snd) |
404 | { |
405 | struct virtio_device *vdev = snd->vdev; |
406 | struct virtio_pcm *vpcm; |
407 | u32 i; |
408 | int rc; |
409 | |
410 | list_for_each_entry(vpcm, &snd->pcm_list, list) { |
411 | unsigned int npbs = |
412 | vpcm->streams[SNDRV_PCM_STREAM_PLAYBACK].nsubstreams; |
413 | unsigned int ncps = |
414 | vpcm->streams[SNDRV_PCM_STREAM_CAPTURE].nsubstreams; |
415 | |
416 | if (!npbs && !ncps) |
417 | continue; |
418 | |
419 | rc = snd_pcm_new(card: snd->card, VIRTIO_SND_CARD_DRIVER, device: vpcm->nid, |
420 | playback_count: npbs, capture_count: ncps, rpcm: &vpcm->pcm); |
421 | if (rc) { |
422 | dev_err(&vdev->dev, "snd_pcm_new[%u] failed: %d\n" , |
423 | vpcm->nid, rc); |
424 | return rc; |
425 | } |
426 | |
427 | vpcm->pcm->info_flags = 0; |
428 | vpcm->pcm->dev_class = SNDRV_PCM_CLASS_GENERIC; |
429 | vpcm->pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX; |
430 | snprintf(buf: vpcm->pcm->name, size: sizeof(vpcm->pcm->name), |
431 | VIRTIO_SND_PCM_NAME " %u" , vpcm->pcm->device); |
432 | vpcm->pcm->private_data = vpcm; |
433 | vpcm->pcm->nonatomic = true; |
434 | |
435 | for (i = 0; i < ARRAY_SIZE(vpcm->streams); ++i) { |
436 | struct virtio_pcm_stream *stream = &vpcm->streams[i]; |
437 | |
438 | if (!stream->nsubstreams) |
439 | continue; |
440 | |
441 | stream->substreams = |
442 | devm_kcalloc(dev: &vdev->dev, n: stream->nsubstreams, |
443 | size: sizeof(*stream->substreams), |
444 | GFP_KERNEL); |
445 | if (!stream->substreams) |
446 | return -ENOMEM; |
447 | |
448 | stream->nsubstreams = 0; |
449 | } |
450 | } |
451 | |
452 | for (i = 0; i < snd->nsubstreams; ++i) { |
453 | struct virtio_pcm_stream *vs; |
454 | struct virtio_pcm_substream *vss = &snd->substreams[i]; |
455 | |
456 | vpcm = virtsnd_pcm_find(snd, nid: vss->nid); |
457 | if (IS_ERR(ptr: vpcm)) |
458 | return PTR_ERR(ptr: vpcm); |
459 | |
460 | vs = &vpcm->streams[vss->direction]; |
461 | vs->substreams[vs->nsubstreams++] = vss; |
462 | } |
463 | |
464 | list_for_each_entry(vpcm, &snd->pcm_list, list) { |
465 | for (i = 0; i < ARRAY_SIZE(vpcm->streams); ++i) { |
466 | struct virtio_pcm_stream *vs = &vpcm->streams[i]; |
467 | struct snd_pcm_str *ks = &vpcm->pcm->streams[i]; |
468 | struct snd_pcm_substream *kss; |
469 | |
470 | if (!vs->nsubstreams) |
471 | continue; |
472 | |
473 | for (kss = ks->substream; kss; kss = kss->next) |
474 | vs->substreams[kss->number]->substream = kss; |
475 | |
476 | snd_pcm_set_ops(pcm: vpcm->pcm, direction: i, ops: &virtsnd_pcm_ops[i]); |
477 | } |
478 | |
479 | snd_pcm_set_managed_buffer_all(pcm: vpcm->pcm, |
480 | SNDRV_DMA_TYPE_VMALLOC, NULL, |
481 | size: 0, max: 0); |
482 | } |
483 | |
484 | return 0; |
485 | } |
486 | |
487 | /** |
488 | * virtsnd_pcm_event() - Handle the PCM device event notification. |
489 | * @snd: VirtIO sound device. |
490 | * @event: VirtIO sound event. |
491 | * |
492 | * Context: Interrupt context. |
493 | */ |
494 | void virtsnd_pcm_event(struct virtio_snd *snd, struct virtio_snd_event *event) |
495 | { |
496 | struct virtio_pcm_substream *vss; |
497 | u32 sid = le32_to_cpu(event->data); |
498 | |
499 | if (sid >= snd->nsubstreams) |
500 | return; |
501 | |
502 | vss = &snd->substreams[sid]; |
503 | |
504 | switch (le32_to_cpu(event->hdr.code)) { |
505 | case VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED: |
506 | /* TODO: deal with shmem elapsed period */ |
507 | break; |
508 | case VIRTIO_SND_EVT_PCM_XRUN: |
509 | spin_lock(lock: &vss->lock); |
510 | if (vss->xfer_enabled) |
511 | vss->xfer_xrun = true; |
512 | spin_unlock(lock: &vss->lock); |
513 | break; |
514 | } |
515 | } |
516 | |