mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Alexander Graf <graf@amazon.com>
To: <linux-kernel@vger.kernel.org>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Arnd Bergmann <arnd@arndb.de>, <nh-open-source@amazon.com>,
	"The AWS Nitro Enclaves Team"
	<aws-nitro-enclaves-devel@amazon.com>
Subject: [PATCH 17/19] nitro_enclaves: Index CPU pool cores by sibling mask
Date: Thu, 12 Nov 2026 03:07:13 +0000	[thread overview]
Message-ID: <0151affce5b48462715b651945c9064a001dfa6e.1794393487.git.graf@amazon.com> (raw)
In-Reply-To: <cover.1794300000.git.graf@amazon.com>

An enclave on a host whose CPU pool spans two packages can get the
threads of a core on a different package. The pool indexes
avail_threads_per_core[] by topology_core_id(), which is package-local
on x86, so two cores in different packages share one entry.

Walk the sibling mask of each pool CPU instead, in
ne_build_core_slots(), which gives each core its own entry and counts
the cores of the pool. An enclave copies that count at NE_CREATE_VM.
NE_ADD_VCPU now bounds vcpu_id by nr_cpu_ids instead of the thread count
of the pool, so an id above the old bound that the pool does not hold
answers NE_ERR_VCPU_NOT_IN_CPU_POOL instead of NE_ERR_INVALID_VCPU.

Fixes: ff8a4d3e3a99 ("nitro_enclaves: Add logic for setting an enclave vCPU")
Signed-off-by: Alexander Graf <graf@amazon.com>
---
 drivers/virt/nitro_enclaves/ne_misc_dev.c | 168 ++++++++++++++--------
 drivers/virt/nitro_enclaves/ne_misc_dev.h |  16 +--
 2 files changed, 113 insertions(+), 71 deletions(-)

diff --git a/drivers/virt/nitro_enclaves/ne_misc_dev.c b/drivers/virt/nitro_enclaves/ne_misc_dev.c
index 355eb0a684ec2..cb1efc95a7f34 100644
--- a/drivers/virt/nitro_enclaves/ne_misc_dev.c
+++ b/drivers/virt/nitro_enclaves/ne_misc_dev.c
@@ -18,6 +18,7 @@
 #include <linux/limits.h>
 #include <linux/list.h>
 #include <linux/miscdevice.h>
+#include <linux/minmax.h>
 #include <linux/mm.h>
 #include <linux/mman.h>
 #include <linux/module.h>
@@ -114,23 +115,23 @@ static const struct kernel_param *ne_cpus_kp;
 /**
  * struct ne_cpu_pool - CPU pool used for Nitro Enclaves.
  * @avail_threads_per_core:	Available full CPU cores to be dedicated to
- *				enclave(s). The cpumasks from the array, indexed
- *				by core id, contain all the threads from the
- *				available cores, that are not set for created
- *				enclave(s). The full CPU cores are part of the
- *				NE CPU pool.
+ *				enclave(s). Each cpumask in the array holds the
+ *				threads of one core in the pool that are not set
+ *				for created enclave(s). Slots are handed out as
+ *				cores are discovered and carry no meaning
+ *				outside the driver.
  * @mutex:			Mutex for the access to the NE CPU pool.
- * @nr_parent_vm_cores :	The size of the available threads per core array.
- *				The total number of CPU cores available on the
- *				primary / parent VM.
- * @nr_threads_per_core:	The number of threads that a full CPU core has.
+ * @nr_parent_vm_cores :	The size of the available threads per core array,
+ *				one slot per full CPU core in the pool. Counted
+ *				from the pool rather than derived from a thread
+ *				count, so a machine whose cores do not all have
+ *				the same number of threads is sized correctly.
  * @numa_node:			NUMA node of the CPUs in the pool.
  */
 struct ne_cpu_pool {
 	cpumask_var_t	*avail_threads_per_core;
 	struct mutex	mutex;
 	unsigned int	nr_parent_vm_cores;
-	unsigned int	nr_threads_per_core;
 	int		numa_node;
 };
 
@@ -169,6 +170,88 @@ static void ne_free_core_slots(void)
 	ne_cpu_pool.avail_threads_per_core = NULL;
 }
 
+/**
+ * ne_build_core_slots() - Group the CPU pool into full cores and give each one
+ *			   a slot of the NE CPU pool arrays.
+ * @cpu_pool:	The CPU pool as parsed out of the ne_cpus parameter.
+ *
+ * The slots preserve the CPU topology of the pool once its CPUs are offlined.
+ * topology_core_id() is package-local on x86, so two cores in different
+ * packages report the same id and would share a slot. The per-CPU sibling
+ * cpumask identifies a core on the architectures this driver builds for, so
+ * walk that instead: once to count the cores the pool holds, and once to hand
+ * each of them the next free slot. Counting first sizes the arrays to the pool,
+ * so the walk has a slot for every core it finds.
+ *
+ * Context: Process context. This function is called with the ne_cpu_pool mutex
+ *	    and the CPU hotplug read lock held.
+ * Return:
+ * * 0 on success.
+ * * Negative return value on failure.
+ */
+static int ne_build_core_slots(const struct cpumask *cpu_pool)
+{
+	unsigned int cpu = 0;
+	unsigned int cpu_sibling = 0;
+	unsigned int i = 0;
+	unsigned int next_core_idx = 0;
+	unsigned int nr_cores = 0;
+	cpumask_var_t processed;
+	int rc = -ENOMEM;
+
+	if (!zalloc_cpumask_var(&processed, GFP_KERNEL))
+		return -ENOMEM;
+
+	for_each_cpu(cpu, cpu_pool) {
+		if (cpumask_test_cpu(cpu, processed))
+			continue;
+
+		cpumask_or(processed, processed, topology_sibling_cpumask(cpu));
+
+		nr_cores++;
+	}
+
+	ne_cpu_pool.avail_threads_per_core = kzalloc_objs(*ne_cpu_pool.avail_threads_per_core,
+							  nr_cores);
+	if (!ne_cpu_pool.avail_threads_per_core)
+		goto free_processed;
+
+	ne_cpu_pool.nr_parent_vm_cores = nr_cores;
+
+	for (i = 0; i < nr_cores; i++)
+		if (!zalloc_cpumask_var(&ne_cpu_pool.avail_threads_per_core[i], GFP_KERNEL))
+			goto free_slots;
+
+	cpumask_clear(processed);
+
+	for_each_cpu(cpu, cpu_pool) {
+		if (cpumask_test_cpu(cpu, processed))
+			continue;
+
+		for_each_cpu(cpu_sibling, topology_sibling_cpumask(cpu)) {
+			if (!cpumask_test_cpu(cpu_sibling, cpu_pool))
+				continue;
+
+			cpumask_set_cpu(cpu_sibling,
+					ne_cpu_pool.avail_threads_per_core[next_core_idx]);
+			cpumask_set_cpu(cpu_sibling, processed);
+		}
+
+		next_core_idx++;
+	}
+
+	free_cpumask_var(processed);
+
+	return 0;
+
+free_slots:
+	ne_free_core_slots();
+free_processed:
+	free_cpumask_var(processed);
+
+	return rc;
+}
+
 /**
  * ne_setup_cpu_pool() - Set the NE CPU pool after handling sanity checks such
  *			 as not sharing CPU cores with the primary / parent VM
@@ -177,7 +260,7 @@ static void ne_free_core_slots(void)
  *			 pool after the checks passed.
  * @cpu_pool:	The CPU mask used for setting NE CPU pool.
  *
- * The topology of the pool - online state, sibling masks, core ids - is read
+ * The topology of the pool - online state, sibling masks, core slots - is read
  * under the CPU hotplug read lock. The lock is dropped before the pool CPUs are
  * offlined, because remove_cpu() takes the write side of the same percpu-rwsem.
  * Nothing reads topology after the drop.
@@ -190,10 +273,8 @@ static void ne_free_core_slots(void)
  */
 static int ne_setup_cpu_pool(const struct cpumask *cpu_pool)
 {
-	int core_id = -1;
 	unsigned int cpu = 0;
 	unsigned int cpu_sibling = 0;
-	unsigned int i = 0;
 	int numa_node = -1;
 	int rc = -EINVAL;
 
@@ -211,7 +292,7 @@ static int ne_setup_cpu_pool(const struct cpumask *cpu_pool)
 
 	/*
 	 * Check if the CPUs are online, to further get info about them
-	 * e.g. numa node, core id, siblings.
+	 * e.g. numa node, siblings.
 	 */
 	for_each_cpu(cpu, cpu_pool)
 		if (cpu_is_offline(cpu)) {
@@ -289,45 +370,9 @@ static int ne_setup_cpu_pool(const struct cpumask *cpu_pool)
 		}
 	}
 
-	/* Calculate the number of threads from a full CPU core. */
-	cpu = cpumask_any(cpu_pool);
-	for_each_cpu(cpu_sibling, topology_sibling_cpumask(cpu))
-		ne_cpu_pool.nr_threads_per_core++;
-
-	ne_cpu_pool.nr_parent_vm_cores = nr_cpu_ids / ne_cpu_pool.nr_threads_per_core;
-
-	ne_cpu_pool.avail_threads_per_core = kzalloc_objs(*ne_cpu_pool.avail_threads_per_core,
-							  ne_cpu_pool.nr_parent_vm_cores);
-	if (!ne_cpu_pool.avail_threads_per_core) {
-		rc = -ENOMEM;
-
+	rc = ne_build_core_slots(cpu_pool);
+	if (rc < 0)
 		goto unlock_hotplug;
-	}
-
-	for (i = 0; i < ne_cpu_pool.nr_parent_vm_cores; i++)
-		if (!zalloc_cpumask_var(&ne_cpu_pool.avail_threads_per_core[i], GFP_KERNEL)) {
-			rc = -ENOMEM;
-
-			goto free_slots;
-		}
-
-	/*
-	 * Split the NE CPU pool in threads per core to keep the CPU topology
-	 * after offlining the CPUs.
-	 */
-	for_each_cpu(cpu, cpu_pool) {
-		core_id = topology_core_id(cpu);
-		if (core_id < 0 || core_id >= ne_cpu_pool.nr_parent_vm_cores) {
-			pr_err("%s: Invalid core id  %d for CPU %d\n",
-			       ne_misc_dev.name, core_id, cpu);
-
-			rc = -EINVAL;
-
-			goto free_slots;
-		}
-
-		cpumask_set_cpu(cpu, ne_cpu_pool.avail_threads_per_core[core_id]);
-	}
 
 	cpus_read_unlock();
 
@@ -365,13 +410,10 @@ static int ne_setup_cpu_pool(const struct cpumask *cpu_pool)
 		add_cpu(cpu);
 	ne_free_core_slots();
 	goto reset_pool;
-free_slots:
-	ne_free_core_slots();
 unlock_hotplug:
 	cpus_read_unlock();
 reset_pool:
 	ne_cpu_pool.nr_parent_vm_cores = 0;
-	ne_cpu_pool.nr_threads_per_core = 0;
 	ne_cpu_pool.numa_node = -1;
 	mutex_unlock(&ne_cpu_pool.mutex);
 
@@ -410,7 +452,6 @@ static void ne_teardown_cpu_pool(void)
 
 	ne_free_core_slots();
 	ne_cpu_pool.nr_parent_vm_cores = 0;
-	ne_cpu_pool.nr_threads_per_core = 0;
 	ne_cpu_pool.numa_node = -1;
 
 	mutex_unlock(&ne_cpu_pool.mutex);
@@ -647,7 +688,8 @@ static int ne_set_enclave_threads_per_core(struct ne_enclave *ne_enclave,
 		return -NE_ERR_VCPU_NOT_IN_CPU_POOL;
 	}
 
-	if (core_id >= ne_enclave->nr_parent_vm_cores) {
+	if (core_id >= min(ne_enclave->nr_parent_vm_cores,
+			   ne_cpu_pool.nr_parent_vm_cores)) {
 		dev_err_ratelimited(ne_misc_dev.this_device,
 				    "Invalid core id %d - ne_enclave\n", core_id);
 
@@ -1276,8 +1318,7 @@ static long ne_enclave_do_ioctl(struct file *file, unsigned int cmd, unsigned lo
 			return -NE_ERR_NOT_IN_INIT_STATE;
 		}
 
-		if (vcpu_id >= (ne_enclave->nr_parent_vm_cores *
-		    ne_enclave->nr_threads_per_core)) {
+		if (vcpu_id >= nr_cpu_ids) {
 			dev_err_ratelimited(ne_misc_dev.this_device,
 					    "vCPU id higher than max CPU id\n");
 
@@ -1566,18 +1607,22 @@ static void ne_enclave_remove_all_mem_region_entries(struct ne_enclave *ne_encla
  */
 static void ne_enclave_remove_all_vcpu_id_entries(struct ne_enclave *ne_enclave)
 {
+	unsigned int nr_cores = 0;
 	unsigned int cpu = 0;
 	unsigned int i = 0;
 
 	mutex_lock(&ne_cpu_pool.mutex);
 
-	for (i = 0; i < ne_enclave->nr_parent_vm_cores; i++) {
+	nr_cores = min(ne_enclave->nr_parent_vm_cores,
+		       ne_cpu_pool.nr_parent_vm_cores);
+
+	for (i = 0; i < nr_cores; i++)
 		for_each_cpu(cpu, ne_enclave->threads_per_core[i])
 			/* Update the available NE CPU pool. */
 			cpumask_set_cpu(cpu, ne_cpu_pool.avail_threads_per_core[i]);
 
+	for (i = 0; i < ne_enclave->nr_parent_vm_cores; i++)
 		free_cpumask_var(ne_enclave->threads_per_core[i]);
-	}
 
 	mutex_unlock(&ne_cpu_pool.mutex);
 
@@ -1816,7 +1861,6 @@ static int ne_create_vm_ioctl(struct ne_pci_dev *ne_pci_dev, u64 __user *slot_ui
 	mutex_lock(&ne_cpu_pool.mutex);
 
 	ne_enclave->nr_parent_vm_cores = ne_cpu_pool.nr_parent_vm_cores;
-	ne_enclave->nr_threads_per_core = ne_cpu_pool.nr_threads_per_core;
 	ne_enclave->numa_node = ne_cpu_pool.numa_node;
 
 	mutex_unlock(&ne_cpu_pool.mutex);
diff --git a/drivers/virt/nitro_enclaves/ne_misc_dev.h b/drivers/virt/nitro_enclaves/ne_misc_dev.h
index 4031d5d23eb00..283c17558f2a3 100644
--- a/drivers/virt/nitro_enclaves/ne_misc_dev.h
+++ b/drivers/virt/nitro_enclaves/ne_misc_dev.h
@@ -50,18 +50,17 @@ struct ne_mem_region {
  * @mem_size:			Enclave memory size.
  * @mm :			Enclave process abstraction mm data struct.
  * @nr_mem_regions:		Number of memory regions associated with the enclave.
- * @nr_parent_vm_cores :	The size of the threads per core array. The
- *				total number of CPU cores available on the
- *				parent / primary VM.
- * @nr_threads_per_core:	The number of threads that a full CPU core has.
+ * @nr_parent_vm_cores :	The size of the threads per core array, one slot
+ *				per full CPU core in the NE CPU pool the enclave
+ *				was created from.
  * @nr_vcpus:			Number of vcpus associated with the enclave.
  * @numa_node:			NUMA node of the enclave memory and CPUs.
  * @slot_uid:			Slot unique id mapped to the enclave.
  * @state:			Enclave state, updated during enclave lifetime.
- * @threads_per_core:		Enclave full CPU cores array, indexed by core id,
- *				consisting of cpumasks with all their threads.
- *				Full CPU cores are taken from the NE CPU pool
- *				and are available to the enclave.
+ * @threads_per_core:		Enclave full CPU cores array. Each cpumask in the
+ *				array holds the threads of one core taken from
+ *				the NE CPU pool and available to the enclave,
+ *				at the index of that core's slot in the pool.
  * @vcpu_ids:			Cpumask of the vCPUs that are set for the enclave.
  */
 struct ne_enclave {
@@ -76,7 +75,6 @@ struct ne_enclave {
 	struct mm_struct	*mm;
 	unsigned int		nr_mem_regions;
 	unsigned int		nr_parent_vm_cores;
-	unsigned int		nr_threads_per_core;
 	unsigned int		nr_vcpus;
 	int			numa_node;
 	u64			slot_uid;
-- 
2.47.1


  parent reply	other threads:[~2026-11-12  3:12 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-11 13:49 [PATCH 00/19] nitro_enclaves: Fix enclave release, command retries and the CPU pool Alexander Graf
2026-11-11 13:49 ` [PATCH 01/19] nitro_enclaves: Release the reference on a rejected region page Alexander Graf
2026-11-11 13:49 ` [PATCH 02/19] nitro_enclaves: Split freeing the slot from freeing the enclave Alexander Graf
2026-11-11 13:49 ` [PATCH 03/19] nitro_enclaves: Ignore the reply of a failed slot info command Alexander Graf
2026-11-11 13:49 ` [PATCH 04/19] nitro_enclaves: Retry a command the device asks us to retry Alexander Graf
2026-11-11 13:49 ` [PATCH 05/19] nitro_enclaves: Stop sending commands to a broken device Alexander Graf
2026-11-11 13:49 ` [PATCH 06/19] nitro_enclaves: Set up the device state before requesting its interrupts Alexander Graf
2026-11-11 13:49 ` [PATCH 07/19] nitro_enclaves: Wait for file operations before unbinding the PCI device Alexander Graf
2026-11-12  3:07 ` [PATCH 08/19] nitro_enclaves: Let the region tracking arrays fall back to vmalloc Alexander Graf
2026-11-12  3:07 ` [PATCH 09/19] nitro_enclaves: Do not return enclave threads to a replaced CPU pool Alexander Graf
2026-11-12  3:07 ` [PATCH 10/19] nitro_enclaves: Do not report a torn down CPU pool as set Alexander Graf
2026-11-12  3:07 ` [PATCH 11/19] nitro_enclaves: Keep the CPU pool when the new CPU list does not parse Alexander Graf
2026-11-12  3:07 ` [PATCH 12/19] nitro_enclaves: Put the CPU pool back when the new CPU list is refused Alexander Graf
2026-11-12  3:07 ` [PATCH 13/19] nitro_enclaves: Initialise the CPU pool mutex statically Alexander Graf
2026-11-12  3:07 ` [PATCH 14/19] nitro_enclaves: Build the boot-time CPU pool once the CPUs are online Alexander Graf
2026-11-12  3:07 ` [PATCH 15/19] nitro_enclaves: Set up the CPU pool under the CPU hotplug read lock Alexander Graf
2026-11-12  3:07 ` [PATCH 16/19] nitro_enclaves: Let 32-bit processes use the ioctl interface Alexander Graf
2026-11-12  3:07 ` Alexander Graf [this message]
2026-11-12  3:07 ` [PATCH 18/19] nitro_enclaves: Time the wait for the device to turn off by the clock Alexander Graf
2026-11-12  3:07 ` [PATCH 19/19] MAINTAINERS: Take over the Nitro Enclaves driver Alexander Graf

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=0151affce5b48462715b651945c9064a001dfa6e.1794393487.git.graf@amazon.com \
    --to=graf@amazon.com \
    --cc=arnd@arndb.de \
    --cc=aws-nitro-enclaves-devel@amazon.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=nh-open-source@amazon.com \
    /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

all inboxes | Powered by JetHome®