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