151104c19SKees Cook // SPDX-License-Identifier: GPL-2.0
251104c19SKees Cook /*
351104c19SKees Cook * KUnit userspace memory allocation resource management.
451104c19SKees Cook */
551104c19SKees Cook #include <kunit/resource.h>
651104c19SKees Cook #include <kunit/test.h>
751104c19SKees Cook #include <linux/kthread.h>
851104c19SKees Cook #include <linux/mm.h>
951104c19SKees Cook
1051104c19SKees Cook struct kunit_vm_mmap_resource {
1151104c19SKees Cook unsigned long addr;
1251104c19SKees Cook size_t size;
1351104c19SKees Cook };
1451104c19SKees Cook
1551104c19SKees Cook /* vm_mmap() arguments */
1651104c19SKees Cook struct kunit_vm_mmap_params {
1751104c19SKees Cook struct file *file;
1851104c19SKees Cook unsigned long addr;
1951104c19SKees Cook unsigned long len;
2051104c19SKees Cook unsigned long prot;
2151104c19SKees Cook unsigned long flag;
2251104c19SKees Cook unsigned long offset;
2351104c19SKees Cook };
2451104c19SKees Cook
2551104c19SKees Cook /* Create and attach a new mm if it doesn't already exist. */
kunit_attach_mm(void)2651104c19SKees Cook static int kunit_attach_mm(void)
2751104c19SKees Cook {
2851104c19SKees Cook struct mm_struct *mm;
2951104c19SKees Cook
3051104c19SKees Cook if (current->mm)
3151104c19SKees Cook return 0;
3251104c19SKees Cook
334d6cf248SKees Cook /* arch_pick_mmap_layout() is only sane with MMU systems. */
344d6cf248SKees Cook if (!IS_ENABLED(CONFIG_MMU))
354d6cf248SKees Cook return -EINVAL;
364d6cf248SKees Cook
3751104c19SKees Cook mm = mm_alloc();
3851104c19SKees Cook if (!mm)
3951104c19SKees Cook return -ENOMEM;
4051104c19SKees Cook
4151104c19SKees Cook /* Define the task size. */
4251104c19SKees Cook mm->task_size = TASK_SIZE;
4351104c19SKees Cook
4451104c19SKees Cook /* Make sure we can allocate new VMAs. */
4551104c19SKees Cook arch_pick_mmap_layout(mm, ¤t->signal->rlim[RLIMIT_STACK]);
4651104c19SKees Cook
4751104c19SKees Cook /* Attach the mm. It will be cleaned up when the process dies. */
4851104c19SKees Cook kthread_use_mm(mm);
4951104c19SKees Cook
5051104c19SKees Cook return 0;
5151104c19SKees Cook }
5251104c19SKees Cook
kunit_vm_mmap_init(struct kunit_resource * res,void * context)5351104c19SKees Cook static int kunit_vm_mmap_init(struct kunit_resource *res, void *context)
5451104c19SKees Cook {
5551104c19SKees Cook struct kunit_vm_mmap_params *p = context;
5651104c19SKees Cook struct kunit_vm_mmap_resource vres;
5751104c19SKees Cook int ret;
5851104c19SKees Cook
5951104c19SKees Cook ret = kunit_attach_mm();
6051104c19SKees Cook if (ret)
6151104c19SKees Cook return ret;
6251104c19SKees Cook
6351104c19SKees Cook vres.size = p->len;
6451104c19SKees Cook vres.addr = vm_mmap(p->file, p->addr, p->len, p->prot, p->flag, p->offset);
6551104c19SKees Cook if (!vres.addr)
6651104c19SKees Cook return -ENOMEM;
6751104c19SKees Cook res->data = kmemdup(&vres, sizeof(vres), GFP_KERNEL);
6851104c19SKees Cook if (!res->data) {
6951104c19SKees Cook vm_munmap(vres.addr, vres.size);
7051104c19SKees Cook return -ENOMEM;
7151104c19SKees Cook }
7251104c19SKees Cook
7351104c19SKees Cook return 0;
7451104c19SKees Cook }
7551104c19SKees Cook
kunit_vm_mmap_free(struct kunit_resource * res)7651104c19SKees Cook static void kunit_vm_mmap_free(struct kunit_resource *res)
7751104c19SKees Cook {
7851104c19SKees Cook struct kunit_vm_mmap_resource *vres = res->data;
7951104c19SKees Cook
8051104c19SKees Cook /*
8151104c19SKees Cook * Since this is executed from the test monitoring process,
8251104c19SKees Cook * the test's mm has already been torn down. We don't need
8351104c19SKees Cook * to run vm_munmap(vres->addr, vres->size), only clean up
8451104c19SKees Cook * the vres.
8551104c19SKees Cook */
8651104c19SKees Cook
8751104c19SKees Cook kfree(vres);
8851104c19SKees Cook res->data = NULL;
8951104c19SKees Cook }
9051104c19SKees Cook
kunit_vm_mmap(struct kunit * test,struct file * file,unsigned long addr,unsigned long len,unsigned long prot,unsigned long flag,unsigned long offset)9151104c19SKees Cook unsigned long kunit_vm_mmap(struct kunit *test, struct file *file,
9251104c19SKees Cook unsigned long addr, unsigned long len,
9351104c19SKees Cook unsigned long prot, unsigned long flag,
9451104c19SKees Cook unsigned long offset)
9551104c19SKees Cook {
9651104c19SKees Cook struct kunit_vm_mmap_params params = {
9751104c19SKees Cook .file = file,
9851104c19SKees Cook .addr = addr,
9951104c19SKees Cook .len = len,
10051104c19SKees Cook .prot = prot,
10151104c19SKees Cook .flag = flag,
10251104c19SKees Cook .offset = offset,
10351104c19SKees Cook };
10451104c19SKees Cook struct kunit_vm_mmap_resource *vres;
10551104c19SKees Cook
10651104c19SKees Cook vres = kunit_alloc_resource(test,
10751104c19SKees Cook kunit_vm_mmap_init,
10851104c19SKees Cook kunit_vm_mmap_free,
10951104c19SKees Cook GFP_KERNEL,
11051104c19SKees Cook ¶ms);
11151104c19SKees Cook if (vres)
11251104c19SKees Cook return vres->addr;
11351104c19SKees Cook return 0;
11451104c19SKees Cook }
11551104c19SKees Cook EXPORT_SYMBOL_GPL(kunit_vm_mmap);
11651104c19SKees Cook
117*cdd30ebbSPeter Zijlstra MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
118