xref: /linux/tools/testing/selftests/mm/hugetlb-vmemmap.c (revision 3d5e48944e824bddc20d7b874e784f7b279636fe)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * A test case of using hugepage memory in a user application using the
4  * mmap system call with MAP_HUGETLB flag.  Before running this program
5  * make sure the administrator has allocated enough default sized huge
6  * pages to cover the 2 MB allocation.
7  */
8 #include <stdlib.h>
9 #include <stdio.h>
10 #include <unistd.h>
11 #include <sys/mman.h>
12 #include <fcntl.h>
13 #include "vm_util.h"
14 #include "hugepage_settings.h"
15 
16 #define PAGE_COMPOUND_HEAD	(1UL << 15)
17 #define PAGE_COMPOUND_TAIL	(1UL << 16)
18 #define PAGE_HUGE		(1UL << 17)
19 
20 #define HEAD_PAGE_FLAGS		(PAGE_COMPOUND_HEAD | PAGE_HUGE)
21 #define TAIL_PAGE_FLAGS		(PAGE_COMPOUND_TAIL | PAGE_HUGE)
22 
23 #define PM_PFRAME_BITS		55
24 #define PM_PFRAME_MASK		~((1UL << PM_PFRAME_BITS) - 1)
25 
26 static size_t pagesize;
27 static size_t maplength;
28 
29 static void write_bytes(char *addr, size_t length)
30 {
31 	unsigned long i;
32 
33 	for (i = 0; i < length; i++)
34 		*(addr + i) = (char)i;
35 }
36 
37 static unsigned long virt_to_pfn(void *addr)
38 {
39 	int fd;
40 	unsigned long pagemap;
41 
42 	fd = open("/proc/self/pagemap", O_RDONLY);
43 	if (fd < 0)
44 		return -1UL;
45 
46 	lseek(fd, (unsigned long)addr / pagesize * sizeof(pagemap), SEEK_SET);
47 	read(fd, &pagemap, sizeof(pagemap));
48 	close(fd);
49 
50 	return pagemap & ~PM_PFRAME_MASK;
51 }
52 
53 static int check_page_flags(unsigned long pfn)
54 {
55 	int fd, i;
56 	unsigned long pageflags;
57 
58 	fd = open("/proc/kpageflags", O_RDONLY);
59 	if (fd < 0)
60 		return -1;
61 
62 	lseek(fd, pfn * sizeof(pageflags), SEEK_SET);
63 
64 	read(fd, &pageflags, sizeof(pageflags));
65 	if ((pageflags & HEAD_PAGE_FLAGS) != HEAD_PAGE_FLAGS) {
66 		close(fd);
67 		ksft_print_msg("Head page flags (%lx) is invalid\n", pageflags);
68 		return -1;
69 	}
70 
71 	/*
72 	 * pages other than the first page must be tail and shouldn't be head;
73 	 * this also verifies kernel has correctly set the fake page_head to tail
74 	 * while hugetlb_free_vmemmap is enabled.
75 	 */
76 	for (i = 1; i < maplength / pagesize; i++) {
77 		read(fd, &pageflags, sizeof(pageflags));
78 		if ((pageflags & TAIL_PAGE_FLAGS) != TAIL_PAGE_FLAGS ||
79 		    (pageflags & HEAD_PAGE_FLAGS) == HEAD_PAGE_FLAGS) {
80 			close(fd);
81 			ksft_print_msg("Tail page flags (%lx) is invalid\n", pageflags);
82 			return -1;
83 		}
84 	}
85 
86 	close(fd);
87 
88 	return 0;
89 }
90 
91 int main(int argc, char **argv)
92 {
93 	void *addr;
94 	unsigned long pfn;
95 	int ret;
96 
97 	ksft_print_header();
98 	ksft_set_plan(1);
99 
100 	if (!hugetlb_setup_default(1))
101 		ksft_exit_skip("Not enough free huge pages\n");
102 
103 	pagesize  = psize();
104 	maplength = default_huge_page_size();
105 	if (!maplength)
106 		ksft_exit_skip("Unable to determine huge page size\n");
107 
108 	addr = mmap(NULL, maplength, PROT_READ | PROT_WRITE,
109 			MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
110 	if (addr == MAP_FAILED)
111 		ksft_exit_fail_perror("mmap");
112 
113 	/* Trigger allocation of HugeTLB page. */
114 	write_bytes(addr, maplength);
115 
116 	pfn = virt_to_pfn(addr);
117 	if (pfn == -1UL) {
118 		ksft_perror("virt_to_pfn");
119 		munmap(addr, maplength);
120 		ksft_exit_fail();
121 	}
122 
123 	ksft_print_msg("Returned address is %p whose pfn is %lx\n", addr, pfn);
124 
125 	ret = check_page_flags(pfn);
126 
127 	if (munmap(addr, maplength))
128 		ksft_exit_fail_perror("munmap");
129 
130 	ksft_test_result(!ret, "HugeTLB vmemmap page flags\n");
131 	ksft_finished();
132 }
133