From: Alexander Graf <graf@amazon.com>
To: <linux-kernel@vger.kernel.org>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Arnd Bergmann <arnd@arndb.de>
Cc: The AWS Nitro Enclaves Team <aws-nitro-enclaves-devel@amazon.com>,
"Shuah Khan" <skhan@linuxfoundation.org>,
Randy Dunlap <rdunlap@infradead.org>,
Marek Szyprowski <m.szyprowski@samsung.com>,
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>, Jonathan Corbet <corbet@lwn.net>
Subject: [PATCH 28/40] nitro_enclaves: Add NE_SET_NOTIFY_EVENTFD
Date: Wed, 11 Nov 2026 22:59:32 +0000 [thread overview]
Message-ID: <9db6d42bdf77370c94a86d4098bbd6a71dcc1e7d.1794393491.git.graf@amazon.com> (raw)
In-Reply-To: <cover.1794300002.git.graf@amazon.com>
Nitro Sandboxes notify the parent through the interrupt multiplexer
when a sandbox kicks a virtqueue, so that the device backend in the
parent can wait for work on its queues on an eventfd.
Add NE_SET_NOTIFY_EVENTFD on the enclave fd. It binds an eventfd to the
notify index of a queue, and the multiplexer handler signals it on a
kick. Binding again replaces the eventfd and NE_EVENTFD_UNBIND removes
it. A bind delivers a kick that was already pending, and an unbind drops
one, so that the next owner of the index does not get it. A notify index
is device-wide, so a bind only takes the indices NE_ADD_DEVICE reported
to the same enclave.
Signed-off-by: Alexander Graf <graf@amazon.com>
---
drivers/virt/nitro_enclaves/Kconfig | 1 +
drivers/virt/nitro_enclaves/ne_misc_dev.c | 418 ++++++++++++++++++++++
drivers/virt/nitro_enclaves/ne_misc_dev.h | 31 ++
drivers/virt/nitro_enclaves/ne_pci_dev.c | 41 ++-
include/uapi/linux/nitro_enclaves.h | 66 ++++
5 files changed, 547 insertions(+), 10 deletions(-)
diff --git a/drivers/virt/nitro_enclaves/Kconfig b/drivers/virt/nitro_enclaves/Kconfig
index 7b2456cdbb9fb..c78a5c654a241 100644
--- a/drivers/virt/nitro_enclaves/Kconfig
+++ b/drivers/virt/nitro_enclaves/Kconfig
@@ -10,6 +10,7 @@ config NITRO_ENCLAVES
depends on (ARM64 || X86) && HOTPLUG_CPU && PCI && SMP
select CMA
select DMA_CMA
+ select EVENTFD
help
This driver consists of support for enclave lifetime management
for Nitro Enclaves (NE).
diff --git a/drivers/virt/nitro_enclaves/ne_misc_dev.c b/drivers/virt/nitro_enclaves/ne_misc_dev.c
index 7698c7210eb86..bc78580bad475 100644
--- a/drivers/virt/nitro_enclaves/ne_misc_dev.c
+++ b/drivers/virt/nitro_enclaves/ne_misc_dev.c
@@ -9,6 +9,7 @@
*/
#include <linux/anon_inodes.h>
+#include <linux/bitops.h>
#include <linux/bsearch.h>
#include <linux/build_bug.h>
#include <linux/capability.h>
@@ -17,6 +18,7 @@
#include <linux/cpumask.h>
#include <linux/device.h>
#include <linux/dma-map-ops.h>
+#include <linux/eventfd.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/highmem.h>
@@ -48,6 +50,7 @@
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/uio.h>
+#include <linux/xarray.h>
#include <uapi/linux/vm_sockets.h>
#include "ne_misc_dev.h"
@@ -1773,6 +1776,357 @@ static int ne_start_enclave_ioctl(struct ne_enclave *ne_enclave,
return 0;
}
+/**
+ * ne_mux_drop_bit() - Take the bit of an enclave queue out of the bitmap of its
+ * pooled vector.
+ * @ne_pci_dev: Private data associated with the PCI device.
+ * @notify_index: Notify index of the queue whose bit to take out.
+ *
+ * The handler leaves a bit it finds no binding for set, so that a bind can take
+ * it and deliver the kick it stands for. Once the binding is gone, the bind
+ * that bit was waiting for has already happened, and the next bind of the index
+ * belongs to whoever holds the queue by then, so leaving the bit would hand one
+ * caller's kick to another. It also costs every later interrupt on the vector a
+ * lookup for a bit nothing will claim. Take it out.
+ *
+ * The caller takes the binding out of the xarray first, so at this point no
+ * binding for the index is left to look up, and the kick this destroys is one
+ * no later scan could have delivered. In the other order a kick the device
+ * recorded in between would be left with a live binding and no interrupt of
+ * its own still to come.
+ *
+ * A kick the enclave raises between the two is dropped. It is a kick that
+ * arrived as its caller stopped listening, so the ioctl documentation says a
+ * release leaves a later bind of the index no kick to collect.
+ *
+ * Context: Process context.
+ */
+static void ne_mux_drop_bit(struct ne_pci_dev *ne_pci_dev, u32 notify_index)
+{
+ u32 nr_vecs = 0;
+ u32 vec_rel = 0;
+ u32 bit = 0;
+
+ /*
+ * ne_mux_teardown() sets the pool size back to zero, so take the size
+ * once and do the arithmetic on that copy, as a bind does. Reading the
+ * field again below would divide by whatever it holds by then.
+ */
+ nr_vecs = READ_ONCE(ne_pci_dev->mux_nr_vecs);
+ if (!nr_vecs)
+ return;
+
+ vec_rel = notify_index % nr_vecs;
+ bit = notify_index / nr_vecs;
+ if (bit >= NE_MUX_MAX_BITS)
+ return;
+
+ if (!ne_pci_dev->mux || !ne_pci_dev->mux[vec_rel].bitmap)
+ return;
+
+ clear_bit(bit, (unsigned long *)ne_pci_dev->mux[vec_rel].bitmap);
+}
+
+/**
+ * ne_del_notify_eventfd() - Release the eventfd bound to the interrupt of an
+ * enclave queue.
+ * @ne_enclave : Private data associated with the current enclave.
+ * @ne_pci_dev: Private data associated with the PCI device, taken by
+ * the caller so that the release runs on the device the
+ * caller looked the enclave up on.
+ * @notify_index: Notify index of the queue to release.
+ *
+ * Context: Process context.
+ * Return:
+ * * 0 on success.
+ * * Negative return value on failure.
+ */
+static int ne_del_notify_eventfd(struct ne_enclave *ne_enclave,
+ struct ne_pci_dev *ne_pci_dev, u32 notify_index)
+{
+ struct ne_notify_binding *notify_binding = NULL;
+ struct ne_mux_binding *binding = NULL;
+ struct ne_notify_binding *entry = NULL;
+ int rc;
+
+ rc = mutex_lock_killable(&ne_enclave->enclave_info_mutex);
+ if (rc)
+ return rc;
+
+ list_for_each_entry(entry, &ne_enclave->notify_bindings,
+ notify_binding_list_entry) {
+ if (entry->notify_index == notify_index) {
+ notify_binding = entry;
+
+ break;
+ }
+ }
+
+ if (!notify_binding) {
+ mutex_unlock(&ne_enclave->enclave_info_mutex);
+
+ return -ENOENT;
+ }
+
+ binding = xa_erase(&ne_pci_dev->notify_xa, notify_index);
+ if (binding) {
+ /*
+ * A handler that looked the binding up before the erase can
+ * take the bit after it and then look the index up again. A
+ * bind of the index can run as soon as the unbind returns, so
+ * wait for that handler first. Otherwise its second lookup
+ * finds the new binding and signals a kick the unbind drops.
+ */
+ synchronize_rcu();
+
+ ne_mux_binding_free_rcu(binding);
+ }
+
+ ne_mux_drop_bit(ne_pci_dev, notify_index);
+
+ list_del(¬ify_binding->notify_binding_list_entry);
+
+ mutex_unlock(&ne_enclave->enclave_info_mutex);
+
+ kfree(notify_binding);
+
+ return 0;
+}
+
+/**
+ * ne_enclave_owns_notify() - Check that NE_ADD_DEVICE reported a notify index
+ * to an enclave.
+ * @ne_enclave : Private data associated with the current enclave.
+ * @notify_index: Notify index to look for.
+ *
+ * Context: Process context. This function is called with the ne_enclave mutex
+ * held.
+ * Return: true if one of the enclave's devices has the notify index.
+ */
+static bool ne_enclave_owns_notify(struct ne_enclave *ne_enclave,
+ u32 notify_index)
+{
+ struct ne_device_wiring *dev = NULL;
+ unsigned int i = 0;
+
+ list_for_each_entry(dev, &ne_enclave->devices, list_entry)
+ for (i = 0; i < dev->num_vqs; i++)
+ if (dev->notify_index[i] == notify_index)
+ return true;
+
+ return false;
+}
+
+/**
+ * ne_set_notify_eventfd() - Bind an eventfd to the interrupt of an enclave
+ * queue.
+ * @ne_enclave : Private data associated with the current enclave.
+ * @notify: The eventfd and the notify index.
+ *
+ * Context: Process context.
+ * Return:
+ * * 0 on success.
+ * * Negative return value on failure.
+ */
+static int ne_set_notify_eventfd(struct ne_enclave *ne_enclave,
+ struct ne_notify_eventfd *notify)
+{
+ struct ne_pci_dev *ne_pci_dev = ne_devs.ne_pci_dev;
+ struct ne_notify_binding *notify_binding = NULL;
+ struct ne_mux_binding *binding = NULL;
+ struct ne_notify_binding *entry = NULL;
+ struct ne_mux_binding *old = NULL;
+ struct eventfd_ctx *ctx = NULL;
+ bool rebind = false;
+ u32 nr_vecs = 0;
+ u32 max_bit = 0;
+ int rc = -EINVAL;
+ u32 vec_rel = 0;
+ u32 bit = 0;
+
+ /*
+ * ne_mux_teardown() sets the pool size back to zero, so take the size
+ * once and do the arithmetic on that copy. Reading the field again
+ * below would divide by whatever it holds by then.
+ */
+ nr_vecs = READ_ONCE(ne_pci_dev->mux_nr_vecs);
+ if (!nr_vecs)
+ return -EINVAL;
+
+ vec_rel = notify->notify_index % nr_vecs;
+ bit = notify->notify_index / nr_vecs;
+ if (bit >= NE_MUX_MAX_BITS)
+ return -EINVAL;
+
+ if (notify->fd == NE_EVENTFD_UNBIND)
+ return ne_del_notify_eventfd(ne_enclave, ne_pci_dev,
+ notify->notify_index);
+
+ ctx = eventfd_ctx_fdget(notify->fd);
+ if (IS_ERR(ctx))
+ return PTR_ERR(ctx);
+
+ binding = kzalloc_obj(*binding);
+ if (!binding) {
+ rc = -ENOMEM;
+
+ goto put_ctx;
+ }
+
+ binding->ctx = ctx;
+
+ notify_binding = kzalloc_obj(*notify_binding);
+ if (!notify_binding) {
+ rc = -ENOMEM;
+
+ goto free_binding;
+ }
+
+ notify_binding->notify_index = notify->notify_index;
+
+ rc = mutex_lock_killable(&ne_enclave->enclave_info_mutex);
+ if (rc)
+ goto free_notify_binding;
+
+ /*
+ * A notify index names a notification across the whole NE PCI device,
+ * so an enclave only binds the ones NE_ADD_DEVICE reported to it.
+ */
+ if (!ne_enclave_owns_notify(ne_enclave, notify->notify_index)) {
+ rc = -EINVAL;
+
+ goto unlock_mutex;
+ }
+
+ list_for_each_entry(entry, &ne_enclave->notify_bindings,
+ notify_binding_list_entry) {
+ if (entry->notify_index == notify->notify_index) {
+ rebind = true;
+
+ break;
+ }
+ }
+
+ if (rebind) {
+ /*
+ * This enclave owns the notify index and, by the invariant the
+ * fresh bind below upholds, is the only owner of its xarray
+ * entry, so the entry that comes back is the binding it set
+ * last time.
+ */
+ old = xa_store(&ne_pci_dev->notify_xa, notify->notify_index, binding,
+ GFP_KERNEL);
+ if (xa_is_err(old)) {
+ rc = xa_err(old);
+
+ goto unlock_mutex;
+ }
+
+ if (old)
+ ne_mux_binding_free_rcu(old);
+
+ kfree(notify_binding);
+ } else {
+ /*
+ * Bind against an empty entry so that an enclave which asks
+ * for a notify index another one still owns leaves that owner's
+ * eventfd in place.
+ */
+ old = xa_cmpxchg(&ne_pci_dev->notify_xa, notify->notify_index, NULL,
+ binding, GFP_KERNEL);
+ if (xa_is_err(old)) {
+ rc = xa_err(old);
+
+ goto unlock_mutex;
+ }
+
+ if (old) {
+ rc = -EEXIST;
+
+ goto unlock_mutex;
+ }
+
+ list_add(¬ify_binding->notify_binding_list_entry,
+ &ne_enclave->notify_bindings);
+ }
+
+ /*
+ * Raise the scan bound of the vector the queue shares. Enclaves bind
+ * under their own mutex, so two of them can reach the same vector at
+ * once and a plain read-modify-write would lose a raise and with it a
+ * queue kick.
+ */
+ max_bit = READ_ONCE(ne_pci_dev->mux[vec_rel].max_bit);
+ while (bit > max_bit)
+ if (try_cmpxchg(&ne_pci_dev->mux[vec_rel].max_bit, &max_bit,
+ bit))
+ break;
+
+ /*
+ * The device may have set the bit of this queue before there was a
+ * binding to deliver it on, and the handler leaves a bit it has no
+ * binding for set, so that bit is still there to be taken. Take it
+ * here, after the binding is in the xarray so that a handler taking it
+ * instead of this path finds the binding, and after the raise above so
+ * that such a handler reaches the word the bit is in. The handler takes
+ * a bit with an atomic read-modify-write on the same word, so exactly
+ * one of the two consumes it. The bitmap words are the width the device
+ * writes them in, which is the width the bit numbering of the handler
+ * is in as well.
+ */
+ if (test_and_clear_bit(bit, (unsigned long *)ne_pci_dev->mux[vec_rel].bitmap))
+ eventfd_signal(binding->ctx);
+
+ mutex_unlock(&ne_enclave->enclave_info_mutex);
+
+ return 0;
+
+unlock_mutex:
+ mutex_unlock(&ne_enclave->enclave_info_mutex);
+free_notify_binding:
+ kfree(notify_binding);
+free_binding:
+ kfree(binding);
+put_ctx:
+ eventfd_ctx_put(ctx);
+
+ return rc;
+}
+
+/**
+ * ne_enclave_remove_all_notify_binding_entries() - Remove all the enclave queue
+ * bindings from the device
+ * wide bindings xarray.
+ * @ne_enclave : Private data associated with the current enclave.
+ * @ne_pci_dev: Private data associated with the PCI device, taken by
+ * the caller so that the removal runs on the device the
+ * caller looked the enclave up on.
+ *
+ * Context: Process context. This function is called with the enclave
+ * information mutex held.
+ */
+static void ne_enclave_remove_all_notify_binding_entries(struct ne_enclave *ne_enclave,
+ struct ne_pci_dev *ne_pci_dev)
+{
+ struct ne_notify_binding *notify_binding = NULL;
+ struct ne_notify_binding *notify_binding_tmp = NULL;
+ struct ne_mux_binding *binding = NULL;
+
+ list_for_each_entry_safe(notify_binding, notify_binding_tmp,
+ &ne_enclave->notify_bindings,
+ notify_binding_list_entry) {
+ binding = xa_erase(&ne_pci_dev->notify_xa, notify_binding->notify_index);
+ if (binding)
+ ne_mux_binding_free_rcu(binding);
+
+ ne_mux_drop_bit(ne_pci_dev, notify_binding->notify_index);
+
+ list_del(¬ify_binding->notify_binding_list_entry);
+
+ kfree(notify_binding);
+ }
+}
+
/**
* ne_enclave_do_ioctl() - Ioctl function provided by the enclave file.
* @file: File associated with this ioctl function.
@@ -2230,6 +2584,22 @@ static long ne_enclave_do_ioctl(struct file *file, unsigned int cmd, unsigned lo
return 0;
}
+ case NE_EA_IOCTL(NE_SET_NOTIFY_EVENTFD): {
+ struct ne_notify_eventfd __user *uargs = (void __user *)arg;
+ unsigned int usize = _IOC_SIZE(cmd);
+ struct ne_notify_eventfd args;
+ int rc;
+
+ if (usize < offsetofend(struct ne_notify_eventfd, notify_index))
+ return -EINVAL;
+
+ rc = copy_struct_from_user(&args, sizeof(args), uargs, usize);
+ if (rc)
+ return rc;
+
+ return ne_set_notify_eventfd(ne_enclave, &args);
+ }
+
default:
return -ENOTTY;
}
@@ -2415,6 +2785,27 @@ int ne_enclave_free_slot(struct ne_pci_dev *ne_pci_dev,
rc = ne_do_request(pdev, ENCLAVE_STOP,
&enclave_stop_request, sizeof(enclave_stop_request),
&cmd_reply, sizeof(cmd_reply));
+
+ /*
+ * The notify index of a queue belongs to the slot, so the device is free
+ * to give it to another enclave once the slot is freed. Take the
+ * bindings of this enclave out of the xarray the handler looks them up
+ * in before that, or a bind for the reissued index finds the entry
+ * still taken. This happens whether or not the stop went through,
+ * because an unbind of the device tears the multiplexer down next.
+ *
+ * A handler that looked up a binding of this enclave before it was
+ * erased here can still be running. Were the index reissued under it,
+ * it would take the bit of a notification the device raises for the
+ * next owner and signal this enclave's eventfd instead, so wait for it
+ * before the slot goes.
+ */
+ if (!list_empty(&ne_enclave->notify_bindings)) {
+ ne_enclave_remove_all_notify_binding_entries(ne_enclave, ne_pci_dev);
+
+ synchronize_rcu();
+ }
+
if (rc < 0)
return rc;
@@ -2437,6 +2828,9 @@ int ne_enclave_free_slot(struct ne_pci_dev *ne_pci_dev,
*/
static void ne_enclave_free(struct ne_enclave *ne_enclave)
{
+ struct ne_device_wiring *dev_tmp = NULL;
+ struct ne_device_wiring *dev = NULL;
+
ne_enclave_remove_all_contig_regions(&ne_enclave->contig_regions_list);
ne_enclave_remove_all_contig_regions(&ne_enclave->device_regions_list);
ne_enclave_remove_all_mem_region_entries(ne_enclave);
@@ -2445,6 +2839,9 @@ static void ne_enclave_free(struct ne_enclave *ne_enclave)
if (ne_enclave->info_file)
fput(ne_enclave->info_file);
+ list_for_each_entry_safe(dev, dev_tmp, &ne_enclave->devices, list_entry)
+ kfree(dev);
+
kfree(ne_enclave);
}
@@ -2686,6 +3083,8 @@ static int ne_create_vm_ioctl(struct ne_pci_dev *ne_pci_dev, u64 flags,
init_waitqueue_head(&ne_enclave->eventq);
ne_enclave->has_event = false;
+ INIT_LIST_HEAD(&ne_enclave->notify_bindings);
+ INIT_LIST_HEAD(&ne_enclave->devices);
mutex_init(&ne_enclave->enclave_info_mutex);
ne_enclave->max_mem_regions = cmd_reply.alloc.mem_regions;
INIT_LIST_HEAD(&ne_enclave->contig_regions_list);
@@ -4255,6 +4654,7 @@ static int ne_add_device_ioctl(struct ne_enclave *ne_enclave,
struct ne_shm_region *shm_regions = NULL;
const struct ne_vq_wiring *wiring = NULL;
struct slot_add_device_req *req = NULL;
+ struct ne_device_wiring *dev = NULL;
struct ne_contig_mem *info = NULL;
LIST_HEAD(regions);
u8 buf[NE_MAX_VQS * sizeof(struct ne_vq_wiring)];
@@ -4346,6 +4746,17 @@ static int ne_add_device_ioctl(struct ne_enclave *ne_enclave,
goto free;
}
+ /*
+ * Allocate the record of what the device reports before it is told
+ * about the device, so that nothing can fail once it has been.
+ */
+ dev = kzalloc_obj(*dev);
+ if (!dev) {
+ rc = -ENOMEM;
+
+ goto free;
+ }
+
for (i = 0; i < args->num_shm_regions; i++) {
rc = ne_add_device_region(ne_enclave, &shm_regions[i], req,
®ions);
@@ -4438,10 +4849,16 @@ static int ne_add_device_ioctl(struct ne_enclave *ne_enclave,
args->vq_info[i].notify_index = wiring[i].notify_index;
args->vq_info[i].msix_entry = wiring[i].msix_entry;
args->vq_info[i].doorbell_offset = wiring[i].doorbell_offset;
+
+ dev->notify_index[i] = wiring[i].notify_index;
}
args->device_uid = cmd_reply.add_device.device_uid;
+ dev->num_vqs = args->num_vqs;
+ list_add(&dev->list_entry, &ne_enclave->devices);
+ dev = NULL;
+
rc = 0;
free:
@@ -4452,6 +4869,7 @@ static int ne_add_device_ioctl(struct ne_enclave *ne_enclave,
* the enclave, leaving nothing here.
*/
ne_enclave_remove_all_contig_regions(®ions);
+ kfree(dev);
kfree(req);
kfree(shm_regions);
diff --git a/drivers/virt/nitro_enclaves/ne_misc_dev.h b/drivers/virt/nitro_enclaves/ne_misc_dev.h
index 4f695f1410aec..57e20fe3ee1da 100644
--- a/drivers/virt/nitro_enclaves/ne_misc_dev.h
+++ b/drivers/virt/nitro_enclaves/ne_misc_dev.h
@@ -112,6 +112,29 @@ struct ne_mem_region {
u64 userspace_addr;
};
+/**
+ * struct ne_device_wiring - What %NE_ADD_DEVICE reported for one device of an
+ * enclave.
+ * @list_entry: Entry in the list of the enclave's devices.
+ * @num_vqs: Number of entries of @notify_index.
+ * @notify_index: Notify index of each virtqueue of the device.
+ */
+struct ne_device_wiring {
+ struct list_head list_entry;
+ u32 num_vqs;
+ u32 notify_index[NE_MAX_VQS_PER_DEVICE];
+};
+
+/**
+ * struct ne_notify_binding - Entry in the list of enclave virtqueue bindings.
+ * @notify_binding_list_entry: Entry in the list of enclave queue bindings.
+ * @notify_index: Notify index of the bound queue.
+ */
+struct ne_notify_binding {
+ struct list_head notify_binding_list_entry;
+ unsigned long notify_index;
+};
+
/**
* struct ne_enclave - Per-enclave data used for enclave lifetime management.
* @enclave_info_mutex : Mutex for accessing this internal state.
@@ -139,6 +162,12 @@ struct ne_mem_region {
* so that the device keeps the page after user space
* has closed the descriptor. NULL until
* %NE_SET_INFO_PAGE is called.
+ * @notify_bindings: List of the virtqueues this enclave has bound an
+ * eventfd to.
+ * @devices: List of struct ne_device_wiring, one for each device
+ * %NE_ADD_DEVICE gave the enclave. The notify indices
+ * in it are the only ones the enclave may bind an
+ * eventfd to.
* @max_mem_regions: The maximum number of memory regions that can be
* handled by the hypervisor.
* @mem_regions_list: Enclave user space memory regions list.
@@ -172,6 +201,8 @@ struct ne_enclave {
bool has_event;
u64 id;
struct file *info_file;
+ struct list_head notify_bindings;
+ struct list_head devices;
u64 max_mem_regions;
struct list_head mem_regions_list;
u64 mem_size;
diff --git a/drivers/virt/nitro_enclaves/ne_pci_dev.c b/drivers/virt/nitro_enclaves/ne_pci_dev.c
index de96d68906bbf..a079abb2b4618 100644
--- a/drivers/virt/nitro_enclaves/ne_pci_dev.c
+++ b/drivers/virt/nitro_enclaves/ne_pci_dev.c
@@ -580,12 +580,15 @@ void ne_mux_binding_free_rcu(struct ne_mux_binding *binding)
* has a binding for.
* @mux_vec: Multiplexer state of the pooled vector.
*
- * A bit leaves the bitmap by the same atomic read-modify-write that decides to
- * deliver it, so a bit this vector has no binding for stays set. It is left set
- * on purpose: a queue can be kicked before user space has bound an eventfd to
- * it, and a bit taken out of the bitmap on the strength of a binding that is not
+ * A bit is delivered by an atomic read-modify-write that clears it as it reads
+ * it, so a bit this vector has no binding for stays set. It is left set on
+ * purpose: a queue can be kicked before user space has bound an eventfd to it,
+ * and a bit taken out of the bitmap on the strength of a binding that is not
* there yet is a kick nobody can ever be told about. The bit waits instead, and
- * the bind of that queue takes it and delivers it.
+ * the bind of that queue takes it and delivers it. A bit is cleared without
+ * being delivered only for an index whose binding has just been erased: by
+ * ne_mux_drop_bit(), or by a scan that took the bit and then found the binding
+ * gone.
*
* So a set bit is consumed at most once, and exactly once if a scan finds a
* binding for it or a bind for its index runs. A bit whose index is never bound
@@ -598,8 +601,8 @@ void ne_mux_binding_free_rcu(struct ne_mux_binding *binding)
*
* Context: Interrupt context. Called with the RCU read-side held.
* Return:
- * * true if the scan found no bit it could deliver, false if it found at least
- * one.
+ * * true if the scan found no set bit with a binding, false if it found at
+ * least one.
*/
static bool ne_mux_scan(struct ne_mux_vec *mux_vec)
{
@@ -626,8 +629,26 @@ static bool ne_mux_scan(struct ne_mux_vec *mux_vec)
empty = false;
- if (test_and_clear_bit(bit, (unsigned long *)mux_vec->bitmap))
- eventfd_signal(binding->ctx);
+ if (!test_and_clear_bit(bit, (unsigned long *)mux_vec->bitmap))
+ continue;
+
+ /*
+ * A rebind can replace the binding between the load above
+ * and the bit taken here. The rebind stores its binding
+ * before it tries to take the bit, and test_and_clear_bit()
+ * is fully ordered on both sides, also when it finds the bit
+ * clear, so a bit taken here after that attempt sees the new
+ * binding. Load it again and signal the binding that load
+ * returns, or drop the kick if the entry is gone. An unbind
+ * waits for this handler before the index can be bound
+ * again, so this load never returns a binding made after
+ * the unbind.
+ */
+ binding = xa_load(&ne_pci_dev->notify_xa, notify_index);
+ if (!binding)
+ continue;
+
+ eventfd_signal(binding->ctx);
}
}
@@ -661,7 +682,7 @@ static irqreturn_t ne_mux_handler(int irq, void *data)
* whatever the bitmap holds, so such a bit has an interrupt of its own
* still to come and reaches its eventfd on that one. Scanning again,
* reading the bound again, delivers it on this interrupt instead, so stop
- * on the first scan that delivers nothing. Stopping on an empty bitmap
+ * on the first scan that finds no bound bit. Stopping on an empty bitmap
* instead would wait for a read a bit no bind is coming for never gives.
* NE_MUX_MAX_PASSES caps the scans either way, so a device that goes on
* setting bits cannot hold the handler.
diff --git a/include/uapi/linux/nitro_enclaves.h b/include/uapi/linux/nitro_enclaves.h
index 6f83a8b0762b3..ff1b32e380e7b 100644
--- a/include/uapi/linux/nitro_enclaves.h
+++ b/include/uapi/linux/nitro_enclaves.h
@@ -1061,4 +1061,70 @@ struct ne_enclave_start_info {
__u64 enclave_cid;
};
+/**
+ * NE_EVENTFD_UNBIND - Value of an eventfd field that releases the binding the
+ * caller holds, instead of naming an eventfd to bind. A kick
+ * the enclave raises as the release lands is dropped, so a
+ * caller that binds the queue again gets no signal for it.
+ * Any other negative value names no open file and is an
+ * error.
+ */
+#define NE_EVENTFD_UNBIND (-1)
+
+/**
+ * NE_SET_NOTIFY_EVENTFD - The command is used to bind an eventfd to the
+ * interrupt of one enclave virtqueue. When the enclave
+ * kicks the queue, the NE PCI device raises the
+ * interrupt the queue shares and the driver signals the
+ * eventfd. A queue the caller has already bound is
+ * rebound to the new eventfd, and the driver drops its
+ * reference on the one it replaces. A kick that is
+ * already being delivered when the rebind lands is
+ * signalled on the outgoing eventfd, so a caller that
+ * rebinds a queue an enclave is still kicking can see
+ * one last kick arrive on the eventfd it replaced.
+ * %NE_EVENTFD_UNBIND
+ * releases the binding the caller holds on the queue.
+ * The ioctl can be invoked on the enclave fd.
+ *
+ * Context: Process context.
+ * Return:
+ * * 0 - Logic successfully completed.
+ * * -1 - There was a failure in the ioctl logic.
+ * On failure, errno is set to:
+ * * ENODEV - The NE PCI device is not available.
+ * * EFAULT - Argument read failure.
+ * * EINVAL - The argument is shorter than @notify_index,
+ * the NE PCI device shares no interrupts,
+ * the notify index addresses a bit outside
+ * the bitmap page of the vector it shares,
+ * %NE_ADD_DEVICE did not report the notify
+ * index to this enclave, or the given fd is
+ * not an eventfd.
+ * * EBADF - There is no open file for the given fd, which
+ * is the case for every negative fd other
+ * than %NE_EVENTFD_UNBIND.
+ * * E2BIG - The argument is longer than this kernel knows
+ * about and the excess is not all zero.
+ * * ENOMEM - Memory allocation failure.
+ * * EEXIST - Another enclave holds a binding on the queue.
+ * * ENOENT - The caller holds no binding on the queue to
+ * release.
+ */
+#define NE_SET_NOTIFY_EVENTFD _IOW(0xAE, 0x28, struct ne_notify_eventfd)
+
+/**
+ * struct ne_notify_eventfd - Eventfd to signal on an enclave virtqueue kick
+ * (in).
+ * @fd: Eventfd to signal when the queue is kicked, or
+ * %NE_EVENTFD_UNBIND to release the binding on the
+ * queue (in).
+ * @notify_index: Index the enclave writes to notify the NE PCI device of
+ * the virtqueue (in).
+ */
+struct ne_notify_eventfd {
+ __s32 fd;
+ __u32 notify_index;
+};
+
#endif /* _UAPI_LINUX_NITRO_ENCLAVES_H_ */
--
2.47.1
next prev parent reply other threads:[~2026-11-11 23:07 UTC|newest]
Thread overview: 41+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-11 22:41 [PATCH 00/40] nitro_enclaves: Add support for Nitro Sandboxes Alexander Graf
2026-11-11 22:41 ` [PATCH 01/40] nitro_enclaves: Allow the CPU pool to span NUMA nodes Alexander Graf
2026-11-11 22:41 ` [PATCH 02/40] nitro_enclaves: Add NE_ADD_ANY_VCPU Alexander Graf
2026-11-11 22:41 ` [PATCH 03/40] nitro_enclaves: Expose CPU pool state under sysfs Alexander Graf
2026-11-11 22:41 ` [PATCH 04/40] nitro_enclaves: Document multi-NUMA CPU pools Alexander Graf
2026-11-11 22:41 ` [PATCH 05/40] dma-contiguous: add dma_get_numa_cma_area() accessor Alexander Graf
2026-11-11 22:41 ` [PATCH 06/40] nitro_enclaves: Add NE_ALLOC_MEMORY ioctl Alexander Graf
2026-11-11 22:41 ` [PATCH 07/40] nitro_enclaves: Add NE_MEMORY_SHARED Alexander Graf
2026-11-11 22:41 ` [PATCH 08/40] nitro_enclaves: Add NE_ADD_MEMORY_REGION Alexander Graf
2026-11-11 22:41 ` [PATCH 09/40] nitro_enclaves: Document the memory pool Alexander Graf
2026-11-11 22:41 ` [PATCH 10/40] nitro_enclaves: Add NE_CREATE_VM2 with a launch flag word Alexander Graf
2026-11-11 22:41 ` [PATCH 11/40] nitro_enclaves: Add NE_ENCLAVE_PCIE_MODE launch flag Alexander Graf
2026-11-11 22:41 ` [PATCH 12/40] nitro_enclaves: Name the device's error codes in the UAPI header Alexander Graf
2026-11-11 22:41 ` [PATCH 13/40] nitro_enclaves: Log a reached device limit at info level Alexander Graf
2026-11-11 22:56 ` [PATCH 14/40] nitro_enclaves: Add NE_SET_INFO_PAGE Alexander Graf
2026-11-11 22:59 ` [PATCH 15/40] nitro_enclaves: Document the info page in the memory pool Alexander Graf
2026-11-11 22:59 ` [PATCH 16/40] nitro_enclaves: Turn the command reply into a per-command union Alexander Graf
2026-11-11 22:59 ` [PATCH 17/40] nitro_enclaves: Add the slot pool donation commands Alexander Graf
2026-11-11 22:59 ` [PATCH 18/40] nitro_enclaves: Donate memory for the slot pool Alexander Graf
2026-11-11 22:59 ` [PATCH 19/40] nitro_enclaves: Add the slot pool growth commands Alexander Graf
2026-11-11 22:59 ` [PATCH 20/40] nitro_enclaves: Add a helper to grow the slot pool Alexander Graf
2026-11-11 22:59 ` [PATCH 21/40] nitro_enclaves: Document " Alexander Graf
2026-11-11 22:59 ` [PATCH 22/40] nitro_enclaves: Expose slot pool state under sysfs Alexander Graf
2026-11-11 22:59 ` [PATCH 23/40] nitro_enclaves: Add the device add command Alexander Graf
2026-11-11 22:59 ` [PATCH 24/40] nitro_enclaves: Add NE_ADD_DEVICE Alexander Graf
2026-11-11 22:59 ` [PATCH 25/40] nitro_enclaves: Read the interrupt multiplexer geometry at probe Alexander Graf
2026-11-11 22:59 ` [PATCH 26/40] nitro_enclaves: Donate a bitmap page per multiplexed vector Alexander Graf
2026-11-11 22:59 ` [PATCH 27/40] nitro_enclaves: Dispatch multiplexed interrupts to per-queue bindings Alexander Graf
2026-11-11 22:59 ` Alexander Graf [this message]
2026-11-11 22:59 ` [PATCH 29/40] nitro_enclaves: Report the device's info notification Alexander Graf
2026-11-11 22:59 ` [PATCH 30/40] nitro_enclaves: Count multiplexed interrupts under debugfs Alexander Graf
2026-11-11 22:59 ` [PATCH 31/40] nitro_enclaves: Map the doorbell window at probe Alexander Graf
2026-11-11 22:59 ` [PATCH 32/40] nitro_enclaves: Add NE_SET_VRING_CALL Alexander Graf
2026-11-11 22:59 ` [PATCH 33/40] nitro_enclaves: Assert the layout of the ioctl argument structs Alexander Graf
2026-11-11 22:59 ` [PATCH 34/40] nitro_enclaves: Pass on the Nitro Sandbox launch flags Alexander Graf
2026-11-11 22:59 ` [PATCH 35/40] nitro_enclaves: Add NE_ENCLAVE_DIRECT_MODE Alexander Graf
2026-11-11 22:59 ` [PATCH 36/40] nitro_enclaves: Add NE_ENCLAVE_PCIE_HOTPLUG_MODE Alexander Graf
2026-11-11 22:59 ` [PATCH 37/40] nitro_enclaves: Add NE_ENCLAVE_CPU_OVERCOMMIT_MODE Alexander Graf
2026-11-11 22:59 ` [PATCH 38/40] nitro_enclaves: Add NE_GET_CAPS Alexander Graf
2026-11-11 22:59 ` [PATCH 39/40] nitro_enclaves: Add NE_UPDATE_DEVICE_CONFIG Alexander Graf
2026-11-11 22:59 ` [PATCH 40/40] nitro_enclaves: Document the driver side of Nitro Sandboxes 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=9db6d42bdf77370c94a86d4098bbd6a71dcc1e7d.1794393491.git.graf@amazon.com \
--to=graf@amazon.com \
--cc=akpm@linux-foundation.org \
--cc=arnd@arndb.de \
--cc=aws-nitro-enclaves-devel@amazon.com \
--cc=corbet@lwn.net \
--cc=david@kernel.org \
--cc=gregkh@linuxfoundation.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
all inboxes | Powered by JetHome®