mm/page_alloc.c: introduce kernelcore=mirror option
[cascardo/linux.git] / mm / page_alloc.c
index 838ca8b..b8160b9 100644 (file)
@@ -247,6 +247,7 @@ static unsigned long __meminitdata arch_zone_highest_possible_pfn[MAX_NR_ZONES];
 static unsigned long __initdata required_kernelcore;
 static unsigned long __initdata required_movablecore;
 static unsigned long __meminitdata zone_movable_pfn[MAX_NUMNODES];
+static bool mirrored_kernelcore;
 
 /* movable_zone is the "real" zone pages in ZONE_MOVABLE are taken from */
 int movable_zone;
@@ -4491,6 +4492,9 @@ void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,
        pg_data_t *pgdat = NODE_DATA(nid);
        unsigned long pfn;
        unsigned long nr_initialised = 0;
+#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
+       struct memblock_region *r = NULL, *tmp;
+#endif
 
        if (highest_memmap_pfn < end_pfn - 1)
                highest_memmap_pfn = end_pfn - 1;
@@ -4516,6 +4520,40 @@ void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,
                        if (!update_defer_init(pgdat, pfn, end_pfn,
                                                &nr_initialised))
                                break;
+
+#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
+                       /*
+                        * if not mirrored_kernelcore and ZONE_MOVABLE exists,
+                        * range from zone_movable_pfn[nid] to end of each node
+                        * should be ZONE_MOVABLE not ZONE_NORMAL. skip it.
+                        */
+                       if (!mirrored_kernelcore && zone_movable_pfn[nid])
+                               if (zone == ZONE_NORMAL &&
+                                   pfn >= zone_movable_pfn[nid])
+                                       continue;
+
+                       /*
+                        * check given memblock attribute by firmware which
+                        * can affect kernel memory layout.
+                        * if zone==ZONE_MOVABLE but memory is mirrored,
+                        * it's an overlapped memmap init. skip it.
+                        */
+                       if (mirrored_kernelcore && zone == ZONE_MOVABLE) {
+                               if (!r ||
+                                   pfn >= memblock_region_memory_end_pfn(r)) {
+                                       for_each_memblock(memory, tmp)
+                                               if (pfn < memblock_region_memory_end_pfn(tmp))
+                                                       break;
+                                       r = tmp;
+                               }
+                               if (pfn >= memblock_region_memory_base_pfn(r) &&
+                                   memblock_is_mirror(r)) {
+                                       /* already initialized as NORMAL */
+                                       pfn = memblock_region_memory_end_pfn(r);
+                                       continue;
+                               }
+                       }
+#endif
                }
 
                /*
@@ -4934,11 +4972,6 @@ static void __meminit adjust_zone_range_for_zone_movable(int nid,
                        *zone_end_pfn = min(node_end_pfn,
                                arch_zone_highest_possible_pfn[movable_zone]);
 
-               /* Adjust for ZONE_MOVABLE starting within this range */
-               } else if (*zone_start_pfn < zone_movable_pfn[nid] &&
-                               *zone_end_pfn > zone_movable_pfn[nid]) {
-                       *zone_end_pfn = zone_movable_pfn[nid];
-
                /* Check if this whole range is within ZONE_MOVABLE */
                } else if (*zone_start_pfn >= zone_movable_pfn[nid])
                        *zone_start_pfn = *zone_end_pfn;
@@ -4953,31 +4986,31 @@ static unsigned long __meminit zone_spanned_pages_in_node(int nid,
                                        unsigned long zone_type,
                                        unsigned long node_start_pfn,
                                        unsigned long node_end_pfn,
+                                       unsigned long *zone_start_pfn,
+                                       unsigned long *zone_end_pfn,
                                        unsigned long *ignored)
 {
-       unsigned long zone_start_pfn, zone_end_pfn;
-
        /* When hotadd a new node from cpu_up(), the node should be empty */
        if (!node_start_pfn && !node_end_pfn)
                return 0;
 
        /* Get the start and end of the zone */
-       zone_start_pfn = arch_zone_lowest_possible_pfn[zone_type];
-       zone_end_pfn = arch_zone_highest_possible_pfn[zone_type];
+       *zone_start_pfn = arch_zone_lowest_possible_pfn[zone_type];
+       *zone_end_pfn = arch_zone_highest_possible_pfn[zone_type];
        adjust_zone_range_for_zone_movable(nid, zone_type,
                                node_start_pfn, node_end_pfn,
-                               &zone_start_pfn, &zone_end_pfn);
+                               zone_start_pfn, zone_end_pfn);
 
        /* Check that this node has pages within the zone's required range */
-       if (zone_end_pfn < node_start_pfn || zone_start_pfn > node_end_pfn)
+       if (*zone_end_pfn < node_start_pfn || *zone_start_pfn > node_end_pfn)
                return 0;
 
        /* Move the zone boundaries inside the node if necessary */
-       zone_end_pfn = min(zone_end_pfn, node_end_pfn);
-       zone_start_pfn = max(zone_start_pfn, node_start_pfn);
+       *zone_end_pfn = min(*zone_end_pfn, node_end_pfn);
+       *zone_start_pfn = max(*zone_start_pfn, node_start_pfn);
 
        /* Return the spanned pages */
-       return zone_end_pfn - zone_start_pfn;
+       return *zone_end_pfn - *zone_start_pfn;
 }
 
 /*
@@ -5023,6 +5056,7 @@ static unsigned long __meminit zone_absent_pages_in_node(int nid,
        unsigned long zone_low = arch_zone_lowest_possible_pfn[zone_type];
        unsigned long zone_high = arch_zone_highest_possible_pfn[zone_type];
        unsigned long zone_start_pfn, zone_end_pfn;
+       unsigned long nr_absent;
 
        /* When hotadd a new node from cpu_up(), the node should be empty */
        if (!node_start_pfn && !node_end_pfn)
@@ -5034,7 +5068,39 @@ static unsigned long __meminit zone_absent_pages_in_node(int nid,
        adjust_zone_range_for_zone_movable(nid, zone_type,
                        node_start_pfn, node_end_pfn,
                        &zone_start_pfn, &zone_end_pfn);
-       return __absent_pages_in_range(nid, zone_start_pfn, zone_end_pfn);
+       nr_absent = __absent_pages_in_range(nid, zone_start_pfn, zone_end_pfn);
+
+       /*
+        * ZONE_MOVABLE handling.
+        * Treat pages to be ZONE_MOVABLE in ZONE_NORMAL as absent pages
+        * and vice versa.
+        */
+       if (zone_movable_pfn[nid]) {
+               if (mirrored_kernelcore) {
+                       unsigned long start_pfn, end_pfn;
+                       struct memblock_region *r;
+
+                       for_each_memblock(memory, r) {
+                               start_pfn = clamp(memblock_region_memory_base_pfn(r),
+                                                 zone_start_pfn, zone_end_pfn);
+                               end_pfn = clamp(memblock_region_memory_end_pfn(r),
+                                               zone_start_pfn, zone_end_pfn);
+
+                               if (zone_type == ZONE_MOVABLE &&
+                                   memblock_is_mirror(r))
+                                       nr_absent += end_pfn - start_pfn;
+
+                               if (zone_type == ZONE_NORMAL &&
+                                   !memblock_is_mirror(r))
+                                       nr_absent += end_pfn - start_pfn;
+                       }
+               } else {
+                       if (zone_type == ZONE_NORMAL)
+                               nr_absent += node_end_pfn - zone_movable_pfn[nid];
+               }
+       }
+
+       return nr_absent;
 }
 
 #else /* CONFIG_HAVE_MEMBLOCK_NODE_MAP */
@@ -5042,8 +5108,18 @@ static inline unsigned long __meminit zone_spanned_pages_in_node(int nid,
                                        unsigned long zone_type,
                                        unsigned long node_start_pfn,
                                        unsigned long node_end_pfn,
+                                       unsigned long *zone_start_pfn,
+                                       unsigned long *zone_end_pfn,
                                        unsigned long *zones_size)
 {
+       unsigned int zone;
+
+       *zone_start_pfn = node_start_pfn;
+       for (zone = 0; zone < zone_type; zone++)
+               *zone_start_pfn += zones_size[zone];
+
+       *zone_end_pfn = *zone_start_pfn + zones_size[zone_type];
+
        return zones_size[zone_type];
 }
 
@@ -5072,15 +5148,22 @@ static void __meminit calculate_node_totalpages(struct pglist_data *pgdat,
 
        for (i = 0; i < MAX_NR_ZONES; i++) {
                struct zone *zone = pgdat->node_zones + i;
+               unsigned long zone_start_pfn, zone_end_pfn;
                unsigned long size, real_size;
 
                size = zone_spanned_pages_in_node(pgdat->node_id, i,
                                                  node_start_pfn,
                                                  node_end_pfn,
+                                                 &zone_start_pfn,
+                                                 &zone_end_pfn,
                                                  zones_size);
                real_size = size - zone_absent_pages_in_node(pgdat->node_id, i,
                                                  node_start_pfn, node_end_pfn,
                                                  zholes_size);
+               if (size)
+                       zone->zone_start_pfn = zone_start_pfn;
+               else
+                       zone->zone_start_pfn = 0;
                zone->spanned_pages = size;
                zone->present_pages = real_size;
 
@@ -5201,7 +5284,6 @@ static void __paginginit free_area_init_core(struct pglist_data *pgdat)
 {
        enum zone_type j;
        int nid = pgdat->node_id;
-       unsigned long zone_start_pfn = pgdat->node_start_pfn;
        int ret;
 
        pgdat_resize_init(pgdat);
@@ -5222,6 +5304,7 @@ static void __paginginit free_area_init_core(struct pglist_data *pgdat)
        for (j = 0; j < MAX_NR_ZONES; j++) {
                struct zone *zone = pgdat->node_zones + j;
                unsigned long size, realsize, freesize, memmap_pages;
+               unsigned long zone_start_pfn = zone->zone_start_pfn;
 
                size = zone->spanned_pages;
                realsize = freesize = zone->present_pages;
@@ -5290,7 +5373,6 @@ static void __paginginit free_area_init_core(struct pglist_data *pgdat)
                ret = init_currently_empty_zone(zone, zone_start_pfn, size);
                BUG_ON(ret);
                memmap_init(size, nid, j, zone_start_pfn);
-               zone_start_pfn += size;
        }
 }
 
@@ -5358,6 +5440,8 @@ void __paginginit free_area_init_node(int nid, unsigned long *zones_size,
        pr_info("Initmem setup node %d [mem %#018Lx-%#018Lx]\n", nid,
                (u64)start_pfn << PAGE_SHIFT,
                end_pfn ? ((u64)end_pfn << PAGE_SHIFT) - 1 : 0);
+#else
+       start_pfn = node_start_pfn;
 #endif
        calculate_node_totalpages(pgdat, start_pfn, end_pfn,
                                  zones_size, zholes_size);
@@ -5528,6 +5612,36 @@ static void __init find_zone_movable_pfns_for_nodes(void)
                goto out2;
        }
 
+       /*
+        * If kernelcore=mirror is specified, ignore movablecore option
+        */
+       if (mirrored_kernelcore) {
+               bool mem_below_4gb_not_mirrored = false;
+
+               for_each_memblock(memory, r) {
+                       if (memblock_is_mirror(r))
+                               continue;
+
+                       nid = r->nid;
+
+                       usable_startpfn = memblock_region_memory_base_pfn(r);
+
+                       if (usable_startpfn < 0x100000) {
+                               mem_below_4gb_not_mirrored = true;
+                               continue;
+                       }
+
+                       zone_movable_pfn[nid] = zone_movable_pfn[nid] ?
+                               min(usable_startpfn, zone_movable_pfn[nid]) :
+                               usable_startpfn;
+               }
+
+               if (mem_below_4gb_not_mirrored)
+                       pr_warn("This configuration results in unmirrored kernel memory.");
+
+               goto out2;
+       }
+
        /*
         * If movablecore=nn[KMG] was specified, calculate what size of
         * kernelcore that corresponds so that memory usable for
@@ -5788,6 +5902,12 @@ static int __init cmdline_parse_core(char *p, unsigned long *core)
  */
 static int __init cmdline_parse_kernelcore(char *p)
 {
+       /* parse kernelcore=mirror */
+       if (parse_option_str(p, "mirror")) {
+               mirrored_kernelcore = true;
+               return 0;
+       }
+
        return cmdline_parse_core(p, &required_kernelcore);
 }