[PATCH v3 3/4] irqchip/ast2700-intc: Add KUnit tests for route resolution
From: Ryan Chen <ryan_chen@aspeedtech.com>
Date: 2026-03-26 06:20:11
Also in:
linux-aspeed, linux-devicetree, linux-riscv, lkml
Subsystem:
irqchip drivers, the rest · Maintainers:
Thomas Gleixner, Linus Torvalds
Add a KUnit suite for aspeed_intc0_resolve_route(). Cover invalid arguments, invalid domain/range data, connected and disconnected mappings, and malformed upstream range cases. Signed-off-by: Ryan Chen <ryan_chen@aspeedtech.com> --- Changes in v2: - add line break before include "irq-ast2700.h" - remove pointless newline. - rename arm_gicv3_fwnode_read_string_array to gicv3_fwnode_read_string_array - add .kunitconfig file --- drivers/irqchip/.kunitconfig | 5 + drivers/irqchip/Kconfig | 11 + drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-ast2700-intc0-test.c | 473 +++++++++++++++++++++++++++++++ 4 files changed, 490 insertions(+)
diff --git a/drivers/irqchip/.kunitconfig b/drivers/irqchip/.kunitconfig
new file mode 100644
index 000000000000..00a12703f635
--- /dev/null
+++ b/drivers/irqchip/.kunitconfig@@ -0,0 +1,5 @@ +CONFIG_KUNIT=y +CONFIG_OF=y +CONFIG_COMPILE_TEST=y +CONFIG_ASPEED_AST2700_INTC=y +CONFIG_ASPEED_AST2700_INTC_TEST=y
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 0156fee89b2c..143af3f30a4b 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig@@ -122,6 +122,17 @@ config ASPEED_AST2700_INTC If unsure, say N. +config ASPEED_AST2700_INTC_TEST + bool "Tests for the ASPEED AST2700 Interrupt Controller" + depends on ASPEED_AST2700_INTC && KUNIT=y + default KUNIT_ALL_TESTS + help + Enable KUnit tests for AST2700 INTC route resolution. + The tests exercise error handling and route selection paths. + This option is intended for test builds. + + If unsure, say N. + config ATMEL_AIC_IRQ bool select GENERIC_IRQ_CHIP
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 62790663f982..ac04a4b97797 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile@@ -90,6 +90,7 @@ obj-$(CONFIG_MVEBU_SEI) += irq-mvebu-sei.o obj-$(CONFIG_LS_EXTIRQ) += irq-ls-extirq.o obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o obj-$(CONFIG_ASPEED_AST2700_INTC) += irq-ast2700.o irq-ast2700-intc0.o irq-ast2700-intc1.o +obj-$(CONFIG_ASPEED_AST2700_INTC_TEST) += irq-ast2700-intc0-test.o obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o irq-aspeed-scu-ic.o obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-intc.o obj-$(CONFIG_STM32MP_EXTI) += irq-stm32mp-exti.o
diff --git a/drivers/irqchip/irq-ast2700-intc0-test.c b/drivers/irqchip/irq-ast2700-intc0-test.c
new file mode 100644
index 000000000000..2aa00df72c04
--- /dev/null
+++ b/drivers/irqchip/irq-ast2700-intc0-test.c@@ -0,0 +1,473 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 Code Construct + */ +#include <kunit/test.h> + +#include "irq-ast2700.h" + +static void aspeed_intc0_resolve_route_bad_args(struct kunit *test) +{ + static const struct aspeed_intc_interrupt_range c1ranges[] = { 0 }; + static const u32 c1outs[] = { 0 }; + struct aspeed_intc_interrupt_range resolved; + const struct irq_domain c0domain = { 0 }; + int rc; + + rc = aspeed_intc0_resolve_route(NULL, 0, c1outs, 0, c1ranges, NULL); + KUNIT_EXPECT_EQ(test, rc, -EINVAL); + + rc = aspeed_intc0_resolve_route(&c0domain, 0, c1outs, + ARRAY_SIZE(c1ranges), c1ranges, + &resolved); + KUNIT_EXPECT_EQ(test, rc, -ENOENT); + + rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs, + 0, c1ranges, &resolved); + KUNIT_EXPECT_EQ(test, rc, -ENOENT); +} + +static int gicv3_fwnode_read_string_array(const struct fwnode_handle *fwnode, + const char *propname, const char **val, size_t nval) +{ + if (!propname) + return -EINVAL; + + if (!val) + return 1; + + if (WARN_ON(nval != 1)) + return -EOVERFLOW; + + *val = "arm,gic-v3"; + return 1; +} + +static const struct fwnode_operations arm_gicv3_fwnode_ops = { + .property_read_string_array = gicv3_fwnode_read_string_array, +}; + +static void aspeed_intc_resolve_route_invalid_c0domain(struct kunit *test) +{ + struct device_node intc0_node = { + .fwnode = { .ops = &arm_gicv3_fwnode_ops }, + }; + const struct irq_domain c0domain = { .fwnode = &intc0_node.fwnode }; + static const struct aspeed_intc_interrupt_range c1ranges[] = { 0 }; + static const u32 c1outs[] = { 0 }; + struct aspeed_intc_interrupt_range resolved; + int rc; + + rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs, + ARRAY_SIZE(c1ranges), c1ranges, + &resolved); + KUNIT_EXPECT_NE(test, rc, 0); +} + +static int +aspeed_intc0_fwnode_read_string_array(const struct fwnode_handle *fwnode_handle, + const char *propname, const char **val, + size_t nval) +{ + if (!propname) + return -EINVAL; + + if (!val) + return 1; + + if (WARN_ON(nval != 1)) + return -EOVERFLOW; + + *val = "aspeed,ast2700-intc0"; + return nval; +} + +static const struct fwnode_operations intc0_fwnode_ops = { + .property_read_string_array = aspeed_intc0_fwnode_read_string_array, +}; + +static void +aspeed_intc0_resolve_route_c1i1o1c0i1o1_connected(struct kunit *test) +{ + struct device_node intc0_node = { + .fwnode = { .ops = &intc0_fwnode_ops }, + }; + struct aspeed_intc_interrupt_range c1ranges[] = { + { + .start = 0, + .count = 1, + .upstream = { + .fwnode = &intc0_node.fwnode, + .param_count = 1, + .param = { 128 } + } + } + }; + static const u32 c1outs[] = { 0 }; + struct aspeed_intc_interrupt_range resolved; + struct aspeed_intc_interrupt_range intc0_ranges[] = { + { + .start = 128, + .count = 1, + .upstream = { + .fwnode = NULL, + .param_count = 0, + .param = { 0 }, + } + } + }; + struct aspeed_intc0 intc0 = { + .ranges = { .ranges = intc0_ranges, .nranges = ARRAY_SIZE(intc0_ranges), } + }; + const struct irq_domain c0domain = { + .host_data = &intc0, + .fwnode = &intc0_node.fwnode + }; + int rc; + + rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs, + ARRAY_SIZE(c1ranges), c1ranges, + &resolved); + KUNIT_EXPECT_EQ(test, rc, 0); + KUNIT_EXPECT_EQ(test, resolved.start, 0); + KUNIT_EXPECT_EQ(test, resolved.count, 1); + KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 128); +} + +static void +aspeed_intc0_resolve_route_c1i1o1c0i1o1_disconnected(struct kunit *test) +{ + struct device_node intc0_node = { + .fwnode = { .ops = &intc0_fwnode_ops }, + }; + struct aspeed_intc_interrupt_range c1ranges[] = { + { + .start = 0, + .count = 1, + .upstream = { + .fwnode = &intc0_node.fwnode, + .param_count = 1, + .param = { 128 } + } + } + }; + static const u32 c1outs[] = { 0 }; + struct aspeed_intc_interrupt_range resolved; + struct aspeed_intc_interrupt_range intc0_ranges[] = { + { + .start = 129, + .count = 1, + .upstream = { + .fwnode = NULL, + .param_count = 0, + .param = { 0 }, + } + } + }; + struct aspeed_intc0 intc0 = { + .ranges = { + .ranges = intc0_ranges, + .nranges = ARRAY_SIZE(intc0_ranges), + } + }; + const struct irq_domain c0domain = { + .host_data = &intc0, + .fwnode = &intc0_node.fwnode + }; + int rc; + + rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs, + ARRAY_SIZE(c1ranges), c1ranges, + &resolved); + KUNIT_EXPECT_NE(test, rc, 0); +} + +static void aspeed_intc0_resolve_route_c1i1o1mc0i1o1(struct kunit *test) +{ + struct device_node intc0_node = { + .fwnode = { .ops = &intc0_fwnode_ops }, + }; + struct aspeed_intc_interrupt_range c1ranges[] = { + { + .start = 0, + .count = 1, + .upstream = { + .fwnode = &intc0_node.fwnode, + .param_count = 1, + .param = { 480 } + } + } + }; + static const u32 c1outs[] = { 0 }; + struct aspeed_intc_interrupt_range resolved; + struct aspeed_intc_interrupt_range intc0_ranges[] = { + { + .start = 192, + .count = 1, + .upstream = { + .fwnode = NULL, + .param_count = 0, + .param = { 0 }, + } + } + }; + struct aspeed_intc0 intc0 = { + .ranges = { + .ranges = intc0_ranges, + .nranges = ARRAY_SIZE(intc0_ranges), + } + }; + const struct irq_domain c0domain = { + .host_data = &intc0, + .fwnode = &intc0_node.fwnode + }; + int rc; + + rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs, + ARRAY_SIZE(c1ranges), c1ranges, + &resolved); + KUNIT_EXPECT_EQ(test, rc, 0); + KUNIT_EXPECT_EQ(test, resolved.start, 0); + KUNIT_EXPECT_EQ(test, resolved.count, 1); + KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 480); +} + +static void aspeed_intc0_resolve_route_c1i2o2mc0i1o1(struct kunit *test) +{ + struct device_node intc0_node = { + .fwnode = { .ops = &intc0_fwnode_ops }, + }; + struct aspeed_intc_interrupt_range c1ranges[] = { + { + .start = 0, + .count = 1, + .upstream = { + .fwnode = &intc0_node.fwnode, + .param_count = 1, + .param = { 480 } + } + }, + { + .start = 1, + .count = 1, + .upstream = { + .fwnode = &intc0_node.fwnode, + .param_count = 1, + .param = { 510 } + } + } + }; + static const u32 c1outs[] = { 1 }; + struct aspeed_intc_interrupt_range resolved; + struct aspeed_intc_interrupt_range intc0_ranges[] = { + { + .start = 208, + .count = 1, + .upstream = { + .fwnode = NULL, + .param_count = 0, + .param = { 0 }, + } + } + }; + struct aspeed_intc0 intc0 = { + .ranges = { + .ranges = intc0_ranges, + .nranges = ARRAY_SIZE(intc0_ranges), + } + }; + const struct irq_domain c0domain = { + .host_data = &intc0, + .fwnode = &intc0_node.fwnode + }; + int rc; + + rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs, + ARRAY_SIZE(c1ranges), c1ranges, + &resolved); + KUNIT_EXPECT_EQ(test, rc, 0); + KUNIT_EXPECT_EQ(test, resolved.start, 1); + KUNIT_EXPECT_EQ(test, resolved.count, 1); + KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 510); +} + +static void aspeed_intc0_resolve_route_c1i1o1mc0i2o1(struct kunit *test) +{ + struct device_node intc0_node = { + .fwnode = { .ops = &intc0_fwnode_ops }, + }; + struct aspeed_intc_interrupt_range c1ranges[] = { + { + .start = 0, + .count = 1, + .upstream = { + .fwnode = &intc0_node.fwnode, + .param_count = 1, + .param = { 510 } + } + }, + }; + static const u32 c1outs[] = { 0 }; + struct aspeed_intc_interrupt_range resolved; + struct aspeed_intc_interrupt_range intc0_ranges[] = { + { + .start = 192, + .count = 1, + .upstream = { + .fwnode = NULL, + .param_count = 0, + .param = {0}, + } + }, + { + .start = 208, + .count = 1, + .upstream = { + .fwnode = NULL, + .param_count = 0, + .param = {0}, + } + } + }; + struct aspeed_intc0 intc0 = { + .ranges = { + .ranges = intc0_ranges, + .nranges = ARRAY_SIZE(intc0_ranges), + } + }; + const struct irq_domain c0domain = { + .host_data = &intc0, + .fwnode = &intc0_node.fwnode + }; + int rc; + + rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs, + ARRAY_SIZE(c1ranges), c1ranges, + &resolved); + KUNIT_EXPECT_EQ(test, rc, 0); + KUNIT_EXPECT_EQ(test, resolved.start, 0); + KUNIT_EXPECT_EQ(test, resolved.count, 1); + KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 510); +} + +static void aspeed_intc0_resolve_route_c1i1o2mc0i1o1_invalid(struct kunit *test) +{ + struct device_node intc0_node = { + .fwnode = { .ops = &intc0_fwnode_ops }, + }; + struct aspeed_intc_interrupt_range c1ranges[] = { + { + .start = 0, + .count = 1, + .upstream = { + .fwnode = &intc0_node.fwnode, + .param_count = 1, + .param = { 480 } + } + } + }; + static const u32 c1outs[] = { + AST2700_INTC_INVALID_ROUTE, 0 + }; + struct aspeed_intc_interrupt_range resolved; + struct aspeed_intc_interrupt_range intc0_ranges[] = { + { + .start = 192, + .count = 1, + .upstream = { + .fwnode = NULL, + .param_count = 0, + .param = { 0 }, + } + } + }; + struct aspeed_intc0 intc0 = { + .ranges = { + .ranges = intc0_ranges, + .nranges = ARRAY_SIZE(intc0_ranges), + } + }; + const struct irq_domain c0domain = { + .host_data = &intc0, + .fwnode = &intc0_node.fwnode + }; + int rc; + + rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs, + ARRAY_SIZE(c1ranges), c1ranges, + &resolved); + KUNIT_EXPECT_EQ(test, rc, 1); + KUNIT_EXPECT_EQ(test, resolved.start, 0); + KUNIT_EXPECT_EQ(test, resolved.count, 1); + KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 480); +} + +static void +aspeed_intc0_resolve_route_c1i1o1mc0i1o1_bad_range_upstream(struct kunit *test) +{ + struct device_node intc0_node = { + .fwnode = { .ops = &intc0_fwnode_ops }, + }; + struct aspeed_intc_interrupt_range c1ranges[] = { + { + .start = 0, + .count = 1, + .upstream = { + .fwnode = &intc0_node.fwnode, + .param_count = 0, + .param = { 0 } + } + } + }; + static const u32 c1outs[] = { 0 }; + struct aspeed_intc_interrupt_range resolved; + struct aspeed_intc_interrupt_range intc0_ranges[] = { + { + .start = 0, + .count = 0, + .upstream = { + .fwnode = NULL, + .param_count = 0, + .param = { 0 }, + } + } + }; + struct aspeed_intc0 intc0 = { + .ranges = { + .ranges = intc0_ranges, + .nranges = ARRAY_SIZE(intc0_ranges), + } + }; + const struct irq_domain c0domain = { + .host_data = &intc0, + .fwnode = &intc0_node.fwnode + }; + int rc; + + rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs, + ARRAY_SIZE(c1ranges), c1ranges, + &resolved); + KUNIT_EXPECT_NE(test, rc, 0); +} + +static struct kunit_case ast2700_intc0_test_cases[] = { + KUNIT_CASE(aspeed_intc0_resolve_route_bad_args), + KUNIT_CASE(aspeed_intc_resolve_route_invalid_c0domain), + KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1c0i1o1_connected), + KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1c0i1o1_disconnected), + KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1mc0i1o1), + KUNIT_CASE(aspeed_intc0_resolve_route_c1i2o2mc0i1o1), + KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1mc0i2o1), + KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o2mc0i1o1_invalid), + KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1mc0i1o1_bad_range_upstream), + {}, +}; + +static struct kunit_suite ast2700_intc0_test_suite = { + .name = "ast2700-intc0", + .test_cases = ast2700_intc0_test_cases, +}; + +kunit_test_suite(ast2700_intc0_test_suite); + +MODULE_LICENSE("GPL");
--
2.34.1