[PATCH] ALSA: opl3: Stop delayed note-offs before freeing the channel set
From: Cen Zhang
Date: Mon Oct 19 2026 - 06:17:03 EST
An in-tree OPL3 device accepts FM instruments through its hwdep write
interface. Loading a bank-127 instrument with nonzero fix_dur and sending
a direct NOTEON without a subscription leaves the device in its initial
SYNTH mode. snd_opl3_note_on() sets the note_off deadline, starts tlist
at the next jiffy and stores the channel pointer in a voice.
Unbinding the OPL3 sequencer device can then free the port's channel set
while the card-owned OPL3 object and its timer remain alive. Port removal
drains event_input users, but the timer is independent. Without a
subscription, no unuse callback runs to perform its timer cleanup:
NOTEON / sequencer-device unbind Delayed-note timer
------------------------------- ------------------
snd_opl3_note_on()
lock(voice_lock)
set voice->note_off deadline
snd_opl3_start_timer()
mod_timer(tlist, jiffies + 1)
voice->note_off_check = 1
voice->chan = channel
unlock(voice_lock)
NOTEON returns
sysfs driver unbind
snd_opl3_seq_remove()
snd_seq_delete_kernel_client()
port_free(): drain input
snd_opl3_synth_free_port()
free opl3->chset
snd_opl3_timer_func()
lock(voice_lock)
note_off == jiffies
snd_opl3_note_off_unsafe()
read voice->chan->number
The timer can thus read a channel from the freed set. Shut it down
synchronously before freeing chset, outside voice_lock and
sys_timer_lock, which the callback needs. Shutdown also prevents a
callback from rearming the timer during teardown. Clear the delayed-note
flags under voice_lock so the timer reinitialization on rebind cannot
revive old channel accesses.
Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@xxxxxxxxx>
---
diff --git a/sound/drivers/opl3/opl3_seq.c b/sound/drivers/opl3/opl3_seq.c
index b77d8e8f12fb9f90ac72b9209f4f59373eadf6f4..86bf2680a3b5e2ffee73b59a9056f93fb2bec891 100644
--- a/sound/drivers/opl3/opl3_seq.c
+++ b/sound/drivers/opl3/opl3_seq.c
@@ -151,7 +151,14 @@ static int snd_opl3_synth_event_input(struct snd_seq_event * ev, int direct,
static void snd_opl3_synth_free_port(void *private_data)
{
struct snd_opl3 *opl3 = private_data;
+ int i;
+ timer_shutdown_sync(&opl3->tlist);
+ /* Discard delayed notes before reusing the timer on rebind. */
+ scoped_guard(spinlock_irqsave, &opl3->voice_lock) {
+ for (i = 0; i < MAX_OPL3_VOICES; i++)
+ opl3->voices[i].note_off_check = 0;
+ }
snd_midi_channel_free_set(opl3->chset);
}