]> git.ipfire.org Git - thirdparty/kernel/linux.git/blobdiff - mm/memory-failure.c
Merge tag 'libnvdimm-for-4.19_dax-memory-failure' of gitolite.kernel.org:pub/scm...
[thirdparty/kernel/linux.git] / mm / memory-failure.c
index 32a644d9c2eeb8a179f6cfe72021a7e6c21fdaf8..0cd3de3550f0830f507d286b0499789d7961171e 100644 (file)
@@ -58,6 +58,7 @@
 #include <linux/memremap.h>
 #include <linux/kfifo.h>
 #include <linux/ratelimit.h>
+#include <linux/page-isolation.h>
 #include "internal.h"
 #include "ras/ras_event.h"
 
@@ -1287,7 +1288,7 @@ int memory_failure(unsigned long pfn, int flags)
         *    R/W the page; let's pray that the page has been
         *    used and will be freed some time later.
         * In fact it's dangerous to directly bump up page count from 0,
-        * that may make page_freeze_refs()/page_unfreeze_refs() mismatch.
+        * that may make page_ref_freeze()/page_ref_unfreeze() mismatch.
         */
        if (!(flags & MF_COUNT_INCREASED) && !get_hwpoison_page(p)) {
                if (is_free_buddy_page(p)) {
@@ -1718,8 +1719,18 @@ static int soft_offline_huge_page(struct page *page, int flags)
                if (ret > 0)
                        ret = -EIO;
        } else {
-               if (PageHuge(page))
-                       dissolve_free_huge_page(page);
+               /*
+                * We set PG_hwpoison only when the migration source hugepage
+                * was successfully dissolved, because otherwise hwpoisoned
+                * hugepage remains on free hugepage list, then userspace will
+                * find it as SIGBUS by allocation failure. That's not expected
+                * in soft-offlining.
+                */
+               ret = dissolve_free_huge_page(page);
+               if (!ret) {
+                       if (set_hwpoison_free_buddy_page(page))
+                               num_poisoned_pages_inc();
+               }
        }
        return ret;
 }
@@ -1807,6 +1818,7 @@ static int __soft_offline_page(struct page *page, int flags)
 static int soft_offline_in_use_page(struct page *page, int flags)
 {
        int ret;
+       int mt;
        struct page *hpage = compound_head(page);
 
        if (!PageHuge(page) && PageTransHuge(hpage)) {
@@ -1825,23 +1837,37 @@ static int soft_offline_in_use_page(struct page *page, int flags)
                put_hwpoison_page(hpage);
        }
 
+       /*
+        * Setting MIGRATE_ISOLATE here ensures that the page will be linked
+        * to free list immediately (not via pcplist) when released after
+        * successful page migration. Otherwise we can't guarantee that the
+        * page is really free after put_page() returns, so
+        * set_hwpoison_free_buddy_page() highly likely fails.
+        */
+       mt = get_pageblock_migratetype(page);
+       set_pageblock_migratetype(page, MIGRATE_ISOLATE);
        if (PageHuge(page))
                ret = soft_offline_huge_page(page, flags);
        else
                ret = __soft_offline_page(page, flags);
-
+       set_pageblock_migratetype(page, mt);
        return ret;
 }
 
-static void soft_offline_free_page(struct page *page)
+static int soft_offline_free_page(struct page *page)
 {
+       int rc = 0;
        struct page *head = compound_head(page);
 
-       if (!TestSetPageHWPoison(head)) {
-               num_poisoned_pages_inc();
-               if (PageHuge(head))
-                       dissolve_free_huge_page(page);
+       if (PageHuge(head))
+               rc = dissolve_free_huge_page(page);
+       if (!rc) {
+               if (set_hwpoison_free_buddy_page(page))
+                       num_poisoned_pages_inc();
+               else
+                       rc = -EBUSY;
        }
+       return rc;
 }
 
 /**
@@ -1893,7 +1919,7 @@ int soft_offline_page(struct page *page, int flags)
        if (ret > 0)
                ret = soft_offline_in_use_page(page, flags);
        else if (ret == 0)
-               soft_offline_free_page(page);
+               ret = soft_offline_free_page(page);
 
        return ret;
 }