pinctrl: mediatek: free EINT resources on unbind

mtk_eint_do_init() creates an IRQ domain, populates it with a mapping for
every EINT line and installs a chained handler on the parent interrupt,
but none of these are ever released. This was harmless while the drivers
were built-in, but now that they can be built as modules and
unbound/rmmod'd it leaves behind a dangling IRQ domain, interrupt mappings
whose chip data points at freed memory, and a chained handler that keeps
firing into that freed data.

The plain allocations in mtk_eint_do_init() already use the device-managed
devm_*() helpers, so tear the remaining resources down the same way:
register a devm action that detaches the chained handler, waits for any
in-flight handler to finish, disposes of the per-line mappings and removes
the IRQ domain. This mirrors the device-managed lifecycle adopted for the
GPIO chip and keeps the whole EINT setup self-cleaning on unbind.

Fixes: e46df235b4 ("pinctrl: mediatek: refactor EINT related code for all MediaTek pinctrl can fit")
Signed-off-by: Justin Yeh <justin.yeh@mediatek.com>
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Signed-off-by: Linus Walleij <linusw@kernel.org>
This commit is contained in:
Justin Yeh 2026-07-23 11:58:13 +08:00 committed by Linus Walleij
parent 9c650317ba
commit 88292b7103

View File

@ -12,8 +12,10 @@
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/gpio/driver.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
@ -509,6 +511,27 @@ int mtk_eint_find_irq(struct mtk_eint *eint, unsigned long eint_n)
}
EXPORT_SYMBOL_GPL(mtk_eint_find_irq);
static void mtk_eint_teardown(void *data)
{
struct mtk_eint *eint = data;
unsigned int i, virq;
/* Detach the demux handler so it can no longer reference freed data. */
irq_set_chained_handler_and_data(eint->irq, NULL, NULL);
/* Wait for any in-flight handler to finish before tearing down. */
synchronize_irq(eint->irq);
/* Dispose of all child mappings before the domain is removed. */
for (i = 0; i < eint->hw->ap_num; i++) {
virq = irq_find_mapping(eint->domain, i);
if (virq)
irq_dispose_mapping(virq);
}
irq_domain_remove(eint->domain);
}
int mtk_eint_do_init(struct mtk_eint *eint, struct mtk_eint_pin *eint_pin)
{
unsigned int size, i, port, virq, inst = 0;
@ -601,7 +624,7 @@ int mtk_eint_do_init(struct mtk_eint *eint, struct mtk_eint_pin *eint_pin)
irq_set_chained_handler_and_data(eint->irq, mtk_eint_irq_handler,
eint);
return 0;
return devm_add_action_or_reset(eint->dev, mtk_eint_teardown, eint);
err_eint:
for (i = 0; i < eint->nbase; i++) {