opnsense-src/sys/dev/sound/pcm
Christos Margiolis 99b8be414c sound: Terminate stream properly when closing vchans
When a channel is closed, dsp_close() either calls vchan_destroy() on vchans,
or chn_abort()/chn_flush() on primary channels. However, the problem with this
is that, when closing a vchan, we end up not terminating the stream properly.

The call sequence we are interested in is the following:

	vchan_destroy(vchan) -> chn_kill(vchan) -> chn_trigger(vchan) ->
	vchan_trigger(vchan) -> chn_notify(parent)

Even though chn_notify() contains codepaths which call chn_abort(parent),
apparently we do not execute any of those codepaths in this case, so the
DMA remains unterminated, hence why we keep seeing the primary
channel(s) being interrupted even once the application has exited:

root@freebsd:~ # sndctl interrupts
dsp0.play.0.interrupts=1139
dsp0.record.0.interrupts=0
root@freebsd:~ # sndctl interrupts
dsp0.play.0.interrupts=1277
dsp0.record.0.interrupts=0
root@freebsd:~ # sndctl interrupts
dsp0.play.0.interrupts=1394
dsp0.record.0.interrupts=0

The only applications that do not have this issue are those (e.g., mpv) that
manually call ioctls which end up calling chn_abort(), like SNDCTL_DSP_HALT, to
abort the channel(s) during shutdown. For all other applications that do not
manually abort the channel(s), we can confirm that chn_abort()/chn_flush(), or
even chn_trigger(PCMTRIG_ABORT) on the parent, doesn't happen during shutdown.

root@freebsd:~ # dtrace -n 'fbt::chn_abort:entry,fbt::chn_flush:entry { printf("%s", args[0]->name); stack(); }'
dtrace: description 'fbt::chn_abort:entry,fbt::chn_flush:entry ' matched 2 probes
dtrace: buffer size lowered to 1m
^C

[...]

root@freebsd:~ # dtrace -n 'fbt::chn_trigger:entry /args[1] == -1/ { printf("%s", args[0]->name); stack(); }'
dtrace: description 'fbt::chn_trigger:entry ' matched 1 probe
dtrace: buffer size lowered to 1m
CPU     ID                    FUNCTION:NAME
  0  68037                chn_trigger:entry dsp0.virtual_play.0
	      sound.ko`chn_kill+0x134
	      sound.ko`vchan_destroy+0x94
	      sound.ko`dsp_close+0x39b
	      kernel`devfs_destroy_cdevpriv+0xab
	      kernel`devfs_close_f+0x63
	      kernel`_fdrop+0x1a
	      kernel`closef+0x1e3
	      kernel`closefp_impl+0x76
	      kernel`amd64_syscall+0x151
	      kernel`0xffffffff8103841b1

To fix this, modify dsp_close() to execute the primary channel case on both
primary and virtual channels. While what we really care about are the
chn_abort()/chn_flush() calls, it shouldn't hurt to call the rest of the
functions on the vchans as well, to avoid complicating things; they get deleted
right below, anyway.

With the patch applied:

root@freebsd:~ # dtrace -n 'fbt::chn_trigger:entry /args[1] == -1/ { printf("%s", args[0]->name); stack(); }'
dtrace: description 'fbt::chn_trigger:entry ' matched 1 probe
dtrace: buffer size lowered to 1m
CPU     ID                    FUNCTION:NAME
  1  68037                chn_trigger:entry dsp0.virtual_play.0
              sound.ko`chn_flush+0x2a
              sound.ko`dsp_close+0x330
              kernel`devfs_destroy_cdevpriv+0xab
              kernel`devfs_close_f+0x63
              kernel`_fdrop+0x1a
              kernel`closef+0x1e3
              kernel`closefp_impl+0x76
              kernel`amd64_syscall+0x151
              kernel`0xffffffff8103841b

  0  68037                chn_trigger:entry dsp0.play.0
              sound.ko`chn_notify+0x4ce
              sound.ko`vchan_trigger+0x105
              sound.ko`chn_trigger+0xb4
              sound.ko`chn_flush+0x2a
              sound.ko`dsp_close+0x330
              kernel`devfs_destroy_cdevpriv+0xab
              kernel`devfs_close_f+0x63
              kernel`_fdrop+0x1a
              kernel`closef+0x1e3
              kernel`closefp_impl+0x76
              kernel`amd64_syscall+0x151
              kernel`0xffffffff8103841b

Above we can see a chn_trigger(PCMTRIG_ABORT) on the parent (dsp0.play.0),
which is coming from the chn_abort() (inlined) in chn_notify():

root@freebsd:~ # dtrace -n 'kinst::chn_abort:entry { stack(); }'
dtrace: description 'kinst::chn_abort:entry ' matched 5 probes
dtrace: buffer size lowered to 1m
CPU     ID                    FUNCTION:NAME
  1  72580                  chn_notify:1192
              sound.ko`0xffffffff8296cab4
              sound.ko`vchan_trigger+0x105
              sound.ko`chn_trigger+0xb4
              sound.ko`chn_flush+0x2a
              sound.ko`dsp_close+0x330
              kernel`devfs_destroy_cdevpriv+0xab
              kernel`devfs_close_f+0x63
              kernel`_fdrop+0x1a
              kernel`closef+0x1e3
              kernel`closefp_impl+0x76
              kernel`amd64_syscall+0x151
              kernel`0xffffffff8103841b

We can also confirm the primary channel(s) are not interrupted anymore:

root@freebsd:/mnt/src # sndctl interrupts
dsp0.play.0.interrupts=0
dsp0.record.0.interrupts=0

In collaboration with:	adrian
Tested by:		adrian, christos, thj
Sponsored by:		The FreeBSD Foundation
MFC after:		2 days
Reviewed by:		thj, adrian, emaste
Differential Revision:	https://reviews.freebsd.org/D50488

(cherry picked from commit f6430bc61df78be070209d52b4452ae9cf4cd015)
(cherry picked from commit 0c6aa445ec0c85e7c9653d20562907742569de6f)

Approved by:		re (cperciva)
2025-05-30 00:51:30 +02:00
..
ac97.c sound: Remove dead code from pcm/ 2024-12-04 13:03:36 +01:00
ac97.h sys: Remove $FreeBSD$: two-line .h pattern 2023-08-16 11:54:11 -06:00
ac97_if.m sys: Remove $FreeBSD$: one-line sh pattern 2023-08-16 11:54:58 -06:00
buffer.c sound: Remove SNDBUF_LOCKASSERT() 2025-03-04 16:46:06 +01:00
buffer.h sound: Remove SNDBUF_LOCKASSERT() 2025-03-04 16:46:06 +01:00
channel.c sound: Use bus_topo_lock() where appropriate 2025-04-06 02:28:14 +02:00
channel.h sound: Update COPYRIGHT notices 2025-03-17 19:29:17 +01:00
channel_if.m sys: Remove $FreeBSD$: one-line sh pattern 2023-08-16 11:54:58 -06:00
dsp.c sound: Terminate stream properly when closing vchans 2025-05-30 00:51:30 +02:00
dsp.h sound: Update COPYRIGHT notices 2025-03-17 19:29:17 +01:00
feeder.c sound: Update COPYRIGHT notices 2025-03-17 19:29:17 +01:00
feeder.h sound: Refactor the format conversion framework 2025-03-17 19:28:54 +01:00
feeder_chain.c sound: Implement AFMT_FLOAT support 2025-04-06 02:28:14 +02:00
feeder_eq.c sound: Update COPYRIGHT notices 2025-03-17 19:29:17 +01:00
feeder_format.c sound: Update COPYRIGHT notices 2025-03-17 19:29:17 +01:00
feeder_if.m sys: Remove $FreeBSD$: one-line sh pattern 2023-08-16 11:54:58 -06:00
feeder_matrix.c sound: Update COPYRIGHT notices 2025-03-17 19:29:17 +01:00
feeder_mixer.c sound: Fix regression in pcm/feeder_mixer.c 2025-04-06 02:28:15 +02:00
feeder_rate.c sound: Use bus_topo_lock() where appropriate 2025-04-06 02:28:14 +02:00
feeder_volume.c sound: Implement AFMT_FLOAT support 2025-04-06 02:28:14 +02:00
g711.h sound: Dissolve pcm/intpcm.h 2024-12-04 13:03:36 +01:00
matrix.h sound: Merge pcm/matrix_map.h with pcm/matrix.h 2024-12-04 13:03:36 +01:00
mixer.c sound: Use bus_topo_lock() where appropriate 2025-04-06 02:28:14 +02:00
mixer.h sound: Handle unavailable devices in various OSS IOCTLs 2024-05-25 21:30:40 +02:00
mixer_if.m sys: Remove $FreeBSD$: one-line sh pattern 2023-08-16 11:54:58 -06:00
pcm.h sound: Implement AFMT_FLOAT support 2025-04-06 02:28:14 +02:00
sndstat.c sound: Update COPYRIGHT notices 2025-03-17 19:29:17 +01:00
sound.c sound: Call PCM_RELEASE() if pcm_addchan() fails 2025-05-14 16:23:53 +02:00
sound.h sound: Implement AFMT_FLOAT support 2025-04-06 02:28:14 +02:00
vchan.c sound: Use bus_topo_lock() where appropriate 2025-04-06 02:28:14 +02:00
vchan.h sound: Update COPYRIGHT notices 2025-03-17 19:29:17 +01:00