/*- * This file is provided under a dual BSD/GPLv2 license. When using or * redistributing this file, you may do so under either license. * * GPL LICENSE SUMMARY * * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * The full GNU General Public License is included in this distribution * in the file called LICENSE.GPL. * * BSD LICENSE * * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /** * @file * * @brief This file contains all of the entrance and exit methods for each * of the domain states defined by the SCI_BASE_DOMAIN state * machine. */ #include #include #include #include #include #include //****************************************************************************** //* P R O T E C T E D M E T H O D S //****************************************************************************** /** * @brief This method will attempt to transition to the stopped state. * The transition will only occur if the criteria for transition is * met (i.e. all IOs are complete and all devices are stopped). * * @param[in] fw_domain This parameter specifies the domain in which to * to attempt to perform the transition. * * @return none */ void scif_sas_domain_transition_to_stopped_state( SCIF_SAS_DOMAIN_T * fw_domain ) { SCIF_LOG_TRACE(( sci_base_object_get_logger(fw_domain), SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY, "scif_sas_domain_transition_to_stopped_state(0x%x) enter\n", fw_domain )); // If IOs are quiesced, and all remote devices are stopped, // then transition directly to the STOPPED state. if ( (fw_domain->request_list.element_count == 0) && (fw_domain->device_start_count == 0) ) { SCIF_LOG_INFO(( sci_base_object_get_logger(fw_domain), SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY, "Domain:0x%x immediate transition to STOPPED\n", fw_domain )); sci_base_state_machine_change_state( &fw_domain->parent.state_machine, SCI_BASE_DOMAIN_STATE_STOPPED ); } } /** * @brief This method is called upon entrance to all states where the * previous state may have been the DISCOVERING state. * We issue the scif_cb_domain_discovery_complete() notification * from this method, assuming pre-requisites are met, as opposed * to in the exit handler of the DISCOVERING state, so that the * appropriate state handlers are in place should the user decide * to call scif_domain_discover() again. * * @param[in] fw_domain This parameter specifies the domain for which * the state transition has occurred. * * @return none */ static void scif_sas_domain_transition_from_discovering_state( SCIF_SAS_DOMAIN_T * fw_domain ) { SCIF_LOG_TRACE(( sci_base_object_get_logger(fw_domain), SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY, "scif_sas_domain_transition_from_discovering_state(0x%x) enter\n", fw_domain )); if (fw_domain->parent.state_machine.previous_state_id == SCI_BASE_DOMAIN_STATE_DISCOVERING) { scif_sas_controller_restore_interrupt_coalescence(fw_domain->controller); scif_cb_timer_stop(fw_domain->controller, fw_domain->operation.timer); scif_cb_domain_discovery_complete( fw_domain->controller, fw_domain, fw_domain->operation.status ); } } /** * @brief This method is called upon entrance to DISCOVERING state. Right before * transitioning to DISCOVERING state, we temporarily change interrupt * coalescence scheme. * * @param[in] fw_domain This parameter specifies the domain for which * the state transition has occurred. * * @return none */ void scif_sas_domain_transition_to_discovering_state( SCIF_SAS_DOMAIN_T * fw_domain ) { scif_sas_controller_save_interrupt_coalescence(fw_domain->controller); sci_base_state_machine_change_state( &fw_domain->parent.state_machine, SCI_BASE_DOMAIN_STATE_DISCOVERING ); } /** * @brief This method implements the actions taken when entering the * INITIAL state. * * @param[in] object This parameter specifies the base object for which * the state transition is occurring. This is cast into a * SCIF_SAS_DOMAIN object in the method implementation. * * @return none */ static void scif_sas_domain_initial_state_enter( SCI_BASE_OBJECT_T * object ) { SCIF_SAS_DOMAIN_T * fw_domain = (SCIF_SAS_DOMAIN_T *)object; SET_STATE_HANDLER( fw_domain, scif_sas_domain_state_handler_table, SCI_BASE_DOMAIN_STATE_INITIAL ); SCIF_LOG_TRACE(( sci_base_object_get_logger(fw_domain), SCIF_LOG_OBJECT_DOMAIN, "scif_sas_domain_initial_state_enter(0x%x) enter\n", fw_domain )); } /** * @brief This method implements the actions taken when entering the * STARTING state. This includes setting the state handlers and * checking to see if the core port has already become READY. * * @param[in] object This parameter specifies the base object for which * the state transition is occurring. This is cast into a * SCIF_SAS_DOMAIN object in the method implementation. * * @return none */ static void scif_sas_domain_starting_state_enter( SCI_BASE_OBJECT_T * object ) { SCIF_SAS_DOMAIN_T * fw_domain = (SCIF_SAS_DOMAIN_T *)object; SET_STATE_HANDLER( fw_domain, scif_sas_domain_state_handler_table, SCI_BASE_DOMAIN_STATE_STARTING ); SCIF_LOG_TRACE(( sci_base_object_get_logger(fw_domain), SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY, "scif_sas_domain_starting_state_enter(0x%x) enter\n", fw_domain )); scif_sas_domain_transition_from_discovering_state(fw_domain); // If we entered the STARTING state and the core port is actually ready, // then directly transition into the READY state. This can occur // if we were in the middle of discovery when the port failed // (causing a transition to STOPPING), then before reaching STOPPED // the port becomes ready again. if (fw_domain->is_port_ready == TRUE) { sci_base_state_machine_change_state( &fw_domain->parent.state_machine, SCI_BASE_DOMAIN_STATE_READY ); } } /** * @brief This method implements the actions taken when entering the * READY state. If the transition into this state came from: * - the STARTING state, then alert the user via a * scif_cb_domain_change_notification() that the domain * has at least 1 device ready for discovery. * - the DISCOVERING state, then alert the user that * discovery is complete via the * scif_cb_domain_discovery_complete() notification that * discovery is finished. * * @param[in] object This parameter specifies the base object for which * the state transition is occurring. This is cast into a * SCIF_SAS_DOMAIN object in the method implementation. * * @return none */ static void scif_sas_domain_ready_state_enter( SCI_BASE_OBJECT_T * object ) { SCIF_SAS_DOMAIN_T * fw_domain = (SCIF_SAS_DOMAIN_T *)object; SET_STATE_HANDLER( fw_domain, scif_sas_domain_state_handler_table, SCI_BASE_DOMAIN_STATE_READY ); SCIF_LOG_TRACE(( sci_base_object_get_logger(fw_domain), SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY, "scif_sas_domain_ready_state_enter(0x%x) enter\n", fw_domain )); if (fw_domain->parent.state_machine.previous_state_id == SCI_BASE_DOMAIN_STATE_STARTING) { scif_cb_domain_ready(fw_domain->controller, fw_domain); // Only indicate the domain change notification if the previous // state was the STARTING state. We issue the notification here // as opposed to exit of the STARTING state so that the appropriate // state handlers are in place should the user call // scif_domain_discover() from scif_cb_domain_change_notification() scif_cb_domain_change_notification(fw_domain->controller, fw_domain); } else if (fw_domain->parent.state_machine.previous_state_id == SCI_BASE_DOMAIN_STATE_DISCOVERING) { //if domain discovery timed out, we will NOT go back to discover even //the broadcast change count is not zero. Instead we finish the discovery //back to user. User can check the operation status and decide to //retry discover all over again. if (fw_domain->operation.status == SCI_FAILURE_TIMEOUT) fw_domain->broadcast_change_count = 0; // Check the broadcast change count to determine if discovery // is indeed complete. if (fw_domain->broadcast_change_count == 0) { scif_sas_domain_transition_from_discovering_state(fw_domain); scif_cb_domain_ready(fw_domain->controller, fw_domain); } else { // The broadcast change count indicates something my have // changed in the domain, while a discovery was ongoing. // Thus, we should start discovery over again. sci_base_state_machine_change_state( &fw_domain->parent.state_machine, SCI_BASE_DOMAIN_STATE_DISCOVERING ); } // Enable the BCN because underneath hardware may disabled any further // BCN. scic_port_enable_broadcast_change_notification(fw_domain->core_object); } } /** * @brief This method implements the actions taken when exiting the * READY state. * * @param[in] object This parameter specifies the base object for which * the state transition is occurring. This is cast into a * SCIF_SAS_DOMAIN object in the method implementation. * * @return none */ static void scif_sas_domain_ready_state_exit( SCI_BASE_OBJECT_T * object ) { SCIF_SAS_DOMAIN_T * fw_domain = (SCIF_SAS_DOMAIN_T *)object; SCIF_LOG_TRACE(( sci_base_object_get_logger(fw_domain), SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY, "scif_sas_domain_ready_state_exit(0x%x) enter\n", fw_domain )); scif_cb_domain_not_ready(fw_domain->controller, fw_domain); } /** * @brief This method implements the actions taken when entering the * STOPPING state. * * @param[in] object This parameter specifies the base object for which * the state transition is occurring. This is cast into a * SCIF_SAS_DOMAIN object in the method implementation. * * @return none */ static void scif_sas_domain_stopping_state_enter( SCI_BASE_OBJECT_T * object ) { SCIF_SAS_REMOTE_DEVICE_T * fw_device; SCIF_SAS_DOMAIN_T * fw_domain = (SCIF_SAS_DOMAIN_T *)object; SCI_ABSTRACT_ELEMENT_T * element = sci_abstract_list_get_front( &fw_domain->remote_device_list ); SET_STATE_HANDLER( fw_domain, scif_sas_domain_state_handler_table, SCI_BASE_DOMAIN_STATE_STOPPING ); // This must be invoked after the state handlers are set to ensure // appropriate processing will occur if the user attempts to perform // additional actions. scif_sas_domain_transition_from_discovering_state(fw_domain); SCIF_LOG_TRACE(( sci_base_object_get_logger(fw_domain), SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY, "scif_sas_domain_stopping_state_enter(0x%x) enter\n", fw_domain )); scif_sas_high_priority_request_queue_purge_domain( &fw_domain->controller->hprq, fw_domain ); // Search the domain's list of devices and put them all in the STOPPING // state. while (element != NULL) { fw_device = (SCIF_SAS_REMOTE_DEVICE_T*) sci_abstract_list_get_object(element); // This method will stop the core device. The core will terminate // all IO requests currently outstanding. fw_device->state_handlers->parent.stop_handler(&fw_device->parent); element = sci_abstract_list_get_next(element); } // Attempt to transition to the stopped state. scif_sas_domain_transition_to_stopped_state(fw_domain); } /** * @brief This method implements the actions taken when entering the * STOPPED state. * * @param[in] object This parameter specifies the base object for which * the state transition is occurring. This is cast into a * SCIF_SAS_DOMAIN object in the method implementation. * * @return none */ static void scif_sas_domain_stopped_state_enter( SCI_BASE_OBJECT_T * object ) { SCIF_SAS_DOMAIN_T * fw_domain = (SCIF_SAS_DOMAIN_T *)object; SET_STATE_HANDLER( fw_domain, scif_sas_domain_state_handler_table, SCI_BASE_DOMAIN_STATE_STOPPED ); SCIF_LOG_TRACE(( sci_base_object_get_logger(fw_domain), SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY, "scif_sas_domain_stopped_state_enter(0x%x) enter\n", fw_domain )); // A hot unplug of the direct attached device has occurred. Thus, // notify the user. Note, if the controller is not in READY state, // mostly likely the controller is in STOPPING or STOPPED state, // meaning the controller is in the process of stopping, we should // not call back to user in the middle of controller stopping. if(fw_domain->controller->parent.state_machine.current_state_id == SCI_BASE_CONTROLLER_STATE_READY) scif_cb_domain_change_notification(fw_domain->controller, fw_domain); } /** * @brief This method implements the actions taken when entering the * DISCOVERING state. This includes determining from which * state we entered. If we entered from stopping that some sort * of hot-remove of the port occurred. In the hot-remove case * all devices should be in the STOPPED state already and, as * a result, are removed from the domain with a notification sent * to the framework user. * * @note This method currently only handles hot-insert/hot-remove of * direct attached SSP devices. * * @param[in] object This parameter specifies the base object for which * the state transition is occurring. This is cast into a * SCIF_SAS_DOMAIN object in the method implementation. * * @return none */ static void scif_sas_domain_discovering_state_enter( SCI_BASE_OBJECT_T * object ) { SCIF_SAS_DOMAIN_T * fw_domain = (SCIF_SAS_DOMAIN_T *)object; SET_STATE_HANDLER( fw_domain, scif_sas_domain_state_handler_table, SCI_BASE_DOMAIN_STATE_DISCOVERING ); SCIF_LOG_TRACE(( sci_base_object_get_logger(fw_domain), SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY, "scif_sas_domain_discovering_state_enter(0x%x) enter\n", fw_domain )); fw_domain->broadcast_change_count = 0; // Did the domain just go through a port not ready action? If it did, // then we will be entering from the STOPPED state. if (fw_domain->parent.state_machine.previous_state_id != SCI_BASE_DOMAIN_STATE_STOPPED) { SCIF_SAS_REMOTE_DEVICE_T * remote_device; SCIC_PORT_PROPERTIES_T properties; scic_port_get_properties(fw_domain->core_object, &properties); // If the device has not yet been added to the domain, then // inform the user that the device is new. remote_device = (SCIF_SAS_REMOTE_DEVICE_T *) scif_domain_get_device_by_sas_address( fw_domain, &properties.remote.sas_address ); if (remote_device == SCI_INVALID_HANDLE) { // simply notify the user of the new DA device and be done // with discovery. scif_cb_domain_da_device_added( fw_domain->controller, fw_domain, &properties.remote.sas_address, &properties.remote.protocols ); } else { if(properties.remote.protocols.u.bits.smp_target) //kick off the smp discover process. scif_sas_domain_start_smp_discover(fw_domain, remote_device); } } else //entered from STOPPED state. { SCI_ABSTRACT_ELEMENT_T * current_element = sci_abstract_list_get_front(&(fw_domain->remote_device_list) ); SCIF_SAS_REMOTE_DEVICE_T * fw_device; while (current_element != NULL) { fw_device = (SCIF_SAS_REMOTE_DEVICE_T *) sci_abstract_list_get_object(current_element); ASSERT(fw_device->parent.state_machine.current_state_id == SCI_BASE_REMOTE_DEVICE_STATE_STOPPED); current_element = sci_abstract_list_get_next(current_element); SCIF_LOG_INFO(( sci_base_object_get_logger(fw_domain), SCIF_LOG_OBJECT_DOMAIN | SCIF_LOG_OBJECT_DOMAIN_DISCOVERY, "Controller:0x%x Domain:0x%x Device:0x%x removed\n", fw_domain->controller, fw_domain, fw_device )); // Notify the framework user of the device removal. scif_cb_domain_device_removed( fw_domain->controller, fw_domain, fw_device ); } ASSERT(fw_domain->request_list.element_count == 0); ASSERT(sci_abstract_list_size(&fw_domain->remote_device_list) == 0); sci_base_state_machine_change_state( &fw_domain->parent.state_machine, SCI_BASE_DOMAIN_STATE_STARTING ); } } SCI_BASE_STATE_T scif_sas_domain_state_table[SCI_BASE_DOMAIN_MAX_STATES] = { { SCI_BASE_DOMAIN_STATE_INITIAL, scif_sas_domain_initial_state_enter, NULL, }, { SCI_BASE_DOMAIN_STATE_STARTING, scif_sas_domain_starting_state_enter, NULL, }, { SCI_BASE_DOMAIN_STATE_READY, scif_sas_domain_ready_state_enter, scif_sas_domain_ready_state_exit, }, { SCI_BASE_DOMAIN_STATE_STOPPING, scif_sas_domain_stopping_state_enter, NULL, }, { SCI_BASE_DOMAIN_STATE_STOPPED, scif_sas_domain_stopped_state_enter, NULL, }, { SCI_BASE_DOMAIN_STATE_DISCOVERING, scif_sas_domain_discovering_state_enter, NULL, } };