sound: Use unr(9) to produce unique channel unit numbers

Currently it is possible to assign a unit number that already exists.
Suppose the following channel list:

[vp2]

If we create 3 channels, we'll end up with the following list:

[vp0, vp1, vp2, vp2]

This happens because chn_init() does not check if the unit number we are
assigning already exists. While fixing this is trivial when the channel
list is sorted in ascending order, it is way more involved when sorted
in descending order. Even though sorting the list in descending order
would require deliberately modifying pcm_chn_add(), and is most likely
not going to happen, make the mechanism more robust by using a unr(9)
allocator for each channel type.

Sponsored by:	The FreeBSD Foundation
MFC after:	2 days
Reviewed by:	dev_submerge.ch
Differential Revision:	https://reviews.freebsd.org/D46680

(cherry picked from commit ad4c8671bddaa30bf0413089c74dde6a618d9021)
This commit is contained in:
Christos Margiolis 2024-10-18 10:39:08 +02:00
parent 30a64793aa
commit 463eafcff9
3 changed files with 38 additions and 9 deletions

View file

@ -1157,6 +1157,24 @@ chn_reset(struct pcm_channel *c, uint32_t fmt, uint32_t spd)
return r;
}
static struct unrhdr *
chn_getunr(struct snddev_info *d, int type)
{
switch (type) {
case SND_DEV_DSPHW_PLAY:
return (d->p_unr);
case SND_DEV_DSPHW_VPLAY:
return (d->vp_unr);
case SND_DEV_DSPHW_REC:
return (d->r_unr);
case SND_DEV_DSPHW_VREC:
return (d->vr_unr);
default:
__assert_unreachable();
}
}
struct pcm_channel *
chn_init(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls,
int dir, void *devinfo)
@ -1165,7 +1183,7 @@ chn_init(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls,
struct feeder_class *fc;
struct snd_dbuf *b, *bs;
char *dirs, buf[CHN_NAMELEN];
int i, direction, type, unit;
int i, direction, type;
PCM_BUSYASSERT(d);
PCM_LOCKASSERT(d);
@ -1198,13 +1216,6 @@ chn_init(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls,
return (NULL);
}
unit = 0;
CHN_FOREACH(c, d, channels.pcm) {
if (c->type != type)
continue;
unit++;
}
PCM_UNLOCK(d);
b = NULL;
bs = NULL;
@ -1216,7 +1227,7 @@ chn_init(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls,
CHN_INIT(c, children.busy);
c->direction = direction;
c->type = type;
c->unit = unit;
c->unit = alloc_unr(chn_getunr(d, c->type));
c->format = SND_FORMAT(AFMT_U8, 1, 0);
c->speed = DSP_DEFAULT_SPEED;
c->pid = -1;
@ -1304,6 +1315,7 @@ chn_init(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls,
return (c);
fail:
free_unr(chn_getunr(d, c->type), c->unit);
feeder_remove(c);
if (c->devinfo && CHANNEL_FREE(c->methods, c->devinfo))
sndbuf_free(b);
@ -1335,6 +1347,7 @@ chn_kill(struct pcm_channel *c)
chn_trigger(c, PCMTRIG_ABORT);
CHN_UNLOCK(c);
}
free_unr(chn_getunr(c->parentsnddev, c->type), c->unit);
feeder_remove(c);
if (CHANNEL_FREE(c->methods, c->devinfo))
sndbuf_free(b);

View file

@ -568,6 +568,10 @@ pcm_register(device_t dev, void *devinfo, int numplay, int numrec)
d->pvchanformat = 0;
d->rvchanrate = 0;
d->rvchanformat = 0;
d->p_unr = new_unrhdr(0, INT_MAX, NULL);
d->vp_unr = new_unrhdr(0, INT_MAX, NULL);
d->r_unr = new_unrhdr(0, INT_MAX, NULL);
d->vr_unr = new_unrhdr(0, INT_MAX, NULL);
CHN_INIT(d, channels.pcm);
CHN_INIT(d, channels.pcm.busy);
@ -653,6 +657,14 @@ pcm_unregister(device_t dev)
cv_destroy(&d->cv);
PCM_UNLOCK(d);
snd_mtxfree(d->lock);
if (d->p_unr != NULL)
delete_unrhdr(d->p_unr);
if (d->vp_unr != NULL)
delete_unrhdr(d->vp_unr);
if (d->r_unr != NULL)
delete_unrhdr(d->r_unr);
if (d->vr_unr != NULL)
delete_unrhdr(d->vr_unr);
if (snd_unit == device_get_unit(dev)) {
snd_unit = pcm_best_unit(-1);

View file

@ -325,6 +325,10 @@ struct snddev_info {
struct sysctl_ctx_list play_sysctl_ctx, rec_sysctl_ctx;
struct sysctl_oid *play_sysctl_tree, *rec_sysctl_tree;
struct cv cv;
struct unrhdr *p_unr;
struct unrhdr *vp_unr;
struct unrhdr *r_unr;
struct unrhdr *vr_unr;
};
void sound_oss_sysinfo(oss_sysinfo *);