diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/scsi/isci/remote_node_context.c | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/scsi/isci/remote_node_context.c')
-rw-r--r-- | drivers/scsi/isci/remote_node_context.c | 805 |
1 files changed, 805 insertions, 0 deletions
diff --git a/drivers/scsi/isci/remote_node_context.c b/drivers/scsi/isci/remote_node_context.c new file mode 100644 index 000000000..77ba02911 --- /dev/null +++ b/drivers/scsi/isci/remote_node_context.c @@ -0,0 +1,805 @@ +/* + * 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. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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 <scsi/sas_ata.h> +#include "host.h" +#include "isci.h" +#include "remote_device.h" +#include "remote_node_context.h" +#include "scu_event_codes.h" +#include "scu_task_context.h" + +#undef C +#define C(a) (#a) +const char *rnc_state_name(enum scis_sds_remote_node_context_states state) +{ + static const char * const strings[] = RNC_STATES; + + if (state >= ARRAY_SIZE(strings)) + return "UNKNOWN"; + + return strings[state]; +} +#undef C + +/** + * sci_remote_node_context_is_ready() + * @sci_rnc: The state of the remote node context object to check. + * + * This method will return true if the remote node context is in a READY state + * otherwise it will return false bool true if the remote node context is in + * the ready state. false if the remote node context is not in the ready state. + */ +bool sci_remote_node_context_is_ready( + struct sci_remote_node_context *sci_rnc) +{ + u32 current_state = sci_rnc->sm.current_state_id; + + if (current_state == SCI_RNC_READY) { + return true; + } + + return false; +} + +bool sci_remote_node_context_is_suspended(struct sci_remote_node_context *sci_rnc) +{ + u32 current_state = sci_rnc->sm.current_state_id; + + if (current_state == SCI_RNC_TX_RX_SUSPENDED) + return true; + return false; +} + +static union scu_remote_node_context *sci_rnc_by_id(struct isci_host *ihost, u16 id) +{ + if (id < ihost->remote_node_entries && + ihost->device_table[id]) + return &ihost->remote_node_context_table[id]; + + return NULL; +} + +static void sci_remote_node_context_construct_buffer(struct sci_remote_node_context *sci_rnc) +{ + struct isci_remote_device *idev = rnc_to_dev(sci_rnc); + struct domain_device *dev = idev->domain_dev; + int rni = sci_rnc->remote_node_index; + union scu_remote_node_context *rnc; + struct isci_host *ihost; + __le64 sas_addr; + + ihost = idev->owning_port->owning_controller; + rnc = sci_rnc_by_id(ihost, rni); + + memset(rnc, 0, sizeof(union scu_remote_node_context) + * sci_remote_device_node_count(idev)); + + rnc->ssp.remote_node_index = rni; + rnc->ssp.remote_node_port_width = idev->device_port_width; + rnc->ssp.logical_port_index = idev->owning_port->physical_port_index; + + /* sas address is __be64, context ram format is __le64 */ + sas_addr = cpu_to_le64(SAS_ADDR(dev->sas_addr)); + rnc->ssp.remote_sas_address_hi = upper_32_bits(sas_addr); + rnc->ssp.remote_sas_address_lo = lower_32_bits(sas_addr); + + rnc->ssp.nexus_loss_timer_enable = true; + rnc->ssp.check_bit = false; + rnc->ssp.is_valid = false; + rnc->ssp.is_remote_node_context = true; + rnc->ssp.function_number = 0; + + rnc->ssp.arbitration_wait_time = 0; + + if (dev_is_sata(dev)) { + rnc->ssp.connection_occupancy_timeout = + ihost->user_parameters.stp_max_occupancy_timeout; + rnc->ssp.connection_inactivity_timeout = + ihost->user_parameters.stp_inactivity_timeout; + } else { + rnc->ssp.connection_occupancy_timeout = + ihost->user_parameters.ssp_max_occupancy_timeout; + rnc->ssp.connection_inactivity_timeout = + ihost->user_parameters.ssp_inactivity_timeout; + } + + rnc->ssp.initial_arbitration_wait_time = 0; + + /* Open Address Frame Parameters */ + rnc->ssp.oaf_connection_rate = idev->connection_rate; + rnc->ssp.oaf_features = 0; + rnc->ssp.oaf_source_zone_group = 0; + rnc->ssp.oaf_more_compatibility_features = 0; +} +/* + * This method will setup the remote node context object so it will transition + * to its ready state. If the remote node context is already setup to + * transition to its final state then this function does nothing. none + */ +static void sci_remote_node_context_setup_to_resume( + struct sci_remote_node_context *sci_rnc, + scics_sds_remote_node_context_callback callback, + void *callback_parameter, + enum sci_remote_node_context_destination_state dest_param) +{ + if (sci_rnc->destination_state != RNC_DEST_FINAL) { + sci_rnc->destination_state = dest_param; + if (callback != NULL) { + sci_rnc->user_callback = callback; + sci_rnc->user_cookie = callback_parameter; + } + } +} + +static void sci_remote_node_context_setup_to_destroy( + struct sci_remote_node_context *sci_rnc, + scics_sds_remote_node_context_callback callback, + void *callback_parameter) +{ + struct isci_host *ihost = idev_to_ihost(rnc_to_dev(sci_rnc)); + + sci_rnc->destination_state = RNC_DEST_FINAL; + sci_rnc->user_callback = callback; + sci_rnc->user_cookie = callback_parameter; + + wake_up(&ihost->eventq); +} + +/* + * This method just calls the user callback function and then resets the + * callback. + */ +static void sci_remote_node_context_notify_user( + struct sci_remote_node_context *rnc) +{ + if (rnc->user_callback != NULL) { + (*rnc->user_callback)(rnc->user_cookie); + + rnc->user_callback = NULL; + rnc->user_cookie = NULL; + } +} + +static void sci_remote_node_context_continue_state_transitions(struct sci_remote_node_context *rnc) +{ + switch (rnc->destination_state) { + case RNC_DEST_READY: + case RNC_DEST_SUSPENDED_RESUME: + rnc->destination_state = RNC_DEST_READY; + fallthrough; + case RNC_DEST_FINAL: + sci_remote_node_context_resume(rnc, rnc->user_callback, + rnc->user_cookie); + break; + default: + rnc->destination_state = RNC_DEST_UNSPECIFIED; + break; + } +} + +static void sci_remote_node_context_validate_context_buffer(struct sci_remote_node_context *sci_rnc) +{ + union scu_remote_node_context *rnc_buffer; + struct isci_remote_device *idev = rnc_to_dev(sci_rnc); + struct domain_device *dev = idev->domain_dev; + struct isci_host *ihost = idev->owning_port->owning_controller; + + rnc_buffer = sci_rnc_by_id(ihost, sci_rnc->remote_node_index); + + rnc_buffer->ssp.is_valid = true; + + if (dev_is_sata(dev) && dev->parent) { + sci_remote_device_post_request(idev, SCU_CONTEXT_COMMAND_POST_RNC_96); + } else { + sci_remote_device_post_request(idev, SCU_CONTEXT_COMMAND_POST_RNC_32); + + if (!dev->parent) + sci_port_setup_transports(idev->owning_port, + sci_rnc->remote_node_index); + } +} + +static void sci_remote_node_context_invalidate_context_buffer(struct sci_remote_node_context *sci_rnc) +{ + union scu_remote_node_context *rnc_buffer; + struct isci_remote_device *idev = rnc_to_dev(sci_rnc); + struct isci_host *ihost = idev->owning_port->owning_controller; + + rnc_buffer = sci_rnc_by_id(ihost, sci_rnc->remote_node_index); + + rnc_buffer->ssp.is_valid = false; + + sci_remote_device_post_request(rnc_to_dev(sci_rnc), + SCU_CONTEXT_COMMAND_POST_RNC_INVALIDATE); +} + +static void sci_remote_node_context_initial_state_enter(struct sci_base_state_machine *sm) +{ + struct sci_remote_node_context *rnc = container_of(sm, typeof(*rnc), sm); + struct isci_remote_device *idev = rnc_to_dev(rnc); + struct isci_host *ihost = idev->owning_port->owning_controller; + + /* Check to see if we have gotten back to the initial state because + * someone requested to destroy the remote node context object. + */ + if (sm->previous_state_id == SCI_RNC_INVALIDATING) { + rnc->destination_state = RNC_DEST_UNSPECIFIED; + sci_remote_node_context_notify_user(rnc); + + smp_wmb(); + wake_up(&ihost->eventq); + } +} + +static void sci_remote_node_context_posting_state_enter(struct sci_base_state_machine *sm) +{ + struct sci_remote_node_context *sci_rnc = container_of(sm, typeof(*sci_rnc), sm); + + sci_remote_node_context_validate_context_buffer(sci_rnc); +} + +static void sci_remote_node_context_invalidating_state_enter(struct sci_base_state_machine *sm) +{ + struct sci_remote_node_context *rnc = container_of(sm, typeof(*rnc), sm); + + /* Terminate all outstanding requests. */ + sci_remote_device_terminate_requests(rnc_to_dev(rnc)); + sci_remote_node_context_invalidate_context_buffer(rnc); +} + +static void sci_remote_node_context_resuming_state_enter(struct sci_base_state_machine *sm) +{ + struct sci_remote_node_context *rnc = container_of(sm, typeof(*rnc), sm); + struct isci_remote_device *idev; + struct domain_device *dev; + + idev = rnc_to_dev(rnc); + dev = idev->domain_dev; + + /* + * For direct attached SATA devices we need to clear the TLCR + * NCQ to TCi tag mapping on the phy and in cases where we + * resume because of a target reset we also need to update + * the STPTLDARNI register with the RNi of the device + */ + if (dev_is_sata(dev) && !dev->parent) + sci_port_setup_transports(idev->owning_port, rnc->remote_node_index); + + sci_remote_device_post_request(idev, SCU_CONTEXT_COMMAND_POST_RNC_RESUME); +} + +static void sci_remote_node_context_ready_state_enter(struct sci_base_state_machine *sm) +{ + struct sci_remote_node_context *rnc = container_of(sm, typeof(*rnc), sm); + enum sci_remote_node_context_destination_state dest_select; + int tell_user = 1; + + dest_select = rnc->destination_state; + rnc->destination_state = RNC_DEST_UNSPECIFIED; + + if ((dest_select == RNC_DEST_SUSPENDED) || + (dest_select == RNC_DEST_SUSPENDED_RESUME)) { + sci_remote_node_context_suspend( + rnc, rnc->suspend_reason, + SCI_SOFTWARE_SUSPEND_EXPECTED_EVENT); + + if (dest_select == RNC_DEST_SUSPENDED_RESUME) + tell_user = 0; /* Wait until ready again. */ + } + if (tell_user) + sci_remote_node_context_notify_user(rnc); +} + +static void sci_remote_node_context_tx_suspended_state_enter(struct sci_base_state_machine *sm) +{ + struct sci_remote_node_context *rnc = container_of(sm, typeof(*rnc), sm); + + sci_remote_node_context_continue_state_transitions(rnc); +} + +static void sci_remote_node_context_tx_rx_suspended_state_enter(struct sci_base_state_machine *sm) +{ + struct sci_remote_node_context *rnc = container_of(sm, typeof(*rnc), sm); + struct isci_remote_device *idev = rnc_to_dev(rnc); + struct isci_host *ihost = idev->owning_port->owning_controller; + u32 new_count = rnc->suspend_count + 1; + + if (new_count == 0) + rnc->suspend_count = 1; + else + rnc->suspend_count = new_count; + smp_wmb(); + + /* Terminate outstanding requests pending abort. */ + sci_remote_device_abort_requests_pending_abort(idev); + + wake_up(&ihost->eventq); + sci_remote_node_context_continue_state_transitions(rnc); +} + +static void sci_remote_node_context_await_suspend_state_exit( + struct sci_base_state_machine *sm) +{ + struct sci_remote_node_context *rnc + = container_of(sm, typeof(*rnc), sm); + struct isci_remote_device *idev = rnc_to_dev(rnc); + + if (dev_is_sata(idev->domain_dev)) + isci_dev_set_hang_detection_timeout(idev, 0); +} + +static const struct sci_base_state sci_remote_node_context_state_table[] = { + [SCI_RNC_INITIAL] = { + .enter_state = sci_remote_node_context_initial_state_enter, + }, + [SCI_RNC_POSTING] = { + .enter_state = sci_remote_node_context_posting_state_enter, + }, + [SCI_RNC_INVALIDATING] = { + .enter_state = sci_remote_node_context_invalidating_state_enter, + }, + [SCI_RNC_RESUMING] = { + .enter_state = sci_remote_node_context_resuming_state_enter, + }, + [SCI_RNC_READY] = { + .enter_state = sci_remote_node_context_ready_state_enter, + }, + [SCI_RNC_TX_SUSPENDED] = { + .enter_state = sci_remote_node_context_tx_suspended_state_enter, + }, + [SCI_RNC_TX_RX_SUSPENDED] = { + .enter_state = sci_remote_node_context_tx_rx_suspended_state_enter, + }, + [SCI_RNC_AWAIT_SUSPENSION] = { + .exit_state = sci_remote_node_context_await_suspend_state_exit, + }, +}; + +void sci_remote_node_context_construct(struct sci_remote_node_context *rnc, + u16 remote_node_index) +{ + memset(rnc, 0, sizeof(struct sci_remote_node_context)); + + rnc->remote_node_index = remote_node_index; + rnc->destination_state = RNC_DEST_UNSPECIFIED; + + sci_init_sm(&rnc->sm, sci_remote_node_context_state_table, SCI_RNC_INITIAL); +} + +enum sci_status sci_remote_node_context_event_handler(struct sci_remote_node_context *sci_rnc, + u32 event_code) +{ + enum scis_sds_remote_node_context_states state; + u32 next_state; + + state = sci_rnc->sm.current_state_id; + switch (state) { + case SCI_RNC_POSTING: + switch (scu_get_event_code(event_code)) { + case SCU_EVENT_POST_RNC_COMPLETE: + sci_change_state(&sci_rnc->sm, SCI_RNC_READY); + break; + default: + goto out; + } + break; + case SCI_RNC_INVALIDATING: + if (scu_get_event_code(event_code) == SCU_EVENT_POST_RNC_INVALIDATE_COMPLETE) { + if (sci_rnc->destination_state == RNC_DEST_FINAL) + next_state = SCI_RNC_INITIAL; + else + next_state = SCI_RNC_POSTING; + sci_change_state(&sci_rnc->sm, next_state); + } else { + switch (scu_get_event_type(event_code)) { + case SCU_EVENT_TYPE_RNC_SUSPEND_TX: + case SCU_EVENT_TYPE_RNC_SUSPEND_TX_RX: + /* We really dont care if the hardware is going to suspend + * the device since it's being invalidated anyway */ + dev_warn(scirdev_to_dev(rnc_to_dev(sci_rnc)), + "%s: SCIC Remote Node Context 0x%p was " + "suspended by hardware while being " + "invalidated.\n", __func__, sci_rnc); + break; + default: + goto out; + } + } + break; + case SCI_RNC_RESUMING: + if (scu_get_event_code(event_code) == SCU_EVENT_POST_RCN_RELEASE) { + sci_change_state(&sci_rnc->sm, SCI_RNC_READY); + } else { + switch (scu_get_event_type(event_code)) { + case SCU_EVENT_TYPE_RNC_SUSPEND_TX: + case SCU_EVENT_TYPE_RNC_SUSPEND_TX_RX: + /* We really dont care if the hardware is going to suspend + * the device since it's being resumed anyway */ + dev_warn(scirdev_to_dev(rnc_to_dev(sci_rnc)), + "%s: SCIC Remote Node Context 0x%p was " + "suspended by hardware while being resumed.\n", + __func__, sci_rnc); + break; + default: + goto out; + } + } + break; + case SCI_RNC_READY: + switch (scu_get_event_type(event_code)) { + case SCU_EVENT_TL_RNC_SUSPEND_TX: + sci_change_state(&sci_rnc->sm, SCI_RNC_TX_SUSPENDED); + sci_rnc->suspend_type = scu_get_event_type(event_code); + break; + case SCU_EVENT_TL_RNC_SUSPEND_TX_RX: + sci_change_state(&sci_rnc->sm, SCI_RNC_TX_RX_SUSPENDED); + sci_rnc->suspend_type = scu_get_event_type(event_code); + break; + default: + goto out; + } + break; + case SCI_RNC_AWAIT_SUSPENSION: + switch (scu_get_event_type(event_code)) { + case SCU_EVENT_TL_RNC_SUSPEND_TX: + next_state = SCI_RNC_TX_SUSPENDED; + break; + case SCU_EVENT_TL_RNC_SUSPEND_TX_RX: + next_state = SCI_RNC_TX_RX_SUSPENDED; + break; + default: + goto out; + } + if (sci_rnc->suspend_type == scu_get_event_type(event_code)) + sci_change_state(&sci_rnc->sm, next_state); + break; + default: + dev_warn(scirdev_to_dev(rnc_to_dev(sci_rnc)), + "%s: invalid state: %s\n", __func__, + rnc_state_name(state)); + return SCI_FAILURE_INVALID_STATE; + } + return SCI_SUCCESS; + + out: + dev_warn(scirdev_to_dev(rnc_to_dev(sci_rnc)), + "%s: code: %#x state: %s\n", __func__, event_code, + rnc_state_name(state)); + return SCI_FAILURE; + +} + +enum sci_status sci_remote_node_context_destruct(struct sci_remote_node_context *sci_rnc, + scics_sds_remote_node_context_callback cb_fn, + void *cb_p) +{ + enum scis_sds_remote_node_context_states state; + + state = sci_rnc->sm.current_state_id; + switch (state) { + case SCI_RNC_INVALIDATING: + sci_remote_node_context_setup_to_destroy(sci_rnc, cb_fn, cb_p); + return SCI_SUCCESS; + case SCI_RNC_POSTING: + case SCI_RNC_RESUMING: + case SCI_RNC_READY: + case SCI_RNC_TX_SUSPENDED: + case SCI_RNC_TX_RX_SUSPENDED: + sci_remote_node_context_setup_to_destroy(sci_rnc, cb_fn, cb_p); + sci_change_state(&sci_rnc->sm, SCI_RNC_INVALIDATING); + return SCI_SUCCESS; + case SCI_RNC_AWAIT_SUSPENSION: + sci_remote_node_context_setup_to_destroy(sci_rnc, cb_fn, cb_p); + return SCI_SUCCESS; + case SCI_RNC_INITIAL: + dev_warn(scirdev_to_dev(rnc_to_dev(sci_rnc)), + "%s: invalid state: %s\n", __func__, + rnc_state_name(state)); + /* We have decided that the destruct request on the remote node context + * can not fail since it is either in the initial/destroyed state or is + * can be destroyed. + */ + return SCI_SUCCESS; + default: + dev_warn(scirdev_to_dev(rnc_to_dev(sci_rnc)), + "%s: invalid state %s\n", __func__, + rnc_state_name(state)); + return SCI_FAILURE_INVALID_STATE; + } +} + +enum sci_status sci_remote_node_context_suspend( + struct sci_remote_node_context *sci_rnc, + enum sci_remote_node_suspension_reasons suspend_reason, + u32 suspend_type) +{ + enum scis_sds_remote_node_context_states state + = sci_rnc->sm.current_state_id; + struct isci_remote_device *idev = rnc_to_dev(sci_rnc); + enum sci_status status = SCI_FAILURE_INVALID_STATE; + enum sci_remote_node_context_destination_state dest_param = + RNC_DEST_UNSPECIFIED; + + dev_dbg(scirdev_to_dev(idev), + "%s: current state %s, current suspend_type %x dest state %d," + " arg suspend_reason %d, arg suspend_type %x", + __func__, rnc_state_name(state), sci_rnc->suspend_type, + sci_rnc->destination_state, suspend_reason, + suspend_type); + + /* Disable automatic state continuations if explicitly suspending. */ + if ((suspend_reason == SCI_HW_SUSPEND) || + (sci_rnc->destination_state == RNC_DEST_FINAL)) + dest_param = sci_rnc->destination_state; + + switch (state) { + case SCI_RNC_READY: + break; + case SCI_RNC_INVALIDATING: + if (sci_rnc->destination_state == RNC_DEST_FINAL) { + dev_warn(scirdev_to_dev(idev), + "%s: already destroying %p\n", + __func__, sci_rnc); + return SCI_FAILURE_INVALID_STATE; + } + fallthrough; /* and handle like SCI_RNC_POSTING */ + case SCI_RNC_RESUMING: + fallthrough; /* and handle like SCI_RNC_POSTING */ + case SCI_RNC_POSTING: + /* Set the destination state to AWAIT - this signals the + * entry into the SCI_RNC_READY state that a suspension + * needs to be done immediately. + */ + if (sci_rnc->destination_state != RNC_DEST_FINAL) + sci_rnc->destination_state = RNC_DEST_SUSPENDED; + sci_rnc->suspend_type = suspend_type; + sci_rnc->suspend_reason = suspend_reason; + return SCI_SUCCESS; + + case SCI_RNC_TX_SUSPENDED: + if (suspend_type == SCU_EVENT_TL_RNC_SUSPEND_TX) + status = SCI_SUCCESS; + break; + case SCI_RNC_TX_RX_SUSPENDED: + if (suspend_type == SCU_EVENT_TL_RNC_SUSPEND_TX_RX) + status = SCI_SUCCESS; + break; + case SCI_RNC_AWAIT_SUSPENSION: + if ((sci_rnc->suspend_type == SCU_EVENT_TL_RNC_SUSPEND_TX_RX) + || (suspend_type == sci_rnc->suspend_type)) + return SCI_SUCCESS; + break; + default: + dev_warn(scirdev_to_dev(rnc_to_dev(sci_rnc)), + "%s: invalid state %s\n", __func__, + rnc_state_name(state)); + return SCI_FAILURE_INVALID_STATE; + } + sci_rnc->destination_state = dest_param; + sci_rnc->suspend_type = suspend_type; + sci_rnc->suspend_reason = suspend_reason; + + if (status == SCI_SUCCESS) { /* Already in the destination state? */ + struct isci_host *ihost = idev->owning_port->owning_controller; + + wake_up_all(&ihost->eventq); /* Let observers look. */ + return SCI_SUCCESS; + } + if ((suspend_reason == SCI_SW_SUSPEND_NORMAL) || + (suspend_reason == SCI_SW_SUSPEND_LINKHANG_DETECT)) { + + if (suspend_reason == SCI_SW_SUSPEND_LINKHANG_DETECT) + isci_dev_set_hang_detection_timeout(idev, 0x00000001); + + sci_remote_device_post_request( + idev, SCI_SOFTWARE_SUSPEND_CMD); + } + if (state != SCI_RNC_AWAIT_SUSPENSION) + sci_change_state(&sci_rnc->sm, SCI_RNC_AWAIT_SUSPENSION); + + return SCI_SUCCESS; +} + +enum sci_status sci_remote_node_context_resume(struct sci_remote_node_context *sci_rnc, + scics_sds_remote_node_context_callback cb_fn, + void *cb_p) +{ + enum scis_sds_remote_node_context_states state; + struct isci_remote_device *idev = rnc_to_dev(sci_rnc); + + state = sci_rnc->sm.current_state_id; + dev_dbg(scirdev_to_dev(idev), + "%s: state %s, cb_fn = %p, cb_p = %p; dest_state = %d; " + "dev resume path %s\n", + __func__, rnc_state_name(state), cb_fn, cb_p, + sci_rnc->destination_state, + test_bit(IDEV_ABORT_PATH_ACTIVE, &idev->flags) + ? "<abort active>" : "<normal>"); + + switch (state) { + case SCI_RNC_INITIAL: + if (sci_rnc->remote_node_index == SCIC_SDS_REMOTE_NODE_CONTEXT_INVALID_INDEX) + return SCI_FAILURE_INVALID_STATE; + + sci_remote_node_context_setup_to_resume(sci_rnc, cb_fn, cb_p, + RNC_DEST_READY); + if (!test_bit(IDEV_ABORT_PATH_ACTIVE, &idev->flags)) { + sci_remote_node_context_construct_buffer(sci_rnc); + sci_change_state(&sci_rnc->sm, SCI_RNC_POSTING); + } + return SCI_SUCCESS; + + case SCI_RNC_POSTING: + case SCI_RNC_INVALIDATING: + case SCI_RNC_RESUMING: + /* We are still waiting to post when a resume was + * requested. + */ + switch (sci_rnc->destination_state) { + case RNC_DEST_SUSPENDED: + case RNC_DEST_SUSPENDED_RESUME: + /* Previously waiting to suspend after posting. + * Now continue onto resumption. + */ + sci_remote_node_context_setup_to_resume( + sci_rnc, cb_fn, cb_p, + RNC_DEST_SUSPENDED_RESUME); + break; + default: + sci_remote_node_context_setup_to_resume( + sci_rnc, cb_fn, cb_p, + RNC_DEST_READY); + break; + } + return SCI_SUCCESS; + + case SCI_RNC_TX_SUSPENDED: + case SCI_RNC_TX_RX_SUSPENDED: + { + struct domain_device *dev = idev->domain_dev; + /* If this is an expander attached SATA device we must + * invalidate and repost the RNC since this is the only + * way to clear the TCi to NCQ tag mapping table for + * the RNi. All other device types we can just resume. + */ + sci_remote_node_context_setup_to_resume( + sci_rnc, cb_fn, cb_p, RNC_DEST_READY); + + if (!test_bit(IDEV_ABORT_PATH_ACTIVE, &idev->flags)) { + if ((dev_is_sata(dev) && dev->parent) || + (sci_rnc->destination_state == RNC_DEST_FINAL)) + sci_change_state(&sci_rnc->sm, + SCI_RNC_INVALIDATING); + else + sci_change_state(&sci_rnc->sm, + SCI_RNC_RESUMING); + } + } + return SCI_SUCCESS; + + case SCI_RNC_AWAIT_SUSPENSION: + sci_remote_node_context_setup_to_resume( + sci_rnc, cb_fn, cb_p, RNC_DEST_SUSPENDED_RESUME); + return SCI_SUCCESS; + default: + dev_warn(scirdev_to_dev(rnc_to_dev(sci_rnc)), + "%s: invalid state %s\n", __func__, + rnc_state_name(state)); + return SCI_FAILURE_INVALID_STATE; + } +} + +enum sci_status sci_remote_node_context_start_io(struct sci_remote_node_context *sci_rnc, + struct isci_request *ireq) +{ + enum scis_sds_remote_node_context_states state; + + state = sci_rnc->sm.current_state_id; + + switch (state) { + case SCI_RNC_READY: + return SCI_SUCCESS; + case SCI_RNC_TX_SUSPENDED: + case SCI_RNC_TX_RX_SUSPENDED: + case SCI_RNC_AWAIT_SUSPENSION: + dev_warn(scirdev_to_dev(rnc_to_dev(sci_rnc)), + "%s: invalid state %s\n", __func__, + rnc_state_name(state)); + return SCI_FAILURE_REMOTE_DEVICE_RESET_REQUIRED; + default: + dev_dbg(scirdev_to_dev(rnc_to_dev(sci_rnc)), + "%s: invalid state %s\n", __func__, + rnc_state_name(state)); + return SCI_FAILURE_INVALID_STATE; + } +} + +enum sci_status sci_remote_node_context_start_task( + struct sci_remote_node_context *sci_rnc, + struct isci_request *ireq, + scics_sds_remote_node_context_callback cb_fn, + void *cb_p) +{ + enum sci_status status = sci_remote_node_context_resume(sci_rnc, + cb_fn, cb_p); + if (status != SCI_SUCCESS) + dev_warn(scirdev_to_dev(rnc_to_dev(sci_rnc)), + "%s: resume failed: %d\n", __func__, status); + return status; +} + +int sci_remote_node_context_is_safe_to_abort( + struct sci_remote_node_context *sci_rnc) +{ + enum scis_sds_remote_node_context_states state; + + state = sci_rnc->sm.current_state_id; + switch (state) { + case SCI_RNC_INVALIDATING: + case SCI_RNC_TX_RX_SUSPENDED: + return 1; + case SCI_RNC_POSTING: + case SCI_RNC_RESUMING: + case SCI_RNC_READY: + case SCI_RNC_TX_SUSPENDED: + case SCI_RNC_AWAIT_SUSPENSION: + case SCI_RNC_INITIAL: + return 0; + default: + dev_warn(scirdev_to_dev(rnc_to_dev(sci_rnc)), + "%s: invalid state %d\n", __func__, state); + return 0; + } +} |