xref: /linux/tools/testing/selftests/mm/hugetlb_dio.c (revision c532de5a67a70f8533d495f8f2aaa9a0491c3ad0)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * This program tests for hugepage leaks after DIO writes to a file using a
4  * hugepage as the user buffer. During DIO, the user buffer is pinned and
5  * should be properly unpinned upon completion. This patch verifies that the
6  * kernel correctly unpins the buffer at DIO completion for both aligned and
7  * unaligned user buffer offsets (w.r.t page boundary), ensuring the hugepage
8  * is freed upon unmapping.
9  */
10 
11 #define _GNU_SOURCE
12 #include <stdio.h>
13 #include <sys/stat.h>
14 #include <stdlib.h>
15 #include <fcntl.h>
16 #include <stdint.h>
17 #include <unistd.h>
18 #include <string.h>
19 #include <sys/mman.h>
20 #include "vm_util.h"
21 #include "../kselftest.h"
22 
23 void run_dio_using_hugetlb(unsigned int start_off, unsigned int end_off)
24 {
25 	int fd;
26 	char *buffer =  NULL;
27 	char *orig_buffer = NULL;
28 	size_t h_pagesize = 0;
29 	size_t writesize;
30 	int free_hpage_b = 0;
31 	int free_hpage_a = 0;
32 	const int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB;
33 	const int mmap_prot  = PROT_READ | PROT_WRITE;
34 
35 	writesize = end_off - start_off;
36 
37 	/* Get the default huge page size */
38 	h_pagesize = default_huge_page_size();
39 	if (!h_pagesize)
40 		ksft_exit_fail_msg("Unable to determine huge page size\n");
41 
42 	/* Open the file to DIO */
43 	fd = open("/tmp", O_TMPFILE | O_RDWR | O_DIRECT, 0664);
44 	if (fd < 0)
45 		ksft_exit_fail_perror("Error opening file\n");
46 
47 	/* Allocate a hugetlb page */
48 	orig_buffer = mmap(NULL, h_pagesize, mmap_prot, mmap_flags, -1, 0);
49 	if (orig_buffer == MAP_FAILED) {
50 		close(fd);
51 		ksft_exit_fail_perror("Error mapping memory\n");
52 	}
53 	buffer = orig_buffer;
54 	buffer += start_off;
55 
56 	memset(buffer, 'A', writesize);
57 
58 	/* Write the buffer to the file */
59 	if (write(fd, buffer, writesize) != (writesize)) {
60 		munmap(orig_buffer, h_pagesize);
61 		close(fd);
62 		ksft_exit_fail_perror("Error writing to file\n");
63 	}
64 
65 	/* unmap the huge page */
66 	munmap(orig_buffer, h_pagesize);
67 	close(fd);
68 
69 	/* Get the free huge pages after unmap*/
70 	free_hpage_a = get_free_hugepages();
71 
72 	/*
73 	 * If the no. of free hugepages before allocation and after unmap does
74 	 * not match - that means there could still be a page which is pinned.
75 	 */
76 	if (free_hpage_a != free_hpage_b) {
77 		ksft_print_msg("No. Free pages before allocation : %d\n", free_hpage_b);
78 		ksft_print_msg("No. Free pages after munmap : %d\n", free_hpage_a);
79 		ksft_test_result_fail(": Huge pages not freed!\n");
80 	} else {
81 		ksft_print_msg("No. Free pages before allocation : %d\n", free_hpage_b);
82 		ksft_print_msg("No. Free pages after munmap : %d\n", free_hpage_a);
83 		ksft_test_result_pass(": Huge pages freed successfully !\n");
84 	}
85 }
86 
87 int main(void)
88 {
89 	size_t pagesize = 0;
90 	int fd;
91 
92 	ksft_print_header();
93 
94 	/* Open the file to DIO */
95 	fd = open("/tmp", O_TMPFILE | O_RDWR | O_DIRECT, 0664);
96 	if (fd < 0)
97 		ksft_exit_skip("Unable to allocate file: %s\n", strerror(errno));
98 	close(fd);
99 
100 	/* Check if huge pages are free */
101 	if (!get_free_hugepages())
102 		ksft_exit_skip("No free hugepage, exiting\n");
103 
104 	ksft_set_plan(4);
105 
106 	/* Get base page size */
107 	pagesize  = psize();
108 
109 	/* start and end is aligned to pagesize */
110 	run_dio_using_hugetlb(0, (pagesize * 3));
111 
112 	/* start is aligned but end is not aligned */
113 	run_dio_using_hugetlb(0, (pagesize * 3) - (pagesize / 2));
114 
115 	/* start is unaligned and end is aligned */
116 	run_dio_using_hugetlb(pagesize / 2, (pagesize * 3));
117 
118 	/* both start and end are unaligned */
119 	run_dio_using_hugetlb(pagesize / 2, (pagesize * 3) + (pagesize / 2));
120 
121 	ksft_finished();
122 }
123