From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752681AbdHGHq1 (ORCPT ); Mon, 7 Aug 2017 03:46:27 -0400 Received: from mail-lf0-f67.google.com ([209.85.215.67]:35211 "EHLO mail-lf0-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751247AbdHGHnj (ORCPT ); Mon, 7 Aug 2017 03:43:39 -0400 From: Oleksandr Andrushchenko To: alsa-devel@alsa-project.org, xen-devel@lists.xen.org, linux-kernel@vger.kernel.org Cc: perex@perex.cz, tiwai@suse.com, andr2000@gmail.com, Oleksandr Andrushchenko Subject: [PATCH 03/11] ALSA: vsnd: Read sound driver configuration from Xen store Date: Mon, 7 Aug 2017 10:43:08 +0300 Message-Id: <1502091796-14413-4-git-send-email-andr2000@gmail.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1502091796-14413-1-git-send-email-andr2000@gmail.com> References: <1502091796-14413-1-git-send-email-andr2000@gmail.com> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Oleksandr Andrushchenko Read configuration values from Xen store according to xen/interface/io/sndif.h protocol: - introduce configuration structures for different components, e.g. sound card, device, stream - read PCM HW parameters, e.g rate, format etc. - detect stream type (capture/playback) - read device and card parameters Fill in platform data with the configuration read, so it can be passed to sound driver later. Signed-off-by: Oleksandr Andrushchenko --- sound/drivers/xen-front.c | 530 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 530 insertions(+) diff --git a/sound/drivers/xen-front.c b/sound/drivers/xen-front.c index c4fd21cac3a7..ef48cbf44cf2 100644 --- a/sound/drivers/xen-front.c +++ b/sound/drivers/xen-front.c @@ -20,24 +20,554 @@ #include +#include +#include + #include #include #include #include +/* maximum number of supported streams */ +#define VSND_MAX_STREAM 8 + +struct cfg_stream { + int unique_id; + char *xenstore_path; + struct snd_pcm_hardware pcm_hw; +}; + +struct cfg_pcm_instance { + char name[80]; + int device_id; + struct snd_pcm_hardware pcm_hw; + int num_streams_pb; + struct cfg_stream *streams_pb; + int num_streams_cap; + struct cfg_stream *streams_cap; +}; + +struct cfg_card { + char name_short[32]; + char name_long[80]; + struct snd_pcm_hardware pcm_hw; + int num_pcm_instances; + struct cfg_pcm_instance *pcm_instances; +}; + +struct sdev_card_plat_data { + struct xdrv_info *xdrv_info; + struct cfg_card cfg_card; +}; + struct xdrv_info { struct xenbus_device *xb_dev; spinlock_t io_lock; struct mutex mutex; + struct sdev_card_plat_data cfg_plat_data; }; +#define MAX_XEN_BUFFER_SIZE (64 * 1024) +#define MAX_BUFFER_SIZE MAX_XEN_BUFFER_SIZE +#define MIN_PERIOD_SIZE 64 +#define MAX_PERIOD_SIZE (MAX_BUFFER_SIZE / 8) +#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 8 + +static struct snd_pcm_hardware sdrv_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, +}; + +struct CFG_HW_SAMPLE_RATE { + const char *name; + unsigned int mask; + unsigned int value; +}; + +static 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 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; +} + +static void cfg_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 if any */ + *pcm_hw = *parent_pcm_hw; + + 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; +} + +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 = -EINVAL; + 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 xdrv_info *drv_info, + struct cfg_pcm_instance *pcm_instance, + const char *path, int index, int *cur_pb, int *cur_cap, + int *stream_idx) +{ + char *str = NULL; + char *stream_path; + struct cfg_stream *stream; + int ret; + + stream_path = devm_kasprintf(&drv_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 = -EINVAL; + 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->unique_id = (*stream_idx)++; + stream->xenstore_path = stream_path; + /* + * check in Xen store 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_pcm_hw(stream->xenstore_path, + &pcm_instance->pcm_hw, &stream->pcm_hw); + ret = 0; + +fail: + kfree(str); + return ret; +} + +static int cfg_device(struct xdrv_info *drv_info, + struct cfg_pcm_instance *pcm_instance, + struct snd_pcm_hardware *parent_pcm_hw, + const char *path, int node_index, int *stream_idx) +{ + 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)) { + strncpy(pcm_instance->name, str, sizeof(pcm_instance->name)); + kfree(str); + } + + pcm_instance->device_id = node_index; + + /* + * check in Xen store 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_pcm_hw(device_path, parent_pcm_hw, &pcm_instance->pcm_hw); + + /* + * find out how many streams were configured in Xen store: + * streams must have sequential unique IDs, so stop when one + * does not exist + */ + 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( + &drv_info->xb_dev->dev, + pcm_instance->num_streams_pb, + sizeof(struct 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( + &drv_info->xb_dev->dev, + pcm_instance->num_streams_cap, + sizeof(struct 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(drv_info, + pcm_instance, device_path, i, &cur_pb, &cur_cap, + stream_idx); + if (ret < 0) + goto fail; + } + ret = 0; + +fail: + kfree(device_path); + return ret; +} + +static int cfg_card(struct xdrv_info *drv_info, + struct sdev_card_plat_data *plat_data, int *stream_idx) +{ + struct xenbus_device *xb_dev = drv_info->xb_dev; + int ret, num_devices, i; + char node[3]; + + 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_pcm_hw(xb_dev->nodename, &sdrv_pcm_hw_default, + &plat_data->cfg_card.pcm_hw); + + plat_data->cfg_card.pcm_instances = devm_kcalloc( + &drv_info->xb_dev->dev, num_devices, + sizeof(struct cfg_pcm_instance), GFP_KERNEL); + if (!plat_data->cfg_card.pcm_instances) + return -ENOMEM; + + for (i = 0; i < num_devices; i++) { + ret = cfg_device(drv_info, + &plat_data->cfg_card.pcm_instances[i], + &plat_data->cfg_card.pcm_hw, + xb_dev->nodename, i, stream_idx); + if (ret < 0) + return ret; + } + plat_data->cfg_card.num_pcm_instances = num_devices; + return 0; +} + static void xdrv_remove_internal(struct xdrv_info *drv_info) { } static int xdrv_be_on_initwait(struct xdrv_info *drv_info) { + int stream_idx; + int ret; + + drv_info->cfg_plat_data.xdrv_info = drv_info; + stream_idx = 0; + ret = cfg_card(drv_info, &drv_info->cfg_plat_data, &stream_idx); + if (ret < 0) + return ret; return 0; } -- 2.7.4