summaryrefslogtreecommitdiff
path: root/drivers/interconnect/core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/interconnect/core.c')
-rw-r--r--drivers/interconnect/core.c389
1 files changed, 246 insertions, 143 deletions
diff --git a/drivers/interconnect/core.c b/drivers/interconnect/core.c
index 25debded65a8..6cc979b26151 100644
--- a/drivers/interconnect/core.c
+++ b/drivers/interconnect/core.c
@@ -13,7 +13,6 @@
#include <linux/interconnect.h>
#include <linux/interconnect-provider.h>
#include <linux/list.h>
-#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/of.h>
@@ -21,6 +20,8 @@
#include "internal.h"
+#define ICC_DYN_ID_START 100000
+
#define CREATE_TRACE_POINTS
#include "trace.h"
@@ -29,6 +30,7 @@ static LIST_HEAD(icc_providers);
static int providers_count;
static bool synced_state;
static DEFINE_MUTEX(icc_lock);
+static DEFINE_MUTEX(icc_bw_lock);
static struct dentry *icc_debugfs_dir;
static void icc_summary_show_one(struct seq_file *s, struct icc_node *n)
@@ -148,6 +150,21 @@ static struct icc_node *node_find(const int id)
return idr_find(&icc_idr, id);
}
+static struct icc_node *node_find_by_name(const char *name)
+{
+ struct icc_provider *provider;
+ struct icc_node *n;
+
+ list_for_each_entry(provider, &icc_providers, provider_list) {
+ list_for_each_entry(n, &provider->nodes, node_list) {
+ if (!strcmp(n->name, name))
+ return n;
+ }
+ }
+
+ return NULL;
+}
+
static struct icc_path *path_init(struct device *dev, struct icc_node *dst,
ssize_t num_nodes)
{
@@ -161,6 +178,8 @@ static struct icc_path *path_init(struct device *dev, struct icc_node *dst,
path->num_nodes = num_nodes;
+ mutex_lock(&icc_bw_lock);
+
for (i = num_nodes - 1; i >= 0; i--) {
node->provider->users++;
hlist_add_head(&path->reqs[i].req_node, &node->req_list);
@@ -171,6 +190,8 @@ static struct icc_path *path_init(struct device *dev, struct icc_node *dst,
node = node->reverse;
}
+ mutex_unlock(&icc_bw_lock);
+
return path;
}
@@ -328,7 +349,7 @@ EXPORT_SYMBOL_GPL(icc_std_aggregate);
* an array of icc nodes specified in the icc_onecell_data struct when
* registering the provider.
*/
-struct icc_node *of_icc_xlate_onecell(struct of_phandle_args *spec,
+struct icc_node *of_icc_xlate_onecell(const struct of_phandle_args *spec,
void *data)
{
struct icc_onecell_data *icc_data = data;
@@ -353,7 +374,7 @@ EXPORT_SYMBOL_GPL(of_icc_xlate_onecell);
* Returns a valid pointer to struct icc_node_data on success or ERR_PTR()
* on failure.
*/
-struct icc_node_data *of_icc_get_from_provider(struct of_phandle_args *spec)
+struct icc_node_data *of_icc_get_from_provider(const struct of_phandle_args *spec)
{
struct icc_node *node = ERR_PTR(-EPROBE_DEFER);
struct icc_node_data *data = NULL;
@@ -364,7 +385,7 @@ struct icc_node_data *of_icc_get_from_provider(struct of_phandle_args *spec)
mutex_lock(&icc_lock);
list_for_each_entry(provider, &icc_providers, provider_list) {
- if (provider->dev->of_node == spec->np) {
+ if (device_match_of_node(provider->dev, spec->np)) {
if (provider->xlate_extended) {
data = provider->xlate_extended(spec, provider->data);
if (!IS_ERR(data)) {
@@ -380,6 +401,9 @@ struct icc_node_data *of_icc_get_from_provider(struct of_phandle_args *spec)
}
mutex_unlock(&icc_lock);
+ if (!node)
+ return ERR_PTR(-EINVAL);
+
if (IS_ERR(node))
return ERR_CAST(node);
@@ -451,7 +475,7 @@ struct icc_path *of_icc_get_by_index(struct device *dev, int idx)
* When the consumer DT node do not have "interconnects" property
* return a NULL path to skip setting constraints.
*/
- if (!of_find_property(np, "interconnects", NULL))
+ if (!of_property_present(np, "interconnects"))
return NULL;
/*
@@ -544,7 +568,7 @@ struct icc_path *of_icc_get(struct device *dev, const char *name)
* When the consumer DT node do not have "interconnects" property
* return a NULL path to skip setting constraints.
*/
- if (!of_find_property(np, "interconnects", NULL))
+ if (!of_property_present(np, "interconnects"))
return NULL;
/*
@@ -563,6 +587,54 @@ struct icc_path *of_icc_get(struct device *dev, const char *name)
EXPORT_SYMBOL_GPL(of_icc_get);
/**
+ * icc_get() - get a path handle between two endpoints
+ * @dev: device pointer for the consumer device
+ * @src: source node name
+ * @dst: destination node name
+ *
+ * This function will search for a path between two endpoints and return an
+ * icc_path handle on success. Use icc_put() to release constraints when they
+ * are not needed anymore.
+ *
+ * Return: icc_path pointer on success or ERR_PTR() on error. NULL is returned
+ * when the API is disabled.
+ */
+struct icc_path *icc_get(struct device *dev, const char *src, const char *dst)
+{
+ struct icc_node *src_node, *dst_node;
+ struct icc_path *path = ERR_PTR(-EPROBE_DEFER);
+
+ mutex_lock(&icc_lock);
+
+ src_node = node_find_by_name(src);
+ if (!src_node) {
+ dev_err(dev, "%s: invalid src=%s\n", __func__, src);
+ goto out;
+ }
+
+ dst_node = node_find_by_name(dst);
+ if (!dst_node) {
+ dev_err(dev, "%s: invalid dst=%s\n", __func__, dst);
+ goto out;
+ }
+
+ path = path_find(dev, src_node, dst_node);
+ if (IS_ERR(path)) {
+ dev_err(dev, "%s: invalid path=%ld\n", __func__, PTR_ERR(path));
+ goto out;
+ }
+
+ path->name = kasprintf(GFP_KERNEL, "%s-%s", src_node->name, dst_node->name);
+ if (!path->name) {
+ kfree(path);
+ path = ERR_PTR(-ENOMEM);
+ }
+out:
+ mutex_unlock(&icc_lock);
+ return path;
+}
+
+/**
* icc_set_tag() - set an optional tag on a path
* @path: the path we want to tag
* @tag: the tag value
@@ -588,7 +660,7 @@ EXPORT_SYMBOL_GPL(icc_set_tag);
/**
* icc_get_name() - Get name of the icc path
- * @path: reference to the path returned by icc_get()
+ * @path: interconnect path
*
* This function is used by an interconnect consumer to get the name of the icc
* path.
@@ -606,7 +678,7 @@ EXPORT_SYMBOL_GPL(icc_get_name);
/**
* icc_set_bw() - set bandwidth constraints on an interconnect path
- * @path: reference to the path returned by icc_get()
+ * @path: interconnect path
* @avg_bw: average bandwidth in kilobytes per second
* @peak_bw: peak bandwidth in kilobytes per second
*
@@ -632,7 +704,7 @@ int icc_set_bw(struct icc_path *path, u32 avg_bw, u32 peak_bw)
if (WARN_ON(IS_ERR(path) || !path->num_nodes))
return -EINVAL;
- mutex_lock(&icc_lock);
+ mutex_lock(&icc_bw_lock);
old_avg = path->reqs[0].avg_bw;
old_peak = path->reqs[0].peak_bw;
@@ -664,7 +736,7 @@ int icc_set_bw(struct icc_path *path, u32 avg_bw, u32 peak_bw)
apply_constraints(path);
}
- mutex_unlock(&icc_lock);
+ mutex_unlock(&icc_bw_lock);
trace_icc_set_bw_end(path, ret);
@@ -706,54 +778,6 @@ int icc_disable(struct icc_path *path)
EXPORT_SYMBOL_GPL(icc_disable);
/**
- * icc_get() - return a handle for path between two endpoints
- * @dev: the device requesting the path
- * @src_id: source device port id
- * @dst_id: destination device port id
- *
- * This function will search for a path between two endpoints and return an
- * icc_path handle on success. Use icc_put() to release
- * constraints when they are not needed anymore.
- * If the interconnect API is disabled, NULL is returned and the consumer
- * drivers will still build. Drivers are free to handle this specifically,
- * but they don't have to.
- *
- * Return: icc_path pointer on success, ERR_PTR() on error or NULL if the
- * interconnect API is disabled.
- */
-struct icc_path *icc_get(struct device *dev, const int src_id, const int dst_id)
-{
- struct icc_node *src, *dst;
- struct icc_path *path = ERR_PTR(-EPROBE_DEFER);
-
- mutex_lock(&icc_lock);
-
- src = node_find(src_id);
- if (!src)
- goto out;
-
- dst = node_find(dst_id);
- if (!dst)
- goto out;
-
- path = path_find(dev, src, dst);
- if (IS_ERR(path)) {
- dev_err(dev, "%s: invalid path=%ld\n", __func__, PTR_ERR(path));
- goto out;
- }
-
- path->name = kasprintf(GFP_KERNEL, "%s-%s", src->name, dst->name);
- if (!path->name) {
- kfree(path);
- path = ERR_PTR(-ENOMEM);
- }
-out:
- mutex_unlock(&icc_lock);
- return path;
-}
-EXPORT_SYMBOL_GPL(icc_get);
-
-/**
* icc_put() - release the reference to the icc_path
* @path: interconnect path
*
@@ -774,15 +798,19 @@ void icc_put(struct icc_path *path)
pr_err("%s: error (%d)\n", __func__, ret);
mutex_lock(&icc_lock);
+ mutex_lock(&icc_bw_lock);
+
for (i = 0; i < path->num_nodes; i++) {
node = path->reqs[i].node;
hlist_del(&path->reqs[i].req_node);
if (!WARN_ON(!node->provider->users))
node->provider->users--;
}
+
+ mutex_unlock(&icc_bw_lock);
mutex_unlock(&icc_lock);
- kfree_const(path->name);
+ kfree(path->name);
kfree(path);
}
EXPORT_SYMBOL_GPL(icc_put);
@@ -791,6 +819,9 @@ static struct icc_node *icc_node_create_nolock(int id)
{
struct icc_node *node;
+ if (id >= ICC_DYN_ID_START)
+ return ERR_PTR(-EINVAL);
+
/* check if node already exists */
node = node_find(id);
if (node)
@@ -800,7 +831,12 @@ static struct icc_node *icc_node_create_nolock(int id)
if (!node)
return ERR_PTR(-ENOMEM);
- id = idr_alloc(&icc_idr, node, id, id + 1, GFP_KERNEL);
+ /* dynamic id allocation */
+ if (id == ICC_ALLOC_DYN_ID)
+ id = idr_alloc(&icc_idr, node, ICC_DYN_ID_START, 0, GFP_KERNEL);
+ else
+ id = idr_alloc(&icc_idr, node, id, id + 1, GFP_KERNEL);
+
if (id < 0) {
WARN(1, "%s: couldn't get idr\n", __func__);
kfree(node);
@@ -813,6 +849,25 @@ static struct icc_node *icc_node_create_nolock(int id)
}
/**
+ * icc_node_create_dyn() - create a node with dynamic id
+ *
+ * Return: icc_node pointer on success, or ERR_PTR() on error
+ */
+struct icc_node *icc_node_create_dyn(void)
+{
+ struct icc_node *node;
+
+ mutex_lock(&icc_lock);
+
+ node = icc_node_create_nolock(ICC_ALLOC_DYN_ID);
+
+ mutex_unlock(&icc_lock);
+
+ return node;
+}
+EXPORT_SYMBOL_GPL(icc_node_create_dyn);
+
+/**
* icc_node_create() - create a node
* @id: node id
*
@@ -850,107 +905,140 @@ void icc_node_destroy(int id)
mutex_unlock(&icc_lock);
+ if (!node)
+ return;
+
+ kfree(node->links);
+ if (node->id >= ICC_DYN_ID_START)
+ kfree(node->name);
kfree(node);
}
EXPORT_SYMBOL_GPL(icc_node_destroy);
/**
- * icc_link_create() - create a link between two nodes
- * @node: source node id
- * @dst_id: destination node id
+ * icc_node_set_name() - set node name
+ * @node: node
+ * @provider: node provider
+ * @name: node name
+ *
+ * Return: 0 on success, or -ENOMEM on allocation failure
+ */
+int icc_node_set_name(struct icc_node *node, const struct icc_provider *provider, const char *name)
+{
+ if (node->id >= ICC_DYN_ID_START) {
+ node->name = kasprintf(GFP_KERNEL, "%s@%s", name,
+ dev_name(provider->dev));
+ if (!node->name)
+ return -ENOMEM;
+ } else {
+ node->name = name;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(icc_node_set_name);
+
+/**
+ * icc_link_nodes() - create link between two nodes
+ * @src_node: source node
+ * @dst_node: destination node
*
* Create a link between two nodes. The nodes might belong to different
- * interconnect providers and the @dst_id node might not exist (if the
- * provider driver has not probed yet). So just create the @dst_id node
+ * interconnect providers and the @dst_node might not exist (if the
+ * provider driver has not probed yet). So just create the @dst_node
* and when the actual provider driver is probed, the rest of the node
* data is filled.
*
* Return: 0 on success, or an error code otherwise
*/
-int icc_link_create(struct icc_node *node, const int dst_id)
+int icc_link_nodes(struct icc_node *src_node, struct icc_node **dst_node)
{
- struct icc_node *dst;
struct icc_node **new;
int ret = 0;
- if (!node->provider)
+ if (!src_node->provider)
return -EINVAL;
mutex_lock(&icc_lock);
- dst = node_find(dst_id);
- if (!dst) {
- dst = icc_node_create_nolock(dst_id);
+ if (!*dst_node) {
+ *dst_node = icc_node_create_nolock(ICC_ALLOC_DYN_ID);
- if (IS_ERR(dst)) {
- ret = PTR_ERR(dst);
+ if (IS_ERR(*dst_node)) {
+ ret = PTR_ERR(*dst_node);
goto out;
}
}
- new = krealloc(node->links,
- (node->num_links + 1) * sizeof(*node->links),
+ new = krealloc(src_node->links,
+ (src_node->num_links + 1) * sizeof(*src_node->links),
GFP_KERNEL);
if (!new) {
ret = -ENOMEM;
goto out;
}
- node->links = new;
- node->links[node->num_links++] = dst;
+ src_node->links = new;
+ src_node->links[src_node->num_links++] = *dst_node;
out:
mutex_unlock(&icc_lock);
return ret;
}
-EXPORT_SYMBOL_GPL(icc_link_create);
+EXPORT_SYMBOL_GPL(icc_link_nodes);
/**
- * icc_link_destroy() - destroy a link between two nodes
- * @src: pointer to source node
- * @dst: pointer to destination node
+ * icc_link_create() - create a link between two nodes
+ * @node: source node id
+ * @dst_id: destination node id
+ *
+ * Create a link between two nodes. The nodes might belong to different
+ * interconnect providers and the @dst_id node might not exist (if the
+ * provider driver has not probed yet). So just create the @dst_id node
+ * and when the actual provider driver is probed, the rest of the node
+ * data is filled.
*
* Return: 0 on success, or an error code otherwise
*/
-int icc_link_destroy(struct icc_node *src, struct icc_node *dst)
+int icc_link_create(struct icc_node *node, const int dst_id)
{
+ struct icc_node *dst;
struct icc_node **new;
- size_t slot;
int ret = 0;
- if (IS_ERR_OR_NULL(src))
- return -EINVAL;
-
- if (IS_ERR_OR_NULL(dst))
+ if (!node->provider)
return -EINVAL;
mutex_lock(&icc_lock);
- for (slot = 0; slot < src->num_links; slot++)
- if (src->links[slot] == dst)
- break;
+ dst = node_find(dst_id);
+ if (!dst) {
+ dst = icc_node_create_nolock(dst_id);
- if (WARN_ON(slot == src->num_links)) {
- ret = -ENXIO;
- goto out;
+ if (IS_ERR(dst)) {
+ ret = PTR_ERR(dst);
+ goto out;
+ }
}
- src->links[slot] = src->links[--src->num_links];
-
- new = krealloc(src->links, src->num_links * sizeof(*src->links),
+ new = krealloc(node->links,
+ (node->num_links + 1) * sizeof(*node->links),
GFP_KERNEL);
- if (new)
- src->links = new;
- else
+ if (!new) {
ret = -ENOMEM;
+ goto out;
+ }
+
+ node->links = new;
+ node->links[node->num_links++] = dst;
out:
mutex_unlock(&icc_lock);
return ret;
}
-EXPORT_SYMBOL_GPL(icc_link_destroy);
+EXPORT_SYMBOL_GPL(icc_link_create);
/**
* icc_node_add() - add interconnect node to interconnect provider
@@ -963,6 +1051,7 @@ void icc_node_add(struct icc_node *node, struct icc_provider *provider)
return;
mutex_lock(&icc_lock);
+ mutex_lock(&icc_bw_lock);
node->provider = provider;
list_add_tail(&node->node_list, &provider->nodes);
@@ -977,17 +1066,21 @@ void icc_node_add(struct icc_node *node, struct icc_provider *provider)
node->avg_bw = node->init_avg;
node->peak_bw = node->init_peak;
- if (provider->pre_aggregate)
- provider->pre_aggregate(node);
+ if (node->avg_bw || node->peak_bw) {
+ if (provider->pre_aggregate)
+ provider->pre_aggregate(node);
- if (provider->aggregate)
- provider->aggregate(node, 0, node->init_avg, node->init_peak,
- &node->avg_bw, &node->peak_bw);
+ if (provider->aggregate)
+ provider->aggregate(node, 0, node->init_avg, node->init_peak,
+ &node->avg_bw, &node->peak_bw);
+ if (provider->set)
+ provider->set(node, node);
+ }
- provider->set(node, node);
node->avg_bw = 0;
node->peak_bw = 0;
+ mutex_unlock(&icc_bw_lock);
mutex_unlock(&icc_lock);
}
EXPORT_SYMBOL_GPL(icc_node_add);
@@ -1029,68 +1122,70 @@ int icc_nodes_remove(struct icc_provider *provider)
EXPORT_SYMBOL_GPL(icc_nodes_remove);
/**
- * icc_provider_add() - add a new interconnect provider
- * @provider: the interconnect provider that will be added into topology
+ * icc_provider_init() - initialize a new interconnect provider
+ * @provider: the interconnect provider to initialize
+ *
+ * Must be called before adding nodes to the provider.
+ */
+void icc_provider_init(struct icc_provider *provider)
+{
+ WARN_ON(!provider->set);
+
+ INIT_LIST_HEAD(&provider->nodes);
+}
+EXPORT_SYMBOL_GPL(icc_provider_init);
+
+/**
+ * icc_provider_register() - register a new interconnect provider
+ * @provider: the interconnect provider to register
*
* Return: 0 on success, or an error code otherwise
*/
-int icc_provider_add(struct icc_provider *provider)
+int icc_provider_register(struct icc_provider *provider)
{
- if (WARN_ON(!provider->set))
- return -EINVAL;
if (WARN_ON(!provider->xlate && !provider->xlate_extended))
return -EINVAL;
mutex_lock(&icc_lock);
-
- INIT_LIST_HEAD(&provider->nodes);
list_add_tail(&provider->provider_list, &icc_providers);
-
mutex_unlock(&icc_lock);
- dev_dbg(provider->dev, "interconnect provider added to topology\n");
+ dev_dbg(provider->dev, "interconnect provider registered\n");
return 0;
}
-EXPORT_SYMBOL_GPL(icc_provider_add);
+EXPORT_SYMBOL_GPL(icc_provider_register);
/**
- * icc_provider_del() - delete previously added interconnect provider
- * @provider: the interconnect provider that will be removed from topology
+ * icc_provider_deregister() - deregister an interconnect provider
+ * @provider: the interconnect provider to deregister
*/
-void icc_provider_del(struct icc_provider *provider)
+void icc_provider_deregister(struct icc_provider *provider)
{
mutex_lock(&icc_lock);
- if (provider->users) {
- pr_warn("interconnect provider still has %d users\n",
- provider->users);
- mutex_unlock(&icc_lock);
- return;
- }
-
- if (!list_empty(&provider->nodes)) {
- pr_warn("interconnect provider still has nodes\n");
- mutex_unlock(&icc_lock);
- return;
- }
+ WARN_ON(provider->users);
list_del(&provider->provider_list);
mutex_unlock(&icc_lock);
}
-EXPORT_SYMBOL_GPL(icc_provider_del);
+EXPORT_SYMBOL_GPL(icc_provider_deregister);
+
+static const struct of_device_id __maybe_unused ignore_list[] = {
+ { .compatible = "qcom,sc7180-ipa-virt" },
+ { .compatible = "qcom,sc8180x-ipa-virt" },
+ { .compatible = "qcom,sdx55-ipa-virt" },
+ { .compatible = "qcom,sm8150-ipa-virt" },
+ { .compatible = "qcom,sm8250-ipa-virt" },
+ {}
+};
static int of_count_icc_providers(struct device_node *np)
{
struct device_node *child;
int count = 0;
- const struct of_device_id __maybe_unused ignore_list[] = {
- { .compatible = "qcom,sc7180-ipa-virt" },
- { .compatible = "qcom,sdx55-ipa-virt" },
- {}
- };
for_each_available_child_of_node(np, child) {
- if (of_property_read_bool(child, "#interconnect-cells") &&
+ if (of_property_present(child, "#interconnect-cells") &&
likely(!of_match_node(ignore_list, child)))
count++;
count += of_count_icc_providers(child);
@@ -1111,6 +1206,7 @@ void icc_sync_state(struct device *dev)
return;
mutex_lock(&icc_lock);
+ mutex_lock(&icc_bw_lock);
synced_state = true;
list_for_each_entry(p, &icc_providers, provider_list) {
dev_dbg(p->dev, "interconnect provider is in synced state\n");
@@ -1123,13 +1219,21 @@ void icc_sync_state(struct device *dev)
}
}
}
+ mutex_unlock(&icc_bw_lock);
mutex_unlock(&icc_lock);
}
EXPORT_SYMBOL_GPL(icc_sync_state);
static int __init icc_init(void)
{
- struct device_node *root = of_find_node_by_path("/");
+ struct device_node *root;
+
+ /* Teach lockdep about lock ordering wrt. shrinker: */
+ fs_reclaim_acquire(GFP_KERNEL);
+ might_lock(&icc_bw_lock);
+ fs_reclaim_release(GFP_KERNEL);
+
+ root = of_find_node_by_path("/");
providers_count = of_count_icc_providers(root);
of_node_put(root);
@@ -1139,11 +1243,10 @@ static int __init icc_init(void)
icc_debugfs_dir, NULL, &icc_summary_fops);
debugfs_create_file("interconnect_graph", 0444,
icc_debugfs_dir, NULL, &icc_graph_fops);
+
+ icc_debugfs_client_init(icc_debugfs_dir);
+
return 0;
}
device_initcall(icc_init);
-
-MODULE_AUTHOR("Georgi Djakov <georgi.djakov@linaro.org>");
-MODULE_DESCRIPTION("Interconnect Driver Core");
-MODULE_LICENSE("GPL v2");