1/*-
2 * This file is provided under a dual BSD/GPLv2 license.  When using or
3 * redistributing this file, you may do so under either license.
4 *
5 * GPL LICENSE SUMMARY
6 *
7 * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved.
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of version 2 of the GNU General Public License as
11 * published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
21 * The full GNU General Public License is included in this distribution
22 * in the file called LICENSE.GPL.
23 *
24 * BSD LICENSE
25 *
26 * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved.
27 * All rights reserved.
28 *
29 * Redistribution and use in source and binary forms, with or without
30 * modification, are permitted provided that the following conditions
31 * are met:
32 *
33 *   * Redistributions of source code must retain the above copyright
34 *     notice, this list of conditions and the following disclaimer.
35 *   * Redistributions in binary form must reproduce the above copyright
36 *     notice, this list of conditions and the following disclaimer in
37 *     the documentation and/or other materials provided with the
38 *     distribution.
39 *
40 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
41 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
42 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
43 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
44 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
45 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
46 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
47 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
48 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
49 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
50 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
51 */
52
53#include <sys/cdefs.h>
54__FBSDID("$FreeBSD$");
55
56/**
57 * @file
58 *
59 * @brief This file contains all of the entrance and exit methods for each
60 *        of the domain states defined by the SCI_BASE_DOMAIN state
61 *        machine.
62 */
63
64#include <dev/isci/scil/intel_sas.h>
65#include <dev/isci/scil/scic_port.h>
66
67#include <dev/isci/scil/scif_sas_logger.h>
68#include <dev/isci/scil/scif_sas_domain.h>
69#include <dev/isci/scil/scif_sas_controller.h>
70#include <dev/isci/scil/scic_controller.h>
71
72//******************************************************************************
73//* P R O T E C T E D    M E T H O D S
74//******************************************************************************
75
76/**
77 * @brief This method will attempt to transition to the stopped state.
78 *        The transition will only occur if the criteria for transition is
79 *        met (i.e. all IOs are complete and all devices are stopped).
80 *
81 * @param[in]  fw_domain This parameter specifies the domain in which to
82 *             to attempt to perform the transition.
83 *
84 * @return none
85 */
86void scif_sas_domain_transition_to_stopped_state(
87   SCIF_SAS_DOMAIN_T * fw_domain
88)
89{
90   SCIF_LOG_TRACE((
91      sci_base_object_get_logger(fw_domain),
92      SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY,
93      "scif_sas_domain_transition_to_stopped_state(0x%x) enter\n",
94      fw_domain
95   ));
96
97   // If IOs are quiesced, and all remote devices are stopped,
98   // then transition directly to the STOPPED state.
99   if (  (fw_domain->request_list.element_count == 0)
100      && (fw_domain->device_start_count == 0) )
101   {
102      SCIF_LOG_INFO((
103         sci_base_object_get_logger(fw_domain),
104         SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY,
105         "Domain:0x%x immediate transition to STOPPED\n",
106         fw_domain
107      ));
108
109      sci_base_state_machine_change_state(
110         &fw_domain->parent.state_machine, SCI_BASE_DOMAIN_STATE_STOPPED
111      );
112   }
113}
114
115
116/**
117 * @brief This method is called upon entrance to all states where the
118 *        previous state may have been the DISCOVERING state.
119 *        We issue the scif_cb_domain_discovery_complete() notification
120 *        from this method, assuming pre-requisites are met, as opposed
121 *        to in the exit handler of the DISCOVERING state, so that the
122 *        appropriate state handlers are in place should the user decide
123 *        to call scif_domain_discover() again.
124 *
125 * @param[in]  fw_domain This parameter specifies the domain for which
126 *             the state transition has occurred.
127 *
128 * @return none
129 */
130static
131void scif_sas_domain_transition_from_discovering_state(
132   SCIF_SAS_DOMAIN_T * fw_domain
133)
134{
135   SCIF_LOG_TRACE((
136      sci_base_object_get_logger(fw_domain),
137      SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY,
138      "scif_sas_domain_transition_from_discovering_state(0x%x) enter\n",
139      fw_domain
140   ));
141
142   if (fw_domain->parent.state_machine.previous_state_id
143       == SCI_BASE_DOMAIN_STATE_DISCOVERING)
144   {
145      scif_sas_controller_restore_interrupt_coalescence(fw_domain->controller);
146
147      scif_cb_timer_stop(fw_domain->controller, fw_domain->operation.timer);
148
149      scif_cb_domain_discovery_complete(
150         fw_domain->controller, fw_domain, fw_domain->operation.status
151      );
152   }
153}
154
155
156/**
157 * @brief This method is called upon entrance to DISCOVERING state. Right before
158 *           transitioning to DISCOVERING state, we temporarily change interrupt
159 *           coalescence scheme.
160 *
161 * @param[in]  fw_domain This parameter specifies the domain for which
162 *             the state transition has occurred.
163 *
164 * @return none
165 */
166void scif_sas_domain_transition_to_discovering_state(
167   SCIF_SAS_DOMAIN_T * fw_domain
168)
169{
170   scif_sas_controller_save_interrupt_coalescence(fw_domain->controller);
171
172   sci_base_state_machine_change_state(
173      &fw_domain->parent.state_machine, SCI_BASE_DOMAIN_STATE_DISCOVERING
174   );
175}
176
177
178/**
179 * @brief This method implements the actions taken when entering the
180 *        INITIAL state.
181 *
182 * @param[in]  object This parameter specifies the base object for which
183 *             the state transition is occurring.  This is cast into a
184 *             SCIF_SAS_DOMAIN object in the method implementation.
185 *
186 * @return none
187 */
188static
189void scif_sas_domain_initial_state_enter(
190   SCI_BASE_OBJECT_T * object
191)
192{
193   SCIF_SAS_DOMAIN_T * fw_domain = (SCIF_SAS_DOMAIN_T *)object;
194
195   SET_STATE_HANDLER(
196      fw_domain,
197      scif_sas_domain_state_handler_table,
198      SCI_BASE_DOMAIN_STATE_INITIAL
199   );
200
201   SCIF_LOG_TRACE((
202      sci_base_object_get_logger(fw_domain),
203      SCIF_LOG_OBJECT_DOMAIN,
204      "scif_sas_domain_initial_state_enter(0x%x) enter\n",
205      fw_domain
206   ));
207}
208
209/**
210 * @brief This method implements the actions taken when entering the
211 *        STARTING state.  This includes setting the state handlers and
212 *        checking to see if the core port has already become READY.
213 *
214 * @param[in]  object This parameter specifies the base object for which
215 *             the state transition is occurring.  This is cast into a
216 *             SCIF_SAS_DOMAIN object in the method implementation.
217 *
218 * @return none
219 */
220static
221void scif_sas_domain_starting_state_enter(
222   SCI_BASE_OBJECT_T * object
223)
224{
225   SCIF_SAS_DOMAIN_T * fw_domain = (SCIF_SAS_DOMAIN_T *)object;
226
227   SET_STATE_HANDLER(
228      fw_domain,
229      scif_sas_domain_state_handler_table,
230      SCI_BASE_DOMAIN_STATE_STARTING
231   );
232
233   SCIF_LOG_TRACE((
234      sci_base_object_get_logger(fw_domain),
235      SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY,
236      "scif_sas_domain_starting_state_enter(0x%x) enter\n",
237      fw_domain
238   ));
239
240   scif_sas_domain_transition_from_discovering_state(fw_domain);
241
242   // If we entered the STARTING state and the core port is actually ready,
243   // then directly transition into the READY state.  This can occur
244   // if we were in the middle of discovery when the port failed
245   // (causing a transition to STOPPING), then before reaching STOPPED
246   // the port becomes ready again.
247   if (fw_domain->is_port_ready == TRUE)
248   {
249      sci_base_state_machine_change_state(
250         &fw_domain->parent.state_machine, SCI_BASE_DOMAIN_STATE_READY
251      );
252   }
253}
254
255/**
256 * @brief This method implements the actions taken when entering the
257 *        READY state.  If the transition into this state came from:
258 *        - the STARTING state, then alert the user via a
259 *          scif_cb_domain_change_notification() that the domain
260 *          has at least 1 device ready for discovery.
261 *        - the DISCOVERING state, then alert the user that
262 *          discovery is complete via the
263 *          scif_cb_domain_discovery_complete() notification that
264 *          discovery is finished.
265 *
266 * @param[in]  object This parameter specifies the base object for which
267 *             the state transition is occurring.  This is cast into a
268 *             SCIF_SAS_DOMAIN object in the method implementation.
269 *
270 * @return none
271 */
272static
273void scif_sas_domain_ready_state_enter(
274   SCI_BASE_OBJECT_T * object
275)
276{
277   SCIF_SAS_DOMAIN_T * fw_domain = (SCIF_SAS_DOMAIN_T *)object;
278
279   SET_STATE_HANDLER(
280      fw_domain,
281      scif_sas_domain_state_handler_table,
282      SCI_BASE_DOMAIN_STATE_READY
283   );
284
285   SCIF_LOG_TRACE((
286      sci_base_object_get_logger(fw_domain),
287      SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY,
288      "scif_sas_domain_ready_state_enter(0x%x) enter\n",
289      fw_domain
290   ));
291
292   if (fw_domain->parent.state_machine.previous_state_id
293       == SCI_BASE_DOMAIN_STATE_STARTING)
294   {
295      scif_cb_domain_ready(fw_domain->controller, fw_domain);
296
297      // Only indicate the domain change notification if the previous
298      // state was the STARTING state.  We issue the notification here
299      // as opposed to exit of the STARTING state so that the appropriate
300      // state handlers are in place should the user call
301      // scif_domain_discover() from scif_cb_domain_change_notification()
302      scif_cb_domain_change_notification(fw_domain->controller, fw_domain);
303   }
304   else if (fw_domain->parent.state_machine.previous_state_id
305            == SCI_BASE_DOMAIN_STATE_DISCOVERING)
306   {
307      //if domain discovery timed out, we will NOT go back to discover even
308      //the broadcast change count is not zero. Instead we finish the discovery
309      //back to user. User can check the operation status and decide to
310      //retry discover all over again.
311      if (fw_domain->operation.status == SCI_FAILURE_TIMEOUT)
312         fw_domain->broadcast_change_count = 0;
313
314      // Check the broadcast change count to determine if discovery
315      // is indeed complete.
316      if (fw_domain->broadcast_change_count == 0)
317      {
318         scif_sas_domain_transition_from_discovering_state(fw_domain);
319         scif_cb_domain_ready(fw_domain->controller, fw_domain);
320      }
321      else
322      {
323         // The broadcast change count indicates something my have
324         // changed in the domain, while a discovery was ongoing.
325         // Thus, we should start discovery over again.
326         sci_base_state_machine_change_state(
327            &fw_domain->parent.state_machine, SCI_BASE_DOMAIN_STATE_DISCOVERING
328         );
329      }
330
331      // Enable the BCN because underneath hardware may disabled any further
332      // BCN.
333      scic_port_enable_broadcast_change_notification(fw_domain->core_object);
334   }
335}
336
337/**
338 * @brief This method implements the actions taken when exiting the
339 *        READY state.
340 *
341 * @param[in]  object This parameter specifies the base object for which
342 *             the state transition is occurring.  This is cast into a
343 *             SCIF_SAS_DOMAIN object in the method implementation.
344 *
345 * @return none
346 */
347static
348void scif_sas_domain_ready_state_exit(
349   SCI_BASE_OBJECT_T * object
350)
351{
352   SCIF_SAS_DOMAIN_T * fw_domain = (SCIF_SAS_DOMAIN_T *)object;
353
354   SCIF_LOG_TRACE((
355      sci_base_object_get_logger(fw_domain),
356      SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY,
357      "scif_sas_domain_ready_state_exit(0x%x) enter\n",
358      fw_domain
359   ));
360
361   scif_cb_domain_not_ready(fw_domain->controller, fw_domain);
362}
363
364/**
365 * @brief This method implements the actions taken when entering the
366 *        STOPPING state.
367 *
368 * @param[in]  object This parameter specifies the base object for which
369 *             the state transition is occurring.  This is cast into a
370 *             SCIF_SAS_DOMAIN object in the method implementation.
371 *
372 * @return none
373 */
374static
375void scif_sas_domain_stopping_state_enter(
376   SCI_BASE_OBJECT_T * object
377)
378{
379   SCIF_SAS_REMOTE_DEVICE_T * fw_device;
380   SCIF_SAS_DOMAIN_T        * fw_domain = (SCIF_SAS_DOMAIN_T *)object;
381   SCI_ABSTRACT_ELEMENT_T   * element   = sci_abstract_list_get_front(
382                                             &fw_domain->remote_device_list
383                                          );
384
385   SET_STATE_HANDLER(
386      fw_domain,
387      scif_sas_domain_state_handler_table,
388      SCI_BASE_DOMAIN_STATE_STOPPING
389   );
390
391   // This must be invoked after the state handlers are set to ensure
392   // appropriate processing will occur if the user attempts to perform
393   // additional actions.
394   scif_sas_domain_transition_from_discovering_state(fw_domain);
395
396   SCIF_LOG_TRACE((
397      sci_base_object_get_logger(fw_domain),
398      SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY,
399      "scif_sas_domain_stopping_state_enter(0x%x) enter\n",
400      fw_domain
401   ));
402
403   scif_sas_high_priority_request_queue_purge_domain(
404      &fw_domain->controller->hprq, fw_domain
405   );
406
407   // Search the domain's list of devices and put them all in the STOPPING
408   // state.
409   while (element != NULL)
410   {
411      fw_device = (SCIF_SAS_REMOTE_DEVICE_T*)
412                  sci_abstract_list_get_object(element);
413
414      // This method will stop the core device.  The core will terminate
415      // all IO requests currently outstanding.
416      fw_device->state_handlers->parent.stop_handler(&fw_device->parent);
417
418      element = sci_abstract_list_get_next(element);
419   }
420
421   // Attempt to transition to the stopped state.
422   scif_sas_domain_transition_to_stopped_state(fw_domain);
423}
424
425/**
426 * @brief This method implements the actions taken when entering the
427 *        STOPPED state.
428 *
429 * @param[in]  object This parameter specifies the base object for which
430 *             the state transition is occurring.  This is cast into a
431 *             SCIF_SAS_DOMAIN object in the method implementation.
432 *
433 * @return none
434 */
435static
436void scif_sas_domain_stopped_state_enter(
437   SCI_BASE_OBJECT_T * object
438)
439{
440   SCIF_SAS_DOMAIN_T * fw_domain = (SCIF_SAS_DOMAIN_T *)object;
441
442   SET_STATE_HANDLER(
443      fw_domain,
444      scif_sas_domain_state_handler_table,
445      SCI_BASE_DOMAIN_STATE_STOPPED
446   );
447
448   SCIF_LOG_TRACE((
449      sci_base_object_get_logger(fw_domain),
450      SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY,
451      "scif_sas_domain_stopped_state_enter(0x%x) enter\n",
452      fw_domain
453   ));
454
455   // A hot unplug of the direct attached device has occurred.  Thus,
456   // notify the user. Note, if the controller is not in READY state,
457   // mostly likely the controller is in STOPPING or STOPPED state,
458   // meaning the controller is in the process of stopping, we should
459   // not call back to user in the middle of controller stopping.
460   if(fw_domain->controller->parent.state_machine.current_state_id
461         == SCI_BASE_CONTROLLER_STATE_READY)
462      scif_cb_domain_change_notification(fw_domain->controller, fw_domain);
463}
464
465/**
466 * @brief This method implements the actions taken when entering the
467 *        DISCOVERING state.  This includes determining from which
468 *        state we entered.  If we entered from stopping that some sort
469 *        of hot-remove of the port occurred.  In the hot-remove case
470 *        all devices should be in the STOPPED state already and, as
471 *        a result, are removed from the domain with a notification sent
472 *        to the framework user.
473 *
474 * @note This method currently only handles hot-insert/hot-remove of
475 *       direct attached SSP devices.
476 *
477 * @param[in]  object This parameter specifies the base object for which
478 *             the state transition is occurring.  This is cast into a
479 *             SCIF_SAS_DOMAIN object in the method implementation.
480 *
481 * @return none
482 */
483static
484void scif_sas_domain_discovering_state_enter(
485   SCI_BASE_OBJECT_T * object
486)
487{
488   SCIF_SAS_DOMAIN_T * fw_domain = (SCIF_SAS_DOMAIN_T *)object;
489
490   SET_STATE_HANDLER(
491      fw_domain,
492      scif_sas_domain_state_handler_table,
493      SCI_BASE_DOMAIN_STATE_DISCOVERING
494   );
495
496   SCIF_LOG_TRACE((
497      sci_base_object_get_logger(fw_domain),
498      SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY,
499      "scif_sas_domain_discovering_state_enter(0x%x) enter\n",
500      fw_domain
501   ));
502
503   fw_domain->broadcast_change_count = 0;
504
505   // Did the domain just go through a port not ready action?  If it did,
506   // then we will be entering from the STOPPED state.
507   if (fw_domain->parent.state_machine.previous_state_id
508       != SCI_BASE_DOMAIN_STATE_STOPPED)
509   {
510      SCIF_SAS_REMOTE_DEVICE_T * remote_device;
511      SCIC_PORT_PROPERTIES_T     properties;
512
513      scic_port_get_properties(fw_domain->core_object, &properties);
514
515      // If the device has not yet been added to the domain, then
516      // inform the user that the device is new.
517      remote_device = (SCIF_SAS_REMOTE_DEVICE_T *)
518                      scif_domain_get_device_by_sas_address(
519                         fw_domain, &properties.remote.sas_address
520                      );
521      if (remote_device == SCI_INVALID_HANDLE)
522      {
523         // simply notify the user of the new DA device and be done
524         // with discovery.
525         scif_cb_domain_da_device_added(
526            fw_domain->controller,
527            fw_domain,
528            &properties.remote.sas_address,
529            &properties.remote.protocols
530         );
531      }
532      else
533      {
534         if(properties.remote.protocols.u.bits.smp_target)
535            //kick off the smp discover process.
536            scif_sas_domain_start_smp_discover(fw_domain, remote_device);
537      }
538   }
539   else  //entered from STOPPED state.
540   {
541      SCI_ABSTRACT_ELEMENT_T * current_element =
542             sci_abstract_list_get_front(&(fw_domain->remote_device_list) );
543
544      SCIF_SAS_REMOTE_DEVICE_T * fw_device;
545
546      while (current_element != NULL)
547      {
548         fw_device = (SCIF_SAS_REMOTE_DEVICE_T *)
549                     sci_abstract_list_get_object(current_element);
550
551         ASSERT(fw_device->parent.state_machine.current_state_id
552                == SCI_BASE_REMOTE_DEVICE_STATE_STOPPED);
553
554         current_element =
555            sci_abstract_list_get_next(current_element);
556
557         SCIF_LOG_INFO((
558            sci_base_object_get_logger(fw_domain),
559            SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY,
560            "Controller:0x%x Domain:0x%x Device:0x%x removed\n",
561            fw_domain->controller, fw_domain, fw_device
562         ));
563
564         // Notify the framework user of the device removal.
565         scif_cb_domain_device_removed(
566            fw_domain->controller, fw_domain, fw_device
567         );
568      }
569
570      ASSERT(fw_domain->request_list.element_count == 0);
571      ASSERT(sci_abstract_list_size(&fw_domain->remote_device_list) == 0);
572
573      sci_base_state_machine_change_state(
574         &fw_domain->parent.state_machine, SCI_BASE_DOMAIN_STATE_STARTING
575      );
576   }
577}
578
579SCI_BASE_STATE_T scif_sas_domain_state_table[SCI_BASE_DOMAIN_MAX_STATES] =
580{
581   {
582      SCI_BASE_DOMAIN_STATE_INITIAL,
583      scif_sas_domain_initial_state_enter,
584      NULL,
585   },
586   {
587      SCI_BASE_DOMAIN_STATE_STARTING,
588      scif_sas_domain_starting_state_enter,
589      NULL,
590   },
591   {
592      SCI_BASE_DOMAIN_STATE_READY,
593      scif_sas_domain_ready_state_enter,
594      scif_sas_domain_ready_state_exit,
595   },
596   {
597      SCI_BASE_DOMAIN_STATE_STOPPING,
598      scif_sas_domain_stopping_state_enter,
599      NULL,
600   },
601   {
602      SCI_BASE_DOMAIN_STATE_STOPPED,
603      scif_sas_domain_stopped_state_enter,
604      NULL,
605   },
606   {
607      SCI_BASE_DOMAIN_STATE_DISCOVERING,
608      scif_sas_domain_discovering_state_enter,
609      NULL,
610   }
611};
612
613