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 <xen/xenbus.h> |
12 | |
13 | #include <xen/interface/io/sndif.h> |
14 | |
15 | #include "xen_snd_front.h" |
16 | #include "xen_snd_front_cfg.h" |
17 | |
18 | /* Maximum number of supported streams. */ |
19 | #define VSND_MAX_STREAM 8 |
20 | |
21 | struct cfg_hw_sample_rate { |
22 | const char *name; |
23 | unsigned int mask; |
24 | unsigned int value; |
25 | }; |
26 | |
27 | static const struct cfg_hw_sample_rate CFG_HW_SUPPORTED_RATES[] = { |
28 | { .name = "5512" , .mask = SNDRV_PCM_RATE_5512, .value = 5512 }, |
29 | { .name = "8000" , .mask = SNDRV_PCM_RATE_8000, .value = 8000 }, |
30 | { .name = "11025" , .mask = SNDRV_PCM_RATE_11025, .value = 11025 }, |
31 | { .name = "16000" , .mask = SNDRV_PCM_RATE_16000, .value = 16000 }, |
32 | { .name = "22050" , .mask = SNDRV_PCM_RATE_22050, .value = 22050 }, |
33 | { .name = "32000" , .mask = SNDRV_PCM_RATE_32000, .value = 32000 }, |
34 | { .name = "44100" , .mask = SNDRV_PCM_RATE_44100, .value = 44100 }, |
35 | { .name = "48000" , .mask = SNDRV_PCM_RATE_48000, .value = 48000 }, |
36 | { .name = "64000" , .mask = SNDRV_PCM_RATE_64000, .value = 64000 }, |
37 | { .name = "96000" , .mask = SNDRV_PCM_RATE_96000, .value = 96000 }, |
38 | { .name = "176400" , .mask = SNDRV_PCM_RATE_176400, .value = 176400 }, |
39 | { .name = "192000" , .mask = SNDRV_PCM_RATE_192000, .value = 192000 }, |
40 | }; |
41 | |
42 | struct cfg_hw_sample_format { |
43 | const char *name; |
44 | u64 mask; |
45 | }; |
46 | |
47 | static const struct cfg_hw_sample_format CFG_HW_SUPPORTED_FORMATS[] = { |
48 | { |
49 | .name = XENSND_PCM_FORMAT_U8_STR, |
50 | .mask = SNDRV_PCM_FMTBIT_U8 |
51 | }, |
52 | { |
53 | .name = XENSND_PCM_FORMAT_S8_STR, |
54 | .mask = SNDRV_PCM_FMTBIT_S8 |
55 | }, |
56 | { |
57 | .name = XENSND_PCM_FORMAT_U16_LE_STR, |
58 | .mask = SNDRV_PCM_FMTBIT_U16_LE |
59 | }, |
60 | { |
61 | .name = XENSND_PCM_FORMAT_U16_BE_STR, |
62 | .mask = SNDRV_PCM_FMTBIT_U16_BE |
63 | }, |
64 | { |
65 | .name = XENSND_PCM_FORMAT_S16_LE_STR, |
66 | .mask = SNDRV_PCM_FMTBIT_S16_LE |
67 | }, |
68 | { |
69 | .name = XENSND_PCM_FORMAT_S16_BE_STR, |
70 | .mask = SNDRV_PCM_FMTBIT_S16_BE |
71 | }, |
72 | { |
73 | .name = XENSND_PCM_FORMAT_U24_LE_STR, |
74 | .mask = SNDRV_PCM_FMTBIT_U24_LE |
75 | }, |
76 | { |
77 | .name = XENSND_PCM_FORMAT_U24_BE_STR, |
78 | .mask = SNDRV_PCM_FMTBIT_U24_BE |
79 | }, |
80 | { |
81 | .name = XENSND_PCM_FORMAT_S24_LE_STR, |
82 | .mask = SNDRV_PCM_FMTBIT_S24_LE |
83 | }, |
84 | { |
85 | .name = XENSND_PCM_FORMAT_S24_BE_STR, |
86 | .mask = SNDRV_PCM_FMTBIT_S24_BE |
87 | }, |
88 | { |
89 | .name = XENSND_PCM_FORMAT_U32_LE_STR, |
90 | .mask = SNDRV_PCM_FMTBIT_U32_LE |
91 | }, |
92 | { |
93 | .name = XENSND_PCM_FORMAT_U32_BE_STR, |
94 | .mask = SNDRV_PCM_FMTBIT_U32_BE |
95 | }, |
96 | { |
97 | .name = XENSND_PCM_FORMAT_S32_LE_STR, |
98 | .mask = SNDRV_PCM_FMTBIT_S32_LE |
99 | }, |
100 | { |
101 | .name = XENSND_PCM_FORMAT_S32_BE_STR, |
102 | .mask = SNDRV_PCM_FMTBIT_S32_BE |
103 | }, |
104 | { |
105 | .name = XENSND_PCM_FORMAT_A_LAW_STR, |
106 | .mask = SNDRV_PCM_FMTBIT_A_LAW |
107 | }, |
108 | { |
109 | .name = XENSND_PCM_FORMAT_MU_LAW_STR, |
110 | .mask = SNDRV_PCM_FMTBIT_MU_LAW |
111 | }, |
112 | { |
113 | .name = XENSND_PCM_FORMAT_F32_LE_STR, |
114 | .mask = SNDRV_PCM_FMTBIT_FLOAT_LE |
115 | }, |
116 | { |
117 | .name = XENSND_PCM_FORMAT_F32_BE_STR, |
118 | .mask = SNDRV_PCM_FMTBIT_FLOAT_BE |
119 | }, |
120 | { |
121 | .name = XENSND_PCM_FORMAT_F64_LE_STR, |
122 | .mask = SNDRV_PCM_FMTBIT_FLOAT64_LE |
123 | }, |
124 | { |
125 | .name = XENSND_PCM_FORMAT_F64_BE_STR, |
126 | .mask = SNDRV_PCM_FMTBIT_FLOAT64_BE |
127 | }, |
128 | { |
129 | .name = XENSND_PCM_FORMAT_IEC958_SUBFRAME_LE_STR, |
130 | .mask = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE |
131 | }, |
132 | { |
133 | .name = XENSND_PCM_FORMAT_IEC958_SUBFRAME_BE_STR, |
134 | .mask = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE |
135 | }, |
136 | { |
137 | .name = XENSND_PCM_FORMAT_IMA_ADPCM_STR, |
138 | .mask = SNDRV_PCM_FMTBIT_IMA_ADPCM |
139 | }, |
140 | { |
141 | .name = XENSND_PCM_FORMAT_MPEG_STR, |
142 | .mask = SNDRV_PCM_FMTBIT_MPEG |
143 | }, |
144 | { |
145 | .name = XENSND_PCM_FORMAT_GSM_STR, |
146 | .mask = SNDRV_PCM_FMTBIT_GSM |
147 | }, |
148 | }; |
149 | |
150 | static void cfg_hw_rates(char *list, unsigned int len, |
151 | const char *path, struct snd_pcm_hardware *pcm_hw) |
152 | { |
153 | char *cur_rate; |
154 | unsigned int cur_mask; |
155 | unsigned int cur_value; |
156 | unsigned int rates; |
157 | unsigned int rate_min; |
158 | unsigned int rate_max; |
159 | int i; |
160 | |
161 | rates = 0; |
162 | rate_min = -1; |
163 | rate_max = 0; |
164 | while ((cur_rate = strsep(&list, XENSND_LIST_SEPARATOR))) { |
165 | for (i = 0; i < ARRAY_SIZE(CFG_HW_SUPPORTED_RATES); i++) |
166 | if (!strncasecmp(s1: cur_rate, |
167 | s2: CFG_HW_SUPPORTED_RATES[i].name, |
168 | XENSND_SAMPLE_RATE_MAX_LEN)) { |
169 | cur_mask = CFG_HW_SUPPORTED_RATES[i].mask; |
170 | cur_value = CFG_HW_SUPPORTED_RATES[i].value; |
171 | rates |= cur_mask; |
172 | if (rate_min > cur_value) |
173 | rate_min = cur_value; |
174 | if (rate_max < cur_value) |
175 | rate_max = cur_value; |
176 | } |
177 | } |
178 | |
179 | if (rates) { |
180 | pcm_hw->rates = rates; |
181 | pcm_hw->rate_min = rate_min; |
182 | pcm_hw->rate_max = rate_max; |
183 | } |
184 | } |
185 | |
186 | static void cfg_formats(char *list, unsigned int len, |
187 | const char *path, struct snd_pcm_hardware *pcm_hw) |
188 | { |
189 | u64 formats; |
190 | char *cur_format; |
191 | int i; |
192 | |
193 | formats = 0; |
194 | while ((cur_format = strsep(&list, XENSND_LIST_SEPARATOR))) { |
195 | for (i = 0; i < ARRAY_SIZE(CFG_HW_SUPPORTED_FORMATS); i++) |
196 | if (!strncasecmp(s1: cur_format, |
197 | s2: CFG_HW_SUPPORTED_FORMATS[i].name, |
198 | XENSND_SAMPLE_FORMAT_MAX_LEN)) |
199 | formats |= CFG_HW_SUPPORTED_FORMATS[i].mask; |
200 | } |
201 | |
202 | if (formats) |
203 | pcm_hw->formats = formats; |
204 | } |
205 | |
206 | #define MAX_BUFFER_SIZE (64 * 1024) |
207 | #define MIN_PERIOD_SIZE 64 |
208 | #define MAX_PERIOD_SIZE MAX_BUFFER_SIZE |
209 | #define USE_FORMATS (SNDRV_PCM_FMTBIT_U8 | \ |
210 | SNDRV_PCM_FMTBIT_S16_LE) |
211 | #define USE_RATE (SNDRV_PCM_RATE_CONTINUOUS | \ |
212 | SNDRV_PCM_RATE_8000_48000) |
213 | #define USE_RATE_MIN 5512 |
214 | #define USE_RATE_MAX 48000 |
215 | #define USE_CHANNELS_MIN 1 |
216 | #define USE_CHANNELS_MAX 2 |
217 | #define USE_PERIODS_MIN 2 |
218 | #define USE_PERIODS_MAX (MAX_BUFFER_SIZE / MIN_PERIOD_SIZE) |
219 | |
220 | static const struct snd_pcm_hardware SND_DRV_PCM_HW_DEFAULT = { |
221 | .info = (SNDRV_PCM_INFO_MMAP | |
222 | SNDRV_PCM_INFO_INTERLEAVED | |
223 | SNDRV_PCM_INFO_RESUME | |
224 | SNDRV_PCM_INFO_MMAP_VALID), |
225 | .formats = USE_FORMATS, |
226 | .rates = USE_RATE, |
227 | .rate_min = USE_RATE_MIN, |
228 | .rate_max = USE_RATE_MAX, |
229 | .channels_min = USE_CHANNELS_MIN, |
230 | .channels_max = USE_CHANNELS_MAX, |
231 | .buffer_bytes_max = MAX_BUFFER_SIZE, |
232 | .period_bytes_min = MIN_PERIOD_SIZE, |
233 | .period_bytes_max = MAX_PERIOD_SIZE, |
234 | .periods_min = USE_PERIODS_MIN, |
235 | .periods_max = USE_PERIODS_MAX, |
236 | .fifo_size = 0, |
237 | }; |
238 | |
239 | static void cfg_read_pcm_hw(const char *path, |
240 | struct snd_pcm_hardware *parent_pcm_hw, |
241 | struct snd_pcm_hardware *pcm_hw) |
242 | { |
243 | char *list; |
244 | int val; |
245 | size_t buf_sz; |
246 | unsigned int len; |
247 | |
248 | /* Inherit parent's PCM HW and read overrides from XenStore. */ |
249 | if (parent_pcm_hw) |
250 | *pcm_hw = *parent_pcm_hw; |
251 | else |
252 | *pcm_hw = SND_DRV_PCM_HW_DEFAULT; |
253 | |
254 | val = xenbus_read_unsigned(dir: path, XENSND_FIELD_CHANNELS_MIN, default_val: 0); |
255 | if (val) |
256 | pcm_hw->channels_min = val; |
257 | |
258 | val = xenbus_read_unsigned(dir: path, XENSND_FIELD_CHANNELS_MAX, default_val: 0); |
259 | if (val) |
260 | pcm_hw->channels_max = val; |
261 | |
262 | list = xenbus_read(XBT_NIL, dir: path, XENSND_FIELD_SAMPLE_RATES, len: &len); |
263 | if (!IS_ERR(list)) { |
264 | cfg_hw_rates(list, len, path, pcm_hw); |
265 | kfree(list); |
266 | } |
267 | |
268 | list = xenbus_read(XBT_NIL, path, XENSND_FIELD_SAMPLE_FORMATS, &len); |
269 | if (!IS_ERR(list)) { |
270 | cfg_formats(list, len, path, pcm_hw); |
271 | kfree(list); |
272 | } |
273 | |
274 | buf_sz = xenbus_read_unsigned(path, XENSND_FIELD_BUFFER_SIZE, 0); |
275 | if (buf_sz) |
276 | pcm_hw->buffer_bytes_max = buf_sz; |
277 | |
278 | /* Update configuration to match new values. */ |
279 | if (pcm_hw->channels_min > pcm_hw->channels_max) |
280 | pcm_hw->channels_min = pcm_hw->channels_max; |
281 | |
282 | if (pcm_hw->rate_min > pcm_hw->rate_max) |
283 | pcm_hw->rate_min = pcm_hw->rate_max; |
284 | |
285 | pcm_hw->period_bytes_max = pcm_hw->buffer_bytes_max; |
286 | |
287 | pcm_hw->periods_max = pcm_hw->period_bytes_max / |
288 | pcm_hw->period_bytes_min; |
289 | } |
290 | |
291 | static int cfg_get_stream_type(const char *path, int index, |
292 | int *num_pb, int *num_cap) |
293 | { |
294 | char *str = NULL; |
295 | char *stream_path; |
296 | int ret; |
297 | |
298 | *num_pb = 0; |
299 | *num_cap = 0; |
300 | stream_path = kasprintf(GFP_KERNEL, fmt: "%s/%d" , path, index); |
301 | if (!stream_path) { |
302 | ret = -ENOMEM; |
303 | goto fail; |
304 | } |
305 | |
306 | str = xenbus_read(XBT_NIL, dir: stream_path, XENSND_FIELD_TYPE, NULL); |
307 | if (IS_ERR(str)) { |
308 | ret = PTR_ERR(str); |
309 | str = NULL; |
310 | goto fail; |
311 | } |
312 | |
313 | if (!strncasecmp(str, XENSND_STREAM_TYPE_PLAYBACK, |
314 | sizeof(XENSND_STREAM_TYPE_PLAYBACK))) { |
315 | (*num_pb)++; |
316 | } else if (!strncasecmp(str, XENSND_STREAM_TYPE_CAPTURE, |
317 | sizeof(XENSND_STREAM_TYPE_CAPTURE))) { |
318 | (*num_cap)++; |
319 | } else { |
320 | ret = -EINVAL; |
321 | goto fail; |
322 | } |
323 | ret = 0; |
324 | |
325 | fail: |
326 | kfree(stream_path); |
327 | kfree(str); |
328 | return ret; |
329 | } |
330 | |
331 | static int cfg_stream(struct xen_snd_front_info *front_info, |
332 | struct xen_front_cfg_pcm_instance *pcm_instance, |
333 | const char *path, int index, int *cur_pb, int *cur_cap, |
334 | int *stream_cnt) |
335 | { |
336 | char *str = NULL; |
337 | char *stream_path; |
338 | struct xen_front_cfg_stream *stream; |
339 | int ret; |
340 | |
341 | stream_path = devm_kasprintf(dev: &front_info->xb_dev->dev, |
342 | GFP_KERNEL, fmt: "%s/%d" , path, index); |
343 | if (!stream_path) { |
344 | ret = -ENOMEM; |
345 | goto fail; |
346 | } |
347 | |
348 | str = xenbus_read(XBT_NIL, dir: stream_path, XENSND_FIELD_TYPE, NULL); |
349 | if (IS_ERR(str)) { |
350 | ret = PTR_ERR(str); |
351 | str = NULL; |
352 | goto fail; |
353 | } |
354 | |
355 | if (!strncasecmp(str, XENSND_STREAM_TYPE_PLAYBACK, |
356 | sizeof(XENSND_STREAM_TYPE_PLAYBACK))) { |
357 | stream = &pcm_instance->streams_pb[(*cur_pb)++]; |
358 | } else if (!strncasecmp(str, XENSND_STREAM_TYPE_CAPTURE, |
359 | sizeof(XENSND_STREAM_TYPE_CAPTURE))) { |
360 | stream = &pcm_instance->streams_cap[(*cur_cap)++]; |
361 | } else { |
362 | ret = -EINVAL; |
363 | goto fail; |
364 | } |
365 | |
366 | /* Get next stream index. */ |
367 | stream->index = (*stream_cnt)++; |
368 | stream->xenstore_path = stream_path; |
369 | /* |
370 | * Check XenStore if PCM HW configuration exists for this stream |
371 | * and update if so, e.g. we inherit all values from device's PCM HW, |
372 | * but can still override some of the values for the stream. |
373 | */ |
374 | cfg_read_pcm_hw(stream->xenstore_path, |
375 | &pcm_instance->pcm_hw, &stream->pcm_hw); |
376 | ret = 0; |
377 | |
378 | fail: |
379 | kfree(str); |
380 | return ret; |
381 | } |
382 | |
383 | static int cfg_device(struct xen_snd_front_info *front_info, |
384 | struct xen_front_cfg_pcm_instance *pcm_instance, |
385 | struct snd_pcm_hardware *parent_pcm_hw, |
386 | const char *path, int node_index, int *stream_cnt) |
387 | { |
388 | char *str; |
389 | char *device_path; |
390 | int ret, i, num_streams; |
391 | int num_pb, num_cap; |
392 | int cur_pb, cur_cap; |
393 | char node[3]; |
394 | |
395 | device_path = kasprintf(GFP_KERNEL, fmt: "%s/%d" , path, node_index); |
396 | if (!device_path) |
397 | return -ENOMEM; |
398 | |
399 | str = xenbus_read(XBT_NIL, dir: device_path, XENSND_FIELD_DEVICE_NAME, NULL); |
400 | if (!IS_ERR(str)) { |
401 | strscpy(pcm_instance->name, str, sizeof(pcm_instance->name)); |
402 | kfree(str); |
403 | } |
404 | |
405 | pcm_instance->device_id = node_index; |
406 | |
407 | /* |
408 | * Check XenStore if PCM HW configuration exists for this device |
409 | * and update if so, e.g. we inherit all values from card's PCM HW, |
410 | * but can still override some of the values for the device. |
411 | */ |
412 | cfg_read_pcm_hw(device_path, parent_pcm_hw, &pcm_instance->pcm_hw); |
413 | |
414 | /* Find out how many streams were configured in Xen store. */ |
415 | num_streams = 0; |
416 | do { |
417 | snprintf(node, sizeof(node), "%d" , num_streams); |
418 | if (!xenbus_exists(XBT_NIL, device_path, node)) |
419 | break; |
420 | |
421 | num_streams++; |
422 | } while (num_streams < VSND_MAX_STREAM); |
423 | |
424 | pcm_instance->num_streams_pb = 0; |
425 | pcm_instance->num_streams_cap = 0; |
426 | /* Get number of playback and capture streams. */ |
427 | for (i = 0; i < num_streams; i++) { |
428 | ret = cfg_get_stream_type(device_path, i, &num_pb, &num_cap); |
429 | if (ret < 0) |
430 | goto fail; |
431 | |
432 | pcm_instance->num_streams_pb += num_pb; |
433 | pcm_instance->num_streams_cap += num_cap; |
434 | } |
435 | |
436 | if (pcm_instance->num_streams_pb) { |
437 | pcm_instance->streams_pb = |
438 | devm_kcalloc(&front_info->xb_dev->dev, |
439 | pcm_instance->num_streams_pb, |
440 | sizeof(struct xen_front_cfg_stream), |
441 | GFP_KERNEL); |
442 | if (!pcm_instance->streams_pb) { |
443 | ret = -ENOMEM; |
444 | goto fail; |
445 | } |
446 | } |
447 | |
448 | if (pcm_instance->num_streams_cap) { |
449 | pcm_instance->streams_cap = |
450 | devm_kcalloc(&front_info->xb_dev->dev, |
451 | pcm_instance->num_streams_cap, |
452 | sizeof(struct xen_front_cfg_stream), |
453 | GFP_KERNEL); |
454 | if (!pcm_instance->streams_cap) { |
455 | ret = -ENOMEM; |
456 | goto fail; |
457 | } |
458 | } |
459 | |
460 | cur_pb = 0; |
461 | cur_cap = 0; |
462 | for (i = 0; i < num_streams; i++) { |
463 | ret = cfg_stream(front_info, pcm_instance, device_path, i, |
464 | &cur_pb, &cur_cap, stream_cnt); |
465 | if (ret < 0) |
466 | goto fail; |
467 | } |
468 | ret = 0; |
469 | |
470 | fail: |
471 | kfree(device_path); |
472 | return ret; |
473 | } |
474 | |
475 | int xen_snd_front_cfg_card(struct xen_snd_front_info *front_info, |
476 | int *stream_cnt) |
477 | { |
478 | struct xenbus_device *xb_dev = front_info->xb_dev; |
479 | struct xen_front_cfg_card *cfg = &front_info->cfg; |
480 | int ret, num_devices, i; |
481 | char node[3]; |
482 | |
483 | *stream_cnt = 0; |
484 | num_devices = 0; |
485 | do { |
486 | scnprintf(buf: node, size: sizeof(node), fmt: "%d" , num_devices); |
487 | if (!xenbus_exists(XBT_NIL, dir: xb_dev->nodename, node)) |
488 | break; |
489 | |
490 | num_devices++; |
491 | } while (num_devices < SNDRV_PCM_DEVICES); |
492 | |
493 | if (!num_devices) { |
494 | dev_warn(&xb_dev->dev, |
495 | "No devices configured for sound card at %s\n" , |
496 | xb_dev->nodename); |
497 | return -ENODEV; |
498 | } |
499 | |
500 | /* Start from default PCM HW configuration for the card. */ |
501 | cfg_read_pcm_hw(xb_dev->nodename, NULL, &cfg->pcm_hw); |
502 | |
503 | cfg->pcm_instances = |
504 | devm_kcalloc(&front_info->xb_dev->dev, num_devices, |
505 | sizeof(struct xen_front_cfg_pcm_instance), |
506 | GFP_KERNEL); |
507 | if (!cfg->pcm_instances) |
508 | return -ENOMEM; |
509 | |
510 | for (i = 0; i < num_devices; i++) { |
511 | ret = cfg_device(front_info, &cfg->pcm_instances[i], |
512 | &cfg->pcm_hw, xb_dev->nodename, i, stream_cnt); |
513 | if (ret < 0) |
514 | return ret; |
515 | } |
516 | cfg->num_pcm_instances = num_devices; |
517 | return 0; |
518 | } |
519 | |
520 | |