| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * This program tests for hugepage leaks after DIO writes to a file using a |
| * hugepage as the user buffer. During DIO, the user buffer is pinned and |
| * should be properly unpinned upon completion. This patch verifies that the |
| * kernel correctly unpins the buffer at DIO completion for both aligned and |
| * unaligned user buffer offsets (w.r.t page boundary), ensuring the hugepage |
| * is freed upon unmapping. |
| */ |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #include <sys/stat.h> |
| #include <stdlib.h> |
| #include <fcntl.h> |
| #include <stdint.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <sys/syscall.h> |
| #include "vm_util.h" |
| #include "kselftest.h" |
| #include "hugepage_settings.h" |
| |
| #ifndef STATX_DIOALIGN |
| #define STATX_DIOALIGN 0x00002000U |
| #endif |
| |
| static int get_dio_alignment(int fd) |
| { |
| struct statx stx; |
| int ret; |
| |
| ret = syscall(__NR_statx, fd, "", AT_EMPTY_PATH, STATX_DIOALIGN, &stx); |
| if (ret < 0) |
| return -1; |
| |
| /* |
| * If STATX_DIOALIGN is unsupported, assume no alignment |
| * constraint and let the test proceed. |
| */ |
| if (!(stx.stx_mask & STATX_DIOALIGN) || !stx.stx_dio_offset_align) |
| return 1; |
| |
| return stx.stx_dio_offset_align; |
| } |
| |
| static bool check_dio_alignment(unsigned int start_off, |
| unsigned int end_off, unsigned int align) |
| { |
| unsigned int writesize = end_off - start_off; |
| |
| /* |
| * The kernel's DIO path checks that file offset, length, and |
| * buffer address are all multiples of dio_offset_align. When |
| * this test case's parameters don't satisfy that, the write |
| * would fail with -EINVAL before exercising the hugetlb unpin |
| * path, so skip. |
| */ |
| if (start_off % align != 0 || writesize % align != 0) { |
| ksft_test_result_skip("DIO align=%u incompatible with offset %u writesize %u\n", |
| align, start_off, writesize); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static void run_dio_using_hugetlb(int fd, unsigned int start_off, |
| unsigned int end_off, unsigned int align) |
| { |
| char *buffer = NULL; |
| char *orig_buffer = NULL; |
| size_t h_pagesize = 0; |
| size_t writesize; |
| int free_hpage_b = 0; |
| int free_hpage_a = 0; |
| const int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB; |
| const int mmap_prot = PROT_READ | PROT_WRITE; |
| |
| if (!check_dio_alignment(start_off, end_off, align)) |
| return; |
| |
| writesize = end_off - start_off; |
| |
| /* Get the default huge page size */ |
| h_pagesize = default_huge_page_size(); |
| |
| /* Reset file position since fd is shared across tests */ |
| if (lseek(fd, 0, SEEK_SET) < 0) |
| ksft_exit_fail_perror("lseek failed\n"); |
| |
| /* Get the free huge pages before allocation */ |
| free_hpage_b = hugetlb_free_default_pages(); |
| |
| /* Allocate a hugetlb page */ |
| orig_buffer = mmap(NULL, h_pagesize, mmap_prot, mmap_flags, -1, 0); |
| if (orig_buffer == MAP_FAILED) { |
| close(fd); |
| ksft_exit_fail_perror("Error mapping memory\n"); |
| } |
| buffer = orig_buffer; |
| buffer += start_off; |
| |
| memset(buffer, 'A', writesize); |
| |
| /* Write the buffer to the file */ |
| if (write(fd, buffer, writesize) != (writesize)) { |
| munmap(orig_buffer, h_pagesize); |
| close(fd); |
| ksft_exit_fail_perror("Error writing to file\n"); |
| } |
| |
| /* unmap the huge page */ |
| munmap(orig_buffer, h_pagesize); |
| |
| /* Get the free huge pages after unmap*/ |
| free_hpage_a = hugetlb_free_default_pages(); |
| |
| ksft_print_msg("No. Free pages before allocation : %d\n", free_hpage_b); |
| ksft_print_msg("No. Free pages after munmap : %d\n", free_hpage_a); |
| |
| /* |
| * If the no. of free hugepages before allocation and after unmap does |
| * not match - that means there could still be a page which is pinned. |
| */ |
| ksft_test_result(free_hpage_a == free_hpage_b, |
| "free huge pages from %u-%u\n", start_off, end_off); |
| } |
| |
| int main(void) |
| { |
| int fd, align; |
| const size_t pagesize = psize(); |
| |
| ksft_print_header(); |
| |
| /* request a huge page */ |
| if (!hugetlb_setup_default(1)) |
| ksft_exit_skip("No free hugepage, exiting\n"); |
| |
| fd = open("/tmp", O_TMPFILE | O_RDWR | O_DIRECT, 0664); |
| if (fd < 0) |
| ksft_exit_skip("Unable to allocate file: %s\n", strerror(errno)); |
| |
| align = get_dio_alignment(fd); |
| if (align < 0) |
| ksft_exit_skip("Unable to obtain DIO alignment: %s\n", |
| strerror(errno)); |
| ksft_set_plan(4); |
| |
| /* start and end is aligned to pagesize */ |
| run_dio_using_hugetlb(fd, 0, (pagesize * 3), align); |
| |
| /* start is aligned but end is not aligned */ |
| run_dio_using_hugetlb(fd, 0, (pagesize * 3) - (pagesize / 2), align); |
| |
| /* start is unaligned and end is aligned */ |
| run_dio_using_hugetlb(fd, pagesize / 2, (pagesize * 3), align); |
| |
| /* both start and end are unaligned */ |
| run_dio_using_hugetlb(fd, pagesize / 2, (pagesize * 3) + (pagesize / 2), align); |
| |
| close(fd); |
| |
| ksft_finished(); |
| } |