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
This commit is contained in:
Ka Ho Ng 2021-03-17 18:51:58 +08:00
parent 096a847216
commit c96151d335
6 changed files with 1086 additions and 10 deletions

View File

@ -526,6 +526,7 @@ MAN= aac.4 \
snd_via8233.4 \
snd_via82c686.4 \
snd_vibes.4 \
sndstat.4 \
snp.4 \
spigen.4 \
${_spkr.4} \

259
share/man/man4/sndstat.4 Normal file
View File

@ -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 .

View File

@ -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");

View File

@ -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>

View File

@ -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,

95
sys/sys/sndstat.h Normal file
View File

@ -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_ */