sound/pcm/* bugs (was: Re: page fault panic tracked down
(selwakeuppri()) - really sound/pcm/*)
Don Lewis
truckman at FreeBSD.org
Tue Jan 6 21:01:48 PST 2004
On 6 Jan, To: shoesoft at gmx.net wrote:
> On 6 Jan, Stefan Ehmann wrote:
>> Finally found out in the handbook why debugging of the modules didn't
>> work and now got it right it seems.
>>
>> #0 doadump () at /usr/src/sys/kern/kern_shutdown.c:240
>> #1 0xc04e5178 in boot (howto=256) at
>> /usr/src/sys/kern/kern_shutdown.c:372
>> #2 0xc04e5507 in panic () at /usr/src/sys/kern/kern_shutdown.c:550
>> #3 0xc07eb648 in feed_vchan_s16 (f=0xc3967c00, c=0x0, b=0xc37d0000 "",
>> count=2048, source=0xc3741500) at
>> /usr/src/sys/dev/sound/pcm/vchan.c:80
>> #4 0xc07e1c6d in sndbuf_feed (from=0xc3741500, to=0xc3741600,
>> channel=0xc37a8880, feeder=0xc3967c00, count=3279164672) at
>> feeder_if.h:60
>> #5 0xc07e2225 in chn_wrfeed (c=0xc37a8880)
>> at /usr/src/sys/dev/sound/pcm/channel.c:221
>> #6 0xc07e227c in chn_wrintr (c=0x800)
>> at /usr/src/sys/dev/sound/pcm/channel.c:237
>> #7 0xc07e2990 in chn_intr (c=0x800)
>> at /usr/src/sys/dev/sound/pcm/channel.c:483
>> #8 0xc07fba2f in csa_intr (p=0xc37a8700)
>> at /usr/src/sys/dev/sound/pci/csapcm.c:623
>> #9 0xc07fa724 in csa_intr (arg=0xc37a8600)
>> at /usr/src/sys/dev/sound/pci/csa.c:532
>> #10 0xc04d1612 in ithread_loop (arg=0xc1737b00)
>> at /usr/src/sys/kern/kern_intr.c:544
>> #11 0xc04d0604 in fork_exit (callout=0xc04d1480 <ithread_loop>, arg=0x0,
>> frame=0x0) at /usr/src/sys/kern/kern_fork.c:796
>
> This code is really, really hard to follow, but it looks like there are
> some assumptions about buffer sizes that could be violated and cause bad
> things to happen.
>
> In chn_wrfeed(), we have the following bits of code:
>
> struct snd_dbuf *b = c->bufhard;
> struct snd_dbuf *bs = c->bufsoft;
> unsigned int ret, amt;
>
> amt = sndbuf_getfree(b);
> sndbuf_feed(bs, b, c, c->feeder, amt)
>
> sndbuf_feed() looks like:
>
> int
> sndbuf_feed(struct snd_dbuf *from, struct snd_dbuf *to, struct pcm_channel *chan
> nel, struct pcm_feeder *feeder, unsigned int count)
> {
> if (sndbuf_getfree(to) < count)
> return EINVAL;
>
> count = FEEDER_FEED(feeder, channel, to->tmpbuf, count, from);
>
> [ snip ]
>
> In this case, FEEDER_FEED() invokes feed_vchan_s16(), which looks like:
>
> static int
> feed_vchan_s16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32
> _t count, void *source)
> {
> /* we're going to abuse things a bit */
> struct snd_dbuf *src = source;
> [ snip ]
> KASSERT(sndbuf_getsize(src) >= count, ("bad bufsize"));
> count &= ~1;
> bzero(b, count);
> [ snip ]
> dst = (int16_t *)b;
> tmp = (int16_t *)sndbuf_getbuf(src);
> bzero(tmp, count);
>
>
> We start off by calculation the amount to copy as the amount of free
> space in bufhard. In sndbuf_feed() "to" points to bufhard and we
> validate the amount of data to copy is will fit into the available free
> space. In feed_vchan_16, the pointer to the destination buffer is in
> "b", and source/src points to bufsoft. We get a pointer to the actual
> buffer for bufsoft and then bzero the desired number of bytes. But what
> if the bufsoft buffer is smaller than the bufhard buffer? It doesn't
> look like the buffer sizes are forced to match. When chn_setblocksize()
> is called, it calls sndbuf_remalloc on the channel's bufsoft, which will
> free and rellocate the buffer, but it does not do the same for bufhard.
> If bufsoft shrinks, feed_vchan_s16() is likely stomp the wrong thing on
> the heap.
>
> When the sound channel is closed, dsp_close() calls chn_reset(), which
> calls chn_setblocksize(c, 0, 0) ...
However it happens, in feed_vchan_s16() count is 2048, while
src->bufsize and src->maxsize are both 688, src->blksz is 344, and
src->blkcnt is 2.
> It looks like there will be one final interrupt after dsp_close().
> dsp_close() calls chn_flush(), which flushes the remaining buffered
> data. Any interrupts which happen after the final call to chn_sleep()
> won't be serviced until after the channel mutex is unlocked and the
> dsp_close() code has had a chance to put things in a bad state.
> chn_flush() should probably wait for the final interrupt, possibly using
> a flag set by the interrupt handler to signal that the handler has seen
> the buffer drain (and the handler should not call chn_dmaupdate(), I
> think).
After thinking about this some more, I think the easiest fix would be to
skip the calls to chn_wrintr() and chn_rdintr() in chn_intr() if the
CHN_F_RUNNING flag is not set. This will avoid running the channel
interrupt code when the channel is more likely to be in a bad state.
> The dsp_read() and dsp_write() routines should lock the channels to
> protect the flag manipulations.
This turns out to be OK since the channels are locked, I just didn't see
it at first.
I found another bug, though. Holding a mutex across a malloc() call is
not allowed because malloc() can sleep, and sleeping while holding a
mutex is not allowed. sndbuf_resize() sndbuf_remalloc() both call
malloc() to allocate buffers, and sndbuf_alloc() and sndbuf_setup() call
sndbuf_resize(). The problem is that chn_setblocksize() calls
sndbuf_remalloc() while the channel mutex is held. We can't just unlock
the mutex around the sndbuf_remalloc() call because the channel
interrupt service routine could run while the buffer is being
re-allocated. It would be best if the channel were to be shut down when
re-allocating the buffer. This is likely to be messy since
chn_setblocksize() is called from lots of different places.
More information about the freebsd-current
mailing list