blob: a07b8f6949c5eb4756f4c77659634e1f93e6d8a5 [file] [log] [blame]
Bhalchandra Gajaree7c627d2019-06-19 15:30:42 -07001/* mixer_plugin.c
2** Copyright (c) 2019, The Linux Foundation.
3**
4** Redistribution and use in source and binary forms, with or without
5** modification, are permitted provided that the following conditions are
6** met:
7** * Redistributions of source code must retain the above copyright
8** notice, this list of conditions and the following disclaimer.
9** * Redistributions in binary form must reproduce the above
10** copyright notice, this list of conditions and the following
11** disclaimer in the documentation and/or other materials provided
12** with the distribution.
13** * Neither the name of The Linux Foundation nor the names of its
14** contributors may be used to endorse or promote products derived
15** from this software without specific prior written permission.
16**
17** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
18** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
20** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
21** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
24** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
26** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28**/
29
30#include <tinyalsa/plugin.h>
31
32#include <stdio.h>
33#include <stdlib.h>
34#include <stdint.h>
35#include <stdarg.h>
36#include <stdbool.h>
37#include <string.h>
38#include <unistd.h>
39#include <fcntl.h>
40#include <errno.h>
41#include <ctype.h>
42#include <poll.h>
43#include <dlfcn.h>
44#include <string.h>
45#include <sys/eventfd.h>
46#include <sys/ioctl.h>
47
48#include <linux/ioctl.h>
49#include <sound/asound.h>
50
51#include "snd_card_plugin.h"
52#include "mixer_io.h"
53
54/** Encapulates the mixer plugin specific data */
55struct mixer_plug_data {
56 /** Card number associated with the plugin */
57 int card;
58 /** Device node for mixer */
59 void *mixer_node;
60 /** Pointer to the plugin's ops */
61 const struct mixer_plugin_ops *ops;
62 /** Pointer to plugin responsible to service the controls */
63 struct mixer_plugin *plugin;
64 /** Handle to the plugin library */
65 void *dl_hdl;
66};
67
68static int mixer_plug_get_elem_id(struct mixer_plug_data *plug_data,
69 struct snd_ctl_elem_id *id, unsigned int offset)
70{
71 struct mixer_plugin *plugin = plug_data->plugin;
72 struct snd_control *ctl;
73
74 if (offset >= plugin->num_controls) {
75 fprintf(stderr, "%s: invalid offset %u\n",
76 __func__, offset);
77 return -EINVAL;
78 }
79
80 ctl = plugin->controls + offset;
81 id->numid = offset;
82 id->iface = ctl->iface;
83
84 strncpy((char *)id->name, (char *)ctl->name,
togeb3e379e2021-11-17 00:58:17 +090085 sizeof(id->name) - 1);
togeafec7cf2021-11-23 22:53:50 +090086 ((char *)id->name)[sizeof(id->name) - 1] = '\0';
Bhalchandra Gajaree7c627d2019-06-19 15:30:42 -070087
88 return 0;
89}
90
91static int mixer_plug_info_enum(struct snd_control *ctl,
92 struct snd_ctl_elem_info *einfo)
93{
94 struct snd_value_enum *val = ctl->value;
95
96 einfo->count = 1;
97 einfo->value.enumerated.items = val->items;
98
Taylor Holbertonfd59e162020-08-12 23:06:08 -050099 if (einfo->value.enumerated.item >= val->items)
Bhalchandra Gajaree7c627d2019-06-19 15:30:42 -0700100 return -EINVAL;
101
102 strncpy(einfo->value.enumerated.name,
103 val->texts[einfo->value.enumerated.item],
togeb3e379e2021-11-17 00:58:17 +0900104 sizeof(einfo->value.enumerated.name) - 1);
togeafec7cf2021-11-23 22:53:50 +0900105 einfo->value.enumerated.name[sizeof(einfo->value.enumerated.name) - 1] = '\0';
Bhalchandra Gajaree7c627d2019-06-19 15:30:42 -0700106
107 return 0;
108}
109
110static int mixer_plug_info_bytes(struct snd_control *ctl,
111 struct snd_ctl_elem_info *einfo)
112{
113 struct snd_value_bytes *val;
114 struct snd_value_tlv_bytes *val_tlv;
115
116 if (ctl->access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE) {
117 val_tlv = ctl->value;
118 einfo->count = val_tlv->size;
119 } else {
120 val = ctl->value;
121 einfo->count = val->size;
122 }
123
124 return 0;
125}
126
127static int mixer_plug_info_integer(struct snd_control *ctl,
128 struct snd_ctl_elem_info *einfo)
129{
130 struct snd_value_int *val = ctl->value;
131
132 einfo->count = val->count;
133 einfo->value.integer.min = val->min;
134 einfo->value.integer.max = val->max;
135 einfo->value.integer.step = val->step;
136
137 return 0;
138}
139
140void mixer_plug_notifier_cb(struct mixer_plugin *plugin)
141{
Bhalchandra Gajaree7c627d2019-06-19 15:30:42 -0700142 plugin->event_cnt++;
Rohit kumardf855e82020-06-02 11:41:13 +0530143 eventfd_write(plugin->eventfd, 1);
Bhalchandra Gajaree7c627d2019-06-19 15:30:42 -0700144}
145
146/* In consume_event/read, do not call eventfd_read until all events are read from list.
147 This will make poll getting unblocked until all events are read */
148static ssize_t mixer_plug_read_event(void *data, struct snd_ctl_event *ev, size_t size)
149{
150 struct mixer_plug_data *plug_data = data;
151 struct mixer_plugin *plugin = plug_data->plugin;
152 eventfd_t evfd;
153 ssize_t result = 0;
154
155 result = plug_data->ops->read_event(plugin, ev, size);
156
157 if (result > 0) {
158 plugin->event_cnt -= result / sizeof(struct snd_ctl_event);
159 if (plugin->event_cnt == 0)
160 eventfd_read(plugin->eventfd, &evfd);
161 }
162
163 return result;
164}
165
166static int mixer_plug_subscribe_events(struct mixer_plug_data *plug_data,
167 int *subscribe)
168{
169 struct mixer_plugin *plugin = plug_data->plugin;
170 eventfd_t evfd;
171
172 if (*subscribe < 0 || *subscribe > 1) {
173 *subscribe = plugin->subscribed;
174 return -EINVAL;
175 }
176
177 if (*subscribe && !plugin->subscribed) {
178 plug_data->ops->subscribe_events(plugin, &mixer_plug_notifier_cb);
179 } else if (plugin->subscribed && !*subscribe) {
180 plug_data->ops->subscribe_events(plugin, NULL);
181
182 if (plugin->event_cnt)
183 eventfd_read(plugin->eventfd, &evfd);
184
185 plugin->event_cnt = 0;
186 }
187
188 plugin->subscribed = *subscribe;
189 return 0;
190}
191
192static int mixer_plug_get_poll_fd(void *data, struct pollfd *pfd, int count)
193{
194 struct mixer_plug_data *plug_data = data;
195 struct mixer_plugin *plugin = plug_data->plugin;
196
197 if (plugin->eventfd != -1) {
198 pfd[count].fd = plugin->eventfd;
199 return 0;
200 }
201 return -ENODEV;
202}
203
204static int mixer_plug_tlv_write(struct mixer_plug_data *plug_data,
205 struct snd_ctl_tlv *tlv)
206{
207 struct mixer_plugin *plugin = plug_data->plugin;
208 struct snd_control *ctl;
209 struct snd_value_tlv_bytes *val_tlv;
210
211 ctl = plugin->controls + tlv->numid;
212 val_tlv = ctl->value;
213
214 return val_tlv->put(plugin, ctl, tlv);
215}
216
217static int mixer_plug_tlv_read(struct mixer_plug_data *plug_data,
218 struct snd_ctl_tlv *tlv)
219{
220 struct mixer_plugin *plugin = plug_data->plugin;
221 struct snd_control *ctl;
222 struct snd_value_tlv_bytes *val_tlv;
223
224 ctl = plugin->controls + tlv->numid;
225 val_tlv = ctl->value;
226
227 return val_tlv->get(plugin, ctl, tlv);
228}
229
230static int mixer_plug_elem_write(struct mixer_plug_data *plug_data,
231 struct snd_ctl_elem_value *ev)
232{
233 struct mixer_plugin *plugin = plug_data->plugin;
234 struct snd_control *ctl;
235 int ret;
236
237 ret = mixer_plug_get_elem_id(plug_data, &ev->id, ev->id.numid);
238 if (ret < 0)
239 return ret;
240
241 ctl = plugin->controls + ev->id.numid;
242
243 return ctl->put(plugin, ctl, ev);
244}
245
246static int mixer_plug_elem_read(struct mixer_plug_data *plug_data,
247 struct snd_ctl_elem_value *ev)
248{
249 struct mixer_plugin *plugin = plug_data->plugin;
250 struct snd_control *ctl;
251 int ret;
252
253 ret = mixer_plug_get_elem_id(plug_data, &ev->id, ev->id.numid);
254 if (ret < 0)
255 return ret;
256
257 ctl = plugin->controls + ev->id.numid;
258
259 return ctl->get(plugin, ctl, ev);
260
261}
262
263static int mixer_plug_get_elem_info(struct mixer_plug_data *plug_data,
264 struct snd_ctl_elem_info *einfo)
265{
266 struct mixer_plugin *plugin = plug_data->plugin;
267 struct snd_control *ctl;
268 int ret;
269
270 ret = mixer_plug_get_elem_id(plug_data, &einfo->id,
271 einfo->id.numid);
272 if (ret < 0)
273 return ret;
274
275 ctl = plugin->controls + einfo->id.numid;
276 einfo->type = ctl->type;
277 einfo->access = ctl->access;
278
279 switch (einfo->type) {
280 case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
281 ret = mixer_plug_info_enum(ctl, einfo);
282 if (ret < 0)
283 return ret;
284 break;
285 case SNDRV_CTL_ELEM_TYPE_BYTES:
286 ret = mixer_plug_info_bytes(ctl, einfo);
287 if (ret < 0)
288 return ret;
289 break;
290 case SNDRV_CTL_ELEM_TYPE_INTEGER:
291 ret = mixer_plug_info_integer(ctl, einfo);
292 if (ret < 0)
293 return ret;
294 break;
295 default:
296 fprintf(stderr,"%s: unknown type %d\n",
297 __func__, einfo->type);
298 return -EINVAL;
299 }
300
301 return 0;
302}
303
304static int mixer_plug_get_elem_list(struct mixer_plug_data *plug_data,
305 struct snd_ctl_elem_list *elist)
306{
307 struct mixer_plugin *plugin = plug_data->plugin;
308 unsigned int avail;
309 struct snd_ctl_elem_id *id;
310 int ret;
311
312 elist->count = plugin->num_controls;
313 elist->used = 0;
314 avail = elist->space;
315
316 while (avail > 0) {
317 id = elist->pids + elist->used;
318 ret = mixer_plug_get_elem_id(plug_data, id, elist->used);
319 if (ret < 0)
320 return ret;
321
322 avail--;
323 elist->used++;
324 }
325
326 return 0;
327}
328
329static int mixer_plug_get_card_info(struct mixer_plug_data *plug_data,
330 struct snd_ctl_card_info *card_info)
331{
332 /*TODO: Fill card_info here from snd-card-def */
333 memset(card_info, 0, sizeof(*card_info));
334 card_info->card = plug_data->card;
Bhalchandra Gajaree7c627d2019-06-19 15:30:42 -0700335
336 return 0;
337}
338
339static void mixer_plug_close(void *data)
340{
341 struct mixer_plug_data *plug_data = data;
342 struct mixer_plugin *plugin = plug_data->plugin;
343 eventfd_t evfd;
344
345 if (plugin->event_cnt)
346 eventfd_read(plugin->eventfd, &evfd);
347
348 plug_data->ops->close(&plugin);
349 dlclose(plug_data->dl_hdl);
350
351 free(plug_data);
352 plug_data = NULL;
353}
354
355static int mixer_plug_ioctl(void *data, unsigned int cmd, ...)
356{
357 struct mixer_plug_data *plug_data = data;
358 int ret;
359 va_list ap;
360 void *arg;
361
362 va_start(ap, cmd);
363 arg = va_arg(ap, void *);
364 va_end(ap);
365
366 switch (cmd) {
367 case SNDRV_CTL_IOCTL_CARD_INFO:
368 ret = mixer_plug_get_card_info(plug_data, arg);
369 break;
370 case SNDRV_CTL_IOCTL_ELEM_LIST:
371 ret = mixer_plug_get_elem_list(plug_data, arg);
372 break;
373 case SNDRV_CTL_IOCTL_ELEM_INFO:
374 ret = mixer_plug_get_elem_info(plug_data, arg);
375 break;
376 case SNDRV_CTL_IOCTL_ELEM_READ:
377 ret = mixer_plug_elem_read(plug_data, arg);
378 break;
379 case SNDRV_CTL_IOCTL_ELEM_WRITE:
380 ret = mixer_plug_elem_write(plug_data, arg);
381 break;
382 case SNDRV_CTL_IOCTL_TLV_READ:
383 ret = mixer_plug_tlv_read(plug_data, arg);
384 break;
385 case SNDRV_CTL_IOCTL_TLV_WRITE:
386 ret = mixer_plug_tlv_write(plug_data, arg);
387 break;
388 case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
389 ret = mixer_plug_subscribe_events(plug_data, arg);
390 break;
391 default:
392 /* TODO: plugin should support ioctl */
393 ret = -EFAULT;
394 break;
395 }
396
397 return ret;
398}
399
400static const struct mixer_ops mixer_plug_ops = {
401 .close = mixer_plug_close,
402 .read_event = mixer_plug_read_event,
403 .get_poll_fd = mixer_plug_get_poll_fd,
404 .ioctl = mixer_plug_ioctl,
405};
406
407int mixer_plugin_open(unsigned int card, void **data,
408 const struct mixer_ops **ops)
409{
410 struct mixer_plug_data *plug_data;
411 struct mixer_plugin *plugin = NULL;
412 void *dl_hdl;
413 char *so_name;
414 int ret;
415
416 plug_data = calloc(1, sizeof(*plug_data));
417 if (!plug_data)
418 return -ENOMEM;
419
420 plug_data->mixer_node = snd_utils_open_mixer(card);
421 if (!plug_data->mixer_node) {
422 /* Do not print error here.
423 * It is valid for card to not have virtual mixer node
424 */
425 goto err_get_mixer_node;
426 }
427
428 ret = snd_utils_get_str(plug_data->mixer_node, "so-name",
429 &so_name);
430 if(ret) {
431 fprintf(stderr, "%s: mixer so-name not found for card %u\n",
432 __func__, card);
433 goto err_get_lib_name;
434
435 }
436
437 dl_hdl = dlopen(so_name, RTLD_NOW);
438 if (!dl_hdl) {
439 fprintf(stderr, "%s: unable to open %s\n",
440 __func__, so_name);
441 goto err_dlopen;
442 }
443
444 dlerror();
445 plug_data->ops = dlsym(dl_hdl, "mixer_plugin_ops");
446 if (!plug_data->ops) {
447 fprintf(stderr, "%s: dlsym open fn failed: %s\n",
448 __func__, dlerror());
449 goto err_ops;
450 }
451
452 ret = plug_data->ops->open(&plugin, card);
453 if (ret) {
454 fprintf(stderr, "%s: failed to open plugin, err: %d\n",
455 __func__, ret);
456 goto err_ops;
457 }
458
459 plug_data->plugin = plugin;
460 plug_data->card = card;
461 plug_data->dl_hdl = dl_hdl;
462 plugin->eventfd = eventfd(0, 0);
463
464 *data = plug_data;
465 *ops = &mixer_plug_ops;
466
467 return 0;
468
469err_ops:
470 dlclose(dl_hdl);
471err_dlopen:
472err_get_lib_name:
473 snd_utils_close_dev_node(plug_data->mixer_node);
474err_get_mixer_node:
475 free(plug_data);
476 return -1;
477}