1 /*
2 * Copyright (c) 2019-2025 Allwinner Technology Co., Ltd. ALL rights reserved.
3 *
4 * Allwinner is a trademark of Allwinner Technology Co.,Ltd., registered in
5 * the the people's Republic of China and other countries.
6 * All Allwinner Technology Co.,Ltd. trademarks are used with permission.
7 *
8 * DISCLAIMER
9 * THIRD PARTY LICENCES MAY BE REQUIRED TO IMPLEMENT THE SOLUTION/PRODUCT.
10 * IF YOU NEED TO INTEGRATE THIRD PARTY’S TECHNOLOGY (SONY, DTS, DOLBY, AVS OR MPEGLA, ETC.)
11 * IN ALLWINNERS’SDK OR PRODUCTS, YOU SHALL BE SOLELY RESPONSIBLE TO OBTAIN
12 * ALL APPROPRIATELY REQUIRED THIRD PARTY LICENCES.
13 * ALLWINNER SHALL HAVE NO WARRANTY, INDEMNITY OR OTHER OBLIGATIONS WITH RESPECT TO MATTERS
14 * COVERED UNDER ANY REQUIRED THIRD PARTY LICENSE.
15 * YOU ARE SOLELY RESPONSIBLE FOR YOUR USAGE OF THIRD PARTY’S TECHNOLOGY.
16 *
17 *
18 * THIS SOFTWARE IS PROVIDED BY ALLWINNER"AS IS" AND TO THE MAXIMUM EXTENT
19 * PERMITTED BY LAW, ALLWINNER EXPRESSLY DISCLAIMS ALL WARRANTIES OF ANY KIND,
20 * WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION REGARDING
21 * THE TITLE, NON-INFRINGEMENT, ACCURACY, CONDITION, COMPLETENESS, PERFORMANCE
22 * OR MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
23 * IN NO EVENT SHALL ALLWINNER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 * LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30 * OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32 
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <errno.h>
36 #include <aw-alsa-lib/pcm_extplug.h>
37 #include "pcm_local.h"
38 #include "pcm_plugin_generic.h"
39 #include "pcm_ext_parm.h"
40 
41 typedef struct snd_pcm_extplug_priv {
42     snd_pcm_plugin_t plug;
43     snd_pcm_extplug_t *data;
44     struct snd_ext_parm params[SND_PCM_EXTPLUG_HW_PARAMS];
45     struct snd_ext_parm sparams[SND_PCM_EXTPLUG_HW_PARAMS];
46 } extplug_priv_t;
47 
48 
49 static const int hw_params_type[SND_PCM_EXTPLUG_HW_PARAMS] = {
50     [SND_PCM_EXTPLUG_HW_FORMAT] = SND_PCM_HW_PARAM_FORMAT,
51     [SND_PCM_EXTPLUG_HW_CHANNELS] = SND_PCM_HW_PARAM_CHANNELS
52 };
53 
54 #define is_mask_type(i) (hw_params_type[i] < SND_PCM_HW_PARAM_FIRST_RANGE)
55 
56 static const unsigned int excl_parbits[SND_PCM_EXTPLUG_HW_PARAMS] = {
57     [SND_PCM_EXTPLUG_HW_FORMAT] = (SND_PCM_HW_PARBIT_FORMAT|
58                        SND_PCM_HW_PARBIT_SAMPLE_BITS),
59     [SND_PCM_EXTPLUG_HW_CHANNELS] = (SND_PCM_HW_PARBIT_CHANNELS|
60                      SND_PCM_HW_PARBIT_FRAME_BITS),
61 };
62 
63 /*
64  * set min/max values for the given parameter
65  */
snd_ext_parm_set_minmax(struct snd_ext_parm * parm,unsigned int min,unsigned int max)66 int snd_ext_parm_set_minmax(struct snd_ext_parm *parm, unsigned int min, unsigned int max)
67 {
68     parm->num_list = 0;
69     free(parm->list);
70     parm->list = NULL;
71     parm->min = min;
72     parm->max = max;
73     parm->active = 1;
74     return 0;
75 }
76 
77 /*
78  * set the list of available values for the given parameter
79  */
val_compar(const void * ap,const void * bp)80 static int val_compar(const void *ap, const void *bp)
81 {
82     return *(const unsigned int *)ap - *(const unsigned int *)bp;
83 }
84 
snd_ext_parm_set_list(struct snd_ext_parm * parm,unsigned int num_list,const unsigned int * list)85 int snd_ext_parm_set_list(struct snd_ext_parm *parm, unsigned int num_list, const unsigned int *list)
86 {
87     unsigned int *new_list;
88 
89     new_list = malloc(sizeof(*new_list) * num_list);
90     if (new_list == NULL)
91         return -ENOMEM;
92     memcpy(new_list, list, sizeof(*new_list) * num_list);
93     qsort(new_list, num_list, sizeof(*new_list), val_compar);
94 
95     free(parm->list);
96     parm->num_list = num_list;
97     parm->list = new_list;
98     parm->active = 1;
99     return 0;
100 }
101 
snd_ext_parm_clear(struct snd_ext_parm * parm)102 void snd_ext_parm_clear(struct snd_ext_parm *parm)
103 {
104     free(parm->list);
105     memset(parm, 0, sizeof(*parm));
106 }
107 
108 /*
109  * limit the interval range to the given list
110  */
_snd_range_list(snd_interval_t * ival,int num_list,unsigned int * list)111 static int _snd_range_list(snd_interval_t *ival, int num_list, unsigned int *list)
112 {
113     int imin, imax;
114     int changed = 0;
115 
116     if (snd_range_empty(ival))
117         return -ENOENT;
118     for (imin = 0; imin < num_list; imin++) {
119         if (ival->range.min == list[imin] && ! ival->range.openmin)
120             break;
121         if (ival->range.min <= list[imin]) {
122             ival->range.min = list[imin];
123             ival->range.openmin = 0;
124             changed = 1;
125             break;
126         }
127     }
128     if (imin >= num_list)
129         return -EINVAL;
130     for (imax = num_list - 1; imax >= imin; imax--) {
131         if (ival->range.max == list[imax] && ! ival->range.openmax)
132             break;
133         if (ival->range.max >= list[imax]) {
134             ival->range.max = list[imax];
135             ival->range.openmax = 0;
136             changed = 1;
137             break;
138         }
139     }
140     if (imax < imin)
141         return -EINVAL;
142     return changed;
143 }
144 
145 /*
146  * refine the range parameter
147  */
snd_ext_parm_range_refine(snd_interval_t * ival,struct snd_ext_parm * parm,int type)148 int snd_ext_parm_range_refine(snd_interval_t *ival, struct snd_ext_parm *parm, int type)
149 {
150     parm += type;
151     if (!parm->active)
152         return 0;
153     ival->range.integer |= parm->integer;
154     if (parm->num_list) {
155         return _snd_range_list(ival, parm->num_list, parm->list);
156     } else if (parm->min || parm->max) {
157         snd_interval_t t;
158         memset(&t, 0, sizeof(t));
159         snd_range_set_minmax(&t, parm->min, parm->max);
160         t.range.integer = ival->range.integer;
161         return snd_range_refine(ival, &t);
162     }
163     return 0;
164 }
165 
166 /*
167  * refine the mask parameter
168  */
snd_ext_parm_mask_refine(snd_interval_t * mask,struct snd_ext_parm * parm,int type)169 int snd_ext_parm_mask_refine(snd_interval_t *mask, struct snd_ext_parm *parm, int type)
170 {
171     snd_interval_t bits;
172     unsigned int i;
173 
174     parm += type;
175     if (!parm->active)
176         return 0;
177     memset(&bits, 0, sizeof(bits));
178     for (i = 0; i < parm->num_list; i++)
179         bits.mask |= 1U << (parm->list[i] % 32);
180     return snd_mask_refine(mask, &bits);
181 }
182 
183 
snd_pcm_extplug_set_param_list(snd_pcm_extplug_t * extplug,int type,unsigned int num_list,const unsigned int * list)184 int snd_pcm_extplug_set_param_list(snd_pcm_extplug_t *extplug, int type,
185         unsigned int num_list, const unsigned int *list)
186 {
187     extplug_priv_t *ext = extplug->pcm->private_data;
188     if (type < 0 || type >= SND_PCM_EXTPLUG_HW_PARAMS) {
189         awalsa_err("EXTPLUG: invalid parameter type %d\n", type);
190         return -EINVAL;
191     }
192     return snd_ext_parm_set_list(&ext->params[type], num_list, list);
193 }
194 
snd_pcm_extplug_set_param_minmax(snd_pcm_extplug_t * extplug,int type,unsigned int min,unsigned int max)195 int snd_pcm_extplug_set_param_minmax(snd_pcm_extplug_t *extplug, int type,
196         unsigned int min, unsigned int max)
197 {
198     extplug_priv_t *ext = extplug->pcm->private_data;
199     if (type < 0 || type >= SND_PCM_EXTPLUG_HW_PARAMS) {
200         awalsa_err("EXTPLUG: invalid parameter type %d\n", type);
201         return -EINVAL;
202     }
203     if (is_mask_type(type)) {
204         awalsa_err("EXTPLUG: invalid parameter type %d\n", type);
205         return -EINVAL;
206     }
207     return snd_ext_parm_set_minmax(&ext->params[type], min, max);
208 }
209 
snd_pcm_extplug_set_slave_param_list(snd_pcm_extplug_t * extplug,int type,unsigned int num_list,const unsigned int * list)210 int snd_pcm_extplug_set_slave_param_list(snd_pcm_extplug_t *extplug, int type,
211         unsigned int num_list, const unsigned int *list)
212 {
213     extplug_priv_t *ext = extplug->pcm->private_data;
214     if (type < 0 || type >= SND_PCM_EXTPLUG_HW_PARAMS) {
215         awalsa_err("EXTPLUG: invalid parameter type %d\n", type);
216         return -EINVAL;
217     }
218     return snd_ext_parm_set_list(&ext->sparams[type], num_list, list);
219 }
220 
snd_pcm_extplug_set_slave_param_minmax(snd_pcm_extplug_t * extplug,int type,unsigned int min,unsigned int max)221 int snd_pcm_extplug_set_slave_param_minmax(snd_pcm_extplug_t *extplug, int type,
222         unsigned int min, unsigned int max)
223 {
224     extplug_priv_t *ext = extplug->pcm->private_data;
225     if (type < 0 || type >= SND_PCM_EXTPLUG_HW_PARAMS) {
226         awalsa_err("EXTPLUG: invalid parameter type %d\n", type);
227         return -EINVAL;
228     }
229     if (is_mask_type(type)) {
230         awalsa_err("EXTPLUG: invalid parameter type %d\n", type);
231         return -EINVAL;
232     }
233     return snd_ext_parm_set_minmax(&ext->sparams[type], min, max);
234 }
235 
clear_ext_params(extplug_priv_t * ext)236 static void clear_ext_params(extplug_priv_t *ext)
237 {
238     int i;
239     for (i = 0; i < SND_PCM_EXTPLUG_HW_PARAMS; i++) {
240         snd_ext_parm_clear(&ext->params[i]);
241         snd_ext_parm_clear(&ext->sparams[i]);
242     }
243 }
244 
snd_pcm_extplug_params_reset(snd_pcm_extplug_t * extplug)245 void snd_pcm_extplug_params_reset(snd_pcm_extplug_t *extplug)
246 {
247     extplug_priv_t *ext = extplug->pcm->private_data;
248     clear_ext_params(ext);
249 }
250 
251 /*
252  * call init callback
253  */
snd_pcm_extplug_init(snd_pcm_t * pcm)254 static int snd_pcm_extplug_init(snd_pcm_t *pcm)
255 {
256     awalsa_debug("\n");
257     extplug_priv_t *ext = pcm->private_data;
258     return ext->data->callback->init(ext->data);
259 }
260 
261 /*
262  * write_areas skeleton - call transfer callback
263  */
264 static snd_pcm_uframes_t
snd_pcm_extplug_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)265 snd_pcm_extplug_write_areas(snd_pcm_t *pcm,
266                 const snd_pcm_channel_area_t *areas,
267                 snd_pcm_uframes_t offset,
268                 snd_pcm_uframes_t size,
269                 const snd_pcm_channel_area_t *slave_areas,
270                 snd_pcm_uframes_t slave_offset,
271                 snd_pcm_uframes_t *slave_sizep)
272 {
273     extplug_priv_t *ext = pcm->private_data;
274 
275     if (size > *slave_sizep)
276         size = *slave_sizep;
277     size = ext->data->callback->transfer(ext->data, slave_areas, slave_offset,
278                          areas, offset, size);
279     *slave_sizep = size;
280     return size;
281 }
282 
283 /*
284  * read_areas skeleton - call transfer callback
285  */
286 static snd_pcm_uframes_t
snd_pcm_extplug_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)287 snd_pcm_extplug_read_areas(snd_pcm_t *pcm,
288                const snd_pcm_channel_area_t *areas,
289                snd_pcm_uframes_t offset,
290                snd_pcm_uframes_t size,
291                const snd_pcm_channel_area_t *slave_areas,
292                snd_pcm_uframes_t slave_offset,
293                snd_pcm_uframes_t *slave_sizep)
294 {
295     extplug_priv_t *ext = pcm->private_data;
296 
297     if (size > *slave_sizep)
298         size = *slave_sizep;
299     size = ext->data->callback->transfer(ext->data, areas, offset,
300                          slave_areas, slave_offset, size);
301     *slave_sizep = size;
302     return size;
303 }
304 
snd_pcm_extplug_close(snd_pcm_t * pcm)305 static int snd_pcm_extplug_close(snd_pcm_t *pcm)
306 {
307     extplug_priv_t *ext = pcm->private_data;
308 
309     awalsa_debug("\n");
310     snd_pcm_close(ext->plug.gen.slave);
311     clear_ext_params(ext);
312     if (ext->data->callback->close)
313         ext->data->callback->close(ext->data);
314     free(ext);
315     return 0;
316 }
317 
318 /*
319  * hw_refine callback
320  */
extplug_hw_refine(snd_pcm_hw_params_t * hw_params,struct snd_ext_parm * parm)321 static int extplug_hw_refine(snd_pcm_hw_params_t *hw_params,
322                  struct snd_ext_parm *parm)
323 {
324     int i, err, change = 0;
325     for (i = 0; i < SND_PCM_EXTPLUG_HW_PARAMS; i++) {
326         int type = hw_params_type[i];
327         if (is_mask_type(i))
328             err = snd_ext_parm_mask_refine(hw_param_interval(hw_params, type),
329                                parm, i);
330         else
331             err = snd_ext_parm_range_refine(hw_param_interval(hw_params, type),
332                             parm, i);
333         if (err < 0)
334             return err;
335         change |= err;
336     }
337     return change;
338 }
339 
snd_pcm_extplug_hw_refine_cprepare(snd_pcm_t * pcm,snd_pcm_hw_params_t * params)340 static int snd_pcm_extplug_hw_refine_cprepare(snd_pcm_t *pcm,
341                           snd_pcm_hw_params_t *params)
342 {
343     extplug_priv_t *ext = pcm->private_data;
344     int err;
345     snd_interval_t access_mask = { .mask = SND_PCM_ACCBIT_SHM };
346     awalsa_debug("\n");
347     err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
348                      &access_mask);
349     if (err < 0)
350         return err;
351     err = extplug_hw_refine(params, ext->params);
352     if (err < 0)
353         return err;
354 #if 0
355     params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
356 #endif
357     return 0;
358 }
359 
snd_pcm_extplug_hw_refine_sprepare(snd_pcm_t * pcm,snd_pcm_hw_params_t * sparams)360 static int snd_pcm_extplug_hw_refine_sprepare(snd_pcm_t *pcm,
361                           snd_pcm_hw_params_t *sparams)
362 {
363     extplug_priv_t *ext = pcm->private_data;
364     snd_interval_t saccess_mask = { .mask = SND_PCM_ACCBIT_MMAP };
365     awalsa_debug("\n");
366     _snd_pcm_hw_params_any(sparams);
367     _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
368                    &saccess_mask);
369     extplug_hw_refine(sparams, ext->sparams);
370     return 0;
371 }
372 
get_links(struct snd_ext_parm * params)373 static unsigned int get_links(struct snd_ext_parm *params)
374 {
375     int i;
376     unsigned int links = (SND_PCM_HW_PARBIT_FORMAT |
377                   SND_PCM_HW_PARBIT_SAMPLE_BITS |
378                   SND_PCM_HW_PARBIT_CHANNELS |
379                   SND_PCM_HW_PARBIT_FRAME_BITS |
380                   SND_PCM_HW_PARBIT_RATE |
381                   SND_PCM_HW_PARBIT_PERIODS |
382                   SND_PCM_HW_PARBIT_PERIOD_SIZE |
383                   SND_PCM_HW_PARBIT_PERIOD_TIME |
384                   SND_PCM_HW_PARBIT_BUFFER_SIZE |
385                   SND_PCM_HW_PARBIT_BUFFER_TIME);
386 
387     for (i = 0; i < SND_PCM_EXTPLUG_HW_PARAMS; i++) {
388         if (params[i].active)
389             links &= ~excl_parbits[i];
390     }
391     return links;
392 }
393 
snd_pcm_extplug_hw_refine_schange(snd_pcm_t * pcm,snd_pcm_hw_params_t * params,snd_pcm_hw_params_t * sparams)394 static int snd_pcm_extplug_hw_refine_schange(snd_pcm_t *pcm,
395                          snd_pcm_hw_params_t *params,
396                          snd_pcm_hw_params_t *sparams)
397 {
398     extplug_priv_t *ext = pcm->private_data;
399     unsigned int links = get_links(ext->sparams);
400 
401     awalsa_debug("\n");
402     return _snd_pcm_hw_params_refine(sparams, links, params);
403 }
404 
snd_pcm_extplug_hw_refine_cchange(snd_pcm_t * pcm,snd_pcm_hw_params_t * params,snd_pcm_hw_params_t * sparams)405 static int snd_pcm_extplug_hw_refine_cchange(snd_pcm_t *pcm,
406                          snd_pcm_hw_params_t *params,
407                          snd_pcm_hw_params_t *sparams)
408 {
409     extplug_priv_t *ext = pcm->private_data;
410     unsigned int links = get_links(ext->params);
411 
412     awalsa_debug("\n");
413     return _snd_pcm_hw_params_refine(params, links, sparams);
414 }
415 
snd_pcm_extplug_hw_refine(snd_pcm_t * pcm,snd_pcm_hw_params_t * params)416 static int snd_pcm_extplug_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
417 {
418 #if 0
419     extplug_priv_t *ext = pcm->private_data;
420     snd_pcm_t *slave = ext->plug.gen.slave;
421     int ret;
422 
423     awalsa_debug("\n");
424 
425     // TODO: how to refine?
426     ret = snd_pcm_hw_refine(slave, params);
427     if (ret < 0)
428         return ret;
429 
430     return 0;
431 #endif
432 
433     awalsa_debug("\n");
434     int err = snd_pcm_hw_refine_slave(pcm, params,
435                        snd_pcm_extplug_hw_refine_cprepare,
436                        snd_pcm_extplug_hw_refine_cchange,
437                        snd_pcm_extplug_hw_refine_sprepare,
438                        snd_pcm_extplug_hw_refine_schange,
439                        snd_pcm_generic_hw_refine);
440     return err;
441 }
442 
snd_pcm_extplug_hw_params(snd_pcm_t * pcm,snd_pcm_hw_params_t * params)443 static int snd_pcm_extplug_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
444 {
445     extplug_priv_t *ext = pcm->private_data;
446     snd_pcm_t *slave = ext->plug.gen.slave;
447     int err;
448 
449     awalsa_debug("\n");
450 
451     err = snd_pcm_hw_params_slave(pcm, params,
452                       snd_pcm_extplug_hw_refine_cchange,
453                       snd_pcm_extplug_hw_refine_sprepare,
454                       snd_pcm_extplug_hw_refine_schange,
455                       snd_pcm_generic_hw_params);
456     if (err < 0)
457         return err;
458 
459     ext->data->slave_format = slave->format;
460     ext->data->slave_channels = slave->channels;
461     ext->data->rate = slave->rate;
462     snd_pcm_hw_params_get_format(params, &ext->data->format);
463     snd_pcm_hw_params_get_channels(params, &ext->data->channels);
464 
465     if (ext->data->callback->hw_params) {
466         err = ext->data->callback->hw_params(ext->data, params);
467         if (err < 0)
468             return err;
469     }
470     return 0;
471 }
472 
snd_pcm_extplug_hw_free(snd_pcm_t * pcm)473 static int snd_pcm_extplug_hw_free(snd_pcm_t *pcm)
474 {
475     extplug_priv_t *ext = pcm->private_data;
476 
477     awalsa_debug("\n");
478     snd_pcm_hw_free(ext->plug.gen.slave);
479     if (ext->data->callback->hw_free)
480         return ext->data->callback->hw_free(ext->data);
481     return 0;
482 }
483 
snd_pcm_extplug_dump(snd_pcm_t * pcm)484 static void snd_pcm_extplug_dump(snd_pcm_t *pcm)
485 {
486     extplug_priv_t *ext = pcm->private_data;
487     awalsa_debug("\n");
488 
489     if (ext->data->callback->dump)
490         ext->data->callback->dump(ext->data);
491     else {
492         if (ext->data->name)
493             printf("%s\n", ext->data->name);
494         else
495             printf("External PCM Plugin\n");
496         if (pcm->setup) {
497             printf("Its setup is:\n");
498             snd_pcm_dump_setup(pcm);
499         }
500     }
501     printf("Slave: ");
502     snd_pcm_dump(ext->plug.gen.slave);
503 }
504 
505 static const snd_pcm_ops_t snd_pcm_extplug_ops = {
506     .close = snd_pcm_extplug_close,
507     .hw_refine = snd_pcm_extplug_hw_refine,
508     .hw_params = snd_pcm_extplug_hw_params,
509     .hw_free = snd_pcm_extplug_hw_free,
510     .sw_params = snd_pcm_generic_sw_params,
511     .channel_info = snd_pcm_generic_channel_info,
512     .dump = snd_pcm_extplug_dump,
513     .mmap = snd_pcm_generic_mmap,
514     .munmap = snd_pcm_generic_munmap,
515     //.query_chmaps = snd_pcm_extplug_query_chmaps,
516     //.get_chmap = snd_pcm_extplug_get_chmap,
517     //.set_chmap = snd_pcm_extplug_set_chmap,
518 };
519 
snd_pcm_extplug_create(snd_pcm_extplug_t * extplug,const char * name,const char * spcm_name,snd_pcm_stream_t stream,int mode)520 int snd_pcm_extplug_create(snd_pcm_extplug_t *extplug, const char *name,
521                const char *spcm_name, snd_pcm_stream_t stream, int mode)
522 {
523     int ret = 0;
524     extplug_priv_t *ext;
525     snd_pcm_t *spcm, *pcm;
526     const snd_pcm_config_t *sconf = NULL;
527 
528     assert(extplug && extplug->callback);
529     assert(extplug->callback->transfer);
530 
531     sconf = snd_pcm_config_get_config(spcm_name);
532     if (!sconf) {
533         awalsa_err("can't find pcm slave: %s\n", spcm_name);
534         ret = -EINVAL;
535         goto err_out;
536     }
537     ret = snd_pcm_open_config(&spcm, sconf, stream, mode);
538     if (ret < 0) {
539         awalsa_err("unable to open slave\n");
540         goto err_out;
541     }
542 
543     ext = calloc(1, sizeof(*ext));
544     if (!ext) {
545         awalsa_err("no memory\n");
546         ret = -ENOMEM;
547         goto err_close_spcm;
548     }
549 
550     ext->data = extplug;
551     extplug->stream = stream;
552 
553     snd_pcm_plugin_init(&ext->plug);
554     ext->plug.read = snd_pcm_extplug_read_areas;
555     ext->plug.write = snd_pcm_extplug_write_areas;
556     ext->plug.undo_read = snd_pcm_plugin_undo_read_generic;
557     ext->plug.undo_write = snd_pcm_plugin_undo_write_generic;
558     ext->plug.gen.slave = spcm;
559     ext->plug.gen.close_slave = 1;
560     if (extplug->callback->init)
561         ext->plug.init = snd_pcm_extplug_init;
562 
563     ret = snd_pcm_new(&pcm, SND_PCM_TYPE_EXTPLUG, name, stream, mode);
564     if (ret < 0) {
565         goto err_free_ext;
566     }
567 
568     extplug->pcm = pcm;
569     pcm->ops = &snd_pcm_extplug_ops;
570     pcm->fast_ops = &snd_pcm_plugin_fast_ops;
571     pcm->private_data = ext;
572     snd_pcm_set_hw_ptr(pcm, &ext->plug.hw_ptr, -1, 0);
573     snd_pcm_set_appl_ptr(pcm, &ext->plug.appl_ptr, -1, 0);
574 
575     return 0;
576 
577 err_free_ext:
578     free(ext);
579 err_close_spcm:
580     snd_pcm_close(spcm);
581 err_out:
582     return ret;
583 }
584