linux/tools/testing/selftests/vm/hmm-tests.c

2055 lines
50 KiB
C
Raw Normal View History

// 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 <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 <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
selftests/vm: hmm-tests: remove the libhugetlbfs dependency HMM selftests are incredibly useful, but they are only effective if people actually build and run them. All the other tests in selftests/vm can be built with very standard, always-available libraries: libpthread, librt. The hmm-tests.c program, on the other hand, requires something that is (much) less readily available: libhugetlbfs. And so the build will typically fail for many developers. A simple attempt to install libhugetlbfs will also run into complications on some common distros these days: Fedora and Arch Linux (yes, Arch AUR has it, but that's fragile, as always with AUR). The library is not maintained actively enough at the moment, for distros to deal with it. I had to build it from source, for Fedora, and that didn't go too smoothly either. It turns out that, out of 21 tests in hmm-tests.c, only 2 actually require functionality from libhugetlbfs. Therefore, if libhugetlbfs is missing, simply ifdef those two tests out and allow the developer to at least have the other 19 tests, if they don't want to pause to work through the above issues. Also issue a warning, so that it's clear that there is an imperfection in the build. In order to do that, a tiny shell script (check_config.sh) runs a quick compile (not link, that's too prone to false failures with library paths), and basically, if the compiler doesn't find hugetlbfs.h in its standard locations, then the script concludes that libhugetlbfs is not available. The output is in two files, one for inclusion in hmm-test.c (local_config.h), and one for inclusion in the Makefile (local_config.mk). Link: https://lkml.kernel.org/r/20201026064021.3545418-9-jhubbard@nvidia.com Signed-off-by: John Hubbard <jhubbard@nvidia.com> Cc: Ralph Campbell <rcampbell@nvidia.com> Cc: Jérôme Glisse <jglisse@redhat.com> Cc: Jonathan Corbet <corbet@lwn.net> Cc: Shuah Khan <shuah@kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-12-15 11:05:28 +08:00
/*
* 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>
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 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: */
#define FOLL_WRITE 0x01 /* check pte is writable */
#define FOLL_LONGTERM 0x10000 /* mapping lifetime is indefinite */
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(exit(0), "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(exit(0), "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);
free(buffer->mirror);
free(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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/*
* 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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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.
*/
TEST_F(hmm, anon_write_child)
{
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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 not 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);
}
/*
* 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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 * TWOMEG;
buffer = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
size = TWOMEG;
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);
}
/*
* Read numeric data from raw and tagged kernel status files. Used to read
* /proc and /sys data (without a tag) and from /proc/meminfo (with a tag).
*/
static long file_read_ulong(char *file, const char *tag)
{
int fd;
char buf[2048];
int len;
char *p, *q;
long val;
fd = open(file, O_RDONLY);
if (fd < 0) {
/* Error opening the file */
return -1;
}
len = read(fd, buf, sizeof(buf));
close(fd);
if (len < 0) {
/* Error in reading the file */
return -1;
}
if (len == sizeof(buf)) {
/* Error file is too large */
return -1;
}
buf[len] = '\0';
/* Search for a tag if provided */
if (tag) {
p = strstr(buf, tag);
if (!p)
return -1; /* looks like the line we want isn't there */
p += strlen(tag);
} else
p = buf;
val = strtol(p, &q, 0);
if (*q != ' ') {
/* Error parsing the file */
return -1;
}
return val;
}
/*
* Write huge TLBFS page.
*/
TEST_F(hmm, anon_write_hugetlbfs)
{
struct hmm_buffer *buffer;
unsigned long npages;
unsigned long size;
unsigned long default_hsize;
unsigned long i;
int *ptr;
int ret;
default_hsize = file_read_ulong("/proc/meminfo", "Hugepagesize:");
if (default_hsize < 0 || default_hsize*1024 < default_hsize)
SKIP(return, "Huge page size could not be determined");
default_hsize = default_hsize*1024; /* KB to B */
size = ALIGN(TWOMEG, default_hsize);
npages = size >> self->page_shift;
buffer = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
-1, 0);
if (buffer->ptr == MAP_FAILED) {
free(buffer);
SKIP(return, "Huge page could not be allocated");
}
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, 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);
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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = fd;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, 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);
buffer->ptr = mmap(NULL, size,
PROT_READ,
MAP_SHARED,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = fd;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_SHARED,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
/* Reserve a range of addresses. */
buffer->ptr = mmap(NULL, size,
PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(npages);
ASSERT_NE(buffer->mirror, NULL);
/* Reserve a range of addresses. */
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE,
self->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(npages);
ASSERT_NE(buffer->mirror, NULL);
/* Reserve a range of addresses. */
buffer->ptr = mmap(NULL, size,
PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
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() HMM_PFN_PMD flag for large pages 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;
int *ptr;
unsigned char *m;
int ret;
unsigned long i;
/* Skip test if we can't allocate a hugetlbfs page. */
default_hsize = file_read_ulong("/proc/meminfo", "Hugepagesize:");
if (default_hsize < 0 || default_hsize*1024 < default_hsize)
SKIP(return, "Huge page size could not be determined");
default_hsize = default_hsize*1024; /* KB to B */
size = ALIGN(TWOMEG, default_hsize);
npages = size >> self->page_shift;
buffer = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
-1, 0);
if (buffer->ptr == MAP_FAILED) {
free(buffer);
return;
}
buffer->size = size;
buffer->mirror = malloc(npages);
ASSERT_NE(buffer->mirror, NULL);
/* 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. */
m = buffer->mirror;
for (i = 0; i < npages; ++i)
ASSERT_EQ(m[i], HMM_DMIRROR_PROT_WRITE |
HMM_DMIRROR_PROT_PMD);
/* 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. */
m = buffer->mirror;
for (i = 0; i < npages; ++i)
ASSERT_EQ(m[i], HMM_DMIRROR_PROT_READ |
HMM_DMIRROR_PROT_PMD);
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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(npages);
ASSERT_NE(buffer->mirror, NULL);
/* Reserve a range of addresses. */
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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;
npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift;
ASSERT_NE(npages, 0);
size = npages << self->page_shift;
buffer = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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);
fork();
/* 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);
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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 = malloc(sizeof(*buffer));
ASSERT_NE(buffer, NULL);
buffer->fd = -1;
buffer->size = size;
buffer->mirror = malloc(size);
ASSERT_NE(buffer->mirror, NULL);
buffer->ptr = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
buffer->fd, 0);
ASSERT_NE(buffer->ptr, MAP_FAILED);
/* 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 waitd for SIGTERM from the parent. */
while (1) {
}
perror("Should not reach this\n");
exit(0);
}
/* 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, SIGTERM));
EXPECT_EQ(pid, waitpid(pid, &status, 0));
EXPECT_NE(0, WIFSIGNALED(status));
EXPECT_EQ(SIGTERM, 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);
}
TEST_HARNESS_MAIN