@@ -2349,6 +2349,7 @@ static void __split_huge_page_tail(struct page *head, int tail,
prep_compound_page(page_tail, new_order);
prep_transhuge_page(page_tail);
}
+ VM_BUG_ON_PAGE(PageTail(page_tail), page_tail);
/* Finally unfreeze refcount. Additional reference from page cache. */
page_ref_unfreeze(page_tail, 1 + ((!PageAnon(head) ||
@@ -197,6 +197,14 @@ int truncate_inode_folio(struct address_space *mapping, struct folio *folio)
return 0;
}
+static unsigned int greatest_pow_of_two_multiplier(unsigned int num)
+{
+ if (num & 1)
+ return 0;
+ return min_t(unsigned int, ilog2(num),
+ ilog2(num - rounddown_pow_of_two(num)));
+}
+
/*
* Handle partial folios. The folio may be entirely within the
* range if a split has raced with us. If not, we zero the part of the
@@ -211,7 +219,8 @@ int truncate_inode_folio(struct address_space *mapping, struct folio *folio)
bool truncate_inode_partial_folio(struct folio *folio, loff_t start, loff_t end)
{
loff_t pos = folio_pos(folio);
- unsigned int offset, length;
+ unsigned int offset, length, remaining;
+ unsigned int new_order = folio_order(folio);
if (pos < start)
offset = start - pos;
@@ -222,6 +231,7 @@ bool truncate_inode_partial_folio(struct folio *folio, loff_t start, loff_t end)
length = length - offset;
else
length = end + 1 - pos - offset;
+ remaining = folio_size(folio) - offset - length;
folio_wait_writeback(folio);
if (length == folio_size(folio)) {
@@ -236,11 +246,30 @@ bool truncate_inode_partial_folio(struct folio *folio, loff_t start, loff_t end)
*/
folio_zero_range(folio, offset, length);
+ /*
+ * Find the greatest common power of two multiplier of the non-zero
+ * offset, length, and remaining as the new order. So we can truncate
+ * a subpage as large as possible.
+ */
+ if (offset)
+ new_order = greatest_pow_of_two_multiplier(offset / PAGE_SIZE);
+ if (length)
+ new_order = min_t(unsigned int, new_order,
+ greatest_pow_of_two_multiplier(length / PAGE_SIZE));
+ if (remaining)
+ new_order = min_t(unsigned int, new_order,
+ greatest_pow_of_two_multiplier(remaining / PAGE_SIZE));
+
+ /* order-1 THP not supported, downgrade to order-0 */
+ if (new_order == 1)
+ new_order = 0;
+
+
if (folio_has_private(folio))
folio_invalidate(folio, offset, length);
if (!folio_test_large(folio))
return true;
- if (split_huge_page(&folio->page) == 0)
+ if (split_huge_page_to_list_to_order(&folio->page, NULL, new_order) == 0)
return true;
if (folio_test_dirty(folio))
return false;