[PATCH v3] selftests/mm: skip hugetlb_dio tests when DIO alignment is incompatible

Li Wang
2026-03-30T05:39:32Z

hugetlb_dio test uses sub-page offsets (pagesize / 2) to verify that
hugepages used as DIO user buffers are correctly unpinned at completion.

However, on filesystems with a logical block size larger than half the
page size (e.g., 4K-sector block devices), these unaligned DIO writes
are rejected with -EINVAL, causing the test to fail unexpectedly.

Add get_dio_alignment() to query the filesystem's required DIO alignment
via statx(STATX_DIOALIGN) and pass it to run_dio_using_hugetlb(). Skip
individual test cases whose writesize/buf-offset is not a multiple of the
alignment, so that aligned cases are still tested.

=== Reproduce Steps ===

# dd if=/dev/zero of=/tmp/test.img bs=1M count=512
# losetup --sector-size 4096 /dev/loop0 /tmp/test.img
# mkfs.xfs /dev/loop0
# mkdir -p /mnt/dio_test
# mount /dev/loop0 /mnt/dio_test

// Modify test to open /mnt/dio_test and rebuild it:
- fd = open("/tmp", O_TMPFILE | O_RDWR | O_DIRECT, 0664);
+ fd = open("/mnt/dio_test", O_TMPFILE | O_RDWR | O_DIRECT, 0664);

# getconf PAGESIZE
4096

# echo 100 >/proc/sys/vm/nr_hugepages

# ./hugetlb_dio
TAP version 13
1..4
# No. Free pages before allocation : 100
# No. Free pages after munmap : 100
ok 1 free huge pages from 0-12288
Bail out! Error writing to file
: Invalid argument (22)
# Planned tests != run tests (4 != 1)
# Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:0

Signed-off-by: Li Wang <liwang@redhat.com>
---

Notes:
v3:
- Adopt statx raw syscall to build on older glibc.
- add buf offset alignment check as well
v2:
- Pass dio_align as a parameter to run_dio_using_hugetlb()
instead of generally page_size/2 alignment check.
- Add O_DIRECT flag back to the first open().
- Add stx_dio_offset_align zero check.

tools/testing/selftests/mm/hugetlb_dio.c | 62 +++++++++++++++++-------
1 file changed, 45 insertions(+), 17 deletions(-)

diff --git a/tools/testing/selftests/mm/hugetlb_dio.c b/tools/testing/selftests/mm/hugetlb_dio.c
index 9ac62eb4c97d..b125092fc6c1 100644
--- a/tools/testing/selftests/mm/hugetlb_dio.c
+++ b/tools/testing/selftests/mm/hugetlb_dio.c
@@ -17,10 +17,41 @@
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
+#include <sys/syscall.h>
#include "vm_util.h"
#include "kselftest.h"

-void run_dio_using_hugetlb(unsigned int start_off, unsigned int end_off)
+#define HP_DIO_TMPDIR "/tmp"
+
+#ifndef STATX_DIOALIGN
+#define STATX_DIOALIGN 0x00002000U
+#endif
+
+unsigned int get_dio_alignment(void)
+{
+ int fd, ret;
+ struct statx stx;
+ unsigned int dio_align = 1;
+
+ fd = open(HP_DIO_TMPDIR, O_TMPFILE | O_RDWR | O_DIRECT, 0664);
+ if (fd < 0)
+ ksft_exit_skip("Unable to allocate file: %s\n", strerror(errno));
+
+ ret = syscall(__NR_statx, fd, "", AT_EMPTY_PATH, STATX_DIOALIGN, &stx);
+ if (ret < 0) {
+ ksft_perror("statx() failed");
+ } else if ((stx.stx_mask & STATX_DIOALIGN) &&
+ stx.stx_dio_offset_align) {
+ dio_align = stx.stx_dio_offset_align;
+ }
+
+ close(fd);
+
+ return dio_align;
+}
+
+void run_dio_using_hugetlb(unsigned int start_off, unsigned int end_off,
+ unsigned int dio_align)
{
int fd;
char *buffer = NULL;
@@ -33,6 +64,12 @@ void run_dio_using_hugetlb(unsigned int start_off, unsigned int end_off)
const int mmap_prot = PROT_READ | PROT_WRITE;

writesize = end_off - start_off;
+ if (start_off % dio_align != 0 || writesize % dio_align != 0) {
+ ksft_test_result_skip("DIO alignment (%u) incompatible with "
+ "buf offset %u and writesize %zu\n",
+ dio_align, start_off, writesize);
+ return;
+ }

/* Get the default huge page size */
h_pagesize = default_huge_page_size();
@@ -40,7 +77,7 @@ void run_dio_using_hugetlb(unsigned int start_off, unsigned int end_off)
ksft_exit_fail_msg("Unable to determine huge page size\n");

/* Open the file to DIO */
- fd = open("/tmp", O_TMPFILE | O_RDWR | O_DIRECT, 0664);
+ fd = open(HP_DIO_TMPDIR, O_TMPFILE | O_RDWR | O_DIRECT, 0664);
if (fd < 0)
ksft_exit_fail_perror("Error opening file\n");

@@ -89,37 +126,28 @@ void run_dio_using_hugetlb(unsigned int start_off, unsigned int end_off)

int main(void)
{
- size_t pagesize = 0;
- int fd;
+ size_t pagesize = psize();
+ unsigned int dio_align = get_dio_alignment();

ksft_print_header();

- /* Open the file to DIO */
- fd = open("/tmp", O_TMPFILE | O_RDWR | O_DIRECT, 0664);
- if (fd < 0)
- ksft_exit_skip("Unable to allocate file: %s\n", strerror(errno));
- close(fd);
-
/* Check if huge pages are free */
if (!get_free_hugepages())
ksft_exit_skip("No free hugepage, exiting\n");

ksft_set_plan(4);

- /* Get base page size */
- pagesize = psize();
-
/* start and end is aligned to pagesize */
- run_dio_using_hugetlb(0, (pagesize * 3));
+ run_dio_using_hugetlb(0, (pagesize * 3), dio_align);

/* start is aligned but end is not aligned */
- run_dio_using_hugetlb(0, (pagesize * 3) - (pagesize / 2));
+ run_dio_using_hugetlb(0, (pagesize * 3) - (pagesize / 2), dio_align);

/* start is unaligned and end is aligned */
- run_dio_using_hugetlb(pagesize / 2, (pagesize * 3));
+ run_dio_using_hugetlb(pagesize / 2, (pagesize * 3), dio_align);

/* both start and end are unaligned */
- run_dio_using_hugetlb(pagesize / 2, (pagesize * 3) + (pagesize / 2));
+ run_dio_using_hugetlb(pagesize / 2, (pagesize * 3) + (pagesize / 2), dio_align);

ksft_finished();
}
--
2.53.0