mirror of
https://github.com/torvalds/linux.git
synced 2026-10-16 04:39:02 +02:00
The LeapRAID driver provides support for LeapRAID PCIe RAID controllers, enabling communication between the host operating system, firmware, and hardware for efficient storage management. The driver is organized into several logical modules, each with a clear responsibility: leapraid_os.c: Integrates with the Linux SCSI subsystem, handling host template callbacks, PCIe device probing, and initialization. leapraid_func.c: Contains low-level routines for firmware/hardware interaction, interrupt handling, and reset logic. leapraid_app.c: Provides the ioctl interface for user-space tools. leapraid_transport.c: Manages interactions with the SCSI transport layer for SAS PHYs and ports. leapraid_func.h: Contains internal definitions shared among driver modules. leapraid.h: Contains low-level hardware definitions for driver/firmware interaction. The leapraid_probe() function orchestrates the setup: it allocates the adapter structure and SCSI host, configures hardware interfaces, and registers it with the SCSI mid-layer. Following registration, scsi_scan_host() is invoked to initiate device discovery, with firmware reporting devices via interrupt-driven events. This initial commit provides the necessary infrastructure for subsequent development of full I/O path handling, error recovery, and advanced management features. Reviewed-by: Damien Le Moal <dlemoal@kernel.org> Reviewed-by: Hannes Reinecke <hare@kernel.org> Signed-off-by: Dongdong Hao <doubled@leap-io-kernel.com> Link: https://patch.msgid.link/0cbc6245aabdc6e8c90587675e76ba316c5b549e.1785823793.git.doubled@leap-io-kernel.com Signed-off-by: Martin K. Petersen (Oracle) <mkp@kernel.org>
1404 lines
38 KiB
C
1404 lines
38 KiB
C
// 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;
|