Commit a915d604 authored by Oswald Buddenhagen's avatar Oswald Buddenhagen Committed by Takashi Iwai

ALSA: emu10k1: revamp playback voice allocator

Instead of separate voices, we now allocate non-interleaved channels,
which may in turn contain two interleaved voices each. The higher-level
code keeps only one pointer per channel. The channels are not allocated
in one block any more, as there is no reason to do that. As a
consequence of that, and because it is cleaner regardless, we now let
the allocator store these pointers at a specified location, rather than
returning only the first one and having the calling code deduce the
remaining ones.
Signed-off-by: default avatarOswald Buddenhagen <oswald.buddenhagen@gmx.de>
Link: https://lore.kernel.org/r/20230518140947.3725394-8-oswald.buddenhagen@gmx.deSigned-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent b4fea2d3
...@@ -1454,6 +1454,7 @@ struct snd_emu10k1_voice { ...@@ -1454,6 +1454,7 @@ struct snd_emu10k1_voice {
unsigned char number; unsigned char number;
unsigned char use; unsigned char use;
unsigned char dirty; unsigned char dirty;
unsigned char last;
void (*interrupt)(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice); void (*interrupt)(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice);
struct snd_emu10k1_pcm *epcm; struct snd_emu10k1_pcm *epcm;
...@@ -1850,7 +1851,7 @@ int snd_emu10k1_synth_copy_from_user(struct snd_emu10k1 *emu, struct snd_util_me ...@@ -1850,7 +1851,7 @@ int snd_emu10k1_synth_copy_from_user(struct snd_emu10k1 *emu, struct snd_util_me
int snd_emu10k1_memblk_map(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk); int snd_emu10k1_memblk_map(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk);
/* voice allocation */ /* voice allocation */
int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int pair, int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int count, int channels,
struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice); struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice);
int snd_emu10k1_voice_free(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice); int snd_emu10k1_voice_free(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice);
......
...@@ -287,7 +287,7 @@ get_voice(struct snd_emux *emu, struct snd_emux_port *port) ...@@ -287,7 +287,7 @@ get_voice(struct snd_emux *emu, struct snd_emux_port *port)
if (vp->ch < 0) { if (vp->ch < 0) {
/* allocate a voice */ /* allocate a voice */
struct snd_emu10k1_voice *hwvoice; struct snd_emu10k1_voice *hwvoice;
if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 1, NULL, &hwvoice) < 0 || hwvoice == NULL) if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 1, 1, NULL, &hwvoice) < 0)
continue; continue;
vp->ch = hwvoice->number; vp->ch = hwvoice->number;
emu->num_voices++; emu->num_voices++;
......
...@@ -1467,13 +1467,13 @@ static int snd_emu10k1_send_routing_put(struct snd_kcontrol *kcontrol, ...@@ -1467,13 +1467,13 @@ static int snd_emu10k1_send_routing_put(struct snd_kcontrol *kcontrol,
change = 1; change = 1;
} }
} }
if (change && mix->epcm) { if (change && mix->epcm && mix->epcm->voices[0]) {
if (mix->epcm->voices[0] && mix->epcm->voices[1]) { if (!mix->epcm->voices[0]->last) {
update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number, update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number,
&mix->send_routing[1][0]); &mix->send_routing[1][0]);
update_emu10k1_fxrt(emu, mix->epcm->voices[1]->number, update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number + 1,
&mix->send_routing[2][0]); &mix->send_routing[2][0]);
} else if (mix->epcm->voices[0]) { } else {
update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number, update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number,
&mix->send_routing[0][0]); &mix->send_routing[0][0]);
} }
...@@ -1535,13 +1535,13 @@ static int snd_emu10k1_send_volume_put(struct snd_kcontrol *kcontrol, ...@@ -1535,13 +1535,13 @@ static int snd_emu10k1_send_volume_put(struct snd_kcontrol *kcontrol,
change = 1; change = 1;
} }
} }
if (change && mix->epcm) { if (change && mix->epcm && mix->epcm->voices[0]) {
if (mix->epcm->voices[0] && mix->epcm->voices[1]) { if (!mix->epcm->voices[0]->last) {
update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number, update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number,
&mix->send_volume[1][0]); &mix->send_volume[1][0]);
update_emu10k1_send_volume(emu, mix->epcm->voices[1]->number, update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number + 1,
&mix->send_volume[2][0]); &mix->send_volume[2][0]);
} else if (mix->epcm->voices[0]) { } else {
update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number, update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number,
&mix->send_volume[0][0]); &mix->send_volume[0][0]);
} }
...@@ -1601,11 +1601,11 @@ static int snd_emu10k1_attn_put(struct snd_kcontrol *kcontrol, ...@@ -1601,11 +1601,11 @@ static int snd_emu10k1_attn_put(struct snd_kcontrol *kcontrol,
change = 1; change = 1;
} }
} }
if (change && mix->epcm) { if (change && mix->epcm && mix->epcm->voices[0]) {
if (mix->epcm->voices[0] && mix->epcm->voices[1]) { if (!mix->epcm->voices[0]->last) {
snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number, mix->attn[1]); snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number, mix->attn[1]);
snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[1]->number, mix->attn[2]); snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number + 1, mix->attn[2]);
} else if (mix->epcm->voices[0]) { } else {
snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number, mix->attn[0]); snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number, mix->attn[0]);
} }
} }
......
...@@ -86,32 +86,26 @@ static void snd_emu10k1_pcm_free_voices(struct snd_emu10k1_pcm *epcm) ...@@ -86,32 +86,26 @@ static void snd_emu10k1_pcm_free_voices(struct snd_emu10k1_pcm *epcm)
} }
} }
static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voices) static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm *epcm,
int type, int count, int channels)
{ {
int err, i; int err;
snd_emu10k1_pcm_free_voices(epcm); snd_emu10k1_pcm_free_voices(epcm);
err = snd_emu10k1_voice_alloc(epcm->emu, err = snd_emu10k1_voice_alloc(epcm->emu,
epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX, type, count, channels,
voices,
epcm, &epcm->voices[0]); epcm, &epcm->voices[0]);
if (err < 0) if (err < 0)
return err; return err;
if (voices > 1) {
for (i = 1; i < voices; i++) {
epcm->voices[i] = &epcm->emu->voices[(epcm->voices[0]->number + i) % NUM_G];
}
}
if (epcm->extra == NULL) { if (epcm->extra == NULL) {
// The hardware supports only (half-)loop interrupts, so to support an // The hardware supports only (half-)loop interrupts, so to support an
// arbitrary number of periods per buffer, we use an extra voice with a // arbitrary number of periods per buffer, we use an extra voice with a
// period-sized loop as the interrupt source. Additionally, the interrupt // period-sized loop as the interrupt source. Additionally, the interrupt
// timing of the hardware is "suboptimal" and needs some compensation. // timing of the hardware is "suboptimal" and needs some compensation.
err = snd_emu10k1_voice_alloc(epcm->emu, err = snd_emu10k1_voice_alloc(epcm->emu,
epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM_IRQ : EMU10K1_EFX_IRQ, type + 1, 1, 1,
1,
epcm, &epcm->extra); epcm, &epcm->extra);
if (err < 0) { if (err < 0) {
/* /*
...@@ -325,9 +319,19 @@ static int snd_emu10k1_playback_hw_params(struct snd_pcm_substream *substream, ...@@ -325,9 +319,19 @@ static int snd_emu10k1_playback_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_runtime *runtime = substream->runtime; struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_emu10k1_pcm *epcm = runtime->private_data; struct snd_emu10k1_pcm *epcm = runtime->private_data;
size_t alloc_size; size_t alloc_size;
int type, channels, count;
int err; int err;
err = snd_emu10k1_pcm_channel_alloc(epcm, params_channels(hw_params)); if (epcm->type == PLAYBACK_EMUVOICE) {
type = EMU10K1_PCM;
channels = 1;
count = params_channels(hw_params);
} else {
type = EMU10K1_EFX;
channels = params_channels(hw_params);
count = 1;
}
err = snd_emu10k1_pcm_channel_alloc(epcm, type, count, channels);
if (err < 0) if (err < 0)
return err; return err;
...@@ -397,8 +401,8 @@ static int snd_emu10k1_playback_prepare(struct snd_pcm_substream *substream) ...@@ -397,8 +401,8 @@ static int snd_emu10k1_playback_prepare(struct snd_pcm_substream *substream)
snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], w_16, stereo, snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], w_16, stereo,
start_addr, end_addr, start_addr, end_addr,
&emu->pcm_mixer[substream->number]); &emu->pcm_mixer[substream->number]);
if (epcm->voices[1]) if (stereo)
snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[1], w_16, true, snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[0] + 1, w_16, true,
start_addr, end_addr, start_addr, end_addr,
&emu->pcm_mixer[substream->number]); &emu->pcm_mixer[substream->number]);
return 0; return 0;
...@@ -589,8 +593,6 @@ static void snd_emu10k1_playback_unmute_voice(struct snd_emu10k1 *emu, ...@@ -589,8 +593,6 @@ static void snd_emu10k1_playback_unmute_voice(struct snd_emu10k1 *emu,
unsigned int vattn; unsigned int vattn;
unsigned int tmp; unsigned int tmp;
if (evoice == NULL) /* skip second voice for mono */
return;
tmp = stereo ? (master ? 1 : 2) : 0; tmp = stereo ? (master ? 1 : 2) : 0;
vattn = mix->attn[tmp] << 16; vattn = mix->attn[tmp] << 16;
snd_emu10k1_playback_commit_volume(emu, evoice, vattn); snd_emu10k1_playback_commit_volume(emu, evoice, vattn);
...@@ -599,8 +601,6 @@ static void snd_emu10k1_playback_unmute_voice(struct snd_emu10k1 *emu, ...@@ -599,8 +601,6 @@ static void snd_emu10k1_playback_unmute_voice(struct snd_emu10k1 *emu,
static void snd_emu10k1_playback_mute_voice(struct snd_emu10k1 *emu, static void snd_emu10k1_playback_mute_voice(struct snd_emu10k1 *emu,
struct snd_emu10k1_voice *evoice) struct snd_emu10k1_voice *evoice)
{ {
if (evoice == NULL)
return;
snd_emu10k1_playback_commit_volume(emu, evoice, 0); snd_emu10k1_playback_commit_volume(emu, evoice, 0);
} }
...@@ -681,7 +681,8 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, ...@@ -681,7 +681,8 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream,
case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_RESUME:
mix = &emu->pcm_mixer[substream->number]; mix = &emu->pcm_mixer[substream->number];
snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0], stereo, true, mix); snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0], stereo, true, mix);
snd_emu10k1_playback_unmute_voice(emu, epcm->voices[1], stereo, false, mix); if (stereo)
snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0] + 1, true, false, mix);
snd_emu10k1_playback_set_running(emu, epcm); snd_emu10k1_playback_set_running(emu, epcm);
snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0]); snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0]);
snd_emu10k1_playback_trigger_voice(emu, epcm->extra); snd_emu10k1_playback_trigger_voice(emu, epcm->extra);
...@@ -693,7 +694,8 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, ...@@ -693,7 +694,8 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream,
snd_emu10k1_playback_stop_voice(emu, epcm->extra); snd_emu10k1_playback_stop_voice(emu, epcm->extra);
snd_emu10k1_playback_set_stopped(emu, epcm); snd_emu10k1_playback_set_stopped(emu, epcm);
snd_emu10k1_playback_mute_voice(emu, epcm->voices[0]); snd_emu10k1_playback_mute_voice(emu, epcm->voices[0]);
snd_emu10k1_playback_mute_voice(emu, epcm->voices[1]); if (stereo)
snd_emu10k1_playback_mute_voice(emu, epcm->voices[0] + 1);
break; break;
default: default:
result = -EINVAL; result = -EINVAL;
......
...@@ -372,12 +372,13 @@ static void snd_emu10k1_proc_voices_read(struct snd_info_entry *entry, ...@@ -372,12 +372,13 @@ static void snd_emu10k1_proc_voices_read(struct snd_info_entry *entry,
}; };
static_assert(ARRAY_SIZE(types) == EMU10K1_NUM_TYPES); static_assert(ARRAY_SIZE(types) == EMU10K1_NUM_TYPES);
snd_iprintf(buffer, "ch\tdirty\tuse\n"); snd_iprintf(buffer, "ch\tdirty\tlast\tuse\n");
for (idx = 0; idx < NUM_G; idx++) { for (idx = 0; idx < NUM_G; idx++) {
voice = &emu->voices[idx]; voice = &emu->voices[idx];
snd_iprintf(buffer, "%i\t%u\t%s\n", snd_iprintf(buffer, "%i\t%u\t%u\t%s\n",
idx, idx,
voice->dirty, voice->dirty,
voice->last,
types[voice->use]); types[voice->use]);
} }
} }
......
...@@ -23,11 +23,7 @@ ...@@ -23,11 +23,7 @@
* allocator uses a round robin scheme. The next free voice is tracked in * allocator uses a round robin scheme. The next free voice is tracked in
* the card record and each allocation begins where the last left off. The * the card record and each allocation begins where the last left off. The
* hardware requires stereo interleaved voices be aligned to an even/odd * hardware requires stereo interleaved voices be aligned to an even/odd
* boundary. For multichannel voice allocation we ensure than the block of * boundary.
* voices does not cross the 32 voice boundary. This simplifies the
* multichannel support and ensures we can use a single write to the
* (set|clear)_loop_stop registers. Otherwise (for example) the voices would
* get out of sync when pausing/resuming a stream.
* --rlrevell * --rlrevell
*/ */
...@@ -35,54 +31,43 @@ static int voice_alloc(struct snd_emu10k1 *emu, int type, int number, ...@@ -35,54 +31,43 @@ static int voice_alloc(struct snd_emu10k1 *emu, int type, int number,
struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice) struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice)
{ {
struct snd_emu10k1_voice *voice; struct snd_emu10k1_voice *voice;
int i, j, k, first_voice, last_voice, skip; int i, j, k, skip;
*rvoice = NULL; for (i = emu->next_free_voice, j = 0; j < NUM_G; i = (i + skip) % NUM_G, j += skip) {
first_voice = last_voice = 0;
for (i = emu->next_free_voice, j = 0; j < NUM_G ; i += number, j += number) {
/* /*
dev_dbg(emu->card->dev, "i %d j %d next free %d!\n", dev_dbg(emu->card->dev, "i %d j %d next free %d!\n",
i, j, emu->next_free_voice); i, j, emu->next_free_voice);
*/ */
i %= NUM_G;
/* stereo voices must be even/odd */ /* stereo voices must be even/odd */
if ((number == 2) && (i % 2)) { if ((number > 1) && (i % 2)) {
i++; skip = 1;
continue; continue;
} }
skip = 0;
for (k = 0; k < number; k++) { for (k = 0; k < number; k++) {
voice = &emu->voices[(i+k) % NUM_G]; voice = &emu->voices[i + k];
if (voice->use) { if (voice->use) {
skip = 1; skip = k + 1;
break; goto next;
} }
} }
if (!skip) {
/* dev_dbg(emu->card->dev, "allocated voice %d\n", i); */ for (k = 0; k < number; k++) {
first_voice = i; voice = &emu->voices[i + k];
last_voice = (i + number) % NUM_G; voice->use = type;
emu->next_free_voice = last_voice; voice->epcm = epcm;
break; /* dev_dbg(emu->card->dev, "allocated voice %d\n", i + k); */
} }
voice->last = 1;
*rvoice = &emu->voices[i];
emu->next_free_voice = (i + number) % NUM_G;
return 0;
next: ;
} }
return -ENOMEM; // -EBUSY would have been better
if (first_voice == last_voice)
return -ENOMEM;
for (i = 0; i < number; i++) {
voice = &emu->voices[(first_voice + i) % NUM_G];
/*
dev_dbg(emu->card->dev, "voice alloc - %i, %i of %i\n",
voice->number, idx-first_voice+1, number);
*/
voice->use = type;
voice->epcm = epcm;
}
*rvoice = &emu->voices[first_voice];
return 0;
} }
static void voice_free(struct snd_emu10k1 *emu, static void voice_free(struct snd_emu10k1 *emu,
...@@ -91,11 +76,11 @@ static void voice_free(struct snd_emu10k1 *emu, ...@@ -91,11 +76,11 @@ static void voice_free(struct snd_emu10k1 *emu,
if (pvoice->dirty) if (pvoice->dirty)
snd_emu10k1_voice_init(emu, pvoice->number); snd_emu10k1_voice_init(emu, pvoice->number);
pvoice->interrupt = NULL; pvoice->interrupt = NULL;
pvoice->use = pvoice->dirty = 0; pvoice->use = pvoice->dirty = pvoice->last = 0;
pvoice->epcm = NULL; pvoice->epcm = NULL;
} }
int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number, int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int count, int channels,
struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice) struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice)
{ {
unsigned long flags; unsigned long flags;
...@@ -103,23 +88,36 @@ int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number, ...@@ -103,23 +88,36 @@ int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number,
if (snd_BUG_ON(!rvoice)) if (snd_BUG_ON(!rvoice))
return -EINVAL; return -EINVAL;
if (snd_BUG_ON(!number)) if (snd_BUG_ON(!count))
return -EINVAL;
if (snd_BUG_ON(!channels))
return -EINVAL; return -EINVAL;
spin_lock_irqsave(&emu->voice_lock, flags); spin_lock_irqsave(&emu->voice_lock, flags);
for (;;) { for (int got = 0; got < channels; ) {
result = voice_alloc(emu, type, number, epcm, rvoice); result = voice_alloc(emu, type, count, epcm, &rvoice[got]);
if (result == 0 || type == EMU10K1_SYNTH) if (result == 0) {
break; got++;
/*
/* free a voice from synth */ dev_dbg(emu->card->dev, "voice alloc - %i, %i of %i\n",
if (emu->get_synth_voice) { rvoice[got - 1]->number, got, want);
*/
continue;
}
if (type != EMU10K1_SYNTH && emu->get_synth_voice) {
/* free a voice from synth */
result = emu->get_synth_voice(emu); result = emu->get_synth_voice(emu);
if (result >= 0) if (result >= 0) {
voice_free(emu, &emu->voices[result]); voice_free(emu, &emu->voices[result]);
continue;
}
}
for (int i = 0; i < got; i++) {
for (int j = 0; j < count; j++)
voice_free(emu, rvoice[i] + j);
rvoice[i] = NULL;
} }
if (result < 0) break;
break;
} }
spin_unlock_irqrestore(&emu->voice_lock, flags); spin_unlock_irqrestore(&emu->voice_lock, flags);
...@@ -132,11 +130,15 @@ int snd_emu10k1_voice_free(struct snd_emu10k1 *emu, ...@@ -132,11 +130,15 @@ int snd_emu10k1_voice_free(struct snd_emu10k1 *emu,
struct snd_emu10k1_voice *pvoice) struct snd_emu10k1_voice *pvoice)
{ {
unsigned long flags; unsigned long flags;
int last;
if (snd_BUG_ON(!pvoice)) if (snd_BUG_ON(!pvoice))
return -EINVAL; return -EINVAL;
spin_lock_irqsave(&emu->voice_lock, flags); spin_lock_irqsave(&emu->voice_lock, flags);
voice_free(emu, pvoice); do {
last = pvoice->last;
voice_free(emu, pvoice++);
} while (!last);
spin_unlock_irqrestore(&emu->voice_lock, flags); spin_unlock_irqrestore(&emu->voice_lock, flags);
return 0; return 0;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment