Skip to content

Commit 06ebbe7

Browse files
visitorckwGeorgi Djakov
authored andcommitted
interconnect: Add kunit tests for core functionality
The interconnect framework currently lacks in-tree unit tests to verify the core logic in isolation. This makes it difficult to validate regression stability when modifying the provider/consumer APIs or aggregation logic. Introduce a kunit test suite that verifies the fundamental behavior of the subsystem. The tests cover: - Provider API (node creation, linking, topology construction). - Consumer API (path enabling/disabling, bandwidth requests). - Standard aggregation logic (accumulating bandwidth across links). - Bulk operations for setting bandwidth on multiple paths. The suite simulates a simple SoC topology with multiple masters and a shared bus to validate traffic aggregation behavior in a controlled software environment, without requiring specific hardware or Device Tree support. Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com> Link: https://lore.kernel.org/r/20260110184309.906735-1-visitorckw@gmail.com Signed-off-by: Georgi Djakov <djakov@kernel.org>
1 parent a3e2ea7 commit 06ebbe7

3 files changed

Lines changed: 340 additions & 0 deletions

File tree

drivers/interconnect/Kconfig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,18 @@ config INTERCONNECT_CLK
2222
help
2323
Support for wrapping clocks into the interconnect nodes.
2424

25+
config INTERCONNECT_KUNIT_TEST
26+
tristate "KUnit tests for Interconnect framework"
27+
depends on KUNIT
28+
default KUNIT_ALL_TESTS
29+
help
30+
This builds the KUnit test suite for the generic system interconnect
31+
framework.
32+
33+
The tests cover the core functionality of the interconnect subsystem,
34+
including provider/consumer APIs, topology management, and bandwidth
35+
aggregation logic.
36+
37+
If unsure, say N.
38+
2539
endif

drivers/interconnect/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ obj-$(CONFIG_INTERCONNECT_QCOM) += qcom/
1010
obj-$(CONFIG_INTERCONNECT_SAMSUNG) += samsung/
1111

1212
obj-$(CONFIG_INTERCONNECT_CLK) += icc-clk.o
13+
14+
obj-$(CONFIG_INTERCONNECT_KUNIT_TEST) += icc-kunit.o

drivers/interconnect/icc-kunit.c

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* KUnit tests for the Interconnect framework.
4+
*
5+
* Copyright (c) 2025 Kuan-Wei Chiu <visitorckw@gmail.com>
6+
*
7+
* This suite verifies the behavior of the interconnect core, including
8+
* topology construction, bandwidth aggregation, and path lifecycle.
9+
*/
10+
11+
#include <kunit/platform_device.h>
12+
#include <kunit/test.h>
13+
#include <linux/interconnect-provider.h>
14+
#include <linux/interconnect.h>
15+
#include <linux/list.h>
16+
#include <linux/module.h>
17+
#include <linux/overflow.h>
18+
#include <linux/platform_device.h>
19+
#include <linux/slab.h>
20+
21+
#include "internal.h"
22+
23+
enum {
24+
NODE_CPU,
25+
NODE_GPU,
26+
NODE_BUS,
27+
NODE_DDR,
28+
NODE_MAX
29+
};
30+
31+
struct test_node_data {
32+
int id;
33+
const char *name;
34+
int num_links;
35+
int links[2];
36+
};
37+
38+
/*
39+
* Static Topology:
40+
* CPU -\
41+
* -> BUS -> DDR
42+
* GPU -/
43+
*/
44+
static const struct test_node_data test_topology[] = {
45+
{ NODE_CPU, "cpu", 1, { NODE_BUS } },
46+
{ NODE_GPU, "gpu", 1, { NODE_BUS } },
47+
{ NODE_BUS, "bus", 1, { NODE_DDR } },
48+
{ NODE_DDR, "ddr", 0, { } },
49+
};
50+
51+
struct icc_test_priv {
52+
struct icc_provider provider;
53+
struct platform_device *pdev;
54+
struct icc_node *nodes[NODE_MAX];
55+
};
56+
57+
static struct icc_node *get_node(struct icc_test_priv *priv, int id)
58+
{
59+
int idx = id - NODE_CPU;
60+
61+
if (idx < 0 || idx >= ARRAY_SIZE(test_topology))
62+
return NULL;
63+
return priv->nodes[idx];
64+
}
65+
66+
static int icc_test_set(struct icc_node *src, struct icc_node *dst)
67+
{
68+
return 0;
69+
}
70+
71+
static int icc_test_aggregate(struct icc_node *node, u32 tag, u32 avg_bw,
72+
u32 peak_bw, u32 *agg_avg, u32 *agg_peak)
73+
{
74+
return icc_std_aggregate(node, tag, avg_bw, peak_bw, agg_avg, agg_peak);
75+
}
76+
77+
static struct icc_node *icc_test_xlate(const struct of_phandle_args *spec, void *data)
78+
{
79+
return NULL;
80+
}
81+
82+
static int icc_test_get_bw(struct icc_node *node, u32 *avg, u32 *peak)
83+
{
84+
*avg = 0;
85+
*peak = 0;
86+
87+
return 0;
88+
}
89+
90+
static int icc_test_init(struct kunit *test)
91+
{
92+
struct icc_test_priv *priv;
93+
struct icc_node *node;
94+
int i, j, ret;
95+
96+
priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
97+
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
98+
test->priv = priv;
99+
100+
priv->pdev = kunit_platform_device_alloc(test, "icc-test-dev", -1);
101+
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->pdev);
102+
KUNIT_ASSERT_EQ(test, kunit_platform_device_add(test, priv->pdev), 0);
103+
104+
priv->provider.set = icc_test_set;
105+
priv->provider.aggregate = icc_test_aggregate;
106+
priv->provider.xlate = icc_test_xlate;
107+
priv->provider.get_bw = icc_test_get_bw;
108+
priv->provider.dev = &priv->pdev->dev;
109+
priv->provider.data = priv;
110+
INIT_LIST_HEAD(&priv->provider.nodes);
111+
112+
ret = icc_provider_register(&priv->provider);
113+
KUNIT_ASSERT_EQ(test, ret, 0);
114+
115+
for (i = 0; i < ARRAY_SIZE(test_topology); i++) {
116+
const struct test_node_data *data = &test_topology[i];
117+
118+
node = icc_node_create(data->id);
119+
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
120+
121+
node->name = data->name;
122+
icc_node_add(node, &priv->provider);
123+
priv->nodes[i] = node;
124+
}
125+
126+
for (i = 0; i < ARRAY_SIZE(test_topology); i++) {
127+
const struct test_node_data *data = &test_topology[i];
128+
struct icc_node *src = get_node(priv, data->id);
129+
130+
for (j = 0; j < data->num_links; j++) {
131+
ret = icc_link_create(src, data->links[j]);
132+
KUNIT_ASSERT_EQ_MSG(test, ret, 0, "Failed to link %s->%d",
133+
src->name, data->links[j]);
134+
}
135+
}
136+
137+
icc_sync_state(&priv->pdev->dev);
138+
139+
return 0;
140+
}
141+
142+
static void icc_test_exit(struct kunit *test)
143+
{
144+
struct icc_test_priv *priv = test->priv;
145+
146+
icc_nodes_remove(&priv->provider);
147+
icc_provider_deregister(&priv->provider);
148+
}
149+
150+
/*
151+
* Helper to construct a mock path.
152+
*
153+
* Because we are bypassing icc_get(), we must manually link the requests
154+
* to the nodes' req_list so that icc_std_aggregate() can discover them.
155+
*/
156+
static struct icc_path *icc_test_create_path(struct kunit *test,
157+
struct icc_node **nodes, int num)
158+
{
159+
struct icc_path *path;
160+
int i;
161+
162+
path = kunit_kzalloc(test, struct_size(path, reqs, num), GFP_KERNEL);
163+
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, path);
164+
165+
path->num_nodes = num;
166+
for (i = 0; i < num; i++) {
167+
path->reqs[i].node = nodes[i];
168+
hlist_add_head(&path->reqs[i].req_node, &nodes[i]->req_list);
169+
}
170+
path->name = "mock-path";
171+
172+
return path;
173+
}
174+
175+
static void icc_test_destroy_path(struct kunit *test, struct icc_path *path)
176+
{
177+
int i;
178+
179+
for (i = 0; i < path->num_nodes; i++)
180+
hlist_del(&path->reqs[i].req_node);
181+
182+
kunit_kfree(test, path);
183+
}
184+
185+
static void icc_test_topology_integrity(struct kunit *test)
186+
{
187+
struct icc_test_priv *priv = test->priv;
188+
struct icc_node *cpu = get_node(priv, NODE_CPU);
189+
struct icc_node *bus = get_node(priv, NODE_BUS);
190+
191+
KUNIT_EXPECT_EQ(test, cpu->num_links, 1);
192+
KUNIT_EXPECT_PTR_EQ(test, cpu->links[0], bus);
193+
KUNIT_EXPECT_PTR_EQ(test, cpu->provider, &priv->provider);
194+
}
195+
196+
static void icc_test_set_bw(struct kunit *test)
197+
{
198+
struct icc_test_priv *priv = test->priv;
199+
struct icc_path *path;
200+
struct icc_node *path_nodes[3];
201+
int ret;
202+
203+
/* Path: CPU -> BUS -> DDR */
204+
path_nodes[0] = get_node(priv, NODE_CPU);
205+
path_nodes[1] = get_node(priv, NODE_BUS);
206+
path_nodes[2] = get_node(priv, NODE_DDR);
207+
208+
path = icc_test_create_path(test, path_nodes, 3);
209+
210+
ret = icc_enable(path);
211+
KUNIT_ASSERT_EQ(test, ret, 0);
212+
213+
ret = icc_set_bw(path, 1000, 2000);
214+
KUNIT_EXPECT_EQ(test, ret, 0);
215+
216+
KUNIT_EXPECT_EQ(test, path_nodes[0]->avg_bw, 1000);
217+
KUNIT_EXPECT_EQ(test, path_nodes[0]->peak_bw, 2000);
218+
KUNIT_EXPECT_EQ(test, path_nodes[1]->avg_bw, 1000);
219+
KUNIT_EXPECT_EQ(test, path_nodes[1]->peak_bw, 2000);
220+
221+
icc_set_tag(path, 0xABC);
222+
KUNIT_EXPECT_EQ(test, path->reqs[0].tag, 0xABC);
223+
224+
icc_disable(path);
225+
KUNIT_EXPECT_EQ(test, path_nodes[0]->avg_bw, 0);
226+
227+
icc_test_destroy_path(test, path);
228+
}
229+
230+
static void icc_test_aggregation(struct kunit *test)
231+
{
232+
struct icc_test_priv *priv = test->priv;
233+
struct icc_path *path_cpu, *path_gpu;
234+
struct icc_node *nodes_cpu[3], *nodes_gpu[2];
235+
struct icc_node *bus = get_node(priv, NODE_BUS);
236+
int ret;
237+
238+
nodes_cpu[0] = get_node(priv, NODE_CPU);
239+
nodes_cpu[1] = bus;
240+
nodes_cpu[2] = get_node(priv, NODE_DDR);
241+
path_cpu = icc_test_create_path(test, nodes_cpu, 3);
242+
243+
nodes_gpu[0] = get_node(priv, NODE_GPU);
244+
nodes_gpu[1] = bus;
245+
path_gpu = icc_test_create_path(test, nodes_gpu, 2);
246+
247+
icc_enable(path_cpu);
248+
icc_enable(path_gpu);
249+
250+
ret = icc_set_bw(path_cpu, 1000, 1000);
251+
KUNIT_EXPECT_EQ(test, ret, 0);
252+
KUNIT_EXPECT_EQ(test, bus->avg_bw, 1000);
253+
254+
ret = icc_set_bw(path_gpu, 2000, 2000);
255+
KUNIT_EXPECT_EQ(test, ret, 0);
256+
257+
/* Bus aggregates: CPU(1000) + GPU(2000) */
258+
KUNIT_EXPECT_EQ(test, bus->avg_bw, 3000);
259+
/* Peak aggregates: max(CPU, GPU) */
260+
KUNIT_EXPECT_EQ(test, bus->peak_bw, 2000);
261+
262+
icc_test_destroy_path(test, path_cpu);
263+
icc_test_destroy_path(test, path_gpu);
264+
}
265+
266+
static void icc_test_bulk_ops(struct kunit *test)
267+
{
268+
struct icc_test_priv *priv = test->priv;
269+
struct icc_node *nodes_cpu[3], *nodes_gpu[2];
270+
struct icc_bulk_data bulk[2];
271+
int ret;
272+
273+
nodes_cpu[0] = get_node(priv, NODE_CPU);
274+
nodes_cpu[1] = get_node(priv, NODE_BUS);
275+
nodes_cpu[2] = get_node(priv, NODE_DDR);
276+
277+
nodes_gpu[0] = get_node(priv, NODE_GPU);
278+
nodes_gpu[1] = get_node(priv, NODE_BUS);
279+
280+
bulk[0].path = icc_test_create_path(test, nodes_cpu, 3);
281+
bulk[0].avg_bw = 500;
282+
bulk[0].peak_bw = 500;
283+
284+
bulk[1].path = icc_test_create_path(test, nodes_gpu, 2);
285+
bulk[1].avg_bw = 600;
286+
bulk[1].peak_bw = 600;
287+
288+
ret = icc_bulk_set_bw(2, bulk);
289+
KUNIT_EXPECT_EQ(test, ret, 0);
290+
/* Paths disabled, bandwidth should be 0 */
291+
KUNIT_EXPECT_EQ(test, get_node(priv, NODE_BUS)->avg_bw, 0);
292+
293+
ret = icc_bulk_enable(2, bulk);
294+
KUNIT_EXPECT_EQ(test, ret, 0);
295+
/* Paths enabled, aggregation applies */
296+
KUNIT_EXPECT_EQ(test, get_node(priv, NODE_BUS)->avg_bw, 1100);
297+
298+
icc_bulk_disable(2, bulk);
299+
KUNIT_EXPECT_EQ(test, get_node(priv, NODE_BUS)->avg_bw, 0);
300+
301+
icc_test_destroy_path(test, bulk[0].path);
302+
icc_test_destroy_path(test, bulk[1].path);
303+
}
304+
305+
static struct kunit_case icc_test_cases[] = {
306+
KUNIT_CASE(icc_test_topology_integrity),
307+
KUNIT_CASE(icc_test_set_bw),
308+
KUNIT_CASE(icc_test_aggregation),
309+
KUNIT_CASE(icc_test_bulk_ops),
310+
{}
311+
};
312+
313+
static struct kunit_suite icc_test_suite = {
314+
.name = "interconnect",
315+
.init = icc_test_init,
316+
.exit = icc_test_exit,
317+
.test_cases = icc_test_cases,
318+
};
319+
320+
kunit_test_suite(icc_test_suite);
321+
322+
MODULE_AUTHOR("Kuan-Wei Chiu <visitorckw@gmail.com>");
323+
MODULE_DESCRIPTION("KUnit tests for the Interconnect framework");
324+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)