From: Lu Baolu <baolu.lu@linux.intel.com>
To: iommu@lists.linux.dev, x86@kernel.org,
linux-coco@lists.linux.dev, kvm@vger.kernel.org
Cc: Jason Gunthorpe <jgg@ziepe.ca>, Kevin Tian <kevin.tian@intel.com>,
Kiryl Shutsemau <kas@kernel.org>,
Rick Edgecombe <rick.p.edgecombe@intel.com>,
yilun.xu@linux.intel.com, xiaoyao.li@intel.com,
Chao Gao <chao.gao@intel.com>,
Binbin Wu <binbin.wu@linux.intel.com>,
linux-kernel@vger.kernel.org, Lu Baolu <baolu.lu@linux.intel.com>
Subject: [PATCH v3 6/9] iommu/vt-d: Refuse system suspend while acquired by the TDX module
Date: Thu, 12 Nov 2026 12:30:51 +0800 [thread overview]
Message-ID: <20261112043054.2664857-7-baolu.lu@linux.intel.com> (raw)
In-Reply-To: <20261112043054.2664857-1-baolu.lu@linux.intel.com>
While tdx-host has acquired an IOMMU, the VT-d driver blocks its GCMD
writes. TDX disables S3 by clearing acpi_suspend_lowlevel, but that is
only checked after the syscore suspend callbacks have run, so an S3
request, e.g. "echo mem > /sys/power/state", still runs iommu_suspend()
and then iommu_resume():
suspend_enter()
syscore_suspend()
iommu_suspend() <- runs on the acquired IOMMU
suspend_ops->enter() = acpi_suspend_enter()
if (!acpi_suspend_lowlevel) <- TDX's "S3 disabled" check
return -ENOSYS
syscore_resume()
iommu_resume() <- runs on the acquired IOMMU
Register a PM notifier for each DMAR unit that supports trusted DMA and
refuse PM_SUSPEND_PREPARE with -EBUSY while the unit is acquired. The
suspend then fails before tasks are frozen and before syscore callbacks
run.
A check at PM_SUSPEND_PREPARE alone leaves a window: user space is only
frozen after the notifiers run, so a device connect may still acquire
the unit in between. Close it with a per-unit tdxc_suspending flag, set
when the notifier lets the suspend proceed and cleared at
PM_POST_SUSPEND. dmar_tdxc_acquire() fails with -EBUSY while it is set.
Both the notifier and dmar_tdxc_acquire() hold the unit's tdxc_lock, so
the check and the acquisition can't interleave.
The notifiers are registered at init only for units advertising
ECAP.TDXCS. Such units are never hot-added or hot-removed, so the set
doesn't change afterwards and the notifiers are never unregistered.
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
---
drivers/iommu/intel/iommu.h | 1 +
include/linux/dmar.h | 4 ++-
drivers/iommu/intel/dmar.c | 53 +++++++++++++++++++++++++++++++++++++
drivers/iommu/intel/iommu.c | 1 +
4 files changed, 58 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h
index 26135248675d..f7554df8cd2d 100644
--- a/drivers/iommu/intel/iommu.h
+++ b/drivers/iommu/intel/iommu.h
@@ -1363,6 +1363,7 @@ extern int rwbf_quirk;
int iommu_calculate_agaw(struct intel_iommu *iommu);
int iommu_calculate_max_sagaw(struct intel_iommu *iommu);
int ecmd_submit_sync(struct intel_iommu *iommu, u8 ecmd, u64 oa, u64 ob);
+void dmar_tdxc_pm_init(void);
static inline bool ecmd_has_pmu_essential(struct intel_iommu *iommu)
{
diff --git a/include/linux/dmar.h b/include/linux/dmar.h
index a39ca13f13dc..8b407bc6d179 100644
--- a/include/linux/dmar.h
+++ b/include/linux/dmar.h
@@ -47,7 +47,9 @@ struct dmar_drhd_unit {
u8 ignored:1; /* ignore drhd */
u8 include_all:1;
u8 gfx_dedicated:1; /* graphic dedicated */
- struct mutex tdxc_lock; /* serializes acquire/release */
+ struct mutex tdxc_lock; /* serializes acquire/release/suspend */
+ struct notifier_block tdxc_pm_nb;
+ bool tdxc_suspending; /* protected by tdxc_lock */
struct intel_iommu *iommu;
};
diff --git a/drivers/iommu/intel/dmar.c b/drivers/iommu/intel/dmar.c
index 91826705a06b..ad6b22601446 100644
--- a/drivers/iommu/intel/dmar.c
+++ b/drivers/iommu/intel/dmar.c
@@ -28,6 +28,7 @@
#include <linux/iommu.h>
#include <linux/numa.h>
#include <linux/limits.h>
+#include <linux/suspend.h>
#include <asm/irq_remapping.h>
#include "iommu.h"
@@ -2683,6 +2684,9 @@ int dmar_tdxc_acquire(struct pci_dev *pdev,
return -EINVAL;
guard(mutex)(&drhd->tdxc_lock);
+ if (drhd->tdxc_suspending)
+ return -EBUSY;
+
/* Still set if TDH.IOMMU.CLEAR failed and tdx-host was reloaded. */
if (iommu->tdxc_acquired)
return -EBUSY;
@@ -2737,3 +2741,52 @@ void dmar_tdxc_release(struct pci_dev *pdev,
dmar_tdxc_unreserve_did(drhd->iommu);
}
EXPORT_SYMBOL_FOR_MODULES(dmar_tdxc_release, "tdx-host");
+
+/*
+ * TDX only fails S3 after syscore suspend/resume have run, and those
+ * would hit the blocked GCMD paths. Refuse the suspend up front instead.
+ */
+static int dmar_tdxc_pm_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct dmar_drhd_unit *drhd =
+ container_of(nb, struct dmar_drhd_unit, tdxc_pm_nb);
+
+ guard(mutex)(&drhd->tdxc_lock);
+
+ switch (action) {
+ case PM_SUSPEND_PREPARE:
+ if (drhd->iommu->tdxc_acquired)
+ return notifier_from_errno(-EBUSY);
+ drhd->tdxc_suspending = true;
+ break;
+ case PM_POST_SUSPEND:
+ drhd->tdxc_suspending = false;
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+void __init dmar_tdxc_pm_init(void)
+{
+ struct dmar_drhd_unit *drhd;
+ struct intel_iommu *iommu;
+
+ down_read(&dmar_global_lock);
+ for_each_active_iommu(iommu, drhd) {
+ if (!ecap_tdxcs(iommu->ecap))
+ continue;
+
+ drhd->tdxc_pm_nb.notifier_call = dmar_tdxc_pm_notify;
+ /*
+ * CPU hotplug takes dmar_global_lock under the PM notifier chain
+ * lock. Drop it to avoid a lock order splat; TDXCS units are never
+ * hot-removed, so @drhd stays valid.
+ */
+ up_read(&dmar_global_lock);
+ register_pm_notifier(&drhd->tdxc_pm_nb);
+ down_read(&dmar_global_lock);
+ }
+ up_read(&dmar_global_lock);
+}
diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index 69e8c2c8680a..d8b9610ae791 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -2041,6 +2041,7 @@ static struct syscore iommu_syscore = {
static void __init init_iommu_pm_ops(void)
{
register_syscore(&iommu_syscore);
+ dmar_tdxc_pm_init();
}
#else
--
2.43.0
next prev parent reply other threads:[~2026-11-12 4:44 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-12 4:30 [PATCH v3 0/9] coco/tdx-host: Introduce trusted DMA initialization support Lu Baolu
2026-11-12 4:30 ` [PATCH v3 1/9] x86/virt/tdx: Add SEAMCALL wrappers for IOMMU setup/clear Lu Baolu
2026-11-12 4:30 ` [PATCH v3 2/9] x86/virt/tdx: Read global metadata for trusted IOMMU Lu Baolu
2026-11-12 4:30 ` [PATCH v3 3/9] x86/virt/tdx: Export Dynamic PAMT helpers to tdx-host Lu Baolu
2026-11-12 4:30 ` [PATCH v3 4/9] iommu/vt-d: Add interface for tdx-host when enabling trusted DMA Lu Baolu
2026-11-12 4:30 ` [PATCH v3 5/9] iommu/vt-d: Block GCMD and ECMD while acquired by the TDX module Lu Baolu
2026-11-12 4:30 ` Lu Baolu [this message]
2026-11-12 4:30 ` [PATCH v3 7/9] coco/tdx-host: Add skeleton to support PCI link encryption Lu Baolu
2026-11-12 4:30 ` [PATCH v3 8/9] coco/tdx-host: Rename tdx-host.c to main.c Lu Baolu
2026-11-12 4:30 ` [PATCH v3 9/9] coco/tdx-host: Enable trusted DMA on IOMMUs on device connect Lu Baolu
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=20261112043054.2664857-7-baolu.lu@linux.intel.com \
--to=baolu.lu@linux.intel.com \
--cc=binbin.wu@linux.intel.com \
--cc=chao.gao@intel.com \
--cc=iommu@lists.linux.dev \
--cc=jgg@ziepe.ca \
--cc=kas@kernel.org \
--cc=kevin.tian@intel.com \
--cc=kvm@vger.kernel.org \
--cc=linux-coco@lists.linux.dev \
--cc=linux-kernel@vger.kernel.org \
--cc=rick.p.edgecombe@intel.com \
--cc=x86@kernel.org \
--cc=xiaoyao.li@intel.com \
--cc=yilun.xu@linux.intel.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®