mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 22:09:30 +02:00
selftests/mm: add HMM test for mmap lock-dropping faults
Add test_hmm coverage for the HMM lock-dropping fault path. The test module gets a new HMM_DMIRROR_READ_UNLOCKED ioctl that calls hmm_range_fault_unlocked_timeout() with a timeout of 0, exercising the unbounded retry mode while allowing the mmap lock to be dropped during fault handling. Add a userfaultfd_read selftest that registers an anonymous mapping with UFFDIO_REGISTER_MODE_MISSING, services the faults from a handler thread with UFFDIO_COPY, and verifies that HMM can read back the data supplied by the handler. This exercises the path where handle_mm_fault() drops mmap_lock and hmm_range_fault_unlocked_timeout() restarts the walk internally. Assisted-by: GitHub-Copilot:claude-opus-4.6 Link: https://lore.kernel.org/20260723-hmm-v10-v11-3-c55b003a4b61@gmail.com Signed-off-by: Stanislav Kinsburskii <skinsburskii@gmail.com> Cc: Danilo Krummrich <dakr@kernel.org> Cc: Dave Airlie <airlied@gmail.com> Cc: David Hildenbrand <david@kernel.org> Cc: Dexuan Cui <decui@microsoft.com> Cc: Haiyang Zhang <haiyangz@microsoft.com> Cc: Jason Gunthorpe <jgg@nvidia.com> Cc: Jonathan Corbet <corbet@lwn.net> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Leon Romanovsky <leon@kernel.org> Cc: Liam R. Howlett <liam@infradead.org> Cc: Lizhi Hou <lizhi.hou@amd.com> Cc: Long Li <longli@microsoft.com> Cc: Lorenzo Stoakes <ljs@kernel.org> Cc: Lyude <lyude@redhat.com> Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com> Cc: Maxime Ripard <mripard@kernel.org> Cc: Michal Hocko <mhocko@suse.com> Cc: Mike Rapoport <rppt@kernel.org> Cc: Oded Gabbay <ogabbay@kernel.org> Cc: Shuah Khan <shuah@kernel.org> Cc: Suren Baghdasaryan <surenb@google.com> Cc: Thomas Zimemrmann <tzimmermann@suse.de> Cc: Vlastimil Babka <vbabka@kernel.org> Cc: Wei Liu <wei.liu@kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
committed by
Andrew Morton
parent
1211708312
commit
9365813cad
+106
-1
@@ -389,6 +389,67 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dmirror_range_fault_unlocked(struct dmirror *dmirror,
|
||||
struct hmm_range *range,
|
||||
unsigned long timeout)
|
||||
{
|
||||
int ret;
|
||||
|
||||
while (true) {
|
||||
ret = hmm_range_fault_unlocked_timeout(range, timeout);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
mutex_lock(&dmirror->mutex);
|
||||
if (mmu_interval_read_retry(range->notifier,
|
||||
range->notifier_seq)) {
|
||||
mutex_unlock(&dmirror->mutex);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ret = dmirror_do_fault(dmirror, range);
|
||||
|
||||
mutex_unlock(&dmirror->mutex);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dmirror_fault_unlocked(struct dmirror *dmirror,
|
||||
unsigned long start,
|
||||
unsigned long end, bool write,
|
||||
unsigned long timeout)
|
||||
{
|
||||
struct mm_struct *mm = dmirror->notifier.mm;
|
||||
unsigned long addr;
|
||||
unsigned long pfns[32];
|
||||
struct hmm_range range = {
|
||||
.notifier = &dmirror->notifier,
|
||||
.hmm_pfns = pfns,
|
||||
.pfn_flags_mask = 0,
|
||||
.default_flags =
|
||||
HMM_PFN_REQ_FAULT | (write ? HMM_PFN_REQ_WRITE : 0),
|
||||
.dev_private_owner = dmirror->mdevice,
|
||||
};
|
||||
int ret = 0;
|
||||
|
||||
if (!mmget_not_zero(mm))
|
||||
return -EFAULT;
|
||||
|
||||
for (addr = start; addr < end; addr = range.end) {
|
||||
range.start = addr;
|
||||
range.end = min(addr + (ARRAY_SIZE(pfns) << PAGE_SHIFT), end);
|
||||
|
||||
ret = dmirror_range_fault_unlocked(dmirror, &range, timeout);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
mmput(mm);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dmirror_fault(struct dmirror *dmirror, unsigned long start,
|
||||
unsigned long end, bool write)
|
||||
{
|
||||
@@ -488,6 +549,48 @@ static int dmirror_read(struct dmirror *dmirror, struct hmm_dmirror_cmd *cmd)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dmirror_read_unlocked(struct dmirror *dmirror,
|
||||
struct hmm_dmirror_cmd *cmd,
|
||||
unsigned long timeout)
|
||||
{
|
||||
struct dmirror_bounce bounce;
|
||||
unsigned long start, end;
|
||||
unsigned long size = cmd->npages << PAGE_SHIFT;
|
||||
int ret;
|
||||
|
||||
start = cmd->addr;
|
||||
end = start + size;
|
||||
if (end < start)
|
||||
return -EINVAL;
|
||||
|
||||
ret = dmirror_bounce_init(&bounce, start, size);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
while (1) {
|
||||
mutex_lock(&dmirror->mutex);
|
||||
ret = dmirror_do_read(dmirror, start, end, &bounce);
|
||||
mutex_unlock(&dmirror->mutex);
|
||||
if (ret != -ENOENT)
|
||||
break;
|
||||
|
||||
start = cmd->addr + (bounce.cpages << PAGE_SHIFT);
|
||||
ret = dmirror_fault_unlocked(dmirror, start, end, false, timeout);
|
||||
if (ret)
|
||||
break;
|
||||
cmd->faults++;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
if (copy_to_user(u64_to_user_ptr(cmd->ptr), bounce.ptr,
|
||||
bounce.size))
|
||||
ret = -EFAULT;
|
||||
}
|
||||
cmd->cpages = bounce.cpages;
|
||||
dmirror_bounce_fini(&bounce);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dmirror_do_write(struct dmirror *dmirror, unsigned long start,
|
||||
unsigned long end, struct dmirror_bounce *bounce)
|
||||
{
|
||||
@@ -1572,7 +1675,9 @@ static long dmirror_fops_unlocked_ioctl(struct file *filp,
|
||||
dmirror->flags = cmd.npages;
|
||||
ret = 0;
|
||||
break;
|
||||
|
||||
case HMM_DMIRROR_READ_UNLOCKED:
|
||||
ret = dmirror_read_unlocked(dmirror, &cmd, 0);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ struct hmm_dmirror_cmd {
|
||||
#define HMM_DMIRROR_CHECK_EXCLUSIVE _IOWR('H', 0x06, struct hmm_dmirror_cmd)
|
||||
#define HMM_DMIRROR_RELEASE _IOWR('H', 0x07, struct hmm_dmirror_cmd)
|
||||
#define HMM_DMIRROR_FLAGS _IOWR('H', 0x08, struct hmm_dmirror_cmd)
|
||||
#define HMM_DMIRROR_READ_UNLOCKED _IOWR('H', 0x09, struct hmm_dmirror_cmd)
|
||||
|
||||
#define HMM_DMIRROR_FLAG_FAIL_ALLOC (1ULL << 0)
|
||||
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
#include <sys/mman.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <linux/userfaultfd.h>
|
||||
#include <poll.h>
|
||||
|
||||
/*
|
||||
* This is a private UAPI to the kernel test module so it isn't exported
|
||||
@@ -2952,4 +2956,150 @@ TEST_F_TIMEOUT(hmm, benchmark_thp_migration, 120)
|
||||
&thp_results, ®ular_results);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Test that HMM can fault in pages backed by userfaultfd using the
|
||||
* hmm_range_fault_unlocked_timeout() path with no timeout. This exercises
|
||||
* the lock-drop retry logic in the HMM framework.
|
||||
*/
|
||||
struct uffd_thread_args {
|
||||
int uffd;
|
||||
int stop_fd;
|
||||
void *page_buffer;
|
||||
unsigned long page_size;
|
||||
};
|
||||
|
||||
static void *uffd_handler_thread(void *arg)
|
||||
{
|
||||
struct uffd_thread_args *args = arg;
|
||||
struct uffd_msg msg;
|
||||
struct uffdio_copy copy;
|
||||
struct pollfd pollfd[2];
|
||||
int ret;
|
||||
|
||||
pollfd[0].fd = args->uffd;
|
||||
pollfd[0].events = POLLIN;
|
||||
pollfd[1].fd = args->stop_fd;
|
||||
pollfd[1].events = POLLIN;
|
||||
|
||||
while (1) {
|
||||
ret = poll(pollfd, 2, -1);
|
||||
if (ret <= 0)
|
||||
break;
|
||||
if (pollfd[1].revents)
|
||||
break;
|
||||
if (!(pollfd[0].revents & POLLIN))
|
||||
break;
|
||||
|
||||
ret = read(args->uffd, &msg, sizeof(msg));
|
||||
if (ret != sizeof(msg))
|
||||
break;
|
||||
|
||||
if (msg.event != UFFD_EVENT_PAGEFAULT)
|
||||
break;
|
||||
|
||||
/* Fill the page with a known pattern */
|
||||
memset(args->page_buffer, 0xAB, args->page_size);
|
||||
|
||||
copy.dst = msg.arg.pagefault.address & ~(args->page_size - 1);
|
||||
copy.src = (unsigned long)args->page_buffer;
|
||||
copy.len = args->page_size;
|
||||
copy.mode = 0;
|
||||
copy.copy = 0;
|
||||
|
||||
ret = ioctl(args->uffd, UFFDIO_COPY, ©);
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TEST_F(hmm, userfaultfd_read)
|
||||
{
|
||||
struct hmm_buffer *buffer;
|
||||
struct uffd_thread_args uffd_args;
|
||||
unsigned long npages;
|
||||
unsigned long size;
|
||||
unsigned long i;
|
||||
unsigned char *ptr;
|
||||
pthread_t thread;
|
||||
int uffd;
|
||||
int stop_fd;
|
||||
int ret;
|
||||
struct uffdio_api api;
|
||||
struct uffdio_register reg;
|
||||
uint64_t stop = 1;
|
||||
ssize_t nwrite;
|
||||
|
||||
npages = 4;
|
||||
size = npages << self->page_shift;
|
||||
|
||||
/* Create userfaultfd */
|
||||
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
|
||||
if (uffd < 0)
|
||||
SKIP(return, "userfaultfd not available");
|
||||
|
||||
api.api = UFFD_API;
|
||||
api.features = 0;
|
||||
ret = ioctl(uffd, UFFDIO_API, &api);
|
||||
ASSERT_EQ(ret, 0);
|
||||
|
||||
buffer = malloc(sizeof(*buffer));
|
||||
ASSERT_NE(buffer, NULL);
|
||||
|
||||
buffer->fd = -1;
|
||||
buffer->size = size;
|
||||
buffer->mirror = malloc(size);
|
||||
ASSERT_NE(buffer->mirror, NULL);
|
||||
|
||||
/* Create anonymous mapping */
|
||||
buffer->ptr = mmap(NULL, size,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS,
|
||||
-1, 0);
|
||||
ASSERT_NE(buffer->ptr, MAP_FAILED);
|
||||
|
||||
/* Register the region with userfaultfd */
|
||||
reg.range.start = (unsigned long)buffer->ptr;
|
||||
reg.range.len = size;
|
||||
reg.mode = UFFDIO_REGISTER_MODE_MISSING;
|
||||
ret = ioctl(uffd, UFFDIO_REGISTER, ®);
|
||||
ASSERT_EQ(ret, 0);
|
||||
|
||||
/* Set up the handler thread */
|
||||
uffd_args.uffd = uffd;
|
||||
stop_fd = eventfd(0, EFD_CLOEXEC);
|
||||
ASSERT_GE(stop_fd, 0);
|
||||
uffd_args.stop_fd = stop_fd;
|
||||
uffd_args.page_buffer = malloc(self->page_size);
|
||||
ASSERT_NE(uffd_args.page_buffer, NULL);
|
||||
uffd_args.page_size = self->page_size;
|
||||
|
||||
ret = pthread_create(&thread, NULL, uffd_handler_thread, &uffd_args);
|
||||
ASSERT_EQ(ret, 0);
|
||||
|
||||
/*
|
||||
* Use the unlocked read path which allows the mmap lock to be
|
||||
* dropped during the fault, enabling userfaultfd resolution.
|
||||
*/
|
||||
ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ_UNLOCKED,
|
||||
buffer, npages);
|
||||
ASSERT_EQ(ret, 0);
|
||||
ASSERT_EQ(buffer->cpages, npages);
|
||||
|
||||
/* Verify the device read the data filled by the uffd handler */
|
||||
ptr = buffer->mirror;
|
||||
for (i = 0; i < size; ++i)
|
||||
ASSERT_EQ(ptr[i], (unsigned char)0xAB);
|
||||
|
||||
nwrite = write(stop_fd, &stop, sizeof(stop));
|
||||
ASSERT_EQ(nwrite, sizeof(stop));
|
||||
pthread_join(thread, NULL);
|
||||
close(stop_fd);
|
||||
free(uffd_args.page_buffer);
|
||||
close(uffd);
|
||||
hmm_buffer_free(buffer);
|
||||
}
|
||||
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
|
||||
Reference in New Issue
Block a user