[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [PATCH v7 01/12] vpci: introduce per-domain lock to protect vpci structure
On 05.07.2023 10:59, Roger Pau Monné wrote: > On Wed, Jul 05, 2023 at 09:11:10AM +0200, Jan Beulich wrote: >> On 04.07.2023 23:03, Volodymyr Babchuk wrote: >>> I am currently implementing your proposal (along with Jan's >>> suggestions), but I am facing ABBA deadlock with IOMMU's >>> reassign_device() call, which has this piece of code: >>> >>> list_move(&pdev->domain_list, &target->pdev_list); >>> >>> My immediate change was: >>> >>> write_lock(&pdev->domain->pci_lock); >>> list_del(&pdev->domain_list); >>> write_unlock(&pdev->domain->pci_lock); >>> >>> write_lock(&target->pci_lock); >>> list_add(&pdev->domain_list, &target->pdev_list); >>> write_unlock(&target->pci_lock); >>> >>> But this will not work because reassign_device is called from >>> pci_release_devices() which iterates over d->pdev_list, so we need to >>> take a d->pci_lock early. >>> >>> Any suggestions on how to fix this? My idea is to remove a device from a >>> list one at time: >>> >>> int pci_release_devices(struct domain *d) >>> { >>> struct pci_dev *pdev; >>> u8 bus, devfn; >>> int ret; >>> >>> pcidevs_lock(); >>> write_lock(&d->pci_lock); >>> ret = arch_pci_clean_pirqs(d); >>> if ( ret ) >>> { >>> pcidevs_unlock(); >>> write_unlock(&d->pci_lock); >>> return ret; >>> } >>> >>> while ( !list_empty(&d->pdev_list) ) >>> { >>> pdev = list_entry(&d->pdev_list, struct pci_dev, domain_list); >>> bus = pdev->bus; >>> devfn = pdev->devfn; >>> list_del(&pdev->domain_list); >>> write_unlock(&d->pci_lock); >>> ret = deassign_device(d, pdev->seg, bus, devfn) ?: ret; >>> write_lock(&d->pci_lock); >> >> I think it needs doing almost like this, but with two more tweaks and >> no list_del() right here (first and foremost to avoid needing to >> figure whether removing early isn't going to subtly break anything; >> see below for an error case that would end up with changed behavior): >> >> while ( !list_empty(&d->pdev_list) ) >> { >> const struct pci_dev *pdev = list_first_entry(&d->pdev_list, struct >> pci_dev, domain_list); >> uint16_t seg = pdev->seg; >> uint8_t bus = pdev->bus; >> uint8_t devfn = pdev->devfn; >> >> write_unlock(&d->pci_lock); > > I think you need to remove the device from the pdev_list before > dropping the lock, or else release could race with other operations. > > That's unlikely, but still if the lock is dropped and the routine > needs to operate on the device it is better remove such device from > the domain so other operations cannot get a reference to it. > > Otherwise you could modify reassign_device() implementations so they > require the caller to hold the source->pci_lock when calling the > routine, but that's ugly because the lock would need to be dropped in > order to reassign the device from source to target domains. > > Another option would be to move the whole d->pdev_list to a local > variable (so that d->pdev_list would be empty) and then iterate over > it without the d->pci_lock. On failure you would take the lock and > add the failing device back into d->pdev_list. Conceptually I like this last variant, but like the individual list_del() it requires auditing code for no dependency on the device still being on that list. In fact deassign_device()'s use of pci_get_pdev() does. The function would then need changing to have struct pci_dev * passed in. Yet who knows where else there are uses of pci_get_pdev() lurking. Jan
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |