| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2026 LeapIO Tech Inc. |
| * |
| * LeapRAID storage and RAID controller driver. |
| */ |
| #include <scsi/scsi_host.h> |
| |
| #include "leapraid_func.h" |
| |
| static struct leapraid_topo_node *leapraid_transport_topo_node_by_sas_addr( |
| struct leapraid_adapter *adapter, |
| u64 sas_addr, |
| struct leapraid_card_port *card_port) |
| { |
| if (adapter->dev_topo.card.sas_address == sas_addr) |
| return &adapter->dev_topo.card; |
| |
| return leapraid_exp_find_by_sas_address(adapter, sas_addr, card_port); |
| } |
| |
| static u8 leapraid_get_port_id_by_expander(struct leapraid_adapter *adapter, |
| struct sas_rphy *rphy) |
| { |
| struct leapraid_topo_node *topo_node_exp; |
| unsigned long flags; |
| u8 port_id = 0xFF; |
| |
| spin_lock_irqsave(&adapter->dev_topo.topo_node_lock, flags); |
| list_for_each_entry(topo_node_exp, &adapter->dev_topo.exp_list, list) { |
| if (topo_node_exp->rphy == rphy) { |
| port_id = topo_node_exp->card_port->port_id; |
| break; |
| } |
| } |
| spin_unlock_irqrestore(&adapter->dev_topo.topo_node_lock, flags); |
| |
| return port_id; |
| } |
| |
| static u8 leapraid_get_port_id_by_end_dev(struct leapraid_adapter *adapter, |
| struct sas_rphy *rphy) |
| { |
| struct leapraid_sas_dev *sas_dev; |
| unsigned long flags; |
| u8 port_id = 0xFF; |
| |
| spin_lock_irqsave(&adapter->dev_topo.sas_dev_lock, flags); |
| sas_dev = leapraid_hold_lock_get_sas_dev_by_addr_and_rphy( |
| adapter, |
| rphy->identify.sas_address, |
| rphy); |
| if (sas_dev) { |
| port_id = sas_dev->card_port->port_id; |
| leapraid_sdev_put(sas_dev); |
| } |
| spin_unlock_irqrestore(&adapter->dev_topo.sas_dev_lock, flags); |
| |
| return port_id; |
| } |
| |
| static u8 leapraid_transport_get_port_id_by_rphy( |
| struct leapraid_adapter *adapter, |
| struct sas_rphy *rphy) |
| { |
| if (!rphy) |
| return 0xFF; |
| |
| switch (rphy->identify.device_type) { |
| case SAS_EDGE_EXPANDER_DEVICE: |
| case SAS_FANOUT_EXPANDER_DEVICE: |
| return leapraid_get_port_id_by_expander(adapter, rphy); |
| case SAS_END_DEVICE: |
| return leapraid_get_port_id_by_end_dev(adapter, rphy); |
| default: |
| return 0xFF; |
| } |
| } |
| |
| static enum sas_linkrate leapraid_transport_convert_phy_link_rate(u8 link_rate) |
| { |
| unsigned int i; |
| |
| #define SAS_RATE_12G SAS_LINK_RATE_12_0_GBPS |
| |
| const struct linkrate_map { |
| u8 in; |
| enum sas_linkrate out; |
| } linkrate_table[] = { |
| { |
| LEAPRAID_SAS_NEG_LINK_RATE_1_5, |
| SAS_LINK_RATE_1_5_GBPS |
| }, |
| { |
| LEAPRAID_SAS_NEG_LINK_RATE_3_0, |
| SAS_LINK_RATE_3_0_GBPS |
| }, |
| { |
| LEAPRAID_SAS_NEG_LINK_RATE_6_0, |
| SAS_LINK_RATE_6_0_GBPS |
| }, |
| { |
| LEAPRAID_SAS_NEG_LINK_RATE_12_0, |
| SAS_RATE_12G |
| }, |
| { |
| LEAPRAID_SAS_NEG_LINK_RATE_PHY_DISABLED, |
| SAS_PHY_DISABLED |
| }, |
| { |
| LEAPRAID_SAS_NEG_LINK_RATE_NEGOTIATION_FAILED, |
| SAS_LINK_RATE_FAILED |
| }, |
| { |
| LEAPRAID_SAS_NEG_LINK_RATE_PORT_SELECTOR, |
| SAS_SATA_PORT_SELECTOR |
| }, |
| { |
| LEAPRAID_SAS_NEG_LINK_RATE_SMP_RESETTING, |
| SAS_LINK_RATE_UNKNOWN |
| }, |
| { |
| LEAPRAID_SAS_NEG_LINK_RATE_SATA_OOB_COMPLETE, |
| SAS_LINK_RATE_UNKNOWN |
| }, |
| { |
| LEAPRAID_SAS_NEG_LINK_RATE_UNKNOWN_LINK_RATE, |
| SAS_LINK_RATE_UNKNOWN |
| }, |
| }; |
| |
| for (i = 0; i < ARRAY_SIZE(linkrate_table); i++) |
| if (linkrate_table[i].in == link_rate) |
| return linkrate_table[i].out; |
| |
| return SAS_LINK_RATE_UNKNOWN; |
| } |
| |
| static void leapraid_set_identify_protocol_flags(u32 dev_info, |
| struct sas_identify *identify) |
| { |
| unsigned int i; |
| |
| const struct protocol_mapping { |
| u32 mask; |
| u32 *target; |
| u32 protocol; |
| } mappings[] = { |
| { |
| LEAPRAID_DEVTYP_SSP_INIT, |
| &identify->initiator_port_protocols, |
| SAS_PROTOCOL_SSP |
| }, |
| { |
| LEAPRAID_DEVTYP_STP_INIT, |
| &identify->initiator_port_protocols, |
| SAS_PROTOCOL_STP |
| }, |
| { |
| LEAPRAID_DEVTYP_SMP_INIT, |
| &identify->initiator_port_protocols, |
| SAS_PROTOCOL_SMP |
| }, |
| { |
| LEAPRAID_DEVTYP_SATA_HOST, |
| &identify->initiator_port_protocols, |
| SAS_PROTOCOL_SATA |
| }, |
| { |
| LEAPRAID_DEVTYP_SSP_TGT, |
| &identify->target_port_protocols, |
| SAS_PROTOCOL_SSP |
| }, |
| { |
| LEAPRAID_DEVTYP_STP_TGT, |
| &identify->target_port_protocols, |
| SAS_PROTOCOL_STP |
| }, |
| { |
| LEAPRAID_DEVTYP_SMP_TGT, |
| &identify->target_port_protocols, |
| SAS_PROTOCOL_SMP |
| }, |
| { |
| LEAPRAID_DEVTYP_SATA_DEV, |
| &identify->target_port_protocols, |
| SAS_PROTOCOL_SATA |
| }, |
| }; |
| |
| for (i = 0; i < ARRAY_SIZE(mappings); i++) |
| if (dev_info & mappings[i].mask && mappings[i].target) |
| *mappings[i].target |= mappings[i].protocol; |
| } |
| |
| static int leapraid_transport_set_identify(struct leapraid_adapter *adapter, |
| u16 hdl, |
| struct sas_identify *identify) |
| { |
| union cfg_param_1 cfgp1 = {0}; |
| union cfg_param_2 cfgp2 = {0}; |
| struct leapraid_sas_dev_p0 sas_dev_pg0; |
| u32 dev_info; |
| |
| if ((adapter->access_ctrl.shost_recovering && |
| !adapter->scan_dev_desc.driver_loading) || |
| adapter->access_ctrl.pcie_recovering) { |
| dev_warn(&adapter->pdev->dev, |
| "%s: Failed, shost_recovering=%d pcie_recovering=%d\n", |
| __func__, |
| adapter->access_ctrl.shost_recovering, |
| adapter->access_ctrl.pcie_recovering); |
| return -EFAULT; |
| } |
| |
| cfgp1.form = LEAPRAID_SAS_DEV_CFG_PGAD_HDL; |
| cfgp2.handle = hdl; |
| if (leapraid_op_config_page(adapter, &sas_dev_pg0, cfgp1, |
| cfgp2, GET_SAS_DEVICE_PG0)) |
| return -ENXIO; |
| |
| memset(identify, 0, sizeof(struct sas_identify)); |
| dev_info = le32_to_cpu(sas_dev_pg0.dev_info); |
| identify->sas_address = le64_to_cpu(sas_dev_pg0.sas_address); |
| identify->phy_identifier = sas_dev_pg0.phy_num; |
| |
| switch (dev_info & LEAPRAID_DEVTYP_MASK_DEV_TYPE) { |
| case LEAPRAID_DEVTYP_NO_DEV: |
| identify->device_type = SAS_PHY_UNUSED; |
| break; |
| case LEAPRAID_DEVTYP_END_DEV: |
| identify->device_type = SAS_END_DEVICE; |
| break; |
| case LEAPRAID_DEVTYP_EDGE_EXPANDER: |
| identify->device_type = SAS_EDGE_EXPANDER_DEVICE; |
| break; |
| case LEAPRAID_DEVTYP_FANOUT_EXPANDER: |
| identify->device_type = SAS_FANOUT_EXPANDER_DEVICE; |
| break; |
| } |
| |
| leapraid_set_identify_protocol_flags(dev_info, identify); |
| |
| return 0; |
| } |
| |
| static void leapraid_transport_exp_set_edev(struct leapraid_adapter *adapter, |
| void *data_out, |
| struct sas_expander_device *edev) |
| { |
| struct leapraid_smp_passthrough_rep *smp_passthrough_rep; |
| struct leapraid_rep_manu_reply *rep_manu_reply; |
| u8 *component_id; |
| |
| smp_passthrough_rep = |
| (void *)&adapter->driver_cmds.transport_cmd.reply; |
| if (le16_to_cpu(smp_passthrough_rep->resp_data_len) != |
| sizeof(struct leapraid_rep_manu_reply)) |
| return; |
| |
| rep_manu_reply = data_out + sizeof(struct leapraid_rep_manu_request); |
| |
| memcpy(edev->vendor_id, rep_manu_reply->vendor_identification, |
| SAS_EXPANDER_VENDOR_ID_LEN); |
| edev->vendor_id[SAS_EXPANDER_VENDOR_ID_LEN] = '\0'; |
| |
| memcpy(edev->product_id, rep_manu_reply->product_identification, |
| SAS_EXPANDER_PRODUCT_ID_LEN); |
| edev->product_id[SAS_EXPANDER_PRODUCT_ID_LEN] = '\0'; |
| |
| memcpy(edev->product_rev, rep_manu_reply->product_revision_level, |
| SAS_EXPANDER_PRODUCT_REV_LEN); |
| edev->product_rev[SAS_EXPANDER_PRODUCT_REV_LEN] = '\0'; |
| |
| edev->level = rep_manu_reply->sas_format & 1; |
| if (edev->level) { |
| memcpy(edev->component_vendor_id, |
| rep_manu_reply->comp_vendor_identification, |
| SAS_EXPANDER_COMPONENT_VENDOR_ID_LEN); |
| edev->component_vendor_id[ |
| SAS_EXPANDER_COMPONENT_VENDOR_ID_LEN] = '\0'; |
| |
| component_id = (u8 *)&rep_manu_reply->component_id; |
| edev->component_id = component_id[0] << 8 | component_id[1]; |
| edev->component_revision_id = |
| rep_manu_reply->component_revision_level; |
| } |
| } |
| |
| static int leapraid_transport_exp_report_manu(struct leapraid_adapter *adapter, |
| u64 sas_address, |
| struct sas_expander_device *edev, |
| u8 port_id) |
| { |
| struct leapraid_smp_passthrough_req *smp_passthrough_req; |
| struct leapraid_rep_manu_request *rep_manu_request; |
| dma_addr_t h2c_dma_addr; |
| dma_addr_t c2h_dma_addr; |
| bool issue_reset = false; |
| void *data_out = NULL; |
| u16 inter_taskid; |
| size_t c2h_size; |
| size_t h2c_size; |
| void *psge; |
| int rc; |
| |
| if (adapter->access_ctrl.shost_recovering || |
| adapter->access_ctrl.pcie_recovering) { |
| dev_warn(&adapter->pdev->dev, |
| "%s: Failed, shost_recovering=%d pcie_recovering=%d\n", |
| __func__, |
| adapter->access_ctrl.shost_recovering, |
| adapter->access_ctrl.pcie_recovering); |
| return -EFAULT; |
| } |
| |
| mutex_lock(&adapter->driver_cmds.transport_cmd.mutex); |
| adapter->driver_cmds.transport_cmd.status = LEAPRAID_CMD_PENDING; |
| rc = leapraid_check_adapter_is_op(adapter, LEAPRAID_DB_WAIT_OP_SHORT, |
| __func__); |
| if (rc) |
| goto out_cleanup; |
| |
| h2c_size = sizeof(struct leapraid_rep_manu_request); |
| c2h_size = sizeof(struct leapraid_rep_manu_reply); |
| data_out = dma_alloc_coherent(&adapter->pdev->dev, |
| h2c_size + c2h_size, |
| &h2c_dma_addr, |
| GFP_ATOMIC); |
| if (!data_out) { |
| rc = -ENOMEM; |
| goto out_cleanup; |
| } |
| |
| rep_manu_request = data_out; |
| rep_manu_request->smp_frame_type = |
| SMP_REPORT_MANUFACTURER_INFORMATION_FRAME_TYPE; |
| rep_manu_request->function = SMP_REPORT_MANUFACTURER_INFORMATION_FUNC; |
| rep_manu_request->allocated_response_length = 0; |
| rep_manu_request->request_length = 0; |
| |
| inter_taskid = adapter->driver_cmds.transport_cmd.inter_taskid; |
| smp_passthrough_req = leapraid_get_task_desc(adapter, inter_taskid); |
| memset(smp_passthrough_req, 0, |
| sizeof(struct leapraid_smp_passthrough_req)); |
| smp_passthrough_req->func = LEAPRAID_FUNC_SMP_PASSTHROUGH; |
| smp_passthrough_req->physical_port = port_id; |
| smp_passthrough_req->sas_address = cpu_to_le64(sas_address); |
| smp_passthrough_req->req_data_len = cpu_to_le16(h2c_size); |
| psge = &smp_passthrough_req->sgl; |
| c2h_dma_addr = h2c_dma_addr + sizeof(struct leapraid_rep_manu_request); |
| leapraid_build_ieee_sg(adapter, psge, h2c_dma_addr, h2c_size, |
| c2h_dma_addr, c2h_size); |
| |
| init_completion(&adapter->driver_cmds.transport_cmd.done); |
| leapraid_fire_task(adapter, inter_taskid); |
| wait_for_completion_timeout(&adapter->driver_cmds.transport_cmd.done, |
| LEAPRAID_TRANSPORT_CMD_TIMEOUT * HZ); |
| if (!(adapter->driver_cmds.transport_cmd.status & LEAPRAID_CMD_DONE)) { |
| rc = -ETIMEDOUT; |
| dev_err(&adapter->pdev->dev, |
| "%s: SMP passthrough timeout, st=0x%x\n", |
| __func__, adapter->driver_cmds.transport_cmd.status); |
| leapraid_log_req_context(adapter, inter_taskid, |
| smp_passthrough_req); |
| if (!(adapter->driver_cmds.transport_cmd.status & |
| LEAPRAID_CMD_RESET)) |
| issue_reset = true; |
| |
| goto hard_reset; |
| } |
| |
| if (adapter->driver_cmds.transport_cmd.status & |
| LEAPRAID_CMD_REPLY_VALID) |
| leapraid_transport_exp_set_edev(adapter, data_out, edev); |
| |
| hard_reset: |
| if (issue_reset) { |
| dev_info(&adapter->pdev->dev, "%s:%d: call hard_reset\n", |
| __func__, __LINE__); |
| rc = leapraid_hard_reset_handler(adapter, FULL_RESET); |
| } |
| out_cleanup: |
| adapter->driver_cmds.transport_cmd.status = LEAPRAID_CMD_NOT_USED; |
| if (data_out) |
| dma_free_coherent(&adapter->pdev->dev, h2c_size + c2h_size, |
| data_out, h2c_dma_addr); |
| |
| mutex_unlock(&adapter->driver_cmds.transport_cmd.mutex); |
| return rc; |
| } |
| |
| static void leapraid_transport_del_port(struct leapraid_adapter *adapter, |
| struct leapraid_sas_port *sas_port) |
| { |
| dev_info(&sas_port->port->dev, |
| "Remove port: SAS addr=0x%016llx\n", |
| (unsigned long long)sas_port->remote_identify.sas_address); |
| switch (sas_port->remote_identify.device_type) { |
| case SAS_END_DEVICE: |
| leapraid_sas_dev_remove_by_sas_address( |
| adapter, |
| sas_port->remote_identify.sas_address, |
| sas_port->card_port); |
| break; |
| case SAS_EDGE_EXPANDER_DEVICE: |
| case SAS_FANOUT_EXPANDER_DEVICE: |
| leapraid_exp_rm(adapter, sas_port->remote_identify.sas_address, |
| sas_port->card_port); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void leapraid_transport_del_phy(struct leapraid_adapter *adapter, |
| struct leapraid_sas_port *sas_port, |
| struct leapraid_card_phy *card_phy) |
| { |
| dev_info(&card_phy->phy->dev, |
| "Remove PHY: SAS addr=0x%016llx, phy=%d\n", |
| (unsigned long long)sas_port->remote_identify.sas_address, |
| card_phy->phy_id); |
| list_del(&card_phy->port_siblings); |
| sas_port->phys_num--; |
| sas_port_delete_phy(sas_port->port, card_phy->phy); |
| card_phy->phy_is_assigned = 0; |
| } |
| |
| static void leapraid_transport_add_phy(struct leapraid_adapter *adapter, |
| struct leapraid_sas_port *sas_port, |
| struct leapraid_card_phy *card_phy) |
| { |
| dev_info(&card_phy->phy->dev, |
| "Add PHY: SAS addr=0x%016llx, phy=%d\n", |
| (unsigned long long)sas_port->remote_identify.sas_address, |
| card_phy->phy_id); |
| list_add_tail(&card_phy->port_siblings, &sas_port->phy_list); |
| sas_port->phys_num++; |
| sas_port_add_phy(sas_port->port, card_phy->phy); |
| card_phy->phy_is_assigned = 1; |
| } |
| |
| void leapraid_transport_attach_phy_to_port( |
| struct leapraid_adapter *adapter, |
| struct leapraid_topo_node *topo_node, |
| struct leapraid_card_phy *card_phy, |
| u64 sas_address, |
| struct leapraid_card_port *card_port) |
| { |
| struct leapraid_sas_port *sas_port; |
| struct leapraid_card_phy *card_phy_srch; |
| |
| if (card_phy->phy_is_assigned) |
| return; |
| |
| if (!card_port) |
| return; |
| |
| list_for_each_entry(sas_port, &topo_node->sas_port_list, port_list) { |
| if (sas_port->remote_identify.sas_address != sas_address) |
| continue; |
| |
| if (sas_port->card_port != card_port) |
| continue; |
| |
| list_for_each_entry(card_phy_srch, &sas_port->phy_list, |
| port_siblings) { |
| if (card_phy_srch == card_phy) |
| return; |
| } |
| leapraid_transport_add_phy(adapter, sas_port, card_phy); |
| return; |
| } |
| } |
| |
| void leapraid_transport_detach_phy_to_port( |
| struct leapraid_adapter *adapter, |
| struct leapraid_topo_node *topo_node, |
| struct leapraid_card_phy *target_card_phy) |
| { |
| struct leapraid_sas_port *sas_port, *sas_port_next; |
| struct leapraid_card_phy *cur_card_phy; |
| |
| if (!target_card_phy->phy_is_assigned) |
| return; |
| |
| list_for_each_entry_safe(sas_port, sas_port_next, |
| &topo_node->sas_port_list, port_list) { |
| list_for_each_entry(cur_card_phy, &sas_port->phy_list, |
| port_siblings) { |
| if (cur_card_phy != target_card_phy) |
| continue; |
| |
| if (sas_port->phys_num == 1 && |
| !adapter->access_ctrl.shost_recovering) |
| leapraid_transport_del_port(adapter, sas_port); |
| else |
| leapraid_transport_del_phy(adapter, sas_port, |
| target_card_phy); |
| return; |
| } |
| } |
| } |
| |
| static void leapraid_detach_phy_from_old_port( |
| struct leapraid_adapter *adapter, |
| struct leapraid_topo_node *topo_node, |
| u64 sas_address, |
| struct leapraid_card_port *card_port) |
| { |
| int i; |
| |
| for (i = 0; i < topo_node->phys_num; i++) { |
| if (topo_node->card_phy[i].remote_identify.sas_address != |
| sas_address || |
| topo_node->card_phy[i].card_port != card_port) |
| continue; |
| if (topo_node->card_phy[i].phy_is_assigned) |
| leapraid_transport_detach_phy_to_port( |
| adapter, |
| topo_node, |
| &topo_node->card_phy[i]); |
| } |
| } |
| |
| static struct leapraid_sas_port *leapraid_prepare_sas_port( |
| struct leapraid_adapter *adapter, |
| u16 handle, u64 sas_address, |
| struct leapraid_card_port *card_port, |
| struct leapraid_topo_node **out_topo_node) |
| { |
| struct leapraid_topo_node *topo_node; |
| struct leapraid_sas_port *sas_port; |
| unsigned long flags; |
| |
| sas_port = kzalloc_obj(*sas_port); |
| if (!sas_port) { |
| dev_warn(&adapter->pdev->dev, |
| "%s: Failed alloc sas_port\n", __func__); |
| return NULL; |
| } |
| |
| INIT_LIST_HEAD(&sas_port->port_list); |
| INIT_LIST_HEAD(&sas_port->phy_list); |
| |
| spin_lock_irqsave(&adapter->dev_topo.topo_node_lock, flags); |
| topo_node = leapraid_transport_topo_node_by_sas_addr(adapter, |
| sas_address, |
| card_port); |
| spin_unlock_irqrestore(&adapter->dev_topo.topo_node_lock, flags); |
| |
| if (!topo_node) { |
| dev_err(&adapter->pdev->dev, |
| "%s: Failed to locate parent for SAS 0x%016llx!\n", |
| __func__, sas_address); |
| goto out_cleanup; |
| } |
| |
| if (leapraid_transport_set_identify(adapter, handle, |
| &sas_port->remote_identify)) |
| goto out_cleanup; |
| |
| if (sas_port->remote_identify.device_type == SAS_PHY_UNUSED) { |
| dev_warn(&adapter->pdev->dev, |
| "%s: Failed, device type is SAS_PHY_UNUSED\n", |
| __func__); |
| goto out_cleanup; |
| } |
| |
| sas_port->card_port = card_port; |
| *out_topo_node = topo_node; |
| |
| return sas_port; |
| |
| out_cleanup: |
| kfree(sas_port); |
| return NULL; |
| } |
| |
| static int leapraid_bind_phys_and_vphy(struct leapraid_adapter *adapter, |
| struct leapraid_sas_port *sas_port, |
| struct leapraid_topo_node *topo_node, |
| struct leapraid_card_port *card_port, |
| struct leapraid_vphy **out_vphy) |
| { |
| struct leapraid_vphy *vphy = NULL; |
| int i; |
| |
| for (i = 0; i < topo_node->phys_num; i++) { |
| if (topo_node->card_phy[i].remote_identify.sas_address != |
| sas_port->remote_identify.sas_address || |
| topo_node->card_phy[i].card_port != card_port) |
| continue; |
| |
| list_add_tail(&topo_node->card_phy[i].port_siblings, |
| &sas_port->phy_list); |
| sas_port->phys_num++; |
| |
| if (topo_node->hdl <= adapter->dev_topo.card.phys_num) { |
| if (!topo_node->card_phy[i].vphy) { |
| card_port->phy_mask |= BIT(i); |
| continue; |
| } |
| |
| vphy = leapraid_get_vphy_by_phy(card_port, i); |
| if (!vphy) |
| return LEAPRAID_OPERATION_FAILED; |
| } |
| } |
| |
| *out_vphy = vphy; |
| return sas_port->phys_num ? 0 : LEAPRAID_OPERATION_FAILED; |
| } |
| |
| static struct sas_rphy *leapraid_create_and_register_rphy( |
| struct leapraid_adapter *adapter, |
| struct leapraid_sas_port *sas_port, |
| struct leapraid_topo_node *topo_node, |
| struct leapraid_card_port *card_port, |
| struct leapraid_vphy *vphy) |
| { |
| struct leapraid_sas_dev *sas_dev = NULL; |
| struct leapraid_card_phy *card_phy; |
| struct sas_port *port; |
| struct sas_rphy *rphy; |
| |
| if (sas_port->remote_identify.device_type == SAS_END_DEVICE) { |
| sas_dev = leapraid_get_sas_dev_by_addr( |
| adapter, |
| sas_port->remote_identify.sas_address, |
| card_port); |
| if (!sas_dev) |
| return NULL; |
| sas_dev->pend_sas_rphy_add = 1; |
| } |
| |
| if (!topo_node->parent_dev) { |
| dev_warn(&adapter->pdev->dev, |
| "%s: topo_node parent device is NULL\n", __func__); |
| goto cleanup_sas_dev; |
| } |
| |
| port = sas_port_alloc_num(topo_node->parent_dev); |
| if (!port) { |
| dev_err(&adapter->pdev->dev, |
| "%s: Failed to allocate SAS port\n", __func__); |
| goto cleanup_sas_dev; |
| } |
| |
| if (sas_port_add(port)) { |
| dev_err(&adapter->pdev->dev, |
| "%s: Failed to add SAS port\n", __func__); |
| goto out_delete_port; |
| } |
| |
| list_for_each_entry(card_phy, &sas_port->phy_list, port_siblings) { |
| sas_port_add_phy(port, card_phy->phy); |
| card_phy->phy_is_assigned = 1; |
| card_phy->card_port = card_port; |
| } |
| |
| if (sas_dev) { |
| rphy = sas_end_device_alloc(port); |
| sas_dev->rphy = rphy; |
| |
| if (topo_node->hdl <= adapter->dev_topo.card.phys_num) { |
| if (!vphy) |
| card_port->sas_address = sas_dev->sas_addr; |
| else |
| vphy->sas_address = sas_dev->sas_addr; |
| } |
| } else { |
| rphy = sas_expander_alloc( |
| port, |
| sas_port->remote_identify.device_type); |
| if (topo_node->hdl <= adapter->dev_topo.card.phys_num) |
| card_port->sas_address = |
| sas_port->remote_identify.sas_address; |
| } |
| |
| if (!rphy) { |
| dev_err(&adapter->pdev->dev, |
| "%s: Failed to allocate RPHY\n", __func__); |
| goto cleanup_card_phy; |
| } |
| |
| rphy->identify = sas_port->remote_identify; |
| |
| if (sas_rphy_add(rphy)) { |
| dev_err(&adapter->pdev->dev, |
| "%s: Failed to add RPHY\n", __func__); |
| if (sas_dev) |
| sas_dev->rphy = NULL; |
| |
| sas_rphy_free(rphy); |
| goto cleanup_card_phy; |
| } |
| sas_port->port = port; |
| |
| if (sas_dev) { |
| sas_dev->pend_sas_rphy_add = 0; |
| leapraid_sdev_put(sas_dev); |
| } |
| |
| return rphy; |
| |
| cleanup_card_phy: |
| if (topo_node->hdl <= adapter->dev_topo.card.phys_num) { |
| if (!vphy) |
| card_port->sas_address = 0; |
| else |
| vphy->sas_address = 0; |
| } |
| |
| list_for_each_entry(card_phy, &sas_port->phy_list, port_siblings) { |
| card_phy->phy_is_assigned = 0; |
| card_phy->card_port = NULL; |
| } |
| |
| out_delete_port: |
| sas_port_delete(port); |
| |
| cleanup_sas_dev: |
| if (sas_dev) { |
| sas_dev->pend_sas_rphy_add = 0; |
| leapraid_sdev_put(sas_dev); |
| } |
| return NULL; |
| } |
| |
| struct leapraid_sas_port *leapraid_transport_port_add( |
| struct leapraid_adapter *adapter, |
| u16 hdl, u64 sas_address, |
| struct leapraid_card_port *card_port) |
| { |
| struct leapraid_card_phy *card_phy, *card_phy_next; |
| struct leapraid_topo_node *topo_node = NULL; |
| struct leapraid_sas_port *sas_port; |
| struct leapraid_vphy *vphy = NULL; |
| struct sas_rphy *rphy; |
| unsigned long flags; |
| |
| if (!card_port) { |
| dev_warn(&adapter->pdev->dev, |
| "%s: Invalid card_port\n", __func__); |
| return NULL; |
| } |
| |
| sas_port = leapraid_prepare_sas_port(adapter, hdl, sas_address, |
| card_port, &topo_node); |
| if (!sas_port) |
| return NULL; |
| |
| leapraid_detach_phy_from_old_port( |
| adapter, |
| topo_node, |
| sas_port->remote_identify.sas_address, |
| card_port); |
| |
| if (leapraid_bind_phys_and_vphy(adapter, sas_port, topo_node, |
| card_port, &vphy)) { |
| dev_err(&adapter->pdev->dev, |
| "%s: Failed to bind phy to vphy\n", __func__); |
| goto out_fail; |
| } |
| |
| rphy = leapraid_create_and_register_rphy(adapter, sas_port, topo_node, |
| card_port, vphy); |
| if (!rphy) { |
| dev_err(&adapter->pdev->dev, |
| "%s: Failed to create rphy\n", __func__); |
| goto out_fail; |
| } |
| |
| dev_info(&rphy->dev, |
| "%s: Added dev: hdl=0x%04x, SAS addr=0x%016llx\n", |
| __func__, hdl, |
| (unsigned long long)sas_port->remote_identify.sas_address); |
| |
| sas_port->rphy = rphy; |
| |
| spin_lock_irqsave(&adapter->dev_topo.topo_node_lock, flags); |
| list_add_tail(&sas_port->port_list, &topo_node->sas_port_list); |
| spin_unlock_irqrestore(&adapter->dev_topo.topo_node_lock, flags); |
| |
| if (sas_port->remote_identify.device_type == |
| SAS_EDGE_EXPANDER_DEVICE || |
| sas_port->remote_identify.device_type == |
| SAS_FANOUT_EXPANDER_DEVICE) |
| leapraid_transport_exp_report_manu( |
| adapter, |
| sas_port->remote_identify.sas_address, |
| rphy_to_expander_device(rphy), |
| card_port->port_id); |
| |
| return sas_port; |
| |
| out_fail: |
| list_for_each_entry_safe(card_phy, card_phy_next, |
| &sas_port->phy_list, port_siblings) { |
| if (topo_node->hdl <= adapter->dev_topo.card.phys_num && |
| !card_phy->vphy) |
| card_port->phy_mask &= ~BIT(card_phy->phy_id); |
| |
| list_del_init(&card_phy->port_siblings); |
| } |
| |
| kfree(sas_port); |
| return NULL; |
| } |
| |
| static struct leapraid_sas_port *leapraid_find_and_remove_sas_port( |
| struct leapraid_topo_node *topo_node, |
| u64 sas_address, |
| struct leapraid_card_port *remove_card_port, |
| bool *found) |
| { |
| struct leapraid_sas_port *sas_port, *sas_port_next; |
| |
| list_for_each_entry_safe(sas_port, sas_port_next, |
| &topo_node->sas_port_list, port_list) { |
| if (sas_port->remote_identify.sas_address != sas_address) |
| continue; |
| |
| if (sas_port->card_port != remove_card_port) |
| continue; |
| |
| *found = true; |
| list_del(&sas_port->port_list); |
| return sas_port; |
| } |
| return NULL; |
| } |
| |
| static void leapraid_cleanup_card_port_and_vphys( |
| struct leapraid_adapter *adapter, |
| u64 sas_address, |
| struct leapraid_card_port *remove_card_port) |
| { |
| struct leapraid_card_port *card_port, *card_port_next; |
| struct leapraid_vphy *vphy, *vphy_next; |
| |
| if (remove_card_port->vphys_mask) { |
| list_for_each_entry_safe(vphy, vphy_next, |
| &remove_card_port->vphys_list, list) { |
| if (vphy->sas_address != sas_address) |
| continue; |
| |
| dev_dbg(&adapter->pdev->dev, |
| "%s: Remove vphy=%p from port=%p port_id=%d\n", |
| __func__, vphy, remove_card_port, |
| remove_card_port->port_id); |
| |
| remove_card_port->vphys_mask &= ~vphy->phy_mask; |
| list_del(&vphy->list); |
| kfree(vphy); |
| } |
| |
| if (!remove_card_port->vphys_mask && |
| !remove_card_port->sas_address) { |
| dev_dbg(&adapter->pdev->dev, |
| "%s: Remove empty hba_port: %p, port_id=%d\n", |
| __func__, |
| remove_card_port, |
| remove_card_port->port_id); |
| list_del(&remove_card_port->list); |
| kfree(remove_card_port); |
| remove_card_port = NULL; |
| } |
| } |
| |
| list_for_each_entry_safe(card_port, card_port_next, |
| &adapter->dev_topo.card_port_list, list) { |
| if (card_port != remove_card_port) |
| continue; |
| |
| if (card_port->sas_address != sas_address) |
| continue; |
| |
| if (!remove_card_port->vphys_mask) { |
| dev_dbg(&adapter->pdev->dev, |
| "%s: Remove hba_port: %p, port_id=%d\n", |
| __func__, card_port, card_port->port_id); |
| list_del(&card_port->list); |
| kfree(card_port); |
| } else { |
| dev_dbg(&adapter->pdev->dev, |
| "Clear sas_address of port=%p, port_id=%d\n", |
| card_port, card_port->port_id); |
| remove_card_port->sas_address = 0; |
| } |
| break; |
| } |
| } |
| |
| static void leapraid_clear_topo_node_phys(struct leapraid_topo_node *topo_node, |
| u64 sas_address) |
| { |
| int i; |
| |
| for (i = 0; i < topo_node->phys_num; i++) |
| if (topo_node->card_phy[i].remote_identify.sas_address == |
| sas_address) { |
| memset(&topo_node->card_phy[i].remote_identify, 0, |
| sizeof(struct sas_identify)); |
| topo_node->card_phy[i].vphy = 0; |
| } |
| } |
| |
| void leapraid_transport_port_remove( |
| struct leapraid_adapter *adapter, |
| u64 sas_address, |
| u64 sas_address_parent, |
| struct leapraid_card_port *remove_card_port) |
| { |
| struct leapraid_card_phy *card_phy, *card_phy_next; |
| struct leapraid_sas_port *sas_port; |
| struct leapraid_topo_node *topo_node; |
| unsigned long flags; |
| bool found = false; |
| |
| if (!remove_card_port) |
| return; |
| |
| spin_lock_irqsave(&adapter->dev_topo.topo_node_lock, flags); |
| |
| topo_node = leapraid_transport_topo_node_by_sas_addr(adapter, |
| sas_address_parent, |
| remove_card_port); |
| if (!topo_node) { |
| spin_unlock_irqrestore(&adapter->dev_topo.topo_node_lock, |
| flags); |
| return; |
| } |
| |
| sas_port = leapraid_find_and_remove_sas_port(topo_node, sas_address, |
| remove_card_port, &found); |
| |
| if (!found) { |
| spin_unlock_irqrestore(&adapter->dev_topo.topo_node_lock, |
| flags); |
| return; |
| } |
| |
| if (topo_node->hdl <= adapter->dev_topo.card.phys_num && |
| adapter->adapter_attr.enable_mp) |
| leapraid_cleanup_card_port_and_vphys(adapter, sas_address, |
| remove_card_port); |
| |
| leapraid_clear_topo_node_phys(topo_node, sas_address); |
| spin_unlock_irqrestore(&adapter->dev_topo.topo_node_lock, flags); |
| |
| list_for_each_entry_safe(card_phy, card_phy_next, |
| &sas_port->phy_list, port_siblings) { |
| card_phy->phy_is_assigned = 0; |
| if (!adapter->access_ctrl.host_removing) |
| sas_port_delete_phy(sas_port->port, card_phy->phy); |
| |
| list_del(&card_phy->port_siblings); |
| } |
| |
| if (!adapter->access_ctrl.host_removing) |
| sas_port_delete(sas_port->port); |
| |
| dev_dbg(&adapter->pdev->dev, |
| "%s: Removed sas_port for SAS addr=0x%016llx\n", |
| __func__, (unsigned long long)sas_address); |
| |
| kfree(sas_port); |
| } |
| |
| static void leapraid_init_sas_or_exp_phy(struct leapraid_adapter *adapter, |
| struct leapraid_card_phy *card_phy, |
| struct sas_phy *phy, |
| struct leapraid_sas_phy_p0 *phy_pg0, |
| struct leapraid_exp_p1 *exp_pg1) |
| { |
| if (exp_pg1 && phy_pg0) |
| return; |
| |
| if (!exp_pg1 && !phy_pg0) |
| return; |
| |
| phy->identify = card_phy->identify; |
| phy->identify.phy_identifier = card_phy->phy_id; |
| phy->negotiated_linkrate = phy_pg0 ? |
| leapraid_transport_convert_phy_link_rate( |
| phy_pg0->neg_link_rate & |
| LEAPRAID_SAS_NEG_LINK_RATE_MASK_PHYSICAL) : |
| leapraid_transport_convert_phy_link_rate( |
| exp_pg1->neg_link_rate & |
| LEAPRAID_SAS_NEG_LINK_RATE_MASK_PHYSICAL); |
| phy->minimum_linkrate_hw = phy_pg0 ? |
| leapraid_transport_convert_phy_link_rate( |
| phy_pg0->hw_link_rate & |
| LEAPRAID_SAS_HWRATE_MIN_RATE_MASK) : |
| leapraid_transport_convert_phy_link_rate( |
| exp_pg1->hw_link_rate & |
| LEAPRAID_SAS_HWRATE_MIN_RATE_MASK); |
| phy->maximum_linkrate_hw = phy_pg0 ? |
| leapraid_transport_convert_phy_link_rate( |
| phy_pg0->hw_link_rate >> |
| LEAPRAID_SAS_NEG_LINK_RATE_SHIFT) : |
| leapraid_transport_convert_phy_link_rate( |
| exp_pg1->hw_link_rate >> |
| LEAPRAID_SAS_NEG_LINK_RATE_SHIFT); |
| phy->minimum_linkrate = phy_pg0 ? |
| leapraid_transport_convert_phy_link_rate( |
| phy_pg0->p_link_rate & |
| LEAPRAID_SAS_PRATE_MIN_RATE_MASK) : |
| leapraid_transport_convert_phy_link_rate( |
| exp_pg1->p_link_rate & |
| LEAPRAID_SAS_PRATE_MIN_RATE_MASK); |
| phy->maximum_linkrate = phy_pg0 ? |
| leapraid_transport_convert_phy_link_rate( |
| phy_pg0->p_link_rate >> |
| LEAPRAID_SAS_NEG_LINK_RATE_SHIFT) : |
| leapraid_transport_convert_phy_link_rate( |
| exp_pg1->p_link_rate >> |
| LEAPRAID_SAS_NEG_LINK_RATE_SHIFT); |
| phy->hostdata = card_phy->card_port; |
| } |
| |
| void leapraid_transport_add_card_phy(struct leapraid_adapter *adapter, |
| struct leapraid_card_phy *card_phy, |
| struct leapraid_sas_phy_p0 *phy_pg0, |
| struct device *parent_dev) |
| { |
| struct sas_phy *phy; |
| int ret; |
| |
| INIT_LIST_HEAD(&card_phy->port_siblings); |
| phy = sas_phy_alloc(parent_dev, card_phy->phy_id); |
| if (!phy) { |
| dev_err(&adapter->pdev->dev, |
| "%s: sas_phy_alloc failed!\n", __func__); |
| return; |
| } |
| |
| if (leapraid_transport_set_identify(adapter, card_phy->hdl, |
| &card_phy->identify)) { |
| dev_err(&adapter->pdev->dev, |
| "%s: Set PHY handle identify failed!\n", __func__); |
| sas_phy_free(phy); |
| return; |
| } |
| |
| card_phy->attached_hdl = le16_to_cpu(phy_pg0->attached_dev_hdl); |
| if (card_phy->attached_hdl) { |
| ret = leapraid_transport_set_identify( |
| adapter, |
| card_phy->attached_hdl, |
| &card_phy->remote_identify); |
| if (ret) { |
| dev_err(&adapter->pdev->dev, |
| "%s: Set PHY attached hdl identify failed!\n", |
| __func__); |
| sas_phy_free(phy); |
| return; |
| } |
| } |
| |
| leapraid_init_sas_or_exp_phy(adapter, card_phy, phy, phy_pg0, NULL); |
| |
| if (sas_phy_add(phy)) { |
| dev_err(&adapter->pdev->dev, |
| "%s: SAS PHY add failed!\n", __func__); |
| sas_phy_free(phy); |
| return; |
| } |
| |
| card_phy->phy = phy; |
| } |
| |
| int leapraid_transport_add_exp_phy(struct leapraid_adapter *adapter, |
| struct leapraid_card_phy *card_phy, |
| struct leapraid_exp_p1 *exp_pg1, |
| struct device *parent_dev) |
| { |
| struct sas_phy *phy; |
| int ret; |
| |
| INIT_LIST_HEAD(&card_phy->port_siblings); |
| phy = sas_phy_alloc(parent_dev, card_phy->phy_id); |
| if (!phy) { |
| dev_err(&adapter->pdev->dev, |
| "%s: sas_phy_alloc failed!\n", __func__); |
| return -EFAULT; |
| } |
| |
| if (leapraid_transport_set_identify(adapter, card_phy->hdl, |
| &card_phy->identify)) { |
| dev_err(&adapter->pdev->dev, |
| "%s: Set PHY hdl identify failed!\n", __func__); |
| sas_phy_free(phy); |
| return -EFAULT; |
| } |
| |
| card_phy->attached_hdl = le16_to_cpu(exp_pg1->attached_dev_hdl); |
| if (card_phy->attached_hdl) { |
| ret = leapraid_transport_set_identify( |
| adapter, |
| card_phy->attached_hdl, |
| &card_phy->remote_identify); |
| if (ret) |
| dev_warn(&adapter->pdev->dev, |
| "%s: Set PHY attached hdl identify failed!\n", |
| __func__); |
| } |
| |
| leapraid_init_sas_or_exp_phy(adapter, card_phy, phy, NULL, exp_pg1); |
| |
| if (sas_phy_add(phy)) { |
| dev_err(&adapter->pdev->dev, |
| "%s: SAS PHY add failed!\n", __func__); |
| sas_phy_free(phy); |
| return -EFAULT; |
| } |
| |
| card_phy->phy = phy; |
| return 0; |
| } |
| |
| void leapraid_transport_update_links( |
| struct leapraid_adapter *adapter, |
| u64 sas_address, u16 hdl, u8 phy_index, |
| u8 link_rate, struct leapraid_card_port *target_card_port) |
| { |
| struct leapraid_topo_node *topo_node; |
| struct leapraid_card_phy *card_phy; |
| struct leapraid_card_port *card_port; |
| unsigned long flags; |
| |
| if (adapter->access_ctrl.shost_recovering || |
| adapter->access_ctrl.pcie_recovering) |
| return; |
| |
| spin_lock_irqsave(&adapter->dev_topo.topo_node_lock, flags); |
| topo_node = leapraid_transport_topo_node_by_sas_addr(adapter, |
| sas_address, |
| target_card_port); |
| if (!topo_node) { |
| spin_unlock_irqrestore(&adapter->dev_topo.topo_node_lock, |
| flags); |
| return; |
| } |
| |
| card_phy = &topo_node->card_phy[phy_index]; |
| card_phy->attached_hdl = hdl; |
| spin_unlock_irqrestore(&adapter->dev_topo.topo_node_lock, flags); |
| |
| if (hdl && link_rate >= LEAPRAID_SAS_NEG_LINK_RATE_1_5) { |
| leapraid_transport_set_identify(adapter, hdl, |
| &card_phy->remote_identify); |
| if (topo_node->hdl <= adapter->dev_topo.card.phys_num && |
| adapter->adapter_attr.enable_mp) { |
| list_for_each_entry(card_port, |
| &adapter->dev_topo.card_port_list, |
| list) { |
| if (card_port->sas_address == sas_address && |
| card_port == target_card_port) |
| card_port->phy_mask |= |
| BIT(card_phy->phy_id); |
| } |
| } |
| leapraid_transport_attach_phy_to_port( |
| adapter, |
| topo_node, |
| card_phy, |
| card_phy->remote_identify.sas_address, |
| target_card_port); |
| } else { |
| memset(&card_phy->remote_identify, 0, |
| sizeof(struct sas_identify)); |
| } |
| |
| if (card_phy->phy) |
| card_phy->phy->negotiated_linkrate = |
| leapraid_transport_convert_phy_link_rate(link_rate); |
| } |
| |
| static int leapraid_dma_map_buffer(struct device *dev, struct bsg_buffer *buf, |
| dma_addr_t *dma_addr, |
| size_t *dma_len, void **p) |
| { |
| 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)) { |
| dev_err(dev, |
| "%s: Failed to map sg buffer for dma\n", |
| __func__); |
| return -ENOMEM; |
| } |
| |
| *dma_addr = sg_dma_address(buf->sg_list); |
| *dma_len = sg_dma_len(buf->sg_list); |
| *p = NULL; |
| } |
| return 0; |
| } |
| |
| static void leapraid_dma_unmap_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); |
| } |
| |
| static void leapraid_build_smp_task(struct leapraid_adapter *adapter, |
| struct sas_rphy *rphy, |
| dma_addr_t h2c_dma_addr, size_t h2c_size, |
| dma_addr_t c2h_dma_addr, size_t c2h_size) |
| { |
| struct leapraid_smp_passthrough_req *smp_passthrough_req; |
| u16 inter_taskid; |
| void *psge; |
| |
| inter_taskid = adapter->driver_cmds.transport_cmd.inter_taskid; |
| smp_passthrough_req = leapraid_get_task_desc(adapter, inter_taskid); |
| memset(smp_passthrough_req, 0, sizeof(*smp_passthrough_req)); |
| |
| smp_passthrough_req->func = LEAPRAID_FUNC_SMP_PASSTHROUGH; |
| smp_passthrough_req->physical_port = |
| leapraid_transport_get_port_id_by_rphy(adapter, rphy); |
| smp_passthrough_req->sas_address = (rphy) ? |
| cpu_to_le64(rphy->identify.sas_address) : |
| cpu_to_le64(adapter->dev_topo.card.sas_address); |
| smp_passthrough_req->req_data_len = |
| cpu_to_le16(h2c_size - LEAPRAID_SMP_FRAME_HEADER_SIZE); |
| psge = &smp_passthrough_req->sgl; |
| leapraid_build_ieee_sg(adapter, psge, h2c_dma_addr, |
| h2c_size - LEAPRAID_SMP_FRAME_HEADER_SIZE, |
| c2h_dma_addr, |
| c2h_size - LEAPRAID_SMP_FRAME_HEADER_SIZE); |
| } |
| |
| static int leapraid_send_smp_req(struct leapraid_adapter *adapter) |
| { |
| const struct leapraid_smp_passthrough_req *smp_passthrough_req; |
| u16 inter_taskid; |
| |
| dev_dbg(&adapter->pdev->dev, |
| "%s: Sending smp request\n", __func__); |
| inter_taskid = adapter->driver_cmds.transport_cmd.inter_taskid; |
| smp_passthrough_req = leapraid_get_task_desc(adapter, inter_taskid); |
| init_completion(&adapter->driver_cmds.transport_cmd.done); |
| leapraid_fire_task(adapter, inter_taskid); |
| wait_for_completion_timeout(&adapter->driver_cmds.transport_cmd.done, |
| LEAPRAID_TRANSPORT_CMD_TIMEOUT * HZ); |
| if (!(adapter->driver_cmds.transport_cmd.status & LEAPRAID_CMD_DONE)) { |
| dev_err(&adapter->pdev->dev, "%s: timeout, st=0x%x\n", |
| __func__, adapter->driver_cmds.transport_cmd.status); |
| leapraid_log_req_context(adapter, inter_taskid, |
| smp_passthrough_req); |
| if (!(adapter->driver_cmds.transport_cmd.status & |
| LEAPRAID_CMD_RESET)) { |
| dev_dbg(&adapter->pdev->dev, |
| "%s:%d: call hard_reset\n", |
| __func__, __LINE__); |
| leapraid_hard_reset_handler(adapter, FULL_RESET); |
| return -ETIMEDOUT; |
| } |
| } |
| |
| dev_dbg(&adapter->pdev->dev, "%s: SMP request complete\n", __func__); |
| if (!(adapter->driver_cmds.transport_cmd.status & |
| LEAPRAID_CMD_REPLY_VALID)) { |
| dev_err(&adapter->pdev->dev, |
| "%s: SMP request no reply\n", __func__); |
| return -ENXIO; |
| } |
| |
| return 0; |
| } |
| |
| static void leapraid_handle_smp_rep(struct leapraid_adapter *adapter, |
| struct bsg_job *job, void *addr_in, |
| unsigned int *reslen) |
| { |
| struct leapraid_smp_passthrough_rep *smp_passthrough_rep; |
| |
| smp_passthrough_rep = |
| (void *)&adapter->driver_cmds.transport_cmd.reply; |
| |
| dev_dbg(&adapter->pdev->dev, "%s: Response data len=%d\n", |
| __func__, le16_to_cpu(smp_passthrough_rep->resp_data_len)); |
| |
| memcpy(job->reply, smp_passthrough_rep, sizeof(*smp_passthrough_rep)); |
| job->reply_len = sizeof(*smp_passthrough_rep); |
| *reslen = le16_to_cpu(smp_passthrough_rep->resp_data_len); |
| |
| if (addr_in) |
| sg_copy_from_buffer(job->reply_payload.sg_list, |
| job->reply_payload.sg_cnt, addr_in, |
| job->reply_payload.payload_len); |
| } |
| |
| static void leapraid_transport_smp_handler(struct bsg_job *job, |
| struct Scsi_Host *shost, |
| struct sas_rphy *rphy) |
| { |
| struct leapraid_adapter *adapter = shost_priv(shost); |
| dma_addr_t c2h_dma_addr; |
| dma_addr_t h2c_dma_addr; |
| void *addr_in = NULL; |
| void *addr_out = NULL; |
| size_t c2h_size; |
| size_t h2c_size; |
| int rc; |
| unsigned int reslen = 0; |
| |
| if (adapter->access_ctrl.shost_recovering || |
| adapter->access_ctrl.pcie_recovering) { |
| dev_err(&adapter->pdev->dev, |
| "%s: Failed, shost_recovering=%d pcie_recovering=%d\n", |
| __func__, |
| adapter->access_ctrl.shost_recovering, |
| adapter->access_ctrl.pcie_recovering); |
| rc = -EFAULT; |
| goto exit_bsg_job; |
| } |
| |
| rc = mutex_lock_interruptible(&adapter->driver_cmds |
| .transport_cmd.mutex); |
| if (rc) |
| goto exit_bsg_job; |
| |
| adapter->driver_cmds.transport_cmd.status = LEAPRAID_CMD_PENDING; |
| rc = leapraid_dma_map_buffer(&adapter->pdev->dev, |
| &job->request_payload, |
| &h2c_dma_addr, &h2c_size, &addr_out); |
| if (rc) |
| goto release_lock; |
| |
| if (h2c_size < LEAPRAID_SMP_FRAME_HEADER_SIZE) { |
| dev_err(&adapter->pdev->dev, |
| "%s: Invalid SMP request payload size=%zu\n", |
| __func__, h2c_size); |
| rc = -EINVAL; |
| goto free_req_buf; |
| } |
| |
| 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 = leapraid_dma_map_buffer(&adapter->pdev->dev, &job->reply_payload, |
| &c2h_dma_addr, &c2h_size, &addr_in); |
| if (rc) |
| goto free_req_buf; |
| |
| if (c2h_size < LEAPRAID_SMP_FRAME_HEADER_SIZE) { |
| dev_err(&adapter->pdev->dev, |
| "%s: Invalid SMP reply payload size=%zu\n", |
| __func__, c2h_size); |
| rc = -EINVAL; |
| goto free_rep_buf; |
| } |
| |
| rc = leapraid_check_adapter_is_op(adapter, LEAPRAID_DB_WAIT_OP_SHORT, |
| __func__); |
| if (rc) |
| goto free_rep_buf; |
| |
| leapraid_build_smp_task(adapter, rphy, h2c_dma_addr, |
| h2c_size, c2h_dma_addr, c2h_size); |
| |
| rc = leapraid_send_smp_req(adapter); |
| if (rc) |
| goto free_rep_buf; |
| |
| leapraid_handle_smp_rep(adapter, job, addr_in, &reslen); |
| |
| free_rep_buf: |
| leapraid_dma_unmap_buffer(&adapter->pdev->dev, &job->reply_payload, |
| c2h_dma_addr, addr_in); |
| free_req_buf: |
| leapraid_dma_unmap_buffer(&adapter->pdev->dev, &job->request_payload, |
| h2c_dma_addr, addr_out); |
| release_lock: |
| adapter->driver_cmds.transport_cmd.status = LEAPRAID_CMD_NOT_USED; |
| mutex_unlock(&adapter->driver_cmds.transport_cmd.mutex); |
| exit_bsg_job: |
| bsg_job_done(job, rc, reslen); |
| } |
| |
| struct sas_function_template leapraid_transport_functions = { |
| .smp_handler = leapraid_transport_smp_handler, |
| }; |
| |
| struct scsi_transport_template *leapraid_transport_template; |