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
15#define PAGE_COMPOUND_HEAD	(1UL << 15)
16#define PAGE_COMPOUND_TAIL	(1UL << 16)
17#define PAGE_HUGE		(1UL << 17)
18
19#define HEAD_PAGE_FLAGS		(PAGE_COMPOUND_HEAD | PAGE_HUGE)
20#define TAIL_PAGE_FLAGS		(PAGE_COMPOUND_TAIL | PAGE_HUGE)
21
22#define PM_PFRAME_BITS		55
23#define PM_PFRAME_MASK		~((1UL << PM_PFRAME_BITS) - 1)
24
25/*
26 * For ia64 architecture, Linux kernel reserves Region number 4 for hugepages.
27 * That means the addresses starting with 0x800000... will need to be
28 * specified.  Specifying a fixed address is not required on ppc64, i386
29 * or x86_64.
30 */
31#ifdef __ia64__
32#define MAP_ADDR		(void *)(0x8000000000000000UL)
33#define MAP_FLAGS		(MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_FIXED)
34#else
35#define MAP_ADDR		NULL
36#define MAP_FLAGS		(MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB)
37#endif
38
39static size_t pagesize;
40static size_t maplength;
41
42static void write_bytes(char *addr, size_t length)
43{
44	unsigned long i;
45
46	for (i = 0; i < length; i++)
47		*(addr + i) = (char)i;
48}
49
50static unsigned long virt_to_pfn(void *addr)
51{
52	int fd;
53	unsigned long pagemap;
54
55	fd = open("/proc/self/pagemap", O_RDONLY);
56	if (fd < 0)
57		return -1UL;
58
59	lseek(fd, (unsigned long)addr / pagesize * sizeof(pagemap), SEEK_SET);
60	read(fd, &pagemap, sizeof(pagemap));
61	close(fd);
62
63	return pagemap & ~PM_PFRAME_MASK;
64}
65
66static int check_page_flags(unsigned long pfn)
67{
68	int fd, i;
69	unsigned long pageflags;
70
71	fd = open("/proc/kpageflags", O_RDONLY);
72	if (fd < 0)
73		return -1;
74
75	lseek(fd, pfn * sizeof(pageflags), SEEK_SET);
76
77	read(fd, &pageflags, sizeof(pageflags));
78	if ((pageflags & HEAD_PAGE_FLAGS) != HEAD_PAGE_FLAGS) {
79		close(fd);
80		printf("Head page flags (%lx) is invalid\n", pageflags);
81		return -1;
82	}
83
84	/*
85	 * pages other than the first page must be tail and shouldn't be head;
86	 * this also verifies kernel has correctly set the fake page_head to tail
87	 * while hugetlb_free_vmemmap is enabled.
88	 */
89	for (i = 1; i < maplength / pagesize; i++) {
90		read(fd, &pageflags, sizeof(pageflags));
91		if ((pageflags & TAIL_PAGE_FLAGS) != TAIL_PAGE_FLAGS ||
92		    (pageflags & HEAD_PAGE_FLAGS) == HEAD_PAGE_FLAGS) {
93			close(fd);
94			printf("Tail page flags (%lx) is invalid\n", pageflags);
95			return -1;
96		}
97	}
98
99	close(fd);
100
101	return 0;
102}
103
104int main(int argc, char **argv)
105{
106	void *addr;
107	unsigned long pfn;
108
109	pagesize  = psize();
110	maplength = default_huge_page_size();
111	if (!maplength) {
112		printf("Unable to determine huge page size\n");
113		exit(1);
114	}
115
116	addr = mmap(MAP_ADDR, maplength, PROT_READ | PROT_WRITE, MAP_FLAGS, -1, 0);
117	if (addr == MAP_FAILED) {
118		perror("mmap");
119		exit(1);
120	}
121
122	/* Trigger allocation of HugeTLB page. */
123	write_bytes(addr, maplength);
124
125	pfn = virt_to_pfn(addr);
126	if (pfn == -1UL) {
127		munmap(addr, maplength);
128		perror("virt_to_pfn");
129		exit(1);
130	}
131
132	printf("Returned address is %p whose pfn is %lx\n", addr, pfn);
133
134	if (check_page_flags(pfn) < 0) {
135		munmap(addr, maplength);
136		perror("check_page_flags");
137		exit(1);
138	}
139
140	/* munmap() length of MAP_HUGETLB memory must be hugepage aligned */
141	if (munmap(addr, maplength)) {
142		perror("munmap");
143		exit(1);
144	}
145
146	return 0;
147}
148