mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-12-27 10:01:39 -05:00
When triggering PCI device recovery by writing into the SysFS attribute
`recover` of a Physical Function with existing child SR-IOV Virtual
Functions, lockdep is reporting a possible deadlock between three
threads:
Thread (A) Thread (B) Thread (C)
| | |
recover_store() zpci_scan_devices() zpci_scan_devices()
lock(pci_rescan_remove_lock) | |
| | |
| | zpci_bus_scan_busses()
| | lock(zbus_list_lock)
| zpci_add_device() |
| lock(zpci_add_remove_lock) |
| | ┴
| | zpci_bus_scan_bus()
| | lock(pci_rescan_remove_lock)
┴ |
zpci_zdev_put() |
lock(zpci_add_remove_lock) |
┴
zpci_bus_get()
lock(zbus_list_lock)
In zpci_bus_scan_busses() the `zbus_list_lock` is taken for the whole
duration of the function, which also includes taking
`pci_rescan_remove_lock`, among other things. But `zbus_list_lock` only
really needs to protect the modification of the global registration
`zbus_list`, it can be dropped while the functions within the list
iteration run; this way we break the cycle above.
Break up zpci_bus_scan_busses() into an "iterator" zpci_bus_get_next()
that iterates over `zbus_list` element by element, and acquires and
releases `zbus_list_lock` as necessary, but never keep holding it.
References to `zpci_bus` objects are also acquired and released.
The reference counting on `zpci_bus` objects is also changed so that all
put() and get() operations are done under the protection of
`zbus_list_lock`, and if the operation results in a modification of
`zpci_bus_list`, this modification is done in the same critical section
(apart the very first initialization). This way objects are never seen
on the list that are about to be released and/or half-initialized.
Fixes: 14c87ba812 ("s390/pci: separate zbus registration from scanning")
Suggested-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Benjamin Block <bblock@linux.ibm.com>
Reviewed-by: Niklas Schnelle <schnelle@linux.ibm.com>
Reviewed-by: Gerd Bayer <gbayer@linux.ibm.com>
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
58 lines
1.5 KiB
C
58 lines
1.5 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* Copyright IBM Corp. 2020
|
|
*
|
|
* Author(s):
|
|
* Pierre Morel <pmorel@linux.ibm.com>
|
|
*
|
|
*/
|
|
#ifndef __S390_PCI_BUS_H
|
|
#define __S390_PCI_BUS_H
|
|
|
|
#include <linux/pci.h>
|
|
|
|
int zpci_bus_device_register(struct zpci_dev *zdev, struct pci_ops *ops);
|
|
void zpci_bus_device_unregister(struct zpci_dev *zdev);
|
|
|
|
int zpci_bus_scan_bus(struct zpci_bus *zbus);
|
|
void zpci_bus_get_next(struct zpci_bus **pos);
|
|
|
|
/**
|
|
* zpci_bus_for_each - iterate over all the registered zbus objects
|
|
* @pos: a struct zpci_bus * as cursor
|
|
*
|
|
* Acquires and releases references as the cursor iterates over the registered
|
|
* objects. Is tolerant against concurrent removals of objects.
|
|
*
|
|
* Context: Process context. May sleep.
|
|
*/
|
|
#define zpci_bus_for_each(pos) \
|
|
for ((pos) = NULL, zpci_bus_get_next(&(pos)); (pos) != NULL; \
|
|
zpci_bus_get_next(&(pos)))
|
|
|
|
int zpci_bus_scan_device(struct zpci_dev *zdev);
|
|
void zpci_bus_remove_device(struct zpci_dev *zdev, bool set_error);
|
|
|
|
void zpci_release_device(struct kref *kref);
|
|
|
|
void zpci_zdev_put(struct zpci_dev *zdev);
|
|
|
|
static inline void zpci_zdev_get(struct zpci_dev *zdev)
|
|
{
|
|
kref_get(&zdev->kref);
|
|
}
|
|
|
|
int zpci_alloc_domain(int domain);
|
|
void zpci_free_domain(int domain);
|
|
int zpci_setup_bus_resources(struct zpci_dev *zdev);
|
|
|
|
static inline struct zpci_dev *zdev_from_bus(struct pci_bus *bus,
|
|
unsigned int devfn)
|
|
{
|
|
struct zpci_bus *zbus = bus->sysdata;
|
|
|
|
return (devfn >= ZPCI_FUNCTIONS_PER_BUS) ? NULL : zbus->function[devfn];
|
|
}
|
|
|
|
#endif /* __S390_PCI_BUS_H */
|