From 4617721c502b2ddaa4e324e86da4997edf738fa5 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Wed, 2 Sep 2026 09:55:01 -0400 Subject: ftrace: Synchronize the initialization of ftrace_ops There's some internal state that ftrace_ops needs to have set, but since it can be declared outside of the ftrace.c code, it calls ftrace_ops_init() on the ops in every global function. The issue is that if two tasks call it on the same ops at the same time it is possible to have the initialization of one corrupt the initialization of the other call. Create a ops_mutex to use to synchronize every initialization of the ftrace_ops. The mutex is taken within checking the ftrace_ops flag that states it was initializied but the flag is checked again after the mutex has been taken. Checking first outside the mutex allows it to shortcut having to take the mutex. But then the check needs to be done again after the mute is taken in case of races. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock") Reported-by: sashiko-bot@kernel.org Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/ Signed-off-by: Steven Rostedt --- drivers/scsi/fnic/Makefile | 22 + drivers/scsi/fnic/cq_desc.h | 66 + drivers/scsi/fnic/cq_enet_desc.h | 155 + drivers/scsi/fnic/cq_exch_desc.h | 170 + drivers/scsi/fnic/fcpio.h | 803 +++++ drivers/scsi/fnic/fdls_disc.c | 5259 +++++++++++++++++++++++++++++ drivers/scsi/fnic/fdls_fc.h | 260 ++ drivers/scsi/fnic/fip.c | 996 ++++++ drivers/scsi/fnic/fip.h | 159 + drivers/scsi/fnic/fnic.h | 663 ++++ drivers/scsi/fnic/fnic_attrs.c | 56 + drivers/scsi/fnic/fnic_debugfs.c | 818 +++++ drivers/scsi/fnic/fnic_fcs.c | 1209 +++++++ drivers/scsi/fnic/fnic_fdls.h | 463 +++ drivers/scsi/fnic/fnic_io.h | 83 + drivers/scsi/fnic/fnic_isr.c | 396 +++ drivers/scsi/fnic/fnic_main.c | 1533 +++++++++ drivers/scsi/fnic/fnic_nvme.c | 2268 +++++++++++++ drivers/scsi/fnic/fnic_nvme.h | 214 ++ drivers/scsi/fnic/fnic_pci_subsys_devid.c | 131 + drivers/scsi/fnic/fnic_res.c | 468 +++ drivers/scsi/fnic/fnic_res.h | 267 ++ drivers/scsi/fnic/fnic_scsi.c | 3058 +++++++++++++++++ drivers/scsi/fnic/fnic_stats.h | 203 ++ drivers/scsi/fnic/fnic_trace.c | 884 +++++ drivers/scsi/fnic/fnic_trace.h | 115 + drivers/scsi/fnic/rq_enet_desc.h | 46 + drivers/scsi/fnic/vnic_cq.c | 73 + drivers/scsi/fnic/vnic_cq.h | 109 + drivers/scsi/fnic/vnic_cq_copy.h | 50 + drivers/scsi/fnic/vnic_dev.c | 947 ++++++ drivers/scsi/fnic/vnic_dev.h | 153 + drivers/scsi/fnic/vnic_devcmd.h | 492 +++ drivers/scsi/fnic/vnic_intr.c | 48 + drivers/scsi/fnic/vnic_intr.h | 106 + drivers/scsi/fnic/vnic_nic.h | 57 + drivers/scsi/fnic/vnic_resource.h | 56 + drivers/scsi/fnic/vnic_rq.c | 181 + drivers/scsi/fnic/vnic_rq.h | 223 ++ drivers/scsi/fnic/vnic_scsi.h | 97 + drivers/scsi/fnic/vnic_stats.h | 56 + drivers/scsi/fnic/vnic_wq.c | 234 ++ drivers/scsi/fnic/vnic_wq.h | 171 + drivers/scsi/fnic/vnic_wq_copy.c | 98 + drivers/scsi/fnic/vnic_wq_copy.h | 116 + drivers/scsi/fnic/wq_enet_desc.h | 84 + 46 files changed, 24116 insertions(+) create mode 100644 drivers/scsi/fnic/Makefile create mode 100644 drivers/scsi/fnic/cq_desc.h create mode 100644 drivers/scsi/fnic/cq_enet_desc.h create mode 100644 drivers/scsi/fnic/cq_exch_desc.h create mode 100644 drivers/scsi/fnic/fcpio.h create mode 100644 drivers/scsi/fnic/fdls_disc.c create mode 100644 drivers/scsi/fnic/fdls_fc.h create mode 100644 drivers/scsi/fnic/fip.c create mode 100644 drivers/scsi/fnic/fip.h create mode 100644 drivers/scsi/fnic/fnic.h create mode 100644 drivers/scsi/fnic/fnic_attrs.c create mode 100644 drivers/scsi/fnic/fnic_debugfs.c create mode 100644 drivers/scsi/fnic/fnic_fcs.c create mode 100644 drivers/scsi/fnic/fnic_fdls.h create mode 100644 drivers/scsi/fnic/fnic_io.h create mode 100644 drivers/scsi/fnic/fnic_isr.c create mode 100644 drivers/scsi/fnic/fnic_main.c create mode 100644 drivers/scsi/fnic/fnic_nvme.c create mode 100644 drivers/scsi/fnic/fnic_nvme.h create mode 100644 drivers/scsi/fnic/fnic_pci_subsys_devid.c create mode 100644 drivers/scsi/fnic/fnic_res.c create mode 100644 drivers/scsi/fnic/fnic_res.h create mode 100644 drivers/scsi/fnic/fnic_scsi.c create mode 100644 drivers/scsi/fnic/fnic_stats.h create mode 100644 drivers/scsi/fnic/fnic_trace.c create mode 100644 drivers/scsi/fnic/fnic_trace.h create mode 100644 drivers/scsi/fnic/rq_enet_desc.h create mode 100644 drivers/scsi/fnic/vnic_cq.c create mode 100644 drivers/scsi/fnic/vnic_cq.h create mode 100644 drivers/scsi/fnic/vnic_cq_copy.h create mode 100644 drivers/scsi/fnic/vnic_dev.c create mode 100644 drivers/scsi/fnic/vnic_dev.h create mode 100644 drivers/scsi/fnic/vnic_devcmd.h create mode 100644 drivers/scsi/fnic/vnic_intr.c create mode 100644 drivers/scsi/fnic/vnic_intr.h create mode 100644 drivers/scsi/fnic/vnic_nic.h create mode 100644 drivers/scsi/fnic/vnic_resource.h create mode 100644 drivers/scsi/fnic/vnic_rq.c create mode 100644 drivers/scsi/fnic/vnic_rq.h create mode 100644 drivers/scsi/fnic/vnic_scsi.h create mode 100644 drivers/scsi/fnic/vnic_stats.h create mode 100644 drivers/scsi/fnic/vnic_wq.c create mode 100644 drivers/scsi/fnic/vnic_wq.h create mode 100644 drivers/scsi/fnic/vnic_wq_copy.c create mode 100644 drivers/scsi/fnic/vnic_wq_copy.h create mode 100644 drivers/scsi/fnic/wq_enet_desc.h (limited to 'drivers/scsi/fnic') 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 + +/* + * 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 +#include "fnic.h" +#include "fdls_fc.h" +#include "fnic_fdls.h" +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + +#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 + +/* 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..c576a7f50 --- /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 +#include +#include +#include +#include +#include +#include +#include +#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.0" +#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 +#include +#include +#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..61f167e20 --- /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 +#include +#include +#include +#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, GFP_KERNEL); + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include "fnic_fdls.h" +#include "fcpio.h" +#include + +#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..028567455 --- /dev/null +++ b/drivers/scsi/fnic/fnic_isr.c @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#include +#include +#include +#include +#include +#include +#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*/ + + /* + * 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(fnic->pdev, min_irqs, vecs, + PCI_IRQ_MSIX | PCI_IRQ_AFFINITY); + 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..9b3025007 --- /dev/null +++ b/drivers/scsi/fnic/fnic_main.c @@ -0,0 +1,1533 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2008 Cisco Systems, Inc. All rights reserved. + * Copyright 2007 Nuova Systems, Inc. All rights reserved. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 , " + "Joseph R. Eykholt "); +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; +} + +void fnic_mq_map_queues_cpus(struct Scsi_Host *host) +{ + 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; + } + + blk_mq_map_hw_queues(qmap, &l_pdev->dev, FNIC_PCI_OFFSET); +} + +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..b237948dc --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 + +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 +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#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 +#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 +#include +#include +#include +#include +#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 +#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 +#include +#include +#include +#include +#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 +#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 +#include +#include +#include +#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 +#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_ */ -- cgit v1.3.1