xref: /openbmc/linux/drivers/scsi/libfc/fc_disc.c (revision 00832084)
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>
3809703660SPaul Gortmaker #include <linux/export.h>
3942e9a92fSRobert Love #include <asm/unaligned.h>
4042e9a92fSRobert Love 
4142e9a92fSRobert Love #include <scsi/fc/fc_gs.h>
4242e9a92fSRobert Love 
4342e9a92fSRobert Love #include <scsi/libfc.h>
4442e9a92fSRobert Love 
458866a5d9SRobert Love #include "fc_libfc.h"
468866a5d9SRobert Love 
4742e9a92fSRobert Love #define FC_DISC_RETRY_LIMIT	3	/* max retries */
4842e9a92fSRobert Love #define FC_DISC_RETRY_DELAY	500UL	/* (msecs) delay */
4942e9a92fSRobert Love 
5042e9a92fSRobert Love static void fc_disc_gpn_ft_req(struct fc_disc *);
5142e9a92fSRobert Love static void fc_disc_gpn_ft_resp(struct fc_seq *, struct fc_frame *, void *);
52786681b9SJoe Eykholt static void fc_disc_done(struct fc_disc *, enum fc_disc_event);
5342e9a92fSRobert Love static void fc_disc_timeout(struct work_struct *);
542ab7e1ecSJoe Eykholt static int fc_disc_single(struct fc_lport *, struct fc_disc_port *);
5542e9a92fSRobert Love static void fc_disc_restart(struct fc_disc *);
5642e9a92fSRobert Love 
5742e9a92fSRobert Love /**
583a3b42bfSRobert Love  * fc_disc_stop_rports() - Delete all the remote ports associated with the lport
593a3b42bfSRobert Love  * @disc: The discovery job to stop remote ports on
6042e9a92fSRobert Love  *
6142e9a92fSRobert Love  * Locking Note: This function expects that the lport mutex is locked before
6242e9a92fSRobert Love  * calling it.
6342e9a92fSRobert Love  */
64c6b21c93SBart Van Assche static void fc_disc_stop_rports(struct fc_disc *disc)
6542e9a92fSRobert Love {
6642e9a92fSRobert Love 	struct fc_lport *lport;
6742e90414SJoe Eykholt 	struct fc_rport_priv *rdata;
6842e9a92fSRobert Love 
690685230cSJoe Eykholt 	lport = fc_disc_lport(disc);
7042e9a92fSRobert Love 
7142e9a92fSRobert Love 	mutex_lock(&disc->disc_mutex);
7242e90414SJoe Eykholt 	list_for_each_entry_rcu(rdata, &disc->rports, peers)
739fb9d328SJoe Eykholt 		lport->tt.rport_logoff(rdata);
7442e9a92fSRobert Love 	mutex_unlock(&disc->disc_mutex);
7542e9a92fSRobert Love }
7642e9a92fSRobert Love 
7742e9a92fSRobert Love /**
7834f42a07SRobert Love  * fc_disc_recv_rscn_req() - Handle Registered State Change Notification (RSCN)
7992261156SJoe Eykholt  * @disc:  The discovery object to which the RSCN applies
803a3b42bfSRobert Love  * @fp:	   The RSCN frame
8142e9a92fSRobert Love  *
8242e9a92fSRobert Love  * Locking Note: This function expects that the disc_mutex is locked
8342e9a92fSRobert Love  *		 before it is called.
8442e9a92fSRobert Love  */
8592261156SJoe Eykholt static void fc_disc_recv_rscn_req(struct fc_disc *disc, struct fc_frame *fp)
8642e9a92fSRobert Love {
8742e9a92fSRobert Love 	struct fc_lport *lport;
8842e9a92fSRobert Love 	struct fc_els_rscn *rp;
8942e9a92fSRobert Love 	struct fc_els_rscn_page *pp;
9042e9a92fSRobert Love 	struct fc_seq_els_data rjt_data;
9142e9a92fSRobert Love 	unsigned int len;
9242e9a92fSRobert Love 	int redisc = 0;
9342e9a92fSRobert Love 	enum fc_els_rscn_ev_qual ev_qual;
9442e9a92fSRobert Love 	enum fc_els_rscn_addr_fmt fmt;
9542e9a92fSRobert Love 	LIST_HEAD(disc_ports);
9642e9a92fSRobert Love 	struct fc_disc_port *dp, *next;
9742e9a92fSRobert Love 
980685230cSJoe Eykholt 	lport = fc_disc_lport(disc);
9942e9a92fSRobert Love 
1007414705eSRobert Love 	FC_DISC_DBG(disc, "Received an RSCN event\n");
10142e9a92fSRobert Love 
10242e9a92fSRobert Love 	/* make sure the frame contains an RSCN message */
10342e9a92fSRobert Love 	rp = fc_frame_payload_get(fp, sizeof(*rp));
10442e9a92fSRobert Love 	if (!rp)
10542e9a92fSRobert Love 		goto reject;
10642e9a92fSRobert Love 	/* make sure the page length is as expected (4 bytes) */
10742e9a92fSRobert Love 	if (rp->rscn_page_len != sizeof(*pp))
10842e9a92fSRobert Love 		goto reject;
10942e9a92fSRobert Love 	/* get the RSCN payload length */
11042e9a92fSRobert Love 	len = ntohs(rp->rscn_plen);
11142e9a92fSRobert Love 	if (len < sizeof(*rp))
11242e9a92fSRobert Love 		goto reject;
11342e9a92fSRobert Love 	/* make sure the frame contains the expected payload */
11442e9a92fSRobert Love 	rp = fc_frame_payload_get(fp, len);
11542e9a92fSRobert Love 	if (!rp)
11642e9a92fSRobert Love 		goto reject;
11742e9a92fSRobert Love 	/* payload must be a multiple of the RSCN page size */
11842e9a92fSRobert Love 	len -= sizeof(*rp);
11942e9a92fSRobert Love 	if (len % sizeof(*pp))
12042e9a92fSRobert Love 		goto reject;
12142e9a92fSRobert Love 
12242e9a92fSRobert Love 	for (pp = (void *)(rp + 1); len > 0; len -= sizeof(*pp), pp++) {
12342e9a92fSRobert Love 		ev_qual = pp->rscn_page_flags >> ELS_RSCN_EV_QUAL_BIT;
12442e9a92fSRobert Love 		ev_qual &= ELS_RSCN_EV_QUAL_MASK;
12542e9a92fSRobert Love 		fmt = pp->rscn_page_flags >> ELS_RSCN_ADDR_FMT_BIT;
12642e9a92fSRobert Love 		fmt &= ELS_RSCN_ADDR_FMT_MASK;
12742e9a92fSRobert Love 		/*
12842e9a92fSRobert Love 		 * if we get an address format other than port
12942e9a92fSRobert Love 		 * (area, domain, fabric), then do a full discovery
13042e9a92fSRobert Love 		 */
13142e9a92fSRobert Love 		switch (fmt) {
13242e9a92fSRobert Love 		case ELS_ADDR_FMT_PORT:
1337414705eSRobert Love 			FC_DISC_DBG(disc, "Port address format for port "
134ce8b5df0SChris Leech 				    "(%6.6x)\n", ntoh24(pp->rscn_fid));
13542e9a92fSRobert Love 			dp = kzalloc(sizeof(*dp), GFP_KERNEL);
13642e9a92fSRobert Love 			if (!dp) {
13742e9a92fSRobert Love 				redisc = 1;
13842e9a92fSRobert Love 				break;
13942e9a92fSRobert Love 			}
14042e9a92fSRobert Love 			dp->lp = lport;
1419737e6a7SRobert Love 			dp->port_id = ntoh24(pp->rscn_fid);
14242e9a92fSRobert Love 			list_add_tail(&dp->peers, &disc_ports);
14342e9a92fSRobert Love 			break;
14442e9a92fSRobert Love 		case ELS_ADDR_FMT_AREA:
14542e9a92fSRobert Love 		case ELS_ADDR_FMT_DOM:
14642e9a92fSRobert Love 		case ELS_ADDR_FMT_FAB:
14742e9a92fSRobert Love 		default:
1487414705eSRobert Love 			FC_DISC_DBG(disc, "Address format is (%d)\n", fmt);
14942e9a92fSRobert Love 			redisc = 1;
15042e9a92fSRobert Love 			break;
15142e9a92fSRobert Love 		}
15242e9a92fSRobert Love 	}
15392261156SJoe Eykholt 	lport->tt.seq_els_rsp_send(fp, ELS_LS_ACC, NULL);
1542ab7e1ecSJoe Eykholt 
1552ab7e1ecSJoe Eykholt 	/*
1562ab7e1ecSJoe Eykholt 	 * If not doing a complete rediscovery, do GPN_ID on
1572ab7e1ecSJoe Eykholt 	 * the individual ports mentioned in the list.
1582ab7e1ecSJoe Eykholt 	 * If any of these get an error, do a full rediscovery.
1592ab7e1ecSJoe Eykholt 	 * In any case, go through the list and free the entries.
1602ab7e1ecSJoe Eykholt 	 */
1612ab7e1ecSJoe Eykholt 	list_for_each_entry_safe(dp, next, &disc_ports, peers) {
1622ab7e1ecSJoe Eykholt 		list_del(&dp->peers);
1632ab7e1ecSJoe Eykholt 		if (!redisc)
1642ab7e1ecSJoe Eykholt 			redisc = fc_disc_single(lport, dp);
1652ab7e1ecSJoe Eykholt 		kfree(dp);
1662ab7e1ecSJoe Eykholt 	}
16742e9a92fSRobert Love 	if (redisc) {
1687414705eSRobert Love 		FC_DISC_DBG(disc, "RSCN received: rediscovering\n");
16942e9a92fSRobert Love 		fc_disc_restart(disc);
17042e9a92fSRobert Love 	} else {
1717414705eSRobert Love 		FC_DISC_DBG(disc, "RSCN received: not rediscovering. "
17242e9a92fSRobert Love 			    "redisc %d state %d in_prog %d\n",
17342e9a92fSRobert Love 			    redisc, lport->state, disc->pending);
17442e9a92fSRobert Love 	}
17542e9a92fSRobert Love 	fc_frame_free(fp);
17642e9a92fSRobert Love 	return;
17742e9a92fSRobert Love reject:
1787414705eSRobert Love 	FC_DISC_DBG(disc, "Received a bad RSCN frame\n");
17942e9a92fSRobert Love 	rjt_data.reason = ELS_RJT_LOGIC;
18042e9a92fSRobert Love 	rjt_data.explan = ELS_EXPL_NONE;
18192261156SJoe Eykholt 	lport->tt.seq_els_rsp_send(fp, ELS_LS_RJT, &rjt_data);
18242e9a92fSRobert Love 	fc_frame_free(fp);
18342e9a92fSRobert Love }
18442e9a92fSRobert Love 
18542e9a92fSRobert Love /**
18634f42a07SRobert Love  * fc_disc_recv_req() - Handle incoming requests
1873a3b42bfSRobert Love  * @lport: The local port receiving the request
18892261156SJoe Eykholt  * @fp:	   The request frame
18942e9a92fSRobert Love  *
19042e9a92fSRobert Love  * Locking Note: This function is called from the EM and will lock
19142e9a92fSRobert Love  *		 the disc_mutex before calling the handler for the
19242e9a92fSRobert Love  *		 request.
19342e9a92fSRobert Love  */
19492261156SJoe Eykholt static void fc_disc_recv_req(struct fc_lport *lport, struct fc_frame *fp)
19542e9a92fSRobert Love {
19642e9a92fSRobert Love 	u8 op;
19742e9a92fSRobert Love 	struct fc_disc *disc = &lport->disc;
19842e9a92fSRobert Love 
19942e9a92fSRobert Love 	op = fc_frame_payload_op(fp);
20042e9a92fSRobert Love 	switch (op) {
20142e9a92fSRobert Love 	case ELS_RSCN:
20242e9a92fSRobert Love 		mutex_lock(&disc->disc_mutex);
20392261156SJoe Eykholt 		fc_disc_recv_rscn_req(disc, fp);
20442e9a92fSRobert Love 		mutex_unlock(&disc->disc_mutex);
20542e9a92fSRobert Love 		break;
20642e9a92fSRobert Love 	default:
2077414705eSRobert Love 		FC_DISC_DBG(disc, "Received an unsupported request, "
2087414705eSRobert Love 			    "the opcode is (%x)\n", op);
20983383dd1SHillf Danton 		fc_frame_free(fp);
21042e9a92fSRobert Love 		break;
21142e9a92fSRobert Love 	}
21242e9a92fSRobert Love }
21342e9a92fSRobert Love 
21442e9a92fSRobert Love /**
21534f42a07SRobert Love  * fc_disc_restart() - Restart discovery
2163a3b42bfSRobert Love  * @disc: The discovery object to be restarted
21742e9a92fSRobert Love  *
21842e9a92fSRobert Love  * Locking Note: This function expects that the disc mutex
21942e9a92fSRobert Love  *		 is already locked.
22042e9a92fSRobert Love  */
22142e9a92fSRobert Love static void fc_disc_restart(struct fc_disc *disc)
22242e9a92fSRobert Love {
223935d0fceSJoe Eykholt 	if (!disc->disc_callback)
224935d0fceSJoe Eykholt 		return;
225935d0fceSJoe Eykholt 
2267414705eSRobert Love 	FC_DISC_DBG(disc, "Restarting discovery\n");
22742e9a92fSRobert Love 
22842e9a92fSRobert Love 	disc->requested = 1;
2290f6c6149SJoe Eykholt 	if (disc->pending)
2300f6c6149SJoe Eykholt 		return;
2310f6c6149SJoe Eykholt 
2320f6c6149SJoe Eykholt 	/*
2330f6c6149SJoe Eykholt 	 * Advance disc_id.  This is an arbitrary non-zero number that will
2340f6c6149SJoe Eykholt 	 * match the value in the fc_rport_priv after discovery for all
2350f6c6149SJoe Eykholt 	 * freshly-discovered remote ports.  Avoid wrapping to zero.
2360f6c6149SJoe Eykholt 	 */
2370f6c6149SJoe Eykholt 	disc->disc_id = (disc->disc_id + 2) | 1;
2383667d7e7SJoe Eykholt 	disc->retry_count = 0;
23942e9a92fSRobert Love 	fc_disc_gpn_ft_req(disc);
24042e9a92fSRobert Love }
24142e9a92fSRobert Love 
24242e9a92fSRobert Love /**
2433a3b42bfSRobert Love  * fc_disc_start() - Start discovery on a local port
2443a3b42bfSRobert Love  * @lport:	   The local port to have discovery started on
2453a3b42bfSRobert Love  * @disc_callback: Callback function to be called when discovery is complete
24642e9a92fSRobert Love  */
24742e9a92fSRobert Love static void fc_disc_start(void (*disc_callback)(struct fc_lport *,
24842e9a92fSRobert Love 						enum fc_disc_event),
24942e9a92fSRobert Love 			  struct fc_lport *lport)
25042e9a92fSRobert Love {
25142e9a92fSRobert Love 	struct fc_disc *disc = &lport->disc;
25242e9a92fSRobert Love 
25342e9a92fSRobert Love 	/*
25442e9a92fSRobert Love 	 * At this point we may have a new disc job or an existing
25542e9a92fSRobert Love 	 * one. Either way, let's lock when we make changes to it
25642e9a92fSRobert Love 	 * and send the GPN_FT request.
25742e9a92fSRobert Love 	 */
25842e9a92fSRobert Love 	mutex_lock(&disc->disc_mutex);
25942e9a92fSRobert Love 	disc->disc_callback = disc_callback;
26029d898e9SJoe Eykholt 	fc_disc_restart(disc);
26142e9a92fSRobert Love 	mutex_unlock(&disc->disc_mutex);
26242e9a92fSRobert Love }
26342e9a92fSRobert Love 
26442e9a92fSRobert Love /**
26534f42a07SRobert Love  * fc_disc_done() - Discovery has been completed
2663a3b42bfSRobert Love  * @disc:  The discovery context
2673a3b42bfSRobert Love  * @event: The discovery completion status
268786681b9SJoe Eykholt  *
2690d228c0fSAbhijeet Joglekar  * Locking Note: This function expects that the disc mutex is locked before
2700d228c0fSAbhijeet Joglekar  * it is called. The discovery callback is then made with the lock released,
2710d228c0fSAbhijeet Joglekar  * and the lock is re-taken before returning from this function
27242e9a92fSRobert Love  */
273786681b9SJoe Eykholt static void fc_disc_done(struct fc_disc *disc, enum fc_disc_event event)
27442e9a92fSRobert Love {
2750685230cSJoe Eykholt 	struct fc_lport *lport = fc_disc_lport(disc);
2760f6c6149SJoe Eykholt 	struct fc_rport_priv *rdata;
27742e9a92fSRobert Love 
2787414705eSRobert Love 	FC_DISC_DBG(disc, "Discovery complete\n");
27942e9a92fSRobert Love 
28042e9a92fSRobert Love 	disc->pending = 0;
2810f6c6149SJoe Eykholt 	if (disc->requested) {
2820f6c6149SJoe Eykholt 		fc_disc_restart(disc);
2830f6c6149SJoe Eykholt 		return;
2840f6c6149SJoe Eykholt 	}
2850f6c6149SJoe Eykholt 
2860f6c6149SJoe Eykholt 	/*
2870f6c6149SJoe Eykholt 	 * Go through all remote ports.	 If they were found in the latest
2880f6c6149SJoe Eykholt 	 * discovery, reverify or log them in.	Otherwise, log them out.
2890f6c6149SJoe Eykholt 	 * Skip ports which were never discovered.  These are the dNS port
2900f6c6149SJoe Eykholt 	 * and ports which were created by PLOGI.
2910f6c6149SJoe Eykholt 	 */
29242e90414SJoe Eykholt 	list_for_each_entry_rcu(rdata, &disc->rports, peers) {
2930f6c6149SJoe Eykholt 		if (!rdata->disc_id)
2940f6c6149SJoe Eykholt 			continue;
2950f6c6149SJoe Eykholt 		if (rdata->disc_id == disc->disc_id)
2960f6c6149SJoe Eykholt 			lport->tt.rport_login(rdata);
2970f6c6149SJoe Eykholt 		else
2980f6c6149SJoe Eykholt 			lport->tt.rport_logoff(rdata);
2990f6c6149SJoe Eykholt 	}
3000d228c0fSAbhijeet Joglekar 
3010d228c0fSAbhijeet Joglekar 	mutex_unlock(&disc->disc_mutex);
3020d228c0fSAbhijeet Joglekar 	disc->disc_callback(lport, event);
3030d228c0fSAbhijeet Joglekar 	mutex_lock(&disc->disc_mutex);
30442e9a92fSRobert Love }
30542e9a92fSRobert Love 
30642e9a92fSRobert Love /**
30734f42a07SRobert Love  * fc_disc_error() - Handle error on dNS request
3083a3b42bfSRobert Love  * @disc: The discovery context
3093a3b42bfSRobert Love  * @fp:	  The error code encoded as a frame pointer
31042e9a92fSRobert Love  */
31142e9a92fSRobert Love static void fc_disc_error(struct fc_disc *disc, struct fc_frame *fp)
31242e9a92fSRobert Love {
3130685230cSJoe Eykholt 	struct fc_lport *lport = fc_disc_lport(disc);
31442e9a92fSRobert Love 	unsigned long delay = 0;
3157414705eSRobert Love 
3167414705eSRobert Love 	FC_DISC_DBG(disc, "Error %ld, retries %d/%d\n",
31742e9a92fSRobert Love 		    PTR_ERR(fp), disc->retry_count,
31842e9a92fSRobert Love 		    FC_DISC_RETRY_LIMIT);
31942e9a92fSRobert Love 
32042e9a92fSRobert Love 	if (!fp || PTR_ERR(fp) == -FC_EX_TIMEOUT) {
32142e9a92fSRobert Love 		/*
32242e9a92fSRobert Love 		 * Memory allocation failure, or the exchange timed out,
32342e9a92fSRobert Love 		 * retry after delay.
32442e9a92fSRobert Love 		 */
32542e9a92fSRobert Love 		if (disc->retry_count < FC_DISC_RETRY_LIMIT) {
32642e9a92fSRobert Love 			/* go ahead and retry */
32742e9a92fSRobert Love 			if (!fp)
32842e9a92fSRobert Love 				delay = msecs_to_jiffies(FC_DISC_RETRY_DELAY);
32942e9a92fSRobert Love 			else {
33042e9a92fSRobert Love 				delay = msecs_to_jiffies(lport->e_d_tov);
33142e9a92fSRobert Love 
33242e9a92fSRobert Love 				/* timeout faster first time */
33342e9a92fSRobert Love 				if (!disc->retry_count)
33442e9a92fSRobert Love 					delay /= 4;
33542e9a92fSRobert Love 			}
33642e9a92fSRobert Love 			disc->retry_count++;
33742e9a92fSRobert Love 			schedule_delayed_work(&disc->disc_work, delay);
338786681b9SJoe Eykholt 		} else
339786681b9SJoe Eykholt 			fc_disc_done(disc, DISC_EV_FAILED);
34000832084SBhanu Prakash Gollapudi 	} else if (PTR_ERR(fp) == -FC_EX_CLOSED) {
34100832084SBhanu Prakash Gollapudi 		/*
34200832084SBhanu Prakash Gollapudi 		 * if discovery fails due to lport reset, clear
34300832084SBhanu Prakash Gollapudi 		 * pending flag so that subsequent discovery can
34400832084SBhanu Prakash Gollapudi 		 * continue
34500832084SBhanu Prakash Gollapudi 		 */
34600832084SBhanu Prakash Gollapudi 		disc->pending = 0;
34742e9a92fSRobert Love 	}
34842e9a92fSRobert Love }
34942e9a92fSRobert Love 
35042e9a92fSRobert Love /**
35134f42a07SRobert Love  * fc_disc_gpn_ft_req() - Send Get Port Names by FC-4 type (GPN_FT) request
3523a3b42bfSRobert Love  * @lport: The discovery context
35342e9a92fSRobert Love  *
35442e9a92fSRobert Love  * Locking Note: This function expects that the disc_mutex is locked
35542e9a92fSRobert Love  *		 before it is called.
35642e9a92fSRobert Love  */
35742e9a92fSRobert Love static void fc_disc_gpn_ft_req(struct fc_disc *disc)
35842e9a92fSRobert Love {
35942e9a92fSRobert Love 	struct fc_frame *fp;
3600685230cSJoe Eykholt 	struct fc_lport *lport = fc_disc_lport(disc);
36142e9a92fSRobert Love 
36242e9a92fSRobert Love 	WARN_ON(!fc_lport_test_ready(lport));
36342e9a92fSRobert Love 
36442e9a92fSRobert Love 	disc->pending = 1;
36542e9a92fSRobert Love 	disc->requested = 0;
36642e9a92fSRobert Love 
36742e9a92fSRobert Love 	disc->buf_len = 0;
36842e9a92fSRobert Love 	disc->seq_count = 0;
36942e9a92fSRobert Love 	fp = fc_frame_alloc(lport,
37042e9a92fSRobert Love 			    sizeof(struct fc_ct_hdr) +
37142e9a92fSRobert Love 			    sizeof(struct fc_ns_gid_ft));
37242e9a92fSRobert Love 	if (!fp)
37342e9a92fSRobert Love 		goto err;
37442e9a92fSRobert Love 
375a46f327aSJoe Eykholt 	if (lport->tt.elsct_send(lport, 0, fp,
37642e9a92fSRobert Love 				 FC_NS_GPN_FT,
37742e9a92fSRobert Love 				 fc_disc_gpn_ft_resp,
378b94f8951SJoe Eykholt 				 disc, 3 * lport->r_a_tov))
37942e9a92fSRobert Love 		return;
38042e9a92fSRobert Love err:
3818f550f93SChris Leech 	fc_disc_error(disc, NULL);
38242e9a92fSRobert Love }
38342e9a92fSRobert Love 
38442e9a92fSRobert Love /**
385786681b9SJoe Eykholt  * fc_disc_gpn_ft_parse() - Parse the body of the dNS GPN_FT response.
3863a3b42bfSRobert Love  * @lport: The local port the GPN_FT was received on
3873a3b42bfSRobert Love  * @buf:   The GPN_FT response buffer
3883a3b42bfSRobert Love  * @len:   The size of response buffer
389786681b9SJoe Eykholt  *
390786681b9SJoe Eykholt  * Goes through the list of IDs and names resulting from a request.
39142e9a92fSRobert Love  */
39242e9a92fSRobert Love static int fc_disc_gpn_ft_parse(struct fc_disc *disc, void *buf, size_t len)
39342e9a92fSRobert Love {
39442e9a92fSRobert Love 	struct fc_lport *lport;
39542e9a92fSRobert Love 	struct fc_gpn_ft_resp *np;
39642e9a92fSRobert Love 	char *bp;
39742e9a92fSRobert Love 	size_t plen;
39842e9a92fSRobert Love 	size_t tlen;
39942e9a92fSRobert Love 	int error = 0;
400795d86f5SJoe Eykholt 	struct fc_rport_identifiers ids;
401ab28f1fdSJoe Eykholt 	struct fc_rport_priv *rdata;
40242e9a92fSRobert Love 
4030685230cSJoe Eykholt 	lport = fc_disc_lport(disc);
404a1c1e4e7SJoe Eykholt 	disc->seq_count++;
40542e9a92fSRobert Love 
40642e9a92fSRobert Love 	/*
40742e9a92fSRobert Love 	 * Handle partial name record left over from previous call.
40842e9a92fSRobert Love 	 */
40942e9a92fSRobert Love 	bp = buf;
41042e9a92fSRobert Love 	plen = len;
41142e9a92fSRobert Love 	np = (struct fc_gpn_ft_resp *)bp;
41242e9a92fSRobert Love 	tlen = disc->buf_len;
41381a67b97SJoe Eykholt 	disc->buf_len = 0;
41442e9a92fSRobert Love 	if (tlen) {
41542e9a92fSRobert Love 		WARN_ON(tlen >= sizeof(*np));
41642e9a92fSRobert Love 		plen = sizeof(*np) - tlen;
41742e9a92fSRobert Love 		WARN_ON(plen <= 0);
41842e9a92fSRobert Love 		WARN_ON(plen >= sizeof(*np));
41942e9a92fSRobert Love 		if (plen > len)
42042e9a92fSRobert Love 			plen = len;
42142e9a92fSRobert Love 		np = &disc->partial_buf;
42242e9a92fSRobert Love 		memcpy((char *)np + tlen, bp, plen);
42342e9a92fSRobert Love 
42442e9a92fSRobert Love 		/*
42542e9a92fSRobert Love 		 * Set bp so that the loop below will advance it to the
42642e9a92fSRobert Love 		 * first valid full name element.
42742e9a92fSRobert Love 		 */
42842e9a92fSRobert Love 		bp -= tlen;
42942e9a92fSRobert Love 		len += tlen;
43042e9a92fSRobert Love 		plen += tlen;
43142e9a92fSRobert Love 		disc->buf_len = (unsigned char) plen;
43242e9a92fSRobert Love 		if (plen == sizeof(*np))
43342e9a92fSRobert Love 			disc->buf_len = 0;
43442e9a92fSRobert Love 	}
43542e9a92fSRobert Love 
43642e9a92fSRobert Love 	/*
43742e9a92fSRobert Love 	 * Handle full name records, including the one filled from above.
43842e9a92fSRobert Love 	 * Normally, np == bp and plen == len, but from the partial case above,
43942e9a92fSRobert Love 	 * bp, len describe the overall buffer, and np, plen describe the
44042e9a92fSRobert Love 	 * partial buffer, which if would usually be full now.
44142e9a92fSRobert Love 	 * After the first time through the loop, things return to "normal".
44242e9a92fSRobert Love 	 */
44342e9a92fSRobert Love 	while (plen >= sizeof(*np)) {
444795d86f5SJoe Eykholt 		ids.port_id = ntoh24(np->fp_fid);
445795d86f5SJoe Eykholt 		ids.port_name = ntohll(np->fp_wwpn);
44642e9a92fSRobert Love 
4477b2787ecSRobert Love 		if (ids.port_id != lport->port_id &&
448795d86f5SJoe Eykholt 		    ids.port_name != lport->wwpn) {
4499737e6a7SRobert Love 			rdata = lport->tt.rport_create(lport, ids.port_id);
4509737e6a7SRobert Love 			if (rdata) {
4519737e6a7SRobert Love 				rdata->ids.port_name = ids.port_name;
4520f6c6149SJoe Eykholt 				rdata->disc_id = disc->disc_id;
4539737e6a7SRobert Love 			} else {
4547414705eSRobert Love 				printk(KERN_WARNING "libfc: Failed to allocate "
4557414705eSRobert Love 				       "memory for the newly discovered port "
456ce8b5df0SChris Leech 				       "(%6.6x)\n", ids.port_id);
45781a67b97SJoe Eykholt 				error = -ENOMEM;
45881a67b97SJoe Eykholt 			}
45942e9a92fSRobert Love 		}
46042e9a92fSRobert Love 
46142e9a92fSRobert Love 		if (np->fp_flags & FC_NS_FID_LAST) {
462786681b9SJoe Eykholt 			fc_disc_done(disc, DISC_EV_SUCCESS);
46342e9a92fSRobert Love 			len = 0;
46442e9a92fSRobert Love 			break;
46542e9a92fSRobert Love 		}
46642e9a92fSRobert Love 		len -= sizeof(*np);
46742e9a92fSRobert Love 		bp += sizeof(*np);
46842e9a92fSRobert Love 		np = (struct fc_gpn_ft_resp *)bp;
46942e9a92fSRobert Love 		plen = len;
47042e9a92fSRobert Love 	}
47142e9a92fSRobert Love 
47242e9a92fSRobert Love 	/*
47342e9a92fSRobert Love 	 * Save any partial record at the end of the buffer for next time.
47442e9a92fSRobert Love 	 */
47542e9a92fSRobert Love 	if (error == 0 && len > 0 && len < sizeof(*np)) {
47642e9a92fSRobert Love 		if (np != &disc->partial_buf) {
4777414705eSRobert Love 			FC_DISC_DBG(disc, "Partial buffer remains "
4787414705eSRobert Love 				    "for discovery\n");
47942e9a92fSRobert Love 			memcpy(&disc->partial_buf, np, len);
48042e9a92fSRobert Love 		}
48142e9a92fSRobert Love 		disc->buf_len = (unsigned char) len;
48242e9a92fSRobert Love 	}
48342e9a92fSRobert Love 	return error;
48442e9a92fSRobert Love }
48542e9a92fSRobert Love 
48634f42a07SRobert Love /**
4873a3b42bfSRobert Love  * fc_disc_timeout() - Handler for discovery timeouts
4883a3b42bfSRobert Love  * @work: Structure holding discovery context that needs to retry discovery
48942e9a92fSRobert Love  */
49042e9a92fSRobert Love static void fc_disc_timeout(struct work_struct *work)
49142e9a92fSRobert Love {
49242e9a92fSRobert Love 	struct fc_disc *disc = container_of(work,
49342e9a92fSRobert Love 					    struct fc_disc,
49442e9a92fSRobert Love 					    disc_work.work);
49542e9a92fSRobert Love 	mutex_lock(&disc->disc_mutex);
49642e9a92fSRobert Love 	fc_disc_gpn_ft_req(disc);
49742e9a92fSRobert Love 	mutex_unlock(&disc->disc_mutex);
49842e9a92fSRobert Love }
49942e9a92fSRobert Love 
50042e9a92fSRobert Love /**
50134f42a07SRobert Love  * fc_disc_gpn_ft_resp() - Handle a response frame from Get Port Names (GPN_FT)
5023a3b42bfSRobert Love  * @sp:	    The sequence that the GPN_FT response was received on
5033a3b42bfSRobert Love  * @fp:	    The GPN_FT response frame
5043a3b42bfSRobert Love  * @lp_arg: The discovery context
50542e9a92fSRobert Love  *
5060d228c0fSAbhijeet Joglekar  * Locking Note: This function is called without disc mutex held, and
5070d228c0fSAbhijeet Joglekar  *		 should do all its processing with the mutex held
50842e9a92fSRobert Love  */
50942e9a92fSRobert Love static void fc_disc_gpn_ft_resp(struct fc_seq *sp, struct fc_frame *fp,
51042e9a92fSRobert Love 				void *disc_arg)
51142e9a92fSRobert Love {
51242e9a92fSRobert Love 	struct fc_disc *disc = disc_arg;
51342e9a92fSRobert Love 	struct fc_ct_hdr *cp;
51442e9a92fSRobert Love 	struct fc_frame_header *fh;
515a1c1e4e7SJoe Eykholt 	enum fc_disc_event event = DISC_EV_NONE;
51642e9a92fSRobert Love 	unsigned int seq_cnt;
51742e9a92fSRobert Love 	unsigned int len;
518a1c1e4e7SJoe Eykholt 	int error = 0;
51942e9a92fSRobert Love 
5200d228c0fSAbhijeet Joglekar 	mutex_lock(&disc->disc_mutex);
5217414705eSRobert Love 	FC_DISC_DBG(disc, "Received a GPN_FT response\n");
52242e9a92fSRobert Love 
52342e9a92fSRobert Love 	if (IS_ERR(fp)) {
52442e9a92fSRobert Love 		fc_disc_error(disc, fp);
5250d228c0fSAbhijeet Joglekar 		mutex_unlock(&disc->disc_mutex);
52642e9a92fSRobert Love 		return;
52742e9a92fSRobert Love 	}
52842e9a92fSRobert Love 
52942e9a92fSRobert Love 	WARN_ON(!fc_frame_is_linear(fp));	/* buffer must be contiguous */
53042e9a92fSRobert Love 	fh = fc_frame_header_get(fp);
53142e9a92fSRobert Love 	len = fr_len(fp) - sizeof(*fh);
53242e9a92fSRobert Love 	seq_cnt = ntohs(fh->fh_seq_cnt);
533a1c1e4e7SJoe Eykholt 	if (fr_sof(fp) == FC_SOF_I3 && seq_cnt == 0 && disc->seq_count == 0) {
53442e9a92fSRobert Love 		cp = fc_frame_payload_get(fp, sizeof(*cp));
53542e9a92fSRobert Love 		if (!cp) {
5367414705eSRobert Love 			FC_DISC_DBG(disc, "GPN_FT response too short, len %d\n",
53742e9a92fSRobert Love 				    fr_len(fp));
538883a337cSJoe Eykholt 			event = DISC_EV_FAILED;
53942e9a92fSRobert Love 		} else if (ntohs(cp->ct_cmd) == FC_FS_ACC) {
54042e9a92fSRobert Love 
54134f42a07SRobert Love 			/* Accepted, parse the response. */
54242e9a92fSRobert Love 			len -= sizeof(*cp);
543a1c1e4e7SJoe Eykholt 			error = fc_disc_gpn_ft_parse(disc, cp + 1, len);
54442e9a92fSRobert Love 		} else if (ntohs(cp->ct_cmd) == FC_FS_RJT) {
5457414705eSRobert Love 			FC_DISC_DBG(disc, "GPN_FT rejected reason %x exp %x "
54642e9a92fSRobert Love 				    "(check zoning)\n", cp->ct_reason,
54742e9a92fSRobert Love 				    cp->ct_explan);
548a1c1e4e7SJoe Eykholt 			event = DISC_EV_FAILED;
549c762608bSJoe Eykholt 			if (cp->ct_reason == FC_FS_RJT_UNABL &&
550c762608bSJoe Eykholt 			    cp->ct_explan == FC_FS_EXP_FTNR)
551c762608bSJoe Eykholt 				event = DISC_EV_SUCCESS;
55242e9a92fSRobert Love 		} else {
5537414705eSRobert Love 			FC_DISC_DBG(disc, "GPN_FT unexpected response code "
5547414705eSRobert Love 				    "%x\n", ntohs(cp->ct_cmd));
555883a337cSJoe Eykholt 			event = DISC_EV_FAILED;
55642e9a92fSRobert Love 		}
557a1c1e4e7SJoe Eykholt 	} else if (fr_sof(fp) == FC_SOF_N3 && seq_cnt == disc->seq_count) {
558a1c1e4e7SJoe Eykholt 		error = fc_disc_gpn_ft_parse(disc, fh + 1, len);
55942e9a92fSRobert Love 	} else {
5607414705eSRobert Love 		FC_DISC_DBG(disc, "GPN_FT unexpected frame - out of sequence? "
56142e9a92fSRobert Love 			    "seq_cnt %x expected %x sof %x eof %x\n",
56242e9a92fSRobert Love 			    seq_cnt, disc->seq_count, fr_sof(fp), fr_eof(fp));
563883a337cSJoe Eykholt 		event = DISC_EV_FAILED;
56442e9a92fSRobert Love 	}
56542e9a92fSRobert Love 	if (error)
56642e9a92fSRobert Love 		fc_disc_error(disc, fp);
567a1c1e4e7SJoe Eykholt 	else if (event != DISC_EV_NONE)
568a1c1e4e7SJoe Eykholt 		fc_disc_done(disc, event);
56942e9a92fSRobert Love 	fc_frame_free(fp);
5700d228c0fSAbhijeet Joglekar 	mutex_unlock(&disc->disc_mutex);
57142e9a92fSRobert Love }
57242e9a92fSRobert Love 
57342e9a92fSRobert Love /**
5742ab7e1ecSJoe Eykholt  * fc_disc_gpn_id_resp() - Handle a response frame from Get Port Names (GPN_ID)
5753a3b42bfSRobert Love  * @sp:	       The sequence the GPN_ID is on
5763a3b42bfSRobert Love  * @fp:	       The response frame
5773a3b42bfSRobert Love  * @rdata_arg: The remote port that sent the GPN_ID response
5782ab7e1ecSJoe Eykholt  *
5792ab7e1ecSJoe Eykholt  * Locking Note: This function is called without disc mutex held.
5802ab7e1ecSJoe Eykholt  */
5812ab7e1ecSJoe Eykholt static void fc_disc_gpn_id_resp(struct fc_seq *sp, struct fc_frame *fp,
5822ab7e1ecSJoe Eykholt 				void *rdata_arg)
5832ab7e1ecSJoe Eykholt {
5842ab7e1ecSJoe Eykholt 	struct fc_rport_priv *rdata = rdata_arg;
5852ab7e1ecSJoe Eykholt 	struct fc_rport_priv *new_rdata;
5862ab7e1ecSJoe Eykholt 	struct fc_lport *lport;
5872ab7e1ecSJoe Eykholt 	struct fc_disc *disc;
5882ab7e1ecSJoe Eykholt 	struct fc_ct_hdr *cp;
5892ab7e1ecSJoe Eykholt 	struct fc_ns_gid_pn *pn;
5902ab7e1ecSJoe Eykholt 	u64 port_name;
5912ab7e1ecSJoe Eykholt 
5922ab7e1ecSJoe Eykholt 	lport = rdata->local_port;
5932ab7e1ecSJoe Eykholt 	disc = &lport->disc;
5942ab7e1ecSJoe Eykholt 
5952ab7e1ecSJoe Eykholt 	mutex_lock(&disc->disc_mutex);
5962ab7e1ecSJoe Eykholt 	if (PTR_ERR(fp) == -FC_EX_CLOSED)
5972ab7e1ecSJoe Eykholt 		goto out;
5982ab7e1ecSJoe Eykholt 	if (IS_ERR(fp))
5992ab7e1ecSJoe Eykholt 		goto redisc;
6002ab7e1ecSJoe Eykholt 
6012ab7e1ecSJoe Eykholt 	cp = fc_frame_payload_get(fp, sizeof(*cp));
6022ab7e1ecSJoe Eykholt 	if (!cp)
6032ab7e1ecSJoe Eykholt 		goto redisc;
6042ab7e1ecSJoe Eykholt 	if (ntohs(cp->ct_cmd) == FC_FS_ACC) {
6052ab7e1ecSJoe Eykholt 		if (fr_len(fp) < sizeof(struct fc_frame_header) +
6062ab7e1ecSJoe Eykholt 		    sizeof(*cp) + sizeof(*pn))
6072ab7e1ecSJoe Eykholt 			goto redisc;
6082ab7e1ecSJoe Eykholt 		pn = (struct fc_ns_gid_pn *)(cp + 1);
6092ab7e1ecSJoe Eykholt 		port_name = get_unaligned_be64(&pn->fn_wwpn);
6102ab7e1ecSJoe Eykholt 		if (rdata->ids.port_name == -1)
6112ab7e1ecSJoe Eykholt 			rdata->ids.port_name = port_name;
6122ab7e1ecSJoe Eykholt 		else if (rdata->ids.port_name != port_name) {
6132ab7e1ecSJoe Eykholt 			FC_DISC_DBG(disc, "GPN_ID accepted.  WWPN changed. "
6149f8f3aa6SChris Leech 				    "Port-id %6.6x wwpn %16.16llx\n",
6152ab7e1ecSJoe Eykholt 				    rdata->ids.port_id, port_name);
6162ab7e1ecSJoe Eykholt 			lport->tt.rport_logoff(rdata);
6172ab7e1ecSJoe Eykholt 
6182ab7e1ecSJoe Eykholt 			new_rdata = lport->tt.rport_create(lport,
6192ab7e1ecSJoe Eykholt 							   rdata->ids.port_id);
6202ab7e1ecSJoe Eykholt 			if (new_rdata) {
6212ab7e1ecSJoe Eykholt 				new_rdata->disc_id = disc->disc_id;
6222ab7e1ecSJoe Eykholt 				lport->tt.rport_login(new_rdata);
6232ab7e1ecSJoe Eykholt 			}
6242ab7e1ecSJoe Eykholt 			goto out;
6252ab7e1ecSJoe Eykholt 		}
6262ab7e1ecSJoe Eykholt 		rdata->disc_id = disc->disc_id;
6272ab7e1ecSJoe Eykholt 		lport->tt.rport_login(rdata);
6282ab7e1ecSJoe Eykholt 	} else if (ntohs(cp->ct_cmd) == FC_FS_RJT) {
6292ab7e1ecSJoe Eykholt 		FC_DISC_DBG(disc, "GPN_ID rejected reason %x exp %x\n",
6302ab7e1ecSJoe Eykholt 			    cp->ct_reason, cp->ct_explan);
6312ab7e1ecSJoe Eykholt 		lport->tt.rport_logoff(rdata);
6322ab7e1ecSJoe Eykholt 	} else {
6332ab7e1ecSJoe Eykholt 		FC_DISC_DBG(disc, "GPN_ID unexpected response code %x\n",
6342ab7e1ecSJoe Eykholt 			    ntohs(cp->ct_cmd));
6352ab7e1ecSJoe Eykholt redisc:
6362ab7e1ecSJoe Eykholt 		fc_disc_restart(disc);
6372ab7e1ecSJoe Eykholt 	}
6382ab7e1ecSJoe Eykholt out:
6392ab7e1ecSJoe Eykholt 	mutex_unlock(&disc->disc_mutex);
6402ab7e1ecSJoe Eykholt 	kref_put(&rdata->kref, lport->tt.rport_destroy);
6412ab7e1ecSJoe Eykholt }
6422ab7e1ecSJoe Eykholt 
6432ab7e1ecSJoe Eykholt /**
6442ab7e1ecSJoe Eykholt  * fc_disc_gpn_id_req() - Send Get Port Names by ID (GPN_ID) request
6453a3b42bfSRobert Love  * @lport: The local port to initiate discovery on
6462ab7e1ecSJoe Eykholt  * @rdata: remote port private data
6472ab7e1ecSJoe Eykholt  *
6482ab7e1ecSJoe Eykholt  * Locking Note: This function expects that the disc_mutex is locked
6492ab7e1ecSJoe Eykholt  *		 before it is called.
6502ab7e1ecSJoe Eykholt  * On failure, an error code is returned.
6512ab7e1ecSJoe Eykholt  */
6522ab7e1ecSJoe Eykholt static int fc_disc_gpn_id_req(struct fc_lport *lport,
6532ab7e1ecSJoe Eykholt 			      struct fc_rport_priv *rdata)
6542ab7e1ecSJoe Eykholt {
6552ab7e1ecSJoe Eykholt 	struct fc_frame *fp;
6562ab7e1ecSJoe Eykholt 
6572ab7e1ecSJoe Eykholt 	fp = fc_frame_alloc(lport, sizeof(struct fc_ct_hdr) +
6582ab7e1ecSJoe Eykholt 			    sizeof(struct fc_ns_fid));
6592ab7e1ecSJoe Eykholt 	if (!fp)
6602ab7e1ecSJoe Eykholt 		return -ENOMEM;
6612ab7e1ecSJoe Eykholt 	if (!lport->tt.elsct_send(lport, rdata->ids.port_id, fp, FC_NS_GPN_ID,
662b94f8951SJoe Eykholt 				  fc_disc_gpn_id_resp, rdata,
663b94f8951SJoe Eykholt 				  3 * lport->r_a_tov))
6642ab7e1ecSJoe Eykholt 		return -ENOMEM;
6652ab7e1ecSJoe Eykholt 	kref_get(&rdata->kref);
6662ab7e1ecSJoe Eykholt 	return 0;
6672ab7e1ecSJoe Eykholt }
6682ab7e1ecSJoe Eykholt 
6692ab7e1ecSJoe Eykholt /**
67034f42a07SRobert Love  * fc_disc_single() - Discover the directory information for a single target
6713a3b42bfSRobert Love  * @lport: The local port the remote port is associated with
67242e9a92fSRobert Love  * @dp:	   The port to rediscover
67342e9a92fSRobert Love  *
67442e9a92fSRobert Love  * Locking Note: This function expects that the disc_mutex is locked
67542e9a92fSRobert Love  *		 before it is called.
67642e9a92fSRobert Love  */
6772ab7e1ecSJoe Eykholt static int fc_disc_single(struct fc_lport *lport, struct fc_disc_port *dp)
67842e9a92fSRobert Love {
679ab28f1fdSJoe Eykholt 	struct fc_rport_priv *rdata;
68042e9a92fSRobert Love 
6819737e6a7SRobert Love 	rdata = lport->tt.rport_create(lport, dp->port_id);
6822ab7e1ecSJoe Eykholt 	if (!rdata)
6832ab7e1ecSJoe Eykholt 		return -ENOMEM;
6842ab7e1ecSJoe Eykholt 	rdata->disc_id = 0;
6852ab7e1ecSJoe Eykholt 	return fc_disc_gpn_id_req(lport, rdata);
68642e9a92fSRobert Love }
68742e9a92fSRobert Love 
68842e9a92fSRobert Love /**
68934f42a07SRobert Love  * fc_disc_stop() - Stop discovery for a given lport
6903a3b42bfSRobert Love  * @lport: The local port that discovery should stop on
69142e9a92fSRobert Love  */
692c6b21c93SBart Van Assche static void fc_disc_stop(struct fc_lport *lport)
69342e9a92fSRobert Love {
69442e9a92fSRobert Love 	struct fc_disc *disc = &lport->disc;
69542e9a92fSRobert Love 
696c531b9b4SBhanu Prakash Gollapudi 	if (disc->pending)
69742e9a92fSRobert Love 		cancel_delayed_work_sync(&disc->disc_work);
69842e9a92fSRobert Love 	fc_disc_stop_rports(disc);
69942e9a92fSRobert Love }
70042e9a92fSRobert Love 
70142e9a92fSRobert Love /**
70234f42a07SRobert Love  * fc_disc_stop_final() - Stop discovery for a given lport
7033a3b42bfSRobert Love  * @lport: The lport that discovery should stop on
70442e9a92fSRobert Love  *
70542e9a92fSRobert Love  * This function will block until discovery has been
70642e9a92fSRobert Love  * completely stopped and all rports have been deleted.
70742e9a92fSRobert Love  */
708c6b21c93SBart Van Assche static void fc_disc_stop_final(struct fc_lport *lport)
70942e9a92fSRobert Love {
71042e9a92fSRobert Love 	fc_disc_stop(lport);
71142e9a92fSRobert Love 	lport->tt.rport_flush_queue();
71242e9a92fSRobert Love }
71342e9a92fSRobert Love 
71442e9a92fSRobert Love /**
7153a3b42bfSRobert Love  * fc_disc_init() - Initialize the discovery layer for a local port
7163a3b42bfSRobert Love  * @lport: The local port that needs the discovery layer to be initialized
71742e9a92fSRobert Love  */
71842e9a92fSRobert Love int fc_disc_init(struct fc_lport *lport)
71942e9a92fSRobert Love {
72042e9a92fSRobert Love 	struct fc_disc *disc;
72142e9a92fSRobert Love 
72242e9a92fSRobert Love 	if (!lport->tt.disc_start)
72342e9a92fSRobert Love 		lport->tt.disc_start = fc_disc_start;
72442e9a92fSRobert Love 
72542e9a92fSRobert Love 	if (!lport->tt.disc_stop)
72642e9a92fSRobert Love 		lport->tt.disc_stop = fc_disc_stop;
72742e9a92fSRobert Love 
72842e9a92fSRobert Love 	if (!lport->tt.disc_stop_final)
72942e9a92fSRobert Love 		lport->tt.disc_stop_final = fc_disc_stop_final;
73042e9a92fSRobert Love 
73142e9a92fSRobert Love 	if (!lport->tt.disc_recv_req)
73242e9a92fSRobert Love 		lport->tt.disc_recv_req = fc_disc_recv_req;
73342e9a92fSRobert Love 
73442e9a92fSRobert Love 	disc = &lport->disc;
73542e9a92fSRobert Love 	INIT_DELAYED_WORK(&disc->disc_work, fc_disc_timeout);
73642e9a92fSRobert Love 	mutex_init(&disc->disc_mutex);
73742e9a92fSRobert Love 	INIT_LIST_HEAD(&disc->rports);
73842e9a92fSRobert Love 
7390685230cSJoe Eykholt 	disc->priv = lport;
74042e9a92fSRobert Love 
74142e9a92fSRobert Love 	return 0;
74242e9a92fSRobert Love }
74342e9a92fSRobert Love EXPORT_SYMBOL(fc_disc_init);
744