Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F141074257
D26884.id84911.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
32 KB
Referenced Files
None
Subscribers
None
D26884.id84911.diff
View Options
Index: share/man/man4/Makefile
===================================================================
--- share/man/man4/Makefile
+++ share/man/man4/Makefile
@@ -526,6 +526,7 @@
snd_via8233.4 \
snd_via82c686.4 \
snd_vibes.4 \
+ sndstat.4 \
snp.4 \
spigen.4 \
${_spkr.4} \
Index: share/man/man4/sndstat.4
===================================================================
--- /dev/null
+++ 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@freebsdfoundation.org .
Index: sys/dev/sound/pcm/sndstat.c
===================================================================
--- sys/dev/sound/pcm/sndstat.c
+++ 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 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 @@
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 @@
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 @@
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 @@
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, *tud;
+
+ KASSERT(
+ sx_xlocked(&pf->lock), ("%s: Called without pf->lock", __func__));
+ TAILQ_FOREACH_SAFE(ud, &pf->userdev_list, link, tud) {
+ 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 @@
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 @@
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 @@
}
}
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 @@
/* 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");
Index: sys/dev/sound/pcm/sound.h
===================================================================
--- sys/dev/sound/pcm/sound.h
+++ 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>
Index: sys/sys/sndstat.h
===================================================================
--- /dev/null
+++ 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_ */
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Jan 1, 1:10 PM (1 h, 25 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
27436986
Default Alt Text
D26884.id84911.diff (32 KB)
Attached To
Mode
D26884: Implement sndstat nvlist-based enumeration ioctls.
Attached
Detach File
Event Timeline
Log In to Comment