// 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 #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]; u8 product_id[SAS_EXPANDER_PRODUCT_ID_LEN]; u8 product_rev[SAS_EXPANDER_PRODUCT_REV_LEN]; u8 component_vendor_id[SAS_EXPANDER_COMPONENT_VENDOR_ID_LEN]; 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; } 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)) { 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_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 * * 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--; 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 * * 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++; 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); 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); 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(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(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); if (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 (i > sizeof(mr_sas_port->phy_mask) * 8) { ioc_warn(mrioc, "skipping port %u, max allowed value is %lu\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++; 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; } 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 = kcalloc(64, sizeof(struct host_port), GFP_KERNEL); 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) 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 -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 = 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. */ 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); } /** * mpi3mr_get_sas_negotiated_logical_linkrate - get linkrate * @mrioc: Adapter instance reference * @tgtdev: Target device * * This function identifies whether the target device is * attached directly or through expander and issues sas phy * page0 or expander phy page1 and gets the link rate, if there * is any failure in reading the pages then this returns link * rate of 1.5. * * 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; 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<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 & MPI3_SAS_NEG_LINK_RATE_LOGICAL_MASK) >> MPI3_SAS_NEG_LINK_RATE_LOGICAL_SHIFT; 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 & MPI3_SAS_NEG_LINK_RATE_LOGICAL_MASK) >> MPI3_SAS_NEG_LINK_RATE_LOGICAL_SHIFT; out: return link_rate; } /** * 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); 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; } 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; } 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; } 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;