mirror of
https://github.com/torvalds/linux.git
synced 2026-11-09 13:44:03 +01:00
ALSA: opl3: Clear voice channels before freeing their sets
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.
Key off active voices before clearing their channel pointers, delayed-
note flags and state under voice_lock. Direct events need no
subscription, so port deletion need not call synth_unuse() to reset the
hardware. Merely marking a keyed-on voice off would retain hardware
KEYON and cause a later note to miss the key-on transition. Reuse
snd_opl3_kill_voice() before either channel set is freed.
Visit higher-numbered voices first. In initial SYNTH mode, direct
NOTEON events map channels to fixed voices without reserving them through
the allocator: a 4-op primary can coexist with an independently keyed-on
2-op partner at voice + 3. Killing the primary clears that partner's
software state without keying off its register. Ascending cleanup would
then skip the partner. Retiring the partner first guarantees its key-off
before the primary can change its state, on both register banks.
The lock 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
its set as well.
Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Link: https://patch.msgid.link/pm-pmb-4552c4e605507d8d9b16-v9-70247b8436e10e26d324-2@gmail.com
This commit is contained in:
parent
a71cef6f64
commit
02b1d5127f
|
|
@ -632,6 +632,21 @@ static void snd_opl3_kill_voice(struct snd_opl3 *opl3, int voice)
|
|||
|
||||
}
|
||||
|
||||
void snd_opl3_clear_voices(struct snd_opl3 *opl3)
|
||||
{
|
||||
int i;
|
||||
|
||||
guard(spinlock_irqsave)(&opl3->voice_lock);
|
||||
/* A 4-op primary clears its higher-numbered partner's state. */
|
||||
for (i = MAX_OPL3_VOICES - 1; i >= 0; i--) {
|
||||
if (opl3->voices[i].state > 0)
|
||||
snd_opl3_kill_voice(opl3, i);
|
||||
opl3->voices[i].chan = NULL;
|
||||
opl3->voices[i].note_off_check = 0;
|
||||
opl3->voices[i].state = SNDRV_OPL3_ST_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Release a note in response to a midi note off.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@ 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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user