1// SPDX-License-Identifier: GPL-2.0
2/*
3 * A test case that must run on a system with one and only one huge page available.
4 *	# echo 1 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
5 *
6 * During setup, the test allocates the only available page, and starts three threads:
7 *  - thread1:
8 *	* madvise(MADV_DONTNEED) on the allocated huge page
9 *  - thread 2:
10 *	* Write to the allocated huge page
11 *  - thread 3:
12 *	* Try to allocated an extra huge page (which must not available)
13 *
14 *  The test fails if thread3 is able to allocate a page.
15 *
16 *  Touching the first page after thread3's allocation will raise a SIGBUS
17 *
18 *  Author: Breno Leitao <leitao@debian.org>
19 */
20#include <pthread.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <sys/mman.h>
24#include <sys/types.h>
25#include <unistd.h>
26
27#include "vm_util.h"
28#include "../kselftest.h"
29
30#define MMAP_SIZE (1 << 21)
31#define INLOOP_ITER 100
32
33char *huge_ptr;
34
35/* Touch the memory while it is being madvised() */
36void *touch(void *unused)
37{
38	for (int i = 0; i < INLOOP_ITER; i++)
39		huge_ptr[0] = '.';
40
41	return NULL;
42}
43
44void *madv(void *unused)
45{
46	for (int i = 0; i < INLOOP_ITER; i++)
47		madvise(huge_ptr, MMAP_SIZE, MADV_DONTNEED);
48
49	return NULL;
50}
51
52/*
53 * We got here, and there must be no huge page available for mapping
54 * The other hugepage should be flipping from used <-> reserved, because
55 * of madvise(DONTNEED).
56 */
57void *map_extra(void *unused)
58{
59	void *ptr;
60
61	for (int i = 0; i < INLOOP_ITER; i++) {
62		ptr = mmap(NULL, MMAP_SIZE, PROT_READ | PROT_WRITE,
63			   MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
64			   -1, 0);
65
66		if ((long)ptr != -1) {
67			/* Touching the other page now will cause a SIGBUG
68			 * huge_ptr[0] = '1';
69			 */
70			return ptr;
71		}
72	}
73
74	return NULL;
75}
76
77int main(void)
78{
79	pthread_t thread1, thread2, thread3;
80	unsigned long free_hugepages;
81	void *ret;
82
83	/*
84	 * On kernel 6.7, we are able to reproduce the problem with ~10
85	 * interactions
86	 */
87	int max = 10;
88
89	free_hugepages = get_free_hugepages();
90
91	if (free_hugepages != 1) {
92		ksft_exit_skip("This test needs one and only one page to execute. Got %lu\n",
93			       free_hugepages);
94	}
95
96	while (max--) {
97		huge_ptr = mmap(NULL, MMAP_SIZE, PROT_READ | PROT_WRITE,
98				MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
99				-1, 0);
100
101		if ((unsigned long)huge_ptr == -1) {
102			ksft_exit_skip("Failed to allocated huge page\n");
103			return KSFT_SKIP;
104		}
105
106		pthread_create(&thread1, NULL, madv, NULL);
107		pthread_create(&thread2, NULL, touch, NULL);
108		pthread_create(&thread3, NULL, map_extra, NULL);
109
110		pthread_join(thread1, NULL);
111		pthread_join(thread2, NULL);
112		pthread_join(thread3, &ret);
113
114		if (ret) {
115			ksft_test_result_fail("Unexpected huge page allocation\n");
116			return KSFT_FAIL;
117		}
118
119		/* Unmap and restart */
120		munmap(huge_ptr, MMAP_SIZE);
121	}
122
123	return KSFT_PASS;
124}
125