diff options
author | Bjorn Helgaas <bhelgaas@google.com> | 2016-10-03 16:43:19 +0200 |
---|---|---|
committer | Bjorn Helgaas <bhelgaas@google.com> | 2016-10-03 16:43:19 +0200 |
commit | 64ea3b99d665d0140cdf5047571e65d8ca28d0e6 (patch) | |
tree | 59bf503a1a14b88eae8ebe6793cd685eb187be6c | |
parent | Merge branch 'pci/enumeration' into next (diff) | |
parent | x86/PCI: VMD: Request userspace control of PCIe hotplug indicators (diff) | |
download | linux-64ea3b99d665d0140cdf5047571e65d8ca28d0e6.tar.xz linux-64ea3b99d665d0140cdf5047571e65d8ca28d0e6.zip |
Merge branch 'pci/hotplug' into next
* pci/hotplug:
x86/PCI: VMD: Request userspace control of PCIe hotplug indicators
PCI: pciehp: Allow exclusive userspace control of indicators
PCI: pciehp: Remove useless pciehp_get_latch_status() calls
PCI: pciehp: Clean up dmesg "Slot(%s)" messages
PCI: pciehp: Remove unnecessary guard
PCI: pciehp: Don't re-read Slot Status when handling surprise event
PCI: pciehp: Don't re-read Slot Status when queuing hotplug event
PCI: pciehp: Process all hotplug events before looking for new ones
PCI: pciehp: Return IRQ_NONE when we can't read interrupt status
PCI: pciehp: Rename pcie_isr() locals for clarity
PCI: pciehp: Clear attention LED on device add
-rw-r--r-- | arch/x86/include/asm/pci.h | 14 | ||||
-rw-r--r-- | arch/x86/pci/common.c | 7 | ||||
-rw-r--r-- | arch/x86/pci/vmd.c | 1 | ||||
-rw-r--r-- | drivers/pci/hotplug/pciehp.h | 3 | ||||
-rw-r--r-- | drivers/pci/hotplug/pciehp_core.c | 3 | ||||
-rw-r--r-- | drivers/pci/hotplug/pciehp_ctrl.c | 84 | ||||
-rw-r--r-- | drivers/pci/hotplug/pciehp_hpc.c | 121 | ||||
-rw-r--r-- | include/linux/pci.h | 3 |
8 files changed, 141 insertions, 95 deletions
diff --git a/arch/x86/include/asm/pci.h b/arch/x86/include/asm/pci.h index 9ab7507ca1c2..1411dbed5e5e 100644 --- a/arch/x86/include/asm/pci.h +++ b/arch/x86/include/asm/pci.h @@ -23,6 +23,9 @@ struct pci_sysdata { #ifdef CONFIG_PCI_MSI_IRQ_DOMAIN void *fwnode; /* IRQ domain for MSI assignment */ #endif +#if IS_ENABLED(CONFIG_VMD) + bool vmd_domain; /* True if in Intel VMD domain */ +#endif }; extern int pci_routeirq; @@ -56,6 +59,17 @@ static inline void *_pci_root_bus_fwnode(struct pci_bus *bus) #define pci_root_bus_fwnode _pci_root_bus_fwnode #endif +static inline bool is_vmd(struct pci_bus *bus) +{ +#if IS_ENABLED(CONFIG_VMD) + struct pci_sysdata *sd = bus->sysdata; + + return sd->vmd_domain; +#else + return false; +#endif +} + /* Can be used to override the logic in pci_scan_bus for skipping already-configured bus numbers - to be used for buggy BIOSes or architectures with incomplete PCI setup by the loader */ diff --git a/arch/x86/pci/common.c b/arch/x86/pci/common.c index 7b6a9d14c8c0..a4fdfa7dcc1b 100644 --- a/arch/x86/pci/common.c +++ b/arch/x86/pci/common.c @@ -677,6 +677,12 @@ static void set_dma_domain_ops(struct pci_dev *pdev) static void set_dma_domain_ops(struct pci_dev *pdev) {} #endif +static void set_dev_domain_options(struct pci_dev *pdev) +{ + if (is_vmd(pdev->bus)) + pdev->hotplug_user_indicators = 1; +} + int pcibios_add_device(struct pci_dev *dev) { struct setup_data *data; @@ -707,6 +713,7 @@ int pcibios_add_device(struct pci_dev *dev) iounmap(data); } set_dma_domain_ops(dev); + set_dev_domain_options(dev); return 0; } diff --git a/arch/x86/pci/vmd.c b/arch/x86/pci/vmd.c index b814ca675131..a021b7b0eb69 100644 --- a/arch/x86/pci/vmd.c +++ b/arch/x86/pci/vmd.c @@ -596,6 +596,7 @@ static int vmd_enable_domain(struct vmd_dev *vmd) .parent = res, }; + sd->vmd_domain = true; sd->domain = vmd_find_free_domain(); if (sd->domain < 0) return sd->domain; diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h index e764918641ae..37d70b5ad22f 100644 --- a/drivers/pci/hotplug/pciehp.h +++ b/drivers/pci/hotplug/pciehp.h @@ -152,6 +152,9 @@ bool pciehp_check_link_active(struct controller *ctrl); void pciehp_release_ctrl(struct controller *ctrl); int pciehp_reset_slot(struct slot *slot, int probe); +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); + static inline const char *slot_name(struct slot *slot) { return hotplug_slot_name(slot->hotplug_slot); diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c index fb0f86335158..7d32fa33dcef 100644 --- a/drivers/pci/hotplug/pciehp_core.c +++ b/drivers/pci/hotplug/pciehp_core.c @@ -113,6 +113,9 @@ static int init_slot(struct controller *ctrl) if (ATTN_LED(ctrl)) { ops->get_attention_status = get_attention_status; ops->set_attention_status = set_attention_status; + } else if (ctrl->pcie->port->hotplug_user_indicators) { + ops->get_attention_status = pciehp_get_raw_indicator_status; + ops->set_attention_status = pciehp_set_raw_indicator_status; } /* register this slot with the hotplug pci core */ diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c index 880978b6d534..efe69e879455 100644 --- a/drivers/pci/hotplug/pciehp_ctrl.c +++ b/drivers/pci/hotplug/pciehp_ctrl.c @@ -106,7 +106,7 @@ static int board_added(struct slot *p_slot) /* Check for a power fault */ if (ctrl->power_fault_detected || pciehp_query_power_fault(p_slot)) { - ctrl_err(ctrl, "Power fault on slot %s\n", slot_name(p_slot)); + ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(p_slot)); retval = -EIO; goto err_exit; } @@ -120,6 +120,7 @@ static int board_added(struct slot *p_slot) } pciehp_green_led_on(p_slot); + pciehp_set_attention_status(p_slot, 0); return 0; err_exit: @@ -253,11 +254,11 @@ static void handle_button_press_event(struct slot *p_slot) pciehp_get_power_status(p_slot, &getstatus); if (getstatus) { p_slot->state = BLINKINGOFF_STATE; - ctrl_info(ctrl, "PCI slot #%s - powering off due to button press\n", + ctrl_info(ctrl, "Slot(%s): Powering off due to button press\n", slot_name(p_slot)); } else { p_slot->state = BLINKINGON_STATE; - ctrl_info(ctrl, "PCI slot #%s - powering on due to button press\n", + ctrl_info(ctrl, "Slot(%s) Powering on due to button press\n", slot_name(p_slot)); } /* blink green LED and turn off amber */ @@ -272,14 +273,14 @@ static void handle_button_press_event(struct slot *p_slot) * press the attention again before the 5 sec. limit * expires to cancel hot-add or hot-remove */ - ctrl_info(ctrl, "Button cancel on Slot(%s)\n", slot_name(p_slot)); + ctrl_info(ctrl, "Slot(%s): Button cancel\n", slot_name(p_slot)); cancel_delayed_work(&p_slot->work); if (p_slot->state == BLINKINGOFF_STATE) pciehp_green_led_on(p_slot); else pciehp_green_led_off(p_slot); pciehp_set_attention_status(p_slot, 0); - ctrl_info(ctrl, "PCI slot #%s - action canceled due to button press\n", + ctrl_info(ctrl, "Slot(%s): Action canceled due to button press\n", slot_name(p_slot)); p_slot->state = STATIC_STATE; break; @@ -290,10 +291,12 @@ static void handle_button_press_event(struct slot *p_slot) * this means that the previous attention button action * to hot-add or hot-remove is undergoing */ - ctrl_info(ctrl, "Button ignore on Slot(%s)\n", slot_name(p_slot)); + ctrl_info(ctrl, "Slot(%s): Button ignored\n", + slot_name(p_slot)); break; default: - ctrl_warn(ctrl, "ignoring invalid state %#x\n", p_slot->state); + ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n", + slot_name(p_slot), p_slot->state); break; } } @@ -301,20 +304,6 @@ static void handle_button_press_event(struct slot *p_slot) /* * Note: This function must be called with slot->lock held */ -static void handle_surprise_event(struct slot *p_slot) -{ - u8 getstatus; - - pciehp_get_adapter_status(p_slot, &getstatus); - if (!getstatus) - pciehp_queue_power_work(p_slot, DISABLE_REQ); - else - pciehp_queue_power_work(p_slot, ENABLE_REQ); -} - -/* - * Note: This function must be called with slot->lock held - */ static void handle_link_event(struct slot *p_slot, u32 event) { struct controller *ctrl = p_slot->ctrl; @@ -330,31 +319,27 @@ static void handle_link_event(struct slot *p_slot, u32 event) break; case POWERON_STATE: if (event == INT_LINK_UP) { - ctrl_info(ctrl, - "Link Up event ignored on slot(%s): already powering on\n", + ctrl_info(ctrl, "Slot(%s): Link Up event ignored; already powering on\n", slot_name(p_slot)); } else { - ctrl_info(ctrl, - "Link Down event queued on slot(%s): currently getting powered on\n", + ctrl_info(ctrl, "Slot(%s): Link Down event queued; currently getting powered on\n", slot_name(p_slot)); pciehp_queue_power_work(p_slot, DISABLE_REQ); } break; case POWEROFF_STATE: if (event == INT_LINK_UP) { - ctrl_info(ctrl, - "Link Up event queued on slot(%s): currently getting powered off\n", + ctrl_info(ctrl, "Slot(%s): Link Up event queued; currently getting powered off\n", slot_name(p_slot)); pciehp_queue_power_work(p_slot, ENABLE_REQ); } else { - ctrl_info(ctrl, - "Link Down event ignored on slot(%s): already powering off\n", + ctrl_info(ctrl, "Slot(%s): Link Down event ignored; already powering off\n", slot_name(p_slot)); } break; default: - ctrl_err(ctrl, "ignoring invalid state %#x on slot(%s)\n", - p_slot->state, slot_name(p_slot)); + ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n", + slot_name(p_slot), p_slot->state); break; } } @@ -377,14 +362,14 @@ static void interrupt_event_handler(struct work_struct *work) pciehp_green_led_off(p_slot); break; case INT_PRESENCE_ON: - handle_surprise_event(p_slot); + pciehp_queue_power_work(p_slot, ENABLE_REQ); break; case INT_PRESENCE_OFF: /* * Regardless of surprise capability, we need to * definitely remove a card that has been pulled out! */ - handle_surprise_event(p_slot); + pciehp_queue_power_work(p_slot, DISABLE_REQ); break; case INT_LINK_UP: case INT_LINK_DOWN: @@ -404,18 +389,17 @@ static void interrupt_event_handler(struct work_struct *work) int pciehp_enable_slot(struct slot *p_slot) { u8 getstatus = 0; - int rc; struct controller *ctrl = p_slot->ctrl; pciehp_get_adapter_status(p_slot, &getstatus); if (!getstatus) { - ctrl_info(ctrl, "No adapter on slot(%s)\n", slot_name(p_slot)); + ctrl_info(ctrl, "Slot(%s): No adapter\n", slot_name(p_slot)); return -ENODEV; } if (MRL_SENS(p_slot->ctrl)) { pciehp_get_latch_status(p_slot, &getstatus); if (getstatus) { - ctrl_info(ctrl, "Latch open on slot(%s)\n", + ctrl_info(ctrl, "Slot(%s): Latch open\n", slot_name(p_slot)); return -ENODEV; } @@ -424,19 +408,13 @@ int pciehp_enable_slot(struct slot *p_slot) if (POWER_CTRL(p_slot->ctrl)) { pciehp_get_power_status(p_slot, &getstatus); if (getstatus) { - ctrl_info(ctrl, "Already enabled on slot(%s)\n", + ctrl_info(ctrl, "Slot(%s): Already enabled\n", slot_name(p_slot)); return -EINVAL; } } - pciehp_get_latch_status(p_slot, &getstatus); - - rc = board_added(p_slot); - if (rc) - pciehp_get_latch_status(p_slot, &getstatus); - - return rc; + return board_added(p_slot); } /* @@ -453,7 +431,7 @@ int pciehp_disable_slot(struct slot *p_slot) if (POWER_CTRL(p_slot->ctrl)) { pciehp_get_power_status(p_slot, &getstatus); if (!getstatus) { - ctrl_info(ctrl, "Already disabled on slot(%s)\n", + ctrl_info(ctrl, "Slot(%s): Already disabled\n", slot_name(p_slot)); return -EINVAL; } @@ -481,17 +459,17 @@ int pciehp_sysfs_enable_slot(struct slot *p_slot) p_slot->state = STATIC_STATE; break; case POWERON_STATE: - ctrl_info(ctrl, "Slot %s is already in powering on state\n", + ctrl_info(ctrl, "Slot(%s): Already in powering on state\n", slot_name(p_slot)); break; case BLINKINGOFF_STATE: case POWEROFF_STATE: - ctrl_info(ctrl, "Already enabled on slot %s\n", + ctrl_info(ctrl, "Slot(%s): Already enabled\n", slot_name(p_slot)); break; default: - ctrl_err(ctrl, "invalid state %#x on slot %s\n", - p_slot->state, slot_name(p_slot)); + ctrl_err(ctrl, "Slot(%s): Invalid state %#x\n", + slot_name(p_slot), p_slot->state); break; } mutex_unlock(&p_slot->lock); @@ -518,17 +496,17 @@ int pciehp_sysfs_disable_slot(struct slot *p_slot) p_slot->state = STATIC_STATE; break; case POWEROFF_STATE: - ctrl_info(ctrl, "Slot %s is already in powering off state\n", + ctrl_info(ctrl, "Slot(%s): Already in powering off state\n", slot_name(p_slot)); break; case BLINKINGON_STATE: case POWERON_STATE: - ctrl_info(ctrl, "Already disabled on slot %s\n", + ctrl_info(ctrl, "Slot(%s): Already disabled\n", slot_name(p_slot)); break; default: - ctrl_err(ctrl, "invalid state %#x on slot %s\n", - p_slot->state, slot_name(p_slot)); + ctrl_err(ctrl, "Slot(%s): Invalid state %#x\n", + slot_name(p_slot), p_slot->state); break; } mutex_unlock(&p_slot->lock); diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index 08e84d61874e..b57fc6d6e28a 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -355,6 +355,18 @@ static int pciehp_link_enable(struct controller *ctrl) return __pciehp_link_set(ctrl, true); } +int pciehp_get_raw_indicator_status(struct hotplug_slot *hotplug_slot, + u8 *status) +{ + struct slot *slot = hotplug_slot->private; + struct pci_dev *pdev = ctrl_dev(slot->ctrl); + u16 slot_ctrl; + + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + *status = (slot_ctrl & (PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC)) >> 6; + return 0; +} + void pciehp_get_attention_status(struct slot *slot, u8 *status) { struct controller *ctrl = slot->ctrl; @@ -431,6 +443,17 @@ int pciehp_query_power_fault(struct slot *slot) return !!(slot_status & PCI_EXP_SLTSTA_PFD); } +int pciehp_set_raw_indicator_status(struct hotplug_slot *hotplug_slot, + u8 status) +{ + struct slot *slot = hotplug_slot->private; + struct controller *ctrl = slot->ctrl; + + pcie_write_cmd_nowait(ctrl, status << 6, + PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC); + return 0; +} + void pciehp_set_attention_status(struct slot *slot, u8 value) { struct controller *ctrl = slot->ctrl; @@ -535,14 +558,14 @@ void pciehp_power_off_slot(struct slot *slot) PCI_EXP_SLTCTL_PWR_OFF); } -static irqreturn_t pcie_isr(int irq, void *dev_id) +static irqreturn_t pciehp_isr(int irq, void *dev_id) { struct controller *ctrl = (struct controller *)dev_id; struct pci_dev *pdev = ctrl_dev(ctrl); struct pci_bus *subordinate = pdev->subordinate; struct pci_dev *dev; struct slot *slot = ctrl->slot; - u16 detected, intr_loc; + u16 status, events; u8 present; bool link; @@ -550,36 +573,31 @@ static irqreturn_t pcie_isr(int irq, void *dev_id) if (pdev->current_state == PCI_D3cold) return IRQ_NONE; + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &status); + if (status == (u16) ~0) { + ctrl_info(ctrl, "%s: no response from device\n", __func__); + return IRQ_NONE; + } + /* - * In order to guarantee that all interrupt events are - * serviced, we need to re-inspect Slot Status register after - * clearing what is presumed to be the last pending interrupt. + * Slot Status contains plain status bits as well as event + * notification bits; right now we only want the event bits. */ - intr_loc = 0; - do { - pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &detected); - if (detected == (u16) ~0) { - ctrl_info(ctrl, "%s: no response from device\n", - __func__); - return IRQ_HANDLED; - } + events = status & (PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD | + PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_CC | + PCI_EXP_SLTSTA_DLLSC); + if (!events) + return IRQ_NONE; - detected &= (PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD | - PCI_EXP_SLTSTA_PDC | - PCI_EXP_SLTSTA_CC | PCI_EXP_SLTSTA_DLLSC); - detected &= ~intr_loc; - intr_loc |= detected; - if (!intr_loc) - return IRQ_NONE; - if (detected) - pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, - intr_loc); - } while (detected); + /* Capture link status before clearing interrupts */ + if (events & PCI_EXP_SLTSTA_DLLSC) + link = pciehp_check_link_active(ctrl); - ctrl_dbg(ctrl, "pending interrupts %#06x from Slot Status\n", intr_loc); + pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, events); + ctrl_dbg(ctrl, "pending interrupts %#06x from Slot Status\n", events); /* Check Command Complete Interrupt Pending */ - if (intr_loc & PCI_EXP_SLTSTA_CC) { + if (events & PCI_EXP_SLTSTA_CC) { ctrl->cmd_busy = 0; smp_mb(); wake_up(&ctrl->queue); @@ -589,42 +607,38 @@ static irqreturn_t pcie_isr(int irq, void *dev_id) list_for_each_entry(dev, &subordinate->devices, bus_list) { if (dev->ignore_hotplug) { ctrl_dbg(ctrl, "ignoring hotplug event %#06x (%s requested no hotplug)\n", - intr_loc, pci_name(dev)); + events, pci_name(dev)); return IRQ_HANDLED; } } } - if (!(intr_loc & ~PCI_EXP_SLTSTA_CC)) - return IRQ_HANDLED; - /* Check Attention Button Pressed */ - if (intr_loc & PCI_EXP_SLTSTA_ABP) { - ctrl_info(ctrl, "Button pressed on Slot(%s)\n", + if (events & PCI_EXP_SLTSTA_ABP) { + ctrl_info(ctrl, "Slot(%s): Attention button pressed\n", slot_name(slot)); pciehp_queue_interrupt_event(slot, INT_BUTTON_PRESS); } /* Check Presence Detect Changed */ - if (intr_loc & PCI_EXP_SLTSTA_PDC) { - pciehp_get_adapter_status(slot, &present); - ctrl_info(ctrl, "Card %spresent on Slot(%s)\n", - present ? "" : "not ", slot_name(slot)); + if (events & PCI_EXP_SLTSTA_PDC) { + present = !!(status & PCI_EXP_SLTSTA_PDS); + ctrl_info(ctrl, "Slot(%s): Card %spresent\n", slot_name(slot), + present ? "" : "not "); pciehp_queue_interrupt_event(slot, present ? INT_PRESENCE_ON : INT_PRESENCE_OFF); } /* Check Power Fault Detected */ - if ((intr_loc & PCI_EXP_SLTSTA_PFD) && !ctrl->power_fault_detected) { + if ((events & PCI_EXP_SLTSTA_PFD) && !ctrl->power_fault_detected) { ctrl->power_fault_detected = 1; - ctrl_err(ctrl, "Power fault on slot %s\n", slot_name(slot)); + ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(slot)); pciehp_queue_interrupt_event(slot, INT_POWER_FAULT); } - if (intr_loc & PCI_EXP_SLTSTA_DLLSC) { - link = pciehp_check_link_active(ctrl); - ctrl_info(ctrl, "slot(%s): Link %s event\n", - slot_name(slot), link ? "Up" : "Down"); + if (events & PCI_EXP_SLTSTA_DLLSC) { + ctrl_info(ctrl, "Slot(%s): Link %s\n", slot_name(slot), + link ? "Up" : "Down"); pciehp_queue_interrupt_event(slot, link ? INT_LINK_UP : INT_LINK_DOWN); } @@ -632,6 +646,25 @@ static irqreturn_t pcie_isr(int irq, void *dev_id) return IRQ_HANDLED; } +static irqreturn_t pcie_isr(int irq, void *dev_id) +{ + irqreturn_t rc, handled = IRQ_NONE; + + /* + * To guarantee that all interrupt events are serviced, we need to + * re-inspect Slot Status register after clearing what is presumed + * to be the last pending interrupt. + */ + do { + rc = pciehp_isr(irq, dev_id); + if (rc == IRQ_HANDLED) + handled = IRQ_HANDLED; + } while (rc == IRQ_HANDLED); + + /* Return IRQ_HANDLED if we handled one or more events */ + return handled; +} + void pcie_enable_notification(struct controller *ctrl) { u16 cmd, mask; @@ -804,6 +837,10 @@ struct controller *pcie_init(struct pcie_device *dev) } ctrl->pcie = dev; pcie_capability_read_dword(pdev, PCI_EXP_SLTCAP, &slot_cap); + + if (pdev->hotplug_user_indicators) + slot_cap &= ~(PCI_EXP_SLTCAP_AIP | PCI_EXP_SLTCAP_PIP); + ctrl->slot_cap = slot_cap; mutex_init(&ctrl->ctrl_lock); init_waitqueue_head(&ctrl->queue); diff --git a/include/linux/pci.h b/include/linux/pci.h index 84d222ad3c08..b0e4ed19b315 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -312,6 +312,9 @@ struct pci_dev { powered on/off by the corresponding bridge */ unsigned int ignore_hotplug:1; /* Ignore hotplug events */ + unsigned int hotplug_user_indicators:1; /* SlotCtl indicators + controlled exclusively by + user sysfs */ unsigned int d3_delay; /* D3->D0 transition time in ms */ unsigned int d3cold_delay; /* D3cold->D0 transition time in ms */ |