|
@@ -46,19 +46,84 @@
|
|
|
static void generic_online_page(struct page *page);
|
|
|
|
|
|
static online_page_callback_t online_page_callback = generic_online_page;
|
|
|
+static DEFINE_MUTEX(online_page_callback_lock);
|
|
|
|
|
|
-DEFINE_MUTEX(mem_hotplug_mutex);
|
|
|
+/* The same as the cpu_hotplug lock, but for memory hotplug. */
|
|
|
+static struct {
|
|
|
+ struct task_struct *active_writer;
|
|
|
+ struct mutex lock; /* Synchronizes accesses to refcount, */
|
|
|
+ /*
|
|
|
+ * Also blocks the new readers during
|
|
|
+ * an ongoing mem hotplug operation.
|
|
|
+ */
|
|
|
+ int refcount;
|
|
|
+
|
|
|
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
|
|
+ struct lockdep_map dep_map;
|
|
|
+#endif
|
|
|
+} mem_hotplug = {
|
|
|
+ .active_writer = NULL,
|
|
|
+ .lock = __MUTEX_INITIALIZER(mem_hotplug.lock),
|
|
|
+ .refcount = 0,
|
|
|
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
|
|
+ .dep_map = {.name = "mem_hotplug.lock" },
|
|
|
+#endif
|
|
|
+};
|
|
|
+
|
|
|
+/* Lockdep annotations for get/put_online_mems() and mem_hotplug_begin/end() */
|
|
|
+#define memhp_lock_acquire_read() lock_map_acquire_read(&mem_hotplug.dep_map)
|
|
|
+#define memhp_lock_acquire() lock_map_acquire(&mem_hotplug.dep_map)
|
|
|
+#define memhp_lock_release() lock_map_release(&mem_hotplug.dep_map)
|
|
|
+
|
|
|
+void get_online_mems(void)
|
|
|
+{
|
|
|
+ might_sleep();
|
|
|
+ if (mem_hotplug.active_writer == current)
|
|
|
+ return;
|
|
|
+ memhp_lock_acquire_read();
|
|
|
+ mutex_lock(&mem_hotplug.lock);
|
|
|
+ mem_hotplug.refcount++;
|
|
|
+ mutex_unlock(&mem_hotplug.lock);
|
|
|
+
|
|
|
+}
|
|
|
|
|
|
-void lock_memory_hotplug(void)
|
|
|
+void put_online_mems(void)
|
|
|
{
|
|
|
- mutex_lock(&mem_hotplug_mutex);
|
|
|
+ if (mem_hotplug.active_writer == current)
|
|
|
+ return;
|
|
|
+ mutex_lock(&mem_hotplug.lock);
|
|
|
+
|
|
|
+ if (WARN_ON(!mem_hotplug.refcount))
|
|
|
+ mem_hotplug.refcount++; /* try to fix things up */
|
|
|
+
|
|
|
+ if (!--mem_hotplug.refcount && unlikely(mem_hotplug.active_writer))
|
|
|
+ wake_up_process(mem_hotplug.active_writer);
|
|
|
+ mutex_unlock(&mem_hotplug.lock);
|
|
|
+ memhp_lock_release();
|
|
|
+
|
|
|
}
|
|
|
|
|
|
-void unlock_memory_hotplug(void)
|
|
|
+static void mem_hotplug_begin(void)
|
|
|
{
|
|
|
- mutex_unlock(&mem_hotplug_mutex);
|
|
|
+ mem_hotplug.active_writer = current;
|
|
|
+
|
|
|
+ memhp_lock_acquire();
|
|
|
+ for (;;) {
|
|
|
+ mutex_lock(&mem_hotplug.lock);
|
|
|
+ if (likely(!mem_hotplug.refcount))
|
|
|
+ break;
|
|
|
+ __set_current_state(TASK_UNINTERRUPTIBLE);
|
|
|
+ mutex_unlock(&mem_hotplug.lock);
|
|
|
+ schedule();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+static void mem_hotplug_done(void)
|
|
|
+{
|
|
|
+ mem_hotplug.active_writer = NULL;
|
|
|
+ mutex_unlock(&mem_hotplug.lock);
|
|
|
+ memhp_lock_release();
|
|
|
+}
|
|
|
|
|
|
/* add this memory to iomem resource */
|
|
|
static struct resource *register_memory_resource(u64 start, u64 size)
|
|
@@ -727,14 +792,16 @@ int set_online_page_callback(online_page_callback_t callback)
|
|
|
{
|
|
|
int rc = -EINVAL;
|
|
|
|
|
|
- lock_memory_hotplug();
|
|
|
+ get_online_mems();
|
|
|
+ mutex_lock(&online_page_callback_lock);
|
|
|
|
|
|
if (online_page_callback == generic_online_page) {
|
|
|
online_page_callback = callback;
|
|
|
rc = 0;
|
|
|
}
|
|
|
|
|
|
- unlock_memory_hotplug();
|
|
|
+ mutex_unlock(&online_page_callback_lock);
|
|
|
+ put_online_mems();
|
|
|
|
|
|
return rc;
|
|
|
}
|
|
@@ -744,14 +811,16 @@ int restore_online_page_callback(online_page_callback_t callback)
|
|
|
{
|
|
|
int rc = -EINVAL;
|
|
|
|
|
|
- lock_memory_hotplug();
|
|
|
+ get_online_mems();
|
|
|
+ mutex_lock(&online_page_callback_lock);
|
|
|
|
|
|
if (online_page_callback == callback) {
|
|
|
online_page_callback = generic_online_page;
|
|
|
rc = 0;
|
|
|
}
|
|
|
|
|
|
- unlock_memory_hotplug();
|
|
|
+ mutex_unlock(&online_page_callback_lock);
|
|
|
+ put_online_mems();
|
|
|
|
|
|
return rc;
|
|
|
}
|
|
@@ -899,7 +968,7 @@ int __ref online_pages(unsigned long pfn, unsigned long nr_pages, int online_typ
|
|
|
int ret;
|
|
|
struct memory_notify arg;
|
|
|
|
|
|
- lock_memory_hotplug();
|
|
|
+ mem_hotplug_begin();
|
|
|
/*
|
|
|
* This doesn't need a lock to do pfn_to_page().
|
|
|
* The section can't be removed here because of the
|
|
@@ -907,23 +976,18 @@ int __ref online_pages(unsigned long pfn, unsigned long nr_pages, int online_typ
|
|
|
*/
|
|
|
zone = page_zone(pfn_to_page(pfn));
|
|
|
|
|
|
+ ret = -EINVAL;
|
|
|
if ((zone_idx(zone) > ZONE_NORMAL || online_type == ONLINE_MOVABLE) &&
|
|
|
- !can_online_high_movable(zone)) {
|
|
|
- unlock_memory_hotplug();
|
|
|
- return -EINVAL;
|
|
|
- }
|
|
|
+ !can_online_high_movable(zone))
|
|
|
+ goto out;
|
|
|
|
|
|
if (online_type == ONLINE_KERNEL && zone_idx(zone) == ZONE_MOVABLE) {
|
|
|
- if (move_pfn_range_left(zone - 1, zone, pfn, pfn + nr_pages)) {
|
|
|
- unlock_memory_hotplug();
|
|
|
- return -EINVAL;
|
|
|
- }
|
|
|
+ if (move_pfn_range_left(zone - 1, zone, pfn, pfn + nr_pages))
|
|
|
+ goto out;
|
|
|
}
|
|
|
if (online_type == ONLINE_MOVABLE && zone_idx(zone) == ZONE_MOVABLE - 1) {
|
|
|
- if (move_pfn_range_right(zone, zone + 1, pfn, pfn + nr_pages)) {
|
|
|
- unlock_memory_hotplug();
|
|
|
- return -EINVAL;
|
|
|
- }
|
|
|
+ if (move_pfn_range_right(zone, zone + 1, pfn, pfn + nr_pages))
|
|
|
+ goto out;
|
|
|
}
|
|
|
|
|
|
/* Previous code may changed the zone of the pfn range */
|
|
@@ -939,8 +1003,7 @@ int __ref online_pages(unsigned long pfn, unsigned long nr_pages, int online_typ
|
|
|
ret = notifier_to_errno(ret);
|
|
|
if (ret) {
|
|
|
memory_notify(MEM_CANCEL_ONLINE, &arg);
|
|
|
- unlock_memory_hotplug();
|
|
|
- return ret;
|
|
|
+ goto out;
|
|
|
}
|
|
|
/*
|
|
|
* If this zone is not populated, then it is not in zonelist.
|
|
@@ -964,8 +1027,7 @@ int __ref online_pages(unsigned long pfn, unsigned long nr_pages, int online_typ
|
|
|
(((unsigned long long) pfn + nr_pages)
|
|
|
<< PAGE_SHIFT) - 1);
|
|
|
memory_notify(MEM_CANCEL_ONLINE, &arg);
|
|
|
- unlock_memory_hotplug();
|
|
|
- return ret;
|
|
|
+ goto out;
|
|
|
}
|
|
|
|
|
|
zone->present_pages += onlined_pages;
|
|
@@ -995,9 +1057,9 @@ int __ref online_pages(unsigned long pfn, unsigned long nr_pages, int online_typ
|
|
|
|
|
|
if (onlined_pages)
|
|
|
memory_notify(MEM_ONLINE, &arg);
|
|
|
- unlock_memory_hotplug();
|
|
|
-
|
|
|
- return 0;
|
|
|
+out:
|
|
|
+ mem_hotplug_done();
|
|
|
+ return ret;
|
|
|
}
|
|
|
#endif /* CONFIG_MEMORY_HOTPLUG_SPARSE */
|
|
|
|
|
@@ -1055,7 +1117,7 @@ int try_online_node(int nid)
|
|
|
if (node_online(nid))
|
|
|
return 0;
|
|
|
|
|
|
- lock_memory_hotplug();
|
|
|
+ mem_hotplug_begin();
|
|
|
pgdat = hotadd_new_pgdat(nid, 0);
|
|
|
if (!pgdat) {
|
|
|
pr_err("Cannot online node %d due to NULL pgdat\n", nid);
|
|
@@ -1073,7 +1135,7 @@ int try_online_node(int nid)
|
|
|
}
|
|
|
|
|
|
out:
|
|
|
- unlock_memory_hotplug();
|
|
|
+ mem_hotplug_done();
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
@@ -1117,7 +1179,7 @@ int __ref add_memory(int nid, u64 start, u64 size)
|
|
|
new_pgdat = !p;
|
|
|
}
|
|
|
|
|
|
- lock_memory_hotplug();
|
|
|
+ mem_hotplug_begin();
|
|
|
|
|
|
new_node = !node_online(nid);
|
|
|
if (new_node) {
|
|
@@ -1158,7 +1220,7 @@ error:
|
|
|
release_memory_resource(res);
|
|
|
|
|
|
out:
|
|
|
- unlock_memory_hotplug();
|
|
|
+ mem_hotplug_done();
|
|
|
return ret;
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(add_memory);
|
|
@@ -1565,7 +1627,7 @@ static int __ref __offline_pages(unsigned long start_pfn,
|
|
|
if (!test_pages_in_a_zone(start_pfn, end_pfn))
|
|
|
return -EINVAL;
|
|
|
|
|
|
- lock_memory_hotplug();
|
|
|
+ mem_hotplug_begin();
|
|
|
|
|
|
zone = page_zone(pfn_to_page(start_pfn));
|
|
|
node = zone_to_nid(zone);
|
|
@@ -1672,7 +1734,7 @@ repeat:
|
|
|
writeback_set_ratelimit();
|
|
|
|
|
|
memory_notify(MEM_OFFLINE, &arg);
|
|
|
- unlock_memory_hotplug();
|
|
|
+ mem_hotplug_done();
|
|
|
return 0;
|
|
|
|
|
|
failed_removal:
|
|
@@ -1684,7 +1746,7 @@ failed_removal:
|
|
|
undo_isolate_page_range(start_pfn, end_pfn, MIGRATE_MOVABLE);
|
|
|
|
|
|
out:
|
|
|
- unlock_memory_hotplug();
|
|
|
+ mem_hotplug_done();
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
@@ -1888,7 +1950,7 @@ void __ref remove_memory(int nid, u64 start, u64 size)
|
|
|
|
|
|
BUG_ON(check_hotplug_memory_range(start, size));
|
|
|
|
|
|
- lock_memory_hotplug();
|
|
|
+ mem_hotplug_begin();
|
|
|
|
|
|
/*
|
|
|
* All memory blocks must be offlined before removing memory. Check
|
|
@@ -1897,10 +1959,8 @@ void __ref remove_memory(int nid, u64 start, u64 size)
|
|
|
*/
|
|
|
ret = walk_memory_range(PFN_DOWN(start), PFN_UP(start + size - 1), NULL,
|
|
|
check_memblock_offlined_cb);
|
|
|
- if (ret) {
|
|
|
- unlock_memory_hotplug();
|
|
|
+ if (ret)
|
|
|
BUG();
|
|
|
- }
|
|
|
|
|
|
/* remove memmap entry */
|
|
|
firmware_map_remove(start, start + size, "System RAM");
|
|
@@ -1909,7 +1969,7 @@ void __ref remove_memory(int nid, u64 start, u64 size)
|
|
|
|
|
|
try_offline_node(nid);
|
|
|
|
|
|
- unlock_memory_hotplug();
|
|
|
+ mem_hotplug_done();
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(remove_memory);
|
|
|
#endif /* CONFIG_MEMORY_HOTREMOVE */
|