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