@@ -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);