mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 11:25:21 +01:00
In advance of additional sound(4) work, remove EOL whitespace and mixed tab/space. Reviewed by: christos Differential Revision: https://reviews.freebsd.org/D60442
1454 lines
37 KiB
C
1454 lines
37 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*
|
|
* Copyright (c) 2005-2009 Ariff Abdullah <[email protected]>
|
|
* Copyright (c) 2001 Cameron Grant <[email protected]>
|
|
* Copyright (c) 2020 The FreeBSD Foundation
|
|
* All rights reserved.
|
|
* Copyright (c) 2024-2025 The FreeBSD Foundation
|
|
*
|
|
* Portions of this software were developed by Christos Margiolis
|
|
* <[email protected]> under sponsorship from the FreeBSD Foundation.
|
|
*
|
|
* 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/dnv.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/nv.h>
|
|
#include <sys/sndstat.h>
|
|
#include <sys/sx.h>
|
|
|
|
#include <dev/sound/pcm/sound.h>
|
|
#include <dev/sound/sndstat.h>
|
|
|
|
#include "feeder_if.h"
|
|
|
|
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;
|
|
enum sndstat_type type;
|
|
int 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_MPSAFE, 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);
|
|
|
|
sbuf_new(&pf->sbuf, NULL, 4096, SBUF_AUTOEXTEND);
|
|
|
|
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;
|
|
|
|
sx_assert(&pf->lock, SX_XLOCKED);
|
|
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, int dir, uint32_t *min_rate,
|
|
uint32_t *max_rate, uint32_t *fmts, uint32_t *minchn, uint32_t *maxchn)
|
|
{
|
|
struct pcm_channel *c;
|
|
struct pcmchan_caps *caps;
|
|
int i;
|
|
|
|
*fmts = 0;
|
|
*min_rate = UINT32_MAX;
|
|
*max_rate = 0;
|
|
*minchn = UINT32_MAX;
|
|
*maxchn = 0;
|
|
|
|
CHN_FOREACH(c, d, channels.pcm) {
|
|
if (c->direction != dir)
|
|
continue;
|
|
CHN_LOCK(c);
|
|
caps = chn_getcaps(c);
|
|
for (i = 0; caps->fmtlist[i]; i++) {
|
|
*fmts |= AFMT_ENCODING(caps->fmtlist[i]);
|
|
*minchn = min(AFMT_CHANNEL(caps->fmtlist[i]), *minchn);
|
|
*maxchn = max(AFMT_CHANNEL(caps->fmtlist[i]), *maxchn);
|
|
}
|
|
if ((c->flags & CHN_F_EXCLUSIVE) ||
|
|
(pcm_getflags(d->dev) & SD_F_BITPERFECT)) {
|
|
*min_rate = min(*min_rate, caps->minspeed);
|
|
*max_rate = max(*max_rate, caps->maxspeed);
|
|
} else {
|
|
*min_rate = min(*min_rate, feeder_rate_min);
|
|
*max_rate = max(*max_rate, feeder_rate_max);
|
|
}
|
|
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)
|
|
{
|
|
struct pcm_channel *c;
|
|
struct pcm_feeder *f;
|
|
struct sbuf sb;
|
|
uint32_t maxrate, minrate, fmts, minchn, maxchn, caps;
|
|
nvlist_t *di = NULL, *sound4di = NULL, *diinfo = NULL, *cdi = NULL;
|
|
int err, nchan;
|
|
char buf[AFMTSTR_LEN];
|
|
|
|
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, PCMDIR_PLAY, &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, PCMDIR_REC, &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_string(sound4di, SNDST_DSPS_SOUND4_STATUS, d->status);
|
|
nvlist_add_bool(
|
|
sound4di, SNDST_DSPS_SOUND4_BITPERFECT, d->flags & SD_F_BITPERFECT);
|
|
nvlist_add_bool(sound4di, SNDST_DSPS_SOUND4_PVCHAN,
|
|
d->flags & SD_F_PVCHANS);
|
|
nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_PVCHANRATE,
|
|
d->pvchanrate);
|
|
nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_PVCHANFORMAT,
|
|
d->pvchanformat);
|
|
nvlist_add_bool(sound4di, SNDST_DSPS_SOUND4_RVCHAN,
|
|
d->flags & SD_F_RVCHANS);
|
|
nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_RVCHANRATE,
|
|
d->rvchanrate);
|
|
nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_RVCHANFORMAT,
|
|
d->rvchanformat);
|
|
|
|
nchan = 0;
|
|
CHN_FOREACH(c, d, channels.pcm) {
|
|
sbuf_new(&sb, NULL, 4096, SBUF_AUTOEXTEND);
|
|
cdi = nvlist_create(0);
|
|
if (cdi == NULL) {
|
|
sbuf_delete(&sb);
|
|
PCM_RELEASE_QUICK(d);
|
|
err = ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
CHN_LOCK(c);
|
|
|
|
caps = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER |
|
|
((c->flags & CHN_F_VIRTUAL) ? PCM_CAP_VIRTUAL : 0) |
|
|
((c->direction == PCMDIR_PLAY) ? PCM_CAP_OUTPUT : PCM_CAP_INPUT);
|
|
|
|
nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_NAME, c->name);
|
|
nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_PARENTCHAN,
|
|
c->parentchannel != NULL ? c->parentchannel->name : "");
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_UNIT, nchan++);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_CAPS, caps);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_LATENCY,
|
|
c->latency);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_RATE, c->speed);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_FORMAT,
|
|
c->format);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_PID, c->pid);
|
|
nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_COMM, c->comm);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_INTR,
|
|
c->interrupts);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_FEEDCNT,
|
|
c->feedcount);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_XRUNS, c->xruns);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_LEFTVOL,
|
|
chn_getvolume_matrix(c, SND_VOL_C_PCM, SND_CHN_T_FL));
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_RIGHTVOL,
|
|
chn_getvolume_matrix(c, SND_VOL_C_PCM, SND_CHN_T_FR));
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_FORMAT,
|
|
c->bufhard->fmt);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_RATE,
|
|
c->bufhard->spd);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_SIZE,
|
|
c->bufhard->bufsize);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_BLKSZ,
|
|
c->bufhard->blksz);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_BLKCNT,
|
|
c->bufhard->blkcnt);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_FREE,
|
|
sndbuf_getfree(c->bufhard));
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_READY,
|
|
sndbuf_getready(c->bufhard));
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_FORMAT,
|
|
c->bufsoft->fmt);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_RATE,
|
|
c->bufsoft->spd);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_SIZE,
|
|
c->bufsoft->bufsize);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_BLKSZ,
|
|
c->bufsoft->blksz);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_BLKCNT,
|
|
c->bufsoft->blkcnt);
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_FREE,
|
|
sndbuf_getfree(c->bufsoft));
|
|
nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_READY,
|
|
sndbuf_getready(c->bufsoft));
|
|
|
|
if (c->parentchannel != NULL) {
|
|
sbuf_printf(&sb, "[%s", (c->direction == PCMDIR_REC) ?
|
|
c->parentchannel->name : "userland");
|
|
} else {
|
|
sbuf_printf(&sb, "[%s", (c->direction == PCMDIR_REC) ?
|
|
"hardware" :
|
|
((d->flags & SD_F_PVCHANS) ? "vchans" : "userland"));
|
|
}
|
|
sbuf_printf(&sb, " -> ");
|
|
f = c->feeder;
|
|
while (f->source != NULL)
|
|
f = f->source;
|
|
while (f != NULL) {
|
|
sbuf_printf(&sb, "%s", f->class->name);
|
|
if (f->class->type == FEEDER_FORMAT) {
|
|
snd_afmt2str(f->desc.in, buf, sizeof(buf));
|
|
sbuf_printf(&sb, "(%s -> ", buf);
|
|
snd_afmt2str(f->desc.out, buf, sizeof(buf));
|
|
sbuf_printf(&sb, "%s)", buf);
|
|
} else if (f->class->type == FEEDER_MATRIX) {
|
|
sbuf_printf(&sb, "(%d.%dch -> %d.%dch)",
|
|
AFMT_CHANNEL(f->desc.in) -
|
|
AFMT_EXTCHANNEL(f->desc.in),
|
|
AFMT_EXTCHANNEL(f->desc.in),
|
|
AFMT_CHANNEL(f->desc.out) -
|
|
AFMT_EXTCHANNEL(f->desc.out),
|
|
AFMT_EXTCHANNEL(f->desc.out));
|
|
} else if (f->class->type == FEEDER_RATE) {
|
|
sbuf_printf(&sb, "(%d -> %d)",
|
|
FEEDER_GET(f, FEEDRATE_SRC),
|
|
FEEDER_GET(f, FEEDRATE_DST));
|
|
} else {
|
|
snd_afmt2str(f->desc.out, buf, sizeof(buf));
|
|
sbuf_printf(&sb, "(%s)", buf);
|
|
}
|
|
sbuf_printf(&sb, " -> ");
|
|
f = f->parent;
|
|
}
|
|
if (c->parentchannel != NULL) {
|
|
sbuf_printf(&sb, "%s]", (c->direction == PCMDIR_REC) ?
|
|
"userland" : c->parentchannel->name);
|
|
} else {
|
|
sbuf_printf(&sb, "%s]", (c->direction == PCMDIR_REC) ?
|
|
((d->flags & SD_F_RVCHANS) ? "vchans" : "userland") :
|
|
"hardware");
|
|
}
|
|
|
|
CHN_UNLOCK(c);
|
|
|
|
sbuf_finish(&sb);
|
|
nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_FEEDERCHAIN,
|
|
sbuf_data(&sb));
|
|
sbuf_delete(&sb);
|
|
|
|
nvlist_append_nvlist_array(sound4di,
|
|
SNDST_DSPS_SOUND4_CHAN_INFO, cdi);
|
|
nvlist_destroy(cdi);
|
|
err = nvlist_error(sound4di);
|
|
if (err) {
|
|
PCM_RELEASE_QUICK(d);
|
|
goto done;
|
|
}
|
|
}
|
|
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) {
|
|
if (ent->type == SNDST_TYPE_PCM) {
|
|
struct snddev_info *d;
|
|
nvlist_t *di;
|
|
|
|
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;
|
|
} else if (ent->type == SNDST_TYPE_MIDI) {
|
|
/* TODO */
|
|
}
|
|
}
|
|
|
|
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, void *arg_buf, size_t *arg_nbytes)
|
|
{
|
|
int err;
|
|
|
|
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 == 0) {
|
|
*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, void *nvlbuf, size_t nbytes)
|
|
{
|
|
int err;
|
|
nvlist_t *nvl = NULL;
|
|
const nvlist_t * const *dsps;
|
|
size_t i, ndsps;
|
|
|
|
if ((pf->fflags & FWRITE) == 0) {
|
|
err = EPERM;
|
|
goto done;
|
|
}
|
|
|
|
if (nbytes > SNDST_UNVLBUF_MAX) {
|
|
err = ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
err = sndstat_unpack_user_nvlbuf(nvlbuf, 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);
|
|
}
|
|
|
|
static int
|
|
sndstat_ioctl(struct cdev *dev, unsigned long cmd, caddr_t data, int fflag,
|
|
struct thread *td)
|
|
{
|
|
int err;
|
|
struct sndstat_file *pf;
|
|
struct sndstioc_nv_arg *arg;
|
|
#ifdef COMPAT_FREEBSD32
|
|
struct sndstioc_nv_arg32 *arg32;
|
|
size_t nbytes;
|
|
#endif
|
|
|
|
err = devfs_get_cdevpriv((void **)&pf);
|
|
if (err != 0)
|
|
return (err);
|
|
|
|
switch (cmd) {
|
|
case SNDSTIOC_GET_DEVS:
|
|
arg = (struct sndstioc_nv_arg *)data;
|
|
err = sndstat_get_devs(pf, arg->buf, &arg->nbytes);
|
|
break;
|
|
#ifdef COMPAT_FREEBSD32
|
|
case SNDSTIOC_GET_DEVS32:
|
|
arg32 = (struct sndstioc_nv_arg32 *)data;
|
|
nbytes = arg32->nbytes;
|
|
err = sndstat_get_devs(pf, (void *)(uintptr_t)arg32->buf,
|
|
&nbytes);
|
|
if (err == 0) {
|
|
KASSERT(nbytes < UINT_MAX, ("impossibly many bytes"));
|
|
arg32->nbytes = nbytes;
|
|
}
|
|
break;
|
|
#endif
|
|
case SNDSTIOC_ADD_USER_DEVS:
|
|
arg = (struct sndstioc_nv_arg *)data;
|
|
err = sndstat_add_user_devs(pf, arg->buf, arg->nbytes);
|
|
break;
|
|
#ifdef COMPAT_FREEBSD32
|
|
case SNDSTIOC_ADD_USER_DEVS32:
|
|
arg32 = (struct sndstioc_nv_arg32 *)data;
|
|
err = sndstat_add_user_devs(pf, (void *)(uintptr_t)arg32->buf,
|
|
arg32->nbytes);
|
|
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 = 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);
|
|
}
|
|
|
|
/************************************************************************/
|
|
|
|
void
|
|
sndstat_register(device_t dev, enum sndstat_type type, char *str)
|
|
{
|
|
struct sndstat_entry *ent;
|
|
struct sndstat_entry *pre;
|
|
int unit;
|
|
|
|
unit = device_get_unit(dev);
|
|
|
|
ent = malloc(sizeof *ent, M_DEVBUF, M_WAITOK | M_ZERO);
|
|
ent->dev = dev;
|
|
ent->str = str;
|
|
ent->type = type;
|
|
ent->unit = unit;
|
|
|
|
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();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/************************************************************************/
|
|
|
|
static int
|
|
sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose)
|
|
{
|
|
struct snddev_info *d;
|
|
struct pcm_channel *c;
|
|
struct pcm_feeder *f;
|
|
|
|
d = device_get_softc(dev);
|
|
PCM_BUSYASSERT(d);
|
|
|
|
if (CHN_EMPTY(d, channels.pcm)) {
|
|
sbuf_printf(s, " (mixer only)");
|
|
return (0);
|
|
}
|
|
|
|
if (verbose < 1) {
|
|
sbuf_printf(s, " (%s%s%s",
|
|
d->playcount ? "play" : "",
|
|
(d->playcount && d->reccount) ? "/" : "",
|
|
d->reccount ? "rec" : "");
|
|
} else {
|
|
sbuf_printf(s, " (%dp:%dv/%dr:%dv",
|
|
d->playcount, d->pvchancount,
|
|
d->reccount, d->rvchancount);
|
|
}
|
|
sbuf_printf(s, "%s)%s",
|
|
((d->playcount != 0 && d->reccount != 0) &&
|
|
(d->flags & SD_F_SIMPLEX)) ? " simplex" : "",
|
|
(device_get_unit(dev) == snd_unit) ? " default" : "");
|
|
|
|
if (verbose <= 1)
|
|
return (0);
|
|
|
|
sbuf_printf(s, "\n\t");
|
|
sbuf_printf(s, "snddev flags=0x%b", d->flags, SD_F_BITS);
|
|
|
|
CHN_FOREACH(c, d, channels.pcm) {
|
|
KASSERT(c->bufhard != NULL && c->bufsoft != NULL,
|
|
("hosed pcm channel setup"));
|
|
|
|
CHN_LOCK(c);
|
|
|
|
sbuf_printf(s, "\n\t");
|
|
|
|
sbuf_printf(s, "%s[%s]: ",
|
|
(c->parentchannel != NULL) ?
|
|
c->parentchannel->name : "", c->name);
|
|
sbuf_printf(s, "spd %d", c->speed);
|
|
if (c->speed != c->bufhard->spd)
|
|
sbuf_printf(s, "/%d", c->bufhard->spd);
|
|
sbuf_printf(s, ", fmt 0x%08x", c->format);
|
|
if (c->format != c->bufhard->fmt) {
|
|
sbuf_printf(s, "/0x%08x", c->bufhard->fmt);
|
|
}
|
|
sbuf_printf(s, ", flags 0x%08x, 0x%08x",
|
|
c->flags, c->feederflags);
|
|
if (c->pid != -1) {
|
|
sbuf_printf(s, ", pid %d (%s)",
|
|
c->pid, c->comm);
|
|
}
|
|
sbuf_printf(s, "\n\t");
|
|
|
|
sbuf_printf(s, "\tinterrupts %d, ", c->interrupts);
|
|
|
|
if (c->direction == PCMDIR_REC) {
|
|
sbuf_printf(s,
|
|
"overruns %d, feed %u, hfree %d, "
|
|
"sfree %d\n\t\t[b:%d/%d/%d|bs:%d/%d/%d]",
|
|
c->xruns, c->feedcount,
|
|
sndbuf_getfree(c->bufhard),
|
|
sndbuf_getfree(c->bufsoft),
|
|
c->bufhard->bufsize,
|
|
c->bufhard->blksz,
|
|
c->bufhard->blkcnt,
|
|
c->bufsoft->bufsize,
|
|
c->bufsoft->blksz,
|
|
c->bufsoft->blkcnt);
|
|
} else {
|
|
sbuf_printf(s,
|
|
"underruns %d, feed %u, ready %d "
|
|
"\n\t\t[b:%d/%d/%d|bs:%d/%d/%d]",
|
|
c->xruns, c->feedcount,
|
|
sndbuf_getready(c->bufsoft),
|
|
c->bufhard->bufsize,
|
|
c->bufhard->blksz,
|
|
c->bufhard->blkcnt,
|
|
c->bufsoft->bufsize,
|
|
c->bufsoft->blksz,
|
|
c->bufsoft->blkcnt);
|
|
}
|
|
sbuf_printf(s, "\n\t");
|
|
|
|
sbuf_printf(s, "\tchannel flags=0x%b", c->flags, CHN_F_BITS);
|
|
sbuf_printf(s, "\n\t");
|
|
|
|
if (c->parentchannel != NULL) {
|
|
sbuf_printf(s, "\t{%s}", (c->direction == PCMDIR_REC) ?
|
|
c->parentchannel->name : "userland");
|
|
} else {
|
|
sbuf_printf(s, "\t{%s}", (c->direction == PCMDIR_REC) ?
|
|
"hardware" :
|
|
((d->flags & SD_F_PVCHANS) ? "vchans" : "userland"));
|
|
}
|
|
sbuf_printf(s, " -> ");
|
|
f = c->feeder;
|
|
while (f->source != NULL)
|
|
f = f->source;
|
|
while (f != NULL) {
|
|
sbuf_printf(s, "%s", f->class->name);
|
|
if (f->class->type == FEEDER_FORMAT) {
|
|
sbuf_printf(s, "(0x%08x -> 0x%08x)",
|
|
f->desc.in, f->desc.out);
|
|
} else if (f->class->type == FEEDER_MATRIX) {
|
|
sbuf_printf(s, "(%d.%d -> %d.%d)",
|
|
AFMT_CHANNEL(f->desc.in) -
|
|
AFMT_EXTCHANNEL(f->desc.in),
|
|
AFMT_EXTCHANNEL(f->desc.in),
|
|
AFMT_CHANNEL(f->desc.out) -
|
|
AFMT_EXTCHANNEL(f->desc.out),
|
|
AFMT_EXTCHANNEL(f->desc.out));
|
|
} else if (f->class->type == FEEDER_RATE) {
|
|
sbuf_printf(s,
|
|
"(0x%08x q:%d %d -> %d)",
|
|
f->desc.out,
|
|
FEEDER_GET(f, FEEDRATE_QUALITY),
|
|
FEEDER_GET(f, FEEDRATE_SRC),
|
|
FEEDER_GET(f, FEEDRATE_DST));
|
|
} else {
|
|
sbuf_printf(s, "(0x%08x)",
|
|
f->desc.out);
|
|
}
|
|
sbuf_printf(s, " -> ");
|
|
f = f->parent;
|
|
}
|
|
if (c->parentchannel != NULL) {
|
|
sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC) ?
|
|
"userland" : c->parentchannel->name);
|
|
} else {
|
|
sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC) ?
|
|
((d->flags & SD_F_RVCHANS) ? "vchans" : "userland") :
|
|
"hardware");
|
|
}
|
|
|
|
CHN_UNLOCK(c);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
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\n");
|
|
|
|
/* generate list of installed devices */
|
|
k = 0;
|
|
TAILQ_FOREACH(ent, &sndstat_devlist, link) {
|
|
if (ent->type == SNDST_TYPE_PCM) {
|
|
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);
|
|
/* XXX Need Giant magic entry ??? */
|
|
PCM_ACQUIRE_QUICK(d);
|
|
sndstat_prepare_pcm(s, ent->dev, snd_verbose);
|
|
PCM_RELEASE_QUICK(d);
|
|
sbuf_printf(s, "\n");
|
|
} else if (ent->type == SNDST_TYPE_MIDI) {
|
|
/* TODO */
|
|
}
|
|
}
|
|
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");
|
|
|
|
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, 0, UID_ROOT, GID_AUDIO, 0640,
|
|
"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);
|