linux-loongson/include/linux/interconnect-provider.h
Gabor Juhos c5b6059288 interconnect: avoid memory allocation when 'icc_bw_lock' is held
The 'icc_bw_lock' mutex is introduced in commit af42269c35
("interconnect: Fix locking for runpm vs reclaim") in order to decouple
serialization of bw aggregation from codepaths that require memory
allocation.

However commit d30f83d278 ("interconnect: core: Add dynamic id
allocation support") added a devm_kasprintf() call into a path protected
by the 'icc_bw_lock' which causes the following lockdep warning on
machines like the Lenovo ThinkPad X13s:

    ======================================================
    WARNING: possible circular locking dependency detected
    6.16.0-rc3 #15 Not tainted
    ------------------------------------------------------
    (udev-worker)/342 is trying to acquire lock:
    ffffb973f7ec4638 (fs_reclaim){+.+.}-{0:0}, at: __kmalloc_node_track_caller_noprof+0xa0/0x3e0

    but task is already holding lock:
    ffffb973f7f7f0e8 (icc_bw_lock){+.+.}-{4:4}, at: icc_node_add+0x44/0x154

    which lock already depends on the new lock.

    the existing dependency chain (in reverse order) is:

    -> #1 (icc_bw_lock){+.+.}-{4:4}:
           icc_init+0x48/0x108
           do_one_initcall+0x64/0x30c
           kernel_init_freeable+0x27c/0x500
           kernel_init+0x20/0x1d8
           ret_from_fork+0x10/0x20

    -> #0 (fs_reclaim){+.+.}-{0:0}:
           __lock_acquire+0x136c/0x2114
           lock_acquire+0x1c8/0x354
           fs_reclaim_acquire+0x74/0xa8
           __kmalloc_node_track_caller_noprof+0xa0/0x3e0
           devm_kmalloc+0x54/0x124
           devm_kvasprintf+0x74/0xd4
           devm_kasprintf+0x58/0x80
           icc_node_add+0xb4/0x154
           qcom_osm_l3_probe+0x20c/0x314 [icc_osm_l3]
           platform_probe+0x68/0xd8
           really_probe+0xc0/0x38c
           __driver_probe_device+0x7c/0x160
           driver_probe_device+0x40/0x110
           __driver_attach+0xfc/0x208
           bus_for_each_dev+0x74/0xd0
           driver_attach+0x24/0x30
           bus_add_driver+0x110/0x234
           driver_register+0x60/0x128
           __platform_driver_register+0x24/0x30
           osm_l3_driver_init+0x20/0x1000 [icc_osm_l3]
           do_one_initcall+0x64/0x30c
           do_init_module+0x58/0x23c
           load_module+0x1df8/0x1f70
           init_module_from_file+0x88/0xc4
           idempotent_init_module+0x188/0x280
           __arm64_sys_finit_module+0x6c/0xd8
           invoke_syscall+0x48/0x110
           el0_svc_common.constprop.0+0xc0/0xe0
           do_el0_svc+0x1c/0x28
           el0_svc+0x4c/0x158
           el0t_64_sync_handler+0xc8/0xcc
           el0t_64_sync+0x198/0x19c

    other info that might help us debug this:

     Possible unsafe locking scenario:

           CPU0                    CPU1
           ----                    ----
      lock(icc_bw_lock);
                                   lock(fs_reclaim);
                                   lock(icc_bw_lock);
      lock(fs_reclaim);

     *** DEADLOCK ***

The icc_node_add() functions is not designed to fail, and as such it
should not do any memory allocation. In order to avoid this, add a new
helper function for the name generation to be called by drivers which
are using the new dynamic id feature.

Fixes: d30f83d278 ("interconnect: core: Add dynamic id allocation support")
Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
Link: https://lore.kernel.org/r/20250625-icc-bw-lockdep-v3-1-2b8f8b8987c4@gmail.com
Co-developed-by: Johan Hovold <johan+linaro@kernel.org>
Signed-off-by: Johan Hovold <johan+linaro@kernel.org>
Link: https://lore.kernel.org/r/20250627075854.26943-1-johan+linaro@kernel.org
Signed-off-by: Georgi Djakov <djakov@kernel.org>
2025-06-27 21:43:40 +03:00

202 lines
6.1 KiB
C

/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2018, Linaro Ltd.
* Author: Georgi Djakov <georgi.djakov@linaro.org>
*/
#ifndef __LINUX_INTERCONNECT_PROVIDER_H
#define __LINUX_INTERCONNECT_PROVIDER_H
#include <linux/interconnect.h>
#define icc_units_to_bps(bw) ((bw) * 1000ULL)
struct icc_node;
struct of_phandle_args;
/**
* struct icc_node_data - icc node data
*
* @node: icc node
* @tag: tag
*/
struct icc_node_data {
struct icc_node *node;
u32 tag;
};
/**
* struct icc_onecell_data - driver data for onecell interconnect providers
*
* @num_nodes: number of nodes in this device
* @nodes: array of pointers to the nodes in this device
*/
struct icc_onecell_data {
unsigned int num_nodes;
struct icc_node *nodes[] __counted_by(num_nodes);
};
struct icc_node *of_icc_xlate_onecell(const struct of_phandle_args *spec,
void *data);
/**
* struct icc_provider - interconnect provider (controller) entity that might
* provide multiple interconnect controls
*
* @provider_list: list of the registered interconnect providers
* @nodes: internal list of the interconnect provider nodes
* @set: pointer to device specific set operation function
* @aggregate: pointer to device specific aggregate operation function
* @pre_aggregate: pointer to device specific function that is called
* before the aggregation begins (optional)
* @get_bw: pointer to device specific function to get current bandwidth
* @xlate: provider-specific callback for mapping nodes from phandle arguments
* @xlate_extended: vendor-specific callback for mapping node data from phandle arguments
* @dev: the device this interconnect provider belongs to
* @users: count of active users
* @inter_set: whether inter-provider pairs will be configured with @set
* @data: pointer to private data
*/
struct icc_provider {
struct list_head provider_list;
struct list_head nodes;
int (*set)(struct icc_node *src, struct icc_node *dst);
int (*aggregate)(struct icc_node *node, u32 tag, u32 avg_bw,
u32 peak_bw, u32 *agg_avg, u32 *agg_peak);
void (*pre_aggregate)(struct icc_node *node);
int (*get_bw)(struct icc_node *node, u32 *avg, u32 *peak);
struct icc_node* (*xlate)(const struct of_phandle_args *spec, void *data);
struct icc_node_data* (*xlate_extended)(const struct of_phandle_args *spec,
void *data);
struct device *dev;
int users;
bool inter_set;
void *data;
};
/**
* struct icc_node - entity that is part of the interconnect topology
*
* @id: platform specific node id
* @name: node name used in debugfs
* @links: a list of targets pointing to where we can go next when traversing
* @num_links: number of links to other interconnect nodes
* @provider: points to the interconnect provider of this node
* @node_list: the list entry in the parent provider's "nodes" list
* @search_list: list used when walking the nodes graph
* @reverse: pointer to previous node when walking the nodes graph
* @is_traversed: flag that is used when walking the nodes graph
* @req_list: a list of QoS constraint requests associated with this node
* @avg_bw: aggregated value of average bandwidth requests from all consumers
* @peak_bw: aggregated value of peak bandwidth requests from all consumers
* @init_avg: average bandwidth value that is read from the hardware during init
* @init_peak: peak bandwidth value that is read from the hardware during init
* @data: pointer to private data
*/
struct icc_node {
int id;
const char *name;
struct icc_node **links;
size_t num_links;
struct icc_provider *provider;
struct list_head node_list;
struct list_head search_list;
struct icc_node *reverse;
u8 is_traversed:1;
struct hlist_head req_list;
u32 avg_bw;
u32 peak_bw;
u32 init_avg;
u32 init_peak;
void *data;
};
#if IS_ENABLED(CONFIG_INTERCONNECT)
int icc_std_aggregate(struct icc_node *node, u32 tag, u32 avg_bw,
u32 peak_bw, u32 *agg_avg, u32 *agg_peak);
struct icc_node *icc_node_create_dyn(void);
struct icc_node *icc_node_create(int id);
void icc_node_destroy(int id);
int icc_node_set_name(struct icc_node *node, const struct icc_provider *provider, const char *name);
int icc_link_nodes(struct icc_node *src_node, struct icc_node **dst_node);
int icc_link_create(struct icc_node *node, const int dst_id);
void icc_node_add(struct icc_node *node, struct icc_provider *provider);
void icc_node_del(struct icc_node *node);
int icc_nodes_remove(struct icc_provider *provider);
void icc_provider_init(struct icc_provider *provider);
int icc_provider_register(struct icc_provider *provider);
void icc_provider_deregister(struct icc_provider *provider);
struct icc_node_data *of_icc_get_from_provider(const struct of_phandle_args *spec);
void icc_sync_state(struct device *dev);
#else
static inline int icc_std_aggregate(struct icc_node *node, u32 tag, u32 avg_bw,
u32 peak_bw, u32 *agg_avg, u32 *agg_peak)
{
return -ENOTSUPP;
}
static inline struct icc_node *icc_node_create_dyn(void)
{
return ERR_PTR(-EOPNOTSUPP);
}
static inline struct icc_node *icc_node_create(int id)
{
return ERR_PTR(-ENOTSUPP);
}
static inline void icc_node_destroy(int id)
{
}
static inline int icc_node_set_name(struct icc_node *node, const struct icc_provider *provider,
const char *name)
{
return -EOPNOTSUPP;
}
static inline int icc_link_nodes(struct icc_node *src_node, struct icc_node **dst_node)
{
return -EOPNOTSUPP;
}
static inline int icc_link_create(struct icc_node *node, const int dst_id)
{
return -ENOTSUPP;
}
static inline void icc_node_add(struct icc_node *node, struct icc_provider *provider)
{
}
static inline void icc_node_del(struct icc_node *node)
{
}
static inline int icc_nodes_remove(struct icc_provider *provider)
{
return -ENOTSUPP;
}
static inline void icc_provider_init(struct icc_provider *provider) { }
static inline int icc_provider_register(struct icc_provider *provider)
{
return -ENOTSUPP;
}
static inline void icc_provider_deregister(struct icc_provider *provider) { }
static inline struct icc_node_data *of_icc_get_from_provider(const struct of_phandle_args *spec)
{
return ERR_PTR(-ENOTSUPP);
}
#endif /* CONFIG_INTERCONNECT */
#endif /* __LINUX_INTERCONNECT_PROVIDER_H */