min_order_for_split() returns -EBUSY when the folio is truncated and cannot
be split. In commit 77008e1b2ef7 ("mm/huge_memory: do not change
split_huge_page*() target order silently"), memory_failure() does not
handle it and pass -EBUSY to try_to_split_thp_page() directly.
try_to_split_thp_page() returns -EINVAL since -EBUSY becomes 0xfffffff0 as
new_order is unsigned int in __folio_split() and this large new_order is
rejected as an invalid input. The code does not cause a bug.
soft_offline_in_use_page() also uses min_order_for_split() but it always
passes 0 as new_order for split.
Handle it properly by checking min_order_for_split() return value and not
calling try_to_split_thp_page() if the value is negative. Add a comment
in soft_offline_in_use_page() to clarify the possible negative new_order
value.
Signed-off-by: Zi Yan <ziy@nvidia.com>
---
mm/memory-failure.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 7f908ad795ad..86582f030159 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -2437,8 +2437,11 @@ int memory_failure(unsigned long pfn, int flags)
* or unhandlable page. The refcount is bumped iff the
* page is a valid handlable page.
*/
- folio_set_has_hwpoisoned(folio);
- err = try_to_split_thp_page(p, new_order, /* release= */ false);
+ if (new_order >= 0) {
+ folio_set_has_hwpoisoned(folio);
+ err = try_to_split_thp_page(p, new_order, /* release= */ false);
+ } else
+ err = new_order;
/*
* If splitting a folio to order-0 fails, kill the process.
* Split the folio regardless to minimize unusable pages.
@@ -2779,6 +2782,7 @@ static int soft_offline_in_use_page(struct page *page)
/*
* If new_order (target split order) is not 0, do not split the
* folio at all to retain the still accessible large folio.
+ * new_order can be -EBUSY, meaning the folio cannot be split.
* NOTE: if minimizing the number of soft offline pages is
* preferred, split it to non-zero new_order like it is done in
* memory_failure().
--
2.51.0
On 11/20/25 04:59, Zi Yan wrote:
> min_order_for_split() returns -EBUSY when the folio is truncated and cannot
> be split. In commit 77008e1b2ef7 ("mm/huge_memory: do not change
> split_huge_page*() target order silently"), memory_failure() does not
> handle it and pass -EBUSY to try_to_split_thp_page() directly.
> try_to_split_thp_page() returns -EINVAL since -EBUSY becomes 0xfffffff0 as
I'm wondering whether we should change min_order_for_split() to something like:
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 7c69572b6c3f5..34eb6fec9a059 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4210,16 +4210,19 @@ int folio_split(struct folio *folio, unsigned int new_order,
SPLIT_TYPE_NON_UNIFORM);
}
-int min_order_for_split(struct folio *folio)
+unsigned int min_order_for_split(struct folio *folio)
{
if (folio_test_anon(folio))
return 0;
+ /*
+ * If the folio got truncated, we don't know the previous mapping and
+ * consequently the old min order. But it doesn't matter, as any split
+ * attempt will immediately fail with -EBUSY as the folio cannot get
+ * split until freed.
+ */
if (!folio->mapping) {
- if (folio_test_pmd_mappable(folio))
- count_vm_event(THP_SPLIT_PAGE_FAILED);
- return -EBUSY;
- }
+ return 0;
return mapping_min_folio_order(folio->mapping);
}
--
Cheers
David
On 20 Nov 2025, at 4:37, David Hildenbrand (Red Hat) wrote:
> On 11/20/25 04:59, Zi Yan wrote:
>> min_order_for_split() returns -EBUSY when the folio is truncated and cannot
>> be split. In commit 77008e1b2ef7 ("mm/huge_memory: do not change
>> split_huge_page*() target order silently"), memory_failure() does not
>> handle it and pass -EBUSY to try_to_split_thp_page() directly.
>> try_to_split_thp_page() returns -EINVAL since -EBUSY becomes 0xfffffff0 as
>
> I'm wondering whether we should change min_order_for_split() to something like:
>
>
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 7c69572b6c3f5..34eb6fec9a059 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -4210,16 +4210,19 @@ int folio_split(struct folio *folio, unsigned int new_order,
> SPLIT_TYPE_NON_UNIFORM);
> }
> -int min_order_for_split(struct folio *folio)
> +unsigned int min_order_for_split(struct folio *folio)
> {
> if (folio_test_anon(folio))
> return 0;
> + /*
> + * If the folio got truncated, we don't know the previous mapping and
> + * consequently the old min order. But it doesn't matter, as any split
> + * attempt will immediately fail with -EBUSY as the folio cannot get
> + * split until freed.
> + */
> if (!folio->mapping) {
> - if (folio_test_pmd_mappable(folio))
> - count_vm_event(THP_SPLIT_PAGE_FAILED);
> - return -EBUSY;
> - }
> + return 0;
> return mapping_min_folio_order(folio->mapping);
> }
I thought about it. My concern was that what if the returned order is not
immediately used for split, maybe for some calculation. I might think too much.
Your approach is much simpler.
I am also going to add a kernel-doc and change the return type to unsigned int:
diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
index 0d55354e3a34..e0731e01df27 100644
--- a/include/linux/huge_mm.h
+++ b/include/linux/huge_mm.h
@@ -373,7 +373,7 @@ bool can_split_folio(struct folio *folio, int caller_pins, int *pextra_pins);
int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,
unsigned int new_order);
int folio_split_unmapped(struct folio *folio, unsigned int new_order);
-int min_order_for_split(struct folio *folio);
+unsigned int min_order_for_split(struct folio *folio);
int split_folio_to_list(struct folio *folio, struct list_head *list);
bool folio_split_supported(struct folio *folio, unsigned int new_order,
enum split_type split_type, bool warns);
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 23239c19b36e..f45560b53210 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4238,7 +4238,18 @@ int folio_split(struct folio *folio, unsigned int new_order,
SPLIT_TYPE_NON_UNIFORM);
}
-int min_order_for_split(struct folio *folio)
+/**
+ * min_order_for_split() - get the minimum order @folio can be split to
+ * @folio: folio to split
+ *
+ * min_order_for_split() tells the minimum order @folio can be split to.
+ * Anonymous folios can be split to order 0, file-backed folios might have
+ * limitations at file system level. If the folio is truncated, 0 will be
+ * returned and any split attempt will get -EBUSY.
+ *
+ * Return: @folio's minimum order
+ */
+unsigned int min_order_for_split(struct folio *folio)
{
if (folio_test_anon(folio))
return 0;
Best Regards,
Yan, Zi
On 11/20/25 14:59, Zi Yan wrote:
> min_order_for_split() returns -EBUSY when the folio is truncated and cannot
> be split. In commit 77008e1b2ef7 ("mm/huge_memory: do not change
> split_huge_page*() target order silently"), memory_failure() does not
> handle it and pass -EBUSY to try_to_split_thp_page() directly.
> try_to_split_thp_page() returns -EINVAL since -EBUSY becomes 0xfffffff0 as
> new_order is unsigned int in __folio_split() and this large new_order is
> rejected as an invalid input. The code does not cause a bug.
> soft_offline_in_use_page() also uses min_order_for_split() but it always
> passes 0 as new_order for split.
>
> Handle it properly by checking min_order_for_split() return value and not
> calling try_to_split_thp_page() if the value is negative. Add a comment
> in soft_offline_in_use_page() to clarify the possible negative new_order
> value.
>
> Signed-off-by: Zi Yan <ziy@nvidia.com>
> ---
> mm/memory-failure.c | 8 ++++++--
> 1 file changed, 6 insertions(+), 2 deletions(-)
>
> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> index 7f908ad795ad..86582f030159 100644
> --- a/mm/memory-failure.c
> +++ b/mm/memory-failure.c
> @@ -2437,8 +2437,11 @@ int memory_failure(unsigned long pfn, int flags)
> * or unhandlable page. The refcount is bumped iff the
> * page is a valid handlable page.
> */
> - folio_set_has_hwpoisoned(folio);
> - err = try_to_split_thp_page(p, new_order, /* release= */ false);
> + if (new_order >= 0) {
> + folio_set_has_hwpoisoned(folio);
if new_order < 0, do we skip setting hwpoisioned bit on the folio?
> + err = try_to_split_thp_page(p, new_order, /* release= */ false);
> + } else
> + err = new_order;
> /*
> * If splitting a folio to order-0 fails, kill the process.
> * Split the folio regardless to minimize unusable pages.
> @@ -2779,6 +2782,7 @@ static int soft_offline_in_use_page(struct page *page)
> /*
> * If new_order (target split order) is not 0, do not split the
> * folio at all to retain the still accessible large folio.
> + * new_order can be -EBUSY, meaning the folio cannot be split.
> * NOTE: if minimizing the number of soft offline pages is
> * preferred, split it to non-zero new_order like it is done in
> * memory_failure().
Balbir
On 19 Nov 2025, at 23:45, Balbir Singh wrote:
> On 11/20/25 14:59, Zi Yan wrote:
>> min_order_for_split() returns -EBUSY when the folio is truncated and cannot
>> be split. In commit 77008e1b2ef7 ("mm/huge_memory: do not change
>> split_huge_page*() target order silently"), memory_failure() does not
>> handle it and pass -EBUSY to try_to_split_thp_page() directly.
>> try_to_split_thp_page() returns -EINVAL since -EBUSY becomes 0xfffffff0 as
>> new_order is unsigned int in __folio_split() and this large new_order is
>> rejected as an invalid input. The code does not cause a bug.
>> soft_offline_in_use_page() also uses min_order_for_split() but it always
>> passes 0 as new_order for split.
>>
>> Handle it properly by checking min_order_for_split() return value and not
>> calling try_to_split_thp_page() if the value is negative. Add a comment
>> in soft_offline_in_use_page() to clarify the possible negative new_order
>> value.
>>
>> Signed-off-by: Zi Yan <ziy@nvidia.com>
>> ---
>> mm/memory-failure.c | 8 ++++++--
>> 1 file changed, 6 insertions(+), 2 deletions(-)
>>
>> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
>> index 7f908ad795ad..86582f030159 100644
>> --- a/mm/memory-failure.c
>> +++ b/mm/memory-failure.c
>> @@ -2437,8 +2437,11 @@ int memory_failure(unsigned long pfn, int flags)
>> * or unhandlable page. The refcount is bumped iff the
>> * page is a valid handlable page.
>> */
>> - folio_set_has_hwpoisoned(folio);
>> - err = try_to_split_thp_page(p, new_order, /* release= */ false);
>> + if (new_order >= 0) {
>> + folio_set_has_hwpoisoned(folio);
>
> if new_order < 0, do we skip setting hwpoisioned bit on the folio?
The bit should be set. Anyway, I am going to take David’s approach to
change min_order_for_split().
Thanks.
>
>> + err = try_to_split_thp_page(p, new_order, /* release= */ false);
>> + } else
>> + err = new_order;
>> /*
>> * If splitting a folio to order-0 fails, kill the process.
>> * Split the folio regardless to minimize unusable pages.
>> @@ -2779,6 +2782,7 @@ static int soft_offline_in_use_page(struct page *page)
>> /*
>> * If new_order (target split order) is not 0, do not split the
>> * folio at all to retain the still accessible large folio.
>> + * new_order can be -EBUSY, meaning the folio cannot be split.
>> * NOTE: if minimizing the number of soft offline pages is
>> * preferred, split it to non-zero new_order like it is done in
>> * memory_failure().
>
> Balbir
Best Regards,
Yan, Zi
© 2016 - 2025 Red Hat, Inc.