tools headers UAPI: Sync linux/prctl.h with the kernel sources
[linux-2.6-microblaze.git] / mm / memory_hotplug.c
index 0cdbbfb..70620d0 100644 (file)
 #include "internal.h"
 #include "shuffle.h"
 
+
+/*
+ * memory_hotplug.memmap_on_memory parameter
+ */
+static bool memmap_on_memory __ro_after_init;
+#ifdef CONFIG_MHP_MEMMAP_ON_MEMORY
+module_param(memmap_on_memory, bool, 0444);
+MODULE_PARM_DESC(memmap_on_memory, "Enable memmap on memory for memory hotplug");
+#endif
+
 /*
  * online_page_callback contains pointer to current page onlining function.
  * Initially it is generic_online_page(). If it is required it could be
@@ -648,9 +658,16 @@ static void online_pages_range(unsigned long start_pfn, unsigned long nr_pages)
         * decide to not expose all pages to the buddy (e.g., expose them
         * later). We account all pages as being online and belonging to this
         * zone ("present").
+        * When using memmap_on_memory, the range might not be aligned to
+        * MAX_ORDER_NR_PAGES - 1, but pageblock aligned. __ffs() will detect
+        * this and the first chunk to online will be pageblock_nr_pages.
         */
-       for (pfn = start_pfn; pfn < end_pfn; pfn += MAX_ORDER_NR_PAGES)
-               (*online_page_callback)(pfn_to_page(pfn), MAX_ORDER - 1);
+       for (pfn = start_pfn; pfn < end_pfn;) {
+               int order = min(MAX_ORDER - 1UL, __ffs(pfn));
+
+               (*online_page_callback)(pfn_to_page(pfn), order);
+               pfn += (1UL << order);
+       }
 
        /* mark all involved sections as online */
        online_mem_sections(start_pfn, end_pfn);
@@ -817,7 +834,7 @@ static inline struct zone *default_zone_for_pfn(int nid, unsigned long start_pfn
        return movable_node_enabled ? movable_zone : kernel_zone;
 }
 
-struct zone * zone_for_pfn_range(int online_type, int nid, unsigned start_pfn,
+struct zone *zone_for_pfn_range(int online_type, int nid, unsigned start_pfn,
                unsigned long nr_pages)
 {
        if (online_type == MMOP_ONLINE_KERNEL)
@@ -829,24 +846,86 @@ struct zone * zone_for_pfn_range(int online_type, int nid, unsigned start_pfn,
        return default_zone_for_pfn(nid, start_pfn, nr_pages);
 }
 
-int __ref online_pages(unsigned long pfn, unsigned long nr_pages,
-                      int online_type, int nid)
+/*
+ * This function should only be called by memory_block_{online,offline},
+ * and {online,offline}_pages.
+ */
+void adjust_present_page_count(struct zone *zone, long nr_pages)
+{
+       unsigned long flags;
+
+       zone->present_pages += nr_pages;
+       pgdat_resize_lock(zone->zone_pgdat, &flags);
+       zone->zone_pgdat->node_present_pages += nr_pages;
+       pgdat_resize_unlock(zone->zone_pgdat, &flags);
+}
+
+int mhp_init_memmap_on_memory(unsigned long pfn, unsigned long nr_pages,
+                             struct zone *zone)
+{
+       unsigned long end_pfn = pfn + nr_pages;
+       int ret;
+
+       ret = kasan_add_zero_shadow(__va(PFN_PHYS(pfn)), PFN_PHYS(nr_pages));
+       if (ret)
+               return ret;
+
+       move_pfn_range_to_zone(zone, pfn, nr_pages, NULL, MIGRATE_UNMOVABLE);
+
+       /*
+        * It might be that the vmemmap_pages fully span sections. If that is
+        * the case, mark those sections online here as otherwise they will be
+        * left offline.
+        */
+       if (nr_pages >= PAGES_PER_SECTION)
+               online_mem_sections(pfn, ALIGN_DOWN(end_pfn, PAGES_PER_SECTION));
+
+       return ret;
+}
+
+void mhp_deinit_memmap_on_memory(unsigned long pfn, unsigned long nr_pages)
+{
+       unsigned long end_pfn = pfn + nr_pages;
+
+       /*
+        * It might be that the vmemmap_pages fully span sections. If that is
+        * the case, mark those sections offline here as otherwise they will be
+        * left online.
+        */
+       if (nr_pages >= PAGES_PER_SECTION)
+               offline_mem_sections(pfn, ALIGN_DOWN(end_pfn, PAGES_PER_SECTION));
+
+        /*
+        * The pages associated with this vmemmap have been offlined, so
+        * we can reset its state here.
+        */
+       remove_pfn_range_from_zone(page_zone(pfn_to_page(pfn)), pfn, nr_pages);
+       kasan_remove_zero_shadow(__va(PFN_PHYS(pfn)), PFN_PHYS(nr_pages));
+}
+
+int __ref online_pages(unsigned long pfn, unsigned long nr_pages, struct zone *zone)
 {
        unsigned long flags;
-       struct zone *zone;
        int need_zonelists_rebuild = 0;
+       const int nid = zone_to_nid(zone);
        int ret;
        struct memory_notify arg;
 
-       /* We can only online full sections (e.g., SECTION_IS_ONLINE) */
+       /*
+        * {on,off}lining is constrained to full memory sections (or more
+        * precisly to memory blocks from the user space POV).
+        * memmap_on_memory is an exception because it reserves initial part
+        * of the physical memory space for vmemmaps. That space is pageblock
+        * aligned.
+        */
        if (WARN_ON_ONCE(!nr_pages ||
-                        !IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION)))
+                        !IS_ALIGNED(pfn, pageblock_nr_pages) ||
+                        !IS_ALIGNED(pfn + nr_pages, PAGES_PER_SECTION)))
                return -EINVAL;
 
        mem_hotplug_begin();
 
        /* associate pfn range with the zone */
-       zone = zone_for_pfn_range(online_type, nid, pfn, nr_pages);
        move_pfn_range_to_zone(zone, pfn, nr_pages, NULL, MIGRATE_ISOLATE);
 
        arg.start_pfn = pfn;
@@ -877,11 +956,7 @@ int __ref online_pages(unsigned long pfn, unsigned long nr_pages,
        }
 
        online_pages_range(pfn, nr_pages);
-       zone->present_pages += nr_pages;
-
-       pgdat_resize_lock(zone->zone_pgdat, &flags);
-       zone->zone_pgdat->node_present_pages += nr_pages;
-       pgdat_resize_unlock(zone->zone_pgdat, &flags);
+       adjust_present_page_count(zone, nr_pages);
 
        node_states_set_node(nid, &arg);
        if (need_zonelists_rebuild)
@@ -1064,6 +1139,45 @@ static int online_memory_block(struct memory_block *mem, void *arg)
        return device_online(&mem->dev);
 }
 
+bool mhp_supports_memmap_on_memory(unsigned long size)
+{
+       unsigned long nr_vmemmap_pages = size / PAGE_SIZE;
+       unsigned long vmemmap_size = nr_vmemmap_pages * sizeof(struct page);
+       unsigned long remaining_size = size - vmemmap_size;
+
+       /*
+        * Besides having arch support and the feature enabled at runtime, we
+        * need a few more assumptions to hold true:
+        *
+        * a) We span a single memory block: memory onlining/offlinin;g happens
+        *    in memory block granularity. We don't want the vmemmap of online
+        *    memory blocks to reside on offline memory blocks. In the future,
+        *    we might want to support variable-sized memory blocks to make the
+        *    feature more versatile.
+        *
+        * b) The vmemmap pages span complete PMDs: We don't want vmemmap code
+        *    to populate memory from the altmap for unrelated parts (i.e.,
+        *    other memory blocks)
+        *
+        * c) The vmemmap pages (and thereby the pages that will be exposed to
+        *    the buddy) have to cover full pageblocks: memory onlining/offlining
+        *    code requires applicable ranges to be page-aligned, for example, to
+        *    set the migratetypes properly.
+        *
+        * TODO: Although we have a check here to make sure that vmemmap pages
+        *       fully populate a PMD, it is not the right place to check for
+        *       this. A much better solution involves improving vmemmap code
+        *       to fallback to base pages when trying to populate vmemmap using
+        *       altmap as an alternative source of memory, and we do not exactly
+        *       populate a single PMD.
+        */
+       return memmap_on_memory &&
+              IS_ENABLED(CONFIG_MHP_MEMMAP_ON_MEMORY) &&
+              size == memory_block_size_bytes() &&
+              IS_ALIGNED(vmemmap_size, PMD_SIZE) &&
+              IS_ALIGNED(remaining_size, (pageblock_nr_pages << PAGE_SHIFT));
+}
+
 /*
  * NOTE: The caller must call lock_device_hotplug() to serialize hotplug
  * and online/offline operations (triggered e.g. by sysfs).
@@ -1073,6 +1187,7 @@ static int online_memory_block(struct memory_block *mem, void *arg)
 int __ref add_memory_resource(int nid, struct resource *res, mhp_t mhp_flags)
 {
        struct mhp_params params = { .pgprot = pgprot_mhp(PAGE_KERNEL) };
+       struct vmem_altmap mhp_altmap = {};
        u64 start, size;
        bool new_node = false;
        int ret;
@@ -1099,13 +1214,26 @@ int __ref add_memory_resource(int nid, struct resource *res, mhp_t mhp_flags)
                goto error;
        new_node = ret;
 
+       /*
+        * Self hosted memmap array
+        */
+       if (mhp_flags & MHP_MEMMAP_ON_MEMORY) {
+               if (!mhp_supports_memmap_on_memory(size)) {
+                       ret = -EINVAL;
+                       goto error;
+               }
+               mhp_altmap.free = PHYS_PFN(size);
+               mhp_altmap.base_pfn = PHYS_PFN(start);
+               params.altmap = &mhp_altmap;
+       }
+
        /* call arch's memory hotadd */
        ret = arch_add_memory(nid, start, size, &params);
        if (ret < 0)
                goto error;
 
        /* create memory block devices after memory was added */
-       ret = create_memory_block_devices(start, size);
+       ret = create_memory_block_devices(start, size, mhp_altmap.alloc);
        if (ret) {
                arch_remove_memory(nid, start, size, NULL);
                goto error;
@@ -1573,9 +1701,16 @@ int __ref offline_pages(unsigned long start_pfn, unsigned long nr_pages)
        int ret, node;
        char *reason;
 
-       /* We can only offline full sections (e.g., SECTION_IS_ONLINE) */
+       /*
+        * {on,off}lining is constrained to full memory sections (or more
+        * precisly to memory blocks from the user space POV).
+        * memmap_on_memory is an exception because it reserves initial part
+        * of the physical memory space for vmemmaps. That space is pageblock
+        * aligned.
+        */
        if (WARN_ON_ONCE(!nr_pages ||
-                        !IS_ALIGNED(start_pfn | nr_pages, PAGES_PER_SECTION)))
+                        !IS_ALIGNED(start_pfn, pageblock_nr_pages) ||
+                        !IS_ALIGNED(start_pfn + nr_pages, PAGES_PER_SECTION)))
                return -EINVAL;
 
        mem_hotplug_begin();
@@ -1611,6 +1746,7 @@ int __ref offline_pages(unsigned long start_pfn, unsigned long nr_pages)
         * in a way that pages from isolated pageblock are left on pcplists.
         */
        zone_pcp_disable(zone);
+       lru_cache_disable();
 
        /* set above range as isolated */
        ret = start_isolate_page_range(start_pfn, end_pfn,
@@ -1642,7 +1778,6 @@ int __ref offline_pages(unsigned long start_pfn, unsigned long nr_pages)
                        }
 
                        cond_resched();
-                       lru_add_drain_all();
 
                        ret = scan_movable_pages(pfn, end_pfn, &pfn);
                        if (!ret) {
@@ -1687,15 +1822,12 @@ int __ref offline_pages(unsigned long start_pfn, unsigned long nr_pages)
        zone->nr_isolate_pageblock -= nr_pages / pageblock_nr_pages;
        spin_unlock_irqrestore(&zone->lock, flags);
 
+       lru_cache_enable();
        zone_pcp_enable(zone);
 
        /* removal success */
        adjust_managed_page_count(pfn_to_page(start_pfn), -nr_pages);
-       zone->present_pages -= nr_pages;
-
-       pgdat_resize_lock(zone->zone_pgdat, &flags);
-       zone->zone_pgdat->node_present_pages -= nr_pages;
-       pgdat_resize_unlock(zone->zone_pgdat, &flags);
+       adjust_present_page_count(zone, -nr_pages);
 
        init_per_zone_wmark_min();
 
@@ -1750,6 +1882,14 @@ static int check_memblock_offlined_cb(struct memory_block *mem, void *arg)
        return 0;
 }
 
+static int get_nr_vmemmap_pages_cb(struct memory_block *mem, void *arg)
+{
+       /*
+        * If not set, continue with the next block.
+        */
+       return mem->nr_vmemmap_pages;
+}
+
 static int check_cpu_on_node(pg_data_t *pgdat)
 {
        int cpu;
@@ -1824,6 +1964,9 @@ EXPORT_SYMBOL(try_offline_node);
 static int __ref try_remove_memory(int nid, u64 start, u64 size)
 {
        int rc = 0;
+       struct vmem_altmap mhp_altmap = {};
+       struct vmem_altmap *altmap = NULL;
+       unsigned long nr_vmemmap_pages;
 
        BUG_ON(check_hotplug_memory_range(start, size));
 
@@ -1836,6 +1979,31 @@ static int __ref try_remove_memory(int nid, u64 start, u64 size)
        if (rc)
                return rc;
 
+       /*
+        * We only support removing memory added with MHP_MEMMAP_ON_MEMORY in
+        * the same granularity it was added - a single memory block.
+        */
+       if (memmap_on_memory) {
+               nr_vmemmap_pages = walk_memory_blocks(start, size, NULL,
+                                                     get_nr_vmemmap_pages_cb);
+               if (nr_vmemmap_pages) {
+                       if (size != memory_block_size_bytes()) {
+                               pr_warn("Refuse to remove %#llx - %#llx,"
+                                       "wrong granularity\n",
+                                       start, start + size);
+                               return -EINVAL;
+                       }
+
+                       /*
+                        * Let remove_pmd_table->free_hugepage_table do the
+                        * right thing if we used vmem_altmap when hot-adding
+                        * the range.
+                        */
+                       mhp_altmap.alloc = nr_vmemmap_pages;
+                       altmap = &mhp_altmap;
+               }
+       }
+
        /* remove memmap entry */
        firmware_map_remove(start, start + size, "System RAM");
 
@@ -1847,7 +2015,7 @@ static int __ref try_remove_memory(int nid, u64 start, u64 size)
 
        mem_hotplug_begin();
 
-       arch_remove_memory(nid, start, size, NULL);
+       arch_remove_memory(nid, start, size, altmap);
 
        if (IS_ENABLED(CONFIG_ARCH_KEEP_MEMBLOCK)) {
                memblock_free(start, size);