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: 0f5f9b6ba9e1 ("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
#define catu_dbg(x, ...) do {} while (0)
#endif
-DEFINE_CORESIGHT_DEVLIST(catu_devs, "catu");
-
struct catu_etr_buf {
struct tmc_sg_table *catu_table;
dma_addr_t sladdr;
if (ret)
return ret;
- catu_desc.name = coresight_alloc_device_name(&catu_devs, dev);
+ catu_desc.name = coresight_alloc_device_name("catu", dev);
if (!catu_desc.name)
return -ENOMEM;
const u32 coresight_barrier_pkt[4] = {0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff};
EXPORT_SYMBOL_GPL(coresight_barrier_pkt);
+/* List maintains the device index */
+static LIST_HEAD(coresight_dev_idx_list);
+
static const struct cti_assoc_op *cti_assoc_ops;
void coresight_set_cti_ops(const struct cti_assoc_op *cti_op)
}
EXPORT_SYMBOL_GPL(coresight_unregister);
+static struct coresight_dev_list *
+coresight_allocate_device_list(const char *prefix)
+{
+ struct coresight_dev_list *list;
-/*
- * coresight_search_device_idx - Search the fwnode handle of a device
- * in the given dev_idx list. Must be called with the coresight_mutex held.
- *
- * Returns the index of the entry, when found. Otherwise, -ENOENT.
- */
-static int coresight_search_device_idx(struct coresight_dev_list *dict,
- struct fwnode_handle *fwnode)
+ /* Check if have already allocated */
+ list_for_each_entry(list, &coresight_dev_idx_list, node) {
+ if (!strcmp(list->pfx, prefix))
+ return list;
+ }
+
+ list = kzalloc(sizeof(*list), GFP_KERNEL);
+ if (!list)
+ return NULL;
+
+ list->pfx = kstrdup(prefix, GFP_KERNEL);
+ if (!list->pfx) {
+ kfree(list);
+ return NULL;
+ }
+
+ list_add(&list->node, &coresight_dev_idx_list);
+ return list;
+}
+
+static int coresight_allocate_device_idx(struct coresight_dev_list *list,
+ struct device *dev)
{
- int i;
+ struct fwnode_handle **fwnode_list;
+ struct fwnode_handle *fwnode = dev_fwnode(dev);
+ int idx;
+
+ for (idx = 0; idx < list->nr_idx; idx++)
+ if (list->fwnode_list[idx] == fwnode)
+ return idx;
+
+ /* Make space for the new entry */
+ idx = list->nr_idx;
+ fwnode_list = krealloc_array(list->fwnode_list,
+ idx + 1, sizeof(*list->fwnode_list),
+ GFP_KERNEL);
+ if (!fwnode_list)
+ return -ENOMEM;
- for (i = 0; i < dict->nr_idx; i++)
- if (dict->fwnode_list[i] == fwnode)
- return i;
- return -ENOENT;
+ fwnode_list[idx] = fwnode;
+ list->fwnode_list = fwnode_list;
+ list->nr_idx = idx + 1;
+
+ return idx;
}
static bool coresight_compare_type(enum coresight_dev_type type_a,
EXPORT_SYMBOL_GPL(coresight_loses_context_with_cpu);
/*
- * coresight_alloc_device_name - Get an index for a given device in the
- * device index list specific to a driver. An index is allocated for a
- * device and is tracked with the fwnode_handle to prevent allocating
+ * coresight_alloc_device_name - Get an index for a given device in the list
+ * specific to a driver (presented by the prefix string). An index is allocated
+ * for a device and is tracked with the fwnode_handle to prevent allocating
* duplicate indices for the same device (e.g, if we defer probing of
* a device due to dependencies), in case the index is requested again.
*/
-char *coresight_alloc_device_name(struct coresight_dev_list *dict,
- struct device *dev)
+char *coresight_alloc_device_name(const char *prefix, struct device *dev)
{
- int idx;
+ struct coresight_dev_list *list;
char *name = NULL;
- struct fwnode_handle **list;
+ int idx;
mutex_lock(&coresight_mutex);
- idx = coresight_search_device_idx(dict, dev_fwnode(dev));
- if (idx < 0) {
- /* Make space for the new entry */
- idx = dict->nr_idx;
- list = krealloc_array(dict->fwnode_list,
- idx + 1, sizeof(*dict->fwnode_list),
- GFP_KERNEL);
- if (ZERO_OR_NULL_PTR(list)) {
- idx = -ENOMEM;
- goto done;
- }
+ list = coresight_allocate_device_list(prefix);
+ if (!list)
+ goto done;
- list[idx] = dev_fwnode(dev);
- dict->fwnode_list = list;
- dict->nr_idx = idx + 1;
- }
+ idx = coresight_allocate_device_idx(list, dev);
- name = devm_kasprintf(dev, GFP_KERNEL, "%s%d", dict->pfx, idx);
+ /*
+ * If index allocation fails, the device list is not released here;
+ * it is instead freed later by coresight_release_device_list() when
+ * the coresight_core module is unloaded.
+ */
+ if (idx < 0)
+ goto done;
+
+ name = devm_kasprintf(dev, GFP_KERNEL, "%s%d", list->pfx, idx);
done:
mutex_unlock(&coresight_mutex);
return name;
}
EXPORT_SYMBOL_GPL(coresight_alloc_device_name);
+static void coresight_release_device_list(void)
+{
+ struct coresight_dev_list *list, *next;
+ int i;
+
+ /*
+ * Here is no need to take coresight_mutex; this is during core module
+ * unloading, no race condition with other modules.
+ */
+
+ list_for_each_entry_safe(list, next, &coresight_dev_idx_list, node) {
+ for (i = 0; i < list->nr_idx; i++)
+ list->fwnode_list[i] = NULL;
+ list->nr_idx = 0;
+ list_del(&list->node);
+
+ kfree(list->pfx);
+ kfree(list->fwnode_list);
+ kfree(list);
+ }
+}
+
const struct bus_type coresight_bustype = {
.name = "coresight",
};
&coresight_notifier);
etm_perf_exit();
bus_unregister(&coresight_bustype);
+ coresight_release_device_list();
}
module_init(coresight_init);
#include "coresight-ctcu.h"
#include "coresight-priv.h"
-DEFINE_CORESIGHT_DEVLIST(ctcu_devs, "ctcu");
-
#define ctcu_writel(drvdata, val, offset) __raw_writel((val), drvdata->base + offset)
#define ctcu_readl(drvdata, offset) __raw_readl(drvdata->base + offset)
void __iomem *base;
int i, ret;
- desc.name = coresight_alloc_device_name(&ctcu_devs, dev);
+ desc.name = coresight_alloc_device_name("ctcu", dev);
if (!desc.name)
return -ENOMEM;
/* quick lookup list for CPU bound CTIs when power handling */
static struct cti_drvdata *cti_cpu_drvdata[NR_CPUS];
-/*
- * CTI naming. CTI bound to cores will have the name cti_cpu<N> where
- * N is the CPU ID. System CTIs will have the name cti_sys<I> where I
- * is an index allocated by order of discovery.
- *
- * CTI device name list - for CTI not bound to cores.
- */
-DEFINE_CORESIGHT_DEVLIST(cti_sys_devs, "cti_sys");
-
/* write set of regs to hardware - call with spinlock claimed */
void cti_write_all_hw_regs(struct cti_drvdata *drvdata)
{
/* default to powered - could change on PM notifications */
drvdata->config.hw_powered = true;
- /* set up device name - will depend if cpu bound or otherwise */
+ /*
+ * Set up device name - will depend if cpu bound or otherwise.
+ *
+ * CTI bound to cores will have the name cti_cpu<N> where N is th
+ * eCPU ID. System CTIs will have the name cti_sys<I> where I is an
+ * index allocated by order of discovery.
+ */
if (drvdata->ctidev.cpu >= 0)
cti_desc.name = devm_kasprintf(dev, GFP_KERNEL, "cti_cpu%d",
drvdata->ctidev.cpu);
else
- cti_desc.name = coresight_alloc_device_name(&cti_sys_devs, dev);
+ cti_desc.name = coresight_alloc_device_name("cti_sys", dev);
if (!cti_desc.name)
return -ENOMEM;
u8 traceid;
};
-DEFINE_CORESIGHT_DEVLIST(source_devs, "dummy_source");
-DEFINE_CORESIGHT_DEVLIST(sink_devs, "dummy_sink");
-
static int dummy_source_enable(struct coresight_device *csdev,
struct perf_event *event, enum cs_mode mode,
__maybe_unused struct coresight_path *path)
if (of_device_is_compatible(node, "arm,coresight-dummy-source")) {
- desc.name = coresight_alloc_device_name(&source_devs, dev);
+ desc.name = coresight_alloc_device_name("dummy_source", dev);
if (!desc.name)
return -ENOMEM;
drvdata->traceid = (u8)trace_id;
} else if (of_device_is_compatible(node, "arm,coresight-dummy-sink")) {
- desc.name = coresight_alloc_device_name(&sink_devs, dev);
+ desc.name = coresight_alloc_device_name("dummy_sink", dev);
if (!desc.name)
return -ENOMEM;
#define ETB_FFSR_BIT 1
#define ETB_FRAME_SIZE_WORDS 4
-DEFINE_CORESIGHT_DEVLIST(etb_devs, "etb");
-
/**
* struct etb_drvdata - specifics associated to an ETB component
* @base: memory mapped base address for this component.
struct resource *res = &adev->res;
struct coresight_desc desc = { 0 };
- desc.name = coresight_alloc_device_name(&etb_devs, dev);
+ desc.name = coresight_alloc_device_name("etb", dev);
if (!desc.name)
return -ENOMEM;
#define FUNNEL_HOLDTIME (0x7 << FUNNEL_HOLDTIME_SHFT)
#define FUNNEL_ENSx_MASK 0xff
-DEFINE_CORESIGHT_DEVLIST(funnel_devs, "funnel");
-
/**
* struct funnel_drvdata - specifics associated to a funnel component
* @base: memory mapped base address for this component.
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_devs, dev);
+ desc.name = coresight_alloc_device_name("funnel", dev);
if (!desc.name)
return -ENOMEM;
#define REPLICATOR_IDFILTER0 0x000
#define REPLICATOR_IDFILTER1 0x004
-DEFINE_CORESIGHT_DEVLIST(replicator_devs, "replicator");
-
/**
* struct replicator_drvdata - specifics associated to a replicator component
* @base: memory mapped base address for this component. Also indicates
dev_warn_once(dev,
"Uses OBSOLETE CoreSight replicator binding\n");
- desc.name = coresight_alloc_device_name(&replicator_devs, dev);
+ desc.name = coresight_alloc_device_name("replicator", dev);
if (!desc.name)
return -ENOMEM;
unsigned long *guaranteed;
};
-DEFINE_CORESIGHT_DEVLIST(stm_devs, "stm");
-
/**
* struct stm_drvdata - specifics associated to an STM component
* @base: memory mapped base address for this component.
struct resource ch_res;
struct coresight_desc desc = { 0 };
- desc.name = coresight_alloc_device_name(&stm_devs, dev);
+ desc.name = coresight_alloc_device_name("stm", dev);
if (!desc.name)
return -ENOMEM;
#include "coresight-priv.h"
#include "coresight-tmc.h"
-DEFINE_CORESIGHT_DEVLIST(etb_devs, "tmc_etb");
-DEFINE_CORESIGHT_DEVLIST(etf_devs, "tmc_etf");
-DEFINE_CORESIGHT_DEVLIST(etr_devs, "tmc_etr");
-
int tmc_wait_for_tmcready(struct tmc_drvdata *drvdata)
{
struct coresight_device *csdev = drvdata->csdev;
struct coresight_platform_data *pdata = NULL;
struct tmc_drvdata *drvdata;
struct coresight_desc desc = { 0 };
- struct coresight_dev_list *dev_list = NULL;
+ const char *dev_list = NULL;
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
if (!drvdata)
desc.type = CORESIGHT_DEV_TYPE_SINK;
desc.subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_BUFFER;
desc.ops = &tmc_etb_cs_ops;
- dev_list = &etb_devs;
+ dev_list = "tmc_etb";
break;
case TMC_CONFIG_TYPE_ETR:
desc.groups = coresight_etr_groups;
goto out;
idr_init(&drvdata->idr);
mutex_init(&drvdata->idr_mutex);
- dev_list = &etr_devs;
+ dev_list = "tmc_etr";
break;
case TMC_CONFIG_TYPE_ETF:
desc.groups = coresight_etf_groups;
desc.subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_BUFFER;
desc.subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_FIFO;
desc.ops = &tmc_etf_cs_ops;
- dev_list = &etf_devs;
+ dev_list = "tmc_etf";
break;
default:
pr_err("%s: Unsupported TMC config\n", desc.name);
int atid;
};
-DEFINE_CORESIGHT_DEVLIST(trace_noc_devs, "traceNoc");
-
static void trace_noc_enable_hw(struct trace_noc_drvdata *drvdata)
{
u32 val;
struct coresight_desc desc = { 0 };
int ret;
- desc.name = coresight_alloc_device_name(&trace_noc_devs, dev);
+ desc.name = coresight_alloc_device_name("traceNoc", dev);
if (!desc.name)
return -ENOMEM;
#include "coresight-trace-id.h"
#include "coresight-tpdm.h"
-DEFINE_CORESIGHT_DEVLIST(tpda_devs, "tpda");
-
static void tpda_clear_element_size(struct coresight_device *csdev)
{
struct tpda_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
if (ret)
return ret;
- desc.name = coresight_alloc_device_name(&tpda_devs, dev);
+ desc.name = coresight_alloc_device_name("tpda", dev);
if (!desc.name)
return -ENOMEM;
desc.type = CORESIGHT_DEV_TYPE_LINK;
#include "coresight-priv.h"
#include "coresight-tpdm.h"
-DEFINE_CORESIGHT_DEVLIST(tpdm_devs, "tpdm");
-
static bool tpdm_has_dsb_dataset(struct tpdm_drvdata *drvdata)
{
return (drvdata->datasets & TPDM_PIDR0_DS_DSB);
}
/* Set up coresight component description */
- desc.name = coresight_alloc_device_name(&tpdm_devs, dev);
+ desc.name = coresight_alloc_device_name("tpdm", dev);
if (!desc.name)
return -ENOMEM;
desc.type = CORESIGHT_DEV_TYPE_SOURCE;
#define FFCR_FON_MAN BIT(6)
#define FFCR_STOP_FI BIT(12)
-DEFINE_CORESIGHT_DEVLIST(tpiu_devs, "tpiu");
-
/*
* @base: memory mapped base address for this component.
* @atclk: optional clock for the core parts of the TPIU.
struct coresight_desc desc = { 0 };
int ret;
- desc.name = coresight_alloc_device_name(&tpiu_devs, dev);
+ desc.name = coresight_alloc_device_name("tpiu", dev);
if (!desc.name)
return -ENOMEM;
#include "coresight-priv.h"
#include "ultrasoc-smb.h"
-DEFINE_CORESIGHT_DEVLIST(sink_devs, "ultra_smb");
-
#define ULTRASOC_SMB_DSM_UUID "82ae1283-7f6a-4cbe-aa06-53e8fb24db18"
static bool smb_buffer_not_empty(struct smb_drv_data *drvdata)
desc.pdata = pdata;
desc.dev = &pdev->dev;
desc.groups = smb_sink_groups;
- desc.name = coresight_alloc_device_name(&sink_devs, &pdev->dev);
+ desc.name = coresight_alloc_device_name("ultra_smb", &pdev->dev);
if (!desc.name) {
dev_err(&pdev->dev, "Failed to alloc coresight device name");
return -ENOMEM;
* coresight_dev_list - Mapping for devices to "name" index for device
* names.
*
+ * @node: Node on the global device index list.
* @nr_idx: Number of entries already allocated.
* @pfx: Prefix pattern for device name.
* @fwnode_list: Array of fwnode_handles associated with each allocated
* index, upto nr_idx entries.
*/
struct coresight_dev_list {
+ struct list_head node;
int nr_idx;
- const char *pfx;
+ char *pfx;
struct fwnode_handle **fwnode_list;
};
-#define DEFINE_CORESIGHT_DEVLIST(var, dev_pfx) \
-static struct coresight_dev_list (var) = { \
- .pfx = dev_pfx, \
- .nr_idx = 0, \
- .fwnode_list = NULL, \
-}
-
#define to_coresight_device(d) container_of(d, struct coresight_device, dev)
/**
void coresight_clear_self_claim_tag_unlocked(struct csdev_access *csa);
void coresight_disclaim_device(struct coresight_device *csdev);
void coresight_disclaim_device_unlocked(struct coresight_device *csdev);
-char *coresight_alloc_device_name(struct coresight_dev_list *devs,
- struct device *dev);
+char *coresight_alloc_device_name(const char *prefix, struct device *dev);
bool coresight_loses_context_with_cpu(struct device *dev);