diff options
Diffstat (limited to 'drivers/interconnect/core.c')
| -rw-r--r-- | drivers/interconnect/core.c | 263 |
1 files changed, 204 insertions, 59 deletions
diff --git a/drivers/interconnect/core.c b/drivers/interconnect/core.c index ec46bcb16d5e..6cc979b26151 100644 --- a/drivers/interconnect/core.c +++ b/drivers/interconnect/core.c @@ -20,6 +20,8 @@ #include "internal.h" +#define ICC_DYN_ID_START 100000 + #define CREATE_TRACE_POINTS #include "trace.h" @@ -28,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) @@ -147,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) { @@ -160,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); @@ -170,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; } @@ -327,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; @@ -352,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; @@ -363,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)) { @@ -379,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); @@ -562,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 @@ -587,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. @@ -605,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 * @@ -631,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; @@ -663,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); @@ -705,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 * @@ -773,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); @@ -790,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) @@ -799,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); @@ -812,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 * @@ -853,11 +909,86 @@ void icc_node_destroy(int id) return; kfree(node->links); + if (node->id >= ICC_DYN_ID_START) + kfree(node->name); kfree(node); } EXPORT_SYMBOL_GPL(icc_node_destroy); /** + * 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_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_nodes(struct icc_node *src_node, struct icc_node **dst_node) +{ + struct icc_node **new; + int ret = 0; + + if (!src_node->provider) + return -EINVAL; + + mutex_lock(&icc_lock); + + if (!*dst_node) { + *dst_node = icc_node_create_nolock(ICC_ALLOC_DYN_ID); + + if (IS_ERR(*dst_node)) { + ret = PTR_ERR(*dst_node); + goto out; + } + } + + new = krealloc(src_node->links, + (src_node->num_links + 1) * sizeof(*src_node->links), + GFP_KERNEL); + if (!new) { + ret = -ENOMEM; + goto out; + } + + 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_nodes); + +/** * icc_link_create() - create a link between two nodes * @node: source node id * @dst_id: destination node id @@ -920,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); @@ -948,6 +1080,7 @@ void icc_node_add(struct icc_node *node, struct icc_provider *provider) node->avg_bw = 0; node->peak_bw = 0; + mutex_unlock(&icc_bw_lock); mutex_unlock(&icc_lock); } EXPORT_SYMBOL_GPL(icc_node_add); @@ -1052,7 +1185,7 @@ static int of_count_icc_providers(struct device_node *np) int count = 0; 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); @@ -1073,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"); @@ -1085,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); @@ -1101,6 +1243,9 @@ 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; } |
