From a6f90a7ca106ce5bbf922b6ced2edea1e7e84134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Jun 2025 09:38:07 +0200 Subject: [PATCH 01/16] kbuild: userprogs: avoid duplicating of flags inherited from kernel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The duplication makes maintenance harder. Changes need to be done in two places and the lines will grow overly long. Use an intermediary variable instead. Signed-off-by: Thomas Weißschuh Signed-off-by: Linux RISC-V bot --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 35e6e5240c61a8..c4293cf91e968c 100644 --- a/Makefile +++ b/Makefile @@ -1130,8 +1130,9 @@ LDFLAGS_vmlinux += --emit-relocs --discard-none endif # Align the bit size of userspace programs with the kernel -KBUILD_USERCFLAGS += $(filter -m32 -m64 --target=%, $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS)) -KBUILD_USERLDFLAGS += $(filter -m32 -m64 --target=%, $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS)) +USERFLAGS_FROM_KERNEL := -m32 -m64 --target=% +KBUILD_USERCFLAGS += $(filter $(USERFLAGS_FROM_KERNEL), $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS)) +KBUILD_USERLDFLAGS += $(filter $(USERFLAGS_FROM_KERNEL), $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS)) # userspace programs are linked via the compiler, use the correct linker ifeq ($(CONFIG_CC_IS_CLANG)$(CONFIG_LD_IS_LLD),yy) From 258f397e2eb6eaa844d48abbd19307d40b96d462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Jun 2025 09:38:08 +0200 Subject: [PATCH 02/16] kbuild: userprogs: also inherit byte order and ABI from kernel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make sure the byte order and ABI of the userprogs matches the one of the kernel, similar to how the bit size is handled. Otherwise the userprogs may not be executable. This happens for example on powerpc little endian, or riscv32. Signed-off-by: Thomas Weißschuh Signed-off-by: Linux RISC-V bot --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c4293cf91e968c..b9aa1058321dab 100644 --- a/Makefile +++ b/Makefile @@ -1129,8 +1129,8 @@ ifneq ($(CONFIG_ARCH_VMLINUX_NEEDS_RELOCS),) LDFLAGS_vmlinux += --emit-relocs --discard-none endif -# Align the bit size of userspace programs with the kernel -USERFLAGS_FROM_KERNEL := -m32 -m64 --target=% +# Align the bit size, byte order and architecture of userspace programs with the kernel +USERFLAGS_FROM_KERNEL := -m32 -m64 -mlittle-endian -mbig-endian --target=% -march=% -mabi=% KBUILD_USERCFLAGS += $(filter $(USERFLAGS_FROM_KERNEL), $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS)) KBUILD_USERLDFLAGS += $(filter $(USERFLAGS_FROM_KERNEL), $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS)) From 6a315133c2612d324322d72945b5e3f7a87413a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Jun 2025 09:38:09 +0200 Subject: [PATCH 03/16] init: re-add CONFIG_CC_CAN_LINK_STATIC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For the kunit UAPI functionality this feature is needed. This reverts commit d1b99cdf22e0 ("init: remove unused CONFIG_CC_CAN_LINK_STATIC") Signed-off-by: Thomas Weißschuh Signed-off-by: Linux RISC-V bot --- init/Kconfig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/init/Kconfig b/init/Kconfig index af4c2f0854554b..26cafbad4f1560 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -87,6 +87,11 @@ config CC_CAN_LINK default $(success,$(srctree)/scripts/cc-can-link.sh $(CC) $(CLANG_FLAGS) $(USERCFLAGS) $(USERLDFLAGS) $(m64-flag)) if 64BIT default $(success,$(srctree)/scripts/cc-can-link.sh $(CC) $(CLANG_FLAGS) $(USERCFLAGS) $(USERLDFLAGS) $(m32-flag)) +config CC_CAN_LINK_STATIC + bool + default $(success,$(srctree)/scripts/cc-can-link.sh $(CC) $(CLANG_FLAGS) $(USERCFLAGS) $(USERLDFLAGS) $(m64-flag) -static) if 64BIT + default $(success,$(srctree)/scripts/cc-can-link.sh $(CC) $(CLANG_FLAGS) $(USERCFLAGS) $(USERLDFLAGS) $(m32-flag) -static) + # Fixed in GCC 14, 13.3, 12.4 and 11.5 # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113921 config GCC_ASM_GOTO_OUTPUT_BROKEN From 6660e8e35c8d733eeed81c668abbe37eac8086ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Jun 2025 09:38:10 +0200 Subject: [PATCH 04/16] kbuild: userprogs: add nolibc support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Userprogs are built with the regular kernel compiler $CC. A kernel compiler does not necessarily contain a libc which is required for a normal userspace application. However the kernel tree does contain a minimal libc implementation "nolibc" which can be used to build userspace applications. Introduce support to build userprogs against nolibc instead of the default libc of the compiler, which may not exist. Signed-off-by: Thomas Weißschuh Signed-off-by: Linux RISC-V bot --- Documentation/kbuild/makefiles.rst | 13 +++++++++++++ scripts/Makefile.userprogs | 13 ++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Documentation/kbuild/makefiles.rst b/Documentation/kbuild/makefiles.rst index 8aef3650c1f32b..4cc7a1b89f1803 100644 --- a/Documentation/kbuild/makefiles.rst +++ b/Documentation/kbuild/makefiles.rst @@ -974,6 +974,19 @@ When linking bpfilter_umh, it will be passed the extra option -static. From command line, :ref:`USERCFLAGS and USERLDFLAGS ` will also be used. +Building userprogs against nolibc +--------------------------------- + +Not all kernel toolchains provide a libc. +Simple userprogs can be built against a very simple libc call "nolibc" provided +by the kernel source tree. +This requires ``CONFIG_HEADERS_INSTALL=y``. + +Example:: + + # lib/kunit/Makefile + uapi-preinit-nolibc := $(CONFIG_ARCH_HAS_NOLIBC) + When userspace programs are actually built ------------------------------------------ diff --git a/scripts/Makefile.userprogs b/scripts/Makefile.userprogs index f3a7e1ef3753b5..b1633a9de6c86a 100644 --- a/scripts/Makefile.userprogs +++ b/scripts/Makefile.userprogs @@ -16,10 +16,17 @@ user-csingle := $(addprefix $(obj)/, $(user-csingle)) user-cmulti := $(addprefix $(obj)/, $(user-cmulti)) user-cobjs := $(addprefix $(obj)/, $(user-cobjs)) +user_nolibc_ccflags := -nostdlib -nostdinc -static -fno-ident -fno-asynchronous-unwind-tables \ + -ffreestanding -fno-stack-protector \ + -isystem $(objtree)/usr/include -include $(srctree)/tools/include/nolibc/nolibc.h -isystem $(srctree)/tools/include/nolibc/ +user_nolibc_ldflags := -nostdlib -nostdinc -static + user_ccflags = -Wp,-MMD,$(depfile) $(KBUILD_USERCFLAGS) $(userccflags) \ - $($(target-stem)-userccflags) -user_ldflags = $(KBUILD_USERLDFLAGS) $(userldflags) $($(target-stem)-userldflags) -user_ldlibs = $(userldlibs) $($(target-stem)-userldlibs) + $($(target-stem)-userccflags) $(if $($(target-stem)-nolibc),$(user_nolibc_ccflags)) +user_ldflags = $(KBUILD_USERLDFLAGS) $(userldflags) $($(target-stem)-userldflags) \ + $(if $($(target-stem)-nolibc),$(user_nolibc_ldflags)) +user_ldlibs = $(userldlibs) $($(target-stem)-userldlibs) \ + $(if $($(target-stem)-nolibc),$(user_nolibc_ldlibs)) # Create an executable from a single .c file quiet_cmd_user_cc_c = CC [U] $@ From 8740994a84faeabdc924bd4874dc1b413d0d4794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Jun 2025 09:38:11 +0200 Subject: [PATCH 05/16] kbuild: introduce CONFIG_ARCH_HAS_NOLIBC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nolibc does not support all architectures. Add a kconfig option, so users can know where it is available. The new option is maintained inside tools/include/nolibc/ as only that directory is responsible for nolibc's availability. Reviewed-by: Nicolas Schier Signed-off-by: Thomas Weißschuh Signed-off-by: Linux RISC-V bot --- init/Kconfig | 2 ++ tools/include/nolibc/Kconfig.nolibc | 15 +++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 tools/include/nolibc/Kconfig.nolibc diff --git a/init/Kconfig b/init/Kconfig index 26cafbad4f1560..6d10f2816e453d 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -92,6 +92,8 @@ config CC_CAN_LINK_STATIC default $(success,$(srctree)/scripts/cc-can-link.sh $(CC) $(CLANG_FLAGS) $(USERCFLAGS) $(USERLDFLAGS) $(m64-flag) -static) if 64BIT default $(success,$(srctree)/scripts/cc-can-link.sh $(CC) $(CLANG_FLAGS) $(USERCFLAGS) $(USERLDFLAGS) $(m32-flag) -static) +source "tools/include/nolibc/Kconfig.nolibc" + # Fixed in GCC 14, 13.3, 12.4 and 11.5 # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113921 config GCC_ASM_GOTO_OUTPUT_BROKEN diff --git a/tools/include/nolibc/Kconfig.nolibc b/tools/include/nolibc/Kconfig.nolibc new file mode 100644 index 00000000000000..29cbc5437e70cb --- /dev/null +++ b/tools/include/nolibc/Kconfig.nolibc @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 + +config ARCH_HAS_NOLIBC + bool + default y if ARM + default y if ARM64 + default y if LOONGARCH + default y if M68K + default y if MIPS + default y if PPC + default y if RISCV + default y if S390 + default y if SPARC + default y if UML_X86 + default y if X86 From 668cfd9fc58dfe1ce446a214c6e236ab98f9c21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Jun 2025 09:38:12 +0200 Subject: [PATCH 06/16] kbuild: doc: add label for userprogs section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some upcoming new documentation should link directly to the userprogs section. Add a label to the section so it can be referenced. Signed-off-by: Thomas Weißschuh Signed-off-by: Linux RISC-V bot --- Documentation/kbuild/makefiles.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/kbuild/makefiles.rst b/Documentation/kbuild/makefiles.rst index 4cc7a1b89f1803..2adea36ac6ebf6 100644 --- a/Documentation/kbuild/makefiles.rst +++ b/Documentation/kbuild/makefiles.rst @@ -891,6 +891,8 @@ This is possible in two ways: This will tell kbuild to build lxdialog even if not referenced in any rule. +.. _kbuild_userprogs: + Userspace Program support ========================= From fb0ef84655691d048477539f36411f628d5e9a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Jun 2025 09:38:13 +0200 Subject: [PATCH 07/16] kbuild: introduce blob framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Various subsystems embed non-code build artifacts into the kernel, for example the initramfs, /proc/config.gz, vDSO image, etc. Currently each user has their own implementation for that. Add a common "blob" framework to provide this functionality. It provides standard kbuild and C APIs to embed and later access non-code build artifacts into the kernel image or modules. Reviewed-by: Nicolas Schier Signed-off-by: Thomas Weißschuh Signed-off-by: Linux RISC-V bot --- Documentation/kbuild/makefiles.rst | 23 ++++++++++++++++++++-- MAINTAINERS | 2 ++ include/linux/blob.h | 31 ++++++++++++++++++++++++++++++ scripts/Makefile.blobs | 19 ++++++++++++++++++ scripts/Makefile.build | 6 ++++++ scripts/Makefile.clean | 2 +- scripts/blob-wrap.c | 27 ++++++++++++++++++++++++++ 7 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 include/linux/blob.h create mode 100644 scripts/Makefile.blobs create mode 100644 scripts/blob-wrap.c diff --git a/Documentation/kbuild/makefiles.rst b/Documentation/kbuild/makefiles.rst index 2adea36ac6ebf6..5d158780948ab6 100644 --- a/Documentation/kbuild/makefiles.rst +++ b/Documentation/kbuild/makefiles.rst @@ -525,8 +525,8 @@ otherwise the command line check will fail, and the target will always be built. If the target is already listed in the recognized syntax such as -obj-y/m, lib-y/m, extra-y/m, always-y/m, hostprogs, userprogs, Kbuild -automatically adds it to $(targets). Otherwise, the target must be +obj-y/m, lib-y/m, extra-y/m, always-y/m, hostprogs, userprogs, blobs, +Kbuild automatically adds it to $(targets). Otherwise, the target must be explicitly added to $(targets). Assignments to $(targets) are without $(obj)/ prefix. if_changed may be @@ -1019,6 +1019,25 @@ There are two ways to do this. This will tell Kbuild to build binderfs_example when it visits this Makefile. +.. _kbuild_blobs: + +Blob framework +============== + +Kbuild supports wrapping source or generated files into object files which are linked +into the kernel and then accessed at runtime through ``include/linux/blob.h``. + +Example:: + + obj-m := some-module.o + userprogs := some-userprog + blobs := some-userprog.blob.o + some-userprog.blob-symbol := some_userprog + some-module-y += some-userprog.blob.o + +Kbuild will build the :ref:`userprog ` ``some-userprog`` and +link it into ``some-module`` from where it can be accessed as ``BLOB(some_userprog)``. + Kbuild clean infrastructure =========================== diff --git a/MAINTAINERS b/MAINTAINERS index a92290fffa163f..435f8af750d40d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13063,11 +13063,13 @@ Q: https://patchwork.kernel.org/project/linux-kbuild/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/masahiroy/linux-kbuild.git F: Documentation/kbuild/ F: Makefile +F: include/linux/blob.h F: scripts/*vmlinux* F: scripts/Kbuild* F: scripts/Makefile* F: scripts/bash-completion/ F: scripts/basic/ +F: scripts/blob-wrap.c F: scripts/clang-tools/ F: scripts/dummy-tools/ F: scripts/include/ diff --git a/include/linux/blob.h b/include/linux/blob.h new file mode 100644 index 00000000000000..4104d04e036fad --- /dev/null +++ b/include/linux/blob.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Linkable blob API. + * + * Copyright (C) 2025, Linutronix GmbH. + * Author: Thomas Weißschuh + */ + +#ifndef _LINUX_BLOB_H +#define _LINUX_BLOB_H + +#include +#include + +struct blob { + const char *const path; + const u8 *data; + const u8 *end; +}; + +#define BLOB(_symbol) ({ \ + extern const struct blob CONCATENATE(__blob_, _symbol); \ + &CONCATENATE(__blob_, _symbol); \ +}) + +static inline size_t blob_size(const struct blob *blob) +{ + return blob->end - blob->data; +} + +#endif /* _LINUX_BLOB_H */ diff --git a/scripts/Makefile.blobs b/scripts/Makefile.blobs new file mode 100644 index 00000000000000..fd20ebb41c1d65 --- /dev/null +++ b/scripts/Makefile.blobs @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Build linkable blobs +# + +blobs := $(addprefix $(obj)/, $(blobs)) + +blob-stem = $(subst -,_,$(subst .blob,,$(basename $(patsubst $(obj)/%,%,$@)))) +blob-symbol = $(or $($(target-stem)-symbol),$(blob-stem)) + +blob-flags = -DBLOB_SYMBOL="$(blob-symbol)" -DBLOB_INPUT=$< + +quiet_cmd_blob = BLOB $@ + cmd_blob = $(CC) $(c_flags) $(blob-flags) -c -o $@ $(srctree)/scripts/blob-wrap.c + +$(blobs): $(obj)/%.blob.o: $(obj)/% $(srctree)/scripts/blob-wrap.c FORCE + $(call if_changed_dep,blob) + +targets += $(blobs) diff --git a/scripts/Makefile.build b/scripts/Makefile.build index a6461ea411f7a9..de000268a53887 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -529,6 +529,12 @@ ifneq ($(need-dtbslist)$(dtb-y)$(dtb-)$(filter %.dtb %.dtb.o %.dtbo.o,$(targets) include $(srctree)/scripts/Makefile.dtbs endif +# $(sort ...) is used here to remove duplicated words and excessive spaces. +blobs := $(sort $(blobs)) +ifneq ($(blobs),) +include $(srctree)/scripts/Makefile.blobs +endif + # Build # --------------------------------------------------------------------------- diff --git a/scripts/Makefile.clean b/scripts/Makefile.clean index 6ead00ec7313b3..536972b0a528d1 100644 --- a/scripts/Makefile.clean +++ b/scripts/Makefile.clean @@ -25,7 +25,7 @@ subdir-ymn := $(addprefix $(obj)/,$(subdir-ymn)) # directory __clean-files := \ - $(clean-files) $(targets) $(hostprogs) $(userprogs) \ + $(clean-files) $(targets) $(hostprogs) $(userprogs) $(blobs) \ $(extra-y) $(extra-m) $(extra-) \ $(always-y) $(always-m) $(always-) \ $(hostprogs-always-y) $(hostprogs-always-m) $(hostprogs-always-) \ diff --git a/scripts/blob-wrap.c b/scripts/blob-wrap.c new file mode 100644 index 00000000000000..82ab3bc641bd69 --- /dev/null +++ b/scripts/blob-wrap.c @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include + +#define BLOB_SYMBOL_DATA CONCATENATE(__blob_data_, BLOB_SYMBOL) +#define BLOB_SYMBOL_END CONCATENATE(__blob_end_, BLOB_SYMBOL) + +asm ( +" .pushsection .rodata, \"a\"\n" +" .global " __stringify(BLOB_SYMBOL_DATA) "\n" +__stringify(BLOB_SYMBOL_DATA) ":\n" +" .incbin \"" __stringify(BLOB_INPUT) "\"\n" +" .global " __stringify(BLOB_SYMBOL_END) "\n" +__stringify(BLOB_SYMBOL_END) ":\n" +" .popsection\n" +); + +extern const u8 BLOB_SYMBOL_DATA; +extern const u8 BLOB_SYMBOL_END; + +const struct blob CONCATENATE(__blob_, BLOB_SYMBOL) = { + .path = __stringify(BLOB_INPUT), + .data = &BLOB_SYMBOL_DATA, + .end = &BLOB_SYMBOL_END, +}; From 980ecba65c9137f30ba20ea9cce4add66ddac900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Jun 2025 09:38:14 +0200 Subject: [PATCH 08/16] kunit: tool: Add test for nested test result reporting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently there is no test validating the result reporting from nested tests. Add one, it will also be used to validate upcoming changes to the nested test parsing. Signed-off-by: Thomas Weißschuh Signed-off-by: Linux RISC-V bot --- tools/testing/kunit/kunit_tool_test.py | 9 +++++++++ .../test_data/test_is_test_passed-failure-nested.log | 7 +++++++ 2 files changed, 16 insertions(+) create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-failure-nested.log diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py index bbba921e0eacb1..691cde9b030f77 100755 --- a/tools/testing/kunit/kunit_tool_test.py +++ b/tools/testing/kunit/kunit_tool_test.py @@ -165,6 +165,15 @@ def test_parse_failed_test_log(self): self.assertEqual(kunit_parser.TestStatus.FAILURE, result.status) self.assertEqual(result.counts.errors, 0) + def test_parse_failed_nested_tests_log(self): + nested_log = test_data_path('test_is_test_passed-failure-nested.log') + with open(nested_log) as file: + result = kunit_parser.parse_run_tests(file.readlines(), stdout) + self.assertEqual(kunit_parser.TestStatus.FAILURE, result.status) + self.assertEqual(result.counts.failed, 2) + self.assertEqual(kunit_parser.TestStatus.FAILURE, result.subtests[0].status) + self.assertEqual(kunit_parser.TestStatus.FAILURE, result.subtests[1].status) + def test_no_header(self): empty_log = test_data_path('test_is_test_passed-no_tests_run_no_header.log') with open(empty_log) as file: diff --git a/tools/testing/kunit/test_data/test_is_test_passed-failure-nested.log b/tools/testing/kunit/test_data/test_is_test_passed-failure-nested.log new file mode 100644 index 00000000000000..835816e0a07715 --- /dev/null +++ b/tools/testing/kunit/test_data/test_is_test_passed-failure-nested.log @@ -0,0 +1,7 @@ +KTAP version 1 +1..2 +not ok 1 subtest 1 + KTAP version 1 + 1..1 + not ok 1 test 1 +not ok 2 subtest 2 From 4a616445092369424bed5e0e0279aea4aecffc0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Jun 2025 09:38:15 +0200 Subject: [PATCH 09/16] kunit: tool: Don't overwrite test status based on subtest counts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a subtest itself reports success, but the outer testcase fails, the whole testcase should be reported as a failure. However the status is recalculated based on the test counts, overwriting the outer test result. Synthesize a failed test in this case to make sure the failure is not swallowed. Signed-off-by: Thomas Weißschuh Signed-off-by: Linux RISC-V bot --- tools/testing/kunit/kunit_parser.py | 5 +++++ tools/testing/kunit/kunit_tool_test.py | 2 +- .../kunit/test_data/test_is_test_passed-failure-nested.log | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/testing/kunit/kunit_parser.py b/tools/testing/kunit/kunit_parser.py index c176487356e6c9..2478beb28fc3db 100644 --- a/tools/testing/kunit/kunit_parser.py +++ b/tools/testing/kunit/kunit_parser.py @@ -686,6 +686,11 @@ def bubble_up_test_results(test: Test) -> None: counts.add_status(status) elif test.counts.get_status() == TestStatus.TEST_CRASHED: test.status = TestStatus.TEST_CRASHED + if not test.ok_status(): + for t in subtests: + if not t.ok_status(): + counts.add_status(t.status) + break def parse_test(lines: LineStream, expected_num: int, log: List[str], is_subtest: bool, printer: Printer) -> Test: """ diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py index 691cde9b030f77..c25f52650837e8 100755 --- a/tools/testing/kunit/kunit_tool_test.py +++ b/tools/testing/kunit/kunit_tool_test.py @@ -170,7 +170,7 @@ def test_parse_failed_nested_tests_log(self): with open(nested_log) as file: result = kunit_parser.parse_run_tests(file.readlines(), stdout) self.assertEqual(kunit_parser.TestStatus.FAILURE, result.status) - self.assertEqual(result.counts.failed, 2) + self.assertEqual(result.counts.failed, 3) self.assertEqual(kunit_parser.TestStatus.FAILURE, result.subtests[0].status) self.assertEqual(kunit_parser.TestStatus.FAILURE, result.subtests[1].status) diff --git a/tools/testing/kunit/test_data/test_is_test_passed-failure-nested.log b/tools/testing/kunit/test_data/test_is_test_passed-failure-nested.log index 835816e0a07715..cd9033c464792e 100644 --- a/tools/testing/kunit/test_data/test_is_test_passed-failure-nested.log +++ b/tools/testing/kunit/test_data/test_is_test_passed-failure-nested.log @@ -1,5 +1,8 @@ KTAP version 1 1..2 + KTAP version 1 + 1..1 + ok 1 test 1 not ok 1 subtest 1 KTAP version 1 1..1 From edcfe33bb196230542366f93078feab1f61153a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Jun 2025 09:38:16 +0200 Subject: [PATCH 10/16] kunit: tool: Parse skipped tests from kselftest.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Skipped tests reported by kselftest.h use a different format than KTAP, there is no explicit test name. Normally the test name is part of the free-form string after the SKIP keyword: ok 3 # SKIP test: some reason Extend the parser to handle those correctly. Use the free-form string as test name instead. Signed-off-by: Thomas Weißschuh Signed-off-by: Linux RISC-V bot --- tools/testing/kunit/kunit_parser.py | 8 +++++--- .../kunit/test_data/test_is_test_passed-kselftest.log | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/testing/kunit/kunit_parser.py b/tools/testing/kunit/kunit_parser.py index 2478beb28fc3db..4599d23c79b79f 100644 --- a/tools/testing/kunit/kunit_parser.py +++ b/tools/testing/kunit/kunit_parser.py @@ -352,9 +352,9 @@ def parse_test_plan(lines: LineStream, test: Test) -> bool: lines.pop() return True -TEST_RESULT = re.compile(r'^\s*(ok|not ok) ([0-9]+) (- )?([^#]*)( # .*)?$') +TEST_RESULT = re.compile(r'^\s*(ok|not ok) ([0-9]+)? ?(- )?([^#]*)( # .*)?$') -TEST_RESULT_SKIP = re.compile(r'^\s*(ok|not ok) ([0-9]+) (- )?(.*) # SKIP(.*)$') +TEST_RESULT_SKIP = re.compile(r'^\s*(ok|not ok) ([0-9]+)? ?(- )?(.*) # SKIP ?(.*)$') def peek_test_name_match(lines: LineStream, test: Test) -> bool: """ @@ -379,6 +379,8 @@ def peek_test_name_match(lines: LineStream, test: Test) -> bool: if not match: return False name = match.group(4) + if not name: + return False return name == test.name def parse_test_result(lines: LineStream, test: Test, @@ -416,7 +418,7 @@ def parse_test_result(lines: LineStream, test: Test, # Set name of test object if skip_match: - test.name = skip_match.group(4) + test.name = skip_match.group(4) or skip_match.group(5) else: test.name = match.group(4) diff --git a/tools/testing/kunit/test_data/test_is_test_passed-kselftest.log b/tools/testing/kunit/test_data/test_is_test_passed-kselftest.log index 65d3f27feaf22a..30d9ef18bcec17 100644 --- a/tools/testing/kunit/test_data/test_is_test_passed-kselftest.log +++ b/tools/testing/kunit/test_data/test_is_test_passed-kselftest.log @@ -1,5 +1,5 @@ TAP version 13 -1..2 +1..3 # selftests: membarrier: membarrier_test_single_thread # TAP version 13 # 1..2 @@ -12,3 +12,4 @@ ok 1 selftests: membarrier: membarrier_test_single_thread # ok 1 sys_membarrier available # ok 2 sys membarrier invalid command test: command = -1, flags = 0, errno = 22. Failed as expected ok 2 selftests: membarrier: membarrier_test_multi_thread +ok 3 # SKIP selftests: membarrier: membarrier_test_multi_thread From 42291e1b0b293045ca1ebafce9917806ec0d07d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Jun 2025 09:38:17 +0200 Subject: [PATCH 11/16] kunit: Always descend into kunit directory during build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An upcoming change will add 'userprogs' to the kunit subdirectory. For kbuild to properly clean up these build artifacts the subdirectory needs to be always processed. Pushing the special logic for hook.o into the kunit Makefile also makes the logic easier to understand. Signed-off-by: Thomas Weißschuh Signed-off-by: Linux RISC-V bot --- lib/Makefile | 4 ---- lib/kunit/Makefile | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/Makefile b/lib/Makefile index c38582f187dd81..698566135091cc 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -108,11 +108,7 @@ test_fpu-y := test_fpu_glue.o test_fpu_impl.o CFLAGS_test_fpu_impl.o += $(CC_FLAGS_FPU) CFLAGS_REMOVE_test_fpu_impl.o += $(CC_FLAGS_NO_FPU) -# Some KUnit files (hooks.o) need to be built-in even when KUnit is a module, -# so we can't just use obj-$(CONFIG_KUNIT). -ifdef CONFIG_KUNIT obj-y += kunit/ -endif ifeq ($(CONFIG_DEBUG_KOBJECT),y) CFLAGS_kobject.o += -DDEBUG diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile index 5aa51978e456ab..656f1fa35abcc6 100644 --- a/lib/kunit/Makefile +++ b/lib/kunit/Makefile @@ -17,7 +17,7 @@ kunit-objs += debugfs.o endif # KUnit 'hooks' are built-in even when KUnit is built as a module. -obj-y += hooks.o +obj-$(if $(CONFIG_KUNIT),y) += hooks.o obj-$(CONFIG_KUNIT_TEST) += kunit-test.o obj-$(CONFIG_KUNIT_TEST) += platform-test.o From 379c2bae3b8d09810e6328e0ad78912f68d6e508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Jun 2025 09:38:18 +0200 Subject: [PATCH 12/16] kunit: qemu_configs: loongarch: Enable LSX/LSAX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upcoming kunit UAPI framework will run userspace executables as part of kunit. These may use the LSX or LASX instructions. Make sure the kunit kernel can handle these instructions. Signed-off-by: Thomas Weißschuh Signed-off-by: Linux RISC-V bot --- tools/testing/kunit/qemu_configs/loongarch.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/testing/kunit/qemu_configs/loongarch.py b/tools/testing/kunit/qemu_configs/loongarch.py index a92422967d1da9..1dba755284f11f 100644 --- a/tools/testing/kunit/qemu_configs/loongarch.py +++ b/tools/testing/kunit/qemu_configs/loongarch.py @@ -11,6 +11,8 @@ CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_CPU_HAS_LSX=y +CONFIG_CPU_HAS_LASX=y ''', qemu_arch='loongarch64', kernel_path='arch/loongarch/boot/vmlinux.elf', From 6823f40b88c6e015cd83083442c4131b7c342dd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Jun 2025 09:38:19 +0200 Subject: [PATCH 13/16] kunit: Introduce UAPI testing framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable running UAPI tests as part of kunit. The selftests are embedded into the kernel image and their output is forwarded to kunit for unified reporting. The implementation reuses parts of usermode drivers and usermode helpers. However these frameworks are not used directly as they make it impossible to retrieve a thread's exit code. Signed-off-by: Thomas Weißschuh Signed-off-by: Linux RISC-V bot --- Documentation/dev-tools/kunit/api/index.rst | 5 + Documentation/dev-tools/kunit/api/uapi.rst | 12 + include/kunit/uapi.h | 24 ++ lib/kunit/Kconfig | 10 + lib/kunit/Makefile | 2 + lib/kunit/uapi.c | 287 ++++++++++++++++++++ 6 files changed, 340 insertions(+) create mode 100644 Documentation/dev-tools/kunit/api/uapi.rst create mode 100644 include/kunit/uapi.h create mode 100644 lib/kunit/uapi.c diff --git a/Documentation/dev-tools/kunit/api/index.rst b/Documentation/dev-tools/kunit/api/index.rst index 5cdb552a0808f2..34d8fee9a97059 100644 --- a/Documentation/dev-tools/kunit/api/index.rst +++ b/Documentation/dev-tools/kunit/api/index.rst @@ -9,6 +9,7 @@ API Reference test resource functionredirection + uapi clk of platformdevice @@ -32,6 +33,10 @@ Documentation/dev-tools/kunit/api/functionredirection.rst - Documents the KUnit Function Redirection API +Documentation/dev-tools/kunit/api/uapi.rst + + - Documents the KUnit Userspace testing API + Driver KUnit API ================ diff --git a/Documentation/dev-tools/kunit/api/uapi.rst b/Documentation/dev-tools/kunit/api/uapi.rst new file mode 100644 index 00000000000000..b4764424c629bf --- /dev/null +++ b/Documentation/dev-tools/kunit/api/uapi.rst @@ -0,0 +1,12 @@ +.. SPDX-License-Identifier: GPL-2.0 + +================== +Userspace Test API +================== + +This file documents all of the userspace testing API. +Userspace tests should be built as :ref:`userprogs ` and included into the test +module or kernel as :ref:`blobs `. + +.. kernel-doc:: include/kunit/uapi.h + :internal: diff --git a/include/kunit/uapi.h b/include/kunit/uapi.h new file mode 100644 index 00000000000000..a6181790c96a42 --- /dev/null +++ b/include/kunit/uapi.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * KUnit Userspace testing API. + * + * Copyright (C) 2025, Linutronix GmbH. + * Author: Thomas Weißschuh + */ + +#ifndef _KUNIT_UAPI_H +#define _KUNIT_UAPI_H + +struct blob; +struct kunit; + +/** + * kunit_uapi_run_kselftest() - Run a userspace kselftest as part of kunit + * @test: The test context object. + * @executable: kselftest executable to run + * + * Runs the kselftest and forwards its TAP output and exit status to kunit. + */ +void kunit_uapi_run_kselftest(struct kunit *test, const struct blob *executable); + +#endif /* _KUNIT_UAPI_H */ diff --git a/lib/kunit/Kconfig b/lib/kunit/Kconfig index a97897edd9642f..1f2f5f2213a7d8 100644 --- a/lib/kunit/Kconfig +++ b/lib/kunit/Kconfig @@ -93,4 +93,14 @@ config KUNIT_AUTORUN_ENABLED In most cases this should be left as Y. Only if additional opt-in behavior is needed should this be set to N. +config KUNIT_UAPI + def_bool y + depends on KUNIT=y + depends on CC_CAN_LINK_STATIC || ARCH_HAS_NOLIBC + select HEADERS_INSTALL + help + Enables support for building and running userspace selftests as part of kunit. + These tests should be statically linked and use kselftest.h or kselftest_harness.h + for status reporting. + endif # KUNIT diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile index 656f1fa35abcc6..dafa09bd4241c2 100644 --- a/lib/kunit/Makefile +++ b/lib/kunit/Makefile @@ -12,6 +12,8 @@ kunit-objs += test.o \ device.o \ platform.o +kunit-$(CONFIG_KUNIT_UAPI) += uapi.o + ifeq ($(CONFIG_KUNIT_DEBUGFS),y) kunit-objs += debugfs.o endif diff --git a/lib/kunit/uapi.c b/lib/kunit/uapi.c new file mode 100644 index 00000000000000..121146dda533b3 --- /dev/null +++ b/lib/kunit/uapi.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit Userspace testing API. + * + * Copyright (C) 2025, Linutronix GmbH. + * Author: Thomas Weißschuh + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define KSFT_PASS 0 +#define KSFT_FAIL 1 +#define KSFT_XFAIL 2 +#define KSFT_XPASS 3 +#define KSFT_SKIP 4 + +static struct vfsmount *kunit_uapi_mount_ramfs(void) +{ + struct file_system_type *type; + struct vfsmount *mnt; + + type = get_fs_type("ramfs"); + if (!type) + return ERR_PTR(-ENODEV); + + /* FIXME + * The mount setup is supposed to look like this: + * kunit_uapi_mount_ramfs() sets up a private mount, + * with nothing visible except the new tmpfs. + * Then each executable execution gets a new namespace on top of that + * on which it can mount whatever it needs. + * However I didn't manage to set this up, so keep everything simple + * for now and let somebody familiar with the VFS figure this out. + */ + + mnt = kern_mount(type); + put_filesystem(type); + + return mnt; +} + +static int kunit_uapi_write_file(struct vfsmount *mnt, const char *name, mode_t mode, + const u8 *data, size_t size) +{ + struct file *file; + ssize_t written; + + file = file_open_root_mnt(mnt, name, O_CREAT | O_WRONLY, mode); + if (IS_ERR(file)) + return PTR_ERR(file); + + written = kernel_write(file, data, size, NULL); + filp_close(file, NULL); + if (written != size) { + if (written >= 0) + return -ENOMEM; + return written; + } + + /* Flush delayed fput so exec can open the file read-only */ + flush_delayed_fput(); + + return 0; +} + +struct kunit_uapi_user_mode_thread_ctx { + const char *executable; + + /* Signals mnt, out, pwd and tgid */ + struct completion setup_done; + struct vfsmount *mnt; + struct file *out; + struct path pwd; + pid_t tgid; + + /* Valid after wait(tgid) */ + int exec_err; +}; + +static int kunit_uapi_user_mode_thread_init(void *data) +{ + struct kunit_uapi_user_mode_thread_ctx *ctx = data; + const char *const argv[] = { + ctx->executable, + NULL + }; + struct file *out[2]; + int err; + + err = create_pipe_files(out, 0); + if (err) + return err; + + /* stdin, use the *write* end to the pipe to have an unreadable input */ + err = replace_fd(0, out[1], 0); + if (err < 0) { + fput(out[0]); + fput(out[1]); + return err; + } + + /* stdout */ + err = replace_fd(1, out[1], 0); + if (err < 0) { + replace_fd(0, NULL, 0); + fput(out[0]); + fput(out[1]); + return err; + } + + /* stderr */ + err = replace_fd(2, out[1], 0); + if (err < 0) { + replace_fd(0, NULL, 0); + replace_fd(1, NULL, 0); + fput(out[0]); + fput(out[1]); + return err; + } + + fput(out[1]); + + ctx->out = out[0]; + ctx->tgid = current->tgid; + + set_fs_pwd(current->fs, &ctx->pwd); + kernel_sigaction(SIGKILL, SIG_DFL); + kernel_sigaction(SIGABRT, SIG_DFL); + + complete(&ctx->setup_done); + ctx->exec_err = kernel_execve(ctx->executable, argv, NULL); + if (!ctx->exec_err) + return 0; + do_exit(0); +} + +static size_t kunit_uapi_printk_subtest_lines(struct kunit *test, char *buf, size_t s) +{ + const char *ptr = buf, *newline; + size_t n; + + while (s) { + newline = strnchr(ptr, s, '\n'); + if (!newline) + break; + + n = newline - ptr + 1; + + kunit_log(KERN_INFO, test, KUNIT_SUBSUBTEST_INDENT "%.*s", (int)n, ptr); + ptr += n; + s -= n; + } + + memmove(buf, ptr, s); + + return s; +} + +static int kunit_uapi_forward_to_printk(struct kunit *test, struct file *output) +{ + /* + * printk() automatically adds a newline after each message. + * Therefore only fully accumulated lines can be forwarded. + * Each line needs to fit into the buffer below. + */ + char buf[512]; + size_t s = 0; + ssize_t n; + + while (1) { + n = kernel_read(output, buf + s, sizeof(buf) - s, NULL); + if (n <= 0) + return n; + s = kunit_uapi_printk_subtest_lines(test, buf, s + n); + } +} + +static void kunit_uapi_kill_pid(pid_t pid) +{ + struct pid *p; + + p = find_get_pid(pid); + kill_pid(p, SIGKILL, 1); + put_pid(p); +} + +static int kunit_uapi_run_executable_in_mount(struct kunit *test, const char *executable, + struct vfsmount *mnt) +{ + struct kunit_uapi_user_mode_thread_ctx ctx = { + .setup_done = COMPLETION_INITIALIZER_ONSTACK(ctx.setup_done), + .executable = executable, + .pwd = { + .mnt = mnt, + .dentry = mnt->mnt_root, + }, + }; + int forward_err, wait_err, ret; + pid_t pid; + + /* If SIGCHLD is ignored do_wait won't populate the status. */ + kernel_sigaction(SIGCHLD, SIG_DFL); + pid = user_mode_thread(kunit_uapi_user_mode_thread_init, &ctx, SIGCHLD); + if (pid < 0) { + kernel_sigaction(SIGCHLD, SIG_IGN); + return pid; + } + + wait_for_completion(&ctx.setup_done); + + forward_err = kunit_uapi_forward_to_printk(test, ctx.out); + if (forward_err) + kunit_uapi_kill_pid(ctx.tgid); + + wait_err = kernel_wait(ctx.tgid, &ret); + + /* Restore default kernel sig handler */ + kernel_sigaction(SIGCHLD, SIG_IGN); + + if (ctx.exec_err) + return ctx.exec_err; + if (forward_err) + return forward_err; + if (wait_err < 0) + return wait_err; + return ret; +} + +static int kunit_uapi_run_executable(struct kunit *test, const struct blob *executable) +{ + const char *exe_name = kbasename(executable->path); + struct vfsmount *mnt; + int err; + + mnt = kunit_uapi_mount_ramfs(); + if (IS_ERR(mnt)) + return PTR_ERR(mnt); + + err = kunit_uapi_write_file(mnt, exe_name, 0755, executable->data, blob_size(executable)); + + if (!err) + err = kunit_uapi_run_executable_in_mount(test, exe_name, mnt); + + kern_unmount(mnt); + + return err; +} + +void kunit_uapi_run_kselftest(struct kunit *test, const struct blob *executable) +{ + u8 exit_code, exit_signal; + int err; + + err = kunit_uapi_run_executable(test, executable); + if (err < 0) + KUNIT_FAIL(test, "Could not run test executable: %pe\n", ERR_PTR(err)); + + exit_code = err >> 8; + exit_signal = err & 0xff; + + if (exit_signal) + KUNIT_FAIL(test, "kselftest exited with signal: %d\n", exit_signal); + else if (exit_code == KSFT_PASS) + ; /* Noop */ + else if (exit_code == KSFT_FAIL) + KUNIT_FAIL(test, "kselftest exited with code KSFT_FAIL\n"); + else if (exit_code == KSFT_XPASS) + KUNIT_FAIL(test, "kselftest exited with code KSFT_XPASS\n"); + else if (exit_code == KSFT_XFAIL) + ; /* Noop */ + else if (exit_code == KSFT_SKIP) + kunit_mark_skipped(test, "kselftest exited with code KSFT_SKIP\n"); + else + KUNIT_FAIL(test, "kselftest exited with unknown exit code: %d\n", exit_code); +} +EXPORT_SYMBOL_GPL(kunit_uapi_run_kselftest); From 448a1a6af02713cfc1e4e9a17d8774c8f959f127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Jun 2025 09:38:20 +0200 Subject: [PATCH 14/16] kunit: uapi: Add example for UAPI tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the example to show how to run a userspace executable. Signed-off-by: Thomas Weißschuh Signed-off-by: Linux RISC-V bot --- lib/kunit/Makefile | 9 ++++++++- lib/kunit/kunit-example-test.c | 15 +++++++++++++++ lib/kunit/kunit-example-uapi.c | 22 ++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 lib/kunit/kunit-example-uapi.c diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile index dafa09bd4241c2..e406a31df1df83 100644 --- a/lib/kunit/Makefile +++ b/lib/kunit/Makefile @@ -30,4 +30,11 @@ obj-$(CONFIG_KUNIT_TEST) += string-stream-test.o obj-$(CONFIG_KUNIT_TEST) += assert_test.o endif -obj-$(CONFIG_KUNIT_EXAMPLE_TEST) += kunit-example-test.o +userprogs += kunit-example-uapi +kunit-example-uapi-userccflags := -static +kunit-example-uapi-nolibc := $(CONFIG_ARCH_HAS_NOLIBC) +blobs += kunit-example-uapi.blob.o + +obj-$(CONFIG_KUNIT_EXAMPLE_TEST) += kunit-example-mod.o +kunit-example-mod-y += kunit-example-test.o +kunit-example-mod-$(CONFIG_KUNIT_UAPI) += kunit-example-uapi.blob.o diff --git a/lib/kunit/kunit-example-test.c b/lib/kunit/kunit-example-test.c index 3056d6bc705d0a..b2681a6e047dfd 100644 --- a/lib/kunit/kunit-example-test.c +++ b/lib/kunit/kunit-example-test.c @@ -6,8 +6,11 @@ * Author: Brendan Higgins */ +#include + #include #include +#include /* * This is the most fundamental element of KUnit, the test case. A test case @@ -277,6 +280,17 @@ static void example_slow_test(struct kunit *test) KUNIT_EXPECT_EQ(test, 1 + 1, 2); } +/* + * This test shows the usage of UAPI tests. + */ +static void example_uapi_test(struct kunit *test) +{ + if (IS_ENABLED(CONFIG_KUNIT_UAPI)) + kunit_uapi_run_kselftest(test, BLOB(kunit_example_uapi)); + else + kunit_skip(test, "CONFIG_KUNIT_UAPI is not enabled"); +} + /* * Here we make a list of all the test cases we want to add to the test suite * below. @@ -297,6 +311,7 @@ static struct kunit_case example_test_cases[] = { KUNIT_CASE(example_priv_test), KUNIT_CASE_PARAM(example_params_test, example_gen_params), KUNIT_CASE_SLOW(example_slow_test), + KUNIT_CASE(example_uapi_test), {} }; diff --git a/lib/kunit/kunit-example-uapi.c b/lib/kunit/kunit-example-uapi.c new file mode 100644 index 00000000000000..4ce657050dd4a5 --- /dev/null +++ b/lib/kunit/kunit-example-uapi.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit Userspace example test. + * + * Copyright (C) 2025, Linutronix GmbH. + * Author: Thomas Weißschuh + * + * This is *userspace* code. + */ + +#include "../../tools/testing/selftests/kselftest.h" + +int main(void) +{ + ksft_print_header(); + ksft_set_plan(4); + ksft_test_result_pass("userspace test 1\n"); + ksft_test_result_pass("userspace test 2\n"); + ksft_test_result_skip("userspace test 3: some reason\n"); + ksft_test_result_pass("userspace test 4\n"); + ksft_finished(); +} From 67923e982a270293ff13721769796c7d3d6a68dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Jun 2025 09:38:21 +0200 Subject: [PATCH 15/16] kunit: uapi: Introduce preinit executable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UAPI selftests may expect a "normal" userspace environment. For example the normal kernel API pseudo-filesystems should be mounted. This could be done from kernel code but it is non-idiomatic. Add a preinit userspace executable which performs these setup steps before running the final test executable. This preinit executable is only ever run from the kernel. Give it access to autoconf.h and kconfig.h to adapt itself to the tested kernel. Signed-off-by: Thomas Weißschuh Signed-off-by: Linux RISC-V bot --- lib/kunit/Makefile | 9 +++++- lib/kunit/uapi-preinit.c | 63 ++++++++++++++++++++++++++++++++++++++++ lib/kunit/uapi.c | 11 +++++-- 3 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 lib/kunit/uapi-preinit.c diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile index e406a31df1df83..19493ec320c61e 100644 --- a/lib/kunit/Makefile +++ b/lib/kunit/Makefile @@ -12,7 +12,14 @@ kunit-objs += test.o \ device.o \ platform.o -kunit-$(CONFIG_KUNIT_UAPI) += uapi.o +userprogs += uapi-preinit +uapi-preinit-nolibc := $(CONFIG_ARCH_HAS_NOLIBC) +uapi-preinit-userccflags += -static \ + -include include/generated/autoconf.h \ + -include $(srctree)/tools/include/linux/kconfig.h +blobs += uapi-preinit.blob.o +uapi-preinit.blob-symbol := kunit_uapi_preinit +kunit-$(CONFIG_KUNIT_UAPI) += uapi.o uapi-preinit.blob.o ifeq ($(CONFIG_KUNIT_DEBUGFS),y) kunit-objs += debugfs.o diff --git a/lib/kunit/uapi-preinit.c b/lib/kunit/uapi-preinit.c new file mode 100644 index 00000000000000..81182039965a8c --- /dev/null +++ b/lib/kunit/uapi-preinit.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit Userspace environment setup. + * + * Copyright (C) 2025, Linutronix GmbH. + * Author: Thomas Weißschuh + * + * This is *userspace* code. + */ + +#include +#include + +#include "../../tools/testing/selftests/kselftest.h" + +static int setup_api_mount(const char *target, const char *fstype) +{ + int ret; + + ret = mkdir(target, 0755); + if (ret && errno != EEXIST) + return -errno; + + ret = mount("none", target, fstype, 0, NULL); + if (ret && errno != EBUSY) + return -errno; + + return 0; +} + +static void exit_failure(const char *stage, int err) +{ + /* If preinit fails synthesize a failed test report. */ + ksft_print_header(); + ksft_set_plan(1); + ksft_test_result_fail("Failed during test setup: %s: %s\n", stage, strerror(-err)); + ksft_finished(); +} + +int main(int argc, char **argv, char **envp) +{ + int ret; + + ret = setup_api_mount("/proc", "proc"); + if (ret) + exit_failure("mount /proc", ret); + + ret = setup_api_mount("/sys", "sysfs"); + if (ret) + exit_failure("mount /sys", ret); + + if (IS_ENABLED(CONFIG_DEVTMPFS)) { + ret = setup_api_mount("/dev", "devtmpfs"); + if (ret) + exit_failure("mount /dev", ret); + } + + ret = execve(argv[0], argv, envp); + if (ret) + exit_failure("execve", ret); + + return 0; +} diff --git a/lib/kunit/uapi.c b/lib/kunit/uapi.c index 121146dda533b3..bccc081a653850 100644 --- a/lib/kunit/uapi.c +++ b/lib/kunit/uapi.c @@ -139,7 +139,7 @@ static int kunit_uapi_user_mode_thread_init(void *data) kernel_sigaction(SIGABRT, SIG_DFL); complete(&ctx->setup_done); - ctx->exec_err = kernel_execve(ctx->executable, argv, NULL); + ctx->exec_err = kernel_execve(kbasename(BLOB(kunit_uapi_preinit)->path), argv, NULL); if (!ctx->exec_err) return 0; do_exit(0); @@ -239,6 +239,7 @@ static int kunit_uapi_run_executable_in_mount(struct kunit *test, const char *ex static int kunit_uapi_run_executable(struct kunit *test, const struct blob *executable) { + const struct blob *preinit = BLOB(kunit_uapi_preinit); const char *exe_name = kbasename(executable->path); struct vfsmount *mnt; int err; @@ -247,7 +248,13 @@ static int kunit_uapi_run_executable(struct kunit *test, const struct blob *exec if (IS_ERR(mnt)) return PTR_ERR(mnt); - err = kunit_uapi_write_file(mnt, exe_name, 0755, executable->data, blob_size(executable)); + err = kunit_uapi_write_file(mnt, kbasename(preinit->path), 0755, + preinit->data, + blob_size(preinit)); + + if (!err) + err = kunit_uapi_write_file(mnt, exe_name, 0755, + executable->data, blob_size(executable)); if (!err) err = kunit_uapi_run_executable_in_mount(test, exe_name, mnt); From 49d2c919656b432baf37c66e7f9e81de407aa305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Jun 2025 09:38:22 +0200 Subject: [PATCH 16/16] kunit: uapi: Validate usability of /proc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Show that the selftests are executed from a fairly "normal" userspace context. Signed-off-by: Thomas Weißschuh Signed-off-by: Linux RISC-V bot --- lib/kunit/kunit-example-uapi.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/kunit/kunit-example-uapi.c b/lib/kunit/kunit-example-uapi.c index 4ce657050dd4a5..5e7a0f3b68f182 100644 --- a/lib/kunit/kunit-example-uapi.c +++ b/lib/kunit/kunit-example-uapi.c @@ -8,13 +8,45 @@ * This is *userspace* code. */ +#include +#include +#include + #include "../../tools/testing/selftests/kselftest.h" +static void test_procfs(void) +{ + char buf[256]; + ssize_t r; + int fd; + + fd = open("/proc/self/comm", O_RDONLY); + if (fd == -1) { + ksft_test_result_fail("procfs: open() failed: %s\n", strerror(errno)); + return; + } + + r = read(fd, buf, sizeof(buf)); + close(fd); + + if (r == -1) { + ksft_test_result_fail("procfs: read() failed: %s\n", strerror(errno)); + return; + } + + if (r != 16 || strncmp("kunit-example-u\n", buf, 16) != 0) { + ksft_test_result_fail("procfs: incorrect comm\n"); + return; + } + + ksft_test_result_pass("procfs\n"); +} + int main(void) { ksft_print_header(); ksft_set_plan(4); - ksft_test_result_pass("userspace test 1\n"); + test_procfs(); ksft_test_result_pass("userspace test 2\n"); ksft_test_result_skip("userspace test 3: some reason\n"); ksft_test_result_pass("userspace test 4\n");