diff --git a/Documentation/ABI/testing/sysfs-power b/Documentation/ABI/testing/sysfs-power
index 5e6ead29124c..b33db9816a8c 100644
--- a/Documentation/ABI/testing/sysfs-power
+++ b/Documentation/ABI/testing/sysfs-power
@@ -192,6 +192,44 @@ Description:
 		Reading from this file will display the current value, which is
 		set to 1 MB by default.
 
+What:		/sys/power/mm_reclaim/
+Date:		May 2020
+Contact:	Andrea Righi <andrea.righi@canonical.com>
+Description:
+		The /sys/power/mm_reclaim directory contains all the
+		opportunistic memory reclaim files.
+
+What:		/sys/power/mm_reclaim/run
+Date:		May 2020
+Contact:	Andrea Righi <andrea.righi@canonical.com>
+Description:
+		The /sys/power/mm_reclaim/run file allows user space to trigger
+		opportunistic memory reclaim. When a string representing a
+		non-negative number is written to this file, it will be assumed
+		to represent the amount of pages to be reclaimed (0 is a special
+		value that means "as many pages as possible").
+
+		When opportunistic memory reclaim is started the system will be
+		put into an artificial memory pressure condition and memory
+		will be reclaimed by dropping clean page cache pages, swapping
+		out anonymous pages, etc.
+
+		NOTE: it is possible to interrupt the memory reclaim sending a
+		signal to writer of this file.
+
+What:		/sys/power/mm_reclaim/release
+Date:		May 2020
+Contact:	Andrea Righi <andrea.righi@canonical.com>
+Description:
+		Force swapped out pages to be loaded back to memory. When a
+		string representing a non-negative number is written to this
+		file, it will be assumed to represent the amount of pages to be
+		pulled back to memory from the swap device(s) (0 is a special
+		value that means "as many pages as possible").
+
+		NOTE: it is possible to interrupt the memory release sending a
+		signal to writer of this file.
+
 What:		/sys/power/autosleep
 Date:		April 2012
 Contact:	Rafael J. Wysocki <rjw@rjwysocki.net>
diff --git a/include/linux/swapfile.h b/include/linux/swapfile.h
index ac4d0ccd1f7b..6f4144099958 100644
--- a/include/linux/swapfile.h
+++ b/include/linux/swapfile.h
@@ -9,6 +9,7 @@
 extern spinlock_t swap_lock;
 extern struct plist_head swap_active_head;
 extern struct swap_info_struct *swap_info[];
+extern void swap_unuse(unsigned long pages);
 extern int try_to_unuse_wait(unsigned int type, bool frontswap, bool wait,
 			     unsigned long pages_to_unuse);
 static inline int
diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c
index 30bd28d1d418..caa06eb5a09f 100644
--- a/kernel/power/hibernate.c
+++ b/kernel/power/hibernate.c
@@ -31,6 +31,7 @@
 #include <linux/genhd.h>
 #include <linux/ktime.h>
 #include <linux/security.h>
+#include <linux/swapfile.h>
 #include <trace/events/power.h>
 
 #include "power.h"
@@ -1150,6 +1151,92 @@ static ssize_t reserved_size_store(struct kobject *kobj,
 
 power_attr(reserved_size);
 
+/*
+ * Try to reclaim some memory in the system, stop when one of the following
+ * conditions occurs:
+ *  - at least "nr_pages" have been reclaimed
+ *  - no more pages can be reclaimed
+ *  - current task explicitly interrupted by a signal (e.g., user space
+ *    timeout)
+ *
+ *  @nr_pages - amount of pages to be reclaimed (0 means "as many pages as
+ *  possible").
+ */
+static void do_mm_reclaim(unsigned long nr_pages)
+{
+	while (nr_pages > 0) {
+		unsigned long reclaimed;
+
+		if (signal_pending(current))
+			break;
+		reclaimed = shrink_all_memory(nr_pages);
+		if (!reclaimed)
+			break;
+		nr_pages -= min_t(unsigned long, reclaimed, nr_pages);
+	}
+}
+
+static ssize_t run_show(struct kobject *kobj,
+			struct kobj_attribute *attr, char *buf)
+{
+	return -EINVAL;
+}
+
+static ssize_t run_store(struct kobject *kobj,
+			 struct kobj_attribute *attr,
+			 const char *buf, size_t n)
+{
+	unsigned long nr_pages;
+	int ret;
+
+	ret = kstrtoul(buf, 0, &nr_pages);
+	if (ret)
+		return ret;
+	if (!nr_pages)
+		nr_pages = ULONG_MAX;
+	do_mm_reclaim(nr_pages);
+
+	return n;
+}
+
+power_attr(run);
+
+static ssize_t release_show(struct kobject *kobj,
+			    struct kobj_attribute *attr, char *buf)
+{
+	return -EINVAL;
+}
+
+static ssize_t release_store(struct kobject *kobj,
+			     struct kobj_attribute *attr,
+			     const char *buf, size_t n)
+{
+	unsigned long nr_pages;
+	int ret;
+
+	ret = kstrtoul(buf, 0, &nr_pages);
+	if (ret)
+		return ret;
+	if (!nr_pages)
+		nr_pages = ULONG_MAX;
+	swap_unuse(nr_pages);
+
+	return n;
+}
+
+power_attr(release);
+
+static struct attribute *mm_reclaim_attrs[] = {
+	&run_attr.attr,
+	&release_attr.attr,
+	NULL,
+};
+
+static struct attribute_group mm_reclaim_attr_group = {
+	.name = "mm_reclaim",
+	.attrs = mm_reclaim_attrs,
+};
+
 static struct attribute * g[] = {
 	&disk_attr.attr,
 	&resume_offset_attr.attr,
@@ -1164,10 +1251,15 @@ static const struct attribute_group attr_group = {
 	.attrs = g,
 };
 
+static const struct attribute_group *attr_groups[] = {
+	&attr_group,
+	&mm_reclaim_attr_group,
+	NULL,
+};
 
 static int __init pm_disk_init(void)
 {
-	return sysfs_create_group(power_kobj, &attr_group);
+	return sysfs_create_groups(power_kobj, attr_groups);
 }
 
 core_initcall(pm_disk_init);
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 651471ccf133..7391f122ad73 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -1749,6 +1749,36 @@ int free_swap_and_cache(swp_entry_t entry)
 }
 
 #ifdef CONFIG_HIBERNATION
+/*
+ * Force pages to be pulled back to memory from all swap devices.
+ *
+ * @nr_pages - number of pages to be pulled from all swap devices
+ * (0 = all pages from any swap device).
+ */
+void swap_unuse(unsigned long pages)
+{
+	int type;
+
+	spin_lock(&swap_lock);
+	for (type = 0; type < nr_swapfiles; type++) {
+		struct swap_info_struct *sis = swap_info[type];
+		struct block_device *bdev;
+
+		if (!(sis->flags & SWP_WRITEOK))
+			continue;
+		bdev = bdgrab(sis->bdev);
+		if (!bdev)
+			continue;
+		spin_unlock(&swap_lock);
+
+		try_to_unuse_wait(type, false, false, pages);
+
+		bdput(sis->bdev);
+		spin_lock(&swap_lock);
+	}
+	spin_unlock(&swap_lock);
+}
+
 /*
  * Find the swap type that corresponds to given device (if any).
  *
