i2c_pnx_probe() calls of_node_get() to take an extra reference on the
platform device's of_node when assigning it to the adapter device, but
none of the probe error paths nor i2c_pnx_remove() drops it.

device_release() does not call of_node_put() and i2c_adapter_dev_release()
only completes a struct, so the extra reference is never released, leaking
the device_node on every probe failure and every adapter removal.

Add the matching of_node_put() to all probe error paths (the three early
return paths before clk_prepare_enable(), and the out_clock label which
covers the wait_reset(), platform_get_irq(), devm_request_irq() and
i2c_add_numbered_adapter() failure paths; dev is devm_kzalloc'ed so
adapter.dev.of_node is NULL and of_node_put() is a no-op when CONFIG_OF
is not set) and to i2c_pnx_remove().

In i2c_pnx_remove(), i2c_del_adapter() clears adap->dev with memset()
at the end (commit bd4bc3dbded9 ("i2c: Clear i2c_adapter.dev on adapter
removal")), which zeroes adap->dev.of_node before of_node_put() runs.
Cache the pointer before calling i2c_del_adapter(), the same approach
used in i2c-mux (i2c_mux_del_adapters) and mtd (commit 56570bdad5e3
("mtd: core: Fix refcount error in del_mtd_device()")).

Compile-tested with gcc on arm64 defconfig using COMPILE_TEST; no
hardware available for runtime testing.

Fixes: b41a216dafe4 ("i2c: Add device tree support to i2c-pnx.c")
Cc: [email protected]
Assisted-by: Claude:claude-opus-5
Signed-off-by: Liu Zhenlong <[email protected]>
---
 drivers/i2c/busses/i2c-pnx.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/drivers/i2c/busses/i2c-pnx.c b/drivers/i2c/busses/i2c-pnx.c
index 8daa0008bd05..0b6c144d9a21 100644
--- a/drivers/i2c/busses/i2c-pnx.c
+++ b/drivers/i2c/busses/i2c-pnx.c
@@ -644,20 +644,26 @@ static int i2c_pnx_probe(struct platform_device *pdev)
        }
 #endif
        alg_data->clk = devm_clk_get(&pdev->dev, NULL);
-       if (IS_ERR(alg_data->clk))
+       if (IS_ERR(alg_data->clk)) {
+               of_node_put(alg_data->adapter.dev.of_node);
                return PTR_ERR(alg_data->clk);
+       }
 
        snprintf(alg_data->adapter.name, sizeof(alg_data->adapter.name),
                 "%s", pdev->name);
 
        /* Register I/O resource */
        alg_data->ioaddr = devm_platform_get_and_ioremap_resource(pdev, 0, 
&res);
-       if (IS_ERR(alg_data->ioaddr))
+       if (IS_ERR(alg_data->ioaddr)) {
+               of_node_put(alg_data->adapter.dev.of_node);
                return PTR_ERR(alg_data->ioaddr);
+       }
 
        ret = clk_prepare_enable(alg_data->clk);
-       if (ret)
+       if (ret) {
+               of_node_put(alg_data->adapter.dev.of_node);
                return ret;
+       }
 
        freq = clk_get_rate(alg_data->clk);
 
@@ -707,14 +713,17 @@ static int i2c_pnx_probe(struct platform_device *pdev)
 
 out_clock:
        clk_disable_unprepare(alg_data->clk);
+       of_node_put(alg_data->adapter.dev.of_node);
        return ret;
 }
 
 static void i2c_pnx_remove(struct platform_device *pdev)
 {
        struct i2c_pnx_algo_data *alg_data = platform_get_drvdata(pdev);
+       struct device_node *node = alg_data->adapter.dev.of_node;
 
        i2c_del_adapter(&alg_data->adapter);
+       of_node_put(node);
        clk_disable_unprepare(alg_data->clk);
 }
 
-- 
2.55.0


Reply via email to