// SPDX-License-Identifier: GPL-2.0 OR MIT /* * Xen para-virtual sound device * * Copyright (C) 2016-2018 EPAM Systems Inc. * * Author: Oleksandr Andrushchenko */ #include #include #include "xen_snd_front.h" #include "xen_snd_front_cfg.h" /* Maximum number of supported streams. */ #define VSND_MAX_STREAM 8 struct cfg_hw_sample_rate { const char *name; unsigned int mask; unsigned int value; }; static const struct cfg_hw_sample_rate CFG_HW_SUPPORTED_RATES[] = { { .name = "5512", .mask = SNDRV_PCM_RATE_5512, .value = 5512 }, { .name = "8000", .mask = SNDRV_PCM_RATE_8000, .value = 8000 }, { .name = "11025", .mask = SNDRV_PCM_RATE_11025, .value = 11025 }, { .name = "16000", .mask = SNDRV_PCM_RATE_16000, .value = 16000 }, { .name = "22050", .mask = SNDRV_PCM_RATE_22050, .value = 22050 }, { .name = "32000", .mask = SNDRV_PCM_RATE_32000, .value = 32000 }, { .name = "44100", .mask = SNDRV_PCM_RATE_44100, .value = 44100 }, { .name = "48000", .mask = SNDRV_PCM_RATE_48000, .value = 48000 }, { .name = "64000", .mask = SNDRV_PCM_RATE_64000, .value = 64000 }, { .name = "96000", .mask = SNDRV_PCM_RATE_96000, .value = 96000 }, { .name = "176400", .mask = SNDRV_PCM_RATE_176400, .value = 176400 }, { .name = "192000", .mask = SNDRV_PCM_RATE_192000, .value = 192000 }, }; struct cfg_hw_sample_format { const char *name; u64 mask; }; static const struct cfg_hw_sample_format CFG_HW_SUPPORTED_FORMATS[] = { { .name = XENSND_PCM_FORMAT_U8_STR, .mask = SNDRV_PCM_FMTBIT_U8 }, { .name = XENSND_PCM_FORMAT_S8_STR, .mask = SNDRV_PCM_FMTBIT_S8 }, { .name = XENSND_PCM_FORMAT_U16_LE_STR, .mask = SNDRV_PCM_FMTBIT_U16_LE }, { .name = XENSND_PCM_FORMAT_U16_BE_STR, .mask = SNDRV_PCM_FMTBIT_U16_BE }, { .name = XENSND_PCM_FORMAT_S16_LE_STR, .mask = SNDRV_PCM_FMTBIT_S16_LE }, { .name = XENSND_PCM_FORMAT_S16_BE_STR, .mask = SNDRV_PCM_FMTBIT_S16_BE }, { .name = XENSND_PCM_FORMAT_U24_LE_STR, .mask = SNDRV_PCM_FMTBIT_U24_LE }, { .name = XENSND_PCM_FORMAT_U24_BE_STR, .mask = SNDRV_PCM_FMTBIT_U24_BE }, { .name = XENSND_PCM_FORMAT_S24_LE_STR, .mask = SNDRV_PCM_FMTBIT_S24_LE }, { .name = XENSND_PCM_FORMAT_S24_BE_STR, .mask = SNDRV_PCM_FMTBIT_S24_BE }, { .name = XENSND_PCM_FORMAT_U32_LE_STR, .mask = SNDRV_PCM_FMTBIT_U32_LE }, { .name = XENSND_PCM_FORMAT_U32_BE_STR, .mask = SNDRV_PCM_FMTBIT_U32_BE }, { .name = XENSND_PCM_FORMAT_S32_LE_STR, .mask = SNDRV_PCM_FMTBIT_S32_LE }, { .name = XENSND_PCM_FORMAT_S32_BE_STR, .mask = SNDRV_PCM_FMTBIT_S32_BE }, { .name = XENSND_PCM_FORMAT_A_LAW_STR, .mask = SNDRV_PCM_FMTBIT_A_LAW }, { .name = XENSND_PCM_FORMAT_MU_LAW_STR, .mask = SNDRV_PCM_FMTBIT_MU_LAW }, { .name = XENSND_PCM_FORMAT_F32_LE_STR, .mask = SNDRV_PCM_FMTBIT_FLOAT_LE }, { .name = XENSND_PCM_FORMAT_F32_BE_STR, .mask = SNDRV_PCM_FMTBIT_FLOAT_BE }, { .name = XENSND_PCM_FORMAT_F64_LE_STR, .mask = SNDRV_PCM_FMTBIT_FLOAT64_LE }, { .name = XENSND_PCM_FORMAT_F64_BE_STR, .mask = SNDRV_PCM_FMTBIT_FLOAT64_BE }, { .name = XENSND_PCM_FORMAT_IEC958_SUBFRAME_LE_STR, .mask = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE }, { .name = XENSND_PCM_FORMAT_IEC958_SUBFRAME_BE_STR, .mask = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE }, { .name = XENSND_PCM_FORMAT_IMA_ADPCM_STR, .mask = SNDRV_PCM_FMTBIT_IMA_ADPCM }, { .name = XENSND_PCM_FORMAT_MPEG_STR, .mask = SNDRV_PCM_FMTBIT_MPEG }, { .name = XENSND_PCM_FORMAT_GSM_STR, .mask = SNDRV_PCM_FMTBIT_GSM }, }; static void cfg_hw_rates(char *list, unsigned int len, const char *path, struct snd_pcm_hardware *pcm_hw) { char *cur_rate; unsigned int cur_mask; unsigned int cur_value; unsigned int rates; unsigned int rate_min; unsigned int rate_max; int i; rates = 0; rate_min = -1; rate_max = 0; while ((cur_rate = strsep(&list, XENSND_LIST_SEPARATOR))) { for (i = 0; i < ARRAY_SIZE(CFG_HW_SUPPORTED_RATES); i++) if (!strncasecmp(cur_rate, CFG_HW_SUPPORTED_RATES[i].name, XENSND_SAMPLE_RATE_MAX_LEN)) { cur_mask = CFG_HW_SUPPORTED_RATES[i].mask; cur_value = CFG_HW_SUPPORTED_RATES[i].value; rates |= cur_mask; if (rate_min > cur_value) rate_min = cur_value; if (rate_max < cur_value) rate_max = cur_value; } } if (rates) { pcm_hw->rates = rates; pcm_hw->rate_min = rate_min; pcm_hw->rate_max = rate_max; } } static void cfg_formats(char *list, unsigned int len, const char *path, struct snd_pcm_hardware *pcm_hw) { u64 formats; char *cur_format; int i; formats = 0; while ((cur_format = strsep(&list, XENSND_LIST_SEPARATOR))) { for (i = 0; i < ARRAY_SIZE(CFG_HW_SUPPORTED_FORMATS); i++) if (!strncasecmp(cur_format, CFG_HW_SUPPORTED_FORMATS[i].name, XENSND_SAMPLE_FORMAT_MAX_LEN)) formats |= CFG_HW_SUPPORTED_FORMATS[i].mask; } if (formats) pcm_hw->formats = formats; } #define MAX_BUFFER_SIZE (64 * 1024) #define MIN_PERIOD_SIZE 64 #define MAX_PERIOD_SIZE MAX_BUFFER_SIZE #define USE_FORMATS (SNDRV_PCM_FMTBIT_U8 | \ SNDRV_PCM_FMTBIT_S16_LE) #define USE_RATE (SNDRV_PCM_RATE_CONTINUOUS | \ SNDRV_PCM_RATE_8000_48000) #define USE_RATE_MIN 5512 #define USE_RATE_MAX 48000 #define USE_CHANNELS_MIN 1 #define USE_CHANNELS_MAX 2 #define USE_PERIODS_MIN 2 #define USE_PERIODS_MAX (MAX_BUFFER_SIZE / MIN_PERIOD_SIZE) static const struct snd_pcm_hardware SND_DRV_PCM_HW_DEFAULT = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = USE_FORMATS, .rates = USE_RATE, .rate_min = USE_RATE_MIN, .rate_max = USE_RATE_MAX, .channels_min = USE_CHANNELS_MIN, .channels_max = USE_CHANNELS_MAX, .buffer_bytes_max = MAX_BUFFER_SIZE, .period_bytes_min = MIN_PERIOD_SIZE, .period_bytes_max = MAX_PERIOD_SIZE, .periods_min = USE_PERIODS_MIN, .periods_max = USE_PERIODS_MAX, .fifo_size = 0, }; static void cfg_read_pcm_hw(const char *path, struct snd_pcm_hardware *parent_pcm_hw, struct snd_pcm_hardware *pcm_hw) { char *list; int val; size_t buf_sz; unsigned int len; /* Inherit parent's PCM HW and read overrides from XenStore. */ if (parent_pcm_hw) *pcm_hw = *parent_pcm_hw; else *pcm_hw = SND_DRV_PCM_HW_DEFAULT; val = xenbus_read_unsigned(path, XENSND_FIELD_CHANNELS_MIN, 0); if (val) pcm_hw->channels_min = val; val = xenbus_read_unsigned(path, XENSND_FIELD_CHANNELS_MAX, 0); if (val) pcm_hw->channels_max = val; list = xenbus_read(XBT_NIL, path, XENSND_FIELD_SAMPLE_RATES, &len); if (!IS_ERR(list)) { cfg_hw_rates(list, len, path, pcm_hw); kfree(list); } list = xenbus_read(XBT_NIL, path, XENSND_FIELD_SAMPLE_FORMATS, &len); if (!IS_ERR(list)) { cfg_formats(list, len, path, pcm_hw); kfree(list); } buf_sz = xenbus_read_unsigned(path, XENSND_FIELD_BUFFER_SIZE, 0); if (buf_sz) pcm_hw->buffer_bytes_max = buf_sz; /* Update configuration to match new values. */ if (pcm_hw->channels_min > pcm_hw->channels_max) pcm_hw->channels_min = pcm_hw->channels_max; if (pcm_hw->rate_min > pcm_hw->rate_max) pcm_hw->rate_min = pcm_hw->rate_max; pcm_hw->period_bytes_max = pcm_hw->buffer_bytes_max; pcm_hw->periods_max = pcm_hw->period_bytes_max / pcm_hw->period_bytes_min; } static int cfg_get_stream_type(const char *path, int index, int *num_pb, int *num_cap) { char *str = NULL; char *stream_path; int ret; *num_pb = 0; *num_cap = 0; stream_path = kasprintf(GFP_KERNEL, "%s/%d", path, index); if (!stream_path) { ret = -ENOMEM; goto fail; } str = xenbus_read(XBT_NIL, stream_path, XENSND_FIELD_TYPE, NULL); if (IS_ERR(str)) { ret = PTR_ERR(str); str = NULL; goto fail; } if (!strncasecmp(str, XENSND_STREAM_TYPE_PLAYBACK, sizeof(XENSND_STREAM_TYPE_PLAYBACK))) { (*num_pb)++; } else if (!strncasecmp(str, XENSND_STREAM_TYPE_CAPTURE, sizeof(XENSND_STREAM_TYPE_CAPTURE))) { (*num_cap)++; } else { ret = -EINVAL; goto fail; } ret = 0; fail: kfree(stream_path); kfree(str); return ret; } static int cfg_stream(struct xen_snd_front_info *front_info, struct xen_front_cfg_pcm_instance *pcm_instance, const char *path, int index, int *cur_pb, int *cur_cap, int *stream_cnt) { char *str = NULL; char *stream_path; struct xen_front_cfg_stream *stream; int ret; stream_path = devm_kasprintf(&front_info->xb_dev->dev, GFP_KERNEL, "%s/%d", path, index); if (!stream_path) { ret = -ENOMEM; goto fail; } str = xenbus_read(XBT_NIL, stream_path, XENSND_FIELD_TYPE, NULL); if (IS_ERR(str)) { ret = PTR_ERR(str); str = NULL; goto fail; } if (!strncasecmp(str, XENSND_STREAM_TYPE_PLAYBACK, sizeof(XENSND_STREAM_TYPE_PLAYBACK))) { stream = &pcm_instance->streams_pb[(*cur_pb)++]; } else if (!strncasecmp(str, XENSND_STREAM_TYPE_CAPTURE, sizeof(XENSND_STREAM_TYPE_CAPTURE))) { stream = &pcm_instance->streams_cap[(*cur_cap)++]; } else { ret = -EINVAL; goto fail; } /* Get next stream index. */ stream->index = (*stream_cnt)++; stream->xenstore_path = stream_path; /* * Check XenStore if PCM HW configuration exists for this stream * and update if so, e.g. we inherit all values from device's PCM HW, * but can still override some of the values for the stream. */ cfg_read_pcm_hw(stream->xenstore_path, &pcm_instance->pcm_hw, &stream->pcm_hw); ret = 0; fail: kfree(str); return ret; } static int cfg_device(struct xen_snd_front_info *front_info, struct xen_front_cfg_pcm_instance *pcm_instance, struct snd_pcm_hardware *parent_pcm_hw, const char *path, int node_index, int *stream_cnt) { char *str; char *device_path; int ret, i, num_streams; int num_pb, num_cap; int cur_pb, cur_cap; char node[3]; device_path = kasprintf(GFP_KERNEL, "%s/%d", path, node_index); if (!device_path) return -ENOMEM; str = xenbus_read(XBT_NIL, device_path, XENSND_FIELD_DEVICE_NAME, NULL); if (!IS_ERR(str)) { strlcpy(pcm_instance->name, str, sizeof(pcm_instance->name)); kfree(str); } pcm_instance->device_id = node_index; /* * Check XenStore if PCM HW configuration exists for this device * and update if so, e.g. we inherit all values from card's PCM HW, * but can still override some of the values for the device. */ cfg_read_pcm_hw(device_path, parent_pcm_hw, &pcm_instance->pcm_hw); /* Find out how many streams were configured in Xen store. */ num_streams = 0; do { snprintf(node, sizeof(node), "%d", num_streams); if (!xenbus_exists(XBT_NIL, device_path, node)) break; num_streams++; } while (num_streams < VSND_MAX_STREAM); pcm_instance->num_streams_pb = 0; pcm_instance->num_streams_cap = 0; /* Get number of playback and capture streams. */ for (i = 0; i < num_streams; i++) { ret = cfg_get_stream_type(device_path, i, &num_pb, &num_cap); if (ret < 0) goto fail; pcm_instance->num_streams_pb += num_pb; pcm_instance->num_streams_cap += num_cap; } if (pcm_instance->num_streams_pb) { pcm_instance->streams_pb = devm_kcalloc(&front_info->xb_dev->dev, pcm_instance->num_streams_pb, sizeof(struct xen_front_cfg_stream), GFP_KERNEL); if (!pcm_instance->streams_pb) { ret = -ENOMEM; goto fail; } } if (pcm_instance->num_streams_cap) { pcm_instance->streams_cap = devm_kcalloc(&front_info->xb_dev->dev, pcm_instance->num_streams_cap, sizeof(struct xen_front_cfg_stream), GFP_KERNEL); if (!pcm_instance->streams_cap) { ret = -ENOMEM; goto fail; } } cur_pb = 0; cur_cap = 0; for (i = 0; i < num_streams; i++) { ret = cfg_stream(front_info, pcm_instance, device_path, i, &cur_pb, &cur_cap, stream_cnt); if (ret < 0) goto fail; } ret = 0; fail: kfree(device_path); return ret; } int xen_snd_front_cfg_card(struct xen_snd_front_info *front_info, int *stream_cnt) { struct xenbus_device *xb_dev = front_info->xb_dev; struct xen_front_cfg_card *cfg = &front_info->cfg; int ret, num_devices, i; char node[3]; *stream_cnt = 0; num_devices = 0; do { snprintf(node, sizeof(node), "%d", num_devices); if (!xenbus_exists(XBT_NIL, xb_dev->nodename, node)) break; num_devices++; } while (num_devices < SNDRV_PCM_DEVICES); if (!num_devices) { dev_warn(&xb_dev->dev, "No devices configured for sound card at %s\n", xb_dev->nodename); return -ENODEV; } /* Start from default PCM HW configuration for the card. */ cfg_read_pcm_hw(xb_dev->nodename, NULL, &cfg->pcm_hw); cfg->pcm_instances = devm_kcalloc(&front_info->xb_dev->dev, num_devices, sizeof(struct xen_front_cfg_pcm_instance), GFP_KERNEL); if (!cfg->pcm_instances) return -ENOMEM; for (i = 0; i < num_devices; i++) { ret = cfg_device(front_info, &cfg->pcm_instances[i], &cfg->pcm_hw, xb_dev->nodename, i, stream_cnt); if (ret < 0) return ret; } cfg->num_pcm_instances = num_devices; return 0; }