1 | // SPDX-License-Identifier: GPL-2.0 OR MIT |
2 | |
3 | /* |
4 | * Xen para-virtual sound device |
5 | * |
6 | * Copyright (C) 2016-2018 EPAM Systems Inc. |
7 | * |
8 | * Author: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com> |
9 | */ |
10 | |
11 | #include <linux/platform_device.h> |
12 | |
13 | #include <sound/core.h> |
14 | #include <sound/pcm.h> |
15 | #include <sound/pcm_params.h> |
16 | |
17 | #include <xen/xenbus.h> |
18 | #include <xen/xen-front-pgdir-shbuf.h> |
19 | |
20 | #include "xen_snd_front.h" |
21 | #include "xen_snd_front_alsa.h" |
22 | #include "xen_snd_front_cfg.h" |
23 | #include "xen_snd_front_evtchnl.h" |
24 | |
25 | struct xen_snd_front_pcm_stream_info { |
26 | struct xen_snd_front_info *front_info; |
27 | struct xen_snd_front_evtchnl_pair *evt_pair; |
28 | |
29 | /* This is the shared buffer with its backing storage. */ |
30 | struct xen_front_pgdir_shbuf shbuf; |
31 | u8 *buffer; |
32 | size_t buffer_sz; |
33 | int num_pages; |
34 | struct page **pages; |
35 | |
36 | int index; |
37 | |
38 | bool is_open; |
39 | struct snd_pcm_hardware pcm_hw; |
40 | |
41 | /* Number of processed frames as reported by the backend. */ |
42 | snd_pcm_uframes_t be_cur_frame; |
43 | /* Current HW pointer to be reported via .period callback. */ |
44 | atomic_t hw_ptr; |
45 | /* Modulo of the number of processed frames - for period detection. */ |
46 | u32 out_frames; |
47 | }; |
48 | |
49 | struct xen_snd_front_pcm_instance_info { |
50 | struct xen_snd_front_card_info *card_info; |
51 | struct snd_pcm *pcm; |
52 | struct snd_pcm_hardware pcm_hw; |
53 | int num_pcm_streams_pb; |
54 | struct xen_snd_front_pcm_stream_info *streams_pb; |
55 | int num_pcm_streams_cap; |
56 | struct xen_snd_front_pcm_stream_info *streams_cap; |
57 | }; |
58 | |
59 | struct xen_snd_front_card_info { |
60 | struct xen_snd_front_info *front_info; |
61 | struct snd_card *card; |
62 | struct snd_pcm_hardware pcm_hw; |
63 | int num_pcm_instances; |
64 | struct xen_snd_front_pcm_instance_info *pcm_instances; |
65 | }; |
66 | |
67 | struct alsa_sndif_sample_format { |
68 | u8 sndif; |
69 | snd_pcm_format_t alsa; |
70 | }; |
71 | |
72 | struct alsa_sndif_hw_param { |
73 | u8 sndif; |
74 | snd_pcm_hw_param_t alsa; |
75 | }; |
76 | |
77 | static const struct alsa_sndif_sample_format ALSA_SNDIF_FORMATS[] = { |
78 | { |
79 | .sndif = XENSND_PCM_FORMAT_U8, |
80 | .alsa = SNDRV_PCM_FORMAT_U8 |
81 | }, |
82 | { |
83 | .sndif = XENSND_PCM_FORMAT_S8, |
84 | .alsa = SNDRV_PCM_FORMAT_S8 |
85 | }, |
86 | { |
87 | .sndif = XENSND_PCM_FORMAT_U16_LE, |
88 | .alsa = SNDRV_PCM_FORMAT_U16_LE |
89 | }, |
90 | { |
91 | .sndif = XENSND_PCM_FORMAT_U16_BE, |
92 | .alsa = SNDRV_PCM_FORMAT_U16_BE |
93 | }, |
94 | { |
95 | .sndif = XENSND_PCM_FORMAT_S16_LE, |
96 | .alsa = SNDRV_PCM_FORMAT_S16_LE |
97 | }, |
98 | { |
99 | .sndif = XENSND_PCM_FORMAT_S16_BE, |
100 | .alsa = SNDRV_PCM_FORMAT_S16_BE |
101 | }, |
102 | { |
103 | .sndif = XENSND_PCM_FORMAT_U24_LE, |
104 | .alsa = SNDRV_PCM_FORMAT_U24_LE |
105 | }, |
106 | { |
107 | .sndif = XENSND_PCM_FORMAT_U24_BE, |
108 | .alsa = SNDRV_PCM_FORMAT_U24_BE |
109 | }, |
110 | { |
111 | .sndif = XENSND_PCM_FORMAT_S24_LE, |
112 | .alsa = SNDRV_PCM_FORMAT_S24_LE |
113 | }, |
114 | { |
115 | .sndif = XENSND_PCM_FORMAT_S24_BE, |
116 | .alsa = SNDRV_PCM_FORMAT_S24_BE |
117 | }, |
118 | { |
119 | .sndif = XENSND_PCM_FORMAT_U32_LE, |
120 | .alsa = SNDRV_PCM_FORMAT_U32_LE |
121 | }, |
122 | { |
123 | .sndif = XENSND_PCM_FORMAT_U32_BE, |
124 | .alsa = SNDRV_PCM_FORMAT_U32_BE |
125 | }, |
126 | { |
127 | .sndif = XENSND_PCM_FORMAT_S32_LE, |
128 | .alsa = SNDRV_PCM_FORMAT_S32_LE |
129 | }, |
130 | { |
131 | .sndif = XENSND_PCM_FORMAT_S32_BE, |
132 | .alsa = SNDRV_PCM_FORMAT_S32_BE |
133 | }, |
134 | { |
135 | .sndif = XENSND_PCM_FORMAT_A_LAW, |
136 | .alsa = SNDRV_PCM_FORMAT_A_LAW |
137 | }, |
138 | { |
139 | .sndif = XENSND_PCM_FORMAT_MU_LAW, |
140 | .alsa = SNDRV_PCM_FORMAT_MU_LAW |
141 | }, |
142 | { |
143 | .sndif = XENSND_PCM_FORMAT_F32_LE, |
144 | .alsa = SNDRV_PCM_FORMAT_FLOAT_LE |
145 | }, |
146 | { |
147 | .sndif = XENSND_PCM_FORMAT_F32_BE, |
148 | .alsa = SNDRV_PCM_FORMAT_FLOAT_BE |
149 | }, |
150 | { |
151 | .sndif = XENSND_PCM_FORMAT_F64_LE, |
152 | .alsa = SNDRV_PCM_FORMAT_FLOAT64_LE |
153 | }, |
154 | { |
155 | .sndif = XENSND_PCM_FORMAT_F64_BE, |
156 | .alsa = SNDRV_PCM_FORMAT_FLOAT64_BE |
157 | }, |
158 | { |
159 | .sndif = XENSND_PCM_FORMAT_IEC958_SUBFRAME_LE, |
160 | .alsa = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE |
161 | }, |
162 | { |
163 | .sndif = XENSND_PCM_FORMAT_IEC958_SUBFRAME_BE, |
164 | .alsa = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE |
165 | }, |
166 | { |
167 | .sndif = XENSND_PCM_FORMAT_IMA_ADPCM, |
168 | .alsa = SNDRV_PCM_FORMAT_IMA_ADPCM |
169 | }, |
170 | { |
171 | .sndif = XENSND_PCM_FORMAT_MPEG, |
172 | .alsa = SNDRV_PCM_FORMAT_MPEG |
173 | }, |
174 | { |
175 | .sndif = XENSND_PCM_FORMAT_GSM, |
176 | .alsa = SNDRV_PCM_FORMAT_GSM |
177 | }, |
178 | }; |
179 | |
180 | static int to_sndif_format(snd_pcm_format_t format) |
181 | { |
182 | int i; |
183 | |
184 | for (i = 0; i < ARRAY_SIZE(ALSA_SNDIF_FORMATS); i++) |
185 | if (ALSA_SNDIF_FORMATS[i].alsa == format) |
186 | return ALSA_SNDIF_FORMATS[i].sndif; |
187 | |
188 | return -EINVAL; |
189 | } |
190 | |
191 | static u64 to_sndif_formats_mask(u64 alsa_formats) |
192 | { |
193 | u64 mask; |
194 | int i; |
195 | |
196 | mask = 0; |
197 | for (i = 0; i < ARRAY_SIZE(ALSA_SNDIF_FORMATS); i++) |
198 | if (pcm_format_to_bits(pcm_format: ALSA_SNDIF_FORMATS[i].alsa) & alsa_formats) |
199 | mask |= BIT_ULL(ALSA_SNDIF_FORMATS[i].sndif); |
200 | |
201 | return mask; |
202 | } |
203 | |
204 | static u64 to_alsa_formats_mask(u64 sndif_formats) |
205 | { |
206 | u64 mask; |
207 | int i; |
208 | |
209 | mask = 0; |
210 | for (i = 0; i < ARRAY_SIZE(ALSA_SNDIF_FORMATS); i++) |
211 | if (BIT_ULL(ALSA_SNDIF_FORMATS[i].sndif) & sndif_formats) |
212 | mask |= pcm_format_to_bits(pcm_format: ALSA_SNDIF_FORMATS[i].alsa); |
213 | |
214 | return mask; |
215 | } |
216 | |
217 | static void stream_clear(struct xen_snd_front_pcm_stream_info *stream) |
218 | { |
219 | stream->is_open = false; |
220 | stream->be_cur_frame = 0; |
221 | stream->out_frames = 0; |
222 | atomic_set(v: &stream->hw_ptr, i: 0); |
223 | xen_snd_front_evtchnl_pair_clear(evt_pair: stream->evt_pair); |
224 | memset(&stream->shbuf, 0, sizeof(stream->shbuf)); |
225 | stream->buffer = NULL; |
226 | stream->buffer_sz = 0; |
227 | stream->pages = NULL; |
228 | stream->num_pages = 0; |
229 | } |
230 | |
231 | static void stream_free(struct xen_snd_front_pcm_stream_info *stream) |
232 | { |
233 | xen_front_pgdir_shbuf_unmap(buf: &stream->shbuf); |
234 | xen_front_pgdir_shbuf_free(buf: &stream->shbuf); |
235 | if (stream->buffer) |
236 | free_pages_exact(virt: stream->buffer, size: stream->buffer_sz); |
237 | kfree(objp: stream->pages); |
238 | stream_clear(stream); |
239 | } |
240 | |
241 | static struct xen_snd_front_pcm_stream_info * |
242 | stream_get(struct snd_pcm_substream *substream) |
243 | { |
244 | struct xen_snd_front_pcm_instance_info *pcm_instance = |
245 | snd_pcm_substream_chip(substream); |
246 | struct xen_snd_front_pcm_stream_info *stream; |
247 | |
248 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
249 | stream = &pcm_instance->streams_pb[substream->number]; |
250 | else |
251 | stream = &pcm_instance->streams_cap[substream->number]; |
252 | |
253 | return stream; |
254 | } |
255 | |
256 | static int alsa_hw_rule(struct snd_pcm_hw_params *params, |
257 | struct snd_pcm_hw_rule *rule) |
258 | { |
259 | struct xen_snd_front_pcm_stream_info *stream = rule->private; |
260 | struct device *dev = &stream->front_info->xb_dev->dev; |
261 | struct snd_mask *formats = |
262 | hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); |
263 | struct snd_interval *rates = |
264 | hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); |
265 | struct snd_interval *channels = |
266 | hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); |
267 | struct snd_interval *period = |
268 | hw_param_interval(params, |
269 | SNDRV_PCM_HW_PARAM_PERIOD_SIZE); |
270 | struct snd_interval *buffer = |
271 | hw_param_interval(params, |
272 | SNDRV_PCM_HW_PARAM_BUFFER_SIZE); |
273 | struct xensnd_query_hw_param req; |
274 | struct xensnd_query_hw_param resp; |
275 | struct snd_interval interval; |
276 | struct snd_mask mask; |
277 | u64 sndif_formats; |
278 | int changed, ret; |
279 | |
280 | /* Collect all the values we need for the query. */ |
281 | |
282 | req.formats = to_sndif_formats_mask(alsa_formats: (u64)formats->bits[0] | |
283 | (u64)(formats->bits[1]) << 32); |
284 | |
285 | req.rates.min = rates->min; |
286 | req.rates.max = rates->max; |
287 | |
288 | req.channels.min = channels->min; |
289 | req.channels.max = channels->max; |
290 | |
291 | req.buffer.min = buffer->min; |
292 | req.buffer.max = buffer->max; |
293 | |
294 | req.period.min = period->min; |
295 | req.period.max = period->max; |
296 | |
297 | ret = xen_snd_front_stream_query_hw_param(evtchnl: &stream->evt_pair->req, |
298 | hw_param_req: &req, hw_param_resp: &resp); |
299 | if (ret < 0) { |
300 | /* Check if this is due to backend communication error. */ |
301 | if (ret == -EIO || ret == -ETIMEDOUT) |
302 | dev_err(dev, "Failed to query ALSA HW parameters\n" ); |
303 | return ret; |
304 | } |
305 | |
306 | /* Refine HW parameters after the query. */ |
307 | changed = 0; |
308 | |
309 | sndif_formats = to_alsa_formats_mask(sndif_formats: resp.formats); |
310 | snd_mask_none(mask: &mask); |
311 | mask.bits[0] = (u32)sndif_formats; |
312 | mask.bits[1] = (u32)(sndif_formats >> 32); |
313 | ret = snd_mask_refine(mask: formats, v: &mask); |
314 | if (ret < 0) |
315 | return ret; |
316 | changed |= ret; |
317 | |
318 | interval.openmin = 0; |
319 | interval.openmax = 0; |
320 | interval.integer = 1; |
321 | |
322 | interval.min = resp.rates.min; |
323 | interval.max = resp.rates.max; |
324 | ret = snd_interval_refine(i: rates, v: &interval); |
325 | if (ret < 0) |
326 | return ret; |
327 | changed |= ret; |
328 | |
329 | interval.min = resp.channels.min; |
330 | interval.max = resp.channels.max; |
331 | ret = snd_interval_refine(i: channels, v: &interval); |
332 | if (ret < 0) |
333 | return ret; |
334 | changed |= ret; |
335 | |
336 | interval.min = resp.buffer.min; |
337 | interval.max = resp.buffer.max; |
338 | ret = snd_interval_refine(i: buffer, v: &interval); |
339 | if (ret < 0) |
340 | return ret; |
341 | changed |= ret; |
342 | |
343 | interval.min = resp.period.min; |
344 | interval.max = resp.period.max; |
345 | ret = snd_interval_refine(i: period, v: &interval); |
346 | if (ret < 0) |
347 | return ret; |
348 | changed |= ret; |
349 | |
350 | return changed; |
351 | } |
352 | |
353 | static int alsa_open(struct snd_pcm_substream *substream) |
354 | { |
355 | struct xen_snd_front_pcm_instance_info *pcm_instance = |
356 | snd_pcm_substream_chip(substream); |
357 | struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); |
358 | struct snd_pcm_runtime *runtime = substream->runtime; |
359 | struct xen_snd_front_info *front_info = |
360 | pcm_instance->card_info->front_info; |
361 | struct device *dev = &front_info->xb_dev->dev; |
362 | int ret; |
363 | |
364 | /* |
365 | * Return our HW properties: override defaults with those configured |
366 | * via XenStore. |
367 | */ |
368 | runtime->hw = stream->pcm_hw; |
369 | runtime->hw.info &= ~(SNDRV_PCM_INFO_MMAP | |
370 | SNDRV_PCM_INFO_MMAP_VALID | |
371 | SNDRV_PCM_INFO_DOUBLE | |
372 | SNDRV_PCM_INFO_BATCH | |
373 | SNDRV_PCM_INFO_NONINTERLEAVED | |
374 | SNDRV_PCM_INFO_RESUME | |
375 | SNDRV_PCM_INFO_PAUSE); |
376 | runtime->hw.info |= SNDRV_PCM_INFO_INTERLEAVED; |
377 | |
378 | stream->evt_pair = &front_info->evt_pairs[stream->index]; |
379 | |
380 | stream->front_info = front_info; |
381 | |
382 | stream->evt_pair->evt.u.evt.substream = substream; |
383 | |
384 | stream_clear(stream); |
385 | |
386 | xen_snd_front_evtchnl_pair_set_connected(evt_pair: stream->evt_pair, is_connected: true); |
387 | |
388 | ret = snd_pcm_hw_rule_add(runtime, cond: 0, SNDRV_PCM_HW_PARAM_FORMAT, |
389 | func: alsa_hw_rule, private: stream, |
390 | SNDRV_PCM_HW_PARAM_FORMAT, -1); |
391 | if (ret) { |
392 | dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_FORMAT\n" ); |
393 | return ret; |
394 | } |
395 | |
396 | ret = snd_pcm_hw_rule_add(runtime, cond: 0, SNDRV_PCM_HW_PARAM_RATE, |
397 | func: alsa_hw_rule, private: stream, |
398 | SNDRV_PCM_HW_PARAM_RATE, -1); |
399 | if (ret) { |
400 | dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_RATE\n" ); |
401 | return ret; |
402 | } |
403 | |
404 | ret = snd_pcm_hw_rule_add(runtime, cond: 0, SNDRV_PCM_HW_PARAM_CHANNELS, |
405 | func: alsa_hw_rule, private: stream, |
406 | SNDRV_PCM_HW_PARAM_CHANNELS, -1); |
407 | if (ret) { |
408 | dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_CHANNELS\n" ); |
409 | return ret; |
410 | } |
411 | |
412 | ret = snd_pcm_hw_rule_add(runtime, cond: 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, |
413 | func: alsa_hw_rule, private: stream, |
414 | SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1); |
415 | if (ret) { |
416 | dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_PERIOD_SIZE\n" ); |
417 | return ret; |
418 | } |
419 | |
420 | ret = snd_pcm_hw_rule_add(runtime, cond: 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, |
421 | func: alsa_hw_rule, private: stream, |
422 | SNDRV_PCM_HW_PARAM_BUFFER_SIZE, -1); |
423 | if (ret) { |
424 | dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_BUFFER_SIZE\n" ); |
425 | return ret; |
426 | } |
427 | |
428 | return 0; |
429 | } |
430 | |
431 | static int alsa_close(struct snd_pcm_substream *substream) |
432 | { |
433 | struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); |
434 | |
435 | xen_snd_front_evtchnl_pair_set_connected(evt_pair: stream->evt_pair, is_connected: false); |
436 | return 0; |
437 | } |
438 | |
439 | static int shbuf_setup_backstore(struct xen_snd_front_pcm_stream_info *stream, |
440 | size_t buffer_sz) |
441 | { |
442 | int i; |
443 | |
444 | stream->buffer = alloc_pages_exact(size: buffer_sz, GFP_KERNEL); |
445 | if (!stream->buffer) |
446 | return -ENOMEM; |
447 | |
448 | stream->buffer_sz = buffer_sz; |
449 | stream->num_pages = DIV_ROUND_UP(stream->buffer_sz, PAGE_SIZE); |
450 | stream->pages = kcalloc(n: stream->num_pages, size: sizeof(struct page *), |
451 | GFP_KERNEL); |
452 | if (!stream->pages) |
453 | return -ENOMEM; |
454 | |
455 | for (i = 0; i < stream->num_pages; i++) |
456 | stream->pages[i] = virt_to_page(stream->buffer + i * PAGE_SIZE); |
457 | |
458 | return 0; |
459 | } |
460 | |
461 | static int alsa_hw_params(struct snd_pcm_substream *substream, |
462 | struct snd_pcm_hw_params *params) |
463 | { |
464 | struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); |
465 | struct xen_snd_front_info *front_info = stream->front_info; |
466 | struct xen_front_pgdir_shbuf_cfg buf_cfg; |
467 | int ret; |
468 | |
469 | /* |
470 | * This callback may be called multiple times, |
471 | * so free the previously allocated shared buffer if any. |
472 | */ |
473 | stream_free(stream); |
474 | ret = shbuf_setup_backstore(stream, buffer_sz: params_buffer_bytes(p: params)); |
475 | if (ret < 0) |
476 | goto fail; |
477 | |
478 | memset(&buf_cfg, 0, sizeof(buf_cfg)); |
479 | buf_cfg.xb_dev = front_info->xb_dev; |
480 | buf_cfg.pgdir = &stream->shbuf; |
481 | buf_cfg.num_pages = stream->num_pages; |
482 | buf_cfg.pages = stream->pages; |
483 | |
484 | ret = xen_front_pgdir_shbuf_alloc(cfg: &buf_cfg); |
485 | if (ret < 0) |
486 | goto fail; |
487 | |
488 | ret = xen_front_pgdir_shbuf_map(buf: &stream->shbuf); |
489 | if (ret < 0) |
490 | goto fail; |
491 | |
492 | return 0; |
493 | |
494 | fail: |
495 | stream_free(stream); |
496 | dev_err(&front_info->xb_dev->dev, |
497 | "Failed to allocate buffers for stream with index %d\n" , |
498 | stream->index); |
499 | return ret; |
500 | } |
501 | |
502 | static int alsa_hw_free(struct snd_pcm_substream *substream) |
503 | { |
504 | struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); |
505 | int ret; |
506 | |
507 | ret = xen_snd_front_stream_close(evtchnl: &stream->evt_pair->req); |
508 | stream_free(stream); |
509 | return ret; |
510 | } |
511 | |
512 | static int alsa_prepare(struct snd_pcm_substream *substream) |
513 | { |
514 | struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); |
515 | |
516 | if (!stream->is_open) { |
517 | struct snd_pcm_runtime *runtime = substream->runtime; |
518 | u8 sndif_format; |
519 | int ret; |
520 | |
521 | ret = to_sndif_format(format: runtime->format); |
522 | if (ret < 0) { |
523 | dev_err(&stream->front_info->xb_dev->dev, |
524 | "Unsupported sample format: %d\n" , |
525 | runtime->format); |
526 | return ret; |
527 | } |
528 | sndif_format = ret; |
529 | |
530 | ret = xen_snd_front_stream_prepare(evtchnl: &stream->evt_pair->req, |
531 | shbuf: &stream->shbuf, |
532 | format: sndif_format, |
533 | channels: runtime->channels, |
534 | rate: runtime->rate, |
535 | buffer_sz: snd_pcm_lib_buffer_bytes(substream), |
536 | period_sz: snd_pcm_lib_period_bytes(substream)); |
537 | if (ret < 0) |
538 | return ret; |
539 | |
540 | stream->is_open = true; |
541 | } |
542 | |
543 | return 0; |
544 | } |
545 | |
546 | static int alsa_trigger(struct snd_pcm_substream *substream, int cmd) |
547 | { |
548 | struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); |
549 | int type; |
550 | |
551 | switch (cmd) { |
552 | case SNDRV_PCM_TRIGGER_START: |
553 | type = XENSND_OP_TRIGGER_START; |
554 | break; |
555 | |
556 | case SNDRV_PCM_TRIGGER_RESUME: |
557 | type = XENSND_OP_TRIGGER_RESUME; |
558 | break; |
559 | |
560 | case SNDRV_PCM_TRIGGER_STOP: |
561 | type = XENSND_OP_TRIGGER_STOP; |
562 | break; |
563 | |
564 | case SNDRV_PCM_TRIGGER_SUSPEND: |
565 | type = XENSND_OP_TRIGGER_PAUSE; |
566 | break; |
567 | |
568 | default: |
569 | return -EINVAL; |
570 | } |
571 | |
572 | return xen_snd_front_stream_trigger(evtchnl: &stream->evt_pair->req, type); |
573 | } |
574 | |
575 | void xen_snd_front_alsa_handle_cur_pos(struct xen_snd_front_evtchnl *evtchnl, |
576 | u64 pos_bytes) |
577 | { |
578 | struct snd_pcm_substream *substream = evtchnl->u.evt.substream; |
579 | struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); |
580 | snd_pcm_uframes_t delta, new_hw_ptr, cur_frame; |
581 | |
582 | cur_frame = bytes_to_frames(runtime: substream->runtime, size: pos_bytes); |
583 | |
584 | delta = cur_frame - stream->be_cur_frame; |
585 | stream->be_cur_frame = cur_frame; |
586 | |
587 | new_hw_ptr = (snd_pcm_uframes_t)atomic_read(v: &stream->hw_ptr); |
588 | new_hw_ptr = (new_hw_ptr + delta) % substream->runtime->buffer_size; |
589 | atomic_set(v: &stream->hw_ptr, i: (int)new_hw_ptr); |
590 | |
591 | stream->out_frames += delta; |
592 | if (stream->out_frames > substream->runtime->period_size) { |
593 | stream->out_frames %= substream->runtime->period_size; |
594 | snd_pcm_period_elapsed(substream); |
595 | } |
596 | } |
597 | |
598 | static snd_pcm_uframes_t alsa_pointer(struct snd_pcm_substream *substream) |
599 | { |
600 | struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); |
601 | |
602 | return (snd_pcm_uframes_t)atomic_read(v: &stream->hw_ptr); |
603 | } |
604 | |
605 | static int alsa_pb_copy(struct snd_pcm_substream *substream, |
606 | int channel, unsigned long pos, struct iov_iter *src, |
607 | unsigned long count) |
608 | { |
609 | struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); |
610 | |
611 | if (unlikely(pos + count > stream->buffer_sz)) |
612 | return -EINVAL; |
613 | |
614 | if (copy_from_iter(addr: stream->buffer + pos, bytes: count, i: src) != count) |
615 | return -EFAULT; |
616 | |
617 | return xen_snd_front_stream_write(evtchnl: &stream->evt_pair->req, pos, count); |
618 | } |
619 | |
620 | static int alsa_cap_copy(struct snd_pcm_substream *substream, |
621 | int channel, unsigned long pos, struct iov_iter *dst, |
622 | unsigned long count) |
623 | { |
624 | struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); |
625 | int ret; |
626 | |
627 | if (unlikely(pos + count > stream->buffer_sz)) |
628 | return -EINVAL; |
629 | |
630 | ret = xen_snd_front_stream_read(evtchnl: &stream->evt_pair->req, pos, count); |
631 | if (ret < 0) |
632 | return ret; |
633 | |
634 | if (copy_to_iter(addr: stream->buffer + pos, bytes: count, i: dst) != count) |
635 | return -EFAULT; |
636 | return 0; |
637 | } |
638 | |
639 | static int alsa_pb_fill_silence(struct snd_pcm_substream *substream, |
640 | int channel, unsigned long pos, |
641 | unsigned long count) |
642 | { |
643 | struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); |
644 | |
645 | if (unlikely(pos + count > stream->buffer_sz)) |
646 | return -EINVAL; |
647 | |
648 | memset(stream->buffer + pos, 0, count); |
649 | |
650 | return xen_snd_front_stream_write(evtchnl: &stream->evt_pair->req, pos, count); |
651 | } |
652 | |
653 | /* |
654 | * FIXME: The mmaped data transfer is asynchronous and there is no |
655 | * ack signal from user-space when it is done. This is the |
656 | * reason it is not implemented in the PV driver as we do need |
657 | * to know when the buffer can be transferred to the backend. |
658 | */ |
659 | |
660 | static const struct snd_pcm_ops snd_drv_alsa_playback_ops = { |
661 | .open = alsa_open, |
662 | .close = alsa_close, |
663 | .hw_params = alsa_hw_params, |
664 | .hw_free = alsa_hw_free, |
665 | .prepare = alsa_prepare, |
666 | .trigger = alsa_trigger, |
667 | .pointer = alsa_pointer, |
668 | .copy = alsa_pb_copy, |
669 | .fill_silence = alsa_pb_fill_silence, |
670 | }; |
671 | |
672 | static const struct snd_pcm_ops snd_drv_alsa_capture_ops = { |
673 | .open = alsa_open, |
674 | .close = alsa_close, |
675 | .hw_params = alsa_hw_params, |
676 | .hw_free = alsa_hw_free, |
677 | .prepare = alsa_prepare, |
678 | .trigger = alsa_trigger, |
679 | .pointer = alsa_pointer, |
680 | .copy = alsa_cap_copy, |
681 | }; |
682 | |
683 | static int new_pcm_instance(struct xen_snd_front_card_info *card_info, |
684 | struct xen_front_cfg_pcm_instance *instance_cfg, |
685 | struct xen_snd_front_pcm_instance_info *pcm_instance_info) |
686 | { |
687 | struct snd_pcm *pcm; |
688 | int ret, i; |
689 | |
690 | dev_dbg(&card_info->front_info->xb_dev->dev, |
691 | "New PCM device \"%s\" with id %d playback %d capture %d" , |
692 | instance_cfg->name, |
693 | instance_cfg->device_id, |
694 | instance_cfg->num_streams_pb, |
695 | instance_cfg->num_streams_cap); |
696 | |
697 | pcm_instance_info->card_info = card_info; |
698 | |
699 | pcm_instance_info->pcm_hw = instance_cfg->pcm_hw; |
700 | |
701 | if (instance_cfg->num_streams_pb) { |
702 | pcm_instance_info->streams_pb = |
703 | devm_kcalloc(dev: &card_info->card->card_dev, |
704 | n: instance_cfg->num_streams_pb, |
705 | size: sizeof(struct xen_snd_front_pcm_stream_info), |
706 | GFP_KERNEL); |
707 | if (!pcm_instance_info->streams_pb) |
708 | return -ENOMEM; |
709 | } |
710 | |
711 | if (instance_cfg->num_streams_cap) { |
712 | pcm_instance_info->streams_cap = |
713 | devm_kcalloc(dev: &card_info->card->card_dev, |
714 | n: instance_cfg->num_streams_cap, |
715 | size: sizeof(struct xen_snd_front_pcm_stream_info), |
716 | GFP_KERNEL); |
717 | if (!pcm_instance_info->streams_cap) |
718 | return -ENOMEM; |
719 | } |
720 | |
721 | pcm_instance_info->num_pcm_streams_pb = |
722 | instance_cfg->num_streams_pb; |
723 | pcm_instance_info->num_pcm_streams_cap = |
724 | instance_cfg->num_streams_cap; |
725 | |
726 | for (i = 0; i < pcm_instance_info->num_pcm_streams_pb; i++) { |
727 | pcm_instance_info->streams_pb[i].pcm_hw = |
728 | instance_cfg->streams_pb[i].pcm_hw; |
729 | pcm_instance_info->streams_pb[i].index = |
730 | instance_cfg->streams_pb[i].index; |
731 | } |
732 | |
733 | for (i = 0; i < pcm_instance_info->num_pcm_streams_cap; i++) { |
734 | pcm_instance_info->streams_cap[i].pcm_hw = |
735 | instance_cfg->streams_cap[i].pcm_hw; |
736 | pcm_instance_info->streams_cap[i].index = |
737 | instance_cfg->streams_cap[i].index; |
738 | } |
739 | |
740 | ret = snd_pcm_new(card: card_info->card, id: instance_cfg->name, |
741 | device: instance_cfg->device_id, |
742 | playback_count: instance_cfg->num_streams_pb, |
743 | capture_count: instance_cfg->num_streams_cap, |
744 | rpcm: &pcm); |
745 | if (ret < 0) |
746 | return ret; |
747 | |
748 | pcm->private_data = pcm_instance_info; |
749 | pcm->info_flags = 0; |
750 | /* we want to handle all PCM operations in non-atomic context */ |
751 | pcm->nonatomic = true; |
752 | strscpy(p: pcm->name, q: "Virtual card PCM" , size: sizeof(pcm->name)); |
753 | |
754 | if (instance_cfg->num_streams_pb) |
755 | snd_pcm_set_ops(pcm, direction: SNDRV_PCM_STREAM_PLAYBACK, |
756 | ops: &snd_drv_alsa_playback_ops); |
757 | |
758 | if (instance_cfg->num_streams_cap) |
759 | snd_pcm_set_ops(pcm, direction: SNDRV_PCM_STREAM_CAPTURE, |
760 | ops: &snd_drv_alsa_capture_ops); |
761 | |
762 | pcm_instance_info->pcm = pcm; |
763 | return 0; |
764 | } |
765 | |
766 | int xen_snd_front_alsa_init(struct xen_snd_front_info *front_info) |
767 | { |
768 | struct device *dev = &front_info->xb_dev->dev; |
769 | struct xen_front_cfg_card *cfg = &front_info->cfg; |
770 | struct xen_snd_front_card_info *card_info; |
771 | struct snd_card *card; |
772 | int ret, i; |
773 | |
774 | dev_dbg(dev, "Creating virtual sound card\n" ); |
775 | |
776 | ret = snd_card_new(parent: dev, idx: 0, XENSND_DRIVER_NAME, THIS_MODULE, |
777 | extra_size: sizeof(struct xen_snd_front_card_info), card_ret: &card); |
778 | if (ret < 0) |
779 | return ret; |
780 | |
781 | card_info = card->private_data; |
782 | card_info->front_info = front_info; |
783 | front_info->card_info = card_info; |
784 | card_info->card = card; |
785 | card_info->pcm_instances = |
786 | devm_kcalloc(dev, n: cfg->num_pcm_instances, |
787 | size: sizeof(struct xen_snd_front_pcm_instance_info), |
788 | GFP_KERNEL); |
789 | if (!card_info->pcm_instances) { |
790 | ret = -ENOMEM; |
791 | goto fail; |
792 | } |
793 | |
794 | card_info->num_pcm_instances = cfg->num_pcm_instances; |
795 | card_info->pcm_hw = cfg->pcm_hw; |
796 | |
797 | for (i = 0; i < cfg->num_pcm_instances; i++) { |
798 | ret = new_pcm_instance(card_info, instance_cfg: &cfg->pcm_instances[i], |
799 | pcm_instance_info: &card_info->pcm_instances[i]); |
800 | if (ret < 0) |
801 | goto fail; |
802 | } |
803 | |
804 | strscpy(p: card->driver, XENSND_DRIVER_NAME, size: sizeof(card->driver)); |
805 | strscpy(p: card->shortname, q: cfg->name_short, size: sizeof(card->shortname)); |
806 | strscpy(p: card->longname, q: cfg->name_long, size: sizeof(card->longname)); |
807 | |
808 | ret = snd_card_register(card); |
809 | if (ret < 0) |
810 | goto fail; |
811 | |
812 | return 0; |
813 | |
814 | fail: |
815 | snd_card_free(card); |
816 | return ret; |
817 | } |
818 | |
819 | void xen_snd_front_alsa_fini(struct xen_snd_front_info *front_info) |
820 | { |
821 | struct xen_snd_front_card_info *card_info; |
822 | struct snd_card *card; |
823 | |
824 | card_info = front_info->card_info; |
825 | if (!card_info) |
826 | return; |
827 | |
828 | card = card_info->card; |
829 | if (!card) |
830 | return; |
831 | |
832 | dev_dbg(&front_info->xb_dev->dev, "Removing virtual sound card %d\n" , |
833 | card->number); |
834 | snd_card_free(card); |
835 | |
836 | /* Card_info will be freed when destroying front_info->xb_dev->dev. */ |
837 | card_info->card = NULL; |
838 | } |
839 | |