aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--share/man/man4/Makefile1
-rw-r--r--share/man/man4/sndstat.4259
-rw-r--r--sys/dev/sound/pcm/sndstat.c738
-rw-r--r--sys/dev/sound/pcm/sound.h1
-rw-r--r--sys/sys/param.h2
-rw-r--r--sys/sys/sndstat.h95
6 files changed, 1086 insertions, 10 deletions
diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index 686e86cf6897..54fd89fe7590 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -526,6 +526,7 @@ MAN= aac.4 \
snd_via8233.4 \
snd_via82c686.4 \
snd_vibes.4 \
+ sndstat.4 \
snp.4 \
spigen.4 \
${_spkr.4} \
diff --git a/share/man/man4/sndstat.4 b/share/man/man4/sndstat.4
new file mode 100644
index 000000000000..ad5f4a76ea7e
--- /dev/null
+++ b/share/man/man4/sndstat.4
@@ -0,0 +1,259 @@
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+.\"
+.\" This software was developed by Ka Ho Ng
+.\" under sponsorship from the FreeBSD Foundation.
+.\"
+.\" Copyright (c) 2020 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.
+.\"
+.\" $FreeBSD$
+.\"
+.\" Note: The date here should be updated whenever a non-trivial
+.\" change is made to the manual page.
+.Dd December 7, 2020
+.Dt SNDSTAT 4
+.Os
+.Sh NAME
+.Nm sndstat
+.Nd "nvlist-based PCM audio device enumeration interface"
+.Sh SYNOPSIS
+To compile the driver into the kernel,
+place the following lines in the
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device sound"
+.Ed
+.Sh DESCRIPTION
+The ioctl interface provided by
+.Pa /dev/sndstat
+device allows callers to enumeration PCM audio devices available for use.
+.Sh IOCTLS
+For all ioctls requiring data exchange between the subsystem and callers,
+the following structures are used to describe a serialized nvlist:
+.Bd -literal -offset indent
+struct sndstat_nvlbuf_arg {
+ size_t nbytes;
+ void *buf;
+};
+.Ed
+.Pp
+Here is an example of an nvlist, with explanations of the common fields:
+.Bd -literal -offset indent
+dsps (NVLIST ARRAY): 1
+ from_user (BOOL): FALSE
+ nameunit (STRING): [pcm0]
+ devnode (STRING): [dsp0]
+ desc (STRING): [Generic (0x8086) (Analog Line-out)]
+ pchan (NUMBER): 1 (1) (0x1)
+ rchan (NUMBER): 0 (0) (0x0)
+ pminrate (NUMBER): 48000 (48000) (0xbb80)
+ pmaxrate (NUMBER): 48000 (48000) (0xbb80)
+ pfmts (NUMBER): 2097168 (2097168) (0x200010)
+ provider_info (NVLIST):
+ unit (NUMBER): 0 (0) (0x0)
+ bitperfect (BOOL): FALSE
+ pvchan (NUMBER): 1 (1) (0x1)
+ rvchan (NUMBER): 0 (0) (0x0)
+ provider (STRING): [sound(4)]
+ ,
+.Ed
+.Bl -tag -width ".Dv provider_info"
+.It Dv from_user
+Whether the PCM audio device node is created by in-kernel audio subsystem or
+userspace providers.
+.It Dv nameunit
+The device identification in the form of subsystem plus a unit number.
+.It Dv devnode
+The PCM audio device node relative path in devfs.
+.It Dv desc
+The descripton of the PCM audio device.
+.It Dv pchan
+The number of playback channels supported by hardware.
+This can be 0 if this PCM audio device does not support playback at all.
+.It Dv rchan
+The number of recording channels supported by hardware.
+This can be 0 if this PCM audio device does not support recording at all.
+.It Dv pminrate
+The minimum supported playback direction sampling rate.
+Only exists if pchan is greater than 0.
+.It Dv pmaxrate
+The maximum supported playback direction sampling rate.
+Only exists if pchan is greater than 0.
+.It Dv pfmts
+The supported playback direction sample format.
+Only exists if pchan is greater than 0.
+.It Dv rminrate
+The minimum supported recording direction sampling rate.
+Only exists if rchan is greater than 0.
+.It Dv rmaxrate
+The maximum supported recording direction sampling rate.
+Only exists if rchan is greater than 0.
+.It Dv rfmts
+The supported playback recording sample format.
+Only exists if rchan is greater than 0.
+.It Dv provider_info
+Provider-specific fields.
+This field may not exist if the PCM audio device is not provided by in-kernel
+interface.
+This field will not exist if the provider field is an empty string.
+.It Dv provider
+A string specifying the provider of the PCm audio device.
+.El
+.Pp
+The following ioctls are providede for use:
+.Bl -tag -width ".Dv SNDSTAT_FLUSH_USER_DEVS"
+.It Dv SNDSTAT_REFRESH_DEVS
+Drop any previously fetched PCM audio devices list snapshots.
+This ioctl takes no arguments.
+.It Dv SNDSTAT_GET_DEVS
+Generate and/or return PCM audio devices list snapshots to callers.
+This ioctl takes a pointer to
+.Fa struct sndstat_nvlbuf_arg
+as the first and the only argument.
+Callers need to provide a sufficiently large buffer to hold a serialized
+nvlist.
+If there is no existing PCM audio device list snapshot available in the
+internal structure of the opened sndstat.
+.Fa fd ,
+a new PCM audio device list snapshot will be automatically generated.
+Callers have to set
+.Fa nbytes
+to either 0 or the size of buffer provided.
+In case
+.Fa nbytes
+is 0, the buffer size required to hold a serialized nvlist
+stream of current snapshot will be returned in
+.Fa nbytes ,
+and
+.Fa buf
+will be ignored.
+Otherwise, if the buffer is not sufficiently large,
+the ioctl returns success, and
+.Fa nbytes
+will be set to 0.
+If the buffer provided is sufficiently large,
+.Fa nbytes
+will be set to the size of the serialized nvlist written to the provided buffer.
+Once a PCM audio device list snapshot is returned to user-space successfully,
+the snapshot stored in the subsystem's internal structure of the given
+.Fa fd
+will be freed.
+.It Dv SNDSTAT_ADD_USER_DEVS
+Add a list of PCM audio devices provided by callers to
+.Pa /dev/sndstat
+device.
+This ioctl takes a pointer to
+.Fa struct sndstat_nvlbuf_arg
+as the first and the only argument.
+Callers have to provide a buffer holding a serialized nvlist.
+.Fa nbytes
+should be set to the length in bytes of the serialized nvlist.
+.Fa buf
+should be pointed to a buffer storing the serialized nvlist.
+Userspace-backed PCM audio device nodes should be listed inside the serialized
+nvlist.
+.It Dv SNDSTAT_FLUSH_USER_DEVS
+Flush any PCM audio devices previously added by callers.
+This ioctl takes no arguments.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /dev/sndstat" -compact
+.It Pa /dev/sndstat
+.El
+.Sh EXAMPLES
+The following code enumerates all available PCM audio devices:
+.Bd -literal -offset indent
+#include <sys/types.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/nv.h>
+#include <sys/sndstat.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+int
+main()
+{
+ int fd;
+ struct sndstat_nvlbuf_arg arg;
+ const nvlist_t * const *di;
+ size_t i, nitems;
+ nvlist_t *nvl;
+
+ /* Open sndstat node in read-only first */
+ fd = open("/dev/sndstat", O_RDONLY);
+
+ if (ioctl(fd, SNDSTAT_REFRESH_DEVS, NULL))
+ err(1, "ioctl(fd, SNDSTAT_REFRESH_DEVS, NULL)");
+
+ /* Get the size of snapshot, when nbytes = 0 */
+ arg.nbytes = 0;
+ arg.buf = NULL;
+ if (ioctl(fd, SNDSTAT_GET_DEVS, &arg))
+ err(1, "ioctl(fd, SNDSTAT_GET_DEVS, &arg)");
+
+ /* Get snapshot data */
+ arg.buf = malloc(arg.nbytes);
+ if (arg.buf == NULL)
+ err(EX_OSERR, "malloc");
+ if (ioctl(fd, SNDSTAT_GET_DEVS, &arg))
+ err(1, "ioctl(fd, SNDSTAT_GET_DEVS, &arg)");
+
+ /* Deserialize the nvlist stream */
+ nvl = nvlist_unpack(arg.buf, arg.nbytes, 0);
+ free(arg.buf);
+
+ /* Get DSPs array */
+ di = nvlist_get_nvlist_array(nvl, SNDSTAT_LABEL_DSPS, &nitems);
+ for (i = 0; i < nitems; i++) {
+ const char *nameunit, *devnode, *desc;
+
+ /*
+ * Examine each device nvlist item
+ */
+
+ nameunit = nvlist_get_string(di[i], SNDSTAT_LABEL_NAMEUNIT);
+ devnode = nvlist_get_string(di[i], SNDSTAT_LABEL_DEVNODE);
+ desc = nvlist_get_string(di[i], SNDSTAT_LABEL_DESC);
+ printf("Name unit: `%s`, Device node: `%s`, Description: `%s`\n",
+ nameunit, devnode, desc);
+ }
+
+ nvlist_destroy(nvl);
+ return (0);
+}
+.Ed
+.Sh SEE ALSO
+.Xr sound 4 ,
+.Xr nv 9
+.Sh HISTORY
+The nvlist-based ioctls support for
+.Nm
+device first appeared in
+.Fx 13.0 .
+.Sh AUTHORS
+This manual page was written by
+.An Ka Ho Ng Aq Mt khng@FreeBSD.org .
diff --git a/sys/dev/sound/pcm/sndstat.c b/sys/dev/sound/pcm/sndstat.c
index 31c4a1b14a79..e89af772fe72 100644
--- a/sys/dev/sound/pcm/sndstat.c
+++ b/sys/dev/sound/pcm/sndstat.c
@@ -3,8 +3,12 @@
*
* 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:
@@ -31,10 +35,20 @@
#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>
-#include <sys/sx.h>
+
SND_DECLARE_FILE("$FreeBSD$");
@@ -47,12 +61,14 @@ 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,
};
@@ -65,11 +81,33 @@ struct sndstat_entry {
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;
+ uint32_t pminrate;
+ uint32_t pmaxrate;
+ uint32_t rminrate;
+ uint32_t rmaxrate;
+ uint32_t pfmts;
+ uint32_t rfmts;
+ 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;
@@ -84,6 +122,8 @@ static TAILQ_HEAD(, sndstat_file) sndstat_filelist = TAILQ_HEAD_INITIALIZER(snds
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)
@@ -112,12 +152,16 @@ sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
pf = malloc(sizeof(*pf), M_DEVBUF, M_WAITOK | M_ZERO);
- SNDSTAT_LOCK();
if (sbuf_new(&pf->sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) {
- SNDSTAT_UNLOCK();
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();
@@ -126,6 +170,29 @@ sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
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)
{
@@ -136,6 +203,12 @@ sndstat_close(void *sndstat_file)
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);
}
@@ -203,7 +276,10 @@ sndstat_write(struct cdev *i_dev, struct uio *buf, int flag)
err = EINVAL;
} else {
/* only remember the last write - allows for updates */
- sbuf_clear(&pf->sbuf);
+ sx_xlock(&pf->lock);
+ sndstat_remove_all_userdevs(pf);
+ sx_xunlock(&pf->lock);
+
while (1) {
len = sizeof(temp);
if (len > buf->uio_resid)
@@ -221,15 +297,647 @@ sndstat_write(struct cdev *i_dev, struct uio *buf, int flag)
}
}
sbuf_finish(&pf->sbuf);
- if (err == 0)
+
+ 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
+ } 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)
+{
+ struct pcm_channel *c;
+ int dir;
+
+ dir = play ? PCMDIR_PLAY : PCMDIR_REC;
+ *min_rate = 0;
+ *max_rate = 0;
+ *fmts = 0;
+
+ if (play && d->pvchancount > 0) {
+ *min_rate = *max_rate = d->pvchanrate;
+ *fmts = d->pvchanformat;
+ return;
+ } else if (!play && d->rvchancount > 0) {
+ *min_rate = *max_rate = d->rvchanrate;
+ *fmts = d->rvchanformat;
+ return;
+ }
+
+ CHN_FOREACH(c, d, channels.pcm) {
+ struct pcmchan_caps *caps;
+
+ if (c->direction != dir || (c->flags & CHN_F_VIRTUAL) != 0)
+ continue;
+
+ CHN_LOCK(c);
+ caps = chn_getcaps(c);
+ *min_rate = caps->minspeed;
+ *max_rate = caps->maxspeed;
+ *fmts = chn_getformats(c);
+ CHN_UNLOCK(c);
+ }
+}
+
+static int
+sndstat_build_sound4_nvlist(struct snddev_info *d, nvlist_t **dip)
+{
+ uint32_t maxrate, minrate, fmts;
+ nvlist_t *di = NULL, *sound4di = 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, SNDSTAT_LABEL_FROM_USER, false);
+ nvlist_add_stringf(di, SNDSTAT_LABEL_NAMEUNIT, "%s",
+ device_get_nameunit(d->dev));
+ nvlist_add_stringf(di, SNDSTAT_LABEL_DEVNODE, "dsp%d",
+ device_get_unit(d->dev));
+ nvlist_add_string(
+ di, SNDSTAT_LABEL_DESC, device_get_desc(d->dev));
+
+ PCM_ACQUIRE_QUICK(d);
+ nvlist_add_number(di, SNDSTAT_LABEL_PCHAN, d->playcount);
+ nvlist_add_number(di, SNDSTAT_LABEL_RCHAN, d->reccount);
+ if (d->playcount > 0) {
+ sndstat_get_caps(d, true, &minrate, &maxrate, &fmts);
+ nvlist_add_number(di, SNDSTAT_LABEL_PMINRATE, minrate);
+ nvlist_add_number(di, SNDSTAT_LABEL_PMAXRATE, maxrate);
+ nvlist_add_number(di, SNDSTAT_LABEL_PFMTS, fmts);
+ }
+ if (d->reccount > 0) {
+ sndstat_get_caps(d, false, &minrate, &maxrate, &fmts);
+ nvlist_add_number(di, SNDSTAT_LABEL_RMINRATE, minrate);
+ nvlist_add_number(di, SNDSTAT_LABEL_RMAXRATE, maxrate);
+ nvlist_add_number(di, SNDSTAT_LABEL_RFMTS, fmts);
+ }
+
+ nvlist_add_number(sound4di, SNDSTAT_LABEL_SOUND4_UNIT,
+ device_get_unit(d->dev)); // XXX: I want signed integer here
+ nvlist_add_bool(
+ sound4di, SNDSTAT_LABEL_SOUND4_BITPERFECT, d->flags & SD_F_BITPERFECT);
+ nvlist_add_number(sound4di, SNDSTAT_LABEL_SOUND4_PVCHAN, d->pvchancount);
+ nvlist_add_number(sound4di, SNDSTAT_LABEL_SOUND4_RVCHAN, d->rvchancount);
+ nvlist_move_nvlist(di, SNDSTAT_LABEL_PROVIDER_INFO, sound4di);
+ sound4di = NULL;
+ PCM_RELEASE_QUICK(d);
+ nvlist_add_string(di, SNDSTAT_LABEL_PROVIDER, SNDSTAT_LABEL_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;
+ int err;
+
+ di = nvlist_create(0);
+ if (di == NULL) {
+ err = ENOMEM;
+ goto done;
+ }
+
+ nvlist_add_bool(di, SNDSTAT_LABEL_FROM_USER, true);
+ nvlist_add_number(di, SNDSTAT_LABEL_PCHAN, ud->pchan);
+ nvlist_add_number(di, SNDSTAT_LABEL_RCHAN, ud->rchan);
+ nvlist_add_string(di, SNDSTAT_LABEL_NAMEUNIT, ud->nameunit);
+ nvlist_add_string(
+ di, SNDSTAT_LABEL_DEVNODE, ud->devnode);
+ nvlist_add_string(di, SNDSTAT_LABEL_DESC, ud->desc);
+ if (ud->pchan != 0) {
+ nvlist_add_number(
+ di, SNDSTAT_LABEL_PMINRATE, ud->pminrate);
+ nvlist_add_number(
+ di, SNDSTAT_LABEL_PMAXRATE, ud->pmaxrate);
+ nvlist_add_number(
+ di, SNDSTAT_LABEL_PFMTS, ud->pfmts);
+ }
+ if (ud->rchan != 0) {
+ nvlist_add_number(
+ di, SNDSTAT_LABEL_RMINRATE, ud->rminrate);
+ nvlist_add_number(
+ di, SNDSTAT_LABEL_RMAXRATE, ud->rmaxrate);
+ nvlist_add_number(
+ di, SNDSTAT_LABEL_RFMTS, ud->rfmts);
+ }
+ nvlist_add_string(di, SNDSTAT_LABEL_PROVIDER,
+ (ud->provider != NULL) ? ud->provider : "");
+ if (ud->provider_nvl != NULL)
+ nvlist_add_nvlist(
+ di, SNDSTAT_LABEL_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, SNDSTAT_LABEL_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, SNDSTAT_LABEL_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 sndstat_nvlbuf_arg *arg = (struct sndstat_nvlbuf_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_dsp_nvlist_is_sane(const nvlist_t *nvlist)
+{
+ if (!(nvlist_exists_string(nvlist, SNDSTAT_LABEL_DEVNODE) &&
+ nvlist_exists_string(nvlist, SNDSTAT_LABEL_DESC) &&
+ nvlist_exists_number(nvlist, SNDSTAT_LABEL_PCHAN) &&
+ nvlist_exists_number(nvlist, SNDSTAT_LABEL_RCHAN)))
+ return (false);
+
+ if (nvlist_get_number(nvlist, SNDSTAT_LABEL_PCHAN) > 0)
+ if (!(nvlist_exists_number(nvlist, SNDSTAT_LABEL_PMINRATE) &&
+ nvlist_exists_number(nvlist, SNDSTAT_LABEL_PMAXRATE) &&
+ nvlist_exists_number(nvlist, SNDSTAT_LABEL_PFMTS)))
+ return (false);
+
+ if (nvlist_get_number(nvlist, SNDSTAT_LABEL_RCHAN) > 0)
+ if (!(nvlist_exists_number(nvlist, SNDSTAT_LABEL_RMINRATE) &&
+ nvlist_exists_number(nvlist, SNDSTAT_LABEL_RMAXRATE) &&
+ nvlist_exists_number(nvlist, SNDSTAT_LABEL_RFMTS)))
+ return (false);
+
+ return (true);
+
+}
+
+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;
+ nvlist_t *provider_nvl = NULL;
+ const char *provider;
+
+ devnode = nvlist_get_string(nvlist, SNDSTAT_LABEL_DEVNODE);
+ if (nvlist_exists_string(nvlist, SNDSTAT_LABEL_NAMEUNIT))
+ nameunit = nvlist_get_string(nvlist, SNDSTAT_LABEL_NAMEUNIT);
+ else
+ nameunit = devnode;
+ desc = nvlist_get_string(nvlist, SNDSTAT_LABEL_DESC);
+ pchan = nvlist_get_number(nvlist, SNDSTAT_LABEL_PCHAN);
+ rchan = nvlist_get_number(nvlist, SNDSTAT_LABEL_RCHAN);
+ if (pchan != 0) {
+ pminrate = nvlist_get_number(nvlist, SNDSTAT_LABEL_PMINRATE);
+ pmaxrate = nvlist_get_number(nvlist, SNDSTAT_LABEL_PMAXRATE);
+ pfmts = nvlist_get_number(nvlist, SNDSTAT_LABEL_PFMTS);
+ }
+ if (rchan != 0) {
+ rminrate = nvlist_get_number(nvlist, SNDSTAT_LABEL_RMINRATE);
+ rmaxrate = nvlist_get_number(nvlist, SNDSTAT_LABEL_RMAXRATE);
+ rfmts = nvlist_get_number(nvlist, SNDSTAT_LABEL_RFMTS);
+ }
+
+ provider = dnvlist_get_string(nvlist, SNDSTAT_LABEL_PROVIDER, "");
+ if (provider[0] == '\0')
+ provider = NULL;
+
+ if (provider != NULL &&
+ nvlist_exists_nvlist(nvlist, SNDSTAT_LABEL_PROVIDER_INFO)) {
+ provider_nvl = nvlist_clone(
+ nvlist_get_nvlist(nvlist, SNDSTAT_LABEL_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->pminrate = pminrate;
+ ud->pmaxrate = pmaxrate;
+ ud->rminrate = rminrate;
+ ud->rmaxrate = rmaxrate;
+ ud->pfmts = pfmts;
+ ud->rfmts = rfmts;
+ 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 sndstat_nvlbuf_arg *arg = (struct sndstat_nvlbuf_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, SNDSTAT_LABEL_DSPS)) {
+ err = EINVAL;
+ goto done;
+ }
+ dsps = nvlist_get_nvlist_array(nvl, SNDSTAT_LABEL_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 sndstat_nvlbuf_arg32 *arg32 = (struct sndstat_nvlbuf_arg32 *)data;
+ struct sndstat_nvlbuf_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 sndstat_nvlbuf_arg32 *arg32 = (struct sndstat_nvlbuf_arg32 *)data;
+ struct sndstat_nvlbuf_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 SNDSTAT_GET_DEVS:
+ err = sndstat_get_devs(pf, data);
+ break;
+#ifdef COMPAT_FREEBSD32
+ case SNDSTAT_GET_DEVS32:
+ if (!SV_CURPROC_FLAG(SV_ILP32)) {
+ err = ENODEV;
+ break;
+ }
+ err = compat_sndstat_get_devs32(pf, data);
+ break;
+#endif
+ case SNDSTAT_ADD_USER_DEVS:
+ err = sndstat_add_user_devs(pf, data);
+ break;
+#ifdef COMPAT_FREEBSD32
+ case SNDSTAT_ADD_USER_DEVS32:
+ if (!SV_CURPROC_FLAG(SV_ILP32)) {
+ err = ENODEV;
+ break;
+ }
+ err = compat_sndstat_add_user_devs32(pf, data);
+ break;
+#endif
+ case SNDSTAT_REFRESH_DEVS:
+ err = sndstat_refresh_devs(pf);
+ break;
+ case SNDSTAT_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
@@ -379,14 +1087,26 @@ sndstat_prepare(struct sndstat_file *pf_self)
/* append any input from userspace */
k = 0;
TAILQ_FOREACH(pf, &sndstat_filelist, entry) {
+ struct sndstat_userdev *ud;
+
if (pf == pf_self)
continue;
- if (pf->out_offset == 0)
+ 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");
- sbuf_bcat(s, sbuf_data(&pf->sbuf),
- sbuf_len(&pf->sbuf));
+ 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");
diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h
index cdae5e837cdc..7b4a4d3a46ca 100644
--- a/sys/dev/sound/pcm/sound.h
+++ b/sys/dev/sound/pcm/sound.h
@@ -64,6 +64,7 @@
#include <sys/poll.h>
#include <sys/sbuf.h>
#include <sys/soundcard.h>
+#include <sys/sndstat.h>
#include <sys/sysctl.h>
#include <sys/kobj.h>
#include <vm/vm.h>
diff --git a/sys/sys/param.h b/sys/sys/param.h
index 0f0854f359fd..5176dd1e9732 100644
--- a/sys/sys/param.h
+++ b/sys/sys/param.h
@@ -60,7 +60,7 @@
* in the range 5 to 9.
*/
#undef __FreeBSD_version
-#define __FreeBSD_version 1400005 /* Master, propagated to newvers */
+#define __FreeBSD_version 1400006 /* Master, propagated to newvers */
/*
* __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD,
diff --git a/sys/sys/sndstat.h b/sys/sys/sndstat.h
new file mode 100644
index 000000000000..995d474f8290
--- /dev/null
+++ b/sys/sys/sndstat.h
@@ -0,0 +1,95 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020 The FreeBSD Foundation
+ *
+ * This software was 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _SYS_SNDSTAT_H_
+#define _SYS_SNDSTAT_H_
+
+#include <sys/types.h>
+#ifndef _IOWR
+#include <sys/ioccom.h>
+#endif /* !_IOWR */
+
+struct sndstat_nvlbuf_arg {
+ size_t nbytes; /* [IN/OUT] buffer size/number of bytes filled */
+ void *buf; /* [OUT] buffer holding a packed nvlist */
+};
+
+/*
+ * Common labels
+ */
+#define SNDSTAT_LABEL_DSPS "dsps"
+#define SNDSTAT_LABEL_FROM_USER "from_user"
+#define SNDSTAT_LABEL_PCHAN "pchan"
+#define SNDSTAT_LABEL_RCHAN "rchan"
+#define SNDSTAT_LABEL_PMINRATE "pminrate"
+#define SNDSTAT_LABEL_PMAXRATE "pmaxrate"
+#define SNDSTAT_LABEL_RMINRATE "rminrate"
+#define SNDSTAT_LABEL_RMAXRATE "rmaxrate"
+#define SNDSTAT_LABEL_PFMTS "pfmts"
+#define SNDSTAT_LABEL_RFMTS "rfmts"
+#define SNDSTAT_LABEL_NAMEUNIT "nameunit"
+#define SNDSTAT_LABEL_DEVNODE "devnode"
+#define SNDSTAT_LABEL_DESC "desc"
+#define SNDSTAT_LABEL_PROVIDER "provider"
+#define SNDSTAT_LABEL_PROVIDER_INFO "provider_info"
+
+/*
+ * sound(4)-specific labels
+ */
+#define SNDSTAT_LABEL_SOUND4_PROVIDER "sound(4)"
+#define SNDSTAT_LABEL_SOUND4_UNIT "unit"
+#define SNDSTAT_LABEL_SOUND4_BITPERFECT "bitperfect"
+#define SNDSTAT_LABEL_SOUND4_PVCHAN "pvchan"
+#define SNDSTAT_LABEL_SOUND4_RVCHAN "rvchan"
+
+#define SNDSTAT_REFRESH_DEVS _IO('D', 100)
+#define SNDSTAT_GET_DEVS _IOWR('D', 101, struct sndstat_nvlbuf_arg)
+#define SNDSTAT_ADD_USER_DEVS _IOWR('D', 102, struct sndstat_nvlbuf_arg)
+#define SNDSTAT_FLUSH_USER_DEVS _IO('D', 103)
+
+#ifdef _KERNEL
+#ifdef COMPAT_FREEBSD32
+
+struct sndstat_nvlbuf_arg32 {
+ uint32_t nbytes;
+ uint32_t buf;
+};
+
+#define SNDSTAT_GET_DEVS32 \
+ _IOC_NEWTYPE(SNDSTAT_GET_DEVS, struct sndstat_nvlbuf_arg32)
+#define SNDSTAT_ADD_USER_DEVS32 \
+ _IOC_NEWTYPE(SNDSTAT_ADD_USER_DEVS, struct sndstat_nvlbuf_arg32)
+
+#endif
+#endif
+
+#endif /* !_SYS_SNDSTAT_H_ */