]>
Commit | Line | Data |
---|---|---|
fd3b3604 OA |
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 <[email protected]> | |
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(cur_rate, | |
167 | 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(cur_format, | |
197 | 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(path, XENSND_FIELD_CHANNELS_MIN, 0); | |
255 | if (val) | |
256 | pcm_hw->channels_min = val; | |
257 | ||
258 | val = xenbus_read_unsigned(path, XENSND_FIELD_CHANNELS_MAX, 0); | |
259 | if (val) | |
260 | pcm_hw->channels_max = val; | |
261 | ||
262 | list = xenbus_read(XBT_NIL, path, XENSND_FIELD_SAMPLE_RATES, &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, "%s/%d", path, index); | |
301 | if (!stream_path) { | |
302 | ret = -ENOMEM; | |
303 | goto fail; | |
304 | } | |
305 | ||
306 | str = xenbus_read(XBT_NIL, stream_path, XENSND_FIELD_TYPE, NULL); | |
307 | if (IS_ERR(str)) { | |
308 | ret = PTR_ERR(str); | |
e46dcbb1 | 309 | str = NULL; |
fd3b3604 OA |
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(&front_info->xb_dev->dev, | |
342 | GFP_KERNEL, "%s/%d", path, index); | |
343 | if (!stream_path) { | |
344 | ret = -ENOMEM; | |
345 | goto fail; | |
346 | } | |
347 | ||
348 | str = xenbus_read(XBT_NIL, stream_path, XENSND_FIELD_TYPE, NULL); | |
349 | if (IS_ERR(str)) { | |
350 | ret = PTR_ERR(str); | |
e46dcbb1 | 351 | str = NULL; |
fd3b3604 OA |
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, "%s/%d", path, node_index); | |
396 | if (!device_path) | |
397 | return -ENOMEM; | |
398 | ||
399 | str = xenbus_read(XBT_NIL, device_path, XENSND_FIELD_DEVICE_NAME, NULL); | |
400 | if (!IS_ERR(str)) { | |
75b1a8f9 | 401 | strscpy(pcm_instance->name, str, sizeof(pcm_instance->name)); |
fd3b3604 OA |
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 { | |
7272b8bf | 486 | scnprintf(node, sizeof(node), "%d", num_devices); |
fd3b3604 OA |
487 | if (!xenbus_exists(XBT_NIL, 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 |