Merge tag 'for-linus-5.9-ofs1' of git://git.kernel.org/pub/scm/linux/kernel/git/hubca...
[linux-2.6-microblaze.git] / mm / memory_hotplug.c
index fc0aad0..ac6961a 100644 (file)
@@ -98,11 +98,14 @@ void mem_hotplug_done(void)
 u64 max_mem_size = U64_MAX;
 
 /* add this memory to iomem resource */
-static struct resource *register_memory_resource(u64 start, u64 size)
+static struct resource *register_memory_resource(u64 start, u64 size,
+                                                const char *resource_name)
 {
        struct resource *res;
        unsigned long flags =  IORESOURCE_SYSTEM_RAM | IORESOURCE_BUSY;
-       char *resource_name = "System RAM";
+
+       if (strcmp(resource_name, "System RAM"))
+               flags |= IORESOURCE_MEM_DRIVER_MANAGED;
 
        /*
         * Make sure value parsed from 'mem=' only restricts memory adding
@@ -468,11 +471,20 @@ void __ref remove_pfn_range_from_zone(struct zone *zone,
                                      unsigned long start_pfn,
                                      unsigned long nr_pages)
 {
+       const unsigned long end_pfn = start_pfn + nr_pages;
        struct pglist_data *pgdat = zone->zone_pgdat;
-       unsigned long flags;
+       unsigned long pfn, cur_nr_pages, flags;
 
        /* Poison struct pages because they are now uninitialized again. */
-       page_init_poison(pfn_to_page(start_pfn), sizeof(struct page) * nr_pages);
+       for (pfn = start_pfn; pfn < end_pfn; pfn += cur_nr_pages) {
+               cond_resched();
+
+               /* Select all remaining pages up to the next section boundary */
+               cur_nr_pages =
+                       min(end_pfn - pfn, SECTION_ALIGN_UP(pfn + 1) - pfn);
+               page_init_poison(pfn_to_page(pfn),
+                                sizeof(struct page) * cur_nr_pages);
+       }
 
 #ifdef CONFIG_ZONE_DEVICE
        /*
@@ -819,6 +831,14 @@ int __ref online_pages(unsigned long pfn, unsigned long nr_pages,
        zone->zone_pgdat->node_present_pages += onlined_pages;
        pgdat_resize_unlock(zone->zone_pgdat, &flags);
 
+       /*
+        * When exposing larger, physically contiguous memory areas to the
+        * buddy, shuffling in the buddy (when freeing onlined pages, putting
+        * them either to the head or the tail of the freelist) is only helpful
+        * for maintaining the shuffle, but not for creating the initial
+        * shuffle. Shuffle the whole zone to make sure the just onlined pages
+        * are properly distributed across the whole freelist.
+        */
        shuffle_zone(zone);
 
        node_states_set_node(nid, &arg);
@@ -832,8 +852,6 @@ int __ref online_pages(unsigned long pfn, unsigned long nr_pages,
        kswapd_run(nid);
        kcompactd_run(nid);
 
-       vm_total_pages = nr_free_pagecache_pages();
-
        writeback_set_ratelimit();
 
        memory_notify(MEM_ONLINE, &arg);
@@ -862,10 +880,9 @@ static void reset_node_present_pages(pg_data_t *pgdat)
 }
 
 /* we are OK calling __meminit stuff here - we have CONFIG_MEMORY_HOTPLUG */
-static pg_data_t __ref *hotadd_new_pgdat(int nid, u64 start)
+static pg_data_t __ref *hotadd_new_pgdat(int nid)
 {
        struct pglist_data *pgdat;
-       unsigned long start_pfn = PFN_DOWN(start);
 
        pgdat = NODE_DATA(nid);
        if (!pgdat) {
@@ -879,13 +896,13 @@ static pg_data_t __ref *hotadd_new_pgdat(int nid, u64 start)
        } else {
                int cpu;
                /*
-                * Reset the nr_zones, order and classzone_idx before reuse.
-                * Note that kswapd will init kswapd_classzone_idx properly
+                * Reset the nr_zones, order and highest_zoneidx before reuse.
+                * Note that kswapd will init kswapd_highest_zoneidx properly
                 * when it starts in the near future.
                 */
                pgdat->nr_zones = 0;
                pgdat->kswapd_order = 0;
-               pgdat->kswapd_classzone_idx = 0;
+               pgdat->kswapd_highest_zoneidx = 0;
                for_each_online_cpu(cpu) {
                        struct per_cpu_nodestat *p;
 
@@ -895,9 +912,8 @@ static pg_data_t __ref *hotadd_new_pgdat(int nid, u64 start)
        }
 
        /* we can use NODE_DATA(nid) from here */
-
        pgdat->node_id = nid;
-       pgdat->node_start_pfn = start_pfn;
+       pgdat->node_start_pfn = 0;
 
        /* init node's zones as empty zones, we don't have any present pages.*/
        free_area_init_core_hotplug(nid);
@@ -932,7 +948,6 @@ static void rollback_node_hotadd(int nid)
 /**
  * try_online_node - online a node if offlined
  * @nid: the node ID
- * @start: start addr of the node
  * @set_node_online: Whether we want to online the node
  * called by cpu_up() to online a node without onlined memory.
  *
@@ -941,7 +956,7 @@ static void rollback_node_hotadd(int nid)
  * 0 -> the node is already online
  * -ENOMEM -> the node could not be allocated
  */
-static int __try_online_node(int nid, u64 start, bool set_node_online)
+static int __try_online_node(int nid, bool set_node_online)
 {
        pg_data_t *pgdat;
        int ret = 1;
@@ -949,7 +964,7 @@ static int __try_online_node(int nid, u64 start, bool set_node_online)
        if (node_online(nid))
                return 0;
 
-       pgdat = hotadd_new_pgdat(nid, start);
+       pgdat = hotadd_new_pgdat(nid);
        if (!pgdat) {
                pr_err("Cannot online node %d due to NULL pgdat\n", nid);
                ret = -ENOMEM;
@@ -973,7 +988,7 @@ int try_online_node(int nid)
        int ret;
 
        mem_hotplug_begin();
-       ret =  __try_online_node(nid, 0, true);
+       ret =  __try_online_node(nid, true);
        mem_hotplug_done();
        return ret;
 }
@@ -1017,17 +1032,17 @@ int __ref add_memory_resource(int nid, struct resource *res)
        if (ret)
                return ret;
 
+       if (!node_possible(nid)) {
+               WARN(1, "node %d was absent from the node_possible_map\n", nid);
+               return -EINVAL;
+       }
+
        mem_hotplug_begin();
 
-       /*
-        * Add new range to memblock so that when hotadd_new_pgdat() is called
-        * to allocate new pgdat, get_pfn_range_for_nid() will be able to find
-        * this new range and calculate total pages correctly.  The range will
-        * be removed at hot-remove time.
-        */
-       memblock_add_node(start, size, nid);
+       if (IS_ENABLED(CONFIG_ARCH_KEEP_MEMBLOCK))
+               memblock_add_node(start, size, nid);
 
-       ret = __try_online_node(nid, start, false);
+       ret = __try_online_node(nid, false);
        if (ret < 0)
                goto error;
        new_node = ret;
@@ -1060,7 +1075,8 @@ int __ref add_memory_resource(int nid, struct resource *res)
        BUG_ON(ret);
 
        /* create new memmap entry */
-       firmware_map_add_hotplug(start, start + size, "System RAM");
+       if (!strcmp(res->name, "System RAM"))
+               firmware_map_add_hotplug(start, start + size, "System RAM");
 
        /* device_online() will take the lock when calling online_pages() */
        mem_hotplug_done();
@@ -1074,7 +1090,8 @@ error:
        /* rollback pgdat allocation and others */
        if (new_node)
                rollback_node_hotadd(nid);
-       memblock_remove(start, size);
+       if (IS_ENABLED(CONFIG_ARCH_KEEP_MEMBLOCK))
+               memblock_remove(start, size);
        mem_hotplug_done();
        return ret;
 }
@@ -1085,7 +1102,7 @@ int __ref __add_memory(int nid, u64 start, u64 size)
        struct resource *res;
        int ret;
 
-       res = register_memory_resource(start, size);
+       res = register_memory_resource(start, size, "System RAM");
        if (IS_ERR(res))
                return PTR_ERR(res);
 
@@ -1107,82 +1124,57 @@ int add_memory(int nid, u64 start, u64 size)
 }
 EXPORT_SYMBOL_GPL(add_memory);
 
-#ifdef CONFIG_MEMORY_HOTREMOVE
 /*
- * A free page on the buddy free lists (not the per-cpu lists) has PageBuddy
- * set and the size of the free page is given by page_order(). Using this,
- * the function determines if the pageblock contains only free pages.
- * Due to buddy contraints, a free page at least the size of a pageblock will
- * be located at the start of the pageblock
+ * Add special, driver-managed memory to the system as system RAM. Such
+ * memory is not exposed via the raw firmware-provided memmap as system
+ * RAM, instead, it is detected and added by a driver - during cold boot,
+ * after a reboot, and after kexec.
+ *
+ * Reasons why this memory should not be used for the initial memmap of a
+ * kexec kernel or for placing kexec images:
+ * - The booting kernel is in charge of determining how this memory will be
+ *   used (e.g., use persistent memory as system RAM)
+ * - Coordination with a hypervisor is required before this memory
+ *   can be used (e.g., inaccessible parts).
+ *
+ * For this memory, no entries in /sys/firmware/memmap ("raw firmware-provided
+ * memory map") are created. Also, the created memory resource is flagged
+ * with IORESOURCE_MEM_DRIVER_MANAGED, so in-kernel users can special-case
+ * this memory as well (esp., not place kexec images onto it).
+ *
+ * The resource_name (visible via /proc/iomem) has to have the format
+ * "System RAM ($DRIVER)".
  */
-static inline int pageblock_free(struct page *page)
+int add_memory_driver_managed(int nid, u64 start, u64 size,
+                             const char *resource_name)
 {
-       return PageBuddy(page) && page_order(page) >= pageblock_order;
-}
+       struct resource *res;
+       int rc;
 
-/* Return the pfn of the start of the next active pageblock after a given pfn */
-static unsigned long next_active_pageblock(unsigned long pfn)
-{
-       struct page *page = pfn_to_page(pfn);
+       if (!resource_name ||
+           strstr(resource_name, "System RAM (") != resource_name ||
+           resource_name[strlen(resource_name) - 1] != ')')
+               return -EINVAL;
 
-       /* Ensure the starting page is pageblock-aligned */
-       BUG_ON(pfn & (pageblock_nr_pages - 1));
+       lock_device_hotplug();
 
-       /* If the entire pageblock is free, move to the end of free page */
-       if (pageblock_free(page)) {
-               int order;
-               /* be careful. we don't have locks, page_order can be changed.*/
-               order = page_order(page);
-               if ((order < MAX_ORDER) && (order >= pageblock_order))
-                       return pfn + (1 << order);
+       res = register_memory_resource(start, size, resource_name);
+       if (IS_ERR(res)) {
+               rc = PTR_ERR(res);
+               goto out_unlock;
        }
 
-       return pfn + pageblock_nr_pages;
-}
-
-static bool is_pageblock_removable_nolock(unsigned long pfn)
-{
-       struct page *page = pfn_to_page(pfn);
-       struct zone *zone;
-
-       /*
-        * We have to be careful here because we are iterating over memory
-        * sections which are not zone aware so we might end up outside of
-        * the zone but still within the section.
-        * We have to take care about the node as well. If the node is offline
-        * its NODE_DATA will be NULL - see page_zone.
-        */
-       if (!node_online(page_to_nid(page)))
-               return false;
-
-       zone = page_zone(page);
-       pfn = page_to_pfn(page);
-       if (!zone_spans_pfn(zone, pfn))
-               return false;
-
-       return !has_unmovable_pages(zone, page, MIGRATE_MOVABLE,
-                                   MEMORY_OFFLINE);
-}
-
-/* Checks if this range of memory is likely to be hot-removable. */
-bool is_mem_section_removable(unsigned long start_pfn, unsigned long nr_pages)
-{
-       unsigned long end_pfn, pfn;
-
-       end_pfn = min(start_pfn + nr_pages,
-                       zone_end_pfn(page_zone(pfn_to_page(start_pfn))));
-
-       /* Check the starting page of each pageblock within the range */
-       for (pfn = start_pfn; pfn < end_pfn; pfn = next_active_pageblock(pfn)) {
-               if (!is_pageblock_removable_nolock(pfn))
-                       return false;
-               cond_resched();
-       }
+       rc = add_memory_resource(nid, res);
+       if (rc < 0)
+               release_memory_resource(res);
 
-       /* All pageblocks in the memory block are likely to be hot-removable */
-       return true;
+out_unlock:
+       unlock_device_hotplug();
+       return rc;
 }
+EXPORT_SYMBOL_GPL(add_memory_driver_managed);
 
+#ifdef CONFIG_MEMORY_HOTREMOVE
 /*
  * Confirm all pages in a range [start, end) belong to the same zone (skipping
  * memory holes). When true, return the zone.
@@ -1224,11 +1216,17 @@ struct zone *test_pages_in_a_zone(unsigned long start_pfn,
 
 /*
  * Scan pfn range [start,end) to find movable/migratable pages (LRU pages,
- * non-lru movable pages and hugepages). We scan pfn because it's much
- * easier than scanning over linked list. This function returns the pfn
- * of the first found movable page if it's found, otherwise 0.
+ * non-lru movable pages and hugepages). Will skip over most unmovable
+ * pages (esp., pages that can be skipped when offlining), but bail out on
+ * definitely unmovable pages.
+ *
+ * Returns:
+ *     0 in case a movable page is found and movable_pfn was updated.
+ *     -ENOENT in case no movable page was found.
+ *     -EBUSY in case a definitely unmovable page was found.
  */
-static unsigned long scan_movable_pages(unsigned long start, unsigned long end)
+static int scan_movable_pages(unsigned long start, unsigned long end,
+                             unsigned long *movable_pfn)
 {
        unsigned long pfn;
 
@@ -1240,18 +1238,30 @@ static unsigned long scan_movable_pages(unsigned long start, unsigned long end)
                        continue;
                page = pfn_to_page(pfn);
                if (PageLRU(page))
-                       return pfn;
+                       goto found;
                if (__PageMovable(page))
-                       return pfn;
+                       goto found;
+
+               /*
+                * PageOffline() pages that are not marked __PageMovable() and
+                * have a reference count > 0 (after MEM_GOING_OFFLINE) are
+                * definitely unmovable. If their reference count would be 0,
+                * they could at least be skipped when offlining memory.
+                */
+               if (PageOffline(page) && page_count(page))
+                       return -EBUSY;
 
                if (!PageHuge(page))
                        continue;
                head = compound_head(page);
                if (page_huge_active(head))
-                       return pfn;
+                       goto found;
                skip = compound_nr(head) - (page - head);
                pfn += skip - 1;
        }
+       return -ENOENT;
+found:
+       *movable_pfn = pfn;
        return 0;
 }
 
@@ -1360,7 +1370,7 @@ offline_isolated_pages_cb(unsigned long start, unsigned long nr_pages,
 }
 
 /*
- * Check all pages in range, recoreded as memory resource, are isolated.
+ * Check all pages in range, recorded as memory resource, are isolated.
  */
 static int
 check_pages_isolated_cb(unsigned long start_pfn, unsigned long nr_pages,
@@ -1372,11 +1382,7 @@ check_pages_isolated_cb(unsigned long start_pfn, unsigned long nr_pages,
 
 static int __init cmdline_parse_movable_node(char *p)
 {
-#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
        movable_node_enabled = true;
-#else
-       pr_warn("movable_node parameter depends on CONFIG_HAVE_MEMBLOCK_NODE_MAP to work properly\n");
-#endif
        return 0;
 }
 early_param("movable_node", cmdline_parse_movable_node);
@@ -1518,7 +1524,8 @@ static int __ref __offline_pages(unsigned long start_pfn,
        }
 
        do {
-               for (pfn = start_pfn; pfn;) {
+               pfn = start_pfn;
+               do {
                        if (signal_pending(current)) {
                                ret = -EINTR;
                                reason = "signal backoff";
@@ -1528,14 +1535,19 @@ static int __ref __offline_pages(unsigned long start_pfn,
                        cond_resched();
                        lru_add_drain_all();
 
-                       pfn = scan_movable_pages(pfn, end_pfn);
-                       if (pfn) {
+                       ret = scan_movable_pages(pfn, end_pfn, &pfn);
+                       if (!ret) {
                                /*
                                 * TODO: fatal migration failures should bail
                                 * out
                                 */
                                do_migrate_range(pfn, end_pfn);
                        }
+               } while (!ret);
+
+               if (ret != -ENOENT) {
+                       reason = "unmovable page";
+                       goto failed_removal_isolated;
                }
 
                /*
@@ -1589,7 +1601,6 @@ static int __ref __offline_pages(unsigned long start_pfn,
                kcompactd_stop(node);
        }
 
-       vm_total_pages = nr_free_pagecache_pages();
        writeback_set_ratelimit();
 
        memory_notify(MEM_OFFLINE, &arg);
@@ -1750,8 +1761,12 @@ static int __ref try_remove_memory(int nid, u64 start, u64 size)
        mem_hotplug_begin();
 
        arch_remove_memory(nid, start, size, NULL);
-       memblock_free(start, size);
-       memblock_remove(start, size);
+
+       if (IS_ENABLED(CONFIG_ARCH_KEEP_MEMBLOCK)) {
+               memblock_free(start, size);
+               memblock_remove(start, size);
+       }
+
        __release_memory_resource(start, size);
 
        try_offline_node(nid);
@@ -1797,4 +1812,41 @@ int remove_memory(int nid, u64 start, u64 size)
        return rc;
 }
 EXPORT_SYMBOL_GPL(remove_memory);
+
+/*
+ * Try to offline and remove a memory block. Might take a long time to
+ * finish in case memory is still in use. Primarily useful for memory devices
+ * that logically unplugged all memory (so it's no longer in use) and want to
+ * offline + remove the memory block.
+ */
+int offline_and_remove_memory(int nid, u64 start, u64 size)
+{
+       struct memory_block *mem;
+       int rc = -EINVAL;
+
+       if (!IS_ALIGNED(start, memory_block_size_bytes()) ||
+           size != memory_block_size_bytes())
+               return rc;
+
+       lock_device_hotplug();
+       mem = find_memory_block(__pfn_to_section(PFN_DOWN(start)));
+       if (mem)
+               rc = device_offline(&mem->dev);
+       /* Ignore if the device is already offline. */
+       if (rc > 0)
+               rc = 0;
+
+       /*
+        * In case we succeeded to offline the memory block, remove it.
+        * This cannot fail as it cannot get onlined in the meantime.
+        */
+       if (!rc) {
+               rc = try_remove_memory(nid, start, size);
+               WARN_ON_ONCE(rc);
+       }
+       unlock_device_hotplug();
+
+       return rc;
+}
+EXPORT_SYMBOL_GPL(offline_and_remove_memory);
 #endif /* CONFIG_MEMORY_HOTREMOVE */