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); 428