From nobody Mon Apr 6 09:09:43 2026 Received: from mail-pj1-f47.google.com (mail-pj1-f47.google.com [209.85.216.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 16CEA2FC00D for ; Fri, 20 Mar 2026 05:42:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773985355; cv=none; b=czJ6iK3AnXtclHeS/LyvQBt6olOmA3Y9DaocJ8qmv2OvFrM9zXJt2cn+KJHlCSn5dge2y0Ob2RIr5FhYMYJxounO9cjAaSCUfMn+7qtEYznjgAegoctbW2AGxzzLKqqpDb8s47EghJWAZjaCdtus33ScGi8T5eqpWijr5mvR4VE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773985355; c=relaxed/simple; bh=oUKWygy2JjPtahcgMja/EAoWoUJw2kPhti0uEnM2Z/Q=; h=From:To:Subject:Date:Message-ID:MIME-Version; b=C8dXn185yB5FFZsH6VxVYA5UQw/g80Q4nxaEFuUQAP0gzGtzPNg6wq3ettWEVC68r6G3fv9ZX3uzjn9sj2uXRnORo6H2Zx88ug68ODjz3jwmBWC1KoiSOXE906qXcl6uuZQswoLqLz5XzdmIp0p74yuICm/DDHEdTUrOcd7hUB8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=AawHuc4k; arc=none smtp.client-ip=209.85.216.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="AawHuc4k" Received: by mail-pj1-f47.google.com with SMTP id 98e67ed59e1d1-35ba749f441so1595034a91.1 for ; Thu, 19 Mar 2026 22:42:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773985353; x=1774590153; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:to :from:from:to:cc:subject:date:message-id:reply-to; bh=ICKlbeihDfz0tjEeNan7n5tL/L7z0Sgh1LpLkTKQTtY=; b=AawHuc4kYTgfBLhjGeDLXW7lnSTav/DT3fVLFFuCxA0hixHUuHF2ifoKI/XodRGEOZ 19dEKsG4Xf04i0CAgQVVhVKhPfaLnWKUVWffGeVFIXmSy4HkkLMS4cAQLC4O0O02IWKG IseZZSdu+53l5bWrRf7C6qFw+h/+aRcWcZYea4gaVeukGUM5n3JR9yUVMKAiaMWNwinv cn/bJxHUVHT+8rEiADrxLKbqqeY2GQ+oIDqK/I/FyrfPlxzLXrvOvpZ4qUZ2tUT4YUMt yjMcnc0QbIG43vRDOdsHVxVZktCZorHrvVaQDrQx5HHzo//ne+WrwOOY8eTWRw+5Lhf7 oO8A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773985353; x=1774590153; h=content-transfer-encoding:mime-version:message-id:date:subject:to :from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=ICKlbeihDfz0tjEeNan7n5tL/L7z0Sgh1LpLkTKQTtY=; b=hwG72Zhuz12/nHTHO+RNST1EIz5rhR9vvSTs1euCGFMw+F/MeoF2OOTwwdaUeTMj/o MOj1UyzYed+d0Zh10lNk0MboL1KeUCfnQsITbvtd6A74Rl79UBKjHy+cfUfwxZM+93XU MRo6CAeJ3I4yjTAagy91BJtJQoQzEeZjBcOv28gDqsUmcmnWizXKzHfb5zW3fYo3ZbbT 4SrKvYIQcOn7QabpXAVSxZWQAK7FLhtA6dyGh+8aTp14S9RiAHafLyS3E8qXPUvHSu1P 6fCIueyuADUXBOCewD5xGWQzBUDtr7oCy4OsqdzV7LE9YkXPFRII6atdezu0NKEMRdR6 4J6g== X-Forwarded-Encrypted: i=1; AJvYcCV4/1O/OjZ3uOIQxkL6Z2iTo1BiYnAErDG4zd7/8nLuWlTH+mZ3vRu0YbLOePeIklRUWIj2ocPZyzCdQww=@vger.kernel.org X-Gm-Message-State: AOJu0YzAcjrMii0Y1+QU78P0OcXazqSudWiAnMJEPLwruSBUqoRgbT4O VpCMmYLrhMLDS6bJveevehpyvVhBH9ysoalPNWoas4d3zMEScNkex8I0ADsHxw== X-Gm-Gg: ATEYQzzTl1ovzWpSYJYiPIXIQIzz68wN4xg4mGIS7D+te6SaqgPazoDskrxbhK8HXi/ 4krcfVne4pC8OoVyENuQAYmEFBCqBKJLEHekz5o/iBnlIPmk3hjKwASOnYTsmjog6DpYOfpgi5j bdtJQvWAR19hQVbz0/1v4XFsEpTRblYOxnIMqVlhKJ8mo4g/0X8eB4f/1wPyrQFVqVWa58dT+DZ ZnjgJfCYIMDFLz6y4wcyhSBjbhYNgG56TAfieL1zjwZxg51wa2mVY2m2bm6apbs5pcQ5No0KkwG CN4ncnJtDKDp/zX3Qh+PCSmkF5xryiXoP09poJng9+gwj2nEmnaJfhPw6TW6LD34j8xZUWlO8Y+ cQmp2Y8FFAIfHAI74P+69xeWRqSKPDMkobsmssSlApqbny0cjQkXLLdQu9G2+bjY1Oj4oknax1S tboeuvYHQa1h2tPAKW9HQ7dusJLnIFZ8c/3Jr3yKSVMXuvJG30xOncHB1YWqCtLegSnoolH54= X-Received: by 2002:a17:90b:3a92:b0:359:f8c3:dada with SMTP id 98e67ed59e1d1-35bd2c22480mr1488873a91.13.1773985353336; Thu, 19 Mar 2026 22:42:33 -0700 (PDT) Received: from li-1a3e774c-28e4-11b2-a85c-acc9f2883e29.ibm.com ([49.207.193.112]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-35bc5ed0777sm4308004a91.0.2026.03.19.22.42.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Mar 2026 22:42:33 -0700 (PDT) From: "Mukesh Kumar Chaurasiya (IBM)" To: maddy@linux.ibm.com, mpe@ellerman.id.au, npiggin@gmail.com, chleroy@kernel.org, mkchauras@gmail.com, linuxppc-dev@lists.ozlabs.org, linux-kernel@vger.kernel.org Subject: [PATCH V2] powerpc/xive: Add KUnit tests for xive_find_target_in_mask() Date: Fri, 20 Mar 2026 11:12:03 +0530 Message-ID: <20260320054202.3245144-2-mkchauras@gmail.com> X-Mailer: git-send-email 2.53.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add comprehensive KUnit test suite for the xive_find_target_in_mask() function, which is responsible for selecting target CPUs for interrupt routing in the XIVE interrupt controller. The test suite includes 8 test cases covering: - Empty CPU mask handling (returns -1) - Single CPU selection - Multiple CPU selection with various mask sizes - Fuzz parameter behavior and modulo arithmetic - Large fuzz values exceeding mask weight - Wrap-around behavior at mask boundaries - System online CPU mask handling - Edge case where fuzz equals mask weight Two tests require multiple CPUs and will skip gracefully in single-CPU environments (e.g., QEMU default configuration). All tests pass without requiring modifications to production code. The test can be run with: ./tools/testing/kunit/kunit.py run --arch=3Dpowerpc \ --kunitconfig=3Darch/powerpc/sysdev/xive --qemu_args=3D"-smp 4" Signed-off-by: Mukesh Kumar Chaurasiya (IBM) --- V1 -> V2: - Updated commit message V1: https://lore.kernel.org/all/20260320052400.3230999-1-mkchauras@gmail.co= m/ arch/powerpc/sysdev/xive/.kunitconfig | 4 + arch/powerpc/sysdev/xive/Kconfig | 9 + arch/powerpc/sysdev/xive/Makefile | 7 +- arch/powerpc/sysdev/xive/xive-test.c | 294 ++++++++++++++++++++++++++ 4 files changed, 311 insertions(+), 3 deletions(-) create mode 100644 arch/powerpc/sysdev/xive/.kunitconfig create mode 100644 arch/powerpc/sysdev/xive/xive-test.c diff --git a/arch/powerpc/sysdev/xive/.kunitconfig b/arch/powerpc/sysdev/xi= ve/.kunitconfig new file mode 100644 index 000000000000..ea6634bb0718 --- /dev/null +++ b/arch/powerpc/sysdev/xive/.kunitconfig @@ -0,0 +1,4 @@ +CONFIG_KUNIT=3Dy +CONFIG_PPC_PSERIES=3Dy +CONFIG_PPC_XIVE_SPAPR=3Dy +CONFIG_PPC_XIVE_KUNIT_TEST=3Dy \ No newline at end of file diff --git a/arch/powerpc/sysdev/xive/Kconfig b/arch/powerpc/sysdev/xive/Kc= onfig index 785c292d104b..81727b9c22b7 100644 --- a/arch/powerpc/sysdev/xive/Kconfig +++ b/arch/powerpc/sysdev/xive/Kconfig @@ -12,3 +12,12 @@ config PPC_XIVE_NATIVE config PPC_XIVE_SPAPR bool select PPC_XIVE + +config PPC_XIVE_KUNIT_TEST + tristate "KUnit tests for XIVE interrupt controller" if !KUNIT_ALL_TESTS + depends on KUNIT && PPC_XIVE + default KUNIT_ALL_TESTS + help + This builds unit tests for the XIVE interrupt controller. + + If unsure, say N. diff --git a/arch/powerpc/sysdev/xive/Makefile b/arch/powerpc/sysdev/xive/M= akefile index e5108883894a..e1f9f513af09 100644 --- a/arch/powerpc/sysdev/xive/Makefile +++ b/arch/powerpc/sysdev/xive/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only =20 -obj-y +=3D common.o -obj-$(CONFIG_PPC_XIVE_NATIVE) +=3D native.o -obj-$(CONFIG_PPC_XIVE_SPAPR) +=3D spapr.o +obj-y +=3D common.o +obj-$(CONFIG_PPC_XIVE_NATIVE) +=3D native.o +obj-$(CONFIG_PPC_XIVE_SPAPR) +=3D spapr.o +obj-$(CONFIG_PPC_XIVE_KUNIT_TEST) +=3D xive-test.o diff --git a/arch/powerpc/sysdev/xive/xive-test.c b/arch/powerpc/sysdev/xiv= e/xive-test.c new file mode 100644 index 000000000000..ee08f18af864 --- /dev/null +++ b/arch/powerpc/sysdev/xive/xive-test.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * KUnit tests for XIVE interrupt controller + * + * Copyright 2026 IBM Corporation. + */ + +#include +#include +#include +#include +#include + +/* + * Mock xive_try_pick_target for testing + * The real function checks queue capacity, we simplify for testing + */ +static bool xive_try_pick_target(int cpu) +{ + /* For testing, accept any online CPU */ + return cpu_online(cpu); +} + +/* + * Copy of xive_find_target_in_mask from common.c for testing + * This allows us to test the static function without modifying source + */ +static int xive_find_target_in_mask(const struct cpumask *mask, + unsigned int fuzz) +{ + int cpu, first; + + /* Pick up a starting point CPU in the mask based on fuzz */ + fuzz %=3D cpumask_weight(mask); + first =3D cpumask_nth(fuzz, mask); + WARN_ON(first >=3D nr_cpu_ids); + + /* + * Now go through the entire mask until we find a valid + * target. + */ + for_each_cpu_wrap(cpu, mask, first) { + if (cpu_online(cpu) && xive_try_pick_target(cpu)) + return cpu; + } + + return -1; +} + +/* + * Test: Empty CPU mask + * Expected: Should return -1 when the mask contains no CPUs + */ +static void xive_test_find_target_empty_mask(struct kunit *test) +{ + struct cpumask empty_mask; + int result; + + cpumask_clear(&empty_mask); + + result =3D xive_find_target_in_mask(&empty_mask, 0); + + KUNIT_EXPECT_EQ(test, result, -1); +} + +/* + * Test: Single CPU in mask + * Expected: Should return that CPU if it's online + */ +static void xive_test_find_target_single_cpu(struct kunit *test) +{ + struct cpumask single_mask; + int cpu =3D 0; + int result; + + /* Skip test if CPU 0 is not online */ + if (!cpu_online(0)) + kunit_skip(test, "CPU 0 is not online"); + + cpumask_clear(&single_mask); + cpumask_set_cpu(cpu, &single_mask); + + result =3D xive_find_target_in_mask(&single_mask, 0); + + KUNIT_EXPECT_EQ(test, result, cpu); +} + +/* + * Test: Multiple CPUs in mask with fuzz=3D0 + * Expected: Should return a valid CPU from the mask + */ +static void xive_test_find_target_multiple_cpus(struct kunit *test) +{ + struct cpumask multi_mask; + int result; + int cpu; + int count =3D 0; + + cpumask_clear(&multi_mask); + + /* Add first 4 online CPUs to the mask */ + for_each_online_cpu(cpu) { + cpumask_set_cpu(cpu, &multi_mask); + count++; + if (count >=3D 4) + break; + } + + if (count =3D=3D 0) + kunit_skip(test, "No online CPUs available"); + + result =3D xive_find_target_in_mask(&multi_mask, 0); + + /* Result should be a valid CPU in the mask */ + KUNIT_EXPECT_NE(test, result, -1); + KUNIT_EXPECT_TRUE(test, cpumask_test_cpu(result, &multi_mask)); + KUNIT_EXPECT_TRUE(test, cpu_online(result)); +} + +/* + * Test: Fuzz parameter affects starting point + * Expected: Different fuzz values may select different CPUs + */ +static void xive_test_find_target_fuzz_variation(struct kunit *test) +{ + struct cpumask multi_mask; + int result1, result2; + int cpu; + int count =3D 0; + + cpumask_clear(&multi_mask); + + /* Add multiple online CPUs to the mask */ + for_each_online_cpu(cpu) { + cpumask_set_cpu(cpu, &multi_mask); + count++; + if (count >=3D 4) + break; + } + + if (count < 2) + kunit_skip(test, "Need at least 2 online CPUs for this test"); + + result1 =3D xive_find_target_in_mask(&multi_mask, 0); + result2 =3D xive_find_target_in_mask(&multi_mask, 1); + + /* Both results should be valid CPUs in the mask */ + KUNIT_EXPECT_NE(test, result1, -1); + KUNIT_EXPECT_NE(test, result2, -1); + KUNIT_EXPECT_TRUE(test, cpumask_test_cpu(result1, &multi_mask)); + KUNIT_EXPECT_TRUE(test, cpumask_test_cpu(result2, &multi_mask)); +} + +/* + * Test: Large fuzz value (modulo behavior) + * Expected: Should handle fuzz values larger than mask weight correctly + */ +static void xive_test_find_target_large_fuzz(struct kunit *test) +{ + struct cpumask multi_mask; + int result; + int cpu; + int count =3D 0; + unsigned int large_fuzz =3D 1000; + + cpumask_clear(&multi_mask); + + /* Add online CPUs to the mask */ + for_each_online_cpu(cpu) { + cpumask_set_cpu(cpu, &multi_mask); + count++; + if (count >=3D 3) + break; + } + + if (count =3D=3D 0) + kunit_skip(test, "No online CPUs available"); + + result =3D xive_find_target_in_mask(&multi_mask, large_fuzz); + + /* Result should be a valid CPU in the mask */ + KUNIT_EXPECT_NE(test, result, -1); + KUNIT_EXPECT_TRUE(test, cpumask_test_cpu(result, &multi_mask)); + KUNIT_EXPECT_TRUE(test, cpu_online(result)); +} + +/* + * Test: Wrap-around behavior at mask boundary + * Expected: Should correctly wrap around when starting near the end + */ +static void xive_test_find_target_wrap_around(struct kunit *test) +{ + struct cpumask wrap_mask; + int result; + int cpu; + int count =3D 0; + unsigned int weight; + + cpumask_clear(&wrap_mask); + + /* Add online CPUs to the mask */ + for_each_online_cpu(cpu) { + cpumask_set_cpu(cpu, &wrap_mask); + count++; + if (count >=3D 4) + break; + } + + if (count < 2) + kunit_skip(test, "Need at least 2 online CPUs for this test"); + + weight =3D cpumask_weight(&wrap_mask); + + /* Test with fuzz at the boundary */ + result =3D xive_find_target_in_mask(&wrap_mask, weight - 1); + + KUNIT_EXPECT_NE(test, result, -1); + KUNIT_EXPECT_TRUE(test, cpumask_test_cpu(result, &wrap_mask)); + KUNIT_EXPECT_TRUE(test, cpu_online(result)); +} + +/* + * Test: Using cpu_online_mask + * Expected: Should handle the system's online CPU mask correctly + */ +static void xive_test_find_target_online_mask(struct kunit *test) +{ + int result; + + if (cpumask_empty(cpu_online_mask)) + kunit_skip(test, "No online CPUs in system"); + + result =3D xive_find_target_in_mask(cpu_online_mask, 0); + + KUNIT_EXPECT_NE(test, result, -1); + KUNIT_EXPECT_TRUE(test, cpu_online(result)); +} + +/* + * Test: Fuzz value equal to mask weight + * Expected: Should wrap to first CPU (fuzz % weight =3D=3D 0) + */ +static void xive_test_find_target_fuzz_equals_weight(struct kunit *test) +{ + struct cpumask test_mask; + int result; + int cpu; + int count =3D 0; + unsigned int weight; + + cpumask_clear(&test_mask); + + /* Add online CPUs to the mask */ + for_each_online_cpu(cpu) { + cpumask_set_cpu(cpu, &test_mask); + count++; + if (count >=3D 3) + break; + } + + if (count =3D=3D 0) + kunit_skip(test, "No online CPUs available"); + + weight =3D cpumask_weight(&test_mask); + + /* Fuzz equal to weight should wrap to start */ + result =3D xive_find_target_in_mask(&test_mask, weight); + + KUNIT_EXPECT_NE(test, result, -1); + KUNIT_EXPECT_TRUE(test, cpumask_test_cpu(result, &test_mask)); +} + +static struct kunit_case xive_test_cases[] =3D { + KUNIT_CASE(xive_test_find_target_empty_mask), + KUNIT_CASE(xive_test_find_target_single_cpu), + KUNIT_CASE(xive_test_find_target_multiple_cpus), + KUNIT_CASE(xive_test_find_target_fuzz_variation), + KUNIT_CASE(xive_test_find_target_large_fuzz), + KUNIT_CASE(xive_test_find_target_wrap_around), + KUNIT_CASE(xive_test_find_target_online_mask), + KUNIT_CASE(xive_test_find_target_fuzz_equals_weight), + {} +}; + +static struct kunit_suite xive_test_suite =3D { + .name =3D "xive_find_target_in_mask", + .test_cases =3D xive_test_cases, +}; + +kunit_test_suites(&xive_test_suite); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("KUnit tests for XIVE interrupt controller"); +MODULE_AUTHOR("Mukesh Kumar Chaurasiya (IBM) "); --=20 2.53.0