xref: /linux/tools/testing/selftests/mm/hugetlb_dio.c (revision 7f71507851fc7764b36a3221839607d3a45c2025)
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 	/* Get the free huge pages before allocation */
48 	free_hpage_b = get_free_hugepages();
49 	if (free_hpage_b == 0) {
50 		close(fd);
51 		ksft_exit_skip("No free hugepage, exiting!\n");
52 	}
53 
54 	/* Allocate a hugetlb page */
55 	orig_buffer = mmap(NULL, h_pagesize, mmap_prot, mmap_flags, -1, 0);
56 	if (orig_buffer == MAP_FAILED) {
57 		close(fd);
58 		ksft_exit_fail_perror("Error mapping memory\n");
59 	}
60 	buffer = orig_buffer;
61 	buffer += start_off;
62 
63 	memset(buffer, 'A', writesize);
64 
65 	/* Write the buffer to the file */
66 	if (write(fd, buffer, writesize) != (writesize)) {
67 		munmap(orig_buffer, h_pagesize);
68 		close(fd);
69 		ksft_exit_fail_perror("Error writing to file\n");
70 	}
71 
72 	/* unmap the huge page */
73 	munmap(orig_buffer, h_pagesize);
74 	close(fd);
75 
76 	/* Get the free huge pages after unmap*/
77 	free_hpage_a = get_free_hugepages();
78 
79 	/*
80 	 * If the no. of free hugepages before allocation and after unmap does
81 	 * not match - that means there could still be a page which is pinned.
82 	 */
83 	if (free_hpage_a != free_hpage_b) {
84 		ksft_print_msg("No. Free pages before allocation : %d\n", free_hpage_b);
85 		ksft_print_msg("No. Free pages after munmap : %d\n", free_hpage_a);
86 		ksft_test_result_fail(": Huge pages not freed!\n");
87 	} else {
88 		ksft_print_msg("No. Free pages before allocation : %d\n", free_hpage_b);
89 		ksft_print_msg("No. Free pages after munmap : %d\n", free_hpage_a);
90 		ksft_test_result_pass(": Huge pages freed successfully !\n");
91 	}
92 }
93 
94 int main(void)
95 {
96 	size_t pagesize = 0;
97 	int fd;
98 
99 	ksft_print_header();
100 
101 	/* Open the file to DIO */
102 	fd = open("/tmp", O_TMPFILE | O_RDWR | O_DIRECT, 0664);
103 	if (fd < 0)
104 		ksft_exit_skip("Unable to allocate file: %s\n", strerror(errno));
105 	close(fd);
106 
107 	/* Check if huge pages are free */
108 	if (!get_free_hugepages())
109 		ksft_exit_skip("No free hugepage, exiting\n");
110 
111 	ksft_set_plan(4);
112 
113 	/* Get base page size */
114 	pagesize  = psize();
115 
116 	/* start and end is aligned to pagesize */
117 	run_dio_using_hugetlb(0, (pagesize * 3));
118 
119 	/* start is aligned but end is not aligned */
120 	run_dio_using_hugetlb(0, (pagesize * 3) - (pagesize / 2));
121 
122 	/* start is unaligned and end is aligned */
123 	run_dio_using_hugetlb(pagesize / 2, (pagesize * 3));
124 
125 	/* both start and end are unaligned */
126 	run_dio_using_hugetlb(pagesize / 2, (pagesize * 3) + (pagesize / 2));
127 
128 	ksft_finished();
129 }
130