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 /** 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 */ 86 void 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 */ 130 static 131 void 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 */ 166 void 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 */ 188 static 189 void 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 */ 220 static 221 void 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 */ 272 static 273 void 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 */ 347 static 348 void 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 */ 374 static 375 void 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 */ 435 static 436 void 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 */ 483 static 484 void 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 579 SCI_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