1 /*        $NetBSD: oss3_mixer.c,v 1.1 2021/06/08 18:43:54 nia Exp $   */
2 
3 /*-
4  * Copyright (c) 1997-2021 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include <sys/audioio.h>
30 #include <sys/stat.h>
31 #include <errno.h>
32 #include "internal.h"
33 
34 /* If the NetBSD mixer device should have more than NETBSD_MAXDEVS devices
35  * some will not be available to OSS applications */
36 #define NETBSD_MAXDEVS 64
37 
38 struct audiodevinfo {
39           int done;
40           dev_t dev;
41           int16_t devmap[SOUND_MIXER_NRDEVICES],
42                   rdevmap[NETBSD_MAXDEVS];
43           char names[NETBSD_MAXDEVS][MAX_AUDIO_DEV_LEN];
44           int enum2opaque[NETBSD_MAXDEVS];
45         u_long devmask, recmask, stereomask;
46           u_long caps;
47           int source;
48 };
49 
50 static struct audiodevinfo *getdevinfo(int);
51 static int opaque_to_enum(struct audiodevinfo *, audio_mixer_name_t *, int);
52 static int enum_to_ord(struct audiodevinfo *, int);
53 static int enum_to_mask(struct audiodevinfo *, int);
54 
55 oss_private int
_oss3_mixer_ioctl(int fd,unsigned long com,void * argp)56 _oss3_mixer_ioctl(int fd, unsigned long com, void *argp)
57 {
58           struct audiodevinfo *di;
59           struct mixer_info *omi;
60           struct audio_device adev;
61           mixer_ctrl_t mc;
62           u_long idat, n;
63           int i;
64           int retval;
65           int l, r, error, e;
66 
67           idat = 0;
68           di = getdevinfo(fd);
69           if (di == 0)
70                     return -1;
71 
72           switch (com) {
73         case OSS_GETVERSION:
74                 idat = SOUND_VERSION;
75                 break;
76           case SOUND_MIXER_INFO:
77           case SOUND_OLD_MIXER_INFO:
78                     error = ioctl(fd, AUDIO_GETDEV, &adev);
79                     if (error)
80                               return (error);
81                     omi = argp;
82                     if (com == SOUND_MIXER_INFO)
83                               omi->modify_counter = 1;
84                     strlcpy(omi->id, adev.name, sizeof omi->id);
85                     strlcpy(omi->name, adev.name, sizeof omi->name);
86                     return 0;
87           case SOUND_MIXER_READ_RECSRC:
88                     if (di->source == -1) {
89                               errno = EINVAL;
90                               return -1;
91                     }
92                     mc.dev = di->source;
93                     if (di->caps & SOUND_CAP_EXCL_INPUT) {
94                               mc.type = AUDIO_MIXER_ENUM;
95                               retval = ioctl(fd, AUDIO_MIXER_READ, &mc);
96                               if (retval < 0)
97                                         return retval;
98                               e = opaque_to_enum(di, NULL, mc.un.ord);
99                               if (e >= 0)
100                                         idat = 1 << di->rdevmap[e];
101                     } else {
102                               mc.type = AUDIO_MIXER_SET;
103                               retval = ioctl(fd, AUDIO_MIXER_READ, &mc);
104                               if (retval < 0)
105                                         return retval;
106                               e = opaque_to_enum(di, NULL, mc.un.mask);
107                               if (e >= 0)
108                                         idat = 1 << di->rdevmap[e];
109                     }
110                     break;
111           case SOUND_MIXER_READ_DEVMASK:
112                     idat = di->devmask;
113                     break;
114           case SOUND_MIXER_READ_RECMASK:
115                     idat = di->recmask;
116                     break;
117           case SOUND_MIXER_READ_STEREODEVS:
118                     idat = di->stereomask;
119                     break;
120           case SOUND_MIXER_READ_CAPS:
121                     idat = di->caps;
122                     break;
123           case SOUND_MIXER_WRITE_RECSRC:
124           case SOUND_MIXER_WRITE_R_RECSRC:
125                     if (di->source == -1) {
126                               errno = EINVAL;
127                               return -1;
128                     }
129                     mc.dev = di->source;
130                     idat = INTARG;
131                     if (di->caps & SOUND_CAP_EXCL_INPUT) {
132                               mc.type = AUDIO_MIXER_ENUM;
133                               for(i = 0; i < SOUND_MIXER_NRDEVICES; i++)
134                                         if (idat & (1 << i))
135                                                   break;
136                               if (i >= SOUND_MIXER_NRDEVICES ||
137                                   di->devmap[i] == -1) {
138                                         errno = EINVAL;
139                                         return -1;
140                               }
141                               mc.un.ord = enum_to_ord(di, di->devmap[i]);
142                     } else {
143                               mc.type = AUDIO_MIXER_SET;
144                               mc.un.mask = 0;
145                               for(i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
146                                         if (idat & (1 << i)) {
147                                                   if (di->devmap[i] == -1) {
148                                                             errno = EINVAL;
149                                                             return -1;
150                                                   }
151                                                   mc.un.mask |=
152                                                       enum_to_mask(di, di->devmap[i]);
153                                         }
154                               }
155                     }
156                     return ioctl(fd, AUDIO_MIXER_WRITE, &mc);
157           default:
158                     if (MIXER_READ(SOUND_MIXER_FIRST) <= com &&
159                         com < MIXER_READ(SOUND_MIXER_NRDEVICES)) {
160                               n = GET_DEV(com);
161                               if (di->devmap[n] == -1) {
162                                         errno = EINVAL;
163                                         return -1;
164                               }
165                               mc.dev = di->devmap[n];
166                               mc.type = AUDIO_MIXER_VALUE;
167                         doread:
168                               mc.un.value.num_channels =
169                                   di->stereomask & (1 << (u_int)n) ? 2 : 1;
170                               retval = ioctl(fd, AUDIO_MIXER_READ, &mc);
171                               if (retval < 0)
172                                         return retval;
173                               if (mc.type != AUDIO_MIXER_VALUE) {
174                                         errno = EINVAL;
175                                         return -1;
176                               }
177                               if (mc.un.value.num_channels != 2) {
178                                         l = r =
179                                             mc.un.value.level[AUDIO_MIXER_LEVEL_MONO];
180                               } else {
181                                         l = mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT];
182                                         r = mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT];
183                               }
184                               idat = TO_OSSVOL(l) | (TO_OSSVOL(r) << 8);
185                               break;
186                     } else if ((MIXER_WRITE_R(SOUND_MIXER_FIRST) <= com &&
187                                  com < MIXER_WRITE_R(SOUND_MIXER_NRDEVICES)) ||
188                                  (MIXER_WRITE(SOUND_MIXER_FIRST) <= com &&
189                                  com < MIXER_WRITE(SOUND_MIXER_NRDEVICES))) {
190                               n = GET_DEV(com);
191                               if (di->devmap[n] == -1) {
192                                         errno = EINVAL;
193                                         return -1;
194                               }
195                               idat = INTARG;
196                               l = FROM_OSSVOL((u_int)idat & 0xff);
197                               r = FROM_OSSVOL(((u_int)idat >> 8) & 0xff);
198                               mc.dev = di->devmap[n];
199                               mc.type = AUDIO_MIXER_VALUE;
200                               if (di->stereomask & (1 << (u_int)n)) {
201                                         mc.un.value.num_channels = 2;
202                                         mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT] = l;
203                                         mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = r;
204                               } else {
205                                         mc.un.value.num_channels = 1;
206                                         mc.un.value.level[AUDIO_MIXER_LEVEL_MONO] =
207                                             (l + r) / 2;
208                               }
209                               retval = ioctl(fd, AUDIO_MIXER_WRITE, &mc);
210                               if (retval < 0)
211                                         return retval;
212                               if (MIXER_WRITE(SOUND_MIXER_FIRST) <= com &&
213                                  com < MIXER_WRITE(SOUND_MIXER_NRDEVICES))
214                                         return 0;
215                               goto doread;
216                     } else {
217                               errno = EINVAL;
218                               return -1;
219                     }
220           }
221           INTARG = (int)idat;
222           return 0;
223 }
224 
225 /*
226  * Collect the audio device information to allow faster
227  * emulation of the OSSv3 mixer ioctls.  Cache the information
228  * to eliminate the overhead of repeating all the ioctls needed
229  * to collect the information.
230  */
231 static struct audiodevinfo *
getdevinfo(int fd)232 getdevinfo(int fd)
233 {
234           mixer_devinfo_t mi;
235           int i, j, e;
236           static struct {
237                     const char *name;
238                     int code;
239           } *dp, devs[] = {
240                     { AudioNmicrophone, SOUND_MIXER_MIC },
241                     { AudioNline,                 SOUND_MIXER_LINE },
242                     { AudioNcd,                   SOUND_MIXER_CD },
243                     { AudioNdac,                  SOUND_MIXER_PCM },
244                     { AudioNaux,                  SOUND_MIXER_LINE1 },
245                     { AudioNrecord,               SOUND_MIXER_IMIX },
246                     { AudioNmaster,               SOUND_MIXER_VOLUME },
247                     { AudioNtreble,               SOUND_MIXER_TREBLE },
248                     { AudioNbass,                 SOUND_MIXER_BASS },
249                     { AudioNspeaker,    SOUND_MIXER_SPEAKER },
250 /*                  { AudioNheadphone,  ?? },*/
251                     { AudioNoutput,               SOUND_MIXER_OGAIN },
252                     { AudioNinput,                SOUND_MIXER_IGAIN },
253 /*                  { AudioNmaster,               SOUND_MIXER_SPEAKER },*/
254 /*                  { AudioNstereo,               ?? },*/
255 /*                  { AudioNmono,                 ?? },*/
256                     { AudioNfmsynth,    SOUND_MIXER_SYNTH },
257 /*                  { AudioNwave,                 SOUND_MIXER_PCM },*/
258                     { AudioNmidi,                 SOUND_MIXER_SYNTH },
259 /*                  { AudioNmixerout,   ?? },*/
260                     { 0, -1 }
261           };
262           static struct audiodevinfo devcache = { .done = 0 };
263           struct audiodevinfo *di = &devcache;
264           struct stat sb;
265           size_t mlen, dlen;
266 
267           /* Figure out what device it is so we can check if the
268            * cached data is valid.
269            */
270           if (fstat(fd, &sb) < 0)
271                     return 0;
272           if (di->done && di->dev == sb.st_dev)
273                     return di;
274 
275           di->done = 1;
276           di->dev = sb.st_dev;
277           di->devmask = 0;
278           di->recmask = 0;
279           di->stereomask = 0;
280           di->source = ~0;
281           di->caps = 0;
282           for(i = 0; i < SOUND_MIXER_NRDEVICES; i++)
283                     di->devmap[i] = -1;
284           for(i = 0; i < NETBSD_MAXDEVS; i++) {
285                     di->rdevmap[i] = -1;
286                     di->names[i][0] = '\0';
287                     di->enum2opaque[i] = -1;
288           }
289           for(i = 0; i < NETBSD_MAXDEVS; i++) {
290                     mi.index = i;
291                     if (ioctl(fd, AUDIO_MIXER_DEVINFO, &mi) < 0)
292                               break;
293                     switch(mi.type) {
294                     case AUDIO_MIXER_VALUE:
295                               for(dp = devs; dp->name; dp++) {
296                                         if (strcmp(dp->name, mi.label.name) == 0)
297                                                   break;
298                                         dlen = strlen(dp->name);
299                                         mlen = strlen(mi.label.name);
300                                         if (dlen < mlen
301                                             && mi.label.name[mlen-dlen-1] == '.'
302                                             && strcmp(dp->name,
303                                             mi.label.name + mlen - dlen) == 0)
304                                                   break;
305                               }
306                               if (dp->code >= 0) {
307                                         di->devmap[dp->code] = i;
308                                         di->rdevmap[i] = dp->code;
309                                         di->devmask |= 1 << dp->code;
310                                         if (mi.un.v.num_channels == 2)
311                                                   di->stereomask |= 1 << dp->code;
312                                         strlcpy(di->names[i], mi.label.name,
313                                                   sizeof di->names[i]);
314                               }
315                               break;
316                     }
317           }
318           for(i = 0; i < NETBSD_MAXDEVS; i++) {
319                     mi.index = i;
320                     if (ioctl(fd, AUDIO_MIXER_DEVINFO, &mi) < 0)
321                               break;
322                     if (strcmp(mi.label.name, AudioNsource) != 0)
323                               continue;
324                     di->source = i;
325                     switch(mi.type) {
326                     case AUDIO_MIXER_ENUM:
327                               for(j = 0; j < mi.un.e.num_mem; j++) {
328                                         e = opaque_to_enum(di,
329                                                                &mi.un.e.member[j].label,
330                                                                mi.un.e.member[j].ord);
331                                         if (e >= 0)
332                                                   di->recmask |= 1 << di->rdevmap[e];
333                               }
334                               di->caps = SOUND_CAP_EXCL_INPUT;
335                               break;
336                     case AUDIO_MIXER_SET:
337                               for(j = 0; j < mi.un.s.num_mem; j++) {
338                                         e = opaque_to_enum(di,
339                                                                &mi.un.s.member[j].label,
340                                                                mi.un.s.member[j].mask);
341                                         if (e >= 0)
342                                                   di->recmask |= 1 << di->rdevmap[e];
343                               }
344                               break;
345                     }
346           }
347           return di;
348 }
349 
350 static int
opaque_to_enum(struct audiodevinfo * di,audio_mixer_name_t * label,int opq)351 opaque_to_enum(struct audiodevinfo *di, audio_mixer_name_t *label, int opq)
352 {
353           int i, o;
354 
355           for (i = 0; i < NETBSD_MAXDEVS; i++) {
356                     o = di->enum2opaque[i];
357                     if (o == opq)
358                               break;
359                     if (o == -1 && label != NULL &&
360                         !strncmp(di->names[i], label->name, sizeof di->names[i])) {
361                               di->enum2opaque[i] = opq;
362                               break;
363                     }
364           }
365           if (i >= NETBSD_MAXDEVS)
366                     i = -1;
367           /*printf("opq_to_enum %s %d -> %d\n", label->name, opq, i);*/
368           return (i);
369 }
370 
371 static int
enum_to_ord(struct audiodevinfo * di,int enm)372 enum_to_ord(struct audiodevinfo *di, int enm)
373 {
374           if (enm >= NETBSD_MAXDEVS)
375                     return (-1);
376 
377           /*printf("enum_to_ord %d -> %d\n", enm, di->enum2opaque[enm]);*/
378           return (di->enum2opaque[enm]);
379 }
380 
381 static int
enum_to_mask(struct audiodevinfo * di,int enm)382 enum_to_mask(struct audiodevinfo *di, int enm)
383 {
384           int m;
385           if (enm >= NETBSD_MAXDEVS)
386                     return (0);
387 
388           m = di->enum2opaque[enm];
389           if (m == -1)
390                     m = 0;
391           /*printf("enum_to_mask %d -> %d\n", enm, di->enum2opaque[enm]);*/
392           return (m);
393 }
394