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, 104 CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, 0, sizeof(int), 105 sysctl_hw_sndverbose, "I", 106 "verbosity level"); 107 108 static int 109 sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td) 110 { 111 struct sndstat_file *pf; 112 113 pf = malloc(sizeof(*pf), M_DEVBUF, M_WAITOK | M_ZERO); 114 115 SNDSTAT_LOCK(); 116 if (sbuf_new(&pf->sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) { 117 SNDSTAT_UNLOCK(); 118 free(pf, M_DEVBUF); 119 return (ENOMEM); 120 } 121 TAILQ_INSERT_TAIL(&sndstat_filelist, pf, entry); 122 SNDSTAT_UNLOCK(); 123 124 devfs_set_cdevpriv(pf, &sndstat_close); 125 126 return (0); 127 } 128 129 static void 130 sndstat_close(void *sndstat_file) 131 { 132 struct sndstat_file *pf = (struct sndstat_file *)sndstat_file; 133 134 SNDSTAT_LOCK(); 135 sbuf_delete(&pf->sbuf); 136 TAILQ_REMOVE(&sndstat_filelist, pf, entry); 137 SNDSTAT_UNLOCK(); 138 139 free(pf, M_DEVBUF); 140 } 141 142 static int 143 sndstat_read(struct cdev *i_dev, struct uio *buf, int flag) 144 { 145 struct sndstat_file *pf; 146 int err; 147 int len; 148 149 err = devfs_get_cdevpriv((void **)&pf); 150 if (err != 0) 151 return (err); 152 153 /* skip zero-length reads */ 154 if (buf->uio_resid == 0) 155 return (0); 156 157 SNDSTAT_LOCK(); 158 if (pf->out_offset != 0) { 159 /* don't allow both reading and writing */ 160 err = EINVAL; 161 goto done; 162 } else if (pf->in_offset == 0) { 163 err = sndstat_prepare(pf); 164 if (err <= 0) { 165 err = ENOMEM; 166 goto done; 167 } 168 } 169 len = sbuf_len(&pf->sbuf) - pf->in_offset; 170 if (len > buf->uio_resid) 171 len = buf->uio_resid; 172 if (len > 0) 173 err = uiomove(sbuf_data(&pf->sbuf) + pf->in_offset, len, buf); 174 pf->in_offset += len; 175 done: 176 SNDSTAT_UNLOCK(); 177 return (err); 178 } 179 180 static int 181 sndstat_write(struct cdev *i_dev, struct uio *buf, int flag) 182 { 183 struct sndstat_file *pf; 184 uint8_t temp[64]; 185 int err; 186 int len; 187 188 err = devfs_get_cdevpriv((void **)&pf); 189 if (err != 0) 190 return (err); 191 192 /* skip zero-length writes */ 193 if (buf->uio_resid == 0) 194 return (0); 195 196 /* don't allow writing more than 64Kbytes */ 197 if (buf->uio_resid > 65536) 198 return (ENOMEM); 199 200 SNDSTAT_LOCK(); 201 if (pf->in_offset != 0) { 202 /* don't allow both reading and writing */ 203 err = EINVAL; 204 } else { 205 /* only remember the last write - allows for updates */ 206 sbuf_clear(&pf->sbuf); 207 while (1) { 208 len = sizeof(temp); 209 if (len > buf->uio_resid) 210 len = buf->uio_resid; 211 if (len > 0) { 212 err = uiomove(temp, len, buf); 213 if (err) 214 break; 215 } else { 216 break; 217 } 218 if (sbuf_bcat(&pf->sbuf, temp, len) < 0) { 219 err = ENOMEM; 220 break; 221 } 222 } 223 sbuf_finish(&pf->sbuf); 224 if (err == 0) 225 pf->out_offset = sbuf_len(&pf->sbuf); 226 else 227 pf->out_offset = 0; 228 } 229 SNDSTAT_UNLOCK(); 230 return (err); 231 } 232 233 /************************************************************************/ 234 235 int 236 sndstat_register(device_t dev, char *str, sndstat_handler handler) 237 { 238 struct sndstat_entry *ent; 239 struct sndstat_entry *pre; 240 const char *devtype; 241 int type, unit; 242 243 if (dev) { 244 unit = device_get_unit(dev); 245 devtype = device_get_name(dev); 246 if (!strcmp(devtype, "pcm")) 247 type = SS_TYPE_PCM; 248 else if (!strcmp(devtype, "midi")) 249 type = SS_TYPE_MIDI; 250 else if (!strcmp(devtype, "sequencer")) 251 type = SS_TYPE_SEQUENCER; 252 else 253 return (EINVAL); 254 } else { 255 type = SS_TYPE_MODULE; 256 unit = -1; 257 } 258 259 ent = malloc(sizeof *ent, M_DEVBUF, M_WAITOK | M_ZERO); 260 ent->dev = dev; 261 ent->str = str; 262 ent->type = type; 263 ent->unit = unit; 264 ent->handler = handler; 265 266 SNDSTAT_LOCK(); 267 /* sorted list insertion */ 268 TAILQ_FOREACH(pre, &sndstat_devlist, link) { 269 if (pre->unit > unit) 270 break; 271 else if (pre->unit < unit) 272 continue; 273 if (pre->type > type) 274 break; 275 else if (pre->type < unit) 276 continue; 277 } 278 if (pre == NULL) { 279 TAILQ_INSERT_TAIL(&sndstat_devlist, ent, link); 280 } else { 281 TAILQ_INSERT_BEFORE(pre, ent, link); 282 } 283 SNDSTAT_UNLOCK(); 284 285 return (0); 286 } 287 288 int 289 sndstat_registerfile(char *str) 290 { 291 return (sndstat_register(NULL, str, NULL)); 292 } 293 294 int 295 sndstat_unregister(device_t dev) 296 { 297 struct sndstat_entry *ent; 298 int error = ENXIO; 299 300 SNDSTAT_LOCK(); 301 TAILQ_FOREACH(ent, &sndstat_devlist, link) { 302 if (ent->dev == dev) { 303 TAILQ_REMOVE(&sndstat_devlist, ent, link); 304 free(ent, M_DEVBUF); 305 error = 0; 306 break; 307 } 308 } 309 SNDSTAT_UNLOCK(); 310 311 return (error); 312 } 313 314 int 315 sndstat_unregisterfile(char *str) 316 { 317 struct sndstat_entry *ent; 318 int error = ENXIO; 319 320 SNDSTAT_LOCK(); 321 TAILQ_FOREACH(ent, &sndstat_devlist, link) { 322 if (ent->dev == NULL && ent->str == str) { 323 TAILQ_REMOVE(&sndstat_devlist, ent, link); 324 free(ent, M_DEVBUF); 325 error = 0; 326 break; 327 } 328 } 329 SNDSTAT_UNLOCK(); 330 331 return (error); 332 } 333 334 /************************************************************************/ 335 336 static int 337 sndstat_prepare(struct sndstat_file *pf_self) 338 { 339 struct sbuf *s = &pf_self->sbuf; 340 struct sndstat_entry *ent; 341 struct snddev_info *d; 342 struct sndstat_file *pf; 343 int k; 344 345 /* make sure buffer is reset */ 346 sbuf_clear(s); 347 348 if (snd_verbose > 0) { 349 sbuf_printf(s, "FreeBSD Audio Driver (%ubit %d/%s)\n", 350 (u_int)sizeof(intpcm32_t) << 3, SND_DRV_VERSION, 351 MACHINE_ARCH); 352 } 353 354 /* generate list of installed devices */ 355 k = 0; 356 TAILQ_FOREACH(ent, &sndstat_devlist, link) { 357 if (ent->dev == NULL) 358 continue; 359 d = device_get_softc(ent->dev); 360 if (!PCM_REGISTERED(d)) 361 continue; 362 if (!k++) 363 sbuf_printf(s, "Installed devices:\n"); 364 sbuf_printf(s, "%s:", device_get_nameunit(ent->dev)); 365 sbuf_printf(s, " <%s>", device_get_desc(ent->dev)); 366 if (snd_verbose > 0) 367 sbuf_printf(s, " %s", ent->str); 368 if (ent->handler) { 369 /* XXX Need Giant magic entry ??? */ 370 PCM_ACQUIRE_QUICK(d); 371 ent->handler(s, ent->dev, snd_verbose); 372 PCM_RELEASE_QUICK(d); 373 } 374 sbuf_printf(s, "\n"); 375 } 376 if (k == 0) 377 sbuf_printf(s, "No devices installed.\n"); 378 379 /* append any input from userspace */ 380 k = 0; 381 TAILQ_FOREACH(pf, &sndstat_filelist, entry) { 382 if (pf == pf_self) 383 continue; 384 if (pf->out_offset == 0) 385 continue; 386 if (!k++) 387 sbuf_printf(s, "Installed devices from userspace:\n"); 388 sbuf_bcat(s, sbuf_data(&pf->sbuf), 389 sbuf_len(&pf->sbuf)); 390 } 391 if (k == 0) 392 sbuf_printf(s, "No devices installed from userspace.\n"); 393 394 /* append any file versions */ 395 if (snd_verbose >= 3) { 396 k = 0; 397 TAILQ_FOREACH(ent, &sndstat_devlist, link) { 398 if (ent->dev == NULL && ent->str != NULL) { 399 if (!k++) 400 sbuf_printf(s, "\nFile Versions:\n"); 401 sbuf_printf(s, "%s\n", ent->str); 402 } 403 } 404 if (k == 0) 405 sbuf_printf(s, "\nNo file versions.\n"); 406 } 407 sbuf_finish(s); 408 return (sbuf_len(s)); 409 } 410 411 static void 412 sndstat_sysinit(void *p) 413 { 414 sx_init(&sndstat_lock, "sndstat lock"); 415 sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS, 416 UID_ROOT, GID_WHEEL, 0644, "sndstat"); 417 } 418 SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL); 419 420 static void 421 sndstat_sysuninit(void *p) 422 { 423 if (sndstat_dev != NULL) { 424 /* destroy_dev() will wait for all references to go away */ 425 destroy_dev(sndstat_dev); 426 } 427 sx_destroy(&sndstat_lock); 428 } 429 SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL); 430