| // 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, |
| }; |