@@ -617,10 +617,10 @@ static inline swp_entry_t get_swap_page(struct page *page)
#endif /* CONFIG_SWAP */
#ifdef CONFIG_THP_SWAP
-extern int split_swap_cluster(swp_entry_t entry);
+extern int split_swap_cluster(swp_entry_t entry, bool force);
extern int split_swap_cluster_map(swp_entry_t entry);
#else
-static inline int split_swap_cluster(swp_entry_t entry)
+static inline int split_swap_cluster(swp_entry_t entry, bool force)
{
return 0;
}
@@ -2502,6 +2502,17 @@ static void __split_huge_page(struct page *page, struct list_head *list,
unfreeze_page(head);
+ /*
+ * Split swap cluster before unlocking sub-pages. So all
+ * sub-pages will be kept locked from THP has been split to
+ * swap cluster is split.
+ */
+ if (PageSwapCache(head)) {
+ swp_entry_t entry = { .val = page_private(head) };
+
+ split_swap_cluster(entry, true);
+ }
+
for (i = 0; i < HPAGE_PMD_NR; i++) {
struct page *subpage = head + i;
if (subpage == page)
@@ -2728,12 +2739,7 @@ int split_huge_page_to_list(struct page *page, struct list_head *list)
__dec_node_page_state(page, NR_SHMEM_THPS);
spin_unlock(&pgdata->split_queue_lock);
__split_huge_page(page, list, flags);
- if (PageSwapCache(head)) {
- swp_entry_t entry = { .val = page_private(head) };
-
- ret = split_swap_cluster(entry);
- } else
- ret = 0;
+ ret = 0;
} else {
if (IS_ENABLED(CONFIG_DEBUG_VM) && mapcount) {
pr_alert("total_mapcount: %u, page_count(): %u\n",
@@ -1414,21 +1414,6 @@ static void swapcache_free_cluster(swp_entry_t entry)
}
}
}
-
-int split_swap_cluster(swp_entry_t entry)
-{
- struct swap_info_struct *si;
- struct swap_cluster_info *ci;
- unsigned long offset = swp_offset(entry);
-
- si = _swap_info_get(entry);
- if (!si)
- return -EBUSY;
- ci = lock_cluster(si, offset);
- cluster_clear_huge(ci);
- unlock_cluster(ci);
- return 0;
-}
#else
static inline void swapcache_free_cluster(swp_entry_t entry)
{
@@ -4072,6 +4057,36 @@ int split_swap_cluster_map(swp_entry_t entry)
unlock_cluster(ci);
return 0;
}
+
+int split_swap_cluster(swp_entry_t entry, bool force)
+{
+ struct swap_info_struct *si;
+ struct swap_cluster_info *ci;
+ unsigned long offset = swp_offset(entry);
+ int ret = 0;
+
+ si = get_swap_device(entry);
+ if (!si)
+ return -EINVAL;
+ ci = lock_cluster(si, offset);
+ /* The swap cluster has been split by someone else */
+ if (!cluster_is_huge(ci))
+ goto out;
+ VM_BUG_ON(!is_cluster_offset(offset));
+ VM_BUG_ON(cluster_count(ci) < SWAPFILE_CLUSTER);
+ /* If not forced, don't split swap cluster has swap cache */
+ if (!force && si->swap_map[offset] & SWAP_HAS_CACHE) {
+ ret = -EEXIST;
+ goto out;
+ }
+ cluster_set_count(ci, SWAPFILE_CLUSTER);
+ cluster_clear_huge(ci);
+
+out:
+ unlock_cluster(ci);
+ put_swap_device(si);
+ return ret;
+}
#endif
static int __init swapfile_init(void)