blob: 77ffbd6ddce4cf214a74989f907d7febdd6f08f6 [file]
// 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;