mirror of
https://github.com/torvalds/linux.git
synced 2026-05-12 16:18:45 +02:00
The memory leak detector reports:
echo clear > /sys/kernel/debug/kmemleak
modprobe coresight_funnel
rmmod coresight_funnel
# Scan memory leak and report it
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak
unreferenced object 0xffff0008020c7200 (size 64):
comm "modprobe", pid 410, jiffies 4295333721
hex dump (first 32 bytes):
d8 da fe 7e 09 00 ff ff e8 2e ff 7e 09 00 ff ff ...~.......~....
b0 6c ff 7e 09 00 ff ff 30 83 00 7f 09 00 ff ff .l.~....0.......
backtrace (crc 4116a690):
kmemleak_alloc+0xd8/0xf8
__kmalloc_node_track_caller_noprof+0x2c8/0x6f0
krealloc_node_align_noprof+0x13c/0x2c8
coresight_alloc_device_name+0xe4/0x158 [coresight]
0xffffd327ecef8394
0xffffd327ecef85ec
amba_probe+0x118/0x1c8
really_probe+0xc8/0x3f0
__driver_probe_device+0x88/0x190
driver_probe_device+0x44/0x120
__driver_attach+0x100/0x238
bus_for_each_dev+0x84/0xf0
driver_attach+0x2c/0x40
bus_add_driver+0x128/0x258
driver_register+0x64/0x138
__amba_driver_register+0x2c/0x48
The memory leak is caused by not freeing the device list that maintains
device indices.
This device list preserves stable device indices across unbind and
rebind device operations, so it does not share the same lifetime as a
device instances and must only be freed when the module is unloaded.
Some modules do not implement a module exit callback because they are
registered using module_platform_driver(). As a result, the device
list cannot be released during module exit for those modules.
Fix this by moving the device list into the core layer. As a general
solution, instead of maintaining a static list in each driver, drivers
now allocate device lists via coresight_allocate_device_list() and
device indices via coresight_allocate_device_idx().
The list is released only when the core module is unloaded by calling
coresight_release_device_list(), avoiding the leak.
Fixes: 0f5f9b6ba9 ("coresight: Use platform agnostic names")
Reviewed-by: James Clark <james.clark@linaro.org>
Signed-off-by: Leo Yan <leo.yan@arm.com>
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Link: https://lore.kernel.org/r/20260209-arm_coresight_refactor_dev_register-v4-1-62d6042f76f7@arm.com
431 lines
10 KiB
C
431 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
|
|
*
|
|
* Description: CoreSight Funnel driver
|
|
*/
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/types.h>
|
|
#include <linux/device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/coresight.h>
|
|
#include <linux/amba/bus.h>
|
|
#include <linux/clk.h>
|
|
|
|
#include "coresight-priv.h"
|
|
|
|
#define FUNNEL_FUNCTL 0x000
|
|
#define FUNNEL_PRICTL 0x004
|
|
|
|
#define FUNNEL_HOLDTIME_MASK 0xf00
|
|
#define FUNNEL_HOLDTIME_SHFT 0x8
|
|
#define FUNNEL_HOLDTIME (0x7 << FUNNEL_HOLDTIME_SHFT)
|
|
#define FUNNEL_ENSx_MASK 0xff
|
|
|
|
/**
|
|
* struct funnel_drvdata - specifics associated to a funnel component
|
|
* @base: memory mapped base address for this component.
|
|
* @atclk: optional clock for the core parts of the funnel.
|
|
* @pclk: APB clock if present, otherwise NULL
|
|
* @csdev: component vitals needed by the framework.
|
|
* @priority: port selection order.
|
|
* @spinlock: serialize enable/disable operations.
|
|
*/
|
|
struct funnel_drvdata {
|
|
void __iomem *base;
|
|
struct clk *atclk;
|
|
struct clk *pclk;
|
|
struct coresight_device *csdev;
|
|
unsigned long priority;
|
|
raw_spinlock_t spinlock;
|
|
};
|
|
|
|
static int dynamic_funnel_enable_hw(struct funnel_drvdata *drvdata, int port)
|
|
{
|
|
u32 functl;
|
|
int rc = 0;
|
|
struct coresight_device *csdev = drvdata->csdev;
|
|
|
|
CS_UNLOCK(drvdata->base);
|
|
|
|
functl = readl_relaxed(drvdata->base + FUNNEL_FUNCTL);
|
|
/* Claim the device only when we enable the first slave */
|
|
if (!(functl & FUNNEL_ENSx_MASK)) {
|
|
rc = coresight_claim_device_unlocked(csdev);
|
|
if (rc)
|
|
goto done;
|
|
}
|
|
|
|
functl &= ~FUNNEL_HOLDTIME_MASK;
|
|
functl |= FUNNEL_HOLDTIME;
|
|
functl |= (1 << port);
|
|
writel_relaxed(functl, drvdata->base + FUNNEL_FUNCTL);
|
|
writel_relaxed(drvdata->priority, drvdata->base + FUNNEL_PRICTL);
|
|
done:
|
|
CS_LOCK(drvdata->base);
|
|
return rc;
|
|
}
|
|
|
|
static int funnel_enable(struct coresight_device *csdev,
|
|
struct coresight_connection *in,
|
|
struct coresight_connection *out)
|
|
{
|
|
int rc = 0;
|
|
struct funnel_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
|
unsigned long flags;
|
|
bool first_enable = false;
|
|
|
|
raw_spin_lock_irqsave(&drvdata->spinlock, flags);
|
|
if (in->dest_refcnt == 0) {
|
|
if (drvdata->base)
|
|
rc = dynamic_funnel_enable_hw(drvdata, in->dest_port);
|
|
if (!rc)
|
|
first_enable = true;
|
|
}
|
|
if (!rc)
|
|
in->dest_refcnt++;
|
|
raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
|
|
|
if (first_enable)
|
|
dev_dbg(&csdev->dev, "FUNNEL inport %d enabled\n",
|
|
in->dest_port);
|
|
return rc;
|
|
}
|
|
|
|
static void dynamic_funnel_disable_hw(struct funnel_drvdata *drvdata,
|
|
int inport)
|
|
{
|
|
u32 functl;
|
|
struct coresight_device *csdev = drvdata->csdev;
|
|
|
|
CS_UNLOCK(drvdata->base);
|
|
|
|
functl = readl_relaxed(drvdata->base + FUNNEL_FUNCTL);
|
|
functl &= ~(1 << inport);
|
|
writel_relaxed(functl, drvdata->base + FUNNEL_FUNCTL);
|
|
|
|
/* Disclaim the device if none of the slaves are now active */
|
|
if (!(functl & FUNNEL_ENSx_MASK))
|
|
coresight_disclaim_device_unlocked(csdev);
|
|
|
|
CS_LOCK(drvdata->base);
|
|
}
|
|
|
|
static void funnel_disable(struct coresight_device *csdev,
|
|
struct coresight_connection *in,
|
|
struct coresight_connection *out)
|
|
{
|
|
struct funnel_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
|
unsigned long flags;
|
|
bool last_disable = false;
|
|
|
|
raw_spin_lock_irqsave(&drvdata->spinlock, flags);
|
|
if (--in->dest_refcnt == 0) {
|
|
if (drvdata->base)
|
|
dynamic_funnel_disable_hw(drvdata, in->dest_port);
|
|
last_disable = true;
|
|
}
|
|
raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
|
|
|
if (last_disable)
|
|
dev_dbg(&csdev->dev, "FUNNEL inport %d disabled\n",
|
|
in->dest_port);
|
|
}
|
|
|
|
static const struct coresight_ops_link funnel_link_ops = {
|
|
.enable = funnel_enable,
|
|
.disable = funnel_disable,
|
|
};
|
|
|
|
static const struct coresight_ops funnel_cs_ops = {
|
|
.link_ops = &funnel_link_ops,
|
|
};
|
|
|
|
static ssize_t priority_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct funnel_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
unsigned long val = drvdata->priority;
|
|
|
|
return sprintf(buf, "%#lx\n", val);
|
|
}
|
|
|
|
static ssize_t priority_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
int ret;
|
|
unsigned long val;
|
|
struct funnel_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
|
|
ret = kstrtoul(buf, 16, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
drvdata->priority = val;
|
|
return size;
|
|
}
|
|
static DEVICE_ATTR_RW(priority);
|
|
|
|
static u32 get_funnel_ctrl_hw(struct funnel_drvdata *drvdata)
|
|
{
|
|
u32 functl;
|
|
|
|
CS_UNLOCK(drvdata->base);
|
|
functl = readl_relaxed(drvdata->base + FUNNEL_FUNCTL);
|
|
CS_LOCK(drvdata->base);
|
|
|
|
return functl;
|
|
}
|
|
|
|
static ssize_t funnel_ctrl_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u32 val;
|
|
struct funnel_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
|
|
pm_runtime_get_sync(dev->parent);
|
|
|
|
val = get_funnel_ctrl_hw(drvdata);
|
|
|
|
pm_runtime_put(dev->parent);
|
|
|
|
return sprintf(buf, "%#x\n", val);
|
|
}
|
|
static DEVICE_ATTR_RO(funnel_ctrl);
|
|
|
|
static struct attribute *coresight_funnel_attrs[] = {
|
|
&dev_attr_funnel_ctrl.attr,
|
|
&dev_attr_priority.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(coresight_funnel);
|
|
|
|
static int funnel_probe(struct device *dev, struct resource *res)
|
|
{
|
|
void __iomem *base;
|
|
struct coresight_platform_data *pdata = NULL;
|
|
struct funnel_drvdata *drvdata;
|
|
struct coresight_desc desc = { 0 };
|
|
int ret;
|
|
|
|
if (is_of_node(dev_fwnode(dev)) &&
|
|
of_device_is_compatible(dev->of_node, "arm,coresight-funnel"))
|
|
dev_warn_once(dev, "Uses OBSOLETE CoreSight funnel binding\n");
|
|
|
|
desc.name = coresight_alloc_device_name("funnel", dev);
|
|
if (!desc.name)
|
|
return -ENOMEM;
|
|
|
|
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
|
|
if (!drvdata)
|
|
return -ENOMEM;
|
|
|
|
ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* Map the device base for dynamic-funnel, which has been
|
|
* validated by AMBA core.
|
|
*/
|
|
if (res) {
|
|
base = devm_ioremap_resource(dev, res);
|
|
if (IS_ERR(base))
|
|
return PTR_ERR(base);
|
|
drvdata->base = base;
|
|
desc.groups = coresight_funnel_groups;
|
|
desc.access = CSDEV_ACCESS_IOMEM(base);
|
|
coresight_clear_self_claim_tag(&desc.access);
|
|
}
|
|
|
|
dev_set_drvdata(dev, drvdata);
|
|
|
|
pdata = coresight_get_platform_data(dev);
|
|
if (IS_ERR(pdata))
|
|
return PTR_ERR(pdata);
|
|
|
|
dev->platform_data = pdata;
|
|
|
|
raw_spin_lock_init(&drvdata->spinlock);
|
|
desc.type = CORESIGHT_DEV_TYPE_LINK;
|
|
desc.subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_MERG;
|
|
desc.ops = &funnel_cs_ops;
|
|
desc.pdata = pdata;
|
|
desc.dev = dev;
|
|
drvdata->csdev = coresight_register(&desc);
|
|
if (IS_ERR(drvdata->csdev))
|
|
return PTR_ERR(drvdata->csdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int funnel_remove(struct device *dev)
|
|
{
|
|
struct funnel_drvdata *drvdata = dev_get_drvdata(dev);
|
|
|
|
coresight_unregister(drvdata->csdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int funnel_runtime_suspend(struct device *dev)
|
|
{
|
|
struct funnel_drvdata *drvdata = dev_get_drvdata(dev);
|
|
|
|
clk_disable_unprepare(drvdata->atclk);
|
|
clk_disable_unprepare(drvdata->pclk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int funnel_runtime_resume(struct device *dev)
|
|
{
|
|
struct funnel_drvdata *drvdata = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
ret = clk_prepare_enable(drvdata->pclk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_prepare_enable(drvdata->atclk);
|
|
if (ret)
|
|
clk_disable_unprepare(drvdata->pclk);
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static const struct dev_pm_ops funnel_dev_pm_ops = {
|
|
SET_RUNTIME_PM_OPS(funnel_runtime_suspend, funnel_runtime_resume, NULL)
|
|
};
|
|
|
|
static int funnel_platform_probe(struct platform_device *pdev)
|
|
{
|
|
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
int ret;
|
|
|
|
pm_runtime_get_noresume(&pdev->dev);
|
|
pm_runtime_set_active(&pdev->dev);
|
|
pm_runtime_enable(&pdev->dev);
|
|
|
|
ret = funnel_probe(&pdev->dev, res);
|
|
pm_runtime_put(&pdev->dev);
|
|
if (ret)
|
|
pm_runtime_disable(&pdev->dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void funnel_platform_remove(struct platform_device *pdev)
|
|
{
|
|
struct funnel_drvdata *drvdata = dev_get_drvdata(&pdev->dev);
|
|
|
|
if (WARN_ON(!drvdata))
|
|
return;
|
|
|
|
funnel_remove(&pdev->dev);
|
|
pm_runtime_disable(&pdev->dev);
|
|
}
|
|
|
|
static const struct of_device_id funnel_match[] = {
|
|
{.compatible = "arm,coresight-static-funnel"},
|
|
{}
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, funnel_match);
|
|
|
|
#ifdef CONFIG_ACPI
|
|
static const struct acpi_device_id funnel_acpi_ids[] = {
|
|
{"ARMHC9FE", 0, 0, 0}, /* ARM Coresight Static Funnel */
|
|
{"ARMHC9FF", 0, 0, 0}, /* ARM CoreSight Dynamic Funnel */
|
|
{},
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(acpi, funnel_acpi_ids);
|
|
#endif
|
|
|
|
static struct platform_driver funnel_driver = {
|
|
.probe = funnel_platform_probe,
|
|
.remove = funnel_platform_remove,
|
|
.driver = {
|
|
.name = "coresight-funnel",
|
|
/* THIS_MODULE is taken care of by platform_driver_register() */
|
|
.of_match_table = funnel_match,
|
|
.acpi_match_table = ACPI_PTR(funnel_acpi_ids),
|
|
.pm = &funnel_dev_pm_ops,
|
|
.suppress_bind_attrs = true,
|
|
},
|
|
};
|
|
|
|
static int dynamic_funnel_probe(struct amba_device *adev,
|
|
const struct amba_id *id)
|
|
{
|
|
int ret;
|
|
|
|
ret = funnel_probe(&adev->dev, &adev->res);
|
|
if (!ret)
|
|
pm_runtime_put(&adev->dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void dynamic_funnel_remove(struct amba_device *adev)
|
|
{
|
|
funnel_remove(&adev->dev);
|
|
}
|
|
|
|
static const struct amba_id dynamic_funnel_ids[] = {
|
|
{
|
|
.id = 0x000bb908,
|
|
.mask = 0x000fffff,
|
|
},
|
|
{
|
|
/* Coresight SoC-600 */
|
|
.id = 0x000bb9eb,
|
|
.mask = 0x000fffff,
|
|
},
|
|
{ 0, 0, NULL },
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(amba, dynamic_funnel_ids);
|
|
|
|
static struct amba_driver dynamic_funnel_driver = {
|
|
.drv = {
|
|
.name = "coresight-dynamic-funnel",
|
|
.pm = &funnel_dev_pm_ops,
|
|
.suppress_bind_attrs = true,
|
|
},
|
|
.probe = dynamic_funnel_probe,
|
|
.remove = dynamic_funnel_remove,
|
|
.id_table = dynamic_funnel_ids,
|
|
};
|
|
|
|
static int __init funnel_init(void)
|
|
{
|
|
return coresight_init_driver("funnel", &dynamic_funnel_driver, &funnel_driver,
|
|
THIS_MODULE);
|
|
}
|
|
|
|
static void __exit funnel_exit(void)
|
|
{
|
|
coresight_remove_driver(&dynamic_funnel_driver, &funnel_driver);
|
|
}
|
|
|
|
module_init(funnel_init);
|
|
module_exit(funnel_exit);
|
|
|
|
MODULE_AUTHOR("Pratik Patel <pratikp@codeaurora.org>");
|
|
MODULE_AUTHOR("Mathieu Poirier <mathieu.poirier@linaro.org>");
|
|
MODULE_DESCRIPTION("Arm CoreSight Funnel Driver");
|
|
MODULE_LICENSE("GPL v2");
|