mm/hugetlb_cma: support percentage-based hugetlb_cma reservation

Currently, hugetlb_cma reservation only supports absolute sizes (e.g.,
hugetlb_cma=2G or hugetlb_cma=0:1G,1:1G).  This can be restrictive in
heterogeneous environments or when deploying common kernel command lines
across machines with different memory capacities.

Add support for percentage-based hugetlb_cma reservation (e.g.,
hugetlb_cma=20% or hugetlb_cma=0:20%,1:10%).

The percentage is calculated against the total memory (for global
settings) or against the node-specific memory (for node-specific settings)
using memblock APIs during early boot.

Link: https://lore.kernel.org/20260807040003.2156630-1-souravpanda@google.com
Signed-off-by: Sourav Panda <souravpanda@google.com>
Acked-by: Usama Arif <usama.arif@linux.dev>
Cc: David Hildenbrand <david@kernel.org>
Cc: David Rientjes <rientjes@google.com>
Cc: Frank van der Linden <fvdl@google.com>
Cc: Greg Thelen <gthelen@google.com>
Cc: Muchun Song <muchun.song@linux.dev>
Cc: Oscar Salvador <osalvador@suse.de>
Cc: Suren Baghdasaryan <surenb@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
Sourav Panda 2026-08-07 04:00:03 +00:00 committed by Andrew Morton
parent 28b13c3c4c
commit 34e0849142
2 changed files with 142 additions and 10 deletions

View File

@ -2064,8 +2064,14 @@ Kernel parameters
hugetlb_cma= [HW,CMA,EARLY] The size of a CMA area used for allocation
of gigantic hugepages. Or using node format, the size
of a CMA area per node can be specified.
Format: nn[KMGTPE] or (node format)
<node>:nn[KMGTPE][,<node>:nn[KMGTPE]]
The size can be an absolute value (e.g., 2G) or a
percentage of the total memory or node memory (e.g., 20%).
Percentage-derived sizes are rounded down to a multiple of
the architecture's gigantic hugepage size and may become
zero.
Format: nn[KMGTPE] or nn% or (node format)
<node>:nn[KMGTPE][,<node>:nn[KMGTPE]] or
<node>:nn%[,<node>:nn%]
The size must be a multiple of the gigantic page size.
When using node format, this applies to each per-node size.

View File

@ -9,6 +9,9 @@
#include <asm/setup.h>
#include <linux/hugetlb.h>
#include <linux/memblock.h>
#include <linux/math.h>
#include <linux/math64.h>
#include "internal.h"
#include "hugetlb_cma.h"
@ -18,6 +21,28 @@ static unsigned long hugetlb_cma_size_in_node[MAX_NUMNODES] __initdata;
static bool hugetlb_cma_only __ro_after_init;
static unsigned long hugetlb_cma_size __ro_after_init;
static unsigned int hugetlb_cma_percent __initdata;
static unsigned int hugetlb_cma_percent_in_node[MAX_NUMNODES] __initdata;
#ifdef CONFIG_NUMA
static phys_addr_t __init memblock_node_memory_size(int nid)
{
struct memblock_region *reg;
phys_addr_t size = 0;
for_each_mem_region(reg) {
if (reg->nid == nid)
size += reg->size;
}
return size;
}
#else
static phys_addr_t __init memblock_node_memory_size(int nid)
{
return memblock_phys_mem_size();
}
#endif
void hugetlb_cma_free_frozen_folio(struct folio *folio)
{
WARN_ON_ONCE(!cma_release_frozen(hugetlb_cma[folio_nid(folio)],
@ -90,14 +115,31 @@ static int __init cmdline_parse_hugetlb_cma(char *p)
break;
if (s[count] == ':') {
char *next;
if (tmp >= MAX_NUMNODES)
break;
nid = array_index_nospec(tmp, MAX_NUMNODES);
hugetlb_cma_size = 0;
hugetlb_cma_percent = 0;
s += count + 1;
tmp = memparse(s, &s);
hugetlb_cma_size_in_node[nid] = tmp;
hugetlb_cma_size += tmp;
tmp = memparse(s, &next);
if (*next == '%') {
if (tmp > 100) {
pr_warn("hugetlb_cma: invalid percentage %lu for node %d\n",
tmp, nid);
break;
}
hugetlb_cma_percent_in_node[nid] = tmp;
hugetlb_cma_size_in_node[nid] = 0;
s = next + 1;
} else {
hugetlb_cma_size_in_node[nid] = tmp;
hugetlb_cma_percent_in_node[nid] = 0;
s = next;
}
/*
* Skip the separator if have one, otherwise
@ -108,7 +150,28 @@ static int __init cmdline_parse_hugetlb_cma(char *p)
else
break;
} else {
hugetlb_cma_size = memparse(p, &p);
char *next;
tmp = memparse(p, &next);
if (*next == '%') {
if (tmp > 100) {
pr_warn("hugetlb_cma: invalid percentage %lu\n", tmp);
} else {
hugetlb_cma_percent = tmp;
hugetlb_cma_size = 0;
for (nid = 0; nid < MAX_NUMNODES; nid++) {
hugetlb_cma_size_in_node[nid] = 0;
hugetlb_cma_percent_in_node[nid] = 0;
}
}
} else {
hugetlb_cma_size = tmp;
hugetlb_cma_percent = 0;
for (nid = 0; nid < MAX_NUMNODES; nid++) {
hugetlb_cma_size_in_node[nid] = 0;
hugetlb_cma_percent_in_node[nid] = 0;
}
}
break;
}
}
@ -134,8 +197,36 @@ void __init hugetlb_cma_reserve(void)
{
unsigned long size, reserved, per_node, order, gigantic_page_size;
bool node_specific_cma_alloc = false;
bool has_node_specific_param = false;
int nid;
for (nid = 0; nid < MAX_NUMNODES; nid++) {
if (hugetlb_cma_size_in_node[nid] || hugetlb_cma_percent_in_node[nid]) {
has_node_specific_param = true;
break;
}
}
if (has_node_specific_param) {
hugetlb_cma_size = 0;
for (nid = 0; nid < MAX_NUMNODES; nid++) {
if (hugetlb_cma_percent_in_node[nid]) {
phys_addr_t node_gfp_mem = memblock_node_memory_size(nid);
u64 s;
s = mul_u64_u32_div((u64)node_gfp_mem,
hugetlb_cma_percent_in_node[nid],
100);
hugetlb_cma_size_in_node[nid] = s;
}
hugetlb_cma_size += hugetlb_cma_size_in_node[nid];
}
} else if (hugetlb_cma_percent) {
hugetlb_cma_size = mul_u64_u32_div((u64)memblock_phys_mem_size(),
hugetlb_cma_percent, 100);
}
if (!hugetlb_cma_size)
return;
@ -154,6 +245,32 @@ void __init hugetlb_cma_reserve(void)
VM_WARN_ON(order <= MAX_PAGE_ORDER);
gigantic_page_size = PAGE_SIZE << order;
if (hugetlb_cma_percent) {
unsigned long orig_size = hugetlb_cma_size;
hugetlb_cma_size = ALIGN_DOWN(hugetlb_cma_size, PAGE_SIZE << order);
if (orig_size && !hugetlb_cma_size)
pr_warn("hugetlb_cma: reservation size rounded down to 0 from %lu MiB (%u%%)\n",
orig_size / SZ_1M, hugetlb_cma_percent);
} else if (has_node_specific_param) {
hugetlb_cma_size = 0;
for (nid = 0; nid < MAX_NUMNODES; nid++) {
if (hugetlb_cma_percent_in_node[nid]) {
unsigned long orig_size = hugetlb_cma_size_in_node[nid];
hugetlb_cma_size_in_node[nid] =
ALIGN_DOWN(hugetlb_cma_size_in_node[nid],
PAGE_SIZE << order);
if (orig_size && !hugetlb_cma_size_in_node[nid])
pr_warn("hugetlb_cma: reservation size rounded down to 0 from %lu MiB (%u%%) on node %d\n",
orig_size / SZ_1M,
hugetlb_cma_percent_in_node[nid],
nid);
}
hugetlb_cma_size += hugetlb_cma_size_in_node[nid];
}
}
hugetlb_bootmem_set_nodes();
for (nid = 0; nid < MAX_NUMNODES; nid++) {
@ -194,8 +311,13 @@ void __init hugetlb_cma_reserve(void)
per_node = DIV_ROUND_UP(hugetlb_cma_size,
nodes_weight(hugetlb_bootmem_nodes));
per_node = round_up(per_node, gigantic_page_size);
pr_info("hugetlb_cma: reserve %lu MiB, up to %lu MiB per node\n",
hugetlb_cma_size / SZ_1M, per_node / SZ_1M);
if (hugetlb_cma_percent)
pr_info("hugetlb_cma: reserve %lu MiB (%u%%), up to %lu MiB per node\n",
hugetlb_cma_size / SZ_1M, hugetlb_cma_percent,
per_node / SZ_1M);
else
pr_info("hugetlb_cma: reserve %lu MiB, up to %lu MiB per node\n",
hugetlb_cma_size / SZ_1M, per_node / SZ_1M);
}
reserved = 0;
@ -230,8 +352,12 @@ void __init hugetlb_cma_reserve(void)
}
reserved += size;
pr_info("hugetlb_cma: reserved %lu MiB on node %d\n",
size / SZ_1M, nid);
if (hugetlb_cma_percent_in_node[nid])
pr_info("hugetlb_cma: reserved %lu MiB (%u%%) on node %d\n",
size / SZ_1M, hugetlb_cma_percent_in_node[nid], nid);
else
pr_info("hugetlb_cma: reserved %lu MiB on node %d\n",
size / SZ_1M, nid);
if (reserved >= hugetlb_cma_size)
break;