mm: sparsemem memory_present() fix
[sfrench/cifs-2.6.git] / mm / sparse.c
index 54f3940406cba1e6bf980e273e171c856296d148..98d6b39c34722c1734bee587a612ce0f74d7fbfa 100644 (file)
@@ -9,6 +9,8 @@
 #include <linux/spinlock.h>
 #include <linux/vmalloc.h>
 #include <asm/dma.h>
+#include <asm/pgalloc.h>
+#include <asm/pgtable.h>
 
 /*
  * Permanent SPARSEMEM data:
@@ -81,6 +83,8 @@ static int __meminit sparse_index_init(unsigned long section_nr, int nid)
                return -EEXIST;
 
        section = sparse_index_alloc(nid);
+       if (!section)
+               return -ENOMEM;
        /*
         * This lock keeps two different sections from
         * reallocating for the same index
@@ -145,8 +149,18 @@ static inline int sparse_early_nid(struct mem_section *section)
 /* Record a memory area against a node. */
 void __init memory_present(int nid, unsigned long start, unsigned long end)
 {
+       unsigned long max_arch_pfn = 1UL << (MAX_PHYSMEM_BITS-PAGE_SHIFT);
        unsigned long pfn;
 
+       /*
+        * Sanity checks - do not allow an architecture to pass
+        * in larger pfns than the maximum scope of sparsemem:
+        */
+       if (start >= max_arch_pfn)
+               return;
+       if (end >= max_arch_pfn)
+               end = max_arch_pfn;
+
        start &= PAGE_SECTION_MASK;
        for (pfn = start; pfn < end; pfn += PAGES_PER_SECTION) {
                unsigned long section = pfn_to_section_nr(pfn);
@@ -204,7 +218,8 @@ struct page *sparse_decode_mem_map(unsigned long coded_mem_map, unsigned long pn
 }
 
 static int __meminit sparse_init_one_section(struct mem_section *ms,
-               unsigned long pnum, struct page *mem_map)
+               unsigned long pnum, struct page *mem_map,
+               unsigned long *pageblock_bitmap)
 {
        if (!present_section(ms))
                return -EINVAL;
@@ -212,37 +227,70 @@ static int __meminit sparse_init_one_section(struct mem_section *ms,
        ms->section_mem_map &= ~SECTION_MAP_MASK;
        ms->section_mem_map |= sparse_encode_mem_map(mem_map, pnum) |
                                                        SECTION_HAS_MEM_MAP;
+       ms->pageblock_flags = pageblock_bitmap;
 
        return 1;
 }
 
-__attribute__((weak)) __init
-void *alloc_bootmem_high_node(pg_data_t *pgdat, unsigned long size)
+static unsigned long usemap_size(void)
 {
-       return NULL;
+       unsigned long size_bytes;
+       size_bytes = roundup(SECTION_BLOCKFLAGS_BITS, 8) / 8;
+       size_bytes = roundup(size_bytes, sizeof(unsigned long));
+       return size_bytes;
 }
 
-static struct page __init *sparse_early_mem_map_alloc(unsigned long pnum)
+#ifdef CONFIG_MEMORY_HOTPLUG
+static unsigned long *__kmalloc_section_usemap(void)
 {
-       struct page *map;
+       return kmalloc(usemap_size(), GFP_KERNEL);
+}
+#endif /* CONFIG_MEMORY_HOTPLUG */
+
+static unsigned long *__init sparse_early_usemap_alloc(unsigned long pnum)
+{
+       unsigned long *usemap;
        struct mem_section *ms = __nr_to_section(pnum);
        int nid = sparse_early_nid(ms);
 
-       map = alloc_remap(nid, sizeof(struct page) * PAGES_PER_SECTION);
-       if (map)
-               return map;
+       usemap = alloc_bootmem_node(NODE_DATA(nid), usemap_size());
+       if (usemap)
+               return usemap;
+
+       /* Stupid: suppress gcc warning for SPARSEMEM && !NUMA */
+       nid = 0;
+
+       printk(KERN_WARNING "%s: allocation failed\n", __FUNCTION__);
+       return NULL;
+}
 
-       map = alloc_bootmem_high_node(NODE_DATA(nid),
-                       sizeof(struct page) * PAGES_PER_SECTION);
+#ifndef CONFIG_SPARSEMEM_VMEMMAP
+struct page __init *sparse_mem_map_populate(unsigned long pnum, int nid)
+{
+       struct page *map;
+
+       map = alloc_remap(nid, sizeof(struct page) * PAGES_PER_SECTION);
        if (map)
                return map;
 
        map = alloc_bootmem_node(NODE_DATA(nid),
                        sizeof(struct page) * PAGES_PER_SECTION);
+       return map;
+}
+#endif /* !CONFIG_SPARSEMEM_VMEMMAP */
+
+struct page __init *sparse_early_mem_map_alloc(unsigned long pnum)
+{
+       struct page *map;
+       struct mem_section *ms = __nr_to_section(pnum);
+       int nid = sparse_early_nid(ms);
+
+       map = sparse_mem_map_populate(pnum, nid);
        if (map)
                return map;
 
-       printk(KERN_WARNING "%s: allocation failed\n", __FUNCTION__);
+       printk(KERN_ERR "%s: sparsemem memory map backing failed "
+                       "some memory will not be available.\n", __FUNCTION__);
        ms->section_mem_map = 0;
        return NULL;
 }
@@ -255,6 +303,7 @@ void __init sparse_init(void)
 {
        unsigned long pnum;
        struct page *map;
+       unsigned long *usemap;
 
        for (pnum = 0; pnum < NR_MEM_SECTIONS; pnum++) {
                if (!present_section_nr(pnum))
@@ -263,11 +312,29 @@ void __init sparse_init(void)
                map = sparse_early_mem_map_alloc(pnum);
                if (!map)
                        continue;
-               sparse_init_one_section(__nr_to_section(pnum), pnum, map);
+
+               usemap = sparse_early_usemap_alloc(pnum);
+               if (!usemap)
+                       continue;
+
+               sparse_init_one_section(__nr_to_section(pnum), pnum, map,
+                                                               usemap);
        }
 }
 
 #ifdef CONFIG_MEMORY_HOTPLUG
+#ifdef CONFIG_SPARSEMEM_VMEMMAP
+static inline struct page *kmalloc_section_memmap(unsigned long pnum, int nid,
+                                                unsigned long nr_pages)
+{
+       /* This will make the necessary allocations eventually. */
+       return sparse_mem_map_populate(pnum, nid);
+}
+static void __kfree_section_memmap(struct page *memmap, unsigned long nr_pages)
+{
+       return; /* XXX: Not implemented yet */
+}
+#else
 static struct page *__kmalloc_section_memmap(unsigned long nr_pages)
 {
        struct page *page, *ret;
@@ -290,22 +357,21 @@ got_map_ptr:
        return ret;
 }
 
-static int vaddr_in_vmalloc_area(void *addr)
+static inline struct page *kmalloc_section_memmap(unsigned long pnum, int nid,
+                                                 unsigned long nr_pages)
 {
-       if (addr >= (void *)VMALLOC_START &&
-           addr < (void *)VMALLOC_END)
-               return 1;
-       return 0;
+       return __kmalloc_section_memmap(nr_pages);
 }
 
 static void __kfree_section_memmap(struct page *memmap, unsigned long nr_pages)
 {
-       if (vaddr_in_vmalloc_area(memmap))
+       if (is_vmalloc_addr(memmap))
                vfree(memmap);
        else
                free_pages((unsigned long)memmap,
                           get_order(sizeof(struct page) * nr_pages));
 }
+#endif /* CONFIG_SPARSEMEM_VMEMMAP */
 
 /*
  * returns the number of sections whose mem_maps were properly
@@ -319,6 +385,7 @@ int sparse_add_one_section(struct zone *zone, unsigned long start_pfn,
        struct pglist_data *pgdat = zone->zone_pgdat;
        struct mem_section *ms;
        struct page *memmap;
+       unsigned long *usemap;
        unsigned long flags;
        int ret;
 
@@ -326,8 +393,17 @@ int sparse_add_one_section(struct zone *zone, unsigned long start_pfn,
         * no locking for this, because it does its own
         * plus, it does a kmalloc
         */
-       sparse_index_init(section_nr, pgdat->node_id);
-       memmap = __kmalloc_section_memmap(nr_pages);
+       ret = sparse_index_init(section_nr, pgdat->node_id);
+       if (ret < 0 && ret != -EEXIST)
+               return ret;
+       memmap = kmalloc_section_memmap(section_nr, pgdat->node_id, nr_pages);
+       if (!memmap)
+               return -ENOMEM;
+       usemap = __kmalloc_section_usemap();
+       if (!usemap) {
+               __kfree_section_memmap(memmap, nr_pages);
+               return -ENOMEM;
+       }
 
        pgdat_resize_lock(pgdat, &flags);
 
@@ -336,14 +412,17 @@ int sparse_add_one_section(struct zone *zone, unsigned long start_pfn,
                ret = -EEXIST;
                goto out;
        }
+
        ms->section_mem_map |= SECTION_MARKED_PRESENT;
 
-       ret = sparse_init_one_section(ms, section_nr, memmap);
+       ret = sparse_init_one_section(ms, section_nr, memmap, usemap);
 
 out:
        pgdat_resize_unlock(pgdat, &flags);
-       if (ret <= 0)
+       if (ret <= 0) {
+               kfree(usemap);
                __kfree_section_memmap(memmap, nr_pages);
+       }
        return ret;
 }
 #endif