1 /**
2  * \file pcm/pcm_linear.c
3  * \ingroup PCM_Plugins
4  * \brief PCM Linear Conversion Plugin Interface
5  * \author Abramo Bagnara <abramo@alsa-project.org>
6  * \date 2000-2001
7  */
8 /*
9  *  PCM - Linear conversion
10  *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
11  *
12  *
13  *   This library is free software; you can redistribute it and/or modify
14  *   it under the terms of the GNU Lesser General Public License as
15  *   published by the Free Software Foundation; either version 2.1 of
16  *   the License, or (at your option) any later version.
17  *
18  *   This program is distributed in the hope that it will be useful,
19  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
20  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  *   GNU Lesser General Public License for more details.
22  *
23  *   You should have received a copy of the GNU Lesser General Public
24  *   License along with this library; if not, write to the Free Software
25  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
26  *
27  */
28 
29 #include "pcm_local.h"
30 #include "pcm_plugin.h"
31 #include "plugin_ops.h"
32 #include "bswap.h"
33 
34 #ifndef PIC
35 /* entry for static linking */
36 const char *_snd_module_pcm_linear = "";
37 #endif
38 
39 #ifndef DOC_HIDDEN
40 typedef struct {
41 	/* This field need to be the first */
42 	snd_pcm_plugin_t plug;
43 	unsigned int use_getput;
44 	unsigned int conv_idx;
45 	unsigned int get_idx, put_idx;
46 	snd_pcm_format_t sformat;
47 } snd_pcm_linear_t;
48 #endif
49 
50 #ifndef DOC_HIDDEN
51 
snd_pcm_linear_convert_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format)52 int snd_pcm_linear_convert_index(snd_pcm_format_t src_format,
53 				 snd_pcm_format_t dst_format)
54 {
55 	int src_endian, dst_endian, sign, src_width, dst_width;
56 
57 	sign = (snd_pcm_format_signed(src_format) !=
58 		snd_pcm_format_signed(dst_format));
59 #ifdef SND_LITTLE_ENDIAN
60 	src_endian = snd_pcm_format_big_endian(src_format);
61 	dst_endian = snd_pcm_format_big_endian(dst_format);
62 #else
63 	src_endian = snd_pcm_format_little_endian(src_format);
64 	dst_endian = snd_pcm_format_little_endian(dst_format);
65 #endif
66 
67 	if (src_endian < 0)
68 		src_endian = 0;
69 	if (dst_endian < 0)
70 		dst_endian = 0;
71 
72 	src_width = snd_pcm_format_width(src_format) / 8 - 1;
73 	dst_width = snd_pcm_format_width(dst_format) / 8 - 1;
74 
75 	return src_width * 32 + src_endian * 16 + sign * 8 + dst_width * 2 + dst_endian;
76 }
77 
snd_pcm_linear_get_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format)78 int snd_pcm_linear_get_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format)
79 {
80 	int sign, width, pwidth, endian;
81 	sign = (snd_pcm_format_signed(src_format) !=
82 		snd_pcm_format_signed(dst_format));
83 #ifdef SND_LITTLE_ENDIAN
84 	endian = snd_pcm_format_big_endian(src_format);
85 #else
86 	endian = snd_pcm_format_little_endian(src_format);
87 #endif
88 	if (endian < 0)
89 		endian = 0;
90 	pwidth = snd_pcm_format_physical_width(src_format);
91 	width = snd_pcm_format_width(src_format);
92 	if (pwidth == 24) {
93 		switch (width) {
94 		case 24:
95 			width = 0; break;
96 		case 20:
97 			width = 1; break;
98 		case 18:
99 		default:
100 			width = 2; break;
101 		}
102 		return width * 4 + endian * 2 + sign + 20;
103 	} else {
104 		if (width == 20)
105 			width = 40;
106 
107 		width = width / 8 - 1;
108 		return width * 4 + endian * 2 + sign;
109 	}
110 }
111 
snd_pcm_linear_put_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format)112 int snd_pcm_linear_put_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format)
113 {
114 	int sign, width, pwidth, endian;
115 	sign = (snd_pcm_format_signed(src_format) !=
116 		snd_pcm_format_signed(dst_format));
117 #ifdef SND_LITTLE_ENDIAN
118 	endian = snd_pcm_format_big_endian(dst_format);
119 #else
120 	endian = snd_pcm_format_little_endian(dst_format);
121 #endif
122 	if (endian < 0)
123 		endian = 0;
124 	pwidth = snd_pcm_format_physical_width(dst_format);
125 	width = snd_pcm_format_width(dst_format);
126 	if (pwidth == 24) {
127 		switch (width) {
128 		case 24:
129 			width = 0; break;
130 		case 20:
131 			width = 1; break;
132 		case 18:
133 		default:
134 			width = 2; break;
135 		}
136 		return width * 4 + endian * 2 + sign + 20;
137 	} else {
138 		if (width == 20)
139 			width = 40;
140 
141 		width = width / 8 - 1;
142 		return width * 4 + endian * 2 + sign;
143 	}
144 }
145 
snd_pcm_linear_convert(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset, const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset, unsigned int channels, snd_pcm_uframes_t frames, unsigned int convidx)146 void snd_pcm_linear_convert(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset,
147 			    const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset,
148 			    unsigned int channels, snd_pcm_uframes_t frames,
149 			    unsigned int convidx)
150 {
151 #define CONV_LABELS
152 #include "plugin_ops.h"
153 #undef CONV_LABELS
154 	void *conv = conv_labels[convidx];
155 	unsigned int channel;
156 	for (channel = 0; channel < channels; ++channel) {
157 		const char *src;
158 		char *dst;
159 		int src_step, dst_step;
160 		snd_pcm_uframes_t frames1;
161 		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
162 		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
163 		src = snd_pcm_channel_area_addr(src_area, src_offset);
164 		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
165 		src_step = snd_pcm_channel_area_step(src_area);
166 		dst_step = snd_pcm_channel_area_step(dst_area);
167 		frames1 = frames;
168 		while (frames1-- > 0) {
169 			goto *conv;
170 #define CONV_END after
171 #include "plugin_ops.h"
172 #undef CONV_END
173 		after:
174 			src += src_step;
175 			dst += dst_step;
176 		}
177 	}
178 }
179 
snd_pcm_linear_getput(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset, const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset, unsigned int channels, snd_pcm_uframes_t frames, unsigned int get_idx, unsigned int put_idx)180 void snd_pcm_linear_getput(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset,
181 			   const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset,
182 			   unsigned int channels, snd_pcm_uframes_t frames,
183 			   unsigned int get_idx, unsigned int put_idx)
184 {
185 #define CONV24_LABELS
186 #include "plugin_ops.h"
187 #undef CONV24_LABELS
188 	void *get = get32_labels[get_idx];
189 	void *put = put32_labels[put_idx];
190 	unsigned int channel;
191 	uint32_t sample = 0;
192 	for (channel = 0; channel < channels; ++channel) {
193 		const char *src;
194 		char *dst;
195 		int src_step, dst_step;
196 		snd_pcm_uframes_t frames1;
197 		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
198 		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
199 		src = snd_pcm_channel_area_addr(src_area, src_offset);
200 		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
201 		src_step = snd_pcm_channel_area_step(src_area);
202 		dst_step = snd_pcm_channel_area_step(dst_area);
203 		frames1 = frames;
204 		while (frames1-- > 0) {
205 			goto *get;
206 #define CONV24_END after
207 #include "plugin_ops.h"
208 #undef CONV24_END
209 		after:
210 			src += src_step;
211 			dst += dst_step;
212 		}
213 	}
214 }
215 
216 #endif /* DOC_HIDDEN */
217 
snd_pcm_linear_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)218 static int snd_pcm_linear_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)
219 {
220 	int err;
221 	snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
222 	snd_pcm_format_mask_t format_mask = { SND_PCM_FMTBIT_LINEAR };
223 	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
224 					 &access_mask);
225 	if (err < 0)
226 		return err;
227 	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_FORMAT,
228 					 &format_mask);
229 	if (err < 0)
230 		return err;
231 	err = _snd_pcm_hw_params_set_subformat(params, SND_PCM_SUBFORMAT_STD);
232 	if (err < 0)
233 		return err;
234 	params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
235 	return 0;
236 }
237 
snd_pcm_linear_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams)238 static int snd_pcm_linear_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams)
239 {
240 	snd_pcm_linear_t *linear = pcm->private_data;
241 	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
242 	_snd_pcm_hw_params_any(sparams);
243 	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
244 				   &saccess_mask);
245 	_snd_pcm_hw_params_set_format(sparams, linear->sformat);
246 	_snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD);
247 	return 0;
248 }
249 
snd_pcm_linear_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params, snd_pcm_hw_params_t *sparams)250 static int snd_pcm_linear_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
251 					    snd_pcm_hw_params_t *sparams)
252 {
253 	int err;
254 	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
255 			      SND_PCM_HW_PARBIT_RATE |
256 			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
257 			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
258 			      SND_PCM_HW_PARBIT_PERIODS |
259 			      SND_PCM_HW_PARBIT_PERIOD_TIME |
260 			      SND_PCM_HW_PARBIT_BUFFER_TIME |
261 			      SND_PCM_HW_PARBIT_TICK_TIME);
262 	err = _snd_pcm_hw_params_refine(sparams, links, params);
263 	if (err < 0)
264 		return err;
265 	return 0;
266 }
267 
snd_pcm_linear_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params, snd_pcm_hw_params_t *sparams)268 static int snd_pcm_linear_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
269 					    snd_pcm_hw_params_t *sparams)
270 {
271 	int err;
272 	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
273 			      SND_PCM_HW_PARBIT_RATE |
274 			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
275 			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
276 			      SND_PCM_HW_PARBIT_PERIODS |
277 			      SND_PCM_HW_PARBIT_PERIOD_TIME |
278 			      SND_PCM_HW_PARBIT_BUFFER_TIME |
279 			      SND_PCM_HW_PARBIT_TICK_TIME);
280 	err = _snd_pcm_hw_params_refine(params, links, sparams);
281 	if (err < 0)
282 		return err;
283 	return 0;
284 }
285 
snd_pcm_linear_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)286 static int snd_pcm_linear_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
287 {
288 	return snd_pcm_hw_refine_slave(pcm, params,
289 				       snd_pcm_linear_hw_refine_cprepare,
290 				       snd_pcm_linear_hw_refine_cchange,
291 				       snd_pcm_linear_hw_refine_sprepare,
292 				       snd_pcm_linear_hw_refine_schange,
293 				       snd_pcm_generic_hw_refine);
294 }
295 
snd_pcm_linear_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)296 static int snd_pcm_linear_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
297 {
298 	snd_pcm_linear_t *linear = pcm->private_data;
299 	snd_pcm_format_t format;
300 	int err = snd_pcm_hw_params_slave(pcm, params,
301 					  snd_pcm_linear_hw_refine_cchange,
302 					  snd_pcm_linear_hw_refine_sprepare,
303 					  snd_pcm_linear_hw_refine_schange,
304 					  snd_pcm_generic_hw_params);
305 	if (err < 0)
306 		return err;
307 	err = INTERNAL(snd_pcm_hw_params_get_format)(params, &format);
308 	if (err < 0)
309 		return err;
310 	linear->use_getput = (snd_pcm_format_physical_width(format) == 24 ||
311 			      snd_pcm_format_physical_width(linear->sformat) == 24 ||
312 			      snd_pcm_format_width(format) == 20 ||
313 			      snd_pcm_format_width(linear->sformat) == 20);
314 	if (linear->use_getput) {
315 		if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
316 			linear->get_idx = snd_pcm_linear_get_index(format, SND_PCM_FORMAT_S32);
317 			linear->put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S32, linear->sformat);
318 		} else {
319 			linear->get_idx = snd_pcm_linear_get_index(linear->sformat, SND_PCM_FORMAT_S32);
320 			linear->put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S32, format);
321 		}
322 	} else {
323 		if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
324 			linear->conv_idx = snd_pcm_linear_convert_index(format,
325 									linear->sformat);
326 		else
327 			linear->conv_idx = snd_pcm_linear_convert_index(linear->sformat,
328 									format);
329 	}
330 	return 0;
331 }
332 
333 static snd_pcm_uframes_t
snd_pcm_linear_write_areas(snd_pcm_t *pcm, const snd_pcm_channel_area_t *areas, snd_pcm_uframes_t offset, snd_pcm_uframes_t size, const snd_pcm_channel_area_t *slave_areas, snd_pcm_uframes_t slave_offset, snd_pcm_uframes_t *slave_sizep)334 snd_pcm_linear_write_areas(snd_pcm_t *pcm,
335 			   const snd_pcm_channel_area_t *areas,
336 			   snd_pcm_uframes_t offset,
337 			   snd_pcm_uframes_t size,
338 			   const snd_pcm_channel_area_t *slave_areas,
339 			   snd_pcm_uframes_t slave_offset,
340 			   snd_pcm_uframes_t *slave_sizep)
341 {
342 	snd_pcm_linear_t *linear = pcm->private_data;
343 	if (size > *slave_sizep)
344 		size = *slave_sizep;
345 	if (linear->use_getput)
346 		snd_pcm_linear_getput(slave_areas, slave_offset,
347 				      areas, offset,
348 				      pcm->channels, size,
349 				      linear->get_idx, linear->put_idx);
350 	else
351 		snd_pcm_linear_convert(slave_areas, slave_offset,
352 				       areas, offset,
353 				       pcm->channels, size, linear->conv_idx);
354 	*slave_sizep = size;
355 	return size;
356 }
357 
358 static snd_pcm_uframes_t
snd_pcm_linear_read_areas(snd_pcm_t *pcm, const snd_pcm_channel_area_t *areas, snd_pcm_uframes_t offset, snd_pcm_uframes_t size, const snd_pcm_channel_area_t *slave_areas, snd_pcm_uframes_t slave_offset, snd_pcm_uframes_t *slave_sizep)359 snd_pcm_linear_read_areas(snd_pcm_t *pcm,
360 			  const snd_pcm_channel_area_t *areas,
361 			  snd_pcm_uframes_t offset,
362 			  snd_pcm_uframes_t size,
363 			  const snd_pcm_channel_area_t *slave_areas,
364 			  snd_pcm_uframes_t slave_offset,
365 			  snd_pcm_uframes_t *slave_sizep)
366 {
367 	snd_pcm_linear_t *linear = pcm->private_data;
368 	if (size > *slave_sizep)
369 		size = *slave_sizep;
370 	if (linear->use_getput)
371 		snd_pcm_linear_getput(areas, offset,
372 				      slave_areas, slave_offset,
373 				      pcm->channels, size,
374 				      linear->get_idx, linear->put_idx);
375 	else
376 		snd_pcm_linear_convert(areas, offset,
377 				       slave_areas, slave_offset,
378 				       pcm->channels, size, linear->conv_idx);
379 	*slave_sizep = size;
380 	return size;
381 }
382 
snd_pcm_linear_dump(snd_pcm_t *pcm, snd_output_t *out)383 static void snd_pcm_linear_dump(snd_pcm_t *pcm, snd_output_t *out)
384 {
385 	snd_pcm_linear_t *linear = pcm->private_data;
386 	snd_output_printf(out, "Linear conversion PCM (%s)\n",
387 		snd_pcm_format_name(linear->sformat));
388 	if (pcm->setup) {
389 		snd_output_printf(out, "Its setup is:\n");
390 		snd_pcm_dump_setup(pcm, out);
391 	}
392 	snd_output_printf(out, "Slave: ");
393 	snd_pcm_dump(linear->plug.gen.slave, out);
394 }
395 
396 static const snd_pcm_ops_t snd_pcm_linear_ops = {
397 	.close = snd_pcm_generic_close,
398 	.info = snd_pcm_generic_info,
399 	.hw_refine = snd_pcm_linear_hw_refine,
400 	.hw_params = snd_pcm_linear_hw_params,
401 	.hw_free = snd_pcm_generic_hw_free,
402 	.sw_params = snd_pcm_generic_sw_params,
403 	.channel_info = snd_pcm_generic_channel_info,
404 	.dump = snd_pcm_linear_dump,
405 	.nonblock = snd_pcm_generic_nonblock,
406 	.async = snd_pcm_generic_async,
407 	.mmap = snd_pcm_generic_mmap,
408 	.munmap = snd_pcm_generic_munmap,
409 	.query_chmaps = snd_pcm_generic_query_chmaps,
410 	.get_chmap = snd_pcm_generic_get_chmap,
411 	.set_chmap = snd_pcm_generic_set_chmap,
412 };
413 
414 
415 /**
416  * \brief Creates a new linear conversion PCM
417  * \param pcmp Returns created PCM handle
418  * \param name Name of PCM
419  * \param sformat Slave (destination) format
420  * \param slave Slave PCM handle
421  * \param close_slave When set, the slave PCM handle is closed with copy PCM
422  * \retval zero on success otherwise a negative error code
423  * \warning Using of this function might be dangerous in the sense
424  *          of compatibility reasons. The prototype might be freely
425  *          changed in future.
426  */
snd_pcm_linear_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, snd_pcm_t *slave, int close_slave)427 int snd_pcm_linear_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, snd_pcm_t *slave, int close_slave)
428 {
429 	snd_pcm_t *pcm;
430 	snd_pcm_linear_t *linear;
431 	int err;
432 	assert(pcmp && slave);
433 	if (snd_pcm_format_linear(sformat) != 1)
434 		return -EINVAL;
435 	linear = calloc(1, sizeof(snd_pcm_linear_t));
436 	if (!linear) {
437 		return -ENOMEM;
438 	}
439 	snd_pcm_plugin_init(&linear->plug);
440 	linear->sformat = sformat;
441 	linear->plug.read = snd_pcm_linear_read_areas;
442 	linear->plug.write = snd_pcm_linear_write_areas;
443 	linear->plug.undo_read = snd_pcm_plugin_undo_read_generic;
444 	linear->plug.undo_write = snd_pcm_plugin_undo_write_generic;
445 	linear->plug.gen.slave = slave;
446 	linear->plug.gen.close_slave = close_slave;
447 
448 	err = snd_pcm_new(&pcm, SND_PCM_TYPE_LINEAR, name, slave->stream, slave->mode);
449 	if (err < 0) {
450 		free(linear);
451 		return err;
452 	}
453 	pcm->ops = &snd_pcm_linear_ops;
454 	pcm->fast_ops = &snd_pcm_plugin_fast_ops;
455 	pcm->private_data = linear;
456 	pcm->poll_fd = slave->poll_fd;
457 	pcm->poll_events = slave->poll_events;
458 	pcm->tstamp_type = slave->tstamp_type;
459 	snd_pcm_set_hw_ptr(pcm, &linear->plug.hw_ptr, -1, 0);
460 	snd_pcm_set_appl_ptr(pcm, &linear->plug.appl_ptr, -1, 0);
461 	*pcmp = pcm;
462 
463 	return 0;
464 }
465 
466 /*! \page pcm_plugins
467 
468 \section pcm_plugins_linear Plugin: linear
469 
470 This plugin converts linear samples from master linear conversion PCM to given
471 slave PCM. The channel count, format and rate must match for both of them.
472 
473 \code
474 pcm.name {
475         type linear             # Linear conversion PCM
476         slave STR               # Slave name
477         # or
478         slave {                 # Slave definition
479                 pcm STR         # Slave PCM name
480                 # or
481                 pcm { }         # Slave PCM definition
482                 format STR      # Slave format
483         }
484 }
485 \endcode
486 
487 \subsection pcm_plugins_linear_funcref Function reference
488 
489 <UL>
490   <LI>snd_pcm_linear_open()
491   <LI>_snd_pcm_linear_open()
492 </UL>
493 
494 */
495 
496 /**
497  * \brief Creates a new linear conversion PCM
498  * \param pcmp Returns created PCM handle
499  * \param name Name of PCM
500  * \param root Root configuration node
501  * \param conf Configuration node with copy PCM description
502  * \param stream Stream type
503  * \param mode Stream mode
504  * \retval zero on success otherwise a negative error code
505  * \warning Using of this function might be dangerous in the sense
506  *          of compatibility reasons. The prototype might be freely
507  *          changed in future.
508  */
_snd_pcm_linear_open(snd_pcm_t **pcmp, const char *name, snd_config_t *root, snd_config_t *conf, snd_pcm_stream_t stream, int mode)509 int _snd_pcm_linear_open(snd_pcm_t **pcmp, const char *name,
510 			 snd_config_t *root, snd_config_t *conf,
511 			 snd_pcm_stream_t stream, int mode)
512 {
513 	snd_config_iterator_t i, next;
514 	int err;
515 	snd_pcm_t *spcm;
516 	snd_config_t *slave = NULL, *sconf;
517 	snd_pcm_format_t sformat;
518 	snd_config_for_each(i, next, conf) {
519 		snd_config_t *n = snd_config_iterator_entry(i);
520 		const char *id;
521 		if (snd_config_get_id(n, &id) < 0)
522 			continue;
523 		if (snd_pcm_conf_generic_id(id))
524 			continue;
525 		if (strcmp(id, "slave") == 0) {
526 			slave = n;
527 			continue;
528 		}
529 		SNDERR("Unknown field %s", id);
530 		return -EINVAL;
531 	}
532 	if (!slave) {
533 		SNDERR("slave is not defined");
534 		return -EINVAL;
535 	}
536 	err = snd_pcm_slave_conf(root, slave, &sconf, 1,
537 				 SND_PCM_HW_PARAM_FORMAT, SCONF_MANDATORY, &sformat);
538 	if (err < 0)
539 		return err;
540 	if (snd_pcm_format_linear(sformat) != 1) {
541 		snd_config_delete(sconf);
542 		SNDERR("slave format is not linear");
543 		return -EINVAL;
544 	}
545 	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
546 	snd_config_delete(sconf);
547 	if (err < 0)
548 		return err;
549 	err = snd_pcm_linear_open(pcmp, name, sformat, spcm, 1);
550 	if (err < 0)
551 		snd_pcm_close(spcm);
552 	return err;
553 }
554 #ifndef DOC_HIDDEN
555 SND_DLSYM_BUILD_VERSION(_snd_pcm_linear_open, SND_PCM_DLSYM_VERSION);
556 #endif
557