162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci#define _GNU_SOURCE 462306a36Sopenharmony_ci#include <stdlib.h> 562306a36Sopenharmony_ci#include <stdio.h> 662306a36Sopenharmony_ci#include <string.h> 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/magic.h> 962306a36Sopenharmony_ci#include <sys/mman.h> 1062306a36Sopenharmony_ci#include <sys/statfs.h> 1162306a36Sopenharmony_ci#include <errno.h> 1262306a36Sopenharmony_ci#include <stdbool.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include "../kselftest.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#define PREFIX " ... " 1762306a36Sopenharmony_ci#define ERROR_PREFIX " !!! " 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#define MAX_WRITE_READ_CHUNK_SIZE (getpagesize() * 16) 2062306a36Sopenharmony_ci#define MAX(a, b) (((a) > (b)) ? (a) : (b)) 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cienum test_status { 2362306a36Sopenharmony_ci TEST_PASSED = 0, 2462306a36Sopenharmony_ci TEST_FAILED = 1, 2562306a36Sopenharmony_ci TEST_SKIPPED = 2, 2662306a36Sopenharmony_ci}; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic char *status_to_str(enum test_status status) 2962306a36Sopenharmony_ci{ 3062306a36Sopenharmony_ci switch (status) { 3162306a36Sopenharmony_ci case TEST_PASSED: 3262306a36Sopenharmony_ci return "TEST_PASSED"; 3362306a36Sopenharmony_ci case TEST_FAILED: 3462306a36Sopenharmony_ci return "TEST_FAILED"; 3562306a36Sopenharmony_ci case TEST_SKIPPED: 3662306a36Sopenharmony_ci return "TEST_SKIPPED"; 3762306a36Sopenharmony_ci default: 3862306a36Sopenharmony_ci return "TEST_???"; 3962306a36Sopenharmony_ci } 4062306a36Sopenharmony_ci} 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistatic int setup_filemap(char *filemap, size_t len, size_t wr_chunk_size) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci char iter = 0; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci for (size_t offset = 0; offset < len; 4762306a36Sopenharmony_ci offset += wr_chunk_size) { 4862306a36Sopenharmony_ci iter++; 4962306a36Sopenharmony_ci memset(filemap + offset, iter, wr_chunk_size); 5062306a36Sopenharmony_ci } 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci return 0; 5362306a36Sopenharmony_ci} 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic bool verify_chunk(char *buf, size_t len, char val) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci size_t i; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci for (i = 0; i < len; ++i) { 6062306a36Sopenharmony_ci if (buf[i] != val) { 6162306a36Sopenharmony_ci printf(PREFIX ERROR_PREFIX "check fail: buf[%lu] = %u != %u\n", 6262306a36Sopenharmony_ci i, buf[i], val); 6362306a36Sopenharmony_ci return false; 6462306a36Sopenharmony_ci } 6562306a36Sopenharmony_ci } 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci return true; 6862306a36Sopenharmony_ci} 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic bool seek_read_hugepage_filemap(int fd, size_t len, size_t wr_chunk_size, 7162306a36Sopenharmony_ci off_t offset, size_t expected) 7262306a36Sopenharmony_ci{ 7362306a36Sopenharmony_ci char buf[MAX_WRITE_READ_CHUNK_SIZE]; 7462306a36Sopenharmony_ci ssize_t ret_count = 0; 7562306a36Sopenharmony_ci ssize_t total_ret_count = 0; 7662306a36Sopenharmony_ci char val = offset / wr_chunk_size + offset % wr_chunk_size; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci printf(PREFIX PREFIX "init val=%u with offset=0x%lx\n", val, offset); 7962306a36Sopenharmony_ci printf(PREFIX PREFIX "expect to read 0x%lx bytes of data in total\n", 8062306a36Sopenharmony_ci expected); 8162306a36Sopenharmony_ci if (lseek(fd, offset, SEEK_SET) < 0) { 8262306a36Sopenharmony_ci perror(PREFIX ERROR_PREFIX "seek failed"); 8362306a36Sopenharmony_ci return false; 8462306a36Sopenharmony_ci } 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci while (offset + total_ret_count < len) { 8762306a36Sopenharmony_ci ret_count = read(fd, buf, wr_chunk_size); 8862306a36Sopenharmony_ci if (ret_count == 0) { 8962306a36Sopenharmony_ci printf(PREFIX PREFIX "read reach end of the file\n"); 9062306a36Sopenharmony_ci break; 9162306a36Sopenharmony_ci } else if (ret_count < 0) { 9262306a36Sopenharmony_ci perror(PREFIX ERROR_PREFIX "read failed"); 9362306a36Sopenharmony_ci break; 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci ++val; 9662306a36Sopenharmony_ci if (!verify_chunk(buf, ret_count, val)) 9762306a36Sopenharmony_ci return false; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci total_ret_count += ret_count; 10062306a36Sopenharmony_ci } 10162306a36Sopenharmony_ci printf(PREFIX PREFIX "actually read 0x%lx bytes of data in total\n", 10262306a36Sopenharmony_ci total_ret_count); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci return total_ret_count == expected; 10562306a36Sopenharmony_ci} 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_cistatic bool read_hugepage_filemap(int fd, size_t len, 10862306a36Sopenharmony_ci size_t wr_chunk_size, size_t expected) 10962306a36Sopenharmony_ci{ 11062306a36Sopenharmony_ci char buf[MAX_WRITE_READ_CHUNK_SIZE]; 11162306a36Sopenharmony_ci ssize_t ret_count = 0; 11262306a36Sopenharmony_ci ssize_t total_ret_count = 0; 11362306a36Sopenharmony_ci char val = 0; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci printf(PREFIX PREFIX "expect to read 0x%lx bytes of data in total\n", 11662306a36Sopenharmony_ci expected); 11762306a36Sopenharmony_ci while (total_ret_count < len) { 11862306a36Sopenharmony_ci ret_count = read(fd, buf, wr_chunk_size); 11962306a36Sopenharmony_ci if (ret_count == 0) { 12062306a36Sopenharmony_ci printf(PREFIX PREFIX "read reach end of the file\n"); 12162306a36Sopenharmony_ci break; 12262306a36Sopenharmony_ci } else if (ret_count < 0) { 12362306a36Sopenharmony_ci perror(PREFIX ERROR_PREFIX "read failed"); 12462306a36Sopenharmony_ci break; 12562306a36Sopenharmony_ci } 12662306a36Sopenharmony_ci ++val; 12762306a36Sopenharmony_ci if (!verify_chunk(buf, ret_count, val)) 12862306a36Sopenharmony_ci return false; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci total_ret_count += ret_count; 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci printf(PREFIX PREFIX "actually read 0x%lx bytes of data in total\n", 13362306a36Sopenharmony_ci total_ret_count); 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci return total_ret_count == expected; 13662306a36Sopenharmony_ci} 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cistatic enum test_status 13962306a36Sopenharmony_citest_hugetlb_read(int fd, size_t len, size_t wr_chunk_size) 14062306a36Sopenharmony_ci{ 14162306a36Sopenharmony_ci enum test_status status = TEST_SKIPPED; 14262306a36Sopenharmony_ci char *filemap = NULL; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci if (ftruncate(fd, len) < 0) { 14562306a36Sopenharmony_ci perror(PREFIX ERROR_PREFIX "ftruncate failed"); 14662306a36Sopenharmony_ci return status; 14762306a36Sopenharmony_ci } 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci filemap = mmap(NULL, len, PROT_READ | PROT_WRITE, 15062306a36Sopenharmony_ci MAP_SHARED | MAP_POPULATE, fd, 0); 15162306a36Sopenharmony_ci if (filemap == MAP_FAILED) { 15262306a36Sopenharmony_ci perror(PREFIX ERROR_PREFIX "mmap for primary mapping failed"); 15362306a36Sopenharmony_ci goto done; 15462306a36Sopenharmony_ci } 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci setup_filemap(filemap, len, wr_chunk_size); 15762306a36Sopenharmony_ci status = TEST_FAILED; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci if (read_hugepage_filemap(fd, len, wr_chunk_size, len)) 16062306a36Sopenharmony_ci status = TEST_PASSED; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci munmap(filemap, len); 16362306a36Sopenharmony_cidone: 16462306a36Sopenharmony_ci if (ftruncate(fd, 0) < 0) { 16562306a36Sopenharmony_ci perror(PREFIX ERROR_PREFIX "ftruncate back to 0 failed"); 16662306a36Sopenharmony_ci status = TEST_FAILED; 16762306a36Sopenharmony_ci } 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci return status; 17062306a36Sopenharmony_ci} 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_cistatic enum test_status 17362306a36Sopenharmony_citest_hugetlb_read_hwpoison(int fd, size_t len, size_t wr_chunk_size, 17462306a36Sopenharmony_ci bool skip_hwpoison_page) 17562306a36Sopenharmony_ci{ 17662306a36Sopenharmony_ci enum test_status status = TEST_SKIPPED; 17762306a36Sopenharmony_ci char *filemap = NULL; 17862306a36Sopenharmony_ci char *hwp_addr = NULL; 17962306a36Sopenharmony_ci const unsigned long pagesize = getpagesize(); 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci if (ftruncate(fd, len) < 0) { 18262306a36Sopenharmony_ci perror(PREFIX ERROR_PREFIX "ftruncate failed"); 18362306a36Sopenharmony_ci return status; 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci filemap = mmap(NULL, len, PROT_READ | PROT_WRITE, 18762306a36Sopenharmony_ci MAP_SHARED | MAP_POPULATE, fd, 0); 18862306a36Sopenharmony_ci if (filemap == MAP_FAILED) { 18962306a36Sopenharmony_ci perror(PREFIX ERROR_PREFIX "mmap for primary mapping failed"); 19062306a36Sopenharmony_ci goto done; 19162306a36Sopenharmony_ci } 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci setup_filemap(filemap, len, wr_chunk_size); 19462306a36Sopenharmony_ci status = TEST_FAILED; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci /* 19762306a36Sopenharmony_ci * Poisoned hugetlb page layout (assume hugepagesize=2MB): 19862306a36Sopenharmony_ci * |<---------------------- 1MB ---------------------->| 19962306a36Sopenharmony_ci * |<---- healthy page ---->|<---- HWPOISON page ----->| 20062306a36Sopenharmony_ci * |<------------------- (1MB - 8KB) ----------------->| 20162306a36Sopenharmony_ci */ 20262306a36Sopenharmony_ci hwp_addr = filemap + len / 2 + pagesize; 20362306a36Sopenharmony_ci if (madvise(hwp_addr, pagesize, MADV_HWPOISON) < 0) { 20462306a36Sopenharmony_ci perror(PREFIX ERROR_PREFIX "MADV_HWPOISON failed"); 20562306a36Sopenharmony_ci goto unmap; 20662306a36Sopenharmony_ci } 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci if (!skip_hwpoison_page) { 20962306a36Sopenharmony_ci /* 21062306a36Sopenharmony_ci * Userspace should be able to read (1MB + 1 page) from 21162306a36Sopenharmony_ci * the beginning of the HWPOISONed hugepage. 21262306a36Sopenharmony_ci */ 21362306a36Sopenharmony_ci if (read_hugepage_filemap(fd, len, wr_chunk_size, 21462306a36Sopenharmony_ci len / 2 + pagesize)) 21562306a36Sopenharmony_ci status = TEST_PASSED; 21662306a36Sopenharmony_ci } else { 21762306a36Sopenharmony_ci /* 21862306a36Sopenharmony_ci * Userspace should be able to read (1MB - 2 pages) from 21962306a36Sopenharmony_ci * HWPOISONed hugepage. 22062306a36Sopenharmony_ci */ 22162306a36Sopenharmony_ci if (seek_read_hugepage_filemap(fd, len, wr_chunk_size, 22262306a36Sopenharmony_ci len / 2 + MAX(2 * pagesize, wr_chunk_size), 22362306a36Sopenharmony_ci len / 2 - MAX(2 * pagesize, wr_chunk_size))) 22462306a36Sopenharmony_ci status = TEST_PASSED; 22562306a36Sopenharmony_ci } 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ciunmap: 22862306a36Sopenharmony_ci munmap(filemap, len); 22962306a36Sopenharmony_cidone: 23062306a36Sopenharmony_ci if (ftruncate(fd, 0) < 0) { 23162306a36Sopenharmony_ci perror(PREFIX ERROR_PREFIX "ftruncate back to 0 failed"); 23262306a36Sopenharmony_ci status = TEST_FAILED; 23362306a36Sopenharmony_ci } 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci return status; 23662306a36Sopenharmony_ci} 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_cistatic int create_hugetlbfs_file(struct statfs *file_stat) 23962306a36Sopenharmony_ci{ 24062306a36Sopenharmony_ci int fd; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci fd = memfd_create("hugetlb_tmp", MFD_HUGETLB); 24362306a36Sopenharmony_ci if (fd < 0) { 24462306a36Sopenharmony_ci perror(PREFIX ERROR_PREFIX "could not open hugetlbfs file"); 24562306a36Sopenharmony_ci return -1; 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci memset(file_stat, 0, sizeof(*file_stat)); 24962306a36Sopenharmony_ci if (fstatfs(fd, file_stat)) { 25062306a36Sopenharmony_ci perror(PREFIX ERROR_PREFIX "fstatfs failed"); 25162306a36Sopenharmony_ci goto close; 25262306a36Sopenharmony_ci } 25362306a36Sopenharmony_ci if (file_stat->f_type != HUGETLBFS_MAGIC) { 25462306a36Sopenharmony_ci printf(PREFIX ERROR_PREFIX "not hugetlbfs file\n"); 25562306a36Sopenharmony_ci goto close; 25662306a36Sopenharmony_ci } 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci return fd; 25962306a36Sopenharmony_ciclose: 26062306a36Sopenharmony_ci close(fd); 26162306a36Sopenharmony_ci return -1; 26262306a36Sopenharmony_ci} 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ciint main(void) 26562306a36Sopenharmony_ci{ 26662306a36Sopenharmony_ci int fd; 26762306a36Sopenharmony_ci struct statfs file_stat; 26862306a36Sopenharmony_ci enum test_status status; 26962306a36Sopenharmony_ci /* Test read() in different granularity. */ 27062306a36Sopenharmony_ci size_t wr_chunk_sizes[] = { 27162306a36Sopenharmony_ci getpagesize() / 2, getpagesize(), 27262306a36Sopenharmony_ci getpagesize() * 2, getpagesize() * 4 27362306a36Sopenharmony_ci }; 27462306a36Sopenharmony_ci size_t i; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(wr_chunk_sizes); ++i) { 27762306a36Sopenharmony_ci printf("Write/read chunk size=0x%lx\n", 27862306a36Sopenharmony_ci wr_chunk_sizes[i]); 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci fd = create_hugetlbfs_file(&file_stat); 28162306a36Sopenharmony_ci if (fd < 0) 28262306a36Sopenharmony_ci goto create_failure; 28362306a36Sopenharmony_ci printf(PREFIX "HugeTLB read regression test...\n"); 28462306a36Sopenharmony_ci status = test_hugetlb_read(fd, file_stat.f_bsize, 28562306a36Sopenharmony_ci wr_chunk_sizes[i]); 28662306a36Sopenharmony_ci printf(PREFIX "HugeTLB read regression test...%s\n", 28762306a36Sopenharmony_ci status_to_str(status)); 28862306a36Sopenharmony_ci close(fd); 28962306a36Sopenharmony_ci if (status == TEST_FAILED) 29062306a36Sopenharmony_ci return -1; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci fd = create_hugetlbfs_file(&file_stat); 29362306a36Sopenharmony_ci if (fd < 0) 29462306a36Sopenharmony_ci goto create_failure; 29562306a36Sopenharmony_ci printf(PREFIX "HugeTLB read HWPOISON test...\n"); 29662306a36Sopenharmony_ci status = test_hugetlb_read_hwpoison(fd, file_stat.f_bsize, 29762306a36Sopenharmony_ci wr_chunk_sizes[i], false); 29862306a36Sopenharmony_ci printf(PREFIX "HugeTLB read HWPOISON test...%s\n", 29962306a36Sopenharmony_ci status_to_str(status)); 30062306a36Sopenharmony_ci close(fd); 30162306a36Sopenharmony_ci if (status == TEST_FAILED) 30262306a36Sopenharmony_ci return -1; 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci fd = create_hugetlbfs_file(&file_stat); 30562306a36Sopenharmony_ci if (fd < 0) 30662306a36Sopenharmony_ci goto create_failure; 30762306a36Sopenharmony_ci printf(PREFIX "HugeTLB seek then read HWPOISON test...\n"); 30862306a36Sopenharmony_ci status = test_hugetlb_read_hwpoison(fd, file_stat.f_bsize, 30962306a36Sopenharmony_ci wr_chunk_sizes[i], true); 31062306a36Sopenharmony_ci printf(PREFIX "HugeTLB seek then read HWPOISON test...%s\n", 31162306a36Sopenharmony_ci status_to_str(status)); 31262306a36Sopenharmony_ci close(fd); 31362306a36Sopenharmony_ci if (status == TEST_FAILED) 31462306a36Sopenharmony_ci return -1; 31562306a36Sopenharmony_ci } 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci return 0; 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_cicreate_failure: 32062306a36Sopenharmony_ci printf(ERROR_PREFIX "Abort test: failed to create hugetlbfs file\n"); 32162306a36Sopenharmony_ci return -1; 32262306a36Sopenharmony_ci} 323