// SPDX-License-Identifier: GPL-2.0-or-later /* * Driver for Broadcom MPI3 Storage Controllers * * Copyright (C) 2017-2022 Broadcom Inc. * (mailto: mpi3mr-linuxdrv.pdl@broadcom.com) * */ #include "mpi3mr.h" /** * __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 */ static 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_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 * * 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) { 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--; 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 * * 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) { 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++; 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); return; } } /** * 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; mpi3mr_delete_sas_phy(mrioc, mr_sas_port, mr_sas_phy); 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 ((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(sizeof(struct mpi3mr_hba_port), GFP_KERNEL); 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); 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) 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 = kcalloc(num_phys, sizeof(struct mpi3mr_sas_phy), GFP_KERNEL); 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(dev_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(sizeof(struct mpi3mr_sas_port), GFP_KERNEL); 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); 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; list_add_tail(&mr_sas_node->phy[i].port_siblings, &mr_sas_port->phy_list); mr_sas_port->num_phys++; } if (!mr_sas_port->num_phys) { ioc_err(mrioc, "failure at %s:%d/%s()!\n", __FILE__, __LINE__, __func__); goto out_fail; } 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); } 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->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); }