Subject: [PATCH] PCI: Release PF ref during removing VF From: Yinghai Lu commit dc087f2f6a2925e81831f3016b9cbb6e470e7423 (PCI: Simplify IOV implementation and fix reference count races) broke the pcie native hotplug with SRIOV enabled: PF is not freed during hot-remove, and later hot-add do not work as old pci_dev is still around, and can not create new pci_dev. That commit need VFs to be removed via pci_disable_sriov/virtfn_remove to make sure ref to PF is put back. So we can not call stop_and_remove for VF before PF, that will make VF get removed directly before PF's driver try to use pci_disable_sriov/virtfn_remove to do it. Solution would be 1. separating stop and remove in two iterations. 2. or make pci_stop_and_remove_bus_device could be used on VF. This patch is second solution: Separate PF ref releasing from virtfn_remove, all that during pci_destroy_dev for VFs. -v2: fix warning when SRIOV is not defined. Signed-off-by: Yinghai Lu --- drivers/pci/iov.c | 34 +++++++++++++++++++++++++++++----- drivers/pci/pci.h | 4 ++++ drivers/pci/remove.c | 17 +++++++++++++++++ 3 files changed, 50 insertions(+), 5 deletions(-) Index: linux-2.6/drivers/pci/iov.c =================================================================== --- linux-2.6.orig/drivers/pci/iov.c +++ linux-2.6/drivers/pci/iov.c @@ -132,9 +132,37 @@ failed: return rc; } -static void virtfn_remove(struct pci_dev *dev, int id, int reset) +void virtfn_release(struct pci_dev *virtfn) { + int i; + struct pci_dev *dev; char buf[VIRTFN_ID_LEN]; + + if (!virtfn->is_virtfn) + return; + + dev = virtfn->physfn; + + /* + * Remove link to virtfn for PF. + * pci_disable_sriov() could be called after pci_stop_dev() is + * called for PF. So check sd to avoid spurious sfsfs warning. + */ + if (dev->dev.kobj.sd) + for (i = 0; i < dev->sriov->num_VFs; i++) + if ((virtfn_bus(dev, i) == virtfn->bus->number) && + (virtfn_devfn(dev, i) == virtfn->devfn)) { + sprintf(buf, "virtfn%u", i); + sysfs_remove_link(&dev->dev.kobj, buf); + break; + } + + virtfn_remove_bus(dev->bus, virtfn->bus); + pci_dev_put(dev); +} + +static void virtfn_remove(struct pci_dev *dev, int id, int reset) +{ struct pci_dev *virtfn; struct pci_sriov *iov = dev->sriov; @@ -149,8 +177,6 @@ static void virtfn_remove(struct pci_dev __pci_reset_function(virtfn); } - sprintf(buf, "virtfn%u", id); - sysfs_remove_link(&dev->dev.kobj, buf); /* * pci_stop_dev() could have been called for this virtfn already, * so the directory for the virtfn may have been removed before. @@ -161,12 +187,10 @@ static void virtfn_remove(struct pci_dev mutex_lock(&iov->dev->sriov->lock); pci_stop_and_remove_bus_device(virtfn); - virtfn_remove_bus(dev->bus, virtfn->bus); mutex_unlock(&iov->dev->sriov->lock); /* balance pci_get_domain_bus_and_slot() */ pci_dev_put(virtfn); - pci_dev_put(dev); } static int sriov_migration(struct pci_dev *dev) Index: linux-2.6/drivers/pci/pci.h =================================================================== --- linux-2.6.orig/drivers/pci/pci.h +++ linux-2.6/drivers/pci/pci.h @@ -259,6 +259,7 @@ int pci_iov_resource_bar(struct pci_dev resource_size_t pci_sriov_resource_alignment(struct pci_dev *dev, int resno); void pci_restore_iov_state(struct pci_dev *dev); int pci_iov_bus_range(struct pci_bus *bus); +void virtfn_release(struct pci_dev *dev); #else static inline int pci_iov_init(struct pci_dev *dev) @@ -281,6 +282,9 @@ static inline int pci_iov_bus_range(stru { return 0; } +static inline void virtfn_release(struct pci_dev *dev) +{ +} #endif /* CONFIG_PCI_IOV */ Index: linux-2.6/drivers/pci/remove.c =================================================================== --- linux-2.6.orig/drivers/pci/remove.c +++ linux-2.6/drivers/pci/remove.c @@ -38,6 +38,8 @@ static void pci_destroy_dev(struct pci_d list_del(&dev->bus_list); up_write(&pci_bus_sem); + virtfn_release(dev); + pci_free_resources(dev); put_device(&dev->dev); } @@ -107,8 +109,23 @@ static void pci_remove_bus_device(struct */ void pci_stop_and_remove_bus_device(struct pci_dev *dev) { +#ifdef CONFIG_PCI_ATS + struct pci_sriov *iov = NULL; + int locked = 0; + + if (dev->is_virtfn) { + iov = dev->physfn->sriov; + locked = mutex_trylock(&iov->dev->sriov->lock); + } +#endif + pci_stop_bus_device(dev); pci_remove_bus_device(dev); + +#ifdef CONFIG_PCI_ATS + if (locked) + mutex_unlock(&iov->dev->sriov->lock); +#endif } EXPORT_SYMBOL(pci_stop_and_remove_bus_device);