*/
 static DEFINE_XARRAY(memory_blocks);
 
+/*
+ * Memory groups, indexed by memory group id (mgid).
+ */
+static DEFINE_XARRAY_FLAGS(memory_groups, XA_FLAGS_ALLOC);
+
 static BLOCKING_NOTIFIER_HEAD(memory_chain);
 
 int register_memory_notifier(struct notifier_block *nb)
 }
 
 static int init_memory_block(unsigned long block_id, unsigned long state,
-                            unsigned long nr_vmemmap_pages)
+                            unsigned long nr_vmemmap_pages,
+                            struct memory_group *group)
 {
        struct memory_block *mem;
        int ret = 0;
        mem->state = state;
        mem->nid = NUMA_NO_NODE;
        mem->nr_vmemmap_pages = nr_vmemmap_pages;
+       INIT_LIST_HEAD(&mem->group_next);
+
+       if (group) {
+               mem->group = group;
+               list_add(&mem->group_next, &group->memory_blocks);
+       }
 
        ret = register_memory(mem);
 
        if (section_count == 0)
                return 0;
        return init_memory_block(memory_block_id(base_section_nr),
-                                MEM_ONLINE, 0);
+                                MEM_ONLINE, 0,  NULL);
 }
 
 static void unregister_memory(struct memory_block *memory)
 
        WARN_ON(xa_erase(&memory_blocks, memory->dev.id) == NULL);
 
+       if (memory->group) {
+               list_del(&memory->group_next);
+               memory->group = NULL;
+       }
+
        /* drop the ref. we got via find_memory_block() */
        put_device(&memory->dev);
        device_unregister(&memory->dev);
  * Called under device_hotplug_lock.
  */
 int create_memory_block_devices(unsigned long start, unsigned long size,
-                               unsigned long vmemmap_pages)
+                               unsigned long vmemmap_pages,
+                               struct memory_group *group)
 {
        const unsigned long start_block_id = pfn_to_block_id(PFN_DOWN(start));
        unsigned long end_block_id = pfn_to_block_id(PFN_DOWN(start + size));
                return -EINVAL;
 
        for (block_id = start_block_id; block_id != end_block_id; block_id++) {
-               ret = init_memory_block(block_id, MEM_OFFLINE, vmemmap_pages);
+               ret = init_memory_block(block_id, MEM_OFFLINE, vmemmap_pages,
+                                       group);
                if (ret)
                        break;
        }
        return bus_for_each_dev(&memory_subsys, NULL, &cb_data,
                                for_each_memory_block_cb);
 }
+
+/*
+ * This is an internal helper to unify allocation and initialization of
+ * memory groups. Note that the passed memory group will be copied to a
+ * dynamically allocated memory group. After this call, the passed
+ * memory group should no longer be used.
+ */
+static int memory_group_register(struct memory_group group)
+{
+       struct memory_group *new_group;
+       uint32_t mgid;
+       int ret;
+
+       if (!node_possible(group.nid))
+               return -EINVAL;
+
+       new_group = kzalloc(sizeof(group), GFP_KERNEL);
+       if (!new_group)
+               return -ENOMEM;
+       *new_group = group;
+       INIT_LIST_HEAD(&new_group->memory_blocks);
+
+       ret = xa_alloc(&memory_groups, &mgid, new_group, xa_limit_31b,
+                      GFP_KERNEL);
+       if (ret) {
+               kfree(new_group);
+               return ret;
+       }
+       return mgid;
+}
+
+/**
+ * memory_group_register_static() - Register a static memory group.
+ * @nid: The node id.
+ * @max_pages: The maximum number of pages we'll have in this static memory
+ *            group.
+ *
+ * Register a new static memory group and return the memory group id.
+ * All memory in the group belongs to a single unit, such as a DIMM. All
+ * memory belonging to a static memory group is added in one go to be removed
+ * in one go -- it's static.
+ *
+ * Returns an error if out of memory, if the node id is invalid, if no new
+ * memory groups can be registered, or if max_pages is invalid (0). Otherwise,
+ * returns the new memory group id.
+ */
+int memory_group_register_static(int nid, unsigned long max_pages)
+{
+       struct memory_group group = {
+               .nid = nid,
+               .s = {
+                       .max_pages = max_pages,
+               },
+       };
+
+       if (!max_pages)
+               return -EINVAL;
+       return memory_group_register(group);
+}
+EXPORT_SYMBOL_GPL(memory_group_register_static);
+
+/**
+ * memory_group_register_dynamic() - Register a dynamic memory group.
+ * @nid: The node id.
+ * @unit_pages: Unit in pages in which is memory added/removed in this dynamic
+ *             memory group.
+ *
+ * Register a new dynamic memory group and return the memory group id.
+ * Memory within a dynamic memory group is added/removed dynamically
+ * in unit_pages.
+ *
+ * Returns an error if out of memory, if the node id is invalid, if no new
+ * memory groups can be registered, or if unit_pages is invalid (0, not a
+ * power of two, smaller than a single memory block). Otherwise, returns the
+ * new memory group id.
+ */
+int memory_group_register_dynamic(int nid, unsigned long unit_pages)
+{
+       struct memory_group group = {
+               .nid = nid,
+               .is_dynamic = true,
+               .d = {
+                       .unit_pages = unit_pages,
+               },
+       };
+
+       if (!unit_pages || !is_power_of_2(unit_pages) ||
+           unit_pages < PHYS_PFN(memory_block_size_bytes()))
+               return -EINVAL;
+       return memory_group_register(group);
+}
+EXPORT_SYMBOL_GPL(memory_group_register_dynamic);
+
+/**
+ * memory_group_unregister() - Unregister a memory group.
+ * @mgid: the memory group id
+ *
+ * Unregister a memory group. If any memory block still belongs to this
+ * memory group, unregistering will fail.
+ *
+ * Returns -EINVAL if the memory group id is invalid, returns -EBUSY if some
+ * memory blocks still belong to this memory group and returns 0 if
+ * unregistering succeeded.
+ */
+int memory_group_unregister(int mgid)
+{
+       struct memory_group *group;
+
+       if (mgid < 0)
+               return -EINVAL;
+
+       group = xa_load(&memory_groups, mgid);
+       if (!group)
+               return -EINVAL;
+       if (!list_empty(&group->memory_blocks))
+               return -EBUSY;
+       xa_erase(&memory_groups, mgid);
+       kfree(group);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(memory_group_unregister);
+
+/*
+ * This is an internal helper only to be used in core memory hotplug code to
+ * lookup a memory group. We don't care about locking, as we don't expect a
+ * memory group to get unregistered while adding memory to it -- because
+ * the group and the memory is managed by the same driver.
+ */
+struct memory_group *memory_group_find_by_id(int mgid)
+{
+       return xa_load(&memory_groups, mgid);
+}
 
 
 #define MIN_MEMORY_BLOCK_SIZE     (1UL << SECTION_SIZE_BITS)
 
+/**
+ * struct memory_group - a logical group of memory blocks
+ * @nid: The node id for all memory blocks inside the memory group.
+ * @blocks: List of all memory blocks belonging to this memory group.
+ * @is_dynamic: The memory group type: static vs. dynamic
+ * @s.max_pages: Valid with &memory_group.is_dynamic == false. The maximum
+ *              number of pages we'll have in this static memory group.
+ * @d.unit_pages: Valid with &memory_group.is_dynamic == true. Unit in pages
+ *               in which memory is added/removed in this dynamic memory group.
+ *               This granularity defines the alignment of a unit in physical
+ *               address space; it has to be at least as big as a single
+ *               memory block.
+ *
+ * A memory group logically groups memory blocks; each memory block
+ * belongs to at most one memory group. A memory group corresponds to
+ * a memory device, such as a DIMM or a NUMA node, which spans multiple
+ * memory blocks and might even span multiple non-contiguous physical memory
+ * ranges.
+ *
+ * Modification of members after registration is serialized by memory
+ * hot(un)plug code.
+ */
+struct memory_group {
+       int nid;
+       struct list_head memory_blocks;
+       bool is_dynamic;
+       union {
+               struct {
+                       unsigned long max_pages;
+               } s;
+               struct {
+                       unsigned long unit_pages;
+               } d;
+       };
+};
+
 struct memory_block {
        unsigned long start_section_nr;
        unsigned long state;            /* serialized by the dev->lock */
         * lay at the beginning of the memory block.
         */
        unsigned long nr_vmemmap_pages;
+       struct memory_group *group;     /* group (if any) for this block */
+       struct list_head group_next;    /* next block inside memory group */
 };
 
 int arch_get_memory_phys_device(unsigned long start_pfn);
 extern int register_memory_notifier(struct notifier_block *nb);
 extern void unregister_memory_notifier(struct notifier_block *nb);
 int create_memory_block_devices(unsigned long start, unsigned long size,
-                               unsigned long vmemmap_pages);
+                               unsigned long vmemmap_pages,
+                               struct memory_group *group);
 void remove_memory_block_devices(unsigned long start, unsigned long size);
 extern void memory_dev_init(void);
 extern int memory_notify(unsigned long val, void *v);
                              void *arg, walk_memory_blocks_func_t func);
 extern int for_each_memory_block(void *arg, walk_memory_blocks_func_t func);
 #define CONFIG_MEM_BLOCK_SIZE  (PAGES_PER_SECTION<<PAGE_SHIFT)
+
+extern int memory_group_register_static(int nid, unsigned long max_pages);
+extern int memory_group_register_dynamic(int nid, unsigned long unit_pages);
+extern int memory_group_unregister(int mgid);
+struct memory_group *memory_group_find_by_id(int mgid);
 #endif /* CONFIG_MEMORY_HOTPLUG_SPARSE */
 
 #ifdef CONFIG_MEMORY_HOTPLUG