mm: teach pfn_to_online_page() about ZONE_DEVICE section collisions
[linux-2.6-microblaze.git] / mm / memory_hotplug.c
index f9d57b9..3af4d38 100644 (file)
@@ -300,6 +300,54 @@ static int check_hotplug_memory_addressable(unsigned long pfn,
        return 0;
 }
 
+/*
+ * Return page for the valid pfn only if the page is online. All pfn
+ * walkers which rely on the fully initialized page->flags and others
+ * should use this rather than pfn_valid && pfn_to_page
+ */
+struct page *pfn_to_online_page(unsigned long pfn)
+{
+       unsigned long nr = pfn_to_section_nr(pfn);
+       struct dev_pagemap *pgmap;
+       struct mem_section *ms;
+
+       if (nr >= NR_MEM_SECTIONS)
+               return NULL;
+
+       ms = __nr_to_section(nr);
+       if (!online_section(ms))
+               return NULL;
+
+       /*
+        * Save some code text when online_section() +
+        * pfn_section_valid() are sufficient.
+        */
+       if (IS_ENABLED(CONFIG_HAVE_ARCH_PFN_VALID) && !pfn_valid(pfn))
+               return NULL;
+
+       if (!pfn_section_valid(ms, pfn))
+               return NULL;
+
+       if (!online_device_section(ms))
+               return pfn_to_page(pfn);
+
+       /*
+        * Slowpath: when ZONE_DEVICE collides with
+        * ZONE_{NORMAL,MOVABLE} within the same section some pfns in
+        * the section may be 'offline' but 'valid'. Only
+        * get_dev_pagemap() can determine sub-section online status.
+        */
+       pgmap = get_dev_pagemap(pfn, NULL);
+       put_dev_pagemap(pgmap);
+
+       /* The presence of a pgmap indicates ZONE_DEVICE offline pfn */
+       if (pgmap)
+               return NULL;
+
+       return pfn_to_page(pfn);
+}
+EXPORT_SYMBOL_GPL(pfn_to_online_page);
+
 /*
  * Reasonably generic function for adding memory.  It is
  * expected that archs that support memory hotplug will
@@ -678,6 +726,14 @@ static void __meminit resize_pgdat_range(struct pglist_data *pgdat, unsigned lon
        pgdat->node_spanned_pages = max(start_pfn + nr_pages, old_end_pfn) - pgdat->node_start_pfn;
 
 }
+
+static void section_taint_zone_device(unsigned long pfn)
+{
+       struct mem_section *ms = __pfn_to_section(pfn);
+
+       ms->section_mem_map |= SECTION_TAINT_ZONE_DEVICE;
+}
+
 /*
  * Associate the pfn range with the given zone, initializing the memmaps
  * and resizing the pgdat/zone data to span the added pages. After this
@@ -707,13 +763,26 @@ void __ref move_pfn_range_to_zone(struct zone *zone, unsigned long start_pfn,
        resize_pgdat_range(pgdat, start_pfn, nr_pages);
        pgdat_resize_unlock(pgdat, &flags);
 
+       /*
+        * Subsection population requires care in pfn_to_online_page().
+        * Set the taint to enable the slow path detection of
+        * ZONE_DEVICE pages in an otherwise  ZONE_{NORMAL,MOVABLE}
+        * section.
+        */
+       if (zone_is_zone_device(zone)) {
+               if (!IS_ALIGNED(start_pfn, PAGES_PER_SECTION))
+                       section_taint_zone_device(start_pfn);
+               if (!IS_ALIGNED(start_pfn + nr_pages, PAGES_PER_SECTION))
+                       section_taint_zone_device(start_pfn + nr_pages);
+       }
+
        /*
         * TODO now we have a visible range of pages which are not associated
         * with their zone properly. Not nice but set_pfnblock_flags_mask
         * expects the zone spans the pfn range. All the pages in the range
         * are reserved so nobody should be touching them so we should be safe
         */
-       memmap_init_zone(nr_pages, nid, zone_idx(zone), start_pfn, 0,
+       memmap_init_range(nr_pages, nid, zone_idx(zone), start_pfn, 0,
                         MEMINIT_HOTPLUG, altmap, migratetype);
 
        set_zone_contiguous(zone);
@@ -1260,7 +1329,14 @@ static int scan_movable_pages(unsigned long start, unsigned long end,
                if (!PageHuge(page))
                        continue;
                head = compound_head(page);
-               if (page_huge_active(head))
+               /*
+                * This test is racy as we hold no reference or lock.  The
+                * hugetlb page could have been free'ed and head is no longer
+                * a hugetlb page before the following check.  In such unlikely
+                * cases false positives and negatives are possible.  Calling
+                * code must deal with these scenarios.
+                */
+               if (HPageMigratable(head))
                        goto found;
                skip = compound_nr(head) - (page - head);
                pfn += skip - 1;