aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorRajat Jain <rajatxjain@gmail.com>2014-02-04 21:29:10 -0500
committerBjorn Helgaas <bhelgaas@google.com>2014-02-10 20:12:44 -0500
commite48f1b67f668762003e8888eccd7acb71109e874 (patch)
treed8b98a5fd5c5a2ba1340558a59467f31499a971c /drivers
parent4703389f7df70518cc4ea584f3e64cb11f28aa7c (diff)
PCI: pciehp: Use link change notifications for hot-plug and removal
A lot of systems do not have the fancy buttons and LEDs, and instead want to rely only on the Link state change events to drive the hotplug and removal state machinery. (http://www.spinics.net/lists/hotplug/msg05802.html) This patch adds support for that functionality. Here are the details about the patch itself: * Define and use interrupt events for linkup / linkdown. * Make the pcie_isr() also look at link events, and direct control to corresponding (new) link state change handler function. * Introduce the functions to handle link-up and link-down events and queue the add / removal work in the slot->wq to be processed by pciehp_power_thread() As a side note, this patch also fixes the bug https://bugzilla.kernel.org/show_bug.cgi?id=65521 "pciehp ignores Data Link Layer State Changed bit." Signed-off-by: Rajat Jain <rajatxjain@gmail.com> Signed-off-by: Rajat Jain <rajatjain@juniper.net> Signed-off-by: Guenter Roeck <groeck@juniper.net> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/pci/hotplug/pciehp.h3
-rw-r--r--drivers/pci/hotplug/pciehp_ctrl.c88
-rw-r--r--drivers/pci/hotplug/pciehp_hpc.c6
3 files changed, 96 insertions, 1 deletions
diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h
index f9524592da0f..d8d033619a77 100644
--- a/drivers/pci/hotplug/pciehp.h
+++ b/drivers/pci/hotplug/pciehp.h
@@ -109,6 +109,8 @@ struct controller {
109#define INT_BUTTON_PRESS 7 109#define INT_BUTTON_PRESS 7
110#define INT_BUTTON_RELEASE 8 110#define INT_BUTTON_RELEASE 8
111#define INT_BUTTON_CANCEL 9 111#define INT_BUTTON_CANCEL 9
112#define INT_LINK_UP 10
113#define INT_LINK_DOWN 11
112 114
113#define STATIC_STATE 0 115#define STATIC_STATE 0
114#define BLINKINGON_STATE 1 116#define BLINKINGON_STATE 1
@@ -132,6 +134,7 @@ u8 pciehp_handle_attention_button(struct slot *p_slot);
132u8 pciehp_handle_switch_change(struct slot *p_slot); 134u8 pciehp_handle_switch_change(struct slot *p_slot);
133u8 pciehp_handle_presence_change(struct slot *p_slot); 135u8 pciehp_handle_presence_change(struct slot *p_slot);
134u8 pciehp_handle_power_fault(struct slot *p_slot); 136u8 pciehp_handle_power_fault(struct slot *p_slot);
137void pciehp_handle_linkstate_change(struct slot *p_slot);
135int pciehp_configure_device(struct slot *p_slot); 138int pciehp_configure_device(struct slot *p_slot);
136int pciehp_unconfigure_device(struct slot *p_slot); 139int pciehp_unconfigure_device(struct slot *p_slot);
137void pciehp_queue_pushbutton_work(struct work_struct *work); 140void pciehp_queue_pushbutton_work(struct work_struct *work);
diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c
index 50628487597d..56082842b265 100644
--- a/drivers/pci/hotplug/pciehp_ctrl.c
+++ b/drivers/pci/hotplug/pciehp_ctrl.c
@@ -150,6 +150,27 @@ u8 pciehp_handle_power_fault(struct slot *p_slot)
150 return 1; 150 return 1;
151} 151}
152 152
153void pciehp_handle_linkstate_change(struct slot *p_slot)
154{
155 u32 event_type;
156 struct controller *ctrl = p_slot->ctrl;
157
158 /* Link Status Change */
159 ctrl_dbg(ctrl, "Data Link Layer State change\n");
160
161 if (pciehp_check_link_active(ctrl)) {
162 ctrl_info(ctrl, "slot(%s): Link Up event\n",
163 slot_name(p_slot));
164 event_type = INT_LINK_UP;
165 } else {
166 ctrl_info(ctrl, "slot(%s): Link Down event\n",
167 slot_name(p_slot));
168 event_type = INT_LINK_DOWN;
169 }
170
171 queue_interrupt_event(p_slot, event_type);
172}
173
153/* The following routines constitute the bulk of the 174/* The following routines constitute the bulk of the
154 hotplug controller logic 175 hotplug controller logic
155 */ 176 */
@@ -415,6 +436,69 @@ static void handle_surprise_event(struct slot *p_slot)
415 queue_work(p_slot->wq, &info->work); 436 queue_work(p_slot->wq, &info->work);
416} 437}
417 438
439/*
440 * Note: This function must be called with slot->lock held
441 */
442static void handle_link_event(struct slot *p_slot, u32 event)
443{
444 struct controller *ctrl = p_slot->ctrl;
445 struct power_work_info *info;
446
447 info = kmalloc(sizeof(*info), GFP_KERNEL);
448 if (!info) {
449 ctrl_err(p_slot->ctrl, "%s: Cannot allocate memory\n",
450 __func__);
451 return;
452 }
453 info->p_slot = p_slot;
454 INIT_WORK(&info->work, pciehp_power_thread);
455
456 switch (p_slot->state) {
457 case BLINKINGON_STATE:
458 case BLINKINGOFF_STATE:
459 cancel_delayed_work(&p_slot->work);
460 /* Fall through */
461 case STATIC_STATE:
462 p_slot->state = event == INT_LINK_UP ?
463 POWERON_STATE : POWEROFF_STATE;
464 queue_work(p_slot->wq, &info->work);
465 break;
466 case POWERON_STATE:
467 if (event == INT_LINK_UP) {
468 ctrl_info(ctrl,
469 "Link Up event ignored on slot(%s): already powering on\n",
470 slot_name(p_slot));
471 kfree(info);
472 } else {
473 ctrl_info(ctrl,
474 "Link Down event queued on slot(%s): currently getting powered on\n",
475 slot_name(p_slot));
476 p_slot->state = POWEROFF_STATE;
477 queue_work(p_slot->wq, &info->work);
478 }
479 break;
480 case POWEROFF_STATE:
481 if (event == INT_LINK_UP) {
482 ctrl_info(ctrl,
483 "Link Up event queued on slot(%s): currently getting powered off\n",
484 slot_name(p_slot));
485 p_slot->state = POWERON_STATE;
486 queue_work(p_slot->wq, &info->work);
487 } else {
488 ctrl_info(ctrl,
489 "Link Down event ignored on slot(%s): already powering off\n",
490 slot_name(p_slot));
491 kfree(info);
492 }
493 break;
494 default:
495 ctrl_err(ctrl, "Not a valid state on slot(%s)\n",
496 slot_name(p_slot));
497 kfree(info);
498 break;
499 }
500}
501
418static void interrupt_event_handler(struct work_struct *work) 502static void interrupt_event_handler(struct work_struct *work)
419{ 503{
420 struct event_info *info = container_of(work, struct event_info, work); 504 struct event_info *info = container_of(work, struct event_info, work);
@@ -439,6 +523,10 @@ static void interrupt_event_handler(struct work_struct *work)
439 ctrl_dbg(ctrl, "Surprise Removal\n"); 523 ctrl_dbg(ctrl, "Surprise Removal\n");
440 handle_surprise_event(p_slot); 524 handle_surprise_event(p_slot);
441 break; 525 break;
526 case INT_LINK_UP:
527 case INT_LINK_DOWN:
528 handle_link_event(p_slot, info->event_type);
529 break;
442 default: 530 default:
443 break; 531 break;
444 } 532 }
diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c
index aed6ab43cc13..b413dce8f194 100644
--- a/drivers/pci/hotplug/pciehp_hpc.c
+++ b/drivers/pci/hotplug/pciehp_hpc.c
@@ -540,7 +540,7 @@ static irqreturn_t pcie_isr(int irq, void *dev_id)
540 540
541 detected &= (PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD | 541 detected &= (PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD |
542 PCI_EXP_SLTSTA_MRLSC | PCI_EXP_SLTSTA_PDC | 542 PCI_EXP_SLTSTA_MRLSC | PCI_EXP_SLTSTA_PDC |
543 PCI_EXP_SLTSTA_CC); 543 PCI_EXP_SLTSTA_CC | PCI_EXP_SLTSTA_DLLSC);
544 detected &= ~intr_loc; 544 detected &= ~intr_loc;
545 intr_loc |= detected; 545 intr_loc |= detected;
546 if (!intr_loc) 546 if (!intr_loc)
@@ -579,6 +579,10 @@ static irqreturn_t pcie_isr(int irq, void *dev_id)
579 ctrl->power_fault_detected = 1; 579 ctrl->power_fault_detected = 1;
580 pciehp_handle_power_fault(slot); 580 pciehp_handle_power_fault(slot);
581 } 581 }
582
583 if (intr_loc & PCI_EXP_SLTSTA_DLLSC)
584 pciehp_handle_linkstate_change(slot);
585
582 return IRQ_HANDLED; 586 return IRQ_HANDLED;
583} 587}
584 588