mm/gup_test: keep longterm pin state per file
Subsystems (2)
David Hildenbrand (Arm)
maintainerPATCH 1/1 mm/gup_test: keep longterm pin state per file
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.
---
Tested with cow+gup_longterm selftests that gup_test infrastructure keeps working.
--- mm/gup_test.c | 94 +++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 62 insertions(+), 32 deletions(-)
mm/gup_test.c +62 -32
@@ -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 @@ static int __gup_test_ioctl(unsigned int cmd,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,
---
base-commit: e5492213654050379e78ec6f9acfd6c9fe00f334
change-id: 20260810-gup_test_data-5a7e8111eef3
--
Cheers,
David
Quoted 13 lines · David Hildenbrand (Arm)
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
thegup_testinterface 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.candgup_longerm.cselftests concurrently, but we usually run them sequentially. Let's add a "Fixes" tag to be safe, but not need to CC stable.
Quoted 6 lines
Fixes c77369b437f9 ("mm/gup_test: start/stop/read functionality for PIN LONGTERM test")Reported-by yunhui cui <[email protected]>Tested-by Yunhui Cui <[email protected]>Signed-off-by David Hildenbrand (Arm) <[email protected]>---
Gave it a spin on x86_64 QEMU. All good, shared-state mess is gone :)