From: Alexander Graf <graf@amazon.com>
To: <linux-kernel@vger.kernel.org>,
Marek Szyprowski <m.szyprowski@samsung.com>
Cc: Jonathan Corbet <corbet@lwn.net>,
Shuah Khan <skhan@linuxfoundation.org>,
Randy Dunlap <rdunlap@infradead.org>,
Robin Murphy <robin.murphy@arm.com>, <linux-doc@vger.kernel.org>,
<iommu@lists.linux.dev>,
Andrew Morton <akpm@linux-foundation.org>,
David Hildenbrand <david@kernel.org>,
"Mike Rapoport" <rppt@kernel.org>, <nh-open-source@amazon.com>,
<linux-mm@kvack.org>,
The AWS Nitro Enclaves Team <aws-nitro-enclaves-devel@amazon.com>
Subject: [PATCH v2] dma-contiguous: support percentage sizes in cma_pernuma= and numa_cma=
Date: Sat, 14 Nov 2026 14:53:28 +0000 [thread overview]
Message-ID: <20261114145328.1307-1-graf@amazon.com> (raw)
While CMA_SIZE_PERCENTAGE supports percentages for built-in defaults, the
CMA allocation kernel parameters currently only take a fixed size argument.
That makes it difficult to have a single system image that sizes its CMA
allocation based on the available memory. To use CMA as backing pool for
large, physically contiguous guest memory, I want to be able to say
"allocate 90% of system RAM minus 1 GiB for CMA". Per NUMA node.
Extend the CMA parser logic to accept command line options like this:
cma_pernuma=80% numa_cma=0:95%-1G,1:4G
To avoid incomplete allocations on parsing errors, check for them
before actually allocating.
Signed-off-by: Alexander Graf <graf@amazon.com>
---
v1 -> v2:
- Reject an empty numa_cma= and a trailing comma (Sashiko)
- Keep the last valid size for each option or node, so an explicit 0
now overrides a percentage (Sashiko)
- Parse percentages as plain decimal, and reject sizes that a 32-bit
phys_addr_t cannot hold
- Reject a bare cma_pernuma or numa_cma without "=", which mainline
dereferences as NULL
- Rework the kernel-parameters.txt text accordingly
v1: https://lore.kernel.org/all/20261111140440.43024-1-graf@amazon.com/
---
.../admin-guide/kernel-parameters.txt | 37 +++-
kernel/dma/contiguous.c | 188 +++++++++++++++---
2 files changed, 194 insertions(+), 31 deletions(-)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 0b3c140babd94..d132b14ba4f73 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -862,10 +862,13 @@ Kernel parameters
contiguous memory allocations and optionally the
placement constraint by the physical address range of
memory allocations. A value of 0 disables CMA
- altogether. For more information, see
+ altogether. The size is absolute; only 'cma_pernuma='
+ and 'numa_cma=' accept a percentage. Without 'cma=',
+ CONFIG_CMA_SIZE_PERCENTAGE can size this area as a
+ fraction of memory. For more information, see
kernel/dma/contiguous.c
- cma_pernuma=nn[MG]
+ cma_pernuma=nn[MG]|nn%[-nn[MG]]
[KNL,CMA,EARLY]
Sets the size of kernel per-numa memory area for
contiguous memory allocations. A value of 0 disables
@@ -875,14 +878,34 @@ Kernel parameters
first try to allocate buffer from the pernuma area
which is located in node nid, if the allocation fails,
they will fallback to the global default memory area.
-
- numa_cma=<node>:nn[MG][,<node>:nn[MG]]
+ The size may also be a decimal percentage of each
+ node's memory as memblock sees it at boot, optionally
+ less a size to keep back, so cma_pernuma=95%-1G
+ reserves 95% of each node minus 1G. Hot-added memory is
+ not counted. A nonzero percentage that leaves nothing
+ to reserve on a node warns and reserves nothing there.
+ If the option is given more than once, the last valid
+ value counts. An invalid value is ignored, and it still
+ stops CONFIG_CMA_SIZE_PERNUMA from mirroring the
+ default area onto every node.
+
+ numa_cma=<node>:nn[MG]|nn%[-nn[MG]][,<node>:nn[MG]|nn%[-nn[MG]]]
[KNL,CMA,EARLY]
Sets the size of kernel numa memory area for
contiguous memory allocations. It will reserve CMA
- area for the specified node.
-
- If it is setup together with upper 'cmd_pernuma='
+ area for the specified node. A size may also be a
+ percentage of that node's memory, optionally less a
+ size to keep back, as for 'cma_pernuma='. A size of 0
+ or 0% is the same as not naming the node, so it falls
+ back to 'cma_pernuma='. A nonzero percentage that
+ leaves nothing to reserve does not fall back. If a node
+ is named more than once, also across several numa_cma=
+ options, the last valid size counts. An entry that does
+ not parse, including an empty list or a trailing comma,
+ makes the kernel ignore that whole option, and the
+ default area is not mirrored either.
+
+ If it is set up together with upper 'cma_pernuma='
(unlikely), its size setting takes priority for the
specified numa nodes.
diff --git a/kernel/dma/contiguous.c b/kernel/dma/contiguous.c
index 90937dc7c627d..ea094d2238136 100644
--- a/kernel/dma/contiguous.c
+++ b/kernel/dma/contiguous.c
@@ -39,8 +39,10 @@
#include <asm/page.h>
+#include <linux/ctype.h>
#include <linux/memblock.h>
#include <linux/err.h>
+#include <linux/math64.h>
#include <linux/sizes.h>
#include <linux/dma-map-ops.h>
#include <linux/cma.h>
@@ -135,45 +137,135 @@ EXPORT_SYMBOL_GPL(dev_get_cma_area);
static struct cma *dma_contiguous_numa_area[MAX_NUMNODES];
static phys_addr_t numa_cma_size[MAX_NUMNODES] __initdata;
static phys_addr_t pernuma_size_bytes __initdata;
+static unsigned int numa_cma_pct[MAX_NUMNODES] __initdata;
+static unsigned int pernuma_pct __initdata;
+static phys_addr_t numa_cma_sub[MAX_NUMNODES] __initdata;
+static phys_addr_t pernuma_sub __initdata;
static bool numa_cma_configured __initdata;
-static int __init early_numa_cma(char *p)
+/*
+ * What numa_cma_parse() does with a string it accepts: NUMA_CMA_CHECK leaves
+ * everything outside the function as it is, so a caller can validate the
+ * whole string before NUMA_CMA_APPLY lets any part of it take effect.
+ */
+enum numa_cma_action {
+ NUMA_CMA_CHECK,
+ NUMA_CMA_APPLY,
+};
+
+/*
+ * Parse one CMA size, either a size or <percent>%[-<size>], and advance *s past
+ * it. A percentage is a plain decimal number up to 100. Exactly one of *size and
+ * *pct describes the result, so the last size given replaces any earlier one.
+ */
+static int __init numa_cma_parse_size(char **s, phys_addr_t *size,
+ unsigned int *pct, phys_addr_t *sub)
+{
+ unsigned long long val;
+ unsigned int n;
+ char *start = *s;
+ int count;
+
+ if (isdigit(*start) && sscanf(start, "%3u%n", &n, &count) == 1 &&
+ start[count] == '%') {
+ if (n > 100)
+ return -EINVAL;
+ *s = start + count + 1;
+ val = 0;
+ if (**s == '-') {
+ start = ++*s;
+ val = memparse(start, s);
+ if (*s == start || val != (phys_addr_t)val)
+ return -EINVAL;
+ }
+ *size = 0;
+ *pct = n;
+ *sub = val;
+ return 0;
+ }
+
+ val = memparse(start, s);
+ if (*s == start || val != (phys_addr_t)val)
+ return -EINVAL;
+ *size = val;
+ *pct = 0;
+ *sub = 0;
+ return 0;
+}
+
+/*
+ * Parse one numa_cma= string: a comma separated list of <node>:<size>, where
+ * <size> is a size or <percent>%[-<size>]. Anything else fails the parse.
+ */
+static int __init numa_cma_parse(char *p, enum numa_cma_action action)
{
int nid, count = 0;
unsigned long node;
- phys_addr_t size;
+ phys_addr_t size, sub;
+ unsigned int pct;
char *s = p;
- while (*s) {
- if (sscanf(s, "%lu%n", &node, &count) != 1)
- break;
-
- if (s[count] == ':') {
- if (node >= MAX_NUMNODES)
- break;
- nid = array_index_nospec(node, MAX_NUMNODES);
-
- s += count + 1;
- size = memparse(s, &s);
+ for (;;) {
+ if (!isdigit(*s) ||
+ sscanf(s, "%4lu%n", &node, &count) != 1 || s[count] != ':' ||
+ node >= MAX_NUMNODES)
+ return -EINVAL;
+ nid = array_index_nospec(node, MAX_NUMNODES);
+
+ s += count + 1;
+ if (numa_cma_parse_size(&s, &size, &pct, &sub))
+ return -EINVAL;
+ if (action == NUMA_CMA_APPLY) {
numa_cma_size[nid] = size;
+ numa_cma_pct[nid] = pct;
+ numa_cma_sub[nid] = sub;
+ }
- if (*s == ',')
- s++;
- else
- break;
- } else
- break;
+ if (!*s)
+ return 0;
+ if (*s != ',')
+ return -EINVAL;
+ s++;
}
+}
+static int __init early_numa_cma(char *p)
+{
+ int rc;
+
+ /*
+ * Per-node CMA is configured either way. Leaving the flag clear on a
+ * string this rejects would tell dma_numa_cma_reserve() that nobody
+ * asked for per-node areas, and on a CMA_SIZE_PERNUMA kernel it would
+ * then clone the default area onto every node.
+ */
numa_cma_configured = true;
- return 0;
+
+ if (!p)
+ return -EINVAL;
+
+ rc = numa_cma_parse(p, NUMA_CMA_CHECK);
+ if (rc)
+ return rc;
+
+ return numa_cma_parse(p, NUMA_CMA_APPLY);
}
early_param("numa_cma", early_numa_cma);
static int __init early_cma_pernuma(char *p)
{
- pernuma_size_bytes = memparse(p, &p);
+ phys_addr_t size, sub;
+ unsigned int pct;
+
+ /* See early_numa_cma(): a rejected string still counts as configured. */
numa_cma_configured = true;
+
+ if (!p || numa_cma_parse_size(&p, &size, &pct, &sub) || *p)
+ return -EINVAL;
+
+ pernuma_size_bytes = size;
+ pernuma_pct = pct;
+ pernuma_sub = sub;
return 0;
}
early_param("cma_pernuma", early_cma_pernuma);
@@ -198,6 +290,33 @@ static inline __maybe_unused phys_addr_t cma_early_percent_memory(void)
#endif
#ifdef CONFIG_DMA_NUMA_CMA
+static phys_addr_t __init numa_cma_node_size(int nid)
+{
+ unsigned long spfn, epfn;
+ phys_addr_t total = 0;
+ int i;
+
+ for_each_mem_pfn_range(i, nid, &spfn, &epfn, NULL)
+ total += (u64)(epfn - spfn) << PAGE_SHIFT;
+
+ return total;
+}
+
+/*
+ * A percentage of the node, less the size the boot line asked to keep back.
+ * Keeping back more than the percentage came to reserves nothing.
+ */
+static phys_addr_t __init numa_cma_pct_size(int nid, unsigned int pct,
+ phys_addr_t sub)
+{
+ phys_addr_t size = mul_u64_u32_div(numa_cma_node_size(nid), pct, 100);
+
+ if (size <= sub)
+ return 0;
+
+ return size - sub;
+}
+
static void __init dma_numa_cma_reserve(void)
{
int nid;
@@ -214,15 +333,36 @@ static void __init dma_numa_cma_reserve(void)
int ret;
if (!node_online(nid)) {
- if (pernuma_size_bytes || numa_cma_size[nid])
+ if (pernuma_size_bytes || pernuma_pct ||
+ numa_cma_size[nid] || numa_cma_pct[nid])
pr_warn("invalid node %d specified\n", nid);
continue;
}
/* per-node numa setting has the priority */
- size = numa_cma_size[nid] ?: pernuma_size_bytes;
- if (!size)
+ if (numa_cma_size[nid])
+ size = numa_cma_size[nid];
+ else if (numa_cma_pct[nid])
+ size = numa_cma_pct_size(nid, numa_cma_pct[nid],
+ numa_cma_sub[nid]);
+ else if (pernuma_size_bytes)
+ size = pernuma_size_bytes;
+ else if (pernuma_pct)
+ size = numa_cma_pct_size(nid, pernuma_pct, pernuma_sub);
+ else
+ continue;
+
+ /*
+ * A percentage of a memoryless online node, and a percentage
+ * entirely consumed by the size kept back, resolve to nothing
+ * to reserve. Say so, since the operator asked for memory.
+ */
+ if (!size) {
+ if (numa_cma_pct[nid] || pernuma_pct)
+ pr_warn("node %d percentage reserves nothing\n",
+ nid);
continue;
+ }
cma = &dma_contiguous_numa_area[nid];
snprintf(name, sizeof(name), "numa%d", nid);
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
--
2.47.1
reply other threads:[~2026-11-14 14:53 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261114145328.1307-1-graf@amazon.com \
--to=graf@amazon.com \
--cc=akpm@linux-foundation.org \
--cc=aws-nitro-enclaves-devel@amazon.com \
--cc=corbet@lwn.net \
--cc=david@kernel.org \
--cc=iommu@lists.linux.dev \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=m.szyprowski@samsung.com \
--cc=nh-open-source@amazon.com \
--cc=rdunlap@infradead.org \
--cc=robin.murphy@arm.com \
--cc=rppt@kernel.org \
--cc=skhan@linuxfoundation.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox