1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright 2017, Anshuman Khandual, IBM Corp.
4 *
5 * Works on architectures which support 128TB virtual
6 * address range and beyond.
7 */
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <unistd.h>
12 #include <errno.h>
13 #include <sys/mman.h>
14 #include <sys/time.h>
15 #include <fcntl.h>
16
17 #include "../kselftest.h"
18
19 /*
20 * Maximum address range mapped with a single mmap()
21 * call is little bit more than 1GB. Hence 1GB is
22 * chosen as the single chunk size for address space
23 * mapping.
24 */
25
26 #define SZ_1GB (1024 * 1024 * 1024UL)
27 #define SZ_1TB (1024 * 1024 * 1024 * 1024UL)
28
29 #define MAP_CHUNK_SIZE SZ_1GB
30
31 /*
32 * Address space till 128TB is mapped without any hint
33 * and is enabled by default. Address space beyond 128TB
34 * till 512TB is obtained by passing hint address as the
35 * first argument into mmap() system call.
36 *
37 * The process heap address space is divided into two
38 * different areas one below 128TB and one above 128TB
39 * till it reaches 512TB. One with size 128TB and the
40 * other being 384TB.
41 *
42 * On Arm64 the address space is 256TB and support for
43 * high mappings up to 4PB virtual address space has
44 * been added.
45 */
46
47 #define NR_CHUNKS_128TB ((128 * SZ_1TB) / MAP_CHUNK_SIZE) /* Number of chunks for 128TB */
48 #define NR_CHUNKS_256TB (NR_CHUNKS_128TB * 2UL)
49 #define NR_CHUNKS_384TB (NR_CHUNKS_128TB * 3UL)
50 #define NR_CHUNKS_3840TB (NR_CHUNKS_128TB * 30UL)
51
52 #define ADDR_MARK_128TB (1UL << 47) /* First address beyond 128TB */
53 #define ADDR_MARK_256TB (1UL << 48) /* First address beyond 256TB */
54
55 #ifdef __aarch64__
56 #define HIGH_ADDR_MARK ADDR_MARK_256TB
57 #define HIGH_ADDR_SHIFT 49
58 #define NR_CHUNKS_LOW NR_CHUNKS_256TB
59 #define NR_CHUNKS_HIGH NR_CHUNKS_3840TB
60 #else
61 #define HIGH_ADDR_MARK ADDR_MARK_128TB
62 #define HIGH_ADDR_SHIFT 48
63 #define NR_CHUNKS_LOW NR_CHUNKS_128TB
64 #define NR_CHUNKS_HIGH NR_CHUNKS_384TB
65 #endif
66
hind_addr(void)67 static char *hind_addr(void)
68 {
69 int bits = HIGH_ADDR_SHIFT + rand() % (63 - HIGH_ADDR_SHIFT);
70
71 return (char *) (1UL << bits);
72 }
73
validate_addr(char * ptr,int high_addr)74 static void validate_addr(char *ptr, int high_addr)
75 {
76 unsigned long addr = (unsigned long) ptr;
77
78 if (high_addr && addr < HIGH_ADDR_MARK)
79 ksft_exit_fail_msg("Bad address %lx\n", addr);
80
81 if (addr > HIGH_ADDR_MARK)
82 ksft_exit_fail_msg("Bad address %lx\n", addr);
83 }
84
validate_lower_address_hint(void)85 static int validate_lower_address_hint(void)
86 {
87 char *ptr;
88
89 ptr = mmap((void *) (1UL << 45), MAP_CHUNK_SIZE, PROT_READ |
90 PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
91
92 if (ptr == MAP_FAILED)
93 return 0;
94
95 return 1;
96 }
97
validate_complete_va_space(void)98 static int validate_complete_va_space(void)
99 {
100 unsigned long start_addr, end_addr, prev_end_addr;
101 char line[400];
102 char prot[6];
103 FILE *file;
104 int fd;
105
106 fd = open("va_dump", O_CREAT | O_WRONLY, 0600);
107 unlink("va_dump");
108 if (fd < 0) {
109 ksft_test_result_skip("cannot create or open dump file\n");
110 ksft_finished();
111 }
112
113 file = fopen("/proc/self/maps", "r");
114 if (file == NULL)
115 ksft_exit_fail_msg("cannot open /proc/self/maps\n");
116
117 prev_end_addr = 0;
118 while (fgets(line, sizeof(line), file)) {
119 unsigned long hop;
120
121 if (sscanf(line, "%lx-%lx %s[rwxp-]",
122 &start_addr, &end_addr, prot) != 3)
123 ksft_exit_fail_msg("cannot parse /proc/self/maps\n");
124
125 /* end of userspace mappings; ignore vsyscall mapping */
126 if (start_addr & (1UL << 63))
127 return 0;
128
129 /* /proc/self/maps must have gaps less than MAP_CHUNK_SIZE */
130 if (start_addr - prev_end_addr >= MAP_CHUNK_SIZE)
131 return 1;
132
133 prev_end_addr = end_addr;
134
135 if (prot[0] != 'r')
136 continue;
137
138 /*
139 * Confirm whether MAP_CHUNK_SIZE chunk can be found or not.
140 * If write succeeds, no need to check MAP_CHUNK_SIZE - 1
141 * addresses after that. If the address was not held by this
142 * process, write would fail with errno set to EFAULT.
143 * Anyways, if write returns anything apart from 1, exit the
144 * program since that would mean a bug in /proc/self/maps.
145 */
146 hop = 0;
147 while (start_addr + hop < end_addr) {
148 if (write(fd, (void *)(start_addr + hop), 1) != 1)
149 return 1;
150 lseek(fd, 0, SEEK_SET);
151
152 hop += MAP_CHUNK_SIZE;
153 }
154 }
155 return 0;
156 }
157
main(int argc,char * argv[])158 int main(int argc, char *argv[])
159 {
160 char *ptr[NR_CHUNKS_LOW];
161 char **hptr;
162 char *hint;
163 unsigned long i, lchunks, hchunks;
164
165 ksft_print_header();
166 ksft_set_plan(1);
167
168 for (i = 0; i < NR_CHUNKS_LOW; i++) {
169 ptr[i] = mmap(NULL, MAP_CHUNK_SIZE, PROT_READ | PROT_WRITE,
170 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
171
172 if (ptr[i] == MAP_FAILED) {
173 if (validate_lower_address_hint())
174 ksft_exit_fail_msg("mmap unexpectedly succeeded with hint\n");
175 break;
176 }
177
178 validate_addr(ptr[i], 0);
179 }
180 lchunks = i;
181 hptr = (char **) calloc(NR_CHUNKS_HIGH, sizeof(char *));
182 if (hptr == NULL) {
183 ksft_test_result_skip("Memory constraint not fulfilled\n");
184 ksft_finished();
185 }
186
187 for (i = 0; i < NR_CHUNKS_HIGH; i++) {
188 hint = hind_addr();
189 hptr[i] = mmap(hint, MAP_CHUNK_SIZE, PROT_READ | PROT_WRITE,
190 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
191
192 if (hptr[i] == MAP_FAILED)
193 break;
194
195 validate_addr(hptr[i], 1);
196 }
197 hchunks = i;
198 if (validate_complete_va_space()) {
199 ksft_test_result_fail("BUG in mmap() or /proc/self/maps\n");
200 ksft_finished();
201 }
202
203 for (i = 0; i < lchunks; i++)
204 munmap(ptr[i], MAP_CHUNK_SIZE);
205
206 for (i = 0; i < hchunks; i++)
207 munmap(hptr[i], MAP_CHUNK_SIZE);
208
209 free(hptr);
210
211 ksft_test_result_pass("Test\n");
212 ksft_finished();
213 }
214