diff options
Diffstat (limited to 'drivers/scsi/isci/port.c')
-rw-r--r-- | drivers/scsi/isci/port.c | 484 |
1 files changed, 484 insertions, 0 deletions
diff --git a/drivers/scsi/isci/port.c b/drivers/scsi/isci/port.c new file mode 100644 index 000000000000..2343f6556428 --- /dev/null +++ b/drivers/scsi/isci/port.c @@ -0,0 +1,484 @@ +/* + * 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. + */ + +/** + * This file contains the isci port implementation. + * + * + */ + + +#include <linux/workqueue.h> +#include "isci.h" +#include "scic_io_request.h" +#include "scic_remote_device.h" +#include "scic_phy.h" +#include "scic_sds_phy.h" +#include "scic_port.h" +#include "port.h" +#include "request.h" + +static void isci_port_change_state( + struct isci_port *isci_port, + enum isci_status status); + + + +/** + * isci_port_init() - This function initializes the given isci_port object. + * @isci_port: This parameter specifies the port object to be initialized. + * @isci_host: This parameter specifies parent controller object for the port. + * @index: This parameter specifies which SCU port the isci_port associates + * with. Generally, SCU port 0 relates to isci_port 0, etc. + * + */ +void isci_port_init( + struct isci_port *isci_port, + struct isci_host *isci_host, + int index) +{ + struct scic_sds_port *scic_port; + struct scic_sds_controller *controller = isci_host->core_controller; + + INIT_LIST_HEAD(&isci_port->remote_dev_list); + INIT_LIST_HEAD(&isci_port->domain_dev_list); + spin_lock_init(&isci_port->remote_device_lock); + spin_lock_init(&isci_port->state_lock); + init_completion(&isci_port->start_complete); + isci_port->isci_host = isci_host; + isci_port_change_state(isci_port, isci_freed); + + (void)scic_controller_get_port_handle(controller, index, &scic_port); + sci_object_set_association(scic_port, isci_port); + isci_port->sci_port_handle = scic_port; +} + + +/** + * isci_port_get_state() - This function gets the status of the port object. + * @isci_port: This parameter points to the isci_port object + * + * status of the object as a isci_status enum. + */ +enum isci_status isci_port_get_state( + struct isci_port *isci_port) +{ + return isci_port->status; +} + +static void isci_port_change_state( + struct isci_port *isci_port, + enum isci_status status) +{ + unsigned long flags; + + dev_dbg(&isci_port->isci_host->pdev->dev, + "%s: isci_port = %p, state = 0x%x\n", + __func__, isci_port, status); + + spin_lock_irqsave(&isci_port->state_lock, flags); + isci_port->status = status; + spin_unlock_irqrestore(&isci_port->state_lock, flags); +} + +void isci_port_bc_change_received( + struct isci_host *isci_host, + struct scic_sds_port *port, + struct scic_sds_phy *phy) +{ + struct isci_phy *isci_phy = + (struct isci_phy *)sci_object_get_association(phy); + + dev_dbg(&isci_host->pdev->dev, + "%s: isci_phy = %p, sas_phy = %p\n", + __func__, + isci_phy, + &isci_phy->sas_phy); + + isci_host->sas_ha.notify_port_event( + &isci_phy->sas_phy, + PORTE_BROADCAST_RCVD + ); + + scic_port_enable_broadcast_change_notification(port); +} + +/** + * isci_port_link_up() - This function is called by the sci core when a link + * becomes active. the identify address frame is retrieved from the core and + * a notify port event is sent to libsas. + * @isci_host: This parameter specifies the isci host object. + * @port: This parameter specifies the sci port with the active link. + * @phy: This parameter specifies the sci phy with the active link. + * + */ +void isci_port_link_up( + struct isci_host *isci_host, + struct scic_sds_port *port, + struct scic_sds_phy *phy) +{ + unsigned long flags; + struct scic_port_properties properties; + struct isci_phy *isci_phy + = (struct isci_phy *)sci_object_get_association(phy); + struct isci_port *isci_port + = (struct isci_port *)sci_object_get_association(port); + enum sci_status call_status; + unsigned long success = true; + + BUG_ON(isci_phy->isci_port != NULL); + isci_phy->isci_port = isci_port; + + dev_dbg(&isci_host->pdev->dev, + "%s: isci_port = %p\n", + __func__, isci_port); + + spin_lock_irqsave(&isci_phy->sas_phy.frame_rcvd_lock, flags); + + isci_port_change_state(isci_phy->isci_port, isci_starting); + + scic_port_get_properties(port, &properties); + + if (properties.remote.protocols.u.bits.stp_target) { + + struct scic_sata_phy_properties sata_phy_properties; + + isci_phy->sas_phy.oob_mode = SATA_OOB_MODE; + + /* Get a copy of the signature fis for libsas */ + call_status = scic_sata_phy_get_properties(phy, + &sata_phy_properties); + + /* + * XXX I am concerned about this "assert". shouldn't we + * handle the return appropriately? + */ + BUG_ON(call_status != SCI_SUCCESS); + + memcpy(isci_phy->frame_rcvd.fis, + &sata_phy_properties.signature_fis, + sizeof(struct sata_fis_reg_d2h)); + + isci_phy->sas_phy.frame_rcvd_size = sizeof(struct sata_fis_reg_d2h); + + /* + * For direct-attached SATA devices, the SCI core will + * automagically assign a SAS address to the end device + * for the purpose of creating a port. This SAS address + * will not be the same as assigned to the PHY and needs + * to be obtained from struct scic_port_properties properties. + */ + + BUG_ON(((size_t)SAS_ADDR_SIZE / 2) + != sizeof(properties.remote.sas_address.low)); + + memcpy(&isci_phy->sas_phy.attached_sas_addr[0], + &properties.remote.sas_address.low, + SAS_ADDR_SIZE / 2); + + memcpy(&isci_phy->sas_phy.attached_sas_addr[4], + &properties.remote.sas_address.high, + SAS_ADDR_SIZE / 2); + + } else if (properties.remote.protocols.u.bits.ssp_target || + properties.remote.protocols.u.bits.smp_target) { + + struct scic_sas_phy_properties sas_phy_properties; + + isci_phy->sas_phy.oob_mode = SAS_OOB_MODE; + + /* Get a copy of the identify address frame for libsas */ + call_status = scic_sas_phy_get_properties(phy, + &sas_phy_properties); + + BUG_ON(call_status != SCI_SUCCESS); + + memcpy(isci_phy->frame_rcvd.aif, + &(sas_phy_properties.received_iaf), + sizeof(struct sci_sas_identify_address_frame)); + + isci_phy->sas_phy.frame_rcvd_size + = sizeof(struct sci_sas_identify_address_frame); + + /* Copy the attached SAS address from the IAF */ + memcpy(isci_phy->sas_phy.attached_sas_addr, + ((struct sas_identify_frame *) + (&isci_phy->frame_rcvd.aif))->sas_addr, + SAS_ADDR_SIZE); + + } else { + dev_err(&isci_host->pdev->dev, "%s: unkown target\n", __func__); + success = false; + } + + spin_unlock_irqrestore(&isci_phy->sas_phy.frame_rcvd_lock, flags); + + /* Notify libsas that we have an address frame, if indeed + * we've found an SSP, SMP, or STP target */ + if (success) + isci_host->sas_ha.notify_port_event(&isci_phy->sas_phy, + PORTE_BYTES_DMAED); +} + + +/** + * isci_port_link_down() - This function is called by the sci core when a link + * becomes inactive. + * @isci_host: This parameter specifies the isci host object. + * @phy: This parameter specifies the isci phy with the active link. + * @port: This parameter specifies the isci port with the active link. + * + */ +void isci_port_link_down( + struct isci_host *isci_host, + struct isci_phy *isci_phy, + struct isci_port *isci_port) +{ + struct isci_remote_device *isci_device; + + dev_dbg(&isci_host->pdev->dev, + "%s: isci_port = %p\n", __func__, isci_port); + + if (isci_port) { + + /* check to see if this is the last phy on this port. */ + if (isci_phy->sas_phy.port + && isci_phy->sas_phy.port->num_phys == 1) { + + /* change the state for all devices on this port. + * The next task sent to this device will be returned + * as SAS_TASK_UNDELIVERED, and the scsi mid layer + * will remove the target + */ + list_for_each_entry(isci_device, + &isci_port->remote_dev_list, + node) { + dev_dbg(&isci_host->pdev->dev, + "%s: isci_device = %p\n", + __func__, isci_device); + isci_remote_device_change_state(isci_device, + isci_stopping); + } + } + isci_port_change_state(isci_port, isci_stopping); + } + + /* Notify libsas of the borken link, this will trigger calls to our + * isci_port_deformed and isci_dev_gone functions. + */ + sas_phy_disconnected(&isci_phy->sas_phy); + isci_host->sas_ha.notify_phy_event(&isci_phy->sas_phy, + PHYE_LOSS_OF_SIGNAL); + + isci_phy->isci_port = NULL; + + dev_dbg(&isci_host->pdev->dev, + "%s: isci_port = %p - Done\n", __func__, isci_port); +} + + +/** + * isci_port_deformed() - This function is called by libsas when a port becomes + * inactive. + * @phy: This parameter specifies the libsas phy with the inactive port. + * + */ +void isci_port_deformed( + struct asd_sas_phy *phy) +{ + pr_debug("%s: sas_phy = %p\n", __func__, phy); +} + +/** + * isci_port_formed() - This function is called by libsas when a port becomes + * active. + * @phy: This parameter specifies the libsas phy with the active port. + * + */ +void isci_port_formed( + struct asd_sas_phy *phy) +{ + pr_debug("%s: sas_phy = %p, sas_port = %p\n", __func__, phy, phy->port); +} + +/** + * isci_port_ready() - This function is called by the sci core when a link + * becomes ready. + * @isci_host: This parameter specifies the isci host object. + * @port: This parameter specifies the sci port with the active link. + * + */ +void isci_port_ready( + struct isci_host *isci_host, + struct isci_port *isci_port) +{ + dev_dbg(&isci_host->pdev->dev, + "%s: isci_port = %p\n", __func__, isci_port); + + complete_all(&isci_port->start_complete); + isci_port_change_state(isci_port, isci_ready); + return; +} + +/** + * isci_port_not_ready() - This function is called by the sci core when a link + * is not ready. All remote devices on this link will be removed if they are + * in the stopping state. + * @isci_host: This parameter specifies the isci host object. + * @port: This parameter specifies the sci port with the active link. + * + */ +void isci_port_not_ready( + struct isci_host *isci_host, + struct isci_port *isci_port) +{ + dev_dbg(&isci_host->pdev->dev, + "%s: isci_port = %p\n", __func__, isci_port); +} + +/** + * isci_port_hard_reset_complete() - This function is called by the sci core + * when the hard reset complete notification has been received. + * @port: This parameter specifies the sci port with the active link. + * @completion_status: This parameter specifies the core status for the reset + * process. + * + */ +void isci_port_hard_reset_complete( + struct isci_port *isci_port, + enum sci_status completion_status) +{ + dev_dbg(&isci_port->isci_host->pdev->dev, + "%s: isci_port = %p, completion_status=%x\n", + __func__, isci_port, completion_status); + + /* Save the status of the hard reset from the port. */ + isci_port->hard_reset_status = completion_status; + + complete_all(&isci_port->hard_reset_complete); +} +/** + * isci_port_perform_hard_reset() - This function is one of the SAS Domain + * Template functions. This is a phy management function. + * @isci_port: + * @isci_phy: + * + * status, TMF_RESP_FUNC_COMPLETE indicates success. + */ +int isci_port_perform_hard_reset( + struct isci_port *isci_port, + struct isci_phy *isci_phy) +{ + enum sci_status status; + int ret = TMF_RESP_FUNC_COMPLETE; + unsigned long flags; + + + dev_dbg(&isci_port->isci_host->pdev->dev, + "%s: isci_port = %p\n", + __func__, isci_port); + + BUG_ON(isci_port == NULL); + + init_completion(&isci_port->hard_reset_complete); + + spin_lock_irqsave(&isci_port->isci_host->scic_lock, flags); + + #define ISCI_PORT_RESET_TIMEOUT SCIC_SDS_SIGNATURE_FIS_TIMEOUT + status = scic_port_hard_reset(isci_port->sci_port_handle, + ISCI_PORT_RESET_TIMEOUT); + + spin_unlock_irqrestore(&isci_port->isci_host->scic_lock, flags); + + if (status == SCI_SUCCESS) { + wait_for_completion(&isci_port->hard_reset_complete); + + dev_dbg(&isci_port->isci_host->pdev->dev, + "%s: isci_port = %p; hard reset completion\n", + __func__, isci_port); + + if (isci_port->hard_reset_status != SCI_SUCCESS) + ret = TMF_RESP_FUNC_FAILED; + } else { + ret = TMF_RESP_FUNC_FAILED; + + dev_err(&isci_port->isci_host->pdev->dev, + "%s: isci_port = %p; scic_port_hard_reset call" + " failed 0x%x\n", + __func__, isci_port, status); + + } + + /* If the hard reset for the port has failed, consider this + * the same as link failures on all phys in the port. + */ + if (ret != TMF_RESP_FUNC_COMPLETE) { + BUG_ON(isci_port->isci_host == NULL); + + dev_err(&isci_port->isci_host->pdev->dev, + "%s: isci_port = %p; hard reset failed " + "(0x%x) - sending link down to libsas for phy %p\n", + __func__, + isci_port, + isci_port->hard_reset_status, + isci_phy); + + isci_port_link_down(isci_port->isci_host, + isci_phy, + isci_port); + } + + return ret; +} |