]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/sound/pcm/sndstat.c
Import libedit 2019-09-10
[FreeBSD/FreeBSD.git] / sys / dev / sound / pcm / sndstat.c
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2005-2009 Ariff Abdullah <ariff@FreeBSD.org>
5  * Copyright (c) 2001 Cameron Grant <cg@FreeBSD.org>
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29
30 #ifdef HAVE_KERNEL_OPTION_HEADERS
31 #include "opt_snd.h"
32 #endif
33
34 #include <dev/sound/pcm/sound.h>
35 #include <dev/sound/pcm/pcm.h>
36 #include <dev/sound/version.h>
37 #include <sys/sx.h>
38
39 SND_DECLARE_FILE("$FreeBSD$");
40
41 #define SS_TYPE_MODULE          0
42 #define SS_TYPE_PCM             1
43 #define SS_TYPE_MIDI            2
44 #define SS_TYPE_SEQUENCER       3
45
46 static d_open_t sndstat_open;
47 static void sndstat_close(void *);
48 static d_read_t sndstat_read;
49 static d_write_t sndstat_write;
50
51 static struct cdevsw sndstat_cdevsw = {
52         .d_version =    D_VERSION,
53         .d_open =       sndstat_open,
54         .d_read =       sndstat_read,
55         .d_write =      sndstat_write,
56         .d_name =       "sndstat",
57         .d_flags =      D_TRACKCLOSE,
58 };
59
60 struct sndstat_entry {
61         TAILQ_ENTRY(sndstat_entry) link;
62         device_t dev;
63         char *str;
64         sndstat_handler handler;
65         int type, unit;
66 };
67
68 struct sndstat_file {
69         TAILQ_ENTRY(sndstat_file) entry;
70         struct sbuf sbuf;
71         int out_offset;
72         int in_offset;
73 };
74
75 static struct sx sndstat_lock;
76 static struct cdev *sndstat_dev;
77
78 #define SNDSTAT_LOCK() sx_xlock(&sndstat_lock)
79 #define SNDSTAT_UNLOCK() sx_xunlock(&sndstat_lock)
80
81 static TAILQ_HEAD(, sndstat_entry) sndstat_devlist = TAILQ_HEAD_INITIALIZER(sndstat_devlist);
82 static TAILQ_HEAD(, sndstat_file) sndstat_filelist = TAILQ_HEAD_INITIALIZER(sndstat_filelist);
83
84 int snd_verbose = 0;
85
86 static int sndstat_prepare(struct sndstat_file *);
87
88 static int
89 sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS)
90 {
91         int error, verbose;
92
93         verbose = snd_verbose;
94         error = sysctl_handle_int(oidp, &verbose, 0, req);
95         if (error == 0 && req->newptr != NULL) {
96                 if (verbose < 0 || verbose > 4)
97                         error = EINVAL;
98                 else
99                         snd_verbose = verbose;
100         }
101         return (error);
102 }
103 SYSCTL_PROC(_hw_snd, OID_AUTO, verbose, CTLTYPE_INT | CTLFLAG_RWTUN,
104             0, sizeof(int), sysctl_hw_sndverbose, "I", "verbosity level");
105
106 static int
107 sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
108 {
109         struct sndstat_file *pf;
110
111         pf = malloc(sizeof(*pf), M_DEVBUF, M_WAITOK | M_ZERO);
112
113         SNDSTAT_LOCK();
114         if (sbuf_new(&pf->sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) {
115                 SNDSTAT_UNLOCK();
116                 free(pf, M_DEVBUF);
117                 return (ENOMEM);
118         }
119         TAILQ_INSERT_TAIL(&sndstat_filelist, pf, entry);
120         SNDSTAT_UNLOCK();
121
122         devfs_set_cdevpriv(pf, &sndstat_close);
123
124         return (0);
125 }
126
127 static void
128 sndstat_close(void *sndstat_file)
129 {
130         struct sndstat_file *pf = (struct sndstat_file *)sndstat_file;
131
132         SNDSTAT_LOCK();
133         sbuf_delete(&pf->sbuf);
134         TAILQ_REMOVE(&sndstat_filelist, pf, entry);
135         SNDSTAT_UNLOCK();
136
137         free(pf, M_DEVBUF);
138 }
139
140 static int
141 sndstat_read(struct cdev *i_dev, struct uio *buf, int flag)
142 {
143         struct sndstat_file *pf;
144         int err;
145         int len;
146
147         err = devfs_get_cdevpriv((void **)&pf);
148         if (err != 0)
149                 return (err);
150
151         /* skip zero-length reads */
152         if (buf->uio_resid == 0)
153                 return (0);
154
155         SNDSTAT_LOCK();
156         if (pf->out_offset != 0) {
157                 /* don't allow both reading and writing */
158                 err = EINVAL;
159                 goto done;
160         } else if (pf->in_offset == 0) {
161                 err = sndstat_prepare(pf);
162                 if (err <= 0) {
163                         err = ENOMEM;
164                         goto done;
165                 }
166         }
167         len = sbuf_len(&pf->sbuf) - pf->in_offset;
168         if (len > buf->uio_resid)
169                 len = buf->uio_resid;
170         if (len > 0)
171                 err = uiomove(sbuf_data(&pf->sbuf) + pf->in_offset, len, buf);
172         pf->in_offset += len;
173 done:
174         SNDSTAT_UNLOCK();
175         return (err);
176 }
177
178 static int
179 sndstat_write(struct cdev *i_dev, struct uio *buf, int flag)
180 {
181         struct sndstat_file *pf;
182         uint8_t temp[64];
183         int err;
184         int len;
185
186         err = devfs_get_cdevpriv((void **)&pf);
187         if (err != 0)
188                 return (err);
189
190         /* skip zero-length writes */
191         if (buf->uio_resid == 0)
192                 return (0);
193
194         /* don't allow writing more than 64Kbytes */
195         if (buf->uio_resid > 65536)
196                 return (ENOMEM);
197
198         SNDSTAT_LOCK();
199         if (pf->in_offset != 0) {
200                 /* don't allow both reading and writing */
201                 err = EINVAL;
202         } else {
203                 /* only remember the last write - allows for updates */
204                 sbuf_clear(&pf->sbuf);
205                 while (1) {
206                         len = sizeof(temp);
207                         if (len > buf->uio_resid)
208                                 len = buf->uio_resid;
209                         if (len > 0) {
210                                 err = uiomove(temp, len, buf);
211                                 if (err)
212                                         break;
213                         } else {
214                                 break;
215                         }
216                         if (sbuf_bcat(&pf->sbuf, temp, len) < 0) {
217                                 err = ENOMEM;
218                                 break;
219                         }
220                 }
221                 sbuf_finish(&pf->sbuf);
222                 if (err == 0)
223                         pf->out_offset = sbuf_len(&pf->sbuf);
224                 else
225                         pf->out_offset = 0;
226         }
227         SNDSTAT_UNLOCK();
228         return (err);
229 }
230
231 /************************************************************************/
232
233 int
234 sndstat_register(device_t dev, char *str, sndstat_handler handler)
235 {
236         struct sndstat_entry *ent;
237         struct sndstat_entry *pre;
238         const char *devtype;
239         int type, unit;
240
241         if (dev) {
242                 unit = device_get_unit(dev);
243                 devtype = device_get_name(dev);
244                 if (!strcmp(devtype, "pcm"))
245                         type = SS_TYPE_PCM;
246                 else if (!strcmp(devtype, "midi"))
247                         type = SS_TYPE_MIDI;
248                 else if (!strcmp(devtype, "sequencer"))
249                         type = SS_TYPE_SEQUENCER;
250                 else
251                         return (EINVAL);
252         } else {
253                 type = SS_TYPE_MODULE;
254                 unit = -1;
255         }
256
257         ent = malloc(sizeof *ent, M_DEVBUF, M_WAITOK | M_ZERO);
258         ent->dev = dev;
259         ent->str = str;
260         ent->type = type;
261         ent->unit = unit;
262         ent->handler = handler;
263
264         SNDSTAT_LOCK();
265         /* sorted list insertion */
266         TAILQ_FOREACH(pre, &sndstat_devlist, link) {
267                 if (pre->unit > unit)
268                         break;
269                 else if (pre->unit < unit)
270                         continue;
271                 if (pre->type > type)
272                         break;
273                 else if (pre->type < unit)
274                         continue;
275         }
276         if (pre == NULL) {
277                 TAILQ_INSERT_TAIL(&sndstat_devlist, ent, link);
278         } else {
279                 TAILQ_INSERT_BEFORE(pre, ent, link);
280         }
281         SNDSTAT_UNLOCK();
282
283         return (0);
284 }
285
286 int
287 sndstat_registerfile(char *str)
288 {
289         return (sndstat_register(NULL, str, NULL));
290 }
291
292 int
293 sndstat_unregister(device_t dev)
294 {
295         struct sndstat_entry *ent;
296         int error = ENXIO;
297
298         SNDSTAT_LOCK();
299         TAILQ_FOREACH(ent, &sndstat_devlist, link) {
300                 if (ent->dev == dev) {
301                         TAILQ_REMOVE(&sndstat_devlist, ent, link);
302                         free(ent, M_DEVBUF);
303                         error = 0;
304                         break;
305                 }
306         }
307         SNDSTAT_UNLOCK();
308
309         return (error);
310 }
311
312 int
313 sndstat_unregisterfile(char *str)
314 {
315         struct sndstat_entry *ent;
316         int error = ENXIO;
317
318         SNDSTAT_LOCK();
319         TAILQ_FOREACH(ent, &sndstat_devlist, link) {
320                 if (ent->dev == NULL && ent->str == str) {
321                         TAILQ_REMOVE(&sndstat_devlist, ent, link);
322                         free(ent, M_DEVBUF);
323                         error = 0;
324                         break;
325                 }
326         }
327         SNDSTAT_UNLOCK();
328
329         return (error);
330 }
331
332 /************************************************************************/
333
334 static int
335 sndstat_prepare(struct sndstat_file *pf_self)
336 {
337         struct sbuf *s = &pf_self->sbuf;
338         struct sndstat_entry *ent;
339         struct snddev_info *d;
340         struct sndstat_file *pf;
341         int k;
342
343         /* make sure buffer is reset */
344         sbuf_clear(s);
345         
346         if (snd_verbose > 0) {
347                 sbuf_printf(s, "FreeBSD Audio Driver (%ubit %d/%s)\n",
348                     (u_int)sizeof(intpcm32_t) << 3, SND_DRV_VERSION,
349                     MACHINE_ARCH);
350         }
351
352         /* generate list of installed devices */
353         k = 0;
354         TAILQ_FOREACH(ent, &sndstat_devlist, link) {
355                 if (ent->dev == NULL)
356                         continue;
357                 d = device_get_softc(ent->dev);
358                 if (!PCM_REGISTERED(d))
359                         continue;
360                 if (!k++)
361                         sbuf_printf(s, "Installed devices:\n");
362                 sbuf_printf(s, "%s:", device_get_nameunit(ent->dev));
363                 sbuf_printf(s, " <%s>", device_get_desc(ent->dev));
364                 if (snd_verbose > 0)
365                         sbuf_printf(s, " %s", ent->str);
366                 if (ent->handler) {
367                         /* XXX Need Giant magic entry ??? */
368                         PCM_ACQUIRE_QUICK(d);
369                         ent->handler(s, ent->dev, snd_verbose);
370                         PCM_RELEASE_QUICK(d);
371                 }
372                 sbuf_printf(s, "\n");
373         }
374         if (k == 0)
375                 sbuf_printf(s, "No devices installed.\n");
376
377         /* append any input from userspace */
378         k = 0;
379         TAILQ_FOREACH(pf, &sndstat_filelist, entry) {
380                 if (pf == pf_self)
381                         continue;
382                 if (pf->out_offset == 0)
383                         continue;
384                 if (!k++)
385                         sbuf_printf(s, "Installed devices from userspace:\n");
386                 sbuf_bcat(s, sbuf_data(&pf->sbuf),
387                     sbuf_len(&pf->sbuf));
388         }
389         if (k == 0)
390                 sbuf_printf(s, "No devices installed from userspace.\n");
391
392         /* append any file versions */
393         if (snd_verbose >= 3) {
394                 k = 0;
395                 TAILQ_FOREACH(ent, &sndstat_devlist, link) {
396                         if (ent->dev == NULL && ent->str != NULL) {
397                                 if (!k++)
398                                         sbuf_printf(s, "\nFile Versions:\n");
399                                 sbuf_printf(s, "%s\n", ent->str);
400                         }
401                 }
402                 if (k == 0)
403                         sbuf_printf(s, "\nNo file versions.\n");
404         }
405         sbuf_finish(s);
406         return (sbuf_len(s));
407 }
408
409 static void
410 sndstat_sysinit(void *p)
411 {
412         sx_init(&sndstat_lock, "sndstat lock");
413         sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS,
414             UID_ROOT, GID_WHEEL, 0644, "sndstat");
415 }
416 SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL);
417
418 static void
419 sndstat_sysuninit(void *p)
420 {
421         if (sndstat_dev != NULL) {
422                 /* destroy_dev() will wait for all references to go away */
423                 destroy_dev(sndstat_dev);
424         }
425         sx_destroy(&sndstat_lock);
426 }
427 SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL);