|
[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
Hi Jan,
Jan Beulich <jbeulich@xxxxxxxx> writes:
> 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);
> ret = deassign_device(d, seg, bus, devfn) ?: ret;
> write_lock(&d->pci_lock);
> }
>
> One caveat though: The original list_for_each_entry_safe() guarantees
> the loop to complete; your use of list_del() would guarantee that too,
> but then the device wouldn't be on the list anymore if deassign_device()
> failed. Therefore I guess you will need another local list where you
> (temporarily) put all the devices which deassign_device() left on the
> list, and which you would then move back to d->pdev_list after the loop
> has finished.
Okay, taking into the account all that you wrote, I came with this
implementation:
int pci_release_devices(struct domain *d)
{
int combined_ret;
LIST_HEAD(failed_pdevs);
pcidevs_lock();
write_lock(&d->pci_lock);
combined_ret = arch_pci_clean_pirqs(d);
if ( combined_ret )
{
pcidevs_unlock();
write_unlock(&d->pci_lock);
return combined_ret;
}
while ( !list_empty(&d->pdev_list) )
{
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;
int ret;
write_unlock(&d->pci_lock);
ret = deassign_device(d, seg, bus, devfn);
write_lock(&d->pci_lock);
if ( ret )
{
bool still_present = false;
const struct pci_dev *tmp;
for_each_pdev ( d, tmp )
{
if ( tmp == (const struct pci_dev*)pdev )
{
still_present = true;
break;
}
}
if ( still_present )
list_move(&pdev->domain_list, &failed_pdevs);
combined_ret = ret;
}
}
list_splice(&failed_pdevs, &d->pdev_list);
write_unlock(&d->pci_lock);
pcidevs_unlock();
return combined_ret;
}
> (Whether it is sufficient to inspect the list head to
> determine whether the pdev is still on the list needs careful checking.)
I am not sure that this is enough. We dropped d->pci_lock so we can
expect that the list was permutated in a random way.
--
WBR, Volodymyr
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |