sndstat: nvlist schema and API definition changes

- SNDSTAT_LABEL_* are renamed to SNDST_DSPS_*, and SNDSTAT_LABEL_DSPS
  becomes SNDST_DSPS.
- Centralize channel number/rate/formats into a single nvlist
  The above nvlist is named "info_play" and "info_rec"
- Expose only encoding format in pfmts/rfmts. Userland has no direct
  access to AFMT_ENCODING/CHANNEL/EXTCHANNEL macros, thus it serves no
  meaning to expose too much information through this pair of labels.
  However pminrate/rminrate, pmaxrate/rmaxrate, pfmts/rfmts are
  deprecated and will be removed in future.

This commit keeps ioctls ABI compatibility with __FreeBSD_version
1400006 for now. In future the compat ABI with 1400006 will be removed
once audio/virtual_oss is rebuilt.

Sponsored by:	The FreeBSD Foundation
Reviewed by:	hselasky
Approved by:	philip (mentor)
Differential Revision:	https://reviews.freebsd.org/D29770
This commit is contained in:
Ka Ho Ng 2021-04-21 16:19:15 +08:00
parent 438b553207
commit 4ce1ba6523
3 changed files with 322 additions and 181 deletions

View File

@ -31,7 +31,7 @@
.\" .\"
.\" Note: The date here should be updated whenever a non-trivial .\" Note: The date here should be updated whenever a non-trivial
.\" change is made to the manual page. .\" change is made to the manual page.
.Dd December 7, 2020 .Dd April 15, 2021
.Dt SNDSTAT 4 .Dt SNDSTAT 4
.Os .Os
.Sh NAME .Sh NAME
@ -52,7 +52,7 @@ device allows callers to enumeration PCM audio devices available for use.
For all ioctls requiring data exchange between the subsystem and callers, For all ioctls requiring data exchange between the subsystem and callers,
the following structures are used to describe a serialized nvlist: the following structures are used to describe a serialized nvlist:
.Bd -literal -offset indent .Bd -literal -offset indent
struct sndstat_nvlbuf_arg { struct sndstioc_nv_arg {
size_t nbytes; size_t nbytes;
void *buf; void *buf;
}; };
@ -67,9 +67,12 @@ dsps (NVLIST ARRAY): 1
desc (STRING): [Generic (0x8086) (Analog Line-out)] desc (STRING): [Generic (0x8086) (Analog Line-out)]
pchan (NUMBER): 1 (1) (0x1) pchan (NUMBER): 1 (1) (0x1)
rchan (NUMBER): 0 (0) (0x0) rchan (NUMBER): 0 (0) (0x0)
pminrate (NUMBER): 48000 (48000) (0xbb80) info_play (NVLIST):
pmaxrate (NUMBER): 48000 (48000) (0xbb80) min_rate (NUMBER): 48000 (48000) (0xbb80)
pfmts (NUMBER): 2097168 (2097168) (0x200010) max_rate (NUMBER): 48000 (48000) (0xbb80)
formats (NUMBER): 16 (16) (0x10)
min_chn (NUMBER): 2 (2) (0x2)
max_chn (NUMBER): 2 (2) (0x2)
provider_info (NVLIST): provider_info (NVLIST):
unit (NUMBER): 0 (0) (0x0) unit (NUMBER): 0 (0) (0x0)
bitperfect (BOOL): FALSE bitperfect (BOOL): FALSE
@ -94,24 +97,38 @@ This can be 0 if this PCM audio device does not support playback at all.
.It Dv rchan .It Dv rchan
The number of recording channels supported by hardware. The number of recording channels supported by hardware.
This can be 0 if this PCM audio device does not support recording at all. This can be 0 if this PCM audio device does not support recording at all.
.It Dv pminrate .It Dv info_play
The minimum supported playback direction sampling rate. Supported configurations in playback direction.
Only exists if pchan is greater than 0. This exists only if this PCM audio device supports playback.
.It Dv pmaxrate There are a number of name/value pairs inside this field:
The maximum supported playback direction sampling rate. .Bl -tag -width ".Dv min_rate"
Only exists if pchan is greater than 0. .It Dv min_rate
.It Dv pfmts Minimum supported sampling rate.
The supported playback direction sample format. .It Dv max_rate
Only exists if pchan is greater than 0. Maximum supported sampling rate.
.It Dv rminrate .It Dv formats
The minimum supported recording direction sampling rate. Supported sample formats.
Only exists if rchan is greater than 0. .It Dv min_chn
.It Dv rmaxrate Minimum supported number of channels in channel layout
The maximum supported recording direction sampling rate. .It Dv max_chn
Only exists if rchan is greater than 0. Maximum supported number of channels in channel layout
.It Dv rfmts .El
The supported playback recording sample format. .It Dv info_rec
Only exists if rchan is greater than 0. Supported configurations in recording direction.
This exists only if this PCM audio device supports recording.
There are a number of name/value pairs inside this field:
.Bl -tag -width ".Dv min_rate"
.It Dv min_rate
Minimum supported sampling rate.
.It Dv max_rate
Maximum supported sampling rate.
.It Dv formats
Supported sample formats.
.It Dv min_chn
Minimum supported number of channels in channel layout
.It Dv max_chn
Maximum supported number of channels in channel layout
.El
.It Dv provider_info .It Dv provider_info
Provider-specific fields. Provider-specific fields.
This field may not exist if the PCM audio device is not provided by in-kernel This field may not exist if the PCM audio device is not provided by in-kernel
@ -121,15 +138,15 @@ This field will not exist if the provider field is an empty string.
A string specifying the provider of the PCm audio device. A string specifying the provider of the PCm audio device.
.El .El
.Pp .Pp
The following ioctls are providede for use: The following ioctls are provided for use:
.Bl -tag -width ".Dv SNDSTAT_FLUSH_USER_DEVS" .Bl -tag -width ".Dv SNDSTIOC_FLUSH_USER_DEVS"
.It Dv SNDSTAT_REFRESH_DEVS .It Dv SNDSTIOC_REFRESH_DEVS
Drop any previously fetched PCM audio devices list snapshots. Drop any previously fetched PCM audio devices list snapshots.
This ioctl takes no arguments. This ioctl takes no arguments.
.It Dv SNDSTAT_GET_DEVS .It Dv SNDSTIOC_GET_DEVS
Generate and/or return PCM audio devices list snapshots to callers. Generate and/or return PCM audio devices list snapshots to callers.
This ioctl takes a pointer to This ioctl takes a pointer to
.Fa struct sndstat_nvlbuf_arg .Fa struct sndstioc_nv_arg
as the first and the only argument. as the first and the only argument.
Callers need to provide a sufficiently large buffer to hold a serialized Callers need to provide a sufficiently large buffer to hold a serialized
nvlist. nvlist.
@ -159,12 +176,12 @@ 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 the snapshot stored in the subsystem's internal structure of the given
.Fa fd .Fa fd
will be freed. will be freed.
.It Dv SNDSTAT_ADD_USER_DEVS .It Dv SNDSTIOC_ADD_USER_DEVS
Add a list of PCM audio devices provided by callers to Add a list of PCM audio devices provided by callers to
.Pa /dev/sndstat .Pa /dev/sndstat
device. device.
This ioctl takes a pointer to This ioctl takes a pointer to
.Fa struct sndstat_nvlbuf_arg .Fa struct sndstioc_nv_arg
as the first and the only argument. as the first and the only argument.
Callers have to provide a buffer holding a serialized nvlist. Callers have to provide a buffer holding a serialized nvlist.
.Fa nbytes .Fa nbytes
@ -173,7 +190,7 @@ should be set to the length in bytes of the serialized nvlist.
should be pointed to a buffer storing the serialized nvlist. should be pointed to a buffer storing the serialized nvlist.
Userspace-backed PCM audio device nodes should be listed inside the serialized Userspace-backed PCM audio device nodes should be listed inside the serialized
nvlist. nvlist.
.It Dv SNDSTAT_FLUSH_USER_DEVS .It Dv SNDSTIOC_FLUSH_USER_DEVS
Flush any PCM audio devices previously added by callers. Flush any PCM audio devices previously added by callers.
This ioctl takes no arguments. This ioctl takes no arguments.
.El .El
@ -198,7 +215,7 @@ int
main() main()
{ {
int fd; int fd;
struct sndstat_nvlbuf_arg arg; struct sndstioc_nv_arg arg;
const nvlist_t * const *di; const nvlist_t * const *di;
size_t i, nitems; size_t i, nitems;
nvlist_t *nvl; nvlist_t *nvl;
@ -206,28 +223,28 @@ main()
/* Open sndstat node in read-only first */ /* Open sndstat node in read-only first */
fd = open("/dev/sndstat", O_RDONLY); fd = open("/dev/sndstat", O_RDONLY);
if (ioctl(fd, SNDSTAT_REFRESH_DEVS, NULL)) if (ioctl(fd, SNDSTIOC_REFRESH_DEVS, NULL))
err(1, "ioctl(fd, SNDSTAT_REFRESH_DEVS, NULL)"); err(1, "ioctl(fd, SNDSTIOC_REFRESH_DEVS, NULL)");
/* Get the size of snapshot, when nbytes = 0 */ /* Get the size of snapshot, when nbytes = 0 */
arg.nbytes = 0; arg.nbytes = 0;
arg.buf = NULL; arg.buf = NULL;
if (ioctl(fd, SNDSTAT_GET_DEVS, &arg)) if (ioctl(fd, SNDSTIOC_GET_DEVS, &arg))
err(1, "ioctl(fd, SNDSTAT_GET_DEVS, &arg)"); err(1, "ioctl(fd, SNDSTIOC_GET_DEVS, &arg)");
/* Get snapshot data */ /* Get snapshot data */
arg.buf = malloc(arg.nbytes); arg.buf = malloc(arg.nbytes);
if (arg.buf == NULL) if (arg.buf == NULL)
err(EX_OSERR, "malloc"); err(EX_OSERR, "malloc");
if (ioctl(fd, SNDSTAT_GET_DEVS, &arg)) if (ioctl(fd, SNDSTIOC_GET_DEVS, &arg))
err(1, "ioctl(fd, SNDSTAT_GET_DEVS, &arg)"); err(1, "ioctl(fd, SNDSTIOC_GET_DEVS, &arg)");
/* Deserialize the nvlist stream */ /* Deserialize the nvlist stream */
nvl = nvlist_unpack(arg.buf, arg.nbytes, 0); nvl = nvlist_unpack(arg.buf, arg.nbytes, 0);
free(arg.buf); free(arg.buf);
/* Get DSPs array */ /* Get DSPs array */
di = nvlist_get_nvlist_array(nvl, SNDSTAT_LABEL_DSPS, &nitems); di = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &nitems);
for (i = 0; i < nitems; i++) { for (i = 0; i < nitems; i++) {
const char *nameunit, *devnode, *desc; const char *nameunit, *devnode, *desc;
@ -235,9 +252,9 @@ main()
* Examine each device nvlist item * Examine each device nvlist item
*/ */
nameunit = nvlist_get_string(di[i], SNDSTAT_LABEL_NAMEUNIT); nameunit = nvlist_get_string(di[i], SNDST_DSPS_NAMEUNIT);
devnode = nvlist_get_string(di[i], SNDSTAT_LABEL_DEVNODE); devnode = nvlist_get_string(di[i], SNDST_DSPS_DEVNODE);
desc = nvlist_get_string(di[i], SNDSTAT_LABEL_DESC); desc = nvlist_get_string(di[i], SNDST_DSPS_DESC);
printf("Name unit: `%s`, Device node: `%s`, Description: `%s`\n", printf("Name unit: `%s`, Device node: `%s`, Description: `%s`\n",
nameunit, devnode, desc); nameunit, devnode, desc);
} }

View File

@ -89,12 +89,13 @@ struct sndstat_userdev {
char *desc; char *desc;
unsigned int pchan; unsigned int pchan;
unsigned int rchan; unsigned int rchan;
uint32_t pminrate; struct {
uint32_t pmaxrate; uint32_t min_rate;
uint32_t rminrate; uint32_t max_rate;
uint32_t rmaxrate; uint32_t formats;
uint32_t pfmts; uint32_t min_chn;
uint32_t rfmts; uint32_t max_chn;
} info_play, info_rec;
nvlist_t *provider_nvl; nvlist_t *provider_nvl;
}; };
@ -326,46 +327,77 @@ sndstat_write(struct cdev *i_dev, struct uio *buf, int flag)
static void static void
sndstat_get_caps(struct snddev_info *d, bool play, uint32_t *min_rate, sndstat_get_caps(struct snddev_info *d, bool play, uint32_t *min_rate,
uint32_t *max_rate, uint32_t *fmts) uint32_t *max_rate, uint32_t *fmts, uint32_t *minchn, uint32_t *maxchn)
{ {
struct pcm_channel *c; struct pcm_channel *c;
unsigned int encoding;
int dir; int dir;
dir = play ? PCMDIR_PLAY : PCMDIR_REC; dir = play ? PCMDIR_PLAY : PCMDIR_REC;
*min_rate = 0;
*max_rate = 0;
*fmts = 0;
if (play && d->pvchancount > 0) { if (play && d->pvchancount > 0) {
*min_rate = *max_rate = d->pvchanrate; *min_rate = *max_rate = d->pvchanrate;
*fmts = d->pvchanformat; *fmts = AFMT_ENCODING(d->pvchanformat);
*minchn = *maxchn = AFMT_CHANNEL(d->pvchanformat);
return; return;
} else if (!play && d->rvchancount > 0) { } else if (!play && d->rvchancount > 0) {
*min_rate = *max_rate = d->rvchanrate; *min_rate = *max_rate = d->rvchanrate;
*fmts = d->rvchanformat; *fmts = AFMT_ENCODING(d->rvchanformat);
*minchn = *maxchn = AFMT_CHANNEL(d->rvchanformat);
return; return;
} }
*min_rate = UINT32_MAX;
*max_rate = 0;
*minchn = UINT32_MAX;
*maxchn = 0;
encoding = 0;
CHN_FOREACH(c, d, channels.pcm) { CHN_FOREACH(c, d, channels.pcm) {
struct pcmchan_caps *caps; struct pcmchan_caps *caps;
int i;
if (c->direction != dir || (c->flags & CHN_F_VIRTUAL) != 0) if (c->direction != dir || (c->flags & CHN_F_VIRTUAL) != 0)
continue; continue;
CHN_LOCK(c); CHN_LOCK(c);
caps = chn_getcaps(c); caps = chn_getcaps(c);
*min_rate = caps->minspeed; *min_rate = min(caps->minspeed, *min_rate);
*max_rate = caps->maxspeed; *max_rate = max(caps->maxspeed, *max_rate);
*fmts = chn_getformats(c); for (i = 0; caps->fmtlist[i]; i++) {
encoding |= AFMT_ENCODING(caps->fmtlist[i]);
*minchn = min(AFMT_CHANNEL(encoding), *minchn);
*maxchn = max(AFMT_CHANNEL(encoding), *maxchn);
}
CHN_UNLOCK(c); CHN_UNLOCK(c);
} }
if (*min_rate == UINT32_MAX)
*min_rate = 0;
if (*minchn == UINT32_MAX)
*minchn = 0;
}
static nvlist_t *
sndstat_create_diinfo_nv(uint32_t min_rate, uint32_t max_rate, uint32_t formats,
uint32_t min_chn, uint32_t max_chn)
{
nvlist_t *nv;
nv = nvlist_create(0);
if (nv == NULL)
return (NULL);
nvlist_add_number(nv, SNDST_DSPS_INFO_MIN_RATE, min_rate);
nvlist_add_number(nv, SNDST_DSPS_INFO_MAX_RATE, max_rate);
nvlist_add_number(nv, SNDST_DSPS_INFO_FORMATS, formats);
nvlist_add_number(nv, SNDST_DSPS_INFO_MIN_CHN, min_chn);
nvlist_add_number(nv, SNDST_DSPS_INFO_MAX_CHN, max_chn);
return (nv);
} }
static int static int
sndstat_build_sound4_nvlist(struct snddev_info *d, nvlist_t **dip) sndstat_build_sound4_nvlist(struct snddev_info *d, nvlist_t **dip)
{ {
uint32_t maxrate, minrate, fmts; uint32_t maxrate, minrate, fmts, minchn, maxchn;
nvlist_t *di = NULL, *sound4di = NULL; nvlist_t *di = NULL, *sound4di = NULL, *diinfo = NULL;
int err; int err;
di = nvlist_create(0); di = nvlist_create(0);
@ -379,40 +411,54 @@ sndstat_build_sound4_nvlist(struct snddev_info *d, nvlist_t **dip)
goto done; goto done;
} }
nvlist_add_bool(di, SNDSTAT_LABEL_FROM_USER, false); nvlist_add_bool(di, SNDST_DSPS_FROM_USER, false);
nvlist_add_stringf(di, SNDSTAT_LABEL_NAMEUNIT, "%s", nvlist_add_stringf(di, SNDST_DSPS_NAMEUNIT, "%s",
device_get_nameunit(d->dev)); device_get_nameunit(d->dev));
nvlist_add_stringf(di, SNDSTAT_LABEL_DEVNODE, "dsp%d", nvlist_add_stringf(di, SNDST_DSPS_DEVNODE, "dsp%d",
device_get_unit(d->dev)); device_get_unit(d->dev));
nvlist_add_string( nvlist_add_string(
di, SNDSTAT_LABEL_DESC, device_get_desc(d->dev)); di, SNDST_DSPS_DESC, device_get_desc(d->dev));
PCM_ACQUIRE_QUICK(d); PCM_ACQUIRE_QUICK(d);
nvlist_add_number(di, SNDSTAT_LABEL_PCHAN, d->playcount); nvlist_add_number(di, SNDST_DSPS_PCHAN, d->playcount);
nvlist_add_number(di, SNDSTAT_LABEL_RCHAN, d->reccount); nvlist_add_number(di, SNDST_DSPS_RCHAN, d->reccount);
if (d->playcount > 0) { if (d->playcount > 0) {
sndstat_get_caps(d, true, &minrate, &maxrate, &fmts); sndstat_get_caps(d, true, &minrate, &maxrate, &fmts, &minchn,
nvlist_add_number(di, SNDSTAT_LABEL_PMINRATE, minrate); &maxchn);
nvlist_add_number(di, SNDSTAT_LABEL_PMAXRATE, maxrate); nvlist_add_number(di, "pminrate", minrate);
nvlist_add_number(di, SNDSTAT_LABEL_PFMTS, fmts); nvlist_add_number(di, "pmaxrate", maxrate);
nvlist_add_number(di, "pfmts", fmts);
diinfo = sndstat_create_diinfo_nv(minrate, maxrate, fmts,
minchn, maxchn);
if (diinfo == NULL)
nvlist_set_error(di, ENOMEM);
else
nvlist_move_nvlist(di, SNDST_DSPS_INFO_PLAY, diinfo);
} }
if (d->reccount > 0) { if (d->reccount > 0) {
sndstat_get_caps(d, false, &minrate, &maxrate, &fmts); sndstat_get_caps(d, false, &minrate, &maxrate, &fmts, &minchn,
nvlist_add_number(di, SNDSTAT_LABEL_RMINRATE, minrate); &maxchn);
nvlist_add_number(di, SNDSTAT_LABEL_RMAXRATE, maxrate); nvlist_add_number(di, "rminrate", minrate);
nvlist_add_number(di, SNDSTAT_LABEL_RFMTS, fmts); nvlist_add_number(di, "rmaxrate", maxrate);
nvlist_add_number(di, "rfmts", fmts);
diinfo = sndstat_create_diinfo_nv(minrate, maxrate, fmts,
minchn, maxchn);
if (diinfo == NULL)
nvlist_set_error(di, ENOMEM);
else
nvlist_move_nvlist(di, SNDST_DSPS_INFO_REC, diinfo);
} }
nvlist_add_number(sound4di, SNDSTAT_LABEL_SOUND4_UNIT, nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_UNIT,
device_get_unit(d->dev)); // XXX: I want signed integer here device_get_unit(d->dev)); // XXX: I want signed integer here
nvlist_add_bool( nvlist_add_bool(
sound4di, SNDSTAT_LABEL_SOUND4_BITPERFECT, d->flags & SD_F_BITPERFECT); sound4di, SNDST_DSPS_SOUND4_BITPERFECT, d->flags & SD_F_BITPERFECT);
nvlist_add_number(sound4di, SNDSTAT_LABEL_SOUND4_PVCHAN, d->pvchancount); nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_PVCHAN, d->pvchancount);
nvlist_add_number(sound4di, SNDSTAT_LABEL_SOUND4_RVCHAN, d->rvchancount); nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_RVCHAN, d->rvchancount);
nvlist_move_nvlist(di, SNDSTAT_LABEL_PROVIDER_INFO, sound4di); nvlist_move_nvlist(di, SNDST_DSPS_PROVIDER_INFO, sound4di);
sound4di = NULL; sound4di = NULL;
PCM_RELEASE_QUICK(d); PCM_RELEASE_QUICK(d);
nvlist_add_string(di, SNDSTAT_LABEL_PROVIDER, SNDSTAT_LABEL_SOUND4_PROVIDER); nvlist_add_string(di, SNDST_DSPS_PROVIDER, SNDST_DSPS_SOUND4_PROVIDER);
err = nvlist_error(di); err = nvlist_error(di);
if (err) if (err)
@ -431,7 +477,7 @@ sndstat_build_sound4_nvlist(struct snddev_info *d, nvlist_t **dip)
static int static int
sndstat_build_userland_nvlist(struct sndstat_userdev *ud, nvlist_t **dip) sndstat_build_userland_nvlist(struct sndstat_userdev *ud, nvlist_t **dip)
{ {
nvlist_t *di; nvlist_t *di, *diinfo;
int err; int err;
di = nvlist_create(0); di = nvlist_create(0);
@ -440,34 +486,48 @@ sndstat_build_userland_nvlist(struct sndstat_userdev *ud, nvlist_t **dip)
goto done; goto done;
} }
nvlist_add_bool(di, SNDSTAT_LABEL_FROM_USER, true); nvlist_add_bool(di, SNDST_DSPS_FROM_USER, true);
nvlist_add_number(di, SNDSTAT_LABEL_PCHAN, ud->pchan); nvlist_add_number(di, SNDST_DSPS_PCHAN, ud->pchan);
nvlist_add_number(di, SNDSTAT_LABEL_RCHAN, ud->rchan); nvlist_add_number(di, SNDST_DSPS_RCHAN, ud->rchan);
nvlist_add_string(di, SNDSTAT_LABEL_NAMEUNIT, ud->nameunit); nvlist_add_string(di, SNDST_DSPS_NAMEUNIT, ud->nameunit);
nvlist_add_string( nvlist_add_string(
di, SNDSTAT_LABEL_DEVNODE, ud->devnode); di, SNDST_DSPS_DEVNODE, ud->devnode);
nvlist_add_string(di, SNDSTAT_LABEL_DESC, ud->desc); nvlist_add_string(di, SNDST_DSPS_DESC, ud->desc);
if (ud->pchan != 0) { if (ud->pchan != 0) {
nvlist_add_number( nvlist_add_number(di, "pminrate",
di, SNDSTAT_LABEL_PMINRATE, ud->pminrate); ud->info_play.min_rate);
nvlist_add_number( nvlist_add_number(di, "pmaxrate",
di, SNDSTAT_LABEL_PMAXRATE, ud->pmaxrate); ud->info_play.max_rate);
nvlist_add_number( nvlist_add_number(di, "pfmts",
di, SNDSTAT_LABEL_PFMTS, ud->pfmts); ud->info_play.formats);
diinfo = sndstat_create_diinfo_nv(ud->info_play.min_rate,
ud->info_play.max_rate, ud->info_play.formats,
ud->info_play.min_chn, ud->info_play.max_chn);
if (diinfo == NULL)
nvlist_set_error(di, ENOMEM);
else
nvlist_move_nvlist(di, SNDST_DSPS_INFO_PLAY, diinfo);
} }
if (ud->rchan != 0) { if (ud->rchan != 0) {
nvlist_add_number( nvlist_add_number(di, "rminrate",
di, SNDSTAT_LABEL_RMINRATE, ud->rminrate); ud->info_rec.min_rate);
nvlist_add_number( nvlist_add_number(di, "rmaxrate",
di, SNDSTAT_LABEL_RMAXRATE, ud->rmaxrate); ud->info_rec.max_rate);
nvlist_add_number( nvlist_add_number(di, "rfmts",
di, SNDSTAT_LABEL_RFMTS, ud->rfmts); ud->info_rec.formats);
diinfo = sndstat_create_diinfo_nv(ud->info_rec.min_rate,
ud->info_rec.max_rate, ud->info_rec.formats,
ud->info_rec.min_chn, ud->info_rec.max_chn);
if (diinfo == NULL)
nvlist_set_error(di, ENOMEM);
else
nvlist_move_nvlist(di, SNDST_DSPS_INFO_REC, diinfo);
} }
nvlist_add_string(di, SNDSTAT_LABEL_PROVIDER, nvlist_add_string(di, SNDST_DSPS_PROVIDER,
(ud->provider != NULL) ? ud->provider : ""); (ud->provider != NULL) ? ud->provider : "");
if (ud->provider_nvl != NULL) if (ud->provider_nvl != NULL)
nvlist_add_nvlist( nvlist_add_nvlist(
di, SNDSTAT_LABEL_PROVIDER_INFO, ud->provider_nvl); di, SNDST_DSPS_PROVIDER_INFO, ud->provider_nvl);
err = nvlist_error(di); err = nvlist_error(di);
if (err) if (err)
@ -511,7 +571,7 @@ sndstat_create_devs_nvlist(nvlist_t **nvlp)
if (err) if (err)
goto done; goto done;
nvlist_append_nvlist_array(nvl, SNDSTAT_LABEL_DSPS, di); nvlist_append_nvlist_array(nvl, SNDST_DSPS, di);
nvlist_destroy(di); nvlist_destroy(di);
err = nvlist_error(nvl); err = nvlist_error(nvl);
if (err) if (err)
@ -531,7 +591,7 @@ sndstat_create_devs_nvlist(nvlist_t **nvlp)
sx_xunlock(&pf->lock); sx_xunlock(&pf->lock);
goto done; goto done;
} }
nvlist_append_nvlist_array(nvl, SNDSTAT_LABEL_DSPS, di); nvlist_append_nvlist_array(nvl, SNDST_DSPS, di);
nvlist_destroy(di); nvlist_destroy(di);
err = nvlist_error(nvl); err = nvlist_error(nvl);
@ -568,7 +628,7 @@ static int
sndstat_get_devs(struct sndstat_file *pf, caddr_t data) sndstat_get_devs(struct sndstat_file *pf, caddr_t data)
{ {
int err; int err;
struct sndstat_nvlbuf_arg *arg = (struct sndstat_nvlbuf_arg *)data; struct sndstioc_nv_arg *arg = (struct sndstioc_nv_arg *)data;
SNDSTAT_LOCK(); SNDSTAT_LOCK();
sx_xlock(&pf->lock); sx_xlock(&pf->lock);
@ -654,31 +714,65 @@ sndstat_unpack_user_nvlbuf(const void *unvlbuf, size_t nbytes, nvlist_t **nvl)
return (0); return (0);
} }
static bool
sndstat_diinfo_is_sane(const nvlist_t *diinfo)
{
if (!(nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_RATE) &&
nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_RATE) &&
nvlist_exists_number(diinfo, SNDST_DSPS_INFO_FORMATS) &&
nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_CHN) &&
nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_CHN)))
return (false);
return (true);
}
static bool static bool
sndstat_dsp_nvlist_is_sane(const nvlist_t *nvlist) sndstat_dsp_nvlist_is_sane(const nvlist_t *nvlist)
{ {
if (!(nvlist_exists_string(nvlist, SNDSTAT_LABEL_DEVNODE) && if (!(nvlist_exists_string(nvlist, SNDST_DSPS_DEVNODE) &&
nvlist_exists_string(nvlist, SNDSTAT_LABEL_DESC) && nvlist_exists_string(nvlist, SNDST_DSPS_DESC) &&
nvlist_exists_number(nvlist, SNDSTAT_LABEL_PCHAN) && nvlist_exists_number(nvlist, SNDST_DSPS_PCHAN) &&
nvlist_exists_number(nvlist, SNDSTAT_LABEL_RCHAN))) nvlist_exists_number(nvlist, SNDST_DSPS_RCHAN)))
return (false); return (false);
if (nvlist_get_number(nvlist, SNDSTAT_LABEL_PCHAN) > 0) if (nvlist_get_number(nvlist, SNDST_DSPS_PCHAN) > 0) {
if (!(nvlist_exists_number(nvlist, SNDSTAT_LABEL_PMINRATE) && if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) {
nvlist_exists_number(nvlist, SNDSTAT_LABEL_PMAXRATE) && if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist,
nvlist_exists_number(nvlist, SNDSTAT_LABEL_PFMTS))) SNDST_DSPS_INFO_PLAY)))
return (false);
} else if (!(nvlist_exists_number(nvlist, "pminrate") &&
nvlist_exists_number(nvlist, "pmaxrate") &&
nvlist_exists_number(nvlist, "pfmts")))
return (false); return (false);
}
if (nvlist_get_number(nvlist, SNDSTAT_LABEL_RCHAN) > 0) if (nvlist_get_number(nvlist, SNDST_DSPS_RCHAN) > 0) {
if (!(nvlist_exists_number(nvlist, SNDSTAT_LABEL_RMINRATE) && if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) {
nvlist_exists_number(nvlist, SNDSTAT_LABEL_RMAXRATE) && if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist,
nvlist_exists_number(nvlist, SNDSTAT_LABEL_RFMTS))) SNDST_DSPS_INFO_REC)))
return (false);
} else if (!(nvlist_exists_number(nvlist, "rminrate") &&
nvlist_exists_number(nvlist, "rmaxrate") &&
nvlist_exists_number(nvlist, "rfmts")))
return (false); return (false);
}
return (true); return (true);
} }
static void
sndstat_get_diinfo_nv(const nvlist_t *nv, uint32_t *min_rate,
uint32_t *max_rate, uint32_t *formats, uint32_t *min_chn,
uint32_t *max_chn)
{
*min_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_RATE);
*max_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_RATE);
*formats = nvlist_get_number(nv, SNDST_DSPS_INFO_FORMATS);
*min_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_CHN);
*max_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_CHN);
}
static int static int
sndstat_dsp_unpack_nvlist(const nvlist_t *nvlist, struct sndstat_userdev *ud) sndstat_dsp_unpack_nvlist(const nvlist_t *nvlist, struct sndstat_userdev *ud)
{ {
@ -687,36 +781,53 @@ sndstat_dsp_unpack_nvlist(const nvlist_t *nvlist, struct sndstat_userdev *ud)
uint32_t pminrate = 0, pmaxrate = 0; uint32_t pminrate = 0, pmaxrate = 0;
uint32_t rminrate = 0, rmaxrate = 0; uint32_t rminrate = 0, rmaxrate = 0;
uint32_t pfmts = 0, rfmts = 0; uint32_t pfmts = 0, rfmts = 0;
uint32_t pminchn = 0, pmaxchn = 0;
uint32_t rminchn = 0, rmaxchn = 0;
nvlist_t *provider_nvl = NULL; nvlist_t *provider_nvl = NULL;
const nvlist_t *diinfo;
const char *provider; const char *provider;
devnode = nvlist_get_string(nvlist, SNDSTAT_LABEL_DEVNODE); devnode = nvlist_get_string(nvlist, SNDST_DSPS_DEVNODE);
if (nvlist_exists_string(nvlist, SNDSTAT_LABEL_NAMEUNIT)) if (nvlist_exists_string(nvlist, SNDST_DSPS_NAMEUNIT))
nameunit = nvlist_get_string(nvlist, SNDSTAT_LABEL_NAMEUNIT); nameunit = nvlist_get_string(nvlist, SNDST_DSPS_NAMEUNIT);
else else
nameunit = devnode; nameunit = devnode;
desc = nvlist_get_string(nvlist, SNDSTAT_LABEL_DESC); desc = nvlist_get_string(nvlist, SNDST_DSPS_DESC);
pchan = nvlist_get_number(nvlist, SNDSTAT_LABEL_PCHAN); pchan = nvlist_get_number(nvlist, SNDST_DSPS_PCHAN);
rchan = nvlist_get_number(nvlist, SNDSTAT_LABEL_RCHAN); rchan = nvlist_get_number(nvlist, SNDST_DSPS_RCHAN);
if (pchan != 0) { if (pchan != 0) {
pminrate = nvlist_get_number(nvlist, SNDSTAT_LABEL_PMINRATE); if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) {
pmaxrate = nvlist_get_number(nvlist, SNDSTAT_LABEL_PMAXRATE); diinfo = nvlist_get_nvlist(nvlist,
pfmts = nvlist_get_number(nvlist, SNDSTAT_LABEL_PFMTS); SNDST_DSPS_INFO_PLAY);
sndstat_get_diinfo_nv(diinfo, &pminrate, &pmaxrate,
&pfmts, &pminchn, &pmaxchn);
} else {
pminrate = nvlist_get_number(nvlist, "pminrate");
pmaxrate = nvlist_get_number(nvlist, "pmaxrate");
pfmts = nvlist_get_number(nvlist, "pfmts");
}
} }
if (rchan != 0) { if (rchan != 0) {
rminrate = nvlist_get_number(nvlist, SNDSTAT_LABEL_RMINRATE); if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) {
rmaxrate = nvlist_get_number(nvlist, SNDSTAT_LABEL_RMAXRATE); diinfo = nvlist_get_nvlist(nvlist,
rfmts = nvlist_get_number(nvlist, SNDSTAT_LABEL_RFMTS); SNDST_DSPS_INFO_REC);
sndstat_get_diinfo_nv(diinfo, &rminrate, &rmaxrate,
&rfmts, &rminchn, &rmaxchn);
} else {
rminrate = nvlist_get_number(nvlist, "rminrate");
rmaxrate = nvlist_get_number(nvlist, "rmaxrate");
rfmts = nvlist_get_number(nvlist, "rfmts");
}
} }
provider = dnvlist_get_string(nvlist, SNDSTAT_LABEL_PROVIDER, ""); provider = dnvlist_get_string(nvlist, SNDST_DSPS_PROVIDER, "");
if (provider[0] == '\0') if (provider[0] == '\0')
provider = NULL; provider = NULL;
if (provider != NULL && if (provider != NULL &&
nvlist_exists_nvlist(nvlist, SNDSTAT_LABEL_PROVIDER_INFO)) { nvlist_exists_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO)) {
provider_nvl = nvlist_clone( provider_nvl = nvlist_clone(
nvlist_get_nvlist(nvlist, SNDSTAT_LABEL_PROVIDER_INFO)); nvlist_get_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO));
if (provider_nvl == NULL) if (provider_nvl == NULL)
return (ENOMEM); return (ENOMEM);
} }
@ -727,12 +838,16 @@ sndstat_dsp_unpack_nvlist(const nvlist_t *nvlist, struct sndstat_userdev *ud)
ud->desc = strdup(desc, M_DEVBUF); ud->desc = strdup(desc, M_DEVBUF);
ud->pchan = pchan; ud->pchan = pchan;
ud->rchan = rchan; ud->rchan = rchan;
ud->pminrate = pminrate; ud->info_play.min_rate = pminrate;
ud->pmaxrate = pmaxrate; ud->info_play.max_rate = pmaxrate;
ud->rminrate = rminrate; ud->info_play.formats = pfmts;
ud->rmaxrate = rmaxrate; ud->info_play.min_chn = pminchn;
ud->pfmts = pfmts; ud->info_play.max_chn = pmaxchn;
ud->rfmts = rfmts; ud->info_rec.min_rate = rminrate;
ud->info_rec.max_rate = rmaxrate;
ud->info_rec.formats = rfmts;
ud->info_rec.min_chn = rminchn;
ud->info_rec.max_chn = rmaxchn;
ud->provider_nvl = provider_nvl; ud->provider_nvl = provider_nvl;
return (0); return (0);
} }
@ -744,7 +859,7 @@ sndstat_add_user_devs(struct sndstat_file *pf, caddr_t data)
nvlist_t *nvl = NULL; nvlist_t *nvl = NULL;
const nvlist_t * const *dsps; const nvlist_t * const *dsps;
size_t i, ndsps; size_t i, ndsps;
struct sndstat_nvlbuf_arg *arg = (struct sndstat_nvlbuf_arg *)data; struct sndstioc_nv_arg *arg = (struct sndstioc_nv_arg *)data;
if ((pf->fflags & FWRITE) == 0) { if ((pf->fflags & FWRITE) == 0) {
err = EPERM; err = EPERM;
@ -755,11 +870,11 @@ sndstat_add_user_devs(struct sndstat_file *pf, caddr_t data)
if (err != 0) if (err != 0)
goto done; goto done;
if (!nvlist_exists_nvlist_array(nvl, SNDSTAT_LABEL_DSPS)) { if (!nvlist_exists_nvlist_array(nvl, SNDST_DSPS)) {
err = EINVAL; err = EINVAL;
goto done; goto done;
} }
dsps = nvlist_get_nvlist_array(nvl, SNDSTAT_LABEL_DSPS, &ndsps); dsps = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &ndsps);
for (i = 0; i < ndsps; i++) { for (i = 0; i < ndsps; i++) {
if (!sndstat_dsp_nvlist_is_sane(dsps[i])) { if (!sndstat_dsp_nvlist_is_sane(dsps[i])) {
err = EINVAL; err = EINVAL;
@ -801,8 +916,8 @@ sndstat_flush_user_devs(struct sndstat_file *pf)
static int static int
compat_sndstat_get_devs32(struct sndstat_file *pf, caddr_t data) compat_sndstat_get_devs32(struct sndstat_file *pf, caddr_t data)
{ {
struct sndstat_nvlbuf_arg32 *arg32 = (struct sndstat_nvlbuf_arg32 *)data; struct sndstioc_nv_arg32 *arg32 = (struct sndstioc_nv_arg32 *)data;
struct sndstat_nvlbuf_arg arg; struct sndstioc_nv_arg arg;
int err; int err;
arg.buf = (void *)(uintptr_t)arg32->buf; arg.buf = (void *)(uintptr_t)arg32->buf;
@ -820,8 +935,8 @@ compat_sndstat_get_devs32(struct sndstat_file *pf, caddr_t data)
static int static int
compat_sndstat_add_user_devs32(struct sndstat_file *pf, caddr_t data) compat_sndstat_add_user_devs32(struct sndstat_file *pf, caddr_t data)
{ {
struct sndstat_nvlbuf_arg32 *arg32 = (struct sndstat_nvlbuf_arg32 *)data; struct sndstioc_nv_arg32 *arg32 = (struct sndstioc_nv_arg32 *)data;
struct sndstat_nvlbuf_arg arg; struct sndstioc_nv_arg arg;
int err; int err;
arg.buf = (void *)(uintptr_t)arg32->buf; arg.buf = (void *)(uintptr_t)arg32->buf;
@ -849,11 +964,11 @@ sndstat_ioctl(
return (err); return (err);
switch (cmd) { switch (cmd) {
case SNDSTAT_GET_DEVS: case SNDSTIOC_GET_DEVS:
err = sndstat_get_devs(pf, data); err = sndstat_get_devs(pf, data);
break; break;
#ifdef COMPAT_FREEBSD32 #ifdef COMPAT_FREEBSD32
case SNDSTAT_GET_DEVS32: case SNDSTIOC_GET_DEVS32:
if (!SV_CURPROC_FLAG(SV_ILP32)) { if (!SV_CURPROC_FLAG(SV_ILP32)) {
err = ENODEV; err = ENODEV;
break; break;
@ -861,11 +976,11 @@ sndstat_ioctl(
err = compat_sndstat_get_devs32(pf, data); err = compat_sndstat_get_devs32(pf, data);
break; break;
#endif #endif
case SNDSTAT_ADD_USER_DEVS: case SNDSTIOC_ADD_USER_DEVS:
err = sndstat_add_user_devs(pf, data); err = sndstat_add_user_devs(pf, data);
break; break;
#ifdef COMPAT_FREEBSD32 #ifdef COMPAT_FREEBSD32
case SNDSTAT_ADD_USER_DEVS32: case SNDSTIOC_ADD_USER_DEVS32:
if (!SV_CURPROC_FLAG(SV_ILP32)) { if (!SV_CURPROC_FLAG(SV_ILP32)) {
err = ENODEV; err = ENODEV;
break; break;
@ -873,10 +988,10 @@ sndstat_ioctl(
err = compat_sndstat_add_user_devs32(pf, data); err = compat_sndstat_add_user_devs32(pf, data);
break; break;
#endif #endif
case SNDSTAT_REFRESH_DEVS: case SNDSTIOC_REFRESH_DEVS:
err = sndstat_refresh_devs(pf); err = sndstat_refresh_devs(pf);
break; break;
case SNDSTAT_FLUSH_USER_DEVS: case SNDSTIOC_FLUSH_USER_DEVS:
err = sndstat_flush_user_devs(pf); err = sndstat_flush_user_devs(pf);
break; break;
default: default:

View File

@ -38,56 +38,65 @@
#include <sys/ioccom.h> #include <sys/ioccom.h>
#endif /* !_IOWR */ #endif /* !_IOWR */
struct sndstat_nvlbuf_arg { struct sndstioc_nv_arg {
size_t nbytes; /* [IN/OUT] buffer size/number of bytes filled */ size_t nbytes; /* [IN/OUT] buffer size/number of bytes filled */
void *buf; /* [OUT] buffer holding a packed nvlist */ void *buf; /* [OUT] buffer holding a packed nvlist */
}; };
/* /*
* Common labels * Common name/value pair names
*/ */
#define SNDSTAT_LABEL_DSPS "dsps" #define SNDST_DSPS "dsps"
#define SNDSTAT_LABEL_FROM_USER "from_user" #define SNDST_DSPS_FROM_USER "from_user"
#define SNDSTAT_LABEL_PCHAN "pchan" #define SNDST_DSPS_PCHAN "pchan"
#define SNDSTAT_LABEL_RCHAN "rchan" #define SNDST_DSPS_RCHAN "rchan"
#define SNDSTAT_LABEL_PMINRATE "pminrate" #define SNDST_DSPS_NAMEUNIT "nameunit"
#define SNDSTAT_LABEL_PMAXRATE "pmaxrate" #define SNDST_DSPS_DEVNODE "devnode"
#define SNDSTAT_LABEL_RMINRATE "rminrate" #define SNDST_DSPS_DESC "desc"
#define SNDSTAT_LABEL_RMAXRATE "rmaxrate" #define SNDST_DSPS_PROVIDER "provider"
#define SNDSTAT_LABEL_PFMTS "pfmts" #define SNDST_DSPS_PROVIDER_INFO "provider_info"
#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 * Common name/value pair names for play/rec info
*/ */
#define SNDSTAT_LABEL_SOUND4_PROVIDER "sound(4)" #define SNDST_DSPS_INFO_PLAY "info_play"
#define SNDSTAT_LABEL_SOUND4_UNIT "unit" #define SNDST_DSPS_INFO_REC "info_rec"
#define SNDSTAT_LABEL_SOUND4_BITPERFECT "bitperfect" #define SNDST_DSPS_INFO_MIN_RATE "min_rate"
#define SNDSTAT_LABEL_SOUND4_PVCHAN "pvchan" #define SNDST_DSPS_INFO_MAX_RATE "max_rate"
#define SNDSTAT_LABEL_SOUND4_RVCHAN "rvchan" #define SNDST_DSPS_INFO_FORMATS "formats"
#define SNDST_DSPS_INFO_MIN_CHN "min_chn"
#define SNDST_DSPS_INFO_MAX_CHN "max_chn"
#define SNDSTAT_REFRESH_DEVS _IO('D', 100) /*
#define SNDSTAT_GET_DEVS _IOWR('D', 101, struct sndstat_nvlbuf_arg) * sound(4)-specific name/value pair names
#define SNDSTAT_ADD_USER_DEVS _IOWR('D', 102, struct sndstat_nvlbuf_arg) */
#define SNDSTAT_FLUSH_USER_DEVS _IO('D', 103) #define SNDST_DSPS_SOUND4_PROVIDER "sound(4)"
#define SNDST_DSPS_SOUND4_UNIT "unit"
#define SNDST_DSPS_SOUND4_BITPERFECT "bitperfect"
#define SNDST_DSPS_SOUND4_PVCHAN "pvchan"
#define SNDST_DSPS_SOUND4_RVCHAN "rvchan"
#define SNDSTIOC_REFRESH_DEVS \
_IO('D', 100)
#define SNDSTIOC_GET_DEVS \
_IOWR('D', 101, struct sndstioc_nv_arg)
#define SNDSTIOC_ADD_USER_DEVS \
_IOWR('D', 102, struct sndstioc_nv_arg)
#define SNDSTIOC_FLUSH_USER_DEVS \
_IO('D', 103)
#ifdef _KERNEL #ifdef _KERNEL
#ifdef COMPAT_FREEBSD32 #ifdef COMPAT_FREEBSD32
struct sndstat_nvlbuf_arg32 { struct sndstioc_nv_arg32 {
uint32_t nbytes; uint32_t nbytes;
uint32_t buf; uint32_t buf;
}; };
#define SNDSTAT_GET_DEVS32 \ #define SNDSTIOC_GET_DEVS32 \
_IOC_NEWTYPE(SNDSTAT_GET_DEVS, struct sndstat_nvlbuf_arg32) _IOC_NEWTYPE(SNDSTIOC_GET_DEVS, struct sndstioc_nv_arg32)
#define SNDSTAT_ADD_USER_DEVS32 \ #define SNDSTIOC_ADD_USER_DEVS32 \
_IOC_NEWTYPE(SNDSTAT_ADD_USER_DEVS, struct sndstat_nvlbuf_arg32) _IOC_NEWTYPE(SNDSTIOC_ADD_USER_DEVS, struct sndstioc_nv_arg32)
#endif #endif
#endif #endif