diff mbox series

[v2,09/15] mpi3mr: Add expander devices to STL

Message ID 20220804131226.16653-10-sreekanth.reddy@broadcom.com (mailing list archive)
State Accepted
Headers show
Series mpi3mr: Added Support for SAS Transport | expand

Commit Message

Sreekanth Reddy Aug. 4, 2022, 1:12 p.m. UTC
Register/unregister the expander devices to
SCSI Transport Layer(STL) whenever the corresponding expander
is added/removed from topology.

Signed-off-by: Sreekanth Reddy <sreekanth.reddy@broadcom.com>
---
 drivers/scsi/mpi3mr/mpi3mr.h           |   7 +
 drivers/scsi/mpi3mr/mpi3mr_os.c        |  36 ++-
 drivers/scsi/mpi3mr/mpi3mr_transport.c | 398 ++++++++++++++++++++++++-
 3 files changed, 432 insertions(+), 9 deletions(-)

Comments

Himanshu Madhani Aug. 9, 2022, 4:18 p.m. UTC | #1
> On Aug 4, 2022, at 6:12 AM, Sreekanth Reddy <sreekanth.reddy@broadcom.com> wrote:
> 
> Register/unregister the expander devices to
> SCSI Transport Layer(STL) whenever the corresponding expander
> is added/removed from topology.
> 
> Signed-off-by: Sreekanth Reddy <sreekanth.reddy@broadcom.com>
> ---
> drivers/scsi/mpi3mr/mpi3mr.h           |   7 +
> drivers/scsi/mpi3mr/mpi3mr_os.c        |  36 ++-
> drivers/scsi/mpi3mr/mpi3mr_transport.c | 398 ++++++++++++++++++++++++-
> 3 files changed, 432 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/scsi/mpi3mr/mpi3mr.h b/drivers/scsi/mpi3mr/mpi3mr.h
> index 8c8703e..9cd5f88 100644
> --- a/drivers/scsi/mpi3mr/mpi3mr.h
> +++ b/drivers/scsi/mpi3mr/mpi3mr.h
> @@ -1341,6 +1341,11 @@ int mpi3mr_cfg_get_driver_pg1(struct mpi3mr_ioc *mrioc,
> 	struct mpi3_driver_page1 *driver_pg1, u16 pg_sz);
> 
> u8 mpi3mr_is_expander_device(u16 device_info);
> +int mpi3mr_expander_add(struct mpi3mr_ioc *mrioc, u16 handle);
> +void mpi3mr_expander_remove(struct mpi3mr_ioc *mrioc, u64 sas_address,
> +	struct mpi3mr_hba_port *hba_port);
> +struct mpi3mr_sas_node *__mpi3mr_expander_find_by_handle(struct mpi3mr_ioc
> +	*mrioc, u16 handle);
> struct mpi3mr_hba_port *mpi3mr_get_hba_port_by_id(struct mpi3mr_ioc *mrioc,
> 	u8 port_id);
> void mpi3mr_sas_host_refresh(struct mpi3mr_ioc *mrioc);
> @@ -1348,6 +1353,8 @@ void mpi3mr_sas_host_add(struct mpi3mr_ioc *mrioc);
> 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);
> +void mpi3mr_remove_tgtdev_from_host(struct mpi3mr_ioc *mrioc,
> +	struct mpi3mr_tgt_dev *tgtdev);
> void mpi3mr_print_device_event_notice(struct mpi3mr_ioc *mrioc,
> 	bool device_add);
> #endif /*MPI3MR_H_INCLUDED*/
> diff --git a/drivers/scsi/mpi3mr/mpi3mr_os.c b/drivers/scsi/mpi3mr/mpi3mr_os.c
> index ae77422..9a32dc6 100644
> --- a/drivers/scsi/mpi3mr/mpi3mr_os.c
> +++ b/drivers/scsi/mpi3mr/mpi3mr_os.c
> @@ -824,7 +824,7 @@ void mpi3mr_print_device_event_notice(struct mpi3mr_ioc *mrioc,
>  *
>  * Return: 0 on success, non zero on failure.
>  */
> -static void mpi3mr_remove_tgtdev_from_host(struct mpi3mr_ioc *mrioc,
> +void mpi3mr_remove_tgtdev_from_host(struct mpi3mr_ioc *mrioc,
> 	struct mpi3mr_tgt_dev *tgtdev)
> {
> 	struct mpi3mr_stgt_priv_data *tgt_priv;
> @@ -1494,7 +1494,10 @@ static void mpi3mr_sastopochg_evt_bh(struct mpi3mr_ioc *mrioc,
> 	int i;
> 	u16 handle;
> 	u8 reason_code;
> +	u64 exp_sas_address = 0;
> +	struct mpi3mr_hba_port *hba_port = NULL;
> 	struct mpi3mr_tgt_dev *tgtdev = NULL;
> +	struct mpi3mr_sas_node *sas_expander = NULL;
> 
> 	mpi3mr_sastopochg_evt_debug(mrioc, event_data);
> 
> @@ -1524,6 +1527,13 @@ static void mpi3mr_sastopochg_evt_bh(struct mpi3mr_ioc *mrioc,
> 		if (tgtdev)
> 			mpi3mr_tgtdev_put(tgtdev);
> 	}
> +
> +	if (mrioc->sas_transport_enabled && (event_data->exp_status ==
> +	    MPI3_EVENT_SAS_TOPO_ES_NOT_RESPONDING)) {
> +		if (sas_expander)
> +			mpi3mr_expander_remove(mrioc, exp_sas_address,
> +			    hba_port);
> +	}
> }
> 
> /**
> @@ -1744,7 +1754,8 @@ static void mpi3mr_fwevt_bh(struct mpi3mr_ioc *mrioc,
> 	struct mpi3mr_fwevt *fwevt)
> {
> 	struct mpi3_device_page0 *dev_pg0 = NULL;
> -	u16 perst_id;
> +	u16 perst_id, handle, dev_info;
> +	struct mpi3_device0_sas_sata_format *sasinf = NULL;
> 
> 	mpi3mr_fwevt_del_from_list(mrioc, fwevt);
> 	mrioc->current_event = fwevt;
> @@ -1758,10 +1769,23 @@ static void mpi3mr_fwevt_bh(struct mpi3mr_ioc *mrioc,
> 	switch (fwevt->event_id) {
> 	case MPI3_EVENT_DEVICE_ADDED:
> 	{
> -		struct mpi3_device_page0 *dev_pg0 =
> -		    (struct mpi3_device_page0 *)fwevt->event_data;
> -		mpi3mr_report_tgtdev_to_host(mrioc,
> -		    le16_to_cpu(dev_pg0->persistent_id));
> +		dev_pg0 = (struct mpi3_device_page0 *)fwevt->event_data;
> +		perst_id = le16_to_cpu(dev_pg0->persistent_id);
> +		handle = le16_to_cpu(dev_pg0->dev_handle);
> +		if (perst_id != MPI3_DEVICE0_PERSISTENTID_INVALID)
> +			mpi3mr_report_tgtdev_to_host(mrioc, perst_id);
> +		else if (mrioc->sas_transport_enabled &&
> +		    (dev_pg0->device_form == MPI3_DEVICE_DEVFORM_SAS_SATA)) {
> +			sasinf = &dev_pg0->device_specific.sas_sata_format;
> +			dev_info = le16_to_cpu(sasinf->device_info);
> +			if (!mrioc->sas_hba.num_phys)
> +				mpi3mr_sas_host_add(mrioc);
> +			else
> +				mpi3mr_sas_host_refresh(mrioc);
> +
> +			if (mpi3mr_is_expander_device(dev_info))
> +				mpi3mr_expander_add(mrioc, handle);
> +		}
> 		break;
> 	}
> 	case MPI3_EVENT_DEVICE_INFO_CHANGED:
> diff --git a/drivers/scsi/mpi3mr/mpi3mr_transport.c b/drivers/scsi/mpi3mr/mpi3mr_transport.c
> index 3de9fa0..ff85cf3 100644
> --- a/drivers/scsi/mpi3mr/mpi3mr_transport.c
> +++ b/drivers/scsi/mpi3mr/mpi3mr_transport.c
> @@ -21,7 +21,7 @@
>  *
>  * Return: Expander sas_node object reference or NULL
>  */
> -static struct mpi3mr_sas_node *__mpi3mr_expander_find_by_handle(struct mpi3mr_ioc
> +struct mpi3mr_sas_node *__mpi3mr_expander_find_by_handle(struct mpi3mr_ioc
> 	*mrioc, u16 handle)
> {
> 	struct mpi3mr_sas_node *sas_expander, *r;
> @@ -159,6 +159,45 @@ 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)) {
> +			list_del_init(&tgtdev->list);
> +			was_on_tgtdev_list = 1;
> +			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_expander_find_by_sas_address - sas expander search
>  * @mrioc: Adapter instance reference
> @@ -378,6 +417,34 @@ static void mpi3mr_add_phy_to_an_existing_port(struct mpi3mr_ioc *mrioc,
> 	}
> }
> 
> +/**
> + * 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
> @@ -401,8 +468,12 @@ static void mpi3mr_del_phy_from_an_existing_port(struct mpi3mr_ioc *mrioc,
> 		    port_siblings) {
> 			if (srch_phy != mr_sas_phy)
> 				continue;
> -			mpi3mr_delete_sas_phy(mrioc, mr_sas_port,
> -			    mr_sas_phy);
> +			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);
> 			return;
> 		}
> 	}
> @@ -1231,3 +1302,324 @@ static void mpi3mr_sas_port_remove(struct mpi3mr_ioc *mrioc, u64 sas_address,
> 
> 	kfree(mr_sas_port);
> }
> +
> +/**
> + * 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)
> +		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;
> +				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(sizeof(struct mpi3mr_sas_node),
> +	    GFP_KERNEL);
> +	if (!sas_expander)
> +		return -1;
> +
> +	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 = kcalloc(sas_expander->num_phys,
> +	    sizeof(struct mpi3mr_sas_phy), GFP_KERNEL);
> +	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.
> + */
> +static 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)
> +			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)
> +		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);
> +
> +}
> -- 
> 2.27.0
> 

Reviewed-by: Himanshu Madhani <himanshu.madhani@oracle.com>

— 
Himanshu Madhani	Oracle Linux Engineering
diff mbox series

Patch

diff --git a/drivers/scsi/mpi3mr/mpi3mr.h b/drivers/scsi/mpi3mr/mpi3mr.h
index 8c8703e..9cd5f88 100644
--- a/drivers/scsi/mpi3mr/mpi3mr.h
+++ b/drivers/scsi/mpi3mr/mpi3mr.h
@@ -1341,6 +1341,11 @@  int mpi3mr_cfg_get_driver_pg1(struct mpi3mr_ioc *mrioc,
 	struct mpi3_driver_page1 *driver_pg1, u16 pg_sz);
 
 u8 mpi3mr_is_expander_device(u16 device_info);
+int mpi3mr_expander_add(struct mpi3mr_ioc *mrioc, u16 handle);
+void mpi3mr_expander_remove(struct mpi3mr_ioc *mrioc, u64 sas_address,
+	struct mpi3mr_hba_port *hba_port);
+struct mpi3mr_sas_node *__mpi3mr_expander_find_by_handle(struct mpi3mr_ioc
+	*mrioc, u16 handle);
 struct mpi3mr_hba_port *mpi3mr_get_hba_port_by_id(struct mpi3mr_ioc *mrioc,
 	u8 port_id);
 void mpi3mr_sas_host_refresh(struct mpi3mr_ioc *mrioc);
@@ -1348,6 +1353,8 @@  void mpi3mr_sas_host_add(struct mpi3mr_ioc *mrioc);
 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);
+void mpi3mr_remove_tgtdev_from_host(struct mpi3mr_ioc *mrioc,
+	struct mpi3mr_tgt_dev *tgtdev);
 void mpi3mr_print_device_event_notice(struct mpi3mr_ioc *mrioc,
 	bool device_add);
 #endif /*MPI3MR_H_INCLUDED*/
diff --git a/drivers/scsi/mpi3mr/mpi3mr_os.c b/drivers/scsi/mpi3mr/mpi3mr_os.c
index ae77422..9a32dc6 100644
--- a/drivers/scsi/mpi3mr/mpi3mr_os.c
+++ b/drivers/scsi/mpi3mr/mpi3mr_os.c
@@ -824,7 +824,7 @@  void mpi3mr_print_device_event_notice(struct mpi3mr_ioc *mrioc,
  *
  * Return: 0 on success, non zero on failure.
  */
-static void mpi3mr_remove_tgtdev_from_host(struct mpi3mr_ioc *mrioc,
+void mpi3mr_remove_tgtdev_from_host(struct mpi3mr_ioc *mrioc,
 	struct mpi3mr_tgt_dev *tgtdev)
 {
 	struct mpi3mr_stgt_priv_data *tgt_priv;
@@ -1494,7 +1494,10 @@  static void mpi3mr_sastopochg_evt_bh(struct mpi3mr_ioc *mrioc,
 	int i;
 	u16 handle;
 	u8 reason_code;
+	u64 exp_sas_address = 0;
+	struct mpi3mr_hba_port *hba_port = NULL;
 	struct mpi3mr_tgt_dev *tgtdev = NULL;
+	struct mpi3mr_sas_node *sas_expander = NULL;
 
 	mpi3mr_sastopochg_evt_debug(mrioc, event_data);
 
@@ -1524,6 +1527,13 @@  static void mpi3mr_sastopochg_evt_bh(struct mpi3mr_ioc *mrioc,
 		if (tgtdev)
 			mpi3mr_tgtdev_put(tgtdev);
 	}
+
+	if (mrioc->sas_transport_enabled && (event_data->exp_status ==
+	    MPI3_EVENT_SAS_TOPO_ES_NOT_RESPONDING)) {
+		if (sas_expander)
+			mpi3mr_expander_remove(mrioc, exp_sas_address,
+			    hba_port);
+	}
 }
 
 /**
@@ -1744,7 +1754,8 @@  static void mpi3mr_fwevt_bh(struct mpi3mr_ioc *mrioc,
 	struct mpi3mr_fwevt *fwevt)
 {
 	struct mpi3_device_page0 *dev_pg0 = NULL;
-	u16 perst_id;
+	u16 perst_id, handle, dev_info;
+	struct mpi3_device0_sas_sata_format *sasinf = NULL;
 
 	mpi3mr_fwevt_del_from_list(mrioc, fwevt);
 	mrioc->current_event = fwevt;
@@ -1758,10 +1769,23 @@  static void mpi3mr_fwevt_bh(struct mpi3mr_ioc *mrioc,
 	switch (fwevt->event_id) {
 	case MPI3_EVENT_DEVICE_ADDED:
 	{
-		struct mpi3_device_page0 *dev_pg0 =
-		    (struct mpi3_device_page0 *)fwevt->event_data;
-		mpi3mr_report_tgtdev_to_host(mrioc,
-		    le16_to_cpu(dev_pg0->persistent_id));
+		dev_pg0 = (struct mpi3_device_page0 *)fwevt->event_data;
+		perst_id = le16_to_cpu(dev_pg0->persistent_id);
+		handle = le16_to_cpu(dev_pg0->dev_handle);
+		if (perst_id != MPI3_DEVICE0_PERSISTENTID_INVALID)
+			mpi3mr_report_tgtdev_to_host(mrioc, perst_id);
+		else if (mrioc->sas_transport_enabled &&
+		    (dev_pg0->device_form == MPI3_DEVICE_DEVFORM_SAS_SATA)) {
+			sasinf = &dev_pg0->device_specific.sas_sata_format;
+			dev_info = le16_to_cpu(sasinf->device_info);
+			if (!mrioc->sas_hba.num_phys)
+				mpi3mr_sas_host_add(mrioc);
+			else
+				mpi3mr_sas_host_refresh(mrioc);
+
+			if (mpi3mr_is_expander_device(dev_info))
+				mpi3mr_expander_add(mrioc, handle);
+		}
 		break;
 	}
 	case MPI3_EVENT_DEVICE_INFO_CHANGED:
diff --git a/drivers/scsi/mpi3mr/mpi3mr_transport.c b/drivers/scsi/mpi3mr/mpi3mr_transport.c
index 3de9fa0..ff85cf3 100644
--- a/drivers/scsi/mpi3mr/mpi3mr_transport.c
+++ b/drivers/scsi/mpi3mr/mpi3mr_transport.c
@@ -21,7 +21,7 @@ 
  *
  * Return: Expander sas_node object reference or NULL
  */
-static struct mpi3mr_sas_node *__mpi3mr_expander_find_by_handle(struct mpi3mr_ioc
+struct mpi3mr_sas_node *__mpi3mr_expander_find_by_handle(struct mpi3mr_ioc
 	*mrioc, u16 handle)
 {
 	struct mpi3mr_sas_node *sas_expander, *r;
@@ -159,6 +159,45 @@  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)) {
+			list_del_init(&tgtdev->list);
+			was_on_tgtdev_list = 1;
+			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_expander_find_by_sas_address - sas expander search
  * @mrioc: Adapter instance reference
@@ -378,6 +417,34 @@  static void mpi3mr_add_phy_to_an_existing_port(struct mpi3mr_ioc *mrioc,
 	}
 }
 
+/**
+ * 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
@@ -401,8 +468,12 @@  static void mpi3mr_del_phy_from_an_existing_port(struct mpi3mr_ioc *mrioc,
 		    port_siblings) {
 			if (srch_phy != mr_sas_phy)
 				continue;
-			mpi3mr_delete_sas_phy(mrioc, mr_sas_port,
-			    mr_sas_phy);
+			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);
 			return;
 		}
 	}
@@ -1231,3 +1302,324 @@  static void mpi3mr_sas_port_remove(struct mpi3mr_ioc *mrioc, u64 sas_address,
 
 	kfree(mr_sas_port);
 }
+
+/**
+ * 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)
+		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;
+				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(sizeof(struct mpi3mr_sas_node),
+	    GFP_KERNEL);
+	if (!sas_expander)
+		return -1;
+
+	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 = kcalloc(sas_expander->num_phys,
+	    sizeof(struct mpi3mr_sas_phy), GFP_KERNEL);
+	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.
+ */
+static 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)
+			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)
+		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);
+
+}