162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/* Test selecting other page sizes for mmap/shmget.
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci   Before running this huge pages for each huge page size must have been
562306a36Sopenharmony_ci   reserved.
662306a36Sopenharmony_ci   For large pages beyond MAX_ORDER (like 1GB on x86) boot options must be used.
762306a36Sopenharmony_ci   Also shmmax must be increased.
862306a36Sopenharmony_ci   And you need to run as root to work around some weird permissions in shm.
962306a36Sopenharmony_ci   And nothing using huge pages should run in parallel.
1062306a36Sopenharmony_ci   When the program aborts you may need to clean up the shm segments with
1162306a36Sopenharmony_ci   ipcrm -m by hand, like this
1262306a36Sopenharmony_ci   sudo ipcs | awk '$1 == "0x00000000" {print $2}' | xargs -n1 sudo ipcrm -m
1362306a36Sopenharmony_ci   (warning this will remove all if someone else uses them) */
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define _GNU_SOURCE 1
1662306a36Sopenharmony_ci#include <sys/mman.h>
1762306a36Sopenharmony_ci#include <stdlib.h>
1862306a36Sopenharmony_ci#include <stdio.h>
1962306a36Sopenharmony_ci#include <sys/ipc.h>
2062306a36Sopenharmony_ci#include <sys/shm.h>
2162306a36Sopenharmony_ci#include <sys/stat.h>
2262306a36Sopenharmony_ci#include <glob.h>
2362306a36Sopenharmony_ci#include <assert.h>
2462306a36Sopenharmony_ci#include <unistd.h>
2562306a36Sopenharmony_ci#include <stdarg.h>
2662306a36Sopenharmony_ci#include <string.h>
2762306a36Sopenharmony_ci#include "vm_util.h"
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define err(x) perror(x), exit(1)
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#define MAP_HUGE_2MB    (21 << MAP_HUGE_SHIFT)
3262306a36Sopenharmony_ci#define MAP_HUGE_1GB    (30 << MAP_HUGE_SHIFT)
3362306a36Sopenharmony_ci#define MAP_HUGE_SHIFT  26
3462306a36Sopenharmony_ci#define MAP_HUGE_MASK   0x3f
3562306a36Sopenharmony_ci#if !defined(MAP_HUGETLB)
3662306a36Sopenharmony_ci#define MAP_HUGETLB	0x40000
3762306a36Sopenharmony_ci#endif
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci#define SHM_HUGETLB     04000   /* segment will use huge TLB pages */
4062306a36Sopenharmony_ci#define SHM_HUGE_SHIFT  26
4162306a36Sopenharmony_ci#define SHM_HUGE_MASK   0x3f
4262306a36Sopenharmony_ci#define SHM_HUGE_2MB    (21 << SHM_HUGE_SHIFT)
4362306a36Sopenharmony_ci#define SHM_HUGE_1GB    (30 << SHM_HUGE_SHIFT)
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci#define NUM_PAGESIZES   5
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci#define NUM_PAGES 4
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci#define Dprintf(fmt...) // printf(fmt)
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ciunsigned long page_sizes[NUM_PAGESIZES];
5262306a36Sopenharmony_ciint num_page_sizes;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ciint ilog2(unsigned long v)
5562306a36Sopenharmony_ci{
5662306a36Sopenharmony_ci	int l = 0;
5762306a36Sopenharmony_ci	while ((1UL << l) < v)
5862306a36Sopenharmony_ci		l++;
5962306a36Sopenharmony_ci	return l;
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_civoid find_pagesizes(void)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	glob_t g;
6562306a36Sopenharmony_ci	int i;
6662306a36Sopenharmony_ci	glob("/sys/kernel/mm/hugepages/hugepages-*kB", 0, NULL, &g);
6762306a36Sopenharmony_ci	assert(g.gl_pathc <= NUM_PAGESIZES);
6862306a36Sopenharmony_ci	for (i = 0; i < g.gl_pathc; i++) {
6962306a36Sopenharmony_ci		sscanf(g.gl_pathv[i], "/sys/kernel/mm/hugepages/hugepages-%lukB",
7062306a36Sopenharmony_ci				&page_sizes[i]);
7162306a36Sopenharmony_ci		page_sizes[i] <<= 10;
7262306a36Sopenharmony_ci		printf("Found %luMB\n", page_sizes[i] >> 20);
7362306a36Sopenharmony_ci	}
7462306a36Sopenharmony_ci	num_page_sizes = g.gl_pathc;
7562306a36Sopenharmony_ci	globfree(&g);
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_civoid show(unsigned long ps)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	char buf[100];
8162306a36Sopenharmony_ci	if (ps == getpagesize())
8262306a36Sopenharmony_ci		return;
8362306a36Sopenharmony_ci	printf("%luMB: ", ps >> 20);
8462306a36Sopenharmony_ci	fflush(stdout);
8562306a36Sopenharmony_ci	snprintf(buf, sizeof buf,
8662306a36Sopenharmony_ci		"cat /sys/kernel/mm/hugepages/hugepages-%lukB/free_hugepages",
8762306a36Sopenharmony_ci		ps >> 10);
8862306a36Sopenharmony_ci	system(buf);
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ciunsigned long read_sysfs(int warn, char *fmt, ...)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	char *line = NULL;
9462306a36Sopenharmony_ci	size_t linelen = 0;
9562306a36Sopenharmony_ci	char buf[100];
9662306a36Sopenharmony_ci	FILE *f;
9762306a36Sopenharmony_ci	va_list ap;
9862306a36Sopenharmony_ci	unsigned long val = 0;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	va_start(ap, fmt);
10162306a36Sopenharmony_ci	vsnprintf(buf, sizeof buf, fmt, ap);
10262306a36Sopenharmony_ci	va_end(ap);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	f = fopen(buf, "r");
10562306a36Sopenharmony_ci	if (!f) {
10662306a36Sopenharmony_ci		if (warn)
10762306a36Sopenharmony_ci			printf("missing %s\n", buf);
10862306a36Sopenharmony_ci		return 0;
10962306a36Sopenharmony_ci	}
11062306a36Sopenharmony_ci	if (getline(&line, &linelen, f) > 0) {
11162306a36Sopenharmony_ci		sscanf(line, "%lu", &val);
11262306a36Sopenharmony_ci	}
11362306a36Sopenharmony_ci	fclose(f);
11462306a36Sopenharmony_ci	free(line);
11562306a36Sopenharmony_ci	return val;
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ciunsigned long read_free(unsigned long ps)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	return read_sysfs(ps != getpagesize(),
12162306a36Sopenharmony_ci			"/sys/kernel/mm/hugepages/hugepages-%lukB/free_hugepages",
12262306a36Sopenharmony_ci			ps >> 10);
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_civoid test_mmap(unsigned long size, unsigned flags)
12662306a36Sopenharmony_ci{
12762306a36Sopenharmony_ci	char *map;
12862306a36Sopenharmony_ci	unsigned long before, after;
12962306a36Sopenharmony_ci	int err;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	before = read_free(size);
13262306a36Sopenharmony_ci	map = mmap(NULL, size*NUM_PAGES, PROT_READ|PROT_WRITE,
13362306a36Sopenharmony_ci			MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB|flags, -1, 0);
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	if (map == (char *)-1) err("mmap");
13662306a36Sopenharmony_ci	memset(map, 0xff, size*NUM_PAGES);
13762306a36Sopenharmony_ci	after = read_free(size);
13862306a36Sopenharmony_ci	Dprintf("before %lu after %lu diff %ld size %lu\n",
13962306a36Sopenharmony_ci		before, after, before - after, size);
14062306a36Sopenharmony_ci	assert(size == getpagesize() || (before - after) == NUM_PAGES);
14162306a36Sopenharmony_ci	show(size);
14262306a36Sopenharmony_ci	err = munmap(map, size * NUM_PAGES);
14362306a36Sopenharmony_ci	assert(!err);
14462306a36Sopenharmony_ci}
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_civoid test_shmget(unsigned long size, unsigned flags)
14762306a36Sopenharmony_ci{
14862306a36Sopenharmony_ci	int id;
14962306a36Sopenharmony_ci	unsigned long before, after;
15062306a36Sopenharmony_ci	int err;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	before = read_free(size);
15362306a36Sopenharmony_ci	id = shmget(IPC_PRIVATE, size * NUM_PAGES, IPC_CREAT|0600|flags);
15462306a36Sopenharmony_ci	if (id < 0) err("shmget");
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	struct shm_info i;
15762306a36Sopenharmony_ci	if (shmctl(id, SHM_INFO, (void *)&i) < 0) err("shmctl");
15862306a36Sopenharmony_ci	Dprintf("alloc %lu res %lu\n", i.shm_tot, i.shm_rss);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	Dprintf("id %d\n", id);
16262306a36Sopenharmony_ci	char *map = shmat(id, NULL, 0600);
16362306a36Sopenharmony_ci	if (map == (char*)-1) err("shmat");
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	shmctl(id, IPC_RMID, NULL);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	memset(map, 0xff, size*NUM_PAGES);
16862306a36Sopenharmony_ci	after = read_free(size);
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	Dprintf("before %lu after %lu diff %ld size %lu\n",
17162306a36Sopenharmony_ci		before, after, before - after, size);
17262306a36Sopenharmony_ci	assert(size == getpagesize() || (before - after) == NUM_PAGES);
17362306a36Sopenharmony_ci	show(size);
17462306a36Sopenharmony_ci	err = shmdt(map);
17562306a36Sopenharmony_ci	assert(!err);
17662306a36Sopenharmony_ci}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_civoid sanity_checks(void)
17962306a36Sopenharmony_ci{
18062306a36Sopenharmony_ci	int i;
18162306a36Sopenharmony_ci	unsigned long largest = getpagesize();
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	for (i = 0; i < num_page_sizes; i++) {
18462306a36Sopenharmony_ci		if (page_sizes[i] > largest)
18562306a36Sopenharmony_ci			largest = page_sizes[i];
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci		if (read_free(page_sizes[i]) < NUM_PAGES) {
18862306a36Sopenharmony_ci			printf("Not enough huge pages for page size %lu MB, need %u\n",
18962306a36Sopenharmony_ci				page_sizes[i] >> 20,
19062306a36Sopenharmony_ci				NUM_PAGES);
19162306a36Sopenharmony_ci			exit(0);
19262306a36Sopenharmony_ci		}
19362306a36Sopenharmony_ci	}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	if (read_sysfs(0, "/proc/sys/kernel/shmmax") < NUM_PAGES * largest) {
19662306a36Sopenharmony_ci		printf("Please do echo %lu > /proc/sys/kernel/shmmax", largest * NUM_PAGES);
19762306a36Sopenharmony_ci		exit(0);
19862306a36Sopenharmony_ci	}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci#if defined(__x86_64__)
20162306a36Sopenharmony_ci	if (largest != 1U<<30) {
20262306a36Sopenharmony_ci		printf("No GB pages available on x86-64\n"
20362306a36Sopenharmony_ci		       "Please boot with hugepagesz=1G hugepages=%d\n", NUM_PAGES);
20462306a36Sopenharmony_ci		exit(0);
20562306a36Sopenharmony_ci	}
20662306a36Sopenharmony_ci#endif
20762306a36Sopenharmony_ci}
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ciint main(void)
21062306a36Sopenharmony_ci{
21162306a36Sopenharmony_ci	int i;
21262306a36Sopenharmony_ci	unsigned default_hps = default_huge_page_size();
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	find_pagesizes();
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	sanity_checks();
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	for (i = 0; i < num_page_sizes; i++) {
21962306a36Sopenharmony_ci		unsigned long ps = page_sizes[i];
22062306a36Sopenharmony_ci		int arg = ilog2(ps) << MAP_HUGE_SHIFT;
22162306a36Sopenharmony_ci		printf("Testing %luMB mmap with shift %x\n", ps >> 20, arg);
22262306a36Sopenharmony_ci		test_mmap(ps, MAP_HUGETLB | arg);
22362306a36Sopenharmony_ci	}
22462306a36Sopenharmony_ci	printf("Testing default huge mmap\n");
22562306a36Sopenharmony_ci	test_mmap(default_hps, MAP_HUGETLB);
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	puts("Testing non-huge shmget");
22862306a36Sopenharmony_ci	test_shmget(getpagesize(), 0);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	for (i = 0; i < num_page_sizes; i++) {
23162306a36Sopenharmony_ci		unsigned long ps = page_sizes[i];
23262306a36Sopenharmony_ci		int arg = ilog2(ps) << SHM_HUGE_SHIFT;
23362306a36Sopenharmony_ci		printf("Testing %luMB shmget with shift %x\n", ps >> 20, arg);
23462306a36Sopenharmony_ci		test_shmget(ps, SHM_HUGETLB | arg);
23562306a36Sopenharmony_ci	}
23662306a36Sopenharmony_ci	puts("default huge shmget");
23762306a36Sopenharmony_ci	test_shmget(default_hps, SHM_HUGETLB);
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	return 0;
24062306a36Sopenharmony_ci}
241