Merge branch 'pci/reset'
- Cache PCIe Device Capabilities register (Amey Narkhede) - Add pcie_reset_flr() with 'probe' argument (Amey Narkhede) - Add pdev->reset_methods[] array to track reset method ordering (Amey Narkhede) - Remove reset_fn field from pci_dev (Amey Narkhede) - Add sysfs interface to query and set device reset mechanism (Amey Narkhede) - Add pci_set_acpi_fwnode() to set ACPI_COMPANION (Shanker Donthineni) - Use acpi_pci_power_manageable() instead of duplicating logic (Shanker Donthineni) - Set ACPI fwnode early and at the same time with OF (Shanker Donthineni) - Add support for ACPI _RST reset method (Shanker Donthineni) - Change reset function 'probe' argument to bool (Amey Narkhede) * pci/reset: PCI: Change the type of probe argument in reset functions PCI: Add support for ACPI _RST reset method PCI: Setup ACPI fwnode early and at the same time with OF PCI: Use acpi_pci_power_manageable() PCI: Add pci_set_acpi_fwnode() to set ACPI_COMPANION PCI: Allow userspace to query and set device reset mechanism PCI: Remove reset_fn field from pci_dev PCI: Add array to track reset method ordering PCI: Add pcie_reset_flr() with 'probe' argument PCI: Cache PCIe Device Capabilities register
This commit is contained in:
commit
e210d9fc09
|
@ -121,6 +121,23 @@ Description:
|
|||
child buses, and re-discover devices removed earlier
|
||||
from this part of the device tree.
|
||||
|
||||
What: /sys/bus/pci/devices/.../reset_method
|
||||
Date: August 2021
|
||||
Contact: Amey Narkhede <ameynarkhede03@gmail.com>
|
||||
Description:
|
||||
Some devices allow an individual function to be reset
|
||||
without affecting other functions in the same slot.
|
||||
|
||||
For devices that have this support, a file named
|
||||
reset_method is present in sysfs. Reading this file
|
||||
gives names of the supported and enabled reset methods and
|
||||
their ordering. Writing a space-separated list of names of
|
||||
reset methods sets the reset methods and ordering to be
|
||||
used when resetting the device. Writing an empty string
|
||||
disables the ability to reset the device. Writing
|
||||
"default" enables all supported reset methods in the
|
||||
default ordering.
|
||||
|
||||
What: /sys/bus/pci/devices/.../reset
|
||||
Date: July 2009
|
||||
Contact: Michael S. Tsirkin <mst@redhat.com>
|
||||
|
|
|
@ -306,9 +306,7 @@ static int nitrox_device_flr(struct pci_dev *pdev)
|
|||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* check flr support */
|
||||
if (pcie_has_flr(pdev))
|
||||
pcie_flr(pdev);
|
||||
pcie_reset_flr(pdev, PCI_RESET_DO_RESET);
|
||||
|
||||
pci_restore_state(pdev);
|
||||
|
||||
|
|
|
@ -526,7 +526,7 @@ static void octeon_destroy_resources(struct octeon_device *oct)
|
|||
oct->irq_name_storage = NULL;
|
||||
}
|
||||
/* Soft reset the octeon device before exiting */
|
||||
if (oct->pci_dev->reset_fn)
|
||||
if (!pcie_reset_flr(oct->pci_dev, PCI_RESET_PROBE))
|
||||
octeon_pci_flr(oct);
|
||||
else
|
||||
cn23xx_vf_ask_pf_to_do_flr(oct);
|
||||
|
|
|
@ -184,7 +184,7 @@ void pciehp_release_ctrl(struct controller *ctrl);
|
|||
|
||||
int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot);
|
||||
int pciehp_sysfs_disable_slot(struct hotplug_slot *hotplug_slot);
|
||||
int pciehp_reset_slot(struct hotplug_slot *hotplug_slot, int probe);
|
||||
int pciehp_reset_slot(struct hotplug_slot *hotplug_slot, bool probe);
|
||||
int pciehp_get_attention_status(struct hotplug_slot *hotplug_slot, u8 *status);
|
||||
int pciehp_set_raw_indicator_status(struct hotplug_slot *h_slot, u8 status);
|
||||
int pciehp_get_raw_indicator_status(struct hotplug_slot *h_slot, u8 *status);
|
||||
|
|
|
@ -870,7 +870,7 @@ void pcie_disable_interrupt(struct controller *ctrl)
|
|||
* momentarily, if we see that they could interfere. Also, clear any spurious
|
||||
* events after.
|
||||
*/
|
||||
int pciehp_reset_slot(struct hotplug_slot *hotplug_slot, int probe)
|
||||
int pciehp_reset_slot(struct hotplug_slot *hotplug_slot, bool probe)
|
||||
{
|
||||
struct controller *ctrl = to_ctrl(hotplug_slot);
|
||||
struct pci_dev *pdev = ctrl_dev(ctrl);
|
||||
|
|
|
@ -526,7 +526,7 @@ static int pnv_php_enable(struct pnv_php_slot *php_slot, bool rescan)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int pnv_php_reset_slot(struct hotplug_slot *slot, int probe)
|
||||
static int pnv_php_reset_slot(struct hotplug_slot *slot, bool probe)
|
||||
{
|
||||
struct pnv_php_slot *php_slot = to_pnv_php_slot(slot);
|
||||
struct pci_dev *bridge = php_slot->pdev;
|
||||
|
|
|
@ -934,58 +934,77 @@ static pci_power_t acpi_pci_choose_state(struct pci_dev *pdev)
|
|||
|
||||
static struct acpi_device *acpi_pci_find_companion(struct device *dev);
|
||||
|
||||
static bool acpi_pci_bridge_d3(struct pci_dev *dev)
|
||||
void pci_set_acpi_fwnode(struct pci_dev *dev)
|
||||
{
|
||||
const struct fwnode_handle *fwnode;
|
||||
struct acpi_device *adev;
|
||||
struct pci_dev *root;
|
||||
u8 val;
|
||||
if (!ACPI_COMPANION(&dev->dev) && !pci_dev_is_added(dev))
|
||||
ACPI_COMPANION_SET(&dev->dev,
|
||||
acpi_pci_find_companion(&dev->dev));
|
||||
}
|
||||
|
||||
if (!dev->is_hotplug_bridge)
|
||||
return false;
|
||||
/**
|
||||
* pci_dev_acpi_reset - do a function level reset using _RST method
|
||||
* @dev: device to reset
|
||||
* @probe: if true, return 0 if device supports _RST
|
||||
*/
|
||||
int pci_dev_acpi_reset(struct pci_dev *dev, bool probe)
|
||||
{
|
||||
acpi_handle handle = ACPI_HANDLE(&dev->dev);
|
||||
|
||||
/* Assume D3 support if the bridge is power-manageable by ACPI. */
|
||||
adev = ACPI_COMPANION(&dev->dev);
|
||||
if (!adev && !pci_dev_is_added(dev)) {
|
||||
adev = acpi_pci_find_companion(&dev->dev);
|
||||
ACPI_COMPANION_SET(&dev->dev, adev);
|
||||
if (!handle || !acpi_has_method(handle, "_RST"))
|
||||
return -ENOTTY;
|
||||
|
||||
if (probe)
|
||||
return 0;
|
||||
|
||||
if (ACPI_FAILURE(acpi_evaluate_object(handle, "_RST", NULL, NULL))) {
|
||||
pci_warn(dev, "ACPI _RST failed\n");
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
||||
if (adev && acpi_device_power_manageable(adev))
|
||||
return true;
|
||||
|
||||
/*
|
||||
* Look for a special _DSD property for the root port and if it
|
||||
* is set we know the hierarchy behind it supports D3 just fine.
|
||||
*/
|
||||
root = pcie_find_root_port(dev);
|
||||
if (!root)
|
||||
return false;
|
||||
|
||||
adev = ACPI_COMPANION(&root->dev);
|
||||
if (root == dev) {
|
||||
/*
|
||||
* It is possible that the ACPI companion is not yet bound
|
||||
* for the root port so look it up manually here.
|
||||
*/
|
||||
if (!adev && !pci_dev_is_added(root))
|
||||
adev = acpi_pci_find_companion(&root->dev);
|
||||
}
|
||||
|
||||
if (!adev)
|
||||
return false;
|
||||
|
||||
fwnode = acpi_fwnode_handle(adev);
|
||||
if (fwnode_property_read_u8(fwnode, "HotPlugSupportInD3", &val))
|
||||
return false;
|
||||
|
||||
return val == 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool acpi_pci_power_manageable(struct pci_dev *dev)
|
||||
{
|
||||
struct acpi_device *adev = ACPI_COMPANION(&dev->dev);
|
||||
return adev ? acpi_device_power_manageable(adev) : false;
|
||||
|
||||
if (!adev)
|
||||
return false;
|
||||
return acpi_device_power_manageable(adev);
|
||||
}
|
||||
|
||||
static bool acpi_pci_bridge_d3(struct pci_dev *dev)
|
||||
{
|
||||
const union acpi_object *obj;
|
||||
struct acpi_device *adev;
|
||||
struct pci_dev *rpdev;
|
||||
|
||||
if (!dev->is_hotplug_bridge)
|
||||
return false;
|
||||
|
||||
/* Assume D3 support if the bridge is power-manageable by ACPI. */
|
||||
if (acpi_pci_power_manageable(dev))
|
||||
return true;
|
||||
|
||||
/*
|
||||
* The ACPI firmware will provide the device-specific properties through
|
||||
* _DSD configuration object. Look for the 'HotPlugSupportInD3' property
|
||||
* for the root port and if it is set we know the hierarchy behind it
|
||||
* supports D3 just fine.
|
||||
*/
|
||||
rpdev = pcie_find_root_port(dev);
|
||||
if (!rpdev)
|
||||
return false;
|
||||
|
||||
adev = ACPI_COMPANION(&rpdev->dev);
|
||||
if (!adev)
|
||||
return false;
|
||||
|
||||
if (acpi_dev_get_property(adev, "HotPlugSupportInD3",
|
||||
ACPI_TYPE_INTEGER, &obj) < 0)
|
||||
return false;
|
||||
|
||||
return obj->integer.value == 1;
|
||||
}
|
||||
|
||||
static int acpi_pci_set_power_state(struct pci_dev *dev, pci_power_t state)
|
||||
|
|
|
@ -1367,7 +1367,7 @@ static umode_t pci_dev_reset_attr_is_visible(struct kobject *kobj,
|
|||
{
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
|
||||
if (!pdev->reset_fn)
|
||||
if (!pci_reset_supported(pdev))
|
||||
return 0;
|
||||
|
||||
return a->mode;
|
||||
|
@ -1491,6 +1491,7 @@ const struct attribute_group *pci_dev_groups[] = {
|
|||
&pci_dev_config_attr_group,
|
||||
&pci_dev_rom_attr_group,
|
||||
&pci_dev_reset_attr_group,
|
||||
&pci_dev_reset_method_attr_group,
|
||||
&pci_dev_vpd_attr_group,
|
||||
#ifdef CONFIG_DMI
|
||||
&pci_dev_smbios_attr_group,
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include <linux/vmalloc.h>
|
||||
#include <asm/dma.h>
|
||||
#include <linux/aer.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include "pci.h"
|
||||
|
||||
DEFINE_MUTEX(pci_slot_mutex);
|
||||
|
@ -72,6 +73,11 @@ static void pci_dev_d3_sleep(struct pci_dev *dev)
|
|||
msleep(delay);
|
||||
}
|
||||
|
||||
bool pci_reset_supported(struct pci_dev *dev)
|
||||
{
|
||||
return dev->reset_methods[0] != 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PCI_DOMAINS
|
||||
int pci_domains_supported = 1;
|
||||
#endif
|
||||
|
@ -4621,32 +4627,12 @@ int pci_wait_for_pending_transaction(struct pci_dev *dev)
|
|||
}
|
||||
EXPORT_SYMBOL(pci_wait_for_pending_transaction);
|
||||
|
||||
/**
|
||||
* pcie_has_flr - check if a device supports function level resets
|
||||
* @dev: device to check
|
||||
*
|
||||
* Returns true if the device advertises support for PCIe function level
|
||||
* resets.
|
||||
*/
|
||||
bool pcie_has_flr(struct pci_dev *dev)
|
||||
{
|
||||
u32 cap;
|
||||
|
||||
if (dev->dev_flags & PCI_DEV_FLAGS_NO_FLR_RESET)
|
||||
return false;
|
||||
|
||||
pcie_capability_read_dword(dev, PCI_EXP_DEVCAP, &cap);
|
||||
return cap & PCI_EXP_DEVCAP_FLR;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcie_has_flr);
|
||||
|
||||
/**
|
||||
* pcie_flr - initiate a PCIe function level reset
|
||||
* @dev: device to reset
|
||||
*
|
||||
* Initiate a function level reset on @dev. The caller should ensure the
|
||||
* device supports FLR before calling this function, e.g. by using the
|
||||
* pcie_has_flr() helper.
|
||||
* Initiate a function level reset unconditionally on @dev without
|
||||
* checking any flags and DEVCAP
|
||||
*/
|
||||
int pcie_flr(struct pci_dev *dev)
|
||||
{
|
||||
|
@ -4669,7 +4655,29 @@ int pcie_flr(struct pci_dev *dev)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(pcie_flr);
|
||||
|
||||
static int pci_af_flr(struct pci_dev *dev, int probe)
|
||||
/**
|
||||
* pcie_reset_flr - initiate a PCIe function level reset
|
||||
* @dev: device to reset
|
||||
* @probe: if true, return 0 if device can be reset this way
|
||||
*
|
||||
* Initiate a function level reset on @dev.
|
||||
*/
|
||||
int pcie_reset_flr(struct pci_dev *dev, bool probe)
|
||||
{
|
||||
if (dev->dev_flags & PCI_DEV_FLAGS_NO_FLR_RESET)
|
||||
return -ENOTTY;
|
||||
|
||||
if (!(dev->devcap & PCI_EXP_DEVCAP_FLR))
|
||||
return -ENOTTY;
|
||||
|
||||
if (probe)
|
||||
return 0;
|
||||
|
||||
return pcie_flr(dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcie_reset_flr);
|
||||
|
||||
static int pci_af_flr(struct pci_dev *dev, bool probe)
|
||||
{
|
||||
int pos;
|
||||
u8 cap;
|
||||
|
@ -4716,7 +4724,7 @@ static int pci_af_flr(struct pci_dev *dev, int probe)
|
|||
/**
|
||||
* pci_pm_reset - Put device into PCI_D3 and back into PCI_D0.
|
||||
* @dev: Device to reset.
|
||||
* @probe: If set, only check if the device can be reset this way.
|
||||
* @probe: if true, return 0 if the device can be reset this way.
|
||||
*
|
||||
* If @dev supports native PCI PM and its PCI_PM_CTRL_NO_SOFT_RESET flag is
|
||||
* unset, it will be reinitialized internally when going from PCI_D3hot to
|
||||
|
@ -4728,7 +4736,7 @@ static int pci_af_flr(struct pci_dev *dev, int probe)
|
|||
* by default (i.e. unless the @dev's d3hot_delay field has a different value).
|
||||
* Moreover, only devices in D0 can be reset by this function.
|
||||
*/
|
||||
static int pci_pm_reset(struct pci_dev *dev, int probe)
|
||||
static int pci_pm_reset(struct pci_dev *dev, bool probe)
|
||||
{
|
||||
u16 csr;
|
||||
|
||||
|
@ -4988,7 +4996,7 @@ int pci_bridge_secondary_bus_reset(struct pci_dev *dev)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(pci_bridge_secondary_bus_reset);
|
||||
|
||||
static int pci_parent_bus_reset(struct pci_dev *dev, int probe)
|
||||
static int pci_parent_bus_reset(struct pci_dev *dev, bool probe)
|
||||
{
|
||||
struct pci_dev *pdev;
|
||||
|
||||
|
@ -5006,7 +5014,7 @@ static int pci_parent_bus_reset(struct pci_dev *dev, int probe)
|
|||
return pci_bridge_secondary_bus_reset(dev->bus->self);
|
||||
}
|
||||
|
||||
static int pci_reset_hotplug_slot(struct hotplug_slot *hotplug, int probe)
|
||||
static int pci_reset_hotplug_slot(struct hotplug_slot *hotplug, bool probe)
|
||||
{
|
||||
int rc = -ENOTTY;
|
||||
|
||||
|
@ -5021,7 +5029,7 @@ static int pci_reset_hotplug_slot(struct hotplug_slot *hotplug, int probe)
|
|||
return rc;
|
||||
}
|
||||
|
||||
static int pci_dev_reset_slot_function(struct pci_dev *dev, int probe)
|
||||
static int pci_dev_reset_slot_function(struct pci_dev *dev, bool probe)
|
||||
{
|
||||
if (dev->multifunction || dev->subordinate || !dev->slot ||
|
||||
dev->dev_flags & PCI_DEV_FLAGS_NO_BUS_RESET)
|
||||
|
@ -5030,7 +5038,7 @@ static int pci_dev_reset_slot_function(struct pci_dev *dev, int probe)
|
|||
return pci_reset_hotplug_slot(dev->slot->hotplug, probe);
|
||||
}
|
||||
|
||||
static int pci_reset_bus_function(struct pci_dev *dev, int probe)
|
||||
static int pci_reset_bus_function(struct pci_dev *dev, bool probe)
|
||||
{
|
||||
int rc;
|
||||
|
||||
|
@ -5114,6 +5122,139 @@ static void pci_dev_restore(struct pci_dev *dev)
|
|||
err_handler->reset_done(dev);
|
||||
}
|
||||
|
||||
/* dev->reset_methods[] is a 0-terminated list of indices into this array */
|
||||
static const struct pci_reset_fn_method pci_reset_fn_methods[] = {
|
||||
{ },
|
||||
{ pci_dev_specific_reset, .name = "device_specific" },
|
||||
{ pci_dev_acpi_reset, .name = "acpi" },
|
||||
{ pcie_reset_flr, .name = "flr" },
|
||||
{ pci_af_flr, .name = "af_flr" },
|
||||
{ pci_pm_reset, .name = "pm" },
|
||||
{ pci_reset_bus_function, .name = "bus" },
|
||||
};
|
||||
|
||||
static ssize_t reset_method_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
ssize_t len = 0;
|
||||
int i, m;
|
||||
|
||||
for (i = 0; i < PCI_NUM_RESET_METHODS; i++) {
|
||||
m = pdev->reset_methods[i];
|
||||
if (!m)
|
||||
break;
|
||||
|
||||
len += sysfs_emit_at(buf, len, "%s%s", len ? " " : "",
|
||||
pci_reset_fn_methods[m].name);
|
||||
}
|
||||
|
||||
if (len)
|
||||
len += sysfs_emit_at(buf, len, "\n");
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int reset_method_lookup(const char *name)
|
||||
{
|
||||
int m;
|
||||
|
||||
for (m = 1; m < PCI_NUM_RESET_METHODS; m++) {
|
||||
if (sysfs_streq(name, pci_reset_fn_methods[m].name))
|
||||
return m;
|
||||
}
|
||||
|
||||
return 0; /* not found */
|
||||
}
|
||||
|
||||
static ssize_t reset_method_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
char *options, *name;
|
||||
int m, n;
|
||||
u8 reset_methods[PCI_NUM_RESET_METHODS] = { 0 };
|
||||
|
||||
if (sysfs_streq(buf, "")) {
|
||||
pdev->reset_methods[0] = 0;
|
||||
pci_warn(pdev, "All device reset methods disabled by user");
|
||||
return count;
|
||||
}
|
||||
|
||||
if (sysfs_streq(buf, "default")) {
|
||||
pci_init_reset_methods(pdev);
|
||||
return count;
|
||||
}
|
||||
|
||||
options = kstrndup(buf, count, GFP_KERNEL);
|
||||
if (!options)
|
||||
return -ENOMEM;
|
||||
|
||||
n = 0;
|
||||
while ((name = strsep(&options, " ")) != NULL) {
|
||||
if (sysfs_streq(name, ""))
|
||||
continue;
|
||||
|
||||
name = strim(name);
|
||||
|
||||
m = reset_method_lookup(name);
|
||||
if (!m) {
|
||||
pci_err(pdev, "Invalid reset method '%s'", name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (pci_reset_fn_methods[m].reset_fn(pdev, PCI_RESET_PROBE)) {
|
||||
pci_err(pdev, "Unsupported reset method '%s'", name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (n == PCI_NUM_RESET_METHODS - 1) {
|
||||
pci_err(pdev, "Too many reset methods\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
reset_methods[n++] = m;
|
||||
}
|
||||
|
||||
reset_methods[n] = 0;
|
||||
|
||||
/* Warn if dev-specific supported but not highest priority */
|
||||
if (pci_reset_fn_methods[1].reset_fn(pdev, PCI_RESET_PROBE) == 0 &&
|
||||
reset_methods[0] != 1)
|
||||
pci_warn(pdev, "Device-specific reset disabled/de-prioritized by user");
|
||||
memcpy(pdev->reset_methods, reset_methods, sizeof(pdev->reset_methods));
|
||||
kfree(options);
|
||||
return count;
|
||||
|
||||
error:
|
||||
/* Leave previous methods unchanged */
|
||||
kfree(options);
|
||||
return -EINVAL;
|
||||
}
|
||||
static DEVICE_ATTR_RW(reset_method);
|
||||
|
||||
static struct attribute *pci_dev_reset_method_attrs[] = {
|
||||
&dev_attr_reset_method.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static umode_t pci_dev_reset_method_attr_is_visible(struct kobject *kobj,
|
||||
struct attribute *a, int n)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
|
||||
if (!pci_reset_supported(pdev))
|
||||
return 0;
|
||||
|
||||
return a->mode;
|
||||
}
|
||||
|
||||
const struct attribute_group pci_dev_reset_method_attr_group = {
|
||||
.attrs = pci_dev_reset_method_attrs,
|
||||
.is_visible = pci_dev_reset_method_attr_is_visible,
|
||||
};
|
||||
|
||||
/**
|
||||
* __pci_reset_function_locked - reset a PCI device function while holding
|
||||
* the @dev mutex lock.
|
||||
|
@ -5136,66 +5277,64 @@ static void pci_dev_restore(struct pci_dev *dev)
|
|||
*/
|
||||
int __pci_reset_function_locked(struct pci_dev *dev)
|
||||
{
|
||||
int rc;
|
||||
int i, m, rc = -ENOTTY;
|
||||
|
||||
might_sleep();
|
||||
|
||||
/*
|
||||
* A reset method returns -ENOTTY if it doesn't support this device
|
||||
* and we should try the next method.
|
||||
* A reset method returns -ENOTTY if it doesn't support this device and
|
||||
* we should try the next method.
|
||||
*
|
||||
* If it returns 0 (success), we're finished. If it returns any
|
||||
* other error, we're also finished: this indicates that further
|
||||
* reset mechanisms might be broken on the device.
|
||||
* If it returns 0 (success), we're finished. If it returns any other
|
||||
* error, we're also finished: this indicates that further reset
|
||||
* mechanisms might be broken on the device.
|
||||
*/
|
||||
rc = pci_dev_specific_reset(dev, 0);
|
||||
if (rc != -ENOTTY)
|
||||
return rc;
|
||||
if (pcie_has_flr(dev)) {
|
||||
rc = pcie_flr(dev);
|
||||
for (i = 0; i < PCI_NUM_RESET_METHODS; i++) {
|
||||
m = dev->reset_methods[i];
|
||||
if (!m)
|
||||
return -ENOTTY;
|
||||
|
||||
rc = pci_reset_fn_methods[m].reset_fn(dev, PCI_RESET_DO_RESET);
|
||||
if (!rc)
|
||||
return 0;
|
||||
if (rc != -ENOTTY)
|
||||
return rc;
|
||||
}
|
||||
rc = pci_af_flr(dev, 0);
|
||||
if (rc != -ENOTTY)
|
||||
return rc;
|
||||
rc = pci_pm_reset(dev, 0);
|
||||
if (rc != -ENOTTY)
|
||||
return rc;
|
||||
return pci_reset_bus_function(dev, 0);
|
||||
|
||||
return -ENOTTY;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__pci_reset_function_locked);
|
||||
|
||||
/**
|
||||
* pci_probe_reset_function - check whether the device can be safely reset
|
||||
* @dev: PCI device to reset
|
||||
* pci_init_reset_methods - check whether device can be safely reset
|
||||
* and store supported reset mechanisms.
|
||||
* @dev: PCI device to check for reset mechanisms
|
||||
*
|
||||
* Some devices allow an individual function to be reset without affecting
|
||||
* other functions in the same device. The PCI device must be responsive
|
||||
* to PCI config space in order to use this function.
|
||||
* other functions in the same device. The PCI device must be in D0-D3hot
|
||||
* state.
|
||||
*
|
||||
* Returns 0 if the device function can be reset or negative if the
|
||||
* device doesn't support resetting a single function.
|
||||
* Stores reset mechanisms supported by device in reset_methods byte array
|
||||
* which is a member of struct pci_dev.
|
||||
*/
|
||||
int pci_probe_reset_function(struct pci_dev *dev)
|
||||
void pci_init_reset_methods(struct pci_dev *dev)
|
||||
{
|
||||
int rc;
|
||||
int m, i, rc;
|
||||
|
||||
BUILD_BUG_ON(ARRAY_SIZE(pci_reset_fn_methods) != PCI_NUM_RESET_METHODS);
|
||||
|
||||
might_sleep();
|
||||
|
||||
rc = pci_dev_specific_reset(dev, 1);
|
||||
if (rc != -ENOTTY)
|
||||
return rc;
|
||||
if (pcie_has_flr(dev))
|
||||
return 0;
|
||||
rc = pci_af_flr(dev, 1);
|
||||
if (rc != -ENOTTY)
|
||||
return rc;
|
||||
rc = pci_pm_reset(dev, 1);
|
||||
if (rc != -ENOTTY)
|
||||
return rc;
|
||||
i = 0;
|
||||
for (m = 1; m < PCI_NUM_RESET_METHODS; m++) {
|
||||
rc = pci_reset_fn_methods[m].reset_fn(dev, PCI_RESET_PROBE);
|
||||
if (!rc)
|
||||
dev->reset_methods[i++] = m;
|
||||
else if (rc != -ENOTTY)
|
||||
break;
|
||||
}
|
||||
|
||||
return pci_reset_bus_function(dev, 1);
|
||||
dev->reset_methods[i] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5218,7 +5357,7 @@ int pci_reset_function(struct pci_dev *dev)
|
|||
{
|
||||
int rc;
|
||||
|
||||
if (!dev->reset_fn)
|
||||
if (!pci_reset_supported(dev))
|
||||
return -ENOTTY;
|
||||
|
||||
pci_dev_lock(dev);
|
||||
|
@ -5254,7 +5393,7 @@ int pci_reset_function_locked(struct pci_dev *dev)
|
|||
{
|
||||
int rc;
|
||||
|
||||
if (!dev->reset_fn)
|
||||
if (!pci_reset_supported(dev))
|
||||
return -ENOTTY;
|
||||
|
||||
pci_dev_save_and_disable(dev);
|
||||
|
@ -5277,7 +5416,7 @@ int pci_try_reset_function(struct pci_dev *dev)
|
|||
{
|
||||
int rc;
|
||||
|
||||
if (!dev->reset_fn)
|
||||
if (!pci_reset_supported(dev))
|
||||
return -ENOTTY;
|
||||
|
||||
if (!pci_dev_trylock(dev))
|
||||
|
@ -5505,7 +5644,7 @@ static void pci_slot_restore_locked(struct pci_slot *slot)
|
|||
}
|
||||
}
|
||||
|
||||
static int pci_slot_reset(struct pci_slot *slot, int probe)
|
||||
static int pci_slot_reset(struct pci_slot *slot, bool probe)
|
||||
{
|
||||
int rc;
|
||||
|
||||
|
@ -5533,7 +5672,7 @@ static int pci_slot_reset(struct pci_slot *slot, int probe)
|
|||
*/
|
||||
int pci_probe_reset_slot(struct pci_slot *slot)
|
||||
{
|
||||
return pci_slot_reset(slot, 1);
|
||||
return pci_slot_reset(slot, PCI_RESET_PROBE);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_probe_reset_slot);
|
||||
|
||||
|
@ -5556,14 +5695,14 @@ static int __pci_reset_slot(struct pci_slot *slot)
|
|||
{
|
||||
int rc;
|
||||
|
||||
rc = pci_slot_reset(slot, 1);
|
||||
rc = pci_slot_reset(slot, PCI_RESET_PROBE);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (pci_slot_trylock(slot)) {
|
||||
pci_slot_save_and_disable_locked(slot);
|
||||
might_sleep();
|
||||
rc = pci_reset_hotplug_slot(slot->hotplug, 0);
|
||||
rc = pci_reset_hotplug_slot(slot->hotplug, PCI_RESET_DO_RESET);
|
||||
pci_slot_restore_locked(slot);
|
||||
pci_slot_unlock(slot);
|
||||
} else
|
||||
|
@ -5572,7 +5711,7 @@ static int __pci_reset_slot(struct pci_slot *slot)
|
|||
return rc;
|
||||
}
|
||||
|
||||
static int pci_bus_reset(struct pci_bus *bus, int probe)
|
||||
static int pci_bus_reset(struct pci_bus *bus, bool probe)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -5618,14 +5757,14 @@ int pci_bus_error_reset(struct pci_dev *bridge)
|
|||
goto bus_reset;
|
||||
|
||||
list_for_each_entry(slot, &bus->slots, list)
|
||||
if (pci_slot_reset(slot, 0))
|
||||
if (pci_slot_reset(slot, PCI_RESET_DO_RESET))
|
||||
goto bus_reset;
|
||||
|
||||
mutex_unlock(&pci_slot_mutex);
|
||||
return 0;
|
||||
bus_reset:
|
||||
mutex_unlock(&pci_slot_mutex);
|
||||
return pci_bus_reset(bridge->subordinate, 0);
|
||||
return pci_bus_reset(bridge->subordinate, PCI_RESET_DO_RESET);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5636,7 +5775,7 @@ int pci_bus_error_reset(struct pci_dev *bridge)
|
|||
*/
|
||||
int pci_probe_reset_bus(struct pci_bus *bus)
|
||||
{
|
||||
return pci_bus_reset(bus, 1);
|
||||
return pci_bus_reset(bus, PCI_RESET_PROBE);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_probe_reset_bus);
|
||||
|
||||
|
@ -5650,7 +5789,7 @@ static int __pci_reset_bus(struct pci_bus *bus)
|
|||
{
|
||||
int rc;
|
||||
|
||||
rc = pci_bus_reset(bus, 1);
|
||||
rc = pci_bus_reset(bus, PCI_RESET_PROBE);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
|
|
|
@ -33,7 +33,8 @@ enum pci_mmap_api {
|
|||
int pci_mmap_fits(struct pci_dev *pdev, int resno, struct vm_area_struct *vmai,
|
||||
enum pci_mmap_api mmap_api);
|
||||
|
||||
int pci_probe_reset_function(struct pci_dev *dev);
|
||||
bool pci_reset_supported(struct pci_dev *dev);
|
||||
void pci_init_reset_methods(struct pci_dev *dev);
|
||||
int pci_bridge_secondary_bus_reset(struct pci_dev *dev);
|
||||
int pci_bus_error_reset(struct pci_dev *dev);
|
||||
|
||||
|
@ -607,13 +608,18 @@ static inline int pci_enable_ptm(struct pci_dev *dev, u8 *granularity)
|
|||
struct pci_dev_reset_methods {
|
||||
u16 vendor;
|
||||
u16 device;
|
||||
int (*reset)(struct pci_dev *dev, int probe);
|
||||
int (*reset)(struct pci_dev *dev, bool probe);
|
||||
};
|
||||
|
||||
struct pci_reset_fn_method {
|
||||
int (*reset_fn)(struct pci_dev *pdev, bool probe);
|
||||
char *name;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PCI_QUIRKS
|
||||
int pci_dev_specific_reset(struct pci_dev *dev, int probe);
|
||||
int pci_dev_specific_reset(struct pci_dev *dev, bool probe);
|
||||
#else
|
||||
static inline int pci_dev_specific_reset(struct pci_dev *dev, int probe)
|
||||
static inline int pci_dev_specific_reset(struct pci_dev *dev, bool probe)
|
||||
{
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
@ -701,7 +707,15 @@ static inline int pci_aer_raw_clear_status(struct pci_dev *dev) { return -EINVAL
|
|||
#ifdef CONFIG_ACPI
|
||||
int pci_acpi_program_hp_params(struct pci_dev *dev);
|
||||
extern const struct attribute_group pci_dev_acpi_attr_group;
|
||||
void pci_set_acpi_fwnode(struct pci_dev *dev);
|
||||
int pci_dev_acpi_reset(struct pci_dev *dev, bool probe);
|
||||
#else
|
||||
static inline int pci_dev_acpi_reset(struct pci_dev *dev, bool probe)
|
||||
{
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
||||
static inline void pci_set_acpi_fwnode(struct pci_dev *dev) {}
|
||||
static inline int pci_acpi_program_hp_params(struct pci_dev *dev)
|
||||
{
|
||||
return -ENODEV;
|
||||
|
@ -712,4 +726,6 @@ static inline int pci_acpi_program_hp_params(struct pci_dev *dev)
|
|||
extern const struct attribute_group aspm_ctrl_attr_group;
|
||||
#endif
|
||||
|
||||
extern const struct attribute_group pci_dev_reset_method_attr_group;
|
||||
|
||||
#endif /* DRIVERS_PCI_H */
|
||||
|
|
|
@ -1407,13 +1407,11 @@ static pci_ers_result_t aer_root_reset(struct pci_dev *dev)
|
|||
}
|
||||
|
||||
if (type == PCI_EXP_TYPE_RC_EC || type == PCI_EXP_TYPE_RC_END) {
|
||||
if (pcie_has_flr(dev)) {
|
||||
rc = pcie_flr(dev);
|
||||
pci_info(dev, "has been reset (%d)\n", rc);
|
||||
} else {
|
||||
pci_info(dev, "not reset (no FLR support)\n");
|
||||
rc = -ENOTTY;
|
||||
}
|
||||
rc = pcie_reset_flr(dev, PCI_RESET_DO_RESET);
|
||||
if (!rc)
|
||||
pci_info(dev, "has been reset\n");
|
||||
else
|
||||
pci_info(dev, "not reset (no FLR support: %d)\n", rc);
|
||||
} else {
|
||||
rc = pci_bus_error_reset(dev);
|
||||
pci_info(dev, "%s Port link has been reset (%d)\n",
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <linux/hypervisor.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include "pci.h"
|
||||
|
||||
#define CARDBUS_LATENCY_TIMER 176 /* secondary latency timer */
|
||||
|
@ -1498,8 +1499,8 @@ void set_pcie_port_type(struct pci_dev *pdev)
|
|||
pdev->pcie_cap = pos;
|
||||
pci_read_config_word(pdev, pos + PCI_EXP_FLAGS, ®16);
|
||||
pdev->pcie_flags_reg = reg16;
|
||||
pci_read_config_word(pdev, pos + PCI_EXP_DEVCAP, ®16);
|
||||
pdev->pcie_mpss = reg16 & PCI_EXP_DEVCAP_PAYLOAD;
|
||||
pci_read_config_dword(pdev, pos + PCI_EXP_DEVCAP, &pdev->devcap);
|
||||
pdev->pcie_mpss = FIELD_GET(PCI_EXP_DEVCAP_PAYLOAD, pdev->devcap);
|
||||
|
||||
parent = pci_upstream_bridge(pdev);
|
||||
if (!parent)
|
||||
|
@ -1809,6 +1810,9 @@ int pci_setup_device(struct pci_dev *dev)
|
|||
dev->error_state = pci_channel_io_normal;
|
||||
set_pcie_port_type(dev);
|
||||
|
||||
pci_set_of_node(dev);
|
||||
pci_set_acpi_fwnode(dev);
|
||||
|
||||
pci_dev_assign_slot(dev);
|
||||
|
||||
/*
|
||||
|
@ -1946,6 +1950,7 @@ int pci_setup_device(struct pci_dev *dev)
|
|||
default: /* unknown header */
|
||||
pci_err(dev, "unknown header type %02x, ignoring device\n",
|
||||
dev->hdr_type);
|
||||
pci_release_of_node(dev);
|
||||
return -EIO;
|
||||
|
||||
bad:
|
||||
|
@ -2374,10 +2379,7 @@ static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn)
|
|||
dev->vendor = l & 0xffff;
|
||||
dev->device = (l >> 16) & 0xffff;
|
||||
|
||||
pci_set_of_node(dev);
|
||||
|
||||
if (pci_setup_device(dev)) {
|
||||
pci_release_of_node(dev);
|
||||
pci_bus_put(dev->bus);
|
||||
kfree(dev);
|
||||
return NULL;
|
||||
|
@ -2428,9 +2430,7 @@ static void pci_init_capabilities(struct pci_dev *dev)
|
|||
pci_rcec_init(dev); /* Root Complex Event Collector */
|
||||
|
||||
pcie_report_downtraining(dev);
|
||||
|
||||
if (pci_probe_reset_function(dev) == 0)
|
||||
dev->reset_fn = 1;
|
||||
pci_init_reset_methods(dev);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -3742,7 +3742,7 @@ DECLARE_PCI_FIXUP_SUSPEND_LATE(PCI_VENDOR_ID_INTEL,
|
|||
* reset a single function if other methods (e.g. FLR, PM D0->D3) are
|
||||
* not available.
|
||||
*/
|
||||
static int reset_intel_82599_sfp_virtfn(struct pci_dev *dev, int probe)
|
||||
static int reset_intel_82599_sfp_virtfn(struct pci_dev *dev, bool probe)
|
||||
{
|
||||
/*
|
||||
* http://www.intel.com/content/dam/doc/datasheet/82599-10-gbe-controller-datasheet.pdf
|
||||
|
@ -3764,7 +3764,7 @@ static int reset_intel_82599_sfp_virtfn(struct pci_dev *dev, int probe)
|
|||
#define NSDE_PWR_STATE 0xd0100
|
||||
#define IGD_OPERATION_TIMEOUT 10000 /* set timeout 10 seconds */
|
||||
|
||||
static int reset_ivb_igd(struct pci_dev *dev, int probe)
|
||||
static int reset_ivb_igd(struct pci_dev *dev, bool probe)
|
||||
{
|
||||
void __iomem *mmio_base;
|
||||
unsigned long timeout;
|
||||
|
@ -3807,7 +3807,7 @@ static int reset_ivb_igd(struct pci_dev *dev, int probe)
|
|||
}
|
||||
|
||||
/* Device-specific reset method for Chelsio T4-based adapters */
|
||||
static int reset_chelsio_generic_dev(struct pci_dev *dev, int probe)
|
||||
static int reset_chelsio_generic_dev(struct pci_dev *dev, bool probe)
|
||||
{
|
||||
u16 old_command;
|
||||
u16 msix_flags;
|
||||
|
@ -3885,14 +3885,14 @@ static int reset_chelsio_generic_dev(struct pci_dev *dev, int probe)
|
|||
* Chapter 3: NVMe control registers
|
||||
* Chapter 7.3: Reset behavior
|
||||
*/
|
||||
static int nvme_disable_and_flr(struct pci_dev *dev, int probe)
|
||||
static int nvme_disable_and_flr(struct pci_dev *dev, bool probe)
|
||||
{
|
||||
void __iomem *bar;
|
||||
u16 cmd;
|
||||
u32 cfg;
|
||||
|
||||
if (dev->class != PCI_CLASS_STORAGE_EXPRESS ||
|
||||
!pcie_has_flr(dev) || !pci_resource_start(dev, 0))
|
||||
pcie_reset_flr(dev, PCI_RESET_PROBE) || !pci_resource_start(dev, 0))
|
||||
return -ENOTTY;
|
||||
|
||||
if (probe)
|
||||
|
@ -3959,15 +3959,12 @@ static int nvme_disable_and_flr(struct pci_dev *dev, int probe)
|
|||
* device too soon after FLR. A 250ms delay after FLR has heuristically
|
||||
* proven to produce reliably working results for device assignment cases.
|
||||
*/
|
||||
static int delay_250ms_after_flr(struct pci_dev *dev, int probe)
|
||||
static int delay_250ms_after_flr(struct pci_dev *dev, bool probe)
|
||||
{
|
||||
if (!pcie_has_flr(dev))
|
||||
return -ENOTTY;
|
||||
|
||||
if (probe)
|
||||
return 0;
|
||||
return pcie_reset_flr(dev, PCI_RESET_PROBE);
|
||||
|
||||
pcie_flr(dev);
|
||||
pcie_reset_flr(dev, PCI_RESET_DO_RESET);
|
||||
|
||||
msleep(250);
|
||||
|
||||
|
@ -3982,7 +3979,7 @@ static int delay_250ms_after_flr(struct pci_dev *dev, int probe)
|
|||
#define HINIC_OPERATION_TIMEOUT 15000 /* 15 seconds */
|
||||
|
||||
/* Device-specific reset method for Huawei Intelligent NIC virtual functions */
|
||||
static int reset_hinic_vf_dev(struct pci_dev *pdev, int probe)
|
||||
static int reset_hinic_vf_dev(struct pci_dev *pdev, bool probe)
|
||||
{
|
||||
unsigned long timeout;
|
||||
void __iomem *bar;
|
||||
|
@ -4059,7 +4056,7 @@ static const struct pci_dev_reset_methods pci_dev_reset_methods[] = {
|
|||
* because when a host assigns a device to a guest VM, the host may need
|
||||
* to reset the device but probably doesn't have a driver for it.
|
||||
*/
|
||||
int pci_dev_specific_reset(struct pci_dev *dev, int probe)
|
||||
int pci_dev_specific_reset(struct pci_dev *dev, bool probe)
|
||||
{
|
||||
const struct pci_dev_reset_methods *i;
|
||||
|
||||
|
@ -5669,7 +5666,7 @@ static void quirk_reset_lenovo_thinkpad_p50_nvgpu(struct pci_dev *pdev)
|
|||
|
||||
if (pdev->subsystem_vendor != PCI_VENDOR_ID_LENOVO ||
|
||||
pdev->subsystem_device != 0x222e ||
|
||||
!pdev->reset_fn)
|
||||
!pci_reset_supported(pdev))
|
||||
return;
|
||||
|
||||
if (pci_enable_device_mem(pdev))
|
||||
|
|
|
@ -19,7 +19,6 @@ static void pci_stop_dev(struct pci_dev *dev)
|
|||
pci_pme_active(dev, false);
|
||||
|
||||
if (pci_dev_is_added(dev)) {
|
||||
dev->reset_fn = 0;
|
||||
|
||||
device_release_driver(&dev->dev);
|
||||
pci_proc_detach_device(dev);
|
||||
|
|
|
@ -49,6 +49,12 @@
|
|||
PCI_STATUS_SIG_TARGET_ABORT | \
|
||||
PCI_STATUS_PARITY)
|
||||
|
||||
/* Number of reset methods used in pci_reset_fn_methods array in pci.c */
|
||||
#define PCI_NUM_RESET_METHODS 7
|
||||
|
||||
#define PCI_RESET_PROBE true
|
||||
#define PCI_RESET_DO_RESET false
|
||||
|
||||
/*
|
||||
* The PCI interface treats multi-function devices as independent
|
||||
* devices. The slot/function address of each device is encoded
|
||||
|
@ -333,6 +339,7 @@ struct pci_dev {
|
|||
struct rcec_ea *rcec_ea; /* RCEC cached endpoint association */
|
||||
struct pci_dev *rcec; /* Associated RCEC device */
|
||||
#endif
|
||||
u32 devcap; /* PCIe Device Capabilities */
|
||||
u8 pcie_cap; /* PCIe capability offset */
|
||||
u8 msi_cap; /* MSI capability offset */
|
||||
u8 msix_cap; /* MSI-X capability offset */
|
||||
|
@ -428,7 +435,6 @@ struct pci_dev {
|
|||
unsigned int state_saved:1;
|
||||
unsigned int is_physfn:1;
|
||||
unsigned int is_virtfn:1;
|
||||
unsigned int reset_fn:1;
|
||||
unsigned int is_hotplug_bridge:1;
|
||||
unsigned int shpc_managed:1; /* SHPC owned by shpchp */
|
||||
unsigned int is_thunderbolt:1; /* Thunderbolt controller */
|
||||
|
@ -506,6 +512,9 @@ struct pci_dev {
|
|||
char *driver_override; /* Driver name to force a match */
|
||||
|
||||
unsigned long priv_flags; /* Private flags for the PCI driver */
|
||||
|
||||
/* These methods index pci_reset_fn_methods[] */
|
||||
u8 reset_methods[PCI_NUM_RESET_METHODS]; /* In priority order */
|
||||
};
|
||||
|
||||
static inline struct pci_dev *pci_physfn(struct pci_dev *dev)
|
||||
|
@ -1229,7 +1238,7 @@ u32 pcie_bandwidth_available(struct pci_dev *dev, struct pci_dev **limiting_dev,
|
|||
enum pci_bus_speed *speed,
|
||||
enum pcie_link_width *width);
|
||||
void pcie_print_link_status(struct pci_dev *dev);
|
||||
bool pcie_has_flr(struct pci_dev *dev);
|
||||
int pcie_reset_flr(struct pci_dev *dev, bool probe);
|
||||
int pcie_flr(struct pci_dev *dev);
|
||||
int __pci_reset_function_locked(struct pci_dev *dev);
|
||||
int pci_reset_function(struct pci_dev *dev);
|
||||
|
|
|
@ -44,7 +44,7 @@ struct hotplug_slot_ops {
|
|||
int (*get_attention_status) (struct hotplug_slot *slot, u8 *value);
|
||||
int (*get_latch_status) (struct hotplug_slot *slot, u8 *value);
|
||||
int (*get_adapter_status) (struct hotplug_slot *slot, u8 *value);
|
||||
int (*reset_slot) (struct hotplug_slot *slot, int probe);
|
||||
int (*reset_slot) (struct hotplug_slot *slot, bool probe);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue