| // SPDX-License-Identifier: GPL-2.0 |
| |
| #define _GNU_SOURCE |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <linux/magic.h> |
| #include <sys/mman.h> |
| #include <sys/statfs.h> |
| #include <errno.h> |
| #include <stdbool.h> |
| |
| #include "../kselftest.h" |
| |
| #define PREFIX " ... " |
| #define ERROR_PREFIX " !!! " |
| |
| #define MAX_WRITE_READ_CHUNK_SIZE (getpagesize() * 16) |
| #define MAX(a, b) (((a) > (b)) ? (a) : (b)) |
| |
| enum test_status { |
| TEST_PASSED = 0, |
| TEST_FAILED = 1, |
| TEST_SKIPPED = 2, |
| }; |
| |
| static char *status_to_str(enum test_status status) |
| { |
| switch (status) { |
| case TEST_PASSED: |
| return "TEST_PASSED"; |
| case TEST_FAILED: |
| return "TEST_FAILED"; |
| case TEST_SKIPPED: |
| return "TEST_SKIPPED"; |
| default: |
| return "TEST_???"; |
| } |
| } |
| |
| static int setup_filemap(char *filemap, size_t len, size_t wr_chunk_size) |
| { |
| char iter = 0; |
| |
| for (size_t offset = 0; offset < len; |
| offset += wr_chunk_size) { |
| iter++; |
| memset(filemap + offset, iter, wr_chunk_size); |
| } |
| |
| return 0; |
| } |
| |
| static bool verify_chunk(char *buf, size_t len, char val) |
| { |
| size_t i; |
| |
| for (i = 0; i < len; ++i) { |
| if (buf[i] != val) { |
| printf(PREFIX ERROR_PREFIX "check fail: buf[%lu] = %u != %u\n", |
| i, buf[i], val); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| static bool seek_read_hugepage_filemap(int fd, size_t len, size_t wr_chunk_size, |
| off_t offset, size_t expected) |
| { |
| char buf[MAX_WRITE_READ_CHUNK_SIZE]; |
| ssize_t ret_count = 0; |
| ssize_t total_ret_count = 0; |
| char val = offset / wr_chunk_size + offset % wr_chunk_size; |
| |
| printf(PREFIX PREFIX "init val=%u with offset=0x%lx\n", val, offset); |
| printf(PREFIX PREFIX "expect to read 0x%lx bytes of data in total\n", |
| expected); |
| if (lseek(fd, offset, SEEK_SET) < 0) { |
| perror(PREFIX ERROR_PREFIX "seek failed"); |
| return false; |
| } |
| |
| while (offset + total_ret_count < len) { |
| ret_count = read(fd, buf, wr_chunk_size); |
| if (ret_count == 0) { |
| printf(PREFIX PREFIX "read reach end of the file\n"); |
| break; |
| } else if (ret_count < 0) { |
| perror(PREFIX ERROR_PREFIX "read failed"); |
| break; |
| } |
| ++val; |
| if (!verify_chunk(buf, ret_count, val)) |
| return false; |
| |
| total_ret_count += ret_count; |
| } |
| printf(PREFIX PREFIX "actually read 0x%lx bytes of data in total\n", |
| total_ret_count); |
| |
| return total_ret_count == expected; |
| } |
| |
| static bool read_hugepage_filemap(int fd, size_t len, |
| size_t wr_chunk_size, size_t expected) |
| { |
| char buf[MAX_WRITE_READ_CHUNK_SIZE]; |
| ssize_t ret_count = 0; |
| ssize_t total_ret_count = 0; |
| char val = 0; |
| |
| printf(PREFIX PREFIX "expect to read 0x%lx bytes of data in total\n", |
| expected); |
| while (total_ret_count < len) { |
| ret_count = read(fd, buf, wr_chunk_size); |
| if (ret_count == 0) { |
| printf(PREFIX PREFIX "read reach end of the file\n"); |
| break; |
| } else if (ret_count < 0) { |
| perror(PREFIX ERROR_PREFIX "read failed"); |
| break; |
| } |
| ++val; |
| if (!verify_chunk(buf, ret_count, val)) |
| return false; |
| |
| total_ret_count += ret_count; |
| } |
| printf(PREFIX PREFIX "actually read 0x%lx bytes of data in total\n", |
| total_ret_count); |
| |
| return total_ret_count == expected; |
| } |
| |
| static enum test_status |
| test_hugetlb_read(int fd, size_t len, size_t wr_chunk_size) |
| { |
| enum test_status status = TEST_SKIPPED; |
| char *filemap = NULL; |
| |
| if (ftruncate(fd, len) < 0) { |
| perror(PREFIX ERROR_PREFIX "ftruncate failed"); |
| return status; |
| } |
| |
| filemap = mmap(NULL, len, PROT_READ | PROT_WRITE, |
| MAP_SHARED | MAP_POPULATE, fd, 0); |
| if (filemap == MAP_FAILED) { |
| perror(PREFIX ERROR_PREFIX "mmap for primary mapping failed"); |
| goto done; |
| } |
| |
| setup_filemap(filemap, len, wr_chunk_size); |
| status = TEST_FAILED; |
| |
| if (read_hugepage_filemap(fd, len, wr_chunk_size, len)) |
| status = TEST_PASSED; |
| |
| munmap(filemap, len); |
| done: |
| if (ftruncate(fd, 0) < 0) { |
| perror(PREFIX ERROR_PREFIX "ftruncate back to 0 failed"); |
| status = TEST_FAILED; |
| } |
| |
| return status; |
| } |
| |
| static enum test_status |
| test_hugetlb_read_hwpoison(int fd, size_t len, size_t wr_chunk_size, |
| bool skip_hwpoison_page) |
| { |
| enum test_status status = TEST_SKIPPED; |
| char *filemap = NULL; |
| char *hwp_addr = NULL; |
| const unsigned long pagesize = getpagesize(); |
| |
| if (ftruncate(fd, len) < 0) { |
| perror(PREFIX ERROR_PREFIX "ftruncate failed"); |
| return status; |
| } |
| |
| filemap = mmap(NULL, len, PROT_READ | PROT_WRITE, |
| MAP_SHARED | MAP_POPULATE, fd, 0); |
| if (filemap == MAP_FAILED) { |
| perror(PREFIX ERROR_PREFIX "mmap for primary mapping failed"); |
| goto done; |
| } |
| |
| setup_filemap(filemap, len, wr_chunk_size); |
| status = TEST_FAILED; |
| |
| /* |
| * Poisoned hugetlb page layout (assume hugepagesize=2MB): |
| * |<---------------------- 1MB ---------------------->| |
| * |<---- healthy page ---->|<---- HWPOISON page ----->| |
| * |<------------------- (1MB - 8KB) ----------------->| |
| */ |
| hwp_addr = filemap + len / 2 + pagesize; |
| if (madvise(hwp_addr, pagesize, MADV_HWPOISON) < 0) { |
| perror(PREFIX ERROR_PREFIX "MADV_HWPOISON failed"); |
| goto unmap; |
| } |
| |
| if (!skip_hwpoison_page) { |
| /* |
| * Userspace should be able to read (1MB + 1 page) from |
| * the beginning of the HWPOISONed hugepage. |
| */ |
| if (read_hugepage_filemap(fd, len, wr_chunk_size, |
| len / 2 + pagesize)) |
| status = TEST_PASSED; |
| } else { |
| /* |
| * Userspace should be able to read (1MB - 2 pages) from |
| * HWPOISONed hugepage. |
| */ |
| if (seek_read_hugepage_filemap(fd, len, wr_chunk_size, |
| len / 2 + MAX(2 * pagesize, wr_chunk_size), |
| len / 2 - MAX(2 * pagesize, wr_chunk_size))) |
| status = TEST_PASSED; |
| } |
| |
| unmap: |
| munmap(filemap, len); |
| done: |
| if (ftruncate(fd, 0) < 0) { |
| perror(PREFIX ERROR_PREFIX "ftruncate back to 0 failed"); |
| status = TEST_FAILED; |
| } |
| |
| return status; |
| } |
| |
| static int create_hugetlbfs_file(struct statfs *file_stat) |
| { |
| int fd; |
| |
| fd = memfd_create("hugetlb_tmp", MFD_HUGETLB); |
| if (fd < 0) { |
| perror(PREFIX ERROR_PREFIX "could not open hugetlbfs file"); |
| return -1; |
| } |
| |
| memset(file_stat, 0, sizeof(*file_stat)); |
| if (fstatfs(fd, file_stat)) { |
| perror(PREFIX ERROR_PREFIX "fstatfs failed"); |
| goto close; |
| } |
| if (file_stat->f_type != HUGETLBFS_MAGIC) { |
| printf(PREFIX ERROR_PREFIX "not hugetlbfs file\n"); |
| goto close; |
| } |
| |
| return fd; |
| close: |
| close(fd); |
| return -1; |
| } |
| |
| int main(void) |
| { |
| int fd; |
| struct statfs file_stat; |
| enum test_status status; |
| /* Test read() in different granularity. */ |
| size_t wr_chunk_sizes[] = { |
| getpagesize() / 2, getpagesize(), |
| getpagesize() * 2, getpagesize() * 4 |
| }; |
| size_t i; |
| |
| for (i = 0; i < ARRAY_SIZE(wr_chunk_sizes); ++i) { |
| printf("Write/read chunk size=0x%lx\n", |
| wr_chunk_sizes[i]); |
| |
| fd = create_hugetlbfs_file(&file_stat); |
| if (fd < 0) |
| goto create_failure; |
| printf(PREFIX "HugeTLB read regression test...\n"); |
| status = test_hugetlb_read(fd, file_stat.f_bsize, |
| wr_chunk_sizes[i]); |
| printf(PREFIX "HugeTLB read regression test...%s\n", |
| status_to_str(status)); |
| close(fd); |
| if (status == TEST_FAILED) |
| return -1; |
| |
| fd = create_hugetlbfs_file(&file_stat); |
| if (fd < 0) |
| goto create_failure; |
| printf(PREFIX "HugeTLB read HWPOISON test...\n"); |
| status = test_hugetlb_read_hwpoison(fd, file_stat.f_bsize, |
| wr_chunk_sizes[i], false); |
| printf(PREFIX "HugeTLB read HWPOISON test...%s\n", |
| status_to_str(status)); |
| close(fd); |
| if (status == TEST_FAILED) |
| return -1; |
| |
| fd = create_hugetlbfs_file(&file_stat); |
| if (fd < 0) |
| goto create_failure; |
| printf(PREFIX "HugeTLB seek then read HWPOISON test...\n"); |
| status = test_hugetlb_read_hwpoison(fd, file_stat.f_bsize, |
| wr_chunk_sizes[i], true); |
| printf(PREFIX "HugeTLB seek then read HWPOISON test...%s\n", |
| status_to_str(status)); |
| close(fd); |
| if (status == TEST_FAILED) |
| return -1; |
| } |
| |
| return 0; |
| |
| create_failure: |
| printf(ERROR_PREFIX "Abort test: failed to create hugetlbfs file\n"); |
| return -1; |
| } |