[PATCH] PM: hibernate: exclude unusable free pages from image preallocation

From: Nhat-Trieu Huynh-Pham

Date: Mon Sep 21 2026 - 06:44:59 EST


hibernate_preallocate_memory() derives the number of page frames to
preallocate for the image from the per-zone NR_FREE_PAGES counters.
Those counters count every free pageblock regardless of its migratetype,
including MIGRATE_HIGHATOMIC and MIGRATE_CMA pageblocks. Such pages are
free from the buddy allocator's point of view, but they cannot be used
by the order-0, non-movable, non-reserve GFP_KERNEL allocations with
which the image is preallocated (GFP_IMAGE = GFP_KERNEL|__GFP_NOWARN).

(*) Reported free vs usable free:

The mm subsystem already accounts for this in
__zone_watermark_unusable_free(). For order-0 GFP_KERNEL neither
ALLOC_RESERVES nor ALLOC_CMA is set, so the allocator subtracts the
high-order atomic reserve and the free CMA pages from the free count before
checking the watermarks. The hibernation preallocation, however, computes
its target from the unadjusted NR_FREE_PAGES value, so it asks for about
half of the unusable pages more than the allocator can deliver. On arm64
(no CONFIG_HIGHMEM) the highmem fallback in the failure path is a no-op,
hence even a small shortfall aborts hibernation, see [3] for example.

(*) Why those pages are not usable by GFP_KERNEL:

(**) CMA is reserved for movable allocations, so GFP_KERNEL has no
ALLOC_CMA, and MIGRATE_CMA is not in the unmovable fallback list. Free
CMA pages are therefore never handed to these allocations.

(**) The high-order atomic reserve is only handed out to atomic, order > 0,
__GFP_HIGH allocations (ALLOC_HIGHATOMIC), which GFP_KERNEL order-0 is not.
These pages can be unreserved under memory pressure, but only all but one
pageblock per zone and only after an allocation has already failed,
so counting them as available still overestimates what the preallocation
can obtain without failing first. Excluding them is conservative.

(*) Consequence of not excluding the unusable pages:

With M = managed, S = saveable, F = free, U = highatomic + CMA and
R = reclaimable, count = S + F - totalreserve and alloc is about
count / 2, while the allocator can actually provide about (F - U) + R
pages. The shortfall is therefore inflated by exactly U compared with
the usable memory.

From, with U = 11430 unusable free pages (5120 highatomic + 6310
CMA), the preallocation target is inflated by about U / 2 = 5715 pages,
while the allocator ends up 1247 pages short and hibernation is aborted
even though the system is able to create the image.

(*) What changes after excluding the unusable pages:

Subtract U from the per-zone free accounting used to compute count and
avail_normal. Then count' = count - U and, since max_size is roughly
count / 2, max_size' = max_size - U / 2 and alloc' = alloc - U / 2.
max_size' remains well above minimum_image_size() in practice, so the
resulting image is effectively unchanged while hibernation no longer
aborts unnecessarily.

Testing
=======
(*) SA6155P running on AAOS with S2D feature
(*) backport locally on GKI 6.1

References
==========
[1]
commit f27ce0e14088 ("page_alloc: consider highatomic reserve in watermark fast")
[2]
commit ac3f3b0a5551 ("mm: page_alloc: unreserve highatomic page blocks before oom")
[3] Example of an abort:
Mem-Info:
free:344621 free_pcp:931 free_cma:6310
DMA32 free:973864kB reserved_highatomic:8192KB free_cma:25240kB
Normal free:404620kB reserved_highatomic:12288KB free_cma:0kB
PM: hibernation: Image allocation is 1247 pages short

Signed-off-by: Nhat-Trieu Huynh-Pham <trieu2.huynh@xxxxxxx>
---
kernel/power/snapshot.c | 25 +++++++++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)

diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c
index b209712cb2c3..fd6164aea272 100644
--- a/kernel/power/snapshot.c
+++ b/kernel/power/snapshot.c
@@ -1867,11 +1867,32 @@ int hibernate_preallocate_memory(void)
highmem = save_highmem;
size = 0;
for_each_populated_zone(zone) {
+ unsigned long free, unusable;
+
size += snapshot_additional_pages(zone);
+
+ /*
+ * Pages reserved for high-order atomic allocations
+ * (MIGRATE_HIGHATOMIC) and free CMA pages cannot be used by
+ * the order-0 non-movable GFP_KERNEL allocations that
+ * preallocate the image, so do not count them as available.
+ * This mirrors __zone_watermark_unusable_free().
+ */
+ free = zone_page_state(zone, NR_FREE_PAGES);
+ unusable = zone->nr_reserved_highatomic;
+#ifdef CONFIG_CMA
+ unusable += zone_page_state(zone, NR_FREE_CMA_PAGES);
+#endif
+ /*
+ * nr_reserved_highatomic counts whole reserved pageblocks,
+ * so it can exceed the actual free pages.
+ */
+ unusable = min(unusable, free);
+
if (is_highmem(zone))
- highmem += zone_page_state(zone, NR_FREE_PAGES);
+ highmem += free - unusable;
else
- count += zone_page_state(zone, NR_FREE_PAGES);
+ count += free - unusable;
}
avail_normal = count;
count += highmem;
--
2.43.0