blob: d92033a245889ddd146e4179283033e980d7010d [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) Zilogic Systems Pvt. Ltd., 2018
* Email: code@zilogic.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License for more details.
*/
/*
* Test: Validating memfd_create() with MFD_HUGETLB flag.
*
* Test case 1: --WRITE CALL IN HUGEPAGES TEST--
* Huge pages are write protected. Any writes to
* the file should return EINVAL error.
*
* Test case 2: --PAGE SIZE OF CREATED FILE TEST--
* Default huge page sized pages are created with
* MFD_HUGETLB flag. Any attempt to unmap memory-mapped
* huge pages with an unmapping length less than
* huge page size should return EINVAL error.
*
* Test case 3: --HUGEPAGE ALLOCATION LIMIT TEST--
* Number of huge pages currently available to use should be
* atmost total number of allowed huge pages. Memory-mapping
* more than allowed huge pages should return ENOMEM error.
*/
#define _GNU_SOURCE
#include "tst_test.h"
#include "memfd_create_common.h"
#include <stdio.h>
#include <errno.h>
#define TOTAL_HP_PATH "/proc/sys/vm/nr_hugepages"
#define MEMINFO_PATH "/proc/meminfo"
#define FREE_HP "HugePages_Free:\t%ld"
#define DEFAULT_HPS "Hugepagesize:\t%ld kB"
static int hugepages_allocated;
static long og_total_pages;
static void *check_huge_mmapable(int fd, unsigned long size)
{
void *mem;
mem = SAFE_MMAP(NULL, size, PROT_WRITE, MAP_PRIVATE, fd, 0);
memset((char *)mem, 0, 1);
tst_res(TINFO,
"mmap(%p, %lu, %d, %d, %d, %d) succeeded",
NULL, size, PROT_WRITE, MAP_PRIVATE, fd, 0);
return mem;
}
static void test_write_protect(int fd)
{
ssize_t ret;
char test_str[] = "LTP";
ret = write(fd, test_str, strlen(test_str));
if (ret < 0) {
if (errno != EINVAL) {
tst_res(TFAIL | TERRNO,
"write(%d, \"%s\", %zu) didn't fail as expected\n",
fd, test_str, strlen(test_str));
return;
}
} else {
tst_res(TFAIL,
"write(%d, \"%s\", %zu) succeeded unexpectedly\n",
fd, test_str, strlen(test_str));
return;
}
tst_res(TPASS,
"write(%d, \"%s\", %zu) failed as expected\n",
fd, test_str, strlen(test_str));
}
static void test_def_pagesize(int fd)
{
unsigned int i;
int unmap_size;
int ret;
long hps;
void *mem;
SAFE_FILE_LINES_SCANF(MEMINFO_PATH, DEFAULT_HPS, &hps);
hps = hps << 10;
unmap_size = hps / 4;
mem = check_huge_mmapable(fd, hps);
for (i = unmap_size; i < hps; i += unmap_size) {
ret = munmap(mem, i);
if (ret == -1) {
if (errno == EINVAL) {
tst_res(TINFO,
"munmap(%p, %dkB) failed as expected",
mem, i/1024);
} else {
tst_res(TFAIL | TERRNO,
"munmap(%p, %dkB) failed unexpectedly",
mem, i/1024);
return;
}
} else {
tst_res(TFAIL,
"munmap(%p, %dkB) suceeded unexpectedly\n",
mem, i);
return;
}
}
SAFE_MUNMAP(mem, hps);
tst_res(TPASS,
"munmap() fails for page sizes less than %ldkB\n", hps/1024);
}
static void test_max_hugepages(int fd)
{
int new_fd;
long hps;
long free_pages;
void *mem;
void *new_mem;
SAFE_FILE_LINES_SCANF(MEMINFO_PATH, FREE_HP, &free_pages);
SAFE_FILE_LINES_SCANF(MEMINFO_PATH, DEFAULT_HPS, &hps);
hps = hps << 10;
mem = check_huge_mmapable(fd, free_pages * hps);
new_fd = sys_memfd_create("new_file", MFD_HUGETLB);
if (new_fd < 0)
tst_brk(TFAIL | TERRNO, "memfd_create() failed");
tst_res(TINFO, "memfd_create() succeeded");
new_mem = mmap(NULL, hps, 0, MAP_PRIVATE, new_fd, 0);
if (new_mem == MAP_FAILED) {
if (errno == ENOMEM)
tst_res(TPASS,
"mmap(%p, %lu, %d, %d, %d, %d) failed as expected",
NULL, hps, 0, MAP_PRIVATE, new_fd, 0);
else
tst_res(TFAIL | TERRNO,
"mmap(%p, %lu, %d, %d, %d, %d) failed unexpectedly",
NULL, hps, 0, MAP_PRIVATE, new_fd, 0);
} else {
tst_res(TFAIL,
"mmap(%p, %lu, %d, %d, %d, %d) succeeded",
NULL, hps, 0, MAP_PRIVATE, new_fd, 0);
SAFE_MUNMAP(new_mem, hps);
}
SAFE_CLOSE(new_fd);
SAFE_MUNMAP(mem, free_pages * hps);
}
static const struct tcase {
void (*func)(int fd);
const char *desc;
} tcases[] = {
{&test_write_protect, "--TESTING WRITE CALL IN HUGEPAGES--"},
{&test_def_pagesize, "--TESTING PAGE SIZE OF CREATED FILE--"},
{&test_max_hugepages, "--TESTING HUGEPAGE ALLOCATION LIMIT--"},
};
static void memfd_huge_controller(unsigned int n)
{
int fd;
const struct tcase *tc;
tc = &tcases[n];
tst_res(TINFO, "%s", tc->desc);
fd = sys_memfd_create("test_file", MFD_HUGETLB);
if (fd < 0)
tst_brk(TFAIL | TERRNO, "memfd_create() failed");
tst_res(TINFO, "memfd_create() succeeded");
tc->func(fd);
SAFE_CLOSE(fd);
}
static void setup(void)
{
char buf[8];
int fd;
long free_pages;
long total_pages;
if (access(MEMINFO_PATH, F_OK) ||
access("/sys/kernel/mm/hugepages", F_OK) ||
access(TOTAL_HP_PATH, F_OK))
tst_brk(TCONF, "Huge page is not supported");
SAFE_FILE_LINES_SCANF(MEMINFO_PATH, FREE_HP, &free_pages);
if (free_pages > 0)
return;
SAFE_FILE_LINES_SCANF(TOTAL_HP_PATH, "%ld", &og_total_pages);
sprintf(buf, "%ld", og_total_pages + 1);
fd = open(TOTAL_HP_PATH, O_RDWR | O_TRUNC);
if (write(fd, buf, strlen(buf)) == -1)
tst_brk(TCONF | TERRNO,
"write() fail: Hugepage allocation failed");
SAFE_CLOSE(fd);
SAFE_FILE_LINES_SCANF(TOTAL_HP_PATH, "%ld", &total_pages);
if (total_pages != (og_total_pages + 1))
tst_brk(TCONF, "Hugepage allocation failed");
hugepages_allocated = 1;
}
static void cleanup(void)
{
char buf[8];
int fd;
long total_pages;
if (hugepages_allocated == 0)
return;
sprintf(buf, "%ld", og_total_pages);
fd = open(TOTAL_HP_PATH, O_RDWR | O_TRUNC);
if (write(fd, buf, strlen(buf)) == -1)
tst_brk(TCONF | TERRNO, "Clean-up failed: write() failed");
SAFE_CLOSE(fd);
SAFE_FILE_LINES_SCANF(TOTAL_HP_PATH, "%ld", &total_pages);
if (og_total_pages != total_pages)
tst_brk(TCONF, "Clean-up failed");
}
static struct tst_test test = {
.setup = setup,
.test = memfd_huge_controller,
.tcnt = ARRAY_SIZE(tcases),
.needs_root = 1,
.min_kver = "4.14",
.cleanup = cleanup,
};