diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-02-12 12:13:01 -0800 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-02-12 12:13:01 -0800 |
| commit | 136114e0abf03005e182d75761ab694648e6d388 (patch) | |
| tree | 05c61b103fc9cb72a7cae99680a4b524347e9616 /lib | |
| parent | 4cff5c05e076d2ee4e34122aa956b84a2eaac587 (diff) | |
| parent | 0dddf20b4fd4afd59767acc144ad4da60259f21f (diff) | |
Merge tag 'mm-nonmm-stable-2026-02-12-10-48' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
Pull non-MM updates from Andrew Morton:
- "ocfs2: give ocfs2 the ability to reclaim suballocator free bg" saves
disk space by teaching ocfs2 to reclaim suballocator block group
space (Heming Zhao)
- "Add ARRAY_END(), and use it to fix off-by-one bugs" adds the
ARRAY_END() macro and uses it in various places (Alejandro Colomar)
- "vmcoreinfo: support VMCOREINFO_BYTES larger than PAGE_SIZE" makes
the vmcore code future-safe, if VMCOREINFO_BYTES ever exceeds the
page size (Pnina Feder)
- "kallsyms: Prevent invalid access when showing module buildid" cleans
up kallsyms code related to module buildid and fixes an invalid
access crash when printing backtraces (Petr Mladek)
- "Address page fault in ima_restore_measurement_list()" fixes a
kexec-related crash that can occur when booting the second-stage
kernel on x86 (Harshit Mogalapalli)
- "kho: ABI headers and Documentation updates" updates the kexec
handover ABI documentation (Mike Rapoport)
- "Align atomic storage" adds the __aligned attribute to atomic_t and
atomic64_t definitions to get natural alignment of both types on
csky, m68k, microblaze, nios2, openrisc and sh (Finn Thain)
- "kho: clean up page initialization logic" simplifies the page
initialization logic in kho_restore_page() (Pratyush Yadav)
- "Unload linux/kernel.h" moves several things out of kernel.h and into
more appropriate places (Yury Norov)
- "don't abuse task_struct.group_leader" removes the usage of
->group_leader when it is "obviously unnecessary" (Oleg Nesterov)
- "list private v2 & luo flb" adds some infrastructure improvements to
the live update orchestrator (Pasha Tatashin)
* tag 'mm-nonmm-stable-2026-02-12-10-48' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm: (107 commits)
watchdog/hardlockup: simplify perf event probe and remove per-cpu dependency
procfs: fix missing RCU protection when reading real_parent in do_task_stat()
watchdog/softlockup: fix sample ring index wrap in need_counting_irqs()
kcsan, compiler_types: avoid duplicate type issues in BPF Type Format
kho: fix doc for kho_restore_pages()
tests/liveupdate: add in-kernel liveupdate test
liveupdate: luo_flb: introduce File-Lifecycle-Bound global state
liveupdate: luo_file: Use private list
list: add kunit test for private list primitives
list: add primitives for private list manipulations
delayacct: fix uapi timespec64 definition
panic: add panic_force_cpu= parameter to redirect panic to a specific CPU
netclassid: use thread_group_leader(p) in update_classid_task()
RDMA/umem: don't abuse current->group_leader
drm/pan*: don't abuse current->group_leader
drm/amd: kill the outdated "Only the pthreads threading model is supported" checks
drm/amdgpu: don't abuse current->group_leader
android/binder: use same_thread_group(proc->tsk, current) in binder_mmap()
android/binder: don't abuse current->group_leader
kho: skip memoryless NUMA nodes when reserving scratch areas
...
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/Kconfig | 13 | ||||
| -rw-r--r-- | lib/Kconfig.debug | 118 | ||||
| -rw-r--r-- | lib/Makefile | 3 | ||||
| -rwxr-xr-x | lib/build_OID_registry | 26 | ||||
| -rw-r--r-- | lib/globtest.c | 167 | ||||
| -rw-r--r-- | lib/group_cpus.c | 271 | ||||
| -rw-r--r-- | lib/hexdump.c | 1 | ||||
| -rw-r--r-- | lib/kfifo.c | 2 | ||||
| -rw-r--r-- | lib/kstrtox.c | 4 | ||||
| -rw-r--r-- | lib/once.c | 2 | ||||
| -rw-r--r-- | lib/string_helpers.c | 1 | ||||
| -rw-r--r-- | lib/test_kho.c | 7 | ||||
| -rw-r--r-- | lib/test_uuid.c | 134 | ||||
| -rw-r--r-- | lib/tests/Makefile | 5 | ||||
| -rw-r--r-- | lib/tests/glob_kunit.c | 125 | ||||
| -rw-r--r-- | lib/tests/list-private-test.c | 76 | ||||
| -rw-r--r-- | lib/tests/liveupdate.c | 158 | ||||
| -rw-r--r-- | lib/tests/min_heap_kunit.c (renamed from lib/test_min_heap.c) | 147 | ||||
| -rw-r--r-- | lib/tests/uuid_kunit.c | 106 | ||||
| -rw-r--r-- | lib/uuid.c | 1 | ||||
| -rw-r--r-- | lib/vsprintf.c | 1 |
21 files changed, 873 insertions, 495 deletions
diff --git a/lib/Kconfig b/lib/Kconfig index 2923924bea78..0f2fb9610647 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -430,19 +430,6 @@ config GLOB are compiling an out-of tree driver which tells you that it depends on this. -config GLOB_SELFTEST - tristate "glob self-test on init" - depends on GLOB - help - This option enables a simple self-test of the glob_match - function on startup. It is primarily useful for people - working on the code to ensure they haven't introduced any - regressions. - - It only adds a little bit of code and slows kernel boot (or - module load) by a small amount, so you're welcome to play with - it, but you probably don't need it. - # # Netlink attribute parsing support is select'ed if needed # diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 64ef7e62eb8a..4e2dfbbd3d78 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1147,13 +1147,14 @@ config SOFTLOCKUP_DETECTOR_INTR_STORM the CPU stats and the interrupt counts during the "soft lockups". config BOOTPARAM_SOFTLOCKUP_PANIC - bool "Panic (Reboot) On Soft Lockups" + int "Panic (Reboot) On Soft Lockups" depends on SOFTLOCKUP_DETECTOR + default 0 help - Say Y here to enable the kernel to panic on "soft lockups", - which are bugs that cause the kernel to loop in kernel - mode for more than 20 seconds (configurable using the watchdog_thresh - sysctl), without giving other tasks a chance to run. + Set to a non-zero value N to enable the kernel to panic on "soft + lockups", which are bugs that cause the kernel to loop in kernel + mode for more than (N * 20 seconds) (configurable using the + watchdog_thresh sysctl), without giving other tasks a chance to run. The panic can be used in combination with panic_timeout, to cause the system to reboot automatically after a @@ -1161,7 +1162,7 @@ config BOOTPARAM_SOFTLOCKUP_PANIC high-availability systems that have uptime guarantees and where a lockup must be resolved ASAP. - Say N if unsure. + Say 0 if unsure. config HAVE_HARDLOCKUP_DETECTOR_BUDDY bool @@ -1310,7 +1311,7 @@ config BOOTPARAM_HUNG_TASK_PANIC high-availability systems that have uptime guarantees and where a hung tasks must be resolved ASAP. - Say N if unsure. + Say 0 if unsure. config DETECT_HUNG_TASK_BLOCKER bool "Dump Hung Tasks Blocker" @@ -1419,6 +1420,24 @@ config DEBUG_PREEMPT depending on workload as it triggers debugging routines for each this_cpu operation. It should only be used for debugging purposes. +config DEBUG_ATOMIC + bool "Debug atomic variables" + depends on DEBUG_KERNEL + help + If you say Y here then the kernel will add a runtime alignment check + to atomic accesses. Useful for architectures that do not have trap on + mis-aligned access. + + This option has potentially significant overhead. + +config DEBUG_ATOMIC_LARGEST_ALIGN + bool "Check alignment only up to __aligned_largest" + depends on DEBUG_ATOMIC + help + If you say Y here then the check for natural alignment of + atomic accesses will be constrained to the compiler's largest + alignment for scalar types. + menu "Lock Debugging (spinlocks, mutexes, etc...)" config LOCK_DEBUGGING_SUPPORT @@ -2337,16 +2356,6 @@ config TEST_LIST_SORT If unsure, say N. -config TEST_MIN_HEAP - tristate "Min heap test" - depends on DEBUG_KERNEL || m - help - Enable this to turn on min heap function tests. This test is - executed only once during system boot (so affects only boot time), - or at module load time. - - If unsure, say N. - config TEST_SORT tristate "Array-based sort test" if !KUNIT_ALL_TESTS depends on KUNIT @@ -2559,9 +2568,6 @@ config TEST_BITMAP If unsure, say N. -config TEST_UUID - tristate "Test functions located in the uuid module at runtime" - config TEST_XARRAY tristate "Test the XArray code at runtime" @@ -2845,6 +2851,20 @@ config LIST_KUNIT_TEST If unsure, say N. +config LIST_PRIVATE_KUNIT_TEST + tristate "KUnit Test for Kernel Private Linked-list structures" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + help + This builds the KUnit test for the private linked-list primitives + defined in include/linux/list_private.h. + + These primitives allow manipulation of list_head members that are + marked as private and require special accessors (ACCESS_PRIVATE) + to strip qualifiers or handle encapsulation. + + If unsure, say N. + config HASHTABLE_KUNIT_TEST tristate "KUnit Test for Kernel Hashtable structures" if !KUNIT_ALL_TESTS depends on KUNIT @@ -2884,6 +2904,29 @@ config CONTEXT_ANALYSIS_TEST If unsure, say N. +config LIVEUPDATE_TEST + bool "Live Update Kernel Test" + default n + depends on LIVEUPDATE + help + Enable a built-in kernel test module for the Live Update + Orchestrator. + + This module validates the File-Lifecycle-Bound subsystem by + registering a set of mock FLB objects with any real file handlers + that support live update (such as the memfd handler). + + When live update operations are performed, this test module will + output messages to the kernel log (dmesg), confirming that its + registration and various callback functions (preserve, retrieve, + finish, etc.) are being invoked correctly. + + This is a debugging and regression testing tool for developers + working on the Live Update subsystem. It should not be enabled in + production kernels. + + If unsure, say N + config CMDLINE_KUNIT_TEST tristate "KUnit test for cmdline API" if !KUNIT_ALL_TESTS depends on KUNIT @@ -2959,6 +3002,17 @@ config MEMCPY_KUNIT_TEST If unsure, say N. +config MIN_HEAP_KUNIT_TEST + tristate "Min heap test" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + help + This option enables the KUnit test suite for the min heap library + which provides functions for creating and managing min heaps. + The test suite checks the functionality of the min heap library. + + If unsure, say N + config IS_SIGNED_TYPE_KUNIT_TEST tristate "Test is_signed_type() macro" if !KUNIT_ALL_TESTS depends on KUNIT @@ -3364,6 +3418,17 @@ config RATELIMIT_KUNIT_TEST If unsure, say N. +config UUID_KUNIT_TEST + tristate "KUnit test for UUID" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + help + This option enables the KUnit test suite for the uuid library, + which provides functions for generating and parsing UUID and GUID. + The test suite checks parsing of UUID and GUID strings. + + If unsure, say N. + config INT_POW_KUNIT_TEST tristate "Integer exponentiation (int_pow) test" if !KUNIT_ALL_TESTS depends on KUNIT @@ -3433,6 +3498,19 @@ config PRIME_NUMBERS_KUNIT_TEST If unsure, say N +config GLOB_KUNIT_TEST + tristate "Glob matching test" if !KUNIT_ALL_TESTS + depends on GLOB + depends on KUNIT + default KUNIT_ALL_TESTS + help + Enable this option to test the glob functions at runtime. + + This test suite verifies the correctness of glob_match() across various + scenarios, including edge cases. + + If unsure, say N + endif # RUNTIME_TESTING_MENU config ARCH_USE_MEMTEST diff --git a/lib/Makefile b/lib/Makefile index 22d8742bba57..1b9ee167517f 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -77,7 +77,6 @@ obj-$(CONFIG_TEST_UBSAN) += test_ubsan.o CFLAGS_test_ubsan.o += $(call cc-disable-warning, unused-but-set-variable) UBSAN_SANITIZE_test_ubsan.o := y obj-$(CONFIG_TEST_KSTRTOX) += test-kstrtox.o -obj-$(CONFIG_TEST_MIN_HEAP) += test_min_heap.o obj-$(CONFIG_TEST_LKM) += test_module.o obj-$(CONFIG_TEST_VMALLOC) += test_vmalloc.o obj-$(CONFIG_TEST_RHASHTABLE) += test_rhashtable.o @@ -91,7 +90,6 @@ ifeq ($(CONFIG_CC_IS_CLANG)$(CONFIG_KASAN),yy) GCOV_PROFILE_test_bitmap.o := n endif -obj-$(CONFIG_TEST_UUID) += test_uuid.o obj-$(CONFIG_TEST_XARRAY) += test_xarray.o obj-$(CONFIG_TEST_MAPLE_TREE) += test_maple_tree.o obj-$(CONFIG_TEST_PARMAN) += test_parman.o @@ -228,7 +226,6 @@ obj-$(CONFIG_CLOSURES) += closure.o obj-$(CONFIG_DQL) += dynamic_queue_limits.o obj-$(CONFIG_GLOB) += glob.o -obj-$(CONFIG_GLOB_SELFTEST) += globtest.o obj-$(CONFIG_DIMLIB) += dim/ obj-$(CONFIG_SIGNATURE) += digsig.o diff --git a/lib/build_OID_registry b/lib/build_OID_registry index 8267e8d71338..30493ac190c0 100755 --- a/lib/build_OID_registry +++ b/lib/build_OID_registry @@ -60,10 +60,12 @@ for (my $i = 0; $i <= $#names; $i++) { # Determine the encoded length of this OID my $size = $#components; for (my $loop = 2; $loop <= $#components; $loop++) { - my $c = $components[$loop]; + $ENV{'BC_LINE_LENGTH'} = "0"; + my $c = `echo "ibase=10; obase=2; $components[$loop]" | bc`; + chomp($c); # We will base128 encode the number - my $tmp = ($c == 0) ? 0 : int(log($c)/log(2)); + my $tmp = length($c) - 1; $tmp = int($tmp / 7); $size += $tmp; } @@ -100,16 +102,24 @@ for (my $i = 0; $i <= $#names; $i++) { push @octets, $components[0] * 40 + $components[1]; for (my $loop = 2; $loop <= $#components; $loop++) { - my $c = $components[$loop]; + # get the base 2 representation of the component + $ENV{'BC_LINE_LENGTH'} = "0"; + my $c = `echo "ibase=10; obase=2; $components[$loop]" | bc`; + chomp($c); - # Base128 encode the number - my $tmp = ($c == 0) ? 0 : int(log($c)/log(2)); + my $tmp = length($c) - 1; $tmp = int($tmp / 7); - for (; $tmp > 0; $tmp--) { - push @octets, (($c >> $tmp * 7) & 0x7f) | 0x80; + # zero pad upto length multiple of 7 + $c = substr("0000000", 0, ($tmp + 1) * 7 - length($c)).$c; + + # Base128 encode the number + for (my $j = 0; $j < $tmp; $j++) { + my $b = oct("0b".substr($c, $j * 7, 7)); + + push @octets, $b | 0x80; } - push @octets, $c & 0x7f; + push @octets, oct("0b".substr($c, $tmp * 7, 7)); } push @encoded_oids, \@octets; diff --git a/lib/globtest.c b/lib/globtest.c deleted file mode 100644 index d8e97d43b905..000000000000 --- a/lib/globtest.c +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Extracted fronm glob.c - */ - -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/glob.h> -#include <linux/printk.h> - -/* Boot with "glob.verbose=1" to show successful tests, too */ -static bool verbose = false; -module_param(verbose, bool, 0); - -struct glob_test { - char const *pat, *str; - bool expected; -}; - -static bool __pure __init test(char const *pat, char const *str, bool expected) -{ - bool match = glob_match(pat, str); - bool success = match == expected; - - /* Can't get string literals into a particular section, so... */ - static char const msg_error[] __initconst = - KERN_ERR "glob: \"%s\" vs. \"%s\": %s *** ERROR ***\n"; - static char const msg_ok[] __initconst = - KERN_DEBUG "glob: \"%s\" vs. \"%s\": %s OK\n"; - static char const mismatch[] __initconst = "mismatch"; - char const *message; - - if (!success) - message = msg_error; - else if (verbose) - message = msg_ok; - else - return success; - - printk(message, pat, str, mismatch + 3*match); - return success; -} - -/* - * The tests are all jammed together in one array to make it simpler - * to place that array in the .init.rodata section. The obvious - * "array of structures containing char *" has no way to force the - * pointed-to strings to be in a particular section. - * - * Anyway, a test consists of: - * 1. Expected glob_match result: '1' or '0'. - * 2. Pattern to match: null-terminated string - * 3. String to match against: null-terminated string - * - * The list of tests is terminated with a final '\0' instead of - * a glob_match result character. - */ -static char const glob_tests[] __initconst = - /* Some basic tests */ - "1" "a\0" "a\0" - "0" "a\0" "b\0" - "0" "a\0" "aa\0" - "0" "a\0" "\0" - "1" "\0" "\0" - "0" "\0" "a\0" - /* Simple character class tests */ - "1" "[a]\0" "a\0" - "0" "[a]\0" "b\0" - "0" "[!a]\0" "a\0" - "1" "[!a]\0" "b\0" - "1" "[ab]\0" "a\0" - "1" "[ab]\0" "b\0" - "0" "[ab]\0" "c\0" - "1" "[!ab]\0" "c\0" - "1" "[a-c]\0" "b\0" - "0" "[a-c]\0" "d\0" - /* Corner cases in character class parsing */ - "1" "[a-c-e-g]\0" "-\0" - "0" "[a-c-e-g]\0" "d\0" - "1" "[a-c-e-g]\0" "f\0" - "1" "[]a-ceg-ik[]\0" "a\0" - "1" "[]a-ceg-ik[]\0" "]\0" - "1" "[]a-ceg-ik[]\0" "[\0" - "1" "[]a-ceg-ik[]\0" "h\0" - "0" "[]a-ceg-ik[]\0" "f\0" - "0" "[!]a-ceg-ik[]\0" "h\0" - "0" "[!]a-ceg-ik[]\0" "]\0" - "1" "[!]a-ceg-ik[]\0" "f\0" - /* Simple wild cards */ - "1" "?\0" "a\0" - "0" "?\0" "aa\0" - "0" "??\0" "a\0" - "1" "?x?\0" "axb\0" - "0" "?x?\0" "abx\0" - "0" "?x?\0" "xab\0" - /* Asterisk wild cards (backtracking) */ - "0" "*??\0" "a\0" - "1" "*??\0" "ab\0" - "1" "*??\0" "abc\0" - "1" "*??\0" "abcd\0" - "0" "??*\0" "a\0" - "1" "??*\0" "ab\0" - "1" "??*\0" "abc\0" - "1" "??*\0" "abcd\0" - "0" "?*?\0" "a\0" - "1" "?*?\0" "ab\0" - "1" "?*?\0" "abc\0" - "1" "?*?\0" "abcd\0" - "1" "*b\0" "b\0" - "1" "*b\0" "ab\0" - "0" "*b\0" "ba\0" - "1" "*b\0" "bb\0" - "1" "*b\0" "abb\0" - "1" "*b\0" "bab\0" - "1" "*bc\0" "abbc\0" - "1" "*bc\0" "bc\0" - "1" "*bc\0" "bbc\0" - "1" "*bc\0" "bcbc\0" - /* Multiple asterisks (complex backtracking) */ - "1" "*ac*\0" "abacadaeafag\0" - "1" "*ac*ae*ag*\0" "abacadaeafag\0" - "1" "*a*b*[bc]*[ef]*g*\0" "abacadaeafag\0" - "0" "*a*b*[ef]*[cd]*g*\0" "abacadaeafag\0" - "1" "*abcd*\0" "abcabcabcabcdefg\0" - "1" "*ab*cd*\0" "abcabcabcabcdefg\0" - "1" "*abcd*abcdef*\0" "abcabcdabcdeabcdefg\0" - "0" "*abcd*\0" "abcabcabcabcefg\0" - "0" "*ab*cd*\0" "abcabcabcabcefg\0"; - -static int __init glob_init(void) -{ - unsigned successes = 0; - unsigned n = 0; - char const *p = glob_tests; - static char const message[] __initconst = - KERN_INFO "glob: %u self-tests passed, %u failed\n"; - - /* - * Tests are jammed together in a string. The first byte is '1' - * or '0' to indicate the expected outcome, or '\0' to indicate the - * end of the tests. Then come two null-terminated strings: the - * pattern and the string to match it against. - */ - while (*p) { - bool expected = *p++ & 1; - char const *pat = p; - - p += strlen(p) + 1; - successes += test(pat, p, expected); - p += strlen(p) + 1; - n++; - } - - n -= successes; - printk(message, successes, n); - - /* What's the errno for "kernel bug detected"? Guess... */ - return n ? -ECANCELED : 0; -} - -/* We need a dummy exit function to allow unload */ -static void __exit glob_fini(void) { } - -module_init(glob_init); -module_exit(glob_fini); - -MODULE_DESCRIPTION("glob(7) matching tests"); -MODULE_LICENSE("Dual MIT/GPL"); diff --git a/lib/group_cpus.c b/lib/group_cpus.c index 6d08ac05f371..a93df70919df 100644 --- a/lib/group_cpus.c +++ b/lib/group_cpus.c @@ -114,48 +114,15 @@ static int ncpus_cmp_func(const void *l, const void *r) return ln->ncpus - rn->ncpus; } -/* - * Allocate group number for each node, so that for each node: - * - * 1) the allocated number is >= 1 - * - * 2) the allocated number is <= active CPU number of this node - * - * The actual allocated total groups may be less than @numgrps when - * active total CPU number is less than @numgrps. - * - * Active CPUs means the CPUs in '@cpu_mask AND @node_to_cpumask[]' - * for each node. - */ -static void alloc_nodes_groups(unsigned int numgrps, - cpumask_var_t *node_to_cpumask, - const struct cpumask *cpu_mask, - const nodemask_t nodemsk, - struct cpumask *nmsk, - struct node_groups *node_groups) +static void alloc_groups_to_nodes(unsigned int numgrps, + unsigned int numcpus, + struct node_groups *node_groups, + unsigned int num_nodes) { - unsigned n, remaining_ncpus = 0; - - for (n = 0; n < nr_node_ids; n++) { - node_groups[n].id = n; - node_groups[n].ncpus = UINT_MAX; - } - - for_each_node_mask(n, nodemsk) { - unsigned ncpus; - - cpumask_and(nmsk, cpu_mask, node_to_cpumask[n]); - ncpus = cpumask_weight(nmsk); - - if (!ncpus) - continue; - remaining_ncpus += ncpus; - node_groups[n].ncpus = ncpus; - } + unsigned int n, remaining_ncpus = numcpus; + unsigned int ngroups, ncpus; - numgrps = min_t(unsigned, remaining_ncpus, numgrps); - - sort(node_groups, nr_node_ids, sizeof(node_groups[0]), + sort(node_groups, num_nodes, sizeof(node_groups[0]), ncpus_cmp_func, NULL); /* @@ -226,9 +193,8 @@ static void alloc_nodes_groups(unsigned int numgrps, * finally for each node X: grps(X) <= ncpu(X). * */ - for (n = 0; n < nr_node_ids; n++) { - unsigned ngroups, ncpus; + for (n = 0; n < num_nodes; n++) { if (node_groups[n].ncpus == UINT_MAX) continue; @@ -246,12 +212,201 @@ static void alloc_nodes_groups(unsigned int numgrps, } } +/* + * Allocate group number for each node, so that for each node: + * + * 1) the allocated number is >= 1 + * + * 2) the allocated number is <= active CPU number of this node + * + * The actual allocated total groups may be less than @numgrps when + * active total CPU number is less than @numgrps. + * + * Active CPUs means the CPUs in '@cpu_mask AND @node_to_cpumask[]' + * for each node. + */ +static void alloc_nodes_groups(unsigned int numgrps, + cpumask_var_t *node_to_cpumask, + const struct cpumask *cpu_mask, + const nodemask_t nodemsk, + struct cpumask *nmsk, + struct node_groups *node_groups) +{ + unsigned int n, numcpus = 0; + + for (n = 0; n < nr_node_ids; n++) { + node_groups[n].id = n; + node_groups[n].ncpus = UINT_MAX; + } + + for_each_node_mask(n, nodemsk) { + unsigned int ncpus; + + cpumask_and(nmsk, cpu_mask, node_to_cpumask[n]); + ncpus = cpumask_weight(nmsk); + + if (!ncpus) + continue; + numcpus += ncpus; + node_groups[n].ncpus = ncpus; + } + + numgrps = min_t(unsigned int, numcpus, numgrps); + alloc_groups_to_nodes(numgrps, numcpus, node_groups, nr_node_ids); +} + +static void assign_cpus_to_groups(unsigned int ncpus, + struct cpumask *nmsk, + struct node_groups *nv, + struct cpumask *masks, + unsigned int *curgrp, + unsigned int last_grp) +{ + unsigned int v, cpus_per_grp, extra_grps; + /* Account for rounding errors */ + extra_grps = ncpus - nv->ngroups * (ncpus / nv->ngroups); + + /* Spread allocated groups on CPUs of the current node */ + for (v = 0; v < nv->ngroups; v++, *curgrp += 1) { + cpus_per_grp = ncpus / nv->ngroups; + + /* Account for extra groups to compensate rounding errors */ + if (extra_grps) { + cpus_per_grp++; + --extra_grps; + } + + /* + * wrapping has to be considered given 'startgrp' + * may start anywhere + */ + if (*curgrp >= last_grp) + *curgrp = 0; + grp_spread_init_one(&masks[*curgrp], nmsk, cpus_per_grp); + } +} + +static int alloc_cluster_groups(unsigned int ncpus, + unsigned int ngroups, + struct cpumask *node_cpumask, + cpumask_var_t msk, + const struct cpumask ***clusters_ptr, + struct node_groups **cluster_groups_ptr) +{ + unsigned int ncluster = 0; + unsigned int cpu, nc, n; + const struct cpumask *cluster_mask; + const struct cpumask **clusters; + struct node_groups *cluster_groups; + + cpumask_copy(msk, node_cpumask); + + /* Probe how many clusters in this node. */ + while (1) { + cpu = cpumask_first(msk); + if (cpu >= nr_cpu_ids) + break; + + cluster_mask = topology_cluster_cpumask(cpu); + if (!cpumask_weight(cluster_mask)) + goto no_cluster; + /* Clean out CPUs on the same cluster. */ + cpumask_andnot(msk, msk, cluster_mask); + ncluster++; + } + + /* If ngroups < ncluster, cross cluster is inevitable, skip. */ + if (ncluster == 0 || ncluster > ngroups) + goto no_cluster; + + /* Allocate memory based on cluster number. */ + clusters = kcalloc(ncluster, sizeof(struct cpumask *), GFP_KERNEL); + if (!clusters) + goto no_cluster; + cluster_groups = kcalloc(ncluster, sizeof(struct node_groups), GFP_KERNEL); + if (!cluster_groups) + goto fail_cluster_groups; + + /* Filling cluster info for later process. */ + cpumask_copy(msk, node_cpumask); + for (n = 0; n < ncluster; n++) { + cpu = cpumask_first(msk); + cluster_mask = topology_cluster_cpumask(cpu); + nc = cpumask_weight_and(cluster_mask, node_cpumask); + clusters[n] = cluster_mask; + cluster_groups[n].id = n; + cluster_groups[n].ncpus = nc; + cpumask_andnot(msk, msk, cluster_mask); + } + + alloc_groups_to_nodes(ngroups, ncpus, cluster_groups, ncluster); + + *clusters_ptr = clusters; + *cluster_groups_ptr = cluster_groups; + return ncluster; + + fail_cluster_groups: + kfree(clusters); + no_cluster: + return 0; +} + +/* + * Try group CPUs evenly for cluster locality within a NUMA node. + * + * Return: true if success, false otherwise. + */ +static bool __try_group_cluster_cpus(unsigned int ncpus, + unsigned int ngroups, + struct cpumask *node_cpumask, + struct cpumask *masks, + unsigned int *curgrp, + unsigned int last_grp) +{ + struct node_groups *cluster_groups; + const struct cpumask **clusters; + unsigned int ncluster; + bool ret = false; + cpumask_var_t nmsk; + unsigned int i, nc; + + if (!zalloc_cpumask_var(&nmsk, GFP_KERNEL)) + goto fail_nmsk_alloc; + + ncluster = alloc_cluster_groups(ncpus, ngroups, node_cpumask, nmsk, + &clusters, &cluster_groups); + + if (ncluster == 0) + goto fail_no_clusters; + + for (i = 0; i < ncluster; i++) { + struct node_groups *nv = &cluster_groups[i]; + + /* Get the cpus on this cluster. */ + cpumask_and(nmsk, node_cpumask, clusters[nv->id]); + nc = cpumask_weight(nmsk); + if (!nc) + continue; + WARN_ON_ONCE(nv->ngroups > nc); + + assign_cpus_to_groups(nc, nmsk, nv, masks, curgrp, last_grp); + } + + ret = true; + kfree(cluster_groups); + kfree(clusters); + fail_no_clusters: + free_cpumask_var(nmsk); + fail_nmsk_alloc: + return ret; +} + static int __group_cpus_evenly(unsigned int startgrp, unsigned int numgrps, cpumask_var_t *node_to_cpumask, const struct cpumask *cpu_mask, struct cpumask *nmsk, struct cpumask *masks) { - unsigned int i, n, nodes, cpus_per_grp, extra_grps, done = 0; + unsigned int i, n, nodes, done = 0; unsigned int last_grp = numgrps; unsigned int curgrp = startgrp; nodemask_t nodemsk = NODE_MASK_NONE; @@ -287,7 +442,7 @@ static int __group_cpus_evenly(unsigned int startgrp, unsigned int numgrps, alloc_nodes_groups(numgrps, node_to_cpumask, cpu_mask, nodemsk, nmsk, node_groups); for (i = 0; i < nr_node_ids; i++) { - unsigned int ncpus, v; + unsigned int ncpus; struct node_groups *nv = &node_groups[i]; if (nv->ngroups == UINT_MAX) @@ -301,28 +456,14 @@ static int __group_cpus_evenly(unsigned int startgrp, unsigned int numgrps, WARN_ON_ONCE(nv->ngroups > ncpus); - /* Account for rounding errors */ - extra_grps = ncpus - nv->ngroups * (ncpus / nv->ngroups); - - /* Spread allocated groups on CPUs of the current node */ - for (v = 0; v < nv->ngroups; v++, curgrp++) { - cpus_per_grp = ncpus / nv->ngroups; - - /* Account for extra groups to compensate rounding errors */ - if (extra_grps) { - cpus_per_grp++; - --extra_grps; - } - - /* - * wrapping has to be considered given 'startgrp' - * may start anywhere - */ - if (curgrp >= last_grp) - curgrp = 0; - grp_spread_init_one(&masks[curgrp], nmsk, - cpus_per_grp); + if (__try_group_cluster_cpus(ncpus, nv->ngroups, nmsk, + masks, &curgrp, last_grp)) { + done += nv->ngroups; + continue; } + + assign_cpus_to_groups(ncpus, nmsk, nv, masks, &curgrp, + last_grp); done += nv->ngroups; } kfree(node_groups); diff --git a/lib/hexdump.c b/lib/hexdump.c index c3db7c3a7643..2e5cd8c24769 100644 --- a/lib/hexdump.c +++ b/lib/hexdump.c @@ -6,6 +6,7 @@ #include <linux/types.h> #include <linux/ctype.h> #include <linux/errno.h> +#include <linux/hex.h> #include <linux/kernel.h> #include <linux/minmax.h> #include <linux/export.h> diff --git a/lib/kfifo.c b/lib/kfifo.c index 525e66f8294c..2633f9cc336c 100644 --- a/lib/kfifo.c +++ b/lib/kfifo.c @@ -41,7 +41,7 @@ int __kfifo_alloc_node(struct __kfifo *fifo, unsigned int size, return -EINVAL; } - fifo->data = kmalloc_array_node(esize, size, gfp_mask, node); + fifo->data = kmalloc_array_node(size, esize, gfp_mask, node); if (!fifo->data) { fifo->mask = 0; diff --git a/lib/kstrtox.c b/lib/kstrtox.c index bdde40cd69d7..97be2a39f537 100644 --- a/lib/kstrtox.c +++ b/lib/kstrtox.c @@ -340,8 +340,8 @@ EXPORT_SYMBOL(kstrtos8); * @s: input string * @res: result * - * This routine returns 0 iff the first character is one of 'YyTt1NnFf0', or - * [oO][NnFf] for "on" and "off". Otherwise it will return -EINVAL. Value + * This routine returns 0 iff the first character is one of 'EeYyTt1DdNnFf0', + * or [oO][NnFf] for "on" and "off". Otherwise it will return -EINVAL. Value * pointed to by res is updated upon finding a match. */ noinline diff --git a/lib/once.c b/lib/once.c index 2c306f0e891e..8557eb489f34 100644 --- a/lib/once.c +++ b/lib/once.c @@ -93,6 +93,6 @@ void __do_once_sleepable_done(bool *done, struct static_key_true *once_key, { *done = true; mutex_unlock(&once_mutex); - once_disable_jump(once_key, mod); + static_branch_disable(once_key); } EXPORT_SYMBOL(__do_once_sleepable_done); diff --git a/lib/string_helpers.c b/lib/string_helpers.c index ffb8ead6d4cd..8cb6f66c9c2b 100644 --- a/lib/string_helpers.c +++ b/lib/string_helpers.c @@ -13,6 +13,7 @@ #include <linux/device.h> #include <linux/errno.h> #include <linux/fs.h> +#include <linux/hex.h> #include <linux/limits.h> #include <linux/mm.h> #include <linux/slab.h> diff --git a/lib/test_kho.c b/lib/test_kho.c index 47de56280795..a20fafaf9846 100644 --- a/lib/test_kho.c +++ b/lib/test_kho.c @@ -19,6 +19,7 @@ #include <linux/printk.h> #include <linux/vmalloc.h> #include <linux/kexec_handover.h> +#include <linux/kho/abi/kexec_handover.h> #include <net/checksum.h> @@ -339,11 +340,15 @@ module_init(kho_test_init); static void kho_test_cleanup(void) { + /* unpreserve and free the data stored in folios */ + kho_test_unpreserve_data(&kho_test_state); for (int i = 0; i < kho_test_state.nr_folios; i++) folio_put(kho_test_state.folios[i]); kvfree(kho_test_state.folios); - vfree(kho_test_state.folios_info); + + /* Unpreserve and release the FDT folio */ + kho_unpreserve_folio(kho_test_state.fdt); folio_put(kho_test_state.fdt); } diff --git a/lib/test_uuid.c b/lib/test_uuid.c deleted file mode 100644 index 0124fad5d72c..000000000000 --- a/lib/test_uuid.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Test cases for lib/uuid.c module. - */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include <linux/init.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/string.h> -#include <linux/uuid.h> - -struct test_uuid_data { - const char *uuid; - guid_t le; - uuid_t be; -}; - -static const struct test_uuid_data test_uuid_test_data[] = { - { - .uuid = "c33f4995-3701-450e-9fbf-206a2e98e576", - .le = GUID_INIT(0xc33f4995, 0x3701, 0x450e, 0x9f, 0xbf, 0x20, 0x6a, 0x2e, 0x98, 0xe5, 0x76), - .be = UUID_INIT(0xc33f4995, 0x3701, 0x450e, 0x9f, 0xbf, 0x20, 0x6a, 0x2e, 0x98, 0xe5, 0x76), - }, - { - .uuid = "64b4371c-77c1-48f9-8221-29f054fc023b", - .le = GUID_INIT(0x64b4371c, 0x77c1, 0x48f9, 0x82, 0x21, 0x29, 0xf0, 0x54, 0xfc, 0x02, 0x3b), - .be = UUID_INIT(0x64b4371c, 0x77c1, 0x48f9, 0x82, 0x21, 0x29, 0xf0, 0x54, 0xfc, 0x02, 0x3b), - }, - { - .uuid = "0cb4ddff-a545-4401-9d06-688af53e7f84", - .le = GUID_INIT(0x0cb4ddff, 0xa545, 0x4401, 0x9d, 0x06, 0x68, 0x8a, 0xf5, 0x3e, 0x7f, 0x84), - .be = UUID_INIT(0x0cb4ddff, 0xa545, 0x4401, 0x9d, 0x06, 0x68, 0x8a, 0xf5, 0x3e, 0x7f, 0x84), - }, -}; - -static const char * const test_uuid_wrong_data[] = { - "c33f4995-3701-450e-9fbf206a2e98e576 ", /* no hyphen(s) */ - "64b4371c-77c1-48f9-8221-29f054XX023b", /* invalid character(s) */ - "0cb4ddff-a545-4401-9d06-688af53e", /* not enough data */ -}; - -static unsigned total_tests __initdata; -static unsigned failed_tests __initdata; - -static void __init test_uuid_failed(const char *prefix, bool wrong, bool be, - const char *data, const char *actual) -{ - pr_err("%s test #%u %s %s data: '%s'\n", - prefix, - total_tests, - wrong ? "passed on wrong" : "failed on", - be ? "BE" : "LE", - data); - if (actual && *actual) - pr_err("%s test #%u actual data: '%s'\n", - prefix, - total_tests, - actual); - failed_tests++; -} - -static void __init test_uuid_test(const struct test_uuid_data *data) -{ - guid_t le; - uuid_t be; - char buf[48]; - - /* LE */ - total_tests++; - if (guid_parse(data->uuid, &le)) - test_uuid_failed("conversion", false, false, data->uuid, NULL); - - total_tests++; - if (!guid_equal(&data->le, &le)) { - sprintf(buf, "%pUl", &le); - test_uuid_failed("cmp", false, false, data->uuid, buf); - } - - /* BE */ - total_tests++; - if (uuid_parse(data->uuid, &be)) - test_uuid_failed("conversion", false, true, data->uuid, NULL); - - total_tests++; - if (!uuid_equal(&data->be, &be)) { - sprintf(buf, "%pUb", &be); - test_uuid_failed("cmp", false, true, data->uuid, buf); - } -} - -static void __init test_uuid_wrong(const char *data) -{ - guid_t le; - uuid_t be; - - /* LE */ - total_tests++; - if (!guid_parse(data, &le)) - test_uuid_failed("negative", true, false, data, NULL); - - /* BE */ - total_tests++; - if (!uuid_parse(data, &be)) - test_uuid_failed("negative", true, true, data, NULL); -} - -static int __init test_uuid_init(void) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(test_uuid_test_data); i++) - test_uuid_test(&test_uuid_test_data[i]); - - for (i = 0; i < ARRAY_SIZE(test_uuid_wrong_data); i++) - test_uuid_wrong(test_uuid_wrong_data[i]); - - if (failed_tests == 0) - pr_info("all %u tests passed\n", total_tests); - else - pr_err("failed %u out of %u tests\n", failed_tests, total_tests); - - return failed_tests ? -EINVAL : 0; -} -module_init(test_uuid_init); - -static void __exit test_uuid_exit(void) -{ - /* do nothing */ -} -module_exit(test_uuid_exit); - -MODULE_AUTHOR("Andy Shevchenko <andriy.shevchenko@linux.intel.com>"); -MODULE_DESCRIPTION("Test cases for lib/uuid.c module"); -MODULE_LICENSE("Dual BSD/GPL"); diff --git a/lib/tests/Makefile b/lib/tests/Makefile index 0f24048f3684..05f74edbc62b 100644 --- a/lib/tests/Makefile +++ b/lib/tests/Makefile @@ -20,20 +20,24 @@ CFLAGS_fortify_kunit.o += $(DISABLE_STRUCTLEAK_PLUGIN) obj-$(CONFIG_FORTIFY_KUNIT_TEST) += fortify_kunit.o CFLAGS_test_fprobe.o += $(CC_FLAGS_FTRACE) obj-$(CONFIG_FPROBE_SANITY_TEST) += test_fprobe.o +obj-$(CONFIG_GLOB_KUNIT_TEST) += glob_kunit.o obj-$(CONFIG_HASHTABLE_KUNIT_TEST) += hashtable_test.o obj-$(CONFIG_HASH_KUNIT_TEST) += test_hash.o obj-$(CONFIG_TEST_IOV_ITER) += kunit_iov_iter.o obj-$(CONFIG_IS_SIGNED_TYPE_KUNIT_TEST) += is_signed_type_kunit.o obj-$(CONFIG_KPROBES_SANITY_TEST) += test_kprobes.o obj-$(CONFIG_LIST_KUNIT_TEST) += list-test.o +obj-$(CONFIG_LIST_PRIVATE_KUNIT_TEST) += list-private-test.o obj-$(CONFIG_KFIFO_KUNIT_TEST) += kfifo_kunit.o obj-$(CONFIG_TEST_LIST_SORT) += test_list_sort.o obj-$(CONFIG_LINEAR_RANGES_TEST) += test_linear_ranges.o +obj-$(CONFIG_LIVEUPDATE_TEST) += liveupdate.o CFLAGS_longest_symbol_kunit.o += $(call cc-disable-warning, missing-prototypes) obj-$(CONFIG_LONGEST_SYM_KUNIT_TEST) += longest_symbol_kunit.o obj-$(CONFIG_MEMCPY_KUNIT_TEST) += memcpy_kunit.o +obj-$(CONFIG_MIN_HEAP_KUNIT_TEST) += min_heap_kunit.o CFLAGS_overflow_kunit.o = $(call cc-disable-warning, tautological-constant-out-of-range-compare) obj-$(CONFIG_OVERFLOW_KUNIT_TEST) += overflow_kunit.o obj-$(CONFIG_PRINTF_KUNIT_TEST) += printf_kunit.o @@ -50,5 +54,6 @@ obj-$(CONFIG_STRING_HELPERS_KUNIT_TEST) += string_helpers_kunit.o obj-$(CONFIG_USERCOPY_KUNIT_TEST) += usercopy_kunit.o obj-$(CONFIG_UTIL_MACROS_KUNIT) += util_macros_kunit.o obj-$(CONFIG_RATELIMIT_KUNIT_TEST) += test_ratelimit.o +obj-$(CONFIG_UUID_KUNIT_TEST) += uuid_kunit.o obj-$(CONFIG_TEST_RUNTIME_MODULE) += module/ diff --git a/lib/tests/glob_kunit.c b/lib/tests/glob_kunit.c new file mode 100644 index 000000000000..362b1eda8e5b --- /dev/null +++ b/lib/tests/glob_kunit.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: MIT OR GPL-2.0 +/* + * Test cases for glob functions. + */ + +#include <kunit/test.h> +#include <linux/glob.h> +#include <linux/module.h> + +/** + * struct glob_test_case - Test case for glob matching. + * @pat: Pattern to match. + * @str: String to match against. + * @expected: Expected glob_match result, true if matched. + */ +struct glob_test_case { + const char *pat; + const char *str; + bool expected; +}; + +static const struct glob_test_case glob_test_cases[] = { + /* Some basic tests */ + { .pat = "a", .str = "a", .expected = true }, + { .pat = "a", .str = "b", .expected = false }, + { .pat = "a", .str = "aa", .expected = false }, + { .pat = "a", .str = "", .expected = false }, + { .pat = "", .str = "", .expected = true }, + { .pat = "", .str = "a", .expected = false }, + /* Simple character class tests */ + { .pat = "[a]", .str = "a", .expected = true }, + { .pat = "[a]", .str = "b", .expected = false }, + { .pat = "[!a]", .str = "a", .expected = false }, + { .pat = "[!a]", .str = "b", .expected = true }, + { .pat = "[ab]", .str = "a", .expected = true }, + { .pat = "[ab]", .str = "b", .expected = true }, + { .pat = "[ab]", .str = "c", .expected = false }, + { .pat = "[!ab]", .str = "c", .expected = true }, + { .pat = "[a-c]", .str = "b", .expected = true }, + { .pat = "[a-c]", .str = "d", .expected = false }, + /* Corner cases in character class parsing */ + { .pat = "[a-c-e-g]", .str = "-", .expected = true }, + { .pat = "[a-c-e-g]", .str = "d", .expected = false }, + { .pat = "[a-c-e-g]", .str = "f", .expected = true }, + { .pat = "[]a-ceg-ik[]", .str = "a", .expected = true }, + { .pat = "[]a-ceg-ik[]", .str = "]", .expected = true }, + { .pat = "[]a-ceg-ik[]", .str = "[", .expected = true }, + { .pat = "[]a-ceg-ik[]", .str = "h", .expected = true }, + { .pat = "[]a-ceg-ik[]", .str = "f", .expected = false }, + { .pat = "[!]a-ceg-ik[]", .str = "h", .expected = false }, + { .pat = "[!]a-ceg-ik[]", .str = "]", .expected = false }, + { .pat = "[!]a-ceg-ik[]", .str = "f", .expected = true }, + /* Simple wild cards */ + { .pat = "?", .str = "a", .expected = true }, + { .pat = "?", .str = "aa", .expected = false }, + { .pat = "??", .str = "a", .expected = false }, + { .pat = "?x?", .str = "axb", .expected = true }, + { .pat = "?x?", .str = "abx", .expected = false }, + { .pat = "?x?", .str = "xab", .expected = false }, + /* Asterisk wild cards (backtracking) */ + { .pat = "*??", .str = "a", .expected = false }, + { .pat = "*??", .str = "ab", .expected = true }, + { .pat = "*??", .str = "abc", .expected = true }, + { .pat = "*??", .str = "abcd", .expected = true }, + { .pat = "??*", .str = "a", .expected = false }, + { .pat = "??*", .str = "ab", .expected = true }, + { .pat = "??*", .str = "abc", .expected = true }, + { .pat = "??*", .str = "abcd", .expected = true }, + { .pat = "?*?", .str = "a", .expected = false }, + { .pat = "?*?", .str = "ab", .expected = true }, + { .pat = "?*?", .str = "abc", .expected = true }, + { .pat = "?*?", .str = "abcd", .expected = true }, + { .pat = "*b", .str = "b", .expected = true }, + { .pat = "*b", .str = "ab", .expected = true }, + { .pat = "*b", .str = "ba", .expected = false }, + { .pat = "*b", .str = "bb", .expected = true }, + { .pat = "*b", .str = "abb", .expected = true }, + { .pat = "*b", .str = "bab", .expected = true }, + { .pat = "*bc", .str = "abbc", .expected = true }, + { .pat = "*bc", .str = "bc", .expected = true }, + { .pat = "*bc", .str = "bbc", .expected = true }, + { .pat = "*bc", .str = "bcbc", .expected = true }, + /* Multiple asterisks (complex backtracking) */ + { .pat = "*ac*", .str = "abacadaeafag", .expected = true }, + { .pat = "*ac*ae*ag*", .str = "abacadaeafag", .expected = true }, + { .pat = "*a*b*[bc]*[ef]*g*", .str = "abacadaeafag", .expected = true }, + { .pat = "*a*b*[ef]*[cd]*g*", .str = "abacadaeafag", .expected = false }, + { .pat = "*abcd*", .str = "abcabcabcabcdefg", .expected = true }, + { .pat = "*ab*cd*", .str = "abcabcabcabcdefg", .expected = true }, + { .pat = "*abcd*abcdef*", .str = "abcabcdabcdeabcdefg", .expected = true }, + { .pat = "*abcd*", .str = "abcabcabcabcefg", .expected = false }, + { .pat = "*ab*cd*", .str = "abcabcabcabcefg", .expected = false }, +}; + +static void glob_case_to_desc(const struct glob_test_case *t, char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "pat:\"%s\" str:\"%s\"", t->pat, t->str); +} + +KUNIT_ARRAY_PARAM(glob, glob_test_cases, glob_case_to_desc); + +static void glob_test_match(struct kunit *test) +{ + const struct glob_test_case *params = test->param_value; + + KUNIT_EXPECT_EQ_MSG(test, + glob_match(params->pat, params->str), + params->expected, + "Pattern: \"%s\", String: \"%s\", Expected: %d", + params->pat, params->str, params->expected); +} + +static struct kunit_case glob_kunit_test_cases[] = { + KUNIT_CASE_PARAM(glob_test_match, glob_gen_params), + {} +}; + +static struct kunit_suite glob_test_suite = { + .name = "glob", + .test_cases = glob_kunit_test_cases, +}; + +kunit_test_suite(glob_test_suite); +MODULE_DESCRIPTION("Test cases for glob functions"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/lib/tests/list-private-test.c b/lib/tests/list-private-test.c new file mode 100644 index 000000000000..3bd62939ae67 --- /dev/null +++ b/lib/tests/list-private-test.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit compilation/smoke test for Private list primitives. + * + * Copyright (c) 2025, Google LLC. + * Pasha Tatashin <pasha.tatashin@soleen.com> + */ +#include <linux/list_private.h> +#include <kunit/test.h> + +/* + * This forces compiler to warn if you access it directly, because list + * primitives expect (struct list_head *), not (volatile struct list_head *). + */ +#undef __private +#define __private volatile + +/* Redefine ACCESS_PRIVATE for this test. */ +#undef ACCESS_PRIVATE +#define ACCESS_PRIVATE(p, member) \ + (*((struct list_head *)((unsigned long)&((p)->member)))) + +struct list_test_struct { + int data; + struct list_head __private list; +}; + +static void list_private_compile_test(struct kunit *test) +{ + struct list_test_struct entry; + struct list_test_struct *pos, *n; + LIST_HEAD(head); + + INIT_LIST_HEAD(&ACCESS_PRIVATE(&entry, list)); + list_add(&ACCESS_PRIVATE(&entry, list), &head); + pos = &entry; + + pos = list_private_entry(&ACCESS_PRIVATE(&entry, list), struct list_test_struct, list); + pos = list_private_first_entry(&head, struct list_test_struct, list); + pos = list_private_last_entry(&head, struct list_test_struct, list); + pos = list_private_next_entry(pos, list); + pos = list_private_prev_entry(pos, list); + pos = list_private_next_entry_circular(pos, &head, list); + pos = list_private_prev_entry_circular(pos, &head, list); + + if (list_private_entry_is_head(pos, &head, list)) + return; + + list_private_for_each_entry(pos, &head, list) { } + list_private_for_each_entry_reverse(pos, &head, list) { } + list_private_for_each_entry_continue(pos, &head, list) { } + list_private_for_each_entry_continue_reverse(pos, &head, list) { } + list_private_for_each_entry_from(pos, &head, list) { } + list_private_for_each_entry_from_reverse(pos, &head, list) { } + + list_private_for_each_entry_safe(pos, n, &head, list) + list_private_safe_reset_next(pos, n, list); + list_private_for_each_entry_safe_continue(pos, n, &head, list) { } + list_private_for_each_entry_safe_from(pos, n, &head, list) { } + list_private_for_each_entry_safe_reverse(pos, n, &head, list) { } +} + +static struct kunit_case list_private_test_cases[] = { + KUNIT_CASE(list_private_compile_test), + {}, +}; + +static struct kunit_suite list_private_test_module = { + .name = "list-private-kunit-test", + .test_cases = list_private_test_cases, +}; + +kunit_test_suite(list_private_test_module); + +MODULE_DESCRIPTION("KUnit compilation test for private list primitives"); +MODULE_LICENSE("GPL"); diff --git a/lib/tests/liveupdate.c b/lib/tests/liveupdate.c new file mode 100644 index 000000000000..496d6ef91a30 --- /dev/null +++ b/lib/tests/liveupdate.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Copyright (c) 2025, Google LLC. + * Pasha Tatashin <pasha.tatashin@soleen.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME " test: " fmt + +#include <linux/cleanup.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/liveupdate.h> +#include <linux/module.h> +#include "../../kernel/liveupdate/luo_internal.h" + +static const struct liveupdate_flb_ops test_flb_ops; +#define DEFINE_TEST_FLB(i) { \ + .ops = &test_flb_ops, \ + .compatible = LIVEUPDATE_TEST_FLB_COMPATIBLE(i), \ +} + +/* Number of Test FLBs to register with every file handler */ +#define TEST_NFLBS 3 +static struct liveupdate_flb test_flbs[TEST_NFLBS] = { + DEFINE_TEST_FLB(0), + DEFINE_TEST_FLB(1), + DEFINE_TEST_FLB(2), +}; + +#define TEST_FLB_MAGIC_BASE 0xFEEDF00DCAFEBEE0ULL + +static int test_flb_preserve(struct liveupdate_flb_op_args *argp) +{ + ptrdiff_t index = argp->flb - test_flbs; + + pr_info("%s: preserve was triggered\n", argp->flb->compatible); + argp->data = TEST_FLB_MAGIC_BASE + index; + + return 0; +} + +static void test_flb_unpreserve(struct liveupdate_flb_op_args *argp) +{ + pr_info("%s: unpreserve was triggered\n", argp->flb->compatible); +} + +static int test_flb_retrieve(struct liveupdate_flb_op_args *argp) +{ + ptrdiff_t index = argp->flb - test_flbs; + u64 expected_data = TEST_FLB_MAGIC_BASE + index; + + if (argp->data == expected_data) { + pr_info("%s: found flb data from the previous boot\n", + argp->flb->compatible); + argp->obj = (void *)argp->data; + } else { + pr_err("%s: ERROR - incorrect data handle: %llx, expected %llx\n", + argp->flb->compatible, argp->data, expected_data); + return -EINVAL; + } + + return 0; +} + +static void test_flb_finish(struct liveupdate_flb_op_args *argp) +{ + ptrdiff_t index = argp->flb - test_flbs; + void *expected_obj = (void *)(TEST_FLB_MAGIC_BASE + index); + + if (argp->obj == expected_obj) { + pr_info("%s: finish was triggered\n", argp->flb->compatible); + } else { + pr_err("%s: ERROR - finish called with invalid object\n", + argp->flb->compatible); + } +} + +static const struct liveupdate_flb_ops test_flb_ops = { + .preserve = test_flb_preserve, + .unpreserve = test_flb_unpreserve, + .retrieve = test_flb_retrieve, + .finish = test_flb_finish, + .owner = THIS_MODULE, +}; + +static void liveupdate_test_init(void) +{ + static DEFINE_MUTEX(init_lock); + static bool initialized; + int i; + + guard(mutex)(&init_lock); + + if (initialized) + return; + + for (i = 0; i < TEST_NFLBS; i++) { + struct liveupdate_flb *flb = &test_flbs[i]; + void *obj; + int err; + + err = liveupdate_flb_get_incoming(flb, &obj); + if (err && err != -ENODATA && err != -ENOENT) { + pr_err("liveupdate_flb_get_incoming for %s failed: %pe\n", + flb->compatible, ERR_PTR(err)); + } + } + initialized = true; +} + +void liveupdate_test_register(struct liveupdate_file_handler *fh) +{ + int err, i; + + liveupdate_test_init(); + + for (i = 0; i < TEST_NFLBS; i++) { + struct liveupdate_flb *flb = &test_flbs[i]; + + err = liveupdate_register_flb(fh, flb); + if (err) { + pr_err("Failed to register %s %pe\n", + flb->compatible, ERR_PTR(err)); + } + } + + err = liveupdate_register_flb(fh, &test_flbs[0]); + if (!err || err != -EEXIST) { + pr_err("Failed: %s should be already registered, but got err: %pe\n", + test_flbs[0].compatible, ERR_PTR(err)); + } + + pr_info("Registered %d FLBs with file handler: [%s]\n", + TEST_NFLBS, fh->compatible); +} + +void liveupdate_test_unregister(struct liveupdate_file_handler *fh) +{ + int err, i; + + for (i = 0; i < TEST_NFLBS; i++) { + struct liveupdate_flb *flb = &test_flbs[i]; + + err = liveupdate_unregister_flb(fh, flb); + if (err) { + pr_err("Failed to unregister %s %pe\n", + flb->compatible, ERR_PTR(err)); + } + } + + pr_info("Unregistered %d FLBs from file handler: [%s]\n", + TEST_NFLBS, fh->compatible); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Pasha Tatashin <pasha.tatashin@soleen.com>"); +MODULE_DESCRIPTION("In-kernel test for LUO mechanism"); diff --git a/lib/test_min_heap.c b/lib/tests/min_heap_kunit.c index a9c4a74d3898..9c1122661698 100644 --- a/lib/test_min_heap.c +++ b/lib/tests/min_heap_kunit.c @@ -1,60 +1,66 @@ // SPDX-License-Identifier: GPL-2.0-only -#define pr_fmt(fmt) "min_heap_test: " fmt - /* * Test cases for the min max heap. */ -#include <linux/log2.h> +#include <kunit/test.h> #include <linux/min_heap.h> #include <linux/module.h> -#include <linux/printk.h> #include <linux/random.h> +struct min_heap_test_case { + const char *str; + bool min_heap; +}; + +static struct min_heap_test_case min_heap_cases[] = { + { + .str = "min", + .min_heap = true, + }, + { + .str = "max", + .min_heap = false, + }, +}; + +KUNIT_ARRAY_PARAM_DESC(min_heap, min_heap_cases, str); + DEFINE_MIN_HEAP(int, min_heap_test); -static __init bool less_than(const void *lhs, const void *rhs, void __always_unused *args) +static bool less_than(const void *lhs, const void *rhs, void __always_unused *args) { return *(int *)lhs < *(int *)rhs; } -static __init bool greater_than(const void *lhs, const void *rhs, void __always_unused *args) +static bool greater_than(const void *lhs, const void *rhs, void __always_unused *args) { return *(int *)lhs > *(int *)rhs; } -static __init int pop_verify_heap(bool min_heap, - struct min_heap_test *heap, - const struct min_heap_callbacks *funcs) +static void pop_verify_heap(struct kunit *test, + bool min_heap, + struct min_heap_test *heap, + const struct min_heap_callbacks *funcs) { int *values = heap->data; - int err = 0; int last; last = values[0]; min_heap_pop_inline(heap, funcs, NULL); while (heap->nr > 0) { - if (min_heap) { - if (last > values[0]) { - pr_err("error: expected %d <= %d\n", last, - values[0]); - err++; - } - } else { - if (last < values[0]) { - pr_err("error: expected %d >= %d\n", last, - values[0]); - err++; - } - } + if (min_heap) + KUNIT_EXPECT_LE(test, last, values[0]); + else + KUNIT_EXPECT_GE(test, last, values[0]); last = values[0]; min_heap_pop_inline(heap, funcs, NULL); } - return err; } -static __init int test_heapify_all(bool min_heap) +static void test_heapify_all(struct kunit *test) { + const struct min_heap_test_case *params = test->param_value; int values[] = { 3, 1, 2, 4, 0x8000000, 0x7FFFFFF, 0, -3, -1, -2, -4, 0x8000000, 0x7FFFFFF }; struct min_heap_test heap = { @@ -63,15 +69,14 @@ static __init int test_heapify_all(bool min_heap) .size = ARRAY_SIZE(values), }; struct min_heap_callbacks funcs = { - .less = min_heap ? less_than : greater_than, + .less = params->min_heap ? less_than : greater_than, .swp = NULL, }; - int i, err; + int i; /* Test with known set of values. */ min_heapify_all_inline(&heap, &funcs, NULL); - err = pop_verify_heap(min_heap, &heap, &funcs); - + pop_verify_heap(test, params->min_heap, &heap, &funcs); /* Test with randomly generated values. */ heap.nr = ARRAY_SIZE(values); @@ -79,13 +84,12 @@ static __init int test_heapify_all(bool min_heap) values[i] = get_random_u32(); min_heapify_all_inline(&heap, &funcs, NULL); - err += pop_verify_heap(min_heap, &heap, &funcs); - - return err; + pop_verify_heap(test, params->min_heap, &heap, &funcs); } -static __init int test_heap_push(bool min_heap) +static void test_heap_push(struct kunit *test) { + const struct min_heap_test_case *params = test->param_value; const int data[] = { 3, 1, 2, 4, 0x80000000, 0x7FFFFFFF, 0, -3, -1, -2, -4, 0x80000000, 0x7FFFFFFF }; int values[ARRAY_SIZE(data)]; @@ -95,29 +99,28 @@ static __init int test_heap_push(bool min_heap) .size = ARRAY_SIZE(values), }; struct min_heap_callbacks funcs = { - .less = min_heap ? less_than : greater_than, + .less = params->min_heap ? less_than : greater_than, .swp = NULL, }; - int i, temp, err; + int i, temp; /* Test with known set of values copied from data. */ for (i = 0; i < ARRAY_SIZE(data); i++) min_heap_push_inline(&heap, &data[i], &funcs, NULL); - err = pop_verify_heap(min_heap, &heap, &funcs); + pop_verify_heap(test, params->min_heap, &heap, &funcs); /* Test with randomly generated values. */ while (heap.nr < heap.size) { temp = get_random_u32(); min_heap_push_inline(&heap, &temp, &funcs, NULL); } - err += pop_verify_heap(min_heap, &heap, &funcs); - - return err; + pop_verify_heap(test, params->min_heap, &heap, &funcs); } -static __init int test_heap_pop_push(bool min_heap) +static void test_heap_pop_push(struct kunit *test) { + const struct min_heap_test_case *params = test->param_value; const int data[] = { 3, 1, 2, 4, 0x80000000, 0x7FFFFFFF, 0, -3, -1, -2, -4, 0x80000000, 0x7FFFFFFF }; int values[ARRAY_SIZE(data)]; @@ -127,13 +130,13 @@ static __init int test_heap_pop_push(bool min_heap) .size = ARRAY_SIZE(values), }; struct min_heap_callbacks funcs = { - .less = min_heap ? less_than : greater_than, + .less = params->min_heap ? less_than : greater_than, .swp = NULL, }; - int i, temp, err; + int i, temp; /* Fill values with data to pop and replace. */ - temp = min_heap ? 0x80000000 : 0x7FFFFFFF; + temp = params->min_heap ? 0x80000000 : 0x7FFFFFFF; for (i = 0; i < ARRAY_SIZE(data); i++) min_heap_push_inline(&heap, &temp, &funcs, NULL); @@ -141,7 +144,7 @@ static __init int test_heap_pop_push(bool min_heap) for (i = 0; i < ARRAY_SIZE(data); i++) min_heap_pop_push_inline(&heap, &data[i], &funcs, NULL); - err = pop_verify_heap(min_heap, &heap, &funcs); + pop_verify_heap(test, params->min_heap, &heap, &funcs); heap.nr = 0; for (i = 0; i < ARRAY_SIZE(data); i++) @@ -152,13 +155,12 @@ static __init int test_heap_pop_push(bool min_heap) temp = get_random_u32(); min_heap_pop_push_inline(&heap, &temp, &funcs, NULL); } - err += pop_verify_heap(min_heap, &heap, &funcs); - - return err; + pop_verify_heap(test, params->min_heap, &heap, &funcs); } -static __init int test_heap_del(bool min_heap) +static void test_heap_del(struct kunit *test) { + const struct min_heap_test_case *params = test->param_value; int values[] = { 3, 1, 2, 4, 0x8000000, 0x7FFFFFF, 0, -3, -1, -2, -4, 0x8000000, 0x7FFFFFF }; struct min_heap_test heap; @@ -166,17 +168,16 @@ static __init int test_heap_del(bool min_heap) min_heap_init_inline(&heap, values, ARRAY_SIZE(values)); heap.nr = ARRAY_SIZE(values); struct min_heap_callbacks funcs = { - .less = min_heap ? less_than : greater_than, + .less = params->min_heap ? less_than : greater_than, .swp = NULL, }; - int i, err; + int i; /* Test with known set of values. */ min_heapify_all_inline(&heap, &funcs, NULL); for (i = 0; i < ARRAY_SIZE(values) / 2; i++) min_heap_del_inline(&heap, get_random_u32() % heap.nr, &funcs, NULL); - err = pop_verify_heap(min_heap, &heap, &funcs); - + pop_verify_heap(test, params->min_heap, &heap, &funcs); /* Test with randomly generated values. */ heap.nr = ARRAY_SIZE(values); @@ -186,37 +187,23 @@ static __init int test_heap_del(bool min_heap) for (i = 0; i < ARRAY_SIZE(values) / 2; i++) min_heap_del_inline(&heap, get_random_u32() % heap.nr, &funcs, NULL); - err += pop_verify_heap(min_heap, &heap, &funcs); - - return err; + pop_verify_heap(test, params->min_heap, &heap, &funcs); } -static int __init test_min_heap_init(void) -{ - int err = 0; - - err += test_heapify_all(true); - err += test_heapify_all(false); - err += test_heap_push(true); - err += test_heap_push(false); - err += test_heap_pop_push(true); - err += test_heap_pop_push(false); - err += test_heap_del(true); - err += test_heap_del(false); - if (err) { - pr_err("test failed with %d errors\n", err); - return -EINVAL; - } - pr_info("test passed\n"); - return 0; -} -module_init(test_min_heap_init); +static struct kunit_case min_heap_test_cases[] = { + KUNIT_CASE_PARAM(test_heapify_all, min_heap_gen_params), + KUNIT_CASE_PARAM(test_heap_push, min_heap_gen_params), + KUNIT_CASE_PARAM(test_heap_pop_push, min_heap_gen_params), + KUNIT_CASE_PARAM(test_heap_del, min_heap_gen_params), + {}, +}; -static void __exit test_min_heap_exit(void) -{ - /* do nothing */ -} -module_exit(test_min_heap_exit); +static struct kunit_suite min_heap_test_suite = { + .name = "min_heap", + .test_cases = min_heap_test_cases, +}; + +kunit_test_suite(min_heap_test_suite); MODULE_DESCRIPTION("Test cases for the min max heap"); MODULE_LICENSE("GPL"); diff --git a/lib/tests/uuid_kunit.c b/lib/tests/uuid_kunit.c new file mode 100644 index 000000000000..de71b2649dac --- /dev/null +++ b/lib/tests/uuid_kunit.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 +/* + * Test cases for lib/uuid.c module. + */ + +#include <kunit/test.h> +#include <linux/uuid.h> + +struct test_uuid_data { + const char *uuid; + guid_t le; + uuid_t be; +}; + +static const struct test_uuid_data test_uuid_test_data[] = { + { + .uuid = "c33f4995-3701-450e-9fbf-206a2e98e576", + .le = GUID_INIT(0xc33f4995, 0x3701, 0x450e, 0x9f, 0xbf, 0x20, 0x6a, 0x2e, 0x98, 0xe5, 0x76), + .be = UUID_INIT(0xc33f4995, 0x3701, 0x450e, 0x9f, 0xbf, 0x20, 0x6a, 0x2e, 0x98, 0xe5, 0x76), + }, + { + .uuid = "64b4371c-77c1-48f9-8221-29f054fc023b", + .le = GUID_INIT(0x64b4371c, 0x77c1, 0x48f9, 0x82, 0x21, 0x29, 0xf0, 0x54, 0xfc, 0x02, 0x3b), + .be = UUID_INIT(0x64b4371c, 0x77c1, 0x48f9, 0x82, 0x21, 0x29, 0xf0, 0x54, 0xfc, 0x02, 0x3b), + }, + { + .uuid = "0cb4ddff-a545-4401-9d06-688af53e7f84", + .le = GUID_INIT(0x0cb4ddff, 0xa545, 0x4401, 0x9d, 0x06, 0x68, 0x8a, 0xf5, 0x3e, 0x7f, 0x84), + .be = UUID_INIT(0x0cb4ddff, 0xa545, 0x4401, 0x9d, 0x06, 0x68, 0x8a, 0xf5, 0x3e, 0x7f, 0x84), + }, +}; + +static const char * const test_uuid_wrong_data[] = { + "c33f4995-3701-450e-9fbf206a2e98e576 ", /* no hyphen(s) */ + "64b4371c-77c1-48f9-8221-29f054XX023b", /* invalid character(s) */ + "0cb4ddff-a545-4401-9d06-688af53e", /* not enough data */ +}; + +static void uuid_test_guid_valid(struct kunit *test) +{ + unsigned int i; + const struct test_uuid_data *data; + guid_t le; + + for (i = 0; i < ARRAY_SIZE(test_uuid_test_data); i++) { + data = &test_uuid_test_data[i]; + KUNIT_EXPECT_EQ(test, guid_parse(data->uuid, &le), 0); + KUNIT_EXPECT_TRUE(test, guid_equal(&data->le, &le)); + } +} + +static void uuid_test_uuid_valid(struct kunit *test) +{ + unsigned int i; + const struct test_uuid_data *data; + uuid_t be; + + for (i = 0; i < ARRAY_SIZE(test_uuid_test_data); i++) { + data = &test_uuid_test_data[i]; + KUNIT_EXPECT_EQ(test, uuid_parse(data->uuid, &be), 0); + KUNIT_EXPECT_TRUE(test, uuid_equal(&data->be, &be)); + } +} + +static void uuid_test_guid_invalid(struct kunit *test) +{ + unsigned int i; + const char *uuid; + guid_t le; + + for (i = 0; i < ARRAY_SIZE(test_uuid_wrong_data); i++) { + uuid = test_uuid_wrong_data[i]; + KUNIT_EXPECT_EQ(test, guid_parse(uuid, &le), -EINVAL); + } +} + +static void uuid_test_uuid_invalid(struct kunit *test) +{ + unsigned int i; + const char *uuid; + uuid_t be; + + for (i = 0; i < ARRAY_SIZE(test_uuid_wrong_data); i++) { + uuid = test_uuid_wrong_data[i]; + KUNIT_EXPECT_EQ(test, uuid_parse(uuid, &be), -EINVAL); + } +} + +static struct kunit_case uuid_test_cases[] = { + KUNIT_CASE(uuid_test_guid_valid), + KUNIT_CASE(uuid_test_uuid_valid), + KUNIT_CASE(uuid_test_guid_invalid), + KUNIT_CASE(uuid_test_uuid_invalid), + {}, +}; + +static struct kunit_suite uuid_test_suite = { + .name = "uuid", + .test_cases = uuid_test_cases, +}; + +kunit_test_suite(uuid_test_suite); + +MODULE_AUTHOR("Andy Shevchenko <andriy.shevchenko@linux.intel.com>"); +MODULE_DESCRIPTION("Test cases for lib/uuid.c module"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/lib/uuid.c b/lib/uuid.c index e309b4c5be3d..e8543c668dc7 100644 --- a/lib/uuid.c +++ b/lib/uuid.c @@ -10,6 +10,7 @@ #include <linux/ctype.h> #include <linux/errno.h> #include <linux/export.h> +#include <linux/hex.h> #include <linux/uuid.h> #include <linux/random.h> diff --git a/lib/vsprintf.c b/lib/vsprintf.c index a3790c43a0ab..800b8ac49f53 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -26,6 +26,7 @@ #include <linux/types.h> #include <linux/string.h> #include <linux/ctype.h> +#include <linux/hex.h> #include <linux/kernel.h> #include <linux/kallsyms.h> #include <linux/math64.h> |
