// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2025 Linaro Limited * Based on the Linux Driver: * Copyright (c) 2017-2019, Linaro Ltd. * Author: Georgi Djakov */ #define LOG_CATEGORY UCLASS_INTERCONNECT #include #include #include #include #include #include #include #include #include #include static struct icc_node *of_icc_get_from_provider(struct udevice *dev, const struct ofnode_phandle_args *args); static struct icc_path *icc_path_find(struct udevice *dev, struct icc_node *src, struct icc_node *dst); static struct icc_node *icc_node_find(const ulong id); /* Public API */ struct icc_path *of_icc_get(struct udevice *dev, const char *name) { int index = 0; if (!dev) return ERR_PTR(-ENODEV); if (!ofnode_has_property(dev_ofnode(dev), "interconnects")) return NULL; if (name) { index = dev_read_stringlist_search(dev, "interconnect-names", name); if (index < 0) { debug("fdt_stringlist_search() failed: %d\n", index); return ERR_PTR(index); } } return of_icc_get_by_index(dev, index); } struct icc_path *of_icc_get_by_index(struct udevice *dev, int index) { struct ofnode_phandle_args src_args, dst_args; struct icc_node *src_node, *dst_node; struct icc_path *path; int ret; if (!dev) return ERR_PTR(-ENODEV); debug("(dev=%p,idx=%d)\n", dev, index); if (!ofnode_has_property(dev_ofnode(dev), "interconnects")) return NULL; ret = dev_read_phandle_with_args(dev, "interconnects", "#interconnect-cells", 0, index * 2, &src_args); if (ret) { dev_err(dev, "dev_read_phandle_with_args src failed: %d\n", ret); return ERR_PTR(ret); } ret = dev_read_phandle_with_args(dev, "interconnects", "#interconnect-cells", 0, index * 2 + 1, &dst_args); if (ret) { dev_err(dev, "dev_read_phandle_with_args dst failed: %d\n", ret); return ERR_PTR(ret); } src_node = of_icc_get_from_provider(dev, &src_args); if (IS_ERR(src_node)) { dev_err(dev, "error finding src node\n"); return ERR_CAST(src_node); } dst_node = of_icc_get_from_provider(dev, &dst_args); if (IS_ERR(dst_node)) { dev_err(dev, "error finding dst node\n"); return ERR_CAST(dst_node); } path = icc_path_find(dev, src_node, dst_node); if (IS_ERR(path)) dev_err(dev, "invalid path=%ld\n", PTR_ERR(path)); debug("(path=%p)\n", path); return path; } int icc_put(struct icc_path *path) { struct icc_node *node; size_t i; int ret; debug("(path=%p)\n", path); if (!path || IS_ERR(path)) return 0; ret = icc_set_bw(path, 0, 0); if (ret) { dev_err(path->dev, "failed to set bandwidth (%d)\n", ret); return ret; } for (i = 0; i < path->num_nodes; i++) { node = path->reqs[i].node; if (node->users) node->users--; if (!node->users) device_remove(node->dev, DM_REMOVE_NORMAL); hlist_del(&path->reqs[i].req_node); } kfree(path); return 0; } static int __icc_enable(struct icc_path *path, bool enable) { int i; if (!path) return 0; if (IS_ERR(path) || !path->num_nodes) return -EINVAL; for (i = 0; i < path->num_nodes; i++) path->reqs[i].enabled = enable; return icc_set_bw(path, path->reqs[0].avg_bw, path->reqs[0].peak_bw); } int icc_enable(struct icc_path *path) { debug("(path=%p)\n", path); return __icc_enable(path, true); } int icc_disable(struct icc_path *path) { debug("(path=%p)\n", path); return __icc_enable(path, false); } static int apply_constraints(struct icc_path *path) { struct icc_node *next, *prev = NULL; const struct interconnect_ops *ops; struct icc_provider *provider; struct udevice *p; int ret = -EINVAL; int i; debug("(path=%p)\n", path); for (i = 0; i < path->num_nodes; i++) { next = path->reqs[i].node; p = next->dev->parent; provider = dev_get_uclass_plat(p); /* both endpoints should be valid master-slave pairs */ if (!prev || (p != prev->dev->parent && !provider->inter_set)) { prev = next; continue; } debug("(path=%p,req=%d,node=%s,provider=%s)\n", path, i, next->dev->name, p->name); ops = device_get_ops(p); /* set the constraints */ if (ops->set) { ret = ops->set(prev, next); if (ret) goto out; } prev = next; } out: return ret; } /* * We want the path to honor all bandwidth requests, so the average and peak * bandwidth requirements from each consumer are aggregated at each node. * The aggregation is platform specific, so each platform can customize it by * implementing its own aggregate() function. */ static int aggregate_requests(struct icc_node *node) { const struct interconnect_ops *ops = device_get_ops(node->dev->parent); struct icc_req *r; u32 avg_bw, peak_bw; debug("(dev=%s)\n", node->dev->name); node->avg_bw = 0; node->peak_bw = 0; if (ops->pre_aggregate) ops->pre_aggregate(node); hlist_for_each_entry(r, &node->req_list, req_node) { if (r->enabled) { avg_bw = r->avg_bw; peak_bw = r->peak_bw; } else { avg_bw = 0; peak_bw = 0; } debug("(dev=%s,req=%s,avg=%d,peak=%d)\n", node->dev->name, r->node->dev->name, avg_bw, peak_bw); if (ops->aggregate) ops->aggregate(node, r->tag, avg_bw, peak_bw, &node->avg_bw, &node->peak_bw); } return 0; } int icc_set_bw(struct icc_path *path, u32 avg_bw, u32 peak_bw) { struct icc_node *node; u32 old_avg, old_peak; size_t i; int ret; debug("(path=%p,avg=%d,peak=%d)\n", path, avg_bw, peak_bw); if (!path) return 0; if (IS_ERR(path) || !path->num_nodes) return -EINVAL; old_avg = path->reqs[0].avg_bw; old_peak = path->reqs[0].peak_bw; for (i = 0; i < path->num_nodes; i++) { node = path->reqs[i].node; /* update the consumer request for this path */ path->reqs[i].avg_bw = avg_bw; path->reqs[i].peak_bw = peak_bw; /* aggregate requests for this node */ aggregate_requests(node); } ret = apply_constraints(path); if (ret) { dev_err(path->dev, "error applying constraints (%d)\n", ret); for (i = 0; i < path->num_nodes; i++) { node = path->reqs[i].node; path->reqs[i].avg_bw = old_avg; path->reqs[i].peak_bw = old_peak; aggregate_requests(node); } apply_constraints(path); } return ret; } /* Provider API */ static struct icc_path *icc_path_init(struct udevice *dev, struct icc_node *dst, ssize_t num_nodes) { struct icc_node *node = dst; struct icc_path *path; struct udevice *node_dev; int i, ret; debug("(dev=%s,node=%s)\n", dev->name, node->dev->name); path = kzalloc(sizeof(struct icc_path) + sizeof(struct icc_req) * num_nodes, GFP_KERNEL); if (!path) return ERR_PTR(-ENOMEM); path->dev = dev; path->num_nodes = num_nodes; for (i = num_nodes - 1; i >= 0; i--) { debug("(req[%d]=%s)\n", i, node->dev->name); hlist_add_head(&path->reqs[i].req_node, &node->req_list); path->reqs[i].node = node; path->reqs[i].enabled = true; /* Probe this node since used in an active path */ ret = uclass_get_device_tail(node->dev, 0, &node_dev); if (ret) return ERR_PTR(ret); node->users++; /* reference to previous node was saved during path traversal */ node = node->reverse; } return path; } static struct icc_path *icc_path_find(struct udevice *dev, struct icc_node *src, struct icc_node *dst) { struct icc_path *path = ERR_PTR(-EPROBE_DEFER); struct icc_node *n, *node = NULL; struct list_head traverse_list; struct list_head edge_list; struct list_head visited_list; size_t i, depth = 1; bool found = false; debug("(dev=%s,src=%s,dest=%p\n", dev->name, src->dev->name, dst->dev->name); INIT_LIST_HEAD(&traverse_list); INIT_LIST_HEAD(&edge_list); INIT_LIST_HEAD(&visited_list); list_add(&src->search_list, &traverse_list); src->reverse = NULL; do { list_for_each_entry_safe(node, n, &traverse_list, search_list) { if (node == dst) { found = true; list_splice_init(&edge_list, &visited_list); list_splice_init(&traverse_list, &visited_list); break; } for (i = 0; i < node->num_links; i++) { struct icc_node *tmp; tmp = icc_node_find(node->links[i]); if (!tmp) { dev_err(dev, "missing link to node id %lx\n", node->links[i]); path = ERR_PTR(-ENOENT); goto out; } if (tmp->is_traversed) continue; tmp->is_traversed = true; tmp->reverse = node; list_add_tail(&tmp->search_list, &edge_list); } } if (found) break; list_splice_init(&traverse_list, &visited_list); list_splice_init(&edge_list, &traverse_list); /* count the hops including the source */ depth++; } while (!list_empty(&traverse_list)); out: /* reset the traversed state */ list_for_each_entry_reverse(n, &visited_list, search_list) n->is_traversed = false; if (found) path = icc_path_init(dev, dst, depth); return path; } static struct icc_node *of_icc_get_from_provider(struct udevice *dev, const struct ofnode_phandle_args *args) { const struct interconnect_ops *ops; struct udevice *icc_dev; int ret; ret = uclass_get_device_by_ofnode(UCLASS_INTERCONNECT, args->node, &icc_dev); if (ret) { dev_err(dev, "uclass_get_device_by_ofnode failed: %d\n", ret); return ERR_PTR(ret); } ops = device_get_ops(icc_dev); return ops->of_xlate(icc_dev, args); } static struct icc_node *icc_node_find(const ulong id) { struct udevice *dev; for (uclass_find_first_device(UCLASS_ICC_NODE, &dev); dev; uclass_find_next_device(&dev)) { if (dev_get_driver_data(dev) == id) return dev_get_uclass_plat(dev); } return NULL; } static bool icc_node_busy(struct udevice *dev) { struct icc_node *node = dev_get_uclass_plat(dev); debug("(dev=%s,users=%d)\n", dev->name, node->users); return !!node->users; } struct icc_node *icc_node_create(struct udevice *dev, ulong id, const char *name) { struct udevice *node; struct driver *drv; int ret; drv = lists_driver_lookup_name("icc_node"); if (!drv) return ERR_PTR(-ENOENT); ret = device_bind_with_driver_data(dev, drv, strdup(name), id, ofnode_null(), &node); if (ret) return ERR_PTR(ret); device_set_name_alloced(node); return dev_get_uclass_plat(node); } int icc_link_create(struct icc_node *node, const ulong dst_id) { ulong *new; new = realloc(node->links, (node->num_links + 1) * sizeof(*node->links)); if (!new) return -ENOMEM; node->links = new; node->links[node->num_links++] = dst_id; return 0; } static int icc_node_bind(struct udevice *dev) { struct icc_node *node = dev_get_uclass_plat(dev); debug("(dev=%s)\n", dev->name); node->dev = dev; return 0; } static int icc_node_probe(struct udevice *dev) { struct icc_node *node = dev_get_uclass_plat(dev); debug("(dev=%s,parent=%p,id=%lx)\n", dev->name, dev->parent->name, dev_get_driver_data(dev)); node->avg_bw = 0; node->peak_bw = 0; return 0; } static int icc_node_remove(struct udevice *dev) { debug("(dev=%s,parent=%s,id=%lx)\n", dev->name, dev->parent->name, dev_get_driver_data(dev)); if (icc_node_busy(dev)) return -EBUSY; return 0; } static int icc_node_unbind(struct udevice *dev) { struct icc_node *node = dev_get_uclass_plat(dev); debug("(dev=%s,id=%lx)\n", dev->name, dev_get_driver_data(dev)); kfree(node->links); return 0; } UCLASS_DRIVER(interconnect) = { .id = UCLASS_INTERCONNECT, .name = "interconnect", .per_device_plat_auto = sizeof(struct icc_provider), }; U_BOOT_DRIVER(icc_node) = { .name = "icc_node", .id = UCLASS_ICC_NODE, .bind = icc_node_bind, .probe = icc_node_probe, .remove = icc_node_remove, .unbind = icc_node_unbind, }; UCLASS_DRIVER(icc_node) = { .id = UCLASS_ICC_NODE, .name = "icc_node", .per_device_plat_auto = sizeof(struct icc_node), };