Merge branch 'pci/hotplug'
- Ignore pciehp Link Down/Up caused by DPC so device remains bound to driver (Lukas Wunner) - Declare global cpci_debug in header file (Krzysztof Wilczyński) * pci/hotplug: PCI: cpcihp: Declare cpci_debug in header file PCI: pciehp: Ignore Link Down/Up caused by DPC
This commit is contained in:
commit
56d2731cb2
|
@ -75,6 +75,9 @@ int cpci_hp_unregister_bus(struct pci_bus *bus);
|
|||
int cpci_hp_start(void);
|
||||
int cpci_hp_stop(void);
|
||||
|
||||
/* Global variables */
|
||||
extern int cpci_debug;
|
||||
|
||||
/*
|
||||
* Internal function prototypes, these functions should not be used by
|
||||
* board/chassis drivers.
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
|
||||
#define MY_NAME "cpci_hotplug"
|
||||
|
||||
extern int cpci_debug;
|
||||
|
||||
#define dbg(format, arg...) \
|
||||
do { \
|
||||
if (cpci_debug) \
|
||||
|
|
|
@ -563,6 +563,32 @@ void pciehp_power_off_slot(struct controller *ctrl)
|
|||
PCI_EXP_SLTCTL_PWR_OFF);
|
||||
}
|
||||
|
||||
static void pciehp_ignore_dpc_link_change(struct controller *ctrl,
|
||||
struct pci_dev *pdev, int irq)
|
||||
{
|
||||
/*
|
||||
* Ignore link changes which occurred while waiting for DPC recovery.
|
||||
* Could be several if DPC triggered multiple times consecutively.
|
||||
*/
|
||||
synchronize_hardirq(irq);
|
||||
atomic_and(~PCI_EXP_SLTSTA_DLLSC, &ctrl->pending_events);
|
||||
if (pciehp_poll_mode)
|
||||
pcie_capability_write_word(pdev, PCI_EXP_SLTSTA,
|
||||
PCI_EXP_SLTSTA_DLLSC);
|
||||
ctrl_info(ctrl, "Slot(%s): Link Down/Up ignored (recovered by DPC)\n",
|
||||
slot_name(ctrl));
|
||||
|
||||
/*
|
||||
* If the link is unexpectedly down after successful recovery,
|
||||
* the corresponding link change may have been ignored above.
|
||||
* Synthesize it to ensure that it is acted on.
|
||||
*/
|
||||
down_read(&ctrl->reset_lock);
|
||||
if (!pciehp_check_link_active(ctrl))
|
||||
pciehp_request(ctrl, PCI_EXP_SLTSTA_DLLSC);
|
||||
up_read(&ctrl->reset_lock);
|
||||
}
|
||||
|
||||
static irqreturn_t pciehp_isr(int irq, void *dev_id)
|
||||
{
|
||||
struct controller *ctrl = (struct controller *)dev_id;
|
||||
|
@ -706,6 +732,16 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
|
|||
PCI_EXP_SLTCTL_ATTN_IND_ON);
|
||||
}
|
||||
|
||||
/*
|
||||
* Ignore Link Down/Up events caused by Downstream Port Containment
|
||||
* if recovery from the error succeeded.
|
||||
*/
|
||||
if ((events & PCI_EXP_SLTSTA_DLLSC) && pci_dpc_recovered(pdev) &&
|
||||
ctrl->state == ON_STATE) {
|
||||
events &= ~PCI_EXP_SLTSTA_DLLSC;
|
||||
pciehp_ignore_dpc_link_change(ctrl, pdev, irq);
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable requests have higher priority than Presence Detect Changed
|
||||
* or Data Link Layer State Changed events.
|
||||
|
|
|
@ -385,6 +385,8 @@ static inline bool pci_dev_is_disconnected(const struct pci_dev *dev)
|
|||
|
||||
/* pci_dev priv_flags */
|
||||
#define PCI_DEV_ADDED 0
|
||||
#define PCI_DPC_RECOVERED 1
|
||||
#define PCI_DPC_RECOVERING 2
|
||||
|
||||
static inline void pci_dev_assign_added(struct pci_dev *dev, bool added)
|
||||
{
|
||||
|
@ -439,10 +441,12 @@ 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);
|
||||
bool pci_dpc_recovered(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) {}
|
||||
static inline bool pci_dpc_recovered(struct pci_dev *pdev) { return false; }
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PCIEPORTBUS
|
||||
|
|
|
@ -71,6 +71,58 @@ void pci_restore_dpc_state(struct pci_dev *dev)
|
|||
pci_write_config_word(dev, dev->dpc_cap + PCI_EXP_DPC_CTL, *cap);
|
||||
}
|
||||
|
||||
static DECLARE_WAIT_QUEUE_HEAD(dpc_completed_waitqueue);
|
||||
|
||||
#ifdef CONFIG_HOTPLUG_PCI_PCIE
|
||||
static bool dpc_completed(struct pci_dev *pdev)
|
||||
{
|
||||
u16 status;
|
||||
|
||||
pci_read_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_STATUS, &status);
|
||||
if ((status != 0xffff) && (status & PCI_EXP_DPC_STATUS_TRIGGER))
|
||||
return false;
|
||||
|
||||
if (test_bit(PCI_DPC_RECOVERING, &pdev->priv_flags))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_dpc_recovered - whether DPC triggered and has recovered successfully
|
||||
* @pdev: PCI device
|
||||
*
|
||||
* Return true if DPC was triggered for @pdev and has recovered successfully.
|
||||
* Wait for recovery if it hasn't completed yet. Called from the PCIe hotplug
|
||||
* driver to recognize and ignore Link Down/Up events caused by DPC.
|
||||
*/
|
||||
bool pci_dpc_recovered(struct pci_dev *pdev)
|
||||
{
|
||||
struct pci_host_bridge *host;
|
||||
|
||||
if (!pdev->dpc_cap)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Synchronization between hotplug and DPC is not supported
|
||||
* if DPC is owned by firmware and EDR is not enabled.
|
||||
*/
|
||||
host = pci_find_host_bridge(pdev->bus);
|
||||
if (!host->native_dpc && !IS_ENABLED(CONFIG_PCIE_EDR))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Need a timeout in case DPC never completes due to failure of
|
||||
* dpc_wait_rp_inactive(). The spec doesn't mandate a time limit,
|
||||
* but reports indicate that DPC completes within 4 seconds.
|
||||
*/
|
||||
wait_event_timeout(dpc_completed_waitqueue, dpc_completed(pdev),
|
||||
msecs_to_jiffies(4000));
|
||||
|
||||
return test_and_clear_bit(PCI_DPC_RECOVERED, &pdev->priv_flags);
|
||||
}
|
||||
#endif /* CONFIG_HOTPLUG_PCI_PCIE */
|
||||
|
||||
static int dpc_wait_rp_inactive(struct pci_dev *pdev)
|
||||
{
|
||||
unsigned long timeout = jiffies + HZ;
|
||||
|
@ -91,8 +143,11 @@ static int dpc_wait_rp_inactive(struct pci_dev *pdev)
|
|||
|
||||
pci_ers_result_t dpc_reset_link(struct pci_dev *pdev)
|
||||
{
|
||||
pci_ers_result_t ret;
|
||||
u16 cap;
|
||||
|
||||
set_bit(PCI_DPC_RECOVERING, &pdev->priv_flags);
|
||||
|
||||
/*
|
||||
* DPC disables the Link automatically in hardware, so it has
|
||||
* already been reset by the time we get here.
|
||||
|
@ -106,18 +161,27 @@ pci_ers_result_t dpc_reset_link(struct pci_dev *pdev)
|
|||
if (!pcie_wait_for_link(pdev, false))
|
||||
pci_info(pdev, "Data Link Layer Link Active not cleared in 1000 msec\n");
|
||||
|
||||
if (pdev->dpc_rp_extensions && dpc_wait_rp_inactive(pdev))
|
||||
return PCI_ERS_RESULT_DISCONNECT;
|
||||
if (pdev->dpc_rp_extensions && dpc_wait_rp_inactive(pdev)) {
|
||||
clear_bit(PCI_DPC_RECOVERED, &pdev->priv_flags);
|
||||
ret = PCI_ERS_RESULT_DISCONNECT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
pci_write_config_word(pdev, cap + PCI_EXP_DPC_STATUS,
|
||||
PCI_EXP_DPC_STATUS_TRIGGER);
|
||||
|
||||
if (!pcie_wait_for_link(pdev, true)) {
|
||||
pci_info(pdev, "Data Link Layer Link Active not set in 1000 msec\n");
|
||||
return PCI_ERS_RESULT_DISCONNECT;
|
||||
clear_bit(PCI_DPC_RECOVERED, &pdev->priv_flags);
|
||||
ret = PCI_ERS_RESULT_DISCONNECT;
|
||||
} else {
|
||||
set_bit(PCI_DPC_RECOVERED, &pdev->priv_flags);
|
||||
ret = PCI_ERS_RESULT_RECOVERED;
|
||||
}
|
||||
|
||||
return PCI_ERS_RESULT_RECOVERED;
|
||||
out:
|
||||
clear_bit(PCI_DPC_RECOVERING, &pdev->priv_flags);
|
||||
wake_up_all(&dpc_completed_waitqueue);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void dpc_process_rp_pio_error(struct pci_dev *pdev)
|
||||
|
|
Loading…
Reference in New Issue