mm/gup_test: keep longterm pin state per file
authorDavid Hildenbrand (Arm) <david@kernel.org>
Mon, 10 Aug 2026 11:31:14 +0000 (13:31 +0200)
committerAndrew Morton <akpm@linux-foundation.org>
Tue, 25 Aug 2026 01:43:17 +0000 (18:43 -0700)
The pin longterm test currently stores its data globally, shared among
multiple concurrent users of the interface (multiple open file descriptors
-> multiple "struct file"'s).  That makes the gup_test interface
problematic to use concurrently: two users, such as concurrent selftest
runs, can interfere with the same longterm pin state.

While this has not been observed as a problem so far in practice, let's
just handle it cleanly.  There could be a way to trigger selftest failures
by e.g., running the cow.c and gup_longerm.c selftests concurrently, but
we usually run them sequentially.  Let's add a "Fixes" tag to be safe, but
not need to CC stable.

Link: https://lore.kernel.org/20260810-gup_test_data-v1-1-fb1d41be5bb4@kernel.org
Fixes: c77369b437f9 ("mm/gup_test: start/stop/read functionality for PIN LONGTERM test")
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Reported-by: yunhui cui <cuiyunhui@bytedance.com>
Closes: https://lore.kernel.org/r/20260608025043.88087-1-cuiyunhui@bytedance.com
Tested-by: Yunhui Cui <cuiyunhui@bytedance.com>
Tested-by: Lance Yang <lance.yang@linux.dev>
Cc: Jason Gunthorpe <jgg@ziepe.ca>
Cc: John Hubbard <jhubbard@nvidia.com>
Cc: Peter Xu <peterx@redhat.com>
Cc: Yang Li <yang.lee@linux.alibaba.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
mm/gup_test.c

index eb4c9cd..44c1cdf 100644 (file)
@@ -8,6 +8,12 @@
 #include <linux/highmem.h>
 #include "gup_test.h"
 
+struct gup_test_data {
+       struct mutex longterm_mutex;
+       struct page **longterm_pages;
+       unsigned long longterm_nr_pages;
+};
+
 static void put_back_pages(unsigned int cmd, struct page **pages,
                           unsigned long nr_pages, unsigned int gup_test_flags)
 {
@@ -208,23 +214,20 @@ free_pages:
        return ret;
 }
 
-static DEFINE_MUTEX(pin_longterm_test_mutex);
-static struct page **pin_longterm_test_pages;
-static unsigned long pin_longterm_test_nr_pages;
-
-static inline void pin_longterm_test_stop(void)
+static inline void pin_longterm_test_stop(struct gup_test_data *data)
 {
-       if (pin_longterm_test_pages) {
-               if (pin_longterm_test_nr_pages)
-                       unpin_user_pages(pin_longterm_test_pages,
-                                        pin_longterm_test_nr_pages);
-               kvfree(pin_longterm_test_pages);
-               pin_longterm_test_pages = NULL;
-               pin_longterm_test_nr_pages = 0;
+       if (data->longterm_pages) {
+               if (data->longterm_nr_pages)
+                       unpin_user_pages(data->longterm_pages,
+                                        data->longterm_nr_pages);
+               kvfree(data->longterm_pages);
+               data->longterm_pages = NULL;
+               data->longterm_nr_pages = 0;
        }
 }
 
-static inline int pin_longterm_test_start(unsigned long arg)
+static inline int pin_longterm_test_start(struct gup_test_data *data,
+               unsigned long arg)
 {
        long nr_pages, cur_pages, addr, remaining_pages;
        int gup_flags = FOLL_LONGTERM;
@@ -233,7 +236,7 @@ static inline int pin_longterm_test_start(unsigned long arg)
        int ret = 0;
        bool fast;
 
-       if (pin_longterm_test_pages)
+       if (data->longterm_pages)
                return -EINVAL;
 
        if (copy_from_user(&args, (void __user *)arg, sizeof(args)))
@@ -263,12 +266,12 @@ static inline int pin_longterm_test_start(unsigned long arg)
                return -EINTR;
        }
 
-       pin_longterm_test_pages = pages;
-       pin_longterm_test_nr_pages = 0;
+       data->longterm_pages = pages;
+       data->longterm_nr_pages = 0;
 
-       while (nr_pages - pin_longterm_test_nr_pages) {
-               remaining_pages = nr_pages - pin_longterm_test_nr_pages;
-               addr = args.addr + pin_longterm_test_nr_pages * PAGE_SIZE;
+       while (nr_pages - data->longterm_nr_pages) {
+               remaining_pages = nr_pages - data->longterm_nr_pages;
+               addr = args.addr + data->longterm_nr_pages * PAGE_SIZE;
 
                if (fast)
                        cur_pages = pin_user_pages_fast(addr, remaining_pages,
@@ -277,11 +280,11 @@ static inline int pin_longterm_test_start(unsigned long arg)
                        cur_pages = pin_user_pages(addr, remaining_pages,
                                                   gup_flags, pages);
                if (cur_pages < 0) {
-                       pin_longterm_test_stop();
+                       pin_longterm_test_stop(data);
                        ret = cur_pages;
                        break;
                }
-               pin_longterm_test_nr_pages += cur_pages;
+               data->longterm_nr_pages += cur_pages;
                pages += cur_pages;
        }
 
@@ -290,19 +293,20 @@ static inline int pin_longterm_test_start(unsigned long arg)
        return ret;
 }
 
-static inline int pin_longterm_test_read(unsigned long arg)
+static inline int pin_longterm_test_read(struct gup_test_data *data,
+               unsigned long arg)
 {
        __u64 user_addr;
        unsigned long i;
 
-       if (!pin_longterm_test_pages)
+       if (!data->longterm_pages)
                return -EINVAL;
 
        if (copy_from_user(&user_addr, (void __user *)arg, sizeof(user_addr)))
                return -EFAULT;
 
-       for (i = 0; i < pin_longterm_test_nr_pages; i++) {
-               void *addr = kmap_local_page(pin_longterm_test_pages[i]);
+       for (i = 0; i < data->longterm_nr_pages; i++) {
+               void *addr = kmap_local_page(data->longterm_pages[i]);
                unsigned long ret;
 
                ret = copy_to_user((void __user *)(unsigned long)user_addr, addr,
@@ -318,25 +322,26 @@ static inline int pin_longterm_test_read(unsigned long arg)
 static long pin_longterm_test_ioctl(struct file *filep, unsigned int cmd,
                                    unsigned long arg)
 {
+       struct gup_test_data *data = filep->private_data;
        int ret = -EINVAL;
 
-       if (mutex_lock_killable(&pin_longterm_test_mutex))
+       if (mutex_lock_killable(&data->longterm_mutex))
                return -EINTR;
 
        switch (cmd) {
        case PIN_LONGTERM_TEST_START:
-               ret = pin_longterm_test_start(arg);
+               ret = pin_longterm_test_start(data, arg);
                break;
        case PIN_LONGTERM_TEST_STOP:
-               pin_longterm_test_stop();
+               pin_longterm_test_stop(data);
                ret = 0;
                break;
        case PIN_LONGTERM_TEST_READ:
-               ret = pin_longterm_test_read(arg);
+               ret = pin_longterm_test_read(data, arg);
                break;
        }
 
-       mutex_unlock(&pin_longterm_test_mutex);
+       mutex_unlock(&data->longterm_mutex);
        return ret;
 }
 
@@ -375,15 +380,40 @@ static long gup_test_ioctl(struct file *filep, unsigned int cmd,
        return 0;
 }
 
+static int gup_test_open(struct inode *inode, struct file *file)
+{
+       struct gup_test_data *data;
+       int ret;
+
+       data = kzalloc_obj(*data);
+       if (!data)
+               return -ENOMEM;
+
+       ret = nonseekable_open(inode, file);
+       if (ret) {
+               kfree(data);
+               return ret;
+       }
+
+       mutex_init(&data->longterm_mutex);
+       file->private_data = data;
+       return 0;
+}
+
 static int gup_test_release(struct inode *inode, struct file *file)
 {
-       pin_longterm_test_stop();
+       struct gup_test_data *data = file->private_data;
+
+       pin_longterm_test_stop(data);
+       mutex_destroy(&data->longterm_mutex);
+       kfree(data);
+       file->private_data = NULL;
 
        return 0;
 }
 
 static const struct file_operations gup_test_fops = {
-       .open = nonseekable_open,
+       .open = gup_test_open,
        .unlocked_ioctl = gup_test_ioctl,
        .compat_ioctl = compat_ptr_ioctl,
        .release = gup_test_release,