aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pci
diff options
context:
space:
mode:
authorRajat Jain <rajatxjain@gmail.com>2014-02-04 21:30:56 -0500
committerBjorn Helgaas <bhelgaas@google.com>2014-02-11 18:13:01 -0500
commitc4f2f5e4981073a5aa0739f93b6733060cd37648 (patch)
tree36ee3f06a3a82f73b1a862d86d5ae237e2679319 /drivers/pci
parent06a8d89af551b6151fa2289dd8660647ea9d2faa (diff)
PCI: pciehp: Ensure very fast hotplug events are also processed
Today, this is how all the hotplug and unplug events work: Hotplug / Removal needs to be done => Set slot->state (protected by slot->lock) to either POWERON_STATE (for enabling) or POWEROFF_STATE (for disabling). => Submit the work item for pciehp_power_thread() to slot->wq. Problem: There is a problem if the hotplug events can happen fast enough that they do not give SW enough time to add or remove the new devices. => Assume: Event for unplug comes (e.g. surprise removal). But before the pciehp_power_thread() work item was executed, the card was replaced by another card, causing surprise hotplug event. => What goes wrong: => The hot-removal event sets slot->state to POWEROFF_STATE, and schedules the pciehp_power_thread(). => The hot-add event sets slot->state to POWERON_STATE, and schedules the pciehp_power_thread(). => Now the pciehp_power_thread() is scheduled twice, and on both occasions it will find POWERON_STATE and will try to add the devices on the slot, and will fail complaining that the devices already exist. => Why this is a problem: If the device was replaced between the hot removal and hot-add, then we should unload the old driver and reload the new one. This does not happen today. The kernel or the driver is not even aware that the device was replaced. The problem is that the pciehp_power_thread() only looks at the slot->state which would only contain the *latest* state - not the actual event (add / remove) that was the intent of the IRQ handler who submitted the work. What this patch does: => Hotplug events pass on an actual request (for addition or removal) to pciehp_power_thread() which is local to that work item submission. => pciehp_power_thread() does not need to look at slote->state and hence no locks needed in that. => Essentially this results in all the hotplug and unplug events "replayed" by pciehp_power_thread(). 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/pci')
-rw-r--r--drivers/pci/hotplug/pciehp_ctrl.c29
1 files changed, 20 insertions, 9 deletions
diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c
index b418e3b09aa4..3e40ec0f7da1 100644
--- a/drivers/pci/hotplug/pciehp_ctrl.c
+++ b/drivers/pci/hotplug/pciehp_ctrl.c
@@ -276,6 +276,9 @@ static int remove_board(struct slot *p_slot)
276struct power_work_info { 276struct power_work_info {
277 struct slot *p_slot; 277 struct slot *p_slot;
278 struct work_struct work; 278 struct work_struct work;
279 unsigned int req;
280#define DISABLE_REQ 0
281#define ENABLE_REQ 1
279}; 282};
280 283
281/** 284/**
@@ -291,10 +294,8 @@ static void pciehp_power_thread(struct work_struct *work)
291 container_of(work, struct power_work_info, work); 294 container_of(work, struct power_work_info, work);
292 struct slot *p_slot = info->p_slot; 295 struct slot *p_slot = info->p_slot;
293 296
294 mutex_lock(&p_slot->lock); 297 switch (info->req) {
295 switch (p_slot->state) { 298 case DISABLE_REQ:
296 case POWEROFF_STATE:
297 mutex_unlock(&p_slot->lock);
298 ctrl_dbg(p_slot->ctrl, 299 ctrl_dbg(p_slot->ctrl,
299 "Disabling domain:bus:device=%04x:%02x:00\n", 300 "Disabling domain:bus:device=%04x:%02x:00\n",
300 pci_domain_nr(p_slot->ctrl->pcie->port->subordinate), 301 pci_domain_nr(p_slot->ctrl->pcie->port->subordinate),
@@ -302,18 +303,22 @@ static void pciehp_power_thread(struct work_struct *work)
302 pciehp_disable_slot(p_slot); 303 pciehp_disable_slot(p_slot);
303 mutex_lock(&p_slot->lock); 304 mutex_lock(&p_slot->lock);
304 p_slot->state = STATIC_STATE; 305 p_slot->state = STATIC_STATE;
305 break;
306 case POWERON_STATE:
307 mutex_unlock(&p_slot->lock); 306 mutex_unlock(&p_slot->lock);
307 break;
308 case ENABLE_REQ:
309 ctrl_dbg(p_slot->ctrl,
310 "Enabling domain:bus:device=%04x:%02x:00\n",
311 pci_domain_nr(p_slot->ctrl->pcie->port->subordinate),
312 p_slot->ctrl->pcie->port->subordinate->number);
308 if (pciehp_enable_slot(p_slot)) 313 if (pciehp_enable_slot(p_slot))
309 pciehp_green_led_off(p_slot); 314 pciehp_green_led_off(p_slot);
310 mutex_lock(&p_slot->lock); 315 mutex_lock(&p_slot->lock);
311 p_slot->state = STATIC_STATE; 316 p_slot->state = STATIC_STATE;
317 mutex_unlock(&p_slot->lock);
312 break; 318 break;
313 default: 319 default:
314 break; 320 break;
315 } 321 }
316 mutex_unlock(&p_slot->lock);
317 322
318 kfree(info); 323 kfree(info);
319} 324}
@@ -336,9 +341,11 @@ void pciehp_queue_pushbutton_work(struct work_struct *work)
336 switch (p_slot->state) { 341 switch (p_slot->state) {
337 case BLINKINGOFF_STATE: 342 case BLINKINGOFF_STATE:
338 p_slot->state = POWEROFF_STATE; 343 p_slot->state = POWEROFF_STATE;
344 info->req = DISABLE_REQ;
339 break; 345 break;
340 case BLINKINGON_STATE: 346 case BLINKINGON_STATE:
341 p_slot->state = POWERON_STATE; 347 p_slot->state = POWERON_STATE;
348 info->req = ENABLE_REQ;
342 break; 349 break;
343 default: 350 default:
344 kfree(info); 351 kfree(info);
@@ -428,10 +435,13 @@ static void handle_surprise_event(struct slot *p_slot)
428 INIT_WORK(&info->work, pciehp_power_thread); 435 INIT_WORK(&info->work, pciehp_power_thread);
429 436
430 pciehp_get_adapter_status(p_slot, &getstatus); 437 pciehp_get_adapter_status(p_slot, &getstatus);
431 if (!getstatus) 438 if (!getstatus) {
432 p_slot->state = POWEROFF_STATE; 439 p_slot->state = POWEROFF_STATE;
433 else 440 info->req = DISABLE_REQ;
441 } else {
434 p_slot->state = POWERON_STATE; 442 p_slot->state = POWERON_STATE;
443 info->req = ENABLE_REQ;
444 }
435 445
436 queue_work(p_slot->wq, &info->work); 446 queue_work(p_slot->wq, &info->work);
437} 447}
@@ -451,6 +461,7 @@ static void handle_link_event(struct slot *p_slot, u32 event)
451 return; 461 return;
452 } 462 }
453 info->p_slot = p_slot; 463 info->p_slot = p_slot;
464 info->req = event == INT_LINK_UP ? ENABLE_REQ : DISABLE_REQ;
454 INIT_WORK(&info->work, pciehp_power_thread); 465 INIT_WORK(&info->work, pciehp_power_thread);
455 466
456 switch (p_slot->state) { 467 switch (p_slot->state) {