| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * HMM stands for Heterogeneous Memory Management, it is a helper layer inside | |
| * the linux kernel to help device drivers mirror a process address space in | |
| * the device. This allows the device to use the same address space which | |
| * makes communication and data exchange a lot easier. | |
| * | |
| * This framework's sole purpose is to exercise various code paths inside | |
| * the kernel to make sure that HMM performs as expected and to flush out any | |
| * bugs. | |
| */ | |
| #include "kselftest_harness.h" | |
| #include "hugepage_settings.h" | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <stdint.h> | |
| #include <unistd.h> | |
| #include <strings.h> | |
| #include <time.h> | |
| #include <pthread.h> | |
| #include <limits.h> | |
| #include <linux/mman.h> | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #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 | |
| * in the usual include/uapi/... directory. | |
| */ | |
| #include <lib/test_hmm_uapi.h> | |
| #include <mm/gup_test.h> | |
| #include <mm/vm_util.h> | |
| struct hmm_buffer { | |
| void *ptr; | |
| void *mirror; | |
| unsigned long size; | |
| int fd; | |
| uint64_t cpages; | |
| uint64_t faults; | |
| }; | |
| enum { | |
| HMM_PRIVATE_DEVICE_ONE, | |
| HMM_PRIVATE_DEVICE_TWO, | |
| HMM_COHERENCE_DEVICE_ONE, | |
| HMM_COHERENCE_DEVICE_TWO, | |
| }; | |
| #define ONEKB (1 << 10) | |
| #define ONEMEG (1 << 20) | |
| #define TWOMEG (1 << 21) | |
| #define HMM_BUFFER_SIZE (1024 << 12) | |
| #define HMM_PATH_MAX 64 | |
| #define NTIMES 10 | |
| #define ALIGN(x, a) (((x) + (a - 1)) & (~((a) - 1))) | |
| /* Just the flags we need, copied from mm.h: */ | |
| #ifndef FOLL_WRITE | |
| #define FOLL_WRITE 0x01 /* check pte is writable */ | |
| #endif | |
| #ifndef FOLL_LONGTERM | |
| #define FOLL_LONGTERM 0x100 /* mapping lifetime is indefinite */ | |
| #endif | |
| HUGETLB_SETUP_DEFAULT_PAGES(1) | |
| FIXTURE(hmm) | |
| { | |
| int fd; | |
| unsigned int page_size; | |
| unsigned int page_shift; | |
| }; | |
| FIXTURE_VARIANT(hmm) | |
| { | |
| int device_number; | |
| }; | |
| FIXTURE_VARIANT_ADD(hmm, hmm_device_private) | |
| { | |
| .device_number = HMM_PRIVATE_DEVICE_ONE, | |
| }; | |
| FIXTURE_VARIANT_ADD(hmm, hmm_device_coherent) | |
| { | |
| .device_number = HMM_COHERENCE_DEVICE_ONE, | |
| }; | |
| FIXTURE(hmm2) | |
| { | |
| int fd0; | |
| int fd1; | |
| unsigned int page_size; | |
| unsigned int page_shift; | |
| }; | |
| FIXTURE_VARIANT(hmm2) | |
| { | |
| int device_number0; | |
| int device_number1; | |
| }; | |
| FIXTURE_VARIANT_ADD(hmm2, hmm2_device_private) | |
| { | |
| .device_number0 = HMM_PRIVATE_DEVICE_ONE, | |
| .device_number1 = HMM_PRIVATE_DEVICE_TWO, | |
| }; | |
| FIXTURE_VARIANT_ADD(hmm2, hmm2_device_coherent) | |
| { | |
| .device_number0 = HMM_COHERENCE_DEVICE_ONE, | |
| .device_number1 = HMM_COHERENCE_DEVICE_TWO, | |
| }; | |
| static int hmm_open(int unit) | |
| { | |
| char pathname[HMM_PATH_MAX]; | |
| int fd; | |
| snprintf(pathname, sizeof(pathname), "/dev/hmm_dmirror%d", unit); | |
| fd = open(pathname, O_RDWR, 0); | |
| if (fd < 0) | |
| fprintf(stderr, "could not open hmm dmirror driver (%s)\n", | |
| pathname); | |
| return fd; | |
| } | |
| static bool hmm_is_coherent_type(int dev_num) | |
| { | |
| return (dev_num >= HMM_COHERENCE_DEVICE_ONE); | |
| } | |
| FIXTURE_SETUP(hmm) | |
| { | |
| self->page_size = sysconf(_SC_PAGE_SIZE); | |
| self->page_shift = ffs(self->page_size) - 1; | |
| self->fd = hmm_open(variant->device_number); | |
| if (self->fd < 0 && hmm_is_coherent_type(variant->device_number)) | |
| SKIP(return, "DEVICE_COHERENT not available"); | |
| ASSERT_GE(self->fd, 0); | |
| } | |
| FIXTURE_SETUP(hmm2) | |
| { | |
| self->page_size = sysconf(_SC_PAGE_SIZE); | |
| self->page_shift = ffs(self->page_size) - 1; | |
| self->fd0 = hmm_open(variant->device_number0); | |
| if (self->fd0 < 0 && hmm_is_coherent_type(variant->device_number0)) | |
| SKIP(return, "DEVICE_COHERENT not available"); | |
| ASSERT_GE(self->fd0, 0); | |
| self->fd1 = hmm_open(variant->device_number1); | |
| ASSERT_GE(self->fd1, 0); | |
| } | |
| FIXTURE_TEARDOWN(hmm) | |
| { | |
| int ret = close(self->fd); | |
| ASSERT_EQ(ret, 0); | |
| self->fd = -1; | |
| } | |
| FIXTURE_TEARDOWN(hmm2) | |
| { | |
| int ret = close(self->fd0); | |
| ASSERT_EQ(ret, 0); | |
| self->fd0 = -1; | |
| ret = close(self->fd1); | |
| ASSERT_EQ(ret, 0); | |
| self->fd1 = -1; | |
| } | |
| static int hmm_dmirror_cmd(int fd, | |
| unsigned long request, | |
| struct hmm_buffer *buffer, | |
| unsigned long npages) | |
| { | |
| struct hmm_dmirror_cmd cmd; | |
| int ret; | |
| /* Simulate a device reading system memory. */ | |
| cmd.addr = (__u64)buffer->ptr; | |
| cmd.ptr = (__u64)buffer->mirror; | |
| cmd.npages = npages; | |
| for (;;) { | |
| ret = ioctl(fd, request, &cmd); | |
| if (ret == 0) | |
| break; | |
| if (errno == EINTR) | |
| continue; | |
| return -errno; | |
| } | |
| buffer->cpages = cmd.cpages; | |
| buffer->faults = cmd.faults; | |
| return 0; | |
| } | |
| static void hmm_buffer_free(struct hmm_buffer *buffer) | |
| { | |
| if (buffer == NULL) | |
| return; | |
| if (buffer->ptr) { | |
| munmap(buffer->ptr, buffer->size); | |
| buffer->ptr = NULL; | |
| } | |
| free(buffer->mirror); | |
| free(buffer); | |
| } | |
| /* | |
| * Allocate a buffer structure with memory mapping and mirror. | |
| * | |
| * @mmap_size: total size of the mmap region (may differ from @mirror_size | |
| * for alignment padding in THP tests). | |
| * @mirror_size: size of the mirror data buffer (the actual working set). | |
| * @prot: protection flags for the mmap (e.g. PROT_READ | PROT_WRITE). | |
| * @flags: flags for the mmap (e.g. MAP_PRIVATE, MAP_SHARED, | |
| * MAP_ANONYMOUS, MAP_HUGETLB). | |
| * @fd: file descriptor for the mmap; pass -1 for MAP_ANONYMOUS. | |
| * | |
| * All internal allocations are checked; returns NULL and cleans up on any | |
| * failure. Caller must ASSERT_NE or otherwise check the return value. | |
| */ | |
| static struct hmm_buffer *hmm_buffer_alloc(unsigned long mmap_size, | |
| unsigned long mirror_size, | |
| int prot, int flags, | |
| int fd) | |
| { | |
| struct hmm_buffer *buffer; | |
| buffer = malloc(sizeof(*buffer)); | |
| if (!buffer) { | |
| perror("malloc buffer"); | |
| return NULL; | |
| } | |
| buffer->fd = fd; | |
| buffer->size = mmap_size; | |
| buffer->mirror = malloc(mirror_size); | |
| if (!buffer->mirror) { | |
| perror("malloc mirror"); | |
| free(buffer); | |
| return NULL; | |
| } | |
| buffer->ptr = mmap(NULL, mmap_size, prot, flags, fd, 0); | |
| if (buffer->ptr == MAP_FAILED) { | |
| perror("mmap"); | |
| free(buffer->mirror); | |
| free(buffer); | |
| return NULL; | |
| } | |
| return buffer; | |
| } | |
| /* | |
| * Create a temporary file that will be deleted on close. | |
| */ | |
| static int hmm_create_file(unsigned long size) | |
| { | |
| char path[HMM_PATH_MAX]; | |
| int fd; | |
| strcpy(path, "/tmp"); | |
| fd = open(path, O_TMPFILE | O_EXCL | O_RDWR, 0600); | |
| if (fd >= 0) { | |
| int r; | |
| do { | |
| r = ftruncate(fd, size); | |
| } while (r == -1 && errno == EINTR); | |
| if (!r) | |
| return fd; | |
| close(fd); | |
| } | |
| return -1; | |
| } | |
| /* | |
| * Return a random unsigned number. | |
| */ | |
| static unsigned int hmm_random(void) | |
| { | |
| static int fd = -1; | |
| unsigned int r; | |
| if (fd < 0) { | |
| fd = open("/dev/urandom", O_RDONLY); | |
| if (fd < 0) { | |
| fprintf(stderr, "%s:%d failed to open /dev/urandom\n", | |
| __FILE__, __LINE__); | |
| return ~0U; | |
| } | |
| } | |
| read(fd, &r, sizeof(r)); | |
| return r; | |
| } | |
| static void hmm_nanosleep(unsigned int n) | |
| { | |
| struct timespec t; | |
| t.tv_sec = 0; | |
| t.tv_nsec = n; | |
| nanosleep(&t, NULL); | |
| } | |
| static int hmm_migrate_sys_to_dev(int fd, | |
| struct hmm_buffer *buffer, | |
| unsigned long npages) | |
| { | |
| return hmm_dmirror_cmd(fd, HMM_DMIRROR_MIGRATE_TO_DEV, buffer, npages); | |
| } | |
| static int hmm_migrate_dev_to_sys(int fd, | |
| struct hmm_buffer *buffer, | |
| unsigned long npages) | |
| { | |
| return hmm_dmirror_cmd(fd, HMM_DMIRROR_MIGRATE_TO_SYS, buffer, npages); | |
| } | |
| /* | |
| * Simple NULL test of device open/close. | |
| */ | |
| TEST_F(hmm, open_close) | |
| { | |
| } | |
| /* | |
| * Read private anonymous memory. | |
| */ | |
| TEST_F(hmm, anon_read) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| int *ptr; | |
| int ret; | |
| int val; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* | |
| * Initialize buffer in system memory but leave the first two pages | |
| * zero (pte_none and pfn_zero). | |
| */ | |
| i = 2 * self->page_size / sizeof(*ptr); | |
| for (ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Set buffer permission to read-only. */ | |
| ret = mprotect(buffer->ptr, size, PROT_READ); | |
| ASSERT_EQ(ret, 0); | |
| /* Populate the CPU page table with a special zero page. */ | |
| val = *(int *)(buffer->ptr + self->page_size); | |
| ASSERT_EQ(val, 0); | |
| /* Simulate a device reading system memory. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| ASSERT_EQ(buffer->faults, 1); | |
| /* Check what the device read. */ | |
| ptr = buffer->mirror; | |
| for (i = 0; i < 2 * self->page_size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], 0); | |
| for (; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Read private anonymous memory which has been protected with | |
| * mprotect() PROT_NONE. | |
| */ | |
| TEST_F(hmm, anon_read_prot) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| int *ptr; | |
| int ret; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Initialize mirror buffer so we can verify it isn't written. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = -i; | |
| /* Protect buffer from reading. */ | |
| ret = mprotect(buffer->ptr, size, PROT_NONE); | |
| ASSERT_EQ(ret, 0); | |
| /* Simulate a device reading system memory. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ, buffer, npages); | |
| ASSERT_EQ(ret, -EFAULT); | |
| /* Allow CPU to read the buffer so we can check it. */ | |
| ret = mprotect(buffer->ptr, size, PROT_READ); | |
| ASSERT_EQ(ret, 0); | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], -i); | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Write private anonymous memory. | |
| */ | |
| TEST_F(hmm, anon_write) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| int *ptr; | |
| int ret; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* Initialize data that the device will write to buffer->ptr. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Simulate a device writing system memory. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_WRITE, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| ASSERT_EQ(buffer->faults, 1); | |
| /* Check what the device wrote. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Write private anonymous memory which has been protected with | |
| * mprotect() PROT_READ. | |
| */ | |
| TEST_F(hmm, anon_write_prot) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| int *ptr; | |
| int ret; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* Simulate a device reading a zero page of memory. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ, buffer, 1); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, 1); | |
| ASSERT_EQ(buffer->faults, 1); | |
| /* Initialize data that the device will write to buffer->ptr. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Simulate a device writing system memory. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_WRITE, buffer, npages); | |
| ASSERT_EQ(ret, -EPERM); | |
| /* Check what the device wrote. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], 0); | |
| /* Now allow writing and see that the zero page is replaced. */ | |
| ret = mprotect(buffer->ptr, size, PROT_WRITE | PROT_READ); | |
| ASSERT_EQ(ret, 0); | |
| /* Simulate a device writing system memory. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_WRITE, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| ASSERT_EQ(buffer->faults, 1); | |
| /* Check what the device wrote. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Check that a device writing an anonymous private mapping | |
| * will copy-on-write if a child process inherits the mapping. | |
| * | |
| * Also verifies after fork() memory the device can be read by child. | |
| */ | |
| TEST_F(hmm, anon_write_child) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| void *old_ptr; | |
| void *map; | |
| int *ptr; | |
| pid_t pid; | |
| int child_fd; | |
| int ret, use_thp, migrate; | |
| for (migrate = 0; migrate < 2; ++migrate) { | |
| for (use_thp = 0; use_thp < 2; ++use_thp) { | |
| npages = ALIGN(use_thp ? read_pmd_pagesize() : HMM_BUFFER_SIZE, | |
| self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size * 2, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| old_ptr = buffer->ptr; | |
| if (use_thp) { | |
| map = (void *)ALIGN((uintptr_t)buffer->ptr, size); | |
| ret = madvise(map, size, MADV_HUGEPAGE); | |
| ASSERT_EQ(ret, 0); | |
| buffer->ptr = map; | |
| } | |
| /* Initialize buffer->ptr so we can tell if it is written. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Initialize data that the device will write to buffer->ptr. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = -i; | |
| if (migrate) { | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| } | |
| pid = fork(); | |
| if (pid == -1) | |
| ASSERT_EQ(pid, 0); | |
| if (pid != 0) { | |
| waitpid(pid, &ret, 0); | |
| ASSERT_EQ(WIFEXITED(ret), 1); | |
| /* Check that the parent's buffer did not change. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| buffer->ptr = old_ptr; | |
| hmm_buffer_free(buffer); | |
| continue; | |
| } | |
| /* Check that we see the parent's values. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| if (!migrate) { | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], -i); | |
| } | |
| /* The child process needs its own mirror to its own mm. */ | |
| child_fd = hmm_open(0); | |
| ASSERT_GE(child_fd, 0); | |
| /* Simulate a device writing system memory. */ | |
| ret = hmm_dmirror_cmd(child_fd, HMM_DMIRROR_WRITE, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| ASSERT_EQ(buffer->faults, 1); | |
| /* Check what the device wrote. */ | |
| if (!migrate) { | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], -i); | |
| } | |
| close(child_fd); | |
| _exit(0); | |
| } | |
| } | |
| } | |
| /* | |
| * Check that a device writing an anonymous shared mapping | |
| * will not copy-on-write if a child process inherits the mapping. | |
| */ | |
| TEST_F(hmm, anon_write_child_shared) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| int *ptr; | |
| pid_t pid; | |
| int child_fd; | |
| int ret; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_SHARED | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* Initialize buffer->ptr so we can tell if it is written. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Initialize data that the device will write to buffer->ptr. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = -i; | |
| pid = fork(); | |
| if (pid == -1) | |
| ASSERT_EQ(pid, 0); | |
| if (pid != 0) { | |
| waitpid(pid, &ret, 0); | |
| ASSERT_EQ(WIFEXITED(ret), 1); | |
| /* Check that the parent's buffer did change. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], -i); | |
| return; | |
| } | |
| /* Check that we see the parent's values. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], -i); | |
| /* The child process needs its own mirror to its own mm. */ | |
| child_fd = hmm_open(0); | |
| ASSERT_GE(child_fd, 0); | |
| /* Simulate a device writing system memory. */ | |
| ret = hmm_dmirror_cmd(child_fd, HMM_DMIRROR_WRITE, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| ASSERT_EQ(buffer->faults, 1); | |
| /* Check what the device wrote. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], -i); | |
| close(child_fd); | |
| _exit(0); | |
| } | |
| /* | |
| * Write private anonymous huge page. | |
| */ | |
| TEST_F(hmm, anon_write_huge) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| void *old_ptr; | |
| void *map; | |
| int *ptr; | |
| int ret; | |
| size = 2 * read_pmd_pagesize(); | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| size /= 2; | |
| npages = size >> self->page_shift; | |
| map = (void *)ALIGN((uintptr_t)buffer->ptr, size); | |
| ret = madvise(map, size, MADV_HUGEPAGE); | |
| ASSERT_EQ(ret, 0); | |
| old_ptr = buffer->ptr; | |
| buffer->ptr = map; | |
| /* Initialize data that the device will write to buffer->ptr. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Simulate a device writing system memory. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_WRITE, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| ASSERT_EQ(buffer->faults, 1); | |
| /* Check what the device wrote. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| buffer->ptr = old_ptr; | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Write huge TLBFS page. | |
| */ | |
| TEST_F(hmm, anon_write_hugetlbfs) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long default_hsize = default_huge_page_size(); | |
| unsigned long i; | |
| int *ptr; | |
| int ret; | |
| if (!hugetlb_free_default_pages()) | |
| SKIP(return, "Not enough huge pages"); | |
| size = ALIGN(TWOMEG, default_hsize); | |
| npages = size >> self->page_shift; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1); | |
| if (!buffer) | |
| SKIP(return, "Huge page could not be allocated"); | |
| /* Initialize data that the device will write to buffer->ptr. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Simulate a device writing system memory. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_WRITE, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| ASSERT_EQ(buffer->faults, 1); | |
| /* Check what the device wrote. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| munmap(buffer->ptr, buffer->size); | |
| buffer->ptr = NULL; | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Read mmap'ed file memory. | |
| */ | |
| TEST_F(hmm, file_read) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| int *ptr; | |
| int ret; | |
| int fd; | |
| ssize_t len; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| fd = hmm_create_file(size); | |
| ASSERT_GE(fd, 0); | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ, | |
| MAP_SHARED, fd); | |
| ASSERT_NE(buffer, NULL); | |
| /* Write initial contents of the file. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| len = pwrite(fd, buffer->mirror, size, 0); | |
| ASSERT_EQ(len, size); | |
| memset(buffer->mirror, 0, size); | |
| /* Simulate a device reading system memory. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| ASSERT_EQ(buffer->faults, 1); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Write mmap'ed file memory. | |
| */ | |
| TEST_F(hmm, file_write) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| int *ptr; | |
| int ret; | |
| int fd; | |
| ssize_t len; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| fd = hmm_create_file(size); | |
| ASSERT_GE(fd, 0); | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_SHARED, fd); | |
| ASSERT_NE(buffer, NULL); | |
| /* Initialize data that the device will write to buffer->ptr. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Simulate a device writing system memory. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_WRITE, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| ASSERT_EQ(buffer->faults, 1); | |
| /* Check what the device wrote. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| /* Check that the device also wrote the file. */ | |
| len = pread(fd, buffer->mirror, size, 0); | |
| ASSERT_EQ(len, size); | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Migrate anonymous memory to device private memory. | |
| */ | |
| TEST_F(hmm, migrate) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| int *ptr; | |
| int ret; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Migrate memory to device. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Migrate private file memory to device private memory. | |
| */ | |
| TEST_F(hmm, migrate_file_private) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| int *ptr; | |
| int ret; | |
| int fd; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| fd = hmm_create_file(size); | |
| ASSERT_GE(fd, 0); | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE, fd); | |
| ASSERT_NE(buffer, NULL); | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Migrate memory to device. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Migrate anonymous memory to device private memory and fault some of it back | |
| * to system memory, then try migrating the resulting mix of system and device | |
| * private memory to the device. | |
| */ | |
| TEST_F(hmm, migrate_fault) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| int *ptr; | |
| int ret; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Migrate memory to device. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| /* Fault half the pages back to system memory and check them. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / (2 * sizeof(*ptr)); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| /* Migrate memory to the device again. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| hmm_buffer_free(buffer); | |
| } | |
| TEST_F(hmm, migrate_release) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| int *ptr; | |
| int ret; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Migrate memory to device. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| /* Release device memory. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_RELEASE, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| /* Fault pages back to system memory and check them. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / (2 * sizeof(*ptr)); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Migrate anonymous shared memory to device private memory. | |
| */ | |
| TEST_F(hmm, migrate_shared) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| int ret; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_SHARED | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* Migrate memory to device. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, -ENOENT); | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Try to migrate various memory types to device private memory. | |
| */ | |
| TEST_F(hmm2, migrate_mixed) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| int *ptr; | |
| unsigned char *p; | |
| int ret; | |
| int val; | |
| npages = 6; | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_NONE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| p = buffer->ptr; | |
| /* Migrating a protected area should be an error. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd1, buffer, npages); | |
| ASSERT_EQ(ret, -EINVAL); | |
| /* Punch a hole after the first page address. */ | |
| ret = munmap(buffer->ptr + self->page_size, self->page_size); | |
| ASSERT_EQ(ret, 0); | |
| /* We expect an error if the vma doesn't cover the range. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd1, buffer, 3); | |
| ASSERT_EQ(ret, -EINVAL); | |
| /* Page 2 will be a read-only zero page. */ | |
| ret = mprotect(buffer->ptr + 2 * self->page_size, self->page_size, | |
| PROT_READ); | |
| ASSERT_EQ(ret, 0); | |
| ptr = (int *)(buffer->ptr + 2 * self->page_size); | |
| val = *ptr + 3; | |
| ASSERT_EQ(val, 3); | |
| /* Page 3 will be read-only. */ | |
| ret = mprotect(buffer->ptr + 3 * self->page_size, self->page_size, | |
| PROT_READ | PROT_WRITE); | |
| ASSERT_EQ(ret, 0); | |
| ptr = (int *)(buffer->ptr + 3 * self->page_size); | |
| *ptr = val; | |
| ret = mprotect(buffer->ptr + 3 * self->page_size, self->page_size, | |
| PROT_READ); | |
| ASSERT_EQ(ret, 0); | |
| /* Page 4-5 will be read-write. */ | |
| ret = mprotect(buffer->ptr + 4 * self->page_size, 2 * self->page_size, | |
| PROT_READ | PROT_WRITE); | |
| ASSERT_EQ(ret, 0); | |
| ptr = (int *)(buffer->ptr + 4 * self->page_size); | |
| *ptr = val; | |
| ptr = (int *)(buffer->ptr + 5 * self->page_size); | |
| *ptr = val; | |
| /* Now try to migrate pages 2-5 to device 1. */ | |
| buffer->ptr = p + 2 * self->page_size; | |
| ret = hmm_migrate_sys_to_dev(self->fd1, buffer, 4); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, 4); | |
| /* Page 5 won't be migrated to device 0 because it's on device 1. */ | |
| buffer->ptr = p + 5 * self->page_size; | |
| ret = hmm_migrate_sys_to_dev(self->fd0, buffer, 1); | |
| ASSERT_EQ(ret, -ENOENT); | |
| buffer->ptr = p; | |
| buffer->ptr = p; | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Migrate anonymous memory to device memory and back to system memory | |
| * multiple times. In case of private zone configuration, this is done | |
| * through fault pages accessed by CPU. In case of coherent zone configuration, | |
| * the pages from the device should be explicitly migrated back to system memory. | |
| * The reason is Coherent device zone has coherent access by CPU, therefore | |
| * it will not generate any page fault. | |
| */ | |
| TEST_F(hmm, migrate_multiple) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| unsigned long c; | |
| int *ptr; | |
| int ret; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| for (c = 0; c < NTIMES; c++) { | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Migrate memory to device. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| /* Migrate back to system memory and check them. */ | |
| if (hmm_is_coherent_type(variant->device_number)) { | |
| ret = hmm_migrate_dev_to_sys(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| } | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| hmm_buffer_free(buffer); | |
| } | |
| } | |
| /* | |
| * Read anonymous memory multiple times. | |
| */ | |
| TEST_F(hmm, anon_read_multiple) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| unsigned long c; | |
| int *ptr; | |
| int ret; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| for (c = 0; c < NTIMES; c++) { | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i + c; | |
| /* Simulate a device reading system memory. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ, buffer, | |
| npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| ASSERT_EQ(buffer->faults, 1); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i + c); | |
| hmm_buffer_free(buffer); | |
| } | |
| } | |
| void *unmap_buffer(void *p) | |
| { | |
| struct hmm_buffer *buffer = p; | |
| /* Delay for a bit and then unmap buffer while it is being read. */ | |
| hmm_nanosleep(hmm_random() % 32000); | |
| munmap(buffer->ptr + buffer->size / 2, buffer->size / 2); | |
| buffer->ptr = NULL; | |
| return NULL; | |
| } | |
| /* | |
| * Try reading anonymous memory while it is being unmapped. | |
| */ | |
| TEST_F(hmm, anon_teardown) | |
| { | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long c; | |
| void *ret; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| for (c = 0; c < NTIMES; ++c) { | |
| pthread_t thread; | |
| struct hmm_buffer *buffer; | |
| unsigned long i; | |
| int *ptr; | |
| int rc; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i + c; | |
| rc = pthread_create(&thread, NULL, unmap_buffer, buffer); | |
| ASSERT_EQ(rc, 0); | |
| /* Simulate a device reading system memory. */ | |
| rc = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ, buffer, | |
| npages); | |
| if (rc == 0) { | |
| ASSERT_EQ(buffer->cpages, npages); | |
| ASSERT_EQ(buffer->faults, 1); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; | |
| i < size / sizeof(*ptr); | |
| ++i) | |
| ASSERT_EQ(ptr[i], i + c); | |
| } | |
| pthread_join(thread, &ret); | |
| hmm_buffer_free(buffer); | |
| } | |
| } | |
| /* | |
| * Test memory snapshot without faulting in pages accessed by the device. | |
| */ | |
| TEST_F(hmm, mixedmap) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned char *m; | |
| int ret; | |
| npages = 1; | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, npages, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE, self->fd); | |
| ASSERT_NE(buffer, NULL); | |
| /* Simulate a device snapshotting CPU pagetables. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_SNAPSHOT, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device saw. */ | |
| m = buffer->mirror; | |
| ASSERT_EQ(m[0], HMM_DMIRROR_PROT_READ); | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Test memory snapshot without faulting in pages accessed by the device. | |
| */ | |
| TEST_F(hmm2, snapshot) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| int *ptr; | |
| unsigned char *p; | |
| unsigned char *m; | |
| int ret; | |
| int val; | |
| npages = 7; | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, npages, | |
| PROT_NONE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| p = buffer->ptr; | |
| /* Punch a hole after the first page address. */ | |
| ret = munmap(buffer->ptr + self->page_size, self->page_size); | |
| ASSERT_EQ(ret, 0); | |
| /* Page 2 will be read-only zero page. */ | |
| ret = mprotect(buffer->ptr + 2 * self->page_size, self->page_size, | |
| PROT_READ); | |
| ASSERT_EQ(ret, 0); | |
| ptr = (int *)(buffer->ptr + 2 * self->page_size); | |
| val = *ptr + 3; | |
| ASSERT_EQ(val, 3); | |
| /* Page 3 will be read-only. */ | |
| ret = mprotect(buffer->ptr + 3 * self->page_size, self->page_size, | |
| PROT_READ | PROT_WRITE); | |
| ASSERT_EQ(ret, 0); | |
| ptr = (int *)(buffer->ptr + 3 * self->page_size); | |
| *ptr = val; | |
| ret = mprotect(buffer->ptr + 3 * self->page_size, self->page_size, | |
| PROT_READ); | |
| ASSERT_EQ(ret, 0); | |
| /* Page 4-6 will be read-write. */ | |
| ret = mprotect(buffer->ptr + 4 * self->page_size, 3 * self->page_size, | |
| PROT_READ | PROT_WRITE); | |
| ASSERT_EQ(ret, 0); | |
| ptr = (int *)(buffer->ptr + 4 * self->page_size); | |
| *ptr = val; | |
| /* Page 5 will be migrated to device 0. */ | |
| buffer->ptr = p + 5 * self->page_size; | |
| ret = hmm_migrate_sys_to_dev(self->fd0, buffer, 1); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, 1); | |
| /* Page 6 will be migrated to device 1. */ | |
| buffer->ptr = p + 6 * self->page_size; | |
| ret = hmm_migrate_sys_to_dev(self->fd1, buffer, 1); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, 1); | |
| /* Simulate a device snapshotting CPU pagetables. */ | |
| buffer->ptr = p; | |
| ret = hmm_dmirror_cmd(self->fd0, HMM_DMIRROR_SNAPSHOT, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device saw. */ | |
| m = buffer->mirror; | |
| ASSERT_EQ(m[0], HMM_DMIRROR_PROT_ERROR); | |
| ASSERT_EQ(m[1], HMM_DMIRROR_PROT_ERROR); | |
| ASSERT_EQ(m[2], HMM_DMIRROR_PROT_ZERO | HMM_DMIRROR_PROT_READ); | |
| ASSERT_EQ(m[3], HMM_DMIRROR_PROT_READ); | |
| ASSERT_EQ(m[4], HMM_DMIRROR_PROT_WRITE); | |
| if (!hmm_is_coherent_type(variant->device_number0)) { | |
| ASSERT_EQ(m[5], HMM_DMIRROR_PROT_DEV_PRIVATE_LOCAL | | |
| HMM_DMIRROR_PROT_WRITE); | |
| ASSERT_EQ(m[6], HMM_DMIRROR_PROT_NONE); | |
| } else { | |
| ASSERT_EQ(m[5], HMM_DMIRROR_PROT_DEV_COHERENT_LOCAL | | |
| HMM_DMIRROR_PROT_WRITE); | |
| ASSERT_EQ(m[6], HMM_DMIRROR_PROT_DEV_COHERENT_REMOTE | | |
| HMM_DMIRROR_PROT_WRITE); | |
| } | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Test the hmm_range_fault() handling of large pages (PMD or PUD) | |
| * that should be mapped by a large page table entry. | |
| */ | |
| TEST_F(hmm, compound) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long default_hsize = default_huge_page_size(); | |
| int *ptr; | |
| unsigned char *m; | |
| unsigned char prot; | |
| int ret; | |
| unsigned long i; | |
| /* Skip test if we can't allocate a hugetlbfs page. */ | |
| if (!hugetlb_free_default_pages()) | |
| SKIP(return, "Not enough huge pages"); | |
| size = ALIGN(TWOMEG, default_hsize); | |
| npages = size >> self->page_shift; | |
| buffer = hmm_buffer_alloc(size, npages, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1); | |
| if (!buffer) | |
| SKIP(return, "Huge page could not be allocated"); | |
| /* Initialize the pages the device will snapshot in buffer->ptr. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Simulate a device snapshotting CPU pagetables. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_SNAPSHOT, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* | |
| * Check what the device saw. The region is backed by a single huge | |
| * page that the device reports either at PMD or at PUD level depending | |
| * on the configured default hugepage size. Determine that level from | |
| * the first page and require every page in the range to match it | |
| * exactly, so that a fragmented mapping mixing levels (or a missing | |
| * large-page bit) is still caught and reported with its actual value. | |
| */ | |
| m = buffer->mirror; | |
| prot = HMM_DMIRROR_PROT_WRITE | | |
| ((m[0] & HMM_DMIRROR_PROT_PUD) ? HMM_DMIRROR_PROT_PUD : | |
| HMM_DMIRROR_PROT_PMD); | |
| for (i = 0; i < npages; ++i) | |
| ASSERT_EQ(m[i], prot); | |
| /* Make the region read-only. */ | |
| ret = mprotect(buffer->ptr, size, PROT_READ); | |
| ASSERT_EQ(ret, 0); | |
| /* Simulate a device snapshotting CPU pagetables. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_SNAPSHOT, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* | |
| * Check what the device saw after mprotect(PROT_READ). Same | |
| * approach as above: determine the mapping level from the first | |
| * page and require every page to match it exactly. | |
| */ | |
| m = buffer->mirror; | |
| prot = HMM_DMIRROR_PROT_READ | | |
| ((m[0] & HMM_DMIRROR_PROT_PUD) ? HMM_DMIRROR_PROT_PUD : | |
| HMM_DMIRROR_PROT_PMD); | |
| for (i = 0; i < npages; ++i) | |
| ASSERT_EQ(m[i], prot); | |
| munmap(buffer->ptr, buffer->size); | |
| buffer->ptr = NULL; | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Test two devices reading the same memory (double mapped). | |
| */ | |
| TEST_F(hmm2, double_map) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| int *ptr; | |
| int ret; | |
| npages = 6; | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Make region read-only. */ | |
| ret = mprotect(buffer->ptr, size, PROT_READ); | |
| ASSERT_EQ(ret, 0); | |
| /* Simulate device 0 reading system memory. */ | |
| ret = hmm_dmirror_cmd(self->fd0, HMM_DMIRROR_READ, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| ASSERT_EQ(buffer->faults, 1); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| /* Simulate device 1 reading system memory. */ | |
| ret = hmm_dmirror_cmd(self->fd1, HMM_DMIRROR_READ, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| ASSERT_EQ(buffer->faults, 1); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| /* Migrate pages to device 1 and try to read from device 0. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd1, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| ret = hmm_dmirror_cmd(self->fd0, HMM_DMIRROR_READ, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| ASSERT_EQ(buffer->faults, 1); | |
| /* Check what device 0 read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Basic check of exclusive faulting. | |
| */ | |
| TEST_F(hmm, exclusive) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| int *ptr; | |
| int ret; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Map memory exclusively for device access. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_EXCLUSIVE, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| /* Fault pages back to system memory and check them. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i]++, i); | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i+1); | |
| /* Check atomic access revoked */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_CHECK_EXCLUSIVE, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| hmm_buffer_free(buffer); | |
| } | |
| TEST_F(hmm, exclusive_mprotect) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| int *ptr; | |
| int ret; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Map memory exclusively for device access. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_EXCLUSIVE, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| ret = mprotect(buffer->ptr, size, PROT_READ); | |
| ASSERT_EQ(ret, 0); | |
| /* Simulate a device writing system memory. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_WRITE, buffer, npages); | |
| ASSERT_EQ(ret, -EPERM); | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Check copy-on-write works. | |
| */ | |
| TEST_F(hmm, exclusive_cow) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| int *ptr; | |
| int ret; | |
| pid_t pid; | |
| int status; | |
| npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; | |
| ASSERT_NE(npages, 0); | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Map memory exclusively for device access. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_EXCLUSIVE, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| pid = fork(); | |
| if (pid == -1) | |
| ASSERT_EQ(pid, 0); | |
| if (pid == 0) { | |
| /* | |
| * Child verifies COW independently, then _exit(0)s so it does | |
| * not run the test teardown. A failed ASSERT_* here makes the | |
| * harness abort() the child, so the parent sees | |
| * !WIFEXITED(status) below and fails in turn. | |
| */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i]++, i); | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i + 1); | |
| _exit(0); | |
| } | |
| /* Parent: also increment to verify COW works for both processes. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i]++, i); | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i + 1); | |
| /* Parent: wait for child and then free the buffer. */ | |
| ASSERT_EQ(waitpid(pid, &status, 0), pid); | |
| ASSERT_TRUE(WIFEXITED(status)); | |
| ASSERT_EQ(WEXITSTATUS(status), 0); | |
| hmm_buffer_free(buffer); | |
| } | |
| static int gup_test_exec(int gup_fd, unsigned long addr, int cmd, | |
| int npages, int size, int flags) | |
| { | |
| struct gup_test gup = { | |
| .nr_pages_per_call = npages, | |
| .addr = addr, | |
| .gup_flags = FOLL_WRITE | flags, | |
| .size = size, | |
| }; | |
| if (ioctl(gup_fd, cmd, &gup)) { | |
| perror("ioctl on error\n"); | |
| return errno; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Test get user device pages through gup_test. Setting PIN_LONGTERM flag. | |
| * This should trigger a migration back to system memory for both, private | |
| * and coherent type pages. | |
| * This test makes use of gup_test module. Make sure GUP_TEST_CONFIG is added | |
| * to your configuration before you run it. | |
| */ | |
| TEST_F(hmm, hmm_gup_test) | |
| { | |
| struct hmm_buffer *buffer; | |
| int gup_fd; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| int *ptr; | |
| int ret; | |
| unsigned char *m; | |
| gup_fd = open("/sys/kernel/debug/gup_test", O_RDWR); | |
| if (gup_fd == -1) | |
| SKIP(return, "Skipping test, could not find gup_test driver"); | |
| npages = 4; | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Migrate memory to device. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| ASSERT_EQ(gup_test_exec(gup_fd, | |
| (unsigned long)buffer->ptr, | |
| GUP_BASIC_TEST, 1, self->page_size, 0), 0); | |
| ASSERT_EQ(gup_test_exec(gup_fd, | |
| (unsigned long)buffer->ptr + 1 * self->page_size, | |
| GUP_FAST_BENCHMARK, 1, self->page_size, 0), 0); | |
| ASSERT_EQ(gup_test_exec(gup_fd, | |
| (unsigned long)buffer->ptr + 2 * self->page_size, | |
| PIN_FAST_BENCHMARK, 1, self->page_size, FOLL_LONGTERM), 0); | |
| ASSERT_EQ(gup_test_exec(gup_fd, | |
| (unsigned long)buffer->ptr + 3 * self->page_size, | |
| PIN_LONGTERM_BENCHMARK, 1, self->page_size, 0), 0); | |
| /* Take snapshot to CPU pagetables */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_SNAPSHOT, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| m = buffer->mirror; | |
| if (hmm_is_coherent_type(variant->device_number)) { | |
| ASSERT_EQ(HMM_DMIRROR_PROT_DEV_COHERENT_LOCAL | HMM_DMIRROR_PROT_WRITE, m[0]); | |
| ASSERT_EQ(HMM_DMIRROR_PROT_DEV_COHERENT_LOCAL | HMM_DMIRROR_PROT_WRITE, m[1]); | |
| } else { | |
| ASSERT_EQ(HMM_DMIRROR_PROT_WRITE, m[0]); | |
| ASSERT_EQ(HMM_DMIRROR_PROT_WRITE, m[1]); | |
| } | |
| ASSERT_EQ(HMM_DMIRROR_PROT_WRITE, m[2]); | |
| ASSERT_EQ(HMM_DMIRROR_PROT_WRITE, m[3]); | |
| /* | |
| * Check again the content on the pages. Make sure there's no | |
| * corrupted data. | |
| */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| close(gup_fd); | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Test copy-on-write in device pages. | |
| * In case of writing to COW private page(s), a page fault will migrate pages | |
| * back to system memory first. Then, these pages will be duplicated. In case | |
| * of COW device coherent type, pages are duplicated directly from device | |
| * memory. | |
| */ | |
| TEST_F(hmm, hmm_cow_in_device) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| int *ptr; | |
| int ret; | |
| unsigned char *m; | |
| pid_t pid; | |
| int status; | |
| npages = 4; | |
| size = npages << self->page_shift; | |
| buffer = hmm_buffer_alloc(size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Migrate memory to device. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| pid = fork(); | |
| if (pid == -1) | |
| ASSERT_EQ(pid, 0); | |
| if (!pid) { | |
| /* Child process waits for SIGKILL from the parent. */ | |
| while (1) { | |
| } | |
| /* Should not reach this */ | |
| } | |
| /* Parent process writes to COW pages(s) and gets a | |
| * new copy in system. In case of device private pages, | |
| * this write causes a migration to system mem first. | |
| */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Terminate child and wait */ | |
| EXPECT_EQ(0, kill(pid, SIGKILL)); | |
| EXPECT_EQ(pid, waitpid(pid, &status, 0)); | |
| EXPECT_NE(0, WIFSIGNALED(status)); | |
| EXPECT_EQ(SIGKILL, WTERMSIG(status)); | |
| /* Take snapshot to CPU pagetables */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_SNAPSHOT, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| m = buffer->mirror; | |
| for (i = 0; i < npages; i++) | |
| ASSERT_EQ(HMM_DMIRROR_PROT_WRITE, m[i]); | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Migrate private anonymous huge empty page. | |
| */ | |
| TEST_F(hmm, migrate_anon_huge_empty) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| void *old_ptr; | |
| void *map; | |
| int *ptr; | |
| int ret; | |
| size = read_pmd_pagesize(); | |
| buffer = hmm_buffer_alloc(2 * size, size, | |
| PROT_READ, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| memset(buffer->mirror, 0xFF, size); | |
| npages = size >> self->page_shift; | |
| map = (void *)ALIGN((uintptr_t)buffer->ptr, size); | |
| ret = madvise(map, size, MADV_HUGEPAGE); | |
| ASSERT_EQ(ret, 0); | |
| old_ptr = buffer->ptr; | |
| buffer->ptr = map; | |
| /* Migrate memory to device. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], 0); | |
| buffer->ptr = old_ptr; | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Migrate private anonymous huge zero page. | |
| */ | |
| TEST_F(hmm, migrate_anon_huge_zero) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| void *old_ptr; | |
| void *map; | |
| int *ptr; | |
| int ret; | |
| int val; | |
| size = read_pmd_pagesize(); | |
| buffer = hmm_buffer_alloc(2 * size, size, | |
| PROT_READ, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| memset(buffer->mirror, 0xFF, size); | |
| npages = size >> self->page_shift; | |
| map = (void *)ALIGN((uintptr_t)buffer->ptr, size); | |
| ret = madvise(map, size, MADV_HUGEPAGE); | |
| ASSERT_EQ(ret, 0); | |
| old_ptr = buffer->ptr; | |
| buffer->ptr = map; | |
| /* Initialize a read-only zero huge page. */ | |
| val = *(int *)buffer->ptr; | |
| ASSERT_EQ(val, 0); | |
| /* Migrate memory to device. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], 0); | |
| /* Fault pages back to system memory and check them. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) { | |
| ASSERT_EQ(ptr[i], 0); | |
| /* If it asserts once, it probably will 500,000 times */ | |
| if (ptr[i] != 0) | |
| break; | |
| } | |
| buffer->ptr = old_ptr; | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Migrate private anonymous huge page and free. | |
| */ | |
| TEST_F(hmm, migrate_anon_huge_free) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| void *old_ptr; | |
| void *map; | |
| int *ptr; | |
| int ret; | |
| size = read_pmd_pagesize(); | |
| buffer = hmm_buffer_alloc(2 * size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| memset(buffer->mirror, 0xFF, size); | |
| npages = size >> self->page_shift; | |
| map = (void *)ALIGN((uintptr_t)buffer->ptr, size); | |
| ret = madvise(map, size, MADV_HUGEPAGE); | |
| ASSERT_EQ(ret, 0); | |
| old_ptr = buffer->ptr; | |
| buffer->ptr = map; | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Migrate memory to device. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| /* Try freeing it. */ | |
| ret = madvise(map, size, MADV_FREE); | |
| ASSERT_EQ(ret, 0); | |
| buffer->ptr = old_ptr; | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Migrate private anonymous huge page and fault back to sysmem. | |
| */ | |
| TEST_F(hmm, migrate_anon_huge_fault) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| unsigned char *m; | |
| uint64_t entry; | |
| void *old_ptr; | |
| void *map; | |
| int pagemap_fd; | |
| int *ptr; | |
| int ret; | |
| size = read_pmd_pagesize(); | |
| buffer = hmm_buffer_alloc(2 * size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| memset(buffer->mirror, 0xFF, size); | |
| npages = size >> self->page_shift; | |
| map = (void *)ALIGN((uintptr_t)buffer->ptr, size); | |
| old_ptr = buffer->ptr; | |
| buffer->ptr = map; | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| ret = madvise(map, size, MADV_COLLAPSE); | |
| ASSERT_EQ(ret, 0); | |
| /* Migrate memory to device. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| if (!hmm_is_coherent_type(variant->device_number)) { | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_SNAPSHOT, | |
| buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| m = buffer->mirror; | |
| for (i = 0; i < npages; ++i) | |
| ASSERT_EQ(m[i], HMM_DMIRROR_PROT_DEV_PRIVATE_LOCAL | | |
| HMM_DMIRROR_PROT_WRITE | | |
| HMM_DMIRROR_PROT_PMD); | |
| pagemap_fd = open("/proc/self/pagemap", O_RDONLY); | |
| ASSERT_GE(pagemap_fd, 0); | |
| for (i = 0; i < npages; ++i) { | |
| entry = pagemap_get_entry(pagemap_fd, | |
| (char *)buffer->ptr + i * self->page_size); | |
| ASSERT_NE(entry & PM_SWAP, 0); | |
| ASSERT_FALSE(PAGEMAP_PRESENT(entry)); | |
| } | |
| close(pagemap_fd); | |
| } | |
| /* Fault pages back to system memory and check them. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| buffer->ptr = old_ptr; | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Migrate memory and fault back to sysmem after partially unmapping. | |
| */ | |
| TEST_F(hmm, migrate_partial_unmap_fault) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size = read_pmd_pagesize(); | |
| unsigned long unmap_size; | |
| unsigned long offsets[3]; | |
| unsigned long i; | |
| void *old_ptr; | |
| void *map; | |
| int *ptr; | |
| int ret, j, use_thp; | |
| if (!size) | |
| size = TWOMEG; | |
| unmap_size = size / 2; | |
| offsets[0] = 0; | |
| offsets[1] = size / 4; | |
| offsets[2] = size / 2; | |
| for (use_thp = 0; use_thp < 2; ++use_thp) { | |
| for (j = 0; j < ARRAY_SIZE(offsets); ++j) { | |
| buffer = hmm_buffer_alloc(2 * size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| memset(buffer->mirror, 0xFF, size); | |
| npages = size >> self->page_shift; | |
| map = (void *)ALIGN((uintptr_t)buffer->ptr, size); | |
| if (use_thp) | |
| ret = madvise(map, size, MADV_HUGEPAGE); | |
| else | |
| ret = madvise(map, size, MADV_NOHUGEPAGE); | |
| ASSERT_EQ(ret, 0); | |
| old_ptr = buffer->ptr; | |
| buffer->ptr = map; | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Migrate memory to device. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| munmap(buffer->ptr + offsets[j], unmap_size); | |
| /* Fault pages back to system memory and check them. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| if (i * sizeof(int) < offsets[j] || | |
| i * sizeof(int) >= offsets[j] + unmap_size) | |
| ASSERT_EQ(ptr[i], i); | |
| buffer->ptr = old_ptr; | |
| hmm_buffer_free(buffer); | |
| } | |
| } | |
| } | |
| TEST_F(hmm, migrate_remap_fault) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size = read_pmd_pagesize(); | |
| unsigned long offsets[3]; | |
| unsigned long i; | |
| void *old_ptr, *new_ptr = NULL; | |
| void *map; | |
| int *ptr; | |
| int ret, j, use_thp, dont_unmap, before; | |
| if (!size) | |
| size = TWOMEG; | |
| offsets[0] = 0; | |
| offsets[1] = size / 4; | |
| offsets[2] = size / 2; | |
| for (before = 0; before < 2; ++before) { | |
| for (dont_unmap = 0; dont_unmap < 2; ++dont_unmap) { | |
| for (use_thp = 0; use_thp < 2; ++use_thp) { | |
| for (j = 0; j < ARRAY_SIZE(offsets); ++j) { | |
| int flags = MREMAP_MAYMOVE | MREMAP_FIXED; | |
| if (dont_unmap) | |
| flags |= MREMAP_DONTUNMAP; | |
| buffer = hmm_buffer_alloc(8 * size, size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| memset(buffer->mirror, 0xFF, size); | |
| npages = size >> self->page_shift; | |
| map = (void *)ALIGN((uintptr_t)buffer->ptr, size); | |
| if (use_thp) | |
| ret = madvise(map, size, MADV_HUGEPAGE); | |
| else | |
| ret = madvise(map, size, MADV_NOHUGEPAGE); | |
| ASSERT_EQ(ret, 0); | |
| old_ptr = buffer->ptr; | |
| munmap(map + size, size * 2); | |
| buffer->ptr = map; | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; | |
| i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| if (before) { | |
| new_ptr = mremap((void *)map, size, size, flags, | |
| map + size + offsets[j]); | |
| ASSERT_NE(new_ptr, MAP_FAILED); | |
| buffer->ptr = new_ptr; | |
| } | |
| /* Migrate memory to device. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; | |
| i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| if (!before) { | |
| new_ptr = mremap((void *)map, size, size, flags, | |
| map + size + offsets[j]); | |
| ASSERT_NE(new_ptr, MAP_FAILED); | |
| buffer->ptr = new_ptr; | |
| } | |
| /* Fault pages back to system memory and check them. */ | |
| for (i = 0, ptr = buffer->ptr; | |
| i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], i); | |
| munmap(new_ptr, size); | |
| buffer->ptr = old_ptr; | |
| hmm_buffer_free(buffer); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /* | |
| * Migrate private anonymous huge page with allocation errors. | |
| */ | |
| TEST_F(hmm, migrate_anon_huge_err) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| void *old_ptr; | |
| void *map; | |
| int *ptr; | |
| int ret; | |
| size = read_pmd_pagesize(); | |
| buffer = hmm_buffer_alloc(2 * size, 2 * size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| memset(buffer->mirror, 0xFF, 2 * size); | |
| old_ptr = buffer->ptr; | |
| npages = size >> self->page_shift; | |
| map = (void *)ALIGN((uintptr_t)old_ptr, size); | |
| ret = madvise(map, size, MADV_HUGEPAGE); | |
| ASSERT_EQ(ret, 0); | |
| buffer->ptr = map; | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Migrate memory to device but force a THP allocation error. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_FLAGS, buffer, | |
| HMM_DMIRROR_FLAG_FAIL_ALLOC); | |
| ASSERT_EQ(ret, 0); | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) { | |
| ASSERT_EQ(ptr[i], i); | |
| if (ptr[i] != i) | |
| break; | |
| } | |
| /* Try faulting back a single (PAGE_SIZE) page. */ | |
| ptr = buffer->ptr; | |
| ASSERT_EQ(ptr[2048], 2048); | |
| /* unmap and remap the region to reset things. */ | |
| ret = munmap(old_ptr, 2 * size); | |
| ASSERT_EQ(ret, 0); | |
| old_ptr = mmap(NULL, 2 * size, PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, buffer->fd, 0); | |
| ASSERT_NE(old_ptr, MAP_FAILED); | |
| map = (void *)ALIGN((uintptr_t)old_ptr, size); | |
| ret = madvise(map, size, MADV_HUGEPAGE); | |
| ASSERT_EQ(ret, 0); | |
| buffer->ptr = map; | |
| /* Initialize buffer in system memory. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ptr[i] = i; | |
| /* Migrate THP to device. */ | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* | |
| * Force an allocation error when faulting back a THP resident in the | |
| * device. | |
| */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_FLAGS, buffer, | |
| HMM_DMIRROR_FLAG_FAIL_ALLOC); | |
| ASSERT_EQ(ret, 0); | |
| ret = hmm_migrate_dev_to_sys(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ptr = buffer->ptr; | |
| ASSERT_EQ(ptr[2048], 2048); | |
| buffer->ptr = old_ptr; | |
| hmm_buffer_free(buffer); | |
| } | |
| /* | |
| * Migrate private anonymous huge zero page with allocation errors. | |
| */ | |
| TEST_F(hmm, migrate_anon_huge_zero_err) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages; | |
| unsigned long size; | |
| unsigned long i; | |
| void *old_ptr; | |
| void *map; | |
| int *ptr; | |
| int ret; | |
| size = read_pmd_pagesize(); | |
| buffer = hmm_buffer_alloc(2 * size, 2 * size, | |
| PROT_READ, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| ASSERT_NE(buffer, NULL); | |
| memset(buffer->mirror, 0xFF, 2 * size); | |
| old_ptr = buffer->ptr; | |
| npages = size >> self->page_shift; | |
| map = (void *)ALIGN((uintptr_t)old_ptr, size); | |
| ret = madvise(map, size, MADV_HUGEPAGE); | |
| ASSERT_EQ(ret, 0); | |
| buffer->ptr = map; | |
| /* Migrate memory to device but force a THP allocation error. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_FLAGS, buffer, | |
| HMM_DMIRROR_FLAG_FAIL_ALLOC); | |
| ASSERT_EQ(ret, 0); | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Check what the device read. */ | |
| for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], 0); | |
| /* Try faulting back a single (PAGE_SIZE) page. */ | |
| ptr = buffer->ptr; | |
| ASSERT_EQ(ptr[2048], 0); | |
| /* unmap and remap the region to reset things. */ | |
| ret = munmap(old_ptr, 2 * size); | |
| ASSERT_EQ(ret, 0); | |
| old_ptr = mmap(NULL, 2 * size, PROT_READ, | |
| MAP_PRIVATE | MAP_ANONYMOUS, buffer->fd, 0); | |
| ASSERT_NE(old_ptr, MAP_FAILED); | |
| map = (void *)ALIGN((uintptr_t)old_ptr, size); | |
| ret = madvise(map, size, MADV_HUGEPAGE); | |
| ASSERT_EQ(ret, 0); | |
| buffer->ptr = map; | |
| /* Initialize buffer in system memory (zero THP page). */ | |
| ret = ptr[0]; | |
| ASSERT_EQ(ret, 0); | |
| /* Migrate memory to device but force a THP allocation error. */ | |
| ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_FLAGS, buffer, | |
| HMM_DMIRROR_FLAG_FAIL_ALLOC); | |
| ASSERT_EQ(ret, 0); | |
| ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(buffer->cpages, npages); | |
| /* Fault the device memory back and check it. */ | |
| for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) | |
| ASSERT_EQ(ptr[i], 0); | |
| buffer->ptr = old_ptr; | |
| hmm_buffer_free(buffer); | |
| } | |
| struct benchmark_results { | |
| double sys_to_dev_time; | |
| double dev_to_sys_time; | |
| double throughput_s2d; | |
| double throughput_d2s; | |
| }; | |
| static double get_time_ms(void) | |
| { | |
| struct timeval tv; | |
| gettimeofday(&tv, NULL); | |
| return (tv.tv_sec * 1000.0) + (tv.tv_usec / 1000.0); | |
| } | |
| static void print_benchmark_results(const char *test_name, size_t buffer_size, | |
| struct benchmark_results *thp, | |
| struct benchmark_results *regular) | |
| { | |
| double s2d_improvement = ((regular->sys_to_dev_time - thp->sys_to_dev_time) / | |
| regular->sys_to_dev_time) * 100.0; | |
| double d2s_improvement = ((regular->dev_to_sys_time - thp->dev_to_sys_time) / | |
| regular->dev_to_sys_time) * 100.0; | |
| double throughput_s2d_improvement = ((thp->throughput_s2d - regular->throughput_s2d) / | |
| regular->throughput_s2d) * 100.0; | |
| double throughput_d2s_improvement = ((thp->throughput_d2s - regular->throughput_d2s) / | |
| regular->throughput_d2s) * 100.0; | |
| printf("\n=== %s (%.1f MB) ===\n", test_name, buffer_size / (1024.0 * 1024.0)); | |
| printf(" | With THP | Without THP | Improvement\n"); | |
| printf("---------------------------------------------------------------------\n"); | |
| printf("Sys->Dev Migration | %.3f ms | %.3f ms | %.1f%%\n", | |
| thp->sys_to_dev_time, regular->sys_to_dev_time, s2d_improvement); | |
| printf("Dev->Sys Migration | %.3f ms | %.3f ms | %.1f%%\n", | |
| thp->dev_to_sys_time, regular->dev_to_sys_time, d2s_improvement); | |
| printf("S->D Throughput | %.2f GB/s | %.2f GB/s | %.1f%%\n", | |
| thp->throughput_s2d, regular->throughput_s2d, throughput_s2d_improvement); | |
| printf("D->S Throughput | %.2f GB/s | %.2f GB/s | %.1f%%\n", | |
| thp->throughput_d2s, regular->throughput_d2s, throughput_d2s_improvement); | |
| } | |
| /* | |
| * Run a single migration benchmark | |
| * fd: file descriptor for hmm device | |
| * use_thp: whether to use THP | |
| * buffer_size: size of buffer to allocate | |
| * iterations: number of iterations | |
| * results: where to store results | |
| */ | |
| static inline int run_migration_benchmark(int fd, int use_thp, size_t buffer_size, | |
| int iterations, struct benchmark_results *results) | |
| { | |
| struct hmm_buffer *buffer; | |
| unsigned long npages = buffer_size / sysconf(_SC_PAGESIZE); | |
| double start, end; | |
| double s2d_total = 0, d2s_total = 0; | |
| int ret, i; | |
| int *ptr; | |
| buffer = hmm_buffer_alloc(buffer_size, buffer_size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1); | |
| if (!buffer) { | |
| ret = -1; | |
| goto cleanup; | |
| } | |
| memset(buffer->mirror, 0xFF, buffer_size); | |
| /* Apply THP hint if requested */ | |
| if (use_thp) | |
| ret = madvise(buffer->ptr, buffer_size, MADV_HUGEPAGE); | |
| else | |
| ret = madvise(buffer->ptr, buffer_size, MADV_NOHUGEPAGE); | |
| if (ret) | |
| goto cleanup; | |
| /* Initialize memory to make sure pages are allocated */ | |
| ptr = (int *)buffer->ptr; | |
| for (i = 0; i < buffer_size / sizeof(int); i++) | |
| ptr[i] = i & 0xFF; | |
| /* Warmup iteration */ | |
| ret = hmm_migrate_sys_to_dev(fd, buffer, npages); | |
| if (ret) | |
| goto cleanup; | |
| ret = hmm_migrate_dev_to_sys(fd, buffer, npages); | |
| if (ret) | |
| goto cleanup; | |
| /* Benchmark iterations */ | |
| for (i = 0; i < iterations; i++) { | |
| /* System to device migration */ | |
| start = get_time_ms(); | |
| ret = hmm_migrate_sys_to_dev(fd, buffer, npages); | |
| if (ret) | |
| goto cleanup; | |
| end = get_time_ms(); | |
| s2d_total += (end - start); | |
| /* Device to system migration */ | |
| start = get_time_ms(); | |
| ret = hmm_migrate_dev_to_sys(fd, buffer, npages); | |
| if (ret) | |
| goto cleanup; | |
| end = get_time_ms(); | |
| d2s_total += (end - start); | |
| } | |
| /* Calculate average times and throughput */ | |
| results->sys_to_dev_time = s2d_total / iterations; | |
| results->dev_to_sys_time = d2s_total / iterations; | |
| results->throughput_s2d = (buffer_size / (1024.0 * 1024.0 * 1024.0)) / | |
| (results->sys_to_dev_time / 1000.0); | |
| results->throughput_d2s = (buffer_size / (1024.0 * 1024.0 * 1024.0)) / | |
| (results->dev_to_sys_time / 1000.0); | |
| cleanup: | |
| hmm_buffer_free(buffer); | |
| return ret; | |
| } | |
| /* | |
| * Benchmark THP migration with different buffer sizes | |
| */ | |
| TEST_F_TIMEOUT(hmm, benchmark_thp_migration, 120) | |
| { | |
| struct benchmark_results thp_results, regular_results; | |
| size_t thp_size = read_pmd_pagesize(); | |
| int iterations = 5; | |
| if (!thp_size) | |
| thp_size = TWOMEG; | |
| printf("\nHMM THP Migration Benchmark\n"); | |
| printf("---------------------------\n"); | |
| printf("System page size: %ld bytes\n", sysconf(_SC_PAGESIZE)); | |
| /* Test different buffer sizes */ | |
| size_t test_sizes[] = { | |
| thp_size / 4, /* quarter THP */ | |
| thp_size / 2, /* half THP */ | |
| thp_size, /* single THP */ | |
| thp_size * 2, /* two THPs */ | |
| thp_size * 4, /* four THPs */ | |
| thp_size * 8, /* eight THPs */ | |
| thp_size * 128, /* one twenty eight THPs */ | |
| }; | |
| static const char *const test_names[] = { | |
| "Small Buffer", | |
| "Half THP Size", | |
| "Single THP Size", | |
| "Two THP Size", | |
| "Four THP Size", | |
| "Eight THP Size", | |
| "One twenty eight THP Size" | |
| }; | |
| int num_tests = ARRAY_SIZE(test_sizes); | |
| /* Run all tests */ | |
| for (int i = 0; i < num_tests; i++) { | |
| /* Skip test sizes exceeding INT_MAX to avoid overflow */ | |
| if (test_sizes[i] > INT_MAX) | |
| break; | |
| /* Test with THP */ | |
| ASSERT_EQ(run_migration_benchmark(self->fd, 1, test_sizes[i], | |
| iterations, &thp_results), 0); | |
| /* Test without THP */ | |
| ASSERT_EQ(run_migration_benchmark(self->fd, 0, test_sizes[i], | |
| iterations, ®ular_results), 0); | |
| /* Print results */ | |
| print_benchmark_results(test_names[i], test_sizes[i], | |
| &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 |