diff options
Diffstat (limited to 'sound/pci/hda/hda_cs_dsp_ctl.c')
-rw-r--r-- | sound/pci/hda/hda_cs_dsp_ctl.c | 47 |
1 files changed, 31 insertions, 16 deletions
diff --git a/sound/pci/hda/hda_cs_dsp_ctl.c b/sound/pci/hda/hda_cs_dsp_ctl.c index 463ca06036..9db45d7c17 100644 --- a/sound/pci/hda/hda_cs_dsp_ctl.c +++ b/sound/pci/hda/hda_cs_dsp_ctl.c @@ -8,6 +8,7 @@ #include <linux/module.h> #include <sound/soc.h> +#include <linux/cleanup.h> #include <linux/firmware/cirrus/cs_dsp.h> #include <linux/firmware/cirrus/wmfw.h> #include "hda_cs_dsp_ctl.h" @@ -97,11 +98,23 @@ static unsigned int wmfw_convert_flags(unsigned int in) return out; } -static void hda_cs_dsp_add_kcontrol(struct hda_cs_dsp_coeff_ctl *ctl, const char *name) +static void hda_cs_dsp_free_kcontrol(struct snd_kcontrol *kctl) { + struct hda_cs_dsp_coeff_ctl *ctl = (struct hda_cs_dsp_coeff_ctl *)snd_kcontrol_chip(kctl); struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + + /* NULL priv to prevent a double-free in hda_cs_dsp_control_remove() */ + cs_ctl->priv = NULL; + kfree(ctl); +} + +static void hda_cs_dsp_add_kcontrol(struct cs_dsp_coeff_ctl *cs_ctl, + const struct hda_cs_dsp_ctl_info *info, + const char *name) +{ struct snd_kcontrol_new kcontrol = {0}; struct snd_kcontrol *kctl; + struct hda_cs_dsp_coeff_ctl *ctl __free(kfree) = NULL; int ret = 0; if (cs_ctl->len > ADSP_MAX_STD_CTRL_SIZE) { @@ -110,6 +123,13 @@ static void hda_cs_dsp_add_kcontrol(struct hda_cs_dsp_coeff_ctl *ctl, const char return; } + ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); + if (!ctl) + return; + + ctl->cs_ctl = cs_ctl; + ctl->card = info->card; + kcontrol.name = name; kcontrol.info = hda_cs_dsp_coeff_info; kcontrol.iface = SNDRV_CTL_ELEM_IFACE_MIXER; @@ -117,20 +137,22 @@ static void hda_cs_dsp_add_kcontrol(struct hda_cs_dsp_coeff_ctl *ctl, const char kcontrol.get = hda_cs_dsp_coeff_get; kcontrol.put = hda_cs_dsp_coeff_put; - /* Save ctl inside private_data, ctl is owned by cs_dsp, - * and will be freed when cs_dsp removes the control */ kctl = snd_ctl_new1(&kcontrol, (void *)ctl); if (!kctl) return; - ret = snd_ctl_add(ctl->card, kctl); + kctl->private_free = hda_cs_dsp_free_kcontrol; + ctl->kctl = kctl; + + /* snd_ctl_add() calls our private_free on error, which will kfree(ctl) */ + cs_ctl->priv = no_free_ptr(ctl); + ret = snd_ctl_add(info->card, kctl); if (ret) { dev_err(cs_ctl->dsp->dev, "Failed to add KControl %s = %d\n", kcontrol.name, ret); return; } dev_dbg(cs_ctl->dsp->dev, "Added KControl: %s\n", kcontrol.name); - ctl->kctl = kctl; } static void hda_cs_dsp_control_add(struct cs_dsp_coeff_ctl *cs_ctl, @@ -138,7 +160,6 @@ static void hda_cs_dsp_control_add(struct cs_dsp_coeff_ctl *cs_ctl, { struct cs_dsp *cs_dsp = cs_ctl->dsp; char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; - struct hda_cs_dsp_coeff_ctl *ctl; const char *region_name; int ret; @@ -163,15 +184,7 @@ static void hda_cs_dsp_control_add(struct cs_dsp_coeff_ctl *cs_ctl, " %.*s", cs_ctl->subname_len - skip, cs_ctl->subname + skip); } - ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); - if (!ctl) - return; - - ctl->cs_ctl = cs_ctl; - ctl->card = info->card; - cs_ctl->priv = ctl; - - hda_cs_dsp_add_kcontrol(ctl, name); + hda_cs_dsp_add_kcontrol(cs_ctl, info, name); } void hda_cs_dsp_add_controls(struct cs_dsp *dsp, const struct hda_cs_dsp_ctl_info *info) @@ -203,7 +216,9 @@ void hda_cs_dsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl) { struct hda_cs_dsp_coeff_ctl *ctl = cs_ctl->priv; - kfree(ctl); + /* ctl and kctl may already have been removed by ALSA private_free */ + if (ctl && ctl->kctl) + snd_ctl_remove(ctl->card, ctl->kctl); } EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_control_remove, SND_HDA_CS_DSP_CONTROLS); |