root/drivers/scsi/mpi3mr/mpi3mr_transport.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Driver for Broadcom MPI3 Storage Controllers
 *
 * Copyright (C) 2017-2023 Broadcom Inc.
 *  (mailto: mpi3mr-linuxdrv.pdl@broadcom.com)
 *
 */

#include <linux/vmalloc.h>

#include "mpi3mr.h"

/**
 * mpi3mr_post_transport_req - Issue transport requests and wait
 * @mrioc: Adapter instance reference
 * @request: Properly populated MPI3 request
 * @request_sz: Size of the MPI3 request
 * @reply: Pointer to return MPI3 reply
 * @reply_sz: Size of the MPI3 reply buffer
 * @timeout: Timeout in seconds
 * @ioc_status: Pointer to return ioc status
 *
 * A generic function for posting MPI3 requests from the SAS
 * transport layer that uses transport command infrastructure.
 * This blocks for the completion of request for timeout seconds
 * and if the request times out this function faults the
 * controller with proper reason code.
 *
 * On successful completion of the request this function returns
 * appropriate ioc status from the firmware back to the caller.
 *
 * Return: 0 on success, non-zero on failure.
 */
static int mpi3mr_post_transport_req(struct mpi3mr_ioc *mrioc, void *request,
        u16 request_sz, void *reply, u16 reply_sz, int timeout,
        u16 *ioc_status)
{
        int retval = 0;

        mutex_lock(&mrioc->transport_cmds.mutex);
        if (mrioc->transport_cmds.state & MPI3MR_CMD_PENDING) {
                retval = -1;
                ioc_err(mrioc, "sending transport request failed due to command in use\n");
                mutex_unlock(&mrioc->transport_cmds.mutex);
                goto out;
        }
        mrioc->transport_cmds.state = MPI3MR_CMD_PENDING;
        mrioc->transport_cmds.is_waiting = 1;
        mrioc->transport_cmds.callback = NULL;
        mrioc->transport_cmds.ioc_status = 0;
        mrioc->transport_cmds.ioc_loginfo = 0;

        init_completion(&mrioc->transport_cmds.done);
        dprint_cfg_info(mrioc, "posting transport request\n");
        if (mrioc->logging_level & MPI3_DEBUG_TRANSPORT_INFO)
                dprint_dump(request, request_sz, "transport_req");
        retval = mpi3mr_admin_request_post(mrioc, request, request_sz, 1);
        if (retval) {
                ioc_err(mrioc, "posting transport request failed\n");
                goto out_unlock;
        }
        wait_for_completion_timeout(&mrioc->transport_cmds.done,
            (timeout * HZ));
        if (!(mrioc->transport_cmds.state & MPI3MR_CMD_COMPLETE)) {
                mpi3mr_check_rh_fault_ioc(mrioc,
                    MPI3MR_RESET_FROM_SAS_TRANSPORT_TIMEOUT);
                ioc_err(mrioc, "transport request timed out\n");
                retval = -1;
                goto out_unlock;
        }
        *ioc_status = mrioc->transport_cmds.ioc_status &
                MPI3_IOCSTATUS_STATUS_MASK;
        if ((*ioc_status) != MPI3_IOCSTATUS_SUCCESS)
                dprint_transport_err(mrioc,
                    "transport request returned with ioc_status(0x%04x), log_info(0x%08x)\n",
                    *ioc_status, mrioc->transport_cmds.ioc_loginfo);

        if ((reply) && (mrioc->transport_cmds.state & MPI3MR_CMD_REPLY_VALID))
                memcpy((u8 *)reply, mrioc->transport_cmds.reply, reply_sz);

out_unlock:
        mrioc->transport_cmds.state = MPI3MR_CMD_NOTUSED;
        mutex_unlock(&mrioc->transport_cmds.mutex);

out:
        return retval;
}

/* report manufacture request structure */
struct rep_manu_request {
        u8 smp_frame_type;
        u8 function;
        u8 reserved;
        u8 request_length;
};

/* report manufacture reply structure */
struct rep_manu_reply {
        u8 smp_frame_type; /* 0x41 */
        u8 function; /* 0x01 */
        u8 function_result;
        u8 response_length;
        u16 expander_change_count;
        u8 reserved0[2];
        u8 sas_format;
        u8 reserved2[3];
        u8 vendor_id[SAS_EXPANDER_VENDOR_ID_LEN] __nonstring;
        u8 product_id[SAS_EXPANDER_PRODUCT_ID_LEN] __nonstring;
        u8 product_rev[SAS_EXPANDER_PRODUCT_REV_LEN] __nonstring;
        u8 component_vendor_id[SAS_EXPANDER_COMPONENT_VENDOR_ID_LEN] __nonstring;
        u16 component_id;
        u8 component_revision_id;
        u8 reserved3;
        u8 vendor_specific[8];
};

/**
 * mpi3mr_report_manufacture - obtain SMP report_manufacture
 * @mrioc: Adapter instance reference
 * @sas_address: SAS address of the expander device
 * @edev: SAS transport layer sas_expander_device object
 * @port_id: ID of the HBA port
 *
 * Fills in the sas_expander_device with manufacturing info.
 *
 * Return: 0 for success, non-zero for failure.
 */
static int mpi3mr_report_manufacture(struct mpi3mr_ioc *mrioc,
        u64 sas_address, struct sas_expander_device *edev, u8 port_id)
{
        struct mpi3_smp_passthrough_request mpi_request;
        struct mpi3_smp_passthrough_reply mpi_reply;
        struct rep_manu_reply *manufacture_reply;
        struct rep_manu_request *manufacture_request;
        int rc = 0;
        void *psge;
        void *data_out = NULL;
        dma_addr_t data_out_dma;
        dma_addr_t data_in_dma;
        size_t data_in_sz;
        size_t data_out_sz;
        u8 sgl_flags = MPI3MR_SGEFLAGS_SYSTEM_SIMPLE_END_OF_LIST;
        u16 request_sz = sizeof(struct mpi3_smp_passthrough_request);
        u16 reply_sz = sizeof(struct mpi3_smp_passthrough_reply);
        u16 ioc_status;
        u8 *tmp;

        if (mrioc->reset_in_progress) {
                ioc_err(mrioc, "%s: host reset in progress!\n", __func__);
                return -EFAULT;
        }

        if (mrioc->pci_err_recovery) {
                ioc_err(mrioc, "%s: pci error recovery in progress!\n", __func__);
                return -EFAULT;
        }

        data_out_sz = sizeof(struct rep_manu_request);
        data_in_sz = sizeof(struct rep_manu_reply);
        data_out = dma_alloc_coherent(&mrioc->pdev->dev,
            data_out_sz + data_in_sz, &data_out_dma, GFP_KERNEL);
        if (!data_out) {
                rc = -ENOMEM;
                goto out;
        }

        data_in_dma = data_out_dma + data_out_sz;
        manufacture_reply = data_out + data_out_sz;

        manufacture_request = data_out;
        manufacture_request->smp_frame_type = 0x40;
        manufacture_request->function = 1;
        manufacture_request->reserved = 0;
        manufacture_request->request_length = 0;

        memset(&mpi_request, 0, request_sz);
        memset(&mpi_reply, 0, reply_sz);
        mpi_request.host_tag = cpu_to_le16(MPI3MR_HOSTTAG_TRANSPORT_CMDS);
        mpi_request.function = MPI3_FUNCTION_SMP_PASSTHROUGH;
        mpi_request.io_unit_port = (u8) port_id;
        mpi_request.sas_address = cpu_to_le64(sas_address);

        psge = &mpi_request.request_sge;
        mpi3mr_add_sg_single(psge, sgl_flags, data_out_sz, data_out_dma);

        psge = &mpi_request.response_sge;
        mpi3mr_add_sg_single(psge, sgl_flags, data_in_sz, data_in_dma);

        dprint_transport_info(mrioc,
            "sending report manufacturer SMP request to sas_address(0x%016llx), port(%d)\n",
            (unsigned long long)sas_address, port_id);

        rc = mpi3mr_post_transport_req(mrioc, &mpi_request, request_sz,
                                       &mpi_reply, reply_sz,
                                       MPI3MR_INTADMCMD_TIMEOUT, &ioc_status);
        if (rc)
                goto out;

        dprint_transport_info(mrioc,
            "report manufacturer SMP request completed with ioc_status(0x%04x)\n",
            ioc_status);

        if (ioc_status != MPI3_IOCSTATUS_SUCCESS) {
                rc = -EINVAL;
                goto out;
        }

        dprint_transport_info(mrioc,
            "report manufacturer - reply data transfer size(%d)\n",
            le16_to_cpu(mpi_reply.response_data_length));

        if (le16_to_cpu(mpi_reply.response_data_length) !=
            sizeof(struct rep_manu_reply)) {
                rc = -EINVAL;
                goto out;
        }

        memtostr(edev->vendor_id, manufacture_reply->vendor_id);
        memtostr(edev->product_id, manufacture_reply->product_id);
        memtostr(edev->product_rev, manufacture_reply->product_rev);
        edev->level = manufacture_reply->sas_format & 1;
        if (edev->level) {
                memtostr(edev->component_vendor_id,
                         manufacture_reply->component_vendor_id);
                tmp = (u8 *)&manufacture_reply->component_id;
                edev->component_id = tmp[0] << 8 | tmp[1];
                edev->component_revision_id =
                    manufacture_reply->component_revision_id;
        }

out:
        if (data_out)
                dma_free_coherent(&mrioc->pdev->dev, data_out_sz + data_in_sz,
                    data_out, data_out_dma);

        return rc;
}

/**
 * __mpi3mr_expander_find_by_handle - expander search by handle
 * @mrioc: Adapter instance reference
 * @handle: Firmware device handle of the expander
 *
 * Context: The caller should acquire sas_node_lock
 *
 * This searches for expander device based on handle, then
 * returns the sas_node object.
 *
 * Return: Expander sas_node object reference or NULL
 */
struct mpi3mr_sas_node *__mpi3mr_expander_find_by_handle(struct mpi3mr_ioc
        *mrioc, u16 handle)
{
        struct mpi3mr_sas_node *sas_expander, *r;

        r = NULL;
        list_for_each_entry(sas_expander, &mrioc->sas_expander_list, list) {
                if (sas_expander->handle != handle)
                        continue;
                r = sas_expander;
                goto out;
        }
 out:
        return r;
}

/**
 * mpi3mr_is_expander_device - if device is an expander
 * @device_info: Bitfield providing information about the device
 *
 * Return: 1 if the device is expander device, else 0.
 */
u8 mpi3mr_is_expander_device(u16 device_info)
{
        if ((device_info & MPI3_SAS_DEVICE_INFO_DEVICE_TYPE_MASK) ==
             MPI3_SAS_DEVICE_INFO_DEVICE_TYPE_EXPANDER)
                return 1;
        else
                return 0;
}

/**
 * mpi3mr_get_sas_address - retrieve sas_address for handle
 * @mrioc: Adapter instance reference
 * @handle: Firmware device handle
 * @sas_address: Address to hold sas address
 *
 * This function issues device page0 read for a given device
 * handle and gets the SAS address and return it back
 *
 * Return: 0 for success, non-zero for failure
 */
static int mpi3mr_get_sas_address(struct mpi3mr_ioc *mrioc, u16 handle,
        u64 *sas_address)
{
        struct mpi3_device_page0 dev_pg0;
        u16 ioc_status;
        struct mpi3_device0_sas_sata_format *sasinf;

        *sas_address = 0;

        if ((mpi3mr_cfg_get_dev_pg0(mrioc, &ioc_status, &dev_pg0,
            sizeof(dev_pg0), MPI3_DEVICE_PGAD_FORM_HANDLE,
            handle))) {
                ioc_err(mrioc, "%s: device page0 read failed\n", __func__);
                return -ENXIO;
        }

        if (ioc_status != MPI3_IOCSTATUS_SUCCESS) {
                ioc_err(mrioc, "device page read failed for handle(0x%04x), with ioc_status(0x%04x) failure at %s:%d/%s()!\n",
                    handle, ioc_status, __FILE__, __LINE__, __func__);
                return -ENXIO;
        }

        if (le16_to_cpu(dev_pg0.flags) &
            MPI3_DEVICE0_FLAGS_CONTROLLER_DEV_HANDLE)
                *sas_address = mrioc->sas_hba.sas_address;
        else if (dev_pg0.device_form == MPI3_DEVICE_DEVFORM_SAS_SATA) {
                sasinf = &dev_pg0.device_specific.sas_sata_format;
                *sas_address = le64_to_cpu(sasinf->sas_address);
        } else {
                ioc_err(mrioc, "%s: device_form(%d) is not SAS_SATA\n",
                    __func__, dev_pg0.device_form);
                return -ENXIO;
        }
        return 0;
}

/**
 * __mpi3mr_get_tgtdev_by_addr - target device search
 * @mrioc: Adapter instance reference
 * @sas_address: SAS address of the device
 * @hba_port: HBA port entry
 *
 * This searches for target device from sas address and hba port
 * pointer then return mpi3mr_tgt_dev object.
 *
 * Return: Valid tget_dev or NULL
 */
static struct mpi3mr_tgt_dev *__mpi3mr_get_tgtdev_by_addr(struct mpi3mr_ioc *mrioc,
        u64 sas_address, struct mpi3mr_hba_port *hba_port)
{
        struct mpi3mr_tgt_dev *tgtdev;

        assert_spin_locked(&mrioc->tgtdev_lock);

        list_for_each_entry(tgtdev, &mrioc->tgtdev_list, list)
                if ((tgtdev->dev_type == MPI3_DEVICE_DEVFORM_SAS_SATA) &&
                    (tgtdev->dev_spec.sas_sata_inf.sas_address == sas_address)
                    && (tgtdev->dev_spec.sas_sata_inf.hba_port == hba_port))
                        goto found_device;
        return NULL;
found_device:
        mpi3mr_tgtdev_get(tgtdev);
        return tgtdev;
}

/**
 * mpi3mr_get_tgtdev_by_addr - target device search
 * @mrioc: Adapter instance reference
 * @sas_address: SAS address of the device
 * @hba_port: HBA port entry
 *
 * This searches for target device from sas address and hba port
 * pointer then return mpi3mr_tgt_dev object.
 *
 * Context: This function will acquire tgtdev_lock and will
 * release before returning the mpi3mr_tgt_dev object.
 *
 * Return: Valid tget_dev or NULL
 */
static struct mpi3mr_tgt_dev *mpi3mr_get_tgtdev_by_addr(struct mpi3mr_ioc *mrioc,
        u64 sas_address, struct mpi3mr_hba_port *hba_port)
{
        struct mpi3mr_tgt_dev *tgtdev = NULL;
        unsigned long flags;

        if (!hba_port)
                goto out;

        spin_lock_irqsave(&mrioc->tgtdev_lock, flags);
        tgtdev = __mpi3mr_get_tgtdev_by_addr(mrioc, sas_address, hba_port);
        spin_unlock_irqrestore(&mrioc->tgtdev_lock, flags);

out:
        return tgtdev;
}

/**
 * mpi3mr_remove_device_by_sas_address - remove the device
 * @mrioc: Adapter instance reference
 * @sas_address: SAS address of the device
 * @hba_port: HBA port entry
 *
 * This searches for target device using sas address and hba
 * port pointer then removes it from the OS.
 *
 * Return: None
 */
static void mpi3mr_remove_device_by_sas_address(struct mpi3mr_ioc *mrioc,
        u64 sas_address, struct mpi3mr_hba_port *hba_port)
{
        struct mpi3mr_tgt_dev *tgtdev = NULL;
        unsigned long flags;
        u8 was_on_tgtdev_list = 0;

        if (!hba_port)
                return;

        spin_lock_irqsave(&mrioc->tgtdev_lock, flags);
        tgtdev = __mpi3mr_get_tgtdev_by_addr(mrioc,
                         sas_address, hba_port);
        if (tgtdev) {
                if (!list_empty(&tgtdev->list)) {
                        was_on_tgtdev_list = 1;
                        if (tgtdev->state == MPI3MR_DEV_REMOVE_HS_STARTED) {
                                list_del_init(&tgtdev->list);
                                mpi3mr_tgtdev_put(tgtdev);
                        }
                }
        }
        spin_unlock_irqrestore(&mrioc->tgtdev_lock, flags);
        if (was_on_tgtdev_list) {
                if (tgtdev->host_exposed)
                        mpi3mr_remove_tgtdev_from_host(mrioc, tgtdev);
                mpi3mr_tgtdev_put(tgtdev);
        }
}

/**
 * __mpi3mr_get_tgtdev_by_addr_and_rphy - target device search
 * @mrioc: Adapter instance reference
 * @sas_address: SAS address of the device
 * @rphy: SAS transport layer rphy object
 *
 * This searches for target device from sas address and rphy
 * pointer then return mpi3mr_tgt_dev object.
 *
 * Return: Valid tget_dev or NULL
 */
struct mpi3mr_tgt_dev *__mpi3mr_get_tgtdev_by_addr_and_rphy(
        struct mpi3mr_ioc *mrioc, u64 sas_address, struct sas_rphy *rphy)
{
        struct mpi3mr_tgt_dev *tgtdev;

        assert_spin_locked(&mrioc->tgtdev_lock);

        list_for_each_entry(tgtdev, &mrioc->tgtdev_list, list)
                if ((tgtdev->dev_type == MPI3_DEVICE_DEVFORM_SAS_SATA) &&
                    (tgtdev->dev_spec.sas_sata_inf.sas_address == sas_address)
                    && (tgtdev->dev_spec.sas_sata_inf.rphy == rphy))
                        goto found_device;
        return NULL;
found_device:
        mpi3mr_tgtdev_get(tgtdev);
        return tgtdev;
}

/**
 * mpi3mr_expander_find_by_sas_address - sas expander search
 * @mrioc: Adapter instance reference
 * @sas_address: SAS address of expander
 * @hba_port: HBA port entry
 *
 * Return: A valid SAS expander node or NULL.
 *
 */
static struct mpi3mr_sas_node *mpi3mr_expander_find_by_sas_address(
        struct mpi3mr_ioc *mrioc, u64 sas_address,
        struct mpi3mr_hba_port *hba_port)
{
        struct mpi3mr_sas_node *sas_expander, *r = NULL;

        if (!hba_port)
                goto out;

        list_for_each_entry(sas_expander, &mrioc->sas_expander_list, list) {
                if ((sas_expander->sas_address != sas_address) ||
                                         (sas_expander->hba_port != hba_port))
                        continue;
                r = sas_expander;
                goto out;
        }
out:
        return r;
}

/**
 * __mpi3mr_sas_node_find_by_sas_address - sas node search
 * @mrioc: Adapter instance reference
 * @sas_address: SAS address of expander or sas host
 * @hba_port: HBA port entry
 * Context: Caller should acquire mrioc->sas_node_lock.
 *
 * If the SAS address indicates the device is direct attached to
 * the controller (controller's SAS address) then the SAS node
 * associated with the controller is returned back else the SAS
 * address and hba port are used to identify the exact expander
 * and the associated sas_node object is returned. If there is
 * no match NULL is returned.
 *
 * Return: A valid SAS node or NULL.
 *
 */
static struct mpi3mr_sas_node *__mpi3mr_sas_node_find_by_sas_address(
        struct mpi3mr_ioc *mrioc, u64 sas_address,
        struct mpi3mr_hba_port *hba_port)
{

        if (mrioc->sas_hba.sas_address == sas_address)
                return &mrioc->sas_hba;
        return mpi3mr_expander_find_by_sas_address(mrioc, sas_address,
            hba_port);
}

/**
 * mpi3mr_parent_present - Is parent present for a phy
 * @mrioc: Adapter instance reference
 * @phy: SAS transport layer phy object
 *
 * Return: 0 if parent is present else non-zero
 */
static int mpi3mr_parent_present(struct mpi3mr_ioc *mrioc, struct sas_phy *phy)
{
        unsigned long flags;
        struct mpi3mr_hba_port *hba_port = phy->hostdata;

        spin_lock_irqsave(&mrioc->sas_node_lock, flags);
        if (__mpi3mr_sas_node_find_by_sas_address(mrioc,
            phy->identify.sas_address,
            hba_port) == NULL) {
                spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);
                return -1;
        }
        spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);
        return 0;
}

/**
 * mpi3mr_convert_phy_link_rate -
 * @link_rate: link rate as defined in the MPI header
 *
 * Convert link_rate from mpi format into sas_transport layer
 * form.
 *
 * Return: A valid SAS transport layer defined link rate
 */
static enum sas_linkrate mpi3mr_convert_phy_link_rate(u8 link_rate)
{
        enum sas_linkrate rc;

        switch (link_rate) {
        case MPI3_SAS_NEG_LINK_RATE_1_5:
                rc = SAS_LINK_RATE_1_5_GBPS;
                break;
        case MPI3_SAS_NEG_LINK_RATE_3_0:
                rc = SAS_LINK_RATE_3_0_GBPS;
                break;
        case MPI3_SAS_NEG_LINK_RATE_6_0:
                rc = SAS_LINK_RATE_6_0_GBPS;
                break;
        case MPI3_SAS_NEG_LINK_RATE_12_0:
                rc = SAS_LINK_RATE_12_0_GBPS;
                break;
        case MPI3_SAS_NEG_LINK_RATE_22_5:
                rc = SAS_LINK_RATE_22_5_GBPS;
                break;
        case MPI3_SAS_NEG_LINK_RATE_PHY_DISABLED:
                rc = SAS_PHY_DISABLED;
                break;
        case MPI3_SAS_NEG_LINK_RATE_NEGOTIATION_FAILED:
                rc = SAS_LINK_RATE_FAILED;
                break;
        case MPI3_SAS_NEG_LINK_RATE_PORT_SELECTOR:
                rc = SAS_SATA_PORT_SELECTOR;
                break;
        case MPI3_SAS_NEG_LINK_RATE_SMP_RESET_IN_PROGRESS:
                rc = SAS_PHY_RESET_IN_PROGRESS;
                break;
        case MPI3_SAS_NEG_LINK_RATE_SATA_OOB_COMPLETE:
        case MPI3_SAS_NEG_LINK_RATE_UNKNOWN_LINK_RATE:
        default:
                rc = SAS_LINK_RATE_UNKNOWN;
                break;
        }
        return rc;
}

/**
 * mpi3mr_delete_sas_phy - Remove a single phy from port
 * @mrioc: Adapter instance reference
 * @mr_sas_port: Internal Port object
 * @mr_sas_phy: Internal Phy object
 * @host_node: Flag to indicate this is a host_node
 *
 * Return: None.
 */
static void mpi3mr_delete_sas_phy(struct mpi3mr_ioc *mrioc,
        struct mpi3mr_sas_port *mr_sas_port,
        struct mpi3mr_sas_phy *mr_sas_phy, u8 host_node)
{
        u64 sas_address = mr_sas_port->remote_identify.sas_address;

        dev_info(&mr_sas_phy->phy->dev,
            "remove: sas_address(0x%016llx), phy(%d)\n",
            (unsigned long long) sas_address, mr_sas_phy->phy_id);

        list_del(&mr_sas_phy->port_siblings);
        mr_sas_port->num_phys--;

        if (host_node) {
                mr_sas_port->phy_mask &= ~(1 << mr_sas_phy->phy_id);

                if (mr_sas_port->lowest_phy == mr_sas_phy->phy_id)
                        mr_sas_port->lowest_phy = ffs(mr_sas_port->phy_mask) - 1;
        }
        sas_port_delete_phy(mr_sas_port->port, mr_sas_phy->phy);
        mr_sas_phy->phy_belongs_to_port = 0;
}

/**
 * mpi3mr_add_sas_phy - Adding a single phy to a port
 * @mrioc: Adapter instance reference
 * @mr_sas_port: Internal Port object
 * @mr_sas_phy: Internal Phy object
 * @host_node: Flag to indicate this is a host_node
 *
 * Return: None.
 */
static void mpi3mr_add_sas_phy(struct mpi3mr_ioc *mrioc,
        struct mpi3mr_sas_port *mr_sas_port,
        struct mpi3mr_sas_phy *mr_sas_phy, u8 host_node)
{
        u64 sas_address = mr_sas_port->remote_identify.sas_address;

        dev_info(&mr_sas_phy->phy->dev,
            "add: sas_address(0x%016llx), phy(%d)\n", (unsigned long long)
            sas_address, mr_sas_phy->phy_id);

        list_add_tail(&mr_sas_phy->port_siblings, &mr_sas_port->phy_list);
        mr_sas_port->num_phys++;
        if (host_node) {
                mr_sas_port->phy_mask |= (1 << mr_sas_phy->phy_id);

                if (mr_sas_phy->phy_id < mr_sas_port->lowest_phy)
                        mr_sas_port->lowest_phy = ffs(mr_sas_port->phy_mask) - 1;
        }
        sas_port_add_phy(mr_sas_port->port, mr_sas_phy->phy);
        mr_sas_phy->phy_belongs_to_port = 1;
}

/**
 * mpi3mr_add_phy_to_an_existing_port - add phy to existing port
 * @mrioc: Adapter instance reference
 * @mr_sas_node: Internal sas node object (expander or host)
 * @mr_sas_phy: Internal Phy object *
 * @sas_address: SAS address of device/expander were phy needs
 *             to be added to
 * @hba_port: HBA port entry
 *
 * Return: None.
 */
static void mpi3mr_add_phy_to_an_existing_port(struct mpi3mr_ioc *mrioc,
        struct mpi3mr_sas_node *mr_sas_node, struct mpi3mr_sas_phy *mr_sas_phy,
        u64 sas_address, struct mpi3mr_hba_port *hba_port)
{
        struct mpi3mr_sas_port *mr_sas_port;
        struct mpi3mr_sas_phy *srch_phy;

        if (mr_sas_phy->phy_belongs_to_port == 1)
                return;

        if (!hba_port)
                return;

        list_for_each_entry(mr_sas_port, &mr_sas_node->sas_port_list,
            port_list) {
                if (mr_sas_port->remote_identify.sas_address !=
                    sas_address)
                        continue;
                if (mr_sas_port->hba_port != hba_port)
                        continue;
                list_for_each_entry(srch_phy, &mr_sas_port->phy_list,
                    port_siblings) {
                        if (srch_phy == mr_sas_phy)
                                return;
                }
                mpi3mr_add_sas_phy(mrioc, mr_sas_port, mr_sas_phy, mr_sas_node->host_node);
                return;
        }
}

/**
 * mpi3mr_delete_sas_port - helper function to removing a port
 * @mrioc: Adapter instance reference
 * @mr_sas_port: Internal Port object
 *
 * Return: None.
 */
static void  mpi3mr_delete_sas_port(struct mpi3mr_ioc *mrioc,
        struct mpi3mr_sas_port *mr_sas_port)
{
        u64 sas_address = mr_sas_port->remote_identify.sas_address;
        struct mpi3mr_hba_port *hba_port = mr_sas_port->hba_port;
        enum sas_device_type device_type =
            mr_sas_port->remote_identify.device_type;

        dev_info(&mr_sas_port->port->dev,
            "remove: sas_address(0x%016llx)\n",
            (unsigned long long) sas_address);

        if (device_type == SAS_END_DEVICE)
                mpi3mr_remove_device_by_sas_address(mrioc, sas_address,
                    hba_port);

        else if (device_type == SAS_EDGE_EXPANDER_DEVICE ||
            device_type == SAS_FANOUT_EXPANDER_DEVICE)
                mpi3mr_expander_remove(mrioc, sas_address, hba_port);
}

/**
 * mpi3mr_del_phy_from_an_existing_port - del phy from a port
 * @mrioc: Adapter instance reference
 * @mr_sas_node: Internal sas node object (expander or host)
 * @mr_sas_phy: Internal Phy object
 *
 * Return: None.
 */
static void mpi3mr_del_phy_from_an_existing_port(struct mpi3mr_ioc *mrioc,
        struct mpi3mr_sas_node *mr_sas_node, struct mpi3mr_sas_phy *mr_sas_phy)
{
        struct mpi3mr_sas_port *mr_sas_port, *next;
        struct mpi3mr_sas_phy *srch_phy;

        if (mr_sas_phy->phy_belongs_to_port == 0)
                return;

        list_for_each_entry_safe(mr_sas_port, next, &mr_sas_node->sas_port_list,
            port_list) {
                list_for_each_entry(srch_phy, &mr_sas_port->phy_list,
                    port_siblings) {
                        if (srch_phy != mr_sas_phy)
                                continue;
                        if ((mr_sas_port->num_phys == 1) &&
                            !mrioc->reset_in_progress)
                                mpi3mr_delete_sas_port(mrioc, mr_sas_port);
                        else
                                mpi3mr_delete_sas_phy(mrioc, mr_sas_port,
                                    mr_sas_phy, mr_sas_node->host_node);
                        return;
                }
        }
}

/**
 * mpi3mr_sas_port_sanity_check - sanity check while adding port
 * @mrioc: Adapter instance reference
 * @mr_sas_node: Internal sas node object (expander or host)
 * @sas_address: SAS address of device/expander
 * @hba_port: HBA port entry
 *
 * Verifies whether the Phys attached to a device with the given
 * SAS address already belongs to an existing sas port if so
 * will remove those phys from the sas port
 *
 * Return: None.
 */
static void mpi3mr_sas_port_sanity_check(struct mpi3mr_ioc *mrioc,
        struct mpi3mr_sas_node *mr_sas_node, u64 sas_address,
        struct mpi3mr_hba_port *hba_port)
{
        int i;

        for (i = 0; i < mr_sas_node->num_phys; i++) {
                if ((mr_sas_node->phy[i].remote_identify.sas_address !=
                    sas_address) || (mr_sas_node->phy[i].hba_port != hba_port))
                        continue;
                if (mr_sas_node->phy[i].phy_belongs_to_port == 1)
                        mpi3mr_del_phy_from_an_existing_port(mrioc,
                            mr_sas_node, &mr_sas_node->phy[i]);
        }
}

/**
 * mpi3mr_set_identify - set identify for phys and end devices
 * @mrioc: Adapter instance reference
 * @handle: Firmware device handle
 * @identify: SAS transport layer's identify info
 *
 * Populates sas identify info for a specific device.
 *
 * Return: 0 for success, non-zero for failure.
 */
static int mpi3mr_set_identify(struct mpi3mr_ioc *mrioc, u16 handle,
        struct sas_identify *identify)
{

        struct mpi3_device_page0 device_pg0;
        struct mpi3_device0_sas_sata_format *sasinf;
        u16 device_info;
        u16 ioc_status;

        if (mrioc->reset_in_progress) {
                ioc_err(mrioc, "%s: host reset in progress!\n", __func__);
                return -EFAULT;
        }

        if (mrioc->pci_err_recovery) {
                ioc_err(mrioc, "%s: pci error recovery in progress!\n",
                    __func__);
                return -EFAULT;
        }

        if ((mpi3mr_cfg_get_dev_pg0(mrioc, &ioc_status, &device_pg0,
            sizeof(device_pg0), MPI3_DEVICE_PGAD_FORM_HANDLE, handle))) {
                ioc_err(mrioc, "%s: device page0 read failed\n", __func__);
                return -ENXIO;
        }

        if (ioc_status != MPI3_IOCSTATUS_SUCCESS) {
                ioc_err(mrioc, "device page read failed for handle(0x%04x), with ioc_status(0x%04x) failure at %s:%d/%s()!\n",
                    handle, ioc_status, __FILE__, __LINE__, __func__);
                return -EIO;
        }

        memset(identify, 0, sizeof(struct sas_identify));
        sasinf = &device_pg0.device_specific.sas_sata_format;
        device_info = le16_to_cpu(sasinf->device_info);

        /* sas_address */
        identify->sas_address = le64_to_cpu(sasinf->sas_address);

        /* phy number of the parent device this device is linked to */
        identify->phy_identifier = sasinf->phy_num;

        /* device_type */
        switch (device_info & MPI3_SAS_DEVICE_INFO_DEVICE_TYPE_MASK) {
        case MPI3_SAS_DEVICE_INFO_DEVICE_TYPE_NO_DEVICE:
                identify->device_type = SAS_PHY_UNUSED;
                break;
        case MPI3_SAS_DEVICE_INFO_DEVICE_TYPE_END_DEVICE:
                identify->device_type = SAS_END_DEVICE;
                break;
        case MPI3_SAS_DEVICE_INFO_DEVICE_TYPE_EXPANDER:
                identify->device_type = SAS_EDGE_EXPANDER_DEVICE;
                break;
        }

        /* initiator_port_protocols */
        if (device_info & MPI3_SAS_DEVICE_INFO_SSP_INITIATOR)
                identify->initiator_port_protocols |= SAS_PROTOCOL_SSP;
        /* MPI3.0 doesn't have define for SATA INIT so setting both here*/
        if (device_info & MPI3_SAS_DEVICE_INFO_STP_INITIATOR)
                identify->initiator_port_protocols |= (SAS_PROTOCOL_STP |
                    SAS_PROTOCOL_SATA);
        if (device_info & MPI3_SAS_DEVICE_INFO_SMP_INITIATOR)
                identify->initiator_port_protocols |= SAS_PROTOCOL_SMP;

        /* target_port_protocols */
        if (device_info & MPI3_SAS_DEVICE_INFO_SSP_TARGET)
                identify->target_port_protocols |= SAS_PROTOCOL_SSP;
        /* MPI3.0 doesn't have define for STP Target so setting both here*/
        if (device_info & MPI3_SAS_DEVICE_INFO_STP_SATA_TARGET)
                identify->target_port_protocols |= (SAS_PROTOCOL_STP |
                    SAS_PROTOCOL_SATA);
        if (device_info & MPI3_SAS_DEVICE_INFO_SMP_TARGET)
                identify->target_port_protocols |= SAS_PROTOCOL_SMP;
        return 0;
}

/**
 * mpi3mr_add_host_phy - report sas_host phy to SAS transport
 * @mrioc: Adapter instance reference
 * @mr_sas_phy: Internal Phy object
 * @phy_pg0: SAS phy page 0
 * @parent_dev: Prent device class object
 *
 * Return: 0 for success, non-zero for failure.
 */
static int mpi3mr_add_host_phy(struct mpi3mr_ioc *mrioc,
        struct mpi3mr_sas_phy *mr_sas_phy, struct mpi3_sas_phy_page0 phy_pg0,
        struct device *parent_dev)
{
        struct sas_phy *phy;
        int phy_index = mr_sas_phy->phy_id;


        INIT_LIST_HEAD(&mr_sas_phy->port_siblings);
        phy = sas_phy_alloc(parent_dev, phy_index);
        if (!phy) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                return -1;
        }
        if ((mpi3mr_set_identify(mrioc, mr_sas_phy->handle,
            &mr_sas_phy->identify))) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                sas_phy_free(phy);
                return -1;
        }
        phy->identify = mr_sas_phy->identify;
        mr_sas_phy->attached_handle = le16_to_cpu(phy_pg0.attached_dev_handle);
        if (mr_sas_phy->attached_handle)
                mpi3mr_set_identify(mrioc, mr_sas_phy->attached_handle,
                    &mr_sas_phy->remote_identify);
        phy->identify.phy_identifier = mr_sas_phy->phy_id;
        phy->negotiated_linkrate = mpi3mr_convert_phy_link_rate(
            (phy_pg0.negotiated_link_rate &
            MPI3_SAS_NEG_LINK_RATE_LOGICAL_MASK) >>
            MPI3_SAS_NEG_LINK_RATE_LOGICAL_SHIFT);
        phy->minimum_linkrate_hw = mpi3mr_convert_phy_link_rate(
            phy_pg0.hw_link_rate & MPI3_SAS_HWRATE_MIN_RATE_MASK);
        phy->maximum_linkrate_hw = mpi3mr_convert_phy_link_rate(
            phy_pg0.hw_link_rate >> 4);
        phy->minimum_linkrate = mpi3mr_convert_phy_link_rate(
            phy_pg0.programmed_link_rate & MPI3_SAS_PRATE_MIN_RATE_MASK);
        phy->maximum_linkrate = mpi3mr_convert_phy_link_rate(
            phy_pg0.programmed_link_rate >> 4);
        phy->hostdata = mr_sas_phy->hba_port;

        if ((sas_phy_add(phy))) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                sas_phy_free(phy);
                return -1;
        }
        if ((mrioc->logging_level & MPI3_DEBUG_TRANSPORT_INFO))
                dev_info(&phy->dev,
                    "add: handle(0x%04x), sas_address(0x%016llx)\n"
                    "\tattached_handle(0x%04x), sas_address(0x%016llx)\n",
                    mr_sas_phy->handle, (unsigned long long)
                    mr_sas_phy->identify.sas_address,
                    mr_sas_phy->attached_handle,
                    (unsigned long long)
                    mr_sas_phy->remote_identify.sas_address);
        mr_sas_phy->phy = phy;
        return 0;
}

/**
 * mpi3mr_add_expander_phy - report expander phy to transport
 * @mrioc: Adapter instance reference
 * @mr_sas_phy: Internal Phy object
 * @expander_pg1: SAS Expander page 1
 * @parent_dev: Parent device class object
 *
 * Return: 0 for success, non-zero for failure.
 */
static int mpi3mr_add_expander_phy(struct mpi3mr_ioc *mrioc,
        struct mpi3mr_sas_phy *mr_sas_phy,
        struct mpi3_sas_expander_page1 expander_pg1,
        struct device *parent_dev)
{
        struct sas_phy *phy;
        int phy_index = mr_sas_phy->phy_id;

        INIT_LIST_HEAD(&mr_sas_phy->port_siblings);
        phy = sas_phy_alloc(parent_dev, phy_index);
        if (!phy) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                return -1;
        }
        if ((mpi3mr_set_identify(mrioc, mr_sas_phy->handle,
            &mr_sas_phy->identify))) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                sas_phy_free(phy);
                return -1;
        }
        phy->identify = mr_sas_phy->identify;
        mr_sas_phy->attached_handle =
            le16_to_cpu(expander_pg1.attached_dev_handle);
        if (mr_sas_phy->attached_handle)
                mpi3mr_set_identify(mrioc, mr_sas_phy->attached_handle,
                    &mr_sas_phy->remote_identify);
        phy->identify.phy_identifier = mr_sas_phy->phy_id;
        phy->negotiated_linkrate = mpi3mr_convert_phy_link_rate(
            (expander_pg1.negotiated_link_rate &
            MPI3_SAS_NEG_LINK_RATE_LOGICAL_MASK) >>
            MPI3_SAS_NEG_LINK_RATE_LOGICAL_SHIFT);
        phy->minimum_linkrate_hw = mpi3mr_convert_phy_link_rate(
            expander_pg1.hw_link_rate & MPI3_SAS_HWRATE_MIN_RATE_MASK);
        phy->maximum_linkrate_hw = mpi3mr_convert_phy_link_rate(
            expander_pg1.hw_link_rate >> 4);
        phy->minimum_linkrate = mpi3mr_convert_phy_link_rate(
            expander_pg1.programmed_link_rate & MPI3_SAS_PRATE_MIN_RATE_MASK);
        phy->maximum_linkrate = mpi3mr_convert_phy_link_rate(
            expander_pg1.programmed_link_rate >> 4);
        phy->hostdata = mr_sas_phy->hba_port;

        if ((sas_phy_add(phy))) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                sas_phy_free(phy);
                return -1;
        }
        if ((mrioc->logging_level & MPI3_DEBUG_TRANSPORT_INFO))
                dev_info(&phy->dev,
                    "add: handle(0x%04x), sas_address(0x%016llx)\n"
                    "\tattached_handle(0x%04x), sas_address(0x%016llx)\n",
                    mr_sas_phy->handle, (unsigned long long)
                    mr_sas_phy->identify.sas_address,
                    mr_sas_phy->attached_handle,
                    (unsigned long long)
                    mr_sas_phy->remote_identify.sas_address);
        mr_sas_phy->phy = phy;
        return 0;
}

/**
 * mpi3mr_alloc_hba_port - alloc hba port object
 * @mrioc: Adapter instance reference
 * @port_id: Port number
 *
 * Alloc memory for hba port object.
 */
static struct mpi3mr_hba_port *
mpi3mr_alloc_hba_port(struct mpi3mr_ioc *mrioc, u16 port_id)
{
        struct mpi3mr_hba_port *hba_port;

        hba_port = kzalloc_obj(struct mpi3mr_hba_port);
        if (!hba_port)
                return NULL;
        hba_port->port_id = port_id;
        ioc_info(mrioc, "hba_port entry: %p, port: %d is added to hba_port list\n",
            hba_port, hba_port->port_id);
        if (mrioc->reset_in_progress ||
                mrioc->pci_err_recovery)
                hba_port->flags = MPI3MR_HBA_PORT_FLAG_NEW;
        list_add_tail(&hba_port->list, &mrioc->hba_port_table_list);
        return hba_port;
}

/**
 * mpi3mr_get_hba_port_by_id - find hba port by id
 * @mrioc: Adapter instance reference
 * @port_id: Port ID to search
 *
 * Return: mpi3mr_hba_port reference for the matched port
 */

struct mpi3mr_hba_port *mpi3mr_get_hba_port_by_id(struct mpi3mr_ioc *mrioc,
        u8 port_id)
{
        struct mpi3mr_hba_port *port, *port_next;

        list_for_each_entry_safe(port, port_next,
            &mrioc->hba_port_table_list, list) {
                if (port->port_id != port_id)
                        continue;
                if (port->flags & MPI3MR_HBA_PORT_FLAG_DIRTY)
                        continue;
                return port;
        }

        return NULL;
}

/**
 * mpi3mr_update_links - refreshing SAS phy link changes
 * @mrioc: Adapter instance reference
 * @sas_address_parent: SAS address of parent expander or host
 * @handle: Firmware device handle of attached device
 * @phy_number: Phy number
 * @link_rate: New link rate
 * @hba_port: HBA port entry
 *
 * Return: None.
 */
void mpi3mr_update_links(struct mpi3mr_ioc *mrioc,
        u64 sas_address_parent, u16 handle, u8 phy_number, u8 link_rate,
        struct mpi3mr_hba_port *hba_port)
{
        unsigned long flags;
        struct mpi3mr_sas_node *mr_sas_node;
        struct mpi3mr_sas_phy *mr_sas_phy;

        if (mrioc->reset_in_progress || mrioc->pci_err_recovery)
                return;

        spin_lock_irqsave(&mrioc->sas_node_lock, flags);
        mr_sas_node = __mpi3mr_sas_node_find_by_sas_address(mrioc,
            sas_address_parent, hba_port);
        if (!mr_sas_node) {
                spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);
                return;
        }

        mr_sas_phy = &mr_sas_node->phy[phy_number];
        mr_sas_phy->attached_handle = handle;
        spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);
        if (handle && (link_rate >= MPI3_SAS_NEG_LINK_RATE_1_5)) {
                mpi3mr_set_identify(mrioc, handle,
                    &mr_sas_phy->remote_identify);
                mpi3mr_add_phy_to_an_existing_port(mrioc, mr_sas_node,
                    mr_sas_phy, mr_sas_phy->remote_identify.sas_address,
                    hba_port);
        } else
                memset(&mr_sas_phy->remote_identify, 0, sizeof(struct
                    sas_identify));

        if (mr_sas_phy->phy)
                mr_sas_phy->phy->negotiated_linkrate =
                    mpi3mr_convert_phy_link_rate(link_rate);

        if ((mrioc->logging_level & MPI3_DEBUG_TRANSPORT_INFO))
                dev_info(&mr_sas_phy->phy->dev,
                    "refresh: parent sas_address(0x%016llx),\n"
                    "\tlink_rate(0x%02x), phy(%d)\n"
                    "\tattached_handle(0x%04x), sas_address(0x%016llx)\n",
                    (unsigned long long)sas_address_parent,
                    link_rate, phy_number, handle, (unsigned long long)
                    mr_sas_phy->remote_identify.sas_address);
}

/**
 * mpi3mr_sas_host_refresh - refreshing sas host object contents
 * @mrioc: Adapter instance reference
 *
 * This function refreshes the controllers phy information and
 * updates the SAS transport layer with updated information,
 * this is executed for each device addition or device info
 * change events
 *
 * Return: None.
 */
void mpi3mr_sas_host_refresh(struct mpi3mr_ioc *mrioc)
{
        int i;
        u8 link_rate;
        u16 sz, port_id, attached_handle;
        struct mpi3_sas_io_unit_page0 *sas_io_unit_pg0 = NULL;

        dprint_transport_info(mrioc,
            "updating handles for sas_host(0x%016llx)\n",
            (unsigned long long)mrioc->sas_hba.sas_address);

        sz = offsetof(struct mpi3_sas_io_unit_page0, phy_data) +
            (mrioc->sas_hba.num_phys *
             sizeof(struct mpi3_sas_io_unit0_phy_data));
        sas_io_unit_pg0 = kzalloc(sz, GFP_KERNEL);
        if (!sas_io_unit_pg0)
                return;
        if (mpi3mr_cfg_get_sas_io_unit_pg0(mrioc, sas_io_unit_pg0, sz)) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                goto out;
        }

        mrioc->sas_hba.handle = 0;
        for (i = 0; i < mrioc->sas_hba.num_phys; i++) {
                if (sas_io_unit_pg0->phy_data[i].phy_flags &
                    (MPI3_SASIOUNIT0_PHYFLAGS_HOST_PHY |
                     MPI3_SASIOUNIT0_PHYFLAGS_VIRTUAL_PHY))
                        continue;
                link_rate =
                    sas_io_unit_pg0->phy_data[i].negotiated_link_rate >> 4;
                if (!mrioc->sas_hba.handle)
                        mrioc->sas_hba.handle = le16_to_cpu(
                            sas_io_unit_pg0->phy_data[i].controller_dev_handle);
                port_id = sas_io_unit_pg0->phy_data[i].io_unit_port;
                if (!(mpi3mr_get_hba_port_by_id(mrioc, port_id)))
                        if (!mpi3mr_alloc_hba_port(mrioc, port_id))
                                goto out;

                mrioc->sas_hba.phy[i].handle = mrioc->sas_hba.handle;
                attached_handle = le16_to_cpu(
                    sas_io_unit_pg0->phy_data[i].attached_dev_handle);
                if (attached_handle && link_rate < MPI3_SAS_NEG_LINK_RATE_1_5)
                        link_rate = MPI3_SAS_NEG_LINK_RATE_1_5;
                mrioc->sas_hba.phy[i].hba_port =
                        mpi3mr_get_hba_port_by_id(mrioc, port_id);
                mpi3mr_update_links(mrioc, mrioc->sas_hba.sas_address,
                    attached_handle, i, link_rate,
                    mrioc->sas_hba.phy[i].hba_port);
        }
 out:
        kfree(sas_io_unit_pg0);
}

/**
 * mpi3mr_sas_host_add - create sas host object
 * @mrioc: Adapter instance reference
 *
 * This function creates the controllers phy information and
 * updates the SAS transport layer with updated information,
 * this is executed for first device addition or device info
 * change event.
 *
 * Return: None.
 */
void mpi3mr_sas_host_add(struct mpi3mr_ioc *mrioc)
{
        int i;
        u16 sz, num_phys = 1, port_id, ioc_status;
        struct mpi3_sas_io_unit_page0 *sas_io_unit_pg0 = NULL;
        struct mpi3_sas_phy_page0 phy_pg0;
        struct mpi3_device_page0 dev_pg0;
        struct mpi3_enclosure_page0 encl_pg0;
        struct mpi3_device0_sas_sata_format *sasinf;

        sz = offsetof(struct mpi3_sas_io_unit_page0, phy_data) +
            (num_phys * sizeof(struct mpi3_sas_io_unit0_phy_data));
        sas_io_unit_pg0 = kzalloc(sz, GFP_KERNEL);
        if (!sas_io_unit_pg0)
                return;

        if (mpi3mr_cfg_get_sas_io_unit_pg0(mrioc, sas_io_unit_pg0, sz)) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                goto out;
        }
        num_phys = sas_io_unit_pg0->num_phys;
        kfree(sas_io_unit_pg0);

        mrioc->sas_hba.host_node = 1;
        INIT_LIST_HEAD(&mrioc->sas_hba.sas_port_list);
        mrioc->sas_hba.parent_dev = &mrioc->shost->shost_gendev;
        mrioc->sas_hba.phy = kzalloc_objs(struct mpi3mr_sas_phy, num_phys);
        if (!mrioc->sas_hba.phy)
                return;

        mrioc->sas_hba.num_phys = num_phys;

        sz = offsetof(struct mpi3_sas_io_unit_page0, phy_data) +
            (num_phys * sizeof(struct mpi3_sas_io_unit0_phy_data));
        sas_io_unit_pg0 = kzalloc(sz, GFP_KERNEL);
        if (!sas_io_unit_pg0)
                return;

        if (mpi3mr_cfg_get_sas_io_unit_pg0(mrioc, sas_io_unit_pg0, sz)) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                goto out;
        }

        mrioc->sas_hba.handle = 0;
        for (i = 0; i < mrioc->sas_hba.num_phys; i++) {
                if (sas_io_unit_pg0->phy_data[i].phy_flags &
                    (MPI3_SASIOUNIT0_PHYFLAGS_HOST_PHY |
                    MPI3_SASIOUNIT0_PHYFLAGS_VIRTUAL_PHY))
                        continue;
                if (mpi3mr_cfg_get_sas_phy_pg0(mrioc, &ioc_status, &phy_pg0,
                    sizeof(struct mpi3_sas_phy_page0),
                    MPI3_SAS_PHY_PGAD_FORM_PHY_NUMBER, i)) {
                        ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                            __FILE__, __LINE__, __func__);
                        goto out;
                }
                if (ioc_status != MPI3_IOCSTATUS_SUCCESS) {
                        ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                            __FILE__, __LINE__, __func__);
                        goto out;
                }

                if (!mrioc->sas_hba.handle)
                        mrioc->sas_hba.handle = le16_to_cpu(
                            sas_io_unit_pg0->phy_data[i].controller_dev_handle);
                port_id = sas_io_unit_pg0->phy_data[i].io_unit_port;

                if (!(mpi3mr_get_hba_port_by_id(mrioc, port_id)))
                        if (!mpi3mr_alloc_hba_port(mrioc, port_id))
                                goto out;

                mrioc->sas_hba.phy[i].handle = mrioc->sas_hba.handle;
                mrioc->sas_hba.phy[i].phy_id = i;
                mrioc->sas_hba.phy[i].hba_port =
                    mpi3mr_get_hba_port_by_id(mrioc, port_id);
                mpi3mr_add_host_phy(mrioc, &mrioc->sas_hba.phy[i],
                    phy_pg0, mrioc->sas_hba.parent_dev);
        }
        if ((mpi3mr_cfg_get_dev_pg0(mrioc, &ioc_status, &dev_pg0,
            sizeof(dev_pg0), MPI3_DEVICE_PGAD_FORM_HANDLE,
            mrioc->sas_hba.handle))) {
                ioc_err(mrioc, "%s: device page0 read failed\n", __func__);
                goto out;
        }
        if (ioc_status != MPI3_IOCSTATUS_SUCCESS) {
                ioc_err(mrioc, "device page read failed for handle(0x%04x), with ioc_status(0x%04x) failure at %s:%d/%s()!\n",
                    mrioc->sas_hba.handle, ioc_status, __FILE__, __LINE__,
                    __func__);
                goto out;
        }
        mrioc->sas_hba.enclosure_handle =
            le16_to_cpu(dev_pg0.enclosure_handle);
        sasinf = &dev_pg0.device_specific.sas_sata_format;
        mrioc->sas_hba.sas_address =
            le64_to_cpu(sasinf->sas_address);
        ioc_info(mrioc,
            "host_add: handle(0x%04x), sas_addr(0x%016llx), phys(%d)\n",
            mrioc->sas_hba.handle,
            (unsigned long long) mrioc->sas_hba.sas_address,
            mrioc->sas_hba.num_phys);

        if (mrioc->sas_hba.enclosure_handle) {
                if (!(mpi3mr_cfg_get_enclosure_pg0(mrioc, &ioc_status,
                    &encl_pg0, sizeof(encl_pg0),
                    MPI3_ENCLOS_PGAD_FORM_HANDLE,
                    mrioc->sas_hba.enclosure_handle)) &&
                    (ioc_status == MPI3_IOCSTATUS_SUCCESS))
                        mrioc->sas_hba.enclosure_logical_id =
                                le64_to_cpu(encl_pg0.enclosure_logical_id);
        }

out:
        kfree(sas_io_unit_pg0);
}

/**
 * mpi3mr_sas_port_add - Expose the SAS device to the SAS TL
 * @mrioc: Adapter instance reference
 * @handle: Firmware device handle of the attached device
 * @sas_address_parent: sas address of parent expander or host
 * @hba_port: HBA port entry
 *
 * This function creates a new sas port object for the given end
 * device matching sas address and hba_port and adds it to the
 * sas_node's sas_port_list and expose the attached sas device
 * to the SAS transport layer through sas_rphy_add.
 *
 * Returns a valid mpi3mr_sas_port reference or NULL.
 */
static struct mpi3mr_sas_port *mpi3mr_sas_port_add(struct mpi3mr_ioc *mrioc,
        u16 handle, u64 sas_address_parent, struct mpi3mr_hba_port *hba_port)
{
        struct mpi3mr_sas_phy *mr_sas_phy, *next;
        struct mpi3mr_sas_port *mr_sas_port;
        unsigned long flags;
        struct mpi3mr_sas_node *mr_sas_node;
        struct sas_rphy *rphy;
        struct mpi3mr_tgt_dev *tgtdev = NULL;
        int i;
        struct sas_port *port;

        if (!hba_port) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                return NULL;
        }

        mr_sas_port = kzalloc_obj(struct mpi3mr_sas_port);
        if (!mr_sas_port)
                return NULL;

        INIT_LIST_HEAD(&mr_sas_port->port_list);
        INIT_LIST_HEAD(&mr_sas_port->phy_list);
        spin_lock_irqsave(&mrioc->sas_node_lock, flags);
        mr_sas_node = __mpi3mr_sas_node_find_by_sas_address(mrioc,
            sas_address_parent, hba_port);
        spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);

        if (!mr_sas_node) {
                ioc_err(mrioc, "%s:could not find parent sas_address(0x%016llx)!\n",
                    __func__, (unsigned long long)sas_address_parent);
                goto out_fail;
        }

        if ((mpi3mr_set_identify(mrioc, handle,
            &mr_sas_port->remote_identify))) {
                ioc_err(mrioc,  "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                goto out_fail;
        }

        if (mr_sas_port->remote_identify.device_type == SAS_PHY_UNUSED) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                goto out_fail;
        }

        mr_sas_port->hba_port = hba_port;
        mpi3mr_sas_port_sanity_check(mrioc, mr_sas_node,
            mr_sas_port->remote_identify.sas_address, hba_port);

        if (mr_sas_node->host_node && mr_sas_node->num_phys >=
                        sizeof(mr_sas_port->phy_mask) * 8)
                ioc_info(mrioc, "max port count %u could be too high\n",
                    mr_sas_node->num_phys);

        for (i = 0; i < mr_sas_node->num_phys; i++) {
                if ((mr_sas_node->phy[i].remote_identify.sas_address !=
                    mr_sas_port->remote_identify.sas_address) ||
                    (mr_sas_node->phy[i].hba_port != hba_port))
                        continue;

                if (mr_sas_node->host_node && (i >= sizeof(mr_sas_port->phy_mask) * 8)) {
                        ioc_warn(mrioc, "skipping port %u, max allowed value is %zu\n",
                            i, sizeof(mr_sas_port->phy_mask) * 8);
                        goto out_fail;
                }
                list_add_tail(&mr_sas_node->phy[i].port_siblings,
                    &mr_sas_port->phy_list);
                mr_sas_port->num_phys++;
                if (mr_sas_node->host_node)
                        mr_sas_port->phy_mask |= (1 << i);
        }

        if (!mr_sas_port->num_phys) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                goto out_fail;
        }

        if (mr_sas_node->host_node)
                mr_sas_port->lowest_phy = ffs(mr_sas_port->phy_mask) - 1;

        if (mr_sas_port->remote_identify.device_type == SAS_END_DEVICE) {
                tgtdev = mpi3mr_get_tgtdev_by_addr(mrioc,
                    mr_sas_port->remote_identify.sas_address,
                    mr_sas_port->hba_port);

                if (!tgtdev) {
                        ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                            __FILE__, __LINE__, __func__);
                        goto out_fail;
                }
                tgtdev->dev_spec.sas_sata_inf.pend_sas_rphy_add = 1;
        }

        if (!mr_sas_node->parent_dev) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                goto out_fail;
        }

        port = sas_port_alloc_num(mr_sas_node->parent_dev);
        if ((sas_port_add(port))) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                goto out_fail;
        }

        list_for_each_entry(mr_sas_phy, &mr_sas_port->phy_list,
            port_siblings) {
                if ((mrioc->logging_level & MPI3_DEBUG_TRANSPORT_INFO))
                        dev_info(&port->dev,
                            "add: handle(0x%04x), sas_address(0x%016llx), phy(%d)\n",
                            handle, (unsigned long long)
                            mr_sas_port->remote_identify.sas_address,
                            mr_sas_phy->phy_id);
                sas_port_add_phy(port, mr_sas_phy->phy);
                mr_sas_phy->phy_belongs_to_port = 1;
                mr_sas_phy->hba_port = hba_port;
        }

        mr_sas_port->port = port;
        if (mr_sas_port->remote_identify.device_type == SAS_END_DEVICE) {
                rphy = sas_end_device_alloc(port);
                tgtdev->dev_spec.sas_sata_inf.rphy = rphy;
        } else {
                rphy = sas_expander_alloc(port,
                    mr_sas_port->remote_identify.device_type);
        }
        rphy->identify = mr_sas_port->remote_identify;

        if (mrioc->current_event)
                mrioc->current_event->pending_at_sml = 1;

        if ((sas_rphy_add(rphy))) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
        }
        if (mr_sas_port->remote_identify.device_type == SAS_END_DEVICE) {
                tgtdev->dev_spec.sas_sata_inf.pend_sas_rphy_add = 0;
                tgtdev->dev_spec.sas_sata_inf.sas_transport_attached = 1;
                mpi3mr_tgtdev_put(tgtdev);
        }

        dev_info(&rphy->dev,
            "%s: added: handle(0x%04x), sas_address(0x%016llx)\n",
            __func__, handle, (unsigned long long)
            mr_sas_port->remote_identify.sas_address);

        mr_sas_port->rphy = rphy;
        spin_lock_irqsave(&mrioc->sas_node_lock, flags);
        list_add_tail(&mr_sas_port->port_list, &mr_sas_node->sas_port_list);
        spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);

        if (mrioc->current_event) {
                mrioc->current_event->pending_at_sml = 0;
                if (mrioc->current_event->discard)
                        mpi3mr_print_device_event_notice(mrioc, true);
        }

        /* fill in report manufacture */
        if (mr_sas_port->remote_identify.device_type ==
            SAS_EDGE_EXPANDER_DEVICE ||
            mr_sas_port->remote_identify.device_type ==
            SAS_FANOUT_EXPANDER_DEVICE)
                mpi3mr_report_manufacture(mrioc,
                    mr_sas_port->remote_identify.sas_address,
                    rphy_to_expander_device(rphy), hba_port->port_id);

        return mr_sas_port;

 out_fail:
        list_for_each_entry_safe(mr_sas_phy, next, &mr_sas_port->phy_list,
            port_siblings)
                list_del(&mr_sas_phy->port_siblings);
        kfree(mr_sas_port);
        return NULL;
}

/**
 * mpi3mr_sas_port_remove - remove port from the list
 * @mrioc: Adapter instance reference
 * @sas_address: SAS address of attached device
 * @sas_address_parent: SAS address of parent expander or host
 * @hba_port: HBA port entry
 *
 * Removing object and freeing associated memory from the
 * sas_port_list.
 *
 * Return: None
 */
static void mpi3mr_sas_port_remove(struct mpi3mr_ioc *mrioc, u64 sas_address,
        u64 sas_address_parent, struct mpi3mr_hba_port *hba_port)
{
        int i;
        unsigned long flags;
        struct mpi3mr_sas_port *mr_sas_port, *next;
        struct mpi3mr_sas_node *mr_sas_node;
        u8 found = 0;
        struct mpi3mr_sas_phy *mr_sas_phy, *next_phy;
        struct mpi3mr_hba_port *srch_port, *hba_port_next = NULL;

        if (!hba_port)
                return;

        spin_lock_irqsave(&mrioc->sas_node_lock, flags);
        mr_sas_node = __mpi3mr_sas_node_find_by_sas_address(mrioc,
            sas_address_parent, hba_port);
        if (!mr_sas_node) {
                spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);
                return;
        }
        list_for_each_entry_safe(mr_sas_port, next, &mr_sas_node->sas_port_list,
            port_list) {
                if (mr_sas_port->remote_identify.sas_address != sas_address)
                        continue;
                if (mr_sas_port->hba_port != hba_port)
                        continue;
                found = 1;
                list_del(&mr_sas_port->port_list);
                goto out;
        }

 out:
        if (!found) {
                spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);
                return;
        }

        if (mr_sas_node->host_node) {
                list_for_each_entry_safe(srch_port, hba_port_next,
                    &mrioc->hba_port_table_list, list) {
                        if (srch_port != hba_port)
                                continue;
                        ioc_info(mrioc,
                            "removing hba_port entry: %p port: %d from hba_port list\n",
                            srch_port, srch_port->port_id);
                        list_del(&hba_port->list);
                        kfree(hba_port);
                        break;
                }
        }

        for (i = 0; i < mr_sas_node->num_phys; i++) {
                if (mr_sas_node->phy[i].remote_identify.sas_address ==
                    sas_address)
                        memset(&mr_sas_node->phy[i].remote_identify, 0,
                            sizeof(struct sas_identify));
        }

        spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);

        if (mrioc->current_event)
                mrioc->current_event->pending_at_sml = 1;

        list_for_each_entry_safe(mr_sas_phy, next_phy,
            &mr_sas_port->phy_list, port_siblings) {
                if ((!mrioc->stop_drv_processing) &&
                    (mrioc->logging_level & MPI3_DEBUG_TRANSPORT_INFO))
                        dev_info(&mr_sas_port->port->dev,
                            "remove: sas_address(0x%016llx), phy(%d)\n",
                            (unsigned long long)
                            mr_sas_port->remote_identify.sas_address,
                            mr_sas_phy->phy_id);
                mr_sas_phy->phy_belongs_to_port = 0;
                if (!mrioc->stop_drv_processing)
                        sas_port_delete_phy(mr_sas_port->port,
                            mr_sas_phy->phy);
                list_del(&mr_sas_phy->port_siblings);
        }
        if (!mrioc->stop_drv_processing)
                sas_port_delete(mr_sas_port->port);
        ioc_info(mrioc, "%s: removed sas_address(0x%016llx)\n",
            __func__, (unsigned long long)sas_address);

        if (mrioc->current_event) {
                mrioc->current_event->pending_at_sml = 0;
                if (mrioc->current_event->discard)
                        mpi3mr_print_device_event_notice(mrioc, false);
        }

        kfree(mr_sas_port);
}

/**
 * struct host_port - host port details
 * @sas_address: SAS Address of the attached device
 * @phy_mask: phy mask of host port
 * @handle: Device Handle of attached device
 * @iounit_port_id: port ID
 * @used: host port is already matched with sas port from sas_port_list
 * @lowest_phy: lowest phy ID of host port
 */
struct host_port {
        u64     sas_address;
        u64     phy_mask;
        u16     handle;
        u8      iounit_port_id;
        u8      used;
        u8      lowest_phy;
};

/**
 * mpi3mr_update_mr_sas_port - update sas port objects during reset
 * @mrioc: Adapter instance reference
 * @h_port: host_port object
 * @mr_sas_port: sas_port objects which needs to be updated
 *
 * Update the port ID of sas port object. Also add the phys if new phys got
 * added to current sas port and remove the phys if some phys are moved
 * out of the current sas port.
 *
 * Return: Nothing.
 */
static void
mpi3mr_update_mr_sas_port(struct mpi3mr_ioc *mrioc, struct host_port *h_port,
        struct mpi3mr_sas_port *mr_sas_port)
{
        struct mpi3mr_sas_phy *mr_sas_phy;
        u64 phy_mask_xor;
        u64 phys_to_be_added, phys_to_be_removed;
        int i;

        h_port->used = 1;
        mr_sas_port->marked_responding = 1;

        dev_info(&mr_sas_port->port->dev,
            "sas_address(0x%016llx), old: port_id %d phy_mask 0x%llx, new: port_id %d phy_mask:0x%llx\n",
            mr_sas_port->remote_identify.sas_address,
            mr_sas_port->hba_port->port_id, mr_sas_port->phy_mask,
            h_port->iounit_port_id, h_port->phy_mask);

        mr_sas_port->hba_port->port_id = h_port->iounit_port_id;
        mr_sas_port->hba_port->flags &= ~MPI3MR_HBA_PORT_FLAG_DIRTY;

        /* Get the newly added phys bit map & removed phys bit map */
        phy_mask_xor = mr_sas_port->phy_mask ^ h_port->phy_mask;
        phys_to_be_added = h_port->phy_mask & phy_mask_xor;
        phys_to_be_removed = mr_sas_port->phy_mask & phy_mask_xor;

        /*
         * Register these new phys to current mr_sas_port's port.
         * if these phys are previously registered with another port
         * then delete these phys from that port first.
         */
        for_each_set_bit(i, (ulong *) &phys_to_be_added, BITS_PER_TYPE(u64)) {
                mr_sas_phy = &mrioc->sas_hba.phy[i];
                if (mr_sas_phy->phy_belongs_to_port)
                        mpi3mr_del_phy_from_an_existing_port(mrioc,
                            &mrioc->sas_hba, mr_sas_phy);
                mpi3mr_add_phy_to_an_existing_port(mrioc,
                    &mrioc->sas_hba, mr_sas_phy,
                    mr_sas_port->remote_identify.sas_address,
                    mr_sas_port->hba_port);
        }

        /* Delete the phys which are not part of current mr_sas_port's port. */
        for_each_set_bit(i, (ulong *) &phys_to_be_removed, BITS_PER_TYPE(u64)) {
                mr_sas_phy = &mrioc->sas_hba.phy[i];
                if (mr_sas_phy->phy_belongs_to_port)
                        mpi3mr_del_phy_from_an_existing_port(mrioc,
                            &mrioc->sas_hba, mr_sas_phy);
        }
}

/**
 * mpi3mr_refresh_sas_ports - update host's sas ports during reset
 * @mrioc: Adapter instance reference
 *
 * Update the host's sas ports during reset by checking whether
 * sas ports are still intact or not. Add/remove phys if any hba
 * phys are (moved in)/(moved out) of sas port. Also update
 * io_unit_port if it got changed during reset.
 *
 * Return: Nothing.
 */
void
mpi3mr_refresh_sas_ports(struct mpi3mr_ioc *mrioc)
{
        struct host_port *h_port = NULL;
        int i, j, found, host_port_count = 0, port_idx;
        u16 sz, attached_handle, ioc_status;
        struct mpi3_sas_io_unit_page0 *sas_io_unit_pg0 = NULL;
        struct mpi3_device_page0 dev_pg0;
        struct mpi3_device0_sas_sata_format *sasinf;
        struct mpi3mr_sas_port *mr_sas_port;

        sz = offsetof(struct mpi3_sas_io_unit_page0, phy_data) +
                (mrioc->sas_hba.num_phys *
                 sizeof(struct mpi3_sas_io_unit0_phy_data));
        sas_io_unit_pg0 = kzalloc(sz, GFP_KERNEL);
        if (!sas_io_unit_pg0)
                return;
        h_port = kzalloc_objs(struct host_port, 64);
        if (!h_port)
                goto out;

        if (mpi3mr_cfg_get_sas_io_unit_pg0(mrioc, sas_io_unit_pg0, sz)) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                goto out;
        }

        /* Create a new expander port table */
        for (i = 0; i < mrioc->sas_hba.num_phys; i++) {
                attached_handle = le16_to_cpu(
                    sas_io_unit_pg0->phy_data[i].attached_dev_handle);
                if (!attached_handle)
                        continue;
                found = 0;
                for (j = 0; j < host_port_count; j++) {
                        if (h_port[j].handle == attached_handle) {
                                h_port[j].phy_mask |= (1 << i);
                                found = 1;
                                break;
                        }
                }
                if (found)
                        continue;
                if ((mpi3mr_cfg_get_dev_pg0(mrioc, &ioc_status, &dev_pg0,
                    sizeof(dev_pg0), MPI3_DEVICE_PGAD_FORM_HANDLE,
                    attached_handle))) {
                        dprint_reset(mrioc,
                            "failed to read dev_pg0 for handle(0x%04x) at %s:%d/%s()!\n",
                            attached_handle, __FILE__, __LINE__, __func__);
                        continue;
                }
                if (ioc_status != MPI3_IOCSTATUS_SUCCESS) {
                        dprint_reset(mrioc,
                            "ioc_status(0x%x) while reading dev_pg0 for handle(0x%04x) at %s:%d/%s()!\n",
                            ioc_status, attached_handle,
                            __FILE__, __LINE__, __func__);
                        continue;
                }
                sasinf = &dev_pg0.device_specific.sas_sata_format;

                port_idx = host_port_count;
                h_port[port_idx].sas_address = le64_to_cpu(sasinf->sas_address);
                h_port[port_idx].handle = attached_handle;
                h_port[port_idx].phy_mask = (1 << i);
                h_port[port_idx].iounit_port_id = sas_io_unit_pg0->phy_data[i].io_unit_port;
                h_port[port_idx].lowest_phy = sasinf->phy_num;
                h_port[port_idx].used = 0;
                host_port_count++;
        }

        if (!host_port_count)
                goto out;

        if (mrioc->logging_level & MPI3_DEBUG_RESET) {
                ioc_info(mrioc, "Host port details before reset\n");
                list_for_each_entry(mr_sas_port, &mrioc->sas_hba.sas_port_list,
                    port_list) {
                        ioc_info(mrioc,
                            "port_id:%d, sas_address:(0x%016llx), phy_mask:(0x%llx), lowest phy id:%d\n",
                            mr_sas_port->hba_port->port_id,
                            mr_sas_port->remote_identify.sas_address,
                            mr_sas_port->phy_mask, mr_sas_port->lowest_phy);
                }
                mr_sas_port = NULL;
                ioc_info(mrioc, "Host port details after reset\n");
                for (i = 0; i < host_port_count; i++) {
                        ioc_info(mrioc,
                            "port_id:%d, sas_address:(0x%016llx), phy_mask:(0x%llx), lowest phy id:%d\n",
                            h_port[i].iounit_port_id, h_port[i].sas_address,
                            h_port[i].phy_mask, h_port[i].lowest_phy);
                }
        }

        /* mark all host sas port entries as dirty */
        list_for_each_entry(mr_sas_port, &mrioc->sas_hba.sas_port_list,
            port_list) {
                mr_sas_port->marked_responding = 0;
                mr_sas_port->hba_port->flags |= MPI3MR_HBA_PORT_FLAG_DIRTY;
        }

        /* First check for matching lowest phy */
        for (i = 0; i < host_port_count; i++) {
                mr_sas_port = NULL;
                list_for_each_entry(mr_sas_port, &mrioc->sas_hba.sas_port_list,
                    port_list) {
                        if (mr_sas_port->marked_responding)
                                continue;
                        if (h_port[i].sas_address != mr_sas_port->remote_identify.sas_address)
                                continue;
                        if (h_port[i].lowest_phy == mr_sas_port->lowest_phy) {
                                mpi3mr_update_mr_sas_port(mrioc, &h_port[i], mr_sas_port);
                                break;
                        }
                }
        }

        /* In case if lowest phy is got enabled or disabled during reset */
        for (i = 0; i < host_port_count; i++) {
                if (h_port[i].used)
                        continue;
                mr_sas_port = NULL;
                list_for_each_entry(mr_sas_port, &mrioc->sas_hba.sas_port_list,
                    port_list) {
                        if (mr_sas_port->marked_responding)
                                continue;
                        if (h_port[i].sas_address != mr_sas_port->remote_identify.sas_address)
                                continue;
                        if (h_port[i].phy_mask & mr_sas_port->phy_mask) {
                                mpi3mr_update_mr_sas_port(mrioc, &h_port[i], mr_sas_port);
                                break;
                        }
                }
        }

        /* In case if expander cable is removed & connected to another HBA port during reset */
        for (i = 0; i < host_port_count; i++) {
                if (h_port[i].used)
                        continue;
                mr_sas_port = NULL;
                list_for_each_entry(mr_sas_port, &mrioc->sas_hba.sas_port_list,
                    port_list) {
                        if (mr_sas_port->marked_responding)
                                continue;
                        if (h_port[i].sas_address != mr_sas_port->remote_identify.sas_address)
                                continue;
                        mpi3mr_update_mr_sas_port(mrioc, &h_port[i], mr_sas_port);
                        break;
                }
        }
out:
        kfree(h_port);
        kfree(sas_io_unit_pg0);
}

/**
 * mpi3mr_refresh_expanders - Refresh expander device exposure
 * @mrioc: Adapter instance reference
 *
 * This is executed post controller reset to identify any
 * missing expander devices during reset and remove from the upper layers
 * or expose any newly detected expander device to the upper layers.
 *
 * Return: Nothing.
 */
void
mpi3mr_refresh_expanders(struct mpi3mr_ioc *mrioc)
{
        struct mpi3mr_sas_node *sas_expander, *sas_expander_next;
        struct mpi3_sas_expander_page0 expander_pg0;
        u16 ioc_status, handle;
        u64 sas_address;
        int i;
        unsigned long flags;
        struct mpi3mr_hba_port *hba_port;

        spin_lock_irqsave(&mrioc->sas_node_lock, flags);
        list_for_each_entry(sas_expander, &mrioc->sas_expander_list, list) {
                sas_expander->non_responding = 1;
        }
        spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);

        sas_expander = NULL;

        handle = 0xffff;

        /* Search for responding expander devices and add them if they are newly got added */
        while (true) {
                if ((mpi3mr_cfg_get_sas_exp_pg0(mrioc, &ioc_status, &expander_pg0,
                    sizeof(struct mpi3_sas_expander_page0),
                    MPI3_SAS_EXPAND_PGAD_FORM_GET_NEXT_HANDLE, handle))) {
                        dprint_reset(mrioc,
                            "failed to read exp pg0 for handle(0x%04x) at %s:%d/%s()!\n",
                            handle, __FILE__, __LINE__, __func__);
                        break;
                }

                if (ioc_status != MPI3_IOCSTATUS_SUCCESS) {
                        dprint_reset(mrioc,
                           "ioc_status(0x%x) while reading exp pg0 for handle:(0x%04x), %s:%d/%s()!\n",
                           ioc_status, handle, __FILE__, __LINE__, __func__);
                        break;
                }

                handle = le16_to_cpu(expander_pg0.dev_handle);
                sas_address = le64_to_cpu(expander_pg0.sas_address);
                hba_port = mpi3mr_get_hba_port_by_id(mrioc, expander_pg0.io_unit_port);

                if (!hba_port) {
                        mpi3mr_sas_host_refresh(mrioc);
                        mpi3mr_expander_add(mrioc, handle);
                        continue;
                }

                spin_lock_irqsave(&mrioc->sas_node_lock, flags);
                sas_expander =
                    mpi3mr_expander_find_by_sas_address(mrioc,
                    sas_address, hba_port);
                spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);

                if (!sas_expander) {
                        mpi3mr_sas_host_refresh(mrioc);
                        mpi3mr_expander_add(mrioc, handle);
                        continue;
                }

                sas_expander->non_responding = 0;
                if (sas_expander->handle == handle)
                        continue;

                sas_expander->handle = handle;
                for (i = 0 ; i < sas_expander->num_phys ; i++)
                        sas_expander->phy[i].handle = handle;
        }

        /*
         * Delete non responding expander devices and the corresponding
         * hba_port if the non responding expander device's parent device
         * is a host node.
         */
        sas_expander = NULL;
        spin_lock_irqsave(&mrioc->sas_node_lock, flags);
        list_for_each_entry_safe_reverse(sas_expander, sas_expander_next,
            &mrioc->sas_expander_list, list) {
                if (sas_expander->non_responding) {
                        spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);
                        mpi3mr_expander_node_remove(mrioc, sas_expander);
                        spin_lock_irqsave(&mrioc->sas_node_lock, flags);
                }
        }
        spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);
}

/**
 * mpi3mr_expander_node_add - insert an expander to the list.
 * @mrioc: Adapter instance reference
 * @sas_expander: Expander sas node
 * Context: This function will acquire sas_node_lock.
 *
 * Adding new object to the ioc->sas_expander_list.
 *
 * Return: None.
 */
static void mpi3mr_expander_node_add(struct mpi3mr_ioc *mrioc,
        struct mpi3mr_sas_node *sas_expander)
{
        unsigned long flags;

        spin_lock_irqsave(&mrioc->sas_node_lock, flags);
        list_add_tail(&sas_expander->list, &mrioc->sas_expander_list);
        spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);
}

/**
 * mpi3mr_expander_add -  Create expander object
 * @mrioc: Adapter instance reference
 * @handle: Expander firmware device handle
 *
 * This function creating expander object, stored in
 * sas_expander_list and expose it to the SAS transport
 * layer.
 *
 * Return: 0 for success, non-zero for failure.
 */
int mpi3mr_expander_add(struct mpi3mr_ioc *mrioc, u16 handle)
{
        struct mpi3mr_sas_node *sas_expander;
        struct mpi3mr_enclosure_node *enclosure_dev;
        struct mpi3_sas_expander_page0 expander_pg0;
        struct mpi3_sas_expander_page1 expander_pg1;
        u16 ioc_status, parent_handle, temp_handle;
        u64 sas_address, sas_address_parent = 0;
        int i;
        unsigned long flags;
        u8 port_id, link_rate;
        struct mpi3mr_sas_port *mr_sas_port = NULL;
        struct mpi3mr_hba_port *hba_port;
        u32 phynum_handle;
        int rc = 0;

        if (!handle)
                return -1;

        if (mrioc->reset_in_progress || mrioc->pci_err_recovery)
                return -1;

        if ((mpi3mr_cfg_get_sas_exp_pg0(mrioc, &ioc_status, &expander_pg0,
            sizeof(expander_pg0), MPI3_SAS_EXPAND_PGAD_FORM_HANDLE, handle))) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                return -1;
        }

        if (ioc_status != MPI3_IOCSTATUS_SUCCESS) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                return -1;
        }

        parent_handle = le16_to_cpu(expander_pg0.parent_dev_handle);
        if (mpi3mr_get_sas_address(mrioc, parent_handle, &sas_address_parent)
            != 0) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                return -1;
        }

        port_id = expander_pg0.io_unit_port;
        hba_port = mpi3mr_get_hba_port_by_id(mrioc, port_id);
        if (!hba_port) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                return -1;
        }

        if (sas_address_parent != mrioc->sas_hba.sas_address) {
                spin_lock_irqsave(&mrioc->sas_node_lock, flags);
                sas_expander =
                   mpi3mr_expander_find_by_sas_address(mrioc,
                    sas_address_parent, hba_port);
                spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);
                if (!sas_expander) {
                        rc = mpi3mr_expander_add(mrioc, parent_handle);
                        if (rc != 0)
                                return rc;
                } else {
                        /*
                         * When there is a parent expander present, update it's
                         * phys where child expander is connected with the link
                         * speed, attached dev handle and sas address.
                         */
                        for (i = 0 ; i < sas_expander->num_phys ; i++) {
                                phynum_handle =
                                    (i << MPI3_SAS_EXPAND_PGAD_PHYNUM_SHIFT) |
                                    parent_handle;
                                if (mpi3mr_cfg_get_sas_exp_pg1(mrioc,
                                    &ioc_status, &expander_pg1,
                                    sizeof(expander_pg1),
                                    MPI3_SAS_EXPAND_PGAD_FORM_HANDLE_PHY_NUM,
                                    phynum_handle)) {
                                        ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                                            __FILE__, __LINE__, __func__);
                                        rc = -1;
                                        return rc;
                                }
                                if (ioc_status != MPI3_IOCSTATUS_SUCCESS) {
                                        ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                                            __FILE__, __LINE__, __func__);
                                        rc = -1;
                                        return rc;
                                }
                                temp_handle = le16_to_cpu(
                                    expander_pg1.attached_dev_handle);
                                if (temp_handle != handle)
                                        continue;
                                link_rate = (expander_pg1.negotiated_link_rate &
                                    MPI3_SAS_NEG_LINK_RATE_LOGICAL_MASK) >>
                                    MPI3_SAS_NEG_LINK_RATE_LOGICAL_SHIFT;
                                if (link_rate < MPI3_SAS_NEG_LINK_RATE_1_5)
                                        link_rate = MPI3_SAS_NEG_LINK_RATE_1_5;
                                mpi3mr_update_links(mrioc, sas_address_parent,
                                    handle, i, link_rate, hba_port);
                        }
                }
        }

        spin_lock_irqsave(&mrioc->sas_node_lock, flags);
        sas_address = le64_to_cpu(expander_pg0.sas_address);
        sas_expander = mpi3mr_expander_find_by_sas_address(mrioc,
            sas_address, hba_port);
        spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);

        if (sas_expander)
                return 0;

        sas_expander = kzalloc_obj(struct mpi3mr_sas_node);
        if (!sas_expander)
                return -ENOMEM;

        sas_expander->handle = handle;
        sas_expander->num_phys = expander_pg0.num_phys;
        sas_expander->sas_address_parent = sas_address_parent;
        sas_expander->sas_address = sas_address;
        sas_expander->hba_port = hba_port;

        ioc_info(mrioc,
            "expander_add: handle(0x%04x), parent(0x%04x), sas_addr(0x%016llx), phys(%d)\n",
            handle, parent_handle, (unsigned long long)
            sas_expander->sas_address, sas_expander->num_phys);

        if (!sas_expander->num_phys) {
                rc = -1;
                goto out_fail;
        }
        sas_expander->phy = kzalloc_objs(struct mpi3mr_sas_phy,
                                         sas_expander->num_phys);
        if (!sas_expander->phy) {
                rc = -1;
                goto out_fail;
        }

        INIT_LIST_HEAD(&sas_expander->sas_port_list);
        mr_sas_port = mpi3mr_sas_port_add(mrioc, handle, sas_address_parent,
            sas_expander->hba_port);
        if (!mr_sas_port) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                rc = -1;
                goto out_fail;
        }
        sas_expander->parent_dev = &mr_sas_port->rphy->dev;
        sas_expander->rphy = mr_sas_port->rphy;

        for (i = 0 ; i < sas_expander->num_phys ; i++) {
                phynum_handle = (i << MPI3_SAS_EXPAND_PGAD_PHYNUM_SHIFT) |
                    handle;
                if (mpi3mr_cfg_get_sas_exp_pg1(mrioc, &ioc_status,
                    &expander_pg1, sizeof(expander_pg1),
                    MPI3_SAS_EXPAND_PGAD_FORM_HANDLE_PHY_NUM,
                    phynum_handle)) {
                        ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                            __FILE__, __LINE__, __func__);
                        rc = -1;
                        goto out_fail;
                }
                if (ioc_status != MPI3_IOCSTATUS_SUCCESS) {
                        ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                            __FILE__, __LINE__, __func__);
                        rc = -1;
                        goto out_fail;
                }

                sas_expander->phy[i].handle = handle;
                sas_expander->phy[i].phy_id = i;
                sas_expander->phy[i].hba_port = hba_port;

                if ((mpi3mr_add_expander_phy(mrioc, &sas_expander->phy[i],
                    expander_pg1, sas_expander->parent_dev))) {
                        ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                            __FILE__, __LINE__, __func__);
                        rc = -1;
                        goto out_fail;
                }
        }

        if (sas_expander->enclosure_handle) {
                enclosure_dev =
                        mpi3mr_enclosure_find_by_handle(mrioc,
                                                sas_expander->enclosure_handle);
                if (enclosure_dev)
                        sas_expander->enclosure_logical_id = le64_to_cpu(
                            enclosure_dev->pg0.enclosure_logical_id);
        }

        mpi3mr_expander_node_add(mrioc, sas_expander);
        return 0;

out_fail:

        if (mr_sas_port)
                mpi3mr_sas_port_remove(mrioc,
                    sas_expander->sas_address,
                    sas_address_parent, sas_expander->hba_port);
        kfree(sas_expander->phy);
        kfree(sas_expander);
        return rc;
}

/**
 * mpi3mr_expander_node_remove - recursive removal of expander.
 * @mrioc: Adapter instance reference
 * @sas_expander: Expander device object
 *
 * Removes expander object and freeing associated memory from
 * the sas_expander_list and removes the same from SAS TL, if
 * one of the attached device is an expander then it recursively
 * removes the expander device too.
 *
 * Return nothing.
 */
void mpi3mr_expander_node_remove(struct mpi3mr_ioc *mrioc,
        struct mpi3mr_sas_node *sas_expander)
{
        struct mpi3mr_sas_port *mr_sas_port, *next;
        unsigned long flags;
        u8 port_id;

        /* remove sibling ports attached to this expander */
        list_for_each_entry_safe(mr_sas_port, next,
           &sas_expander->sas_port_list, port_list) {
                if (mrioc->reset_in_progress || mrioc->pci_err_recovery)
                        return;
                if (mr_sas_port->remote_identify.device_type ==
                    SAS_END_DEVICE)
                        mpi3mr_remove_device_by_sas_address(mrioc,
                            mr_sas_port->remote_identify.sas_address,
                            mr_sas_port->hba_port);
                else if (mr_sas_port->remote_identify.device_type ==
                    SAS_EDGE_EXPANDER_DEVICE ||
                    mr_sas_port->remote_identify.device_type ==
                    SAS_FANOUT_EXPANDER_DEVICE)
                        mpi3mr_expander_remove(mrioc,
                            mr_sas_port->remote_identify.sas_address,
                            mr_sas_port->hba_port);
        }

        port_id = sas_expander->hba_port->port_id;
        mpi3mr_sas_port_remove(mrioc, sas_expander->sas_address,
            sas_expander->sas_address_parent, sas_expander->hba_port);

        ioc_info(mrioc, "expander_remove: handle(0x%04x), sas_addr(0x%016llx), port:%d\n",
            sas_expander->handle, (unsigned long long)
            sas_expander->sas_address, port_id);

        spin_lock_irqsave(&mrioc->sas_node_lock, flags);
        list_del(&sas_expander->list);
        spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);

        kfree(sas_expander->phy);
        kfree(sas_expander);
}

/**
 * mpi3mr_expander_remove - Remove expander object
 * @mrioc: Adapter instance reference
 * @sas_address: Remove expander sas_address
 * @hba_port: HBA port reference
 *
 * This function remove expander object, stored in
 * mrioc->sas_expander_list and removes it from the SAS TL by
 * calling mpi3mr_expander_node_remove().
 *
 * Return: None
 */
void mpi3mr_expander_remove(struct mpi3mr_ioc *mrioc, u64 sas_address,
        struct mpi3mr_hba_port *hba_port)
{
        struct mpi3mr_sas_node *sas_expander;
        unsigned long flags;

        if (mrioc->reset_in_progress || mrioc->pci_err_recovery)
                return;

        if (!hba_port)
                return;

        spin_lock_irqsave(&mrioc->sas_node_lock, flags);
        sas_expander = mpi3mr_expander_find_by_sas_address(mrioc, sas_address,
            hba_port);
        spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);
        if (sas_expander)
                mpi3mr_expander_node_remove(mrioc, sas_expander);

}

/**
 * mpi3mr_get_sas_negotiated_logical_linkrate - get linkrate
 * @mrioc: Adapter instance reference
 * @tgtdev: Target device
 *
 * This function first tries to use the link rate from DevicePage0
 * (populated by firmware during device discovery). If the cached
 * value is not available or invalid, it falls back to reading from
 * sas phy page0 or expander phy page1.
 *
 *
 * Return: logical link rate.
 */
static u8 mpi3mr_get_sas_negotiated_logical_linkrate(struct mpi3mr_ioc *mrioc,
        struct mpi3mr_tgt_dev *tgtdev)
{
        u8 link_rate = MPI3_SAS_NEG_LINK_RATE_1_5, phy_number;
        struct mpi3_sas_expander_page1 expander_pg1;
        struct mpi3_sas_phy_page0 phy_pg0;
        u32 phynum_handle;
        u16 ioc_status;

        /* First, try to use link rate from DevicePage0 (populated by firmware) */
        if (tgtdev->dev_spec.sas_sata_inf.negotiated_link_rate >=
            MPI3_SAS_NEG_LINK_RATE_1_5) {
                link_rate = tgtdev->dev_spec.sas_sata_inf.negotiated_link_rate;
                goto out;
        }

        /* Fallback to reading from phy pages if DevicePage0 value not available */
        phy_number = tgtdev->dev_spec.sas_sata_inf.phy_id;
        if (!(tgtdev->devpg0_flag & MPI3_DEVICE0_FLAGS_ATT_METHOD_DIR_ATTACHED)) {
                phynum_handle = ((phy_number<<MPI3_SAS_EXPAND_PGAD_PHYNUM_SHIFT)
                                 | tgtdev->parent_handle);
                if (mpi3mr_cfg_get_sas_exp_pg1(mrioc, &ioc_status,
                    &expander_pg1, sizeof(expander_pg1),
                    MPI3_SAS_EXPAND_PGAD_FORM_HANDLE_PHY_NUM,
                    phynum_handle)) {
                        ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                            __FILE__, __LINE__, __func__);
                        goto out;
                }
                if (ioc_status != MPI3_IOCSTATUS_SUCCESS) {
                        ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                            __FILE__, __LINE__, __func__);
                        goto out;
                }
                link_rate = expander_pg1.negotiated_link_rate;
                goto out;
        }
        if (mpi3mr_cfg_get_sas_phy_pg0(mrioc, &ioc_status, &phy_pg0,
            sizeof(struct mpi3_sas_phy_page0),
            MPI3_SAS_PHY_PGAD_FORM_PHY_NUMBER, phy_number)) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                goto out;
        }
        if (ioc_status != MPI3_IOCSTATUS_SUCCESS) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                goto out;
        }
        link_rate = phy_pg0.negotiated_link_rate;

out:
        return ((link_rate & MPI3_SAS_NEG_LINK_RATE_LOGICAL_MASK) >>
                MPI3_SAS_NEG_LINK_RATE_LOGICAL_SHIFT);
}

/**
 * mpi3mr_report_tgtdev_to_sas_transport - expose dev to SAS TL
 * @mrioc: Adapter instance reference
 * @tgtdev: Target device
 *
 * This function exposes the target device after
 * preparing host_phy, setting up link rate etc.
 *
 * Return: 0 on success, non-zero for failure.
 */
int mpi3mr_report_tgtdev_to_sas_transport(struct mpi3mr_ioc *mrioc,
        struct mpi3mr_tgt_dev *tgtdev)
{
        int retval = 0;
        u8 link_rate, parent_phy_number;
        u64 sas_address_parent, sas_address;
        struct mpi3mr_hba_port *hba_port;
        u8 port_id;

        if ((tgtdev->dev_type != MPI3_DEVICE_DEVFORM_SAS_SATA) ||
            !mrioc->sas_transport_enabled)
                return -1;

        sas_address = tgtdev->dev_spec.sas_sata_inf.sas_address;
        if (!mrioc->sas_hba.num_phys)
                mpi3mr_sas_host_add(mrioc);
        else
                mpi3mr_sas_host_refresh(mrioc);

        if (mpi3mr_get_sas_address(mrioc, tgtdev->parent_handle,
            &sas_address_parent) != 0) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                return -1;
        }
        tgtdev->dev_spec.sas_sata_inf.sas_address_parent = sas_address_parent;

        parent_phy_number = tgtdev->dev_spec.sas_sata_inf.phy_id;
        port_id = tgtdev->io_unit_port;

        hba_port = mpi3mr_get_hba_port_by_id(mrioc, port_id);
        if (!hba_port) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                return -1;
        }
        tgtdev->dev_spec.sas_sata_inf.hba_port = hba_port;

        link_rate = mpi3mr_get_sas_negotiated_logical_linkrate(mrioc, tgtdev);

        if (link_rate < MPI3_SAS_NEG_LINK_RATE_1_5)
                link_rate = MPI3_SAS_NEG_LINK_RATE_1_5;

        mpi3mr_update_links(mrioc, sas_address_parent, tgtdev->dev_handle,
            parent_phy_number, link_rate, hba_port);

        tgtdev->host_exposed = 1;
        if (!mpi3mr_sas_port_add(mrioc, tgtdev->dev_handle,
            sas_address_parent, hba_port)) {
                retval = -1;
                } else if ((!tgtdev->starget) && (!mrioc->is_driver_loading)) {
                        mpi3mr_sas_port_remove(mrioc, sas_address,
                            sas_address_parent, hba_port);
                retval = -1;
        }
        if (retval) {
                tgtdev->dev_spec.sas_sata_inf.hba_port = NULL;
                tgtdev->host_exposed = 0;
        }
        return retval;
}

/**
 * mpi3mr_remove_tgtdev_from_sas_transport - remove from SAS TL
 * @mrioc: Adapter instance reference
 * @tgtdev: Target device
 *
 * This function removes the target device
 *
 * Return: None.
 */
void mpi3mr_remove_tgtdev_from_sas_transport(struct mpi3mr_ioc *mrioc,
        struct mpi3mr_tgt_dev *tgtdev)
{
        u64 sas_address_parent, sas_address;
        struct mpi3mr_hba_port *hba_port;

        if ((tgtdev->dev_type != MPI3_DEVICE_DEVFORM_SAS_SATA) ||
            !mrioc->sas_transport_enabled)
                return;

        hba_port = tgtdev->dev_spec.sas_sata_inf.hba_port;
        sas_address = tgtdev->dev_spec.sas_sata_inf.sas_address;
        sas_address_parent = tgtdev->dev_spec.sas_sata_inf.sas_address_parent;
        mpi3mr_sas_port_remove(mrioc, sas_address, sas_address_parent,
            hba_port);
        tgtdev->host_exposed = 0;
        tgtdev->dev_spec.sas_sata_inf.hba_port = NULL;
}

/**
 * mpi3mr_get_port_id_by_sas_phy -  Get port ID of the given phy
 * @phy: SAS transport layer phy object
 *
 * Return: Port number for valid ID else 0xFFFF
 */
static inline u8 mpi3mr_get_port_id_by_sas_phy(struct sas_phy *phy)
{
        u8 port_id = 0xFF;
        struct mpi3mr_hba_port *hba_port = phy->hostdata;

        if (hba_port)
                port_id = hba_port->port_id;

        return port_id;
}

/**
 * mpi3mr_get_port_id_by_rphy - Get Port number from SAS rphy
 *
 * @mrioc: Adapter instance reference
 * @rphy: SAS transport layer remote phy object
 *
 * Retrieves HBA port number in which the device pointed by the
 * rphy object is attached with.
 *
 * Return: Valid port number on success else OxFFFF.
 */
static u8 mpi3mr_get_port_id_by_rphy(struct mpi3mr_ioc *mrioc, struct sas_rphy *rphy)
{
        struct mpi3mr_sas_node *sas_expander;
        struct mpi3mr_tgt_dev *tgtdev;
        unsigned long flags;
        u8 port_id = 0xFF;

        if (!rphy)
                return port_id;

        if (rphy->identify.device_type == SAS_EDGE_EXPANDER_DEVICE ||
            rphy->identify.device_type == SAS_FANOUT_EXPANDER_DEVICE) {
                spin_lock_irqsave(&mrioc->sas_node_lock, flags);
                list_for_each_entry(sas_expander, &mrioc->sas_expander_list,
                    list) {
                        if (sas_expander->rphy == rphy) {
                                port_id = sas_expander->hba_port->port_id;
                                break;
                        }
                }
                spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);
        } else if (rphy->identify.device_type == SAS_END_DEVICE) {
                spin_lock_irqsave(&mrioc->tgtdev_lock, flags);

                tgtdev = __mpi3mr_get_tgtdev_by_addr_and_rphy(mrioc,
                            rphy->identify.sas_address, rphy);
                if (tgtdev && tgtdev->dev_spec.sas_sata_inf.hba_port) {
                        port_id =
                                tgtdev->dev_spec.sas_sata_inf.hba_port->port_id;
                        mpi3mr_tgtdev_put(tgtdev);
                }
                spin_unlock_irqrestore(&mrioc->tgtdev_lock, flags);
        }
        return port_id;
}

static inline struct mpi3mr_ioc *phy_to_mrioc(struct sas_phy *phy)
{
        struct Scsi_Host *shost = dev_to_shost(phy->dev.parent);

        return shost_priv(shost);
}

static inline struct mpi3mr_ioc *rphy_to_mrioc(struct sas_rphy *rphy)
{
        struct Scsi_Host *shost = dev_to_shost(rphy->dev.parent->parent);

        return shost_priv(shost);
}

/* report phy error log structure */
struct phy_error_log_request {
        u8 smp_frame_type; /* 0x40 */
        u8 function; /* 0x11 */
        u8 allocated_response_length;
        u8 request_length; /* 02 */
        u8 reserved_1[5];
        u8 phy_identifier;
        u8 reserved_2[2];
};

/* report phy error log reply structure */
struct phy_error_log_reply {
        u8 smp_frame_type; /* 0x41 */
        u8 function; /* 0x11 */
        u8 function_result;
        u8 response_length;
        __be16 expander_change_count;
        u8 reserved_1[3];
        u8 phy_identifier;
        u8 reserved_2[2];
        __be32 invalid_dword;
        __be32 running_disparity_error;
        __be32 loss_of_dword_sync;
        __be32 phy_reset_problem;
};


/**
 * mpi3mr_get_expander_phy_error_log - return expander counters:
 * @mrioc: Adapter instance reference
 * @phy: The SAS transport layer phy object
 *
 * Return: 0 for success, non-zero for failure.
 *
 */
static int mpi3mr_get_expander_phy_error_log(struct mpi3mr_ioc *mrioc,
        struct sas_phy *phy)
{
        struct mpi3_smp_passthrough_request mpi_request;
        struct mpi3_smp_passthrough_reply mpi_reply;
        struct phy_error_log_request *phy_error_log_request;
        struct phy_error_log_reply *phy_error_log_reply;
        int rc;
        void *psge;
        void *data_out = NULL;
        dma_addr_t data_out_dma, data_in_dma;
        u32 data_out_sz, data_in_sz, sz;
        u8 sgl_flags = MPI3MR_SGEFLAGS_SYSTEM_SIMPLE_END_OF_LIST;
        u16 request_sz = sizeof(struct mpi3_smp_passthrough_request);
        u16 reply_sz = sizeof(struct mpi3_smp_passthrough_reply);
        u16 ioc_status;

        if (mrioc->reset_in_progress) {
                ioc_err(mrioc, "%s: host reset in progress!\n", __func__);
                return -EFAULT;
        }

        if (mrioc->pci_err_recovery) {
                ioc_err(mrioc, "%s: pci error recovery in progress!\n", __func__);
                return -EFAULT;
        }

        data_out_sz = sizeof(struct phy_error_log_request);
        data_in_sz = sizeof(struct phy_error_log_reply);
        sz = data_out_sz + data_in_sz;
        data_out = dma_alloc_coherent(&mrioc->pdev->dev, sz, &data_out_dma,
            GFP_KERNEL);
        if (!data_out) {
                rc = -ENOMEM;
                goto out;
        }

        data_in_dma = data_out_dma + data_out_sz;
        phy_error_log_reply = data_out + data_out_sz;

        rc = -EINVAL;
        memset(data_out, 0, sz);
        phy_error_log_request = data_out;
        phy_error_log_request->smp_frame_type = 0x40;
        phy_error_log_request->function = 0x11;
        phy_error_log_request->request_length = 2;
        phy_error_log_request->allocated_response_length = 0;
        phy_error_log_request->phy_identifier = phy->number;

        memset(&mpi_request, 0, request_sz);
        memset(&mpi_reply, 0, reply_sz);
        mpi_request.host_tag = cpu_to_le16(MPI3MR_HOSTTAG_TRANSPORT_CMDS);
        mpi_request.function = MPI3_FUNCTION_SMP_PASSTHROUGH;
        mpi_request.io_unit_port = (u8) mpi3mr_get_port_id_by_sas_phy(phy);
        mpi_request.sas_address = cpu_to_le64(phy->identify.sas_address);

        psge = &mpi_request.request_sge;
        mpi3mr_add_sg_single(psge, sgl_flags, data_out_sz, data_out_dma);

        psge = &mpi_request.response_sge;
        mpi3mr_add_sg_single(psge, sgl_flags, data_in_sz, data_in_dma);

        dprint_transport_info(mrioc,
            "sending phy error log SMP request to sas_address(0x%016llx), phy_id(%d)\n",
            (unsigned long long)phy->identify.sas_address, phy->number);

        if (mpi3mr_post_transport_req(mrioc, &mpi_request, request_sz,
            &mpi_reply, reply_sz, MPI3MR_INTADMCMD_TIMEOUT, &ioc_status))
                goto out;

        dprint_transport_info(mrioc,
            "phy error log SMP request completed with ioc_status(0x%04x)\n",
            ioc_status);

        if (ioc_status == MPI3_IOCSTATUS_SUCCESS) {
                dprint_transport_info(mrioc,
                    "phy error log - reply data transfer size(%d)\n",
                    le16_to_cpu(mpi_reply.response_data_length));

                if (le16_to_cpu(mpi_reply.response_data_length) !=
                    sizeof(struct phy_error_log_reply))
                        goto out;

                dprint_transport_info(mrioc,
                    "phy error log - function_result(%d)\n",
                    phy_error_log_reply->function_result);

                phy->invalid_dword_count =
                    be32_to_cpu(phy_error_log_reply->invalid_dword);
                phy->running_disparity_error_count =
                    be32_to_cpu(phy_error_log_reply->running_disparity_error);
                phy->loss_of_dword_sync_count =
                    be32_to_cpu(phy_error_log_reply->loss_of_dword_sync);
                phy->phy_reset_problem_count =
                    be32_to_cpu(phy_error_log_reply->phy_reset_problem);
                rc = 0;
        }

out:
        if (data_out)
                dma_free_coherent(&mrioc->pdev->dev, sz, data_out,
                    data_out_dma);

        return rc;
}

/**
 * mpi3mr_transport_get_linkerrors - return phy error counters
 * @phy: The SAS transport layer phy object
 *
 * This function retrieves the phy error log information of the
 * HBA or expander for which the phy belongs to
 *
 * Return: 0 for success, non-zero for failure.
 */
static int mpi3mr_transport_get_linkerrors(struct sas_phy *phy)
{
        struct mpi3mr_ioc *mrioc = phy_to_mrioc(phy);
        struct mpi3_sas_phy_page1 phy_pg1;
        int rc = 0;
        u16 ioc_status;

        rc = mpi3mr_parent_present(mrioc, phy);
        if (rc)
                return rc;

        if (phy->identify.sas_address != mrioc->sas_hba.sas_address)
                return mpi3mr_get_expander_phy_error_log(mrioc, phy);

        memset(&phy_pg1, 0, sizeof(struct mpi3_sas_phy_page1));
        /* get hba phy error logs */
        if ((mpi3mr_cfg_get_sas_phy_pg1(mrioc, &ioc_status, &phy_pg1,
            sizeof(struct mpi3_sas_phy_page1),
            MPI3_SAS_PHY_PGAD_FORM_PHY_NUMBER, phy->number))) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                return -ENXIO;
        }

        if (ioc_status != MPI3_IOCSTATUS_SUCCESS) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                return -ENXIO;
        }
        phy->invalid_dword_count = le32_to_cpu(phy_pg1.invalid_dword_count);
        phy->running_disparity_error_count =
                le32_to_cpu(phy_pg1.running_disparity_error_count);
        phy->loss_of_dword_sync_count =
                le32_to_cpu(phy_pg1.loss_dword_synch_count);
        phy->phy_reset_problem_count =
                le32_to_cpu(phy_pg1.phy_reset_problem_count);
        return 0;
}

/**
 * mpi3mr_transport_get_enclosure_identifier - Get Enclosure ID
 * @rphy: The SAS transport layer remote phy object
 * @identifier: Enclosure identifier to be returned
 *
 * Returns the enclosure id for the device pointed by the remote
 * phy object.
 *
 * Return: 0 on success or -ENXIO
 */
static int
mpi3mr_transport_get_enclosure_identifier(struct sas_rphy *rphy,
        u64 *identifier)
{
        struct mpi3mr_ioc *mrioc = rphy_to_mrioc(rphy);
        struct mpi3mr_tgt_dev *tgtdev = NULL;
        unsigned long flags;
        int rc;

        spin_lock_irqsave(&mrioc->tgtdev_lock, flags);
        tgtdev = __mpi3mr_get_tgtdev_by_addr_and_rphy(mrioc,
            rphy->identify.sas_address, rphy);
        if (tgtdev) {
                *identifier =
                        tgtdev->enclosure_logical_id;
                rc = 0;
                mpi3mr_tgtdev_put(tgtdev);
        } else {
                *identifier = 0;
                rc = -ENXIO;
        }
        spin_unlock_irqrestore(&mrioc->tgtdev_lock, flags);

        return rc;
}

/**
 * mpi3mr_transport_get_bay_identifier - Get bay ID
 * @rphy: The SAS transport layer remote phy object
 *
 * Returns the slot id for the device pointed by the remote phy
 * object.
 *
 * Return: Valid slot ID on success or -ENXIO
 */
static int
mpi3mr_transport_get_bay_identifier(struct sas_rphy *rphy)
{
        struct mpi3mr_ioc *mrioc = rphy_to_mrioc(rphy);
        struct mpi3mr_tgt_dev *tgtdev = NULL;
        unsigned long flags;
        int rc;

        spin_lock_irqsave(&mrioc->tgtdev_lock, flags);
        tgtdev = __mpi3mr_get_tgtdev_by_addr_and_rphy(mrioc,
            rphy->identify.sas_address, rphy);
        if (tgtdev) {
                rc = tgtdev->slot;
                mpi3mr_tgtdev_put(tgtdev);
        } else
                rc = -ENXIO;
        spin_unlock_irqrestore(&mrioc->tgtdev_lock, flags);

        return rc;
}

/* phy control request structure */
struct phy_control_request {
        u8 smp_frame_type; /* 0x40 */
        u8 function; /* 0x91 */
        u8 allocated_response_length;
        u8 request_length; /* 0x09 */
        u16 expander_change_count;
        u8 reserved_1[3];
        u8 phy_identifier;
        u8 phy_operation;
        u8 reserved_2[13];
        u64 attached_device_name;
        u8 programmed_min_physical_link_rate;
        u8 programmed_max_physical_link_rate;
        u8 reserved_3[6];
};

/* phy control reply structure */
struct phy_control_reply {
        u8 smp_frame_type; /* 0x41 */
        u8 function; /* 0x11 */
        u8 function_result;
        u8 response_length;
};

#define SMP_PHY_CONTROL_LINK_RESET      (0x01)
#define SMP_PHY_CONTROL_HARD_RESET      (0x02)
#define SMP_PHY_CONTROL_DISABLE         (0x03)

/**
 * mpi3mr_expander_phy_control - expander phy control
 * @mrioc: Adapter instance reference
 * @phy: The SAS transport layer phy object
 * @phy_operation: The phy operation to be executed
 *
 * Issues SMP passthru phy control request to execute a specific
 * phy operation for a given expander device.
 *
 * Return: 0 for success, non-zero for failure.
 */
static int
mpi3mr_expander_phy_control(struct mpi3mr_ioc *mrioc,
        struct sas_phy *phy, u8 phy_operation)
{
        struct mpi3_smp_passthrough_request mpi_request;
        struct mpi3_smp_passthrough_reply mpi_reply;
        struct phy_control_request *phy_control_request;
        struct phy_control_reply *phy_control_reply;
        int rc;
        void *psge;
        void *data_out = NULL;
        dma_addr_t data_out_dma;
        dma_addr_t data_in_dma;
        size_t data_in_sz;
        size_t data_out_sz;
        u8 sgl_flags = MPI3MR_SGEFLAGS_SYSTEM_SIMPLE_END_OF_LIST;
        u16 request_sz = sizeof(struct mpi3_smp_passthrough_request);
        u16 reply_sz = sizeof(struct mpi3_smp_passthrough_reply);
        u16 ioc_status;
        u16 sz;

        if (mrioc->reset_in_progress) {
                ioc_err(mrioc, "%s: host reset in progress!\n", __func__);
                return -EFAULT;
        }

        if (mrioc->pci_err_recovery) {
                ioc_err(mrioc, "%s: pci error recovery in progress!\n",
                    __func__);
                return -EFAULT;
        }

        data_out_sz = sizeof(struct phy_control_request);
        data_in_sz = sizeof(struct phy_control_reply);
        sz = data_out_sz + data_in_sz;
        data_out = dma_alloc_coherent(&mrioc->pdev->dev, sz, &data_out_dma,
            GFP_KERNEL);
        if (!data_out) {
                rc = -ENOMEM;
                goto out;
        }

        data_in_dma = data_out_dma + data_out_sz;
        phy_control_reply = data_out + data_out_sz;

        rc = -EINVAL;
        memset(data_out, 0, sz);

        phy_control_request = data_out;
        phy_control_request->smp_frame_type = 0x40;
        phy_control_request->function = 0x91;
        phy_control_request->request_length = 9;
        phy_control_request->allocated_response_length = 0;
        phy_control_request->phy_identifier = phy->number;
        phy_control_request->phy_operation = phy_operation;
        phy_control_request->programmed_min_physical_link_rate =
            phy->minimum_linkrate << 4;
        phy_control_request->programmed_max_physical_link_rate =
            phy->maximum_linkrate << 4;

        memset(&mpi_request, 0, request_sz);
        memset(&mpi_reply, 0, reply_sz);
        mpi_request.host_tag = cpu_to_le16(MPI3MR_HOSTTAG_TRANSPORT_CMDS);
        mpi_request.function = MPI3_FUNCTION_SMP_PASSTHROUGH;
        mpi_request.io_unit_port = (u8) mpi3mr_get_port_id_by_sas_phy(phy);
        mpi_request.sas_address = cpu_to_le64(phy->identify.sas_address);

        psge = &mpi_request.request_sge;
        mpi3mr_add_sg_single(psge, sgl_flags, data_out_sz, data_out_dma);

        psge = &mpi_request.response_sge;
        mpi3mr_add_sg_single(psge, sgl_flags, data_in_sz, data_in_dma);

        dprint_transport_info(mrioc,
            "sending phy control SMP request to sas_address(0x%016llx), phy_id(%d) opcode(%d)\n",
            (unsigned long long)phy->identify.sas_address, phy->number,
            phy_operation);

        if (mpi3mr_post_transport_req(mrioc, &mpi_request, request_sz,
            &mpi_reply, reply_sz, MPI3MR_INTADMCMD_TIMEOUT, &ioc_status))
                goto out;

        dprint_transport_info(mrioc,
            "phy control SMP request completed with ioc_status(0x%04x)\n",
            ioc_status);

        if (ioc_status == MPI3_IOCSTATUS_SUCCESS) {
                dprint_transport_info(mrioc,
                    "phy control - reply data transfer size(%d)\n",
                    le16_to_cpu(mpi_reply.response_data_length));

                if (le16_to_cpu(mpi_reply.response_data_length) !=
                    sizeof(struct phy_control_reply))
                        goto out;
                dprint_transport_info(mrioc,
                    "phy control - function_result(%d)\n",
                    phy_control_reply->function_result);
                rc = 0;
        }
 out:
        if (data_out)
                dma_free_coherent(&mrioc->pdev->dev, sz, data_out,
                    data_out_dma);

        return rc;
}

/**
 * mpi3mr_transport_phy_reset - Reset a given phy
 * @phy: The SAS transport layer phy object
 * @hard_reset: Flag to indicate the type of reset
 *
 * Return: 0 for success, non-zero for failure.
 */
static int
mpi3mr_transport_phy_reset(struct sas_phy *phy, int hard_reset)
{
        struct mpi3mr_ioc *mrioc = phy_to_mrioc(phy);
        struct mpi3_iounit_control_request mpi_request;
        struct mpi3_iounit_control_reply mpi_reply;
        u16 request_sz = sizeof(struct mpi3_iounit_control_request);
        u16 reply_sz = sizeof(struct mpi3_iounit_control_reply);
        int rc = 0;
        u16 ioc_status;

        rc = mpi3mr_parent_present(mrioc, phy);
        if (rc)
                return rc;

        /* handle expander phys */
        if (phy->identify.sas_address != mrioc->sas_hba.sas_address)
                return mpi3mr_expander_phy_control(mrioc, phy,
                    (hard_reset == 1) ? SMP_PHY_CONTROL_HARD_RESET :
                    SMP_PHY_CONTROL_LINK_RESET);

        /* handle hba phys */
        memset(&mpi_request, 0, request_sz);
        mpi_request.host_tag = cpu_to_le16(MPI3MR_HOSTTAG_TRANSPORT_CMDS);
        mpi_request.function = MPI3_FUNCTION_IO_UNIT_CONTROL;
        mpi_request.operation = MPI3_CTRL_OP_SAS_PHY_CONTROL;
        mpi_request.param8[MPI3_CTRL_OP_SAS_PHY_CONTROL_PARAM8_ACTION_INDEX] =
                (hard_reset ? MPI3_CTRL_ACTION_HARD_RESET :
                 MPI3_CTRL_ACTION_LINK_RESET);
        mpi_request.param8[MPI3_CTRL_OP_SAS_PHY_CONTROL_PARAM8_PHY_INDEX] =
                phy->number;

        dprint_transport_info(mrioc,
            "sending phy reset request to sas_address(0x%016llx), phy_id(%d) hard_reset(%d)\n",
            (unsigned long long)phy->identify.sas_address, phy->number,
            hard_reset);

        if (mpi3mr_post_transport_req(mrioc, &mpi_request, request_sz,
            &mpi_reply, reply_sz, MPI3MR_INTADMCMD_TIMEOUT, &ioc_status)) {
                rc = -EAGAIN;
                goto out;
        }

        dprint_transport_info(mrioc,
            "phy reset request completed with ioc_status(0x%04x)\n",
            ioc_status);
out:
        return rc;
}

/**
 * mpi3mr_transport_phy_enable - enable/disable phys
 * @phy: The SAS transport layer phy object
 * @enable: flag to enable/disable, enable phy when true
 *
 * This function enables/disables a given by executing required
 * configuration page changes or expander phy control command
 *
 * Return: 0 for success, non-zero for failure.
 */
static int
mpi3mr_transport_phy_enable(struct sas_phy *phy, int enable)
{
        struct mpi3mr_ioc *mrioc = phy_to_mrioc(phy);
        struct mpi3_sas_io_unit_page0 *sas_io_unit_pg0 = NULL;
        struct mpi3_sas_io_unit_page1 *sas_io_unit_pg1 = NULL;
        u16 sz;
        int rc = 0;
        int i, discovery_active;

        rc = mpi3mr_parent_present(mrioc, phy);
        if (rc)
                return rc;

        /* handle expander phys */
        if (phy->identify.sas_address != mrioc->sas_hba.sas_address)
                return mpi3mr_expander_phy_control(mrioc, phy,
                    (enable == 1) ? SMP_PHY_CONTROL_LINK_RESET :
                    SMP_PHY_CONTROL_DISABLE);

        /* handle hba phys */
        sz = offsetof(struct mpi3_sas_io_unit_page0, phy_data) +
                (mrioc->sas_hba.num_phys *
                 sizeof(struct mpi3_sas_io_unit0_phy_data));
        sas_io_unit_pg0 = kzalloc(sz, GFP_KERNEL);
        if (!sas_io_unit_pg0) {
                rc = -ENOMEM;
                goto out;
        }
        if (mpi3mr_cfg_get_sas_io_unit_pg0(mrioc, sas_io_unit_pg0, sz)) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                rc = -ENXIO;
                goto out;
        }

        /* unable to enable/disable phys when discovery is active */
        for (i = 0, discovery_active = 0; i < mrioc->sas_hba.num_phys ; i++) {
                if (sas_io_unit_pg0->phy_data[i].port_flags &
                    MPI3_SASIOUNIT0_PORTFLAGS_DISC_IN_PROGRESS) {
                        ioc_err(mrioc,
                            "discovery is active on port = %d, phy = %d\n"
                            "\tunable to enable/disable phys, try again later!\n",
                            sas_io_unit_pg0->phy_data[i].io_unit_port, i);
                        discovery_active = 1;
                }
        }

        if (discovery_active) {
                rc = -EAGAIN;
                goto out;
        }

        if ((sas_io_unit_pg0->phy_data[phy->number].phy_flags &
             (MPI3_SASIOUNIT0_PHYFLAGS_HOST_PHY |
              MPI3_SASIOUNIT0_PHYFLAGS_VIRTUAL_PHY))) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                rc = -ENXIO;
                goto out;
        }

        /* read sas_iounit page 1 */
        sz = offsetof(struct mpi3_sas_io_unit_page1, phy_data) +
                (mrioc->sas_hba.num_phys *
                 sizeof(struct mpi3_sas_io_unit1_phy_data));
        sas_io_unit_pg1 = kzalloc(sz, GFP_KERNEL);
        if (!sas_io_unit_pg1) {
                rc = -ENOMEM;
                goto out;
        }

        if (mpi3mr_cfg_get_sas_io_unit_pg1(mrioc, sas_io_unit_pg1, sz)) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                rc = -ENXIO;
                goto out;
        }

        if (enable)
                sas_io_unit_pg1->phy_data[phy->number].phy_flags
                    &= ~MPI3_SASIOUNIT1_PHYFLAGS_PHY_DISABLE;
        else
                sas_io_unit_pg1->phy_data[phy->number].phy_flags
                    |= MPI3_SASIOUNIT1_PHYFLAGS_PHY_DISABLE;

        mpi3mr_cfg_set_sas_io_unit_pg1(mrioc, sas_io_unit_pg1, sz);

        /* link reset */
        if (enable)
                mpi3mr_transport_phy_reset(phy, 0);

 out:
        kfree(sas_io_unit_pg1);
        kfree(sas_io_unit_pg0);
        return rc;
}

/**
 * mpi3mr_transport_phy_speed - set phy min/max speed
 * @phy: The SAS transport later phy object
 * @rates: Rates defined as in sas_phy_linkrates
 *
 * This function sets the link rates given in the rates
 * argument to the given phy by executing required configuration
 * page changes or expander phy control command
 *
 * Return: 0 for success, non-zero for failure.
 */
static int
mpi3mr_transport_phy_speed(struct sas_phy *phy, struct sas_phy_linkrates *rates)
{
        struct mpi3mr_ioc *mrioc = phy_to_mrioc(phy);
        struct mpi3_sas_io_unit_page1 *sas_io_unit_pg1 = NULL;
        struct mpi3_sas_phy_page0 phy_pg0;
        u16 sz, ioc_status;
        int rc = 0;

        rc = mpi3mr_parent_present(mrioc, phy);
        if (rc)
                return rc;

        if (!rates->minimum_linkrate)
                rates->minimum_linkrate = phy->minimum_linkrate;
        else if (rates->minimum_linkrate < phy->minimum_linkrate_hw)
                rates->minimum_linkrate = phy->minimum_linkrate_hw;

        if (!rates->maximum_linkrate)
                rates->maximum_linkrate = phy->maximum_linkrate;
        else if (rates->maximum_linkrate > phy->maximum_linkrate_hw)
                rates->maximum_linkrate = phy->maximum_linkrate_hw;

        /* handle expander phys */
        if (phy->identify.sas_address != mrioc->sas_hba.sas_address) {
                phy->minimum_linkrate = rates->minimum_linkrate;
                phy->maximum_linkrate = rates->maximum_linkrate;
                return mpi3mr_expander_phy_control(mrioc, phy,
                    SMP_PHY_CONTROL_LINK_RESET);
        }

        /* handle hba phys */
        sz = offsetof(struct mpi3_sas_io_unit_page1, phy_data) +
                (mrioc->sas_hba.num_phys *
                 sizeof(struct mpi3_sas_io_unit1_phy_data));
        sas_io_unit_pg1 = kzalloc(sz, GFP_KERNEL);
        if (!sas_io_unit_pg1) {
                rc = -ENOMEM;
                goto out;
        }

        if (mpi3mr_cfg_get_sas_io_unit_pg1(mrioc, sas_io_unit_pg1, sz)) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                rc = -ENXIO;
                goto out;
        }

        sas_io_unit_pg1->phy_data[phy->number].max_min_link_rate =
                (rates->minimum_linkrate + (rates->maximum_linkrate << 4));

        if (mpi3mr_cfg_set_sas_io_unit_pg1(mrioc, sas_io_unit_pg1, sz)) {
                ioc_err(mrioc, "failure at %s:%d/%s()!\n",
                    __FILE__, __LINE__, __func__);
                rc = -ENXIO;
                goto out;
        }

        /* link reset */
        mpi3mr_transport_phy_reset(phy, 0);

        /* read phy page 0, then update the rates in the sas transport phy */
        if (!mpi3mr_cfg_get_sas_phy_pg0(mrioc, &ioc_status, &phy_pg0,
            sizeof(struct mpi3_sas_phy_page0),
            MPI3_SAS_PHY_PGAD_FORM_PHY_NUMBER, phy->number) &&
            (ioc_status == MPI3_IOCSTATUS_SUCCESS)) {
                phy->minimum_linkrate = mpi3mr_convert_phy_link_rate(
                    phy_pg0.programmed_link_rate &
                    MPI3_SAS_PRATE_MIN_RATE_MASK);
                phy->maximum_linkrate = mpi3mr_convert_phy_link_rate(
                    phy_pg0.programmed_link_rate >> 4);
                phy->negotiated_linkrate =
                        mpi3mr_convert_phy_link_rate(
                            (phy_pg0.negotiated_link_rate &
                            MPI3_SAS_NEG_LINK_RATE_LOGICAL_MASK)
                            >> MPI3_SAS_NEG_LINK_RATE_LOGICAL_SHIFT);
        }

out:
        kfree(sas_io_unit_pg1);
        return rc;
}

/**
 * mpi3mr_map_smp_buffer - map BSG dma buffer
 * @dev: Generic device reference
 * @buf: BSG buffer pointer
 * @dma_addr: Physical address holder
 * @dma_len: Mapped DMA buffer length.
 * @p: Virtual address holder
 *
 * This function maps the DMAable buffer
 *
 * Return: 0 on success, non-zero on failure
 */
static int
mpi3mr_map_smp_buffer(struct device *dev, struct bsg_buffer *buf,
                dma_addr_t *dma_addr, size_t *dma_len, void **p)
{
        /* Check if the request is split across multiple segments */
        if (buf->sg_cnt > 1) {
                *p = dma_alloc_coherent(dev, buf->payload_len, dma_addr,
                                GFP_KERNEL);
                if (!*p)
                        return -ENOMEM;
                *dma_len = buf->payload_len;
        } else {
                if (!dma_map_sg(dev, buf->sg_list, 1, DMA_BIDIRECTIONAL))
                        return -ENOMEM;
                *dma_addr = sg_dma_address(buf->sg_list);
                *dma_len = sg_dma_len(buf->sg_list);
                *p = NULL;
        }

        return 0;
}

/**
 * mpi3mr_unmap_smp_buffer - unmap BSG dma buffer
 * @dev: Generic device reference
 * @buf: BSG buffer pointer
 * @dma_addr: Physical address to be unmapped
 * @p: Virtual address
 *
 * This function unmaps the DMAable buffer
 */
static void
mpi3mr_unmap_smp_buffer(struct device *dev, struct bsg_buffer *buf,
                dma_addr_t dma_addr, void *p)
{
        if (p)
                dma_free_coherent(dev, buf->payload_len, p, dma_addr);
        else
                dma_unmap_sg(dev, buf->sg_list, 1, DMA_BIDIRECTIONAL);
}

/**
 * mpi3mr_transport_smp_handler - handler for smp passthru
 * @job: BSG job reference
 * @shost: SCSI host object reference
 * @rphy: SAS transport rphy object pointing the expander
 *
 * This is used primarily by smp utils for sending the SMP
 * commands to the expanders attached to the controller
 */
static void
mpi3mr_transport_smp_handler(struct bsg_job *job, struct Scsi_Host *shost,
        struct sas_rphy *rphy)
{
        struct mpi3mr_ioc *mrioc = shost_priv(shost);
        struct mpi3_smp_passthrough_request mpi_request;
        struct mpi3_smp_passthrough_reply mpi_reply;
        int rc;
        void *psge;
        dma_addr_t dma_addr_in;
        dma_addr_t dma_addr_out;
        void *addr_in = NULL;
        void *addr_out = NULL;
        size_t dma_len_in;
        size_t dma_len_out;
        unsigned int reslen = 0;
        u16 request_sz = sizeof(struct mpi3_smp_passthrough_request);
        u16 reply_sz = sizeof(struct mpi3_smp_passthrough_reply);
        u8 sgl_flags = MPI3MR_SGEFLAGS_SYSTEM_SIMPLE_END_OF_LIST;
        u16 ioc_status;

        if (mrioc->reset_in_progress) {
                ioc_err(mrioc, "%s: host reset in progress!\n", __func__);
                rc = -EFAULT;
                goto out;
        }

        if (mrioc->pci_err_recovery) {
                ioc_err(mrioc, "%s: pci error recovery in progress!\n", __func__);
                rc = -EFAULT;
                goto out;
        }

        rc = mpi3mr_map_smp_buffer(&mrioc->pdev->dev, &job->request_payload,
            &dma_addr_out, &dma_len_out, &addr_out);
        if (rc)
                goto out;

        if (addr_out)
                sg_copy_to_buffer(job->request_payload.sg_list,
                    job->request_payload.sg_cnt, addr_out,
                    job->request_payload.payload_len);

        rc = mpi3mr_map_smp_buffer(&mrioc->pdev->dev, &job->reply_payload,
                        &dma_addr_in, &dma_len_in, &addr_in);
        if (rc)
                goto unmap_out;

        memset(&mpi_request, 0, request_sz);
        memset(&mpi_reply, 0, reply_sz);
        mpi_request.host_tag = cpu_to_le16(MPI3MR_HOSTTAG_TRANSPORT_CMDS);
        mpi_request.function = MPI3_FUNCTION_SMP_PASSTHROUGH;
        mpi_request.io_unit_port = (u8) mpi3mr_get_port_id_by_rphy(mrioc, rphy);
        mpi_request.sas_address = ((rphy) ?
            cpu_to_le64(rphy->identify.sas_address) :
            cpu_to_le64(mrioc->sas_hba.sas_address));
        psge = &mpi_request.request_sge;
        mpi3mr_add_sg_single(psge, sgl_flags, dma_len_out - 4, dma_addr_out);

        psge = &mpi_request.response_sge;
        mpi3mr_add_sg_single(psge, sgl_flags, dma_len_in - 4, dma_addr_in);

        dprint_transport_info(mrioc, "sending SMP request\n");

        rc = mpi3mr_post_transport_req(mrioc, &mpi_request, request_sz,
                                       &mpi_reply, reply_sz,
                                       MPI3MR_INTADMCMD_TIMEOUT, &ioc_status);
        if (rc)
                goto unmap_in;

        dprint_transport_info(mrioc,
            "SMP request completed with ioc_status(0x%04x)\n", ioc_status);

        dprint_transport_info(mrioc,
                    "SMP request - reply data transfer size(%d)\n",
                    le16_to_cpu(mpi_reply.response_data_length));

        memcpy(job->reply, &mpi_reply, reply_sz);
        job->reply_len = reply_sz;
        reslen = le16_to_cpu(mpi_reply.response_data_length);

        if (addr_in)
                sg_copy_from_buffer(job->reply_payload.sg_list,
                                job->reply_payload.sg_cnt, addr_in,
                                job->reply_payload.payload_len);

        rc = 0;
unmap_in:
        mpi3mr_unmap_smp_buffer(&mrioc->pdev->dev, &job->reply_payload,
                        dma_addr_in, addr_in);
unmap_out:
        mpi3mr_unmap_smp_buffer(&mrioc->pdev->dev, &job->request_payload,
                        dma_addr_out, addr_out);
out:
        bsg_job_done(job, rc, reslen);
}

struct sas_function_template mpi3mr_transport_functions = {
        .get_linkerrors         = mpi3mr_transport_get_linkerrors,
        .get_enclosure_identifier = mpi3mr_transport_get_enclosure_identifier,
        .get_bay_identifier     = mpi3mr_transport_get_bay_identifier,
        .phy_reset              = mpi3mr_transport_phy_reset,
        .phy_enable             = mpi3mr_transport_phy_enable,
        .set_phy_speed          = mpi3mr_transport_phy_speed,
        .smp_handler            = mpi3mr_transport_smp_handler,
};

struct scsi_transport_template *mpi3mr_transport_template;