On Thu, Sep 29, 2022 at 05:32:58PM +0200, Niklas Schnelle wrote: > Since commit fa7e9ecc5e1c ("iommu/s390: Tolerate repeat attach_dev > calls") we can end up with duplicates in the list of devices attached to > a domain. This is inefficient and confusing since only one domain can > actually be in control of the IOMMU translations for a device. Fix this > by detaching the device from the previous domain, if any, on attach. > Add a WARN_ON() in case we still have attached devices on freeing the > domain. While here remove the re-attach on failure dance as it was > determined to be unlikely to help and may confuse debug and recovery. > > Fixes: fa7e9ecc5e1c ("iommu/s390: Tolerate repeat attach_dev calls") > Signed-off-by: Niklas Schnelle <schnelle@xxxxxxxxxxxxx> > --- > Changes since v2: > - Make __s390_iommu_detach_device() return void (Jason) > - Remove superfluous locking when we're freeing anyway (Jason) > - Remove the re-attach on failure dance as it is unlikely to help > and complicates debug and recovery (Jason) > - Ignore attempts to detach from domain that the device is no longer > attached to. > > drivers/iommu/s390-iommu.c | 77 +++++++++++++++++--------------------- > 1 file changed, 34 insertions(+), 43 deletions(-) > > diff --git a/drivers/iommu/s390-iommu.c b/drivers/iommu/s390-iommu.c > index c898bcbbce11..6fcb64e4b5e6 100644 > --- a/drivers/iommu/s390-iommu.c > +++ b/drivers/iommu/s390-iommu.c > @@ -79,10 +79,36 @@ static void s390_domain_free(struct iommu_domain *domain) > { > struct s390_domain *s390_domain = to_s390_domain(domain); > > + WARN_ON(!list_empty(&s390_domain->devices)); > dma_cleanup_tables(s390_domain->dma_table); > kfree(s390_domain); > } > > +static void __s390_iommu_detach_device(struct s390_domain *s390_domain, > + struct zpci_dev *zdev) > +{ > + struct s390_domain_device *domain_device, *tmp; > + unsigned long flags; > + > + if (!zdev || zdev->s390_domain != s390_domain) Please drop the s390_domain from this function, it is pointless.. Calling detach_device with a mismatched domain argument is a WARN_ON offense, the correct recovery is still to remove the domain. And zdev can already never be null due to the call chain Also, s390_iommu_release_device() should call this new function since we don't want to return back to the platform DMA when releasing. So, like this: diff --git a/drivers/iommu/s390-iommu.c b/drivers/iommu/s390-iommu.c index 2bad24d6cfef59..e8333a9301ec95 100644 --- a/drivers/iommu/s390-iommu.c +++ b/drivers/iommu/s390-iommu.c @@ -75,12 +75,12 @@ static void s390_domain_free(struct iommu_domain *domain) kfree(s390_domain); } -static void __s390_iommu_detach_device(struct s390_domain *s390_domain, - struct zpci_dev *zdev) +static void __s390_iommu_detach_device(struct zpci_dev *zdev) { + struct s390_domain *s390_domain = zdev->s390_domain; unsigned long flags; - if (!zdev || zdev->s390_domain != s390_domain) + if (!s390_domain) return; spin_lock_irqsave(&s390_domain->list_lock, flags); @@ -108,7 +108,7 @@ static int s390_iommu_attach_device(struct iommu_domain *domain, return -EINVAL; if (zdev->s390_domain) - __s390_iommu_detach_device(zdev->s390_domain, zdev); + __s390_iommu_detach_device(zdev); else if (zdev->dma_table) zpci_dma_exit_device(zdev); @@ -130,10 +130,11 @@ static int s390_iommu_attach_device(struct iommu_domain *domain, static void s390_iommu_detach_device(struct iommu_domain *domain, struct device *dev) { - struct s390_domain *s390_domain = to_s390_domain(domain); struct zpci_dev *zdev = to_zpci_dev(dev); - __s390_iommu_detach_device(s390_domain, zdev); + WARN_ON(zdev->s390_domain != to_s390_domain(domain)); + + __s390_iommu_detach_device(zdev); zpci_dma_init_device(zdev); } @@ -174,21 +175,11 @@ static void s390_iommu_release_device(struct device *dev) struct iommu_domain *domain; /* - * This is a workaround for a scenario where the IOMMU API common code - * "forgets" to call the detach_dev callback: After binding a device - * to vfio-pci and completing the VFIO_SET_IOMMU ioctl (which triggers - * the attach_dev), removing the device via - * "echo 1 > /sys/bus/pci/devices/.../remove" won't trigger detach_dev, - * only release_device will be called via the BUS_NOTIFY_REMOVED_DEVICE - * notifier. - * - * So let's call detach_dev from here if it hasn't been called before. + * release_device is expected to detach any domain currently attached + * to the device, but keep it attached to other devices in the group. */ - if (zdev && zdev->s390_domain) { - domain = iommu_get_domain_for_dev(dev); - if (domain) - s390_iommu_detach_device(domain, dev); - } + if (zdev) + __s390_iommu_detach_device(zdev); } static int s390_iommu_update_trans(struct s390_domain *s390_domain,