[RFC v1 1/3] interconnect: Add generic interconnect controller API

From: Georgi Djakov
Date: Mon May 15 2017 - 11:36:28 EST


This patch introduce a new API to get the requirement and configure the
interconnect buses across the entire chipset to fit with the current demand.

The API is using a consumer/provider-based model, where the providers are
the interconnect controllers and the consumers could be various drivers.
The consumers request interconnect resources (path) to an endpoint and set
the desired constraints on this data flow path. The provider(s) receive
requests from consumers and aggregate these requests for all master-slave
pairs on that path. Then the providers configure each participating in the
topology node according to the requested data flow path, physical links and
constraints. The topology could be complicated and multi-tiered and is SoC
specific.

Signed-off-by: Georgi Djakov <georgi.djakov@xxxxxxxxxx>
---
Documentation/interconnect/interconnect.txt | 65 ++++++
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/interconnect/Kconfig | 10 +
drivers/interconnect/Makefile | 1 +
drivers/interconnect/interconnect.c | 317 ++++++++++++++++++++++++++++
include/linux/interconnect-consumer.h | 84 ++++++++
include/linux/interconnect-provider.h | 110 ++++++++++
8 files changed, 590 insertions(+)
create mode 100644 Documentation/interconnect/interconnect.txt
create mode 100644 drivers/interconnect/Kconfig
create mode 100644 drivers/interconnect/Makefile
create mode 100644 drivers/interconnect/interconnect.c
create mode 100644 include/linux/interconnect-consumer.h
create mode 100644 include/linux/interconnect-provider.h

diff --git a/Documentation/interconnect/interconnect.txt b/Documentation/interconnect/interconnect.txt
new file mode 100644
index 000000000000..f761a2fb553c
--- /dev/null
+++ b/Documentation/interconnect/interconnect.txt
@@ -0,0 +1,65 @@
+GENERIC SYSTEM INTERCONNECT CONTROLLER SUBSYSTEM
+===============================================
+
+1. Introduction
+---------------
+This framework is designed to provide a standard kernel interface to control
+the settings of the interconnects on a SoC. These settings can be throughput,
+latency and priority between multiple interconnected devices. This can be
+controlled dynamically in order to save power or provide maximum performance.
+
+The interconnect controller is a hardware with configurable parameters, which
+can be set on a data path according to the requests received from various
+drivers. An example of interconnect controllers are the interconnects between
+various components on chipsets. There can be multiple interconnects on a SoC
+that can be multi-tiered.
+
+Below is a simplified diagram of a real-world SoC topology. The interconnect
+providers are the memory front end and the NoCs.
+
++----------------+ +----------------+
+| HW Accelerator |--->| M NoC |<---------------+
++----------------+ +----------------+ |
+ | | +------------+
+ +-------------+ V +------+ | |
+ | +--------+ | PCIe | | |
+ | | Slaves | +------+ | |
+ | +--------+ | | C NoC |
+ V V | |
++------------------+ +------------------------+ | | +-----+
+| |-->| |-->| |-->| CPU |
+| |-->| |<--| | +-----+
+| Memory | | S NoC | +------------+
+| |<--| |---------+ |
+| |<--| |<------+ | | +--------+
++------------------+ +------------------------+ | | +-->| Slaves |
+ ^ ^ ^ ^ | | +--------+
+ | | | | | V
++-----+ | +-----+ +-----+ +---------+ +----------------+ +--------+
+| CPU | | | GPU | | DSP | | Masters |-->| P NoC |-->| Slaves |
++-----+ | +-----+ +-----+ +---------+ +----------------+ +--------+
+ |
+ +-------+
+ | Modem |
+ +-------+
+
+2. Interconnect providers
+------------------------
+Interconnect provider is an entity that implements methods to initialize and
+configure a interconnect controller hardware.
+
+An interconnect controller should register with the interconnect provider core
+with interconnect_add_provider().
+
+A previously registered interconnect provider is unregistered with
+interconnect_del_provider().
+
+3. Interconnect consumers
+------------------------
+Interconnect consumers are the entities which make use of the data paths exposed
+by the providers. The consumers send requests to providers requesting various
+throughput, latency and priority. Usually the consumers are device drivers, that
+send request based on their needs.
+
+The interconnect framework consumer API functions are documented in
+include/linux/interconnect-consumer.h
diff --git a/drivers/Kconfig b/drivers/Kconfig
index d2ac339de85f..6e4d80e98f5c 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -198,4 +198,6 @@ source "drivers/hwtracing/intel_th/Kconfig"

source "drivers/fpga/Kconfig"

+source "drivers/interconnect/Kconfig"
+
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 795d0ca714bf..d5b4733f3875 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -172,3 +172,4 @@ obj-$(CONFIG_STM) += hwtracing/stm/
obj-$(CONFIG_ANDROID) += android/
obj-$(CONFIG_NVMEM) += nvmem/
obj-$(CONFIG_FPGA) += fpga/
+obj-$(CONFIG_INTERCONNECT) += interconnect/
diff --git a/drivers/interconnect/Kconfig b/drivers/interconnect/Kconfig
new file mode 100644
index 000000000000..1e50e951cdc1
--- /dev/null
+++ b/drivers/interconnect/Kconfig
@@ -0,0 +1,10 @@
+menuconfig INTERCONNECT
+ tristate "On-Chip Interconnect management support"
+ help
+ Support for management of the on-chip interconnects.
+
+ This framework is designed to provide a generic interface for
+ managing the interconnects in a SoC.
+
+ If unsure, say no.
+
diff --git a/drivers/interconnect/Makefile b/drivers/interconnect/Makefile
new file mode 100644
index 000000000000..d9da6a6c3560
--- /dev/null
+++ b/drivers/interconnect/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_INTERCONNECT) += interconnect.o
diff --git a/drivers/interconnect/interconnect.c b/drivers/interconnect/interconnect.c
new file mode 100644
index 000000000000..633ee157226f
--- /dev/null
+++ b/drivers/interconnect/interconnect.c
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) 2017, Linaro Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/interconnect-consumer.h>
+#include <linux/interconnect-provider.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+static DEFINE_MUTEX(interconnect_provider_list_mutex);
+static LIST_HEAD(interconnect_provider_list);
+
+/**
+ * struct interconnect_path - interconnect path structure
+ *
+ * @nodes: array of the nodes in this path
+ * @num_nodes: number of hops (nodes)
+ */
+struct interconnect_path {
+ struct interconnect_node **nodes;
+ size_t num_nodes;
+};
+
+static struct interconnect_node *node_find(const char *dev_id, int con_id)
+{
+ struct icp *icp;
+ struct interconnect_node *node = ERR_PTR(-EPROBE_DEFER);
+ int match, best = 0;
+
+ mutex_lock(&interconnect_provider_list_mutex);
+
+ list_for_each_entry(icp, &interconnect_provider_list, icp_list) {
+ struct interconnect_node *n;
+
+ match = 0;
+
+ list_for_each_entry(n, &icp->nodes, icn_list) {
+ if (n->dev_id) {
+ if (!dev_id || strncmp(n->dev_id, dev_id,
+ strlen(dev_id)))
+ continue;
+ match += 2;
+ }
+ if (n->con_id) {
+ if (!con_id || n->con_id != con_id)
+ continue;
+ match += 1;
+ }
+
+ if (match > best) {
+ node = n;
+ if (match == 3)
+ goto out;
+
+ best = match;
+ }
+ }
+ }
+
+out:
+ mutex_unlock(&interconnect_provider_list_mutex);
+
+ return node;
+}
+
+static struct interconnect_path *path_find(struct interconnect_node *src,
+ struct interconnect_node *dst)
+{
+ struct list_head edge_list;
+ struct list_head traverse_list;
+ struct list_head tmp_list;
+ struct interconnect_path *path = ERR_PTR(-EPROBE_DEFER);
+ struct interconnect_node *node = NULL;
+ size_t i, number = 1;
+ bool found = false;
+
+ INIT_LIST_HEAD(&traverse_list);
+ INIT_LIST_HEAD(&edge_list);
+ INIT_LIST_HEAD(&tmp_list);
+
+ list_add_tail(&src->search_list, &traverse_list);
+
+ do {
+ list_for_each_entry(node, &traverse_list, search_list) {
+ if (node == dst) {
+ found = true;
+ list_add(&node->search_list, &tmp_list);
+ break;
+ }
+ for (i = 0; i < node->num_links; i++) {
+ struct interconnect_node *tmp = node->links[i];
+
+ if (!tmp) {
+ WARN_ON(1);
+ return ERR_PTR(-ENOENT);
+ }
+
+ 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, &tmp_list);
+ list_splice_init(&edge_list, &traverse_list);
+
+ /* count the number of nodes */
+ number++;
+
+ } while (!list_empty(&traverse_list));
+
+ /* reset the traversed state */
+ list_for_each_entry(node, &tmp_list, search_list) {
+ node->is_traversed = false;
+ }
+
+ if (found) {
+ path = kzalloc(sizeof(*path), GFP_KERNEL);
+ if (!path)
+ return ERR_PTR(-ENOMEM);
+
+ path->nodes = kcalloc(number, sizeof(*node), GFP_KERNEL);
+ if (!path->nodes) {
+ kfree(path);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ path->num_nodes = number;
+
+ /* start from the destination and go back to source */
+ node = dst;
+
+ for (i = 0; i < number; i++) {
+ path->nodes[i] = node;
+ node = node->reverse;
+ }
+ }
+
+ return path;
+}
+
+int interconnect_set(struct interconnect_path *path, u32 bandwidth)
+{
+ struct interconnect_node *next, *prev = NULL;
+ size_t i;
+
+ for (i = 0; i < path->num_nodes; i++) {
+ struct icn_qos *req;
+ u32 agg_bw = 0;
+
+ next = path->nodes[i];
+
+ /* aggregate requests */
+ hlist_for_each_entry(req, &next->qos_list, node) {
+ if (req->path == path) {
+ /* update the bandwidth for the path */
+ req->bandwidth = bandwidth;
+ }
+
+ agg_bw += req->bandwidth;
+ }
+
+ if (next->icp->ops->set)
+ next->icp->ops->set(prev, next, agg_bw);
+
+ prev = next;
+
+ /* is this the last node? */
+ if (i == path->num_nodes - 1) {
+ if (next->icp->ops->set)
+ next->icp->ops->set(next, NULL, agg_bw);
+ }
+ }
+
+ return 0;
+}
+
+struct interconnect_path *interconnect_get(const char *sdev, const int sid,
+ const char *ddev, const int did)
+{
+ struct interconnect_node *src, *dst, *node;
+ struct interconnect_path *path;
+ size_t i;
+
+ src = node_find(sdev, sid);
+ if (IS_ERR(src))
+ return ERR_CAST(src);
+
+ dst = node_find(ddev, did);
+ if (IS_ERR(dst))
+ return ERR_CAST(dst);
+
+ /* TODO: cache the path */
+ path = path_find(src, dst);
+ if (IS_ERR(path)) {
+ pr_err("error finding path between %p and %p (%ld)\n",
+ src, dst, PTR_ERR(path));
+ return path;
+ }
+
+ for (i = 0; i < path->num_nodes; i++) {
+ struct icn_qos *req;
+
+ node = path->nodes[i];
+
+ pr_debug("%s: i=%lu node=%p\n", __func__, i, node);
+
+ /*
+ * Create icn_qos for each separate link between the nodes.
+ * They may have different constraints and may belong to
+ * different interconnect providers.
+ */
+ req = kzalloc(sizeof(*req), GFP_KERNEL);
+ if (!req)
+ return ERR_PTR(-ENOMEM);
+
+ req->path = path;
+ req->bandwidth = 0;
+
+ hlist_add_head(&req->node, &node->qos_list);
+
+ node->icp->users++;
+ }
+
+ return path;
+}
+EXPORT_SYMBOL_GPL(interconnect_get);
+
+void interconnect_put(struct interconnect_path *path)
+{
+ struct interconnect_node *node;
+ struct icn_qos *req;
+ struct hlist_node *tmp;
+ size_t i;
+ int ret;
+
+ if (IS_ERR(path))
+ return;
+
+ for (i = 0; i < path->num_nodes; i++) {
+ node = path->nodes[i];
+
+ hlist_for_each_entry_safe(req, tmp, &node->qos_list, node) {
+ if (req->path == path) {
+ /*
+ * Remove the constraints from the path,
+ * update the nodes and free the memory
+ */
+ ret = interconnect_set(path, 0);
+ if (ret)
+ pr_err("%s error %d\n", __func__, ret);
+
+ hlist_del(&req->node);
+ kfree(req);
+ }
+ }
+
+ node->icp->users--;
+ }
+
+ kfree(path);
+}
+EXPORT_SYMBOL_GPL(interconnect_put);
+
+int interconnect_add_provider(struct icp *icp)
+{
+ struct interconnect_node *node;
+
+ WARN(!icp->ops->set, "%s: .set is not implemented\n", __func__);
+
+ mutex_lock(&interconnect_provider_list_mutex);
+ list_add(&icp->icp_list, &interconnect_provider_list);
+ mutex_unlock(&interconnect_provider_list_mutex);
+
+ list_for_each_entry(node, &icp->nodes, icn_list) {
+ INIT_HLIST_HEAD(&node->qos_list);
+ }
+
+ dev_info(icp->dev, "interconnect provider is added to topology\n");
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(interconnect_add_provider);
+
+int interconnect_del_provider(struct icp *icp)
+{
+ if (icp->users)
+ return -EBUSY;
+
+ mutex_lock(&interconnect_provider_list_mutex);
+ list_del(&icp->icp_list);
+ mutex_unlock(&interconnect_provider_list_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(interconnect_del_provider);
+
+MODULE_AUTHOR("Georgi Djakov <georgi.djakov@xxxxxxxxxx");
+MODULE_DESCRIPTION("Interconnect Driver Core");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/interconnect-consumer.h b/include/linux/interconnect-consumer.h
new file mode 100644
index 000000000000..e187efa6c20a
--- /dev/null
+++ b/include/linux/interconnect-consumer.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2017, Linaro Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _LINUX_INTERCONNECT_CONSUMER_H
+#define _LINUX_INTERCONNECT_CONSUMER_H
+
+struct interconnect_node;
+struct interconnect_path;
+
+#if IS_ENABLED(CONFIG_INTERCONNECT)
+
+/**
+ * interconnect_get() - get an interconnect path between two endpoits
+ *
+ * @sdev: source device identifier
+ * @sid: source device port identifier
+ * @ddev: destination device identifier
+ * @did: destination device port identifier
+ *
+ * This function will search for a path between the source device (caller)
+ * and a destination endpoint. It returns an interconnect_path handle on
+ * success. Use interconnect_put() to release constraints when the they
+ * are not needed anymore.
+ *
+ * This function returns a handle on success, or ERR_PTR() otherwise.
+ */
+struct interconnect_path *interconnect_get(const char *sdev, const int sid,
+ const char *ddev, const int did);
+
+/**
+ * interconnect_put() - release the reference to the interconnect path
+ *
+ * @path: interconnect path
+ *
+ * Use this function to release the path and free the memory when setting
+ * constraints on the path is no longer needed.
+ */
+void interconnect_put(struct interconnect_path *path);
+
+/**
+ * interconnect_set() - set constraints on a path between two endpoints
+ * @path: reference to the path returned by interconnect_get()
+ * @bandwidth: the requested bandwidth in kpbs between the path endpoints
+ *
+ * This function sends a request for bandwidth between the two endpoints,
+ * (path). It aggragates the requests for constraints and updates each node
+ * accordingly.
+ *
+ * Returns 0 on success, or an approproate error code otherwise.
+ */
+int interconnect_set(struct interconnect_path *path, u32 bandwidth);
+
+#else
+
+inline struct interconnect_path *interconnect_get(const char *sdev,
+ const int sid,
+ const char *ddev,
+ const int did)
+{
+ return ERR_PTR(-ENOSYS);
+}
+
+inline void interconnect_put(struct interconnect_path *path)
+{
+}
+
+inline int interconnect_set(struct interconnect_path *path, u32 bandwidth)
+{
+ return -ENOSYS;
+}
+
+#endif /* CONFIG_INTERCONNECT */
+
+#endif /* _LINUX_INTERCONNECT_CONSUMER_H */
diff --git a/include/linux/interconnect-provider.h b/include/linux/interconnect-provider.h
new file mode 100644
index 000000000000..733d5c3e00b7
--- /dev/null
+++ b/include/linux/interconnect-provider.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2017, Linaro Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _LINUX_INTERCONNECT_PROVIDER_H
+#define _LINUX_INTERCONNECT_PROVIDER_H
+
+#include <linux/interconnect-consumer.h>
+
+/**
+ * struct icp_ops - platform specific callback operations for interconnect
+ * providers that will be called from drivers
+ *
+ * @set: set constraints on interconnect
+ */
+struct icp_ops {
+ int (*set)(struct interconnect_node *src, struct interconnect_node *dst, u32 bandwidth);
+};
+
+/**
+ * struct icp - interconnect provider (controller) entity that might
+ * provide multiple interconnect controls
+ *
+ * @icp_list: list of the registered interconnect providers
+ * @nodes: internal list of the interconnect provider nodes
+ * @ops: pointer to device specific struct icp_ops
+ * @dev: the device this interconnect provider belongs to
+ * @users: count of active users
+ * @data: pointer to private data
+ */
+struct icp {
+ struct list_head icp_list;
+ struct list_head nodes;
+ const struct icp_ops *ops;
+ struct device *dev;
+ int users;
+ void *data;
+};
+
+/**
+ * struct interconnect_node - entity that is part of the interconnect topology
+ *
+ * @links: links to other interconnect nodes
+ * @num_links: number of links to other interconnect nodes
+ * @icp: points to the interconnect provider of this node
+ * @icn_list: list of interconnect nodes
+ * @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
+ * @qos_list: a list of QoS constraints
+ * @dev_id: device id
+ * @con_id: connection id
+ */
+struct interconnect_node {
+ struct interconnect_node **links;
+ size_t num_links;
+
+ struct icp *icp;
+ struct list_head icn_list;
+ struct list_head search_list;
+ struct interconnect_node *reverse;
+ bool is_traversed;
+ struct hlist_head qos_list;
+
+ const char *dev_id;
+ int con_id;
+};
+
+/**
+ * struct icn_qos - constraints that are attached to each node
+ *
+ * @node: linked list node
+ * @path: the interconnect path which is using this constraint
+ * @bandwidth: an integer describing the bandwidth in kbps
+ */
+struct icn_qos {
+ struct hlist_node node;
+ struct interconnect_path *path;
+ u32 bandwidth;
+};
+
+#if IS_ENABLED(CONFIG_INTERCONNECT)
+
+int interconnect_add_provider(struct icp *icp);
+int interconnect_del_provider(struct icp *icp);
+
+#else
+
+static inline int interconnect_add_provider(struct icp *icp)
+{
+ return -ENOSYS;
+}
+
+static inline int interconnect_del_provider(struct icp *icp)
+{
+ return -ENOSYS;
+}
+
+#endif /* CONFIG_INTERCONNECT */
+
+#endif /* _LINUX_INTERCONNECT_PROVIDER_H */