xref: /openbmc/linux/drivers/scsi/libfc/fc_disc.c (revision 5a0e3ad6)
142e9a92fSRobert Love /*
242e9a92fSRobert Love  * Copyright(c) 2007 - 2008 Intel Corporation. All rights reserved.
342e9a92fSRobert Love  *
442e9a92fSRobert Love  * This program is free software; you can redistribute it and/or modify it
542e9a92fSRobert Love  * under the terms and conditions of the GNU General Public License,
642e9a92fSRobert Love  * version 2, as published by the Free Software Foundation.
742e9a92fSRobert Love  *
842e9a92fSRobert Love  * This program is distributed in the hope it will be useful, but WITHOUT
942e9a92fSRobert Love  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1042e9a92fSRobert Love  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
1142e9a92fSRobert Love  * more details.
1242e9a92fSRobert Love  *
1342e9a92fSRobert Love  * You should have received a copy of the GNU General Public License along with
1442e9a92fSRobert Love  * this program; if not, write to the Free Software Foundation, Inc.,
1542e9a92fSRobert Love  * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
1642e9a92fSRobert Love  *
1742e9a92fSRobert Love  * Maintained at www.Open-FCoE.org
1842e9a92fSRobert Love  */
1942e9a92fSRobert Love 
2042e9a92fSRobert Love /*
2142e9a92fSRobert Love  * Target Discovery
2242e9a92fSRobert Love  *
2342e9a92fSRobert Love  * This block discovers all FC-4 remote ports, including FCP initiators. It
2442e9a92fSRobert Love  * also handles RSCN events and re-discovery if necessary.
2542e9a92fSRobert Love  */
2642e9a92fSRobert Love 
2742e9a92fSRobert Love /*
2842e9a92fSRobert Love  * DISC LOCKING
2942e9a92fSRobert Love  *
3042e9a92fSRobert Love  * The disc mutex is can be locked when acquiring rport locks, but may not
3142e9a92fSRobert Love  * be held when acquiring the lport lock. Refer to fc_lport.c for more
3242e9a92fSRobert Love  * details.
3342e9a92fSRobert Love  */
3442e9a92fSRobert Love 
3542e9a92fSRobert Love #include <linux/timer.h>
365a0e3ad6STejun Heo #include <linux/slab.h>
3742e9a92fSRobert Love #include <linux/err.h>
3842e9a92fSRobert Love #include <asm/unaligned.h>
3942e9a92fSRobert Love 
4042e9a92fSRobert Love #include <scsi/fc/fc_gs.h>
4142e9a92fSRobert Love 
4242e9a92fSRobert Love #include <scsi/libfc.h>
4342e9a92fSRobert Love 
448866a5d9SRobert Love #include "fc_libfc.h"
458866a5d9SRobert Love 
4642e9a92fSRobert Love #define FC_DISC_RETRY_LIMIT	3	/* max retries */
4742e9a92fSRobert Love #define FC_DISC_RETRY_DELAY	500UL	/* (msecs) delay */
4842e9a92fSRobert Love 
4942e9a92fSRobert Love static void fc_disc_gpn_ft_req(struct fc_disc *);
5042e9a92fSRobert Love static void fc_disc_gpn_ft_resp(struct fc_seq *, struct fc_frame *, void *);
51786681b9SJoe Eykholt static void fc_disc_done(struct fc_disc *, enum fc_disc_event);
5242e9a92fSRobert Love static void fc_disc_timeout(struct work_struct *);
532ab7e1ecSJoe Eykholt static int fc_disc_single(struct fc_lport *, struct fc_disc_port *);
5442e9a92fSRobert Love static void fc_disc_restart(struct fc_disc *);
5542e9a92fSRobert Love 
5642e9a92fSRobert Love /**
573a3b42bfSRobert Love  * fc_disc_stop_rports() - Delete all the remote ports associated with the lport
583a3b42bfSRobert Love  * @disc: The discovery job to stop remote ports on
5942e9a92fSRobert Love  *
6042e9a92fSRobert Love  * Locking Note: This function expects that the lport mutex is locked before
6142e9a92fSRobert Love  * calling it.
6242e9a92fSRobert Love  */
6342e9a92fSRobert Love void fc_disc_stop_rports(struct fc_disc *disc)
6442e9a92fSRobert Love {
6542e9a92fSRobert Love 	struct fc_lport *lport;
66ab28f1fdSJoe Eykholt 	struct fc_rport_priv *rdata, *next;
6742e9a92fSRobert Love 
6842e9a92fSRobert Love 	lport = disc->lport;
6942e9a92fSRobert Love 
7042e9a92fSRobert Love 	mutex_lock(&disc->disc_mutex);
719e9d0452SJoe Eykholt 	list_for_each_entry_safe(rdata, next, &disc->rports, peers)
729fb9d328SJoe Eykholt 		lport->tt.rport_logoff(rdata);
7342e9a92fSRobert Love 	mutex_unlock(&disc->disc_mutex);
7442e9a92fSRobert Love }
7542e9a92fSRobert Love 
7642e9a92fSRobert Love /**
7734f42a07SRobert Love  * fc_disc_recv_rscn_req() - Handle Registered State Change Notification (RSCN)
783a3b42bfSRobert Love  * @sp:	   The sequence of the RSCN exchange
793a3b42bfSRobert Love  * @fp:	   The RSCN frame
803a3b42bfSRobert Love  * @lport: The local port that the request will be sent on
8142e9a92fSRobert Love  *
8242e9a92fSRobert Love  * Locking Note: This function expects that the disc_mutex is locked
8342e9a92fSRobert Love  *		 before it is called.
8442e9a92fSRobert Love  */
8542e9a92fSRobert Love static void fc_disc_recv_rscn_req(struct fc_seq *sp, struct fc_frame *fp,
8642e9a92fSRobert Love 				  struct fc_disc *disc)
8742e9a92fSRobert Love {
8842e9a92fSRobert Love 	struct fc_lport *lport;
8942e9a92fSRobert Love 	struct fc_els_rscn *rp;
9042e9a92fSRobert Love 	struct fc_els_rscn_page *pp;
9142e9a92fSRobert Love 	struct fc_seq_els_data rjt_data;
9242e9a92fSRobert Love 	unsigned int len;
9342e9a92fSRobert Love 	int redisc = 0;
9442e9a92fSRobert Love 	enum fc_els_rscn_ev_qual ev_qual;
9542e9a92fSRobert Love 	enum fc_els_rscn_addr_fmt fmt;
9642e9a92fSRobert Love 	LIST_HEAD(disc_ports);
9742e9a92fSRobert Love 	struct fc_disc_port *dp, *next;
9842e9a92fSRobert Love 
9942e9a92fSRobert Love 	lport = disc->lport;
10042e9a92fSRobert Love 
1017414705eSRobert Love 	FC_DISC_DBG(disc, "Received an RSCN event\n");
10242e9a92fSRobert Love 
10342e9a92fSRobert Love 	/* make sure the frame contains an RSCN message */
10442e9a92fSRobert Love 	rp = fc_frame_payload_get(fp, sizeof(*rp));
10542e9a92fSRobert Love 	if (!rp)
10642e9a92fSRobert Love 		goto reject;
10742e9a92fSRobert Love 	/* make sure the page length is as expected (4 bytes) */
10842e9a92fSRobert Love 	if (rp->rscn_page_len != sizeof(*pp))
10942e9a92fSRobert Love 		goto reject;
11042e9a92fSRobert Love 	/* get the RSCN payload length */
11142e9a92fSRobert Love 	len = ntohs(rp->rscn_plen);
11242e9a92fSRobert Love 	if (len < sizeof(*rp))
11342e9a92fSRobert Love 		goto reject;
11442e9a92fSRobert Love 	/* make sure the frame contains the expected payload */
11542e9a92fSRobert Love 	rp = fc_frame_payload_get(fp, len);
11642e9a92fSRobert Love 	if (!rp)
11742e9a92fSRobert Love 		goto reject;
11842e9a92fSRobert Love 	/* payload must be a multiple of the RSCN page size */
11942e9a92fSRobert Love 	len -= sizeof(*rp);
12042e9a92fSRobert Love 	if (len % sizeof(*pp))
12142e9a92fSRobert Love 		goto reject;
12242e9a92fSRobert Love 
12342e9a92fSRobert Love 	for (pp = (void *)(rp + 1); len > 0; len -= sizeof(*pp), pp++) {
12442e9a92fSRobert Love 		ev_qual = pp->rscn_page_flags >> ELS_RSCN_EV_QUAL_BIT;
12542e9a92fSRobert Love 		ev_qual &= ELS_RSCN_EV_QUAL_MASK;
12642e9a92fSRobert Love 		fmt = pp->rscn_page_flags >> ELS_RSCN_ADDR_FMT_BIT;
12742e9a92fSRobert Love 		fmt &= ELS_RSCN_ADDR_FMT_MASK;
12842e9a92fSRobert Love 		/*
12942e9a92fSRobert Love 		 * if we get an address format other than port
13042e9a92fSRobert Love 		 * (area, domain, fabric), then do a full discovery
13142e9a92fSRobert Love 		 */
13242e9a92fSRobert Love 		switch (fmt) {
13342e9a92fSRobert Love 		case ELS_ADDR_FMT_PORT:
1347414705eSRobert Love 			FC_DISC_DBG(disc, "Port address format for port "
1357414705eSRobert Love 				    "(%6x)\n", ntoh24(pp->rscn_fid));
13642e9a92fSRobert Love 			dp = kzalloc(sizeof(*dp), GFP_KERNEL);
13742e9a92fSRobert Love 			if (!dp) {
13842e9a92fSRobert Love 				redisc = 1;
13942e9a92fSRobert Love 				break;
14042e9a92fSRobert Love 			}
14142e9a92fSRobert Love 			dp->lp = lport;
1429737e6a7SRobert Love 			dp->port_id = ntoh24(pp->rscn_fid);
14342e9a92fSRobert Love 			list_add_tail(&dp->peers, &disc_ports);
14442e9a92fSRobert Love 			break;
14542e9a92fSRobert Love 		case ELS_ADDR_FMT_AREA:
14642e9a92fSRobert Love 		case ELS_ADDR_FMT_DOM:
14742e9a92fSRobert Love 		case ELS_ADDR_FMT_FAB:
14842e9a92fSRobert Love 		default:
1497414705eSRobert Love 			FC_DISC_DBG(disc, "Address format is (%d)\n", fmt);
15042e9a92fSRobert Love 			redisc = 1;
15142e9a92fSRobert Love 			break;
15242e9a92fSRobert Love 		}
15342e9a92fSRobert Love 	}
15442e9a92fSRobert Love 	lport->tt.seq_els_rsp_send(sp, ELS_LS_ACC, NULL);
1552ab7e1ecSJoe Eykholt 
1562ab7e1ecSJoe Eykholt 	/*
1572ab7e1ecSJoe Eykholt 	 * If not doing a complete rediscovery, do GPN_ID on
1582ab7e1ecSJoe Eykholt 	 * the individual ports mentioned in the list.
1592ab7e1ecSJoe Eykholt 	 * If any of these get an error, do a full rediscovery.
1602ab7e1ecSJoe Eykholt 	 * In any case, go through the list and free the entries.
1612ab7e1ecSJoe Eykholt 	 */
1622ab7e1ecSJoe Eykholt 	list_for_each_entry_safe(dp, next, &disc_ports, peers) {
1632ab7e1ecSJoe Eykholt 		list_del(&dp->peers);
1642ab7e1ecSJoe Eykholt 		if (!redisc)
1652ab7e1ecSJoe Eykholt 			redisc = fc_disc_single(lport, dp);
1662ab7e1ecSJoe Eykholt 		kfree(dp);
1672ab7e1ecSJoe Eykholt 	}
16842e9a92fSRobert Love 	if (redisc) {
1697414705eSRobert Love 		FC_DISC_DBG(disc, "RSCN received: rediscovering\n");
17042e9a92fSRobert Love 		fc_disc_restart(disc);
17142e9a92fSRobert Love 	} else {
1727414705eSRobert Love 		FC_DISC_DBG(disc, "RSCN received: not rediscovering. "
17342e9a92fSRobert Love 			    "redisc %d state %d in_prog %d\n",
17442e9a92fSRobert Love 			    redisc, lport->state, disc->pending);
17542e9a92fSRobert Love 	}
17642e9a92fSRobert Love 	fc_frame_free(fp);
17742e9a92fSRobert Love 	return;
17842e9a92fSRobert Love reject:
1797414705eSRobert Love 	FC_DISC_DBG(disc, "Received a bad RSCN frame\n");
18042e9a92fSRobert Love 	rjt_data.fp = NULL;
18142e9a92fSRobert Love 	rjt_data.reason = ELS_RJT_LOGIC;
18242e9a92fSRobert Love 	rjt_data.explan = ELS_EXPL_NONE;
18342e9a92fSRobert Love 	lport->tt.seq_els_rsp_send(sp, ELS_LS_RJT, &rjt_data);
18442e9a92fSRobert Love 	fc_frame_free(fp);
18542e9a92fSRobert Love }
18642e9a92fSRobert Love 
18742e9a92fSRobert Love /**
18834f42a07SRobert Love  * fc_disc_recv_req() - Handle incoming requests
1893a3b42bfSRobert Love  * @sp:	   The sequence of the request exchange
1903a3b42bfSRobert Love  * @fp:	   The request frame
1913a3b42bfSRobert Love  * @lport: The local port receiving the request
19242e9a92fSRobert Love  *
19342e9a92fSRobert Love  * Locking Note: This function is called from the EM and will lock
19442e9a92fSRobert Love  *		 the disc_mutex before calling the handler for the
19542e9a92fSRobert Love  *		 request.
19642e9a92fSRobert Love  */
19742e9a92fSRobert Love static void fc_disc_recv_req(struct fc_seq *sp, struct fc_frame *fp,
19842e9a92fSRobert Love 			     struct fc_lport *lport)
19942e9a92fSRobert Love {
20042e9a92fSRobert Love 	u8 op;
20142e9a92fSRobert Love 	struct fc_disc *disc = &lport->disc;
20242e9a92fSRobert Love 
20342e9a92fSRobert Love 	op = fc_frame_payload_op(fp);
20442e9a92fSRobert Love 	switch (op) {
20542e9a92fSRobert Love 	case ELS_RSCN:
20642e9a92fSRobert Love 		mutex_lock(&disc->disc_mutex);
20742e9a92fSRobert Love 		fc_disc_recv_rscn_req(sp, fp, disc);
20842e9a92fSRobert Love 		mutex_unlock(&disc->disc_mutex);
20942e9a92fSRobert Love 		break;
21042e9a92fSRobert Love 	default:
2117414705eSRobert Love 		FC_DISC_DBG(disc, "Received an unsupported request, "
2127414705eSRobert Love 			    "the opcode is (%x)\n", op);
21342e9a92fSRobert Love 		break;
21442e9a92fSRobert Love 	}
21542e9a92fSRobert Love }
21642e9a92fSRobert Love 
21742e9a92fSRobert Love /**
21834f42a07SRobert Love  * fc_disc_restart() - Restart discovery
2193a3b42bfSRobert Love  * @disc: The discovery object to be restarted
22042e9a92fSRobert Love  *
22142e9a92fSRobert Love  * Locking Note: This function expects that the disc mutex
22242e9a92fSRobert Love  *		 is already locked.
22342e9a92fSRobert Love  */
22442e9a92fSRobert Love static void fc_disc_restart(struct fc_disc *disc)
22542e9a92fSRobert Love {
226935d0fceSJoe Eykholt 	if (!disc->disc_callback)
227935d0fceSJoe Eykholt 		return;
228935d0fceSJoe Eykholt 
2297414705eSRobert Love 	FC_DISC_DBG(disc, "Restarting discovery\n");
23042e9a92fSRobert Love 
23142e9a92fSRobert Love 	disc->requested = 1;
2320f6c6149SJoe Eykholt 	if (disc->pending)
2330f6c6149SJoe Eykholt 		return;
2340f6c6149SJoe Eykholt 
2350f6c6149SJoe Eykholt 	/*
2360f6c6149SJoe Eykholt 	 * Advance disc_id.  This is an arbitrary non-zero number that will
2370f6c6149SJoe Eykholt 	 * match the value in the fc_rport_priv after discovery for all
2380f6c6149SJoe Eykholt 	 * freshly-discovered remote ports.  Avoid wrapping to zero.
2390f6c6149SJoe Eykholt 	 */
2400f6c6149SJoe Eykholt 	disc->disc_id = (disc->disc_id + 2) | 1;
2413667d7e7SJoe Eykholt 	disc->retry_count = 0;
24242e9a92fSRobert Love 	fc_disc_gpn_ft_req(disc);
24342e9a92fSRobert Love }
24442e9a92fSRobert Love 
24542e9a92fSRobert Love /**
2463a3b42bfSRobert Love  * fc_disc_start() - Start discovery on a local port
2473a3b42bfSRobert Love  * @lport:	   The local port to have discovery started on
2483a3b42bfSRobert Love  * @disc_callback: Callback function to be called when discovery is complete
24942e9a92fSRobert Love  */
25042e9a92fSRobert Love static void fc_disc_start(void (*disc_callback)(struct fc_lport *,
25142e9a92fSRobert Love 						enum fc_disc_event),
25242e9a92fSRobert Love 			  struct fc_lport *lport)
25342e9a92fSRobert Love {
25442e9a92fSRobert Love 	struct fc_disc *disc = &lport->disc;
25542e9a92fSRobert Love 
25642e9a92fSRobert Love 	/*
25742e9a92fSRobert Love 	 * At this point we may have a new disc job or an existing
25842e9a92fSRobert Love 	 * one. Either way, let's lock when we make changes to it
25942e9a92fSRobert Love 	 * and send the GPN_FT request.
26042e9a92fSRobert Love 	 */
26142e9a92fSRobert Love 	mutex_lock(&disc->disc_mutex);
26242e9a92fSRobert Love 	disc->disc_callback = disc_callback;
26329d898e9SJoe Eykholt 	fc_disc_restart(disc);
26442e9a92fSRobert Love 	mutex_unlock(&disc->disc_mutex);
26542e9a92fSRobert Love }
26642e9a92fSRobert Love 
26742e9a92fSRobert Love /**
26834f42a07SRobert Love  * fc_disc_done() - Discovery has been completed
2693a3b42bfSRobert Love  * @disc:  The discovery context
2703a3b42bfSRobert Love  * @event: The discovery completion status
271786681b9SJoe Eykholt  *
2720d228c0fSAbhijeet Joglekar  * Locking Note: This function expects that the disc mutex is locked before
2730d228c0fSAbhijeet Joglekar  * it is called. The discovery callback is then made with the lock released,
2740d228c0fSAbhijeet Joglekar  * and the lock is re-taken before returning from this function
27542e9a92fSRobert Love  */
276786681b9SJoe Eykholt static void fc_disc_done(struct fc_disc *disc, enum fc_disc_event event)
27742e9a92fSRobert Love {
27842e9a92fSRobert Love 	struct fc_lport *lport = disc->lport;
2790f6c6149SJoe Eykholt 	struct fc_rport_priv *rdata;
28042e9a92fSRobert Love 
2817414705eSRobert Love 	FC_DISC_DBG(disc, "Discovery complete\n");
28242e9a92fSRobert Love 
28342e9a92fSRobert Love 	disc->pending = 0;
2840f6c6149SJoe Eykholt 	if (disc->requested) {
2850f6c6149SJoe Eykholt 		fc_disc_restart(disc);
2860f6c6149SJoe Eykholt 		return;
2870f6c6149SJoe Eykholt 	}
2880f6c6149SJoe Eykholt 
2890f6c6149SJoe Eykholt 	/*
2900f6c6149SJoe Eykholt 	 * Go through all remote ports.	 If they were found in the latest
2910f6c6149SJoe Eykholt 	 * discovery, reverify or log them in.	Otherwise, log them out.
2920f6c6149SJoe Eykholt 	 * Skip ports which were never discovered.  These are the dNS port
2930f6c6149SJoe Eykholt 	 * and ports which were created by PLOGI.
2940f6c6149SJoe Eykholt 	 */
2950f6c6149SJoe Eykholt 	list_for_each_entry(rdata, &disc->rports, peers) {
2960f6c6149SJoe Eykholt 		if (!rdata->disc_id)
2970f6c6149SJoe Eykholt 			continue;
2980f6c6149SJoe Eykholt 		if (rdata->disc_id == disc->disc_id)
2990f6c6149SJoe Eykholt 			lport->tt.rport_login(rdata);
3000f6c6149SJoe Eykholt 		else
3010f6c6149SJoe Eykholt 			lport->tt.rport_logoff(rdata);
3020f6c6149SJoe Eykholt 	}
3030d228c0fSAbhijeet Joglekar 
3040d228c0fSAbhijeet Joglekar 	mutex_unlock(&disc->disc_mutex);
3050d228c0fSAbhijeet Joglekar 	disc->disc_callback(lport, event);
3060d228c0fSAbhijeet Joglekar 	mutex_lock(&disc->disc_mutex);
30742e9a92fSRobert Love }
30842e9a92fSRobert Love 
30942e9a92fSRobert Love /**
31034f42a07SRobert Love  * fc_disc_error() - Handle error on dNS request
3113a3b42bfSRobert Love  * @disc: The discovery context
3123a3b42bfSRobert Love  * @fp:	  The error code encoded as a frame pointer
31342e9a92fSRobert Love  */
31442e9a92fSRobert Love static void fc_disc_error(struct fc_disc *disc, struct fc_frame *fp)
31542e9a92fSRobert Love {
31642e9a92fSRobert Love 	struct fc_lport *lport = disc->lport;
31742e9a92fSRobert Love 	unsigned long delay = 0;
3187414705eSRobert Love 
3197414705eSRobert Love 	FC_DISC_DBG(disc, "Error %ld, retries %d/%d\n",
32042e9a92fSRobert Love 		    PTR_ERR(fp), disc->retry_count,
32142e9a92fSRobert Love 		    FC_DISC_RETRY_LIMIT);
32242e9a92fSRobert Love 
32342e9a92fSRobert Love 	if (!fp || PTR_ERR(fp) == -FC_EX_TIMEOUT) {
32442e9a92fSRobert Love 		/*
32542e9a92fSRobert Love 		 * Memory allocation failure, or the exchange timed out,
32642e9a92fSRobert Love 		 * retry after delay.
32742e9a92fSRobert Love 		 */
32842e9a92fSRobert Love 		if (disc->retry_count < FC_DISC_RETRY_LIMIT) {
32942e9a92fSRobert Love 			/* go ahead and retry */
33042e9a92fSRobert Love 			if (!fp)
33142e9a92fSRobert Love 				delay = msecs_to_jiffies(FC_DISC_RETRY_DELAY);
33242e9a92fSRobert Love 			else {
33342e9a92fSRobert Love 				delay = msecs_to_jiffies(lport->e_d_tov);
33442e9a92fSRobert Love 
33542e9a92fSRobert Love 				/* timeout faster first time */
33642e9a92fSRobert Love 				if (!disc->retry_count)
33742e9a92fSRobert Love 					delay /= 4;
33842e9a92fSRobert Love 			}
33942e9a92fSRobert Love 			disc->retry_count++;
34042e9a92fSRobert Love 			schedule_delayed_work(&disc->disc_work, delay);
341786681b9SJoe Eykholt 		} else
342786681b9SJoe Eykholt 			fc_disc_done(disc, DISC_EV_FAILED);
34342e9a92fSRobert Love 	}
34442e9a92fSRobert Love }
34542e9a92fSRobert Love 
34642e9a92fSRobert Love /**
34734f42a07SRobert Love  * fc_disc_gpn_ft_req() - Send Get Port Names by FC-4 type (GPN_FT) request
3483a3b42bfSRobert Love  * @lport: The discovery context
34942e9a92fSRobert Love  *
35042e9a92fSRobert Love  * Locking Note: This function expects that the disc_mutex is locked
35142e9a92fSRobert Love  *		 before it is called.
35242e9a92fSRobert Love  */
35342e9a92fSRobert Love static void fc_disc_gpn_ft_req(struct fc_disc *disc)
35442e9a92fSRobert Love {
35542e9a92fSRobert Love 	struct fc_frame *fp;
35642e9a92fSRobert Love 	struct fc_lport *lport = disc->lport;
35742e9a92fSRobert Love 
35842e9a92fSRobert Love 	WARN_ON(!fc_lport_test_ready(lport));
35942e9a92fSRobert Love 
36042e9a92fSRobert Love 	disc->pending = 1;
36142e9a92fSRobert Love 	disc->requested = 0;
36242e9a92fSRobert Love 
36342e9a92fSRobert Love 	disc->buf_len = 0;
36442e9a92fSRobert Love 	disc->seq_count = 0;
36542e9a92fSRobert Love 	fp = fc_frame_alloc(lport,
36642e9a92fSRobert Love 			    sizeof(struct fc_ct_hdr) +
36742e9a92fSRobert Love 			    sizeof(struct fc_ns_gid_ft));
36842e9a92fSRobert Love 	if (!fp)
36942e9a92fSRobert Love 		goto err;
37042e9a92fSRobert Love 
371a46f327aSJoe Eykholt 	if (lport->tt.elsct_send(lport, 0, fp,
37242e9a92fSRobert Love 				 FC_NS_GPN_FT,
37342e9a92fSRobert Love 				 fc_disc_gpn_ft_resp,
374b94f8951SJoe Eykholt 				 disc, 3 * lport->r_a_tov))
37542e9a92fSRobert Love 		return;
37642e9a92fSRobert Love err:
3778f550f93SChris Leech 	fc_disc_error(disc, NULL);
37842e9a92fSRobert Love }
37942e9a92fSRobert Love 
38042e9a92fSRobert Love /**
381786681b9SJoe Eykholt  * fc_disc_gpn_ft_parse() - Parse the body of the dNS GPN_FT response.
3823a3b42bfSRobert Love  * @lport: The local port the GPN_FT was received on
3833a3b42bfSRobert Love  * @buf:   The GPN_FT response buffer
3843a3b42bfSRobert Love  * @len:   The size of response buffer
385786681b9SJoe Eykholt  *
386786681b9SJoe Eykholt  * Goes through the list of IDs and names resulting from a request.
38742e9a92fSRobert Love  */
38842e9a92fSRobert Love static int fc_disc_gpn_ft_parse(struct fc_disc *disc, void *buf, size_t len)
38942e9a92fSRobert Love {
39042e9a92fSRobert Love 	struct fc_lport *lport;
39142e9a92fSRobert Love 	struct fc_gpn_ft_resp *np;
39242e9a92fSRobert Love 	char *bp;
39342e9a92fSRobert Love 	size_t plen;
39442e9a92fSRobert Love 	size_t tlen;
39542e9a92fSRobert Love 	int error = 0;
396795d86f5SJoe Eykholt 	struct fc_rport_identifiers ids;
397ab28f1fdSJoe Eykholt 	struct fc_rport_priv *rdata;
39842e9a92fSRobert Love 
39942e9a92fSRobert Love 	lport = disc->lport;
400a1c1e4e7SJoe Eykholt 	disc->seq_count++;
40142e9a92fSRobert Love 
40242e9a92fSRobert Love 	/*
40342e9a92fSRobert Love 	 * Handle partial name record left over from previous call.
40442e9a92fSRobert Love 	 */
40542e9a92fSRobert Love 	bp = buf;
40642e9a92fSRobert Love 	plen = len;
40742e9a92fSRobert Love 	np = (struct fc_gpn_ft_resp *)bp;
40842e9a92fSRobert Love 	tlen = disc->buf_len;
40981a67b97SJoe Eykholt 	disc->buf_len = 0;
41042e9a92fSRobert Love 	if (tlen) {
41142e9a92fSRobert Love 		WARN_ON(tlen >= sizeof(*np));
41242e9a92fSRobert Love 		plen = sizeof(*np) - tlen;
41342e9a92fSRobert Love 		WARN_ON(plen <= 0);
41442e9a92fSRobert Love 		WARN_ON(plen >= sizeof(*np));
41542e9a92fSRobert Love 		if (plen > len)
41642e9a92fSRobert Love 			plen = len;
41742e9a92fSRobert Love 		np = &disc->partial_buf;
41842e9a92fSRobert Love 		memcpy((char *)np + tlen, bp, plen);
41942e9a92fSRobert Love 
42042e9a92fSRobert Love 		/*
42142e9a92fSRobert Love 		 * Set bp so that the loop below will advance it to the
42242e9a92fSRobert Love 		 * first valid full name element.
42342e9a92fSRobert Love 		 */
42442e9a92fSRobert Love 		bp -= tlen;
42542e9a92fSRobert Love 		len += tlen;
42642e9a92fSRobert Love 		plen += tlen;
42742e9a92fSRobert Love 		disc->buf_len = (unsigned char) plen;
42842e9a92fSRobert Love 		if (plen == sizeof(*np))
42942e9a92fSRobert Love 			disc->buf_len = 0;
43042e9a92fSRobert Love 	}
43142e9a92fSRobert Love 
43242e9a92fSRobert Love 	/*
43342e9a92fSRobert Love 	 * Handle full name records, including the one filled from above.
43442e9a92fSRobert Love 	 * Normally, np == bp and plen == len, but from the partial case above,
43542e9a92fSRobert Love 	 * bp, len describe the overall buffer, and np, plen describe the
43642e9a92fSRobert Love 	 * partial buffer, which if would usually be full now.
43742e9a92fSRobert Love 	 * After the first time through the loop, things return to "normal".
43842e9a92fSRobert Love 	 */
43942e9a92fSRobert Love 	while (plen >= sizeof(*np)) {
440795d86f5SJoe Eykholt 		ids.port_id = ntoh24(np->fp_fid);
441795d86f5SJoe Eykholt 		ids.port_name = ntohll(np->fp_wwpn);
44242e9a92fSRobert Love 
443795d86f5SJoe Eykholt 		if (ids.port_id != fc_host_port_id(lport->host) &&
444795d86f5SJoe Eykholt 		    ids.port_name != lport->wwpn) {
4459737e6a7SRobert Love 			rdata = lport->tt.rport_create(lport, ids.port_id);
4469737e6a7SRobert Love 			if (rdata) {
4479737e6a7SRobert Love 				rdata->ids.port_name = ids.port_name;
4480f6c6149SJoe Eykholt 				rdata->disc_id = disc->disc_id;
4499737e6a7SRobert Love 			} else {
4507414705eSRobert Love 				printk(KERN_WARNING "libfc: Failed to allocate "
4517414705eSRobert Love 				       "memory for the newly discovered port "
452795d86f5SJoe Eykholt 				       "(%6x)\n", ids.port_id);
45381a67b97SJoe Eykholt 				error = -ENOMEM;
45481a67b97SJoe Eykholt 			}
45542e9a92fSRobert Love 		}
45642e9a92fSRobert Love 
45742e9a92fSRobert Love 		if (np->fp_flags & FC_NS_FID_LAST) {
458786681b9SJoe Eykholt 			fc_disc_done(disc, DISC_EV_SUCCESS);
45942e9a92fSRobert Love 			len = 0;
46042e9a92fSRobert Love 			break;
46142e9a92fSRobert Love 		}
46242e9a92fSRobert Love 		len -= sizeof(*np);
46342e9a92fSRobert Love 		bp += sizeof(*np);
46442e9a92fSRobert Love 		np = (struct fc_gpn_ft_resp *)bp;
46542e9a92fSRobert Love 		plen = len;
46642e9a92fSRobert Love 	}
46742e9a92fSRobert Love 
46842e9a92fSRobert Love 	/*
46942e9a92fSRobert Love 	 * Save any partial record at the end of the buffer for next time.
47042e9a92fSRobert Love 	 */
47142e9a92fSRobert Love 	if (error == 0 && len > 0 && len < sizeof(*np)) {
47242e9a92fSRobert Love 		if (np != &disc->partial_buf) {
4737414705eSRobert Love 			FC_DISC_DBG(disc, "Partial buffer remains "
4747414705eSRobert Love 				    "for discovery\n");
47542e9a92fSRobert Love 			memcpy(&disc->partial_buf, np, len);
47642e9a92fSRobert Love 		}
47742e9a92fSRobert Love 		disc->buf_len = (unsigned char) len;
47842e9a92fSRobert Love 	}
47942e9a92fSRobert Love 	return error;
48042e9a92fSRobert Love }
48142e9a92fSRobert Love 
48234f42a07SRobert Love /**
4833a3b42bfSRobert Love  * fc_disc_timeout() - Handler for discovery timeouts
4843a3b42bfSRobert Love  * @work: Structure holding discovery context that needs to retry discovery
48542e9a92fSRobert Love  */
48642e9a92fSRobert Love static void fc_disc_timeout(struct work_struct *work)
48742e9a92fSRobert Love {
48842e9a92fSRobert Love 	struct fc_disc *disc = container_of(work,
48942e9a92fSRobert Love 					    struct fc_disc,
49042e9a92fSRobert Love 					    disc_work.work);
49142e9a92fSRobert Love 	mutex_lock(&disc->disc_mutex);
49242e9a92fSRobert Love 	fc_disc_gpn_ft_req(disc);
49342e9a92fSRobert Love 	mutex_unlock(&disc->disc_mutex);
49442e9a92fSRobert Love }
49542e9a92fSRobert Love 
49642e9a92fSRobert Love /**
49734f42a07SRobert Love  * fc_disc_gpn_ft_resp() - Handle a response frame from Get Port Names (GPN_FT)
4983a3b42bfSRobert Love  * @sp:	    The sequence that the GPN_FT response was received on
4993a3b42bfSRobert Love  * @fp:	    The GPN_FT response frame
5003a3b42bfSRobert Love  * @lp_arg: The discovery context
50142e9a92fSRobert Love  *
5020d228c0fSAbhijeet Joglekar  * Locking Note: This function is called without disc mutex held, and
5030d228c0fSAbhijeet Joglekar  *		 should do all its processing with the mutex held
50442e9a92fSRobert Love  */
50542e9a92fSRobert Love static void fc_disc_gpn_ft_resp(struct fc_seq *sp, struct fc_frame *fp,
50642e9a92fSRobert Love 				void *disc_arg)
50742e9a92fSRobert Love {
50842e9a92fSRobert Love 	struct fc_disc *disc = disc_arg;
50942e9a92fSRobert Love 	struct fc_ct_hdr *cp;
51042e9a92fSRobert Love 	struct fc_frame_header *fh;
511a1c1e4e7SJoe Eykholt 	enum fc_disc_event event = DISC_EV_NONE;
51242e9a92fSRobert Love 	unsigned int seq_cnt;
51342e9a92fSRobert Love 	unsigned int len;
514a1c1e4e7SJoe Eykholt 	int error = 0;
51542e9a92fSRobert Love 
5160d228c0fSAbhijeet Joglekar 	mutex_lock(&disc->disc_mutex);
5177414705eSRobert Love 	FC_DISC_DBG(disc, "Received a GPN_FT response\n");
51842e9a92fSRobert Love 
51942e9a92fSRobert Love 	if (IS_ERR(fp)) {
52042e9a92fSRobert Love 		fc_disc_error(disc, fp);
5210d228c0fSAbhijeet Joglekar 		mutex_unlock(&disc->disc_mutex);
52242e9a92fSRobert Love 		return;
52342e9a92fSRobert Love 	}
52442e9a92fSRobert Love 
52542e9a92fSRobert Love 	WARN_ON(!fc_frame_is_linear(fp));	/* buffer must be contiguous */
52642e9a92fSRobert Love 	fh = fc_frame_header_get(fp);
52742e9a92fSRobert Love 	len = fr_len(fp) - sizeof(*fh);
52842e9a92fSRobert Love 	seq_cnt = ntohs(fh->fh_seq_cnt);
529a1c1e4e7SJoe Eykholt 	if (fr_sof(fp) == FC_SOF_I3 && seq_cnt == 0 && disc->seq_count == 0) {
53042e9a92fSRobert Love 		cp = fc_frame_payload_get(fp, sizeof(*cp));
53142e9a92fSRobert Love 		if (!cp) {
5327414705eSRobert Love 			FC_DISC_DBG(disc, "GPN_FT response too short, len %d\n",
53342e9a92fSRobert Love 				    fr_len(fp));
534883a337cSJoe Eykholt 			event = DISC_EV_FAILED;
53542e9a92fSRobert Love 		} else if (ntohs(cp->ct_cmd) == FC_FS_ACC) {
53642e9a92fSRobert Love 
53734f42a07SRobert Love 			/* Accepted, parse the response. */
53842e9a92fSRobert Love 			len -= sizeof(*cp);
539a1c1e4e7SJoe Eykholt 			error = fc_disc_gpn_ft_parse(disc, cp + 1, len);
54042e9a92fSRobert Love 		} else if (ntohs(cp->ct_cmd) == FC_FS_RJT) {
5417414705eSRobert Love 			FC_DISC_DBG(disc, "GPN_FT rejected reason %x exp %x "
54242e9a92fSRobert Love 				    "(check zoning)\n", cp->ct_reason,
54342e9a92fSRobert Love 				    cp->ct_explan);
544a1c1e4e7SJoe Eykholt 			event = DISC_EV_FAILED;
545c762608bSJoe Eykholt 			if (cp->ct_reason == FC_FS_RJT_UNABL &&
546c762608bSJoe Eykholt 			    cp->ct_explan == FC_FS_EXP_FTNR)
547c762608bSJoe Eykholt 				event = DISC_EV_SUCCESS;
54842e9a92fSRobert Love 		} else {
5497414705eSRobert Love 			FC_DISC_DBG(disc, "GPN_FT unexpected response code "
5507414705eSRobert Love 				    "%x\n", ntohs(cp->ct_cmd));
551883a337cSJoe Eykholt 			event = DISC_EV_FAILED;
55242e9a92fSRobert Love 		}
553a1c1e4e7SJoe Eykholt 	} else if (fr_sof(fp) == FC_SOF_N3 && seq_cnt == disc->seq_count) {
554a1c1e4e7SJoe Eykholt 		error = fc_disc_gpn_ft_parse(disc, fh + 1, len);
55542e9a92fSRobert Love 	} else {
5567414705eSRobert Love 		FC_DISC_DBG(disc, "GPN_FT unexpected frame - out of sequence? "
55742e9a92fSRobert Love 			    "seq_cnt %x expected %x sof %x eof %x\n",
55842e9a92fSRobert Love 			    seq_cnt, disc->seq_count, fr_sof(fp), fr_eof(fp));
559883a337cSJoe Eykholt 		event = DISC_EV_FAILED;
56042e9a92fSRobert Love 	}
56142e9a92fSRobert Love 	if (error)
56242e9a92fSRobert Love 		fc_disc_error(disc, fp);
563a1c1e4e7SJoe Eykholt 	else if (event != DISC_EV_NONE)
564a1c1e4e7SJoe Eykholt 		fc_disc_done(disc, event);
56542e9a92fSRobert Love 	fc_frame_free(fp);
5660d228c0fSAbhijeet Joglekar 	mutex_unlock(&disc->disc_mutex);
56742e9a92fSRobert Love }
56842e9a92fSRobert Love 
56942e9a92fSRobert Love /**
5702ab7e1ecSJoe Eykholt  * fc_disc_gpn_id_resp() - Handle a response frame from Get Port Names (GPN_ID)
5713a3b42bfSRobert Love  * @sp:	       The sequence the GPN_ID is on
5723a3b42bfSRobert Love  * @fp:	       The response frame
5733a3b42bfSRobert Love  * @rdata_arg: The remote port that sent the GPN_ID response
5742ab7e1ecSJoe Eykholt  *
5752ab7e1ecSJoe Eykholt  * Locking Note: This function is called without disc mutex held.
5762ab7e1ecSJoe Eykholt  */
5772ab7e1ecSJoe Eykholt static void fc_disc_gpn_id_resp(struct fc_seq *sp, struct fc_frame *fp,
5782ab7e1ecSJoe Eykholt 				void *rdata_arg)
5792ab7e1ecSJoe Eykholt {
5802ab7e1ecSJoe Eykholt 	struct fc_rport_priv *rdata = rdata_arg;
5812ab7e1ecSJoe Eykholt 	struct fc_rport_priv *new_rdata;
5822ab7e1ecSJoe Eykholt 	struct fc_lport *lport;
5832ab7e1ecSJoe Eykholt 	struct fc_disc *disc;
5842ab7e1ecSJoe Eykholt 	struct fc_ct_hdr *cp;
5852ab7e1ecSJoe Eykholt 	struct fc_ns_gid_pn *pn;
5862ab7e1ecSJoe Eykholt 	u64 port_name;
5872ab7e1ecSJoe Eykholt 
5882ab7e1ecSJoe Eykholt 	lport = rdata->local_port;
5892ab7e1ecSJoe Eykholt 	disc = &lport->disc;
5902ab7e1ecSJoe Eykholt 
5912ab7e1ecSJoe Eykholt 	mutex_lock(&disc->disc_mutex);
5922ab7e1ecSJoe Eykholt 	if (PTR_ERR(fp) == -FC_EX_CLOSED)
5932ab7e1ecSJoe Eykholt 		goto out;
5942ab7e1ecSJoe Eykholt 	if (IS_ERR(fp))
5952ab7e1ecSJoe Eykholt 		goto redisc;
5962ab7e1ecSJoe Eykholt 
5972ab7e1ecSJoe Eykholt 	cp = fc_frame_payload_get(fp, sizeof(*cp));
5982ab7e1ecSJoe Eykholt 	if (!cp)
5992ab7e1ecSJoe Eykholt 		goto redisc;
6002ab7e1ecSJoe Eykholt 	if (ntohs(cp->ct_cmd) == FC_FS_ACC) {
6012ab7e1ecSJoe Eykholt 		if (fr_len(fp) < sizeof(struct fc_frame_header) +
6022ab7e1ecSJoe Eykholt 		    sizeof(*cp) + sizeof(*pn))
6032ab7e1ecSJoe Eykholt 			goto redisc;
6042ab7e1ecSJoe Eykholt 		pn = (struct fc_ns_gid_pn *)(cp + 1);
6052ab7e1ecSJoe Eykholt 		port_name = get_unaligned_be64(&pn->fn_wwpn);
6062ab7e1ecSJoe Eykholt 		if (rdata->ids.port_name == -1)
6072ab7e1ecSJoe Eykholt 			rdata->ids.port_name = port_name;
6082ab7e1ecSJoe Eykholt 		else if (rdata->ids.port_name != port_name) {
6092ab7e1ecSJoe Eykholt 			FC_DISC_DBG(disc, "GPN_ID accepted.  WWPN changed. "
6102ab7e1ecSJoe Eykholt 				    "Port-id %x wwpn %llx\n",
6112ab7e1ecSJoe Eykholt 				    rdata->ids.port_id, port_name);
6122ab7e1ecSJoe Eykholt 			lport->tt.rport_logoff(rdata);
6132ab7e1ecSJoe Eykholt 
6142ab7e1ecSJoe Eykholt 			new_rdata = lport->tt.rport_create(lport,
6152ab7e1ecSJoe Eykholt 							   rdata->ids.port_id);
6162ab7e1ecSJoe Eykholt 			if (new_rdata) {
6172ab7e1ecSJoe Eykholt 				new_rdata->disc_id = disc->disc_id;
6182ab7e1ecSJoe Eykholt 				lport->tt.rport_login(new_rdata);
6192ab7e1ecSJoe Eykholt 			}
6202ab7e1ecSJoe Eykholt 			goto out;
6212ab7e1ecSJoe Eykholt 		}
6222ab7e1ecSJoe Eykholt 		rdata->disc_id = disc->disc_id;
6232ab7e1ecSJoe Eykholt 		lport->tt.rport_login(rdata);
6242ab7e1ecSJoe Eykholt 	} else if (ntohs(cp->ct_cmd) == FC_FS_RJT) {
6252ab7e1ecSJoe Eykholt 		FC_DISC_DBG(disc, "GPN_ID rejected reason %x exp %x\n",
6262ab7e1ecSJoe Eykholt 			    cp->ct_reason, cp->ct_explan);
6272ab7e1ecSJoe Eykholt 		lport->tt.rport_logoff(rdata);
6282ab7e1ecSJoe Eykholt 	} else {
6292ab7e1ecSJoe Eykholt 		FC_DISC_DBG(disc, "GPN_ID unexpected response code %x\n",
6302ab7e1ecSJoe Eykholt 			    ntohs(cp->ct_cmd));
6312ab7e1ecSJoe Eykholt redisc:
6322ab7e1ecSJoe Eykholt 		fc_disc_restart(disc);
6332ab7e1ecSJoe Eykholt 	}
6342ab7e1ecSJoe Eykholt out:
6352ab7e1ecSJoe Eykholt 	mutex_unlock(&disc->disc_mutex);
6362ab7e1ecSJoe Eykholt 	kref_put(&rdata->kref, lport->tt.rport_destroy);
6372ab7e1ecSJoe Eykholt }
6382ab7e1ecSJoe Eykholt 
6392ab7e1ecSJoe Eykholt /**
6402ab7e1ecSJoe Eykholt  * fc_disc_gpn_id_req() - Send Get Port Names by ID (GPN_ID) request
6413a3b42bfSRobert Love  * @lport: The local port to initiate discovery on
6422ab7e1ecSJoe Eykholt  * @rdata: remote port private data
6432ab7e1ecSJoe Eykholt  *
6442ab7e1ecSJoe Eykholt  * Locking Note: This function expects that the disc_mutex is locked
6452ab7e1ecSJoe Eykholt  *		 before it is called.
6462ab7e1ecSJoe Eykholt  * On failure, an error code is returned.
6472ab7e1ecSJoe Eykholt  */
6482ab7e1ecSJoe Eykholt static int fc_disc_gpn_id_req(struct fc_lport *lport,
6492ab7e1ecSJoe Eykholt 			      struct fc_rport_priv *rdata)
6502ab7e1ecSJoe Eykholt {
6512ab7e1ecSJoe Eykholt 	struct fc_frame *fp;
6522ab7e1ecSJoe Eykholt 
6532ab7e1ecSJoe Eykholt 	fp = fc_frame_alloc(lport, sizeof(struct fc_ct_hdr) +
6542ab7e1ecSJoe Eykholt 			    sizeof(struct fc_ns_fid));
6552ab7e1ecSJoe Eykholt 	if (!fp)
6562ab7e1ecSJoe Eykholt 		return -ENOMEM;
6572ab7e1ecSJoe Eykholt 	if (!lport->tt.elsct_send(lport, rdata->ids.port_id, fp, FC_NS_GPN_ID,
658b94f8951SJoe Eykholt 				  fc_disc_gpn_id_resp, rdata,
659b94f8951SJoe Eykholt 				  3 * lport->r_a_tov))
6602ab7e1ecSJoe Eykholt 		return -ENOMEM;
6612ab7e1ecSJoe Eykholt 	kref_get(&rdata->kref);
6622ab7e1ecSJoe Eykholt 	return 0;
6632ab7e1ecSJoe Eykholt }
6642ab7e1ecSJoe Eykholt 
6652ab7e1ecSJoe Eykholt /**
66634f42a07SRobert Love  * fc_disc_single() - Discover the directory information for a single target
6673a3b42bfSRobert Love  * @lport: The local port the remote port is associated with
66842e9a92fSRobert Love  * @dp:	   The port to rediscover
66942e9a92fSRobert Love  *
67042e9a92fSRobert Love  * Locking Note: This function expects that the disc_mutex is locked
67142e9a92fSRobert Love  *		 before it is called.
67242e9a92fSRobert Love  */
6732ab7e1ecSJoe Eykholt static int fc_disc_single(struct fc_lport *lport, struct fc_disc_port *dp)
67442e9a92fSRobert Love {
675ab28f1fdSJoe Eykholt 	struct fc_rport_priv *rdata;
67642e9a92fSRobert Love 
6779737e6a7SRobert Love 	rdata = lport->tt.rport_create(lport, dp->port_id);
6782ab7e1ecSJoe Eykholt 	if (!rdata)
6792ab7e1ecSJoe Eykholt 		return -ENOMEM;
6802ab7e1ecSJoe Eykholt 	rdata->disc_id = 0;
6812ab7e1ecSJoe Eykholt 	return fc_disc_gpn_id_req(lport, rdata);
68242e9a92fSRobert Love }
68342e9a92fSRobert Love 
68442e9a92fSRobert Love /**
68534f42a07SRobert Love  * fc_disc_stop() - Stop discovery for a given lport
6863a3b42bfSRobert Love  * @lport: The local port that discovery should stop on
68742e9a92fSRobert Love  */
68842e9a92fSRobert Love void fc_disc_stop(struct fc_lport *lport)
68942e9a92fSRobert Love {
69042e9a92fSRobert Love 	struct fc_disc *disc = &lport->disc;
69142e9a92fSRobert Love 
69242e9a92fSRobert Love 	if (disc) {
69342e9a92fSRobert Love 		cancel_delayed_work_sync(&disc->disc_work);
69442e9a92fSRobert Love 		fc_disc_stop_rports(disc);
69542e9a92fSRobert Love 	}
69642e9a92fSRobert Love }
69742e9a92fSRobert Love 
69842e9a92fSRobert Love /**
69934f42a07SRobert Love  * fc_disc_stop_final() - Stop discovery for a given lport
7003a3b42bfSRobert Love  * @lport: The lport that discovery should stop on
70142e9a92fSRobert Love  *
70242e9a92fSRobert Love  * This function will block until discovery has been
70342e9a92fSRobert Love  * completely stopped and all rports have been deleted.
70442e9a92fSRobert Love  */
70542e9a92fSRobert Love void fc_disc_stop_final(struct fc_lport *lport)
70642e9a92fSRobert Love {
70742e9a92fSRobert Love 	fc_disc_stop(lport);
70842e9a92fSRobert Love 	lport->tt.rport_flush_queue();
70942e9a92fSRobert Love }
71042e9a92fSRobert Love 
71142e9a92fSRobert Love /**
7123a3b42bfSRobert Love  * fc_disc_init() - Initialize the discovery layer for a local port
7133a3b42bfSRobert Love  * @lport: The local port that needs the discovery layer to be initialized
71442e9a92fSRobert Love  */
71542e9a92fSRobert Love int fc_disc_init(struct fc_lport *lport)
71642e9a92fSRobert Love {
71742e9a92fSRobert Love 	struct fc_disc *disc;
71842e9a92fSRobert Love 
71942e9a92fSRobert Love 	if (!lport->tt.disc_start)
72042e9a92fSRobert Love 		lport->tt.disc_start = fc_disc_start;
72142e9a92fSRobert Love 
72242e9a92fSRobert Love 	if (!lport->tt.disc_stop)
72342e9a92fSRobert Love 		lport->tt.disc_stop = fc_disc_stop;
72442e9a92fSRobert Love 
72542e9a92fSRobert Love 	if (!lport->tt.disc_stop_final)
72642e9a92fSRobert Love 		lport->tt.disc_stop_final = fc_disc_stop_final;
72742e9a92fSRobert Love 
72842e9a92fSRobert Love 	if (!lport->tt.disc_recv_req)
72942e9a92fSRobert Love 		lport->tt.disc_recv_req = fc_disc_recv_req;
73042e9a92fSRobert Love 
73142e9a92fSRobert Love 	disc = &lport->disc;
73242e9a92fSRobert Love 	INIT_DELAYED_WORK(&disc->disc_work, fc_disc_timeout);
73342e9a92fSRobert Love 	mutex_init(&disc->disc_mutex);
73442e9a92fSRobert Love 	INIT_LIST_HEAD(&disc->rports);
73542e9a92fSRobert Love 
73642e9a92fSRobert Love 	disc->lport = lport;
73742e9a92fSRobert Love 
73842e9a92fSRobert Love 	return 0;
73942e9a92fSRobert Love }
74042e9a92fSRobert Love EXPORT_SYMBOL(fc_disc_init);
741