staticconststruct dmi_system_id inband_presence_disabled_dmi_table[] = { /* * Match all Dell systems, as some Dell systems have inband * presence disabled on NVMe slots (but don't support the bit to * report it). Setting inband presence disabled should have no * negative effect, except on broken hotplug slots that never * assert presence detect--and those will still work, they will * just have a bit of extra delay before being probed.
*/
{
.ident = "Dell System",
.matches = {
DMI_MATCH(DMI_OEM_STRING, "Dell System"),
},
},
{}
};
do {
pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); if (PCI_POSSIBLE_ERROR(slot_status)) {
ctrl_info(ctrl, "%s: no response from device\n",
__func__); return 0;
}
/* * If the controller does not generate notifications for command * completions, we never need to wait between writes.
*/ if (NO_CMD_CMPL(ctrl)) return;
if (!ctrl->cmd_busy) return;
/* * Even if the command has already timed out, we want to call * pcie_poll_cmd() so it can clear PCI_EXP_SLTSTA_CC.
*/
now = jiffies; if (time_before_eq(cmd_timeout, now))
timeout = 1; else
timeout = cmd_timeout - now;
/* * Controllers with the Intel CF118 and similar errata advertise * Command Completed support, but they only set Command Completed * if we change the "Control" bits for power, power indicator, * attention indicator, or interlock. If we only change the * "Enable" bits, they never set the Command Completed bit.
*/ if (pdev->broken_cmd_compl &&
(slot_ctrl_orig & CC_ERRATUM_MASK) == (slot_ctrl & CC_ERRATUM_MASK))
ctrl->cmd_busy = 0;
/* * Optionally wait for the hardware to be ready for a new command, * indicating completion of the above issued command.
*/ if (wait)
pcie_wait_cmd(ctrl);
out:
mutex_unlock(&ctrl->ctrl_lock);
}
/** * pcie_write_cmd - Issue controller command * @ctrl: controller to which the command is issued * @cmd: command value written to slot control register * @mask: bitmask of slot control register to be modified
*/ staticvoid pcie_write_cmd(struct controller *ctrl, u16 cmd, u16 mask)
{
pcie_do_write_cmd(ctrl, cmd, mask, true);
}
/* Same as above without waiting for the hardware to latch */ staticvoid pcie_write_cmd_nowait(struct controller *ctrl, u16 cmd, u16 mask)
{
pcie_do_write_cmd(ctrl, cmd, mask, false);
}
/** * pciehp_check_link_active() - Is the link active * @ctrl: PCIe hotplug controller * * Check whether the downstream link is currently active. Note it is * possible that the card is removed immediately after this so the * caller may need to take it into account. * * If the hotplug controller itself is not available anymore returns * %-ENODEV.
*/ int pciehp_check_link_active(struct controller *ctrl)
{ struct pci_dev *pdev = ctrl_dev(ctrl);
u16 lnk_status; int ret;
ret = pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status); if (ret == PCIBIOS_DEVICE_NOT_FOUND || PCI_POSSIBLE_ERROR(lnk_status)) return -ENODEV;
staticbool pci_bus_check_dev(struct pci_bus *bus, int devfn)
{
u32 l; int count = 0; int delay = 1000, step = 20; bool found = false;
do {
found = pci_bus_read_dev_vendor_id(bus, devfn, &l, 0);
count++;
if (found) break;
msleep(step);
delay -= step;
} while (delay > 0);
if (count > 1)
pr_debug("pci %04x:%02x:%02x.%d id reading try %d times with interval %d ms to get %08x\n",
pci_domain_nr(bus), bus->number, PCI_SLOT(devfn),
PCI_FUNC(devfn), count, step, l);
/** * pciehp_card_present() - Is the card present * @ctrl: PCIe hotplug controller * * Function checks whether the card is currently present in the slot and * in that case returns true. Note it is possible that the card is * removed immediately after the check so the caller may need to take * this into account. * * If the hotplug controller itself is not available anymore returns * %-ENODEV.
*/ int pciehp_card_present(struct controller *ctrl)
{ struct pci_dev *pdev = ctrl_dev(ctrl);
u16 slot_status; int ret;
ret = pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); if (ret == PCIBIOS_DEVICE_NOT_FOUND || PCI_POSSIBLE_ERROR(slot_status)) return -ENODEV;
return !!(slot_status & PCI_EXP_SLTSTA_PDS);
}
/** * pciehp_card_present_or_link_active() - whether given slot is occupied * @ctrl: PCIe hotplug controller * * Unlike pciehp_card_present(), which determines presence solely from the * Presence Detect State bit, this helper also returns true if the Link Active * bit is set. This is a concession to broken hotplug ports which hardwire * Presence Detect State to zero, such as Wilocity's [1ae9:0200]. * * Returns: %1 if the slot is occupied and %0 if it is not. If the hotplug * port is not present anymore returns %-ENODEV.
*/ int pciehp_card_present_or_link_active(struct controller *ctrl)
{ int ret;
ret = pciehp_card_present(ctrl); if (ret) return ret;
/* Attention and Power Indicator Control bits are supported */
pcie_write_cmd_nowait(ctrl, FIELD_PREP(PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC, status),
PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC);
pci_config_pm_runtime_put(pdev); return 0;
}
/** * pciehp_set_indicators() - set attention indicator, power indicator, or both * @ctrl: PCIe hotplug controller * @pwr: one of: * PCI_EXP_SLTCTL_PWR_IND_ON * PCI_EXP_SLTCTL_PWR_IND_BLINK * PCI_EXP_SLTCTL_PWR_IND_OFF * @attn: one of: * PCI_EXP_SLTCTL_ATTN_IND_ON * PCI_EXP_SLTCTL_ATTN_IND_BLINK * PCI_EXP_SLTCTL_ATTN_IND_OFF * * Either @pwr or @attn can also be INDICATOR_NOOP to leave that indicator * unchanged.
*/ void pciehp_set_indicators(struct controller *ctrl, int pwr, int attn)
{
u16 cmd = 0, mask = 0;
staticvoid pciehp_ignore_link_change(struct controller *ctrl, struct pci_dev *pdev, int irq,
u16 ignored_events)
{ /* * Ignore link changes which occurred while waiting for DPC recovery. * Could be several if DPC triggered multiple times consecutively. * Also ignore link changes caused by Secondary Bus Reset, etc.
*/
synchronize_hardirq(irq);
atomic_and(~ignored_events, &ctrl->pending_events); if (pciehp_poll_mode)
pcie_capability_write_word(pdev, PCI_EXP_SLTSTA,
ignored_events);
ctrl_info(ctrl, "Slot(%s): Link Down/Up ignored\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_nested(&ctrl->reset_lock, ctrl->depth); if (!pciehp_check_link_active(ctrl) || pciehp_device_replaced(ctrl))
pciehp_request(ctrl, ignored_events);
up_read(&ctrl->reset_lock);
}
/* * Interrupts only occur in D3hot or shallower and only if enabled * in the Slot Control register (PCIe r4.0, sec 6.7.3.4).
*/ if (pdev->current_state == PCI_D3cold ||
(!(ctrl->slot_ctrl & PCI_EXP_SLTCTL_HPIE) && !pciehp_poll_mode)) return IRQ_NONE;
/* * Keep the port accessible by holding a runtime PM ref on its parent. * Defer resume of the parent to the IRQ thread if it's suspended. * Mask the interrupt until then.
*/ if (parent) {
pm_runtime_get_noresume(parent); if (!pm_runtime_active(parent)) {
pm_runtime_put(parent);
disable_irq_nosync(irq);
atomic_or(RERUN_ISR, &ctrl->pending_events); return IRQ_WAKE_THREAD;
}
}
read_status:
pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &status); if (PCI_POSSIBLE_ERROR(status)) {
ctrl_info(ctrl, "%s: no response from device\n", __func__); if (parent)
pm_runtime_put(parent); return IRQ_NONE;
}
/* * Slot Status contains plain status bits as well as event * notification bits; right now we only want the event bits.
*/
status &= PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD |
PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_CC |
PCI_EXP_SLTSTA_DLLSC;
/* * If we've already reported a power fault, don't report it again * until we've done something to handle it.
*/ if (ctrl->power_fault_detected)
status &= ~PCI_EXP_SLTSTA_PFD; elseif (status & PCI_EXP_SLTSTA_PFD)
ctrl->power_fault_detected = true;
events |= status; if (!events) { if (parent)
pm_runtime_put(parent); return IRQ_NONE;
}
if (status) {
pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, status);
/* * In MSI mode, all event bits must be zero before the port * will send a new interrupt (PCIe Base Spec r5.0 sec 6.7.3.4). * So re-read the Slot Status register in case a bit was set * between read and write.
*/ if (pci_dev_msi_enabled(pdev) && !pciehp_poll_mode) goto read_status;
}
ctrl_dbg(ctrl, "pending interrupts %#06x from Slot Status\n", events); if (parent)
pm_runtime_put(parent);
/* * Command Completed notifications are not deferred to the * IRQ thread because it may be waiting for their arrival.
*/ if (events & PCI_EXP_SLTSTA_CC) {
ctrl->cmd_busy = 0;
smp_mb();
wake_up(&ctrl->queue);
if (events == PCI_EXP_SLTSTA_CC) return IRQ_HANDLED;
/* rerun pciehp_isr() if the port was inaccessible on interrupt */ if (atomic_fetch_and(~RERUN_ISR, &ctrl->pending_events) & RERUN_ISR) {
ret = pciehp_isr(irq, dev_id);
enable_irq(irq); if (ret != IRQ_WAKE_THREAD) goto out;
}
synchronize_hardirq(irq);
events = atomic_xchg(&ctrl->pending_events, 0); if (!events) {
ret = IRQ_NONE; goto out;
}
/* Check Power Fault Detected */ if (events & PCI_EXP_SLTSTA_PFD) {
ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(ctrl));
pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF,
PCI_EXP_SLTCTL_ATTN_IND_ON);
}
/* * Ignore Link Down/Up events caused by Downstream Port Containment * if recovery succeeded, or caused by Secondary Bus Reset, * suspend to D3cold, firmware update, FPGA reconfiguration, etc.
*/ if ((events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC)) &&
(pci_dpc_recovered(pdev) || pci_hp_spurious_link_change(pdev)) &&
ctrl->state == ON_STATE) {
u16 ignored_events = PCI_EXP_SLTSTA_DLLSC;
if (!ctrl->inband_presence_disabled)
ignored_events |= PCI_EXP_SLTSTA_PDC;
schedule_timeout_idle(10 * HZ); /* start with 10 sec delay */
while (!kthread_should_stop()) { /* poll for interrupt events or user requests */ while (pciehp_isr(IRQ_NOTCONNECTED, ctrl) == IRQ_WAKE_THREAD ||
atomic_read(&ctrl->pending_events))
pciehp_ist(IRQ_NOTCONNECTED, ctrl);
if (pciehp_poll_time <= 0 || pciehp_poll_time > 60)
pciehp_poll_time = 2; /* clamp to sane value */
/* * TBD: Power fault detected software notification support. * * Power fault detected software notification is not enabled * now, because it caused power fault detected interrupt storm * on some machines. On those machines, power fault detected * bit in the slot status register was set again immediately * when it is cleared in the interrupt service routine, and * next power fault detected interrupt was notified again.
*/
/* * Always enable link events: thus link-up and link-down shall * always be treated as hotplug and unplug respectively. Enable * presence detect only if Attention Button is not present.
*/
cmd = PCI_EXP_SLTCTL_DLLSCE; if (ATTN_BUTTN(ctrl))
cmd |= PCI_EXP_SLTCTL_ABPE; else
cmd |= PCI_EXP_SLTCTL_PDCE; if (!pciehp_poll_mode)
cmd |= PCI_EXP_SLTCTL_HPIE; if (!pciehp_poll_mode && !NO_CMD_CMPL(ctrl))
cmd |= PCI_EXP_SLTCTL_CCIE;
/* * Mask hot-plug interrupt to prevent it triggering immediately * when the link goes inactive (we still get PME when any of the * enabled events is detected). Same goes with Link Layer State * changed event which generates PME immediately when the link goes * inactive so mask it as well.
*/
mask = PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_DLLSCE;
pcie_write_cmd(ctrl, 0, mask);
}
/** * pciehp_slot_reset() - ignore link event caused by error-induced hot reset * @dev: PCI Express port service device * * Called from pcie_portdrv_slot_reset() after AER or DPC initiated a reset * further up in the hierarchy to recover from an error. The reset was * propagated down to this hotplug port. Ignore the resulting link flap. * If the link failed to retrain successfully, synthesize the ignored event. * Surprise removal during reset is detected through Presence Detect Changed.
*/ int pciehp_slot_reset(struct pcie_device *dev)
{ struct controller *ctrl = get_service_data(dev);
if (!pciehp_check_link_active(ctrl))
pciehp_request(ctrl, PCI_EXP_SLTSTA_DLLSC);
return 0;
}
/* * pciehp has a 1:1 bus:slot relationship so we ultimately want a secondary * bus reset of the bridge, but at the same time we want to ensure that it is * not seen as a hot-unplug, followed by the hot-plug of the device. Thus, * disable link state notification and presence detection change notification * momentarily, if we see that they could interfere. Also, clear any spurious * events after.
*/ 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); int rc;
if (pdev->hotplug_user_indicators)
slot_cap &= ~(PCI_EXP_SLTCAP_AIP | PCI_EXP_SLTCAP_PIP);
/* * We assume no Thunderbolt controllers support Command Complete events, * but some controllers falsely claim they do.
*/ if (pdev->is_thunderbolt)
slot_cap |= PCI_EXP_SLTCAP_NCCS;
/* * If empty slot's power status is on, turn power off. The IRQ isn't * requested yet, so avoid triggering a notification with this command.
*/ if (POWER_CTRL(ctrl)) {
pciehp_get_power_status(ctrl, &poweron); if (!pciehp_card_present_or_link_active(ctrl) && poweron) {
pcie_disable_notification(ctrl);
pciehp_power_off_slot(ctrl);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.