summaryrefslogtreecommitdiffstats
path: root/sys/dev/sound/pcm/sound.c
diff options
context:
space:
mode:
authortruckman <truckman@FreeBSD.org>2004-01-28 08:02:15 +0000
committertruckman <truckman@FreeBSD.org>2004-01-28 08:02:15 +0000
commit860115ff61133d494820140aaffe13b6bd0d0256 (patch)
treea74abb2113dfe2f0c468f9e6bf902217071b53f1 /sys/dev/sound/pcm/sound.c
parent0004b4717fe5160dc86420ca1eca3b19178d4fd7 (diff)
downloadFreeBSD-src-860115ff61133d494820140aaffe13b6bd0d0256.zip
FreeBSD-src-860115ff61133d494820140aaffe13b6bd0d0256.tar.gz
Change KASSERT() in feed_vchan16() into an explicit test and call to
panic() so that the buffer overflow just beyond this point is always caught, even when the code is not compiled with INVARIANTS. Change chn_setblocksize() buffer reallocation code to attempt to avoid the feed_vchan16() buffer overflow by attempting to always keep the bufsoft buffer at least as large as the bufhard buffer. Print a diagnositic message Danger! %s bufsoft size increasing from %d to %d after CHANNEL_SETBLOCKSIZE() if our best attempts fail. If feed_vchan16() were to be called by the interrupt handler while locks are dropped in chn_setblocksize() to increase the size bufsoft to match the size of bufhard, the panic() code in feed_vchan16() will be triggered. If the diagnostic message is printed, it is a warning that a panic is possible if the system were to see events in an "unlucky" order. Change the locking code to avoid the need for MTX_RECURSIVE mutexes. Add the MTX_DUPOK option to the channel mutexes and change the locking sequence to always lock the parent channel before its children to avoid the possibility of deadlock. Actually implement locking assertions for the channel mutexes and fix the problems found by the resulting assertion violations. Clean up the locking code in dsp_ioctl(). Allocate the channel buffers using the malloc() M_WAITOK option instead of M_NOWAIT so that buffer allocation won't fail. Drop locks across the malloc() calls. Add/modify KASSERTS() in attempt to detect problems early. Abuse layering by adding a pointer to the snd_dbuf structure that points back to the pcm_channel that owns it. This allows sndbuf_resize() to do proper locking without having to change the its API, which is used by the hardware drivers. Don't dereference a NULL pointer when setting hw.snd.maxautovchans if a hardware driver is not loaded. Noticed by Ryan Sommers <ryans at gamersimpact.com>. Tested by: Stefan Ehmann <shoesoft AT gmx.net> Tested by: matk (Mathew Kanner) Tested by: Gordon Bergling <gbergling AT 0xfce3.net>
Diffstat (limited to 'sys/dev/sound/pcm/sound.c')
-rw-r--r--sys/dev/sound/pcm/sound.c76
1 files changed, 57 insertions, 19 deletions
diff --git a/sys/dev/sound/pcm/sound.c b/sys/dev/sound/pcm/sound.c
index 3ba4636..edb765e 100644
--- a/sys/dev/sound/pcm/sound.c
+++ b/sys/dev/sound/pcm/sound.c
@@ -75,7 +75,23 @@ snd_mtxcreate(const char *desc, const char *type)
m = malloc(sizeof(*m), M_DEVBUF, M_WAITOK | M_ZERO);
if (m == NULL)
return NULL;
- mtx_init(m, desc, type, MTX_DEF | MTX_RECURSE);
+ mtx_init(m, desc, type, MTX_DEF);
+ return m;
+#else
+ return (void *)0xcafebabe;
+#endif
+}
+
+void *
+snd_chnmtxcreate(const char *desc, const char *type)
+{
+#ifdef USING_MUTEX
+ struct mtx *m;
+
+ m = malloc(sizeof(*m), M_DEVBUF, M_WAITOK | M_ZERO);
+ if (m == NULL)
+ return NULL;
+ mtx_init(m, desc, type, MTX_DEF | MTX_DUPOK);
return m;
#else
return (void *)0xcafebabe;
@@ -188,13 +204,16 @@ pcm_chnalloc(struct snddev_info *d, int direction, pid_t pid, int chnum)
/* try to create a vchan */
SLIST_FOREACH(sce, &d->channels, link) {
c = sce->channel;
+ CHN_LOCK(c);
if (!SLIST_EMPTY(&c->children)) {
err = vchan_create(c);
+ CHN_UNLOCK(c);
if (!err)
return pcm_chnalloc(d, direction, pid, -1);
else
device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err);
- }
+ } else
+ CHN_UNLOCK(c);
}
}
}
@@ -250,15 +269,19 @@ pcm_setmaxautovchans(struct snddev_info *d, int num)
if (num > 0 && d->vchancount == 0) {
SLIST_FOREACH(sce, &d->channels, link) {
c = sce->channel;
+ CHN_LOCK(c);
if ((c->direction == PCMDIR_PLAY) && !(c->flags & CHN_F_BUSY)) {
c->flags |= CHN_F_BUSY;
err = vchan_create(c);
if (err) {
- device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err);
c->flags &= ~CHN_F_BUSY;
- }
+ CHN_UNLOCK(c);
+ device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err);
+ } else
+ CHN_UNLOCK(c);
return;
}
+ CHN_UNLOCK(c);
}
}
if (num == 0 && d->vchancount > 0) {
@@ -313,7 +336,7 @@ sysctl_hw_snd_maxautovchans(SYSCTL_HANDLER_ARGS)
v = snd_maxautovchans;
error = sysctl_handle_int(oidp, &v, sizeof(v), req);
if (error == 0 && req->newptr != NULL) {
- if (v < 0 || v >= SND_MAXVCHANS)
+ if (v < 0 || v >= SND_MAXVCHANS || pcm_devclass == NULL)
return EINVAL;
if (v != snd_maxautovchans) {
for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) {
@@ -529,20 +552,23 @@ pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo)
err = pcm_chn_add(d, ch);
if (err) {
device_printf(d->dev, "pcm_chn_add(%s) failed, err=%d\n", ch->name, err);
- snd_mtxunlock(d->lock);
pcm_chn_destroy(ch);
return err;
}
+ CHN_LOCK(ch);
if (snd_maxautovchans > 0 && (d->flags & SD_F_AUTOVCHAN) &&
ch->direction == PCMDIR_PLAY && d->vchancount == 0) {
ch->flags |= CHN_F_BUSY;
err = vchan_create(ch);
if (err) {
- device_printf(d->dev, "vchan_create(%s) == %d\n", ch->name, err);
ch->flags &= ~CHN_F_BUSY;
+ CHN_UNLOCK(ch);
+ device_printf(d->dev, "vchan_create(%s) == %d\n", ch->name, err);
+ return err;
}
}
+ CHN_UNLOCK(ch);
return err;
}
@@ -866,11 +892,13 @@ sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS)
cnt = 0;
SLIST_FOREACH(sce, &d->channels, link) {
c = sce->channel;
+ CHN_LOCK(c);
if ((c->direction == PCMDIR_PLAY) && (c->flags & CHN_F_VIRTUAL)) {
cnt++;
if (c->flags & CHN_F_BUSY)
busy++;
}
+ CHN_UNLOCK(c);
}
newcnt = cnt;
@@ -888,23 +916,28 @@ sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS)
/* add new vchans - find a parent channel first */
SLIST_FOREACH(sce, &d->channels, link) {
c = sce->channel;
+ CHN_LOCK(c);
/* not a candidate if not a play channel */
if (c->direction != PCMDIR_PLAY)
- continue;
+ goto next;
/* not a candidate if a virtual channel */
if (c->flags & CHN_F_VIRTUAL)
- continue;
+ goto next;
/* not a candidate if it's in use */
- if ((c->flags & CHN_F_BUSY) && (SLIST_EMPTY(&c->children)))
- continue;
- /*
- * if we get here we're a nonvirtual play channel, and either
- * 1) not busy
- * 2) busy with children, not directly open
- *
- * thus we can add children
- */
- goto addok;
+ if (!(c->flags & CHN_F_BUSY) ||
+ !(SLIST_EMPTY(&c->children)))
+ /*
+ * if we get here we're a nonvirtual
+ * play channel, and either
+ * 1) not busy
+ * 2) busy with children, not directly
+ * open
+ *
+ * thus we can add children
+ */
+ goto addok;
+next:
+ CHN_UNLOCK(c);
}
pcm_inprog(d, -1);
return EBUSY;
@@ -917,6 +950,7 @@ addok:
}
if (SLIST_EMPTY(&c->children))
c->flags &= ~CHN_F_BUSY;
+ CHN_UNLOCK(c);
} else if (newcnt < cnt) {
if (busy > newcnt) {
printf("cnt %d, newcnt %d, busy %d\n", cnt, newcnt, busy);
@@ -928,13 +962,17 @@ addok:
while (err == 0 && newcnt < cnt) {
SLIST_FOREACH(sce, &d->channels, link) {
c = sce->channel;
+ CHN_LOCK(c);
if ((c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL)) == CHN_F_VIRTUAL)
goto remok;
+
+ CHN_UNLOCK(c);
}
snd_mtxunlock(d->lock);
pcm_inprog(d, -1);
return EINVAL;
remok:
+ CHN_UNLOCK(c);
err = vchan_destroy(c);
if (err == 0)
cnt--;
OpenPOWER on IntegriCloud