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