git: c96151d33509 - main - Implement sndstat nvlist-based enumeration ioctls.

Ka Ho Ng khng at FreeBSD.org
Wed Mar 17 11:09:02 UTC 2021


The branch main has been updated by khng:

URL: https://cgit.FreeBSD.org/src/commit/?id=c96151d33509655efb7fb26768cb56a041c176f1

commit c96151d33509655efb7fb26768cb56a041c176f1
Author:     Ka Ho Ng <khng at FreeBSD.org>
AuthorDate: 2021-03-17 10:51:58 +0000
Commit:     Ka Ho Ng <khng at FreeBSD.org>
CommitDate: 2021-03-17 11:05:43 +0000

    Implement sndstat nvlist-based enumeration ioctls.
    
    These ioctl commands aim to provide easier ways for user space
    applications to enumerate existing audio devices and the node they can
    potentially use.
    
    The exchange of device lists between user space and kernel is done on
    nv(9). Some ioctl commands are added to /dev/sndstat node:
      - SNDSTAT_REFRESH_DEVS
      - SNDSTAT_GET_DEVS
      - SNDSTAT_ADD_USER_DEVS
      - SNDSTAT_FLUSH_USER_DEVS
    
    Bump __FreeBSD_version to reflect the addition of the ioctls.
    
    Sponsored by:   The FreeBSD Foundation
    Reviewed by:    hselasky
    Approved by:    philip (mentor)
    Differential Revision:  https://reviews.freebsd.org/D26884
---
 share/man/man4/Makefile     |   1 +
 share/man/man4/sndstat.4    | 259 ++++++++++++++++
 sys/dev/sound/pcm/sndstat.c | 738 +++++++++++++++++++++++++++++++++++++++++++-
 sys/dev/sound/pcm/sound.h   |   1 +
 sys/sys/param.h             |   2 +-
 sys/sys/sndstat.h           |  95 ++++++
 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 at 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 at FreeBSD.org>
  * Copyright (c) 2001 Cameron Grant <cg at 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);
*** 305 LINES SKIPPED ***


More information about the dev-commits-src-all mailing list