summaryrefslogblamecommitdiff
path: root/drivers/scsi/isci/remote_device.c
blob: 48556e47bb9df01a5b0d79254f9d9c2e92668ce9 (plain) (tree)




































































                                                                                

                                                                 

   
                                                                                                    
 

                                                          




                                                                   
                                                     
 
                                          



                                                                         








                                                      




















                                                                              
                                                              
































































                                                                             

                                                                                    

              
                                                                                  











                                                                       
                                                                         



                                        

                                                                            














































                                                                              
                                                                          
 

                                        
 




                                                                    
 

                                                                      


                            


                                                          
 
                    
 




                                                                             
                                                    

   
                                                              
 
                                                             

                            

                                                          
 




                                                                            





































                                                                               










                                                                   



























                                                                             
                                                                                                 


                               
 

                                                          
 

                                                             
 
                                                    
                                                               
                                                         

                                                  
                                    
                                                  

                                                        
 


                                                          
 








                                                                              
                                                       
 

                                                         
 
                                  
                                                                             
                                                      
 
                                             





















                                                                               





                                                                  

                                  















                                                                           

                               










                                                                       
                                                         
                                                                      

                                                                      














                                                                              


                                                      



























































































































                                                                               



                                                                
/*
 * 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 "isci.h"
#include "scic_io_request.h"
#include "scic_remote_device.h"
#include "scic_phy.h"
#include "scic_port.h"
#include "port.h"
#include "remote_device.h"
#include "request.h"
#include "task.h"



/**
 * isci_remote_device_deconstruct() - This function frees an isci_remote_device.
 * @ihost: This parameter specifies the isci host object.
 * @idev: This parameter specifies the remote device to be freed.
 *
 */
static void isci_remote_device_deconstruct(struct isci_host *ihost, struct isci_remote_device *idev)
{
	dev_dbg(&ihost->pdev->dev,
		"%s: isci_device = %p\n", __func__, idev);

	/* There should not be any outstanding io's. All paths to
	 * here should go through isci_remote_device_nuke_requests.
	 * If we hit this condition, we will need a way to complete
	 * io requests in process */
	while (!list_empty(&idev->reqs_in_process)) {

		dev_err(&ihost->pdev->dev,
			"%s: ** request list not empty! **\n", __func__);
		BUG();
	}

	scic_remote_device_destruct(to_sci_dev(idev));
	idev->domain_dev->lldd_dev = NULL;
	idev->domain_dev = NULL;
	idev->isci_port = NULL;
	list_del_init(&idev->node);

	clear_bit(IDEV_START_PENDING, &idev->flags);
	clear_bit(IDEV_STOP_PENDING, &idev->flags);
	wake_up(&ihost->eventq);
}


/**
 * isci_remote_device_construct() - This function calls the scic remote device
 *    construct and start functions, it waits on the remote device start
 *    completion.
 * @port: This parameter specifies the isci port with the remote device.
 * @isci_device: This parameter specifies the isci remote device
 *
 * status from the scic calls, the caller to this function should clean up
 * resources as appropriate.
 */
static enum sci_status isci_remote_device_construct(
	struct isci_port *port,
	struct isci_remote_device *isci_device)
{
	enum sci_status status = SCI_SUCCESS;

	/* let the core do it's common constuction. */
	scic_remote_device_construct(port->sci_port_handle,
				     to_sci_dev(isci_device));

	/* let the core do it's device specific constuction. */
	if (isci_device->domain_dev->parent &&
	    (isci_device->domain_dev->parent->dev_type == EDGE_DEV)) {
		int i;

		/* struct smp_response_discover discover_response; */
		struct discover_resp discover_response;
		struct domain_device *parent =
			isci_device->domain_dev->parent;

		struct expander_device *parent_ex = &parent->ex_dev;

		for (i = 0; i < parent_ex->num_phys; i++) {

			struct ex_phy *phy = &parent_ex->ex_phy[i];

			if ((phy->phy_state == PHY_VACANT) ||
			    (phy->phy_state == PHY_NOT_PRESENT))
				continue;

			if (SAS_ADDR(phy->attached_sas_addr)
			    == SAS_ADDR(isci_device->domain_dev->sas_addr)) {

				discover_response.attached_dev_type
					= phy->attached_dev_type;
				discover_response.linkrate
					= phy->linkrate;
				discover_response.attached_sata_host
					= phy->attached_sata_host;
				discover_response.attached_sata_dev
					= phy->attached_sata_dev;
				discover_response.attached_sata_ps
					= phy->attached_sata_ps;
				discover_response.iproto
					= phy->attached_iproto >> 1;
				discover_response.tproto
					= phy->attached_tproto >> 1;
				memcpy(
					discover_response.attached_sas_addr,
					phy->attached_sas_addr,
					SAS_ADDR_SIZE
					);
				discover_response.attached_phy_id
					= phy->attached_phy_id;
				discover_response.change_count
					= phy->phy_change_count;
				discover_response.routing_attr
					= phy->routing_attr;
				discover_response.hmin_linkrate
					= phy->phy->minimum_linkrate_hw;
				discover_response.hmax_linkrate
					= phy->phy->maximum_linkrate_hw;
				discover_response.pmin_linkrate
					= phy->phy->minimum_linkrate;
				discover_response.pmax_linkrate
					= phy->phy->maximum_linkrate;
			}
		}


		dev_dbg(&port->isci_host->pdev->dev,
			"%s: parent->dev_type = EDGE_DEV\n",
			__func__);

		status = scic_remote_device_ea_construct(to_sci_dev(isci_device),
				(struct smp_response_discover *)&discover_response);

	} else
		status = scic_remote_device_da_construct(to_sci_dev(isci_device));


	if (status != SCI_SUCCESS) {
		dev_dbg(&port->isci_host->pdev->dev,
			"%s: scic_remote_device_da_construct failed - "
			"isci_device = %p\n",
			__func__,
			isci_device);

		return status;
	}

	sci_object_set_association(to_sci_dev(isci_device), isci_device);

	BUG_ON(port->isci_host == NULL);

	/* start the device. */
	status = scic_remote_device_start(to_sci_dev(isci_device),
					  ISCI_REMOTE_DEVICE_START_TIMEOUT);

	if (status != SCI_SUCCESS) {
		dev_warn(&port->isci_host->pdev->dev,
			 "%s: scic_remote_device_start failed\n",
			 __func__);
		return status;
	}

	return status;
}


/**
 * isci_remote_device_nuke_requests() - This function terminates all requests
 *    for a given remote device.
 * @isci_device: This parameter specifies the remote device
 *
 */
void isci_remote_device_nuke_requests(
	struct isci_remote_device *isci_device)
{
	DECLARE_COMPLETION_ONSTACK(aborted_task_completion);
	struct isci_host *isci_host;

	isci_host = isci_device->isci_port->isci_host;

	dev_dbg(&isci_host->pdev->dev,
		"%s: isci_device = %p\n", __func__, isci_device);

	/* Cleanup all requests pending for this device. */
	isci_terminate_pending_requests(isci_host, isci_device, terminating);

	dev_dbg(&isci_host->pdev->dev,
		"%s: isci_device = %p, done\n", __func__, isci_device);
}



/**
 * This function builds the isci_remote_device when a libsas dev_found message
 *    is received.
 * @isci_host: This parameter specifies the isci host object.
 * @port: This parameter specifies the isci_port conected to this device.
 *
 * pointer to new isci_remote_device.
 */
static struct isci_remote_device *
isci_remote_device_alloc(struct isci_host *ihost, struct isci_port *iport)
{
	struct isci_remote_device *idev;
	int i;

	for (i = 0; i < SCI_MAX_REMOTE_DEVICES; i++) {
		idev = idev_by_id(ihost, i);
		if (!test_and_set_bit(IDEV_ALLOCATED, &idev->flags))
			break;
	}

	if (i >= SCI_MAX_REMOTE_DEVICES) {
		dev_warn(&ihost->pdev->dev, "%s: failed\n", __func__);
		return NULL;
	}

	BUG_ON(!list_empty(&idev->reqs_in_process));
	BUG_ON(!list_empty(&idev->node));
	isci_remote_device_change_state(idev, isci_freed);

	return idev;
}

/**
 * isci_remote_device_ready() - This function is called by the scic when the
 *    remote device is ready. We mark the isci device as ready and signal the
 *    waiting proccess.
 * @idev: This parameter specifies the remote device
 *
 */
void isci_remote_device_ready(struct isci_remote_device *idev)
{
	struct isci_host *ihost = idev->isci_port->isci_host;
	unsigned long flags;

	dev_dbg(&ihost->pdev->dev,
		"%s: isci_device = %p\n", __func__, idev);

	spin_lock_irqsave(&idev->isci_port->remote_device_lock, flags);
	isci_remote_device_change_state(idev, isci_ready_for_io);
	if (test_and_clear_bit(IDEV_START_PENDING, &idev->flags))
		wake_up(&ihost->eventq);
	spin_unlock_irqrestore(&idev->isci_port->remote_device_lock, flags);
}

/**
 * isci_remote_device_not_ready() - This function is called by the scic when
 *    the remote device is not ready. We mark the isci device as ready (not
 *    "ready_for_io") and signal the waiting proccess.
 * @isci_host: This parameter specifies the isci host object.
 * @isci_device: This parameter specifies the remote device
 *
 */
void isci_remote_device_not_ready(
	struct isci_remote_device *isci_device,
	u32 reason_code)
{
	dev_dbg(&isci_device->isci_port->isci_host->pdev->dev,
		"%s: isci_device = %p\n", __func__, isci_device);

	if (reason_code == SCIC_REMOTE_DEVICE_NOT_READY_STOP_REQUESTED)
		isci_remote_device_change_state(isci_device, isci_stopping);
	else
		/* device ready is actually a "not ready for io" state. */
		isci_remote_device_change_state(isci_device, isci_ready);
}

/**
 * isci_remote_device_stop_complete() - This function is called by the scic
 *    when the remote device stop has completed. We mark the isci device as not
 *    ready and remove the isci remote device.
 * @isci_host: This parameter specifies the isci host object.
 * @isci_device: This parameter specifies the remote device.
 * @status: This parameter specifies status of the completion.
 *
 */
void isci_remote_device_stop_complete(
	struct isci_host *isci_host,
	struct isci_remote_device *isci_device,
	enum sci_status status)
{
	dev_dbg(&isci_host->pdev->dev,
		"%s: complete isci_device = %p, status = 0x%x\n",
		__func__,
		isci_device,
		status);

	isci_remote_device_change_state(isci_device, isci_stopped);

	/* after stop, we can tear down resources. */
	isci_remote_device_deconstruct(isci_host, isci_device);

}

/**
 * isci_remote_device_start_complete() - This function is called by the scic
 *    when the remote device start has completed
 * @isci_host: This parameter specifies the isci host object.
 * @isci_device: This parameter specifies the remote device.
 * @status: This parameter specifies status of the completion.
 *
 */
void isci_remote_device_start_complete(
	struct isci_host *isci_host,
	struct isci_remote_device *isci_device,
	enum sci_status status)
{


}


/**
 * isci_remote_device_stop() - This function is called internally to stop the
 *    remote device.
 * @isci_host: This parameter specifies the isci host object.
 * @isci_device: This parameter specifies the remote device.
 *
 * The status of the scic request to stop.
 */
enum sci_status isci_remote_device_stop(struct isci_host *ihost, struct isci_remote_device *idev)
{
	enum sci_status status;
	unsigned long flags;

	dev_dbg(&ihost->pdev->dev,
		"%s: isci_device = %p\n", __func__, idev);

	isci_remote_device_change_state(idev, isci_stopping);
	set_bit(IDEV_STOP_PENDING, &idev->flags);

	spin_lock_irqsave(&ihost->scic_lock, flags);
	status = scic_remote_device_stop(to_sci_dev(idev), 50);
	spin_unlock_irqrestore(&ihost->scic_lock, flags);

	/* Wait for the stop complete callback. */
	if (status == SCI_SUCCESS) {
		wait_for_device_stop(ihost, idev);
		clear_bit(IDEV_ALLOCATED, &idev->flags);
	}

	dev_dbg(&ihost->pdev->dev,
		"%s: idev = %p - after completion wait\n",
		__func__, idev);

	return status;
}

/**
 * isci_remote_device_gone() - This function is called by libsas when a domain
 *    device is removed.
 * @domain_device: This parameter specifies the libsas domain device.
 *
 */
void isci_remote_device_gone(struct domain_device *dev)
{
	struct isci_host *ihost = dev->port->ha->lldd_ha;
	struct isci_remote_device *idev = dev->lldd_dev;

	dev_dbg(&ihost->pdev->dev,
		"%s: domain_device = %p, isci_device = %p, isci_port = %p\n",
		__func__, dev, idev, idev->isci_port);

	isci_remote_device_stop(ihost, idev);
}


/**
 * isci_remote_device_found() - This function is called by libsas when a remote
 *    device is discovered. A remote device object is created and started. the
 *    function then sleeps until the sci core device started message is
 *    received.
 * @domain_device: This parameter specifies the libsas domain device.
 *
 * status, zero indicates success.
 */
int isci_remote_device_found(struct domain_device *domain_dev)
{
	unsigned long flags;
	struct isci_host *isci_host;
	struct isci_port *isci_port;
	struct isci_phy *isci_phy;
	struct asd_sas_port *sas_port;
	struct asd_sas_phy *sas_phy;
	struct isci_remote_device *isci_device;
	enum sci_status status;

	isci_host = isci_host_from_sas_ha(domain_dev->port->ha);

	dev_dbg(&isci_host->pdev->dev,
		"%s: domain_device = %p\n", __func__, domain_dev);

	wait_for_start(isci_host);

	sas_port = domain_dev->port;
	sas_phy = list_first_entry(&sas_port->phy_list, struct asd_sas_phy,
				   port_phy_el);
	isci_phy = to_isci_phy(sas_phy);
	isci_port = isci_phy->isci_port;

	/* we are being called for a device on this port,
	 * so it has to come up eventually
	 */
	wait_for_completion(&isci_port->start_complete);

	if ((isci_stopping == isci_port_get_state(isci_port)) ||
	    (isci_stopped == isci_port_get_state(isci_port)))
		return -ENODEV;

	isci_device = isci_remote_device_alloc(isci_host, isci_port);
	if (!isci_device)
		return -ENODEV;

	INIT_LIST_HEAD(&isci_device->node);
	domain_dev->lldd_dev = isci_device;
	isci_device->domain_dev = domain_dev;
	isci_device->isci_port = isci_port;
	isci_remote_device_change_state(isci_device, isci_starting);


	spin_lock_irqsave(&isci_port->remote_device_lock, flags);
	list_add_tail(&isci_device->node, &isci_port->remote_dev_list);

	set_bit(IDEV_START_PENDING, &isci_device->flags);
	status = isci_remote_device_construct(isci_port, isci_device);
	spin_unlock_irqrestore(&isci_port->remote_device_lock, flags);

	dev_dbg(&isci_host->pdev->dev,
		"%s: isci_device = %p\n",
		__func__, isci_device);

	if (status != SCI_SUCCESS) {

		spin_lock_irqsave(&isci_port->remote_device_lock, flags);
		isci_remote_device_deconstruct(
			isci_host,
			isci_device
			);
		spin_unlock_irqrestore(&isci_port->remote_device_lock, flags);
		return -ENODEV;
	}

	/* wait for the device ready callback. */
	wait_for_device_start(isci_host, isci_device);

	return 0;
}
/**
 * isci_device_is_reset_pending() - This function will check if there is any
 *    pending reset condition on the device.
 * @request: This parameter is the isci_device object.
 *
 * true if there is a reset pending for the device.
 */
bool isci_device_is_reset_pending(
	struct isci_host *isci_host,
	struct isci_remote_device *isci_device)
{
	struct isci_request *isci_request;
	struct isci_request *tmp_req;
	bool reset_is_pending = false;
	unsigned long flags;

	dev_dbg(&isci_host->pdev->dev,
		"%s: isci_device = %p\n", __func__, isci_device);

	spin_lock_irqsave(&isci_host->scic_lock, flags);

	/* Check for reset on all pending requests. */
	list_for_each_entry_safe(isci_request, tmp_req,
				 &isci_device->reqs_in_process, dev_node) {
		dev_dbg(&isci_host->pdev->dev,
			"%s: isci_device = %p request = %p\n",
			__func__, isci_device, isci_request);

		if (isci_request->ttype == io_task) {

			unsigned long flags;
			struct sas_task *task = isci_request_access_task(
				isci_request);

			spin_lock_irqsave(&task->task_state_lock, flags);
			if (task->task_state_flags & SAS_TASK_NEED_DEV_RESET)
				reset_is_pending = true;
			spin_unlock_irqrestore(&task->task_state_lock, flags);
		}
	}

	spin_unlock_irqrestore(&isci_host->scic_lock, flags);

	dev_dbg(&isci_host->pdev->dev,
		"%s: isci_device = %p reset_is_pending = %d\n",
		__func__, isci_device, reset_is_pending);

	return reset_is_pending;
}

/**
 * isci_device_clear_reset_pending() - This function will clear if any pending
 *    reset condition flags on the device.
 * @request: This parameter is the isci_device object.
 *
 * true if there is a reset pending for the device.
 */
void isci_device_clear_reset_pending(struct isci_remote_device *isci_device)
{
	struct isci_request *isci_request;
	struct isci_request *tmp_req;
	struct isci_host *isci_host = NULL;
	unsigned long flags = 0;

	/* FIXME more port gone confusion, and this time it makes the
	 * locking "fun"
	 */
	if (isci_device->isci_port != NULL)
		isci_host = isci_device->isci_port->isci_host;

	/*
	 * FIXME when the isci_host gets sorted out
	 * use dev_dbg()
	 */
	pr_debug("%s: isci_device=%p, isci_host=%p\n",
		 __func__, isci_device, isci_host);

	if (isci_host != NULL)
		spin_lock_irqsave(&isci_host->scic_lock, flags);
	else
		pr_err("%s: isci_device %p; isci_host == NULL!\n",
		       __func__, isci_device);

	/* Clear reset pending on all pending requests. */
	list_for_each_entry_safe(isci_request, tmp_req,
				 &isci_device->reqs_in_process, dev_node) {
		/*
		 * FIXME when the conditional spinlock is gone
		 * change to dev_dbg()
		 */
		pr_debug("%s: isci_device = %p request = %p\n",
			 __func__, isci_device, isci_request);

		if (isci_request->ttype == io_task) {

			unsigned long flags2;
			struct sas_task *task = isci_request_access_task(
				isci_request);

			spin_lock_irqsave(&task->task_state_lock, flags2);
			task->task_state_flags &= ~SAS_TASK_NEED_DEV_RESET;
			spin_unlock_irqrestore(&task->task_state_lock, flags2);
		}
	}

	if (isci_host != NULL)
		spin_unlock_irqrestore(&isci_host->scic_lock, flags);
}

/**
 * isci_remote_device_change_state() - This function gets the status of the
 *    remote_device object.
 * @isci_device: This parameter points to the isci_remote_device object
 *
 * status of the object as a isci_status enum.
 */
void isci_remote_device_change_state(
	struct isci_remote_device *isci_device,
	enum isci_status status)
{
	unsigned long flags;

	spin_lock_irqsave(&isci_device->state_lock, flags);
	isci_device->status = status;
	spin_unlock_irqrestore(&isci_device->state_lock, flags);
}