214 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright 2017, Anshuman Khandual, IBM Corp.4 *5 * Works on architectures which support 128TB virtual6 * 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 is22 * chosen as the single chunk size for address space23 * 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_1GB30 31/*32 * Address space till 128TB is mapped without any hint33 * and is enabled by default. Address space beyond 128TB34 * till 512TB is obtained by passing hint address as the35 * first argument into mmap() system call.36 *37 * The process heap address space is divided into two38 * different areas one below 128TB and one above 128TB39 * till it reaches 512TB. One with size 128TB and the40 * other being 384TB.41 *42 * On Arm64 the address space is 256TB and support for43 * high mappings up to 4PB virtual address space has44 * 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_256TB57#define HIGH_ADDR_SHIFT 4958#define NR_CHUNKS_LOW NR_CHUNKS_256TB59#define NR_CHUNKS_HIGH NR_CHUNKS_3840TB60#else61#define HIGH_ADDR_MARK ADDR_MARK_128TB62#define HIGH_ADDR_SHIFT 4863#define NR_CHUNKS_LOW NR_CHUNKS_128TB64#define NR_CHUNKS_HIGH NR_CHUNKS_384TB65#endif66 67static char *hind_addr(void)68{69 int bits = HIGH_ADDR_SHIFT + rand() % (63 - HIGH_ADDR_SHIFT);70 71 return (char *) (1UL << bits);72}73 74static 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 85static 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 98static 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 - 1141 * addresses after that. If the address was not held by this142 * process, write would fail with errno set to EFAULT.143 * Anyways, if write returns anything apart from 1, exit the144 * 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 158int 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