| From 4a0623432dc59ddf485d86474908907c1e6646d6 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Thu, 13 Nov 2025 10:30:32 +0800 |
| Subject: watchdog: wdat_wdt: Fix ACPI table leak in probe function |
| |
| From: Haotian Zhang <vulab@iscas.ac.cn> |
| |
| [ Upstream commit 25c0b472eab8379683d4eef681185c104bed8ffd ] |
| |
| wdat_wdt_probe() calls acpi_get_table() to obtain the WDAT ACPI table but |
| never calls acpi_put_table() on any paths. This causes a permanent ACPI |
| table memory leak. |
| |
| Add a single cleanup path which calls acpi_put_table() to ensure |
| the ACPI table is always released. |
| |
| Fixes: 058dfc767008 ("ACPI / watchdog: Add support for WDAT hardware watchdog") |
| Suggested-by: Guenter Roeck <linux@roeck-us.net> |
| Signed-off-by: Haotian Zhang <vulab@iscas.ac.cn> |
| Reviewed-by: Guenter Roeck <linux@roeck-us.net> |
| Signed-off-by: Guenter Roeck <linux@roeck-us.net> |
| Signed-off-by: Wim Van Sebroeck <wim@linux-watchdog.org> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| drivers/watchdog/wdat_wdt.c | 64 +++++++++++++++++++++++++------------ |
| 1 file changed, 43 insertions(+), 21 deletions(-) |
| |
| diff --git a/drivers/watchdog/wdat_wdt.c b/drivers/watchdog/wdat_wdt.c |
| index ec308836aad9c..cabeec210f166 100644 |
| --- a/drivers/watchdog/wdat_wdt.c |
| +++ b/drivers/watchdog/wdat_wdt.c |
| @@ -327,19 +327,27 @@ static int wdat_wdt_probe(struct platform_device *pdev) |
| return -ENODEV; |
| |
| wdat = devm_kzalloc(dev, sizeof(*wdat), GFP_KERNEL); |
| - if (!wdat) |
| - return -ENOMEM; |
| + if (!wdat) { |
| + ret = -ENOMEM; |
| + goto out_put_table; |
| + } |
| |
| regs = devm_kcalloc(dev, pdev->num_resources, sizeof(*regs), |
| GFP_KERNEL); |
| - if (!regs) |
| - return -ENOMEM; |
| + if (!regs) { |
| + ret = -ENOMEM; |
| + goto out_put_table; |
| + } |
| |
| /* WDAT specification wants to have >= 1ms period */ |
| - if (tbl->timer_period < 1) |
| - return -EINVAL; |
| - if (tbl->min_count > tbl->max_count) |
| - return -EINVAL; |
| + if (tbl->timer_period < 1) { |
| + ret = -EINVAL; |
| + goto out_put_table; |
| + } |
| + if (tbl->min_count > tbl->max_count) { |
| + ret = -EINVAL; |
| + goto out_put_table; |
| + } |
| |
| wdat->period = tbl->timer_period; |
| wdat->wdd.min_hw_heartbeat_ms = wdat->period * tbl->min_count; |
| @@ -356,15 +364,20 @@ static int wdat_wdt_probe(struct platform_device *pdev) |
| res = &pdev->resource[i]; |
| if (resource_type(res) == IORESOURCE_MEM) { |
| reg = devm_ioremap_resource(dev, res); |
| - if (IS_ERR(reg)) |
| - return PTR_ERR(reg); |
| + if (IS_ERR(reg)) { |
| + ret = PTR_ERR(reg); |
| + goto out_put_table; |
| + } |
| } else if (resource_type(res) == IORESOURCE_IO) { |
| reg = devm_ioport_map(dev, res->start, 1); |
| - if (!reg) |
| - return -ENOMEM; |
| + if (!reg) { |
| + ret = -ENOMEM; |
| + goto out_put_table; |
| + } |
| } else { |
| dev_err(dev, "Unsupported resource\n"); |
| - return -EINVAL; |
| + ret = -EINVAL; |
| + goto out_put_table; |
| } |
| |
| regs[i] = reg; |
| @@ -386,8 +399,10 @@ static int wdat_wdt_probe(struct platform_device *pdev) |
| } |
| |
| instr = devm_kzalloc(dev, sizeof(*instr), GFP_KERNEL); |
| - if (!instr) |
| - return -ENOMEM; |
| + if (!instr) { |
| + ret = -ENOMEM; |
| + goto out_put_table; |
| + } |
| |
| INIT_LIST_HEAD(&instr->node); |
| instr->entry = entries[i]; |
| @@ -418,7 +433,8 @@ static int wdat_wdt_probe(struct platform_device *pdev) |
| |
| if (!instr->reg) { |
| dev_err(dev, "I/O resource not found\n"); |
| - return -EINVAL; |
| + ret = -EINVAL; |
| + goto out_put_table; |
| } |
| |
| instructions = wdat->instructions[action]; |
| @@ -426,8 +442,10 @@ static int wdat_wdt_probe(struct platform_device *pdev) |
| instructions = devm_kzalloc(dev, |
| sizeof(*instructions), |
| GFP_KERNEL); |
| - if (!instructions) |
| - return -ENOMEM; |
| + if (!instructions) { |
| + ret = -ENOMEM; |
| + goto out_put_table; |
| + } |
| |
| INIT_LIST_HEAD(instructions); |
| wdat->instructions[action] = instructions; |
| @@ -441,7 +459,7 @@ static int wdat_wdt_probe(struct platform_device *pdev) |
| |
| ret = wdat_wdt_enable_reboot(wdat); |
| if (ret) |
| - return ret; |
| + goto out_put_table; |
| |
| platform_set_drvdata(pdev, wdat); |
| |
| @@ -459,12 +477,16 @@ static int wdat_wdt_probe(struct platform_device *pdev) |
| |
| ret = wdat_wdt_set_timeout(&wdat->wdd, timeout); |
| if (ret) |
| - return ret; |
| + goto out_put_table; |
| |
| watchdog_set_nowayout(&wdat->wdd, nowayout); |
| watchdog_stop_on_reboot(&wdat->wdd); |
| watchdog_stop_on_unregister(&wdat->wdd); |
| - return devm_watchdog_register_device(dev, &wdat->wdd); |
| + ret = devm_watchdog_register_device(dev, &wdat->wdd); |
| + |
| +out_put_table: |
| + acpi_put_table((struct acpi_table_header *)tbl); |
| + return ret; |
| } |
| |
| #ifdef CONFIG_PM_SLEEP |
| -- |
| 2.51.0 |
| |