* [PATCH v1 1/5] PCI: Don't suspend IOMMU when probing reset capability
2026-11-03 18:33 [PATCH v1 0/5] iommu: Keep a device blocked after a failed PCI reset Nicolin Chen
@ 2026-11-03 18:33 ` Nicolin Chen
2026-11-03 18:33 ` [PATCH v1 2/5] PCI/CXL: Probe the underlying bus reset in cxl_reset_bus_function() Nicolin Chen
` (3 subsequent siblings)
4 siblings, 0 replies; 9+ messages in thread
From: Nicolin Chen @ 2026-11-03 18:33 UTC (permalink / raw)
To: Jason Gunthorpe, joro, Bjorn Helgaas
Cc: Will Deacon, Robin Murphy, rafael, Len Brown, Joerg Roedel,
Kevin Tian, Jonathan Cameron, Dave Jiang, Dan Williams, iommu,
linux-kernel, linux-acpi, linux-pci
reset_method_store() in drivers/pci/pci-sysfs.c discovers supported reset
methods by calling reset_fn(pdev, PCI_RESET_PROBE, ...) without holding a
device_lock, since the probe path is expected to query the device's reset
capability without changing device state.
However, pci_reset_bus_function() and __pci_dev_specific_reset() violate
that contract after pci_dev_reset_iommu_prepare/done() were added, which
moves the device into a blocking domain and abruptly aborts any in-flight
DMA. Doing this for a probe -- a state-query call that does not even hold
device_lock -- can cause driver timeouts and data loss on a DMAing device.
The peer reset helpers all handle this correctly: they short-circuit on a
probe input before touching the IOMMU.
Skip pci_dev_reset_iommu_prepare()/_done() entirely when probe is set. The
inner reset routines already implement their own probe semantics, and they
perform the capability checks and return without changing device state.
Fixes: f5b16b802174 ("PCI: Suspend iommu function prior to resetting a device")
Cc: stable@vger.kernel.org
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
---
drivers/pci/pci.c | 13 ++++++++-----
drivers/pci/quirks.c | 13 ++++++++-----
2 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 7c6f2aba81443..38b680e7c3d60 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5042,10 +5042,12 @@ static int pci_reset_bus_function(struct pci_dev *dev, bool probe)
if (bridge && pcie_is_cxl(bridge) && cxl_sbr_masked(bridge))
return -ENOTTY;
- rc = pci_dev_reset_iommu_prepare(dev);
- if (rc) {
- pci_err(dev, "failed to stop IOMMU for a PCI reset: %d\n", rc);
- return rc;
+ if (!probe) {
+ rc = pci_dev_reset_iommu_prepare(dev);
+ if (rc) {
+ pci_err(dev, "failed to stop IOMMU for a PCI reset: %d\n", rc);
+ return rc;
+ }
}
rc = pci_dev_reset_slot_function(dev, probe);
@@ -5054,7 +5056,8 @@ static int pci_reset_bus_function(struct pci_dev *dev, bool probe)
rc = pci_parent_bus_reset(dev, probe);
done:
- pci_dev_reset_iommu_done(dev);
+ if (!probe)
+ pci_dev_reset_iommu_done(dev);
return rc;
}
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index 7f1f47ea2ee41..3d3e95cb9f7dc 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -4406,14 +4406,17 @@ static int __pci_dev_specific_reset(struct pci_dev *dev, bool probe,
{
int ret;
- ret = pci_dev_reset_iommu_prepare(dev);
- if (ret) {
- pci_err(dev, "failed to stop IOMMU for a PCI reset: %d\n", ret);
- return ret;
+ if (!probe) {
+ ret = pci_dev_reset_iommu_prepare(dev);
+ if (ret) {
+ pci_err(dev, "failed to stop IOMMU for a PCI reset: %d\n", ret);
+ return ret;
+ }
}
ret = i->reset(dev, probe);
- pci_dev_reset_iommu_done(dev);
+ if (!probe)
+ pci_dev_reset_iommu_done(dev);
return ret;
}
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v1 2/5] PCI/CXL: Probe the underlying bus reset in cxl_reset_bus_function()
2026-11-03 18:33 [PATCH v1 0/5] iommu: Keep a device blocked after a failed PCI reset Nicolin Chen
2026-11-03 18:33 ` [PATCH v1 1/5] PCI: Don't suspend IOMMU when probing reset capability Nicolin Chen
@ 2026-11-03 18:33 ` Nicolin Chen
2026-11-03 18:44 ` Nicolin Chen
2026-11-03 18:33 ` [PATCH v1 3/5] iommu: Convert gdev->blocked from bool to enum blocked_reason Nicolin Chen
` (2 subsequent siblings)
4 siblings, 1 reply; 9+ messages in thread
From: Nicolin Chen @ 2026-11-03 18:33 UTC (permalink / raw)
To: Jason Gunthorpe, joro, Bjorn Helgaas
Cc: Will Deacon, Robin Murphy, rafael, Len Brown, Joerg Roedel,
Kevin Tian, Jonathan Cameron, Dave Jiang, Dan Williams, iommu,
linux-kernel, linux-acpi, linux-pci
cxl_reset_bus_function() reports "supported" to a probe after checking only
that the upstream bridge carries a CXL port DVSEC. The underlying bus reset
can still be unavailable, e.g. on a bus shared with other devices, so both
the reset_methods[] array and the reset_method sysfs node end up listing a
"cxl_bus" that is guaranteed to fail with -ENOTTY when it is attempted.
Probe the underlying pci_dev_reset_slot_function() and then, if it is not
applicable, pci_parent_bus_reset(). These are the same two checks that the
actual reset runs, so a shared-bus CXL device no longer advertises a method
that can never succeed.
Probing via pci_reset_bus_function() would not work: its cxl_sbr_masked()
check rejects every CXL port with a masked SBR, while the do-reset path in
this function unmasks the SBR before resetting. Such a port would wrongly
probe as unsupported.
Move the port control register read above the probe branch as well, so that
probe runs the same checks. That read asserts no reset, and a failure means
the device cannot be reset this way, which is a probe-time answer.
Also pass an explicit PCI_RESET_DO_RESET at the do-reset call site, since
probe is always false at that point.
Fixes: 53c49b6e6dd2e ("PCI/CXL: Add 'cxl_bus' reset method for devices below CXL Ports")
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
---
drivers/pci/pci.c | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 38b680e7c3d60..e9b06b360850e 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5075,13 +5075,21 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
if (!dvsec)
return -ENOTTY;
- if (probe)
- return 0;
-
rc = pci_read_config_word(bridge, dvsec + PCI_DVSEC_CXL_PORT_CTL, ®);
if (rc)
return -ENOTTY;
+ /*
+ * Do not probe via pci_reset_bus_function(), which would reject a
+ * masked SBR that the do-reset path below unmasks before resetting.
+ */
+ if (probe) {
+ rc = pci_dev_reset_slot_function(dev, PCI_RESET_PROBE);
+ if (rc != -ENOTTY)
+ return rc;
+ return pci_parent_bus_reset(dev, PCI_RESET_PROBE);
+ }
+
rc = pci_dev_reset_iommu_prepare(dev);
if (rc) {
pci_err(dev, "failed to stop IOMMU for a PCI reset: %d\n", rc);
@@ -5096,7 +5104,7 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
val);
}
- rc = pci_reset_bus_function(dev, probe);
+ rc = pci_reset_bus_function(dev, PCI_RESET_DO_RESET);
if (reg != val)
pci_write_config_word(bridge, dvsec + PCI_DVSEC_CXL_PORT_CTL,
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH v1 2/5] PCI/CXL: Probe the underlying bus reset in cxl_reset_bus_function()
2026-11-03 18:33 ` [PATCH v1 2/5] PCI/CXL: Probe the underlying bus reset in cxl_reset_bus_function() Nicolin Chen
@ 2026-11-03 18:44 ` Nicolin Chen
2026-11-03 21:02 ` Jonathan Cameron
0 siblings, 1 reply; 9+ messages in thread
From: Nicolin Chen @ 2026-11-03 18:44 UTC (permalink / raw)
To: Jonathan Cameron
Cc: Jason Gunthorpe, joro, Bjorn Helgaas, Will Deacon, Robin Murphy,
rafael, Len Brown, Joerg Roedel, Kevin Tian, Jonathan Cameron,
Dave Jiang, Dan Williams, iommu, linux-kernel, linux-acpi,
linux-pci
On Tue, Nov 03, 2026 at 10:33:43AM -0800, Nicolin Chen wrote:
> cxl_reset_bus_function() reports "supported" to a probe after checking only
> that the upstream bridge carries a CXL port DVSEC. The underlying bus reset
> can still be unavailable, e.g. on a bus shared with other devices, so both
> the reset_methods[] array and the reset_method sysfs node end up listing a
> "cxl_bus" that is guaranteed to fail with -ENOTTY when it is attempted.
>
> Probe the underlying pci_dev_reset_slot_function() and then, if it is not
> applicable, pci_parent_bus_reset(). These are the same two checks that the
> actual reset runs, so a shared-bus CXL device no longer advertises a method
> that can never succeed.
>
> Probing via pci_reset_bus_function() would not work: its cxl_sbr_masked()
> check rejects every CXL port with a masked SBR, while the do-reset path in
> this function unmasks the SBR before resetting. Such a port would wrongly
> probe as unsupported.
>
> Move the port control register read above the probe branch as well, so that
> probe runs the same checks. That read asserts no reset, and a failure means
> the device cannot be reset this way, which is a probe-time answer.
>
> Also pass an explicit PCI_RESET_DO_RESET at the do-reset call site, since
> probe is always false at that point.
>
> Fixes: 53c49b6e6dd2e ("PCI/CXL: Add 'cxl_bus' reset method for devices below CXL Ports")
> Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
Ah, I missed Jonathan's review tag during the split:
https://lore.kernel.org/linux-iommu/20260926001311.7c4be583@jic23-hlaptop/
Jonathan, would you mind giving another review tag here? It's the
exact same patch that you reviewed. Sorry for the inconvenience.
Nicolin
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH v1 2/5] PCI/CXL: Probe the underlying bus reset in cxl_reset_bus_function()
2026-11-03 18:44 ` Nicolin Chen
@ 2026-11-03 21:02 ` Jonathan Cameron
0 siblings, 0 replies; 9+ messages in thread
From: Jonathan Cameron @ 2026-11-03 21:02 UTC (permalink / raw)
To: Nicolin Chen
Cc: Jason Gunthorpe, joro, Bjorn Helgaas, Will Deacon, Robin Murphy,
rafael, Len Brown, Joerg Roedel, Kevin Tian, Jonathan Cameron,
Dave Jiang, Dan Williams, iommu, linux-kernel, linux-acpi,
linux-pci
On Tue, 3 Nov 2026 10:44:29 -0800
Nicolin Chen <nicolinc@nvidia.com> wrote:
> On Tue, Nov 03, 2026 at 10:33:43AM -0800, Nicolin Chen wrote:
> > cxl_reset_bus_function() reports "supported" to a probe after checking only
> > that the upstream bridge carries a CXL port DVSEC. The underlying bus reset
> > can still be unavailable, e.g. on a bus shared with other devices, so both
> > the reset_methods[] array and the reset_method sysfs node end up listing a
> > "cxl_bus" that is guaranteed to fail with -ENOTTY when it is attempted.
> >
> > Probe the underlying pci_dev_reset_slot_function() and then, if it is not
> > applicable, pci_parent_bus_reset(). These are the same two checks that the
> > actual reset runs, so a shared-bus CXL device no longer advertises a method
> > that can never succeed.
> >
> > Probing via pci_reset_bus_function() would not work: its cxl_sbr_masked()
> > check rejects every CXL port with a masked SBR, while the do-reset path in
> > this function unmasks the SBR before resetting. Such a port would wrongly
> > probe as unsupported.
> >
> > Move the port control register read above the probe branch as well, so that
> > probe runs the same checks. That read asserts no reset, and a failure means
> > the device cannot be reset this way, which is a probe-time answer.
> >
> > Also pass an explicit PCI_RESET_DO_RESET at the do-reset call site, since
> > probe is always false at that point.
> >
> > Fixes: 53c49b6e6dd2e ("PCI/CXL: Add 'cxl_bus' reset method for devices below CXL Ports")
> > Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
>
> Ah, I missed Jonathan's review tag during the split:
> https://lore.kernel.org/linux-iommu/20260926001311.7c4be583@jic23-hlaptop/
>
> Jonathan, would you mind giving another review tag here? It's the
> exact same patch that you reviewed. Sorry for the inconvenience.
>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
> Nicolin
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v1 3/5] iommu: Convert gdev->blocked from bool to enum blocked_reason
2026-11-03 18:33 [PATCH v1 0/5] iommu: Keep a device blocked after a failed PCI reset Nicolin Chen
2026-11-03 18:33 ` [PATCH v1 1/5] PCI: Don't suspend IOMMU when probing reset capability Nicolin Chen
2026-11-03 18:33 ` [PATCH v1 2/5] PCI/CXL: Probe the underlying bus reset in cxl_reset_bus_function() Nicolin Chen
@ 2026-11-03 18:33 ` Nicolin Chen
2026-11-03 18:33 ` [PATCH v1 4/5] iommu: Pass in gdev's blocked state to iommu_deinit_device() Nicolin Chen
2026-11-03 18:33 ` [PATCH v1 5/5] iommu: Pass in reset result to pci_dev_reset_iommu_done() Nicolin Chen
4 siblings, 0 replies; 9+ messages in thread
From: Nicolin Chen @ 2026-11-03 18:33 UTC (permalink / raw)
To: Jason Gunthorpe, joro, Bjorn Helgaas
Cc: Will Deacon, Robin Murphy, rafael, Len Brown, Joerg Roedel,
Kevin Tian, Jonathan Cameron, Dave Jiang, Dan Williams, iommu,
linux-kernel, linux-acpi, linux-pci
The gdev->blocked flag tracks whether a device is individually being held
in the group->blocking_domain while group->domain is retained. Up to now,
a PCI reset in flight is the only producer, so a bool suffices.
Subsequent changes will add more reasons to keep a device blocked, e.g. a
failed-reset case that must not auto-unblock, or a driver-side quarantine
for a hardware fault. These reasons are cleared by different events, which
a single bool cannot encode.
Convert the "bool blocked" into "enum blocked_reason blocked", provisioned
with two initial values: BLOCKED_NONE and BLOCKED_RESETTING, covering the
existing use cases. All readers keep the "if (gdev->blocked)" form, since
BLOCKED_NONE == 0.
This is a pure type change with no behavior change. Follow-on changes will
add new enum values along with their producers.
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Reviewed-by: Lu Baolu <baolu.lu@linux.intel.com>
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
---
drivers/iommu/iommu.c | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index b486b8bbd1fcd..ee026f5f14349 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -73,16 +73,20 @@ struct iommu_group {
void *owner;
};
+enum blocked_reason {
+ BLOCKED_NONE = 0, /* Not blocked */
+ BLOCKED_RESETTING, /* PCI reset in flight */
+};
+
struct group_device {
struct list_head list;
struct device *dev;
char *name;
/*
* Device is blocked for a pending recovery while its group->domain is
- * retained. This can happen when:
- * - Device is undergoing a reset
+ * retained.
*/
- bool blocked;
+ enum blocked_reason blocked;
unsigned int reset_depth;
};
@@ -4072,7 +4076,7 @@ int pci_dev_reset_iommu_prepare(struct pci_dev *pdev)
* the correct domain in iommu_driver_get_domain_for_dev() that might be
* called in a set_dev_pasid callback function.
*/
- gdev->blocked = true;
+ gdev->blocked = BLOCKED_RESETTING;
/*
* Stage PASID domains at blocking_domain while retaining pasid_array.
@@ -4198,7 +4202,7 @@ void pci_dev_reset_iommu_done(struct pci_dev *pdev)
* the correct domain in iommu_driver_get_domain_for_dev() that might be
* called in a set_dev_pasid callback function.
*/
- gdev->blocked = false;
+ gdev->blocked = BLOCKED_NONE;
/*
* Re-attach PASID domains back to the domains retained in pasid_array.
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v1 4/5] iommu: Pass in gdev's blocked state to iommu_deinit_device()
2026-11-03 18:33 [PATCH v1 0/5] iommu: Keep a device blocked after a failed PCI reset Nicolin Chen
` (2 preceding siblings ...)
2026-11-03 18:33 ` [PATCH v1 3/5] iommu: Convert gdev->blocked from bool to enum blocked_reason Nicolin Chen
@ 2026-11-03 18:33 ` Nicolin Chen
2026-11-03 18:33 ` [PATCH v1 5/5] iommu: Pass in reset result to pci_dev_reset_iommu_done() Nicolin Chen
4 siblings, 0 replies; 9+ messages in thread
From: Nicolin Chen @ 2026-11-03 18:33 UTC (permalink / raw)
To: Jason Gunthorpe, joro, Bjorn Helgaas
Cc: Will Deacon, Robin Murphy, rafael, Len Brown, Joerg Roedel,
Kevin Tian, Jonathan Cameron, Dave Jiang, Dan Williams, iommu,
linux-kernel, linux-acpi, linux-pci
iommu_deinit_device() parks the device on the driver's release_domain and
passes group->domain to its attach_dev() callback as the old domain. That
is only correct while the device is still attached to group->domain.
A subsequent change keeps a device blocked on group->blocking_domain after
a failed reset, so the old domain has to follow the device's blocked state.
The group_device holding that state is freed by __iommu_group_free_device()
before iommu_deinit_device() runs, so read it at the call site and pass it
in.
No functional change.
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
---
drivers/iommu/iommu.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index ee026f5f14349..0d40eb4f15cc1 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -550,7 +550,7 @@ static int iommu_init_device(struct device *dev)
return ret;
}
-static void iommu_deinit_device(struct device *dev)
+static void iommu_deinit_device(struct device *dev, bool blocked)
{
struct iommu_group *group = dev->iommu_group;
const struct iommu_ops *ops = dev_iommu_ops(dev);
@@ -703,7 +703,7 @@ static int __iommu_probe_device(struct device *dev, struct list_head *group_list
list_del(&gdev->list);
__iommu_group_free_device(group, gdev);
err_put_group:
- iommu_deinit_device(dev);
+ iommu_deinit_device(dev, false);
mutex_unlock(&group->mutex);
iommu_group_put(group);
@@ -759,13 +759,15 @@ static void __iommu_group_remove_device(struct device *dev)
mutex_lock(&group->mutex);
for_each_group_device(group, device) {
+ bool blocked = device->blocked;
+
if (device->dev != dev)
continue;
list_del(&device->list);
__iommu_group_free_device(group, device);
if (dev_has_iommu(dev))
- iommu_deinit_device(dev);
+ iommu_deinit_device(dev, blocked);
else
dev->iommu_group = NULL;
break;
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v1 5/5] iommu: Pass in reset result to pci_dev_reset_iommu_done()
2026-11-03 18:33 [PATCH v1 0/5] iommu: Keep a device blocked after a failed PCI reset Nicolin Chen
` (3 preceding siblings ...)
2026-11-03 18:33 ` [PATCH v1 4/5] iommu: Pass in gdev's blocked state to iommu_deinit_device() Nicolin Chen
@ 2026-11-03 18:33 ` Nicolin Chen
2026-11-03 19:59 ` Nicolin Chen
4 siblings, 1 reply; 9+ messages in thread
From: Nicolin Chen @ 2026-11-03 18:33 UTC (permalink / raw)
To: Jason Gunthorpe, joro, Bjorn Helgaas
Cc: Will Deacon, Robin Murphy, rafael, Len Brown, Joerg Roedel,
Kevin Tian, Jonathan Cameron, Dave Jiang, Dan Williams, iommu,
linux-kernel, linux-acpi, linux-pci
IOMMU drivers handle ATC cache maintenance. They may encounter ATC-related
errors (e.g., ATC invalidation timeout), indicating that the ATC cache may
have stale entries that can corrupt the memory. In this case, IOMMU driver
has no choice but to block the device's ATS function and wait for a device
recovery.
The pci_dev_reset_iommu_done() called at the end of a reset function could
serve as a reliable signal to the IOMMU subsystem that the physical device
cache is completely clean. However, the function is called unconditionally
even if the reset operation had actually failed, which would re-attach the
faulty device back to a normal translation domain. And this will leave the
system highly exposed, creating vulnerabilities for data corruption:
IOMMU blocks RID/ATS
pci_reset_function():
pci_dev_reset_iommu_prepare(); // Block RID/ATS
__reset(); // Failed (ATC is still stale)
pci_dev_reset_iommu_done(); // Unblock RID/ATS (ah-ha)
Instead, pass in @reset_result to pci_dev_reset_iommu_done() from callers:
IOMMU blocks RID/ATS
pci_reset_function():
pci_dev_reset_iommu_prepare(); // Block RID/ATS
rc = __reset();
pci_dev_reset_iommu_done(rc); // Unblock or quarantine
On a successful reset, done() restores the device to its RID/PASID domains
and decrements group->recovery_cnt. On failure, the device remains blocked,
and concurrent domain attachment will be rejected until a successful reset.
Note: -ENOTTY is overloaded with different meanings by PCI reset functions.
Some of them indicate "reset was not attempted", while others indicate "try
the next reset method and the current method failed". IOMMU that must react
these two outcomes separately has no choice but to keep the device blocked
on -ENOTTY as well. Leave an inline FIXME and warning.
This introduces a new situation where a blocked device is being unplugged.
Decrement the group->recovery_cnt accordingly. Such a device stays on the
group->blocking_domain, so hand that to the release_domain attach_dev() as
the old domain in place of group->domain.
Suggested-by: Kevin Tian <kevin.tian@intel.com>
Reviewed-by: Lu Baolu <baolu.lu@linux.intel.com>
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
---
include/linux/iommu.h | 5 ++--
drivers/iommu/iommu.c | 65 +++++++++++++++++++++++++++++++++++++++---
drivers/pci/pci-acpi.c | 2 +-
drivers/pci/pci.c | 10 +++----
drivers/pci/quirks.c | 2 +-
5 files changed, 71 insertions(+), 13 deletions(-)
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index ef5043752a365..36417d596bb43 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -1224,7 +1224,7 @@ void iommu_free_global_pasid(ioasid_t pasid);
/* PCI device reset functions */
int pci_dev_reset_iommu_prepare(struct pci_dev *pdev);
-void pci_dev_reset_iommu_done(struct pci_dev *pdev);
+void pci_dev_reset_iommu_done(struct pci_dev *pdev, int reset_result);
#else /* CONFIG_IOMMU_API */
struct iommu_ops {};
@@ -1554,7 +1554,8 @@ static inline int pci_dev_reset_iommu_prepare(struct pci_dev *pdev)
return 0;
}
-static inline void pci_dev_reset_iommu_done(struct pci_dev *pdev)
+static inline void pci_dev_reset_iommu_done(struct pci_dev *pdev,
+ int reset_result)
{
}
#endif /* CONFIG_IOMMU_API */
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index 0d40eb4f15cc1..1e3a243a6f1ef 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -76,6 +76,7 @@ struct iommu_group {
enum blocked_reason {
BLOCKED_NONE = 0, /* Not blocked */
BLOCKED_RESETTING, /* PCI reset in flight */
+ BLOCKED_RESET_FAILED, /* PCI reset failed */
};
struct group_device {
@@ -589,7 +590,8 @@ static void iommu_deinit_device(struct device *dev, bool blocked)
release_domain = ops->identity_domain;
release_domain->ops->attach_dev(release_domain, dev,
- group->domain);
+ blocked ? group->blocking_domain :
+ group->domain);
}
if (ops->release_device)
@@ -764,6 +766,9 @@ static void __iommu_group_remove_device(struct device *dev)
if (device->dev != dev)
continue;
+ /* Must drop the recovery_cnt when removing a blocked device */
+ if (blocked && !WARN_ON(group->recovery_cnt == 0))
+ group->recovery_cnt--;
list_del(&device->list);
__iommu_group_free_device(group, device);
if (dev_has_iommu(dev))
@@ -4027,7 +4032,12 @@ EXPORT_SYMBOL_NS_GPL(iommu_replace_group_handle, "IOMMUFD_INTERNAL");
* reset is finished, pci_dev_reset_iommu_done() can restore everything.
*
* Caller must use pci_dev_reset_iommu_prepare() with pci_dev_reset_iommu_done()
- * before/after the core-level reset routine, to decrement the recovery_cnt.
+ * before/after the core-level reset routine. On a successful reset, done() will
+ * decrement group->recovery_cnt and restore domains. On a failure, recovery_cnt
+ * is left intact and the device stays blocked.
+ *
+ * Callers must skip pci_dev_reset_iommu_prepare/done() entirely when no reset
+ * is attempted (e.g. probe mode).
*
* Return: 0 on success or negative error code if the preparation failed.
*
@@ -4057,6 +4067,10 @@ int pci_dev_reset_iommu_prepare(struct pci_dev *pdev)
if (gdev->reset_depth++)
return 0;
+ /* Device might be already blocked for a quarantine */
+ if (gdev->blocked)
+ return 0;
+
ret = __iommu_group_alloc_blocking_domain(group);
if (ret) {
gdev->reset_depth--;
@@ -4138,20 +4152,28 @@ static bool group_device_dma_alias_is_blocked(struct iommu_group *group,
/**
* pci_dev_reset_iommu_done() - Restore IOMMU after a PCI device reset is done
* @pdev: PCI device that has finished a reset routine
+ * @reset_result: Return code from the reset routine
*
* After a PCIe device finishes a reset routine, it wants to restore its IOMMU
* activity, including new translation and cache invalidation, by re-attaching
* all RID/PASID of the device back to the domains retained in the core-level
* structure.
*
- * Caller must pair it with a successful pci_dev_reset_iommu_prepare().
+ * This is a pairing function for pci_dev_reset_iommu_prepare(). Caller passes
+ * the reset return value to @reset_result. On a failed reset, the device will
+ * remain blocked as a quarantine measure, with group->recovery_cnt intact, to
+ * protect system memory until a subsequent successful reset.
+ *
+ * Callers must skip pci_dev_reset_iommu_prepare/done() entirely when no reset
+ * is attempted (e.g. probe mode).
*
* Note that, although unlikely, there is a risk that re-attaching domains might
* fail due to some unexpected happening like OOM.
*/
-void pci_dev_reset_iommu_done(struct pci_dev *pdev)
+void pci_dev_reset_iommu_done(struct pci_dev *pdev, int reset_result)
{
struct iommu_group *group = pdev->dev.iommu_group;
+ enum blocked_reason old_blocked;
struct group_device *gdev;
unsigned long pasid;
void *entry;
@@ -4174,6 +4196,37 @@ void pci_dev_reset_iommu_done(struct pci_dev *pdev)
if (WARN_ON(!group->blocking_domain))
return;
+ /*
+ * A reset failure implies that the device might be unreliable. E.g. its
+ * device cache might retain stale entries, which might result in memory
+ * corruption. Thus, do not unblock the device until a successful reset.
+ */
+ if (reset_result) {
+ /*
+ * FIXME: the int-return values from the PCI reset functions are
+ * not consistent: some reset functions use -ENOTTY to indicate
+ * "no reset was attempted" (in which case IOMMU should revert a
+ * prepare), while others use -ENOTTY to indicate "reset failed;
+ * try the next reset method" (in which case IOMMU should keep
+ * the device blocked). Without fixing the PCI return result, we
+ * cannot tell the difference between the two cases. Warn it.
+ */
+ if (reset_result == -ENOTTY)
+ dev_warn_ratelimited(
+ &pdev->dev,
+ "Reset may have been skipped. Keep it blocked conservatively\n");
+ else
+ dev_err_ratelimited(
+ &pdev->dev,
+ "Reset failed. Keep it blocked to protect memory\n");
+ if (gdev->blocked == BLOCKED_RESETTING)
+ gdev->blocked = BLOCKED_RESET_FAILED;
+ return;
+ }
+
+ if (WARN_ON(!gdev->blocked))
+ return;
+
if (group_device_dma_alias_is_blocked(group, gdev)) {
/*
* FIXME: DMA aliased devices share the same RID, which would be
@@ -4204,6 +4257,7 @@ void pci_dev_reset_iommu_done(struct pci_dev *pdev)
* the correct domain in iommu_driver_get_domain_for_dev() that might be
* called in a set_dev_pasid callback function.
*/
+ old_blocked = gdev->blocked;
gdev->blocked = BLOCKED_NONE;
/*
@@ -4225,6 +4279,9 @@ void pci_dev_reset_iommu_done(struct pci_dev *pdev)
if (!WARN_ON(group->recovery_cnt == 0))
group->recovery_cnt--;
+
+ if (old_blocked > BLOCKED_RESETTING)
+ pci_info(pdev, "Device is unblocked after successful reset\n");
}
EXPORT_SYMBOL_GPL(pci_dev_reset_iommu_done);
diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c
index 1150f2fbabf41..76a535adf0cd6 100644
--- a/drivers/pci/pci-acpi.c
+++ b/drivers/pci/pci-acpi.c
@@ -974,7 +974,7 @@ int pci_dev_acpi_reset(struct pci_dev *dev, bool probe)
ret = -ENOTTY;
}
- pci_dev_reset_iommu_done(dev);
+ pci_dev_reset_iommu_done(dev, ret);
return ret;
}
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index e9b06b360850e..90a38f2d0d172 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -4469,7 +4469,7 @@ int pcie_flr(struct pci_dev *dev)
ret = pci_dev_wait(dev, "FLR", PCIE_RESET_READY_POLL_MS);
done:
- pci_dev_reset_iommu_done(dev);
+ pci_dev_reset_iommu_done(dev, ret);
return ret;
}
EXPORT_SYMBOL_GPL(pcie_flr);
@@ -4547,7 +4547,7 @@ static int pci_af_flr(struct pci_dev *dev, bool probe)
ret = pci_dev_wait(dev, "AF_FLR", PCIE_RESET_READY_POLL_MS);
done:
- pci_dev_reset_iommu_done(dev);
+ pci_dev_reset_iommu_done(dev, ret);
return ret;
}
@@ -4601,7 +4601,7 @@ static int pci_pm_reset(struct pci_dev *dev, bool probe)
pci_dev_d3_sleep(dev);
ret = pci_dev_wait(dev, "PM D3hot->D0", PCIE_RESET_READY_POLL_MS);
- pci_dev_reset_iommu_done(dev);
+ pci_dev_reset_iommu_done(dev, ret);
return ret;
}
@@ -5057,7 +5057,7 @@ static int pci_reset_bus_function(struct pci_dev *dev, bool probe)
rc = pci_parent_bus_reset(dev, probe);
done:
if (!probe)
- pci_dev_reset_iommu_done(dev);
+ pci_dev_reset_iommu_done(dev, rc);
return rc;
}
@@ -5110,7 +5110,7 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
pci_write_config_word(bridge, dvsec + PCI_DVSEC_CXL_PORT_CTL,
reg);
- pci_dev_reset_iommu_done(dev);
+ pci_dev_reset_iommu_done(dev, rc);
return rc;
}
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index 3d3e95cb9f7dc..92e81460c2d8f 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -4416,7 +4416,7 @@ static int __pci_dev_specific_reset(struct pci_dev *dev, bool probe,
ret = i->reset(dev, probe);
if (!probe)
- pci_dev_reset_iommu_done(dev);
+ pci_dev_reset_iommu_done(dev, ret);
return ret;
}
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH v1 5/5] iommu: Pass in reset result to pci_dev_reset_iommu_done()
2026-11-03 18:33 ` [PATCH v1 5/5] iommu: Pass in reset result to pci_dev_reset_iommu_done() Nicolin Chen
@ 2026-11-03 19:59 ` Nicolin Chen
0 siblings, 0 replies; 9+ messages in thread
From: Nicolin Chen @ 2026-11-03 19:59 UTC (permalink / raw)
To: Jason Gunthorpe, joro, Bjorn Helgaas
Cc: Will Deacon, Robin Murphy, rafael, Len Brown, Joerg Roedel,
Kevin Tian, Jonathan Cameron, Dave Jiang, Dan Williams, iommu,
linux-kernel, linux-acpi, linux-pci
On Tue, Nov 03, 2026 at 10:33:46AM -0800, Nicolin Chen wrote:
> diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
> index 3d3e95cb9f7dc..92e81460c2d8f 100644
> --- a/drivers/pci/quirks.c
> +++ b/drivers/pci/quirks.c
> @@ -4416,7 +4416,7 @@ static int __pci_dev_specific_reset(struct pci_dev *dev, bool probe,
>
> ret = i->reset(dev, probe);
> if (!probe)
> - pci_dev_reset_iommu_done(dev);
> + pci_dev_reset_iommu_done(dev, ret);
Sashiko pointed out a finding against the flr in quirks:
"Can a nested FLR failure inside a device-specific reset quirk be lost,
allowing the device to be unblocked even when the underlying FLR failed?"
Justification: this is deliberately ignored, per Jason's suggestion:
https://lore.kernel.org/all/178786269177.3808739.6763094519382046767.b4-review@b4/
Nicolin
^ permalink raw reply [flat|nested] 9+ messages in thread