Linux Sound subsystem development
 help / color / mirror / Atom feed
* [PATCH v3 0/3] ALSA: opl3: Fix sequencer timer and channel lifetimes
@ 2026-10-24  6:30 Cen Zhang
  2026-10-24  6:30 ` [PATCH v3 1/3] ALSA: opl3: Initialize the timer before publishing a sequencer port Cen Zhang
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Cen Zhang @ 2026-10-24  6:30 UTC (permalink / raw)
  To: perex, tiwai
  Cc: linux-sound, linux-kernel, tiwai, baijiaju1990, jjzuming,
	zzzccc427

OPL3 can receive a fixed-duration note before its timer is initialized,
and the card-owned voice array can retain channel pointers after either
sequencer port has freed its channel set. The delayed-note timer can also
remain queued after the sequencer client has been deleted.

Initialize the timer before publishing ports, key off active voices and
clear their aliases before freeing each channel set, and shut down the
shared timer after client deletion has drained both event producers.

The first two patches address existing initialization and rebind issues
identified by Sashiko. The third closes the shared timer lifetime after
all sequencer ports have been drained.

---
Cen Zhang (3):
  ALSA: opl3: Initialize the timer before publishing a sequencer port
  ALSA: opl3: Clear voice channels before freeing their sets
  ALSA: opl3: Shut down the timer after draining sequencer ports

 sound/drivers/opl3/opl3_midi.c  | 14 ++++++++++++++
 sound/drivers/opl3/opl3_oss.c   |  1 +
 sound/drivers/opl3/opl3_seq.c   | 13 ++++++++-----
 sound/drivers/opl3/opl3_voice.h |  1 +
 4 files changed, 24 insertions(+), 5 deletions(-)

Changes in v3:
- Key off active hardware voices before retiring channel pointers and
  delayed-note state in patch 2, addressing the Sashiko review.
- Move the voice cleanup helper beside snd_opl3_kill_voice() and reuse its
  existing two- and four-operator note handling under voice_lock.
- Keep the code in patches 1 and 3 unchanged.

Link to v1: https://lore.kernel.org/r/pm-pmb-4552c4e605507d8d9b16-v4-213af356c0046331c8ff@gmail.com
Link to v2: https://lore.kernel.org/r/pm-pmb-4552c4e605507d8d9b16-v6-f3fa18aca2c7d18be670-0@gmail.com

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v3 1/3] ALSA: opl3: Initialize the timer before publishing a sequencer port
  2026-10-24  6:30 [PATCH v3 0/3] ALSA: opl3: Fix sequencer timer and channel lifetimes Cen Zhang
@ 2026-10-24  6:30 ` Cen Zhang
  2026-10-24  6:30 ` [PATCH v3 2/3] ALSA: opl3: Clear voice channels before freeing their sets Cen Zhang
  2026-10-24  6:30 ` [PATCH v3 3/3] ALSA: opl3: Shut down the timer after draining sequencer ports Cen Zhang
  2 siblings, 0 replies; 5+ messages in thread
From: Cen Zhang @ 2026-10-24  6:30 UTC (permalink / raw)
  To: perex, tiwai
  Cc: linux-sound, linux-kernel, tiwai, baijiaju1990, jjzuming,
	zzzccc427

snd_opl3_seq_probe() initializes the delayed-note timer and its lock
after snd_opl3_synth_create_port() has made the native port reachable.
A direct NOTEON does not require a subscription, and a fixed-duration
instrument makes it enter snd_opl3_start_timer().

On sequencer-driver rebind, the card-owned instrument bank remains
available. An event delivered as soon as the port appears can race the
late initialization:

  Sequencer driver probe             Direct NOTEON
  ----------------------             -------------
  snd_opl3_synth_create_port()
    snd_seq_event_port_attach()
      publish port
                                     snd_opl3_synth_event_input()
                                       snd_opl3_note_on()
                                         snd_opl3_start_timer()
                                           use tlist and sys_timer_lock
  timer_setup(tlist)
  spin_lock_init(sys_timer_lock)
  sys_timer_status = 0

Initialize the timer, its lock and its status before creating the client
and attaching either port. No event can then reach partially initialized
timer state or have its newly armed timer reinitialized by the probe.

Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
---
 sound/drivers/opl3/opl3_seq.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/sound/drivers/opl3/opl3_seq.c b/sound/drivers/opl3/opl3_seq.c
index b77d8e8f12fb..2bacefc3201d 100644
--- a/sound/drivers/opl3/opl3_seq.c
+++ b/sound/drivers/opl3/opl3_seq.c
@@ -214,6 +214,11 @@ static int snd_opl3_seq_probe(struct snd_seq_device *dev)
 
 	spin_lock_init(&opl3->voice_lock);
 
+	/* Initialize the timer before an attached port can receive events. */
+	timer_setup(&opl3->tlist, snd_opl3_timer_func, 0);
+	spin_lock_init(&opl3->sys_timer_lock);
+	opl3->sys_timer_status = 0;
+
 	opl3->seq_client = -1;
 
 	/* allocate new client */
@@ -232,11 +237,6 @@ static int snd_opl3_seq_probe(struct snd_seq_device *dev)
 		return err;
 	}
 
-	/* setup system timer */
-	timer_setup(&opl3->tlist, snd_opl3_timer_func, 0);
-	spin_lock_init(&opl3->sys_timer_lock);
-	opl3->sys_timer_status = 0;
-
 #if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS)
 	snd_opl3_init_seq_oss(opl3, name);
 #endif
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v3 2/3] ALSA: opl3: Clear voice channels before freeing their sets
  2026-10-24  6:30 [PATCH v3 0/3] ALSA: opl3: Fix sequencer timer and channel lifetimes Cen Zhang
  2026-10-24  6:30 ` [PATCH v3 1/3] ALSA: opl3: Initialize the timer before publishing a sequencer port Cen Zhang
@ 2026-10-24  6:30 ` Cen Zhang
  2026-10-24  6:47   ` sashiko-bot
  2026-10-24  6:30 ` [PATCH v3 3/3] ALSA: opl3: Shut down the timer after draining sequencer ports Cen Zhang
  2 siblings, 1 reply; 5+ messages in thread
From: Cen Zhang @ 2026-10-24  6:30 UTC (permalink / raw)
  To: perex, tiwai
  Cc: linux-sound, linux-kernel, tiwai, baijiaju1990, jjzuming,
	zzzccc427

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 leave it sounding
and cause the next note to miss the key-on transition. Reuse
snd_opl3_kill_voice(), which also handles a paired four-operator voice,
before either channel set is freed. 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@gmail.com>
---
 sound/drivers/opl3/opl3_midi.c  | 14 ++++++++++++++
 sound/drivers/opl3/opl3_oss.c   |  1 +
 sound/drivers/opl3/opl3_seq.c   |  1 +
 sound/drivers/opl3/opl3_voice.h |  1 +
 4 files changed, 17 insertions(+)

diff --git a/sound/drivers/opl3/opl3_midi.c b/sound/drivers/opl3/opl3_midi.c
index 6d3c5b5a35ff..ba807be2cf1d 100644
--- a/sound/drivers/opl3/opl3_midi.c
+++ b/sound/drivers/opl3/opl3_midi.c
@@ -632,6 +632,20 @@ 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);
+	for (i = 0; i < MAX_OPL3_VOICES; 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.
  */
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..e03c88377bb6 100644
--- a/sound/drivers/opl3/opl3_seq.c
+++ b/sound/drivers/opl3/opl3_seq.c
@@ -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);
 }
 
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



^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v3 3/3] ALSA: opl3: Shut down the timer after draining sequencer ports
  2026-10-24  6:30 [PATCH v3 0/3] ALSA: opl3: Fix sequencer timer and channel lifetimes Cen Zhang
  2026-10-24  6:30 ` [PATCH v3 1/3] ALSA: opl3: Initialize the timer before publishing a sequencer port Cen Zhang
  2026-10-24  6:30 ` [PATCH v3 2/3] ALSA: opl3: Clear voice channels before freeing their sets Cen Zhang
@ 2026-10-24  6:30 ` Cen Zhang
  2 siblings, 0 replies; 5+ messages in thread
From: Cen Zhang @ 2026-10-24  6:30 UTC (permalink / raw)
  To: perex, tiwai
  Cc: linux-sound, linux-kernel, tiwai, baijiaju1990, jjzuming,
	zzzccc427

The delayed-note timer is independent of the sequencer ports' use-locks
and runs code in snd-opl3-synth. Deleting the client drains the event
callbacks but does not cancel this timer. A direct fixed-duration NOTEON
without a subscription can arm it without any unuse callback to stop it.

A shutdown in only the native port's private_free callback is too early
to finish shared voice cleanup when the OSS port is enabled:

  Sequencer client deletion          In-flight OSS NOTEON
  -------------------------          -------------------
  drain native port
    clear native voice aliases
                                     snd_opl3_oss_event_input()
                                       refill shared voice array
  drain OSS port
    clear voice aliases again
    free OSS channel set
  client deletion returns

The per-port voice cleanup prevents the timer from dereferencing a freed
channel during that sequence, but a queued or running timer can still
outlive client deletion and the sequencer driver itself.

Shut the timer down synchronously after snd_seq_delete_kernel_client()
has drained both ports. No event can then start it again, and shutdown
waits for a callback already running and prevents callback self-rearming.
Do this outside voice_lock and sys_timer_lock, which the callback takes.
The probe initializes timer state before publishing ports on rebind.

Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
---
 sound/drivers/opl3/opl3_seq.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/sound/drivers/opl3/opl3_seq.c b/sound/drivers/opl3/opl3_seq.c
index e03c88377bb6..1004a7df1b8c 100644
--- a/sound/drivers/opl3/opl3_seq.c
+++ b/sound/drivers/opl3/opl3_seq.c
@@ -259,6 +259,8 @@ static void snd_opl3_seq_remove(struct snd_seq_device *dev)
 		snd_seq_delete_kernel_client(opl3->seq_client);
 		opl3->seq_client = -1;
 	}
+	/* Stop the shared timer after all event callbacks have been drained. */
+	timer_shutdown_sync(&opl3->tlist);
 }
 
 static struct snd_seq_driver opl3_seq_driver = {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 2/3] ALSA: opl3: Clear voice channels before freeing their sets
  2026-10-24  6:30 ` [PATCH v3 2/3] ALSA: opl3: Clear voice channels before freeing their sets Cen Zhang
@ 2026-10-24  6:47   ` sashiko-bot
  0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-10-24  6:47 UTC (permalink / raw)
  To: Cen Zhang; +Cc: linux-sound

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] ALSA: opl3: snd_opl3_clear_voices() skips keying off active paired voice
--

commit 01c630bf3a01da16872929739a7ef4ffc65b5a71
Author: Cen Zhang <zzzccc427@gmail.com>

ALSA: opl3: Clear voice channels before freeing their sets

This commit adds snd_opl3_clear_voices() and calls it from both the native
and OSS sequencer port teardown callbacks to key off active voices and clear
their channel pointers before freeing the MIDI channel sets.

> diff --git a/sound/drivers/opl3/opl3_midi.c b/sound/drivers/opl3/opl3_midi.c
> index 6d3c5b5a35ffc..ba807be2cf1de 100644
> --- a/sound/drivers/opl3/opl3_midi.c
> +++ b/sound/drivers/opl3/opl3_midi.c
> @@ -632,6 +632,20 @@ 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);
> +	for (i = 0; i < MAX_OPL3_VOICES; 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;
> +	}
> +}

[Severity: Medium]
Can snd_opl3_clear_voices() skip keying off voice i + 3 in hardware when
voice i is in SNDRV_OPL3_ST_ON_4OP and voice i + 3 has been keyed on in
SNDRV_OPL3_MODE_SYNTH?

In sound/drivers/opl3/opl3_midi.c:snd_opl3_note_on(), when opl3->synth_mode
is SNDRV_OPL3_MODE_SYNTH, channels are mapped directly via snd_opl3_oss_map
without calling opl3_get_voice():

	if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
		voice = opl3_get_voice(opl3, instr_4op, chan);
	} else {
		/* remap OSS voice */
		voice = snd_opl3_oss_map[chan->number];		
	}

If a 4-operator instrument is keyed on for channel 0..2 (or 3..5 on
oss_chset), primary voice i (0..2 or 9..11) is set to SNDRV_OPL3_ST_ON_4OP
and secondary voice i + 3 is set to SNDRV_OPL3_ST_NOT_AVAIL. If a
2-operator instrument is then keyed on for channel 12..14 (or 15..17 on
oss_chset), snd_opl3_oss_map maps directly to voice i + 3 (3..5 or 12..14),
writing OPL3_KEYON_BIT to OPL3_REG_KEYON_BLOCK for voice i + 3 and setting
opl3->voices[i + 3].state = SNDRV_OPL3_ST_ON_2OP while opl3->voices[i].state
remains SNDRV_OPL3_ST_ON_4OP.

When snd_opl3_clear_voices() iterates from 0 to MAX_OPL3_VOICES - 1, it
processes voice i first and calls snd_opl3_kill_voice(opl3, i):

	if (vp->state == SNDRV_OPL3_ST_ON_4OP) {
		vp2 = &opl3->voices[voice + 3];

		vp2->time = opl3->use_time++;
		vp2->state = SNDRV_OPL3_ST_OFF;
	}

This clears OPL3_KEYON_BIT only on voice i, while resetting
opl3->voices[i + 3].state to SNDRV_OPL3_ST_OFF. When the loop later reaches
i + 3, opl3->voices[i + 3].state > 0 is false, so snd_opl3_kill_voice() is
not called for i + 3, leaving OPL3_KEYON_BIT set in hardware while its state
is SNDRV_OPL3_ST_OFF.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/pm-pmb-4552c4e605507d8d9b16-v8-8b61497d9bd754defcda-0@gmail.com?part=2

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-10-24  6:47 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-24  6:30 [PATCH v3 0/3] ALSA: opl3: Fix sequencer timer and channel lifetimes Cen Zhang
2026-10-24  6:30 ` [PATCH v3 1/3] ALSA: opl3: Initialize the timer before publishing a sequencer port Cen Zhang
2026-10-24  6:30 ` [PATCH v3 2/3] ALSA: opl3: Clear voice channels before freeing their sets Cen Zhang
2026-10-24  6:47   ` sashiko-bot
2026-10-24  6:30 ` [PATCH v3 3/3] ALSA: opl3: Shut down the timer after draining sequencer ports Cen Zhang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox