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/Documentation/kbuild/makefiles.rst b/Documentation/kbuild/makefiles.rst index 8aef3650c1f32b..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 @@ -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 ========================= @@ -974,6 +976,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 ------------------------------------------ @@ -1004,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/Makefile b/Makefile index 35e6e5240c61a8..b9aa1058321dab 100644 --- a/Makefile +++ b/Makefile @@ -1129,9 +1129,10 @@ ifneq ($(CONFIG_ARCH_VMLINUX_NEEDS_RELOCS),) 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)) +# 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)) # userspace programs are linked via the compiler, use the correct linker ifeq ($(CONFIG_CC_IS_CLANG)$(CONFIG_LD_IS_LLD),yy) 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/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/init/Kconfig b/init/Kconfig index af4c2f0854554b..6d10f2816e453d 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -87,6 +87,13 @@ 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) + +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/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/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 5aa51978e456ab..19493ec320c61e 100644 --- a/lib/kunit/Makefile +++ b/lib/kunit/Makefile @@ -12,12 +12,21 @@ kunit-objs += test.o \ device.o \ platform.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 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 @@ -28,4 +37,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..5e7a0f3b68f182 --- /dev/null +++ b/lib/kunit/kunit-example-uapi.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit Userspace example test. + * + * Copyright (C) 2025, Linutronix GmbH. + * Author: Thomas Weißschuh + * + * 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); + 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"); + ksft_finished(); +} 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 new file mode 100644 index 00000000000000..bccc081a653850 --- /dev/null +++ b/lib/kunit/uapi.c @@ -0,0 +1,294 @@ +// 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(kbasename(BLOB(kunit_uapi_preinit)->path), 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 struct blob *preinit = BLOB(kunit_uapi_preinit); + 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, 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); + + 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); 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/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] $@ 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, +}; 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 diff --git a/tools/testing/kunit/kunit_parser.py b/tools/testing/kunit/kunit_parser.py index c176487356e6c9..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) @@ -686,6 +688,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 bbba921e0eacb1..c25f52650837e8 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, 3) + 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/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', 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..cd9033c464792e --- /dev/null +++ b/tools/testing/kunit/test_data/test_is_test_passed-failure-nested.log @@ -0,0 +1,10 @@ +KTAP version 1 +1..2 + KTAP version 1 + 1..1 + ok 1 test 1 +not ok 1 subtest 1 + KTAP version 1 + 1..1 + not ok 1 test 1 +not ok 2 subtest 2 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