Index | Thread | Search

From:
Alexandre Ratchov <alex@caoua.org>
Subject:
sndio: show the real device name in server.device control
To:
tech@openbsd.org
Date:
Thu, 7 Mar 2024 11:33:54 +0100

Download raw body.

Thread
This diff adds a "display string" to sndio controls, and starts using
it to report the device names of the server.device control. Ex:

$ sndioctl
output.level=1.000
output.mute=0
server.device=6(uaudio2)

$ sndioctl -i server.device
server.device=0(azalia0),2(envy0),3(envy1),4(uaudio0),5(uaudio1),6(uaudio2)

Above strings are simply the driver names. The plan is to change the
low-level drivers to report the chipset, the vendor/product name, the
codec models, or whatever appropriate; there are preliminary diffs for
this. The goal is to avoid having to read the dmesg(8) output and
/etc/rc.conf.local to figure out how to get sound from a given piece
of hardware.

Easing the use of multiple audio devices is almost necessary to work
with many USB audio interfaces and will be necessary to enable all
azalia(4) codecs (ex. HDMI and digital-only ones).

Here's what the diff does:

- Add AUDIO_GETDEV ioctl to pledge(4)'s "audio" promise. The ioctl is
  a simple strlcpy() call in audio(4) copying the driver name.

- Add a "char display[]" member to the sioctl_desc structure. To do
  so, we reuse the padding, which allows binaries linked to the old
  libsndio to use the new libsndio. So this is a shlib_minor bump,
  allowing to test this diff without rebuilding all audio ports.

- The sndiod(8) network protocol gets a new AMSG_CTLSUB op-code to
  subscribe to the new version of the control descriptions (with the
  display string). The old op-code remains, which allows systems or
  VMs with the old libsndio version (or static binaries) to connect to
  the new sndiod.

- To get the driver names, sndiod needs to open the devices, so
  server.device will report only *usable* devices, not only the -fF
  options. This affects the "reopen" logic and as a side effect fixes
  SIGHUP to do what the man-page says: reopen the devices. Even if
  that's not required, this diff updates the MIDI bits to behave
  similarly. This part requires testing.

To test this:

- rebuild the kernel (to update the pledge(4) promise)
- install sndio.h in /usr/include
- rebuild and reinstall libsndio
- rebuild and reinstall sndiod and sndioctl
- reboot

Thoughts? OKs?

Index: sys/kern/kern_pledge.c
===================================================================
RCS file: /cvs/src/sys/kern/kern_pledge.c,v
diff -u -p -u -p -r1.310 kern_pledge.c
--- sys/kern/kern_pledge.c	12 Dec 2023 17:43:10 -0000	1.310
+++ sys/kern/kern_pledge.c	7 Mar 2024 08:23:30 -0000
@@ -1139,6 +1139,7 @@ pledge_ioctl(struct proc *p, long com, s
 #if NAUDIO > 0
 	if ((pledge & PLEDGE_AUDIO)) {
 		switch (com) {
+		case AUDIO_GETDEV:
 		case AUDIO_GETPOS:
 		case AUDIO_GETPAR:
 		case AUDIO_SETPAR:
Index: include/sndio.h
===================================================================
RCS file: /cvs/src/include/sndio.h,v
diff -u -p -u -p -r1.14 sndio.h
--- include/sndio.h	29 Apr 2022 08:30:48 -0000	1.14
+++ include/sndio.h	7 Mar 2024 08:23:30 -0000
@@ -27,8 +27,17 @@
 
 /*
  * limits
+ *
+ * For now SIOCTL_DISPLAYMAX is 12 byte only. It nicely fits in the
+ * padding of the sioctl_desc structure: this allows any binary linked
+ * to the library version with no sioctl_desc->display to work with
+ * this library version. Currently, any string reported by the lower
+ * layers fits in the 12-byte buffer. Once larger strings start
+ * being used (or the ABI changes for any other reason) increase
+ * SIOCTL_DISPLAYMAX and properly pad the sioctl_desc structure.
  */
 #define SIOCTL_NAMEMAX		12	/* max name length */
+#define SIOCTL_DISPLAYMAX	12	/* max display string length */
 
 /*
  * private ``handle'' structure
@@ -115,7 +124,7 @@ struct sioctl_desc {
 	struct sioctl_node node0;	/* affected node */
 	struct sioctl_node node1;	/* dito for SIOCTL_{VEC,LIST,SEL} */
 	unsigned int maxval;		/* max value */
-	int __pad[3];
+	char display[SIOCTL_DISPLAYMAX];	/* free-format hint */
 };
 
 /*
Index: lib/libsndio/amsg.h
===================================================================
RCS file: /cvs/src/lib/libsndio/amsg.h,v
diff -u -p -u -p -r1.15 amsg.h
--- lib/libsndio/amsg.h	29 Apr 2022 08:30:48 -0000	1.15
+++ lib/libsndio/amsg.h	7 Mar 2024 08:23:30 -0000
@@ -46,6 +46,13 @@
  * limits
  */
 #define AMSG_CTL_NAMEMAX	16	/* max name length */
+#define AMSG_CTL_DISPLAYMAX	32	/* max display string length */
+
+/*
+ * Size of the struct amsg_ctl_desc expected by clients
+ * using the AMSG_CTLSUB_OLD request
+ */
+#define AMSG_OLD_DESC_SIZE	92
 
 /*
  * WARNING: since the protocol may be simultaneously used by static
@@ -69,9 +76,10 @@ struct amsg {
 #define AMSG_HELLO	10	/* say hello, check versions and so ... */
 #define AMSG_BYE	11	/* ask server to drop connection */
 #define AMSG_AUTH	12	/* send authentication cookie */
-#define AMSG_CTLSUB	13	/* ondesc/onctl subscription */
+#define AMSG_CTLSUB_OLD	13	/* amsg_ctl_desc with no "display" attribute */
 #define AMSG_CTLSET	14	/* set control value */
 #define AMSG_CTLSYNC	15	/* end of controls descriptions */
+#define AMSG_CTLSUB	16	/* ondesc/onctl subscription */
 	uint32_t cmd;
 	uint32_t __pad;
 	union {
@@ -151,7 +159,8 @@ struct amsg_ctl_desc {
 	uint16_t addr;			/* control address */
 	uint16_t maxval;
 	uint16_t curval;
-	uint32_t __pad2[3];
+	uint32_t __pad2[4];
+	char display[AMSG_CTL_DISPLAYMAX];	/* free-format hint */
 };
 
 /*
Index: lib/libsndio/shlib_version
===================================================================
RCS file: /cvs/src/lib/libsndio/shlib_version,v
diff -u -p -u -p -r1.13 shlib_version
--- lib/libsndio/shlib_version	29 Apr 2022 08:30:48 -0000	1.13
+++ lib/libsndio/shlib_version	7 Mar 2024 08:23:30 -0000
@@ -1,2 +1,2 @@
 major=7
-minor=2
+minor=3
Index: lib/libsndio/sioctl_aucat.c
===================================================================
RCS file: /cvs/src/lib/libsndio/sioctl_aucat.c,v
diff -u -p -u -p -r1.1 sioctl_aucat.c
--- lib/libsndio/sioctl_aucat.c	26 Feb 2020 13:53:58 -0000	1.1
+++ lib/libsndio/sioctl_aucat.c	7 Mar 2024 08:23:30 -0000
@@ -87,6 +87,7 @@ sioctl_aucat_rdata(struct sioctl_aucat_h
 			strlcpy(desc.node1.name, c->node1.name, SIOCTL_NAMEMAX);
 			desc.node1.unit = (int16_t)ntohs(c->node1.unit);
 			strlcpy(desc.func, c->func, SIOCTL_NAMEMAX);
+			strlcpy(desc.display, c->display, SIOCTL_DISPLAYMAX);
 			desc.type = c->type;
 			desc.addr = ntohs(c->addr);
 			desc.maxval = ntohs(c->maxval);
Index: lib/libsndio/sioctl_open.3
===================================================================
RCS file: /cvs/src/lib/libsndio/sioctl_open.3,v
diff -u -p -u -p -r1.13 sioctl_open.3
--- lib/libsndio/sioctl_open.3	3 May 2022 13:03:30 -0000	1.13
+++ lib/libsndio/sioctl_open.3	7 Mar 2024 08:23:30 -0000
@@ -168,6 +168,7 @@ struct sioctl_desc {
 	struct sioctl_node node0;	/* affected node */
 	struct sioctl_node node1;	/* dito for SIOCTL_{VEC,LIST,SEL} */
 	unsigned int maxval;		/* max value */
+	char display[SIOCTL_DISPLAYMAX];	/* free-format hint */
 };
 .Ed
 .Pp
@@ -238,6 +239,11 @@ The
 .Fa maxval
 attribute indicates the maximum value of this control.
 For boolean control types it is set to 1.
+.Pp
+The
+.Fa display
+attribute contains an optional free-format string providing additional
+hints about the control, like the hardware model, or the units.
 .Ss Changing and reading control values
 Controls are changed with the
 .Fn sioctl_setval
Index: lib/libsndio/sioctl_sun.c
===================================================================
RCS file: /cvs/src/lib/libsndio/sioctl_sun.c,v
diff -u -p -u -p -r1.2 sioctl_sun.c
--- lib/libsndio/sioctl_sun.c	30 Apr 2020 12:30:47 -0000	1.2
+++ lib/libsndio/sioctl_sun.c	7 Mar 2024 08:23:31 -0000
@@ -53,6 +53,8 @@ struct volume
 
 struct sioctl_sun_hdl {
 	struct sioctl_hdl sioctl;
+	char display[SIOCTL_DISPLAYMAX];
+	int display_addr;
 	struct volume output, input;
 	int fd, events;
 };
@@ -147,6 +149,7 @@ init(struct sioctl_sun_hdl *hdl)
 		{AudioCinputs, AudioNvolume},
 		{AudioCinputs, AudioNinput}
 	};
+	struct audio_device getdev;
 	int i;
 
 	for (i = 0; i < sizeof(output_names) / sizeof(output_names[0]); i++) {
@@ -165,6 +168,13 @@ init(struct sioctl_sun_hdl *hdl)
 			break;
 		}
 	}
+
+	hdl->display_addr = 128;
+	if (ioctl(hdl->fd, AUDIO_GETDEV, &getdev) == -1)
+		strlcpy(hdl->display, "unknown", SIOCTL_DISPLAYMAX);
+	else
+		strlcpy(hdl->display, getdev.name, SIOCTL_DISPLAYMAX);
+	DPRINTF("init: server.device: display = %s\n", hdl->display);
 }
 
 static int
@@ -407,12 +417,27 @@ static int
 sioctl_sun_ondesc(struct sioctl_hdl *addr)
 {
 	struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
+	struct sioctl_desc desc;
 
 	if (!scanvol(hdl, &hdl->output) ||
 	    !scanvol(hdl, &hdl->input)) {
 		hdl->sioctl.eof = 1;
 		return 0;
 	}
+
+	/* report "server.device" control */
+	memset(&desc, 0, sizeof(struct sioctl_desc));
+	desc.type = SIOCTL_SEL;
+	desc.maxval = 1;
+	strlcpy(desc.func, "device", SIOCTL_NAMEMAX);
+	strlcpy(desc.node0.name, "server", SIOCTL_NAMEMAX);
+	desc.node0.unit = -1;
+	strlcpy(desc.node1.name, "0", SIOCTL_NAMEMAX);
+	desc.node1.unit = -1;
+	strlcpy(desc.display, hdl->display, SIOCTL_DISPLAYMAX);
+	desc.addr = hdl->display_addr;
+	_sioctl_ondesc_cb(&hdl->sioctl, &desc, 1);
+
 	_sioctl_ondesc_cb(&hdl->sioctl, NULL, 0);
 	return 1;
 }
Index: usr.bin/sndiod/dev.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.c,v
diff -u -p -u -p -r1.107 dev.c
--- usr.bin/sndiod/dev.c	9 Dec 2023 22:12:03 -0000	1.107
+++ usr.bin/sndiod/dev.c	7 Mar 2024 08:23:31 -0000
@@ -1055,6 +1055,8 @@ dev_allocbufs(struct dev *d)
 int
 dev_open(struct dev *d)
 {
+	struct opt *o;
+
 	d->mode = d->reqmode;
 	d->round = d->reqround;
 	d->bufsz = d->reqbufsz;
@@ -1077,6 +1079,18 @@ dev_open(struct dev *d)
 		return 0;
 
 	d->pstate = DEV_INIT;
+
+	/* add server.device if device is opened after opt_ref() call */
+	for (o = opt_list; o != NULL; o = o->next) {
+		if (o->refcnt > 0 && !ctl_find(CTL_OPT_DEV, o, d)) {
+			ctl_new(CTL_OPT_DEV, o, d,
+			    CTL_SEL, dev_getdisplay(d),
+			    o->name, "server", -1, "device",
+			    d->name, -1, 1, o->dev == d);
+			d->refcnt++;
+		}
+	}
+
 	return 1;
 }
 
@@ -1108,7 +1122,7 @@ dev_abort(struct dev *d)
 			if (c->ops == NULL)
 				continue;
 			if (c->opt == o) {
-				c->ops->exit(s->arg);
+				c->ops->exit(c->arg);
 				c->ops = NULL;
 			}
 		}
@@ -1151,6 +1165,14 @@ dev_freebufs(struct dev *d)
 void
 dev_close(struct dev *d)
 {
+	struct opt *o;
+
+	/* remove server.device entries */
+	for (o = opt_list; o != NULL; o = o->next) {
+		if (ctl_del(CTL_OPT_DEV, o, d))
+			d->refcnt--;
+	}
+
 	d->pstate = DEV_CFG;
 	dev_sio_close(d);
 	dev_freebufs(d);
@@ -1777,7 +1799,7 @@ slot_new(struct opt *opt, unsigned int i
 	s->opt = opt;
 	slot_ctlname(s, ctl_name, CTL_NAMEMAX);
 	ctl_new(CTL_SLOT_LEVEL, s, NULL,
-	    CTL_NUM, "app", ctl_name, -1, "level",
+	    CTL_NUM, "", "app", ctl_name, -1, "level",
 	    NULL, -1, 127, s->vol);
 
 found:
@@ -2291,6 +2313,14 @@ ctlslot_visible(struct ctlslot *s, struc
 		return 1;
 	switch (c->scope) {
 	case CTL_HW:
+		/*
+		 * Disable hardware's server.device control as its
+		 * replaced by sndiod's one
+		 */
+		if (strcmp(c->node0.name, "server") == 0 &&
+		    strcmp(c->func, "device") == 0)
+			return 0;
+		/* FALLTHROUHG */
 	case CTL_DEV_MASTER:
 		return (s->opt->dev == c->u.any.arg0);
 	case CTL_OPT_DEV:
@@ -2404,6 +2434,11 @@ ctl_log(struct ctl *c)
 	default:
 		log_puts("unknown");
 	}
+	if (c->display[0] != 0) {
+		log_puts(" (");
+		log_puts(c->display);
+		log_puts(")");
+	}
 }
 
 int
@@ -2466,7 +2501,7 @@ ctl_setval(struct ctl *c, int val)
  */
 struct ctl *
 ctl_new(int scope, void *arg0, void *arg1,
-    int type, char *gstr,
+    int type, char *display, char *gstr,
     char *str0, int unit0, char *func,
     char *str1, int unit1, int maxval, int val)
 {
@@ -2490,6 +2525,7 @@ ctl_new(int scope, void *arg0, void *arg
 	c->type = type;
 	strlcpy(c->func, func, CTL_NAMEMAX);
 	strlcpy(c->group, gstr, CTL_NAMEMAX);
+	strlcpy(c->display, display, CTL_DISPLAYMAX);
 	strlcpy(c->node0.name, str0, CTL_NAMEMAX);
 	c->node0.unit = unit0;
 	if (c->type == CTL_VEC || c->type == CTL_LIST || c->type == CTL_SEL) {
@@ -2601,16 +2637,18 @@ ctl_onval(int scope, void *arg0, void *a
 	return 1;
 }
 
-void
+int
 ctl_del(int scope, void *arg0, void *arg1)
 {
 	struct ctl *c, **pc;
+	int found;
 
+	found = 0;
 	pc = &ctl_list;
 	for (;;) {
 		c = *pc;
 		if (c == NULL)
-			return;
+			return found;
 		if (ctl_match(c, scope, arg0, arg1)) {
 #ifdef DEBUG
 			if (log_level >= 2) {
@@ -2618,6 +2656,7 @@ ctl_del(int scope, void *arg0, void *arg
 				log_puts(": removed\n");
 			}
 #endif
+			found++;
 			c->refs_mask &= ~CTL_DEVMASK;
 			if (c->refs_mask == 0) {
 				*pc = c->next;
@@ -2632,6 +2671,41 @@ ctl_del(int scope, void *arg0, void *arg
 }
 
 void
+dev_setdisplay(struct dev *d, char *display)
+{
+	struct ctl *c;
+
+	for (c = ctl_list; c != NULL; c = c->next) {
+		if (c->scope != CTL_OPT_DEV ||
+		    c->u.opt_dev.dev != d ||
+		    strcmp(c->display, display) == 0)
+			continue;
+		strlcpy(c->display, display, CTL_DISPLAYMAX);
+		c->desc_mask = ~0;
+	}
+}
+
+char *
+dev_getdisplay(struct dev *d)
+{
+	struct ctl *c;
+	char *display;
+
+	display = "";
+	for (c = ctl_list; c != NULL; c = c->next) {
+		if (c->scope == CTL_HW &&
+		    c->u.hw.dev == d &&
+		    c->type == CTL_SEL &&
+		    strcmp(c->group, d->name) == 0 &&
+		    strcmp(c->node0.name, "server") == 0 &&
+		    strcmp(c->func, "device") == 0 &&
+		    c->curval == 1)
+			display = c->display;
+	}
+	return display;
+}
+
+void
 dev_ctlsync(struct dev *d)
 {
 	struct ctl *c;
@@ -2649,6 +2723,8 @@ dev_ctlsync(struct dev *d)
 			found = 1;
 	}
 
+	dev_setdisplay(d, dev_getdisplay(d));
+
 	if (d->master_enabled && found) {
 		if (log_level >= 2) {
 			dev_log(d);
@@ -2663,7 +2739,7 @@ dev_ctlsync(struct dev *d)
 		}
 		d->master_enabled = 1;
 		ctl_new(CTL_DEV_MASTER, d, NULL,
-		    CTL_NUM, d->name, "output", -1, "level",
+		    CTL_NUM, "", d->name, "output", -1, "level",
 		    NULL, -1, 127, d->master);
 	}
 
Index: usr.bin/sndiod/dev.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.h,v
diff -u -p -u -p -r1.43 dev.h
--- usr.bin/sndiod/dev.h	26 Dec 2022 19:16:03 -0000	1.43
+++ usr.bin/sndiod/dev.h	7 Mar 2024 08:23:32 -0000
@@ -155,9 +155,11 @@ struct ctl {
 	} u;
 
 	unsigned int addr;		/* slot side control address */
-#define CTL_NAMEMAX	16		/* max name length */
+#define CTL_NAMEMAX	12		/* max name length */
+#define CTL_DISPLAYMAX	24		/* max name length */
 	char func[CTL_NAMEMAX];		/* parameter function name */
 	char group[CTL_NAMEMAX];	/* group aka namespace */
+	char display[CTL_DISPLAYMAX];	/* free-format hint */
 	struct ctl_node {
 		char name[CTL_NAMEMAX];	/* stream name */
 		int unit;
@@ -351,8 +353,8 @@ void slot_detach(struct slot *);
  */
 
 struct ctl *ctl_new(int, void *, void *,
-    int, char *, char *, int, char *, char *, int, int, int);
-void ctl_del(int, void *, void *);
+    int, char *, char *, char *, int, char *, char *, int, int, int);
+int ctl_del(int, void *, void *);
 void ctl_log(struct ctl *);
 int ctl_setval(struct ctl *c, int val);
 int ctl_match(struct ctl *, int, void *, void *);
@@ -367,6 +369,8 @@ struct ctl *ctlslot_lookup(struct ctlslo
 void ctlslot_update(struct ctlslot *);
 
 void dev_label(struct dev *, int);
+void dev_setdisplay(struct dev *, char *);
+char *dev_getdisplay(struct dev *);
 void dev_ctlsync(struct dev *);
 
 #endif /* !defined(DEV_H) */
Index: usr.bin/sndiod/dev_sioctl.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev_sioctl.c,v
diff -u -p -u -p -r1.7 dev_sioctl.c
--- usr.bin/sndiod/dev_sioctl.c	3 Mar 2021 10:00:27 -0000	1.7
+++ usr.bin/sndiod/dev_sioctl.c	7 Mar 2024 08:23:32 -0000
@@ -70,7 +70,7 @@ dev_sioctl_ondesc(void *arg, struct sioc
 	}
 
 	ctl_new(CTL_HW, d, &desc->addr,
-	    desc->type, group,
+	    desc->type, desc->display, group,
 	    desc->node0.name, desc->node0.unit, desc->func,
 	    desc->node1.name, desc->node1.unit, desc->maxval, val);
 }
@@ -89,7 +89,8 @@ dev_sioctl_onval(void *arg, unsigned int
 	log_puts("\n");
 
 	for (c = ctl_list; c != NULL; c = c->next) {
-		if (c->scope != CTL_HW || c->u.hw.addr != addr)
+		if (c->scope != CTL_HW || c->u.hw.dev != d ||
+		    c->u.hw.addr != addr)
 			continue;
 		ctl_log(c);
 		log_puts(": new value -> ");
@@ -97,6 +98,14 @@ dev_sioctl_onval(void *arg, unsigned int
 		log_puts("\n");
 		c->val_mask = ~0U;
 		c->curval = val;
+
+		/* if hardware's server.device changed, update name */
+		if (c->type == CTL_SEL &&
+		    strcmp(c->group, d->name) == 0 &&
+		    strcmp(c->node0.name, "server") == 0 &&
+		    strcmp(c->func, "device") == 0 &&
+		    c->curval == 1)
+			dev_setdisplay(d, c->display);
 	}
 }
 
Index: usr.bin/sndiod/midi.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/midi.c,v
diff -u -p -u -p -r1.29 midi.c
--- usr.bin/sndiod/midi.c	1 Nov 2021 14:43:25 -0000	1.29
+++ usr.bin/sndiod/midi.c	7 Mar 2024 08:23:32 -0000
@@ -155,6 +155,23 @@ midi_link(struct midi *ep, struct midi *
 }
 
 /*
+ * return the list of endpoints the given one receives from
+ */
+unsigned int
+midi_rxmask(struct midi *ep)
+{
+	int i, rxmask;
+
+	for (rxmask = 0, i = 0; i < MIDI_NEP; i++) {
+		if ((midi_ep[i].txmask & ep->self) == 0)
+			continue;
+		rxmask |= midi_ep[i].self;
+	}
+
+	return rxmask;
+}
+
+/*
  * add the midi endpoint in the ``tag'' midi thru box
  */
 void
Index: usr.bin/sndiod/midi.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/midi.h,v
diff -u -p -u -p -r1.15 midi.h
--- usr.bin/sndiod/midi.h	1 Nov 2021 14:43:25 -0000	1.15
+++ usr.bin/sndiod/midi.h	7 Mar 2024 08:23:32 -0000
@@ -112,6 +112,7 @@ void midi_send(struct midi *, unsigned c
 void midi_fill(struct midi *);
 void midi_tag(struct midi *, unsigned int);
 unsigned int midi_tags(struct midi *);
+unsigned int midi_rxmask(struct midi *);
 void midi_link(struct midi *, struct midi *);
 void midi_abort(struct midi *);
 void midi_migrate(struct midi *, struct midi *);
Index: usr.bin/sndiod/opt.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/opt.c,v
diff -u -p -u -p -r1.9 opt.c
--- usr.bin/sndiod/opt.c	1 Nov 2021 14:43:25 -0000	1.9
+++ usr.bin/sndiod/opt.c	7 Mar 2024 08:23:32 -0000
@@ -346,15 +346,6 @@ opt_del(struct opt *o)
 void
 opt_init(struct opt *o)
 {
-	struct dev *d;
-
-	if (strcmp(o->name, o->dev->name) != 0) {
-		for (d = dev_list; d != NULL; d = d->next) {
-			ctl_new(CTL_OPT_DEV, o, d,
-			    CTL_SEL, o->name, "server", -1, "device",
-			    d->name, -1, 1, o->dev == d);
-		}
-	}
 }
 
 void
@@ -382,6 +373,7 @@ opt_setdev(struct opt *o, struct dev *nd
 	struct ctl *c;
 	struct ctlslot *p;
 	struct slot *s;
+	char *display;
 	int i;
 
 	if (!dev_ref(ndev))
@@ -437,6 +429,11 @@ opt_setdev(struct opt *o, struct dev *nd
 	if (c != NULL) {
 		c->curval = 1;
 		c->val_mask = ~0;
+		display = dev_getdisplay(o->dev);
+		if (strcmp(c->display, display) != 0) {
+			strlcpy(c->display, display, CTL_DISPLAYMAX);
+			c->desc_mask = ~0;
+		}
 	}
 
 	/* attach clients to new device */
@@ -496,6 +493,16 @@ opt_ref(struct opt *o)
 			/* if device changed, move everything to the new one */
 			if (d != o->dev)
 				opt_setdev(o, d);
+
+			/* create server.device control */
+			for (d = dev_list; d != NULL; d = d->next) {
+				if (!dev_ref(d))
+					continue;
+				ctl_new(CTL_OPT_DEV, o, d,
+				    CTL_SEL, dev_getdisplay(d),
+				    o->name, "server", -1, "device",
+				    d->name, -1, 1, o->dev == d);
+			}
 		}
 	}
 
@@ -509,7 +516,15 @@ opt_ref(struct opt *o)
 void
 opt_unref(struct opt *o)
 {
+	struct dev *d;
+
 	o->refcnt--;
-	if (o->refcnt == 0)
+	if (o->refcnt == 0) {
+		/* delete server.device control */
+		for (d = dev_list; d != NULL; d = d->next) {
+			if (ctl_del(CTL_OPT_DEV, o, d))
+				dev_unref(d);
+		}
 		dev_unref(o->dev);
+	}
 }
Index: usr.bin/sndiod/siofile.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/siofile.c,v
diff -u -p -u -p -r1.26 siofile.c
--- usr.bin/sndiod/siofile.c	29 Apr 2022 08:30:48 -0000	1.26
+++ usr.bin/sndiod/siofile.c	7 Mar 2024 08:23:32 -0000
@@ -84,6 +84,7 @@ dev_sio_timeout(void *arg)
 
 	dev_log(d);
 	log_puts(": watchdog timeout\n");
+	dev_migrate(d);
 	dev_abort(d);
 }
 
Index: usr.bin/sndiod/sndiod.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sndiod.c,v
diff -u -p -u -p -r1.48 sndiod.c
--- usr.bin/sndiod/sndiod.c	7 Mar 2022 08:58:33 -0000	1.48
+++ usr.bin/sndiod/sndiod.c	7 Mar 2024 08:23:32 -0000
@@ -254,6 +254,79 @@ opt_mode(void)
 	return mode;
 }
 
+/*
+ * Open all devices. Possibly switch to the new devices if they have higher
+ * priorities than the current ones.
+ */
+static void
+dev_reopen(void)
+{
+	struct opt *o;
+	struct dev *d, *dpri;
+
+	for (o = opt_list; o != NULL; o = o->next) {
+
+		/* skip unused logical devices and ones with fixed hardware */
+		if (o->refcnt == 0 || strcmp(o->name, o->dev->name) == 0)
+			continue;
+
+		/* open alt devices, find the one with the highest prio */
+		dpri = NULL;
+		d = o->alt_first;
+		do {
+			if (d->pstate == DEV_CFG && !dev_open(d))
+				continue;
+			if (dpri == NULL)
+				dpri = d;
+		} while ((d = d->alt_next) != o->alt_first);
+
+		/* switch to the alt device with the highest prio */
+		if (o->dev != dpri)
+			opt_setdev(o, dpri);
+	}
+}
+
+/*
+ * For each port, open the alt with the highest priority and switch to it
+ */
+static void
+port_reopen(void)
+{
+	struct port *p, *a, *apri;
+	int inuse;
+
+	for (p = port_list; p != NULL; p = a->next) {
+
+		/* skip unused ports */
+		inuse = 0;
+		a = p;
+		while (1) {
+			if (midi_rxmask(a->midi) || a->midi->txmask)
+				inuse = 1;
+			if (a->alt_next == p)
+				break;
+			a = a->alt_next;
+		}
+		if (!inuse)
+			continue;
+
+		/* open the alt with the highest prio */
+		apri = port_alt_ref(p->num);
+
+		/* switch to it */
+		a = p;
+		while (1) {
+			if (a != apri) {
+				midi_migrate(a->midi, apri->midi);
+				port_unref(a);
+			}
+			if (a->alt_next == p)
+				break;
+			a = a->alt_next;
+		}
+	}
+}
+
 void
 setsig(void)
 {
@@ -461,7 +534,6 @@ main(int argc, char **argv)
 	char base[SOCKPATH_MAX], path[SOCKPATH_MAX];
 	unsigned int mode, dup, mmc, vol;
 	unsigned int hold, autovol, bufsz, round, rate;
-	unsigned int reopen_list;
 	const char *str;
 	struct aparams par;
 	struct opt *o;
@@ -712,33 +784,14 @@ main(int argc, char **argv)
 		if (pledge("stdio audio recvfd unix", NULL) == -1)
 			err(1, "pledge");
 	}
+
 	for (;;) {
 		if (quit_flag)
 			break;
 		if (reopen_flag) {
 			reopen_flag = 0;
-
-			reopen_list = 0;
-			for (d = dev_list; d != NULL; d = d->next) {
-				if (d->pstate != DEV_CFG)
-					reopen_list |= (1 << d->num);
-			}
-			for (d = dev_list; d != NULL; d = d->next) {
-				if (reopen_list & (1 << d->num))
-					dev_migrate(d);
-			}
-
-			reopen_list = 0;
-			for (p = port_list; p != NULL; p = p->next) {
-				if (p->state != PORT_CFG)
-					reopen_list |= (1 << p->num);
-			}
-			for (p = port_list; p != NULL; p = p->next) {
-				if (reopen_list & (1 << p->num)) {
-					if (port_migrate(p) != p)
-						port_close(p);
-				}
-			}
+			dev_reopen();
+			port_reopen();
 		}
 		if (!fdpass_peer)
 			break;
Index: usr.bin/sndiod/sock.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sock.c,v
diff -u -p -u -p -r1.47 sock.c
--- usr.bin/sndiod/sock.c	26 Dec 2022 19:16:03 -0000	1.47
+++ usr.bin/sndiod/sock.c	7 Mar 2024 08:23:33 -0000
@@ -33,7 +33,7 @@
 #include "sock.h"
 #include "utils.h"
 
-#define SOCK_CTLDESC_SIZE	16	/* number of entries in s->ctldesc */
+#define SOCK_CTLDESC_SIZE	0x800	/* size of s->ctldesc */
 
 void sock_log(struct sock *);
 void sock_close(struct sock *);
@@ -626,8 +626,7 @@ sock_wdata(struct sock *f)
 		else if (f->midi)
 			data = abuf_rgetblk(&f->midi->obuf, &count);
 		else {
-			data = (unsigned char *)f->ctldesc +
-			    (f->wsize - f->wtodo);
+			data = f->ctldesc + (f->wsize - f->wtodo);
 			count = f->wtodo;
 		}
 		if (count > f->wtodo)
@@ -961,8 +960,7 @@ sock_hello(struct sock *f)
 			}
 			return 0;
 		}
-		f->ctldesc = xmalloc(SOCK_CTLDESC_SIZE *
-		    sizeof(struct amsg_ctl_desc));
+		f->ctldesc = xmalloc(SOCK_CTLDESC_SIZE);
 		f->ctlops = 0;
 		f->ctlsyncpending = 0;
 		return 1;
@@ -989,8 +987,10 @@ sock_execmsg(struct sock *f)
 	struct amsg *m = &f->rmsg;
 	unsigned char *data;
 	int size, ctl;
+	int cmd;
 
-	switch (ntohl(m->cmd)) {
+	cmd = ntohl(m->cmd);
+	switch (cmd) {
 	case AMSG_DATA:
 #ifdef DEBUG
 		if (log_level >= 4) {
@@ -1284,6 +1284,7 @@ sock_execmsg(struct sock *f)
 		dev_midi_vol(s->opt->dev, s);
 		ctl_onval(CTL_SLOT_LEVEL, s, NULL, ctl);
 		break;
+	case AMSG_CTLSUB_OLD:
 	case AMSG_CTLSUB:
 #ifdef DEBUG
 		if (log_level >= 3) {
@@ -1316,6 +1317,9 @@ sock_execmsg(struct sock *f)
 				}
 				f->ctlops |= SOCK_CTLDESC;
 				f->ctlsyncpending = 1;
+				f->ctl_desc_size = (cmd == AMSG_CTLSUB) ?
+				    sizeof(struct amsg_ctl_desc) :
+				    AMSG_OLD_DESC_SIZE;
 			}
 		} else
 			f->ctlops &= ~SOCK_CTLDESC;
@@ -1604,7 +1608,6 @@ sock_buildmsg(struct sock *f)
 	 * searching for the {desc,val}_mask bits
 	 */
 	if (f->ctlslot && (f->ctlops & SOCK_CTLDESC)) {
-		desc = f->ctldesc;
 		mask = f->ctlslot->self;
 		size = 0;
 		pc = &ctl_list;
@@ -1614,9 +1617,9 @@ sock_buildmsg(struct sock *f)
 				pc = &c->next;
 				continue;
 			}
-			if (size == SOCK_CTLDESC_SIZE *
-				sizeof(struct amsg_ctl_desc))
+			if (size + f->ctl_desc_size > SOCK_CTLDESC_SIZE)
 				break;
+			desc = (struct amsg_ctl_desc *)(f->ctldesc + size);
 			c->desc_mask &= ~mask;
 			c->val_mask &= ~mask;
 			type = ctlslot_visible(f->ctlslot, c) ?
@@ -1633,8 +1636,14 @@ sock_buildmsg(struct sock *f)
 			desc->addr = htons(c->addr);
 			desc->maxval = htons(c->maxval);
 			desc->curval = htons(c->curval);
-			size += sizeof(struct amsg_ctl_desc);
-			desc++;
+
+			/* old clients don't have the 'display' member */
+			if (f->ctl_desc_size >= offsetof(struct amsg_ctl_desc,
+				display) + AMSG_CTL_DISPLAYMAX) {
+				strlcpy(desc->display, c->display, AMSG_CTL_DISPLAYMAX);
+			}
+
+			size += f->ctl_desc_size;
 
 			/* if this is a deleted entry unref it */
 			if (type == CTL_NONE) {
@@ -1660,6 +1669,7 @@ sock_buildmsg(struct sock *f)
 				log_puts(": building control DATA message\n");
 			}
 #endif
+			f->ctlsyncpending = 1;
 			return 1;
 		}
 	}
Index: usr.bin/sndiod/sock.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sock.h,v
diff -u -p -u -p -r1.7 sock.h
--- usr.bin/sndiod/sock.h	26 Apr 2020 14:13:22 -0000	1.7
+++ usr.bin/sndiod/sock.h	7 Mar 2024 08:23:33 -0000
@@ -59,7 +59,8 @@ struct sock {
 	struct midi *midi;		/* midi endpoint */
 	struct port *port;		/* midi port */
 	struct ctlslot *ctlslot;
-	struct amsg_ctl_desc *ctldesc;	/* temporary buffer */
+	unsigned char *ctldesc;		/* temporary buffer */
+	size_t ctl_desc_size;		/* size of client amsg_ctl_desc */
 #define SOCK_CTLDESC	1		/* dump desc and send changes */
 #define SOCK_CTLVAL	2		/* send value changes */
 	unsigned int ctlops;		/* bitmap of above */
Index: usr.bin/sndioctl/sndioctl.c
===================================================================
RCS file: /cvs/src/usr.bin/sndioctl/sndioctl.c,v
diff -u -p -u -p -r1.19 sndioctl.c
--- usr.bin/sndioctl/sndioctl.c	31 Jan 2023 21:38:01 -0000	1.19
+++ usr.bin/sndioctl/sndioctl.c	7 Mar 2024 08:23:33 -0000
@@ -47,6 +47,7 @@ int matchpar(struct info *, char *, int)
 int matchent(struct info *, char *, int);
 int ismono(struct info *);
 void print_node(struct sioctl_node *, int);
+void print_display(struct info *);
 void print_desc(struct info *, int);
 void print_num(struct info *);
 void print_ent(struct info *, char *);
@@ -310,6 +311,9 @@ ismono(struct info *g)
 						continue;
 					if (e1->curval != e2->curval)
 						return 0;
+					if (strcmp(e1->desc.display,
+						e2->desc.display) != 0)
+						return 0;
 				}
 			}
 		}
@@ -330,6 +334,28 @@ print_node(struct sioctl_node *c, int mo
 }
 
 /*
+ * print display string, with '(' and ')' and non-printable chars removed
+ * in order to match command syntax
+ */
+void
+print_display(struct info *p)
+{
+	char buf[SIOCTL_NAMEMAX], *s, *d;
+	unsigned int c;
+
+	s = p->desc.display;
+	d = buf;
+	while ((c = *s++) != 0) {
+		if (c == '(' || c == ')' || c < ' ')
+			continue;
+		*d++ = c;
+	}
+	*d = 0;
+	if (buf[0] != 0)
+		printf("(%s)", buf);
+}
+
+/*
  * print info about the parameter
  */
 void
@@ -342,6 +368,7 @@ print_desc(struct info *p, int mono)
 	case SIOCTL_NUM:
 	case SIOCTL_SW:
 		printf("*");
+		print_display(p);
 		break;
 	case SIOCTL_SEL:
 	case SIOCTL_VEC:
@@ -359,6 +386,8 @@ print_desc(struct info *p, int mono)
 			print_node(&e->desc.node1, mono);
 			if (p->desc.type != SIOCTL_SEL)
 				printf(":*");
+			if (e->desc.display[0] != 0)
+				print_display(e);
 			more = 1;
 		}
 	}
@@ -404,6 +433,7 @@ print_ent(struct info *e, char *comment)
 	case SIOCTL_NUM:
 		print_num(e);
 	}
+	print_display(e);
 	if (comment)
 		printf("\t# %s", comment);
 	printf("\n");
@@ -422,6 +452,7 @@ print_val(struct info *p, int mono)
 	case SIOCTL_NUM:
 	case SIOCTL_SW:
 		print_num(p);
+		print_display(p);
 		break;
 	case SIOCTL_SEL:
 	case SIOCTL_VEC:
@@ -439,6 +470,7 @@ print_val(struct info *p, int mono)
 					if (more)
 						printf(",");
 					print_node(&e->desc.node1, mono);
+					print_display(e);
 					more = 1;
 				}
 			} else {
@@ -447,6 +479,7 @@ print_val(struct info *p, int mono)
 				print_node(&e->desc.node1, mono);
 				printf(":");
 				print_num(e);
+				print_display(e);
 				more = 1;
 			}
 		}
@@ -631,6 +664,7 @@ dump(void)
 			print_node(&i->desc.node1, 0);
 			printf(":0..%d (%u)", i->desc.maxval, i->curval);
 		}
+		print_display(i);
 		printf("\n");
 	}
 }
@@ -753,6 +787,12 @@ cmd(char *line)
 					}
 				}
 			}
+			if (*pos == '(') {
+				while (*pos != 0) {
+					if (*pos++ == ')')
+						break;
+				}
+			}
 			if (nent == 0) {
 				/* XXX: use print_node()-like routine */
 				fprintf(stderr, "%s[%d]: invalid value\n", vstr, vunit);
@@ -879,12 +919,7 @@ ondesc(void *arg, struct sioctl_desc *d,
 	 */
 	for (pi = &infolist; (i = *pi) != NULL; pi = &i->next) {
 		cmp = cmpdesc(d, &i->desc);
-		if (cmp == 0) {
-			fprintf(stderr, "fatal: duplicate control:\n");
-			print_ent(i, "duplicate");
-			exit(1);
-		}
-		if (cmp < 0)
+		if (cmp <= 0)
 			break;
 	}
 	i = malloc(sizeof(struct info));