| // SPDX-License-Identifier: GPL-2.0 OR MIT | 
 |  | 
 | /* | 
 |  * Xen para-virtual sound device | 
 |  * | 
 |  * Copyright (C) 2016-2018 EPAM Systems Inc. | 
 |  * | 
 |  * Author: Oleksandr Andrushchenko <[email protected]> | 
 |  */ | 
 |  | 
 | #include <xen/xenbus.h> | 
 |  | 
 | #include <xen/interface/io/sndif.h> | 
 |  | 
 | #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; | 
 | } | 
 |  |