mm, memory_hotplug: add nid parameter to arch_remove_memory
[platform/kernel/linux-rpi.git] / mm / memory_hotplug.c
index af67355..a51e5ff 100644 (file)
@@ -255,7 +255,7 @@ static int __meminit __add_section(int nid, unsigned long phys_start_pfn,
        if (pfn_valid(phys_start_pfn))
                return -EEXIST;
 
-       ret = sparse_add_one_section(NODE_DATA(nid), phys_start_pfn, altmap);
+       ret = sparse_add_one_section(nid, phys_start_pfn, altmap);
        if (ret < 0)
                return ret;
 
@@ -321,12 +321,8 @@ static unsigned long find_smallest_section_pfn(int nid, struct zone *zone,
                                     unsigned long start_pfn,
                                     unsigned long end_pfn)
 {
-       struct mem_section *ms;
-
        for (; start_pfn < end_pfn; start_pfn += PAGES_PER_SECTION) {
-               ms = __pfn_to_section(start_pfn);
-
-               if (unlikely(!valid_section(ms)))
+               if (unlikely(!pfn_to_online_page(start_pfn)))
                        continue;
 
                if (unlikely(pfn_to_nid(start_pfn) != nid))
@@ -346,15 +342,12 @@ static unsigned long find_biggest_section_pfn(int nid, struct zone *zone,
                                    unsigned long start_pfn,
                                    unsigned long end_pfn)
 {
-       struct mem_section *ms;
        unsigned long pfn;
 
        /* pfn is the end pfn of a memory section. */
        pfn = end_pfn - 1;
        for (; pfn >= start_pfn; pfn -= PAGES_PER_SECTION) {
-               ms = __pfn_to_section(pfn);
-
-               if (unlikely(!valid_section(ms)))
+               if (unlikely(!pfn_to_online_page(pfn)))
                        continue;
 
                if (unlikely(pfn_to_nid(pfn) != nid))
@@ -376,7 +369,6 @@ static void shrink_zone_span(struct zone *zone, unsigned long start_pfn,
        unsigned long z = zone_end_pfn(zone); /* zone_end_pfn namespace clash */
        unsigned long zone_end_pfn = z;
        unsigned long pfn;
-       struct mem_section *ms;
        int nid = zone_to_nid(zone);
 
        zone_span_writelock(zone);
@@ -414,9 +406,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_SECTION) {
-               ms = __pfn_to_section(pfn);
-
-               if (unlikely(!valid_section(ms)))
+               if (unlikely(!pfn_to_online_page(pfn)))
                        continue;
 
                if (page_zone(pfn_to_page(pfn)) != zone)
@@ -437,70 +427,33 @@ static void shrink_zone_span(struct zone *zone, unsigned long start_pfn,
        zone_span_writeunlock(zone);
 }
 
-static void shrink_pgdat_span(struct pglist_data *pgdat,
-                             unsigned long start_pfn, unsigned long end_pfn)
+static void update_pgdat_span(struct pglist_data *pgdat)
 {
-       unsigned long pgdat_start_pfn = pgdat->node_start_pfn;
-       unsigned long p = pgdat_end_pfn(pgdat); /* pgdat_end_pfn namespace clash */
-       unsigned long pgdat_end_pfn = p;
-       unsigned long pfn;
-       struct mem_section *ms;
-       int nid = pgdat->node_id;
-
-       if (pgdat_start_pfn == start_pfn) {
-               /*
-                * If the section is smallest section in the pgdat, it need
-                * shrink pgdat->node_start_pfn and pgdat->node_spanned_pages.
-                * In this case, we find second smallest valid mem_section
-                * for shrinking zone.
-                */
-               pfn = find_smallest_section_pfn(nid, NULL, end_pfn,
-                                               pgdat_end_pfn);
-               if (pfn) {
-                       pgdat->node_start_pfn = pfn;
-                       pgdat->node_spanned_pages = pgdat_end_pfn - pfn;
-               }
-       } else if (pgdat_end_pfn == end_pfn) {
-               /*
-                * If the section is biggest section in the pgdat, it need
-                * shrink pgdat->node_spanned_pages.
-                * In this case, we find second biggest valid mem_section for
-                * shrinking zone.
-                */
-               pfn = find_biggest_section_pfn(nid, NULL, pgdat_start_pfn,
-                                              start_pfn);
-               if (pfn)
-                       pgdat->node_spanned_pages = pfn - pgdat_start_pfn + 1;
-       }
-
-       /*
-        * If the section is not biggest or smallest mem_section in the pgdat,
-        * it only creates a hole in the pgdat. So in this case, we need not
-        * change the pgdat.
-        * But perhaps, the pgdat has only hole data. Thus it check the pgdat
-        * has only hole or not.
-        */
-       pfn = pgdat_start_pfn;
-       for (; pfn < pgdat_end_pfn; pfn += PAGES_PER_SECTION) {
-               ms = __pfn_to_section(pfn);
+       unsigned long node_start_pfn = 0, node_end_pfn = 0;
+       struct zone *zone;
 
-               if (unlikely(!valid_section(ms)))
-                       continue;
+       for (zone = pgdat->node_zones;
+            zone < pgdat->node_zones + MAX_NR_ZONES; zone++) {
+               unsigned long zone_end_pfn = zone->zone_start_pfn +
+                                            zone->spanned_pages;
 
-               if (pfn_to_nid(pfn) != nid)
+               /* No need to lock the zones, they can't change. */
+               if (!zone->spanned_pages)
                        continue;
-
-                /* If the section is current section, it continues the loop */
-               if (start_pfn == pfn)
+               if (!node_end_pfn) {
+                       node_start_pfn = zone->zone_start_pfn;
+                       node_end_pfn = zone_end_pfn;
                        continue;
+               }
 
-               /* If we find valid section, we have nothing to do */
-               return;
+               if (zone_end_pfn > node_end_pfn)
+                       node_end_pfn = zone_end_pfn;
+               if (zone->zone_start_pfn < node_start_pfn)
+                       node_start_pfn = zone->zone_start_pfn;
        }
 
-       /* The pgdat has no valid section */
-       pgdat->node_start_pfn = 0;
-       pgdat->node_spanned_pages = 0;
+       pgdat->node_start_pfn = node_start_pfn;
+       pgdat->node_spanned_pages = node_end_pfn - node_start_pfn;
 }
 
 static void __remove_zone(struct zone *zone, unsigned long start_pfn)
@@ -509,9 +462,19 @@ static void __remove_zone(struct zone *zone, unsigned long start_pfn)
        int nr_pages = PAGES_PER_SECTION;
        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
+
        pgdat_resize_lock(zone->zone_pgdat, &flags);
        shrink_zone_span(zone, start_pfn, start_pfn + nr_pages);
-       shrink_pgdat_span(pgdat, start_pfn, start_pfn + nr_pages);
+       update_pgdat_span(pgdat);
        pgdat_resize_unlock(zone->zone_pgdat, &flags);
 }
 
@@ -883,7 +846,6 @@ static struct zone * __meminit move_pfn_range(int online_type, int nid,
        return zone;
 }
 
-/* Must be protected by mem_hotplug_begin() or a device_lock */
 int __ref online_pages(unsigned long pfn, unsigned long nr_pages, int online_type)
 {
        unsigned long flags;
@@ -895,6 +857,8 @@ int __ref online_pages(unsigned long pfn, unsigned long nr_pages, int online_typ
        struct memory_notify arg;
        struct memory_block *mem;
 
+       mem_hotplug_begin();
+
        /*
         * We can't use pfn_to_nid() because nid might be stored in struct page
         * which is not yet initialized. Instead, we find nid from memory block.
@@ -960,6 +924,7 @@ int __ref online_pages(unsigned long pfn, unsigned long nr_pages, int online_typ
 
        if (onlined_pages)
                memory_notify(MEM_ONLINE, &arg);
+       mem_hotplug_done();
        return 0;
 
 failed_addition:
@@ -967,6 +932,7 @@ failed_addition:
                 (unsigned long long) pfn << PAGE_SHIFT,
                 (((unsigned long long) pfn + nr_pages) << PAGE_SHIFT) - 1);
        memory_notify(MEM_CANCEL_ONLINE, &arg);
+       mem_hotplug_done();
        return ret;
 }
 #endif /* CONFIG_MEMORY_HOTPLUG_SPARSE */
@@ -1114,7 +1080,12 @@ static int online_memory_block(struct memory_block *mem, void *arg)
        return device_online(&mem->dev);
 }
 
-/* we are OK calling __meminit stuff here - we have CONFIG_MEMORY_HOTPLUG */
+/*
+ * NOTE: The caller must call lock_device_hotplug() to serialize hotplug
+ * and online/offline operations (triggered e.g. by sysfs).
+ *
+ * we are OK calling __meminit stuff here - we have CONFIG_MEMORY_HOTPLUG
+ */
 int __ref add_memory_resource(int nid, struct resource *res, bool online)
 {
        u64 start, size;
@@ -1166,26 +1137,26 @@ int __ref add_memory_resource(int nid, struct resource *res, bool online)
        /* create new memmap entry */
        firmware_map_add_hotplug(start, start + size, "System RAM");
 
+       /* device_online() will take the lock when calling online_pages() */
+       mem_hotplug_done();
+
        /* online pages if requested */
        if (online)
                walk_memory_range(PFN_DOWN(start), PFN_UP(start + size - 1),
                                  NULL, online_memory_block);
 
-       goto out;
-
+       return ret;
 error:
        /* rollback pgdat allocation and others */
        if (new_node)
                rollback_node_hotadd(nid);
        memblock_remove(start, size);
-
-out:
        mem_hotplug_done();
        return ret;
 }
-EXPORT_SYMBOL_GPL(add_memory_resource);
 
-int __ref add_memory(int nid, u64 start, u64 size)
+/* requires device_hotplug_lock, see add_memory_resource() */
+int __ref __add_memory(int nid, u64 start, u64 size)
 {
        struct resource *res;
        int ret;
@@ -1199,6 +1170,17 @@ int __ref add_memory(int nid, u64 start, u64 size)
                release_memory_resource(res);
        return ret;
 }
+
+int add_memory(int nid, u64 start, u64 size)
+{
+       int rc;
+
+       lock_device_hotplug();
+       rc = __add_memory(nid, start, size);
+       unlock_device_hotplug();
+
+       return rc;
+}
 EXPORT_SYMBOL_GPL(add_memory);
 
 #ifdef CONFIG_MEMORY_HOTREMOVE
@@ -1635,10 +1617,16 @@ static int __ref __offline_pages(unsigned long start_pfn,
                return -EINVAL;
        if (!IS_ALIGNED(end_pfn, pageblock_nr_pages))
                return -EINVAL;
+
+       mem_hotplug_begin();
+
        /* This makes hotplug much easier...and readable.
           we assume this for now. .*/
-       if (!test_pages_in_a_zone(start_pfn, end_pfn, &valid_start, &valid_end))
+       if (!test_pages_in_a_zone(start_pfn, end_pfn, &valid_start,
+                                 &valid_end)) {
+               mem_hotplug_done();
                return -EINVAL;
+       }
 
        zone = page_zone(pfn_to_page(valid_start));
        node = zone_to_nid(zone);
@@ -1647,8 +1635,10 @@ static int __ref __offline_pages(unsigned long start_pfn,
        /* set above range as isolated */
        ret = start_isolate_page_range(start_pfn, end_pfn,
                                       MIGRATE_MOVABLE, true);
-       if (ret)
+       if (ret) {
+               mem_hotplug_done();
                return ret;
+       }
 
        arg.start_pfn = start_pfn;
        arg.nr_pages = nr_pages;
@@ -1719,6 +1709,7 @@ repeat:
        writeback_set_ratelimit();
 
        memory_notify(MEM_OFFLINE, &arg);
+       mem_hotplug_done();
        return 0;
 
 failed_removal:
@@ -1728,10 +1719,10 @@ failed_removal:
        memory_notify(MEM_CANCEL_OFFLINE, &arg);
        /* pushback to free area */
        undo_isolate_page_range(start_pfn, end_pfn, MIGRATE_MOVABLE);
+       mem_hotplug_done();
        return ret;
 }
 
-/* Must be protected by mem_hotplug_begin() or a device_lock */
 int offline_pages(unsigned long start_pfn, unsigned long nr_pages)
 {
        return __offline_pages(start_pfn, start_pfn + nr_pages);
@@ -1902,7 +1893,7 @@ EXPORT_SYMBOL(try_offline_node);
  * and online/offline operations before this call, as required by
  * try_offline_node().
  */
-void __ref remove_memory(int nid, u64 start, u64 size)
+void __ref __remove_memory(int nid, u64 start, u64 size)
 {
        int ret;
 
@@ -1925,11 +1916,18 @@ void __ref remove_memory(int nid, u64 start, u64 size)
        memblock_free(start, size);
        memblock_remove(start, size);
 
-       arch_remove_memory(start, size, NULL);
+       arch_remove_memory(nid, start, size, NULL);
 
        try_offline_node(nid);
 
        mem_hotplug_done();
 }
+
+void remove_memory(int nid, u64 start, u64 size)
+{
+       lock_device_hotplug();
+       __remove_memory(nid, start, size);
+       unlock_device_hotplug();
+}
 EXPORT_SYMBOL_GPL(remove_memory);
 #endif /* CONFIG_MEMORY_HOTREMOVE */