[PATCH v2 2/3] ALSA: opl3: Clear voice channels before freeing their sets
From: Cen Zhang
Date: Thu Oct 22 2026 - 10:59:00 EST
The card-owned OPL3 voice array survives sequencer-driver unbind, but
its chan pointers refer to the native or OSS port's channel set. Neither
port's private_free callback clears those pointers before freeing its set.
A direct NOTEON in the initial SYNTH mode can leave a voice allocated
without a subscription. Even with fix_dur zero and no timer, unbind and
rebind followed by direct PITCHBEND can read the old channel:
Sequencer driver unbind/rebind Direct PITCHBEND
----------------------------- ----------------
snd_seq_delete_kernel_client()
port_free()
free channel set
snd_opl3_seq_probe()
allocate new channel set
snd_opl3_pitch_ctrl()
snd_opl3_update_pitch()
old voice->chan is non-NULL
snd_opl3_calc_pitch()
read freed channel
Clearing only note_off_check or the voice state does not protect this
path: snd_opl3_update_pitch() tests the channel pointer itself.
Clear channel pointers, delayed-note flags and voice state under
voice_lock before each port frees its set. This also excludes a timer
or pitch update already using a voice. Both private_free callbacks must
do the cleanup because an in-flight OSS event can refill the shared
voice array after the native port has been drained and freed. That event
can only reference its own still-live set; the OSS callback's final
cleanup retires that reference before freeing it as well.
Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@xxxxxxxxx>
---
sound/drivers/opl3/opl3_oss.c | 1 +
sound/drivers/opl3/opl3_seq.c | 13 +++++++++++++
sound/drivers/opl3/opl3_voice.h | 1 +
3 files changed, 15 insertions(+)
diff --git a/sound/drivers/opl3/opl3_oss.c b/sound/drivers/opl3/opl3_oss.c
index 6d39b2b77b80..1f01d27955fe 100644
--- a/sound/drivers/opl3/opl3_oss.c
+++ b/sound/drivers/opl3/opl3_oss.c
@@ -41,6 +41,7 @@ static void snd_opl3_oss_free_port(void *private_data)
{
struct snd_opl3 *opl3 = private_data;
+ snd_opl3_clear_voices(opl3);
snd_midi_channel_free_set(opl3->oss_chset);
}
diff --git a/sound/drivers/opl3/opl3_seq.c b/sound/drivers/opl3/opl3_seq.c
index 2bacefc3201d..f711139c5067 100644
--- a/sound/drivers/opl3/opl3_seq.c
+++ b/sound/drivers/opl3/opl3_seq.c
@@ -148,10 +148,23 @@ static int snd_opl3_synth_event_input(struct snd_seq_event * ev, int direct,
/* ------------------------------ */
+void snd_opl3_clear_voices(struct snd_opl3 *opl3)
+{
+ int i;
+
+ guard(spinlock_irqsave)(&opl3->voice_lock);
+ for (i = 0; i < MAX_OPL3_VOICES; i++) {
+ opl3->voices[i].chan = NULL;
+ opl3->voices[i].note_off_check = 0;
+ opl3->voices[i].state = SNDRV_OPL3_ST_OFF;
+ }
+}
+
static void snd_opl3_synth_free_port(void *private_data)
{
struct snd_opl3 *opl3 = private_data;
+ snd_opl3_clear_voices(opl3);
snd_midi_channel_free_set(opl3->chset);
}
diff --git a/sound/drivers/opl3/opl3_voice.h b/sound/drivers/opl3/opl3_voice.h
index be9ccca2d952..aa81512d23c9 100644
--- a/sound/drivers/opl3/opl3_voice.h
+++ b/sound/drivers/opl3/opl3_voice.h
@@ -13,6 +13,7 @@ int snd_opl3_synth_use_inc(struct snd_opl3 * opl3);
void snd_opl3_synth_use_dec(struct snd_opl3 * opl3);
int snd_opl3_synth_setup(struct snd_opl3 * opl3);
void snd_opl3_synth_cleanup(struct snd_opl3 * opl3);
+void snd_opl3_clear_voices(struct snd_opl3 *opl3);
/* Prototypes for opl3_midi.c */
void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan);
--
2.43.0