From nobody Mon Apr 6 09:34:52 2026 Received: from mail-wr1-f74.google.com (mail-wr1-f74.google.com [209.85.221.74]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4CD753E868D for ; Fri, 20 Mar 2026 18:23:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.74 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774031032; cv=none; b=h5WZs9xcDSfbTVABI01L8liYdxYuURBeRl0ktwR5Jio0dz3OhVkdbSDURVkZ5Q+1a0k7JiE4esI4QLXoOty77zEZB/riIAaEeqR9gBFoeasEBwNw3P6sNdZRuzucKpYMVLTQA9Kian4IZO9P8jbXkza995nV0bHl/zVnddouLeA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774031032; c=relaxed/simple; bh=9bqV6KhfAqSWtb7ARSmaCUC26KbfPjDBVyCqLPQNPIQ=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=BznWSzMKhA578y5tR0m7Z+RRu42EZRi2s0XejM5D5i9X8PRqDQ+35B9Z4hAovHwnNkRtQV608geeYuNnhW7fOSlxuIrYkMULir6SzeHoj0DbfD9cIjKPY7JxvGYVxAJXLpTF2DRS27/S/jjbxkRa7Mxs6+HnX2RHw0XwWIDoK+k= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--jackmanb.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=faYk2yFg; arc=none smtp.client-ip=209.85.221.74 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--jackmanb.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="faYk2yFg" Received: by mail-wr1-f74.google.com with SMTP id ffacd0b85a97d-43b40c22eaeso2687814f8f.2 for ; Fri, 20 Mar 2026 11:23:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1774031028; x=1774635828; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=7WoeG/ZuUSN8okdcfkvX8H8rOAlcPJMzezDraYSY2lo=; b=faYk2yFgO9fk2dai0Q90OJWSlhCCdeiaLUWtSU7VKV8bMShHTKSBuLbAgQ+KdhmF4y ARKhoo2YWcOkYoaGYpfedaOCbK0UD2gc52wXq/DpyUwaHqQQzeCoqgI6Ir/84kemeoat px3+6n5PVb0r0dm0W38hhFPGgbuI/ki4LOPy9wnnujpe05URhlrlUA+RAgdyf4ad1bcU G3FDrEhZVEvv6B1PvtdnQGdn+rO7uOj+qun3eTf9iLYeVAvM4CGzNK3Z7hy+EKHv/kPR OFJTs9537M1rinDGTlTPUHxdYIUwr/l02UHnsOPI8GLvqbemzT8UNkKSpoTdpRf9g2Fu Y9Sw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774031028; x=1774635828; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=7WoeG/ZuUSN8okdcfkvX8H8rOAlcPJMzezDraYSY2lo=; b=CpqrAJ0BI9gRJpkHSSZfhx5JYaLVvN+cFfiPk7NKYunWAzC1TuAD2uEZscdx6ehzVS xi1fbt7t7EFj61bR0Cdb9IL6ijHqoH/d4AvQXMsWdlNiVBDpOh7fPUOcGO9P09jyVI+t K3pyfMRziMg4/g+rSHWoPN3I0OBwiO9ilBTyLttiqTsjYK0IgL3xudGiDxcxznoIBATL TteixYjFXesWxKoiHom0URg1M9xKgO/G5I9V2M3+G90oVFLC0rscCZt0K8yetbA1Rgvo BzmLlijPruHBnn1zryQ9V0mxFkNvfrxyiroG1ipFS0u6igVmJy1FrEFnzoqxAzDrd8tc geQA== X-Forwarded-Encrypted: i=1; AJvYcCWhenqF9eEvd6ByM9wKMQcQY2W7dZpZnfwRsYNgXi9H0fYmOagSgHpbDKfxYpTQbavBc+c/hkTX6uTQ55Q=@vger.kernel.org X-Gm-Message-State: AOJu0YwVUMaOls7t5y60d2yRFyVP2sol1dn2vYr2ndppq18Zzj3kE/ky DUwpmXfAWg6Jw5kbov6WfnT9D7wJ3DXNeYDKFj+lZfMlnkAeZxObuSABRQ+0QVdiv7X+fLDSe0s 2h1quNVGW9cJ8Aw== X-Received: from wrpz9.prod.google.com ([2002:adf:f749:0:b0:43b:402b:468d]) (user=jackmanb job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6000:26c8:b0:439:bcb8:54b7 with SMTP id ffacd0b85a97d-43b6424b9eemr6565674f8f.15.1774031028223; Fri, 20 Mar 2026 11:23:48 -0700 (PDT) Date: Fri, 20 Mar 2026 18:23:31 +0000 In-Reply-To: <20260320-page_alloc-unmapped-v2-0-28bf1bd54f41@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260320-page_alloc-unmapped-v2-0-28bf1bd54f41@google.com> X-Mailer: b4 0.14.3 Message-ID: <20260320-page_alloc-unmapped-v2-7-28bf1bd54f41@google.com> Subject: [PATCH v2 07/22] mm: KUnit tests for the mermap From: Brendan Jackman To: Borislav Petkov , Dave Hansen , Peter Zijlstra , Andrew Morton , David Hildenbrand , Vlastimil Babka , Wei Xu , Johannes Weiner , Zi Yan , Lorenzo Stoakes Cc: linux-mm@kvack.org, linux-kernel@vger.kernel.org, x86@kernel.org, rppt@kernel.org, Sumit Garg , derkling@google.com, reijiw@google.com, Will Deacon , rientjes@google.com, "Kalyazin, Nikita" , patrick.roy@linux.dev, "Itazuri, Takahiro" , Andy Lutomirski , David Kaplan , Thomas Gleixner , Brendan Jackman , Yosry Ahmed Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Some simple smoke-tests for the mermap. Mainly aiming to test: 1. That there aren't any silly off-by-ones. 2. That the pagetables are not completely broken. 3. That the TLB appears to get flushed basically when expected. This last point requires a bit of ifdeffery to detect when the flushing has been performed. Signed-off-by: Brendan Jackman --- include/linux/mermap_types.h | 3 + mm/Kconfig | 11 ++ mm/Makefile | 1 + mm/tests/mermap_kunit.c | 250 +++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 265 insertions(+) diff --git a/include/linux/mermap_types.h b/include/linux/mermap_types.h index c1c83b223c28d..13110fcb4c387 100644 --- a/include/linux/mermap_types.h +++ b/include/linux/mermap_types.h @@ -24,6 +24,9 @@ struct mermap_cpu { unsigned long next_addr; struct mermap_alloc normal_allocs[3]; struct mermap_alloc reserve_alloc; +#if IS_ENABLED(CONFIG_MERMAP_KUNIT_TEST) + u64 tlb_flushes; +#endif }; =20 struct mermap { diff --git a/mm/Kconfig b/mm/Kconfig index 2bf1dbcc8cb10..e98db58d515fc 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -1494,4 +1494,15 @@ config MERMAP help Support for epheMERal mappings within the kernel. =20 +config MERMAP_KUNIT_TEST + tristate "KUnit tests for the mermap" if !KUNIT_ALL_TESTS + depends on ARCH_SUPPORTS_MERMAP + depends on KUNIT + depends on MERMAP + default KUNIT_ALL_TESTS + help + KUnit test for the mermap. + + If unsure, say N. + endmenu diff --git a/mm/Makefile b/mm/Makefile index 0c45677f4a538..93a1756303cf9 100644 --- a/mm/Makefile +++ b/mm/Makefile @@ -151,3 +151,4 @@ obj-$(CONFIG_EXECMEM) +=3D execmem.o obj-$(CONFIG_TMPFS_QUOTA) +=3D shmem_quota.o obj-$(CONFIG_LAZY_MMU_MODE_KUNIT_TEST) +=3D tests/lazy_mmu_mode_kunit.o obj-$(CONFIG_MERMAP) +=3D mermap.o +obj-$(CONFIG_MERMAP_KUNIT_TEST) +=3D tests/mermap_kunit.o diff --git a/mm/tests/mermap_kunit.c b/mm/tests/mermap_kunit.c new file mode 100644 index 0000000000000..4ac6bce2d75f7 --- /dev/null +++ b/mm/tests/mermap_kunit.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include + +#include + +#define NR_NORMAL_ALLOCS ARRAY_SIZE(((struct mm_struct *)NULL)->mermap.cpu= ->normal_allocs) + +KUNIT_DEFINE_ACTION_WRAPPER(__free_page_wrapper, __free_page, struct page = *); + +static inline struct page *alloc_page_wrapper(struct kunit *test, gfp_t gf= p) +{ + struct page *page =3D alloc_page(gfp); + + KUNIT_ASSERT_NOT_NULL(test, page); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, __free_page_wrapper= , page), 0); + return page; +} + +KUNIT_DEFINE_ACTION_WRAPPER(mmput_wrapper, mmput, struct mm_struct *); + +static inline struct mm_struct *mm_alloc_wrapper(struct kunit *test) +{ + struct mm_struct *mm =3D mm_alloc(); + + KUNIT_ASSERT_NOT_NULL(test, mm); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, mmput_wrapper, mm),= 0); + return mm; +} + +static inline struct mm_struct *get_mm(struct kunit *test) +{ + struct mm_struct *mm =3D mm_alloc_wrapper(test); + + KUNIT_ASSERT_EQ(test, mermap_mm_prepare(mm), 0); + return mm; +} + +struct __mermap_put_args { + struct mm_struct *mm; + struct mermap_alloc *alloc; + unsigned long size; +}; + +static inline void __mermap_put_wrapper(void *ctx) +{ + struct __mermap_put_args *args =3D (struct __mermap_put_args *)ctx; + + __mermap_put(args->mm, args->alloc); +} + +/* Call __mermap_get() with use_reserve=3Dfalse, deal with cleanup. */ +static inline struct __mermap_put_args * +__mermap_get_wrapper(struct kunit *test, struct mm_struct *mm, + struct page *page, unsigned long size, pgprot_t prot) +{ + struct __mermap_put_args *args =3D + kunit_kmalloc(test, sizeof(struct __mermap_put_args), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, args); + args->mm =3D mm; + args->alloc =3D __mermap_get(mm, page, size, prot, false); + args->size =3D size; + + if (args->alloc) { + int err =3D kunit_add_action_or_reset(test, __mermap_put_wrapper, args); + + KUNIT_ASSERT_EQ(test, err, 0); + } + + return args; +} + +/* Do the cleanup from __mermap_get_wrapper, now. */ +static inline void __mermap_put_early(struct kunit *test, struct __mermap_= put_args *args) +{ + kunit_release_action(test, __mermap_put_wrapper, args); +} + +static void test_basic_alloc(struct kunit *test) +{ + struct page *page =3D alloc_page_wrapper(test, GFP_KERNEL); + struct mm_struct *mm =3D get_mm(test); + struct __mermap_put_args *args; + + args =3D __mermap_get_wrapper(test, mm, page, PAGE_SIZE, PAGE_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, args->alloc); +} + +/* Dumb check for off-by-ones. */ +static void test_size(struct kunit *test) +{ + struct page *page =3D alloc_page_wrapper(test, GFP_KERNEL); + struct __mermap_put_args *full, *large, *small, *fail; + struct mm_struct *mm =3D get_mm(test); + unsigned long region_size, large_size; + struct mermap_alloc *alloc; + int cpu; + + migrate_disable(); + cpu =3D raw_smp_processor_id(); + region_size =3D mermap_cpu_end(cpu) - mermap_cpu_base(cpu) - PAGE_SIZE; + large_size =3D region_size - PAGE_SIZE; + + /* Allocate whole region at once. */ + full =3D __mermap_get_wrapper(test, mm, page, region_size, PAGE_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, full->alloc); + __mermap_put_early(test, full); + + /* Allocate larger than region size. */ + fail =3D __mermap_get_wrapper(test, mm, page, region_size + PAGE_SIZE, PA= GE_KERNEL); + KUNIT_ASSERT_NULL(test, fail->alloc); + + /* Tiptoe up to the edge then past it. */ + large =3D __mermap_get_wrapper(test, mm, page, large_size, PAGE_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, large->alloc); + small =3D __mermap_get_wrapper(test, mm, page, PAGE_SIZE, PAGE_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, small->alloc); + fail =3D __mermap_get_wrapper(test, mm, page, PAGE_SIZE, PAGE_KERNEL); + KUNIT_ASSERT_NULL(test, fail->alloc); + + /* Can still allocate the reserved page. */ + local_irq_disable(); + alloc =3D __mermap_get(mm, page, PAGE_SIZE, PAGE_KERNEL, true); + local_irq_enable(); + KUNIT_ASSERT_NOT_NULL(test, alloc); + __mermap_put(mm, alloc); +} + +static void test_multiple_allocs(struct kunit *test) +{ + struct __mermap_put_args *argss[NR_NORMAL_ALLOCS] =3D { }; + struct page *pages[NR_NORMAL_ALLOCS + 1]; + struct mermap_alloc *reserved_alloc; + struct mm_struct *mm =3D get_mm(test); + int magic =3D 0xE4A4; + + for (int i =3D 0; i < ARRAY_SIZE(pages); i++) { + pages[i] =3D alloc_page_wrapper(test, GFP_KERNEL); + WRITE_ONCE(*(int *)page_to_virt(pages[i]), magic + i); + } + + for (int i =3D 0; i < ARRAY_SIZE(argss); i++) { + unsigned long base =3D mermap_cpu_base(raw_smp_processor_id()); + unsigned long end =3D mermap_cpu_end(raw_smp_processor_id()); + unsigned long addr; + + argss[i] =3D __mermap_get_wrapper(test, mm, pages[i], PAGE_SIZE, PAGE_KE= RNEL); + KUNIT_ASSERT_NOT_NULL_MSG(test, argss[i], "alloc %d failed", i); + + addr =3D (unsigned long) mermap_addr(argss[i]->alloc); + KUNIT_EXPECT_GE_MSG(test, addr, base, "alloc %d out of range", i); + KUNIT_EXPECT_LT_MSG(test, addr, end, "alloc %d out of range", i); + }; + + /* + * Read through the mappings to try and detect if they point to the + * pages we wrote earlier. + */ + kthread_use_mm(mm); + for (int i =3D 0; i < ARRAY_SIZE(pages) - 1; i++) { + int *ptr =3D (int *)mermap_addr(argss[i]->alloc); + + KUNIT_EXPECT_EQ(test, *ptr, magic + i); + } + + /* Run out of alloc structures, only reserved allocs should succeed now. = */ + KUNIT_ASSERT_NULL(test, __mermap_get(mm, pages[NR_NORMAL_ALLOCS], + PAGE_SIZE, PAGE_KERNEL, false)); + preempt_disable(); + reserved_alloc =3D __mermap_get(mm, pages[NR_NORMAL_ALLOCS], + PAGE_SIZE, PAGE_KERNEL, true); + KUNIT_EXPECT_NOT_NULL(test, reserved_alloc); + /* Also check if this mapping seems correct*/ + if (reserved_alloc) { + int *ptr =3D (int *)mermap_addr(reserved_alloc); + + KUNIT_EXPECT_EQ(test, *ptr, magic + NR_NORMAL_ALLOCS); + + mermap_put(reserved_alloc); + } + preempt_enable(); + + kthread_unuse_mm(mm); +} + +static void test_tlb_flushed(struct kunit *test) +{ + struct page *page =3D alloc_page_wrapper(test, GFP_KERNEL); + struct mm_struct *mm =3D get_mm(test); + unsigned long addr, prev_addr =3D 0; + /* Avoid running for ever in failure case. */ + unsigned long max_iters =3D 1000000; + struct mermap_cpu *mc; + + migrate_disable(); + mc =3D this_cpu_ptr(mm->mermap.cpu); + + /* + * Allocate until we see an address less than what we had before - assume + * that means a reuse. + */ + for (int i =3D 0; i < max_iters; i++) { + struct mermap_alloc *alloc; + + /* + * Obviously flushing the TLB already is not wrong per se, but + * it's unexpected and probably means there's some bug. + * Use ASSERT to avoid spamming the log in the failure case. + */ + KUNIT_ASSERT_EQ_MSG(test, mc->tlb_flushes, 0, + "unexpected flush before alloc %d", i); + + alloc =3D __mermap_get(mm, page, PAGE_SIZE, PAGE_KERNEL, false); + KUNIT_ASSERT_NOT_NULL_MSG(test, alloc, "alloc %d failed", i); + + addr =3D (unsigned long)mermap_addr(alloc); + __mermap_put(mm, alloc); + if (addr < prev_addr) + break; + + prev_addr =3D addr; + cond_resched(); + } + KUNIT_ASSERT_TRUE_MSG(test, addr < prev_addr, "no address reuse"); + /* Again, more than one flush isn't wrong per se, but probably a bug. */ + KUNIT_ASSERT_EQ(test, mc->tlb_flushes, 1); + + migrate_enable(); +} + +static struct kunit_case mermap_test_cases[] =3D { + KUNIT_CASE(test_basic_alloc), + KUNIT_CASE(test_size), + KUNIT_CASE(test_multiple_allocs), + KUNIT_CASE(test_tlb_flushed), + {} +}; + +static struct kunit_suite mermap_test_suite =3D { + .name =3D "mermap", + .test_cases =3D mermap_test_cases, +}; +kunit_test_suite(mermap_test_suite); + +MODULE_DESCRIPTION("Mermap unit tests"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); --=20 2.51.2