From 909a737b8f9d04d318d6726e0d3fa6b44d596c6e Mon Sep 17 00:00:00 2001 From: "Alexander M. Merritt" Date: Thu, 15 Oct 2026 22:45:10 -0500 Subject: [PATCH 1/2] xen/balloon: add boot-time extra memory as 2 MiB blocks Signed-off-by: Alexander M. Merritt (cherry picked from commit 2bf98044438994101e998014b68743d2622791ef) --- drivers/xen/balloon.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/drivers/xen/balloon.c b/drivers/xen/balloon.c index d5444e699acce2..f4ec5e7c2786bb 100644 --- a/drivers/xen/balloon.c +++ b/drivers/xen/balloon.c @@ -1024,10 +1024,26 @@ static struct notifier_block balloon_oom_nb = { .notifier_call = balloon_oom_notify, }; +static void __init balloon_append_range(unsigned long pfn, + unsigned long end_pfn) +{ + while (pfn < end_pfn) { + if (balloon_blocks_usable() && + IS_ALIGNED(pfn, BALLOON_BLOCK_NR_PAGES) && + end_pfn - pfn >= BALLOON_BLOCK_NR_PAGES) { + balloon_append_block(pfn_to_page(pfn)); + pfn += BALLOON_BLOCK_NR_PAGES; + } else { + balloon_append(pfn_to_page(pfn)); + pfn++; + } + } +} + static int __init balloon_add_regions(bool append) { unsigned long start_pfn, pages; - unsigned long pfn, extra_pfn_end; + unsigned long extra_pfn_end; unsigned int i; for (i = 0; i < XEN_EXTRA_MEM_MAX_REGIONS; i++) { @@ -1044,8 +1060,7 @@ static int __init balloon_add_regions(bool append) */ extra_pfn_end = min(max_pfn, start_pfn + pages); - for (pfn = start_pfn; pfn < extra_pfn_end; pfn++) - balloon_append(pfn_to_page(pfn)); + balloon_append_range(start_pfn, extra_pfn_end); /* * There are two different use-cases depending on how the From a8871dac6f7e71dd85eb455fe56cb970b72c5ee5 Mon Sep 17 00:00:00 2001 From: "Alexander M. Merritt" Date: Thu, 15 Oct 2026 22:46:10 -0500 Subject: [PATCH 2/2] x86/xen: keep dom0's growth room on the balloon All of the extra memory Linux reserves at boot goes to the mapping pool, so dom0's balloon starts empty. Growing dom0 at runtime then means memory hotplug, which places the new range at the first free physical address: on PVH that is the host's own map, where nothing guarantees the range is not a device's MMIO window. Keep the part of the extra memory dom0 may grow into on the balloon instead. Xen's ceiling for dom0 (dom0_mem=...,max:) sizes that part, so a host booted without a max: keeps today's behaviour. Every region gives up the same fraction, which spreads the growth over the NUMA nodes on PVH and leaves each node's mapping pool its share, and the last memory section stays with the pool so that early grant mappings never fall back to holes in the physical map. Signed-off-by: Alexander M. Merritt (cherry picked from commit a26875e62c613e663047e71113ef291effd40e29) --- arch/x86/xen/enlighten.c | 54 ++++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/arch/x86/xen/enlighten.c b/arch/x86/xen/enlighten.c index 23b91bf9b66303..852610dfe704bd 100644 --- a/arch/x86/xen/enlighten.c +++ b/arch/x86/xen/enlighten.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -440,8 +441,34 @@ void __init xen_add_extra_mem(unsigned long start_pfn, unsigned long n_pfns) } #ifdef CONFIG_XEN_UNPOPULATED_ALLOC +/* + * How much of the extra memory dom0 keeps on the balloon, to grow into up + * to the ceiling Xen set for it (dom0_mem=...,max:). Without a max: Xen + * reports UINT_MAX and dom0 keeps nothing, so growing stays opt-in. The + * last memory section always stays with the mapping pool, so that the + * grant table and other early users never fall back to holes in the + * physical map before PCI devices have claimed their MMIO windows. + */ +static unsigned long __init xen_dom0_growth_pages(unsigned long extra_pages) +{ + domid_t domid = DOMID_SELF; + long max_pages, cur_pages; + + if (!xen_initial_domain() || extra_pages <= PAGES_PER_SECTION) + return 0; + + max_pages = HYPERVISOR_memory_op(XENMEM_maximum_reservation, &domid); + cur_pages = HYPERVISOR_memory_op(XENMEM_current_reservation, &domid); + if (cur_pages <= 0 || max_pages <= cur_pages || max_pages >= UINT_MAX) + return 0; + + return min_t(unsigned long, max_pages - cur_pages, + extra_pages - PAGES_PER_SECTION); +} + int __init arch_xen_unpopulated_init(struct resource **res) { + unsigned long extra_pages = 0, growth_pages; unsigned int i; if (!xen_domain()) @@ -450,16 +477,27 @@ int __init arch_xen_unpopulated_init(struct resource **res) /* Must be set strictly before calling xen_free_unpopulated_pages(). */ *res = &iomem_resource; + for (i = 0; i < XEN_EXTRA_MEM_MAX_REGIONS; i++) + extra_pages += xen_extra_mem[i].n_pfns; + growth_pages = xen_dom0_growth_pages(extra_pages); + /* * Initialize with pages from the extra memory regions (see - * arch/x86/xen/setup.c). + * arch/x86/xen/setup.c), less what dom0 keeps to grow into. Every + * region gives up the same fraction: on PVH that spreads dom0's + * growth over the NUMA nodes the regions belong to, and leaves each + * node's pool its share of the rest. */ for (i = 0; i < XEN_EXTRA_MEM_MAX_REGIONS; i++) { - unsigned int j; + struct xen_memory_region *region = &xen_extra_mem[i]; + unsigned long keep = 0, j; + + if (growth_pages) + keep = mult_frac(region->n_pfns, growth_pages, + extra_pages); - for (j = 0; j < xen_extra_mem[i].n_pfns; j++) { - struct page *pg = - pfn_to_page(xen_extra_mem[i].start_pfn + j); + for (j = keep; j < region->n_pfns; j++) { + struct page *pg = pfn_to_page(region->start_pfn + j); xen_free_unpopulated_pages(1, &pg); } @@ -470,9 +508,9 @@ int __init arch_xen_unpopulated_init(struct resource **res) * driver to know how much of the physmap is unpopulated and * set an accurate initial memory target. */ - xen_unpopulated_pages += xen_extra_mem[i].n_pfns; - /* Zero so region is not also added to the balloon driver. */ - xen_extra_mem[i].n_pfns = 0; + xen_unpopulated_pages += region->n_pfns - keep; + /* Only the part dom0 keeps is added to the balloon driver. */ + region->n_pfns = keep; } return 0;