[PATCH v8 14/15] PCI/TSM: Wait for device contexts before removing a DSM
From: Aneesh Kumar K.V (Arm)
Date: Wed Nov 04 2026 - 11:28:36 EST
A PCI Device Security Manager (DSM) on physical function 0 (PF0) can be
removed while a sibling function still has an IOMMUFD vDEVICE object.
A vDEVICE represents a physical device within a virtual IOMMU for a guest
and holds a PCI/TSM context for its lifetime.
The context holds PCI device references to both the assigned function and
its DSM, but these references do not keep the PF0 Data Object Exchange
(DOE) mailbox alive. Ignoring -EBUSY from link disconnect allows removal
to reach pci_doe_destroy() while the context can still use the mailbox.
Reject new contexts and subfunction attachments once DSM removal starts.
Wait for existing contexts to be released before disconnecting the link
and allowing PCI removal to destroy the mailbox.
As with VFIO PCI removal, removal waits for userspace to release the
device. This patch does not provide an eventfd notification to request
that release.
Cc: Bjorn Helgaas <bhelgaas@xxxxxxxxxx>
Cc: Jonathan Cameron <jonathan.cameron@xxxxxxxxxx>
Cc: Alexey Kardashevskiy <aik@xxxxxxx>
Cc: Xu Yilun <yilun.xu@xxxxxxxxxxxxxxx>
Cc: Lukas Wunner <lukas@xxxxxxxxx>
Cc: Samuel Ortiz <sameo@xxxxxxxxxxxx>
Cc: Suzuki K Poulose <suzuki.poulose@xxxxxxx>
Cc: Jason Gunthorpe <jgg@xxxxxxxx>
Cc: Kevin Tian <kevin.tian@xxxxxxxxx>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@xxxxxxxxxx>
---
drivers/pci/tsm/core.c | 75 ++++++++++++++++++++++++++++++++++++++---
include/linux/pci-tsm.h | 9 +++--
2 files changed, 77 insertions(+), 7 deletions(-)
diff --git a/drivers/pci/tsm/core.c b/drivers/pci/tsm/core.c
index 5ded1a5a4f7e..2c77321fa779 100644
--- a/drivers/pci/tsm/core.c
+++ b/drivers/pci/tsm/core.c
@@ -10,10 +10,13 @@
#include <linux/bitfield.h>
#include <linux/iommufd.h>
+#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pci-doe.h>
#include <linux/pci-tsm.h>
+#include <linux/pid.h>
+#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/tsm.h>
@@ -355,7 +358,15 @@ struct pci_tsm_context *pci_tsm_context_get(struct pci_dev *pdev,
return ERR_PTR(-ENOMEM);
guard(mutex)(&pf0->lock);
- pf0->context_users++;
+ if (pf0->removing) {
+ kfree(context);
+ return ERR_PTR(-ENODEV);
+ }
+
+ if (!pf0->dsm_context_users)
+ reinit_completion(&pf0->dsm_context_drain_complete);
+
+ pf0->dsm_context_users++;
context->pf0 = pf0;
context->pdev = pci_dev_get(pdev);
context->dsm_dev = pci_dev_get(pf0->base_tsm.pdev);
@@ -369,8 +380,12 @@ void pci_tsm_context_put(struct pci_tsm_context *context)
down_read(&pci_tsm_rwsem);
mutex_lock(&pf0->lock);
- if (!WARN_ON(!pf0->context_users))
- pf0->context_users--;
+ if (WARN_ON(!pf0->dsm_context_users))
+ goto out_unlock;
+
+ if (!--pf0->dsm_context_users)
+ complete_all(&pf0->dsm_context_drain_complete);
+out_unlock:
mutex_unlock(&pf0->lock);
up_read(&pci_tsm_rwsem);
@@ -418,7 +433,7 @@ static int __pci_tsm_disconnect(struct pci_dev *pdev)
* teardown.
*/
guard(mutex)(&tsm_pf0->lock);
- if (tsm_pf0->context_users)
+ if (tsm_pf0->dsm_context_users)
return -EBUSY;
pci_tsm_walk_fns_reverse(pdev, remove_fn, NULL);
ops->disconnect(pdev);
@@ -454,6 +469,10 @@ static ssize_t disconnect_store(struct device *dev,
if (!sysfs_streq(buf, dev_name(&tsm_dev->dev)))
return -EINVAL;
+ if (is_link_tsm(tsm_dev) && is_pci_tsm_pf0(pdev) &&
+ to_pci_tsm_pf0(pdev->tsm)->removing)
+ return -ENODEV;
+
rc = pci_tsm_disconnect(pdev);
if (rc)
return rc;
@@ -664,6 +683,9 @@ int pci_tsm_pf0_constructor(struct pci_dev *pdev, struct pci_tsm_pf0 *tsm,
struct tsm_dev *tsm_dev)
{
mutex_init(&tsm->lock);
+ init_completion(&tsm->dsm_context_drain_complete);
+ tsm->dsm_context_users = 0;
+ tsm->removing = false;
tsm->doe_mb = pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG,
PCI_DOE_FEATURE_CMA);
if (!tsm->doe_mb) {
@@ -752,8 +774,47 @@ static void __pci_tsm_destroy(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
void pci_tsm_destroy(struct pci_dev *pdev)
{
- guard(rwsem_write)(&pci_tsm_rwsem);
+ struct pci_tsm_pf0 *pf0 = NULL;
+ struct completion *drained;
+ bool interrupted = false;
+ long rc;
+
+ down_write(&pci_tsm_rwsem);
+ if (pdev->tsm &&
+ is_link_tsm(pdev->tsm->tsm_dev) && is_pci_tsm_pf0(pdev)) {
+ pf0 = to_pci_tsm_pf0(pdev->tsm);
+ drained = &pf0->dsm_context_drain_complete;
+ mutex_lock(&pf0->lock);
+ pf0->removing = true;
+
+ mutex_unlock(&pf0->lock);
+ /* An unused DSM may never have completed dsm_context_drain_complete. */
+ if (pf0->dsm_context_users)
+ rc = try_wait_for_completion(drained);
+ else
+ rc = 1;
+
+ /* Context release needs the read side of pci_tsm_rwsem. */
+ up_write(&pci_tsm_rwsem);
+ while (rc <= 0) {
+ if (interrupted) {
+ rc = wait_for_completion_timeout(drained, HZ * 10);
+ } else {
+ rc = wait_for_completion_interruptible_timeout(drained,
+ HZ * 10);
+ if (rc < 0) {
+ interrupted = true;
+ pci_warn(pdev, "Task \"%s\" (%d) blocked until vdevices are released\n",
+ current->comm, task_pid_nr(current));
+ }
+ }
+ if (!rc)
+ pci_warn(pdev, "TSM connection is in use, waiting for vdevices\n");
+ }
+ down_write(&pci_tsm_rwsem);
+ }
__pci_tsm_destroy(pdev, NULL);
+ up_write(&pci_tsm_rwsem);
}
void pci_tsm_init(struct pci_dev *pdev)
@@ -781,6 +842,10 @@ void pci_tsm_init(struct pci_dev *pdev)
if (!dsm->tsm)
return;
+ if (is_link_tsm(dsm->tsm->tsm_dev) &&
+ to_pci_tsm_pf0(dsm->tsm)->removing)
+ return;
+
probe_fn(pdev, dsm);
}
}
diff --git a/include/linux/pci-tsm.h b/include/linux/pci-tsm.h
index 19e9e0ef3bbb..47ca2dff6c0d 100644
--- a/include/linux/pci-tsm.h
+++ b/include/linux/pci-tsm.h
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __PCI_TSM_H
#define __PCI_TSM_H
+#include <linux/completion.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <uapi/linux/iommufd.h>
@@ -107,14 +108,18 @@ struct pci_tsm {
* struct pci_tsm_pf0 - Physical Function 0 TDISP link context
* @base_tsm: generic core "tsm" context
* @lock: mutual exclustion for pci_tsm_ops invocation
- * @context_users: live per-function contexts on this PF0; a nonzero count
+ * @dsm_context_users: live per-function contexts on this PF0; a nonzero count
* blocks link disconnect
+ * @dsm_context_drain_complete: completed when the last context is released
+ * @removing: reject new contexts while the DSM is being removed
* @doe_mb: PCIe Data Object Exchange mailbox
*/
struct pci_tsm_pf0 {
struct pci_tsm base_tsm;
struct mutex lock;
- unsigned int context_users;
+ unsigned int dsm_context_users;
+ struct completion dsm_context_drain_complete;
+ bool removing;
struct pci_doe_mb *doe_mb;
};
--
2.43.0