4ce1ba6523
- SNDSTAT_LABEL_* are renamed to SNDST_DSPS_*, and SNDSTAT_LABEL_DSPS becomes SNDST_DSPS. - Centralize channel number/rate/formats into a single nvlist The above nvlist is named "info_play" and "info_rec" - Expose only encoding format in pfmts/rfmts. Userland has no direct access to AFMT_ENCODING/CHANNEL/EXTCHANNEL macros, thus it serves no meaning to expose too much information through this pair of labels. However pminrate/rminrate, pmaxrate/rmaxrate, pfmts/rfmts are deprecated and will be removed in future. This commit keeps ioctls ABI compatibility with __FreeBSD_version 1400006 for now. In future the compat ABI with 1400006 will be removed once audio/virtual_oss is rebuilt. Sponsored by: The FreeBSD Foundation Reviewed by: hselasky Approved by: philip (mentor) Differential Revision: https://reviews.freebsd.org/D29770
1265 lines
30 KiB
C
1265 lines
30 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 2005-2009 Ariff Abdullah <ariff@FreeBSD.org>
|
|
* Copyright (c) 2001 Cameron Grant <cg@FreeBSD.org>
|
|
* Copyright (c) 2020 The FreeBSD Foundation
|
|
* All rights reserved.
|
|
*
|
|
* Portions of this software were developed by Ka Ho Ng
|
|
* under sponsorship from the FreeBSD Foundation.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#ifdef HAVE_KERNEL_OPTION_HEADERS
|
|
#include "opt_snd.h"
|
|
#endif
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/nv.h>
|
|
#include <sys/dnv.h>
|
|
#include <sys/sx.h>
|
|
#ifdef COMPAT_FREEBSD32
|
|
#include <sys/sysent.h>
|
|
#endif
|
|
|
|
#include <dev/sound/pcm/sound.h>
|
|
#include <dev/sound/pcm/pcm.h>
|
|
#include <dev/sound/version.h>
|
|
|
|
|
|
SND_DECLARE_FILE("$FreeBSD$");
|
|
|
|
#define SS_TYPE_MODULE 0
|
|
#define SS_TYPE_PCM 1
|
|
#define SS_TYPE_MIDI 2
|
|
#define SS_TYPE_SEQUENCER 3
|
|
|
|
static d_open_t sndstat_open;
|
|
static void sndstat_close(void *);
|
|
static d_read_t sndstat_read;
|
|
static d_write_t sndstat_write;
|
|
static d_ioctl_t sndstat_ioctl;
|
|
|
|
static struct cdevsw sndstat_cdevsw = {
|
|
.d_version = D_VERSION,
|
|
.d_open = sndstat_open,
|
|
.d_read = sndstat_read,
|
|
.d_write = sndstat_write,
|
|
.d_ioctl = sndstat_ioctl,
|
|
.d_name = "sndstat",
|
|
.d_flags = D_TRACKCLOSE,
|
|
};
|
|
|
|
struct sndstat_entry {
|
|
TAILQ_ENTRY(sndstat_entry) link;
|
|
device_t dev;
|
|
char *str;
|
|
sndstat_handler handler;
|
|
int type, unit;
|
|
};
|
|
|
|
struct sndstat_userdev {
|
|
TAILQ_ENTRY(sndstat_userdev) link;
|
|
char *provider;
|
|
char *nameunit;
|
|
char *devnode;
|
|
char *desc;
|
|
unsigned int pchan;
|
|
unsigned int rchan;
|
|
struct {
|
|
uint32_t min_rate;
|
|
uint32_t max_rate;
|
|
uint32_t formats;
|
|
uint32_t min_chn;
|
|
uint32_t max_chn;
|
|
} info_play, info_rec;
|
|
nvlist_t *provider_nvl;
|
|
};
|
|
|
|
struct sndstat_file {
|
|
TAILQ_ENTRY(sndstat_file) entry;
|
|
struct sbuf sbuf;
|
|
struct sx lock;
|
|
void *devs_nvlbuf; /* (l) */
|
|
size_t devs_nbytes; /* (l) */
|
|
TAILQ_HEAD(, sndstat_userdev) userdev_list; /* (l) */
|
|
int out_offset;
|
|
int in_offset;
|
|
int fflags;
|
|
};
|
|
|
|
static struct sx sndstat_lock;
|
|
static struct cdev *sndstat_dev;
|
|
|
|
#define SNDSTAT_LOCK() sx_xlock(&sndstat_lock)
|
|
#define SNDSTAT_UNLOCK() sx_xunlock(&sndstat_lock)
|
|
|
|
static TAILQ_HEAD(, sndstat_entry) sndstat_devlist = TAILQ_HEAD_INITIALIZER(sndstat_devlist);
|
|
static TAILQ_HEAD(, sndstat_file) sndstat_filelist = TAILQ_HEAD_INITIALIZER(sndstat_filelist);
|
|
|
|
int snd_verbose = 0;
|
|
|
|
static int sndstat_prepare(struct sndstat_file *);
|
|
static struct sndstat_userdev *
|
|
sndstat_line2userdev(struct sndstat_file *, const char *, int);
|
|
|
|
static int
|
|
sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
int error, verbose;
|
|
|
|
verbose = snd_verbose;
|
|
error = sysctl_handle_int(oidp, &verbose, 0, req);
|
|
if (error == 0 && req->newptr != NULL) {
|
|
if (verbose < 0 || verbose > 4)
|
|
error = EINVAL;
|
|
else
|
|
snd_verbose = verbose;
|
|
}
|
|
return (error);
|
|
}
|
|
SYSCTL_PROC(_hw_snd, OID_AUTO, verbose,
|
|
CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, 0, sizeof(int),
|
|
sysctl_hw_sndverbose, "I",
|
|
"verbosity level");
|
|
|
|
static int
|
|
sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
|
|
{
|
|
struct sndstat_file *pf;
|
|
|
|
pf = malloc(sizeof(*pf), M_DEVBUF, M_WAITOK | M_ZERO);
|
|
|
|
if (sbuf_new(&pf->sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) {
|
|
free(pf, M_DEVBUF);
|
|
return (ENOMEM);
|
|
}
|
|
|
|
pf->fflags = flags;
|
|
TAILQ_INIT(&pf->userdev_list);
|
|
sx_init(&pf->lock, "sndstat_file");
|
|
|
|
SNDSTAT_LOCK();
|
|
TAILQ_INSERT_TAIL(&sndstat_filelist, pf, entry);
|
|
SNDSTAT_UNLOCK();
|
|
|
|
devfs_set_cdevpriv(pf, &sndstat_close);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Should only be called either when:
|
|
* * Closing
|
|
* * pf->lock held
|
|
*/
|
|
static void
|
|
sndstat_remove_all_userdevs(struct sndstat_file *pf)
|
|
{
|
|
struct sndstat_userdev *ud;
|
|
|
|
KASSERT(
|
|
sx_xlocked(&pf->lock), ("%s: Called without pf->lock", __func__));
|
|
while ((ud = TAILQ_FIRST(&pf->userdev_list)) != NULL) {
|
|
TAILQ_REMOVE(&pf->userdev_list, ud, link);
|
|
free(ud->provider, M_DEVBUF);
|
|
free(ud->desc, M_DEVBUF);
|
|
free(ud->devnode, M_DEVBUF);
|
|
free(ud->nameunit, M_DEVBUF);
|
|
nvlist_destroy(ud->provider_nvl);
|
|
free(ud, M_DEVBUF);
|
|
}
|
|
}
|
|
|
|
static void
|
|
sndstat_close(void *sndstat_file)
|
|
{
|
|
struct sndstat_file *pf = (struct sndstat_file *)sndstat_file;
|
|
|
|
SNDSTAT_LOCK();
|
|
sbuf_delete(&pf->sbuf);
|
|
TAILQ_REMOVE(&sndstat_filelist, pf, entry);
|
|
SNDSTAT_UNLOCK();
|
|
|
|
free(pf->devs_nvlbuf, M_NVLIST);
|
|
sx_xlock(&pf->lock);
|
|
sndstat_remove_all_userdevs(pf);
|
|
sx_xunlock(&pf->lock);
|
|
sx_destroy(&pf->lock);
|
|
|
|
free(pf, M_DEVBUF);
|
|
}
|
|
|
|
static int
|
|
sndstat_read(struct cdev *i_dev, struct uio *buf, int flag)
|
|
{
|
|
struct sndstat_file *pf;
|
|
int err;
|
|
int len;
|
|
|
|
err = devfs_get_cdevpriv((void **)&pf);
|
|
if (err != 0)
|
|
return (err);
|
|
|
|
/* skip zero-length reads */
|
|
if (buf->uio_resid == 0)
|
|
return (0);
|
|
|
|
SNDSTAT_LOCK();
|
|
if (pf->out_offset != 0) {
|
|
/* don't allow both reading and writing */
|
|
err = EINVAL;
|
|
goto done;
|
|
} else if (pf->in_offset == 0) {
|
|
err = sndstat_prepare(pf);
|
|
if (err <= 0) {
|
|
err = ENOMEM;
|
|
goto done;
|
|
}
|
|
}
|
|
len = sbuf_len(&pf->sbuf) - pf->in_offset;
|
|
if (len > buf->uio_resid)
|
|
len = buf->uio_resid;
|
|
if (len > 0)
|
|
err = uiomove(sbuf_data(&pf->sbuf) + pf->in_offset, len, buf);
|
|
pf->in_offset += len;
|
|
done:
|
|
SNDSTAT_UNLOCK();
|
|
return (err);
|
|
}
|
|
|
|
static int
|
|
sndstat_write(struct cdev *i_dev, struct uio *buf, int flag)
|
|
{
|
|
struct sndstat_file *pf;
|
|
uint8_t temp[64];
|
|
int err;
|
|
int len;
|
|
|
|
err = devfs_get_cdevpriv((void **)&pf);
|
|
if (err != 0)
|
|
return (err);
|
|
|
|
/* skip zero-length writes */
|
|
if (buf->uio_resid == 0)
|
|
return (0);
|
|
|
|
/* don't allow writing more than 64Kbytes */
|
|
if (buf->uio_resid > 65536)
|
|
return (ENOMEM);
|
|
|
|
SNDSTAT_LOCK();
|
|
if (pf->in_offset != 0) {
|
|
/* don't allow both reading and writing */
|
|
err = EINVAL;
|
|
} else {
|
|
/* only remember the last write - allows for updates */
|
|
sx_xlock(&pf->lock);
|
|
sndstat_remove_all_userdevs(pf);
|
|
sx_xunlock(&pf->lock);
|
|
|
|
while (1) {
|
|
len = sizeof(temp);
|
|
if (len > buf->uio_resid)
|
|
len = buf->uio_resid;
|
|
if (len > 0) {
|
|
err = uiomove(temp, len, buf);
|
|
if (err)
|
|
break;
|
|
} else {
|
|
break;
|
|
}
|
|
if (sbuf_bcat(&pf->sbuf, temp, len) < 0) {
|
|
err = ENOMEM;
|
|
break;
|
|
}
|
|
}
|
|
sbuf_finish(&pf->sbuf);
|
|
|
|
if (err == 0) {
|
|
char *line, *str;
|
|
|
|
str = sbuf_data(&pf->sbuf);
|
|
while ((line = strsep(&str, "\n")) != NULL) {
|
|
struct sndstat_userdev *ud;
|
|
|
|
ud = sndstat_line2userdev(pf, line, strlen(line));
|
|
if (ud == NULL)
|
|
continue;
|
|
|
|
sx_xlock(&pf->lock);
|
|
TAILQ_INSERT_TAIL(&pf->userdev_list, ud, link);
|
|
sx_xunlock(&pf->lock);
|
|
}
|
|
|
|
pf->out_offset = sbuf_len(&pf->sbuf);
|
|
} else
|
|
pf->out_offset = 0;
|
|
|
|
sbuf_clear(&pf->sbuf);
|
|
}
|
|
SNDSTAT_UNLOCK();
|
|
return (err);
|
|
}
|
|
|
|
static void
|
|
sndstat_get_caps(struct snddev_info *d, bool play, uint32_t *min_rate,
|
|
uint32_t *max_rate, uint32_t *fmts, uint32_t *minchn, uint32_t *maxchn)
|
|
{
|
|
struct pcm_channel *c;
|
|
unsigned int encoding;
|
|
int dir;
|
|
|
|
dir = play ? PCMDIR_PLAY : PCMDIR_REC;
|
|
|
|
if (play && d->pvchancount > 0) {
|
|
*min_rate = *max_rate = d->pvchanrate;
|
|
*fmts = AFMT_ENCODING(d->pvchanformat);
|
|
*minchn = *maxchn = AFMT_CHANNEL(d->pvchanformat);
|
|
return;
|
|
} else if (!play && d->rvchancount > 0) {
|
|
*min_rate = *max_rate = d->rvchanrate;
|
|
*fmts = AFMT_ENCODING(d->rvchanformat);
|
|
*minchn = *maxchn = AFMT_CHANNEL(d->rvchanformat);
|
|
return;
|
|
}
|
|
|
|
*min_rate = UINT32_MAX;
|
|
*max_rate = 0;
|
|
*minchn = UINT32_MAX;
|
|
*maxchn = 0;
|
|
encoding = 0;
|
|
CHN_FOREACH(c, d, channels.pcm) {
|
|
struct pcmchan_caps *caps;
|
|
int i;
|
|
|
|
if (c->direction != dir || (c->flags & CHN_F_VIRTUAL) != 0)
|
|
continue;
|
|
|
|
CHN_LOCK(c);
|
|
caps = chn_getcaps(c);
|
|
*min_rate = min(caps->minspeed, *min_rate);
|
|
*max_rate = max(caps->maxspeed, *max_rate);
|
|
for (i = 0; caps->fmtlist[i]; i++) {
|
|
encoding |= AFMT_ENCODING(caps->fmtlist[i]);
|
|
*minchn = min(AFMT_CHANNEL(encoding), *minchn);
|
|
*maxchn = max(AFMT_CHANNEL(encoding), *maxchn);
|
|
}
|
|
CHN_UNLOCK(c);
|
|
}
|
|
if (*min_rate == UINT32_MAX)
|
|
*min_rate = 0;
|
|
if (*minchn == UINT32_MAX)
|
|
*minchn = 0;
|
|
}
|
|
|
|
static nvlist_t *
|
|
sndstat_create_diinfo_nv(uint32_t min_rate, uint32_t max_rate, uint32_t formats,
|
|
uint32_t min_chn, uint32_t max_chn)
|
|
{
|
|
nvlist_t *nv;
|
|
|
|
nv = nvlist_create(0);
|
|
if (nv == NULL)
|
|
return (NULL);
|
|
nvlist_add_number(nv, SNDST_DSPS_INFO_MIN_RATE, min_rate);
|
|
nvlist_add_number(nv, SNDST_DSPS_INFO_MAX_RATE, max_rate);
|
|
nvlist_add_number(nv, SNDST_DSPS_INFO_FORMATS, formats);
|
|
nvlist_add_number(nv, SNDST_DSPS_INFO_MIN_CHN, min_chn);
|
|
nvlist_add_number(nv, SNDST_DSPS_INFO_MAX_CHN, max_chn);
|
|
return (nv);
|
|
}
|
|
|
|
static int
|
|
sndstat_build_sound4_nvlist(struct snddev_info *d, nvlist_t **dip)
|
|
{
|
|
uint32_t maxrate, minrate, fmts, minchn, maxchn;
|
|
nvlist_t *di = NULL, *sound4di = NULL, *diinfo = NULL;
|
|
int err;
|
|
|
|
di = nvlist_create(0);
|
|
if (di == NULL) {
|
|
err = ENOMEM;
|
|
goto done;
|
|
}
|
|
sound4di = nvlist_create(0);
|
|
if (sound4di == NULL) {
|
|
err = ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
nvlist_add_bool(di, SNDST_DSPS_FROM_USER, false);
|
|
nvlist_add_stringf(di, SNDST_DSPS_NAMEUNIT, "%s",
|
|
device_get_nameunit(d->dev));
|
|
nvlist_add_stringf(di, SNDST_DSPS_DEVNODE, "dsp%d",
|
|
device_get_unit(d->dev));
|
|
nvlist_add_string(
|
|
di, SNDST_DSPS_DESC, device_get_desc(d->dev));
|
|
|
|
PCM_ACQUIRE_QUICK(d);
|
|
nvlist_add_number(di, SNDST_DSPS_PCHAN, d->playcount);
|
|
nvlist_add_number(di, SNDST_DSPS_RCHAN, d->reccount);
|
|
if (d->playcount > 0) {
|
|
sndstat_get_caps(d, true, &minrate, &maxrate, &fmts, &minchn,
|
|
&maxchn);
|
|
nvlist_add_number(di, "pminrate", minrate);
|
|
nvlist_add_number(di, "pmaxrate", maxrate);
|
|
nvlist_add_number(di, "pfmts", fmts);
|
|
diinfo = sndstat_create_diinfo_nv(minrate, maxrate, fmts,
|
|
minchn, maxchn);
|
|
if (diinfo == NULL)
|
|
nvlist_set_error(di, ENOMEM);
|
|
else
|
|
nvlist_move_nvlist(di, SNDST_DSPS_INFO_PLAY, diinfo);
|
|
}
|
|
if (d->reccount > 0) {
|
|
sndstat_get_caps(d, false, &minrate, &maxrate, &fmts, &minchn,
|
|
&maxchn);
|
|
nvlist_add_number(di, "rminrate", minrate);
|
|
nvlist_add_number(di, "rmaxrate", maxrate);
|
|
nvlist_add_number(di, "rfmts", fmts);
|
|
diinfo = sndstat_create_diinfo_nv(minrate, maxrate, fmts,
|
|
minchn, maxchn);
|
|
if (diinfo == NULL)
|
|
nvlist_set_error(di, ENOMEM);
|
|
else
|
|
nvlist_move_nvlist(di, SNDST_DSPS_INFO_REC, diinfo);
|
|
}
|
|
|
|
nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_UNIT,
|
|
device_get_unit(d->dev)); // XXX: I want signed integer here
|
|
nvlist_add_bool(
|
|
sound4di, SNDST_DSPS_SOUND4_BITPERFECT, d->flags & SD_F_BITPERFECT);
|
|
nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_PVCHAN, d->pvchancount);
|
|
nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_RVCHAN, d->rvchancount);
|
|
nvlist_move_nvlist(di, SNDST_DSPS_PROVIDER_INFO, sound4di);
|
|
sound4di = NULL;
|
|
PCM_RELEASE_QUICK(d);
|
|
nvlist_add_string(di, SNDST_DSPS_PROVIDER, SNDST_DSPS_SOUND4_PROVIDER);
|
|
|
|
err = nvlist_error(di);
|
|
if (err)
|
|
goto done;
|
|
|
|
*dip = di;
|
|
|
|
done:
|
|
if (err) {
|
|
nvlist_destroy(sound4di);
|
|
nvlist_destroy(di);
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int
|
|
sndstat_build_userland_nvlist(struct sndstat_userdev *ud, nvlist_t **dip)
|
|
{
|
|
nvlist_t *di, *diinfo;
|
|
int err;
|
|
|
|
di = nvlist_create(0);
|
|
if (di == NULL) {
|
|
err = ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
nvlist_add_bool(di, SNDST_DSPS_FROM_USER, true);
|
|
nvlist_add_number(di, SNDST_DSPS_PCHAN, ud->pchan);
|
|
nvlist_add_number(di, SNDST_DSPS_RCHAN, ud->rchan);
|
|
nvlist_add_string(di, SNDST_DSPS_NAMEUNIT, ud->nameunit);
|
|
nvlist_add_string(
|
|
di, SNDST_DSPS_DEVNODE, ud->devnode);
|
|
nvlist_add_string(di, SNDST_DSPS_DESC, ud->desc);
|
|
if (ud->pchan != 0) {
|
|
nvlist_add_number(di, "pminrate",
|
|
ud->info_play.min_rate);
|
|
nvlist_add_number(di, "pmaxrate",
|
|
ud->info_play.max_rate);
|
|
nvlist_add_number(di, "pfmts",
|
|
ud->info_play.formats);
|
|
diinfo = sndstat_create_diinfo_nv(ud->info_play.min_rate,
|
|
ud->info_play.max_rate, ud->info_play.formats,
|
|
ud->info_play.min_chn, ud->info_play.max_chn);
|
|
if (diinfo == NULL)
|
|
nvlist_set_error(di, ENOMEM);
|
|
else
|
|
nvlist_move_nvlist(di, SNDST_DSPS_INFO_PLAY, diinfo);
|
|
}
|
|
if (ud->rchan != 0) {
|
|
nvlist_add_number(di, "rminrate",
|
|
ud->info_rec.min_rate);
|
|
nvlist_add_number(di, "rmaxrate",
|
|
ud->info_rec.max_rate);
|
|
nvlist_add_number(di, "rfmts",
|
|
ud->info_rec.formats);
|
|
diinfo = sndstat_create_diinfo_nv(ud->info_rec.min_rate,
|
|
ud->info_rec.max_rate, ud->info_rec.formats,
|
|
ud->info_rec.min_chn, ud->info_rec.max_chn);
|
|
if (diinfo == NULL)
|
|
nvlist_set_error(di, ENOMEM);
|
|
else
|
|
nvlist_move_nvlist(di, SNDST_DSPS_INFO_REC, diinfo);
|
|
}
|
|
nvlist_add_string(di, SNDST_DSPS_PROVIDER,
|
|
(ud->provider != NULL) ? ud->provider : "");
|
|
if (ud->provider_nvl != NULL)
|
|
nvlist_add_nvlist(
|
|
di, SNDST_DSPS_PROVIDER_INFO, ud->provider_nvl);
|
|
|
|
err = nvlist_error(di);
|
|
if (err)
|
|
goto done;
|
|
|
|
*dip = di;
|
|
|
|
done:
|
|
if (err)
|
|
nvlist_destroy(di);
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Should only be called with the following locks held:
|
|
* * sndstat_lock
|
|
*/
|
|
static int
|
|
sndstat_create_devs_nvlist(nvlist_t **nvlp)
|
|
{
|
|
int err;
|
|
nvlist_t *nvl;
|
|
struct sndstat_entry *ent;
|
|
struct sndstat_file *pf;
|
|
|
|
nvl = nvlist_create(0);
|
|
if (nvl == NULL)
|
|
return (ENOMEM);
|
|
|
|
TAILQ_FOREACH(ent, &sndstat_devlist, link) {
|
|
struct snddev_info *d;
|
|
nvlist_t *di;
|
|
|
|
if (ent->dev == NULL)
|
|
continue;
|
|
d = device_get_softc(ent->dev);
|
|
if (!PCM_REGISTERED(d))
|
|
continue;
|
|
|
|
err = sndstat_build_sound4_nvlist(d, &di);
|
|
if (err)
|
|
goto done;
|
|
|
|
nvlist_append_nvlist_array(nvl, SNDST_DSPS, di);
|
|
nvlist_destroy(di);
|
|
err = nvlist_error(nvl);
|
|
if (err)
|
|
goto done;
|
|
}
|
|
|
|
TAILQ_FOREACH(pf, &sndstat_filelist, entry) {
|
|
struct sndstat_userdev *ud;
|
|
|
|
sx_xlock(&pf->lock);
|
|
|
|
TAILQ_FOREACH(ud, &pf->userdev_list, link) {
|
|
nvlist_t *di;
|
|
|
|
err = sndstat_build_userland_nvlist(ud, &di);
|
|
if (err != 0) {
|
|
sx_xunlock(&pf->lock);
|
|
goto done;
|
|
}
|
|
nvlist_append_nvlist_array(nvl, SNDST_DSPS, di);
|
|
nvlist_destroy(di);
|
|
|
|
err = nvlist_error(nvl);
|
|
if (err != 0) {
|
|
sx_xunlock(&pf->lock);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
sx_xunlock(&pf->lock);
|
|
}
|
|
|
|
*nvlp = nvl;
|
|
|
|
done:
|
|
if (err != 0)
|
|
nvlist_destroy(nvl);
|
|
return (err);
|
|
}
|
|
|
|
static int
|
|
sndstat_refresh_devs(struct sndstat_file *pf)
|
|
{
|
|
sx_xlock(&pf->lock);
|
|
free(pf->devs_nvlbuf, M_NVLIST);
|
|
pf->devs_nvlbuf = NULL;
|
|
pf->devs_nbytes = 0;
|
|
sx_unlock(&pf->lock);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
sndstat_get_devs(struct sndstat_file *pf, caddr_t data)
|
|
{
|
|
int err;
|
|
struct sndstioc_nv_arg *arg = (struct sndstioc_nv_arg *)data;
|
|
|
|
SNDSTAT_LOCK();
|
|
sx_xlock(&pf->lock);
|
|
|
|
if (pf->devs_nvlbuf == NULL) {
|
|
nvlist_t *nvl;
|
|
void *nvlbuf;
|
|
size_t nbytes;
|
|
int err;
|
|
|
|
sx_xunlock(&pf->lock);
|
|
|
|
err = sndstat_create_devs_nvlist(&nvl);
|
|
if (err) {
|
|
SNDSTAT_UNLOCK();
|
|
return (err);
|
|
}
|
|
|
|
sx_xlock(&pf->lock);
|
|
|
|
nvlbuf = nvlist_pack(nvl, &nbytes);
|
|
err = nvlist_error(nvl);
|
|
nvlist_destroy(nvl);
|
|
if (nvlbuf == NULL || err != 0) {
|
|
SNDSTAT_UNLOCK();
|
|
sx_xunlock(&pf->lock);
|
|
if (err == 0)
|
|
return (ENOMEM);
|
|
return (err);
|
|
}
|
|
|
|
free(pf->devs_nvlbuf, M_NVLIST);
|
|
pf->devs_nvlbuf = nvlbuf;
|
|
pf->devs_nbytes = nbytes;
|
|
}
|
|
|
|
SNDSTAT_UNLOCK();
|
|
|
|
if (!arg->nbytes) {
|
|
arg->nbytes = pf->devs_nbytes;
|
|
err = 0;
|
|
goto done;
|
|
}
|
|
if (arg->nbytes < pf->devs_nbytes) {
|
|
arg->nbytes = 0;
|
|
err = 0;
|
|
goto done;
|
|
}
|
|
|
|
err = copyout(pf->devs_nvlbuf, arg->buf, pf->devs_nbytes);
|
|
if (err)
|
|
goto done;
|
|
|
|
arg->nbytes = pf->devs_nbytes;
|
|
|
|
free(pf->devs_nvlbuf, M_NVLIST);
|
|
pf->devs_nvlbuf = NULL;
|
|
pf->devs_nbytes = 0;
|
|
|
|
done:
|
|
sx_unlock(&pf->lock);
|
|
return (err);
|
|
}
|
|
|
|
static int
|
|
sndstat_unpack_user_nvlbuf(const void *unvlbuf, size_t nbytes, nvlist_t **nvl)
|
|
{
|
|
void *nvlbuf;
|
|
int err;
|
|
|
|
nvlbuf = malloc(nbytes, M_DEVBUF, M_WAITOK);
|
|
err = copyin(unvlbuf, nvlbuf, nbytes);
|
|
if (err != 0) {
|
|
free(nvlbuf, M_DEVBUF);
|
|
return (err);
|
|
}
|
|
*nvl = nvlist_unpack(nvlbuf, nbytes, 0);
|
|
free(nvlbuf, M_DEVBUF);
|
|
if (nvl == NULL) {
|
|
return (EINVAL);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static bool
|
|
sndstat_diinfo_is_sane(const nvlist_t *diinfo)
|
|
{
|
|
if (!(nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_RATE) &&
|
|
nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_RATE) &&
|
|
nvlist_exists_number(diinfo, SNDST_DSPS_INFO_FORMATS) &&
|
|
nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_CHN) &&
|
|
nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_CHN)))
|
|
return (false);
|
|
return (true);
|
|
}
|
|
|
|
static bool
|
|
sndstat_dsp_nvlist_is_sane(const nvlist_t *nvlist)
|
|
{
|
|
if (!(nvlist_exists_string(nvlist, SNDST_DSPS_DEVNODE) &&
|
|
nvlist_exists_string(nvlist, SNDST_DSPS_DESC) &&
|
|
nvlist_exists_number(nvlist, SNDST_DSPS_PCHAN) &&
|
|
nvlist_exists_number(nvlist, SNDST_DSPS_RCHAN)))
|
|
return (false);
|
|
|
|
if (nvlist_get_number(nvlist, SNDST_DSPS_PCHAN) > 0) {
|
|
if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) {
|
|
if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist,
|
|
SNDST_DSPS_INFO_PLAY)))
|
|
return (false);
|
|
} else if (!(nvlist_exists_number(nvlist, "pminrate") &&
|
|
nvlist_exists_number(nvlist, "pmaxrate") &&
|
|
nvlist_exists_number(nvlist, "pfmts")))
|
|
return (false);
|
|
}
|
|
|
|
if (nvlist_get_number(nvlist, SNDST_DSPS_RCHAN) > 0) {
|
|
if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) {
|
|
if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist,
|
|
SNDST_DSPS_INFO_REC)))
|
|
return (false);
|
|
} else if (!(nvlist_exists_number(nvlist, "rminrate") &&
|
|
nvlist_exists_number(nvlist, "rmaxrate") &&
|
|
nvlist_exists_number(nvlist, "rfmts")))
|
|
return (false);
|
|
}
|
|
|
|
return (true);
|
|
|
|
}
|
|
|
|
static void
|
|
sndstat_get_diinfo_nv(const nvlist_t *nv, uint32_t *min_rate,
|
|
uint32_t *max_rate, uint32_t *formats, uint32_t *min_chn,
|
|
uint32_t *max_chn)
|
|
{
|
|
*min_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_RATE);
|
|
*max_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_RATE);
|
|
*formats = nvlist_get_number(nv, SNDST_DSPS_INFO_FORMATS);
|
|
*min_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_CHN);
|
|
*max_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_CHN);
|
|
}
|
|
|
|
static int
|
|
sndstat_dsp_unpack_nvlist(const nvlist_t *nvlist, struct sndstat_userdev *ud)
|
|
{
|
|
const char *nameunit, *devnode, *desc;
|
|
unsigned int pchan, rchan;
|
|
uint32_t pminrate = 0, pmaxrate = 0;
|
|
uint32_t rminrate = 0, rmaxrate = 0;
|
|
uint32_t pfmts = 0, rfmts = 0;
|
|
uint32_t pminchn = 0, pmaxchn = 0;
|
|
uint32_t rminchn = 0, rmaxchn = 0;
|
|
nvlist_t *provider_nvl = NULL;
|
|
const nvlist_t *diinfo;
|
|
const char *provider;
|
|
|
|
devnode = nvlist_get_string(nvlist, SNDST_DSPS_DEVNODE);
|
|
if (nvlist_exists_string(nvlist, SNDST_DSPS_NAMEUNIT))
|
|
nameunit = nvlist_get_string(nvlist, SNDST_DSPS_NAMEUNIT);
|
|
else
|
|
nameunit = devnode;
|
|
desc = nvlist_get_string(nvlist, SNDST_DSPS_DESC);
|
|
pchan = nvlist_get_number(nvlist, SNDST_DSPS_PCHAN);
|
|
rchan = nvlist_get_number(nvlist, SNDST_DSPS_RCHAN);
|
|
if (pchan != 0) {
|
|
if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) {
|
|
diinfo = nvlist_get_nvlist(nvlist,
|
|
SNDST_DSPS_INFO_PLAY);
|
|
sndstat_get_diinfo_nv(diinfo, &pminrate, &pmaxrate,
|
|
&pfmts, &pminchn, &pmaxchn);
|
|
} else {
|
|
pminrate = nvlist_get_number(nvlist, "pminrate");
|
|
pmaxrate = nvlist_get_number(nvlist, "pmaxrate");
|
|
pfmts = nvlist_get_number(nvlist, "pfmts");
|
|
}
|
|
}
|
|
if (rchan != 0) {
|
|
if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) {
|
|
diinfo = nvlist_get_nvlist(nvlist,
|
|
SNDST_DSPS_INFO_REC);
|
|
sndstat_get_diinfo_nv(diinfo, &rminrate, &rmaxrate,
|
|
&rfmts, &rminchn, &rmaxchn);
|
|
} else {
|
|
rminrate = nvlist_get_number(nvlist, "rminrate");
|
|
rmaxrate = nvlist_get_number(nvlist, "rmaxrate");
|
|
rfmts = nvlist_get_number(nvlist, "rfmts");
|
|
}
|
|
}
|
|
|
|
provider = dnvlist_get_string(nvlist, SNDST_DSPS_PROVIDER, "");
|
|
if (provider[0] == '\0')
|
|
provider = NULL;
|
|
|
|
if (provider != NULL &&
|
|
nvlist_exists_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO)) {
|
|
provider_nvl = nvlist_clone(
|
|
nvlist_get_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO));
|
|
if (provider_nvl == NULL)
|
|
return (ENOMEM);
|
|
}
|
|
|
|
ud->provider = (provider != NULL) ? strdup(provider, M_DEVBUF) : NULL;
|
|
ud->devnode = strdup(devnode, M_DEVBUF);
|
|
ud->nameunit = strdup(nameunit, M_DEVBUF);
|
|
ud->desc = strdup(desc, M_DEVBUF);
|
|
ud->pchan = pchan;
|
|
ud->rchan = rchan;
|
|
ud->info_play.min_rate = pminrate;
|
|
ud->info_play.max_rate = pmaxrate;
|
|
ud->info_play.formats = pfmts;
|
|
ud->info_play.min_chn = pminchn;
|
|
ud->info_play.max_chn = pmaxchn;
|
|
ud->info_rec.min_rate = rminrate;
|
|
ud->info_rec.max_rate = rmaxrate;
|
|
ud->info_rec.formats = rfmts;
|
|
ud->info_rec.min_chn = rminchn;
|
|
ud->info_rec.max_chn = rmaxchn;
|
|
ud->provider_nvl = provider_nvl;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
sndstat_add_user_devs(struct sndstat_file *pf, caddr_t data)
|
|
{
|
|
int err;
|
|
nvlist_t *nvl = NULL;
|
|
const nvlist_t * const *dsps;
|
|
size_t i, ndsps;
|
|
struct sndstioc_nv_arg *arg = (struct sndstioc_nv_arg *)data;
|
|
|
|
if ((pf->fflags & FWRITE) == 0) {
|
|
err = EPERM;
|
|
goto done;
|
|
}
|
|
|
|
err = sndstat_unpack_user_nvlbuf(arg->buf, arg->nbytes, &nvl);
|
|
if (err != 0)
|
|
goto done;
|
|
|
|
if (!nvlist_exists_nvlist_array(nvl, SNDST_DSPS)) {
|
|
err = EINVAL;
|
|
goto done;
|
|
}
|
|
dsps = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &ndsps);
|
|
for (i = 0; i < ndsps; i++) {
|
|
if (!sndstat_dsp_nvlist_is_sane(dsps[i])) {
|
|
err = EINVAL;
|
|
goto done;
|
|
}
|
|
}
|
|
sx_xlock(&pf->lock);
|
|
for (i = 0; i < ndsps; i++) {
|
|
struct sndstat_userdev *ud =
|
|
malloc(sizeof(*ud), M_DEVBUF, M_WAITOK);
|
|
err = sndstat_dsp_unpack_nvlist(dsps[i], ud);
|
|
if (err) {
|
|
sx_unlock(&pf->lock);
|
|
goto done;
|
|
}
|
|
TAILQ_INSERT_TAIL(&pf->userdev_list, ud, link);
|
|
}
|
|
sx_unlock(&pf->lock);
|
|
|
|
done:
|
|
nvlist_destroy(nvl);
|
|
return (err);
|
|
}
|
|
|
|
static int
|
|
sndstat_flush_user_devs(struct sndstat_file *pf)
|
|
{
|
|
if ((pf->fflags & FWRITE) == 0)
|
|
return (EPERM);
|
|
|
|
sx_xlock(&pf->lock);
|
|
sndstat_remove_all_userdevs(pf);
|
|
sx_xunlock(&pf->lock);
|
|
|
|
return (0);
|
|
}
|
|
|
|
#ifdef COMPAT_FREEBSD32
|
|
static int
|
|
compat_sndstat_get_devs32(struct sndstat_file *pf, caddr_t data)
|
|
{
|
|
struct sndstioc_nv_arg32 *arg32 = (struct sndstioc_nv_arg32 *)data;
|
|
struct sndstioc_nv_arg arg;
|
|
int err;
|
|
|
|
arg.buf = (void *)(uintptr_t)arg32->buf;
|
|
arg.nbytes = arg32->nbytes;
|
|
|
|
err = sndstat_get_devs(pf, (caddr_t)&arg);
|
|
if (err == 0) {
|
|
arg32->buf = (uint32_t)(uintptr_t)arg.buf;
|
|
arg32->nbytes = arg.nbytes;
|
|
}
|
|
|
|
return (err);
|
|
}
|
|
|
|
static int
|
|
compat_sndstat_add_user_devs32(struct sndstat_file *pf, caddr_t data)
|
|
{
|
|
struct sndstioc_nv_arg32 *arg32 = (struct sndstioc_nv_arg32 *)data;
|
|
struct sndstioc_nv_arg arg;
|
|
int err;
|
|
|
|
arg.buf = (void *)(uintptr_t)arg32->buf;
|
|
arg.nbytes = arg32->nbytes;
|
|
|
|
err = sndstat_add_user_devs(pf, (caddr_t)&arg);
|
|
if (err == 0) {
|
|
arg32->buf = (uint32_t)(uintptr_t)arg.buf;
|
|
arg32->nbytes = arg.nbytes;
|
|
}
|
|
|
|
return (err);
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
sndstat_ioctl(
|
|
struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td)
|
|
{
|
|
int err;
|
|
struct sndstat_file *pf;
|
|
|
|
err = devfs_get_cdevpriv((void **)&pf);
|
|
if (err != 0)
|
|
return (err);
|
|
|
|
switch (cmd) {
|
|
case SNDSTIOC_GET_DEVS:
|
|
err = sndstat_get_devs(pf, data);
|
|
break;
|
|
#ifdef COMPAT_FREEBSD32
|
|
case SNDSTIOC_GET_DEVS32:
|
|
if (!SV_CURPROC_FLAG(SV_ILP32)) {
|
|
err = ENODEV;
|
|
break;
|
|
}
|
|
err = compat_sndstat_get_devs32(pf, data);
|
|
break;
|
|
#endif
|
|
case SNDSTIOC_ADD_USER_DEVS:
|
|
err = sndstat_add_user_devs(pf, data);
|
|
break;
|
|
#ifdef COMPAT_FREEBSD32
|
|
case SNDSTIOC_ADD_USER_DEVS32:
|
|
if (!SV_CURPROC_FLAG(SV_ILP32)) {
|
|
err = ENODEV;
|
|
break;
|
|
}
|
|
err = compat_sndstat_add_user_devs32(pf, data);
|
|
break;
|
|
#endif
|
|
case SNDSTIOC_REFRESH_DEVS:
|
|
err = sndstat_refresh_devs(pf);
|
|
break;
|
|
case SNDSTIOC_FLUSH_USER_DEVS:
|
|
err = sndstat_flush_user_devs(pf);
|
|
break;
|
|
default:
|
|
err = ENODEV;
|
|
}
|
|
|
|
return (err);
|
|
}
|
|
|
|
static struct sndstat_userdev *
|
|
sndstat_line2userdev(struct sndstat_file *pf, const char *line, int n)
|
|
{
|
|
struct sndstat_userdev *ud;
|
|
const char *e, *m;
|
|
|
|
ud = malloc(sizeof(*ud), M_DEVBUF, M_WAITOK|M_ZERO);
|
|
|
|
ud->provider = NULL;
|
|
ud->provider_nvl = NULL;
|
|
e = strchr(line, ':');
|
|
if (e == NULL)
|
|
goto fail;
|
|
ud->nameunit = strndup(line, e - line, M_DEVBUF);
|
|
ud->devnode = (char *)malloc(e - line + 1, M_DEVBUF, M_WAITOK | M_ZERO);
|
|
strlcat(ud->devnode, ud->nameunit, e - line + 1);
|
|
line = e + 1;
|
|
|
|
e = strchr(line, '<');
|
|
if (e == NULL)
|
|
goto fail;
|
|
line = e + 1;
|
|
e = strrchr(line, '>');
|
|
if (e == NULL)
|
|
goto fail;
|
|
ud->desc = strndup(line, e - line, M_DEVBUF);
|
|
line = e + 1;
|
|
|
|
e = strchr(line, '(');
|
|
if (e == NULL)
|
|
goto fail;
|
|
line = e + 1;
|
|
e = strrchr(line, ')');
|
|
if (e == NULL)
|
|
goto fail;
|
|
m = strstr(line, "play");
|
|
if (m != NULL && m < e)
|
|
ud->pchan = 1;
|
|
m = strstr(line, "rec");
|
|
if (m != NULL && m < e)
|
|
ud->rchan = 1;
|
|
|
|
return (ud);
|
|
|
|
fail:
|
|
free(ud->nameunit, M_DEVBUF);
|
|
free(ud->devnode, M_DEVBUF);
|
|
free(ud->desc, M_DEVBUF);
|
|
free(ud, M_DEVBUF);
|
|
return (NULL);
|
|
}
|
|
|
|
/************************************************************************/
|
|
|
|
int
|
|
sndstat_register(device_t dev, char *str, sndstat_handler handler)
|
|
{
|
|
struct sndstat_entry *ent;
|
|
struct sndstat_entry *pre;
|
|
const char *devtype;
|
|
int type, unit;
|
|
|
|
if (dev) {
|
|
unit = device_get_unit(dev);
|
|
devtype = device_get_name(dev);
|
|
if (!strcmp(devtype, "pcm"))
|
|
type = SS_TYPE_PCM;
|
|
else if (!strcmp(devtype, "midi"))
|
|
type = SS_TYPE_MIDI;
|
|
else if (!strcmp(devtype, "sequencer"))
|
|
type = SS_TYPE_SEQUENCER;
|
|
else
|
|
return (EINVAL);
|
|
} else {
|
|
type = SS_TYPE_MODULE;
|
|
unit = -1;
|
|
}
|
|
|
|
ent = malloc(sizeof *ent, M_DEVBUF, M_WAITOK | M_ZERO);
|
|
ent->dev = dev;
|
|
ent->str = str;
|
|
ent->type = type;
|
|
ent->unit = unit;
|
|
ent->handler = handler;
|
|
|
|
SNDSTAT_LOCK();
|
|
/* sorted list insertion */
|
|
TAILQ_FOREACH(pre, &sndstat_devlist, link) {
|
|
if (pre->unit > unit)
|
|
break;
|
|
else if (pre->unit < unit)
|
|
continue;
|
|
if (pre->type > type)
|
|
break;
|
|
else if (pre->type < unit)
|
|
continue;
|
|
}
|
|
if (pre == NULL) {
|
|
TAILQ_INSERT_TAIL(&sndstat_devlist, ent, link);
|
|
} else {
|
|
TAILQ_INSERT_BEFORE(pre, ent, link);
|
|
}
|
|
SNDSTAT_UNLOCK();
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
sndstat_registerfile(char *str)
|
|
{
|
|
return (sndstat_register(NULL, str, NULL));
|
|
}
|
|
|
|
int
|
|
sndstat_unregister(device_t dev)
|
|
{
|
|
struct sndstat_entry *ent;
|
|
int error = ENXIO;
|
|
|
|
SNDSTAT_LOCK();
|
|
TAILQ_FOREACH(ent, &sndstat_devlist, link) {
|
|
if (ent->dev == dev) {
|
|
TAILQ_REMOVE(&sndstat_devlist, ent, link);
|
|
free(ent, M_DEVBUF);
|
|
error = 0;
|
|
break;
|
|
}
|
|
}
|
|
SNDSTAT_UNLOCK();
|
|
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
sndstat_unregisterfile(char *str)
|
|
{
|
|
struct sndstat_entry *ent;
|
|
int error = ENXIO;
|
|
|
|
SNDSTAT_LOCK();
|
|
TAILQ_FOREACH(ent, &sndstat_devlist, link) {
|
|
if (ent->dev == NULL && ent->str == str) {
|
|
TAILQ_REMOVE(&sndstat_devlist, ent, link);
|
|
free(ent, M_DEVBUF);
|
|
error = 0;
|
|
break;
|
|
}
|
|
}
|
|
SNDSTAT_UNLOCK();
|
|
|
|
return (error);
|
|
}
|
|
|
|
/************************************************************************/
|
|
|
|
static int
|
|
sndstat_prepare(struct sndstat_file *pf_self)
|
|
{
|
|
struct sbuf *s = &pf_self->sbuf;
|
|
struct sndstat_entry *ent;
|
|
struct snddev_info *d;
|
|
struct sndstat_file *pf;
|
|
int k;
|
|
|
|
/* make sure buffer is reset */
|
|
sbuf_clear(s);
|
|
|
|
if (snd_verbose > 0) {
|
|
sbuf_printf(s, "FreeBSD Audio Driver (%ubit %d/%s)\n",
|
|
(u_int)sizeof(intpcm32_t) << 3, SND_DRV_VERSION,
|
|
MACHINE_ARCH);
|
|
}
|
|
|
|
/* generate list of installed devices */
|
|
k = 0;
|
|
TAILQ_FOREACH(ent, &sndstat_devlist, link) {
|
|
if (ent->dev == NULL)
|
|
continue;
|
|
d = device_get_softc(ent->dev);
|
|
if (!PCM_REGISTERED(d))
|
|
continue;
|
|
if (!k++)
|
|
sbuf_printf(s, "Installed devices:\n");
|
|
sbuf_printf(s, "%s:", device_get_nameunit(ent->dev));
|
|
sbuf_printf(s, " <%s>", device_get_desc(ent->dev));
|
|
if (snd_verbose > 0)
|
|
sbuf_printf(s, " %s", ent->str);
|
|
if (ent->handler) {
|
|
/* XXX Need Giant magic entry ??? */
|
|
PCM_ACQUIRE_QUICK(d);
|
|
ent->handler(s, ent->dev, snd_verbose);
|
|
PCM_RELEASE_QUICK(d);
|
|
}
|
|
sbuf_printf(s, "\n");
|
|
}
|
|
if (k == 0)
|
|
sbuf_printf(s, "No devices installed.\n");
|
|
|
|
/* append any input from userspace */
|
|
k = 0;
|
|
TAILQ_FOREACH(pf, &sndstat_filelist, entry) {
|
|
struct sndstat_userdev *ud;
|
|
|
|
if (pf == pf_self)
|
|
continue;
|
|
sx_xlock(&pf->lock);
|
|
if (TAILQ_EMPTY(&pf->userdev_list)) {
|
|
sx_unlock(&pf->lock);
|
|
continue;
|
|
}
|
|
if (!k++)
|
|
sbuf_printf(s, "Installed devices from userspace:\n");
|
|
TAILQ_FOREACH(ud, &pf->userdev_list, link) {
|
|
const char *caps = (ud->pchan && ud->rchan) ?
|
|
"play/rec" :
|
|
(ud->pchan ? "play" : (ud->rchan ? "rec" : ""));
|
|
sbuf_printf(s, "%s: <%s>", ud->nameunit, ud->desc);
|
|
sbuf_printf(s, " (%s)", caps);
|
|
sbuf_printf(s, "\n");
|
|
}
|
|
sx_unlock(&pf->lock);
|
|
}
|
|
if (k == 0)
|
|
sbuf_printf(s, "No devices installed from userspace.\n");
|
|
|
|
/* append any file versions */
|
|
if (snd_verbose >= 3) {
|
|
k = 0;
|
|
TAILQ_FOREACH(ent, &sndstat_devlist, link) {
|
|
if (ent->dev == NULL && ent->str != NULL) {
|
|
if (!k++)
|
|
sbuf_printf(s, "\nFile Versions:\n");
|
|
sbuf_printf(s, "%s\n", ent->str);
|
|
}
|
|
}
|
|
if (k == 0)
|
|
sbuf_printf(s, "\nNo file versions.\n");
|
|
}
|
|
sbuf_finish(s);
|
|
return (sbuf_len(s));
|
|
}
|
|
|
|
static void
|
|
sndstat_sysinit(void *p)
|
|
{
|
|
sx_init(&sndstat_lock, "sndstat lock");
|
|
sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS,
|
|
UID_ROOT, GID_WHEEL, 0644, "sndstat");
|
|
}
|
|
SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL);
|
|
|
|
static void
|
|
sndstat_sysuninit(void *p)
|
|
{
|
|
if (sndstat_dev != NULL) {
|
|
/* destroy_dev() will wait for all references to go away */
|
|
destroy_dev(sndstat_dev);
|
|
}
|
|
sx_destroy(&sndstat_lock);
|
|
}
|
|
SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL);
|