|
@@ -54,6 +54,8 @@ MODULE_LICENSE("GPL v2");
|
|
|
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 int fcoe_ctlr_flogi_retry(struct fcoe_ctlr *);
|
|
|
+static void fcoe_ctlr_select(struct fcoe_ctlr *);
|
|
|
|
|
|
static void fcoe_ctlr_vn_start(struct fcoe_ctlr *);
|
|
|
static int fcoe_ctlr_vn_recv(struct fcoe_ctlr *, struct sk_buff *);
|
|
@@ -176,6 +178,7 @@ void fcoe_ctlr_init(struct fcoe_ctlr *fip, enum fip_state mode)
|
|
|
fip->mode = mode;
|
|
|
INIT_LIST_HEAD(&fip->fcfs);
|
|
|
mutex_init(&fip->ctlr_mutex);
|
|
|
+ spin_lock_init(&fip->ctlr_lock);
|
|
|
fip->flogi_oxid = FC_XID_UNKNOWN;
|
|
|
setup_timer(&fip->timer, fcoe_ctlr_timeout, (unsigned long)fip);
|
|
|
INIT_WORK(&fip->timer_work, fcoe_ctlr_timer_work);
|
|
@@ -231,17 +234,31 @@ void fcoe_ctlr_destroy(struct fcoe_ctlr *fip)
|
|
|
EXPORT_SYMBOL(fcoe_ctlr_destroy);
|
|
|
|
|
|
/**
|
|
|
- * fcoe_ctlr_announce() - announce new selection
|
|
|
+ * fcoe_ctlr_announce() - announce new FCF selection
|
|
|
* @fip: The FCoE controller
|
|
|
*
|
|
|
* Also sets the destination MAC for FCoE and control packets
|
|
|
+ *
|
|
|
+ * Called with neither ctlr_mutex nor ctlr_lock held.
|
|
|
*/
|
|
|
static void fcoe_ctlr_announce(struct fcoe_ctlr *fip)
|
|
|
{
|
|
|
- struct fcoe_fcf *sel = fip->sel_fcf;
|
|
|
+ struct fcoe_fcf *sel;
|
|
|
+ struct fcoe_fcf *fcf;
|
|
|
+
|
|
|
+ mutex_lock(&fip->ctlr_mutex);
|
|
|
+ spin_lock_bh(&fip->ctlr_lock);
|
|
|
+
|
|
|
+ kfree_skb(fip->flogi_req);
|
|
|
+ fip->flogi_req = NULL;
|
|
|
+ list_for_each_entry(fcf, &fip->fcfs, list)
|
|
|
+ fcf->flogi_sent = 0;
|
|
|
+
|
|
|
+ spin_unlock_bh(&fip->ctlr_lock);
|
|
|
+ sel = fip->sel_fcf;
|
|
|
|
|
|
if (sel && !compare_ether_addr(sel->fcf_mac, fip->dest_addr))
|
|
|
- return;
|
|
|
+ goto unlock;
|
|
|
if (!is_zero_ether_addr(fip->dest_addr)) {
|
|
|
printk(KERN_NOTICE "libfcoe: host%d: "
|
|
|
"FIP Fibre-Channel Forwarder MAC %pM deselected\n",
|
|
@@ -255,6 +272,8 @@ static void fcoe_ctlr_announce(struct fcoe_ctlr *fip)
|
|
|
memcpy(fip->dest_addr, sel->fcf_mac, ETH_ALEN);
|
|
|
fip->map_dest = 0;
|
|
|
}
|
|
|
+unlock:
|
|
|
+ mutex_unlock(&fip->ctlr_mutex);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -591,6 +610,9 @@ 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.
|
|
|
+ *
|
|
|
+ * This is called from the lower-level driver with spinlocks held,
|
|
|
+ * so we must not take a mutex here.
|
|
|
*/
|
|
|
int fcoe_ctlr_els_send(struct fcoe_ctlr *fip, struct fc_lport *lport,
|
|
|
struct sk_buff *skb)
|
|
@@ -628,7 +650,15 @@ int fcoe_ctlr_els_send(struct fcoe_ctlr *fip, struct fc_lport *lport,
|
|
|
switch (op) {
|
|
|
case ELS_FLOGI:
|
|
|
op = FIP_DT_FLOGI;
|
|
|
- break;
|
|
|
+ if (fip->mode == FIP_MODE_VN2VN)
|
|
|
+ break;
|
|
|
+ spin_lock_bh(&fip->ctlr_lock);
|
|
|
+ kfree_skb(fip->flogi_req);
|
|
|
+ fip->flogi_req = skb;
|
|
|
+ fip->flogi_req_send = 1;
|
|
|
+ spin_unlock_bh(&fip->ctlr_lock);
|
|
|
+ schedule_work(&fip->timer_work);
|
|
|
+ return -EINPROGRESS;
|
|
|
case ELS_FDISC:
|
|
|
if (ntoh24(fh->fh_s_id))
|
|
|
return 0;
|
|
@@ -1088,18 +1118,24 @@ static void fcoe_ctlr_recv_els(struct fcoe_ctlr *fip, struct sk_buff *skb)
|
|
|
els_op = *(u8 *)(fh + 1);
|
|
|
|
|
|
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);
|
|
|
+ sub == FIP_SC_REP && fip->mode != FIP_MODE_VN2VN) {
|
|
|
+ if (els_op == ELS_LS_ACC) {
|
|
|
+ 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 (fip->flogi_oxid == ntohs(fh->fh_ox_id)) {
|
|
|
+ fip->flogi_oxid = FC_XID_UNKNOWN;
|
|
|
+ if (els_dtype == FIP_DT_FLOGI)
|
|
|
+ fcoe_ctlr_announce(fip);
|
|
|
+ }
|
|
|
+ } else if (els_dtype == FIP_DT_FLOGI &&
|
|
|
+ !fcoe_ctlr_flogi_retry(fip))
|
|
|
+ goto drop; /* retrying FLOGI so drop reject */
|
|
|
}
|
|
|
|
|
|
if ((desc_cnt == 0) || ((els_op != ELS_LS_RJT) &&
|
|
@@ -1355,12 +1391,15 @@ drop:
|
|
|
*
|
|
|
* If there are conflicting advertisements, no FCF can be chosen.
|
|
|
*
|
|
|
+ * If there is already a selected FCF, this will choose a better one or
|
|
|
+ * an equivalent one that hasn't already been sent a FLOGI.
|
|
|
+ *
|
|
|
* Called with lock held.
|
|
|
*/
|
|
|
static void fcoe_ctlr_select(struct fcoe_ctlr *fip)
|
|
|
{
|
|
|
struct fcoe_fcf *fcf;
|
|
|
- struct fcoe_fcf *best = NULL;
|
|
|
+ struct fcoe_fcf *best = fip->sel_fcf;
|
|
|
struct fcoe_fcf *first;
|
|
|
|
|
|
first = list_first_entry(&fip->fcfs, struct fcoe_fcf, list);
|
|
@@ -1377,6 +1416,8 @@ static void fcoe_ctlr_select(struct fcoe_ctlr *fip)
|
|
|
"or FC-MAP\n");
|
|
|
return NULL;
|
|
|
}
|
|
|
+ if (fcf->flogi_sent)
|
|
|
+ continue;
|
|
|
if (!fcoe_ctlr_fcf_usable(fcf)) {
|
|
|
LIBFCOE_FIP_DBG(fip, "FCF for fab %16.16llx "
|
|
|
"map %x %svalid %savailable\n",
|
|
@@ -1386,11 +1427,7 @@ static void fcoe_ctlr_select(struct fcoe_ctlr *fip)
|
|
|
"" : "un");
|
|
|
continue;
|
|
|
}
|
|
|
- if (!best) {
|
|
|
- best = fcf;
|
|
|
- continue;
|
|
|
- }
|
|
|
- if (fcf->pri < best->pri)
|
|
|
+ if (!best || fcf->pri < best->pri || best->flogi_sent)
|
|
|
best = fcf;
|
|
|
}
|
|
|
fip->sel_fcf = best;
|
|
@@ -1403,6 +1440,121 @@ static void fcoe_ctlr_select(struct fcoe_ctlr *fip)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_flogi_send_locked() - send FIP-encapsulated FLOGI to current FCF
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ *
|
|
|
+ * Returns non-zero error if it could not be sent.
|
|
|
+ *
|
|
|
+ * Called with ctlr_mutex and ctlr_lock held.
|
|
|
+ * Caller must verify that fip->sel_fcf is not NULL.
|
|
|
+ */
|
|
|
+static int fcoe_ctlr_flogi_send_locked(struct fcoe_ctlr *fip)
|
|
|
+{
|
|
|
+ struct sk_buff *skb;
|
|
|
+ struct sk_buff *skb_orig;
|
|
|
+ struct fc_frame_header *fh;
|
|
|
+ int error;
|
|
|
+
|
|
|
+ skb_orig = fip->flogi_req;
|
|
|
+ if (!skb_orig)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Clone and send the FLOGI request. If clone fails, use original.
|
|
|
+ */
|
|
|
+ skb = skb_clone(skb_orig, GFP_ATOMIC);
|
|
|
+ if (!skb) {
|
|
|
+ skb = skb_orig;
|
|
|
+ fip->flogi_req = NULL;
|
|
|
+ }
|
|
|
+ fh = (struct fc_frame_header *)skb->data;
|
|
|
+ error = fcoe_ctlr_encaps(fip, fip->lp, FIP_DT_FLOGI, skb,
|
|
|
+ ntoh24(fh->fh_d_id));
|
|
|
+ if (error) {
|
|
|
+ kfree_skb(skb);
|
|
|
+ return error;
|
|
|
+ }
|
|
|
+ fip->send(fip, skb);
|
|
|
+ fip->sel_fcf->flogi_sent = 1;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_flogi_retry() - resend FLOGI request to a new FCF if possible
|
|
|
+ * @fip: The FCoE controller
|
|
|
+ *
|
|
|
+ * Returns non-zero error code if there's no FLOGI request to retry or
|
|
|
+ * no alternate FCF available.
|
|
|
+ */
|
|
|
+static int fcoe_ctlr_flogi_retry(struct fcoe_ctlr *fip)
|
|
|
+{
|
|
|
+ struct fcoe_fcf *fcf;
|
|
|
+ int error;
|
|
|
+
|
|
|
+ mutex_lock(&fip->ctlr_mutex);
|
|
|
+ spin_lock_bh(&fip->ctlr_lock);
|
|
|
+ LIBFCOE_FIP_DBG(fip, "re-sending FLOGI - reselect\n");
|
|
|
+ fcoe_ctlr_select(fip);
|
|
|
+ fcf = fip->sel_fcf;
|
|
|
+ if (!fcf || fcf->flogi_sent) {
|
|
|
+ kfree_skb(fip->flogi_req);
|
|
|
+ fip->flogi_req = NULL;
|
|
|
+ error = -ENOENT;
|
|
|
+ } else {
|
|
|
+ fcoe_ctlr_solicit(fip, NULL);
|
|
|
+ error = fcoe_ctlr_flogi_send_locked(fip);
|
|
|
+ }
|
|
|
+ spin_unlock_bh(&fip->ctlr_lock);
|
|
|
+ mutex_unlock(&fip->ctlr_mutex);
|
|
|
+ return error;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * fcoe_ctlr_flogi_send() - Handle sending of FIP FLOGI.
|
|
|
+ * @fip: The FCoE controller that timed out
|
|
|
+ *
|
|
|
+ * Done here because fcoe_ctlr_els_send() can't get mutex.
|
|
|
+ *
|
|
|
+ * Called with ctlr_mutex held. The caller must not hold ctlr_lock.
|
|
|
+ */
|
|
|
+static void fcoe_ctlr_flogi_send(struct fcoe_ctlr *fip)
|
|
|
+{
|
|
|
+ struct fcoe_fcf *fcf;
|
|
|
+
|
|
|
+ spin_lock_bh(&fip->ctlr_lock);
|
|
|
+ fcf = fip->sel_fcf;
|
|
|
+ if (!fcf || !fip->flogi_req_send)
|
|
|
+ goto unlock;
|
|
|
+
|
|
|
+ LIBFCOE_FIP_DBG(fip, "sending FLOGI\n");
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If this FLOGI is being sent due to a timeout retry
|
|
|
+ * to the same FCF as before, select a different FCF if possible.
|
|
|
+ */
|
|
|
+ if (fcf->flogi_sent) {
|
|
|
+ LIBFCOE_FIP_DBG(fip, "sending FLOGI - reselect\n");
|
|
|
+ fcoe_ctlr_select(fip);
|
|
|
+ fcf = fip->sel_fcf;
|
|
|
+ if (!fcf || fcf->flogi_sent) {
|
|
|
+ LIBFCOE_FIP_DBG(fip, "sending FLOGI - clearing\n");
|
|
|
+ list_for_each_entry(fcf, &fip->fcfs, list)
|
|
|
+ fcf->flogi_sent = 0;
|
|
|
+ fcoe_ctlr_select(fip);
|
|
|
+ fcf = fip->sel_fcf;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (fcf) {
|
|
|
+ fcoe_ctlr_flogi_send_locked(fip);
|
|
|
+ fip->flogi_req_send = 0;
|
|
|
+ } else /* XXX */
|
|
|
+ LIBFCOE_FIP_DBG(fip, "No FCF selected - defer send\n");
|
|
|
+unlock:
|
|
|
+ spin_unlock_bh(&fip->ctlr_lock);
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* fcoe_ctlr_timeout() - FIP timeout handler
|
|
|
* @arg: The FCoE controller that timed out
|
|
@@ -1455,15 +1607,10 @@ static void fcoe_ctlr_timer_work(struct work_struct *work)
|
|
|
next_timer = fip->sel_time;
|
|
|
}
|
|
|
|
|
|
- if (sel != fcf) {
|
|
|
- fcf = sel; /* the old FCF may have been freed */
|
|
|
- fcoe_ctlr_announce(fip);
|
|
|
- if (sel) {
|
|
|
- if (time_after(next_timer, fip->ctlr_ka_time))
|
|
|
- next_timer = fip->ctlr_ka_time;
|
|
|
- } else
|
|
|
- reset = 1;
|
|
|
- }
|
|
|
+ if (sel && fip->flogi_req_send)
|
|
|
+ fcoe_ctlr_flogi_send(fip);
|
|
|
+ else if (!sel && fcf)
|
|
|
+ reset = 1;
|
|
|
|
|
|
if (sel && !sel->fd_flags) {
|
|
|
if (time_after_eq(jiffies, fip->ctlr_ka_time)) {
|