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 37 SND_DECLARE_FILE("$FreeBSD$"); 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 44 static d_open_t sndstat_open; 45 static void sndstat_close(void *); 46 static d_read_t sndstat_read; 47 static d_write_t sndstat_write; 48 49 static 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 58 struct 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 66 struct sndstat_file { 67 TAILQ_ENTRY(sndstat_file) entry; 68 struct sbuf sbuf; 69 int out_offset; 70 int in_offset; 71 }; 72 73 static struct sx sndstat_lock; 74 static struct cdev *sndstat_dev; 75 76 #define SNDSTAT_LOCK() sx_xlock(&sndstat_lock) 77 #define SNDSTAT_UNLOCK() sx_xunlock(&sndstat_lock) 78 79 static TAILQ_HEAD(, sndstat_entry) sndstat_devlist = TAILQ_HEAD_INITIALIZER(sndstat_devlist); 80 static TAILQ_HEAD(, sndstat_file) sndstat_filelist = TAILQ_HEAD_INITIALIZER(sndstat_filelist); 81 82 int snd_verbose = 0; 83 84 static int sndstat_prepare(struct sndstat_file *); 85 86 static int 87 sysctl_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 } 101 SYSCTL_PROC(_hw_snd, OID_AUTO, verbose, CTLTYPE_INT | CTLFLAG_RWTUN, 102 0, sizeof(int), sysctl_hw_sndverbose, "I", "verbosity level"); 103 104 static int 105 sndstat_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 125 static void 126 sndstat_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 138 static int 139 sndstat_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; 171 done: 172 SNDSTAT_UNLOCK(); 173 return (err); 174 } 175 176 static int 177 sndstat_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 231 int 232 sndstat_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 284 int 285 sndstat_registerfile(char *str) 286 { 287 return (sndstat_register(NULL, str, NULL)); 288 } 289 290 int 291 sndstat_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 310 int 311 sndstat_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 332 static int 333 sndstat_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 407 static void 408 sndstat_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 } 414 SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL); 415 416 static void 417 sndstat_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 } 425 SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL); 426