]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - mm/memory_hotplug.c
NFS: Switch readdir to using iterate_shared()
[linux.git] / mm / memory_hotplug.c
index 3b62a9ff8ea07394682ae29b1f13bd503f1f25a4..a91a072f2b2ce6baf432f09c3cfe74dbaec0033b 100644 (file)
@@ -49,8 +49,6 @@
  * and restore_online_page_callback() for generic callback restore.
  */
 
-static void generic_online_page(struct page *page, unsigned int order);
-
 static online_page_callback_t online_page_callback = generic_online_page;
 static DEFINE_MUTEX(online_page_callback_lock);
 
@@ -278,6 +276,22 @@ static int check_pfn_span(unsigned long pfn, unsigned long nr_pages,
        return 0;
 }
 
+static int check_hotplug_memory_addressable(unsigned long pfn,
+                                           unsigned long nr_pages)
+{
+       const u64 max_addr = PFN_PHYS(pfn + nr_pages) - 1;
+
+       if (max_addr >> MAX_PHYSMEM_BITS) {
+               const u64 max_allowed = (1ull << (MAX_PHYSMEM_BITS + 1)) - 1;
+               WARN(1,
+                    "Hotplugged memory exceeds maximum addressable address, range=%#llx-%#llx, maximum=%#llx\n",
+                    (u64)PFN_PHYS(pfn), max_addr, max_allowed);
+               return -E2BIG;
+       }
+
+       return 0;
+}
+
 /*
  * Reasonably generic function for adding memory.  It is
  * expected that archs that support memory hotplug will
@@ -291,6 +305,10 @@ int __ref __add_pages(int nid, unsigned long pfn, unsigned long nr_pages,
        unsigned long nr, start_sec, end_sec;
        struct vmem_altmap *altmap = restrictions->altmap;
 
+       err = check_hotplug_memory_addressable(pfn, nr_pages);
+       if (err)
+               return err;
+
        if (altmap) {
                /*
                 * Validate altmap is within bounds of the total request
@@ -331,7 +349,7 @@ static unsigned long find_smallest_section_pfn(int nid, struct zone *zone,
                                     unsigned long end_pfn)
 {
        for (; start_pfn < end_pfn; start_pfn += PAGES_PER_SUBSECTION) {
-               if (unlikely(!pfn_valid(start_pfn)))
+               if (unlikely(!pfn_to_online_page(start_pfn)))
                        continue;
 
                if (unlikely(pfn_to_nid(start_pfn) != nid))
@@ -356,7 +374,7 @@ static unsigned long find_biggest_section_pfn(int nid, struct zone *zone,
        /* pfn is the end pfn of a memory section. */
        pfn = end_pfn - 1;
        for (; pfn >= start_pfn; pfn -= PAGES_PER_SUBSECTION) {
-               if (unlikely(!pfn_valid(pfn)))
+               if (unlikely(!pfn_to_online_page(pfn)))
                        continue;
 
                if (unlikely(pfn_to_nid(pfn) != nid))
@@ -415,7 +433,7 @@ static void shrink_zone_span(struct zone *zone, unsigned long start_pfn,
         */
        pfn = zone_start_pfn;
        for (; pfn < zone_end_pfn; pfn += PAGES_PER_SUBSECTION) {
-               if (unlikely(!pfn_valid(pfn)))
+               if (unlikely(!pfn_to_online_page(pfn)))
                        continue;
 
                if (page_zone(pfn_to_page(pfn)) != zone)
@@ -465,34 +483,47 @@ static void update_pgdat_span(struct pglist_data *pgdat)
        pgdat->node_spanned_pages = node_end_pfn - node_start_pfn;
 }
 
-static void __remove_zone(struct zone *zone, unsigned long start_pfn,
-               unsigned long nr_pages)
+void __ref remove_pfn_range_from_zone(struct zone *zone,
+                                     unsigned long start_pfn,
+                                     unsigned long nr_pages)
 {
        struct pglist_data *pgdat = zone->zone_pgdat;
        unsigned long flags;
 
+#ifdef CONFIG_ZONE_DEVICE
+       /*
+        * Zone shrinking code cannot properly deal with ZONE_DEVICE. So
+        * we will not try to shrink the zones - which is okay as
+        * set_zone_contiguous() cannot deal with ZONE_DEVICE either way.
+        */
+       if (zone_idx(zone) == ZONE_DEVICE)
+               return;
+#endif
+
+       clear_zone_contiguous(zone);
+
        pgdat_resize_lock(zone->zone_pgdat, &flags);
        shrink_zone_span(zone, start_pfn, start_pfn + nr_pages);
        update_pgdat_span(pgdat);
        pgdat_resize_unlock(zone->zone_pgdat, &flags);
+
+       set_zone_contiguous(zone);
 }
 
-static void __remove_section(struct zone *zone, unsigned long pfn,
-               unsigned long nr_pages, unsigned long map_offset,
-               struct vmem_altmap *altmap)
+static void __remove_section(unsigned long pfn, unsigned long nr_pages,
+                            unsigned long map_offset,
+                            struct vmem_altmap *altmap)
 {
        struct mem_section *ms = __nr_to_section(pfn_to_section_nr(pfn));
 
        if (WARN_ON_ONCE(!valid_section(ms)))
                return;
 
-       __remove_zone(zone, pfn, nr_pages);
        sparse_remove_section(ms, pfn, nr_pages, map_offset, altmap);
 }
 
 /**
- * __remove_pages() - remove sections of pages from a zone
- * @zone: zone from which pages need to be removed
+ * __remove_pages() - remove sections of pages
  * @pfn: starting pageframe (must be aligned to start of a section)
  * @nr_pages: number of pages to remove (must be multiple of section size)
  * @altmap: alternative device page map or %NULL if default memmap is used
@@ -502,16 +533,14 @@ static void __remove_section(struct zone *zone, unsigned long pfn,
  * sure that pages are marked reserved and zones are adjust properly by
  * calling offline_pages().
  */
-void __remove_pages(struct zone *zone, unsigned long pfn,
-                   unsigned long nr_pages, struct vmem_altmap *altmap)
+void __remove_pages(unsigned long pfn, unsigned long nr_pages,
+                   struct vmem_altmap *altmap)
 {
        unsigned long map_offset = 0;
        unsigned long nr, start_sec, end_sec;
 
        map_offset = vmem_altmap_offset(altmap);
 
-       clear_zone_contiguous(zone);
-
        if (check_pfn_span(pfn, nr_pages, "remove"))
                return;
 
@@ -523,13 +552,11 @@ void __remove_pages(struct zone *zone, unsigned long pfn,
                cond_resched();
                pfns = min(nr_pages, PAGES_PER_SECTION
                                - (pfn & ~PAGE_SECTION_MASK));
-               __remove_section(zone, pfn, pfns, map_offset, altmap);
+               __remove_section(pfn, pfns, map_offset, altmap);
                pfn += pfns;
                nr_pages -= pfns;
                map_offset = 0;
        }
-
-       set_zone_contiguous(zone);
 }
 
 int set_online_page_callback(online_page_callback_t callback)
@@ -570,24 +597,7 @@ int restore_online_page_callback(online_page_callback_t callback)
 }
 EXPORT_SYMBOL_GPL(restore_online_page_callback);
 
-void __online_page_set_limits(struct page *page)
-{
-}
-EXPORT_SYMBOL_GPL(__online_page_set_limits);
-
-void __online_page_increment_counters(struct page *page)
-{
-       adjust_managed_page_count(page, 1);
-}
-EXPORT_SYMBOL_GPL(__online_page_increment_counters);
-
-void __online_page_free(struct page *page)
-{
-       __free_reserved_page(page);
-}
-EXPORT_SYMBOL_GPL(__online_page_free);
-
-static void generic_online_page(struct page *page, unsigned int order)
+void generic_online_page(struct page *page, unsigned int order)
 {
        kernel_map_pages(page, 1 << order, 1);
        __free_pages_core(page, order);
@@ -597,6 +607,7 @@ static void generic_online_page(struct page *page, unsigned int order)
                totalhigh_pages_add(1UL << order);
 #endif
 }
+EXPORT_SYMBOL_GPL(generic_online_page);
 
 static int online_pages_range(unsigned long start_pfn, unsigned long nr_pages,
                        void *arg)
@@ -857,6 +868,7 @@ int __ref online_pages(unsigned long pfn, unsigned long nr_pages, int online_typ
                 (unsigned long long) pfn << PAGE_SHIFT,
                 (((unsigned long long) pfn + nr_pages) << PAGE_SHIFT) - 1);
        memory_notify(MEM_CANCEL_ONLINE, &arg);
+       remove_pfn_range_from_zone(zone, pfn, nr_pages);
        mem_hotplug_done();
        return ret;
 }
@@ -1170,7 +1182,8 @@ static bool is_pageblock_removable_nolock(unsigned long pfn)
        if (!zone_spans_pfn(zone, pfn))
                return false;
 
-       return !has_unmovable_pages(zone, page, 0, MIGRATE_MOVABLE, SKIP_HWPOISON);
+       return !has_unmovable_pages(zone, page, 0, MIGRATE_MOVABLE,
+                                   MEMORY_OFFLINE);
 }
 
 /* Checks if this range of memory is likely to be hot-removable. */
@@ -1367,9 +1380,7 @@ do_migrate_range(unsigned long start_pfn, unsigned long end_pfn)
        return ret;
 }
 
-/*
- * remove from free_area[] and mark all as Reserved.
- */
+/* Mark all sections offline and remove all free pages from the buddy. */
 static int
 offline_isolated_pages_cb(unsigned long start, unsigned long nr_pages,
                        void *data)
@@ -1387,7 +1398,8 @@ static int
 check_pages_isolated_cb(unsigned long start_pfn, unsigned long nr_pages,
                        void *data)
 {
-       return test_pages_isolated(start_pfn, start_pfn + nr_pages, true);
+       return test_pages_isolated(start_pfn, start_pfn + nr_pages,
+                                  MEMORY_OFFLINE);
 }
 
 static int __init cmdline_parse_movable_node(char *p)
@@ -1468,10 +1480,19 @@ static void node_states_clear_node(int node, struct memory_notify *arg)
                node_clear_state(node, N_MEMORY);
 }
 
+static int count_system_ram_pages_cb(unsigned long start_pfn,
+                                    unsigned long nr_pages, void *data)
+{
+       unsigned long *nr_system_ram_pages = data;
+
+       *nr_system_ram_pages += nr_pages;
+       return 0;
+}
+
 static int __ref __offline_pages(unsigned long start_pfn,
                  unsigned long end_pfn)
 {
-       unsigned long pfn, nr_pages;
+       unsigned long pfn, nr_pages = 0;
        unsigned long offlined_pages = 0;
        int ret, node, nr_isolate_pageblock;
        unsigned long flags;
@@ -1482,6 +1503,22 @@ static int __ref __offline_pages(unsigned long start_pfn,
 
        mem_hotplug_begin();
 
+       /*
+        * Don't allow to offline memory blocks that contain holes.
+        * Consequently, memory blocks with holes can never get onlined
+        * via the hotplug path - online_pages() - as hotplugged memory has
+        * no holes. This way, we e.g., don't have to worry about marking
+        * memory holes PG_reserved, don't need pfn_valid() checks, and can
+        * avoid using walk_system_ram_range() later.
+        */
+       walk_system_ram_range(start_pfn, end_pfn - start_pfn, &nr_pages,
+                             count_system_ram_pages_cb);
+       if (nr_pages != end_pfn - start_pfn) {
+               ret = -EINVAL;
+               reason = "memory holes";
+               goto failed_removal;
+       }
+
        /* This makes hotplug much easier...and readable.
           we assume this for now. .*/
        if (!test_pages_in_a_zone(start_pfn, end_pfn, &valid_start,
@@ -1493,12 +1530,11 @@ static int __ref __offline_pages(unsigned long start_pfn,
 
        zone = page_zone(pfn_to_page(valid_start));
        node = zone_to_nid(zone);
-       nr_pages = end_pfn - start_pfn;
 
        /* set above range as isolated */
        ret = start_isolate_page_range(start_pfn, end_pfn,
                                       MIGRATE_MOVABLE,
-                                      SKIP_HWPOISON | REPORT_FAILURE);
+                                      MEMORY_OFFLINE | REPORT_FAILURE);
        if (ret < 0) {
                reason = "failure to isolate range";
                goto failed_removal;
@@ -1592,6 +1628,7 @@ static int __ref __offline_pages(unsigned long start_pfn,
        writeback_set_ratelimit();
 
        memory_notify(MEM_OFFLINE, &arg);
+       remove_pfn_range_from_zone(zone, start_pfn, nr_pages);
        mem_hotplug_done();
        return 0;
 
@@ -1740,13 +1777,13 @@ static int __ref try_remove_memory(int nid, u64 start, u64 size)
 
        /* remove memmap entry */
        firmware_map_remove(start, start + size, "System RAM");
-       memblock_free(start, size);
-       memblock_remove(start, size);
 
        /* remove memory block devices before removing memory */
        remove_memory_block_devices(start, size);
 
        arch_remove_memory(nid, start, size, NULL);
+       memblock_free(start, size);
+       memblock_remove(start, size);
        __release_memory_resource(start, size);
 
        try_offline_node(nid);