Merge branch 'pci/edr'

- Update error status after reset_link() so we don't report "recovery
    failed" when it in fact succeeded (Kuppuswamy Sathyanarayanan)

  - Move DPC data into struct pci_dev instead of allocating a separate
    struct dpc_dev (Bjorn Helgaas)

  - Remove AER/DPC service dependency to simplify error recovery
    (Kuppuswamy Sathyanarayanan)

  - Return error recovery status for future use by EDR, which needs to tell
    firmware whether recovery was successful (Kuppuswamy Sathyanarayanan)

  - Cache DPC capability info in core since it's needed by EDR as well as
    DPC driver (Kuppuswamy Sathyanarayanan)

  - Add pci_aer_raw_clear_status() to allow EDR recovery path to clear AER
    status even when OS doesn't own the AER capability (Kuppuswamy
    Sathyanarayanan)

  - Add Error Disconnect Recover (EDR) support, so firmware can use ACPI
    notification to tell the OS that devices have been disconnected, e.g.,
    via DPC, and that OS should attempt recovery (Kuppuswamy
    Sathyanarayanan)

  - Rename AER error status clearing interfaces to be more consistent
    (Kuppuswamy Sathyanarayanan)

* pci/edr:
  PCI/AER: Rationalize error status register clearing
  PCI/DPC: Add Error Disconnect Recover (EDR) support
  PCI/DPC: Expose dpc_process_error(), dpc_reset_link() for use by EDR
  PCI/AER: Add pci_aer_raw_clear_status() to unconditionally clear Error Status
  PCI/DPC: Cache DPC capabilities in pci_init_capabilities()
  PCI/ERR: Return status of pcie_do_recovery()
  PCI/ERR: Remove service dependency in pcie_do_recovery()
  PCI/DPC: Move DPC data into struct pci_dev
  PCI/ERR: Update error status after reset_link()
  PCI/ERR: Combine pci_channel_io_frozen cases
Esse commit está contido em:
Bjorn Helgaas
2020-04-02 14:26:30 -05:00
21 arquivos alterados com 409 adições e 208 exclusões

Ver arquivo

@@ -1241,6 +1241,7 @@ static void pci_acpi_setup(struct device *dev)
pci_acpi_optimize_delay(pci_dev, adev->handle);
pci_acpi_set_untrusted(pci_dev);
pci_acpi_add_edr_notifier(pci_dev);
pci_acpi_add_pm_notifier(adev, pci_dev);
if (!adev->wakeup.flags.valid)
@@ -1268,6 +1269,7 @@ static void pci_acpi_cleanup(struct device *dev)
if (!adev)
return;
pci_acpi_remove_edr_notifier(pci_dev);
pci_acpi_remove_pm_notifier(adev);
if (adev->wakeup.flags.valid) {
acpi_device_power_remove_dependent(adev, dev);

Ver arquivo

@@ -1503,7 +1503,7 @@ void pci_restore_state(struct pci_dev *dev)
pci_restore_rebar_state(dev);
pci_restore_dpc_state(dev);
pci_cleanup_aer_error_status_regs(dev);
pci_aer_clear_status(dev);
pci_restore_aer_state(dev);
pci_restore_config_space(dev);

Ver arquivo

@@ -448,9 +448,13 @@ void aer_print_error(struct pci_dev *dev, struct aer_err_info *info);
#ifdef CONFIG_PCIE_DPC
void pci_save_dpc_state(struct pci_dev *dev);
void pci_restore_dpc_state(struct pci_dev *dev);
void pci_dpc_init(struct pci_dev *pdev);
void dpc_process_error(struct pci_dev *pdev);
pci_ers_result_t dpc_reset_link(struct pci_dev *pdev);
#else
static inline void pci_save_dpc_state(struct pci_dev *dev) {}
static inline void pci_restore_dpc_state(struct pci_dev *dev) {}
static inline void pci_dpc_init(struct pci_dev *pdev) {}
#endif
#ifdef CONFIG_PCI_ATS
@@ -547,8 +551,9 @@ static inline int pci_dev_specific_disable_acs_redir(struct pci_dev *dev)
#endif
/* PCI error reporting and recovery */
void pcie_do_recovery(struct pci_dev *dev, enum pci_channel_state state,
u32 service);
pci_ers_result_t pcie_do_recovery(struct pci_dev *dev,
enum pci_channel_state state,
pci_ers_result_t (*reset_link)(struct pci_dev *pdev));
bool pcie_wait_for_link(struct pci_dev *pdev, bool active);
#ifdef CONFIG_PCIEASPM
@@ -651,12 +656,16 @@ void pci_aer_exit(struct pci_dev *dev);
extern const struct attribute_group aer_stats_attr_group;
void pci_aer_clear_fatal_status(struct pci_dev *dev);
void pci_aer_clear_device_status(struct pci_dev *dev);
int pci_aer_clear_status(struct pci_dev *dev);
int pci_aer_raw_clear_status(struct pci_dev *dev);
#else
static inline void pci_no_aer(void) { }
static inline void pci_aer_init(struct pci_dev *d) { }
static inline void pci_aer_exit(struct pci_dev *d) { }
static inline void pci_aer_clear_fatal_status(struct pci_dev *dev) { }
static inline void pci_aer_clear_device_status(struct pci_dev *dev) { }
static inline int pci_aer_clear_status(struct pci_dev *dev) { return -EINVAL; }
static inline int pci_aer_raw_clear_status(struct pci_dev *dev) { return -EINVAL; }
#endif
#ifdef CONFIG_ACPI

Ver arquivo

@@ -140,3 +140,13 @@ config PCIE_BW
This enables PCI Express Bandwidth Change Notification. If
you know link width or rate changes occur only to correct
unreliable links, you may answer Y.
config PCIE_EDR
bool "PCI Express Error Disconnect Recover support"
depends on PCIE_DPC && ACPI
help
This option adds Error Disconnect Recover support as specified
in the Downstream Port Containment Related Enhancements ECN to
the PCI Firmware Specification r3.2. Enable this if you want to
support hybrid DPC model which uses both firmware and OS to
implement DPC.

Ver arquivo

@@ -13,3 +13,4 @@ obj-$(CONFIG_PCIE_PME) += pme.o
obj-$(CONFIG_PCIE_DPC) += dpc.o
obj-$(CONFIG_PCIE_PTM) += ptm.o
obj-$(CONFIG_PCIE_BW) += bw_notification.o
obj-$(CONFIG_PCIE_EDR) += edr.o

Ver arquivo

@@ -102,6 +102,7 @@ struct aer_stats {
#define ERR_UNCOR_ID(d) (d >> 16)
static int pcie_aer_disable;
static pci_ers_result_t aer_root_reset(struct pci_dev *dev);
void pci_no_aer(void)
{
@@ -376,7 +377,7 @@ void pci_aer_clear_device_status(struct pci_dev *dev)
pcie_capability_write_word(dev, PCI_EXP_DEVSTA, sta);
}
int pci_cleanup_aer_uncorrect_error_status(struct pci_dev *dev)
int pci_aer_clear_nonfatal_status(struct pci_dev *dev)
{
int pos;
u32 status, sev;
@@ -397,7 +398,7 @@ int pci_cleanup_aer_uncorrect_error_status(struct pci_dev *dev)
return 0;
}
EXPORT_SYMBOL_GPL(pci_cleanup_aer_uncorrect_error_status);
EXPORT_SYMBOL_GPL(pci_aer_clear_nonfatal_status);
void pci_aer_clear_fatal_status(struct pci_dev *dev)
{
@@ -419,7 +420,16 @@ void pci_aer_clear_fatal_status(struct pci_dev *dev)
pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, status);
}
int pci_cleanup_aer_error_status_regs(struct pci_dev *dev)
/**
* pci_aer_raw_clear_status - Clear AER error registers.
* @dev: the PCI device
*
* Clearing AER error status registers unconditionally, regardless of
* whether they're owned by firmware or the OS.
*
* Returns 0 on success, or negative on failure.
*/
int pci_aer_raw_clear_status(struct pci_dev *dev)
{
int pos;
u32 status;
@@ -432,9 +442,6 @@ int pci_cleanup_aer_error_status_regs(struct pci_dev *dev)
if (!pos)
return -EIO;
if (pcie_aer_get_firmware_first(dev))
return -EIO;
port_type = pci_pcie_type(dev);
if (port_type == PCI_EXP_TYPE_ROOT_PORT) {
pci_read_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, &status);
@@ -450,6 +457,14 @@ int pci_cleanup_aer_error_status_regs(struct pci_dev *dev)
return 0;
}
int pci_aer_clear_status(struct pci_dev *dev)
{
if (pcie_aer_get_firmware_first(dev))
return -EIO;
return pci_aer_raw_clear_status(dev);
}
void pci_save_aer_state(struct pci_dev *dev)
{
struct pci_cap_saved_state *save_state;
@@ -515,7 +530,7 @@ void pci_aer_init(struct pci_dev *dev)
n = pcie_cap_has_rtctl(dev) ? 5 : 4;
pci_add_ext_cap_save_buffer(dev, PCI_EXT_CAP_ID_ERR, sizeof(u32) * n);
pci_cleanup_aer_error_status_regs(dev);
pci_aer_clear_status(dev);
}
void pci_aer_exit(struct pci_dev *dev)
@@ -1053,11 +1068,9 @@ static void handle_error_source(struct pci_dev *dev, struct aer_err_info *info)
info->status);
pci_aer_clear_device_status(dev);
} else if (info->severity == AER_NONFATAL)
pcie_do_recovery(dev, pci_channel_io_normal,
PCIE_PORT_SERVICE_AER);
pcie_do_recovery(dev, pci_channel_io_normal, aer_root_reset);
else if (info->severity == AER_FATAL)
pcie_do_recovery(dev, pci_channel_io_frozen,
PCIE_PORT_SERVICE_AER);
pcie_do_recovery(dev, pci_channel_io_frozen, aer_root_reset);
pci_dev_put(dev);
}
@@ -1094,10 +1107,10 @@ static void aer_recover_work_func(struct work_struct *work)
cper_print_aer(pdev, entry.severity, entry.regs);
if (entry.severity == AER_NONFATAL)
pcie_do_recovery(pdev, pci_channel_io_normal,
PCIE_PORT_SERVICE_AER);
aer_root_reset);
else if (entry.severity == AER_FATAL)
pcie_do_recovery(pdev, pci_channel_io_frozen,
PCIE_PORT_SERVICE_AER);
aer_root_reset);
pci_dev_put(pdev);
}
}
@@ -1501,7 +1514,6 @@ static struct pcie_port_service_driver aerdriver = {
.probe = aer_probe,
.remove = aer_remove,
.reset_link = aer_root_reset,
};
/**

Ver arquivo

@@ -17,13 +17,6 @@
#include "portdrv.h"
#include "../pci.h"
struct dpc_dev {
struct pcie_device *dev;
u16 cap_pos;
bool rp_extensions;
u8 rp_log_size;
};
static const char * const rp_pio_error_string[] = {
"Configuration Request received UR Completion", /* Bit Position 0 */
"Configuration Request received CA Completion", /* Bit Position 1 */
@@ -46,63 +39,42 @@ static const char * const rp_pio_error_string[] = {
"Memory Request Completion Timeout", /* Bit Position 18 */
};
static struct dpc_dev *to_dpc_dev(struct pci_dev *dev)
{
struct device *device;
device = pcie_port_find_device(dev, PCIE_PORT_SERVICE_DPC);
if (!device)
return NULL;
return get_service_data(to_pcie_device(device));
}
void pci_save_dpc_state(struct pci_dev *dev)
{
struct dpc_dev *dpc;
struct pci_cap_saved_state *save_state;
u16 *cap;
if (!pci_is_pcie(dev))
return;
dpc = to_dpc_dev(dev);
if (!dpc)
return;
save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_DPC);
if (!save_state)
return;
cap = (u16 *)&save_state->cap.data[0];
pci_read_config_word(dev, dpc->cap_pos + PCI_EXP_DPC_CTL, cap);
pci_read_config_word(dev, dev->dpc_cap + PCI_EXP_DPC_CTL, cap);
}
void pci_restore_dpc_state(struct pci_dev *dev)
{
struct dpc_dev *dpc;
struct pci_cap_saved_state *save_state;
u16 *cap;
if (!pci_is_pcie(dev))
return;
dpc = to_dpc_dev(dev);
if (!dpc)
return;
save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_DPC);
if (!save_state)
return;
cap = (u16 *)&save_state->cap.data[0];
pci_write_config_word(dev, dpc->cap_pos + PCI_EXP_DPC_CTL, *cap);
pci_write_config_word(dev, dev->dpc_cap + PCI_EXP_DPC_CTL, *cap);
}
static int dpc_wait_rp_inactive(struct dpc_dev *dpc)
static int dpc_wait_rp_inactive(struct pci_dev *pdev)
{
unsigned long timeout = jiffies + HZ;
struct pci_dev *pdev = dpc->dev->port;
u16 cap = dpc->cap_pos, status;
u16 cap = pdev->dpc_cap, status;
pci_read_config_word(pdev, cap + PCI_EXP_DPC_STATUS, &status);
while (status & PCI_EXP_DPC_RP_BUSY &&
@@ -117,17 +89,15 @@ static int dpc_wait_rp_inactive(struct dpc_dev *dpc)
return 0;
}
static pci_ers_result_t dpc_reset_link(struct pci_dev *pdev)
pci_ers_result_t dpc_reset_link(struct pci_dev *pdev)
{
struct dpc_dev *dpc;
u16 cap;
/*
* DPC disables the Link automatically in hardware, so it has
* already been reset by the time we get here.
*/
dpc = to_dpc_dev(pdev);
cap = dpc->cap_pos;
cap = pdev->dpc_cap;
/*
* Wait until the Link is inactive, then clear DPC Trigger Status
@@ -135,7 +105,7 @@ static pci_ers_result_t dpc_reset_link(struct pci_dev *pdev)
*/
pcie_wait_for_link(pdev, false);
if (dpc->rp_extensions && dpc_wait_rp_inactive(dpc))
if (pdev->dpc_rp_extensions && dpc_wait_rp_inactive(pdev))
return PCI_ERS_RESULT_DISCONNECT;
pci_write_config_word(pdev, cap + PCI_EXP_DPC_STATUS,
@@ -147,10 +117,9 @@ static pci_ers_result_t dpc_reset_link(struct pci_dev *pdev)
return PCI_ERS_RESULT_RECOVERED;
}
static void dpc_process_rp_pio_error(struct dpc_dev *dpc)
static void dpc_process_rp_pio_error(struct pci_dev *pdev)
{
struct pci_dev *pdev = dpc->dev->port;
u16 cap = dpc->cap_pos, dpc_status, first_error;
u16 cap = pdev->dpc_cap, dpc_status, first_error;
u32 status, mask, sev, syserr, exc, dw0, dw1, dw2, dw3, log, prefix;
int i;
@@ -175,7 +144,7 @@ static void dpc_process_rp_pio_error(struct dpc_dev *dpc)
first_error == i ? " (First)" : "");
}
if (dpc->rp_log_size < 4)
if (pdev->dpc_rp_log_size < 4)
goto clear_status;
pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_HEADER_LOG,
&dw0);
@@ -188,12 +157,12 @@ static void dpc_process_rp_pio_error(struct dpc_dev *dpc)
pci_err(pdev, "TLP Header: %#010x %#010x %#010x %#010x\n",
dw0, dw1, dw2, dw3);
if (dpc->rp_log_size < 5)
if (pdev->dpc_rp_log_size < 5)
goto clear_status;
pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_IMPSPEC_LOG, &log);
pci_err(pdev, "RP PIO ImpSpec Log %#010x\n", log);
for (i = 0; i < dpc->rp_log_size - 5; i++) {
for (i = 0; i < pdev->dpc_rp_log_size - 5; i++) {
pci_read_config_dword(pdev,
cap + PCI_EXP_DPC_RP_PIO_TLPPREFIX_LOG, &prefix);
pci_err(pdev, "TLP Prefix Header: dw%d, %#010x\n", i, prefix);
@@ -224,12 +193,10 @@ static int dpc_get_aer_uncorrect_severity(struct pci_dev *dev,
return 1;
}
static irqreturn_t dpc_handler(int irq, void *context)
void dpc_process_error(struct pci_dev *pdev)
{
u16 cap = pdev->dpc_cap, status, source, reason, ext_reason;
struct aer_err_info info;
struct dpc_dev *dpc = context;
struct pci_dev *pdev = dpc->dev->port;
u16 cap = dpc->cap_pos, status, source, reason, ext_reason;
pci_read_config_word(pdev, cap + PCI_EXP_DPC_STATUS, &status);
pci_read_config_word(pdev, cap + PCI_EXP_DPC_SOURCE_ID, &source);
@@ -248,27 +215,33 @@ static irqreturn_t dpc_handler(int irq, void *context)
"reserved error");
/* show RP PIO error detail information */
if (dpc->rp_extensions && reason == 3 && ext_reason == 0)
dpc_process_rp_pio_error(dpc);
if (pdev->dpc_rp_extensions && reason == 3 && ext_reason == 0)
dpc_process_rp_pio_error(pdev);
else if (reason == 0 &&
dpc_get_aer_uncorrect_severity(pdev, &info) &&
aer_get_device_error_info(pdev, &info)) {
aer_print_error(pdev, &info);
pci_cleanup_aer_uncorrect_error_status(pdev);
pci_aer_clear_nonfatal_status(pdev);
pci_aer_clear_fatal_status(pdev);
}
}
static irqreturn_t dpc_handler(int irq, void *context)
{
struct pci_dev *pdev = context;
dpc_process_error(pdev);
/* We configure DPC so it only triggers on ERR_FATAL */
pcie_do_recovery(pdev, pci_channel_io_frozen, PCIE_PORT_SERVICE_DPC);
pcie_do_recovery(pdev, pci_channel_io_frozen, dpc_reset_link);
return IRQ_HANDLED;
}
static irqreturn_t dpc_irq(int irq, void *context)
{
struct dpc_dev *dpc = (struct dpc_dev *)context;
struct pci_dev *pdev = dpc->dev->port;
u16 cap = dpc->cap_pos, status;
struct pci_dev *pdev = context;
u16 cap = pdev->dpc_cap, status;
pci_read_config_word(pdev, cap + PCI_EXP_DPC_STATUS, &status);
@@ -282,10 +255,30 @@ static irqreturn_t dpc_irq(int irq, void *context)
return IRQ_HANDLED;
}
void pci_dpc_init(struct pci_dev *pdev)
{
u16 cap;
pdev->dpc_cap = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_DPC);
if (!pdev->dpc_cap)
return;
pci_read_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_CAP, &cap);
if (!(cap & PCI_EXP_DPC_CAP_RP_EXT))
return;
pdev->dpc_rp_extensions = true;
pdev->dpc_rp_log_size = (cap & PCI_EXP_DPC_RP_PIO_LOG_SIZE) >> 8;
if (pdev->dpc_rp_log_size < 4 || pdev->dpc_rp_log_size > 9) {
pci_err(pdev, "RP PIO log size %u is invalid\n",
pdev->dpc_rp_log_size);
pdev->dpc_rp_log_size = 0;
}
}
#define FLAG(x, y) (((x) & (y)) ? '+' : '-')
static int dpc_probe(struct pcie_device *dev)
{
struct dpc_dev *dpc;
struct pci_dev *pdev = dev->port;
struct device *device = &dev->device;
int status;
@@ -294,43 +287,25 @@ static int dpc_probe(struct pcie_device *dev)
if (pcie_aer_get_firmware_first(pdev) && !pcie_ports_dpc_native)
return -ENOTSUPP;
dpc = devm_kzalloc(device, sizeof(*dpc), GFP_KERNEL);
if (!dpc)
return -ENOMEM;
dpc->cap_pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_DPC);
dpc->dev = dev;
set_service_data(dev, dpc);
status = devm_request_threaded_irq(device, dev->irq, dpc_irq,
dpc_handler, IRQF_SHARED,
"pcie-dpc", dpc);
"pcie-dpc", pdev);
if (status) {
pci_warn(pdev, "request IRQ%d failed: %d\n", dev->irq,
status);
return status;
}
pci_read_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CAP, &cap);
pci_read_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CTL, &ctl);
dpc->rp_extensions = (cap & PCI_EXP_DPC_CAP_RP_EXT);
if (dpc->rp_extensions) {
dpc->rp_log_size = (cap & PCI_EXP_DPC_RP_PIO_LOG_SIZE) >> 8;
if (dpc->rp_log_size < 4 || dpc->rp_log_size > 9) {
pci_err(pdev, "RP PIO log size %u is invalid\n",
dpc->rp_log_size);
dpc->rp_log_size = 0;
}
}
pci_read_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_CAP, &cap);
pci_read_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_CTL, &ctl);
ctl = (ctl & 0xfff4) | PCI_EXP_DPC_CTL_EN_FATAL | PCI_EXP_DPC_CTL_INT_EN;
pci_write_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CTL, ctl);
pci_write_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_CTL, ctl);
pci_info(pdev, "error containment capabilities: Int Msg #%d, RPExt%c PoisonedTLP%c SwTrigger%c RP PIO Log %d, DL_ActiveErr%c\n",
cap & PCI_EXP_DPC_IRQ, FLAG(cap, PCI_EXP_DPC_CAP_RP_EXT),
FLAG(cap, PCI_EXP_DPC_CAP_POISONED_TLP),
FLAG(cap, PCI_EXP_DPC_CAP_SW_TRIGGER), dpc->rp_log_size,
FLAG(cap, PCI_EXP_DPC_CAP_SW_TRIGGER), pdev->dpc_rp_log_size,
FLAG(cap, PCI_EXP_DPC_CAP_DL_ACTIVE));
pci_add_ext_cap_save_buffer(pdev, PCI_EXT_CAP_ID_DPC, sizeof(u16));
@@ -339,13 +314,12 @@ static int dpc_probe(struct pcie_device *dev)
static void dpc_remove(struct pcie_device *dev)
{
struct dpc_dev *dpc = get_service_data(dev);
struct pci_dev *pdev = dev->port;
u16 ctl;
pci_read_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CTL, &ctl);
pci_read_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_CTL, &ctl);
ctl &= ~(PCI_EXP_DPC_CTL_EN_FATAL | PCI_EXP_DPC_CTL_INT_EN);
pci_write_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CTL, ctl);
pci_write_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_CTL, ctl);
}
static struct pcie_port_service_driver dpcdriver = {
@@ -354,7 +328,6 @@ static struct pcie_port_service_driver dpcdriver = {
.service = PCIE_PORT_SERVICE_DPC,
.probe = dpc_probe,
.remove = dpc_remove,
.reset_link = dpc_reset_link,
};
int __init pcie_dpc_init(void)

239
drivers/pci/pcie/edr.c Arquivo normal
Ver arquivo

@@ -0,0 +1,239 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCI Error Disconnect Recover support
* Author: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
*
* Copyright (C) 2020 Intel Corp.
*/
#define dev_fmt(fmt) "EDR: " fmt
#include <linux/pci.h>
#include <linux/pci-acpi.h>
#include "portdrv.h"
#include "../pci.h"
#define EDR_PORT_DPC_ENABLE_DSM 0x0C
#define EDR_PORT_LOCATE_DSM 0x0D
#define EDR_OST_SUCCESS 0x80
#define EDR_OST_FAILED 0x81
/*
* _DSM wrapper function to enable/disable DPC
* @pdev : PCI device structure
*
* returns 0 on success or errno on failure.
*/
static int acpi_enable_dpc(struct pci_dev *pdev)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
union acpi_object *obj, argv4, req;
int status = 0;
/*
* Behavior when calling unsupported _DSM functions is undefined,
* so check whether EDR_PORT_DPC_ENABLE_DSM is supported.
*/
if (!acpi_check_dsm(adev->handle, &pci_acpi_dsm_guid, 5,
1ULL << EDR_PORT_DPC_ENABLE_DSM))
return 0;
req.type = ACPI_TYPE_INTEGER;
req.integer.value = 1;
argv4.type = ACPI_TYPE_PACKAGE;
argv4.package.count = 1;
argv4.package.elements = &req;
/*
* Per Downstream Port Containment Related Enhancements ECN to PCI
* Firmware Specification r3.2, sec 4.6.12, EDR_PORT_DPC_ENABLE_DSM is
* optional. Return success if it's not implemented.
*/
obj = acpi_evaluate_dsm(adev->handle, &pci_acpi_dsm_guid, 5,
EDR_PORT_DPC_ENABLE_DSM, &argv4);
if (!obj)
return 0;
if (obj->type != ACPI_TYPE_INTEGER) {
pci_err(pdev, FW_BUG "Enable DPC _DSM returned non integer\n");
status = -EIO;
}
if (obj->integer.value != 1) {
pci_err(pdev, "Enable DPC _DSM failed to enable DPC\n");
status = -EIO;
}
ACPI_FREE(obj);
return status;
}
/*
* _DSM wrapper function to locate DPC port
* @pdev : Device which received EDR event
*
* Returns pci_dev or NULL. Caller is responsible for dropping a reference
* on the returned pci_dev with pci_dev_put().
*/
static struct pci_dev *acpi_dpc_port_get(struct pci_dev *pdev)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
union acpi_object *obj;
u16 port;
/*
* Behavior when calling unsupported _DSM functions is undefined,
* so check whether EDR_PORT_DPC_ENABLE_DSM is supported.
*/
if (!acpi_check_dsm(adev->handle, &pci_acpi_dsm_guid, 5,
1ULL << EDR_PORT_LOCATE_DSM))
return pci_dev_get(pdev);
obj = acpi_evaluate_dsm(adev->handle, &pci_acpi_dsm_guid, 5,
EDR_PORT_LOCATE_DSM, NULL);
if (!obj)
return pci_dev_get(pdev);
if (obj->type != ACPI_TYPE_INTEGER) {
ACPI_FREE(obj);
pci_err(pdev, FW_BUG "Locate Port _DSM returned non integer\n");
return NULL;
}
/*
* Firmware returns DPC port BDF details in following format:
* 15:8 = bus
* 7:3 = device
* 2:0 = function
*/
port = obj->integer.value;
ACPI_FREE(obj);
return pci_get_domain_bus_and_slot(pci_domain_nr(pdev->bus),
PCI_BUS_NUM(port), port & 0xff);
}
/*
* _OST wrapper function to let firmware know the status of EDR event
* @pdev : Device used to send _OST
* @edev : Device which experienced EDR event
* @status : Status of EDR event
*/
static int acpi_send_edr_status(struct pci_dev *pdev, struct pci_dev *edev,
u16 status)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
u32 ost_status;
pci_dbg(pdev, "Status for %s: %#x\n", pci_name(edev), status);
ost_status = PCI_DEVID(edev->bus->number, edev->devfn) << 16;
ost_status |= status;
status = acpi_evaluate_ost(adev->handle, ACPI_NOTIFY_DISCONNECT_RECOVER,
ost_status, NULL);
if (ACPI_FAILURE(status))
return -EINVAL;
return 0;
}
static void edr_handle_event(acpi_handle handle, u32 event, void *data)
{
struct pci_dev *pdev = data, *edev;
pci_ers_result_t estate = PCI_ERS_RESULT_DISCONNECT;
u16 status;
pci_info(pdev, "ACPI event %#x received\n", event);
if (event != ACPI_NOTIFY_DISCONNECT_RECOVER)
return;
/* Locate the port which issued EDR event */
edev = acpi_dpc_port_get(pdev);
if (!edev) {
pci_err(pdev, "Firmware failed to locate DPC port\n");
return;
}
pci_dbg(pdev, "Reported EDR dev: %s\n", pci_name(edev));
/* If port does not support DPC, just send the OST */
if (!edev->dpc_cap) {
pci_err(edev, FW_BUG "This device doesn't support DPC\n");
goto send_ost;
}
/* Check if there is a valid DPC trigger */
pci_read_config_word(edev, edev->dpc_cap + PCI_EXP_DPC_STATUS, &status);
if (!(status & PCI_EXP_DPC_STATUS_TRIGGER)) {
pci_err(edev, "Invalid DPC trigger %#010x\n", status);
goto send_ost;
}
dpc_process_error(edev);
pci_aer_raw_clear_status(edev);
/*
* Irrespective of whether the DPC event is triggered by ERR_FATAL
* or ERR_NONFATAL, since the link is already down, use the FATAL
* error recovery path for both cases.
*/
estate = pcie_do_recovery(edev, pci_channel_io_frozen, dpc_reset_link);
send_ost:
/*
* If recovery is successful, send _OST(0xF, BDF << 16 | 0x80)
* to firmware. If not successful, send _OST(0xF, BDF << 16 | 0x81).
*/
if (estate == PCI_ERS_RESULT_RECOVERED) {
pci_dbg(edev, "DPC port successfully recovered\n");
acpi_send_edr_status(pdev, edev, EDR_OST_SUCCESS);
} else {
pci_dbg(edev, "DPC port recovery failed\n");
acpi_send_edr_status(pdev, edev, EDR_OST_FAILED);
}
pci_dev_put(edev);
}
void pci_acpi_add_edr_notifier(struct pci_dev *pdev)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
acpi_status status;
if (!adev) {
pci_dbg(pdev, "No valid ACPI node, skipping EDR init\n");
return;
}
status = acpi_install_notify_handler(adev->handle, ACPI_SYSTEM_NOTIFY,
edr_handle_event, pdev);
if (ACPI_FAILURE(status)) {
pci_err(pdev, "Failed to install notify handler\n");
return;
}
if (acpi_enable_dpc(pdev))
acpi_remove_notify_handler(adev->handle, ACPI_SYSTEM_NOTIFY,
edr_handle_event);
else
pci_dbg(pdev, "Notify handler installed\n");
}
void pci_acpi_remove_edr_notifier(struct pci_dev *pdev)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
if (!adev)
return;
acpi_remove_notify_handler(adev->handle, ACPI_SYSTEM_NOTIFY,
edr_handle_event);
pci_dbg(pdev, "Notify handler removed\n");
}

Ver arquivo

@@ -146,49 +146,9 @@ out:
return 0;
}
/**
* default_reset_link - default reset function
* @dev: pointer to pci_dev data structure
*
* Invoked when performing link reset on a Downstream Port or a
* Root Port with no aer driver.
*/
static pci_ers_result_t default_reset_link(struct pci_dev *dev)
{
int rc;
rc = pci_bus_error_reset(dev);
pci_printk(KERN_DEBUG, dev, "downstream link has been reset\n");
return rc ? PCI_ERS_RESULT_DISCONNECT : PCI_ERS_RESULT_RECOVERED;
}
static pci_ers_result_t reset_link(struct pci_dev *dev, u32 service)
{
pci_ers_result_t status;
struct pcie_port_service_driver *driver = NULL;
driver = pcie_port_find_service(dev, service);
if (driver && driver->reset_link) {
status = driver->reset_link(dev);
} else if (pcie_downstream_port(dev)) {
status = default_reset_link(dev);
} else {
pci_printk(KERN_DEBUG, dev, "no link-reset support at upstream device %s\n",
pci_name(dev));
return PCI_ERS_RESULT_DISCONNECT;
}
if (status != PCI_ERS_RESULT_RECOVERED) {
pci_printk(KERN_DEBUG, dev, "link reset at upstream device %s failed\n",
pci_name(dev));
return PCI_ERS_RESULT_DISCONNECT;
}
return status;
}
void pcie_do_recovery(struct pci_dev *dev, enum pci_channel_state state,
u32 service)
pci_ers_result_t pcie_do_recovery(struct pci_dev *dev,
enum pci_channel_state state,
pci_ers_result_t (*reset_link)(struct pci_dev *pdev))
{
pci_ers_result_t status = PCI_ERS_RESULT_CAN_RECOVER;
struct pci_bus *bus;
@@ -203,14 +163,16 @@ void pcie_do_recovery(struct pci_dev *dev, enum pci_channel_state state,
bus = dev->subordinate;
pci_dbg(dev, "broadcast error_detected message\n");
if (state == pci_channel_io_frozen)
if (state == pci_channel_io_frozen) {
pci_walk_bus(bus, report_frozen_detected, &status);
else
status = reset_link(dev);
if (status != PCI_ERS_RESULT_RECOVERED) {
pci_warn(dev, "link reset failed\n");
goto failed;
}
} else {
pci_walk_bus(bus, report_normal_detected, &status);
if (state == pci_channel_io_frozen &&
reset_link(dev, service) != PCI_ERS_RESULT_RECOVERED)
goto failed;
}
if (status == PCI_ERS_RESULT_CAN_RECOVER) {
status = PCI_ERS_RESULT_RECOVERED;
@@ -236,13 +198,15 @@ void pcie_do_recovery(struct pci_dev *dev, enum pci_channel_state state,
pci_walk_bus(bus, report_resume, &status);
pci_aer_clear_device_status(dev);
pci_cleanup_aer_uncorrect_error_status(dev);
pci_aer_clear_nonfatal_status(dev);
pci_info(dev, "device recovery successful\n");
return;
return status;
failed:
pci_uevent_ers(dev, PCI_ERS_RESULT_DISCONNECT);
/* TODO: Should kernel panic here? */
pci_info(dev, "device recovery failed\n");
return status;
}

Ver arquivo

@@ -92,9 +92,6 @@ struct pcie_port_service_driver {
/* Device driver may resume normal operations */
void (*error_resume)(struct pci_dev *dev);
/* Link Reset Capability - AER service driver specific */
pci_ers_result_t (*reset_link)(struct pci_dev *dev);
int port_type; /* Type of the port this driver can handle */
u32 service; /* Port service this device represents */
@@ -161,7 +158,5 @@ static inline int pcie_aer_get_firmware_first(struct pci_dev *pci_dev)
}
#endif
struct pcie_port_service_driver *pcie_port_find_service(struct pci_dev *dev,
u32 service);
struct device *pcie_port_find_device(struct pci_dev *dev, u32 service);
#endif /* _PORTDRV_H_ */

Ver arquivo

@@ -458,27 +458,6 @@ static int find_service_iter(struct device *device, void *data)
return 0;
}
/**
* pcie_port_find_service - find the service driver
* @dev: PCI Express port the service is associated with
* @service: Service to find
*
* Find PCI Express port service driver associated with given service
*/
struct pcie_port_service_driver *pcie_port_find_service(struct pci_dev *dev,
u32 service)
{
struct pcie_port_service_driver *drv;
struct portdrv_service_data pdrvs;
pdrvs.drv = NULL;
pdrvs.service = service;
device_for_each_child(&dev->dev, &pdrvs, find_service_iter);
drv = pdrvs.drv;
return drv;
}
/**
* pcie_port_find_device - find the struct device
* @dev: PCI Express port the service is associated with

Ver arquivo

@@ -598,6 +598,7 @@ static void pci_init_host_bridge(struct pci_host_bridge *bridge)
bridge->native_shpc_hotplug = 1;
bridge->native_pme = 1;
bridge->native_ltr = 1;
bridge->native_dpc = 1;
}
struct pci_host_bridge *pci_alloc_host_bridge(size_t priv)
@@ -2329,6 +2330,7 @@ static void pci_init_capabilities(struct pci_dev *dev)
pci_enable_acs(dev); /* Enable ACS P2P upstream forwarding */
pci_ptm_init(dev); /* Precision Time Measurement */
pci_aer_init(dev); /* Advanced Error Reporting */
pci_dpc_init(dev); /* Downstream Port Containment */
pcie_report_downtraining(dev);