|
@@ -39,6 +39,7 @@
|
|
|
#include <scsi/fc/fc_fip.h>
|
|
|
#include <scsi/fc/fc_encaps.h>
|
|
|
#include <scsi/fc/fc_fcoe.h>
|
|
|
+#include <scsi/fc/fc_fcp.h>
|
|
|
|
|
|
#include <scsi/libfc.h>
|
|
|
#include <scsi/libfcoe.h>
|
|
@@ -54,7 +55,15 @@ static void fcoe_ctlr_timeout(unsigned long);
|
|
|
static void fcoe_ctlr_timer_work(struct work_struct *);
|
|
|
static void fcoe_ctlr_recv_work(struct work_struct *);
|
|
|
|
|
|
+static void fcoe_ctlr_vn_start(struct fcoe_ctlr *);
|
|
|
+static int fcoe_ctlr_vn_recv(struct fcoe_ctlr *, struct sk_buff *);
|
|
|
+static void fcoe_ctlr_vn_timeout(struct fcoe_ctlr *);
|
|
|
+static int fcoe_ctlr_vn_lookup(struct fcoe_ctlr *, u32, u8 *);
|
|
|
+
|
|
|
static u8 fcoe_all_fcfs[ETH_ALEN] = FIP_ALL_FCF_MACS;
|
|
|
+static u8 fcoe_all_enode[ETH_ALEN] = FIP_ALL_ENODE_MACS;
|
|
|
+static u8 fcoe_all_vn2vn[ETH_ALEN] = FIP_ALL_VN2VN_MACS;
|
|
|
+static u8 fcoe_all_p2p[ETH_ALEN] = FIP_ALL_P2P_MACS;
|
|
|
|
|
|
unsigned int libfcoe_debug_logging;
|
|
|
module_param_named(debug_logging, libfcoe_debug_logging, int, S_IRUGO|S_IWUSR);
|
|
@@ -86,6 +95,11 @@ static const char *fcoe_ctlr_states[] = {
|
|
|
[FIP_ST_AUTO] = "AUTO",
|
|
|
[FIP_ST_NON_FIP] = "NON_FIP",
|
|
|
[FIP_ST_ENABLED] = "ENABLED",
|
|
|
+ [FIP_ST_VNMP_START] = "VNMP_START",
|
|
|
+ [FIP_ST_VNMP_PROBE1] = "VNMP_PROBE1",
|
|
|
+ [FIP_ST_VNMP_PROBE2] = "VNMP_PROBE2",
|
|
|
+ [FIP_ST_VNMP_CLAIM] = "VNMP_CLAIM",
|
|
|
+ [FIP_ST_VNMP_UP] = "VNMP_UP",
|
|
|
};
|
|
|
|
|
|
static const char *fcoe_ctlr_state(enum fip_state state)
|
|
@@ -295,11 +309,25 @@ void fcoe_ctlr_link_up(struct fcoe_ctlr *fip)
|
|
|
fc_linkup(fip->lp);
|
|
|
} else if (fip->state == FIP_ST_LINK_WAIT) {
|
|
|
fcoe_ctlr_set_state(fip, fip->mode);
|
|
|
- mutex_unlock(&fip->ctlr_mutex);
|
|
|
- if (fip->state == FIP_ST_AUTO)
|
|
|
+ switch (fip->mode) {
|
|
|
+ default:
|
|
|
+ LIBFCOE_FIP_DBG(fip, "invalid mode %d\n", fip->mode);
|
|
|
+ /* fall-through */
|
|
|
+ case FIP_MODE_AUTO:
|
|
|
LIBFCOE_FIP_DBG(fip, "%s", "setting AUTO mode.\n");
|
|
|
- fc_linkup(fip->lp);
|
|
|
- fcoe_ctlr_solicit(fip, NULL);
|
|
|
+ /* fall-through */
|
|
|
+ case FIP_MODE_FABRIC:
|
|
|
+ case FIP_MODE_NON_FIP:
|
|
|
+ mutex_unlock(&fip->ctlr_mutex);
|
|
|
+ fc_linkup(fip->lp);
|
|
|
+ fcoe_ctlr_solicit(fip, NULL);
|
|
|
+ break;
|
|
|
+ case FIP_MODE_VN2VN:
|
|
|
+ fcoe_ctlr_vn_start(fip);
|
|
|
+ mutex_unlock(&fip->ctlr_mutex);
|
|
|
+ fc_linkup(fip->lp);
|
|
|
+ break;
|
|
|
+ }
|
|
|
} else
|
|
|
mutex_unlock(&fip->ctlr_mutex);
|
|
|
}
|
|
@@ -423,6 +451,7 @@ static void fcoe_ctlr_send_keep_alive(struct fcoe_ctlr *fip,
|
|
|
* @fip: The FCoE controller for the ELS frame
|
|
|
* @dtype: The FIP descriptor type for the frame
|
|
|
* @skb: The FCoE ELS frame including FC header but no FCoE headers
|
|
|
+ * @d_id: The destination port ID.
|
|
|
*
|
|
|
* Returns non-zero error code on failure.
|
|
|
*
|
|
@@ -433,7 +462,7 @@ static void fcoe_ctlr_send_keep_alive(struct fcoe_ctlr *fip,
|
|
|
* Ethernet header. The tailroom is for the FIP MAC descriptor.
|
|
|
*/
|
|
|
static int fcoe_ctlr_encaps(struct fcoe_ctlr *fip, struct fc_lport *lport,
|
|
|
- u8 dtype, struct sk_buff *skb)
|
|
|
+ u8 dtype, struct sk_buff *skb, u32 d_id)
|
|
|
{
|
|
|
struct fip_encaps_head {
|
|
|
struct ethhdr eth;
|
|
@@ -445,21 +474,24 @@ static int fcoe_ctlr_encaps(struct fcoe_ctlr *fip, struct fc_lport *lport,
|
|
|
size_t dlen;
|
|
|
u16 fip_flags;
|
|
|
|
|
|
- fcf = fip->sel_fcf;
|
|
|
- if (!fcf)
|
|
|
- return -ENODEV;
|
|
|
-
|
|
|
- /* set flags according to both FCF and lport's capability on SPMA */
|
|
|
- fip_flags = fcf->flags;
|
|
|
- fip_flags &= fip->spma ? FIP_FL_SPMA | FIP_FL_FPMA : FIP_FL_FPMA;
|
|
|
- if (!fip_flags)
|
|
|
- return -ENODEV;
|
|
|
-
|
|
|
dlen = sizeof(struct fip_encaps) + skb->len; /* len before push */
|
|
|
cap = (struct fip_encaps_head *)skb_push(skb, sizeof(*cap));
|
|
|
-
|
|
|
memset(cap, 0, sizeof(*cap));
|
|
|
- memcpy(cap->eth.h_dest, fcf->fcf_mac, ETH_ALEN);
|
|
|
+
|
|
|
+ if (lport->point_to_multipoint) {
|
|
|
+ if (fcoe_ctlr_vn_lookup(fip, d_id, cap->eth.h_dest))
|
|
|
+ return -ENODEV;
|
|
|
+ } else {
|
|
|
+ fcf = fip->sel_fcf;
|
|
|
+ if (!fcf)
|
|
|
+ return -ENODEV;
|
|
|
+ fip_flags = fcf->flags;
|
|
|
+ fip_flags &= fip->spma ? FIP_FL_SPMA | FIP_FL_FPMA :
|
|
|
+ FIP_FL_FPMA;
|
|
|
+ if (!fip_flags)
|
|
|
+ return -ENODEV;
|
|
|
+ memcpy(cap->eth.h_dest, fcf->fcf_mac, ETH_ALEN);
|
|
|
+ }
|
|
|
memcpy(cap->eth.h_source, fip->ctl_src_addr, ETH_ALEN);
|
|
|
cap->eth.h_proto = htons(ETH_P_FIP);
|
|
|
|
|
@@ -503,19 +535,22 @@ static int fcoe_ctlr_encaps(struct fcoe_ctlr *fip, struct fc_lport *lport,
|
|
|
*
|
|
|
* The caller must check that the length is a multiple of 4.
|
|
|
* The SKB must have enough headroom (28 bytes) and tailroom (8 bytes).
|
|
|
+ * The the skb must also be an fc_frame.
|
|
|
*/
|
|
|
int fcoe_ctlr_els_send(struct fcoe_ctlr *fip, struct fc_lport *lport,
|
|
|
struct sk_buff *skb)
|
|
|
{
|
|
|
+ struct fc_frame *fp;
|
|
|
struct fc_frame_header *fh;
|
|
|
u16 old_xid;
|
|
|
u8 op;
|
|
|
u8 mac[ETH_ALEN];
|
|
|
|
|
|
+ fp = container_of(skb, struct fc_frame, skb);
|
|
|
fh = (struct fc_frame_header *)skb->data;
|
|
|
op = *(u8 *)(fh + 1);
|
|
|
|
|
|
- if (op == ELS_FLOGI) {
|
|
|
+ if (op == ELS_FLOGI && fip->mode != FIP_MODE_VN2VN) {
|
|
|
old_xid = fip->flogi_oxid;
|
|
|
fip->flogi_oxid = ntohs(fh->fh_ox_id);
|
|
|
if (fip->state == FIP_ST_AUTO) {
|
|
@@ -533,9 +568,8 @@ int fcoe_ctlr_els_send(struct fcoe_ctlr *fip, struct fc_lport *lport,
|
|
|
|
|
|
if (fip->state == FIP_ST_NON_FIP)
|
|
|
return 0;
|
|
|
- if (!fip->sel_fcf)
|
|
|
+ if (!fip->sel_fcf && fip->mode != FIP_MODE_VN2VN)
|
|
|
goto drop;
|
|
|
-
|
|
|
switch (op) {
|
|
|
case ELS_FLOGI:
|
|
|
op = FIP_DT_FLOGI;
|
|
@@ -546,36 +580,49 @@ int fcoe_ctlr_els_send(struct fcoe_ctlr *fip, struct fc_lport *lport,
|
|
|
op = FIP_DT_FDISC;
|
|
|
break;
|
|
|
case ELS_LOGO:
|
|
|
- if (fip->state != FIP_ST_ENABLED)
|
|
|
- return 0;
|
|
|
- if (ntoh24(fh->fh_d_id) != FC_FID_FLOGI)
|
|
|
- return 0;
|
|
|
+ if (fip->mode == FIP_MODE_VN2VN) {
|
|
|
+ if (fip->state != FIP_ST_VNMP_UP)
|
|
|
+ return -EINVAL;
|
|
|
+ if (ntoh24(fh->fh_d_id) == FC_FID_FLOGI)
|
|
|
+ return -EINVAL;
|
|
|
+ } else {
|
|
|
+ if (fip->state != FIP_ST_ENABLED)
|
|
|
+ return 0;
|
|
|
+ if (ntoh24(fh->fh_d_id) != FC_FID_FLOGI)
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
op = FIP_DT_LOGO;
|
|
|
break;
|
|
|
case ELS_LS_ACC:
|
|
|
- if (fip->flogi_oxid == FC_XID_UNKNOWN)
|
|
|
- return 0;
|
|
|
- if (!ntoh24(fh->fh_s_id))
|
|
|
- return 0;
|
|
|
- if (fip->state == FIP_ST_AUTO)
|
|
|
- return 0;
|
|
|
/*
|
|
|
- * Here we must've gotten an SID by accepting an FLOGI
|
|
|
+ * If non-FIP, we may have gotten an SID by accepting an FLOGI
|
|
|
* from a point-to-point connection. Switch to using
|
|
|
* the source mac based on the SID. The destination
|
|
|
* MAC in this case would have been set by receving the
|
|
|
* FLOGI.
|
|
|
*/
|
|
|
- fip->flogi_oxid = FC_XID_UNKNOWN;
|
|
|
- fc_fcoe_set_mac(mac, fh->fh_d_id);
|
|
|
- fip->update_mac(lport, mac);
|
|
|
+ if (fip->state == FIP_ST_NON_FIP) {
|
|
|
+ if (fip->flogi_oxid == FC_XID_UNKNOWN)
|
|
|
+ return 0;
|
|
|
+ fip->flogi_oxid = FC_XID_UNKNOWN;
|
|
|
+ fc_fcoe_set_mac(mac, fh->fh_d_id);
|
|
|
+ fip->update_mac(lport, mac);
|
|
|
+ }
|
|
|
+ /* fall through */
|
|
|
+ case ELS_LS_RJT:
|
|
|
+ op = fr_encaps(fp);
|
|
|
+ if (op)
|
|
|
+ break;
|
|
|
return 0;
|
|
|
default:
|
|
|
- if (fip->state != FIP_ST_ENABLED)
|
|
|
+ if (fip->state != FIP_ST_ENABLED &&
|
|
|
+ fip->state != FIP_ST_VNMP_UP)
|
|
|
goto drop;
|
|
|
return 0;
|
|
|
}
|
|
|
- if (fcoe_ctlr_encaps(fip, lport, op, skb))
|
|
|
+ LIBFCOE_FIP_DBG(fip, "els_send op %u d_id %x\n",
|
|
|
+ op, ntoh24(fh->fh_d_id));
|
|
|
+ if (fcoe_ctlr_encaps(fip, lport, op, skb, ntoh24(fh->fh_d_id)))
|
|
|
goto drop;
|
|
|
fip->send(fip, skb);
|
|
|
return -EINPROGRESS;
|
|
@@ -717,8 +764,9 @@ static int fcoe_ctlr_parse_adv(struct fcoe_ctlr *fip,
|
|
|
((struct fip_mac_desc *)desc)->fd_mac,
|
|
|
ETH_ALEN);
|
|
|
if (!is_valid_ether_addr(fcf->fcf_mac)) {
|
|
|
- LIBFCOE_FIP_DBG(fip, "Invalid MAC address "
|
|
|
- "in FIP adv\n");
|
|
|
+ LIBFCOE_FIP_DBG(fip,
|
|
|
+ "Invalid MAC addr %pM in FIP adv\n",
|
|
|
+ fcf->fcf_mac);
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
desc_mask &= ~BIT(FIP_DT_MAC);
|
|
@@ -944,12 +992,6 @@ static void fcoe_ctlr_recv_els(struct fcoe_ctlr *fip, struct sk_buff *skb)
|
|
|
memcpy(granted_mac,
|
|
|
((struct fip_mac_desc *)desc)->fd_mac,
|
|
|
ETH_ALEN);
|
|
|
- if (!is_valid_ether_addr(granted_mac)) {
|
|
|
- LIBFCOE_FIP_DBG(fip, "Invalid MAC address "
|
|
|
- "in FIP ELS\n");
|
|
|
- goto drop;
|
|
|
- }
|
|
|
- memcpy(fr_cb(fp)->granted_mac, granted_mac, ETH_ALEN);
|
|
|
break;
|
|
|
case FIP_DT_FLOGI:
|
|
|
case FIP_DT_FDISC:
|
|
@@ -990,10 +1032,20 @@ static void fcoe_ctlr_recv_els(struct fcoe_ctlr *fip, struct sk_buff *skb)
|
|
|
goto drop;
|
|
|
els_op = *(u8 *)(fh + 1);
|
|
|
|
|
|
- if (els_dtype == FIP_DT_FLOGI && sub == FIP_SC_REP &&
|
|
|
- fip->flogi_oxid == ntohs(fh->fh_ox_id) &&
|
|
|
- els_op == ELS_LS_ACC && is_valid_ether_addr(granted_mac))
|
|
|
- fip->flogi_oxid = FC_XID_UNKNOWN;
|
|
|
+ if ((els_dtype == FIP_DT_FLOGI || els_dtype == FIP_DT_FDISC) &&
|
|
|
+ sub == FIP_SC_REP && els_op == ELS_LS_ACC &&
|
|
|
+ fip->mode != FIP_MODE_VN2VN) {
|
|
|
+ if (!is_valid_ether_addr(granted_mac)) {
|
|
|
+ LIBFCOE_FIP_DBG(fip,
|
|
|
+ "Invalid MAC address %pM in FIP ELS\n",
|
|
|
+ granted_mac);
|
|
|
+ goto drop;
|
|
|
+ }
|
|
|
+ memcpy(fr_cb(fp)->granted_mac, granted_mac, ETH_ALEN);
|
|
|
+
|
|
|
+ if (fip->flogi_oxid == ntohs(fh->fh_ox_id))
|
|
|
+ fip->flogi_oxid = FC_XID_UNKNOWN;
|
|
|
+ }
|
|
|
|
|
|
if ((desc_cnt == 0) || ((els_op != ELS_LS_RJT) &&
|
|
|
(!(1U << FIP_DT_MAC & desc_mask)))) {
|
|
@@ -1012,6 +1064,7 @@ static void fcoe_ctlr_recv_els(struct fcoe_ctlr *fip, struct sk_buff *skb)
|
|
|
fr_sof(fp) = FC_SOF_I3;
|
|
|
fr_eof(fp) = FC_EOF_T;
|
|
|
fr_dev(fp) = lport;
|
|
|
+ fr_encaps(fp) = els_dtype;
|
|
|
|
|
|
stats = per_cpu_ptr(lport->dev_stats, get_cpu());
|
|
|
stats->RxFrames++;
|
|
@@ -1188,8 +1241,13 @@ static int fcoe_ctlr_recv_handler(struct fcoe_ctlr *fip, struct sk_buff *skb)
|
|
|
if (skb->len < sizeof(*fiph))
|
|
|
goto drop;
|
|
|
eh = eth_hdr(skb);
|
|
|
- if (compare_ether_addr(eh->h_dest, fip->ctl_src_addr) &&
|
|
|
- compare_ether_addr(eh->h_dest, FIP_ALL_ENODE_MACS))
|
|
|
+ if (fip->mode == FIP_MODE_VN2VN) {
|
|
|
+ if (compare_ether_addr(eh->h_dest, fip->ctl_src_addr) &&
|
|
|
+ compare_ether_addr(eh->h_dest, fcoe_all_vn2vn) &&
|
|
|
+ compare_ether_addr(eh->h_dest, fcoe_all_p2p))
|
|
|
+ goto drop;
|
|
|
+ } else if (compare_ether_addr(eh->h_dest, fip->ctl_src_addr) &&
|
|
|
+ compare_ether_addr(eh->h_dest, fcoe_all_enode))
|
|
|
goto drop;
|
|
|
fiph = (struct fip_header *)skb->data;
|
|
|
op = ntohs(fiph->fip_op);
|
|
@@ -1209,13 +1267,22 @@ static int fcoe_ctlr_recv_handler(struct fcoe_ctlr *fip, struct sk_buff *skb)
|
|
|
LIBFCOE_FIP_DBG(fip, "Using FIP mode\n");
|
|
|
}
|
|
|
mutex_unlock(&fip->ctlr_mutex);
|
|
|
- if (state != FIP_ST_ENABLED)
|
|
|
+
|
|
|
+ if (fip->mode == FIP_MODE_VN2VN && op == FIP_OP_VN2VN)
|
|
|
+ return fcoe_ctlr_vn_recv(fip, skb);
|
|
|
+
|
|
|
+ if (state != FIP_ST_ENABLED && state != FIP_ST_VNMP_UP &&
|
|
|
+ state != FIP_ST_VNMP_CLAIM)
|
|
|
goto drop;
|
|
|
|
|
|
if (op == FIP_OP_LS) {
|
|
|
fcoe_ctlr_recv_els(fip, skb); /* consumes skb */
|
|
|
return 0;
|
|
|
}
|
|
|
+
|
|
|
+ if (state != FIP_ST_ENABLED)
|
|
|
+ goto drop;
|
|
|
+
|
|
|
if (op == FIP_OP_DISC && sub == FIP_SC_ADV)
|
|
|
fcoe_ctlr_recv_adv(fip, skb);
|
|
|
else if (op == FIP_OP_CTRL && sub == FIP_SC_CLR_VLINK)
|
|
@@ -1302,7 +1369,8 @@ static void fcoe_ctlr_timer_work(struct work_struct *work)
|
|
|
unsigned long next_timer;
|
|
|
|
|
|
fip = container_of(work, struct fcoe_ctlr, timer_work);
|
|
|
-
|
|
|
+ if (fip->mode == FIP_MODE_VN2VN)
|
|
|
+ return fcoe_ctlr_vn_timeout(fip);
|
|
|
mutex_lock(&fip->ctlr_mutex);
|
|
|
if (fip->state == FIP_ST_DISABLED) {
|
|
|
mutex_unlock(&fip->ctlr_mutex);
|
|
@@ -1340,7 +1408,6 @@ static void fcoe_ctlr_timer_work(struct work_struct *work)
|
|
|
"Starting FCF discovery.\n",
|
|
|
fip->lp->host->host_no);
|
|
|
reset = 1;
|
|
|
- schedule_work(&fip->timer_work);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1514,27 +1581,917 @@ u64 fcoe_wwn_from_mac(unsigned char mac[MAX_ADDR_LEN],
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(fcoe_wwn_from_mac);
|
|
|
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_rport() - return the fcoe_rport for a given fc_rport_priv
|
|
|
+ * @rdata: libfc remote port
|
|
|
+ */
|
|
|
+static inline struct fcoe_rport *fcoe_ctlr_rport(struct fc_rport_priv *rdata)
|
|
|
+{
|
|
|
+ return (struct fcoe_rport *)(rdata + 1);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_vn_send() - Send a FIP VN2VN Probe Request or Reply.
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ * @sub: sub-opcode for probe request, reply, or advertisement.
|
|
|
+ * @dest: The destination Ethernet MAC address
|
|
|
+ * @min_len: minimum size of the Ethernet payload to be sent
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_vn_send(struct fcoe_ctlr *fip,
|
|
|
+ enum fip_vn2vn_subcode sub,
|
|
|
+ const u8 *dest, size_t min_len)
|
|
|
+{
|
|
|
+ struct sk_buff *skb;
|
|
|
+ struct fip_frame {
|
|
|
+ struct ethhdr eth;
|
|
|
+ struct fip_header fip;
|
|
|
+ struct fip_mac_desc mac;
|
|
|
+ struct fip_wwn_desc wwnn;
|
|
|
+ struct fip_vn_desc vn;
|
|
|
+ } __attribute__((packed)) *frame;
|
|
|
+ struct fip_fc4_feat *ff;
|
|
|
+ struct fip_size_desc *size;
|
|
|
+ u32 fcp_feat;
|
|
|
+ size_t len;
|
|
|
+ size_t dlen;
|
|
|
+
|
|
|
+ len = sizeof(*frame);
|
|
|
+ dlen = 0;
|
|
|
+ if (sub == FIP_SC_VN_CLAIM_NOTIFY || sub == FIP_SC_VN_CLAIM_REP) {
|
|
|
+ dlen = sizeof(struct fip_fc4_feat) +
|
|
|
+ sizeof(struct fip_size_desc);
|
|
|
+ len += dlen;
|
|
|
+ }
|
|
|
+ dlen += sizeof(frame->mac) + sizeof(frame->wwnn) + sizeof(frame->vn);
|
|
|
+ len = max(len, min_len + sizeof(struct ethhdr));
|
|
|
+
|
|
|
+ skb = dev_alloc_skb(len);
|
|
|
+ if (!skb)
|
|
|
+ return;
|
|
|
+
|
|
|
+ frame = (struct fip_frame *)skb->data;
|
|
|
+ memset(frame, 0, len);
|
|
|
+ memcpy(frame->eth.h_dest, dest, ETH_ALEN);
|
|
|
+ memcpy(frame->eth.h_source, fip->ctl_src_addr, ETH_ALEN);
|
|
|
+ frame->eth.h_proto = htons(ETH_P_FIP);
|
|
|
+
|
|
|
+ frame->fip.fip_ver = FIP_VER_ENCAPS(FIP_VER);
|
|
|
+ frame->fip.fip_op = htons(FIP_OP_VN2VN);
|
|
|
+ frame->fip.fip_subcode = sub;
|
|
|
+ frame->fip.fip_dl_len = htons(dlen / FIP_BPW);
|
|
|
+
|
|
|
+ frame->mac.fd_desc.fip_dtype = FIP_DT_MAC;
|
|
|
+ frame->mac.fd_desc.fip_dlen = sizeof(frame->mac) / FIP_BPW;
|
|
|
+ memcpy(frame->mac.fd_mac, fip->ctl_src_addr, ETH_ALEN);
|
|
|
+
|
|
|
+ frame->wwnn.fd_desc.fip_dtype = FIP_DT_NAME;
|
|
|
+ frame->wwnn.fd_desc.fip_dlen = sizeof(frame->wwnn) / FIP_BPW;
|
|
|
+ put_unaligned_be64(fip->lp->wwnn, &frame->wwnn.fd_wwn);
|
|
|
+
|
|
|
+ frame->vn.fd_desc.fip_dtype = FIP_DT_VN_ID;
|
|
|
+ frame->vn.fd_desc.fip_dlen = sizeof(frame->vn) / FIP_BPW;
|
|
|
+ hton24(frame->vn.fd_mac, FIP_VN_FC_MAP);
|
|
|
+ hton24(frame->vn.fd_mac + 3, fip->port_id);
|
|
|
+ hton24(frame->vn.fd_fc_id, fip->port_id);
|
|
|
+ put_unaligned_be64(fip->lp->wwpn, &frame->vn.fd_wwpn);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * For claims, add FC-4 features.
|
|
|
+ * TBD: Add interface to get fc-4 types and features from libfc.
|
|
|
+ */
|
|
|
+ if (sub == FIP_SC_VN_CLAIM_NOTIFY || sub == FIP_SC_VN_CLAIM_REP) {
|
|
|
+ ff = (struct fip_fc4_feat *)(frame + 1);
|
|
|
+ ff->fd_desc.fip_dtype = FIP_DT_FC4F;
|
|
|
+ ff->fd_desc.fip_dlen = sizeof(*ff) / FIP_BPW;
|
|
|
+ ff->fd_fts = fip->lp->fcts;
|
|
|
+
|
|
|
+ fcp_feat = 0;
|
|
|
+ if (fip->lp->service_params & FCP_SPPF_INIT_FCN)
|
|
|
+ fcp_feat |= FCP_FEAT_INIT;
|
|
|
+ if (fip->lp->service_params & FCP_SPPF_TARG_FCN)
|
|
|
+ fcp_feat |= FCP_FEAT_TARG;
|
|
|
+ fcp_feat <<= (FC_TYPE_FCP * 4) % 32;
|
|
|
+ ff->fd_ff.fd_feat[FC_TYPE_FCP * 4 / 32] = htonl(fcp_feat);
|
|
|
+
|
|
|
+ size = (struct fip_size_desc *)(ff + 1);
|
|
|
+ size->fd_desc.fip_dtype = FIP_DT_FCOE_SIZE;
|
|
|
+ size->fd_desc.fip_dlen = sizeof(*size) / FIP_BPW;
|
|
|
+ size->fd_size = htons(fcoe_ctlr_fcoe_size(fip));
|
|
|
+ }
|
|
|
+
|
|
|
+ skb_put(skb, len);
|
|
|
+ skb->protocol = htons(ETH_P_FIP);
|
|
|
+ skb_reset_mac_header(skb);
|
|
|
+ skb_reset_network_header(skb);
|
|
|
+
|
|
|
+ fip->send(fip, skb);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_vn_rport_callback - Event handler for rport events.
|
|
|
+ * @lport: The lport which is receiving the event
|
|
|
+ * @rdata: remote port private data
|
|
|
+ * @event: The event that occured
|
|
|
+ *
|
|
|
+ * Locking Note: The rport lock must not be held when calling this function.
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_vn_rport_callback(struct fc_lport *lport,
|
|
|
+ struct fc_rport_priv *rdata,
|
|
|
+ enum fc_rport_event event)
|
|
|
+{
|
|
|
+ struct fcoe_ctlr *fip = lport->disc.priv;
|
|
|
+ struct fcoe_rport *frport = fcoe_ctlr_rport(rdata);
|
|
|
+
|
|
|
+ LIBFCOE_FIP_DBG(fip, "vn_rport_callback %x event %d\n",
|
|
|
+ rdata->ids.port_id, event);
|
|
|
+
|
|
|
+ mutex_lock(&fip->ctlr_mutex);
|
|
|
+ switch (event) {
|
|
|
+ case RPORT_EV_READY:
|
|
|
+ frport->login_count = 0;
|
|
|
+ break;
|
|
|
+ case RPORT_EV_LOGO:
|
|
|
+ case RPORT_EV_FAILED:
|
|
|
+ case RPORT_EV_STOP:
|
|
|
+ frport->login_count++;
|
|
|
+ if (frport->login_count > FCOE_CTLR_VN2VN_LOGIN_LIMIT) {
|
|
|
+ LIBFCOE_FIP_DBG(fip,
|
|
|
+ "rport FLOGI limited port_id %6.6x\n",
|
|
|
+ rdata->ids.port_id);
|
|
|
+ lport->tt.rport_logoff(rdata);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ mutex_unlock(&fip->ctlr_mutex);
|
|
|
+}
|
|
|
+
|
|
|
+static struct fc_rport_operations fcoe_ctlr_vn_rport_ops = {
|
|
|
+ .event_callback = fcoe_ctlr_vn_rport_callback,
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_disc_stop_locked() - stop discovery in VN2VN mode
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ *
|
|
|
+ * Called with ctlr_mutex held.
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_disc_stop_locked(struct fc_lport *lport)
|
|
|
+{
|
|
|
+ mutex_lock(&lport->disc.disc_mutex);
|
|
|
+ lport->disc.disc_callback = NULL;
|
|
|
+ mutex_unlock(&lport->disc.disc_mutex);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_disc_stop() - stop discovery in VN2VN mode
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ *
|
|
|
+ * Called through the local port template for discovery.
|
|
|
+ * Called without the ctlr_mutex held.
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_disc_stop(struct fc_lport *lport)
|
|
|
+{
|
|
|
+ struct fcoe_ctlr *fip = lport->disc.priv;
|
|
|
+
|
|
|
+ mutex_lock(&fip->ctlr_mutex);
|
|
|
+ fcoe_ctlr_disc_stop_locked(lport);
|
|
|
+ mutex_unlock(&fip->ctlr_mutex);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_disc_stop_final() - stop discovery for shutdown in VN2VN mode
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ *
|
|
|
+ * Called through the local port template for discovery.
|
|
|
+ * Called without the ctlr_mutex held.
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_disc_stop_final(struct fc_lport *lport)
|
|
|
+{
|
|
|
+ fcoe_ctlr_disc_stop(lport);
|
|
|
+ lport->tt.rport_flush_queue();
|
|
|
+ synchronize_rcu();
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_vn_restart() - VN2VN probe restart with new port_id
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ *
|
|
|
+ * Called with fcoe_ctlr lock held.
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_vn_restart(struct fcoe_ctlr *fip)
|
|
|
+{
|
|
|
+ unsigned long wait;
|
|
|
+ u32 port_id;
|
|
|
+
|
|
|
+ fcoe_ctlr_disc_stop_locked(fip->lp);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Get proposed port ID.
|
|
|
+ * If this is the first try after link up, use any previous port_id.
|
|
|
+ * If there was none, use the low bits of the port_name.
|
|
|
+ * On subsequent tries, get the next random one.
|
|
|
+ * Don't use reserved IDs, use another non-zero value, just as random.
|
|
|
+ */
|
|
|
+ port_id = fip->port_id;
|
|
|
+ if (fip->probe_tries)
|
|
|
+ port_id = prandom32(&fip->rnd_state) & 0xffff;
|
|
|
+ else if (!port_id)
|
|
|
+ port_id = fip->lp->wwpn & 0xffff;
|
|
|
+ if (!port_id || port_id == 0xffff)
|
|
|
+ port_id = 1;
|
|
|
+ fip->port_id = port_id;
|
|
|
+
|
|
|
+ if (fip->probe_tries < FIP_VN_RLIM_COUNT) {
|
|
|
+ fip->probe_tries++;
|
|
|
+ wait = random32() % FIP_VN_PROBE_WAIT;
|
|
|
+ } else
|
|
|
+ wait = FIP_VN_RLIM_INT;
|
|
|
+ mod_timer(&fip->timer, jiffies + msecs_to_jiffies(wait));
|
|
|
+ fcoe_ctlr_set_state(fip, FIP_ST_VNMP_START);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_vn_start() - Start in VN2VN mode
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ *
|
|
|
+ * Called with fcoe_ctlr lock held.
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_vn_start(struct fcoe_ctlr *fip)
|
|
|
+{
|
|
|
+ fip->probe_tries = 0;
|
|
|
+ prandom32_seed(&fip->rnd_state, fip->lp->wwpn);
|
|
|
+ fcoe_ctlr_vn_restart(fip);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_vn_parse - parse probe request or response
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ * @skb: incoming packet
|
|
|
+ * @rdata: buffer for resulting parsed VN entry plus fcoe_rport
|
|
|
+ *
|
|
|
+ * Returns non-zero error number on error.
|
|
|
+ * Does not consume the packet.
|
|
|
+ */
|
|
|
+static int fcoe_ctlr_vn_parse(struct fcoe_ctlr *fip,
|
|
|
+ struct sk_buff *skb,
|
|
|
+ struct fc_rport_priv *rdata)
|
|
|
+{
|
|
|
+ struct fip_header *fiph;
|
|
|
+ struct fip_desc *desc = NULL;
|
|
|
+ struct fip_mac_desc *macd = NULL;
|
|
|
+ struct fip_wwn_desc *wwn = NULL;
|
|
|
+ struct fip_vn_desc *vn = NULL;
|
|
|
+ struct fip_size_desc *size = NULL;
|
|
|
+ struct fcoe_rport *frport;
|
|
|
+ size_t rlen;
|
|
|
+ size_t dlen;
|
|
|
+ u32 desc_mask = 0;
|
|
|
+ u32 dtype;
|
|
|
+ u8 sub;
|
|
|
+
|
|
|
+ memset(rdata, 0, sizeof(*rdata) + sizeof(*frport));
|
|
|
+ frport = fcoe_ctlr_rport(rdata);
|
|
|
+
|
|
|
+ fiph = (struct fip_header *)skb->data;
|
|
|
+ frport->flags = ntohs(fiph->fip_flags);
|
|
|
+
|
|
|
+ sub = fiph->fip_subcode;
|
|
|
+ switch (sub) {
|
|
|
+ case FIP_SC_VN_PROBE_REQ:
|
|
|
+ case FIP_SC_VN_PROBE_REP:
|
|
|
+ case FIP_SC_VN_BEACON:
|
|
|
+ desc_mask = BIT(FIP_DT_MAC) | BIT(FIP_DT_NAME) |
|
|
|
+ BIT(FIP_DT_VN_ID);
|
|
|
+ break;
|
|
|
+ case FIP_SC_VN_CLAIM_NOTIFY:
|
|
|
+ case FIP_SC_VN_CLAIM_REP:
|
|
|
+ desc_mask = BIT(FIP_DT_MAC) | BIT(FIP_DT_NAME) |
|
|
|
+ BIT(FIP_DT_VN_ID) | BIT(FIP_DT_FC4F) |
|
|
|
+ BIT(FIP_DT_FCOE_SIZE);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ LIBFCOE_FIP_DBG(fip, "vn_parse unknown subcode %u\n", sub);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ rlen = ntohs(fiph->fip_dl_len) * 4;
|
|
|
+ if (rlen + sizeof(*fiph) > skb->len)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ desc = (struct fip_desc *)(fiph + 1);
|
|
|
+ while (rlen > 0) {
|
|
|
+ dlen = desc->fip_dlen * FIP_BPW;
|
|
|
+ if (dlen < sizeof(*desc) || dlen > rlen)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ dtype = desc->fip_dtype;
|
|
|
+ if (dtype < 32) {
|
|
|
+ if (!(desc_mask & BIT(dtype))) {
|
|
|
+ LIBFCOE_FIP_DBG(fip,
|
|
|
+ "unexpected or duplicated desc "
|
|
|
+ "desc type %u in "
|
|
|
+ "FIP VN2VN subtype %u\n",
|
|
|
+ dtype, sub);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+ desc_mask &= ~BIT(dtype);
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (dtype) {
|
|
|
+ case FIP_DT_MAC:
|
|
|
+ if (dlen != sizeof(struct fip_mac_desc))
|
|
|
+ goto len_err;
|
|
|
+ macd = (struct fip_mac_desc *)desc;
|
|
|
+ if (!is_valid_ether_addr(macd->fd_mac)) {
|
|
|
+ LIBFCOE_FIP_DBG(fip,
|
|
|
+ "Invalid MAC addr %pM in FIP VN2VN\n",
|
|
|
+ macd->fd_mac);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+ memcpy(frport->enode_mac, macd->fd_mac, ETH_ALEN);
|
|
|
+ break;
|
|
|
+ case FIP_DT_NAME:
|
|
|
+ if (dlen != sizeof(struct fip_wwn_desc))
|
|
|
+ goto len_err;
|
|
|
+ wwn = (struct fip_wwn_desc *)desc;
|
|
|
+ rdata->ids.node_name = get_unaligned_be64(&wwn->fd_wwn);
|
|
|
+ break;
|
|
|
+ case FIP_DT_VN_ID:
|
|
|
+ if (dlen != sizeof(struct fip_vn_desc))
|
|
|
+ goto len_err;
|
|
|
+ vn = (struct fip_vn_desc *)desc;
|
|
|
+ memcpy(frport->vn_mac, vn->fd_mac, ETH_ALEN);
|
|
|
+ rdata->ids.port_id = ntoh24(vn->fd_fc_id);
|
|
|
+ rdata->ids.port_name = get_unaligned_be64(&vn->fd_wwpn);
|
|
|
+ break;
|
|
|
+ case FIP_DT_FC4F:
|
|
|
+ if (dlen != sizeof(struct fip_fc4_feat))
|
|
|
+ goto len_err;
|
|
|
+ break;
|
|
|
+ case FIP_DT_FCOE_SIZE:
|
|
|
+ if (dlen != sizeof(struct fip_size_desc))
|
|
|
+ goto len_err;
|
|
|
+ size = (struct fip_size_desc *)desc;
|
|
|
+ frport->fcoe_len = ntohs(size->fd_size);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ LIBFCOE_FIP_DBG(fip, "unexpected descriptor type %x "
|
|
|
+ "in FIP probe\n", dtype);
|
|
|
+ /* standard says ignore unknown descriptors >= 128 */
|
|
|
+ if (dtype < FIP_DT_VENDOR_BASE)
|
|
|
+ return -EINVAL;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ desc = (struct fip_desc *)((char *)desc + dlen);
|
|
|
+ rlen -= dlen;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+
|
|
|
+len_err:
|
|
|
+ LIBFCOE_FIP_DBG(fip, "FIP length error in descriptor type %x len %zu\n",
|
|
|
+ dtype, dlen);
|
|
|
+ return -EINVAL;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_vn_send_claim() - send multicast FIP VN2VN Claim Notification.
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ *
|
|
|
+ * Called with ctlr_mutex held.
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_vn_send_claim(struct fcoe_ctlr *fip)
|
|
|
+{
|
|
|
+ fcoe_ctlr_vn_send(fip, FIP_SC_VN_CLAIM_NOTIFY, fcoe_all_vn2vn, 0);
|
|
|
+ fip->sol_time = jiffies;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_vn_probe_req() - handle incoming VN2VN probe request.
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ * @rdata: parsed remote port with frport from the probe request
|
|
|
+ *
|
|
|
+ * Called with ctlr_mutex held.
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_vn_probe_req(struct fcoe_ctlr *fip,
|
|
|
+ struct fc_rport_priv *rdata)
|
|
|
+{
|
|
|
+ struct fcoe_rport *frport = fcoe_ctlr_rport(rdata);
|
|
|
+
|
|
|
+ if (rdata->ids.port_id != fip->port_id)
|
|
|
+ return;
|
|
|
+
|
|
|
+ switch (fip->state) {
|
|
|
+ case FIP_ST_VNMP_CLAIM:
|
|
|
+ case FIP_ST_VNMP_UP:
|
|
|
+ fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REP,
|
|
|
+ frport->enode_mac, 0);
|
|
|
+ break;
|
|
|
+ case FIP_ST_VNMP_PROBE1:
|
|
|
+ case FIP_ST_VNMP_PROBE2:
|
|
|
+ /*
|
|
|
+ * Decide whether to reply to the Probe.
|
|
|
+ * Our selected address is never a "recorded" one, so
|
|
|
+ * only reply if our WWPN is greater and the
|
|
|
+ * Probe's REC bit is not set.
|
|
|
+ * If we don't reply, we will change our address.
|
|
|
+ */
|
|
|
+ if (fip->lp->wwpn > rdata->ids.port_name &&
|
|
|
+ !(frport->flags & FIP_FL_REC_OR_P2P)) {
|
|
|
+ fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REP,
|
|
|
+ frport->enode_mac, 0);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ /* fall through */
|
|
|
+ case FIP_ST_VNMP_START:
|
|
|
+ fcoe_ctlr_vn_restart(fip);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_vn_probe_reply() - handle incoming VN2VN probe reply.
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ * @rdata: parsed remote port with frport from the probe request
|
|
|
+ *
|
|
|
+ * Called with ctlr_mutex held.
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_vn_probe_reply(struct fcoe_ctlr *fip,
|
|
|
+ struct fc_rport_priv *rdata)
|
|
|
+{
|
|
|
+ if (rdata->ids.port_id != fip->port_id)
|
|
|
+ return;
|
|
|
+ switch (fip->state) {
|
|
|
+ case FIP_ST_VNMP_START:
|
|
|
+ case FIP_ST_VNMP_PROBE1:
|
|
|
+ case FIP_ST_VNMP_PROBE2:
|
|
|
+ case FIP_ST_VNMP_CLAIM:
|
|
|
+ fcoe_ctlr_vn_restart(fip);
|
|
|
+ break;
|
|
|
+ case FIP_ST_VNMP_UP:
|
|
|
+ fcoe_ctlr_vn_send_claim(fip);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_vn_add() - Add a VN2VN entry to the list, based on a claim reply.
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ * @new: newly-parsed remote port with frport as a template for new rdata
|
|
|
+ *
|
|
|
+ * Called with ctlr_mutex held.
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_vn_add(struct fcoe_ctlr *fip, struct fc_rport_priv *new)
|
|
|
+{
|
|
|
+ struct fc_lport *lport = fip->lp;
|
|
|
+ struct fc_rport_priv *rdata;
|
|
|
+ struct fc_rport_identifiers *ids;
|
|
|
+ struct fcoe_rport *frport;
|
|
|
+ u32 port_id;
|
|
|
+
|
|
|
+ port_id = new->ids.port_id;
|
|
|
+ if (port_id == fip->port_id)
|
|
|
+ return;
|
|
|
+
|
|
|
+ mutex_lock(&lport->disc.disc_mutex);
|
|
|
+ rdata = lport->tt.rport_create(lport, port_id);
|
|
|
+ if (!rdata) {
|
|
|
+ mutex_unlock(&lport->disc.disc_mutex);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ rdata->ops = &fcoe_ctlr_vn_rport_ops;
|
|
|
+ rdata->disc_id = lport->disc.disc_id;
|
|
|
+
|
|
|
+ ids = &rdata->ids;
|
|
|
+ if ((ids->port_name != -1 && ids->port_name != new->ids.port_name) ||
|
|
|
+ (ids->node_name != -1 && ids->node_name != new->ids.node_name))
|
|
|
+ lport->tt.rport_logoff(rdata);
|
|
|
+ ids->port_name = new->ids.port_name;
|
|
|
+ ids->node_name = new->ids.node_name;
|
|
|
+ mutex_unlock(&lport->disc.disc_mutex);
|
|
|
+
|
|
|
+ frport = fcoe_ctlr_rport(rdata);
|
|
|
+ LIBFCOE_FIP_DBG(fip, "vn_add rport %6.6x %s\n",
|
|
|
+ port_id, frport->fcoe_len ? "old" : "new");
|
|
|
+ *frport = *fcoe_ctlr_rport(new);
|
|
|
+ frport->time = 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_vn_lookup() - Find VN remote port's MAC address
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ * @port_id: The port_id of the remote VN_node
|
|
|
+ * @mac: buffer which will hold the VN_NODE destination MAC address, if found.
|
|
|
+ *
|
|
|
+ * Returns non-zero error if no remote port found.
|
|
|
+ */
|
|
|
+static int fcoe_ctlr_vn_lookup(struct fcoe_ctlr *fip, u32 port_id, u8 *mac)
|
|
|
+{
|
|
|
+ struct fc_lport *lport = fip->lp;
|
|
|
+ struct fc_rport_priv *rdata;
|
|
|
+ struct fcoe_rport *frport;
|
|
|
+ int ret = -1;
|
|
|
+
|
|
|
+ rcu_read_lock();
|
|
|
+ rdata = lport->tt.rport_lookup(lport, port_id);
|
|
|
+ if (rdata) {
|
|
|
+ frport = fcoe_ctlr_rport(rdata);
|
|
|
+ memcpy(mac, frport->enode_mac, ETH_ALEN);
|
|
|
+ ret = 0;
|
|
|
+ }
|
|
|
+ rcu_read_unlock();
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_vn_claim_notify() - handle received FIP VN2VN Claim Notification
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ * @new: newly-parsed remote port with frport as a template for new rdata
|
|
|
+ *
|
|
|
+ * Called with ctlr_mutex held.
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_vn_claim_notify(struct fcoe_ctlr *fip,
|
|
|
+ struct fc_rport_priv *new)
|
|
|
+{
|
|
|
+ struct fcoe_rport *frport = fcoe_ctlr_rport(new);
|
|
|
+
|
|
|
+ if (frport->flags & FIP_FL_REC_OR_P2P) {
|
|
|
+ fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REQ, fcoe_all_vn2vn, 0);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ switch (fip->state) {
|
|
|
+ case FIP_ST_VNMP_START:
|
|
|
+ case FIP_ST_VNMP_PROBE1:
|
|
|
+ case FIP_ST_VNMP_PROBE2:
|
|
|
+ if (new->ids.port_id == fip->port_id)
|
|
|
+ fcoe_ctlr_vn_restart(fip);
|
|
|
+ break;
|
|
|
+ case FIP_ST_VNMP_CLAIM:
|
|
|
+ case FIP_ST_VNMP_UP:
|
|
|
+ if (new->ids.port_id == fip->port_id) {
|
|
|
+ if (new->ids.port_name > fip->lp->wwpn) {
|
|
|
+ fcoe_ctlr_vn_restart(fip);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ fcoe_ctlr_vn_send_claim(fip);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ fcoe_ctlr_vn_send(fip, FIP_SC_VN_CLAIM_REP, frport->enode_mac,
|
|
|
+ min((u32)frport->fcoe_len,
|
|
|
+ fcoe_ctlr_fcoe_size(fip)));
|
|
|
+ fcoe_ctlr_vn_add(fip, new);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_vn_claim_resp() - handle received Claim Response
|
|
|
+ * @fip: The FCoE controller that received the frame
|
|
|
+ * @new: newly-parsed remote port with frport from the Claim Response
|
|
|
+ *
|
|
|
+ * Called with ctlr_mutex held.
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_vn_claim_resp(struct fcoe_ctlr *fip,
|
|
|
+ struct fc_rport_priv *new)
|
|
|
+{
|
|
|
+ LIBFCOE_FIP_DBG(fip, "claim resp from from rport %x - state %s\n",
|
|
|
+ new->ids.port_id, fcoe_ctlr_state(fip->state));
|
|
|
+ if (fip->state == FIP_ST_VNMP_UP || fip->state == FIP_ST_VNMP_CLAIM)
|
|
|
+ fcoe_ctlr_vn_add(fip, new);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_vn_beacon() - handle received beacon.
|
|
|
+ * @fip: The FCoE controller that received the frame
|
|
|
+ * @new: newly-parsed remote port with frport from the Beacon
|
|
|
+ *
|
|
|
+ * Called with ctlr_mutex held.
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_vn_beacon(struct fcoe_ctlr *fip,
|
|
|
+ struct fc_rport_priv *new)
|
|
|
+{
|
|
|
+ struct fc_lport *lport = fip->lp;
|
|
|
+ struct fc_rport_priv *rdata;
|
|
|
+ struct fcoe_rport *frport;
|
|
|
+
|
|
|
+ frport = fcoe_ctlr_rport(new);
|
|
|
+ if (frport->flags & FIP_FL_REC_OR_P2P) {
|
|
|
+ fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REQ, fcoe_all_vn2vn, 0);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ mutex_lock(&lport->disc.disc_mutex);
|
|
|
+ rdata = lport->tt.rport_lookup(lport, new->ids.port_id);
|
|
|
+ if (rdata)
|
|
|
+ kref_get(&rdata->kref);
|
|
|
+ mutex_unlock(&lport->disc.disc_mutex);
|
|
|
+ if (rdata) {
|
|
|
+ if (rdata->ids.node_name == new->ids.node_name &&
|
|
|
+ rdata->ids.port_name == new->ids.port_name) {
|
|
|
+ frport = fcoe_ctlr_rport(rdata);
|
|
|
+ if (!frport->time && fip->state == FIP_ST_VNMP_UP)
|
|
|
+ lport->tt.rport_login(rdata);
|
|
|
+ frport->time = jiffies;
|
|
|
+ }
|
|
|
+ kref_put(&rdata->kref, lport->tt.rport_destroy);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (fip->state != FIP_ST_VNMP_UP)
|
|
|
+ return;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Beacon from a new neighbor.
|
|
|
+ * Send a claim notify if one hasn't been sent recently.
|
|
|
+ * Don't add the neighbor yet.
|
|
|
+ */
|
|
|
+ LIBFCOE_FIP_DBG(fip, "beacon from new rport %x. sending claim notify\n",
|
|
|
+ new->ids.port_id);
|
|
|
+ if (time_after(jiffies,
|
|
|
+ fip->sol_time + msecs_to_jiffies(FIP_VN_ANN_WAIT)))
|
|
|
+ fcoe_ctlr_vn_send_claim(fip);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_vn_age() - Check for VN_ports without recent beacons
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ *
|
|
|
+ * Called with ctlr_mutex held.
|
|
|
+ * Called only in state FIP_ST_VNMP_UP.
|
|
|
+ * Returns the soonest time for next age-out or a time far in the future.
|
|
|
+ */
|
|
|
+static unsigned long fcoe_ctlr_vn_age(struct fcoe_ctlr *fip)
|
|
|
+{
|
|
|
+ struct fc_lport *lport = fip->lp;
|
|
|
+ struct fc_rport_priv *rdata;
|
|
|
+ struct fcoe_rport *frport;
|
|
|
+ unsigned long next_time;
|
|
|
+ unsigned long deadline;
|
|
|
+
|
|
|
+ next_time = jiffies + msecs_to_jiffies(FIP_VN_BEACON_INT * 10);
|
|
|
+ mutex_lock(&lport->disc.disc_mutex);
|
|
|
+ list_for_each_entry_rcu(rdata, &lport->disc.rports, peers) {
|
|
|
+ frport = fcoe_ctlr_rport(rdata);
|
|
|
+ if (!frport->time)
|
|
|
+ continue;
|
|
|
+ deadline = frport->time +
|
|
|
+ msecs_to_jiffies(FIP_VN_BEACON_INT * 25 / 10);
|
|
|
+ if (time_after_eq(jiffies, deadline)) {
|
|
|
+ frport->time = 0;
|
|
|
+ LIBFCOE_FIP_DBG(fip,
|
|
|
+ "port %16.16llx fc_id %6.6x beacon expired\n",
|
|
|
+ rdata->ids.port_name, rdata->ids.port_id);
|
|
|
+ lport->tt.rport_logoff(rdata);
|
|
|
+ } else if (time_before(deadline, next_time))
|
|
|
+ next_time = deadline;
|
|
|
+ }
|
|
|
+ mutex_unlock(&lport->disc.disc_mutex);
|
|
|
+ return next_time;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_vn_recv() - Receive a FIP frame
|
|
|
+ * @fip: The FCoE controller that received the frame
|
|
|
+ * @skb: The received FIP frame
|
|
|
+ *
|
|
|
+ * Returns non-zero if the frame is dropped.
|
|
|
+ * Always consumes the frame.
|
|
|
+ */
|
|
|
+static int fcoe_ctlr_vn_recv(struct fcoe_ctlr *fip, struct sk_buff *skb)
|
|
|
+{
|
|
|
+ struct fip_header *fiph;
|
|
|
+ enum fip_vn2vn_subcode sub;
|
|
|
+ union {
|
|
|
+ struct fc_rport_priv rdata;
|
|
|
+ struct fcoe_rport frport;
|
|
|
+ } buf;
|
|
|
+ int rc;
|
|
|
+
|
|
|
+ fiph = (struct fip_header *)skb->data;
|
|
|
+ sub = fiph->fip_subcode;
|
|
|
+
|
|
|
+ rc = fcoe_ctlr_vn_parse(fip, skb, &buf.rdata);
|
|
|
+ if (rc) {
|
|
|
+ LIBFCOE_FIP_DBG(fip, "vn_recv vn_parse error %d\n", rc);
|
|
|
+ goto drop;
|
|
|
+ }
|
|
|
+
|
|
|
+ mutex_lock(&fip->ctlr_mutex);
|
|
|
+ switch (sub) {
|
|
|
+ case FIP_SC_VN_PROBE_REQ:
|
|
|
+ fcoe_ctlr_vn_probe_req(fip, &buf.rdata);
|
|
|
+ break;
|
|
|
+ case FIP_SC_VN_PROBE_REP:
|
|
|
+ fcoe_ctlr_vn_probe_reply(fip, &buf.rdata);
|
|
|
+ break;
|
|
|
+ case FIP_SC_VN_CLAIM_NOTIFY:
|
|
|
+ fcoe_ctlr_vn_claim_notify(fip, &buf.rdata);
|
|
|
+ break;
|
|
|
+ case FIP_SC_VN_CLAIM_REP:
|
|
|
+ fcoe_ctlr_vn_claim_resp(fip, &buf.rdata);
|
|
|
+ break;
|
|
|
+ case FIP_SC_VN_BEACON:
|
|
|
+ fcoe_ctlr_vn_beacon(fip, &buf.rdata);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ LIBFCOE_FIP_DBG(fip, "vn_recv unknown subcode %d\n", sub);
|
|
|
+ rc = -1;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ mutex_unlock(&fip->ctlr_mutex);
|
|
|
+drop:
|
|
|
+ kfree_skb(skb);
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_disc_recv - discovery receive handler for VN2VN mode.
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ *
|
|
|
+ * This should never be called since we don't see RSCNs or other
|
|
|
+ * fabric-generated ELSes.
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_disc_recv(struct fc_seq *seq, struct fc_frame *fp,
|
|
|
+ struct fc_lport *lport)
|
|
|
+{
|
|
|
+ struct fc_seq_els_data rjt_data;
|
|
|
+
|
|
|
+ rjt_data.fp = NULL;
|
|
|
+ rjt_data.reason = ELS_RJT_UNSUP;
|
|
|
+ rjt_data.explan = ELS_EXPL_NONE;
|
|
|
+ lport->tt.seq_els_rsp_send(seq, ELS_LS_RJT, &rjt_data);
|
|
|
+ fc_frame_free(fp);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_disc_recv - start discovery for VN2VN mode.
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ *
|
|
|
+ * This sets a flag indicating that remote ports should be created
|
|
|
+ * and started for the peers we discover. We use the disc_callback
|
|
|
+ * pointer as that flag. Peers already discovered are created here.
|
|
|
+ *
|
|
|
+ * The lport lock is held during this call. The callback must be done
|
|
|
+ * later, without holding either the lport or discovery locks.
|
|
|
+ * The fcoe_ctlr lock may also be held during this call.
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_disc_start(void (*callback)(struct fc_lport *,
|
|
|
+ enum fc_disc_event),
|
|
|
+ struct fc_lport *lport)
|
|
|
+{
|
|
|
+ struct fc_disc *disc = &lport->disc;
|
|
|
+ struct fcoe_ctlr *fip = disc->priv;
|
|
|
+
|
|
|
+ mutex_lock(&disc->disc_mutex);
|
|
|
+ disc->disc_callback = callback;
|
|
|
+ disc->disc_id = (disc->disc_id + 2) | 1;
|
|
|
+ disc->pending = 1;
|
|
|
+ schedule_work(&fip->timer_work);
|
|
|
+ mutex_unlock(&disc->disc_mutex);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_vn_disc() - report FIP VN_port discovery results after claim state.
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ *
|
|
|
+ * Starts the FLOGI and PLOGI login process to each discovered rport for which
|
|
|
+ * we've received at least one beacon.
|
|
|
+ * Performs the discovery complete callback.
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_vn_disc(struct fcoe_ctlr *fip)
|
|
|
+{
|
|
|
+ struct fc_lport *lport = fip->lp;
|
|
|
+ struct fc_disc *disc = &lport->disc;
|
|
|
+ struct fc_rport_priv *rdata;
|
|
|
+ struct fcoe_rport *frport;
|
|
|
+ void (*callback)(struct fc_lport *, enum fc_disc_event);
|
|
|
+
|
|
|
+ mutex_lock(&disc->disc_mutex);
|
|
|
+ callback = disc->pending ? disc->disc_callback : NULL;
|
|
|
+ disc->pending = 0;
|
|
|
+ list_for_each_entry_rcu(rdata, &disc->rports, peers) {
|
|
|
+ frport = fcoe_ctlr_rport(rdata);
|
|
|
+ if (frport->time)
|
|
|
+ lport->tt.rport_login(rdata);
|
|
|
+ }
|
|
|
+ mutex_unlock(&disc->disc_mutex);
|
|
|
+ if (callback)
|
|
|
+ callback(lport, DISC_EV_SUCCESS);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_vn_timeout - timer work function for VN2VN mode.
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_vn_timeout(struct fcoe_ctlr *fip)
|
|
|
+{
|
|
|
+ unsigned long next_time;
|
|
|
+ u8 mac[ETH_ALEN];
|
|
|
+ u32 new_port_id = 0;
|
|
|
+
|
|
|
+ mutex_lock(&fip->ctlr_mutex);
|
|
|
+ switch (fip->state) {
|
|
|
+ case FIP_ST_VNMP_START:
|
|
|
+ fcoe_ctlr_set_state(fip, FIP_ST_VNMP_PROBE1);
|
|
|
+ fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REQ, fcoe_all_vn2vn, 0);
|
|
|
+ next_time = jiffies + msecs_to_jiffies(FIP_VN_PROBE_WAIT);
|
|
|
+ break;
|
|
|
+ case FIP_ST_VNMP_PROBE1:
|
|
|
+ fcoe_ctlr_set_state(fip, FIP_ST_VNMP_PROBE2);
|
|
|
+ fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REQ, fcoe_all_vn2vn, 0);
|
|
|
+ next_time = jiffies + msecs_to_jiffies(FIP_VN_ANN_WAIT);
|
|
|
+ break;
|
|
|
+ case FIP_ST_VNMP_PROBE2:
|
|
|
+ fcoe_ctlr_set_state(fip, FIP_ST_VNMP_CLAIM);
|
|
|
+ new_port_id = fip->port_id;
|
|
|
+ hton24(mac, FIP_VN_FC_MAP);
|
|
|
+ hton24(mac + 3, new_port_id);
|
|
|
+ fip->update_mac(fip->lp, mac);
|
|
|
+ fcoe_ctlr_vn_send_claim(fip);
|
|
|
+ next_time = jiffies + msecs_to_jiffies(FIP_VN_ANN_WAIT);
|
|
|
+ break;
|
|
|
+ case FIP_ST_VNMP_CLAIM:
|
|
|
+ /*
|
|
|
+ * This may be invoked either by starting discovery so don't
|
|
|
+ * go to the next state unless it's been long enough.
|
|
|
+ */
|
|
|
+ next_time = fip->sol_time + msecs_to_jiffies(FIP_VN_ANN_WAIT);
|
|
|
+ if (time_after_eq(jiffies, next_time)) {
|
|
|
+ fcoe_ctlr_set_state(fip, FIP_ST_VNMP_UP);
|
|
|
+ fcoe_ctlr_vn_send(fip, FIP_SC_VN_BEACON,
|
|
|
+ fcoe_all_vn2vn, 0);
|
|
|
+ next_time = jiffies + msecs_to_jiffies(FIP_VN_ANN_WAIT);
|
|
|
+ fip->port_ka_time = next_time;
|
|
|
+ }
|
|
|
+ fcoe_ctlr_vn_disc(fip);
|
|
|
+ break;
|
|
|
+ case FIP_ST_VNMP_UP:
|
|
|
+ next_time = fcoe_ctlr_vn_age(fip);
|
|
|
+ if (time_after_eq(jiffies, fip->port_ka_time)) {
|
|
|
+ fcoe_ctlr_vn_send(fip, FIP_SC_VN_BEACON,
|
|
|
+ fcoe_all_vn2vn, 0);
|
|
|
+ fip->port_ka_time = jiffies +
|
|
|
+ msecs_to_jiffies(FIP_VN_BEACON_INT +
|
|
|
+ (random32() % FIP_VN_BEACON_FUZZ));
|
|
|
+ }
|
|
|
+ if (time_before(fip->port_ka_time, next_time))
|
|
|
+ next_time = fip->port_ka_time;
|
|
|
+ break;
|
|
|
+ case FIP_ST_LINK_WAIT:
|
|
|
+ goto unlock;
|
|
|
+ default:
|
|
|
+ WARN(1, "unexpected state %d", fip->state);
|
|
|
+ goto unlock;
|
|
|
+ }
|
|
|
+ mod_timer(&fip->timer, next_time);
|
|
|
+unlock:
|
|
|
+ mutex_unlock(&fip->ctlr_mutex);
|
|
|
+
|
|
|
+ /* If port ID is new, notify local port after dropping ctlr_mutex */
|
|
|
+ if (new_port_id)
|
|
|
+ fc_lport_set_local_id(fip->lp, new_port_id);
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* fcoe_libfc_config() - Sets up libfc related properties for local port
|
|
|
* @lp: The local port to configure libfc for
|
|
|
+ * @fip: The FCoE controller in use by the local port
|
|
|
* @tt: The libfc function template
|
|
|
+ * @init_fcp: If non-zero, the FCP portion of libfc should be initialized
|
|
|
*
|
|
|
* Returns : 0 for success
|
|
|
*/
|
|
|
-int fcoe_libfc_config(struct fc_lport *lport,
|
|
|
- struct libfc_function_template *tt)
|
|
|
+int fcoe_libfc_config(struct fc_lport *lport, struct fcoe_ctlr *fip,
|
|
|
+ const struct libfc_function_template *tt, int init_fcp)
|
|
|
{
|
|
|
/* Set the function pointers set by the LLDD */
|
|
|
memcpy(&lport->tt, tt, sizeof(*tt));
|
|
|
- if (fc_fcp_init(lport))
|
|
|
+ if (init_fcp && fc_fcp_init(lport))
|
|
|
return -ENOMEM;
|
|
|
fc_exch_init(lport);
|
|
|
fc_elsct_init(lport);
|
|
|
fc_lport_init(lport);
|
|
|
+ if (fip->mode == FIP_MODE_VN2VN)
|
|
|
+ lport->rport_priv_size = sizeof(struct fcoe_rport);
|
|
|
fc_rport_init(lport);
|
|
|
- fc_disc_init(lport);
|
|
|
-
|
|
|
+ if (fip->mode == FIP_MODE_VN2VN) {
|
|
|
+ lport->point_to_multipoint = 1;
|
|
|
+ lport->tt.disc_recv_req = fcoe_ctlr_disc_recv;
|
|
|
+ lport->tt.disc_start = fcoe_ctlr_disc_start;
|
|
|
+ lport->tt.disc_stop = fcoe_ctlr_disc_stop;
|
|
|
+ lport->tt.disc_stop_final = fcoe_ctlr_disc_stop_final;
|
|
|
+ mutex_init(&lport->disc.disc_mutex);
|
|
|
+ INIT_LIST_HEAD(&lport->disc.rports);
|
|
|
+ lport->disc.priv = fip;
|
|
|
+ } else {
|
|
|
+ fc_disc_init(lport);
|
|
|
+ }
|
|
|
return 0;
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(fcoe_libfc_config);
|
|
|
-
|