aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/scsi/libsas
diff options
context:
space:
mode:
authorDan Williams <dan.j.williams@intel.com>2012-06-22 02:41:51 -0400
committerJames Bottomley <JBottomley@Parallels.com>2012-08-24 05:10:23 -0400
commit303694eeee5eacad5b84105a15afd9e351e1891b (patch)
treeba8e16274ffbde39b21100ced62b5b11e8c926d6 /drivers/scsi/libsas
parent2fcbdcb4c802fe40d6827dbc365dac90cfe8c0a3 (diff)
[SCSI] libsas: suspend / resume support
libsas power management routines to suspend and recover the sas domain based on a model where the lldd is allowed and expected to be "forgetful". sas_suspend_ha - disable event processing allowing the lldd to take down links without concern for causing hotplug events. Regardless of whether the lldd actually posts link down messages libsas notifies the lldd that all domain_devices are gone. sas_prep_resume_ha - on the way back up before the lldd starts link training clean out any spurious events that were generated on the way down, and re-enable event processing sas_resume_ha - after the lldd has started and decided that all phys have posted link-up events this routine is called to let libsas start it's own timeout of any phys that did not resume. After the timeout an lldd can cancel the phy teardown by posting a link-up event. Storage for ex_change_count (u16) and phy_change_count (u8) are changed to int so they can be set to -1 to indicate 'invalidated'. Signed-off-by: Dan Williams <dan.j.williams@intel.com> Reviewed-by: Jacek Danecki <jacek.danecki@intel.com> Tested-by: Maciej Patelczyk <maciej.patelczyk@intel.com> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: James Bottomley <JBottomley@Parallels.com>
Diffstat (limited to 'drivers/scsi/libsas')
-rw-r--r--drivers/scsi/libsas/sas_ata.c86
-rw-r--r--drivers/scsi/libsas/sas_discover.c69
-rw-r--r--drivers/scsi/libsas/sas_dump.c1
-rw-r--r--drivers/scsi/libsas/sas_event.c4
-rw-r--r--drivers/scsi/libsas/sas_init.c90
-rw-r--r--drivers/scsi/libsas/sas_internal.h1
-rw-r--r--drivers/scsi/libsas/sas_phy.c21
-rw-r--r--drivers/scsi/libsas/sas_port.c52
8 files changed, 310 insertions, 14 deletions
diff --git a/drivers/scsi/libsas/sas_ata.c b/drivers/scsi/libsas/sas_ata.c
index a59fcdc8fd6..905ae45133f 100644
--- a/drivers/scsi/libsas/sas_ata.c
+++ b/drivers/scsi/libsas/sas_ata.c
@@ -700,6 +700,92 @@ void sas_probe_sata(struct asd_sas_port *port)
700 if (ata_dev_disabled(sas_to_ata_dev(dev))) 700 if (ata_dev_disabled(sas_to_ata_dev(dev)))
701 sas_fail_probe(dev, __func__, -ENODEV); 701 sas_fail_probe(dev, __func__, -ENODEV);
702 } 702 }
703
704}
705
706static bool sas_ata_flush_pm_eh(struct asd_sas_port *port, const char *func)
707{
708 struct domain_device *dev, *n;
709 bool retry = false;
710
711 list_for_each_entry_safe(dev, n, &port->dev_list, dev_list_node) {
712 int rc;
713
714 if (!dev_is_sata(dev))
715 continue;
716
717 sas_ata_wait_eh(dev);
718 rc = dev->sata_dev.pm_result;
719 if (rc == -EAGAIN)
720 retry = true;
721 else if (rc) {
722 /* since we don't have a
723 * ->port_{suspend|resume} routine in our
724 * ata_port ops, and no entanglements with
725 * acpi, suspend should just be mechanical trip
726 * through eh, catch cases where these
727 * assumptions are invalidated
728 */
729 WARN_ONCE(1, "failed %s %s error: %d\n", func,
730 dev_name(&dev->rphy->dev), rc);
731 }
732
733 /* if libata failed to power manage the device, tear it down */
734 if (ata_dev_disabled(sas_to_ata_dev(dev)))
735 sas_fail_probe(dev, func, -ENODEV);
736 }
737
738 return retry;
739}
740
741void sas_suspend_sata(struct asd_sas_port *port)
742{
743 struct domain_device *dev;
744
745 retry:
746 mutex_lock(&port->ha->disco_mutex);
747 list_for_each_entry(dev, &port->dev_list, dev_list_node) {
748 struct sata_device *sata;
749
750 if (!dev_is_sata(dev))
751 continue;
752
753 sata = &dev->sata_dev;
754 if (sata->ap->pm_mesg.event == PM_EVENT_SUSPEND)
755 continue;
756
757 sata->pm_result = -EIO;
758 ata_sas_port_async_suspend(sata->ap, &sata->pm_result);
759 }
760 mutex_unlock(&port->ha->disco_mutex);
761
762 if (sas_ata_flush_pm_eh(port, __func__))
763 goto retry;
764}
765
766void sas_resume_sata(struct asd_sas_port *port)
767{
768 struct domain_device *dev;
769
770 retry:
771 mutex_lock(&port->ha->disco_mutex);
772 list_for_each_entry(dev, &port->dev_list, dev_list_node) {
773 struct sata_device *sata;
774
775 if (!dev_is_sata(dev))
776 continue;
777
778 sata = &dev->sata_dev;
779 if (sata->ap->pm_mesg.event == PM_EVENT_ON)
780 continue;
781
782 sata->pm_result = -EIO;
783 ata_sas_port_async_resume(sata->ap, &sata->pm_result);
784 }
785 mutex_unlock(&port->ha->disco_mutex);
786
787 if (sas_ata_flush_pm_eh(port, __func__))
788 goto retry;
703} 789}
704 790
705/** 791/**
diff --git a/drivers/scsi/libsas/sas_discover.c b/drivers/scsi/libsas/sas_discover.c
index 3e9dc1a8435..a0c3003e0c7 100644
--- a/drivers/scsi/libsas/sas_discover.c
+++ b/drivers/scsi/libsas/sas_discover.c
@@ -24,6 +24,7 @@
24 24
25#include <linux/scatterlist.h> 25#include <linux/scatterlist.h>
26#include <linux/slab.h> 26#include <linux/slab.h>
27#include <linux/async.h>
27#include <scsi/scsi_host.h> 28#include <scsi/scsi_host.h>
28#include <scsi/scsi_eh.h> 29#include <scsi/scsi_eh.h>
29#include "sas_internal.h" 30#include "sas_internal.h"
@@ -180,16 +181,18 @@ int sas_notify_lldd_dev_found(struct domain_device *dev)
180 struct Scsi_Host *shost = sas_ha->core.shost; 181 struct Scsi_Host *shost = sas_ha->core.shost;
181 struct sas_internal *i = to_sas_internal(shost->transportt); 182 struct sas_internal *i = to_sas_internal(shost->transportt);
182 183
183 if (i->dft->lldd_dev_found) { 184 if (!i->dft->lldd_dev_found)
184 res = i->dft->lldd_dev_found(dev); 185 return 0;
185 if (res) { 186
186 printk("sas: driver on pcidev %s cannot handle " 187 res = i->dft->lldd_dev_found(dev);
187 "device %llx, error:%d\n", 188 if (res) {
188 dev_name(sas_ha->dev), 189 printk("sas: driver on pcidev %s cannot handle "
189 SAS_ADDR(dev->sas_addr), res); 190 "device %llx, error:%d\n",
190 } 191 dev_name(sas_ha->dev),
191 kref_get(&dev->kref); 192 SAS_ADDR(dev->sas_addr), res);
192 } 193 }
194 set_bit(SAS_DEV_FOUND, &dev->state);
195 kref_get(&dev->kref);
193 return res; 196 return res;
194} 197}
195 198
@@ -200,7 +203,10 @@ void sas_notify_lldd_dev_gone(struct domain_device *dev)
200 struct Scsi_Host *shost = sas_ha->core.shost; 203 struct Scsi_Host *shost = sas_ha->core.shost;
201 struct sas_internal *i = to_sas_internal(shost->transportt); 204 struct sas_internal *i = to_sas_internal(shost->transportt);
202 205
203 if (i->dft->lldd_dev_gone) { 206 if (!i->dft->lldd_dev_gone)
207 return;
208
209 if (test_and_clear_bit(SAS_DEV_FOUND, &dev->state)) {
204 i->dft->lldd_dev_gone(dev); 210 i->dft->lldd_dev_gone(dev);
205 sas_put_device(dev); 211 sas_put_device(dev);
206 } 212 }
@@ -234,6 +240,47 @@ static void sas_probe_devices(struct work_struct *work)
234 } 240 }
235} 241}
236 242
243static void sas_suspend_devices(struct work_struct *work)
244{
245 struct asd_sas_phy *phy;
246 struct domain_device *dev;
247 struct sas_discovery_event *ev = to_sas_discovery_event(work);
248 struct asd_sas_port *port = ev->port;
249 struct Scsi_Host *shost = port->ha->core.shost;
250 struct sas_internal *si = to_sas_internal(shost->transportt);
251
252 clear_bit(DISCE_SUSPEND, &port->disc.pending);
253
254 sas_suspend_sata(port);
255
256 /* lldd is free to forget the domain_device across the
257 * suspension, we force the issue here to keep the reference
258 * counts aligned
259 */
260 list_for_each_entry(dev, &port->dev_list, dev_list_node)
261 sas_notify_lldd_dev_gone(dev);
262
263 /* we are suspending, so we know events are disabled and
264 * phy_list is not being mutated
265 */
266 list_for_each_entry(phy, &port->phy_list, port_phy_el) {
267 if (si->dft->lldd_port_formed)
268 si->dft->lldd_port_deformed(phy);
269 phy->suspended = 1;
270 port->suspended = 1;
271 }
272}
273
274static void sas_resume_devices(struct work_struct *work)
275{
276 struct sas_discovery_event *ev = to_sas_discovery_event(work);
277 struct asd_sas_port *port = ev->port;
278
279 clear_bit(DISCE_RESUME, &port->disc.pending);
280
281 sas_resume_sata(port);
282}
283
237/** 284/**
238 * sas_discover_end_dev -- discover an end device (SSP, etc) 285 * sas_discover_end_dev -- discover an end device (SSP, etc)
239 * @end: pointer to domain device of interest 286 * @end: pointer to domain device of interest
@@ -530,6 +577,8 @@ void sas_init_disc(struct sas_discovery *disc, struct asd_sas_port *port)
530 [DISCE_DISCOVER_DOMAIN] = sas_discover_domain, 577 [DISCE_DISCOVER_DOMAIN] = sas_discover_domain,
531 [DISCE_REVALIDATE_DOMAIN] = sas_revalidate_domain, 578 [DISCE_REVALIDATE_DOMAIN] = sas_revalidate_domain,
532 [DISCE_PROBE] = sas_probe_devices, 579 [DISCE_PROBE] = sas_probe_devices,
580 [DISCE_SUSPEND] = sas_suspend_devices,
581 [DISCE_RESUME] = sas_resume_devices,
533 [DISCE_DESTRUCT] = sas_destruct_devices, 582 [DISCE_DESTRUCT] = sas_destruct_devices,
534 }; 583 };
535 584
diff --git a/drivers/scsi/libsas/sas_dump.c b/drivers/scsi/libsas/sas_dump.c
index fc460933575..cd6f99c1ae7 100644
--- a/drivers/scsi/libsas/sas_dump.c
+++ b/drivers/scsi/libsas/sas_dump.c
@@ -41,6 +41,7 @@ static const char *sas_phye_str[] = {
41 [1] = "PHYE_OOB_DONE", 41 [1] = "PHYE_OOB_DONE",
42 [2] = "PHYE_OOB_ERROR", 42 [2] = "PHYE_OOB_ERROR",
43 [3] = "PHYE_SPINUP_HOLD", 43 [3] = "PHYE_SPINUP_HOLD",
44 [4] = "PHYE_RESUME_TIMEOUT",
44}; 45};
45 46
46void sas_dprint_porte(int phyid, enum port_event pe) 47void sas_dprint_porte(int phyid, enum port_event pe)
diff --git a/drivers/scsi/libsas/sas_event.c b/drivers/scsi/libsas/sas_event.c
index 789c4d8bb7a..aadbd5314c5 100644
--- a/drivers/scsi/libsas/sas_event.c
+++ b/drivers/scsi/libsas/sas_event.c
@@ -134,7 +134,7 @@ static void notify_port_event(struct asd_sas_phy *phy, enum port_event event)
134 &phy->port_events[event].work, ha); 134 &phy->port_events[event].work, ha);
135} 135}
136 136
137static void notify_phy_event(struct asd_sas_phy *phy, enum phy_event event) 137void sas_notify_phy_event(struct asd_sas_phy *phy, enum phy_event event)
138{ 138{
139 struct sas_ha_struct *ha = phy->ha; 139 struct sas_ha_struct *ha = phy->ha;
140 140
@@ -159,7 +159,7 @@ int sas_init_events(struct sas_ha_struct *sas_ha)
159 159
160 sas_ha->notify_ha_event = notify_ha_event; 160 sas_ha->notify_ha_event = notify_ha_event;
161 sas_ha->notify_port_event = notify_port_event; 161 sas_ha->notify_port_event = notify_port_event;
162 sas_ha->notify_phy_event = notify_phy_event; 162 sas_ha->notify_phy_event = sas_notify_phy_event;
163 163
164 return 0; 164 return 0;
165} 165}
diff --git a/drivers/scsi/libsas/sas_init.c b/drivers/scsi/libsas/sas_init.c
index 014297c0588..dbc8a793fd8 100644
--- a/drivers/scsi/libsas/sas_init.c
+++ b/drivers/scsi/libsas/sas_init.c
@@ -178,7 +178,7 @@ Undo_phys:
178 return error; 178 return error;
179} 179}
180 180
181int sas_unregister_ha(struct sas_ha_struct *sas_ha) 181static void sas_disable_events(struct sas_ha_struct *sas_ha)
182{ 182{
183 /* Set the state to unregistered to avoid further unchained 183 /* Set the state to unregistered to avoid further unchained
184 * events to be queued, and flush any in-progress drainers 184 * events to be queued, and flush any in-progress drainers
@@ -189,7 +189,11 @@ int sas_unregister_ha(struct sas_ha_struct *sas_ha)
189 spin_unlock_irq(&sas_ha->lock); 189 spin_unlock_irq(&sas_ha->lock);
190 __sas_drain_work(sas_ha); 190 __sas_drain_work(sas_ha);
191 mutex_unlock(&sas_ha->drain_mutex); 191 mutex_unlock(&sas_ha->drain_mutex);
192}
192 193
194int sas_unregister_ha(struct sas_ha_struct *sas_ha)
195{
196 sas_disable_events(sas_ha);
193 sas_unregister_ports(sas_ha); 197 sas_unregister_ports(sas_ha);
194 198
195 /* flush unregistration work */ 199 /* flush unregistration work */
@@ -381,6 +385,90 @@ int sas_set_phy_speed(struct sas_phy *phy,
381 return ret; 385 return ret;
382} 386}
383 387
388void sas_prep_resume_ha(struct sas_ha_struct *ha)
389{
390 int i;
391
392 set_bit(SAS_HA_REGISTERED, &ha->state);
393
394 /* clear out any stale link events/data from the suspension path */
395 for (i = 0; i < ha->num_phys; i++) {
396 struct asd_sas_phy *phy = ha->sas_phy[i];
397
398 memset(phy->attached_sas_addr, 0, SAS_ADDR_SIZE);
399 phy->port_events_pending = 0;
400 phy->phy_events_pending = 0;
401 phy->frame_rcvd_size = 0;
402 }
403}
404EXPORT_SYMBOL(sas_prep_resume_ha);
405
406static int phys_suspended(struct sas_ha_struct *ha)
407{
408 int i, rc = 0;
409
410 for (i = 0; i < ha->num_phys; i++) {
411 struct asd_sas_phy *phy = ha->sas_phy[i];
412
413 if (phy->suspended)
414 rc++;
415 }
416
417 return rc;
418}
419
420void sas_resume_ha(struct sas_ha_struct *ha)
421{
422 const unsigned long tmo = msecs_to_jiffies(25000);
423 int i;
424
425 /* deform ports on phys that did not resume
426 * at this point we may be racing the phy coming back (as posted
427 * by the lldd). So we post the event and once we are in the
428 * libsas context check that the phy remains suspended before
429 * tearing it down.
430 */
431 i = phys_suspended(ha);
432 if (i)
433 dev_info(ha->dev, "waiting up to 25 seconds for %d phy%s to resume\n",
434 i, i > 1 ? "s" : "");
435 wait_event_timeout(ha->eh_wait_q, phys_suspended(ha) == 0, tmo);
436 for (i = 0; i < ha->num_phys; i++) {
437 struct asd_sas_phy *phy = ha->sas_phy[i];
438
439 if (phy->suspended) {
440 dev_warn(&phy->phy->dev, "resume timeout\n");
441 sas_notify_phy_event(phy, PHYE_RESUME_TIMEOUT);
442 }
443 }
444
445 /* all phys are back up or timed out, turn on i/o so we can
446 * flush out disks that did not return
447 */
448 scsi_unblock_requests(ha->core.shost);
449 sas_drain_work(ha);
450}
451EXPORT_SYMBOL(sas_resume_ha);
452
453void sas_suspend_ha(struct sas_ha_struct *ha)
454{
455 int i;
456
457 sas_disable_events(ha);
458 scsi_block_requests(ha->core.shost);
459 for (i = 0; i < ha->num_phys; i++) {
460 struct asd_sas_port *port = ha->sas_port[i];
461
462 sas_discover_event(port, DISCE_SUSPEND);
463 }
464
465 /* flush suspend events while unregistered */
466 mutex_lock(&ha->drain_mutex);
467 __sas_drain_work(ha);
468 mutex_unlock(&ha->drain_mutex);
469}
470EXPORT_SYMBOL(sas_suspend_ha);
471
384static void sas_phy_release(struct sas_phy *phy) 472static void sas_phy_release(struct sas_phy *phy)
385{ 473{
386 kfree(phy->hostdata); 474 kfree(phy->hostdata);
diff --git a/drivers/scsi/libsas/sas_internal.h b/drivers/scsi/libsas/sas_internal.h
index 507e4cf12e5..1de67964e5a 100644
--- a/drivers/scsi/libsas/sas_internal.h
+++ b/drivers/scsi/libsas/sas_internal.h
@@ -89,6 +89,7 @@ int sas_smp_phy_control(struct domain_device *dev, int phy_id,
89 enum phy_func phy_func, struct sas_phy_linkrates *); 89 enum phy_func phy_func, struct sas_phy_linkrates *);
90int sas_smp_get_phy_events(struct sas_phy *phy); 90int sas_smp_get_phy_events(struct sas_phy *phy);
91 91
92void sas_notify_phy_event(struct asd_sas_phy *phy, enum phy_event event);
92void sas_device_set_phy(struct domain_device *dev, struct sas_port *port); 93void sas_device_set_phy(struct domain_device *dev, struct sas_port *port);
93struct domain_device *sas_find_dev_by_rphy(struct sas_rphy *rphy); 94struct domain_device *sas_find_dev_by_rphy(struct sas_rphy *rphy);
94struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id); 95struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id);
diff --git a/drivers/scsi/libsas/sas_phy.c b/drivers/scsi/libsas/sas_phy.c
index 521422e857a..cdee446c29e 100644
--- a/drivers/scsi/libsas/sas_phy.c
+++ b/drivers/scsi/libsas/sas_phy.c
@@ -94,6 +94,25 @@ static void sas_phye_spinup_hold(struct work_struct *work)
94 i->dft->lldd_control_phy(phy, PHY_FUNC_RELEASE_SPINUP_HOLD, NULL); 94 i->dft->lldd_control_phy(phy, PHY_FUNC_RELEASE_SPINUP_HOLD, NULL);
95} 95}
96 96
97static void sas_phye_resume_timeout(struct work_struct *work)
98{
99 struct asd_sas_event *ev = to_asd_sas_event(work);
100 struct asd_sas_phy *phy = ev->phy;
101
102 clear_bit(PHYE_RESUME_TIMEOUT, &phy->phy_events_pending);
103
104 /* phew, lldd got the phy back in the nick of time */
105 if (!phy->suspended) {
106 dev_info(&phy->phy->dev, "resume timeout cancelled\n");
107 return;
108 }
109
110 phy->error = 0;
111 phy->suspended = 0;
112 sas_deform_port(phy, 1);
113}
114
115
97/* ---------- Phy class registration ---------- */ 116/* ---------- Phy class registration ---------- */
98 117
99int sas_register_phys(struct sas_ha_struct *sas_ha) 118int sas_register_phys(struct sas_ha_struct *sas_ha)
@@ -105,6 +124,8 @@ int sas_register_phys(struct sas_ha_struct *sas_ha)
105 [PHYE_OOB_DONE] = sas_phye_oob_done, 124 [PHYE_OOB_DONE] = sas_phye_oob_done,
106 [PHYE_OOB_ERROR] = sas_phye_oob_error, 125 [PHYE_OOB_ERROR] = sas_phye_oob_error,
107 [PHYE_SPINUP_HOLD] = sas_phye_spinup_hold, 126 [PHYE_SPINUP_HOLD] = sas_phye_spinup_hold,
127 [PHYE_RESUME_TIMEOUT] = sas_phye_resume_timeout,
128
108 }; 129 };
109 130
110 static const work_func_t sas_port_event_fns[PORT_NUM_EVENTS] = { 131 static const work_func_t sas_port_event_fns[PORT_NUM_EVENTS] = {
diff --git a/drivers/scsi/libsas/sas_port.c b/drivers/scsi/libsas/sas_port.c
index e884a8c58a0..1398b714c01 100644
--- a/drivers/scsi/libsas/sas_port.c
+++ b/drivers/scsi/libsas/sas_port.c
@@ -39,6 +39,49 @@ static bool phy_is_wideport_member(struct asd_sas_port *port, struct asd_sas_phy
39 return true; 39 return true;
40} 40}
41 41
42static void sas_resume_port(struct asd_sas_phy *phy)
43{
44 struct domain_device *dev;
45 struct asd_sas_port *port = phy->port;
46 struct sas_ha_struct *sas_ha = phy->ha;
47 struct sas_internal *si = to_sas_internal(sas_ha->core.shost->transportt);
48
49 if (si->dft->lldd_port_formed)
50 si->dft->lldd_port_formed(phy);
51
52 if (port->suspended)
53 port->suspended = 0;
54 else {
55 /* we only need to handle "link returned" actions once */
56 return;
57 }
58
59 /* if the port came back:
60 * 1/ presume every device came back
61 * 2/ force the next revalidation to check all expander phys
62 */
63 list_for_each_entry(dev, &port->dev_list, dev_list_node) {
64 int i, rc;
65
66 rc = sas_notify_lldd_dev_found(dev);
67 if (rc) {
68 sas_unregister_dev(port, dev);
69 continue;
70 }
71
72 if (dev->dev_type == EDGE_DEV || dev->dev_type == FANOUT_DEV) {
73 dev->ex_dev.ex_change_count = -1;
74 for (i = 0; i < dev->ex_dev.num_phys; i++) {
75 struct ex_phy *phy = &dev->ex_dev.ex_phy[i];
76
77 phy->phy_change_count = -1;
78 }
79 }
80 }
81
82 sas_discover_event(port, DISCE_RESUME);
83}
84
42/** 85/**
43 * sas_form_port -- add this phy to a port 86 * sas_form_port -- add this phy to a port
44 * @phy: the phy of interest 87 * @phy: the phy of interest
@@ -58,7 +101,14 @@ static void sas_form_port(struct asd_sas_phy *phy)
58 if (port) { 101 if (port) {
59 if (!phy_is_wideport_member(port, phy)) 102 if (!phy_is_wideport_member(port, phy))
60 sas_deform_port(phy, 0); 103 sas_deform_port(phy, 0);
61 else { 104 else if (phy->suspended) {
105 phy->suspended = 0;
106 sas_resume_port(phy);
107
108 /* phy came back, try to cancel the timeout */
109 wake_up(&sas_ha->eh_wait_q);
110 return;
111 } else {
62 SAS_DPRINTK("%s: phy%d belongs to port%d already(%d)!\n", 112 SAS_DPRINTK("%s: phy%d belongs to port%d already(%d)!\n",
63 __func__, phy->id, phy->port->id, 113 __func__, phy->id, phy->port->id,
64 phy->port->num_phys); 114 phy->port->num_phys);