12616b370SDavid Hildenbrand // SPDX-License-Identifier: GPL-2.0-only 22616b370SDavid Hildenbrand /* 32616b370SDavid Hildenbrand * Basic VM_PFNMAP tests relying on mmap() of '/dev/mem' 42616b370SDavid Hildenbrand * 52616b370SDavid Hildenbrand * Copyright 2025, Red Hat, Inc. 62616b370SDavid Hildenbrand * 72616b370SDavid Hildenbrand * Author(s): David Hildenbrand <david@redhat.com> 82616b370SDavid Hildenbrand */ 92616b370SDavid Hildenbrand #define _GNU_SOURCE 102616b370SDavid Hildenbrand #include <stdlib.h> 112616b370SDavid Hildenbrand #include <string.h> 122616b370SDavid Hildenbrand #include <stdint.h> 132616b370SDavid Hildenbrand #include <unistd.h> 142616b370SDavid Hildenbrand #include <errno.h> 15*bb084994SDavid Hildenbrand #include <stdio.h> 16*bb084994SDavid Hildenbrand #include <ctype.h> 172616b370SDavid Hildenbrand #include <fcntl.h> 182616b370SDavid Hildenbrand #include <signal.h> 192616b370SDavid Hildenbrand #include <setjmp.h> 202616b370SDavid Hildenbrand #include <linux/mman.h> 212616b370SDavid Hildenbrand #include <sys/mman.h> 222616b370SDavid Hildenbrand #include <sys/wait.h> 232616b370SDavid Hildenbrand 242616b370SDavid Hildenbrand #include "../kselftest_harness.h" 252616b370SDavid Hildenbrand #include "vm_util.h" 262616b370SDavid Hildenbrand 272616b370SDavid Hildenbrand static sigjmp_buf sigjmp_buf_env; 282616b370SDavid Hildenbrand 292616b370SDavid Hildenbrand static void signal_handler(int sig) 302616b370SDavid Hildenbrand { 312616b370SDavid Hildenbrand siglongjmp(sigjmp_buf_env, -EFAULT); 322616b370SDavid Hildenbrand } 332616b370SDavid Hildenbrand 342616b370SDavid Hildenbrand static int test_read_access(char *addr, size_t size, size_t pagesize) 352616b370SDavid Hildenbrand { 362616b370SDavid Hildenbrand size_t offs; 372616b370SDavid Hildenbrand int ret; 382616b370SDavid Hildenbrand 392616b370SDavid Hildenbrand if (signal(SIGSEGV, signal_handler) == SIG_ERR) 402616b370SDavid Hildenbrand return -EINVAL; 412616b370SDavid Hildenbrand 422616b370SDavid Hildenbrand ret = sigsetjmp(sigjmp_buf_env, 1); 432616b370SDavid Hildenbrand if (!ret) { 442616b370SDavid Hildenbrand for (offs = 0; offs < size; offs += pagesize) 452616b370SDavid Hildenbrand /* Force a read that the compiler cannot optimize out. */ 462616b370SDavid Hildenbrand *((volatile char *)(addr + offs)); 472616b370SDavid Hildenbrand } 48*bb084994SDavid Hildenbrand if (signal(SIGSEGV, SIG_DFL) == SIG_ERR) 492616b370SDavid Hildenbrand return -EINVAL; 502616b370SDavid Hildenbrand 512616b370SDavid Hildenbrand return ret; 522616b370SDavid Hildenbrand } 532616b370SDavid Hildenbrand 54*bb084994SDavid Hildenbrand static int find_ram_target(off_t *phys_addr, 55*bb084994SDavid Hildenbrand unsigned long long pagesize) 56*bb084994SDavid Hildenbrand { 57*bb084994SDavid Hildenbrand unsigned long long start, end; 58*bb084994SDavid Hildenbrand char line[80], *end_ptr; 59*bb084994SDavid Hildenbrand FILE *file; 60*bb084994SDavid Hildenbrand 61*bb084994SDavid Hildenbrand /* Search /proc/iomem for the first suitable "System RAM" range. */ 62*bb084994SDavid Hildenbrand file = fopen("/proc/iomem", "r"); 63*bb084994SDavid Hildenbrand if (!file) 64*bb084994SDavid Hildenbrand return -errno; 65*bb084994SDavid Hildenbrand 66*bb084994SDavid Hildenbrand while (fgets(line, sizeof(line), file)) { 67*bb084994SDavid Hildenbrand /* Ignore any child nodes. */ 68*bb084994SDavid Hildenbrand if (!isalnum(line[0])) 69*bb084994SDavid Hildenbrand continue; 70*bb084994SDavid Hildenbrand 71*bb084994SDavid Hildenbrand if (!strstr(line, "System RAM\n")) 72*bb084994SDavid Hildenbrand continue; 73*bb084994SDavid Hildenbrand 74*bb084994SDavid Hildenbrand start = strtoull(line, &end_ptr, 16); 75*bb084994SDavid Hildenbrand /* Skip over the "-" */ 76*bb084994SDavid Hildenbrand end_ptr++; 77*bb084994SDavid Hildenbrand /* Make end "exclusive". */ 78*bb084994SDavid Hildenbrand end = strtoull(end_ptr, NULL, 16) + 1; 79*bb084994SDavid Hildenbrand 80*bb084994SDavid Hildenbrand /* Actual addresses are not exported */ 81*bb084994SDavid Hildenbrand if (!start && !end) 82*bb084994SDavid Hildenbrand break; 83*bb084994SDavid Hildenbrand 84*bb084994SDavid Hildenbrand /* We need full pages. */ 85*bb084994SDavid Hildenbrand start = (start + pagesize - 1) & ~(pagesize - 1); 86*bb084994SDavid Hildenbrand end &= ~(pagesize - 1); 87*bb084994SDavid Hildenbrand 88*bb084994SDavid Hildenbrand if (start != (off_t)start) 89*bb084994SDavid Hildenbrand break; 90*bb084994SDavid Hildenbrand 91*bb084994SDavid Hildenbrand /* We need two pages. */ 92*bb084994SDavid Hildenbrand if (end > start + 2 * pagesize) { 93*bb084994SDavid Hildenbrand fclose(file); 94*bb084994SDavid Hildenbrand *phys_addr = start; 95*bb084994SDavid Hildenbrand return 0; 96*bb084994SDavid Hildenbrand } 97*bb084994SDavid Hildenbrand } 98*bb084994SDavid Hildenbrand return -ENOENT; 99*bb084994SDavid Hildenbrand } 100*bb084994SDavid Hildenbrand 1012616b370SDavid Hildenbrand FIXTURE(pfnmap) 1022616b370SDavid Hildenbrand { 103*bb084994SDavid Hildenbrand off_t phys_addr; 1042616b370SDavid Hildenbrand size_t pagesize; 1052616b370SDavid Hildenbrand int dev_mem_fd; 1062616b370SDavid Hildenbrand char *addr1; 1072616b370SDavid Hildenbrand size_t size1; 1082616b370SDavid Hildenbrand char *addr2; 1092616b370SDavid Hildenbrand size_t size2; 1102616b370SDavid Hildenbrand }; 1112616b370SDavid Hildenbrand 1122616b370SDavid Hildenbrand FIXTURE_SETUP(pfnmap) 1132616b370SDavid Hildenbrand { 1142616b370SDavid Hildenbrand self->pagesize = getpagesize(); 1152616b370SDavid Hildenbrand 116*bb084994SDavid Hildenbrand /* We'll require two physical pages throughout our tests ... */ 117*bb084994SDavid Hildenbrand if (find_ram_target(&self->phys_addr, self->pagesize)) 118*bb084994SDavid Hildenbrand SKIP(return, "Cannot find ram target in '/proc/iomem'\n"); 119*bb084994SDavid Hildenbrand 1202616b370SDavid Hildenbrand self->dev_mem_fd = open("/dev/mem", O_RDONLY); 1212616b370SDavid Hildenbrand if (self->dev_mem_fd < 0) 1222616b370SDavid Hildenbrand SKIP(return, "Cannot open '/dev/mem'\n"); 1232616b370SDavid Hildenbrand 1242616b370SDavid Hildenbrand self->size1 = self->pagesize * 2; 1252616b370SDavid Hildenbrand self->addr1 = mmap(NULL, self->size1, PROT_READ, MAP_SHARED, 126*bb084994SDavid Hildenbrand self->dev_mem_fd, self->phys_addr); 1272616b370SDavid Hildenbrand if (self->addr1 == MAP_FAILED) 1282616b370SDavid Hildenbrand SKIP(return, "Cannot mmap '/dev/mem'\n"); 1292616b370SDavid Hildenbrand 1302616b370SDavid Hildenbrand /* ... and want to be able to read from them. */ 1312616b370SDavid Hildenbrand if (test_read_access(self->addr1, self->size1, self->pagesize)) 1322616b370SDavid Hildenbrand SKIP(return, "Cannot read-access mmap'ed '/dev/mem'\n"); 1332616b370SDavid Hildenbrand 1342616b370SDavid Hildenbrand self->size2 = 0; 1352616b370SDavid Hildenbrand self->addr2 = MAP_FAILED; 1362616b370SDavid Hildenbrand } 1372616b370SDavid Hildenbrand 1382616b370SDavid Hildenbrand FIXTURE_TEARDOWN(pfnmap) 1392616b370SDavid Hildenbrand { 1402616b370SDavid Hildenbrand if (self->addr2 != MAP_FAILED) 1412616b370SDavid Hildenbrand munmap(self->addr2, self->size2); 1422616b370SDavid Hildenbrand if (self->addr1 != MAP_FAILED) 1432616b370SDavid Hildenbrand munmap(self->addr1, self->size1); 1442616b370SDavid Hildenbrand if (self->dev_mem_fd >= 0) 1452616b370SDavid Hildenbrand close(self->dev_mem_fd); 1462616b370SDavid Hildenbrand } 1472616b370SDavid Hildenbrand 1482616b370SDavid Hildenbrand TEST_F(pfnmap, madvise_disallowed) 1492616b370SDavid Hildenbrand { 1502616b370SDavid Hildenbrand int advices[] = { 1512616b370SDavid Hildenbrand MADV_DONTNEED, 1522616b370SDavid Hildenbrand MADV_DONTNEED_LOCKED, 1532616b370SDavid Hildenbrand MADV_FREE, 1542616b370SDavid Hildenbrand MADV_WIPEONFORK, 1552616b370SDavid Hildenbrand MADV_COLD, 1562616b370SDavid Hildenbrand MADV_PAGEOUT, 1572616b370SDavid Hildenbrand MADV_POPULATE_READ, 1582616b370SDavid Hildenbrand MADV_POPULATE_WRITE, 1592616b370SDavid Hildenbrand }; 1602616b370SDavid Hildenbrand int i; 1612616b370SDavid Hildenbrand 1622616b370SDavid Hildenbrand /* All these advices must be rejected. */ 1632616b370SDavid Hildenbrand for (i = 0; i < ARRAY_SIZE(advices); i++) { 1642616b370SDavid Hildenbrand EXPECT_LT(madvise(self->addr1, self->pagesize, advices[i]), 0); 1652616b370SDavid Hildenbrand EXPECT_EQ(errno, EINVAL); 1662616b370SDavid Hildenbrand } 1672616b370SDavid Hildenbrand } 1682616b370SDavid Hildenbrand 1692616b370SDavid Hildenbrand TEST_F(pfnmap, munmap_split) 1702616b370SDavid Hildenbrand { 1712616b370SDavid Hildenbrand /* 1722616b370SDavid Hildenbrand * Unmap the first page. This munmap() call is not really expected to 1732616b370SDavid Hildenbrand * fail, but we might be able to trigger other internal issues. 1742616b370SDavid Hildenbrand */ 1752616b370SDavid Hildenbrand ASSERT_EQ(munmap(self->addr1, self->pagesize), 0); 1762616b370SDavid Hildenbrand 1772616b370SDavid Hildenbrand /* 1782616b370SDavid Hildenbrand * Remap the first page while the second page is still mapped. This 1792616b370SDavid Hildenbrand * makes sure that any PAT tracking on x86 will allow for mmap()'ing 1802616b370SDavid Hildenbrand * a page again while some parts of the first mmap() are still 1812616b370SDavid Hildenbrand * around. 1822616b370SDavid Hildenbrand */ 1832616b370SDavid Hildenbrand self->size2 = self->pagesize; 1842616b370SDavid Hildenbrand self->addr2 = mmap(NULL, self->pagesize, PROT_READ, MAP_SHARED, 185*bb084994SDavid Hildenbrand self->dev_mem_fd, self->phys_addr); 1862616b370SDavid Hildenbrand ASSERT_NE(self->addr2, MAP_FAILED); 1872616b370SDavid Hildenbrand } 1882616b370SDavid Hildenbrand 1892616b370SDavid Hildenbrand TEST_F(pfnmap, mremap_fixed) 1902616b370SDavid Hildenbrand { 1912616b370SDavid Hildenbrand char *ret; 1922616b370SDavid Hildenbrand 1932616b370SDavid Hildenbrand /* Reserve a destination area. */ 1942616b370SDavid Hildenbrand self->size2 = self->size1; 1952616b370SDavid Hildenbrand self->addr2 = mmap(NULL, self->size2, PROT_READ, MAP_ANON | MAP_PRIVATE, 1962616b370SDavid Hildenbrand -1, 0); 1972616b370SDavid Hildenbrand ASSERT_NE(self->addr2, MAP_FAILED); 1982616b370SDavid Hildenbrand 1992616b370SDavid Hildenbrand /* mremap() over our destination. */ 2002616b370SDavid Hildenbrand ret = mremap(self->addr1, self->size1, self->size2, 2012616b370SDavid Hildenbrand MREMAP_FIXED | MREMAP_MAYMOVE, self->addr2); 2022616b370SDavid Hildenbrand ASSERT_NE(ret, MAP_FAILED); 2032616b370SDavid Hildenbrand } 2042616b370SDavid Hildenbrand 2052616b370SDavid Hildenbrand TEST_F(pfnmap, mremap_shrink) 2062616b370SDavid Hildenbrand { 2072616b370SDavid Hildenbrand char *ret; 2082616b370SDavid Hildenbrand 2092616b370SDavid Hildenbrand /* Shrinking is expected to work. */ 2102616b370SDavid Hildenbrand ret = mremap(self->addr1, self->size1, self->size1 - self->pagesize, 0); 2112616b370SDavid Hildenbrand ASSERT_NE(ret, MAP_FAILED); 2122616b370SDavid Hildenbrand } 2132616b370SDavid Hildenbrand 2142616b370SDavid Hildenbrand TEST_F(pfnmap, mremap_expand) 2152616b370SDavid Hildenbrand { 2162616b370SDavid Hildenbrand /* 2172616b370SDavid Hildenbrand * Growing is not expected to work, and getting it right would 2182616b370SDavid Hildenbrand * be challenging. So this test primarily serves as an early warning 2192616b370SDavid Hildenbrand * that something that probably should never work suddenly works. 2202616b370SDavid Hildenbrand */ 2212616b370SDavid Hildenbrand self->size2 = self->size1 + self->pagesize; 2222616b370SDavid Hildenbrand self->addr2 = mremap(self->addr1, self->size1, self->size2, MREMAP_MAYMOVE); 2232616b370SDavid Hildenbrand ASSERT_EQ(self->addr2, MAP_FAILED); 2242616b370SDavid Hildenbrand } 2252616b370SDavid Hildenbrand 2262616b370SDavid Hildenbrand TEST_F(pfnmap, fork) 2272616b370SDavid Hildenbrand { 2282616b370SDavid Hildenbrand pid_t pid; 2292616b370SDavid Hildenbrand int ret; 2302616b370SDavid Hildenbrand 2312616b370SDavid Hildenbrand /* fork() a child and test if the child can access the pages. */ 2322616b370SDavid Hildenbrand pid = fork(); 2332616b370SDavid Hildenbrand ASSERT_GE(pid, 0); 2342616b370SDavid Hildenbrand 2352616b370SDavid Hildenbrand if (!pid) { 2362616b370SDavid Hildenbrand EXPECT_EQ(test_read_access(self->addr1, self->size1, 2372616b370SDavid Hildenbrand self->pagesize), 0); 2382616b370SDavid Hildenbrand exit(0); 2392616b370SDavid Hildenbrand } 2402616b370SDavid Hildenbrand 2412616b370SDavid Hildenbrand wait(&ret); 2422616b370SDavid Hildenbrand if (WIFEXITED(ret)) 2432616b370SDavid Hildenbrand ret = WEXITSTATUS(ret); 2442616b370SDavid Hildenbrand else 2452616b370SDavid Hildenbrand ret = -EINVAL; 2462616b370SDavid Hildenbrand ASSERT_EQ(ret, 0); 2472616b370SDavid Hildenbrand } 2482616b370SDavid Hildenbrand 2492616b370SDavid Hildenbrand TEST_HARNESS_MAIN 250