Skip to content

arm64: kexec: stop cloning the linear map 16 times (2.65s -> 0.57s per load) - #24

Open
Yury-MonZon wants to merge 1 commit into
flipper-develfrom
kexec-vabits-fix
Open

arm64: kexec: stop cloning the linear map 16 times (2.65s -> 0.57s per load)#24
Yury-MonZon wants to merge 1 commit into
flipper-develfrom
kexec-vabits-fix

Conversation

@Yury-MonZon

@Yury-MonZon Yury-MonZon commented Sep 1, 2026

Copy link
Copy Markdown

Cherry-picked from the list, not written here: arm64: trans_pgd: clone only the linear map that exists at runtime by Breno Leitao, posted to linux-arm-kernel on 2026-08-28 and not yet merged. Applied with git am, so his authorship and sign-off are intact and it will deduplicate when it lands upstream.

Why this board is affected

trans_pgd_create_copy() walks from the compile-time PAGE_OFFSET. We build CONFIG_ARM64_VA_BITS=52 and RK3576 has no FEAT_LPA2, so vabits_actual is 48: the fifth level is folded, pgd_none() is never true, and the 15 extra PGDIR_SIZE slots all alias the same table. The kernel page table gets cloned 16 times on every kexec load.

Upstream reports it as wasted memory. On this board it is mostly wasted time, because every profile switch that cannot be pivoted into goes through a kexec load.

Measured on Flipper One, 8GB, kernel 7.2.0 with and without the patch

machine_kexec_post_load timed with ftrace (function_graph, set_graph_function=machine_kexec_post_load), page-table pages counted with the function tracer on kimage_alloc_control_pages, load timed around kexec -l of a profile's own BLS entry (11MB compressed kernel, 10MB initrd):

before after
trans_pgd_create_copy 2.231s 0.132s (16.9x)
page-table pages allocated 51,276 4,110 (12.5x)
whole kexec -l 2.65s 0.566s
memory held while an image is loaded ~300MB 87MB

The 16.9x matches the 16 aliased clones exactly, which is what confirms this is the whole bug rather than one contributor to it.

Two supporting measurements, from before the fix, that show what the cost was made of:

  • It scaled with RAM and not with the image: 2.231s at 8.1GB against 0.559s with mem=2G, loading the same image. A 10MB initrd added 58ms; reading kernel and initrd off btrfs cold was 0.08s; rodata=on changed nothing.
  • One kimage_alloc_page per payload page and no retries, so it was never destination-collision thrash.

What this does and does not buy

A kexec boot to first pixel on this board was 13.4-16.5s, of which the load was 2.65s. With the load at 0.566s that is ~2.1s off any profile switch that has to kexec, and it makes an armed prefetch cheap in memory (87MB rather than 300MB) so keeping one loaded while the boot menu waits costs little.

It does not touch the 10.5s underneath, which is a fresh kernel plus systemd reaching the panel. Only a pivot avoids that, and pivots stay at 6.3-6.6s.

Testing

Verified on Flipper One, 8GB, with a kernel built from this branch.

Load path, measured (numbers above), and reproducible: five consecutive loads of an
overlay-free profile came in at 0.469 / 0.414 / 0.408 / 0.423 / 0.442s, and five of a profile
whose device tree is assembled from an overlay at 0.440 / 0.414 / 0.419 / 0.429 / 0.417s. The
2.65s before the patch had no such spread to speak of, being dominated by a fixed walk.

Jumps, seven of them, all successful. This is the check that mattered: the patch changes
which range is cloned into the transitional table, and a table missing a page the relocation
touches does not boot slowly, it hangs with the MMU off and no console output. Both device
tree shapes were exercised, because they place their segments differently:

jump device tree result
@desktop -> @desktop the live one booted, fresh kernel at t=0
@desktop -> @No-Graphics base dtb + 1 overlay via fdtoverlay booted, overlay applied: vop, hdmi and display-subsystem all disabled in the live tree
@minimal -> @No-Graphics base dtb + 1 overlay booted
@No-Graphics -> @desktop assembled base dtb booted
@desktop -> @minimal assembled base dtb booted
@minimal -> @No-Graphics base dtb + 1 overlay booted
@No-Graphics -> @desktop assembled base dtb booted

Every one reached userspace in 34s wall from the request, machine_kexec.o builds
warning-free with the device config, and hibernate is untouched since it keeps calling
trans_pgd_create_copy.

Worth sending these numbers to the upstream thread: the author reports the memory waste and
has no timing from a non-LPA2 board.

kexec_file_load() fails on arm64 if we have CONFIG_ARM64_VA_BITS_52 but
it runs on a !FEAT_LPA2 host (such as my loving Grace machine).

That is because trans_pgd_create_copy() uses the compile time
PAGE_OFFSET (VA 52) instead of the actual VA size (48 -- due to the lack
of LPA2). With the fifth level folded, pgd_none() is always false, so
the walk cannot skip the 15 extra PGDIR_SIZE slots, and they all alias
back to the same table: the whole kernel page table gets cloned 16
times, KASAN shadow included. Without KASAN it does not blow up, it just
wastes ~RAM/32 in page tables.

Fix it by copying the linear map that is the actual one, not the
compiled one.

Fixes: a6bbf5d ("arm64: mm: Add definitions to support 5 levels of paging")
Signed-off-by: Breno Leitao <leitao@debian.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants