For not-yet-faulted hugepage containing HWPOISON raw page, test 1. only HWPOISON raw page will not be faulted, and a BUS_MCEERR_AR SIGBUS will be sent to userspace. 2. healthy raw pages are faulted in as normal. Since the hugepage has been writeprotect by UFFD, non BUS_MCEERR_AR SIGBUS will be sent to userspace. Signed-off-by: Jiaqi Yan <jiaqiyan@xxxxxxxxxx> --- tools/testing/selftests/mm/hugetlb-hgm.c | 170 +++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/tools/testing/selftests/mm/hugetlb-hgm.c b/tools/testing/selftests/mm/hugetlb-hgm.c index bc9529986b66..81ee2d99fea8 100644 --- a/tools/testing/selftests/mm/hugetlb-hgm.c +++ b/tools/testing/selftests/mm/hugetlb-hgm.c @@ -515,6 +515,169 @@ static int uffd_register(int uffd, char *primary_map, unsigned long len, return ioctl(uffd, UFFDIO_REGISTER, ®); } +static int setup_present_map(char *present_map, size_t len) +{ + size_t offset = 0; + unsigned char iter = 0; + unsigned long pagesize = getpagesize(); + uint64_t size; + + for (size = len/2; size >= pagesize; + offset += size, size /= 2) { + iter++; + memset(present_map + offset, iter, size); + } + return 0; +} + +static enum test_status test_hwpoison_absent_uffd_wp(int fd, size_t hugepagesize, size_t len) +{ + int uffd; + char *absent_map, *present_map; + struct uffdio_api api; + int register_args; + struct sigaction new, old; + enum test_status status = TEST_SKIPPED; + const unsigned long pagesize = getpagesize(); + const unsigned long hwpoison_index = 128; + char *hwpoison_addr; + + if (hwpoison_index >= (len / pagesize)) { + printf(ERROR_PREFIX "hwpoison_index out of range"); + return TEST_FAILED; + } + + if (ftruncate(fd, len) < 0) { + perror(ERROR_PREFIX "ftruncate failed"); + return TEST_FAILED; + } + + uffd = userfaultfd(O_CLOEXEC); + if (uffd < 0) { + perror(ERROR_PREFIX "uffd not created"); + return TEST_FAILED; + } + + absent_map = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (absent_map == MAP_FAILED) { + perror(ERROR_PREFIX "mmap for ABSENT mapping failed"); + goto close_uffd; + } + printf(PREFIX "ABSENT mapping: %p\n", absent_map); + + api.api = UFFD_API; + api.features = UFFD_FEATURE_SIGBUS | UFFD_FEATURE_EXACT_ADDRESS | + UFFD_FEATURE_EVENT_FORK; + if (ioctl(uffd, UFFDIO_API, &api) == -1) { + perror(ERROR_PREFIX "UFFDIO_API failed"); + goto unmap_absent; + } + + /* + * Register with UFFDIO_REGISTER_MODE_WP to have UFFD WP bit on + * the HugeTLB page table entry. + */ + register_args = UFFDIO_REGISTER_MODE_MISSING | UFFDIO_REGISTER_MODE_WP; + if (uffd_register(uffd, absent_map, len, register_args)) { + perror(ERROR_PREFIX "UFFDIO_REGISTER failed"); + goto unmap_absent; + } + + new.sa_sigaction = &sigbus_handler; + new.sa_flags = SA_SIGINFO; + if (sigaction(SIGBUS, &new, &old) < 0) { + perror(ERROR_PREFIX "could not setup SIGBUS handler"); + goto unmap_absent; + } + + /* + * Set WP markers to the absent huge mapping. With HGM enabled in + * kernel CONFIG, memory_failure will enabled HGM in kernel, + * so no need to enable HGM from userspace. + */ + if (userfaultfd_writeprotect(uffd, absent_map, len, true) < 0) { + status = TEST_FAILED; + goto unmap_absent; + } + + status = TEST_PASSED; + + /* + * With MAP_SHARED hugetlb memory, we cna inject memory error to + * not-yet-faulted mapping (absent_map) by injecting memory error + * to a already faulted mapping (present_map). + */ + present_map = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (present_map == MAP_FAILED) { + perror(ERROR_PREFIX "mmap for non present mapping failed"); + goto close_uffd; + } + printf(PREFIX "PRESENT mapping: %p\n", present_map); + setup_present_map(present_map, len); + + hwpoison_addr = present_map + hwpoison_index * pagesize; + if (madvise(hwpoison_addr, pagesize, MADV_HWPOISON)) { + perror(PREFIX "MADV_HWPOISON a page in PRESENT mapping failed"); + status = TEST_FAILED; + goto unmap_present; + } + + printf(PREFIX "checking poisoned range [%p, %p) (len=%#lx) in PRESENT mapping\n", + hwpoison_addr, hwpoison_addr + pagesize, pagesize); + if (test_sigbus(hwpoison_addr, true) < 0) { + status = TEST_FAILED; + goto done; + } + printf(PREFIX "checking healthy pages in PRESENT mapping\n"); + unsigned long hwpoison_addrs[] = { + (unsigned long)hwpoison_addr, + (unsigned long)hwpoison_addr, + (unsigned long)hwpoison_addr + }; + status = verify_raw_pages(present_map, len, hwpoison_addrs); + if (status != TEST_PASSED) { + printf(ERROR_PREFIX "checking healthy pages failed\n"); + goto done; + } + + for (int i = 0; i < len; i += pagesize) { + if (i == hwpoison_index * pagesize) { + printf(PREFIX "checking poisoned range [%p, %p) (len=%#lx) in ABSENT mapping\n", + absent_map + i, absent_map + i + pagesize, pagesize); + if (test_sigbus(absent_map + i, true) < 0) { + status = TEST_FAILED; + break; + } + } else { + /* + * With UFFD_FEATURE_SIGBUS, we should get a SIGBUS for + * every not faulted (non present) page/byte. + */ + if (test_sigbus(absent_map + i, false) < 0) { + printf(PREFIX "checking healthy range [%p, %p) (len=%#lx) in ABSENT mapping failed\n", + absent_map + i, absent_map + i + pagesize, pagesize); + status = TEST_FAILED; + break; + } + } + } +done: + if (ftruncate(fd, 0) < 0) { + perror(ERROR_PREFIX "ftruncate back to 0 failed"); + status = TEST_FAILED; + } +unmap_present: + printf(PREFIX "Unmap PRESENT mapping=%p\n", absent_map); + munmap(present_map, len); +unmap_absent: + printf(PREFIX "Unmap ABSENT mapping=%p\n", absent_map); + munmap(absent_map, len); +close_uffd: + printf(PREFIX "Close UFFD\n"); + close(uffd); + return status; +} + enum test_type { TEST_DEFAULT, TEST_UFFDWP, @@ -744,6 +907,13 @@ int main(void) printf("HGM hwpoison test: %s\n", status_to_str(status)); if (status == TEST_FAILED) ret = -1; + + printf("HGM hwpoison UFFD-WP marker test...\n"); + status = test_hwpoison_absent_uffd_wp(fd, hugepagesize, len); + printf("HGM hwpoison UFFD-WP marker test: %s\n", + status_to_str(status)); + if (status == TEST_FAILED) + ret = -1; close: close(fd); -- 2.40.1.495.gc816e09b53d-goog