]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
ASoC: SDCA: Fix errors in IRQ cleanup
authorCharles Keepax <ckeepax@opensource.cirrus.com>
Mon, 16 Mar 2026 14:14:49 +0000 (14:14 +0000)
committerMark Brown <broonie@kernel.org>
Mon, 6 Apr 2026 12:17:31 +0000 (13:17 +0100)
IRQs are enabled through sdca_irq_populate() from component probe
using devm_request_threaded_irq(), this however means the IRQs can
persist if the sound card is torn down. Some of the IRQ handlers
store references to the card and the kcontrols which can then
fail. Some detail of the crash was explained in [1].

Generally it is not advised to use devm outside of bus probe, so
the code is updated to not use devm. The IRQ requests are not moved
to bus probe time as it makes passing the snd_soc_component into
the IRQs very awkward and would the require a second step once the
component is available, so it is simpler to just register the IRQs
at this point, even though that necessitates some manual cleanup.

Link: https://lore.kernel.org/linux-sound/20260310183829.2907805-1-gaggery.tsai@intel.com/
Fixes: b126394d9ec6 ("ASoC: SDCA: Generic interrupt support")
Reported-by: Gaggery Tsai <gaggery.tsai@intel.com>
Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Link: https://patch.msgid.link/20260316141449.2950215-1-ckeepax@opensource.cirrus.com
Signed-off-by: Mark Brown <broonie@kernel.org>
include/sound/sdca_interrupts.h
sound/soc/sdca/sdca_class_function.c
sound/soc/sdca/sdca_interrupts.c

index 9bcb5d8fd592bf1e028b258bcd90aca4ccdf09e6..b47003c3d26ef4a31f607d398bd75c73d266b6fc 100644 (file)
@@ -69,6 +69,8 @@ struct sdca_interrupt_info {
 int sdca_irq_request(struct device *dev, struct sdca_interrupt_info *interrupt_info,
                     int sdca_irq, const char *name, irq_handler_t handler,
                     void *data);
+void sdca_irq_free(struct device *dev, struct sdca_interrupt_info *interrupt_info,
+                  int sdca_irq, const char *name, void *data);
 int sdca_irq_data_populate(struct device *dev, struct regmap *function_regmap,
                           struct snd_soc_component *component,
                           struct sdca_function_data *function,
@@ -81,6 +83,9 @@ int sdca_irq_populate_early(struct device *dev, struct regmap *function_regmap,
 int sdca_irq_populate(struct sdca_function_data *function,
                      struct snd_soc_component *component,
                      struct sdca_interrupt_info *info);
+void sdca_irq_cleanup(struct sdca_function_data *function,
+                     struct snd_soc_component *component,
+                     struct sdca_interrupt_info *info);
 struct sdca_interrupt_info *sdca_irq_allocate(struct device *dev,
                                              struct regmap *regmap, int irq);
 
index 98fd3fd1052b42e9fef967fbbe705b5e18847427..fe404d769c78c8d0759e74cf561da24cc747af87 100644 (file)
@@ -198,6 +198,14 @@ static int class_function_component_probe(struct snd_soc_component *component)
        return sdca_irq_populate(drv->function, component, core->irq_info);
 }
 
+static void class_function_component_remove(struct snd_soc_component *component)
+{
+       struct class_function_drv *drv = snd_soc_component_get_drvdata(component);
+       struct sdca_class_drv *core = drv->core;
+
+       sdca_irq_cleanup(drv->function, component, core->irq_info);
+}
+
 static int class_function_set_jack(struct snd_soc_component *component,
                                   struct snd_soc_jack *jack, void *d)
 {
@@ -209,6 +217,7 @@ static int class_function_set_jack(struct snd_soc_component *component,
 
 static const struct snd_soc_component_driver class_function_component_drv = {
        .probe                  = class_function_component_probe,
+       .remove                 = class_function_component_remove,
        .endianness             = 1,
 };
 
index 95b1ab4ba1b0369cc45c440e0a16d64b52dcc2d0..1838dabcdf604740e2757d71ac365200633462cf 100644 (file)
@@ -252,8 +252,7 @@ static int sdca_irq_request_locked(struct device *dev,
        if (irq < 0)
                return irq;
 
-       ret = devm_request_threaded_irq(dev, irq, NULL, handler,
-                                       IRQF_ONESHOT, name, data);
+       ret = request_threaded_irq(irq, NULL, handler, IRQF_ONESHOT, name, data);
        if (ret)
                return ret;
 
@@ -264,6 +263,22 @@ static int sdca_irq_request_locked(struct device *dev,
        return 0;
 }
 
+static void sdca_irq_free_locked(struct device *dev, struct sdca_interrupt_info *info,
+                                int sdca_irq, const char *name, void *data)
+{
+       int irq;
+
+       irq = regmap_irq_get_virq(info->irq_data, sdca_irq);
+       if (irq < 0)
+               return;
+
+       free_irq(irq, data);
+
+       info->irqs[sdca_irq].irq = 0;
+
+       dev_dbg(dev, "freed irq %d for %s\n", irq, name);
+}
+
 /**
  * sdca_irq_request - request an individual SDCA interrupt
  * @dev: Pointer to the struct device against which things should be allocated.
@@ -302,6 +317,30 @@ int sdca_irq_request(struct device *dev, struct sdca_interrupt_info *info,
 }
 EXPORT_SYMBOL_NS_GPL(sdca_irq_request, "SND_SOC_SDCA");
 
+/**
+ * sdca_irq_free - free an individual SDCA interrupt
+ * @dev: Pointer to the struct device.
+ * @info: Pointer to the interrupt information structure.
+ * @sdca_irq: SDCA interrupt position.
+ * @name: Name to be given to the IRQ.
+ * @data: Private data pointer that will be passed to the handler.
+ *
+ * Typically this is handled internally by sdca_irq_cleanup, however if
+ * a device requires custom IRQ handling this can be called manually before
+ * calling sdca_irq_cleanup, which will then skip that IRQ whilst processing.
+ */
+void sdca_irq_free(struct device *dev, struct sdca_interrupt_info *info,
+                  int sdca_irq, const char *name, void *data)
+{
+       if (sdca_irq < 0 || sdca_irq >= SDCA_MAX_INTERRUPTS)
+               return;
+
+       guard(mutex)(&info->irq_lock);
+
+       sdca_irq_free_locked(dev, info, sdca_irq, name, data);
+}
+EXPORT_SYMBOL_NS_GPL(sdca_irq_free, "SND_SOC_SDCA");
+
 /**
  * sdca_irq_data_populate - Populate common interrupt data
  * @dev: Pointer to the Function device.
@@ -328,8 +367,8 @@ int sdca_irq_data_populate(struct device *dev, struct regmap *regmap,
        if (!dev)
                return -ENODEV;
 
-       name = devm_kasprintf(dev, GFP_KERNEL, "%s %s %s", function->desc->name,
-                             entity->label, control->label);
+       name = kasprintf(GFP_KERNEL, "%s %s %s", function->desc->name,
+                        entity->label, control->label);
        if (!name)
                return -ENOMEM;
 
@@ -516,6 +555,36 @@ int sdca_irq_populate(struct sdca_function_data *function,
 }
 EXPORT_SYMBOL_NS_GPL(sdca_irq_populate, "SND_SOC_SDCA");
 
+/**
+ * sdca_irq_cleanup - Free all the individual IRQs for an SDCA Function
+ * @function: Pointer to the SDCA Function.
+ * @component: Pointer to the ASoC component for the Function.
+ * @info: Pointer to the SDCA interrupt info for this device.
+ *
+ * Typically this would be called from the driver for a single SDCA Function.
+ */
+void sdca_irq_cleanup(struct sdca_function_data *function,
+                     struct snd_soc_component *component,
+                     struct sdca_interrupt_info *info)
+{
+       struct device *dev = component->dev;
+       int i;
+
+       guard(mutex)(&info->irq_lock);
+
+       for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) {
+               struct sdca_interrupt *interrupt = &info->irqs[i];
+
+               if (interrupt->function != function || !interrupt->irq)
+                       continue;
+
+               sdca_irq_free_locked(dev, info, i, interrupt->name, interrupt);
+
+               kfree(interrupt->name);
+       }
+}
+EXPORT_SYMBOL_NS_GPL(sdca_irq_cleanup, "SND_SOC_SDCA");
+
 /**
  * sdca_irq_allocate - allocate an SDCA interrupt structure for a device
  * @sdev: Device pointer against which things should be allocated.