git: e6ecffccdec6 - stable/14 - sound: Include sound(4) channel information in sndstat nvlist

From: Christos Margiolis <christos_at_FreeBSD.org>
Date: Fri, 14 Jun 2024 16:58:05 UTC
The branch stable/14 has been updated by christos:

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

commit e6ecffccdec60420c9e3654b8c9c4408e2fe4537
Author:     Christos Margiolis <christos@FreeBSD.org>
AuthorDate: 2024-06-09 15:30:22 +0000
Commit:     Christos Margiolis <christos@FreeBSD.org>
CommitDate: 2024-06-14 16:57:57 +0000

    sound: Include sound(4) channel information in sndstat nvlist
    
    Extend SNDST_DSPS_PROVIDER_INFO for sound(4) to include information
    about each channel in a given device, similar to how cat'ing
    /dev/sndstat with hw.snd.verbose=2 works.
    
    While here, document all provider_info fields.
    
    Sponsored by:   The FreeBSD Foundation
    MFC after:      3 days
    Reviewed by:    dev_submerge.ch, markj
    Differential Revision:  https://reviews.freebsd.org/D45501
    
    (cherry picked from commit bbca3a75bb412f7106a569b82c616404103be084)
---
 share/man/man4/sndstat.4    | 140 +++++++++++++++++++++++++++++++++++++-------
 sys/dev/sound/pcm/sndstat.c | 115 +++++++++++++++++++++++++++++++++++-
 sys/sys/sndstat.h           |  37 ++++++++++--
 3 files changed, 265 insertions(+), 27 deletions(-)

diff --git a/share/man/man4/sndstat.4 b/share/man/man4/sndstat.4
index ba130dacaecb..f638fc6834fe 100644
--- a/share/man/man4/sndstat.4
+++ b/share/man/man4/sndstat.4
@@ -29,7 +29,7 @@
 .\"
 .\" Note: The date here should be updated whenever a non-trivial
 .\" change is made to the manual page.
-.Dd April 15, 2021
+.Dd June 5, 2024
 .Dt SNDSTAT 4
 .Os
 .Sh NAME
@@ -60,25 +60,55 @@ struct sndstioc_nv_arg {
 Here is an example of an nvlist object 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)
-    info_play (NVLIST):
-        min_rate (NUMBER): 48000 (48000) (0xbb80)
-        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):
-        unit (NUMBER): 0 (0) (0x0)
-        bitperfect (BOOL): FALSE
-        pvchan (NUMBER): 1 (1) (0x1)
-        rvchan (NUMBER): 0 (0) (0x0)
-    provider (STRING): [sound(4)]
-    ,
+	from_user (BOOL): FALSE
+	nameunit (STRING): [pcm0]
+	devnode (STRING): [dsp0]
+	desc (STRING): [Generic (0x8086) (Analog Line-out)]
+	pchan (NUMBER): 1
+	rchan (NUMBER): 0
+	info_play (NVLIST):
+		min_rate (NUMBER): 48000
+		max_rate (NUMBER): 48000
+		formats (NUMBER): 16
+		min_chn (NUMBER): 2
+		max_chn (NUMBER): 2
+	provider_info (NVLIST):
+		unit (NUMBER): 0
+		bitperfect (BOOL): FALSE
+		pvchan (NUMBER): 1
+		rvchan (NUMBER): 0
+		channel_info (NVLIST_ARRAY): 1
+			name (STRING): pcm0:virtual_play:dsp0.vp0
+			parentchan (STRING): pcm0:play:dsp0.p0
+			unit (NUMBER): 1
+			latency (NUMBER): 2
+			rate (NUMBER): 48000
+			format (NUMBER): 0x201000
+			pid (NUMBER): 1234
+			comm (STRING): mpv
+			interrupts (NUMBER): 0
+			feedcount (NUMBER): 0
+			xruns (NUMBER): 0
+			left_volume (NUMBER): 45
+			right_volume (NUMBER): 45
+			hwbuf_fmt (NUMBER): 0x200010
+			hwbuf_size (NUMBER): 0
+			hwbuf_blksz (NUMBER): 0
+			hwbuf_blkcnt (NUMBER): 0
+			hwbuf_free (NUMBER): 0
+			hwbuf_ready (NUMBER): 0
+			swbuf_fmt (NUMBER): 0x201000
+			swbuf_size (NUMBER): 16384
+			swbuf_blksz (NUMBER): 2048
+			swbuf_blkcnt (NUMBER): 8
+			swbuf_free (NUMBER): 16384
+			swbuf_ready (NUMBER): 0
+			feederchain (STRING):
+				[userland ->
+				feeder_root(0x00201000) ->
+				feeder_format(0x00201000 -> 0x00200010) ->
+				feeder_volume(0x00200010) -> hardware]
+	provider (STRING): [sound(4)]
 .Ed
 .Bl -tag -width ".Dv provider_info"
 .It Dv from_user
@@ -133,6 +163,76 @@ 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.
+For the
+.Xr sound 4
+provider, there are a number of name/value pairs inside this field:
+.Bl -tag -width ".Dv channel_info"
+.It Dv unit
+Sound card unit.
+.It Dv bitperfect
+Whether the sound card has bit-perfect mode enabled.
+.It Dv pvchan
+Number of playback virtual channels.
+.It Dv rvchan
+Number of recording virtual channels.
+.It Dv channel_info
+Channel information.
+There are a number of name/value pairs inside this field:
+.Bl -tag -width ".Dv hwbuf_blkcnt"
+.It Dv name
+Channel name.
+.It Dv parenchan
+Parent channel name (e.g., in the case of virtual channels).
+.It Dv unit
+Channel unit.
+.It Dv latency
+Latency.
+.It Dv rate
+Sampling rate.
+.It Dv format
+Sampling format.
+.It Dv pid
+PID of the process consuming the channel.
+.It Dv comm
+Name of the process consuming the channel.
+.It Dv interrupts
+Number of interrupts since the channel has been opened.
+.It Dv xruns
+Number of overruns/underruns, depending on channel direction.
+.It Dv feedcount
+Number of read/written bytes since the channel has been opened.
+.It Dv left_volume
+Left volume.
+.It Dv right_volume
+Right volume.
+.It Dv hwbuf_format
+Hardware buffer format.
+.It Dv hwbuf_size
+Hardware buffer size.
+.It Dv hwbuf_blksz
+Hardware buffer block size.
+.It Dv hwbuf_blkcnt
+Hardware buffer block count.
+.It Dv hwbuf_free
+Free space in hardware buffer (in bytes).
+.It Dv hwbuf_ready
+Number of bytes ready to be read/written from hardware buffer.
+.It Dv swbuf_format
+Software buffer format.
+.It Dv swbuf_size
+Software buffer size.
+.It Dv swbuf_blksz
+Software buffer block size.
+.It Dv swbuf_blkcnt
+Software buffer block count.
+.It Dv swbuf_free
+Free space in software buffer (in bytes).
+.It Dv swbuf_ready
+Number of bytes ready to be read/written from software buffer.
+.It Dv feederchain
+Channel feeder chain.
+.El
+.El
 .It Dv provider
 A string specifying the provider of the PCm audio device.
 .El
diff --git a/sys/dev/sound/pcm/sndstat.c b/sys/dev/sound/pcm/sndstat.c
index 6670a1e43aac..3be376e1da01 100644
--- a/sys/dev/sound/pcm/sndstat.c
+++ b/sys/dev/sound/pcm/sndstat.c
@@ -392,9 +392,12 @@ sndstat_create_diinfo_nv(uint32_t min_rate, uint32_t max_rate, uint32_t formats,
 static int
 sndstat_build_sound4_nvlist(struct snddev_info *d, nvlist_t **dip)
 {
+	struct pcm_channel *c;
+	struct pcm_feeder *f;
+	struct sbuf sb;
 	uint32_t maxrate, minrate, fmts, minchn, maxchn;
-	nvlist_t *di = NULL, *sound4di = NULL, *diinfo = NULL;
-	int err;
+	nvlist_t *di = NULL, *sound4di = NULL, *diinfo = NULL, *cdi = NULL;
+	int err, nchan;
 
 	di = nvlist_create(0);
 	if (di == NULL) {
@@ -451,8 +454,116 @@ sndstat_build_sound4_nvlist(struct snddev_info *d, nvlist_t **dip)
 	    sound4di, SNDST_DSPS_SOUND4_BITPERFECT, d->flags & SD_F_BITPERFECT);
 	nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_PVCHAN, d->pvchancount);
 	nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_RVCHAN, d->rvchancount);
+
+	nchan = 0;
+	CHN_FOREACH(c, d, channels.pcm) {
+		sbuf_new(&sb, NULL, 4096, SBUF_AUTOEXTEND);
+		cdi = nvlist_create(0);
+		if (cdi == NULL) {
+			sbuf_delete(&sb);
+			PCM_RELEASE_QUICK(d);
+			err = ENOMEM;
+			goto done;
+		}
+
+		nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_NAME, c->name);
+		nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_PARENTCHAN,
+		    c->parentchannel != NULL ? c->parentchannel->name : "");
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_UNIT, nchan++);
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_LATENCY,
+		    c->latency);
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_RATE, c->speed);
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_FORMAT,
+		    c->format);
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_PID, c->pid);
+		nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_COMM, c->comm);
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_INTR,
+		    c->interrupts);
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_FEEDCNT,
+		    c->feedcount);
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_XRUNS, c->xruns);
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_LEFTVOL,
+		    CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FL));
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_RIGHTVOL,
+		    CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FR));
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_FORMAT,
+		    sndbuf_getfmt(c->bufhard));
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_SIZE,
+		    sndbuf_getsize(c->bufhard));
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_BLKSZ,
+		    sndbuf_getblksz(c->bufhard));
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_BLKCNT,
+		    sndbuf_getblkcnt(c->bufhard));
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_FREE,
+		    sndbuf_getfree(c->bufhard));
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_READY,
+		    sndbuf_getready(c->bufhard));
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_FORMAT,
+		    sndbuf_getfmt(c->bufsoft));
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_SIZE,
+		    sndbuf_getsize(c->bufsoft));
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_BLKSZ,
+		    sndbuf_getblksz(c->bufsoft));
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_BLKCNT,
+		    sndbuf_getblkcnt(c->bufsoft));
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_FREE,
+		    sndbuf_getfree(c->bufsoft));
+		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_READY,
+		    sndbuf_getready(c->bufsoft));
+
+		sbuf_printf(&sb, "[%s",
+		    (c->direction == PCMDIR_REC) ? "hardware" : "userland");
+		sbuf_printf(&sb, " -> ");
+		f = c->feeder;
+		while (f->source != NULL)
+			f = f->source;
+		while (f != NULL) {
+			sbuf_printf(&sb, "%s", f->class->name);
+			if (f->desc->type == FEEDER_FORMAT) {
+				sbuf_printf(&sb, "(0x%08x -> 0x%08x)",
+				    f->desc->in, f->desc->out);
+			} else if (f->desc->type == FEEDER_MATRIX) {
+				sbuf_printf(&sb, "(%d.%d -> %d.%d)",
+				    AFMT_CHANNEL(f->desc->in) -
+				    AFMT_EXTCHANNEL(f->desc->in),
+				    AFMT_EXTCHANNEL(f->desc->in),
+				    AFMT_CHANNEL(f->desc->out) -
+				    AFMT_EXTCHANNEL(f->desc->out),
+				    AFMT_EXTCHANNEL(f->desc->out));
+			} else if (f->desc->type == FEEDER_RATE) {
+				sbuf_printf(&sb,
+				    "(0x%08x q:%d %d -> %d)",
+				    f->desc->out,
+				    FEEDER_GET(f, FEEDRATE_QUALITY),
+				    FEEDER_GET(f, FEEDRATE_SRC),
+				    FEEDER_GET(f, FEEDRATE_DST));
+			} else {
+				sbuf_printf(&sb, "(0x%08x)",
+				    f->desc->out);
+			}
+			sbuf_printf(&sb, " -> ");
+			f = f->parent;
+		}
+		sbuf_printf(&sb, "%s]",
+		    (c->direction == PCMDIR_REC) ? "userland" : "hardware");
+
+		sbuf_finish(&sb);
+		nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_FEEDERCHAIN,
+		    sbuf_data(&sb));
+		sbuf_delete(&sb);
+
+		nvlist_append_nvlist_array(sound4di,
+		    SNDST_DSPS_SOUND4_CHAN_INFO, cdi);
+		nvlist_destroy(cdi);
+		err = nvlist_error(sound4di);
+		if (err) {
+			PCM_RELEASE_QUICK(d);
+			goto done;
+		}
+	}
 	nvlist_move_nvlist(di, SNDST_DSPS_PROVIDER_INFO, sound4di);
 	sound4di = NULL;
+
 	PCM_RELEASE_QUICK(d);
 	nvlist_add_string(di, SNDST_DSPS_PROVIDER, SNDST_DSPS_SOUND4_PROVIDER);
 
diff --git a/sys/sys/sndstat.h b/sys/sys/sndstat.h
index e0e403b1a72a..6fef6502ec89 100644
--- a/sys/sys/sndstat.h
+++ b/sys/sys/sndstat.h
@@ -68,11 +68,38 @@ struct sndstioc_nv_arg {
 /*
  * sound(4)-specific name/value pair names
  */
-#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 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 SNDST_DSPS_SOUND4_CHAN_INFO		"channel_info"
+#define SNDST_DSPS_SOUND4_CHAN_NAME		"name"
+#define SNDST_DSPS_SOUND4_CHAN_PARENTCHAN	"parentchan"
+#define SNDST_DSPS_SOUND4_CHAN_UNIT		"unit"
+#define SNDST_DSPS_SOUND4_CHAN_LATENCY		"latency"
+#define SNDST_DSPS_SOUND4_CHAN_RATE		"rate"
+#define SNDST_DSPS_SOUND4_CHAN_FORMAT		"format"
+#define SNDST_DSPS_SOUND4_CHAN_PID		"pid"
+#define SNDST_DSPS_SOUND4_CHAN_COMM		"comm"
+#define SNDST_DSPS_SOUND4_CHAN_INTR		"interrupts"
+#define SNDST_DSPS_SOUND4_CHAN_FEEDCNT		"feedcount"
+#define SNDST_DSPS_SOUND4_CHAN_XRUNS		"xruns"
+#define SNDST_DSPS_SOUND4_CHAN_LEFTVOL		"left_volume"
+#define SNDST_DSPS_SOUND4_CHAN_RIGHTVOL		"right_volume"
+#define SNDST_DSPS_SOUND4_CHAN_HWBUF_FORMAT	"hwbuf_format"
+#define SNDST_DSPS_SOUND4_CHAN_HWBUF_SIZE	"hwbuf_size"
+#define SNDST_DSPS_SOUND4_CHAN_HWBUF_BLKSZ	"hwbuf_blksz"
+#define SNDST_DSPS_SOUND4_CHAN_HWBUF_BLKCNT	"hwbuf_blkcnt"
+#define SNDST_DSPS_SOUND4_CHAN_HWBUF_FREE	"hwbuf_free"
+#define SNDST_DSPS_SOUND4_CHAN_HWBUF_READY	"hwbuf_ready"
+#define SNDST_DSPS_SOUND4_CHAN_SWBUF_FORMAT	"swbuf_format"
+#define SNDST_DSPS_SOUND4_CHAN_SWBUF_SIZE	"swbuf_size"
+#define SNDST_DSPS_SOUND4_CHAN_SWBUF_BLKSZ	"swbuf_blksz"
+#define SNDST_DSPS_SOUND4_CHAN_SWBUF_BLKCNT	"swbuf_blkcnt"
+#define SNDST_DSPS_SOUND4_CHAN_SWBUF_FREE	"swbuf_free"
+#define SNDST_DSPS_SOUND4_CHAN_SWBUF_READY	"swbuf_ready"
+#define SNDST_DSPS_SOUND4_CHAN_FEEDERCHAIN	"feederchain"
 
 /*
  * Maximum user-specified nvlist buffer size