Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
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