diff options
Diffstat (limited to 'drivers/scsi/fnic')
46 files changed, 24162 insertions, 0 deletions
diff --git a/drivers/scsi/fnic/Makefile b/drivers/scsi/fnic/Makefile new file mode 100644 index 000000000..48c5e0cb8 --- /dev/null +++ b/drivers/scsi/fnic/Makefile @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_FCOE_FNIC) += fnic.o + +fnic-y := \ + fip.o\ + fnic_attrs.o \ + fnic_isr.o \ + fnic_main.o \ + fnic_nvme.o \ + fnic_res.o \ + fnic_fcs.o \ + fdls_disc.o \ + fnic_scsi.o \ + fnic_trace.o \ + fnic_debugfs.o \ + vnic_cq.o \ + vnic_dev.o \ + vnic_intr.o \ + vnic_rq.o \ + vnic_wq_copy.o \ + vnic_wq.o \ + fnic_pci_subsys_devid.o diff --git a/drivers/scsi/fnic/cq_desc.h b/drivers/scsi/fnic/cq_desc.h new file mode 100644 index 000000000..0eb4ba277 --- /dev/null +++ b/drivers/scsi/fnic/cq_desc.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _CQ_DESC_H_ +#define _CQ_DESC_H_ + +/* + * Completion queue descriptor types + */ +enum cq_desc_types { + CQ_DESC_TYPE_WQ_ENET = 0, + CQ_DESC_TYPE_DESC_COPY = 1, + CQ_DESC_TYPE_WQ_EXCH = 2, + CQ_DESC_TYPE_RQ_ENET = 3, + CQ_DESC_TYPE_RQ_FCP = 4, +}; + +/* Completion queue descriptor: 16B + * + * All completion queues have this basic layout. The + * type_specfic area is unique for each completion + * queue type. + */ +struct cq_desc { + __le16 completed_index; + __le16 q_number; + u8 type_specfic[11]; + u8 type_color; +}; + +#define CQ_DESC_TYPE_BITS 4 +#define CQ_DESC_TYPE_MASK ((1 << CQ_DESC_TYPE_BITS) - 1) +#define CQ_DESC_COLOR_MASK 1 +#define CQ_DESC_COLOR_SHIFT 7 +#define CQ_DESC_Q_NUM_BITS 10 +#define CQ_DESC_Q_NUM_MASK ((1 << CQ_DESC_Q_NUM_BITS) - 1) +#define CQ_DESC_COMP_NDX_BITS 12 +#define CQ_DESC_COMP_NDX_MASK ((1 << CQ_DESC_COMP_NDX_BITS) - 1) + +static inline void cq_desc_dec(const struct cq_desc *desc_arg, + u8 *type, u8 *color, u16 *q_number, u16 *completed_index) +{ + const struct cq_desc *desc = desc_arg; + const u8 type_color = desc->type_color; + + *color = (type_color >> CQ_DESC_COLOR_SHIFT) & CQ_DESC_COLOR_MASK; + + /* + * Make sure color bit is read from desc *before* other fields + * are read from desc. Hardware guarantees color bit is last + * bit (byte) written. Adding the rmb() prevents the compiler + * and/or CPU from reordering the reads which would potentially + * result in reading stale values. + */ + + rmb(); + + *type = type_color & CQ_DESC_TYPE_MASK; + *q_number = le16_to_cpu(desc->q_number) & CQ_DESC_Q_NUM_MASK; + *completed_index = le16_to_cpu(desc->completed_index) & + CQ_DESC_COMP_NDX_MASK; +} + +#endif /* _CQ_DESC_H_ */ diff --git a/drivers/scsi/fnic/cq_enet_desc.h b/drivers/scsi/fnic/cq_enet_desc.h new file mode 100644 index 000000000..b6113291c --- /dev/null +++ b/drivers/scsi/fnic/cq_enet_desc.h @@ -0,0 +1,155 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _CQ_ENET_DESC_H_ +#define _CQ_ENET_DESC_H_ + +#include "cq_desc.h" + +/* Ethernet completion queue descriptor: 16B */ +struct cq_enet_wq_desc { + __le16 completed_index; + __le16 q_number; + u8 reserved[11]; + u8 type_color; +}; + +static inline void cq_enet_wq_desc_dec(struct cq_enet_wq_desc *desc, + u8 *type, u8 *color, u16 *q_number, u16 *completed_index) +{ + cq_desc_dec((struct cq_desc *)desc, type, + color, q_number, completed_index); +} + +/* Completion queue descriptor: Ethernet receive queue, 16B */ +struct cq_enet_rq_desc { + __le16 completed_index_flags; + __le16 q_number_rss_type_flags; + __le32 rss_hash; + __le16 bytes_written_flags; + __le16 vlan; + __le16 checksum_fcoe; + u8 flags; + u8 type_color; +}; + +#define CQ_ENET_RQ_DESC_FLAGS_INGRESS_PORT (0x1 << 12) +#define CQ_ENET_RQ_DESC_FLAGS_FCOE (0x1 << 13) +#define CQ_ENET_RQ_DESC_FLAGS_EOP (0x1 << 14) +#define CQ_ENET_RQ_DESC_FLAGS_SOP (0x1 << 15) + +#define CQ_ENET_RQ_DESC_RSS_TYPE_BITS 4 +#define CQ_ENET_RQ_DESC_RSS_TYPE_MASK \ + ((1 << CQ_ENET_RQ_DESC_RSS_TYPE_BITS) - 1) +#define CQ_ENET_RQ_DESC_RSS_TYPE_NONE 0 +#define CQ_ENET_RQ_DESC_RSS_TYPE_IPv4 1 +#define CQ_ENET_RQ_DESC_RSS_TYPE_TCP_IPv4 2 +#define CQ_ENET_RQ_DESC_RSS_TYPE_IPv6 3 +#define CQ_ENET_RQ_DESC_RSS_TYPE_TCP_IPv6 4 +#define CQ_ENET_RQ_DESC_RSS_TYPE_IPv6_EX 5 +#define CQ_ENET_RQ_DESC_RSS_TYPE_TCP_IPv6_EX 6 + +#define CQ_ENET_RQ_DESC_FLAGS_CSUM_NOT_CALC (0x1 << 14) + +#define CQ_ENET_RQ_DESC_BYTES_WRITTEN_BITS 14 +#define CQ_ENET_RQ_DESC_BYTES_WRITTEN_MASK \ + ((1 << CQ_ENET_RQ_DESC_BYTES_WRITTEN_BITS) - 1) +#define CQ_ENET_RQ_DESC_FLAGS_TRUNCATED (0x1 << 14) +#define CQ_ENET_RQ_DESC_FLAGS_VLAN_STRIPPED (0x1 << 15) + +#define CQ_ENET_RQ_DESC_FCOE_SOF_BITS 4 +#define CQ_ENET_RQ_DESC_FCOE_SOF_MASK \ + ((1 << CQ_ENET_RQ_DESC_FCOE_SOF_BITS) - 1) +#define CQ_ENET_RQ_DESC_FCOE_EOF_BITS 8 +#define CQ_ENET_RQ_DESC_FCOE_EOF_MASK \ + ((1 << CQ_ENET_RQ_DESC_FCOE_EOF_BITS) - 1) +#define CQ_ENET_RQ_DESC_FCOE_EOF_SHIFT 8 + +#define CQ_ENET_RQ_DESC_FLAGS_TCP_UDP_CSUM_OK (0x1 << 0) +#define CQ_ENET_RQ_DESC_FCOE_FC_CRC_OK (0x1 << 0) +#define CQ_ENET_RQ_DESC_FLAGS_UDP (0x1 << 1) +#define CQ_ENET_RQ_DESC_FCOE_ENC_ERROR (0x1 << 1) +#define CQ_ENET_RQ_DESC_FLAGS_TCP (0x1 << 2) +#define CQ_ENET_RQ_DESC_FLAGS_IPV4_CSUM_OK (0x1 << 3) +#define CQ_ENET_RQ_DESC_FLAGS_IPV6 (0x1 << 4) +#define CQ_ENET_RQ_DESC_FLAGS_IPV4 (0x1 << 5) +#define CQ_ENET_RQ_DESC_FLAGS_IPV4_FRAGMENT (0x1 << 6) +#define CQ_ENET_RQ_DESC_FLAGS_FCS_OK (0x1 << 7) + +static inline void cq_enet_rq_desc_dec(struct cq_enet_rq_desc *desc, + u8 *type, u8 *color, u16 *q_number, u16 *completed_index, + u8 *ingress_port, u8 *fcoe, u8 *eop, u8 *sop, u8 *rss_type, + u8 *csum_not_calc, u32 *rss_hash, u16 *bytes_written, u8 *packet_error, + u8 *vlan_stripped, u16 *vlan, u16 *checksum, u8 *fcoe_sof, + u8 *fcoe_fc_crc_ok, u8 *fcoe_enc_error, u8 *fcoe_eof, + u8 *tcp_udp_csum_ok, u8 *udp, u8 *tcp, u8 *ipv4_csum_ok, + u8 *ipv6, u8 *ipv4, u8 *ipv4_fragment, u8 *fcs_ok) +{ + u16 completed_index_flags = le16_to_cpu(desc->completed_index_flags); + u16 q_number_rss_type_flags = + le16_to_cpu(desc->q_number_rss_type_flags); + u16 bytes_written_flags = le16_to_cpu(desc->bytes_written_flags); + + cq_desc_dec((struct cq_desc *)desc, type, + color, q_number, completed_index); + + *ingress_port = (completed_index_flags & + CQ_ENET_RQ_DESC_FLAGS_INGRESS_PORT) ? 1 : 0; + *fcoe = (completed_index_flags & CQ_ENET_RQ_DESC_FLAGS_FCOE) ? + 1 : 0; + *eop = (completed_index_flags & CQ_ENET_RQ_DESC_FLAGS_EOP) ? + 1 : 0; + *sop = (completed_index_flags & CQ_ENET_RQ_DESC_FLAGS_SOP) ? + 1 : 0; + + *rss_type = (u8)((q_number_rss_type_flags >> CQ_DESC_Q_NUM_BITS) & + CQ_ENET_RQ_DESC_RSS_TYPE_MASK); + *csum_not_calc = (q_number_rss_type_flags & + CQ_ENET_RQ_DESC_FLAGS_CSUM_NOT_CALC) ? 1 : 0; + + *rss_hash = le32_to_cpu(desc->rss_hash); + + *bytes_written = bytes_written_flags & + CQ_ENET_RQ_DESC_BYTES_WRITTEN_MASK; + *packet_error = (bytes_written_flags & + CQ_ENET_RQ_DESC_FLAGS_TRUNCATED) ? 1 : 0; + *vlan_stripped = (bytes_written_flags & + CQ_ENET_RQ_DESC_FLAGS_VLAN_STRIPPED) ? 1 : 0; + + *vlan = le16_to_cpu(desc->vlan); + + if (*fcoe) { + *fcoe_sof = (u8)(le16_to_cpu(desc->checksum_fcoe) & + CQ_ENET_RQ_DESC_FCOE_SOF_MASK); + *fcoe_fc_crc_ok = (desc->flags & + CQ_ENET_RQ_DESC_FCOE_FC_CRC_OK) ? 1 : 0; + *fcoe_enc_error = (desc->flags & + CQ_ENET_RQ_DESC_FCOE_ENC_ERROR) ? 1 : 0; + *fcoe_eof = (u8)((desc->checksum_fcoe >> + CQ_ENET_RQ_DESC_FCOE_EOF_SHIFT) & + CQ_ENET_RQ_DESC_FCOE_EOF_MASK); + *checksum = 0; + } else { + *fcoe_sof = 0; + *fcoe_fc_crc_ok = 0; + *fcoe_enc_error = 0; + *fcoe_eof = 0; + *checksum = le16_to_cpu(desc->checksum_fcoe); + } + + *tcp_udp_csum_ok = + (desc->flags & CQ_ENET_RQ_DESC_FLAGS_TCP_UDP_CSUM_OK) ? 1 : 0; + *udp = (desc->flags & CQ_ENET_RQ_DESC_FLAGS_UDP) ? 1 : 0; + *tcp = (desc->flags & CQ_ENET_RQ_DESC_FLAGS_TCP) ? 1 : 0; + *ipv4_csum_ok = + (desc->flags & CQ_ENET_RQ_DESC_FLAGS_IPV4_CSUM_OK) ? 1 : 0; + *ipv6 = (desc->flags & CQ_ENET_RQ_DESC_FLAGS_IPV6) ? 1 : 0; + *ipv4 = (desc->flags & CQ_ENET_RQ_DESC_FLAGS_IPV4) ? 1 : 0; + *ipv4_fragment = + (desc->flags & CQ_ENET_RQ_DESC_FLAGS_IPV4_FRAGMENT) ? 1 : 0; + *fcs_ok = (desc->flags & CQ_ENET_RQ_DESC_FLAGS_FCS_OK) ? 1 : 0; +} + +#endif /* _CQ_ENET_DESC_H_ */ diff --git a/drivers/scsi/fnic/cq_exch_desc.h b/drivers/scsi/fnic/cq_exch_desc.h new file mode 100644 index 000000000..4d94329c8 --- /dev/null +++ b/drivers/scsi/fnic/cq_exch_desc.h @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _CQ_EXCH_DESC_H_ +#define _CQ_EXCH_DESC_H_ + +#include "cq_desc.h" + +/* Exchange completion queue descriptor: 16B */ +struct cq_exch_wq_desc { + u16 completed_index; + u16 q_number; + u16 exchange_id; + u8 tmpl; + u8 reserved0; + u32 reserved1; + u8 exch_status; + u8 reserved2[2]; + u8 type_color; +}; + +#define CQ_EXCH_WQ_STATUS_BITS 2 +#define CQ_EXCH_WQ_STATUS_MASK ((1 << CQ_EXCH_WQ_STATUS_BITS) - 1) + +enum cq_exch_status_types { + CQ_EXCH_WQ_STATUS_TYPE_COMPLETE = 0, + CQ_EXCH_WQ_STATUS_TYPE_ABORT = 1, + CQ_EXCH_WQ_STATUS_TYPE_SGL_EOF = 2, + CQ_EXCH_WQ_STATUS_TYPE_TMPL_ERR = 3, +}; + +static inline void cq_exch_wq_desc_dec(struct cq_exch_wq_desc *desc_ptr, + u8 *type, + u8 *color, + u16 *q_number, + u16 *completed_index, + u8 *exch_status) +{ + cq_desc_dec((struct cq_desc *)desc_ptr, type, + color, q_number, completed_index); + *exch_status = desc_ptr->exch_status & CQ_EXCH_WQ_STATUS_MASK; +} + +struct cq_fcp_rq_desc { + u16 completed_index_eop_sop_prt; + u16 q_number; + u16 exchange_id; + u16 tmpl; + u16 bytes_written; + u16 vlan; + u8 sof; + u8 eof; + u8 fcs_fer_fck; + u8 type_color; +}; + +#define CQ_FCP_RQ_DESC_FLAGS_SOP (1 << 15) +#define CQ_FCP_RQ_DESC_FLAGS_EOP (1 << 14) +#define CQ_FCP_RQ_DESC_FLAGS_PRT (1 << 12) +#define CQ_FCP_RQ_DESC_TMPL_MASK 0x1f +#define CQ_FCP_RQ_DESC_BYTES_WRITTEN_MASK 0x3fff +#define CQ_FCP_RQ_DESC_PACKET_ERR_SHIFT 14 +#define CQ_FCP_RQ_DESC_PACKET_ERR_MASK (1 << CQ_FCP_RQ_DESC_PACKET_ERR_SHIFT) +#define CQ_FCP_RQ_DESC_VS_STRIPPED_SHIFT 15 +#define CQ_FCP_RQ_DESC_VS_STRIPPED_MASK (1 << CQ_FCP_RQ_DESC_VS_STRIPPED_SHIFT) +#define CQ_FCP_RQ_DESC_FC_CRC_OK_MASK 0x1 +#define CQ_FCP_RQ_DESC_FCOE_ERR_SHIFT 1 +#define CQ_FCP_RQ_DESC_FCOE_ERR_MASK (1 << CQ_FCP_RQ_DESC_FCOE_ERR_SHIFT) +#define CQ_FCP_RQ_DESC_FCS_OK_SHIFT 7 +#define CQ_FCP_RQ_DESC_FCS_OK_MASK (1 << CQ_FCP_RQ_DESC_FCS_OK_SHIFT) + +static inline void cq_fcp_rq_desc_dec(struct cq_fcp_rq_desc *desc_ptr, + u8 *type, + u8 *color, + u16 *q_number, + u16 *completed_index, + u8 *eop, + u8 *sop, + u8 *fck, + u16 *exchange_id, + u16 *tmpl, + u32 *bytes_written, + u8 *sof, + u8 *eof, + u8 *ingress_port, + u8 *packet_err, + u8 *fcoe_err, + u8 *fcs_ok, + u8 *vlan_stripped, + u16 *vlan) +{ + cq_desc_dec((struct cq_desc *)desc_ptr, type, + color, q_number, completed_index); + *eop = (desc_ptr->completed_index_eop_sop_prt & + CQ_FCP_RQ_DESC_FLAGS_EOP) ? 1 : 0; + *sop = (desc_ptr->completed_index_eop_sop_prt & + CQ_FCP_RQ_DESC_FLAGS_SOP) ? 1 : 0; + *ingress_port = + (desc_ptr->completed_index_eop_sop_prt & + CQ_FCP_RQ_DESC_FLAGS_PRT) ? 1 : 0; + *exchange_id = desc_ptr->exchange_id; + *tmpl = desc_ptr->tmpl & CQ_FCP_RQ_DESC_TMPL_MASK; + *bytes_written = + desc_ptr->bytes_written & CQ_FCP_RQ_DESC_BYTES_WRITTEN_MASK; + *packet_err = + (desc_ptr->bytes_written & CQ_FCP_RQ_DESC_PACKET_ERR_MASK) >> + CQ_FCP_RQ_DESC_PACKET_ERR_SHIFT; + *vlan_stripped = + (desc_ptr->bytes_written & CQ_FCP_RQ_DESC_VS_STRIPPED_MASK) >> + CQ_FCP_RQ_DESC_VS_STRIPPED_SHIFT; + *vlan = desc_ptr->vlan; + *sof = desc_ptr->sof; + *fck = desc_ptr->fcs_fer_fck & CQ_FCP_RQ_DESC_FC_CRC_OK_MASK; + *fcoe_err = (desc_ptr->fcs_fer_fck & CQ_FCP_RQ_DESC_FCOE_ERR_MASK) >> + CQ_FCP_RQ_DESC_FCOE_ERR_SHIFT; + *eof = desc_ptr->eof; + *fcs_ok = + (desc_ptr->fcs_fer_fck & CQ_FCP_RQ_DESC_FCS_OK_MASK) >> + CQ_FCP_RQ_DESC_FCS_OK_SHIFT; +} + +struct cq_sgl_desc { + u16 exchange_id; + u16 q_number; + u32 active_burst_offset; + u32 tot_data_bytes; + u16 tmpl; + u8 sgl_err; + u8 type_color; +}; + +enum cq_sgl_err_types { + CQ_SGL_ERR_NO_ERROR = 0, + CQ_SGL_ERR_OVERFLOW, /* data ran beyond end of SGL */ + CQ_SGL_ERR_SGL_LCL_ADDR_ERR, /* sgl access to local vnic addr illegal*/ + CQ_SGL_ERR_ADDR_RSP_ERR, /* sgl address error */ + CQ_SGL_ERR_DATA_RSP_ERR, /* sgl data rsp error */ + CQ_SGL_ERR_CNT_ZERO_ERR, /* SGL count is 0 */ + CQ_SGL_ERR_CNT_MAX_ERR, /* SGL count is larger than supported */ + CQ_SGL_ERR_ORDER_ERR, /* frames recv on both ports, order err */ + CQ_SGL_ERR_DATA_LCL_ADDR_ERR,/* sgl data buf to local vnic addr ill */ + CQ_SGL_ERR_HOST_CQ_ERR, /* host cq entry to local vnic addr ill */ +}; + +#define CQ_SGL_SGL_ERR_MASK 0x1f +#define CQ_SGL_TMPL_MASK 0x1f + +static inline void cq_sgl_desc_dec(struct cq_sgl_desc *desc_ptr, + u8 *type, + u8 *color, + u16 *q_number, + u16 *exchange_id, + u32 *active_burst_offset, + u32 *tot_data_bytes, + u16 *tmpl, + u8 *sgl_err) +{ + /* Cheat a little by assuming exchange_id is the same as completed + index */ + cq_desc_dec((struct cq_desc *)desc_ptr, type, color, q_number, + exchange_id); + *active_burst_offset = desc_ptr->active_burst_offset; + *tot_data_bytes = desc_ptr->tot_data_bytes; + *tmpl = desc_ptr->tmpl & CQ_SGL_TMPL_MASK; + *sgl_err = desc_ptr->sgl_err & CQ_SGL_SGL_ERR_MASK; +} + +#endif /* _CQ_EXCH_DESC_H_ */ diff --git a/drivers/scsi/fnic/fcpio.h b/drivers/scsi/fnic/fcpio.h new file mode 100644 index 000000000..a98a2a3bf --- /dev/null +++ b/drivers/scsi/fnic/fcpio.h @@ -0,0 +1,803 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _FCPIO_H_ +#define _FCPIO_H_ + +#include <linux/if_ether.h> + +/* + * This header file includes all of the data structures used for + * communication by the host driver to the fcp firmware. + */ + +/* + * Exchange and sequence id space allocated to the host driver + */ +#define FCPIO_HOST_EXCH_RANGE_START 0x1000 +#define FCPIO_HOST_EXCH_RANGE_END 0x1fff +#define FCPIO_HOST_SEQ_ID_RANGE_START 0x80 +#define FCPIO_HOST_SEQ_ID_RANGE_END 0xff + +struct fnic_nvme_io_event { + struct list_head links; + struct work_struct io_work; + void *arg1; +}; + +struct fnic_io_event_s { + struct list_head links; + struct work_struct io_work; + void *arg1; +}; + +/* + * Command entry type + */ +enum fcpio_type { + /* + * Initiator request types + */ + FCPIO_ICMND_16 = 0x1, + FCPIO_ICMND_32, + FCPIO_ICMND_CMPL, + FCPIO_ITMF, + FCPIO_ITMF_CMPL, + FCPIO_NVME_CMD, + FCPIO_NVME_ERSP_HW_CMPL, + FCPIO_NVME_ERSP_FW_CMPL, + + /* + * Target request types + */ + FCPIO_TCMND_16 = 0x11, + FCPIO_TCMND_32, + FCPIO_TDATA, + FCPIO_TXRDY, + FCPIO_TRSP, + FCPIO_TDRSP_CMPL, + FCPIO_TTMF, + FCPIO_TTMF_ACK, + FCPIO_TABORT, + FCPIO_TABORT_CMPL, + + /* + * Misc request types + */ + FCPIO_ACK = 0x20, + FCPIO_RESET, + FCPIO_RESET_CMPL, + FCPIO_FLOGI_REG, + FCPIO_FLOGI_REG_CMPL, + FCPIO_ECHO, + FCPIO_ECHO_CMPL, + FCPIO_LUNMAP_CHNG, + FCPIO_LUNMAP_REQ, + FCPIO_LUNMAP_REQ_CMPL, + FCPIO_FLOGI_FIP_REG, + FCPIO_FLOGI_FIP_REG_CMPL, +}; + +/* + * Header status codes from the firmware + */ +enum fcpio_status { + FCPIO_SUCCESS = 0, /* request was successful */ + + /* + * If a request to the firmware is rejected, the original request + * header will be returned with the status set to one of the following: + */ + FCPIO_INVALID_HEADER, /* header contains invalid data */ + FCPIO_OUT_OF_RESOURCE, /* out of resources to complete request */ + FCPIO_INVALID_PARAM, /* some parameter in request is invalid */ + FCPIO_REQ_NOT_SUPPORTED, /* request type is not supported */ + FCPIO_IO_NOT_FOUND, /* requested I/O was not found */ + + /* + * Once a request is processed, the firmware will usually return + * a cmpl message type. In cases where errors occurred, + * the header status field would be filled in with one of the following: + */ + FCPIO_ABORTED = 0x41, /* request was aborted */ + FCPIO_TIMEOUT, /* request was timed out */ + FCPIO_SGL_INVALID, /* request was aborted due to sgl error */ + FCPIO_MSS_INVALID, /* request was aborted due to mss error */ + FCPIO_DATA_CNT_MISMATCH, /* recv/sent more/less data than exp. */ + FCPIO_FW_ERR, /* request was terminated due to fw error */ + FCPIO_ITMF_REJECTED, /* itmf req was rejected by remote node */ + FCPIO_ITMF_FAILED, /* itmf req was failed by remote node */ + FCPIO_ITMF_INCORRECT_LUN, /* itmf req targeted incorrect LUN */ + FCPIO_CMND_REJECTED, /* request was invalid and rejected */ + FCPIO_NO_PATH_AVAIL, /* no paths to the lun was available */ + FCPIO_PATH_FAILED, /* i/o sent to current path failed */ + FCPIO_LUNMAP_CHNG_PEND, /* i/o rejected due to lunmap change */ +}; + +/* + * The header command tag. All host requests will use the "tag" field + * to mark commands with a unique tag. When the firmware responds to + * a host request, it will copy the tag field into the response. + * + * The only firmware requests that will use the rx_id/ox_id fields instead + * of the tag field will be the target command and target task management + * requests. These two requests do not have corresponding host requests + * since they come directly from the FC initiator on the network. + */ +struct fcpio_tag { + union { + u32 req_id; + struct { + u16 rx_id; + u16 ox_id; + } ex_id; + } u; +}; + +static inline void +fcpio_tag_id_enc(struct fcpio_tag *tag, u32 id) +{ + tag->u.req_id = id; +} + +static inline void +fcpio_tag_id_dec(struct fcpio_tag *tag, u32 *id) +{ + *id = tag->u.req_id; +} + +static inline void +fcpio_tag_exid_enc(struct fcpio_tag *tag, u16 ox_id, u16 rx_id) +{ + tag->u.ex_id.rx_id = rx_id; + tag->u.ex_id.ox_id = ox_id; +} + +static inline void +fcpio_tag_exid_dec(struct fcpio_tag *tag, u16 *ox_id, u16 *rx_id) +{ + *rx_id = tag->u.ex_id.rx_id; + *ox_id = tag->u.ex_id.ox_id; +} + +/* + * The header for an fcpio request, whether from the firmware or from the + * host driver + */ +struct fcpio_header { + u8 type; /* enum fcpio_type */ + u8 status; /* header status entry */ + u16 _resvd; /* reserved */ + struct fcpio_tag tag; /* header tag */ +}; + +static inline void +fcpio_header_enc(struct fcpio_header *hdr, + u8 type, u8 status, + struct fcpio_tag tag) +{ + hdr->type = type; + hdr->status = status; + hdr->_resvd = 0; + hdr->tag = tag; +} + +static inline void +fcpio_header_dec(struct fcpio_header *hdr, + u8 *type, u8 *status, + struct fcpio_tag *tag) +{ + *type = hdr->type; + *status = hdr->status; + *tag = hdr->tag; +} + +#define NVME_CMD_SZ 96 +#define CDB_16 16 +#define CDB_32 32 +#define LUN_ADDRESS 8 + +struct fcpio_nvme_cmnd { + uint8_t d_id[3]; + u_int8_t flags; /* command flags */ + u_int32_t sgl_cnt; /* scatter-gather list count */ + u_int64_t sgl_addr; /* scatter-gather list addr */ + u_int16_t cmd_len; + u_int16_t _resvd1; /* reserved: should be 0 */ + u_int32_t data_len; /* length of data expected */ + u_int8_t nvme_cmnd[NVME_CMD_SZ]; /* NVME command */ +}; + +/* + * fcpio_icmnd_16: host -> firmware request + * + * used for sending out an initiator SCSI 16-byte command + */ +struct fcpio_icmnd_16 { + u32 lunmap_id; /* index into lunmap table */ + u8 special_req_flags; /* special exchange request flags */ + u8 _resvd0[3]; /* reserved */ + u32 sgl_cnt; /* scatter-gather list count */ + u32 sense_len; /* sense buffer length */ + u64 sgl_addr; /* scatter-gather list addr */ + u64 sense_addr; /* sense buffer address */ + u8 crn; /* SCSI Command Reference No. */ + u8 pri_ta; /* SCSI Priority and Task attribute */ + u8 _resvd1; /* reserved: should be 0 */ + u8 flags; /* command flags */ + u8 scsi_cdb[CDB_16]; /* SCSI Cmnd Descriptor Block */ + u32 data_len; /* length of data expected */ + u8 lun[LUN_ADDRESS]; /* FC vNIC only: LUN address */ + u8 _resvd2; /* reserved */ + u8 d_id[3]; /* FC vNIC only: Target D_ID */ + u16 mss; /* FC vNIC only: max burst */ + u16 _resvd3; /* reserved */ + u32 r_a_tov; /* FC vNIC only: Res. Alloc Timeout */ + u32 e_d_tov; /* FC vNIC only: Err Detect Timeout */ +}; + +/* + * Special request flags + */ +#define FCPIO_ICMND_SRFLAG_RETRY 0x01 /* Enable Retry handling on exchange */ + +/* + * Priority/Task Attribute settings + */ +#define FCPIO_ICMND_PTA_SIMPLE 0 /* simple task attribute */ +#define FCPIO_ICMND_PTA_HEADQ 1 /* head of queue task attribute */ +#define FCPIO_ICMND_PTA_ORDERED 2 /* ordered task attribute */ +#define FCPIO_ICMND_PTA_ACA 4 /* auto contingent allegiance */ +#define FCPIO_ICMND_PRI_SHIFT 3 /* priority field starts in bit 3 */ + +/* + * Command flags + */ +#define FCPIO_ICMND_RDDATA 0x02 /* read data */ +#define FCPIO_ICMND_WRDATA 0x01 /* write data */ + +/* + * fcpio_icmnd_32: host -> firmware request + * + * used for sending out an initiator SCSI 32-byte command + */ +struct fcpio_icmnd_32 { + u32 lunmap_id; /* index into lunmap table */ + u8 special_req_flags; /* special exchange request flags */ + u8 _resvd0[3]; /* reserved */ + u32 sgl_cnt; /* scatter-gather list count */ + u32 sense_len; /* sense buffer length */ + u64 sgl_addr; /* scatter-gather list addr */ + u64 sense_addr; /* sense buffer address */ + u8 crn; /* SCSI Command Reference No. */ + u8 pri_ta; /* SCSI Priority and Task attribute */ + u8 _resvd1; /* reserved: should be 0 */ + u8 flags; /* command flags */ + u8 scsi_cdb[CDB_32]; /* SCSI Cmnd Descriptor Block */ + u32 data_len; /* length of data expected */ + u8 lun[LUN_ADDRESS]; /* FC vNIC only: LUN address */ + u8 _resvd2; /* reserved */ + u8 d_id[3]; /* FC vNIC only: Target D_ID */ + u16 mss; /* FC vNIC only: max burst */ + u16 _resvd3; /* reserved */ + u32 r_a_tov; /* FC vNIC only: Res. Alloc Timeout */ + u32 e_d_tov; /* FC vNIC only: Error Detect Timeout */ +}; + +/* + * fcpio_itmf: host -> firmware request + * + * used for requesting the firmware to abort a request and/or send out + * a task management function + * + * The t_tag field is only needed when the request type is ABT_TASK. + */ +struct fcpio_itmf { + u32 lunmap_id; /* index into lunmap table */ + u32 tm_req; /* SCSI Task Management request */ + u32 t_tag; /* header tag of fcpio to be aborted */ + u32 _resvd; /* _reserved */ + u8 lun[LUN_ADDRESS]; /* FC vNIC only: LUN address */ + u8 _resvd1; /* reserved */ + u8 d_id[3]; /* FC vNIC only: Target D_ID */ + u32 r_a_tov; /* FC vNIC only: R_A_TOV in msec */ + u32 e_d_tov; /* FC vNIC only: E_D_TOV in msec */ +}; + +/* + * Task Management request + */ +enum fcpio_itmf_tm_req_type { + FCPIO_ITMF_ABT_TASK_TERM = 0x01, /* abort task and terminate */ + FCPIO_ITMF_ABT_TASK, /* abort task and issue abts */ + FCPIO_ITMF_ABT_TASK_SET, /* abort task set */ + FCPIO_ITMF_CLR_TASK_SET, /* clear task set */ + FCPIO_ITMF_LUN_RESET, /* logical unit reset task mgmt */ + FCPIO_ITMF_CLR_ACA, /* Clear ACA condition */ +}; + +/* + * fcpio_tdata: host -> firmware request + * + * used for requesting the firmware to send out a read data transfer for a + * target command + */ +struct fcpio_tdata { + u16 rx_id; /* FC rx_id of target command */ + u16 flags; /* command flags */ + u32 rel_offset; /* data sequence relative offset */ + u32 sgl_cnt; /* scatter-gather list count */ + u32 data_len; /* length of data expected to send */ + u64 sgl_addr; /* scatter-gather list address */ +}; + +/* + * Command flags + */ +#define FCPIO_TDATA_SCSI_RSP 0x01 /* send a scsi resp. after last frame */ + +/* + * fcpio_txrdy: host -> firmware request + * + * used for requesting the firmware to send out a write data transfer for a + * target command + */ +struct fcpio_txrdy { + u16 rx_id; /* FC rx_id of target command */ + u16 _resvd0; /* reserved */ + u32 rel_offset; /* data sequence relative offset */ + u32 sgl_cnt; /* scatter-gather list count */ + u32 data_len; /* length of data expected to send */ + u64 sgl_addr; /* scatter-gather list address */ +}; + +/* + * fcpio_trsp: host -> firmware request + * + * used for requesting the firmware to send out a response for a target + * command + */ +struct fcpio_trsp { + u16 rx_id; /* FC rx_id of target command */ + u16 _resvd0; /* reserved */ + u32 sense_len; /* sense data buffer length */ + u64 sense_addr; /* sense data buffer address */ + u16 _resvd1; /* reserved */ + u8 flags; /* response request flags */ + u8 scsi_status; /* SCSI status */ + u32 residual; /* SCSI data residual value of I/O */ +}; + +/* + * resposnse request flags + */ +#define FCPIO_TRSP_RESID_UNDER 0x08 /* residual is valid and is underflow */ +#define FCPIO_TRSP_RESID_OVER 0x04 /* residual is valid and is overflow */ + +/* + * fcpio_ttmf_ack: host -> firmware response + * + * used by the host to indicate to the firmware it has received and processed + * the target tmf request + */ +struct fcpio_ttmf_ack { + u16 rx_id; /* FC rx_id of target command */ + u16 _resvd0; /* reserved */ + u32 tmf_status; /* SCSI task management status */ +}; + +/* + * fcpio_tabort: host -> firmware request + * + * used by the host to request the firmware to abort a target request that was + * received by the firmware + */ +struct fcpio_tabort { + u16 rx_id; /* rx_id of the target request */ +}; + +/* + * fcpio_reset: host -> firmware request + * + * used by the host to signal a reset of the driver to the firmware + * and to request firmware to clean up all outstanding I/O + */ +struct fcpio_reset { + u32 _resvd; +}; + +enum fcpio_flogi_reg_format_type { + FCPIO_FLOGI_REG_DEF_DEST = 0, /* Use the oui | s_id mac format */ + FCPIO_FLOGI_REG_GW_DEST, /* Use the fixed gateway mac */ +}; + +/* + * fcpio_flogi_reg: host -> firmware request + * + * fc vnic only + * used by the host to notify the firmware of the lif's s_id + * and destination mac address format + */ +struct fcpio_flogi_reg { + u8 format; + u8 s_id[3]; /* FC vNIC only: Source S_ID */ + u8 gateway_mac[ETH_ALEN]; /* Destination gateway mac */ + u16 _resvd; + u32 r_a_tov; /* R_A_TOV in msec */ + u32 e_d_tov; /* E_D_TOV in msec */ +}; + +/* + * fcpio_echo: host -> firmware request + * + * sends a heartbeat echo request to the firmware + */ +struct fcpio_echo { + u32 _resvd; +}; + +/* + * fcpio_lunmap_req: host -> firmware request + * + * scsi vnic only + * sends a request to retrieve the lunmap table for scsi vnics + */ +struct fcpio_lunmap_req { + u64 addr; /* address of the buffer */ + u32 len; /* len of the buffer */ +}; + +/* + * fcpio_flogi_fip_reg: host -> firmware request + * + * fc vnic only + * used by the host to notify the firmware of the lif's s_id + * and destination mac address format + */ +struct fcpio_flogi_fip_reg { + u8 _resvd0; + u8 s_id[3]; /* FC vNIC only: Source S_ID */ + u8 fcf_mac[ETH_ALEN]; /* FCF Target destination mac */ + u16 _resvd1; + u32 r_a_tov; /* R_A_TOV in msec */ + u32 e_d_tov; /* E_D_TOV in msec */ + u8 ha_mac[ETH_ALEN]; /* Host adapter source mac */ + u16 _resvd2; +}; + +/* + * Basic structure for all fcpio structures that are sent from the host to the + * firmware. They are 128 bytes per structure. + */ +#define FCPIO_HOST_REQ_LEN 128 /* expected length of host requests */ + +struct fcpio_host_req { + struct fcpio_header hdr; + + union { + /* + * Defines space needed for request + */ + u8 buf[FCPIO_HOST_REQ_LEN - sizeof(struct fcpio_header)]; + + /* + * Initiator host requests + */ + struct fcpio_nvme_cmnd nvcmnd; + struct fcpio_icmnd_16 icmnd_16; + struct fcpio_icmnd_32 icmnd_32; + struct fcpio_itmf itmf; + + /* + * Target host requests + */ + struct fcpio_tdata tdata; + struct fcpio_txrdy txrdy; + struct fcpio_trsp trsp; + struct fcpio_ttmf_ack ttmf_ack; + struct fcpio_tabort tabort; + + /* + * Misc requests + */ + struct fcpio_reset reset; + struct fcpio_flogi_reg flogi_reg; + struct fcpio_echo echo; + struct fcpio_lunmap_req lunmap_req; + struct fcpio_flogi_fip_reg flogi_fip_reg; + } u; +}; + +struct fcpio_nvme_cmpl { + uint8_t resp_size; + uint8_t resvd[3]; + uint8_t resp_bytes[32]; +}; + +/* + * fcpio_icmnd_cmpl: firmware -> host response + * + * used for sending the host a response to an initiator command + */ +struct fcpio_icmnd_cmpl { + u8 _resvd0[6]; /* reserved */ + u8 flags; /* response flags */ + u8 scsi_status; /* SCSI status */ + u32 residual; /* SCSI data residual length */ + u32 sense_len; /* SCSI sense length */ +}; + +/* + * response flags + */ +#define FCPIO_ICMND_CMPL_RESID_UNDER 0x08 /* resid under and valid */ +#define FCPIO_ICMND_CMPL_RESID_OVER 0x04 /* resid over and valid */ + +/* + * fcpio_itmf_cmpl: firmware -> host response + * + * used for sending the host a response for a itmf request + */ +struct fcpio_itmf_cmpl { + u32 _resvd; /* reserved */ +}; + +/* + * fcpio_tcmnd_16: firmware -> host request + * + * used by the firmware to notify the host of an incoming target SCSI 16-Byte + * request + */ +struct fcpio_tcmnd_16 { + u8 lun[LUN_ADDRESS]; /* FC vNIC only: LUN address */ + u8 crn; /* SCSI Command Reference No. */ + u8 pri_ta; /* SCSI Priority and Task attribute */ + u8 _resvd2; /* reserved: should be 0 */ + u8 flags; /* command flags */ + u8 scsi_cdb[CDB_16]; /* SCSI Cmnd Descriptor Block */ + u32 data_len; /* length of data expected */ + u8 _resvd1; /* reserved */ + u8 s_id[3]; /* FC vNIC only: Source S_ID */ +}; + +/* + * Priority/Task Attribute settings + */ +#define FCPIO_TCMND_PTA_SIMPLE 0 /* simple task attribute */ +#define FCPIO_TCMND_PTA_HEADQ 1 /* head of queue task attribute */ +#define FCPIO_TCMND_PTA_ORDERED 2 /* ordered task attribute */ +#define FCPIO_TCMND_PTA_ACA 4 /* auto contingent allegiance */ +#define FCPIO_TCMND_PRI_SHIFT 3 /* priority field starts in bit 3 */ + +/* + * Command flags + */ +#define FCPIO_TCMND_RDDATA 0x02 /* read data */ +#define FCPIO_TCMND_WRDATA 0x01 /* write data */ + +/* + * fcpio_tcmnd_32: firmware -> host request + * + * used by the firmware to notify the host of an incoming target SCSI 32-Byte + * request + */ +struct fcpio_tcmnd_32 { + u8 lun[LUN_ADDRESS]; /* FC vNIC only: LUN address */ + u8 crn; /* SCSI Command Reference No. */ + u8 pri_ta; /* SCSI Priority and Task attribute */ + u8 _resvd2; /* reserved: should be 0 */ + u8 flags; /* command flags */ + u8 scsi_cdb[CDB_32]; /* SCSI Cmnd Descriptor Block */ + u32 data_len; /* length of data expected */ + u8 _resvd0; /* reserved */ + u8 s_id[3]; /* FC vNIC only: Source S_ID */ +}; + +/* + * fcpio_tdrsp_cmpl: firmware -> host response + * + * used by the firmware to notify the host of a response to a host target + * command + */ +struct fcpio_tdrsp_cmpl { + u16 rx_id; /* rx_id of the target request */ + u16 _resvd0; /* reserved */ +}; + +/* + * fcpio_ttmf: firmware -> host request + * + * used by the firmware to notify the host of an incoming task management + * function request + */ +struct fcpio_ttmf { + u8 _resvd0; /* reserved */ + u8 s_id[3]; /* FC vNIC only: Source S_ID */ + u8 lun[LUN_ADDRESS]; /* FC vNIC only: LUN address */ + u8 crn; /* SCSI Command Reference No. */ + u8 _resvd2[3]; /* reserved */ + u32 tmf_type; /* task management request type */ +}; + +/* + * Task Management request + */ +#define FCPIO_TTMF_CLR_ACA 0x40 /* Clear ACA condition */ +#define FCPIO_TTMF_LUN_RESET 0x10 /* logical unit reset task mgmt */ +#define FCPIO_TTMF_CLR_TASK_SET 0x04 /* clear task set */ +#define FCPIO_TTMF_ABT_TASK_SET 0x02 /* abort task set */ +#define FCPIO_TTMF_ABT_TASK 0x01 /* abort task */ + +/* + * fcpio_tabort_cmpl: firmware -> host response + * + * used by the firmware to respond to a host's tabort request + */ +struct fcpio_tabort_cmpl { + u16 rx_id; /* rx_id of the target request */ + u16 _resvd0; /* reserved */ +}; + +/* + * fcpio_ack: firmware -> host response + * + * used by firmware to notify the host of the last work request received + */ +struct fcpio_ack { + u16 request_out; /* last host entry received */ + u16 _resvd; +}; + +/* + * fcpio_reset_cmpl: firmware -> host response + * + * use by firmware to respond to the host's reset request + */ +struct fcpio_reset_cmpl { + u16 vnic_id; +}; + +/* + * fcpio_flogi_reg_cmpl: firmware -> host response + * + * fc vnic only + * response to the fcpio_flogi_reg request + */ +struct fcpio_flogi_reg_cmpl { + u32 _resvd; +}; + +/* + * fcpio_echo_cmpl: firmware -> host response + * + * response to the fcpio_echo request + */ +struct fcpio_echo_cmpl { + u32 _resvd; +}; + +/* + * fcpio_lunmap_chng: firmware -> host notification + * + * scsi vnic only + * notifies the host that the lunmap tables have changed + */ +struct fcpio_lunmap_chng { + u32 _resvd; +}; + +/* + * fcpio_lunmap_req_cmpl: firmware -> host response + * + * scsi vnic only + * response for lunmap table request from the host + */ +struct fcpio_lunmap_req_cmpl { + u32 _resvd; +}; + +/* + * Basic structure for all fcpio structures that are sent from the firmware to + * the host. They are 64 bytes per structure. + */ +#define FCPIO_FW_REQ_LEN 64 /* expected length of fw requests */ +struct fcpio_fw_req { + struct fcpio_header hdr; + + union { + /* + * Defines space needed for request + */ + u8 buf[FCPIO_FW_REQ_LEN - sizeof(struct fcpio_header)]; + + /* + * Initiator firmware responses + */ + struct fcpio_icmnd_cmpl icmnd_cmpl; + struct fcpio_nvme_cmpl nvme_cmpl; + struct fcpio_itmf_cmpl itmf_cmpl; + + /* + * Target firmware new requests + */ + struct fcpio_tcmnd_16 tcmnd_16; + struct fcpio_tcmnd_32 tcmnd_32; + + /* + * Target firmware responses + */ + struct fcpio_tdrsp_cmpl tdrsp_cmpl; + struct fcpio_ttmf ttmf; + struct fcpio_tabort_cmpl tabort_cmpl; + + /* + * Firmware response to work received + */ + struct fcpio_ack ack; + + /* + * Misc requests + */ + struct fcpio_reset_cmpl reset_cmpl; + struct fcpio_flogi_reg_cmpl flogi_reg_cmpl; + struct fcpio_echo_cmpl echo_cmpl; + struct fcpio_lunmap_chng lunmap_chng; + struct fcpio_lunmap_req_cmpl lunmap_req_cmpl; + } u; +}; + +/* + * Access routines to encode and decode the color bit, which is the most + * significant bit of the MSB of the structure + */ +static inline void fcpio_color_enc(struct fcpio_fw_req *fw_req, u8 color) +{ + u8 *c = ((u8 *) fw_req) + sizeof(struct fcpio_fw_req) - 1; + + if (color) + *c |= 0x80; + else + *c &= ~0x80; +} + +static inline void fcpio_color_dec(struct fcpio_fw_req *fw_req, u8 *color) +{ + u8 *c = ((u8 *) fw_req) + sizeof(struct fcpio_fw_req) - 1; + + *color = *c >> 7; + + /* + * Make sure color bit is read from desc *before* other fields + * are read from desc. Hardware guarantees color bit is last + * bit (byte) written. Adding the rmb() prevents the compiler + * and/or CPU from reordering the reads which would potentially + * result in reading stale values. + */ + + rmb(); + +} + +/* + * Lunmap table entry for scsi vnics + */ +#define FCPIO_LUNMAP_TABLE_SIZE 256 +#define FCPIO_FLAGS_LUNMAP_VALID 0x80 +#define FCPIO_FLAGS_BOOT 0x01 +struct fcpio_lunmap_entry { + u8 bus; + u8 target; + u8 lun; + u8 path_cnt; + u16 flags; + u16 update_cnt; +}; + +struct fcpio_lunmap_tbl { + u32 update_cnt; + struct fcpio_lunmap_entry lunmaps[FCPIO_LUNMAP_TABLE_SIZE]; +}; + +#endif /* _FCPIO_H_ */ diff --git a/drivers/scsi/fnic/fdls_disc.c b/drivers/scsi/fnic/fdls_disc.c new file mode 100644 index 000000000..8cb404668 --- /dev/null +++ b/drivers/scsi/fnic/fdls_disc.c @@ -0,0 +1,5259 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ + +#include <linux/workqueue.h> +#include "fnic.h" +#include "fdls_fc.h" +#include "fnic_fdls.h" +#include <scsi/fc/fc_fcp.h> +#include <scsi/scsi_transport_fc.h> +#include <linux/utsname.h> + +#define FC_FC4_TYPE_SCSI 0x08 +#define FNIC_NVME_FEAT_TARG (1 << 0) +#define FNIC_NVME_FEAT_INIT (1 << 1) +#define FNIC_NVME_FEAT_DISC (1 << 2) + +#define PORT_SPEED_BIT_8 8 +#define PORT_SPEED_BIT_9 9 +#define PORT_SPEED_BIT_14 14 +#define PORT_SPEED_BIT_15 15 + +/* FNIC FDMI Register HBA Macros */ +#define FNIC_FDMI_NUM_PORTS 1 +#define FNIC_FDMI_NUM_HBA_ATTRS 9 +#define FNIC_FDMI_TYPE_NODE_NAME 0X1 +#define FNIC_FDMI_TYPE_MANUFACTURER 0X2 +#define FNIC_FDMI_MANUFACTURER "Cisco Systems" +#define FNIC_FDMI_TYPE_SERIAL_NUMBER 0X3 +#define FNIC_FDMI_TYPE_MODEL 0X4 +#define FNIC_FDMI_TYPE_MODEL_DES 0X5 +#define FNIC_FDMI_MODEL_DESCRIPTION "Cisco Virtual Interface Card" +#define FNIC_FDMI_TYPE_HARDWARE_VERSION 0X6 +#define FNIC_FDMI_TYPE_DRIVER_VERSION 0X7 +#define FNIC_FDMI_TYPE_ROM_VERSION 0X8 +#define FNIC_FDMI_TYPE_FIRMWARE_VERSION 0X9 +#define FNIC_FDMI_NN_LEN 8 +#define FNIC_FDMI_MANU_LEN 20 +#define FNIC_FDMI_SERIAL_LEN 16 +#define FNIC_FDMI_MODEL_LEN 12 +#define FNIC_FDMI_MODEL_DES_LEN 56 +#define FNIC_FDMI_HW_VER_LEN 16 +#define FNIC_FDMI_DR_VER_LEN 28 +#define FNIC_FDMI_ROM_VER_LEN 8 +#define FNIC_FDMI_FW_VER_LEN 16 + +/* FNIC FDMI Register PA Macros */ +#define FNIC_FDMI_TYPE_FC4_TYPES 0X1 +#define FNIC_FDMI_TYPE_SUPPORTED_SPEEDS 0X2 +#define FNIC_FDMI_TYPE_CURRENT_SPEED 0X3 +#define FNIC_FDMI_TYPE_MAX_FRAME_SIZE 0X4 +#define FNIC_FDMI_TYPE_OS_NAME 0X5 +#define FNIC_FDMI_TYPE_HOST_NAME 0X6 +#define FNIC_FDMI_NUM_PORT_ATTRS 6 +#define FNIC_FDMI_FC4_LEN 32 +#define FNIC_FDMI_SUPP_SPEED_LEN 4 +#define FNIC_FDMI_CUR_SPEED_LEN 4 +#define FNIC_FDMI_MFS_LEN 4 +#define FNIC_FDMI_MFS 0x800 +#define FNIC_FDMI_OS_NAME_LEN 16 +#define FNIC_FDMI_HN_LEN 24 + +#define FDLS_FDMI_PLOGI_PENDING 0x1 +#define FDLS_FDMI_REG_HBA_PENDING 0x2 +#define FDLS_FDMI_RPA_PENDING 0x4 +#define FDLS_FDMI_ABORT_PENDING 0x8 +#define FDLS_FDMI_MAX_RETRY 3 + +#define RETRIES_EXHAUSTED(iport) \ + (iport->fabric.retry_counter == FABRIC_LOGO_MAX_RETRY) + +#define FNIC_TPORT_MAX_NEXUS_RESTART (8) + +#define SCHEDULE_OXID_FREE_RETRY_TIME (300) + +/* Private Functions */ +static void fdls_fdmi_register_hba(struct fnic_iport_s *iport); +static void fdls_fdmi_register_pa(struct fnic_iport_s *iport); +static void fdls_send_rpn_id(struct fnic_iport_s *iport); +static void fdls_process_flogi_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr, + void *rx_frame); +static void fnic_fdls_start_plogi(struct fnic_iport_s *iport); +static void fnic_fdls_start_flogi(struct fnic_iport_s *iport); +static struct fnic_tport_s *fdls_create_tport(struct fnic_iport_s *iport, + uint32_t fcid, + uint64_t wwpn); +static void fdls_target_restart_nexus(struct fnic_tport_s *tport); +static void fdls_start_tport_timer(struct fnic_iport_s *iport, + struct fnic_tport_s *tport, int timeout); +static void fdls_tport_timer_callback(struct timer_list *t); +static void fdls_send_fdmi_plogi(struct fnic_iport_s *iport); +static void fdls_start_fabric_timer(struct fnic_iport_s *iport, + int timeout); +static void fdls_init_plogi_frame(uint8_t *frame, struct fnic_iport_s *iport); +static void fdls_init_els_acc_frame(uint8_t *frame, struct fnic_iport_s *iport); +static void fdls_init_els_rjt_frame(uint8_t *frame, struct fnic_iport_s *iport); +static void fdls_init_logo_frame(uint8_t *frame, struct fnic_iport_s *iport); +static void fdls_init_fabric_abts_frame(uint8_t *frame, + struct fnic_iport_s *iport); + +uint8_t *fdls_alloc_frame(struct fnic_iport_s *iport) +{ + struct fnic *fnic = iport->fnic; + uint8_t *frame = NULL; + + frame = mempool_alloc(fnic->frame_pool, GFP_ATOMIC); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame"); + return NULL; + } + + memset(frame, 0, FNIC_FCOE_FRAME_MAXSZ); + return frame; +} + +/** + * fdls_alloc_oxid - Allocate an oxid from the bitmap based oxid pool + * @iport: Handle to iport instance + * @oxid_frame_type: Type of frame to allocate + * @active_oxid: the oxid which is in use + * + * Called with fnic lock held + */ +uint16_t fdls_alloc_oxid(struct fnic_iport_s *iport, int oxid_frame_type, + uint16_t *active_oxid) +{ + struct fnic *fnic = iport->fnic; + struct fnic_oxid_pool_s *oxid_pool = &iport->oxid_pool; + int idx; + uint16_t oxid; + + lockdep_assert_held(&fnic->fnic_lock); + + /* + * Allocate next available oxid from bitmap + */ + idx = find_next_zero_bit(oxid_pool->bitmap, FNIC_OXID_POOL_SZ, oxid_pool->next_idx); + if (idx == FNIC_OXID_POOL_SZ) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Alloc oxid: all oxid slots are busy iport state:%d\n", + iport->state); + return FNIC_UNASSIGNED_OXID; + } + + WARN_ON(test_and_set_bit(idx, oxid_pool->bitmap)); + oxid_pool->next_idx = (idx + 1) % FNIC_OXID_POOL_SZ; /* cycle through the bitmap */ + + oxid = FNIC_OXID_ENCODE(idx, oxid_frame_type); + *active_oxid = oxid; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "alloc oxid: 0x%x, iport state: %d\n", + oxid, iport->state); + return oxid; +} + +/** + * fdls_free_oxid_idx - Free the oxid using the idx + * @iport: Handle to iport instance + * @oxid_idx: The index to free + * + * Free the oxid immediately and make it available for new requests + * Called with fnic lock held + */ +static void fdls_free_oxid_idx(struct fnic_iport_s *iport, uint16_t oxid_idx) +{ + struct fnic *fnic = iport->fnic; + struct fnic_oxid_pool_s *oxid_pool = &iport->oxid_pool; + + lockdep_assert_held(&fnic->fnic_lock); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "free oxid idx: 0x%x\n", oxid_idx); + + WARN_ON(!test_and_clear_bit(oxid_idx, oxid_pool->bitmap)); +} + +/** + * fdls_reclaim_oxid_handler - Callback handler for delayed_oxid_work + * @work: Handle to work_struct + * + * Scheduled when an oxid is to be freed later + * After freeing expired oxid(s), the handler schedules + * another callback with the remaining time + * of next unexpired entry in the reclaim list. + */ +void fdls_reclaim_oxid_handler(struct work_struct *work) +{ + struct fnic_oxid_pool_s *oxid_pool = container_of(work, + struct fnic_oxid_pool_s, oxid_reclaim_work.work); + struct fnic_iport_s *iport = container_of(oxid_pool, + struct fnic_iport_s, oxid_pool); + struct fnic *fnic = iport->fnic; + struct reclaim_entry_s *reclaim_entry, *next; + unsigned long delay_j, cur_jiffies; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Reclaim oxid callback\n"); + + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + + /* Though the work was scheduled for one entry, + * walk through and free the expired entries which might have been scheduled + * at around the same time as the first entry + */ + list_for_each_entry_safe(reclaim_entry, next, + &(oxid_pool->oxid_reclaim_list), links) { + + /* The list is always maintained in the order of expiry time */ + cur_jiffies = jiffies; + if (time_before(cur_jiffies, reclaim_entry->expires)) + break; + + list_del(&reclaim_entry->links); + fdls_free_oxid_idx(iport, reclaim_entry->oxid_idx); + kfree(reclaim_entry); + } + + /* schedule to free up the next entry */ + if (!list_empty(&oxid_pool->oxid_reclaim_list)) { + reclaim_entry = list_first_entry(&oxid_pool->oxid_reclaim_list, + struct reclaim_entry_s, links); + + delay_j = reclaim_entry->expires - cur_jiffies; + schedule_delayed_work(&oxid_pool->oxid_reclaim_work, delay_j); + FNIC_FCS_DBG(KERN_INFO, fnic, + "Scheduling next callback at:%ld jiffies\n", delay_j); + } + + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); +} + +/** + * fdls_free_oxid - Helper function to free the oxid + * @iport: Handle to iport instance + * @oxid: oxid to free + * @active_oxid: the oxid which is in use + * + * Called with fnic lock held + */ +void fdls_free_oxid(struct fnic_iport_s *iport, + uint16_t oxid, uint16_t *active_oxid) +{ + fdls_free_oxid_idx(iport, FNIC_OXID_IDX(oxid)); + *active_oxid = FNIC_UNASSIGNED_OXID; +} + +/** + * fdls_schedule_oxid_free - Schedule oxid to be freed later + * @iport: Handle to iport instance + * @active_oxid: the oxid which is in use + * + * Gets called in a rare case scenario when both a command + * (fdls or target discovery) timed out and the following ABTS + * timed out as well, without a link change. + * + * Called with fnic lock held + */ +void fdls_schedule_oxid_free(struct fnic_iport_s *iport, uint16_t *active_oxid) +{ + struct fnic *fnic = iport->fnic; + struct fnic_oxid_pool_s *oxid_pool = &iport->oxid_pool; + struct reclaim_entry_s *reclaim_entry; + unsigned long delay_j = msecs_to_jiffies(OXID_RECLAIM_TOV(iport)); + int oxid_idx = FNIC_OXID_IDX(*active_oxid); + + lockdep_assert_held(&fnic->fnic_lock); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Schedule oxid free. oxid: 0x%x\n", *active_oxid); + + *active_oxid = FNIC_UNASSIGNED_OXID; + + reclaim_entry = (struct reclaim_entry_s *) + kzalloc_obj(struct reclaim_entry_s, GFP_ATOMIC); + + if (!reclaim_entry) { + FNIC_FCS_DBG(KERN_WARNING, fnic, + "Failed to allocate memory for reclaim struct for oxid idx: %d\n", + oxid_idx); + + /* Retry the scheduling */ + WARN_ON(test_and_set_bit(oxid_idx, oxid_pool->pending_schedule_free)); + schedule_delayed_work(&oxid_pool->schedule_oxid_free_retry, 0); + return; + } + + reclaim_entry->oxid_idx = oxid_idx; + reclaim_entry->expires = round_jiffies(jiffies + delay_j); + + list_add_tail(&reclaim_entry->links, &oxid_pool->oxid_reclaim_list); + + schedule_delayed_work(&oxid_pool->oxid_reclaim_work, delay_j); +} + +/** + * fdls_schedule_oxid_free_retry_work - Thread to schedule the + * oxid to be freed later + * + * @work: Handle to the work struct + */ +void fdls_schedule_oxid_free_retry_work(struct work_struct *work) +{ + struct fnic_oxid_pool_s *oxid_pool = container_of(work, + struct fnic_oxid_pool_s, schedule_oxid_free_retry.work); + struct fnic_iport_s *iport = container_of(oxid_pool, + struct fnic_iport_s, oxid_pool); + struct fnic *fnic = iport->fnic; + struct reclaim_entry_s *reclaim_entry; + unsigned long delay_j = msecs_to_jiffies(OXID_RECLAIM_TOV(iport)); + unsigned long flags; + int idx; + + for_each_set_bit(idx, oxid_pool->pending_schedule_free, FNIC_OXID_POOL_SZ) { + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Schedule oxid free. oxid idx: %d\n", idx); + + reclaim_entry = kzalloc_obj(*reclaim_entry); + if (!reclaim_entry) { + schedule_delayed_work(&oxid_pool->schedule_oxid_free_retry, + msecs_to_jiffies(SCHEDULE_OXID_FREE_RETRY_TIME)); + return; + } + + reclaim_entry->oxid_idx = idx; + reclaim_entry->expires = round_jiffies(jiffies + delay_j); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + /* Reset may have cleared this bit while this worker allocated. */ + if (!test_and_clear_bit(idx, oxid_pool->pending_schedule_free)) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + kfree(reclaim_entry); + continue; + } + list_add_tail(&reclaim_entry->links, &oxid_pool->oxid_reclaim_list); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + schedule_delayed_work(&oxid_pool->oxid_reclaim_work, delay_j); + } +} + +static bool fdls_is_oxid_fabric_req(uint16_t oxid) +{ + int oxid_frame_type = FNIC_FRAME_TYPE(oxid); + + switch (oxid_frame_type) { + case FNIC_FRAME_TYPE_FABRIC_FLOGI: + case FNIC_FRAME_TYPE_FABRIC_PLOGI: + case FNIC_FRAME_TYPE_FABRIC_RPN: + case FNIC_FRAME_TYPE_FABRIC_RFT: + case FNIC_FRAME_TYPE_FABRIC_RFF: + case FNIC_FRAME_TYPE_FABRIC_GPN_FT: + case FNIC_FRAME_TYPE_FABRIC_LOGO: + break; + default: + return false; + } + return true; +} + +static bool fdls_is_oxid_fdmi_req(uint16_t oxid) +{ + int oxid_frame_type = FNIC_FRAME_TYPE(oxid); + + switch (oxid_frame_type) { + case FNIC_FRAME_TYPE_FDMI_PLOGI: + case FNIC_FRAME_TYPE_FDMI_RHBA: + case FNIC_FRAME_TYPE_FDMI_RPA: + break; + default: + return false; + } + return true; +} + +static bool fdls_is_oxid_tgt_req(uint16_t oxid) +{ + int oxid_frame_type = FNIC_FRAME_TYPE(oxid); + + switch (oxid_frame_type) { + case FNIC_FRAME_TYPE_TGT_PLOGI: + case FNIC_FRAME_TYPE_TGT_PRLI: + case FNIC_FRAME_TYPE_TGT_ADISC: + case FNIC_FRAME_TYPE_TGT_LOGO: + break; + default: + return false; + } + return true; +} + +static inline bool fdls_is_oxid_nvme_req(uint16_t oxid) +{ + return FNIC_FRAME_TYPE(oxid) == FNIC_FRAME_TYPE_NVME_LS; +} + +static void fdls_reset_oxid_pool(struct fnic_iport_s *iport) +{ + struct fnic_oxid_pool_s *oxid_pool = &iport->oxid_pool; + struct reclaim_entry_s *reclaim_entry, *next; + + cancel_delayed_work(&oxid_pool->oxid_reclaim_work); + cancel_delayed_work(&oxid_pool->schedule_oxid_free_retry); + list_for_each_entry_safe(reclaim_entry, next, + &oxid_pool->oxid_reclaim_list, links) { + list_del(&reclaim_entry->links); + kfree(reclaim_entry); + } + bitmap_clear(oxid_pool->pending_schedule_free, 0, FNIC_OXID_POOL_SZ); + bitmap_clear(oxid_pool->bitmap, 0, FNIC_OXID_POOL_SZ); + oxid_pool->next_idx = 0; +} + +void fnic_del_fabric_timer_sync(struct fnic *fnic) +{ + fnic->iport.fabric.del_timer_inprogress = 1; + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + timer_delete_sync(&fnic->iport.fabric.retry_timer); + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + fnic->iport.fabric.del_timer_inprogress = 0; +} + +void fnic_del_tport_timer_sync(struct fnic *fnic, + struct fnic_tport_s *tport) +{ + tport->del_timer_inprogress = 1; + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + timer_delete_sync(&tport->retry_timer); + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + tport->del_timer_inprogress = 0; +} + +static void +fdls_start_fabric_timer(struct fnic_iport_s *iport, int timeout) +{ + u64 fabric_tov; + struct fnic *fnic = iport->fnic; + + if (iport->fabric.timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport fcid: 0x%x: Canceling fabric disc timer\n", + iport->fcid); + fnic_del_fabric_timer_sync(fnic); + iport->fabric.timer_pending = 0; + } + + if (!(iport->fabric.flags & FNIC_FDLS_FABRIC_ABORT_ISSUED)) + iport->fabric.retry_counter++; + + fabric_tov = jiffies + msecs_to_jiffies(timeout); + mod_timer(&iport->fabric.retry_timer, round_jiffies(fabric_tov)); + iport->fabric.timer_pending = 1; + FNIC_FCS_DBG(KERN_INFO, fnic, + "fabric timer is %d ", timeout); +} + +static void +fdls_start_tport_timer(struct fnic_iport_s *iport, + struct fnic_tport_s *tport, int timeout) +{ + u64 fabric_tov; + struct fnic *fnic = iport->fnic; + + if (tport->timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "tport fcid 0x%x: Canceling disc timer\n", + tport->fcid); + fnic_del_tport_timer_sync(fnic, tport); + tport->timer_pending = 0; + } + + if (!(tport->flags & FNIC_FDLS_TGT_ABORT_ISSUED)) + tport->retry_counter++; + + fabric_tov = jiffies + msecs_to_jiffies(timeout); + mod_timer(&tport->retry_timer, round_jiffies(fabric_tov)); + tport->timer_pending = 1; +} + +void fdls_init_plogi_frame(uint8_t *frame, + struct fnic_iport_s *iport) +{ + struct fc_std_flogi *pplogi; + uint8_t s_id[3]; + + pplogi = (struct fc_std_flogi *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + *pplogi = (struct fc_std_flogi) { + .fchdr = {.fh_r_ctl = FC_RCTL_ELS_REQ, .fh_d_id = {0xFF, 0xFF, 0xFC}, + .fh_type = FC_TYPE_ELS, .fh_f_ctl = {FNIC_ELS_REQ_FCTL, 0, 0}, + .fh_rx_id = cpu_to_be16(FNIC_UNASSIGNED_RXID)}, + .els = { + .fl_cmd = ELS_PLOGI, + .fl_csp = {.sp_hi_ver = FNIC_FC_PH_VER_HI, + .sp_lo_ver = FNIC_FC_PH_VER_LO, + .sp_bb_cred = cpu_to_be16(FNIC_FC_B2B_CREDIT), + .sp_features = cpu_to_be16(FC_SP_FT_CIRO), + .sp_bb_data = cpu_to_be16(FNIC_FC_B2B_RDF_SZ), + .sp_tot_seq = cpu_to_be16(FNIC_FC_CONCUR_SEQS), + .sp_rel_off = cpu_to_be16(FNIC_FC_RO_INFO), + .sp_e_d_tov = cpu_to_be32(FC_DEF_E_D_TOV)}, + .fl_cssp[2].cp_class = cpu_to_be16(FC_CPC_VALID | FC_CPC_SEQ), + .fl_cssp[2].cp_rdfs = cpu_to_be16(0x800), + .fl_cssp[2].cp_con_seq = cpu_to_be16(0xFF), + .fl_cssp[2].cp_open_seq = 1} + }; + + FNIC_STD_SET_NPORT_NAME(&pplogi->els.fl_wwpn, iport->wwpn); + FNIC_STD_SET_NODE_NAME(&pplogi->els.fl_wwnn, iport->wwnn); + FNIC_LOGI_SET_RDF_SIZE(pplogi->els, iport->max_payload_size); + + hton24(s_id, iport->fcid); + FNIC_STD_SET_S_ID(pplogi->fchdr, s_id); +} + +static void fdls_init_els_acc_frame(uint8_t *frame, + struct fnic_iport_s *iport) +{ + struct fc_std_els_acc_rsp *pels_acc; + uint8_t s_id[3]; + + pels_acc = (struct fc_std_els_acc_rsp *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + *pels_acc = (struct fc_std_els_acc_rsp) { + .fchdr = {.fh_r_ctl = FC_RCTL_ELS_REP, + .fh_type = FC_TYPE_ELS, .fh_f_ctl = {FNIC_ELS_REP_FCTL, 0, 0}}, + .acc.la_cmd = ELS_LS_ACC, + }; + + hton24(s_id, iport->fcid); + FNIC_STD_SET_S_ID(pels_acc->fchdr, s_id); + FNIC_STD_SET_RX_ID(pels_acc->fchdr, FNIC_UNASSIGNED_RXID); +} + +static void fdls_init_els_rjt_frame(uint8_t *frame, + struct fnic_iport_s *iport) +{ + struct fc_std_els_rjt_rsp *pels_rjt; + + pels_rjt = (struct fc_std_els_rjt_rsp *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + *pels_rjt = (struct fc_std_els_rjt_rsp) { + .fchdr = {.fh_r_ctl = FC_RCTL_ELS_REP, .fh_type = FC_TYPE_ELS, + .fh_f_ctl = {FNIC_ELS_REP_FCTL, 0, 0}}, + .rej.er_cmd = ELS_LS_RJT, + }; + + FNIC_STD_SET_RX_ID(pels_rjt->fchdr, FNIC_UNASSIGNED_RXID); +} + +static void fdls_init_logo_frame(uint8_t *frame, + struct fnic_iport_s *iport) +{ + struct fc_std_logo *plogo; + uint8_t s_id[3]; + + plogo = (struct fc_std_logo *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + *plogo = (struct fc_std_logo) { + .fchdr = {.fh_r_ctl = FC_RCTL_ELS_REQ, .fh_type = FC_TYPE_ELS, + .fh_f_ctl = {FNIC_ELS_REQ_FCTL, 0, 0}}, + .els.fl_cmd = ELS_LOGO, + }; + + hton24(s_id, iport->fcid); + FNIC_STD_SET_S_ID(plogo->fchdr, s_id); + memcpy(plogo->els.fl_n_port_id, s_id, 3); + + FNIC_STD_SET_NPORT_NAME(&plogo->els.fl_n_port_wwn, + iport->wwpn); +} + +static void fdls_init_fabric_abts_frame(uint8_t *frame, + struct fnic_iport_s *iport) +{ + struct fc_frame_header *pfabric_abts; + + pfabric_abts = (struct fc_frame_header *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + *pfabric_abts = (struct fc_frame_header) { + .fh_r_ctl = FC_RCTL_BA_ABTS, /* ABTS */ + .fh_s_id = {0x00, 0x00, 0x00}, + .fh_cs_ctl = 0x00, .fh_type = FC_TYPE_BLS, + .fh_f_ctl = {FNIC_REQ_ABTS_FCTL, 0, 0}, .fh_seq_id = 0x00, + .fh_df_ctl = 0x00, .fh_seq_cnt = 0x0000, + .fh_rx_id = cpu_to_be16(FNIC_UNASSIGNED_RXID), + .fh_parm_offset = 0x00000000, /* bit:0 = 0 Abort a exchange */ + }; +} + +static void fdls_set_frame_type(struct fnic *fnic, uint8_t *frame_type) +{ + if (IS_FNIC_FCP_INITIATOR(fnic)) + *frame_type = FC_TYPE_FCP; + else if (IS_FNIC_NVME_INITIATOR(fnic)) + *frame_type = FC_TYPE_NVME; +} + +static void fdls_set_feature_type(struct fnic *fnic, uint8_t *feature_type) +{ + if (IS_FNIC_FCP_INITIATOR(fnic)) + *feature_type = FCP_FEAT_INIT; + else if (IS_FNIC_NVME_INITIATOR(fnic)) + *feature_type = FNIC_NVME_FEAT_INIT; +} + +static void +fdls_send_rscn_resp(struct fnic_iport_s *iport, + struct fc_frame_header *rscn_fchdr) +{ + uint8_t *frame; + struct fc_std_els_acc_rsp *pels_acc; + struct fnic *fnic = iport->fnic; + uint16_t oxid; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_els_acc_rsp); + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send RSCN response"); + return; + } + + pels_acc = (struct fc_std_els_acc_rsp *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + fdls_init_els_acc_frame(frame, iport); + + FNIC_STD_SET_D_ID(pels_acc->fchdr, rscn_fchdr->fh_s_id); + + oxid = FNIC_STD_GET_OX_ID(rscn_fchdr); + FNIC_STD_SET_OX_ID(pels_acc->fchdr, oxid); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send RSCN response with oxid: 0x%x", + iport->fcid, oxid); + + fnic_send_fcoe_frame(iport, frame, frame_size); +} + +static void +fdls_send_logo_resp(struct fnic_iport_s *iport, + struct fc_frame_header *req_fchdr) +{ + uint8_t *frame; + struct fc_std_els_acc_rsp *plogo_resp; + struct fnic *fnic = iport->fnic; + uint16_t oxid; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_els_acc_rsp); + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send LOGO response"); + return; + } + + plogo_resp = (struct fc_std_els_acc_rsp *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + fdls_init_els_acc_frame(frame, iport); + + FNIC_STD_SET_D_ID(plogo_resp->fchdr, req_fchdr->fh_s_id); + + oxid = FNIC_STD_GET_OX_ID(req_fchdr); + FNIC_STD_SET_OX_ID(plogo_resp->fchdr, oxid); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send LOGO response with oxid: 0x%x", + iport->fcid, oxid); + + fnic_send_fcoe_frame(iport, frame, frame_size); +} + +int fdls_send_ls_req_abts(struct fnic_iport_s *iport, + struct fnic_tport_s *tport, unsigned int oxid) +{ + uint8_t *frame; + uint8_t s_id[3]; + uint8_t d_id[3]; + struct fnic *fnic = iport->fnic; + struct fc_frame_header *pls_req_abts; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_frame_header); + int ret; + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send ls req ABTS"); + return -ENOMEM; + } + + pls_req_abts = (struct fc_frame_header *) (frame + + FNIC_ETH_FCOE_HDRS_OFFSET); + fdls_init_fabric_abts_frame(frame, iport); + + hton24(s_id, iport->fcid); + hton24(d_id, tport->fcid); + FNIC_STD_SET_S_ID(*pls_req_abts, s_id); + FNIC_STD_SET_D_ID(*pls_req_abts, d_id); + + FNIC_STD_SET_OX_ID(*pls_req_abts, oxid); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport 0x%x: tport: 0x%x FDLS sending ls req abts with oxid: 0x%x", + iport->fcid, tport->fcid, oxid); + + ret = fnic_send_fcoe_frame(iport, frame, frame_size); + if (ret) { + mempool_free(frame, fnic->frame_pool); + return ret; + } + + return 0; +} + + +void +fdls_send_tport_abts(struct fnic_iport_s *iport, + struct fnic_tport_s *tport) +{ + uint8_t *frame; + uint8_t s_id[3]; + uint8_t d_id[3]; + struct fnic *fnic = iport->fnic; + struct fc_frame_header *ptport_abts; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_frame_header); + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send tport ABTS"); + return; + } + + ptport_abts = (struct fc_frame_header *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + *ptport_abts = (struct fc_frame_header) { + .fh_r_ctl = FC_RCTL_BA_ABTS, /* ABTS */ + .fh_cs_ctl = 0x00, .fh_type = FC_TYPE_BLS, + .fh_f_ctl = {FNIC_REQ_ABTS_FCTL, 0, 0}, .fh_seq_id = 0x00, + .fh_df_ctl = 0x00, .fh_seq_cnt = 0x0000, + .fh_rx_id = cpu_to_be16(FNIC_UNASSIGNED_RXID), + .fh_parm_offset = 0x00000000, /* bit:0 = 0 Abort a exchange */ + }; + + hton24(s_id, iport->fcid); + hton24(d_id, tport->fcid); + FNIC_STD_SET_S_ID(*ptport_abts, s_id); + FNIC_STD_SET_D_ID(*ptport_abts, d_id); + tport->flags |= FNIC_FDLS_TGT_ABORT_ISSUED; + + FNIC_STD_SET_OX_ID(*ptport_abts, tport->active_oxid); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send tport abts: tport->state: %d ", + iport->fcid, tport->state); + + fnic_send_fcoe_frame(iport, frame, frame_size); + + /* Even if fnic_send_fcoe_frame() fails we want to retry after timeout */ + fdls_start_tport_timer(iport, tport, 2 * iport->e_d_tov); +} +static void fdls_send_fabric_abts(struct fnic_iport_s *iport) +{ + uint8_t *frame; + uint8_t s_id[3]; + uint8_t d_id[3]; + struct fnic *fnic = iport->fnic; + struct fc_frame_header *pfabric_abts; + uint16_t oxid; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_frame_header); + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send fabric ABTS"); + return; + } + + pfabric_abts = (struct fc_frame_header *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + fdls_init_fabric_abts_frame(frame, iport); + + hton24(s_id, iport->fcid); + + switch (iport->fabric.state) { + case FDLS_STATE_FABRIC_LOGO: + hton24(d_id, FC_FID_FLOGI); + FNIC_STD_SET_D_ID(*pfabric_abts, d_id); + break; + + case FDLS_STATE_FABRIC_FLOGI: + hton24(d_id, FC_FID_FLOGI); + FNIC_STD_SET_D_ID(*pfabric_abts, d_id); + break; + + case FDLS_STATE_FABRIC_PLOGI: + FNIC_STD_SET_S_ID(*pfabric_abts, s_id); + hton24(d_id, FC_FID_DIR_SERV); + FNIC_STD_SET_D_ID(*pfabric_abts, d_id); + break; + + case FDLS_STATE_RPN_ID: + FNIC_STD_SET_S_ID(*pfabric_abts, s_id); + hton24(d_id, FC_FID_DIR_SERV); + FNIC_STD_SET_D_ID(*pfabric_abts, d_id); + break; + + case FDLS_STATE_SCR: + FNIC_STD_SET_S_ID(*pfabric_abts, s_id); + hton24(d_id, FC_FID_FCTRL); + FNIC_STD_SET_D_ID(*pfabric_abts, d_id); + break; + + case FDLS_STATE_REGISTER_FC4_TYPES: + FNIC_STD_SET_S_ID(*pfabric_abts, s_id); + hton24(d_id, FC_FID_DIR_SERV); + FNIC_STD_SET_D_ID(*pfabric_abts, d_id); + break; + + case FDLS_STATE_REGISTER_FC4_FEATURES: + FNIC_STD_SET_S_ID(*pfabric_abts, s_id); + hton24(d_id, FC_FID_DIR_SERV); + FNIC_STD_SET_D_ID(*pfabric_abts, d_id); + break; + + case FDLS_STATE_GPN_FT: + FNIC_STD_SET_S_ID(*pfabric_abts, s_id); + hton24(d_id, FC_FID_DIR_SERV); + FNIC_STD_SET_D_ID(*pfabric_abts, d_id); + break; + default: + return; + } + + oxid = iport->active_oxid_fabric_req; + FNIC_STD_SET_OX_ID(*pfabric_abts, oxid); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send fabric abts. iport->fabric.state: %d oxid: 0x%x", + iport->fcid, iport->fabric.state, oxid); + + iport->fabric.flags |= FNIC_FDLS_FABRIC_ABORT_ISSUED; + + fnic_send_fcoe_frame(iport, frame, frame_size); + + /* Even if fnic_send_fcoe_frame() fails we want to retry after timeout */ + fdls_start_fabric_timer(iport, 2 * iport->e_d_tov); + iport->fabric.timer_pending = 1; +} + +static uint8_t *fdls_alloc_init_fdmi_abts_frame(struct fnic_iport_s *iport, + uint16_t oxid) +{ + struct fc_frame_header *pfdmi_abts; + uint8_t d_id[3]; + uint8_t *frame; + struct fnic *fnic = iport->fnic; + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send FDMI ABTS"); + return NULL; + } + + pfdmi_abts = (struct fc_frame_header *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + fdls_init_fabric_abts_frame(frame, iport); + + hton24(d_id, FC_FID_MGMT_SERV); + FNIC_STD_SET_D_ID(*pfdmi_abts, d_id); + FNIC_STD_SET_OX_ID(*pfdmi_abts, oxid); + + return frame; +} + +static void fdls_send_fdmi_abts(struct fnic_iport_s *iport) +{ + uint8_t *frame; + struct fnic *fnic = iport->fnic; + unsigned long fdmi_tov; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_frame_header); + + if (iport->fabric.fdmi_pending & FDLS_FDMI_PLOGI_PENDING) { + frame = fdls_alloc_init_fdmi_abts_frame(iport, + iport->active_oxid_fdmi_plogi); + if (frame == NULL) + return; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send FDMI PLOGI abts. iport->fabric.state: %d oxid: 0x%x", + iport->fcid, iport->fabric.state, iport->active_oxid_fdmi_plogi); + fnic_send_fcoe_frame(iport, frame, frame_size); + } else { + if (iport->fabric.fdmi_pending & FDLS_FDMI_REG_HBA_PENDING) { + frame = fdls_alloc_init_fdmi_abts_frame(iport, + iport->active_oxid_fdmi_rhba); + if (frame == NULL) + return; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send FDMI RHBA abts. iport->fabric.state: %d oxid: 0x%x", + iport->fcid, iport->fabric.state, iport->active_oxid_fdmi_rhba); + fnic_send_fcoe_frame(iport, frame, frame_size); + } + if (iport->fabric.fdmi_pending & FDLS_FDMI_RPA_PENDING) { + frame = fdls_alloc_init_fdmi_abts_frame(iport, + iport->active_oxid_fdmi_rpa); + if (frame == NULL) { + if (iport->fabric.fdmi_pending & FDLS_FDMI_REG_HBA_PENDING) + goto arm_timer; + else + return; + } + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send FDMI RPA abts. iport->fabric.state: %d oxid: 0x%x", + iport->fcid, iport->fabric.state, iport->active_oxid_fdmi_rpa); + fnic_send_fcoe_frame(iport, frame, frame_size); + } + } + +arm_timer: + fdmi_tov = jiffies + msecs_to_jiffies(2 * iport->e_d_tov); + mod_timer(&iport->fabric.fdmi_timer, round_jiffies(fdmi_tov)); + iport->fabric.fdmi_pending |= FDLS_FDMI_ABORT_PENDING; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: iport->fabric.fdmi_pending: 0x%x", + iport->fcid, iport->fabric.fdmi_pending); +} + +static void fdls_send_fabric_flogi(struct fnic_iport_s *iport) +{ + uint8_t *frame; + struct fc_std_flogi *pflogi; + struct fnic *fnic = iport->fnic; + uint16_t oxid; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_flogi); + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send FLOGI"); + iport->fabric.flags |= FNIC_FDLS_RETRY_FRAME; + goto err_out; + } + + pflogi = (struct fc_std_flogi *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + *pflogi = (struct fc_std_flogi) { + .fchdr = {.fh_r_ctl = FC_RCTL_ELS_REQ, .fh_d_id = {0xFF, 0xFF, 0xFE}, + .fh_type = FC_TYPE_ELS, .fh_f_ctl = {FNIC_ELS_REQ_FCTL, 0, 0}, + .fh_rx_id = cpu_to_be16(FNIC_UNASSIGNED_RXID)}, + .els.fl_cmd = ELS_FLOGI, + .els.fl_csp = {.sp_hi_ver = FNIC_FC_PH_VER_HI, + .sp_lo_ver = FNIC_FC_PH_VER_LO, + .sp_bb_cred = cpu_to_be16(FNIC_FC_B2B_CREDIT), + .sp_bb_data = cpu_to_be16(FNIC_FC_B2B_RDF_SZ)}, + .els.fl_cssp[2].cp_class = cpu_to_be16(FC_CPC_VALID | FC_CPC_SEQ) + }; + + FNIC_STD_SET_NPORT_NAME(&pflogi->els.fl_wwpn, iport->wwpn); + FNIC_STD_SET_NODE_NAME(&pflogi->els.fl_wwnn, iport->wwnn); + FNIC_LOGI_SET_RDF_SIZE(pflogi->els, iport->max_payload_size); + FNIC_LOGI_SET_R_A_TOV(pflogi->els, iport->r_a_tov); + FNIC_LOGI_SET_E_D_TOV(pflogi->els, iport->e_d_tov); + + oxid = fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_FABRIC_FLOGI, + &iport->active_oxid_fabric_req); + + if (oxid == FNIC_UNASSIGNED_OXID) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Failed to allocate OXID to send FLOGI", + iport->fcid); + mempool_free(frame, fnic->frame_pool); + iport->fabric.flags |= FNIC_FDLS_RETRY_FRAME; + goto err_out; + } + FNIC_STD_SET_OX_ID(pflogi->fchdr, oxid); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send fabric FLOGI with oxid: 0x%x", iport->fcid, + oxid); + + fnic_send_fcoe_frame(iport, frame, frame_size); + atomic64_inc(&iport->iport_stats.fabric_flogi_sent); +err_out: + /* Even if fnic_send_fcoe_frame() fails we want to retry after timeout */ + fdls_start_fabric_timer(iport, 2 * iport->e_d_tov); +} + +static void fdls_send_fabric_plogi(struct fnic_iport_s *iport) +{ + uint8_t *frame; + struct fc_std_flogi *pplogi; + struct fnic *fnic = iport->fnic; + uint16_t oxid; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_flogi); + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send PLOGI"); + iport->fabric.flags |= FNIC_FDLS_RETRY_FRAME; + goto err_out; + } + + pplogi = (struct fc_std_flogi *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + fdls_init_plogi_frame(frame, iport); + + oxid = fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_FABRIC_PLOGI, + &iport->active_oxid_fabric_req); + if (oxid == FNIC_UNASSIGNED_OXID) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Failed to allocate OXID to send fabric PLOGI", + iport->fcid); + mempool_free(frame, fnic->frame_pool); + iport->fabric.flags |= FNIC_FDLS_RETRY_FRAME; + goto err_out; + } + FNIC_STD_SET_OX_ID(pplogi->fchdr, oxid); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send fabric PLOGI with oxid: 0x%x", iport->fcid, + oxid); + + fnic_send_fcoe_frame(iport, frame, frame_size); + atomic64_inc(&iport->iport_stats.fabric_plogi_sent); + +err_out: + /* Even if fnic_send_fcoe_frame() fails we want to retry after timeout */ + fdls_start_fabric_timer(iport, 2 * iport->e_d_tov); +} + +static void fdls_send_fdmi_plogi(struct fnic_iport_s *iport) +{ + uint8_t *frame; + struct fc_std_flogi *pplogi; + struct fnic *fnic = iport->fnic; + uint16_t oxid; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_flogi); + uint8_t d_id[3]; + u64 fdmi_tov; + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send FDMI PLOGI"); + goto err_out; + } + + pplogi = (struct fc_std_flogi *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + fdls_init_plogi_frame(frame, iport); + + oxid = fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_FDMI_PLOGI, + &iport->active_oxid_fdmi_plogi); + + if (oxid == FNIC_UNASSIGNED_OXID) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Failed to allocate OXID to send FDMI PLOGI", + iport->fcid); + mempool_free(frame, fnic->frame_pool); + goto err_out; + } + FNIC_STD_SET_OX_ID(pplogi->fchdr, oxid); + + hton24(d_id, FC_FID_MGMT_SERV); + FNIC_STD_SET_D_ID(pplogi->fchdr, d_id); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send FDMI PLOGI with oxid: 0x%x", + iport->fcid, oxid); + + fnic_send_fcoe_frame(iport, frame, frame_size); + +err_out: + fdmi_tov = jiffies + msecs_to_jiffies(2 * iport->e_d_tov); + mod_timer(&iport->fabric.fdmi_timer, round_jiffies(fdmi_tov)); + iport->fabric.fdmi_pending = FDLS_FDMI_PLOGI_PENDING; +} + +static void fdls_send_rpn_id(struct fnic_iport_s *iport) +{ + uint8_t *frame; + struct fc_std_rpn_id *prpn_id; + struct fnic *fnic = iport->fnic; + uint16_t oxid; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_rpn_id); + uint8_t fcid[3]; + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send RPN_ID"); + iport->fabric.flags |= FNIC_FDLS_RETRY_FRAME; + goto err_out; + } + + prpn_id = (struct fc_std_rpn_id *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + *prpn_id = (struct fc_std_rpn_id) { + .fchdr = {.fh_r_ctl = FC_RCTL_DD_UNSOL_CTL, + .fh_d_id = {0xFF, 0xFF, 0xFC}, .fh_type = FC_TYPE_CT, + .fh_f_ctl = {FNIC_ELS_REQ_FCTL, 0, 0}, + .fh_rx_id = cpu_to_be16(FNIC_UNASSIGNED_RXID)}, + .fc_std_ct_hdr = {.ct_rev = FC_CT_REV, .ct_fs_type = FC_FST_DIR, + .ct_fs_subtype = FC_NS_SUBTYPE, + .ct_cmd = cpu_to_be16(FC_NS_RPN_ID)} + }; + + hton24(fcid, iport->fcid); + FNIC_STD_SET_S_ID(prpn_id->fchdr, fcid); + + FNIC_STD_SET_PORT_ID(prpn_id->rpn_id, fcid); + FNIC_STD_SET_PORT_NAME(prpn_id->rpn_id, iport->wwpn); + + oxid = fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_FABRIC_RPN, + &iport->active_oxid_fabric_req); + + if (oxid == FNIC_UNASSIGNED_OXID) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Failed to allocate OXID to send RPN_ID", + iport->fcid); + mempool_free(frame, fnic->frame_pool); + iport->fabric.flags |= FNIC_FDLS_RETRY_FRAME; + goto err_out; + } + FNIC_STD_SET_OX_ID(prpn_id->fchdr, oxid); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send RPN ID with oxid: 0x%x", iport->fcid, + oxid); + + fnic_send_fcoe_frame(iport, frame, frame_size); + +err_out: + /* Even if fnic_send_fcoe_frame() fails we want to retry after timeout */ + fdls_start_fabric_timer(iport, 2 * iport->e_d_tov); +} + +static void fdls_send_scr(struct fnic_iport_s *iport) +{ + uint8_t *frame; + struct fc_std_scr *pscr; + struct fnic *fnic = iport->fnic; + uint16_t oxid; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_scr); + uint8_t fcid[3]; + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send SCR"); + iport->fabric.flags |= FNIC_FDLS_RETRY_FRAME; + goto err_out; + } + + pscr = (struct fc_std_scr *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + *pscr = (struct fc_std_scr) { + .fchdr = {.fh_r_ctl = FC_RCTL_ELS_REQ, + .fh_d_id = {0xFF, 0xFF, 0xFD}, .fh_type = FC_TYPE_ELS, + .fh_f_ctl = {FNIC_ELS_REQ_FCTL, 0, 0}, + .fh_rx_id = cpu_to_be16(FNIC_UNASSIGNED_RXID)}, + .scr = {.scr_cmd = ELS_SCR, + .scr_reg_func = ELS_SCRF_FULL} + }; + + hton24(fcid, iport->fcid); + FNIC_STD_SET_S_ID(pscr->fchdr, fcid); + + oxid = fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_FABRIC_SCR, + &iport->active_oxid_fabric_req); + if (oxid == FNIC_UNASSIGNED_OXID) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Failed to allocate OXID to send SCR", + iport->fcid); + mempool_free(frame, fnic->frame_pool); + iport->fabric.flags |= FNIC_FDLS_RETRY_FRAME; + goto err_out; + } + FNIC_STD_SET_OX_ID(pscr->fchdr, oxid); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send SCR with oxid: 0x%x", iport->fcid, + oxid); + + fnic_send_fcoe_frame(iport, frame, frame_size); + atomic64_inc(&iport->iport_stats.fabric_scr_sent); + +err_out: + /* Even if fnic_send_fcoe_frame() fails we want to retry after timeout */ + fdls_start_fabric_timer(iport, 2 * iport->e_d_tov); +} + +static void fdls_send_gpn_ft(struct fnic_iport_s *iport, int fdls_state) +{ + uint8_t *frame; + struct fc_std_gpn_ft *pgpn_ft; + struct fnic *fnic = iport->fnic; + uint16_t oxid; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_gpn_ft); + uint8_t fcid[3]; + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send GPN FT"); + iport->fabric.flags |= FNIC_FDLS_RETRY_FRAME; + goto err_out; + } + + pgpn_ft = (struct fc_std_gpn_ft *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + *pgpn_ft = (struct fc_std_gpn_ft) { + .fchdr = {.fh_r_ctl = FC_RCTL_DD_UNSOL_CTL, + .fh_d_id = {0xFF, 0xFF, 0xFC}, .fh_type = FC_TYPE_CT, + .fh_f_ctl = {FNIC_ELS_REQ_FCTL, 0, 0}, + .fh_rx_id = cpu_to_be16(FNIC_UNASSIGNED_RXID)}, + .fc_std_ct_hdr = {.ct_rev = FC_CT_REV, .ct_fs_type = FC_FST_DIR, + .ct_fs_subtype = FC_NS_SUBTYPE, + .ct_cmd = cpu_to_be16(FC_NS_GPN_FT)} + }; + + fdls_set_frame_type(fnic, &pgpn_ft->gpn_ft.fn_fc4_type); + hton24(fcid, iport->fcid); + FNIC_STD_SET_S_ID(pgpn_ft->fchdr, fcid); + + oxid = fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_FABRIC_GPN_FT, + &iport->active_oxid_fabric_req); + + if (oxid == FNIC_UNASSIGNED_OXID) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Failed to allocate OXID to send GPN FT", + iport->fcid); + mempool_free(frame, fnic->frame_pool); + iport->fabric.flags |= FNIC_FDLS_RETRY_FRAME; + goto err_out; + } + FNIC_STD_SET_OX_ID(pgpn_ft->fchdr, oxid); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send GPN FT with oxid: 0x%x", iport->fcid, + oxid); + + fnic_send_fcoe_frame(iport, frame, frame_size); + +err_out: + /* Even if fnic_send_fcoe_frame() fails we want to retry after timeout */ + fdls_start_fabric_timer(iport, 2 * iport->e_d_tov); + fdls_set_state((&iport->fabric), fdls_state); +} + +static void +fdls_send_tgt_adisc(struct fnic_iport_s *iport, struct fnic_tport_s *tport) +{ + uint8_t *frame; + struct fc_std_els_adisc *padisc; + uint8_t s_id[3]; + uint8_t d_id[3]; + uint16_t oxid; + struct fnic *fnic = iport->fnic; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_els_adisc); + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send TGT ADISC"); + tport->flags |= FNIC_FDLS_RETRY_FRAME; + goto err_out; + } + + padisc = (struct fc_std_els_adisc *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + + hton24(s_id, iport->fcid); + hton24(d_id, tport->fcid); + memcpy(padisc->els.adisc_port_id, s_id, 3); + FNIC_STD_SET_S_ID(padisc->fchdr, s_id); + FNIC_STD_SET_D_ID(padisc->fchdr, d_id); + + FNIC_STD_SET_F_CTL(padisc->fchdr, FNIC_ELS_REQ_FCTL << 16); + FNIC_STD_SET_R_CTL(padisc->fchdr, FC_RCTL_ELS_REQ); + FNIC_STD_SET_TYPE(padisc->fchdr, FC_TYPE_ELS); + + oxid = fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_TGT_ADISC, &tport->active_oxid); + if (oxid == FNIC_UNASSIGNED_OXID) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Failed to allocate OXID to send TGT ADISC", + iport->fcid); + mempool_free(frame, fnic->frame_pool); + tport->flags |= FNIC_FDLS_RETRY_FRAME; + goto err_out; + } + FNIC_STD_SET_OX_ID(padisc->fchdr, oxid); + FNIC_STD_SET_RX_ID(padisc->fchdr, FNIC_UNASSIGNED_RXID); + + tport->flags &= ~FNIC_FDLS_TGT_ABORT_ISSUED; + + FNIC_STD_SET_NPORT_NAME(&padisc->els.adisc_wwpn, + iport->wwpn); + FNIC_STD_SET_NODE_NAME(&padisc->els.adisc_wwnn, + iport->wwnn); + + padisc->els.adisc_cmd = ELS_ADISC; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send ADISC to tgt fcid: 0x%x", + iport->fcid, tport->fcid); + + atomic64_inc(&iport->iport_stats.tport_adisc_sent); + + fnic_send_fcoe_frame(iport, frame, frame_size); + +err_out: + /* Even if fnic_send_fcoe_frame() fails we want to retry after timeout */ + fdls_start_tport_timer(iport, tport, 2 * iport->e_d_tov); +} + +bool fdls_delete_tport(struct fnic_iport_s *iport, struct fnic_tport_s *tport) +{ + struct fnic_tport_event_s *tport_del_evt; + struct fnic *fnic = iport->fnic; + + if ((tport->state == FDLS_TGT_STATE_OFFLINING) + || (tport->state == FDLS_TGT_STATE_OFFLINE)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "tport fcid 0x%x: tport state is offlining/offline\n", + tport->fcid); + return false; + } + + fdls_set_tport_state(tport, FDLS_TGT_STATE_OFFLINING); + /* + * By setting this flag, the tport will not be seen in a look-up + * in an RSCN. Even if we move to multithreaded model, this tport + * will be destroyed and a new RSCN will have to create a new one + */ + tport->flags |= FNIC_FDLS_TPORT_TERMINATING; + + if (tport->timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "tport fcid 0x%x: Canceling disc timer\n", + tport->fcid); + fnic_del_tport_timer_sync(fnic, tport); + tport->timer_pending = 0; + } + + if (IS_FNIC_FCP_INITIATOR(fnic)) { + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + fnic_rport_exch_reset(iport->fnic, tport->fcid); + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + } else if (IS_FNIC_NVME_INITIATOR(fnic)) { + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + nvfnic_exch_reset(iport, tport); + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + } + + if ((tport->flags & FNIC_FDLS_SCSI_REGISTERED) || + (tport->flags & FNIC_FDLS_NVME_REGISTERED)) { + tport_del_evt = + kzalloc_obj(struct fnic_tport_event_s, GFP_ATOMIC); + if (!tport_del_evt) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport: 0x%x tport 0x%x: Failed to allocate memory\n", + iport->fcid, tport->fcid); + return false; + } + tport_del_evt->event = TGT_EV_RPORT_DEL; + tport_del_evt->arg1 = (void *) tport; + list_add_tail(&tport_del_evt->links, &fnic->tport_event_list); + queue_work(fnic_event_queue, &fnic->tport_work); + } else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "tport 0x%x not registered, freeing locally\n", + tport->fcid); + list_del(&tport->links); + kfree(tport); + } + + return true; +} + +static void +fdls_send_tgt_plogi(struct fnic_iport_s *iport, struct fnic_tport_s *tport) +{ + uint8_t *frame; + struct fc_std_flogi *pplogi; + struct fnic *fnic = iport->fnic; + uint16_t oxid; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_flogi); + uint8_t d_id[3]; + uint32_t timeout; + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send TGT PLOGI"); + tport->flags |= FNIC_FDLS_RETRY_FRAME; + goto err_out; + } + + pplogi = (struct fc_std_flogi *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + fdls_init_plogi_frame(frame, iport); + + oxid = fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_TGT_PLOGI, &tport->active_oxid); + if (oxid == FNIC_UNASSIGNED_OXID) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Failed to allocate oxid to send PLOGI to fcid: 0x%x", + iport->fcid, tport->fcid); + mempool_free(frame, fnic->frame_pool); + tport->flags |= FNIC_FDLS_RETRY_FRAME; + goto err_out; + } + FNIC_STD_SET_OX_ID(pplogi->fchdr, oxid); + + tport->flags &= ~FNIC_FDLS_TGT_ABORT_ISSUED; + + hton24(d_id, tport->fcid); + FNIC_STD_SET_D_ID(pplogi->fchdr, d_id); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send tgt PLOGI to tgt: 0x%x with oxid: 0x%x", + iport->fcid, tport->fcid, oxid); + + fnic_send_fcoe_frame(iport, frame, frame_size); + atomic64_inc(&iport->iport_stats.tport_plogi_sent); + +err_out: + timeout = max(2 * iport->e_d_tov, iport->plogi_timeout); + /* Even if fnic_send_fcoe_frame() fails we want to retry after timeout */ + fdls_start_tport_timer(iport, tport, timeout); +} + +static uint16_t +fnic_fc_plogi_rsp_rdf(struct fnic_iport_s *iport, + struct fc_std_flogi *plogi_rsp) +{ + uint16_t b2b_rdf_size = + be16_to_cpu(FNIC_LOGI_RDF_SIZE(plogi_rsp->els)); + uint16_t spc3_rdf_size = + be16_to_cpu(plogi_rsp->els.fl_cssp[2].cp_rdfs) & FNIC_FC_C3_RDF; + struct fnic *fnic = iport->fnic; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "MFS: b2b_rdf_size: 0x%x spc3_rdf_size: 0x%x", + b2b_rdf_size, spc3_rdf_size); + + return min(b2b_rdf_size, spc3_rdf_size); +} + +static void fdls_send_register_fc4_types(struct fnic_iport_s *iport) +{ + uint8_t *frame; + struct fc_std_rft_id *prft_id; + struct fnic *fnic = iport->fnic; + uint16_t oxid; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_rft_id); + uint8_t fcid[3]; + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send RFT"); + return; + } + + prft_id = (struct fc_std_rft_id *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + *prft_id = (struct fc_std_rft_id) { + .fchdr = {.fh_r_ctl = FC_RCTL_DD_UNSOL_CTL, + .fh_d_id = {0xFF, 0xFF, 0xFC}, .fh_type = FC_TYPE_CT, + .fh_f_ctl = {FNIC_ELS_REQ_FCTL, 0, 0}, + .fh_rx_id = cpu_to_be16(FNIC_UNASSIGNED_RXID)}, + .fc_std_ct_hdr = {.ct_rev = FC_CT_REV, .ct_fs_type = FC_FST_DIR, + .ct_fs_subtype = FC_NS_SUBTYPE, + .ct_cmd = cpu_to_be16(FC_NS_RFT_ID)} + }; + + hton24(fcid, iport->fcid); + FNIC_STD_SET_S_ID(prft_id->fchdr, fcid); + FNIC_STD_SET_PORT_ID(prft_id->rft_id, fcid); + + oxid = fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_FABRIC_RFT, + &iport->active_oxid_fabric_req); + + if (oxid == FNIC_UNASSIGNED_OXID) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Failed to allocate OXID to send RFT", + iport->fcid); + mempool_free(frame, fnic->frame_pool); + return; + } + FNIC_STD_SET_OX_ID(prft_id->fchdr, oxid); + + if (IS_FNIC_NVME_INITIATOR(fnic)) + prft_id->rft_id.fr_fts.ff_type_map[FC_TYPE_NVME / FC_NS_BPW] = + cpu_to_be32(1 << (FC_TYPE_NVME % FC_NS_BPW)); + else if (IS_FNIC_FCP_INITIATOR(fnic)) + prft_id->rft_id.fr_fts.ff_type_map[FC_TYPE_FCP / FC_NS_BPW] = + cpu_to_be32(1 << (FC_TYPE_FCP % FC_NS_BPW)); + + prft_id->rft_id.fr_fts.ff_type_map[FC_TYPE_CT / FC_NS_BPW] |= + cpu_to_be32(1 << (FC_TYPE_CT % FC_NS_BPW)); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send RFT 0x%08x 0x%08x 0x%08x with oxid: 0x%x", + iport->fcid, prft_id->rft_id.fr_fts.ff_type_map[0], + prft_id->rft_id.fr_fts.ff_type_map[1], + prft_id->rft_id.fr_fts.ff_type_map[2], + oxid); + + fnic_send_fcoe_frame(iport, frame, frame_size); + + /* Even if fnic_send_fcoe_frame() fails we want to retry after timeout */ + fdls_start_fabric_timer(iport, 2 * iport->e_d_tov); +} + +static void fdls_send_register_fc4_features(struct fnic_iport_s *iport) +{ + uint8_t *frame; + struct fc_std_rff_id *prff_id; + struct fnic *fnic = iport->fnic; + uint16_t oxid; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_rff_id); + uint8_t fcid[3]; + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send RFF"); + return; + } + + prff_id = (struct fc_std_rff_id *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + *prff_id = (struct fc_std_rff_id) { + .fchdr = {.fh_r_ctl = FC_RCTL_DD_UNSOL_CTL, + .fh_d_id = {0xFF, 0xFF, 0xFC}, .fh_type = FC_TYPE_CT, + .fh_f_ctl = {FNIC_ELS_REQ_FCTL, 0, 0}, + .fh_rx_id = cpu_to_be16(FNIC_UNASSIGNED_RXID)}, + .fc_std_ct_hdr = {.ct_rev = FC_CT_REV, .ct_fs_type = FC_FST_DIR, + .ct_fs_subtype = FC_NS_SUBTYPE, + .ct_cmd = cpu_to_be16(FC_NS_RFF_ID)}, + }; + + fdls_set_frame_type(fnic, &prff_id->rff_id.fr_type); + fdls_set_feature_type(fnic, &prff_id->rff_id.fr_feat); + + hton24(fcid, iport->fcid); + FNIC_STD_SET_S_ID(prff_id->fchdr, fcid); + FNIC_STD_SET_PORT_ID(prff_id->rff_id, fcid); + + oxid = fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_FABRIC_RFF, + &iport->active_oxid_fabric_req); + + if (oxid == FNIC_UNASSIGNED_OXID) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Failed to allocate OXID to send RFF", + iport->fcid); + mempool_free(frame, fnic->frame_pool); + return; + } + FNIC_STD_SET_OX_ID(prff_id->fchdr, oxid); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send RFF with oxid: 0x%x type 0%x feat 0%x", + iport->fcid, oxid, prff_id->rff_id.fr_type, + prff_id->rff_id.fr_feat); + + fnic_send_fcoe_frame(iport, frame, frame_size); + + /* Even if fnic_send_fcoe_frame() fails we want to retry after timeout */ + fdls_start_fabric_timer(iport, 2 * iport->e_d_tov); +} + +static void +fdls_send_tgt_prli(struct fnic_iport_s *iport, struct fnic_tport_s *tport) +{ + uint8_t *frame; + struct fc_std_els_prli *pprli; + struct fnic *fnic = iport->fnic; + uint16_t oxid; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_els_prli); + uint8_t s_id[3]; + uint8_t d_id[3]; + uint32_t timeout; + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send TGT PRLI"); + tport->flags |= FNIC_FDLS_RETRY_FRAME; + goto err_out; + } + + pprli = (struct fc_std_els_prli *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + *pprli = (struct fc_std_els_prli) { + .fchdr = {.fh_r_ctl = FC_RCTL_ELS_REQ, .fh_type = FC_TYPE_ELS, + .fh_f_ctl = {FNIC_ELS_REQ_FCTL, 0, 0}, + .fh_rx_id = cpu_to_be16(FNIC_UNASSIGNED_RXID)}, + .els_prli = {.prli_cmd = ELS_PRLI}, + .sp = {.spp_params = cpu_to_be32(iport->service_params)} + }; + + fdls_set_frame_type(fnic, &pprli->sp.spp_type); + if (IS_FNIC_FCP_INITIATOR(fnic)) { + pprli->els_prli.prli_spp_len = 16; + pprli->els_prli.prli_len = cpu_to_be16(0x14); + pprli->sp.spp_flags = FC_SPP_EST_IMG_PAIR; + } else if (IS_FNIC_NVME_INITIATOR(fnic)) { + /* Per FC-NVMe, Establish Image Pair must not be set for NVMe PRLI. */ + pprli->els_prli.prli_spp_len = 20; + pprli->els_prli.prli_len = cpu_to_be16(0x18); + } + + oxid = fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_TGT_PRLI, &tport->active_oxid); + if (oxid == FNIC_UNASSIGNED_OXID) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "0x%x: Failed to allocate OXID to send TGT PRLI to 0x%x", + iport->fcid, tport->fcid); + mempool_free(frame, fnic->frame_pool); + tport->flags |= FNIC_FDLS_RETRY_FRAME; + goto err_out; + } + + tport->flags &= ~FNIC_FDLS_TGT_ABORT_ISSUED; + + hton24(s_id, iport->fcid); + hton24(d_id, tport->fcid); + + FNIC_STD_SET_OX_ID(pprli->fchdr, oxid); + FNIC_STD_SET_S_ID(pprli->fchdr, s_id); + FNIC_STD_SET_D_ID(pprli->fchdr, d_id); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send PRLI to tgt: 0x%x with oxid: 0x%x", + iport->fcid, tport->fcid, oxid); + + fnic_send_fcoe_frame(iport, frame, frame_size); + atomic64_inc(&iport->iport_stats.tport_prli_sent); + +err_out: + timeout = max(2 * iport->e_d_tov, iport->plogi_timeout); + /* Even if fnic_send_fcoe_frame() fails we want to retry after timeout */ + fdls_start_tport_timer(iport, tport, timeout); +} + +/** + * fdls_send_fabric_logo - Send flogo to the fcf + * @iport: Handle to fnic iport + * + * This function does not change or check the fabric state. + * It the caller's responsibility to set the appropriate iport fabric + * state when this is called. Normally it is FDLS_STATE_FABRIC_LOGO. + * Currently this assumes to be called with fnic lock held. + */ +void fdls_send_fabric_logo(struct fnic_iport_s *iport) +{ + uint8_t *frame; + struct fc_std_logo *plogo; + struct fnic *fnic = iport->fnic; + uint8_t d_id[3]; + uint16_t oxid; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_logo); + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send fabric LOGO"); + return; + } + + plogo = (struct fc_std_logo *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + fdls_init_logo_frame(frame, iport); + + oxid = fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_FABRIC_LOGO, + &iport->active_oxid_fabric_req); + + if (oxid == FNIC_UNASSIGNED_OXID) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Failed to allocate OXID to send fabric LOGO", + iport->fcid); + mempool_free(frame, fnic->frame_pool); + return; + } + FNIC_STD_SET_OX_ID(plogo->fchdr, oxid); + + hton24(d_id, FC_FID_FLOGI); + FNIC_STD_SET_D_ID(plogo->fchdr, d_id); + + iport->fabric.flags &= ~FNIC_FDLS_FABRIC_ABORT_ISSUED; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send fabric LOGO with oxid: 0x%x", + iport->fcid, oxid); + + fnic_send_fcoe_frame(iport, frame, frame_size); + + fdls_start_fabric_timer(iport, 2 * iport->e_d_tov); +} + +/** + * fdls_tgt_logout - Send plogo to the remote port + * @iport: Handle to fnic iport + * @tport: Handle to remote port + * + * This function does not change or check the fabric/tport state. + * It the caller's responsibility to set the appropriate tport/fabric + * state when this is called. Normally that is fdls_tgt_state_plogo. + * This could be used to send plogo to nameserver process + * also not just target processes + */ +void fdls_tgt_logout(struct fnic_iport_s *iport, struct fnic_tport_s *tport) +{ + uint8_t *frame; + struct fc_std_logo *plogo; + struct fnic *fnic = iport->fnic; + uint8_t d_id[3]; + uint16_t oxid; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_logo); + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send fabric LOGO"); + return; + } + + plogo = (struct fc_std_logo *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + fdls_init_logo_frame(frame, iport); + + oxid = fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_TGT_LOGO, &tport->active_oxid); + if (oxid == FNIC_UNASSIGNED_OXID) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Failed to allocate OXID to send tgt LOGO", + iport->fcid); + mempool_free(frame, fnic->frame_pool); + return; + } + FNIC_STD_SET_OX_ID(plogo->fchdr, oxid); + + hton24(d_id, tport->fcid); + FNIC_STD_SET_D_ID(plogo->fchdr, d_id); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send tgt LOGO with oxid: 0x%x", + iport->fcid, oxid); + + fnic_send_fcoe_frame(iport, frame, frame_size); + + atomic64_inc(&iport->iport_stats.tport_logo_sent); +} + +static void fdls_tgt_discovery_start(struct fnic_iport_s *iport) +{ + struct fnic_tport_s *tport, *next; + u32 old_link_down_cnt = iport->fnic->link_down_cnt; + struct fnic *fnic = iport->fnic; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Starting FDLS target discovery", iport->fcid); + + list_for_each_entry_safe(tport, next, &iport->tport_list, links) { + if ((old_link_down_cnt != iport->fnic->link_down_cnt) + || (iport->state != FNIC_IPORT_STATE_READY)) { + break; + } + /* if we marked the tport as deleted due to GPN_FT + * We should not send ADISC anymore + */ + if ((tport->state == FDLS_TGT_STATE_OFFLINING) || + (tport->state == FDLS_TGT_STATE_OFFLINE)) + continue; + + /* For tports which have received RSCN */ + if (tport->flags & FNIC_FDLS_TPORT_SEND_ADISC) { + tport->retry_counter = 0; + fdls_set_tport_state(tport, FDLS_TGT_STATE_ADISC); + tport->flags &= ~FNIC_FDLS_TPORT_SEND_ADISC; + fdls_send_tgt_adisc(iport, tport); + continue; + } + if (fdls_get_tport_state(tport) != FDLS_TGT_STATE_INIT) { + /* Not a new port, skip */ + continue; + } + tport->retry_counter = 0; + fdls_set_tport_state(tport, FDLS_TGT_STATE_PLOGI); + fdls_send_tgt_plogi(iport, tport); + } + fdls_set_state((&iport->fabric), FDLS_STATE_TGT_DISCOVERY); +} + +/* + * Function to restart the IT nexus if we received any out of + * sequence PLOGI/PRLI response from the target. + * The memory for the new tport structure is allocated + * inside fdls_create_tport and added to the iport's tport list. + * This will get freed later during tport_offline/linkdown + * or module unload. The new_tport pointer will go out of scope + * safely since the memory it is + * pointing to it will be freed later + */ +static void fdls_target_restart_nexus(struct fnic_tport_s *tport) +{ + struct fnic_iport_s *iport = tport->iport; + struct fnic_tport_s *new_tport = NULL; + uint32_t fcid; + uint64_t wwpn; + int nexus_restart_count; + struct fnic *fnic = iport->fnic; + bool retval = true; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "tport fcid: 0x%x state: %d restart_count: %d", + tport->fcid, tport->state, tport->nexus_restart_count); + + fcid = tport->fcid; + wwpn = tport->wwpn; + nexus_restart_count = tport->nexus_restart_count; + + retval = fdls_delete_tport(iport, tport); + if (retval != true) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Error deleting tport: 0x%x", fcid); + return; + } + + if (nexus_restart_count >= FNIC_TPORT_MAX_NEXUS_RESTART) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Exceeded nexus restart retries tport: 0x%x", + fcid); + return; + } + + /* + * Allocate memory for the new tport and add it to + * iport's tport list. + * This memory will be freed during tport_offline/linkdown + * or module unload. The pointer new_tport is safe to go + * out of scope when this function returns, since the memory + * it is pointing to is guaranteed to be freed later + * as mentioned above. + */ + new_tport = fdls_create_tport(iport, fcid, wwpn); + if (!new_tport) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Error creating new tport: 0x%x", fcid); + return; + } + + new_tport->nexus_restart_count = nexus_restart_count + 1; + fdls_send_tgt_plogi(iport, new_tport); + fdls_set_tport_state(new_tport, FDLS_TGT_STATE_PLOGI); +} + +struct fnic_tport_s *fnic_find_tport_by_fcid(struct fnic_iport_s *iport, + uint32_t fcid) +{ + struct fnic_tport_s *tport, *next; + + list_for_each_entry_safe(tport, next, &(iport->tport_list), links) { + if ((tport->fcid == fcid) + && !(tport->flags & FNIC_FDLS_TPORT_TERMINATING)) + return tport; + } + return NULL; +} + +static struct fnic_tport_s *fdls_create_tport(struct fnic_iport_s *iport, + uint32_t fcid, uint64_t wwpn) +{ + struct fnic_tport_s *tport; + struct fnic *fnic = iport->fnic; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "FDLS create tport: fcid: 0x%x wwpn: 0x%llx", fcid, wwpn); + + tport = kzalloc_obj(struct fnic_tport_s, GFP_ATOMIC); + if (!tport) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Memory allocation failure while creating tport: 0x%x\n", + fcid); + return NULL; + } + + tport->max_payload_size = FNIC_FCOE_MAX_FRAME_SZ; + tport->r_a_tov = FC_DEF_R_A_TOV; + tport->e_d_tov = FC_DEF_E_D_TOV; + tport->fcid = fcid; + tport->wwpn = wwpn; + tport->iport = iport; + INIT_LIST_HEAD(&tport->ls_req_list); + + FNIC_FCS_DBG(KERN_DEBUG, fnic, + "Need to setup tport timer callback"); + + timer_setup(&tport->retry_timer, fdls_tport_timer_callback, 0); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Added tport 0x%x", tport->fcid); + fdls_set_tport_state(tport, FDLS_TGT_STATE_INIT); + list_add_tail(&tport->links, &iport->tport_list); + atomic_set(&tport->in_flight, 0); + return tport; +} + +struct fnic_tport_s *fnic_find_tport_by_wwpn(struct fnic_iport_s *iport, + uint64_t wwpn) +{ + struct fnic_tport_s *tport, *next; + + list_for_each_entry_safe(tport, next, &(iport->tport_list), links) { + if ((tport->wwpn == wwpn) + && !(tport->flags & FNIC_FDLS_TPORT_TERMINATING)) + return tport; + } + return NULL; +} + +static void +fnic_fdmi_attr_set(void *attr_start, u16 type, u16 len, + void *data, u32 *off) +{ + u16 size = len + FC_FDMI_ATTR_ENTRY_HEADER_LEN; + struct fc_fdmi_attr_entry *fdmi_attr = (struct fc_fdmi_attr_entry *) + ((u8 *)attr_start + *off); + + put_unaligned_be16(type, &fdmi_attr->type); + put_unaligned_be16(size, &fdmi_attr->len); + memcpy(fdmi_attr->value, data, len); + *off += size; +} + +static void fdls_fdmi_register_hba(struct fnic_iport_s *iport) +{ + uint8_t *frame; + struct fc_std_fdmi_rhba *prhba; + struct fc_fdmi_attr_entry *fdmi_attr; + uint8_t fcid[3]; + int err; + struct fnic *fnic = iport->fnic; + struct vnic_devcmd_fw_info *fw_info = NULL; + uint16_t oxid; + u32 attr_off_bytes, len; + u8 data[64]; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET; + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send FDMI RHBA"); + return; + } + + prhba = (struct fc_std_fdmi_rhba *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + *prhba = (struct fc_std_fdmi_rhba) { + .fchdr = { + .fh_r_ctl = FC_RCTL_DD_UNSOL_CTL, + .fh_d_id = {0xFF, 0XFF, 0XFA}, + .fh_type = FC_TYPE_CT, + .fh_f_ctl = {FNIC_ELS_REQ_FCTL, 0, 0}, + .fh_rx_id = cpu_to_be16(FNIC_UNASSIGNED_RXID) + }, + .fc_std_ct_hdr = { + .ct_rev = FC_CT_REV, .ct_fs_type = FC_FST_MGMT, + .ct_fs_subtype = FC_FDMI_SUBTYPE, + .ct_cmd = cpu_to_be16(FC_FDMI_RHBA) + }, + }; + + hton24(fcid, iport->fcid); + FNIC_STD_SET_S_ID(prhba->fchdr, fcid); + + oxid = fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_FDMI_RHBA, + &iport->active_oxid_fdmi_rhba); + + if (oxid == FNIC_UNASSIGNED_OXID) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Failed to allocate OXID to send FDMI RHBA", + iport->fcid); + mempool_free(frame, fnic->frame_pool); + return; + } + FNIC_STD_SET_OX_ID(prhba->fchdr, oxid); + + put_unaligned_be64(iport->wwpn, &prhba->rhba.hbaid.id); + put_unaligned_be32(FNIC_FDMI_NUM_PORTS, &prhba->rhba.port.numport); + put_unaligned_be64(iport->wwpn, &prhba->rhba.port.port[0].portname); + put_unaligned_be32(FNIC_FDMI_NUM_HBA_ATTRS, + &prhba->rhba.hba_attrs.numattrs); + + fdmi_attr = prhba->rhba.hba_attrs.attr; + attr_off_bytes = 0; + + put_unaligned_be64(iport->wwnn, data); + fnic_fdmi_attr_set(fdmi_attr, FNIC_FDMI_TYPE_NODE_NAME, + FNIC_FDMI_NN_LEN, data, &attr_off_bytes); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "NN set, off=%d", attr_off_bytes); + + strscpy_pad(data, FNIC_FDMI_MANUFACTURER, FNIC_FDMI_MANU_LEN); + fnic_fdmi_attr_set(fdmi_attr, FNIC_FDMI_TYPE_MANUFACTURER, + FNIC_FDMI_MANU_LEN, data, &attr_off_bytes); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "MFG set <%s>, off=%d", data, attr_off_bytes); + + err = vnic_dev_fw_info(fnic->vdev, &fw_info); + if (!err) { + strscpy_pad(data, fw_info->hw_serial_number, + FNIC_FDMI_SERIAL_LEN); + fnic_fdmi_attr_set(fdmi_attr, FNIC_FDMI_TYPE_SERIAL_NUMBER, + FNIC_FDMI_SERIAL_LEN, data, &attr_off_bytes); + FNIC_FCS_DBG(KERN_INFO, fnic, + "SERIAL set <%s>, off=%d", data, attr_off_bytes); + + } + + if (fnic->subsys_desc_len >= FNIC_FDMI_MODEL_LEN) + fnic->subsys_desc_len = FNIC_FDMI_MODEL_LEN - 1; + strscpy_pad(data, fnic->subsys_desc, FNIC_FDMI_MODEL_LEN); + fnic_fdmi_attr_set(fdmi_attr, FNIC_FDMI_TYPE_MODEL, FNIC_FDMI_MODEL_LEN, + data, &attr_off_bytes); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "MODEL set <%s>, off=%d", data, attr_off_bytes); + + strscpy_pad(data, FNIC_FDMI_MODEL_DESCRIPTION, FNIC_FDMI_MODEL_DES_LEN); + fnic_fdmi_attr_set(fdmi_attr, FNIC_FDMI_TYPE_MODEL_DES, + FNIC_FDMI_MODEL_DES_LEN, data, &attr_off_bytes); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "MODEL_DESC set <%s>, off=%d", data, attr_off_bytes); + + if (!err) { + strscpy_pad(data, fw_info->hw_version, FNIC_FDMI_HW_VER_LEN); + fnic_fdmi_attr_set(fdmi_attr, FNIC_FDMI_TYPE_HARDWARE_VERSION, + FNIC_FDMI_HW_VER_LEN, data, &attr_off_bytes); + FNIC_FCS_DBG(KERN_INFO, fnic, + "HW_VER set <%s>, off=%d", data, attr_off_bytes); + + } + + strscpy_pad(data, DRV_VERSION, FNIC_FDMI_DR_VER_LEN); + fnic_fdmi_attr_set(fdmi_attr, FNIC_FDMI_TYPE_DRIVER_VERSION, + FNIC_FDMI_DR_VER_LEN, data, &attr_off_bytes); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "DRV_VER set <%s>, off=%d", data, attr_off_bytes); + + strscpy_pad(data, "N/A", FNIC_FDMI_ROM_VER_LEN); + fnic_fdmi_attr_set(fdmi_attr, FNIC_FDMI_TYPE_ROM_VERSION, + FNIC_FDMI_ROM_VER_LEN, data, &attr_off_bytes); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "ROM_VER set <%s>, off=%d", data, attr_off_bytes); + + if (!err) { + strscpy_pad(data, fw_info->fw_version, FNIC_FDMI_FW_VER_LEN); + fnic_fdmi_attr_set(fdmi_attr, FNIC_FDMI_TYPE_FIRMWARE_VERSION, + FNIC_FDMI_FW_VER_LEN, data, &attr_off_bytes); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "FW_VER set <%s>, off=%d", data, attr_off_bytes); + } + + len = sizeof(struct fc_std_fdmi_rhba) + attr_off_bytes; + frame_size += len; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send FDMI RHBA with oxid: 0x%x fs: %d", iport->fcid, + oxid, frame_size); + + fnic_send_fcoe_frame(iport, frame, frame_size); + iport->fabric.fdmi_pending |= FDLS_FDMI_REG_HBA_PENDING; +} + +static void fdls_fdmi_register_pa(struct fnic_iport_s *iport) +{ + uint8_t *frame; + struct fc_std_fdmi_rpa *prpa; + struct fc_fdmi_attr_entry *fdmi_attr; + uint8_t fcid[3]; + struct fnic *fnic = iport->fnic; + u32 port_speed_bm; + u32 port_speed = vnic_dev_port_speed(fnic->vdev); + uint16_t oxid; + u32 attr_off_bytes, len; + u8 tmp_data[16], data[64]; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET; + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send FDMI RPA"); + return; + } + + prpa = (struct fc_std_fdmi_rpa *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + *prpa = (struct fc_std_fdmi_rpa) { + .fchdr = { + .fh_r_ctl = FC_RCTL_DD_UNSOL_CTL, + .fh_d_id = {0xFF, 0xFF, 0xFA}, + .fh_type = FC_TYPE_CT, + .fh_f_ctl = {FNIC_ELS_REQ_FCTL, 0, 0}, + .fh_rx_id = cpu_to_be16(FNIC_UNASSIGNED_RXID) + }, + .fc_std_ct_hdr = { + .ct_rev = FC_CT_REV, .ct_fs_type = FC_FST_MGMT, + .ct_fs_subtype = FC_FDMI_SUBTYPE, + .ct_cmd = cpu_to_be16(FC_FDMI_RPA) + }, + }; + + hton24(fcid, iport->fcid); + FNIC_STD_SET_S_ID(prpa->fchdr, fcid); + + oxid = fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_FDMI_RPA, + &iport->active_oxid_fdmi_rpa); + + if (oxid == FNIC_UNASSIGNED_OXID) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Failed to allocate OXID to send FDMI RPA", + iport->fcid); + mempool_free(frame, fnic->frame_pool); + return; + } + FNIC_STD_SET_OX_ID(prpa->fchdr, oxid); + + put_unaligned_be64(iport->wwpn, &prpa->rpa.port.portname); + put_unaligned_be32(FNIC_FDMI_NUM_PORT_ATTRS, + &prpa->rpa.hba_attrs.numattrs); + + /* MDS does not support GIGE speed. + * Bit shift standard definitions from scsi_transport_fc.h to + * match FC spec. + */ + switch (port_speed) { + case DCEM_PORTSPEED_10G: + case DCEM_PORTSPEED_20G: + /* There is no bit for 20G */ + port_speed_bm = FC_PORTSPEED_10GBIT << PORT_SPEED_BIT_14; + break; + case DCEM_PORTSPEED_25G: + port_speed_bm = FC_PORTSPEED_25GBIT << PORT_SPEED_BIT_8; + break; + case DCEM_PORTSPEED_40G: + case DCEM_PORTSPEED_4x10G: + port_speed_bm = FC_PORTSPEED_40GBIT << PORT_SPEED_BIT_9; + break; + case DCEM_PORTSPEED_100G: + port_speed_bm = FC_PORTSPEED_100GBIT << PORT_SPEED_BIT_8; + break; + default: + port_speed_bm = FC_PORTSPEED_1GBIT << PORT_SPEED_BIT_15; + break; + } + attr_off_bytes = 0; + + fdmi_attr = prpa->rpa.hba_attrs.attr; + + put_unaligned_be64(iport->wwnn, data); + + memset(data, 0, FNIC_FDMI_FC4_LEN); + if (IS_FNIC_FCP_INITIATOR(fnic)) + data[2] = 1; + else if (IS_FNIC_NVME_INITIATOR(fnic)) + data[6] = 1; + fnic_fdmi_attr_set(fdmi_attr, FNIC_FDMI_TYPE_FC4_TYPES, + FNIC_FDMI_FC4_LEN, data, &attr_off_bytes); + + put_unaligned_be32(port_speed_bm, data); + fnic_fdmi_attr_set(fdmi_attr, FNIC_FDMI_TYPE_SUPPORTED_SPEEDS, + FNIC_FDMI_SUPP_SPEED_LEN, data, &attr_off_bytes); + + put_unaligned_be32(port_speed_bm, data); + fnic_fdmi_attr_set(fdmi_attr, FNIC_FDMI_TYPE_CURRENT_SPEED, + FNIC_FDMI_CUR_SPEED_LEN, data, &attr_off_bytes); + + put_unaligned_be32(FNIC_FDMI_MFS, data); + fnic_fdmi_attr_set(fdmi_attr, FNIC_FDMI_TYPE_MAX_FRAME_SIZE, + FNIC_FDMI_MFS_LEN, data, &attr_off_bytes); + + if (IS_FNIC_FCP_INITIATOR(fnic)) + snprintf(tmp_data, FNIC_FDMI_OS_NAME_LEN - 1, "host%d", + fnic->host->host_no); + else if (IS_FNIC_NVME_INITIATOR(fnic)) + snprintf(tmp_data, FNIC_FDMI_OS_NAME_LEN - 1, "nvfnic%d", + fnic->fnic_num); + strscpy_pad(data, tmp_data, FNIC_FDMI_OS_NAME_LEN); + fnic_fdmi_attr_set(fdmi_attr, FNIC_FDMI_TYPE_OS_NAME, + FNIC_FDMI_OS_NAME_LEN, data, &attr_off_bytes); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "OS name set <%s>, off=%d", data, attr_off_bytes); + + if (IS_FNIC_FCP_INITIATOR(fnic)) + sprintf(fc_host_system_hostname(fnic->host), "%s", + utsname()->nodename); + + strscpy_pad(data, utsname()->nodename, FNIC_FDMI_HN_LEN); + fnic_fdmi_attr_set(fdmi_attr, FNIC_FDMI_TYPE_HOST_NAME, + FNIC_FDMI_HN_LEN, data, &attr_off_bytes); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Host name set <%s>, off=%d", data, attr_off_bytes); + + len = sizeof(struct fc_std_fdmi_rpa) + attr_off_bytes; + frame_size += len; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send FDMI RPA with oxid: 0x%x fs: %d", iport->fcid, + oxid, frame_size); + + fnic_send_fcoe_frame(iport, frame, frame_size); + iport->fabric.fdmi_pending |= FDLS_FDMI_RPA_PENDING; +} + +void fdls_fabric_timer_callback(struct timer_list *t) +{ + struct fnic_fdls_fabric_s *fabric = timer_container_of(fabric, t, + retry_timer); + struct fnic_iport_s *iport = + container_of(fabric, struct fnic_iport_s, fabric); + struct fnic *fnic = iport->fnic; + unsigned long flags; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "tp: %d fab state: %d fab retry counter: %d max_flogi_retries: %d", + iport->fabric.timer_pending, iport->fabric.state, + iport->fabric.retry_counter, iport->max_flogi_retries); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + + if (!iport->fabric.timer_pending) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return; + } + + if (iport->fabric.del_timer_inprogress) { + iport->fabric.del_timer_inprogress = 0; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + FNIC_FCS_DBG(KERN_INFO, fnic, + "fabric_del_timer inprogress(%d). Skip timer cb", + iport->fabric.del_timer_inprogress); + return; + } + + iport->fabric.timer_pending = 0; + + /* The fabric state indicates which frames have time out, and we retry */ + switch (iport->fabric.state) { + case FDLS_STATE_FABRIC_FLOGI: + /* Flogi received a LS_RJT with busy we retry from here */ + if ((iport->fabric.flags & FNIC_FDLS_RETRY_FRAME) + && (iport->fabric.retry_counter < iport->max_flogi_retries)) { + iport->fabric.flags &= ~FNIC_FDLS_RETRY_FRAME; + fdls_send_fabric_flogi(iport); + } else if (!(iport->fabric.flags & FNIC_FDLS_FABRIC_ABORT_ISSUED)) { + /* Flogi has time out 2*ed_tov send abts */ + fdls_send_fabric_abts(iport); + } else { + /* ABTS has timed out + * Mark the OXID to be freed after 2 * r_a_tov and retry the req + */ + fdls_schedule_oxid_free(iport, &iport->active_oxid_fabric_req); + if (iport->fabric.retry_counter < iport->max_flogi_retries) { + iport->fabric.flags &= ~FNIC_FDLS_FABRIC_ABORT_ISSUED; + fdls_send_fabric_flogi(iport); + } else + FNIC_FCS_DBG(KERN_INFO, fnic, + "Exceeded max FLOGI retries"); + } + break; + case FDLS_STATE_FABRIC_PLOGI: + /* Plogi received a LS_RJT with busy we retry from here */ + if ((iport->fabric.flags & FNIC_FDLS_RETRY_FRAME) + && (iport->fabric.retry_counter < iport->max_plogi_retries)) { + iport->fabric.flags &= ~FNIC_FDLS_RETRY_FRAME; + fdls_send_fabric_plogi(iport); + } else if (!(iport->fabric.flags & FNIC_FDLS_FABRIC_ABORT_ISSUED)) { + /* Plogi has timed out 2*ed_tov send abts */ + fdls_send_fabric_abts(iport); + } else { + /* ABTS has timed out + * Mark the OXID to be freed after 2 * r_a_tov and retry the req + */ + fdls_schedule_oxid_free(iport, &iport->active_oxid_fabric_req); + if (iport->fabric.retry_counter < iport->max_plogi_retries) { + iport->fabric.flags &= ~FNIC_FDLS_FABRIC_ABORT_ISSUED; + fdls_send_fabric_plogi(iport); + } else + FNIC_FCS_DBG(KERN_INFO, fnic, + "Exceeded max PLOGI retries"); + } + break; + case FDLS_STATE_RPN_ID: + /* Rpn_id received a LS_RJT with busy we retry from here */ + if ((iport->fabric.flags & FNIC_FDLS_RETRY_FRAME) + && (iport->fabric.retry_counter < FDLS_RETRY_COUNT)) { + iport->fabric.flags &= ~FNIC_FDLS_RETRY_FRAME; + fdls_send_rpn_id(iport); + } else if (!(iport->fabric.flags & FNIC_FDLS_FABRIC_ABORT_ISSUED)) + /* RPN has timed out. Send abts */ + fdls_send_fabric_abts(iport); + else { + /* ABTS has timed out */ + fdls_schedule_oxid_free(iport, &iport->active_oxid_fabric_req); + fnic_fdls_start_plogi(iport); /* go back to fabric Plogi */ + } + break; + case FDLS_STATE_SCR: + /* scr received a LS_RJT with busy we retry from here */ + if ((iport->fabric.flags & FNIC_FDLS_RETRY_FRAME) + && (iport->fabric.retry_counter < FDLS_RETRY_COUNT)) { + iport->fabric.flags &= ~FNIC_FDLS_RETRY_FRAME; + fdls_send_scr(iport); + } else if (!(iport->fabric.flags & FNIC_FDLS_FABRIC_ABORT_ISSUED)) + /* scr has timed out. Send abts */ + fdls_send_fabric_abts(iport); + else { + /* ABTS has timed out */ + fdls_schedule_oxid_free(iport, &iport->active_oxid_fabric_req); + FNIC_FCS_DBG(KERN_INFO, fnic, + "ABTS timed out. Starting PLOGI: %p", iport); + fnic_fdls_start_plogi(iport); + } + break; + case FDLS_STATE_REGISTER_FC4_TYPES: + /* scr received a LS_RJT with busy we retry from here */ + if ((iport->fabric.flags & FNIC_FDLS_RETRY_FRAME) + && (iport->fabric.retry_counter < FDLS_RETRY_COUNT)) { + iport->fabric.flags &= ~FNIC_FDLS_RETRY_FRAME; + fdls_send_register_fc4_types(iport); + } else if (!(iport->fabric.flags & FNIC_FDLS_FABRIC_ABORT_ISSUED)) { + /* RFT_ID timed out send abts */ + fdls_send_fabric_abts(iport); + } else { + /* ABTS has timed out */ + fdls_schedule_oxid_free(iport, &iport->active_oxid_fabric_req); + FNIC_FCS_DBG(KERN_INFO, fnic, + "ABTS timed out. Starting PLOGI: %p", iport); + fnic_fdls_start_plogi(iport); /* go back to fabric Plogi */ + } + break; + case FDLS_STATE_REGISTER_FC4_FEATURES: + /* scr received a LS_RJT with busy we retry from here */ + if ((iport->fabric.flags & FNIC_FDLS_RETRY_FRAME) + && (iport->fabric.retry_counter < FDLS_RETRY_COUNT)) { + iport->fabric.flags &= ~FNIC_FDLS_RETRY_FRAME; + fdls_send_register_fc4_features(iport); + } else if (!(iport->fabric.flags & FNIC_FDLS_FABRIC_ABORT_ISSUED)) + /* SCR has timed out. Send abts */ + fdls_send_fabric_abts(iport); + else { + /* ABTS has timed out */ + fdls_schedule_oxid_free(iport, &iport->active_oxid_fabric_req); + FNIC_FCS_DBG(KERN_INFO, fnic, + "ABTS timed out. Starting PLOGI %p", iport); + fnic_fdls_start_plogi(iport); /* go back to fabric Plogi */ + } + break; + case FDLS_STATE_RSCN_GPN_FT: + case FDLS_STATE_SEND_GPNFT: + case FDLS_STATE_GPN_FT: + /* GPN_FT received a LS_RJT with busy we retry from here */ + if ((iport->fabric.flags & FNIC_FDLS_RETRY_FRAME) + && (iport->fabric.retry_counter < FDLS_RETRY_COUNT)) { + iport->fabric.flags &= ~FNIC_FDLS_RETRY_FRAME; + fdls_send_gpn_ft(iport, iport->fabric.state); + } else if (!(iport->fabric.flags & FNIC_FDLS_FABRIC_ABORT_ISSUED)) { + /* gpn_ft has timed out. Send abts */ + fdls_send_fabric_abts(iport); + } else { + /* ABTS has timed out */ + fdls_schedule_oxid_free(iport, &iport->active_oxid_fabric_req); + if (iport->fabric.retry_counter < FDLS_RETRY_COUNT) { + fdls_send_gpn_ft(iport, iport->fabric.state); + } else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "ABTS timeout for fabric GPN_FT. Check name server: %p", + iport); + } + } + break; + default: + break; + } + spin_unlock_irqrestore(&fnic->fnic_lock, flags); +} + +void fdls_fdmi_retry_plogi(struct fnic_iport_s *iport) +{ + struct fnic *fnic = iport->fnic; + + iport->fabric.fdmi_pending = 0; + /* If max retries not exhausted, start over from fdmi plogi */ + if (iport->fabric.fdmi_retry < FDLS_FDMI_MAX_RETRY) { + iport->fabric.fdmi_retry++; + FNIC_FCS_DBG(KERN_INFO, fnic, + "Retry FDMI PLOGI. FDMI retry: %d", + iport->fabric.fdmi_retry); + fdls_send_fdmi_plogi(iport); + } +} + +void fdls_fdmi_timer_callback(struct timer_list *t) +{ + struct fnic_fdls_fabric_s *fabric = timer_container_of(fabric, t, + fdmi_timer); + struct fnic_iport_s *iport = + container_of(fabric, struct fnic_iport_s, fabric); + struct fnic *fnic = iport->fnic; + unsigned long flags; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport->fabric.fdmi_pending: 0x%x\n", iport->fabric.fdmi_pending); + + if (!iport->fabric.fdmi_pending) { + /* timer expired after fdmi responses received. */ + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return; + } + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport->fabric.fdmi_pending: 0x%x\n", iport->fabric.fdmi_pending); + + /* if not abort pending, send an abort */ + if (!(iport->fabric.fdmi_pending & FDLS_FDMI_ABORT_PENDING)) { + fdls_send_fdmi_abts(iport); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return; + } + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport->fabric.fdmi_pending: 0x%x\n", iport->fabric.fdmi_pending); + + /* ABTS pending for an active fdmi request that is pending. + * That means FDMI ABTS timed out + * Schedule to free the OXID after 2*r_a_tov and proceed + */ + if (iport->fabric.fdmi_pending & FDLS_FDMI_PLOGI_PENDING) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "FDMI PLOGI ABTS timed out. Schedule oxid free: 0x%x\n", + iport->active_oxid_fdmi_plogi); + fdls_schedule_oxid_free(iport, &iport->active_oxid_fdmi_plogi); + } else { + if (iport->fabric.fdmi_pending & FDLS_FDMI_REG_HBA_PENDING) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "FDMI RHBA ABTS timed out. Schedule oxid free: 0x%x\n", + iport->active_oxid_fdmi_rhba); + fdls_schedule_oxid_free(iport, &iport->active_oxid_fdmi_rhba); + } + if (iport->fabric.fdmi_pending & FDLS_FDMI_RPA_PENDING) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "FDMI RPA ABTS timed out. Schedule oxid free: 0x%x\n", + iport->active_oxid_fdmi_rpa); + fdls_schedule_oxid_free(iport, &iport->active_oxid_fdmi_rpa); + } + } + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport->fabric.fdmi_pending: 0x%x\n", iport->fabric.fdmi_pending); + + iport->fabric.fdmi_pending = 0; + /* If max retries not exhaused, start over from fdmi plogi */ + if (iport->fabric.fdmi_retry < FDLS_FDMI_MAX_RETRY) { + iport->fabric.fdmi_retry++; + FNIC_FCS_DBG(KERN_INFO, fnic, + "retry fdmi timer %d", iport->fabric.fdmi_retry); + fdls_send_fdmi_plogi(iport); + } + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport->fabric.fdmi_pending: 0x%x\n", iport->fabric.fdmi_pending); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); +} + +static void fdls_send_delete_tport_msg(struct fnic_tport_s *tport) +{ + struct fnic_iport_s *iport = (struct fnic_iport_s *) tport->iport; + struct fnic *fnic = iport->fnic; + struct fnic_tport_event_s *tport_del_evt; + + tport_del_evt = kzalloc_obj(struct fnic_tport_event_s, GFP_ATOMIC); + if (!tport_del_evt) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Failed to allocate memory for tport event fcid: 0x%x", + tport->fcid); + return; + } + tport_del_evt->event = TGT_EV_TPORT_DELETE; + tport_del_evt->arg1 = (void *) tport; + list_add_tail(&tport_del_evt->links, &fnic->tport_event_list); + queue_work(fnic_event_queue, &fnic->tport_work); +} + +static void fdls_tport_timer_callback(struct timer_list *t) +{ + struct fnic_tport_s *tport = timer_container_of(tport, t, retry_timer); + struct fnic_iport_s *iport = (struct fnic_iport_s *) tport->iport; + struct fnic *fnic = iport->fnic; + uint16_t oxid; + unsigned long flags; + struct fc_frame_header fchdr = {0}; + uint8_t fcid[3]; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + if (!tport->timer_pending) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return; + } + + if (iport->state != FNIC_IPORT_STATE_READY) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return; + } + + if (tport->del_timer_inprogress) { + tport->del_timer_inprogress = 0; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + FNIC_FCS_DBG(KERN_INFO, fnic, + "tport_del_timer inprogress. Skip timer cb tport fcid: 0x%x\n", + tport->fcid); + return; + } + + FNIC_FCS_DBG(KERN_INFO, fnic, + "tport fcid: 0x%x timer pending: %d state: %d retry counter: %d", + tport->fcid, tport->timer_pending, tport->state, + tport->retry_counter); + + tport->timer_pending = 0; + oxid = tport->active_oxid; + + /* We retry plogi/prli/adisc frames depending on the tport state */ + switch (tport->state) { + case FDLS_TGT_STATE_PLOGI: + /* PLOGI frame received a LS_RJT with busy, we retry from here */ + if ((tport->flags & FNIC_FDLS_RETRY_FRAME) + && (tport->retry_counter < iport->max_plogi_retries)) { + tport->flags &= ~FNIC_FDLS_RETRY_FRAME; + fdls_send_tgt_plogi(iport, tport); + } else if (!(tport->flags & FNIC_FDLS_TGT_ABORT_ISSUED)) { + /* Plogi frame has timed out, send abts */ + fdls_send_tport_abts(iport, tport); + } else if (tport->retry_counter < iport->max_plogi_retries) { + /* + * ABTS has timed out + */ + fdls_schedule_oxid_free(iport, &tport->active_oxid); + fdls_send_tgt_plogi(iport, tport); + } else { + /* exceeded plogi retry count */ + fdls_schedule_oxid_free(iport, &tport->active_oxid); + fdls_send_delete_tport_msg(tport); + } + break; + case FDLS_TGT_STATE_PRLI: + /* PRLI received a LS_RJT with busy , hence we retry from here */ + if ((tport->flags & FNIC_FDLS_RETRY_FRAME) + && (tport->retry_counter < FDLS_RETRY_COUNT)) { + tport->flags &= ~FNIC_FDLS_RETRY_FRAME; + fdls_send_tgt_prli(iport, tport); + } else if (!(tport->flags & FNIC_FDLS_TGT_ABORT_ISSUED)) { + /* PRLI has time out, send abts */ + fdls_send_tport_abts(iport, tport); + } else { + /* ABTS has timed out for prli, we go back to PLOGI */ + fdls_schedule_oxid_free(iport, &tport->active_oxid); + fdls_send_tgt_plogi(iport, tport); + fdls_set_tport_state(tport, FDLS_TGT_STATE_PLOGI); + } + break; + case FDLS_TGT_STATE_ADISC: + /* ADISC timed out send an ABTS */ + if (!(tport->flags & FNIC_FDLS_TGT_ABORT_ISSUED)) { + fdls_send_tport_abts(iport, tport); + } else if ((tport->flags & FNIC_FDLS_TGT_ABORT_ISSUED) + && (tport->retry_counter < FDLS_RETRY_COUNT)) { + /* + * ABTS has timed out + */ + fdls_schedule_oxid_free(iport, &tport->active_oxid); + fdls_send_tgt_adisc(iport, tport); + } else { + /* exceeded retry count */ + fdls_schedule_oxid_free(iport, &tport->active_oxid); + FNIC_FCS_DBG(KERN_INFO, fnic, + "ADISC not responding. Deleting target port: 0x%x", + tport->fcid); + fdls_send_delete_tport_msg(tport); + } + break; + default: + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x timeout tport 0x%x oxid 0x%x state %d\n", + iport->fcid, tport->fcid, oxid, tport->state); + if (IS_FNIC_NVME_INITIATOR(fnic)) { + hton24(fcid, tport->fcid); + FNIC_STD_SET_S_ID(fchdr, fcid); + FNIC_STD_SET_OX_ID(fchdr, oxid); + nvfnic_process_ls_abts_rsp(iport, &fchdr); + } + break; + } + spin_unlock_irqrestore(&fnic->fnic_lock, flags); +} + +static void fnic_fdls_start_flogi(struct fnic_iport_s *iport) +{ + iport->fabric.retry_counter = 0; + fdls_send_fabric_flogi(iport); + fdls_set_state((&iport->fabric), FDLS_STATE_FABRIC_FLOGI); + iport->fabric.flags = 0; +} + +static void fnic_fdls_start_plogi(struct fnic_iport_s *iport) +{ + iport->fabric.retry_counter = 0; + fdls_send_fabric_plogi(iport); + fdls_set_state((&iport->fabric), FDLS_STATE_FABRIC_PLOGI); + iport->fabric.flags &= ~FNIC_FDLS_FABRIC_ABORT_ISSUED; + + if ((fnic_fdmi_support == 1) && (!(iport->flags & FNIC_FDMI_ACTIVE))) { + /* we can do FDMI at the same time */ + iport->fabric.fdmi_retry = 0; + timer_setup(&iport->fabric.fdmi_timer, fdls_fdmi_timer_callback, + 0); + fdls_send_fdmi_plogi(iport); + iport->flags |= FNIC_FDMI_ACTIVE; + } +} +static void +fdls_process_tgt_adisc_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + uint32_t tgt_fcid; + struct fnic_tport_s *tport; + uint8_t *fcid; + uint64_t frame_wwnn; + uint64_t frame_wwpn; + uint16_t oxid; + struct fc_std_els_adisc *adisc_rsp = (struct fc_std_els_adisc *)fchdr; + struct fc_std_els_rjt_rsp *els_rjt = (struct fc_std_els_rjt_rsp *)fchdr; + struct fnic *fnic = iport->fnic; + + fcid = FNIC_STD_GET_S_ID(fchdr); + tgt_fcid = ntoh24(fcid); + tport = fnic_find_tport_by_fcid(iport, tgt_fcid); + + if (!tport) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Tgt ADISC response tport not found: 0x%x", tgt_fcid); + return; + } + if ((iport->state != FNIC_IPORT_STATE_READY) + || (tport->state != FDLS_TGT_STATE_ADISC) + || (tport->flags & FNIC_FDLS_TGT_ABORT_ISSUED)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Dropping this ADISC response"); + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport state: %d tport state: %d Is abort issued on PRLI? %d", + iport->state, tport->state, + (tport->flags & FNIC_FDLS_TGT_ABORT_ISSUED)); + return; + } + if (FNIC_STD_GET_OX_ID(fchdr) != tport->active_oxid) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Dropping frame from target: 0x%x", + tgt_fcid); + FNIC_FCS_DBG(KERN_INFO, fnic, + "Reason: Stale ADISC/Aborted ADISC/OOO frame delivery"); + return; + } + + oxid = FNIC_STD_GET_OX_ID(fchdr); + fdls_free_oxid(iport, oxid, &tport->active_oxid); + + switch (adisc_rsp->els.adisc_cmd) { + case ELS_LS_ACC: + atomic64_inc(&iport->iport_stats.tport_adisc_ls_accepts); + if (tport->timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "tport 0x%p Canceling fabric disc timer\n", + tport); + fnic_del_tport_timer_sync(fnic, tport); + } + tport->timer_pending = 0; + tport->retry_counter = 0; + frame_wwnn = get_unaligned_be64(&adisc_rsp->els.adisc_wwnn); + frame_wwpn = get_unaligned_be64(&adisc_rsp->els.adisc_wwpn); + if ((frame_wwnn == tport->wwnn) && (frame_wwpn == tport->wwpn)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "ADISC accepted from target: 0x%x. Target logged in", + tgt_fcid); + fdls_set_tport_state(tport, FDLS_TGT_STATE_READY); + } else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Error mismatch frame: ADISC"); + } + break; + + case ELS_LS_RJT: + atomic64_inc(&iport->iport_stats.tport_adisc_ls_rejects); + if (((els_rjt->rej.er_reason == ELS_RJT_BUSY) + || (els_rjt->rej.er_reason == ELS_RJT_UNAB)) + && (tport->retry_counter < FDLS_RETRY_COUNT)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "ADISC ret ELS_LS_RJT BUSY. Retry from timer routine: 0x%x", + tgt_fcid); + + /* Retry ADISC again from the timer routine. */ + tport->flags |= FNIC_FDLS_RETRY_FRAME; + } else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "ADISC returned ELS_LS_RJT from target: 0x%x", + tgt_fcid); + fdls_delete_tport(iport, tport); + } + break; + } +} +static void +fdls_process_tgt_plogi_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + uint32_t tgt_fcid; + struct fnic_tport_s *tport; + uint8_t *fcid; + uint16_t oxid; + struct fc_std_flogi *plogi_rsp = (struct fc_std_flogi *)fchdr; + struct fc_std_els_rjt_rsp *els_rjt = (struct fc_std_els_rjt_rsp *)fchdr; + uint16_t max_payload_size; + struct fnic *fnic = iport->fnic; + + fcid = FNIC_STD_GET_S_ID(fchdr); + tgt_fcid = ntoh24(fcid); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "FDLS processing target PLOGI response: tgt_fcid: 0x%x", + tgt_fcid); + + tport = fnic_find_tport_by_fcid(iport, tgt_fcid); + if (!tport) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "tport not found: 0x%x", tgt_fcid); + return; + } + if ((iport->state != FNIC_IPORT_STATE_READY) + || (tport->flags & FNIC_FDLS_TGT_ABORT_ISSUED)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Dropping frame! iport state: %d tport state: %d", + iport->state, tport->state); + return; + } + + if (tport->state != FDLS_TGT_STATE_PLOGI) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "PLOGI rsp recvd in wrong state. Drop the frame and restart nexus"); + fdls_target_restart_nexus(tport); + return; + } + + if (FNIC_STD_GET_OX_ID(fchdr) != tport->active_oxid) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "PLOGI response from target: 0x%x. Dropping frame", + tgt_fcid); + return; + } + + oxid = FNIC_STD_GET_OX_ID(fchdr); + fdls_free_oxid(iport, oxid, &tport->active_oxid); + + switch (plogi_rsp->els.fl_cmd) { + case ELS_LS_ACC: + atomic64_inc(&iport->iport_stats.tport_plogi_ls_accepts); + FNIC_FCS_DBG(KERN_INFO, fnic, + "PLOGI accepted by target: 0x%x", tgt_fcid); + break; + + case ELS_LS_RJT: + atomic64_inc(&iport->iport_stats.tport_plogi_ls_rejects); + if (((els_rjt->rej.er_reason == ELS_RJT_BUSY) + || (els_rjt->rej.er_reason == ELS_RJT_UNAB)) + && (tport->retry_counter < iport->max_plogi_retries)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "PLOGI ret ELS_LS_RJT BUSY. Retry from timer routine: 0x%x", + tgt_fcid); + /* Retry plogi again from the timer routine. */ + tport->flags |= FNIC_FDLS_RETRY_FRAME; + return; + } + FNIC_FCS_DBG(KERN_INFO, fnic, + "PLOGI returned ELS_LS_RJT from target: 0x%x", + tgt_fcid); + fdls_delete_tport(iport, tport); + return; + + default: + atomic64_inc(&iport->iport_stats.tport_plogi_misc_rejects); + FNIC_FCS_DBG(KERN_INFO, fnic, + "PLOGI not accepted from target fcid: 0x%x", + tgt_fcid); + return; + } + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Found the PLOGI target: 0x%x and state: %d", + (unsigned int) tgt_fcid, tport->state); + + if (tport->timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "tport fcid 0x%x: Canceling disc timer\n", + tport->fcid); + fnic_del_tport_timer_sync(fnic, tport); + } + + tport->timer_pending = 0; + tport->wwpn = get_unaligned_be64(&FNIC_LOGI_PORT_NAME(plogi_rsp->els)); + tport->wwnn = get_unaligned_be64(&FNIC_LOGI_NODE_NAME(plogi_rsp->els)); + + /* Learn the Service Params */ + + /* Max frame size - choose the lowest */ + max_payload_size = fnic_fc_plogi_rsp_rdf(iport, plogi_rsp); + tport->max_payload_size = + min(max_payload_size, iport->max_payload_size); + + if (tport->max_payload_size < FNIC_MIN_DATA_FIELD_SIZE) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "MFS: tport max frame size below spec bounds: %d", + tport->max_payload_size); + tport->max_payload_size = FNIC_MIN_DATA_FIELD_SIZE; + } + + FNIC_FCS_DBG(KERN_INFO, fnic, + "MAX frame size: %u iport max_payload_size: %d tport mfs: %d", + max_payload_size, iport->max_payload_size, + tport->max_payload_size); + + tport->max_concur_seqs = FNIC_FC_PLOGI_RSP_CONCUR_SEQ(plogi_rsp); + + tport->retry_counter = 0; + fdls_set_tport_state(tport, FDLS_TGT_STATE_PRLI); + fdls_send_tgt_prli(iport, tport); +} +static void +fdls_process_tgt_prli_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + uint32_t tgt_fcid; + struct fnic_tport_s *tport; + uint8_t *fcid; + uint16_t oxid; + struct fc_std_els_prli *prli_rsp = (struct fc_std_els_prli *)fchdr; + struct fc_std_els_rjt_rsp *els_rjt = (struct fc_std_els_rjt_rsp *)fchdr; + struct fnic_tport_event_s *tport_add_evt; + struct fnic *fnic = iport->fnic; + bool mismatched_tgt = false; + + fcid = FNIC_STD_GET_S_ID(fchdr); + tgt_fcid = ntoh24(fcid); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "FDLS process tgt PRLI response: 0x%x", tgt_fcid); + + tport = fnic_find_tport_by_fcid(iport, tgt_fcid); + if (!tport) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "tport not found: 0x%x", tgt_fcid); + /* Handle or just drop? */ + return; + } + + if ((iport->state != FNIC_IPORT_STATE_READY) + || (tport->flags & FNIC_FDLS_TGT_ABORT_ISSUED)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Dropping frame! iport st: %d tport st: %d tport fcid: 0x%x", + iport->state, tport->state, tport->fcid); + return; + } + + if (tport->state != FDLS_TGT_STATE_PRLI) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "PRLI rsp recvd in wrong state. Drop frame. Restarting nexus"); + fdls_target_restart_nexus(tport); + return; + } + + if (FNIC_STD_GET_OX_ID(fchdr) != tport->active_oxid) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Dropping PRLI response from target: 0x%x ", + tgt_fcid); + FNIC_FCS_DBG(KERN_INFO, fnic, + "Reason: Stale PRLI response/Aborted PDISC/OOO frame delivery"); + return; + } + + oxid = FNIC_STD_GET_OX_ID(fchdr); + fdls_free_oxid(iport, oxid, &tport->active_oxid); + + switch (prli_rsp->els_prli.prli_cmd) { + case ELS_LS_ACC: + atomic64_inc(&iport->iport_stats.tport_prli_ls_accepts); + FNIC_FCS_DBG(KERN_INFO, fnic, + "PRLI accepted from target: 0x%x", tgt_fcid); + + if (IS_FNIC_FCP_INITIATOR(fnic) && + prli_rsp->sp.spp_type != FC_FC4_TYPE_SCSI) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "mismatched target zoned with FC SCSI initiator: 0x%x", + tgt_fcid); + mismatched_tgt = true; + } else if (IS_FNIC_NVME_INITIATOR(fnic) && + prli_rsp->sp.spp_type != FC_TYPE_NVME) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "mismatched target zoned with NVME initiator: 0x%x", + tgt_fcid); + mismatched_tgt = true; + } + if (mismatched_tgt) { + fdls_tgt_logout(iport, tport); + fdls_delete_tport(iport, tport); + return; + } + break; + case ELS_LS_RJT: + atomic64_inc(&iport->iport_stats.tport_prli_ls_rejects); + if (((els_rjt->rej.er_reason == ELS_RJT_BUSY) + || (els_rjt->rej.er_reason == ELS_RJT_UNAB)) + && (tport->retry_counter < FDLS_RETRY_COUNT)) { + + FNIC_FCS_DBG(KERN_INFO, fnic, + "PRLI ret ELS_LS_RJT BUSY. Retry from timer routine: 0x%x", + tgt_fcid); + + /*Retry Plogi again from the timer routine. */ + tport->flags |= FNIC_FDLS_RETRY_FRAME; + return; + } + FNIC_FCS_DBG(KERN_INFO, fnic, + "PRLI returned ELS_LS_RJT from target: 0x%x", + tgt_fcid); + + fdls_tgt_logout(iport, tport); + fdls_delete_tport(iport, tport); + return; + default: + atomic64_inc(&iport->iport_stats.tport_prli_misc_rejects); + FNIC_FCS_DBG(KERN_INFO, fnic, + "PRLI not accepted from target: 0x%x", tgt_fcid); + return; + } + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Found the PRLI target: 0x%x and state: %d", + (unsigned int) tgt_fcid, tport->state); + + if (tport->timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "tport fcid 0x%x: Canceling disc timer\n", + tport->fcid); + fnic_del_tport_timer_sync(fnic, tport); + } + tport->timer_pending = 0; + + /* Learn Service Params */ + tport->fcp_csp = be32_to_cpu(prli_rsp->sp.spp_params); + tport->retry_counter = 0; + + if (IS_FNIC_FCP_INITIATOR(fnic)) { + if (tport->fcp_csp & FCP_SPPF_RETRY) + tport->tgt_flags |= FNIC_FC_RP_FLAGS_RETRY; + } else if (IS_FNIC_NVME_INITIATOR(fnic)) { + if (tport->fcp_csp & FNIC_NVME_SP_SLER) + tport->tgt_flags |= FNIC_FC_RP_FLAGS_RETRY; + } + + /* Check if the device plays Target Mode Function */ + if ((IS_FNIC_FCP_INITIATOR(fnic) && + !(tport->fcp_csp & FCP_PRLI_FUNC_TARGET)) || + (IS_FNIC_NVME_INITIATOR(fnic) && + !(tport->fcp_csp & FCP_PRLI_FUNC_TARGET))) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Remote port(0x%x): no target support. Deleting it\n", + tgt_fcid); + fdls_tgt_logout(iport, tport); + fdls_delete_tport(iport, tport); + return; + } + + fdls_set_tport_state(tport, FDLS_TGT_STATE_READY); + + /* Inform the driver about new target added */ + if (IS_FNIC_FCP_INITIATOR(fnic) || + IS_FNIC_NVME_INITIATOR(fnic)) { + tport_add_evt = kzalloc_obj(struct fnic_tport_event_s, GFP_ATOMIC); + if (!tport_add_evt) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport fcid: 0x%x tport event memory allocation failure: 0x%0x\n", + iport->fcid, tport->fcid); + return; + } + tport_add_evt->event = TGT_EV_RPORT_ADD; + tport_add_evt->arg1 = (void *)tport; + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport fcid: 0x%x add tport event fcid: 0x%x\n", + tport->fcid, iport->fcid); + list_add_tail(&tport_add_evt->links, &fnic->tport_event_list); + queue_work(fnic_event_queue, &fnic->tport_work); + } +} + + +static void +fdls_process_rff_id_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + struct fnic *fnic = iport->fnic; + struct fnic_fdls_fabric_s *fdls = &iport->fabric; + struct fc_std_rff_id *rff_rsp = (struct fc_std_rff_id *) fchdr; + uint16_t rsp; + uint8_t reason_code; + uint16_t oxid = FNIC_STD_GET_OX_ID(fchdr); + + if (fdls_get_state(fdls) != FDLS_STATE_REGISTER_FC4_FEATURES) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "RFF_ID resp recvd in state(%d). Dropping.", + fdls_get_state(fdls)); + return; + } + + if (iport->active_oxid_fabric_req != oxid) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Incorrect OXID in response. state: %d, oxid recvd: 0x%x, active oxid: 0x%x\n", + fdls_get_state(fdls), oxid, iport->active_oxid_fabric_req); + return; + } + + rsp = FNIC_STD_GET_FC_CT_CMD((&rff_rsp->fc_std_ct_hdr)); + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS process RFF ID response: 0x%04x", iport->fcid, + (uint32_t) rsp); + + fdls_free_oxid(iport, oxid, &iport->active_oxid_fabric_req); + + switch (rsp) { + case FC_FS_ACC: + if (iport->fabric.timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Canceling fabric disc timer %p\n", iport); + fnic_del_fabric_timer_sync(fnic); + } + iport->fabric.timer_pending = 0; + fdls->retry_counter = 0; + fdls_set_state((&iport->fabric), FDLS_STATE_SCR); + fdls_send_scr(iport); + break; + case FC_FS_RJT: + reason_code = rff_rsp->fc_std_ct_hdr.ct_reason; + if (((reason_code == FC_FS_RJT_BSY) + || (reason_code == FC_FS_RJT_UNABL)) + && (fdls->retry_counter < FDLS_RETRY_COUNT)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "RFF_ID ret ELS_LS_RJT BUSY. Retry from timer routine %p", + iport); + + /* Retry again from the timer routine */ + fdls->flags |= FNIC_FDLS_RETRY_FRAME; + } else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "RFF_ID returned ELS_LS_RJT. Halting discovery %p", + iport); + if (iport->fabric.timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Canceling fabric disc timer %p\n", iport); + fnic_del_fabric_timer_sync(fnic); + } + fdls->timer_pending = 0; + fdls->retry_counter = 0; + } + break; + default: + break; + } +} + +static void +fdls_process_rft_id_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + struct fnic_fdls_fabric_s *fdls = &iport->fabric; + struct fc_std_rft_id *rft_rsp = (struct fc_std_rft_id *) fchdr; + uint16_t rsp; + uint8_t reason_code; + struct fnic *fnic = iport->fnic; + uint16_t oxid = FNIC_STD_GET_OX_ID(fchdr); + + if (fdls_get_state(fdls) != FDLS_STATE_REGISTER_FC4_TYPES) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "RFT_ID resp recvd in state(%d). Dropping.", + fdls_get_state(fdls)); + return; + } + + if (iport->active_oxid_fabric_req != oxid) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Incorrect OXID in response. state: %d, oxid recvd: 0x%x, active oxid: 0x%x\n", + fdls_get_state(fdls), oxid, iport->active_oxid_fabric_req); + return; + } + + + rsp = FNIC_STD_GET_FC_CT_CMD((&rft_rsp->fc_std_ct_hdr)); + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS process RFT ID response: 0x%04x", iport->fcid, + (uint32_t) rsp); + + fdls_free_oxid(iport, oxid, &iport->active_oxid_fabric_req); + + switch (rsp) { + case FC_FS_ACC: + if (iport->fabric.timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Canceling fabric disc timer %p\n", iport); + fnic_del_fabric_timer_sync(fnic); + } + iport->fabric.timer_pending = 0; + fdls->retry_counter = 0; + fdls_send_register_fc4_features(iport); + fdls_set_state((&iport->fabric), FDLS_STATE_REGISTER_FC4_FEATURES); + break; + case FC_FS_RJT: + reason_code = rft_rsp->fc_std_ct_hdr.ct_reason; + if (((reason_code == FC_FS_RJT_BSY) + || (reason_code == FC_FS_RJT_UNABL)) + && (fdls->retry_counter < FDLS_RETRY_COUNT)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: RFT_ID ret ELS_LS_RJT BUSY. Retry from timer routine", + iport->fcid); + + /* Retry again from the timer routine */ + fdls->flags |= FNIC_FDLS_RETRY_FRAME; + } else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: RFT_ID REJ. Halting discovery reason %d expl %d", + iport->fcid, reason_code, + rft_rsp->fc_std_ct_hdr.ct_explan); + if (iport->fabric.timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Canceling fabric disc timer %p\n", iport); + fnic_del_fabric_timer_sync(fnic); + } + fdls->timer_pending = 0; + fdls->retry_counter = 0; + } + break; + default: + break; + } +} + +static void +fdls_process_rpn_id_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + struct fnic_fdls_fabric_s *fdls = &iport->fabric; + struct fc_std_rpn_id *rpn_rsp = (struct fc_std_rpn_id *) fchdr; + uint16_t rsp; + uint8_t reason_code; + struct fnic *fnic = iport->fnic; + uint16_t oxid = FNIC_STD_GET_OX_ID(fchdr); + + if (fdls_get_state(fdls) != FDLS_STATE_RPN_ID) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "RPN_ID resp recvd in state(%d). Dropping.", + fdls_get_state(fdls)); + return; + } + if (iport->active_oxid_fabric_req != oxid) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Incorrect OXID in response. state: %d, oxid recvd: 0x%x, active oxid: 0x%x\n", + fdls_get_state(fdls), oxid, iport->active_oxid_fabric_req); + return; + } + + rsp = FNIC_STD_GET_FC_CT_CMD((&rpn_rsp->fc_std_ct_hdr)); + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS process RPN ID response: 0x%04x", iport->fcid, + (uint32_t) rsp); + fdls_free_oxid(iport, oxid, &iport->active_oxid_fabric_req); + + switch (rsp) { + case FC_FS_ACC: + if (iport->fabric.timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Canceling fabric disc timer %p\n", iport); + fnic_del_fabric_timer_sync(fnic); + } + iport->fabric.timer_pending = 0; + fdls->retry_counter = 0; + fdls_send_register_fc4_types(iport); + fdls_set_state((&iport->fabric), FDLS_STATE_REGISTER_FC4_TYPES); + break; + case FC_FS_RJT: + reason_code = rpn_rsp->fc_std_ct_hdr.ct_reason; + if (((reason_code == FC_FS_RJT_BSY) + || (reason_code == FC_FS_RJT_UNABL)) + && (fdls->retry_counter < FDLS_RETRY_COUNT)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "RPN_ID returned REJ BUSY. Retry from timer routine %p", + iport); + + /* Retry again from the timer routine */ + fdls->flags |= FNIC_FDLS_RETRY_FRAME; + } else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "RPN_ID ELS_LS_RJT. Halting discovery %p", iport); + if (iport->fabric.timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Canceling fabric disc timer %p\n", iport); + fnic_del_fabric_timer_sync(fnic); + } + fdls->timer_pending = 0; + fdls->retry_counter = 0; + } + break; + default: + break; + } +} + +static void +fdls_process_scr_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + struct fnic_fdls_fabric_s *fdls = &iport->fabric; + struct fc_std_scr *scr_rsp = (struct fc_std_scr *) fchdr; + struct fc_std_els_rjt_rsp *els_rjt = (struct fc_std_els_rjt_rsp *) fchdr; + struct fnic *fnic = iport->fnic; + uint16_t oxid = FNIC_STD_GET_OX_ID(fchdr); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "FDLS process SCR response: 0x%04x", + (uint32_t) scr_rsp->scr.scr_cmd); + + if (fdls_get_state(fdls) != FDLS_STATE_SCR) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "SCR resp recvd in state(%d). Dropping.", + fdls_get_state(fdls)); + return; + } + if (iport->active_oxid_fabric_req != oxid) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Incorrect OXID in response. state: %d, oxid recvd: 0x%x, active oxid: 0x%x\n", + fdls_get_state(fdls), oxid, iport->active_oxid_fabric_req); + } + + fdls_free_oxid(iport, oxid, &iport->active_oxid_fabric_req); + + switch (scr_rsp->scr.scr_cmd) { + case ELS_LS_ACC: + atomic64_inc(&iport->iport_stats.fabric_scr_ls_accepts); + if (iport->fabric.timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Canceling fabric disc timer %p\n", iport); + fnic_del_fabric_timer_sync(fnic); + } + iport->fabric.timer_pending = 0; + iport->fabric.retry_counter = 0; + fdls_send_gpn_ft(iport, FDLS_STATE_GPN_FT); + break; + + case ELS_LS_RJT: + atomic64_inc(&iport->iport_stats.fabric_scr_ls_rejects); + if (((els_rjt->rej.er_reason == ELS_RJT_BUSY) + || (els_rjt->rej.er_reason == ELS_RJT_UNAB)) + && (fdls->retry_counter < FDLS_RETRY_COUNT)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "SCR ELS_LS_RJT BUSY. Retry from timer routine %p", + iport); + /* Retry again from the timer routine */ + fdls->flags |= FNIC_FDLS_RETRY_FRAME; + } else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "SCR returned ELS_LS_RJT. Halting discovery %p", + iport); + if (iport->fabric.timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Canceling fabric disc timer %p\n", + iport); + fnic_del_fabric_timer_sync(fnic); + } + fdls->timer_pending = 0; + fdls->retry_counter = 0; + } + break; + + default: + atomic64_inc(&iport->iport_stats.fabric_scr_misc_rejects); + break; + } +} + +static void +fdls_process_gpn_ft_tgt_list(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr, int len) +{ + struct fc_gpn_ft_rsp_iu *gpn_ft_tgt; + struct fnic_tport_s *tport, *next; + uint32_t fcid; + uint64_t wwpn; + int rem_len = len; + u32 old_link_down_cnt = iport->fnic->link_down_cnt; + struct fnic *fnic = iport->fnic; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS process GPN_FT tgt list", iport->fcid); + + gpn_ft_tgt = + (struct fc_gpn_ft_rsp_iu *)((uint8_t *) fchdr + + sizeof(struct fc_frame_header) + + sizeof(struct fc_ct_hdr)); + len -= sizeof(struct fc_frame_header) + sizeof(struct fc_ct_hdr); + + while (rem_len > 0) { + + fcid = ntoh24(gpn_ft_tgt->fcid); + wwpn = be64_to_cpu(gpn_ft_tgt->wwpn); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "tport: 0x%x: ctrl:0x%x", fcid, gpn_ft_tgt->ctrl); + + if (fcid == iport->fcid) { + if (gpn_ft_tgt->ctrl & FC_NS_FID_LAST) + break; + gpn_ft_tgt++; + rem_len -= sizeof(struct fc_gpn_ft_rsp_iu); + continue; + } + + tport = fnic_find_tport_by_wwpn(iport, wwpn); + if (!tport) { + /* + * New port registered with the switch or first time query + */ + tport = fdls_create_tport(iport, fcid, wwpn); + if (!tport) + return; + } + /* + * check if this was an existing tport with same fcid + * but whose wwpn has changed now ,then remove it and + * create a new one + */ + if (tport->fcid != fcid) { + fdls_delete_tport(iport, tport); + tport = fdls_create_tport(iport, fcid, wwpn); + if (!tport) + return; + } + + /* + * If this GPN_FT rsp is after RSCN then mark the tports which + * matches with the new GPN_FT list, if some tport is not + * found in GPN_FT we went to delete that tport later. + */ + if (fdls_get_state((&iport->fabric)) == FDLS_STATE_RSCN_GPN_FT) + tport->flags |= FNIC_FDLS_TPORT_IN_GPN_FT_LIST; + + if (gpn_ft_tgt->ctrl & FC_NS_FID_LAST) + break; + + gpn_ft_tgt++; + rem_len -= sizeof(struct fc_gpn_ft_rsp_iu); + } + if (rem_len <= 0) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "GPN_FT response: malformed/corrupt frame rxlen: %d remlen: %d", + len, rem_len); +} + + /*remove those ports which was not listed in GPN_FT */ + if (fdls_get_state((&iport->fabric)) == FDLS_STATE_RSCN_GPN_FT) { + list_for_each_entry_safe(tport, next, &iport->tport_list, links) { + + if (!(tport->flags & FNIC_FDLS_TPORT_IN_GPN_FT_LIST)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Remove port: 0x%x not found in GPN_FT list", + tport->fcid); + fdls_delete_tport(iport, tport); + } else { + tport->flags &= ~FNIC_FDLS_TPORT_IN_GPN_FT_LIST; + } + if ((old_link_down_cnt != iport->fnic->link_down_cnt) + || (iport->state != FNIC_IPORT_STATE_READY)) { + return; + } + } + } +} + +static void +fdls_process_gpn_ft_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr, int len) +{ + struct fnic_fdls_fabric_s *fdls = &iport->fabric; + struct fc_std_gpn_ft *gpn_ft_rsp = (struct fc_std_gpn_ft *) fchdr; + uint16_t rsp; + uint8_t reason_code; + int count = 0; + struct fnic_tport_s *tport, *next; + u32 old_link_down_cnt = iport->fnic->link_down_cnt; + struct fnic *fnic = iport->fnic; + uint16_t oxid = FNIC_STD_GET_OX_ID(fchdr); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "FDLS process GPN_FT response: iport state: %d len: %d", + iport->state, len); + + /* + * GPNFT response :- + * FDLS_STATE_GPN_FT : GPNFT send after SCR state + * during fabric discovery(FNIC_IPORT_STATE_FABRIC_DISC) + * FDLS_STATE_RSCN_GPN_FT : GPNFT send in response to RSCN + * FDLS_STATE_SEND_GPNFT : GPNFT send after deleting a Target, + * e.g. after receiving Target LOGO + * FDLS_STATE_TGT_DISCOVERY :Target discovery is currently in progress + * from previous GPNFT response,a new GPNFT response has come. + */ + if (!(((iport->state == FNIC_IPORT_STATE_FABRIC_DISC) + && (fdls_get_state(fdls) == FDLS_STATE_GPN_FT)) + || ((iport->state == FNIC_IPORT_STATE_READY) + && ((fdls_get_state(fdls) == FDLS_STATE_RSCN_GPN_FT) + || (fdls_get_state(fdls) == FDLS_STATE_SEND_GPNFT) + || (fdls_get_state(fdls) == FDLS_STATE_TGT_DISCOVERY))))) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "GPNFT resp recvd in fab state(%d) iport_state(%d). Dropping.", + fdls_get_state(fdls), iport->state); + return; + } + + if (iport->active_oxid_fabric_req != oxid) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Incorrect OXID in response. state: %d, oxid recvd: 0x%x, active oxid: 0x%x\n", + fdls_get_state(fdls), oxid, iport->active_oxid_fabric_req); + } + + fdls_free_oxid(iport, oxid, &iport->active_oxid_fabric_req); + + iport->state = FNIC_IPORT_STATE_READY; + rsp = FNIC_STD_GET_FC_CT_CMD((&gpn_ft_rsp->fc_std_ct_hdr)); + + switch (rsp) { + + case FC_FS_ACC: + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: GPNFT_RSP accept", iport->fcid); + if (iport->fabric.timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Canceling fabric disc timer\n", + iport->fcid); + fnic_del_fabric_timer_sync(fnic); + } + iport->fabric.timer_pending = 0; + iport->fabric.retry_counter = 0; + fdls_process_gpn_ft_tgt_list(iport, fchdr, len); + + /* + * iport state can change only if link down event happened + * We don't need to undo fdls_process_gpn_ft_tgt_list, + * that will be taken care in next link up event + */ + if (iport->state != FNIC_IPORT_STATE_READY) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Halting target discovery: fab st: %d iport st: %d ", + fdls_get_state(fdls), iport->state); + break; + } + fdls_tgt_discovery_start(iport); + break; + + case FC_FS_RJT: + reason_code = gpn_ft_rsp->fc_std_ct_hdr.ct_reason; + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: GPNFT_RSP Reject reason: %d", iport->fcid, reason_code); + + if (((reason_code == FC_FS_RJT_BSY) + || (reason_code == FC_FS_RJT_UNABL)) + && (fdls->retry_counter < FDLS_RETRY_COUNT)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: GPNFT_RSP ret REJ/BSY. Retry from timer routine", + iport->fcid); + /* Retry again from the timer routine */ + fdls->flags |= FNIC_FDLS_RETRY_FRAME; + } else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: GPNFT_RSP reject", iport->fcid); + if (iport->fabric.timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Canceling fabric disc timer\n", + iport->fcid); + fnic_del_fabric_timer_sync(fnic); + } + iport->fabric.timer_pending = 0; + iport->fabric.retry_counter = 0; + /* + * If GPN_FT ls_rjt then we should delete + * all existing tports + */ + count = 0; + list_for_each_entry_safe(tport, next, &iport->tport_list, + links) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "GPN_FT_REJECT: Remove port: 0x%x", + tport->fcid); + fdls_delete_tport(iport, tport); + if ((old_link_down_cnt != iport->fnic->link_down_cnt) + || (iport->state != FNIC_IPORT_STATE_READY)) { + return; + } + count++; + } + FNIC_FCS_DBG(KERN_INFO, fnic, + "GPN_FT_REJECT: Removed (0x%x) ports", count); + } + break; + + default: + break; + } +} + +/** + * fdls_process_fabric_logo_rsp - Handle an flogo response from the fcf + * @iport: Handle to fnic iport + * @fchdr: Incoming frame + */ +static void +fdls_process_fabric_logo_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + struct fc_std_flogi *flogo_rsp = (struct fc_std_flogi *) fchdr; + struct fnic_fdls_fabric_s *fdls = &iport->fabric; + struct fnic *fnic = iport->fnic; + uint16_t oxid = FNIC_STD_GET_OX_ID(fchdr); + + if (iport->active_oxid_fabric_req != oxid) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Incorrect OXID in response. state: %d, oxid recvd: 0x%x, active oxid: 0x%x\n", + fdls_get_state(fdls), oxid, iport->active_oxid_fabric_req); + } + fdls_free_oxid(iport, oxid, &iport->active_oxid_fabric_req); + + switch (flogo_rsp->els.fl_cmd) { + case ELS_LS_ACC: + if (iport->fabric.state != FDLS_STATE_FABRIC_LOGO) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Flogo response. Fabric not in LOGO state. Dropping! %p", + iport); + return; + } + + iport->fabric.state = FDLS_STATE_FLOGO_DONE; + iport->state = FNIC_IPORT_STATE_LINK_WAIT; + + if (iport->fabric.timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport 0x%p Canceling fabric disc timer\n", + iport); + fnic_del_fabric_timer_sync(fnic); + } + iport->fabric.timer_pending = 0; + FNIC_FCS_DBG(KERN_INFO, fnic, + "Flogo response from Fabric for did: 0x%x", + ntoh24(fchdr->fh_d_id)); + return; + + case ELS_LS_RJT: + FNIC_FCS_DBG(KERN_INFO, fnic, + "Flogo response from Fabric for did: 0x%x returned ELS_LS_RJT", + ntoh24(fchdr->fh_d_id)); + return; + + default: + FNIC_FCS_DBG(KERN_INFO, fnic, + "FLOGO response not accepted or rejected: 0x%x", + flogo_rsp->els.fl_cmd); + } +} + +static void +fdls_process_flogi_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr, void *rx_frame) +{ + struct fnic_fdls_fabric_s *fabric = &iport->fabric; + struct fc_std_flogi *flogi_rsp = (struct fc_std_flogi *) fchdr; + uint8_t *fcid; + uint16_t rdf_size; + uint8_t fcmac[6] = { 0x0E, 0XFC, 0x00, 0x00, 0x00, 0x00 }; + struct fnic *fnic = iport->fnic; + uint16_t oxid = FNIC_STD_GET_OX_ID(fchdr); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS processing FLOGI response", iport->fcid); + + if (fdls_get_state(fabric) != FDLS_STATE_FABRIC_FLOGI) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "FLOGI response received in state (%d). Dropping frame", + fdls_get_state(fabric)); + return; + } + if (iport->active_oxid_fabric_req != oxid) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Incorrect OXID in response. state: %d, oxid recvd: 0x%x, active oxid: 0x%x\n", + fdls_get_state(fabric), oxid, iport->active_oxid_fabric_req); + return; + } + + fdls_free_oxid(iport, oxid, &iport->active_oxid_fabric_req); + + switch (flogi_rsp->els.fl_cmd) { + case ELS_LS_ACC: + atomic64_inc(&iport->iport_stats.fabric_flogi_ls_accepts); + if (iport->fabric.timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport fcid: 0x%x Canceling fabric disc timer\n", + iport->fcid); + fnic_del_fabric_timer_sync(fnic); + } + + iport->fabric.timer_pending = 0; + iport->fabric.retry_counter = 0; + fcid = FNIC_STD_GET_D_ID(fchdr); + iport->fcid = ntoh24(fcid); + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FLOGI response accepted", iport->fcid); + + /* Learn the Service Params */ + rdf_size = be16_to_cpu(FNIC_LOGI_RDF_SIZE(flogi_rsp->els)); + if ((rdf_size >= FNIC_MIN_DATA_FIELD_SIZE) + && (rdf_size < FNIC_FC_MAX_PAYLOAD_LEN)) + iport->max_payload_size = min(rdf_size, + iport->max_payload_size); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "max_payload_size from fabric: %u set: %d", rdf_size, + iport->max_payload_size); + + iport->r_a_tov = be32_to_cpu(FNIC_LOGI_R_A_TOV(flogi_rsp->els)); + iport->e_d_tov = be32_to_cpu(FNIC_LOGI_E_D_TOV(flogi_rsp->els)); + + if (FNIC_LOGI_FEATURES(flogi_rsp->els) & FNIC_FC_EDTOV_NSEC) + iport->e_d_tov = iport->e_d_tov / FNIC_NSEC_TO_MSEC; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "From fabric: R_A_TOV: %d E_D_TOV: %d", + iport->r_a_tov, iport->e_d_tov); + + if (IS_FNIC_FCP_INITIATOR(fnic)) { + fc_host_fabric_name(iport->fnic->host) = + get_unaligned_be64(&FNIC_LOGI_NODE_NAME(flogi_rsp->els)); + fc_host_port_id(iport->fnic->host) = iport->fcid; + } + + fnic_fdls_learn_fcoe_macs(iport, rx_frame, fcid); + + if (fnic_fdls_register_portid(iport, iport->fcid, rx_frame) != 0) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FLOGI registration failed", iport->fcid); + break; + } + + memcpy(&fcmac[3], fcid, 3); + FNIC_FCS_DBG(KERN_INFO, fnic, + "Adding vNIC device MAC addr: %02x:%02x:%02x:%02x:%02x:%02x", + fcmac[0], fcmac[1], fcmac[2], fcmac[3], fcmac[4], + fcmac[5]); + vnic_dev_add_addr(iport->fnic->vdev, fcmac); + + if (fdls_get_state(fabric) == FDLS_STATE_FABRIC_FLOGI) { + fnic_fdls_start_plogi(iport); + FNIC_FCS_DBG(KERN_INFO, fnic, + "FLOGI response received. Starting PLOGI"); + } else { + /* From FDLS_STATE_FABRIC_FLOGI state fabric can only go to + * FDLS_STATE_LINKDOWN + * state, hence we don't have to worry about undoing: + * the fnic_fdls_register_portid and vnic_dev_add_addr + */ + FNIC_FCS_DBG(KERN_INFO, fnic, + "FLOGI response received in state (%d). Dropping frame", + fdls_get_state(fabric)); + } + break; + + case ELS_LS_RJT: + atomic64_inc(&iport->iport_stats.fabric_flogi_ls_rejects); + if (fabric->retry_counter < iport->max_flogi_retries) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "FLOGI returned ELS_LS_RJT BUSY. Retry from timer routine %p", + iport); + + /* Retry Flogi again from the timer routine. */ + fabric->flags |= FNIC_FDLS_RETRY_FRAME; + + } else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "FLOGI returned ELS_LS_RJT. Halting discovery %p", + iport); + if (iport->fabric.timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport 0x%p Canceling fabric disc timer\n", + iport); + fnic_del_fabric_timer_sync(fnic); + } + fabric->timer_pending = 0; + fabric->retry_counter = 0; + } + break; + + default: + FNIC_FCS_DBG(KERN_INFO, fnic, + "FLOGI response not accepted: 0x%x", + flogi_rsp->els.fl_cmd); + atomic64_inc(&iport->iport_stats.fabric_flogi_misc_rejects); + break; + } +} + +static void +fdls_process_fabric_plogi_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + struct fc_std_flogi *plogi_rsp = (struct fc_std_flogi *) fchdr; + struct fc_std_els_rjt_rsp *els_rjt = (struct fc_std_els_rjt_rsp *) fchdr; + struct fnic_fdls_fabric_s *fdls = &iport->fabric; + struct fnic *fnic = iport->fnic; + uint16_t oxid = FNIC_STD_GET_OX_ID(fchdr); + + if (fdls_get_state((&iport->fabric)) != FDLS_STATE_FABRIC_PLOGI) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Fabric PLOGI response received in state (%d). Dropping frame", + fdls_get_state(&iport->fabric)); + return; + } + if (iport->active_oxid_fabric_req != oxid) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Incorrect OXID in response. state: %d, oxid recvd: 0x%x, active oxid: 0x%x\n", + fdls_get_state(fdls), oxid, iport->active_oxid_fabric_req); + return; + } + fdls_free_oxid(iport, oxid, &iport->active_oxid_fabric_req); + + switch (plogi_rsp->els.fl_cmd) { + case ELS_LS_ACC: + atomic64_inc(&iport->iport_stats.fabric_plogi_ls_accepts); + if (iport->fabric.timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport fcid: 0x%x fabric PLOGI response: Accepted\n", + iport->fcid); + fnic_del_fabric_timer_sync(fnic); + } + iport->fabric.timer_pending = 0; + iport->fabric.retry_counter = 0; + fdls_set_state(&iport->fabric, FDLS_STATE_RPN_ID); + fdls_send_rpn_id(iport); + break; + case ELS_LS_RJT: + atomic64_inc(&iport->iport_stats.fabric_plogi_ls_rejects); + if (((els_rjt->rej.er_reason == ELS_RJT_BUSY) + || (els_rjt->rej.er_reason == ELS_RJT_UNAB)) + && (iport->fabric.retry_counter < iport->max_plogi_retries)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Fabric PLOGI ELS_LS_RJT BUSY. Retry from timer routine", + iport->fcid); + } else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Fabric PLOGI ELS_LS_RJT. Halting discovery", + iport->fcid); + if (iport->fabric.timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport fcid: 0x%x Canceling fabric disc timer\n", + iport->fcid); + fnic_del_fabric_timer_sync(fnic); + } + iport->fabric.timer_pending = 0; + iport->fabric.retry_counter = 0; + return; + } + break; + default: + FNIC_FCS_DBG(KERN_INFO, fnic, + "PLOGI response not accepted: 0x%x", + plogi_rsp->els.fl_cmd); + atomic64_inc(&iport->iport_stats.fabric_plogi_misc_rejects); + break; + } +} + +static void fdls_process_fdmi_plogi_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + struct fc_std_flogi *plogi_rsp = (struct fc_std_flogi *)fchdr; + struct fc_std_els_rjt_rsp *els_rjt = (struct fc_std_els_rjt_rsp *)fchdr; + struct fnic_fdls_fabric_s *fdls = &iport->fabric; + struct fnic *fnic = iport->fnic; + u64 fdmi_tov; + uint16_t oxid = FNIC_STD_GET_OX_ID(fchdr); + + if (iport->active_oxid_fdmi_plogi != oxid) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Incorrect OXID in response. state: %d, oxid recvd: 0x%x, active oxid: 0x%x\n", + fdls_get_state(fdls), oxid, iport->active_oxid_fdmi_plogi); + return; + } + + iport->fabric.fdmi_pending &= ~FDLS_FDMI_PLOGI_PENDING; + fdls_free_oxid(iport, oxid, &iport->active_oxid_fdmi_plogi); + + if (ntoh24(fchdr->fh_s_id) == FC_FID_MGMT_SERV) { + timer_delete_sync(&iport->fabric.fdmi_timer); + iport->fabric.fdmi_pending = 0; + switch (plogi_rsp->els.fl_cmd) { + case ELS_LS_ACC: + FNIC_FCS_DBG(KERN_INFO, fnic, + "FDLS process fdmi PLOGI response status: ELS_LS_ACC\n"); + FNIC_FCS_DBG(KERN_INFO, fnic, + "Sending fdmi registration for port 0x%x\n", + iport->fcid); + + fdls_fdmi_register_hba(iport); + fdls_fdmi_register_pa(iport); + fdmi_tov = jiffies + msecs_to_jiffies(5000); + mod_timer(&iport->fabric.fdmi_timer, + round_jiffies(fdmi_tov)); + break; + case ELS_LS_RJT: + FNIC_FCS_DBG(KERN_INFO, fnic, + "Fabric FDMI PLOGI returned ELS_LS_RJT reason: 0x%x", + els_rjt->rej.er_reason); + + if (((els_rjt->rej.er_reason == ELS_RJT_BUSY) + || (els_rjt->rej.er_reason == ELS_RJT_UNAB)) + && (iport->fabric.fdmi_retry < 7)) { + iport->fabric.fdmi_retry++; + fdls_send_fdmi_plogi(iport); + } + break; + default: + break; + } + } +} +static void fdls_process_fdmi_reg_ack(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr, + int rsp_type) +{ + struct fnic *fnic = iport->fnic; + uint16_t oxid; + + if (!iport->fabric.fdmi_pending) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Received FDMI ack while not waiting: 0x%x\n", + FNIC_STD_GET_OX_ID(fchdr)); + return; + } + + oxid = FNIC_STD_GET_OX_ID(fchdr); + + if ((iport->active_oxid_fdmi_rhba != oxid) && + (iport->active_oxid_fdmi_rpa != oxid)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Incorrect OXID in response. oxid recvd: 0x%x, active oxids(rhba,rpa): 0x%x, 0x%x\n", + oxid, iport->active_oxid_fdmi_rhba, iport->active_oxid_fdmi_rpa); + return; + } + if (FNIC_FRAME_TYPE(oxid) == FNIC_FRAME_TYPE_FDMI_RHBA) { + iport->fabric.fdmi_pending &= ~FDLS_FDMI_REG_HBA_PENDING; + fdls_free_oxid(iport, oxid, &iport->active_oxid_fdmi_rhba); + } else { + iport->fabric.fdmi_pending &= ~FDLS_FDMI_RPA_PENDING; + fdls_free_oxid(iport, oxid, &iport->active_oxid_fdmi_rpa); + } + + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport fcid: 0x%x: Received FDMI registration ack\n", + iport->fcid); + + if (!iport->fabric.fdmi_pending) { + timer_delete_sync(&iport->fabric.fdmi_timer); + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport fcid: 0x%x: Canceling FDMI timer\n", + iport->fcid); + } +} + +static void fdls_process_fdmi_abts_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + uint32_t s_id; + struct fnic *fnic = iport->fnic; + uint16_t oxid; + + s_id = ntoh24(FNIC_STD_GET_S_ID(fchdr)); + + if (!(s_id != FC_FID_MGMT_SERV)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received abts rsp with invalid SID: 0x%x. Dropping frame", + s_id); + return; + } + + oxid = FNIC_STD_GET_OX_ID(fchdr); + + switch (FNIC_FRAME_TYPE(oxid)) { + case FNIC_FRAME_TYPE_FDMI_PLOGI: + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received FDMI PLOGI ABTS rsp with oxid: 0x%x", oxid); + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: iport->fabric.fdmi_pending: 0x%x", + iport->fcid, iport->fabric.fdmi_pending); + fdls_free_oxid(iport, oxid, &iport->active_oxid_fdmi_plogi); + + iport->fabric.fdmi_pending &= ~FDLS_FDMI_PLOGI_PENDING; + iport->fabric.fdmi_pending &= ~FDLS_FDMI_ABORT_PENDING; + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: iport->fabric.fdmi_pending: 0x%x", + iport->fcid, iport->fabric.fdmi_pending); + break; + case FNIC_FRAME_TYPE_FDMI_RHBA: + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received FDMI RHBA ABTS rsp with oxid: 0x%x", oxid); + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: iport->fabric.fdmi_pending: 0x%x", + iport->fcid, iport->fabric.fdmi_pending); + + iport->fabric.fdmi_pending &= ~FDLS_FDMI_REG_HBA_PENDING; + + /* If RPA is still pending, don't turn off ABORT PENDING. + * We count on the timer to detect the ABTS timeout and take + * corrective action. + */ + if (!(iport->fabric.fdmi_pending & FDLS_FDMI_RPA_PENDING)) + iport->fabric.fdmi_pending &= ~FDLS_FDMI_ABORT_PENDING; + + fdls_free_oxid(iport, oxid, &iport->active_oxid_fdmi_rhba); + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: iport->fabric.fdmi_pending: 0x%x", + iport->fcid, iport->fabric.fdmi_pending); + break; + case FNIC_FRAME_TYPE_FDMI_RPA: + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received FDMI RPA ABTS rsp with oxid: 0x%x", oxid); + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: iport->fabric.fdmi_pending: 0x%x", + iport->fcid, iport->fabric.fdmi_pending); + + iport->fabric.fdmi_pending &= ~FDLS_FDMI_RPA_PENDING; + + /* If RHBA is still pending, don't turn off ABORT PENDING. + * We count on the timer to detect the ABTS timeout and take + * corrective action. + */ + if (!(iport->fabric.fdmi_pending & FDLS_FDMI_REG_HBA_PENDING)) + iport->fabric.fdmi_pending &= ~FDLS_FDMI_ABORT_PENDING; + + fdls_free_oxid(iport, oxid, &iport->active_oxid_fdmi_rpa); + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: iport->fabric.fdmi_pending: 0x%x", + iport->fcid, iport->fabric.fdmi_pending); + break; + default: + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received abts rsp with invalid oxid: 0x%x. Dropping frame", + oxid); + break; + } + + /* + * Only if ABORT PENDING is off, delete the timer, and if no other + * operations are pending, retry FDMI. + * Otherwise, let the timer pop and take the appropriate action. + */ + if (!(iport->fabric.fdmi_pending & FDLS_FDMI_ABORT_PENDING)) { + timer_delete_sync(&iport->fabric.fdmi_timer); + if (!iport->fabric.fdmi_pending) + fdls_fdmi_retry_plogi(iport); + } +} + +static void +fdls_process_fabric_abts_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + uint32_t s_id; + struct fc_std_abts_ba_acc *ba_acc = (struct fc_std_abts_ba_acc *)fchdr; + struct fc_std_abts_ba_rjt *ba_rjt; + uint32_t fabric_state = iport->fabric.state; + struct fnic *fnic = iport->fnic; + int frame_type; + uint16_t oxid; + + s_id = ntoh24(fchdr->fh_s_id); + ba_rjt = (struct fc_std_abts_ba_rjt *) fchdr; + + if (!((s_id == FC_FID_DIR_SERV) || (s_id == FC_FID_FLOGI) + || (s_id == FC_FID_FCTRL))) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received abts rsp with invalid SID: 0x%x. Dropping frame", + s_id); + return; + } + + oxid = FNIC_STD_GET_OX_ID(fchdr); + if (iport->active_oxid_fabric_req != oxid) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received abts rsp with invalid oxid: 0x%x. Dropping frame", + oxid); + return; + } + + if (iport->fabric.timer_pending) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Canceling fabric disc timer %p\n", iport); + fnic_del_fabric_timer_sync(fnic); + } + iport->fabric.timer_pending = 0; + iport->fabric.flags &= ~FNIC_FDLS_FABRIC_ABORT_ISSUED; + + if (fchdr->fh_r_ctl == FC_RCTL_BA_ACC) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received abts rsp BA_ACC for fabric_state: %d OX_ID: 0x%x", + fabric_state, be16_to_cpu(ba_acc->acc.ba_ox_id)); + } else if (fchdr->fh_r_ctl == FC_RCTL_BA_RJT) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "BA_RJT fs: %d OX_ID: 0x%x rc: 0x%x rce: 0x%x", + fabric_state, FNIC_STD_GET_OX_ID(&ba_rjt->fchdr), + ba_rjt->rjt.br_reason, ba_rjt->rjt.br_explan); + } + + frame_type = FNIC_FRAME_TYPE(oxid); + fdls_free_oxid(iport, oxid, &iport->active_oxid_fabric_req); + + /* currently error handling/retry logic is same for ABTS BA_ACC & BA_RJT */ + switch (frame_type) { + case FNIC_FRAME_TYPE_FABRIC_FLOGI: + if (iport->fabric.retry_counter < iport->max_flogi_retries) + fdls_send_fabric_flogi(iport); + else + FNIC_FCS_DBG(KERN_INFO, fnic, + "Exceeded max FLOGI retries"); + break; + case FNIC_FRAME_TYPE_FABRIC_LOGO: + if (iport->fabric.retry_counter < FABRIC_LOGO_MAX_RETRY) + fdls_send_fabric_logo(iport); + break; + case FNIC_FRAME_TYPE_FABRIC_PLOGI: + if (iport->fabric.retry_counter < iport->max_plogi_retries) + fdls_send_fabric_plogi(iport); + else + FNIC_FCS_DBG(KERN_INFO, fnic, + "Exceeded max PLOGI retries"); + break; + case FNIC_FRAME_TYPE_FABRIC_RPN: + if (iport->fabric.retry_counter < FDLS_RETRY_COUNT) + fdls_send_rpn_id(iport); + else + /* go back to fabric Plogi */ + fnic_fdls_start_plogi(iport); + break; + case FNIC_FRAME_TYPE_FABRIC_SCR: + if (iport->fabric.retry_counter < FDLS_RETRY_COUNT) + fdls_send_scr(iport); + else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "SCR exhausted retries. Start fabric PLOGI %p", + iport); + fnic_fdls_start_plogi(iport); /* go back to fabric Plogi */ + } + break; + case FNIC_FRAME_TYPE_FABRIC_RFT: + if (iport->fabric.retry_counter < FDLS_RETRY_COUNT) + fdls_send_register_fc4_types(iport); + else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "RFT exhausted retries. Start fabric PLOGI %p", + iport); + fnic_fdls_start_plogi(iport); /* go back to fabric Plogi */ + } + break; + case FNIC_FRAME_TYPE_FABRIC_RFF: + if (iport->fabric.retry_counter < FDLS_RETRY_COUNT) + fdls_send_register_fc4_features(iport); + else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "RFF exhausted retries. Start fabric PLOGI %p", + iport); + fnic_fdls_start_plogi(iport); /* go back to fabric Plogi */ + } + break; + case FNIC_FRAME_TYPE_FABRIC_GPN_FT: + if (iport->fabric.retry_counter <= FDLS_RETRY_COUNT) + fdls_send_gpn_ft(iport, fabric_state); + else + FNIC_FCS_DBG(KERN_INFO, fnic, + "GPN FT exhausted retries. Start fabric PLOGI %p", + iport); + break; + default: + /* + * We should not be here since we already validated rx oxid with + * our active_oxid_fabric_req + */ + FNIC_FCS_DBG(KERN_INFO, fnic, + "Invalid OXID/active oxid 0x%x\n", oxid); + WARN_ON(true); + return; + } +} + +static void +fdls_process_abts_req(struct fnic_iport_s *iport, struct fc_frame_header *fchdr) +{ + uint8_t *frame; + struct fc_std_abts_ba_acc *pba_acc; + uint32_t nport_id; + uint16_t oxid = FNIC_STD_GET_OX_ID(fchdr); + struct fnic_tport_s *tport; + struct fnic *fnic = iport->fnic; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_abts_ba_acc); + + nport_id = ntoh24(fchdr->fh_s_id); + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received abort from SID 0x%8x", nport_id); + + tport = fnic_find_tport_by_fcid(iport, nport_id); + if (tport) { + if (tport->active_oxid == oxid) { + tport->flags |= FNIC_FDLS_TGT_ABORT_ISSUED; + fdls_free_oxid(iport, oxid, &tport->active_oxid); + } + } + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "0x%x: Failed to allocate frame to send response for ABTS req", + iport->fcid); + return; + } + + pba_acc = (struct fc_std_abts_ba_acc *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + *pba_acc = (struct fc_std_abts_ba_acc) { + .fchdr = {.fh_r_ctl = FC_RCTL_BA_ACC, + .fh_f_ctl = {FNIC_FCP_RSP_FCTL, 0, 0}}, + .acc = {.ba_low_seq_cnt = 0, .ba_high_seq_cnt = cpu_to_be16(0xFFFF)} + }; + + FNIC_STD_SET_S_ID(pba_acc->fchdr, fchdr->fh_d_id); + FNIC_STD_SET_D_ID(pba_acc->fchdr, fchdr->fh_s_id); + FNIC_STD_SET_OX_ID(pba_acc->fchdr, FNIC_STD_GET_OX_ID(fchdr)); + FNIC_STD_SET_RX_ID(pba_acc->fchdr, FNIC_STD_GET_RX_ID(fchdr)); + + pba_acc->acc.ba_rx_id = cpu_to_be16(FNIC_STD_GET_RX_ID(fchdr)); + pba_acc->acc.ba_ox_id = cpu_to_be16(FNIC_STD_GET_OX_ID(fchdr)); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS send BA ACC with oxid: 0x%x", + iport->fcid, oxid); + + fnic_send_fcoe_frame(iport, frame, frame_size); +} + +static void +fdls_process_unsupported_els_req(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + uint8_t *frame; + struct fc_std_els_rjt_rsp *pls_rsp; + uint16_t oxid; + uint32_t d_id = ntoh24(fchdr->fh_d_id); + struct fnic *fnic = iport->fnic; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_els_rjt_rsp); + + if (iport->fcid != d_id) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Dropping unsupported ELS with illegal frame bits 0x%x\n", + d_id); + atomic64_inc(&iport->iport_stats.unsupported_frames_dropped); + return; + } + + if ((iport->state != FNIC_IPORT_STATE_READY) + && (iport->state != FNIC_IPORT_STATE_FABRIC_DISC)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Dropping unsupported ELS request in iport state: %d", + iport->state); + atomic64_inc(&iport->iport_stats.unsupported_frames_dropped); + return; + } + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send response to unsupported ELS request"); + return; + } + + pls_rsp = (struct fc_std_els_rjt_rsp *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + fdls_init_els_rjt_frame(frame, iport); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Process unsupported ELS request from SID: 0x%x", + iport->fcid, ntoh24(fchdr->fh_s_id)); + + /* We don't support this ELS request, send a reject */ + pls_rsp->rej.er_reason = 0x0B; + pls_rsp->rej.er_explan = 0x0; + pls_rsp->rej.er_vendor = 0x0; + + FNIC_STD_SET_S_ID(pls_rsp->fchdr, fchdr->fh_d_id); + FNIC_STD_SET_D_ID(pls_rsp->fchdr, fchdr->fh_s_id); + oxid = FNIC_STD_GET_OX_ID(fchdr); + FNIC_STD_SET_OX_ID(pls_rsp->fchdr, oxid); + + fnic_send_fcoe_frame(iport, frame, frame_size); +} + +static void +fdls_process_rls_req(struct fnic_iport_s *iport, struct fc_frame_header *fchdr) +{ + uint8_t *frame; + struct fc_std_rls_acc *prls_acc_rsp; + uint16_t oxid; + struct fnic *fnic = iport->fnic; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_rls_acc); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Process RLS request %d", iport->fnic->fnic_num); + + if ((iport->state != FNIC_IPORT_STATE_READY) + && (iport->state != FNIC_IPORT_STATE_FABRIC_DISC)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received RLS req in iport state: %d. Dropping the frame.", + iport->state); + return; + } + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send RLS accept"); + return; + } + prls_acc_rsp = (struct fc_std_rls_acc *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + + FNIC_STD_SET_S_ID(prls_acc_rsp->fchdr, fchdr->fh_d_id); + FNIC_STD_SET_D_ID(prls_acc_rsp->fchdr, fchdr->fh_s_id); + + oxid = FNIC_STD_GET_OX_ID(fchdr); + FNIC_STD_SET_OX_ID(prls_acc_rsp->fchdr, oxid); + FNIC_STD_SET_RX_ID(prls_acc_rsp->fchdr, FNIC_UNASSIGNED_RXID); + + FNIC_STD_SET_F_CTL(prls_acc_rsp->fchdr, FNIC_ELS_REP_FCTL << 16); + FNIC_STD_SET_R_CTL(prls_acc_rsp->fchdr, FC_RCTL_ELS_REP); + FNIC_STD_SET_TYPE(prls_acc_rsp->fchdr, FC_TYPE_ELS); + + prls_acc_rsp->els.rls_cmd = ELS_LS_ACC; + prls_acc_rsp->els.rls_lesb.lesb_link_fail = + cpu_to_be32(iport->fnic->link_down_cnt); + + fnic_send_fcoe_frame(iport, frame, frame_size); +} + +static void +fdls_process_els_req(struct fnic_iport_s *iport, struct fc_frame_header *fchdr, + uint32_t len) +{ + uint8_t *frame; + struct fc_std_els_acc_rsp *pels_acc; + uint16_t oxid; + uint8_t *fc_payload; + uint8_t type; + struct fnic *fnic = iport->fnic; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET; + + fc_payload = (uint8_t *) fchdr + sizeof(struct fc_frame_header); + type = *fc_payload; + + if ((iport->state != FNIC_IPORT_STATE_READY) + && (iport->state != FNIC_IPORT_STATE_FABRIC_DISC)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Dropping ELS frame type: 0x%x in iport state: %d", + type, iport->state); + return; + } + switch (type) { + case ELS_ECHO: + FNIC_FCS_DBG(KERN_INFO, fnic, + "sending LS_ACC for ECHO request %d\n", + iport->fnic->fnic_num); + break; + + case ELS_RRQ: + FNIC_FCS_DBG(KERN_INFO, fnic, + "sending LS_ACC for RRQ request %d\n", + iport->fnic->fnic_num); + break; + + default: + FNIC_FCS_DBG(KERN_INFO, fnic, + "sending LS_ACC for 0x%x ELS frame\n", type); + break; + } + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send ELS response for 0x%x", + type); + return; + } + + if (type == ELS_ECHO) { + /* Brocade sends a longer payload, copy all frame back */ + memcpy(frame, fchdr, len); + } + + pels_acc = (struct fc_std_els_acc_rsp *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + fdls_init_els_acc_frame(frame, iport); + + FNIC_STD_SET_D_ID(pels_acc->fchdr, fchdr->fh_s_id); + + oxid = FNIC_STD_GET_OX_ID(fchdr); + FNIC_STD_SET_OX_ID(pels_acc->fchdr, oxid); + + if (type == ELS_ECHO) + frame_size += len; + else + frame_size += sizeof(struct fc_std_els_acc_rsp); + + fnic_send_fcoe_frame(iport, frame, frame_size); +} + +static void +fdls_process_tgt_abts_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + uint32_t s_id; + struct fnic_tport_s *tport; + uint32_t tport_state; + struct fc_std_abts_ba_acc *ba_acc; + struct fc_std_abts_ba_rjt *ba_rjt; + uint16_t oxid; + struct fnic *fnic = iport->fnic; + int frame_type; + + s_id = ntoh24(fchdr->fh_s_id); + ba_acc = (struct fc_std_abts_ba_acc *)fchdr; + ba_rjt = (struct fc_std_abts_ba_rjt *)fchdr; + + tport = fnic_find_tport_by_fcid(iport, s_id); + if (!tport) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Received tgt abts rsp with invalid SID: 0x%x", s_id); + return; + } + if (tport->timer_pending) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "tport 0x%p Canceling fabric disc timer\n", tport); + fnic_del_tport_timer_sync(fnic, tport); + } + if (iport->state != FNIC_IPORT_STATE_READY) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Received tgt abts rsp in iport state(%d). Dropping.", + iport->state); + return; + } + tport->timer_pending = 0; + tport->flags &= ~FNIC_FDLS_TGT_ABORT_ISSUED; + tport_state = tport->state; + oxid = FNIC_STD_GET_OX_ID(fchdr); + + /*This abort rsp is for ADISC */ + frame_type = FNIC_FRAME_TYPE(oxid); + switch (frame_type) { + case FNIC_FRAME_TYPE_TGT_ADISC: + if (fchdr->fh_r_ctl == FC_RCTL_BA_ACC) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "OX_ID: 0x%x tgt_fcid: 0x%x rcvd tgt adisc abts resp BA_ACC", + be16_to_cpu(ba_acc->acc.ba_ox_id), + tport->fcid); + } else if (fchdr->fh_r_ctl == FC_RCTL_BA_RJT) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "ADISC BA_RJT rcvd tport_fcid: 0x%x tport_state: %d ", + tport->fcid, tport_state); + FNIC_FCS_DBG(KERN_ERR, fnic, + "reason code: 0x%x reason code explanation:0x%x ", + ba_rjt->rjt.br_reason, + ba_rjt->rjt.br_explan); + } + if ((tport->retry_counter < FDLS_RETRY_COUNT) + && (fchdr->fh_r_ctl == FC_RCTL_BA_ACC)) { + fdls_free_oxid(iport, oxid, &tport->active_oxid); + fdls_send_tgt_adisc(iport, tport); + return; + } + fdls_free_oxid(iport, oxid, &tport->active_oxid); + FNIC_FCS_DBG(KERN_ERR, fnic, + "ADISC not responding. Deleting target port: 0x%x", + tport->fcid); + fdls_delete_tport(iport, tport); + /* Restart discovery of targets */ + if ((iport->state == FNIC_IPORT_STATE_READY) + && (iport->fabric.state != FDLS_STATE_SEND_GPNFT) + && (iport->fabric.state != FDLS_STATE_RSCN_GPN_FT)) { + fdls_send_gpn_ft(iport, FDLS_STATE_SEND_GPNFT); + } + break; + case FNIC_FRAME_TYPE_TGT_PLOGI: + if (fchdr->fh_r_ctl == FC_RCTL_BA_ACC) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Received tgt PLOGI abts response BA_ACC tgt_fcid: 0x%x", + tport->fcid); + } else if (fchdr->fh_r_ctl == FC_RCTL_BA_RJT) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "PLOGI BA_RJT received for tport_fcid: 0x%x OX_ID: 0x%x", + tport->fcid, FNIC_STD_GET_OX_ID(fchdr)); + FNIC_FCS_DBG(KERN_INFO, fnic, + "reason code: 0x%x reason code explanation: 0x%x", + ba_rjt->rjt.br_reason, + ba_rjt->rjt.br_explan); + } + if ((tport->retry_counter < iport->max_plogi_retries) + && (fchdr->fh_r_ctl == FC_RCTL_BA_ACC)) { + fdls_free_oxid(iport, oxid, &tport->active_oxid); + fdls_send_tgt_plogi(iport, tport); + return; + } + + fdls_free_oxid(iport, oxid, &tport->active_oxid); + fdls_delete_tport(iport, tport); + /* Restart discovery of targets */ + if ((iport->state == FNIC_IPORT_STATE_READY) + && (iport->fabric.state != FDLS_STATE_SEND_GPNFT) + && (iport->fabric.state != FDLS_STATE_RSCN_GPN_FT)) { + fdls_send_gpn_ft(iport, FDLS_STATE_SEND_GPNFT); + } + break; + case FNIC_FRAME_TYPE_TGT_PRLI: + if (fchdr->fh_r_ctl == FC_RCTL_BA_ACC) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Received tgt PRLI abts response BA_ACC", + tport->fcid); + } else if (fchdr->fh_r_ctl == FC_RCTL_BA_RJT) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "PRLI BA_RJT received for tport_fcid: 0x%x OX_ID: 0x%x ", + tport->fcid, FNIC_STD_GET_OX_ID(fchdr)); + FNIC_FCS_DBG(KERN_INFO, fnic, + "reason code: 0x%x reason code explanation: 0x%x", + ba_rjt->rjt.br_reason, + ba_rjt->rjt.br_explan); + } + if ((tport->retry_counter < FDLS_RETRY_COUNT) + && (fchdr->fh_r_ctl == FC_RCTL_BA_ACC)) { + fdls_free_oxid(iport, oxid, &tport->active_oxid); + fdls_send_tgt_prli(iport, tport); + return; + } + fdls_free_oxid(iport, oxid, &tport->active_oxid); + fdls_send_tgt_plogi(iport, tport); /* go back to plogi */ + fdls_set_tport_state(tport, FDLS_TGT_STATE_PLOGI); + break; + default: + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received ABTS response for unknown frame %p", iport); + break; + } + +} + +static void +fdls_process_plogi_req(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + uint8_t *frame; + struct fc_std_els_rjt_rsp *pplogi_rsp; + uint16_t oxid; + uint32_t d_id = ntoh24(fchdr->fh_d_id); + struct fnic *fnic = iport->fnic; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_els_rjt_rsp); + + if (iport->fcid != d_id) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received PLOGI with illegal frame bits. Dropping frame from 0x%x", + d_id); + return; + } + + if (iport->state != FNIC_IPORT_STATE_READY) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received PLOGI request in iport state: %d Dropping frame", + iport->state); + return; + } + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send response to PLOGI request"); + return; + } + + pplogi_rsp = (struct fc_std_els_rjt_rsp *) (frame + FNIC_ETH_FCOE_HDRS_OFFSET); + fdls_init_els_rjt_frame(frame, iport); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Process PLOGI request from SID: 0x%x", + iport->fcid, ntoh24(fchdr->fh_s_id)); + + /* We don't support PLOGI request, send a reject */ + pplogi_rsp->rej.er_reason = 0x0B; + pplogi_rsp->rej.er_explan = 0x0; + pplogi_rsp->rej.er_vendor = 0x0; + + FNIC_STD_SET_S_ID(pplogi_rsp->fchdr, fchdr->fh_d_id); + FNIC_STD_SET_D_ID(pplogi_rsp->fchdr, fchdr->fh_s_id); + oxid = FNIC_STD_GET_OX_ID(fchdr); + FNIC_STD_SET_OX_ID(pplogi_rsp->fchdr, oxid); + + fnic_send_fcoe_frame(iport, frame, frame_size); +} + +static void +fdls_process_logo_req(struct fnic_iport_s *iport, struct fc_frame_header *fchdr) +{ + struct fc_std_logo *logo = (struct fc_std_logo *)fchdr; + uint32_t nport_id; + uint64_t nport_name; + struct fnic_tport_s *tport; + struct fnic *fnic = iport->fnic; + uint16_t oxid; + + nport_id = ntoh24(logo->els.fl_n_port_id); + nport_name = be64_to_cpu(logo->els.fl_n_port_wwn); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Process LOGO request from fcid: 0x%x", nport_id); + + if (iport->state != FNIC_IPORT_STATE_READY) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Dropping LOGO req from 0x%x in iport state: %d", + nport_id, iport->state); + return; + } + + tport = fnic_find_tport_by_fcid(iport, nport_id); + + if (!tport) { + /* We are not logged in with the nport, log and drop... */ + FNIC_FCS_DBG(KERN_ERR, fnic, + "Received LOGO from an nport not logged in: 0x%x(0x%llx)", + nport_id, nport_name); + return; + } + if (tport->fcid != nport_id) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Received LOGO with invalid target port fcid: 0x%x(0x%llx)", + nport_id, nport_name); + return; + } + if (tport->timer_pending) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "tport fcid 0x%x: Canceling disc timer\n", + tport->fcid); + fnic_del_tport_timer_sync(fnic, tport); + tport->timer_pending = 0; + } + + /* got a logo in response to adisc to a target which has logged out */ + if (tport->state == FDLS_TGT_STATE_ADISC) { + tport->retry_counter = 0; + oxid = tport->active_oxid; + fdls_free_oxid(iport, oxid, &tport->active_oxid); + fdls_delete_tport(iport, tport); + fdls_send_logo_resp(iport, &logo->fchdr); + if ((iport->state == FNIC_IPORT_STATE_READY) + && (fdls_get_state(&iport->fabric) != FDLS_STATE_SEND_GPNFT) + && (fdls_get_state(&iport->fabric) != FDLS_STATE_RSCN_GPN_FT)) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Sending GPNFT in response to LOGO from Target:0x%x", + nport_id); + fdls_send_gpn_ft(iport, FDLS_STATE_SEND_GPNFT); + return; + } + } else { + fdls_delete_tport(iport, tport); + } + if (iport->state == FNIC_IPORT_STATE_READY) { + fdls_send_logo_resp(iport, &logo->fchdr); + if ((fdls_get_state(&iport->fabric) != FDLS_STATE_SEND_GPNFT) && + (fdls_get_state(&iport->fabric) != FDLS_STATE_RSCN_GPN_FT)) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Sending GPNFT in response to LOGO from Target:0x%x", + nport_id); + fdls_send_gpn_ft(iport, FDLS_STATE_SEND_GPNFT); + } + } +} + +static void +fdls_process_rscn(struct fnic_iport_s *iport, struct fc_frame_header *fchdr) +{ + struct fc_std_rscn *rscn; + struct fc_els_rscn_page *rscn_port = NULL; + int num_ports; + struct fnic_tport_s *tport, *next; + uint32_t nport_id; + uint8_t fcid[3]; + int newports = 0; + struct fnic_fdls_fabric_s *fdls = &iport->fabric; + struct fnic *fnic = iport->fnic; + int rscn_type = NOT_PC_RSCN; + uint32_t sid = ntoh24(fchdr->fh_s_id); + unsigned long reset_fnic_list_lock_flags = 0; + uint16_t rscn_payload_len; + + atomic64_inc(&iport->iport_stats.num_rscns); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "FDLS process RSCN %p", iport); + + if (iport->state != FNIC_IPORT_STATE_READY) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "FDLS RSCN received in state(%d). Dropping", + fdls_get_state(fdls)); + return; + } + + rscn = (struct fc_std_rscn *)fchdr; + rscn_payload_len = be16_to_cpu(rscn->els.rscn_plen); + + /* frame validation */ + if ((rscn_payload_len % 4 != 0) || (rscn_payload_len < 8) + || (rscn_payload_len > 1024) + || (rscn->els.rscn_page_len != 4)) { + num_ports = 0; + if ((rscn_payload_len == 0xFFFF) + && (sid == FC_FID_FCTRL)) { + rscn_type = PC_RSCN; + FNIC_FCS_DBG(KERN_INFO, fnic, + "pcrscn: PCRSCN received. sid: 0x%x payload len: 0x%x", + sid, rscn_payload_len); + } else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "RSCN payload_len: 0x%x page_len: 0x%x", + rscn_payload_len, rscn->els.rscn_page_len); + /* if this happens then we need to send ADISC to all the tports. */ + list_for_each_entry_safe(tport, next, &iport->tport_list, links) { + if (tport->state == FDLS_TGT_STATE_READY) + tport->flags |= FNIC_FDLS_TPORT_SEND_ADISC; + FNIC_FCS_DBG(KERN_INFO, fnic, + "RSCN for port id: 0x%x", tport->fcid); + } + } /* end else */ + } else { + num_ports = (rscn_payload_len - 4) / rscn->els.rscn_page_len; + rscn_port = (struct fc_els_rscn_page *)(rscn + 1); + } + FNIC_FCS_DBG(KERN_INFO, fnic, + "RSCN received for num_ports: %d payload_len: %d page_len: %d ", + num_ports, rscn_payload_len, rscn->els.rscn_page_len); + + /* + * RSCN have at least one Port_ID page , but may not have any port_id + * in it. If no port_id is specified in the Port_ID page , we send + * ADISC to all the tports + */ + + while (num_ports) { + + memcpy(fcid, rscn_port->rscn_fid, 3); + + nport_id = ntoh24(fcid); + rscn_port++; + num_ports--; + /* if this happens then we need to send ADISC to all the tports. */ + if (nport_id == 0) { + list_for_each_entry_safe(tport, next, &iport->tport_list, + links) { + if (tport->state == FDLS_TGT_STATE_READY) + tport->flags |= FNIC_FDLS_TPORT_SEND_ADISC; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "RSCN for port id: 0x%x", tport->fcid); + } + break; + } + tport = fnic_find_tport_by_fcid(iport, nport_id); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "RSCN port id list: 0x%x", nport_id); + + if (!tport) { + newports++; + continue; + } + if (tport->state == FDLS_TGT_STATE_READY) + tport->flags |= FNIC_FDLS_TPORT_SEND_ADISC; + } + + if (pc_rscn_handling_feature_flag == PC_RSCN_HANDLING_FEATURE_ON && + rscn_type == PC_RSCN && fnic->role == FNIC_ROLE_FCP_INITIATOR) { + + if (fnic->pc_rscn_handling_status == PC_RSCN_HANDLING_IN_PROGRESS) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "PCRSCN handling already in progress. Skip host reset: %d", + iport->fnic->fnic_num); + return; + } + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Processing PCRSCN. Queuing fnic for host reset: %d", + iport->fnic->fnic_num); + fnic->pc_rscn_handling_status = PC_RSCN_HANDLING_IN_PROGRESS; + + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + + spin_lock_irqsave(&reset_fnic_list_lock, + reset_fnic_list_lock_flags); + list_add_tail(&fnic->links, &reset_fnic_list); + spin_unlock_irqrestore(&reset_fnic_list_lock, + reset_fnic_list_lock_flags); + + queue_work(reset_fnic_work_queue, &reset_fnic_work); + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + } else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "FDLS process RSCN sending GPN_FT: newports: %d", newports); + fdls_send_gpn_ft(iport, FDLS_STATE_RSCN_GPN_FT); + fdls_send_rscn_resp(iport, fchdr); + } +} + +void fnic_fdls_disc_start(struct fnic_iport_s *iport) +{ + struct fnic *fnic = iport->fnic; + + if (IS_FNIC_FCP_INITIATOR(fnic)) { + fc_host_fabric_name(iport->fnic->host) = 0; + fc_host_post_event(iport->fnic->host, fc_get_event_number(), + FCH_EVT_LIPRESET, 0); + } + + if (!iport->usefip) { + if (iport->flags & FNIC_FIRST_LINK_UP) { + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + fnic_fcpio_reset(iport->fnic); + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + + iport->flags &= ~FNIC_FIRST_LINK_UP; + } + fnic_fdls_start_flogi(iport); + } else + fnic_fdls_start_plogi(iport); +} + +static void +fdls_process_adisc_req(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + struct fc_std_els_adisc *padisc_acc; + struct fc_std_els_adisc *adisc_req = (struct fc_std_els_adisc *)fchdr; + uint64_t frame_wwnn; + uint64_t frame_wwpn; + uint32_t tgt_fcid; + struct fnic_tport_s *tport; + uint8_t *fcid; + uint8_t *rjt_frame; + uint8_t *acc_frame; + struct fc_std_els_rjt_rsp *prjts_rsp; + uint16_t oxid; + struct fnic *fnic = iport->fnic; + uint16_t rjt_frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_els_rjt_rsp); + uint16_t acc_frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_std_els_adisc); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Process ADISC request %d", iport->fnic->fnic_num); + + fcid = FNIC_STD_GET_S_ID(fchdr); + tgt_fcid = ntoh24(fcid); + tport = fnic_find_tport_by_fcid(iport, tgt_fcid); + if (!tport) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "tport for fcid: 0x%x not found. Dropping ADISC req.", + tgt_fcid); + return; + } + if (iport->state != FNIC_IPORT_STATE_READY) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Dropping ADISC req from fcid: 0x%x in iport state: %d", + tgt_fcid, iport->state); + return; + } + + frame_wwnn = be64_to_cpu(adisc_req->els.adisc_wwnn); + frame_wwpn = be64_to_cpu(adisc_req->els.adisc_wwpn); + + if ((frame_wwnn != tport->wwnn) || (frame_wwpn != tport->wwpn)) { + /* send reject */ + FNIC_FCS_DBG(KERN_ERR, fnic, + "ADISC req from fcid: 0x%x mismatch wwpn: 0x%llx wwnn: 0x%llx", + tgt_fcid, frame_wwpn, frame_wwnn); + FNIC_FCS_DBG(KERN_ERR, fnic, + "local tport wwpn: 0x%llx wwnn: 0x%llx. Sending RJT", + tport->wwpn, tport->wwnn); + + rjt_frame = fdls_alloc_frame(iport); + if (rjt_frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate rjt_frame to send response to ADISC request"); + return; + } + + prjts_rsp = (struct fc_std_els_rjt_rsp *) (rjt_frame + FNIC_ETH_FCOE_HDRS_OFFSET); + fdls_init_els_rjt_frame(rjt_frame, iport); + + prjts_rsp->rej.er_reason = 0x03; /* logical error */ + prjts_rsp->rej.er_explan = 0x1E; /* N_port login required */ + prjts_rsp->rej.er_vendor = 0x0; + + FNIC_STD_SET_S_ID(prjts_rsp->fchdr, fchdr->fh_d_id); + FNIC_STD_SET_D_ID(prjts_rsp->fchdr, fchdr->fh_s_id); + oxid = FNIC_STD_GET_OX_ID(fchdr); + FNIC_STD_SET_OX_ID(prjts_rsp->fchdr, oxid); + + fnic_send_fcoe_frame(iport, rjt_frame, rjt_frame_size); + return; + } + + acc_frame = fdls_alloc_frame(iport); + if (acc_frame == NULL) { + FNIC_FCS_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send ADISC accept"); + return; + } + + padisc_acc = (struct fc_std_els_adisc *) (acc_frame + FNIC_ETH_FCOE_HDRS_OFFSET); + + FNIC_STD_SET_S_ID(padisc_acc->fchdr, fchdr->fh_d_id); + FNIC_STD_SET_D_ID(padisc_acc->fchdr, fchdr->fh_s_id); + + FNIC_STD_SET_F_CTL(padisc_acc->fchdr, FNIC_ELS_REP_FCTL << 16); + FNIC_STD_SET_R_CTL(padisc_acc->fchdr, FC_RCTL_ELS_REP); + FNIC_STD_SET_TYPE(padisc_acc->fchdr, FC_TYPE_ELS); + + oxid = FNIC_STD_GET_OX_ID(fchdr); + FNIC_STD_SET_OX_ID(padisc_acc->fchdr, oxid); + FNIC_STD_SET_RX_ID(padisc_acc->fchdr, FNIC_UNASSIGNED_RXID); + + padisc_acc->els.adisc_cmd = ELS_LS_ACC; + + FNIC_STD_SET_NPORT_NAME(&padisc_acc->els.adisc_wwpn, + iport->wwpn); + FNIC_STD_SET_NODE_NAME(&padisc_acc->els.adisc_wwnn, + iport->wwnn); + memcpy(padisc_acc->els.adisc_port_id, fchdr->fh_d_id, 3); + + fnic_send_fcoe_frame(iport, acc_frame, acc_frame_size); +} + +/* + * Performs a validation for all FCOE frames and return the frame type + */ +int +fnic_fdls_validate_and_get_frame_type(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + uint8_t type; + uint8_t *fc_payload; + uint16_t oxid; + uint32_t s_id; + uint32_t d_id; + struct fnic *fnic = iport->fnic; + struct fnic_fdls_fabric_s *fabric = &iport->fabric; + int oxid_frame_type; + + oxid = FNIC_STD_GET_OX_ID(fchdr); + fc_payload = (uint8_t *) fchdr + sizeof(struct fc_frame_header); + type = *fc_payload; + s_id = ntoh24(fchdr->fh_s_id); + d_id = ntoh24(fchdr->fh_d_id); + + /* some common validation */ + if (fdls_get_state(fabric) > FDLS_STATE_FABRIC_FLOGI) { + if (iport->fcid != d_id || (!FNIC_FC_FRAME_CS_CTL(fchdr))) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "invalid frame received. Dropping frame"); + return -1; + } + } + + /* BLS ABTS response */ + if ((fchdr->fh_r_ctl == FC_RCTL_BA_ACC) + || (fchdr->fh_r_ctl == FC_RCTL_BA_RJT)) { + if (!(FNIC_FC_FRAME_TYPE_BLS(fchdr))) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received ABTS invalid frame. Dropping frame"); + return -1; + + } + if (fdls_is_oxid_fabric_req(oxid)) { + if (!(iport->fabric.flags & FNIC_FDLS_FABRIC_ABORT_ISSUED)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received unexpected ABTS RSP(oxid:0x%x) from 0x%x. Dropping frame", + oxid, s_id); + return -1; + } + return FNIC_FABRIC_BLS_ABTS_RSP; + } else if (fdls_is_oxid_fdmi_req(oxid)) { + return FNIC_FDMI_BLS_ABTS_RSP; + } else if (fdls_is_oxid_tgt_req(oxid)) { + return FNIC_TPORT_BLS_ABTS_RSP; + } else if (fdls_is_oxid_nvme_req(oxid)) { + return FNIC_LS_REQ_ABTS_RSP; + } + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received ABTS rsp with unknown oxid(0x%x) from 0x%x. Dropping frame", + oxid, s_id); + return -1; + } + + /* BLS ABTS Req */ + if ((fchdr->fh_r_ctl == FC_RCTL_BA_ABTS) + && (FNIC_FC_FRAME_TYPE_BLS(fchdr))) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Receiving Abort Request from s_id: 0x%x", s_id); + return FNIC_BLS_ABTS_REQ; + } + + /* unsolicited requests frames */ + if (FNIC_FC_FRAME_UNSOLICITED(fchdr)) { + switch (type) { + case ELS_LOGO: + if ((!FNIC_FC_FRAME_FCTL_FIRST_LAST_SEQINIT(fchdr)) + || (!FNIC_FC_FRAME_UNSOLICITED(fchdr)) + || (!FNIC_FC_FRAME_TYPE_ELS(fchdr))) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received LOGO invalid frame. Dropping frame"); + return -1; + } + return FNIC_ELS_LOGO_REQ; + case ELS_RSCN: + if ((!FNIC_FC_FRAME_FCTL_FIRST_LAST_SEQINIT(fchdr)) + || (!FNIC_FC_FRAME_TYPE_ELS(fchdr)) + || (!FNIC_FC_FRAME_UNSOLICITED(fchdr))) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received RSCN invalid FCTL. Dropping frame"); + return -1; + } + if (s_id != FC_FID_FCTRL) + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received RSCN from target FCTL: 0x%x type: 0x%x s_id: 0x%x.", + fchdr->fh_f_ctl[0], fchdr->fh_type, s_id); + return FNIC_ELS_RSCN_REQ; + case ELS_PLOGI: + return FNIC_ELS_PLOGI_REQ; + case ELS_ECHO: + return FNIC_ELS_ECHO_REQ; + case ELS_ADISC: + return FNIC_ELS_ADISC; + case ELS_RLS: + return FNIC_ELS_RLS; + case ELS_RRQ: + return FNIC_ELS_RRQ; + default: + FNIC_FCS_DBG(KERN_INFO, fnic, + "Unsupported frame (type:0x%02x) from fcid: 0x%x", + type, s_id); + return FNIC_ELS_UNSUPPORTED_REQ; + } + } + + /* solicited response from fabric or target */ + oxid_frame_type = FNIC_FRAME_TYPE(oxid); + FNIC_FCS_DBG(KERN_INFO, fnic, + "oxid frame code: 0x%x, oxid: 0x%x\n", oxid_frame_type, oxid); + switch (oxid_frame_type) { + case FNIC_FRAME_TYPE_FABRIC_FLOGI: + if (type == ELS_LS_ACC) { + if ((s_id != FC_FID_FLOGI) + || (!FNIC_FC_FRAME_TYPE_ELS(fchdr))) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received unknown frame. Dropping frame"); + return -1; + } + } + return FNIC_FABRIC_FLOGI_RSP; + + case FNIC_FRAME_TYPE_FABRIC_PLOGI: + if (type == ELS_LS_ACC) { + if ((s_id != FC_FID_DIR_SERV) + || (!FNIC_FC_FRAME_TYPE_ELS(fchdr))) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received unknown frame. Dropping frame"); + return -1; + } + } + return FNIC_FABRIC_PLOGI_RSP; + + case FNIC_FRAME_TYPE_FABRIC_SCR: + if (type == ELS_LS_ACC) { + if ((s_id != FC_FID_FCTRL) + || (!FNIC_FC_FRAME_TYPE_ELS(fchdr))) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received unknown frame. Dropping frame"); + return -1; + } + } + return FNIC_FABRIC_SCR_RSP; + + case FNIC_FRAME_TYPE_FABRIC_RPN: + if ((s_id != FC_FID_DIR_SERV) || (!FNIC_FC_FRAME_TYPE_FC_GS(fchdr))) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received unknown frame. Dropping frame"); + return -1; + } + return FNIC_FABRIC_RPN_RSP; + + case FNIC_FRAME_TYPE_FABRIC_RFT: + if ((s_id != FC_FID_DIR_SERV) || (!FNIC_FC_FRAME_TYPE_FC_GS(fchdr))) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received unknown frame. Dropping frame"); + return -1; + } + return FNIC_FABRIC_RFT_RSP; + + case FNIC_FRAME_TYPE_FABRIC_RFF: + if ((s_id != FC_FID_DIR_SERV) || (!FNIC_FC_FRAME_TYPE_FC_GS(fchdr))) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received unknown frame. Dropping frame"); + return -1; + } + return FNIC_FABRIC_RFF_RSP; + + case FNIC_FRAME_TYPE_FABRIC_GPN_FT: + if ((s_id != FC_FID_DIR_SERV) || (!FNIC_FC_FRAME_TYPE_FC_GS(fchdr))) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received unknown frame. Dropping frame"); + return -1; + } + return FNIC_FABRIC_GPN_FT_RSP; + + case FNIC_FRAME_TYPE_FABRIC_LOGO: + return FNIC_FABRIC_LOGO_RSP; + case FNIC_FRAME_TYPE_FDMI_PLOGI: + return FNIC_FDMI_PLOGI_RSP; + case FNIC_FRAME_TYPE_FDMI_RHBA: + return FNIC_FDMI_REG_HBA_RSP; + case FNIC_FRAME_TYPE_FDMI_RPA: + return FNIC_FDMI_RPA_RSP; + case FNIC_FRAME_TYPE_TGT_PLOGI: + return FNIC_TPORT_PLOGI_RSP; + case FNIC_FRAME_TYPE_TGT_PRLI: + return FNIC_TPORT_PRLI_RSP; + case FNIC_FRAME_TYPE_TGT_ADISC: + return FNIC_TPORT_ADISC_RSP; + case FNIC_FRAME_TYPE_TGT_LOGO: + if (!FNIC_FC_FRAME_TYPE_ELS(fchdr)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Dropping Unknown frame in tport solicited exchange range type: 0x%x.", + fchdr->fh_type); + return -1; + } + return FNIC_TPORT_LOGO_RSP; + default: + /* Drop the Rx frame and log/stats it */ + FNIC_FCS_DBG(KERN_INFO, fnic, + "Solicited response: unknown OXID: 0x%x", oxid); + return -1; + } + + return -1; +} + +void fnic_fdls_recv_frame(struct fnic_iport_s *iport, void *rx_frame, + int len, int fchdr_offset) +{ + struct fc_frame_header *fchdr; + uint32_t s_id = 0; + uint32_t d_id = 0; + struct fnic *fnic = iport->fnic; + int frame_type; + + fchdr = (struct fc_frame_header *) ((uint8_t *) rx_frame + fchdr_offset); + s_id = ntoh24(fchdr->fh_s_id); + d_id = ntoh24(fchdr->fh_d_id); + + fnic_debug_dump_fc_frame(fnic, fchdr, len, "Incoming"); + + frame_type = + fnic_fdls_validate_and_get_frame_type(iport, fchdr); + + /*if we are in flogo drop everything else */ + if (iport->fabric.state == FDLS_STATE_FABRIC_LOGO && + frame_type != FNIC_FABRIC_LOGO_RSP) + return; + + switch (frame_type) { + case FNIC_FABRIC_FLOGI_RSP: + fdls_process_flogi_rsp(iport, fchdr, rx_frame); + break; + case FNIC_FABRIC_PLOGI_RSP: + fdls_process_fabric_plogi_rsp(iport, fchdr); + break; + case FNIC_FDMI_PLOGI_RSP: + fdls_process_fdmi_plogi_rsp(iport, fchdr); + break; + case FNIC_FABRIC_RPN_RSP: + fdls_process_rpn_id_rsp(iport, fchdr); + break; + case FNIC_FABRIC_RFT_RSP: + fdls_process_rft_id_rsp(iport, fchdr); + break; + case FNIC_FABRIC_RFF_RSP: + fdls_process_rff_id_rsp(iport, fchdr); + break; + case FNIC_FABRIC_SCR_RSP: + fdls_process_scr_rsp(iport, fchdr); + break; + case FNIC_FABRIC_GPN_FT_RSP: + fdls_process_gpn_ft_rsp(iport, fchdr, len); + break; + case FNIC_TPORT_PLOGI_RSP: + fdls_process_tgt_plogi_rsp(iport, fchdr); + break; + case FNIC_TPORT_PRLI_RSP: + fdls_process_tgt_prli_rsp(iport, fchdr); + break; + case FNIC_TPORT_ADISC_RSP: + fdls_process_tgt_adisc_rsp(iport, fchdr); + break; + case FNIC_TPORT_BLS_ABTS_RSP: + fdls_process_tgt_abts_rsp(iport, fchdr); + break; + case FNIC_TPORT_LOGO_RSP: + /* Logo response from tgt which we have deleted */ + FNIC_FCS_DBG(KERN_INFO, fnic, + "Logo response from tgt: 0x%x", + ntoh24(fchdr->fh_s_id)); + break; + case FNIC_FABRIC_LOGO_RSP: + fdls_process_fabric_logo_rsp(iport, fchdr); + break; + case FNIC_FABRIC_BLS_ABTS_RSP: + fdls_process_fabric_abts_rsp(iport, fchdr); + break; + case FNIC_LS_REQ_ABTS_RSP: + nvfnic_process_ls_abts_rsp(iport, fchdr); + break; + case FNIC_FDMI_BLS_ABTS_RSP: + fdls_process_fdmi_abts_rsp(iport, fchdr); + break; + case FNIC_BLS_ABTS_REQ: + fdls_process_abts_req(iport, fchdr); + break; + case FNIC_ELS_UNSUPPORTED_REQ: + fdls_process_unsupported_els_req(iport, fchdr); + break; + case FNIC_ELS_PLOGI_REQ: + fdls_process_plogi_req(iport, fchdr); + break; + case FNIC_ELS_RSCN_REQ: + fdls_process_rscn(iport, fchdr); + break; + case FNIC_ELS_LOGO_REQ: + fdls_process_logo_req(iport, fchdr); + break; + case FNIC_ELS_RRQ: + case FNIC_ELS_ECHO_REQ: + fdls_process_els_req(iport, fchdr, len); + break; + case FNIC_ELS_ADISC: + fdls_process_adisc_req(iport, fchdr); + break; + case FNIC_ELS_RLS: + fdls_process_rls_req(iport, fchdr); + break; + case FNIC_FDMI_REG_HBA_RSP: + case FNIC_FDMI_RPA_RSP: + fdls_process_fdmi_reg_ack(iport, fchdr, frame_type); + break; + default: + FNIC_FCS_DBG(KERN_INFO, fnic, + "s_id: 0x%x d_did: 0x%x", s_id, d_id); + FNIC_FCS_DBG(KERN_INFO, fnic, + "Received unknown FCoE frame of len: %d. Dropping frame", len); + break; + } +} + +void fnic_fdls_disc_init(struct fnic_iport_s *iport) +{ + fdls_reset_oxid_pool(iport); + fdls_set_state((&iport->fabric), FDLS_STATE_INIT); +} + +void fnic_fdls_link_down(struct fnic_iport_s *iport) +{ + struct fnic_tport_s *tport, *next; + struct fnic *fnic = iport->fnic; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS processing link down", iport->fcid); + + fdls_set_state((&iport->fabric), FDLS_STATE_LINKDOWN); + iport->fabric.flags = 0; + + if (IS_FNIC_FCP_INITIATOR(fnic) || + IS_FNIC_NVME_INITIATOR(fnic)) { + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + fnic_fcpio_reset(iport->fnic); + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + list_for_each_entry_safe(tport, next, &iport->tport_list, links) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "removing rport: 0x%x", tport->fcid); + fdls_delete_tport(iport, tport); + } + } + fdls_reset_oxid_pool(iport); + + if (fnic_fdmi_support == 1) { + if (iport->fabric.fdmi_pending > 0) { + timer_delete_sync(&iport->fabric.fdmi_timer); + iport->fabric.fdmi_pending = 0; + } + iport->flags &= ~FNIC_FDMI_ACTIVE; + } + + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: FDLS finish processing link down", iport->fcid); +} diff --git a/drivers/scsi/fnic/fdls_fc.h b/drivers/scsi/fnic/fdls_fc.h new file mode 100644 index 000000000..cdf84462d --- /dev/null +++ b/drivers/scsi/fnic/fdls_fc.h @@ -0,0 +1,260 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ + +#ifndef _FDLS_FC_H_ +#define _FDLS_FC_H_ + +/* This file contains the declarations for FC fabric services + * and target discovery + * + * Request and Response for + * 1. FLOGI + * 2. PLOGI to Fabric Controller + * 3. GPN_ID, GPN_FT + * 4. RSCN + * 5. PLOGI to Target + * 6. PRLI to Target + */ + +#include <scsi/scsi.h> +#include <scsi/fc/fc_els.h> +#include <uapi/scsi/fc/fc_fs.h> +#include <uapi/scsi/fc/fc_ns.h> +#include <uapi/scsi/fc/fc_gs.h> +#include <uapi/linux/if_ether.h> +#include <scsi/fc/fc_ms.h> +#include <linux/minmax.h> +#include <linux/if_ether.h> +#include <scsi/fc/fc_encaps.h> +#include <scsi/fc/fc_fcoe.h> +#include <linux/nvme.h> +#include <linux/nvme-fc.h> +#include <linux/nvme-fc-driver.h> + +#define FDLS_MIN_FRAMES (32) +#define FDLS_MIN_FRAME_ELEM (4) +#define FNIC_FCP_SP_RD_XRDY_DIS 0x00000002 +#define FNIC_FCP_SP_TARGET 0x00000010 +#define FNIC_FCP_SP_INITIATOR 0x00000020 +#define FNIC_FCP_SP_CONF_CMPL 0x00000080 +#define FNIC_FCP_SP_RETRY 0x00000100 + +#define FNIC_NVME_SP_INITIATOR 0x00000020 +#define FNIC_NVME_SP_SLER 0x00000100 + +#define FNIC_FC_CONCUR_SEQS (0xFF) +#define FNIC_FC_RO_INFO (0x1F) + +/* Little Endian */ +#define FNIC_UNASSIGNED_OXID (0xffff) +#define FNIC_UNASSIGNED_RXID (0xffff) +#define FNIC_ELS_REQ_FCTL (0x000029) +#define FNIC_ELS_REP_FCTL (0x000099) + +#define FNIC_FCP_RSP_FCTL (0x000099) +#define FNIC_REQ_ABTS_FCTL (0x000009) +#define FNIC_NVME_LS_REQ_FCTL (0x000029) + +#define FNIC_FC_PH_VER_HI (0x20) +#define FNIC_FC_PH_VER_LO (0x20) +#define FNIC_FC_PH_VER (0x2020) +#define FNIC_FC_B2B_CREDIT (0x0A) +#define FNIC_FC_B2B_RDF_SZ (0x0800) + +#define FNIC_LOGI_RDF_SIZE(_logi) ((_logi).fl_csp.sp_bb_data) +#define FNIC_LOGI_R_A_TOV(_logi) ((_logi).fl_csp.sp_r_a_tov) +#define FNIC_LOGI_E_D_TOV(_logi) ((_logi).fl_csp.sp_e_d_tov) +#define FNIC_LOGI_FEATURES(_logi) (be16_to_cpu((_logi).fl_csp.sp_features)) +#define FNIC_LOGI_PORT_NAME(_logi) ((_logi).fl_wwpn) +#define FNIC_LOGI_NODE_NAME(_logi) ((_logi).fl_wwnn) + +#define FNIC_LOGI_SET_RDF_SIZE(_logi, _rdf_size) \ + (FNIC_LOGI_RDF_SIZE(_logi) = cpu_to_be16(_rdf_size)) +#define FNIC_LOGI_SET_E_D_TOV(_logi, _e_d_tov) \ + (FNIC_LOGI_E_D_TOV(_logi) = cpu_to_be32(_e_d_tov)) +#define FNIC_LOGI_SET_R_A_TOV(_logi, _r_a_tov) \ + (FNIC_LOGI_R_A_TOV(_logi) = cpu_to_be32(_r_a_tov)) + +#define FNIC_STD_SET_S_ID(_fchdr, _sid) memcpy((_fchdr).fh_s_id, _sid, 3) +#define FNIC_STD_SET_D_ID(_fchdr, _did) memcpy((_fchdr).fh_d_id, _did, 3) +#define FNIC_STD_SET_OX_ID(_fchdr, _oxid) ((_fchdr).fh_ox_id = cpu_to_be16(_oxid)) +#define FNIC_STD_SET_RX_ID(_fchdr, _rxid) ((_fchdr).fh_rx_id = cpu_to_be16(_rxid)) + +#define FNIC_STD_SET_R_CTL(_fchdr, _rctl) ((_fchdr).fh_r_ctl = _rctl) +#define FNIC_STD_SET_TYPE(_fchdr, _type) ((_fchdr).fh_type = _type) +#define FNIC_STD_SET_F_CTL(_fchdr, _fctl) \ + put_unaligned_be24(_fctl, &((_fchdr).fh_f_ctl)) + +#define FNIC_STD_SET_NPORT_NAME(_ptr, _wwpn) put_unaligned_be64(_wwpn, _ptr) +#define FNIC_STD_SET_NODE_NAME(_ptr, _wwnn) put_unaligned_be64(_wwnn, _ptr) +#define FNIC_STD_SET_PORT_ID(__req, __portid) \ + memcpy(__req.fr_fid.fp_fid, __portid, 3) +#define FNIC_STD_SET_PORT_NAME(_req, _pName) \ + (put_unaligned_be64(_pName, &_req.fr_wwn)) + +#define FNIC_STD_GET_OX_ID(_fchdr) (be16_to_cpu((_fchdr)->fh_ox_id)) +#define FNIC_STD_GET_RX_ID(_fchdr) (be16_to_cpu((_fchdr)->fh_rx_id)) +#define FNIC_STD_GET_S_ID(_fchdr) ((_fchdr)->fh_s_id) +#define FNIC_STD_GET_D_ID(_fchdr) ((_fchdr)->fh_d_id) +#define FNIC_STD_GET_TYPE(_fchdr) ((_fchdr)->fh_type) +#define FNIC_STD_GET_F_CTL(_fchdr) ((_fchdr)->fh_f_ctl) +#define FNIC_STD_GET_R_CTL(_fchdr) ((_fchdr)->fh_r_ctl) + +#define FNIC_STD_GET_FC_CT_CMD(__fcct_hdr) (be16_to_cpu(__fcct_hdr->ct_cmd)) + +#define FNIC_FCOE_MAX_FRAME_SZ (2048) +#define FNIC_FCOE_MIN_FRAME_SZ (280) +#define FNIC_FC_MAX_PAYLOAD_LEN (2048) +#define FNIC_MIN_DATA_FIELD_SIZE (256) + +#define FNIC_FC_EDTOV_NSEC (0x400) +#define FNIC_NSEC_TO_MSEC (0x1000000) +#define FCP_PRLI_FUNC_TARGET (0x0010) + +#define FNIC_FC_R_CTL_SOLICITED_DATA (0x21) +#define FNIC_FC_F_CTL_LAST_END_SEQ (0x98) +#define FNIC_FC_F_CTL_LAST_END_SEQ_INT (0x99) +#define FNIC_FC_F_CTL_FIRST_LAST_SEQINIT (0x29) +#define FNIC_FC_R_CTL_FC4_SCTL (0x03) +#define FNIC_FC_CS_CTL (0x00) + +#define FNIC_FC_FRAME_UNSOLICITED(_fchdr) \ + (_fchdr->fh_r_ctl == FC_RCTL_ELS_REQ) +#define FNIC_FC_FRAME_SOLICITED_DATA(_fchdr) \ + (_fchdr->fh_r_ctl == FNIC_FC_R_CTL_SOLICITED_DATA) +#define FNIC_FC_FRAME_SOLICITED_CTRL_REPLY(_fchdr) \ + (_fchdr->fh_r_ctl == FC_RCTL_ELS_REP) +#define FNIC_FC_FRAME_FCTL_LAST_END_SEQ(_fchdr) \ + (_fchdr->fh_f_ctl[0] == FNIC_FC_F_CTL_LAST_END_SEQ) +#define FNIC_FC_FRAME_FCTL_LAST_END_SEQ_INT(_fchdr) \ + (_fchdr->fh_f_ctl[0] == FNIC_FC_F_CTL_LAST_END_SEQ_INT) +#define FNIC_FC_FRAME_FCTL_FIRST_LAST_SEQINIT(_fchdr) \ + (_fchdr->fh_f_ctl[0] == FNIC_FC_F_CTL_FIRST_LAST_SEQINIT) +#define FNIC_FC_FRAME_FC4_SCTL(_fchdr) \ + (_fchdr->fh_r_ctl == FNIC_FC_R_CTL_FC4_SCTL) +#define FNIC_FC_FRAME_TYPE_BLS(_fchdr) (_fchdr->fh_type == FC_TYPE_BLS) +#define FNIC_FC_FRAME_TYPE_ELS(_fchdr) (_fchdr->fh_type == FC_TYPE_ELS) +#define FNIC_FC_FRAME_TYPE_FC_GS(_fchdr) (_fchdr->fh_type == FC_TYPE_CT) +#define FNIC_FC_FRAME_CS_CTL(_fchdr) (_fchdr->fh_cs_ctl == FNIC_FC_CS_CTL) + +#define FNIC_FC_C3_RDF (0xfff) +#define FNIC_FC_PLOGI_RSP_RDF(_plogi_rsp) \ + (min(_plogi_rsp->u.csp_plogi.b2b_rdf_size, \ + (_plogi_rsp->spc3[4] & FNIC_FC_C3_RDF))) +#define FNIC_FC_PLOGI_RSP_CONCUR_SEQ(_plogi_rsp) \ + (min((uint16_t) (be16_to_cpu(_plogi_rsp->els.fl_csp.sp_tot_seq)), \ + (uint16_t) (be16_to_cpu(_plogi_rsp->els.fl_cssp[2].cp_con_seq) & 0xff))) + +/* FLOGI/PLOGI struct */ +struct fc_std_flogi { + struct fc_frame_header fchdr; + struct fc_els_flogi els; +} __packed; + +struct fc_std_els_acc_rsp { + struct fc_frame_header fchdr; + struct fc_els_ls_acc acc; +} __packed; + +struct fc_std_els_rjt_rsp { + struct fc_frame_header fchdr; + struct fc_els_ls_rjt rej; +} __packed; + +struct fc_std_els_adisc { + struct fc_frame_header fchdr; + struct fc_els_adisc els; +} __packed; + +struct fc_std_rls_acc { + struct fc_frame_header fchdr; + struct fc_els_rls_resp els; +} __packed; + +struct fc_std_abts_ba_acc { + struct fc_frame_header fchdr; + struct fc_ba_acc acc; +} __packed; + +struct fc_std_abts_ba_rjt { + struct fc_frame_header fchdr; + struct fc_ba_rjt rjt; +} __packed; + +struct fc_std_els_prli { + struct fc_frame_header fchdr; + struct fc_els_prli els_prli; + struct fc_els_spp sp; +} __packed; + +struct fc_std_rpn_id { + struct fc_frame_header fchdr; + struct fc_ct_hdr fc_std_ct_hdr; + struct fc_ns_rn_id rpn_id; +} __packed; + +struct fc_std_fdmi_rhba { + struct fc_frame_header fchdr; + struct fc_ct_hdr fc_std_ct_hdr; + struct fc_fdmi_rhba rhba; +} __packed; + +struct fc_std_fdmi_rpa { + struct fc_frame_header fchdr; + struct fc_ct_hdr fc_std_ct_hdr; + struct fc_fdmi_rpa rpa; +} __packed; + +struct fc_std_rft_id { + struct fc_frame_header fchdr; + struct fc_ct_hdr fc_std_ct_hdr; + struct fc_ns_rft_id rft_id; +} __packed; + +struct fc_std_rff_id { + struct fc_frame_header fchdr; + struct fc_ct_hdr fc_std_ct_hdr; + struct fc_ns_rff_id rff_id; +} __packed; + +struct fc_std_gpn_ft { + struct fc_frame_header fchdr; + struct fc_ct_hdr fc_std_ct_hdr; + struct fc_ns_gid_ft gpn_ft; +} __packed; + +/* Accept CT_IU for GPN_FT */ +struct fc_gpn_ft_rsp_iu { + uint8_t ctrl; + uint8_t fcid[3]; + uint32_t rsvd; + __be64 wwpn; +} __packed; + +struct fc_std_rls { + struct fc_frame_header fchdr; + struct fc_els_rls els; +} __packed; + +struct fc_std_scr { + struct fc_frame_header fchdr; + struct fc_els_scr scr; +} __packed; + +struct fc_std_rscn { + struct fc_frame_header fchdr; + struct fc_els_rscn els; +} __packed; + +struct fc_std_logo { + struct fc_frame_header fchdr; + struct fc_els_logo els; +} __packed; + +#define FNIC_ETH_FCOE_HDRS_OFFSET \ + (sizeof(struct ethhdr) + sizeof(struct fcoe_hdr)) + +#endif /* _FDLS_FC_H */ diff --git a/drivers/scsi/fnic/fip.c b/drivers/scsi/fnic/fip.c new file mode 100644 index 000000000..231dec180 --- /dev/null +++ b/drivers/scsi/fnic/fip.c @@ -0,0 +1,996 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#include "fnic.h" +#include "fip.h" +#include <linux/etherdevice.h> + +#define FIP_FNIC_RESET_WAIT_COUNT 15 + +/** + * fnic_fcoe_reset_vlans - Free up the list of discovered vlans + * @fnic: Handle to fnic driver instance + */ +void fnic_fcoe_reset_vlans(struct fnic *fnic) +{ + unsigned long flags; + struct fcoe_vlan *vlan, *next; + + spin_lock_irqsave(&fnic->vlans_lock, flags); + if (!list_empty(&fnic->vlan_list)) { + list_for_each_entry_safe(vlan, next, &fnic->vlan_list, list) { + list_del(&vlan->list); + kfree(vlan); + } + } + + spin_unlock_irqrestore(&fnic->vlans_lock, flags); + FNIC_FIP_DBG(KERN_INFO, fnic, + "Reset vlan complete\n"); +} + +/** + * fnic_fcoe_send_vlan_req - Send FIP vlan request to all FCFs MAC + * @fnic: Handle to fnic driver instance + */ +void fnic_fcoe_send_vlan_req(struct fnic *fnic) +{ + uint8_t *frame; + struct fnic_iport_s *iport = &fnic->iport; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + u64 vlan_tov; + struct fip_vlan_req *pvlan_req; + uint16_t frame_size = sizeof(struct fip_vlan_req); + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FIP_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send VLAN req"); + return; + } + + fnic_fcoe_reset_vlans(fnic); + + fnic->set_vlan(fnic, 0); + FNIC_FIP_DBG(KERN_INFO, fnic, + "set vlan done\n"); + + FNIC_FIP_DBG(KERN_INFO, fnic, + "got MAC 0x%x:%x:%x:%x:%x:%x\n", iport->hwmac[0], + iport->hwmac[1], iport->hwmac[2], iport->hwmac[3], + iport->hwmac[4], iport->hwmac[5]); + + pvlan_req = (struct fip_vlan_req *) frame; + *pvlan_req = (struct fip_vlan_req) { + .eth = {.h_dest = FCOE_ALL_FCFS_MAC, + .h_proto = cpu_to_be16(ETH_P_FIP)}, + .fip = {.fip_ver = FIP_VER_ENCAPS(FIP_VER), + .fip_op = cpu_to_be16(FIP_OP_VLAN), + .fip_subcode = FIP_SC_REQ, + .fip_dl_len = cpu_to_be16(FIP_VLAN_REQ_LEN)}, + .mac_desc = {.fd_desc = {.fip_dtype = FIP_DT_MAC, + .fip_dlen = 2}} + }; + + memcpy(pvlan_req->eth.h_source, iport->hwmac, ETH_ALEN); + memcpy(pvlan_req->mac_desc.fd_mac, iport->hwmac, ETH_ALEN); + + atomic64_inc(&fnic_stats->vlan_stats.vlan_disc_reqs); + + iport->fip.state = FDLS_FIP_VLAN_DISCOVERY_STARTED; + + FNIC_FIP_DBG(KERN_INFO, fnic, + "Send VLAN req\n"); + fnic_send_fip_frame(iport, frame, frame_size); + + vlan_tov = jiffies + msecs_to_jiffies(FCOE_CTLR_FIPVLAN_TOV); + mod_timer(&fnic->retry_fip_timer, round_jiffies(vlan_tov)); + FNIC_FIP_DBG(KERN_INFO, fnic, + "fip timer set\n"); +} + +/** + * fnic_fcoe_process_vlan_resp - Processes the vlan response from one FCF and + * populates VLAN list. + * @fnic: Handle to fnic driver instance + * @fiph: Received FIP frame + * + * Will wait for responses from multiple FCFs until timeout. + */ +void fnic_fcoe_process_vlan_resp(struct fnic *fnic, struct fip_header *fiph) +{ + struct fip_vlan_notif *vlan_notif = (struct fip_vlan_notif *)fiph; + + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + u16 vid; + int num_vlan = 0; + int cur_desc, desc_len; + struct fcoe_vlan *vlan; + struct fip_vlan_desc *vlan_desc; + unsigned long flags; + + FNIC_FIP_DBG(KERN_INFO, fnic, + "fnic 0x%p got vlan resp\n", fnic); + + desc_len = be16_to_cpu(vlan_notif->fip.fip_dl_len); + FNIC_FIP_DBG(KERN_INFO, fnic, + "desc_len %d\n", desc_len); + + spin_lock_irqsave(&fnic->vlans_lock, flags); + + cur_desc = 0; + while (desc_len > 0) { + vlan_desc = + (struct fip_vlan_desc *)(((char *)vlan_notif->vlans_desc) + + cur_desc * 4); + + if (vlan_desc->fd_desc.fip_dtype == FIP_DT_VLAN) { + if (vlan_desc->fd_desc.fip_dlen != 1) { + FNIC_FIP_DBG(KERN_INFO, fnic, + "Invalid descriptor length(%x) in VLan response\n", + vlan_desc->fd_desc.fip_dlen); + + } + num_vlan++; + vid = be16_to_cpu(vlan_desc->fd_vlan); + FNIC_FIP_DBG(KERN_INFO, fnic, + "process_vlan_resp: FIP VLAN %d\n", vid); + vlan = kzalloc_obj(*vlan, GFP_ATOMIC); + + if (!vlan) { + /* retry from timer */ + FNIC_FIP_DBG(KERN_INFO, fnic, + "Mem Alloc failure\n"); + spin_unlock_irqrestore(&fnic->vlans_lock, + flags); + goto out; + } + vlan->vid = vid & 0x0fff; + vlan->state = FIP_VLAN_AVAIL; + list_add_tail(&vlan->list, &fnic->vlan_list); + break; + } + /* + * Note : skip any type=2 descriptor here + * (i.e. FIP MAC Address Descriptor) + */ + if (vlan_desc->fd_desc.fip_dtype != FIP_DT_MAC) + FNIC_FIP_DBG(KERN_INFO, fnic, + "Invalid descriptor type(0x%x) in vlan response\n", + vlan_desc->fd_desc.fip_dtype); + cur_desc += vlan_desc->fd_desc.fip_dlen; + desc_len -= vlan_desc->fd_desc.fip_dlen; + } + + /* any VLAN descriptors present ? */ + if (num_vlan == 0) { + atomic64_inc(&fnic_stats->vlan_stats.resp_withno_vlanID); + FNIC_FIP_DBG(KERN_INFO, fnic, + "fnic 0x%p No VLAN descriptors in FIP VLAN response\n", + fnic); + } + + spin_unlock_irqrestore(&fnic->vlans_lock, flags); + + out: + return; +} + +/** + * fnic_fcoe_start_fcf_discovery - Start FIP FCF discovery in a selected vlan + * @fnic: Handle to fnic driver instance + */ +void fnic_fcoe_start_fcf_discovery(struct fnic *fnic) +{ + uint8_t *frame; + struct fnic_iport_s *iport = &fnic->iport; + u64 fcs_tov; + struct fip_discovery *pdisc_sol; + uint16_t frame_size = sizeof(struct fip_discovery); + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FIP_DBG(KERN_ERR, fnic, + "Failed to allocate frame to start FCF discovery"); + return; + } + + eth_zero_addr(iport->selected_fcf.fcf_mac); + + pdisc_sol = (struct fip_discovery *) frame; + *pdisc_sol = (struct fip_discovery) { + .eth = {.h_dest = FCOE_ALL_FCFS_MAC, + .h_proto = cpu_to_be16(ETH_P_FIP)}, + .fip = { + .fip_ver = FIP_VER_ENCAPS(FIP_VER), .fip_op = cpu_to_be16(FIP_OP_DISC), + .fip_subcode = FIP_SC_REQ, .fip_dl_len = cpu_to_be16(FIP_DISC_SOL_LEN), + .fip_flags = cpu_to_be16(FIP_FL_FPMA)}, + .mac_desc = {.fd_desc = {.fip_dtype = FIP_DT_MAC, .fip_dlen = 2}}, + .name_desc = {.fd_desc = {.fip_dtype = FIP_DT_NAME, .fip_dlen = 3}}, + .fcoe_desc = {.fd_desc = {.fip_dtype = FIP_DT_FCOE_SIZE, .fip_dlen = 1}, + .fd_size = cpu_to_be16(FCOE_MAX_SIZE)} + }; + + memcpy(pdisc_sol->eth.h_source, iport->hwmac, ETH_ALEN); + memcpy(pdisc_sol->mac_desc.fd_mac, iport->hwmac, ETH_ALEN); + iport->selected_fcf.fcf_priority = 0xFF; + + FNIC_STD_SET_NODE_NAME(&pdisc_sol->name_desc.fd_wwn, iport->wwnn); + + FNIC_FIP_DBG(KERN_INFO, fnic, + "Start FCF discovery\n"); + fnic_send_fip_frame(iport, frame, frame_size); + + iport->fip.state = FDLS_FIP_FCF_DISCOVERY_STARTED; + + fcs_tov = jiffies + msecs_to_jiffies(FCOE_CTLR_FCS_TOV); + mod_timer(&fnic->retry_fip_timer, round_jiffies(fcs_tov)); +} + +/** + * fnic_fcoe_fip_discovery_resp - Processes FCF advertisements. + * @fnic: Handle to fnic driver instance + * @fiph: Received frame + * + * FCF advertisements can be: + * solicited - Sent in response of a discover FCF FIP request + * Store the information of the FCF with highest priority. + * Wait until timeout in case of multiple FCFs. + * + * unsolicited - Sent periodically by the FCF for keep alive. + * If FLOGI is in progress or completed and the advertisement is + * received by our selected FCF, refresh the keep alive timer. + */ +void fnic_fcoe_fip_discovery_resp(struct fnic *fnic, struct fip_header *fiph) +{ + struct fnic_iport_s *iport = &fnic->iport; + struct fip_disc_adv *disc_adv = (struct fip_disc_adv *)fiph; + u64 fcs_ka_tov; + u64 tov; + int fka_has_changed; + + switch (iport->fip.state) { + case FDLS_FIP_FCF_DISCOVERY_STARTED: + if (be16_to_cpu(disc_adv->fip.fip_flags) & FIP_FL_SOL) { + FNIC_FIP_DBG(KERN_INFO, fnic, + "fnic 0x%p Solicited adv\n", fnic); + + if ((disc_adv->prio_desc.fd_pri < + iport->selected_fcf.fcf_priority) + && (be16_to_cpu(disc_adv->fip.fip_flags) & FIP_FL_AVAIL)) { + + FNIC_FIP_DBG(KERN_INFO, fnic, + "fnic 0x%p FCF Available\n", fnic); + memcpy(iport->selected_fcf.fcf_mac, + disc_adv->mac_desc.fd_mac, ETH_ALEN); + iport->selected_fcf.fcf_priority = + disc_adv->prio_desc.fd_pri; + iport->selected_fcf.fka_adv_period = + be32_to_cpu(disc_adv->fka_adv_desc.fd_fka_period); + FNIC_FIP_DBG(KERN_INFO, fnic, + "adv time %d", + iport->selected_fcf.fka_adv_period); + iport->selected_fcf.ka_disabled = + (disc_adv->fka_adv_desc.fd_flags & 1); + } + } + break; + case FDLS_FIP_FLOGI_STARTED: + case FDLS_FIP_FLOGI_COMPLETE: + if (!(be16_to_cpu(disc_adv->fip.fip_flags) & FIP_FL_SOL)) { + /* same fcf */ + if (memcmp + (iport->selected_fcf.fcf_mac, + disc_adv->mac_desc.fd_mac, ETH_ALEN) == 0) { + if (iport->selected_fcf.fka_adv_period != + be32_to_cpu(disc_adv->fka_adv_desc.fd_fka_period)) { + iport->selected_fcf.fka_adv_period = + be32_to_cpu(disc_adv->fka_adv_desc.fd_fka_period); + FNIC_FIP_DBG(KERN_INFO, + fnic, + "change fka to %d", + iport->selected_fcf.fka_adv_period); + } + + fka_has_changed = + (iport->selected_fcf.ka_disabled == 1) + && ((disc_adv->fka_adv_desc.fd_flags & 1) == + 0); + + iport->selected_fcf.ka_disabled = + (disc_adv->fka_adv_desc.fd_flags & 1); + if (!((iport->selected_fcf.ka_disabled) + || (iport->selected_fcf.fka_adv_period == + 0))) { + + fcs_ka_tov = jiffies + + 3 + * + msecs_to_jiffies(iport->selected_fcf.fka_adv_period); + mod_timer(&fnic->fcs_ka_timer, + round_jiffies(fcs_ka_tov)); + } else { + if (timer_pending(&fnic->fcs_ka_timer)) + timer_delete_sync(&fnic->fcs_ka_timer); + } + + if (fka_has_changed) { + if (iport->selected_fcf.fka_adv_period != 0) { + tov = + jiffies + + msecs_to_jiffies( + iport->selected_fcf.fka_adv_period); + mod_timer(&fnic->enode_ka_timer, + round_jiffies(tov)); + + tov = + jiffies + + msecs_to_jiffies + (FIP_VN_KA_PERIOD); + mod_timer(&fnic->vn_ka_timer, + round_jiffies(tov)); + } + } + } + } + break; + default: + break; + } /* end switch */ +} + +/** + * fnic_fcoe_start_flogi - Send FIP FLOGI to the selected FCF + * @fnic: Handle to fnic driver instance + */ +void fnic_fcoe_start_flogi(struct fnic *fnic) +{ + uint8_t *frame; + struct fnic_iport_s *iport = &fnic->iport; + struct fip_flogi *pflogi_req; + u64 flogi_tov; + uint16_t oxid; + uint16_t frame_size = sizeof(struct fip_flogi); + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FIP_DBG(KERN_ERR, fnic, + "Failed to allocate frame to start FIP FLOGI"); + return; + } + + pflogi_req = (struct fip_flogi *) frame; + *pflogi_req = (struct fip_flogi) { + .eth = { + .h_proto = cpu_to_be16(ETH_P_FIP)}, + .fip = { + .fip_ver = FIP_VER_ENCAPS(FIP_VER), + .fip_op = cpu_to_be16(FIP_OP_LS), + .fip_subcode = FIP_SC_REQ, + .fip_dl_len = cpu_to_be16(FIP_FLOGI_LEN), + .fip_flags = cpu_to_be16(FIP_FL_FPMA)}, + .flogi_desc = { + .fd_desc = {.fip_dtype = FIP_DT_FLOGI, .fip_dlen = 36}, + .flogi = { + .fchdr = { + .fh_r_ctl = FC_RCTL_ELS_REQ, + .fh_d_id = {0xFF, 0xFF, 0xFE}, + .fh_type = FC_TYPE_ELS, + .fh_f_ctl = {FNIC_ELS_REQ_FCTL, 0, 0}, + .fh_rx_id = cpu_to_be16(FNIC_UNASSIGNED_RXID)}, + .els = { + .fl_cmd = ELS_FLOGI, + .fl_csp = { + .sp_hi_ver = + FNIC_FC_PH_VER_HI, + .sp_lo_ver = + FNIC_FC_PH_VER_LO, + .sp_bb_cred = + cpu_to_be16 + (FNIC_FC_B2B_CREDIT), + .sp_bb_data = + cpu_to_be16 + (FNIC_FC_B2B_RDF_SZ)}, + .fl_cssp[2].cp_class = + cpu_to_be16(FC_CPC_VALID | FC_CPC_SEQ) + }, + } + }, + .mac_desc = {.fd_desc = {.fip_dtype = FIP_DT_MAC, .fip_dlen = 2}} + }; + + memcpy(pflogi_req->eth.h_source, iport->hwmac, ETH_ALEN); + if (iport->usefip) + memcpy(pflogi_req->eth.h_dest, iport->selected_fcf.fcf_mac, + ETH_ALEN); + + oxid = fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_FABRIC_FLOGI, + &iport->active_oxid_fabric_req); + if (oxid == FNIC_UNASSIGNED_OXID) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Failed to allocate OXID to send FIP FLOGI"); + mempool_free(frame, fnic->frame_pool); + return; + } + FNIC_STD_SET_OX_ID(pflogi_req->flogi_desc.flogi.fchdr, oxid); + + FNIC_STD_SET_NPORT_NAME(&pflogi_req->flogi_desc.flogi.els.fl_wwpn, + iport->wwpn); + FNIC_STD_SET_NODE_NAME(&pflogi_req->flogi_desc.flogi.els.fl_wwnn, + iport->wwnn); + + FNIC_FIP_DBG(KERN_INFO, fnic, + "FIP start FLOGI\n"); + fnic_send_fip_frame(iport, frame, frame_size); + iport->fip.flogi_retry++; + + iport->fip.state = FDLS_FIP_FLOGI_STARTED; + flogi_tov = jiffies + msecs_to_jiffies(fnic->config.flogi_timeout); + mod_timer(&fnic->retry_fip_timer, round_jiffies(flogi_tov)); +} + +/** + * fnic_fcoe_process_flogi_resp - Processes FLOGI response from FCF. + * @fnic: Handle to fnic driver instance + * @fiph: Received frame + * + * If successful save assigned fc_id and MAC, program firmware + * and start fdls discovery, else restart vlan discovery. + */ +void fnic_fcoe_process_flogi_resp(struct fnic *fnic, struct fip_header *fiph) +{ + struct fnic_iport_s *iport = &fnic->iport; + struct fip_flogi_rsp *flogi_rsp = (struct fip_flogi_rsp *)fiph; + int desc_len; + uint32_t s_id; + int frame_type; + uint16_t oxid; + + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + struct fc_frame_header *fchdr = &flogi_rsp->rsp_desc.flogi.fchdr; + + FNIC_FIP_DBG(KERN_INFO, fnic, + "fnic 0x%p FIP FLOGI rsp\n", fnic); + desc_len = be16_to_cpu(flogi_rsp->fip.fip_dl_len); + if (desc_len != 38) { + FNIC_FIP_DBG(KERN_INFO, fnic, + "Invalid Descriptor List len (%x). Dropping frame\n", + desc_len); + return; + } + + if (!((flogi_rsp->rsp_desc.fd_desc.fip_dtype == 7) + && (flogi_rsp->rsp_desc.fd_desc.fip_dlen == 36)) + || !((flogi_rsp->mac_desc.fd_desc.fip_dtype == 2) + && (flogi_rsp->mac_desc.fd_desc.fip_dlen == 2))) { + FNIC_FIP_DBG(KERN_INFO, fnic, + "Dropping frame invalid type and len mix\n"); + return; + } + + frame_type = fnic_fdls_validate_and_get_frame_type(iport, fchdr); + + s_id = ntoh24(fchdr->fh_s_id); + if ((fchdr->fh_f_ctl[0] != 0x98) + || (fchdr->fh_r_ctl != 0x23) + || (s_id != FC_FID_FLOGI) + || (frame_type != FNIC_FABRIC_FLOGI_RSP) + || (fchdr->fh_type != 0x01)) { + FNIC_FIP_DBG(KERN_INFO, fnic, + "Dropping invalid frame: s_id %x F %x R %x t %x OX_ID %x\n", + s_id, fchdr->fh_f_ctl[0], fchdr->fh_r_ctl, + fchdr->fh_type, FNIC_STD_GET_OX_ID(fchdr)); + return; + } + + if (iport->fip.state == FDLS_FIP_FLOGI_STARTED) { + FNIC_FIP_DBG(KERN_INFO, fnic, + "fnic 0x%p rsp for pending FLOGI\n", fnic); + + oxid = FNIC_STD_GET_OX_ID(fchdr); + fdls_free_oxid(iport, oxid, &iport->active_oxid_fabric_req); + timer_delete_sync(&fnic->retry_fip_timer); + + if ((be16_to_cpu(flogi_rsp->fip.fip_dl_len) == FIP_FLOGI_LEN) + && (flogi_rsp->rsp_desc.flogi.els.fl_cmd == ELS_LS_ACC)) { + + FNIC_FIP_DBG(KERN_INFO, fnic, + "fnic 0x%p FLOGI success\n", fnic); + memcpy(iport->fpma, flogi_rsp->mac_desc.fd_mac, ETH_ALEN); + iport->fcid = + ntoh24(flogi_rsp->rsp_desc.flogi.fchdr.fh_d_id); + + iport->r_a_tov = + be32_to_cpu(flogi_rsp->rsp_desc.flogi.els.fl_csp.sp_r_a_tov); + iport->e_d_tov = + be32_to_cpu(flogi_rsp->rsp_desc.flogi.els.fl_csp.sp_e_d_tov); + memcpy(fnic->iport.fcfmac, iport->selected_fcf.fcf_mac, + ETH_ALEN); + vnic_dev_add_addr(fnic->vdev, flogi_rsp->mac_desc.fd_mac); + + if (fnic_fdls_register_portid(iport, iport->fcid, NULL) + != 0) { + FNIC_FIP_DBG(KERN_INFO, fnic, + "fnic 0x%p flogi registration failed\n", + fnic); + return; + } + + iport->fip.state = FDLS_FIP_FLOGI_COMPLETE; + iport->state = FNIC_IPORT_STATE_FABRIC_DISC; + FNIC_FIP_DBG(KERN_INFO, fnic, + "iport->state:%d\n", + iport->state); + fnic_fdls_disc_start(iport); + if (!((iport->selected_fcf.ka_disabled) + || (iport->selected_fcf.fka_adv_period == 0))) { + u64 tov; + + tov = jiffies + + + msecs_to_jiffies(iport->selected_fcf.fka_adv_period); + mod_timer(&fnic->enode_ka_timer, + round_jiffies(tov)); + + tov = + jiffies + + msecs_to_jiffies(FIP_VN_KA_PERIOD); + mod_timer(&fnic->vn_ka_timer, + round_jiffies(tov)); + + } + } else { + /* + * If there's FLOGI rejects - clear all + * fcf's & restart from scratch + */ + atomic64_inc(&fnic_stats->vlan_stats.flogi_rejects); + /* start FCoE VLAN discovery */ + fnic_fcoe_send_vlan_req(fnic); + + iport->fip.state = FDLS_FIP_VLAN_DISCOVERY_STARTED; + } + } +} + +/** + * fnic_common_fip_cleanup - Clean up FCF info and timers in case of + * link down/CVL + * @fnic: Handle to fnic driver instance + */ +void fnic_common_fip_cleanup(struct fnic *fnic) +{ + + struct fnic_iport_s *iport = &fnic->iport; + + if (!iport->usefip) + return; + FNIC_FIP_DBG(KERN_INFO, fnic, + "fnic 0x%p fip cleanup\n", fnic); + + iport->fip.state = FDLS_FIP_INIT; + + timer_delete_sync(&fnic->retry_fip_timer); + timer_delete_sync(&fnic->fcs_ka_timer); + timer_delete_sync(&fnic->enode_ka_timer); + timer_delete_sync(&fnic->vn_ka_timer); + + if (!is_zero_ether_addr(iport->fpma)) + vnic_dev_del_addr(fnic->vdev, iport->fpma); + + eth_zero_addr(iport->fpma); + iport->fcid = 0; + iport->r_a_tov = 0; + iport->e_d_tov = 0; + eth_zero_addr(fnic->iport.fcfmac); + eth_zero_addr(iport->selected_fcf.fcf_mac); + iport->selected_fcf.fcf_priority = 0; + iport->selected_fcf.fka_adv_period = 0; + iport->selected_fcf.ka_disabled = 0; + + fnic_fcoe_reset_vlans(fnic); +} + +/** + * fnic_fcoe_process_cvl - Processes Clear Virtual Link from FCF. + * @fnic: Handle to fnic driver instance + * @fiph: Received frame + * + * Verify that cvl is received from our current FCF for our assigned MAC + * and clean up and restart the vlan discovery. + */ +void fnic_fcoe_process_cvl(struct fnic *fnic, struct fip_header *fiph) +{ + struct fnic_iport_s *iport = &fnic->iport; + struct fip_cvl *cvl_msg = (struct fip_cvl *)fiph; + int i; + int found = false; + int max_count = 0; + + FNIC_FIP_DBG(KERN_INFO, fnic, + "fnic 0x%p clear virtual link handler\n", fnic); + + if (!((cvl_msg->fcf_mac_desc.fd_desc.fip_dtype == 2) + && (cvl_msg->fcf_mac_desc.fd_desc.fip_dlen == 2)) + || !((cvl_msg->name_desc.fd_desc.fip_dtype == 4) + && (cvl_msg->name_desc.fd_desc.fip_dlen == 3))) { + + FNIC_FIP_DBG(KERN_INFO, fnic, + "invalid mix: ft %x fl %x ndt %x ndl %x", + cvl_msg->fcf_mac_desc.fd_desc.fip_dtype, + cvl_msg->fcf_mac_desc.fd_desc.fip_dlen, + cvl_msg->name_desc.fd_desc.fip_dtype, + cvl_msg->name_desc.fd_desc.fip_dlen); + } + + if (memcmp + (iport->selected_fcf.fcf_mac, cvl_msg->fcf_mac_desc.fd_mac, ETH_ALEN) + == 0) { + for (i = 0; i < ((be16_to_cpu(fiph->fip_dl_len) / 5) - 1); i++) { + if (!((cvl_msg->vn_ports_desc[i].fd_desc.fip_dtype == 11) + && (cvl_msg->vn_ports_desc[i].fd_desc.fip_dlen == 5))) { + + FNIC_FIP_DBG(KERN_INFO, fnic, + "Invalid type and len mix type: %d len: %d\n", + cvl_msg->vn_ports_desc[i].fd_desc.fip_dtype, + cvl_msg->vn_ports_desc[i].fd_desc.fip_dlen); + } + if (memcmp + (iport->fpma, cvl_msg->vn_ports_desc[i].fd_mac, + ETH_ALEN) == 0) { + found = true; + break; + } + } + if (!found) + return; + fnic_common_fip_cleanup(fnic); + + while (fnic->reset_in_progress == IN_PROGRESS) { + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + wait_for_completion_timeout(&fnic->reset_completion_wait, + msecs_to_jiffies(5000)); + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + max_count++; + if (max_count >= FIP_FNIC_RESET_WAIT_COUNT) { + FNIC_FIP_DBG(KERN_INFO, fnic, + "Rthr waited too long. Skipping handle link event %p\n", + fnic); + return; + } + FNIC_FIP_DBG(KERN_INFO, fnic, + "fnic reset in progress. Link event needs to wait %p", + fnic); + } + fnic->reset_in_progress = IN_PROGRESS; + fnic_fdls_link_down(iport); + fnic->reset_in_progress = NOT_IN_PROGRESS; + complete(&fnic->reset_completion_wait); + fnic_fcoe_send_vlan_req(fnic); + } +} + +/** + * fdls_fip_recv_frame - Demultiplexer for FIP frames + * @fnic: Handle to fnic driver instance + * @frame: Received ethernet frame + */ +int fdls_fip_recv_frame(struct fnic *fnic, void *frame) +{ + struct ethhdr *eth = (struct ethhdr *)frame; + struct fip_header *fiph; + u16 op; + u8 sub; + int len = 2048; + + if (be16_to_cpu(eth->h_proto) == ETH_P_FIP) { + fiph = (struct fip_header *)(eth + 1); + op = be16_to_cpu(fiph->fip_op); + sub = fiph->fip_subcode; + + fnic_debug_dump_fip_frame(fnic, eth, len, "Incoming"); + + if (op == FIP_OP_DISC && sub == FIP_SC_REP) + fnic_fcoe_fip_discovery_resp(fnic, fiph); + else if (op == FIP_OP_VLAN && sub == FIP_SC_REP) + fnic_fcoe_process_vlan_resp(fnic, fiph); + else if (op == FIP_OP_CTRL && sub == FIP_SC_REP) + fnic_fcoe_process_cvl(fnic, fiph); + else if (op == FIP_OP_LS && sub == FIP_SC_REP) + fnic_fcoe_process_flogi_resp(fnic, fiph); + + /* Return true if the frame was a FIP frame */ + return true; + } + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Not a FIP Frame"); + return false; +} + +void fnic_work_on_fip_timer(struct work_struct *work) +{ + struct fnic *fnic = container_of(work, struct fnic, fip_timer_work); + struct fnic_iport_s *iport = &fnic->iport; + + FNIC_FIP_DBG(KERN_INFO, fnic, + "FIP timeout\n"); + + if (iport->fip.state == FDLS_FIP_VLAN_DISCOVERY_STARTED) { + fnic_vlan_discovery_timeout(fnic); + } else if (iport->fip.state == FDLS_FIP_FCF_DISCOVERY_STARTED) { + u8 zmac[ETH_ALEN] = { 0, 0, 0, 0, 0, 0 }; + + FNIC_FIP_DBG(KERN_INFO, fnic, + "FCF Discovery timeout\n"); + if (memcmp(iport->selected_fcf.fcf_mac, zmac, ETH_ALEN) != 0) { + + if (iport->flags & FNIC_FIRST_LINK_UP) { + fnic_fcpio_reset(iport->fnic); + iport->flags &= ~FNIC_FIRST_LINK_UP; + } + + fnic_fcoe_start_flogi(fnic); + if (!((iport->selected_fcf.ka_disabled) + || (iport->selected_fcf.fka_adv_period == 0))) { + u64 fcf_tov; + + fcf_tov = jiffies + + 3 + * + msecs_to_jiffies(iport->selected_fcf.fka_adv_period); + mod_timer(&fnic->fcs_ka_timer, + round_jiffies(fcf_tov)); + } + } else { + FNIC_FIP_DBG(KERN_INFO, fnic, + "FCF Discovery timeout\n"); + fnic_vlan_discovery_timeout(fnic); + } + } else if (iport->fip.state == FDLS_FIP_FLOGI_STARTED) { + fdls_schedule_oxid_free(iport, &iport->active_oxid_fabric_req); + FNIC_FIP_DBG(KERN_INFO, fnic, + "FLOGI timeout\n"); + if (iport->fip.flogi_retry < fnic->config.flogi_retries) + fnic_fcoe_start_flogi(fnic); + else + fnic_vlan_discovery_timeout(fnic); + } +} + +/** + * fnic_handle_fip_timer - Timeout handler for FIP discover phase. + * @t: Handle to the timer list + * + * Based on the current state, start next phase or restart discovery. + */ +void fnic_handle_fip_timer(struct timer_list *t) +{ + struct fnic *fnic = timer_container_of(fnic, t, retry_fip_timer); + + INIT_WORK(&fnic->fip_timer_work, fnic_work_on_fip_timer); + queue_work(fnic_fip_queue, &fnic->fip_timer_work); +} + +/** + * fnic_handle_enode_ka_timer - FIP node keep alive. + * @t: Handle to the timer list + */ +void fnic_handle_enode_ka_timer(struct timer_list *t) +{ + uint8_t *frame; + struct fnic *fnic = timer_container_of(fnic, t, enode_ka_timer); + + struct fnic_iport_s *iport = &fnic->iport; + struct fip_enode_ka *penode_ka; + u64 enode_ka_tov; + uint16_t frame_size = sizeof(struct fip_enode_ka); + + if (iport->fip.state != FDLS_FIP_FLOGI_COMPLETE) + return; + + if ((iport->selected_fcf.ka_disabled) + || (iport->selected_fcf.fka_adv_period == 0)) { + return; + } + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FIP_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send enode ka"); + return; + } + + penode_ka = (struct fip_enode_ka *) frame; + *penode_ka = (struct fip_enode_ka) { + .eth = { + .h_proto = cpu_to_be16(ETH_P_FIP)}, + .fip = { + .fip_ver = FIP_VER_ENCAPS(FIP_VER), + .fip_op = cpu_to_be16(FIP_OP_CTRL), + .fip_subcode = FIP_SC_REQ, + .fip_dl_len = cpu_to_be16(FIP_ENODE_KA_LEN)}, + .mac_desc = {.fd_desc = {.fip_dtype = FIP_DT_MAC, .fip_dlen = 2}} + }; + + memcpy(penode_ka->eth.h_source, iport->hwmac, ETH_ALEN); + memcpy(penode_ka->eth.h_dest, iport->selected_fcf.fcf_mac, ETH_ALEN); + memcpy(penode_ka->mac_desc.fd_mac, iport->hwmac, ETH_ALEN); + + FNIC_FIP_DBG(KERN_DEBUG, fnic, + "Handle enode KA timer\n"); + fnic_send_fip_frame(iport, frame, frame_size); + enode_ka_tov = jiffies + + msecs_to_jiffies(iport->selected_fcf.fka_adv_period); + mod_timer(&fnic->enode_ka_timer, round_jiffies(enode_ka_tov)); +} + +/** + * fnic_handle_vn_ka_timer - FIP virtual port keep alive. + * @t: Handle to the timer list + */ +void fnic_handle_vn_ka_timer(struct timer_list *t) +{ + uint8_t *frame; + struct fnic *fnic = timer_container_of(fnic, t, vn_ka_timer); + + struct fnic_iport_s *iport = &fnic->iport; + struct fip_vn_port_ka *pvn_port_ka; + u64 vn_ka_tov; + uint8_t fcid[3]; + uint16_t frame_size = sizeof(struct fip_vn_port_ka); + + if (iport->fip.state != FDLS_FIP_FLOGI_COMPLETE) + return; + + if ((iport->selected_fcf.ka_disabled) + || (iport->selected_fcf.fka_adv_period == 0)) { + return; + } + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_FIP_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send vn ka"); + return; + } + + pvn_port_ka = (struct fip_vn_port_ka *) frame; + *pvn_port_ka = (struct fip_vn_port_ka) { + .eth = { + .h_proto = cpu_to_be16(ETH_P_FIP)}, + .fip = { + .fip_ver = FIP_VER_ENCAPS(FIP_VER), + .fip_op = cpu_to_be16(FIP_OP_CTRL), + .fip_subcode = FIP_SC_REQ, + .fip_dl_len = cpu_to_be16(FIP_VN_KA_LEN)}, + .mac_desc = {.fd_desc = {.fip_dtype = FIP_DT_MAC, .fip_dlen = 2}}, + .vn_port_desc = {.fd_desc = {.fip_dtype = FIP_DT_VN_ID, .fip_dlen = 5}} + }; + + memcpy(pvn_port_ka->eth.h_source, iport->fpma, ETH_ALEN); + memcpy(pvn_port_ka->eth.h_dest, iport->selected_fcf.fcf_mac, ETH_ALEN); + memcpy(pvn_port_ka->mac_desc.fd_mac, iport->hwmac, ETH_ALEN); + memcpy(pvn_port_ka->vn_port_desc.fd_mac, iport->fpma, ETH_ALEN); + hton24(fcid, iport->fcid); + memcpy(pvn_port_ka->vn_port_desc.fd_fc_id, fcid, 3); + FNIC_STD_SET_NPORT_NAME(&pvn_port_ka->vn_port_desc.fd_wwpn, iport->wwpn); + + FNIC_FIP_DBG(KERN_DEBUG, fnic, + "Handle vnport KA timer\n"); + fnic_send_fip_frame(iport, frame, frame_size); + vn_ka_tov = jiffies + msecs_to_jiffies(FIP_VN_KA_PERIOD); + mod_timer(&fnic->vn_ka_timer, round_jiffies(vn_ka_tov)); +} + +/** + * fnic_vlan_discovery_timeout - Handle vlan discovery timeout + * @fnic: Handle to fnic driver instance + * + * End of VLAN discovery or FCF discovery time window. + * Start the FCF discovery if VLAN was never used. + */ +void fnic_vlan_discovery_timeout(struct fnic *fnic) +{ + struct fcoe_vlan *vlan; + struct fnic_iport_s *iport = &fnic->iport; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + unsigned long flags; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + if (fnic->stop_rx_link_events) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return; + } + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + if (!iport->usefip) + return; + + spin_lock_irqsave(&fnic->vlans_lock, flags); + if (list_empty(&fnic->vlan_list)) { + /* no vlans available, try again */ + spin_unlock_irqrestore(&fnic->vlans_lock, flags); + fnic_fcoe_send_vlan_req(fnic); + return; + } + + vlan = list_first_entry(&fnic->vlan_list, struct fcoe_vlan, list); + + if (vlan->state == FIP_VLAN_SENT) { + if (vlan->sol_count >= FCOE_CTLR_MAX_SOL) { + /* + * no response on this vlan, remove from the list. + * Try the next vlan + */ + list_del(&vlan->list); + kfree(vlan); + vlan = NULL; + if (list_empty(&fnic->vlan_list)) { + /* we exhausted all vlans, restart vlan disc */ + spin_unlock_irqrestore(&fnic->vlans_lock, + flags); + fnic_fcoe_send_vlan_req(fnic); + return; + } + /* check the next vlan */ + vlan = + list_first_entry(&fnic->vlan_list, struct fcoe_vlan, + list); + + fnic->set_vlan(fnic, vlan->vid); + vlan->state = FIP_VLAN_SENT; /* sent now */ + + } + atomic64_inc(&fnic_stats->vlan_stats.sol_expiry_count); + + } else { + fnic->set_vlan(fnic, vlan->vid); + vlan->state = FIP_VLAN_SENT; /* sent now */ + } + vlan->sol_count++; + spin_unlock_irqrestore(&fnic->vlans_lock, flags); + fnic_fcoe_start_fcf_discovery(fnic); +} + +/** + * fnic_work_on_fcs_ka_timer - Handle work on FCS keep alive timer. + * @work: the work queue to be serviced + * + * Finish handling fcs_ka_timer in process context. + * Clean up, bring the link down, and restart all FIP discovery. + */ +void fnic_work_on_fcs_ka_timer(struct work_struct *work) +{ + struct fnic + *fnic = container_of(work, struct fnic, fip_timer_work); + struct fnic_iport_s *iport = &fnic->iport; + + FNIC_FIP_DBG(KERN_INFO, fnic, + "fnic 0x%p fcs ka timeout\n", fnic); + + fnic_common_fip_cleanup(fnic); + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + fnic_fdls_link_down(iport); + iport->state = FNIC_IPORT_STATE_FIP; + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + + fnic_fcoe_send_vlan_req(fnic); +} + +/** + * fnic_handle_fcs_ka_timer - Handle FCS keep alive timer. + * @t: Handle to the timer list + * + * No keep alives received from FCF. Clean up, bring the link down + * and restart all the FIP discovery. + */ +void fnic_handle_fcs_ka_timer(struct timer_list *t) +{ + struct fnic *fnic = timer_container_of(fnic, t, fcs_ka_timer); + + INIT_WORK(&fnic->fip_timer_work, fnic_work_on_fcs_ka_timer); + queue_work(fnic_fip_queue, &fnic->fip_timer_work); +} diff --git a/drivers/scsi/fnic/fip.h b/drivers/scsi/fnic/fip.h new file mode 100644 index 000000000..4866152a3 --- /dev/null +++ b/drivers/scsi/fnic/fip.h @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _FIP_H_ +#define _FIP_H_ + +#include "fdls_fc.h" +#include "fnic_fdls.h" +#include <scsi/fc/fc_fip.h> + +/* Drop the cast from the standard definition */ +#define FCOE_ALL_FCFS_MAC {0x01, 0x10, 0x18, 0x01, 0x00, 0x02} +#define FCOE_MAX_SIZE 0x082E + +#define FCOE_CTLR_FIPVLAN_TOV (3*1000) +#define FCOE_CTLR_FCS_TOV (3*1000) +#define FCOE_CTLR_MAX_SOL (5*1000) + +#define FIP_DISC_SOL_LEN (6) +#define FIP_VLAN_REQ_LEN (2) +#define FIP_ENODE_KA_LEN (2) +#define FIP_VN_KA_LEN (7) +#define FIP_FLOGI_LEN (38) + +enum fdls_vlan_state { + FIP_VLAN_AVAIL, + FIP_VLAN_SENT +}; + +enum fdls_fip_state { + FDLS_FIP_INIT, + FDLS_FIP_VLAN_DISCOVERY_STARTED, + FDLS_FIP_FCF_DISCOVERY_STARTED, + FDLS_FIP_FLOGI_STARTED, + FDLS_FIP_FLOGI_COMPLETE, +}; + +/* + * VLAN entry. + */ +struct fcoe_vlan { + struct list_head list; + uint16_t vid; /* vlan ID */ + uint16_t sol_count; /* no. of sols sent */ + uint16_t state; /* state */ +}; + +struct fip_vlan_req { + struct ethhdr eth; + struct fip_header fip; + struct fip_mac_desc mac_desc; +} __packed; + +struct fip_vlan_notif { + struct fip_header fip; + struct fip_vlan_desc vlans_desc[]; +} __packed; + +struct fip_vn_port_ka { + struct ethhdr eth; + struct fip_header fip; + struct fip_mac_desc mac_desc; + struct fip_vn_desc vn_port_desc; +} __packed; + +struct fip_enode_ka { + struct ethhdr eth; + struct fip_header fip; + struct fip_mac_desc mac_desc; +} __packed; + +struct fip_cvl { + struct fip_header fip; + struct fip_mac_desc fcf_mac_desc; + struct fip_wwn_desc name_desc; + struct fip_vn_desc vn_ports_desc[]; +} __packed; + +struct fip_flogi_desc { + struct fip_desc fd_desc; + uint16_t rsvd; + struct fc_std_flogi flogi; +} __packed; + +struct fip_flogi_rsp_desc { + struct fip_desc fd_desc; + uint16_t rsvd; + struct fc_std_flogi flogi; +} __packed; + +struct fip_flogi { + struct ethhdr eth; + struct fip_header fip; + struct fip_flogi_desc flogi_desc; + struct fip_mac_desc mac_desc; +} __packed; + +struct fip_flogi_rsp { + struct fip_header fip; + struct fip_flogi_rsp_desc rsp_desc; + struct fip_mac_desc mac_desc; +} __packed; + +struct fip_discovery { + struct ethhdr eth; + struct fip_header fip; + struct fip_mac_desc mac_desc; + struct fip_wwn_desc name_desc; + struct fip_size_desc fcoe_desc; +} __packed; + +struct fip_disc_adv { + struct fip_header fip; + struct fip_pri_desc prio_desc; + struct fip_mac_desc mac_desc; + struct fip_wwn_desc name_desc; + struct fip_fab_desc fabric_desc; + struct fip_fka_desc fka_adv_desc; +} __packed; + +void fnic_fcoe_process_vlan_resp(struct fnic *fnic, struct fip_header *fiph); +void fnic_fcoe_fip_discovery_resp(struct fnic *fnic, struct fip_header *fiph); +void fnic_fcoe_process_flogi_resp(struct fnic *fnic, struct fip_header *fiph); +void fnic_work_on_fip_timer(struct work_struct *work); +void fnic_work_on_fcs_ka_timer(struct work_struct *work); +void fnic_fcoe_send_vlan_req(struct fnic *fnic); +void fnic_fcoe_start_fcf_discovery(struct fnic *fnic); +void fnic_fcoe_start_flogi(struct fnic *fnic); +void fnic_fcoe_process_cvl(struct fnic *fnic, struct fip_header *fiph); +void fnic_vlan_discovery_timeout(struct fnic *fnic); + +extern struct workqueue_struct *fnic_fip_queue; + +#ifdef FNIC_DEBUG +static inline void +fnic_debug_dump_fip_frame(struct fnic *fnic, struct ethhdr *eth, + int len, char *pfx) +{ + struct fip_header *fiph = (struct fip_header *)(eth + 1); + u16 op = be16_to_cpu(fiph->fip_op); + u8 sub = fiph->fip_subcode; + + FNIC_FCS_DBG(KERN_DEBUG, fnic, + "FIP %s packet contents: op: 0x%x sub: 0x%x (len = %d)", + pfx, op, sub, len); + + fnic_debug_dump(fnic, (uint8_t *)eth, len); +} + +#else /* FNIC_DEBUG */ + +static inline void +fnic_debug_dump_fip_frame(struct fnic *fnic, struct ethhdr *eth, + int len, char *pfx) {} +#endif /* FNIC_DEBUG */ + +#endif /* _FIP_H_ */ diff --git a/drivers/scsi/fnic/fnic.h b/drivers/scsi/fnic/fnic.h new file mode 100644 index 000000000..3ba159294 --- /dev/null +++ b/drivers/scsi/fnic/fnic.h @@ -0,0 +1,663 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _FNIC_H_ +#define _FNIC_H_ + +#include <linux/interrupt.h> +#include <linux/netdevice.h> +#include <linux/workqueue.h> +#include <linux/bitops.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_transport.h> +#include <scsi/scsi_transport_fc.h> +#include <scsi/fc_frame.h> +#include "fnic_io.h" +#include "fnic_res.h" +#include "fnic_trace.h" +#include "fnic_stats.h" +#include "vnic_dev.h" +#include "vnic_wq.h" +#include "vnic_rq.h" +#include "vnic_cq.h" +#include "vnic_wq_copy.h" +#include "vnic_intr.h" +#include "vnic_stats.h" +#include "vnic_scsi.h" +#include "fnic_fdls.h" +#include "fnic_nvme.h" + +#define DRV_NAME "fnic" +#define DRV_DESCRIPTION "Cisco FCoE HBA Driver" +#define DRV_VERSION "1.9.0.1" +#define PFX DRV_NAME ": " +#define DFX DRV_NAME "%d: " + +#define FABRIC_LOGO_MAX_RETRY 3 +#define DESC_CLEAN_LOW_WATERMARK 8 +#define FNIC_UCSM_DFLT_THROTTLE_CNT_BLD 16 /* UCSM default throttle count */ +#define FNIC_MIN_IO_REQ 256 /* Min IO throttle count */ +#define FNIC_MAX_IO_REQ 1024 /* scsi_cmnd tag map entries */ +#define FNIC_DFLT_IO_REQ 256 /* Default scsi_cmnd tag map entries */ +#define FNIC_DFLT_QUEUE_DEPTH 256 +#define FNIC_STATS_RATE_LIMIT 4 /* limit rate at which stats are pulled up */ +#define LUN0_DELAY_TIME 9 +#define FNIC_ROLE_CONFIG_MASK (0xFF0) + +/* + * Tag bits used for special requests. + */ +#define FNIC_TAG_ABORT BIT(30) /* tag bit indicating abort */ +#define FNIC_TAG_DEV_RST BIT(29) /* indicates device reset */ +#define FNIC_TAG_MASK (BIT(24) - 1) /* mask for lookup */ +#define FNIC_NO_TAG -1 + +/* + * Command flags to identify the type of command and for other future + * use. + */ +#define FNIC_NO_FLAGS 0 +#define FNIC_IO_INITIALIZED BIT(0) +#define FNIC_IO_ISSUED BIT(1) +#define FNIC_IO_DONE BIT(2) +#define FNIC_IO_REQ_NULL BIT(3) +#define FNIC_IO_ABTS_PENDING BIT(4) +#define FNIC_IO_ABORTED BIT(5) +#define FNIC_IO_ABTS_ISSUED BIT(6) +#define FNIC_IO_TERM_ISSUED BIT(7) +#define FNIC_IO_INTERNAL_TERM_ISSUED BIT(8) +#define FNIC_IO_ABT_TERM_DONE BIT(9) +#define FNIC_IO_ABT_TERM_REQ_NULL BIT(10) +#define FNIC_IO_ABT_TERM_TIMED_OUT BIT(11) +#define FNIC_DEVICE_RESET BIT(12) /* Device reset request */ +#define FNIC_DEV_RST_ISSUED BIT(13) +#define FNIC_DEV_RST_TIMED_OUT BIT(14) +#define FNIC_DEV_RST_ABTS_ISSUED BIT(15) +#define FNIC_DEV_RST_TERM_ISSUED BIT(16) +#define FNIC_DEV_RST_DONE BIT(17) +#define FNIC_DEV_RST_REQ_NULL BIT(18) +#define FNIC_DEV_RST_ABTS_DONE BIT(19) +#define FNIC_DEV_RST_TERM_DONE BIT(20) +#define FNIC_DEV_RST_ABTS_PENDING BIT(21) +#define FNIC_NVME_ADMIN_IO_TIMER_PENDING BIT(22) +#define FNIC_NVME_ADMIN_IO BIT(23) + +#define FNIC_FW_RESET_TIMEOUT 60000 /* mSec */ +#define FNIC_FCOE_MAX_CMD_LEN 16 +/* Retry supported by rport (returned by PRLI service parameters) */ +#define FNIC_FC_RP_FLAGS_RETRY 0x1 + +/* Cisco vendor id */ +#define PCI_VENDOR_ID_CISCO 0x1137 +#define PCI_DEVICE_ID_CISCO_VIC_FC 0x0045 /* fc vnic */ + +/* sereno pcie switch */ +#define PCI_DEVICE_ID_CISCO_SERENO 0x004e +#define PCI_DEVICE_ID_CISCO_CRUZ 0x007a /* Cruz */ +#define PCI_DEVICE_ID_CISCO_BODEGA 0x0131 /* Bodega */ +#define PCI_DEVICE_ID_CISCO_BEVERLY 0x025f /* Beverly */ + +/* Sereno */ +#define PCI_SUBDEVICE_ID_CISCO_VASONA 0x004f /* vasona mezz */ +#define PCI_SUBDEVICE_ID_CISCO_COTATI 0x0084 /* cotati mlom */ +#define PCI_SUBDEVICE_ID_CISCO_LEXINGTON 0x0085 /* lexington pcie */ +#define PCI_SUBDEVICE_ID_CISCO_ICEHOUSE 0x00cd /* Icehouse */ +#define PCI_SUBDEVICE_ID_CISCO_KIRKWOODLAKE 0x00ce /* KirkwoodLake pcie */ +#define PCI_SUBDEVICE_ID_CISCO_SUSANVILLE 0x012e /* Susanville MLOM */ +#define PCI_SUBDEVICE_ID_CISCO_TORRANCE 0x0139 /* Torrance MLOM */ + +/* Cruz */ +#define PCI_SUBDEVICE_ID_CISCO_CALISTOGA 0x012c /* Calistoga MLOM */ +#define PCI_SUBDEVICE_ID_CISCO_MOUNTAINVIEW 0x0137 /* Cruz Mezz */ +/* Cruz MountTian SIOC */ +#define PCI_SUBDEVICE_ID_CISCO_MOUNTTIAN 0x014b +#define PCI_SUBDEVICE_ID_CISCO_CLEARLAKE 0x014d /* ClearLake pcie */ +/* Cruz MountTian2 SIOC */ +#define PCI_SUBDEVICE_ID_CISCO_MOUNTTIAN2 0x0157 +#define PCI_SUBDEVICE_ID_CISCO_CLAREMONT 0x015d /* Claremont MLOM */ + +/* Bodega */ +/* VIC 1457 PCIe mLOM */ +#define PCI_SUBDEVICE_ID_CISCO_BRADBURY 0x0218 +#define PCI_SUBDEVICE_ID_CISCO_BRENTWOOD 0x0217 /* VIC 1455 PCIe */ +/* VIC 1487 PCIe mLOM */ +#define PCI_SUBDEVICE_ID_CISCO_BURLINGAME 0x021a +#define PCI_SUBDEVICE_ID_CISCO_BAYSIDE 0x0219 /* VIC 1485 PCIe */ +/* VIC 1440 Mezz mLOM */ +#define PCI_SUBDEVICE_ID_CISCO_BAKERSFIELD 0x0215 +#define PCI_SUBDEVICE_ID_CISCO_BOONVILLE 0x0216 /* VIC 1480 Mezz */ +#define PCI_SUBDEVICE_ID_CISCO_BENICIA 0x024a /* VIC 1495 */ +#define PCI_SUBDEVICE_ID_CISCO_BEAUMONT 0x024b /* VIC 1497 */ +#define PCI_SUBDEVICE_ID_CISCO_BRISBANE 0x02af /* VIC 1467 */ +#define PCI_SUBDEVICE_ID_CISCO_BENTON 0x02b0 /* VIC 1477 */ +#define PCI_SUBDEVICE_ID_CISCO_TWIN_RIVER 0x02cf /* VIC 14425 */ +#define PCI_SUBDEVICE_ID_CISCO_TWIN_PEAK 0x02d0 /* VIC 14825 */ + +/* Beverly */ +#define PCI_SUBDEVICE_ID_CISCO_BERN 0x02de /* VIC 15420 */ +#define PCI_SUBDEVICE_ID_CISCO_STOCKHOLM 0x02dd /* VIC 15428 */ +#define PCI_SUBDEVICE_ID_CISCO_KRAKOW 0x02dc /* VIC 15411 */ +#define PCI_SUBDEVICE_ID_CISCO_LUCERNE 0x02db /* VIC 15231 */ +#define PCI_SUBDEVICE_ID_CISCO_TURKU 0x02e8 /* VIC 15238 */ +#define PCI_SUBDEVICE_ID_CISCO_TURKU_PLUS 0x02f3 /* VIC 15237 */ +#define PCI_SUBDEVICE_ID_CISCO_ZURICH 0x02df /* VIC 15230 */ +#define PCI_SUBDEVICE_ID_CISCO_RIGA 0x02e0 /* VIC 15427 */ +#define PCI_SUBDEVICE_ID_CISCO_GENEVA 0x02e1 /* VIC 15422 */ +#define PCI_SUBDEVICE_ID_CISCO_HELSINKI 0x02e4 /* VIC 15235 */ +#define PCI_SUBDEVICE_ID_CISCO_GOTHENBURG 0x02f2 /* VIC 15425 */ + +#define IS_FNIC_FCP_INITIATOR(fnic) (fnic->role == FNIC_ROLE_FCP_INITIATOR) +#define IS_FNIC_NVME_INITIATOR(fnic) (fnic->role == FNIC_ROLE_NVME_INITIATOR) + +struct fnic_pcie_device { + u32 device; + u8 *desc; + u32 subsystem_device; + u8 *subsys_desc; +}; + +/* + * fnic private data per SCSI command. + * These fields are locked by the hashed io_req_lock. + */ +struct fnic_cmd_priv { + struct fnic_io_req *io_req; + enum fnic_ioreq_state state; + u32 flags; + u16 abts_status; + u16 lr_status; +}; + +static inline struct fnic_cmd_priv *fnic_priv(struct scsi_cmnd *cmd) +{ + return scsi_cmd_priv(cmd); +} + +static inline u64 fnic_flags_and_state(struct scsi_cmnd *cmd) +{ + struct fnic_cmd_priv *fcmd = fnic_priv(cmd); + + return ((u64)fcmd->flags << 32) | fcmd->state; +} + +#define FCPIO_INVALID_CODE 0x100 /* hdr_status value unused by firmware */ + +#define FNIC_LUN_RESET_TIMEOUT 10000 /* mSec */ +#define FNIC_HOST_RESET_TIMEOUT 10000 /* mSec */ +#define FNIC_RMDEVICE_TIMEOUT 1000 /* mSec */ +#define FNIC_HOST_RESET_SETTLE_TIME 30 /* Sec */ +#define FNIC_ABT_TERM_DELAY_TIMEOUT 500 /* mSec */ + +#define FNIC_MAX_FCP_TARGET 256 +#define FNIC_PCI_OFFSET 2 +/** + * state_flags to identify host state along along with fnic's state + **/ +#define __FNIC_FLAGS_FWRESET BIT(0) /* fwreset in progress */ +#define __FNIC_FLAGS_BLOCK_IO BIT(1) /* IOs are blocked */ + +#define FNIC_FLAGS_NONE (0) +#define FNIC_FLAGS_FWRESET (__FNIC_FLAGS_FWRESET | \ + __FNIC_FLAGS_BLOCK_IO) + +#define FNIC_FLAGS_IO_BLOCKED (__FNIC_FLAGS_BLOCK_IO) + +#define fnic_set_state_flags(fnicp, st_flags) \ + __fnic_set_state_flags(fnicp, st_flags, 0) + +#define fnic_clear_state_flags(fnicp, st_flags) \ + __fnic_set_state_flags(fnicp, st_flags, 1) + +enum reset_states { + NOT_IN_PROGRESS = 0, + IN_PROGRESS, + RESET_ERROR +}; + +enum rscn_type { + NOT_PC_RSCN = 0, + PC_RSCN +}; + +enum pc_rscn_handling_status { + PC_RSCN_HANDLING_NOT_IN_PROGRESS = 0, + PC_RSCN_HANDLING_IN_PROGRESS +}; + +enum pc_rscn_handling_feature { + PC_RSCN_HANDLING_FEATURE_OFF = 0, + PC_RSCN_HANDLING_FEATURE_ON +}; + +extern unsigned int fnic_fdmi_support; +extern unsigned int fnic_log_level; +extern unsigned int io_completions; +extern struct workqueue_struct *fnic_event_queue; + +extern unsigned int pc_rscn_handling_feature_flag; +extern spinlock_t reset_fnic_list_lock; +extern struct list_head reset_fnic_list; +extern struct workqueue_struct *reset_fnic_work_queue; +extern struct work_struct reset_fnic_work; +extern struct workqueue_struct *fnic_cmpl_queue; + +#define FNIC_MAIN_LOGGING 0x01 +#define FNIC_FCS_LOGGING 0x02 +#define FNIC_SCSI_LOGGING 0x04 +#define FNIC_ISR_LOGGING 0x08 +#define FNIC_FDLS_LOGGING 0x10 +#define FNIC_NVME_LOGGING 0x20 +#define FNIC_FIP_LOGGING 0x40 + +#define FNIC_CHECK_LOGGING(LEVEL, CMD) \ +do { \ + if (unlikely(fnic_log_level & LEVEL)) \ + do { \ + CMD; \ + } while (0); \ +} while (0) + +#define fnic_printk(kern_level, fnic, fmt, ...) \ + (IS_FNIC_FCP_INITIATOR(fnic) ? \ + shost_printk(kern_level, fnic->host, "fnic<%d>: %s: %d: " fmt, \ + fnic->fnic_num, __func__, __LINE__, ##__VA_ARGS__) : \ + printk(kern_level "fnic<%d>: %s: %d: " fmt, fnic->fnic_num, \ + __func__, __LINE__, ##__VA_ARGS__)) + +#define FNIC_MAIN_DBG(kern_level, fnic, fmt, args...) \ + FNIC_CHECK_LOGGING(FNIC_MAIN_LOGGING, \ + fnic_printk(kern_level, fnic, fmt, ##args);) + +#define FNIC_FCS_DBG(kern_level, fnic, fmt, args...) \ + FNIC_CHECK_LOGGING(FNIC_FCS_LOGGING, \ + fnic_printk(kern_level, fnic, fmt, ##args);) + +#define FNIC_FIP_DBG(kern_level, fnic, fmt, args...) \ + FNIC_CHECK_LOGGING(FNIC_FIP_LOGGING, \ + fnic_printk(kern_level, fnic, fmt, ##args);) + +#define FNIC_SCSI_DBG(kern_level, fnic, fmt, args...) \ + FNIC_CHECK_LOGGING(FNIC_SCSI_LOGGING, \ + fnic_printk(kern_level, fnic, fmt, ##args);) + +#define FNIC_ISR_DBG(kern_level, fnic, fmt, args...) \ + FNIC_CHECK_LOGGING(FNIC_ISR_LOGGING, \ + fnic_printk(kern_level, fnic, fmt, ##args);) + +#define FNIC_NVME_DBG(kern_level, fnic, fmt, args...) \ + FNIC_CHECK_LOGGING(FNIC_NVME_LOGGING, \ + fnic_printk(kern_level, fnic, fmt, ##args);) + +#define FNIC_MAIN_NOTE(kern_level, fnic, fmt, args...) \ + fnic_printk(kern_level, fnic, fmt, ##args) + +#define FNIC_WQ_COPY_MAX 64 +#define FNIC_WQ_MAX 1 +#define FNIC_RQ_MAX 1 +#define FNIC_CQ_MAX (FNIC_WQ_COPY_MAX + FNIC_WQ_MAX + FNIC_RQ_MAX) +#define FNIC_DFLT_IO_COMPLETIONS 256 + +#define FNIC_MQ_CQ_INDEX 2 + +extern const char *fnic_state_str[]; + +enum fnic_intx_intr_index { + FNIC_INTX_WQ_RQ_COPYWQ, + FNIC_INTX_DUMMY, + FNIC_INTX_NOTIFY, + FNIC_INTX_ERR, + FNIC_INTX_INTR_MAX, +}; + +enum fnic_msix_intr_index { + FNIC_MSIX_RQ, + FNIC_MSIX_WQ, + FNIC_MSIX_WQ_COPY, + FNIC_MSIX_ERR_NOTIFY = FNIC_MSIX_WQ_COPY + FNIC_WQ_COPY_MAX, + FNIC_MSIX_INTR_MAX, +}; + +struct fnic_msix_entry { + int requested; + char devname[IFNAMSIZ + 11]; + irqreturn_t (*isr)(int, void *); + void *devid; + int irq_num; +}; + +enum fnic_state { + FNIC_IN_FC_MODE = 0, + FNIC_IN_FC_TRANS_ETH_MODE, + FNIC_IN_ETH_MODE, + FNIC_IN_ETH_TRANS_FC_MODE, +}; + +enum fnic_role_e { + FNIC_ROLE_FCP_INITIATOR = 0, + FNIC_ROLE_NVME_INITIATOR, +}; + +enum fnic_evt { + FNIC_EVT_START_VLAN_DISC = 1, + FNIC_EVT_START_FCF_DISC = 2, + FNIC_EVT_MAX, +}; + +struct fnic_frame_list { + /* + * Link to frame lists + */ + struct list_head links; + void *fp; + int frame_len; + int rx_ethhdr_stripped; +}; + +struct fnic_tag_t { + struct list_head free_list; + int tag_id; +}; + +struct fnic_event { + struct list_head list; + struct fnic *fnic; + enum fnic_evt event; +}; + +struct fnic_cpy_wq { + unsigned long hw_lock_flags; + u16 active_ioreq_count; + u16 ioreq_table_size; + ____cacheline_aligned struct fnic_io_req **io_req_table; +}; + +/* Per-instance private data structure */ +struct fnic { + int fnic_num; + enum fnic_role_e role; + struct fnic_iport_s iport; + struct Scsi_Host *host; + struct vnic_dev_bar bar0; + + struct fnic_msix_entry msix[FNIC_MSIX_INTR_MAX]; + + struct vnic_stats *stats; + unsigned long stats_time; /* time of stats update */ + unsigned long stats_reset_time; /* time of stats reset */ + struct vnic_nic_cfg *nic_cfg; + char name[IFNAMSIZ]; + struct timer_list notify_timer; /* used for MSI interrupts */ + + unsigned int fnic_max_tag_id; + unsigned int err_intr_offset; + unsigned int link_intr_offset; + + unsigned int wq_count; + unsigned int cq_count; + + struct completion reset_completion_wait; + struct mutex sgreset_mutex; + spinlock_t sgreset_lock; /* lock for sgreset */ + struct scsi_cmnd *sgreset_sc; + struct dentry *fnic_stats_debugfs_host; + struct dentry *fnic_stats_debugfs_file; + struct dentry *fnic_reset_debugfs_file; + unsigned int reset_stats; + atomic64_t io_cmpl_skip; + struct fnic_stats fnic_stats; + + u32 vlan_hw_insert:1; /* let hw insert the tag */ + u32 in_remove:1; /* fnic device in removal */ + u32 stop_rx_link_events:1; /* stop proc. rx frames, link events */ + + struct completion *fw_reset_done; + u32 reset_in_progress; + atomic_t in_flight; /* io counter */ + bool internal_reset_inprogress; + u32 _reserved; /* fill hole */ + unsigned long state_flags; /* protected by host lock */ + enum fnic_state state; + spinlock_t fnic_lock; + unsigned long lock_flags; + + u16 vlan_id; /* VLAN tag including priority */ + u8 data_src_addr[ETH_ALEN]; + u64 fcp_input_bytes; /* internal statistic */ + u64 fcp_output_bytes; /* internal statistic */ + u32 link_down_cnt; + u32 soft_reset_count; + int link_status; + + struct list_head list; + struct list_head links; + struct pci_dev *pdev; + struct vnic_fc_config config; + struct vnic_dev *vdev; + unsigned int raw_wq_count; + unsigned int wq_copy_count; + unsigned int rq_count; + int fw_ack_index[FNIC_WQ_COPY_MAX]; + unsigned short fw_ack_recd[FNIC_WQ_COPY_MAX]; + unsigned short wq_copy_desc_low[FNIC_WQ_COPY_MAX]; + unsigned int intr_count; + u32 __iomem *legacy_pba; + struct fnic_host_tag *tags; + mempool_t *io_req_pool; + mempool_t *io_sgl_pool[FNIC_SGL_NUM_CACHES]; + + unsigned int copy_wq_base; + struct work_struct link_work; + struct work_struct frame_work; + struct work_struct flush_work; + struct list_head frame_queue; + struct list_head tx_queue; + mempool_t *frame_pool; + mempool_t *frame_elem_pool; + mempool_t *frame_recv_pool; + struct work_struct tport_work; + struct list_head tport_event_list; + + char subsys_desc[14]; + int subsys_desc_len; + int pc_rscn_handling_status; + + /*** FIP related data members -- start ***/ + void (*set_vlan)(struct fnic *, u16 vlan); + struct work_struct fip_frame_work; + struct work_struct fip_timer_work; + struct list_head fip_frame_queue; + struct timer_list fip_timer; + spinlock_t vlans_lock; + struct timer_list retry_fip_timer; + struct timer_list fcs_ka_timer; + struct timer_list enode_ka_timer; + struct timer_list vn_ka_timer; + struct list_head vlan_list; + /*** FIP related data members -- end ***/ + + /* NVME data members */ + struct dentry *fnic_nvmef_debugfs_host; + struct dentry *fnic_nvmef_debugfs_file; + struct sbitmap nvfnic_tag_map; + struct work_struct nvme_io_cmpl_work; + atomic_t nvme_io_event_queued; + struct llist_head nvme_io_event_llist; + struct completion *nvme_lport_unreg_done; + + /* copy work queue cache line section */ + ____cacheline_aligned struct vnic_wq_copy hw_copy_wq[FNIC_WQ_COPY_MAX]; + ____cacheline_aligned struct fnic_cpy_wq sw_copy_wq[FNIC_WQ_COPY_MAX]; + + /* completion queue cache line section */ + ____cacheline_aligned struct vnic_cq cq[FNIC_CQ_MAX]; + + spinlock_t wq_copy_lock[FNIC_WQ_COPY_MAX]; + + /* work queue cache line section */ + ____cacheline_aligned struct vnic_wq wq[FNIC_WQ_MAX]; + spinlock_t wq_lock[FNIC_WQ_MAX]; + + /* receive queue cache line section */ + ____cacheline_aligned struct vnic_rq rq[FNIC_RQ_MAX]; + + /* interrupt resource cache line section */ + ____cacheline_aligned struct vnic_intr intr[FNIC_MSIX_INTR_MAX]; +}; + +extern struct workqueue_struct *fnic_event_queue; +extern struct workqueue_struct *fnic_fip_queue; +extern const struct attribute_group *fnic_host_groups[]; + +void fnic_clear_intr_mode(struct fnic *fnic); +int fnic_set_intr_mode(struct fnic *fnic); +int fnic_set_intr_mode_msix(struct fnic *fnic); +void fnic_free_intr(struct fnic *fnic); +int fnic_request_intr(struct fnic *fnic); + +void fnic_free_wq_buf(struct vnic_wq *wq, struct vnic_wq_buf *buf); +void fnic_handle_frame(struct work_struct *work); +void fnic_tport_event_handler(struct work_struct *work); +void fnic_handle_link(struct work_struct *work); +void fnic_handle_event(struct work_struct *work); +void fdls_reclaim_oxid_handler(struct work_struct *work); +void fdls_schedule_oxid_free(struct fnic_iport_s *iport, uint16_t *active_oxid); +void fdls_schedule_oxid_free_retry_work(struct work_struct *work); +int fnic_rq_cmpl_handler(struct fnic *fnic, int); +int fnic_alloc_rq_frame(struct vnic_rq *rq); +void fnic_free_rq_buf(struct vnic_rq *rq, struct vnic_rq_buf *buf); +void fnic_flush_tx(struct work_struct *work); +void fnic_update_mac_locked(struct fnic *, u8 *new); + +enum scsi_qc_status fnic_queuecommand(struct Scsi_Host *shost, + struct scsi_cmnd *sc); +int fnic_abort_cmd(struct scsi_cmnd *); +int fnic_device_reset(struct scsi_cmnd *); +int fnic_eh_host_reset_handler(struct scsi_cmnd *sc); +int fnic_host_reset(struct Scsi_Host *shost); +void fnic_reset(struct Scsi_Host *shost); +int fnic_issue_fc_host_lip(struct Scsi_Host *shost); +void fnic_get_host_port_state(struct Scsi_Host *shost); +void fnic_fcpio_reset(struct fnic *fnic); +int fnic_wq_copy_cmpl_handler(struct fnic *fnic, int copy_work_to_do, unsigned int cq_index); +int fnic_wq_cmpl_handler(struct fnic *fnic, int); +int fnic_flogi_reg_handler(struct fnic *fnic, u32); +void fnic_wq_copy_cleanup_handler(struct vnic_wq_copy *wq, + struct fcpio_host_req *desc); +int fnic_fw_reset_handler(struct fnic *fnic); +void fnic_terminate_rport_io(struct fc_rport *); +const char *fnic_state_to_str(unsigned int state); +void fnic_mq_map_queues_cpus(struct Scsi_Host *host); +void fnic_log_q_error(struct fnic *fnic); +void fnic_handle_link_event(struct fnic *fnic); +int fnic_stats_debugfs_init(struct fnic *fnic); +void fnic_stats_debugfs_remove(struct fnic *fnic); +void fnic_nvmef_debugfs_init(struct fnic *fnic); +void fnic_nvmef_debugfs_remove(struct fnic *fnic); +int nvfnic_get_nvmef_info(struct fnic *fnic, struct fnic_nvmef_info *info); +int fnic_is_abts_pending(struct fnic *, struct scsi_cmnd *); + +void fnic_handle_fip_frame(struct work_struct *work); +void fnic_reset_work_handler(struct work_struct *work); +void fnic_handle_fip_event(struct fnic *fnic); +void fnic_fcoe_reset_vlans(struct fnic *fnic); +extern void fnic_handle_fip_timer(struct timer_list *t); + +static inline int +fnic_chk_state_flags_locked(struct fnic *fnic, unsigned long st_flags) +{ + return ((fnic->state_flags & st_flags) == st_flags); +} +void __fnic_set_state_flags(struct fnic *, unsigned long, unsigned long); +void fnic_dump_fchost_stats(struct Scsi_Host *, struct fc_host_statistics *); +void fnic_free_txq(struct fnic *fnic); +void fnic_free_rxq(struct fnic *fnic); +int fnic_get_desc_by_devid(struct pci_dev *pdev, char **desc, + char **subsys_desc); +void fnic_fdls_link_status_change(struct fnic *fnic, int linkup); +void fnic_delete_fcp_tports(struct fnic *fnic); +void fnic_flush_tport_event_list(struct fnic *fnic); +int fnic_count_ioreqs_wq(struct fnic *fnic, u32 hwq, u32 portid); +unsigned int fnic_count_ioreqs(struct fnic *fnic, u32 portid); +unsigned int fnic_count_all_ioreqs(struct fnic *fnic); +unsigned int fnic_count_lun_ioreqs_wq(struct fnic *fnic, u32 hwq, + struct scsi_device *device); +unsigned int fnic_count_lun_ioreqs(struct fnic *fnic, + struct scsi_device *device); +void fnic_scsi_unload(struct fnic *fnic); +void fnic_scsi_unload_cleanup(struct fnic *fnic); +int fnic_get_debug_info(struct stats_debug_info *info, + struct fnic *fnic); +int free_wq_copy_descs(struct fnic *fnic, struct vnic_wq_copy *wq, + unsigned int hwq); + +struct fnic_scsi_iter_data { + struct fnic *fnic; + void *data1; + void *data2; + bool (*fn)(struct fnic *fnic, struct scsi_cmnd *sc, + void *data1, void *data2); +}; + +static inline bool +fnic_io_iter_handler(struct scsi_cmnd *sc, void *iter_data) +{ + struct fnic_scsi_iter_data *iter = iter_data; + + return iter->fn(iter->fnic, sc, iter->data1, iter->data2); +} + +static inline void +fnic_scsi_io_iter(struct fnic *fnic, + bool (*fn)(struct fnic *fnic, struct scsi_cmnd *sc, + void *data1, void *data2), + void *data1, void *data2) +{ + struct fnic_scsi_iter_data iter_data = { + .fn = fn, + .fnic = fnic, + .data1 = data1, + .data2 = data2, + }; + scsi_host_busy_iter(fnic->host, fnic_io_iter_handler, &iter_data); +} + +#ifdef FNIC_DEBUG +static inline void +fnic_debug_dump(struct fnic *fnic, uint8_t *u8arr, int len) +{ + int i; + + for (i = 0; i < len; i = i+8) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "%d: %02x %02x %02x %02x %02x %02x %02x %02x", i / 8, + u8arr[i + 0], u8arr[i + 1], u8arr[i + 2], u8arr[i + 3], + u8arr[i + 4], u8arr[i + 5], u8arr[i + 6], u8arr[i + 7]); + } +} + +static inline void +fnic_debug_dump_fc_frame(struct fnic *fnic, struct fc_frame_header *fchdr, + int len, char *pfx) +{ + uint32_t s_id, d_id; + + s_id = ntoh24(fchdr->fh_s_id); + d_id = ntoh24(fchdr->fh_d_id); + FNIC_FCS_DBG(KERN_INFO, fnic, + "%s packet contents: sid/did/type/oxid = 0x%x/0x%x/0x%x/0x%x (len = %d)\n", + pfx, s_id, d_id, fchdr->fh_type, + FNIC_STD_GET_OX_ID(fchdr), len); + + fnic_debug_dump(fnic, (uint8_t *)fchdr, len); + +} +#else /* FNIC_DEBUG */ +static inline void +fnic_debug_dump(struct fnic *fnic, uint8_t *u8arr, int len) {} +static inline void +fnic_debug_dump_fc_frame(struct fnic *fnic, struct fc_frame_header *fchdr, + uint32_t len, char *pfx) {} +#endif /* FNIC_DEBUG */ +#endif /* _FNIC_H_ */ diff --git a/drivers/scsi/fnic/fnic_attrs.c b/drivers/scsi/fnic/fnic_attrs.c new file mode 100644 index 000000000..705718f08 --- /dev/null +++ b/drivers/scsi/fnic/fnic_attrs.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#include <linux/string.h> +#include <linux/device.h> +#include <scsi/scsi_host.h> +#include "fnic.h" + +static ssize_t fnic_show_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fnic *fnic = + *((struct fnic **) shost_priv(class_to_shost(dev))); + + return sysfs_emit(buf, "%s\n", fnic_state_str[fnic->state]); +} + +static ssize_t fnic_show_drv_version(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%s\n", DRV_VERSION); +} + +static ssize_t fnic_show_link_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fnic *fnic = + *((struct fnic **) shost_priv(class_to_shost(dev))); + + return sysfs_emit(buf, "%s\n", + ((fnic->iport.state != FNIC_IPORT_STATE_INIT) && + (fnic->iport.state != FNIC_IPORT_STATE_LINK_WAIT)) ? + "Link Up" : "Link Down"); +} + +static DEVICE_ATTR(fnic_state, S_IRUGO, fnic_show_state, NULL); +static DEVICE_ATTR(drv_version, S_IRUGO, fnic_show_drv_version, NULL); +static DEVICE_ATTR(link_state, S_IRUGO, fnic_show_link_state, NULL); + +static struct attribute *fnic_host_attrs[] = { + &dev_attr_fnic_state.attr, + &dev_attr_drv_version.attr, + &dev_attr_link_state.attr, + NULL, +}; + +static const struct attribute_group fnic_host_attr_group = { + .attrs = fnic_host_attrs +}; + +const struct attribute_group *fnic_host_groups[] = { + &fnic_host_attr_group, + NULL +}; diff --git a/drivers/scsi/fnic/fnic_debugfs.c b/drivers/scsi/fnic/fnic_debugfs.c new file mode 100644 index 000000000..21b7e9666 --- /dev/null +++ b/drivers/scsi/fnic/fnic_debugfs.c @@ -0,0 +1,818 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright 2012 Cisco Systems, Inc. All rights reserved. + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/debugfs.h> +#include <linux/vmalloc.h> +#include "fnic.h" + +extern int fnic_get_debug_info(struct stats_debug_info *debug_buffer, + struct fnic *fnic); + +static int fnic_nvmef_debugfs_open(struct inode *inode, + struct file *file); +static ssize_t fnic_nvmef_debugfs_read(struct file *file, + char __user *ubuf, + size_t nbytes, loff_t *pos); +static int fnic_nvmef_debugfs_release(struct inode *inode, + struct file *file); + +static struct dentry *fnic_trace_debugfs_root; +static struct dentry *fnic_trace_debugfs_file; +static struct dentry *fnic_trace_enable; +static struct dentry *fnic_stats_debugfs_root; +static struct dentry *fnic_nvmef_debugfs_root; + +static struct dentry *fnic_fc_trace_debugfs_file; +static struct dentry *fnic_fc_rdata_trace_debugfs_file; +static struct dentry *fnic_fc_trace_enable; +static struct dentry *fnic_fc_trace_clear; + +struct fc_trace_flag_type { + u8 fc_row_file; + u8 fc_normal_file; + u8 fnic_trace; + u8 fc_trace; + u8 fc_clear; +}; + +static struct fc_trace_flag_type *fc_trc_flag; + +/* + * fnic_debugfs_init - Initialize debugfs for fnic debug logging + * + * Description: + * When Debugfs is configured this routine sets up the fnic debugfs + * file system. If not already created, this routine will create the + * fnic directory and statistics directory for trace buffer and + * stats logging. + */ +int fnic_debugfs_init(void) +{ + fnic_trace_debugfs_root = debugfs_create_dir("fnic", NULL); + + fnic_stats_debugfs_root = debugfs_create_dir("statistics", + fnic_trace_debugfs_root); + + fnic_nvmef_debugfs_root = debugfs_create_dir("nvme_info", + fnic_trace_debugfs_root); + + /* Allocate memory to structure */ + fc_trc_flag = vmalloc(sizeof(struct fc_trace_flag_type)); + + if (fc_trc_flag) { + fc_trc_flag->fc_row_file = 0; + fc_trc_flag->fc_normal_file = 1; + fc_trc_flag->fnic_trace = 2; + fc_trc_flag->fc_trace = 3; + fc_trc_flag->fc_clear = 4; + return 0; + } + + return -ENOMEM; +} + +/* + * fnic_debugfs_terminate - Tear down debugfs infrastructure + * + * Description: + * When Debugfs is configured this routine removes debugfs file system + * elements that are specific to fnic. + */ +void fnic_debugfs_terminate(void) +{ + debugfs_remove(fnic_nvmef_debugfs_root); + fnic_nvmef_debugfs_root = NULL; + + debugfs_remove(fnic_stats_debugfs_root); + fnic_stats_debugfs_root = NULL; + + debugfs_remove(fnic_trace_debugfs_root); + fnic_trace_debugfs_root = NULL; + + vfree(fc_trc_flag); +} + +/* + * fnic_trace_ctrl_read - + * Read trace_enable ,fc_trace_enable + * or fc_trace_clear debugfs file + * @filp: The file pointer to read from. + * @ubuf: The buffer to copy the data to. + * @cnt: The number of bytes to read. + * @ppos: The position in the file to start reading from. + * + * Description: + * This routine reads value of variable fnic_tracing_enabled or + * fnic_fc_tracing_enabled or fnic_fc_trace_cleared + * and stores into local @buf. + * It will start reading file at @ppos and + * copy up to @cnt of data to @ubuf from @buf. + * + * Returns: + * This function returns the amount of data that was read. + */ +static ssize_t fnic_trace_ctrl_read(struct file *filp, + char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + char buf[64]; + int len; + u8 *trace_type; + len = 0; + trace_type = (u8 *)filp->private_data; + if (*trace_type == fc_trc_flag->fnic_trace) + len = sprintf(buf, "%d\n", fnic_tracing_enabled); + else if (*trace_type == fc_trc_flag->fc_trace) + len = sprintf(buf, "%d\n", fnic_fc_tracing_enabled); + else if (*trace_type == fc_trc_flag->fc_clear) + len = sprintf(buf, "%d\n", fnic_fc_trace_cleared); + else + pr_err("fnic: Cannot read to any debugfs file\n"); + + return simple_read_from_buffer(ubuf, cnt, ppos, buf, len); +} + +/* + * fnic_trace_ctrl_write - + * Write to trace_enable, fc_trace_enable or + * fc_trace_clear debugfs file + * @filp: The file pointer to write from. + * @ubuf: The buffer to copy the data from. + * @cnt: The number of bytes to write. + * @ppos: The position in the file to start writing to. + * + * Description: + * This routine writes data from user buffer @ubuf to buffer @buf and + * sets fc_trace_enable ,tracing_enable or fnic_fc_trace_cleared + * value as per user input. + * + * Returns: + * This function returns the amount of data that was written. + */ +static ssize_t fnic_trace_ctrl_write(struct file *filp, + const char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + char buf[64]; + unsigned long val; + int ret; + u8 *trace_type; + trace_type = (u8 *)filp->private_data; + + if (cnt >= sizeof(buf)) + return -EINVAL; + + if (copy_from_user(&buf, ubuf, cnt)) + return -EFAULT; + + buf[cnt] = 0; + + ret = kstrtoul(buf, 10, &val); + if (ret < 0) + return ret; + + if (*trace_type == fc_trc_flag->fnic_trace) + fnic_tracing_enabled = val; + else if (*trace_type == fc_trc_flag->fc_trace) + fnic_fc_tracing_enabled = val; + else if (*trace_type == fc_trc_flag->fc_clear) + fnic_fc_trace_cleared = val; + else + pr_err("fnic: cannot write to any debugfs file\n"); + + (*ppos)++; + + return cnt; +} + +static const struct file_operations fnic_trace_ctrl_fops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = fnic_trace_ctrl_read, + .write = fnic_trace_ctrl_write, +}; + +/* + * fnic_trace_debugfs_open - Open the fnic trace log + * @inode: The inode pointer + * @file: The file pointer to attach the log output + * + * Description: + * This routine is the entry point for the debugfs open file operation. + * It allocates the necessary buffer for the log, fills the buffer from + * the in-memory log and then returns a pointer to that log in + * the private_data field in @file. + * + * Returns: + * This function returns zero if successful. On error it will return + * a negative error value. + */ +static int fnic_trace_debugfs_open(struct inode *inode, + struct file *file) +{ + fnic_dbgfs_t *fnic_dbg_prt; + u8 *rdata_ptr; + rdata_ptr = (u8 *)inode->i_private; + fnic_dbg_prt = kzalloc_obj(fnic_dbgfs_t); + if (!fnic_dbg_prt) + return -ENOMEM; + + if (*rdata_ptr == fc_trc_flag->fnic_trace) { + fnic_dbg_prt->buffer = vzalloc(array3_size(3, trace_max_pages, + PAGE_SIZE)); + if (!fnic_dbg_prt->buffer) { + kfree(fnic_dbg_prt); + return -ENOMEM; + } + fnic_dbg_prt->buffer_len = fnic_get_trace_data(fnic_dbg_prt); + } else { + fnic_dbg_prt->buffer = + vzalloc(array3_size(3, fnic_fc_trace_max_pages, + PAGE_SIZE)); + if (!fnic_dbg_prt->buffer) { + kfree(fnic_dbg_prt); + return -ENOMEM; + } + fnic_dbg_prt->buffer_len = + fnic_fc_trace_get_data(fnic_dbg_prt, *rdata_ptr); + } + file->private_data = fnic_dbg_prt; + + return 0; +} + +/* + * fnic_trace_debugfs_lseek - Seek through a debugfs file + * @file: The file pointer to seek through. + * @offset: The offset to seek to or the amount to seek by. + * @howto: Indicates how to seek. + * + * Description: + * This routine is the entry point for the debugfs lseek file operation. + * The @howto parameter indicates whether @offset is the offset to directly + * seek to, or if it is a value to seek forward or reverse by. This function + * figures out what the new offset of the debugfs file will be and assigns + * that value to the f_pos field of @file. + * + * Returns: + * This function returns the new offset if successful and returns a negative + * error if unable to process the seek. + */ +static loff_t fnic_trace_debugfs_lseek(struct file *file, + loff_t offset, + int howto) +{ + fnic_dbgfs_t *fnic_dbg_prt = file->private_data; + return fixed_size_llseek(file, offset, howto, + fnic_dbg_prt->buffer_len); +} + +/* + * fnic_trace_debugfs_read - Read a debugfs file + * @file: The file pointer to read from. + * @ubuf: The buffer to copy the data to. + * @nbytes: The number of bytes to read. + * @pos: The position in the file to start reading from. + * + * Description: + * This routine reads data from the buffer indicated in the private_data + * field of @file. It will start reading at @pos and copy up to @nbytes of + * data to @ubuf. + * + * Returns: + * This function returns the amount of data that was read (this could be + * less than @nbytes if the end of the file was reached). + */ +static ssize_t fnic_trace_debugfs_read(struct file *file, + char __user *ubuf, + size_t nbytes, + loff_t *pos) +{ + fnic_dbgfs_t *fnic_dbg_prt = file->private_data; + int rc = 0; + rc = simple_read_from_buffer(ubuf, nbytes, pos, + fnic_dbg_prt->buffer, + fnic_dbg_prt->buffer_len); + return rc; +} + +/* + * fnic_trace_debugfs_release - Release the buffer used to store + * debugfs file data + * @inode: The inode pointer + * @file: The file pointer that contains the buffer to release + * + * Description: + * This routine frees the buffer that was allocated when the debugfs + * file was opened. + * + * Returns: + * This function returns zero. + */ +static int fnic_trace_debugfs_release(struct inode *inode, + struct file *file) +{ + fnic_dbgfs_t *fnic_dbg_prt = file->private_data; + + vfree(fnic_dbg_prt->buffer); + kfree(fnic_dbg_prt); + return 0; +} + +static const struct file_operations fnic_trace_debugfs_fops = { + .owner = THIS_MODULE, + .open = fnic_trace_debugfs_open, + .llseek = fnic_trace_debugfs_lseek, + .read = fnic_trace_debugfs_read, + .release = fnic_trace_debugfs_release, +}; + +/* + * fnic_trace_debugfs_init - Initialize debugfs for fnic trace logging + * + * Description: + * When Debugfs is configured this routine sets up the fnic debugfs + * file system. If not already created, this routine will create the + * create file trace to log fnic trace buffer output into debugfs and + * it will also create file trace_enable to control enable/disable of + * trace logging into trace buffer. + */ +void fnic_trace_debugfs_init(void) +{ + fnic_trace_enable = debugfs_create_file("tracing_enable", + S_IFREG|S_IRUGO|S_IWUSR, + fnic_trace_debugfs_root, + &(fc_trc_flag->fnic_trace), + &fnic_trace_ctrl_fops); + + fnic_trace_debugfs_file = debugfs_create_file("trace", + S_IFREG|S_IRUGO|S_IWUSR, + fnic_trace_debugfs_root, + &(fc_trc_flag->fnic_trace), + &fnic_trace_debugfs_fops); +} + +/* + * fnic_trace_debugfs_terminate - Tear down debugfs infrastructure + * + * Description: + * When Debugfs is configured this routine removes debugfs file system + * elements that are specific to fnic trace logging. + */ +void fnic_trace_debugfs_terminate(void) +{ + debugfs_remove(fnic_trace_debugfs_file); + fnic_trace_debugfs_file = NULL; + + debugfs_remove(fnic_trace_enable); + fnic_trace_enable = NULL; +} + +/* + * fnic_fc_trace_debugfs_init - + * Initialize debugfs for fnic control frame trace logging + * + * Description: + * When Debugfs is configured this routine sets up the fnic_fc debugfs + * file system. If not already created, this routine will create the + * create file trace to log fnic fc trace buffer output into debugfs and + * it will also create file fc_trace_enable to control enable/disable of + * trace logging into trace buffer. + */ + +void fnic_fc_trace_debugfs_init(void) +{ + fnic_fc_trace_enable = debugfs_create_file("fc_trace_enable", + S_IFREG|S_IRUGO|S_IWUSR, + fnic_trace_debugfs_root, + &(fc_trc_flag->fc_trace), + &fnic_trace_ctrl_fops); + + fnic_fc_trace_clear = debugfs_create_file("fc_trace_clear", + S_IFREG|S_IRUGO|S_IWUSR, + fnic_trace_debugfs_root, + &(fc_trc_flag->fc_clear), + &fnic_trace_ctrl_fops); + + fnic_fc_rdata_trace_debugfs_file = + debugfs_create_file("fc_trace_rdata", + S_IFREG|S_IRUGO|S_IWUSR, + fnic_trace_debugfs_root, + &(fc_trc_flag->fc_normal_file), + &fnic_trace_debugfs_fops); + + fnic_fc_trace_debugfs_file = + debugfs_create_file("fc_trace", + S_IFREG|S_IRUGO|S_IWUSR, + fnic_trace_debugfs_root, + &(fc_trc_flag->fc_row_file), + &fnic_trace_debugfs_fops); +} + +/* + * fnic_fc_trace_debugfs_terminate - Tear down debugfs infrastructure + * + * Description: + * When Debugfs is configured this routine removes debugfs file system + * elements that are specific to fnic_fc trace logging. + */ + +void fnic_fc_trace_debugfs_terminate(void) +{ + debugfs_remove(fnic_fc_trace_debugfs_file); + fnic_fc_trace_debugfs_file = NULL; + + debugfs_remove(fnic_fc_rdata_trace_debugfs_file); + fnic_fc_rdata_trace_debugfs_file = NULL; + + debugfs_remove(fnic_fc_trace_enable); + fnic_fc_trace_enable = NULL; + + debugfs_remove(fnic_fc_trace_clear); + fnic_fc_trace_clear = NULL; +} + +/* + * fnic_reset_stats_open - Open the reset_stats file + * @inode: The inode pointer. + * @file: The file pointer to attach the stats reset flag. + * + * Description: + * This routine opens a debugsfs file reset_stats and stores i_private data + * to debug structure to retrieve later for while performing other + * file oprations. + * + * Returns: + * This function returns zero if successful. + */ +static int fnic_reset_stats_open(struct inode *inode, struct file *file) +{ + struct stats_debug_info *debug; + + debug = kzalloc_obj(struct stats_debug_info); + if (!debug) + return -ENOMEM; + + debug->i_private = inode->i_private; + + file->private_data = debug; + + return 0; +} + +/* + * fnic_reset_stats_read - Read a reset_stats debugfs file + * @filp: The file pointer to read from. + * @ubuf: The buffer to copy the data to. + * @cnt: The number of bytes to read. + * @ppos: The position in the file to start reading from. + * + * Description: + * This routine reads value of variable reset_stats + * and stores into local @buf. It will start reading file at @ppos and + * copy up to @cnt of data to @ubuf from @buf. + * + * Returns: + * This function returns the amount of data that was read. + */ +static ssize_t fnic_reset_stats_read(struct file *file, + char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + struct stats_debug_info *debug = file->private_data; + struct fnic *fnic = (struct fnic *)debug->i_private; + char buf[64]; + int len; + + len = sprintf(buf, "%u\n", fnic->reset_stats); + + return simple_read_from_buffer(ubuf, cnt, ppos, buf, len); +} + +/* + * fnic_reset_stats_write - Write to reset_stats debugfs file + * @filp: The file pointer to write from. + * @ubuf: The buffer to copy the data from. + * @cnt: The number of bytes to write. + * @ppos: The position in the file to start writing to. + * + * Description: + * This routine writes data from user buffer @ubuf to buffer @buf and + * resets cumulative stats of fnic. + * + * Returns: + * This function returns the amount of data that was written. + */ +static ssize_t fnic_reset_stats_write(struct file *file, + const char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + struct stats_debug_info *debug = file->private_data; + struct fnic *fnic = (struct fnic *)debug->i_private; + struct fnic_stats *stats = &fnic->fnic_stats; + u64 *io_stats_p = (u64 *)&stats->io_stats; + u64 *fw_stats_p = (u64 *)&stats->fw_stats; + char buf[64]; + unsigned long val; + int ret; + + if (cnt >= sizeof(buf)) + return -EINVAL; + + if (copy_from_user(&buf, ubuf, cnt)) + return -EFAULT; + + buf[cnt] = 0; + + ret = kstrtoul(buf, 10, &val); + if (ret < 0) + return ret; + + fnic->reset_stats = val; + + if (fnic->reset_stats) { + /* Skip variable is used to avoid descrepancies to Num IOs + * and IO Completions stats. Skip incrementing No IO Compls + * for pending active IOs after reset stats + */ + atomic64_set(&fnic->io_cmpl_skip, + atomic64_read(&stats->io_stats.active_ios)); + memset(&stats->abts_stats, 0, sizeof(struct abort_stats)); + memset(&stats->term_stats, 0, + sizeof(struct terminate_stats)); + memset(&stats->reset_stats, 0, sizeof(struct reset_stats)); + memset(&stats->misc_stats, 0, sizeof(struct misc_stats)); + memset(&stats->vlan_stats, 0, sizeof(struct vlan_stats)); + memset(io_stats_p+1, 0, + sizeof(struct io_path_stats) - sizeof(u64)); + memset(fw_stats_p+1, 0, + sizeof(struct fw_stats) - sizeof(u64)); + ktime_get_real_ts64(&stats->stats_timestamps.last_reset_time); + } + + (*ppos)++; + return cnt; +} + +/* + * fnic_reset_stats_release - Release the buffer used to store + * debugfs file data + * @inode: The inode pointer + * @file: The file pointer that contains the buffer to release + * + * Description: + * This routine frees the buffer that was allocated when the debugfs + * file was opened. + * + * Returns: + * This function returns zero. + */ +static int fnic_reset_stats_release(struct inode *inode, + struct file *file) +{ + struct stats_debug_info *debug = file->private_data; + kfree(debug); + return 0; +} + +/* + * fnic_stats_debugfs_open - Open the stats file for specific host + * and get fnic stats. + * @inode: The inode pointer. + * @file: The file pointer to attach the specific host statistics. + * + * Description: + * This routine opens a debugsfs file stats of specific host and print + * fnic stats. + * + * Returns: + * This function returns zero if successful. + */ +static int fnic_stats_debugfs_open(struct inode *inode, + struct file *file) +{ + struct fnic *fnic = inode->i_private; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + struct stats_debug_info *debug; + int buf_size = 2 * PAGE_SIZE; + + debug = kzalloc_obj(struct stats_debug_info); + if (!debug) + return -ENOMEM; + + debug->debug_buffer = vmalloc(buf_size); + if (!debug->debug_buffer) { + kfree(debug); + return -ENOMEM; + } + + debug->buf_size = buf_size; + memset((void *)debug->debug_buffer, 0, buf_size); + debug->buffer_len = fnic_get_stats_data(debug, fnic_stats); + debug->buffer_len += fnic_get_debug_info(debug, fnic); + + file->private_data = debug; + + return 0; +} + +/* + * fnic_stats_debugfs_read - Read a debugfs file + * @file: The file pointer to read from. + * @ubuf: The buffer to copy the data to. + * @nbytes: The number of bytes to read. + * @pos: The position in the file to start reading from. + * + * Description: + * This routine reads data from the buffer indicated in the private_data + * field of @file. It will start reading at @pos and copy up to @nbytes of + * data to @ubuf. + * + * Returns: + * This function returns the amount of data that was read (this could be + * less than @nbytes if the end of the file was reached). + */ +static ssize_t fnic_stats_debugfs_read(struct file *file, + char __user *ubuf, + size_t nbytes, + loff_t *pos) +{ + struct stats_debug_info *debug = file->private_data; + int rc = 0; + rc = simple_read_from_buffer(ubuf, nbytes, pos, + debug->debug_buffer, + debug->buffer_len); + return rc; +} + +/* + * fnic_stats_stats_release - Release the buffer used to store + * debugfs file data + * @inode: The inode pointer + * @file: The file pointer that contains the buffer to release + * + * Description: + * This routine frees the buffer that was allocated when the debugfs + * file was opened. + * + * Returns: + * This function returns zero. + */ +static int fnic_stats_debugfs_release(struct inode *inode, + struct file *file) +{ + struct stats_debug_info *debug = file->private_data; + vfree(debug->debug_buffer); + kfree(debug); + return 0; +} + +static const struct file_operations fnic_stats_debugfs_fops = { + .owner = THIS_MODULE, + .open = fnic_stats_debugfs_open, + .read = fnic_stats_debugfs_read, + .release = fnic_stats_debugfs_release, +}; + +static const struct file_operations fnic_reset_debugfs_fops = { + .owner = THIS_MODULE, + .open = fnic_reset_stats_open, + .read = fnic_reset_stats_read, + .write = fnic_reset_stats_write, + .release = fnic_reset_stats_release, +}; + +static const struct file_operations fnic_nvmef_debugfs_fops = { + .owner = THIS_MODULE, + .open = fnic_nvmef_debugfs_open, + .read = fnic_nvmef_debugfs_read, + .release = fnic_nvmef_debugfs_release, +}; + +/* + * fnic_stats_init - Initialize stats struct and create stats file per fnic + * + * Description: + * When Debugfs is configured this routine sets up the stats file per fnic + * It will create file stats and reset_stats under statistics/host# directory + * to log per fnic stats. + */ +int fnic_stats_debugfs_init(struct fnic *fnic) +{ + char name[16]; + + if (IS_FNIC_FCP_INITIATOR(fnic)) + snprintf(name, sizeof(name), "host%d", fnic->host->host_no); + else if (IS_FNIC_NVME_INITIATOR(fnic)) + snprintf(name, sizeof(name), "nvfnic%d", fnic->fnic_num); + + fnic->fnic_stats_debugfs_host = debugfs_create_dir(name, + fnic_stats_debugfs_root); + fnic->fnic_stats_debugfs_file = debugfs_create_file("stats", + S_IFREG|S_IRUGO|S_IWUSR, + fnic->fnic_stats_debugfs_host, + fnic, + &fnic_stats_debugfs_fops); + fnic->fnic_reset_debugfs_file = debugfs_create_file("reset_stats", + S_IFREG|S_IRUGO|S_IWUSR, + fnic->fnic_stats_debugfs_host, + fnic, + &fnic_reset_debugfs_fops); + return 0; +} + +/* + * fnic_stats_debugfs_remove - Tear down debugfs infrastructure of stats + * + * Description: + * When Debugfs is configured this routine removes debugfs file system + * elements that are specific to fnic stats. + */ +void fnic_stats_debugfs_remove(struct fnic *fnic) +{ + if (!fnic) + return; + + debugfs_remove(fnic->fnic_stats_debugfs_file); + fnic->fnic_stats_debugfs_file = NULL; + + debugfs_remove(fnic->fnic_reset_debugfs_file); + fnic->fnic_reset_debugfs_file = NULL; + + debugfs_remove(fnic->fnic_stats_debugfs_host); + fnic->fnic_stats_debugfs_host = NULL; +} + +void fnic_nvmef_debugfs_init(struct fnic *fnic) +{ + char name[16]; + + snprintf(name, sizeof(name), "host%d", fnic->fnic_num); + + fnic->fnic_nvmef_debugfs_host = debugfs_create_dir(name, + fnic_nvmef_debugfs_root); + fnic->fnic_nvmef_debugfs_file = debugfs_create_file("nvmef_info", + S_IFREG | 0444, + fnic->fnic_nvmef_debugfs_host, + fnic, + &fnic_nvmef_debugfs_fops); +} + +static int fnic_nvmef_debugfs_open(struct inode *inode, struct file *file) +{ + + struct fnic *fnic = inode->i_private; + struct fnic_nvmef_info *info; + int buf_size = 2 * PAGE_SIZE; + + info = kzalloc_obj(struct fnic_nvmef_info); + if (!info) + return -ENOMEM; + + info->info_buffer = vmalloc(buf_size); + if (!info->info_buffer) { + kfree(info); + return -ENOMEM; + } + + info->buf_size = buf_size; + memset((void *)info->info_buffer, 0, buf_size); + info->buffer_len = nvfnic_get_nvmef_info(fnic, info); + + file->private_data = info; + + return 0; +} + +static ssize_t fnic_nvmef_debugfs_read(struct file *file, + char __user *ubuf, + size_t nbytes, loff_t *pos) +{ + struct fnic_nvmef_info *info = file->private_data; + + return simple_read_from_buffer(ubuf, nbytes, pos, + info->info_buffer, info->buffer_len); +} + +static int fnic_nvmef_debugfs_release(struct inode *inode, struct file *file) +{ + struct fnic_nvmef_info *info = file->private_data; + + vfree(info->info_buffer); + kfree(info); + return 0; +} + +void fnic_nvmef_debugfs_remove(struct fnic *fnic) +{ + if (!fnic) + return; + + debugfs_remove(fnic->fnic_nvmef_debugfs_file); + fnic->fnic_nvmef_debugfs_file = NULL; + + debugfs_remove(fnic->fnic_nvmef_debugfs_host); + fnic->fnic_nvmef_debugfs_host = NULL; +} diff --git a/drivers/scsi/fnic/fnic_fcs.c b/drivers/scsi/fnic/fnic_fcs.c new file mode 100644 index 000000000..b00672ef8 --- /dev/null +++ b/drivers/scsi/fnic/fnic_fcs.c @@ -0,0 +1,1209 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#include <linux/errno.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/skbuff.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/if_ether.h> +#include <linux/if_vlan.h> +#include <linux/workqueue.h> +#include <scsi/fc/fc_fip.h> +#include <scsi/fc/fc_els.h> +#include <scsi/fc_frame.h> +#include <linux/etherdevice.h> +#include <scsi/scsi_transport_fc.h> +#include "fnic_io.h" +#include "fnic.h" +#include "fnic_fdls.h" +#include "fdls_fc.h" +#include "cq_enet_desc.h" +#include "cq_exch_desc.h" +#include "fip.h" + +#define MAX_RESET_WAIT_COUNT 64 + +struct workqueue_struct *fnic_event_queue; + +static uint8_t FCOE_ALL_FCF_MAC[6] = FC_FCOE_FLOGI_MAC; + +static inline bool fnic_is_nvme_frame(struct fc_frame_header *fchdr) +{ + return (fchdr->fh_type == FC_TYPE_NVME); +} + +/* + * Internal Functions + * This function will initialize the src_mac address to be + * used in outgoing frames + */ +static inline void fnic_fdls_set_fcoe_srcmac(struct fnic *fnic, + uint8_t *src_mac) +{ + FNIC_FCS_DBG(KERN_INFO, fnic, + "Setting src mac: %02x:%02x:%02x:%02x:%02x:%02x", + src_mac[0], src_mac[1], src_mac[2], src_mac[3], + src_mac[4], src_mac[5]); + + memcpy(fnic->iport.fpma, src_mac, 6); +} + +/* + * This function will initialize the dst_mac address to be + * used in outgoing frames + */ +static inline void fnic_fdls_set_fcoe_dstmac(struct fnic *fnic, + uint8_t *dst_mac) +{ + FNIC_FCS_DBG(KERN_INFO, fnic, + "Setting dst mac: %02x:%02x:%02x:%02x:%02x:%02x", + dst_mac[0], dst_mac[1], dst_mac[2], dst_mac[3], + dst_mac[4], dst_mac[5]); + + memcpy(fnic->iport.fcfmac, dst_mac, 6); +} + +void fnic_get_host_port_state(struct Scsi_Host *shost) +{ + struct fnic *fnic = *((struct fnic **) shost_priv(shost)); + struct fnic_iport_s *iport = &fnic->iport; + unsigned long flags; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + if (!fnic->link_status) + fc_host_port_state(shost) = FC_PORTSTATE_LINKDOWN; + else if (iport->state == FNIC_IPORT_STATE_READY) + fc_host_port_state(shost) = FC_PORTSTATE_ONLINE; + else + fc_host_port_state(shost) = FC_PORTSTATE_OFFLINE; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); +} + +void fnic_fdls_link_status_change(struct fnic *fnic, int linkup) +{ + struct fnic_iport_s *iport = &fnic->iport; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "link up: %d, usefip: %d", linkup, iport->usefip); + + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + + if (linkup) { + if (iport->usefip) { + iport->state = FNIC_IPORT_STATE_FIP; + FNIC_FCS_DBG(KERN_INFO, fnic, + "link up: %d, usefip: %d", linkup, iport->usefip); + fnic_fcoe_send_vlan_req(fnic); + } else { + iport->state = FNIC_IPORT_STATE_FABRIC_DISC; + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport->state: %d", iport->state); + fnic_fdls_disc_start(iport); + } + } else { + iport->state = FNIC_IPORT_STATE_LINK_WAIT; + if (!is_zero_ether_addr(iport->fpma)) + vnic_dev_del_addr(fnic->vdev, iport->fpma); + fnic_common_fip_cleanup(fnic); + fnic_fdls_link_down(iport); + + } + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); +} + + +/* + * FPMA can be either taken from ethhdr(dst_mac) or flogi resp + * or derive from FC_MAP and FCID combination. While it should be + * same, revisit this if there is any possibility of not-correct. + */ +void fnic_fdls_learn_fcoe_macs(struct fnic_iport_s *iport, void *rx_frame, + uint8_t *fcid) +{ + struct fnic *fnic = iport->fnic; + struct ethhdr *ethhdr = (struct ethhdr *) rx_frame; + uint8_t fcmac[6] = { 0x0E, 0xFC, 0x00, 0x00, 0x00, 0x00 }; + + memcpy(&fcmac[3], fcid, 3); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "learn fcoe: dst_mac: %02x:%02x:%02x:%02x:%02x:%02x", + ethhdr->h_dest[0], ethhdr->h_dest[1], + ethhdr->h_dest[2], ethhdr->h_dest[3], + ethhdr->h_dest[4], ethhdr->h_dest[5]); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "learn fcoe: fc_mac: %02x:%02x:%02x:%02x:%02x:%02x", + fcmac[0], fcmac[1], fcmac[2], fcmac[3], fcmac[4], + fcmac[5]); + + fnic_fdls_set_fcoe_srcmac(fnic, fcmac); + fnic_fdls_set_fcoe_dstmac(fnic, ethhdr->h_source); +} + +void fnic_fdls_init(struct fnic *fnic, int usefip) +{ + struct fnic_iport_s *iport = &fnic->iport; + + /* Initialize iPort structure */ + iport->state = FNIC_IPORT_STATE_INIT; + iport->fnic = fnic; + iport->usefip = usefip; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "iportsrcmac: %02x:%02x:%02x:%02x:%02x:%02x", + iport->hwmac[0], iport->hwmac[1], iport->hwmac[2], + iport->hwmac[3], iport->hwmac[4], iport->hwmac[5]); + + INIT_LIST_HEAD(&iport->tport_list); + + fnic_fdls_disc_init(iport); +} + +void fnic_handle_link(struct work_struct *work) +{ + struct fnic *fnic = container_of(work, struct fnic, link_work); + int old_link_status; + u32 old_link_down_cnt; + int max_count = 0; + + if (vnic_dev_get_intr_mode(fnic->vdev) != VNIC_DEV_INTR_MODE_MSI) + FNIC_FCS_DBG(KERN_INFO, fnic, + "Interrupt mode is not MSI\n"); + + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + + if (fnic->stop_rx_link_events) { + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + FNIC_FCS_DBG(KERN_INFO, fnic, + "Stop link rx events\n"); + return; + } + + /* Do not process if the fnic is already in transitional state */ + if ((fnic->state != FNIC_IN_ETH_MODE) + && (fnic->state != FNIC_IN_FC_MODE)) { + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + FNIC_FCS_DBG(KERN_INFO, fnic, + "fnic in transitional state: %d. link up: %d ignored", + fnic->state, vnic_dev_link_status(fnic->vdev)); + FNIC_FCS_DBG(KERN_INFO, fnic, + "Current link status: %d iport state: %d\n", + fnic->link_status, fnic->iport.state); + return; + } + + old_link_down_cnt = fnic->link_down_cnt; + old_link_status = fnic->link_status; + fnic->link_status = vnic_dev_link_status(fnic->vdev); + fnic->link_down_cnt = vnic_dev_link_down_cnt(fnic->vdev); + + while (fnic->reset_in_progress == IN_PROGRESS) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "fnic reset in progress. Link event needs to wait\n"); + + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + FNIC_FCS_DBG(KERN_INFO, fnic, + "waiting for reset completion\n"); + wait_for_completion_timeout(&fnic->reset_completion_wait, + msecs_to_jiffies(5000)); + FNIC_FCS_DBG(KERN_INFO, fnic, + "woken up from reset completion wait\n"); + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + + max_count++; + if (max_count >= MAX_RESET_WAIT_COUNT) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Rstth waited for too long. Skipping handle link event\n"); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + } + FNIC_FCS_DBG(KERN_INFO, fnic, + "Marking fnic reset in progress\n"); + fnic->reset_in_progress = IN_PROGRESS; + + if ((vnic_dev_get_intr_mode(fnic->vdev) != VNIC_DEV_INTR_MODE_MSI) || + (fnic->link_status != old_link_status)) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "old link status: %d link status: %d\n", + old_link_status, (int) fnic->link_status); + FNIC_FCS_DBG(KERN_INFO, fnic, + "old down count %d down count: %d\n", + old_link_down_cnt, (int) fnic->link_down_cnt); + } + + if (old_link_status == fnic->link_status) { + if (!fnic->link_status) { + /* DOWN -> DOWN */ + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + FNIC_FCS_DBG(KERN_INFO, fnic, + "down->down\n"); + } else { + if (old_link_down_cnt != fnic->link_down_cnt) { + /* UP -> DOWN -> UP */ + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + FNIC_FCS_DBG(KERN_INFO, fnic, + "up->down. Link down\n"); + fnic_fdls_link_status_change(fnic, 0); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "down->up. Link up\n"); + fnic_fdls_link_status_change(fnic, 1); + } else { + /* UP -> UP */ + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + FNIC_FCS_DBG(KERN_INFO, fnic, + "up->up\n"); + } + } + } else if (fnic->link_status) { + /* DOWN -> UP */ + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + FNIC_FCS_DBG(KERN_INFO, fnic, + "down->up. Link up\n"); + fnic_fdls_link_status_change(fnic, 1); + } else { + /* UP -> DOWN */ + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + FNIC_FCS_DBG(KERN_INFO, fnic, + "up->down. Link down\n"); + fnic_fdls_link_status_change(fnic, 0); + } + + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + fnic->reset_in_progress = NOT_IN_PROGRESS; + complete(&fnic->reset_completion_wait); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Marking fnic reset completion\n"); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); +} + +void fnic_handle_frame(struct work_struct *work) +{ + struct fnic *fnic = container_of(work, struct fnic, frame_work); + struct fnic_frame_list *cur_frame, *next; + int fchdr_offset = 0; + struct fc_frame_header *fchdr; + + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + list_for_each_entry_safe(cur_frame, next, &fnic->frame_queue, links) { + if (fnic->stop_rx_link_events) { + list_del(&cur_frame->links); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + mempool_free(cur_frame->fp, fnic->frame_recv_pool); + mempool_free(cur_frame, fnic->frame_elem_pool); + return; + } + + /* + * If we're in a transitional state, just re-queue and return. + * The queue will be serviced when we get to a stable state. + */ + if (fnic->state != FNIC_IN_FC_MODE && + fnic->state != FNIC_IN_ETH_MODE) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Cannot process frame in transitional state\n"); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + + list_del(&cur_frame->links); + + /* Frames from FCP_RQ will have ethhdrs stripped off */ + fchdr_offset = (cur_frame->rx_ethhdr_stripped) ? + 0 : FNIC_ETH_FCOE_HDRS_OFFSET; + + fchdr = (struct fc_frame_header *)((u8 *)cur_frame->fp + fchdr_offset); + if (IS_FNIC_NVME_INITIATOR(fnic) && fnic_is_nvme_frame(fchdr)) { + nvfnic_ls_rsp_recv(&fnic->iport, fchdr, + cur_frame->frame_len - fchdr_offset); + } else { + fnic_fdls_recv_frame(&fnic->iport, cur_frame->fp, + cur_frame->frame_len, fchdr_offset); + } + + mempool_free(cur_frame->fp, fnic->frame_recv_pool); + mempool_free(cur_frame, fnic->frame_elem_pool); + } + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); +} + +void fnic_handle_fip_frame(struct work_struct *work) +{ + struct fnic_frame_list *cur_frame, *next; + struct fnic *fnic = container_of(work, struct fnic, fip_frame_work); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Processing FIP frame\n"); + + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + list_for_each_entry_safe(cur_frame, next, &fnic->fip_frame_queue, + links) { + if (fnic->stop_rx_link_events) { + list_del(&cur_frame->links); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + mempool_free(cur_frame->fp, fnic->frame_recv_pool); + mempool_free(cur_frame, fnic->frame_elem_pool); + return; + } + + /* + * If we're in a transitional state, just re-queue and return. + * The queue will be serviced when we get to a stable state. + */ + if (fnic->state != FNIC_IN_FC_MODE && + fnic->state != FNIC_IN_ETH_MODE) { + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + + list_del(&cur_frame->links); + + if (fdls_fip_recv_frame(fnic, cur_frame->fp)) { + mempool_free(cur_frame->fp, fnic->frame_recv_pool); + mempool_free(cur_frame, fnic->frame_elem_pool); + } + } + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); +} + +/** + * fnic_import_rq_eth_pkt() - handle received FCoE or FIP frame. + * @fnic: fnic instance. + * @fp: Ethernet Frame. + */ +static inline int fnic_import_rq_eth_pkt(struct fnic *fnic, void *fp) +{ + struct ethhdr *eh; + struct fnic_frame_list *fip_fr_elem; + unsigned long flags; + + eh = (struct ethhdr *) fp; + if ((eh->h_proto == cpu_to_be16(ETH_P_FIP)) && (fnic->iport.usefip)) { + fip_fr_elem = mempool_alloc(fnic->frame_elem_pool, GFP_ATOMIC); + if (!fip_fr_elem) + return 0; + memset(fip_fr_elem, 0, sizeof(struct fnic_frame_list)); + fip_fr_elem->fp = fp; + spin_lock_irqsave(&fnic->fnic_lock, flags); + list_add_tail(&fip_fr_elem->links, &fnic->fip_frame_queue); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + queue_work(fnic_fip_queue, &fnic->fip_frame_work); + return 1; /* let caller know packet was used */ + } else + return 0; +} + +/** + * fnic_update_mac_locked() - set data MAC address and filters. + * @fnic: fnic instance. + * @new: newly-assigned FCoE MAC address. + * + * Called with the fnic lock held. + */ +void fnic_update_mac_locked(struct fnic *fnic, u8 *new) +{ + struct fnic_iport_s *iport = &fnic->iport; + u8 *ctl = iport->hwmac; + u8 *data = fnic->data_src_addr; + + if (is_zero_ether_addr(new)) + new = ctl; + if (ether_addr_equal(data, new)) + return; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Update MAC: %u\n", *new); + + if (!is_zero_ether_addr(data) && !ether_addr_equal(data, ctl)) + vnic_dev_del_addr(fnic->vdev, data); + + memcpy(data, new, ETH_ALEN); + if (!ether_addr_equal(new, ctl)) + vnic_dev_add_addr(fnic->vdev, new); +} + +static void fnic_rq_cmpl_frame_recv(struct vnic_rq *rq, struct cq_desc + *cq_desc, struct vnic_rq_buf *buf, + int skipped __attribute__((unused)), + void *opaque) +{ + struct fnic *fnic = vnic_dev_priv(rq->vdev); + uint8_t *fp; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + unsigned int ethhdr_stripped; + u8 type, color, eop, sop, ingress_port, vlan_stripped; + u8 fcoe_fnic_crc_ok = 1, fcoe_enc_error = 0; + u8 fcs_ok = 1, packet_error = 0; + u16 q_number, completed_index, vlan; + u32 rss_hash; + u16 checksum; + u8 csum_not_calc, rss_type, ipv4, ipv6, ipv4_fragment; + u8 tcp_udp_csum_ok, udp, tcp, ipv4_csum_ok; + u8 fcoe = 0, fcoe_sof, fcoe_eof; + u16 exchange_id, tmpl; + u8 sof = 0; + u8 eof = 0; + u32 fcp_bytes_written = 0; + u16 enet_bytes_written = 0; + u32 bytes_written = 0; + unsigned long flags; + struct fnic_frame_list *frame_elem = NULL; + struct ethhdr *eh; + + dma_unmap_single(&fnic->pdev->dev, buf->dma_addr, buf->len, + DMA_FROM_DEVICE); + fp = (uint8_t *) buf->os_buf; + buf->os_buf = NULL; + + cq_desc_dec(cq_desc, &type, &color, &q_number, &completed_index); + if (type == CQ_DESC_TYPE_RQ_FCP) { + cq_fcp_rq_desc_dec((struct cq_fcp_rq_desc *) cq_desc, &type, + &color, &q_number, &completed_index, &eop, &sop, + &fcoe_fnic_crc_ok, &exchange_id, &tmpl, + &fcp_bytes_written, &sof, &eof, &ingress_port, + &packet_error, &fcoe_enc_error, &fcs_ok, + &vlan_stripped, &vlan); + ethhdr_stripped = 1; + bytes_written = fcp_bytes_written; + } else if (type == CQ_DESC_TYPE_RQ_ENET) { + cq_enet_rq_desc_dec((struct cq_enet_rq_desc *) cq_desc, &type, + &color, &q_number, &completed_index, + &ingress_port, &fcoe, &eop, &sop, &rss_type, + &csum_not_calc, &rss_hash, &enet_bytes_written, + &packet_error, &vlan_stripped, &vlan, + &checksum, &fcoe_sof, &fcoe_fnic_crc_ok, + &fcoe_enc_error, &fcoe_eof, &tcp_udp_csum_ok, + &udp, &tcp, &ipv4_csum_ok, &ipv6, &ipv4, + &ipv4_fragment, &fcs_ok); + + ethhdr_stripped = 0; + bytes_written = enet_bytes_written; + + if (!fcs_ok) { + atomic64_inc(&fnic_stats->misc_stats.frame_errors); + FNIC_FCS_DBG(KERN_INFO, fnic, + "fnic 0x%p fcs error. Dropping packet.\n", fnic); + goto drop; + } + eh = (struct ethhdr *) fp; + if (eh->h_proto != cpu_to_be16(ETH_P_FCOE)) { + + if (fnic_import_rq_eth_pkt(fnic, fp)) + return; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Dropping h_proto 0x%x", + be16_to_cpu(eh->h_proto)); + goto drop; + } + } else { + /* wrong CQ type */ + FNIC_FCS_DBG(KERN_INFO, fnic, + "fnic rq_cmpl wrong cq type x%x\n", type); + goto drop; + } + + if (!fcs_ok || packet_error || !fcoe_fnic_crc_ok || fcoe_enc_error) { + atomic64_inc(&fnic_stats->misc_stats.frame_errors); + FNIC_FCS_DBG(KERN_INFO, fnic, + "fcoe %x fcsok %x pkterr %x ffco %x fee %x\n", + fcoe, fcs_ok, packet_error, + fcoe_fnic_crc_ok, fcoe_enc_error); + goto drop; + } + + spin_lock_irqsave(&fnic->fnic_lock, flags); + if (fnic->stop_rx_link_events) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + FNIC_FCS_DBG(KERN_INFO, fnic, + "fnic->stop_rx_link_events: %d\n", + fnic->stop_rx_link_events); + goto drop; + } + + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + frame_elem = mempool_alloc(fnic->frame_elem_pool, GFP_ATOMIC); + if (!frame_elem) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Failed to allocate memory for frame elem"); + goto drop; + } + memset(frame_elem, 0, sizeof(struct fnic_frame_list)); + frame_elem->fp = fp; + frame_elem->rx_ethhdr_stripped = ethhdr_stripped; + frame_elem->frame_len = bytes_written; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + list_add_tail(&frame_elem->links, &fnic->frame_queue); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + queue_work(fnic_event_queue, &fnic->frame_work); + return; + +drop: + mempool_free(fp, fnic->frame_recv_pool); +} + +static int fnic_rq_cmpl_handler_cont(struct vnic_dev *vdev, + struct cq_desc *cq_desc, u8 type, + u16 q_number, u16 completed_index, + void *opaque) +{ + struct fnic *fnic = vnic_dev_priv(vdev); + + vnic_rq_service(&fnic->rq[q_number], cq_desc, completed_index, + VNIC_RQ_RETURN_DESC, fnic_rq_cmpl_frame_recv, + NULL); + return 0; +} + +int fnic_rq_cmpl_handler(struct fnic *fnic, int rq_work_to_do) +{ + unsigned int tot_rq_work_done = 0, cur_work_done; + unsigned int i; + int err; + + for (i = 0; i < fnic->rq_count; i++) { + cur_work_done = vnic_cq_service(&fnic->cq[i], rq_work_to_do, + fnic_rq_cmpl_handler_cont, + NULL); + if (cur_work_done && fnic->stop_rx_link_events != 1) { + err = vnic_rq_fill(&fnic->rq[i], fnic_alloc_rq_frame); + if (err) + fnic_printk(KERN_ERR, fnic, + "fnic_alloc_rq_frame can't alloc" + " frame\n"); + } + tot_rq_work_done += cur_work_done; + } + + return tot_rq_work_done; +} + +/* + * This function is called once at init time to allocate and fill RQ + * buffers. Subsequently, it is called in the interrupt context after RQ + * buffer processing to replenish the buffers in the RQ + */ +int fnic_alloc_rq_frame(struct vnic_rq *rq) +{ + struct fnic *fnic = vnic_dev_priv(rq->vdev); + void *buf; + u16 len; + dma_addr_t pa; + int ret; + + len = FNIC_FRAME_HT_ROOM; + buf = mempool_alloc(fnic->frame_recv_pool, GFP_ATOMIC); + if (!buf) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Unable to allocate RQ buffer of size: %d\n", len); + return -ENOMEM; + } + + pa = dma_map_single(&fnic->pdev->dev, buf, len, DMA_FROM_DEVICE); + if (dma_mapping_error(&fnic->pdev->dev, pa)) { + ret = -ENOMEM; + FNIC_FCS_DBG(KERN_INFO, fnic, + "PCI mapping failed with error %d\n", ret); + goto free_buf; + } + + fnic_queue_rq_desc(rq, buf, pa, len); + return 0; +free_buf: + mempool_free(buf, fnic->frame_recv_pool); + return ret; +} + +void fnic_free_rq_buf(struct vnic_rq *rq, struct vnic_rq_buf *buf) +{ + void *rq_buf; + struct fnic *fnic = vnic_dev_priv(rq->vdev); + + if (WARN_ON(!buf)) + return; + + rq_buf = buf->os_buf; + dma_unmap_single(&fnic->pdev->dev, buf->dma_addr, buf->len, + DMA_FROM_DEVICE); + + mempool_free(rq_buf, fnic->frame_recv_pool); + buf->os_buf = NULL; +} + +/* + * Send FC frame. + */ +static int fnic_send_frame(struct fnic *fnic, void *frame, int frame_len) +{ + struct vnic_wq *wq = &fnic->wq[0]; + dma_addr_t pa; + int ret = 0; + unsigned long flags; + + pa = dma_map_single(&fnic->pdev->dev, frame, frame_len, DMA_TO_DEVICE); + if (dma_mapping_error(&fnic->pdev->dev, pa)) + return -ENOMEM; + + if ((fnic_fc_trace_set_data(fnic->fnic_num, + FNIC_FC_SEND | 0x80, (char *) frame, + frame_len)) != 0) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "fnic ctlr frame trace error"); + } + + spin_lock_irqsave(&fnic->wq_lock[0], flags); + + if (!vnic_wq_desc_avail(wq)) { + dma_unmap_single(&fnic->pdev->dev, pa, frame_len, DMA_TO_DEVICE); + FNIC_FCS_DBG(KERN_INFO, fnic, + "vnic work queue descriptor is not available"); + ret = -ENXIO; + goto fnic_send_frame_end; + } + + /* hw inserts cos value */ + fnic_queue_wq_desc(wq, frame, pa, frame_len, FC_EOF_T, + 0, fnic->vlan_id, 1, 1, 1); + +fnic_send_frame_end: + spin_unlock_irqrestore(&fnic->wq_lock[0], flags); + return ret; +} + +/** + * fdls_send_fcoe_frame - send a filled-in FC frame, filling in eth and FCoE + * info. This interface is used only in the non fast path. (login, fabric + * registrations etc.) + * + * @fnic: fnic instance + * @frame: frame structure with FC payload filled in + * @frame_size: length of the frame to be sent + * @srcmac: source mac address + * @dstmac: destination mac address + * + * Called with the fnic lock held. + */ +static int +fdls_send_fcoe_frame(struct fnic *fnic, void *frame, int frame_size, + uint8_t *srcmac, uint8_t *dstmac) +{ + struct ethhdr *pethhdr; + struct fcoe_hdr *pfcoe_hdr; + struct fnic_frame_list *frame_elem; + int len = frame_size; + struct fc_frame_header *fchdr = (struct fc_frame_header *) (frame + + FNIC_ETH_FCOE_HDRS_OFFSET); + + pethhdr = (struct ethhdr *) frame; + pethhdr->h_proto = cpu_to_be16(ETH_P_FCOE); + memcpy(pethhdr->h_source, srcmac, ETH_ALEN); + memcpy(pethhdr->h_dest, dstmac, ETH_ALEN); + + pfcoe_hdr = (struct fcoe_hdr *) (frame + sizeof(struct ethhdr)); + pfcoe_hdr->fcoe_sof = FC_SOF_I3; + + /* + * Queue frame if in a transitional state. + * This occurs while registering the Port_ID / MAC address after FLOGI. + */ + if ((fnic->state != FNIC_IN_FC_MODE) + && (fnic->state != FNIC_IN_ETH_MODE)) { + frame_elem = mempool_alloc(fnic->frame_elem_pool, GFP_ATOMIC); + if (!frame_elem) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Failed to allocate memory for frame elem"); + return -ENOMEM; + } + memset(frame_elem, 0, sizeof(struct fnic_frame_list)); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Queueing FC frame: sid/did/type/oxid = 0x%x/0x%x/0x%x/0x%x\n", + ntoh24(fchdr->fh_s_id), ntoh24(fchdr->fh_d_id), + fchdr->fh_type, FNIC_STD_GET_OX_ID(fchdr)); + frame_elem->fp = frame; + frame_elem->frame_len = len; + list_add_tail(&frame_elem->links, &fnic->tx_queue); + return 0; + } + + fnic_debug_dump_fc_frame(fnic, fchdr, frame_size, "Outgoing"); + + return fnic_send_frame(fnic, frame, len); +} + +int fnic_send_fcoe_frame(struct fnic_iport_s *iport, void *frame, + int frame_size) +{ + struct fnic *fnic = iport->fnic; + uint8_t *dstmac, *srcmac; + + /* If module unload is in-progress, don't send */ + if (fnic->in_remove) + return -ESHUTDOWN; + + if (iport->fabric.flags & FNIC_FDLS_FPMA_LEARNT) { + srcmac = iport->fpma; + dstmac = iport->fcfmac; + } else { + srcmac = iport->hwmac; + dstmac = FCOE_ALL_FCF_MAC; + } + + return fdls_send_fcoe_frame(fnic, frame, frame_size, srcmac, dstmac); +} + +int +fnic_send_fip_frame(struct fnic_iport_s *iport, void *frame, + int frame_size) +{ + struct fnic *fnic = iport->fnic; + + if (fnic->in_remove) + return -1; + + fnic_debug_dump_fip_frame(fnic, frame, frame_size, "Outgoing"); + return fnic_send_frame(fnic, frame, frame_size); +} + +/** + * fnic_flush_tx() - send queued frames. + * @work: pointer to work element + * + * Send frames that were waiting to go out in FC or Ethernet mode. + * Whenever changing modes we purge queued frames, so these frames should + * be queued for the stable mode that we're in, either FC or Ethernet. + * + * Called without fnic_lock held. + */ +void fnic_flush_tx(struct work_struct *work) +{ + struct fnic *fnic = container_of(work, struct fnic, flush_work); + struct fc_frame *fp; + struct fnic_frame_list *cur_frame, *next; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Flush queued frames"); + + list_for_each_entry_safe(cur_frame, next, &fnic->tx_queue, links) { + fp = cur_frame->fp; + list_del(&cur_frame->links); + fnic_send_frame(fnic, fp, cur_frame->frame_len); + mempool_free(cur_frame, fnic->frame_elem_pool); + } +} + +int +fnic_fdls_register_portid(struct fnic_iport_s *iport, u32 port_id, + void *fp) +{ + struct fnic *fnic = iport->fnic; + struct ethhdr *ethhdr; + int ret; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Setting port id: 0x%x fp: 0x%p fnic state: %d", port_id, + fp, fnic->state); + + if (fp) { + ethhdr = (struct ethhdr *) fp; + vnic_dev_add_addr(fnic->vdev, ethhdr->h_dest); + } + + /* Change state to reflect transition to FC mode */ + if (fnic->state == FNIC_IN_ETH_MODE || fnic->state == FNIC_IN_FC_MODE) + fnic->state = FNIC_IN_ETH_TRANS_FC_MODE; + else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Unexpected fnic state while processing FLOGI response\n"); + return -1; + } + + /* + * Send FLOGI registration to firmware to set up FC mode. + * The new address will be set up when registration completes. + */ + ret = fnic_flogi_reg_handler(fnic, port_id); + if (ret < 0) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "FLOGI registration error ret: %d fnic state: %d\n", + ret, fnic->state); + if (fnic->state == FNIC_IN_ETH_TRANS_FC_MODE) + fnic->state = FNIC_IN_ETH_MODE; + + return -1; + } + iport->fabric.flags |= FNIC_FDLS_FPMA_LEARNT; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "FLOGI registration success\n"); + return 0; +} + +void fnic_free_txq(struct fnic *fnic) +{ + struct fnic_frame_list *cur_frame, *next; + + list_for_each_entry_safe(cur_frame, next, &fnic->tx_queue, links) { + list_del(&cur_frame->links); + mempool_free(cur_frame->fp, fnic->frame_pool); + mempool_free(cur_frame, fnic->frame_elem_pool); + } +} + +void fnic_free_rxq(struct fnic *fnic) +{ + struct fnic_frame_list *cur_frame, *next; + + list_for_each_entry_safe(cur_frame, next, &fnic->frame_queue, links) { + list_del(&cur_frame->links); + mempool_free(cur_frame->fp, fnic->frame_recv_pool); + mempool_free(cur_frame, fnic->frame_elem_pool); + } + + if (fnic->config.flags & VFCF_FIP_CAPABLE) { + list_for_each_entry_safe(cur_frame, next, + &fnic->fip_frame_queue, links) { + list_del(&cur_frame->links); + mempool_free(cur_frame->fp, fnic->frame_recv_pool); + mempool_free(cur_frame, fnic->frame_elem_pool); + } + } +} + +static void fnic_wq_complete_frame_send(struct vnic_wq *wq, + struct cq_desc *cq_desc, + struct vnic_wq_buf *buf, void *opaque) +{ + struct fnic *fnic = vnic_dev_priv(wq->vdev); + + if (WARN_ON(!buf)) + return; + + dma_unmap_single(&fnic->pdev->dev, buf->dma_addr, buf->len, + DMA_TO_DEVICE); + mempool_free(buf->os_buf, fnic->frame_pool); + buf->os_buf = NULL; +} + +static int fnic_wq_cmpl_handler_cont(struct vnic_dev *vdev, + struct cq_desc *cq_desc, u8 type, + u16 q_number, u16 completed_index, + void *opaque) +{ + struct fnic *fnic = vnic_dev_priv(vdev); + unsigned long flags; + + spin_lock_irqsave(&fnic->wq_lock[q_number], flags); + vnic_wq_service(&fnic->wq[q_number], cq_desc, completed_index, + fnic_wq_complete_frame_send, NULL); + spin_unlock_irqrestore(&fnic->wq_lock[q_number], flags); + + return 0; +} + +int fnic_wq_cmpl_handler(struct fnic *fnic, int work_to_do) +{ + unsigned int wq_work_done = 0; + unsigned int i; + + for (i = 0; i < fnic->raw_wq_count; i++) { + wq_work_done += vnic_cq_service(&fnic->cq[fnic->rq_count+i], + work_to_do, + fnic_wq_cmpl_handler_cont, + NULL); + } + + return wq_work_done; +} + + +void fnic_free_wq_buf(struct vnic_wq *wq, struct vnic_wq_buf *buf) +{ + struct fnic *fnic = vnic_dev_priv(wq->vdev); + + if (WARN_ON(!buf)) + return; + + dma_unmap_single(&fnic->pdev->dev, buf->dma_addr, buf->len, + DMA_TO_DEVICE); + + mempool_free(buf->os_buf, fnic->frame_pool); + buf->os_buf = NULL; +} + +void +fnic_fdls_add_tport(struct fnic_iport_s *iport, struct fnic_tport_s *tport, + unsigned long flags) +{ + struct fnic *fnic = iport->fnic; + struct fc_rport *rport; + struct fc_rport_identifiers ids; + struct rport_dd_data_s *rdd_data; + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Adding rport fcid: 0x%x", tport->fcid); + + ids.node_name = tport->wwnn; + ids.port_name = tport->wwpn; + ids.port_id = tport->fcid; + ids.roles = FC_RPORT_ROLE_FCP_TARGET; + + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + rport = fc_remote_port_add(fnic->host, 0, &ids); + spin_lock_irqsave(&fnic->fnic_lock, flags); + if (!rport) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Failed to add rport for tport: 0x%x", tport->fcid); + return; + } + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Added rport fcid: 0x%x", tport->fcid); + + /* Mimic these assignments in queuecommand to avoid timing issues */ + rport->maxframe_size = FNIC_FC_MAX_PAYLOAD_LEN; + rport->supported_classes = FC_COS_CLASS3 | FC_RPORT_ROLE_FCP_TARGET; + rdd_data = rport->dd_data; + rdd_data->tport = tport; + rdd_data->iport = iport; + tport->rport = rport; + tport->flags |= FNIC_FDLS_SCSI_REGISTERED; +} + +void +fnic_fdls_remove_tport(struct fnic_iport_s *iport, + struct fnic_tport_s *tport, unsigned long flags) +{ + struct fnic *fnic = iport->fnic; + struct rport_dd_data_s *rdd_data; + + struct fc_rport *rport; + + if (!tport) + return; + + fdls_set_tport_state(tport, FDLS_TGT_STATE_OFFLINE); + rport = tport->rport; + + if (rport) { + /* tport resource release will be done + * after fnic_terminate_rport_io() + */ + tport->flags |= FNIC_FDLS_TPORT_DELETED; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + /* Interface to scsi_fc_transport */ + fc_remote_port_delete(rport); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + FNIC_FCS_DBG(KERN_INFO, fnic, + "Deregistered and freed tport fcid: 0x%x from scsi transport fc", + tport->fcid); + + /* + * the dd_data is allocated by fc transport + * of size dd_fcrport_size + */ + rdd_data = rport->dd_data; + rdd_data->tport = NULL; + rdd_data->iport = NULL; + list_del(&tport->links); + kfree(tport); + } else { + fnic_del_tport_timer_sync(fnic, tport); + list_del(&tport->links); + kfree(tport); + } +} + +void fnic_delete_fcp_tports(struct fnic *fnic) +{ + struct fnic_tport_s *tport, *next; + unsigned long flags; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + list_for_each_entry_safe(tport, next, &fnic->iport.tport_list, links) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "removing fcp rport fcid: 0x%x", tport->fcid); + fdls_set_tport_state(tport, FDLS_TGT_STATE_OFFLINING); + fnic_del_tport_timer_sync(fnic, tport); + if (IS_FNIC_FCP_INITIATOR(fnic)) + fnic_fdls_remove_tport(&fnic->iport, tport, flags); + else if (IS_FNIC_NVME_INITIATOR(fnic)) + nvfnic_delete_tport(&fnic->iport, tport, flags); + } + spin_unlock_irqrestore(&fnic->fnic_lock, flags); +} + +/** + * fnic_tport_event_handler() - Handler for remote port events + * in the tport_event_queue. + * + * @work: Handle to the remote port being dequeued + */ +void fnic_tport_event_handler(struct work_struct *work) +{ + struct fnic *fnic = container_of(work, struct fnic, tport_work); + struct fnic_tport_event_s *cur_evt, *next; + unsigned long flags; + struct fnic_tport_s *tport; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + list_for_each_entry_safe(cur_evt, next, &fnic->tport_event_list, links) { + tport = cur_evt->arg1; + switch (cur_evt->event) { + case TGT_EV_RPORT_ADD: + FNIC_FCS_DBG(KERN_INFO, fnic, + "Add rport event"); + if (tport->state == FDLS_TGT_STATE_READY) { + if (IS_FNIC_FCP_INITIATOR(fnic)) + fnic_fdls_add_tport(&fnic->iport, tport, flags); + else if (IS_FNIC_NVME_INITIATOR(fnic)) + nvfnic_add_tport(fnic, tport, flags); + } else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Target not ready. Add rport event dropped: 0x%x", + tport->fcid); + } + break; + case TGT_EV_RPORT_DEL: + FNIC_FCS_DBG(KERN_INFO, fnic, + "Remove rport event"); + if (tport->state == FDLS_TGT_STATE_OFFLINING) { + if (IS_FNIC_FCP_INITIATOR(fnic)) + fnic_fdls_remove_tport(&fnic->iport, tport, flags); + else if (IS_FNIC_NVME_INITIATOR(fnic)) + nvfnic_delete_tport(&fnic->iport, tport, flags); + } else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "remove rport event dropped tport fcid: 0x%x", + tport->fcid); + } + break; + case TGT_EV_TPORT_DELETE: + FNIC_FCS_DBG(KERN_INFO, fnic, + "Delete tport event"); + fdls_delete_tport(tport->iport, tport); + break; + default: + FNIC_FCS_DBG(KERN_INFO, fnic, + "Unknown tport event"); + break; + } + list_del(&cur_evt->links); + kfree(cur_evt); + } + spin_unlock_irqrestore(&fnic->fnic_lock, flags); +} + +void fnic_flush_tport_event_list(struct fnic *fnic) +{ + struct fnic_tport_event_s *cur_evt, *next; + unsigned long flags; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + list_for_each_entry_safe(cur_evt, next, &fnic->tport_event_list, links) { + list_del(&cur_evt->links); + kfree(cur_evt); + } + spin_unlock_irqrestore(&fnic->fnic_lock, flags); +} + +void fnic_reset_work_handler(struct work_struct *work) +{ + struct fnic *cur_fnic, *next_fnic; + unsigned long reset_fnic_list_lock_flags; + int host_reset_ret_code; + + /* + * This is a single thread. It is per fnic module, not per fnic + * All the fnics that need to be reset + * have been serialized via the reset fnic list. + */ + spin_lock_irqsave(&reset_fnic_list_lock, reset_fnic_list_lock_flags); + list_for_each_entry_safe(cur_fnic, next_fnic, &reset_fnic_list, links) { + list_del(&cur_fnic->links); + spin_unlock_irqrestore(&reset_fnic_list_lock, + reset_fnic_list_lock_flags); + + if (IS_FNIC_FCP_INITIATOR(cur_fnic)) { + dev_err(&cur_fnic->pdev->dev, + "fnic: <%d>: issuing a host reset\n", + cur_fnic->fnic_num); + host_reset_ret_code = fnic_host_reset(cur_fnic->host); + dev_err(&cur_fnic->pdev->dev, + "fnic: <%d>: returned from host reset with status: %d\n", + cur_fnic->fnic_num, host_reset_ret_code); + } + + spin_lock_irqsave(&cur_fnic->fnic_lock, cur_fnic->lock_flags); + cur_fnic->pc_rscn_handling_status = + PC_RSCN_HANDLING_NOT_IN_PROGRESS; + spin_unlock_irqrestore(&cur_fnic->fnic_lock, cur_fnic->lock_flags); + + spin_lock_irqsave(&reset_fnic_list_lock, + reset_fnic_list_lock_flags); + } + spin_unlock_irqrestore(&reset_fnic_list_lock, + reset_fnic_list_lock_flags); +} + +void fnic_fcpio_reset(struct fnic *fnic) +{ + unsigned long flags; + enum fnic_state old_state; + struct fnic_iport_s *iport = &fnic->iport; + DECLARE_COMPLETION_ONSTACK(fw_reset_done); + int time_remain; + + /* issue fw reset */ + spin_lock_irqsave(&fnic->fnic_lock, flags); + if (unlikely(fnic->state == FNIC_IN_FC_TRANS_ETH_MODE)) { + /* fw reset is in progress, poll for its completion */ + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + FNIC_FCS_DBG(KERN_INFO, fnic, + "fnic is in unexpected state: %d for fw_reset\n", + fnic->state); + return; + } + + old_state = fnic->state; + fnic->state = FNIC_IN_FC_TRANS_ETH_MODE; + + fnic_update_mac_locked(fnic, iport->hwmac); + fnic->fw_reset_done = &fw_reset_done; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + FNIC_FCS_DBG(KERN_INFO, fnic, + "Issuing fw reset\n"); + if (fnic_fw_reset_handler(fnic)) { + spin_lock_irqsave(&fnic->fnic_lock, flags); + if (fnic->state == FNIC_IN_FC_TRANS_ETH_MODE) + fnic->state = old_state; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + } else { + FNIC_FCS_DBG(KERN_INFO, fnic, + "Waiting for fw completion\n"); + time_remain = wait_for_completion_timeout(&fw_reset_done, + msecs_to_jiffies(FNIC_FW_RESET_TIMEOUT)); + FNIC_FCS_DBG(KERN_INFO, fnic, + "Woken up after fw completion timeout\n"); + if (time_remain == 0) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "FW reset completion timed out after %d ms\n", + FNIC_FW_RESET_TIMEOUT); + } + atomic64_inc(&fnic->fnic_stats.reset_stats.fw_reset_timeouts); + } + fnic->fw_reset_done = NULL; +} diff --git a/drivers/scsi/fnic/fnic_fdls.h b/drivers/scsi/fnic/fnic_fdls.h new file mode 100644 index 000000000..ce4b3aae7 --- /dev/null +++ b/drivers/scsi/fnic/fnic_fdls.h @@ -0,0 +1,463 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ + +#ifndef _FNIC_FDLS_H_ +#define _FNIC_FDLS_H_ + +#include "fnic_stats.h" +#include "fdls_fc.h" + +/* FDLS - Fabric discovery and login services + * -> VLAN discovery + * -> retry every retry delay seconds until it succeeds. + * <- List of VLANs + * + * -> Solicitation + * <- Solicitation response (Advertisement) + * + * -> FCF selection & FLOGI ( FLOGI timeout - 2 * E_D_TOV) + * <- FLOGI response + * + * -> FCF keep alive + * <- FCF keep alive + * + * -> PLOGI to FFFFFC (DNS) (PLOGI timeout - 2 * R_A_TOV) + * -> ABTS if timeout (ABTS tomeout - 2 * R_A_TOV) + * <- PLOGI response + * -> Retry PLOGI to FFFFFC (DNS) - Number of retries from vnic.cfg + * + * -> SCR to FFFFFC (DNS) (SCR timeout - 2 * R_A_TOV) + * -> ABTS if timeout (ABTS tomeout - 2 * R_A_TOV) + * <- SCR response + * -> Retry SCR - Number of retries 2 + * + * -> GPN_FT to FFFFFC (GPN_FT timeout - 2 * R_A_TOV)a + * -> Retry on BUSY until it succeeds + * -> Retry on BUSY until it succeeds + * -> 2 retries on timeout + * + * -> RFT_ID to FFFFFC (DNS) (RFT_ID timeout - 3 * R_A_TOV) + * -> ABTS if timeout (ABTS tomeout - 2 * R_A_TOV) + * -> Retry RFT_ID to FFFFFC (DNS) (Number of retries 2 ) + * -> Ignore if both retires fail. + * + * Session establishment with targets + * For each PWWN + * -> PLOGI to FCID of that PWWN (PLOGI timeout 2 * R_A_TOV) + * -> ABTS if timeout (ABTS tomeout - 2 * R_A_TOV) + * <- PLOGI response + * -> Retry PLOGI. Num retries using vnic.cfg + * + * -> PRLI to FCID of that PWWN (PRLI timeout 2 * R_A_TOV) + * -> ABTS if timeout (ABTS tomeout - 2 * R_A_TOV) + * <- PRLI response + * -> Retry PRLI. Num retries using vnic.cfg + * + */ + +#define FDLS_RETRY_COUNT 2 + +/* + * OXID encoding: + * bits 0-8: oxid idx - allocated from poool + * bits 9-13: oxid frame code from fnic_oxid_frame_type_e + * bits 14-15: all zeros + */ +#define FNIC_OXID_POOL_SZ (512) /* always power of 2 */ +#define FNIC_OXID_ENCODE(idx, frame_type) (frame_type | idx) +#define FNIC_FRAME_MASK 0xFE00 +#define FNIC_FRAME_TYPE(oxid) (oxid & FNIC_FRAME_MASK) +#define FNIC_OXID_IDX(oxid) ((oxid) & (FNIC_OXID_POOL_SZ - 1)) + +#define OXID_RECLAIM_TOV(iport) (2 * iport->r_a_tov) /* in milliseconds */ + +#define FNIC_FDLS_FABRIC_ABORT_ISSUED 0x1 +#define FNIC_FDLS_FPMA_LEARNT 0x2 + +/* tport flags */ +#define FNIC_FDLS_TPORT_IN_GPN_FT_LIST 0x1 +#define FNIC_FDLS_TGT_ABORT_ISSUED 0x2 +#define FNIC_FDLS_TPORT_SEND_ADISC 0x4 +#define FNIC_FDLS_RETRY_FRAME 0x8 +#define FNIC_FDLS_TPORT_BUSY 0x10 +#define FNIC_FDLS_TPORT_TERMINATING 0x20 +#define FNIC_FDLS_TPORT_DELETED 0x40 +#define FNIC_FDLS_NVME_REGISTERED 0x80 +#define FNIC_FDLS_NVME_TPORT_CLEANUP_PENDING 0x100 +#define FNIC_FDLS_SCSI_REGISTERED 0x200 +#define FNIC_TPORT_CAN_BE_FREED 0x400 + +/* Retry supported by rport(returned by prli service parameters) */ +#define FDLS_FC_RP_FLAGS_RETRY 0x1 + +#define fdls_set_state(_fdls_fabric, _state) ((_fdls_fabric)->state = _state) +#define fdls_get_state(_fdls_fabric) ((_fdls_fabric)->state) + +#define FNIC_FDMI_ACTIVE 0x8 +#define FNIC_LPORT_NVME_REGISTERED 0x4 +#define FNIC_FIRST_LINK_UP 0x2 + +#define fdls_set_tport_state(_tport, _state) (_tport->state = _state) +#define fdls_get_tport_state(_tport) (_tport->state) + +#define FNIC_PORTSPEED_10GBIT 1 +#define FNIC_FRAME_HT_ROOM (2148) +#define FNIC_FCOE_FRAME_MAXSZ (2112) + + +#define FNIC_FRAME_TYPE_FABRIC_FLOGI 0x1000 +#define FNIC_FRAME_TYPE_FABRIC_PLOGI 0x1200 +#define FNIC_FRAME_TYPE_FABRIC_RPN 0x1400 +#define FNIC_FRAME_TYPE_FABRIC_RFT 0x1600 +#define FNIC_FRAME_TYPE_FABRIC_RFF 0x1800 +#define FNIC_FRAME_TYPE_FABRIC_SCR 0x1A00 +#define FNIC_FRAME_TYPE_FABRIC_GPN_FT 0x1C00 +#define FNIC_FRAME_TYPE_FABRIC_LOGO 0x1E00 +#define FNIC_FRAME_TYPE_FDMI_PLOGI 0x2000 +#define FNIC_FRAME_TYPE_FDMI_RHBA 0x2200 +#define FNIC_FRAME_TYPE_FDMI_RPA 0x2400 +#define FNIC_FRAME_TYPE_TGT_PLOGI 0x2600 +#define FNIC_FRAME_TYPE_TGT_PRLI 0x2800 +#define FNIC_FRAME_TYPE_TGT_ADISC 0x2A00 +#define FNIC_FRAME_TYPE_TGT_LOGO 0x2C00 +#define FNIC_FRAME_TYPE_NVME_LS 0x3000 + +#define NVFNIC_FCPIO_TAG_POOL_SZ (2048) +#define NVFNIC_LS_REQ_OXID_POOL_SZ (64) +#define NVFNIC_LS_REQ_OXID_BASE (0x2500) +#define FNIC_LPORT_NVME_REGISTERED 0x4 + + +struct fnic_fip_fcf_s { + uint16_t vlan_id; + uint8_t fcf_mac[6]; + uint8_t fcf_priority; + uint32_t fka_adv_period; + uint8_t ka_disabled; +}; + +enum fnic_fdls_state_e { + FDLS_STATE_INIT = 0, + FDLS_STATE_LINKDOWN, + FDLS_STATE_FABRIC_LOGO, + FDLS_STATE_FLOGO_DONE, + FDLS_STATE_FABRIC_FLOGI, + FDLS_STATE_FABRIC_PLOGI, + FDLS_STATE_RPN_ID, + FDLS_STATE_REGISTER_FC4_TYPES, + FDLS_STATE_REGISTER_FC4_FEATURES, + FDLS_STATE_SCR, + FDLS_STATE_GPN_FT, + FDLS_STATE_TGT_DISCOVERY, + FDLS_STATE_RSCN_GPN_FT, + FDLS_STATE_SEND_GPNFT +}; + +struct fnic_fdls_fabric_s { + enum fnic_fdls_state_e state; + uint32_t flags; + struct list_head tport_list; /* List of discovered tports */ + struct timer_list retry_timer; + int del_timer_inprogress; + int del_fdmi_timer_inprogress; + int retry_counter; + int timer_pending; + int fdmi_retry; + struct timer_list fdmi_timer; + int fdmi_pending; +}; + +struct fnic_fdls_fip_s { + uint32_t state; + uint32_t flogi_retry; +}; + +/* Message to tport_event_handler */ +enum fnic_tgt_msg_id { + TGT_EV_NONE = 0, + TGT_EV_RPORT_ADD, + TGT_EV_RPORT_DEL, + TGT_EV_TPORT_DELETE, + TGT_EV_REMOVE +}; + +struct fnic_tport_event_s { + struct list_head links; + enum fnic_tgt_msg_id event; + void *arg1; +}; + +enum fdls_tgt_state_e { + FDLS_TGT_STATE_INIT = 0, + FDLS_TGT_STATE_PLOGI, + FDLS_TGT_STATE_PRLI, + FDLS_TGT_STATE_READY, + FDLS_TGT_STATE_LOGO_RECEIVED, + FDLS_TGT_STATE_ADISC, + FDL_TGT_STATE_PLOGO, + FDLS_TGT_STATE_OFFLINING, + FDLS_TGT_STATE_OFFLINE +}; + +struct fnic_tport_s { + struct list_head links; /* To link the tports */ + enum fdls_tgt_state_e state; + uint32_t flags; + uint32_t fcid; + uint64_t wwpn; + uint64_t wwnn; + uint16_t active_oxid; + uint16_t tgt_flags; + atomic_t in_flight; /* io counter */ + uint16_t max_payload_size; + uint16_t r_a_tov; + uint16_t e_d_tov; + uint16_t lun0_delay; + int max_concur_seqs; + uint32_t fcp_csp; + struct timer_list retry_timer; + int del_timer_inprogress; + int retry_counter; + int timer_pending; + unsigned int num_pending_cmds; + int nexus_restart_count; + int exch_reset_in_progress; + void *iport; + struct work_struct tport_del_work; + struct completion *tport_del_done; + struct fc_rport *rport; + char str_wwpn[20]; + char str_wwnn[20]; + struct list_head ls_req_list; + struct nvme_fc_remote_port *nv_rport; +}; + +/* OXID pool related structures */ +struct reclaim_entry_s { + struct list_head links; + /* oxid that needs to be freed after 2*r_a_tov */ + uint16_t oxid_idx; + /* in jiffies. Use this to waiting time */ + unsigned long expires; + unsigned long *bitmap; +}; + +/* used for allocating oxids for fabric and fdmi requests */ +struct fnic_oxid_pool_s { + DECLARE_BITMAP(bitmap, FNIC_OXID_POOL_SZ); + int sz; /* size of the pool or block */ + int next_idx; /* used for cycling through the oxid pool */ + + /* retry schedule free */ + DECLARE_BITMAP(pending_schedule_free, FNIC_OXID_POOL_SZ); + struct delayed_work schedule_oxid_free_retry; + + /* List of oxids that need to be freed and reclaimed. + * This list is shared by all the oxid pools + */ + struct list_head oxid_reclaim_list; + /* Work associated with reclaim list */ + struct delayed_work oxid_reclaim_work; +}; + +/* iport */ +enum fnic_iport_state_e { + FNIC_IPORT_STATE_INIT = 0, + FNIC_IPORT_STATE_LINK_WAIT, + FNIC_IPORT_STATE_FIP, + FNIC_IPORT_STATE_FABRIC_DISC, + FNIC_IPORT_STATE_READY +}; + +struct fnic_iport_s { + enum fnic_iport_state_e state; + struct fnic *fnic; + uint64_t boot_time; + uint32_t flags; + int usefip; + uint8_t hwmac[6]; /* HW MAC Addr */ + uint8_t fpma[6]; /* Fabric Provided MA */ + uint8_t fcfmac[6]; /* MAC addr of Fabric */ + uint16_t vlan_id; + uint32_t fcid; + + /* oxid pool */ + struct fnic_oxid_pool_s oxid_pool; + + /* + * fabric reqs are serialized and only one req at a time. + * Tracking the oxid for sending abort + */ + uint16_t active_oxid_fabric_req; + /* fdmi only */ + uint16_t active_oxid_fdmi_plogi; + uint16_t active_oxid_fdmi_rhba; + uint16_t active_oxid_fdmi_rpa; + + struct fnic_fip_fcf_s selected_fcf; + struct fnic_fdls_fip_s fip; + struct fnic_fdls_fabric_s fabric; + struct list_head tport_list; + /* list of tports for which we are yet to send PLOGO */ + struct list_head inprocess_tport_list; + struct list_head deleted_tport_list; + struct work_struct tport_event_work; + uint32_t e_d_tov; /* msec */ + uint32_t r_a_tov; /* msec */ + uint32_t link_supported_speeds; + uint32_t max_flogi_retries; + uint32_t max_plogi_retries; + uint32_t plogi_timeout; + uint32_t service_params; + uint64_t wwpn; + uint64_t wwnn; + uint16_t max_payload_size; + spinlock_t deleted_tport_lst_lock; + struct completion *flogi_reg_done; + struct fnic_iport_stats iport_stats; + char str_wwpn[20]; + char str_wwnn[20]; + + /* nvme */; + void *nvfnic_fcpio_tag[NVFNIC_FCPIO_TAG_POOL_SZ]; + struct nvme_fc_local_port *nv_lport; + struct nvme_fc_port_template *nv_tmpl; + struct fnic_oxid_pool_s ls_req_oxid_pool; + struct list_head fcpio_list; +}; + +struct rport_dd_data_s { + struct fnic_tport_s *tport; + struct fnic_iport_s *iport; +}; + +enum fnic_recv_frame_type_e { + FNIC_FABRIC_FLOGI_RSP = 1, + FNIC_FABRIC_PLOGI_RSP, + FNIC_FABRIC_RPN_RSP, + FNIC_FABRIC_RFT_RSP, + FNIC_FABRIC_RFF_RSP, + FNIC_FABRIC_SCR_RSP, + FNIC_FABRIC_GPN_FT_RSP, + FNIC_FABRIC_BLS_ABTS_RSP, + FNIC_FDMI_PLOGI_RSP, + FNIC_FDMI_REG_HBA_RSP, + FNIC_FDMI_RPA_RSP, + FNIC_FDMI_BLS_ABTS_RSP, + FNIC_FABRIC_LOGO_RSP, + + /* responses to target requests */ + FNIC_TPORT_PLOGI_RSP, + FNIC_TPORT_PRLI_RSP, + FNIC_TPORT_ADISC_RSP, + FNIC_TPORT_BLS_ABTS_RSP, + FNIC_TPORT_LOGO_RSP, + FNIC_LS_REQ_ABTS_RSP, + + /* unsolicited requests */ + FNIC_BLS_ABTS_REQ, + FNIC_ELS_PLOGI_REQ, + FNIC_ELS_RSCN_REQ, + FNIC_ELS_LOGO_REQ, + FNIC_ELS_ECHO_REQ, + FNIC_ELS_ADISC, + FNIC_ELS_RLS, + FNIC_ELS_RRQ, + FNIC_ELS_UNSUPPORTED_REQ, +}; + +enum fnic_port_speeds { + DCEM_PORTSPEED_NONE = 0, + DCEM_PORTSPEED_1G = 1000, + DCEM_PORTSPEED_2G = 2000, + DCEM_PORTSPEED_4G = 4000, + DCEM_PORTSPEED_8G = 8000, + DCEM_PORTSPEED_10G = 10000, + DCEM_PORTSPEED_16G = 16000, + DCEM_PORTSPEED_20G = 20000, + DCEM_PORTSPEED_25G = 25000, + DCEM_PORTSPEED_32G = 32000, + DCEM_PORTSPEED_40G = 40000, + DCEM_PORTSPEED_4x10G = 41000, + DCEM_PORTSPEED_50G = 50000, + DCEM_PORTSPEED_64G = 64000, + DCEM_PORTSPEED_100G = 100000, + DCEM_PORTSPEED_128G = 128000, +}; + +static inline bool fdls_tport_is_offline(struct fnic_tport_s *tport) +{ + return (tport->state == FDLS_TGT_STATE_OFFLINING || + tport->state == FDLS_TGT_STATE_OFFLINE); +} + +/* Function Declarations */ +/* fdls_disc.c */ +void fnic_fdls_disc_init(struct fnic_iport_s *iport); +void fnic_fdls_disc_start(struct fnic_iport_s *iport); +void fnic_fdls_recv_frame(struct fnic_iport_s *iport, void *rx_frame, + int len, int fchdr_offset); +void fnic_fdls_link_down(struct fnic_iport_s *iport); +int fdls_init_frame_pool(struct fnic_iport_s *iport); +uint8_t *fdls_alloc_frame(struct fnic_iport_s *iport); +uint16_t fdls_alloc_oxid(struct fnic_iport_s *iport, int oxid_frame_type, + uint16_t *active_oxid); +void fdls_free_oxid(struct fnic_iport_s *iport, + uint16_t oxid, uint16_t *active_oxid); +int fdls_send_ls_req_abts(struct fnic_iport_s *iport, + struct fnic_tport_s *tport, unsigned int oxid); +void fdls_tgt_logout(struct fnic_iport_s *iport, + struct fnic_tport_s *tport); +void fnic_del_fabric_timer_sync(struct fnic *fnic); +void fnic_del_tport_timer_sync(struct fnic *fnic, + struct fnic_tport_s *tport); +void fdls_send_fabric_logo(struct fnic_iport_s *iport); +int fnic_fdls_validate_and_get_frame_type(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr); +void fdls_send_tport_abts(struct fnic_iport_s *iport, + struct fnic_tport_s *tport); +bool fdls_delete_tport(struct fnic_iport_s *iport, + struct fnic_tport_s *tport); +void fdls_fdmi_timer_callback(struct timer_list *t); +void fdls_fdmi_retry_plogi(struct fnic_iport_s *iport); + +/* fnic_fcs.c */ +void fnic_fdls_init(struct fnic *fnic, int usefip); +int fnic_send_fcoe_frame(struct fnic_iport_s *iport, void *frame, + int frame_size); +void fnic_fcoe_send_vlan_req(struct fnic *fnic); +int fnic_send_fip_frame(struct fnic_iport_s *iport, + void *frame, int frame_size); +void fnic_fdls_learn_fcoe_macs(struct fnic_iport_s *iport, void *rx_frame, + uint8_t *fcid); +void fnic_fdls_add_tport(struct fnic_iport_s *iport, + struct fnic_tport_s *tport, unsigned long flags); +void fnic_fdls_remove_tport(struct fnic_iport_s *iport, + struct fnic_tport_s *tport, + unsigned long flags); +void fnic_fcpio_reset(struct fnic *fnic); + +/* fip.c */ +void fnic_fcoe_send_vlan_req(struct fnic *fnic); +void fnic_common_fip_cleanup(struct fnic *fnic); +int fdls_fip_recv_frame(struct fnic *fnic, void *frame); +void fnic_handle_fcs_ka_timer(struct timer_list *t); +void fnic_handle_enode_ka_timer(struct timer_list *t); +void fnic_handle_vn_ka_timer(struct timer_list *t); +void fnic_handle_fip_timer(struct timer_list *t); +extern void fdls_fabric_timer_callback(struct timer_list *t); + +/* fnic_scsi.c */ +extern void fdls_fabric_timer_callback(struct timer_list *t); +void fnic_rport_exch_reset(struct fnic *fnic, u32 fcid); +int fnic_fdls_register_portid(struct fnic_iport_s *iport, u32 port_id, + void *fp); +struct fnic_tport_s *fnic_find_tport_by_fcid(struct fnic_iport_s *iport, + uint32_t fcid); +struct fnic_tport_s *fnic_find_tport_by_wwpn(struct fnic_iport_s *iport, + uint64_t wwpn); + +#endif /* _FNIC_FDLS_H_ */ diff --git a/drivers/scsi/fnic/fnic_io.h b/drivers/scsi/fnic/fnic_io.h new file mode 100644 index 000000000..93a24efba --- /dev/null +++ b/drivers/scsi/fnic/fnic_io.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _FNIC_IO_H_ +#define _FNIC_IO_H_ + +#include <scsi/fc/fc_fs.h> +#include <scsi/fc/fc_fcp.h> +#include "fnic_fdls.h" +#include "fcpio.h" +#include <linux/nvme-fc-driver.h> + +#define FNIC_DFLT_SG_DESC_CNT 32 +#define FNIC_MAX_SG_DESC_CNT 256 /* Maximum descriptors per sgl */ +#define FNIC_SG_DESC_ALIGN 16 /* Descriptor address alignment */ + +#define NVME_STAT_ERROR 0x2 +#define NVME_STAT_TASK_SET_FULL 0x3 + +struct host_sg_desc { + __le64 addr; + __le32 len; + u32 _resvd; +}; + +struct fnic_dflt_sgl_list { + struct host_sg_desc sg_desc[FNIC_DFLT_SG_DESC_CNT]; +}; + +struct fnic_sgl_list { + struct host_sg_desc sg_desc[FNIC_MAX_SG_DESC_CNT]; +}; + +enum fnic_sgl_list_type { + FNIC_SGL_CACHE_DFLT = 0, /* cache with default size sgl */ + FNIC_SGL_CACHE_MAX, /* cache with max size sgl */ + FNIC_SGL_NUM_CACHES /* number of sgl caches */ +}; + +enum fnic_ioreq_state { + FNIC_IOREQ_NOT_INITED = 0, + FNIC_IOREQ_CMD_PENDING, + FNIC_IOREQ_ABTS_PENDING, + FNIC_IOREQ_ABTS_COMPLETE, + FNIC_IOREQ_CMD_COMPLETE, + FNIC_IOREQ_RESET_TERM, +}; + +struct fnic_io_req { + struct fnic_iport_s *iport; + struct fnic_tport_s *tport; + struct host_sg_desc *sgl_list; /* sgl list */ + void *sgl_list_alloc; /* sgl list address used for free */ + dma_addr_t sense_buf_pa; /* dma address for sense buffer*/ + dma_addr_t sgl_list_pa; /* dma address for sgl list */ + u16 sgl_cnt; + u8 sgl_type; /* device DMA descriptor list type */ + u8 sgl_mapped:1; /* set when sgl_list_pa is a valid DMA mapping */ + u8 io_completed:1; /* set to 1 when fw completes IO */ + u32 port_id; /* remote port DID */ + unsigned long start_time; /* in jiffies */ + u32 tag; + enum fnic_ioreq_state cmd_state; + u32 cmd_flags; + u32 abts_state; + struct completion *abts_done; /* completion for abts */ + struct completion *dr_done; /* completion for device reset */ + struct scsi_cmnd *sc; /* midlayer's cmd pointer */ + + struct vnic_wq_copy *wq; + struct llist_node nvfnic_io_cmpl; + struct nvmefc_fcp_req *fcp_req; + void (*done)(struct fnic_io_req *io_req); + unsigned long waitq_start_time; /* in jiffies */ + + struct timer_list admin_io_timer; + uint32_t status; + struct fnic_tag_t *tag_data; + struct fnic_io_event_s io_evt; +}; +#endif /* _FNIC_IO_H_ */ diff --git a/drivers/scsi/fnic/fnic_isr.c b/drivers/scsi/fnic/fnic_isr.c new file mode 100644 index 000000000..43149d831 --- /dev/null +++ b/drivers/scsi/fnic/fnic_isr.c @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <scsi/scsi_transport_fc.h> +#include <scsi/fc_frame.h> +#include "vnic_dev.h" +#include "vnic_intr.h" +#include "vnic_stats.h" +#include "fnic_io.h" +#include "fnic.h" + +static irqreturn_t fnic_isr_legacy(int irq, void *data) +{ + struct fnic *fnic = data; + u32 pba; + unsigned long work_done = 0; + + pba = vnic_intr_legacy_pba(fnic->legacy_pba); + if (!pba) + return IRQ_NONE; + + fnic->fnic_stats.misc_stats.last_isr_time = jiffies; + atomic64_inc(&fnic->fnic_stats.misc_stats.isr_count); + + if (pba & (1 << FNIC_INTX_NOTIFY)) { + vnic_intr_return_all_credits(&fnic->intr[FNIC_INTX_NOTIFY]); + fnic_handle_link_event(fnic); + } + + if (pba & (1 << FNIC_INTX_ERR)) { + vnic_intr_return_all_credits(&fnic->intr[FNIC_INTX_ERR]); + fnic_log_q_error(fnic); + } + + if (pba & (1 << FNIC_INTX_DUMMY)) { + atomic64_inc(&fnic->fnic_stats.misc_stats.intx_dummy); + vnic_intr_return_all_credits(&fnic->intr[FNIC_INTX_DUMMY]); + } + + if (pba & (1 << FNIC_INTX_WQ_RQ_COPYWQ)) { + work_done += fnic_wq_copy_cmpl_handler(fnic, io_completions, FNIC_MQ_CQ_INDEX); + work_done += fnic_wq_cmpl_handler(fnic, -1); + work_done += fnic_rq_cmpl_handler(fnic, -1); + + vnic_intr_return_credits(&fnic->intr[FNIC_INTX_WQ_RQ_COPYWQ], + work_done, + 1 /* unmask intr */, + 1 /* reset intr timer */); + } + + return IRQ_HANDLED; +} + +static irqreturn_t fnic_isr_msi(int irq, void *data) +{ + struct fnic *fnic = data; + unsigned long work_done = 0; + + fnic->fnic_stats.misc_stats.last_isr_time = jiffies; + atomic64_inc(&fnic->fnic_stats.misc_stats.isr_count); + + work_done += fnic_wq_copy_cmpl_handler(fnic, io_completions, FNIC_MQ_CQ_INDEX); + work_done += fnic_wq_cmpl_handler(fnic, -1); + work_done += fnic_rq_cmpl_handler(fnic, -1); + + vnic_intr_return_credits(&fnic->intr[0], + work_done, + 1 /* unmask intr */, + 1 /* reset intr timer */); + + return IRQ_HANDLED; +} + +static irqreturn_t fnic_isr_msix_rq(int irq, void *data) +{ + struct fnic *fnic = data; + unsigned long rq_work_done = 0; + + fnic->fnic_stats.misc_stats.last_isr_time = jiffies; + atomic64_inc(&fnic->fnic_stats.misc_stats.isr_count); + + rq_work_done = fnic_rq_cmpl_handler(fnic, -1); + vnic_intr_return_credits(&fnic->intr[FNIC_MSIX_RQ], + rq_work_done, + 1 /* unmask intr */, + 1 /* reset intr timer */); + + return IRQ_HANDLED; +} + +static irqreturn_t fnic_isr_msix_wq(int irq, void *data) +{ + struct fnic *fnic = data; + unsigned long wq_work_done = 0; + + fnic->fnic_stats.misc_stats.last_isr_time = jiffies; + atomic64_inc(&fnic->fnic_stats.misc_stats.isr_count); + + wq_work_done = fnic_wq_cmpl_handler(fnic, -1); + vnic_intr_return_credits(&fnic->intr[FNIC_MSIX_WQ], + wq_work_done, + 1 /* unmask intr */, + 1 /* reset intr timer */); + return IRQ_HANDLED; +} + +static irqreturn_t fnic_isr_msix_wq_copy(int irq, void *data) +{ + struct fnic *fnic = data; + unsigned long wq_copy_work_done = 0; + int i; + + fnic->fnic_stats.misc_stats.last_isr_time = jiffies; + atomic64_inc(&fnic->fnic_stats.misc_stats.isr_count); + + i = irq - fnic->msix[0].irq_num; + if (i >= fnic->wq_copy_count + fnic->copy_wq_base || + i < 0 || fnic->msix[i].irq_num != irq) { + for (i = fnic->copy_wq_base; i < fnic->wq_copy_count + fnic->copy_wq_base ; i++) { + if (fnic->msix[i].irq_num == irq) + break; + } + } + + wq_copy_work_done = fnic_wq_copy_cmpl_handler(fnic, io_completions, i); + vnic_intr_return_credits(&fnic->intr[i], + wq_copy_work_done, + 1 /* unmask intr */, + 1 /* reset intr timer */); + return IRQ_HANDLED; +} + +static irqreturn_t fnic_isr_msix_err_notify(int irq, void *data) +{ + struct fnic *fnic = data; + + fnic->fnic_stats.misc_stats.last_isr_time = jiffies; + atomic64_inc(&fnic->fnic_stats.misc_stats.isr_count); + + vnic_intr_return_all_credits(&fnic->intr[fnic->err_intr_offset]); + fnic_log_q_error(fnic); + fnic_handle_link_event(fnic); + + return IRQ_HANDLED; +} + +void fnic_free_intr(struct fnic *fnic) +{ + int i; + + switch (vnic_dev_get_intr_mode(fnic->vdev)) { + case VNIC_DEV_INTR_MODE_INTX: + case VNIC_DEV_INTR_MODE_MSI: + free_irq(pci_irq_vector(fnic->pdev, 0), fnic); + break; + + case VNIC_DEV_INTR_MODE_MSIX: + for (i = 0; i < ARRAY_SIZE(fnic->msix); i++) + if (fnic->msix[i].requested) + free_irq(pci_irq_vector(fnic->pdev, i), + fnic->msix[i].devid); + break; + + default: + break; + } +} + +int fnic_request_intr(struct fnic *fnic) +{ + int err = 0; + int i; + + switch (vnic_dev_get_intr_mode(fnic->vdev)) { + + case VNIC_DEV_INTR_MODE_INTX: + err = request_irq(pci_irq_vector(fnic->pdev, 0), + &fnic_isr_legacy, IRQF_SHARED, DRV_NAME, fnic); + break; + + case VNIC_DEV_INTR_MODE_MSI: + err = request_irq(pci_irq_vector(fnic->pdev, 0), &fnic_isr_msi, + 0, fnic->name, fnic); + break; + + case VNIC_DEV_INTR_MODE_MSIX: + + sprintf(fnic->msix[FNIC_MSIX_RQ].devname, + "%.11s-fcs-rq", fnic->name); + fnic->msix[FNIC_MSIX_RQ].isr = fnic_isr_msix_rq; + fnic->msix[FNIC_MSIX_RQ].devid = fnic; + + sprintf(fnic->msix[FNIC_MSIX_WQ].devname, + "%.11s-fcs-wq", fnic->name); + fnic->msix[FNIC_MSIX_WQ].isr = fnic_isr_msix_wq; + fnic->msix[FNIC_MSIX_WQ].devid = fnic; + + for (i = fnic->copy_wq_base; i < fnic->wq_copy_count + fnic->copy_wq_base; i++) { + sprintf(fnic->msix[i].devname, + "%.11s-scsi-wq-%d", fnic->name, i-FNIC_MSIX_WQ_COPY); + fnic->msix[i].isr = fnic_isr_msix_wq_copy; + fnic->msix[i].devid = fnic; + } + + sprintf(fnic->msix[fnic->err_intr_offset].devname, + "%.11s-err-notify", fnic->name); + fnic->msix[fnic->err_intr_offset].isr = + fnic_isr_msix_err_notify; + fnic->msix[fnic->err_intr_offset].devid = fnic; + + for (i = 0; i < fnic->intr_count; i++) { + fnic->msix[i].irq_num = pci_irq_vector(fnic->pdev, i); + + err = request_irq(fnic->msix[i].irq_num, + fnic->msix[i].isr, 0, + fnic->msix[i].devname, + fnic->msix[i].devid); + if (err) { + FNIC_ISR_DBG(KERN_ERR, fnic, + "request_irq failed with error: %d\n", + err); + fnic_free_intr(fnic); + break; + } + fnic->msix[i].requested = 1; + } + break; + + default: + break; + } + + return err; +} + +int fnic_set_intr_mode_msix(struct fnic *fnic) +{ + unsigned int n = ARRAY_SIZE(fnic->rq); + unsigned int m = ARRAY_SIZE(fnic->wq); + unsigned int o = ARRAY_SIZE(fnic->hw_copy_wq); + unsigned int min_irqs = n + m + 1 + 1; /*rq, raw wq, wq, err*/ + /* + * Make driver critical vectors unmanaged, or else it can get tied + * to an offline CPU. This can happen when hyper-threading is off. + */ + struct irq_affinity affd = { + .pre_vectors = n + m + 1, /* rq, raw wq, 1 ioq */ + .post_vectors = 1, /* err */ + }; + /* + * We need n RQs, m WQs, o Copy WQs, n+m+o CQs, and n+m+o+1 INTRs + * (last INTR is used for WQ/RQ errors and notification area) + */ + FNIC_ISR_DBG(KERN_INFO, fnic, + "rq-array size: %d wq-array size: %d copy-wq array size: %d\n", + n, m, o); + FNIC_ISR_DBG(KERN_INFO, fnic, + "rq_count: %d raw_wq_count: %d wq_copy_count: %d cq_count: %d\n", + fnic->rq_count, fnic->raw_wq_count, + fnic->wq_copy_count, fnic->cq_count); + + if (fnic->rq_count <= n && fnic->raw_wq_count <= m && + fnic->wq_copy_count <= o) { + int vec_count = 0; + int vecs = fnic->rq_count + fnic->raw_wq_count + fnic->wq_copy_count + 1; + + vec_count = pci_alloc_irq_vectors_affinity(fnic->pdev, min_irqs, + vecs, PCI_IRQ_MSIX|PCI_IRQ_AFFINITY, &affd); + FNIC_ISR_DBG(KERN_INFO, fnic, + "allocated %d MSI-X vectors\n", + vec_count); + + if (vec_count > 0) { + if (vec_count < vecs) { + FNIC_ISR_DBG(KERN_ERR, fnic, + "interrupts number mismatch: vec_count: %d vecs: %d\n", + vec_count, vecs); + if (vec_count < min_irqs) { + FNIC_ISR_DBG(KERN_ERR, fnic, + "no interrupts for copy wq\n"); + return 1; + } + } + + fnic->rq_count = n; + fnic->raw_wq_count = m; + fnic->copy_wq_base = fnic->rq_count + fnic->raw_wq_count; + fnic->wq_copy_count = vec_count - n - m - 1; + fnic->wq_count = fnic->raw_wq_count + fnic->wq_copy_count; + if (fnic->cq_count != vec_count - 1) { + FNIC_ISR_DBG(KERN_ERR, fnic, + "CQ count: %d does not match MSI-X vector count: %d\n", + fnic->cq_count, vec_count); + fnic->cq_count = vec_count - 1; + } + fnic->intr_count = vec_count; + fnic->err_intr_offset = fnic->rq_count + fnic->wq_count; + + FNIC_ISR_DBG(KERN_INFO, fnic, + "rq_count: %d raw_wq_count: %d copy_wq_base: %d\n", + fnic->rq_count, + fnic->raw_wq_count, fnic->copy_wq_base); + + FNIC_ISR_DBG(KERN_INFO, fnic, + "wq_copy_count: %d wq_count: %d cq_count: %d\n", + fnic->wq_copy_count, + fnic->wq_count, fnic->cq_count); + + FNIC_ISR_DBG(KERN_INFO, fnic, + "intr_count: %d err_intr_offset: %u\n", + fnic->intr_count, + fnic->err_intr_offset); + + vnic_dev_set_intr_mode(fnic->vdev, VNIC_DEV_INTR_MODE_MSIX); + FNIC_ISR_DBG(KERN_INFO, fnic, + "fnic using MSI-X\n"); + return 0; + } + } + return 1; +} + +int fnic_set_intr_mode(struct fnic *fnic) +{ + int ret_status = 0; + + /* + * Set interrupt mode (INTx, MSI, MSI-X) depending + * system capabilities. + * + * Try MSI-X first + */ + ret_status = fnic_set_intr_mode_msix(fnic); + if (ret_status == 0) + return ret_status; + + /* + * Next try MSI + * We need 1 RQ, 1 WQ, 1 WQ_COPY, 3 CQs, and 1 INTR + */ + if (fnic->rq_count >= 1 && + fnic->raw_wq_count >= 1 && + fnic->wq_copy_count >= 1 && + fnic->cq_count >= 3 && + fnic->intr_count >= 1 && + pci_alloc_irq_vectors(fnic->pdev, 1, 1, PCI_IRQ_MSI) == 1) { + fnic->rq_count = 1; + fnic->raw_wq_count = 1; + fnic->wq_copy_count = 1; + fnic->wq_count = 2; + fnic->cq_count = 3; + fnic->intr_count = 1; + fnic->err_intr_offset = 0; + + FNIC_ISR_DBG(KERN_DEBUG, fnic, + "Using MSI Interrupts\n"); + vnic_dev_set_intr_mode(fnic->vdev, VNIC_DEV_INTR_MODE_MSI); + + return 0; + } + + /* + * Next try INTx + * We need 1 RQ, 1 WQ, 1 WQ_COPY, 3 CQs, and 3 INTRs + * 1 INTR is used for all 3 queues, 1 INTR for queue errors + * 1 INTR for notification area + */ + + if (fnic->rq_count >= 1 && + fnic->raw_wq_count >= 1 && + fnic->wq_copy_count >= 1 && + fnic->cq_count >= 3 && + fnic->intr_count >= 3) { + + fnic->rq_count = 1; + fnic->raw_wq_count = 1; + fnic->wq_copy_count = 1; + fnic->cq_count = 3; + fnic->intr_count = 3; + + FNIC_ISR_DBG(KERN_DEBUG, fnic, + "Using Legacy Interrupts\n"); + vnic_dev_set_intr_mode(fnic->vdev, VNIC_DEV_INTR_MODE_INTX); + + return 0; + } + + vnic_dev_set_intr_mode(fnic->vdev, VNIC_DEV_INTR_MODE_UNKNOWN); + + return -EINVAL; +} + +void fnic_clear_intr_mode(struct fnic *fnic) +{ + pci_free_irq_vectors(fnic->pdev); + vnic_dev_set_intr_mode(fnic->vdev, VNIC_DEV_INTR_MODE_INTX); +} diff --git a/drivers/scsi/fnic/fnic_main.c b/drivers/scsi/fnic/fnic_main.c new file mode 100644 index 000000000..f13c381a6 --- /dev/null +++ b/drivers/scsi/fnic/fnic_main.c @@ -0,0 +1,1572 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#include <linux/module.h> +#include <linux/mempool.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/skbuff.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include <linux/if_ether.h> +#include <scsi/fc/fc_fip.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_transport.h> +#include <scsi/scsi_transport_fc.h> +#include <scsi/scsi_tcq.h> +#include <scsi/fc_frame.h> + +#include "vnic_dev.h" +#include "vnic_intr.h" +#include "vnic_stats.h" +#include "fnic_io.h" +#include "fnic.h" +#include "fnic_fdls.h" +#include "fdls_fc.h" + +#define PCI_DEVICE_ID_CISCO_FNIC 0x0045 + +/* Timer to poll notification area for events. Used for MSI interrupts */ +#define FNIC_NOTIFY_TIMER_PERIOD (2 * HZ) + +static struct kmem_cache *fnic_sgl_cache[FNIC_SGL_NUM_CACHES]; +static struct kmem_cache *fnic_io_req_cache; +static struct kmem_cache *fdls_frame_cache; +static struct kmem_cache *fdls_frame_elem_cache; +static struct kmem_cache *fdls_frame_recv_cache; +static LIST_HEAD(fnic_list); +static DEFINE_IDA(fnic_ida); + +struct work_struct reset_fnic_work; +LIST_HEAD(reset_fnic_list); +DEFINE_SPINLOCK(reset_fnic_list_lock); +DEFINE_SPINLOCK(fnic_list_lock); + +/* Supported devices by fnic module */ +static const struct pci_device_id fnic_id_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_CISCO, PCI_DEVICE_ID_CISCO_FNIC) }, + { 0, } +}; + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR("Abhijeet Joglekar <abjoglek@cisco.com>, " + "Joseph R. Eykholt <jeykholt@cisco.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(DRV_VERSION); +MODULE_DEVICE_TABLE(pci, fnic_id_table); + +unsigned int fnic_log_level; +module_param(fnic_log_level, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(fnic_log_level, "bit mask of fnic logging levels"); + +unsigned int fnic_fdmi_support = 1; +module_param(fnic_fdmi_support, int, 0644); +MODULE_PARM_DESC(fnic_fdmi_support, "FDMI support"); + +static unsigned int fnic_tgt_id_binding = 1; +module_param(fnic_tgt_id_binding, uint, 0644); +MODULE_PARM_DESC(fnic_tgt_id_binding, + "Target ID binding (0 for none. 1 for binding by WWPN (default))"); + +unsigned int io_completions = FNIC_DFLT_IO_COMPLETIONS; +module_param(io_completions, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(io_completions, "Max CQ entries to process at a time"); + +unsigned int fnic_trace_max_pages = 16; +module_param(fnic_trace_max_pages, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(fnic_trace_max_pages, "Total allocated memory pages " + "for fnic trace buffer"); + +unsigned int fnic_fc_trace_max_pages = 64; +module_param(fnic_fc_trace_max_pages, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(fnic_fc_trace_max_pages, + "Total allocated memory pages for fc trace buffer"); + +unsigned int nvme_dev_loss_tmo = 30; +module_param_named(nvme_dev_loss_tmo, nvme_dev_loss_tmo, uint, 0644); +MODULE_PARM_DESC(nvme_dev_loss_tmo, "configurable NVME dev loss timeout"); + +unsigned int nvme_max_ios_to_process = 16; +module_param(nvme_max_ios_to_process, uint, 0644); +MODULE_PARM_DESC(nvme_max_ios_to_process, + "Maximum number of NVME IOs to process per work queue"); + +static unsigned int fnic_max_qdepth = FNIC_DFLT_QUEUE_DEPTH; +module_param(fnic_max_qdepth, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(fnic_max_qdepth, "Queue depth to report for each LUN"); + +unsigned int pc_rscn_handling_feature_flag = PC_RSCN_HANDLING_FEATURE_ON; +module_param(pc_rscn_handling_feature_flag, uint, 0644); +MODULE_PARM_DESC(pc_rscn_handling_feature_flag, + "PCRSCN handling (0 for none. 1 to handle PCRSCN (default))"); + +struct workqueue_struct *reset_fnic_work_queue; +struct workqueue_struct *fnic_fip_queue; +struct workqueue_struct *fnic_cmpl_queue; + +static int fnic_sdev_init(struct scsi_device *sdev) +{ + struct fc_rport *rport = starget_to_rport(scsi_target(sdev)); + + if (!rport || fc_remote_port_chkready(rport)) + return -ENXIO; + + scsi_change_queue_depth(sdev, fnic_max_qdepth); + return 0; +} + +static const struct scsi_host_template fnic_host_template = { + .module = THIS_MODULE, + .name = DRV_NAME, + .queuecommand = fnic_queuecommand, + .eh_timed_out = fc_eh_timed_out, + .eh_abort_handler = fnic_abort_cmd, + .eh_device_reset_handler = fnic_device_reset, + .eh_host_reset_handler = fnic_eh_host_reset_handler, + .sdev_init = fnic_sdev_init, + .change_queue_depth = scsi_change_queue_depth, + .this_id = -1, + .cmd_per_lun = 3, + .can_queue = FNIC_DFLT_IO_REQ, + .sg_tablesize = FNIC_MAX_SG_DESC_CNT, + .max_sectors = 0xffff, + .shost_groups = fnic_host_groups, + .track_queue_depth = 1, + .cmd_size = sizeof(struct fnic_cmd_priv), + .map_queues = fnic_mq_map_queues_cpus, +}; + +static void +fnic_set_rport_dev_loss_tmo(struct fc_rport *rport, u32 timeout) +{ + if (timeout) + rport->dev_loss_tmo = timeout; + else + rport->dev_loss_tmo = 1; +} + +static void fnic_get_host_speed(struct Scsi_Host *shost); +static struct scsi_transport_template *fnic_fc_transport; +static struct fc_host_statistics *fnic_get_stats(struct Scsi_Host *); +static void fnic_reset_host_stats(struct Scsi_Host *); + +static struct fc_function_template fnic_fc_functions = { + + .show_host_node_name = 1, + .show_host_port_name = 1, + .show_host_supported_classes = 1, + .show_host_supported_fc4s = 1, + .show_host_active_fc4s = 1, + .show_host_maxframe_size = 1, + .show_host_port_id = 1, + .show_host_supported_speeds = 1, + .get_host_speed = fnic_get_host_speed, + .show_host_speed = 1, + .show_host_port_type = 1, + .get_host_port_state = fnic_get_host_port_state, + .show_host_port_state = 1, + .show_host_symbolic_name = 1, + .show_rport_maxframe_size = 1, + .show_rport_supported_classes = 1, + .show_host_fabric_name = 1, + .show_starget_node_name = 1, + .show_starget_port_name = 1, + .show_starget_port_id = 1, + .show_rport_dev_loss_tmo = 1, + .set_rport_dev_loss_tmo = fnic_set_rport_dev_loss_tmo, + .issue_fc_host_lip = fnic_issue_fc_host_lip, + .get_fc_host_stats = fnic_get_stats, + .reset_fc_host_stats = fnic_reset_host_stats, + .dd_fcrport_size = sizeof(struct rport_dd_data_s), + .terminate_rport_io = fnic_terminate_rport_io, + .bsg_request = NULL, +}; + +static void fnic_get_host_speed(struct Scsi_Host *shost) +{ + struct fnic *fnic = *((struct fnic **) shost_priv(shost)); + u32 port_speed = vnic_dev_port_speed(fnic->vdev); + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + + FNIC_MAIN_DBG(KERN_INFO, fnic, + "port_speed: %d Mbps", port_speed); + atomic64_set(&fnic_stats->misc_stats.port_speed_in_mbps, port_speed); + + /* Add in other values as they get defined in fw */ + switch (port_speed) { + case DCEM_PORTSPEED_1G: + fc_host_speed(shost) = FC_PORTSPEED_1GBIT; + break; + case DCEM_PORTSPEED_2G: + fc_host_speed(shost) = FC_PORTSPEED_2GBIT; + break; + case DCEM_PORTSPEED_4G: + fc_host_speed(shost) = FC_PORTSPEED_4GBIT; + break; + case DCEM_PORTSPEED_8G: + fc_host_speed(shost) = FC_PORTSPEED_8GBIT; + break; + case DCEM_PORTSPEED_10G: + fc_host_speed(shost) = FC_PORTSPEED_10GBIT; + break; + case DCEM_PORTSPEED_16G: + fc_host_speed(shost) = FC_PORTSPEED_16GBIT; + break; + case DCEM_PORTSPEED_20G: + fc_host_speed(shost) = FC_PORTSPEED_20GBIT; + break; + case DCEM_PORTSPEED_25G: + fc_host_speed(shost) = FC_PORTSPEED_25GBIT; + break; + case DCEM_PORTSPEED_32G: + fc_host_speed(shost) = FC_PORTSPEED_32GBIT; + break; + case DCEM_PORTSPEED_40G: + case DCEM_PORTSPEED_4x10G: + fc_host_speed(shost) = FC_PORTSPEED_40GBIT; + break; + case DCEM_PORTSPEED_50G: + fc_host_speed(shost) = FC_PORTSPEED_50GBIT; + break; + case DCEM_PORTSPEED_64G: + fc_host_speed(shost) = FC_PORTSPEED_64GBIT; + break; + case DCEM_PORTSPEED_100G: + fc_host_speed(shost) = FC_PORTSPEED_100GBIT; + break; + case DCEM_PORTSPEED_128G: + fc_host_speed(shost) = FC_PORTSPEED_128GBIT; + break; + default: + FNIC_MAIN_DBG(KERN_INFO, fnic, + "Unknown FC speed: %d Mbps", port_speed); + fc_host_speed(shost) = FC_PORTSPEED_UNKNOWN; + break; + } +} + +/* Placeholder function */ +static struct fc_host_statistics *fnic_get_stats(struct Scsi_Host *host) +{ + int ret; + struct fnic *fnic = *((struct fnic **) shost_priv(host)); + struct fc_host_statistics *stats = &fnic->fnic_stats.host_stats; + struct vnic_stats *vs; + unsigned long flags; + + if (time_before + (jiffies, fnic->stats_time + HZ / FNIC_STATS_RATE_LIMIT)) + return stats; + fnic->stats_time = jiffies; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + ret = vnic_dev_stats_dump(fnic->vdev, &fnic->stats); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + if (ret) { + FNIC_MAIN_DBG(KERN_DEBUG, fnic, + "fnic: Get vnic stats failed: 0x%x", ret); + return stats; + } + vs = fnic->stats; + stats->tx_frames = vs->tx.tx_unicast_frames_ok; + stats->tx_words = vs->tx.tx_unicast_bytes_ok / 4; + stats->rx_frames = vs->rx.rx_unicast_frames_ok; + stats->rx_words = vs->rx.rx_unicast_bytes_ok / 4; + stats->error_frames = vs->tx.tx_errors + vs->rx.rx_errors; + stats->dumped_frames = vs->tx.tx_drops + vs->rx.rx_drop; + stats->invalid_crc_count = vs->rx.rx_crc_errors; + stats->seconds_since_last_reset = + (jiffies - fnic->stats_reset_time) / HZ; + stats->fcp_input_megabytes = div_u64(fnic->fcp_input_bytes, 1000000); + stats->fcp_output_megabytes = div_u64(fnic->fcp_output_bytes, 1000000); + return stats; +} + +/* + * fnic_dump_fchost_stats + * note : dumps fc_statistics into system logs + */ +void fnic_dump_fchost_stats(struct Scsi_Host *host, + struct fc_host_statistics *stats) +{ + struct fnic *fnic = *((struct fnic **) shost_priv(host)); + + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: seconds since last reset = %llu\n", + stats->seconds_since_last_reset); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: tx frames = %llu\n", + stats->tx_frames); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: tx words = %llu\n", + stats->tx_words); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: rx frames = %llu\n", + stats->rx_frames); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: rx words = %llu\n", + stats->rx_words); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: lip count = %llu\n", + stats->lip_count); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: nos count = %llu\n", + stats->nos_count); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: error frames = %llu\n", + stats->error_frames); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: dumped frames = %llu\n", + stats->dumped_frames); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: link failure count = %llu\n", + stats->link_failure_count); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: loss of sync count = %llu\n", + stats->loss_of_sync_count); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: loss of signal count = %llu\n", + stats->loss_of_signal_count); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: prim seq protocol err count = %llu\n", + stats->prim_seq_protocol_err_count); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: invalid tx word count= %llu\n", + stats->invalid_tx_word_count); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: invalid crc count = %llu\n", + stats->invalid_crc_count); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: fcp input requests = %llu\n", + stats->fcp_input_requests); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: fcp output requests = %llu\n", + stats->fcp_output_requests); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: fcp control requests = %llu\n", + stats->fcp_control_requests); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: fcp input megabytes = %llu\n", + stats->fcp_input_megabytes); + FNIC_MAIN_NOTE(KERN_NOTICE, fnic, + "fnic: fcp output megabytes = %llu\n", + stats->fcp_output_megabytes); + return; +} + +/* + * fnic_reset_host_stats : clears host stats + * note : called when reset_statistics set under sysfs dir + */ +static void fnic_reset_host_stats(struct Scsi_Host *host) +{ + int ret; + struct fnic *fnic = *((struct fnic **) shost_priv(host)); + struct fc_host_statistics *stats; + unsigned long flags; + + /* dump current stats, before clearing them */ + stats = fnic_get_stats(host); + fnic_dump_fchost_stats(host, stats); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + ret = vnic_dev_stats_clear(fnic->vdev); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + if (ret) { + FNIC_MAIN_DBG(KERN_DEBUG, fnic, + "fnic: Reset vnic stats failed" + " 0x%x", ret); + return; + } + fnic->stats_reset_time = jiffies; + memset(stats, 0, sizeof(*stats)); + + return; +} + +void fnic_log_q_error(struct fnic *fnic) +{ + unsigned int i; + u32 error_status; + + for (i = 0; i < fnic->raw_wq_count; i++) { + error_status = ioread32(&fnic->wq[i].ctrl->error_status); + if (error_status) + dev_err(&fnic->pdev->dev, "WQ[%d] error_status %d\n", i, error_status); + } + + for (i = 0; i < fnic->rq_count; i++) { + error_status = ioread32(&fnic->rq[i].ctrl->error_status); + if (error_status) + dev_err(&fnic->pdev->dev, "RQ[%d] error_status %d\n", i, error_status); + } + + for (i = 0; i < fnic->wq_copy_count; i++) { + error_status = ioread32(&fnic->hw_copy_wq[i].ctrl->error_status); + if (error_status) + dev_err(&fnic->pdev->dev, "CWQ[%d] error_status %d\n", i, error_status); + } +} + +void fnic_handle_link_event(struct fnic *fnic) +{ + unsigned long flags; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + if (fnic->stop_rx_link_events) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return; + } + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + queue_work(fnic_event_queue, &fnic->link_work); + +} + +static int fnic_notify_set(struct fnic *fnic) +{ + int err; + + switch (vnic_dev_get_intr_mode(fnic->vdev)) { + case VNIC_DEV_INTR_MODE_INTX: + err = vnic_dev_notify_set(fnic->vdev, FNIC_INTX_NOTIFY); + break; + case VNIC_DEV_INTR_MODE_MSI: + err = vnic_dev_notify_set(fnic->vdev, -1); + break; + case VNIC_DEV_INTR_MODE_MSIX: + err = vnic_dev_notify_set(fnic->vdev, fnic->wq_copy_count + fnic->copy_wq_base); + break; + default: + dev_err(&fnic->pdev->dev, "Interrupt mode should be set up" + " before devcmd notify set %d\n", + vnic_dev_get_intr_mode(fnic->vdev)); + err = -1; + break; + } + + return err; +} + +static void fnic_notify_timer(struct timer_list *t) +{ + struct fnic *fnic = timer_container_of(fnic, t, notify_timer); + + fnic_handle_link_event(fnic); + mod_timer(&fnic->notify_timer, + round_jiffies(jiffies + FNIC_NOTIFY_TIMER_PERIOD)); +} + +static void fnic_notify_timer_start(struct fnic *fnic) +{ + switch (vnic_dev_get_intr_mode(fnic->vdev)) { + case VNIC_DEV_INTR_MODE_MSI: + /* + * Schedule first timeout immediately. The driver is + * initiatialized and ready to look for link up notification + */ + mod_timer(&fnic->notify_timer, jiffies); + break; + default: + /* Using intr for notification for INTx/MSI-X */ + break; + } +} + +static int fnic_dev_wait(struct vnic_dev *vdev, + int (*start)(struct vnic_dev *, int), + int (*finished)(struct vnic_dev *, int *), + int arg) +{ + unsigned long time; + int done; + int err; + int count; + + count = 0; + + err = start(vdev, arg); + if (err) + return err; + + /* Wait for func to complete. + * Sometime schedule_timeout_uninterruptible take long time + * to wake up so we do not retry as we are only waiting for + * 2 seconds in while loop. By adding count, we make sure + * we try atleast three times before returning -ETIMEDOUT + */ + time = jiffies + (HZ * 2); + do { + err = finished(vdev, &done); + count++; + if (err) + return err; + if (done) + return 0; + schedule_timeout_uninterruptible(HZ / 10); + } while (time_after(time, jiffies) || (count < 3)); + + return -ETIMEDOUT; +} + +static int fnic_cleanup(struct fnic *fnic) +{ + unsigned int i; + int err; + int raw_wq_rq_counts; + + vnic_dev_disable(fnic->vdev); + for (i = 0; i < fnic->intr_count; i++) + vnic_intr_mask(&fnic->intr[i]); + + for (i = 0; i < fnic->rq_count; i++) { + err = vnic_rq_disable(&fnic->rq[i]); + if (err) + return err; + } + for (i = 0; i < fnic->raw_wq_count; i++) { + err = vnic_wq_disable(&fnic->wq[i]); + if (err) + return err; + } + for (i = 0; i < fnic->wq_copy_count; i++) { + err = vnic_wq_copy_disable(&fnic->hw_copy_wq[i]); + if (err) + return err; + raw_wq_rq_counts = fnic->raw_wq_count + fnic->rq_count; + fnic_wq_copy_cmpl_handler(fnic, -1, i + raw_wq_rq_counts); + } + + /* Clean up completed IOs and FCS frames */ + fnic_wq_cmpl_handler(fnic, -1); + fnic_rq_cmpl_handler(fnic, -1); + + /* Clean up the IOs and FCS frames that have not completed */ + for (i = 0; i < fnic->raw_wq_count; i++) + vnic_wq_clean(&fnic->wq[i], fnic_free_wq_buf); + for (i = 0; i < fnic->rq_count; i++) + vnic_rq_clean(&fnic->rq[i], fnic_free_rq_buf); + for (i = 0; i < fnic->wq_copy_count; i++) + vnic_wq_copy_clean(&fnic->hw_copy_wq[i], + fnic_wq_copy_cleanup_handler); + + if (IS_FNIC_NVME_INITIATOR(fnic)) + nvfnic_flush_nvme_io_list(fnic); + + for (i = 0; i < fnic->cq_count; i++) + vnic_cq_clean(&fnic->cq[i]); + for (i = 0; i < fnic->intr_count; i++) + vnic_intr_clean(&fnic->intr[i]); + + mempool_destroy(fnic->io_req_pool); + mempool_destroy(fnic->frame_pool); + mempool_destroy(fnic->frame_elem_pool); + mempool_destroy(fnic->frame_recv_pool); + for (i = 0; i < FNIC_SGL_NUM_CACHES; i++) + mempool_destroy(fnic->io_sgl_pool[i]); + + return 0; +} + +static void fnic_iounmap(struct fnic *fnic) +{ + if (fnic->bar0.vaddr) + iounmap(fnic->bar0.vaddr); +} + +static void fnic_set_vlan(struct fnic *fnic, u16 vlan_id) +{ + vnic_dev_set_default_vlan(fnic->vdev, vlan_id); +} + +static void fnic_scsi_init(struct fnic *fnic) +{ + struct Scsi_Host *host = fnic->host; + + snprintf(fnic->name, sizeof(fnic->name) - 1, "%s%d", DRV_NAME, + host->host_no); + + host->transportt = fnic_fc_transport; +} + +static void fnic_free_ioreq_tables_mq(struct fnic *fnic) +{ + int hwq; + + for (hwq = 0; hwq < fnic->wq_copy_count; hwq++) + kfree(fnic->sw_copy_wq[hwq].io_req_table); +} + +static int fnic_scsi_drv_init(struct fnic *fnic) +{ + struct Scsi_Host *host = fnic->host; + int err; + struct pci_dev *pdev = fnic->pdev; + struct fnic_iport_s *iport = &fnic->iport; + int hwq; + + /* Configure maximum outstanding IO reqs*/ + if (fnic->config.io_throttle_count != FNIC_UCSM_DFLT_THROTTLE_CNT_BLD) + host->can_queue = min_t(u32, FNIC_MAX_IO_REQ, + max_t(u32, FNIC_MIN_IO_REQ, + fnic->config.io_throttle_count)); + + fnic->fnic_max_tag_id = host->can_queue; + host->max_lun = fnic->config.luns_per_tgt; + host->max_id = FNIC_MAX_FCP_TARGET; + host->max_cmd_len = FNIC_FCOE_MAX_CMD_LEN; + + host->nr_hw_queues = fnic->wq_copy_count; + + dev_info(&fnic->pdev->dev, "fnic: can_queue: %d max_lun: %llu", + host->can_queue, host->max_lun); + + dev_info(&fnic->pdev->dev, "fnic: max_id: %d max_cmd_len: %d nr_hw_queues: %d", + host->max_id, host->max_cmd_len, host->nr_hw_queues); + + for (hwq = 0; hwq < fnic->wq_copy_count; hwq++) { + fnic->sw_copy_wq[hwq].ioreq_table_size = fnic->fnic_max_tag_id; + fnic->sw_copy_wq[hwq].io_req_table = + kzalloc((fnic->sw_copy_wq[hwq].ioreq_table_size + 1) * + sizeof(struct fnic_io_req *), GFP_KERNEL); + + if (!fnic->sw_copy_wq[hwq].io_req_table) { + fnic_free_ioreq_tables_mq(fnic); + return -ENOMEM; + } + } + + dev_info(&fnic->pdev->dev, "fnic copy wqs: %d, Q0 ioreq table size: %d\n", + fnic->wq_copy_count, fnic->sw_copy_wq[0].ioreq_table_size); + + fnic_scsi_init(fnic); + + err = scsi_add_host(fnic->host, &pdev->dev); + if (err) { + dev_err(&fnic->pdev->dev, "fnic: scsi add host failed: aborting\n"); + return err; + } + fc_host_maxframe_size(fnic->host) = iport->max_payload_size; + fc_host_dev_loss_tmo(fnic->host) = + fnic->config.port_down_timeout / 1000; + sprintf(fc_host_symbolic_name(fnic->host), + DRV_NAME " v" DRV_VERSION " over %s", fnic->name); + fc_host_port_type(fnic->host) = FC_PORTTYPE_NPORT; + fc_host_node_name(fnic->host) = iport->wwnn; + fc_host_port_name(fnic->host) = iport->wwpn; + fc_host_supported_classes(fnic->host) = FC_COS_CLASS3; + memset(fc_host_supported_fc4s(fnic->host), 0, + sizeof(fc_host_supported_fc4s(fnic->host))); + fc_host_supported_fc4s(fnic->host)[2] = 1; + fc_host_supported_fc4s(fnic->host)[7] = 1; + fc_host_supported_speeds(fnic->host) = 0; + fc_host_supported_speeds(fnic->host) |= FC_PORTSPEED_8GBIT; + + dev_info(&fnic->pdev->dev, "shost_data: 0x%p\n", fnic->host->shost_data); + if (fnic->host->shost_data != NULL) { + if (fnic_tgt_id_binding == 0) { + dev_info(&fnic->pdev->dev, "Setting target binding to NONE\n"); + fc_host_tgtid_bind_type(fnic->host) = FC_TGTID_BIND_NONE; + } else { + dev_info(&fnic->pdev->dev, "Setting target binding to WWPN\n"); + fc_host_tgtid_bind_type(fnic->host) = FC_TGTID_BIND_BY_WWPN; + } + } + + fnic->io_req_pool = mempool_create_slab_pool(2, fnic_io_req_cache); + if (!fnic->io_req_pool) { + scsi_remove_host(fnic->host); + return -ENOMEM; + } + + return 0; +} + +static int fnic_nvme_drv_init(struct fnic *fnic) +{ + int ret; + int hwq; + + fnic->fnic_max_tag_id = NVFNIC_FCPIO_TAG_POOL_SZ; + + for (hwq = 0; hwq < fnic->wq_copy_count; hwq++) { + fnic->sw_copy_wq[hwq].ioreq_table_size = fnic->fnic_max_tag_id; + fnic->sw_copy_wq[hwq].io_req_table = + kzalloc((fnic->sw_copy_wq[hwq].ioreq_table_size + 1) * + sizeof(struct fnic_io_req *), GFP_KERNEL); + + if (!fnic->sw_copy_wq[hwq].io_req_table) { + fnic_free_ioreq_tables_mq(fnic); + return -ENOMEM; + } + } + + dev_info(&fnic->pdev->dev, "fnic copy wqs: %d, Q0 ioreq table size: %d\n", + fnic->wq_copy_count, fnic->sw_copy_wq[0].ioreq_table_size); + + if (sbitmap_init_node(&fnic->nvfnic_tag_map, NVFNIC_FCPIO_TAG_POOL_SZ, + -1, GFP_KERNEL, NUMA_NO_NODE, false, true)) { + dev_err(&fnic->pdev->dev, + "Unable to allocate tag pool\n"); + ret = -ENOMEM; + goto out_free_ioreq_tables; + } + + fnic->io_req_pool = mempool_create_slab_pool(2, fnic_io_req_cache); + if (!fnic->io_req_pool) { + ret = -ENOMEM; + goto out_free_sbitmap; + } + + init_llist_head(&fnic->nvme_io_event_llist); + INIT_WORK(&fnic->nvme_io_cmpl_work, nvfnic_nvme_iodone_work); + + ret = nvfnic_add_lport(fnic); + if (ret) + goto out_free_req_pool; + + return 0; +out_free_req_pool: + mempool_destroy(fnic->io_req_pool); +out_free_sbitmap: + sbitmap_free(&fnic->nvfnic_tag_map); +out_free_ioreq_tables: + fnic_free_ioreq_tables_mq(fnic); + return ret; +} + +static void fnic_mq_init_queue_map(struct fnic *fnic, + struct blk_mq_queue_map *qmap) +{ + unsigned int cpu; + + for_each_possible_cpu(cpu) + qmap->mq_map[cpu] = 0; +} + +void fnic_mq_map_queues_cpus(struct Scsi_Host *host) +{ + const struct cpumask *mask; + unsigned int queue, cpu; + struct fnic *fnic = *((struct fnic **) shost_priv(host)); + struct pci_dev *l_pdev = fnic->pdev; + int intr_mode = fnic->config.intr_mode; + struct blk_mq_queue_map *qmap = &host->tag_set.map[HCTX_TYPE_DEFAULT]; + + if (intr_mode == VNIC_DEV_INTR_MODE_MSI || intr_mode == VNIC_DEV_INTR_MODE_INTX) { + FNIC_MAIN_DBG(KERN_ERR, fnic, + "intr_mode is not msix\n"); + return; + } + + FNIC_MAIN_DBG(KERN_INFO, fnic, + "qmap->nr_queues: %d\n", qmap->nr_queues); + + if (l_pdev == NULL) { + FNIC_MAIN_DBG(KERN_ERR, fnic, + "l_pdev is null\n"); + return; + } + + fnic_mq_init_queue_map(fnic, qmap); + + /* + * Setup CPU to Queue mapping for all managed MSI-X IRQs. + * Q0 is driver critical and non-managed, hence start from Q1. + */ + for (queue = 1; queue < qmap->nr_queues; queue++) { + int irq_num = pci_irq_vector(fnic->pdev, + queue + FNIC_PCI_OFFSET); + + if (irq_num < 0) + continue; + + mask = pci_irq_get_affinity(fnic->pdev, + queue + FNIC_PCI_OFFSET); + if (!mask) { + shost_printk(KERN_ERR, host, + "failed to get irq_affinity map for queue:%d\n", irq_num); + continue; + } + FNIC_MAIN_DBG(KERN_INFO, fnic, + "got irq_affinity map for %d:\n", irq_num); + for_each_cpu(cpu, mask) { + qmap->mq_map[cpu] = qmap->queue_offset + queue; + FNIC_MAIN_DBG(KERN_INFO, fnic, + "[Q%d] cpu:%d <=> irq:%d\n", + queue, cpu, irq_num); + } + } +} + +static int fnic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct Scsi_Host *host = NULL; + struct fnic *fnic; + mempool_t *pool; + struct fnic_iport_s *iport; + int err = 0; + int fnic_id = 0; + int i; + unsigned long flags; + char *desc, *subsys_desc; + int len; + + /* + * Allocate fnic + */ + fnic = kzalloc_obj(struct fnic); + if (!fnic) { + err = -ENOMEM; + goto err_out_fnic_alloc; + } + + iport = &fnic->iport; + + fnic_id = ida_alloc(&fnic_ida, GFP_KERNEL); + if (fnic_id < 0) { + dev_err(&pdev->dev, "Unable to alloc fnic ID\n"); + err = fnic_id; + goto err_out_ida_alloc; + } + + fnic->pdev = pdev; + fnic->fnic_num = fnic_id; + + /* Find model name from PCIe subsys ID */ + if (fnic_get_desc_by_devid(pdev, &desc, &subsys_desc) == 0) { + dev_info(&fnic->pdev->dev, "Model: %s\n", subsys_desc); + + /* Update FDMI model */ + fnic->subsys_desc_len = strlen(subsys_desc); + len = ARRAY_SIZE(fnic->subsys_desc); + if (fnic->subsys_desc_len > len) + fnic->subsys_desc_len = len; + memcpy(fnic->subsys_desc, subsys_desc, fnic->subsys_desc_len); + dev_info(&fnic->pdev->dev, "FDMI Model: %s\n", fnic->subsys_desc); + } else { + fnic->subsys_desc_len = 0; + dev_info(&fnic->pdev->dev, "Model: %s subsys_id: 0x%04x\n", "Unknown", + pdev->subsystem_device); + } + + err = pci_enable_device(pdev); + if (err) { + dev_err(&fnic->pdev->dev, "Cannot enable PCI device, aborting.\n"); + goto err_out_pci_enable_device; + } + + err = pci_request_regions(pdev, DRV_NAME); + if (err) { + dev_err(&fnic->pdev->dev, "Cannot enable PCI resources, aborting\n"); + goto err_out_pci_request_regions; + } + + pci_set_master(pdev); + + /* Query PCI controller on system for DMA addressing + * limitation for the device. Try 47-bit first, and + * fail to 32-bit. Cisco VIC supports 47 bits only. + */ + err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(47)); + if (err) { + err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (err) { + dev_err(&fnic->pdev->dev, "No usable DMA configuration " + "aborting\n"); + goto err_out_set_dma_mask; + } + } + + /* Map vNIC resources from BAR0 */ + if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM)) { + dev_err(&fnic->pdev->dev, "BAR0 not memory-map'able, aborting.\n"); + err = -ENODEV; + goto err_out_map_bar; + } + + fnic->bar0.vaddr = pci_iomap(pdev, 0, 0); + fnic->bar0.bus_addr = pci_resource_start(pdev, 0); + fnic->bar0.len = pci_resource_len(pdev, 0); + + if (!fnic->bar0.vaddr) { + dev_err(&fnic->pdev->dev, "Cannot memory-map BAR0 res hdr, " + "aborting.\n"); + err = -ENODEV; + goto err_out_fnic_map_bar; + } + + fnic->vdev = vnic_dev_register(NULL, fnic, pdev, &fnic->bar0); + if (!fnic->vdev) { + dev_err(&fnic->pdev->dev, "vNIC registration failed, " + "aborting.\n"); + err = -ENODEV; + goto err_out_dev_register; + } + + err = vnic_dev_cmd_init(fnic->vdev); + if (err) { + dev_err(&fnic->pdev->dev, "vnic_dev_cmd_init() returns %d, aborting\n", + err); + goto err_out_dev_cmd_init; + } + + err = fnic_dev_wait(fnic->vdev, vnic_dev_open, + vnic_dev_open_done, CMD_OPENF_RQ_ENABLE_THEN_POST); + if (err) { + dev_err(&fnic->pdev->dev, "vNIC dev open failed, aborting.\n"); + goto err_out_dev_open; + } + + err = vnic_dev_init(fnic->vdev, 0); + if (err) { + dev_err(&fnic->pdev->dev, "vNIC dev init failed, aborting.\n"); + goto err_out_dev_init; + } + + err = vnic_dev_mac_addr(fnic->vdev, iport->hwmac); + if (err) { + dev_err(&fnic->pdev->dev, "vNIC get MAC addr failed\n"); + goto err_out_dev_mac_addr; + } + /* set data_src for point-to-point mode and to keep it non-zero */ + memcpy(fnic->data_src_addr, iport->hwmac, ETH_ALEN); + + /* Get vNIC configuration */ + err = fnic_get_vnic_config(fnic); + if (err) { + dev_err(&fnic->pdev->dev, "Get vNIC configuration failed, " + "aborting.\n"); + goto err_out_fnic_get_config; + } + + switch (fnic->config.flags & FNIC_ROLE_CONFIG_MASK) { + case VFCF_FC_INITIATOR: + { + host = + scsi_host_alloc(&fnic_host_template, + sizeof(struct fnic *)); + if (!host) { + dev_err(&fnic->pdev->dev, "Unable to allocate scsi host\n"); + err = -ENOMEM; + goto err_out_scsi_host_alloc; + } + *((struct fnic **) shost_priv(host)) = fnic; + + fnic->host = host; + fnic->role = FNIC_ROLE_FCP_INITIATOR; + dev_info(&fnic->pdev->dev, "fnic: %d is scsi initiator\n", + fnic->fnic_num); + } + break; + case VFCF_FC_TARGET: + dev_info(&fnic->pdev->dev, + "fnic: %d is scsi target\n", + fnic->fnic_num); + err = -EOPNOTSUPP; + goto err_out_fnic_role; + case VFCF_FC_NVME_INITIATOR: + fnic_nvmef_debugfs_init(fnic); + fnic->role = FNIC_ROLE_NVME_INITIATOR; + dev_info(&fnic->pdev->dev, "fnic: %d is NVME initiator\n", + fnic->fnic_num); + break; + case VFCF_FC_NVME_TARGET: + dev_info(&fnic->pdev->dev, + "fnic: %d is NVME target\n", + fnic->fnic_num); + err = -EOPNOTSUPP; + goto err_out_fnic_role; + default: + dev_info(&fnic->pdev->dev, + "fnic: %d has no role defined (0x%x)\n", + fnic->fnic_num, fnic->config.flags & FNIC_ROLE_CONFIG_MASK); + err = -EINVAL; + goto err_out_fnic_role; + } + + /* Setup PCI resources */ + pci_set_drvdata(pdev, fnic); + + fnic_get_res_counts(fnic); + + err = fnic_set_intr_mode(fnic); + if (err) { + dev_err(&fnic->pdev->dev, "Failed to set intr mode, " + "aborting.\n"); + goto err_out_fnic_set_intr_mode; + } + + err = fnic_alloc_vnic_resources(fnic); + if (err) { + dev_err(&fnic->pdev->dev, "Failed to alloc vNIC resources, " + "aborting.\n"); + goto err_out_fnic_alloc_vnic_res; + } + dev_info(&fnic->pdev->dev, "fnic copy wqs: %d, Q0 ioreq table size: %d\n", + fnic->wq_copy_count, fnic->sw_copy_wq[0].ioreq_table_size); + + /* initialize all fnic locks */ + spin_lock_init(&fnic->fnic_lock); + + for (i = 0; i < FNIC_WQ_MAX; i++) + spin_lock_init(&fnic->wq_lock[i]); + + for (i = 0; i < FNIC_WQ_COPY_MAX; i++) { + spin_lock_init(&fnic->wq_copy_lock[i]); + fnic->wq_copy_desc_low[i] = DESC_CLEAN_LOW_WATERMARK; + fnic->fw_ack_recd[i] = 0; + fnic->fw_ack_index[i] = -1; + } + + pool = mempool_create_slab_pool(2, fnic_sgl_cache[FNIC_SGL_CACHE_DFLT]); + if (!pool) { + err = -ENOMEM; + goto err_out_free_resources; + } + fnic->io_sgl_pool[FNIC_SGL_CACHE_DFLT] = pool; + + pool = mempool_create_slab_pool(2, fnic_sgl_cache[FNIC_SGL_CACHE_MAX]); + if (!pool) { + err = -ENOMEM; + goto err_out_free_dflt_pool; + } + fnic->io_sgl_pool[FNIC_SGL_CACHE_MAX] = pool; + + pool = mempool_create_slab_pool(FDLS_MIN_FRAMES, fdls_frame_cache); + if (!pool) { + err = -ENOMEM; + goto err_out_fdls_frame_pool; + } + fnic->frame_pool = pool; + + pool = mempool_create_slab_pool(FDLS_MIN_FRAME_ELEM, + fdls_frame_elem_cache); + if (!pool) { + err = -ENOMEM; + goto err_out_fdls_frame_elem_pool; + } + fnic->frame_elem_pool = pool; + + pool = mempool_create_slab_pool(FDLS_MIN_FRAMES, + fdls_frame_recv_cache); + if (!pool) { + err = -ENOMEM; + goto err_out_fdls_frame_recv_pool; + } + fnic->frame_recv_pool = pool; + + /* setup vlan config, hw inserts vlan header */ + fnic->vlan_hw_insert = 1; + fnic->vlan_id = 0; + + if (fnic->config.flags & VFCF_FIP_CAPABLE) { + dev_info(&fnic->pdev->dev, "firmware supports FIP\n"); + /* enable directed and multicast */ + vnic_dev_packet_filter(fnic->vdev, 1, 1, 0, 0, 0); + vnic_dev_add_addr(fnic->vdev, FIP_ALL_ENODE_MACS); + vnic_dev_add_addr(fnic->vdev, iport->hwmac); + spin_lock_init(&fnic->vlans_lock); + INIT_WORK(&fnic->fip_frame_work, fnic_handle_fip_frame); + INIT_LIST_HEAD(&fnic->fip_frame_queue); + INIT_LIST_HEAD(&fnic->vlan_list); + timer_setup(&fnic->retry_fip_timer, fnic_handle_fip_timer, 0); + timer_setup(&fnic->fcs_ka_timer, fnic_handle_fcs_ka_timer, 0); + timer_setup(&fnic->enode_ka_timer, fnic_handle_enode_ka_timer, 0); + timer_setup(&fnic->vn_ka_timer, fnic_handle_vn_ka_timer, 0); + fnic->set_vlan = fnic_set_vlan; + } else { + dev_info(&fnic->pdev->dev, "firmware uses non-FIP mode\n"); + } + fnic->state = FNIC_IN_FC_MODE; + + atomic_set(&fnic->in_flight, 0); + fnic->state_flags = FNIC_FLAGS_NONE; + + /* Enable hardware stripping of vlan header on ingress */ + fnic_set_nic_config(fnic, 0, 0, 0, 0, 0, 0, 1); + + /* Setup notification buffer area */ + err = fnic_notify_set(fnic); + if (err) { + dev_err(&fnic->pdev->dev, "Failed to alloc notify buffer, aborting.\n"); + goto err_out_fnic_notify_set; + } + + /* Setup notify timer when using MSI interrupts */ + if (vnic_dev_get_intr_mode(fnic->vdev) == VNIC_DEV_INTR_MODE_MSI) + timer_setup(&fnic->notify_timer, fnic_notify_timer, 0); + + /* allocate RQ buffers and post them to RQ*/ + for (i = 0; i < fnic->rq_count; i++) { + err = vnic_rq_fill(&fnic->rq[i], fnic_alloc_rq_frame); + if (err) { + dev_err(&fnic->pdev->dev, "fnic_alloc_rq_frame can't alloc " + "frame\n"); + goto err_out_alloc_rq_buf; + } + } + + init_completion(&fnic->reset_completion_wait); + + /* Start local port initialization */ + iport->max_flogi_retries = fnic->config.flogi_retries; + iport->max_plogi_retries = fnic->config.plogi_retries; + iport->plogi_timeout = fnic->config.plogi_timeout; + if (IS_FNIC_FCP_INITIATOR(fnic)) { + iport->service_params = (FNIC_FCP_SP_INITIATOR | + FNIC_FCP_SP_RD_XRDY_DIS | FNIC_FCP_SP_CONF_CMPL); + if (fnic->config.flags & VFCF_FCP_SEQ_LVL_ERR) + iport->service_params |= FNIC_FCP_SP_RETRY; + } else if (IS_FNIC_NVME_INITIATOR(fnic)) { + iport->service_params = (FNIC_NVME_SP_INITIATOR); + if (fnic->config.flags & VFCF_FCP_SEQ_LVL_ERR) + iport->service_params |= FNIC_NVME_SP_SLER; + } + + iport->boot_time = jiffies; + iport->e_d_tov = fnic->config.ed_tov; + iport->r_a_tov = fnic->config.ra_tov; + iport->link_supported_speeds = FNIC_PORTSPEED_10GBIT; + iport->wwpn = fnic->config.port_wwn; + iport->wwnn = fnic->config.node_wwn; + + iport->max_payload_size = fnic->config.maxdatafieldsize; + + if ((iport->max_payload_size < FNIC_MIN_DATA_FIELD_SIZE) || + (iport->max_payload_size > FNIC_FC_MAX_PAYLOAD_LEN) || + ((iport->max_payload_size % 4) != 0)) { + iport->max_payload_size = FNIC_FC_MAX_PAYLOAD_LEN; + } + + iport->flags |= FNIC_FIRST_LINK_UP; + + timer_setup(&(iport->fabric.retry_timer), fdls_fabric_timer_callback, + 0); + + fnic->stats_reset_time = jiffies; + + INIT_WORK(&fnic->link_work, fnic_handle_link); + INIT_WORK(&fnic->frame_work, fnic_handle_frame); + INIT_WORK(&fnic->tport_work, fnic_tport_event_handler); + INIT_WORK(&fnic->flush_work, fnic_flush_tx); + + INIT_LIST_HEAD(&fnic->frame_queue); + INIT_LIST_HEAD(&fnic->tx_queue); + INIT_LIST_HEAD(&fnic->tport_event_list); + init_llist_head(&fnic->nvme_io_event_llist); + + INIT_DELAYED_WORK(&iport->oxid_pool.schedule_oxid_free_retry, + fdls_schedule_oxid_free_retry_work); + + /* Initialize the oxid reclaim list and work struct */ + INIT_LIST_HEAD(&iport->oxid_pool.oxid_reclaim_list); + INIT_DELAYED_WORK(&iport->oxid_pool.oxid_reclaim_work, fdls_reclaim_oxid_handler); + + /* Enable all queues */ + for (i = 0; i < fnic->raw_wq_count; i++) + vnic_wq_enable(&fnic->wq[i]); + for (i = 0; i < fnic->rq_count; i++) { + if (!ioread32(&fnic->rq[i].ctrl->enable)) + vnic_rq_enable(&fnic->rq[i]); + } + for (i = 0; i < fnic->wq_copy_count; i++) + vnic_wq_copy_enable(&fnic->hw_copy_wq[i]); + + vnic_dev_enable(fnic->vdev); + + err = fnic_request_intr(fnic); + if (err) { + dev_err(&fnic->pdev->dev, "Unable to request irq.\n"); + goto err_out_fnic_request_intr; + } + + fnic_notify_timer_start(fnic); + + fnic_fdls_init(fnic, (fnic->config.flags & VFCF_FIP_CAPABLE)); + + if (IS_FNIC_FCP_INITIATOR(fnic)) { + err = fnic_scsi_drv_init(fnic); + if (err) + goto err_out_scsi_drv_init; + } else if (IS_FNIC_NVME_INITIATOR(fnic)) { + err = fnic_nvme_drv_init(fnic); + if (err) + goto err_out_nvme_drv_init; + } + + err = fnic_stats_debugfs_init(fnic); + if (err) { + dev_err(&fnic->pdev->dev, "Failed to initialize debugfs for stats\n"); + goto err_out_free_stats_debugfs; + } + + for (i = 0; i < fnic->intr_count; i++) + vnic_intr_unmask(&fnic->intr[i]); + + spin_lock_irqsave(&fnic_list_lock, flags); + list_add_tail(&fnic->list, &fnic_list); + spin_unlock_irqrestore(&fnic_list_lock, flags); + + return 0; + +err_out_free_stats_debugfs: + fnic_stats_debugfs_remove(fnic); + fnic_free_ioreq_tables_mq(fnic); + if (IS_FNIC_FCP_INITIATOR(fnic)) + scsi_remove_host(fnic->host); +err_out_nvme_drv_init: +err_out_scsi_drv_init: + fnic_free_intr(fnic); +err_out_fnic_request_intr: +err_out_alloc_rq_buf: + for (i = 0; i < fnic->rq_count; i++) { + if (ioread32(&fnic->rq[i].ctrl->enable)) + vnic_rq_disable(&fnic->rq[i]); + vnic_rq_clean(&fnic->rq[i], fnic_free_rq_buf); + } + vnic_dev_notify_unset(fnic->vdev); +err_out_fnic_notify_set: + mempool_destroy(fnic->frame_recv_pool); +err_out_fdls_frame_recv_pool: + mempool_destroy(fnic->frame_elem_pool); +err_out_fdls_frame_elem_pool: + mempool_destroy(fnic->frame_pool); +err_out_fdls_frame_pool: + mempool_destroy(fnic->io_sgl_pool[FNIC_SGL_CACHE_MAX]); +err_out_free_dflt_pool: + mempool_destroy(fnic->io_sgl_pool[FNIC_SGL_CACHE_DFLT]); +err_out_free_resources: + fnic_free_vnic_resources(fnic); +err_out_fnic_alloc_vnic_res: + fnic_clear_intr_mode(fnic); +err_out_fnic_set_intr_mode: + if (IS_FNIC_NVME_INITIATOR(fnic)) + fnic_nvmef_debugfs_remove(fnic); + if (IS_FNIC_FCP_INITIATOR(fnic)) + scsi_host_put(fnic->host); +err_out_fnic_role: +err_out_scsi_host_alloc: +err_out_fnic_get_config: +err_out_dev_mac_addr: +err_out_dev_init: + vnic_dev_close(fnic->vdev); +err_out_dev_open: +err_out_dev_cmd_init: + vnic_dev_unregister(fnic->vdev); +err_out_dev_register: + fnic_iounmap(fnic); +err_out_fnic_map_bar: +err_out_map_bar: +err_out_set_dma_mask: + pci_release_regions(pdev); +err_out_pci_request_regions: + pci_disable_device(pdev); +err_out_pci_enable_device: + ida_free(&fnic_ida, fnic->fnic_num); +err_out_ida_alloc: + kfree(fnic); +err_out_fnic_alloc: + return err; +} + +static void fnic_remove(struct pci_dev *pdev) +{ + struct fnic *fnic = pci_get_drvdata(pdev); + unsigned long flags; + + /* + * Sometimes when probe() fails and do not exit with an error code, + * remove() gets called with 'drvdata' not set. Avoid a crash by + * adding a defensive check. + */ + if (!fnic) + return; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + fnic->stop_rx_link_events = 1; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + /* + * Flush the fnic event queue. After this call, there should + * be no event queued for this fnic device in the workqueue + */ + flush_workqueue(fnic_event_queue); + + if (IS_FNIC_FCP_INITIATOR(fnic)) + fnic_scsi_unload(fnic); + else if (IS_FNIC_NVME_INITIATOR(fnic)) + nvfnic_nvme_unload(fnic); + + if (vnic_dev_get_intr_mode(fnic->vdev) == VNIC_DEV_INTR_MODE_MSI) + timer_delete_sync(&fnic->notify_timer); + + if (fnic->config.flags & VFCF_FIP_CAPABLE) { + timer_delete_sync(&fnic->retry_fip_timer); + timer_delete_sync(&fnic->fcs_ka_timer); + timer_delete_sync(&fnic->enode_ka_timer); + timer_delete_sync(&fnic->vn_ka_timer); + + fnic_fcoe_reset_vlans(fnic); + } + + if ((fnic_fdmi_support == 1) && (fnic->iport.fabric.fdmi_pending > 0)) + timer_delete_sync(&fnic->iport.fabric.fdmi_timer); + + fnic_nvmef_debugfs_remove(fnic); + fnic_stats_debugfs_remove(fnic); + + /* + * This stops the fnic device, masks all interrupts. Completed + * CQ entries are drained. Posted WQ/RQ/Copy-WQ entries are + * cleaned up + */ + fnic_cleanup(fnic); + + if (IS_FNIC_NVME_INITIATOR(fnic)) { + sbitmap_free(&fnic->nvfnic_tag_map); + fnic_free_ioreq_tables_mq(fnic); + } + + spin_lock_irqsave(&fnic_list_lock, flags); + list_del(&fnic->list); + spin_unlock_irqrestore(&fnic_list_lock, flags); + + fnic_free_rxq(fnic); + fnic_free_txq(fnic); + + vnic_dev_notify_unset(fnic->vdev); + fnic_free_intr(fnic); + fnic_free_vnic_resources(fnic); + fnic_clear_intr_mode(fnic); + vnic_dev_close(fnic->vdev); + vnic_dev_unregister(fnic->vdev); + fnic_iounmap(fnic); + pci_release_regions(pdev); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); + ida_free(&fnic_ida, fnic->fnic_num); + if (IS_FNIC_FCP_INITIATOR(fnic)) { + fnic_scsi_unload_cleanup(fnic); + scsi_host_put(fnic->host); + } + kfree(fnic); +} + +static struct pci_driver fnic_driver = { + .name = DRV_NAME, + .id_table = fnic_id_table, + .probe = fnic_probe, + .remove = fnic_remove, +}; + +static int __init fnic_init_module(void) +{ + size_t len; + int err = 0; + + printk(KERN_INFO PFX "%s, ver %s\n", DRV_DESCRIPTION, DRV_VERSION); + + /* Create debugfs entries for fnic */ + err = fnic_debugfs_init(); + if (err < 0) { + printk(KERN_ERR PFX "Failed to create fnic directory " + "for tracing and stats logging\n"); + fnic_debugfs_terminate(); + } + + /* Allocate memory for trace buffer */ + err = fnic_trace_buf_init(); + if (err < 0) { + printk(KERN_ERR PFX + "Trace buffer initialization Failed. " + "Fnic Tracing utility is disabled\n"); + fnic_trace_free(); + } + + /* Allocate memory for fc trace buffer */ + err = fnic_fc_trace_init(); + if (err < 0) { + printk(KERN_ERR PFX "FC trace buffer initialization Failed " + "FC frame tracing utility is disabled\n"); + fnic_fc_trace_free(); + } + + /* Create a cache for allocation of default size sgls */ + len = sizeof(struct fnic_dflt_sgl_list); + fnic_sgl_cache[FNIC_SGL_CACHE_DFLT] = kmem_cache_create + ("fnic_sgl_dflt", len + FNIC_SG_DESC_ALIGN, FNIC_SG_DESC_ALIGN, + SLAB_HWCACHE_ALIGN, + NULL); + if (!fnic_sgl_cache[FNIC_SGL_CACHE_DFLT]) { + printk(KERN_ERR PFX "failed to create fnic dflt sgl slab\n"); + err = -ENOMEM; + goto err_create_fnic_sgl_slab_dflt; + } + + /* Create a cache for allocation of max size sgls*/ + len = sizeof(struct fnic_sgl_list); + fnic_sgl_cache[FNIC_SGL_CACHE_MAX] = kmem_cache_create + ("fnic_sgl_max", len + FNIC_SG_DESC_ALIGN, FNIC_SG_DESC_ALIGN, + SLAB_HWCACHE_ALIGN, + NULL); + if (!fnic_sgl_cache[FNIC_SGL_CACHE_MAX]) { + printk(KERN_ERR PFX "failed to create fnic max sgl slab\n"); + err = -ENOMEM; + goto err_create_fnic_sgl_slab_max; + } + + /* Create a cache of io_req structs for use via mempool */ + fnic_io_req_cache = kmem_cache_create("fnic_io_req", + sizeof(struct fnic_io_req), + 0, SLAB_HWCACHE_ALIGN, NULL); + if (!fnic_io_req_cache) { + printk(KERN_ERR PFX "failed to create fnic io_req slab\n"); + err = -ENOMEM; + goto err_create_fnic_ioreq_slab; + } + + fdls_frame_cache = kmem_cache_create("fdls_frames", + FNIC_FCOE_FRAME_MAXSZ, + 0, SLAB_HWCACHE_ALIGN, NULL); + if (!fdls_frame_cache) { + pr_err("fnic fdls frame cache create failed\n"); + err = -ENOMEM; + goto err_create_fdls_frame_cache; + } + + fdls_frame_elem_cache = kmem_cache_create("fdls_frame_elem", + sizeof(struct fnic_frame_list), + 0, SLAB_HWCACHE_ALIGN, NULL); + if (!fdls_frame_elem_cache) { + pr_err("fnic fdls frame elem cache create failed\n"); + err = -ENOMEM; + goto err_create_fdls_frame_cache_elem; + } + + fdls_frame_recv_cache = kmem_cache_create("fdls_frame_recv", + FNIC_FRAME_HT_ROOM, + 0, SLAB_HWCACHE_ALIGN, NULL); + if (!fdls_frame_recv_cache) { + pr_err("fnic fdls frame recv cach create failed\n"); + err = -ENOMEM; + goto err_create_fdls_frame_recv_cache; + } + + fnic_event_queue = + alloc_ordered_workqueue("%s", WQ_MEM_RECLAIM, "fnic_event_wq"); + if (!fnic_event_queue) { + printk(KERN_ERR PFX "fnic work queue create failed\n"); + err = -ENOMEM; + goto err_create_fnic_workq; + } + + fnic_fip_queue = + alloc_ordered_workqueue("%s", WQ_MEM_RECLAIM, "fnic_fip_q"); + if (!fnic_fip_queue) { + printk(KERN_ERR PFX "fnic FIP work queue create failed\n"); + err = -ENOMEM; + goto err_create_fip_workq; + } + + fnic_cmpl_queue = + alloc_workqueue("fnic_cmpl_wq", + WQ_PERCPU | WQ_HIGHPRI | WQ_MEM_RECLAIM, 0); + if (!fnic_cmpl_queue) { + pr_err("fnic completion work queue create failed\n"); + err = -ENOMEM; + goto err_create_cmpl_workq; + } + + if (pc_rscn_handling_feature_flag == PC_RSCN_HANDLING_FEATURE_ON) { + reset_fnic_work_queue = + create_singlethread_workqueue("reset_fnic_work_queue"); + if (!reset_fnic_work_queue) { + pr_err("reset fnic work queue create failed\n"); + err = -ENOMEM; + goto err_create_reset_fnic_workq; + } + spin_lock_init(&reset_fnic_list_lock); + INIT_LIST_HEAD(&reset_fnic_list); + INIT_WORK(&reset_fnic_work, fnic_reset_work_handler); + } + + fnic_fc_transport = fc_attach_transport(&fnic_fc_functions); + if (!fnic_fc_transport) { + printk(KERN_ERR PFX "fc_attach_transport error\n"); + err = -ENOMEM; + goto err_fc_transport; + } + + /* register the driver with PCI system */ + err = pci_register_driver(&fnic_driver); + if (err < 0) { + printk(KERN_ERR PFX "pci register error\n"); + goto err_pci_register; + } + return err; + +err_pci_register: + fc_release_transport(fnic_fc_transport); +err_fc_transport: + destroy_workqueue(fnic_cmpl_queue); +err_create_cmpl_workq: + destroy_workqueue(fnic_fip_queue); +err_create_fip_workq: + if (pc_rscn_handling_feature_flag == PC_RSCN_HANDLING_FEATURE_ON) + destroy_workqueue(reset_fnic_work_queue); +err_create_reset_fnic_workq: + kmem_cache_destroy(fdls_frame_recv_cache); +err_create_fdls_frame_recv_cache: + destroy_workqueue(fnic_event_queue); +err_create_fnic_workq: + kmem_cache_destroy(fdls_frame_elem_cache); +err_create_fdls_frame_cache_elem: + kmem_cache_destroy(fdls_frame_cache); +err_create_fdls_frame_cache: + kmem_cache_destroy(fnic_io_req_cache); +err_create_fnic_ioreq_slab: + kmem_cache_destroy(fnic_sgl_cache[FNIC_SGL_CACHE_MAX]); +err_create_fnic_sgl_slab_max: + kmem_cache_destroy(fnic_sgl_cache[FNIC_SGL_CACHE_DFLT]); +err_create_fnic_sgl_slab_dflt: + fnic_trace_free(); + fnic_fc_trace_free(); + fnic_debugfs_terminate(); + return err; +} + +static void __exit fnic_cleanup_module(void) +{ + pci_unregister_driver(&fnic_driver); + destroy_workqueue(fnic_cmpl_queue); + destroy_workqueue(fnic_event_queue); + + if (pc_rscn_handling_feature_flag == PC_RSCN_HANDLING_FEATURE_ON) + destroy_workqueue(reset_fnic_work_queue); + + if (fnic_fip_queue) + destroy_workqueue(fnic_fip_queue); + + kmem_cache_destroy(fnic_sgl_cache[FNIC_SGL_CACHE_MAX]); + kmem_cache_destroy(fnic_sgl_cache[FNIC_SGL_CACHE_DFLT]); + kmem_cache_destroy(fnic_io_req_cache); + kmem_cache_destroy(fdls_frame_cache); + fc_release_transport(fnic_fc_transport); + fnic_trace_free(); + fnic_fc_trace_free(); + fnic_debugfs_terminate(); + ida_destroy(&fnic_ida); +} + +module_init(fnic_init_module); +module_exit(fnic_cleanup_module); diff --git a/drivers/scsi/fnic/fnic_nvme.c b/drivers/scsi/fnic/fnic_nvme.c new file mode 100644 index 000000000..00d9d5d43 --- /dev/null +++ b/drivers/scsi/fnic/fnic_nvme.c @@ -0,0 +1,2268 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#include <linux/mempool.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/workqueue.h> +#include <linux/pci.h> +#include <linux/scatterlist.h> +#include <linux/skbuff.h> +#include <linux/spinlock.h> +#include <linux/kthread.h> +#include <linux/if_ether.h> +#include <linux/if_vlan.h> +#include <linux/delay.h> +#include <linux/gfp.h> +#include <linux/nvme.h> +#include <linux/nvme-fc.h> +#include "fnic.h" +#include "fnic_trace.h" +#include "fdls_fc.h" + +#if IS_REACHABLE(CONFIG_NVME_FC) + +static bool nvfnic_ls_req_cleanup(struct fnic_iport_s *iport, + struct nvmefc_ls_req *lsreq, + uint16_t oxid); + +int nvfnic_get_sg_count(struct fnic_io_req *io_req) +{ + return io_req->fcp_req->sg_cnt; +} + +int nvfnic_dma_map_sgl(struct fnic *fnic, struct fnic_io_req *io_req, + int sg_count) +{ + io_req->sgl_list_pa = dma_map_single(&fnic->pdev->dev, + io_req->sgl_list, + sizeof(io_req->sgl_list[0]) * + sg_count, DMA_TO_DEVICE); + if (dma_mapping_error(&fnic->pdev->dev, io_req->sgl_list_pa)) { + dev_err(&fnic->pdev->dev, "DMA mapping failed\n"); + io_req->sgl_list_pa = 0; + io_req->sgl_mapped = 0; + return -EBUSY; + } + + io_req->sgl_mapped = 1; + return 0; +} + +void nvfnic_dma_unmap_sgl(struct fnic *fnic, struct fnic_io_req *io_req) +{ + if (io_req->sgl_mapped) { + dma_unmap_single(&fnic->pdev->dev, io_req->sgl_list_pa, + sizeof(io_req->sgl_list[0]) * io_req->sgl_cnt, + DMA_TO_DEVICE); + io_req->sgl_mapped = 0; + io_req->sgl_list_pa = 0; + } +} + +static void nvfnic_update_io_bytes(struct fnic *fnic, + struct fnic_io_req *io_req, u8 opcode) +{ + if (opcode == nvme_cmd_read) + fnic->fcp_input_bytes += io_req->fcp_req->transferred_length; + else if (opcode == nvme_cmd_write) + fnic->fcp_output_bytes += io_req->fcp_req->transferred_length; +} + +static void nvfnic_update_io_stats(struct fnic *fnic, u8 opcode) +{ + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + + switch (opcode) { + case nvme_cmd_read: + atomic64_inc(&fnic_stats->nvme_stats.nvme_input_requests); + break; + case nvme_cmd_write: + atomic64_inc(&fnic_stats->nvme_stats.nvme_output_requests); + break; + default: + atomic64_inc(&fnic_stats->nvme_stats.nvme_control_requests); + break; + } +} + +static void nvfnic_update_cmpl_stats(struct fnic *fnic, + struct fnic_io_req *io_req) +{ + struct io_path_stats *io_stats = &fnic->fnic_stats.io_stats; + atomic64_t *duration_stat; + unsigned long io_duration_time; + + atomic64_dec(&io_stats->active_ios); + if (atomic64_read(&fnic->io_cmpl_skip)) + atomic64_dec(&fnic->io_cmpl_skip); + else + atomic64_inc(&io_stats->io_completions); + + io_duration_time = jiffies_to_msecs(jiffies - io_req->start_time); + + if (io_duration_time <= 10) + duration_stat = &io_stats->io_btw_0_to_10_msec; + else if (io_duration_time <= 100) + duration_stat = &io_stats->io_btw_10_to_100_msec; + else if (io_duration_time <= 500) + duration_stat = &io_stats->io_btw_100_to_500_msec; + else if (io_duration_time <= 5000) + duration_stat = &io_stats->io_btw_500_to_5000_msec; + else if (io_duration_time <= 10000) + duration_stat = &io_stats->io_btw_5000_to_10000_msec; + else if (io_duration_time <= 30000) + duration_stat = &io_stats->io_btw_10000_to_30000_msec; + else { + duration_stat = &io_stats->io_greater_than_30000_msec; + if (io_duration_time > + atomic64_read(&io_stats->current_max_io_time)) + atomic64_set(&io_stats->current_max_io_time, + io_duration_time); + } + + atomic64_inc(duration_stat); +} + +int +nvfnic_alloc_fcpio_tag(struct fnic_iport_s *iport, struct fnic_io_req *io_req) +{ + struct fnic *fnic = iport->fnic; + int tag; + + tag = sbitmap_get(&fnic->nvfnic_tag_map); + if (tag >= 0) { + WRITE_ONCE(io_req->tag, tag); + fnic->sw_copy_wq[0].io_req_table[tag] = io_req; + return tag; + } + return FNIC_NVME_NO_FREE_TAG; +} + +void +nvfnic_free_fcpio_tag(struct fnic_iport_s *iport, struct fnic_io_req *io_req) +{ + struct fnic *fnic = iport->fnic; + uint16_t tag = io_req->tag; + + if (tag == FNIC_NVME_NO_FREE_TAG) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "Attempting to free invalid tag: 0x%x\n", tag); + return; + } + fnic->sw_copy_wq[0].io_req_table[tag] = NULL; + WRITE_ONCE(io_req->tag, FNIC_NVME_NO_FREE_TAG); + sbitmap_clear_bit(&fnic->nvfnic_tag_map, tag); +} + +void +nvfnic_reset_fcpio_tag_pool(struct fnic_iport_s *iport) +{ + WARN_ON(sbitmap_weight(&iport->fnic->nvfnic_tag_map)); +} + +struct fnic_io_req * +nvfnic_find_io_req_by_tag(struct fnic *fnic, uint16_t tag) +{ + if (tag == FNIC_NVME_NO_FREE_TAG || + !sbitmap_test_bit(&fnic->nvfnic_tag_map, tag)) + return NULL; + return fnic->sw_copy_wq[0].io_req_table[tag]; +} + +/* + * Unmap the data buffer and sense buffer for an io_req, + * also unmap and free the device-private scatter/gather list. + */ +void nvfnic_release_nvme_ioreq_buf(struct fnic_iport_s *iport, + struct fnic_io_req *io_req) +{ + struct fnic *fnic = iport->fnic; + + nvfnic_dma_unmap_sgl(fnic, io_req); + + if (io_req->sgl_cnt) + mempool_free(io_req->sgl_list_alloc, + fnic->io_sgl_pool[io_req->sgl_type]); +} + +int nvfnic_get_nvmef_info(struct fnic *fnic, struct fnic_nvmef_info *info) +{ + int len = 0; + struct fnic_iport_s *iport = &fnic->iport; + int buf_size = info->buf_size; + struct fnic_tport_s *tport; + struct fnic_tport_s *next; + unsigned long flags; + + if (buf_size <= 0) + return 0; + + len += scnprintf(info->info_buffer + len, buf_size - len, + "lport wwpn 0x%llx wwnn 0x%llx fcid 0x%06x\n", + iport->wwpn, iport->wwnn, iport->fcid); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + list_for_each_entry_safe(tport, next, &iport->tport_list, links) { + if (len >= buf_size - 1) + break; + + len += scnprintf(info->info_buffer + len, buf_size - len, + "tport wwpn 0x%llx wwnn 0x%llx fcid 0x%06x\n", + tport->wwpn, tport->wwnn, tport->fcid); + } + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + return len; +} + +inline int nvfnic_queue_wq_nvme_copy_desc(struct fnic *fnic, + struct vnic_wq_copy *wq, + struct fnic_io_req *io_req, + int sg_count) +{ + struct scatterlist *sg; + struct fnic_tport_s *tport = io_req->tport; + struct host_sg_desc *desc; + struct misc_stats *misc_stats = &fnic->fnic_stats.misc_stats; + unsigned int i; + unsigned long intr_flags; + int flags; + u8 exch_flags; + struct scatterlist *sgl; + int idx; + int ret = 0; + + if (sg_count) { + /* For each SGE, create a device desc entry */ + desc = io_req->sgl_list; + sgl = io_req->fcp_req->first_sgl; + for_each_sg(sgl, sg, sg_count, i) { + desc->addr = cpu_to_le64(sg_dma_address(sg)); + desc->len = cpu_to_le32(sg_dma_len(sg)); + desc->_resvd = 0; + desc++; + } + + ret = nvfnic_dma_map_sgl(fnic, io_req, sg_count); + if (ret) + return ret; + } + + idx = (struct vnic_wq_copy *)wq - &fnic->hw_copy_wq[0]; + + /* Enqueue the descriptor in the Copy WQ */ + spin_lock_irqsave(&fnic->wq_copy_lock[idx], intr_flags); + + if (vnic_wq_copy_desc_avail(wq) <= fnic->wq_copy_desc_low[idx]) + free_wq_copy_descs(fnic, wq, idx); + + if (unlikely(!vnic_wq_copy_desc_avail(wq))) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[idx], intr_flags); + FNIC_NVME_DBG(KERN_ERR, fnic, + "Enqueue failure: No descriptors\n"); + atomic64_inc(&misc_stats->io_cpwq_alloc_failures); + return -EBUSY; + } + + flags = 0; + if (io_req->fcp_req->io_dir == NVMEFC_FCP_READ) + flags = FCPIO_ICMND_RDDATA; + else if (io_req->fcp_req->io_dir == NVMEFC_FCP_WRITE) + flags = FCPIO_ICMND_WRDATA; + + exch_flags = 0; + + fnic_queue_wq_copy_desc_nvme_io(wq, io_req->tag, + exch_flags, io_req->sgl_cnt, + io_req->sgl_list_pa, flags, + io_req->fcp_req->cmdaddr, + io_req->fcp_req->cmdlen, + io_req->fcp_req->payload_length, + io_req->port_id, + tport->max_payload_size, tport->r_a_tov, + tport->e_d_tov); + + atomic64_inc(&fnic->fnic_stats.fw_stats.active_fw_reqs); + if (atomic64_read(&fnic->fnic_stats.fw_stats.active_fw_reqs) > + atomic64_read(&fnic->fnic_stats.fw_stats.max_fw_reqs)) + atomic64_set(&fnic->fnic_stats.fw_stats.max_fw_reqs, + atomic64_read(&fnic->fnic_stats.fw_stats.active_fw_reqs)); + + spin_unlock_irqrestore(&fnic->wq_copy_lock[idx], intr_flags); + return 0; +} + +bool +nvfnic_transport_ready(struct fnic_iport_s *iport, struct fnic_tport_s *tport) +{ + struct fnic *fnic = iport->fnic; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + + if (tport == NULL) + return false; + + if (fdls_get_state(&iport->fabric) == FDLS_STATE_LINKDOWN || + iport->state != FNIC_IPORT_STATE_READY) { + atomic64_inc(&fnic_stats->misc_stats.iport_not_ready); + return false; + } + + if (unlikely(fnic_chk_state_flags_locked(fnic, FNIC_FLAGS_IO_BLOCKED))) + return false; + + if (fdls_tport_is_offline(tport)) { + atomic64_inc(&fnic_stats->misc_stats.tport_not_ready); + return false; + } + + return true; +} + +int nvfnic_queuecommand(struct fnic_io_req *io_req) +{ + struct fnic_iport_s *iport = io_req->iport; + struct fnic *fnic = iport->fnic; + struct fnic_tport_s *tport = io_req->tport; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + struct vnic_wq_copy *wq = io_req->wq; + int ret = 0; + int sg_count = 0; + unsigned long ptr; + unsigned char *lba; + u64 cmd_trace; + int idx; + struct nvme_fc_cmd_iu *cmdiu = io_req->fcp_req->cmdaddr; + + io_req->cmd_state = FNIC_IOREQ_NOT_INITED; + io_req->cmd_flags = FNIC_NO_FLAGS; + /* Map the data buffer */ + sg_count = nvfnic_get_sg_count(io_req); + if (sg_count < 0) { + FNIC_TRACE(nvfnic_queuecommand, fnic->fnic_num, + io_req->tag, io_req, 0, io_req->fcp_req->io_dir, + sg_count, io_req->cmd_state); + FNIC_NVME_DBG(KERN_INFO, fnic, "sg count is less-than-zero\n"); + ret = -1; + goto out; + } + + /* Determine the type of scatter/gather list we need */ + io_req->sgl_cnt = sg_count; + io_req->sgl_type = FNIC_SGL_CACHE_DFLT; + if (sg_count > FNIC_DFLT_SG_DESC_CNT) + io_req->sgl_type = FNIC_SGL_CACHE_MAX; + + if (sg_count) { + io_req->sgl_list = + mempool_alloc(fnic->io_sgl_pool[io_req->sgl_type], + GFP_ATOMIC); + if (!io_req->sgl_list) { + atomic64_inc(&fnic_stats->io_stats.alloc_failures); + FNIC_NVME_DBG(KERN_INFO, fnic, + "Unable to alloc SGLs\n"); + ret = -ENOMEM; + goto out; + } + + /* Cache sgl list allocated address before alignment */ + io_req->sgl_list_alloc = io_req->sgl_list; + ptr = (unsigned long)io_req->sgl_list; + if (ptr % FNIC_SG_DESC_ALIGN) { + io_req->sgl_list = (struct host_sg_desc *) + (((unsigned long)ptr + FNIC_SG_DESC_ALIGN - 1) + & ~(FNIC_SG_DESC_ALIGN - 1)); + } + } + + io_req->port_id = tport->fcid; + io_req->start_time = jiffies; + io_req->cmd_state = FNIC_IOREQ_CMD_PENDING; + io_req->cmd_flags = FNIC_IO_INITIALIZED; + + /* create copy wq desc and enqueue it */ + idx = wq - &fnic->hw_copy_wq[0]; + atomic64_inc(&fnic_stats->io_stats.ios[idx]); + ret = nvfnic_queue_wq_nvme_copy_desc(fnic, io_req->wq, io_req, sg_count); + if (ret) { + FNIC_NVME_DBG(KERN_ERR, fnic, "Unable to queue frame\n"); + /* + * In case another thread cancelled the request, + * refetch the pointer under the lock. + */ + nvfnic_release_nvme_ioreq_buf(iport, io_req); + FNIC_TRACE(nvfnic_queuecommand, fnic->fnic_num, + io_req->tag, io_req->fcp_req, 0, 0, 0, + (((u64)io_req->cmd_flags << 32) | io_req->cmd_state)); + return ret; + } + + atomic64_inc(&fnic_stats->io_stats.active_ios); + atomic64_inc(&fnic_stats->io_stats.num_ios); + if (atomic64_read(&fnic_stats->io_stats.active_ios) > + atomic64_read(&fnic_stats->io_stats.max_active_ios)) + atomic64_set(&fnic_stats->io_stats.max_active_ios, + atomic64_read(&fnic_stats->io_stats.active_ios)); + io_req->cmd_flags |= FNIC_IO_ISSUED; + out: + lba = (char *)&cmdiu->sqe.rw.slba; + cmd_trace = ((u64) cmdiu->sqe.rw.opcode << 56 | (u64) lba[4] << 40 | + (u64) lba[5] << 32 | (u64) lba[0] << 24 | + (u64) lba[1] << 16 | (u64) lba[2] << 8 | lba[3]); + + FNIC_TRACE(nvfnic_queuecommand, fnic->fnic_num, + io_req->tag, 0, io_req, + sg_count, cmd_trace, + (((u64)io_req->cmd_flags >> 32) | + io_req->cmd_state)); + + return ret; +} + +int nvfnic_fcpio_send(struct nvme_fc_local_port *lport, + struct nvme_fc_remote_port *rport, void *hw_queue_handle, + struct nvmefc_fcp_req *fcp_req) +{ + struct fnic_iport_s *iport = lport->private; + struct fnic_io_req *io_req; + int ret; + struct fnic *fnic = iport->fnic; + unsigned long flags = 0; + struct fnic_tport_s *tport; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + + tport = (struct fnic_tport_s *)rport->private; + atomic64_inc(&fnic_stats->io_stats.nvme_io_reqs_rcvd); + if (!nvfnic_transport_ready(iport, tport)) { + atomic64_inc(&fnic_stats->io_stats.nvme_io_rsps_sent); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + if (tport != NULL) + FNIC_NVME_DBG(KERN_INFO, fnic, + "iport: 0x%x tport: 0x%x not ready\n", + iport->fcid, tport->fcid); + else + FNIC_NVME_DBG(KERN_INFO, fnic, + "iport: 0x%x tport not ready\n", + iport->fcid); + return -ENODEV; + } + atomic_inc(&fnic->in_flight); + + io_req = (struct fnic_io_req *) fcp_req->private; + io_req->iport = iport; + io_req->tport = (struct fnic_tport_s *)rport->private; + io_req->fcp_req = fcp_req; + io_req->done = nvfnic_fcpio_cmpl; + io_req->sgl_list = NULL; + io_req->sgl_list_alloc = NULL; + io_req->sgl_list_pa = 0; + io_req->sgl_cnt = 0; + io_req->sgl_mapped = 0; + io_req->wq = hw_queue_handle; + init_llist_node(&io_req->nvfnic_io_cmpl); + + io_req->tag = nvfnic_alloc_fcpio_tag(iport, io_req); + if (io_req->tag == FNIC_NVME_NO_FREE_TAG) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "No free tag available. Failing IO\n"); + atomic64_inc(&fnic_stats->io_stats.alloc_failures); + atomic_dec(&fnic->in_flight); + atomic64_inc(&fnic_stats->io_stats.nvme_io_rsps_sent); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return -EBUSY; + } + + ret = nvfnic_queuecommand(io_req); + if (ret) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "Queuecommand failed tag: 0x%x\n", + io_req->tag); + nvfnic_free_fcpio_tag(iport, io_req); + } + + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + atomic_dec(&fnic->in_flight); + return ret; +} + +void nvfnic_fcpio_nvme_fast_cmpl_handler(struct fnic *fnic, + struct fcpio_fw_req *desc) +{ + u8 type; + u8 hdr_status; + struct fcpio_tag ftag; + u32 id; + struct fnic_io_req *io_req; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + unsigned long start_time; + u64 cmd_trace; + char *lba; + struct nvme_fc_cmd_iu *cmdiu; + struct fnic_iport_s *iport; + unsigned int tag; + + /* Decode the cmpl description to get the io_req id */ + fcpio_header_dec(&desc->hdr, &type, &hdr_status, &ftag); + fcpio_tag_id_dec(&ftag, &id); + tag = id & FNIC_TAG_MASK; + + if (tag >= fnic->fnic_max_tag_id) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "Tag out of range tag: 0x%x hdr status: %s\n", tag, + fnic_fcpio_status_to_str(hdr_status)); + return; + } + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + + io_req = nvfnic_find_io_req_by_tag(fnic, tag); + + WARN_ON_ONCE(!io_req); + if (!io_req) { + atomic64_inc(&fnic_stats->io_stats.ioreq_null); + FNIC_NVME_DBG(KERN_ERR, fnic, + "IO req null hdr: %s tag: 0x%x desc: 0x%p\n", + fnic_fcpio_status_to_str(hdr_status), id, desc); + FNIC_NVME_DBG(KERN_ERR, fnic, + "type: 0x%x status: 0x%x rsvd: 0x%x tag: 0x%x\n", + desc->hdr.type, desc->hdr.status, desc->hdr._resvd, + id); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + + cmdiu = io_req->fcp_req->cmdaddr; + if (io_req->tag != tag) { + FNIC_NVME_DBG(KERN_INFO, fnic, + "Tag mismatch tag:%d io:0x%x id:0x%x csn:%08x\n", + tag, io_req->tag, id, be32_to_cpu(cmdiu->csn)); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + iport = io_req->iport; + + start_time = io_req->start_time; + + /* firmware completed the io */ + io_req->io_completed = 1; + if (io_req->cmd_state == FNIC_IOREQ_ABTS_PENDING) { + /* + * set the FNIC_IO_DONE so that this doesn't get + * flagged as 'out of order' if it was not aborted + */ + io_req->cmd_flags |= FNIC_IO_DONE; + io_req->cmd_flags |= FNIC_IO_ABTS_PENDING; + if (hdr_status == FCPIO_ABORTED) + io_req->cmd_flags |= FNIC_IO_ABORTED; + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + + FNIC_NVME_DBG(KERN_INFO, fnic, + "icmnd abts hdr:%d %s tag:0x%x io:%p", + hdr_status, fnic_fcpio_status_to_str(hdr_status), + id, io_req); + return; + } + + if (io_req->cmd_state != FNIC_IOREQ_CMD_PENDING) { + FNIC_NVME_DBG(KERN_INFO, fnic, + "IO freed id:%d tag:0x%x st:0x%x csn:%08x\n", + id, io_req->tag, io_req->cmd_state, + be32_to_cpu(cmdiu->csn)); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + + /* Mark the IO as complete */ + io_req->cmd_state = FNIC_IOREQ_CMD_COMPLETE; + switch (hdr_status) { + case FCPIO_SUCCESS: + io_req->fcp_req->status = 0; + io_req->fcp_req->transferred_length = + io_req->fcp_req->payload_length; + io_req->fcp_req->rcv_rsplen = 12; + break; + default: + FNIC_NVME_DBG(KERN_ERR, fnic, "HDR status is non-zero\n"); + io_req->fcp_req->status = NVME_SC_INTERNAL; + break; + } + + if (hdr_status != FCPIO_SUCCESS) { + atomic64_inc(&fnic_stats->io_stats.io_failures); + FNIC_NVME_DBG(KERN_INFO, fnic, "hdr status: %s\n", + fnic_fcpio_status_to_str(hdr_status)); + } + + io_req->cmd_flags |= FNIC_IO_DONE; + + cmdiu = io_req->fcp_req->cmdaddr; + lba = (char *)&cmdiu->sqe.rw.slba; + cmd_trace = ((u64) hdr_status << 56) | + (u64) cmdiu->sqe.rw.opcode << 32 | + (u64) lba[0] << 24 | (u64) lba[1] << 16 | + (u64) lba[2] << 8 | lba[3]; + + FNIC_TRACE(nvfnic_fcpio_nvme_fast_cmpl_handler, fnic->fnic_num, + tag, io_req, + jiffies_to_msecs(jiffies - start_time), + desc, cmd_trace, + (((u64) io_req->cmd_flags << 32) | + io_req->cmd_state)); + + nvfnic_update_io_stats(fnic, cmdiu->sqe.rw.opcode); + nvfnic_update_io_bytes(fnic, io_req, cmdiu->sqe.rw.opcode); + nvfnic_update_cmpl_stats(fnic, io_req); + + nvfnic_release_nvme_ioreq_buf(iport, io_req); + if (io_req->done) + io_req->done(io_req); + + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); +} + +void nvfnic_fcpio_ersp_cmpl_handler(struct fnic *fnic, + struct fcpio_fw_req *desc, int sw_flag) +{ + u8 type; + u8 hdr_status; + struct fcpio_tag ftag; + u32 id; + struct fcpio_nvme_cmpl *nvme_cmpl; + struct fnic_io_req *io_req; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + unsigned long start_time; + uint32_t rsplen; + struct nvme_fc_ersp_iu *ersp; + struct nvme_fc_ersp_iu *nrsp; + struct nvme_fc_cmd_iu *cmdiu; + struct nvme_command *sqe; + struct nvme_completion *cqe; + u64 cmd_trace; + struct fnic_iport_s *iport; + unsigned int tag; + char *lba; + uint64_t tport_wwpn = 0; + + /* Decode the cmpl description to get the io_req id */ + fcpio_header_dec(&desc->hdr, &type, &hdr_status, &ftag); + fcpio_tag_id_dec(&ftag, &id); + nvme_cmpl = &desc->u.nvme_cmpl; + ersp = (struct nvme_fc_ersp_iu *) nvme_cmpl->resp_bytes; + tag = id & FNIC_TAG_MASK; + + if (tag >= fnic->fnic_max_tag_id) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "Tag out of range tag:0x%x hdr:%s\n", tag, + fnic_fcpio_status_to_str(hdr_status)); + return; + } + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + + io_req = nvfnic_find_io_req_by_tag(fnic, tag); + if (!io_req) { + atomic64_inc(&fnic_stats->io_stats.ioreq_null); + FNIC_NVME_DBG(KERN_ERR, fnic, + "IOREQ is null hdr status: %s tag: 0x%x desc: %p\n", + fnic_fcpio_status_to_str(hdr_status), tag, desc); + FNIC_NVME_DBG(KERN_ERR, fnic, + "type: 0x%x status: 0x%x rsvd: 0x%x\n", + desc->hdr.type, desc->hdr.status, desc->hdr._resvd); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + iport = io_req->iport; + if (io_req->tport != NULL) + tport_wwpn = io_req->tport->wwpn; + cmdiu = io_req->fcp_req->cmdaddr; + + if (io_req->tag != tag) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "Tag mismatch io:0x%x tag:0x%x id:0x%x\n", + io_req->tag, tag, id); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + + nrsp = (struct nvme_fc_ersp_iu *)io_req->fcp_req->rspaddr; + cmdiu = (struct nvme_fc_cmd_iu *)io_req->fcp_req->cmdaddr; + sqe = &cmdiu->sqe; + cqe = &nrsp->cqe; + start_time = io_req->start_time; + + /* firmware completed the io */ + io_req->io_completed = 1; + + if (io_req->cmd_state == FNIC_IOREQ_ABTS_PENDING) { + /* + * set the FNIC_IO_DONE so that this doesn't get + * flagged as 'out of order' if it was not aborted + */ + io_req->cmd_flags |= FNIC_IO_DONE; + io_req->cmd_flags |= FNIC_IO_ABTS_PENDING; + if (hdr_status == FCPIO_ABORTED) + io_req->cmd_flags |= FNIC_IO_ABORTED; + + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + FNIC_NVME_DBG(KERN_INFO, fnic, + "ABTS pending hdr status: %s tag: 0x%x", + fnic_fcpio_status_to_str(hdr_status), tag); + return; + } + + if (io_req->cmd_state != FNIC_IOREQ_CMD_PENDING) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "IO already freed by abort. tag: 0x%x id: 0x%x\n", + io_req->tag, id); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + + /* Mark the IO as complete */ + io_req->cmd_state = FNIC_IOREQ_CMD_COMPLETE; + + switch (hdr_status) { + case FCPIO_SUCCESS: + io_req->fcp_req->status = 0; + if (!sw_flag) { + io_req->fcp_req->transferred_length = + io_req->fcp_req->payload_length; + rsplen = 32; + nrsp->iu_len = + cpu_to_be16(sizeof(struct nvme_fc_ersp_iu) / 4); + nrsp->xfrd_len = + cpu_to_be32(io_req->fcp_req->payload_length); + + nrsp->ersp_result = 0; + cqe->command_id = sqe->common.command_id; + cqe->status = 0; + cqe->result.u64 = 0; + } else { + io_req->fcp_req->transferred_length = + be32_to_cpu(ersp->xfrd_len); + rsplen = be16_to_cpu(ersp->iu_len) * 4; + if (rsplen > sizeof(nvme_cmpl->resp_bytes) || + rsplen > io_req->fcp_req->rsplen) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "tport wwpn 0x%llx ERSP len %u desc %u req %u\n", + tport_wwpn, rsplen, + (u32)sizeof(nvme_cmpl->resp_bytes), + io_req->fcp_req->rsplen); + io_req->fcp_req->status = NVME_SC_INTERNAL; + io_req->fcp_req->rcv_rsplen = 0; + break; + } + memcpy(io_req->fcp_req->rspaddr, ersp, rsplen); + } + atomic64_inc(&fnic_stats->nvme_stats.nvme_ersps); + io_req->fcp_req->rcv_rsplen = rsplen; + break; + + default: + FNIC_NVME_DBG(KERN_ERR, fnic, + "Unexpected header status: %d\n", hdr_status); + io_req->fcp_req->status = NVME_SC_INTERNAL; + break; + } + + if (hdr_status != FCPIO_SUCCESS) { + atomic64_inc(&fnic_stats->io_stats.io_failures); + FNIC_NVME_DBG(KERN_ERR, fnic, "hdr status: %s tag: 0x%x\n", + fnic_fcpio_status_to_str(hdr_status), tag); + } + + io_req->cmd_flags |= FNIC_IO_DONE; + + lba = (char *) &cmdiu->sqe.rw.slba; + cmd_trace = ((u64) hdr_status << 56) | + (u64) ersp->ersp_result << 48 | + (u64) cmdiu->sqe.rw.opcode << 32 | + (u64) lba[0] << 24 | (u64) lba[1] << 16 | + (u64) lba[2] << 8 | lba[3]; + + FNIC_TRACE(nvfnic_fcpio_ersp_cmpl_handler, fnic->fnic_num, + tag, io_req, + ((u64) nvme_cmpl->resvd[1] << 56 | + (u64) nvme_cmpl->resvd[0] << 48 | + jiffies_to_msecs(jiffies - start_time)), + desc, cmd_trace, + (((u64) io_req->cmd_flags << 32) | + io_req->cmd_state)); + + nvfnic_update_io_stats(fnic, cmdiu->sqe.rw.opcode); + nvfnic_update_io_bytes(fnic, io_req, cmdiu->sqe.rw.opcode); + nvfnic_update_cmpl_stats(fnic, io_req); + + nvfnic_release_nvme_ioreq_buf(iport, io_req); + + /* Call NVME completion function to complete the IO */ + if (io_req->done) + io_req->done(io_req); + + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); +} + +void nvfnic_fcpio_nvme_itmf_cmpl_handler(struct fnic *fnic, + struct fcpio_fw_req *desc) +{ + u8 type; + u8 hdr_status; + struct fcpio_tag ftag; + u32 id; + unsigned int tag; + struct fnic_io_req *io_req; + struct nvme_fc_cmd_iu *cmd_iu; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + struct abort_stats *abts_stats = &fnic->fnic_stats.abts_stats; + struct terminate_stats *term_stats = &fnic->fnic_stats.term_stats; + struct misc_stats *misc_stats = &fnic->fnic_stats.misc_stats; + struct fnic_iport_s *iport; + + fcpio_header_dec(&desc->hdr, &type, &hdr_status, &ftag); + fcpio_tag_id_dec(&ftag, &id); + tag = id & FNIC_TAG_MASK; + + if (tag >= fnic->fnic_max_tag_id) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "Tag out of range id:0x%x tag:0x%x hdr:%s\n", + id, tag, fnic_fcpio_status_to_str(hdr_status)); + return; + } + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + + io_req = nvfnic_find_io_req_by_tag(fnic, tag); + WARN_ON_ONCE(!io_req); + if (!io_req) { + atomic64_inc(&fnic_stats->io_stats.ioreq_null); + FNIC_NVME_DBG(KERN_ERR, fnic, + "IOREQ null hdr:%s tag:0x%x desc:%p\n", + fnic_fcpio_status_to_str(hdr_status), tag, desc); + FNIC_NVME_DBG(KERN_ERR, fnic, + "type: 0x%x status: 0x%x rsvd: 0x%x\n", + desc->hdr.type, desc->hdr.status, desc->hdr._resvd); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + + cmd_iu = io_req->fcp_req->cmdaddr; + FNIC_NVME_DBG(KERN_INFO, fnic, + "Received ITMF completion tag: 0x%x hdr_status: %d csn: 0x%08x\n", + tag, hdr_status, be32_to_cpu(cmd_iu->csn)); + + iport = io_req->iport; + + /* Completion of abort cmd */ + switch (hdr_status) { + case FCPIO_SUCCESS: + FNIC_NVME_DBG(KERN_DEBUG, fnic, + "Abort success received tag: 0x%x id: 0x%x\n", + tag, id); + break; + case FCPIO_TIMEOUT: + FNIC_NVME_DBG(KERN_ERR, fnic, + "Abort timeout received tag: 0x%x id: 0x%x\n", + tag, id); + if (io_req->cmd_flags & FNIC_IO_ABTS_ISSUED) + atomic64_inc(&abts_stats->abort_fw_timeouts); + else + atomic64_inc(&term_stats->terminate_fw_timeouts); + break; + case FCPIO_ITMF_REJECTED: + FNIC_NVME_DBG(KERN_ERR, fnic, + "Abort reject received tag: 0x%x id: 0x%x\n", + tag, id); + break; + + case FCPIO_IO_NOT_FOUND: + FNIC_NVME_DBG(KERN_ERR, fnic, + "Abort IO not found tag:0x%x id:0x%x\n", + tag, id); + if (io_req->cmd_flags & FNIC_IO_ABTS_ISSUED) + atomic64_inc(&abts_stats->abort_io_not_found); + else + atomic64_inc(&term_stats->terminate_io_not_found); + break; + default: + FNIC_NVME_DBG(KERN_ERR, fnic, + "Abort unknown received tag: 0x%x id: 0x%x\n", + tag, id); + if (io_req->cmd_flags & FNIC_IO_ABTS_ISSUED) + atomic64_inc(&abts_stats->abort_failures); + else + atomic64_inc(&term_stats->terminate_failures); + break; + } + + if (io_req->cmd_state != FNIC_IOREQ_ABTS_PENDING) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "Abort late completion tag:0x%x id:0x%x\n", + tag, id); + /* This is a late completion. Ignore it */ + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + + io_req->abts_state = hdr_status; + + /* If the status is IO not found consider it as success. + * NVME sends abort even if rport is down in which case + * we will get FCPIO_TIMEOUT. Consider this as success. + */ + if ((hdr_status == FCPIO_IO_NOT_FOUND) || + (hdr_status == FCPIO_TIMEOUT) || + (hdr_status == FCPIO_ITMF_REJECTED)) + io_req->abts_state = FCPIO_SUCCESS; + + io_req->cmd_flags |= FNIC_IO_ABT_TERM_DONE; + + if (!(io_req->cmd_flags & (FNIC_IO_ABORTED | FNIC_IO_DONE))) + atomic64_inc(&misc_stats->no_icmnd_itmf_cmpls); + + io_req->fcp_req->transferred_length = 0; + io_req->fcp_req->rcv_rsplen = 0; + if (io_req->abts_state == FCPIO_SUCCESS) + io_req->fcp_req->status = NVME_SC_ABORT_REQ; + else + io_req->fcp_req->status = NVME_SC_INTERNAL; + + atomic64_dec(&fnic_stats->io_stats.active_ios); + if (atomic64_read(&fnic->io_cmpl_skip)) + atomic64_dec(&fnic->io_cmpl_skip); + else + atomic64_inc(&fnic_stats->io_stats.io_completions); + + nvfnic_release_nvme_ioreq_buf(iport, io_req); + if (io_req->done) + io_req->done(io_req); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); +} + +bool _cleanup_tport_io(struct sbitmap *map, unsigned int tag, + void *data) +{ + struct fnic_tport_s *tport = data; + struct fnic_iport_s *iport = tport->iport; + struct fnic *fnic = iport->fnic; + struct fnic_io_req *io_req; + enum fnic_ioreq_state old_ioreq_state; + unsigned long flags; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + io_req = nvfnic_find_io_req_by_tag(fnic, tag); + if (!io_req || io_req->tport != tport) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return true; + } + + if ((io_req->cmd_state == FNIC_IOREQ_ABTS_PENDING) || + (io_req->cmd_state == FNIC_DEV_RST_TERM_ISSUED)) { + FNIC_NVME_DBG(KERN_INFO, fnic, + "Abort already pending 0x%x\n", io_req->tag); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return true; + } + + FNIC_NVME_DBG(KERN_ERR, fnic, + "io_req tag: 0x%x abort after unregister timeout\n", + io_req->tag); + + old_ioreq_state = io_req->cmd_state; + io_req->cmd_state = FNIC_IOREQ_ABTS_PENDING; + io_req->abts_state = FCPIO_INVALID_CODE; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + if (!nvfnic_queue_abort_io_req(fnic, io_req->tag, + FCPIO_ITMF_ABT_TASK_TERM, + io_req)) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "Failed to enqueue abort for ioreq tag: 0x%x\n", + io_req->tag); + spin_lock_irqsave(&fnic->fnic_lock, flags); + io_req->cmd_state = old_ioreq_state; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + } + return true; +} + +void +nvfnic_cleanup_tport_io(struct fnic *fnic, struct fnic_tport_s *tport) +{ + unsigned long flags; + struct nvfnic_ls_req *nvfnic_ls_req, *next; + struct nvmefc_ls_req *lsreq; + uint16_t oxid; + LIST_HEAD(done_reqs); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + list_for_each_entry_safe(nvfnic_ls_req, next, + &(tport->ls_req_list), list) { + lsreq = nvfnic_ls_req->ls_req; + if (!lsreq || (lsreq->private == NULL)) { + FNIC_NVME_DBG(KERN_INFO, fnic, + "fnic_cleanup_tport_io lsreq NULL\n"); + continue; + } + if (nvfnic_ls_req->state == FNIC_LS_REQ_CMD_ABTS_STARTED) { + FNIC_NVME_DBG(KERN_INFO, fnic, + "fnic_cleanup_tport_io lsreq abort started\n"); + continue; + } + list_del_init(&nvfnic_ls_req->list); + lsreq->private = NULL; + oxid = nvfnic_ls_req->oxid; + fdls_free_oxid(&fnic->iport, oxid, &nvfnic_ls_req->oxid); + nvfnic_ls_req->state = FNIC_LS_REQ_CMD_COMPLETE; + list_add_tail(&nvfnic_ls_req->list, &done_reqs); + } + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + list_for_each_entry_safe(nvfnic_ls_req, next, &done_reqs, list) { + list_del_init(&nvfnic_ls_req->list); + lsreq = nvfnic_ls_req->ls_req; + timer_delete_sync(&nvfnic_ls_req->ls_req_timer); + lsreq->done(lsreq, -ENXIO); + } + + /* For link-down, IOs are freed by firmware reset completion */ + if (fdls_get_state(&fnic->iport.fabric) == FDLS_STATE_LINKDOWN) + return; + + sbitmap_for_each_set(&fnic->nvfnic_tag_map, _cleanup_tport_io, tport); +} + +void +nvfnic_terminate_tport_ls_reqs(struct fnic *fnic, struct fnic_tport_s *tport) +{ + struct nvmefc_ls_req *lsreq; + struct nvfnic_ls_req *nvfnic_ls_req, *next; + int count = 0; + uint16_t oxid; + LIST_HEAD(done_reqs); + + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + list_for_each_entry_safe(nvfnic_ls_req, next, + &(tport->ls_req_list), list) { + + lsreq = nvfnic_ls_req->ls_req; + if (!lsreq || (lsreq->private == NULL)) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "lsreq is NULL\n"); + continue; + } + if (nvfnic_ls_req->state == FNIC_LS_REQ_CMD_ABTS_STARTED) { + FNIC_NVME_DBG(KERN_INFO, fnic, + "lsreq abort started\n"); + continue; + } + oxid = nvfnic_ls_req->oxid; + list_del_init(&nvfnic_ls_req->list); + lsreq->private = NULL; + fdls_free_oxid(&fnic->iport, oxid, &nvfnic_ls_req->oxid); + nvfnic_ls_req->state = FNIC_LS_REQ_CMD_COMPLETE; + list_add_tail(&nvfnic_ls_req->list, &done_reqs); + } + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + + list_for_each_entry_safe(nvfnic_ls_req, next, &done_reqs, list) { + list_del_init(&nvfnic_ls_req->list); + lsreq = nvfnic_ls_req->ls_req; + timer_delete_sync(&nvfnic_ls_req->ls_req_timer); + lsreq->done(lsreq, -ENXIO); + count++; + } + + FNIC_NVME_DBG(KERN_INFO, fnic, + "fnic_terminate_tport_lsreqs tport: 0x%x: freed lsreq: %d\n", + tport->fcid, count); +} + +bool _terminate_tport_ios(struct sbitmap *map, unsigned int tag, + void *data) +{ + struct fnic_tport_s *tport = data; + struct fnic_iport_s *iport = tport->iport; + struct fnic *fnic = iport->fnic; + struct fnic_io_req *io_req; + enum fnic_ioreq_state old_ioreq_state; + unsigned long flags; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + io_req = fnic->sw_copy_wq[0].io_req_table[tag]; + if (!io_req) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return true; + } + + if (io_req->tport != tport) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return true; + } + + if ((io_req->cmd_state == FNIC_IOREQ_ABTS_PENDING) || + (io_req->cmd_state == FNIC_DEV_RST_TERM_ISSUED)) { + FNIC_NVME_DBG(KERN_INFO, fnic, + "Abort already pending 0x%x\n", io_req->tag); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return true; + } + + FNIC_NVME_DBG(KERN_INFO, fnic, + "Terminate tag: 0x%x (tport fcid 0x%x)\n", + io_req->tag, io_req->tport->fcid); + + old_ioreq_state = io_req->cmd_state; + io_req->cmd_state = FNIC_IOREQ_ABTS_PENDING; + io_req->abts_state = FCPIO_INVALID_CODE; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + if (!nvfnic_queue_abort_io_req(fnic, io_req->tag, + FCPIO_ITMF_ABT_TASK_TERM, io_req)) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "Failed to enqueue abort for ioreq tag: 0x%x\n", + io_req->tag); + spin_lock_irqsave(&fnic->fnic_lock, flags); + io_req->cmd_state = old_ioreq_state; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + } + return true; +} + +void nvfnic_terminate_tport_ios(struct fnic *fnic, + struct fnic_tport_s *tport) +{ + struct abort_stats *abts_stats = &fnic->fnic_stats.abts_stats; + + sbitmap_for_each_set(&fnic->nvfnic_tag_map, _terminate_tport_ios, tport); + + FNIC_NVME_DBG(KERN_INFO, fnic, + "tport: 0x%x aborted %lld in_flight %d\n", + tport->fcid, atomic64_read(&abts_stats->aborts), + atomic_read(&fnic->in_flight)); +} + +bool _cleanup_all_nvme_io(struct sbitmap *map, unsigned int tag, + void *data) +{ + struct fnic_iport_s *iport = data; + struct fnic_io_req *io_req; + + io_req = iport->fnic->sw_copy_wq[0].io_req_table[tag]; + if (!io_req) + return true; + + io_req->cmd_state = FNIC_DEV_RST_TERM_ISSUED; + io_req->fcp_req->status = NVME_SC_INTERNAL; + io_req->fcp_req->transferred_length = 0; + io_req->fcp_req->rcv_rsplen = 0; + nvfnic_release_nvme_ioreq_buf(iport, io_req); + io_req->done(io_req); + return true; +} + +void nvfnic_cleanup_all_nvme_ios(struct fnic *fnic) +{ + struct fnic_iport_s *iport = &fnic->iport; + + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + sbitmap_for_each_set(&fnic->nvfnic_tag_map, _cleanup_all_nvme_io, + iport); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); +} + +void nvfnic_nvme_zero_devloss_tports(struct fnic *fnic) +{ + struct fnic_tport_s *tport, *next; + + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + list_for_each_entry_safe(tport, next, &fnic->iport.tport_list, links) { + if (tport->flags & FNIC_FDLS_NVME_REGISTERED) { + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + nvme_fc_set_remoteport_devloss(tport->nv_rport, 0); + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + } + } + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); +} + +void nvfnic_nvme_unload(struct fnic *fnic) +{ + int ret = 0; + struct fnic_iport_s *iport = &fnic->iport; + unsigned long flags; + unsigned int time_wait = FNIC_NVME_LPORT_REMOVE_WAIT; + unsigned int time_remain; + DECLARE_COMPLETION_ONSTACK(nvme_lport_unreg_done); + + /* Mark iport state as INIT so that no IOs can be issued from this point */ + spin_lock_irqsave(&fnic->fnic_lock, flags); + fnic->in_remove = 1; + fnic->iport.state = FNIC_IPORT_STATE_LINK_WAIT; + fnic->nvme_lport_unreg_done = &nvme_lport_unreg_done; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + /* + * If fnic is already processing link-down or fnic is held + * in disabled state following a reboot we dont need to issue + * firmware reset and unregister remote ports as it is already + * done as part of link down handling. + */ + if (fdls_get_state(&iport->fabric) == FDLS_STATE_LINKDOWN) { + while (fnic->reset_in_progress == IN_PROGRESS) { + wait_for_completion_timeout(&fnic->reset_completion_wait, + msecs_to_jiffies(5000)); + FNIC_NVME_DBG(KERN_INFO, fnic, + "rmmod waiting for reset %p\n", fnic); + } + } else if (fdls_get_state(&iport->fabric) != FDLS_STATE_INIT) + fnic_fcpio_reset(fnic); + + /* + * Mark state so that the workqueue thread stops forwarding + * received frames and link events to the local port. ISR and + * other threads that can queue work items will also stop + * creating work items on the fnic workqueue + */ + nvfnic_nvme_zero_devloss_tports(fnic); + fnic_flush_tport_event_list(fnic); + fnic_delete_fcp_tports(fnic); + + if (iport->flags & FNIC_LPORT_NVME_REGISTERED) { + ret = nvme_fc_unregister_localport(fnic->iport.nv_lport); + if (ret) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "Unregister nvme localport failed: %d\n", ret); + } else { + time_remain = wait_for_completion_timeout( + fnic->nvme_lport_unreg_done, + msecs_to_jiffies(time_wait)); + if (!time_remain) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "Local port removal timed out\n"); + WARN_ON(1); + } + iport->flags &= ~FNIC_LPORT_NVME_REGISTERED; + kfree(iport->nv_tmpl); + } + } + + spin_lock_irqsave(&fnic->fnic_lock, flags); + fnic->nvme_lport_unreg_done = NULL; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + nvfnic_flush_nvme_io_list(fnic); +} + +struct nvfnic_ls_req* +nvfnic_find_ls_req(struct fnic_tport_s *tport, uint16_t oxid) +{ + struct nvfnic_ls_req *nvfnic_ls_req, *next; + + list_for_each_entry_safe(nvfnic_ls_req, next, &(tport->ls_req_list), list) { + if (nvfnic_ls_req->oxid == oxid) + return nvfnic_ls_req; + } + return NULL; +} + +void nvfnic_fcpio_cmpl(struct fnic_io_req *io_req) +{ + struct fnic *fnic = io_req->iport->fnic; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + + nvfnic_free_fcpio_tag(io_req->iport, io_req); + atomic64_inc(&fnic_stats->io_stats.nvme_ios_queued_for_rsp); + io_req->waitq_start_time = jiffies; + + llist_add(&io_req->nvfnic_io_cmpl, &fnic->nvme_io_event_llist); + atomic_inc(&fnic->nvme_io_event_queued); + atomic64_inc(&fnic_stats->io_stats.nvme_num_ios_in_waitq); + + queue_work(fnic_cmpl_queue, &fnic->nvme_io_cmpl_work); +} + +void nvfnic_process_ls_abts_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ + uint32_t tport_fcid; + struct fnic_tport_s *tport; + struct nvfnic_ls_req *nvfnic_ls_req; + struct nvmefc_ls_req *lsreq; + uint8_t *fcid; + uint16_t oxid = FNIC_STD_GET_OX_ID(fchdr); + struct fnic *fnic = iport->fnic; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + + fcid = FNIC_STD_GET_S_ID(fchdr); + tport_fcid = ntoh24(fcid); + + tport = fnic_find_tport_by_fcid(iport, tport_fcid); + if (tport == NULL) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "tport: 0x%x not found\n", tport_fcid); + return; + } + + nvfnic_ls_req = nvfnic_find_ls_req(tport, oxid); + if (nvfnic_ls_req == NULL) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "tport: 0x%x lsreq oxid: 0x%x not found\n", + tport_fcid, oxid); + return; + } + + lsreq = nvfnic_ls_req->ls_req; + if ((lsreq == NULL) || (lsreq->private == NULL)) { + FNIC_NVME_DBG(KERN_INFO, fnic, + "tport: 0x%x lsreq oxid: 0x%x already aborted\n", + tport_fcid, oxid); + return; + } + + atomic64_inc(&fnic_stats->nvme_stats.nvme_ls_abort_responses); + nvfnic_ls_req->state = FNIC_LS_REQ_ABTS_COMPLETE; + + FNIC_NVME_DBG(KERN_DEBUG, fnic, "nvme_ls_requests: %lld\n", + (u64) atomic64_read(&fnic_stats->nvme_stats.nvme_ls_requests)); + + list_del(&nvfnic_ls_req->list); + fdls_free_oxid(iport, oxid, &nvfnic_ls_req->oxid); + lsreq->private = NULL; + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + timer_delete_sync(&nvfnic_ls_req->ls_req_timer); + lsreq->done(lsreq, NVME_SC_HOST_ABORTED_CMD); + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); +} + +/** + * nvfnic_ls_rsp_recv - Handle received NVMe FC link service (LS) response + * @iport: Pointer to the local FNIC port structure + * @fchdr: Pointer to the Fibre Channel frame header for the + * received response + * @len: Length of the received frame + * + * This function processes link service (LS) responses received from + * NVMe Discovery Controllers or regular NVMe subsystems during + * association. + */ +void nvfnic_ls_rsp_recv(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr, int len) +{ + uint8_t *fcid; + uint32_t tport_fcid; + struct fnic_tport_s *tport; + struct nvfnic_ls_req *nvfnic_ls_req; + struct nvmefc_ls_req *lsreq; + uint16_t oxid; + uint32_t rsp_len; + int sid_len = offsetof(struct fc_frame_header, fh_s_id) + + sizeof(fchdr->fh_s_id); + int status = 0; + struct fnic *fnic = iport->fnic; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + + if (len < (int)sizeof(*fchdr)) { + if (len >= sid_len) { + fcid = FNIC_STD_GET_S_ID(fchdr); + tport_fcid = ntoh24(fcid); + FNIC_NVME_DBG(KERN_ERR, fnic, + "tport: 0x%x LS rsp len %d too short\n", + tport_fcid, len); + } else { + FNIC_NVME_DBG(KERN_ERR, fnic, + "LS response len %d too short\n", len); + } + return; + } + rsp_len = len - sizeof(*fchdr); + + fcid = FNIC_STD_GET_S_ID(fchdr); + tport_fcid = ntoh24(fcid); + + tport = fnic_find_tport_by_fcid(iport, tport_fcid); + if (!tport) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "tport: 0x%x not found\n", tport_fcid); + return; + } + + oxid = FNIC_STD_GET_OX_ID(fchdr); + nvfnic_ls_req = nvfnic_find_ls_req(tport, oxid); + if (!nvfnic_ls_req) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "tport: 0x%x no nvfnic_lsreq for oxid: 0x%x\n", + tport_fcid, oxid); + return; + } + + lsreq = nvfnic_ls_req->ls_req; + if (!lsreq || (lsreq->private == NULL)) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "tport:0x%x lsreq:0x%x already done\n", + tport_fcid, oxid); + return; + } + if (!lsreq->rspaddr) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "tport 0x%x lsreq 0x%x rspaddr NULL\n", + tport_fcid, oxid); + return; + } + + if ((nvfnic_ls_req->state == FNIC_LS_REQ_CMD_ABTS_PENDING) || + (nvfnic_ls_req->state == FNIC_LS_REQ_CMD_ABTS_STARTED)) { + FNIC_NVME_DBG(KERN_INFO, fnic, + "tport 0x%x lsreq oxid: 0x%x abts pending\n", + tport_fcid, oxid); + return; + } + + if (rsp_len > lsreq->rsplen) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "tport:0x%x lsreq:0x%x rsp %u > %u\n", + tport_fcid, oxid, rsp_len, lsreq->rsplen); + status = -EOVERFLOW; + } + + nvfnic_ls_req->state = FNIC_LS_REQ_CMD_COMPLETE; + atomic64_inc(&fnic_stats->nvme_stats.nvme_ls_responses); + + list_del_init(&nvfnic_ls_req->list); + lsreq->private = NULL; + fdls_free_oxid(iport, oxid, &nvfnic_ls_req->oxid); + + if (status == 0) { + FNIC_NVME_DBG(KERN_DEBUG, fnic, + "tport:0x%x lsreq:0x%x completed\n", + tport_fcid, oxid); + + /* Copy the Response */ + memcpy(lsreq->rspaddr, (uint8_t *)fchdr + sizeof(*fchdr), + rsp_len); + } + + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + timer_delete_sync(&nvfnic_ls_req->ls_req_timer); + lsreq->done(lsreq, status); + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); +} + +static bool nvfnic_ls_req_cleanup(struct fnic_iport_s *iport, + struct nvmefc_ls_req *lsreq, + uint16_t oxid) +{ + struct nvfnic_ls_req *nvfnic_ls_req = lsreq->private; + + if (!nvfnic_ls_req) + return false; + + lsreq->private = NULL; + list_del(&nvfnic_ls_req->list); + fdls_free_oxid(iport, oxid, &nvfnic_ls_req->oxid); + nvfnic_ls_req->state = FNIC_LS_REQ_CMD_COMPLETE; + + return true; +} + +void nvfnic_ls_req_timeout(struct timer_list *t) +{ + struct nvfnic_ls_req *nvfnic_ls_req = timer_container_of(nvfnic_ls_req, + t, ls_req_timer); + struct fnic *fnic = nvfnic_ls_req->fnic; + struct nvmefc_ls_req *ls_req = nvfnic_ls_req->ls_req; + struct fnic_iport_s *iport = &fnic->iport; + struct fnic_tport_s *tport = (struct fnic_tport_s *) nvfnic_ls_req->tport; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + uint16_t oxid = nvfnic_ls_req->oxid; + int timeout; + + FNIC_NVME_DBG(KERN_INFO, fnic, + "tport: 0x%x lsreq: 0x%x state: %d timeout\n", + tport->fcid, nvfnic_ls_req->oxid, + nvfnic_ls_req->state); + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + + if ((ls_req->private == NULL) || + (nvfnic_ls_req->state == FNIC_LS_REQ_CMD_ABTS_STARTED)) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "tport: 0x%x lsreq: 0x%x already aborted\n", + tport->fcid, nvfnic_ls_req->oxid); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + + if (nvfnic_ls_req->state == FNIC_LS_REQ_CMD_ABTS_PENDING) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "tport: 0x%x lsreq: 0x%x abort timeout\n", + tport->fcid, nvfnic_ls_req->oxid); + + ls_req = nvfnic_ls_req->ls_req; + nvfnic_ls_req_cleanup(iport, ls_req, oxid); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + ls_req->done(ls_req, -ETIMEDOUT); + return; + } else if ((nvfnic_ls_req->state == FNIC_LS_REQ_CMD_PENDING) && + (nvfnic_transport_ready(iport, tport))) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "tport: 0x%x lsreq: 0x%x sending abort\n", + tport->fcid, nvfnic_ls_req->oxid); + nvfnic_ls_req->state = FNIC_LS_REQ_CMD_ABTS_PENDING; + atomic64_inc(&fnic_stats->nvme_stats.nvme_ls_aborts); + + if (fdls_send_ls_req_abts(iport, tport, nvfnic_ls_req->oxid) == 0) { + timeout = FNIC_LS_REQ_TMO_MSECS(ls_req->timeout); + mod_timer(&nvfnic_ls_req->ls_req_timer, + round_jiffies(jiffies + msecs_to_jiffies(timeout))); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + FNIC_NVME_DBG(KERN_ERR, fnic, + "tport: 0x%x lsreq: 0x%x cannot send abort\n", + tport->fcid, oxid); + } + + if (ls_req->private == NULL) { + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + + ls_req = nvfnic_ls_req->ls_req; + nvfnic_ls_req_cleanup(iport, ls_req, oxid); + + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + ls_req->done(ls_req, -ETIMEDOUT); +} + +/** + * nvfnic_ls_req_send - Send NVMe FC link service (LS) request + * @lport: Pointer to local NVMe FC port structure + * @rport: Pointer to remote NVMe FC port structure + * @ls_req: Pointer to the link service request structure + * + * This function is used to send link service (LS) commands to an NVMe + * Discovery Controller for discovery operations, as well as to regular + * NVMe subsystems during association. It encapsulates the logic for + * transmitting LS requests over the NVMe over Fabrics (NVMe-oF) FC + * transport. + * + * Returns: 0 on success, or a negative error code on failure. + */ +int nvfnic_ls_req_send(struct nvme_fc_local_port *lport, + struct nvme_fc_remote_port *rport, + struct nvmefc_ls_req *ls_req) +{ + int timeout; + uint8_t *frame; + uint8_t fcid[3]; + unsigned long flags = 0; + struct fnic_iport_s *iport = lport->private; + uint8_t *ls_req_payload; + struct fnic *fnic = iport->fnic; + struct fc_frame_header *fchdr; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + struct nvfnic_ls_req *nvfnic_ls_req = ls_req->private; + uint16_t frame_size = FNIC_ETH_FCOE_HDRS_OFFSET + + sizeof(struct fc_frame_header) + ls_req->rqstlen; + struct fnic_tport_s *tport; + int ret; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + + tport = (struct fnic_tport_s *)rport->private; + INIT_LIST_HEAD(&nvfnic_ls_req->list); + + if (!nvfnic_transport_ready(iport, tport)) { + if (tport != NULL) + FNIC_NVME_DBG(KERN_INFO, fnic, + "iport: 0x%x tport: 0x%x transport not ready\n", + iport->fcid, tport->fcid); + else + FNIC_NVME_DBG(KERN_INFO, fnic, + "iport: 0x%x transport not ready\n", + iport->fcid); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return -ENOLINK; + } + + frame = fdls_alloc_frame(iport); + if (frame == NULL) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "Failed to allocate frame to send NVME LS REQ"); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return -ENOMEM; + } + + if (fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_NVME_LS, + &nvfnic_ls_req->oxid) == FNIC_UNASSIGNED_OXID) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "0x%x: Failed to allocate OXID to send NVME LS REQ", + iport->fcid); + mempool_free(frame, fnic->frame_pool); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return -EAGAIN; + } + + atomic64_inc(&fnic_stats->nvme_stats.nvme_ls_requests); + timer_setup(&nvfnic_ls_req->ls_req_timer, nvfnic_ls_req_timeout, + 0UL); + + nvfnic_ls_req->fnic = fnic; + nvfnic_ls_req->tport = tport; + nvfnic_ls_req->state = FNIC_LS_REQ_CMD_INIT; + nvfnic_ls_req->ls_req = ls_req; + + fchdr = (struct fc_frame_header *)(frame + FNIC_ETH_FCOE_HDRS_OFFSET); + *fchdr = (struct fc_frame_header) { + .fh_r_ctl = FC_RCTL_ELS4_REQ, + .fh_type = FC_TYPE_NVME, + .fh_f_ctl = {FNIC_ELS_REQ_FCTL, 0, 0}, + .fh_rx_id = cpu_to_be16(FNIC_UNASSIGNED_RXID) + }; + + hton24(fcid, iport->fcid); + FNIC_STD_SET_S_ID(*fchdr, fcid); + + hton24(fcid, tport->fcid); + FNIC_STD_SET_D_ID(*fchdr, fcid); + + FNIC_STD_SET_OX_ID(*fchdr, nvfnic_ls_req->oxid); + + ls_req_payload = frame + FNIC_ETH_FCOE_HDRS_OFFSET + sizeof(*fchdr); + memcpy(ls_req_payload, ls_req->rqstaddr, ls_req->rqstlen); + + FNIC_NVME_DBG(KERN_INFO, fnic, + "0x%x: NVME send ls req with oxid: 0x%x type: 0x%02x len: %d", + iport->fcid, nvfnic_ls_req->oxid, *((uint8_t *) ls_req->rqstaddr), + ls_req->rqstlen); + + FNIC_NVME_DBG(KERN_INFO, fnic, + "0x%x: ls_reqs count: %lld", + iport->fcid, + (u64) atomic64_read(&fnic_stats->nvme_stats.nvme_ls_requests)); + + list_add_tail(&nvfnic_ls_req->list, &tport->ls_req_list); + nvfnic_ls_req->state = FNIC_LS_REQ_CMD_PENDING; + + ret = fnic_send_fcoe_frame(iport, frame, frame_size); + if (ret) { + nvfnic_ls_req_cleanup(iport, ls_req, nvfnic_ls_req->oxid); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + mempool_free(frame, fnic->frame_pool); + return ret; + } + + timeout = FNIC_LS_REQ_TMO_MSECS(ls_req->timeout); + mod_timer(&nvfnic_ls_req->ls_req_timer, + round_jiffies(jiffies + msecs_to_jiffies(timeout))); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + return 0; +} + +void nvfnic_local_port_delete(struct nvme_fc_local_port *lport) +{ + struct fnic_iport_s *iport = (struct fnic_iport_s *) lport->private; + struct fnic *fnic = iport->fnic; + unsigned long flags = 0; + + FNIC_NVME_DBG(KERN_INFO, fnic, "lport delete 0x%x\n", + iport->fcid); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + if (fnic->nvme_lport_unreg_done) + complete(fnic->nvme_lport_unreg_done); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); +} + +void nvfnic_remote_port_delete(struct nvme_fc_remote_port *rport) +{ + /* + * Read rport->private without the lock only to find fnic. + * Re-read it under fnic_lock to claim this delete callback, since + * another callback may already have cleared it. + */ + struct fnic_tport_s *tport = (struct fnic_tport_s *)rport->private; + struct fnic_iport_s *iport; + struct fnic *fnic = NULL; + unsigned long flags = 0; + + if (tport == NULL) { + pr_err("Attempt to delete already deleted tport\n"); + return; + } + + iport = tport->iport; + fnic = iport->fnic; + FNIC_NVME_DBG(KERN_INFO, fnic, "0x%x tport 0x%x\n", + iport->fcid, tport->fcid); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + tport = (struct fnic_tport_s *)rport->private; + if (tport == NULL) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "NVMe tport callback after NULL set %p\n", + rport); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return; + } + + iport = tport->iport; + rport->private = NULL; + + if (tport->timer_pending) { + FNIC_NVME_DBG(KERN_INFO, fnic, + "tport: 0x%x canceling discovery timer\n", + tport->fcid); + /* + * The retry timer callback takes fnic_lock and dereferences + * this tport. Set del_timer_inprogress before dropping the + * lock so a callback that is already running observes teardown + * and exits. + * + * timer_delete_sync() then waits until no callback can still + * hold a reference before the remoteport-delete path completes + * or frees the target port. + */ + tport->del_timer_inprogress = 1; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + timer_delete_sync(&tport->retry_timer); + spin_lock_irqsave(&fnic->fnic_lock, flags); + tport->del_timer_inprogress = 0; + tport->timer_pending = 0; + } + + if (tport->flags & FNIC_FDLS_NVME_TPORT_CLEANUP_PENDING) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "tport %8x waiting on clean pending\n", + tport->fcid); + } + + while (tport->flags & FNIC_FDLS_NVME_TPORT_CLEANUP_PENDING) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + msleep(2000); + spin_lock_irqsave(&fnic->fnic_lock, flags); + } + + list_del(&tport->links); + + if (tport->tport_del_done) { + tport->flags |= FNIC_TPORT_CAN_BE_FREED; + complete(tport->tport_del_done); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return; + } + + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + kfree(tport); +} + +int nvfnic_create_queue(struct nvme_fc_local_port *lport, + unsigned int idx, u16 size, void **handle) +{ + struct fnic_iport_s *iport = (struct fnic_iport_s *)lport->private; + struct fnic *fnic = iport->fnic; + + FNIC_NVME_DBG(KERN_DEBUG, fnic, + "iport:0x%x queue:%d size:%d\n", iport->fcid, idx, size); + + if (idx > fnic->wq_copy_count) + return -EINVAL; + + if (idx == 0) { + /* Admin queue */ + *handle = &fnic->hw_copy_wq[0]; + } else { + /* IO queues */ + *handle = &fnic->hw_copy_wq[idx-1]; + } + + return 0; +} + +void nvfnic_ls_req_abort(struct nvme_fc_local_port *lport, + struct nvme_fc_remote_port *rport, + struct nvmefc_ls_req *lsreq) +{ + struct fnic_iport_s *iport = lport->private; + struct fnic *fnic = iport->fnic; + struct fnic_tport_s *tport; + struct nvfnic_ls_req *nvfnic_ls_req; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + uint16_t oxid; + int timeout; + int ret; + + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + + tport = (struct fnic_tport_s *) rport->private; + /* find the request */ + nvfnic_ls_req = lsreq->private; + + if (nvfnic_ls_req == NULL) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "0x%x null lsreq already scheduled for abort\n", + iport->fcid); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + + if (nvfnic_ls_req->state == FNIC_LS_REQ_CMD_ABTS_PENDING) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "0x%x lsreq 0x%x already scheduled for abort\n", + iport->fcid, nvfnic_ls_req->oxid); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + + FNIC_NVME_DBG(KERN_INFO, fnic, + "0x%x lsreq 0x%x abts\n", + iport->fcid, nvfnic_ls_req->oxid); + + nvfnic_ls_req->state = FNIC_LS_REQ_CMD_ABTS_STARTED; + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + timer_delete_sync(&nvfnic_ls_req->ls_req_timer); + + spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags); + nvfnic_ls_req = lsreq->private; + + if ((nvfnic_ls_req == NULL) || + (nvfnic_ls_req->state == FNIC_LS_REQ_CMD_ABTS_PENDING)) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "lsreq timeout raced with midlayer abort\n"); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + + /* Basic validations of the state */ + if (!nvfnic_transport_ready(iport, tport)) { + /* If iport or tport offline, it will be handled from that event */ + oxid = nvfnic_ls_req->oxid; + lsreq->private = NULL; + list_del(&nvfnic_ls_req->list); + fdls_free_oxid(iport, oxid, &nvfnic_ls_req->oxid); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + lsreq->done(lsreq, -ENXIO); + FNIC_NVME_DBG(KERN_ERR, fnic, + "nvfnic_lsreq_abort transport not ready\n"); + return; + } + + /* Mark the state and flags */ + nvfnic_ls_req->state = FNIC_LS_REQ_CMD_ABTS_PENDING; + atomic64_inc(&fnic_stats->nvme_stats.nvme_ls_aborts); + oxid = nvfnic_ls_req->oxid; + + ret = fdls_send_ls_req_abts(iport, tport, oxid); + if (!ret) { + timeout = FNIC_LS_REQ_TMO_MSECS(lsreq->timeout); + mod_timer(&nvfnic_ls_req->ls_req_timer, + round_jiffies(jiffies + msecs_to_jiffies(timeout))); + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + + if (!nvfnic_ls_req_cleanup(iport, lsreq, oxid)) { + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + return; + } + + spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags); + lsreq->done(lsreq, -EAGAIN); +} + +bool nvfnic_queue_abort_io_req(struct fnic *fnic, int tag, + u32 task_req, struct fnic_io_req *io_req) +{ + int idx; + unsigned long flags; + struct misc_stats *misc_stats = &fnic->fnic_stats.misc_stats; + + idx = io_req->wq - &fnic->hw_copy_wq[0]; + + atomic_inc(&fnic->in_flight); + + spin_lock_irqsave(&fnic->wq_copy_lock[idx], flags); + + if (vnic_wq_copy_desc_avail(io_req->wq) <= fnic->wq_copy_desc_low[idx]) + free_wq_copy_descs(fnic, io_req->wq, idx); + + if (!vnic_wq_copy_desc_avail(io_req->wq)) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[idx], flags); + atomic_dec(&fnic->in_flight); + FNIC_NVME_DBG(KERN_ERR, fnic, + "tag 0x%x failure: no descriptors\n", tag); + atomic64_inc(&misc_stats->abts_cpwq_alloc_failures); + return false; + } + fnic_queue_wq_copy_desc_itmf(io_req->wq, tag | FNIC_TAG_ABORT, + 0, task_req, tag, NULL, io_req->port_id, + fnic->config.ra_tov, fnic->config.ed_tov); + + atomic64_inc(&fnic->fnic_stats.fw_stats.active_fw_reqs); + if (atomic64_read(&fnic->fnic_stats.fw_stats.active_fw_reqs) > + atomic64_read(&fnic->fnic_stats.fw_stats.max_fw_reqs)) + atomic64_set(&fnic->fnic_stats.fw_stats.max_fw_reqs, + atomic64_read(&fnic->fnic_stats.fw_stats.active_fw_reqs)); + + spin_unlock_irqrestore(&fnic->wq_copy_lock[idx], flags); + atomic_dec(&fnic->in_flight); + + return true; +} + +void nvfnic_fcpio_abort(struct nvme_fc_local_port *lport, + struct nvme_fc_remote_port *rport, + void *hw_queue_handle, struct nvmefc_fcp_req *fcp_req) +{ + struct fnic_iport_s *iport = lport->private; + struct fnic *fnic = iport->fnic; + struct nvme_fc_cmd_iu *cmd_iu = fcp_req->cmdaddr; + struct fnic_io_req *io_req = (struct fnic_io_req *)fcp_req->private; + unsigned int tag = io_req->tag; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + struct abort_stats *abts_stats; + unsigned long flags = 0; + unsigned long abt_issued_time; + unsigned int task_req; + enum fnic_ioreq_state old_ioreq_state; + unsigned long num_ios_waitq, waitq_2sec, waitq_max_time; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + + if (io_req->tag == FNIC_NVME_NO_FREE_TAG) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "tag: (0x%x) tport_fcid: 0x%x\n", + io_req->tag, io_req->tport->fcid); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return; + } + + if (io_req != nvfnic_find_io_req_by_tag(fnic, io_req->tag)) { + FNIC_NVME_DBG(KERN_INFO, fnic, + "cmd tag freed or not issued:0x%x sn:0x%08x\n", + io_req->tag, be32_to_cpu(cmd_iu->csn)); + num_ios_waitq = + atomic64_read(&fnic_stats->io_stats.nvme_num_ios_in_waitq); + waitq_2sec = + atomic64_read(&fnic_stats->io_stats.nvme_ios_in_waitq_3000_msec); + waitq_max_time = + atomic64_read(&fnic_stats->io_stats.nvme_ios_in_waitq_max_time); + FNIC_NVME_DBG(KERN_INFO, fnic, + "waitq:%ld waitq_2sec:%ld max_wait:%ld\n", + num_ios_waitq, waitq_2sec, waitq_max_time); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return; + } + + if (io_req->cmd_state == FNIC_IOREQ_CMD_COMPLETE) { + FNIC_NVME_DBG(KERN_INFO, fnic, + "IO already completed before abort: 0x%x\n", tag); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return; + } + + if ((io_req->cmd_state == FNIC_IOREQ_ABTS_PENDING) || + (io_req->cmd_state == FNIC_DEV_RST_TERM_ISSUED)) { + FNIC_NVME_DBG(KERN_INFO, fnic, "abort already pending %d\n", + tag); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return; + } + + if (io_req->cmd_state != FNIC_IOREQ_CMD_PENDING) { + FNIC_NVME_DBG(KERN_INFO, fnic, + "io_req completed or aborted for tag:0x%x\n", + tag); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return; + } + + FNIC_NVME_DBG(KERN_INFO, fnic, "in abort cmd_sn:%08x %llx tag: 0x%x\n", + be32_to_cpu(cmd_iu->csn), + le64_to_cpu(cmd_iu->sqe.rw.slba), io_req->tag); + + if (unlikely(fnic_chk_state_flags_locked(fnic, FNIC_FLAGS_IO_BLOCKED))) { + FNIC_NVME_DBG(KERN_INFO, fnic, + "abort tag:0x%x returned during fw reset\n", + io_req->tag); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return; + } + atomic_inc(&fnic->in_flight); + + if (fdls_tport_is_offline(io_req->tport) || + (io_req->cmd_state == FNIC_IOREQ_RESET_TERM)) { + task_req = FCPIO_ITMF_ABT_TASK_TERM; + } else { + task_req = FCPIO_ITMF_ABT_TASK; + } + + abts_stats = &fnic->fnic_stats.abts_stats; + atomic64_inc(&abts_stats->aborts); + + abt_issued_time = jiffies_to_msecs(jiffies - io_req->start_time); + if (abt_issued_time <= 6000) + atomic64_inc(&abts_stats->abort_issued_btw_0_to_6_sec); + else if (abt_issued_time > 6000 && abt_issued_time <= 20000) + atomic64_inc(&abts_stats->abort_issued_btw_6_to_20_sec); + else if (abt_issued_time > 20000 && abt_issued_time <= 30000) + atomic64_inc(&abts_stats->abort_issued_btw_20_to_30_sec); + else if (abt_issued_time > 30000 && abt_issued_time <= 40000) + atomic64_inc(&abts_stats->abort_issued_btw_30_to_40_sec); + else if (abt_issued_time > 40000 && abt_issued_time <= 50000) + atomic64_inc(&abts_stats->abort_issued_btw_40_to_50_sec); + else if (abt_issued_time > 50000 && abt_issued_time <= 60000) + atomic64_inc(&abts_stats->abort_issued_btw_50_to_60_sec); + else + atomic64_inc(&abts_stats->abort_issued_greater_than_60_sec); + + old_ioreq_state = io_req->cmd_state; + io_req->cmd_state = FNIC_IOREQ_ABTS_PENDING; + io_req->abts_state = FCPIO_INVALID_CODE; + + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + if (!nvfnic_queue_abort_io_req(fnic, io_req->tag, task_req, io_req)) { + FNIC_NVME_DBG(KERN_INFO, fnic, + "Abort io req queue failed\n"); + spin_lock_irqsave(&fnic->fnic_lock, flags); + io_req->cmd_state = old_ioreq_state; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + } + atomic_dec(&fnic->in_flight); +} + +struct +nvme_fc_port_template nvfnic_port = { + .localport_delete = nvfnic_local_port_delete, + .remoteport_delete = nvfnic_remote_port_delete, + .create_queue = nvfnic_create_queue, + .delete_queue = NULL, + .ls_req = nvfnic_ls_req_send, + .ls_abort = nvfnic_ls_req_abort, + .fcp_io = nvfnic_fcpio_send, + .fcp_abort = nvfnic_fcpio_abort, + .max_hw_queues = 1, + .max_sgl_segments = 256, + .max_dif_sgl_segments = 64, + .dma_boundary = 0xFFFFFFFF, + .local_priv_sz = sizeof(struct fnic_iport_s *), + .remote_priv_sz = sizeof(struct fnic_tport_s *), + .lsrqst_priv_sz = sizeof(struct nvfnic_ls_req), + .fcprqst_priv_sz = sizeof(struct fnic_io_req), +}; + +void nvfnic_flush_nvme_io_list(struct fnic *fnic) +{ + queue_work(fnic_cmpl_queue, &fnic->nvme_io_cmpl_work); + flush_work(&fnic->nvme_io_cmpl_work); +} + +void nvfnic_nvme_iodone_work(struct work_struct *work) +{ + struct fnic *fnic = container_of(work, struct fnic, nvme_io_cmpl_work); + struct llist_node *llnode; + struct fnic_io_req *io_req, *tmp; + + llnode = llist_del_all(&fnic->nvme_io_event_llist); + llist_for_each_entry_safe(io_req, tmp, llnode, nvfnic_io_cmpl) { + atomic_dec(&fnic->nvme_io_event_queued); + atomic64_dec(&fnic->fnic_stats.io_stats.nvme_num_ios_in_waitq); + io_req->fcp_req->done(io_req->fcp_req); + } +} + +void nvfnic_exch_reset(struct fnic_iport_s *iport, struct fnic_tport_s *tport) +{ + FNIC_NVME_DBG(KERN_DEBUG, iport->fnic, + "0x%x: Exchange reset scheduled for tport: 0x%x\n", + iport->fcid, tport->fcid); + + nvfnic_terminate_tport_ls_reqs(iport->fnic, tport); + nvfnic_terminate_tport_ios(iport->fnic, tport); +} + +void nvfnic_delete_tport(struct fnic_iport_s *iport, + struct fnic_tport_s *tport, + unsigned long flags) +{ + struct fnic *fnic = iport->fnic; + int ret; + unsigned int time_wait = FNIC_NVME_TPORT_REMOVE_WAIT; + unsigned int time_remain; + DECLARE_COMPLETION_ONSTACK(tm_done); + unsigned int fcid; + int count = 0; + + if (!tport) + return; + + fcid = tport->fcid; + fdls_set_tport_state(tport, FDLS_TGT_STATE_OFFLINE); + + FNIC_NVME_DBG(KERN_DEBUG, fnic, + "0x%x: scheduled deletion for tport: 0x%x\n", + iport->fcid, tport->fcid); + + if (!(tport->flags & FNIC_FDLS_NVME_REGISTERED)) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "0x%x: tport: 0x%x not registered. Freeing\n", + iport->fcid, tport->fcid); + list_del(&tport->links); + kfree(tport); + return; + } + + tport->tport_del_done = &tm_done; + + tport->flags |= FNIC_FDLS_TPORT_DELETED; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + ret = nvme_fc_unregister_remoteport(tport->nv_rport); + if (ret) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "tport: 0x%x unregister failed %d\n", + tport->fcid, ret); + + nvfnic_terminate_tport_ls_reqs(fnic, tport); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + tport->tport_del_done = NULL; + if (tport->nv_rport && tport->nv_rport->private == tport) + tport->nv_rport->private = NULL; + list_del(&tport->links); + kfree(tport); + return; + } + time_remain = wait_for_completion_timeout(tport->tport_del_done, + msecs_to_jiffies(time_wait)); + + FNIC_NVME_DBG(KERN_DEBUG, fnic, + "tport: 0x%x wait for deletion done\n", + tport->fcid); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + tport->tport_del_done = NULL; + + if (!time_remain) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "tport: 0x%x nvme midlayer completion timed out\n", + tport->fcid); + + if (tport->flags & FNIC_TPORT_CAN_BE_FREED) { + kfree(tport); + FNIC_NVME_DBG(KERN_INFO, fnic, + "tport: 0x%x delete complete\n", fcid); + return; + } + + tport->flags |= FNIC_FDLS_NVME_TPORT_CLEANUP_PENDING; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + nvfnic_cleanup_tport_io(fnic, tport); + spin_lock_irqsave(&fnic->fnic_lock, flags); + tport->flags &= ~FNIC_FDLS_NVME_TPORT_CLEANUP_PENDING; + return; + } + while (!(tport->flags & FNIC_TPORT_CAN_BE_FREED) && + (count < FNIC_TPORT_CLEANUP_WAIT_COUNT)) { + count++; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + msleep(2000); + spin_lock_irqsave(&fnic->fnic_lock, flags); + } + if (tport->flags & FNIC_TPORT_CAN_BE_FREED) + kfree(tport); + + FNIC_NVME_DBG(KERN_INFO, fnic, + "tport: 0x%x delete complete\n", fcid); +} + +int nvfnic_add_tport(struct fnic *fnic, struct fnic_tport_s *tport, + unsigned long flags) +{ + struct fnic_iport_s *iport = &fnic->iport; + struct nvme_fc_port_info pinfo; + int ret = 0; + + FNIC_NVME_DBG(KERN_INFO, fnic, + "Adding tport to nvme wwpn: 0x%llx\n", + tport->wwpn); + + memset(&pinfo, 0, sizeof(struct nvme_fc_port_info)); + + pinfo.port_name = tport->wwpn; + pinfo.node_name = tport->wwnn; + pinfo.port_role = FC_PORT_ROLE_NVME_DISCOVERY | FC_PORT_ROLE_NVME_TARGET; + pinfo.port_id = tport->fcid; + pinfo.dev_loss_tmo = nvme_dev_loss_tmo; + + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + ret = nvme_fc_register_remoteport(iport->nv_lport, &pinfo, + &tport->nv_rport); + spin_lock_irqsave(&fnic->fnic_lock, flags); + if (ret) { + FNIC_NVME_DBG(KERN_INFO, fnic, + "Failed to register tport wwpn: 0x%llx ret: %d\n", + tport->wwpn, ret); + return ret; + } + tport->flags |= FNIC_FDLS_NVME_REGISTERED; + tport->nv_rport->private = tport; + + snprintf(tport->str_wwpn, sizeof(tport->str_wwpn), "0x%llx", tport->wwpn); + snprintf(tport->str_wwnn, sizeof(tport->str_wwnn), "0x%llx", tport->wwnn); + return ret; +} + +int nvfnic_add_lport(struct fnic *fnic) +{ + struct nvme_fc_port_info pinfo = {}; + struct fnic_iport_s *iport = &fnic->iport; + int ret = 0; + + FNIC_NVME_DBG(KERN_INFO, fnic, + "Adding lport nvme wwpn: 0x%llx\n", + iport->wwpn); + + pinfo.node_name = iport->wwnn; + pinfo.port_name = iport->wwpn; + pinfo.port_role = FC_PORT_ROLE_NVME_INITIATOR; + pinfo.port_id = iport->fcid; + + nvfnic_reset_fcpio_tag_pool(iport); + + if (!(iport->flags & FNIC_LPORT_NVME_REGISTERED)) { + iport->nv_tmpl = kzalloc_obj(struct nvme_fc_port_template, GFP_ATOMIC); + if (!iport->nv_tmpl) { + FNIC_FCS_DBG(KERN_INFO, fnic, + "iport:0x%x NVMe tmpl alloc failed\n", + iport->fcid); + return -ENOMEM; + } + memcpy(iport->nv_tmpl, &nvfnic_port, + sizeof(struct nvme_fc_port_template)); + iport->nv_tmpl->max_hw_queues = fnic->wq_copy_count; + + ret = nvme_fc_register_localport(&pinfo, iport->nv_tmpl, + &fnic->pdev->dev, &iport->nv_lport); + if (ret) { + FNIC_NVME_DBG(KERN_ERR, fnic, + "Failed to add wwpn: 0x%llx ret: %d\n", + iport->wwpn, ret); + kfree(iport->nv_tmpl); + return ret; + } + iport->flags |= FNIC_LPORT_NVME_REGISTERED; + iport->nv_lport->private = iport; + } + + sprintf(iport->str_wwpn, "0x%llx", iport->wwpn); + sprintf(iport->str_wwnn, "0x%llx", iport->wwnn); + + FNIC_NVME_DBG(KERN_INFO, fnic, + "Successfully added lport wwpn: 0x%llx\n", + iport->wwpn); + return 0; +} + +#endif diff --git a/drivers/scsi/fnic/fnic_nvme.h b/drivers/scsi/fnic/fnic_nvme.h new file mode 100644 index 000000000..c05908b68 --- /dev/null +++ b/drivers/scsi/fnic/fnic_nvme.h @@ -0,0 +1,214 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _FNIC_NVME_H +#define _FNIC_NVME_H + +#include "fdls_fc.h" +#include "fnic_fdls.h" + +#define FNIC_NVME_TPORT_REMOVE_WAIT (5 * 1000) +#define FNIC_NVME_TPORT_LIST_EMPTY_WAIT (FNIC_NVME_TPORT_REMOVE_WAIT * 2) +#define FNIC_TPORT_CLEANUP_WAIT_COUNT 8 +#define FNIC_NVME_LPORT_REMOVE_WAIT (2 * 60 * 1000) + +#define FNIC_LS_REQ_FLAGS_NONE 0x0 +#define FNIC_LS_REQ_FLAGS_ABORTED 0x1 +#define FNIC_LS_REQ_FLAGS_DONE 0x2 +#define FNIC_LS_REQ_ABORT_COMPLETED 0x4 +#define FNIC_STATUS_LS_REQ_ABORTED 0x1 + +#define FNIC_LS_REQ_MIN_TMO_SECS (2) +#define FNIC_LS_REQ_MAX_TMO_SECS (5) +#define FNIC_NVME_ADMIN_IO_TIMEOUT 30000 /* mSec */ +#define FNIC_NVME_NO_FREE_TAG (0xFFFF) + +#define FNIC_LS_REQ_TMO_MSECS(tmo) (((tmo >= FNIC_LS_REQ_MIN_TMO_SECS) && \ + (tmo <= FNIC_LS_REQ_MAX_TMO_SECS)) ? \ + (tmo * 1000) : (FNIC_LS_REQ_MIN_TMO_SECS * 1000)) + +#define IS_ADMIN_IO(_io_req) \ + (NVME_CMD_FLAGS(_io_req) & FNIC_NVME_ADMIN_IO_TIMER_PENDING) + +extern unsigned int nvme_max_ios_to_process; +extern unsigned int nvme_dev_loss_tmo; +extern spinlock_t fnic_list_lock; + +enum nvfnic_lsreq_state_e { + FNIC_LS_REQ_CMD_INIT = 0, + FNIC_LS_REQ_CMD_PENDING, + FNIC_LS_REQ_CMD_ABTS_PENDING, + FNIC_LS_REQ_CMD_COMPLETE, + FNIC_LS_REQ_ABTS_COMPLETE, + FNIC_LS_REQ_CMD_ABTS_STARTED, +}; + +struct fnic_nvme_tag { + struct list_head free_list; + int tag_id; +}; + +struct nvfnic_ls_req { + struct list_head list; + struct nvmefc_ls_req *ls_req; + uint16_t oxid; + struct timer_list ls_req_timer; + struct fnic *fnic; + struct fnic_tport_s *tport; + int state; + unsigned int flags; +}; + +#if IS_REACHABLE(CONFIG_NVME_FC) +int nvfnic_nvme_io_done_handler(void *arg); +struct fnic_io_req *nvfnic_find_io_req_by_tag(struct fnic *fnic, uint16_t tag); +void nvfnic_reset_fcpio_tag_pool(struct fnic_iport_s *iport); +int nvfnic_add_lport(struct fnic *fnic); +void nvfnic_fcpio_abort(struct nvme_fc_local_port *lport, + struct nvme_fc_remote_port *rport, + void *hw_queue_handle, struct nvmefc_fcp_req *fcp_req); +void nvfnic_remote_port_delete(struct nvme_fc_remote_port *rport); +void nvfnic_local_port_delete(struct nvme_fc_local_port *lport); +int nvfnic_dma_map_sgl(struct fnic *fnic, struct fnic_io_req *io_req, + int sg_count); +void nvfnic_dma_unmap_sgl(struct fnic *fnic, struct fnic_io_req *io_req); +int nvfnic_get_sg_count(struct fnic_io_req *io_req); +void nvfnic_release_nvme_ioreq_buf(struct fnic_iport_s *iport, + struct fnic_io_req *io_req); +void nvfnic_dump_nvcmd(struct fnic_io_req *io_req, uint8_t flags); +bool _cleanup_tport_io(struct sbitmap *map, unsigned int tag, void *data); +void nvfnic_flush_nvme_io_list(struct fnic *fnic); +void nvfnic_fcpio_cmpl(struct fnic_io_req *io_req); +bool nvfnic_transport_ready(struct fnic_iport_s *iport, + struct fnic_tport_s *tport); +int nvfnic_alloc_fcpio_tag(struct fnic_iport_s *iport, + struct fnic_io_req *io_req); +int nvfnic_queuecommand(struct fnic_io_req *io_req); +void nvfnic_free_fcpio_tag(struct fnic_iport_s *iport, + struct fnic_io_req *io_req); +void nvfnic_delete_lport(struct fnic_iport_s *iport); +int nvfnic_add_tport(struct fnic *fnic, struct fnic_tport_s *tport, + unsigned long flags); +void nvfnic_cleanup_all_nvme_ios(struct fnic *fnic); +void nvfnic_delete_tport_work(struct work_struct *work); +void nvfnic_delete_tport(struct fnic_iport_s *iport, + struct fnic_tport_s *tport, unsigned long flags); +void nvfnic_fcpio_nvme_fast_cmpl_handler(struct fnic *fnic, + struct fcpio_fw_req *desc); +void nvfnic_ls_rsp_recv(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr, int len); +void nvfnic_process_ls_abts_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr); +void nvfnic_admin_io_timeout(struct timer_list *t); +void nvfnic_fcpio_nvme_itmf_cmpl_handler(struct fnic *fnic, + struct fcpio_fw_req *desc); +void nvfnic_nvme_zero_devloss_tports(struct fnic *fnic); +bool nvfnic_queue_abort_io_req(struct fnic *fnic, int tag, u32 task_req, + struct fnic_io_req *io_req); +void nvfnic_ls_req_abort(struct nvme_fc_local_port *lport, + struct nvme_fc_remote_port *rport, + struct nvmefc_ls_req *lsreq); +int nvfnic_create_queue(struct nvme_fc_local_port *lport, unsigned int idx, + u16 size, void **handle); +int nvfnic_ls_req_send(struct nvme_fc_local_port *lport, + struct nvme_fc_remote_port *rport, + struct nvmefc_ls_req *ls_req); +void nvfnic_ls_req_timeout(struct timer_list *t); +uint16_t nvfnic_alloc_ls_req_oxid(struct fnic_iport_s *iport); +struct nvfnic_ls_req *nvfnic_find_ls_req(struct fnic_tport_s *tport, + uint16_t oxid); +void nvfnic_terminate_tport_ios(struct fnic *fnic, struct fnic_tport_s *tport); +bool _terminate_tport_ios(struct sbitmap *map, unsigned int tag, void *data); +bool _cleanup_all_nvme_io(struct sbitmap *map, unsigned int tag, void *data); +void nvfnic_cleanup_all_nvme_ios(struct fnic *fnic); +int nvfnic_fcpio_send(struct nvme_fc_local_port *lport, + struct nvme_fc_remote_port *rport, void *hw_queue_handle, + struct nvmefc_fcp_req *fcp_req); +void nvfnic_fcpio_ersp_cmpl_handler(struct fnic *fnic, + struct fcpio_fw_req *desc, int sw_flag); +void nvfnic_terminate_tport_ls_reqs(struct fnic *fnic, + struct fnic_tport_s *tport); +void nvfnic_terminate_tport_admin_ios(struct fnic *fnic, + struct fnic_tport_s *tport); +void nvfnic_cleanup_tport_io(struct fnic *fnic, struct fnic_tport_s *tport); +void nvfnic_nvme_unload(struct fnic *fnic); +int nvfnic_get_nvmef_info(struct fnic *fnic, struct fnic_nvmef_info *info); +void nvfnic_exch_reset(struct fnic_iport_s *iport, struct fnic_tport_s *tport); +void nvfnic_nvme_iodone_work(struct work_struct *work); +#else +static inline int nvfnic_add_lport(struct fnic *fnic) +{ + return -EOPNOTSUPP; +} + +static inline void nvfnic_nvme_unload(struct fnic *fnic) +{ +} + +static inline void nvfnic_flush_nvme_io_list(struct fnic *fnic) +{ +} + +static inline void nvfnic_nvme_iodone_work(struct work_struct *work) +{ +} + +static inline void nvfnic_exch_reset(struct fnic_iport_s *iport, + struct fnic_tport_s *tport) +{ +} + +static inline void nvfnic_process_ls_abts_rsp(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr) +{ +} + +static inline void nvfnic_ls_rsp_recv(struct fnic_iport_s *iport, + struct fc_frame_header *fchdr, int len) +{ +} + +static inline int nvfnic_add_tport(struct fnic *fnic, + struct fnic_tport_s *tport, + unsigned long flags) +{ + return 0; +} + +static inline void nvfnic_delete_tport(struct fnic_iport_s *iport, + struct fnic_tport_s *tport, + unsigned long flags) +{ +} + +static inline void nvfnic_cleanup_all_nvme_ios(struct fnic *fnic) +{ +} + +static inline void nvfnic_fcpio_nvme_fast_cmpl_handler(struct fnic *fnic, + struct fcpio_fw_req *desc) +{ +} + +static inline void nvfnic_fcpio_ersp_cmpl_handler(struct fnic *fnic, + struct fcpio_fw_req *desc, + int sw_flag) +{ +} + +static inline void nvfnic_fcpio_nvme_itmf_cmpl_handler(struct fnic *fnic, + struct fcpio_fw_req *desc) +{ +} + +static inline int nvfnic_get_nvmef_info(struct fnic *fnic, + struct fnic_nvmef_info *info) +{ + return 0; +} +#endif + +extern const char *fnic_fcpio_status_to_str(unsigned int status); +#endif /* _FNIC_NVME_H */ diff --git a/drivers/scsi/fnic/fnic_pci_subsys_devid.c b/drivers/scsi/fnic/fnic_pci_subsys_devid.c new file mode 100644 index 000000000..36a2c1268 --- /dev/null +++ b/drivers/scsi/fnic/fnic_pci_subsys_devid.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ + +#include <linux/module.h> +#include <linux/mempool.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include <linux/kthread.h> +#include <linux/if_ether.h> +#include "fnic.h" + +static struct fnic_pcie_device fnic_pcie_device_table[] = { + {PCI_DEVICE_ID_CISCO_SERENO, "Sereno", PCI_SUBDEVICE_ID_CISCO_VASONA, + "VIC 1280"}, + {PCI_DEVICE_ID_CISCO_SERENO, "Sereno", PCI_SUBDEVICE_ID_CISCO_COTATI, + "VIC 1240"}, + {PCI_DEVICE_ID_CISCO_SERENO, "Sereno", + PCI_SUBDEVICE_ID_CISCO_LEXINGTON, "VIC 1225"}, + {PCI_DEVICE_ID_CISCO_SERENO, "Sereno", PCI_SUBDEVICE_ID_CISCO_ICEHOUSE, + "VIC 1285"}, + {PCI_DEVICE_ID_CISCO_SERENO, "Sereno", + PCI_SUBDEVICE_ID_CISCO_KIRKWOODLAKE, "VIC 1225T"}, + {PCI_DEVICE_ID_CISCO_SERENO, "Sereno", + PCI_SUBDEVICE_ID_CISCO_SUSANVILLE, "VIC 1227"}, + {PCI_DEVICE_ID_CISCO_SERENO, "Sereno", PCI_SUBDEVICE_ID_CISCO_TORRANCE, + "VIC 1227T"}, + + {PCI_DEVICE_ID_CISCO_CRUZ, "Cruz", PCI_SUBDEVICE_ID_CISCO_CALISTOGA, + "VIC 1340"}, + {PCI_DEVICE_ID_CISCO_CRUZ, "Cruz", PCI_SUBDEVICE_ID_CISCO_MOUNTAINVIEW, + "VIC 1380"}, + {PCI_DEVICE_ID_CISCO_CRUZ, "Cruz", PCI_SUBDEVICE_ID_CISCO_MOUNTTIAN, + "C3260-SIOC"}, + {PCI_DEVICE_ID_CISCO_CRUZ, "Cruz", PCI_SUBDEVICE_ID_CISCO_CLEARLAKE, + "VIC 1385"}, + {PCI_DEVICE_ID_CISCO_CRUZ, "Cruz", PCI_SUBDEVICE_ID_CISCO_MOUNTTIAN2, + "C3260-SIOC"}, + {PCI_DEVICE_ID_CISCO_CRUZ, "Cruz", PCI_SUBDEVICE_ID_CISCO_CLAREMONT, + "VIC 1387"}, + + {PCI_DEVICE_ID_CISCO_BODEGA, "Bodega", PCI_SUBDEVICE_ID_CISCO_BRADBURY, + "VIC 1457"}, + {PCI_DEVICE_ID_CISCO_BODEGA, "Bodega", + PCI_SUBDEVICE_ID_CISCO_BRENTWOOD, "VIC 1455"}, + {PCI_DEVICE_ID_CISCO_BODEGA, "Bodega", + PCI_SUBDEVICE_ID_CISCO_BURLINGAME, "VIC 1487"}, + {PCI_DEVICE_ID_CISCO_BODEGA, "Bodega", PCI_SUBDEVICE_ID_CISCO_BAYSIDE, + "VIC 1485"}, + {PCI_DEVICE_ID_CISCO_BODEGA, "Bodega", + PCI_SUBDEVICE_ID_CISCO_BAKERSFIELD, "VIC 1440"}, + {PCI_DEVICE_ID_CISCO_BODEGA, "Bodega", + PCI_SUBDEVICE_ID_CISCO_BOONVILLE, "VIC 1480"}, + {PCI_DEVICE_ID_CISCO_BODEGA, "Bodega", PCI_SUBDEVICE_ID_CISCO_BENICIA, + "VIC 1495"}, + {PCI_DEVICE_ID_CISCO_BODEGA, "Bodega", PCI_SUBDEVICE_ID_CISCO_BEAUMONT, + "VIC 1497"}, + {PCI_DEVICE_ID_CISCO_BODEGA, "Bodega", PCI_SUBDEVICE_ID_CISCO_BRISBANE, + "VIC 1467"}, + {PCI_DEVICE_ID_CISCO_BODEGA, "Bodega", PCI_SUBDEVICE_ID_CISCO_BENTON, + "VIC 1477"}, + {PCI_DEVICE_ID_CISCO_BODEGA, "Bodega", + PCI_SUBDEVICE_ID_CISCO_TWIN_RIVER, "VIC 14425"}, + {PCI_DEVICE_ID_CISCO_BODEGA, "Bodega", + PCI_SUBDEVICE_ID_CISCO_TWIN_PEAK, "VIC 14825"}, + + {PCI_DEVICE_ID_CISCO_BEVERLY, "Beverly", PCI_SUBDEVICE_ID_CISCO_BERN, + "VIC 15420"}, + {PCI_DEVICE_ID_CISCO_BEVERLY, "Beverly", + PCI_SUBDEVICE_ID_CISCO_STOCKHOLM, "VIC 15428"}, + {PCI_DEVICE_ID_CISCO_BEVERLY, "Beverly", PCI_SUBDEVICE_ID_CISCO_KRAKOW, + "VIC 15411"}, + {PCI_DEVICE_ID_CISCO_BEVERLY, "Beverly", + PCI_SUBDEVICE_ID_CISCO_LUCERNE, "VIC 15231"}, + {PCI_DEVICE_ID_CISCO_BEVERLY, "Beverly", PCI_SUBDEVICE_ID_CISCO_TURKU, + "VIC 15238"}, + {PCI_DEVICE_ID_CISCO_BEVERLY, "Beverly", PCI_SUBDEVICE_ID_CISCO_GENEVA, + "VIC 15422"}, + {PCI_DEVICE_ID_CISCO_BEVERLY, "Beverly", + PCI_SUBDEVICE_ID_CISCO_HELSINKI, "VIC 15235"}, + {PCI_DEVICE_ID_CISCO_BEVERLY, "Beverly", + PCI_SUBDEVICE_ID_CISCO_GOTHENBURG, "VIC 15425"}, + {PCI_DEVICE_ID_CISCO_BEVERLY, "Beverly", + PCI_SUBDEVICE_ID_CISCO_TURKU_PLUS, "VIC 15237"}, + {PCI_DEVICE_ID_CISCO_BEVERLY, "Beverly", PCI_SUBDEVICE_ID_CISCO_ZURICH, + "VIC 15230"}, + {PCI_DEVICE_ID_CISCO_BEVERLY, "Beverly", PCI_SUBDEVICE_ID_CISCO_RIGA, + "VIC 15427"}, + + {0,} +}; + +int fnic_get_desc_by_devid(struct pci_dev *pdev, char **desc, + char **subsys_desc) +{ + unsigned short device = PCI_DEVICE_ID_CISCO_VIC_FC; + int max = ARRAY_SIZE(fnic_pcie_device_table); + struct fnic_pcie_device *t = fnic_pcie_device_table; + int index = 0; + + if (pdev->device != device) + return 1; + + while (t->device != 0) { + if (memcmp + ((char *) &pdev->subsystem_device, + (char *) &t->subsystem_device, sizeof(short)) == 0) + break; + t++; + index++; + } + + if (index >= max - 1) { + *desc = NULL; + *subsys_desc = NULL; + return 1; + } + + *desc = fnic_pcie_device_table[index].desc; + *subsys_desc = fnic_pcie_device_table[index].subsys_desc; + return 0; +} diff --git a/drivers/scsi/fnic/fnic_res.c b/drivers/scsi/fnic/fnic_res.c new file mode 100644 index 000000000..d75d7046c --- /dev/null +++ b/drivers/scsi/fnic/fnic_res.c @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/pci.h> +#include "wq_enet_desc.h" +#include "rq_enet_desc.h" +#include "cq_enet_desc.h" +#include "vnic_resource.h" +#include "vnic_dev.h" +#include "vnic_wq.h" +#include "vnic_rq.h" +#include "vnic_cq.h" +#include "vnic_intr.h" +#include "vnic_stats.h" +#include "vnic_nic.h" +#include "fnic.h" + +int fnic_get_vnic_config(struct fnic *fnic) +{ + struct vnic_fc_config *c = &fnic->config; + u32 role; + int err; + +#define GET_CONFIG(m) \ + do { \ + err = vnic_dev_spec(fnic->vdev, \ + offsetof(struct vnic_fc_config, m), \ + sizeof(c->m), &c->m); \ + if (err) { \ + dev_err(&fnic->pdev->dev, "Error getting %s, %d\n", #m, err); \ + return err; \ + } \ + } while (0); + + GET_CONFIG(node_wwn); + GET_CONFIG(port_wwn); + GET_CONFIG(wq_enet_desc_count); + GET_CONFIG(wq_copy_desc_count); + GET_CONFIG(rq_desc_count); + GET_CONFIG(maxdatafieldsize); + GET_CONFIG(ed_tov); + GET_CONFIG(ra_tov); + GET_CONFIG(intr_timer); + GET_CONFIG(intr_timer_type); + GET_CONFIG(flags); + GET_CONFIG(flogi_retries); + GET_CONFIG(flogi_timeout); + GET_CONFIG(plogi_retries); + GET_CONFIG(plogi_timeout); + GET_CONFIG(io_throttle_count); + GET_CONFIG(link_down_timeout); + GET_CONFIG(port_down_timeout); + GET_CONFIG(port_down_io_retries); + GET_CONFIG(luns_per_tgt); + GET_CONFIG(intr_mode); + GET_CONFIG(lun_queue_depth); + GET_CONFIG(io_timeout_retry); + GET_CONFIG(wq_copy_count); + + role = c->flags & FNIC_ROLE_CONFIG_MASK; + switch (role) { + case 0: + dev_info(&fnic->pdev->dev, + "vNIC role not defined (def role: FC Init)\n"); + c->flags |= VFCF_FC_INITIATOR; + break; + case VFCF_FC_INITIATOR: + case VFCF_FC_NVME_INITIATOR: + break; + case VFCF_FC_TARGET: + dev_info(&fnic->pdev->dev, + "vNIC role is FC Target (unsupported)\n"); + break; + case VFCF_FC_NVME_TARGET: + dev_info(&fnic->pdev->dev, + "vNIC role is FC-NVMe Target (unsupported)\n"); + break; + default: + dev_info(&fnic->pdev->dev, + "vNIC role not supported (0x%x), defaulting to FC Init\n", + role); + c->flags &= ~FNIC_ROLE_CONFIG_MASK; + c->flags |= VFCF_FC_INITIATOR; + break; + } + + c->wq_enet_desc_count = + min_t(u32, VNIC_FNIC_WQ_DESCS_MAX, + max_t(u32, VNIC_FNIC_WQ_DESCS_MIN, + c->wq_enet_desc_count)); + c->wq_enet_desc_count = ALIGN(c->wq_enet_desc_count, 16); + + c->wq_copy_desc_count = + min_t(u32, VNIC_FNIC_WQ_COPY_DESCS_MAX, + max_t(u32, VNIC_FNIC_WQ_COPY_DESCS_MIN, + c->wq_copy_desc_count)); + c->wq_copy_desc_count = ALIGN(c->wq_copy_desc_count, 16); + + c->rq_desc_count = + min_t(u32, VNIC_FNIC_RQ_DESCS_MAX, + max_t(u32, VNIC_FNIC_RQ_DESCS_MIN, + c->rq_desc_count)); + c->rq_desc_count = ALIGN(c->rq_desc_count, 16); + + c->maxdatafieldsize = + min_t(u16, VNIC_FNIC_MAXDATAFIELDSIZE_MAX, + max_t(u16, VNIC_FNIC_MAXDATAFIELDSIZE_MIN, + c->maxdatafieldsize)); + c->ed_tov = + min_t(u32, VNIC_FNIC_EDTOV_MAX, + max_t(u32, VNIC_FNIC_EDTOV_MIN, + c->ed_tov)); + + c->ra_tov = + min_t(u32, VNIC_FNIC_RATOV_MAX, + max_t(u32, VNIC_FNIC_RATOV_MIN, + c->ra_tov)); + + c->flogi_retries = + min_t(u32, VNIC_FNIC_FLOGI_RETRIES_MAX, c->flogi_retries); + + c->flogi_timeout = + min_t(u32, VNIC_FNIC_FLOGI_TIMEOUT_MAX, + max_t(u32, VNIC_FNIC_FLOGI_TIMEOUT_MIN, + c->flogi_timeout)); + + c->plogi_retries = + min_t(u32, VNIC_FNIC_PLOGI_RETRIES_MAX, c->plogi_retries); + + c->plogi_timeout = + min_t(u32, VNIC_FNIC_PLOGI_TIMEOUT_MAX, + max_t(u32, VNIC_FNIC_PLOGI_TIMEOUT_MIN, + c->plogi_timeout)); + + c->io_throttle_count = + min_t(u32, VNIC_FNIC_IO_THROTTLE_COUNT_MAX, + max_t(u32, VNIC_FNIC_IO_THROTTLE_COUNT_MIN, + c->io_throttle_count)); + + c->link_down_timeout = + min_t(u32, VNIC_FNIC_LINK_DOWN_TIMEOUT_MAX, + c->link_down_timeout); + + c->port_down_timeout = + min_t(u32, VNIC_FNIC_PORT_DOWN_TIMEOUT_MAX, + c->port_down_timeout); + + c->port_down_io_retries = + min_t(u32, VNIC_FNIC_PORT_DOWN_IO_RETRIES_MAX, + c->port_down_io_retries); + + c->luns_per_tgt = + min_t(u32, VNIC_FNIC_LUNS_PER_TARGET_MAX, + max_t(u32, VNIC_FNIC_LUNS_PER_TARGET_MIN, + c->luns_per_tgt)); + + c->intr_timer = min_t(u16, VNIC_INTR_TIMER_MAX, c->intr_timer); + + /* for older firmware, GET_CONFIG will not return anything */ + if (c->wq_copy_count == 0) + c->wq_copy_count = 1; + + c->wq_copy_count = min_t(u16, FNIC_WQ_COPY_MAX, c->wq_copy_count); + + dev_info(&fnic->pdev->dev, "fNIC MAC addr %p wq/wq_copy/rq %d/%d/%d\n", + fnic->data_src_addr, + c->wq_enet_desc_count, c->wq_copy_desc_count, + c->rq_desc_count); + dev_info(&fnic->pdev->dev, "fNIC node wwn 0x%llx port wwn 0x%llx\n", + c->node_wwn, c->port_wwn); + dev_info(&fnic->pdev->dev, "fNIC ed_tov %d ra_tov %d\n", + c->ed_tov, c->ra_tov); + dev_info(&fnic->pdev->dev, "fNIC mtu %d intr timer %d\n", + c->maxdatafieldsize, c->intr_timer); + dev_info(&fnic->pdev->dev, "fNIC flags 0x%x luns per tgt %d\n", + c->flags, c->luns_per_tgt); + dev_info(&fnic->pdev->dev, "fNIC flogi_retries %d flogi timeout %d\n", + c->flogi_retries, c->flogi_timeout); + dev_info(&fnic->pdev->dev, "fNIC plogi retries %d plogi timeout %d\n", + c->plogi_retries, c->plogi_timeout); + dev_info(&fnic->pdev->dev, "fNIC io throttle count %d link dn timeout %d\n", + c->io_throttle_count, c->link_down_timeout); + dev_info(&fnic->pdev->dev, "fNIC port dn io retries %d port dn timeout %d\n", + c->port_down_io_retries, c->port_down_timeout); + dev_info(&fnic->pdev->dev, "fNIC wq_copy_count: %d\n", c->wq_copy_count); + dev_info(&fnic->pdev->dev, "fNIC intr mode: %d\n", c->intr_mode); + dev_info(&fnic->pdev->dev, "fNIC role flags: 0x%x\n", + (c->flags & FNIC_ROLE_CONFIG_MASK)); + + return 0; +} + +int fnic_set_nic_config(struct fnic *fnic, u8 rss_default_cpu, + u8 rss_hash_type, + u8 rss_hash_bits, u8 rss_base_cpu, u8 rss_enable, + u8 tso_ipid_split_en, u8 ig_vlan_strip_en) +{ + u64 a0, a1; + u32 nic_cfg; + int wait = 1000; + + vnic_set_nic_cfg(&nic_cfg, rss_default_cpu, + rss_hash_type, rss_hash_bits, rss_base_cpu, + rss_enable, tso_ipid_split_en, ig_vlan_strip_en); + + a0 = nic_cfg; + a1 = 0; + + return vnic_dev_cmd(fnic->vdev, CMD_NIC_CFG, &a0, &a1, wait); +} + +void fnic_get_res_counts(struct fnic *fnic) +{ + fnic->wq_count = vnic_dev_get_res_count(fnic->vdev, RES_TYPE_WQ); + fnic->raw_wq_count = 1; + fnic->wq_copy_count = fnic->config.wq_copy_count; + fnic->rq_count = vnic_dev_get_res_count(fnic->vdev, RES_TYPE_RQ); + fnic->cq_count = vnic_dev_get_res_count(fnic->vdev, RES_TYPE_CQ); + fnic->intr_count = vnic_dev_get_res_count(fnic->vdev, + RES_TYPE_INTR_CTRL); + + dev_info(&fnic->pdev->dev, "vNIC fw resources wq_count: %d\n", fnic->wq_count); + dev_info(&fnic->pdev->dev, "vNIC fw resources raw_wq_count: %d\n", fnic->raw_wq_count); + dev_info(&fnic->pdev->dev, "vNIC fw resources wq_copy_count: %d\n", fnic->wq_copy_count); + dev_info(&fnic->pdev->dev, "vNIC fw resources rq_count: %d\n", fnic->rq_count); + dev_info(&fnic->pdev->dev, "vNIC fw resources cq_count: %d\n", fnic->cq_count); + dev_info(&fnic->pdev->dev, "vNIC fw resources intr_count: %d\n", fnic->intr_count); +} + +void fnic_free_vnic_resources(struct fnic *fnic) +{ + unsigned int i; + + for (i = 0; i < fnic->raw_wq_count; i++) + vnic_wq_free(&fnic->wq[i]); + + for (i = 0; i < fnic->wq_copy_count; i++) + vnic_wq_copy_free(&fnic->hw_copy_wq[i]); + + for (i = 0; i < fnic->rq_count; i++) + vnic_rq_free(&fnic->rq[i]); + + for (i = 0; i < fnic->cq_count; i++) + vnic_cq_free(&fnic->cq[i]); + + for (i = 0; i < fnic->intr_count; i++) + vnic_intr_free(&fnic->intr[i]); +} + +int fnic_alloc_vnic_resources(struct fnic *fnic) +{ + enum vnic_dev_intr_mode intr_mode; + unsigned int mask_on_assertion; + unsigned int interrupt_offset; + unsigned int error_interrupt_enable; + unsigned int error_interrupt_offset; + unsigned int i, cq_index; + unsigned int wq_copy_cq_desc_count; + int err; + + intr_mode = vnic_dev_get_intr_mode(fnic->vdev); + + dev_info(&fnic->pdev->dev, "vNIC interrupt mode: %s\n", + intr_mode == VNIC_DEV_INTR_MODE_INTX ? "legacy PCI INTx" : + intr_mode == VNIC_DEV_INTR_MODE_MSI ? "MSI" : + intr_mode == VNIC_DEV_INTR_MODE_MSIX ? + "MSI-X" : "unknown"); + + dev_info(&fnic->pdev->dev, "res avail: wq %d cp_wq %d raw_wq %d rq %d", + fnic->wq_count, fnic->wq_copy_count, + fnic->raw_wq_count, fnic->rq_count); + + dev_info(&fnic->pdev->dev, "res avail: cq %d intr %d cpy-wq desc count %d\n", + fnic->cq_count, fnic->intr_count, + fnic->config.wq_copy_desc_count); + + /* Allocate Raw WQ used for FCS frames */ + for (i = 0; i < fnic->raw_wq_count; i++) { + err = vnic_wq_alloc(fnic->vdev, &fnic->wq[i], i, + fnic->config.wq_enet_desc_count, + sizeof(struct wq_enet_desc)); + if (err) + goto err_out_cleanup; + } + + /* Allocate Copy WQs used for SCSI IOs */ + for (i = 0; i < fnic->wq_copy_count; i++) { + err = vnic_wq_copy_alloc(fnic->vdev, &fnic->hw_copy_wq[i], + (fnic->raw_wq_count + i), + fnic->config.wq_copy_desc_count, + sizeof(struct fcpio_host_req)); + if (err) + goto err_out_cleanup; + } + + /* RQ for receiving FCS frames */ + for (i = 0; i < fnic->rq_count; i++) { + err = vnic_rq_alloc(fnic->vdev, &fnic->rq[i], i, + fnic->config.rq_desc_count, + sizeof(struct rq_enet_desc)); + if (err) + goto err_out_cleanup; + } + + /* CQ for each RQ */ + for (i = 0; i < fnic->rq_count; i++) { + cq_index = i; + err = vnic_cq_alloc(fnic->vdev, + &fnic->cq[cq_index], cq_index, + fnic->config.rq_desc_count, + sizeof(struct cq_enet_rq_desc)); + if (err) + goto err_out_cleanup; + } + + /* CQ for each WQ */ + for (i = 0; i < fnic->raw_wq_count; i++) { + cq_index = fnic->rq_count + i; + err = vnic_cq_alloc(fnic->vdev, &fnic->cq[cq_index], cq_index, + fnic->config.wq_enet_desc_count, + sizeof(struct cq_enet_wq_desc)); + if (err) + goto err_out_cleanup; + } + + /* CQ for each COPY WQ */ + wq_copy_cq_desc_count = (fnic->config.wq_copy_desc_count * 3); + for (i = 0; i < fnic->wq_copy_count; i++) { + cq_index = fnic->raw_wq_count + fnic->rq_count + i; + err = vnic_cq_alloc(fnic->vdev, &fnic->cq[cq_index], + cq_index, + wq_copy_cq_desc_count, + sizeof(struct fcpio_fw_req)); + if (err) + goto err_out_cleanup; + } + + for (i = 0; i < fnic->intr_count; i++) { + err = vnic_intr_alloc(fnic->vdev, &fnic->intr[i], i); + if (err) + goto err_out_cleanup; + } + + fnic->legacy_pba = vnic_dev_get_res(fnic->vdev, + RES_TYPE_INTR_PBA_LEGACY, 0); + + if (!fnic->legacy_pba && intr_mode == VNIC_DEV_INTR_MODE_INTX) { + dev_err(&fnic->pdev->dev, "Failed to hook legacy pba resource\n"); + err = -ENODEV; + goto err_out_cleanup; + } + + /* + * Init RQ/WQ resources. + * + * RQ[0 to n-1] point to CQ[0 to n-1] + * WQ[0 to m-1] point to CQ[n to n+m-1] + * WQ_COPY[0 to k-1] points to CQ[n+m to n+m+k-1] + * + * Note for copy wq we always initialize with cq_index = 0 + * + * Error interrupt is not enabled for MSI. + */ + + switch (intr_mode) { + case VNIC_DEV_INTR_MODE_INTX: + case VNIC_DEV_INTR_MODE_MSIX: + error_interrupt_enable = 1; + error_interrupt_offset = fnic->err_intr_offset; + break; + default: + error_interrupt_enable = 0; + error_interrupt_offset = 0; + break; + } + + for (i = 0; i < fnic->rq_count; i++) { + cq_index = i; + vnic_rq_init(&fnic->rq[i], + cq_index, + error_interrupt_enable, + error_interrupt_offset); + } + + for (i = 0; i < fnic->raw_wq_count; i++) { + cq_index = i + fnic->rq_count; + vnic_wq_init(&fnic->wq[i], + cq_index, + error_interrupt_enable, + error_interrupt_offset); + } + + for (i = 0; i < fnic->wq_copy_count; i++) { + vnic_wq_copy_init(&fnic->hw_copy_wq[i], + 0 /* cq_index 0 - always */, + error_interrupt_enable, + error_interrupt_offset); + } + + for (i = 0; i < fnic->cq_count; i++) { + + switch (intr_mode) { + case VNIC_DEV_INTR_MODE_MSIX: + interrupt_offset = i; + break; + default: + interrupt_offset = 0; + break; + } + + vnic_cq_init(&fnic->cq[i], + 0 /* flow_control_enable */, + 1 /* color_enable */, + 0 /* cq_head */, + 0 /* cq_tail */, + 1 /* cq_tail_color */, + 1 /* interrupt_enable */, + 1 /* cq_entry_enable */, + 0 /* cq_message_enable */, + interrupt_offset, + 0 /* cq_message_addr */); + } + + /* + * Init INTR resources + * + * mask_on_assertion is not used for INTx due to the level- + * triggered nature of INTx + */ + + switch (intr_mode) { + case VNIC_DEV_INTR_MODE_MSI: + case VNIC_DEV_INTR_MODE_MSIX: + mask_on_assertion = 1; + break; + default: + mask_on_assertion = 0; + break; + } + + for (i = 0; i < fnic->intr_count; i++) { + vnic_intr_init(&fnic->intr[i], + fnic->config.intr_timer, + fnic->config.intr_timer_type, + mask_on_assertion); + } + + /* init the stats memory by making the first call here */ + err = vnic_dev_stats_dump(fnic->vdev, &fnic->stats); + if (err) { + dev_err(&fnic->pdev->dev, "vnic_dev_stats_dump failed - x%x\n", err); + goto err_out_cleanup; + } + + /* Clear LIF stats */ + vnic_dev_stats_clear(fnic->vdev); + + return 0; + +err_out_cleanup: + fnic_free_vnic_resources(fnic); + + return err; +} diff --git a/drivers/scsi/fnic/fnic_res.h b/drivers/scsi/fnic/fnic_res.h new file mode 100644 index 000000000..12a2a356d --- /dev/null +++ b/drivers/scsi/fnic/fnic_res.h @@ -0,0 +1,267 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _FNIC_RES_H_ +#define _FNIC_RES_H_ + +#include "wq_enet_desc.h" +#include "rq_enet_desc.h" +#include "vnic_wq.h" +#include "vnic_rq.h" +#include "fnic_io.h" +#include "fcpio.h" +#include "vnic_wq_copy.h" +#include "vnic_cq_copy.h" + +static inline void fnic_queue_wq_desc(struct vnic_wq *wq, + void *os_buf, dma_addr_t dma_addr, + unsigned int len, unsigned int fc_eof, + int vlan_tag_insert, + unsigned int vlan_tag, + int cq_entry, int sop, int eop) +{ + struct wq_enet_desc *desc = vnic_wq_next_desc(wq); + + wq_enet_desc_enc(desc, + (u64)dma_addr | VNIC_PADDR_TARGET, + (u16)len, + 0, /* mss_or_csum_offset */ + (u16)fc_eof, + 0, /* offload_mode */ + (u8)eop, (u8)cq_entry, + 1, /* fcoe_encap */ + (u8)vlan_tag_insert, + (u16)vlan_tag, + 0 /* loopback */); + + vnic_wq_post(wq, os_buf, dma_addr, len, sop, eop); +} + +static inline void fnic_queue_wq_eth_desc(struct vnic_wq *wq, + void *os_buf, dma_addr_t dma_addr, + unsigned int len, + int vlan_tag_insert, + unsigned int vlan_tag, + int cq_entry) +{ + struct wq_enet_desc *desc = vnic_wq_next_desc(wq); + + wq_enet_desc_enc(desc, + (u64)dma_addr | VNIC_PADDR_TARGET, + (u16)len, + 0, /* mss_or_csum_offset */ + 0, /* fc_eof */ + 0, /* offload_mode */ + 1, /* eop */ + (u8)cq_entry, + 0, /* fcoe_encap */ + (u8)vlan_tag_insert, + (u16)vlan_tag, + 0 /* loopback */); + + vnic_wq_post(wq, os_buf, dma_addr, len, 1, 1); +} + +static inline void fnic_queue_wq_copy_desc_icmnd_16(struct vnic_wq_copy *wq, + u32 req_id, + u32 lunmap_id, u8 spl_flags, + u32 sgl_cnt, u32 sense_len, + u64 sgl_addr, u64 sns_addr, + u8 crn, u8 pri_ta, + u8 flags, u8 *scsi_cdb, + u8 cdb_len, + u32 data_len, u8 *lun, + u32 d_id, u16 mss, + u32 ratov, u32 edtov) +{ + struct fcpio_host_req *desc = vnic_wq_copy_next_desc(wq); + + desc->hdr.type = FCPIO_ICMND_16; /* enum fcpio_type */ + desc->hdr.status = 0; /* header status entry */ + desc->hdr._resvd = 0; /* reserved */ + desc->hdr.tag.u.req_id = req_id; /* id for this request */ + + desc->u.icmnd_16.lunmap_id = lunmap_id; /* index into lunmap table */ + desc->u.icmnd_16.special_req_flags = spl_flags; /* exch req flags */ + desc->u.icmnd_16._resvd0[0] = 0; /* reserved */ + desc->u.icmnd_16._resvd0[1] = 0; /* reserved */ + desc->u.icmnd_16._resvd0[2] = 0; /* reserved */ + desc->u.icmnd_16.sgl_cnt = sgl_cnt; /* scatter-gather list count */ + desc->u.icmnd_16.sense_len = sense_len; /* sense buffer length */ + desc->u.icmnd_16.sgl_addr = sgl_addr; /* scatter-gather list addr */ + desc->u.icmnd_16.sense_addr = sns_addr; /* sense buffer address */ + desc->u.icmnd_16.crn = crn; /* SCSI Command Reference No.*/ + desc->u.icmnd_16.pri_ta = pri_ta; /* SCSI Pri & Task attribute */ + desc->u.icmnd_16._resvd1 = 0; /* reserved: should be 0 */ + desc->u.icmnd_16.flags = flags; /* command flags */ + memset(desc->u.icmnd_16.scsi_cdb, 0, CDB_16); + memcpy(desc->u.icmnd_16.scsi_cdb, scsi_cdb, cdb_len); /* SCSI CDB */ + desc->u.icmnd_16.data_len = data_len; /* length of data expected */ + memcpy(desc->u.icmnd_16.lun, lun, LUN_ADDRESS); /* LUN address */ + desc->u.icmnd_16._resvd2 = 0; /* reserved */ + hton24(desc->u.icmnd_16.d_id, d_id); /* FC vNIC only: Target D_ID */ + desc->u.icmnd_16.mss = mss; /* FC vNIC only: max burst */ + desc->u.icmnd_16.r_a_tov = ratov; /*FC vNIC only: Res. Alloc Timeout */ + desc->u.icmnd_16.e_d_tov = edtov; /*FC vNIC only: Err Detect Timeout */ + + vnic_wq_copy_post(wq); +} + +static inline void fnic_queue_wq_copy_desc_itmf(struct vnic_wq_copy *wq, + u32 req_id, u32 lunmap_id, + u32 tm_req, u32 tm_id, u8 *lun, + u32 d_id, u32 r_a_tov, + u32 e_d_tov) +{ + struct fcpio_host_req *desc = vnic_wq_copy_next_desc(wq); + + desc->hdr.type = FCPIO_ITMF; /* enum fcpio_type */ + desc->hdr.status = 0; /* header status entry */ + desc->hdr._resvd = 0; /* reserved */ + desc->hdr.tag.u.req_id = req_id; /* id for this request */ + + desc->u.itmf.lunmap_id = lunmap_id; /* index into lunmap table */ + desc->u.itmf.tm_req = tm_req; /* SCSI Task Management request */ + desc->u.itmf.t_tag = tm_id; /* tag of fcpio to be aborted */ + desc->u.itmf._resvd = 0; + if (lun) + memcpy(desc->u.itmf.lun, lun, LUN_ADDRESS); /* LUN address */ + desc->u.itmf._resvd1 = 0; + hton24(desc->u.itmf.d_id, d_id); /* FC vNIC only: Target D_ID */ + desc->u.itmf.r_a_tov = r_a_tov; /* FC vNIC only: R_A_TOV in msec */ + desc->u.itmf.e_d_tov = e_d_tov; /* FC vNIC only: E_D_TOV in msec */ + + vnic_wq_copy_post(wq); +} + +static inline void fnic_queue_wq_copy_desc_flogi_reg(struct vnic_wq_copy *wq, + u32 req_id, u8 format, + u32 s_id, u8 *gw_mac) +{ + struct fcpio_host_req *desc = vnic_wq_copy_next_desc(wq); + + desc->hdr.type = FCPIO_FLOGI_REG; /* enum fcpio_type */ + desc->hdr.status = 0; /* header status entry */ + desc->hdr._resvd = 0; /* reserved */ + desc->hdr.tag.u.req_id = req_id; /* id for this request */ + + desc->u.flogi_reg.format = format; + desc->u.flogi_reg._resvd = 0; + hton24(desc->u.flogi_reg.s_id, s_id); + memcpy(desc->u.flogi_reg.gateway_mac, gw_mac, ETH_ALEN); + + vnic_wq_copy_post(wq); +} + +static inline void fnic_queue_wq_copy_desc_fip_reg(struct vnic_wq_copy *wq, + u32 req_id, u32 s_id, + u8 *fcf_mac, u8 *ha_mac, + u32 r_a_tov, u32 e_d_tov) +{ + struct fcpio_host_req *desc = vnic_wq_copy_next_desc(wq); + + desc->hdr.type = FCPIO_FLOGI_FIP_REG; /* enum fcpio_type */ + desc->hdr.status = 0; /* header status entry */ + desc->hdr._resvd = 0; /* reserved */ + desc->hdr.tag.u.req_id = req_id; /* id for this request */ + + desc->u.flogi_fip_reg._resvd0 = 0; + hton24(desc->u.flogi_fip_reg.s_id, s_id); + memcpy(desc->u.flogi_fip_reg.fcf_mac, fcf_mac, ETH_ALEN); + desc->u.flogi_fip_reg._resvd1 = 0; + desc->u.flogi_fip_reg.r_a_tov = r_a_tov; + desc->u.flogi_fip_reg.e_d_tov = e_d_tov; + memcpy(desc->u.flogi_fip_reg.ha_mac, ha_mac, ETH_ALEN); + desc->u.flogi_fip_reg._resvd2 = 0; + + vnic_wq_copy_post(wq); +} + +static inline void fnic_queue_wq_copy_desc_fw_reset(struct vnic_wq_copy *wq, + u32 req_id) +{ + struct fcpio_host_req *desc = vnic_wq_copy_next_desc(wq); + + desc->hdr.type = FCPIO_RESET; /* enum fcpio_type */ + desc->hdr.status = 0; /* header status entry */ + desc->hdr._resvd = 0; /* reserved */ + desc->hdr.tag.u.req_id = req_id; /* id for this request */ + + vnic_wq_copy_post(wq); +} + +static inline void fnic_queue_wq_copy_desc_lunmap(struct vnic_wq_copy *wq, + u32 req_id, u64 lunmap_addr, + u32 lunmap_len) +{ + struct fcpio_host_req *desc = vnic_wq_copy_next_desc(wq); + + desc->hdr.type = FCPIO_LUNMAP_REQ; /* enum fcpio_type */ + desc->hdr.status = 0; /* header status entry */ + desc->hdr._resvd = 0; /* reserved */ + desc->hdr.tag.u.req_id = req_id; /* id for this request */ + + desc->u.lunmap_req.addr = lunmap_addr; /* address of the buffer */ + desc->u.lunmap_req.len = lunmap_len; /* len of the buffer */ + + vnic_wq_copy_post(wq); +} + +static inline void fnic_queue_rq_desc(struct vnic_rq *rq, + void *os_buf, dma_addr_t dma_addr, + u16 len) +{ + struct rq_enet_desc *desc = vnic_rq_next_desc(rq); + + rq_enet_desc_enc(desc, + (u64)dma_addr | VNIC_PADDR_TARGET, + RQ_ENET_TYPE_ONLY_SOP, + (u16)len); + + vnic_rq_post(rq, os_buf, 0, dma_addr, len); +} + +static inline void fnic_queue_wq_copy_desc_nvme_io(struct vnic_wq_copy *wq, + u32 req_id, + u8 spl_flags, + u32 sgl_cnt, + u64 sgl_addr, + u8 flags, u8 *nvme_cmd_iu, + u16 cmd_len, + u32 data_len, + u32 d_id, u32 mss, + u32 ratov, u32 edtov) +{ + struct fcpio_host_req *desc = vnic_wq_copy_next_desc(wq); + + desc->hdr.type = FCPIO_NVME_CMD; /* enum fcpio_type */ + desc->hdr.status = 0; /* header status entry */ + desc->hdr._resvd = 0; /* reserved */ + desc->hdr.tag.u.req_id = req_id; /* id for this request */ + desc->u.nvcmnd.sgl_cnt = sgl_cnt; /* scatter-gather list count */ + desc->u.nvcmnd.sgl_addr = sgl_addr; /* scatter-gather list addr */ + desc->u.nvcmnd._resvd1 = 0; /* reserved: should be 0 */ + desc->u.nvcmnd.flags = flags; /* command flags */ + memset(desc->u.nvcmnd.nvme_cmnd, 0, NVME_CMD_SZ); + memcpy(desc->u.nvcmnd.nvme_cmnd, nvme_cmd_iu, cmd_len); /* SCSI CDB */ + desc->u.nvcmnd.cmd_len = cmd_len; + desc->u.nvcmnd.data_len = data_len; /* length of data expected */ + hton24(desc->u.nvcmnd.d_id, d_id); /* FC vNIC only: Target D_ID */ + + vnic_wq_copy_post(wq); +} + +struct fnic; + +int fnic_get_vnic_config(struct fnic *); +int fnic_alloc_vnic_resources(struct fnic *); +void fnic_free_vnic_resources(struct fnic *); +void fnic_get_res_counts(struct fnic *); +int fnic_set_nic_config(struct fnic *fnic, u8 rss_default_cpu, + u8 rss_hash_type, u8 rss_hash_bits, u8 rss_base_cpu, + u8 rss_enable, u8 tso_ipid_split_en, + u8 ig_vlan_strip_en); + +#endif /* _FNIC_RES_H_ */ diff --git a/drivers/scsi/fnic/fnic_scsi.c b/drivers/scsi/fnic/fnic_scsi.c new file mode 100644 index 000000000..9607684bc --- /dev/null +++ b/drivers/scsi/fnic/fnic_scsi.c @@ -0,0 +1,3058 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#include <linux/mempool.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/workqueue.h> +#include <linux/pci.h> +#include <linux/scatterlist.h> +#include <linux/skbuff.h> +#include <linux/spinlock.h> +#include <linux/etherdevice.h> +#include <linux/if_ether.h> +#include <linux/if_vlan.h> +#include <linux/delay.h> +#include <linux/gfp.h> +#include <scsi/scsi.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_tcq.h> +#include <scsi/fc/fc_els.h> +#include <scsi/fc/fc_fcoe.h> +#include <scsi/fc_frame.h> +#include <scsi/scsi_transport_fc.h> +#include "fnic_io.h" +#include "fnic.h" + +static void fnic_cleanup_io(struct fnic *fnic, int exclude_id); + +const char *fnic_state_str[] = { + [FNIC_IN_FC_MODE] = "FNIC_IN_FC_MODE", + [FNIC_IN_FC_TRANS_ETH_MODE] = "FNIC_IN_FC_TRANS_ETH_MODE", + [FNIC_IN_ETH_MODE] = "FNIC_IN_ETH_MODE", + [FNIC_IN_ETH_TRANS_FC_MODE] = "FNIC_IN_ETH_TRANS_FC_MODE", +}; + +static const char *fnic_ioreq_state_str[] = { + [FNIC_IOREQ_NOT_INITED] = "FNIC_IOREQ_NOT_INITED", + [FNIC_IOREQ_CMD_PENDING] = "FNIC_IOREQ_CMD_PENDING", + [FNIC_IOREQ_ABTS_PENDING] = "FNIC_IOREQ_ABTS_PENDING", + [FNIC_IOREQ_ABTS_COMPLETE] = "FNIC_IOREQ_ABTS_COMPLETE", + [FNIC_IOREQ_CMD_COMPLETE] = "FNIC_IOREQ_CMD_COMPLETE", +}; + +static const char *fcpio_status_str[] = { + [FCPIO_SUCCESS] = "FCPIO_SUCCESS", /*0x0*/ + [FCPIO_INVALID_HEADER] = "FCPIO_INVALID_HEADER", + [FCPIO_OUT_OF_RESOURCE] = "FCPIO_OUT_OF_RESOURCE", + [FCPIO_INVALID_PARAM] = "FCPIO_INVALID_PARAM]", + [FCPIO_REQ_NOT_SUPPORTED] = "FCPIO_REQ_NOT_SUPPORTED", + [FCPIO_IO_NOT_FOUND] = "FCPIO_IO_NOT_FOUND", + [FCPIO_ABORTED] = "FCPIO_ABORTED", /*0x41*/ + [FCPIO_TIMEOUT] = "FCPIO_TIMEOUT", + [FCPIO_SGL_INVALID] = "FCPIO_SGL_INVALID", + [FCPIO_MSS_INVALID] = "FCPIO_MSS_INVALID", + [FCPIO_DATA_CNT_MISMATCH] = "FCPIO_DATA_CNT_MISMATCH", + [FCPIO_FW_ERR] = "FCPIO_FW_ERR", + [FCPIO_ITMF_REJECTED] = "FCPIO_ITMF_REJECTED", + [FCPIO_ITMF_FAILED] = "FCPIO_ITMF_FAILED", + [FCPIO_ITMF_INCORRECT_LUN] = "FCPIO_ITMF_INCORRECT_LUN", + [FCPIO_CMND_REJECTED] = "FCPIO_CMND_REJECTED", + [FCPIO_NO_PATH_AVAIL] = "FCPIO_NO_PATH_AVAIL", + [FCPIO_PATH_FAILED] = "FCPIO_PATH_FAILED", + [FCPIO_LUNMAP_CHNG_PEND] = "FCPIO_LUNHMAP_CHNG_PEND", +}; + +enum terminate_io_return { + TERM_SUCCESS = 0, + TERM_NO_SC = 1, + TERM_IO_REQ_NOT_FOUND, + TERM_ANOTHER_PORT, + TERM_GSTATE, + TERM_IO_BLOCKED, + TERM_OUT_OF_WQ_DESC, + TERM_TIMED_OUT, + TERM_MISC, +}; + +const char *fnic_state_to_str(unsigned int state) +{ + if (state >= ARRAY_SIZE(fnic_state_str) || !fnic_state_str[state]) + return "unknown"; + + return fnic_state_str[state]; +} + +static const char *fnic_ioreq_state_to_str(unsigned int state) +{ + if (state >= ARRAY_SIZE(fnic_ioreq_state_str) || + !fnic_ioreq_state_str[state]) + return "unknown"; + + return fnic_ioreq_state_str[state]; +} + +const char *fnic_fcpio_status_to_str(unsigned int status) +{ + if (status >= ARRAY_SIZE(fcpio_status_str) || !fcpio_status_str[status]) + return "unknown"; + + return fcpio_status_str[status]; +} + +/* + * Unmap the data buffer and sense buffer for an io_req, + * also unmap and free the device-private scatter/gather list. + */ +static void fnic_release_ioreq_buf(struct fnic *fnic, + struct fnic_io_req *io_req, + struct scsi_cmnd *sc) +{ + if (io_req->sgl_list_pa) + dma_unmap_single(&fnic->pdev->dev, io_req->sgl_list_pa, + sizeof(io_req->sgl_list[0]) * io_req->sgl_cnt, + DMA_TO_DEVICE); + scsi_dma_unmap(sc); + + if (io_req->sgl_cnt) + mempool_free(io_req->sgl_list_alloc, + fnic->io_sgl_pool[io_req->sgl_type]); + if (io_req->sense_buf_pa) + dma_unmap_single(&fnic->pdev->dev, io_req->sense_buf_pa, + SCSI_SENSE_BUFFERSIZE, DMA_FROM_DEVICE); +} + +static bool +fnic_count_portid_ioreqs_iter(struct fnic *fnic, struct scsi_cmnd *sc, + void *data1, void *data2) +{ + u32 *portid = data1; + unsigned int *count = data2; + struct fnic_io_req *io_req = fnic_priv(sc)->io_req; + + if (!io_req || (*portid && (io_req->port_id != *portid))) + return true; + + *count += 1; + return true; +} + +unsigned int fnic_count_ioreqs(struct fnic *fnic, u32 portid) +{ + unsigned int count = 0; + + if (IS_FNIC_FCP_INITIATOR(fnic)) + fnic_scsi_io_iter(fnic, fnic_count_portid_ioreqs_iter, + &portid, &count); + + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "portid = 0x%x count = %u\n", portid, count); + return count; +} + +unsigned int fnic_count_all_ioreqs(struct fnic *fnic) +{ + return fnic_count_ioreqs(fnic, 0); +} + +static bool +fnic_count_lun_ioreqs_iter(struct fnic *fnic, struct scsi_cmnd *sc, + void *data1, void *data2) +{ + struct scsi_device *scsi_device = data1; + unsigned int *count = data2; + + if (sc->device != scsi_device || !fnic_priv(sc)->io_req) + return true; + + *count += 1; + return true; +} + +unsigned int +fnic_count_lun_ioreqs(struct fnic *fnic, struct scsi_device *scsi_device) +{ + unsigned int count = 0; + + fnic_scsi_io_iter(fnic, fnic_count_lun_ioreqs_iter, + scsi_device, &count); + + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "lun = %p count = %u\n", scsi_device, count); + return count; +} + +/* Free up Copy Wq descriptors. Called with copy_wq lock held */ +int free_wq_copy_descs(struct fnic *fnic, struct vnic_wq_copy *wq, unsigned int hwq) +{ + /* if no Ack received from firmware, then nothing to clean */ + if (!fnic->fw_ack_recd[hwq]) + return 1; + + /* + * Update desc_available count based on number of freed descriptors + * Account for wraparound + */ + if (wq->to_clean_index <= fnic->fw_ack_index[hwq]) + wq->ring.desc_avail += (fnic->fw_ack_index[hwq] + - wq->to_clean_index + 1); + else + wq->ring.desc_avail += (wq->ring.desc_count + - wq->to_clean_index + + fnic->fw_ack_index[hwq] + 1); + + /* + * just bump clean index to ack_index+1 accounting for wraparound + * this will essentially free up all descriptors between + * to_clean_index and fw_ack_index, both inclusive + */ + wq->to_clean_index = + (fnic->fw_ack_index[hwq] + 1) % wq->ring.desc_count; + + /* we have processed the acks received so far */ + fnic->fw_ack_recd[hwq] = 0; + return 0; +} + + +/* + * __fnic_set_state_flags + * Sets/Clears bits in fnic's state_flags + **/ +void +__fnic_set_state_flags(struct fnic *fnic, unsigned long st_flags, + unsigned long clearbits) +{ + unsigned long flags = 0; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + + if (clearbits) + fnic->state_flags &= ~st_flags; + else + fnic->state_flags |= st_flags; + + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + return; +} + + +/* + * fnic_fw_reset_handler + * Routine to send reset msg to fw + */ +int fnic_fw_reset_handler(struct fnic *fnic) +{ + struct vnic_wq_copy *wq = &fnic->hw_copy_wq[0]; + int ret = 0; + unsigned long flags; + unsigned int ioreq_count; + + /* indicate fwreset to io path */ + fnic_set_state_flags(fnic, FNIC_FLAGS_FWRESET); + ioreq_count = fnic_count_all_ioreqs(fnic); + + /* wait for io cmpl */ + while (atomic_read(&fnic->in_flight)) + schedule_timeout(msecs_to_jiffies(1)); + + spin_lock_irqsave(&fnic->wq_copy_lock[0], flags); + + if (vnic_wq_copy_desc_avail(wq) <= fnic->wq_copy_desc_low[0]) + free_wq_copy_descs(fnic, wq, 0); + + if (!vnic_wq_copy_desc_avail(wq)) + ret = -EAGAIN; + else { + FNIC_SCSI_DBG(KERN_INFO, fnic, + "ioreq_count: %u\n", ioreq_count); + fnic_queue_wq_copy_desc_fw_reset(wq, SCSI_NO_TAG); + atomic64_inc(&fnic->fnic_stats.fw_stats.active_fw_reqs); + if (atomic64_read(&fnic->fnic_stats.fw_stats.active_fw_reqs) > + atomic64_read(&fnic->fnic_stats.fw_stats.max_fw_reqs)) + atomic64_set(&fnic->fnic_stats.fw_stats.max_fw_reqs, + atomic64_read( + &fnic->fnic_stats.fw_stats.active_fw_reqs)); + } + + spin_unlock_irqrestore(&fnic->wq_copy_lock[0], flags); + + if (!ret) { + atomic64_inc(&fnic->fnic_stats.reset_stats.fw_resets); + FNIC_SCSI_DBG(KERN_INFO, fnic, + "Issued fw reset\n"); + } else { + fnic_clear_state_flags(fnic, FNIC_FLAGS_FWRESET); + FNIC_SCSI_DBG(KERN_ERR, fnic, + "Failed to issue fw reset\n"); + } + + return ret; +} + + +/* + * fnic_flogi_reg_handler + * Routine to send flogi register msg to fw + */ +int fnic_flogi_reg_handler(struct fnic *fnic, u32 fc_id) +{ + struct vnic_wq_copy *wq = &fnic->hw_copy_wq[0]; + enum fcpio_flogi_reg_format_type format; + u8 gw_mac[ETH_ALEN]; + int ret = 0; + unsigned long flags; + struct fnic_iport_s *iport = &fnic->iport; + + spin_lock_irqsave(&fnic->wq_copy_lock[0], flags); + + if (vnic_wq_copy_desc_avail(wq) <= fnic->wq_copy_desc_low[0]) + free_wq_copy_descs(fnic, wq, 0); + + if (!vnic_wq_copy_desc_avail(wq)) { + ret = -EAGAIN; + goto flogi_reg_ioreq_end; + } + + memcpy(gw_mac, fnic->iport.fcfmac, ETH_ALEN); + format = FCPIO_FLOGI_REG_GW_DEST; + + if (fnic->config.flags & VFCF_FIP_CAPABLE) { + fnic_queue_wq_copy_desc_fip_reg(wq, SCSI_NO_TAG, + fc_id, gw_mac, + fnic->iport.fpma, + iport->r_a_tov, iport->e_d_tov); + FNIC_SCSI_DBG(KERN_INFO, fnic, + "FLOGI FIP reg issued fcid: 0x%x src %p dest %p\n", + fc_id, fnic->iport.fpma, gw_mac); + } else { + fnic_queue_wq_copy_desc_flogi_reg(wq, SCSI_NO_TAG, + format, fc_id, gw_mac); + FNIC_SCSI_DBG(KERN_INFO, fnic, + "FLOGI reg issued fcid 0x%x dest %p\n", + fc_id, gw_mac); + } + + atomic64_inc(&fnic->fnic_stats.fw_stats.active_fw_reqs); + if (atomic64_read(&fnic->fnic_stats.fw_stats.active_fw_reqs) > + atomic64_read(&fnic->fnic_stats.fw_stats.max_fw_reqs)) + atomic64_set(&fnic->fnic_stats.fw_stats.max_fw_reqs, + atomic64_read(&fnic->fnic_stats.fw_stats.active_fw_reqs)); + +flogi_reg_ioreq_end: + spin_unlock_irqrestore(&fnic->wq_copy_lock[0], flags); + return ret; +} + +/* + * fnic_queue_wq_copy_desc + * Routine to enqueue a wq copy desc + */ +static inline int fnic_queue_wq_copy_desc(struct fnic *fnic, + struct vnic_wq_copy *wq, + struct fnic_io_req *io_req, + struct scsi_cmnd *sc, + int sg_count, + uint32_t mqtag, + uint16_t hwq) +{ + struct scatterlist *sg; + struct fc_rport *rport = starget_to_rport(scsi_target(sc->device)); + struct host_sg_desc *desc; + struct misc_stats *misc_stats = &fnic->fnic_stats.misc_stats; + unsigned int i; + int flags; + u8 exch_flags; + struct scsi_lun fc_lun; + struct fnic_tport_s *tport; + struct rport_dd_data_s *rdd_data; + + rdd_data = rport->dd_data; + tport = rdd_data->tport; + + if (sg_count) { + /* For each SGE, create a device desc entry */ + desc = io_req->sgl_list; + for_each_sg(scsi_sglist(sc), sg, sg_count, i) { + desc->addr = cpu_to_le64(sg_dma_address(sg)); + desc->len = cpu_to_le32(sg_dma_len(sg)); + desc->_resvd = 0; + desc++; + } + + io_req->sgl_list_pa = dma_map_single(&fnic->pdev->dev, + io_req->sgl_list, + sizeof(io_req->sgl_list[0]) * sg_count, + DMA_TO_DEVICE); + if (dma_mapping_error(&fnic->pdev->dev, io_req->sgl_list_pa)) { + printk(KERN_ERR "DMA mapping failed\n"); + return SCSI_MLQUEUE_HOST_BUSY; + } + } + + io_req->sense_buf_pa = dma_map_single(&fnic->pdev->dev, + sc->sense_buffer, + SCSI_SENSE_BUFFERSIZE, + DMA_FROM_DEVICE); + if (dma_mapping_error(&fnic->pdev->dev, io_req->sense_buf_pa)) { + dma_unmap_single(&fnic->pdev->dev, io_req->sgl_list_pa, + sizeof(io_req->sgl_list[0]) * sg_count, + DMA_TO_DEVICE); + printk(KERN_ERR "DMA mapping failed\n"); + return SCSI_MLQUEUE_HOST_BUSY; + } + + int_to_scsilun(sc->device->lun, &fc_lun); + + /* Enqueue the descriptor in the Copy WQ */ + if (vnic_wq_copy_desc_avail(wq) <= fnic->wq_copy_desc_low[hwq]) + free_wq_copy_descs(fnic, wq, hwq); + + if (unlikely(!vnic_wq_copy_desc_avail(wq))) { + FNIC_SCSI_DBG(KERN_INFO, fnic, + "fnic_queue_wq_copy_desc failure - no descriptors\n"); + atomic64_inc(&misc_stats->io_cpwq_alloc_failures); + return SCSI_MLQUEUE_HOST_BUSY; + } + + flags = 0; + if (sc->sc_data_direction == DMA_FROM_DEVICE) + flags = FCPIO_ICMND_RDDATA; + else if (sc->sc_data_direction == DMA_TO_DEVICE) + flags = FCPIO_ICMND_WRDATA; + + exch_flags = 0; + if ((fnic->config.flags & VFCF_FCP_SEQ_LVL_ERR) && + (tport->tgt_flags & FDLS_FC_RP_FLAGS_RETRY)) + exch_flags |= FCPIO_ICMND_SRFLAG_RETRY; + + fnic_queue_wq_copy_desc_icmnd_16(wq, mqtag, + 0, exch_flags, io_req->sgl_cnt, + SCSI_SENSE_BUFFERSIZE, + io_req->sgl_list_pa, + io_req->sense_buf_pa, + 0, /* scsi cmd ref, always 0 */ + FCPIO_ICMND_PTA_SIMPLE, + /* scsi pri and tag */ + flags, /* command flags */ + sc->cmnd, sc->cmd_len, + scsi_bufflen(sc), + fc_lun.scsi_lun, io_req->port_id, + tport->max_payload_size, + tport->r_a_tov, tport->e_d_tov); + + atomic64_inc(&fnic->fnic_stats.fw_stats.active_fw_reqs); + if (atomic64_read(&fnic->fnic_stats.fw_stats.active_fw_reqs) > + atomic64_read(&fnic->fnic_stats.fw_stats.max_fw_reqs)) + atomic64_set(&fnic->fnic_stats.fw_stats.max_fw_reqs, + atomic64_read(&fnic->fnic_stats.fw_stats.active_fw_reqs)); + + return 0; +} + +enum scsi_qc_status fnic_queuecommand(struct Scsi_Host *shost, + struct scsi_cmnd *sc) +{ + struct request *const rq = scsi_cmd_to_rq(sc); + uint32_t mqtag = 0; + void (*done)(struct scsi_cmnd *) = scsi_done; + struct fc_rport *rport; + struct fnic_io_req *io_req = NULL; + struct fnic *fnic = *((struct fnic **) shost_priv(sc->device->host)); + struct fnic_iport_s *iport = NULL; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + struct vnic_wq_copy *wq; + int ret = 1; + u64 cmd_trace; + int sg_count = 0; + unsigned long flags = 0; + unsigned long ptr; + uint16_t hwq = 0; + struct fnic_tport_s *tport = NULL; + struct rport_dd_data_s *rdd_data; + uint16_t lun0_delay = 0; + + rport = starget_to_rport(scsi_target(sc->device)); + if (!rport) { + FNIC_SCSI_DBG(KERN_ERR, fnic, + "returning DID_NO_CONNECT for IO as rport is NULL\n"); + sc->result = DID_NO_CONNECT << 16; + done(sc); + return 0; + } + + ret = fc_remote_port_chkready(rport); + if (ret) { + FNIC_SCSI_DBG(KERN_ERR, fnic, + "rport is not ready\n"); + atomic64_inc(&fnic_stats->misc_stats.tport_not_ready); + sc->result = ret; + done(sc); + return 0; + } + + mqtag = blk_mq_unique_tag(rq); + spin_lock_irqsave(&fnic->fnic_lock, flags); + iport = &fnic->iport; + + if (iport->state != FNIC_IPORT_STATE_READY) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + FNIC_SCSI_DBG(KERN_ERR, fnic, + "returning DID_NO_CONNECT for IO as iport state: %d\n", + iport->state); + sc->result = DID_NO_CONNECT << 16; + done(sc); + return 0; + } + + /* fc_remote_port_add() may have added the tport to + * fc_transport but dd_data not yet set + */ + rdd_data = rport->dd_data; + tport = rdd_data->tport; + if (!tport || (rdd_data->iport != iport)) { + FNIC_SCSI_DBG(KERN_ERR, fnic, + "dd_data not yet set in SCSI for rport portid: 0x%x\n", + rport->port_id); + tport = fnic_find_tport_by_fcid(iport, rport->port_id); + if (!tport) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + FNIC_SCSI_DBG(KERN_ERR, fnic, + "returning DID_BUS_BUSY for IO as tport not found for: 0x%x\n", + rport->port_id); + sc->result = DID_BUS_BUSY << 16; + done(sc); + return 0; + } + + /* Re-assign same params as in fnic_fdls_add_tport */ + rport->maxframe_size = FNIC_FC_MAX_PAYLOAD_LEN; + rport->supported_classes = + FC_COS_CLASS3 | FC_RPORT_ROLE_FCP_TARGET; + /* the dd_data is allocated by fctransport of size dd_fcrport_size */ + rdd_data = rport->dd_data; + rdd_data->tport = tport; + rdd_data->iport = iport; + tport->rport = rport; + tport->flags |= FNIC_FDLS_SCSI_REGISTERED; + } + + if ((tport->state != FDLS_TGT_STATE_READY) + && (tport->state != FDLS_TGT_STATE_ADISC)) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + FNIC_SCSI_DBG(KERN_ERR, fnic, + "returning DID_NO_CONNECT for IO as tport state: %d\n", + tport->state); + sc->result = DID_NO_CONNECT << 16; + done(sc); + return 0; + } + + atomic_inc(&fnic->in_flight); + atomic_inc(&tport->in_flight); + + if (unlikely(fnic_chk_state_flags_locked(fnic, FNIC_FLAGS_IO_BLOCKED))) { + atomic_dec(&fnic->in_flight); + atomic_dec(&tport->in_flight); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return SCSI_MLQUEUE_HOST_BUSY; + } + + if (unlikely(fnic_chk_state_flags_locked(fnic, FNIC_FLAGS_FWRESET))) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + FNIC_SCSI_DBG(KERN_ERR, fnic, + "fnic flags FW reset: 0x%lx. Returning SCSI_MLQUEUE_HOST_BUSY\n", + fnic->state_flags); + return SCSI_MLQUEUE_HOST_BUSY; + } + + if (!tport->lun0_delay) { + lun0_delay = 1; + tport->lun0_delay++; + } + + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + fnic_priv(sc)->state = FNIC_IOREQ_NOT_INITED; + fnic_priv(sc)->flags = FNIC_NO_FLAGS; + + /* Get a new io_req for this SCSI IO */ + io_req = mempool_alloc(fnic->io_req_pool, GFP_ATOMIC); + if (!io_req) { + atomic64_inc(&fnic_stats->io_stats.alloc_failures); + ret = SCSI_MLQUEUE_HOST_BUSY; + goto out; + } + memset(io_req, 0, sizeof(*io_req)); + + /* Map the data buffer */ + sg_count = scsi_dma_map(sc); + if (sg_count < 0) { + FNIC_TRACE(fnic_queuecommand, sc->device->host->host_no, + mqtag, sc, 0, sc->cmnd[0], sg_count, fnic_priv(sc)->state); + mempool_free(io_req, fnic->io_req_pool); + goto out; + } + + io_req->tport = tport; + /* Determine the type of scatter/gather list we need */ + io_req->sgl_cnt = sg_count; + io_req->sgl_type = FNIC_SGL_CACHE_DFLT; + if (sg_count > FNIC_DFLT_SG_DESC_CNT) + io_req->sgl_type = FNIC_SGL_CACHE_MAX; + + if (sg_count) { + io_req->sgl_list = + mempool_alloc(fnic->io_sgl_pool[io_req->sgl_type], + GFP_ATOMIC); + if (!io_req->sgl_list) { + atomic64_inc(&fnic_stats->io_stats.alloc_failures); + ret = SCSI_MLQUEUE_HOST_BUSY; + scsi_dma_unmap(sc); + mempool_free(io_req, fnic->io_req_pool); + goto out; + } + + /* Cache sgl list allocated address before alignment */ + io_req->sgl_list_alloc = io_req->sgl_list; + ptr = (unsigned long) io_req->sgl_list; + if (ptr % FNIC_SG_DESC_ALIGN) { + io_req->sgl_list = (struct host_sg_desc *) + (((unsigned long) ptr + + FNIC_SG_DESC_ALIGN - 1) + & ~(FNIC_SG_DESC_ALIGN - 1)); + } + } + + /* + * Will acquire lock before setting to IO initialized. + */ + hwq = blk_mq_unique_tag_to_hwq(mqtag); + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + + /* initialize rest of io_req */ + io_req->port_id = rport->port_id; + io_req->start_time = jiffies; + fnic_priv(sc)->state = FNIC_IOREQ_CMD_PENDING; + fnic_priv(sc)->io_req = io_req; + fnic_priv(sc)->flags |= FNIC_IO_INITIALIZED; + io_req->sc = sc; + + if (fnic->sw_copy_wq[hwq].io_req_table[blk_mq_unique_tag_to_tag(mqtag)] != NULL) { + WARN(1, "fnic<%d>: %s: hwq: %d tag 0x%x already exists\n", + fnic->fnic_num, __func__, hwq, blk_mq_unique_tag_to_tag(mqtag)); + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + return SCSI_MLQUEUE_HOST_BUSY; + } + + fnic->sw_copy_wq[hwq].io_req_table[blk_mq_unique_tag_to_tag(mqtag)] = io_req; + io_req->tag = mqtag; + + /* create copy wq desc and enqueue it */ + wq = &fnic->hw_copy_wq[hwq]; + atomic64_inc(&fnic_stats->io_stats.ios[hwq]); + ret = fnic_queue_wq_copy_desc(fnic, wq, io_req, sc, sg_count, mqtag, hwq); + if (ret) { + /* + * In case another thread cancelled the request, + * refetch the pointer under the lock. + */ + FNIC_TRACE(fnic_queuecommand, sc->device->host->host_no, + mqtag, sc, 0, 0, 0, fnic_flags_and_state(sc)); + io_req = fnic_priv(sc)->io_req; + fnic_priv(sc)->io_req = NULL; + if (io_req) + fnic->sw_copy_wq[hwq].io_req_table[blk_mq_unique_tag_to_tag(mqtag)] = NULL; + fnic_priv(sc)->state = FNIC_IOREQ_CMD_COMPLETE; + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + if (io_req) { + fnic_release_ioreq_buf(fnic, io_req, sc); + mempool_free(io_req, fnic->io_req_pool); + } + atomic_dec(&fnic->in_flight); + atomic_dec(&tport->in_flight); + return ret; + } else { + atomic64_inc(&fnic_stats->io_stats.active_ios); + atomic64_inc(&fnic_stats->io_stats.num_ios); + if (atomic64_read(&fnic_stats->io_stats.active_ios) > + atomic64_read(&fnic_stats->io_stats.max_active_ios)) + atomic64_set(&fnic_stats->io_stats.max_active_ios, + atomic64_read(&fnic_stats->io_stats.active_ios)); + + /* REVISIT: Use per IO lock in the final code */ + fnic_priv(sc)->flags |= FNIC_IO_ISSUED; + } + + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + +out: + cmd_trace = ((u64)sc->cmnd[0] << 56 | (u64)sc->cmnd[7] << 40 | + (u64)sc->cmnd[8] << 32 | (u64)sc->cmnd[2] << 24 | + (u64)sc->cmnd[3] << 16 | (u64)sc->cmnd[4] << 8 | + sc->cmnd[5]); + + FNIC_TRACE(fnic_queuecommand, sc->device->host->host_no, + mqtag, sc, io_req, sg_count, cmd_trace, + fnic_flags_and_state(sc)); + + atomic_dec(&fnic->in_flight); + atomic_dec(&tport->in_flight); + + if (lun0_delay) { + FNIC_SCSI_DBG(KERN_INFO, fnic, + "LUN0 delay\n"); + mdelay(LUN0_DELAY_TIME); + } + + return ret; +} + + +/* + * fnic_fcpio_fw_reset_cmpl_handler + * Routine to handle fw reset completion + */ +static int fnic_fcpio_fw_reset_cmpl_handler(struct fnic *fnic, + struct fcpio_fw_req *desc) +{ + u8 type; + u8 hdr_status; + struct fcpio_tag tag; + int ret = 0; + unsigned long flags; + struct reset_stats *reset_stats = &fnic->fnic_stats.reset_stats; + + fcpio_header_dec(&desc->hdr, &type, &hdr_status, &tag); + + atomic64_inc(&reset_stats->fw_reset_completions); + + /* Clean up all outstanding io requests */ + if (IS_FNIC_FCP_INITIATOR(fnic)) + fnic_cleanup_io(fnic, SCSI_NO_TAG); + else if (IS_FNIC_NVME_INITIATOR(fnic)) + nvfnic_cleanup_all_nvme_ios(fnic); + + atomic64_set(&fnic->fnic_stats.fw_stats.active_fw_reqs, 0); + atomic64_set(&fnic->fnic_stats.io_stats.active_ios, 0); + atomic64_set(&fnic->io_cmpl_skip, 0); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + + /* fnic should be in FC_TRANS_ETH_MODE */ + if (fnic->state == FNIC_IN_FC_TRANS_ETH_MODE) { + /* Check status of reset completion */ + if (!hdr_status) { + FNIC_SCSI_DBG(KERN_INFO, fnic, + "reset cmpl success\n"); + /* Ready to send flogi out */ + fnic->state = FNIC_IN_ETH_MODE; + } else { + FNIC_SCSI_DBG(KERN_ERR, fnic, + "reset failed with header status: %s\n", + fnic_fcpio_status_to_str(hdr_status)); + + fnic->state = FNIC_IN_FC_MODE; + atomic64_inc(&reset_stats->fw_reset_failures); + ret = -1; + } + } else { + FNIC_SCSI_DBG(KERN_ERR, fnic, + "Unexpected state while processing reset completion: %s\n", + fnic_state_to_str(fnic->state)); + atomic64_inc(&reset_stats->fw_reset_failures); + ret = -1; + } + + if (fnic->fw_reset_done) + complete(fnic->fw_reset_done); + + /* + * If fnic is being removed, or fw reset failed + * free the flogi frame. Else, send it out + */ + if (ret) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + fnic_free_txq(fnic); + goto reset_cmpl_handler_end; + } + + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + queue_work(fnic_event_queue, &fnic->flush_work); + + reset_cmpl_handler_end: + fnic_clear_state_flags(fnic, FNIC_FLAGS_FWRESET); + + return ret; +} + +/* + * fnic_fcpio_flogi_reg_cmpl_handler + * Routine to handle flogi register completion + */ +static int fnic_fcpio_flogi_reg_cmpl_handler(struct fnic *fnic, + struct fcpio_fw_req *desc) +{ + u8 type; + u8 hdr_status; + struct fcpio_tag tag; + int ret = 0; + unsigned long flags; + + fcpio_header_dec(&desc->hdr, &type, &hdr_status, &tag); + + /* Update fnic state based on status of flogi reg completion */ + spin_lock_irqsave(&fnic->fnic_lock, flags); + + if (fnic->state == FNIC_IN_ETH_TRANS_FC_MODE) { + + /* Check flogi registration completion status */ + if (!hdr_status) { + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "FLOGI reg succeeded\n"); + fnic->state = FNIC_IN_FC_MODE; + } else { + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "fnic flogi reg failed: %s\n", + fnic_fcpio_status_to_str(hdr_status)); + fnic->state = FNIC_IN_ETH_MODE; + ret = -1; + } + } else { + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "Unexpected fnic state %s while" + " processing flogi reg completion\n", + fnic_state_to_str(fnic->state)); + ret = -1; + } + + if (!ret) { + if (fnic->stop_rx_link_events) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + goto reg_cmpl_handler_end; + } + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + queue_work(fnic_event_queue, &fnic->flush_work); + queue_work(fnic_event_queue, &fnic->frame_work); + } else { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + } + +reg_cmpl_handler_end: + return ret; +} + +static inline int is_ack_index_in_range(struct vnic_wq_copy *wq, + u16 request_out) +{ + if (wq->to_clean_index <= wq->to_use_index) { + /* out of range, stale request_out index */ + if (request_out < wq->to_clean_index || + request_out >= wq->to_use_index) + return 0; + } else { + /* out of range, stale request_out index */ + if (request_out < wq->to_clean_index && + request_out >= wq->to_use_index) + return 0; + } + /* request_out index is in range */ + return 1; +} + + +/* + * Mark that ack received and store the Ack index. If there are multiple + * acks received before Tx thread cleans it up, the latest value will be + * used which is correct behavior. This state should be in the copy Wq + * instead of in the fnic + */ +static inline void fnic_fcpio_ack_handler(struct fnic *fnic, + unsigned int cq_index, + struct fcpio_fw_req *desc) +{ + struct vnic_wq_copy *wq; + u16 request_out = desc->u.ack.request_out; + unsigned long flags; + u64 *ox_id_tag = (u64 *)(void *)desc; + unsigned int wq_index = cq_index; + + /* mark the ack state */ + wq = &fnic->hw_copy_wq[cq_index]; + spin_lock_irqsave(&fnic->wq_copy_lock[wq_index], flags); + + fnic->fnic_stats.misc_stats.last_ack_time = jiffies; + if (is_ack_index_in_range(wq, request_out)) { + fnic->fw_ack_index[wq_index] = request_out; + fnic->fw_ack_recd[wq_index] = 1; + } else + atomic64_inc( + &fnic->fnic_stats.misc_stats.ack_index_out_of_range); + + spin_unlock_irqrestore(&fnic->wq_copy_lock[wq_index], flags); + FNIC_TRACE(fnic_fcpio_ack_handler, + fnic->fnic_num, 0, 0, ox_id_tag[2], ox_id_tag[3], + ox_id_tag[4], ox_id_tag[5]); +} + +/* + * fnic_fcpio_icmnd_cmpl_handler + * Routine to handle icmnd completions + */ +static void fnic_fcpio_icmnd_cmpl_handler(struct fnic *fnic, unsigned int cq_index, + struct fcpio_fw_req *desc) +{ + u8 type; + u8 hdr_status; + struct fcpio_tag ftag; + u32 id; + u64 xfer_len = 0; + struct fcpio_icmnd_cmpl *icmnd_cmpl; + struct fnic_io_req *io_req; + struct scsi_cmnd *sc; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + unsigned long flags; + u64 cmd_trace; + unsigned long start_time; + unsigned long io_duration_time; + unsigned int hwq = 0; + unsigned int mqtag = 0; + unsigned int tag = 0; + + /* Decode the cmpl description to get the io_req id */ + fcpio_header_dec(&desc->hdr, &type, &hdr_status, &ftag); + fcpio_tag_id_dec(&ftag, &id); + icmnd_cmpl = &desc->u.icmnd_cmpl; + + mqtag = id; + tag = blk_mq_unique_tag_to_tag(mqtag); + hwq = blk_mq_unique_tag_to_hwq(mqtag); + + if (hwq != cq_index) { + FNIC_SCSI_DBG(KERN_ERR, fnic, + "hwq: %d mqtag: 0x%x tag: 0x%x cq index: %d ", + hwq, mqtag, tag, cq_index); + FNIC_SCSI_DBG(KERN_ERR, fnic, + "hdr status: %s icmnd completion on the wrong queue\n", + fnic_fcpio_status_to_str(hdr_status)); + } + + if (tag >= fnic->fnic_max_tag_id) { + FNIC_SCSI_DBG(KERN_ERR, fnic, + "hwq: %d mqtag: 0x%x tag: 0x%x cq index: %d ", + hwq, mqtag, tag, cq_index); + FNIC_SCSI_DBG(KERN_ERR, fnic, + "hdr status: %s Out of range tag\n", + fnic_fcpio_status_to_str(hdr_status)); + return; + } + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + + sc = scsi_host_find_tag(fnic->host, id); + WARN_ON_ONCE(!sc); + if (!sc) { + atomic64_inc(&fnic_stats->io_stats.sc_null); + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + fnic_printk(KERN_ERR, fnic, + "icmnd_cmpl sc is null - " + "hdr status = %s tag = 0x%x desc = 0x%p\n", + fnic_fcpio_status_to_str(hdr_status), id, desc); + FNIC_TRACE(fnic_fcpio_icmnd_cmpl_handler, + fnic->host->host_no, id, + ((u64)icmnd_cmpl->_resvd0[1] << 16 | + (u64)icmnd_cmpl->_resvd0[0]), + ((u64)hdr_status << 16 | + (u64)icmnd_cmpl->scsi_status << 8 | + (u64)icmnd_cmpl->flags), desc, + (u64)icmnd_cmpl->residual, 0); + return; + } + + io_req = fnic_priv(sc)->io_req; + if (fnic->sw_copy_wq[hwq].io_req_table[tag] != io_req) { + WARN(1, "%s: %d: hwq: %d mqtag: 0x%x tag: 0x%x io_req tag mismatch\n", + __func__, __LINE__, hwq, mqtag, tag); + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + return; + } + + WARN_ON_ONCE(!io_req); + if (!io_req) { + atomic64_inc(&fnic_stats->io_stats.ioreq_null); + fnic_priv(sc)->flags |= FNIC_IO_REQ_NULL; + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + fnic_printk(KERN_ERR, fnic, + "icmnd_cmpl io_req is null - " + "hdr status = %s tag = 0x%x sc 0x%p\n", + fnic_fcpio_status_to_str(hdr_status), id, sc); + return; + } + start_time = io_req->start_time; + + /* firmware completed the io */ + io_req->io_completed = 1; + + /* + * if SCSI-ML has already issued abort on this command, + * set completion of the IO. The abts path will clean it up + */ + if (fnic_priv(sc)->state == FNIC_IOREQ_ABTS_PENDING) { + + /* + * set the FNIC_IO_DONE so that this doesn't get + * flagged as 'out of order' if it was not aborted + */ + fnic_priv(sc)->flags |= FNIC_IO_DONE; + fnic_priv(sc)->flags |= FNIC_IO_ABTS_PENDING; + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + if(FCPIO_ABORTED == hdr_status) + fnic_priv(sc)->flags |= FNIC_IO_ABORTED; + + FNIC_SCSI_DBG(KERN_INFO, fnic, + "icmnd_cmpl abts pending " + "hdr status = %s tag = 0x%x sc = 0x%p " + "scsi_status = %x residual = %d\n", + fnic_fcpio_status_to_str(hdr_status), + id, sc, + icmnd_cmpl->scsi_status, + icmnd_cmpl->residual); + return; + } + + /* Mark the IO as complete */ + fnic_priv(sc)->state = FNIC_IOREQ_CMD_COMPLETE; + + icmnd_cmpl = &desc->u.icmnd_cmpl; + + switch (hdr_status) { + case FCPIO_SUCCESS: + sc->result = (DID_OK << 16) | icmnd_cmpl->scsi_status; + xfer_len = scsi_bufflen(sc); + + if (icmnd_cmpl->flags & FCPIO_ICMND_CMPL_RESID_UNDER) { + xfer_len -= icmnd_cmpl->residual; + scsi_set_resid(sc, icmnd_cmpl->residual); + } + + if (icmnd_cmpl->scsi_status == SAM_STAT_CHECK_CONDITION) + atomic64_inc(&fnic_stats->misc_stats.check_condition); + + if (icmnd_cmpl->scsi_status == SAM_STAT_TASK_SET_FULL) + atomic64_inc(&fnic_stats->misc_stats.queue_fulls); + + FNIC_SCSI_DBG(KERN_INFO, fnic, + "xfer_len: %llu", xfer_len); + break; + + case FCPIO_TIMEOUT: /* request was timed out */ + atomic64_inc(&fnic_stats->misc_stats.fcpio_timeout); + sc->result = (DID_TIME_OUT << 16) | icmnd_cmpl->scsi_status; + break; + + case FCPIO_ABORTED: /* request was aborted */ + atomic64_inc(&fnic_stats->misc_stats.fcpio_aborted); + sc->result = (DID_ERROR << 16) | icmnd_cmpl->scsi_status; + break; + + case FCPIO_DATA_CNT_MISMATCH: /* recv/sent more/less data than exp. */ + atomic64_inc(&fnic_stats->misc_stats.data_count_mismatch); + scsi_set_resid(sc, icmnd_cmpl->residual); + sc->result = (DID_ERROR << 16) | icmnd_cmpl->scsi_status; + break; + + case FCPIO_OUT_OF_RESOURCE: /* out of resources to complete request */ + atomic64_inc(&fnic_stats->fw_stats.fw_out_of_resources); + sc->result = (DID_REQUEUE << 16) | icmnd_cmpl->scsi_status; + break; + + case FCPIO_IO_NOT_FOUND: /* requested I/O was not found */ + atomic64_inc(&fnic_stats->io_stats.io_not_found); + sc->result = (DID_ERROR << 16) | icmnd_cmpl->scsi_status; + break; + + case FCPIO_SGL_INVALID: /* request was aborted due to sgl error */ + atomic64_inc(&fnic_stats->misc_stats.sgl_invalid); + sc->result = (DID_ERROR << 16) | icmnd_cmpl->scsi_status; + break; + + case FCPIO_FW_ERR: /* request was terminated due fw error */ + atomic64_inc(&fnic_stats->fw_stats.io_fw_errs); + sc->result = (DID_ERROR << 16) | icmnd_cmpl->scsi_status; + break; + + case FCPIO_MSS_INVALID: /* request was aborted due to mss error */ + atomic64_inc(&fnic_stats->misc_stats.mss_invalid); + sc->result = (DID_ERROR << 16) | icmnd_cmpl->scsi_status; + break; + + case FCPIO_INVALID_HEADER: /* header contains invalid data */ + case FCPIO_INVALID_PARAM: /* some parameter in request invalid */ + case FCPIO_REQ_NOT_SUPPORTED:/* request type is not supported */ + default: + sc->result = (DID_ERROR << 16) | icmnd_cmpl->scsi_status; + break; + } + + /* Break link with the SCSI command */ + fnic_priv(sc)->io_req = NULL; + io_req->sc = NULL; + fnic_priv(sc)->flags |= FNIC_IO_DONE; + fnic->sw_copy_wq[hwq].io_req_table[tag] = NULL; + + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + + if (hdr_status != FCPIO_SUCCESS) { + atomic64_inc(&fnic_stats->io_stats.io_failures); + fnic_printk(KERN_ERR, fnic, "hdr status = %s\n", + fnic_fcpio_status_to_str(hdr_status)); + } + + fnic_release_ioreq_buf(fnic, io_req, sc); + + cmd_trace = ((u64)hdr_status << 56) | + (u64)icmnd_cmpl->scsi_status << 48 | + (u64)icmnd_cmpl->flags << 40 | (u64)sc->cmnd[0] << 32 | + (u64)sc->cmnd[2] << 24 | (u64)sc->cmnd[3] << 16 | + (u64)sc->cmnd[4] << 8 | sc->cmnd[5]; + + FNIC_TRACE(fnic_fcpio_icmnd_cmpl_handler, + sc->device->host->host_no, id, sc, + ((u64)icmnd_cmpl->_resvd0[1] << 56 | + (u64)icmnd_cmpl->_resvd0[0] << 48 | + jiffies_to_msecs(jiffies - start_time)), + desc, cmd_trace, fnic_flags_and_state(sc)); + + if (sc->sc_data_direction == DMA_FROM_DEVICE) { + fnic_stats->host_stats.fcp_input_requests++; + fnic->fcp_input_bytes += xfer_len; + } else if (sc->sc_data_direction == DMA_TO_DEVICE) { + fnic_stats->host_stats.fcp_output_requests++; + fnic->fcp_output_bytes += xfer_len; + } else + fnic_stats->host_stats.fcp_control_requests++; + + /* Call SCSI completion function to complete the IO */ + scsi_done(sc); + + mempool_free(io_req, fnic->io_req_pool); + + atomic64_dec(&fnic_stats->io_stats.active_ios); + if (atomic64_read(&fnic->io_cmpl_skip)) + atomic64_dec(&fnic->io_cmpl_skip); + else + atomic64_inc(&fnic_stats->io_stats.io_completions); + + + io_duration_time = jiffies_to_msecs(jiffies) - + jiffies_to_msecs(start_time); + + if(io_duration_time <= 10) + atomic64_inc(&fnic_stats->io_stats.io_btw_0_to_10_msec); + else if(io_duration_time <= 100) + atomic64_inc(&fnic_stats->io_stats.io_btw_10_to_100_msec); + else if(io_duration_time <= 500) + atomic64_inc(&fnic_stats->io_stats.io_btw_100_to_500_msec); + else if(io_duration_time <= 5000) + atomic64_inc(&fnic_stats->io_stats.io_btw_500_to_5000_msec); + else if(io_duration_time <= 10000) + atomic64_inc(&fnic_stats->io_stats.io_btw_5000_to_10000_msec); + else if(io_duration_time <= 30000) + atomic64_inc(&fnic_stats->io_stats.io_btw_10000_to_30000_msec); + else { + atomic64_inc(&fnic_stats->io_stats.io_greater_than_30000_msec); + + if(io_duration_time > atomic64_read(&fnic_stats->io_stats.current_max_io_time)) + atomic64_set(&fnic_stats->io_stats.current_max_io_time, io_duration_time); + } +} + +/* fnic_fcpio_itmf_cmpl_handler + * Routine to handle itmf completions + */ +static void fnic_fcpio_itmf_cmpl_handler(struct fnic *fnic, unsigned int cq_index, + struct fcpio_fw_req *desc) +{ + u8 type; + u8 hdr_status; + struct fcpio_tag ftag; + u32 id; + struct scsi_cmnd *sc = NULL; + struct fnic_io_req *io_req; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + struct abort_stats *abts_stats = &fnic->fnic_stats.abts_stats; + struct terminate_stats *term_stats = &fnic->fnic_stats.term_stats; + struct misc_stats *misc_stats = &fnic->fnic_stats.misc_stats; + unsigned long flags; + unsigned long start_time; + unsigned int hwq = cq_index; + unsigned int mqtag; + unsigned int tag; + + fcpio_header_dec(&desc->hdr, &type, &hdr_status, &ftag); + fcpio_tag_id_dec(&ftag, &id); + + mqtag = id & FNIC_TAG_MASK; + tag = blk_mq_unique_tag_to_tag(id & FNIC_TAG_MASK); + hwq = blk_mq_unique_tag_to_hwq(id & FNIC_TAG_MASK); + + if (hwq != cq_index) { + FNIC_SCSI_DBG(KERN_ERR, fnic, + "hwq: %d mqtag: 0x%x tag: 0x%x cq index: %d ", + hwq, mqtag, tag, cq_index); + FNIC_SCSI_DBG(KERN_ERR, fnic, + "hdr status: %s ITMF completion on the wrong queue\n", + fnic_fcpio_status_to_str(hdr_status)); + } + + if (tag > fnic->fnic_max_tag_id) { + FNIC_SCSI_DBG(KERN_ERR, fnic, + "hwq: %d mqtag: 0x%x tag: 0x%x cq index: %d ", + hwq, mqtag, tag, cq_index); + FNIC_SCSI_DBG(KERN_ERR, fnic, + "hdr status: %s Tag out of range\n", + fnic_fcpio_status_to_str(hdr_status)); + return; + } else if ((tag == fnic->fnic_max_tag_id) && !(id & FNIC_TAG_DEV_RST)) { + FNIC_SCSI_DBG(KERN_ERR, fnic, + "hwq: %d mqtag: 0x%x tag: 0x%x cq index: %d ", + hwq, mqtag, tag, cq_index); + FNIC_SCSI_DBG(KERN_ERR, fnic, + "hdr status: %s Tag out of range\n", + fnic_fcpio_status_to_str(hdr_status)); + return; + } + + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + + /* If it is sg3utils allocated SC then tag_id + * is max_tag_id and SC is retrieved from io_req + */ + if ((mqtag == fnic->fnic_max_tag_id) && (id & FNIC_TAG_DEV_RST)) { + io_req = fnic->sw_copy_wq[hwq].io_req_table[tag]; + if (io_req) + sc = io_req->sc; + } else { + sc = scsi_host_find_tag(fnic->host, id & FNIC_TAG_MASK); + } + + WARN_ON_ONCE(!sc); + if (!sc) { + atomic64_inc(&fnic_stats->io_stats.sc_null); + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + fnic_printk(KERN_ERR, fnic, + "itmf_cmpl sc is null - hdr status = %s tag = 0x%x\n", + fnic_fcpio_status_to_str(hdr_status), tag); + return; + } + + io_req = fnic_priv(sc)->io_req; + WARN_ON_ONCE(!io_req); + if (!io_req) { + atomic64_inc(&fnic_stats->io_stats.ioreq_null); + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + fnic_priv(sc)->flags |= FNIC_IO_ABT_TERM_REQ_NULL; + fnic_printk(KERN_ERR, fnic, + "itmf_cmpl io_req is null - " + "hdr status = %s tag = 0x%x sc 0x%p\n", + fnic_fcpio_status_to_str(hdr_status), tag, sc); + return; + } + start_time = io_req->start_time; + + if ((id & FNIC_TAG_ABORT) && (id & FNIC_TAG_DEV_RST)) { + /* Abort and terminate completion of device reset req */ + /* REVISIT : Add asserts about various flags */ + FNIC_SCSI_DBG(KERN_INFO, fnic, + "hwq: %d mqtag: 0x%x tag: 0x%x hst: %s Abt/term completion received\n", + hwq, mqtag, tag, + fnic_fcpio_status_to_str(hdr_status)); + fnic_priv(sc)->state = FNIC_IOREQ_ABTS_COMPLETE; + fnic_priv(sc)->abts_status = hdr_status; + fnic_priv(sc)->flags |= FNIC_DEV_RST_DONE; + if (io_req->abts_done) + complete(io_req->abts_done); + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + } else if (id & FNIC_TAG_ABORT) { + /* Completion of abort cmd */ + fnic_printk(KERN_DEBUG, fnic, + "hwq: %d mqtag: 0x%x tag: 0x%x Abort header status: %s\n", + hwq, mqtag, tag, + fnic_fcpio_status_to_str(hdr_status)); + switch (hdr_status) { + case FCPIO_SUCCESS: + break; + case FCPIO_TIMEOUT: + if (fnic_priv(sc)->flags & FNIC_IO_ABTS_ISSUED) + atomic64_inc(&abts_stats->abort_fw_timeouts); + else + atomic64_inc( + &term_stats->terminate_fw_timeouts); + break; + case FCPIO_ITMF_REJECTED: + FNIC_SCSI_DBG(KERN_INFO, fnic, + "abort reject recd. id %d\n", + (int)(id & FNIC_TAG_MASK)); + break; + case FCPIO_IO_NOT_FOUND: + if (fnic_priv(sc)->flags & FNIC_IO_ABTS_ISSUED) + atomic64_inc(&abts_stats->abort_io_not_found); + else + atomic64_inc( + &term_stats->terminate_io_not_found); + break; + default: + if (fnic_priv(sc)->flags & FNIC_IO_ABTS_ISSUED) + atomic64_inc(&abts_stats->abort_failures); + else + atomic64_inc( + &term_stats->terminate_failures); + break; + } + if (fnic_priv(sc)->state != FNIC_IOREQ_ABTS_PENDING) { + /* This is a late completion. Ignore it */ + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + return; + } + + fnic_priv(sc)->flags |= FNIC_IO_ABT_TERM_DONE; + fnic_priv(sc)->abts_status = hdr_status; + + /* If the status is IO not found consider it as success */ + if (hdr_status == FCPIO_IO_NOT_FOUND) + fnic_priv(sc)->abts_status = FCPIO_SUCCESS; + + if (!(fnic_priv(sc)->flags & (FNIC_IO_ABORTED | FNIC_IO_DONE))) + atomic64_inc(&misc_stats->no_icmnd_itmf_cmpls); + + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "abts cmpl recd. id %d status %s\n", + (int)(id & FNIC_TAG_MASK), + fnic_fcpio_status_to_str(hdr_status)); + + /* + * If scsi_eh thread is blocked waiting for abts to complete, + * signal completion to it. IO will be cleaned in the thread + * else clean it in this context + */ + if (io_req->abts_done) { + complete(io_req->abts_done); + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + fnic_printk(KERN_INFO, fnic, + "hwq: %d mqtag: 0x%x tag: 0x%x Waking up abort thread\n", + hwq, mqtag, tag); + } else { + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "hwq: %d mqtag: 0x%x tag: 0x%x hst: %s Completing IO\n", + hwq, mqtag, + tag, fnic_fcpio_status_to_str(hdr_status)); + fnic_priv(sc)->io_req = NULL; + sc->result = (DID_ERROR << 16); + fnic->sw_copy_wq[hwq].io_req_table[tag] = NULL; + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + + fnic_release_ioreq_buf(fnic, io_req, sc); + mempool_free(io_req, fnic->io_req_pool); + FNIC_TRACE(fnic_fcpio_itmf_cmpl_handler, + sc->device->host->host_no, id, + sc, + jiffies_to_msecs(jiffies - start_time), + desc, + (((u64)hdr_status << 40) | + (u64)sc->cmnd[0] << 32 | + (u64)sc->cmnd[2] << 24 | + (u64)sc->cmnd[3] << 16 | + (u64)sc->cmnd[4] << 8 | sc->cmnd[5]), + fnic_flags_and_state(sc)); + scsi_done(sc); + atomic64_dec(&fnic_stats->io_stats.active_ios); + if (atomic64_read(&fnic->io_cmpl_skip)) + atomic64_dec(&fnic->io_cmpl_skip); + else + atomic64_inc(&fnic_stats->io_stats.io_completions); + } + } else if (id & FNIC_TAG_DEV_RST) { + /* Completion of device reset */ + fnic_printk(KERN_INFO, fnic, + "hwq: %d mqtag: 0x%x tag: 0x%x DR hst: %s\n", + hwq, mqtag, + tag, fnic_fcpio_status_to_str(hdr_status)); + fnic_priv(sc)->lr_status = hdr_status; + if (fnic_priv(sc)->state == FNIC_IOREQ_ABTS_PENDING) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + fnic_priv(sc)->flags |= FNIC_DEV_RST_ABTS_PENDING; + FNIC_TRACE(fnic_fcpio_itmf_cmpl_handler, + sc->device->host->host_no, id, sc, + jiffies_to_msecs(jiffies - start_time), + desc, 0, fnic_flags_and_state(sc)); + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "hwq: %d mqtag: 0x%x tag: 0x%x hst: %s Terminate pending\n", + hwq, mqtag, + tag, fnic_fcpio_status_to_str(hdr_status)); + return; + } + if (fnic_priv(sc)->flags & FNIC_DEV_RST_TIMED_OUT) { + /* Need to wait for terminate completion */ + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + FNIC_TRACE(fnic_fcpio_itmf_cmpl_handler, + sc->device->host->host_no, id, sc, + jiffies_to_msecs(jiffies - start_time), + desc, 0, fnic_flags_and_state(sc)); + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "dev reset cmpl recd after time out. " + "id %d status %s\n", + (int)(id & FNIC_TAG_MASK), + fnic_fcpio_status_to_str(hdr_status)); + return; + } + fnic_priv(sc)->state = FNIC_IOREQ_CMD_COMPLETE; + fnic_priv(sc)->flags |= FNIC_DEV_RST_DONE; + FNIC_SCSI_DBG(KERN_INFO, fnic, + "hwq: %d mqtag: 0x%x tag: 0x%x hst: %s DR completion received\n", + hwq, mqtag, + tag, fnic_fcpio_status_to_str(hdr_status)); + if (io_req->dr_done) + complete(io_req->dr_done); + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + + } else { + fnic_printk(KERN_ERR, fnic, + "%s: Unexpected itmf io state: hwq: %d tag 0x%x %s\n", + __func__, hwq, id, fnic_ioreq_state_to_str(fnic_priv(sc)->state)); + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + } + +} + +/* + * fnic_fcpio_cmpl_handler + * Routine to service the cq for wq_copy + */ +static int fnic_fcpio_cmpl_handler(struct vnic_dev *vdev, + unsigned int cq_index, + struct fcpio_fw_req *desc) +{ + struct fnic *fnic = vnic_dev_priv(vdev); + + switch (desc->hdr.type) { + case FCPIO_ICMND_CMPL: /* fw completed a command */ + case FCPIO_ITMF_CMPL: /* fw completed itmf (abort cmd, lun reset)*/ + case FCPIO_FLOGI_REG_CMPL: /* fw completed flogi_reg */ + case FCPIO_FLOGI_FIP_REG_CMPL: /* fw completed flogi_fip_reg */ + case FCPIO_RESET_CMPL: /* fw completed reset */ + case FCPIO_NVME_ERSP_HW_CMPL: /* fw completed NVMe ERSP */ + atomic64_dec(&fnic->fnic_stats.fw_stats.active_fw_reqs); + break; + default: + break; + } + + cq_index -= fnic->copy_wq_base; + + switch (desc->hdr.type) { + case FCPIO_ACK: /* fw copied copy wq desc to its queue */ + fnic_fcpio_ack_handler(fnic, cq_index, desc); + break; + + case FCPIO_ICMND_CMPL: /* fw completed a command */ + if (IS_FNIC_FCP_INITIATOR(fnic)) + fnic_fcpio_icmnd_cmpl_handler(fnic, cq_index, desc); + else if (IS_FNIC_NVME_INITIATOR(fnic)) + nvfnic_fcpio_nvme_fast_cmpl_handler(fnic, desc); + break; + + case FCPIO_NVME_ERSP_HW_CMPL: /* fw completed NVMe ERSP */ + if (IS_FNIC_NVME_INITIATOR(fnic)) + nvfnic_fcpio_ersp_cmpl_handler(fnic, desc, 1); + break; + + case FCPIO_ITMF_CMPL: /* fw completed itmf (abort cmd, lun reset)*/ + if (IS_FNIC_FCP_INITIATOR(fnic)) + fnic_fcpio_itmf_cmpl_handler(fnic, cq_index, desc); + else if (IS_FNIC_NVME_INITIATOR(fnic)) + nvfnic_fcpio_nvme_itmf_cmpl_handler(fnic, desc); + break; + + case FCPIO_FLOGI_REG_CMPL: /* fw completed flogi_reg */ + case FCPIO_FLOGI_FIP_REG_CMPL: /* fw completed flogi_fip_reg */ + fnic_fcpio_flogi_reg_cmpl_handler(fnic, desc); + break; + + case FCPIO_RESET_CMPL: /* fw completed reset */ + fnic_fcpio_fw_reset_cmpl_handler(fnic, desc); + break; + + default: + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "firmware completion type %d\n", + desc->hdr.type); + break; + } + + return 0; +} + +/* + * fnic_wq_copy_cmpl_handler + * Routine to process wq copy + */ +int fnic_wq_copy_cmpl_handler(struct fnic *fnic, int copy_work_to_do, unsigned int cq_index) +{ + unsigned int cur_work_done; + struct misc_stats *misc_stats = &fnic->fnic_stats.misc_stats; + u64 start_jiffies = 0; + u64 end_jiffies = 0; + u64 delta_jiffies = 0; + u64 delta_ms = 0; + + start_jiffies = jiffies; + cur_work_done = vnic_cq_copy_service(&fnic->cq[cq_index], + fnic_fcpio_cmpl_handler, + copy_work_to_do); + end_jiffies = jiffies; + delta_jiffies = end_jiffies - start_jiffies; + if (delta_jiffies > (u64) atomic64_read(&misc_stats->max_isr_jiffies)) { + atomic64_set(&misc_stats->max_isr_jiffies, delta_jiffies); + delta_ms = jiffies_to_msecs(delta_jiffies); + atomic64_set(&misc_stats->max_isr_time_ms, delta_ms); + atomic64_set(&misc_stats->corr_work_done, cur_work_done); + } + + return cur_work_done; +} + +static bool fnic_cleanup_io_iter(struct scsi_cmnd *sc, void *data) +{ + struct request *const rq = scsi_cmd_to_rq(sc); + struct fnic *fnic = data; + struct fnic_io_req *io_req; + unsigned long start_time = 0; + unsigned long flags; + struct fnic_stats *fnic_stats = &fnic->fnic_stats; + uint16_t hwq = 0; + int tag; + int mqtag; + + mqtag = blk_mq_unique_tag(rq); + hwq = blk_mq_unique_tag_to_hwq(mqtag); + tag = blk_mq_unique_tag_to_tag(mqtag); + + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + + fnic->sw_copy_wq[hwq].io_req_table[tag] = NULL; + + io_req = fnic_priv(sc)->io_req; + if (!io_req) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + FNIC_SCSI_DBG(KERN_ERR, fnic, + "hwq: %d mqtag: 0x%x tag: 0x%x flags: 0x%x No ioreq. Returning\n", + hwq, mqtag, tag, fnic_priv(sc)->flags); + return true; + } + + if ((fnic_priv(sc)->flags & FNIC_DEVICE_RESET) && + !(fnic_priv(sc)->flags & FNIC_DEV_RST_DONE)) { + /* + * We will be here only when FW completes reset + * without sending completions for outstanding ios. + */ + fnic_priv(sc)->flags |= FNIC_DEV_RST_DONE; + if (io_req && io_req->dr_done) + complete(io_req->dr_done); + else if (io_req && io_req->abts_done) + complete(io_req->abts_done); + + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + return true; + } else if (fnic_priv(sc)->flags & FNIC_DEVICE_RESET) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + return true; + } + + fnic_priv(sc)->io_req = NULL; + io_req->sc = NULL; + start_time = io_req->start_time; + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + + /* + * If there is a scsi_cmnd associated with this io_req, then + * free the corresponding state + */ + fnic_release_ioreq_buf(fnic, io_req, sc); + mempool_free(io_req, fnic->io_req_pool); + + sc->result = DID_TRANSPORT_DISRUPTED << 16; + FNIC_SCSI_DBG(KERN_ERR, fnic, + "mqtag: 0x%x tag: 0x%x sc: 0x%p duration = %lu DID_TRANSPORT_DISRUPTED\n", + mqtag, tag, sc, (jiffies - start_time)); + + if (atomic64_read(&fnic->io_cmpl_skip)) + atomic64_dec(&fnic->io_cmpl_skip); + else + atomic64_inc(&fnic_stats->io_stats.io_completions); + + FNIC_TRACE(fnic_cleanup_io, + sc->device->host->host_no, tag, sc, + jiffies_to_msecs(jiffies - start_time), + 0, ((u64) sc->cmnd[0] << 32 | + (u64) sc->cmnd[2] << 24 | + (u64) sc->cmnd[3] << 16 | + (u64) sc->cmnd[4] << 8 | sc->cmnd[5]), + (((u64) fnic_priv(sc)->flags << 32) | fnic_priv(sc)-> + state)); + + /* Complete the command to SCSI */ + scsi_done(sc); + return true; +} + +static void fnic_cleanup_io(struct fnic *fnic, int exclude_id) +{ + unsigned int io_count = 0; + unsigned long flags; + struct fnic_io_req *io_req = NULL; + struct scsi_cmnd *sc = NULL; + + io_count = fnic_count_all_ioreqs(fnic); + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "Outstanding ioreq count: %d active io count: %lld Waiting\n", + io_count, + atomic64_read(&fnic->fnic_stats.io_stats.active_ios)); + + scsi_host_busy_iter(fnic->host, + fnic_cleanup_io_iter, fnic); + + /* with sg3utils device reset, SC needs to be retrieved from ioreq */ + spin_lock_irqsave(&fnic->wq_copy_lock[0], flags); + io_req = fnic->sw_copy_wq[0].io_req_table[fnic->fnic_max_tag_id]; + if (io_req) { + sc = io_req->sc; + if (sc) { + if ((fnic_priv(sc)->flags & FNIC_DEVICE_RESET) + && !(fnic_priv(sc)->flags & FNIC_DEV_RST_DONE)) { + fnic_priv(sc)->flags |= FNIC_DEV_RST_DONE; + if (io_req && io_req->dr_done) + complete(io_req->dr_done); + } + } + } + spin_unlock_irqrestore(&fnic->wq_copy_lock[0], flags); + + while ((io_count = fnic_count_all_ioreqs(fnic))) { + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "Outstanding ioreq count: %d active io count: %lld Waiting\n", + io_count, + atomic64_read(&fnic->fnic_stats.io_stats.active_ios)); + + schedule_timeout(msecs_to_jiffies(100)); + } +} + +void fnic_wq_copy_cleanup_handler(struct vnic_wq_copy *wq, + struct fcpio_host_req *desc) +{ + u32 id; + struct fnic *fnic = vnic_dev_priv(wq->vdev); + struct fnic_io_req *io_req; + struct scsi_cmnd *sc; + unsigned long flags; + unsigned long start_time = 0; + uint16_t hwq; + + /* + * Clean up outstanding NVMe requests if firmware reset did not + * complete them before WQ copy cleanup. + */ + if (IS_FNIC_NVME_INITIATOR(fnic)) { + nvfnic_cleanup_all_nvme_ios(fnic); + return; + } + + /* get the tag reference */ + fcpio_tag_id_dec(&desc->hdr.tag, &id); + id &= FNIC_TAG_MASK; + + if (id >= fnic->fnic_max_tag_id) + return; + + sc = scsi_host_find_tag(fnic->host, id); + if (!sc) + return; + + hwq = blk_mq_unique_tag_to_hwq(id); + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + + /* Get the IO context which this desc refers to */ + io_req = fnic_priv(sc)->io_req; + + /* fnic interrupts are turned off by now */ + + if (!io_req) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + goto wq_copy_cleanup_scsi_cmd; + } + + fnic_priv(sc)->io_req = NULL; + io_req->sc = NULL; + fnic->sw_copy_wq[hwq].io_req_table[blk_mq_unique_tag_to_tag(id)] = NULL; + + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + + start_time = io_req->start_time; + fnic_release_ioreq_buf(fnic, io_req, sc); + mempool_free(io_req, fnic->io_req_pool); + +wq_copy_cleanup_scsi_cmd: + sc->result = DID_NO_CONNECT << 16; + FNIC_SCSI_DBG(KERN_DEBUG, fnic, "wq_copy_cleanup_handler:" + " DID_NO_CONNECT\n"); + + FNIC_TRACE(fnic_wq_copy_cleanup_handler, + sc->device->host->host_no, id, sc, + jiffies_to_msecs(jiffies - start_time), + 0, ((u64)sc->cmnd[0] << 32 | + (u64)sc->cmnd[2] << 24 | (u64)sc->cmnd[3] << 16 | + (u64)sc->cmnd[4] << 8 | sc->cmnd[5]), + fnic_flags_and_state(sc)); + + scsi_done(sc); +} + +static inline int fnic_queue_abort_io_req(struct fnic *fnic, int tag, + u32 task_req, u8 *fc_lun, + struct fnic_io_req *io_req, + unsigned int hwq) +{ + struct vnic_wq_copy *wq = &fnic->hw_copy_wq[hwq]; + struct misc_stats *misc_stats = &fnic->fnic_stats.misc_stats; + unsigned long flags; + struct fnic_tport_s *tport = io_req->tport; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + if (unlikely(fnic_chk_state_flags_locked(fnic, + FNIC_FLAGS_IO_BLOCKED))) { + atomic_dec(&fnic->in_flight); + atomic_dec(&tport->in_flight); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return 1; + } else + atomic_inc(&fnic->in_flight); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + + if (vnic_wq_copy_desc_avail(wq) <= fnic->wq_copy_desc_low[hwq]) + free_wq_copy_descs(fnic, wq, hwq); + + if (!vnic_wq_copy_desc_avail(wq)) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + atomic_dec(&fnic->in_flight); + atomic_dec(&tport->in_flight); + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "fnic_queue_abort_io_req: failure: no descriptors\n"); + atomic64_inc(&misc_stats->abts_cpwq_alloc_failures); + return 1; + } + fnic_queue_wq_copy_desc_itmf(wq, tag | FNIC_TAG_ABORT, + 0, task_req, tag, fc_lun, io_req->port_id, + fnic->config.ra_tov, fnic->config.ed_tov); + + atomic64_inc(&fnic->fnic_stats.fw_stats.active_fw_reqs); + if (atomic64_read(&fnic->fnic_stats.fw_stats.active_fw_reqs) > + atomic64_read(&fnic->fnic_stats.fw_stats.max_fw_reqs)) + atomic64_set(&fnic->fnic_stats.fw_stats.max_fw_reqs, + atomic64_read(&fnic->fnic_stats.fw_stats.active_fw_reqs)); + + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + atomic_dec(&fnic->in_flight); + + return 0; +} + +struct fnic_rport_abort_io_iter_data { + struct fnic *fnic; + u32 port_id; + int term_cnt; +}; + +static bool fnic_rport_abort_io_iter(struct scsi_cmnd *sc, void *data) +{ + struct request *const rq = scsi_cmd_to_rq(sc); + struct fnic_rport_abort_io_iter_data *iter_data = data; + struct fnic *fnic = iter_data->fnic; + int abt_tag = 0; + struct fnic_io_req *io_req; + struct reset_stats *reset_stats = &fnic->fnic_stats.reset_stats; + struct terminate_stats *term_stats = &fnic->fnic_stats.term_stats; + struct scsi_lun fc_lun; + enum fnic_ioreq_state old_ioreq_state; + uint16_t hwq = 0; + unsigned long flags; + + abt_tag = blk_mq_unique_tag(rq); + hwq = blk_mq_unique_tag_to_hwq(abt_tag); + + if (!sc) { + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "sc is NULL abt_tag: 0x%x hwq: %d\n", abt_tag, hwq); + return true; + } + + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + io_req = fnic_priv(sc)->io_req; + if (!io_req || io_req->port_id != iter_data->port_id) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + return true; + } + + if ((fnic_priv(sc)->flags & FNIC_DEVICE_RESET) && + !(fnic_priv(sc)->flags & FNIC_DEV_RST_ISSUED)) { + FNIC_SCSI_DBG(KERN_ERR, fnic, + "hwq: %d abt_tag: 0x%x flags: 0x%x Device reset is not pending\n", + hwq, abt_tag, fnic_priv(sc)->flags); + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + return true; + } + + /* + * Found IO that is still pending with firmware and + * belongs to rport that went away + */ + if (fnic_priv(sc)->state == FNIC_IOREQ_ABTS_PENDING) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + return true; + } + + if (io_req->abts_done) { + fnic_printk(KERN_ERR, fnic, + "fnic_rport_exch_reset: io_req->abts_done is set state is %s\n", + fnic_ioreq_state_to_str(fnic_priv(sc)->state)); + } + + if (!(fnic_priv(sc)->flags & FNIC_IO_ISSUED)) { + fnic_printk(KERN_ERR, fnic, + "rport_exch_reset IO not yet issued %p abt_tag 0x%x", + sc, abt_tag); + fnic_printk(KERN_ERR, fnic, + "flags %x state %d\n", fnic_priv(sc)->flags, + fnic_priv(sc)->state); + } + old_ioreq_state = fnic_priv(sc)->state; + fnic_priv(sc)->state = FNIC_IOREQ_ABTS_PENDING; + fnic_priv(sc)->abts_status = FCPIO_INVALID_CODE; + + if (fnic_priv(sc)->flags & FNIC_DEVICE_RESET) { + atomic64_inc(&reset_stats->device_reset_terminates); + abt_tag |= FNIC_TAG_DEV_RST; + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "dev reset sc 0x%p\n", sc); + } + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "fnic_rport_exch_reset: dev rst sc 0x%p\n", sc); + WARN_ON_ONCE(io_req->abts_done); + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "fnic_rport_reset_exch: Issuing abts\n"); + + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + + /* Queue the abort command to firmware */ + int_to_scsilun(sc->device->lun, &fc_lun); + + if (fnic_queue_abort_io_req(fnic, abt_tag, + FCPIO_ITMF_ABT_TASK_TERM, + fc_lun.scsi_lun, io_req, hwq)) { + /* + * Revert the cmd state back to old state, if + * it hasn't changed in between. This cmd will get + * aborted later by scsi_eh, or cleaned up during + * lun reset + */ + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + FNIC_SCSI_DBG(KERN_ERR, fnic, + "hwq: %d abt_tag: 0x%x flags: 0x%x Queuing abort failed\n", + hwq, abt_tag, fnic_priv(sc)->flags); + if (fnic_priv(sc)->state == FNIC_IOREQ_ABTS_PENDING) + fnic_priv(sc)->state = old_ioreq_state; + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + } else { + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + if (fnic_priv(sc)->flags & FNIC_DEVICE_RESET) + fnic_priv(sc)->flags |= FNIC_DEV_RST_TERM_ISSUED; + else + fnic_priv(sc)->flags |= FNIC_IO_INTERNAL_TERM_ISSUED; + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + atomic64_inc(&term_stats->terminates); + iter_data->term_cnt++; + } + + return true; +} + +void fnic_rport_exch_reset(struct fnic *fnic, u32 port_id) +{ + unsigned int io_count = 0; + unsigned long flags; + struct terminate_stats *term_stats = &fnic->fnic_stats.term_stats; + struct fnic_rport_abort_io_iter_data iter_data = { + .fnic = fnic, + .port_id = port_id, + .term_cnt = 0, + }; + + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "fnic rport exchange reset for tport: 0x%06x\n", + port_id); + + if (fnic->in_remove) + return; + + io_count = fnic_count_ioreqs(fnic, port_id); + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "Starting terminates: rport:0x%x portid-io-count: %d active-io-count: %lld\n", + port_id, io_count, + atomic64_read(&fnic->fnic_stats.io_stats.active_ios)); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + /* Bump in_flight counter to hold off fnic_fw_reset_handler. */ + atomic_inc(&fnic->in_flight); + if (unlikely(fnic_chk_state_flags_locked(fnic, FNIC_FLAGS_IO_BLOCKED))) { + atomic_dec(&fnic->in_flight); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return; + } + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + scsi_host_busy_iter(fnic->host, fnic_rport_abort_io_iter, + &iter_data); + + if (iter_data.term_cnt > atomic64_read(&term_stats->max_terminates)) + atomic64_set(&term_stats->max_terminates, iter_data.term_cnt); + + atomic_dec(&fnic->in_flight); + + while ((io_count = fnic_count_ioreqs(fnic, port_id))) + schedule_timeout(msecs_to_jiffies(1000)); + + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "rport: 0x%x remaining portid-io-count: %d ", + port_id, io_count); +} + +void fnic_terminate_rport_io(struct fc_rport *rport) +{ + struct fnic_tport_s *tport; + struct rport_dd_data_s *rdd_data; + struct fnic_iport_s *iport = NULL; + struct fnic *fnic = NULL; + + if (!rport) { + pr_err("rport is NULL\n"); + return; + } + + rdd_data = rport->dd_data; + if (rdd_data) { + tport = rdd_data->tport; + if (!tport) { + pr_err( + "term rport io called after tport is deleted. Returning 0x%8x\n", + rport->port_id); + } else { + pr_err( + "term rport io called after tport is set 0x%8x\n", + rport->port_id); + pr_err( + "tport maybe rediscovered\n"); + + iport = (struct fnic_iport_s *) tport->iport; + fnic = iport->fnic; + fnic_rport_exch_reset(fnic, rport->port_id); + } + } +} + +/* + * FCP-SCSI specific handling for module unload + * + */ +void fnic_scsi_unload(struct fnic *fnic) +{ + unsigned long flags; + + /* + * Mark state so that the workqueue thread stops forwarding + * received frames and link events to the local port. ISR and + * other threads that can queue work items will also stop + * creating work items on the fnic workqueue + */ + spin_lock_irqsave(&fnic->fnic_lock, flags); + fnic->iport.state = FNIC_IPORT_STATE_LINK_WAIT; + fnic->in_remove = 1; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + fnic_fcpio_reset(fnic); + + fnic_flush_tport_event_list(fnic); + fnic_delete_fcp_tports(fnic); +} + +void fnic_scsi_unload_cleanup(struct fnic *fnic) +{ + int hwq = 0; + + fc_remove_host(fnic->host); + scsi_remove_host(fnic->host); + for (hwq = 0; hwq < fnic->wq_copy_count; hwq++) + kfree(fnic->sw_copy_wq[hwq].io_req_table); +} + +/* + * This function is exported to SCSI for sending abort cmnds. + * A SCSI IO is represented by a io_req in the driver. + * The ioreq is linked to the SCSI Cmd, thus a link with the ULP's IO. + */ +int fnic_abort_cmd(struct scsi_cmnd *sc) +{ + struct request *const rq = scsi_cmd_to_rq(sc); + struct fnic_iport_s *iport; + struct fnic_tport_s *tport; + struct fnic *fnic; + struct fnic_io_req *io_req = NULL; + struct fc_rport *rport; + struct rport_dd_data_s *rdd_data; + unsigned long flags; + unsigned long start_time = 0; + int ret = SUCCESS; + u32 task_req = 0; + struct scsi_lun fc_lun; + struct fnic_stats *fnic_stats; + struct abort_stats *abts_stats; + struct terminate_stats *term_stats; + enum fnic_ioreq_state old_ioreq_state; + int mqtag; + unsigned long abt_issued_time; + uint16_t hwq = 0; + + DECLARE_COMPLETION_ONSTACK(tm_done); + + /* Wait for rport to unblock */ + fc_block_scsi_eh(sc); + + /* Get local-port, check ready and link up */ + fnic = *((struct fnic **) shost_priv(sc->device->host)); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + iport = &fnic->iport; + + fnic_stats = &fnic->fnic_stats; + abts_stats = &fnic->fnic_stats.abts_stats; + term_stats = &fnic->fnic_stats.term_stats; + + rport = starget_to_rport(scsi_target(sc->device)); + mqtag = blk_mq_unique_tag(rq); + hwq = blk_mq_unique_tag_to_hwq(mqtag); + + fnic_priv(sc)->flags = FNIC_NO_FLAGS; + + rdd_data = rport->dd_data; + tport = rdd_data->tport; + + if (!tport) { + FNIC_SCSI_DBG(KERN_ERR, fnic, + "Abort cmd called after tport delete! rport fcid: 0x%x", + rport->port_id); + FNIC_SCSI_DBG(KERN_ERR, fnic, + "lun: %llu hwq: 0x%x mqtag: 0x%x Op: 0x%x flags: 0x%x\n", + sc->device->lun, hwq, mqtag, + sc->cmnd[0], fnic_priv(sc)->flags); + ret = FAILED; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + goto fnic_abort_cmd_end; + } + + FNIC_SCSI_DBG(KERN_INFO, fnic, + "Abort cmd called rport fcid: 0x%x lun: %llu hwq: 0x%x mqtag: 0x%x", + rport->port_id, sc->device->lun, hwq, mqtag); + + FNIC_SCSI_DBG(KERN_INFO, fnic, + "Op: 0x%x flags: 0x%x\n", + sc->cmnd[0], + fnic_priv(sc)->flags); + + if (iport->state != FNIC_IPORT_STATE_READY) { + atomic64_inc(&fnic_stats->misc_stats.iport_not_ready); + FNIC_SCSI_DBG(KERN_INFO, fnic, + "iport NOT in READY state"); + ret = FAILED; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + goto fnic_abort_cmd_end; + } + + if ((tport->state != FDLS_TGT_STATE_READY) && + (tport->state != FDLS_TGT_STATE_ADISC)) { + FNIC_SCSI_DBG(KERN_ERR, fnic, + "tport state: %d\n", tport->state); + ret = FAILED; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + goto fnic_abort_cmd_end; + } + + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + /* + * Avoid a race between SCSI issuing the abort and the device + * completing the command. + * + * If the command is already completed by the fw cmpl code, + * we just return SUCCESS from here. This means that the abort + * succeeded. In the SCSI ML, since the timeout for command has + * happened, the completion wont actually complete the command + * and it will be considered as an aborted command + * + * .io_req will not be cleared except while holding io_req_lock. + */ + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + io_req = fnic_priv(sc)->io_req; + if (!io_req) { + ret = FAILED; + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + goto fnic_abort_cmd_end; + } + + io_req->abts_done = &tm_done; + + if (fnic_priv(sc)->state == FNIC_IOREQ_ABTS_PENDING) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + goto wait_pending; + } + + abt_issued_time = jiffies_to_msecs(jiffies) - jiffies_to_msecs(io_req->start_time); + if (abt_issued_time <= 6000) + atomic64_inc(&abts_stats->abort_issued_btw_0_to_6_sec); + else if (abt_issued_time > 6000 && abt_issued_time <= 20000) + atomic64_inc(&abts_stats->abort_issued_btw_6_to_20_sec); + else if (abt_issued_time > 20000 && abt_issued_time <= 30000) + atomic64_inc(&abts_stats->abort_issued_btw_20_to_30_sec); + else if (abt_issued_time > 30000 && abt_issued_time <= 40000) + atomic64_inc(&abts_stats->abort_issued_btw_30_to_40_sec); + else if (abt_issued_time > 40000 && abt_issued_time <= 50000) + atomic64_inc(&abts_stats->abort_issued_btw_40_to_50_sec); + else if (abt_issued_time > 50000 && abt_issued_time <= 60000) + atomic64_inc(&abts_stats->abort_issued_btw_50_to_60_sec); + else + atomic64_inc(&abts_stats->abort_issued_greater_than_60_sec); + + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "CDB Opcode: 0x%02x Abort issued time: %lu msec\n", + sc->cmnd[0], abt_issued_time); + /* + * Command is still pending, need to abort it + * If the firmware completes the command after this point, + * the completion wont be done till mid-layer, since abort + * has already started. + */ + old_ioreq_state = fnic_priv(sc)->state; + fnic_priv(sc)->state = FNIC_IOREQ_ABTS_PENDING; + fnic_priv(sc)->abts_status = FCPIO_INVALID_CODE; + + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + + /* + * Check readiness of the remote port. If the path to remote + * port is up, then send abts to the remote port to terminate + * the IO. Else, just locally terminate the IO in the firmware + */ + if (fc_remote_port_chkready(rport) == 0) + task_req = FCPIO_ITMF_ABT_TASK; + else { + atomic64_inc(&fnic_stats->misc_stats.tport_not_ready); + task_req = FCPIO_ITMF_ABT_TASK_TERM; + } + + /* Now queue the abort command to firmware */ + int_to_scsilun(sc->device->lun, &fc_lun); + + if (fnic_queue_abort_io_req(fnic, mqtag, task_req, fc_lun.scsi_lun, + io_req, hwq)) { + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + if (fnic_priv(sc)->state == FNIC_IOREQ_ABTS_PENDING) + fnic_priv(sc)->state = old_ioreq_state; + io_req = fnic_priv(sc)->io_req; + if (io_req) + io_req->abts_done = NULL; + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + ret = FAILED; + goto fnic_abort_cmd_end; + } + if (task_req == FCPIO_ITMF_ABT_TASK) { + fnic_priv(sc)->flags |= FNIC_IO_ABTS_ISSUED; + atomic64_inc(&fnic_stats->abts_stats.aborts); + } else { + fnic_priv(sc)->flags |= FNIC_IO_TERM_ISSUED; + atomic64_inc(&fnic_stats->term_stats.terminates); + } + + /* + * We queued an abort IO, wait for its completion. + * Once the firmware completes the abort command, it will + * wake up this thread. + */ + wait_pending: + wait_for_completion_timeout(&tm_done, + msecs_to_jiffies + (2 * fnic->config.ra_tov + + fnic->config.ed_tov)); + + /* Check the abort status */ + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + + io_req = fnic_priv(sc)->io_req; + if (!io_req) { + atomic64_inc(&fnic_stats->io_stats.ioreq_null); + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + fnic_priv(sc)->flags |= FNIC_IO_ABT_TERM_REQ_NULL; + ret = FAILED; + goto fnic_abort_cmd_end; + } + io_req->abts_done = NULL; + + /* fw did not complete abort, timed out */ + if (fnic_priv(sc)->abts_status == FCPIO_INVALID_CODE) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + if (task_req == FCPIO_ITMF_ABT_TASK) { + atomic64_inc(&abts_stats->abort_drv_timeouts); + } else { + atomic64_inc(&term_stats->terminate_drv_timeouts); + } + fnic_priv(sc)->flags |= FNIC_IO_ABT_TERM_TIMED_OUT; + ret = FAILED; + goto fnic_abort_cmd_end; + } + + /* IO out of order */ + + if (!(fnic_priv(sc)->flags & (FNIC_IO_ABORTED | FNIC_IO_DONE))) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + FNIC_SCSI_DBG(KERN_ERR, fnic, + "Issuing host reset due to out of order IO\n"); + + ret = FAILED; + goto fnic_abort_cmd_end; + } + + fnic_priv(sc)->state = FNIC_IOREQ_ABTS_COMPLETE; + + start_time = io_req->start_time; + /* + * firmware completed the abort, check the status, + * free the io_req if successful. If abort fails, + * Device reset will clean the I/O. + */ + if (fnic_priv(sc)->abts_status == FCPIO_SUCCESS || + (fnic_priv(sc)->abts_status == FCPIO_ABORTED)) { + fnic_priv(sc)->io_req = NULL; + io_req->sc = NULL; + } else { + ret = FAILED; + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + goto fnic_abort_cmd_end; + } + + fnic->sw_copy_wq[hwq].io_req_table[blk_mq_unique_tag_to_tag(mqtag)] = NULL; + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + + fnic_release_ioreq_buf(fnic, io_req, sc); + mempool_free(io_req, fnic->io_req_pool); + + /* Call SCSI completion function to complete the IO */ + sc->result = DID_ABORT << 16; + scsi_done(sc); + atomic64_dec(&fnic_stats->io_stats.active_ios); + if (atomic64_read(&fnic->io_cmpl_skip)) + atomic64_dec(&fnic->io_cmpl_skip); + else + atomic64_inc(&fnic_stats->io_stats.io_completions); + +fnic_abort_cmd_end: + FNIC_TRACE(fnic_abort_cmd, sc->device->host->host_no, mqtag, sc, + jiffies_to_msecs(jiffies - start_time), + 0, ((u64)sc->cmnd[0] << 32 | + (u64)sc->cmnd[2] << 24 | (u64)sc->cmnd[3] << 16 | + (u64)sc->cmnd[4] << 8 | sc->cmnd[5]), + fnic_flags_and_state(sc)); + + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "Returning from abort cmd type %x %s\n", task_req, + (ret == SUCCESS) ? + "SUCCESS" : "FAILED"); + return ret; +} + +static inline int fnic_queue_dr_io_req(struct fnic *fnic, + struct scsi_cmnd *sc, + struct fnic_io_req *io_req) +{ + struct vnic_wq_copy *wq; + struct misc_stats *misc_stats = &fnic->fnic_stats.misc_stats; + struct scsi_lun fc_lun; + int ret = 0; + unsigned long flags; + uint16_t hwq = 0; + uint32_t tag = 0; + struct fnic_tport_s *tport = io_req->tport; + + tag = io_req->tag; + hwq = blk_mq_unique_tag_to_hwq(tag); + wq = &fnic->hw_copy_wq[hwq]; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + if (unlikely(fnic_chk_state_flags_locked(fnic, + FNIC_FLAGS_IO_BLOCKED))) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return FAILED; + } else { + atomic_inc(&fnic->in_flight); + atomic_inc(&tport->in_flight); + } + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + + if (vnic_wq_copy_desc_avail(wq) <= fnic->wq_copy_desc_low[hwq]) + free_wq_copy_descs(fnic, wq, hwq); + + if (!vnic_wq_copy_desc_avail(wq)) { + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "queue_dr_io_req failure - no descriptors\n"); + atomic64_inc(&misc_stats->devrst_cpwq_alloc_failures); + ret = -EAGAIN; + goto lr_io_req_end; + } + + /* fill in the lun info */ + int_to_scsilun(sc->device->lun, &fc_lun); + + tag |= FNIC_TAG_DEV_RST; + fnic_queue_wq_copy_desc_itmf(wq, tag, + 0, FCPIO_ITMF_LUN_RESET, SCSI_NO_TAG, + fc_lun.scsi_lun, io_req->port_id, + fnic->config.ra_tov, fnic->config.ed_tov); + + atomic64_inc(&fnic->fnic_stats.fw_stats.active_fw_reqs); + if (atomic64_read(&fnic->fnic_stats.fw_stats.active_fw_reqs) > + atomic64_read(&fnic->fnic_stats.fw_stats.max_fw_reqs)) + atomic64_set(&fnic->fnic_stats.fw_stats.max_fw_reqs, + atomic64_read(&fnic->fnic_stats.fw_stats.active_fw_reqs)); + +lr_io_req_end: + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + atomic_dec(&fnic->in_flight); + atomic_dec(&tport->in_flight); + + return ret; +} + +struct fnic_pending_aborts_iter_data { + struct fnic *fnic; + struct scsi_cmnd *lr_sc; + struct scsi_device *lun_dev; + int ret; +}; + +static bool fnic_pending_aborts_iter(struct scsi_cmnd *sc, void *data) +{ + struct request *const rq = scsi_cmd_to_rq(sc); + struct fnic_pending_aborts_iter_data *iter_data = data; + struct fnic *fnic = iter_data->fnic; + struct scsi_device *lun_dev = iter_data->lun_dev; + unsigned long abt_tag = 0; + uint16_t hwq = 0; + struct fnic_io_req *io_req; + unsigned long flags; + struct scsi_lun fc_lun; + DECLARE_COMPLETION_ONSTACK(tm_done); + enum fnic_ioreq_state old_ioreq_state; + + if (sc == iter_data->lr_sc || sc->device != lun_dev) + return true; + + abt_tag = blk_mq_unique_tag(rq); + hwq = blk_mq_unique_tag_to_hwq(abt_tag); + + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + io_req = fnic_priv(sc)->io_req; + if (!io_req) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + return true; + } + + /* + * Found IO that is still pending with firmware and + * belongs to the LUN that we are resetting + */ + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "Found IO in %s on lun\n", + fnic_ioreq_state_to_str(fnic_priv(sc)->state)); + + if (fnic_priv(sc)->state == FNIC_IOREQ_ABTS_PENDING) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + return true; + } + if ((fnic_priv(sc)->flags & FNIC_DEVICE_RESET) && + (!(fnic_priv(sc)->flags & FNIC_DEV_RST_ISSUED))) { + FNIC_SCSI_DBG(KERN_INFO, fnic, + "dev rst not pending sc 0x%p\n", sc); + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + return true; + } + + if (io_req->abts_done) + fnic_printk(KERN_ERR, fnic, + "%s: io_req->abts_done is set state is %s\n", + __func__, fnic_ioreq_state_to_str(fnic_priv(sc)->state)); + old_ioreq_state = fnic_priv(sc)->state; + /* + * Any pending IO issued prior to reset is expected to be + * in abts pending state, if not we need to set + * FNIC_IOREQ_ABTS_PENDING to indicate the IO is abort pending. + * When IO is completed, the IO will be handed over and + * handled in this function. + */ + fnic_priv(sc)->state = FNIC_IOREQ_ABTS_PENDING; + + BUG_ON(io_req->abts_done); + + if (fnic_priv(sc)->flags & FNIC_DEVICE_RESET) { + FNIC_SCSI_DBG(KERN_INFO, fnic, + "dev rst sc 0x%p\n", sc); + } + + fnic_priv(sc)->abts_status = FCPIO_INVALID_CODE; + io_req->abts_done = &tm_done; + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + + /* Now queue the abort command to firmware */ + int_to_scsilun(sc->device->lun, &fc_lun); + + if (fnic_queue_abort_io_req(fnic, abt_tag, + FCPIO_ITMF_ABT_TASK_TERM, + fc_lun.scsi_lun, io_req, hwq)) { + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + io_req = fnic_priv(sc)->io_req; + if (io_req) + io_req->abts_done = NULL; + if (fnic_priv(sc)->state == FNIC_IOREQ_ABTS_PENDING) + fnic_priv(sc)->state = old_ioreq_state; + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + iter_data->ret = FAILED; + FNIC_SCSI_DBG(KERN_ERR, fnic, + "hwq: %d abt_tag: 0x%lx Abort could not be queued\n", + hwq, abt_tag); + return false; + } else { + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + if (fnic_priv(sc)->flags & FNIC_DEVICE_RESET) + fnic_priv(sc)->flags |= FNIC_DEV_RST_TERM_ISSUED; + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + } + fnic_priv(sc)->flags |= FNIC_IO_INTERNAL_TERM_ISSUED; + + wait_for_completion_timeout(&tm_done, msecs_to_jiffies + (fnic->config.ed_tov)); + + /* Recheck cmd state to check if it is now aborted */ + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + io_req = fnic_priv(sc)->io_req; + if (!io_req) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + fnic_priv(sc)->flags |= FNIC_IO_ABT_TERM_REQ_NULL; + return true; + } + + io_req->abts_done = NULL; + + /* if abort is still pending with fw, fail */ + if (fnic_priv(sc)->abts_status == FCPIO_INVALID_CODE) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + fnic_priv(sc)->flags |= FNIC_IO_ABT_TERM_DONE; + iter_data->ret = FAILED; + return false; + } + fnic_priv(sc)->state = FNIC_IOREQ_ABTS_COMPLETE; + + /* original sc used for lr is handled by dev reset code */ + if (sc != iter_data->lr_sc) { + fnic_priv(sc)->io_req = NULL; + fnic->sw_copy_wq[hwq].io_req_table[blk_mq_unique_tag_to_tag(abt_tag)] = NULL; + } + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + + /* original sc used for lr is handled by dev reset code */ + if (sc != iter_data->lr_sc) { + fnic_release_ioreq_buf(fnic, io_req, sc); + mempool_free(io_req, fnic->io_req_pool); + } + + /* + * Any IO is returned during reset, it needs to call scsi_done + * to return the scsi_cmnd to upper layer. + */ + /* Set result to let upper SCSI layer retry */ + sc->result = DID_RESET << 16; + scsi_done(sc); + + return true; +} + +/* + * Clean up any pending aborts on the lun + * For each outstanding IO on this lun, whose abort is not completed by fw, + * issue a local abort. Wait for abort to complete. Return 0 if all commands + * successfully aborted, 1 otherwise + */ +static int fnic_clean_pending_aborts(struct fnic *fnic, + struct scsi_cmnd *lr_sc, + bool new_sc) + +{ + int ret = 0; + struct fnic_pending_aborts_iter_data iter_data = { + .fnic = fnic, + .lun_dev = lr_sc->device, + .ret = SUCCESS, + }; + + iter_data.lr_sc = lr_sc; + + scsi_host_busy_iter(fnic->host, + fnic_pending_aborts_iter, &iter_data); + if (iter_data.ret == FAILED) { + ret = iter_data.ret; + goto clean_pending_aborts_end; + } + schedule_timeout(msecs_to_jiffies(2 * fnic->config.ed_tov)); + + /* walk again to check, if IOs are still pending in fw */ + if (fnic_is_abts_pending(fnic, lr_sc)) + ret = 1; + +clean_pending_aborts_end: + FNIC_SCSI_DBG(KERN_INFO, fnic, + "exit status: %d\n", ret); + return ret; +} + +/* + * SCSI Eh thread issues a Lun Reset when one or more commands on a LUN + * fail to get aborted. It calls driver's eh_device_reset with a SCSI command + * on the LUN. + */ +int fnic_device_reset(struct scsi_cmnd *sc) +{ + struct request *rq = scsi_cmd_to_rq(sc); + struct fnic *fnic; + struct fnic_io_req *io_req = NULL; + struct fc_rport *rport; + int status; + int count = 0; + int ret = FAILED; + unsigned long flags; + unsigned long start_time = 0; + struct scsi_lun fc_lun; + struct fnic_stats *fnic_stats; + struct reset_stats *reset_stats; + int mqtag = rq->tag; + DECLARE_COMPLETION_ONSTACK(tm_done); + bool new_sc = 0; + uint16_t hwq = 0; + struct fnic_iport_s *iport = NULL; + struct rport_dd_data_s *rdd_data; + struct fnic_tport_s *tport; + u32 old_soft_reset_count; + u32 old_link_down_cnt; + int exit_dr = 0; + + /* Wait for rport to unblock */ + fc_block_scsi_eh(sc); + + /* Get local-port, check ready and link up */ + fnic = *((struct fnic **) shost_priv(sc->device->host)); + iport = &fnic->iport; + + fnic_stats = &fnic->fnic_stats; + reset_stats = &fnic_stats->reset_stats; + + atomic64_inc(&reset_stats->device_resets); + + rport = starget_to_rport(scsi_target(sc->device)); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "fcid: 0x%x lun: %llu hwq: %d mqtag: 0x%x flags: 0x%x Device reset\n", + rport->port_id, sc->device->lun, hwq, mqtag, + fnic_priv(sc)->flags); + + rdd_data = rport->dd_data; + tport = rdd_data->tport; + if (!tport) { + FNIC_SCSI_DBG(KERN_ERR, fnic, + "Dev rst called after tport delete! rport fcid: 0x%x lun: %llu\n", + rport->port_id, sc->device->lun); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + goto fnic_device_reset_end; + } + + if (iport->state != FNIC_IPORT_STATE_READY) { + atomic64_inc(&fnic_stats->misc_stats.iport_not_ready); + FNIC_SCSI_DBG(KERN_INFO, fnic, + "iport NOT in READY state"); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + goto fnic_device_reset_end; + } + + if ((tport->state != FDLS_TGT_STATE_READY) && + (tport->state != FDLS_TGT_STATE_ADISC)) { + FNIC_SCSI_DBG(KERN_ERR, fnic, + "tport state: %d\n", tport->state); + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + goto fnic_device_reset_end; + } + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + /* Check if remote port up */ + if (fc_remote_port_chkready(rport)) { + atomic64_inc(&fnic_stats->misc_stats.tport_not_ready); + goto fnic_device_reset_end; + } + + fnic_priv(sc)->flags = FNIC_DEVICE_RESET; + + if (unlikely(mqtag < 0)) { + /* + * For device reset issued through sg3utils, we let + * only one LUN_RESET to go through and use a special + * tag equal to max_tag_id so that we don't have to allocate + * or free it. It won't interact with tags + * allocated by mid layer. + */ + mutex_lock(&fnic->sgreset_mutex); + mqtag = fnic->fnic_max_tag_id; + new_sc = 1; + } else { + mqtag = blk_mq_unique_tag(rq); + hwq = blk_mq_unique_tag_to_hwq(mqtag); + } + + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + io_req = fnic_priv(sc)->io_req; + + /* + * If there is a io_req attached to this command, then use it, + * else allocate a new one. + */ + if (!io_req) { + io_req = mempool_alloc(fnic->io_req_pool, GFP_ATOMIC); + if (!io_req) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + goto fnic_device_reset_end; + } + memset(io_req, 0, sizeof(*io_req)); + io_req->port_id = rport->port_id; + io_req->tag = mqtag; + fnic_priv(sc)->io_req = io_req; + io_req->tport = tport; + io_req->sc = sc; + + if (fnic->sw_copy_wq[hwq].io_req_table[blk_mq_unique_tag_to_tag(mqtag)] != NULL) + WARN(1, "fnic<%d>: %s: tag 0x%x already exists\n", + fnic->fnic_num, __func__, blk_mq_unique_tag_to_tag(mqtag)); + + fnic->sw_copy_wq[hwq].io_req_table[blk_mq_unique_tag_to_tag(mqtag)] = + io_req; + } + io_req->dr_done = &tm_done; + fnic_priv(sc)->state = FNIC_IOREQ_CMD_PENDING; + fnic_priv(sc)->lr_status = FCPIO_INVALID_CODE; + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + + FNIC_SCSI_DBG(KERN_DEBUG, fnic, "TAG %x\n", mqtag); + + /* + * issue the device reset, if enqueue failed, clean up the ioreq + * and break assoc with scsi cmd + */ + if (fnic_queue_dr_io_req(fnic, sc, io_req)) { + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + io_req = fnic_priv(sc)->io_req; + if (io_req) + io_req->dr_done = NULL; + goto fnic_device_reset_clean; + } + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + fnic_priv(sc)->flags |= FNIC_DEV_RST_ISSUED; + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + old_link_down_cnt = iport->fnic->link_down_cnt; + old_soft_reset_count = fnic->soft_reset_count; + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + /* + * Wait on the local completion for LUN reset. The io_req may be + * freed while we wait since we hold no lock. + */ + wait_for_completion_timeout(&tm_done, + msecs_to_jiffies(FNIC_LUN_RESET_TIMEOUT)); + + /* + * Wake up can be due to the following reasons: + * 1) The device reset completed from target. + * 2) Device reset timed out. + * 3) A link-down/host_reset may have happened in between. + * 4) The device reset was aborted and io_req->dr_done was called. + */ + + exit_dr = 0; + spin_lock_irqsave(&fnic->fnic_lock, flags); + if ((old_link_down_cnt != fnic->link_down_cnt) || + (fnic->reset_in_progress) || + (fnic->soft_reset_count != old_soft_reset_count) || + (iport->state != FNIC_IPORT_STATE_READY)) + exit_dr = 1; + + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + io_req = fnic_priv(sc)->io_req; + if (!io_req) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "io_req is null mqtag 0x%x sc 0x%p\n", mqtag, sc); + goto fnic_device_reset_end; + } + + if (exit_dr) { + FNIC_SCSI_DBG(KERN_ERR, fnic, + "Host reset called for fnic. Exit device reset\n"); + io_req->dr_done = NULL; + goto fnic_device_reset_clean; + } + io_req->dr_done = NULL; + + status = fnic_priv(sc)->lr_status; + + /* + * If lun reset not completed, bail out with failed. io_req + * gets cleaned up during higher levels of EH + */ + if (status == FCPIO_INVALID_CODE) { + atomic64_inc(&reset_stats->device_reset_timeouts); + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "Device reset timed out\n"); + fnic_priv(sc)->flags |= FNIC_DEV_RST_TIMED_OUT; + int_to_scsilun(sc->device->lun, &fc_lun); + goto fnic_device_reset_clean; + } else { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + } + + /* Completed, but not successful, clean up the io_req, return fail */ + if (status != FCPIO_SUCCESS) { + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "Device reset completed - failed\n"); + io_req = fnic_priv(sc)->io_req; + goto fnic_device_reset_clean; + } + + /* + * Clean up any aborts on this lun that have still not + * completed. If any of these fail, then LUN reset fails. + * clean_pending_aborts cleans all cmds on this lun except + * the lun reset cmd. If all cmds get cleaned, the lun reset + * succeeds + */ + if (fnic_clean_pending_aborts(fnic, sc, new_sc)) { + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + io_req = fnic_priv(sc)->io_req; + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "Device reset failed: Cannot abort all IOs\n"); + goto fnic_device_reset_clean; + } + + /* Clean lun reset command */ + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + io_req = fnic_priv(sc)->io_req; + if (io_req) + /* Completed, and successful */ + ret = SUCCESS; + +fnic_device_reset_clean: + if (io_req) { + fnic_priv(sc)->io_req = NULL; + io_req->sc = NULL; + fnic->sw_copy_wq[hwq].io_req_table[blk_mq_unique_tag_to_tag(io_req->tag)] = NULL; + } + + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + + if (io_req) { + start_time = io_req->start_time; + fnic_release_ioreq_buf(fnic, io_req, sc); + mempool_free(io_req, fnic->io_req_pool); + } + + /* + * If link-event is seen while LUN reset is issued we need + * to complete the LUN reset here + */ + if (!new_sc) { + sc->result = DID_RESET << 16; + scsi_done(sc); + } + +fnic_device_reset_end: + FNIC_TRACE(fnic_device_reset, sc->device->host->host_no, rq->tag, sc, + jiffies_to_msecs(jiffies - start_time), + 0, ((u64)sc->cmnd[0] << 32 | + (u64)sc->cmnd[2] << 24 | (u64)sc->cmnd[3] << 16 | + (u64)sc->cmnd[4] << 8 | sc->cmnd[5]), + fnic_flags_and_state(sc)); + + if (new_sc) { + fnic->sgreset_sc = NULL; + mutex_unlock(&fnic->sgreset_mutex); + } + + while ((ret == SUCCESS) && fnic_count_lun_ioreqs(fnic, sc->device)) { + if (count >= 2) { + ret = FAILED; + break; + } + FNIC_SCSI_DBG(KERN_ERR, fnic, + "Cannot clean up all IOs for the LUN\n"); + schedule_timeout(msecs_to_jiffies(1000)); + count++; + } + + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "Returning from device reset %s\n", + (ret == SUCCESS) ? + "SUCCESS" : "FAILED"); + + if (ret == FAILED) + atomic64_inc(&reset_stats->device_reset_failures); + + return ret; +} + +static void fnic_post_flogo_linkflap(struct fnic *fnic) +{ + unsigned long flags; + + fnic_fdls_link_status_change(fnic, 0); + spin_lock_irqsave(&fnic->fnic_lock, flags); + + if (fnic->link_status) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + fnic_fdls_link_status_change(fnic, 1); + return; + } + spin_unlock_irqrestore(&fnic->fnic_lock, flags); +} + +/* Logout from all the targets and simulate link flap */ +void fnic_reset(struct Scsi_Host *shost) +{ + struct fnic *fnic; + struct reset_stats *reset_stats; + + fnic = *((struct fnic **) shost_priv(shost)); + reset_stats = &fnic->fnic_stats.reset_stats; + + FNIC_SCSI_DBG(KERN_INFO, fnic, + "Issuing fnic reset\n"); + + atomic64_inc(&reset_stats->fnic_resets); + fnic_post_flogo_linkflap(fnic); + + FNIC_SCSI_DBG(KERN_INFO, fnic, + "Returning from fnic reset"); + + atomic64_inc(&reset_stats->fnic_reset_completions); +} + +int fnic_issue_fc_host_lip(struct Scsi_Host *shost) +{ + int ret = 0; + struct fnic *fnic = *((struct fnic **) shost_priv(shost)); + + FNIC_SCSI_DBG(KERN_INFO, fnic, + "FC host lip issued"); + + ret = fnic_host_reset(shost); + return ret; +} + +int fnic_host_reset(struct Scsi_Host *shost) +{ + int ret = SUCCESS; + unsigned long wait_host_tmo; + struct fnic *fnic = *((struct fnic **) shost_priv(shost)); + unsigned long flags; + struct fnic_iport_s *iport = &fnic->iport; + + spin_lock_irqsave(&fnic->fnic_lock, flags); + if (fnic->reset_in_progress == NOT_IN_PROGRESS) { + fnic->reset_in_progress = IN_PROGRESS; + } else { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + wait_for_completion_timeout(&fnic->reset_completion_wait, + msecs_to_jiffies(10000)); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + if (fnic->reset_in_progress == IN_PROGRESS) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + FNIC_SCSI_DBG(KERN_WARNING, fnic, + "Firmware reset in progress. Skipping another host reset\n"); + return SUCCESS; + } + fnic->reset_in_progress = IN_PROGRESS; + } + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + /* + * If fnic_reset is successful, wait for fabric login to complete + * scsi-ml tries to send a TUR to every device if host reset is + * successful, so before returning to scsi, fabric should be up + */ + fnic_reset(shost); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + fnic->reset_in_progress = NOT_IN_PROGRESS; + complete(&fnic->reset_completion_wait); + fnic->soft_reset_count++; + + /* wait till the link is up */ + if (fnic->link_status) { + wait_host_tmo = jiffies + FNIC_HOST_RESET_SETTLE_TIME * HZ; + ret = FAILED; + while (time_before(jiffies, wait_host_tmo)) { + if (iport->state != FNIC_IPORT_STATE_READY + && fnic->link_status) { + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + ssleep(1); + spin_lock_irqsave(&fnic->fnic_lock, flags); + } else { + ret = SUCCESS; + break; + } + } + } + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + + FNIC_SCSI_DBG(KERN_INFO, fnic, + "host reset return status: %d\n", ret); + return ret; +} + +static bool fnic_abts_pending_iter(struct scsi_cmnd *sc, void *data) +{ + struct request *const rq = scsi_cmd_to_rq(sc); + struct fnic_pending_aborts_iter_data *iter_data = data; + struct fnic *fnic = iter_data->fnic; + int cmd_state; + struct fnic_io_req *io_req; + unsigned long flags; + uint16_t hwq = 0; + int tag; + + tag = blk_mq_unique_tag(rq); + hwq = blk_mq_unique_tag_to_hwq(tag); + + /* + * ignore this lun reset cmd or cmds that do not belong to + * this lun + */ + if (iter_data->lr_sc && sc == iter_data->lr_sc) + return true; + if (iter_data->lun_dev && sc->device != iter_data->lun_dev) + return true; + + spin_lock_irqsave(&fnic->wq_copy_lock[hwq], flags); + + io_req = fnic_priv(sc)->io_req; + if (!io_req) { + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + return true; + } + + /* + * Found IO that is still pending with firmware and + * belongs to the LUN that we are resetting + */ + FNIC_SCSI_DBG(KERN_INFO, fnic, + "hwq: %d tag: 0x%x Found IO in state: %s on lun\n", + hwq, tag, + fnic_ioreq_state_to_str(fnic_priv(sc)->state)); + cmd_state = fnic_priv(sc)->state; + spin_unlock_irqrestore(&fnic->wq_copy_lock[hwq], flags); + if (cmd_state == FNIC_IOREQ_ABTS_PENDING) + iter_data->ret = 1; + + return iter_data->ret ? false : true; +} + +/* + * fnic_is_abts_pending() is a helper function that + * walks through tag map to check if there is any IOs pending,if there is one, + * then it returns 1 (true), otherwise 0 (false) + * if @lr_sc is non NULL, then it checks IOs specific to particular LUN, + * otherwise, it checks for all IOs. + */ +int fnic_is_abts_pending(struct fnic *fnic, struct scsi_cmnd *lr_sc) +{ + struct fnic_pending_aborts_iter_data iter_data = { + .fnic = fnic, + .lun_dev = NULL, + .ret = 0, + }; + + if (lr_sc) { + iter_data.lun_dev = lr_sc->device; + iter_data.lr_sc = lr_sc; + } + + /* walk again to check, if IOs are still pending in fw */ + scsi_host_busy_iter(fnic->host, + fnic_abts_pending_iter, &iter_data); + + return iter_data.ret; +} + +/* + * SCSI Error handling calls driver's eh_host_reset if all prior + * error handling levels return FAILED. If host reset completes + * successfully, and if link is up, then Fabric login begins. + * + * Host Reset is the highest level of error recovery. If this fails, then + * host is offlined by SCSI. + * + */ +int fnic_eh_host_reset_handler(struct scsi_cmnd *sc) +{ + int ret = 0; + struct Scsi_Host *shost = sc->device->host; + struct fnic *fnic = *((struct fnic **) shost_priv(shost)); + + FNIC_SCSI_DBG(KERN_ERR, fnic, + "SCSI error handling: fnic host reset"); + + ret = fnic_host_reset(shost); + return ret; +} diff --git a/drivers/scsi/fnic/fnic_stats.h b/drivers/scsi/fnic/fnic_stats.h new file mode 100644 index 000000000..a3ddd7b55 --- /dev/null +++ b/drivers/scsi/fnic/fnic_stats.h @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright 2013 Cisco Systems, Inc. All rights reserved. */ +#ifndef _FNIC_STATS_H_ +#define _FNIC_STATS_H_ +#define FNIC_MQ_MAX_QUEUES 64 +#include <scsi/scsi_transport_fc.h> + +struct stats_timestamps { + struct timespec64 last_reset_time; + struct timespec64 last_read_time; +}; + +struct io_path_stats { + atomic64_t active_ios; + atomic64_t max_active_ios; + atomic64_t io_completions; + atomic64_t io_failures; + atomic64_t ioreq_null; + atomic64_t alloc_failures; + atomic64_t sc_null; + atomic64_t io_not_found; + atomic64_t num_ios; + atomic64_t io_btw_0_to_10_msec; + atomic64_t io_btw_10_to_100_msec; + atomic64_t io_btw_100_to_500_msec; + atomic64_t io_btw_500_to_5000_msec; + atomic64_t io_btw_5000_to_10000_msec; + atomic64_t io_btw_10000_to_30000_msec; + atomic64_t io_greater_than_30000_msec; + atomic64_t current_max_io_time; + atomic64_t ios[FNIC_MQ_MAX_QUEUES]; + + atomic64_t nvme_io_reqs_rcvd; + atomic64_t nvme_ios_queued_for_rsp; + atomic64_t nvme_io_rsps_unqueued; + atomic64_t nvme_io_rsps_sending; + atomic64_t nvme_io_rsps_sent; + atomic64_t nvme_num_ios_in_waitq; + atomic64_t nvme_ios_in_waitq_3000_msec; + atomic64_t nvme_ios_in_waitq_max_time; +}; + +struct abort_stats { + atomic64_t aborts; + atomic64_t abort_failures; + atomic64_t abort_drv_timeouts; + atomic64_t abort_fw_timeouts; + atomic64_t abort_io_not_found; + atomic64_t abort_issued_btw_0_to_6_sec; + atomic64_t abort_issued_btw_6_to_20_sec; + atomic64_t abort_issued_btw_20_to_30_sec; + atomic64_t abort_issued_btw_30_to_40_sec; + atomic64_t abort_issued_btw_40_to_50_sec; + atomic64_t abort_issued_btw_50_to_60_sec; + atomic64_t abort_issued_greater_than_60_sec; +}; + +struct terminate_stats { + atomic64_t terminates; + atomic64_t max_terminates; + atomic64_t terminate_drv_timeouts; + atomic64_t terminate_fw_timeouts; + atomic64_t terminate_io_not_found; + atomic64_t terminate_failures; +}; + +struct reset_stats { + atomic64_t device_resets; + atomic64_t device_reset_failures; + atomic64_t device_reset_aborts; + atomic64_t device_reset_timeouts; + atomic64_t device_reset_terminates; + atomic64_t fw_resets; + atomic64_t fw_reset_completions; + atomic64_t fw_reset_failures; + atomic64_t fw_reset_timeouts; + atomic64_t fnic_resets; + atomic64_t fnic_reset_completions; + atomic64_t fnic_reset_failures; +}; + +struct fw_stats { + atomic64_t active_fw_reqs; + atomic64_t max_fw_reqs; + atomic64_t fw_out_of_resources; + atomic64_t io_fw_errs; +}; + +struct vlan_stats { + atomic64_t vlan_disc_reqs; + atomic64_t resp_withno_vlanID; + atomic64_t sol_expiry_count; + atomic64_t flogi_rejects; +}; + +struct misc_stats { + u64 last_isr_time; + u64 last_ack_time; + atomic64_t max_isr_jiffies; + atomic64_t max_isr_time_ms; + atomic64_t corr_work_done; + atomic64_t isr_count; + atomic64_t max_cq_entries; + atomic64_t ack_index_out_of_range; + atomic64_t data_count_mismatch; + atomic64_t fcpio_timeout; + atomic64_t fcpio_aborted; + atomic64_t sgl_invalid; + atomic64_t mss_invalid; + atomic64_t abts_cpwq_alloc_failures; + atomic64_t devrst_cpwq_alloc_failures; + atomic64_t io_cpwq_alloc_failures; + atomic64_t no_icmnd_itmf_cmpls; + atomic64_t check_condition; + atomic64_t queue_fulls; + atomic64_t tport_not_ready; + atomic64_t iport_not_ready; + atomic64_t frame_errors; + atomic64_t current_port_speed; + atomic64_t intx_dummy; + atomic64_t port_speed_in_mbps; +}; + +struct fnic_iport_stats { + atomic64_t num_linkdn; + atomic64_t num_linkup; + atomic64_t link_failure_count; + atomic64_t num_rscns; + atomic64_t rscn_redisc; + atomic64_t rscn_not_redisc; + atomic64_t frame_err; + atomic64_t num_rnid; + atomic64_t fabric_flogi_sent; + atomic64_t fabric_flogi_ls_accepts; + atomic64_t fabric_flogi_ls_rejects; + atomic64_t fabric_flogi_misc_rejects; + atomic64_t fabric_plogi_sent; + atomic64_t fabric_plogi_ls_accepts; + atomic64_t fabric_plogi_ls_rejects; + atomic64_t fabric_plogi_misc_rejects; + atomic64_t fabric_scr_sent; + atomic64_t fabric_scr_ls_accepts; + atomic64_t fabric_scr_ls_rejects; + atomic64_t fabric_scr_misc_rejects; + atomic64_t fabric_logo_sent; + atomic64_t tport_alive; + atomic64_t tport_plogi_sent; + atomic64_t tport_plogi_ls_accepts; + atomic64_t tport_plogi_ls_rejects; + atomic64_t tport_plogi_misc_rejects; + atomic64_t tport_prli_sent; + atomic64_t tport_prli_ls_accepts; + atomic64_t tport_prli_ls_rejects; + atomic64_t tport_prli_misc_rejects; + atomic64_t tport_adisc_sent; + atomic64_t tport_adisc_ls_accepts; + atomic64_t tport_adisc_ls_rejects; + atomic64_t tport_logo_sent; + atomic64_t unsupported_frames_ls_rejects; + atomic64_t unsupported_frames_dropped; +}; + +struct nvme_host_statistics { + atomic64_t nvme_input_requests; + atomic64_t nvme_output_requests; + atomic64_t nvme_control_requests; + atomic64_t nvme_ersps; + atomic64_t nvme_ls_requests; + atomic64_t nvme_ls_responses; + atomic64_t nvme_ls_aborts; + atomic64_t nvme_ls_abort_responses; +}; + +struct fnic_stats { + struct stats_timestamps stats_timestamps; + struct io_path_stats io_stats; + struct abort_stats abts_stats; + struct terminate_stats term_stats; + struct reset_stats reset_stats; + struct fw_stats fw_stats; + struct vlan_stats vlan_stats; + struct fc_host_statistics host_stats; + struct misc_stats misc_stats; + struct nvme_host_statistics nvme_stats; +}; + +struct stats_debug_info { + char *debug_buffer; + void *i_private; + int buf_size; + int buffer_len; +}; + +struct fnic_nvmef_info { + char *info_buffer; + void *i_private; + int buf_size; + int buffer_len; +}; + +int fnic_get_stats_data(struct stats_debug_info *, struct fnic_stats *); +const char *fnic_role_to_str(unsigned int role); +#endif /* _FNIC_STATS_H_ */ diff --git a/drivers/scsi/fnic/fnic_trace.c b/drivers/scsi/fnic/fnic_trace.c new file mode 100644 index 000000000..a3f583703 --- /dev/null +++ b/drivers/scsi/fnic/fnic_trace.c @@ -0,0 +1,884 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright 2012 Cisco Systems, Inc. All rights reserved. + +#include <linux/module.h> +#include <linux/mempool.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/kallsyms.h> +#include <linux/time.h> +#include <linux/vmalloc.h> +#include <scsi/scsi_transport_fc.h> +#include "fnic_io.h" +#include "fnic.h" + +unsigned int trace_max_pages; +static int fnic_max_trace_entries; + +static unsigned long fnic_trace_buf_p; +static DEFINE_SPINLOCK(fnic_trace_lock); + +static fnic_trace_dbg_t fnic_trace_entries; +int fnic_tracing_enabled = 1; + +/* static char *fnic_fc_ctlr_trace_buf_p; */ + +static int fc_trace_max_entries; +static unsigned long fnic_fc_ctlr_trace_buf_p; +static fnic_trace_dbg_t fc_trace_entries; +int fnic_fc_tracing_enabled = 1; +int fnic_fc_trace_cleared = 1; +static DEFINE_SPINLOCK(fnic_fc_trace_lock); + +static const char * const fnic_role_str[] = { + [FNIC_ROLE_FCP_INITIATOR] = "FCP_Initiator", + [FNIC_ROLE_NVME_INITIATOR] = "NVMeF_Initiator", +}; + +const char *fnic_role_to_str(unsigned int role) +{ + if (role >= ARRAY_SIZE(fnic_role_str) || !fnic_role_str[role]) + return "Unknown"; + + return fnic_role_str[role]; +} + +/* + * fnic_trace_get_buf - Give buffer pointer to user to fill up trace information + * + * Description: + * This routine gets next available trace buffer entry location @wr_idx + * from allocated trace buffer pages and give that memory location + * to user to store the trace information. + * + * Return Value: + * This routine returns pointer to next available trace entry + * @fnic_buf_head for user to fill trace information. + */ +fnic_trace_data_t *fnic_trace_get_buf(void) +{ + unsigned long fnic_buf_head; + unsigned long flags; + + spin_lock_irqsave(&fnic_trace_lock, flags); + + /* + * Get next available memory location for writing trace information + * at @wr_idx and increment @wr_idx + */ + fnic_buf_head = + fnic_trace_entries.page_offset[fnic_trace_entries.wr_idx]; + fnic_trace_entries.wr_idx++; + + /* + * Verify if trace buffer is full then change wd_idx to + * start from zero + */ + if (fnic_trace_entries.wr_idx >= fnic_max_trace_entries) + fnic_trace_entries.wr_idx = 0; + + /* + * Verify if write index @wr_idx and read index @rd_idx are same then + * increment @rd_idx to move to next entry in trace buffer + */ + if (fnic_trace_entries.wr_idx == fnic_trace_entries.rd_idx) { + fnic_trace_entries.rd_idx++; + if (fnic_trace_entries.rd_idx >= fnic_max_trace_entries) + fnic_trace_entries.rd_idx = 0; + } + spin_unlock_irqrestore(&fnic_trace_lock, flags); + return (fnic_trace_data_t *)fnic_buf_head; +} + +/* + * fnic_get_trace_data - Copy trace buffer to a memory file + * @fnic_dbgfs_t: pointer to debugfs trace buffer + * + * Description: + * This routine gathers the fnic trace debugfs data from the fnic_trace_data_t + * buffer and dumps it to fnic_dbgfs_t. It will start at the rd_idx entry in + * the log and process the log until the end of the buffer. Then it will gather + * from the beginning of the log and process until the current entry @wr_idx. + * + * Return Value: + * This routine returns the amount of bytes that were dumped into fnic_dbgfs_t + */ +int fnic_get_trace_data(fnic_dbgfs_t *fnic_dbgfs_prt) +{ + int rd_idx; + int wr_idx; + int len = 0; + unsigned long flags; + char str[KSYM_SYMBOL_LEN]; + struct timespec64 val; + fnic_trace_data_t *tbp; + + spin_lock_irqsave(&fnic_trace_lock, flags); + rd_idx = fnic_trace_entries.rd_idx; + wr_idx = fnic_trace_entries.wr_idx; + if (wr_idx < rd_idx) { + while (1) { + /* Start from read index @rd_idx */ + tbp = (fnic_trace_data_t *) + fnic_trace_entries.page_offset[rd_idx]; + if (!tbp) { + spin_unlock_irqrestore(&fnic_trace_lock, flags); + return 0; + } + /* Convert function pointer to function name */ + if (sizeof(unsigned long) < 8) { + sprint_symbol(str, tbp->fnaddr.low); + jiffies_to_timespec64(tbp->timestamp.low, &val); + } else { + sprint_symbol(str, tbp->fnaddr.val); + jiffies_to_timespec64(tbp->timestamp.val, &val); + } + /* + * Dump trace buffer entry to memory file + * and increment read index @rd_idx + */ + len += scnprintf(fnic_dbgfs_prt->buffer + len, + (trace_max_pages * PAGE_SIZE * 3) - len, + "%ptSp %-50s %8x %8x %16llx %16llx %16llx %16llx %16llx\n", + &val, str, tbp->host_no, tbp->tag, + tbp->data[0], tbp->data[1], tbp->data[2], + tbp->data[3], tbp->data[4]); + rd_idx++; + /* + * If rd_idx is reached to maximum trace entries + * then move rd_idx to zero + */ + if (rd_idx > (fnic_max_trace_entries-1)) + rd_idx = 0; + /* + * Continue dumping trace buffer entries into + * memory file till rd_idx reaches write index + */ + if (rd_idx == wr_idx) + break; + } + } else if (wr_idx > rd_idx) { + while (1) { + /* Start from read index @rd_idx */ + tbp = (fnic_trace_data_t *) + fnic_trace_entries.page_offset[rd_idx]; + if (!tbp) { + spin_unlock_irqrestore(&fnic_trace_lock, flags); + return 0; + } + /* Convert function pointer to function name */ + if (sizeof(unsigned long) < 8) { + sprint_symbol(str, tbp->fnaddr.low); + jiffies_to_timespec64(tbp->timestamp.low, &val); + } else { + sprint_symbol(str, tbp->fnaddr.val); + jiffies_to_timespec64(tbp->timestamp.val, &val); + } + /* + * Dump trace buffer entry to memory file + * and increment read index @rd_idx + */ + len += scnprintf(fnic_dbgfs_prt->buffer + len, + (trace_max_pages * PAGE_SIZE * 3) - len, + "%ptSp %-50s %8x %8x %16llx %16llx %16llx %16llx %16llx\n", + &val, str, tbp->host_no, tbp->tag, + tbp->data[0], tbp->data[1], tbp->data[2], + tbp->data[3], tbp->data[4]); + rd_idx++; + /* + * Continue dumping trace buffer entries into + * memory file till rd_idx reaches write index + */ + if (rd_idx == wr_idx) + break; + } + } + spin_unlock_irqrestore(&fnic_trace_lock, flags); + return len; +} + +/* + * fnic_get_stats_data - Copy fnic stats buffer to a memory file + * @fnic_dbgfs_t: pointer to debugfs fnic stats buffer + * + * Description: + * This routine gathers the fnic stats debugfs data from the fnic_stats struct + * and dumps it to stats_debug_info. + * + * Return Value: + * This routine returns the amount of bytes that were dumped into + * stats_debug_info + */ +int fnic_get_stats_data(struct stats_debug_info *debug, + struct fnic_stats *stats) +{ + int len = 0; + int buf_size = debug->buf_size; + struct timespec64 val, val1, val2; + int i = 0; + + ktime_get_real_ts64(&val); + len = scnprintf(debug->debug_buffer + len, buf_size - len, + "------------------------------------------\n" + "\t\tTime\n" + "------------------------------------------\n"); + + val1 = timespec64_sub(val, stats->stats_timestamps.last_reset_time); + val2 = timespec64_sub(val, stats->stats_timestamps.last_read_time); + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "Current time : [%ptSp]\n" + "Last stats reset time: [%ptSp]\n" + "Last stats read time: [%ptSp]\n" + "delta since last reset: [%ptSp]\n" + "delta since last read: [%ptSp]\n", + &val, + &stats->stats_timestamps.last_reset_time, + &stats->stats_timestamps.last_read_time, + &val1, &val2); + + stats->stats_timestamps.last_read_time = val; + + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "------------------------------------------\n" + "\t\tIO Statistics\n" + "------------------------------------------\n"); + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "Number of Active IOs: %lld\nMaximum Active IOs: %lld\n" + "Number of IOs: %lld\nNumber of IO Completions: %lld\n" + "Number of IO Failures: %lld\nNumber of IO NOT Found: %lld\n" + "Number of Memory alloc Failures: %lld\n" + "Number of IOREQ Null: %lld\n" + "Number of SCSI cmd pointer Null: %lld\n" + + "\nIO completion times: \n" + " < 10 ms : %lld\n" + " 10 ms - 100 ms : %lld\n" + " 100 ms - 500 ms : %lld\n" + " 500 ms - 5 sec: %lld\n" + " 5 sec - 10 sec: %lld\n" + " 10 sec - 30 sec: %lld\n" + " > 30 sec: %lld\n", + (u64)atomic64_read(&stats->io_stats.active_ios), + (u64)atomic64_read(&stats->io_stats.max_active_ios), + (u64)atomic64_read(&stats->io_stats.num_ios), + (u64)atomic64_read(&stats->io_stats.io_completions), + (u64)atomic64_read(&stats->io_stats.io_failures), + (u64)atomic64_read(&stats->io_stats.io_not_found), + (u64)atomic64_read(&stats->io_stats.alloc_failures), + (u64)atomic64_read(&stats->io_stats.ioreq_null), + (u64)atomic64_read(&stats->io_stats.sc_null), + (u64)atomic64_read(&stats->io_stats.io_btw_0_to_10_msec), + (u64)atomic64_read(&stats->io_stats.io_btw_10_to_100_msec), + (u64)atomic64_read(&stats->io_stats.io_btw_100_to_500_msec), + (u64)atomic64_read(&stats->io_stats.io_btw_500_to_5000_msec), + (u64)atomic64_read(&stats->io_stats.io_btw_5000_to_10000_msec), + (u64)atomic64_read(&stats->io_stats.io_btw_10000_to_30000_msec), + (u64)atomic64_read(&stats->io_stats.io_greater_than_30000_msec)); + + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "------------------------------------------\n" + "\t\tIO Queues and cumulative IOs\n" + "------------------------------------------\n"); + + for (i = 0; i < FNIC_MQ_MAX_QUEUES; i++) { + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "Q:%d -> %lld\n", i, (u64)atomic64_read(&stats->io_stats.ios[i])); + } + + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "\nCurrent Max IO time : %lld\n", + (u64)atomic64_read(&stats->io_stats.current_max_io_time)); + + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "\n------------------------------------------\n" + "\t\tAbort Statistics\n" + "------------------------------------------\n"); + + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "Number of Aborts: %lld\n" + "Number of Abort Failures: %lld\n" + "Number of Abort Driver Timeouts: %lld\n" + "Number of Abort FW Timeouts: %lld\n" + "Number of Abort IO NOT Found: %lld\n" + + "Abort issued times: \n" + " < 6 sec : %lld\n" + " 6 sec - 20 sec : %lld\n" + " 20 sec - 30 sec : %lld\n" + " 30 sec - 40 sec : %lld\n" + " 40 sec - 50 sec : %lld\n" + " 50 sec - 60 sec : %lld\n" + " > 60 sec: %lld\n", + + (u64)atomic64_read(&stats->abts_stats.aborts), + (u64)atomic64_read(&stats->abts_stats.abort_failures), + (u64)atomic64_read(&stats->abts_stats.abort_drv_timeouts), + (u64)atomic64_read(&stats->abts_stats.abort_fw_timeouts), + (u64)atomic64_read(&stats->abts_stats.abort_io_not_found), + (u64)atomic64_read(&stats->abts_stats.abort_issued_btw_0_to_6_sec), + (u64)atomic64_read(&stats->abts_stats.abort_issued_btw_6_to_20_sec), + (u64)atomic64_read(&stats->abts_stats.abort_issued_btw_20_to_30_sec), + (u64)atomic64_read(&stats->abts_stats.abort_issued_btw_30_to_40_sec), + (u64)atomic64_read(&stats->abts_stats.abort_issued_btw_40_to_50_sec), + (u64)atomic64_read(&stats->abts_stats.abort_issued_btw_50_to_60_sec), + (u64)atomic64_read(&stats->abts_stats.abort_issued_greater_than_60_sec)); + + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "\n------------------------------------------\n" + "\t\tTerminate Statistics\n" + "------------------------------------------\n"); + + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "Number of Terminates: %lld\n" + "Maximum Terminates: %lld\n" + "Number of Terminate Driver Timeouts: %lld\n" + "Number of Terminate FW Timeouts: %lld\n" + "Number of Terminate IO NOT Found: %lld\n" + "Number of Terminate Failures: %lld\n", + (u64)atomic64_read(&stats->term_stats.terminates), + (u64)atomic64_read(&stats->term_stats.max_terminates), + (u64)atomic64_read(&stats->term_stats.terminate_drv_timeouts), + (u64)atomic64_read(&stats->term_stats.terminate_fw_timeouts), + (u64)atomic64_read(&stats->term_stats.terminate_io_not_found), + (u64)atomic64_read(&stats->term_stats.terminate_failures)); + + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "\n------------------------------------------\n" + "\t\tReset Statistics\n" + "------------------------------------------\n"); + + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "Number of Device Resets: %lld\n" + "Number of Device Reset Failures: %lld\n" + "Number of Device Reset Aborts: %lld\n" + "Number of Device Reset Timeouts: %lld\n" + "Number of Device Reset Terminates: %lld\n" + "Number of FW Resets: %lld\n" + "Number of FW Reset Completions: %lld\n" + "Number of FW Reset Failures: %lld\n" + "Number of Fnic Reset: %lld\n" + "Number of Fnic Reset Completions: %lld\n" + "Number of Fnic Reset Failures: %lld\n", + (u64)atomic64_read(&stats->reset_stats.device_resets), + (u64)atomic64_read(&stats->reset_stats.device_reset_failures), + (u64)atomic64_read(&stats->reset_stats.device_reset_aborts), + (u64)atomic64_read(&stats->reset_stats.device_reset_timeouts), + (u64)atomic64_read( + &stats->reset_stats.device_reset_terminates), + (u64)atomic64_read(&stats->reset_stats.fw_resets), + (u64)atomic64_read(&stats->reset_stats.fw_reset_completions), + (u64)atomic64_read(&stats->reset_stats.fw_reset_failures), + (u64)atomic64_read(&stats->reset_stats.fnic_resets), + (u64)atomic64_read( + &stats->reset_stats.fnic_reset_completions), + (u64)atomic64_read(&stats->reset_stats.fnic_reset_failures)); + + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "\n------------------------------------------\n" + "\t\tFirmware Statistics\n" + "------------------------------------------\n"); + + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "Number of Active FW Requests %lld\n" + "Maximum FW Requests: %lld\n" + "Number of FW out of resources: %lld\n" + "Number of FW IO errors: %lld\n", + (u64)atomic64_read(&stats->fw_stats.active_fw_reqs), + (u64)atomic64_read(&stats->fw_stats.max_fw_reqs), + (u64)atomic64_read(&stats->fw_stats.fw_out_of_resources), + (u64)atomic64_read(&stats->fw_stats.io_fw_errs)); + + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "\n------------------------------------------\n" + "\t\tVlan Discovery Statistics\n" + "------------------------------------------\n"); + + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "Number of Vlan Discovery Requests Sent %lld\n" + "Vlan Response Received with no FCF VLAN ID: %lld\n" + "No solicitations recvd after vlan set, expiry count: %lld\n" + "Flogi rejects count: %lld\n", + (u64)atomic64_read(&stats->vlan_stats.vlan_disc_reqs), + (u64)atomic64_read(&stats->vlan_stats.resp_withno_vlanID), + (u64)atomic64_read(&stats->vlan_stats.sol_expiry_count), + (u64)atomic64_read(&stats->vlan_stats.flogi_rejects)); + + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "\n------------------------------------------\n" + "\t\tOther Important Statistics\n" + "------------------------------------------\n"); + + jiffies_to_timespec64(stats->misc_stats.last_isr_time, &val1); + jiffies_to_timespec64(stats->misc_stats.last_ack_time, &val2); + + len += scnprintf(debug->debug_buffer + len, buf_size - len, + "Last ISR time: %llu (%ptSp)\n" + "Last ACK time: %llu (%ptSp)\n" + "Max ISR jiffies: %llu\n" + "Max ISR time (ms) (0 denotes < 1 ms): %llu\n" + "Corr. work done: %llu\n" + "Number of ISRs: %lld\n" + "Maximum CQ Entries: %lld\n" + "Number of ACK index out of range: %lld\n" + "Number of data count mismatch: %lld\n" + "Number of FCPIO Timeouts: %lld\n" + "Number of FCPIO Aborted: %lld\n" + "Number of SGL Invalid: %lld\n" + "Number of Copy WQ Alloc Failures for ABTs: %lld\n" + "Number of Copy WQ Alloc Failures for Device Reset: %lld\n" + "Number of Copy WQ Alloc Failures for IOs: %lld\n" + "Number of no icmnd itmf Completions: %lld\n" + "Number of Check Conditions encountered: %lld\n" + "Number of QUEUE Fulls: %lld\n" + "Number of rport not ready: %lld\n" + "Number of receive frame errors: %lld\n" + "Port speed (in Mbps): %lld\n", + (u64)stats->misc_stats.last_isr_time, &val1, + (u64)stats->misc_stats.last_ack_time, &val2, + (u64)atomic64_read(&stats->misc_stats.max_isr_jiffies), + (u64)atomic64_read(&stats->misc_stats.max_isr_time_ms), + (u64)atomic64_read(&stats->misc_stats.corr_work_done), + (u64)atomic64_read(&stats->misc_stats.isr_count), + (u64)atomic64_read(&stats->misc_stats.max_cq_entries), + (u64)atomic64_read(&stats->misc_stats.ack_index_out_of_range), + (u64)atomic64_read(&stats->misc_stats.data_count_mismatch), + (u64)atomic64_read(&stats->misc_stats.fcpio_timeout), + (u64)atomic64_read(&stats->misc_stats.fcpio_aborted), + (u64)atomic64_read(&stats->misc_stats.sgl_invalid), + (u64)atomic64_read( + &stats->misc_stats.abts_cpwq_alloc_failures), + (u64)atomic64_read( + &stats->misc_stats.devrst_cpwq_alloc_failures), + (u64)atomic64_read(&stats->misc_stats.io_cpwq_alloc_failures), + (u64)atomic64_read(&stats->misc_stats.no_icmnd_itmf_cmpls), + (u64)atomic64_read(&stats->misc_stats.check_condition), + (u64)atomic64_read(&stats->misc_stats.queue_fulls), + (u64)atomic64_read(&stats->misc_stats.tport_not_ready), + (u64)atomic64_read(&stats->misc_stats.frame_errors), + (u64)atomic64_read(&stats->misc_stats.port_speed_in_mbps)); + + return len; + +} + +int fnic_get_debug_info(struct stats_debug_info *info, struct fnic *fnic) +{ + struct fnic_iport_s *iport = &fnic->iport; + int buf_size = info->buf_size; + int len = info->buffer_len; + struct fnic_tport_s *tport, *next; + unsigned long flags; + + len += snprintf(info->debug_buffer + len, buf_size - len, + "------------------------------------------\n" + "\t\t Debug Info\n" + "------------------------------------------\n"); + len += snprintf(info->debug_buffer + len, buf_size - len, + "fnic Name:%s number:%d Role:%s State:%s\n", + fnic->name, fnic->fnic_num, + fnic_role_to_str(fnic->role), + fnic_state_to_str(fnic->state)); + len += + snprintf(info->debug_buffer + len, buf_size - len, + "iport State:%d Flags:0x%x vlan_id:%d fcid:0x%x\n", + iport->state, iport->flags, iport->vlan_id, iport->fcid); + len += + snprintf(info->debug_buffer + len, buf_size - len, + "usefip:%d fip_state:%d fip_flogi_retry:%d\n", + iport->usefip, iport->fip.state, iport->fip.flogi_retry); + len += + snprintf(info->debug_buffer + len, buf_size - len, + "fpma %02x:%02x:%02x:%02x:%02x:%02x", + iport->fpma[5], iport->fpma[4], iport->fpma[3], + iport->fpma[2], iport->fpma[1], iport->fpma[0]); + len += + snprintf(info->debug_buffer + len, buf_size - len, + "fcfmac %02x:%02x:%02x:%02x:%02x:%02x\n", + iport->fcfmac[5], iport->fcfmac[4], iport->fcfmac[3], + iport->fcfmac[2], iport->fcfmac[1], iport->fcfmac[0]); + len += + snprintf(info->debug_buffer + len, buf_size - len, + "fabric state:%d flags:0x%x retry_counter:%d e_d_tov:%d r_a_tov:%d\n", + iport->fabric.state, iport->fabric.flags, + iport->fabric.retry_counter, iport->e_d_tov, + iport->r_a_tov); + + spin_lock_irqsave(&fnic->fnic_lock, flags); + list_for_each_entry_safe(tport, next, &iport->tport_list, links) { + len += snprintf(info->debug_buffer + len, buf_size - len, + "tport fcid:0x%x state:%d flags:0x%x inflight:%d retry_counter:%d\n", + tport->fcid, tport->state, tport->flags, + atomic_read(&tport->in_flight), + tport->retry_counter); + } + spin_unlock_irqrestore(&fnic->fnic_lock, flags); + return len; +} + +/* + * fnic_trace_buf_init - Initialize fnic trace buffer logging facility + * + * Description: + * Initialize trace buffer data structure by allocating required memory and + * setting page_offset information for every trace entry by adding trace entry + * length to previous page_offset value. + */ +int fnic_trace_buf_init(void) +{ + unsigned long fnic_buf_head; + int i; + int err = 0; + + trace_max_pages = fnic_trace_max_pages; + fnic_max_trace_entries = (trace_max_pages * PAGE_SIZE)/ + FNIC_ENTRY_SIZE_BYTES; + + fnic_trace_buf_p = (unsigned long)vcalloc(trace_max_pages, PAGE_SIZE); + if (!fnic_trace_buf_p) { + printk(KERN_ERR PFX "Failed to allocate memory " + "for fnic_trace_buf_p\n"); + err = -ENOMEM; + goto err_fnic_trace_buf_init; + } + + fnic_trace_entries.page_offset = + vcalloc(fnic_max_trace_entries, sizeof(unsigned long)); + if (!fnic_trace_entries.page_offset) { + printk(KERN_ERR PFX "Failed to allocate memory for" + " page_offset\n"); + if (fnic_trace_buf_p) { + vfree((void *)fnic_trace_buf_p); + fnic_trace_buf_p = 0; + } + err = -ENOMEM; + goto err_fnic_trace_buf_init; + } + fnic_trace_entries.wr_idx = fnic_trace_entries.rd_idx = 0; + fnic_buf_head = fnic_trace_buf_p; + + /* + * Set page_offset field of fnic_trace_entries struct by + * calculating memory location for every trace entry using + * length of each trace entry + */ + for (i = 0; i < fnic_max_trace_entries; i++) { + fnic_trace_entries.page_offset[i] = fnic_buf_head; + fnic_buf_head += FNIC_ENTRY_SIZE_BYTES; + } + fnic_trace_debugfs_init(); + pr_info("fnic: Successfully Initialized Trace Buffer\n"); + return err; + +err_fnic_trace_buf_init: + return err; +} + +/* + * fnic_trace_free - Free memory of fnic trace data structures. + */ +void fnic_trace_free(void) +{ + fnic_tracing_enabled = 0; + fnic_trace_debugfs_terminate(); + if (fnic_trace_entries.page_offset) { + vfree((void *)fnic_trace_entries.page_offset); + fnic_trace_entries.page_offset = NULL; + } + if (fnic_trace_buf_p) { + vfree((void *)fnic_trace_buf_p); + fnic_trace_buf_p = 0; + } + printk(KERN_INFO PFX "Successfully Freed Trace Buffer\n"); +} + +/* + * fnic_fc_ctlr_trace_buf_init - + * Initialize trace buffer to log fnic control frames + * Description: + * Initialize trace buffer data structure by allocating + * required memory for trace data as well as for Indexes. + * Frame size is 256 bytes and + * memory is allocated for 1024 entries of 256 bytes. + * Page_offset(Index) is set to the address of trace entry + * and page_offset is initialized by adding frame size + * to the previous page_offset entry. + */ + +int fnic_fc_trace_init(void) +{ + unsigned long fc_trace_buf_head; + int err = 0; + int i; + + fc_trace_max_entries = (fnic_fc_trace_max_pages * PAGE_SIZE)/ + FC_TRC_SIZE_BYTES; + fnic_fc_ctlr_trace_buf_p = + (unsigned long)vcalloc(fnic_fc_trace_max_pages, PAGE_SIZE); + if (!fnic_fc_ctlr_trace_buf_p) { + pr_err("fnic: Failed to allocate memory for " + "FC Control Trace Buf\n"); + err = -ENOMEM; + goto err_fnic_fc_ctlr_trace_buf_init; + } + + /* Allocate memory for page offset */ + fc_trace_entries.page_offset = + vcalloc(fc_trace_max_entries, sizeof(unsigned long)); + if (!fc_trace_entries.page_offset) { + pr_err("fnic:Failed to allocate memory for page_offset\n"); + if (fnic_fc_ctlr_trace_buf_p) { + pr_err("fnic: Freeing FC Control Trace Buf\n"); + vfree((void *)fnic_fc_ctlr_trace_buf_p); + fnic_fc_ctlr_trace_buf_p = 0; + } + err = -ENOMEM; + goto err_fnic_fc_ctlr_trace_buf_init; + } + + fc_trace_entries.rd_idx = fc_trace_entries.wr_idx = 0; + fc_trace_buf_head = fnic_fc_ctlr_trace_buf_p; + + /* + * Set up fc_trace_entries.page_offset field with memory location + * for every trace entry + */ + for (i = 0; i < fc_trace_max_entries; i++) { + fc_trace_entries.page_offset[i] = fc_trace_buf_head; + fc_trace_buf_head += FC_TRC_SIZE_BYTES; + } + fnic_fc_trace_debugfs_init(); + pr_info("fnic: Successfully Initialized FC_CTLR Trace Buffer\n"); + return err; + +err_fnic_fc_ctlr_trace_buf_init: + return err; +} + +/* + * Fnic_fc_ctlr_trace_free - Free memory of fnic_fc_ctlr trace data structures. + */ +void fnic_fc_trace_free(void) +{ + fnic_fc_tracing_enabled = 0; + fnic_fc_trace_debugfs_terminate(); + if (fc_trace_entries.page_offset) { + vfree((void *)fc_trace_entries.page_offset); + fc_trace_entries.page_offset = NULL; + } + if (fnic_fc_ctlr_trace_buf_p) { + vfree((void *)fnic_fc_ctlr_trace_buf_p); + fnic_fc_ctlr_trace_buf_p = 0; + } + pr_info("fnic:Successfully FC_CTLR Freed Trace Buffer\n"); +} + +/* + * fnic_fc_ctlr_set_trace_data: + * Maintain rd & wr idx accordingly and set data + * Passed parameters: + * host_no: host number associated with fnic + * frame_type: send_frame, rece_frame or link event + * fc_frame: pointer to fc_frame + * frame_len: Length of the fc_frame + * Description: + * This routine will get next available wr_idx and + * copy all passed trace data to the buffer pointed by wr_idx + * and increment wr_idx. It will also make sure that we dont + * overwrite the entry which we are reading and also + * wrap around if we reach the maximum entries. + * Returned Value: + * It will return 0 for success or -1 for failure + */ +int fnic_fc_trace_set_data(u32 host_no, u8 frame_type, + char *frame, u32 fc_trc_frame_len) +{ + unsigned long flags; + struct fc_trace_hdr *fc_buf; + unsigned long eth_fcoe_hdr_len; + char *fc_trace; + + if (fnic_fc_tracing_enabled == 0) + return 0; + + spin_lock_irqsave(&fnic_fc_trace_lock, flags); + + if (fnic_fc_trace_cleared == 1) { + fc_trace_entries.rd_idx = fc_trace_entries.wr_idx = 0; + pr_info("fnic: Resetting the read idx\n"); + memset((void *)fnic_fc_ctlr_trace_buf_p, 0, + fnic_fc_trace_max_pages * PAGE_SIZE); + fnic_fc_trace_cleared = 0; + } + + fc_buf = (struct fc_trace_hdr *) + fc_trace_entries.page_offset[fc_trace_entries.wr_idx]; + + fc_trace_entries.wr_idx++; + + if (fc_trace_entries.wr_idx >= fc_trace_max_entries) + fc_trace_entries.wr_idx = 0; + + if (fc_trace_entries.wr_idx == fc_trace_entries.rd_idx) { + fc_trace_entries.rd_idx++; + if (fc_trace_entries.rd_idx >= fc_trace_max_entries) + fc_trace_entries.rd_idx = 0; + } + + ktime_get_real_ts64(&fc_buf->time_stamp); + fc_buf->host_no = host_no; + fc_buf->frame_type = frame_type; + + fc_trace = (char *)FC_TRACE_ADDRESS(fc_buf); + + /* During the receive path, we do not have eth hdr as well as fcoe hdr + * at trace entry point so we will stuff 0xff just to make it generic. + */ + if (frame_type == FNIC_FC_RECV) { + eth_fcoe_hdr_len = sizeof(struct ethhdr) + + sizeof(struct fcoe_hdr); + memset((char *)fc_trace, 0xff, eth_fcoe_hdr_len); + /* Copy the rest of data frame */ + memcpy((char *)(fc_trace + eth_fcoe_hdr_len), (void *)frame, + min_t(u8, fc_trc_frame_len, + (u8)(FC_TRC_SIZE_BYTES - FC_TRC_HEADER_SIZE + - eth_fcoe_hdr_len))); + } else { + memcpy((char *)fc_trace, (void *)frame, + min_t(u8, fc_trc_frame_len, + (u8)(FC_TRC_SIZE_BYTES - FC_TRC_HEADER_SIZE))); + } + + /* Store the actual received length */ + fc_buf->frame_len = fc_trc_frame_len; + + spin_unlock_irqrestore(&fnic_fc_trace_lock, flags); + return 0; +} + +/* + * fnic_fc_ctlr_get_trace_data: Copy trace buffer to a memory file + * Passed parameter: + * @fnic_dbgfs_t: pointer to debugfs trace buffer + * rdata_flag: 1 => Unformatted file + * 0 => formatted file + * Description: + * This routine will copy the trace data to memory file with + * proper formatting and also copy to another memory + * file without formatting for further processing. + * Return Value: + * Number of bytes that were dumped into fnic_dbgfs_t + */ + +int fnic_fc_trace_get_data(fnic_dbgfs_t *fnic_dbgfs_prt, u8 rdata_flag) +{ + int rd_idx, wr_idx; + unsigned long flags; + int len = 0, j; + struct fc_trace_hdr *tdata; + char *fc_trace; + + spin_lock_irqsave(&fnic_fc_trace_lock, flags); + if (fc_trace_entries.wr_idx == fc_trace_entries.rd_idx) { + spin_unlock_irqrestore(&fnic_fc_trace_lock, flags); + pr_info("fnic: Buffer is empty\n"); + return 0; + } + rd_idx = fc_trace_entries.rd_idx; + wr_idx = fc_trace_entries.wr_idx; + if (rdata_flag == 0) { + len += scnprintf(fnic_dbgfs_prt->buffer + len, + (fnic_fc_trace_max_pages * PAGE_SIZE * 3) - len, + "Time Stamp (UTC)\t\t" + "Host No: F Type: len: FCoE_FRAME:\n"); + } + + while (rd_idx != wr_idx) { + tdata = (struct fc_trace_hdr *) + fc_trace_entries.page_offset[rd_idx]; + if (!tdata) { + pr_info("fnic: Rd data is NULL\n"); + spin_unlock_irqrestore(&fnic_fc_trace_lock, flags); + return 0; + } + if (rdata_flag == 0) { + copy_and_format_trace_data(tdata, + fnic_dbgfs_prt, &len, rdata_flag); + } else { + fc_trace = (char *)tdata; + for (j = 0; j < FC_TRC_SIZE_BYTES; j++) { + len += scnprintf(fnic_dbgfs_prt->buffer + len, + (fnic_fc_trace_max_pages * PAGE_SIZE * 3) + - len, "%02x", fc_trace[j] & 0xff); + } /* for loop */ + len += scnprintf(fnic_dbgfs_prt->buffer + len, + (fnic_fc_trace_max_pages * PAGE_SIZE * 3) - len, + "\n"); + } + rd_idx++; + if (rd_idx > (fc_trace_max_entries - 1)) + rd_idx = 0; + } + + spin_unlock_irqrestore(&fnic_fc_trace_lock, flags); + return len; +} + +/* + * copy_and_format_trace_data: Copy formatted data to char * buffer + * Passed Parameter: + * @fc_trace_hdr_t: pointer to trace data + * @fnic_dbgfs_t: pointer to debugfs trace buffer + * @orig_len: pointer to len + * rdata_flag: 0 => Formatted file, 1 => Unformatted file + * Description: + * This routine will format and copy the passed trace data + * for formatted file or unformatted file accordingly. + */ + +void copy_and_format_trace_data(struct fc_trace_hdr *tdata, + fnic_dbgfs_t *fnic_dbgfs_prt, int *orig_len, + u8 rdata_flag) +{ + int j, i = 1, len; + int ethhdr_len = sizeof(struct ethhdr) - 1; + int fcoehdr_len = sizeof(struct fcoe_hdr); + int fchdr_len = sizeof(struct fc_frame_header); + int max_size = fnic_fc_trace_max_pages * PAGE_SIZE * 3; + char *fc_trace; + + tdata->frame_type = tdata->frame_type & 0x7F; + + len = *orig_len; + + len += scnprintf(fnic_dbgfs_prt->buffer + len, max_size - len, + "%ptSs ns%8x %c%8x\t", + &tdata->time_stamp, + tdata->host_no, tdata->frame_type, tdata->frame_len); + + fc_trace = (char *)FC_TRACE_ADDRESS(tdata); + + for (j = 0; j < min_t(u8, tdata->frame_len, + (u8)(FC_TRC_SIZE_BYTES - FC_TRC_HEADER_SIZE)); j++) { + if (tdata->frame_type == FNIC_FC_LE) { + len += scnprintf(fnic_dbgfs_prt->buffer + len, + max_size - len, "%c", fc_trace[j]); + } else { + len += scnprintf(fnic_dbgfs_prt->buffer + len, + max_size - len, "%02x", fc_trace[j] & 0xff); + len += scnprintf(fnic_dbgfs_prt->buffer + len, + max_size - len, " "); + if (j == ethhdr_len || + j == ethhdr_len + fcoehdr_len || + j == ethhdr_len + fcoehdr_len + fchdr_len || + (i > 3 && j%fchdr_len == 0)) { + len += scnprintf(fnic_dbgfs_prt->buffer + + len, max_size - len, + "\n\t\t\t\t\t\t\t\t"); + i++; + } + } /* end of else*/ + } /* End of for loop*/ + len += scnprintf(fnic_dbgfs_prt->buffer + len, + max_size - len, "\n"); + *orig_len = len; +} diff --git a/drivers/scsi/fnic/fnic_trace.h b/drivers/scsi/fnic/fnic_trace.h new file mode 100644 index 000000000..d1c301bf3 --- /dev/null +++ b/drivers/scsi/fnic/fnic_trace.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright 2012 Cisco Systems, Inc. All rights reserved. */ + +#ifndef __FNIC_TRACE_H__ +#define __FNIC_TRACE_H__ + +#define FNIC_ENTRY_SIZE_BYTES 64 +#define FC_TRC_SIZE_BYTES 256 +#define FC_TRC_HEADER_SIZE sizeof(struct fc_trace_hdr) + +/* + * Fisrt bit of FNIC_FC_RECV and FNIC_FC_SEND is used to represent the type + * of frame 1 => Eth frame, 0=> FC frame + */ + +#define FNIC_FC_RECV 0x52 /* Character R */ +#define FNIC_FC_SEND 0x54 /* Character T */ +#define FNIC_FC_LE 0x4C /* Character L */ + +extern ssize_t simple_read_from_buffer(void __user *to, + size_t count, + loff_t *ppos, + const void *from, + size_t available); + +extern unsigned int fnic_trace_max_pages; +extern int fnic_tracing_enabled; +extern unsigned int trace_max_pages; + +extern unsigned int fnic_fc_trace_max_pages; +extern int fnic_fc_tracing_enabled; +extern int fnic_fc_trace_cleared; + +typedef struct fnic_trace_dbg { + int wr_idx; + int rd_idx; + unsigned long *page_offset; +} fnic_trace_dbg_t; + +typedef struct fnic_dbgfs { + int buffer_len; + char *buffer; +} fnic_dbgfs_t; + +struct fnic_trace_data { + union { + struct { + u32 low; + u32 high; + }; + u64 val; + } timestamp, fnaddr; + u32 host_no; + u32 tag; + u64 data[5]; +} __attribute__((__packed__)); + +typedef struct fnic_trace_data fnic_trace_data_t; + +struct fc_trace_hdr { + struct timespec64 time_stamp; + u32 host_no; + u8 frame_type; + u8 frame_len; +} __attribute__((__packed__)); + +#define FC_TRACE_ADDRESS(a) \ + ((unsigned long)(a) + sizeof(struct fc_trace_hdr)) + +#define FNIC_TRACE_ENTRY_SIZE \ + (FNIC_ENTRY_SIZE_BYTES - sizeof(fnic_trace_data_t)) + +#define FNIC_TRACE(_fn, _hn, _t, _a, _b, _c, _d, _e) \ + if (unlikely(fnic_tracing_enabled)) { \ + fnic_trace_data_t *trace_buf = fnic_trace_get_buf(); \ + if (trace_buf) { \ + if (sizeof(unsigned long) < 8) { \ + trace_buf->timestamp.low = jiffies; \ + trace_buf->fnaddr.low = (u32)(unsigned long)_fn; \ + } else { \ + trace_buf->timestamp.val = jiffies; \ + trace_buf->fnaddr.val = (u64)(unsigned long)_fn; \ + } \ + trace_buf->host_no = _hn; \ + trace_buf->tag = _t; \ + trace_buf->data[0] = (u64)(unsigned long)_a; \ + trace_buf->data[1] = (u64)(unsigned long)_b; \ + trace_buf->data[2] = (u64)(unsigned long)_c; \ + trace_buf->data[3] = (u64)(unsigned long)_d; \ + trace_buf->data[4] = (u64)(unsigned long)_e; \ + } \ + } + +fnic_trace_data_t *fnic_trace_get_buf(void); +int fnic_get_trace_data(fnic_dbgfs_t *); +int fnic_trace_buf_init(void); +void fnic_trace_free(void); +int fnic_debugfs_init(void); +void fnic_debugfs_terminate(void); +void fnic_trace_debugfs_init(void); +void fnic_trace_debugfs_terminate(void); + +/* Fnic FC CTLR Trace releated function */ +int fnic_fc_trace_init(void); +void fnic_fc_trace_free(void); +int fnic_fc_trace_set_data(u32 host_no, u8 frame_type, + char *frame, u32 fc_frame_len); +int fnic_fc_trace_get_data(fnic_dbgfs_t *fnic_dbgfs_prt, u8 rdata_flag); +void copy_and_format_trace_data(struct fc_trace_hdr *tdata, + fnic_dbgfs_t *fnic_dbgfs_prt, + int *len, u8 rdata_flag); +void fnic_fc_trace_debugfs_init(void); +void fnic_fc_trace_debugfs_terminate(void); + +#endif diff --git a/drivers/scsi/fnic/rq_enet_desc.h b/drivers/scsi/fnic/rq_enet_desc.h new file mode 100644 index 000000000..9bc509d35 --- /dev/null +++ b/drivers/scsi/fnic/rq_enet_desc.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _RQ_ENET_DESC_H_ +#define _RQ_ENET_DESC_H_ + +/* Ethernet receive queue descriptor: 16B */ +struct rq_enet_desc { + __le64 address; + __le16 length_type; + u8 reserved[6]; +}; + +enum rq_enet_type_types { + RQ_ENET_TYPE_ONLY_SOP = 0, + RQ_ENET_TYPE_NOT_SOP = 1, + RQ_ENET_TYPE_RESV2 = 2, + RQ_ENET_TYPE_RESV3 = 3, +}; + +#define RQ_ENET_ADDR_BITS 64 +#define RQ_ENET_LEN_BITS 14 +#define RQ_ENET_LEN_MASK ((1 << RQ_ENET_LEN_BITS) - 1) +#define RQ_ENET_TYPE_BITS 2 +#define RQ_ENET_TYPE_MASK ((1 << RQ_ENET_TYPE_BITS) - 1) + +static inline void rq_enet_desc_enc(struct rq_enet_desc *desc, + u64 address, u8 type, u16 length) +{ + desc->address = cpu_to_le64(address); + desc->length_type = cpu_to_le16((length & RQ_ENET_LEN_MASK) | + ((type & RQ_ENET_TYPE_MASK) << RQ_ENET_LEN_BITS)); +} + +static inline void rq_enet_desc_dec(struct rq_enet_desc *desc, + u64 *address, u8 *type, u16 *length) +{ + *address = le64_to_cpu(desc->address); + *length = le16_to_cpu(desc->length_type) & RQ_ENET_LEN_MASK; + *type = (u8)((le16_to_cpu(desc->length_type) >> RQ_ENET_LEN_BITS) & + RQ_ENET_TYPE_MASK); +} + +#endif /* _RQ_ENET_DESC_H_ */ diff --git a/drivers/scsi/fnic/vnic_cq.c b/drivers/scsi/fnic/vnic_cq.c new file mode 100644 index 000000000..ed3dd443f --- /dev/null +++ b/drivers/scsi/fnic/vnic_cq.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/pci.h> +#include "vnic_dev.h" +#include "vnic_cq.h" + +void vnic_cq_free(struct vnic_cq *cq) +{ + vnic_dev_free_desc_ring(cq->vdev, &cq->ring); + + cq->ctrl = NULL; +} + +int vnic_cq_alloc(struct vnic_dev *vdev, struct vnic_cq *cq, unsigned int index, + unsigned int desc_count, unsigned int desc_size) +{ + int err; + + cq->index = index; + cq->vdev = vdev; + + cq->ctrl = vnic_dev_get_res(vdev, RES_TYPE_CQ, index); + if (!cq->ctrl) { + printk(KERN_ERR "Failed to hook CQ[%d] resource\n", index); + return -EINVAL; + } + + err = vnic_dev_alloc_desc_ring(vdev, &cq->ring, desc_count, desc_size); + if (err) + return err; + + return 0; +} + +void vnic_cq_init(struct vnic_cq *cq, unsigned int flow_control_enable, + unsigned int color_enable, unsigned int cq_head, unsigned int cq_tail, + unsigned int cq_tail_color, unsigned int interrupt_enable, + unsigned int cq_entry_enable, unsigned int cq_message_enable, + unsigned int interrupt_offset, u64 cq_message_addr) +{ + u64 paddr; + + paddr = (u64)cq->ring.base_addr | VNIC_PADDR_TARGET; + writeq(paddr, &cq->ctrl->ring_base); + iowrite32(cq->ring.desc_count, &cq->ctrl->ring_size); + iowrite32(flow_control_enable, &cq->ctrl->flow_control_enable); + iowrite32(color_enable, &cq->ctrl->color_enable); + iowrite32(cq_head, &cq->ctrl->cq_head); + iowrite32(cq_tail, &cq->ctrl->cq_tail); + iowrite32(cq_tail_color, &cq->ctrl->cq_tail_color); + iowrite32(interrupt_enable, &cq->ctrl->interrupt_enable); + iowrite32(cq_entry_enable, &cq->ctrl->cq_entry_enable); + iowrite32(cq_message_enable, &cq->ctrl->cq_message_enable); + iowrite32(interrupt_offset, &cq->ctrl->interrupt_offset); + writeq(cq_message_addr, &cq->ctrl->cq_message_addr); +} + +void vnic_cq_clean(struct vnic_cq *cq) +{ + cq->to_clean = 0; + cq->last_color = 0; + + iowrite32(0, &cq->ctrl->cq_head); + iowrite32(0, &cq->ctrl->cq_tail); + iowrite32(1, &cq->ctrl->cq_tail_color); + + vnic_dev_clear_desc_ring(&cq->ring); +} diff --git a/drivers/scsi/fnic/vnic_cq.h b/drivers/scsi/fnic/vnic_cq.h new file mode 100644 index 000000000..e7cc1f165 --- /dev/null +++ b/drivers/scsi/fnic/vnic_cq.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _VNIC_CQ_H_ +#define _VNIC_CQ_H_ + +#include "cq_desc.h" +#include "vnic_dev.h" + +/* + * These defines avoid symbol clash between fnic and enic (Cisco 10G Eth + * Driver) when both are built with CONFIG options =y + */ +#define vnic_cq_service fnic_cq_service +#define vnic_cq_free fnic_cq_free +#define vnic_cq_alloc fnic_cq_alloc +#define vnic_cq_init fnic_cq_init +#define vnic_cq_clean fnic_cq_clean + +/* Completion queue control */ +struct vnic_cq_ctrl { + u64 ring_base; /* 0x00 */ + u32 ring_size; /* 0x08 */ + u32 pad0; + u32 flow_control_enable; /* 0x10 */ + u32 pad1; + u32 color_enable; /* 0x18 */ + u32 pad2; + u32 cq_head; /* 0x20 */ + u32 pad3; + u32 cq_tail; /* 0x28 */ + u32 pad4; + u32 cq_tail_color; /* 0x30 */ + u32 pad5; + u32 interrupt_enable; /* 0x38 */ + u32 pad6; + u32 cq_entry_enable; /* 0x40 */ + u32 pad7; + u32 cq_message_enable; /* 0x48 */ + u32 pad8; + u32 interrupt_offset; /* 0x50 */ + u32 pad9; + u64 cq_message_addr; /* 0x58 */ + u32 pad10; +}; + +struct vnic_cq { + unsigned int index; + struct vnic_dev *vdev; + struct vnic_cq_ctrl __iomem *ctrl; /* memory-mapped */ + struct vnic_dev_ring ring; + unsigned int to_clean; + unsigned int last_color; +}; + +static inline unsigned int vnic_cq_service(struct vnic_cq *cq, + unsigned int work_to_do, + int (*q_service)(struct vnic_dev *vdev, struct cq_desc *cq_desc, + u8 type, u16 q_number, u16 completed_index, void *opaque), + void *opaque) +{ + struct cq_desc *cq_desc; + unsigned int work_done = 0; + u16 q_number, completed_index; + u8 type, color; + + cq_desc = (struct cq_desc *)((u8 *)cq->ring.descs + + cq->ring.desc_size * cq->to_clean); + cq_desc_dec(cq_desc, &type, &color, + &q_number, &completed_index); + + while (color != cq->last_color) { + + if ((*q_service)(cq->vdev, cq_desc, type, + q_number, completed_index, opaque)) + break; + + cq->to_clean++; + if (cq->to_clean == cq->ring.desc_count) { + cq->to_clean = 0; + cq->last_color = cq->last_color ? 0 : 1; + } + + cq_desc = (struct cq_desc *)((u8 *)cq->ring.descs + + cq->ring.desc_size * cq->to_clean); + cq_desc_dec(cq_desc, &type, &color, + &q_number, &completed_index); + + work_done++; + if (work_done >= work_to_do) + break; + } + + return work_done; +} + +void vnic_cq_free(struct vnic_cq *cq); +int vnic_cq_alloc(struct vnic_dev *vdev, struct vnic_cq *cq, unsigned int index, + unsigned int desc_count, unsigned int desc_size); +void vnic_cq_init(struct vnic_cq *cq, unsigned int flow_control_enable, + unsigned int color_enable, unsigned int cq_head, unsigned int cq_tail, + unsigned int cq_tail_color, unsigned int interrupt_enable, + unsigned int cq_entry_enable, unsigned int message_enable, + unsigned int interrupt_offset, u64 message_addr); +void vnic_cq_clean(struct vnic_cq *cq); + +#endif /* _VNIC_CQ_H_ */ diff --git a/drivers/scsi/fnic/vnic_cq_copy.h b/drivers/scsi/fnic/vnic_cq_copy.h new file mode 100644 index 000000000..1b198ee59 --- /dev/null +++ b/drivers/scsi/fnic/vnic_cq_copy.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _VNIC_CQ_COPY_H_ +#define _VNIC_CQ_COPY_H_ + +#include "fcpio.h" + +static inline unsigned int vnic_cq_copy_service( + struct vnic_cq *cq, + int (*q_service)(struct vnic_dev *vdev, + unsigned int index, + struct fcpio_fw_req *desc), + unsigned int work_to_do) + +{ + struct fcpio_fw_req *desc; + unsigned int work_done = 0; + u8 color; + + desc = (struct fcpio_fw_req *)((u8 *)cq->ring.descs + + cq->ring.desc_size * cq->to_clean); + fcpio_color_dec(desc, &color); + + while (color != cq->last_color) { + + if ((*q_service)(cq->vdev, cq->index, desc)) + break; + + cq->to_clean++; + if (cq->to_clean == cq->ring.desc_count) { + cq->to_clean = 0; + cq->last_color = cq->last_color ? 0 : 1; + } + + desc = (struct fcpio_fw_req *)((u8 *)cq->ring.descs + + cq->ring.desc_size * cq->to_clean); + fcpio_color_dec(desc, &color); + + work_done++; + if (work_done >= work_to_do) + break; + } + + return work_done; +} + +#endif /* _VNIC_CQ_COPY_H_ */ diff --git a/drivers/scsi/fnic/vnic_dev.c b/drivers/scsi/fnic/vnic_dev.c new file mode 100644 index 000000000..0e5ae0540 --- /dev/null +++ b/drivers/scsi/fnic/vnic_dev.c @@ -0,0 +1,947 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/if_ether.h> +#include <linux/slab.h> +#include "vnic_resource.h" +#include "vnic_devcmd.h" +#include "vnic_dev.h" +#include "vnic_stats.h" +#include "vnic_wq.h" + +struct devcmd2_controller { + struct vnic_wq_ctrl *wq_ctrl; + struct vnic_dev_ring results_ring; + struct vnic_wq wq; + struct vnic_devcmd2 *cmd_ring; + struct devcmd2_result *result; + u16 next_result; + u16 result_size; + int color; +}; + +enum vnic_proxy_type { + PROXY_NONE, + PROXY_BY_BDF, + PROXY_BY_INDEX, +}; + +struct vnic_res { + void __iomem *vaddr; + unsigned int count; +}; + +struct vnic_dev { + void *priv; + struct pci_dev *pdev; + struct vnic_res res[RES_TYPE_MAX]; + enum vnic_dev_intr_mode intr_mode; + struct vnic_devcmd __iomem *devcmd; + struct vnic_devcmd_notify *notify; + struct vnic_devcmd_notify notify_copy; + dma_addr_t notify_pa; + u32 *linkstatus; + dma_addr_t linkstatus_pa; + struct vnic_stats *stats; + dma_addr_t stats_pa; + struct vnic_devcmd_fw_info *fw_info; + dma_addr_t fw_info_pa; + enum vnic_proxy_type proxy; + u32 proxy_index; + u64 args[VNIC_DEVCMD_NARGS]; + struct devcmd2_controller *devcmd2; + int (*devcmd_rtn)(struct vnic_dev *vdev, enum vnic_devcmd_cmd cmd, + int wait); +}; + +#define VNIC_MAX_RES_HDR_SIZE \ + (sizeof(struct vnic_resource_header) + \ + sizeof(struct vnic_resource) * RES_TYPE_MAX) +#define VNIC_RES_STRIDE 128 + +void *vnic_dev_priv(struct vnic_dev *vdev) +{ + return vdev->priv; +} + +static int vnic_dev_discover_res(struct vnic_dev *vdev, + struct vnic_dev_bar *bar) +{ + struct vnic_resource_header __iomem *rh; + struct vnic_resource __iomem *r; + u8 type; + + if (bar->len < VNIC_MAX_RES_HDR_SIZE) { + printk(KERN_ERR "vNIC BAR0 res hdr length error\n"); + return -EINVAL; + } + + rh = bar->vaddr; + if (!rh) { + printk(KERN_ERR "vNIC BAR0 res hdr not mem-mapped\n"); + return -EINVAL; + } + + if (ioread32(&rh->magic) != VNIC_RES_MAGIC || + ioread32(&rh->version) != VNIC_RES_VERSION) { + printk(KERN_ERR "vNIC BAR0 res magic/version error " + "exp (%lx/%lx) curr (%x/%x)\n", + VNIC_RES_MAGIC, VNIC_RES_VERSION, + ioread32(&rh->magic), ioread32(&rh->version)); + return -EINVAL; + } + + r = (struct vnic_resource __iomem *)(rh + 1); + + while ((type = ioread8(&r->type)) != RES_TYPE_EOL) { + + u8 bar_num = ioread8(&r->bar); + u32 bar_offset = ioread32(&r->bar_offset); + u32 count = ioread32(&r->count); + u32 len; + + r++; + + if (bar_num != 0) /* only mapping in BAR0 resources */ + continue; + + switch (type) { + case RES_TYPE_WQ: + case RES_TYPE_RQ: + case RES_TYPE_CQ: + case RES_TYPE_INTR_CTRL: + /* each count is stride bytes long */ + len = count * VNIC_RES_STRIDE; + if (len + bar_offset > bar->len) { + printk(KERN_ERR "vNIC BAR0 resource %d " + "out-of-bounds, offset 0x%x + " + "size 0x%x > bar len 0x%lx\n", + type, bar_offset, + len, + bar->len); + return -EINVAL; + } + break; + case RES_TYPE_INTR_PBA_LEGACY: + case RES_TYPE_DEVCMD2: + case RES_TYPE_DEVCMD: + len = count; + break; + default: + continue; + } + + vdev->res[type].count = count; + vdev->res[type].vaddr = (char __iomem *)bar->vaddr + bar_offset; + } + + pr_info("res_type_wq: %d res_type_rq: %d res_type_cq: %d res_type_intr_ctrl: %d\n", + vdev->res[RES_TYPE_WQ].count, vdev->res[RES_TYPE_RQ].count, + vdev->res[RES_TYPE_CQ].count, vdev->res[RES_TYPE_INTR_CTRL].count); + + return 0; +} + +unsigned int vnic_dev_get_res_count(struct vnic_dev *vdev, + enum vnic_res_type type) +{ + return vdev->res[type].count; +} + +void __iomem *vnic_dev_get_res(struct vnic_dev *vdev, enum vnic_res_type type, + unsigned int index) +{ + if (!vdev->res[type].vaddr) + return NULL; + + switch (type) { + case RES_TYPE_WQ: + case RES_TYPE_RQ: + case RES_TYPE_CQ: + case RES_TYPE_INTR_CTRL: + return (char __iomem *)vdev->res[type].vaddr + + index * VNIC_RES_STRIDE; + default: + return (char __iomem *)vdev->res[type].vaddr; + } +} + +unsigned int vnic_dev_desc_ring_size(struct vnic_dev_ring *ring, + unsigned int desc_count, + unsigned int desc_size) +{ + /* The base address of the desc rings must be 512 byte aligned. + * Descriptor count is aligned to groups of 32 descriptors. A + * count of 0 means the maximum 4096 descriptors. Descriptor + * size is aligned to 16 bytes. + */ + + unsigned int count_align = 32; + unsigned int desc_align = 16; + + ring->base_align = 512; + + if (desc_count == 0) + desc_count = 4096; + + ring->desc_count = ALIGN(desc_count, count_align); + + ring->desc_size = ALIGN(desc_size, desc_align); + + ring->size = ring->desc_count * ring->desc_size; + ring->size_unaligned = ring->size + ring->base_align; + + return ring->size_unaligned; +} + +void vnic_dev_clear_desc_ring(struct vnic_dev_ring *ring) +{ + memset(ring->descs, 0, ring->size); +} + +int vnic_dev_alloc_desc_ring(struct vnic_dev *vdev, struct vnic_dev_ring *ring, + unsigned int desc_count, unsigned int desc_size) +{ + vnic_dev_desc_ring_size(ring, desc_count, desc_size); + + ring->descs_unaligned = dma_alloc_coherent(&vdev->pdev->dev, + ring->size_unaligned, + &ring->base_addr_unaligned, GFP_KERNEL); + + if (!ring->descs_unaligned) { + printk(KERN_ERR + "Failed to allocate ring (size=%d), aborting\n", + (int)ring->size); + return -ENOMEM; + } + + ring->base_addr = ALIGN(ring->base_addr_unaligned, + ring->base_align); + ring->descs = (u8 *)ring->descs_unaligned + + (ring->base_addr - ring->base_addr_unaligned); + + vnic_dev_clear_desc_ring(ring); + + ring->desc_avail = ring->desc_count - 1; + + return 0; +} + +void vnic_dev_free_desc_ring(struct vnic_dev *vdev, struct vnic_dev_ring *ring) +{ + if (ring->descs) { + dma_free_coherent(&vdev->pdev->dev, + ring->size_unaligned, + ring->descs_unaligned, + ring->base_addr_unaligned); + ring->descs = NULL; + } +} + +static int vnic_dev_cmd1(struct vnic_dev *vdev, enum vnic_devcmd_cmd cmd, int wait) +{ + struct vnic_devcmd __iomem *devcmd = vdev->devcmd; + int delay; + u32 status; + static const int dev_cmd_err[] = { + /* convert from fw's version of error.h to host's version */ + 0, /* ERR_SUCCESS */ + EINVAL, /* ERR_EINVAL */ + EFAULT, /* ERR_EFAULT */ + EPERM, /* ERR_EPERM */ + EBUSY, /* ERR_EBUSY */ + }; + int err; + u64 *a0 = &vdev->args[0]; + u64 *a1 = &vdev->args[1]; + + status = ioread32(&devcmd->status); + if (status & STAT_BUSY) { + printk(KERN_ERR "Busy devcmd %d\n", _CMD_N(cmd)); + return -EBUSY; + } + + if (_CMD_DIR(cmd) & _CMD_DIR_WRITE) { + writeq(*a0, &devcmd->args[0]); + writeq(*a1, &devcmd->args[1]); + wmb(); + } + + iowrite32(cmd, &devcmd->cmd); + + if ((_CMD_FLAGS(cmd) & _CMD_FLAGS_NOWAIT)) + return 0; + + for (delay = 0; delay < wait; delay++) { + + udelay(100); + + status = ioread32(&devcmd->status); + if (!(status & STAT_BUSY)) { + + if (status & STAT_ERROR) { + err = dev_cmd_err[(int)readq(&devcmd->args[0])]; + printk(KERN_ERR "Error %d devcmd %d\n", + err, _CMD_N(cmd)); + return -err; + } + + if (_CMD_DIR(cmd) & _CMD_DIR_READ) { + rmb(); + *a0 = readq(&devcmd->args[0]); + *a1 = readq(&devcmd->args[1]); + } + + return 0; + } + } + + printk(KERN_ERR "Timedout devcmd %d\n", _CMD_N(cmd)); + return -ETIMEDOUT; +} + +static int vnic_dev_cmd2(struct vnic_dev *vdev, enum vnic_devcmd_cmd cmd, + int wait) +{ + struct devcmd2_controller *dc2c = vdev->devcmd2; + struct devcmd2_result *result; + u8 color; + unsigned int i; + int delay; + int err; + u32 fetch_index; + u32 posted; + u32 new_posted; + + posted = ioread32(&dc2c->wq_ctrl->posted_index); + fetch_index = ioread32(&dc2c->wq_ctrl->fetch_index); + + if (posted == 0xFFFFFFFF || fetch_index == 0xFFFFFFFF) { + /* Hardware surprise removal: return error */ + pr_err("%s: devcmd2 invalid posted or fetch index on cmd %d\n", + pci_name(vdev->pdev), _CMD_N(cmd)); + pr_err("%s: fetch index: %u, posted index: %u\n", + pci_name(vdev->pdev), fetch_index, posted); + + return -ENODEV; + + } + + new_posted = (posted + 1) % DEVCMD2_RING_SIZE; + + if (new_posted == fetch_index) { + pr_err("%s: devcmd2 wq full while issuing cmd %d\n", + pci_name(vdev->pdev), _CMD_N(cmd)); + pr_err("%s: fetch index: %u, posted index: %u\n", + pci_name(vdev->pdev), fetch_index, posted); + return -EBUSY; + + } + dc2c->cmd_ring[posted].cmd = cmd; + dc2c->cmd_ring[posted].flags = 0; + + if ((_CMD_FLAGS(cmd) & _CMD_FLAGS_NOWAIT)) + dc2c->cmd_ring[posted].flags |= DEVCMD2_FNORESULT; + if (_CMD_DIR(cmd) & _CMD_DIR_WRITE) { + for (i = 0; i < VNIC_DEVCMD_NARGS; i++) + dc2c->cmd_ring[posted].args[i] = vdev->args[i]; + + } + + /* Adding write memory barrier prevents compiler and/or CPU + * reordering, thus avoiding descriptor posting before + * descriptor is initialized. Otherwise, hardware can read + * stale descriptor fields. + */ + wmb(); + iowrite32(new_posted, &dc2c->wq_ctrl->posted_index); + + if (dc2c->cmd_ring[posted].flags & DEVCMD2_FNORESULT) + return 0; + + result = dc2c->result + dc2c->next_result; + color = dc2c->color; + + dc2c->next_result++; + if (dc2c->next_result == dc2c->result_size) { + dc2c->next_result = 0; + dc2c->color = dc2c->color ? 0 : 1; + } + + for (delay = 0; delay < wait; delay++) { + udelay(100); + if (result->color == color) { + if (result->error) { + err = -(int) result->error; + if (err != -ERR_ECMDUNKNOWN || + cmd != CMD_CAPABILITY) + pr_err("%s:Error %d devcmd %d\n", + pci_name(vdev->pdev), + err, _CMD_N(cmd)); + return err; + } + if (_CMD_DIR(cmd) & _CMD_DIR_READ) { + rmb(); /*prevent reorder while reding result*/ + for (i = 0; i < VNIC_DEVCMD_NARGS; i++) + vdev->args[i] = result->results[i]; + } + return 0; + } + } + + pr_err("%s:Timed out devcmd %d\n", pci_name(vdev->pdev), _CMD_N(cmd)); + + return -ETIMEDOUT; +} + + +static int vnic_dev_init_devcmd1(struct vnic_dev *vdev) +{ + vdev->devcmd = vnic_dev_get_res(vdev, RES_TYPE_DEVCMD, 0); + if (!vdev->devcmd) + return -ENODEV; + + vdev->devcmd_rtn = &vnic_dev_cmd1; + return 0; +} + + +static int vnic_dev_init_devcmd2(struct vnic_dev *vdev) +{ + int err; + unsigned int fetch_index; + + if (vdev->devcmd2) + return 0; + + vdev->devcmd2 = kzalloc_obj(*vdev->devcmd2, GFP_ATOMIC); + if (!vdev->devcmd2) + return -ENOMEM; + + vdev->devcmd2->color = 1; + vdev->devcmd2->result_size = DEVCMD2_RING_SIZE; + err = vnic_wq_devcmd2_alloc(vdev, &vdev->devcmd2->wq, + DEVCMD2_RING_SIZE, DEVCMD2_DESC_SIZE); + if (err) + goto err_free_devcmd2; + + fetch_index = ioread32(&vdev->devcmd2->wq.ctrl->fetch_index); + if (fetch_index == 0xFFFFFFFF) { /* check for hardware gone */ + pr_err("error in devcmd2 init"); + err = -ENODEV; + goto err_free_wq; + } + + /* + * Don't change fetch_index ever and + * set posted_index same as fetch_index + * when setting up the WQ for devcmd2. + */ + vnic_wq_init_start(&vdev->devcmd2->wq, 0, fetch_index, + fetch_index, 0, 0); + + vnic_wq_enable(&vdev->devcmd2->wq); + + err = vnic_dev_alloc_desc_ring(vdev, &vdev->devcmd2->results_ring, + DEVCMD2_RING_SIZE, DEVCMD2_DESC_SIZE); + if (err) + goto err_disable_wq; + + vdev->devcmd2->result = + (struct devcmd2_result *) vdev->devcmd2->results_ring.descs; + vdev->devcmd2->cmd_ring = + (struct vnic_devcmd2 *) vdev->devcmd2->wq.ring.descs; + vdev->devcmd2->wq_ctrl = vdev->devcmd2->wq.ctrl; + vdev->args[0] = (u64) vdev->devcmd2->results_ring.base_addr | + VNIC_PADDR_TARGET; + vdev->args[1] = DEVCMD2_RING_SIZE; + + err = vnic_dev_cmd2(vdev, CMD_INITIALIZE_DEVCMD2, 1000); + if (err) + goto err_free_desc_ring; + + vdev->devcmd_rtn = &vnic_dev_cmd2; + + return 0; + +err_free_desc_ring: + vnic_dev_free_desc_ring(vdev, &vdev->devcmd2->results_ring); +err_disable_wq: + vnic_wq_disable(&vdev->devcmd2->wq); +err_free_wq: + vnic_wq_free(&vdev->devcmd2->wq); +err_free_devcmd2: + kfree(vdev->devcmd2); + vdev->devcmd2 = NULL; + + return err; +} + + +static void vnic_dev_deinit_devcmd2(struct vnic_dev *vdev) +{ + vnic_dev_free_desc_ring(vdev, &vdev->devcmd2->results_ring); + vnic_wq_disable(&vdev->devcmd2->wq); + vnic_wq_free(&vdev->devcmd2->wq); + kfree(vdev->devcmd2); + vdev->devcmd2 = NULL; + vdev->devcmd_rtn = &vnic_dev_cmd1; +} + + +static int vnic_dev_cmd_no_proxy(struct vnic_dev *vdev, + enum vnic_devcmd_cmd cmd, u64 *a0, u64 *a1, int wait) +{ + int err; + + vdev->args[0] = *a0; + vdev->args[1] = *a1; + + err = (*vdev->devcmd_rtn)(vdev, cmd, wait); + + *a0 = vdev->args[0]; + *a1 = vdev->args[1]; + + return err; +} + + +int vnic_dev_cmd(struct vnic_dev *vdev, enum vnic_devcmd_cmd cmd, + u64 *a0, u64 *a1, int wait) +{ + memset(vdev->args, 0, sizeof(vdev->args)); + + switch (vdev->proxy) { + case PROXY_NONE: + default: + return vnic_dev_cmd_no_proxy(vdev, cmd, a0, a1, wait); + } +} + + +int vnic_dev_fw_info(struct vnic_dev *vdev, + struct vnic_devcmd_fw_info **fw_info) +{ + u64 a0, a1 = 0; + int wait = 1000; + int err = 0; + + if (!vdev->fw_info) { + vdev->fw_info = dma_alloc_coherent(&vdev->pdev->dev, + sizeof(struct vnic_devcmd_fw_info), + &vdev->fw_info_pa, GFP_KERNEL); + if (!vdev->fw_info) + return -ENOMEM; + + a0 = vdev->fw_info_pa; + + /* only get fw_info once and cache it */ + err = vnic_dev_cmd(vdev, CMD_MCPU_FW_INFO, &a0, &a1, wait); + } + + *fw_info = vdev->fw_info; + + return err; +} + +int vnic_dev_spec(struct vnic_dev *vdev, unsigned int offset, unsigned int size, + void *value) +{ + u64 a0, a1; + int wait = 1000; + int err; + + a0 = offset; + a1 = size; + + err = vnic_dev_cmd(vdev, CMD_DEV_SPEC, &a0, &a1, wait); + + switch (size) { + case 1: + *(u8 *)value = (u8)a0; + break; + case 2: + *(u16 *)value = (u16)a0; + break; + case 4: + *(u32 *)value = (u32)a0; + break; + case 8: + *(u64 *)value = a0; + break; + default: + BUG(); + break; + } + + return err; +} + +int vnic_dev_stats_clear(struct vnic_dev *vdev) +{ + u64 a0 = 0, a1 = 0; + int wait = 1000; + return vnic_dev_cmd(vdev, CMD_STATS_CLEAR, &a0, &a1, wait); +} + +int vnic_dev_stats_dump(struct vnic_dev *vdev, struct vnic_stats **stats) +{ + u64 a0, a1; + int wait = 1000; + + if (!vdev->stats) { + vdev->stats = dma_alloc_coherent(&vdev->pdev->dev, + sizeof(struct vnic_stats), &vdev->stats_pa, GFP_KERNEL); + if (!vdev->stats) + return -ENOMEM; + } + + *stats = vdev->stats; + a0 = vdev->stats_pa; + a1 = sizeof(struct vnic_stats); + + return vnic_dev_cmd(vdev, CMD_STATS_DUMP, &a0, &a1, wait); +} + +int vnic_dev_close(struct vnic_dev *vdev) +{ + u64 a0 = 0, a1 = 0; + int wait = 1000; + return vnic_dev_cmd(vdev, CMD_CLOSE, &a0, &a1, wait); +} + +int vnic_dev_enable(struct vnic_dev *vdev) +{ + u64 a0 = 0, a1 = 0; + int wait = 1000; + return vnic_dev_cmd(vdev, CMD_ENABLE, &a0, &a1, wait); +} + +int vnic_dev_disable(struct vnic_dev *vdev) +{ + u64 a0 = 0, a1 = 0; + int wait = 1000; + return vnic_dev_cmd(vdev, CMD_DISABLE, &a0, &a1, wait); +} + +int vnic_dev_open(struct vnic_dev *vdev, int arg) +{ + u64 a0 = (u32)arg, a1 = 0; + int wait = 1000; + return vnic_dev_cmd(vdev, CMD_OPEN, &a0, &a1, wait); +} + +int vnic_dev_open_done(struct vnic_dev *vdev, int *done) +{ + u64 a0 = 0, a1 = 0; + int wait = 1000; + int err; + + *done = 0; + + err = vnic_dev_cmd(vdev, CMD_OPEN_STATUS, &a0, &a1, wait); + if (err) + return err; + + *done = (a0 == 0); + + return 0; +} + +int vnic_dev_soft_reset(struct vnic_dev *vdev, int arg) +{ + u64 a0 = (u32)arg, a1 = 0; + int wait = 1000; + return vnic_dev_cmd(vdev, CMD_SOFT_RESET, &a0, &a1, wait); +} + +int vnic_dev_soft_reset_done(struct vnic_dev *vdev, int *done) +{ + u64 a0 = 0, a1 = 0; + int wait = 1000; + int err; + + *done = 0; + + err = vnic_dev_cmd(vdev, CMD_SOFT_RESET_STATUS, &a0, &a1, wait); + if (err) + return err; + + *done = (a0 == 0); + + return 0; +} + +int vnic_dev_hang_notify(struct vnic_dev *vdev) +{ + u64 a0 = 0, a1 = 0; + int wait = 1000; + return vnic_dev_cmd(vdev, CMD_HANG_NOTIFY, &a0, &a1, wait); +} + +int vnic_dev_mac_addr(struct vnic_dev *vdev, u8 *mac_addr) +{ + u64 a[2] = {}; + int wait = 1000; + int err, i; + + for (i = 0; i < ETH_ALEN; i++) + mac_addr[i] = 0; + + err = vnic_dev_cmd(vdev, CMD_MAC_ADDR, &a[0], &a[1], wait); + if (err) + return err; + + for (i = 0; i < ETH_ALEN; i++) + mac_addr[i] = ((u8 *)&a)[i]; + + return 0; +} + +void vnic_dev_packet_filter(struct vnic_dev *vdev, int directed, int multicast, + int broadcast, int promisc, int allmulti) +{ + u64 a0, a1 = 0; + int wait = 1000; + int err; + + a0 = (directed ? CMD_PFILTER_DIRECTED : 0) | + (multicast ? CMD_PFILTER_MULTICAST : 0) | + (broadcast ? CMD_PFILTER_BROADCAST : 0) | + (promisc ? CMD_PFILTER_PROMISCUOUS : 0) | + (allmulti ? CMD_PFILTER_ALL_MULTICAST : 0); + + err = vnic_dev_cmd(vdev, CMD_PACKET_FILTER, &a0, &a1, wait); + if (err) + printk(KERN_ERR "Can't set packet filter\n"); +} + +void vnic_dev_add_addr(struct vnic_dev *vdev, u8 *addr) +{ + u64 a[2] = {}; + int wait = 1000; + int err; + int i; + + for (i = 0; i < ETH_ALEN; i++) + ((u8 *)&a)[i] = addr[i]; + + err = vnic_dev_cmd(vdev, CMD_ADDR_ADD, &a[0], &a[1], wait); + if (err) + pr_err("Can't add addr [%pM], %d\n", addr, err); +} + +void vnic_dev_del_addr(struct vnic_dev *vdev, u8 *addr) +{ + u64 a[2] = {}; + int wait = 1000; + int err; + int i; + + for (i = 0; i < ETH_ALEN; i++) + ((u8 *)&a)[i] = addr[i]; + + err = vnic_dev_cmd(vdev, CMD_ADDR_DEL, &a[0], &a[1], wait); + if (err) + pr_err("Can't del addr [%pM], %d\n", addr, err); +} + +int vnic_dev_notify_set(struct vnic_dev *vdev, u16 intr) +{ + u64 a0, a1; + int wait = 1000; + + if (!vdev->notify) { + vdev->notify = dma_alloc_coherent(&vdev->pdev->dev, + sizeof(struct vnic_devcmd_notify), + &vdev->notify_pa, GFP_KERNEL); + if (!vdev->notify) + return -ENOMEM; + } + + a0 = vdev->notify_pa; + a1 = ((u64)intr << 32) & 0x0000ffff00000000ULL; + a1 += sizeof(struct vnic_devcmd_notify); + + return vnic_dev_cmd(vdev, CMD_NOTIFY, &a0, &a1, wait); +} + +void vnic_dev_notify_unset(struct vnic_dev *vdev) +{ + u64 a0, a1; + int wait = 1000; + + a0 = 0; /* paddr = 0 to unset notify buffer */ + a1 = 0x0000ffff00000000ULL; /* intr num = -1 to unreg for intr */ + a1 += sizeof(struct vnic_devcmd_notify); + + vnic_dev_cmd(vdev, CMD_NOTIFY, &a0, &a1, wait); +} + +static int vnic_dev_notify_ready(struct vnic_dev *vdev) +{ + u32 *words; + unsigned int nwords = sizeof(struct vnic_devcmd_notify) / 4; + unsigned int i; + u32 csum; + + if (!vdev->notify) + return 0; + + do { + csum = 0; + memcpy(&vdev->notify_copy, vdev->notify, + sizeof(struct vnic_devcmd_notify)); + words = (u32 *)&vdev->notify_copy; + for (i = 1; i < nwords; i++) + csum += words[i]; + } while (csum != words[0]); + + return 1; +} + +int vnic_dev_init(struct vnic_dev *vdev, int arg) +{ + u64 a0 = (u32)arg, a1 = 0; + int wait = 1000; + return vnic_dev_cmd(vdev, CMD_INIT, &a0, &a1, wait); +} + +u16 vnic_dev_set_default_vlan(struct vnic_dev *vdev, u16 new_default_vlan) +{ + u64 a0 = new_default_vlan, a1 = 0; + int wait = 1000; + int old_vlan = 0; + + old_vlan = vnic_dev_cmd(vdev, CMD_SET_DEFAULT_VLAN, &a0, &a1, wait); + return (u16)old_vlan; +} + +int vnic_dev_link_status(struct vnic_dev *vdev) +{ + if (vdev->linkstatus) + return *vdev->linkstatus; + + if (!vnic_dev_notify_ready(vdev)) + return 0; + + return vdev->notify_copy.link_state; +} + +u32 vnic_dev_port_speed(struct vnic_dev *vdev) +{ + if (!vnic_dev_notify_ready(vdev)) + return 0; + + return vdev->notify_copy.port_speed; +} + +u32 vnic_dev_msg_lvl(struct vnic_dev *vdev) +{ + if (!vnic_dev_notify_ready(vdev)) + return 0; + + return vdev->notify_copy.msglvl; +} + +u32 vnic_dev_mtu(struct vnic_dev *vdev) +{ + if (!vnic_dev_notify_ready(vdev)) + return 0; + + return vdev->notify_copy.mtu; +} + +u32 vnic_dev_link_down_cnt(struct vnic_dev *vdev) +{ + if (!vnic_dev_notify_ready(vdev)) + return 0; + + return vdev->notify_copy.link_down_cnt; +} + +void vnic_dev_set_intr_mode(struct vnic_dev *vdev, + enum vnic_dev_intr_mode intr_mode) +{ + vdev->intr_mode = intr_mode; +} + +enum vnic_dev_intr_mode vnic_dev_get_intr_mode( + struct vnic_dev *vdev) +{ + return vdev->intr_mode; +} + +void vnic_dev_unregister(struct vnic_dev *vdev) +{ + if (vdev) { + if (vdev->notify) + dma_free_coherent(&vdev->pdev->dev, + sizeof(struct vnic_devcmd_notify), + vdev->notify, + vdev->notify_pa); + if (vdev->linkstatus) + dma_free_coherent(&vdev->pdev->dev, + sizeof(u32), + vdev->linkstatus, + vdev->linkstatus_pa); + if (vdev->stats) + dma_free_coherent(&vdev->pdev->dev, + sizeof(struct vnic_stats), + vdev->stats, vdev->stats_pa); + if (vdev->fw_info) + dma_free_coherent(&vdev->pdev->dev, + sizeof(struct vnic_devcmd_fw_info), + vdev->fw_info, vdev->fw_info_pa); + if (vdev->devcmd2) + vnic_dev_deinit_devcmd2(vdev); + kfree(vdev); + } +} + +struct vnic_dev *vnic_dev_register(struct vnic_dev *vdev, + void *priv, struct pci_dev *pdev, struct vnic_dev_bar *bar) +{ + if (!vdev) { + vdev = kzalloc_obj(struct vnic_dev); + if (!vdev) + return NULL; + } + + vdev->priv = priv; + vdev->pdev = pdev; + + if (vnic_dev_discover_res(vdev, bar)) + goto err_out; + + return vdev; + +err_out: + vnic_dev_unregister(vdev); + return NULL; +} + +int vnic_dev_cmd_init(struct vnic_dev *vdev) +{ + int err; + void *p; + + p = vnic_dev_get_res(vdev, RES_TYPE_DEVCMD2, 0); + if (p) { + pr_err("fnic: DEVCMD2 resource found!\n"); + err = vnic_dev_init_devcmd2(vdev); + } else { + pr_err("fnic: DEVCMD2 not found, fall back to Devcmd\n"); + err = vnic_dev_init_devcmd1(vdev); + } + + return err; +} diff --git a/drivers/scsi/fnic/vnic_dev.h b/drivers/scsi/fnic/vnic_dev.h new file mode 100644 index 000000000..7a568d141 --- /dev/null +++ b/drivers/scsi/fnic/vnic_dev.h @@ -0,0 +1,153 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _VNIC_DEV_H_ +#define _VNIC_DEV_H_ + +#include "vnic_resource.h" +#include "vnic_devcmd.h" + +/* + * These defines avoid symbol clash between fnic and enic (Cisco 10G Eth + * Driver) when both are built with CONFIG options =y + */ +#define vnic_dev_priv fnic_dev_priv +#define vnic_dev_get_res_count fnic_dev_get_res_count +#define vnic_dev_get_res fnic_dev_get_res +#define vnic_dev_desc_ring_size fnic_dev_desc_ring_siz +#define vnic_dev_clear_desc_ring fnic_dev_clear_desc_ring +#define vnic_dev_alloc_desc_ring fnic_dev_alloc_desc_ring +#define vnic_dev_free_desc_ring fnic_dev_free_desc_ring +#define vnic_dev_cmd fnic_dev_cmd +#define vnic_dev_fw_info fnic_dev_fw_info +#define vnic_dev_spec fnic_dev_spec +#define vnic_dev_stats_clear fnic_dev_stats_clear +#define vnic_dev_cmd_init fnic_dev_cmd_init +#define vnic_dev_stats_dump fnic_dev_stats_dump +#define vnic_dev_hang_notify fnic_dev_hang_notify +#define vnic_dev_packet_filter fnic_dev_packet_filter +#define vnic_dev_add_addr fnic_dev_add_addr +#define vnic_dev_del_addr fnic_dev_del_addr +#define vnic_dev_mac_addr fnic_dev_mac_addr +#define vnic_dev_notify_set fnic_dev_notify_set +#define vnic_dev_notify_unset fnic_dev_notify_unset +#define vnic_dev_link_status fnic_dev_link_status +#define vnic_dev_port_speed fnic_dev_port_speed +#define vnic_dev_msg_lvl fnic_dev_msg_lvl +#define vnic_dev_mtu fnic_dev_mtu +#define vnic_dev_link_down_cnt fnic_dev_link_down_cnt +#define vnic_dev_close fnic_dev_close +#define vnic_dev_enable fnic_dev_enable +#define vnic_dev_disable fnic_dev_disable +#define vnic_dev_open fnic_dev_open +#define vnic_dev_open_done fnic_dev_open_done +#define vnic_dev_init fnic_dev_init +#define vnic_dev_soft_reset fnic_dev_soft_reset +#define vnic_dev_soft_reset_done fnic_dev_soft_reset_done +#define vnic_dev_set_intr_mode fnic_dev_set_intr_mode +#define vnic_dev_get_intr_mode fnic_dev_get_intr_mode +#define vnic_dev_unregister fnic_dev_unregister +#define vnic_dev_register fnic_dev_register + +#ifndef VNIC_PADDR_TARGET +#define VNIC_PADDR_TARGET 0x0000000000000000ULL +#endif + +#ifndef readq +static inline u64 readq(void __iomem *reg) +{ + return ((u64)readl(reg + 0x4UL) << 32) | (u64)readl(reg); +} + +static inline void writeq(u64 val, void __iomem *reg) +{ + writel(val & 0xffffffff, reg); + writel(val >> 32, reg + 0x4UL); +} +#endif + +enum vnic_dev_intr_mode { + VNIC_DEV_INTR_MODE_UNKNOWN, + VNIC_DEV_INTR_MODE_INTX, + VNIC_DEV_INTR_MODE_MSI, + VNIC_DEV_INTR_MODE_MSIX, +}; + +struct vnic_dev_bar { + void __iomem *vaddr; + dma_addr_t bus_addr; + unsigned long len; +}; + +struct vnic_dev_ring { + void *descs; + size_t size; + dma_addr_t base_addr; + size_t base_align; + void *descs_unaligned; + size_t size_unaligned; + dma_addr_t base_addr_unaligned; + unsigned int desc_size; + unsigned int desc_count; + unsigned int desc_avail; +}; + +struct vnic_dev; +struct vnic_stats; + +void *vnic_dev_priv(struct vnic_dev *vdev); +unsigned int vnic_dev_get_res_count(struct vnic_dev *vdev, + enum vnic_res_type type); +void __iomem *vnic_dev_get_res(struct vnic_dev *vdev, enum vnic_res_type type, + unsigned int index); +unsigned int vnic_dev_desc_ring_size(struct vnic_dev_ring *ring, + unsigned int desc_count, + unsigned int desc_size); +void vnic_dev_clear_desc_ring(struct vnic_dev_ring *ring); +int vnic_dev_alloc_desc_ring(struct vnic_dev *vdev, struct vnic_dev_ring *ring, + unsigned int desc_count, unsigned int desc_size); +void vnic_dev_free_desc_ring(struct vnic_dev *vdev, + struct vnic_dev_ring *ring); +int vnic_dev_cmd(struct vnic_dev *vdev, enum vnic_devcmd_cmd cmd, + u64 *a0, u64 *a1, int wait); +int vnic_dev_fw_info(struct vnic_dev *vdev, + struct vnic_devcmd_fw_info **fw_info); +int vnic_dev_spec(struct vnic_dev *vdev, unsigned int offset, + unsigned int size, void *value); +int vnic_dev_stats_clear(struct vnic_dev *vdev); +int vnic_dev_cmd_init(struct vnic_dev *vdev); +int vnic_dev_stats_dump(struct vnic_dev *vdev, struct vnic_stats **stats); +int vnic_dev_hang_notify(struct vnic_dev *vdev); +void vnic_dev_packet_filter(struct vnic_dev *vdev, int directed, int multicast, + int broadcast, int promisc, int allmulti); +void vnic_dev_add_addr(struct vnic_dev *vdev, u8 *addr); +void vnic_dev_del_addr(struct vnic_dev *vdev, u8 *addr); +int vnic_dev_mac_addr(struct vnic_dev *vdev, u8 *mac_addr); +int vnic_dev_notify_set(struct vnic_dev *vdev, u16 intr); +void vnic_dev_notify_unset(struct vnic_dev *vdev); +int vnic_dev_link_status(struct vnic_dev *vdev); +u32 vnic_dev_port_speed(struct vnic_dev *vdev); +u32 vnic_dev_msg_lvl(struct vnic_dev *vdev); +u32 vnic_dev_mtu(struct vnic_dev *vdev); +u32 vnic_dev_link_down_cnt(struct vnic_dev *vdev); +int vnic_dev_close(struct vnic_dev *vdev); +int vnic_dev_enable(struct vnic_dev *vdev); +int vnic_dev_disable(struct vnic_dev *vdev); +int vnic_dev_open(struct vnic_dev *vdev, int arg); +int vnic_dev_open_done(struct vnic_dev *vdev, int *done); +int vnic_dev_init(struct vnic_dev *vdev, int arg); +u16 vnic_dev_set_default_vlan(struct vnic_dev *vdev, + u16 new_default_vlan); +int vnic_dev_soft_reset(struct vnic_dev *vdev, int arg); +int vnic_dev_soft_reset_done(struct vnic_dev *vdev, int *done); +void vnic_dev_set_intr_mode(struct vnic_dev *vdev, + enum vnic_dev_intr_mode intr_mode); +enum vnic_dev_intr_mode vnic_dev_get_intr_mode(struct vnic_dev *vdev); +void vnic_dev_unregister(struct vnic_dev *vdev); +struct vnic_dev *vnic_dev_register(struct vnic_dev *vdev, + void *priv, struct pci_dev *pdev, + struct vnic_dev_bar *bar); + +#endif /* _VNIC_DEV_H_ */ diff --git a/drivers/scsi/fnic/vnic_devcmd.h b/drivers/scsi/fnic/vnic_devcmd.h new file mode 100644 index 000000000..20878a3e9 --- /dev/null +++ b/drivers/scsi/fnic/vnic_devcmd.h @@ -0,0 +1,492 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _VNIC_DEVCMD_H_ +#define _VNIC_DEVCMD_H_ + +#define _CMD_NBITS 14 +#define _CMD_VTYPEBITS 10 +#define _CMD_FLAGSBITS 6 +#define _CMD_DIRBITS 2 + +#define _CMD_NMASK ((1 << _CMD_NBITS)-1) +#define _CMD_VTYPEMASK ((1 << _CMD_VTYPEBITS)-1) +#define _CMD_FLAGSMASK ((1 << _CMD_FLAGSBITS)-1) +#define _CMD_DIRMASK ((1 << _CMD_DIRBITS)-1) + +#define _CMD_NSHIFT 0 +#define _CMD_VTYPESHIFT (_CMD_NSHIFT+_CMD_NBITS) +#define _CMD_FLAGSSHIFT (_CMD_VTYPESHIFT+_CMD_VTYPEBITS) +#define _CMD_DIRSHIFT (_CMD_FLAGSSHIFT+_CMD_FLAGSBITS) + +/* + * Direction bits (from host perspective). + */ +#define _CMD_DIR_NONE 0U +#define _CMD_DIR_WRITE 1U +#define _CMD_DIR_READ 2U +#define _CMD_DIR_RW (_CMD_DIR_WRITE | _CMD_DIR_READ) + +/* + * Flag bits. + */ +#define _CMD_FLAGS_NONE 0U +#define _CMD_FLAGS_NOWAIT 1U + +/* + * vNIC type bits. + */ +#define _CMD_VTYPE_NONE 0U +#define _CMD_VTYPE_ENET 1U +#define _CMD_VTYPE_FC 2U +#define _CMD_VTYPE_SCSI 4U +#define _CMD_VTYPE_ALL (_CMD_VTYPE_ENET | _CMD_VTYPE_FC | _CMD_VTYPE_SCSI) + +/* + * Used to create cmds.. +*/ +#define _CMDCF(dir, flags, vtype, nr) \ + (((dir) << _CMD_DIRSHIFT) | \ + ((flags) << _CMD_FLAGSSHIFT) | \ + ((vtype) << _CMD_VTYPESHIFT) | \ + ((nr) << _CMD_NSHIFT)) +#define _CMDC(dir, vtype, nr) _CMDCF(dir, 0, vtype, nr) +#define _CMDCNW(dir, vtype, nr) _CMDCF(dir, _CMD_FLAGS_NOWAIT, vtype, nr) + +/* + * Used to decode cmds.. +*/ +#define _CMD_DIR(cmd) (((cmd) >> _CMD_DIRSHIFT) & _CMD_DIRMASK) +#define _CMD_FLAGS(cmd) (((cmd) >> _CMD_FLAGSSHIFT) & _CMD_FLAGSMASK) +#define _CMD_VTYPE(cmd) (((cmd) >> _CMD_VTYPESHIFT) & _CMD_VTYPEMASK) +#define _CMD_N(cmd) (((cmd) >> _CMD_NSHIFT) & _CMD_NMASK) + +enum vnic_devcmd_cmd { + CMD_NONE = _CMDC(_CMD_DIR_NONE, _CMD_VTYPE_NONE, 0), + + /* mcpu fw info in mem: (u64)a0=paddr to struct vnic_devcmd_fw_info */ + CMD_MCPU_FW_INFO = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ALL, 1), + + /* dev-specific block member: + * in: (u16)a0=offset,(u8)a1=size + * out: a0=value */ + CMD_DEV_SPEC = _CMDC(_CMD_DIR_RW, _CMD_VTYPE_ALL, 2), + + /* stats clear */ + CMD_STATS_CLEAR = _CMDCNW(_CMD_DIR_NONE, _CMD_VTYPE_ALL, 3), + + /* stats dump in mem: (u64)a0=paddr to stats area, + * (u16)a1=sizeof stats area */ + CMD_STATS_DUMP = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ALL, 4), + + /* set Rx packet filter: (u32)a0=filters (see CMD_PFILTER_*) */ + CMD_PACKET_FILTER = _CMDCNW(_CMD_DIR_WRITE, _CMD_VTYPE_ALL, 7), + + /* hang detection notification */ + CMD_HANG_NOTIFY = _CMDC(_CMD_DIR_NONE, _CMD_VTYPE_ALL, 8), + + /* MAC address in (u48)a0 */ + CMD_MAC_ADDR = _CMDC(_CMD_DIR_READ, + _CMD_VTYPE_ENET | _CMD_VTYPE_FC, 9), + + /* disable/enable promisc mode: (u8)a0=0/1 */ +/***** XXX DEPRECATED *****/ + CMD_PROMISC_MODE = _CMDCNW(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 10), + + /* disable/enable all-multi mode: (u8)a0=0/1 */ +/***** XXX DEPRECATED *****/ + CMD_ALLMULTI_MODE = _CMDCNW(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 11), + + /* add addr from (u48)a0 */ + CMD_ADDR_ADD = _CMDCNW(_CMD_DIR_WRITE, + _CMD_VTYPE_ENET | _CMD_VTYPE_FC, 12), + + /* del addr from (u48)a0 */ + CMD_ADDR_DEL = _CMDCNW(_CMD_DIR_WRITE, + _CMD_VTYPE_ENET | _CMD_VTYPE_FC, 13), + + /* add VLAN id in (u16)a0 */ + CMD_VLAN_ADD = _CMDCNW(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 14), + + /* del VLAN id in (u16)a0 */ + CMD_VLAN_DEL = _CMDCNW(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 15), + + /* nic_cfg in (u32)a0 */ + CMD_NIC_CFG = _CMDCNW(_CMD_DIR_WRITE, _CMD_VTYPE_ALL, 16), + + /* union vnic_rss_key in mem: (u64)a0=paddr, (u16)a1=len */ + CMD_RSS_KEY = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 17), + + /* union vnic_rss_cpu in mem: (u64)a0=paddr, (u16)a1=len */ + CMD_RSS_CPU = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 18), + + /* initiate softreset */ + CMD_SOFT_RESET = _CMDCNW(_CMD_DIR_NONE, _CMD_VTYPE_ALL, 19), + + /* softreset status: + * out: a0=0 reset complete, a0=1 reset in progress */ + CMD_SOFT_RESET_STATUS = _CMDC(_CMD_DIR_READ, _CMD_VTYPE_ALL, 20), + + /* set struct vnic_devcmd_notify buffer in mem: + * in: + * (u64)a0=paddr to notify (set paddr=0 to unset) + * (u32)a1 & 0x00000000ffffffff=sizeof(struct vnic_devcmd_notify) + * (u16)a1 & 0x0000ffff00000000=intr num (-1 for no intr) + * out: + * (u32)a1 = effective size + */ + CMD_NOTIFY = _CMDC(_CMD_DIR_RW, _CMD_VTYPE_ALL, 21), + + /* UNDI API: (u64)a0=paddr to s_PXENV_UNDI_ struct, + * (u8)a1=PXENV_UNDI_xxx */ + CMD_UNDI = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 22), + + /* initiate open sequence (u32)a0=flags (see CMD_OPENF_*) */ + CMD_OPEN = _CMDCNW(_CMD_DIR_WRITE, _CMD_VTYPE_ALL, 23), + + /* open status: + * out: a0=0 open complete, a0=1 open in progress */ + CMD_OPEN_STATUS = _CMDC(_CMD_DIR_READ, _CMD_VTYPE_ALL, 24), + + /* close vnic */ + CMD_CLOSE = _CMDC(_CMD_DIR_NONE, _CMD_VTYPE_ALL, 25), + + /* initialize virtual link: (u32)a0=flags (see CMD_INITF_*) */ + CMD_INIT = _CMDCNW(_CMD_DIR_READ, _CMD_VTYPE_ALL, 26), + + /* variant of CMD_INIT, with provisioning info + * (u64)a0=paddr of vnic_devcmd_provinfo + * (u32)a1=sizeof provision info + */ + CMD_INIT_PROV_INFO = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 27), + + /* enable virtual link */ + CMD_ENABLE = _CMDCNW(_CMD_DIR_WRITE, _CMD_VTYPE_ALL, 28), + + /* disable virtual link */ + CMD_DISABLE = _CMDC(_CMD_DIR_NONE, _CMD_VTYPE_ALL, 29), + + /* stats dump all vnics on uplink in mem: (u64)a0=paddr (u32)a1=uif */ + CMD_STATS_DUMP_ALL = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ALL, 30), + + /* init status: + * out: a0=0 init complete, a0=1 init in progress + * if a0=0, a1=errno */ + CMD_INIT_STATUS = _CMDC(_CMD_DIR_READ, _CMD_VTYPE_ALL, 31), + + /* INT13 API: (u64)a0=paddr to vnic_int13_params struct + * (u8)a1=INT13_CMD_xxx */ + CMD_INT13 = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_FC, 32), + + /* logical uplink enable/disable: (u64)a0: 0/1=disable/enable */ + CMD_LOGICAL_UPLINK = _CMDCNW(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 33), + + /* undo initialize of virtual link */ + CMD_DEINIT = _CMDCNW(_CMD_DIR_NONE, _CMD_VTYPE_ALL, 34), + + /* check fw capability of a cmd: + * in: (u32)a0=cmd + * out: (u32)a0=errno, 0:valid cmd, a1=supported VNIC_STF_* bits */ + CMD_CAPABILITY = _CMDC(_CMD_DIR_RW, _CMD_VTYPE_ALL, 36), + + /* persistent binding info + * in: (u64)a0=paddr of arg + * (u32)a1=CMD_PERBI_XXX */ + CMD_PERBI = _CMDC(_CMD_DIR_RW, _CMD_VTYPE_FC, 37), + + /* Interrupt Assert Register functionality + * in: (u16)a0=interrupt number to assert + */ + CMD_IAR = _CMDCNW(_CMD_DIR_WRITE, _CMD_VTYPE_ALL, 38), + + /* initiate hangreset, like softreset after hang detected */ + CMD_HANG_RESET = _CMDC(_CMD_DIR_NONE, _CMD_VTYPE_ALL, 39), + + /* hangreset status: + * out: a0=0 reset complete, a0=1 reset in progress */ + CMD_HANG_RESET_STATUS = _CMDC(_CMD_DIR_READ, _CMD_VTYPE_ALL, 40), + + /* + * Set hw ingress packet vlan rewrite mode: + * in: (u32)a0=new vlan rewrite mode + * out: (u32)a0=old vlan rewrite mode */ + CMD_IG_VLAN_REWRITE_MODE = _CMDC(_CMD_DIR_RW, _CMD_VTYPE_ENET, 41), + + /* + * in: (u16)a0=bdf of target vnic + * (u32)a1=cmd to proxy + * a2-a15=args to cmd in a1 + * out: (u32)a0=status of proxied cmd + * a1-a15=out args of proxied cmd */ + CMD_PROXY_BY_BDF = _CMDC(_CMD_DIR_RW, _CMD_VTYPE_ALL, 42), + + /* + * As for BY_BDF except a0 is index of hvnlink subordinate vnic + * or SR-IOV virtual vnic + */ + CMD_PROXY_BY_INDEX = _CMDC(_CMD_DIR_RW, _CMD_VTYPE_ALL, 43), + + /* + * For HPP toggle: + * adapter-info-get + * in: (u64)a0=phsical address of buffer passed in from caller. + * (u16)a1=size of buffer specified in a0. + * out: (u64)a0=phsical address of buffer passed in from caller. + * (u16)a1=actual bytes from VIF-CONFIG-INFO TLV, or + * 0 if no VIF-CONFIG-INFO TLV was ever received. */ + CMD_CONFIG_INFO_GET = _CMDC(_CMD_DIR_RW, _CMD_VTYPE_ALL, 44), + + /* + * INT13 API: (u64)a0=paddr to vnic_int13_params struct + * (u32)a1=INT13_CMD_xxx + */ + CMD_INT13_ALL = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ALL, 45), + + /* + * Set default vlan: + * in: (u16)a0=new default vlan + * (u16)a1=zero for overriding vlan with param a0, + * non-zero for resetting vlan to the default + * out: (u16)a0=old default vlan + */ + CMD_SET_DEFAULT_VLAN = _CMDC(_CMD_DIR_RW, _CMD_VTYPE_ALL, 46), + + /* init_prov_info2: + * Variant of CMD_INIT_PROV_INFO, where it will not try to enable + * the vnic until CMD_ENABLE2 is issued. + * (u64)a0=paddr of vnic_devcmd_provinfo + * (u32)a1=sizeof provision info + */ + CMD_INIT_PROV_INFO2 = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 47), + + /* enable2: + * (u32)a0=0 ==> standby + * =CMD_ENABLE2_ACTIVE ==> active + */ + CMD_ENABLE2 = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 48), + + /* + * cmd_status: + * Returns the status of the specified command + * Input: + * a0 = command for which status is being queried. + * Possible values are: + * CMD_SOFT_RESET + * CMD_HANG_RESET + * CMD_OPEN + * CMD_INIT + * CMD_INIT_PROV_INFO + * CMD_DEINIT + * CMD_INIT_PROV_INFO2 + * CMD_ENABLE2 + * Output: + * if status == STAT_ERROR + * a0 = ERR_ENOTSUPPORTED - status for command in a0 is + * not supported + * if status == STAT_NONE + * a0 = status of the devcmd specified in a0 as follows. + * ERR_SUCCESS - command in a0 completed successfully + * ERR_EINPROGRESS - command in a0 is still in progress + */ + CMD_STATUS = _CMDC(_CMD_DIR_RW, _CMD_VTYPE_ALL, 49), + + /* + * Returns interrupt coalescing timer conversion factors. + * After calling this devcmd, ENIC driver can convert + * interrupt coalescing timer in usec into CPU cycles as follows: + * + * intr_timer_cycles = intr_timer_usec * multiplier / divisor + * + * Interrupt coalescing timer in usecs can be be converted/obtained + * from CPU cycles as follows: + * + * intr_timer_usec = intr_timer_cycles * divisor / multiplier + * + * in: none + * out: (u32)a0 = multiplier + * (u32)a1 = divisor + * (u32)a2 = maximum timer value in usec + */ + CMD_INTR_COAL_CONVERT = _CMDC(_CMD_DIR_READ, _CMD_VTYPE_ALL, 50), + + /* + * ISCSI DUMP API: + * in: (u64)a0=paddr of the param or param itself + * (u32)a1=ISCSI_CMD_xxx + */ + CMD_ISCSI_DUMP_REQ = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ALL, 51), + + /* + * ISCSI DUMP STATUS API: + * in: (u32)a0=cmd tag + * in: (u32)a1=ISCSI_CMD_xxx + * out: (u32)a0=cmd status + */ + CMD_ISCSI_DUMP_STATUS = _CMDC(_CMD_DIR_RW, _CMD_VTYPE_ALL, 52), + + /* + * Subvnic migration from MQ <--> VF. + * Enable the LIF migration from MQ to VF and vice versa. MQ and VF + * indexes are statically bound at the time of initialization. + * Based on the + * direction of migration, the resources of either MQ or the VF shall + * be attached to the LIF. + * in: (u32)a0=Direction of Migration + * 0=> Migrate to VF + * 1=> Migrate to MQ + * (u32)a1=VF index (MQ index) + */ + CMD_MIGRATE_SUBVNIC = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 53), + + /* + * Register / Deregister the notification block for MQ subvnics + * in: + * (u64)a0=paddr to notify (set paddr=0 to unset) + * (u32)a1 & 0x00000000ffffffff=sizeof(struct vnic_devcmd_notify) + * (u16)a1 & 0x0000ffff00000000=intr num (-1 for no intr) + * out: + * (u32)a1 = effective size + */ + CMD_SUBVNIC_NOTIFY = _CMDC(_CMD_DIR_RW, _CMD_VTYPE_ALL, 54), + + /* + * Set the predefined mac address as default + * in: + * (u48)a0=mac addr + */ + CMD_SET_MAC_ADDR = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 55), + + /* Update the provisioning info of the given VIF + * (u64)a0=paddr of vnic_devcmd_provinfo + * (u32)a1=sizeof provision info + */ + CMD_PROV_INFO_UPDATE = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 56), + + /* + * Initialization for the devcmd2 interface. + * in: (u64) a0=host result buffer physical address + * in: (u16) a1=number of entries in result buffer + */ + CMD_INITIALIZE_DEVCMD2 = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ALL, 57) +}; + +/* flags for CMD_OPEN */ +#define CMD_OPENF_OPROM 0x1 /* open coming from option rom */ + +#define CMD_OPENF_RQ_ENABLE_THEN_POST 0x2 + +/* flags for CMD_INIT */ +#define CMD_INITF_DEFAULT_MAC 0x1 /* init with default mac addr */ + +/* flags for CMD_PACKET_FILTER */ +#define CMD_PFILTER_DIRECTED 0x01 +#define CMD_PFILTER_MULTICAST 0x02 +#define CMD_PFILTER_BROADCAST 0x04 +#define CMD_PFILTER_PROMISCUOUS 0x08 +#define CMD_PFILTER_ALL_MULTICAST 0x10 + +enum vnic_devcmd_status { + STAT_NONE = 0, + STAT_BUSY = 1 << 0, /* cmd in progress */ + STAT_ERROR = 1 << 1, /* last cmd caused error (code in a0) */ +}; + +enum vnic_devcmd_error { + ERR_SUCCESS = 0, + ERR_EINVAL = 1, + ERR_EFAULT = 2, + ERR_EPERM = 3, + ERR_EBUSY = 4, + ERR_ECMDUNKNOWN = 5, + ERR_EBADSTATE = 6, + ERR_ENOMEM = 7, + ERR_ETIMEDOUT = 8, + ERR_ELINKDOWN = 9, +}; + +struct vnic_devcmd_fw_info { + char fw_version[32]; + char fw_build[32]; + char hw_version[32]; + char hw_serial_number[32]; +}; + +struct vnic_devcmd_notify { + u32 csum; /* checksum over following words */ + + u32 link_state; /* link up == 1 */ + u32 port_speed; /* effective port speed (rate limit) */ + u32 mtu; /* MTU */ + u32 msglvl; /* requested driver msg lvl */ + u32 uif; /* uplink interface */ + u32 status; /* status bits (see VNIC_STF_*) */ + u32 error; /* error code (see ERR_*) for first ERR */ + u32 link_down_cnt; /* running count of link down transitions */ +}; +#define VNIC_STF_FATAL_ERR 0x0001 /* fatal fw error */ + +struct vnic_devcmd_provinfo { + u8 oui[3]; + u8 type; + u8 data[0]; +}; + +/* + * Writing cmd register causes STAT_BUSY to get set in status register. + * When cmd completes, STAT_BUSY will be cleared. + * + * If cmd completed successfully STAT_ERROR will be clear + * and args registers contain cmd-specific results. + * + * If cmd error, STAT_ERROR will be set and args[0] contains error code. + * + * status register is read-only. While STAT_BUSY is set, + * all other register contents are read-only. + */ + +/* Make sizeof(vnic_devcmd) a power-of-2 for I/O BAR. */ +#define VNIC_DEVCMD_NARGS 15 +struct vnic_devcmd { + u32 status; /* RO */ + u32 cmd; /* RW */ + u64 args[VNIC_DEVCMD_NARGS]; /* RW cmd args (little-endian) */ +}; + +/* + * Version 2 of the interface. + * + * Some things are carried over, notably the vnic_devcmd_cmd enum. + */ + +/* + * Flags for vnic_devcmd2.flags + */ + +#define DEVCMD2_FNORESULT 0x1 /* Don't copy result to host */ + +#define VNIC_DEVCMD2_NARGS VNIC_DEVCMD_NARGS + +struct vnic_devcmd2 { + u16 pad; + u16 flags; + u32 cmd; /* same command #defines as original */ + u64 args[VNIC_DEVCMD2_NARGS]; +}; + +#define VNIC_DEVCMD2_NRESULTS VNIC_DEVCMD_NARGS +struct devcmd2_result { + u64 results[VNIC_DEVCMD2_NRESULTS]; + u32 pad; + u16 completed_index; /* into copy WQ */ + u8 error; /* same error codes as original */ + u8 color; /* 0 or 1 as with completion queues */ +}; + +#define DEVCMD2_RING_SIZE 32 +#define DEVCMD2_DESC_SIZE 128 + +#define DEVCMD2_RESULTS_SIZE_MAX ((1 << 16) - 1) + +#endif /* _VNIC_DEVCMD_H_ */ diff --git a/drivers/scsi/fnic/vnic_intr.c b/drivers/scsi/fnic/vnic_intr.c new file mode 100644 index 000000000..df7f63acd --- /dev/null +++ b/drivers/scsi/fnic/vnic_intr.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include "vnic_dev.h" +#include "vnic_intr.h" + +void vnic_intr_free(struct vnic_intr *intr) +{ + intr->ctrl = NULL; +} + +int vnic_intr_alloc(struct vnic_dev *vdev, struct vnic_intr *intr, + unsigned int index) +{ + intr->index = index; + intr->vdev = vdev; + + intr->ctrl = vnic_dev_get_res(vdev, RES_TYPE_INTR_CTRL, index); + if (!intr->ctrl) { + printk(KERN_ERR "Failed to hook INTR[%d].ctrl resource\n", + index); + return -EINVAL; + } + + return 0; +} + +void vnic_intr_init(struct vnic_intr *intr, unsigned int coalescing_timer, + unsigned int coalescing_type, unsigned int mask_on_assertion) +{ + iowrite32(coalescing_timer, &intr->ctrl->coalescing_timer); + iowrite32(coalescing_type, &intr->ctrl->coalescing_type); + iowrite32(mask_on_assertion, &intr->ctrl->mask_on_assertion); + iowrite32(0, &intr->ctrl->int_credits); +} + +void vnic_intr_clean(struct vnic_intr *intr) +{ + iowrite32(0, &intr->ctrl->int_credits); +} diff --git a/drivers/scsi/fnic/vnic_intr.h b/drivers/scsi/fnic/vnic_intr.h new file mode 100644 index 000000000..acc194c0f --- /dev/null +++ b/drivers/scsi/fnic/vnic_intr.h @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _VNIC_INTR_H_ +#define _VNIC_INTR_H_ + +#include <linux/pci.h> +#include "vnic_dev.h" + +/* + * These defines avoid symbol clash between fnic and enic (Cisco 10G Eth + * Driver) when both are built with CONFIG options =y + */ +#define vnic_intr_unmask fnic_intr_unmask +#define vnic_intr_mask fnic_intr_mask +#define vnic_intr_return_credits fnic_intr_return_credits +#define vnic_intr_credits fnic_intr_credits +#define vnic_intr_return_all_credits fnic_intr_return_all_credits +#define vnic_intr_legacy_pba fnic_intr_legacy_pba +#define vnic_intr_free fnic_intr_free +#define vnic_intr_alloc fnic_intr_alloc +#define vnic_intr_init fnic_intr_init +#define vnic_intr_clean fnic_intr_clean + +#define VNIC_INTR_TIMER_MAX 0xffff + +#define VNIC_INTR_TIMER_TYPE_ABS 0 +#define VNIC_INTR_TIMER_TYPE_QUIET 1 + +/* Interrupt control */ +struct vnic_intr_ctrl { + u32 coalescing_timer; /* 0x00 */ + u32 pad0; + u32 coalescing_value; /* 0x08 */ + u32 pad1; + u32 coalescing_type; /* 0x10 */ + u32 pad2; + u32 mask_on_assertion; /* 0x18 */ + u32 pad3; + u32 mask; /* 0x20 */ + u32 pad4; + u32 int_credits; /* 0x28 */ + u32 pad5; + u32 int_credit_return; /* 0x30 */ + u32 pad6; +}; + +struct vnic_intr { + unsigned int index; + struct vnic_dev *vdev; + struct vnic_intr_ctrl __iomem *ctrl; /* memory-mapped */ +}; + +static inline void vnic_intr_unmask(struct vnic_intr *intr) +{ + iowrite32(0, &intr->ctrl->mask); +} + +static inline void vnic_intr_mask(struct vnic_intr *intr) +{ + iowrite32(1, &intr->ctrl->mask); +} + +static inline void vnic_intr_return_credits(struct vnic_intr *intr, + unsigned int credits, int unmask, int reset_timer) +{ +#define VNIC_INTR_UNMASK_SHIFT 16 +#define VNIC_INTR_RESET_TIMER_SHIFT 17 + + u32 int_credit_return = (credits & 0xffff) | + (unmask ? (1 << VNIC_INTR_UNMASK_SHIFT) : 0) | + (reset_timer ? (1 << VNIC_INTR_RESET_TIMER_SHIFT) : 0); + + iowrite32(int_credit_return, &intr->ctrl->int_credit_return); +} + +static inline unsigned int vnic_intr_credits(struct vnic_intr *intr) +{ + return ioread32(&intr->ctrl->int_credits); +} + +static inline void vnic_intr_return_all_credits(struct vnic_intr *intr) +{ + unsigned int credits = vnic_intr_credits(intr); + int unmask = 1; + int reset_timer = 1; + + vnic_intr_return_credits(intr, credits, unmask, reset_timer); +} + +static inline u32 vnic_intr_legacy_pba(u32 __iomem *legacy_pba) +{ + /* read PBA without clearing */ + return ioread32(legacy_pba); +} + +void vnic_intr_free(struct vnic_intr *intr); +int vnic_intr_alloc(struct vnic_dev *vdev, struct vnic_intr *intr, + unsigned int index); +void vnic_intr_init(struct vnic_intr *intr, unsigned int coalescing_timer, + unsigned int coalescing_type, unsigned int mask_on_assertion); +void vnic_intr_clean(struct vnic_intr *intr); + +#endif /* _VNIC_INTR_H_ */ diff --git a/drivers/scsi/fnic/vnic_nic.h b/drivers/scsi/fnic/vnic_nic.h new file mode 100644 index 000000000..6896f16d5 --- /dev/null +++ b/drivers/scsi/fnic/vnic_nic.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _VNIC_NIC_H_ +#define _VNIC_NIC_H_ + +/* + * These defines avoid symbol clash between fnic and enic (Cisco 10G Eth + * Driver) when both are built with CONFIG options =y + */ +#define vnic_set_nic_cfg fnic_set_nic_cfg + +#define NIC_CFG_RSS_DEFAULT_CPU_MASK_FIELD 0xffUL +#define NIC_CFG_RSS_DEFAULT_CPU_SHIFT 0 +#define NIC_CFG_RSS_HASH_TYPE (0xffUL << 8) +#define NIC_CFG_RSS_HASH_TYPE_MASK_FIELD 0xffUL +#define NIC_CFG_RSS_HASH_TYPE_SHIFT 8 +#define NIC_CFG_RSS_HASH_BITS (7UL << 16) +#define NIC_CFG_RSS_HASH_BITS_MASK_FIELD 7UL +#define NIC_CFG_RSS_HASH_BITS_SHIFT 16 +#define NIC_CFG_RSS_BASE_CPU (7UL << 19) +#define NIC_CFG_RSS_BASE_CPU_MASK_FIELD 7UL +#define NIC_CFG_RSS_BASE_CPU_SHIFT 19 +#define NIC_CFG_RSS_ENABLE (1UL << 22) +#define NIC_CFG_RSS_ENABLE_MASK_FIELD 1UL +#define NIC_CFG_RSS_ENABLE_SHIFT 22 +#define NIC_CFG_TSO_IPID_SPLIT_EN (1UL << 23) +#define NIC_CFG_TSO_IPID_SPLIT_EN_MASK_FIELD 1UL +#define NIC_CFG_TSO_IPID_SPLIT_EN_SHIFT 23 +#define NIC_CFG_IG_VLAN_STRIP_EN (1UL << 24) +#define NIC_CFG_IG_VLAN_STRIP_EN_MASK_FIELD 1UL +#define NIC_CFG_IG_VLAN_STRIP_EN_SHIFT 24 + +static inline void vnic_set_nic_cfg(u32 *nic_cfg, + u8 rss_default_cpu, u8 rss_hash_type, + u8 rss_hash_bits, u8 rss_base_cpu, + u8 rss_enable, u8 tso_ipid_split_en, + u8 ig_vlan_strip_en) +{ + *nic_cfg = (rss_default_cpu & NIC_CFG_RSS_DEFAULT_CPU_MASK_FIELD) | + ((rss_hash_type & NIC_CFG_RSS_HASH_TYPE_MASK_FIELD) + << NIC_CFG_RSS_HASH_TYPE_SHIFT) | + ((rss_hash_bits & NIC_CFG_RSS_HASH_BITS_MASK_FIELD) + << NIC_CFG_RSS_HASH_BITS_SHIFT) | + ((rss_base_cpu & NIC_CFG_RSS_BASE_CPU_MASK_FIELD) + << NIC_CFG_RSS_BASE_CPU_SHIFT) | + ((rss_enable & NIC_CFG_RSS_ENABLE_MASK_FIELD) + << NIC_CFG_RSS_ENABLE_SHIFT) | + ((tso_ipid_split_en & NIC_CFG_TSO_IPID_SPLIT_EN_MASK_FIELD) + << NIC_CFG_TSO_IPID_SPLIT_EN_SHIFT) | + ((ig_vlan_strip_en & NIC_CFG_IG_VLAN_STRIP_EN_MASK_FIELD) + << NIC_CFG_IG_VLAN_STRIP_EN_SHIFT); +} + +#endif /* _VNIC_NIC_H_ */ diff --git a/drivers/scsi/fnic/vnic_resource.h b/drivers/scsi/fnic/vnic_resource.h new file mode 100644 index 000000000..3d260b831 --- /dev/null +++ b/drivers/scsi/fnic/vnic_resource.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _VNIC_RESOURCE_H_ +#define _VNIC_RESOURCE_H_ + +#define VNIC_RES_MAGIC 0x766E6963L /* 'vnic' */ +#define VNIC_RES_VERSION 0x00000000L + +/* vNIC resource types */ +enum vnic_res_type { + RES_TYPE_EOL, /* End-of-list */ + RES_TYPE_WQ, /* Work queues */ + RES_TYPE_RQ, /* Receive queues */ + RES_TYPE_CQ, /* Completion queues */ + RES_TYPE_RSVD1, + RES_TYPE_NIC_CFG, /* Enet NIC config registers */ + RES_TYPE_RSVD2, + RES_TYPE_RSVD3, + RES_TYPE_RSVD4, + RES_TYPE_RSVD5, + RES_TYPE_INTR_CTRL, /* Interrupt ctrl table */ + RES_TYPE_INTR_TABLE, /* MSI/MSI-X Interrupt table */ + RES_TYPE_INTR_PBA, /* MSI/MSI-X PBA table */ + RES_TYPE_INTR_PBA_LEGACY, /* Legacy intr status */ + RES_TYPE_RSVD6, + RES_TYPE_RSVD7, + RES_TYPE_DEVCMD, /* Device command region */ + RES_TYPE_PASS_THRU_PAGE, /* Pass-thru page */ + RES_TYPE_SUBVNIC, /* subvnic resource type */ + RES_TYPE_MQ_WQ, /* MQ Work queues */ + RES_TYPE_MQ_RQ, /* MQ Receive queues */ + RES_TYPE_MQ_CQ, /* MQ Completion queues */ + RES_TYPE_DEPRECATED1, /* Old version of devcmd 2 */ + RES_TYPE_DEPRECATED2, /* Old version of devcmd 2 */ + RES_TYPE_DEVCMD2, /* Device control region */ + + RES_TYPE_MAX, /* Count of resource types */ +}; + +struct vnic_resource_header { + u32 magic; + u32 version; +}; + +struct vnic_resource { + u8 type; + u8 bar; + u8 pad[2]; + u32 bar_offset; + u32 count; +}; + +#endif /* _VNIC_RESOURCE_H_ */ diff --git a/drivers/scsi/fnic/vnic_rq.c b/drivers/scsi/fnic/vnic_rq.c new file mode 100644 index 000000000..350607d13 --- /dev/null +++ b/drivers/scsi/fnic/vnic_rq.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ + +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include "vnic_dev.h" +#include "vnic_rq.h" + +static int vnic_rq_alloc_bufs(struct vnic_rq *rq) +{ + struct vnic_rq_buf *buf; + unsigned int i, j, count = rq->ring.desc_count; + unsigned int blks = VNIC_RQ_BUF_BLKS_NEEDED(count); + + for (i = 0; i < blks; i++) { + rq->bufs[i] = kzalloc(VNIC_RQ_BUF_BLK_SZ, GFP_ATOMIC); + if (!rq->bufs[i]) { + printk(KERN_ERR "Failed to alloc rq_bufs\n"); + return -ENOMEM; + } + } + + for (i = 0; i < blks; i++) { + buf = rq->bufs[i]; + for (j = 0; j < VNIC_RQ_BUF_BLK_ENTRIES; j++) { + buf->index = i * VNIC_RQ_BUF_BLK_ENTRIES + j; + buf->desc = (u8 *)rq->ring.descs + + rq->ring.desc_size * buf->index; + if (buf->index + 1 == count) { + buf->next = rq->bufs[0]; + break; + } else if (j + 1 == VNIC_RQ_BUF_BLK_ENTRIES) { + buf->next = rq->bufs[i + 1]; + } else { + buf->next = buf + 1; + buf++; + } + } + } + + rq->to_use = rq->to_clean = rq->bufs[0]; + rq->buf_index = 0; + + return 0; +} + +void vnic_rq_free(struct vnic_rq *rq) +{ + struct vnic_dev *vdev; + unsigned int i; + + vdev = rq->vdev; + + vnic_dev_free_desc_ring(vdev, &rq->ring); + + for (i = 0; i < VNIC_RQ_BUF_BLKS_MAX; i++) { + kfree(rq->bufs[i]); + rq->bufs[i] = NULL; + } + + rq->ctrl = NULL; +} + +int vnic_rq_alloc(struct vnic_dev *vdev, struct vnic_rq *rq, unsigned int index, + unsigned int desc_count, unsigned int desc_size) +{ + int err; + + rq->index = index; + rq->vdev = vdev; + + rq->ctrl = vnic_dev_get_res(vdev, RES_TYPE_RQ, index); + if (!rq->ctrl) { + printk(KERN_ERR "Failed to hook RQ[%d] resource\n", index); + return -EINVAL; + } + + vnic_rq_disable(rq); + + err = vnic_dev_alloc_desc_ring(vdev, &rq->ring, desc_count, desc_size); + if (err) + return err; + + err = vnic_rq_alloc_bufs(rq); + if (err) { + vnic_rq_free(rq); + return err; + } + + return 0; +} + +void vnic_rq_init(struct vnic_rq *rq, unsigned int cq_index, + unsigned int error_interrupt_enable, + unsigned int error_interrupt_offset) +{ + u64 paddr; + u32 fetch_index; + + paddr = (u64)rq->ring.base_addr | VNIC_PADDR_TARGET; + writeq(paddr, &rq->ctrl->ring_base); + iowrite32(rq->ring.desc_count, &rq->ctrl->ring_size); + iowrite32(cq_index, &rq->ctrl->cq_index); + iowrite32(error_interrupt_enable, &rq->ctrl->error_interrupt_enable); + iowrite32(error_interrupt_offset, &rq->ctrl->error_interrupt_offset); + iowrite32(0, &rq->ctrl->dropped_packet_count); + iowrite32(0, &rq->ctrl->error_status); + + /* Use current fetch_index as the ring starting point */ + fetch_index = ioread32(&rq->ctrl->fetch_index); + rq->to_use = rq->to_clean = + &rq->bufs[fetch_index / VNIC_RQ_BUF_BLK_ENTRIES] + [fetch_index % VNIC_RQ_BUF_BLK_ENTRIES]; + iowrite32(fetch_index, &rq->ctrl->posted_index); + + rq->buf_index = 0; +} + +unsigned int vnic_rq_error_status(struct vnic_rq *rq) +{ + return ioread32(&rq->ctrl->error_status); +} + +void vnic_rq_enable(struct vnic_rq *rq) +{ + iowrite32(1, &rq->ctrl->enable); +} + +int vnic_rq_disable(struct vnic_rq *rq) +{ + unsigned int wait; + + iowrite32(0, &rq->ctrl->enable); + + /* Wait for HW to ACK disable request */ + for (wait = 0; wait < 100; wait++) { + if (!(ioread32(&rq->ctrl->running))) + return 0; + udelay(1); + } + + printk(KERN_ERR "Failed to disable RQ[%d]\n", rq->index); + + return -ETIMEDOUT; +} + +void vnic_rq_clean(struct vnic_rq *rq, + void (*buf_clean)(struct vnic_rq *rq, struct vnic_rq_buf *buf)) +{ + struct vnic_rq_buf *buf; + u32 fetch_index; + + WARN_ON(ioread32(&rq->ctrl->enable)); + + buf = rq->to_clean; + + while (vnic_rq_desc_used(rq) > 0) { + + (*buf_clean)(rq, buf); + + buf = rq->to_clean = buf->next; + rq->ring.desc_avail++; + } + + /* Use current fetch_index as the ring starting point */ + fetch_index = ioread32(&rq->ctrl->fetch_index); + rq->to_use = rq->to_clean = + &rq->bufs[fetch_index / VNIC_RQ_BUF_BLK_ENTRIES] + [fetch_index % VNIC_RQ_BUF_BLK_ENTRIES]; + iowrite32(fetch_index, &rq->ctrl->posted_index); + + rq->buf_index = 0; + + vnic_dev_clear_desc_ring(&rq->ring); +} diff --git a/drivers/scsi/fnic/vnic_rq.h b/drivers/scsi/fnic/vnic_rq.h new file mode 100644 index 000000000..1066255de --- /dev/null +++ b/drivers/scsi/fnic/vnic_rq.h @@ -0,0 +1,223 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _VNIC_RQ_H_ +#define _VNIC_RQ_H_ + +#include <linux/pci.h> +#include "vnic_dev.h" +#include "vnic_cq.h" + +/* + * These defines avoid symbol clash between fnic and enic (Cisco 10G Eth + * Driver) when both are built with CONFIG options =y + */ +#define vnic_rq_desc_avail fnic_rq_desc_avail +#define vnic_rq_desc_used fnic_rq_desc_used +#define vnic_rq_next_desc fnic_rq_next_desc +#define vnic_rq_next_index fnic_rq_next_index +#define vnic_rq_next_buf_index fnic_rq_next_buf_index +#define vnic_rq_post fnic_rq_post +#define vnic_rq_posting_soon fnic_rq_posting_soon +#define vnic_rq_return_descs fnic_rq_return_descs +#define vnic_rq_service fnic_rq_service +#define vnic_rq_fill fnic_rq_fill +#define vnic_rq_free fnic_rq_free +#define vnic_rq_alloc fnic_rq_alloc +#define vnic_rq_init fnic_rq_init +#define vnic_rq_error_status fnic_rq_error_status +#define vnic_rq_enable fnic_rq_enable +#define vnic_rq_disable fnic_rq_disable +#define vnic_rq_clean fnic_rq_clean + +/* Receive queue control */ +struct vnic_rq_ctrl { + u64 ring_base; /* 0x00 */ + u32 ring_size; /* 0x08 */ + u32 pad0; + u32 posted_index; /* 0x10 */ + u32 pad1; + u32 cq_index; /* 0x18 */ + u32 pad2; + u32 enable; /* 0x20 */ + u32 pad3; + u32 running; /* 0x28 */ + u32 pad4; + u32 fetch_index; /* 0x30 */ + u32 pad5; + u32 error_interrupt_enable; /* 0x38 */ + u32 pad6; + u32 error_interrupt_offset; /* 0x40 */ + u32 pad7; + u32 error_status; /* 0x48 */ + u32 pad8; + u32 dropped_packet_count; /* 0x50 */ + u32 pad9; + u32 dropped_packet_count_rc; /* 0x58 */ + u32 pad10; +}; + +/* Break the vnic_rq_buf allocations into blocks of 64 entries */ +#define VNIC_RQ_BUF_BLK_ENTRIES 64 +#define VNIC_RQ_BUF_BLK_SZ \ + (VNIC_RQ_BUF_BLK_ENTRIES * sizeof(struct vnic_rq_buf)) +#define VNIC_RQ_BUF_BLKS_NEEDED(entries) \ + DIV_ROUND_UP(entries, VNIC_RQ_BUF_BLK_ENTRIES) +#define VNIC_RQ_BUF_BLKS_MAX VNIC_RQ_BUF_BLKS_NEEDED(4096) + +struct vnic_rq_buf { + struct vnic_rq_buf *next; + dma_addr_t dma_addr; + void *os_buf; + unsigned int os_buf_index; + unsigned int len; + unsigned int index; + void *desc; +}; + +struct vnic_rq { + unsigned int index; + struct vnic_dev *vdev; + struct vnic_rq_ctrl __iomem *ctrl; /* memory-mapped */ + struct vnic_dev_ring ring; + struct vnic_rq_buf *bufs[VNIC_RQ_BUF_BLKS_MAX]; + struct vnic_rq_buf *to_use; + struct vnic_rq_buf *to_clean; + void *os_buf_head; + unsigned int buf_index; + unsigned int pkts_outstanding; +}; + +static inline unsigned int vnic_rq_desc_avail(struct vnic_rq *rq) +{ + /* how many does SW own? */ + return rq->ring.desc_avail; +} + +static inline unsigned int vnic_rq_desc_used(struct vnic_rq *rq) +{ + /* how many does HW own? */ + return rq->ring.desc_count - rq->ring.desc_avail - 1; +} + +static inline void *vnic_rq_next_desc(struct vnic_rq *rq) +{ + return rq->to_use->desc; +} + +static inline unsigned int vnic_rq_next_index(struct vnic_rq *rq) +{ + return rq->to_use->index; +} + +static inline unsigned int vnic_rq_next_buf_index(struct vnic_rq *rq) +{ + return rq->buf_index++; +} + +static inline void vnic_rq_post(struct vnic_rq *rq, + void *os_buf, unsigned int os_buf_index, + dma_addr_t dma_addr, unsigned int len) +{ + struct vnic_rq_buf *buf = rq->to_use; + + buf->os_buf = os_buf; + buf->os_buf_index = os_buf_index; + buf->dma_addr = dma_addr; + buf->len = len; + + buf = buf->next; + rq->to_use = buf; + rq->ring.desc_avail--; + + /* Move the posted_index every nth descriptor + */ + +#ifndef VNIC_RQ_RETURN_RATE +#define VNIC_RQ_RETURN_RATE 0xf /* keep 2^n - 1 */ +#endif + + if ((buf->index & VNIC_RQ_RETURN_RATE) == 0) { + /* Adding write memory barrier prevents compiler and/or CPU + * reordering, thus avoiding descriptor posting before + * descriptor is initialized. Otherwise, hardware can read + * stale descriptor fields. + */ + wmb(); + iowrite32(buf->index, &rq->ctrl->posted_index); + } +} + +static inline int vnic_rq_posting_soon(struct vnic_rq *rq) +{ + return (rq->to_use->index & VNIC_RQ_RETURN_RATE) == 0; +} + +static inline void vnic_rq_return_descs(struct vnic_rq *rq, unsigned int count) +{ + rq->ring.desc_avail += count; +} + +enum desc_return_options { + VNIC_RQ_RETURN_DESC, + VNIC_RQ_DEFER_RETURN_DESC, +}; + +static inline void vnic_rq_service(struct vnic_rq *rq, + struct cq_desc *cq_desc, u16 completed_index, + int desc_return, void (*buf_service)(struct vnic_rq *rq, + struct cq_desc *cq_desc, struct vnic_rq_buf *buf, + int skipped, void *opaque), void *opaque) +{ + struct vnic_rq_buf *buf; + int skipped; + + buf = rq->to_clean; + while (1) { + + skipped = (buf->index != completed_index); + + (*buf_service)(rq, cq_desc, buf, skipped, opaque); + + if (desc_return == VNIC_RQ_RETURN_DESC) + rq->ring.desc_avail++; + + rq->to_clean = buf->next; + + if (!skipped) + break; + + buf = rq->to_clean; + } +} + +static inline int vnic_rq_fill(struct vnic_rq *rq, + int (*buf_fill)(struct vnic_rq *rq)) +{ + int err; + + while (vnic_rq_desc_avail(rq) > 1) { + + err = (*buf_fill)(rq); + if (err) + return err; + } + + return 0; +} + +void vnic_rq_free(struct vnic_rq *rq); +int vnic_rq_alloc(struct vnic_dev *vdev, struct vnic_rq *rq, unsigned int index, + unsigned int desc_count, unsigned int desc_size); +void vnic_rq_init(struct vnic_rq *rq, unsigned int cq_index, + unsigned int error_interrupt_enable, + unsigned int error_interrupt_offset); +unsigned int vnic_rq_error_status(struct vnic_rq *rq); +void vnic_rq_enable(struct vnic_rq *rq); +int vnic_rq_disable(struct vnic_rq *rq); +void vnic_rq_clean(struct vnic_rq *rq, + void (*buf_clean)(struct vnic_rq *rq, struct vnic_rq_buf *buf)); + +#endif /* _VNIC_RQ_H_ */ diff --git a/drivers/scsi/fnic/vnic_scsi.h b/drivers/scsi/fnic/vnic_scsi.h new file mode 100644 index 000000000..f715f7942 --- /dev/null +++ b/drivers/scsi/fnic/vnic_scsi.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _VNIC_SCSI_H_ +#define _VNIC_SCSI_H_ + +#define VNIC_FNIC_WQ_COPY_COUNT_MIN 1 +#define VNIC_FNIC_WQ_COPY_COUNT_MAX 1 + +#define VNIC_FNIC_WQ_DESCS_MIN 64 +#define VNIC_FNIC_WQ_DESCS_MAX 128 + +#define VNIC_FNIC_WQ_COPY_DESCS_MIN 64 +#define VNIC_FNIC_WQ_COPY_DESCS_MAX 512 + +#define VNIC_FNIC_RQ_DESCS_MIN 64 +#define VNIC_FNIC_RQ_DESCS_MAX 128 + +#define VNIC_FNIC_EDTOV_MIN 1000 +#define VNIC_FNIC_EDTOV_MAX 255000 +#define VNIC_FNIC_EDTOV_DEF 2000 + +#define VNIC_FNIC_RATOV_MIN 1000 +#define VNIC_FNIC_RATOV_MAX 255000 + +#define VNIC_FNIC_MAXDATAFIELDSIZE_MIN 256 +#define VNIC_FNIC_MAXDATAFIELDSIZE_MAX 2048 + +#define VNIC_FNIC_FLOGI_RETRIES_MIN 0 +#define VNIC_FNIC_FLOGI_RETRIES_MAX 0xffffffff +#define VNIC_FNIC_FLOGI_RETRIES_DEF 0xffffffff + +#define VNIC_FNIC_FLOGI_TIMEOUT_MIN 1000 +#define VNIC_FNIC_FLOGI_TIMEOUT_MAX 255000 + +#define VNIC_FNIC_PLOGI_RETRIES_MIN 0 +#define VNIC_FNIC_PLOGI_RETRIES_MAX 255 +#define VNIC_FNIC_PLOGI_RETRIES_DEF 8 + +#define VNIC_FNIC_PLOGI_TIMEOUT_MIN 1000 +#define VNIC_FNIC_PLOGI_TIMEOUT_MAX 255000 + +#define VNIC_FNIC_IO_THROTTLE_COUNT_MIN 1 +#define VNIC_FNIC_IO_THROTTLE_COUNT_MAX 2048 + +#define VNIC_FNIC_LINK_DOWN_TIMEOUT_MIN 0 +#define VNIC_FNIC_LINK_DOWN_TIMEOUT_MAX 240000 + +#define VNIC_FNIC_PORT_DOWN_TIMEOUT_MIN 0 +#define VNIC_FNIC_PORT_DOWN_TIMEOUT_MAX 240000 + +#define VNIC_FNIC_PORT_DOWN_IO_RETRIES_MIN 0 +#define VNIC_FNIC_PORT_DOWN_IO_RETRIES_MAX 255 + +#define VNIC_FNIC_LUNS_PER_TARGET_MIN 1 +#define VNIC_FNIC_LUNS_PER_TARGET_MAX 4096 + +/* Device-specific region: scsi configuration */ +struct vnic_fc_config { + u64 node_wwn; + u64 port_wwn; + u32 flags; + u32 wq_enet_desc_count; + u32 wq_copy_desc_count; + u32 rq_desc_count; + u32 flogi_retries; + u32 flogi_timeout; + u32 plogi_retries; + u32 plogi_timeout; + u32 io_throttle_count; + u32 link_down_timeout; + u32 port_down_timeout; + u32 port_down_io_retries; + u32 luns_per_tgt; + u16 maxdatafieldsize; + u16 ed_tov; + u16 ra_tov; + u16 intr_timer; + u8 intr_timer_type; + u8 intr_mode; + u8 lun_queue_depth; + u8 io_timeout_retry; + u16 wq_copy_count; +}; + +#define VFCF_FCP_SEQ_LVL_ERR 0x1 /* Enable FCP-2 Error Recovery */ +#define VFCF_PERBI 0x2 /* persistent binding info available */ +#define VFCF_FIP_CAPABLE 0x4 /* firmware can handle FIP */ + +#define VFCF_FC_INITIATOR 0x20 /* FC Initiator Mode */ +#define VFCF_FC_TARGET 0x40 /* FC Target Mode */ +#define VFCF_FC_NVME_INITIATOR 0x80 /* FC-NVMe Initiator Mode */ +#define VFCF_FC_NVME_TARGET 0x100 /* FC-NVMe Target Mode */ + +#endif /* _VNIC_SCSI_H_ */ diff --git a/drivers/scsi/fnic/vnic_stats.h b/drivers/scsi/fnic/vnic_stats.h new file mode 100644 index 000000000..4396397b0 --- /dev/null +++ b/drivers/scsi/fnic/vnic_stats.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _VNIC_STATS_H_ +#define _VNIC_STATS_H_ + +/* Tx statistics */ +struct vnic_tx_stats { + u64 tx_frames_ok; + u64 tx_unicast_frames_ok; + u64 tx_multicast_frames_ok; + u64 tx_broadcast_frames_ok; + u64 tx_bytes_ok; + u64 tx_unicast_bytes_ok; + u64 tx_multicast_bytes_ok; + u64 tx_broadcast_bytes_ok; + u64 tx_drops; + u64 tx_errors; + u64 tx_tso; + u64 rsvd[16]; +}; + +/* Rx statistics */ +struct vnic_rx_stats { + u64 rx_frames_ok; + u64 rx_frames_total; + u64 rx_unicast_frames_ok; + u64 rx_multicast_frames_ok; + u64 rx_broadcast_frames_ok; + u64 rx_bytes_ok; + u64 rx_unicast_bytes_ok; + u64 rx_multicast_bytes_ok; + u64 rx_broadcast_bytes_ok; + u64 rx_drop; + u64 rx_no_bufs; + u64 rx_errors; + u64 rx_rss; + u64 rx_crc_errors; + u64 rx_frames_64; + u64 rx_frames_127; + u64 rx_frames_255; + u64 rx_frames_511; + u64 rx_frames_1023; + u64 rx_frames_1518; + u64 rx_frames_to_max; + u64 rsvd[16]; +}; + +struct vnic_stats { + struct vnic_tx_stats tx; + struct vnic_rx_stats rx; +}; + +#endif /* _VNIC_STATS_H_ */ diff --git a/drivers/scsi/fnic/vnic_wq.c b/drivers/scsi/fnic/vnic_wq.c new file mode 100644 index 000000000..426b901c8 --- /dev/null +++ b/drivers/scsi/fnic/vnic_wq.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ + +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include "vnic_dev.h" +#include "vnic_wq.h" + + +static int vnic_wq_get_ctrl(struct vnic_dev *vdev, struct vnic_wq *wq, + unsigned int index, enum vnic_res_type res_type) +{ + wq->ctrl = vnic_dev_get_res(vdev, res_type, index); + + if (!wq->ctrl) + return -EINVAL; + + return 0; +} + + +static int vnic_wq_alloc_ring(struct vnic_dev *vdev, struct vnic_wq *wq, + unsigned int desc_count, unsigned int desc_size) +{ + return vnic_dev_alloc_desc_ring(vdev, &wq->ring, desc_count, desc_size); +} + + +static int vnic_wq_alloc_bufs(struct vnic_wq *wq) +{ + struct vnic_wq_buf *buf; + unsigned int i, j, count = wq->ring.desc_count; + unsigned int blks = VNIC_WQ_BUF_BLKS_NEEDED(count); + + for (i = 0; i < blks; i++) { + wq->bufs[i] = kzalloc(VNIC_WQ_BUF_BLK_SZ, GFP_ATOMIC); + if (!wq->bufs[i]) { + printk(KERN_ERR "Failed to alloc wq_bufs\n"); + return -ENOMEM; + } + } + + for (i = 0; i < blks; i++) { + buf = wq->bufs[i]; + for (j = 0; j < VNIC_WQ_BUF_BLK_ENTRIES; j++) { + buf->index = i * VNIC_WQ_BUF_BLK_ENTRIES + j; + buf->desc = (u8 *)wq->ring.descs + + wq->ring.desc_size * buf->index; + if (buf->index + 1 == count) { + buf->next = wq->bufs[0]; + break; + } else if (j + 1 == VNIC_WQ_BUF_BLK_ENTRIES) { + buf->next = wq->bufs[i + 1]; + } else { + buf->next = buf + 1; + buf++; + } + } + } + + wq->to_use = wq->to_clean = wq->bufs[0]; + + return 0; +} + +void vnic_wq_free(struct vnic_wq *wq) +{ + struct vnic_dev *vdev; + unsigned int i; + + vdev = wq->vdev; + + vnic_dev_free_desc_ring(vdev, &wq->ring); + + for (i = 0; i < VNIC_WQ_BUF_BLKS_MAX; i++) { + kfree(wq->bufs[i]); + wq->bufs[i] = NULL; + } + + wq->ctrl = NULL; + +} + +int vnic_wq_alloc(struct vnic_dev *vdev, struct vnic_wq *wq, unsigned int index, + unsigned int desc_count, unsigned int desc_size) +{ + int err; + + wq->index = index; + wq->vdev = vdev; + + wq->ctrl = vnic_dev_get_res(vdev, RES_TYPE_WQ, index); + if (!wq->ctrl) { + printk(KERN_ERR "Failed to hook WQ[%d] resource\n", index); + return -EINVAL; + } + + vnic_wq_disable(wq); + + err = vnic_dev_alloc_desc_ring(vdev, &wq->ring, desc_count, desc_size); + if (err) + return err; + + err = vnic_wq_alloc_bufs(wq); + if (err) { + vnic_wq_free(wq); + return err; + } + + return 0; +} + + +int vnic_wq_devcmd2_alloc(struct vnic_dev *vdev, struct vnic_wq *wq, + unsigned int desc_count, unsigned int desc_size) +{ + int err; + + wq->index = 0; + wq->vdev = vdev; + + err = vnic_wq_get_ctrl(vdev, wq, 0, RES_TYPE_DEVCMD2); + if (err) { + pr_err("Failed to get devcmd2 resource\n"); + return err; + } + vnic_wq_disable(wq); + + err = vnic_wq_alloc_ring(vdev, wq, desc_count, desc_size); + if (err) + return err; + return 0; +} + +void vnic_wq_init_start(struct vnic_wq *wq, unsigned int cq_index, + unsigned int fetch_index, unsigned int posted_index, + unsigned int error_interrupt_enable, + unsigned int error_interrupt_offset) +{ + u64 paddr; + unsigned int count = wq->ring.desc_count; + + paddr = (u64)wq->ring.base_addr | VNIC_PADDR_TARGET; + writeq(paddr, &wq->ctrl->ring_base); + iowrite32(count, &wq->ctrl->ring_size); + iowrite32(fetch_index, &wq->ctrl->fetch_index); + iowrite32(posted_index, &wq->ctrl->posted_index); + iowrite32(cq_index, &wq->ctrl->cq_index); + iowrite32(error_interrupt_enable, &wq->ctrl->error_interrupt_enable); + iowrite32(error_interrupt_offset, &wq->ctrl->error_interrupt_offset); + iowrite32(0, &wq->ctrl->error_status); + + wq->to_use = wq->to_clean = + &wq->bufs[fetch_index / VNIC_WQ_BUF_BLK_ENTRIES] + [fetch_index % VNIC_WQ_BUF_BLK_ENTRIES]; +} + + +void vnic_wq_init(struct vnic_wq *wq, unsigned int cq_index, + unsigned int error_interrupt_enable, + unsigned int error_interrupt_offset) +{ + u64 paddr; + + paddr = (u64)wq->ring.base_addr | VNIC_PADDR_TARGET; + writeq(paddr, &wq->ctrl->ring_base); + iowrite32(wq->ring.desc_count, &wq->ctrl->ring_size); + iowrite32(0, &wq->ctrl->fetch_index); + iowrite32(0, &wq->ctrl->posted_index); + iowrite32(cq_index, &wq->ctrl->cq_index); + iowrite32(error_interrupt_enable, &wq->ctrl->error_interrupt_enable); + iowrite32(error_interrupt_offset, &wq->ctrl->error_interrupt_offset); + iowrite32(0, &wq->ctrl->error_status); +} + +unsigned int vnic_wq_error_status(struct vnic_wq *wq) +{ + return ioread32(&wq->ctrl->error_status); +} + +void vnic_wq_enable(struct vnic_wq *wq) +{ + iowrite32(1, &wq->ctrl->enable); +} + +int vnic_wq_disable(struct vnic_wq *wq) +{ + unsigned int wait; + + iowrite32(0, &wq->ctrl->enable); + + /* Wait for HW to ACK disable request */ + for (wait = 0; wait < 100; wait++) { + if (!(ioread32(&wq->ctrl->running))) + return 0; + udelay(1); + } + + printk(KERN_ERR "Failed to disable WQ[%d]\n", wq->index); + + return -ETIMEDOUT; +} + +void vnic_wq_clean(struct vnic_wq *wq, + void (*buf_clean)(struct vnic_wq *wq, struct vnic_wq_buf *buf)) +{ + struct vnic_wq_buf *buf; + + BUG_ON(ioread32(&wq->ctrl->enable)); + + buf = wq->to_clean; + + while (vnic_wq_desc_used(wq) > 0) { + + (*buf_clean)(wq, buf); + + buf = wq->to_clean = buf->next; + wq->ring.desc_avail++; + } + + wq->to_use = wq->to_clean = wq->bufs[0]; + + iowrite32(0, &wq->ctrl->fetch_index); + iowrite32(0, &wq->ctrl->posted_index); + iowrite32(0, &wq->ctrl->error_status); + + vnic_dev_clear_desc_ring(&wq->ring); +} diff --git a/drivers/scsi/fnic/vnic_wq.h b/drivers/scsi/fnic/vnic_wq.h new file mode 100644 index 000000000..041618e13 --- /dev/null +++ b/drivers/scsi/fnic/vnic_wq.h @@ -0,0 +1,171 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _VNIC_WQ_H_ +#define _VNIC_WQ_H_ + +#include <linux/pci.h> +#include "vnic_dev.h" +#include "vnic_cq.h" + +/* + * These defines avoid symbol clash between fnic and enic (Cisco 10G Eth + * Driver) when both are built with CONFIG options =y + */ +#define vnic_wq_desc_avail fnic_wq_desc_avail +#define vnic_wq_desc_used fnic_wq_desc_used +#define vnic_wq_next_desc fni_cwq_next_desc +#define vnic_wq_post fnic_wq_post +#define vnic_wq_service fnic_wq_service +#define vnic_wq_free fnic_wq_free +#define vnic_wq_alloc fnic_wq_alloc +#define vnic_wq_devcmd2_alloc fnic_wq_devcmd2_alloc +#define vnic_wq_init_start fnic_wq_init_start +#define vnic_wq_init fnic_wq_init +#define vnic_wq_error_status fnic_wq_error_status +#define vnic_wq_enable fnic_wq_enable +#define vnic_wq_disable fnic_wq_disable +#define vnic_wq_clean fnic_wq_clean + +/* Work queue control */ +struct vnic_wq_ctrl { + u64 ring_base; /* 0x00 */ + u32 ring_size; /* 0x08 */ + u32 pad0; + u32 posted_index; /* 0x10 */ + u32 pad1; + u32 cq_index; /* 0x18 */ + u32 pad2; + u32 enable; /* 0x20 */ + u32 pad3; + u32 running; /* 0x28 */ + u32 pad4; + u32 fetch_index; /* 0x30 */ + u32 pad5; + u32 dca_value; /* 0x38 */ + u32 pad6; + u32 error_interrupt_enable; /* 0x40 */ + u32 pad7; + u32 error_interrupt_offset; /* 0x48 */ + u32 pad8; + u32 error_status; /* 0x50 */ + u32 pad9; +}; + +struct vnic_wq_buf { + struct vnic_wq_buf *next; + dma_addr_t dma_addr; + void *os_buf; + unsigned int len; + unsigned int index; + int sop; + void *desc; +}; + +/* Break the vnic_wq_buf allocations into blocks of 64 entries */ +#define VNIC_WQ_BUF_BLK_ENTRIES 64 +#define VNIC_WQ_BUF_BLK_SZ \ + (VNIC_WQ_BUF_BLK_ENTRIES * sizeof(struct vnic_wq_buf)) +#define VNIC_WQ_BUF_BLKS_NEEDED(entries) \ + DIV_ROUND_UP(entries, VNIC_WQ_BUF_BLK_ENTRIES) +#define VNIC_WQ_BUF_BLKS_MAX VNIC_WQ_BUF_BLKS_NEEDED(4096) + +struct vnic_wq { + unsigned int index; + struct vnic_dev *vdev; + struct vnic_wq_ctrl __iomem *ctrl; /* memory-mapped */ + struct vnic_dev_ring ring; + struct vnic_wq_buf *bufs[VNIC_WQ_BUF_BLKS_MAX]; + struct vnic_wq_buf *to_use; + struct vnic_wq_buf *to_clean; + unsigned int pkts_outstanding; +}; + +static inline unsigned int vnic_wq_desc_avail(struct vnic_wq *wq) +{ + /* how many does SW own? */ + return wq->ring.desc_avail; +} + +static inline unsigned int vnic_wq_desc_used(struct vnic_wq *wq) +{ + /* how many does HW own? */ + return wq->ring.desc_count - wq->ring.desc_avail - 1; +} + +static inline void *vnic_wq_next_desc(struct vnic_wq *wq) +{ + return wq->to_use->desc; +} + +static inline void vnic_wq_post(struct vnic_wq *wq, + void *os_buf, dma_addr_t dma_addr, + unsigned int len, int sop, int eop) +{ + struct vnic_wq_buf *buf = wq->to_use; + + buf->sop = sop; + buf->os_buf = eop ? os_buf : NULL; + buf->dma_addr = dma_addr; + buf->len = len; + + buf = buf->next; + if (eop) { + /* Adding write memory barrier prevents compiler and/or CPU + * reordering, thus avoiding descriptor posting before + * descriptor is initialized. Otherwise, hardware can read + * stale descriptor fields. + */ + wmb(); + iowrite32(buf->index, &wq->ctrl->posted_index); + } + wq->to_use = buf; + + wq->ring.desc_avail--; +} + +static inline void vnic_wq_service(struct vnic_wq *wq, + struct cq_desc *cq_desc, u16 completed_index, + void (*buf_service)(struct vnic_wq *wq, + struct cq_desc *cq_desc, struct vnic_wq_buf *buf, void *opaque), + void *opaque) +{ + struct vnic_wq_buf *buf; + + buf = wq->to_clean; + while (1) { + + (*buf_service)(wq, cq_desc, buf, opaque); + + wq->ring.desc_avail++; + + wq->to_clean = buf->next; + + if (buf->index == completed_index) + break; + + buf = wq->to_clean; + } +} + +void vnic_wq_free(struct vnic_wq *wq); +int vnic_wq_alloc(struct vnic_dev *vdev, struct vnic_wq *wq, unsigned int index, + unsigned int desc_count, unsigned int desc_size); +int vnic_wq_devcmd2_alloc(struct vnic_dev *vdev, struct vnic_wq *wq, + unsigned int desc_count, unsigned int desc_size); +void vnic_wq_init_start(struct vnic_wq *wq, unsigned int cq_index, + unsigned int fetch_index, unsigned int posted_index, + unsigned int error_interrupt_enable, + unsigned int error_interrupt_offset); +void vnic_wq_init(struct vnic_wq *wq, unsigned int cq_index, + unsigned int error_interrupt_enable, + unsigned int error_interrupt_offset); +unsigned int vnic_wq_error_status(struct vnic_wq *wq); +void vnic_wq_enable(struct vnic_wq *wq); +int vnic_wq_disable(struct vnic_wq *wq); +void vnic_wq_clean(struct vnic_wq *wq, + void (*buf_clean)(struct vnic_wq *wq, struct vnic_wq_buf *buf)); + +#endif /* _VNIC_WQ_H_ */ diff --git a/drivers/scsi/fnic/vnic_wq_copy.c b/drivers/scsi/fnic/vnic_wq_copy.c new file mode 100644 index 000000000..96569d4cc --- /dev/null +++ b/drivers/scsi/fnic/vnic_wq_copy.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ + +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include "vnic_wq_copy.h" + +void vnic_wq_copy_enable(struct vnic_wq_copy *wq) +{ + iowrite32(1, &wq->ctrl->enable); +} + +int vnic_wq_copy_disable(struct vnic_wq_copy *wq) +{ + unsigned int wait; + + iowrite32(0, &wq->ctrl->enable); + + /* Wait for HW to ACK disable request */ + for (wait = 0; wait < 100; wait++) { + if (!(ioread32(&wq->ctrl->running))) + return 0; + udelay(1); + } + + printk(KERN_ERR "Failed to disable Copy WQ[%d]," + " fetch index=%d, posted_index=%d\n", + wq->index, ioread32(&wq->ctrl->fetch_index), + ioread32(&wq->ctrl->posted_index)); + + return -ENODEV; +} + +void vnic_wq_copy_clean(struct vnic_wq_copy *wq, + void (*q_clean)(struct vnic_wq_copy *wq, + struct fcpio_host_req *wq_desc)) +{ + BUG_ON(ioread32(&wq->ctrl->enable)); + + if (vnic_wq_copy_desc_in_use(wq)) + vnic_wq_copy_service(wq, -1, q_clean); + + wq->to_use_index = wq->to_clean_index = 0; + + iowrite32(0, &wq->ctrl->fetch_index); + iowrite32(0, &wq->ctrl->posted_index); + iowrite32(0, &wq->ctrl->error_status); + + vnic_dev_clear_desc_ring(&wq->ring); +} + +void vnic_wq_copy_free(struct vnic_wq_copy *wq) +{ + struct vnic_dev *vdev; + + vdev = wq->vdev; + vnic_dev_free_desc_ring(vdev, &wq->ring); + wq->ctrl = NULL; +} + +int vnic_wq_copy_alloc(struct vnic_dev *vdev, struct vnic_wq_copy *wq, + unsigned int index, unsigned int desc_count, + unsigned int desc_size) +{ + wq->index = index; + wq->vdev = vdev; + wq->to_use_index = wq->to_clean_index = 0; + wq->ctrl = vnic_dev_get_res(vdev, RES_TYPE_WQ, index); + if (!wq->ctrl) { + printk(KERN_ERR "Failed to hook COPY WQ[%d] resource\n", index); + return -EINVAL; + } + + vnic_wq_copy_disable(wq); + + return vnic_dev_alloc_desc_ring(vdev, &wq->ring, desc_count, desc_size); +} + +void vnic_wq_copy_init(struct vnic_wq_copy *wq, unsigned int cq_index, + unsigned int error_interrupt_enable, + unsigned int error_interrupt_offset) +{ + u64 paddr; + + paddr = (u64)wq->ring.base_addr | VNIC_PADDR_TARGET; + writeq(paddr, &wq->ctrl->ring_base); + iowrite32(wq->ring.desc_count, &wq->ctrl->ring_size); + iowrite32(0, &wq->ctrl->fetch_index); + iowrite32(0, &wq->ctrl->posted_index); + iowrite32(cq_index, &wq->ctrl->cq_index); + iowrite32(error_interrupt_enable, &wq->ctrl->error_interrupt_enable); + iowrite32(error_interrupt_offset, &wq->ctrl->error_interrupt_offset); +} diff --git a/drivers/scsi/fnic/vnic_wq_copy.h b/drivers/scsi/fnic/vnic_wq_copy.h new file mode 100644 index 000000000..2f8340144 --- /dev/null +++ b/drivers/scsi/fnic/vnic_wq_copy.h @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _VNIC_WQ_COPY_H_ +#define _VNIC_WQ_COPY_H_ + +#include <linux/pci.h> +#include "vnic_wq.h" +#include "fcpio.h" + +#define VNIC_WQ_COPY_MAX 1 + +struct vnic_wq_copy { + unsigned int index; + struct vnic_dev *vdev; + struct vnic_wq_ctrl __iomem *ctrl; /* memory-mapped */ + struct vnic_dev_ring ring; + unsigned to_use_index; + unsigned to_clean_index; +}; + +static inline unsigned int vnic_wq_copy_desc_avail(struct vnic_wq_copy *wq) +{ + return wq->ring.desc_avail; +} + +static inline unsigned int vnic_wq_copy_desc_in_use(struct vnic_wq_copy *wq) +{ + return wq->ring.desc_count - 1 - wq->ring.desc_avail; +} + +static inline void *vnic_wq_copy_next_desc(struct vnic_wq_copy *wq) +{ + struct fcpio_host_req *desc = wq->ring.descs; + return &desc[wq->to_use_index]; +} + +static inline void vnic_wq_copy_post(struct vnic_wq_copy *wq) +{ + + ((wq->to_use_index + 1) == wq->ring.desc_count) ? + (wq->to_use_index = 0) : (wq->to_use_index++); + wq->ring.desc_avail--; + + /* Adding write memory barrier prevents compiler and/or CPU + * reordering, thus avoiding descriptor posting before + * descriptor is initialized. Otherwise, hardware can read + * stale descriptor fields. + */ + wmb(); + + iowrite32(wq->to_use_index, &wq->ctrl->posted_index); +} + +static inline void vnic_wq_copy_desc_process(struct vnic_wq_copy *wq, u16 index) +{ + unsigned int cnt; + + if (wq->to_clean_index <= index) + cnt = (index - wq->to_clean_index) + 1; + else + cnt = wq->ring.desc_count - wq->to_clean_index + index + 1; + + wq->to_clean_index = ((index + 1) % wq->ring.desc_count); + wq->ring.desc_avail += cnt; + +} + +static inline void vnic_wq_copy_service(struct vnic_wq_copy *wq, + u16 completed_index, + void (*q_service)(struct vnic_wq_copy *wq, + struct fcpio_host_req *wq_desc)) +{ + struct fcpio_host_req *wq_desc = wq->ring.descs; + unsigned int curr_index; + + while (1) { + + if (q_service) + (*q_service)(wq, &wq_desc[wq->to_clean_index]); + + wq->ring.desc_avail++; + + curr_index = wq->to_clean_index; + + /* increment the to-clean index so that we start + * with an unprocessed index next time we enter the loop + */ + ((wq->to_clean_index + 1) == wq->ring.desc_count) ? + (wq->to_clean_index = 0) : (wq->to_clean_index++); + + if (curr_index == completed_index) + break; + + /* we have cleaned all the entries */ + if ((completed_index == (u16)-1) && + (wq->to_clean_index == wq->to_use_index)) + break; + } +} + +void vnic_wq_copy_enable(struct vnic_wq_copy *wq); +int vnic_wq_copy_disable(struct vnic_wq_copy *wq); +void vnic_wq_copy_free(struct vnic_wq_copy *wq); +int vnic_wq_copy_alloc(struct vnic_dev *vdev, struct vnic_wq_copy *wq, + unsigned int index, unsigned int desc_count, unsigned int desc_size); +void vnic_wq_copy_init(struct vnic_wq_copy *wq, unsigned int cq_index, + unsigned int error_interrupt_enable, + unsigned int error_interrupt_offset); +void vnic_wq_copy_clean(struct vnic_wq_copy *wq, + void (*q_clean)(struct vnic_wq_copy *wq, + struct fcpio_host_req *wq_desc)); + +#endif /* _VNIC_WQ_COPY_H_ */ diff --git a/drivers/scsi/fnic/wq_enet_desc.h b/drivers/scsi/fnic/wq_enet_desc.h new file mode 100644 index 000000000..9a933a5de --- /dev/null +++ b/drivers/scsi/fnic/wq_enet_desc.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#ifndef _WQ_ENET_DESC_H_ +#define _WQ_ENET_DESC_H_ + +/* Ethernet work queue descriptor: 16B */ +struct wq_enet_desc { + __le64 address; + __le16 length; + __le16 mss_loopback; + __le16 header_length_flags; + __le16 vlan_tag; +}; + +#define WQ_ENET_ADDR_BITS 64 +#define WQ_ENET_LEN_BITS 14 +#define WQ_ENET_LEN_MASK ((1 << WQ_ENET_LEN_BITS) - 1) +#define WQ_ENET_MSS_BITS 14 +#define WQ_ENET_MSS_MASK ((1 << WQ_ENET_MSS_BITS) - 1) +#define WQ_ENET_MSS_SHIFT 2 +#define WQ_ENET_LOOPBACK_SHIFT 1 +#define WQ_ENET_HDRLEN_BITS 10 +#define WQ_ENET_HDRLEN_MASK ((1 << WQ_ENET_HDRLEN_BITS) - 1) +#define WQ_ENET_FLAGS_OM_BITS 2 +#define WQ_ENET_FLAGS_OM_MASK ((1 << WQ_ENET_FLAGS_OM_BITS) - 1) +#define WQ_ENET_FLAGS_EOP_SHIFT 12 +#define WQ_ENET_FLAGS_CQ_ENTRY_SHIFT 13 +#define WQ_ENET_FLAGS_FCOE_ENCAP_SHIFT 14 +#define WQ_ENET_FLAGS_VLAN_TAG_INSERT_SHIFT 15 + +#define WQ_ENET_OFFLOAD_MODE_CSUM 0 +#define WQ_ENET_OFFLOAD_MODE_RESERVED 1 +#define WQ_ENET_OFFLOAD_MODE_CSUM_L4 2 +#define WQ_ENET_OFFLOAD_MODE_TSO 3 + +static inline void wq_enet_desc_enc(struct wq_enet_desc *desc, + u64 address, u16 length, u16 mss, u16 header_length, + u8 offload_mode, u8 eop, u8 cq_entry, u8 fcoe_encap, + u8 vlan_tag_insert, u16 vlan_tag, u8 loopback) +{ + desc->address = cpu_to_le64(address); + desc->length = cpu_to_le16(length & WQ_ENET_LEN_MASK); + desc->mss_loopback = cpu_to_le16((mss & WQ_ENET_MSS_MASK) << + WQ_ENET_MSS_SHIFT | (loopback & 1) << WQ_ENET_LOOPBACK_SHIFT); + desc->header_length_flags = cpu_to_le16( + (header_length & WQ_ENET_HDRLEN_MASK) | + (offload_mode & WQ_ENET_FLAGS_OM_MASK) << WQ_ENET_HDRLEN_BITS | + (eop & 1) << WQ_ENET_FLAGS_EOP_SHIFT | + (cq_entry & 1) << WQ_ENET_FLAGS_CQ_ENTRY_SHIFT | + (fcoe_encap & 1) << WQ_ENET_FLAGS_FCOE_ENCAP_SHIFT | + (vlan_tag_insert & 1) << WQ_ENET_FLAGS_VLAN_TAG_INSERT_SHIFT); + desc->vlan_tag = cpu_to_le16(vlan_tag); +} + +static inline void wq_enet_desc_dec(struct wq_enet_desc *desc, + u64 *address, u16 *length, u16 *mss, u16 *header_length, + u8 *offload_mode, u8 *eop, u8 *cq_entry, u8 *fcoe_encap, + u8 *vlan_tag_insert, u16 *vlan_tag, u8 *loopback) +{ + *address = le64_to_cpu(desc->address); + *length = le16_to_cpu(desc->length) & WQ_ENET_LEN_MASK; + *mss = (le16_to_cpu(desc->mss_loopback) >> WQ_ENET_MSS_SHIFT) & + WQ_ENET_MSS_MASK; + *loopback = (u8)((le16_to_cpu(desc->mss_loopback) >> + WQ_ENET_LOOPBACK_SHIFT) & 1); + *header_length = le16_to_cpu(desc->header_length_flags) & + WQ_ENET_HDRLEN_MASK; + *offload_mode = (u8)((le16_to_cpu(desc->header_length_flags) >> + WQ_ENET_HDRLEN_BITS) & WQ_ENET_FLAGS_OM_MASK); + *eop = (u8)((le16_to_cpu(desc->header_length_flags) >> + WQ_ENET_FLAGS_EOP_SHIFT) & 1); + *cq_entry = (u8)((le16_to_cpu(desc->header_length_flags) >> + WQ_ENET_FLAGS_CQ_ENTRY_SHIFT) & 1); + *fcoe_encap = (u8)((le16_to_cpu(desc->header_length_flags) >> + WQ_ENET_FLAGS_FCOE_ENCAP_SHIFT) & 1); + *vlan_tag_insert = (u8)((le16_to_cpu(desc->header_length_flags) >> + WQ_ENET_FLAGS_VLAN_TAG_INSERT_SHIFT) & 1); + *vlan_tag = le16_to_cpu(desc->vlan_tag); +} + +#endif /* _WQ_ENET_DESC_H_ */ |
